perf(content): close prepared asset cutover

This commit is contained in:
Erik 2026-07-24 15:49:24 +02:00
parent b1ad4b7c0a
commit a564c4b782
11 changed files with 428 additions and 66 deletions

View file

@ -130,6 +130,18 @@ user-accepted. Favorite-spell press-time selection and right-click local
examination are implemented; the current visual gate is its corrected modern
scarab/prismatic formula, DAT component icons, and authored 310 x 400 extent.
**Active performance side track:** Slices AC of
`docs/plans/2026-07-24-modern-runtime-architecture.md` completed 2026-07-24.
Production world-mesh streaming now consumes the validated machine-local
`acdream.pak` through `IPreparedAssetSource`; live DAT extraction remains
explicit bake/equivalence/UI-Studio tooling. Capped, uncapped, dense-Arwic,
lifetime, full-suite, and user-visual gates passed. Process allocation fell
41.861.9%, GC pause time fell 48.454.1%, uncapped CPU/GPU p99 improved
17.0%/20.6%, and invalid Setup-probe exceptions fell to zero. Slice D (typed
handles and unified residency accounting) is next; ECS/MP3 remains behind its
explicit user decision gate. Evidence:
`docs/research/2026-07-24-slice-c-prepared-asset-cutover-report.md`.
**Structural prerequisite before new M4 subsystem work:** all eight
behavior-preserving `GameWindow` decomposition slices and the automated
closeout landed 2026-07-22. The class is now a 1,622-line native
@ -149,7 +161,7 @@ uncapped Release presentation is smooth, while physics, collision, and wire
truth remain correct.
See `docs/plans/2026-07-22-gamewindow-slice-8-composition-lifecycle.md` and
`docs/architecture/code-structure.md`. **Carried:** #153, #116, remaining
R6 ownership cleanup, TS-50/TS-51/TS-53, Modern Pipeline MP1b+, and #225's
R6 ownership cleanup, TS-50/TS-51/TS-53, Modern Runtime Slice D+, and #225's
lifestone/particle alpha visual gate.
Start structural work at `memory/project_gamewindow_decomposition.md` and

View file

@ -128,6 +128,18 @@ user-accepted. Favorite-spell press-time selection and right-click local
examination are implemented; the current visual gate is its corrected modern
scarab/prismatic formula, DAT component icons, and authored 310 x 400 extent.
**Active performance side track:** Slices AC of
`docs/plans/2026-07-24-modern-runtime-architecture.md` completed 2026-07-24.
Production world-mesh streaming now consumes the validated machine-local
`acdream.pak` through `IPreparedAssetSource`; live DAT extraction remains
explicit bake/equivalence/UI-Studio tooling. Capped, uncapped, dense-Arwic,
lifetime, full-suite, and user-visual gates passed. Process allocation fell
41.861.9%, GC pause time fell 48.454.1%, uncapped CPU/GPU p99 improved
17.0%/20.6%, and invalid Setup-probe exceptions fell to zero. Slice D (typed
handles and unified residency accounting) is next; ECS/MP3 remains behind its
explicit user decision gate. Evidence:
`docs/research/2026-07-24-slice-c-prepared-asset-cutover-report.md`.
**Structural prerequisite before new M4 subsystem work:** all eight
behavior-preserving `GameWindow` decomposition slices and the automated
closeout landed 2026-07-22. The class is now a 1,622-line native
@ -147,7 +159,7 @@ uncapped Release presentation is smooth, while physics, collision, and wire
truth remain correct.
See `docs/plans/2026-07-22-gamewindow-slice-8-composition-lifecycle.md` and
`docs/architecture/code-structure.md`. **Carried:** #153, #116, remaining
R6 ownership cleanup, TS-50/TS-51/TS-53, Modern Pipeline MP1b+, and #225's
R6 ownership cleanup, TS-50/TS-51/TS-53, Modern Runtime Slice D+, and #225's
lifestone/particle alpha visual gate.
Start structural work at `memory/project_gamewindow_decomposition.md` and

View file

