acdream/docs/research/2026-07-02-inbound-motion-maps/map-research-claims.md
Erik fb3ee0544a docs(L.2g): inbound motion deviation map + campaign registration
/investigate deliverable for the inbound (remote-entity) animation+position
retail-parity effort. 10 deviations (DEV-1..10) mapped and adversarially
verified against the named retail decomp + ACE port + current code (9
confirmed, 1 refuted-and-corrected).

Headline: the #39-era UP-pace->cycle inference layer's premise ('wire goes
silent on Shift toggle') is refuted at both oracles — retail sends a fresh
MoveToState on HoldRun toggle while moving (0x006b37a8) and ACE rebroadcasts
every MoveToState unconditionally (GameActionMoveToState.cs:36); retail has
NO pace->animation adaptation anywhere (position error is absorbed solely by
the InterpolationManager chase, already ported verbatim in L.3).

Registers sub-lane L.2g in the roadmap: port the CMotionInterp inbound funnel
verbatim for all remote entity classes, slices S0-S6.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 15:20:46 +02:00

92 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Claim map: inbound motion/animation research vs current code
Date: 2026-07-02. Repo (worktree): `C:/Users/erikn/source/repos/acdream/.claude/worktrees/vigorous-joliot-f0c3ad`
(branch `claude/vigorous-joliot-f0c3ad`, HEAD `b60b9b4a`).
**Path note:** `docs/research/2026-06-04-animation-sequencer-deep-dive.md` does NOT
exist in this worktree or in git history at all (it is an *untracked* file only
in the main checkout `C:/Users/erikn/source/repos/acdream/docs/research/...`).
It was never committed. Read directly from that absolute path for this audit.
The other two docs (`2026-06-26-movement-animation-retail-parity-audit.md`,
`2026-07-01-d6-motion-interp-pseudocode.md`) exist and are committed in this
worktree.
---
## Doc 1: 2026-06-04-animation-sequencer-deep-dive.md (8 ranked divergences)
| # | Claim (doc anchor) | Current-code check | Status | Evidence (file:line) |
|---|---|---|---|---|
| 1 | Missing `pending_motions`/`MotionDone` chain (MotionInterp, HIGH). `add_to_queue` + `CheckForCompletedMotions` unimplemented. Affects multi-step action sequences, combo attacks, `UpdateMotion Commands[]` action chains. (doc anchor: "Divergences ranked" #1, L1242-1247) | Grepped `MotionInterpreter.cs`/`AnimationSequencer.cs` for `pending_motions`/`MotionDone`/`CheckForCompletedMotions`/`add_to_queue` — only comment references (`FUN_00510900 — CheckForCompletedMotions (animation flush, not simulated here)`, `FUN_00528790(…) — add_to_queue`). No queue data structure, no `MotionDone` event. | **STILL-TRUE** | `src/AcDream.Core/Physics/MotionInterpreter.cs:399,412,558` (comments only, no impl) |
| 2 | `contact_allows_move` level mismatch (MotionInterp, HIGH). acdream blocks Fallen/Dead/Crouch-range inside `contact_allows_move` itself; retail does this at `DoMotion` level with distinct error codes. (doc anchor: "Divergences ranked" #2, L1249-1253) | Read `contact_allows_move` body: still explicitly checks `fwd == MotionCommand.Fallen \|\| fwd == MotionCommand.Dead` and the crouch/sit/sleep range, returning `false` (blocked) directly from this function — exactly the divergence described. | **STILL-TRUE** | `src/AcDream.Core/Physics/MotionInterpreter.cs:1081-1107` (see lines 1092-1099 for the Fallen/Dead/Crouch block) |
| 3 | `re_modify` not implemented after cycle rebuild (CSequence, MEDIUM). Stacked Modifier motions (combat stance overlays) lose velocity/omega contribution on cycle switch. (doc anchor: "Divergences ranked" #3, L1255-1260) | Grepped `AnimationSequencer.cs` for `re_modify`/`ReModify`/`ModifierList`/`_activeModifiers` — zero hits. | **STILL-TRUE** | `src/AcDream.Core/Physics/AnimationSequencer.cs` (no match found for the term at all) |
| 4 | `TransparentHook` fires as instant snap vs smooth lerp (anim-hooks, MEDIUM). No `FPHook` time-interpolation equivalent. (doc anchor: "Divergences ranked" #4, L1262-1266) | Not directly re-verified this session (out of scope for walk/run + stop-slide focus — this is a fade/translucency concern, not motion/position). | **COULD-NOT-VERIFY** (not checked; low relevance to task's (a)/(b)/(c) scope beyond "(c) inbound animation machinery generally") | n/a |
| 5 | `direction_` default `0xFFFFFFFE` vs `AnimationHookDir.Both` (anim-hooks, LOW). Dat-baked hooks with missing/zero direction may fire when retail would never fire them. (doc anchor: "Divergences ranked" #5, L1268-1272) | Not re-verified this session — orthogonal to motion/position, low priority. | **COULD-NOT-VERIFY** | n/a |
| 6 | Legacy entity path has no hook dispatch (anim-hooks, LOW-MEDIUM), at old `GameWindow.cs:8563-8573`. (doc anchor: "Divergences ranked" #6, L1274-1277) | Not re-verified — line numbers are 2026-06-04-vintage and GameWindow.cs has grown/shifted substantially (now 13,759 lines vs whatever it was then); the cited range is very likely stale. | **STALE-ANCHOR** (line numbers unverified/likely shifted; concept not re-checked) | n/a |
| 7 | `enter_default_state` not called on spawn (MotionInterp, LOW). No `InitializeMotionTables`/pending_motions Ready-node seeding at spawn. (doc anchor: "Divergences ranked" #7, L1279-1282) | Grepped `MotionInterpreter.cs` for `enter_default_state`/`EnterDefaultState`/`InitializeMotionTables` — zero hits. | **STILL-TRUE** | `src/AcDream.Core/Physics/MotionInterpreter.cs` (no match) |
| 8 | `StandingLongJump` path not called per-tick (MotionInterp, LOW). The standing-long-jump pre-jump pose sub-state exists as a flag but `apply_interpreted_movement`'s per-tick branch (Ready + StopSideStep) is not wired. (doc anchor: "Divergences ranked" #8, L1284-1287) | `StandingLongJump` field exists and is set/cleared in a few places (`MotionInterpreter.cs:328,941,1119,1146`) but no per-tick `apply_interpreted_movement`-style dispatch was found calling `DoInterpretedMotion(Ready)` + `StopInterpretedMotion(SideStepRight)` from the flag. Also confirmed D3 in doc 2 (MoveToState longjump bit) is now WIRED on the *outbound* wire side (`standingLongjump` param exists in `MoveToState.Build`), but that's the wire byte, not the per-tick animation branch this claim is about — those are different things. | **STILL-TRUE** (the per-tick animation branch is still not called; note the wire-side `standingLongjump` param now exists as an input but nothing sets it to true from a live per-tick standing-longjump detection) | `src/AcDream.Core/Physics/MotionInterpreter.cs:328,941,1119,1146` (flag only, no per-tick apply path); cf. `src/AcDream.Core.Net/Messages/MoveToState.cs:56-70` (wire param exists but is a pass-through, always `false` by default) |
| 9 (supporting) | Remote-entity path dispatches via direct `SetCycle` call from `GameWindow.OnLiveMotionUpdated`, NOT via `move_to_interpreted_state` (executive summary + divergence #5/#9 in the MotionInterp section, L303-317). | Confirmed: `move_to_interpreted_state`/`MoveToInterpretedState` still does not exist anywhere except one comment reference. `OnLiveMotionUpdated` still directly manipulates `ae.Sequencer` (`SetCycle`) and does a raw bulk-copy into `remoteMot.Motion.InterpretedState.ForwardCommand/ForwardSpeed` — bypassing `adjust_motion` for remotes entirely. This is THE central mechanism relevant to the walk<->run-lag symptom: the D6-ported `adjust_motion`/`apply_raw_movement`/`get_state_velocity` triad is **local-player-only** (see doc-3 cross-check below). | **STILL-TRUE** (confirmed, and directly load-bearing for the task's (a)/(c) focus) | `src/AcDream.App/Rendering/GameWindow.cs:4517` (comment only ref to move_to_interpreted_state); `GameWindow.cs:4590,4598` (direct bulk-copy into InterpretedState, bypassing adjust_motion); `GameWindow.cs:4327-4492` (SetCycle-direct dispatch) |
| 10 (supporting) | acdream's remote velocity path bypasses `apply_current_movement` gating differences: acdream calls `PhysicsObj.OnWalkable` gate directly rather than the full retail contact/gravity flow (MotionInterp section divergence #3, L305). | `apply_current_movement` (used by BOTH local and remote via `rm.Motion.apply_current_movement` calls) still gates on `PhysicsObj.OnWalkable` directly and calls `get_state_velocity()` — same shape as described. | **STILL-TRUE** | `src/AcDream.Core/Physics/MotionInterpreter.cs:890-910`; called for remotes at `GameWindow.cs:10042,10074` |
---
## Doc 2: 2026-06-26-movement-animation-retail-parity-audit.md (D1-D12)
| # | Claim (doc anchor) | Current-code check | Status | Evidence (file:line) |
|---|---|---|---|---|
| D1 | MoveToState raw flags not retail — presence-based instead of default-difference packing; overs-sends default values. (doc L118-130) | `MoveToState.cs` doc header now explicitly says "default-difference flags dword + conditional fields + actions" and `RawMotionState.Default` exists with retail defaults (`CurrentHoldKey=None`, `ForwardCommand=0x41000003`, `ForwardSpeed=1.0`, etc. matching doc's retail defaults table L60-72). Shipped as part of L.2b (`78e163a4`, 2026-06-30). | **CLOSED-SINCE** | `src/AcDream.Core.Net/Messages/MoveToState.cs:22`; `src/AcDream.Core/Physics/RawMotionState.cs:44-58` |
| D2 | RawMotionState action-list and style packing incomplete — no full action-list packing, current-style scenarios. (doc L132-136) | `RawMotionState` now has `Actions` (IReadOnlyList<RawMotionAction>) with a doc comment describing retail's `num_actions` bits 11-15 packing. Appears substantially addressed as part of L.2b, though full conformance vs the bitfield layout (11 one-bit flags + num_actions:5) was not independently re-verified byte-for-byte this session. | **CLOSED-SINCE** (structure now present; exact bit-packing not re-verified) | `src/AcDream.Core/Physics/RawMotionState.cs:57-63` |
| D3 | MoveToState longjump bit not modeled — only contact 0/1 passed, `standing_longjump` not wired as named state. (doc L138-147) | `MoveToState.Build` now has a `standingLongjump` parameter and packs `trailing = (byte)((standingLongjump ? 0x02 : 0) \| (contact ? 0x01 : 0))` — matches doc's described retail byte layout. NOTE: the parameter is wired as an INPUT but this session did not verify any call site sets it to `true` from a live per-tick standing-longjump detection (see doc-1 item #8 above — the per-tick animation branch driving this flag is still missing). So the WIRE FORMAT is fixed; whether it's ever populated correctly is a separate, still-open question. | **CLOSED-SINCE** (wire format only; upstream detection still absent) | `src/AcDream.Core.Net/Messages/MoveToState.cs:56-70,100-101` |
| D4 | JumpAction packet layout retail-incompatible — missing full Position, extra objectGuid/spellId fields, wrong order. (doc L149-161) | `JumpAction.cs` doc header now cites `Position::Pack (0x005a9640)` and explicitly notes the fix: "the pre-slice code had it backwards (two spurious trailing zero u32s, no Position at all)" — confirms this was found and fixed as part of L.2b. | **CLOSED-SINCE** | `src/AcDream.Core.Net/Messages/JumpAction.cs:21-31` |
| D5 | Position heartbeat close but not fully proven — `NotePositionSent` stamps all three (time/position/contact-plane) on BOTH MTS and AP paths, but retail's `SendMovementEvent` (MTS) only stamps `last_sent_position_time`; only `SendPositionEvent` (AP) stamps all three. (doc L163-169) | Confirmed STILL live and explicitly re-audited: a 2026-06-30 dated comment at the MTS send site says verbatim "this is a real, audit-confirmed divergence from retail... left UNCHANGED; reported to the lead engineer instead of fixed here." This is the most concrete, freshest confirmation in the whole audit — it was re-checked via Ghidra decompile-by-address and deliberately NOT fixed. | **STILL-TRUE** (explicitly re-confirmed and deliberately deferred, not a stale claim) | `src/AcDream.App/Rendering/GameWindow.cs:8320-8339` |
| D6 | MotionInterpreter lacks canonical retail raw→interpreted normalization (`adjust_motion`, `apply_run_to_command`, `apply_raw_movement` not ported); SideStepLeft/WalkBackward produce zero velocity; jump lateral velocity hand-patched. (doc L171-182) | **This is exactly what Doc 3 (D6 pseudocode doc) documents as SHIPPED** for the local player. Confirmed present: `adjust_motion` (`MotionInterpreter.cs:741`), `apply_run_to_command` (`:805`), `apply_raw_movement` (`:848`), `get_state_velocity` (`:639`) all exist and are called from `PlayerMovementController.cs:909,974,1014`. | **CLOSED-SINCE** (for LOCAL player only — see D7 below, this does NOT extend to remotes) | `src/AcDream.Core/Physics/MotionInterpreter.cs:639,741,805,848`; `src/AcDream.App/Input/PlayerMovementController.cs:880-1014` |
| D7 | Animation application split from retail motion flow — `AnimationSequencer` has useful pieces but `MotionInterpreter.apply_current_movement` is velocity-oriented and doesn't drive animation state through retail's order (style/forward/sidestep/turn/actions sequencing). (doc L184-188) | **THE central claim for the task's (a)/(c) focus.** Confirmed still true and now MORE precisely characterized: for remotes, `OnLiveMotionUpdated` bulk-copies `ForwardCommand`/`ForwardSpeed` directly into `InterpretedState` (bypassing `adjust_motion`), AND separately calls `ae.Sequencer.SetCycle(...)` directly for the visual cycle — two parallel, only loosely-coupled paths (one drives position/velocity via `apply_current_movement``get_state_velocity`, the other drives the visual cycle via `SetCycle`). Neither goes through `apply_interpreted_movement`'s full style/forward/sidestep/turn sequencing. Additionally there is a SEPARATE `ApplyServerControlledVelocityCycle`/`ApplyPlayerLocomotionRefinement` mechanism (UP-velocity-derived, with a 200ms UM grace window + 4.5/5.5 m/s hysteresis bucketing) that ALSO writes `SetCycle` for the local-player-observed-remotely case — this is a THIRD parallel path, not documented in either research doc, that is very likely a major contributor to "walk<->run reacts too slowly" (its grace window + hysteresis band means a walk<->run toggle needs velocity to cross a threshold via UpdatePosition samples before the visual cycle re-buckets, since a UM may not arrive for HoldKey-only toggles per the code's own comments). | **STILL-TRUE** (confirmed and refined — root mechanism now identified precisely, more detailed than the original doc knew) | `src/AcDream.App/Rendering/GameWindow.cs:4590,4598` (bulk-copy bypass); `GameWindow.cs:5112-5176` (`ApplyServerControlledVelocityCycle`); `GameWindow.cs:5178-5300+` (`ApplyPlayerLocomotionRefinement`, grace/hysteresis constants at `:5095,5104,5110`) |
| D8 | Force-walk and MoveTo are approximations — `ServerControlledLocomotion`, `RemoteMoveToDriver`, `PlayerMovementController.BeginServerAutoWalk` approximate steering/cycle selection vs retail's full `MoveToManager` queue (pre-turn, move, aux turn, final heading, sticky targeting, progress failure, `MovementParameters::get_command`). (doc L190-196) | `ServerControlledLocomotion.cs` is still only 87 lines (a `PlanMoveToStart`/`PlanFromVelocity` helper, not a queued state machine). No pre-turn/aux-turn/sticky-target/progress-failure machinery found. | **STILL-TRUE** | `src/AcDream.Core/Physics/ServerControlledLocomotion.cs` (87 lines total); `src/AcDream.Core/Physics/RemoteMoveToDriver.cs` (340 lines, still a simplified steering helper) |
| D9 | Inbound movement types 8 (TurnToObject) and 9 (TurnToHeading) dropped — `UpdateMotion` handles interpreted state and MoveTo 6/7 but not 8/9. (doc L198-207) | Confirmed: `UpdateMotion.TryParse` only branches on `movementType == 0` and `movementType is 6 or 7`; no `8`/`9` case anywhere. Grep for `TurnToObject`/`TurnToHeading` across `UpdateMotion.cs` and `GameWindow.cs` returns zero hits. The 2026-07-02 handoff doc explicitly reconfirms this live: "CONFIRMED live: mt=0x09 arrives and just SetCycle(Ready) instead of turning." | **STILL-TRUE** (independently re-confirmed via grep + corroborated by a live capture cited in the handoff doc) | `src/AcDream.Core.Net/Messages/UpdateMotion.cs:136-238` (no case 8/9) |
| D10 | Spawn-time movement state weaker than live — `CreateObject` detects movement types/flags but doesn't preserve full MoveTo target/origin/threshold data the way `UpdateMotion` does. (doc L209-213) | Confirmed with exact detail: `CreateObject.TryParseMoveToPayload` (private, 3-out-param overload: `movementParameters`, `speed`, `runRate`) SKIPS Origin/distanceToObject/minDistance/failDistance/walkRunThreshold/desiredHeading via `pos +=` advances with no output — so `CreateObject`'s returned `ServerMotionState.MoveToPath` is always `null`. By contrast `UpdateMotion.TryParseMoveToPayload` (a DIFFERENT, more complete overload) DOES populate `out CreateObject.MoveToPathData? path` with Origin/thresholds. Two parsers of the same wire shape, one materially weaker. | **STILL-TRUE** (confirmed with precise mechanism: two divergent parser overloads for the same MoveTo payload) | `src/AcDream.Core.Net/Messages/CreateObject.cs:1194-1226` (weak 3-field overload, spawn path) vs `src/AcDream.Core.Net/Messages/UpdateMotion.cs:255-331` (full path-populating overload, live-update path) |
| D11 | Sequence/autonomy data parsed then discarded — retail carries movement sequence, server-control sequence, autonomous state, motion flags, position sequence into movement application; acdream exposes only a subset. (doc L215-219) | Confirmed: `UpdateMotion.TryParse` still does a blind `pos += 6` skip over `movementSequence`(u16) + `serverControlSequence`(u16) + `isAutonomous`(u8) + align — none of these three fields are captured into the `Parsed` record or `ServerMotionState`. (Note: `WorldSession.cs` DOES track `_serverControlSequence`/`_instanceSequence`/etc., but those are the OUTBOUND sequence counters sourced from `UpdatePosition`, not the INBOUND per-entity `UpdateMotion` fields this claim is about — different mechanism entirely, do not conflate.) `_motionFlags` (byte) is read into a local var and never used beyond that line either. | **STILL-TRUE** (confirmed, and clarified that the WorldSession sequence counters some might assume closes this are unrelated) | `src/AcDream.Core.Net/Messages/UpdateMotion.cs:89-106` (skip), `:111` (`_motionFlags` read, unused past declaration) |
| D12 | Jump/falling/contact gates simplified — retail allows specific movement while falling/dead, has separate jump checks for posture/stamina/constraints/pending-motion/contact/leave-hit-ground reapplication; acdream blocks or simplifies several. (doc L221-225) | Not exhaustively re-verified line-by-line this session (large surface). Spot-checked `contact_allows_move`/`motion_allows_jump`-equivalent: the Fallen/Dead/Crouch block still lives inside `contact_allows_move` (see doc-1 item #2), which is itself evidence the gates remain simplified/misplaced relative to retail's `DoMotion`-level error codes. No `pending_motions`-driven jump eligibility check found (consistent with item #1). | **STILL-TRUE** (partially re-verified via the contact_allows_move overlap with doc-1 #2; full jump/posture/stamina gate audit not redone) | `src/AcDream.Core/Physics/MotionInterpreter.cs:1081-1107` (same evidence as doc-1 #2) |
---
## Doc 3: 2026-07-01-d6-motion-interp-pseudocode.md — cross-check of its own claims
| Claim | Current-code check | Status |
|---|---|---|
| "D6 ships `adjust_motion`/`apply_run_to_command`/`apply_raw_movement`/`get_state_velocity` in `MotionInterpreter.cs`, unifying LOCAL player velocity+turn+jump+wire onto one `RawMotionState` at `forward_speed=1.0`" | Confirmed via git log (`0f099bb6` D6.2a, `d34721fa` D6.2b) and via direct code read — all four functions exist and are wired into `PlayerMovementController.cs` only. | **STILL-TRUE (as scoped)** |
| "Out of scope for D6: RawMotionState unification — retail uses ONE raw state for both the wire and the velocity pipeline; D6 keeps the L.2b wire construction separate... unifying is a later slice" (doc L153-156) | This scoping statement is itself accurate and still holds: the wire-side `RawMotionState`/`MoveToState.Build` path and the local velocity-side `apply_raw_movement` input are DIFFERENT `RawMotionState` construction sites (confirmed by design decision #1 in the doc itself, "ONE `RawMotionState`... is built from `MovementInput`" — but this unification was the D6.2 GOAL for local-only; there is still no unification with the REMOTE/inbound path at all). | **STILL-TRUE (scoping honored, and the doc's own "later slice" is exactly the gap this task's handoff doc targets)** |
| "Out of scope for D6: turn ... acdream keeps direct-Yaw turn is replaced" / actually turn WAS ported per design decision #3 ("Turn ported to interpreted, feel unchanged... omega.Z = ±(π/2) × turn_speed... the fixed TurnRateFor direct-Yaw is replaced by the pipeline") — LOCAL only. | For remotes, `RemoteMoveToDriver.TurnRateFor` is still the mechanism (per doc's own "AP-9 stays" note) — confirmed no remote-side `adjust_motion`-sourced turn omega found; `RemoteMoveToDriver.cs` (340 lines) still exists with its own turn-rate logic, separate from `MotionInterpreter`. | **STILL-TRUE (remote turn is still the old direct-rate approximation; local turn was ported per D6 but that's a different code path)** |
---
## Synthesis: what this means for the walk<->run lag + stop-slide symptom (task focus a/b)
1. **D6 (the shipped port) does not touch the remote/inbound path at all.** Every function it
ported (`adjust_motion`, `apply_run_to_command`, `apply_raw_movement`, `get_state_velocity`)
is called exclusively from `PlayerMovementController.cs` (local player). Zero call sites
from `GameWindow.cs`'s remote-entity code (`OnLiveMotionUpdated`, `_remoteDeadReckon`,
`ApplyServerControlledVelocityCycle`) invoke these D6-ported functions.
2. **The remote path bulk-copies wire commands directly into `InterpretedState`**
(`GameWindow.cs:4590,4598`), bypassing `adjust_motion` — so backward/sidestep-left
normalization and run-rate scaling never happen for remotes the retail-faithful way.
This matches the retail `copy_movement_from` bulk-copy semantics for THAT ONE STEP, but the
surrounding `pending_motions`/queue/`move_to_interpreted_state` state machine around it
(doc-1 items #1, #9) is still entirely absent.
3. **A newly-identified (not named in either doc) mechanism likely explains "reacts too
slowly":** `ApplyPlayerLocomotionRefinement` (`GameWindow.cs:5178+`) only kicks in after a
`UmGraceSeconds = 0.2` (200ms) window since the last UM, and then applies **hysteresis**
bands (`PlayerRunPromoteSpeed = 5.5f`, `PlayerRunDemoteSpeed = 4.5f`) before re-bucketing
walk↔run from UpdatePosition-derived velocity — this is architecturally exactly the kind of
mechanism that would produce a visible lag on a walk↔run toggle, especially combined with
the code's own comment that retail may not broadcast a fresh MoveToState for HoldKey-only
toggles (Shift while W held), forcing reliance on this slower velocity-inference path.
4. **D9 (types 8/9 dropped)** means any server-driven turn command mid-locomotion is silently
turned into a `SetCycle(Ready)`, which would visibly interrupt/desync locomotion — a
plausible contributor to "compounding error... running around + turning."
5. **D11 (sequence numbers discarded)** means acdream has no way to detect/reject
out-of-order or duplicate `UpdateMotion` packets, which is a plausible contributor to the
"position errors once the entity stops" (a late/duplicate packet could re-apply a stale
state after a newer one, with no sequence check to catch it).
None of the above is a fix recommendation — this is a raw claim/verification map only, per
the report-only investigation mode.