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
# Stray bash-mangled path artifacts from PowerShell-via-bash escaping
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
@ -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,
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).**
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.
**WorldBuilder code lives in our tree.** Phase O extracted ~33 WB files
(~7.7K LOC) into our own namespaces and dropped the two external project
references. `DatCollection` is 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.
**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.
- `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.
**Modern rendering path is MANDATORY** as of the N.5 ship amendment.
`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. Engineering cribs (WbMeshAdapter
seams, N.5 SSBO layout, translucency model, gotchas) live in
`memory/reference_modern_rendering_pipeline.md`.
**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.
- **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`.
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.
**Execution model:** the active source of truth is the **milestones doc**
(`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
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 +
`Silk.NET.OpenGL.Extensions.ImGui` for Phase D.2a, custom retail-look
toolkit for D.2b later) / stable `AcDream.UI.Abstractions` layer
(ViewModels + Commands + `IPanel` / `IPanelRenderer`) / unchanged game
state. **As of Phase I (2026-04-25), ImGui hosts every dev/debug
panel** — Vitals, Chat, Debug. The previous custom-StbTrueTypeSharp
`DebugOverlay` was deleted in I.2; `TextRenderer` + `BitmapFont` are
kept alive specifically for the future world-space HUD (D.6 — damage
floaters, name plates) where ImGui can't reach into the 3D scene.
D.2b remains the long-term retail-look path (panels reskinned one at a
time using dat assets); ImGui persists forever as the
`ACDREAM_DEVTOOLS=1` overlay. **All plugin-facing UI targets
`AcDream.UI.Abstractions`never import a backend namespace from a
panel.** Full design: `docs/plans/2026-04-24-ui-framework.md`.
state. **As of Phase I, ImGui hosts every dev/debug panel** — Vitals,
Chat, Debug. The previous custom-StbTrueTypeSharp `DebugOverlay` was
deleted in I.2; `TextRenderer` + `BitmapFont` are kept alive
specifically for the future world-space HUD (D.6 — damage floaters,
name plates) where ImGui can't reach into the 3D scene. D.2b remains
the long-term retail-look path (panels reskinned one at a time using
dat assets); ImGui persists forever as the `ACDREAM_DEVTOOLS=1`
overlay. **All plugin-facing UI targets `AcDream.UI.Abstractions`
never import a backend namespace from a panel.** Full design:
[`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
Phase I), `memory/project_input_pipeline.md` (input pipeline as of
Phase K). UI architecture full design at
[`docs/plans/2026-04-24-ui-framework.md`](docs/plans/2026-04-24-ui-framework.md).
Phase K).
**Input pipeline:** `src/AcDream.UI.Abstractions/Input/` (action enum,
`KeyChord`, `KeyBindings`, multicast `InputDispatcher` with scope
@ -179,9 +102,43 @@ stack + modal capture for rebind UX) + `src/AcDream.App/Input/`
`KeyBindings.RetailDefaults()` matching
`docs/research/named-retail/retail-default.keymap.txt`). The Settings
panel (F11 / View → Settings) lets users remap any action via
click-to-rebind. As of Phase K (2026-04-26), ALL keyboard / mouse
input flows through the dispatcher — no IsKeyPressed polling outside
the per-frame movement queries.
click-to-rebind. As of Phase K, ALL keyboard / mouse input flows
through the dispatcher — no IsKeyPressed polling outside the per-frame
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
@ -202,8 +159,8 @@ 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.** As of Phase O
(2026-05-21), the only allowed seams are the extracted helpers in
projects, except via documented interop seams.** As of Phase O,
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`
@ -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
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.
inventory-doc update explaining why.
3. **UI panels target `AcDream.UI.Abstractions` only.** No panel may
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
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`
(physics / collision / #98 / membership). Each digest is current-truth-on-top
plus a DO-NOT-RETRY table — it supersedes the dated banners that used to sprawl
across this file. The memory-handling protocol (distill-don't-journal, the digest
pattern, tags, recall + capture) is in `reference_obsidian_vault.md`. **Do NOT add
new dated banners to this file — update the relevant digest instead.** Obsidian
(auto-started in the main repo via a SessionStart hook) is the live search / graph /
tag lens over `claude-memory/` through the `mcp__obsidian__*` tools when it's
running; the files read and write the same with it closed.
(physics / collision / membership). Each digest is current-truth-on-top
plus a DO-NOT-RETRY table — it supersedes dated banners. The memory-handling
protocol (distill-don't-journal, the digest pattern, tags, recall + capture)
is in `reference_obsidian_vault.md`. **Do NOT add new dated status banners to
this file — update the relevant digest (or the Current state pointer list)
instead.** Obsidian (auto-started in the main repo via a SessionStart hook) is
the live search / graph / tag lens over `claude-memory/` through the
`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 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
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
from 30 minutes to 5 seconds. And as of 2026-04-30, when "what does
retail actually DO at runtime?" is the question and decomp alone
isn't enough, attach cdb to a live retail client (Step -1).**
from 30 minutes to 5 seconds. When "what does retail actually DO at
runtime?" is the question and decomp alone isn't enough, attach cdb
to a live retail client (Step -1).**
### 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?"**
the decomp alone is often not enough — code paths interact with state
(LastKnownContactPlane, transient flags, accumulated counters) in ways
that aren't obvious from reading. As of 2026-04-30 we have a working
toolchain to attach Windows' console debugger (cdb.exe) to a live
retail acclient.exe with full PDB symbols and capture state at any
breakpoint. **Use this when guessing has failed twice in a row.**
that aren't obvious from reading. We have a working toolchain to attach
Windows' console debugger (cdb.exe) to a live retail acclient.exe with
full PDB symbols and capture state at any breakpoint. **Use this when
guessing has failed twice in a row.**
### 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
+ 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)
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
sure what to work on. Phases are too granular to feel like progress;
this doc is the multi-week target.
- **`docs/plans/2026-04-11-roadmap.md`** — the strategic roadmap (next
section). Phase-level index. This is where you orient when you know
the 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.
- **`docs/plans/2026-04-11-roadmap.md`** — the strategic roadmap.
Phase-level index. This is where you orient when you know the
milestone and need the next concrete sub-phase.
**Work-order autonomy — the meta-rule.** You decide what to work on
next, always. **The user does NOT pick between phases, milestones, or
@ -816,15 +713,16 @@ four below actually work.
**The four motivation-keeping rules:**
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
muted. This is the single rule that kills the "jumping between
things" feeling. If a phase isn't part of the current milestone, it
doesn't get touched — even if it's tempting, even if it would be
"quick", even if it would be "while I'm here."
path to the current milestone gets filed in `docs/ISSUES.md` with
the appropriate `post-Mx` tag and muted. This is the single rule
that kills the "jumping between things" feeling. If a phase isn't
part of the current milestone, it doesn't get touched — even if
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
until M7's polish pass. Concretely: no rework on streaming, chat,
input, the WB rendering migration, sky/lighting, the particle
2. **Frozen phases are off-limits.** Shipped-milestone phases are
frozen until M7's polish pass. Concretely: no rework on streaming,
chat, input, the WB rendering migration, sky/lighting, the particle
system, or the network handshake. Those are done. Don't revisit them
— even if you see something that could be 10% better. Visual
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
`2026-05-12-milestones.md` with a one-paragraph writeup describing
what works end-to-end, flip the freeze list, and update the
"currently working toward" line in this CLAUDE.md to the next
milestone. Do NOT ask the user to record a demo video — they find
it pointless. The milestones doc + the CLAUDE.md flip ARE the
milestone artifact. Phases ship; milestones land.
"currently working toward" line in this CLAUDE.md's **Current
state** section to the next milestone. Do NOT ask the user to
record a demo video — they find it pointless. The milestones doc
+ the CLAUDE.md flip ARE the milestone artifact. Phases ship;
milestones land.
4. **State both altitudes at session start.** First action of any
session: "Currently working toward M1 — Walkable + clickable world.
Current phase: L.2. Next concrete step: [whatever]." This keeps the
high-level orientation visible alongside the immediate task and
makes mid-session drift obvious.
session: "Currently working toward [milestone]. Current phase:
[phase]. Next concrete step: [whatever]." This keeps the high-level
orientation visible alongside the immediate task and makes
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
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
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:**
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]`,
`[FWD_WIRE]`, `[OMEGA_DIAG]`, `[SEQSTATE]`, `[PARTSDIAG]`,
`[VEL_DIAG]`, `[UPCYCLE]`). Heavy.
- `ACDREAM_PROBE_RESOLVE=1` — L.2a slice 1+2+3 (2026-05-12). One
`[resolve]` line per `PhysicsEngine.ResolveWithTransition` call:
input + target + output position/cell, ok-vs-partial, grounded-in,
contact-plane status, wall normal if hit, **responsible entity
guid** (post-slice-3 attribution plumbing), env flag, walkable
polygon valid. Heavy (~30 Hz × every entity). Runtime-toggleable
via the DebugPanel "Diagnostics" section if `ACDREAM_DEVTOOLS=1`.
- `ACDREAM_PROBE_CELL=1` — L.2a slice 1 (2026-05-12). One
`[cell-transit]` line per `PlayerMovementController.CellId`
change: old → new cell, world position, reason tag
(`resolver` / `teleport`). Low volume — only fires on actual cell
crossings. Runtime-toggleable via the same DebugPanel section.
- `ACDREAM_PROBE_PUSH_BACK=1` — A6.P1 cdb probe spike (2026-05-21).
Emits three line types per physics tick: `[push-back]` (per
`BSPQuery.AdjustSphereToPlane` call), `[push-back-disp]` (per
`BSPQuery.FindCollisions` dispatch), `[push-back-cell]` (per
`Transition.CheckOtherCells` off-cell hit). Heavy under motion
(~100500 lines/sec). Pair with retail's cdb breakpoint set at
`tools/cdb/a6-probe.cdb` for the A6.P1 capture protocol.
Runtime-toggleable via the DebugPanel "Diagnostics" section.
- `ACDREAM_CAPTURE_RESOLVE=<path>` — A6.P3 #98 live capture of every
player-side `PhysicsEngine.ResolveWithTransition` call (2026-05-23 PM
apparatus). Each call appends one JSON Lines record with full inputs,
PhysicsBody snapshot before AND after, plus the `ResolveResult`.
Filtered to `IsPlayer` mover flag — NPC / remote DR calls don't
pollute. Pairs with the trajectory replay harness comparison test
(`CellarUpTrajectoryReplayTests.Capture_*`) to diff captured vs harness
state per field — the first divergence pinpoints missing apparatus
state. Capture is OFF when the env var is unset (one null-check
cost per call).
- *(retired 2026-05-05 by L.3 M2/M3)* `ACDREAM_INTERP_MANAGER` was an
env-var gate on an experimental per-tick remote motion path. L.3 M2
(commit 40d88b9) replaced both gates (`OnLivePositionUpdated` +
`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.
- `ACDREAM_PROBE_RESOLVE=1` — one `[resolve]` line per
`PhysicsEngine.ResolveWithTransition` call: input + target + output
position/cell, ok-vs-partial, grounded-in, contact-plane status,
wall normal if hit, **responsible entity guid**, env flag, walkable
polygon valid. Heavy (~30 Hz × every entity). Runtime-toggleable via
the DebugPanel "Diagnostics" section if `ACDREAM_DEVTOOLS=1`.
- `ACDREAM_PROBE_CELL=1` — one `[cell-transit]` line per
`PlayerMovementController.CellId` change: old → new cell, world
position, reason tag (`resolver` / `teleport`). Low volume — only
fires on actual cell crossings. Runtime-toggleable via the same
DebugPanel section.
- `ACDREAM_PROBE_PUSH_BACK=1` — emits three line types per physics
tick: `[push-back]` (per `BSPQuery.AdjustSphereToPlane` call),
`[push-back-disp]` (per `BSPQuery.FindCollisions` dispatch),
`[push-back-cell]` (per `Transition.CheckOtherCells` off-cell hit).
Heavy under motion (~100500 lines/sec). Pair with retail's cdb
breakpoint set at `tools/cdb/a6-probe.cdb` for the A6.P1 capture
protocol. Runtime-toggleable via the DebugPanel.
- `ACDREAM_PROBE_FLAP=1` — capture probe for indoor visibility
decisions at frame boundaries. Used to converge the U.4c flap fix
(root indoor visibility at player's cell, not eye).
- `ACDREAM_CAPTURE_RESOLVE=<path>` — live capture of every player-side
`PhysicsEngine.ResolveWithTransition` call. Each call appends one
JSON Lines record with full inputs, PhysicsBody snapshot before AND
after, plus the `ResolveResult`. Filtered to `IsPlayer` mover flag
— NPC / remote DR calls don't pollute. Pairs with the trajectory
replay harness comparison tests to diff captured vs harness state
per field — the first divergence pinpoints missing apparatus state.
Capture is OFF when the env var is unset (one null-check cost per
call).
- `ACDREAM_DUMP_CELLS=<path>` / `ACDREAM_DUMP_GFXOBJS=<path>` — dump
resolved cell/GfxObj polygon tables as JSON when ids cache. Used
for harness fixture extraction.
### 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
demote/promote), acdream sends a fresh `MoveToState` with the new
HoldKey + ForwardSpeed. Retail's outbound likely does the same, but
ACE's behavior on relay is uncertain — see `#L.X` in ISSUES.md for
the open Run↔Walk cycle bug on observed retail-driven remotes.
ACE's behavior on relay is uncertain — see open issues in `docs/ISSUES.md`
for the Run↔Walk cycle bug on observed retail-driven remotes.
### Visual verification workflow
@ -1358,12 +1031,12 @@ The six references:
`ACViewer/Render/TextureCache.cs::IndexToColor` for the canonical
subpalette overlay algorithm.
- **`references/WorldBuilder/`** — **acdream's rendering + dat-handling
base.** WorldBuilder is not just a reference: as of Phase N.4 (shipped
2026-05-08), `ObjectMeshManager` is the production mesh pipeline,
`WbMeshAdapter` is the seam, and `WbDrawDispatcher` is the production
draw path. The modern path (`N.5`) is **mandatory** — missing bindless
throws at startup, there is no legacy fallback. **Before re-porting
any rendering or dat-handling algorithm from retail decomp, read
base.** WorldBuilder is not just a reference: `ObjectMeshManager` is
the production mesh pipeline, `WbMeshAdapter` is the seam, and
`WbDrawDispatcher` is the production draw path. The modern path is
**mandatory** — missing bindless throws at startup, there is no
legacy fallback. **Before re-porting any rendering or dat-handling
algorithm from retail decomp, read
`docs/architecture/worldbuilder-inventory.md` first.** The inventory
tells you what WB covers (terrain, scenery, static objects, EnvCells,
portals, sky, particles, texture decoding, mesh extraction,
@ -1406,13 +1079,6 @@ The six references:
and uses the server's authoritative Z. See
`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
**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?)")
_main()
if __name__ == "__main__":
_main()