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 (~27 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 only.

DefaultDatReaderWriter eliminated; DatCollection is the only dat reader.
WbMeshAdapter consumes our DatCollection via DatCollectionAdapter
(O-D7 fallback adapter; ObjectMeshManager has 26 _dats.X call sites,
exceeding the 20 refactor threshold).

Visual side-by-side passed: Holtburg town, inn interior, dungeon all
render identically to pre-O.

Doc updates:
- CLAUDE.md: rewrote WB integration cribs to point at extracted code.
  Code Structure Rules rule 2 updated to remove stale seam names.
  "Currently working toward" flipped from Phase O to M1.5 resumption.
- docs/architecture/worldbuilder-inventory.md: Phase O banner added.
  Status/integration model updated to post-O ownership. Workflow
  section updated to reference our extracted tree, not WB project ref.
- docs/plans/2026-04-11-roadmap.md: Phase O moved to shipped table.
  Phase O "ahead" block collapsed to SHIPPED note. M1.5 block updated
  to ACTIVE (Phase O shipped; resuming from 2026-05-20 baseline).
- docs/plans/2026-05-12-milestones.md: M1.5 heading updated to ACTIVE;
  Phase O ship writeup prepended to the M1.5 block.

Phase O ship closes Tasks O-T1..O-T7 shipped across this session.
Specs + audit + plan: docs/superpowers/{specs,plans}/2026-05-21-phase-o-*

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-21 17:41:15 +02:00
parent 57ee19968c
commit 2256006cb7
4 changed files with 162 additions and 140 deletions

144
CLAUDE.md
View file

@ -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

View file

@ -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 🟢.

View file

@ -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 —

View file

@ -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 (57 rooms with stairs + a multi-Z chamber).