@ -24,7 +24,7 @@ What does NOT go here:
- Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending.
- Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed.
## Current queue — 2026-07-23
## Current queue — 2026-07-24
- **Structural handoff:** all eight `GameWindow` decomposition slices and the
automated closeout are complete. The 1,622-line native shell has typed
@ -44,6 +44,9 @@ What does NOT go here:
smooth and physics, collision, and wire state remain correct.
- **Build hygiene:** `#228` records 17 clean-Release test-project warnings;
production compilation and all tests pass.
- **Performance side track:** prepared-content Slices AC are complete;
`#244` is closed and Slice D unified residency/accounting is next. Evidence:
[`research/2026-07-24-slice-c-prepared-asset-cutover-report.md`](research/2026-07-24-slice-c-prepared-asset-cutover-report.md).
The [documentation map](README.md) defines how this tactical ledger relates to
the milestone, roadmap, architecture, divergence register, research, and
@ -453,7 +456,7 @@ insert site ~1757).
## #244 — SequencerFactory probes Setup without a DID guard or catch; [dat-miss] logs under the database lock
**Status:** OPEN
**Status:** DONE — 2026-07-24, Modern Runtime Slice C
**Severity:** MEDIUM
**Filed:** 2026-07-24
**Component:** composition / content
@ -470,11 +473,14 @@ probes hit the dated TEMP `[dat-miss]` `Console.WriteLine`
synchronous console I/O while holding `_databaseLock`, serializing all
readers of that dat.
**Root cause / status:** 2026-07-24 audit review finding. Plan Slice C's
typed DID metadata removes the probe pattern; the Slice C amendment also
requires narrowing `ResolveActivation`'s blanket catch. The `[dat-miss]`
tripwire should be resolved (root-caused or demoted to a rate-limited
diagnostic) in the same change.
**Root cause / status:** Slice C's prepared TOC presence index now gates both
`SequencerFactory` and `ResolveActivation`, so GfxObj IDs do not enter the
Setup parser. Known malformed DAT failures are diagnosed once; cancellation
and unexpected failures propagate. The remaining `[dat-miss]` anomaly report
now captures its condition while holding the serialized DAT lock, performs
console I/O only after releasing it, and is limited to one report per database.
Connected capped, uncapped, and dense routes recorded zero invalid Setup-probe
exceptions.
**Files:** `src/AcDream.App/Composition/LivePresentationComposition.cs:200-225`;
`src/AcDream.Content/DatCollectionAdapter.cs:196-206`;

View file

@ -4,31 +4,34 @@ This page is the entry point for project documentation. It distinguishes
current sources of truth from implementation history so an old plan or issue
banner cannot silently override the current program state.
## Current snapshot — 2026-07-21
## Current snapshot — 2026-07-24
- **Milestone state:** M3, “Cast a spell,” landed 2026-07-21. M4, “Live in the
world,” is next.
- **Active program:** behavior-preserving `GameWindow` decomposition before
new M4 subsystem work. The reconciled ownership ledger and slice order are
in [`architecture/code-structure.md`](architecture/code-structure.md).
Selection/interaction Slice 1 is complete; live animation presentation is
next.
world,” is active.
- **Active gameplay program:** the pre-M4 world-interaction completion program
in
[`plans/2026-07-23-world-interaction-completion.md`](plans/2026-07-23-world-interaction-completion.md).
The eight-slice `GameWindow` decomposition and its connected visual matrix
are complete.
- **Active performance side track:** Slices AC of
[`plans/2026-07-24-modern-runtime-architecture.md`](plans/2026-07-24-modern-runtime-architecture.md)
are complete. Production world-mesh streaming consumes the validated
prepared package; Slice D unified residency/accounting is next. See the
[Slice C physical cutover report](research/2026-07-24-slice-c-prepared-asset-cutover-report.md).
- **Completed gates:** R6 locomotion/collision/projectile/teleport/radar and
the final two-client portal-out/materialization observer comparison (`#218`).
- **Separate visual verification:** the shared-alpha lifestone/particle result
in `#225`; its connected resource-lifetime and performance routes pass.
- **Carried behavior debt:** `#153` (far teleport onto an unstreamed edge),
`#116` (two narrowed slide-response cases), and divergence rows TS-50/TS-51.
- **Deferred engineering tracks:** Modern Pipeline MP1b+ and Linux/headless
automation (Track LH). Neither is part of the active structural campaign.
- **Deferred engineering tracks:** render ECS/MP3 remains behind its explicit
user decision gate; Linux/headless automation (Track LH) remains future work.
- **Divergence audit:** 178 active rows — IA 17, AD 37, AP 85, TS 34,
and UN 5 — plus the retained retired TS-37 history note.
- **Latest automated baseline:** Release build succeeds with 17 known
test-project warnings (`#228`); 6,558 tests passed and 5 intentionally
skipped. The unattended connected R6 route
completed seven portal materializations, production input exercises, and a
graceful close. See
[`research/2026-07-20-connected-r6-soak.md`](research/2026-07-20-connected-r6-soak.md).
- **Latest automated baseline:** Release build succeeds; 8,076 tests pass and
five are intentionally skipped. The capped, uncapped, and dense-Arwic
physical prepared-asset routes all completed their portal/input/lifetime
assertions and disconnected gracefully.
## Sources of truth

