diff --git a/docs/plans/2026-04-11-roadmap.md b/docs/plans/2026-04-11-roadmap.md index fda684be..21ee6f9f 100644 --- a/docs/plans/2026-04-11-roadmap.md +++ b/docs/plans/2026-04-11-roadmap.md @@ -1192,8 +1192,8 @@ critical path wins every conflict. | Phase | What | Status | |---|---|---| -| MP0 | Honest frame profiler (`[frame-prof]`) + baseline capture | πŸ”΅ IN PROGRESS β€” code shipped 2026-07-05 (plan `docs/superpowers/plans/2026-07-05-mp0-frame-profiler.md`); baseline capture pending | -| MP1 | Baked asset pak (`acdream-bake` CLI + mmap zero-copy `PakReader`) | βšͺ next after the MP0 gate | +| MP0 | Honest frame profiler (`[frame-prof]`) + baseline capture | βœ… SHIPPED 2026-07-05 β€” gate PASSED (`docs/research/2026-07-05-mp0-baseline.md`); headline: town spikes = GC churn (1.5–3 MB/frame), teleport hitch 211 ms | +| MP1 | Baked asset pak (`acdream-bake` CLI + mmap zero-copy `PakReader`) | βšͺ NEXT β€” then the baseline's allocation-triage amendment | | MP2 | Retail distance-degrade port, hide-only cut (retires a divergence row) | βšͺ | | MP3 | Arch ECS render world + delta submission (the 300-FPS lever) | βšͺ | | MP4 | Zero-alloc frame loop + flat physics data | βšͺ hard-queued behind M1.5 #137 | diff --git a/docs/research/2026-07-05-mp0-baseline.md b/docs/research/2026-07-05-mp0-baseline.md new file mode 100644 index 00000000..581e9f3d --- /dev/null +++ b/docs/research/2026-07-05-mp0-baseline.md @@ -0,0 +1,94 @@ +# MP0 baseline β€” frame-profiler capture, attribution verdict, gate decision + +**Date:** 2026-07-05. **Build:** Release, `ACDREAM_FRAME_PROF=1`, vsync off. +**Character:** `+Horan` (`0x5000000B`, testaccount2). **Logs:** `mp0-baseline.log` +(primary run: full route with 4 teleports, ~2 min) + `mp0-baseline-run3.log` +(supplemental: longer town session, same build) β€” both untracked in the worktree +root; the numbers below are transcribed from them. + +**Route (reconstructed from log markers):** login β†’ Holtburg outdoor +(`0xA9B4`) β†’ movement/turning β†’ teleport 1 β†’ **dungeon `0x0007`** (dungeon +collapse streaming path) β†’ walk β†’ teleport 2 β†’ **outdoor town `0xCE94`** β†’ +pan/walk β†’ teleport 3 β†’ **dungeon again** β†’ walk β†’ teleport 4 β†’ Holtburg β†’ +close (graceful). + +## The numbers (per 5-second `[frame-prof]` window) + +| State | cpu_ms p50 | p95 | p99 | max | gpu_ms p50 | alloc/frame p50 | GC gen0/gen1/gen2 per 5 s | +|---|---|---|---|---|---|---|---| +| Login settle (Holtburg) | 2.1 | 10.2 | **52.8** | **123.3** | 0.4 | 1.57 MB | 93 / **50** / **36** | +| Holtburg steady/turning | 1.6–2.0 | 2.2–2.6 | 2.8–3.7 | 15–20 | 0.4 | 1.58–1.60 MB | ~80 / 3–13 / 3–5 | +| Dungeon `0x0007` walk | **0.4–0.5** | 0.5–0.6 | 0.9 | 1.2–1.5 | 0.1 | 0.50–0.63 MB | 119 / **0** / **0** | +| Town `0xCE94` pan/walk | 2.9–3.6 | 4.0–5.0 | 7.2–15.7 | 26–37 | 1.2–1.6 | 2.5–3.0 MB | ~60 / 5–22 / 5–11 | +| Teleportβ†’dungeon window | 2.8 | 3.3 | 14.1 | **211.2** | 1.3 | (max **75.7 MB** in one frame) | 62 / 11 / 10 | +| Teleportβ†’outdoor windows | 1.2–2.9 | 3.4–3.8 | 4.5–7.0 | 33 | 0.2–1.5 | (max 20–31 MB single frame) | ~80–93 / 15–26 / 5 | +| Holtburg end-of-run moving | 2.6–2.9 | 3.3–5.1 | 8.5–**23.0** | 20–**87** | 2.5–2.7 | 2.14 MB | ~50–58 / 24–32 / 7–8 | +| (run 3) town views | 1.7–2.4 | 2.7–3.4 | 7.9–15.8 | 29–39 | 0.4–2.2 | 1.7–2.2 MB | ~70–109 / 17–32 / 6–11 | + +Stage attribution (`upd` / `upl` / `imgui`): **β‰ˆ 0.0 ms p50 in every window** +(occasional upd p95 0.1–1.7 during streaming). GPU never exceeded p95 3.7 ms. + +## Attribution verdict + +1. **Steady-state medians are far better than the spec Β§1 assumption.** The + spec assumed ~6 ms / ~165 FPS dense-town CPU. Captured: worst steady town + view is p50 3.3–3.6 ms (~280–300 FPS); Holtburg ~1.6–2.4 ms (~400–600 FPS); + dungeons 0.4–0.5 ms (~2000 FPS). **Caveat:** the canonical Fort + Tethana/Arwic "dense worst-case axiom view" was NOT in this route β€” the + Β§1 numbers came from there. The 300-FPS throughput goal is close to met in + the captured views but must be re-verified at that view before any + "target met" claim. +2. **The frame is CPU-render-side, as assumed.** GPU ≀ ~2.7 ms everywhere; + upd/upl/imgui β‰ˆ 0 β†’ virtually all CPU time is unattributed render-side + submission. The spec's MP3 thesis stands. +3. **The smoothness gap is real, and it is GC.** Every town window violates + the ≀16 ms goal at max (20–87 ms) and several at p99 (up to 23 ms), while + dungeon windows β€” the ONLY windows with **zero gen1/gen2 collections** β€” + are spike-free (max 1.5 ms). Steady-state allocation is **1.5–3.0 MB per + frame** (β‰ˆ 0.6–1.2 GB/s at these frame rates), driving gen2 collections + roughly 1–2 per second in towns. The correlation is airtight across all + 28 windows: spikes appear exactly where gen1/gen2 counts are non-zero. +4. **The hitch scenario is confirmed and quantified.** Teleport-into-dungeon + produced the worst frame of the session (**211 ms**) with a **75.7 MB** + single-frame allocation (decode + hydrate storm); login settle hit 123 ms + max with 36 gen2 collections in one window. This is exactly the class MP1 + (bake β€” load becomes mmap reads, not managed decode) targets. + +## Gate decision (spec Β§5) + +**PROCEED to MP1, with one amendment.** The assumed cost split (render-side +CPU dominant, GPU idle, hitches at load boundaries) is CONFIRMED, so the +phase order stands. The amendment: the capture elevates **steady-state +allocation churn** from "MP4 cleanup" to a first-class smoothness lever β€” +1.5–3 MB/frame of per-frame garbage is the direct cause of the town p99/max +violations, and it is NOT load-related (it persists in steady windows). + +**Amendment (recorded in the spec):** after MP1 ships, run a bounded +**steady-state allocation triage** (one session: `dotnet-trace` / +allocation-sampling on the live client, identify the top ~5 per-frame churn +sites). Sites *inside* the MP3 rewrite surface (per-frame submission +rebuild β€” the prime suspect for MB-scale per-frame lists) are left for MP3 +to remove structurally; sites *outside* it get targeted fixes immediately. +The full zero-alloc pass remains MP4. This front-loads the biggest +smoothness win without letting "fix allocations" sprawl into an unphased +rewrite. + +## Follow-ups noted (not gates) + +- Re-measure the Fort Tethana axiom view at the MP2 and MP3 gates (the + degrade port materially changes exactly that view). +- `upd β‰ˆ 0.0` everywhere is suspicious-adjacent but plausible (light sim in + these scenes); if a future capture shows heavy OnUpdate work still reading + 0.0, verify the stage scope wasn't bypassed by an early return upstream. +- Dungeon at ~2000 FPS with zero GC pressure is the existence proof that the + engine's frame loop CAN run allocation-free at four-digit FPS β€” towns are + paying for churn, not for fundamentals. + +## Apparatus state + +`[frame-prof]` (env `ACDREAM_FRAME_PROF=1`, DebugPanel "Frame profiler" +checkbox) is PERMANENT β€” it is the measurement instrument for every MP gate. +Implementation: `src/AcDream.App/Diagnostics/` (FrameStatsBuffer / +GpuFrameTimer / FrameProfiler), plan +`docs/superpowers/plans/2026-07-05-mp0-frame-profiler.md`, commits +`7d74c68c`..`4b44a152`. diff --git a/docs/superpowers/specs/2026-07-05-modern-pipeline-design.md b/docs/superpowers/specs/2026-07-05-modern-pipeline-design.md index 9d71a97a..ec8ef50e 100644 --- a/docs/superpowers/specs/2026-07-05-modern-pipeline-design.md +++ b/docs/superpowers/specs/2026-07-05-modern-pipeline-design.md @@ -117,6 +117,17 @@ CLAUDE.md structure rule 1): the CPU 6 ms attributed per stage. If the attribution contradicts Β§1's assumed split, the MP phase order is re-sequenced and this doc amended before MP1 starts. +**Gate PASSED 2026-07-05** β€” see `docs/research/2026-07-05-mp0-baseline.md`. +Verdict: render-side-CPU split CONFIRMED (GPU ≀ ~2.7 ms, stages β‰ˆ 0); steady +medians better than assumed (worst town p50 3.6 ms; Fort Tethana axiom view +not yet re-measured β€” re-check at the MP2/MP3 gates). AMENDMENT: steady-state +allocation churn (1.5–3 MB/frame β†’ gen2 GC ~1–2/s β†’ all town p99/max +violations) is promoted to a first-class smoothness lever: after MP1, one +bounded allocation-triage session fixes top churn sites OUTSIDE the MP3 +rewrite surface; sites inside it wait for MP3; the full zero-alloc pass +remains MP4. Teleport hitch quantified: 211 ms worst frame, 75.7 MB +single-frame allocation β€” MP1's "before" number. + ## 6. MP1 β€” Bake + pak (the smoothness backbone) ### 6.1 The bake tool