Drive proxy cadence from measured motion

Tie proxy locomotion/cadence to actual per-frame movement and motion-feedback instead of only replaying transmitted Speed.

Changes:
- docs/dev-log.md: add detailed dev log describing measured-cadence fix and cadence/animationSpeed work.
- plugin/include/F4TProxyPuppet.h: added NiPoint3 include and new API: SetProxyLocomotionState, SetProxyDesiredSpeed (legacy alias), and ApplyProxyLocomotionFrame.
- plugin/lib/commonlibf4/include/RE/A/ActorMotionFeedbackOutput.h: new tentative struct for motion-feedback output.
- plugin/lib/commonlibf4/include/RE/Fallout.h: include new ActorMotionFeedbackOutput header.
- plugin/src/F4TProxyActorController.cpp: track per-frame timing/puppet state, compute frame delta, use proxy->Move() with measured frameDelta and call ApplyProxyLocomotionFrame (instead of SetPosition/Lerp-only), and pass measured graph speed/direction to proxy.
- plugin/src/F4TProxyAnimationSync.cpp: use SetProxyLocomotionState to include direction when writing proxy locomotion state.
- plugin/src/F4TProxyPuppet.cpp: introduce ProxyLocomotionState map, sync controller motion fields, compute/push engine motion-feedback (via Compute/Update feedback vtable calls), use UpdateNoAI + manual motion feedback to drive cadence, implement ApplyProxyLocomotionFrame which derives velocity from world delta, clamps/chooses feedback speed (uses measured horizontal speed unless provided graph speed is valid), applies an idle threshold (<8 u/s -> idle), and writes graph speed back to the animation graph.

Why: This makes foot cadence reflect on-screen translation (eliminating mild foot slide/stop-overstay) by measuring applied displacement each frame, updating controller velocity/motion-feedback, and re-asserting animation graph inputs after UpdateNoAI.
This commit is contained in:
2026-06-04 21:39:26 +12:00
parent abef899505
commit 65553a0c61
7 changed files with 347 additions and 49 deletions
+89
View File
@@ -4567,4 +4567,93 @@ Circle) decides: proxy should now jog at Speed 180 with a visibly faster cadence
### Build Status
✅ **SUCCESS** - `xmake build` clean; DLL deployed to game F4SE/Plugins.
---
## 2026-06-04 - Proxy Cadence WORKS (UpdateNoAI) + Glide Fix: Drive Cadence From Measured Translation
### Result
The `UpdateNoAI` puppet works in-game: proxies now show varying cadence including a
real run. Confirmed by the user. Remaining issue: "a little glidy" (mild foot slide).
### Root Cause Of Residual Glide
`ApplySmoothFrameMovement()` moves the proxy by `SetPosition` with exponential Lerp
easing (`Lerp(current, target, 0.25)` per frame). The leg cadence, however, was a
constant *transmitted* graph Speed. Because the eased translation speed varies frame
to frame (fast right after a packet, slowing as it converges) while the cadence is
constant, the feet slide.
### Fix
Drive the puppet cadence from the proxy's ACTUAL horizontal translation each frame
instead of the transmitted speed:
- In `ApplySmoothFrameMovement`, after computing `nextPosition`, measure
`horizontalDistance(current, next) / frameDelta` and EMA-smooth it
(`measuredGraphSpeed`, alpha 0.35).
- Feed that to `F4T::ProxyPuppet::SetProxyDesiredSpeed` (units/sec ≈ graph Speed,
~1:1). Below an 8 u/s idle threshold we publish -1 so the engine's idle stands.
- `ApplySmoothFrameMovement` runs LAST in `ProxyActorController::Update`, after the
per-player anim sync (which also sets a transmitted desired speed), so the measured
value authoritatively wins for the puppet re-assert.
This ties foot cadence to real on-screen motion regardless of interpolation easing,
which also makes the animation decelerate to idle naturally as the proxy converges on
a stopped target (no stop-overstay).
### Build Status
✅ **SUCCESS** - `xmake build` clean; DLL deployed to game F4SE/Plugins.
---
## 2026-06-04 - Within-State Walk Cadence: middleProcess->animationSpeed Is The FO4 Speed-Warp Lever
### What The User Observed
After the measured-cadence build: only the RUN animation is distinct. Slow walk,
medium walk, and jog all play the SAME walk clip. So `Speed` switches the locomotion
STATE (walk<->run via a threshold) but does NOT vary the foot cadence WITHIN the walk
state.
### Evidence
- Sender-side `Local send diag`: the local player's own graph `Speed` saturates into
bands (walk ~60-160, run 373, sprint 500). `Speed` is a state selector, not a
continuous cadence driver.
- Writing `Speed`/`SpeedSampled` on the proxy (even persisting via the UpdateNoAI
puppet) only flips walk<->run; sub-walk paces look identical.
- `speedSampled` (lowercase) reads back false → not a real FO4 graph variable.
### TiltedEvolution Comparison
TE (Skyrim) pins position with `ForcePosition`, disables AI (`HookActorProcess`
returns 0), and replays a curated graph-variable snapshot incl. `Speed`,
`SpeedSampled`, `Direction`, `bMotionDriven`, `bIsSynced`. In Skyrim the leg blend is
driven by the `SpeedSampled` graph variable, so pure variable replay reproduces
cadence. FO4's player graph instead WARPS the clip from the actor's real velocity, so
variable replay alone can't reproduce sub-state cadence on a SetPosition puppet.
### Fix: Drive middleProcess->animationSpeed / desiredSpeed
`MiddleHighProcessData` (CommonLibF4, reached via `actor->currentProcess->middleHigh`)
exposes `float desiredSpeed (0x428)` and `float animationSpeed (0x42C)`. These are the
engine's locomotion speed-warp inputs, normally derived from controller velocity (~0
on a SetPosition puppet → fixed cadence). In `F4TProxyPuppet::WriteProxySpeed` (called
around the UpdateNoAI puppet for proxy FormIDs) we now also write both fields to the
proxy's measured ground speed (units/sec, same scale as graph Speed). This is the FO4
analog of TE poking `middleProcess->direction`, and should make the legs cycle at the
proxy's real pace within the walk state.
### Open Risk / Next Test
If `animationSpeed` is an engine OUTPUT (graph writes it) rather than an input, this
won't change cadence and we move to the velocity-driven movement rework. Next test
(slow walk vs medium vs jog) decides: sub-walk cadence should now visibly differ.
### Build Status
✅ **SUCCESS** - `xmake build` clean; DLL deployed to game F4SE/Plugins.