View file

@ -35,9 +35,10 @@ acdream streams asynchronously while the WB editor's manager owned its own loadi
loop. Do not reintroduce worker-side `RegisterCell` calls or shared pending cell
collections.
`DatCollectionAdapter` bridges our `IDatCollection` to the `IDatReaderWriter`
interface WB's internals expect (O-D7 fallback; `ObjectMeshManager` has 26
internal `_dats.*` call sites — above the 20-site inline-swap threshold).
`DatCollectionAdapter` bridges the sole `DatCollection` to Content's
`IDatReaderWriter`. Since MP1c, production `ObjectMeshManager` no longer reads
DAT; the adapter remains the bounded typed-object access seam for runtime
non-render content plus explicit bake/equivalence tooling.
**MP1a (2026-07-05): CPU mesh-extraction half moved to `AcDream.Content`.**
The GL-free portion of the former `ObjectMeshManager` — dat read → polygon
@ -82,14 +83,13 @@ behavior change, no divergence-register row.
throw (preloads staged before a malformed-dat texture-decode exception
survive, as they always did). The MP1b bake tool passes its own
collector.
- **Stays in `src/AcDream.App/Rendering/Wb/`:** `ObjectMeshManager` (now a
thin wrapper owning the staged-queue/worker-pool/Dispose-quiesce lifecycle
and all GL upload — constructs one `MeshExtractor` and delegates every
former `Prepare*` call site to it), `ObjectRenderData`/`ObjectRenderBatch`
- **Stays in `src/AcDream.App/Rendering/Wb/`:** `ObjectMeshManager` (the
staged-queue/worker-pool/Dispose-quiesce lifecycle and all GL upload;
production workers now consume `IPreparedAssetSource`),
`ObjectRenderData`/`ObjectRenderBatch`
(hold a GL `TextureAtlasManager` field), `TextureAtlasManager`,
`DatCollectionAdapter` (concrete `DatCollection`-backed implementation of
the now-Content-namespaced `IDatReaderWriter`), `GeometryUtils` (used only
by App-side raycasting, not by extraction), `AcSurfaceMetadata`/
`GeometryUtils` (used only by App-side raycasting, not by extraction),
`AcSurfaceMetadata`/
`AcSurfaceMetadataTable` (not on the extraction path), `Building.cs`
(explicitly out of scope).
- `AcDream.Core` is untouched; `AcDream.Content` references `AcDream.Core`
@ -114,6 +114,23 @@ aliasing and fails on any collision. This is an acdream resource-ownership seam,
not a second DAT interpreter; `DatCollection` and `MeshExtractor` remain the
only reader/extractor path.
**MP1c production prepared-asset cutover (2026-07-24).** Production
world-mesh workers no longer invoke `MeshExtractor` or rebuild Setup, GfxObj,
EnvCell, Surface, palette, and texture graphs during portals. The validated
machine-local `acdream.pak` is opened through Content's
`IPreparedAssetSource`; typed GfxObj and EnvCell requests deserialize immutable
`ObjectMeshData` while retaining the existing App worker, staging, render-thread
upload, cache, ownership, and shutdown contracts. The format-1/bake-tool-3
payload persists exact batch translucency so App does not reconstruct a
`GfxObjMesh` for metadata. Setup activation uses the package TOC as an explicit
type-presence index before reading valid Setup records through the bounded DAT
cache. `DatPreparedAssetSource` and `MeshExtractor` remain explicit
bake/equivalence/UI-Studio tools, not a production fallback. Portal → HighRes
→ Language → Cell lookup precedence is encoded directly in
`DatCollectionAdapter.TryResolvePreferred`. The connected physical and
installed-DAT gates are recorded in
`docs/research/2026-07-24-slice-c-prepared-asset-cutover-report.md`.
**Retail VFX hook compatibility seam (2026-07-14).** Chorizite.DatReaderWriter
2.1.7 models `CreateBlockingParticleHook` as the common hook header only, while
retail inherits the complete `CreateParticleHook` payload. The narrow readers in

View file

@ -1562,9 +1562,10 @@ port in any phase — no separate listing here.
> remains DEFERRED**: ECS is the *throughput* lever (raise avg FPS ~230→300), a
> DIFFERENT goal from steadiness — revisit ONLY if the user wants a higher raw FPS
> number and accepts the rewrite risk. Rust rejected. Parked (resume from the table
> below): MP1b pak/bake is now complete and the corrected physical-local
> baselines have landed, so MP1c is active. The remaining parked items are
> MP-Alloc hard sites and MP1c. MP2 has shipped. Full rationale:
> below): MP1b pak/bake and MP1c production cutover are now complete. The
> remaining parked items are MP-Alloc hard sites; typed residency and
> cost-budgeted streaming continue as Slices D/E in the modern-runtime plan.
> MP2 has shipped. Full rationale:
> `project_mp_track_findings.md`.
> The 2026-07-18 portal/resource-lifetime repair does not resume MP's ECS, pak,
> or throughput redesign. It is corrective ownership/reclamation work for a
@ -1588,10 +1589,15 @@ port in any phase — no separate listing here.
> portal windows, observer-effect measurement, binary provenance, and
> contention stacks. The physical runs confirm fast steady rendering but also
> preserve repeated-portal memory plateau failures, ~200 MiB allocation frames,
> and thousands of invalid typed-Setup probes as MP1c's before-state. Slice C
> is unblocked and active. Slice B EnvCell dedup is complete; its
> and thousands of invalid typed-Setup probes as MP1c's before-state. Slice B
> EnvCell dedup is complete; its
> [full-bake report](../research/2026-07-24-slice-b-full-bake-report.md)
> records the 42.6× result and corrected geometry-identity collision.
> **Slice C completed 2026-07-24:** production streaming consumes the validated
> bake-tool-3 package, all physical connected and user-visual gates passed,
> process allocation fell 41.861.9%, GC pause time fell 48.454.1%, and
> invalid typed-Setup exceptions fell to zero. [Cutover
> report](../research/2026-07-24-slice-c-prepared-asset-cutover-report.md).
**Spec:** `docs/superpowers/specs/2026-07-05-modern-pipeline-design.md` (the
umbrella design — read it first). **Goal:** smoothness first (no frame over
@ -1616,7 +1622,7 @@ hitch and follows.
| 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 2087ms → 610ms, alloc spikes (3075MB single-frame) eliminated, gen2 GC 511/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) |
| MP1c | Streaming cutover to pak + hitch gate | **ACTIVE 2026-07-24** — physical-local Slice A gate committed; baseline: uncapped CPU p50/p95/p99 1.520/5.694/7.825 ms, GPU 0.897/3.239/3.406 ms, portal allocation max 212.7 MiB, repeated-Caul working-set growth 295.1 MiB. Slice B package foundation is complete |
| 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 |

View file

@ -2,8 +2,7 @@
**Date:** 2026-07-24
**Status:** Slice A complete on the physical reference display; Slice B
complete. Slice C is unblocked and active.
**Status:** Slices AC complete. Slice D is next and not yet started.
**Scope:** Reconcile and sequence the existing Modern Pipeline (`MP`) and
Linux/headless (`LH`) tracks using the 2026-07-24 connected performance audit.
@ -631,6 +630,15 @@ collision in WorldBuilder's legacy 31× geometry hash. Evidence:
single-frame allocation and p99 materially improve from the 2026-07-24 baseline;
connected nine-stop route and screenshots pass.
**Completed 2026-07-24:** production streaming now consumes the validated
format-1/bake-tool-3 package through `IPreparedAssetSource`, with no implicit
live-DAT mesh fallback. Capped, uncapped, and dense-Arwic physical routes,
installed-DAT equivalence, lifecycle assertions, graceful teardown, the full
Release suite, and the user's visual gate passed. Process allocation fell
41.861.9%, GC pause time fell 48.454.1%, uncapped CPU/GPU p99 improved
17.0%/20.6%, and invalid typed-Setup exceptions fell to zero. Evidence:
[`../research/2026-07-24-slice-c-prepared-asset-cutover-report.md`](../research/2026-07-24-slice-c-prepared-asset-cutover-report.md).
**Amendment (2026-07-24 review).** Binding contracts for this slice:
- **Precedence contract.** The LINQ being replaced is load-bearing content
@ -1129,13 +1137,14 @@ an improvement" carries the same evidence burden as a regression claim.
## 14. Current execution point
Slice A's tooling and authoritative physical-local evidence are complete, and
Slice B's prepared-content bake is complete. Slice C is active. Its comparison
baseline is the committed physical-local capture at
`49f3a48ee63b82d00731b0f91497930925279c86`: uncapped CPU p50/p95/p99
1.520/5.694/7.825 ms, GPU p50/p95/p99 0.897/3.239/3.406 ms, portal allocation
max 212.7 MiB, process allocation mean 50.0 MB/s, and a repeated-Caul working
set plateau failure of 295.1 MiB.
Slices AC are complete. Slice C replaced production live-DAT world-mesh
extraction with the validated prepared source and passed all connected,
performance, lifetime, automated, and user-visual gates. Its physical
uncapped result is CPU p50/p95/p99 1.409/4.909/6.496 ms, GPU
0.832/2.510/2.706 ms, a 39.9 MiB largest frame allocation, zero invalid Setup
probe exceptions, and a passing warm repeated-Caul memory assertion. Slice D
is the next execution point; it unifies accounting and policy around the
specialized caches that remain, rather than replacing their working storage.
The intended order is therefore:

