From bc0139e3f9fab127b46cf81e504a74c6f377bec5 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 5 Jul 2026 19:58:29 +0200 Subject: [PATCH] docs(pipeline): MP1a implementation plan - AcDream.Content extraction Mechanical-move plan: lift TextureKey out of the GL atlas class, move the ObjectMeshData family, extract the Prepare* CPU pipeline verbatim into a new MeshExtractor in AcDream.Content (net10.0, no Silk.NET), App keeps queue/worker/upload lifecycle and delegates. Binding rules: verbatim bodies (no SurfaceDecoder switch - byte-identity is MP1b conformance foundation), compiler enumerates dependencies, BLOCKED on any hidden GL dependency, zero divergence rows. Gates: full suite green per task + user launch smoke. Boundary facts verified against source this session: the Prepare* region touches GL only via the TextureKey struct; first GL call is UploadGfxObjMeshData. Co-Authored-By: Claude Fable 5 --- .../2026-07-05-mp1a-content-extraction.md | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-05-mp1a-content-extraction.md diff --git a/docs/superpowers/plans/2026-07-05-mp1a-content-extraction.md b/docs/superpowers/plans/2026-07-05-mp1a-content-extraction.md new file mode 100644 index 00000000..79aab3c1 --- /dev/null +++ b/docs/superpowers/plans/2026-07-05-mp1a-content-extraction.md @@ -0,0 +1,221 @@ +# MP1a — AcDream.Content Extraction Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Extract the GL-free CPU half of the WB mesh/texture pipeline out of `AcDream.App` into a new `AcDream.Content` assembly, so the MP1b bake tool can run it without an OpenGL context — with **zero behavior change** (the client keeps running the exact same code, relocated). + +**Architecture:** Mechanical move, not a rewrite. `ObjectMeshManager`'s `Prepare*` family (dat read → mesh extraction → inline texture decode → `ObjectMeshData`) moves verbatim into a new `MeshExtractor` class in `AcDream.Content`; `ObjectMeshManager` keeps its queue/worker/GL-upload lifecycle and delegates. The one GL-domain entanglement is `TextureAtlasManager.TextureKey` (a pure-data struct nested in a GL class) — it gets lifted out first. Spec: `docs/superpowers/specs/2026-07-05-modern-pipeline-design.md` §6.1 ("Extraction prerequisite"). MP1b (bake tool + pak) and MP1c (streaming cutover) are separate plans. + +**Tech Stack:** .NET 10 class library (no Silk.NET), `BCnEncoder.Net.ImageSharp` 1.1.2 + `SixLabors.ImageSharp` 3.1.12 (the inline DXT decode's packages), `AcDream.Core` project reference (TextureHelpers etc.). + +**Binding rules:** +- **Verbatim bodies.** Method bodies move unchanged — same statements, same inline decode (do NOT "clean up" by switching to `SurfaceDecoder`; the two decoders are not proven bit-identical and byte-identity is MP1b's conformance foundation). Only mechanical edits allowed: namespace/using lines, `private` → `public` on moved members the old class must still reach, field references rehomed onto the new class. +- **The compiler enumerates dependencies.** If moving a method drags in a type, move it if it's GL-free; if a dependency turns out to touch GL (`Silk.NET`, `OpenGLGraphicsDevice`, `GlobalMeshBuffer`, `TextureAtlasManager` beyond the lifted key, `ManagedGL*`), **STOP and report BLOCKED** — do not improvise a redesign. +- **No divergence-register rows** — a pure relocation introduces no behavior deviation. Any observed behavior change is a bug in the move. +- Verified boundary facts (2026-07-05, this worktree): the `Prepare*` region's only GL-domain reference is `TextureAtlasManager.TextureKey` (`ObjectMeshManager.cs:1286,1631,2236,2254`); first real GL call is `UploadGfxObjMeshData` at `:1833`. Line numbers drift — match by symbol. + +--- + +## File map + +| File | Action | Responsibility | +|---|---|---| +| `src/AcDream.Content/AcDream.Content.csproj` | Create | GL-free content/asset assembly (net10.0) | +| `src/AcDream.Content/TextureKey.cs` | Create (lift) | The atlas dedup key struct, moved out of `TextureAtlasManager` | +| `src/AcDream.Content/ObjectMeshData.cs` | Create (move) | The boundary records: `ObjectMeshData`, `MeshBatchData`, `TextureBatchData`, `VertexPositionNormalTexture`, and sibling GL-free data records from `ObjectMeshManager.cs`'s data region | +| `src/AcDream.Content/MeshExtractor.cs` | Create (move) | The `Prepare*` family + its private helpers/caches, verbatim | +| `src/AcDream.Content/` (as compiler demands) | Create (move) | `IDatReaderWriter.cs`, `DatCollectionAdapter.cs`, `AcSurfaceMetadata.cs`, `AcSurfaceMetadataTable.cs`, `EdgeLineBuilder.cs`, `GeometryUtils.cs` — GL-free deps of the extractor (move only the ones the build actually requires) | +| `src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs` | Modify | Keeps queue/worker/upload lifecycle; delegates mesh prep to `MeshExtractor`; loses the moved types/methods | +| `src/AcDream.App/Rendering/Wb/TextureAtlasManager.cs` | Modify | Uses the lifted `TextureKey` | +| `src/AcDream.App/AcDream.App.csproj` | Modify | Add `ProjectReference` to Content | +| `AcDream.slnx` | Modify | Register the new project | +| `docs/architecture/worldbuilder-inventory.md` | Modify | Record the relocation (Code Structure Rule 2 requires the inventory note for project-reference changes) | + +`AcDream.Core` is untouched. `AcDream.Content` references Core; App references Content; Core must NOT reference Content. + +--- + +### Task 1: Create the AcDream.Content project + +**Files:** +- Create: `src/AcDream.Content/AcDream.Content.csproj` +- Modify: `AcDream.slnx`, `src/AcDream.App/AcDream.App.csproj` + +- [ ] **Step 1: Project file** + +```xml + + + + net10.0 + enable + enable + true + + + + + + + + + + + + + +``` + +NOTE: `Chorizite.DatReaderWriter` flows transitively via AcDream.Core. If the moved code fails to resolve DRW types, add the explicit `PackageReference` pinned to the same version as Core (2.1.7) rather than bumping anything. + +- [ ] **Step 2: Register in `AcDream.slnx`** — add the project entry following the existing `` pattern (open the file, copy the shape). + +- [ ] **Step 3: App references Content** — in `src/AcDream.App/AcDream.App.csproj`, add alongside the existing ProjectReferences: + +```xml + +``` + +- [ ] **Step 4: Build** + +Run: `dotnet build` +Expected: green (empty project compiles, references resolve). + +- [ ] **Step 5: Commit** + +```bash +git add src/AcDream.Content/ AcDream.slnx src/AcDream.App/AcDream.App.csproj +git commit -m "feat(pipeline): MP1a - AcDream.Content assembly scaffold" +``` + +--- + +### Task 2: Lift TextureKey out of TextureAtlasManager + +**Files:** +- Create: `src/AcDream.Content/TextureKey.cs` +- Modify: `src/AcDream.App/Rendering/Wb/TextureAtlasManager.cs` (delete the nested struct, use the lifted one) +- Modify: every `TextureAtlasManager.TextureKey` reference site (find with `grep -rn "TextureAtlasManager.TextureKey" src/`) + +- [ ] **Step 1: Move the struct.** Copy the nested `TextureKey` struct out of `TextureAtlasManager` VERBATIM (fields, equality/GetHashCode if present, doc comments) into: + +```csharp +namespace AcDream.Content; + +// MP1a (2026-07-05): lifted verbatim out of TextureAtlasManager (GL class) +// so the GL-free mesh-extraction path (MeshExtractor / ObjectMeshData) can +// reference the atlas dedup key without a Silk.NET dependency. Field set, +// equality semantics, and hash behavior are UNCHANGED. +public struct TextureKey +{ + // ... the verbatim body from TextureAtlasManager.TextureKey ... +} +``` + +(The plan intentionally does not restate the body — copy it exactly from the source; it is pure data: SurfaceId/PaletteId/Stippling/IsSolid per the boundary survey.) + +- [ ] **Step 2: Update all reference sites.** `TextureAtlasManager.TextureKey` → `TextureKey` with `using AcDream.Content;`. Keep a `using` alias only if a name collision appears (none expected). + +- [ ] **Step 3: Build + full test suite** + +Run: `dotnet build` then `dotnet test` +Expected: green / all passing (this is a pure type relocation). + +- [ ] **Step 4: Commit** + +```bash +git add -A +git commit -m "refactor(pipeline): MP1a - lift TextureKey out of the GL atlas class" +``` + +--- + +### Task 3: Move the boundary data records + +**Files:** +- Create: `src/AcDream.Content/ObjectMeshData.cs` +- Modify: `src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs` (types removed), all reference sites (`grep -rn "ObjectMeshData\|VertexPositionNormalTexture\|TextureBatchData\|MeshBatchData" src/ tests/`) + +- [ ] **Step 1: Move these types from `ObjectMeshManager.cs`'s data region into `AcDream.Content` (namespace `AcDream.Content`), VERBATIM:** `VertexPositionNormalTexture` (the 32-byte GPU vertex — this is the pak layout target, do not alter layout/stride), `ObjectMeshData`, `MeshBatchData`, `TextureBatchData`, plus any small GL-free records the compiler then demands (e.g. setup-part / emitter-info records referenced by `ObjectMeshData` fields). + + **Explicitly stays in App:** `ObjectRenderData` and `ObjectRenderBatch` (they hold `TextureAtlasManager Atlas` — a GL class) and anything else the compiler shows touching GL. If `ObjectMeshData` itself turns out to hold a GL-typed field the survey missed, STOP and report BLOCKED. + +- [ ] **Step 2: Update reference sites** (`using AcDream.Content;` in the App files + any tests). + +- [ ] **Step 3: Build + full test suite** — green / all passing. + +- [ ] **Step 4: Commit** + +```bash +git add -A +git commit -m "refactor(pipeline): MP1a - ObjectMeshData family moved to AcDream.Content" +``` + +--- + +### Task 4: Extract MeshExtractor (the Prepare* family) + +**Files:** +- Create: `src/AcDream.Content/MeshExtractor.cs` +- Move as the compiler demands: `IDatReaderWriter.cs`, `DatCollectionAdapter.cs`, `AcSurfaceMetadata.cs`, `AcSurfaceMetadataTable.cs`, `EdgeLineBuilder.cs`, `GeometryUtils.cs` from `src/AcDream.App/Rendering/Wb/` → `src/AcDream.Content/` (verbatim, namespace change only) +- Modify: `src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs` + +- [ ] **Step 1: Create the extractor class.** New class in Content: + +```csharp +namespace AcDream.Content; + +/// +/// MP1a (2026-07-05): the GL-free CPU half of the former ObjectMeshManager — +/// dat read → polygon walk → vertex/index build → inline texture decode → +/// . Extracted VERBATIM so the MP1b bake tool +/// and the live client run the SAME extraction code (byte-identical output +/// is the pak conformance foundation). ObjectMeshManager (App) retains the +/// queue/worker lifecycle and all GL upload; it delegates here. +/// Spec: docs/superpowers/specs/2026-07-05-modern-pipeline-design.md §6.1. +/// +public sealed class MeshExtractor +{ + // Constructor takes the extraction dependencies the moved methods used + // as ObjectMeshManager fields — expected from the boundary survey: + // IDatReaderWriter dats, AcSurfaceMetadataTable surfaceMetadata + // plus the CPU-side caches that belong with extraction: + // the ThreadLocal, the decoded-texture cache, bounds cache. + // Let the compiler produce the exact list — add fields here, do not + // leave them behind in App if only Prepare* uses them. +} +``` + +- [ ] **Step 2: Move the methods VERBATIM onto `MeshExtractor`, making the entry points `public`:** `PrepareMeshData`, `PrepareGfxObjMeshData`, `PrepareEnvCellMeshData`, `PrepareCellStructMeshData`, `PrepareSetupMeshData`, `PrepareCellStructEdgeLineData`, and the private helpers they call (`CollectParts`, `ComputeBounds`, `CollectDrawingBspPolygonIds`, `BuildPolygonIndices`, `BuildCellStructPolygonIndices`, `CollectEmittersFromScript`, plus whatever else the compiler names). Move the fields/caches only they use. Bodies unchanged. + +- [ ] **Step 3: Delegate from ObjectMeshManager.** It constructs one `MeshExtractor` in its constructor (passing its dat reader + metadata table) and every former `Prepare*` call site becomes `_extractor.Prepare*`. The staged-queue enqueue, worker pool, retry, and `Dispose` quiesce logic stay in `ObjectMeshManager` untouched (the MP0-investigated teardown discipline is a runtime concern; the bake tool will drive the extractor with its own simple loop in MP1b). + +- [ ] **Step 4: Move the support files** the compiler demands (list above), namespace `AcDream.Content`, bodies verbatim. `Building.cs` is NOT in scope (its GL-query fields keep it in App for now). + +- [ ] **Step 5: Build + FULL test suite** + +Run: `dotnet build` then `dotnet test` +Expected: green / all passing. The full suite is the behavior gate — no test may change. + +- [ ] **Step 6: Update the inventory doc.** In `docs/architecture/worldbuilder-inventory.md`, add an MP1a note under the extracted-code section: the CPU mesh-extraction half now lives in `src/AcDream.Content/` (list the moved files), App retains GL upload/lifecycle, Core untouched, reason = MP1 bake tool needs GL-free extraction (spec §6.1). + +- [ ] **Step 7: Commit** + +```bash +git add -A +git commit -m "refactor(pipeline): MP1a - MeshExtractor extracted to AcDream.Content (verbatim move)" +``` + +--- + +### Task 5: Launch smoke (user-gated) + +- [ ] **Step 1:** `dotnet build -c Release` — green. +- [ ] **Step 2:** Launch per the standard live-launch recipe (CLAUDE.md), user in-world for ~2 minutes: Holtburg buildings + an interior + a dungeon teleport. Acceptance: world renders identically — no missing/white/wrong geometry or textures (the #105-class signature would indicate the move broke a per-frame driver; per `feedback_extraction_perframe_drivers` the lifecycle stayed in App precisely to avoid this, so any anomaly = STOP and investigate the move). +- [ ] **Step 3:** On user confirmation, update the roadmap Track MP table (MP1a slice shipped) and commit. + +```bash +git add docs/plans/2026-04-11-roadmap.md +git commit -m "docs(pipeline): MP1a shipped - Content extraction user-gated" +```