feat(physics): define deterministic flat collision assets

Add immutable indexed physics and containment BSP records, exact-bit polygon and Setup payloads, separated CellStruct/topology ownership, and iterative positive-before-negative flattening. Reject malformed graphs and ranges, and prove source identity over synthetic edge cases and installed retail DAT samples without cutting production traversal over.
This commit is contained in:
Erik 2026-07-25 14:52:47 +02:00
parent 16d182c2f0
commit d9300c7854
11 changed files with 2094 additions and 21 deletions

View file

@ -0,0 +1,104 @@
# Slice I2 — deterministic immutable collision assets
**Status:** COMPLETE
**Parent:** `docs/plans/2026-07-25-modern-runtime-slice-i.md`
**Scope:** Core collision storage and preparation only. Production movement,
BSP traversal, streaming publication, and package format are unchanged.
## Result
Slice I2 defines the immutable representation that will replace parsed
collision DBObj graphs after the later shadow and differential gates:
- physics BSP nodes in deterministic positive-before-negative pre-order;
- explicit signed child indices, where `-1` means absent;
- one ordered leaf polygon-index stream;
- polygon rows with checked vertex ranges and one contiguous vertex stream;
- a separate, smaller cell-containment BSP schema;
- ordered Setup cylinder/sphere rows and verbatim dimensions;
- separate reusable CellStruct geometry and per-EnvCell topology;
- direct prepared indices from portals and BSP leaves to polygon rows;
- optional GfxObj root bounds and visual bounds.
All collections are immutable arrays. Per-EnvCell transforms remain
publication state and are deliberately absent from reusable CellStruct
geometry.
## Retail and source ordering
The storage contract is pinned to the existing retail traversal oracle:
- `BSPTREE::find_collisions` `0x0053A440`
- `BSPNODE::find_walkable` `0x0053CC80`
- `BSPLEAF::find_walkable` `0x0053D6F0`
The source graph is flattened iteratively. A stack pushes the negative child
before the positive child, so the output records the current node, then its
complete positive subtree, then its complete negative subtree. Leaf polygon
references stay in their stored order. Polygon-table rows are sorted by ID,
which makes output independent of dictionary insertion and bucket order;
leaves and portals resolve IDs to direct indices during preparation.
No plane is reconstructed and no vector is normalized. `Plane`, `Vector3`,
sphere, cylinder, Setup-dimension, and visual-bound floats are copied directly.
Tests compare all components through `BitConverter.SingleToInt32Bits`.
## Corruption contracts
Construction rejects:
- unsupported BSP node types or child presence inconsistent with the encoded
node type;
- source cycles and shared child nodes;
- flat cycles, shared children, unreachable nodes, and non-pre-order layouts;
- child, polygon-index, polygon, or vertex ranges outside their arrays;
- missing leaf or portal polygon IDs;
- duplicate/unsorted polygon IDs or visible-cell IDs;
- polygon key/row-ID disagreement or point-count disagreement;
- default immutable arrays and null source rows.
An empty tree uses root index `-1`. A populated deterministic tree always uses
root index `0`.
## Evidence
The synthetic suite covers:
- positive/negative child ordering;
- multi-polygon leaf order;
- dictionary-order independence;
- negative-zero and nontrivial float payload preservation;
- source mutation after preparation;
- missing IDs, invalid ranges, malformed child patterns, cycles, shared
children, and unreachable/non-pre-order shapes;
- asymmetric containment trees;
- a 20,000-node containment tree without call-stack recursion;
- Setup row order and exact dimensions;
- portal direct-index resolution and deterministic visible-cell ordering.
Installed retail DAT coverage:
- Facility Hub cells `0x8A02016E` and `0x8A02017A`;
- Holtburg cottage cell `0xA9B4013F`;
- cottage, stair/ramp, and multipart-door GfxObjs `0x01000A2B`,
`0x01000AC5`, and `0x010044B5`;
- player, wide-low collision-family, and multipart-door Setups `0x02000001`,
`0x020019E3`, and `0x020019FF`.
For every installed sample, tests compare source node type, plane bits,
positive/negative child identity, leaf index, solid marker, bounds, polygon
order, polygon metadata, vertex bits, Setup shapes, dimensions, and visual
bounds.
Release gates:
- I2 focused structural/installed-DAT tests: **13 passed**
- expanded Slice I graph + flat oracle: **131 passed**
- complete Core tests: **3,263 passed / 2 skipped**
- `dotnet build AcDream.slnx -c Release --no-restore`: **passed**
- complete solution tests: **8,355 passed / 5 skipped**
There is intentionally no traversal cutover and no new retail divergence in
this commit. Slice I3 serializes these records into the package, adds typed
prepared collision access, and bakes the complete installed catalog before any
flat query can affect gameplay.