fix(R4-V5): moveto wedge - StopCompletely's missing animation dispatch orphaned pending_motions; + the P1 autonomous store family
Live bug (2026-07-03, user door test): a server MoveTo armed (movingTo=True) but the local body never moved; the retail observer saw ACE walk the server-side player to the door; the next local input reasserted the stale position (rubber-band). Log evidence: the player's pending_motions queue was non-empty on 92/94 MotionDone pops (remotes: 0/40) - MoveToManager's retail wait-for-anims gate (BeginTurnToHeading's MotionsPending check) never opened. ROOT CAUSE (found via a production-faithful repro test - the original suite force-drained the queue, masking it): the R3 port MISIDENTIFIED retail's StopCompletely_Internal body. It is NOT a physics velocity zero - CPhysicsObj::StopCompletely_Internal (0x0050ead0) tailcalls CPartArray::StopCompletelyInternal (0x00518890) which is MotionTableManager::PerformMovement(type 5): an ANIMATION-side full stop whose UNCONDITIONALLY-queued pending_animations entry is the completable partner of the A9 pending_motions node CMotionInterp::StopCompletely enqueues (raw @00527e90 between the state writes and add_to_queue). Without the dispatch, every StopCompletely (login/teleport SetPosition + every MoveToManager PerformMovement head) left an orphan node with no completion - the queue never drained at idle. (The R3 code comment claimed a register row for the stand-in; none was ever added - porting the real mechanism closes that violation without a row.) Fix set (all retail-anchored): - IInterpretedMotionSink gains StopCompletely() (default true = the null-sink posture); MotionTableDispatchSink routes it to the R2 manager's already-ported type-5 op (StopObjectCompletely + Ready-sentinel entry) + TurnStopped for the ObservedOmega seam. - MotionInterpreter.StopCompletely calls DefaultSink?.StopCompletely() at the exact retail slot (after the state writes, before add_to_queue); the immediate set_velocity(Zero) is kept as the documented physics-effect stand-in. - PlayerMovementController ticks CheckForCompletedMotions after UseTime - retail's per-tick CPartArray::HandleMovement slot (0x00517d60 = MotionTableManager::UseTime = CheckForCompletedMotions tailcall, called every tick from UpdateObjectInternal @005159a4). NOTE: a first-cut per-tick apply_current_movement pump was tried and REVERTED - retail's apply callers are all event-driven (hold-key, HitGround/LeaveGround, ReportExhaustion), not per-tick; the remote block's per-tick apply is a pre-existing adaptation outside this fix's scope. - The P1 autonomous-store family, completing the pin's port shape (the gate landed in V5; the STORE side was missing): (a) the unpack store (00509730: last_move_was_autonomous = wire byte) in the GameWindow player branch - local player only for now (remote interps have no WeenieObj, which A3 reads as IsThePlayer; storing a remote's autonomous byte would mis-route their per-tick apply onto the raw branch); (b) input edges store =1 at the controller edge block - retail's CPhysicsObj::DoMotion @00510030 / StopMotion @005100e0 / TakeControlFromServer @006b32f4 input-boundary stores; (c) section 2's per-frame velocity write now PRESERVES the flag (V5's first cut stamped it per frame, the wrong altitude - retail stores it at event boundaries only); (d) InstallSpeculativeTurnToTarget stores false (models the wire mt-6's unpack store; part of the AP-23 adaptation) so the event-driven applies (e.g. HitGround mid-moveto) route interpreted and cannot clobber the manager's dispatched motions with idle raw state. Tests: new ServerMoveToPosition_ProductionCompletionFeed_WalksToArrival drives the controller under the PRODUCTION completion feed (sequencer.Advance -> MotionDoneTarget) instead of force-draining - this is the test that reproduced the live wedge (queue stuck at the two A9 Ready orphans) and now proves the drain. Existing cutover tests mirror the wire-path autonomous store before PerformMovement. Full suite green: 3,957. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b87726dc2c
commit
c2dc1a889c
6 changed files with 201 additions and 12 deletions
|
|
@ -4690,6 +4690,19 @@ public sealed class GameWindow : IDisposable
|
|||
// R5/MovementManager scope — V0-pins.md P1 adjacent seam).
|
||||
if (_playerController is not null)
|
||||
{
|
||||
// P1 tail (00509730): the unpack path stores the wire
|
||||
// autonomous byte BEFORE unpack_movement — always false
|
||||
// here (the gate above dropped autonomous events). This
|
||||
// is what routes the controller's per-tick pump (A3
|
||||
// dual dispatch) to the INTERPRETED branch during a
|
||||
// server moveto. LOCAL PLAYER ONLY for now: remotes'
|
||||
// interps have no WeenieObj, which A3 treats as
|
||||
// IsThePlayer — storing a remote player's autonomous
|
||||
// byte would flip their per-tick apply onto the raw
|
||||
// branch and clobber their funnel state; the remote
|
||||
// store lands with real remote weenies (R5+).
|
||||
_playerController.SetLastMoveWasAutonomous(update.IsAutonomous);
|
||||
|
||||
_playerController.Motion.InterruptCurrentMovement?.Invoke();
|
||||
_playerController.Motion.UnstickFromObject?.Invoke();
|
||||
|
||||
|
|
@ -12336,6 +12349,13 @@ public sealed class GameWindow : IDisposable
|
|||
? AcDream.Core.Physics.MovementType.TurnToObject
|
||||
: AcDream.Core.Physics.MovementType.MoveToObject,
|
||||
};
|
||||
// Part of this install's AP-23 adaptation: store the autonomy flag
|
||||
// exactly as the wire mt-6 this install anticipates would (the P1
|
||||
// unpack store, IsAutonomous=false) — without it the per-tick
|
||||
// pump's A3 dispatch stays on the raw branch (the user's last input
|
||||
// set it autonomous) and clobbers this moveto's dispatched motions
|
||||
// with the idle raw state.
|
||||
_playerController.SetLastMoveWasAutonomous(false);
|
||||
playerMoveTo.PerformMovement(ms);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue