CLAUDE.md: adopt the condensed structure, updated to current truth (-503/+176)

Reconciles the wip/main-local-claudemd-condensation distillation (made
~2026-06-03 by a parallel session) onto post-merge main:

- Current state section: ONE status block (<=5 lines + pointers, by
  rule) + canonical reading order + the two digest entry points + the
  divergence register; replaces status sediment scattered through
  Goal/Roadmap sections.
- Kept from the merged main (the condensation predated them): the
  memory/digest rule in How to operate, the divergence-register
  section + phase-checklist item, de-dated milestone rules.
- Dropped: shipped-phase ship-notes, stale next-phase candidate lists,
  the superseded reference_render_pipeline_state pointer.
- Also salvaged from the wip branch: .gitignore entries (.obsidian/,
  claude-memory junction) + pdb_extract.py __main__ guard. The wip's
  TextureDump edit predates main's args support (discarded) and its
  physics-probe edits were STRIP-marked leftovers (discarded).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-12 12:42:27 +02:00
parent 3c3293aebb
commit c007f5a962
3 changed files with 173 additions and 500 deletions

6
.gitignore vendored
View file

@ -81,3 +81,9 @@ substep_trace*
sg_built.txt sg_built.txt
# Stray bash-mangled path artifacts from PowerShell-via-bash escaping # Stray bash-mangled path artifacts from PowerShell-via-bash escaping
C[€-￿]* C[€-￿]*
# Obsidian vault config (personal, not project-wide)
.obsidian/
# Junction to Claude Code per-project memory (Obsidian vault visibility)
claude-memory

664
CLAUDE.md
View file

