acdream/docs/plans/2026-07-25-modern-runtime-slice-i.md
Erik 2effba5127 docs(rendering): close retained-scene G5
Record the exact visual, route, lifecycle, production CPU/GPU/memory, and allocation evidence for retiring the normal InteriorEntityPartition. Reconcile the roadmap, milestones, shared session instructions, and Slice I handoff so the measured transition/query scratch remainder has one owner.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-25 13:55:38 +02:00

277 lines
13 KiB
Markdown

# Modern runtime Slice I — flat collision assets and zero-allocation physics
**Status:** I0 COMPLETE — retail oracle, representative graph fixtures, and
four-mover allocation baselines fixed; G5 production closeout complete; I1
active
**Parent:** `2026-07-24-modern-runtime-architecture.md`, Slice I
**Purpose:** remove parsed DAT object graphs and steady collision allocations
without changing one retail collision decision, float, comparison, or traversal
order.
## 1. Non-negotiable boundaries
1. Retail collision math is frozen. This slice changes storage and scratch
ownership only.
2. Every float read from DAT is copied bit-for-bit. The bake performs no
normalization, quantization, coordinate conversion, `double` round-trip, or
plane reconstruction.
3. Positive/negative child order, polygon order within every leaf, early
returns, epsilon comparisons, and contact-selection tie behavior remain
exact.
4. The current object-graph traversal remains an executable oracle until the
flat traversal passes mass differential and connected gates.
5. Physics `Transition` reuse lands only after reset-completeness,
cross-mover-identity, failure, and reentrancy tests prove that no state can
leak between resolves.
6. No GL/App dependency enters Core. Flat collision records live in
`AcDream.Core`; serialization and package access live in `AcDream.Content`;
the bake tool produces them offline.
7. The active G4 render cutover is independent. Its exact visual rollback
remains:
```text
git revert ef1d263337997bb030eadb7b8e71d73dc659907a
```
## 2. Target data architecture
The runtime collision representation is immutable, array-backed, and addressed
by integer index:
```text
FlatCollisionAsset
├── FlatPhysicsBsp
│ ├── FlatBspNode[] child indices; -1 = no child
│ ├── int[] leaf polygon-index stream
│ ├── FlatPolygon[] plane, cull mode, id, vertex range
│ └── Vector3[] verbatim polygon vertices
├── FlatCellContainmentBsp split planes + positive/negative indices
├── FlatSetupCollision cyl/sphere records + verbatim dimensions
└── EnvCellTopology portals, visible cells, seen-outside
```
Node order is deterministic pre-order. The flattener records the original
positive child before the negative child and retains each source leaf's
polygon list order. Traversal follows indices using the same recursive
control flow as the current retail port; “flat” does not mean a new collision
algorithm.
Per-EnvCell world transform remains publication state, not baked geometry.
CellStruct collision geometry can therefore alias by exact source identity;
cell-specific portals, visibility, `seen_outside`, and placement remain a
separate small payload/publication record.
## 3. Execution slices
### I0 — oracle inventory and fixed evidence
1. Grep named retail before touching traversal:
- `BSPTREE::find_collisions`
- `BSPNODE::find_walkable`
- `BSPLEAF::find_walkable`
- `BSPNODE::sphere_intersects_*`
- `BSPNODE::point_inside_cell_bsp`
- `CTransition::find_transitional_position`
- `CTransition::init_path`
- `CPhysicsObj::transition`
2. Cross-reference ACE and the existing acdream pseudocode/trajectory notes.
3. Write one collision-layout pseudocode note that separates:
source storage, traversal order, mutable query scratch, and returned
side-effects.
4. Inventory every production call into `BSPQuery`, every field read from
`PhysicsBSPNode`, `CellBSPNode`, `ResolvedPolygon`, `CellPhysics`,
`GfxObjPhysics`, and `SetupPhysics`, and every raw DAT graph retained by
`LandblockBuild`/`PhysicsDatBundle`.
5. Capture representative installed-DAT fixtures:
- outdoor/no-BSP;
- Facility Hub and cottage indoor cells;
- staircase/ramp and cellar-lip cases;
- a multipart door/building shell;
- projectile/thin-wall geometry;
- cells with asymmetric/null BSP children;
- leaves with multiple polygons and equal-distance candidates.
6. Record allocation baselines for player, remote, projectile, and camera
resolve loops.
Gate: source inventory is complete, fixtures reproduce through the current
graph path, and no behavior code changed.
### I1 — reusable transition and query scratch
1. Add explicit `ResetForReuse` methods to `ObjectInfo`, `CollisionInfo`,
`SpherePath`, and `Transition`.
2. Reset every scalar, nullable, property backing field, diagnostic counter,
collision GUID list, sphere element, walkable reference, backup value, and
transient flag. Retain only storage identity whose contents are completely
reset.
3. Give each `PhysicsEngine` one owned transition lease. The normal
update-thread path rents, initializes, resolves, snapshots the value-only
result, and returns in `finally`.
4. Make reentrancy explicit and tested. Never hand one mutable transition to
two concurrent/nested resolves and never silently share session scratch.
5. Reuse walkable vertex arrays only when their exact logical length matches;
allocate only on a true size change. No larger-capacity array may leak stale
vertices into polygon math.
6. Convert short-lived `BSPQuery.CollisionSphere` reference objects to
value/ref scratch while preserving each mutation/copy boundary.
7. Add structural reflection tests that poison every resettable member, reset,
and compare its complete value graph with a fresh instance while asserting
retained buffer identities.
8. Differential-run fresh-transition and reused-transition engines across
success, collision, failure, placement, grounded, airborne, sliding,
step-up/down, two-sphere, projectile, and camera cases.
Gate: bit-identical `ResolveResult` and body side effects, no state leakage
when alternating hostile fixtures between different mover IDs, and zero
steady transition/query-scratch allocation. Close issue #237 only here.
### I2 — immutable flat collision schema and flattener
1. Add Core-only flat record types with explicit index/range contracts.
2. Flatten physics and containment BSPs iteratively during preparation while
emitting deterministic pre-order node arrays.
3. Copy planes, spheres, vertices, setup dimensions, and collision metadata
verbatim. Tests compare every float via `SingleToInt32Bits`.
4. Resolve each leaf polygon ID to a direct polygon index at preparation time.
Missing IDs are corruption, not a runtime fallback.
5. Validate:
- child indices/ranges;
- acyclic/reachable tree shape;
- polygon and vertex ranges;
- source-vs-flat node, child, polygon, and float identity;
- deterministic output independent of dictionary enumeration.
6. Keep source graph and flat asset together only in test/shadow fixtures.
Gate: round-trip structural equality over synthetic edge cases and installed
DAT samples; no traversal cutover yet.
### I3 — package serialization, bake, and prepared collision source
1. Append new `PakAssetType` values; never renumber the existing mesh values.
2. Bump `CurrentBakeToolVersion` because a complete production package now
includes collision assets.
3. Add strict little-endian serializers with checked counts/ranges, exact float
bits, cancellation, CRC coverage, corruption rejection, and trailing-byte
rejection.
4. Add a typed `IPreparedCollisionSource` over the existing memory-mapped pak.
It shares package/catalog lifetime but does not overload the render-only
`ObjectMeshData` API.
5. Bake:
- GfxObj physics BSP + polygons + visual bounds;
- Setup cylinder/sphere/dimension records;
- CellStruct physics BSP + containment BSP + physics/portal polygons;
- EnvCell topology records.
6. Alias only byte-identical immutable collision payloads. Cell-specific
topology and world placement cannot be aliased merely because geometry is.
7. Extend full-bake determinism, two-thread equivalence, corruption,
cancellation, publish-transaction, and catalog-completeness tests.
Gate: two independent bakes are byte-identical; every collision key required
by the canonical route is present; a failed/cancelled bake cannot replace the
last good pak.
### I4 — flat traversal shadow implementation
1. Port each current entry point line-for-line against integer-index nodes:
- point-in-cell;
- sphere/cell overlap;
- walkable search;
- six-path moving collision dispatcher;
- static sphere/polygon overlap;
- time-of-impact overlap.
2. Share only polygon-level math that is storage-independent. Do not
“simplify” recursion, branch ordering, or early returns.
3. Preserve mutation semantics for valid-position spheres, hit polygon, path,
contact plane, slide normal, walk interpolation, and transition state.
4. Add a differential harness that executes old and flat queries from cloned
transition/body inputs and compares:
- bool/enum result;
- all output vectors/planes by float bits;
- selected polygon ID;
- path/collision/object side effects;
- cell membership and final `ResolveResult`.
5. Sweep a large installed-DAT sample with randomized points, spheres,
movement vectors, insert modes, orientations, scales, and boundary values.
6. Require explicit fixtures for null children, on-plane equality, radius
equality, equal candidate distances, multi-polygon leaf order, and deep
trees.
Gate: zero differential mismatch. Any mismatch blocks cutover; no tolerance
band is permitted for deterministic scalar results.
### I5 — dual publication and connected shadow gate
1. Extend collision preparation so a `LandblockBuild` carries immutable flat
assets and small placement/topology records rather than reparsing them on
the update thread.
2. During the shadow phase, publish both current and flat views under one
landblock generation/receipt.
3. Keep the current graph path authoritative. Run sampled flat queries from
cloned inputs and emit a deterministic mismatch artifact without affecting
gameplay.
4. Verify load, pending-to-loaded, demotion, rehydrate, same-location revisit,
removal, cancellation, and session reset release both views exactly once.
5. Run connected login, indoor/outdoor portal churn, doors, ramps, jumping,
projectiles, camera collision, and graceful reconnect.
Gate: zero shadow mismatch and no retained collision owner after landblock or
session teardown.
### I6 — production cutover and graph removal
1. Flip canonical `PhysicsDataCache` records and every `BSPQuery` production
call to flat assets in one bisectable commit.
2. Keep the graph route as an automated referee for the first cutover gate,
with an exact revert command recorded in this plan and project memory.
3. After automated and user correctness acceptance, remove:
- production `PhysicsBSPTree`/`CellBSPTree` retention;
- production polygon dictionaries and vertex DBObj graphs;
- raw collision graphs from streaming build/publication payloads;
- obsolete graph adapters and referee code.
4. Live-DAT tooling may parse source graphs only to produce/compare flat
assets; gameplay production never falls back from a missing/corrupt prepared
collision asset.
Gate: trajectories and collision fixtures remain bit-identical, package-only
startup succeeds, and retained-memory accounting shows no parsed collision
DBObj graph after stable world reveal.
### I7 — allocation, soak, and documentation closeout
1. Measure capped and uncapped resolve allocation separately for player,
remotes, projectiles, and camera.
2. Run the canonical connected lifecycle/reconnect route and the nine-stop R6
portal soak.
3. Repeat indoor door/ramp/cellar, jump/fall, combat/projectile, and camera-wall
gates.
4. Under RDP, accept correctness and lifecycle evidence only; defer absolute
GPU/frame-time acceptance to a physical-display run.
5. Update architecture, WorldBuilder inventory, milestones, roadmap, issue
ledger, divergence register, and both physics/render memory digests.
Final gate:
- full Release build and solution tests;
- zero deterministic old-vs-flat mismatch;
- zero steady transition/query-scratch allocation;
- no raw collision graph in production stable state;
- connected portal/reconnect/interaction routes pass;
- exact cutover rollback recorded before user verification.
## 4. Commit order
Each unit is independently buildable and reversible:
1. `docs(physics): pin Slice I collision oracle and fixtures`
2. `perf(physics): reuse reset-complete transition scratch`
3. `feat(physics): define deterministic flat collision assets`
4. `feat(content): bake and read flat collision assets`
5. `feat(physics): add differential flat BSP traversal`
6. `feat(streaming): shadow-publish flat collision assets`
7. `perf(physics): cut production traversal to flat assets`
8. `refactor(physics): remove parsed collision graphs`
9. `docs(physics): close Slice I evidence and roadmap`
The exact production-cutover commit and its `git revert <sha>` command are
added to this plan and `claude-memory/project_physics_collision_digest.md`
immediately when step 7 lands, before any visual/correctness gate.