From d34721fa941a35f74534014e4fe207c5114331e2 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 2 Jul 2026 10:36:46 +0200 Subject: [PATCH] feat(D6.2b): send retail-faithful raw forward_speed=1.0 on the wire The outbound MoveToState now sends the RAW forward_speed 1.0 (omitted by default-difference packing) instead of the pre-computed runRate, matching what retail's client sends. Settled the open question with a live echo-test: acdream sends forward_speed=1.0, ACE broadcasts back RunForward @ runRate (not 1.0), and a retail observer saw +Acdream run at full pace. So ACE RECOMPUTES the broadcast run speed from the character's run skill and auto-upgrades WalkForward+HoldKey.Run -> RunForward for observers. The earlier PlayerMovementController comment citing ACE MovementData.cs ("ACE relays the speed, so the wire must send run_rate") was wrong; corrected. Changes: - PlayerMovementController section 6: forward wire command is WalkForward @ 1.0 (+ HoldKey.Run when running); LocalAnimationCommand still carries RunForward for the local cycle. Backward already 1.0. The MotionStateChanged detection now keys the walk<->run toggle off HoldKey + LocalAnimationCommand (forward_speed is constant 1.0); the forward_speed comparison is retained but never fires. - GameWindow: reverted the D6.2b echo-test one-liner; the wire RawMotionState takes forward_speed from result (now 1.0). The wire and the D6.2a local velocity are now both raw-1.0. Threading them onto one shared RawMotionState (removing section-6's duplicate build) is a behavior-neutral cleanup follow-up. Full suite green (3255). Docs: roadmap + pseudocode doc updated with the echo-test finding. Co-Authored-By: Claude Opus 4.8 --- docs/plans/2026-04-11-roadmap.md | 12 +++- .../2026-07-01-d6-motion-interp-pseudocode.md | 8 ++- .../Input/PlayerMovementController.cs | 58 ++++++++----------- src/AcDream.App/Rendering/GameWindow.cs | 4 ++ 4 files changed, 45 insertions(+), 37 deletions(-) diff --git a/docs/plans/2026-04-11-roadmap.md b/docs/plans/2026-04-11-roadmap.md index 47ead196..77befbd6 100644 --- a/docs/plans/2026-04-11-roadmap.md +++ b/docs/plans/2026-04-11-roadmap.md @@ -627,8 +627,16 @@ change must keep both phase plans in sync. lateral, turn feel unchanged; no crash/rejection). Retired register TS-22 + UN-5; added TS-34 (IsCreature guard no-op). `WalkAnimSpeed` unified to the retail-exact 3.11999989f. Pseudocode: - `docs/research/2026-07-01-d6-motion-interp-pseudocode.md`. NEXT D6.2b: the - echo-gated wire `forward_speed=1.0` question.) + `docs/research/2026-07-01-d6-motion-interp-pseudocode.md`.)* + *(D6.2b, shipped 2026-07-01:* the wire now sends the retail-faithful RAW + `forward_speed=1.0` (omitted by default-difference packing) instead of runRate. + Echo-test settled the open question: ACE RECOMPUTES the broadcast run speed from + run skill and auto-upgrades WalkForward+HoldKey.Run → RunForward — sending 1.0 + still broadcasts RunForward @ runRate, and a retail observer saw +Acdream run at + full pace. The stale `MovementData.cs`-citing "ACE relays" comment was + corrected. Wire + velocity are now both raw-1.0; threading them onto one shared + RawMotionState (removing section-6's duplicate build) is a no-op-behavior + cleanup follow-up.) - **L.1c — Combat animation wiring.** Combat mode tracking, draw/sheath style transitions, attack swings by stance/power/height, hit reactions, evades/blocks/parries, and death handoff. diff --git a/docs/research/2026-07-01-d6-motion-interp-pseudocode.md b/docs/research/2026-07-01-d6-motion-interp-pseudocode.md index 52ff40a7..a96957f4 100644 --- a/docs/research/2026-07-01-d6-motion-interp-pseudocode.md +++ b/docs/research/2026-07-01-d6-motion-interp-pseudocode.md @@ -166,7 +166,13 @@ Integration seams: AND the local velocity/turn pipeline (`apply_raw_movement` → `get_state_velocity` + turn omega). The separate GameWindow wire-construction is retired; `MovementResult` carries the raw state, GameWindow packs it. -2. **`forward_speed = 1.0` on run (decomp-forced).** The raw `forward_speed` +2. **`forward_speed = 1.0` on run — CONFIRMED viable (echo-test 2026-07-01).** + The echo-test settled it: acdream sends `forward_speed=1.0`, ACE broadcasts + back `RunForward @ runRate` (not `1.0`), and a retail observer saw +Acdream run + at full pace — so **ACE recomputes the broadcast run speed from run skill** + (the `MovementData.cs`-citing "ACE relays" assumption was wrong). The wire now + sends the retail-faithful raw `1.0` (D6.2b). Original reasoning below stands: + The raw `forward_speed` must be `1.0` when running, because `apply_run_to_command` multiplies by run-rate — carrying `runRate` in the raw state would double-scale to `runRate²`. So the wire now sends `forward_speed=1.0` (omitted by diff --git a/src/AcDream.App/Input/PlayerMovementController.cs b/src/AcDream.App/Input/PlayerMovementController.cs index b4df15b1..0f52d4c7 100644 --- a/src/AcDream.App/Input/PlayerMovementController.cs +++ b/src/AcDream.App/Input/PlayerMovementController.cs @@ -1268,31 +1268,26 @@ public sealed class PlayerMovementController uint? outTurnCmd = null; float? outTurnSpeed = null; - // Retail-faithful wire commands. ACE's MovementData constructor only - // computes interpState.ForwardSpeed for WalkForward / WalkBackwards - // (Network/Motion/MovementData.cs:104-119) — for any other command - // the else-branch passes through without setting speed, so observers - // dead-reckon at speed=0. The wire therefore must be: + // Retail-faithful wire commands — the wire carries the RAW motion state: // - Forward (walk): WalkForward @ 1.0 - // - Forward (run): WalkForward @ run_rate + HoldKey.Run - // (ACE auto-upgrades to RunForward for observers) + // - Forward (run): WalkForward @ 1.0 + HoldKey.Run // - Backward: WalkBackward @ 1.0 - // Our own local animation still wants the actual RunForward cycle - // though — that's carried separately in LocalAnimationCommand below. + // D6.2b (echo-test 2026-07-01): ACE RECOMPUTES the broadcast run speed + // from the character's run skill and auto-upgrades WalkForward+HoldKey.Run + // → RunForward for observers. Sending the raw forward_speed=1.0 (omitted by + // default-difference packing) still broadcasts RunForward @ runRate — a + // retail observer saw +Acdream run at full pace. The earlier "wire must + // send run_rate because ACE relays it" comment (citing MovementData.cs) + // was WRONG. Our own local animation still wants the actual RunForward + // cycle — carried separately in LocalAnimationCommand below. uint? localAnimCmd = null; if (input.Forward) { - outForwardCmd = MotionCommand.WalkForward; - if (input.Run && _weenie.InqRunRate(out float runRate)) - { - outForwardSpeed = runRate; - localAnimCmd = MotionCommand.RunForward; // local cycle is RunForward - } - else - { - outForwardSpeed = 1.0f; - localAnimCmd = MotionCommand.WalkForward; - } + outForwardCmd = MotionCommand.WalkForward; + outForwardSpeed = 1.0f; // RAW — ACE recomputes the broadcast speed + localAnimCmd = (input.Run && _weenie.InqRunRate(out _)) + ? MotionCommand.RunForward // local cycle is RunForward + : MotionCommand.WalkForward; } else if (input.Backward) { @@ -1333,20 +1328,15 @@ public sealed class PlayerMovementController } // ── 7. Detect motion state change ───────────────────────────────────── - // Bug fix: ForwardCommand can stay the same (WalkForward) while ONLY - // ForwardSpeed or the run-hold bit changes. If the user is already - // walking (W held), then presses Shift, the outbound wire still has - // ForwardCommand=WalkForward but outForwardSpeed jumps from 1.0 to - // runRate. Without also tracking speed + hold-key here, no new - // MoveToState is sent — the server keeps thinking the player walks, - // and retail observers render walking animation despite the local - // player's RunForward cycle. - // - // Similarly LocalAnimationCommand change (Walk→Run on local cycle) - // must force a fresh outbound so ACE's BroadcastMovement re-runs - // MovementData(this, moveToState) which only reads ForwardCommand + - // ForwardSpeed + HoldKey to pick between WalkForward vs RunForward - // for remote observers. + // ForwardCommand can stay WalkForward while only the run-hold bit changes + // (walk W held, then Shift → run). Since D6.2b the wire forward_speed is + // always the RAW 1.0 (ACE recomputes the broadcast run speed), so the + // walk↔run toggle is detected via the HoldKey (runHold) and the + // LocalAnimationCommand change (Walk↔Run cycle), NOT via forward_speed. A + // fresh MoveToState on that toggle lets ACE's BroadcastMovement re-pick + // WalkForward vs RunForward (via HoldKey) and recompute the run speed for + // observers. The forward_speed comparison below is retained (harmless — it + // never fires now that forward_speed is constant) for defensiveness. bool runHold = input.Run; bool changed = outForwardCmd != _prevForwardCmd || outSidestepCmd != _prevSidestepCmd diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 015f3b92..adfcbb96 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -8287,6 +8287,10 @@ public sealed class GameWindow : IDisposable // the retail default (0x8000003D) so the packer omits it. ForwardCommand = result.ForwardCommand ?? AcDream.Core.Physics.RawMotionState.Default.ForwardCommand, ForwardHoldKey = result.ForwardCommand.HasValue ? axisHoldKey : AcDream.Core.Physics.HoldKey.Invalid, + // D6.2b: the wire carries the RAW forward_speed (1.0 for run/walk; + // the controller now sets outForwardSpeed=1.0). ACE recomputes the + // broadcast run speed from run skill (echo-confirmed 2026-07-01), so + // this is retail-faithful; 1.0 is omitted by default-difference packing. ForwardSpeed = result.ForwardSpeed ?? AcDream.Core.Physics.RawMotionState.Default.ForwardSpeed, SidestepCommand = result.SidestepCommand ?? AcDream.Core.Physics.RawMotionState.Default.SidestepCommand, SidestepHoldKey = result.SidestepCommand.HasValue ? axisHoldKey : AcDream.Core.Physics.HoldKey.Invalid,