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

11 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 creates empty private cache, graph, engine, and shadow facades and retains the active aggregate root reference in O(1). Global immutable GfxObj/Setup catalogs are not copied; the accepted build's exact closure is populated by the existing cursors.
  3. Stable landblock and logical-owner slot suffixes then materialize each non-target cache, CellGraph, engine, and shadow leaf into an empty private root under the host's existing frame meter. The 32-resident-landblock gate proves admission performs no resident copy and every advance reports at most one work unit.
  4. App and Headless publish terrain, EnvCells, topology, buildings, prepared collision assets, and target-root static owners only into that private generation.
  5. Stable per-prefix owner slots capture 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. The scan has a fixed slot suffix and is unaffected by mutations in other prefixes. Vacated slots are tombstoned and reused rather than retained for the whole session. A single Runtime-scoped versioned journal records each mutation once and coalesces repeated changes by logical owner, independently of the number of live drafts. After topology sealing, each draft reconciles the latest exact state of owners changed during that draft's lifetime one owner per seal call. A discovered relevant owner then receives scoped exact updates, preserving continuous-motion progress without restoring global fanout. A membership transition is routed by the owner's changed landblock prefix to the one matching draft, so an owner first entering or leaving the target after its global journal slot was visited is still reconciled once. During topology construction, a visited unrelated owner retains only a cheap coalesced notification; its exact mirror runs later as one metered seal unit rather than once per draft on the mutation path. Once the topology seal exists, observed owners temporarily write through exactly until same-call activation. The finite pre-seal queue therefore drains even when two or more unrelated owners mutate before every host step. Slots predating a newer root snapshot are superseded by a tail slot, not reused behind live cursors. New drafts begin at their captured suffix, obsolete slots compact one visit per seal call, and the journal clears when its last draft closes. Unrelated and continuously moving owners therefore never restart capture or sealing.
  6. 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. A late unarmed relevant owner consumes at most one refresh unit on a seal call; when that drains the queue an already-built seal is immediately ready. Immutable global GfxObj/Setup closure entries are preinstalled during these metered steps, not during activation.
  7. Cache, CellGraph, engine-landblock, and shadow topology share one CollisionWorldStateSlot. CommitCollisionGeneration transfers the complete off-side aggregate through one volatile reference on the update thread, then revokes the staging slot. The public PhysicsDataCache, CellGraph, PhysicsEngine, and ShadowObjectRegistry facade identities stay stable. Warm 256-owner, cold first-load, changed EnvCell/building, and new static-bucket gates all measure exactly zero managed bytes in final activation. Only afterwards does Runtime emit CollisionGenerationCommitted and a ready acknowledgement.
  8. Multiple landblocks may prepare concurrently. Preparation order is the activation order. Only after an older generation commits is its exact delta queued into every later draft. Each additional seal call applies at most one cache, CellGraph, synthesized outdoor-cell, engine-landblock, or logical owner leaf. Later generations retain their own completed target seal but cannot activate before every committed delta drains. Cancelled older drafts therefore contribute nothing, final activation performs no peer work, and a later root cannot overwrite or expose an older snapshot. Seam-crossing statics are forcibly re-evaluated against the later topology. Demotion and withdrawal cancel a matching queued or active rebase, suppress that prefix in unfinished source scans, and retire one shadow owner, cache/graph leaf, authored outdoor cell, or landblock leaf per later seal call before activation. Retirement storage is growable rather than coupled to the concurrent-preparation limit, and final commit rechecks both pending rebase and retirement work after the seal-to-commit gap. The host performs the zero-work root transfer in the same update-thread call that completes final reconciliation, eliminating a seal-to-next-frame quiet window for continuously moving unrelated owners.
  9. GfxObj/Setup closure entries are immutable content-addressed catalog data, not world topology. Their metered early installation may survive a cancelled generation as ordinary process cache residency; no cell, building, landblock, or shadow becomes visible through that catalog alone.

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;
  • unrelated movement on every capture/seal step never restarts the target;
  • two relevant owners moving on every seal step converge without restarting the topology meter and install their latest positions at activation;
  • an authoritative state change on a retained rowless owner updates an already-sealed generation without a global restart;
  • a neighboring static whose shadow crossed the seam is restored atomically on reload and its withdrawn-prefix marker clears only at activation;
  • a late spawn blocks activation until its one metered refresh; deletion of an armed owner writes through directly;
  • 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 warm 256-owner, cold first-load, changed EnvCell/building, and new static-bucket activation all allocate zero managed bytes;
  • concurrently prepared landblocks rebase and preserve both terrain roots and static-shadow owners across their activation order, with zero-byte final commits and revoked staging access;
  • dense 32-landblock admission performs no resident copy, stays within its constant allocation envelope, and materializes at most one leaf per advance;
  • cancelled older drafts contribute no topology to later roots, while a live owner mutation after the older commit wins over the queued rebase;
  • a newly committed seam-crossing static refloods against the later draft's topology before that draft may activate;
  • post-seal arrivals drain one owner per seal call without resetting capture;
  • an unrelated owner entering the target after its journal slot was visited is routed by prefix and reconciled in one metered seal unit;
  • target departure and same-ID reuse preserve the exact new-prefix owner rows;
  • unrelated state mutation publishes only after every row changes;
  • unrelated demotion/withdrawal and the live CurrCell cannot be resurrected or rolled back by a later draft;
  • queued and partially applied peer rebases cannot resurrect a later demoted or withdrawn landblock;
  • deleting an outgoing target static before, during, or after staging cannot erase an authored same-ID replacement;
  • 10,000 repeated mutations with 32 drafts retain one coalesced journal entry and allocate no more than the owner mutation itself;
  • 512 unique changed owners reconcile in exactly 512 metered seal units and the final activation still allocates zero managed bytes;
  • compacted journal slots are never reused behind a live cursor, while a 4,096-slot obsolete tail retires incrementally and a later draft starts at its captured suffix rather than scanning old tombstones;
  • post-seal retirement blocks activation until its cursor drains, and more than 256 distinct retirements remain metered and lossless;
  • prefix-owner slots remain bounded under GUID churn and empty containers are reclaimed across unique prefixes without invalidating a live seal cursor;
  • 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.