Root cause: pending-only live projection buckets were misclassified as landblock presentation owners during origin recentering. That manufactured a second full cleanup receipt for a generation whose first receipt was still advancing; the duplicate guard threw and the broad retry path replayed the already-committed detach 243 times. Keep pending live projections through the spatial identity map without issuing another receipt, and fail fast when a receipt-ledger invariant occurs after detachment. Evidence: docs/research/2026-08-02-collision-throughput-handoff/p1-retirement-receipt-loop.md. Release suite, lifecycle gate, and nine-stop soak pass.
4.3 KiB
P1 — origin-recenter retirement-receipt loop
Observed failure
launch-feeltest-oclone.log contains 243 consecutive failures with this
shape:
streaming: origin-recenter preparation will resume:
InvalidOperationException: Landblock 0xC85AFFFF already has a full
retirement receipt.
The stack is StreamingController.TryAdvanceOriginRecenterPreparation →
LandblockPresentationPipeline.DetachAllForOriginRecenter →
LandblockRetirementCoordinator.AdoptDetachedFull.
Root cause
An ordinary full retirement detaches every landblock-owned presentation
resource first, then parks surviving live entities in
GpuWorldState._pendingByLandblock while the exact cleanup ticket advances
asynchronously (GpuWorldState.DetachLandblock, around lines 1188–1314).
The origin-recenter swap incorrectly treated every pending-only live bucket
as another landblock presentation generation (GpuWorldState.cs, former
lines 1352–1353). It therefore emitted a second full cleanup receipt for the
same already-retired generation. LandblockRetirementCoordinator correctly
rejected that duplicate at lines 416–425. Because spatial detachment had
already committed, the broad retry catch in
StreamingController.TryAdvanceOriginRecenterPreparation then repeated the
detach against the changed state every frame.
The pre-fix regression test
OriginRecenterAdoption_PendingOnlyLiveProjectionDoesNotCreateSecondFullReceipt
failed because the recenter returned one receipt for the pending-only bucket.
Retail and reference boundary
Retail destroys one concrete landblock owner synchronously:
CLandBlock::destroy_static_objects (0x0052FA50) leaves and deletes the
landblock's static objects; CLandBlock::Destroy (0x0052FAA0) releases its
buildings and landblock data; CLandBlock::release_all (0x0052FCF0)
releases the landblock's object and visibility ownership. A live object
parked outside a loaded landblock is not a second CLandBlock and therefore
cannot create a second landblock-destruction transaction.
The extracted WorldBuilder reference follows the same ownership boundary:
ObjectRenderManagerBase removes an actual _landblocks entry before
UnloadLandblockResources, and PortalRenderManager only unloads a removed
PortalLandblock. Neither treats an independently parked object as a new
landblock resource owner.
Acdream retains its approved asynchronous adaptation: the first exact receipt owns cleanup, while the live projection survives spatial recentering.
Fix
GpuWorldState.DetachAllForOriginRecenterno longer creates retirement receipts from_pendingByLandblockalone. Pending live identities are still captured from_projectionLocations, cleared atomically, and re-parked unchanged.- A landblock that also owns loaded, pending-render, pending-near, tier, or bounds state still receives its exact full receipt.
- A receipt-ledger invariant thrown after spatial detachment is now surfaced
as a committed
StreamingMutationException; it is terminal rather than falsely logged as resumable work. - The genuine duplicate-receipt guard remains unchanged.
Deterministic evidence
- The new pending-only regression failed before the source fix and passes afterward.
OriginRecenter_PendingOnlyLiveProjectionKeepsItsExistingRetirementOwnerdrives the production recenter/controller sequence and proves the origin commits while the first cleanup ticket remains pending.OriginRecenter_CommittedReceiptInvariantFailsFastInsteadOfReplayingDetachproves a genuine post-detach ledger violation surfaces once rather than entering a frame-by-frame retry loop.- The complete
OriginRecenterfocused group passes 20/20.
Gate evidence
- Release build: 0 errors (21 pre-existing warnings).
- Complete Release suite: 10,815 passed, 0 failed, 4 skipped.
- Connected lifecycle/reconnect gate:
logs/connected-world-gate-20260802-203751/report.json—Passed=true. - Connected nine-stop soak:
logs/connected-r6-soak-20260802-204309.report.json—Passed=true,Failures=[], graceful exit, all 9 canonical checkpoints present, no wait cue, no pending landblock retirement, no reveal invariant failure, and no render-shadow mismatch. - The soak artifacts contain zero occurrences of
already has a full retirement receipt; the captured failing session had 243.