View file

@ -2,15 +2,18 @@
**Date:** 2026-07-24
**Status:** active — C1 and C2 landed; C3 implemented, C4 connected gate next
**Status:** complete — automated, connected, performance, and user visual
gates passed 2026-07-24
**Checkpoint ledger:**
- C1 typed prepared-source boundary — `c42f93b3`
- C2 production renderer injection — `f05afc07`
- C3 activation probes and lookup precedence — `230a7df4`; automated gate
green, connected exception-count gate remains in C4
- C4 connected equivalence and performance closeout — pending
green
- C4 prepared-source diagnostics — `b1ad4b7c`
- C4 connected equivalence and performance closeout —
[`../research/2026-07-24-slice-c-prepared-asset-cutover-report.md`](../research/2026-07-24-slice-c-prepared-asset-cutover-report.md)
**Parent:** [`2026-07-24-modern-runtime-architecture.md`](2026-07-24-modern-runtime-architecture.md),
Slice C / MP1c
@ -300,6 +303,32 @@ If pixels or behavior differ, the slice is not complete even when performance
improves. If timing improves but heap reservation still fails, keep the failure
open and attribute it before considering Slice C closed.
### C4 result — completed 2026-07-24
The format-1/bake-tool-3 production package validated with 751,141 keys and
the exact installed DAT iterations. Installed-DAT seam/equivalence tests, all
three connected physical-display routes, canonical lifetime assertions, and
the complete Release suite passed. The user accepted the prepared path's
geometry, materials, textures, and world presentation.
Against the committed Slice A physical baseline:
- capped CPU p95/p99 improved 15.8%/16.9%, GPU p95/p99 17.4%/20.0%;
- uncapped CPU p95/p99 improved 13.8%/17.0%, GPU p95/p99 22.5%/20.6%;
- dense-Arwic CPU/GPU p99 improved 12.0%/16.8%;
- process allocation fell 54.9% capped, 41.8% uncapped, and 61.9% dense;
- GC pause time fell 52.8%, 48.4%, and 54.1%;
- the invalid typed-Setup exception count fell to zero in every route;
- the largest frame-thread allocation fell from roughly 203 MiB to 46.8 MiB
capped and 39.9 MiB uncapped;
- both warm repeated-Caul memory comparisons passed the unchanged assertion.
Windows reports clean memory-mapped package pages in working set, so total
working set is higher even though private memory and ownership remain bounded.
That is file cache, not retained world state. Exact measurements and the
artifact policy are recorded in the
[Slice C report](../research/2026-07-24-slice-c-prepared-asset-cutover-report.md).
## 4. Review checklist per checkpoint
Because this task is running without delegated reviewers, the primary engineer

View file

