docs(issues): close #45 (sidestep slow); file #46 (retail observer of acdream blippy)

#45 — closed by commit e9e080d. PlayerMovementController hands a raw
localAnimSpeed (1.0 slow / runRate fast); UpdatePlayerAnimation now
scales sidestep cycles by WalkAnimSpeed/SidestepAnimSpeed × 0.5 to
match ACE's BroadcastMovement formula. User-verified.

#46 — filed. Retail clients observing acdream's local +Acdream
character see visibly blippy / laggy movement. Local acdream view of
the same character is fine; acdream observing retail-driven
characters is also fine (after #39 / #45). The degradation is
specifically on the OUTBOUND path. Likely culprits ranked: AutoPos
heartbeat cadence (acdream's fixed 200 ms is suspect per
project_retail_motion_outbound memory), MoveToState send conditions,
sequence counters, or absent HasVelocity on UPs. Verification approach
documented (two retail clients + one acdream side-by-side; cdb
breakpoint count of MovementManager::unpack_movement on retail
observer).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-06 09:03:35 +02:00
parent e9e080db8c
commit 24407fec3c

View file

@ -1258,37 +1258,61 @@ If hypothesis (a) is correct, this issue effectively rolls into **#28** — the
---
## #45 — Local +Acdream sidestep walking renders too slow
## #46 — Retail observer of acdream sees blippy / laggy movement
**Status:** OPEN
**Severity:** LOW (visible animation cadence; not a correctness/wire bug)
**Severity:** MEDIUM (degrades external perception of acdream-driven characters)
**Filed:** 2026-05-06
**Component:** physics / animation (local player path: `PlayerMovementController``UpdatePlayerAnimation``AnimationSequencer.SetCycle`)
**Component:** net / motion (acdream's outbound path: `PlayerMovementController``MoveToState` (0xF61C) / `AutonomousPosition` heartbeat → ACE → retail observer)
**Description:** When the local +Acdream character strafes (A or D held) at slow pace (no Shift), the visible leg cycle for the local player plays slower than retail's equivalent. Fast strafe (with Shift held) appears correct. Observed by user 2026-05-06 immediately after fix #5 (commit `349ba65`) landed the matching fix on the *observer-side* `ApplyPlayerLocomotionRefinement` for retail-driven remotes.
**Description:** When viewing acdream's local +Acdream character through a parallel retail acclient.exe, the retail observer sees the character's movement as visibly blippy and laggy — position appears to step in discrete jumps rather than translating smoothly. The local acdream view of the same character looks fine, and acdream observing a retail-driven character (after #39 / #45) also looks fine. The degradation is specifically on the **outbound** side: what acdream sends to ACE for relay to other clients.
**Root cause / status:**
Likely the same constant mismatch as fix #5: the local player's sidestep speedMod was being computed off `WalkAnimSpeed` (3.12 m/s) where it should use `SidestepAnimSpeed` (1.25 m/s). Because the wire-emitted `SideStepSpeed` already encodes a 0.5× multiplier (per ACE `MovementData.cs:124-131`), dividing the wrong base on either send or render side compresses the slow strafe to a sub-walk cadence.
Unverified. The likely culprits, ranked by suspected probability:
To confirm: look at `UpdatePlayerAnimation` in `src/AcDream.App/Rendering/GameWindow.cs` (~line 7000) and trace where the strafe speedMod is sourced from. Either the speedMod passed to `SetCycle` is too small, or the framerate computation downstream applies an extra 0.5×.
1. **AutonomousPosition heartbeat cadence.** `memory/project_retail_motion_outbound.md` notes acdream's fixed 200 ms heartbeat is a probable retail mismatch. Retail's `CommandInterpreter::SendPositionEvent` gates on transient_state (Contact + OnWalkable + valid Position) and may broadcast at a different cadence — fewer / more / variable. If acdream sends too rarely, observer dead-reckons too long between updates and visibly stutters when each AutoPos arrives.
2. **MoveToState send conditions.** `PlayerMovementController.cs:813-840` decides when a fresh MoveToState fires (state-change detection). If important transitions are missed (e.g., direction changes that don't flip ForwardCommand/SidestepCommand), the observer's last-known motion stays stale and AutoPos updates blip the body to the new authoritative position.
3. **InstanceSequence / ObjectMovement sequence counters.** ACE rejects out-of-order packets. If acdream's sequence stamping is off, ACE silently drops some packets; observer dead-reckons through the gap.
4. **Velocity field absent on AutoPos.** ACE relays UPs without HasVelocity for player characters (per `OnLivePositionUpdated` comment). Observer's dead-reckoning between UPs may extrapolate using stale velocity, producing visible position drift that snaps back on the next UP — exactly the blippy pattern.
Cross-reference:
- `MotionInterpreter.SidestepAnimSpeed = 1.25f`
- `MotionInterpreter.WalkAnimSpeed = 3.12f`
- ACE `MovementData(MoveToState)`: `interpState.SidestepSpeed = speed * 3.12f / 1.25f * 0.5f`
**Verification approach:**
- Run two retail clients + one acdream client. Drive acdream; observe acdream's character on retail #1 and on retail #2 (both retail observers see the same wire). Compare to a retail-driven character observed from the same retail clients — does it look smooth there? If yes, the issue is acdream-outbound-specific. If both look blippy, it's something on the ACE side (less likely).
- cdb-attach a retail observer client and breakpoint `MovementManager::unpack_movement` to count UPs and UMs received per second from the acdream-driven character vs from another retail character. The cadence delta will identify which packet stream is misbehaving.
- Compare acdream's outbound packet timing against holtburger's `client/movement/system.rs` heartbeat logic — that's the closest known-working reference for how a non-retail client should pace its outbound.
**Files:**
- `src/AcDream.App/Rendering/GameWindow.cs``UpdatePlayerAnimation` (~line 7000)
- `src/AcDream.App/Input/PlayerMovementController.cs` — wire-builder for outbound MoveToState (sidestep path)
- `src/AcDream.Core/Physics/MotionInterpreter.cs:243-247` — anim-speed constants
- `src/AcDream.App/Input/PlayerMovementController.cs` — outbound state-change detection + heartbeat
- `src/AcDream.Core.Net/WorldSession.cs` — sequence counters + send path
- `src/AcDream.Core.Net/Net/Outbound/...MoveToState.cs` / `AutonomousPosition.cs` — wire builders
- `references/holtburger/crates/holtburger-core/src/client/movement/system.rs` — reference cadence
**Acceptance:**
- Local +Acdream slow strafe (A or D, no Shift) plays at the same visible cadence as retail's slow strafe in a side-by-side comparison.
- Local fast strafe (A or D + Shift) does not regress.
- Forward / backward / run cycles do not regress.
- Side-by-side comparison: retail observer of acdream-driven character and retail observer of retail-driven character look equally smooth during running, walking, sidestepping, turning, and stopping.
- No visible "step" pattern when acdream-driven character translates between AutoPos updates.
**Cross-reference:**
- `memory/project_retail_motion_outbound.md` — 2026-05-01 cdb live trace of retail's outbound (`CommandInterpreter::SendMovementEvent` for WASD, `Event_Jump` per-frame while charging).
- CLAUDE.md "Outbound motion wire format" — the `WalkForward + HoldKey.Run``RunForward` auto-upgrade ACE applies on broadcast.
---
## #45 — [DONE 2026-05-06 · e9e080d] Local +Acdream sidestep walking renders too slow
**Status:** DONE
**Closed:** 2026-05-06
**Commit:** `e9e080d`
**Component:** physics / animation (local player path: `UpdatePlayerAnimation`)
**Resolution:** `PlayerMovementController.cs:871` computes `localAnimSpeed` as raw `runRate || 1.0`, but ACE's `BroadcastMovement` converts the 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.
`UpdatePlayerAnimation` now multiplies `animSpeed` by `WalkAnimSpeed / SidestepAnimSpeed × 0.5 = 1.248` when `animCommand` is `SideStepLeft / Right` (low byte 0x0F or 0x10). User-verified: local strafe cadence matches retail / observer-side rendering.
**Original investigation note (preserved):** Same constant mismatch pattern as #39 fix #5 (commit `349ba65`) but on the local-player render path instead of the observer-side `ApplyPlayerLocomotionRefinement` — both fixed by aligning the speedMod base to ACE's wire formula.
---