acdream/docs/plans/2026-07-02-retail-motion-animation-rewrite.md
Erik a987cad182 feat(R1-P6): root-motion Frame seam + dead-API removal — R1 COMPLETE
- Advance(dt, Frame? rootMotionFrame) overload: retail's actual root-
  motion contract (CSequence::update(quantum, Frame*) 0x00525b80) —
  every crossed frame's pos_frame combines into the caller's Frame plus
  the sequence velocity/omega via apply_physics. This is the seam R6's
  retail per-tick order (CPartArray.Update -> adjust_offset ->
  Frame.combine) consumes.
- DELETED: ConsumeRootMotionDelta + the dead adapter accumulator fields
  (zero external callers; gap-map API-migration table).
- Root-motion test now asserts REAL accumulation through the wired
  Frame path (replaces the P5 inert-stub pin).
- Phase R plan: R1 stage marked SHIPPED with its commit trail.

Full suite green (3346). R1 done: P0 research/pins -> P1 node -> P2
container -> P3 physics -> P4 advance core -> P5 adapter cutover ->
P6 wiring. Next: R2 (GetObjectSequence + MotionTableManager; extraction
workflow already running).

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

8.2 KiB
Raw Blame History

Phase R — retail motion & animation stack, ground-up reconstruction

Date: 2026-07-02. Mandate (user, verbatim intent): a complete new movement + animation system, verbatim retail equivalent — all movement, inbound and outbound, all animation, for players, NPCs, and monsters. No frozen code, no bandaids, no guessing, no approximation. Total overhaul.

Execution shape: a staged verbatim RECONSTRUCTION, not a big-bang cutover — each stage builds a retail class 1:1 with a conformance harness, cuts consumers over, and DELETES the legacy path in the same stage. "New system" is the destination; the client keeps running between stages. The cdb-golden technique is proven (183/183 live-retail dispatch conformance in S2a) and is the acceptance mechanism for every stage: the user's eyes are a final sanity pass only.

Supersedes the L.2g S3S6 slice plan (S1/S2/S5 landed and are absorbed as components). This doc is the plan of record; the deviation map (docs/research/2026-07-02-inbound-motion-deviation-map.md), the funnel pseudocode (…-s2-inbound-funnel-pseudocode.md), and the 2026-06-04 sequencer deep-dive are its research base.

The retail module map to reconstruct (per physics object, ALL classes)

CPhysicsObj (local player, remote player, NPC, monster — ONE pipeline)
 ├─ MovementManager                unpack_movement 0x00524440 (10-way dispatch),
 │   │                             PerformMovement, MotionDone relay
 │   ├─ CMotionInterp              raw_state + interpreted_state + pending_motions,
 │   │                             DoMotion / DoInterpretedMotion / StopInterpretedMotion,
 │   │                             apply_raw/interpreted/current_movement, my_run_rate,
 │   │                             HitGround / LeaveGround / ReportExhaustion, jump family
 │   └─ MoveToManager              movement types 6/7/8/9 (MoveToObject/Position,
 │                                 TurnToObject/Heading), node stepping, arrival radii,
 │                                 fail distance, CanCharge walk/run selection
 ├─ CPartArray → MotionTableManager pending_animations, add_to_queue 0x0051bfe0,
 │   │                             remove_redundant_links 0x0051bf20,
 │   │                             CheckForCompletedMotions 0x0051be00 → AnimationDone
 │   └─ CMotionTable::GetObjectSequence 0x00522860
 │       │                         same-substate fast path (change_cycle_speed +
 │       │                         subtract/combine_motion), link path (get_link +
 │       │                         style-default double-hop), re_modify, is_allowed
 │       └─ CSequence              anim-node DLList, append_animation 0x00525510,
 │                                 remove_cyclic_anims, clear_physics, velocity/omega
 │                                 accumulators, update/update_internal + apply_physics
 │                                 (root motion), placement frames, hook dispatch
 ├─ PositionManager                InterpolationManager [PORTED ✓ L.3] + StickyManager
 │                                 + ConstraintManager
 └─ per-tick UpdateObjectInternal 0x005156b0 order:
     CPartArray.Update (CSequence root motion) → PositionManager.adjust_offset
     (chase REPLACES) → Frame.combine → UpdatePhysicsInternal → FULL transition
     sweep (ALL entities — retires the remote no-sweep fork) → MovementManager.UseTime
     / CPartArray.HandleMovement / PositionManager.UseTime → process_hooks