@ -0,0 +1,134 @@
{
"schemaVersion": 1,
"capturedUtcDate": "2026-07-24",
"binaryCommit": "b1ad4b7c0a0a519ab36d707baba74890634e6028",
"result": "pass",
"visualGate": {
"status": "user-accepted",
"date": "2026-07-24",
"note": "Geometry, materials, textures, and world presentation accepted. Raw cross-run pixel differences were attributable to world lighting and post-turn camera state; no comparison tolerance was loosened."
},
"pak": {
"formatVersion": 1,
"bakeToolVersion": 3,
"datIterations": {
"portal": 2072,
"cell": 982,
"highRes": 497,
"language": 994
},
"keys": {
"gfxObj": 15318,
"setup": 5935,
"envCell": 729888,
"total": 751141
},
"bytes": 29562150456,
"gibibytes": 27.53,
"validation": "pass"
},
"runs": {
"capped": {
"baselineStem": "connected-r6-soak-20260724-141608",
"candidateStem": "connected-r6-soak-20260724-151701",
"candidateFrames": 111997,
"candidateRouteSeconds": 503.8,
"cpuMicroseconds": {
"baseline": { "p50": 4287, "p95": 6972, "p99": 9404 },
"candidate": { "p50": 4247, "p95": 5873, "p99": 7814 }
},
"gpuMicroseconds": {
"baseline": { "p50": 1702, "p95": 3261, "p99": 3847 },
"candidate": { "p50": 1836, "p95": 2694, "p99": 3078 }
},
"frameAllocationBytes": {
"baselineMaximum": 213380600,
"candidateMaximum": 49066536
},
"runtime": {
"baselineAllocatedBytes": 22611515504,
"candidateAllocatedBytes": 10207585632,
"baselineGcPauseSeconds": 2.9,
"candidateGcPauseSeconds": 1.368,
"baselineGcCollections": { "gen0": 191, "gen1": 159, "gen2": 54 },
"candidateGcCollections": { "gen0": 82, "gen1": 84, "gen2": 26 },
"baselineInvalidSetupProbeExceptions": 4851,
"candidateInvalidSetupProbeExceptions": 0
},
"warmCaulPlateauMibibytes": {
"workingSetGrowth": 80.6,
"privateGrowth": 53.8
},
"result": "pass"
},
"uncapped": {
"baselineStem": "connected-r6-soak-20260724-142604",
"candidateStem": "connected-r6-soak-20260724-153039",
"candidateFrames": 252334,
"candidateRouteSeconds": 497.893,
"cpuMicroseconds": {
"baseline": { "p50": 1520, "p95": 5694, "p99": 7825 },
"candidate": { "p50": 1409, "p95": 4909, "p99": 6496 }
},
"gpuMicroseconds": {
"baseline": { "p50": 897, "p95": 3239, "p99": 3406 },
"candidate": { "p50": 832, "p95": 2510, "p99": 2706 }
},
"frameAllocationBytes": {
"baselineMaximum": 212712648,
"candidateMaximum": 41885648
},
"runtime": {
"baselineAllocatedBytes": 25308730712,
"candidateAllocatedBytes": 14738650240,
"baselineGcPauseSeconds": 2.567,
"candidateGcPauseSeconds": 1.325,
"baselineGcCollections": { "gen0": 278, "gen1": 143, "gen2": 58 },
"candidateGcCollections": { "gen0": 188, "gen1": 83, "gen2": 23 },
"baselineInvalidSetupProbeExceptions": 4659,
"candidateInvalidSetupProbeExceptions": 0
},
"warmCaulPlateauMibibytes": {
"workingSetGrowth": 232.0,
"privateGrowth": 183.7
},
"result": "pass"
},
"denseArwic": {
"baselineStem": "connected-dense-town-20260724-143503",
"candidateStem": "connected-dense-town-20260724-153930",
"candidateFrames": 16732,
"candidateRouteSeconds": 57.394,
"cpuMicroseconds": {
"baseline": { "p50": 3672, "p95": 6561, "p99": 14121 },
"candidate": { "p50": 3487, "p95": 6044, "p99": 12432 }
},
"gpuMicroseconds": {
"baseline": { "p50": 2476, "p95": 3284, "p99": 3752 },
"candidate": { "p50": 2126, "p95": 2768, "p99": 3122 }
},
"frameAllocationBytes": {
"baselineMaximum": 29875456,
"candidateMaximum": 28200384
},
"runtime": {
"baselineAllocatedBytes": 5602582864,
"candidateAllocatedBytes": 2136709504,
"baselineGcPauseSeconds": 0.623,
"candidateGcPauseSeconds": 0.286,
"baselineGcCollections": { "gen0": 53, "gen1": 34, "gen2": 14 },
"candidateGcCollections": { "gen0": 19, "gen1": 19, "gen2": 7 },
"baselineInvalidSetupProbeExceptions": 331,
"candidateInvalidSetupProbeExceptions": 0
},
"result": "pass"
}
},
"focusedValidation": {
"preparedSourceSerializerAndEquivalence": { "passed": 20, "failed": 0 },
"fullBakeDeterminismAndArtifact": { "passed": 2, "failed": 0 },
"releaseBuild": "pass",
"completeReleaseSuite": { "passed": 8076, "skipped": 5, "failed": 0 }
},
"artifactPolicy": "The validated full pak is an untracked machine-local production input beside the DAT files. Raw routes, counters, screenshots, and logs remain untracked; this report is path-free committed evidence."
}

View file

