# Inbound motion deviation map — remote-entity animation + position vs retail Date: 2026-07-02 Mode: **/investigate report — no code changes made.** This doc + the four mapper reports under `2026-07-02-inbound-motion-maps/` are the only writes (uncommitted). Branch: `claude/vigorous-joliot-f0c3ad`. Follows the handoff `2026-07-02-inbound-motion-verbatim-port-handoff.md`. ## Symptom (acceptance oracle — user's live observation, axiom) Watching a remote player in acdream: walk↔run transitions WITHOUT stopping react too slowly in the motion interpreter, throwing animation + position off afterward and compounding through continued running/turning; plus general sliding + position errors on stop. ## Headline findings 1. **The premise behind acdream's current walk↔run mechanism is refuted.** The #39-era UP-velocity refinement layer (`ApplyPlayerLocomotionRefinement`, 0.2 s UM-grace + 5–10 Hz UP cadence + 5.5/4.5 m/s hysteresis) was built on the 2026-05-06 finding "the wire goes silent on Shift-toggle." Both oracles now contradict that finding: - **Retail sends.** `CommandInterpreter`'s HoldRun command handler (`0x85000001`, pseudo-C `acclient_2013_pseudo_c.txt:699322-699328`, addr 0x006b37a8→0x006b3852) calls `SetHoldRun` then `SendMovementEvent()` **whenever the player is not standing still** — i.e. a Shift toggle while W is held emits a fresh MoveToState. (Same for HoldSidestep `0x85000002`.) - **ACE relays.** `GameActionMoveToState.cs:36` calls `BroadcastMovement(moveToState)` **unconditionally** for every inbound MoveToState (`Player_Networking.cs:364-365` → `GameMessageUpdateMotion` broadcast, with the WalkForward+HoldKey.Run→RunForward upgrade and run-skill speed recompute). So an observer **should** receive a UM with ForwardCommand flipping Walk↔Run on every Shift toggle. Why the 2026-05-06 clean test saw only Ready↔Run `[FWD_WIRE]` transitions is the one open empirical question — settle it with probe S0 below before touching the refinement layer. 2. **Retail never adapts animation from observed pace.** Three independent decomp dives + the ACE port cross-check all converge: the inbound position path (0xF748/0xF619 → `SmartBox::HandleReceivedPosition` → `MoveOrTeleport` → `InterpolationManager` chase) has **zero writes into `CMotionInterp`** — it only *reads* `get_adjusted_max_speed()` (×2.0) as the chase-speed cap. The wire's velocity field is a **dead parameter** in `MoveOrTeleport` (pseudo-C:284304 — passed, never dereferenced). Animation changes come only from motion events (0xF74C → `MovementManager::unpack_movement`). acdream's pace→cycle inference layer has **no retail equivalent** (DEV-2) and is an **unregistered deviation**. 3. **acdream's instant DR-velocity snap on walk↔run is retail-correct; the pose hard-cut is the real deviation.** Adversarial verification refuted the initial "retail paces velocity through the link clip" claim: in `GetObjectSequence`'s link-transition branch, `add_motion(new cycle)` is the LAST synchronous call and `CSequence::set_velocity` is a hard overwrite — the sequence velocity snaps to the new cycle's value the same tick (pseudo-C:298437-298468, 300798-300814; ACE `MotionTable.cs:144-168`, `Sequence.cs:127-130`). What acdream's Fix B loses is only the link **pose** (plus its authored root-motion frames): retail plays the authored walk↔run link clip to completion before the new cycle's animation starts. 4. **Retail's remote pipeline is one atomic funnel; acdream's is three loosely coupled writes.** That structural split (DEV-1) is what lets every gap (DEV-2/3/5/6) desync pose from velocity with nothing to reconcile them. ## The retail inbound model (what "verbatim" means here) Two decoupled inbound paths, meeting only at the per-tick frame combiner: **Motion events (0xF74C / 0xF619-embedded)** — `ACSmartBox::DispatchSmartBoxEvent` (pseudo-C:357117) → `CPhysics::SetObjectMovement` (271370, staleness-gated on `update_times[8]`) → `MovementManager::unpack_movement` (300563, **10-way jump table**: case 0 = InterpretedMotionState, 6/7 = MoveTo, 8/9 = TurnToObject/TurnToHeading) → `CMotionInterp::move_to_interpreted_state` (305936: flat `copy_movement_from` overwrite of `interpreted_state`, caches `my_run_rate` from RunForward's forward_speed) → `apply_interpreted_movement` (305713: per-slot `DoInterpretedMotion`/`StopInterpretedMotion` dispatch in retail order — style, forward, sidestep-or-stop, turn-or-stop, idle-stop enqueue) → `CMotionTable::GetObjectSequence` (298636), which decides: - **same substate + same speed sign** → fast path: `change_cycle_speed` (playback rate × newSpeed/oldSpeed) + `subtract_motion`/`combine_motion` velocity swap — same node keeps playing, **no restart** (acdream's SCFAST fast-path is the equivalent); - **substate change (walk↔run!)** → link path: `get_link` (double-hop through the style default when no direct link / sign flip), `clear_physics` + `remove_cyclic_anims` (keeps a mid-playback link, drops the old loop), `add_motion(link)` + `add_motion(cycle)`, `re_modify` re-applies active modifiers. Velocity = the **cycle's** (last overwrite, instant); pose = link first, then cycle. Bookkeeping: `MotionTableManager::add_to_queue`/`remove_redundant_links`/ `CheckForCompletedMotions` (290645-290854) + `CMotionInterp::pending_motions`/ `MotionDone` (305032/305238) reconcile completed commands at anim boundaries. **Position events (0xF748/0xF619)** — `SmartBox::HandleReceivedPosition` (92896): for remotes, `MoveOrTeleport` (284304) only — staleness-gated (`POSITION_TS`/`TELEPORT_TS`), >96 units from player → plain snap, else `InterpolationManager::InterpolateTo` node queue (cap 20). Per tick, `adjust_offset` (353071) moves the full remaining distance capped at `2 × get_adjusted_max_speed()` (fallback 7.5 m/s), stall detection (<30% progress per 5 frames → fail counter >3 → hard snap). **acdream's `InterpolationManager` is already a verbatim port of this (L.3).** **Per tick** (`UpdateObjectInternal` 283611): `CPartArray::Update` → `CSequence::update` fills the frame delta from **the animation frames' baked per-frame deltas** (anim-node path) or `velocity×dt` (`apply_physics` fallback); `PositionManager::adjust_offset` **overwrites** it when a chase node is active (last-writer-wins, NOT additive — matches acdream's `ComputeOffset` REPLACE dichotomy); then the **full collision sweep** (`transition`) runs for remotes too (acdream deliberately skips it for grounded player remotes — DEV-10). **Stop** — one path for every stop: `CMotionTable::StopSequenceMotion` (298954) re-enters `GetObjectSequence` targeting the style default with force=1: `clear_physics` zeroes velocity/omega **at transition start**, the idle-link's authored deceleration plays, `add_to_queue(READY)` + `RemoveMotion` bookkeeping. Modifier stop = `subtract_motion` only. No pace-derived stop inference. ## Ranked deviation map All 10 deviations were adversarially verified through two lenses (refute vs current acdream code / refute vs retail decomp+ACE). 9 CONFIRMED, 1 REFUTED (DEV-4's velocity half — corrected below). Full verify evidence: `…/tasks/waqdgyk0m.output` (session temp) — the load-bearing citations were additionally re-read first-hand in this session. | # | Sev | Deviation | Symptom link | |---|-----|-----------|--------------| | DEV-1 | HIGH | Remote inbound bypasses the CMotionInterp funnel: bulk-copy + direct `SetCycle` instead of `move_to_interpreted_state`/`apply_interpreted_movement` | Structural root: pose + DR velocity are separate writes; every gap desyncs them with no reconciliation | | DEV-2 | HIGH | Non-retail grace-window + hysteresis pace→cycle inference for player remotes (`UmGraceSeconds=0.2`, promote 5.5 / demote 4.5 m/s) | The direct "reacts too slowly": ≥0.2 s + UP cadence + hysteresis-crossing before the gait flips; backlog builds meanwhile. **Unregistered deviation**; premise refuted (headline 1) | | DEV-3 | HIGH | Stop is three unwired mechanisms (UM-Ready overwrite / dead UP-velocity StopCompletely / landing); no `StopSequenceMotion` path; nothing zeroes body velocity or reconciles the queue | Direct cause of stop-slide: pose goes Ready while the body drains pre-stop waypoints at up to 2× maxSpeed | | DEV-4 | MED (corrected) | Fix B drops the authored walk↔run link **pose** (hard visual cut + lost link root motion). ~~Velocity snap~~ — REFUTED: retail snaps velocity instantly too | Visual-only hard cut at each toggle; NOT a position-error source | | DEV-5 | HIGH | Inbound movement types 8/9 (TurnToObject/TurnToHeading) dropped → misapplied as `SetCycle(Ready)` | Mid-locomotion turn command becomes a spurious full stop — error injected exactly during "compounding through running/turning" | | DEV-6 | MED | Sequence-number staleness gate missing (movementSequence/serverControlSequence/isAutonomous parsed-then-discarded; retail gates on `update_times[8]`) | A reordered stale UM re-applies the old gait or un-stops a stop (extends register TS-26 to UM) | | DEV-7 | MED | `pending_motions`/`pending_animations`/`MotionDone`/`remove_redundant_links` lifecycle absent | Enabler: missing `remove_redundant_links` is what made rapid toggles restart links forever, forcing Fix B; without MotionDone nothing clears per-transition bookkeeping | | DEV-8 | MED | Remote DR velocity synthesized from hardcoded gait constants at SetCycle time vs retail's MotionData-accumulated CSequence velocity (+ baked per-frame anim deltas) | Any per-gait speed mismatch vs ACE's integration = constant-rate error that fills the interp backlog → discharges as slide/snap | | DEV-9 | MED | Modifier layer missing: no `re_modify`, single-cycle selection, omega formula-seeded instead of combine/subtract | Turn contribution approximated independently of the motion table → heading-rate error while running+turning | | DEV-10 | LOW | Bifurcated position-drive paths; grounded player remotes skip the per-tick collision sweep (retail sweeps every entity) | None for this symptom directly; parity + pass-through-geometry hazard | ### Key anchors per deviation - **DEV-1** — retail: pseudo-C 305936 (`move_to_interpreted_state` 0x005289c0), 305713 (`apply_interpreted_movement` 0x00528600), 298636 (`GetObjectSequence` 0x00522860), 293301 (`copy_movement_from` 0x0051e750). acdream: `GameWindow.cs:4590/4598` (bulk-copy), `:4769` (separate SetCycle), `:4517` (comment-only funnel ref); D6.2 raw-input funnel is local-player-only (`PlayerMovementController.cs:761-1014`). Nuance (verify correction): the sidestep/turn axes DO route through `DoInterpretedMotion`/`StopInterpretedMotion` (`GameWindow.cs:4794-4857`); the forward axis — the crux — is the bulk-copy. - **DEV-2** — acdream: `GameWindow.cs:5095/5104/5110` (constants), `:5112-5300` (both methods), `:5128-5134` (the unconfirmed-premise comment). Retail: no equivalent exists (searched: InterpolationManager/PositionManager ranges 352000-353400 — zero `CMotionInterp` writes; `MoveOrTeleport` velocity param dead at 284304). - **DEV-3** — retail: 298954 (`StopSequenceMotion` 0x00522fc0), 305635 (`StopInterpretedMotion` 0x00528470), 301194 (`clear_physics` 0x00524d50). acdream: `GameWindow.cs:4364-4367` (UM-Ready), `:5711-5731` (dead UP-velocity stop — ACE player UPs carry velocity=null per `:5700-5710`), `:5527-5553` (landing = only `Interp.Clear` site); plus the acdream-invented 300 ms stop-detection window (`GameWindow.cs:4862-4890` + TickAnimations) — also retail-less, part of this cluster. - **DEV-4** — retail: 298636 link branch; 298552 (`get_link`); 301777 (`append_animation`); velocity-overwrite refutation: 298437-298468 (`add_motion` 0x005224b0) + 300798-300814 (`set_velocity`/`set_omega` overwrites), ACE `MotionTable.cs:144-168` + `Sequence.cs:127-130/221-230`. acdream: `AnimationSequencer.cs:585-635` (Fix B), `:686-754` (velocity synthesis — retail-equivalent in timing). - **DEV-5** — retail: 300563/300707 (jump table cases 8/9 → `MoveToManager::TurnToObject/TurnToHeading`). acdream: `UpdateMotion.cs:136-238` (no case 8/9), `GameWindow.cs:4364-4367` (null-command → Ready misfire). Confirmed live earlier: `mt=0x09` arrives, acdream plays Ready (2026-06-26 audit D9). - **DEV-6** — retail: 271370 (`SetObjectMovement` staleness gate), 357224 (`update_times[8]` compare). acdream: `UpdateMotion.cs:89-106`, `UpdatePosition.cs:29-31/156` (parsed-then-discarded). - **DEV-7** — retail: 305238 (`MotionDone` 0x00527ec0), 290854 (`MotionTableManager::add_to_queue` 0x0051bfe0), 290771 (`remove_redundant_links` 0x0051bf20), 290645 (`CheckForCompletedMotions`). acdream: `AnimationSequencer.cs:401` (always-synchronous SetCycle), `:1128-1129` (`AnimationDoneSentinel` exists, unconsumed by any motion bookkeeping). - **DEV-8** — retail: 298437-298492 (`add_motion`/`combine_motion`/ `subtract_motion`), 302402 (`CSequence::update` — anim-frame deltas are the primary drive), 305160 (`get_state_velocity` constants 3.12/4.0 — the *logical* values acdream synthesizes). acdream: `AnimationSequencer.cs:686-754`. - **DEV-9** — retail: 298636 (`re_modify` at rebuild tail), 298954 (modifier `subtract_motion` branch), 305713 (per-slot dispatch). acdream: `GameWindow.cs:4648-4683` (single-cycle pick), `:4841-4847` (ObservedOmega formula seed), `:10097-10106` (manual omega integration). - **DEV-10** — retail: 280817/283611 (sweep for all entities). acdream: `GameWindow.cs:9717-9722` (fork), `:9883-9890` (sweep skipped, player-remote path), `:10156-10181` (sweep present, NPC path), `:5657` (NPC UP hard-snap). ## Recommended port campaign (for approval — no code written yet) Ordered so each slice is independently landable, tests-first, minimal-surgery (no sequencer rewrite; `SetCycle` internals stay). Every slice updates `docs/architecture/retail-divergence-register.md` in the same commit. - **S0 — the wire probe (no code).** Live capture, acdream as observer, actor driven from retail: `ACDREAM_DUMP_MOTION=1 ACDREAM_REMOTE_VEL_DIAG=1`, structured protocol with the walk↔run Shift toggle as the centerpiece; optionally WireMCP on loopback :9000 for the raw S→C bytes. Expected per the oracles: a UM with ForwardCommand flipping Walk↔Run on every toggle (`[UM_RAW]` is the arbiter — the 2026-05-06 test read `[FWD_WIRE]`, which only fires on interpreted-command *change* after acdream's own resolution). Outcome decides DEV-2's disposition. *(Needs the user driving the retail client — this is the one stop-and-wait step.)* - **S1 — DEV-6 staleness gate.** ~30 isolated lines: capture the two u16s into `UpdateMotion.Parsed`, retail wraparound-newer compare per guid, drop stale. Unit-testable against the 0x7fff wraparound rule. Safe first commit. - **S2 — DEV-1 funnel (the centerpiece), absorbing DEV-3 + DEV-9 dispatch.** Port `move_to_interpreted_state` + `apply_interpreted_movement` into `MotionInterpreter`, with `DoInterpretedMotion` routing to the existing `AnimationSequencer.SetCycle` as the `GetObjectSequence` backend. `OnLiveMotionUpdated`'s remote SubState branch collapses to one `move_to_interpreted_state(ims)` call; stop flows through `StopInterpretedMotion` (no `Interp.Clear()` band-aid — the queue's final waypoints ARE the true stop position); sidestep/turn become per-slot retail dispatch. Parity test first: replay a captured UM stream through old + new paths, assert identical SetCycle sequence + InterpretedState trace; then a stop-slide acceptance case. - **S3 — DEV-7 bookkeeping.** `MotionTableManager` companion (pending_animations, `add_to_queue`, `remove_redundant_links`, `CheckForCompletedMotions`) wiring the existing `AnimationDoneSentinel`; `pending_motions`/`MotionDone` in `MotionInterpreter` second. Additive, decomp-semantics unit tests first. - **S4 — DEV-4 corrected: retire Fix B.** Restore the link-pose enqueue for locomotion↔locomotion now that S3's `remove_redundant_links` removes Fix B's original justification (link-restart spam). Velocity stays instant (retail-correct). Acceptance: user visual side-by-side. - **S5 — DEV-2 disposition per S0.** If UMs arrive: delete the refinement layer (dead weight + latency + feedback loop) and let the S2 funnel drive. If the wire is genuinely silent: keep a minimal layer as an explicitly registered ACE-adaptation row (a retail client on the same wire would show the stale gait too — matching retail display means NOT inferring), and file the gap against ACE. Either way DEV-2's machinery stops being an unregistered deviation. - **S6 — remainder.** DEV-5 (parse types 8/9 → heading target; cross-check field order vs Chorizite + holtburger first), DEV-8 (evidence first: dump Humanoid MotionTable Walk/Run `MotionData.Velocity`+Flags from the dat, diff synthesized magnitudes vs ACE's integration), DEV-10 deferred (fold-in after S2; NPC hard-snap replacement needs explicit user approval per the reverted-campaign precedent). ## What we've ruled out - "acdream lacks any transition machinery" — false; `SetCycle` resolves authored links (`GetLink`) and has the retail same-substate fast path (SCFAST ≈ `change_cycle_speed`). - "Retail paces the walk↔run *velocity* change through the link clip" — refuted (headline 3); instant velocity snap is retail-correct. - "Retail refines the remote's cycle from UP-derived velocity" (#39's premise) — refuted; no such mechanism exists in the decomp (two independent dives + ACE cross-check). - The `InterpolationManager` chase itself — already a verbatim port (L.3); constants re-verified this session (2× adjusted max speed, 0.05 desired distance, 30%/5-frame stall, fail>3 blip, 20-node cap, 96-unit gate upstream). ## What this is NOT This is NOT a "port the whole CSequence/CPartArray stack" campaign — the sequencer's node/link/queue mechanics are close enough that the funnel (DEV-1), bookkeeping (DEV-7), and stop path (DEV-3) can be ported around it surgically; and it is NOT a dead-reckoning-tuning problem — the chase is already retail, it's the inputs (cycle + velocity source + stop signal) that deviate. ## S0 wire-probe RESULTS (2026-07-02, post-S1 live capture) Observer acdream (+Acdream), actor retail-driven (guid 0x5000000B), structured protocol incl. Shift toggles. 280 UMs captured (`launch-s0-wireprobe.log`). 1. **The wire is NOT silent — DEV-2's premise is dead.** Walk↔run toggles arrive explicitly: `fwd=0x0005 fwdSpd=null(=1.0)` ↔ `fwd=0x0007 fwdSpd=2.85`. Root cause of the 2026-05-06 misreading: retail's default-difference packing baselines `forward_command` against **Ready** (`RawMotionState.Default`, our own D6.2b verbatim port) — W-held is non-default and always packed, so ACE (`MovementData.cs:104-119`) always re-emits it with the holdKey upgrade + run-skill speed. 2. **flags=0 "empty" autonomous UMs are frequent (127/209) and LEGITIMATE** — they are genuine keys-released / heading-only MoveToState relays (all-default raw state packs nothing; ACE `RawMotionState.Read` leaves absent = Invalid; `MovementData` emits nothing). Retail semantics (verified: `InterpretedMotionState` ctor 0x0051e8d0 defaults `forward_command=Ready`; `MovementManager::unpack_movement` case 0 UnPacks into a default-constructed state; `CMotionInterp::move_to_interpreted_state` 0x005289c0 does a FLAT `copy_movement_from` + `apply_current_movement`): an empty UM = a real full stop. acdream already maps it to Ready — but the DEV-2 refinement then re-promotes the cycle from UP pace after the 0.2 s grace, producing a Ready↔Run thrash against legitimate stop signals. That thrash is the observed flicker/rubber-band component. 3. **S1 gate validated live**: 0 `[UM_STALE]` drops across 280 UMs; movementSeq advances +1 per UM; byte-level layout confirmed (`instanceSeq|movementSeq|serverControlSeq|isAutonomous` exactly where the parser reads them). 4. Also confirmed in `unpack_movement` (0x00524440) for the S2 port: the outer style u16 routes through `command_ids[]` and applies via `DoMotion` ONLY when it differs from the current style; motionFlags carries sticky-object (0x100 → `stick_to_object`) and standing_longjump (0x200); the actions list is consumed under a 15-bit `server_action_stamp` wraparound compare (`move_to_interpreted_state` 0x005289c0, lines 305953-305989). **Disposition: S5 = delete the player pace-inference layer** (executed in the same session; NPC `PlanFromVelocity` path untouched — its unification is S6). ## Provenance - Workflow `wf_b92a5670-283`: 4 read-only mappers (retail decomp / ACE port / acdream code / prior-research claims — full reports preserved in `2026-07-02-inbound-motion-maps/`) → synthesis → 2-lens adversarial verify per deviation (25 agents, ~3.0 M tokens). The ACE-port mapper crashed on structured output AFTER writing its report file; synthesis read all four files. One verify lens (DEV-4 retail side) delivered the REFUTED verdict incorporated above. - Targeted agents: retail remote-anim adaptation dive (+2 children: InterpolationManager decomp dump, ACE InterpolationManager cross-check). - First-hand session reads: `OnLiveMotionUpdated` (GameWindow 4230-4930), DR tick path (9700-9920), `AnimationSequencer.SetCycle` (340-790), `ApplyServerControlledVelocityCycle`/`ApplyPlayerLocomotionRefinement` (5077-5300), `ServerControlledLocomotion`, `PositionManager`, `InterpolationManager`, `UpdatePosition` parser, ACE `Player_Tick.cs` / `Player_Networking.cs` / `GameActionMoveToState.cs`, decomp `SendMovementEvent`/HoldRun sites (699274-700312), 2026-06-04 deep-dive divergence list, ISSUES #39, divergence register scan. - Corrections applied to the raw workflow output: DEV-1 anchor fix (`MotionInterpreter.cs:399/412/558` reference different retail functions — the only funnel comment-refs are `GameWindow.cs:4517` + `PlayerMovementController.cs:593`); DEV-4 rewritten per its refutation.