fix(#170): refresh remote body velocity from get_state_velocity (kill the attack glide)

Root cause (confirmed by a live ACDREAM_DUMP_MOTION capture of Mite Scamp
0x80000244 + the retail decomp): a chasing+attacking creature's attacks arrive
as the ForwardCommand of frequent mt-0 InterpretedMotionState UMs (66 attack UMs
0x62/63/64 vs 2 mt-6 chase MoveTos in the capture). Retail's get_state_velocity
(0x00527d50) computes the body's translation velocity from the current forward
command: WalkForward→3.12×spd, RunForward→4.0×spd, and 0 for everything else
(an attack) — so the creature plants its feet. acdream ALREADY has a faithful
get_state_velocity (returns 0 for a non-locomotion command; cross-checked vs
holtburger grounded_local_velocity's `_ => zero`), but it was never WIRED to the
remote body for entities with an animation sink: apply_current_movement's sink
path (all remotes have a DefaultSink) dispatches the animation and early-returns
BEFORE the set_local_velocity(get_state_velocity()) write, which lives only in
the no-sink fallback (MotionInterpreter ~1702). So a remote NPC's body.Velocity
was never recomputed from its motion state and kept the STALE run velocity from
the last chase — the body dead-reckoned forward at ~4 m/s while playing an
idle+attack pose ("glides after me").

Fix: after apply_current_movement in the grounded remote-NPC dead-reckon path
(GameWindow ~9992, restricted to remotes by serverGuid != player and to grounded
by OnWalkable), refresh rm.Body via set_local_velocity(get_state_velocity()) —
the exact write retail's apply_current_movement performs, reusing the verbatim
ported get_state_velocity. An attack forward command now resolves to 0, so the
creature stops and swings in place; RunForward still yields the run velocity.
Narrowest safe seam: the local player (which also has a sink) is excluded by the
loop's player guard, so its PlayerMovementController velocity path is untouched.

The earlier suspicion (#159 combat-command numbering) was a red herring — the
scamp's 0x62/63/64 were always in the correct block and the planner is unwired;
the wire even carries full attack variety, so "uniform" was the visual artifact
of rapid attacks over a gliding idle base, not a classification bug.

Tests: MotionVelocityPipelineTests.AttackForwardCommand_ZeroVelocity (4 cases)
pins get_state_velocity → 0 for the attack forward commands the fix depends on.
Core suite 2503 green. Visual gate pending (retail side-by-side: creature should
plant + step, not glide).

Ref: docs/research/named-retail get_state_velocity 0x00527d50; ISSUES #170.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-04 09:56:40 +02:00
parent fe24289ac5
commit d2ccc80e59
2 changed files with 59 additions and 0 deletions

View file

@ -9990,6 +9990,35 @@ public sealed class GameWindow : IDisposable
// per-tick shape ordinary locomotion uses.
TickRemoteMoveTo(rm);
rm.Motion.apply_current_movement(cancelMoveTo: false, allowJump: false);
// #170 glide fix (2026-07-04): refresh the body's
// translation velocity from the current interpreted
// state, matching retail. apply_current_movement's
// SINK path (all remotes have a DefaultSink) only
// dispatches the ANIMATION — the
// get_state_velocity → set_local_velocity write
// that retail's apply_current_movement performs
// lives ONLY in acdream's no-sink fallback
// (MotionInterpreter.ApplyCurrentMovementInterpreted,
// ~1702), so a remote's body.Velocity is never
// recomputed and goes STALE. get_state_velocity
// (0x00527d50, already a verbatim port) returns
// WalkForward→3.12×spd, RunForward→4.0×spd, and 0
// for every other forward command — so when a
// chasing creature's ForwardCommand switches to an
// attack (0x1000006x, action-class), the body
// stops instead of coasting at the last run
// velocity while playing an idle+attack pose (the
// user-reported "glides after me"). Grounded only
// (this branch already asserted Contact+OnWalkable);
// airborne velocity is owned by the jump arc.
// Confirmed vs a live ACDREAM_DUMP_MOTION capture of
// Mite Scamp 0x80000244 (66 mt-0 attack UMs carrying
// ForwardCommand=0x62/63/64 vs 2 mt-6 chase UMs).
if (rm.Body.OnWalkable)
rm.Body.set_local_velocity(
rm.Motion.get_state_velocity(),
rm.Body.LastMoveWasAutonomous);
}
}
else