docs(perf): bind Slice C prepared-asset cutover
Define the typed prepared-source contract, runtime identity rules, production injection and lifetime order, activation probe cleanup, and the exact equivalence/performance gates for MP1c.
This commit is contained in:
parent
2421d09f5d
commit
7fb7189de8
1 changed files with 306 additions and 0 deletions
306
docs/plans/2026-07-24-modern-runtime-slice-c-prepared-assets.md
Normal file
306
docs/plans/2026-07-24-modern-runtime-slice-c-prepared-assets.md
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
# Modern Runtime Slice C — prepared-asset cutover
|
||||
|
||||
**Date:** 2026-07-24
|
||||
|
||||
**Status:** active
|
||||
|
||||
**Parent:** [`2026-07-24-modern-runtime-architecture.md`](2026-07-24-modern-runtime-architecture.md),
|
||||
Slice C / MP1c
|
||||
|
||||
**Authoritative before-state:**
|
||||
[`../research/2026-07-24-slice-a-physical-local-baselines.md`](../research/2026-07-24-slice-a-physical-local-baselines.md)
|
||||
at source and binary commit
|
||||
`49f3a48ee63b82d00731b0f91497930925279c86`.
|
||||
|
||||
## 1. Outcome
|
||||
|
||||
Production world-mesh streaming reads immutable `ObjectMeshData` from the
|
||||
validated `acdream.pak`; it no longer reconstructs Setup, GfxObj, EnvCell,
|
||||
Surface, texture, and particle dependency graphs from DAT on four background
|
||||
workers during gameplay.
|
||||
|
||||
DAT remains the retail source of truth for simulation, UI, animation, dynamic
|
||||
appearance composition, physics, audio, and effects. The cutover changes when
|
||||
static render payloads are prepared, not their bytes, pixels, ownership, or
|
||||
gameplay meaning.
|
||||
|
||||
The slice is complete only when:
|
||||
|
||||
- the package is a required production input with exact DAT-iteration and bake
|
||||
version validation;
|
||||
- ordinary GfxObj and deduplicated EnvCell requests use typed pak keys;
|
||||
- missing, corrupt, canceled, and successful reads remain distinct;
|
||||
- production streaming has no implicit live-DAT extraction fallback;
|
||||
- Setup activation uses an explicit typed presence check before parsing DAT;
|
||||
- Portal DAT still wins when an ID exists in both Portal and HighRes;
|
||||
- cancellation and unexpected exceptions are never swallowed;
|
||||
- installed-DAT byte/field equivalence and the connected visual/lifetime gates
|
||||
pass;
|
||||
- portal allocation, exception count, p99, and repeated-Caul memory materially
|
||||
improve against the committed physical baseline.
|
||||
|
||||
## 2. Fixed architecture
|
||||
|
||||
### 2.1 Content-layer contract
|
||||
|
||||
Add a BCL/Content-only `IPreparedAssetSource` with a typed request and typed
|
||||
result:
|
||||
|
||||
```csharp
|
||||
enum PreparedAssetReadStatus
|
||||
{
|
||||
Loaded,
|
||||
Missing,
|
||||
Corrupt,
|
||||
}
|
||||
|
||||
readonly record struct PreparedAssetRequest(
|
||||
PakAssetType Type,
|
||||
uint SourceFileId,
|
||||
ulong RuntimeObjectId,
|
||||
PreparedEnvCellSchema? EnvCell);
|
||||
|
||||
readonly record struct PreparedAssetReadResult(
|
||||
PreparedAssetReadStatus Status,
|
||||
ObjectMeshData? Data);
|
||||
|
||||
interface IPreparedAssetSource : IDisposable
|
||||
{
|
||||
PreparedAssetPresence Probe(PakAssetType type, uint sourceFileId);
|
||||
PreparedAssetReadResult Read(
|
||||
in PreparedAssetRequest request,
|
||||
CancellationToken cancellationToken);
|
||||
PreparedAssetSourceStats Stats { get; }
|
||||
}
|
||||
```
|
||||
|
||||
`CancellationToken` cancellation throws `OperationCanceledException`; it is
|
||||
not converted into a missing asset. `Loaded` always carries data and
|
||||
Missing/Corrupt never do.
|
||||
|
||||
The existing `PakKey` already persists the asset-type byte in each TOC key.
|
||||
Therefore Slice C does not invent a second type table or bump the binary
|
||||
format. A TOC-only probe binary-searches the exact
|
||||
`(PakAssetType, SourceFileId)` key without reading, CRC-checking, copying, or
|
||||
deserializing the blob. This is the explicit positive/negative/type metadata.
|
||||
|
||||
`PakPreparedAssetSource` owns one `PakReader`. It:
|
||||
|
||||
- validates format, bake-tool version, and all four DAT iterations at open;
|
||||
- exposes TOC-only presence;
|
||||
- maps PakReader's missing/corrupt/loaded outcomes without ambiguity;
|
||||
- validates the deserialized payload's logical type and runtime identity;
|
||||
- never falls through to DAT extraction.
|
||||
|
||||
An explicit `DatPreparedAssetSource` is retained only for bake/equivalence and
|
||||
UI Studio tooling. It uses `MeshExtractor` and is never selected by
|
||||
`GameWindow`.
|
||||
|
||||
### 2.2 Request identity
|
||||
|
||||
The renderer's internal runtime ID and the package lookup ID are not always
|
||||
the same:
|
||||
|
||||
| Request | Pak key | `ObjectMeshData.ObjectId` |
|
||||
|---|---|---|
|
||||
| ordinary world/live mesh | `GfxObjMesh + GfxObj DID` | GfxObj DID |
|
||||
| Setup tooling request | `SetupMesh + Setup DID` | Setup DID |
|
||||
| EnvCell shell | `EnvCellMesh + source Cell DID` | deduplicated geometry ID |
|
||||
|
||||
`EnvCellShellPlacement.CellId` is therefore carried into
|
||||
`EnvCellMeshPreparationScheduler` and retained beside the existing geometry
|
||||
schema. Cancellation/re-arm reuses that exact descriptor; it never derives a
|
||||
fake DAT ID from the synthetic geometry hash.
|
||||
|
||||
Aliases in the pak make every source Cell DID resolve to the one shared
|
||||
physical geometry blob. Runtime validates that the blob's `ObjectId` equals
|
||||
the scheduler's deduplicated geometry ID before publishing it.
|
||||
|
||||
### 2.3 Ownership and thread model
|
||||
|
||||
- `ContentEffectsAudioComposition` opens DAT first, then the prepared source,
|
||||
and publishes both through typed owners.
|
||||
- `GameWindow` stores only the additional lifetime root and passes the source
|
||||
through `ContentEffectsAudioResult`.
|
||||
- `WorldRenderComposition` injects the source into `WbMeshAdapter`.
|
||||
- `ObjectMeshManager`'s existing four workers read immutable prepared
|
||||
payloads. They retain the current request deduplication, LIFO locality,
|
||||
cancellation, CPU cache, staging high-water, ownership, and retry rules.
|
||||
- Pak reads and deserialization remain worker-only. GL upload remains
|
||||
render-thread-only.
|
||||
- Shutdown disposes `WbMeshAdapter` and joins its workers before unmapping the
|
||||
pak, then disposes DAT. Construction rollback uses the reverse order.
|
||||
- No worker, source, or result stores a window reference or service locator.
|
||||
|
||||
### 2.4 Setup activation and non-allocating DAT lookup
|
||||
|
||||
`WorldEntity.SourceGfxObjOrSetupId` can be either a GfxObj or Setup. The current
|
||||
`SequencerFactory` and `ResolveActivation` try to parse every value as Setup;
|
||||
the physical baseline records 4,659 `ArgumentOutOfRangeException` events on one
|
||||
uncapped route.
|
||||
|
||||
Both paths first probe `SetupMesh + id` in the prepared source:
|
||||
|
||||
- absent: return the existing no-Setup/no-script result without a DAT call;
|
||||
- present: parse the valid Setup through the bounded DAT object cache;
|
||||
- corrupt package entry: renderer load fails loudly; activation does not guess;
|
||||
- cancellation: rethrow;
|
||||
- known corrupt DAT data (`InvalidDataException`, `EndOfStreamException`, and
|
||||
the exact verified DatReaderWriter malformed-record exception): diagnose and
|
||||
return no activation;
|
||||
- unexpected exceptions: diagnose and rethrow.
|
||||
|
||||
The exception list is evidence-driven from installed fixtures and tests; a
|
||||
blanket `catch` is forbidden.
|
||||
|
||||
`IDatReaderWriter.TryResolvePreferred` is added as a non-allocating lookup.
|
||||
The concrete adapter checks Portal first, then HighRes, Language, and Cell.
|
||||
That encodes the existing load-bearing Portal-wins behavior directly.
|
||||
`ResolveId()` remains for compatibility tooling, but production mesh
|
||||
extraction and bounds code no longer use
|
||||
`ResolveId().ToList().OrderByDescending(...)`.
|
||||
|
||||
### 2.5 Texture identity
|
||||
|
||||
Pak payloads contain default-palette texture bytes. Dynamic appearance
|
||||
composition continues through the existing owner-scoped `TextureCache`.
|
||||
Default and palette-overlaid pixels retain distinct cache keys; the prepared
|
||||
source never inserts default bytes into the dynamic composite cache.
|
||||
|
||||
## 3. Checkpoints
|
||||
|
||||
### C1 — typed prepared-source boundary
|
||||
|
||||
Implement:
|
||||
|
||||
- typed request/presence/read-result/stat records;
|
||||
- PakReader TOC-only probe and status-preserving read;
|
||||
- `PakPreparedAssetSource` compatibility validation;
|
||||
- explicit `DatPreparedAssetSource` for tooling;
|
||||
- `RuntimeOptions.PreparedAssetPath`, defaulting to
|
||||
`<DatDir>/acdream.pak`, with `ACDREAM_PAK_PATH` as the sole override;
|
||||
- Content tests for absent/present/corrupt/canceled/type-mismatched reads,
|
||||
alias identity, iteration mismatch, bake-version mismatch, and disposal.
|
||||
|
||||
Gate:
|
||||
|
||||
- Content and Bake tests green;
|
||||
- current pak round-trip/equivalence tests remain green;
|
||||
- no App/runtime behavior changed yet.
|
||||
|
||||
Commit as one boundary unit.
|
||||
|
||||
### C2 — production renderer injection
|
||||
|
||||
Implement:
|
||||
|
||||
- prepared-source acquisition/publication/teardown in composition;
|
||||
- `WorldRenderComposition` and `WbMeshAdapter` injection;
|
||||
- `ObjectMeshManager` worker reads through `IPreparedAssetSource`;
|
||||
- source Cell DID propagation for EnvCell aliases;
|
||||
- exact terminal-failure and cancellation semantics;
|
||||
- current CPU cache, staging, upload, retry, ownership, and shutdown behavior
|
||||
retained;
|
||||
- UI Studio explicitly opts into the live-DAT tooling source;
|
||||
- remove production worker calls to `MeshExtractor`.
|
||||
|
||||
Focused tests:
|
||||
|
||||
- GfxObj request key and identity;
|
||||
- EnvCell alias key plus geometry identity;
|
||||
- cached/re-armed/canceled generation;
|
||||
- missing and corrupt entries become terminal preparation failures without
|
||||
retry storms;
|
||||
- source disposed only after workers and mesh adapter;
|
||||
- production composition cannot construct without a valid prepared source;
|
||||
- tooling source remains explicit.
|
||||
|
||||
Gate:
|
||||
|
||||
- App Release build/tests green;
|
||||
- source audit finds no production `MeshExtractor.Prepare*` call from the
|
||||
streaming workers;
|
||||
- missing pak and incompatible pak fail at startup with actionable messages.
|
||||
|
||||
Commit as one integration unit.
|
||||
|
||||
### C3 — activation probes and lookup precedence
|
||||
|
||||
Implement:
|
||||
|
||||
- Setup-presence gate shared by `SequencerFactory` and `ResolveActivation`;
|
||||
- specific corrupt-data exception handling and diagnostics;
|
||||
- cancellation and unexpected-exception propagation;
|
||||
- non-allocating Portal-first `TryResolvePreferred`;
|
||||
- remove the old LINQ lookup in `MeshExtractor`, recursive part collection,
|
||||
and remaining bounds helper;
|
||||
- explicit default-palette versus palette-overlay cache-identity tests.
|
||||
|
||||
Focused tests:
|
||||
|
||||
- GfxObj entity never enters Setup parser;
|
||||
- valid Setup parses once then hits bounded cache;
|
||||
- corrupt Setup is diagnosed once;
|
||||
- unexpected exception is visible;
|
||||
- cancellation propagates;
|
||||
- dual-DAT fixture chooses Portal;
|
||||
- missing ID allocates no result collection;
|
||||
- drudge, robed-player, and palette-dyed-armor seam fixtures preserve fields
|
||||
and pixels.
|
||||
|
||||
Gate:
|
||||
|
||||
- no `ResolveActivation` bare catch;
|
||||
- zero `ArgumentOutOfRangeException` Setup-probe storm on connected route;
|
||||
- full Release suite green.
|
||||
|
||||
Commit as one semantic-cleanup unit.
|
||||
|
||||
### C4 — bake, connected equivalence, and performance closeout
|
||||
|
||||
1. Re-bake installed DATs atomically to the production path with the current
|
||||
tool.
|
||||
2. Validate the full artifact, key counts, aliases, CRC contract, iterations,
|
||||
and exact source/binary provenance.
|
||||
3. Run focused byte/field equivalence and seam fixtures.
|
||||
4. Run the capped and uncapped physical nine-stop routes plus pinned dense
|
||||
Arwic with the same reference configuration.
|
||||
5. Compare fixed-camera screenshots under the committed tolerance/mask rule.
|
||||
6. Compare per-portal p99/allocation, process allocation/GC, exception count,
|
||||
cache counters, and repeated-Caul ownership/memory to Slice A.
|
||||
7. Run Release build and the complete Release suite.
|
||||
8. Update the parent plan, roadmap, WorldBuilder inventory, divergence
|
||||
register if and only if a real deviation changed, and durable memory.
|
||||
|
||||
Acceptance:
|
||||
|
||||
- every route reaches every destination and disconnects gracefully;
|
||||
- no world, animation, particle, effect, texture, mesh, landblock, or GPU
|
||||
ownership regression;
|
||||
- fixed-camera screenshots pass;
|
||||
- no invalid Setup exception storm;
|
||||
- no production DAT mesh extraction;
|
||||
- portal single-frame allocation and p99 materially improve;
|
||||
- process allocation/GC materially improve;
|
||||
- repeated-Caul memory passes the existing assertion without weakening it.
|
||||
|
||||
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.
|
||||
|
||||
## 4. Review checklist per checkpoint
|
||||
|
||||
Because this task is running without delegated reviewers, the primary engineer
|
||||
performs three explicit read-only passes over each complete checkpoint diff:
|
||||
|
||||
1. **Contract/conformance:** request identity, Portal precedence, package
|
||||
compatibility, byte/field/pixel equivalence.
|
||||
2. **Architecture/lifetime:** dependency direction, worker/render-thread
|
||||
ownership, cancellation, construction rollback, shutdown order, no
|
||||
production fallback.
|
||||
3. **Adversarial:** corrupt/truncated package, missing key, stale/canceled
|
||||
request, alias mismatch, callback failure, exception visibility, repeated
|
||||
portal/GUID/landblock churn.
|
||||
|
||||
Every confirmed finding is fixed and the full diff is re-read before the
|
||||
checkpoint commit.
|
||||
Loading…
Add table
Add a link
Reference in a new issue