@ -0,0 +1,123 @@
# Slice C prepared-asset cutover report
**Date:** 2026-07-24
**Measured binary:** `b1ad4b7c0a0a519ab36d707baba74890634e6028`
**Result:** pass
Slice C moves production world-mesh preparation out of the live client.
`acdream-bake` converts installed retail DAT geometry and material metadata
into an immutable `acdream.pak`; production streaming now reads those prepared
records without reconstructing the same DAT object graphs during portals.
DAT remains authoritative for simulation, UI, animation, dynamic appearance,
physics, audio, and effects.
The user's connected screenshot review passed on 2026-07-24. Raw screenshot
differences against Slice A were caused by non-identical world lighting and
post-turn camera state; the user confirmed the prepared path's geometry,
materials, textures, and world presentation look correct. No tolerance was
widened and no world pixels were masked to obtain that result.
## Production package
| Measure | Result |
|---|---:|
| Format / bake-tool version | 1 / 3 |
| GfxObj keys | 15,318 |
| Setup keys | 5,935 |
| EnvCell keys | 729,888 |
| Total TOC keys | 751,141 |
| Portal / Cell / HighRes / Language iteration | 2072 / 982 / 497 / 994 |
| Artifact size | 29,562,150,456 bytes (27.53 GiB) |
| Header, TOC, ranges, aliases, CRCs, and iterations | pass |
Bake-tool version 3 persists each texture batch's exact translucency
classification. That lets the render thread consume the prepared record
directly instead of rebuilding a `GfxObjMesh` solely to recover material
metadata. The validated package is a required machine-local production input
beside the DAT files; it is not a repository artifact.
Focused installed-DAT validation passed 20 prepared-source, serializer, and
pak-equivalence tests plus two full-bake determinism/artifact tests. The
complete Release suite passed 8,076 tests with five skips and zero failures.
## Connected physical-display result
All three runs used the committed reference AMD display configuration, a
source-matched Release binary, the same deterministic routes as Slice A, and
normal retained gameplay UI. Every destination materialized, movement/jump/
combat exercises passed, and every client disconnected gracefully.
| Capture | CPU p50 / p95 / p99 | GPU p50 / p95 / p99 | Frame allocation max |
|---|---:|---:|---:|
| Capped Slice A | 4.287 / 6.972 / 9.404 ms | 1.702 / 3.261 / 3.847 ms | 203.5 MiB |
| Capped Slice C | 4.247 / 5.873 / 7.814 ms | 1.836 / 2.694 / 3.078 ms | 46.8 MiB |
| Uncapped Slice A | 1.520 / 5.694 / 7.825 ms | 0.897 / 3.239 / 3.406 ms | 202.9 MiB |
| Uncapped Slice C | 1.409 / 4.909 / 6.496 ms | 0.832 / 2.510 / 2.706 ms | 39.9 MiB |
| Dense Arwic Slice A | 3.672 / 6.561 / 14.121 ms | 2.476 / 3.284 / 3.752 ms | 28.5 MiB |
| Dense Arwic Slice C | 3.487 / 6.044 / 12.432 ms | 2.126 / 2.768 / 3.122 ms | 26.9 MiB |
The capped path improved CPU p95/p99 by 15.8%/16.9% and GPU p95/p99 by
17.4%/20.0%. The uncapped path improved CPU p95/p99 by 13.8%/17.0% and GPU
p95/p99 by 22.5%/20.6%. Dense Arwic improved CPU p99 by 12.0% and GPU p99 by
16.8%. Capped GPU p50 rose by 0.134 ms, but every capped GPU tail percentile
improved; neither the uncapped nor dense captures repeated that median cost.
## Allocation, GC, and probe result
| Capture | Process allocation | GC pause | Gen0 / Gen1 / Gen2 | Invalid Setup probe exceptions |
|---|---:|---:|---:|---:|
| Capped Slice A | 22.61 GB | 2.900 s | 191 / 159 / 54 | 4,851 |
| Capped Slice C | 10.21 GB | 1.368 s | 82 / 84 / 26 | 0 |
| Uncapped Slice A | 25.31 GB | 2.567 s | 278 / 143 / 58 | 4,659 |
| Uncapped Slice C | 14.74 GB | 1.325 s | 188 / 83 / 23 | 0 |
| Dense Slice A | 5.60 GB | 0.623 s | 53 / 34 / 14 | 331 |
| Dense Slice C | 2.14 GB | 0.286 s | 19 / 19 / 7 | 0 |
Process allocation fell 54.9% capped, 41.8% uncapped, and 61.9% in dense
Arwic. GC pause time fell 52.8%, 48.4%, and 54.1%, respectively. The explicit
prepared Setup presence probe eliminated the normal-control-flow
`ArgumentOutOfRangeException` storm without swallowing unexpected failures.
The largest frame-thread allocation fell 77.0% capped and 80.3% uncapped.
This slice therefore removed most of the portal decode spike even though the
2026-07-24 audit correctly assigned the remaining publication/retirement work
to cost-budgeted streaming Slice E.
## Residency interpretation
Both repeated-Caul routes passed the existing warm-return/plateau memory
assertion without changing its threshold:
- capped Caul return to plateau: +80.6 MiB working set, +53.8 MiB private;
- uncapped Caul return to plateau: +232.0 MiB working set, +183.7 MiB private.
Handles and threads also returned to their established bands. First-to-final
working set is higher than Slice A because Windows includes clean pages from
the 27.53 GiB memory-mapped package in process working set. Those pages are
discardable file cache, not private heap or retained world ownership. Private
memory remained bounded across warm revisits, while entity, animation,
particle, mesh-upload, teardown, and ownership invariants passed.
This is a correctness-first package, not the final disk/residency format.
Deduplication and compression may reduce its footprint later, but they were
deliberately excluded from the byte/field-equivalence cutover.
## Architecture closeout
- Production `ObjectMeshManager` reads only `IPreparedAssetSource`; it has no
live-DAT extraction fallback.
- `DatPreparedAssetSource` remains explicit tooling/equivalence infrastructure.
- GfxObj, Setup, and EnvCell identities are typed and validated.
- Missing, corrupt, canceled, and loaded reads remain distinct.
- Portal DAT wins the non-allocating preferred-ID lookup.
- Worker preparation and render-thread GL upload ownership remain unchanged.
- Mesh workers stop before the package is unmapped during shutdown.
- Prepared-source probe/read/result counters are exposed in the existing
render resource diagnostic record.
Machine-readable, path-free evidence is in
[`2026-07-24-slice-c-prepared-asset-cutover-report.json`](2026-07-24-slice-c-prepared-asset-cutover-report.json).
Raw frame histories, counters, reports, screenshots, and logs remain local
untracked diagnostic artifacts.

