fix(anim): Phase L.1c bulk-copy ForwardCommand for MoveTo packets too

User-observed regression on commit ff6d3d0: at login, monsters appear
as "just a torso on the ground" until they start moving.

Cause: f794832 lifted the InterpretedState bulk-copy to apply to BOTH
overlay and substate packets, but gated it on
`!IsServerControlledMoveTo`. The original substate-only
DoInterpretedMotion call I removed had previously updated
InterpretedState for MoveTo packets too (fullMotion=RunForward seed
from PlanMoveToStart routed through ApplyMotionToInterpretedState's
RunForward case). My replacement only fired for non-MoveTo packets,
silently regressing MoveTo creatures to a default
ForwardCommand=Ready InterpretedState.

Consequence: chasing creatures had ForwardCommand=Ready in their
InterpretedState even though the cycle on the sequencer was
RunForward. apply_current_movement (gate: RunForward||WalkForward)
returned zero velocity — body never advanced via the steering branch's
velocity integration. The body ONLY translated when an UpdatePosition
hard-snap arrived (every ~200ms server tick), producing the
"torso on the ground at spawn" pose before the first UP snap landed
and "running on the spot" between snaps.

Fix: drop the IsServerControlledMoveTo gate. Bulk-copy
InterpretedState.ForwardCommand=fullMotion and ForwardSpeed=speedMod
UNCONDITIONALLY for any packet that reaches OnLiveMotionUpdated.
Matches retail's copy_movement_from
(acclient_2013_pseudo_c.txt:293301-293311) which doesn't filter by
movement type — for MoveTo, RunForward/speed*runRate; for substate,
the wire's command/speed; for overlay, Attack/animSpeed (and
get_state_velocity gates correctly to zero, the desired stop).

Tests still 1420 green — the existing parser/driver tests cover the
data; this is a code-path completeness fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-29 10:25:37 +02:00
parent ff6d3d0c94
commit 37de771778

View file

@ -2465,6 +2465,32 @@ public sealed class GameWindow : IDisposable
{ {
remoteMot.ServerMoveToActive = update.MotionState.IsServerControlledMoveTo; remoteMot.ServerMoveToActive = update.MotionState.IsServerControlledMoveTo;
// Bulk-copy the wire's resolved ForwardCommand + speed
// into InterpretedState UNCONDITIONALLY (overlay,
// substate, AND MoveTo packets). Matches retail's
// copy_movement_from semantics
// (acclient_2013_pseudo_c.txt:293301-293311) which does
// not filter by MovementType.
//
// For MoveTo packets, fullMotion is the RunForward seed
// from PlanMoveToStart, so this populates
// ForwardCommand=RunForward + ForwardSpeed=speed*runRate
// — what the OLD substate-only DoInterpretedMotion call
// (commit f794832 removed) used to set. Without it,
// apply_current_movement reads the default
// ForwardCommand=Ready and produces zero velocity, so
// chasing creatures only translate via UpdatePosition
// hard-snaps and at spawn appear posed at default
// (visible as "torso on the ground" until the first UP
// snap hits).
//
// For overlay (Action) packets this sets ForwardCommand
// to the Attack/Twitch/etc command, and
// get_state_velocity returns 0 because the gate is
// RunForward||WalkForward — body stops moving forward.
remoteMot.Motion.InterpretedState.ForwardCommand = fullMotion;
remoteMot.Motion.InterpretedState.ForwardSpeed = speedMod <= 0f ? 1f : speedMod;
if (update.MotionState.IsServerControlledMoveTo if (update.MotionState.IsServerControlledMoveTo
&& update.MotionState.MoveToPath is { } path) && update.MotionState.MoveToPath is { } path)
{ {
@ -2488,15 +2514,6 @@ public sealed class GameWindow : IDisposable
// Off MoveTo — clear stale destination so the per-tick // Off MoveTo — clear stale destination so the per-tick
// driver doesn't keep steering. // driver doesn't keep steering.
remoteMot.HasMoveToDestination = false; remoteMot.HasMoveToDestination = false;
// Bulk-copy the wire's resolved ForwardCommand + speed
// into InterpretedState. For Action commands this
// makes apply_current_movement return zero velocity
// on the next tick (gate fails). For substate
// commands (Run/Walk/Ready), this is identical to
// what DoInterpretedMotion would have written.
remoteMot.Motion.InterpretedState.ForwardCommand = fullMotion;
remoteMot.Motion.InterpretedState.ForwardSpeed = speedMod <= 0f ? 1f : speedMod;
} }
} }