fix(physics): restore a cancelled park instead of leaving the entity withdrawn
Shipped-code defect affecting committed route-2 code, found while reviewing route 4b-1. RuntimeSetPositionState.ParkDeferred withdraws an entity from the world: body.InWorld = false, TransientStateFlags.Active cleared, WithdrawCanonical, SuspendObjectClock. CancelCoreDeferred then removed the operation and rewrote the pending Withdraw into a Discard while restoring NONE of it. So cancelling a wakeable park was strictly worse than keeping one — the park is wakeable, the cancel destroys the only object that could ever wake it, and the entity is left invisible AND intangible with nothing to bring it back. Route 2's re-issue funnel masked this: re-issuing is correct for a one-shot ForcePosition ACE never repeats, and wrong for a repeated remote stream, so the hole was hidden rather than fixed. Retail's own answer is a working park, verified in the decomp rather than assumed: CPhysicsObj::SetPositionInternal @0x00515BD0, when AdjustPosition yields no cell @0x00515C1D, calls prepare_to_leave_visibility @0x00515CDA, store_position @0x00515CE2 (the DESTINATION pose is committed), GotoLostCell @0x00515CF2 registering at m_position.objcell_id read AFTER store_position (so the destination cell), clears transient 0x80 @0x00515CF7, and returns OK @0x00515D07. InitObjCell @0x00508260 drains the lost list on cell load and calls reenter_visibility @0x00516250, which re-places from the object's OWN m_position with flags 0x11. Two corrections to the direction I gave, both forced by evidence and both right: The pose must NOT be rolled back — only the withdrawal. Three shipped route-2 tests capture positionAtPark AFTER the park and assert it survives the cancel, and retail agrees: store_position commits the destination and nothing un-commits it. Restoring residency at the body's committed cell is therefore retail's own cell choice, not merely self-consistent. The gate defaults to FALSE with four explicit opt-ins, rather than defaulting true with opt-outs at the withdrawal callers. That keeps every one of the ~20 shipped Forget/ForgetExactPlacement sites at exactly its current behaviour instead of depending on having correctly enumerated the withdrawal transactions. Review had already found the broad version corrupting five of them (TryApplyPickup, CommitAcceptedParent, CommitAcceptedParentCellless, CommitWithdrawal, CommitPositionChannelUpdate): they hand-roll a partial re-withdrawal that undoes the clock and FullCellId but not InWorld or the _spatialRoots re-registration, leaving a picked-up item both in inventory and an InWorld cellless spatial root in the physics workset. ParkDeferred's restorableOnCancel is opt-in for exactly one of its four callers — the plain unplaceable-destination park. Every quiescence and retirement park is excluded deliberately: those entities are withdrawn because their world is going away, and restoring residency inside a quiescing prefix blocks its retirement. VerifyPositionChannelCancellation now asserts InWorld and IsSpatialRoot per channel — Position is a cancellation and must restore; Pickup and Parent are withdrawals and must not. It previously asserted only !IsDeferred and counts, which is why five green states hid this. Register row AP-136 measured against GotoLostCell/reenter_visibility rather than labelled "retail-shaped". Files #309 (the restore-on-cancel residual, with park-survives recorded as the retail-faithful target and its two blockers named: the NewerPositionPickupAndParentEachCancelExactLostOperation invariant and teardown convergence) and #310 (an unbounded retirement stall — a retained preparation retry pins its prefix through HasOldPrefixPlacementDebt forever, and TickLostCellDeadlines has no production caller so the 25 s timer never fires). This is a user-observable change to shipped paths: restorableOnCancel: true sits in SubmitPreparedPlacementCore, the shared core behind every production placement. AP-136 and #309 carry the proposed two-client check. Gates: complete Release solution 10,973 passed / 4 skipped / 0 failed (baseline 10,938). Every new test discrimination-verified by reverting the fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
9e97be1896
commit
634bc5513a
9 changed files with 666 additions and 10 deletions
|
|
@ -307,8 +307,13 @@ public sealed class RuntimeAcceptedPositionDriveController
|
|||
_pending = null;
|
||||
RuntimeSetPositionState setPosition = _entityObjects.Physics.SetPosition;
|
||||
setPosition.ForgetPlacementCompletion(pending.Token);
|
||||
// Cancellation, not withdrawal: the placement intent is abandoned but
|
||||
// the local player stays in the world, so a DeferredCell park must be
|
||||
// rolled back rather than left stranding the body.
|
||||
RuntimePlacementCancellationReceipt cancellation =
|
||||
setPosition.ForgetExactPlacement(pending.Token);
|
||||
setPosition.ForgetExactPlacement(
|
||||
pending.Token,
|
||||
restoreCancelledPark: true);
|
||||
if (cancellation.IsValid)
|
||||
setPosition.PublishCancellation(cancellation);
|
||||
}
|
||||
|
|
@ -796,8 +801,11 @@ public sealed class RuntimeAcceptedPositionDriveController
|
|||
RuntimeSetPositionState setPosition,
|
||||
in RuntimeEntityPlacementToken token)
|
||||
{
|
||||
// Cancellation, not withdrawal - see AbandonPending.
|
||||
RuntimePlacementCancellationReceipt cancellation =
|
||||
setPosition.ForgetExactPlacement(token);
|
||||
setPosition.ForgetExactPlacement(
|
||||
token,
|
||||
restoreCancelledPark: true);
|
||||
if (cancellation.IsValid)
|
||||
setPosition.PublishCancellation(cancellation);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue