diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 6d0c297..bc6e49f 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -376,6 +376,7 @@ public sealed class GameWindow : IDisposable public System.Numerics.Vector3 PrevServerPos; public double PrevServerPosTime; public double LastVelDiagLogTime; + public double LastOmegaDiagLogTime; public RemoteMotion() { @@ -2862,7 +2863,17 @@ public sealed class GameWindow : IDisposable .ReconstructFullCommand(turnForAnim); if (turnFullForAnim == 0) turnFullForAnim = 0x65000000u | turnForAnim; animCycle = turnFullForAnim; - animSpeed = MathF.Abs(update.MotionState.TurnSpeed ?? 1f); + // SIGNED — do NOT MathF.Abs. ACE encodes TurnLeft on the + // wire as (TurnCommand=TurnRight, TurnSpeed=NEGATIVE), + // mirroring retail's adjust_motion convention. The + // sequencer's negative-speed path (EnqueueMotionData + // multiplies MotionData.Omega by speedMod, the + // synthesize-omega fallback flips zomega via + // -(pi/2)*adjustedSpeed) only produces the correct + // CCW rotation when the sign is preserved here. + // Confirmed by live wire trace 2026-05-03: TurnLeft + // input arrives as turnCmd16=0x000D, speed=-1.500. + animSpeed = update.MotionState.TurnSpeed ?? 1f; } } // K-fix17 (2026-04-26): preserve the Falling cycle while @@ -2993,6 +3004,14 @@ public sealed class GameWindow : IDisposable .ReconstructFullCommand(turnCmd16); if (turnFull == 0) turnFull = 0x65000000u | turnCmd16; float turnSpd = update.MotionState.TurnSpeed ?? 1f; + if (System.Environment.GetEnvironmentVariable("ACDREAM_REMOTE_VEL_DIAG") == "1") + { + System.Console.WriteLine( + $"[TURN_WIRE] guid={update.Guid:X8} turnCmd16=0x{turnCmd16:X4} " + + $"turnFull=0x{turnFull:X8} low=0x{turnFull & 0xFFu:X2} " + + $"({(((turnFull & 0xFFu) == 0x0D) ? "TurnRight" : ((turnFull & 0xFFu) == 0x0E) ? "TurnLeft" : "OTHER")}) " + + $"speed={turnSpd:F3}"); + } remoteMot.Motion.DoInterpretedMotion( turnFull, turnSpd, modifyInterpretedState: true); // Seed ObservedOmega with formula so rotation starts @@ -5874,6 +5893,23 @@ public sealed class GameWindow : IDisposable var rot = System.Numerics.Quaternion.CreateFromAxisAngle(axis, angleDelta); rm.Body.Orientation = System.Numerics.Quaternion.Normalize( System.Numerics.Quaternion.Concatenate(rm.Body.Orientation, rot)); + + // Diagnostic (ACDREAM_REMOTE_VEL_DIAG=1): print seqOmega direction + // once per remote per ~1 second so we can confirm whether the omega + // sign actually being applied matches the retail-observed turn + // direction. Z>0 = CCW (TurnLeft); Z<0 = CW (TurnRight). + if (System.Environment.GetEnvironmentVariable("ACDREAM_REMOTE_VEL_DIAG") == "1") + { + double nowSec = (System.DateTime.UtcNow - System.DateTime.UnixEpoch).TotalSeconds; + if (nowSec - rm.LastOmegaDiagLogTime > 0.5) + { + uint seqMotion = ae.Sequencer?.CurrentMotion ?? 0; + System.Console.WriteLine( + $"[OMEGA_DIAG] guid={serverGuid:X8} motion=0x{seqMotion:X8} " + + $"seqOmega.Z={seqOmega.Z:F3} (Z>0=CCW=TurnLeft, Z<0=CW=TurnRight)"); + rm.LastOmegaDiagLogTime = nowSec; + } + } } // Step 3: calc_acceleration sets body.Acceleration from the Gravity flag