From 2653b307c70e5779d149d540cf859473e85bb687 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 6 May 2026 08:25:10 +0200 Subject: [PATCH] =?UTF-8?q?fix(motion):=20#39=20=E2=80=94=20wire=20ApplySe?= =?UTF-8?q?rverControlledVelocityCycle=20into=20player-remote=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Visual verify with the proper Shift-toggle scenario revealed that fix #1's ApplyPlayerLocomotionRefinement was UNREACHABLE for player remotes — the L.3 M2 routing at line 3626+ returns at line 3755, BEFORE the call site at line 3879. The legacy NPC-only block that compute server velocity + calls ApplyServerControlledVelocityCycle never runs for players. [UPCYCLE_PLAYER] count = 0 in launch-39-fix2.log and launch-39-diag2.log proved the velocity-fallback path was completely dead code for players. Wire-level evidence (launch-39-diag2.log): - [FWD_WIRE] for retail actor 0x50000001 over a clean Hold-W-press-Shift- release-Shift-release-W test shows ONLY Ready→Run and Run→Ready transitions. NO Walk wire transitions for the Shift toggle. So retail's outbound MoveToState logic does NOT emit a fresh packet on HoldKey-only changes (refutes the launch-39-fix2 hypothesis that both directions emit; the earlier fix2 log's many Walk↔Run transitions came from W press/release cycles WITH Shift held continuously, not from Shift toggling alone). - [VEL_DIAG] over the same test shows clear walk-pace (~2.5 m/s) and run-pace (~11.5 m/s) periods, so the actor's actual physical speed IS changing despite the wire silence. Fix: in OnLivePositionUpdated's L.3 M2 player-remote block, after the near-enqueue / far-snap routing and before the early `return`, compute synth velocity from PrevServerPos / LastServerPos and call into ApplyServerControlledVelocityCycle. The function's internal routing (commit 8fa04af) sends player remotes through ApplyPlayerLocomotionRefinement which has the 500 ms UM grace + forward-direction + hysteresis logic to flip Run↔Walk only when no fresh UM is authoritative. Build clean. Diagnostics: [UPCYCLE_SRC] now prints `src=synth-player` when the player-remote path fires (distinct from `src=synth`/`src=wire` in the legacy NPC path). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.App/Rendering/GameWindow.cs | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 12828de..4d7e278 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -3745,6 +3745,51 @@ public sealed class GameWindow : IDisposable isMovingTo: false, currentBodyPosition: rmState.Body.Position); } + // #39 fix-3 (2026-05-06): velocity-fallback cycle refinement + // for player remotes. Wire-level evidence (`launch-39-diag2.log`): + // when retail's actor toggles Shift while a direction key + // is held, retail's outbound MoveToState logic does NOT + // emit a fresh packet (only Ready ↔ Run UMs visible in + // `[FWD_WIRE]`, despite a clear walk-pace ↔ run-pace + // velocity transition in `[VEL_DIAG]`). ACE has nothing + // to broadcast → no UM arrives at the observer → cycle + // sticks at whatever the last UM set. Compute the + // synth-velocity here in the player-remote path AND + // call into ApplyServerControlledVelocityCycle, which + // routes through the direction-preserving + UM-grace + // ApplyPlayerLocomotionRefinement helper (added in + // commit 8fa04af). + // + // The legacy non-player block below (3759+) covers NPCs + // and is gated `!IsPlayerGuid`; this block fills the + // matching gap for players. + if (rmState.PrevServerPosTime > 0.0) + { + double nowSecVel = rmState.LastServerPosTime; + double dtPos = nowSecVel - rmState.PrevServerPosTime; + if (dtPos > 0.001) + { + var synthVel = (worldPos - rmState.PrevServerPos) / (float)dtPos; + rmState.ServerVelocity = synthVel; + rmState.HasServerVelocity = true; + + if (_animatedEntities.TryGetValue(entity.Id, out var aeForVel) + && aeForVel.Sequencer is not null) + { + if (System.Environment.GetEnvironmentVariable("ACDREAM_REMOTE_VEL_DIAG") == "1") + { + System.Console.WriteLine( + $"[UPCYCLE_SRC] guid={update.Guid:X8} src=synth-player"); + } + ApplyServerControlledVelocityCycle( + update.Guid, + aeForVel, + rmState, + synthVel); + } + } + } + // Sync the visible entity to the body — overrides the unconditional // entity.Position = worldPos snap at the top of this function. // For the far-snap branch this is a no-op (body == worldPos); for