fix #174: RemoveLinkAnimations seam is HandleEnterWorld (strip + drain)

Retail CPhysicsObj::RemoveLinkAnimations (0x0050fe20) is a tailcall to
CPartArray::HandleEnterWorld (0x00517d70) -> MotionTableManager::
HandleEnterWorld (0x0051bdd0): remove_all_link_animations PLUS a full
pending_animations drain (while (head) AnimationDone(0)), each pop
relaying MotionDone so CMotionInterp pops its pending_motions node in
lockstep. acdream bound the seam to the bare sequence strip, so every
jump's LeaveGround removed the animations that queued manager nodes
were counting down on — orphaning them (NumAnims>0, anims gone) and
permanently damming BOTH queues. MotionsPending() then never drained
(probe round: last player pending=False at the first MovementJump
press; old jump motions still completing at rest minutes later) and
BeginTurnToHeading/BeginMoveForward's verbatim motions_pending gates
starved every armed moveto: ACE's mt-6 walk-to-door armed but the body
never walked (wire-proven, seqs 98-101); the close-range use turn
never completed so the deferred action was silently eaten. Doors only
worked on a fresh session (shallow queue).

Rebind both production sites (remote EnsureRemoteMotionBindings +
the player's EnterPlayerModeNow block) to Manager.HandleEnterWorld();
the sequencer wrapper was a pure passthrough so the manager call is a
strict superset. All six interp seam sites (LeaveGround, HitGround,
Dead, and the detached-object guards) are the same retail chain.
Harness mirrors updated; pins: Issue174LinkStripDrainTests (the seam
drains both queues; fresh motions queue and complete after). Suites:
Core 2535 / App 713 / UI 425 / Net 385 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-05 16:33:41 +02:00
parent 62b6fa52e8
commit b54555da62
6 changed files with 146 additions and 7 deletions

View file

@ -4288,7 +4288,20 @@ public sealed class GameWindow : IDisposable
rmForSink.ObservedOmega = System.Numerics.Vector3.Zero,
};
rm.Motion.DefaultSink = rm.Sink;
rm.Motion.RemoveLinkAnimations = ae.Sequencer.RemoveAllLinkAnimations;
// #174 (2026-07-05): the RemoveLinkAnimations seam is retail
// CPhysicsObj::RemoveLinkAnimations 0x0050fe20 — a TAILCALL to
// CPartArray::HandleEnterWorld 0x00517d70 →
// MotionTableManager::HandleEnterWorld 0x0051bdd0: strip the
// sequence's link animations AND drain pending_animations
// completely (each pop fires MotionDone → CMotionInterp pops its
// pending_motions node in lockstep). The previous binding was the
// bare sequence strip — every LeaveGround (jump) stripped the
// animations that queued manager nodes were counting down on,
// orphaning them (NumAnims>0, anims gone) and permanently damming
// BOTH queues: MotionsPending() never drained again, so every
// armed moveto (ACE's walk-to-door mt-6, the close-range use turn)
// starved — the "door only works on a fresh session" bug.
rm.Motion.RemoveLinkAnimations = () => ae.Sequencer.Manager.HandleEnterWorld();
rm.Motion.InitializeMotionTables = () => ae.Sequencer.Manager.InitializeState();
// R3-W5: the per-op zero-tick flush (CPhysicsObj::CheckForCompletedMotions
// 0x0050fe30 -> MotionTableManager.CheckForCompletedMotions).
@ -13697,7 +13710,14 @@ public sealed class GameWindow : IDisposable
// R3-W4: bind the player interp's retail seams to the player
// sequencer — LeaveGround/HitGround strip transition links here
// (the K-fix18 replacement).
_playerController.Motion.RemoveLinkAnimations = playerSeq.RemoveAllLinkAnimations;
// #174 (2026-07-05): the seam is HandleEnterWorld (strip + FULL
// queue drain), not the bare sequence strip — see the remote
// bind site's comment (retail 0x0050fe20 → 0x00517d70 →
// 0x0051bdd0). The bare strip orphaned every pending manager
// node on each jump's LeaveGround; the local player's
// pending_motions then never drained and every armed moveto
// starved (#174: doors unusable after the first jump/run).
_playerController.Motion.RemoveLinkAnimations = () => playerSeq.Manager.HandleEnterWorld();
_playerController.Motion.InitializeMotionTables =
() => playerSeq.Manager.InitializeState();
_playerController.Motion.CheckForCompletedMotions =