View file

@ -5,14 +5,15 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
namespace AcDream.Content;
/// <summary>
/// THE <see cref="DatCollection"/> → <see cref="IDatReaderWriter"/> adapter,
/// shared by all three consumers: ObjectMeshManager (AcDream.App, via
/// WbMeshAdapter), acdream-bake (AcDream.Bake.BakeRunner), and the pak
/// equivalence suite (AcDream.Content.Tests). MP1b's review found the
/// shared by runtime DAT-backed gameplay/content access,
/// acdream-bake (AcDream.Bake.BakeRunner), and the pak equivalence suite
/// (AcDream.Content.Tests). MP1b's review found the
/// original App-internal copy had silently forked into three near-identical
/// versions — drift between them is exactly what the live-vs-pak equivalence
/// suite CANNOT detect (both sides would drift together only if they share
@ -218,6 +219,7 @@ public sealed class DatDatabaseWrapper : IDatDatabase {
// cache itself documents why estimated bytes are not a hard heap bound.
private readonly BoundedDatObjectCache _cache = new();
private readonly object _databaseLock = new();
private int _malformedRecordDiagnosticEmitted;
public DatDatabaseWrapper(DatDatabase db) {
ArgumentNullException.ThrowIfNull(db);
@ -241,6 +243,7 @@ public sealed class DatDatabaseWrapper : IDatDatabase {
return true;
}
bool existingRecordFailed;
lock (_databaseLock) {
// A different reader may have populated the cache while this
// caller waited for the serialized DatDatabase read path.
@ -253,17 +256,25 @@ public sealed class DatDatabaseWrapper : IDatDatabase {
return true;
}
// TEMP diagnostic (dat-race investigation 2026-06-09, strip with fix):
// a miss for an id whose BTree entry EXISTS is always an anomaly —
// either Unpack returned false or the lookup flickered transiently.
// Legit not-found probes (e.g. Portal→HighRes fallback) stay silent.
// Kept through the MP1b unification — the tripwire now covers the
// bake tool and the equivalence suite too, not just the client.
if (_db.Tree.TryGetFile(fileId, out _)) {
Console.WriteLine(
$"[dat-miss] {typeof(T).Name} 0x{fileId:X8} entry EXISTS but TryGet failed " +
$"(thread={Environment.CurrentManagedThreadId})");
}
// A miss for an id whose BTree entry exists is a typed-access
// anomaly (wrong requested type, malformed content, or an unpacker
// failure). Capture only the fact while serialized DAT access is
// held; diagnostics must never perform console I/O under this
// shared lock.
existingRecordFailed = _db.Tree.TryGetFile(fileId, out _);
}
// One actionable report per database keeps a repeatedly requested
// malformed record from becoming another exception/logging-style hot
// path. Missing keys stay silent.
if (existingRecordFailed
&& Interlocked.Exchange(
ref _malformedRecordDiagnosticEmitted,
1) == 0)
{
Console.Error.WriteLine(
$"[dat-miss] {typeof(T).Name} 0x{fileId:X8} entry EXISTS but TryGet failed " +
$"(thread={Environment.CurrentManagedThreadId}; further reports suppressed)");
}
return false;