acdream/docs/research/2026-07-25-slice-i1-transition-scratch.md
Erik 16d182c2f0 perf(physics): reuse reset-complete transition scratch
Mirror retail's ten-deep LIFO transition lifetime, retain all query scratch with complete reset contracts, and remove Tier-0 enum boxing without changing collision decisions. Fresh and retained engines are bit-identical across the expanded oracle, while measured transition profiles now allocate 0 bytes per resolve.
2026-07-25 14:37:02 +02:00

108 lines
4.7 KiB
Markdown

# Slice I1 — reset-complete transition and query scratch
**Status:** COMPLETE
**Parent:** `docs/plans/2026-07-25-modern-runtime-slice-i.md`
**Scope:** storage lifetime and allocation only; retail collision math,
comparisons, branch order, traversal order, and returned behavior are
unchanged.
## Retail lifetime carried forward
Named retail establishes a retained, stack-disciplined transition lifetime:
- `CTransition::makeTransition` `0x0050B150`
- `CTransition::cleanupTransition` `0x00509DC0`
- `CTransition::init` `0x00509DD0`
- `COLLISIONINFO::init` `0x00509D60`
- `SPHEREPATH::init` `0x0050C330`
Retail owns ten pre-existing `CTransition` records, selects one by nesting
depth, resets it before use, and releases it in LIFO order. The acdream port
now gives each `PhysicsEngine` the same ten-deep, same-thread lease contract.
`ResolveWithTransition` and `ResolvePlacement` always return their lease in a
`finally` block.
## Storage changes
- `ObjectInfo`, `CollisionInfo`, `SpherePath`, and `Transition` have complete
`ResetForReuse` contracts.
- Fixed sphere objects, collision GUID storage, exact-length walkable buffers,
candidate-cell storage, and reentrant ordered-cell lists retain identity.
Every logical value and retained buffer content is reset before reuse.
- Neighboring-cell scans use a retained LIFO list arena. This is required
because a cell collision may recursively enter step-up/step-down and another
`CheckOtherCells` before the outer scan resumes.
- `BSPQuery.CollisionSphere` is value/ref scratch instead of a short-lived
reference object.
- Terrain's always-three-vertex surface sample is stored inline. Production
BSP entry points take center/radius values directly instead of constructing
temporary DAT `Sphere` objects.
- `CellTransit` reuses the transition-owned ordered/deduplicated candidate
collection.
- Walkable publication to `PhysicsBody` reuses only an exact-length backing
array; a real polygon-size change replaces it.
- Hot enum predicates use equivalent bit masks. This removes Tier-0
`Enum.HasFlag` boxing while preserving the exact branch condition.
The wider collision oracle exposed the reentrant-cell-list hazard before
landing: three Facility Hub corridor seam replays initially threw
`Collection was modified`. The final LIFO query arena keeps every nested
scan independent; all three exact seam replays pass.
## Differential and structural evidence
`TransitionScratchResetTests`:
- reflection-poisons every stored member;
- resets and compares the complete logical graph with a fresh transition;
- proves sphere, child, list, candidate, and walkable-buffer identities are
retained while their values are cleared;
- proves exact-length walkable reuse and replacement on a true size change;
- proves ten distinct nested leases, capacity rejection, LIFO enforcement,
same-thread enforcement, and clean reuse.
`TransitionScratchDifferentialTests` runs a fresh-transition engine beside the
production retained engine and compares every `ResolveResult` float by bits
plus every public `PhysicsBody` side effect. The hostile alternating sequence
covers:
- successful and failed transitions;
- grounded and airborne movement;
- wall collision and carried sliding state;
- step-up and step-down inputs;
- two-sphere characters;
- one-sphere projectiles;
- viewer/camera sweeps;
- placement overlap search;
- different mover and designated-target identities.
The fixed graph oracle, including corridor seams, cellar, doors, projectiles,
camera, stairs, cylinder/sphere families, and the new I1 gates, is
**118 passed / 0 skipped / 0 failed**.
## Allocation result
Release, one thread, 256 warmups and 4,096 measured resolves per profile:
| Profile | I0 graph baseline | I1 retained scratch |
|---|---:|---:|
| player, two spheres | 4,448 B/resolve | **0 B/resolve** |
| remote, two spheres | 4,448 B/resolve | **0 B/resolve** |
| projectile, one 5 cm sphere | 6,848 B/resolve | **0 B/resolve** |
| camera/viewer, one 30 cm sphere | 2,512 B/resolve | **0 B/resolve** |
| grounded walkable publication | not isolated | **0 B/resolve** |
This is an isolated transition/query-scratch gate. Optional diagnostics and
capture remain intentionally allocating, and the ordinary connected
frame-allocation profile is repeated at Slice I7 after flat-asset cutover.
## Release gates
- focused collision oracle: **118 passed**
- complete Core tests: **3,250 passed / 2 skipped**
- `dotnet build AcDream.slnx -c Release --no-restore`: **passed**
- complete solution tests: **8,342 passed / 5 skipped**
Issue #237 is closed. Slice I2 begins with immutable Core-only flat collision
records and deterministic source-graph flattening; the current graph traversal
remains the executable behavior oracle.