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 <noreply@anthropic.com>
This commit is contained in:
parent
86d7094451
commit
bc0139e3f9
1 changed files with 221 additions and 0 deletions
221
docs/superpowers/plans/2026-07-05-mp1a-content-extraction.md
Normal file
221
docs/superpowers/plans/2026-07-05-mp1a-content-extraction.md
Normal file
|
|
@ -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
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- MP1a: the inline DXT decode moved from ObjectMeshManager uses these
|
||||
exact versions (mirror AcDream.App.csproj — keep in lockstep). -->
|
||||
<PackageReference Include="BCnEncoder.Net.ImageSharp" Version="1.1.2" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AcDream.Core\AcDream.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
```
|
||||
|
||||
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 `<Project Path="src/AcDream.Core/AcDream.Core.csproj" />` 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
|
||||
<ProjectReference Include="..\AcDream.Content\AcDream.Content.csproj" />
|
||||
```
|
||||
|
||||
- [ ] **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;
|
||||
|
||||
/// <summary>
|
||||
/// MP1a (2026-07-05): the GL-free CPU half of the former ObjectMeshManager —
|
||||
/// dat read → polygon walk → vertex/index build → inline texture decode →
|
||||
/// <see cref="ObjectMeshData"/>. 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.
|
||||
/// </summary>
|
||||
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<BcDecoder>, 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"
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue