From 882a07cfde08da3ce09a3fc9730b7b83b90491f4 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 28 Apr 2026 21:12:03 +0200 Subject: [PATCH] fix(anim): Phase L.1c anchor monster MoveTo prediction Keep the retail MoveTo speed/runRate parsing from 9812965 for animation playback, but do not use the partial MoveTo state as a body-position solver. Until the full retail MoveToManager target path is ported, retain UpdatePosition-derived velocity for server-controlled creature position and prevent that velocity from clobbering the packet-derived animation cycle speed. --- src/AcDream.App/Rendering/GameWindow.cs | 30 ++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 333b948..353f8b4 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -226,8 +226,9 @@ public sealed class GameWindow : IDisposable /// /// True while a server MoveToObject/MoveToPosition packet is the /// active locomotion source. Retail runs these through MoveToManager - /// and CMotionInterp using the packet's runRate; deriving velocity - /// from sparse UpdatePosition deltas under-speeds combat chases. + /// and CMotionInterp using the packet's runRate; until we port the + /// full target solver, use this only to protect packet-derived + /// animation speed from velocity-cycle clobbering. /// public bool ServerMoveToActive; /// @@ -2452,16 +2453,6 @@ public sealed class GameWindow : IDisposable if (_remoteDeadReckon.TryGetValue(update.Guid, out var remoteMot)) { remoteMot.ServerMoveToActive = update.MotionState.IsServerControlledMoveTo; - if (remoteMot.ServerMoveToActive && !IsPlayerGuid(update.Guid)) - { - // Retail MoveTo packets already carry enough state - // for CMotionInterp to drive velocity. A velocity - // inferred from sparse UpdatePosition packets lags - // during combat chases and visibly under-speeds the - // run cycle until the next hard snap. - remoteMot.HasServerVelocity = false; - remoteMot.ServerVelocity = System.Numerics.Vector3.Zero; - } // Forward axis (Ready / WalkForward / RunForward / WalkBackward). remoteMot.Motion.DoInterpretedMotion( @@ -2685,6 +2676,11 @@ public sealed class GameWindow : IDisposable if (IsPlayerGuid(serverGuid)) return; if (rm.Airborne) return; if (ae.Sequencer is null) return; + // MoveTo packets already seeded the retail speed/runRate cycle. + // Keep UpdatePosition-derived velocity for render position only; + // using it to choose the cycle reverts fast chases back to slow + // velocity-estimated animation. + if (rm.ServerMoveToActive) return; var plan = AcDream.Core.Physics.ServerControlledLocomotion .PlanFromVelocity(velocity); @@ -2771,7 +2767,6 @@ public sealed class GameWindow : IDisposable System.Numerics.Vector3? serverVelocity = update.Velocity; if (serverVelocity is null && !IsPlayerGuid(update.Guid) - && !rmState.ServerMoveToActive && rmState.LastServerPosTime > 0.0) { double elapsed = nowSec - rmState.LastServerPosTime; @@ -5047,6 +5042,15 @@ public sealed class GameWindow : IDisposable rm.Body.Velocity = rm.ServerVelocity; } } + else if (!IsPlayerGuid(serverGuid) && rm.ServerMoveToActive) + { + // We only parse enough of MoveTo to recover retail + // animation speed. Do not let apply_current_movement + // extrapolate position from an incomplete target + // solver; hold until the next UpdatePosition-derived + // velocity arrives. + rm.Body.Velocity = System.Numerics.Vector3.Zero; + } else { rm.Motion.apply_current_movement(cancelMoveTo: false, allowJump: false);