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.
This commit is contained in:
Erik 2026-04-28 19:48:12 +02:00
parent 7656fe0970
commit 4dd8d4b46e
3 changed files with 36 additions and 7 deletions

View file

@ -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(

View file

@ -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(

View file

@ -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()
{