Already-verbatim components (absorbed, not rewritten)

  • MotionSequenceGate (S1) — is_newer 3-stamp gate, live-validated.
  • Inbound funnel: MoveToInterpretedState / ApplyInterpretedMovement / DispatchInterpretedMotion / verbatim contact_allows_move (S2a) + 183-case live-trace conformance suite.
  • InterpolationManager (L.3) — chase constants re-verified 2026-07-02.
  • Outbound: RawMotionState::Pack default-difference, MoveToStatePack trailer, JumpPack (L.2b/D6.2b, golden-byte tests); dual command catalogs (L.1b); adjust_motion/apply_run_to_command/apply_raw_movement/ get_state_velocity (D6).

Everything else is reconstruction scope — especially: AnimationSequencer internals (becomes the CSequence port's host or is replaced by it), the three per-tick drive paths in GameWindow.TickAnimations, RemoteMotionSink (temporary S2b seam — dissolves into GetObjectSequence), RemoteMoveToDriver, ServerControlledLocomotion, the motion half of PlayerMovementController, the 300 ms stop-detection window, NPC UP hard-snaps.

Stage plan (each: pseudocode → harness → port → cutover → DELETE legacy → register sweep)

  • R1 — CSequence verbatim. SHIPPED 2026-07-02 (commits 1371c2a1 P0/P1, 778744bf P2, 5138b8fb P3, 658b91d8 P4, 9147344a P5, +P6): the verbatim core (AnimSequenceNode/CSequence/FrameOps, 56 conformance tests) + the AnimationSequencer adapter rehost deleting the legacy epsilon/stale-head/safety-cap/per-node-flag mechanisms; root motion flows through Advance(dt, Frame) = retail update(Frame*). Registers AD-33/AD-34. Node list, framerate/rate math, velocity+ omega accumulators (set/combine/subtract), update/update_internal root motion, apply_physics, placement frames, hook dispatch. Goldens: dat MotionData fixtures + a cdb trace of append_animation/remove_cyclic_anims args (script pattern: tools/cdb/l2g-observer.cdb). Cutover: becomes the sequencer core behind the existing AnimationSequencer API, then the API narrows to retail's.
  • R2 — GetObjectSequence + MotionTableManager. Fast path, link path (restores the walk↔run link pose — old S4), re_modify (modifier blend — retires AP-73), pending_animations + remove_redundant_links + CheckForCompletedMotions → AnimationDone→MotionDone chain (old S3). RemoteMotionSink's single-cycle pick DELETED — GetObjectSequence decides.
  • R3 — CMotionInterp completion. pending_motions/MotionDone, DoMotion, jump family (jump_charge_is_allowed/motion_allows_jump verbatim — the misattribution found in S2a), HitGround/LeaveGround/ReportExhaustion, enter_default_state. LOCAL PLAYER unifies onto it (PlayerMovementController keeps input + camera; motion state machine moves here).
  • R4 — MoveToManager verbatim. Types 6/7/8/9 (TurnToObject/TurnToHeading — the dropped D9/DEV-5 commands), node stepping, arrival, fail distance, CanCharge. Replaces RemoteMoveToDriver + ServerControlledLocomotion. Retires AD-8/AD-9/AP-8/AP-9.
  • R5 — MovementManager + MovementSystem facade. One per-object pipeline for every entity class; StickyManager (stick_to_object — the motionFlags 0x100 bit) + ConstraintManager ports; GameWindow's OnLiveMotionUpdated shrinks to parse→MovementSystem.HandleMovementEvent.
  • R6 — per-tick UpdateObjectInternal order. Retail tick order for ALL entities incl. the transition sweep for remotes (retires the L.3-M2 no-sweep fork + the path A/B split + the 300 ms stop window + NPC UP hard-snap special cases). GameWindow.TickAnimations sheds its motion logic entirely (Code Structure Rule 1).
  • R7 — outbound autonomy cadence. ShouldSendPositionEvent (0x006b45e0)
    • the MTS/AP stamp split — retires TS-33. Outbound becomes 100% verbatim.
  • R8 — cutover audit. grep-sweep for legacy motion code, register reconciliation (every AD/AP/TS motion row retired or re-justified), full live protocol (walk/run/toggle/turn/circle/stop/jump/MoveTo/TurnTo, player+NPC+monster), ONE final user visual pass.

Standing rules for every stage

  1. Decomp is the oracle; ACE the interpretation aid; cdb/TTD the runtime arbiter when they disagree (the S0/S2 pattern).
  2. No stage ships without its conformance fixtures. Golden sources: dat tables, cdb traces (both observer + actor side), captured wire logs.
  3. Every commit: build + full suite green; register rows added/retired in the same commit; roadmap stage table updated on stage completion.
  4. New code lives in src/AcDream.Core/Physics/Motion/ (pure logic; GL-free) with App seams only for rendering handoff (Structure Rule 2).
  5. Delete, don't gate: when a stage cuts over, the legacy path is REMOVED in that stage (mandate: no frozen code, no bandaids).