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:
parent
57ee19968c
commit
2256006cb7
4 changed files with 162 additions and 140 deletions
144
CLAUDE.md
144
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue