Plan: 7 tasks decomposing spec T2..T9 with bite-sized TDD-style steps, exact file paths, commit-message templates, and a T4 safety-check branch (refactor in place if ObjectMeshManager._dats call sites <=20; fall back to thin adapter otherwise). Spec fix: §4.1 mesh-pipeline files now correctly placed under src/AcDream.App/Rendering/Wb/ instead of Core (ObjectMeshManager uses Silk.NET.OpenGL types from Managed* wrappers, and CLAUDE.md forbids Core depending on GL). §4.2's layer split (TextureHelpers in Core, rest in App) was already correct. Plan task order: T2 (setup) -> T5 (Core helpers, lowest risk) -> T3 (App GL infra) -> T4 (App mesh pipeline + dat-shim) -> T7 (drop refs + cleanup) -> T8 (visual verification) -> T9 (ship). T5 moved earlier than spec order to validate the namespace migration flow on small-blast-radius files before the load-bearing T4. Self-review: all 12 spec decisions (O-D1..O-D12) mapped to plan tasks; placeholders intentional + explained (MIT license body fetched at T2 step 4; commit-message parameters filled at task close). Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
45 KiB
Phase O — DatPath Unification 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 load-bearing WorldBuilder code we use into acdream's own source tree, route all dat reads through our DatCollection, and drop the two <ProjectReference> entries that pull in WB. End state: ONE dat reader in the process, ZERO references to WorldBuilder.* / Chorizite.OpenGLSDLBackend.* namespaces in our source.
Architecture: Verbatim copy of ~33 WB files (~7.7K LOC) split across two layers — src/AcDream.Core/Rendering/Wb/ for pure-CPU helpers (TextureHelpers, SceneryHelpers, TerrainUtils, TerrainEntry, CellSplitDirection); src/AcDream.App/Rendering/Wb/ for everything that touches GL (OpenGLGraphicsDevice, ManagedGL*, GLSLShader, GLHelpers, GLStateScope, ObjectMeshManager, particle pipeline, render data structs). ObjectMeshManager refactored to take our DatCollection directly (with fallback to a thin adapter if the call-site count exceeds 20). Three internal types in Chorizite promoted to public.
Tech Stack: C# .NET 10, Silk.NET.OpenGL, BCnEncoder.Net, Chorizite.DatReaderWriter (NuGet — stays). MIT attribution required for the extracted code per WB's license.
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Audit: docs/research/2026-05-21-phase-o-t1-wb-audit.md
File Structure
Created during this phase
src/AcDream.Core/Rendering/Wb/ (new directory):
TextureHelpers.cs— pure pixel-format decoders (Fill*helpers for INDEX16/P8/A8/A8R8G8B8/R8G8B8/R5G6B5/A4R4G4B4/etc.).SceneryHelpers.cs— stateless scenery transformations (Displace,RotateObj,ScaleObj,ObjAlign,CheckSlope).TerrainUtils.cs— terrain math (GetHeight,GetNormal,OnRoad,CalculateSplitDirection).TerrainEntry.cs— packed per-vertex terrain struct ([MemoryPackable]attribute STRIPPED).CellSplitDirection.cs—SWtoNE/SEtoNWenum.
src/AcDream.App/Rendering/Wb/ (existing directory, ~25 files added):
OpenGLGraphicsDevice.cs— Silk.NET wrapper, GL context,ProcessGLQueue.GLSLShader.cs,GLHelpers.cs,GLStateScope.cs— shader + state wrappers.ManagedGLTexture.cs,ManagedGLTextureArray.cs,ManagedGLVertexBuffer.cs,ManagedGLIndexBuffer.cs,ManagedGLVertexArray.cs,ManagedGLFrameBuffer.cs,ManagedGLUniformBuffer.cs— GL resource wrappers (7 files).ObjectMeshManager.cs— mesh extraction + GPU upload (REFACTORED to takeDatCollection).ObjectRenderBatch.cs,ObjectRenderData.cs— render-data structs.DebugRenderSettings.cs— settings struct forOpenGLGraphicsDevicector.- Particle batcher + emitter files (count + names confirmed during T4 closure walk).
GlobalMeshBuffer.cs, modern render data structs (count + names confirmed during T4 closure walk).EmbeddedResourceReader.cs,TextureFormatExtensions.cs,BufferUsageExtensions.cs— threeinternaltypes promoted topublic.
Repo root: NOTICE.md updated (or created) with WB MIT attribution.
Modified during this phase
src/AcDream.Core/AcDream.Core.csproj— drop 2<ProjectReference>lines.src/AcDream.App/AcDream.App.csproj— drop 2<ProjectReference>lines.src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs— drop_wbDatsfield/ctor/dispose; drop[indoor-upload] NULL_RESULTblock (lines 192-263 currently); simplify ctor to passDatCollectiondirectly toObjectMeshManager.src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs— updateusing Chorizite.OpenGLSDLBackend.Lib;→using AcDream.App.Rendering.Wb;.src/AcDream.Core/World/WbSceneryAdapter.cs— updateusing WorldBuilder.Shared.Models;→using AcDream.Core.Rendering.Wb;.src/AcDream.Core/World/SceneryGenerator.cs— update bothusinglines to new namespaces.src/AcDream.Core/Textures/SurfaceDecoder.cs— updateusing Chorizite.OpenGLSDLBackend.Lib;→using AcDream.Core.Rendering.Wb;.tests/AcDream.Core.Tests/Textures/TextureDecodeConformanceTests.cs— updateusingline.tests/AcDream.Core.Tests/World/WbSceneryAdapterTests.cs— doc-comment fix only.tests/AcDream.Core.Tests/World/SceneryGeneratorTests.cs— doc-comment fix only.tests/AcDream.Core.Tests/Rendering/Wb/MeshExtractionConformanceTests.cs— doc-comment fix only.tests/AcDream.Core.Tests/Terrain/ClientReference.cs,ClientConformanceTests.cs— doc-comment fix only.CLAUDE.md— rewrite the "WB integration cribs" section.docs/architecture/worldbuilder-inventory.md— update ownership notes.
Deleted during this phase
tests/AcDream.Core.Tests/Terrain/SplitFormulaDivergenceTest.cs— one-time data-collection test, job done at N.5b.
Task 1: Setup (was O-T2)
Files:
- Create:
src/AcDream.Core/Rendering/Wb/(directory only — no files yet) - Create/Modify:
NOTICE.md(repo root)
The src/AcDream.App/Rendering/Wb/ directory already exists.
- Step 1: Create the Core/Rendering/Wb directory
Run:
New-Item -ItemType Directory -Force -Path "src/AcDream.Core/Rendering/Wb"
Expected: directory created, no error.
- Step 2: Check whether NOTICE.md exists
Run:
Test-Path "NOTICE.md"
Expected: True or False. If True, read it before editing. If False, the next step creates it.
- Step 3: Create or append the WorldBuilder attribution section in NOTICE.md
Write to NOTICE.md (append if exists, create otherwise):
## WorldBuilder
Portions of acdream's rendering and dat-handling code are copied from
WorldBuilder (https://github.com/Chorizite/WorldBuilder), MIT-licensed.
The extracted code lives under:
- `src/AcDream.Core/Rendering/Wb/` — pure helpers (texture decode,
scenery transforms, terrain math).
- `src/AcDream.App/Rendering/Wb/` — GL infrastructure and mesh pipeline.
Original copyright holders: Chorizite contributors (see WorldBuilder's
LICENSE file). Adapted by acdream maintainers to consume our
`DatCollection` directly (replacing WB's `DefaultDatReaderWriter`) and
to remove editor-only code paths.
Original MIT license text:
[Insert full MIT license body here from references/WorldBuilder/LICENSE — verify the file exists at that path and use its exact content]
If the user has a different NOTICE.md style, follow theirs; this is the minimum content.
- Step 4: Verify the MIT license body
Run:
Test-Path "references/WorldBuilder/LICENSE"
Get-Content "references/WorldBuilder/LICENSE" -TotalCount 5
Expected: file exists; first few lines should be MIT license text. Copy the full LICENSE body into NOTICE.md, replacing the [Insert ...] placeholder.
- Step 5: Commit
git add NOTICE.md
git add "src/AcDream.Core/Rendering/Wb/.gitkeep" # if needed to track empty dir
git commit -m @'
chore(O-T2): create Core/Rendering/Wb directory + NOTICE.md attribution
Phase O setup: extracted-WB code home + MIT attribution per O-D5.
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
'@
Note: New-Item -ItemType Directory doesn't track empty dirs in git. If T1 needs the directory to exist before later commits, write a placeholder file (e.g., .gitkeep or a tiny README.md describing the directory's purpose) and commit it.
Task 2: Extract pure stateless helpers to Core (was O-T5)
Moved earlier in the plan from spec's T5 position because: (a) lowest-risk extraction (pure functions, no GL, no dat behavior change); (b) unblocks the namespace migrations in our own Core files; (c) catches any namespace-update gotchas before the load-bearing T4.
Files:
-
Create:
src/AcDream.Core/Rendering/Wb/TextureHelpers.cs -
Create:
src/AcDream.Core/Rendering/Wb/SceneryHelpers.cs -
Create:
src/AcDream.Core/Rendering/Wb/TerrainUtils.cs -
Create:
src/AcDream.Core/Rendering/Wb/TerrainEntry.cs -
Create:
src/AcDream.Core/Rendering/Wb/CellSplitDirection.cs -
Modify:
src/AcDream.Core/World/SceneryGenerator.cs(2usinglines) -
Modify:
src/AcDream.Core/World/WbSceneryAdapter.cs(1usingline) -
Modify:
src/AcDream.Core/Textures/SurfaceDecoder.cs(1usingline) -
Modify:
tests/AcDream.Core.Tests/Textures/TextureDecodeConformanceTests.cs(1usingline) -
Modify:
tests/AcDream.Core.Tests/Terrain/SplitFormulaDivergenceTest.cs— actually this gets DELETED at T7, but its imports still need to work between now and then OR the test is moved to deleted-list earlier. Decision: leave it broken-but-doesn't-affect-other-tests for now, delete cleanly at T7. -
Step 1: Read each of the 5 WB source files
Read:
references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/TextureHelpers.csreferences/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/SceneryHelpers.csreferences/WorldBuilder/WorldBuilder.Shared/Modules/Landscape/Lib/TerrainUtils.csreferences/WorldBuilder/WorldBuilder.Shared/Modules/Landscape/Models/TerrainEntry.csreferences/WorldBuilder/WorldBuilder.Shared/Modules/Landscape/Models/CellSplitDirection.cs
(Note: the worktree's references/WorldBuilder submodule is empty. Use the main checkout at /c/Users/erikn/source/repos/acdream/references/WorldBuilder/ if needed.)
Confirm each file is what the audit expected. Note any unexpected dependencies (other WB types referenced beyond NuGet packages like Silk.NET, BCnEncoder.Net, MemoryPack, etc.).
- Step 2: Copy
TextureHelpers.csto Core
Copy references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/TextureHelpers.cs to src/AcDream.Core/Rendering/Wb/TextureHelpers.cs. Then update the file:
Change the namespace declaration line from:
namespace Chorizite.OpenGLSDLBackend.Lib;
to:
namespace AcDream.Core.Rendering.Wb;
If TextureHelpers has any using statements pointing to other WB types, note them but DO NOT change yet — copy is verbatim except for the namespace.
- Step 3: Copy
SceneryHelpers.csto Core (same process)
Same as Step 2 for SceneryHelpers.cs. Update its namespace to AcDream.Core.Rendering.Wb.
If SceneryHelpers.cs has a using WorldBuilder.Shared.Modules.Landscape.Lib; or similar for TerrainUtils, leave it — we'll update it at Step 6.
- Step 4: Copy
TerrainUtils.csto Core
Same. Update namespace from WorldBuilder.Shared.Modules.Landscape.Lib → AcDream.Core.Rendering.Wb.
- Step 5: Copy
TerrainEntry.csto Core + strip[MemoryPackable]
Copy. Update namespace. Then strip the [MemoryPackable] attribute + any [MemoryPack...] attributes on fields:
// BEFORE
[MemoryPackable]
public partial struct TerrainEntry { ... }
// AFTER
public struct TerrainEntry { ... }
Drop the partial modifier as well if the only reason it's partial is the MemoryPack source generator (verify by checking whether any other file extends the type via a separate partial).
Drop any using MemoryPack; line.
- Step 6: Copy
CellSplitDirection.csto Core
Enum. Update namespace. Nothing else.
- Step 7: Update internal cross-references in the new files
After all 5 files exist in src/AcDream.Core/Rendering/Wb/:
If SceneryHelpers.cs had using WorldBuilder.Shared.Modules.Landscape.Lib; (for TerrainUtils), change to nothing — they're in the same namespace now.
If TerrainUtils.cs had using WorldBuilder.Shared.Modules.Landscape.Models; (for TerrainEntry / CellSplitDirection), change to nothing — same namespace.
Run:
Grep pattern="using (WorldBuilder|Chorizite\.OpenGLSDLBackend)" path="src/AcDream.Core/Rendering/Wb"
Expected: no matches. If any, that file's still pointing at the old namespace; fix.
- Step 8: Update our own Core source files'
usinglines
Three Core files import WB types directly:
In src/AcDream.Core/World/WbSceneryAdapter.cs:2:
// BEFORE
using WorldBuilder.Shared.Models;
// AFTER
using AcDream.Core.Rendering.Wb;
Wait — WbSceneryAdapter.cs:2 actually says using WorldBuilder.Shared.Models; not ...Modules.Landscape.Models;. Verify the actual using and update accordingly. The import is for TerrainEntry. After T2 step 6, TerrainEntry is in AcDream.Core.Rendering.Wb.
In src/AcDream.Core/World/SceneryGenerator.cs:2 and :6:
// BEFORE
using Chorizite.OpenGLSDLBackend.Lib;
using WorldBuilder.Shared.Modules.Landscape.Lib;
// AFTER
using AcDream.Core.Rendering.Wb;
// (single using replaces both, since both target types now live in the new namespace)
In src/AcDream.Core/Textures/SurfaceDecoder.cs:3:
// BEFORE
using Chorizite.OpenGLSDLBackend.Lib;
// AFTER
using AcDream.Core.Rendering.Wb;
- Step 9: Update conformance test's
using
In tests/AcDream.Core.Tests/Textures/TextureDecodeConformanceTests.cs:1:
// BEFORE
using Chorizite.OpenGLSDLBackend.Lib;
// AFTER
using AcDream.Core.Rendering.Wb;
Leave tests/AcDream.Core.Tests/Terrain/SplitFormulaDivergenceTest.cs alone for now — it's deleted at T7.
- Step 10: Build solution; expect green (or only the SplitFormulaDivergenceTest broken)
Run:
dotnet build
Expected: green. The SplitFormulaDivergenceTest.cs still has its using WbTerrainUtils = WorldBuilder.Shared.Modules.Landscape.Lib.TerrainUtils; alias — but WorldBuilder.Shared is still project-referenced at this point, so it still compiles. If anything else fails, the namespace migration missed a file. Fix and rerun.
- Step 11: Run tests; expect green
Run:
dotnet test --no-build
Expected: green. Pay particular attention to TextureDecodeConformanceTests — those should still pass byte-for-byte after the namespace move.
- Step 12: Commit T5
git add src/AcDream.Core/Rendering/Wb/ src/AcDream.Core/World/WbSceneryAdapter.cs src/AcDream.Core/World/SceneryGenerator.cs src/AcDream.Core/Textures/SurfaceDecoder.cs tests/AcDream.Core.Tests/Textures/TextureDecodeConformanceTests.cs
git commit -m @'
feat(O-T5): extract pure stateless helpers to AcDream.Core.Rendering.Wb
Verbatim copy of 5 WorldBuilder files into src/AcDream.Core/Rendering/Wb/:
- TextureHelpers.cs (pixel-format decoders, Chorizite Lib)
- SceneryHelpers.cs (scenery transforms, Chorizite Lib)
- TerrainUtils.cs, TerrainEntry.cs, CellSplitDirection.cs (WB.Shared Landscape)
Namespace migrated from WorldBuilder.* / Chorizite.OpenGLSDLBackend.Lib
to AcDream.Core.Rendering.Wb per O-D11. [MemoryPackable] stripped from
TerrainEntry per O-D10 (we don't serialize the struct).
Updated 3 source files + 1 test file to import from the new namespace.
Verbatim discipline (O-D1): only namespace + MemoryPack attribute changed.
All algorithm bodies byte-identical to upstream.
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
'@
Task 3: Extract GL infrastructure to App (was O-T3)
Files:
-
Create: 11+ files under
src/AcDream.App/Rendering/Wb/(exact count after closure walk) -
Modify: none yet (the existing App
Wb/adapter files don't import GL infra directly — they go through Silk.NET — butWbMeshAdapter.csconstructsOpenGLGraphicsDevicewhich it currently imports fromChorizite.OpenGLSDLBackend. That single import will be updated at T4 whenObjectMeshManagerarrives.) -
Step 1: Walk the GL-infra closure
Read references/WorldBuilder/Chorizite.OpenGLSDLBackend/OpenGLGraphicsDevice.cs first. List every WB type it references (look at field types, method parameters, constructor calls, using statements).
Then read each referenced WB type. Continue until closure is closed.
Expected closure (per audit):
OpenGLGraphicsDevice.csGLSLShader.cs,GLHelpers.cs,GLStateScope.cs(likely inLib/)ManagedGLTexture.cs,ManagedGLTextureArray.cs,ManagedGLVertexBuffer.cs,ManagedGLIndexBuffer.cs,ManagedGLVertexArray.cs,ManagedGLFrameBuffer.cs,ManagedGLUniformBuffer.cs(root orLib/)EmbeddedResourceReader.cs,TextureFormatExtensions.cs,BufferUsageExtensions.cs(3 internals to promote)DebugRenderSettings.cs(sometimes needed byOpenGLGraphicsDevicector)
Write down the actual file list with paths.
- Step 2: Check for
SixLabors.ImageSharpreachability
Run:
Grep pattern="SixLabors\.ImageSharp" path="references/WorldBuilder/Chorizite.OpenGLSDLBackend"
List every file in the closure that imports ImageSharp.
If empty: document "ImageSharp not reachable" in the T3 commit message. Skip step 3.
If non-empty: proceed to step 3.
- Step 3: Strip ImageSharp usage from the closure files (only if needed)
For each closure file that imports ImageSharp:
- Identify the call sites (typically PNG/JPG load helpers).
- Replace with our existing byte-handling or BCnEncoder calls.
- Remove the
using SixLabors.ImageSharp;line.
If a file's ImageSharp usage cannot be cleanly removed (e.g., it's the file's main purpose), then either: (a) drop the file from the extraction (verify it's not called by our closure), or (b) add SixLabors.ImageSharp as a <PackageReference> to AcDream.App.csproj and keep the file.
Document the decision in the T3 commit.
- Step 4: Copy each GL-infra file to
src/AcDream.App/Rendering/Wb/
For each file in the closure list:
- Copy verbatim from WB source to
src/AcDream.App/Rendering/Wb/<filename>. - Update the namespace from
Chorizite.OpenGLSDLBackend(or.Lib) toAcDream.App.Rendering.Wb. - Update any
using Chorizite.OpenGLSDLBackend.Lib;/using Chorizite.OpenGLSDLBackend;lines inside the copied files to point at the new namespace. - For the 3 internal types (
EmbeddedResourceReader,TextureFormatExtensions,BufferUsageExtensions): changeinternal class/internal static class/ etc. topublic class/public static class.
Suggested copy order (each follows the same pattern): managed wrappers first (lowest deps), then helpers, then OpenGLGraphicsDevice last (depends on all the rest).
- Step 5: Build, iterate until green
Run:
dotnet build
Common failures + fixes:
- "The type or namespace name 'X' could not be found" →
usingwas missed; addusing AcDream.App.Rendering.Wb;. OR a transitive WB type wasn't copied — copy it. - "The type 'X' is defined in an assembly that is not referenced" → ImageSharp/MemoryPack/other NuGet — see step 3.
- "Inconsistent accessibility" → an internal type isn't yet promoted to public — promote it.
Iterate until green.
- Step 6: Run tests; expect green (no test count change)
Run:
dotnet test --no-build
Tests should pass; this task doesn't change behavior, only file location and namespace.
- Step 7: Commit T3
git add src/AcDream.App/Rendering/Wb/
git commit -m @'
feat(O-T3): extract GL infrastructure to AcDream.App.Rendering.Wb
Verbatim copy of GL infra (~15 files) from
Chorizite.OpenGLSDLBackend into src/AcDream.App/Rendering/Wb/:
- OpenGLGraphicsDevice.cs, GLSLShader.cs, GLHelpers.cs, GLStateScope.cs
- ManagedGL{Texture,TextureArray,VertexBuffer,IndexBuffer,
VertexArray,FrameBuffer,UniformBuffer}.cs (7 files)
- DebugRenderSettings.cs
Three internal types promoted to public per O-D9:
- EmbeddedResourceReader, TextureFormatExtensions, BufferUsageExtensions
SixLabors.ImageSharp reachability: <fill in: "not reachable" OR
"stripped from N paths" OR "added as NuGet PackageReference">.
Verbatim discipline (O-D1): namespace + access modifier changes only.
All algorithm bodies byte-identical to upstream.
Build green; tests green (no count change).
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
'@
Task 4: Extract mesh pipeline + dat refactor (was O-T4)
The load-bearing task. Includes the ObjectMeshManager refactor or fallback to adapter, per O-D7.
Files:
-
Create:
src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs -
Create:
src/AcDream.App/Rendering/Wb/ObjectRenderBatch.cs -
Create:
src/AcDream.App/Rendering/Wb/ObjectRenderData.cs -
Create: ~5+ supporting files (particle batcher / emitter, global mesh buffer, modern render data — count + exact filenames during closure walk)
-
Modify:
src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs(constructor +usingupdates; cleanup at T7) -
Step 1: Walk the mesh-pipeline closure
Read references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/ObjectMeshManager.cs end-to-end. It's ~2K LOC; use Read with offsets if needed. List every WB type it references that ISN'T already in the GL-infra closure (T3) or stateless-helpers closure (T2).
Expected new types in closure (per audit):
ObjectRenderBatch,ObjectRenderDataGlobalMeshBuffer(or similar; the single-VBO/IBO holder for modern rendering)ParticleBatcher,ParticleEmitterRenderer(referenced from mesh path)- Modern render data structs (likely a couple of small files)
Write the actual file list with paths.
- Step 2: Count the
_dats.Xcall sites inObjectMeshManager.cs
Run:
Grep pattern="_dats\." path="references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/ObjectMeshManager.cs"
Count the matches.
Branch on count:
- ≤ 20 sites: take the refactor path (O-D7). Plan steps 3-7 cover this.
- > 20 sites: take the adapter fallback. Plan steps 3-7 are skipped; instead, plan steps A-D below (after step 7).
Document the actual count in the T4 commit message regardless.
- Step 3: (refactor path) Copy mesh-pipeline files to App, namespace-update
Copy each closure file to src/AcDream.App/Rendering/Wb/<filename>. Update each file's namespace from Chorizite.OpenGLSDLBackend.Lib → AcDream.App.Rendering.Wb. Update any internal using lines.
Do NOT yet modify ObjectMeshManager's dat type — that's the next step. First, the verbatim copy + namespace.
- Step 4: (refactor path) Refactor
ObjectMeshManagerdat field type
In the newly-copied src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs:
a) Drop the import:
// BEFORE
using WorldBuilder.Shared.Services;
(plus any related; remove if present.)
b) Add the import:
using AcDream.Core;
// or wherever DatCollection lives — verify with:
// Grep pattern="^namespace.*DatCollection" path="src/AcDream.Core"
// or:
// Grep pattern="public.*class DatCollection\b" path="src/AcDream.Core"
c) Change the field declaration from:
private readonly IDatReaderWriter _dats;
to:
private readonly DatCollection _dats;
d) Change the constructor parameter from IDatReaderWriter dats to DatCollection dats.
e) Update each _dats.X call site. The most common shapes:
_dats.Portal.TryGet<T>(id, out var x)— verify DatCollection exposesPortal.TryGet<T>. If yes, no change. If no, see step 5._dats.Cell.TryGet<T>(id, out var x)— verify the same._dats.HighRes.TryGet<T>(...)— same._dats.Language.TryGet<T>(...)— same._dats.ResolveId(id)— drop the call site if it's diagnostic-only (per O-D12). If it's load-bearing, see step 5.
For each call site, either: (a) leave it alone (DatCollection has the equivalent), (b) replace with DatCollection's API shape, or (c) escalate to step 5 (DatCollection needs a new method).
- Step 5: (refactor path) Add missing methods to DatCollection if needed
If step 4 surfaced a call shape DatCollection doesn't support, add the method to src/AcDream.Core/Dats/DatCollection.cs (or wherever DatCollection lives — find via grep).
DO NOT add methods speculatively — only add what ObjectMeshManager actually calls. Document each added method in the T4 commit.
- Step 6: (refactor path) Update
WbMeshAdapterto constructObjectMeshManagerwithDatCollection
In src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs:
Update line 7-15 using lines: drop using Chorizite.OpenGLSDLBackend;, using Chorizite.OpenGLSDLBackend.Lib;, using WorldBuilder.Shared.Models;, using WorldBuilder.Shared.Services;. Add using AcDream.App.Rendering.Wb; (and AcDream.Core.Rendering.Wb; if needed for any of the now-Core types referenced).
Update line 85-88 ObjectMeshManager construction. Today:
_meshManager = new ObjectMeshManager(
_graphicsDevice,
_wbDats,
new ConsoleErrorLogger<ObjectMeshManager>());
Change to:
_meshManager = new ObjectMeshManager(
_graphicsDevice,
dats, // the DatCollection passed to WbMeshAdapter's ctor
new ConsoleErrorLogger<ObjectMeshManager>());
Leave _wbDats field + its construction at line 79 + its disposal at line 346 in place for now — they're cleaned up at T7.
Do NOT touch the [indoor-upload] NULL_RESULT block (lines 192-263) yet — also cleaned up at T7.
- Step 7: (refactor path) Build, iterate until green
Run:
dotnet build
Common failures:
_dats.Xcall shape doesn't match DatCollection — fix per step 4/5.- Missing
usingfor a moved type — add.
Iterate until green.
- Step A: (adapter fallback path, only if count > 20)
Create src/AcDream.App/Rendering/Wb/DatCollectionAdapter.cs:
using AcDream.Core; // or wherever DatCollection lives
using DatReaderWriter;
using DatReaderWriter.DBObjs;
// ... add interfaces / types needed to satisfy IDatReaderWriter
namespace AcDream.App.Rendering.Wb;
/// <summary>
/// Adapts our DatCollection to WB's IDatReaderWriter interface so
/// the extracted ObjectMeshManager can consume it without internal
/// refactoring. O-D7 fallback path — taken because ObjectMeshManager
/// has >20 _dats.X call sites and the refactor was deemed too invasive
/// for one ship-window.
/// </summary>
internal sealed class DatCollectionAdapter : IDatReaderWriter
{
private readonly DatCollection _dats;
public DatCollectionAdapter(DatCollection dats)
{
ArgumentNullException.ThrowIfNull(dats);
_dats = dats;
}
// Implement each member of IDatReaderWriter by delegating to _dats.
// Members are determined by reading IDatReaderWriter.cs (which is
// also copied into this directory at this step, since we no longer
// have it via the WorldBuilder.Shared project reference).
// ...
}
You also need to copy IDatReaderWriter.cs from WB.Shared into src/AcDream.App/Rendering/Wb/ (or define a slimmed-down version with only the members ObjectMeshManager uses). Document the choice.
- Step B: (adapter fallback) Wire the adapter in WbMeshAdapter
// BEFORE (in WbMeshAdapter ctor)
_meshManager = new ObjectMeshManager(_graphicsDevice, _wbDats, ...);
// AFTER
_meshManager = new ObjectMeshManager(
_graphicsDevice,
new DatCollectionAdapter(dats),
...);
- Step C: (adapter fallback) Build, iterate until green
Same as step 7 but with the adapter in place.
- Step D: (adapter fallback) Document the choice
Note in the T4 commit message that the adapter path was taken, with the actual call-site count and the rationale (e.g., "37 call sites; 22 use methods DatCollection doesn't currently expose, escalating to a separate refactor phase").
- Step 8: (both paths) Run tests; expect green
Run:
dotnet test --no-build
Expected: green. No test count regression.
- Step 9: (both paths) Commit T4
git add src/AcDream.App/Rendering/Wb/
git add src/AcDream.Core/Dats/ # only if DatCollection grew methods at step 5
git commit -m @'
feat(O-T4): extract mesh pipeline; <refactor OR adapter> dat-shim
Verbatim copy of ObjectMeshManager + supports (~8 files, ~3.3K LOC) from
Chorizite.OpenGLSDLBackend.Lib into src/AcDream.App/Rendering/Wb/.
ObjectMeshManager._dats call-site count: <N>.
Path taken: <refactor in place to DatCollection> OR <thin DatCollectionAdapter>.
Rationale: <one or two sentences>.
DatCollection additions (if any): <list new methods + why>.
WbMeshAdapter updated to construct ObjectMeshManager via the new path.
The _wbDats field + [indoor-upload] NULL_RESULT block stay in place for
now; cleaned up at T7.
SixLabors.ImageSharp: <if any was reachable in this closure, document>.
Verbatim discipline (O-D1): namespace + dat-surface only. Mesh extraction,
texture decode, particle pipeline byte-identical to upstream.
Build green; tests green (no count change).
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
'@
Task 5: Drop project references + cleanup (was O-T7)
Files:
-
Modify:
src/AcDream.App/AcDream.App.csproj -
Modify:
src/AcDream.Core/AcDream.Core.csproj -
Modify:
src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs -
Modify:
src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs(usingline) -
Modify: doc-comment-only files in tests/ (5-6 files, comment edits)
-
Delete:
tests/AcDream.Core.Tests/Terrain/SplitFormulaDivergenceTest.cs -
Step 1: Delete
_wbDatsfield + construction fromWbMeshAdapter.cs
Open src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs. Delete:
a) Line 34 (the field declaration):
private readonly DefaultDatReaderWriter? _wbDats;
b) Line 79 (the construction):
_wbDats = new DefaultDatReaderWriter(datDir);
c) Line 346 (the disposal in Dispose()):
_wbDats?.Dispose();
d) The datDir parameter — verify it's no longer used after _wbDats removal. If OpenGLGraphicsDevice ctor doesn't need it, remove datDir from WbMeshAdapter's constructor signature + all call sites. If it's still needed for something else, keep it.
- Step 2: Delete the
[indoor-upload] NULL_RESULTblock
In WbMeshAdapter.cs, delete the block from line ~192 to line ~263 (the diagnostic block that uses _wbDats.ResolveId(...)). Also delete the _pendingEnvCellRequests field and its associated logic in Tick() (the isPendingEnvCell block at ~305-321).
Result: WbMeshAdapter.cs shrinks substantially — should be down from 349 to ~250 LOC.
- Step 3: Update
WbMeshAdapter.csandWbDrawDispatcher.csusinglines
In WbMeshAdapter.cs (lines 7-15):
- Drop:
using Chorizite.OpenGLSDLBackend;,using Chorizite.OpenGLSDLBackend.Lib;,using WorldBuilder.Shared.Models;,using WorldBuilder.Shared.Services;,using DatReaderWriter;,using DatReaderWriter.DBObjs;(verify whether the last two are still needed — they probably are forGfxObjreferences inPopulateMetadata). - Add:
using AcDream.App.Rendering.Wb;if not present.
In WbDrawDispatcher.cs (line 9):
-
Drop:
using Chorizite.OpenGLSDLBackend.Lib; -
Add:
using AcDream.App.Rendering.Wb; -
Step 4: Delete the
<ProjectReference>lines in both csprojs
In src/AcDream.App/AcDream.App.csproj, delete lines 38-39 (the two <ProjectReference> entries to WorldBuilder.Shared and Chorizite.OpenGLSDLBackend). Also drop the comment immediately above them ("Phase N.4 Task 9: ...") since the reference rationale is now historical.
In src/AcDream.Core/AcDream.Core.csproj, delete lines 27-28 (same two references). Also drop the multi-line comment immediately above ("Phase N: WorldBuilder is acdream's rendering + dat-handling base...") since the references are gone.
- Step 5: Delete
SplitFormulaDivergenceTest.cs
Remove-Item "tests/AcDream.Core.Tests/Terrain/SplitFormulaDivergenceTest.cs"
This was a one-time data-collection test; its job finished at N.5b ship.
- Step 6: Fix doc-only WB mentions in remaining test files
For each of these files, update comment-only references to WB to point at the extracted location instead. Files (per audit grep):
tests/AcDream.Core.Tests/World/WbSceneryAdapterTests.cs:10-17— mentionsWorldBuilder.Shared.Models.TerrainEntry. Update toAcDream.Core.Rendering.Wb.TerrainEntry.tests/AcDream.Core.Tests/World/SceneryGeneratorTests.cs:9— mentions WB helpers in comment. Update to refer toAcDream.Core.Rendering.Wb.tests/AcDream.Core.Tests/Rendering/Wb/MeshExtractionConformanceTests.cs:12— mentionsWB's ObjectMeshManager. Either update to extracted name OR keep with a clarifying comment "(formerly WB; now ours, see Phase O)".tests/AcDream.Core.Tests/Terrain/ClientReference.cs:11andClientConformanceTests.cs:10— both "Ported from WorldBuilder-ACME-Edition". These are historical attributions; leave them OR update phrasing.
This is comment-edit work; no logic changes.
- Step 7: Build solution; expect green
Run:
dotnet build
Expected: green. If a using WorldBuilder.* or using Chorizite.OpenGLSDLBackend* was missed, the build will fail with "namespace not found." Find it and update.
Also expect: if any file still references IDatReaderWriter / DefaultDatReaderWriter directly, the build fails. Grep:
Grep pattern="(IDatReaderWriter|DefaultDatReaderWriter)" path="src"
Should return zero matches.
- Step 8: Run tests; expect green minus deleted test count
Run:
dotnet test --no-build
Expected: green. The total test count drops by however many tests were in the deleted SplitFormulaDivergenceTest.cs (likely a single test or two — record the actual delta).
- Step 9: Verify the acceptance criteria for reference deletion
Run all three:
Grep pattern="(WorldBuilder|Chorizite\.OpenGLSDLBackend)" path="src/AcDream.App/AcDream.App.csproj"
Grep pattern="(WorldBuilder|Chorizite\.OpenGLSDLBackend)" path="src/AcDream.Core/AcDream.Core.csproj"
Grep pattern="^using (WorldBuilder|Chorizite\.OpenGLSDLBackend)" path="src"
Expected: all three return zero matches.
Grep pattern="DefaultDatReaderWriter" path="src"
Expected: zero matches.
If any return matches: something was missed in this task. Fix before commit.
- Step 10: Commit T7
git add src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs src/AcDream.App/AcDream.App.csproj src/AcDream.Core/AcDream.Core.csproj tests/
git rm tests/AcDream.Core.Tests/Terrain/SplitFormulaDivergenceTest.cs
git commit -m @'
feat(O-T7): drop WB project references; cleanup _wbDats + diagnostic block
End of Phase O extraction:
- Dropped <ProjectReference> entries to WorldBuilder.Shared and
Chorizite.OpenGLSDLBackend from AcDream.App.csproj and
AcDream.Core.csproj.
- Deleted _wbDats field + ctor + dispose from WbMeshAdapter.cs.
- Deleted [indoor-upload] NULL_RESULT diagnostic block (lines 192-263);
the Phase 2 cell-resolution diagnostic served its purpose, and its
dependency on _wbDats.ResolveId goes away with this commit (per O-D12).
- Deleted SplitFormulaDivergenceTest.cs (one-time N.5b data-collection
test; job done).
- Updated comment-only WB mentions in 5 test files to point at the
extracted namespace.
Verified acceptance criteria:
- Zero refs to WorldBuilder.* / Chorizite.OpenGLSDLBackend.* in any csproj.
- Zero "using WorldBuilder.*" / "using Chorizite.OpenGLSDLBackend*" in src/.
- DefaultDatReaderWriter referenced in zero places.
Build green; tests green (count: -<N> from deleted test, no regressions).
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
'@
Task 6: Visual verification gate (was O-T8)
Goal: Confirm that the extraction didn't regress rendering by side-by-side with main against retail in three scenes.
Files: none modified; this is a runtime test.
- Step 1: Capture pre-extraction screenshots (do this BEFORE Task 2 if not already done)
If not already captured: pause this plan, switch back to main (or a commit at HEAD before Task 1), launch acdream at three scenes:
- Holtburg town (outdoor + scenery)
- An inn interior (EnvCell)
- A dungeon (portals)
Save screenshots to docs/research/phase-o-pre/holtburg-town.png, inn-interior.png, dungeon.png (not committed; reference only).
If you forgot to capture pre-screenshots before starting: you can either roll back to a pre-O commit (git checkout <sha> on a scratch branch), or compare visually against the retail client side-by-side instead.
- Step 2: Launch acdream after Task 5 with the test character
Use the launch script from CLAUDE.md (PowerShell):
$env:ACDREAM_DAT_DIR = "$env:USERPROFILE\Documents\Asheron's Call"
$env:ACDREAM_LIVE = "1"
$env:ACDREAM_TEST_HOST = "127.0.0.1"
$env:ACDREAM_TEST_PORT = "9000"
$env:ACDREAM_TEST_USER = "testaccount"
$env:ACDREAM_TEST_PASS = "testpassword"
dotnet build # must be green first
dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug 2>&1 |
Tee-Object -FilePath "launch-O-verification.log"
Run in the background per CLAUDE.md guidance.
- Step 3: Visit the three scenes
In the running client:
- Holtburg town — walk around, check terrain blending, scenery placement (trees/bushes), building exteriors. Compare with pre-O screenshot.
- Inn interior — enter the inn at Holtburg, look at EnvCell geometry (walls, floor, fireplace), check that statics render correctly.
- Dungeon — find any portal (Holtburg Town network portal works for spawn-based visuals; for a real dungeon, walk to a dungeon entrance). Check portal visibility, room geometry.
User confirms each scene reads identical to pre-O.
- Step 4: Measure resident memory at radius=4 + 50 entities visible
In the running client:
- Set quality preset to Low (radius=4 — see CLAUDE.md "Quality Preset system").
- Wait for streaming to settle.
- Open Task Manager (Windows) → Details tab → find
AcDream.App.exe→ Working Set (Memory). - Record the value.
Compare to a pre-O baseline (same scene, same settings, on main HEAD before Phase O). Expected delta: ≥50 MB reduction (validates that the second DefaultDatReaderWriter cache is actually gone).
If <50 MB: investigate why. The dat-reader duplication should be ~50-100 MB per WbMeshAdapter.cs:27 comment.
- Step 5: Close client gracefully and record findings
Use the graceful close from CLAUDE.md (WM_CLOSE) so ACE's session clears quickly.
Write a brief verification note (in chat or in the T8 commit message):
- Three scenes: identical / different (describe difference).
- Memory delta: X MB reduction.
- User confirmation: yes / no.
Do NOT proceed to Task 7 if any scene regressed or memory delta is <50 MB.
- Step 6: Tag the verification commit (or just push to next task)
No code change at T8 — but optionally record the verification artifacts:
git add docs/research/ # if you committed any post-O screenshots
git commit --allow-empty -m @'
chore(O-T8): visual verification passed
Three scenes side-by-side against pre-O baseline: identical.
- Holtburg town (outdoor + scenery): OK
- Holtburg inn interior (EnvCell): OK
- <Dungeon name> (portals): OK
Memory measurement at radius=4 + 50 entities visible:
- Pre-O: <X> MB working set
- Post-O: <Y> MB working set
- Delta: <Z> MB reduction (>= 50 MB target)
User confirmed identical rendering.
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
'@
Task 7: Ship (was O-T9)
Files:
-
Modify:
CLAUDE.md— rewrite "WB integration cribs" section. -
Modify:
docs/architecture/worldbuilder-inventory.md— update ownership notes. -
Modify:
docs/plans/2026-04-11-roadmap.md— move Phase O from "in flight" to "shipped". -
Modify:
docs/plans/2026-05-12-milestones.md— note Phase O ship if appropriate. -
Step 1: Rewrite the "WB integration cribs" section in CLAUDE.md
Find the block in CLAUDE.md that starts with "WorldBuilder is acdream's rendering + dat-handling base, integrated as of Phase N.4 ship (2026-05-08)." and continues through the WB integration cribs (the bullet list of files and patterns).
Replace it with text that reflects post-O reality:
**WorldBuilder code is extracted into acdream's source tree as of Phase
O ship (2026-XX-XX).** The pure helpers (texture decode, scenery
transforms, terrain math) live in `src/AcDream.Core/Rendering/Wb/`.
The GL-touching infrastructure and mesh pipeline live in
`src/AcDream.App/Rendering/Wb/`. There is no `<ProjectReference>` to
`WorldBuilder.Shared` or `Chorizite.OpenGLSDLBackend` in any csproj —
those projects remain in `references/WorldBuilder/` as read-references
only (grep against them when porting NEW pieces).
ALL dat reads go through `DatCollection` (Phase O ship: ONE thing
touches the DATs). The previous `DefaultDatReaderWriter` second-reader
allocation in `WbMeshAdapter` is deleted.
[update the file-path cribs below the header to point at the new
locations under `src/AcDream.{Core,App}/Rendering/Wb/`]
Preserve all of the substantive cribs about GL pipeline behavior, the indirect-draw command struct, the surface-id rules, etc. — just retarget the file paths and remove the "WB is a project reference" language.
- Step 2: Update
docs/architecture/worldbuilder-inventory.md
Add a top-of-file banner indicating the inventory now describes "what we extracted vs what we left in references/." Update the §1 status line. The body inventory tables stay valuable (they describe the WB source we might still grep). Add a column or section noting "Extracted into acdream at Phase O" for each component that moved.
- Step 3: Update the roadmap
In docs/plans/2026-04-11-roadmap.md, find Phase O in the "active" /
"in flight" section. Move it to "shipped" with a one-line summary:
Phase O — DatPath Unification (2026-XX-XX, <commit-sha>): extracted
~33 WB files / ~7.7K LOC into src/AcDream.{Core,App}/Rendering/Wb/.
ObjectMeshManager refactored to take DatCollection (<refactor or
adapter>). Memory reduction <X> MB. Reference: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md.
- Step 4: Update milestones doc
In docs/plans/2026-05-12-milestones.md, locate the M1.5 block. Phase O
pre-empted M1.5 — add a brief writeup that O has shipped and M1.5 work
can resume from the 2026-05-20 baseline.
- Step 5: Build + test final
Run:
dotnet build
dotnet test
Expected: both green.
- Step 6: Final commit + Phase O ship marker
git add CLAUDE.md docs/architecture/worldbuilder-inventory.md docs/plans/2026-04-11-roadmap.md docs/plans/2026-05-12-milestones.md
git commit -m @'
ship(O): Phase O — DatPath Unification — SHIPPED
ONE thing touches the DATs. WB code lives in our repo:
- src/AcDream.Core/Rendering/Wb/ — pure helpers (5 files, ~782 LOC)
- src/AcDream.App/Rendering/Wb/ — GL infra + mesh pipeline (~25 files, ~7K LOC)
Project references to WorldBuilder.Shared + Chorizite.OpenGLSDLBackend
dropped from AcDream.App.csproj and AcDream.Core.csproj. references/
WorldBuilder remains in-tree as read-reference.
DefaultDatReaderWriter removed; DatCollection is the only dat reader.
Working-set memory delta: <Z> MB reduction at radius=4 + 50 entities.
Visual side-by-side passed: Holtburg town, inn interior, dungeon.
Updates:
- CLAUDE.md: rewrote WB integration cribs to point at extracted locations.
- docs/architecture/worldbuilder-inventory.md: ownership now ours.
- Roadmap: moved Phase O to "shipped".
- Milestones: M1.5 resumes from 2026-05-20 baseline.
Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
'@
- Step 7: Merge to main
Per spec O-D6 (one ship-window). Open a PR (or fast-forward merge if the
worktree branch is clean) targeting main. Use gh pr create per
CLAUDE.md if a PR is desired; or:
git checkout main
git merge claude/serene-leakey-e6b8bc --ff-only
git push
Verify with:
git status
git log -10
Self-review notes
Plan reviewed against spec sections 4, 5, 6:
Spec coverage:
- §4.1 (mesh pipeline) → Task 4. ✓
- §4.2 (GL infra) → Task 3. ✓
- §4.3 (stateless helpers) → Task 2. ✓
- §4.4 (NOT extracted) → no task needed (these are explicitly skipped).
- §4.5 (drops) → Task 5. ✓
- §5 task breakdown T2/T3/T4/T5/T7/T8/T9 → plan Tasks 1/3/4/2/5/6/7. ✓
- §6 acceptance criteria → checked at Task 5 step 9 + Task 6 step 4 + Task 7. ✓
- §7 risks → mitigations baked into task steps (T4 safety check at step 2; visual screenshots before Task 1 / Task 6 step 1). ✓
Placeholder scan:
- "[Insert full MIT license body here...]" in Task 1 step 3 — intentional, requires reading the actual LICENSE file at step 4. Acceptable since step 4 explicitly tells the executor to fetch the content.
- "exact filenames during closure walk" in Task 3 + Task 4 file-list — intentional honesty since the audit didn't persist the full per-file table. The closure-walk steps cover producing the exact list.
- "<fill in: ...>" in commit message templates — intentional; commit messages parameterize on findings (e.g., ImageSharp reachability) discovered during the task.
Type consistency:
_datsfield name preserved across tasks.DatCollectionclass name verified — see Task 4 step 4b (executor confirms via grep).- Namespace conventions:
AcDream.Core.Rendering.Wbfor Core helpers,AcDream.App.Rendering.Wbfor App GL/mesh code. Consistent.
Scope check: Single ship-window per spec O-D6. Plan is one cohesive unit; no decomposition needed.