3.7 KiB
Atomic collision-generation activation (Slice 3B)
Retail anchor
Retail hydrates a cell synchronously. CObjCell::init_objects
(0x0052B420) visits objects associated with that cell and invokes
CPhysicsObj::recalc_cross_cells (0x00515A30). The final position path also
replaces shadows as one SetPositionInternal operation (0x00515330). Retail
therefore never exposes a world where the new cell exists but the objects that
overlap it still have their old cross-cell set.
Acdream streams a landblock over several update frames. Literal per-cell
mutation during those frames was not equivalent: the active PhysicsDataCache,
CellGraph, PhysicsEngine, buildings, static shadows, and retained-object
refloods changed at different cursors. Collision queries could observe a mixed
generation, and correctness depended on a later optional landblock callback.
Ported adaptation
The asynchronous unit is now one Runtime-owned collision generation:
BeginCollisionAdmissionissues the exact Runtime/landblock generation.PrepareCollisionGenerationclones the bounded resident spatial records into a private cache, graph, engine, and shadow registry. Global immutable GfxObj/Setup catalogs are not copied; the accepted build's exact closure is populated by the existing cursors.- App and Headless publish terrain, EnvCells, topology, buildings, prepared collision assets, static owners, and retained dynamic-owner cell sets only into that private generation.
- Each dynamic owner refresh captures its exact
ShadowObjectRegistrymutation version. Movement, state/payload change, spawn, suspension, or deletion changes that version. CommitCollisionGenerationvalidates the admission, exact affected-owner set, and all captured versions. If any owner is dirty, it returns the sorted dirty IDs without touching the active world; App advances those IDs through its existing work meter and retries.- Once fresh, Runtime precomputes the replacement arrays and synchronously
replaces the landblock's cache/graph/engine/building/static and dynamic
shadow state on the same update thread. Only after the complete replacement
does it emit
CollisionGenerationCommittedand a ready acknowledgement.
The stable borrowed PhysicsEngine and PhysicsDataCache object identities do
not change. Presentation and no-window hosts use the same Runtime transaction.
Network workers still enqueue immutable messages and cannot mutate collision or
shadow state.
Failure and lifetime rules
- A newer admission invalidates an older prepared generation.
- Demotion, withdrawal, reset, and disposal invalidate the admission before changing the active generation.
- Disposing a stale/cancelled prepared generation clears only its private engine/cache/shadows.
- The prior complete generation remains queryable throughout preparation.
- The commit notification is the future lost-cell-registry seam. Slice 3B does
not implement
GotoLostCellor changeSetPositionrecovery behavior.
Deterministic evidence
The focused Runtime/App tests pin:
- previous terrain/cells/buildings/statics remain visible until commit;
- exactly one notification after a successful complete activation;
- stale admission replacement has no active-world side effect;
- movement during staging rejects, refreshes only the dirty owner, and then installs its latest cell set;
- spawn and deletion during staging both reject stale activation;
- graphical and no-window publishers use the same Runtime transaction;
- removal and terminal teardown converge the active ownership ledger.
This retires divergence row AD-6. The remaining lost-cell state-machine work is deliberately outside this slice.