From e9e080db8c7ab5dbf2b5a1772f13e006eb0cd8b2 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 6 May 2026 08:58:41 +0200 Subject: [PATCH] =?UTF-8?q?fix(motion):=20close=20#45=20=E2=80=94=20scale?= =?UTF-8?q?=20local=20sidestep=20speedMod=20by=20ACE's=20wire=20factor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User observed (during fix #5 visual verify of #39): "our own Acdream client renders sidestep walking too slow". Filed as #45. Root cause: PlayerMovementController.cs:871 computes localAnimSpeed as the raw `runRate || 1.0`, while ACE's BroadcastMovement converts inbound MoveToState SidestepSpeed via speed × 3.12 / 1.25 × 0.5 (Network/Motion/MovementData.cs:124-131). Observer-side cycles play at the ACE-scaled value (~1.248 slow / ~3.0 fast clamped); the local cycle was playing at the raw 1.0 / runRate — about 80% of retail cadence for slow strafe. Fix: in UpdatePlayerAnimation, when animCommand is SideStepLeft / Right (low byte 0x0F or 0x10), multiply animSpeed by WalkAnimSpeed / SidestepAnimSpeed × 0.5 = 3.12 / 1.25 × 0.5 = 1.248 before calling SetCycle. Same factor as ACE; no clamp on the local side (sequencer handles MultiplyCyclicFramerate naturally). Forward / backward / turn cycles unchanged — those use WalkAnimSpeed or RunAnimSpeed as base, where localAnimSpeed = wire ForwardSpeed already produces the right cadence. Build clean. Visual verify pending: user reports slow-strafe cadence should match retail / our own observed-remote rendering after this. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.App/Rendering/GameWindow.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 81d27ae..0bae242 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -7244,6 +7244,27 @@ public sealed class GameWindow : IDisposable // For everything else (Walk → Run, Run → Ready, etc.) we // keep the link so transitions stay smooth. bool skipLink = animCommand == AcDream.Core.Physics.MotionCommand.Falling; + + // #45 (2026-05-06): scale sidestep speedMod to match ACE's + // wire formula. PlayerMovementController hands us a raw + // localAnimSpeed (1.0 slow / runRate fast), but ACE's + // BroadcastMovement converts SidestepSpeed via + // `speed × 3.12 / 1.25 × 0.5` + // (Network/Motion/MovementData.cs:124-131). Without the + // matching multiplier here, the local sidestep cycle plays + // at speedMod = 1.0 while the observer-side cycle plays at + // ~1.248 — local strafe visibly slower than retail (user + // report at #45 close-out of #39). + // Factor = WalkAnimSpeed / SidestepAnimSpeed × 0.5 + // = 3.12 / 1.25 × 0.5 = 1.248. + uint cmdLow = animCommand & 0xFFu; + if (cmdLow == 0x0Fu /* SideStepRight */ || cmdLow == 0x10u /* SideStepLeft */) + { + animSpeed *= AcDream.Core.Physics.MotionInterpreter.WalkAnimSpeed + / AcDream.Core.Physics.MotionInterpreter.SidestepAnimSpeed + * 0.5f; + } + ae.Sequencer.SetCycle(fullStyle, animCommand, animSpeed * animScale, skipTransitionLink: skipLink); }