acdream/docs/research/2026-07-17-remote-root-motion-reconciliation-pseudocode.md
Erik 124e046976 fix(runtime): align portal and movement presentation
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>
2026-07-17 08:48:27 +02:00

115 lines
3.5 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.

# Remote root-motion reconciliation — retail pseudocode
**Date:** 2026-07-17
**Symptom:** an observed character walks forward, blips backward, then repeats.
**Oracle:** Sept 2013 named retail decomp, installed retail DAT, ACE physics port.
## Named retail path
### `CPhysicsObj::UpdatePositionInternal` — `0x00512C30`
```text
offset = identity Frame
if object is visible:
if partArray exists:
partArray.Update(dt, offset)
// CPartArray::Update 0x00517DB0 calls
// CSequence::update(sequence, dt, offset)
if OnWalkable:
offset.origin *= objectScale
else:
offset.origin = zero
if positionManager exists:
positionManager.adjust_offset(offset, dt)
// InterpolationManager may REPLACE offset.origin with the
// queue-head catch-up delta.
newFrame = currentWorldFrame combined with offset
if object is visible:
UpdatePhysicsInternal(dt, newFrame)
process_hooks()
```
### `add_motion` — `0x005224B0`
```text
if motionData exists:
sequence.velocity = motionData.velocity * speed
sequence.omega = motionData.omega * speed
append every authored AnimData * speed
```
There is no substitution of `CMotionInterp::get_state_velocity` constants into
`CSequence::velocity`. Those constants drive body physics in the interpreted
movement path; they are not animation-root data.
### `InterpolationManager::adjust_offset` — `0x00555D30`
```text
if queue empty, no physics object, or object lacks Contact:
leave offset unchanged
if queue head is within 0.05 m:
complete the node
leave offset unchanged
maxSpeed = minterp.adjustedMaxSpeed * 2
if maxSpeed is effectively zero:
maxSpeed = 7.5 m/s
offset.origin = directionToHead * min(maxSpeed * dt, distanceToHead)
```
The queue catch-up replaces the CSequence displacement while active. Once the
head is reached, the literal CSequence displacement remains.
## Installed-DAT result
`client_portal.dat` MotionTable `0x09000001` (Humanoid), NonCombat style
`0x8000003D`:
- WalkForward `0x40000005`: `MotionData.Velocity == (0,0,0)`
- RunForward `0x40000007`: `MotionData.Velocity == (0,0,0)`
Pinned by `HumanoidMotionTableRootMotionTests`.
## acdream correction
Previous behavior synthesized `WalkAnimSpeed × speed` or
`RunAnimSpeed × speed` into `CSequence.Velocity`, then used that guessed value
after the interpolation queue reached its latest waypoint. The body could run
past server truth; the next UpdatePosition queued a backward correction,
creating the repeating blip.
Correct frame order:
```text
rootDelta = identity Frame
partTransforms = sequencer.Advance(dt, rootDelta)
worldDelta = RemoteMotionCombiner(
rootDelta.origin,
interpolationQueue,
positionManager)
sweep and commit worldDelta
publish root and part transforms
```
The client now consumes the complete root delta produced by the already-ported
`CSequence` (authored PosFrames plus literal MotionData velocity). It does not
derive remote translation from a command name or packet cadence.
## Cross-reference
- ACE `PhysicsObj.UpdatePositionInternal`: PartArray update → PositionManager
adjustment → world-frame combine, matching retail ordering.
- ACE `InterpolationManager.adjust_offset`: queue correction replaces the
supplied frame origin and uses the same 0.05 m completion radius and 2× speed.
- Earlier live retail cdb trace:
`docs/research/2026-05-02-remote-entity-motion/resolved-via-cdb.md` — walking
remotes use queued position catch-up; their body velocity is not synthesized
from UpdatePosition.