From 9960ce3bce1eaf961b8e127a37b586d6aa739650 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 3 May 2026 13:01:43 +0200 Subject: [PATCH] fix(motion): preserve signed TurnSpeed for remote turn animations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wire-arrival animCycle picker in OnLiveMotionUpdated was passing MathF.Abs(turnSpeed) to the sequencer, stripping the sign that ACE uses to encode TurnLeft. Confirmed via live wire trace 2026-05-03: TurnLeft input from a retail-driven character arrives as turnCmd16=0x000D (TurnRight), TurnSpeed=-1.500 — mirroring retail's adjust_motion convention on the wire. With Abs, both directions collapsed onto motion=TurnRight + speedMod=+1.5, and the synthesize- omega path computed -2.25 (CW = right) for both. Visible symptom: TurnLeft animated as TurnRight then blipped to the correct facing on the next UpdatePosition. Pass the signed speed through unchanged. The sequencer's negative- speed path (EnqueueMotionData multiplies MotionData.Omega by speedMod; the synthesize-omega fallback uses -(pi/2)*adjustedSpeed) produces the correct CCW omega for TurnLeft now that the sign survives. Also adds a TURN_WIRE diagnostic gated on ACDREAM_REMOTE_VEL_DIAG=1 that prints every wire-arrived TurnCommand with reconstructed enum and signed speed, plus splits the OMEGA_DIAG throttle off LastVelDiagLogTime onto its own LastOmegaDiagLogTime so the two diagnostics don't starve each other. Verified with the same trace: TURN_WIRE speed=-1.500 -> OMEGA_DIAG Z=+2.250 (CCW = TurnLeft), TURN_WIRE speed=+1.500 -> OMEGA_DIAG Z=-2.250 (CW = TurnRight). Both directions now have correct sign. Co-Authored-By: Claude Opus 4.7 --- src/AcDream.App/Rendering/GameWindow.cs | 38 ++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) 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