# acdream — strategic roadmap

**Status:** Living document. Updated 2026-05-19. **Between phases.** **Since the last header update:** Indoor walkable-plane BSP port FOUNDATION shipped (6 commits, `ff548b9` → `f845b22`) but visual verification failed — cellar descent, 2nd-floor walking, single-floor cottage regressions all confirm the shipped fix doesn't address the user-reported indoor bugs. Root cause now diagnosed as deeper than originally thought: `TryFindIndoorWalkablePlane` exists as a Phase 2 stop-gap that retail doesn't have an analog for. Retail retains ContactPlane state across frames; we re-synthesize per frame. Foundation work (BSP walker + probe + tests) remains useful; next phase needs to port retail's ContactPlane retention mechanism and likely eliminate `TryFindIndoorWalkablePlane` entirely. Handoff: [`docs/research/2026-05-19-indoor-walkable-plane-bsp-port-shipped-handoff.md`](../research/2026-05-19-indoor-walkable-plane-bsp-port-shipped-handoff.md). ISSUES #83 remains OPEN with deeper diagnosis. **Earlier:** Indoor cell rendering Phase 1 (diagnostics) + Phase 2 (fix) shipped — root cause was a one-line WB bug at `ObjectMeshManager.cs:1223` (blind `TryGet<Setup>` on GfxObj-prefixed stab ids threw `ArgumentOutOfRangeException` which WB's outer catch silently swallowed, causing 26/123 Holtburg cells to fail upload). Identified via diagnostic chain (5 `[indoor-*]` probes + a `ContinueWith` exception surfacer + a `ConsoleErrorLogger` injected into WB), fixed with a Setup-prefix guard. User visually confirmed floors render. Surfaced 9 pre-existing indoor bugs filed in `docs/ISSUES.md`. **Earlier:** C.1.5b shipped (issue #56 per-part transforms for multi-emitter PES + `EntityScriptActivator` extended to dat-hydrated EnvCell statics & exterior stabs — portal swirl, inn fireplace flames, cottage chimney smoke, spell-cast particles all match retail). post-A.5 polish completed (#52 lifestone, #54 JobKind, #53 Tier 1 cache); N.6 slice 1 shipped (gpu_us fix + radius=12 perf baseline, conclusion CPU dominates GPU 30–50×); C.1.5a shipped (portal PES wiring; surfaced #56 → resolved in C.1.5b).
**Purpose:** One source of truth for where the project is and where it's going. Every observed defect or missing feature has a named phase that owns it; when something looks wrong in-game, look here to find the phase that'll address it. Implementation details live in per-phase specs under `docs/superpowers/specs/`, not in this file.

---

## Phases already shipped

| Phase | What landed | Verification |
|---|---|---|
| 1 | Terrain rendering, plugin host scaffold | Visual ✓ |
| 2a | Static stabs/buildings (126 entities) | Visual ✓ |
| 2b | Textured 3×3 landblock grid + FlyCamera + IGameState | Visual ✓ |
| 2c | Procedural scenery (419 trees/rocks/bushes) | Visual ✓ |
| 2d | Interior EnvCell walker (475 static interior objects) | Visual ✓ |
| 3a/3b | Directional sun lighting + per-vertex terrain normals | Visual ✓ |
| 3c | Per-cell terrain texture blending (alpha atlas) | Visual ✓ |
| 4 | Full UDP codec + handshake + character login + WorldSession | Live ✓ |
| 5 | ObjDesc: AnimPartChange + TextureChanges + SubPalettes + ObjScale + Placement.Resting | Live ✓ |
| 6.1 | Idle motion frame resolution (MotionResolver MVP) | Live ✓ |
| 6.2 | Server-sent `MovementData` stance + forward command honored | Live ✓ |
| 6.3 | Server-supplied `MotionTableId` override (fixes drudge statue) | Live ✓ |
| 6.4 | Per-frame animation playback (breathing, idle cycles) | Live ✓ |
| 6.5 | Slerp between keyframes for smooth animation | Live ✓ |
| 6.6 | `UpdateMotion` (0xF74C) parser + dispatch to animation tick | Live ✓ |
| 6.7 | `UpdatePosition` (0xF748) parser + position reseating | Live ✓ |
| 7.1 | EnvCell room geometry — walls/floors/ceilings via CellStruct + Environment dats | Visual ✓ |
| 9.1 | Translucent render pass (AlphaBlend / Additive / InvAlpha + per-kind blend funcs) | Visual ✓ |
| 9.2 | Back-face culling in translucent pass (fixes lifestone crystal) | Visual ✓ |
| A.1 | Streaming landblock loader — runtime-configurable visible window (default 5×5, `ACDREAM_STREAM_RADIUS`), camera-centered offline / player-centered live, hysteresis-based unloads, pending-spawn list for late CreateObject events | Live ✓ |
| A.2 | Frustum culling — per-landblock AABB test (Gribb-Hartmann), terrain + static-mesh renderers skip culled landblocks, perf overlay in window title | Visual ✓ |
| A.3 | Background net receive thread — dedicated daemon thread buffers UDP into Channel, render thread drains | Visual ✓ |
| A.5 | Two-tier streaming + horizon LOD — N₁=4 (full detail, 81 LBs) + N₂=12 (terrain only, 544 LBs); fog blend at N₁; per-LB entity dispatcher walk tightened (Change #1 animated-walk fix + Change #2 cached AABB); single-worker off-thread mesh build; mipmaps + 16x anisotropic on TerrainAtlas; A2C with MSAA 4x on foliage; depth-write audit + lock-in test; **NEW T22.5: QualityPreset system** (Low/Medium/High/Ultra) with per-preset radii + MSAA + anisotropic + A2C + completions; env-var overrides per field; F11 mid-session re-apply. **Bug fixes post-T26 ship-prep**: (Bug A) far-tier worker now strips entities from far-tier loads — without this fix, far-tier LBs were loading their full entity layer (~71K entities) defeating the two-tier optimization; (Bug B) WalkEntities switched from per-frame fresh-list allocation to caller-provided scratch list (eliminated ~480 KB/frame GC pressure). **Deferred to post-A.5**: Tier 1 entity-classification cache (first attempt broke animation; revert + redo with animation-mutation audit), lifestone visual (missing in render), JobKind plumbing through BuildLandblockForStreaming (proper Bug A fix), Tier 2/3 perf optimizations (roadmap at docs/plans/2026-05-10-perf-tiers-2-3-roadmap.md). Plan archived at docs/superpowers/plans/2026-05-09-phase-a5-two-tier-streaming.md. | Live ✓ |
| B.3 | Physics MVP resolver foundation — terrain contact, CellSurface prototype, streaming-populated collision inputs, and first `PhysicsEngine` resolver path. Not the complete retail collision system. | Tests ✓ |
| B.2 | Player movement mode — Tab-toggled WASD ground walking, walk/run/idle animations, third-person chase camera, MoveToState + AutonomousPosition outbound, portal entry. Outdoor-only MVP. | Live ✓ |
| D.1 | 2D ortho overlay + font rendering (StbTrueTypeSharp atlas + TextRenderer + DebugOverlay) | Visual ✓ |
| E.1 | Motion-hook expansion — AnimationSequencer fires all 27 hook types per crossed frame; PosFrames root motion + vel/omega exposure; IAnimationHookSink + AnimationHookRouter fan-out | Tests ✓ |
| E.2 | Audio engine — OpenAL 16-voice 3D pool with retail-faithful quieter-slot eviction, SoundTable cookbook (probability-weighted variant picking), Wave PCM decoder, AudioHookSink wiring | Tests ✓ |
| E.3 | Particle system (data layer) — ParticleSystem with 13 motion integrators, EmitterDescRegistry, ParticleHookSink wiring all CreateParticle / DestroyParticle / StopParticle hooks | Tests ✓ |
| E.4 | Combat notifications + outbound — AttackTargetRequest (0x0008), 7 combat notification parsers (Victim/Defender/Attacker/Evasion/AttackDone/UpdateHealth), CombatState per-entity health tracker | Tests ✓ |
| E.5 | Spell cast wire — CastSpellRequest targeted (0x004A) + untargeted (0x0048), Spellbook (learned spells + active-enchantment layers), 5 enchantment GameEvent parsers | Tests ✓ |
| F.1 | GameEvent (0xF7B0) envelope dispatcher — all 94 sub-opcodes routed, exception-isolated handler registry, unhandled-count diagnostic bag; 18 event-payload parsers | Tests ✓ |
| F.2 | Item model + Appraise — ItemRepository with move/equip/property events, AppraiseRequest (0x00C8), IdentifyObjectResponse header, WieldObject + InventoryPutObjInContainer | Tests ✓ |
| G.1 | Sky + day/night — DerethDateTime (retail-exact 7620-tick calendar + 16-hour names + PY year), SkyStateProvider (4-keyframe default with angular-wrap lerp), WorldTimeService (server-synced clock with real-time advance) | Tests ✓ |
| G.2 | Dynamic lighting (selection) — LightSource + LightManager with retail 8-light cap, range-squared with 1.1× slack, slot 0 reserved for Sun, OwnerId-keyed unregister | Tests ✓ |
| G.1+ | Full sky visuals + weather + dynamic-light shader — SkyDescLoader parses Region 0x13000000 dat keyframes with retail fog fields (start/end/mode); WeatherSystem picks Clear/Overcast/Rain/Snow/Storm deterministically per in-game day with 10s fade; SkyRenderer draws far-plane-1e6 celestial meshes with UV scroll; SceneLightingUbo binds at std140 location=1 with 8 Light slots + fog + lightning flash; terrain.vert + mesh.frag + mesh_instanced.frag + sky.frag all consume the shared UBO; LightingHookSink auto-registers Setup.Lights per entity + flips IsLit on SetLightHook; ParticleRenderer renders rain/snow billboards; F7 cycles day time override, F10 cycles weather; WorldSession surfaces server time via ServerTimeUpdated (ConnectRequest + TimeSync flag) | Tests ✓ |
| H.1 | Chat window — wire layer + panel + outbound input + holtburger inbound parity all shipped (I.1-I.7). Talk (0x0015) / Tell (0x005D) / ChatChannel (0x0147) outbound + EmoteText (0x01E0) / SoulEmote (0x01E2) / ServerMessage (0xF7E0) / PlayerKilled (0x019E) / TurbineChat (0xF7DE) / SetTurbineChatChannels (0x0295) inbound; `ChatPanel` with Enter-to-submit input + slash commands; `CombatChatTranslator` posts combat events as chat lines. | Live ✓ |
| Glue | GameEventWiring.WireAll — single-call registration mapping parsed GameEvents → Core state classes (ChatLog, CombatState, Spellbook, ItemRepository); GameWindow exposes state classes + wires them to live session | Tests ✓ |
| D.2a | UI scaffold — `AcDream.UI.Abstractions` stable contract (`IPanel` / `IPanelHost` / `IPanelRenderer` / `ICommandBus` + `VitalsVM` / `VitalsPanel`); `AcDream.UI.ImGui` backend on ImGui.NET + `Silk.NET.OpenGL.Extensions.ImGui` (pivoted from Hexa.NET.ImGui on 2026-04-25 — Hexa's native OpenGL3 backend resolves GL via GLFW/SDL and crashed 0xC0000005 without them); VitalsPanel wired into GameWindow behind `ACDREAM_DEVTOOLS=1` with `ImGui.WantCaptureKeyboard` WASD suppression. 11 new tests. | Live ✓ |
| I.1 | `IPanelRenderer` widget extension — TextColored / Checkbox / Combo / InputTextSubmit / BeginTable + ~9 more widget signatures on `IPanelRenderer`; matching `ImGuiPanelRenderer` impls. Foundation for I.2 + I.4. | Tests ✓ |
| I.2 | DebugPanel migration — replaced 473-LOC StbTrueTypeSharp `DebugOverlay` with `AcDream.UI.Abstractions/Panels/Debug/DebugPanel` (collapsing-headers + diagnostics checkboxes + combat-event tail). `DebugOverlay.cs` deleted; `TextRenderer` + `BitmapFont` retained for the future world-space HUD (D.6). | Live ✓ |
| I.3 | `LiveCommandBus` + `WorldSession.SendTalk` / `SendTell` / `SendChannel` — replaces `NullCommandBus.Instance` with a real handler-registry `ICommandBus`. New `SendChatCmd` record + `ChannelResolver` legacy-id mapping (per holtburger). 3-line wrappers around existing `ChatRequests.BuildTalk/Tell/ChatChannel`. | Tests ✓ |
| I.4 | `ChatPanel` input field + slash commands — Enter-to-submit input field; `ChatInputParser` recognises `/say` `/t` `/tell` `/r` `/g` `/f` `/a` `/m` `/p` `/v` `/cv` `/lfg` `/trade` `/role` `/society` `/olthoi`; `ChatVM.LastIncomingTellSender` tracks for `/r` reply. `ImGui.WantCaptureKeyboard` already suppresses WASD on focus. | Live ✓ |
| I.5 | Holtburger inbound chat parity + Windows-1252 codec — `EmoteText (0x01E0)`, `SoulEmote (0x01E2)`, `ServerMessage (0xF7E0)`, `PlayerKilled (0x019E)` parsers + `WeenieError` routing through `GameEventWiring`. Global string codec switch from `Encoding.ASCII` to `Encoding.GetEncoding(1252)` so accented names round-trip per retail + holtburger. | Tests ✓ |
| I.6 | TurbineChat codec + `ChatChannelInfo` — full `0xF7DE` codec with three payload variants (`EventSendToRoom`, `RequestSendToRoomById`, `Response`), UTF-16LE strings with variable-length prefix, `SetTurbineChatChannels (0x0295)` parser, unified `ChatChannelInfo` (Legacy + Turbine variants), `TurbineChatState`. **ACE doesn't host a TurbineChat server — codec is ready when retail-emulating servers exist.** | Tests ✓ |
| I.7 | `CombatChatTranslator` — retail-faithful combat-text formatters into `ChatLog` ("You hit drudge for 50 slashing damage (87%)"). Subscribes to `CombatState`'s `DamageTaken` / `DamageDealtAccepted` / `EvadedIncoming` / `MissedOutgoing` / `AttackDone` / `KillLanded`; templates ported verbatim from holtburger `panels/chat.rs:221-308`. | Tests ✓ |
| K | Input architecture — `Action` enum, `KeyChord`, `KeyBindings`, multicast `InputDispatcher` with scope-stack + modal capture, retail-default keymap (152 bindings), `keybinds.json` persistence, F11 Settings panel with click-to-rebind + conflict detection, main menu bar + View menu | Live ✓ |
| L.0 | Full retail-style Settings interface — F11 tabbed panel with 6 tabs (Keybinds + Display + Audio + Gameplay + Chat + Character). `settings.json` at `%LOCALAPPDATA%\acdream\`, per-toon `Character` keying (swapped on EnterWorld). Display GL knobs (Resolution / Fullscreen / VSync / FOV / ShowFps) + Audio (Master / SFX) live-wired; Gameplay / Chat / Character settings persist for server-sync wiring later. Tab API extension to `IPanelRenderer`; chat Copy mode (read-only multi-line); per-panel layout reset; FramebufferResize handler keeps GL viewport + camera aspect + panel positions in sync. | Live ✓ |
| C.1 | PES particle system + sky-pass refinements — retail-faithful `ParticleEmitterInfo` unpack with all 13 motion integrators (`Particle::Init`/`Update` ports of `0x0051c290`/`0x0051c930`), `PhysicsScriptRunner` with `CallPES` self-loop semantics, `ParticleHookSink` with `EmitterDied` cleanup, instanced billboard `ParticleRenderer` with material-derived blend (DAT emitters never default additive — pulled from particle GfxObj surface), global back-to-front sort, BC clipmap alpha-keying, AttachLocal `is_parent_local=1` live-parent follow via `UpdateEmitterAnchor`. Sky pass: `Translucent+ClipMap` → alpha-blend cloud sheet (matches `D3DPolyRender::SetSurface` `0x0059c4d0`), raw-`Additive` fog-skip (matches `0x0059c882`), per-keyframe `SkyObjectReplace` Translucency/Luminosity/MaxBright divide-by-100, bit `0x01` pre/post-scene split (matches `GameSky::CreateDeletePhysicsObjects` `0x005073c0`), Setup-backed (`0x020xxxxx`) sky objects via `SetupMesh.Flatten`, persistent GL sampler objects (Wrap + ClampToEdge) replace per-frame wrap-mode mutation (ported from WorldBuilder's `OpenGLGraphicsDevice`), post-scene Z-offset gated on `(Properties & 4) != 0 && (Properties & 8) == 0` per `GameSky::UpdatePosition` `0x00506dd0`. Sky-PES playback disabled by default (named-retail proves `GameSky` drops `pes_id`); `ACDREAM_ENABLE_SKY_PES=1` opens the experimental path. 1325 → 1331 tests. | Live ✓ |
| N.1 | WorldBuilder-backed scenery (Chorizite/WorldBuilder fork as submodule, SceneryHelpers + TerrainUtils replace our inline ports) | Live ✓ |
| N.3 | WorldBuilder-backed texture decode — `SurfaceDecoder` delegates INDEX16 / P8 / A8R8G8B8 / R8G8B8 / A8(+Additive) to `TextureHelpers.Fill*`; `isAdditive` threaded through (terrain alpha → `FillA8Additive`, non-additive entity surfaces → `FillA8`). R5G6B5 + A4R4G4B4 newly handled (previously magenta). X8R8G8B8, DXT1/3/5, SolidColor remain ours (no WB equivalent). 9 conformance tests prove byte-identical equivalence per format. | Live ✓ |
| N.4 | Rendering pipeline foundation — adopted WB's `ObjectMeshManager` as the production mesh pipeline behind `ACDREAM_USE_WB_FOUNDATION` (default-on). `WbMeshAdapter` is the single seam (owns `ObjectMeshManager`, drains the staged-upload queue per frame, populates `AcSurfaceMetadataTable` with per-batch translucency / luminosity / fog metadata). `WbDrawDispatcher` is the production draw path: groups all visible (entity, batch) pairs, single-uploads the matrix buffer, fires one `glDrawElementsInstancedBaseVertexBaseInstance` per group with `BaseInstance` slicing into the shared instance VBO. `LandblockSpawnAdapter` + `EntitySpawnAdapter` bridge spawn lifecycle to WB ref-counts (atlas tier vs per-instance). Perf wins shipped as part of N.4: per-entity frustum cull, opaque front-to-back sort, palette-hash memoization (compute once per entity, reuse across batches). Visual verification at Holtburg passed: scenery + connected characters with full close-detail geometry (Issue #47 regression resolved). Legacy `InstancedMeshRenderer` retained as `ACDREAM_USE_WB_FOUNDATION=0` escape hatch until N.6 (retired early in N.5 ship amendment). | Live ✓ |
| N.5 | Modern rendering path — lifted `WbDrawDispatcher` onto bindless textures (`GL_ARB_bindless_texture`) + `glMultiDrawElementsIndirect`. Per-frame entity rendering: 3 SSBO uploads (instance matrices @ binding=0, batch data @ binding=1, indirect commands) + 2 indirect draw calls (opaque + transparent). ~12-15 GL calls per frame regardless of group count, down from hundreds-of-per-group in N.4. CPU dispatcher: 1.23 ms/frame median at Holtburg courtyard (1662 groups, ~810 fps sustained). All textures on the WB modern path use 1-layer `Texture2DArray` + `sampler2DArray`. Legacy callers keep `Texture2D` / `sampler2D` via the parallel `TextureCache` path until N.6 retires them. Three gotchas captured in memory: texture target lock-in, bindless Dispose order (two-phase non-resident before delete), GL_TIME_ELAPSED double-buffering. **Ship amendment 2026-05-08:** legacy renderers (`InstancedMeshRenderer`, `StaticMeshRenderer`, `WbFoundationFlag`) retired within N.5 — modern path is mandatory; missing bindless throws `NotSupportedException` at startup. N.6 scope narrowed accordingly. Plan archived at `docs/superpowers/plans/2026-05-08-phase-n5-modern-rendering.md`. | Live ✓ |
| N.5b | Terrain on the modern rendering path — `TerrainModernRenderer` replaces `TerrainChunkRenderer` (the latter plus `TerrainRenderer` + `terrain.vert/.frag` deleted). Single global VBO/EBO with slot allocator (one slot per landblock), per-frame `DrawElementsIndirectCommand[]` upload + `glMultiDrawElementsIndirect`, bindless atlas handles passed as `uvec2` uniforms reconstructed via `sampler2DArray(handle)`. **Path C** chosen: mirrors WB's `TerrainRenderManager` pattern but consumes `LandblockMesh.Build` so retail's `FSplitNESW` formula is preserved (closes ISSUE #51). Path A killed by 49.98% measured divergence between WB's `CalculateSplitDirection` and retail's at addr `00531d10`; Path B (fork-patch WB) rejected for permanent maintenance burden. Perf at Holtburg radius=5 (commit `da56063`): modern 6.4-7.0 µs / 9-14 µs p95 vs legacy 1.5 µs / 3.0 µs — **modern is ~4× SLOWER on CPU at radius=5** because legacy's 16×16-LB chunking collapsed visible LBs to one `glDrawElements`. Architectural wins (zero `glBindTexture`/frame, constant-cost dispatch, per-LB frustum cull) manifest at higher radius (A.5 territory). Spec acceptance criterion 5 ("≥10% lower CPU at radius=5") amended via `docs/plans/2026-05-09-phase-n5b-perf-baseline.md`. Three gotchas captured in memory: `uniform sampler2DArray` + `glProgramUniformHandleARB` GL_INVALID_OPERATIONs on at least one driver (use `uniform uvec2` + `sampler2DArray(handle)` constructor instead — N.5's mesh_modern pattern); `MaybeFlushTerrainDiag` median-calc underflow on first sample; visual gates need actual visual confirmation, not assent. Plan archived at `docs/superpowers/plans/2026-05-09-phase-n5b-terrain-modern.md`. | Live ✓ |
| N.6 slice 1 | GPU timing fix + radius=12 perf baseline. Fixed the gpu_us double-buffering bug in `WbDrawDispatcher` (ring-of-3 query slots, read-before-overwrite, vendor-neutral across AMD/NVIDIA/Intel desktop GL). Added env-gated `ACDREAM_DUMP_SURFACES=1` one-shot surface-format histogram dump in `TextureCache` for the atlas-opportunity audit. Captured authoritative baseline at Holtburg radii 4 / 8 / 12 (standstill + walking) with the now-working `gpu_us` diagnostic; baseline doc concludes CPU dominates GPU by 30–50× at every radius and recommends C.1.5 next then reduced-scope slice 2 (atlas + persistent-mapped buffers dropped). Baseline numbers at [docs/plans/2026-05-11-phase-n6-perf-baseline.md](2026-05-11-phase-n6-perf-baseline.md). Plan archived at `docs/superpowers/plans/2026-05-11-phase-n6-slice1.md`. | Live ✓ |
| C.1.5a | Portal PES wiring — server-spawned `WorldEntity` entities now fire their `Setup.DefaultScript` through the already-shipped `PhysicsScriptRunner` on enter-world. New ~70-line [`EntityScriptActivator`](../../src/AcDream.App/Rendering/Vfx/EntityScriptActivator.cs) class wires into `GpuWorldState`'s spawn lifecycle (`AppendLiveEntity` → `OnCreate`, `RemoveEntityByServerGuid` → `OnRemove`). Resolver lambda in `GameWindow` hits `_dats.Get<Setup>(...)?.DefaultScript.DataId` with defensive try/catch returning `0u` on miss. Activator also seeds `_particleSink.SetEntityRotation` so hook offsets transform from entity-local to world space correctly. **Verified at the Holtburg Town network portal**: 10-hook portal script fires end-to-end with correct color, persistence, orientation, multi-emitter dispatch. **Known limitation surfaced and filed as issue #56**: `ParticleHookSink` ignores `CreateParticleHook.PartIndex`, so the 10 emitters collapse to one root position instead of distributing across the portal Setup's parts — visually produces a compressed, partly-ground-buried swirl. Mechanism is correct; per-part transform handling is the next vfx-pipeline work (blocks slice 2 visual delight; affects every multi-emitter PES). Spec: [`docs/superpowers/specs/2026-05-12-phase-c1.5a-portals-design.md`](../superpowers/specs/2026-05-12-phase-c1.5a-portals-design.md). Plan: [`docs/superpowers/plans/2026-05-12-phase-c1.5a-portals.md`](../superpowers/plans/2026-05-12-phase-c1.5a-portals.md). | Live ✓ (with #56) |
| B.4b | Outbound Use handler wiring + 4 bonus fixes (L.2g slices 1b+1c, double-click detection, DoubleClick gate fix). Shipped 2026-05-13 (branch `claude/compassionate-wilson-23ff99`, merge pending). Closes #57. Files #58 (door swing animation, M1-deferred). `WorldPicker.BuildRay` + `Pick` (ray-sphere entity pick with inside-sphere guard); `GameWindow.OnInputAction` switch cases for `SelectLeft` / `SelectDblLeft` / `UseSelected`; `_entitiesByServerGuid` reverse-lookup dict + ServerGuid→entity.Id translation in `OnLiveStateUpdated` (L.2g slice 1c — THE actual blocker); `InputDispatcher` double-click detection 500ms threshold (binding was dead code without it); `CollisionExemption.ShouldSkip` widened to ETHEREAL-alone (ACE Door.Open() sends `state=0x0001000C`, not `0x14`). M1 demo target "open the inn door" verified at Holtburg inn doorway. Plan: [`docs/superpowers/plans/2026-05-13-phase-b4b-plan.md`](../superpowers/plans/2026-05-13-phase-b4b-plan.md). Handoff: [`docs/research/2026-05-13-b4b-shipped-handoff.md`](../research/2026-05-13-b4b-shipped-handoff.md). | Live ✓ |
| B.4c | Door swing animation. Shipped 2026-05-13 (branch `claude/phase-b4c-door-anim`, merge pending). Closes #58. Files #61 (AnimationSequencer link→cycle boundary flash; low-severity polish) + #62 (PARTSDIAG null-guard; latent). Spawn-time `AnimationSequencer` registration for door entities in `GameWindow.OnLiveEntitySpawnedLocked`: initial cycle seeded from `spawn.PhysicsState` (Off for closed, On for open). Shared `IsDoorName` / `IsDoorSpawn` helpers. `[door-cycle]` diagnostic in `OnLiveMotionUpdated` (gated on `ACDREAM_PROBE_BUILDING`). Bonus stance-value fix: `NonCombat = 0x3D` not `0x01` (wrong value caused doors to render halfway underground via empty sequencer frames). Visual-verified 2026-05-13 at Holtburg inn doorway: swing-open + swing-close cycles both play. M1 demo target "open the inn door" now has full visual feedback. Plan: [`docs/superpowers/plans/2026-05-13-phase-b4c-plan.md`](../superpowers/plans/2026-05-13-phase-b4c-plan.md). Handoff: [`docs/research/2026-05-13-b4c-shipped-handoff.md`](../research/2026-05-13-b4c-shipped-handoff.md). | Live ✓ |
| B.5 | Ground-item pickup (F-key, close-range path). Shipped 2026-05-14 (branch `claude/phase-b5-pickup`, merge pending). Closes M1 demo target 4/4 *"pick up an item"*. New `InteractRequests.BuildPickUp(seq, itemGuid, containerGuid, placement)` builds the 24-byte `PutItemInContainer (0xF7B1/0x0019)` wire body verified against `references/ACE/Source/ACE.Server/Network/GameAction/Actions/GameActionPutItemInContainer.cs`. New private `GameWindow.SendPickUp(uint itemGuid)` helper mirrors `SendUse`'s gate-on-InWorld pattern; `case InputAction.SelectionPickUp` in `OnInputAction` switch routes the F-key through `_selectedGuid`. **Bonus wire-handler fix (Task 2b):** ACE despawns picked-up items via `GameMessagePickupEvent (0xF74A)`, not the `GameMessageDeleteObject (0xF747)` we already handled — surfaced during visual testing (item kept rendering on ground after successful server-side pickup). New `PickupEvent.cs` parser + `WorldSession` dispatch branch adapt to `DeleteObject.Parsed` and reuse the existing `EntityDeleted → OnLiveEntityDeleted → RemoveLiveEntityByServerGuid` chain. Files #63 (server-initiated `MoveToObject` auto-walk not honored — out-of-range pickup / double-click fails server-side timeout) + #64 (local-player pickup animation does not render). Visual-verified 2026-05-14 at Holtburg: 3 successful close-range pickups (Pink Taper + Violet Tapers), item despawns locally as ACE acks. Plan: [`docs/superpowers/plans/2026-05-14-phase-b5-pickup.md`](../superpowers/plans/2026-05-14-phase-b5-pickup.md). Handoff: [`docs/research/2026-05-14-b5-shipped-handoff.md`](../research/2026-05-14-b5-shipped-handoff.md). | Live ✓ |
| Indoor lighting + rendering — Phase 1 (diagnostics) | Five `[indoor-*]` probes wired through new `AcDream.Core.Rendering.RenderingDiagnostics` static class + DebugVM mirrors + DebugPanel checkboxes. `WbMeshAdapter` emits `[indoor-upload] requested/completed`; `WbDrawDispatcher` emits `[indoor-walk]`, `[indoor-lookup]`, `[indoor-xform]`, `[indoor-cull]` per cell entity. All rate-limited via per-cellId frame counter; lookup probe uses high-bit-tagged key namespace to avoid cross-probe suppression. Holtburg `ACDREAM_PROBE_INDOOR_ALL=1` capture identified 26/123 cells silently failing — confirmed H1 (WB swallowed exception). Spec: [`docs/superpowers/specs/2026-05-19-indoor-cell-rendering-fix-design.md`](../superpowers/specs/2026-05-19-indoor-cell-rendering-fix-design.md). Plan: [`docs/superpowers/plans/2026-05-19-indoor-cell-rendering-phase1-diagnostics.md`](../superpowers/plans/2026-05-19-indoor-cell-rendering-phase1-diagnostics.md). Capture: [`docs/research/2026-05-19-indoor-cell-rendering-probe-capture.md`](../research/2026-05-19-indoor-cell-rendering-probe-capture.md). | Tests ✓ |
| Indoor lighting + rendering — Phase 2 (fix) | Three-component diagnostic-driven fix for missing-floor bug. Component 1: `WbMeshAdapter` captures the `Task<ObjectMeshData?>` from `PrepareMeshDataAsync` and attaches a `ContinueWith` for EnvCell ids — surfaces faulted-task exceptions + clean-null returns. Component 2: replaced `NullLogger<ObjectMeshManager>` with a Console-backed `ConsoleErrorLogger<T>` so WB's intentional `_logger.LogError(ex, ...)` at the swallow site at `ObjectMeshManager.cs:589` writes `[wb-error]` lines. **Root cause definitively identified in one capture: `ArgumentOutOfRangeException` from `DatReaderWriter.Setup.Unpack` at WB's `PrepareEnvCellMeshData` line 1223 — `TryGet<Setup>(stab.Id, ...)` was called blindly on every `envCell.StaticObjects` id without checking the Setup-prefix bit. GfxObj-typed stabs (0x01xxxxxx) caused mid-deserialization throws, bubbling up to PrepareMeshData's outer catch which silently returned null. Entire cell upload failed, room mesh never reached `_renderData`.** Component 3 fix: one-line type-check guard `(stab.Id & 0xFF000000u) == 0x02000000u && _dats.Portal.TryGet<Setup>(stab.Id, out var stabSetup)`. Committed to WB submodule on branch `acdream-fix-floor-rendering` at SHA `34460c4` — needs submodule pointer advance at merge time. **Verification: 0 [wb-error] (was 385), 0 NULL_RESULT (was 55), Holtburg 123/123 cells complete (was 97/123). User visually confirmed floors render in Holtburg Inn.** Surfaced 9 pre-existing indoor bugs (see-through floor, indoor collision, stairs, walls, click-thru, indoor lighting artifacts, atmospheric-lighting-on-stabs, slope terrain lighting) — all filed in `docs/ISSUES.md` for follow-up phases. Cause report: [`docs/research/2026-05-19-indoor-cell-rendering-cause.md`](../research/2026-05-19-indoor-cell-rendering-cause.md). Verification: [`docs/research/2026-05-19-indoor-cell-rendering-verification.md`](../research/2026-05-19-indoor-cell-rendering-verification.md). Plan: [`docs/superpowers/plans/2026-05-19-phase2-indoor-cell-rendering-fix.md`](../superpowers/plans/2026-05-19-phase2-indoor-cell-rendering-fix.md). | Live ✓ |
| C.1.5b | Per-part PES transforms + dat-hydrated entity DefaultScript dispatch. Closes issue #56. Shipped 2026-05-12 across 5 commits (`1e3c33b` docs+plan, `f3bc15e` SetupPartTransforms helper, `11521f4` ParticleHookSink applies `CreateParticleHook.PartIndex`, `5ca5827` activator refactor + GameWindow resolver lambda, `8735c39` GpuWorldState 4 new fire-sites). **Slice A** — new [`SetupPartTransforms.Compute(setup)`](../../src/AcDream.Core/Meshing/SetupPartTransforms.cs) walks `PlacementFrames[Resting]` → `[Default]` → first-available (mirrors `SetupMesh.Flatten` priority) and returns `Matrix4x4` per part; new `ParticleHookSink.SetEntityPartTransforms(entityId, partTransforms)` mirrors the existing `_rotationByEntity` pattern; `SpawnFromHook` now transforms hook offset through `partTransforms[partIndex]` before applying entity rotation. **Slice B** — activator's `ServerGuid==0` guard relaxed: keys by `entity.ServerGuid` when non-zero, else `entity.Id` (collision-free with server guids in the `0x40xxxxxx` interior / `0x80xxxxxx` scenery / `0xC0xxxxxx` ranges). Resolver delegate refactored to return `ScriptActivationInfo(ScriptId, PartTransforms)` so one dat lookup yields both pieces. `GpuWorldState` fires the activator from 4 new sites: `AddLandblock` + `AddEntitiesToExistingLandblock` (Far→Near promotion) for OnCreate, `RemoveLandblock` + `RemoveEntitiesFromLandblock` (Near→Far demotion) for OnRemove. ServerGuid==0 filter on AddLandblock avoids double-firing pending-bucket merges. **Reality discovery folded into spec §3**: EnvCell `StaticObjects` are already hydrated as `WorldEntity` instances by `GameWindow.BuildInteriorEntitiesForStreaming` (with stable `entity.Id` in `0x40xxxxxx`) — no synthetic-ID scheme or separate walker class needed (handoff §4 Q1/Q2 mooted). **Visual verification 2026-05-12**: Holtburg Town network portal swirl distributes across the arch (no ground-burial), Inn fireplace flames render over the firebox, cottage chimney smoke columns render, spell-cast animation-hook particles all match retail. 18 new + 4 updated tests, all Vfx/Meshing/Streaming/Activator green. Spec: [`docs/superpowers/specs/2026-05-13-phase-c1.5b-design.md`](../superpowers/specs/2026-05-13-phase-c1.5b-design.md). Plan: [`docs/superpowers/plans/2026-05-13-phase-c1.5b.md`](../superpowers/plans/2026-05-13-phase-c1.5b.md). | Live ✓ |
| Indoor walking Phase 1 — BSP cluster (partial) | 2026-05-19. Probe + WorldPicker cell-BSP occlusion (#86 closed) + CellId promotion via AABB containment (partial #84 fix). Seven commits across 5 phases: `18a2e28` plan, `27d7de1` Phase A `[indoor-bsp]` probe + toggle, `3764867` Phase B CellBspRayOccluder in WorldPicker, `4e308d5` Phase B screen-rect tests, `c19d6fb` Phase D AABB containment + L.2e bare-low-byte fix, `fda6af7` Phase E `[cell-cache]` diagnostic, `1f11ba9` Phase E extended AABB/bsphere/poly-count fields. **#86 closed** (picker occlusion). **#84 partially closed** (spawn-in-building stuck-above-floor resolved; threshold/doorway walls remain open under #87). **#85 open** (wall pass-through root cause confirmed as same as #84 remaining symptom — CellId doesn't stay promoted during outdoor→indoor walking). **#87 filed** (portal-based indoor cell tracking — retail-faithful follow-up). `[indoor-bsp]` + `[cell-cache]` probes stay in place as scaffolding for the follow-up phase. Handoff: [`docs/research/2026-05-19-cluster-a-shipped-handoff.md`](../research/2026-05-19-cluster-a-shipped-handoff.md). Plan: [`docs/superpowers/plans/2026-05-19-indoor-walking-phase1-bsp-cluster.md`](../superpowers/plans/2026-05-19-indoor-walking-phase1-bsp-cluster.md). | Tests ✓ |
| Indoor walking Phase 2 — Portal-based cell tracking | 2026-05-19. Portal-graph traversal replaces Phase D's AABB containment. Six commits: `1969c55` CellBSP+Portals wired into CellPhysics; `aad6976` CellTransit.FindCellList + FindTransitCellsSphere + AddAllOutsideCells + ResolveCellId rename; `069534a` BuildingPhysics + CheckBuildingTransit for outdoor→indoor entry; `702b30a` code-review polish; `3ffe1e4` pass foot-sphere center to ResolveCellId (critical fix — was passing CheckPos instead of GlobalSphere[0].Origin, causing PointInsideCellBsp to return false at floor level); `eb0f772` TryFindIndoorWalkablePlane synthesizes walkable plane from cell floor poly so the resolver doesn't fall through to outdoor SampleTerrainWalkable. **Closes #87, #85, and the wall-pass-through portion of #84 (fully closes #84).** Files #88 (indoor static object vibration — pre-existing) and #89 (BSPQuery.SphereIntersectsCellBsp — approximation in CheckBuildingTransit). `[cell-transit]`, `[indoor-bsp]`, `[check-bldg]`, `[cell-cache]` probes stay in place. Handoff: [`docs/research/2026-05-19-indoor-walking-phase2-shipped-handoff.md`](../research/2026-05-19-indoor-walking-phase2-shipped-handoff.md). | Live ✓ |
| Indoor walking Phase A4 — Multi-cell BSP iteration | 2026-05-20. Ports retail's `CTransition::check_other_cells` (`acclient_2013_pseudo_c.txt:272717-272798`). After the primary cell's BSP returns OK, every other cell the foot-sphere overlaps is queried via `BSPQuery.FindCollisions`. Halt on first Collided/Adjusted/Slid; Slid clears the contact-plane fields. Three commits land the slices: `e6369e2` `CellTransit.FindCellSet` overload (refactor `FindCellList` to expose the candidate set); `493c5e5` `Transition.CheckOtherCells` + `ApplyOtherCellResult` combine helper; `691493e` (orig `967d065`, then `3add110` revert, then this reapply) wires `CheckOtherCells` into `FindEnvCollisions`. 10 new unit tests; 1139 + 8 baseline maintained. **Visual verification surfaced a separate, pre-existing M2 blocker:** at the Holtburg inn doorway the CellId ping-pongs between outdoor `0xA9B40022` and indoor vestibule `0xA9B40164` rapidly because indoor BSP push-back exits the indoor CellBSP volume → ResolveCellId reclassifies as outdoor → wall checks bypassed on outdoor ticks. Bug reproduces fully with A4 reverted (`launch-revert2.log`), confirming A4 is not the cause. A4 is correct and tested but **dormant in practice** until the ping-pong is fixed. Handoff: [`docs/research/2026-05-20-phase-a4-shipped-cell-pingpong-finding.md`](../research/2026-05-20-phase-a4-shipped-cell-pingpong-finding.md). Spec: [`docs/superpowers/specs/2026-05-20-phase-a4-multi-cell-bsp-design.md`](../superpowers/specs/2026-05-20-phase-a4-multi-cell-bsp-design.md). Plan: [`docs/superpowers/plans/2026-05-20-phase-a4-multi-cell-bsp.md`](../superpowers/plans/2026-05-20-phase-a4-multi-cell-bsp.md). | Live ✓ (dormant pending cell-tracking fix) |
| Phase O — DatPath Unification | 2026-05-21. ONE thing touches the DATs. Extracted ~33 WB files / ~7.7K LOC into `src/AcDream.{Core,App}/Rendering/Wb/`. Dropped project references to `WorldBuilder.Shared` + `Chorizite.OpenGLSDLBackend` from `AcDream.App.csproj` and `AcDream.Core.csproj`. `DefaultDatReaderWriter` eliminated; `DatCollection` is the only dat reader. `WbMeshAdapter` consumes it via `DatCollectionAdapter` (O-D7 fallback; `ObjectMeshManager` has 26 internal `_dats.*` call sites exceeding the 20-site inline-swap threshold). `references/WorldBuilder/` stays in-tree as read-reference. **Visual side-by-side passed**: Holtburg town, inn interior, dungeon all render identically to pre-O. `NOTICE.md` includes WB MIT attribution. Spec: [`docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md`](../superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md). Audit: [`docs/superpowers/specs/2026-05-21-phase-o-t1-wb-audit.md`](../superpowers/specs/2026-05-21-phase-o-t1-wb-audit.md). Plan: [`docs/superpowers/plans/2026-05-21-phase-o-plan.md`](../superpowers/plans/2026-05-21-phase-o-plan.md). | Live ✓ |

Plus polish that doesn't get its own phase number:
- FlyCamera default speed lowered + Shift-to-boost
- SurfaceDecoder: PFID_P8 / PFID_R8G8B8 / PFID_X8R8G8B8 decoders
- GfxObjMesh: emit both pos and neg sides of double-sided polygons
- EnvCell mesh Z-lift to fix ground-floor / terrain flicker

---

## Phases ahead — agreed order

### Phase O — DatPath Unification — SHIPPED 2026-05-21

**Tagline:** ONE thing touches the DATs.

**Filed:** 2026-05-21. **Status:** SHIPPED. **Spec:** [`docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md`](../superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md).

See the shipped-table entry above for the full summary. Phase O is
complete; M1.5 (indoor walking, paused for Phase O) resumes from
the 2026-05-20 baseline.

**Acceptance met** (full list in spec §6):
- `dotnet build` + `dotnet test` green; ~1147 test count maintained.
- Zero `using WorldBuilder.*` or `Chorizite.OpenGLSDLBackend.*` in `src/AcDream.*`.
- `DefaultDatReaderWriter` referenced in zero places in our source.
- Resident memory at `radius=4 + 50 entities visible`: **≥ 50 MB reduction** vs. pre-O main.
- Visual side-by-side identical for the three reference scenes; user confirms.
- `NOTICE.md` includes WB MIT attribution.

**Non-goals** (explicit, spec §8): re-porting from retail decomp;
performance optimization of extracted code; API cleanup. Verbatim
copy + swap to `DatCollection` only. Refactors in follow-up phases.

**After Phase O ships:** M1.5 resumes from its 2026-05-20 baseline
with no code changes lost — M1.5 doesn't touch WB-extracted territory.

### Milestone M1.5 — "Indoor world feels right" (ACTIVE — Phase O shipped; resuming from 2026-05-20 baseline)

The current top of the work order. Two phases (A6 + A7) inside one
milestone. M2 ("kill a drudge") is deferred until M1.5 lands —
drudges live in dungeons and the M2 demo target requires solid indoor
navigation. Full milestone block in
[`docs/plans/2026-05-12-milestones.md`](2026-05-12-milestones.md).

**Today's pre-M1.5 baseline** (2026-05-20 — committed in this
session): A4 multi-cell BSP iteration (`691493e`), #89 sphere-overlap
in CheckBuildingTransit (`7ac8f54`), #90 sphere-overlap stickiness in
ResolveCellId (`4ca3596` — **WORKAROUND**, scheduled 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. Holtburg inn + cottage interiors visually verified
2026-05-20.

#### Phase A6 — Indoor physics fidelity (cdb-driven)

**Hypothesis:** Our `BSPQuery.FindCollisions` 6-path dispatcher (and
its callers) produce collision responses that systematically diverge
from retail's. Symptoms in different geometry (doorways, stairs,
multi-Z, cellars, dungeons) share one underlying mechanism — most
likely push-back distance / direction / CP synthesis.

**Investigation methodology:** cdb-attached comparison. Toolchain
documented in CLAUDE.md's "Retail debugger toolchain" section. Used
successfully 2026-04-30 for the steep-roof case. Matching binaries
(acclient.exe v11.4186) + PDB present.

**Sub-pieces (slices):**
- **✓ SHIPPED — A6.P1 — cdb probe spike** (2026-05-21). Built cdb
  scripts (`tools/cdb/a6-probe.cdb` v4 with PDB-verified offsets +
  hex-bits float output + Python decoder), PowerShell runner with
  ASCII encoding, PDB-match verification, and the new
  `[push-back]`/`[push-back-disp]`/`[push-back-cell]` acdream probe
  family (env `ACDREAM_PROBE_PUSH_BACK=1`). Captured 5 of 9 scenarios
  with paired retail+acdream traces (scen1 inn doorway, scen2 inn
  stairs, scen3 inn 2nd floor, scen4 cottage cellar, scen5 town
  network portal as substitute for Holtburg Sewer entry). Scen6-9
  cancelled — Holtburg Sewer doesn't exist on this server, and any
  substitute dungeon hits issue #95 (portal-graph visibility blowup)
  on portal entry, making physics-only analysis impossible. Five
  captures are sufficient evidence for A6.P2. Commits: infra Tasks
  1-14 + cdb iterations + scen1 capture (prior session); scen2-5
  captures (`a9a427f`, `297d1c5`, `4b5aebc`, `46c6e08`, `35d5c58`)
  + issue #95 filing (`5be784e`) (this session).
- **✓ SHIPPED — A6.P2 — Analysis report** (2026-05-21, `184933d`).
  Output: [`docs/research/2026-05-21-a6-cdb-capture-findings.md`](../research/2026-05-21-a6-cdb-capture-findings.md).
  Four findings ready for A6.P3: Finding 2 (ContactPlane resynthesis
  blowup — 250× to ∞× more CP writes in acdream; primary M1.5 root
  cause) is HIGH severity and the highest-confidence single-cause
  fix candidate. Finding 1 (dispatcher entry frequency mismatch —
  4× to 281× fewer dispatcher entries in acdream) is likely a
  secondary effect of Finding 2's missing retention paths. Finding 3
  (indoor cell-resolver sling-out captured in scen4) — HIGH severity,
  separate fix surface in ResolveCellId/CheckBuildingTransit.
  Finding 4 (portal-graph visibility blowup discovered incidentally
  in scen5) — filed as issue #95, scope-adjacent, handled outside A6.
  Tables 1+2 (per-site push-back delta + path-frequency diff)
  deferred to optional A6.P1.5 (entry+exit BPs in cdb script);
  not blocking A6.P3. M1.5 symptom coverage matrix shows every
  in-scope physics symptom mapped to at least one finding.
- **A6.P3 — Fix the BSP correction paths** (~3–5 days). Multi-slice.
  - **✓ SHIPPED — A6.P3 slice 1 — Indoor ContactPlane retention**
    (2026-05-21, commits `ba9655f` plan + `6b4be7f`/`c6bc2b9` T1
    research + `869edd9` T2 counter + `36975ef`/`a32f569` T3 test +
    `5aba071` T4 Mechanism B + `5f7722a`/`39fc037`/`bd5fe2e` T5 strip
    + `066568a` scen2 postfix proof + `<this commit>` T8 bookkeeping).
    Stripped `TryFindIndoorWalkablePlane` synthesis path from
    `Transition.FindEnvCollisions` indoor branch (matches retail's
    tiny `CEnvCell::find_env_collisions` shape at acclient_2013_pseudo_c.txt:309573).
    Added Mechanism B (LKCP restore) in `Transition.ValidateTransition`
    matching retail's pattern at acclient_2013_pseudo_c.txt:272565-272583.
    Per-unit-of-activity CP-write rate dropped 63×. **Unexpected win:
    stairs + cellar descent now WORK in acdream** (user happy-test
    confirmed). A6.P2 Finding 1 (dispatcher entry frequency mismatch)
    CLOSED as side-effect (dispatcher shape now retail-like). Finding 2
    PARTIALLY CLOSED — 99.3% of remaining cp-writes come from L622
    per-tick body-CP seed at `PhysicsEngine.ResolveWithTransition:622`
    (filed as issue #96 for slice 2).
  - **✓ SHIPPED — A6.P3 slice 2 — L622 seed investigation + no-op guard**
    (2026-05-22, commits `892019b` v1 + `f8d669b` v2). v1 removed the
    L622 seed entirely; broke BSP step_up at the last step of stairs
    (user happy-test surfaced the regression). v2 reverted v1 + added
    a no-op-if-unchanged guard inside `CollisionInfo.SetContactPlane`.
    **#96 PARTIALLY ADDRESSED — accepted as documented retail
    divergence.** The seed is load-bearing for `AdjustOffset`
    slope-projection on sub-step 1 which BSP step_up depends on.
    Matching retail would require deeper refactor (e.g. AdjustOffset
    fallback to body.ContactPlane). Guard is benign improvement;
    further #96 closure deferred.
  - **✓ SHIPPED — A6.P3 slice 3 — cell-resolver stickiness**
    (2026-05-22, commits `8898166` v1 + `3e140cf` v2). Added
    point-in stickiness check at top of `ResolveCellId`'s indoor
    branch. Cell-resolver ping-pong FULLY CLOSED (data: scen4 cellar
    capture shows 1 cell-transit vs 20+ pre-fix). **Outcomes:**
    Finding 3 (cell-resolver instability) closed. #90 workaround
    redundant (deferred A6.P4 removal). #97 phantom collisions
    hypothesis pending re-test (likely closed too). #98 cellar-up
    symptom PERSISTS but with NEW diagnosis (re-filed in #98 as BSP
    step-physics at cellar stair top — sloped step-face mis-handling,
    NOT cell-resolver).
  - **A6.P3 slice 4 (or A6.P4)? — BSP step-physics at cellar
    stair top (#98 new diagnosis)** (NEXT or DEFERRED). Investigate
    why step-down probe consumes all walk-interp at cellar stair top.
    Evidence: scen4 cottage_cellar_slice3v2 push-back trace. May
    require reading BSP step_up + step_down decomp + comparing to
    cellar stair geometry. Could be its own slice or merged into a
    broader A6.P4 cleanup phase.
  - Issue #95 (visibility blowup) NOT in A6.P3 scope — separate work
    surface.
- **A6.P4 — Remove workarounds + visual verification** (~1 day after
  P3). Revert #90 sphere-overlap stickiness in
  `PhysicsEngine.ResolveCellId`. Delete `Transition.TryFindIndoorWalkablePlane`
  + its caller in `FindEnvCollisions`. Visual verification at Holtburg
  inn + cellar + (if #95 is also fixed by then) a dungeon. The
  original A6.P4 plan named "Holtburg Sewer end-to-end" as the
  acceptance walk; since the sewer doesn't exist, the M1.5 demo
  scenario needs an alternative (see milestones doc).

#### Phase A7 — Indoor lighting fidelity (RenderDoc + retail-decomp driven)

**Hypothesis layers (less mapped than physics):**
- Per-cell environment-light tag association — indoor cells should
  inherit only their own env lights, not outdoor day-cycle.
- Light visibility culling — what lights actually contribute to each
  cell's render.
- Per-entity light direction transform — held-item-spotlight bug
  (#L-spotlight) is per-entity attribution gone wrong.
- Static-stab atmospheric inheritance (#81).

**Investigation methodology:** less existing infrastructure than
physics. Requires:
- New `[indoor-light]` probe (per-frame dump of active lights for the
  player's cell + each visible entity: position, color, attenuation,
  direction).
- RenderDoc frame capture at the same 9 scenarios as A6.
- Grep retail's `Render::lighting_*` family in
  `acclient_2013_pseudo_c.txt` to map per-cell light selection logic.

**Sub-pieces (slices):**
- **A7.L1 — Lighting probe spike** (~3–5 days). Build `[indoor-light]`
  probe. Capture baselines at all 9 scenarios. RenderDoc captures
  paired with each. Decomp study of retail's lighting selection.
- **A7.L2 — Analysis report** (~1–2 days). Likely surfaces 2–4
  distinct bugs across the lighting issues.
- **A7.L3 — Fix lighting paths** (~3–7 days). Wide variance because
  the surface area is unknown. Could touch indoor env-light parsing,
  `LightingHookSink`, WB rendering pipeline, shader uniforms.

**M1.5 acceptance criterion (shared by A6 + A7):** Walk Holtburg Sewer
end-to-end. Walls block (physics). Stairs work (physics). Items
block (physics). Lighting reads correctly throughout (lighting).
Cell transitions are smooth (physics). No regressions in M1 outdoor
behavior. Estimated 17–26 days focused work, 3–5 weeks calendar.

**Specs:** to be written 2026-05-20 (after milestone commit lands).

---

### Phase A — Foundation (in progress)

**Goal:** walk across 10+ landblocks without crashes, without hitches at landblock boundaries, and without framerate cratering.

**Sub-pieces:**
- **✓ SHIPPED — A.1 — Streaming landblock loader.** Runtime-configurable visible window (default 5×5, `ACDREAM_STREAM_RADIUS` env var override). Center follows the fly camera offline and the server-sent player position in live mode. Currently runs **synchronously** on the render thread — the original async-worker design hit DatCollection's lack of thread safety and was reverted for correctness. The Channel-based outbox API is preserved so async loading can return cleanly when `Phase A.3` introduces a thread-safe dat wrapper. Pending-spawn list in `GpuWorldState` parks live `CreateObject` events whose target landblock hasn't been streamed in yet and back-fills them when it arrives, so spawn-vs-streaming races are no longer racy at all. `MaxCompletionsPerFrame=4` spreads the 5×5 first-frame load over ~7 frames (~116ms) to avoid GPU OOM.
- **✓ SHIPPED — A.2 — Frustum culling.** Per-landblock AABB test (Gribb-Hartmann plane extraction + positive-vertex AABB test) in both `TerrainRenderer.Draw` and `StaticMeshRenderer.Draw`. Per-entity culling deferred. LOD deferred to Phase C. Performance overlay in window title shows FPS, frame time, visible/total landblock ratio, entity count, animated count. ~160fps uncapped at 5×5 radius.
- **✓ SHIPPED — A.3 — Background net receive thread.** Dedicated daemon thread continuously pulls raw UDP datagrams from the kernel buffer into a `Channel<byte[]>`. Render thread's `Tick()` drains the channel. All decode, fragment assembly, ISAAC crypto, event dispatch, and ack-sending remain on the render thread — minimal change that prevents packet drops during frame stalls. Thread starts after `EnterWorld()` completes; `PumpOnce()` during handshake still reads the socket directly.
- **A.4 — Async dat decoding.** Folded into the streaming worker — it's the worker's read path, not a separate subsystem. Called out here because regressions in dat caching could land on this surface.
- **✓ SHIPPED — A.5 — Two-tier streaming + horizon LOD.** Shipped 2026-05-10. See shipped table above for full description. Plan archived at `docs/superpowers/plans/2026-05-09-phase-a5-two-tier-streaming.md`.

**Acceptance:**
- Walk across 10+ landblocks in any direction, no crashes, no empty voids.
- Landblock-boundary crossings produce no visible hitch.
- Runtime window radius toggleable via environment variable.

**Detailed spec:** `docs/superpowers/specs/2026-04-11-foundation-phase-design.md`

---

### Phase B — Gameplay / interaction

**Goal:** actually play the game — walk the character on the server, click NPCs, pick up items, chat, basic combat loop.

**Sub-pieces:**
- **✓ SHIPPED — B.1 — Outbound ack pump.** Shipped as Phase 4.9 — per-packet ACK_SEQUENCE, not periodic. Server no longer drops idle clients.
- **✓ SHIPPED — B.2 — Player movement mode.** Tab-toggled WASD ground walking with collision-resolved outdoor terrain, walk/run/idle/turn-right animations, third-person chase camera, outbound MoveToState (0xF61C) + AutonomousPosition (0xF753) server messages, portal entry works. Outdoor→indoor transition disabled for MVP (CellSurface floor polygons too aggressive without portal-based detection). Minor polish remaining: strafe animation, turn-left animation. Spec: `docs/superpowers/specs/2026-04-12-player-movement-design.md`.
- **✓ SHIPPED — B.3 — Physics MVP resolver foundation.** Terrain contact, CellSurface prototype, streaming-populated collision inputs, and first `PhysicsEngine` resolver path. This shipped enough foundation for outdoor walking and early portal experiments, but it is not the complete retail collision system. Current conformance work lives under **Phase L.2 — Movement & Collision Conformance**. Spec history: `docs/superpowers/specs/2026-04-12-physics-collision-engine-design.md`.
- **B.4 — `Use` / `UseWithTarget` / `PickUp`.** Outbound interaction messages. Drives opening doors, looting, talking to vendors.
- **B.5 — Chat.** `SendTell`, `SendChat` outbound + receive/display inbound (display side depends on Phase D.1).

**References:**
- `references/ACE/Source/ACE.Server/Network/Handlers/MovementHandler.cs`
- `references/ACE/Source/ACE.Server/Network/Handlers/UseObjectHandler.cs`
- `references/holtburger/src/session/send.rs` for outbound packet-building patterns

**Acceptance:** walk on-server with your character, open a door, talk to an NPC, send a chat message and see the echo.

---

### Phase C — Polish / visuals

**Goal:** close the visible gaps that make the world read as "old / broken" compared to retail.

**Sub-pieces:**
- **✓ SHIPPED — C.1 — VFX / particle system + sky-pass refinements.** Retail-faithful `ParticleEmitterInfo` runtime + 13-type motion integrator port + `PhysicsScript` runner + instanced billboard renderer with material-derived blend + global back-to-front sort + AttachLocal live-parent follow. Sky-pass refinements: Translucent+ClipMap alpha-blend, raw-Additive fog-skip, per-keyframe SkyObjectReplace divide-by-100, sampler-object wrap selection (ported from WorldBuilder), gated post-scene Z-offset. Sky-PES disabled by default — named-retail decomp proves `GameSky` drops `pes_id`. **Portal swirls, chimney smoke, fireplace flames** still need a Phase C.1.5 follow-up to wire entity-attached emitters to retail effect IDs (the data layer is ready; only the wiring is deferred). Lands as merge `feat(vfx): Phase C.1 — PES particle renderer + post-review fixes` (`ec1bbb4`) + `refactor(sky): replace per-frame wrap-mode mutation with persistent samplers` (`3d21c13`).
- **C.1.5 — entity-attached PES wiring (sliced).** Three sub-slices wiring `PhysicsScript` / `DefaultScript` dispatch to the entity lifecycle so portals, chimneys, fireplaces, and sky effects animate per retail:
    - **✓ SHIPPED — C.1.5a (portals)** — 2026-05-11 (merge `88bda12`). `EntityScriptActivator` fires `Setup.DefaultScript` on every server-spawned `WorldEntity` via `PhysicsScriptRunner`. Visual-verified at Holtburg Town network portal. Surfaced known limitation as issue #56 (per-part transform handling) — addressed in C.1.5b. Plan archived at [`docs/superpowers/plans/2026-05-12-phase-c1.5a-portals.md`](../superpowers/plans/2026-05-12-phase-c1.5a-portals.md).
    - **✓ SHIPPED — C.1.5b (per-part transforms + EnvCell statics)** — 2026-05-12. Closes #56. `SetupPartTransforms.Compute` + `ParticleHookSink.SetEntityPartTransforms` + `SpawnFromHook` part-transform application — multi-emitter scripts now distribute across mesh parts. `EntityScriptActivator` `ServerGuid==0` guard relaxed (keys by `entity.Id` when ServerGuid is zero) + 4 new `GpuWorldState` fire-sites pick up dat-hydrated entities (EnvCell statics + exterior stabs) — fireplaces and chimneys now fire their `DefaultScript` automatically. Reality discovery during design: EnvCell statics are already hydrated as `WorldEntity` items by `BuildInteriorEntitiesForStreaming`, so no synthetic-ID scheme or separate walker was needed. Visual-verified at Holtburg portal + Inn fireplace + cottage chimney + spell cast. Plan archived at [`docs/superpowers/plans/2026-05-13-phase-c1.5b.md`](../superpowers/plans/2026-05-13-phase-c1.5b.md).
    - **PLANNED — C.1.5c (sky-PES dispatch chain).** Promoted from former issue #36 (2026-05-11 triage). Ports retail's persistent-emitter creation on celestial / sky objects + the PES timeline driver (`CallPESHook::Execute` → `CPhysicsObj::CallPES` → `create_particle_emitter`) that drives them ~150×/min. Decomp anchors + live-trace evidence + 6-step impl outline in closed issue [#36](../ISSUES.md#36). **Closes #2 (lightning), #28 (aurora), #29 (cloud thinness) when shipped.** Does NOT close #4 (sky horizon-glow fog) — that's shader work, not PES.
- **C.2 — Dynamic point lights.** Fireplaces and lamps need local lighting; small upgrade to the mesh shader to accumulate N (e.g., 4) nearest point lights per draw. Uniform-buffer or UBO-friendly layout.
- **C.3 — Palette range tuning.** Small per-range offset/length tweaks to match retail skin/hair/eye colors. Mostly diff and verify work, no architecture change.
- **C.4 — Double-sided translucent polys.** Edge case left by Phase 9.2: neg-side translucent polys are culled because cull is always BACK. Fix by tracking per-sub-mesh `CullMode` and flipping GL state per draw (or drawing twice with opposite cull). Minor.
- **C.5 — Shadow mapping (optional).** Deferred unless it becomes a bottleneck in screenshots — dynamic shadows are a known complexity trap.

**References:**
- `references/ACE/Source/ACE.DatLoader/FileTypes/PhysicsScript.cs` for the emitter schema
- `references/ACViewer/ACViewer/Physics/Particles/` for the visual model
- `references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/ParticleBatcher.cs` for the Silk.NET-flavored implementation

**Acceptance:** portals look like swirly gates, chimneys smoke, fireplaces burn, character skin matches retail screenshots.

---

### Phase D — UI / HUD + Sound

**Goal:** chat window, nameplates, inventory, and audio. Can run concurrently with Phase B or C because it doesn't touch gameplay/net/rendering surfaces.

> **Updated 2026-04-24 — staged backend strategy.** We split Phase D into
> two stages so game logic can be validated quickly without waiting for
> the full retail-look custom toolkit. See
> [`docs/plans/2026-04-24-ui-framework.md`](2026-04-24-ui-framework.md)
> for the full design. Short version:
>
> 1. **D.2a — ImGui as the short-term backend.** Wire up in days,
>    iterate game logic (chat-send, inventory actions, vitals HUD reading
>    real state) in weeks. Looks like a debugger; that's fine. *(Shipped
>    2026-04-25 on ImGui.NET + `Silk.NET.OpenGL.Extensions.ImGui` after a
>    day-one pivot away from Hexa.NET.ImGui; see shipped table.)*
> 2. **Stable `AcDream.UI.Abstractions` layer** — ViewModels + Commands +
>    `IPanel` / `IPanelRenderer` interfaces. Backend-agnostic. Plugin API
>    publishes against this layer and never sees ImGui.
> 3. **D.2b onward — custom retail-look backend** using dat assets (icons,
>    panels, fonts). Swap one panel at a time; ImGui stays forever as the
>    `ACDREAM_DEVTOOLS=1` overlay for packet trace / state dump / dat browser.
>
> Every sub-piece below (D.3 AcFont, D.4 dat sprites, D.5 core panels,
> D.6 HUD, D.7 cursors) ships against the abstraction layer, so it
> doesn't matter which backend is drawing when the port lands.

**Sub-pieces:**
- **D.1 — 2D ortho overlay + font rendering.** ✅ SHIPPED 2026-04-17 as the dev-facing debug overlay (StbTrueTypeSharp system-font atlas + `TextRenderer` + `DebugOverlay`).
- **✓ SHIPPED — D.2a — ImGui scaffold + `AcDream.UI.Abstractions` layer.** Shipped 2026-04-25. Wires ImGui as the short-term backend behind `ACDREAM_DEVTOOLS=1`. Defines `IPanel` / `IPanelHost` / `IPanelRenderer` / `ICommandBus` + the first ViewModel (`VitalsVM`) in the new `AcDream.UI.Abstractions` project. First real panel: `VitalsPanel` reading HP from `CombatState.GetHealthPercent`. **Backend pivoted Hexa.NET.ImGui → ImGui.NET + `Silk.NET.OpenGL.Extensions.ImGui` during integration** — Hexa's native OpenGL3 backend does its own GL function resolution via GLFW/SDL and crashed with `0xC0000005` in `ImGuiImplOpenGL3.InitNative` against Silk.NET (no GLFW/SDL present). The Silk.NET extension is purpose-built for this scenario and is the `ImGui.NET` mitigation path that `docs/plans/2026-04-24-ui-framework.md` already called out as a "one-morning operation". Stam/Mana return `float?` null in D.2a because absolute values need `LocalPlayerState` + `PlayerDescription (0x0013)` parsing (filed post-D.2a). 11 new `AcDream.UI.Abstractions.Tests` green.
- **D.2b — Custom retail-look backend.** Implements the same `IPanel` / `IPanelRenderer` contracts using a custom retained-mode toolkit sourced from retail dat assets. Requires D.2a shipped. Panels get reskinned one at a time; ImGui stays as the `ACDREAM_DEVTOOLS=1` overlay forever. The original 2026-04-17 scaffold research (`UiRoot` / `UiElement` / `UiPanel` / `UiHost` + retail event codes + focus / drag-drop state machine + `WorldMouseFallThrough`) is the implementation foundation here — see `docs/research/retail-ui/`.
- **D.3 — AcFont from portal.dat.** Replace stb_truetype system font with retail `Font` DBObjs (`0x40000000..0x40000FFF`) baked from `RenderSurface` source sheets — see research slice 03 §4. Preserves retail visual identity. **(D.2b dependency — needs the custom renderer.)**
- **D.4 — Dat sprites + 9-slice panel backgrounds.** Load `RenderSurface` (`0x06xxxxxx`) as GL textures; add `DrawSprite` to `UiRenderContext`. Enables retail panel art. **(D.2b dependency.)**
- **D.5 — Core panels.** Attributes (`chunk_00470000.c:FUN_0047ba70`), Skills (same), Paperdoll (`chunk_004A0000.c:FUN_004A5200`), Inventory, Spellbook (`chunk_004C0000.c`), Fellowship, Allegiance. Each uses the port sketches in slice 05. **(Targets `AcDream.UI.Abstractions` — ships with D.2a using ImGui-rendered widgets; reskinned by D.2b.)** The *chat* panel originally listed under D.5 shipped early in Phase I (I.4 input + I.7 combat translator superseded the chat-panel design here); this entry now covers Attributes / Skills / Paperdoll / Inventory / Spellbook / Fellowship / Allegiance only.
- **D.6 — HUD.** Vital orbs (scissor-rect partial fill, dat sprites `0x060013B2`), radar (`0x06001388` / `0x06004CC1`, 1.18× range factor), compass strip (scrolling U), target name plate, damage floaters, selection indicator. See slice 06. **(Targets `AcDream.UI.Abstractions` — ships with D.2a; reskinned by D.2b.)** Phase I.2 retired the StbTrueTypeSharp `DebugOverlay` but kept `TextRenderer` + `BitmapFont` alive specifically for D.6's world-space HUD elements (damage floaters, name plates) — they need raw GL text drawing that ImGui can't reach into the 3D scene.
- **D.7 — Cursor manager.** OS + dat-sourced custom cursors (`FUN_0043c1c0` GDI HCURSOR builder pattern from slice 03). **(D.2b dependency.)**
- ~~**D.8 — Sound.**~~ **Superseded — shipped as Phase E.2** (`SoundTable`/`Sound` dat decode, OpenAL 16-voice engine, per-entity 3D positional audio via `AudioHookSink`). Entry kept here for history; see the shipped table.

**Reference docs:** `docs/research/retail-ui/00-master-synthesis.md` + slices 01-06. Every AC-specific behavior has a decompiled FUN_ / DAT_ citation.

**Acceptance:** chat messages display in a retail-style panel, health/stamina/mana orbs fill correctly, attributes panel shows player stats, inventory opens with drag-drop working, and sound plays on hit/footstep.

---

### Phase E — "Feel alive" — motion hooks + audio + VFX

Unlocks the sensory layer: footsteps, swing whooshes, impact sparks,
spell auras. All three systems share animation-hook delivery as the
trigger source — motion hooks are the blocker.

Research backing: `docs/research/deepdives/00-master-synthesis.md` + R3 + R4 + R5.

- **E.1 — Motion-hook expansion.** Extend MotionInterpreter + AnimationSequencer to deliver all 27 AnimationHookType values (Sound=1, SoundTable=2, CreateParticle=18, AttackFrame=20, SoundTweaked=21, …) during frame advance. See `r03-motion-animation.md`.
- **E.2 — Audio engine.** `AudioEngine` via `Silk.NET.OpenAL`, inverse-square CPU falloff, 16-voice pool. SoundTable + Wave dat decoders. Motion-hook wiring for footsteps + attacks. See `r05-audio-sound.md`.
- **E.3 — Particle system.** `ParticleSystem` on Silk.NET GL (port WorldBuilder's `ParticleBatcher`). 13 motion-type integrators. `PhysicsScript` dispatcher tied to motion-hooks. See `r04-vfx-particles.md`.

**Acceptance:** walk around, footstep sounds per material; swing a weapon, hear whoosh; combat impact shows sparks + hit sound.

### Phase F — Fight + cast + gear

Core gameplay loop on top of Phase E.

Research: R1 + R2 + R6 + R8 + UI slices 04/05.

- **F.1 — GameEvent envelope dispatcher.** 94 sub-opcodes in `0xF7B0`. Zero handled today. Start with `PlayerDescription (0x0013)` + property-update events. See `r08-network-protocol-atlas.md`.
- **F.2 — Item + inventory model.** `ItemInstance` / `Container` / `PropertyBundle`. Appraise round-trip (`0x00C8` → `0x00C9`). Burden math. See `r06-items-inventory.md` + `src/AcDream.Core/Items/`.
- **F.3 — Combat math + damage flow.** Damage formula, per-body-part AL, crit, hit-chance sigmoid. Server broadcasts damage events; client displays + HP bar. See `r02-combat-system.md` + `src/AcDream.Core/Combat/`.
- **F.4 — Spell cast state machine.** `SpellCastStateMachine` + active buff tracking. Buffs + recalls first, projectile spells later. Fizzle sigmoid + mana conversion. See `r01-spell-system.md` + `src/AcDream.Core/Spells/`.
- **F.5 — Core panels.** Attributes / Skills / Paperdoll / Inventory / Spellbook — using the retail-ui framework from Phase D.2. See `05-panels.md` under retail-ui. **(Targets `AcDream.UI.Abstractions`; unblocked by D.2a — ships with ImGui widgets — and reskinned when D.2b lands.)**
  - **F.5a — Visible-at-login dev panels.** First deliverable on top of #13 (PD trailer parser shipped 2026-05-10): wire `PlayerDescriptionParser.Parsed.{Inventory, Equipped, Shortcuts, HotbarSpells, DesiredComps, Options1, Options2, SpellbookFilters}` and `ItemRepository.Items` into minimal ImGui dev panels under `ACDREAM_DEVTOOLS=1` so the parsed data is observable in-game without a real retail-skin panel. Establishes the binding pattern (`AcDream.UI.Abstractions` ViewModels → ImGui renderer) the eventual D.2b retail-skinned panels reuse. Acceptance: log in, open dev overlay, see your inventory list / hotbars / shortcuts / character-options bitfields populated from the live PD message. **Targets:** `src/AcDream.UI.Abstractions/` (ViewModels) + `src/AcDream.App/UI/ImGui/` (panels). Spec to brainstorm before code.

**Acceptance:** equip a weapon, swing at a monster, see damage numbers, buff yourself, recall to the lifestone.

### Phase G — World systems

Research: R9 + R12 + R13.

- **✓ SHIPPED — G.1 — Sky + weather + day-night.** Deterministic client-side from Portal Year time. Sky dome geometry + keyframe gradients + rain/snow particles. See `r12-weather-daynight.md`. Full data + visual stack shipped: Region dat loader, keyframe interp, WeatherSystem with 5-kind PDF + transitions + storm flashes, WorldSession→WorldTimeService sync via ConnectRequest+TimeSync, SkyRenderer with sky-object arcs + UV scroll, rain/snow billboard renderer, F7/F10 debug cycle keys.
- **✓ SHIPPED — G.2 — Dynamic lighting.** 8-light D3D-style fixed pipeline. Hard-cutoff at Range, no attenuation inside. Cell ambient. Shader UBO per frame. See `r13-dynamic-lighting.md`. SceneLightingUbo std140 at binding=1 feeds terrain + mesh + mesh_instanced + sky shaders. LightingHookSink auto-registers Setup.Lights at entity stream-in, flips IsLit on SetLightHook, unregisters on landblock unload.
- **Indoor portal-based cell tracking (follow-up to Indoor walking Phase 1 / issue #87).** Replace `PhysicsDataCache.TryFindContainingCell` AABB containment with retail's `CObjMaint::HandleObjectEnterCell` portal traversal. When the player crosses a cell portal boundary, `CellId` propagates through the `CEnvCell` portal connectivity graph. Prerequisite for wall collision from outside (#85) and the remaining #84 threshold symptom. PDB symbols and `acclient.h` `CCellStructure` refs are in place (see #87). **Unblocks G.3.**
- **G.3 — Dungeon streaming + portal space.** `EnvCellStreamer`, portal-visibility BFS, `PlayerTeleport (0xF751)` handling with `LoginComplete` re-send, "pink bubble" loading state. **Blocked on indoor portal-based cell tracking above** (and previously on L.2e) for trustworthy indoor/outdoor portal transit, adjacent-cell ownership, and building entry/exit collision boundaries. See `r09-dungeon-portal-space.md`.

**Acceptance:** walk outside at dusk, see the sky gradient + sun moving; enter a torch-lit dungeon via portal; leave back to daylight.

### Phase H — Social + progression

Research: R7 + R10 + R11 + UI slice 05.

- **✓ SHIPPED — H.1 — Chat window.** UI panel + all 6 wire opcodes (Channel, Tell, System, HearSpeech, HearRangedSpeech, TurbineChat). Wire layer + panel + outbound input + holtburger inbound parity + combat translator all shipped across I.1-I.7 on 2026-04-25. Targets `AcDream.UI.Abstractions`; will be reskinned when D.2b's custom retail-look toolkit lands.
- **H.2 — Allegiance.** Tree model + XP pass-up math + 5 allegiance chat channels + MOTD. See `r11-allegiance.md`.
- **H.3 — Emote scripts + quests + dialogs.** 122 EmoteType × 39 Trigger mini-VM. Contract tracker UI. NPC dialog rendered via chat with `<Tell:…>` markup. See `r10-quest-dialogs.md`.
- **H.4 — Character creation.** `0xE000002 CharGen` dat + 13 heritages + templates + appearance picker + preview renderer. See `r07-character-creation.md`.

**Acceptance:** create a character from scratch, talk to an NPC, get + complete a quest, gain XP that passes up to the patron.

### Phase I — UI consolidation + complete chat system

**Goal:** retire the dual UI stack (custom StbTrueTypeSharp DebugOverlay + ImGui)
in favour of ImGui hosting *all* dev/debug surfaces, then finish the chat
system end-to-end (input, slash commands, holtburger inbound parity, combat
translator). After this phase, dev tools live in one place and chat works
the way retail + holtburger expect.

**Sub-pieces (all ✓ SHIPPED 2026-04-25):**

- **✓ SHIPPED — I.1 — `IPanelRenderer` widget extension.** Adds `TextColored`, `Checkbox`, `Combo`, `InputTextSubmit`, `BeginTable` + ~9 more widget signatures to `IPanelRenderer` and matching `ImGuiPanelRenderer` impls. Foundation for I.2 (DebugPanel) and I.4 (chat input). Commit `b131514`.
- **✓ SHIPPED — I.2 — DebugPanel migration.** Replaces the 473-LOC StbTrueTypeSharp `DebugOverlay` with `AcDream.UI.Abstractions/Panels/Debug/DebugPanel` (collapsing-headers + diagnostics checkboxes + combat-event tail). `DebugOverlay.cs` deleted; `TextRenderer` + `BitmapFont` retained for the future world-space HUD (D.6 — damage floaters, name plates). Commit `56037a4`.
- **✓ SHIPPED — I.3 — `LiveCommandBus` + `WorldSession.Send{Talk,Tell,Channel}`.** Replaces `NullCommandBus.Instance` with a real handler-registry `ICommandBus`. New `SendChatCmd` record + `ChatChannelKind` enum + `ChannelResolver` legacy-id mapping (per holtburger). `WorldSession.SendTalk` / `SendTell` / `SendChannel` are 3-line wrappers around existing `ChatRequests.BuildTalk/Tell/ChatChannel`. Commit `8e6e5a0`.
- **✓ SHIPPED — I.4 — `ChatPanel` input field + slash commands.** Enter-to-submit input field on `ChatPanel`; `ChatInputParser` recognises `/say` `/t` `/tell` `/r` `/g` `/f` `/a` `/m` `/p` `/v` `/cv` `/lfg` `/trade` `/role` `/society` `/olthoi`; `ChatVM.LastIncomingTellSender` tracks for `/r` reply. `ImGui.WantCaptureKeyboard` already suppresses WASD on input focus. Commit `f14296c`.
- **✓ SHIPPED — I.5 — Holtburger inbound chat parity + Windows-1252.** `EmoteText (0x01E0)`, `SoulEmote (0x01E2)`, `ServerMessage (0xF7E0)`, `PlayerKilled (0x019E)` parsers + `WeenieError` routing through `GameEventWiring`. Global string codec switch from `Encoding.ASCII` to `Encoding.GetEncoding(1252)` so accented names round-trip per retail + holtburger. Commit `ff5ed9e`.
- **✓ SHIPPED — I.6 — TurbineChat codec + `ChatChannelInfo`.** Full `0xF7DE` codec with three payload variants (`EventSendToRoom`, `RequestSendToRoomById`, `Response`), UTF-16LE strings with variable-length prefix, `SetTurbineChatChannels (0x0295)` parser, unified `ChatChannelInfo` (Legacy + Turbine variants), `TurbineChatState`. **ACE doesn't host a TurbineChat server — codec is ready when retail-emulating servers exist.** Commit `ca968fc`.
- **✓ SHIPPED — I.7 — `CombatChatTranslator`.** Retail-faithful combat-text formatters into `ChatLog` ("You hit drudge for 50 slashing damage (87%)"). Subscribes to `CombatState`'s `DamageTaken` / `DamageDealtAccepted` / `EvadedIncoming` / `MissedOutgoing` / `AttackDone` / `KillLanded`; templates ported verbatim from holtburger `panels/chat.rs:221-308`. Commit `3d26c8e`.
- **✓ SHIPPED — I.8 — Docs alignment.** Roadmap (this file) + `docs/ISSUES.md` issues #14-#20 closed + `memory/project_chat_pipeline.md` crib + `MEMORY.md` index entry + `CLAUDE.md` UI strategy paragraph all updated to reflect Phase I shipped state. Commit `(this commit)`.

**Acceptance (verified 2026-04-25):**
- All ImGui dev panels open with `ACDREAM_DEVTOOLS=1`: VitalsPanel + ChatPanel + DebugPanel.
- Old `DebugOverlay.cs` is deleted; `TextRenderer` / `BitmapFont` still compile for D.6.
- `/say hello` in chat sends a Talk packet; local echo lands in the panel.
- Attacking a creature produces "You hit Drudge for 12 slashing damage (87%)" lines in chat.
- Accented character names round-trip through the wire correctly (CP1252).

**Memory crib:** `memory/project_chat_pipeline.md`.

---

### Phase K — Input architecture + retail bindings + Settings panel

**Goal:** retire the hardcoded WASD-style scheme; ship retail-faithful
bindings as the default; let users remap any action via an in-game
Settings panel; auto-enter player mode at login; replace mouse-X-yaw
with retail's MMB-hold mouse-look.

**Sub-pieces (all ✓ SHIPPED 2026-04-26):**

- **✓ SHIPPED — K.1a — Input architecture skeleton.** Action enum,
  multicast `InputDispatcher` with scope stack, `KeyChord` /
  `Binding` / `KeyBindings`, Silk.NET adapters, parallel to existing
  handlers — no behavior change. Commit `84512d3`.
- **✓ SHIPPED — K.1b — Cut handlers over to dispatcher.** Drop the
  legacy mouse-X-character-yaw path, fix `WantCaptureMouse` gating,
  single input path. Commit `256e962`.
- **✓ SHIPPED — K.1c — Retail-default keymap + JSON persistence.**
  ~149 bindings matching
  `docs/research/named-retail/retail-default.keymap.txt` + JSON
  load/save with merge-over-defaults migration at
  `%LOCALAPPDATA%\acdream\keybinds.json`. Acdream debug F-keys
  relocated to `Ctrl+F*` to avoid retail conflicts. Commit `da18910`.
- **✓ SHIPPED — K.2 — Login flow + MMB mouse-look + free-fly button.**
  Auto-enter player mode at login (one-shot guard reusing the existing
  Tab handler), MMB-hold mouse-look (retail's `CameraInstantMouseLook`
  — cursor-locked camera + character yaw drive together), `DebugPanel`
  "Toggle Free-Fly Mode" button, `Tab → ToggleChatEntry →
  ChatPanel.FocusInput()`. Commit `af74eac`.
- **✓ SHIPPED — K.3 — Settings panel + Keybindings UI + roadmap/issues
  update.** `SettingsPanel` with click-to-rebind UX (modal capture
  via `InputDispatcher.BeginCapture`, Esc cancels, conflict-detection-
  with-confirmation prompt, draft / Save / Cancel semantics), F11
  toggle + ImGui MainMenuBar entry, per-action / per-section /
  reset-all-defaults buttons. Roadmap + ISSUES + memory crib +
  CLAUDE.md updated in this commit. Commit `(this commit)`.

**Acceptance (verified 2026-04-26):**
- Login → spawn at the character's actual world position in chase
  camera (NOT Holtburg orbit). Tab not required.
- W = run forward; X = run back; A/D = turn; Z/C = strafe;
  Alt+A/D/Left/Right = strafe (alternate); Q = autorun; Space = jump;
  Shift = walk modifier; S = stop; Y/G/H/B = postures.
- Mouse alone does NOT drive character yaw. MMB-hold = cursor-locked
  camera + character yaw drive together. RMB and LMB are
  `SelectRight` / `SelectLeft` per retail.
- F11 / View → Settings opens panel. Click "Rebind" next to any
  action → press a key → if conflict, prompt. Save writes
  `%LOCALAPPDATA%\acdream\keybinds.json`. Restart preserves
  customizations.
- Mode-dependent combat keys (Insert/PgUp/Delete/End/PgDn) bound but
  dormant — light up in Phase L when `CombatState.CurrentMode` is
  wired (issue #L.3).

**Memory crib:** `memory/project_input_pipeline.md`.

---

### Phase L.1 — Animation system completion

**Status:** IN PROGRESS on `feature/animation-system-complete`.

**Goal:** complete the retail-faithful animation surface beyond the
locomotion/jump K-fix series: combat swings, spell casting, emotes, death,
item-use, NPC/monster special actions, remote observer parity, and the
remaining floating-point polish around style transitions, modifiers, action
queues, speed scaling, and PosFrame root motion.

**Plan of record:** `docs/plans/animation-system-audit.md`.

**Coupling to L.2:** L.1 owns animation/motion parity. L.2 owns collision,
contact truth, movement packets, and server-visible placement. They meet where
root motion or observer movement changes the predicted body path; any such
change must keep both phase plans in sync.

**Sub-pieces:**
- **L.1a — Audit & inventory.** Map retail named-decomp evidence, ACE
  cross-references, existing acdream hook points, and current gaps for each
  animation category. Output: `docs/plans/animation-system-audit.md`.
- **L.1b — Command router + motion-state cleanup.** Extract tested
  `SetCycle` vs `PlayAction` routing, add missing `MotionCommand` constants,
  and split death `Sanctuary` action from persistent `Dead` substate.
- **L.1c — Combat animation wiring.** Combat mode tracking, draw/sheath
  style transitions, attack swings by stance/power/height, hit reactions,
  evades/blocks/parries, and death handoff.
- **L.1d — Spell casting wiring.** Cast command classification, windup,
  release, fizzle/interruption, recoil, and school/effect distinctions.
- **L.1e — Emotes + postures.** Outbound slash emotes, inbound
  command-list emotes, and persistent sit/lie/kneel/sleep states.
- **L.1f — NPC/monster + item-use coverage.** Scripted gestures, monster
  special actions, potion/food/scroll/recall cycles, and remote parity.
- **L.1g — Polish + conformance.** Style-transition chain, durable
  modifiers/action queues, root-motion handling, speed scaling, and broad
  synthetic MotionTable tests.

**Acceptance:**
- `dotnet build` and `dotnet test` green at each commit.
- Test count grows by at least 30 with one representative cycle/action test
  per major animation category.
- Every AC-specific behavior cites named retail decomp or ACE/holtburger
  cross-reference evidence in code comments, tests, or commit notes.
- User visual sign-off for local and remote attack, spell, emote, death, and
  item-use animation parity before marking shipped.

---

### Phase L.2 — Movement & Collision Conformance

**Status:** ACTIVE.

**Goal:** make acdream's movement and collision behavior retail-faithful across
terrain, buildings, walls, roof edges, cell seams, portal boundaries, outbound
movement packets, and server correction. This is the holistic bucket for the
work previously scattered across B.3 physics follow-ups, L.1 motion coupling,
and G.3 dungeon/portal ownership.

**Plan of record:** `docs/plans/2026-04-29-movement-collision-conformance.md`.

**Current foundation:** `PhysicsEngine.ResolveWithTransition`,
`BSPQuery`, `TransitionTypes`, `PhysicsDataCache`, and
`ShadowObjectRegistry` exist and are active. They are partial retail ports and
diagnostic scaffolding, not yet the final collision system.

**Sub-lanes:**
- **L.2a — Truth & diagnostics.** Local placement/contact/cell logs, object-hit
  probes, correction-delta diagnostics, retail-observer capture workflow, and
  real-DAT fixture capture.
- **L.2b — Movement wire/contact authority.** Fix outbound contact truth,
  full-cell id handling, packet cadence, and routine server correction handling.
- **L.2c — Transition parity: edge/slide/neg-poly.** Port and test retail
  `edge_slide`, `cliff_slide`, `precipice_slide`, step-up/down slide, and
  `NegPolyHit` dispatch behavior.
- **L.2d — Shape fidelity: sphere/cylsphere/building objects.** Finish
  `CSphere` / `CCylSphere` parity, live-entity object shapes, building object
  collision identity, and `Setup.Radius` fallback audit.
- **L.2e — Cell ownership: outdoor seams, `CELLARRAY`, `cell_bsp`.** Update
  low outdoor cell id across 24m seams, port adjacent-cell checks, activate
  `cell_bsp`, and hand G.3 a trustworthy building/portal boundary model.
- **L.2f — Real-DAT and live retail-observer conformance.** Promote synthetic
  tests to real-world fixtures and verify local acdream view plus retail
  observer view. ACE accepting a position is a compatibility check, not proof
  of fine-grained retail collision parity.

**Acceptance:**
- A developer can trace the active movement path: input/motion -> body
  prediction -> `ResolveWithTransition` -> contact/cell result -> outbound
  packets -> server echo/correction.
- Buildings, edge-slide, wall-slide, cell seams, packet authority, and dungeon
  portal ownership each have an L.2 lane.
- Every AC-specific algorithm port cites named retail decomp, or a documented
  fallback when named retail lacks the body.
- `dotnet build` and `dotnet test` are green for each implementation slice.

---

### Phase M — Network Stack Conformance

**Status:** PLANNED.

**Goal:** replace the current happy-path `WorldSession` networking shape with a
proper AC client network stack that reaches functional parity with
`references/holtburger/` while preserving acdream's stricter packet checksum
verification and live ACE compatibility. This phase owns packet reliability,
ordered delivery, retransmission, ACK piggybacking, echo/keepalive, typed
message/action routing, diagnostics, and the migration of low-level network
responsibilities out of the render tick.

**Why now:** The Phase 4/A.3 stack is good enough for local ACE smoke testing:
login, ISAAC, checksums, per-packet ACKs, fragments, movement, chat, combat,
and object updates all work. It is not yet a complete client network runtime.
Compared with holtburger it lacks ordered S2C delivery, retransmit request
handling, outbound packet caching/retransmission, ACK piggybacking,
EchoRequest/EchoResponse handling, runtime ping/timeout policy, and a typed
protocol/action layer. These gaps will become expensive as movement, dungeons,
inventory, combat, and plugins depend on stable packet semantics.

**Plan of record:** Detailed design spec at
[`docs/superpowers/specs/2026-05-10-phase-m-network-stack-design.md`](../superpowers/specs/2026-05-10-phase-m-network-stack-design.md)
(supersedes the planned-but-never-written `2026-05-02-network-stack-conformance.md`
the original entry referenced). The spec defines: **Bar C** ("wireable on demand")
as the completeness target; a **three-layer architecture** (`INetTransport` /
`IReliableSession` / `IGameProtocol`) with `WorldSession` as a thin behavior
consumer on top; a **worktree-branch big-bang** migration model on
`claude/phase-m-network-stack` with weekly rebase cadence and single-merge ship;
per-sub-phase entry/exit gates with hour estimates; conformance test plan
(golden vectors + live capture replay + live ACE smoke); risk register; and a
**256-hour / ~6.4-week single-developer cost estimate** (4–6 weeks calendar
with subagent parallelization on M.1 and M.6). Treat holtburger as the
client-behavior oracle, ACE as server-outbound authority, named retail decomp
as wire-format ground truth.

**2026-05-10 update:** holtburger pulled to `629695a` (+237 commits since
last audit). First parity-pass at
[`docs/research/2026-05-10-holtburger-network-stack-study.md`](../research/2026-05-10-holtburger-network-stack-study.md);
formal opcode coverage matrix (M.1's main deliverable) under construction
at `docs/research/2026-05-10-phase-m-opcode-matrix.md` via parallel
class-by-class agent dispatch. Most relevant recent holtburger commits:
`99974cc` (session crate split + retransmit core), `403bc98` (port-switch
race), `336cbad` (turning + locomotion fix), `797aece` (disconnect
carries client_id). Six "Tier 1" quick-wins identified by the study
(originally tracked as M.0) are folded into M.3 / M.4 / M.6 per the
spec — they no longer ship as a separate sub-phase.

**Sub-lanes:** *(brief summary; the spec has full entry/exit criteria,
conformance gates, and hour estimates for each.)*
- **M.1 — Audit & opcode matrix.** Build the per-opcode coverage table
  citing holtburger / ACE / named retail / acdream-today / Phase M target.
  Status: parity-pass done; matrix construction in flight via per-class
  agent dispatch (transport flags + optional headers, GameMessages,
  GameEvents, GameActions). 16h.
- **M.2 — Layer extraction.** Split the low-level stack under `WorldSession`
  into testable components: `INetTransport`, `PacketCodec`,
  `ReliablePacketSession`, `FragmentSession`, `GameMessageSession`, and the
  high-level `WorldSession` behavior layer. Preserve existing public events and
  live-client call sites during the migration.
- **M.3 — Reliability core.** Port holtburger-style sequence tracking:
  last-delivered server sequence, duplicate/old-packet suppression,
  out-of-order buffering, missing-sequence detection, throttled
  `RequestRetransmit`, outbound packet cache, server-requested C2S retransmit,
  `RejectRetransmit` handling, and retransmission checksum recomputation.
- **M.4 — ACK and control-packet policy.** Replace standalone-only ACKs with
  queued ACK state, ACK piggybacking on normal outbound packets, standalone ACK
  flush when idle, and clean handling for ACK-only packets. Add EchoRequest /
  EchoResponse and idle ping/timeout behavior matching holtburger unless named
  retail proves a different cadence.
- **M.5 — Fragment and payload completeness.** Keep inbound multi-fragment
  assembly, add TTL/eviction diagnostics for orphaned partials, implement
  outbound multi-fragment splitting when payloads exceed the current single
  fragment limit, and verify fragment id/sequence/queue behavior against
  holtburger, ACE, and retail evidence.
- **M.6 — Typed protocol surface.** Introduce typed `GameMessage` and
  `GameAction` routing modeled on holtburger. Migrate current builders and
  parsers first: login complete, DDD, movement, chat/TurbineChat, combat,
  spellcast, item/appraise, object/update/motion/position, and teleport.
  Unknown opcodes must remain observable and non-fatal.
- **M.7 — Runtime loop and diagnostics.** Move decode/order/reassembly/control
  handling out of the render tick into a dedicated network runtime that emits
  clean session events through channels. Add byte counters, packet trace hooks,
  optional capture/replay fixtures, idle/disconnect state, and dev-panel
  diagnostics suitable for packet debugging.
- **M.8 — Conformance tests and live validation.** Add deterministic unit tests
  for checksum, ISAAC key consumption, optional headers, ordering, retransmit,
  ACK piggybacking, echo, fragments, typed actions, and login flow. Add
  replay/capture tests from holtburger-style fixtures where possible. Finish
  with `dotnet build`, `dotnet test`, local ACE login, chat, movement, combat
  action smoke test, reconnect test, and user visual sign-off where networking
  affects the running client.

**Non-goals:**
- Do not reimplement ACE server behavior.
- Do not replace acdream's stricter inbound checksum verification unless named
  retail proves it is wrong.
- Do not rewrite renderer/gameplay systems as part of this phase; migrate
  network call sites through compatibility adapters first.
- Do not remove unknown-message visibility. Plugins and devtools need packet
  trace surfaces even when parsers are incomplete.

**Acceptance:**
- acdream has a layered, testable network stack rather than one monolithic
  `WorldSession`.
- Every holtburger session capability has an acdream equivalent, an explicit
  test, or a documented intentional divergence with retail/ACE evidence.
- Packet ordering, retransmit, outbound cache, ACK piggybacking, echo/keepalive,
  fragment assembly/splitting, and typed message/action routing are covered by
  tests.
- Live ACE loop succeeds: login, enter world, movement, chat, combat action,
  teleport/reconnect, and clean logout.
- `dotnet build` and `dotnet test` are green for every implementation slice.

---

### Phase N — WorldBuilder Rendering Migration

**Goal:** Stop re-porting AC-specific rendering / dat-handling
algorithms. Depend on a fork of `Chorizite/WorldBuilder` (MIT) for
terrain, scenery, static objects, EnvCells, portals, sky, particles,
texture decoding, mesh extraction, and visibility. Acdream keeps its
own network, physics, animation, motion, UI, plugin, audio, chat
layers (those aren't in WB).

**Why now (2026-05-08):** the scenery edge-vertex bug at landblock
`0xA9B1` was the third subtle porting bug in a quarter (after the
triangle-Z bug and the hover-over-terrain bug). Even when our code
looked byte-identical to WB's, our output diverged. WB renders the
world correctly; the cost of "we re-port retail algorithms" is now
higher than "we depend on WB's tested port."

**Design + inventory:**

- `docs/architecture/worldbuilder-inventory.md` — full taxonomy of
  what WB has and what we keep porting ourselves.
- `docs/superpowers/specs/2026-05-08-phase-n-worldbuilder-migration-design.md` —
  parent design doc.
- `docs/superpowers/specs/2026-05-08-phase-n1-scenery-via-wb-helpers-design.md` —
  N.1 detailed design.

**Integration model:** fork at
`https://github.com/eriknihlen/WorldBuilder` (already created), git
submodule replacing `references/WorldBuilder/` snapshot, project
references in our solution. Long-lived `acdream` branch in the fork
for our deletions/additions; merge upstream `master` periodically.

**Lessons from N.1 (apply to N.2-N.10):**

1. **Per-helper conformance tests work.** The N.1 conformance test caught a
   ~180° rotation bug in our retail port that had been silently wrong
   forever. Write the conformance test BEFORE the substitution in each
   sub-phase.

2. **ACME ≠ Chorizite/WorldBuilder.** ACME is a downstream fork of WB with
   additional retail-faithful filters that upstream WB (our submodule)
   doesn't have. When a visual discrepancy appears, check ACME's source
   (`references/WorldBuilder-ACME-Edition/`) for delta filters BEFORE
   investigating retail decomp directly. ACME's deltas tend to come as
   coherent units — porting one filter without its companions can
   over-suppress.

3. **"Whackamole" is the warning sign.** If a phase generates 3+ visual
   regressions on default-on, stop, accept the cosmetic deltas as
   ISSUES.md entries, ship the migration. Bugs we leave behind are
   debuggable; bugs we never ship are forgotten.

4. **Subagent-driven execution holds up at this scope.** Fresh subagent
   per task with the full task text inline keeps quality high without
   polluting the controller's context. Each task should be self-contained
   enough that a subagent without session history can complete it.

**Sub-phases (strangler-fig with feature flags):**

- **✓ SHIPPED — N.0 — Setup.** Shipped 2026-05-08 (commit `c8782c9`).
  WorldBuilder fork at `github.com/eriknihlen/WorldBuilder.git` registered
  as git submodule at `references/WorldBuilder/` tracking the `acdream`
  branch. `AcDream.Core.csproj` references `WorldBuilder.Shared` +
  `Chorizite.OpenGLSDLBackend`. Build green, all 28 scenery/terrain tests
  passing.
- **✓ SHIPPED — N.1 — Scenery algorithm calls.** Shipped 2026-05-08.
  Replaced `IsOnRoad` / `DisplaceObject` / slope-normal calc / rotation /
  scale inside `SceneryGenerator.Generate()` with calls to WB's
  `SceneryHelpers` + `TerrainUtils`. Adapter `WbSceneryAdapter` produces
  `TerrainEntry[]`. Visual verification at Holtburg confirmed Issue #49's
  previously missing edge-vertex trees still visible after the migration;
  rotation bug fixed (our retail port's `yawDeg = -(450-degrees)%360`
  formula was ~180° off from retail's actual `Frame::set_heading` atan2
  round-trip). One known cosmetic difference filed in ISSUES.md
  (road-edge tree at landblock 0xA9B1).
- **N.2 — Terrain math helpers.** ⚠️ **Blocked on N.5 — do not attempt
  in isolation.** Originally scoped as a 1-2 day low-risk substitution
  of `TerrainSurface.SampleZ` / `SampleSurface` / `SampleSurfacePolygon`
  with WB's `TerrainUtils.GetHeight` / `GetNormal`. Audit during N.3
  follow-up uncovered that **WB's `CalculateSplitDirection` uses a
  different formula than retail's `FSplitNESW`** (the AC2D-cited
  polynomial `0x0CCAC033` / `0x421BE3BD` / `0x6C1AC587` / `0x519B8F25`
  that our visual terrain mesh and physics already share). The
  formulas pick different cell-diagonals on disputed cells, producing
  up to ~2m Z divergence at the same world position. Substituting
  physics-side alone would un-sync physics from the still-ours visual
  mesh — exactly the triangle-Z hover bug class. N.1's conformance
  test proved WB's `GetNormal` is good enough for slope-filtering
  (boolean walkable check) but NOT that WB's height formula matches
  retail. Resolution: fold this work into **N.5** when the visual
  mesh switches to WB's renderer in lockstep with physics. Until
  then, leave `TerrainSurface` alone. See ISSUE #51.
- **✓ SHIPPED — N.3 — Texture decoding.** Shipped 2026-05-08. `SurfaceDecoder`
  now delegates INDEX16 / P8 / A8R8G8B8 / R8G8B8 / A8 to WB's
  `TextureHelpers.Fill*`. The A8 divergence (our old code did R=G=B=A=val
  always; WB splits additive vs non-additive) was resolved by threading an
  `isAdditive` parameter through `DecodeRenderSurface`: terrain alpha masks
  pass `isAdditive: true` (matches our prior behavior, preserves the
  shader's `.r` blend-weight read), entity surfaces pass
  `surface.Type.HasFlag(SurfaceType.Additive)`. Bonus: R5G6B5 + A4R4G4B4
  formats now decode (previously fell to magenta). X8R8G8B8, DXT1/3/5, and
  SolidColor remain ours (no WB equivalent). **9 conformance tests prove
  byte-identical equivalence per format** before substitution; updated
  `SurfaceDecoderTests` to match the new A8 split semantics. Visual
  verification at Holtburg passed 2026-05-08 — no texture regressions.
- **✓ SHIPPED — N.4 — Rendering pipeline foundation.** Shipped 2026-05-08.
  WB's `ObjectMeshManager` is integrated as the production mesh pipeline
  behind `ACDREAM_USE_WB_FOUNDATION=1` (default-on). The integration is
  three pieces: `WbMeshAdapter` (single seam owning the WB pipeline,
  drains the staged-upload queue per frame, populates
  `AcSurfaceMetadataTable` for translucency / luminosity / fog),
  `WbDrawDispatcher` (production draw path — groups all visible
  (entity, batch) pairs, uploads matrices in a single `glBufferData`,
  fires one `glDrawElementsInstancedBaseVertexBaseInstance` per group
  with `BaseInstance` slicing the shared instance VBO), and the
  `LandblockSpawnAdapter` + `EntitySpawnAdapter` bridge that wires our
  streaming loader to WB's `IncrementRefCount` / `PrepareMeshDataAsync`
  lifecycle (atlas tier vs per-instance customized).
  Issue #47 (close-detail mesh) preserved; sky pass structurally
  independent of the WB foundation. Perf wins shipped as part of N.4:
  per-entity AABB frustum cull, opaque front-to-back sort, palette-hash
  memoization. Legacy `InstancedMeshRenderer` retained as flag-off
  fallback until N.6 fully retires it. Plan archived at
  `docs/superpowers/plans/2026-05-08-phase-n4-rendering-foundation.md`.
- **✓ SHIPPED — N.5 — Modern rendering path.** Shipped 2026-05-08.
  **Rebranded from "Terrain rendering" 2026-05-08 after N.4 perf
  review.** Lifted `WbDrawDispatcher` onto bindless textures
  (`GL_ARB_bindless_texture`) + `glMultiDrawElementsIndirect`. Per-frame
  entity rendering: 3 SSBO uploads (instance matrices @ binding=0, batch
  data @ binding=1, indirect commands) + 2 indirect calls (opaque +
  transparent). ~12-15 GL calls per frame regardless of group count, down
  from hundreds-of-per-group in N.4. CPU dispatcher: 1.23 ms/frame median
  at Holtburg (1662 groups, ~810 fps). All textures on the modern path use
  1-layer `Texture2DArray` + `sampler2DArray`; legacy callers retain
  `Texture2D` via the parallel `TextureCache` path until N.6 retires them.
  Three gotchas in memory (`project_phase_n5_state.md`): texture target
  lock-in, bindless Dispose two-phase order, GL_TIME_ELAPSED double-
  buffering. Plan archived at
  `docs/superpowers/plans/2026-05-08-phase-n5-modern-rendering.md`.
- **✓ SHIPPED — N.5b — Terrain on the modern rendering path.** Shipped
  2026-05-09. **Path C** (mirror WB's `TerrainRenderManager` pattern but
  consume `LandblockMesh.Build` for retail-formula compliance). Path A
  (substitute WB's `CalculateSplitDirection`) killed during pre-implementation
  divergence test: WB's formula disagrees with retail's `FSplitNESW`
  (addr `00531d10`) on **49.98%** of cells across `tests/AcDream.Core.Tests/Terrain/SplitFormulaDivergenceTest.cs`'s
  sweep — wholly incompatible with our shared physics + visual mesh.
  Path B (fork-patch WB to use retail's formula) rejected for permanent
  maintenance burden. Path C ships the architectural pattern (single
  global VBO/EBO + slot allocator + bindless atlas + `glMultiDrawElementsIndirect`)
  while keeping retail's formula via `LandblockMesh.Build` →
  `TerrainBlending.CalculateSplitDirection`. `TerrainModernRenderer` +
  `terrain_modern.vert/.frag` shipped, `TerrainChunkRenderer` +
  `TerrainRenderer` + legacy `terrain.vert/.frag` deleted in T9.
  Closes ISSUE #51. **Perf reality check:** at radius=5 in Holtburg,
  modern is ~4× SLOWER on CPU than legacy was (6.4 µs vs 1.5 µs median;
  legacy collapsed radius=5's visible LBs into one `glDrawElements`
  via 16×16-LB chunking). Architectural wins (zero `glBindTexture`/frame,
  constant-cost dispatch as A.5 raises radius, per-LB frustum cull)
  manifest at higher radius. Spec acceptance criterion #5 was wrong;
  amended via `docs/plans/2026-05-09-phase-n5b-perf-baseline.md`. Plan
  archived at `docs/superpowers/plans/2026-05-09-phase-n5b-terrain-modern.md`.
- **✓ SHIPPED — N.6 slice 1 — GPU timing fix + radius=12 perf baseline.** Shipped 2026-05-11.
  Fixed the gpu_us double-buffering bug in `WbDrawDispatcher` (ring-of-3
  query slots, read-before-overwrite, vendor-neutral across AMD/NVIDIA/Intel
  desktop GL). Added env-gated surface-format histogram dump in `TextureCache`
  for atlas-opportunity audit. Captured authoritative baseline at Holtburg
  radii 4 / 8 / 12 (standstill + walking) with the now-working `gpu_us`
  diagnostic. Plan + spec at `docs/superpowers/{specs,plans}/2026-05-11-phase-n6-slice1-*.md`.
  Baseline numbers + next-phase recommendation at
  [docs/plans/2026-05-11-phase-n6-perf-baseline.md](2026-05-11-phase-n6-perf-baseline.md).
- **N.6 slice 2 — Perf polish cleanup.** **Planned — deferred until after C.1.5
  (PES emitter wiring) per the baseline doc's recommendation.** Builds on
  slice 1's measurement. Scope: retire the legacy `Texture2D`/`sampler2D` path
  in `TextureCache` (currently kept for Sky + Debug + particle paths now that
  Terrain has migrated); delete orphan `mesh.frag` (verify zero callers post-N.5
  amendment); decide bindless-everywhere vs legacy-island for the remaining
  `sampler2D` consumers. **Dropped from slice 2 scope per baseline data**:
  WB atlas adoption and persistent-mapped buffers — both target GPU/sampler
  throughput but the baseline shows GPU is wildly under-utilized (max gpu_us
  p95 ~600 µs vs 16,600 µs frame budget). Slice 2 reduces to a ~1-day cleanup.
  Plan + spec written when work begins. **Estimate: ~1 day once C.1.5 lands.**
- **N.7 — EnvCells / dungeons.** Replace EnvCell rendering with WB's
  `EnvCellRenderManager` + `PortalRenderManager` on top of N.4's
  foundation. **Estimate: 1-2 weeks** (was 2-3 — naturally smaller now
  that infrastructure is shared).
- **N.8 — Sky + particles.** Replace sky rendering + particle pipeline
  (#36 / C.1 work) with WB's `SkyboxRenderManager` +
  `ParticleEmitterRenderer`. **Estimate: ~1 week** (was 1.5-2 — C.1
  already shipped most of this; N.8 is glue + sampler-object reuse).
- **N.9 — Visibility / culling.** Replace `CellVisibility` +
  `FrustumCuller` with WB's `VisibilityManager`. **Estimate: ~1 week**
  (was 3-5 days, slight bump for streaming-loader interaction).
- **N.10 — GL infrastructure consolidation (optional).** Replace our
  `Shader` / `TextureCache` / `SamplerCache` plumbing with WB's
  `ManagedGL*` wrappers + `OpenGLGraphicsDevice`. **Largely subsumed by
  N.4** — `OpenGLGraphicsDevice` arrives as the host of `ObjectMeshManager`
  and atlas. May not need a dedicated phase; revisit after N.6.

**Estimated calendar:** **2.5-3 months / 9-13 engineering weeks for
N.4-N.9 (N.10 likely subsumed; N.2 folded into N.5; N.3 shipped).**
Revised 2026-05-08 after recognizing N.4-N.6 are one rendering rebuild
on shared infrastructure rather than three independent substitutions.

**Each sub-phase:**
- Ships behind `ACDREAM_USE_WB_<NAME>=1` flag.
- Has its own conformance test (side-by-side against existing path).
- Visual verification before flag becomes default-on.
- Old code deleted after default-on lands cleanly.

**N.2-N.10 detailed specs are NOT yet written** — each gets its own
brainstorm + spec when we reach it.

**Acceptance:**
- All 10 sub-phases shipped, feature flags removed, old rendering code
  paths deleted.
- Visual verification at Holtburg + Foundry statue + a representative
  dungeon shows no regression vs Phase C.1.
- WB upstream merges into our `acdream` branch are clean (or have
  documented conflict-resolution patterns).

---

### Phase J — Long-tail (deferred / low-priority)

Not detailed here; each gets its own brainstorm when it becomes relevant.

- Player character full rig (held weapons, spell effects, death/revive animation)
- Group/fellowship UI
- Trade window (multi-player confirms)
- Salvage + tinker UI
- House ownership + hooks
- Society UI
- GM visible tools (we explicitly don't build GM powers; but DEV-mode tools like teleport anywhere are useful)

---

### Phase R — Retail research infrastructure

**Goal:** sustainable, scalable access to retail-client decompilation —
named symbols, struct layouts, wire schemas — so every future port is
a 5-second grep instead of 30-minute archaeology.

**Sub-pieces:**

- **✓ SHIPPED — R.1 — Named-retail corpus committed.** Shipped 2026-04-25
  (commit `a9a01d8`). `docs/research/named-retail/{acclient_2013_pseudo_c.txt,
  acclient.h, acclient.c}` + `docs/research/data/spells.csv` + vendored
  `references/actestclient/`. 1.4 M lines of named pseudo-C (99.6% function
  naming) + 70K lines of verbatim retail headers + 3,956 spells with `Family`
  for buff stacking + machine-readable wire schema in `messages.xml`.
- **✓ SHIPPED — R.2 — pdb-extract tool + JSON sidecars.** Shipped 2026-04-25
  (commit `69d884a`). `tools/pdb-extract/pdb_extract.py` reads
  `refs/acclient.pdb` (Sept 2013 EoR build) and emits `symbols.json`
  (18,366 named functions) + `types.json` (5,371 named struct types) to
  `docs/research/named-retail/`. Pure Python, no deps, runs in <1 s.
- **✓ SHIPPED — R.3 — actestclient vendored.** Shipped 2026-04-25 alongside
  R.1. `references/actestclient/` (covered by `references/` gitignore)
  includes the canonical `messages.xml` AC-protocol wire schema.

**Acceptance:** Step 0 of `CLAUDE.md`'s development workflow points at
`named-retail/` first; subsequent issue closures (#6 / #7 / #9 / #11)
all consume this foundation.

**Effects on other phases:** Issue closures unblocked by Phase R land
under their existing letter phases (#6 enchantment buffs → Phase D /
F.5 maintenance; #7 PlayerDescription trailer → Phase H.1 / F.2
maintenance). The PDB symbols + headers also accelerate any future
port in any phase — no separate listing here.

---

## Cross-cutting work tracked in parallel

- **Test coverage.** Each phase lands with unit + integration tests in `tests/`. Current count: 98 Core + 96 Core.Net = 194. Keep the ratio as new phases land.
- **Memory files.** Project state under `memory/project_phase_*_state.md` is updated when a phase ships. `MEMORY.md` is the index.
- **`CLAUDE.md` discipline.** Check all four references (ACE, ACViewer, WorldBuilder, Chorizite) before committing to an approach. WorldBuilder is the closest stack match and should be checked first.

---

## Explicitly out of scope

- **Server emulation** — we use ACE for server, never reimplement.
- **Account creation** — direct user to ACE tooling.
- **Anti-cheat / GM tools / live-ops** — irrelevant for personal use.
- **Cross-platform support** — Windows-only; the dat path assumptions depend on retail Windows install layout. Silk.NET is cross-platform but we don't promise.
- **Custom game content** — this is a client for existing AC data, not a toolchain.

---

## "When will my specific complaint be fixed?" — quick lookup

| Observation | Phase |
|---|---|
| Drudge statue in wrong pose | **6.3 FIXED** ✓ |
| Characters in T-pose / wrong idle | **6.1 FIXED** ✓ |
| No breathing on NPCs | **6.4 + sentinel fix FIXED** ✓ |
| Lifestone crystal has one side missing | **9.2 FIXED** ✓ |
| Ground floor flickering with terrain | **7.1 FIXED** ✓ |
| Houses missing second floors / walls | **7.1 FIXED** ✓ (interior mesh landed) |
| Character clothing missing / wrong | **5 FIXED** ✓ |
| Statue wrong color / wrong scale | **5 FIXED** ✓ |
| Holtburg sign half-buried | **5 FIXED** ✓ |
| Can't walk past the loaded 3×3 window | **A.1 FIXED** ✓ (5×5 default, `ACDREAM_STREAM_RADIUS` to tune) |
| Frame hitch crossing landblock boundary | **Phase A.3** (synchronous loader for now; async returns when DatCollection is thread-safe) |
| Walking around doesn't move me on the server | **Phase B.2/B.3 FIXED** ✓ for coarse server movement; fine retail collision parity is **Phase L.2** |
| Packet loss / out-of-order UDP causes stale or missing world updates | **Phase M.3** |
| Server asks for retransmit and client doesn't resend cached packets | **Phase M.3** |
| ACKs are standalone-only instead of piggybacked like a full client | **Phase M.4** |
| Echo / idle keepalive / reconnect behavior is incomplete | **Phase M.4 + M.7** |
| Large outbound game messages exceed the current single-fragment path | **Phase M.5** |
| Network protocol coverage is spread across ad-hoc builders/parsers | **Phase M.6** |
| Sliding along buildings / walls feels wrong | **Phase L.2c + L.2d** |
| Roof edge / cliff / precipice blocks or slides wrong | **Phase L.2c** |
| Crossing outdoor cell seams reports the wrong cell | **Phase L.2e** |
| Can't talk to NPCs | **Phase H.3** (emote scripts + dialogs) |
| Can't open a door | **Phase F** (object-use action) |
| Portals render as a rotating black disk | **Phase E.3** (particle system) |
| Chimneys have no smoke | **Phase E.3** |
| Houses have no fireplace fire | **Phase E.3** |
| No fireplace / torch lighting | **Phase G.2** (shipped; Setup.Lights auto-register, 8-light cap with hard-cutoff) |
| Skin/hair color slightly off | **Phase C.3** |
| No chat window | **Phase H.1 / I.1-I.7 FIXED** ✓ |
| Chat? You can finally type and send messages | **I.4 FIXED** ✓ (shipped 2026-04-25) |
| Combat doesn't show in the chat log | **I.7 FIXED** ✓ |
| Accented character names show as `?` or garbled | **I.5 FIXED** ✓ (Windows-1252 codec) |
| No sound | **Phase E.2** |
| Dungeons / foundry interior missing | **Phase G.3** after **L.2e** cell/building ownership |
| Can't fight monsters | **Phase F.3** (combat math + damage) |
| Can't cast spells | **Phase F.4** |
| No inventory panel | **Phase F.2 + F.5** |
| No character creation — must use ACE admin | **Phase H.4** |
| Sky is a flat color | **Phase G.1** (shipped; F7 cycles time, F10 cycles weather) |
| Can't join allegiance | **Phase H.2** |
| No quest tracker | **Phase H.3** |

If you see something not on this list, add it here and assign a phase.
