Port retail portal viewport projection and reveal behavior, preserve outbound combat style, drive remote and local grounded movement from authored CSequence root frames, and reuse the local prepared pose so animation hooks advance once. User-verified portal, observer movement, combat stance, and short-tap locomotion gates. Release build passed with 5,767 tests and five intentional skips. Co-authored-by: OpenAI Codex <codex@openai.com>
3.7 KiB
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 @ 0x00528360CMotionInterp::apply_interpreted_movement @ 0x00528600CMotionInterp::apply_current_movement @ 0x00528870CMotionInterp::get_state_velocity @ 0x00527D50CPhysicsObj::UpdatePositionInternal @ 0x00512C30CPhysicsObj::UpdateObjectInternal @ 0x005156B0CPhysicsObj::get_velocity @ 0x005113C0
Cross-checks:
docs/research/2026-07-02-inbound-motion-maps/map-ace-port.md, which records ACE'sPartArray.Updateinto the shared offset frame beforePositionManager.AdjustOffset.docs/research/2026-07-02-r3-motioninterp/r3-ace-motioninterp.md, which independently showsapply_interpreted_movementdispatching motions rather than installing ordinary grounded velocity.
Pseudocode
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
- The local player's input edge must reach
MotionTableManagerbefore the current object's animation advance. AnimationSequencer.Advance(dt, rootFrame)runs exactly once for the local owner and publishes both the pose androotFrame.Origin.PlayerMovementControlleraccumulates that local displacement until the existing 30 Hz physics quantum runs.- While grounded, scale it by the server object scale and seed the same
MotionDeltaFramepassed throughPositionManager.AdjustOffset. - The transition sweep resolves the combined delta and computes cached velocity from the realized displacement.
- 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. - 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.