@ -1,4 +1,4 @@
# acdream — project instructions for Claude # acdream — project instructions for Claude
## Goal ## Goal
@ -25,119 +25,40 @@ single source of truth for how the client is structured. All work must
align with this document. When the architecture doc and reality diverge, align with this document. When the architecture doc and reality diverge,
update one or the other — never leave them out of sync. update one or the other — never leave them out of sync.
**WorldBuilder code lives in our tree as of Phase O (shipped 2026-05-21).** **WorldBuilder code lives in our tree.** Phase O extracted ~33 WB files
Phase N.4 (2026-05-08) adopted WB's rendering + dat-handling base as a (~7.7K LOC) into our own namespaces and dropped the two external project
project reference. Phase O (2026-05-21) extracted the ~33 files / ~7.7K LOC references. `DatCollection` is the **only** dat reader in process —
we actually use into our own namespaces and dropped the two external project `DefaultDatReaderWriter` is gone. `references/WorldBuilder/` remains
references. `DatCollection` is now the **only** dat reader in process — in-tree as a read-reference (MIT-licensed; grep it freely), but nothing
`DefaultDatReaderWriter` is gone. `references/WorldBuilder/` remains in-tree in `src/AcDream.*` references it as a project dependency.
as a read-reference (MIT-licensed; grep it freely), but nothing in
`src/AcDream.*` references it as a project dependency.
**Where the extracted code lives (post-Phase O):** **Where the extracted code lives (post-Phase O):**
- `src/AcDream.Core/Rendering/Wb/` — pure dat/mesh helpers (5 files, ~782 LOC): - `src/AcDream.Core/Rendering/Wb/` — pure dat/mesh helpers (5 files,
`TerrainUtils`, `TerrainEntry`, `RegionInfo`, `SceneryHelpers`, ~782 LOC): `TerrainUtils`, `TerrainEntry`, `RegionInfo`,
`TextureHelpers`. No GL dependency; safe to use from Core. `SceneryHelpers`, `TextureHelpers`. No GL dependency; safe to use
- `src/AcDream.App/Rendering/Wb/` — GL infrastructure + mesh pipeline (~27 files, from Core.
~7K LOC): `ObjectMeshManager`, `WbMeshAdapter`, `WbDrawDispatcher`, - `src/AcDream.App/Rendering/Wb/` — GL infrastructure + mesh pipeline
`LandblockSpawnAdapter`, `EntitySpawnAdapter`, `TextureCache`, (~27 files, ~7K LOC): `ObjectMeshManager`, `WbMeshAdapter`,
`GlobalMeshBuffer`, shader infrastructure, and the EnvCell/portal/scenery/ `WbDrawDispatcher`, `LandblockSpawnAdapter`, `EntitySpawnAdapter`,
terrain-blending pipeline classes. `TextureCache`, `GlobalMeshBuffer`, shader infrastructure, and the
EnvCell/portal/scenery/terrain-blending pipeline classes.
Before re-implementing any AC-specific rendering or dat-handling algorithm, **Modern rendering path is MANDATORY** as of the N.5 ship amendment.
**read `docs/architecture/worldbuilder-inventory.md` FIRST**. The inventory `WbFoundationFlag`, `InstancedMeshRenderer`, and `StaticMeshRenderer`
describes what we extracted (now in our tree) and what we still write ourselves. are deleted. Missing `GL_ARB_bindless_texture` or
Re-porting from retail decomp when we already have a tested port is how subtle `GL_ARB_shader_draw_parameters` throws `NotSupportedException` at
bugs (the scenery edge-vertex bug, the triangle-Z bug) keep slipping in. Retail startup. There is no legacy fallback. Engineering cribs (WbMeshAdapter
decomp remains the oracle for network, physics, animation, movement, UI, plugin, seams, N.5 SSBO layout, translucency model, gotchas) live in
audio, chat — see the inventory doc's 🔴 list. `memory/reference_modern_rendering_pipeline.md`.
**WB rendering cribs (all paths now in `src/AcDream.App/Rendering/Wb/`):** Before re-implementing any AC-specific rendering or dat-handling
- `WbMeshAdapter.cs` — single seam over `ObjectMeshManager`. Owns the mesh algorithm, **read `docs/architecture/worldbuilder-inventory.md` FIRST**.
pipeline, drains its staged-upload queue per frame via `Tick()`, populates The inventory describes what we extracted (now in our tree) and what we
`AcSurfaceMetadataTable` with per-batch translucency / luminosity / fog still write ourselves. Re-porting from retail decomp when we already
metadata. Consumes `DatCollection` via `DatCollectionAdapter` (O-D7 fallback have a tested port is how subtle bugs (the scenery edge-vertex bug, the
path; `ObjectMeshManager` has 26 internal `_dats.X` call sites that exceed triangle-Z bug) keep slipping in. Retail decomp remains the oracle for
the inline-swap threshold — the adapter bridges our `IDatCollection` to the network, physics, animation, movement, UI, plugin, audio, chat — see
`IDatReaderWriter` interface WB's internals expect). the inventory doc's 🔴 list.
- `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.
- **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.)
- **`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
`PrepareMeshDataAsync(id, isSetup)` to fire the background decode.
Result auto-enqueues to `_stagedMeshData` which `Tick()` drains.
`WbMeshAdapter` does this for you on first registration.
- **N.5 modern dispatch** (`docs/superpowers/specs/2026-05-08-phase-n5-modern-rendering-design.md`)
uses bindless textures + multi-draw indirect on top of N.4's grouped
pipeline. Per frame: three SSBO uploads (`_instanceSsbo` mat4 per
instance @ binding=0; `_batchSsbo` `(uvec2 textureHandle, uint layer,
uint flags)` per group @ binding=1; `_indirectBuffer`
`DrawElementsIndirectCommand[]` opaque-section + transparent-section).
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 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 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
regression shows up, add a third indirect call with
`glBlendFunc(SrcAlpha, One)` per spec §6 fallback (~30 min change).
- **Per-instance highlight (selection blink) is reserved — open
backlog, no scheduled phase.** `mesh_modern.vert`'s `InstanceData`
struct has a documented hook for `vec4 highlightColor`. Whoever
eventually picks it up finds the hook there; the change is localized:
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 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
`uniform sampler2DArray` + `glProgramUniformHandleARB` form, which
GL_INVALID_OPERATIONs on at least one driver).
- **Two-tier streaming architecture (Phase A.5, shipped 2026-05-10).**
`src/AcDream.App/Streaming/` owns the full streaming pipeline. Key types:
`StreamingRegion` (two-radius Chebyshev window: N₁=near, N₂=far; produces
`TwoTierDiff` with 5 transition lists per tick), `StreamingController`
(render-thread coordinator: routes `TwoTierDiff` to the worker queue and
drains completions up to `MaxCompletionsPerFrame` per frame),
`LandblockStreamer` (single background worker thread: `LoadFar` = heightmap
+ mesh only, `LoadNear` = heightmap + `LandBlockInfo` + scenery + mesh,
`PromoteToNear` = `LandBlockInfo` + scenery only),
`GpuWorldState` (render-thread entity state: `AddEntitiesToExistingLandblock`
for promotions, `RemoveEntitiesFromLandblock` for demotions).
Default: N₁=4 (81 near LBs, full detail), N₂=12 (544 far LBs, terrain
only). Quality Preset system (`QualitySettings.From(preset)`) controls
both radii and MSAA/anisotropic/A2C/completions-per-frame as a unit.
Spec: `docs/superpowers/specs/2026-05-09-phase-a5-two-tier-streaming-design.md`.
**Execution model:** the active source of truth is the **milestones doc** **Execution model:** the active source of truth is the **milestones doc**
(`docs/plans/2026-05-12-milestones.md`) for "what are we building right (`docs/plans/2026-05-12-milestones.md`) for "what are we building right
@ -150,26 +71,28 @@ live under `docs/superpowers/specs/`.
The codebase is organized by layer (see architecture doc + the **Code The codebase is organized by layer (see architecture doc + the **Code
Structure Rules** section below). Plans live in `docs/plans/`, Structure Rules** section below). Plans live in `docs/plans/`,
research in `docs/research/`, persistent project memory in `memory/`. research in `docs/research/`, persistent project memory in `memory/`
and `~/.claude/projects/.../memory/` (the latter is browsable in
Obsidian via the `claude-memory/` junction in the repo root; see
`memory/reference_obsidian_vault.md`).
**UI strategy:** three-layer split — swappable backend (ImGui.NET + **UI strategy:** three-layer split — swappable backend (ImGui.NET +
`Silk.NET.OpenGL.Extensions.ImGui` for Phase D.2a, custom retail-look `Silk.NET.OpenGL.Extensions.ImGui` for Phase D.2a, custom retail-look
toolkit for D.2b later) / stable `AcDream.UI.Abstractions` layer toolkit for D.2b later) / stable `AcDream.UI.Abstractions` layer
(ViewModels + Commands + `IPanel` / `IPanelRenderer`) / unchanged game (ViewModels + Commands + `IPanel` / `IPanelRenderer`) / unchanged game
state. **As of Phase I (2026-04-25), ImGui hosts every dev/debug state. **As of Phase I, ImGui hosts every dev/debug panel** — Vitals,
panel** — Vitals, Chat, Debug. The previous custom-StbTrueTypeSharp Chat, Debug. The previous custom-StbTrueTypeSharp `DebugOverlay` was
`DebugOverlay` was deleted in I.2; `TextRenderer` + `BitmapFont` are deleted in I.2; `TextRenderer` + `BitmapFont` are kept alive
kept alive specifically for the future world-space HUD (D.6 — damage specifically for the future world-space HUD (D.6 — damage floaters,
floaters, name plates) where ImGui can't reach into the 3D scene. name plates) where ImGui can't reach into the 3D scene. D.2b remains
D.2b remains the long-term retail-look path (panels reskinned one at a the long-term retail-look path (panels reskinned one at a time using
time using dat assets); ImGui persists forever as the dat assets); ImGui persists forever as the `ACDREAM_DEVTOOLS=1`
`ACDREAM_DEVTOOLS=1` overlay. **All plugin-facing UI targets overlay. **All plugin-facing UI targets `AcDream.UI.Abstractions`
`AcDream.UI.Abstractions`never import a backend namespace from a never import a backend namespace from a panel.** Full design:
panel.** Full design: `docs/plans/2026-04-24-ui-framework.md`. [`docs/plans/2026-04-24-ui-framework.md`](docs/plans/2026-04-24-ui-framework.md).
Memory cribs: `memory/project_chat_pipeline.md` (chat pipeline as of Memory cribs: `memory/project_chat_pipeline.md` (chat pipeline as of
Phase I), `memory/project_input_pipeline.md` (input pipeline as of Phase I), `memory/project_input_pipeline.md` (input pipeline as of
Phase K). UI architecture full design at Phase K).
[`docs/plans/2026-04-24-ui-framework.md`](docs/plans/2026-04-24-ui-framework.md).
**Input pipeline:** `src/AcDream.UI.Abstractions/Input/` (action enum, **Input pipeline:** `src/AcDream.UI.Abstractions/Input/` (action enum,
`KeyChord`, `KeyBindings`, multicast `InputDispatcher` with scope `KeyChord`, `KeyBindings`, multicast `InputDispatcher` with scope
@ -179,9 +102,43 @@ stack + modal capture for rebind UX) + `src/AcDream.App/Input/`
`KeyBindings.RetailDefaults()` matching `KeyBindings.RetailDefaults()` matching
`docs/research/named-retail/retail-default.keymap.txt`). The Settings `docs/research/named-retail/retail-default.keymap.txt`). The Settings
panel (F11 / View → Settings) lets users remap any action via panel (F11 / View → Settings) lets users remap any action via
click-to-rebind. As of Phase K (2026-04-26), ALL keyboard / mouse click-to-rebind. As of Phase K, ALL keyboard / mouse input flows
input flows through the dispatcher — no IsKeyPressed polling outside through the dispatcher — no IsKeyPressed polling outside the per-frame
the per-frame movement queries. movement queries.
## Current state
**Currently working toward: M1.5 — Indoor world feels right**
(M1 — Walkable + clickable world — landed 2026-05-16 via Phase B.6).
The holistic building-render port (Option A: ONE `DrawInside(viewer_cell)`,
no inside/outside branch; BR-2..BR-7/T1..T6) is SHIPPED and user-gated,
as are the 2026-06-12 closes: #119/#128 tower stairs, #112 cottage
transparency. Open render/physics ledger: #113 re-check, #124, #129,
#130, #108-residual, #116, #127 (leads in ISSUES.md). Keep this
paragraph ≤5 lines + pointers — detail lives in the docs below, NOT here.
For canonical state, read in this order:
- [`docs/plans/2026-05-12-milestones.md`](docs/plans/2026-05-12-milestones.md) — milestone targets + freeze list per milestone
- [`docs/plans/2026-04-11-roadmap.md`](docs/plans/2026-04-11-roadmap.md) — what's shipped, what's in flight, what's next
- [`docs/ISSUES.md`](docs/ISSUES.md) — open + recently closed bugs (tactical)
- [`docs/architecture/retail-divergence-register.md`](docs/architecture/retail-divergence-register.md) — every known acdream-vs-retail deviation (see the register rules in the workflow section)
**Domain entry points (START HERE before domain work):**
- `claude-memory/project_render_pipeline_digest.md` — indoor render / doorway-FLAP / portal flood SSOT, with the DO-NOT-RETRY table
- `claude-memory/project_physics_collision_digest.md` — physics / collision / cell-membership SSOT, with the DO-NOT-RETRY table
For engineering reference (read on demand, not at session start):
- `memory/reference_modern_rendering_pipeline.md` — N.4/N.5 bindless+MDI dispatch, WbMeshAdapter/WbDrawDispatcher, translucency model
- `memory/reference_two_tier_streaming.md` — Phase A.5 streaming architecture
- `memory/reference_indoor_cell_tracking.md` — Phase 2 + A4 portal-based cell tracking + multi-cell BSP iteration
- `memory/reference_obsidian_vault.md` — Obsidian-as-memory-viewer setup + the memory-handling protocol
- `memory/reference_ghidra_projects.md` — Ghidra + GhidraMCP for acclient RE
- `memory/reference_repos.md` — what each `references/*` repo is for
The memory dir also holds `feedback_*.md` lessons-learned (cross-cutting
patterns the project has agreed on) and `project_*.md` per-subsystem
cribs (chat pipeline, input pipeline, interaction pipeline, etc.).
See `memory/MEMORY.md` for the index.
## Code Structure Rules ## Code Structure Rules
@ -202,8 +159,8 @@ pursuing live in [`docs/architecture/code-structure.md`](docs/architecture/code-
as part of the change. as part of the change.
2. **`AcDream.Core` must not depend on the window / GL / backend 2. **`AcDream.Core` must not depend on the window / GL / backend
projects, except via documented interop seams.** As of Phase O projects, except via documented interop seams.** As of Phase O,
(2026-05-21), the only allowed seams are the extracted helpers in the only allowed seams are the extracted helpers in
`src/AcDream.Core/Rendering/Wb/` (`TerrainUtils`, `TerrainEntry`, `src/AcDream.Core/Rendering/Wb/` (`TerrainUtils`, `TerrainEntry`,
`RegionInfo`, `SceneryHelpers`, `TextureHelpers` — stateless, no GL). `RegionInfo`, `SceneryHelpers`, `TextureHelpers` — stateless, no GL).
The former `WorldBuilder.Shared` and `Chorizite.OpenGLSDLBackend.Lib` The former `WorldBuilder.Shared` and `Chorizite.OpenGLSDLBackend.Lib`
@ -211,8 +168,7 @@ pursuing live in [`docs/architecture/code-structure.md`](docs/architecture/code-
paths. New Core code that needs a GL surface must define an interface 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 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 need to add a project reference to Core, the change must come with an
inventory-doc inventory-doc update explaining why.
update explaining why.
3. **UI panels target `AcDream.UI.Abstractions` only.** No panel may 3. **UI panels target `AcDream.UI.Abstractions` only.** No panel may
import `AcDream.UI.ImGui` or any backend namespace. ViewModels, import `AcDream.UI.ImGui` or any backend namespace. ViewModels,
@ -253,14 +209,15 @@ pursuing live in [`docs/architecture/code-structure.md`](docs/architecture/code-
lives in `claude-memory/` (the auto-loaded index is `MEMORY.md`). Before starting lives in `claude-memory/` (the auto-loaded index is `MEMORY.md`). Before starting
work in a domain that has a **digest**, read it first: `project_render_pipeline_digest.md` work in a domain that has a **digest**, read it first: `project_render_pipeline_digest.md`
(indoor render / doorway FLAP) and `project_physics_collision_digest.md` (indoor render / doorway FLAP) and `project_physics_collision_digest.md`
(physics / collision / #98 / membership). Each digest is current-truth-on-top (physics / collision / membership). Each digest is current-truth-on-top
plus a DO-NOT-RETRY table — it supersedes the dated banners that used to sprawl plus a DO-NOT-RETRY table — it supersedes dated banners. The memory-handling
across this file. The memory-handling protocol (distill-don't-journal, the digest protocol (distill-don't-journal, the digest pattern, tags, recall + capture)
pattern, tags, recall + capture) is in `reference_obsidian_vault.md`. **Do NOT add is in `reference_obsidian_vault.md`. **Do NOT add new dated status banners to
new dated banners to this file — update the relevant digest instead.** Obsidian this file — update the relevant digest (or the Current state pointer list)
(auto-started in the main repo via a SessionStart hook) is the live search / graph / instead.** Obsidian (auto-started in the main repo via a SessionStart hook) is
tag lens over `claude-memory/` through the `mcp__obsidian__*` tools when it's the live search / graph / tag lens over `claude-memory/` through the
running; the files read and write the same with it closed. `mcp__obsidian__*` tools when it's running; the files read and write the same
with it closed.
**You are the lead engineer AND architect on this project at all times.** **You are the lead engineer AND architect on this project at all times.**
You own the architecture (`docs/architecture/acdream-architecture.md`), You own the architecture (`docs/architecture/acdream-architecture.md`),
@ -393,9 +350,9 @@ The triangle-boundary Z bug cost 5 failed fix attempts from guessing.
The animation frame-swap bug cost 4 failed attempts. Every time we The animation frame-swap bug cost 4 failed attempts. Every time we
checked the decompiled code first, we got it right on the first try. checked the decompiled code first, we got it right on the first try.
**Now we have named retail symbols too — Step 0 cuts most lookups **Now we have named retail symbols too — Step 0 cuts most lookups
from 30 minutes to 5 seconds. And as of 2026-04-30, when "what does from 30 minutes to 5 seconds. When "what does retail actually DO at
retail actually DO at runtime?" is the question and decomp alone runtime?" is the question and decomp alone isn't enough, attach cdb
isn't enough, attach cdb to a live retail client (Step -1).** to a live retail client (Step -1).**
### For each new feature or bug fix: ### For each new feature or bug fix:
@ -528,10 +485,10 @@ Before marking any phase as done:
**When the question is "what does retail actually DO frame-by-frame?"** **When the question is "what does retail actually DO frame-by-frame?"**
the decomp alone is often not enough — code paths interact with state the decomp alone is often not enough — code paths interact with state
(LastKnownContactPlane, transient flags, accumulated counters) in ways (LastKnownContactPlane, transient flags, accumulated counters) in ways
that aren't obvious from reading. As of 2026-04-30 we have a working that aren't obvious from reading. We have a working toolchain to attach
toolchain to attach Windows' console debugger (cdb.exe) to a live Windows' console debugger (cdb.exe) to a live retail acclient.exe with
retail acclient.exe with full PDB symbols and capture state at any full PDB symbols and capture state at any breakpoint. **Use this when
breakpoint. **Use this when guessing has failed twice in a row.** guessing has failed twice in a row.**
### What we have ### What we have
@ -636,11 +593,6 @@ breakpoint. **Use this when guessing has failed twice in a row.**
- **Network protocol questions**`holtburger` references + ACE source - **Network protocol questions**`holtburger` references + ACE source
+ Wireshark are the right tools, not cdb. + Wireshark are the right tools, not cdb.
This toolchain was used to settle the L.5 steep-roof investigation:
30Hz physics tick (vs our 60Hz), `kill_velocity` gating,
`set_collide` rate per minute. See commit history around 2026-04-30
for the trace data and the decisions it drove.
## MCP servers (live tooling) ## MCP servers (live tooling)
Two MCP servers extend the static decomp + cdb workflow with live Two MCP servers extend the static decomp + cdb workflow with live
@ -740,64 +692,9 @@ acdream operates at **two altitudes** above the daily commit:
is where you orient when the project feels half-built and you're not is where you orient when the project feels half-built and you're not
sure what to work on. Phases are too granular to feel like progress; sure what to work on. Phases are too granular to feel like progress;
this doc is the multi-week target. this doc is the multi-week target.
- **`docs/plans/2026-04-11-roadmap.md`** — the strategic roadmap (next - **`docs/plans/2026-04-11-roadmap.md`** — the strategic roadmap.
section). Phase-level index. This is where you orient when you know Phase-level index. This is where you orient when you know the
the milestone and need the next concrete sub-phase. milestone and need the next concrete sub-phase.
**M1 landed 2026-05-16** via Phase B.6 (`d640ed7`). L.2 collision +
B.4 interaction + B.5 pickup + B.6 server-driven auto-walk all
shipped. The four demo targets work end-to-end: walk Holtburg, open
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`.
**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).
**Currently working toward: M1.5 — Indoor world feels right** (resumed
from 2026-05-20 baseline after Phase O ship).
**Indoor render & the doorway "FLAP" — read the digest, not banners.**
The full current state, the root cause, the DO-NOT-RETRY list, the ⚠️
`ACDREAM_PROBE_FLAP`→white-textures landmine, and the detail-doc pointers are
distilled in **`claude-memory/project_render_pipeline_digest.md`** (auto-loaded
via MEMORY.md). As of 2026-06-09 (HEAD `a1b12df`): governing direction is
**Option A — one `DrawInside(viewer_cell)`, NO inside/outside branch**;
R-A1/R-A2/R-A2b shipped (outside-looking-in flap GONE, seams GONE, portal-flood
churn KILLED); remaining = the visible indoor flap narrowed to §4 (edge-on
doorway grey + corner camera-seal). Render roots at the **VIEWER** cell, not the
player cell. Read the digest before any render/flap work — it supersedes the
dated render banners that used to live here.
**Physics / collision / cell-membership — read the digest, not banners.** The #98 cellar-ascent saga, the A6.P* phase ledger, the door/step-up (P2) work, the phantom-collision fixes (#100/#101), P1 membership, the apparatus inventory, and the full 18-entry DO-NOT-RETRY list are distilled in **`claude-memory/project_physics_collision_digest.md`** (auto-loaded via MEMORY.md). Current state (2026-06-09): #98 CLOSED via the `b3ce505` stopgap (a WORKAROUND — it introduced #99 door run-through, OPEN HIGH); P2 cellar-lip FIXED (`cc4590f`, visual-gated); P1 membership matches retail (no port needed); #100/#101 CLOSED. The open debt is the per-cell shadow architecture (A6.P4) that closes #99. Read the digest before any collision/physics/membership work — it supersedes the dated A6 banners that used to live here.
**Today's pre-M1.5 baseline (2026-05-20).** Five surgical fixes
shipped to close the user-reported "logged in inside the inn, ran
through walls" bug: A4 (multi-cell BSP iteration, `691493e`),
#89 (sphere-overlap in CheckBuildingTransit, `7ac8f54`),
#90 (sphere-overlap stickiness in ResolveCellId, `4ca3596` — WORKAROUND,
flagged for removal in A6.P4), #91 (indoor cell shadows in
FindObjCollisions, `c0d8405`), #92 (server cell id at player-mode
entry, `23ab173`). 1147 + 8 baseline maintained throughout. Walls
+ furniture block correctly at Holtburg inn and surrounding cottages
as of visual verification 2026-05-20. M1.5 starts from this baseline.
**M2 ("Kill a drudge") — deferred.** Equip a sword, walk to a drudge,
swing, see damage in chat, watch the swing animation, drudge dies
and drops loot, pick up the loot, open inventory and see it. Phases
to ship after M1.5: F.2 (Inventory panel), F.3 (Combat math + damage
flow), F.5a (visible-at-login dev panels — Attributes / Skills /
Equipped / Inventory list, minimal ImGui), L.1c (combat animation
wiring), L.1b (command router prereq). ~610 weeks once M1.5 lands.
**Work-order autonomy — the meta-rule.** You decide what to work on **Work-order autonomy — the meta-rule.** You decide what to work on
next, always. **The user does NOT pick between phases, milestones, or next, always. **The user does NOT pick between phases, milestones, or
@ -816,15 +713,16 @@ four below actually work.
**The four motivation-keeping rules:** **The four motivation-keeping rules:**
1. **One active milestone at a time.** Work that isn't on the critical 1. **One active milestone at a time.** Work that isn't on the critical
path to M1 gets filed in `docs/ISSUES.md` with a `post-M1` tag and path to the current milestone gets filed in `docs/ISSUES.md` with
muted. This is the single rule that kills the "jumping between the appropriate `post-Mx` tag and muted. This is the single rule
things" feeling. If a phase isn't part of the current milestone, it that kills the "jumping between things" feeling. If a phase isn't
doesn't get touched — even if it's tempting, even if it would be part of the current milestone, it doesn't get touched — even if
"quick", even if it would be "while I'm here." it's tempting, even if it would be "quick", even if it would be
"while I'm here."
2. **Frozen phases are off-limits.** M0's ~25 shipped phases are frozen 2. **Frozen phases are off-limits.** Shipped-milestone phases are
until M7's polish pass. Concretely: no rework on streaming, chat, frozen until M7's polish pass. Concretely: no rework on streaming,
input, the WB rendering migration, sky/lighting, the particle chat, input, the WB rendering migration, sky/lighting, the particle
system, or the network handshake. Those are done. Don't revisit them system, or the network handshake. Those are done. Don't revisit them
— even if you see something that could be 10% better. Visual — even if you see something that could be 10% better. Visual
nice-to-haves and architecture second-guesses on frozen phases are nice-to-haves and architecture second-guesses on frozen phases are
@ -835,16 +733,18 @@ four below actually work.
When a milestone's demo scenario is functionally complete, update When a milestone's demo scenario is functionally complete, update
`2026-05-12-milestones.md` with a one-paragraph writeup describing `2026-05-12-milestones.md` with a one-paragraph writeup describing
what works end-to-end, flip the freeze list, and update the what works end-to-end, flip the freeze list, and update the
"currently working toward" line in this CLAUDE.md to the next "currently working toward" line in this CLAUDE.md's **Current
milestone. Do NOT ask the user to record a demo video — they find state** section to the next milestone. Do NOT ask the user to
it pointless. The milestones doc + the CLAUDE.md flip ARE the record a demo video — they find it pointless. The milestones doc
milestone artifact. Phases ship; milestones land. + the CLAUDE.md flip ARE the milestone artifact. Phases ship;
milestones land.
4. **State both altitudes at session start.** First action of any 4. **State both altitudes at session start.** First action of any
session: "Currently working toward M1 — Walkable + clickable world. session: "Currently working toward [milestone]. Current phase:
Current phase: L.2. Next concrete step: [whatever]." This keeps the [phase]. Next concrete step: [whatever]." This keeps the high-level
high-level orientation visible alongside the immediate task and orientation visible alongside the immediate task and makes
makes mid-session drift obvious. mid-session drift obvious. The **Current state** section at the
top of this file is the always-current snapshot.
When reality and the milestones diverge — a phase grows beyond the When reality and the milestones diverge — a phase grows beyond the
milestone's scope, a demo scenario turns out to be unreachable without milestone's scope, a demo scenario turns out to be unreachable without
@ -868,228 +768,6 @@ acdream's plan lives in two files committed to the repo:
acceptance criteria. Do not drift from the spec without explicit user acceptance criteria. Do not drift from the spec without explicit user
approval. approval.
**Indoor walking Phase 2 — Portal-based cell tracking shipped
2026-05-19.** Six commits:
- `1969c55` — CellBSP + Portals wired into CellPhysics (`PortalInfo` struct, `VisibleCellIds`)
- `aad6976``CellTransit.FindCellList` + `FindTransitCellsSphere` + `AddAllOutsideCells`; `ResolveCellId` rename
- `069534a``BuildingPhysics` + `CheckBuildingTransit` for outdoor→indoor entry via `BldPortalInfo`
- `702b30a` — code-review polish (DRY cell-id derivation, `PortalFlags.ExactMatch` enum, docs)
- `3ffe1e4` — critical fix: pass foot-sphere center (`GlobalSphere[0].Origin`) not `CheckPos` to `ResolveCellId`
- `eb0f772``TryFindIndoorWalkablePlane` synthesizes indoor walkable plane from cell floor poly
**#86** (click selection penetrates walls) — **CLOSED** (Phase 1 Cluster A).
**#84** (blocked by air indoors) — **FULLY CLOSED.** Spawn-in-building variant
closed by Phase 1 (Phase D AABB containment). Wall-block-from-inside variant
closed by Phase 2 (portal-graph traversal).
**#85** (pass through walls outside→in) — **CLOSED** by Phase 2.
`CheckBuildingTransit` promotes CellId via the building-shell portal graph
on outdoor→indoor entry; indoor-BSP collision fires from both sides.
**#87** (indoor portal-based cell tracking) — **CLOSED** by Phase 2.
**#88** (indoor static objects vibrate) — **FILED** (pre-existing, Medium).
**#89** (port `BSPQuery.SphereIntersectsCellBsp`) — **FILED** (Low, documented
approximation in `CheckBuildingTransit`).
Diagnostic infrastructure: `[indoor-bsp]`, `[cell-cache]`, `[cell-transit]`,
`[check-bldg]` probes all stay in place.
Handoff: [`docs/research/2026-05-19-indoor-walking-phase2-shipped-handoff.md`](docs/research/2026-05-19-indoor-walking-phase2-shipped-handoff.md).
Phase 1 handoff: [`docs/research/2026-05-19-cluster-a-shipped-handoff.md`](docs/research/2026-05-19-cluster-a-shipped-handoff.md).
**Indoor walking Phase A4 — Multi-cell BSP iteration shipped 2026-05-20.**
Three commits land the slices (with one revert/reapply during visual
verification proving A4 wasn't the cause of the bug that surfaced):
- `e6369e2``CellTransit.FindCellSet` overload exposes the candidate set
- `493c5e5``Transition.CheckOtherCells` + `ApplyOtherCellResult` combine helper
- `691493e` — wire `CheckOtherCells` into `FindEnvCollisions` (orig `967d065`, revert `3add110`, reapply)
Ports retail's `CTransition::check_other_cells` at
`acclient_2013_pseudo_c.txt:272717-272798`. After the primary cell's BSP
returns OK, every other cell the foot-sphere overlaps is queried. Halt
on first Collided/Adjusted/Slid; Slid clears the contact-plane fields.
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` every few ticks. Indoor
BSP DOES detect walls (Collided/Adjusted/Slid fire on push-back), but
the push-back exits the indoor CellBSP volume → ResolveCellId
reclassifies as outdoor → wall checks bypassed on outdoor ticks → net
appearance "walls walk through." Bug reproduces fully with A4 reverted
(see `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`](docs/research/2026-05-20-phase-a4-shipped-cell-pingpong-finding.md).
**Next: cell-tracking ping-pong fix.** Retail oracle:
`acclient_2013_pseudo_c.txt:308742-308783` (`CObjCell::find_cell_list`
Position-variant). Look for the cell-array hysteresis / stickiness
logic that prevents flipping CellId on a single push-back. Likely
modifies `PhysicsEngine.ResolveCellId` to prefer the previous indoor
classification when the sphere is close to the indoor CellBSP volume.
**Next phase is Claude's choice** per work-order autonomy. Candidates:
M2 critical path (F.2 / F.3 / F.5a / L.1c / L.1b — kill-a-drudge demo);
or the pre-existing "next phase candidates" list below.
**Previously in Phase L.2 (Movement & Collision Conformance).** L.2a slices
1+2+3 + L.2d slice 1+1.5 + L.2g slice 1 + L.2g slice 1b + L.2g slice 1c +
**Phase B.4b** + **Phase B.4c** all shipped and visual-verified 2026-05-13;
**Phase B.5** (ground-item pickup, F-key) shipped and visual-verified
2026-05-14. The M1 demo target *"pick up an item"* is met for the
close-range path — single-click a ground item to select, walk within
~0.6 m of it, press F, and the item is removed from the world and added
to the player's inventory. Wire chain: `InteractRequests.BuildPickUp`
sends `PutItemInContainer (0xF7B1/0x0019)`; ACE despawns the item with
`GameMessagePickupEvent (0xF74A)` (NOT `0xF747 DeleteObject` — the
distinction surfaced during visual testing and is fixed by the new
`PickupEvent.cs` parser routed through the shared `EntityDeleted`
event). The M1 demo target *"open the inn door"* remains met from B.4b
+ B.4c. Issue #57 (B.4 handler gap) is closed. Issue #58 (door swing
animation) is closed by B.4c. Issues #61 (link→cycle boundary flash),
#62 (PARTSDIAG null-guard), **#63 (server-initiated MoveToObject
auto-walk not honored — blocks out-of-range pickup / Use)**, and **#64
(local-player pickup animation does not render)** are filed as
M1-deferred follow-up.
**B.5 ship handoff:** [`docs/research/2026-05-14-b5-shipped-handoff.md`](docs/research/2026-05-14-b5-shipped-handoff.md)
— full evidence for the 5 commits across InteractRequests / GameWindow / WorldSession + the bonus `PickupEvent (0xF74A)` wire-handler fix that closes the despawn gap.
**B.4c ship handoff:** [`docs/research/2026-05-13-b4c-shipped-handoff.md`](docs/research/2026-05-13-b4c-shipped-handoff.md)
— full evidence for the 4 commits + 2 bonus discoveries (stance-value wrong
`0x01` vs `0x3D` causing underground doors; link→cycle boundary flash).
**B.4b ship handoff:** [`docs/research/2026-05-13-b4b-shipped-handoff.md`](docs/research/2026-05-13-b4b-shipped-handoff.md)
— full evidence for the 9 commits + 4 bonus discoveries (double-click dead
code, DoubleClick gate, CollisionExemption, ServerGuid→Id translation).
**L.2g slice 1 ship handoff:** [`docs/research/2026-05-12-l2g-slice1-shipped-handoff.md`](docs/research/2026-05-12-l2g-slice1-shipped-handoff.md).
**L.2d ship handoff:** [`docs/research/2026-05-13-l2d-slice1-shipped-handoff.md`](docs/research/2026-05-13-l2d-slice1-shipped-handoff.md).
**Phase L.2a (Truth & Diagnostics) slices 1-3 shipped 2026-05-12.**
Three commits land the L.2 "make every bad movement outcome explainable"
diagnostic foundation. Slice 1 (`ebef820`) adds runtime-toggleable
`ACDREAM_PROBE_RESOLVE` (one `[resolve]` line per
`PhysicsEngine.ResolveWithTransition` call) + `ACDREAM_PROBE_CELL` (one
`[cell-transit]` line per `PlayerMovementController.CellId` change),
both backed by a new `AcDream.Core.Physics.PhysicsDiagnostics` static
class and mirrored as DebugPanel checkboxes. Slice 2 (`e0c08bc`) extends
the `[resolve]` line with `obj=0x...` attribution. Slice 3 (`a068292`)
populates the previously-stub `CollisionInfo.CollideObjectGuids` /
`LastCollidedObjectGuid` (declared in `TransitionTypes.cs` but never
written anywhere) at the per-object iteration in `FindObjCollisions`,
so the slice-2 promise is now actually delivered. Visual-verified at
the Holtburg Town doorway: probes captured 140 wall hits attributed to
`obj=0xA9B47900` (landblock-baked static = the building itself,
**NOT** a door entity), confirming L.2d sub-direction as **port
`CBuildingObj` collision + per-cell walkability** rather than door-
state-toggle. Plus a definitive L.2e finding: player `CellId` tracked
as bare low byte (`0x00000029`) with no landblock prefix.
**Phase C.1.5b (per-part PES transforms + dat-hydrated entity DefaultScript)
shipped 2026-05-12.** Closes issue #56. `SetupPartTransforms.Compute(setup)`
walks `PlacementFrames[Resting]``[Default]` → first-available and
returns one `Matrix4x4` per Setup part; `ParticleHookSink.SpawnFromHook`
now transforms each `CreateParticleHook.Offset` through
`partTransforms[PartIndex]` before applying entity rotation, so
multi-emitter scripts distribute across mesh parts instead of collapsing
to entity root. The `EntityScriptActivator.OnCreate` `ServerGuid==0`
guard was relaxed: it now keys by `entity.ServerGuid` when non-zero, else
`entity.Id` (the `0x40xxxxxx` interior-entity range is collision-free
with server guids, so no synthetic-ID scheme is needed). `GpuWorldState`
fires the activator from 4 new sites — `AddLandblock` +
`AddEntitiesToExistingLandblock` (Far→Near promotion) for OnCreate,
`RemoveLandblock` + `RemoveEntitiesFromLandblock` (Near→Far demotion)
for OnRemove — so dat-hydrated EnvCell statics (inn fireplaces, building
decorations) and exterior stabs (cottage chimneys) now activate their
`Setup.DefaultScript` automatically. **Reality discovery during design
(folded into spec §3):** EnvCell `StaticObjects` are already hydrated as
`WorldEntity` instances by `GameWindow.BuildInteriorEntitiesForStreaming`
with stable `entity.Id` in `0x40xxxxxx` — the handoff's §4 Q1/Q2
(synthetic ID scheme, separate walker class) were mooted by this.
**Visual-verified 2026-05-12** at Holtburg Town network portal (no
ground-burial, distributed swirl), Inn fireplace flames, cottage
chimney smoke, and a spell cast on `+Acdream`. Plan archived at
[`docs/superpowers/plans/2026-05-13-phase-c1.5b.md`](docs/superpowers/plans/2026-05-13-phase-c1.5b.md).
**Phase C.1.5a (portal PES wiring) shipped 2026-05-11** (merge `88bda12`).
Server-spawned `WorldEntity` entities fire their `Setup.DefaultScript`
through `PhysicsScriptRunner` on enter-world via the
`EntityScriptActivator` ([src/AcDream.App/Rendering/Vfx/EntityScriptActivator.cs](src/AcDream.App/Rendering/Vfx/EntityScriptActivator.cs)).
Visual-verified at the Holtburg Town network portal: 10-hook portal
script fires end-to-end with correct color, persistence, orientation,
multi-emitter dispatch. Filed #56 for per-part transform handling
(resolved in C.1.5b above). Plan archived at
[`docs/superpowers/plans/2026-05-12-phase-c1.5a-portals.md`](docs/superpowers/plans/2026-05-12-phase-c1.5a-portals.md).
**Phase N.6 slice 1 (gpu_us fix + radius=12 perf baseline) shipped
2026-05-11** (merge `9b447d4`). Fixed `gpu_us` double-buffering in
`WbDrawDispatcher` (ring-of-3 query slots, read-before-overwrite,
vendor-neutral). Captured authoritative perf baseline at Holtburg radii
4 / 8 / 12. **Conclusion: CPU dominates GPU by 3050× at every radius**;
GPU sits at 3.6% of frame budget; per-LB walk is the next bottleneck.
Baseline-doc recommendation: do C.1.5 next, then a reduced-scope slice 2
(atlas + persistent-mapped buffers dropped from slice-2 scope). Baseline
at [`docs/plans/2026-05-11-phase-n6-perf-baseline.md`](docs/plans/2026-05-11-phase-n6-perf-baseline.md).
Plan archived at [`docs/superpowers/plans/2026-05-11-phase-n6-slice1.md`](docs/superpowers/plans/2026-05-11-phase-n6-slice1.md).
Issue #55 filed (static-entity slow path reports ~1.45M `meshMissing`
per 5s at r4 standstill — diagnostic, not a visible regression).
**Post-A.5 polish phase complete 2026-05-11.** All three post-A.5
issues closed: #52 (lifestone, `e40159f`), #54 (JobKind, `bf31e59`),
#53 (Tier 1 entity cache, `f928e66`). Phase A.5 + post-A.5 polish
together comprise the streaming + rendering perf foundation for the
project.
**Next phase candidates (in rough preference order):**
- **"Click an NPC" verification spike (M1 critical path).** B.4b's
`WorldPicker` + `BuildUse` is already wired. The question is whether ACE
NPCs respond to a Use message from our testaccount and what they broadcast
back (TalkDirect? MoveToObject?). Spike: stand near a Holtburg NPC,
double-click, read what ACE sends back. If ACE responds with recognizable
packets, wire the handlers; if it is silent, investigate ACE's NPC handler
configuration. ~30 min spike, outcome determines whether NPC interaction
needs a full phase or is a one-commit fix.
- **Phase B.6 — Client-side MoveToObject auto-walk handling (closes #63).**
ACE auto-walks the player to out-of-range Use / Pickup targets via
`CreateMoveToChain` + `EnqueueBroadcastMotion(MoveToObject)`, but our client
doesn't honor the inbound motion broadcast — character drifts toward the
target and snaps back, ACE's chain times out. Reference implementation
exists in `references/holtburger/crates/holtburger-core/src/client/simulation.rs`
(the `approximate_move_to_object_projection_target` + `MoveToObject` case).
Unlocks double-click pickup, F-key pickup from any distance, Use on
out-of-range NPCs / corpses. Probably 1-2 commits + visual verification.
- **Triage the chronic open-issue list** in `docs/ISSUES.md`#2 (lightning),
#4 (sky horizon-glow), #28 (aurora), #29 (cloud thinness), #37 (humanoid
coat), #41 (remote-motion blips) have been open since April/early-May and
keep getting deferred. Either link each to a future phase or downgrade.
~1 hour, surfaces what's chronic vs. linked-to-a-phase.
- **More Phase C visual-fidelity work** (C.2 dynamic point lights, C.3
palette tuning, C.4 double-sided translucent polys) closing the
"world reads as old / broken vs. retail" backlog.
- **N.6 slice 2** at reduced scope (atlas opportunities only — persistent-
mapped buffers and other slice-2 items dropped per slice-1 baseline doc).
- **Perf tiers 2/3** (`docs/plans/2026-05-10-perf-tiers-2-3-roadmap.md`)
only if user wants sustained 500+ FPS. With Tier 1 dispatcher at ~1.2 ms
the project comfortably hits 200-400 FPS at radius=12 standstill;
escalation is optional from here.
- **Issue #61 — AnimationSequencer link→cycle boundary flash** (M1-deferred
polish). Brief flap at end of door-swing animations. Low severity; does
not block M1 demo. Address before milestone demo record if distracting.
- **Issue #62 — PARTSDIAG null-guard** (trivial latent fix). One-line
null-coalescing guard in `GameWindow.TickAnimations`. Address any time a
diagnostic-related PR is open nearby.
**Earlier rendering + streaming arc (2026-05-08 → 2026-05-10).**
Phases **N.4 → N.5 → N.5b → A.5** shipped the modern rendering
pipeline + two-tier streaming foundation: WB `ObjectMeshManager` as
production mesh path (N.4); bindless + `glMultiDrawElementsIndirect`
for entities (N.5, ~12-15 GL calls/frame) and terrain via Path C
preserving retail's `FSplitNESW` formula (N.5b, closes #51); two-tier
streaming N₁=4 / N₂=12 + QualityPreset system (A.5). Modern path is
mandatory as of N.5 ship amendment — `InstancedMeshRenderer`,
`StaticMeshRenderer`, `WbFoundationFlag` all deleted; missing bindless
throws at startup. Detail + decomp anchors + plan archives in roadmap
shipped-table rows 6366 at `docs/plans/2026-04-11-roadmap.md`.
Engineering gotchas (bindless Dispose order, texture target lock-in,
`uvec2` sampler-handle pattern, WB-vs-retail formula divergence)
documented inline at the relevant call sites and in
`feedback_wb_migration_*.md` memory entries.
**Rules:** **Rules:**
1. Before starting a new phase or sub-piece, re-read the roadmap and the 1. Before starting a new phase or sub-piece, re-read the roadmap and the
@ -1248,44 +926,39 @@ via `PlayerMovementController.ApplyServerRunRate`) or from
diagnostics (`[UM_RAW]`, `[SCFAST]`, `[SCFULL]`, `[SETCYCLE]`, diagnostics (`[UM_RAW]`, `[SCFAST]`, `[SCFULL]`, `[SETCYCLE]`,
`[FWD_WIRE]`, `[OMEGA_DIAG]`, `[SEQSTATE]`, `[PARTSDIAG]`, `[FWD_WIRE]`, `[OMEGA_DIAG]`, `[SEQSTATE]`, `[PARTSDIAG]`,
`[VEL_DIAG]`, `[UPCYCLE]`). Heavy. `[VEL_DIAG]`, `[UPCYCLE]`). Heavy.
- `ACDREAM_PROBE_RESOLVE=1` — L.2a slice 1+2+3 (2026-05-12). One - `ACDREAM_PROBE_RESOLVE=1` — one `[resolve]` line per
`[resolve]` line per `PhysicsEngine.ResolveWithTransition` call: `PhysicsEngine.ResolveWithTransition` call: input + target + output
input + target + output position/cell, ok-vs-partial, grounded-in, position/cell, ok-vs-partial, grounded-in, contact-plane status,
contact-plane status, wall normal if hit, **responsible entity wall normal if hit, **responsible entity guid**, env flag, walkable
guid** (post-slice-3 attribution plumbing), env flag, walkable polygon valid. Heavy (~30 Hz × every entity). Runtime-toggleable via
polygon valid. Heavy (~30 Hz × every entity). Runtime-toggleable the DebugPanel "Diagnostics" section if `ACDREAM_DEVTOOLS=1`.
via the DebugPanel "Diagnostics" section if `ACDREAM_DEVTOOLS=1`. - `ACDREAM_PROBE_CELL=1` — one `[cell-transit]` line per
- `ACDREAM_PROBE_CELL=1` — L.2a slice 1 (2026-05-12). One `PlayerMovementController.CellId` change: old → new cell, world
`[cell-transit]` line per `PlayerMovementController.CellId` position, reason tag (`resolver` / `teleport`). Low volume — only
change: old → new cell, world position, reason tag fires on actual cell crossings. Runtime-toggleable via the same
(`resolver` / `teleport`). Low volume — only fires on actual cell DebugPanel section.
crossings. Runtime-toggleable via the same DebugPanel section. - `ACDREAM_PROBE_PUSH_BACK=1` — emits three line types per physics
- `ACDREAM_PROBE_PUSH_BACK=1` — A6.P1 cdb probe spike (2026-05-21). tick: `[push-back]` (per `BSPQuery.AdjustSphereToPlane` call),
Emits three line types per physics tick: `[push-back]` (per `[push-back-disp]` (per `BSPQuery.FindCollisions` dispatch),
`BSPQuery.AdjustSphereToPlane` call), `[push-back-disp]` (per `[push-back-cell]` (per `Transition.CheckOtherCells` off-cell hit).
`BSPQuery.FindCollisions` dispatch), `[push-back-cell]` (per Heavy under motion (~100500 lines/sec). Pair with retail's cdb
`Transition.CheckOtherCells` off-cell hit). Heavy under motion breakpoint set at `tools/cdb/a6-probe.cdb` for the A6.P1 capture
(~100500 lines/sec). Pair with retail's cdb breakpoint set at protocol. Runtime-toggleable via the DebugPanel.
`tools/cdb/a6-probe.cdb` for the A6.P1 capture protocol. - `ACDREAM_PROBE_FLAP=1` — capture probe for indoor visibility
Runtime-toggleable via the DebugPanel "Diagnostics" section. decisions at frame boundaries. Used to converge the U.4c flap fix
- `ACDREAM_CAPTURE_RESOLVE=<path>` — A6.P3 #98 live capture of every (root indoor visibility at player's cell, not eye).
player-side `PhysicsEngine.ResolveWithTransition` call (2026-05-23 PM - `ACDREAM_CAPTURE_RESOLVE=<path>` — live capture of every player-side
apparatus). Each call appends one JSON Lines record with full inputs, `PhysicsEngine.ResolveWithTransition` call. Each call appends one
PhysicsBody snapshot before AND after, plus the `ResolveResult`. JSON Lines record with full inputs, PhysicsBody snapshot before AND
Filtered to `IsPlayer` mover flag — NPC / remote DR calls don't after, plus the `ResolveResult`. Filtered to `IsPlayer` mover flag
pollute. Pairs with the trajectory replay harness comparison test — NPC / remote DR calls don't pollute. Pairs with the trajectory
(`CellarUpTrajectoryReplayTests.Capture_*`) to diff captured vs harness replay harness comparison tests to diff captured vs harness state
state per field — the first divergence pinpoints missing apparatus per field — the first divergence pinpoints missing apparatus state.
state. Capture is OFF when the env var is unset (one null-check Capture is OFF when the env var is unset (one null-check cost per
cost per call). call).
- *(retired 2026-05-05 by L.3 M2/M3)* `ACDREAM_INTERP_MANAGER` was an - `ACDREAM_DUMP_CELLS=<path>` / `ACDREAM_DUMP_GFXOBJS=<path>` — dump
env-var gate on an experimental per-tick remote motion path. L.3 M2 resolved cell/GfxObj polygon tables as JSON when ids cache. Used
(commit 40d88b9) replaced both gates (`OnLivePositionUpdated` + for harness fixture extraction.
`TickAnimations`) with `IsPlayerGuid(...)` so player remotes use the
retail-faithful queue routing (InterpolationManager queue catch-up +
PositionManager combiner) unconditionally. NPCs and airborne player
remotes still flow through the legacy `apply_current_movement` +
`ResolveWithTransition` path. The env-var no longer toggles anything.
### Outbound motion wire format (acdream → ACE) ### Outbound motion wire format (acdream → ACE)
@ -1304,8 +977,8 @@ when relaying to remote observers. So our INBOUND parser sees
When the local player toggles Shift while keeping W held (Run↔Walk When the local player toggles Shift while keeping W held (Run↔Walk
demote/promote), acdream sends a fresh `MoveToState` with the new demote/promote), acdream sends a fresh `MoveToState` with the new
HoldKey + ForwardSpeed. Retail's outbound likely does the same, but HoldKey + ForwardSpeed. Retail's outbound likely does the same, but
ACE's behavior on relay is uncertain — see `#L.X` in ISSUES.md for ACE's behavior on relay is uncertain — see open issues in `docs/ISSUES.md`
the open Run↔Walk cycle bug on observed retail-driven remotes. for the Run↔Walk cycle bug on observed retail-driven remotes.
### Visual verification workflow ### Visual verification workflow
@ -1358,12 +1031,12 @@ The six references:
`ACViewer/Render/TextureCache.cs::IndexToColor` for the canonical `ACViewer/Render/TextureCache.cs::IndexToColor` for the canonical
subpalette overlay algorithm. subpalette overlay algorithm.
- **`references/WorldBuilder/`** — **acdream's rendering + dat-handling - **`references/WorldBuilder/`** — **acdream's rendering + dat-handling
base.** WorldBuilder is not just a reference: as of Phase N.4 (shipped base.** WorldBuilder is not just a reference: `ObjectMeshManager` is
2026-05-08), `ObjectMeshManager` is the production mesh pipeline, the production mesh pipeline, `WbMeshAdapter` is the seam, and
`WbMeshAdapter` is the seam, and `WbDrawDispatcher` is the production `WbDrawDispatcher` is the production draw path. The modern path is
draw path. The modern path (`N.5`) is **mandatory** — missing bindless **mandatory** — missing bindless throws at startup, there is no
throws at startup, there is no legacy fallback. **Before re-porting legacy fallback. **Before re-porting any rendering or dat-handling
any rendering or dat-handling algorithm from retail decomp, read algorithm from retail decomp, read
`docs/architecture/worldbuilder-inventory.md` first.** The inventory `docs/architecture/worldbuilder-inventory.md` first.** The inventory
tells you what WB covers (terrain, scenery, static objects, EnvCells, tells you what WB covers (terrain, scenery, static objects, EnvCells,
portals, sky, particles, texture decoding, mesh extraction, portals, sky, particles, texture decoding, mesh extraction,
@ -1406,13 +1079,6 @@ The six references:
and uses the server's authoritative Z. See and uses the server's authoritative Z. See
`docs/research/2026-04-12-movement-deep-dive.md` for the full analysis. `docs/research/2026-04-12-movement-deep-dive.md` for the full analysis.
Pattern: when you encounter an unknown behavior, grep all four for the
relevant term, read each hit, and compose a multi-source understanding
BEFORE writing acdream code. A single reference can be misleading; the
intersection of all four is almost always the truth. The user has
repeatedly had to remind me about this when I narrowly searched one ref
and missed obvious answers in another.
### Reference hierarchy by domain ### Reference hierarchy by domain
**NEVER GUESS an algorithm, formula, constant, wire format, or coordinate **NEVER GUESS an algorithm, formula, constant, wire format, or coordinate

View file

@ -455,4 +455,5 @@ def _main():
print(f"\nspot check: {target} NOT FOUND in symbols (PDB lookup mismatch?)") print(f"\nspot check: {target} NOT FOUND in symbols (PDB lookup mismatch?)")
_main() if __name__ == "__main__":
_main()