From 37de7717788e2419c302bed5eb1d86e0205064aa Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 29 Apr 2026 10:25:37 +0200 Subject: [PATCH] fix(anim): Phase L.1c bulk-copy ForwardCommand for MoveTo packets too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/AcDream.App/Rendering/GameWindow.cs | 35 ++++++++++++++++++------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 9ce420c..542359a 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -2465,6 +2465,32 @@ public sealed class GameWindow : IDisposable { 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 && update.MotionState.MoveToPath is { } path) { @@ -2488,15 +2514,6 @@ public sealed class GameWindow : IDisposable // Off MoveTo — clear stale destination so the per-tick // driver doesn't keep steering. 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; } }