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