feat(R3-W5): DoMotion family verbatim + the ONE DoInterpretedMotion + per-op zero-tick flush (closes J3, J4, J9, J14)

The two parallel DoInterpretedMotion implementations MERGE into one
verbatim pair (0x00528360/@305639): contact gate, StandingLongJump
state-only branch, Dead → RemoveLinkAnimations, the jump_error_code
double-check WITH the DisableJumpDuringLink 0x20000 leg (W2's TODO(W5)
resolved — MovementParameters now flows), ModifyInterpretedState
gating, CurCell-null tail, AddToQueue with the real ContextId;
StopInterpretedMotion's post-stop Ready node + raw-mirror RemoveMotion.
Legacy overloads + ApplyMotionToInterpretedState DELETED. The funnel's
public surface unchanged (183-case suite compiles + passes as-is).

DoMotion 0x00528d20 verbatim: cancel_moveto interrupt; SetHoldKey(key,
cancelMoveToBit) BEFORE adjust_motion; params re-default for the
interpreted call; combat-stance gates on the ORIGINAL id (Crouch/Sit/
Sleep → 0x3f/0x40/0x41; & 0x2000000 → 0x42 outside NonCombat); action
depth cap ≥6 → 0x45; raw mirror ORIGINAL id. StopMotion 0x00528530
mirror shape. StopCompletely 0x00527e40 with the A9 snapshot quirk
(motion_allows_jump on the OLD forward command, stashed in the queued
node) + the J9 fix (sidestep/turn SPEEDS untouched — the 1.0 resets
were a divergence). PerformMovement flushes zero-tick completions after
every dispatched op via the new CheckForCompletedMotions seam
(0x0050fe30) — bound App-side per entity (remotes via
EnsureRemoteMotionBindings, player at the sequencer bind site).

PORT DISCOVERY (not in the W0 pins): retail's DoInterpretedMotion
RESULT gates both add_to_queue AND the state write — a void sink let
the style dispatch's apply-only failure clobber ForwardCommand before
the forward axis read it, regressing 74/183 live-trace cases.
IInterpretedMotionSink.ApplyMotion/StopMotion now return bool
(documented on the interface with the raw anchors); one funnel
assertion corrected with raw-line citations (airborne ForwardCommand
ends at Falling under the fully-wired verbatim algorithm).

62 new gate-table tests. Full suite: 3,729 passed.

Implemented by a dedicated agent against the W0-pinned spec; seam
bound + suite independently re-verified by the orchestrator.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-03 08:57:33 +02:00
parent e214acdf23
commit df7b096d6f
7 changed files with 1522 additions and 241 deletions

View file

@ -24,14 +24,38 @@ public interface IInterpretedMotionSink
/// (<c>PerformMovement</c> → GetObjectSequence + is_allowed decide) —
/// no sink-side axis pick or fallback chain.
/// </summary>
void ApplyMotion(uint motion, float speed);
/// <returns>
/// R3-W5: retail's own <c>CPhysicsObj::DoInterpretedMotion</c> RETURNS a
/// result (<c>result == 0</c> = the motion table found a cycle for this
/// id; nonzero = <c>MotionTableManagerError.MotionFailed</c>/no-table) —
/// <c>CMotionInterp::DoInterpretedMotion</c>'s caller (raw 305591-305610)
/// gates BOTH the <c>add_to_queue</c> call AND the
/// <c>InterpretedMotionState::ApplyMotion</c> state-write on that result.
/// This matters concretely for the very first dispatch of every
/// <c>apply_interpreted_movement</c> call — the STYLE/stance id
/// (<c>current_style</c>, always <c>&gt;= 0x80000000</c>) has no
/// locomotion <c>MotionData</c> entry in the dat, so
/// <c>CMotionTable::DoObjectMotion</c> genuinely fails for it; if that
/// failure isn't propagated, the state-write's negative-motion branch
/// (<see cref="InterpretedMotionState.ApplyMotion"/>: <c>arg2 &lt; 0 →
/// forward_command = 0x41000003</c>) clobbers <c>ForwardCommand</c>
/// BEFORE the very next line reads it for the real forward dispatch —
/// confirmed regression against the 183-case live retail-observer trace
/// (<c>RetailObserverTraceConformanceTests</c>) when this was <c>void</c>.
/// Return <c>true</c> when the underlying <c>PerformMovement</c> call
/// succeeded (<c>MotionTableManagerError.Success</c>).
/// </returns>
bool ApplyMotion(uint motion, float speed);
/// <summary>
/// <c>StopInterpretedMotion</c> notification for an axis the incoming
/// state cleared (sidestep 0x6500000F / turn 0x6500000D). Unconditional
/// (no contact gate) per apply_interpreted_movement.
/// </summary>
void StopMotion(uint motion);
/// <returns>Same result-propagation rationale as <see cref="ApplyMotion"/>
/// — retail's <c>CPhysicsObj::StopInterpretedMotion</c> also returns a
/// result that gates the post-stop <c>add_to_queue</c> + state-removal.</returns>
bool StopMotion(uint motion);
}
/// <summary>