174 lines
11 KiB
Markdown
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.
|