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

174 lines
11 KiB
Markdown

# 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.