diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 669f599..b304d37 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -2180,7 +2180,22 @@ public sealed class GameWindow : IDisposable animSpeed = MathF.Abs(update.MotionState.TurnSpeed ?? 1f); } } - ae.Sequencer.SetCycle(fullStyle, animCycle, animSpeed); + // K-fix17 (2026-04-26): preserve the Falling cycle while + // the remote is airborne. ACE broadcasts UpdateMotion + // mid-arc when the player turns / runs — the previous + // SetCycle here swapped Falling → RunForward, breaking + // the visible jump animation. The arc still played out + // physics-wise (body went up/down), but the legs ran + // instead of folded. Skip the cycle swap when airborne; + // the InterpretedState updates below still fire so the + // body's velocity matches the new motion command, AND + // the post-resolve landing path restores the cycle to + // whatever the interpreted state says when the body + // lands. + bool remoteIsAirborne = _remoteDeadReckon.TryGetValue(update.Guid, out var rmCheck) + && rmCheck.Airborne; + if (!remoteIsAirborne) + ae.Sequencer.SetCycle(fullStyle, animCycle, animSpeed); // Retail runs the full MotionInterp state machine on every // remote. Route each wire command (forward, sidestep, turn) @@ -4755,6 +4770,35 @@ public sealed class GameWindow : IDisposable rm.Body.Velocity = new System.Numerics.Vector3( rm.Body.Velocity.X, rm.Body.Velocity.Y, 0f); rm.Motion.HitGround(); + + // K-fix17 (2026-04-26): reset the sequencer cycle + // from Falling back to whatever the interpreted + // motion state says they should be doing now. + // Without this, the remote stays in the Falling + // pose forever (legs folded) until the next + // server-sent UpdateMotion arrives. Use the + // sequencer's current style (preserved across + // jump) and pick the cycle from + // InterpretedState.ForwardCommand: Ready + // (idle), WalkForward, RunForward, WalkBackward. + // SideStep / Turn aren't strict locomotion + // priorities — the next UM the server sends will + // refine the cycle if the player is mid-strafe + // when they land; this just gets the legs out + // of Falling immediately. + if (ae.Sequencer is not null) + { + uint style = ae.Sequencer.CurrentStyle != 0 + ? ae.Sequencer.CurrentStyle + : 0x8000003Du; + uint landingCmd = rm.Motion.InterpretedState.ForwardCommand; + if (landingCmd == 0) + landingCmd = AcDream.Core.Physics.MotionCommand.Ready; + float landingSpeed = rm.Motion.InterpretedState.ForwardSpeed; + if (landingSpeed <= 0f) landingSpeed = 1f; + ae.Sequencer.SetCycle(style, landingCmd, landingSpeed); + } + if (Environment.GetEnvironmentVariable("ACDREAM_DUMP_MOTION") == "1") Console.WriteLine($"VU.land guid=0x{serverGuid:X8} Z={rm.Body.Position.Z:F2}"); }