fix(physics): validate retail cell containment roots

This commit is contained in:
Erik 2026-07-31 14:48:26 +02:00
parent 7716c2ee89
commit 3e0f3b6206
23 changed files with 429 additions and 197 deletions

View file

@ -64,8 +64,10 @@ accepted-divergence entries (#96, #49, #50).
## 2. Adaptation (AD) — 44 active rows
Recent retirements: AD-3/AD-4 retired 2026-07-31 by the exact loaded-cell
availability and null-root containment port; AD-25 retired 2026-07-30 by the
Recent retirements: AD-3/AD-4 retired 2026-07-31 by exact active/per-candidate
visible-cell availability, full-catalog containment-root validation, and the
zero-portals point-in-cell guard (rootless payloads are quarantined; only a
missing positive child below a valid root is the inside base case); AD-25 retired 2026-07-30 by the
shared `PhysicsObjUpdate.HandleAllCollisions` remote path; AD-11 retired
2026-07-23 by the exact low-bit ItemUses predicate; AD-31 retired 2026-07-15
by the DAT-authored portal-space viewport. Recent additions and splits:

View file

@ -178,13 +178,18 @@ package schema, bake, DAT reader, collision formula, or render portal graph
changed. Evidence:
`docs/research/2026-07-26-prepared-indoor-transit-regression.md`.
**Cell availability semantics (2026-07-31).** Raw and prepared CellStruct
publication now retains a `CellPhysics` record even when authored physics or
containment roots are empty. Payload absence is the unavailable state; a loaded
null/-1 containment root keeps retail's universal-inside BSP base case.
Registration-side outdoor floods still add outside cells but skip transit when
the active CLandCell is unavailable, then recover through the existing reflood
after terrain/cell hydration. No package schema or DAT reader changed.
**Cell availability semantics (2026-07-31, corrected after full-catalog
audit).** Raw and prepared CellStruct publication retains a `CellPhysics`
record when the physics root is empty but requires a valid containment root.
The installed 729,888-record raw and prepared catalogs contain zero rootless
containment payloads. A malformed null/-1 root is quarantined atomically; the
recursive inside base case applies only to a missing positive child below a
valid root. Registration-side outdoor floods still add outside cells but skip
transit when the active CLandCell is unavailable, and every later outdoor
candidate independently requires its own visible landcell before building
transit. The existing reflood retries after terrain/cell hydration. Both raw
and prepared point-in-cell paths preserve retail's zero-portals guard. No
package schema or DAT reader changed.
Evidence: `docs/research/2026-07-31-cell-availability-semantics.md`.
**Retail VFX hook compatibility seam (2026-07-14).** Chorizite.DatReaderWriter

View file

@ -1,26 +1,41 @@
# Retail cell availability and null-root containment — 2026-07-31
# Retail cell availability and containment-root validation — 2026-07-31
## Scope
This note closes divergence rows AD-3 and AD-4. It does not begin AD-6's
atomic streaming-generation work.
The bug was one collapsed state. acdream treated all three of these as
“containment unavailable”:
The corrected port distinguishes these states:
1. no visible cell payload is loaded;
2. a loaded CellStruct has a null containment root;
3. a loaded CellStruct has an authored containment root.
2. a malformed raw/prepared payload has no containment root;
3. a loaded CellStruct has a valid authored containment root (its physics root
may independently be absent).
Retail distinguishes (1) from (2). A failed visible-cell lookup is
unavailable. A loaded CellStruct remains a real cell even when its BSPTREE
root is null, and the containment query's null-node base case is inside.
Only (3) is published. State (1) remains unavailable and retryable. State (2)
is quarantined atomically so a later valid hydration can retry; it must not
become a world-wide containing cell.
## Installed-data audit
The complete installed EoR catalog and matching prepared package were audited
before choosing this invariant:
- enumerated EnvCells: **729,888**;
- raw: 0 missing EnvCells, 0 missing Environments, 0 missing CellStructs,
0 null `CellBSP` objects, **0 null `CellBSP.Root`**, 729,888 valid roots;
- prepared `acdream.pak`: 0 missing aliases, 0 corrupt payloads,
**0 `ContainmentBsp.RootIndex < 0`**, 729,888 valid roots;
- 6,940 raw EnvCells have zero portals, so the retail portal-pointer guard is
a real catalog path rather than dead defensive code.
There are therefore no root-null record IDs to preserve in either source.
## Retail oracle
`CObjCell::find_cell_list @ 0x0052B4E0` in
`docs/research/named-retail/acclient_2013_pseudo_c.txt:308742` establishes the
availability gate:
availability gates:
- `CEnvCell::GetVisible` / `CLandCell::GetVisible` resolves the active seed at
`0x0052B50C..0x0052B515`;
@ -31,32 +46,47 @@ availability gate:
- each later candidate is independently skipped when its stored cell pointer
is null at `0x0052B58E`.
`CCellStruct::point_in_cell @ 0x005338F0` delegates directly to
`BSPTREE::point_inside_cell_bsp @ 0x005398C0`. The already-ported graph and
flat BSP queries preserve the retail null-root base case: a negative/null root
returns true. Root presence is therefore not an availability predicate.
`CEnvCell::point_in_cell @ 0x0052C300` first returns false when
`this->portals == 0`, then transforms the point and calls
`CCellStruct::point_in_cell`.
`CCellStruct::point_in_cell @ 0x005338F0` calls
`BSPTREE::point_inside_cell_bsp @ 0x005398C0`, which immediately invokes
`BSPNODE::point_inside_cell_bsp(this->root_node, ...)`. The BSP node method at
`0x0053C1F0` dereferences `this` before walking positive children. Only a
missing **positive child below a valid root** is the inside terminal case. A
missing root is not.
## Ported behavior
- `PhysicsDataCache` now publishes a `CellPhysics` record whenever an authored
raw or prepared CellStruct payload exists, even if its physics BSP and/or
containment BSP root is absent.
- `CollisionTraversal.HasCellContainment` tests representation payload
availability, not `Root` / `RootIndex`. `PointInsideCell` then lets the
graph or flat query return true for the null-root base case.
- `PhysicsDataCache` publishes graph, collision, and prepared records only
after a valid raw/prepared containment root is present. A missing physics
root is retained as a valid non-colliding cell. Invalid containment
publication changes no cache, so later hydration can retry.
- `CollisionTraversal.HasCellContainment` tests `Root` / `RootIndex`.
- Both raw and prepared `EnvCell.PointInCell` paths apply the zero-portals
guard before containment. `CellTransit` applies the same guard to its
`CellPhysics` representation.
- `CellTransit.BuildShadowCellSet` still seeds all overlapped outdoor cells,
but skips the transit walk when the active outdoor seed cannot be resolved
from `CellGraph`. A separately cached building can no longer promote an
object through an unavailable landcell.
from `CellGraph`. Every later outdoor candidate independently resolves via
`GetVisible` before building transit, so a stale building cannot promote an
object through an unavailable adjacent landcell.
- The existing reflood lifecycle remains the recovery mechanism. Once terrain
or an indoor CellStruct publishes, the next reflood walks the same authored
or a valid indoor CellStruct publishes, the next reflood walks the authored
portal/building relationships without reconstructing a different rule.
## Gates
Focused tests cover raw graph and prepared flat cache publication, absent
versus loaded-null-root containment, indoor and outdoor seeds, preservation of
outside-cell seeding, suppression of spurious building promotion, and
hydration/reflood recovery. Final Release gates passed: Core 4,162 / 1 skipped,
Runtime 440 / 0 skipped, App 4,002 / 3 skipped, plus the complete solution
build with zero errors.
Focused tests cover raw/prepared rootless quarantine and valid retry, valid
containment with missing physics, raw/prepared zero-portal parity, indoor and
outdoor seeds, preservation of outside-cell seeding, per-candidate adjacent
landcell availability, suppression of stale-building promotion, and
hydration/reflood recovery. The corrective checkpoint passes:
- focused cell-availability suite: **54/54**;
- Core Release: **4,165 passed / 1 skipped**;
- Runtime Release: **440/440**;
- App Release: **4,002 passed / 3 skipped**;
- complete Release solution: **10,122 passed / 4 skipped**;
- `dotnet build AcDream.slnx -c Release`: **0 warnings / 0 errors**.