acdream/docs/research/2026-07-31-atomic-collision-generation.md

4.8 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:

  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, and target-root static owners only into that private generation.
  4. A mutation-stable cursor captures every non-suspended owner that touches — or has a withdrawn repair marker for — the target prefix. That includes live dynamic owners and statics rooted in an adjacent landblock. Only a target-root static is omitted, because the authored replacement supersedes it. Each retained owner refresh captures its exact ShadowObjectRegistry mutation version; a rowless withdrawn owner therefore remains freshness-gated when state or payload changes.
  5. Explicit one-work-unit cursors build the complete replacement before the activation frame: requested global collision records, cells/topology, buildings, cell graph removals, affected static owners, retained-owner states, and removal lists. An active-owner mutation restarts capture and sealing without touching the active world.
  6. CommitCollisionGeneration performs only the final mutation-version check and installs the already sealed replacement synchronously on the update thread. The dense 256-owner gate measures zero managed bytes in this final activation. Only after the complete replacement does Runtime 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.
  • Cancellation names one admission and its private staging generation. It can never withdraw or demote the active landblock, and cancelling a stale receipt cannot invalidate a newer admission.
  • 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;
  • an authoritative state change on a retained rowless owner rejects a stale seal and installs the refreshed state on retry;
  • a neighboring static whose shadow crossed the seam is restored atomically on reload and its withdrawn-prefix marker clears only at activation;
  • spawn and deletion during staging both reject stale activation;
  • Headless faults immediately after admission and after staging preserve the prior complete world and leave no collision admission behind;
  • dense sealing consumes at most one work unit per call, while its final 256-owner activation allocates zero managed bytes;
  • 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.