fix(physics): activate collision generations atomically
This commit is contained in:
parent
3e0f3b6206
commit
be94bc9b06
18 changed files with 1402 additions and 80 deletions
72
docs/research/2026-07-31-atomic-collision-generation.md
Normal file
72
docs/research/2026-07-31-atomic-collision-generation.md
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# 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:
|
||||
|
||||
1. `BeginCollisionAdmission` issues the exact Runtime/landblock generation.
|
||||
2. `PrepareCollisionGeneration` clones 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.
|
||||
3. App and Headless publish terrain, EnvCells, topology, buildings, prepared
|
||||
collision assets, static owners, and retained dynamic-owner cell sets only
|
||||
into that private generation.
|
||||
4. Each dynamic owner refresh captures its exact `ShadowObjectRegistry`
|
||||
mutation version. Movement, state/payload change, spawn, suspension, or
|
||||
deletion changes that version.
|
||||
5. `CommitCollisionGeneration` validates 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.
|
||||
6. 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 `CollisionGenerationCommitted` and 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 `GotoLostCell` or change `SetPosition` recovery 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue