# Local-player root-motion start — retail pseudocode (2026-07-17) ## Symptom A short forward-key tap moved the local acdream body immediately while the Ready-to-locomotion animation was still starting. The character therefore glided before its legs began the corresponding movement. ## Retail oracle Named Sept-2013 client: - `CMotionInterp::DoInterpretedMotion @ 0x00528360` - `CMotionInterp::apply_interpreted_movement @ 0x00528600` - `CMotionInterp::apply_current_movement @ 0x00528870` - `CMotionInterp::get_state_velocity @ 0x00527D50` - `CPhysicsObj::UpdatePositionInternal @ 0x00512C30` - `CPhysicsObj::UpdateObjectInternal @ 0x005156B0` - `CPhysicsObj::get_velocity @ 0x005113C0` Cross-checks: - `docs/research/2026-07-02-inbound-motion-maps/map-ace-port.md`, which records ACE's `PartArray.Update` into the shared offset frame before `PositionManager.AdjustOffset`. - `docs/research/2026-07-02-r3-motioninterp/r3-ace-motioninterp.md`, which independently shows `apply_interpreted_movement` dispatching motions rather than installing ordinary grounded velocity. ## Pseudocode ```text on input motion edge(command, params): CMotionInterp updates raw/interpreted state apply_interpreted_movement dispatches style, forward, sidestep, and turn CPhysicsObj::DoInterpretedMotion forwards those operations to MotionTableManager // No ordinary grounded set_local_velocity occurs here. CPhysicsObj::UpdatePositionInternal(dt): offset = identity Frame if visible and part_array exists: part_array.Update(dt, offset) if OnWalkable: offset.origin *= object_scale else: offset.origin = zero position_manager.adjust_offset(offset, dt) candidate = current_position.frame combined with offset candidate.origin += m_velocityVector * dt + acceleration * dt^2 / 2 m_velocityVector += acceleration * dt return candidate CPhysicsObj::UpdateObjectInternal(dt): old = current_position candidate = UpdatePositionInternal(dt) transition = sweep(old, candidate) if transition succeeds: cached_velocity = offset(old, resolved_position) / dt SetPositionInternal(transition) else: cached_velocity = zero ``` `get_state_velocity` is not the ordinary grounded locomotion driver. Retail uses it for leave-ground/jump velocity and related state queries. Grounded translation comes from the complete `CSequence::update` Frame, including the authored Ready-to-Walk/Run link timing. `CPhysicsObj::get_velocity` returns the resolved `cached_velocity`, distinct from the integrator's `m_velocityVector`. ## Port shape 1. The local player's input edge must reach `MotionTableManager` before the current object's animation advance. 2. `AnimationSequencer.Advance(dt, rootFrame)` runs exactly once for the local owner and publishes both the pose and `rootFrame.Origin`. 3. `PlayerMovementController` accumulates that local displacement until the existing 30 Hz physics quantum runs. 4. While grounded, scale it by the server object scale and seed the same `MotionDeltaFrame` passed through `PositionManager.AdjustOffset`. 5. The transition sweep resolves the combined delta and computes cached velocity from the realized displacement. 6. Do not install `get_state_velocity()` as ordinary grounded body velocity when a retail root-motion source is attached. Keep that function for leave-ground/jump behavior. 7. The animation pass consumes the already-advanced pose instead of advancing the local sequence a second time. The existing manual yaw path remains separate; this slice consumes the root Frame's translation only. Full root-orientation ownership remains part of the registered turn/omega divergence and is not guessed here.