fix(streaming): stop replaying committed recenter retirements

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.
This commit is contained in:
Erik 2026-08-02 20:53:11 +02:00
parent c65559d8f8
commit 01f4791e95
9 changed files with 309 additions and 9 deletions

View file

@ -1349,8 +1349,14 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
if (id != 0u)
AddId(id);
}
foreach (uint id in _pendingByLandblock.Keys)
AddId(id);
// A pending-only bucket owns live spatial projections, not a loaded
// landblock presentation generation. Those projections are retained
// below through _projectionLocations, but there is no terrain,
// collision, static-script, or renderer owner to retire. In
// particular, DetachLandblock deliberately parks surviving live
// projections here while its existing exact retirement receipt is
// still advancing. Emitting another full receipt during a recenter
// would give the same retired generation two cleanup owners.
foreach (uint id in _pendingRenderIdsByLandblock.Keys)
AddId(id);
foreach (uint id in _pendingNearTierLandblocks)