From 4dd8d4b46e00e167ed526f9672ee885b0a08ab53 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 28 Apr 2026 19:48:12 +0200 Subject: [PATCH] fix(anim): Phase L.1c seed move-to locomotion Retail MoveToManager::BeginMoveForward calls MovementParameters::get_command (0x0052AA00) and then _DoMotion/adjust_motion, so a server-controlled MoveTo begins visible forward locomotion before the next UpdatePosition echo. Seed RunForward for MoveTo packets that omit ForwardCommand, while preserving active locomotion and letting position velocity refine walk/run/stop. --- src/AcDream.App/Rendering/GameWindow.cs | 26 ++++++++++++++----- .../Physics/ServerControlledLocomotion.cs | 7 +++++ .../ServerControlledLocomotionTests.cs | 10 +++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 329abd8..0142fe6 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -2273,16 +2273,30 @@ public sealed class GameWindow : IDisposable // command == null → retail stop signal → Ready // command.Value == 0 → explicit 0 (rare) → Ready // otherwise → resolve class byte and use full cmd + float speedMod = update.MotionState.ForwardSpeed ?? 1f; uint fullMotion; if ((!command.HasValue || command.Value == 0) && update.MotionState.IsServerControlledMoveTo) { - // MoveTo packets preserve the current cycle until velocity - // chooses the visible walk/run/ready state. uint current = ae.Sequencer.CurrentMotion; - fullMotion = (current & 0xFF000000u) != 0 - ? current - : AcDream.Core.Physics.MotionCommand.Ready; + if (IsRemoteLocomotion(current)) + { + // MoveTo packets preserve an active locomotion cycle; + // position velocity will refine the speed. + fullMotion = current; + } + else + { + // Retail MoveToManager::BeginMoveForward calls + // MovementParameters::get_command (0x0052AA00), then + // _DoMotion -> adjust_motion. With default CanRun and + // enough distance, WalkForward + HoldKey_Run becomes + // RunForward immediately, before the next position echo. + var seed = AcDream.Core.Physics.ServerControlledLocomotion + .PlanMoveToStart(); + fullMotion = seed.Motion; + speedMod = seed.SpeedMod; + } } else if (!command.HasValue || command.Value == 0) { @@ -2313,8 +2327,6 @@ public sealed class GameWindow : IDisposable // apply_run_to_command). Treating zero as "unspecified / 1.0" // produces "slow walk that never stops" — exactly what the // stop bug looked like. - float speedMod = update.MotionState.ForwardSpeed ?? 1f; - if (Environment.GetEnvironmentVariable("ACDREAM_DUMP_MOTION") == "1" && update.Guid != _playerServerGuid) Console.WriteLine( diff --git a/src/AcDream.Core/Physics/ServerControlledLocomotion.cs b/src/AcDream.Core/Physics/ServerControlledLocomotion.cs index 992a597..ce59998 100644 --- a/src/AcDream.Core/Physics/ServerControlledLocomotion.cs +++ b/src/AcDream.Core/Physics/ServerControlledLocomotion.cs @@ -32,6 +32,13 @@ public static class ServerControlledLocomotion public const float MinSpeedMod = 0.25f; public const float MaxSpeedMod = 3.00f; + // Retail MoveToManager::BeginMoveForward -> MovementParameters::get_command + // (0x0052AA00) seeds forward motion before the next position update. + public static LocomotionCycle PlanMoveToStart() + { + return new LocomotionCycle(MotionCommand.RunForward, 1f, true); + } + public static LocomotionCycle PlanFromVelocity(Vector3 worldVelocity) { float horizontalSpeed = MathF.Sqrt( diff --git a/tests/AcDream.Core.Tests/Physics/ServerControlledLocomotionTests.cs b/tests/AcDream.Core.Tests/Physics/ServerControlledLocomotionTests.cs index 78f3f0e..448f1f8 100644 --- a/tests/AcDream.Core.Tests/Physics/ServerControlledLocomotionTests.cs +++ b/tests/AcDream.Core.Tests/Physics/ServerControlledLocomotionTests.cs @@ -6,6 +6,16 @@ namespace AcDream.Core.Tests.Physics; public sealed class ServerControlledLocomotionTests { + [Fact] + public void PlanMoveToStart_SeedsImmediateRunCycle() + { + var plan = ServerControlledLocomotion.PlanMoveToStart(); + + Assert.True(plan.IsMoving); + Assert.Equal(MotionCommand.RunForward, plan.Motion); + Assert.Equal(1.0f, plan.SpeedMod); + } + [Fact] public void PlanFromVelocity_StopsBelowRetailNoiseThreshold() {