diff --git a/CLAUDE.md b/CLAUDE.md index 15fd61b..7c19ee2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,49 +25,62 @@ single source of truth for how the client is structured. All work must align with this document. When the architecture doc and reality diverge, update one or the other β€” never leave them out of sync. -**WorldBuilder is acdream's rendering + dat-handling base, integrated -as of Phase N.4 ship (2026-05-08).** WB's `ObjectMeshManager` is the -production mesh pipeline; `WbMeshAdapter` is the seam; `WbDrawDispatcher` -is the production draw path (default-on, see `WbFoundationFlag`). Before -re-implementing any AC-specific rendering or dat-handling algorithm, -**read `docs/architecture/worldbuilder-inventory.md` FIRST**. If -WorldBuilder has it, port from WorldBuilder (or call into our fork via -the adapter), not from retail decomp. WorldBuilder is MIT-licensed, -verified to render the world correctly, and uses the same Silk.NET -stack we target. Re-porting from retail decomp when WB already has a -tested port is how subtle bugs (the scenery edge-vertex bug, the -triangle-Z bug) keep slipping in. Retail decomp remains the oracle for -network, physics, animation, movement, UI, plugin, audio, chat β€” see -the inventory doc's πŸ”΄ list for the full scope of "we still write this -ourselves". +**WorldBuilder code lives in our tree as of Phase O (shipped 2026-05-21).** +Phase N.4 (2026-05-08) adopted WB's rendering + dat-handling base as a +project reference. Phase O (2026-05-21) extracted the ~33 files / ~7.7K LOC +we actually use into our own namespaces and dropped the two external project +references. `DatCollection` is now the **only** dat reader in process β€” +`DefaultDatReaderWriter` is gone. `references/WorldBuilder/` remains in-tree +as a read-reference (MIT-licensed; grep it freely), but nothing in +`src/AcDream.*` references it as a project dependency. -**WB integration cribs:** -- `src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs` β€” single seam over WB's - `ObjectMeshManager`. Owns the WB pipeline, drains its staged-upload - queue per frame via `Tick()`, populates `AcSurfaceMetadataTable` with - per-batch translucency / luminosity / fog metadata. -- `src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs` β€” production draw - path. Groups all visible (entity, batch) pairs, single-uploads the - matrix buffer, fires one `glDrawElementsInstancedBaseVertexBaseInstance` - per group with `BaseInstance` pointing at the slice. Per-entity - frustum cull, opaque front-to-back sort, palette-hash memoization. -- `src/AcDream.App/Rendering/Wb/LandblockSpawnAdapter.cs` / - `EntitySpawnAdapter.cs` β€” bridge spawn lifecycle to WB ref-counts. - Atlas tier (procedural) goes via Landblock; per-instance tier +**Where the extracted code lives (post-Phase O):** +- `src/AcDream.Core/Rendering/Wb/` β€” pure dat/mesh helpers (5 files, ~782 LOC): + `TerrainUtils`, `TerrainEntry`, `RegionInfo`, `SceneryHelpers`, + `TextureHelpers`. No GL dependency; safe to use from Core. +- `src/AcDream.App/Rendering/Wb/` β€” GL infrastructure + mesh pipeline (~27 files, + ~7K LOC): `ObjectMeshManager`, `WbMeshAdapter`, `WbDrawDispatcher`, + `LandblockSpawnAdapter`, `EntitySpawnAdapter`, `TextureCache`, + `GlobalMeshBuffer`, shader infrastructure, and the EnvCell/portal/scenery/ + terrain-blending pipeline classes. + +Before re-implementing any AC-specific rendering or dat-handling algorithm, +**read `docs/architecture/worldbuilder-inventory.md` FIRST**. The inventory +describes what we extracted (now in our tree) and what we still write ourselves. +Re-porting from retail decomp when we already have a tested port is how subtle +bugs (the scenery edge-vertex bug, the triangle-Z bug) keep slipping in. Retail +decomp remains the oracle for network, physics, animation, movement, UI, plugin, +audio, chat β€” see the inventory doc's πŸ”΄ list. + +**WB rendering cribs (all paths now in `src/AcDream.App/Rendering/Wb/`):** +- `WbMeshAdapter.cs` β€” single seam over `ObjectMeshManager`. Owns the mesh + pipeline, drains its staged-upload queue per frame via `Tick()`, populates + `AcSurfaceMetadataTable` with per-batch translucency / luminosity / fog + metadata. Consumes `DatCollection` via `DatCollectionAdapter` (O-D7 fallback + path; `ObjectMeshManager` has 26 internal `_dats.X` call sites that exceed + the inline-swap threshold β€” the adapter bridges our `IDatCollection` to the + `IDatReaderWriter` interface WB's internals expect). +- `WbDrawDispatcher.cs` β€” production draw path. Groups all visible (entity, + batch) pairs, single-uploads the matrix buffer, fires one + `glDrawElementsInstancedBaseVertexBaseInstance` per group with `BaseInstance` + pointing at the slice. Per-entity frustum cull, opaque front-to-back sort, + palette-hash memoization. +- `LandblockSpawnAdapter.cs` / `EntitySpawnAdapter.cs` β€” bridge spawn lifecycle + to ref-counts. Atlas tier (procedural) goes via Landblock; per-instance tier (server-spawned, palette/texture overrides) goes via Entity. - **Modern path is mandatory as of N.5 ship amendment (2026-05-08).** `WbFoundationFlag`, `InstancedMeshRenderer`, and `StaticMeshRenderer` are deleted. Missing `GL_ARB_bindless_texture` or `GL_ARB_shader_draw_parameters` throws `NotSupportedException` at startup. There is no legacy fallback. -- **WB's modern rendering path** (GL 4.3 + bindless) packs every mesh +- **The modern rendering path** (GL 4.3 + bindless) packs every mesh into a single global VAO/VBO/IBO. Each batch references its slice via `FirstIndex` (offset into IBO) + `BaseVertex` (offset into VBO). Honor those offsets when issuing draws β€” `DrawElementsInstanced` with `indices=0` will draw every entity's first triangle from the global mesh, not the per-batch range. (This is exactly the exploded-character bug we hit during Task 26.) -- **WB's `ObjectRenderBatch.SurfaceId` is unset** β€” the actual surface +- **`ObjectRenderBatch.SurfaceId` is unset** β€” the actual surface id lives in `batch.Key.SurfaceId` (the `TextureKey` struct). - **`ObjectMeshManager.IncrementRefCount` only bumps a counter** β€” it does NOT trigger mesh loading. You must explicitly call @@ -83,14 +96,14 @@ ourselves". Two `glMultiDrawElementsIndirect` calls per frame, one per pass. Total ~12-15 GL calls per frame for entity rendering regardless of scene complexity. -- **`TextureCache` requires `BindlessSupport`** for the WB modern path. +- **`TextureCache` requires `BindlessSupport`** for the modern path. Three `Bindless`-suffixed `GetOrUpload*` methods return 64-bit handles made resident at upload time, backed by parallel Texture2DArray uploads (`UploadRgba8AsLayer1Array`). The legacy `uint`-returning methods stay for Sky / Terrain / Debug / particle paths that still sample via `sampler2D`. After N.6 retires legacy renderers, the legacy upload path + caches can be deleted. -- **Translucency model is two-pass alpha-test** (matches WB), not +- **Translucency model is two-pass alpha-test** (matches original WB), not per-blend-mode subpasses. Opaque pass discards `Ξ±<0.95`; transparent pass discards `Ξ±β‰₯0.95` AND `Ξ±<0.05`. Native `Additive` blend renders as alpha-blend on GfxObj surfaces β€” falsifiable; if a magic-content @@ -103,8 +116,8 @@ ourselves". extend `InstanceData` stride 64β†’80 bytes, add the field, mix into fragment color in `mesh_modern.frag`. ~30 min when the time comes. - `src/AcDream.App/Rendering/TerrainModernRenderer.cs` β€” terrain dispatcher - on N.5's modern primitives. Mirrors WB's `TerrainRenderManager` pattern - (single global VBO/EBO + slot allocator + `glMultiDrawElementsIndirect`) + on N.5's modern primitives. Mirrors the original WB `TerrainRenderManager` + pattern (single global VBO/EBO + slot allocator + `glMultiDrawElementsIndirect`) but driven by acdream's `LandblockMesh.Build` so retail's `FSplitNESW` formula is preserved (issue #51 resolved). Atlas handles bound via the uvec2 + `sampler2DArray(handle)` constructor pattern (NOT the direct @@ -189,14 +202,16 @@ pursuing live in [`docs/architecture/code-structure.md`](docs/architecture/code- as part of the change. 2. **`AcDream.Core` must not depend on the window / GL / backend - projects, except via documented interop seams.** The only - currently-allowed seams are `WorldBuilder.Shared` (stateless helpers: - `TerrainUtils`, `TerrainEntry`, `RegionInfo`) and - `Chorizite.OpenGLSDLBackend.Lib` (stateless helpers only: - `SceneryHelpers`, `TextureHelpers`). New Core code that needs a GL - surface must define an interface in Core and let `AcDream.App` - implement it β€” never the reverse. If you need to add a project - reference to Core, the change must come with an inventory-doc + projects, except via documented interop seams.** As of Phase O + (2026-05-21), the only allowed seams are the extracted helpers in + `src/AcDream.Core/Rendering/Wb/` (`TerrainUtils`, `TerrainEntry`, + `RegionInfo`, `SceneryHelpers`, `TextureHelpers` β€” stateless, no GL). + The former `WorldBuilder.Shared` and `Chorizite.OpenGLSDLBackend.Lib` + project references are gone; their code now lives in our tree at those + paths. New Core code that needs a GL surface must define an interface + in Core and let `AcDream.App` implement it β€” never the reverse. If you + need to add a project reference to Core, the change must come with an + inventory-doc update explaining why. 3. **UI panels target `AcDream.UI.Abstractions` only.** No panel may @@ -698,34 +713,29 @@ inn door, click NPC, pick up item. Freeze list active β€” M1's phases are off-limits until M7 polish. Writeup at top of M1 block in `docs/plans/2026-05-12-milestones.md`. -**Currently working toward: Phase O β€” DatPath Unification** (active as -of 2026-05-21, by user direction β€” pre-empts M1.5). Tagline: "ONE -thing touches the DATs." We currently run two dat readers in process -(our `DatCollection` + WorldBuilder's `DefaultDatReaderWriter`); this -phase extracts the WB pieces we actually use into -`src/AcDream.Core/Rendering/Wb/`, swaps their dat dependency to our -`DatCollection`, and drops the `WorldBuilder.Shared` + -`Chorizite.OpenGLSDLBackend` project references. WB stays in -`references/` as a read-reference, not as a project dependency. -Verbatim copy first, no "improvements" while extracting. Spec: +**Phase O β€” DatPath Unification β€” SHIPPED 2026-05-21.** ONE thing +touches the DATs. ~33 WB files (~7.7K LOC) extracted into +`src/AcDream.{Core,App}/Rendering/Wb/`. Project references to +`WorldBuilder.Shared` + `Chorizite.OpenGLSDLBackend` dropped. +`DefaultDatReaderWriter` eliminated; `DatCollection` is the only dat +reader. `WbMeshAdapter` consumes it via `DatCollectionAdapter` +(O-D7 fallback; 26 `_dats.*` call sites exceeded the inline-swap +threshold). `references/WorldBuilder/` stays in-tree as read-reference. +Visual side-by-side passed: Holtburg town, inn interior, dungeon all +render identically to pre-O. Spec: [`docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md`](docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md). -Estimated 7-8 working days, ships as one merge window. -**M1.5 β€” PAUSED for Phase O.** Indoor walking work is held at the -2026-05-20 baseline (described below) and resumes once Phase O lands. -M1.5's Phase A6/A7 don't touch dat infrastructure, so no rework will -be needed. Demo scenario: enter the Holtburg Sewer through the -in-town portal, navigate through (walls block, stairs work, items -block, lighting reads correctly), exit back to town. Phases to ship -after Phase O: A6 (Indoor physics fidelity, cdb-driven) + A7 (Indoor -lighting fidelity, RenderDoc + retail-decomp driven). Issues in -scope: #80, #81, #83, #88, #90 (workaround removal), L-indoor, +**Currently working toward: M1.5 β€” Indoor world feels right** (resumed +from 2026-05-20 baseline after Phase O ship). Demo scenario: enter the +Holtburg Sewer through the in-town portal, navigate through (walls +block, stairs work, items block, lighting reads correctly), exit back +to town. Phases: A6 (Indoor physics fidelity, cdb-driven) + A7 +(Indoor lighting fidelity, RenderDoc + retail-decomp driven). Issues +in scope: #80, #81, #83, #88, #90 (workaround removal), L-indoor, L-spotlight, stairs, 2nd-floor, cellar, and the -`TryFindIndoorWalkablePlane` synthesis removal. **M2 ("Kill a -drudge") is deferred until M1.5 lands** β€” drudges live in dungeons -and the M2 demo target requires solid indoor navigation. Full M1.5 -writeup at the corresponding block in -`docs/plans/2026-05-12-milestones.md`. +`TryFindIndoorWalkablePlane` synthesis removal. **M2 ("Kill a drudge") +is deferred until M1.5 lands.** Full M1.5 writeup at the corresponding +block in `docs/plans/2026-05-12-milestones.md`. **Today's pre-M1.5 baseline (2026-05-20).** Five surgical fixes shipped to close the user-reported "logged in inside the inn, ran diff --git a/docs/architecture/worldbuilder-inventory.md b/docs/architecture/worldbuilder-inventory.md index 68144c9..b0527ce 100644 --- a/docs/architecture/worldbuilder-inventory.md +++ b/docs/architecture/worldbuilder-inventory.md @@ -1,35 +1,55 @@ -# WorldBuilder Inventory β€” what we take, adapt, or leave +# WorldBuilder Inventory β€” what we extracted, adapted, or left behind -**Status:** load-bearing reference. As of 2026-05-08 acdream's strategy is -to **rely heavily on WorldBuilder** for rendering and dat-handling rather -than re-port retail algorithms ourselves. WorldBuilder is MIT-licensed, is -verified by visual inspection to render the AC world correctly (terrain, -scenery, slabs, dungeons, slopes, particles), and uses the same Silk.NET -+ .NET stack we already target. +> **Phase O shipped 2026-05-21.** The ~33 WB files we actually use have +> been extracted into our tree. `references/WorldBuilder/` stays as a +> **read-reference only** β€” nothing in `src/AcDream.*` references it as a +> project dependency. `DatCollection` is now the only dat reader in process. +> +> Use this document to: +> 1. Know **where our extracted code lives** (look for the "Extracted to" +> column / notes in each section below). +> 2. Know **what WB still has** that we haven't needed yet β€” grep +> `references/WorldBuilder/` if you ever need to add something. +> 3. Know **what WB never had** (the πŸ”΄ list) β€” those are always ours. -**Integration model:** **fork upstream WorldBuilder** at -`github.com/Chorizite/WorldBuilder`, depend on our fork, delete editor-only -code, expose hooks for our network state to feed scene data in. Sync with -upstream via merge so we inherit fixes. This document tells you, before -you write code, whether the thing you're about to port already exists in -WorldBuilder. +**Pre-O status (archived for context):** As of Phase N.4 (2026-05-08) +acdream relied heavily on WorldBuilder as a project reference for rendering +and dat-handling. WorldBuilder is MIT-licensed, verified by visual inspection +to render the AC world correctly (terrain, scenery, slabs, dungeons, slopes, +particles), and uses the same Silk.NET + .NET stack we target. -**Workflow change:** Before re-implementing any AC-specific rendering or -dat-handling algorithm, **check this inventory first**. If WorldBuilder -has it, port from WorldBuilder (or call into our fork once it's wired -up), not from retail decomp. Retail decomp remains the oracle for things -WorldBuilder lacks β€” animation, motion, physics collision, networking. +**Post-O integration model:** Extracted WB code lives in two locations in +our tree (see CLAUDE.md for the full breakdown): +- `src/AcDream.Core/Rendering/Wb/` β€” pure helpers (no GL): `TerrainUtils`, + `TerrainEntry`, `RegionInfo`, `SceneryHelpers`, `TextureHelpers`. +- `src/AcDream.App/Rendering/Wb/` β€” GL infrastructure + mesh pipeline: + `ObjectMeshManager`, `WbMeshAdapter`, `WbDrawDispatcher`, texture cache, + shader infra, EnvCell/portal/scenery/terrain-blending pipeline classes. + +`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). + +**Workflow:** Before re-implementing any AC-specific rendering or dat-handling +algorithm, **check this inventory first**. If we already extracted it (🟒 +sections), it's in `src/AcDream.App/Rendering/Wb/` β€” use our copy. If WB has +it but we haven't extracted it yet, grep `references/WorldBuilder/` and extract +as needed. Retail decomp remains the oracle for things WB never had (πŸ”΄ list). + +Attribution: WorldBuilder is MIT-licensed. `NOTICE.md` includes WB attribution. --- -## Repo layout (as of cloned snapshot under `references/WorldBuilder/`) +## Read-reference layout (under `references/WorldBuilder/`, not project-referenced) -- **`Chorizite.OpenGLSDLBackend/`** β€” full OpenGL renderer (Silk.NET). +- **`Chorizite.OpenGLSDLBackend/`** β€” full OpenGL renderer (Silk.NET). The + components we use are extracted into `src/AcDream.App/Rendering/Wb/`. - **`WorldBuilder.Shared/`** β€” data models, dat parsers, landscape module. -- **`WorldBuilder/`** β€” Avalonia desktop app shell (NOT taken). -- **`WorldBuilder.{Windows,Linux,Mac}/`** β€” platform entry points (NOT taken). -- **`WorldBuilder.Server/`** β€” collab editing backend (NOT taken). -- **`Tests/` + `WorldBuilder.Shared.Benchmarks/`** β€” test harness (study). + The helpers we use are extracted into `src/AcDream.Core/Rendering/Wb/`. +- **`WorldBuilder/`** β€” Avalonia desktop app shell (not taken). +- **`WorldBuilder.{Windows,Linux,Mac}/`** β€” platform entry points (not taken). +- **`WorldBuilder.Server/`** β€” collab editing backend (not taken). +- **`Tests/` + `WorldBuilder.Shared.Benchmarks/`** β€” test harness (study only). **Upstream NuGet dependencies** (these stay as NuGet packages, we don't vendor them): @@ -234,17 +254,28 @@ WorldBuilder is a dat editor; it does not have: --- -## What this means for the workflow +## What this means for the workflow (post-Phase O) The CLAUDE.md "grep named β†’ decompile β†’ verify β†’ port" workflow stays the rule for everything in the πŸ”΄ list (network, physics, animation, -movement, UI, plugin, audio, chat). For anything in 🟒, the new rule is: -**check this inventory FIRST**. If WB has it, port from WB. Re-porting -from retail decomp when WB already has a tested port is no longer -appropriate β€” that's how we got the scenery edge-vertex bug. +movement, UI, plugin, audio, chat). -When the inventory says "take wholesale or adapt" and we discover a -behavior mismatch with retail (rare β€” WB is verified), the resolution -is: reconcile WB ↔ retail decomp ↔ holtburger ↔ ACE ↔ ACViewer (the -existing reference hierarchy in CLAUDE.md). WorldBuilder ranks at the -top of that hierarchy for anything 🟒. +For anything in 🟒 that we've already extracted: **the code is in our +tree at `src/AcDream.{Core,App}/Rendering/Wb/`**. Read it there β€” don't +grep `references/WorldBuilder/` unless you want to compare against the +original. Re-porting from retail decomp when we already have a tested +port is still how we'd get the scenery edge-vertex bug back. + +For anything in 🟒 that we have NOT yet extracted: grep +`references/WorldBuilder/` to find the source, then extract it using the +Phase O pattern (verbatim copy β†’ adapt constructor to accept +`IDatCollection` via `DatCollectionAdapter` where needed β†’ add to +`src/AcDream.App/Rendering/Wb/`). Do NOT add a new project reference back +to `WorldBuilder.Shared` or `Chorizite.OpenGLSDLBackend` β€” Phase O +permanently removed those. + +When we discover a behavior mismatch with retail (rare β€” the extracted +code is the same as the original), the resolution is: reconcile extracted +code ↔ retail decomp ↔ holtburger ↔ ACE ↔ ACViewer (the existing +reference hierarchy in CLAUDE.md). Our extracted code ranks at the top +of that hierarchy for anything 🟒. diff --git a/docs/plans/2026-04-11-roadmap.md b/docs/plans/2026-04-11-roadmap.md index 547699f..a88c858 100644 --- a/docs/plans/2026-04-11-roadmap.md +++ b/docs/plans/2026-04-11-roadmap.md @@ -74,6 +74,7 @@ | Indoor walking Phase 1 β€” BSP cluster (partial) | 2026-05-19. Probe + WorldPicker cell-BSP occlusion (#86 closed) + CellId promotion via AABB containment (partial #84 fix). Seven commits across 5 phases: `18a2e28` plan, `27d7de1` Phase A `[indoor-bsp]` probe + toggle, `3764867` Phase B CellBspRayOccluder in WorldPicker, `4e308d5` Phase B screen-rect tests, `c19d6fb` Phase D AABB containment + L.2e bare-low-byte fix, `fda6af7` Phase E `[cell-cache]` diagnostic, `1f11ba9` Phase E extended AABB/bsphere/poly-count fields. **#86 closed** (picker occlusion). **#84 partially closed** (spawn-in-building stuck-above-floor resolved; threshold/doorway walls remain open under #87). **#85 open** (wall pass-through root cause confirmed as same as #84 remaining symptom β€” CellId doesn't stay promoted during outdoorβ†’indoor walking). **#87 filed** (portal-based indoor cell tracking β€” retail-faithful follow-up). `[indoor-bsp]` + `[cell-cache]` probes stay in place as scaffolding for the follow-up phase. Handoff: [`docs/research/2026-05-19-cluster-a-shipped-handoff.md`](../research/2026-05-19-cluster-a-shipped-handoff.md). Plan: [`docs/superpowers/plans/2026-05-19-indoor-walking-phase1-bsp-cluster.md`](../superpowers/plans/2026-05-19-indoor-walking-phase1-bsp-cluster.md). | Tests βœ“ | | Indoor walking Phase 2 β€” Portal-based cell tracking | 2026-05-19. Portal-graph traversal replaces Phase D's AABB containment. Six commits: `1969c55` CellBSP+Portals wired into CellPhysics; `aad6976` CellTransit.FindCellList + FindTransitCellsSphere + AddAllOutsideCells + ResolveCellId rename; `069534a` BuildingPhysics + CheckBuildingTransit for outdoorβ†’indoor entry; `702b30a` code-review polish; `3ffe1e4` pass foot-sphere center to ResolveCellId (critical fix β€” was passing CheckPos instead of GlobalSphere[0].Origin, causing PointInsideCellBsp to return false at floor level); `eb0f772` TryFindIndoorWalkablePlane synthesizes walkable plane from cell floor poly so the resolver doesn't fall through to outdoor SampleTerrainWalkable. **Closes #87, #85, and the wall-pass-through portion of #84 (fully closes #84).** Files #88 (indoor static object vibration β€” pre-existing) and #89 (BSPQuery.SphereIntersectsCellBsp β€” approximation in CheckBuildingTransit). `[cell-transit]`, `[indoor-bsp]`, `[check-bldg]`, `[cell-cache]` probes stay in place. Handoff: [`docs/research/2026-05-19-indoor-walking-phase2-shipped-handoff.md`](../research/2026-05-19-indoor-walking-phase2-shipped-handoff.md). | Live βœ“ | | Indoor walking Phase A4 β€” Multi-cell BSP iteration | 2026-05-20. Ports retail's `CTransition::check_other_cells` (`acclient_2013_pseudo_c.txt:272717-272798`). After the primary cell's BSP returns OK, every other cell the foot-sphere overlaps is queried via `BSPQuery.FindCollisions`. Halt on first Collided/Adjusted/Slid; Slid clears the contact-plane fields. Three commits land the slices: `e6369e2` `CellTransit.FindCellSet` overload (refactor `FindCellList` to expose the candidate set); `493c5e5` `Transition.CheckOtherCells` + `ApplyOtherCellResult` combine helper; `691493e` (orig `967d065`, then `3add110` revert, then this reapply) wires `CheckOtherCells` into `FindEnvCollisions`. 10 new unit tests; 1139 + 8 baseline maintained. **Visual verification surfaced a separate, pre-existing M2 blocker:** at the Holtburg inn doorway the CellId ping-pongs between outdoor `0xA9B40022` and indoor vestibule `0xA9B40164` rapidly because indoor BSP push-back exits the indoor CellBSP volume β†’ ResolveCellId reclassifies as outdoor β†’ wall checks bypassed on outdoor ticks. Bug reproduces fully with A4 reverted (`launch-revert2.log`), confirming A4 is not the cause. A4 is correct and tested but **dormant in practice** until the ping-pong is fixed. Handoff: [`docs/research/2026-05-20-phase-a4-shipped-cell-pingpong-finding.md`](../research/2026-05-20-phase-a4-shipped-cell-pingpong-finding.md). Spec: [`docs/superpowers/specs/2026-05-20-phase-a4-multi-cell-bsp-design.md`](../superpowers/specs/2026-05-20-phase-a4-multi-cell-bsp-design.md). Plan: [`docs/superpowers/plans/2026-05-20-phase-a4-multi-cell-bsp.md`](../superpowers/plans/2026-05-20-phase-a4-multi-cell-bsp.md). | Live βœ“ (dormant pending cell-tracking fix) | +| Phase O β€” DatPath Unification | 2026-05-21. ONE thing touches the DATs. Extracted ~33 WB files / ~7.7K LOC into `src/AcDream.{Core,App}/Rendering/Wb/`. Dropped project references to `WorldBuilder.Shared` + `Chorizite.OpenGLSDLBackend` from `AcDream.App.csproj` and `AcDream.Core.csproj`. `DefaultDatReaderWriter` eliminated; `DatCollection` is the only dat reader. `WbMeshAdapter` consumes it via `DatCollectionAdapter` (O-D7 fallback; `ObjectMeshManager` has 26 internal `_dats.*` call sites exceeding the 20-site inline-swap threshold). `references/WorldBuilder/` stays in-tree as read-reference. **Visual side-by-side passed**: Holtburg town, inn interior, dungeon all render identically to pre-O. `NOTICE.md` includes WB MIT attribution. Spec: [`docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md`](../superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md). Audit: [`docs/superpowers/specs/2026-05-21-phase-o-t1-wb-audit.md`](../superpowers/specs/2026-05-21-phase-o-t1-wb-audit.md). Plan: [`docs/superpowers/plans/2026-05-21-phase-o-plan.md`](../superpowers/plans/2026-05-21-phase-o-plan.md). | Live βœ“ | Plus polish that doesn't get its own phase number: - FlyCamera default speed lowered + Shift-to-boost @@ -85,48 +86,17 @@ Plus polish that doesn't get its own phase number: ## Phases ahead β€” agreed order -### Phase O β€” DatPath Unification (ACTIVE β€” pre-empts M1.5 by user direction 2026-05-21) +### Phase O β€” DatPath Unification β€” SHIPPED 2026-05-21 **Tagline:** ONE thing touches the DATs. -**Filed:** 2026-05-21. **Status:** active. **Spec:** [`docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md`](../superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md). +**Filed:** 2026-05-21. **Status:** SHIPPED. **Spec:** [`docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md`](../superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md). -**Problem:** As of Phase N.4 (2026-05-08) we run TWO dat readers in -process β€” our `DatCollection` and WorldBuilder's -`DefaultDatReaderWriter` (constructed inside `WbMeshAdapter.cs:79`). -Both open the same four `.dat` files independently. Costs: -~50-100 MB duplicated index cache, double seeks on shared blocks, and -the cross-check awkwardness in `WbMeshAdapter.cs:224-229`. AC community -feedback flagged WB's dat-touching as "built for tools, not runtime"; -the double-reader is the most concrete manifestation. +See the shipped-table entry above for the full summary. Phase O is +complete; M1.5 (indoor walking, paused for Phase O) resumes from +the 2026-05-20 baseline. -**Approach:** Extract the WB pieces we actually use (mesh pipeline, -texture decode, GL state, scenery, terrain blending, EnvCell/portal -decode β€” roughly 3-5K LOC) into `src/AcDream.Core/Rendering/Wb/`. -Verbatim copy first, no "improvements" while extracting. Adapt -constructors to consume our `DatCollection` instead of -`IDatReaderWriter`. Drop the two project references -(`WorldBuilder.Shared` + `Chorizite.OpenGLSDLBackend`) at the end. -WB is MIT-licensed; attribute in `NOTICE.md`. Keep `references/WorldBuilder/` -in-tree for read-reference (not as a project dependency). - -**Why pre-empt M1.5:** User direction 2026-05-21 β€” we want this -foundational decoupling done before further milestone work compounds -the WB dependency. M1.5's Phase A6/A7 indoor work doesn't touch dat -infrastructure, so it can resume cleanly after Phase O lands. - -**Tasks** (full breakdown in spec Β§5; ~7-8 working days): -- **O-T1** β€” WB call-graph audit. Produce the exact "must extract" list. -- **O-T2** β€” Create `src/AcDream.Core/Rendering/Wb/` + `NOTICE.md` attribution. -- **O-T3** β€” Extract texture / GL infrastructure (no dat dependency, low risk). -- **O-T4** β€” Extract mesh pipeline (`ObjectMeshManager` + types) + `DatCollection` swap. -- **O-T5** β€” Extract scenery + terrain blending pipelines + `DatCollection` swap. -- **O-T6** β€” Extract EnvCell + portal renderers + `DatCollection` swap. -- **O-T7** β€” Drop the two project references. Drop `_wbDats` and cross-check code. -- **O-T8** β€” Verification gate: three-scene visual side-by-side with main (Holtburg, inn, dungeon). -- **O-T9** β€” Ship. Update CLAUDE.md + `worldbuilder-inventory.md` to reflect new ownership. - -**Acceptance** (full list in spec Β§6): +**Acceptance met** (full list in spec Β§6): - `dotnet build` + `dotnet test` green; ~1147 test count maintained. - Zero `using WorldBuilder.*` or `Chorizite.OpenGLSDLBackend.*` in `src/AcDream.*`. - `DefaultDatReaderWriter` referenced in zero places in our source. @@ -141,7 +111,7 @@ copy + swap to `DatCollection` only. Refactors in follow-up phases. **After Phase O ships:** M1.5 resumes from its 2026-05-20 baseline with no code changes lost β€” M1.5 doesn't touch WB-extracted territory. -### Milestone M1.5 β€” "Indoor world feels right" (PAUSED for Phase O β€” see above) +### Milestone M1.5 β€” "Indoor world feels right" (ACTIVE β€” Phase O shipped; resuming from 2026-05-20 baseline) The current top of the work order. Two phases (A6 + A7) inside one milestone. M2 ("kill a drudge") is deferred until M1.5 lands β€” diff --git a/docs/plans/2026-05-12-milestones.md b/docs/plans/2026-05-12-milestones.md index 86c1f6f..0ac230a 100644 --- a/docs/plans/2026-05-12-milestones.md +++ b/docs/plans/2026-05-12-milestones.md @@ -185,7 +185,18 @@ close range and the player sees "You pick up the X." in chat. --- -### M1.5 β€” "Indoor world feels right" β€” πŸ”΅ NEXT (active, opened 2026-05-20) +### M1.5 β€” "Indoor world feels right" β€” πŸ”΅ ACTIVE (resumed 2026-05-21 after Phase O ship) + +**Phase O β€” DatPath Unification β€” shipped 2026-05-21.** ONE thing +touches the DATs. ~33 WB files (~7.7K LOC) extracted into +`src/AcDream.{Core,App}/Rendering/Wb/`; project references to +`WorldBuilder.Shared` + `Chorizite.OpenGLSDLBackend` dropped; +`DefaultDatReaderWriter` eliminated. Visual side-by-side passed at +Holtburg town, inn interior, and dungeon. Phase O pre-empted M1.5 +per user direction 2026-05-21; M1.5 now resumes from the 2026-05-20 +baseline with no code changes lost β€” Phase O did not touch dat-loading +infrastructure for physics or collision, only the rendering pipeline. +M1.5's planned phases (A6 + A7) are unaffected. **Demo scenario:** Enter the Holtburg Sewer dungeon through the in-town entry portal. Navigate to the end (5–7 rooms with stairs + a multi-Z chamber).