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.
This commit is contained in:
parent
2effba5127
commit
16d182c2f0
19 changed files with 3037 additions and 1228 deletions
|
|
@ -327,7 +327,7 @@ covered by a zero-managed-allocation test. Evidence:
|
|||
|
||||
## #237 — PhysicsEngine allocates a fresh Transition graph per resolve call
|
||||
|
||||
**Status:** OPEN
|
||||
**Status:** DONE — 2026-07-25, Modern Runtime Slice I1
|
||||
**Severity:** MEDIUM
|
||||
**Filed:** 2026-07-24
|
||||
**Component:** physics
|
||||
|
|
@ -339,14 +339,21 @@ NPC/remote, projectiles, and the camera probe — per-tick GC pressure that
|
|||
scales with active-mover count (combat/crowds), a scenario the audit's
|
||||
dwell-heavy route under-measured.
|
||||
|
||||
**Root cause / status:** 2026-07-24 audit review finding. Pooling is only
|
||||
allowed under plan Slice I's identity/lifetime-test condition (the adjacent
|
||||
`LiveEntityAnimationScheduler` scratch-reuse pattern is the precedent) —
|
||||
Transition state must be provably reset-complete between movers or a pooled
|
||||
graph corrupts collision state, which is worse than the allocation.
|
||||
**Resolution:** Each `PhysicsEngine` now owns retail-shaped ten-deep,
|
||||
same-thread, LIFO transition scratch. Every stored member is reset and
|
||||
reflection-poisoned in tests; exact-length walkable, candidate-cell, collision
|
||||
GUID, and reentrant neighbor-order storage retains identity without exposing
|
||||
stale state. Fresh-vs-reused engines are bit-identical across hostile
|
||||
player/remote/projectile/camera/placement/failure/step/slide cases. Release
|
||||
allocation fell from 2,512–6,848 B/resolve to 0 B/resolve for all measured
|
||||
mover profiles. Evidence:
|
||||
`docs/research/2026-07-25-slice-i1-transition-scratch.md`.
|
||||
|
||||
**Files:** `src/AcDream.Core/Physics/PhysicsEngine.cs:995`;
|
||||
`src/AcDream.Core/Physics/TransitionTypes.cs:360-367,694-698`.
|
||||
**Files:** `src/AcDream.Core/Physics/TransitionScratchArena.cs`;
|
||||
`src/AcDream.Core/Physics/PhysicsEngine.cs`;
|
||||
`src/AcDream.Core/Physics/TransitionTypes.cs`;
|
||||
`tests/AcDream.Core.Tests/Physics/TransitionScratchResetTests.cs`;
|
||||
`tests/AcDream.Core.Tests/Physics/TransitionScratchDifferentialTests.cs`.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1647,14 +1647,16 @@ port in any phase — no separate listing here.
|
|||
> come from the retained frame product. The exact lifecycle/reconnect matrix,
|
||||
> capped/uncapped/dense correctness routes, and ordinary production profile
|
||||
> pass. The final stable ordinary-production minute sustains 519.7 FPS with
|
||||
> CPU/GPU p50 1.869/1.096 ms; the measured 22.34 KiB/frame remainder is the
|
||||
> active Slice-I transition/query-scratch owner.
|
||||
> CPU/GPU p50 1.869/1.096 ms. Slice I1 now removes the isolated
|
||||
> transition/query owner: player, remote, projectile, camera, and grounded
|
||||
> walkable-publication profiles each measure 0 B/resolve, and retained
|
||||
> transitions remain bit-identical to the fresh-graph oracle.
|
||||
> The historical exact cutover
|
||||
> rollback remains
|
||||
> `git revert ef1d263337997bb030eadb7b8e71d73dc659907a`. Slice H is complete:
|
||||
> retained UI/frame work, exact bounded light selection, and
|
||||
> pooled/borrowed/direct network I/O pass 8,292 Release tests / 5 skips and the
|
||||
> connected lifecycle/reconnect gate. Slice I is active at I1 from
|
||||
> connected lifecycle/reconnect gate. Slice I is active at I2 from
|
||||
> [its detailed plan](2026-07-25-modern-runtime-slice-i.md).
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-07-05-modern-pipeline-design.md` (the
|
||||
|
|
@ -1679,11 +1681,11 @@ hitch and follows.
|
|||
| MP1a | `AcDream.Content` extraction (GL-free MeshExtractor + boundary records out of App) | ✅ SHIPPED 2026-07-05 — user-gated (renders identical, zero tripwires, perf-neutral); 8 commits `651d041e`..`b0758d77` |
|
||||
| MP1b | Pak format + `acdream-bake` CLI + mmap `PakReader` + equivalence/full-scale gate | ✅ **SHIPPED 2026-07-24** — 729,888 EnvCell keys → 17,117 unique geometries + 712,771 aliases (42.6×), 751,141 total keys / 38,370 physical blobs, zero failures, 28,192.4 MiB, 81.4 s validated atomic publish; full-DAT gate also corrected a real collision in WB's legacy geometry hash. [Report](../research/2026-07-24-slice-b-full-bake-report.md) |
|
||||
| **MP-Alloc (safe batch)** | Reuse per-frame buffers: anim pose, particle draw-list, interior partition, animatedIds/drawableCells sets | ✅ SHIPPED + USER-GATED 2026-07-05 — dense-town frame-time spikes 20–87ms → 6–10ms, alloc spikes (30–75MB single-frame) eliminated, gen2 GC 5–11/window → ~0; user confirms FPS steady. 4 commits `b8c05e2b`..`91afea24` |
|
||||
| MP-Alloc (hard sites) | EnvCell settled-camera rebuild gate + physics `Transition` pooling — the residual steady ~1.6MB/frame | ⚪ OPTIONAL follow-up — would lower the median too, but the wild-swing complaint is already resolved; each needs its own careful gate (batch correctness / physics faithfulness) |
|
||||
| MP-Alloc (hard sites) | EnvCell settled-camera rebuild gate + physics `Transition` reuse | 🟡 Physics half SHIPPED 2026-07-25 — retail-shaped reset-complete transition/query scratch is bit-identical and 0 B/resolve across five profiles; flat collision storage continues in Slice I2. |
|
||||
| MP1c | Streaming cutover to pak + hitch gate | ✅ **SHIPPED 2026-07-24** — typed package-only production source, explicit Setup probes, non-allocating Portal-first lookup, exact translucency metadata, and ordered teardown; capped/uncapped/dense physical routes + user visual gate pass. Uncapped CPU p99 7.825→6.496 ms, GPU p99 3.406→2.706 ms, portal frame max 202.9→39.9 MiB, process allocation −41.8%, zero invalid Setup probes. [Report](../research/2026-07-24-slice-c-prepared-asset-cutover-report.md) |
|
||||
| MP2 | Retail particle distance/cell-view degradation: DAT-authored range, exact infinite/finite off-view update branches, emitter-first render scan, user-requested 2× default range | ✅ SHIPPED 2026-07-17 — Aerlinthe hotspot `0x32000223` resolves to retail 64m and defaults to 128m; landscape/entity streaming distance unchanged |
|
||||
| MP3 | Arch ECS render world + delta submission (the 300-FPS lever) | ⚪ — note: does NOT fix the steady-state GC churn (that's MP-Alloc); MP3 is the draw-submission throughput lever |
|
||||
| MP4 | Zero-alloc frame loop + flat physics data (residual, post-MP-Alloc) | ⚪ hard-queued behind M1.5 #137 |
|
||||
| MP4 | Zero-alloc frame loop + flat physics data (residual, post-MP-Alloc) | 🟡 ACTIVE — Slice I1 zero-allocation transition scratch shipped; I2 immutable flat collision assets in progress. |
|
||||
| MP5 | Job-system parallelism | ⚪ stretch, evidence-gated |
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -116,10 +116,12 @@ the retained frame product without building the old
|
|||
correctness routes, exact seven-checkpoint lifecycle/reconnect gate, and
|
||||
ordinary production profile pass. The final stable production minute sustains
|
||||
519.7 FPS with CPU/GPU p50 1.869/1.096 ms and 22.34 KiB/frame allocation.
|
||||
The complete Release gate is 3,826 App tests / 3 skips and 8,335 solution
|
||||
tests / 5 skips. Slice H is complete: retained UI/frame work, exact bounded
|
||||
light selection, and pooled/borrowed/direct network I/O pass. Slice I is active
|
||||
at I1 under
|
||||
Slice I1 subsequently removes the isolated per-resolve owner: player, remote,
|
||||
projectile, camera, and grounded walkable-publication profiles are all
|
||||
0 B/resolve with bit-identical fresh-vs-reused outcomes. The complete I1
|
||||
Release gate is 3,826 App tests / 3 skips and 8,342 solution tests / 5 skips.
|
||||
Slice H is complete: retained UI/frame work, exact bounded light selection,
|
||||
and pooled/borrowed/direct network I/O pass. Slice I is active at I2 under
|
||||
[`2026-07-25-modern-runtime-slice-i.md`](2026-07-25-modern-runtime-slice-i.md).
|
||||
The historical exact G4 visual rollback remains
|
||||
`git revert ef1d263337997bb030eadb7b8e71d73dc659907a`.
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
**Date:** 2026-07-24
|
||||
|
||||
**Status:** Slices A–H and I0 are complete. Slices F–L were explicitly
|
||||
approved 2026-07-24; Slice I1 is active.
|
||||
**Status:** Slices A–H and I0–I1 are complete. Slices F–L were explicitly
|
||||
approved 2026-07-24; Slice I2 is active.
|
||||
|
||||
**Scope:** Reconcile and sequence the existing Modern Pipeline (`MP`) and
|
||||
Linux/headless (`LH`) tracks using the 2026-07-24 connected performance audit.
|
||||
|
|
@ -1193,12 +1193,16 @@ seven-checkpoint lifecycle/reconnect gate pass with graceful shutdown.
|
|||
The final ordinary-production minute on exact `14fbe92b` sustained 519.7 FPS:
|
||||
CPU p50/p95/p99 1.869/2.306/2.484 ms and GPU
|
||||
1.096/1.108/1.136 ms. Working/private memory was 652.1/928.3 MiB.
|
||||
Frame-thread allocation is now 22.34 KiB median; allocation-tick evidence and
|
||||
the four-mover baseline assign that remainder primarily to reset-complete
|
||||
`Transition` collision/query scratch. I0 has pinned the retail oracle and
|
||||
fixtures, so I1 is the current execution point. Evidence:
|
||||
Frame-thread allocation at the G5 checkpoint was 22.34 KiB median. I1 has now
|
||||
replaced fresh per-resolve transition graphs and query temporaries with
|
||||
reset-complete retained scratch: player, remote, projectile, camera, and
|
||||
grounded walkable-publication profiles each measure 0 B/resolve, while
|
||||
fresh-vs-reused output and body state remain bit-identical. I2 immutable flat
|
||||
collision assets are the current execution point. Evidence:
|
||||
[`../research/2026-07-25-slice-g5-production-profile.md`](../research/2026-07-25-slice-g5-production-profile.md)
|
||||
and
|
||||
[`../research/2026-07-25-slice-i1-transition-scratch.md`](../research/2026-07-25-slice-i1-transition-scratch.md)
|
||||
and
|
||||
[`2026-07-25-modern-runtime-slice-i.md`](2026-07-25-modern-runtime-slice-i.md).
|
||||
|
||||
The intended order is therefore:
|
||||
|
|
@ -1212,7 +1216,7 @@ honest metrics + committed baselines (A — exit criteria block C)
|
|||
-> incremental render scene (F)
|
||||
-> delta GPU submission (G)
|
||||
-> residual frame cleanup (H-a, H-b, H-c)
|
||||
-> flat collision assets (I — CURRENT at I1)
|
||||
-> flat collision assets (I — CURRENT at I2)
|
||||
-> presentation-independent runtime (J — §0.3 authorization)
|
||||
-> Linux/headless/multi-session (K)
|
||||
-> evidence-gated GPU jobs (L)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
# 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
|
||||
**Status:** I0–I1 COMPLETE — retail oracle, graph fixtures, and
|
||||
reset-complete zero-allocation transition/query scratch fixed; I2 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
|
||||
|
|
@ -123,7 +122,11 @@ graph path, and no behavior code changed.
|
|||
|
||||
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.
|
||||
steady transition/query-scratch allocation. **PASSED 2026-07-25:** the expanded
|
||||
118-test graph oracle, structural poison/reset suite, hostile fresh-vs-reused
|
||||
differential, Core/solution Release gates, and all five measured profiles pass.
|
||||
Issue #237 is closed. Evidence:
|
||||
[`../research/2026-07-25-slice-i1-transition-scratch.md`](../research/2026-07-25-slice-i1-transition-scratch.md).
|
||||
|
||||
### I2 — immutable flat collision schema and flattener
|
||||
|
||||
|
|
|
|||
108
docs/research/2026-07-25-slice-i1-transition-scratch.md
Normal file
108
docs/research/2026-07-25-slice-i1-transition-scratch.md
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
# 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue