Restore the named-retail object update order across local, remote, static, projectile, animation, shadow, teleport, and effect lifetimes. Separate authoritative root commits from spatial rebucketing, preserve per-owner hook/FIFO ordering, and remove update-path allocations with exact lifecycle and residency gates. Add deterministic conformance, adversarial lifetime, GUID-reuse, pending-cell, quaternion, timestamp, and allocation coverage. Release build is warning-free and all 6,446 tests pass with five intentional skips; retail, architecture, and adversarial reviews are clean. Co-authored-by: OpenAI Codex <codex@openai.com>
181 KiB
acdream — strategic roadmap
Status: Living document. Updated 2026-07-17. M3 active; automated implementation complete, visual gates pending. M2's connected demo loop is complete. M3 now has the retail cast-intent/component/target lifecycle, exact live enchantment wire state, scoped Magic input, retained spell bar, spellbook, component book, positive/negative effects panels with authored rows and selected-spell details, the complete seven-control gameplay indicator strip and its Character Information/Link/Vitae detail pages, and shared gmPanelUI geometry/switching over the Step 9 projectile/PhysicsScript/particle/portal foundation. The remaining M3 work is the connected single-client magic-UI/casting gate and final two-client portal-out/materialization observer gate.
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.
Current program: M3 — Cast a spell
Outcome: Cast a projectile spell at a hostile target, see the complete turn/windup/release/projectile/impact lifecycle, self-cast a buff and see its active enchantment, then recall through the DAT-driven portal presentation.
Current gate order:
- Done (automated): named-retail F.4/L.1d reconciliation, full live enchantment messages, account/focus-aware component formula preflight, retail target eligibility, authoritative cast request/completion ownership, and the complete 6,266-record end-of-retail DAT spell catalog. The former 3,956-row CSV is no longer a production dependency.
- Done (automated): authored Magic combat page, eight favorite tabs,
equipped caster endowment, scoped retail keys, spellbook/component book,
positive/negative active-enchantment panels, the seven-control link/effect/
Vitae/burden/mini-game/session indicator strip, and the retail
one-active-main-panel lifecycle. Link Status, Helpful/Harmful, Vitae, and
Character Information, and the inactive Mini Game shell now occupy that same
canonical main-panel size and position; Link RTT and Vitae recovery XP are
live. Effects now use the exact icon/name/time row template, selection state,
and
SELECT A SPELL/description field; burden opens Character Information. Connected-gate correction now also pins type-12 spellbook tabs, retail zero text margins, andPassToChildrenClosed/Open chrome through real-DAT fixtures. - Pending user visual gate: connected projectile/self-buff casts plus the complete retained magic UI interaction checklist.
- Pending user visual gate: two-client portal-out/materialization observer presentation before M3 closes, including #218's finished Hidden silhouette, fully rendered destination reveal, and no indoor stop-time position snap.
Opportunistic UI/physics fixes are no longer the work-order driver. They enter the active slice only when they block this demo or represent a severe regression.
Historical program: Phase W — Unified Cell Graph (UCG)
[SUPERSEDED — G.3 dungeon work shipped; M1.5 next step = #138 round-trip
gate + #145-residual. See the shipped table + docs/plans/2026-05-12-milestones.md
M1.5 section for current status. This section's W1/W2a history below is kept
for record.]
Pivot (2026-06-02). The render-pipeline reset stalled because the indoor "world from
below" is a cell-membership disagreement between the render-side CellVisibility and
the physics-side ResolveCellId — not any single draw gate (pixel-grounded evidence:
docs/research/2026-06-02-render-cell-membership-evidence.md).
We committed to a full migration onto one retail CObjCell cell graph shared by
physics + collision + render + streaming. Supersedes the render-only "Phase U" framing
and the abandoned A8 two-pipe (#103). Five verify-each stages on branch
claude/thirsty-goldberg-51bb9b:
| Stage | What | Status |
|---|---|---|
| W1 | ObjCell scaffold — Core ObjCell/EnvCell/LandCell/CellPortal/CellGraph built alongside the legacy systems, consumed by nobody (zero behavior change). |
Shipped 2026-06-02 (9cb1571→f2663b7; 22 tests; Opus-reviewed) |
| W2 | One membership — player curr_cell via retail find_cell_list + change_cell + doorway hysteresis; collapses ResolveCellId + FindCameraCell into one answer. First behavior-changing stage. |
W2a shipped + visually verified 2026-06-02 (0e27a6c+02acac5: render reads physics CurrCell; the indoor "world from below" is FIXED — cellar/stairs seal walls+floor). W2b next (doorway hysteresis — ping-pong 0170↔0031 confirmed). Baseline+handoff: docs/research/2026-06-02-phase-w-w2a-shipped-baseline-handoff.md |
| W3 | Render on the graph — PView walk + one gate for terrain/shells/entities. The visible M1.5 indoor fix. | Planned |
| W4 | Collision on the graph — physics queries the same ObjCells; retire parallel CellPhysics. |
Planned |
| W5 | Streaming → ObjCells — terrain as LandCell; the frozen-streaming rewrite. |
Planned |
W1 spec: docs/superpowers/specs/2026-06-02-unified-cell-graph-stage1-design.md ·
W1 plan: docs/superpowers/plans/2026-06-02-unified-cell-graph-stage1.md.
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 / KillLanded; AttackDone is control-only and deliberately silent. |
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. 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 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. Plan: docs/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. Handoff: docs/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. Handoff: docs/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. Handoff: docs/research/2026-05-14-b5-shipped-handoff.md. |
Live ✓ |
| B.5+ external-container looting | 2026-07-17. Retail ClientUISystem::groundObject lifetime plus gmExternalContainerUI LayoutDesc 0x21000008: authoritative root/nested ViewContents, root-replacement NoLongerViewingContents, authored close/range Use → CloseGroundContainer, temporary projection-tree teardown, shared outer bevel with horizontal-only resizing, nested-container browsing, double-click loot, drag return, partial-stack requests, and UIElement_ItemList::m_pendingItem destination-slot feedback. Corpse keyboard Use now shares ItemHolder::UseObject policy and cannot issue a pickup for the BF_STUCK corpse itself. Owned side packs no longer send the external-only packet. Research: docs/research/2026-07-17-retail-external-container-looting-pseudocode.md. Closes #196 and retires AP-106. |
Tests ✓; corrective live visual gate pending |
| 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. Plan: docs/superpowers/plans/2026-05-19-indoor-cell-rendering-phase1-diagnostics.md. Capture: docs/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. Verification: docs/research/2026-05-19-indoor-cell-rendering-verification.md. Plan: docs/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) 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. Plan: docs/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. Plan: docs/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. |
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. Spec: docs/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. |
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. Audit: docs/superpowers/specs/2026-05-21-phase-o-t1-wb-audit.md. Plan: docs/superpowers/plans/2026-05-21-phase-o-plan.md. |
Live ✓ |
| G.3 — Dungeon render/stream | Dungeon support shipped: the dungeon LandBlock is flat-terrain and streams through the existing pipeline (premise that terrain-less landblocks were unsupported was refuted at design time). Portal-graph visibility blowup closed (#95 RESOLVED, 9.1M→~39k instances, a40c38e8). |
Live ✓ |
| G.3a — Teleport-into-dungeon | Hold-until-hydrate FSM: teleport-in no longer Resolves before the dungeon landblock hydrates. Closes #133. Commits 7947d7a / 2ce5e5c8 / 47ae237e. |
Live ✓ |
| #137 — Dungeon collision | Dungeon BSP/collision fidelity fix. CLOSED 2026-07-08, user re-gate 0efa7ed2. |
Live ✓ |
| A7.L1 — Dungeon lighting | Indoor/dungeon lighting fidelity (#79/#93/#80/#154). CLOSED 2026-07-09, commits d275ed55 / 9ebb2060 (see also 44cced71). |
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.
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 testgreen; ~1147 test count maintained.- Zero
using WorldBuilder.*orChorizite.OpenGLSDLBackend.*insrc/AcDream.*. DefaultDatReaderWriterreferenced 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.mdincludes 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. 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-30 — render-pipeline pivot. Indoor rendering (the seamless in/out seam: the flap, missing/transparent walls, terrain bleed) is NO LONGER pursued via the WB-inherited two-pipe (inside/outside) split. That whole approach (Phase A8/A8.F, issue #103) is abandoned. Indoor rendering is now Phase U below. Phase A6 (physics) and A7 (lighting) inside M1.5 are unaffected.
Phase U — Unified retail-faithful render pipeline (NEW — supersedes A8/A8.F)
Decision (2026-05-30): replace the two render paths (outdoor Draw +
RenderInsideOut stencil, toggled on cameraInsideBuilding) with ONE pipeline driven
by retail's portal-visibility view (PView::ConstructView / ClipPortals / GetClip;
CEnvCell::find_visible_child_cell). The camera's cell is just the root of a recursive
per-portal clip-region traversal; all visible cells (indoor + outdoor) draw in one pass.
Seamless in/out by construction — no inside/outside branch. Modern code, retail
behavior.
- Why: the two-pipe split is a WB-editor inheritance, not a game-client design; you cannot make two pipes hand off seamlessly at a doorway. Retail never splits. The A8.F attempt to graft retail recursion onto the WB stencil failed its visual gate (#103).
- Keep: WB mesh/dat pipeline (ObjectMeshManager/WbDrawDispatcher/terrain), the 2026-05-30 camera-collision + physics work. Salvage (verify): the A8.F CPU clip-builder (PortalProjection/ScreenPolygonClip/ViewPolygon/PortalVisibilityBuilder — unit-test-correct). Task 1: delete the dead two-pipe code (RenderInsideOutAcdream, the cameraInsideBuilding branch, IndoorCellStencilPipeline, the ACDREAM_A8_INDOOR_BRANCH kill-switch) — audit first; some A8 commits fixed real bugs (BuildingId stamping, pool aliasing) that stay.
- Scope + next-session pickup:
docs/research/2026-05-30-unified-render-pipeline-decision-and-handoff.md. Start withsuperpowers:brainstorming; visual verification at Holtburg cottage/cellar/inn + a portal dungeon is the acceptance gate (unit tests did not catch #103). - Camera-collision (shipped 2026-05-30, kept): retail
SmartBox::update_viewerswept-sphere spring arm (CameraDiagnostics.CollideCamera,PhysicsCameraCollisionProbe,RetailChaseCameraintegration); Step 6 later removed the non-retail 30-step transition cap for every mover after real missile catch-up sweeps proved it invalid. Specs:2026-05-29-a8f-camera-collision-design.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.cdbv4 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 (envACDREAM_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).
- issue #95 filing (
- ✓ SHIPPED — A6.P2 — Analysis report (2026-05-21,
184933d). Output:docs/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
ba9655fplan +6b4be7f/c6bc2b9T1 research +869edd9T2 counter +36975ef/a32f569T3 test +5aba071T4 Mechanism B +5f7722a/39fc037/bd5fe2eT5 strip066568ascen2 postfix proof +<this commit>T8 bookkeeping). StrippedTryFindIndoorWalkablePlanesynthesis path fromTransition.FindEnvCollisionsindoor branch (matches retail's tinyCEnvCell::find_env_collisionsshape at acclient_2013_pseudo_c.txt:309573). Added Mechanism B (LKCP restore) inTransition.ValidateTransitionmatching 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 atPhysicsEngine.ResolveWithTransition:622(filed as issue #96 for slice 2).
- ✓ SHIPPED — A6.P3 slice 2 — L622 seed investigation + no-op guard
(2026-05-22, commits
892019bv1 +f8d669bv2). 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 insideCollisionInfo.SetContactPlane. #96 PARTIALLY ADDRESSED — accepted as documented retail divergence. The seed is load-bearing forAdjustOffsetslope-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
8898166v1 +3e140cfv2). Added point-in stickiness check at top ofResolveCellId'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.
- ✓ SHIPPED — A6.P3 slice 1 — Indoor ContactPlane retention
(2026-05-21, commits
- A6.P4 — Remove workarounds + visual verification (~1 day after
P3). Revert #90 sphere-overlap stickiness in
PhysicsEngine.ResolveCellId. DeleteTransition.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).
- its caller in
Phase A7 — Indoor lighting fidelity (RenderDoc + retail-decomp driven)
A7.L1 progress (2026-07-09, d275ed55 + 9ebb2060): the Town Network
"fountain room too dark" report (#79/#93 umbrella) is root-caused to TWO
distinct bugs, both fixed and user-confirmed improved ("lightning is
better"): (1) BuildPointLightSnapshot's player-nearest cap had no
visibility filter — in a 463+ fixture hub, wall-disconnected-but-Euclidean-
closer fixtures could out-rank a visible room's own torches; scoped
candidacy to last frame's rendered visible-cell set. (2) THE bigger fix: the
mesh-empty hydration gate dropped an entity's ENTIRE registration — Setup
Lights included — whenever its visual mesh flattened to zero parts (a
mesh-less "light attach point" carrying only a Light, common dat pattern);
fixed via EntityHydrationRules.ShouldKeepEntity. Dungeon-wide effect:
registered lights 498→892 on the same launch. #93 + #80 CLOSED same
session (user re-verified the 2nd-floor case directly). #189 (missing
fountain particle) also CLOSED same session — turned out to be a THIRD,
unrelated bug: #190, an interior-entity-id counter overflow the light fix
triggered as a side effect (widened 8→12 bits, e651cb6d). #94
(held-item spotlight) stays open but BLOCKED — acdream doesn't yet support
equipping hand-held items at all, so it's currently untestable; not an A7
blocker. Full detail in ISSUES.md #93/#189/#190/#94 (Recently closed +
Open sections) + AP-85 in the divergence register.
Now also owns #176/#177 (2026-07-06): the Facility Hub purple seam
flash + stair-room light pop-in are ROOT-CAUSED to this phase's "light
visibility culling" layer — a camera-nearest MaxGlobalLights=128
snapshot cap evicts in-range lights of visible cells (Hub has 366
fixtures), so per-cell 8-light sets churn as the camera moves.
Uncapping was live-tested and reverted because the full pool exposes the
per-cell-reach + fixture-curve defects below (through-floor light,
1/d-vs-1/d³). Analysis PRE-PAID — see
docs/research/2026-07-06-176-177-handoff-A7-lighting.md (the fix order:
per-cell insert_light registration → static fixture curve → stripe
hunt → uncap) + register AP-85.
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. CONFIRMED bug here (#176/#177): no per-cell light
registration — lights are a flat world-space sphere-overlap pool that
reaches through solid floors; retail's
insert_light0x0054d1b0 scopes each light to its cell. - Per-entity light direction transform — held-item-spotlight bug (#L-spotlight) is per-entity attribution gone wrong.
- Static-stab atmospheric inheritance (#81).
- Fixture falloff curve (#176/#177): stationary server-spawned
fixtures ride the DYNAMIC 1/d path (#143
isDynamic); should be the static 1/d³ bake (calc_point_light0x0059c8b0).
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 inacclient_2013_pseudo_c.txtto 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_RADIUSenv 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 whenPhase A.3introduces a thread-safe dat wrapper. Pending-spawn list inGpuWorldStateparks liveCreateObjectevents 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=4spreads 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.DrawandStaticMeshRenderer.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'sTick()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 afterEnterWorld()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
PhysicsEngineresolver 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,SendChatoutbound + receive/display inbound (display side depends on Phase D.1).
References:
references/ACE/Source/ACE.Server/Network/Handlers/MovementHandler.csreferences/ACE/Source/ACE.Server/Network/Handlers/UseObjectHandler.csreferences/holtburger/src/session/send.rsfor 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
ParticleEmitterInforuntime + 13-type motion integrator port +PhysicsScriptrunner + 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 provesGameSkydropspes_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 mergefeat(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/DefaultScriptdispatch to the entity lifecycle so portals, chimneys, fireplaces, and sky effects animate per retail:- ✓ SHIPPED — C.1.5a (portals) — 2026-05-11 (merge
88bda12).EntityScriptActivatorfiresSetup.DefaultScripton every server-spawnedWorldEntityviaPhysicsScriptRunner. Visual-verified at Holtburg Town network portal. Surfaced known limitation as issue #56 (per-part transform handling) — addressed in C.1.5b. Plan archived atdocs/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+SpawnFromHookpart-transform application — multi-emitter scripts now distribute across mesh parts.EntityScriptActivatorServerGuid==0guard relaxed (keys byentity.Idwhen ServerGuid is zero) + 4 newGpuWorldStatefire-sites pick up dat-hydrated entities (EnvCell statics + exterior stabs) — fireplaces and chimneys now fire theirDefaultScriptautomatically. Reality discovery during design: EnvCell statics are already hydrated asWorldEntityitems byBuildInteriorEntitiesForStreaming, so no synthetic-ID scheme or separate walker was needed. Visual-verified at Holtburg portal + Inn fireplace + cottage chimney + spell cast. Plan archived atdocs/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. Closes #2 (lightning), #28 (aurora), #29 (cloud thinness) when shipped. Does NOT close #4 (sky horizon-glow fog) — that's shader work, not PES.
- ✓ SHIPPED — C.1.5a (portals) — 2026-05-11 (merge
- 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
CullModeand 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.csfor the emitter schemareferences/ACViewer/ACViewer/Physics/Particles/for the visual modelreferences/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/ParticleBatcher.csfor 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.mdfor the full design. Short version:
- 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.ImGuiafter a day-one pivot away from Hexa.NET.ImGui; see shipped table.)- Stable
AcDream.UI.Abstractionslayer — ViewModels + Commands +IPanel/IPanelRendererinterfaces. Backend-agnostic. Plugin API publishes against this layer and never sees ImGui.- 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=1overlay 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.Abstractionslayer. Shipped 2026-04-25. Wires ImGui as the short-term backend behindACDREAM_DEVTOOLS=1. DefinesIPanel/IPanelHost/IPanelRenderer/ICommandBus+ the first ViewModel (VitalsVM) in the newAcDream.UI.Abstractionsproject. First real panel:VitalsPanelreading HP fromCombatState.GetHealthPercent. Backend pivoted Hexa.NET.ImGui → ImGui.NET +Silk.NET.OpenGL.Extensions.ImGuiduring integration — Hexa's native OpenGL3 backend does its own GL function resolution via GLFW/SDL and crashed with0xC0000005inImGuiImplOpenGL3.InitNativeagainst Silk.NET (no GLFW/SDL present). The Silk.NET extension is purpose-built for this scenario and is theImGui.NETmitigation path thatdocs/plans/2026-04-24-ui-framework.mdalready called out as a "one-morning operation". Stam/Mana returnfloat?null in D.2a because absolute values needLocalPlayerState+PlayerDescription (0x0013)parsing (filed post-D.2a). 11 newAcDream.UI.Abstractions.Testsgreen. -
✓ SHIPPED — D.2b — Custom retail-look backend (Spec 1: markup engine + first panel). Shipped 2026-06-14 (
626d06e→019350f). Wired the dormantUiHost/UiElementtree intoGameWindow(ACDREAM_RETAIL_UI=1) and built the Approach-C markup engine:MarkupDocument(XML →UiElementsubtree,{Binding}reflection) +ControlsInistylesheet loader + anIUiRegistryplugin UI surface (plugins ship markup + a binding, drained into the sameUiRoot). First retail-faithful panel: a markup-driven Vitals window (vitals.xml) — 8-piece dat-sprite frame (UiNineSlicePanel) + threeUiMeterbars (red/gold/blue + cur/max numbers) bound toVitalsVM, rendering live over the 3D world and coexisting with the ImGui devtools path. Retired divergence TS-30, added IA-15. Specdocs/superpowers/specs/2026-06-14-d2b-retail-panel-frame-design.md; plandocs/superpowers/plans/2026-06-14-d2b-retail-panel-frame-plan.md. NOTE the prove-out corrected two stale "facts": retail vitals are bars not orbs, stamina is gold not cyan. Remaining: D.3 (AcFont — using the stb stopgap for now) + the glassy gradient bar sprite / brightness tune (polish); input integration (movable/clickable windows — theUiRootdrag/click machinery exists but isn't bridged to the Phase-K dispatcher yet); and the rest of the panels (D.5). -
✓ SHIPPED — D.2b LayoutDesc importer (Plan 1 + default flip). Plan 1 shipped 2026-06-15. Retroactive registration — the spec requested this phase but it was not pre-registered before implementation. Data-driven vitals window from
LayoutDesc 0x2100006C:LayoutImporterresolvesBaseElement/BaseLayoutIdinheritance, walks theElementDesctree, and builds a liveUiElementtree viaDatWidgetFactory(Type 7 →UiMeter; all others →UiDatElementgeneric renderer).VitalsControllerbinds live HP/Stamina/Mana by element id (mirrors retailgmVitalsUI). A/B visual gate PASSED: pixel-identical to the hand-authored window. Default flip shipped 2026-06-15 (bf77a23): the importer is now the default vitals window atACDREAM_RETAIL_UI=1; the hand-authoredvitals.xmland theACDREAM_RETAIL_UI_IMPORTERflag were retired (vitals.xmlrecoverable from git history). The window is movable (Anchors=None + Draggable) AND horizontally resizable (Resizable/ResizeX,8aa643f): the dat edge-anchors reflow the pieces on width change per retailUIElement::UpdateForParentSizeChange @0x00462640(an earlier "fixed-size" note was wrong — inverted edge-flag reading, corrected; stretch isRightEdge==1). Faithful grip/dragbar-driven drag/resize INPUT is Plan 2. Post-flip number-render fixes (43064ba,34243f2): submission-order sprite draw (stamina/mana numbers had been overpainted by their own bars) + glyph pixel-snap (sharp at all resize widths).MarkupDocument/UiNineSlicePanelremain for the chat window + plugin panels. Spec:docs/superpowers/specs/2026-06-15-layoutdesc-importer-design.md; plan:docs/superpowers/plans/2026-06-15-layoutdesc-importer.md. -
✓ SHIPPED — D.2b LayoutDesc importer (Plan 2 — chat-window re-drive). Shipped 2026-06-15.
GameWindow's hand-authored chat block (UiNineSlicePanel+ inlineUiChatView) replaced byChatWindowController.Bind(LayoutDesc 0x21000006, …)— the same importer path as vitals.ChatWindowControllerplacesUiChatView(transcript) +UiChatInput(text entry + on-submit) +UiChatScrollbar(scrollbar thumb) +UiChannelMenu(channel selector) inside the dat-authored chrome; dead local staticsBuildRetailChatLines/RetailChatColordeleted fromGameWindow(moved into the controller). Wired to_commandBus(sameLiveCommandBusas the ImGuiChatPanel) so type+Enter dispatchesSendChatCmdserver-ward. Transcript keyboard set from_uiHost.Keyboardfor Ctrl+C/Ctrl+A. 392 tests green. Added divergence rows AD-28 / AP-38–40 / TS-30–31; updated IA-15. -
✓ SHIPPED — D.2b LayoutDesc importer (Plan 2 — widget generalization). Shipped 2026-06-16 (
b7f7e2b→89626cd). The hand-named chat widgets became GENERIC, Type-registered widgets built byDatWidgetFactory(1→UiButton,6→UiMenu,7→UiMeter,11→UiScrollbar,12→UiText);UiField(editable) ships controller-placed.ChatWindowController+VitalsControllercollapsed to thingm*UI::PostInit-style find-by-id binders — this is the reusable toolkit + assembly pattern the future inventory/vendor/spell-bar windows build on. NewUiElement.ConsumesDatChildrenleaf-widget rule: behavioral widgets reproduce their dat sub-elements procedurally, so the importer must not build their children (an invisible Menu label child was swallowing the button click → dropdown wouldn't open). Type 3 deliberately NOT registered →UiField(acdream's Type-3 elements are inert sprite-bearing chrome/containers → stayUiDatElement; a subagent's Type-3→UiFieldregistration was reverted — it blanked the vitals bevel + masked the regression by weakening a test). The editable input resolves to Type 12 → controller-placedUiField(Variant B). Vitals numbers rewired to a centeredUiText(Task 8) —UiText.Centeredreuses the meter's former centering formula, pixel-identical. Both visual gates (chat + vitals) user-confirmed; 404 tests green; newchat_21000006.jsongolden fixture. Amended AP-37, narrowed AP-41, added AP-42. Spec/plan:docs/superpowers/{specs,plans}/2026-06-16-d2b-widget-generalization*.md. -
D.3 — AcFont from portal.dat. Replace stb_truetype system font with retail
FontDBObjs (0x40000000..0x40000FFF) baked fromRenderSurfacesource sheets — see research slice 03 §4. Preserves retail visual identity. (D.2b dependency — needs the custom renderer.) -
✓ SHIPPED — D.4 — Dat sprites + 9-slice panel backgrounds. Shipped 2026-06-14 with D.2b.
TextureCache.GetOrUploadRenderSurface(id, out w, out h)decodesRenderSurface(0x06xxxxxx) directly (not via the Surface→SurfaceTexture chain — the prove-out finding) → GLTexture2D;DrawSprite(explicit UV-rect, per-texture batch) added toTextRenderer+UiRenderContext+ auUseTexture=2RGBA frag branch;UiNineSlicePanelcomposes the universal 8-piece bevel (corners0x060074C3..C6, edges0x060074BF/C0/C1/C2, center0x06004CC2). Remaining art polish: the glassy gradient bar fill sprite (D.2b polish). -
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. (TargetsAcDream.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. -
✓ SHIPPED — D.5.1 — Toolbar (action bar). Shipped 2026-06-16/17 (
30b28c2→0e7a083, branch claude/hopeful-maxwell-214a12). First data-driven game panel:gmToolbarUI(LayoutDesc 0x21000016) — 18 shortcut slots from the persistedPlayerDescriptionSHORTCUT block, real composited item icons (opaque type-default underlay via theEnumIDMap 0x10000004resolve), occupancy-gated slot numbers 1–9 (occupied = dark-box peace/war0x10000042/43; empty = background0x1000005efrom cell composite0x10000341), click-to-use (ItemHolder::UseObject→0x0036), peace/war stance indicator live-wired toCombatState, movable, and a chrome frame (UiNineSlicePanel drawn over content via the newUiElement.OnDrawAfterChildrenhook). New shared widgetsUiItemSlot(UIElement_UIItem0x10000032, procedural leaf) +UiItemList(UIElement_ItemList0x10000031, factory branch) +IconComposer(CPU layered composite).CreateObject.TryParseextended to the full ACE-order weenie-header tail to captureIconId/IconOverlay/IconUnderlay→ItemRepository.EnrichItem→ re-render. Spec/plandocs/superpowers/{specs,plans}/2026-06-16-d2b-toolbar-phase1*.md; research dropdocs/research/2026-06-16-*deep-dive.md+ synthesis. Divergence IA-16/IA-17 added. User-confirmed (numbers, icons, frame). Per-task spec+code-review throughout. -
✓ SHIPPED — D.5.2 — Stateful item-icon system. Shipped 2026-06-17/18 (
419c3ac..fb288ad, branch claude/hopeful-maxwell-214a12; visually verified on a live Coldeve server). Faithful retail icon composite (IconData::RenderIcons@0x0058d180): (1)UiEffectsbitfield captured from theCreateObjectweenie header (was discarded) →ItemInstance.Effects; (2)IconComposer.GetIconrewritten as a 2-stage composite — Stage 1 = drag icon (base + custom overlay) + the effect treatment, Stage 2 = type-default underlay + custom underlay + drag. The effect treatment ports the surface overload ofSurfaceWindow::ReplaceColor(0x004415b0): the textured effect tile (EnumIDMap 0x10000005byLowestSetBit(effects)+1, fallback0x21solid-black) is copied per-pixel into the icon's pure-white pixels — magical items take the tile's GRADIENT hue, mundane items go black; (3)PublicUpdatePropertyInt (0x02CE)parser +WorldSession.ObjectIntPropertyUpdatedevent +GameWindowsubscription →ItemRepository.UpdateIntProperty→ icon re-composites live. Appraise (0x00C9) carries NO icon data (ACE proof:Icon/IconOverlay/IconUnderlay/UiEffectsall lack[AssessmentProperty]) — dropped as a no-op. Two visual-verification fixes landed after the subagent build: theeffects==0recolor MUST run (mundane white edges → black,40c97a5) and the tint is a per-pixel GRADIENT not a flat color (the surface overload,fb288ad) — both confirmed via clean Ghidra + named decomp. Divergence: IA-16 retired; IA-18 (per-pixel surface-copy anti-regression) + AP-45 (0x02CE sequence) added; AP-43/AP-44 retired by the visual fixes. Spec/plan/research:docs/superpowers/{specs,plans}/2026-06-17-d2b-stateful-icon*.md,docs/research/2026-06-17-stateful-icon-RESOLVED.md. -
D.5 remaining — sub-phase ledger. D.5.1 (toolbar + the
UiItemSlot/UiItemList/IconComposerspine) ✅, D.5.2 (stateful icons) ✅, D.5.4 (client object/item data model) ✅, D.2b-B window manager (abbd97b) ✅, D.2b-B B-Grid (inventory sub-window mount +UiItemListgrid) ✅, D.2b-B B-Controller (inventory population + burden meter + captions,03fbf44; visually confirmed 2026-06-21 — two render bugs fixed at the gate417b137: backdrop wash-out [a #145 continuation — mounted sub-window slots must keep their own frame ZLevel, not inherit the base root's 1000] + captions [drive the host UiText directly]) ✅, D.2b-B B-Wire (EncumbranceVal/PropertyInt 5 wire delivery, AP-48/49 retired) ✅, inventory window finish Stage 1 (scroll/frame/vertical-resize/102-slot grid) ✅, inventory empty-slot art ✅, container-switching + open/selected indicators + main-pack icon + per-bag capacity bar ✅, B-Drag (inventory drag-drop / item moving, including retail select-before-waiting direct-drag order; visually confirmed 2026-07-13,609ed8bf) ✅, Sub-phase C (paperdoll): Slice 1 equip slots ✅ + Slice 2 3-D dollUiViewport(Type 0xD via theIUiViewportRendererRTT seam) + Slots toggle ✅ (visually confirmed 2026-06-25,8fa66c2— pose/camera/heading all retail-verbatim) + exact 24-location empty backgrounds and progressive Aetheria visibility visually confirmed 2026-07-13 (1be18cc4,edc9be30; AP-66 retired) ✅, UI Studio (AcDream.App ui-studio) ✅, importer dat-fidelity (Fix A/B/C) ✅, Character window (LayoutDesc 0x2100002E,CharacterStatController) ✅, Wave 3.1 unified selection (CoreSelectionState; AP-58 retired) ✅, and Wave 3.2 pure item-use/drop policy (ItemInteractionPolicy; AP-99 retired) ✅ are shipped. Build order from here: finish Wave 3 activation router/cursors, then D.5.3 selected-object mana + stack entry/slider and the separate favorite-spell bars; ISSUES #146/#147/#197. Each ☐ below gets its own focused oracle/spec/plan/gate. -
✓ SHIPPED — D.5 paperdoll background correction (2026-07-13,
1be18cc4+edc9be30). All 24 per-location backgrounds passed live. The three Aetheria locations use exact blue/yellow/red UIItem prototypes and independent playerAetheriaBitfield (322)login/live visibility; ACE states0→1→3→7progressively revealed none→blue→blue+yellow→all without relogging. AP-66 retired and Aetheria removed from AP-108. -
✓ SHIPPED — UI Studio (2026-06-26, branch
claude/hopeful-maxwell-214a12, ~33693c6→HEAD). StandaloneAcDream.App ui-studio <datdir> [--layout 0xNNNN | --dump <slug>] [--screenshot <png>]Silk tool that previews any panel through the production renderer (RenderBootstrap.cs+ GameWindow untouched). Sources: 26-window retail dump (docs/research/2026-06-25-retail-ui-layout-dump.json) via--dump, or live dat import + fixtures via--layout. Interactive canvas (click-routing toUiHost, Interact/Inspect ImGui toggle), headless--screenshot. Architecture:src/AcDream.App/Studio/(StudioWindow,PanelFbo,FixtureProvider,LayoutSource) +src/AcDream.App/Rendering/RenderBootstrap.cs. ISSUES #156 (inventory all-black in studio) and #157 (GameWindow font resolver) filed. -
✓ SHIPPED — Importer dat-fidelity (Fix A/B/C, 2026-06-26, same branch). Boundary established: look = importer (font/justification/color from dat); state/behavior = runtime controller logic. Fix A: justification property
0x14/0x15→UiTextCentered/RightAligned/VerticalJustify. Fix B: FontColor property0x1B→UiText.DefaultColor; character colors proved RUNTIME (no importer fix). Fix C: FontDid → per-element dat font via a resolver inDatWidgetFactory— STUDIO path only; GameWindow passesnull(Issue #157). Fix 4 (UIState-group activation): INVESTIGATED — no importer fix exists; the dat has no Visible encoding; runtimegm*UIis correct. -
✓ SHIPPED — Wave 3.3 unified primary-click router.
ItemInteractionController.OfferPrimaryClickreturnsNotActive/ConsumedSuccess/ConsumedRejected; inventory, bags, main pack, paperdoll, toolbar, radar, and world clicks all offer target mode before local fallback. Rejected targets cannot drift selection or open/use the clicked object. Issue #197 and AP-107 retired; retained matrix and live ACE-provisioned healing gate user-confirmed. The 3-D doll routes through authored hit mask0x100001D6(resolvedUiButtonabove viewport0x100001D5), and local health prefers authoritative private-vital state. -
✓ SHIPPED — retail world selection + complete SmartBox feedback (2026-07-18). The projected Setup-sphere rectangle and independent physics-wall ray are deleted. The normal visible-mesh traversal now publishes server-object parts through their exact drawing-BSP spheres;
RetailWorldPickerports the camera-eye ray, per-part local transform, double-precision retail sphere solve, first visual-polygon hit in DAT order, one-sided/convex-edge rules, and global polygon-over-sphere arbitration. The retained gameplay UI resolves client-enum category0x10000009values1..12to the four installed 12×12 corner surfaces and eight 24×24 off-screen direction surfaces, colorizes them with the radar-blip color, and ports the exact Setup-sphere rectangle, through-wall/no-distance SmartBox callback, direction sectors, and 8-pixel viewport-edge clamp. Successful select/use/targeted-use/drop clicks also run SmartBox's separate 0.8-second HIGH/LOW/HIGH/LOW material-lighting pulse with the exact retail luminosity/diffuse replacements before restoring authored rendering. Issues #70/#71 closed; AP-29/TS-16 retired. Research:docs/research/2026-07-17-retail-world-selection-pseudocode.md. -
✓ SHIPPED — Wave 4.1 retail quick-slot input. Bare
1..9use toolbar slots 0..8, Ctrl+1..9selects them, Alt+5..9uses slots 13..17, and0creates a shortcut to the selected owned/eligible object in retail's first empty slot.ToolbarInputControlleris the focused semantic adapter;ToolbarControllerownsUseShortcuttarget-before-use/select ordering and auto-slot eligibility. Keybinding schema v2 migrates only the exact old default Ctrl+number chords, so custom bindings retain their chosen action. Full automated conformance and live user gate passed 2026-07-11. -
✓ SHIPPED — Wave 4.2 exact shortcut records. One Core-owned
ShortcutEntrypreserves retail's signedindex_, unsignedobjectID_, and raw 32-bitspellID_through PlayerDescription parse, the nullable 18-slot session store, drag snapshots/reindexing, WorldSession, and exact 12-byte AddShortcut packing. The visiblegmToolbarUIremains object-only;favorite_spells_[8]remains a separate future spell-bar source. AP-103 retired. Full automated conformance and live mutate→clean-relog persistence gate passed 2026-07-11. -
✓ SHIPPED — Wave 4.3a shortcut-alias boundary. Drag feedback now preserves retail's neutral/accept/reject distinction. Inventory and paperdoll apply the
(DropItemFlags & 0xE) == 0physical-item gate, so a toolbar alias can reorder within the toolbar but can never move, unwield, wield, or drop the object it references. Live equipped-helmet gate passed 2026-07-11: inventory drop removed only the shortcut. -
✓ SHIPPED — Wave 4.3b source-aware toolbar transactions. Pure Core
ShortcutDropPlannerportsgmToolbarUI::HandleDropRelease @ 0x004BE7C0: fresh inventory drops displace cyclically right (with wrap), shortcut aliases restore to their vacated source, duplicate objects are removed before placement, full bars intentionally lose the displaced entry when retail finds no empty slot, and every transaction is planned before local mutation then emitted in exact Remove/Add order. Full-stack rekey planning preserves the raw spell word. AP-102 retired; 4,646 automated tests pass; live inventory-to-occupied displacement and toolbar reorder gate passed 2026-07-11. -
✓ SHIPPED — Wave 4.3c remaining drop branches + stack merge.
UiButtonnow has a reusable retained item-drop-target seam; gmToolbarUI's inventory/backpack button accepts only fresh physical items, draws the exact authored green-arrow overlay0x060011F7, and sends them to the player container while shortcut aliases remain neutral. Pure CoreStackMergePlannerportsIsMergeAttemptLegal @ 0x00586F30andAttemptMerge @ 0x005878F0; inventory drops try legal same-WCID stack merges before normal insertion, clamp0x0054amount to destination capacity, immediately broadcast retail's misleadingly namedFullMergingItemnotice to rekey the toolbar, and select the destination. Existing destination shortcuts are removed before rekey. Warning-free Release build and 4,660 tests pass; live stack-merge gate passed 2026-07-11. Backpack-button drop gate remains pending. -
✓ SHIPPED — Wave 4.4a selected-stack controls and transfers.
SelectedObjectControllerformats retail's count-firstNAME_APPROPRIATEstack name from the wire plural, reveals the authored right-aligned numeric entry and horizontal DAT slider, initializes both to the full stack, refreshes them on selected-object stack changes, and shares one Core split quantity with every item-holder transfer path. The thumb owns its raw pointer position so quantity 1 and maximum reach the exact endpoints; entry writes retain retail'ssplit/maxattribute behavior. ExactItemHolder::GetObjectSplitSizeselection scoping drives merge, split-to-container0x0055, and split-to-ground0x0056; partial splits wait for the server-created object instead of moving the source optimistically. Exact cdb-pinned 1000-step rounding, plural fallback, real-DAT media/alignment, pointer drag, and wire bytes have conformance tests. Warning-free App Release build and 4,697 tests passed; controls, partial inventory split, and partial ground drop were live-confirmed 2026-07-11. -
✓ SHIPPED — Wave 4.4b selected-item mana. Owned non-stack selections send exact
QueryItemMana 0x0263;0x0264parses guid/fraction/trailing-valid into CoreItemManaState; valid matching responses reveal authored meter0x100001A2, invalid and visible selection-change paths cancel with guid zero. Health uses the same retail visible-meter cancellation rule. Wire/state/controller conformance is green in the 4,712-test Release suite. Live Mana Stone source-destruction → armor-mana-transfer → bar-update gate passed 2026-07-11. AP-101 now covers remaining toolbar controls only. -
✓ SHIPPED — Wave 4.4c exact selected-health policy. Core ports
ObjectIsAttackable @ 0x0056A600; CreateObject preserves second-header PetOwner; toolbar player/pet composition is exact. Self, pets, and Free-PK creatures now query health while friendly NPCs and attackable non-creatures remain name-only. AP-46 retired. The world/radar policy matrix and exact DAT paperdoll body-map + upper-item-priority route passed live 2026-07-11. -
✓ SHIPPED — Wave 4.4d toolbar launchers + Use/Examine. All seven authored launchers are discovered through their DAT panel-id attribute; mounted Inventory/Character panels toggle through the registry and the other five buttons are ghosted. Typed window-visibility events own Highlight/Normal state. Use and Examine act on selection or enter their one-shot retail target modes, with exact Appraise wire dispatch and retail cursors. Warning-free App build and 4,747-pass / 5-skip Release suite are green; the connected live gate passed 2026-07-11. AP-101 is narrowed to the remaining ammo-number display.
-
Wave 4.4e implemented — exact missile ammo number (live gate pending). Pure Core reproduces retail's thrown-weapon-vs-separate-ammo resolution over the ordered player equipment list and its zero-to-one count normalization. Relevant equipment/stack events update authored missile indicator
0x10000194with the DAT font. Warning-free Release build and 4,754-pass / 5-skip suite are green; AP-101 is retired. -
✓ Wave 4.5 — retail primary-weapon switching (live-gated 2026-07-15). Inventory double-click now routes through a focused
AutoWieldController: the occupied0x03500000weapon-ready group is returned to the player container, followed when necessary by an incompatible shield or mismatched ammo; each blocker waits for authoritativeInventoryPutObjInContainerbefore the next step, thenGetAndWieldItemequips the requested bow/sword/caster/two-hander. The transaction remains inventory-busy through the exact authoritativeWieldObjectacknowledgement, independent of the object table's rollback counter, matching retail's waiting state and preventing rapid switches from overlapping or remaining stuck.CreateObjectretains exactAmmoTypefor compatibility. The same transaction runs in peace and war; ACE owns the latter's old-stance → peace → new-stance motion chain. Player PropertyInt 40 updatesCombatState, keeping toolbar/combat UI synchronized with ACE's selected Melee/Missile/Magic stance. Research:docs/research/2026-07-12-retail-weapon-switch-pseudocode.md. -
✓ Wave 4 inventory drag visuals (live-gated 2026-07-15).
IconComposernow preserves retail's separate underlay-freem_pDragIconfor cursor tracking across inventory, paperdoll, and toolbar bindings. Physical source cells keep their full icon in place and reveal authored ghost mesh0x0600109Afrom drag begin through every release/cancel path, with the persistent selection square above the mesh; shortcut aliases remain unghosted.ItemList_DragOverdestination states are split exactly: contents-grid placement uses the green accept circle0x060011F9, while side-bag/main-pack container drop-in uses the green arrow0x060011F7. AP-47 retired. Research:docs/research/2026-07-13-retail-item-drag-visuals-pseudocode.md. -
✓ Wave 4.6 item giving (starter-dungeon live gate passed 2026-07-13). SmartBox drag release now preserves the exact release coordinates, world-picks the target, and runs the existing retail
ItemHolder::AttemptPlaceIn3Dpolicy. NPC/creature targets send exactGiveObjectRequest 0x00CD(target,item,amount)bytes, selected partial stacks use the shared split quantity, and the object table waits for ACE's authoritative remove/stack-size response.PlayerDescription.Options1supplies the realDragItemOnPlayerOpensSecureTradeoption. Issue #216; research:docs/research/2026-07-13-retail-give-item-pseudocode.md. -
M2 held-object parenting shipped and live-gated 2026-07-11. The combat toggle now ports
GetDefaultCombatModeover ordered equipped contents, so a bow requests Missile instead of the old hardcoded Melee. CreateObject preserves parent/placement/timestamp fields,0xF749ParentEvent is handled, andEquippedChildRenderControllerrenders the weapon as a separate child composed from the animated hand part + holding frame + child placement frame. Live gate passed: bow selected missile stance, rendered in-hand, followed animation, unequipped cleanly, and melee remained correct. App Release builds with zero warnings; the full 4,765-pass / 5-skip suite is green. AP-111 is retired; research:docs/research/2026-07-11-combat-default-and-parent-event-pseudocode.md. -
Missile/portal VFX campaign Steps 0–5 implemented and independently reviewed 2026-07-14. The retail oracle and packet fixtures are pinned, complete
PhysicsDescplus F754/F755 parsing and nine-channel gates are shipped, and App now has one canonicalLiveEntityRuntimerecord per server-object incarnation. Logical register/unregister is separate from spatial rebucketing, so landblock churn and attached equipment retain identity without replaying renderer/script creation or reconstructing from stale spawn data. Canonical materialized and visible target/radar views are distinct; pickup/parent leave-world preserves owners while pausing root simulation; spawn publication is once per incarnation.GpuWorldStateis spatial-only for live objects. Production PhysicsScript and Animation loading shares a narrowDatCollection-backed compatibility reader for retail's inherited blocking-particle payload, including mesh-side preloading; the proven concurrent-safe DatReaderWriter 2.1.7 read path avoids blocking update-thread effects behind streaming. Typed-table selection preserves DAT order and retail's firstintensity <= Modboundary, and livePhysicsDesceffect defaults replace rather than fall back to Setup. The Step 4 scheduler is now one serial FIFO per owner with duplicate stacking, catch-up dispatch, deterministic delayedCallPES, complete hook-router fan-out, update-frame clock publication, retail cell/Frozen eligibility, and structural rejection of malformed zero-time recursive DAT chains without rejecting valid timed weather loops.EntityEffectControllerretains pre-create F754/F755 in one mixed per-GUID FIFO, resolves local identity only throughLiveEntityRuntime, replays once only after the canonical owner is fully ready and in-world, drops later plays while that existing owner is cell-less, routes attached-child updates through the eligible parent, and replaces/clears the live SoundTable on every PhysicsDesc application. Step 5 publishes current rigid root/indexed-part poses after animation and recursively composed held-child transforms, keeping render-only Setup scale out of particle and holding-location anchors; it then drains animation hooks, owner PhysicsScripts, moving emitters/lights, and particle simulation in fixed order. Normal/blocking/anonymous logical emitter identity is exact, including Stop retaining a blocking ID until final-particle retirement; moving and held Setup lights follow their current roots and share retail's PhysicsState Lighting/SetLight transition state; world-released versus parent-local particle behavior is preserved; missing emitter DAT produces an actionable diagnostic and no invented fallback. Drawable meshes no longer collapse stable Setup-part indices; nested attachments update parent-before-child, recursively withdraw on ancestor pose loss, and recover the complete descendant chain only on real publication edges. Loaded↔pending projection edges skip particle update/draw while retaining original absolute creation times and logical identity, and withdraw light presentation; re-entry evaluates elapsed state once without backlog emission or recreating an effect owner. Scripts, particles, lights, translucency, audio, and teardown all share canonicalWorldEntity.Id; static allocators fail fast before namespace wrap. IA-7, AD-14, TS-10, TS-11, TS-12, TS-13, and AP-67 are retired; AD-32 now covers only the remaining non-effect, non-Parent pre-create packet families, and AD-43 registers the corrupt-DAT zero-time-cycle safety boundary. -
Missile/portal VFX cell-less rule clarified 2026-07-16. The Steps 0–5 statement that later plays drop while an existing owner is cell-less applies only to ordinary F754/F755/default playback. Hidden/UnHide is retail's internal exception:
CPhysicsObj::set_hiddenresolves the typed script and callsplay_script_internal, queuing it until scheduler eligibility returns. This preserves the human table's purple portal materialization across asynchronous destination placement. -
✓ M2 local attack receive funnel (live-gated 2026-07-15). Retail
ExecuteAttackonly sends the request; ACE chooses the concrete melee/missile action and returns it in a non-autonomous mt-0UpdateMotion. The local branch now runs that state through the same constructor-defaultedMoveToInterpretedStatefunnel and 15-bit action-stamp gate as remotes, then applies sticky/long-jump tails. The old local-only directCommands[]replay is deleted. Shared conversion lives inInboundInterpretedMotionFactory; research:docs/research/2026-07-11-local-combat-motion-pseudocode.md. -
✓ M2 basic retail combat bar (live-gated 2026-07-15). Production mounts authored
gmCombatUILayoutDesc0x21000073, shows it only for Melee/Missile, and routes mouse plus keyboard through oneCombatAttackController. Corrections include the authored wider centered dark-red child range (accepted IA-20), full-width left-to-right bright live attack-charge feedback, exact Speed-left/Power-right justification, silent control-onlyAttackDone(ActionCancelled), target-frame Keep in View with manual orbit, and persistent corpse motion: the AP-80 velocity-only NPC adaptation can now replace only Ready/Walk/Run, never authoritative Dead/actions.CombatTargetControllerports the selection-cleared AutoTarget consumer, so a selected creature's authoritative Dead motion clears it and selects the nearest eligible creature when enabled. The 2026-07-12 replacement-corpse correction unifies both multi-frame and static/reactive spawns behind retail's CreateObject lifecycle: apply the wire's Dead state while detached, thenMotionTableManager::HandleEnterWorldstrips Ready→Dead links before the first in-world tick; multiple corpses remaining fallen passed the live user gate that day. Follow-up #205 makes the toolbar read the final canonical selection after a reentrant Auto Target notice and restricts automatic candidates to hostile non-player monsters (intentional PK-edge divergence IA-19); that live gate also passed 2026-07-12. Research:docs/research/2026-07-11-retail-combat-bar-pseudocode.md,docs/research/2026-07-11-combat-target-camera-pseudocode.md,docs/research/2026-07-12-death-and-auto-target-pseudocode.md; AP-24/AP-95 retired, AP-80 narrowed, AP-110 narrowed, AP-112 records the remaining attack-start and exact trained-Recklessness seams. -
M3 cast lifecycle + retained magic UI implemented 2026-07-15; visual gates pending.
SpellCastingControllerports retail's immediate targeted/untargeted request boundary, component preflight, target eligibility, movement stop, and shared UI-busy lifetime while leaving turn/cast/fizzle/impact outcomes authoritative to ACE. Formula preflight reads decrypted portal.dat formulas, the SCID↔WCID map, canonical account randomization, exact school-focus map, infused-magic properties, and scarab-only substitution.MagicCatalognow projects all 6,266 end-of-retail SpellTable records into the shared Core metadata table; the incomplete 3,956-row CSV is test/research-only and AP-17 is retired. Complete live enchantment packets preserve spell/layer identity, all StatMod fields, client-clock normalization, and exact purge buckets. Authored retained surfaces now include the Magic page ofgmCombatUI, all eight favorite tabs, equipped caster endowment,gmSpellbookUI,gmSpellComponentUI, bothgmEffectsUIwindows, and the floating Helpful/Harmful effect indicators. The favorite bar now ports retail horizontal empty-cell padding, shared UIItem 1–9 shortcut overlays, and the Cast button's three independently reflowed DAT face segments. Spell rows resolve UIItem prototype0x10000343; the component page now instantiates LayoutDesc0x21000033category/component templates0x10000466/67, including localized headings, exact row textures and columns, owned/desired counts, selection, and the 0..5000 desired-value contract. The authored scrollbars, exact filters/display ordering, learned-spell drag, and shared server-authoritative delete dialog are connected.RetailPanelUiControllerports the shared one-active-panel lifecycle so toolbar and indicator launchers converge on one owner. #L.2/#L.3 close and #L.4/AP-110 narrow. Research:docs/research/2026-07-15-retail-magic-ui-and-casting-pseudocode.md,docs/research/2026-07-15-retail-spell-catalog-pseudocode.md. -
M3 favorite-bar empty-state correction 2026-07-15; visual gate pending. Shortcut-number arrays remain shared with the status toolbar, but the cell background is now resolved independently from the favorite ItemList's inherited
0x1000000Ecell prototype. LayoutDesc0x21000010selects prototype0x10000341, whose exact brown/goldItemSlot_Emptysurface is0x06001A97; the blue toolbar-style0x060074CFsubstitution is removed. The cross-layout resolver and controller presentation are pinned by live-DAT and retained-layout conformance tests. -
Retail client command families implemented 2026-07-13; corrective live gate pending. One shared typed catalog now separates retail client actions from ACE administrator commands for both chat backends. Named-decomp ports cover recall/house/PK travel; age/birth; framerate, lock, version, location, corpse, and die confirmation; clear plus named/automatic UI layouts; AFK/consent; emotes; friends; squelch/filter/message types; and fill-components. App owns execution, Core owns authoritative friends/squelch state, Core.Net owns exact UIQueue/ControlQueue packets. Confirmation reuse now ports retail
DialogFactorycontexts, queue groups/priority, fresh DAT roots, property results, callbacks/close notices, and server abort handling;/die, gameplay request tuples, and guarded item-use prompts share its type-1 LayoutDesc0x2100003Cpresenter. The first live gate passed the family except for raw suicide-success code0x004Aand the title-bar-only FPS presentation; the correction maps retail's text and mounts SmartBox element0x10000047with live two-decimalFPS/DEG. #L.6 is closed; TS-31/TS-47 are narrowed. Research:docs/research/2026-07-13-retail-client-command-families-pseudocode.md,docs/research/2026-07-13-retail-dialog-factory-pseudocode.md. -
✓ SHIPPED — Character window (
LayoutDesc 0x2100002E,CharacterStatController, 2026-06-26, same branch). Visually user-confirmed 2026-06-26 — Attributes tab reads as retail. Three tabs, header (name/heritage/PK), large-gold level number (dat font,largeDatFont18px), "Total Experience (XP):" + "XP for next level:" captions, 9-row attribute list (icons + right-aligned values + Health/Stamina/Mana vitals), click-to-select (top/bottom selection bars + footer State-B "{Attr}: {value}" / "Experience To Raise: Infinity!" + affordability-gated raise triangles), centered footer. User noted "still needs some polish for later" — deferred to Issue #158. -
✓ CORRECTIVE PORT — live character experience qualities (2026-07-13, #217; connected gate passed). Added retail
PrivateUpdatePropertyInt64 (0x02CF)so Total Experience, the level-progress meter, and Skills' Unassigned Experience refresh from the same authoritative server update; Total XP value alignment now matches the authored right-justified layout. -
✓ SHIPPED — D.5.4 — Client object/item data model (foundation). Shipped 2026-06-18 (
b506f53..a33e897, 11 commits). RenamedItemRepository→ClientObjectTable/ItemInstance→ClientObject; broadened the table to hold EVERY server object (retailweenie_object_tableshape).CreateObjectis now the canonical merge-upsert (ClientObjectTable.Ingest, retailSetWeenieDescsemantics) via a new Core.NetObjectTableWiring(off GameWindow);DeleteObjectevicts;PlayerDescriptionis a membership manifest (RecordMembership); live container-membership index (GetContents, retailobject_inventory_table)._liveEntityInfoByGuidretired (selection/describe resolve from the one table). Root fix: the old enrich-existing-onlyEnrichItemdroppedCreateObjects for items with noPlayerDescriptionstub — live-Coldeve 4/6 hotbar slots blank; items are now created, not dropped. Crux resolved: retail is TWO tables (object_table+weenie_object_table), NOT one — acdream'sWorldEntity(3D system) +ClientObjectTable(data/UI) split was already architecturally faithful; the fix was the ingestion path, not a table unification. 2671 tests green. -
Roadmap correction (2026-07-10): the completion order is now the architecture-first campaign in
docs/superpowers/plans/2026-07-10-retail-ui-fidelity-completion.md. RetailgmToolbarUIis object-only: preserveShortCutData.index_,objectID_, andspellID_, but do not invent spell glyphs on this bar.PlayerModule::favorite_spells_[8]feeds separate spell bars. -
✓ D.5.3 — Toolbar selected-object display (issue #141) SHIPPED. Exact health/player/pet/Free-PK policy, selected-item mana (
0x100001A2), stack entry (0x100001A3), stack slider (0x100001A4), formatted count/name, shared split quantity, click-to-use, peace/war, health/name, and paperdoll self/upper-item selection all passed live by 2026-07-11. Exact rawShortCutDatapreservation is already shipped in Wave 4.2; the eightfavorite_spells_lists belong to the separate spellbook/spell-bar phase. -
☐ D.5.5+ — Remaining core panels. Inventory, equipment/paperdoll, spellbook/component book, and active-effects surfaces are shipped through the retained window manager. Vendor, trade, salvage, tinkering, and the remaining social/world panels stay open. Research drop:
docs/research/2026-06-16-*; current magic research:docs/research/2026-07-15-retail-magic-ui-and-casting-pseudocode.md. -
D.6 — HUD. Radar/compass IMPLEMENTED 2026-07-10; jump power bar IMPLEMENTED 2026-07-13; live-world visual gates pending. The radar is retained retail
gmRadarUI(LayoutDesc 0x21000074): a 120×140 circular face (0x06004CC1) with orbiting N/E/S/W tokens, a fixed bright-green player marker, and coordinate footer (0x06004CC0). Projection isradarPixelRadius / radarRangewith 75 m outdoor / 25 m indoor ranges — no 1.18 factor or scrolling strip. The separategmFloatyPowerBarUI(LayoutDesc 0x21000072) now appears only while jump is pending and clips the authored JumpMode fill left-to-right from the movement controller's shared 0–1 charge. Both live inAcDream.App/UIbehind focused controllers;GameWindowsupplies state delegates only. Remaining D.6: target name plate, damage floaters, and other world-space HUD elements through the rawTextRendererpath. See slice 06 §A.2,docs/research/2026-07-10-retail-radar-pseudocode.md,docs/research/2026-07-13-retail-jump-powerbar-pseudocode.md, and the namedgmRadarUI/gmPowerbarUIdecomp. -
✓ SHIPPED — D.7 — Cursor manager (user-confirmed 2026-07-11). All reachable
ClientUISystem::UpdateCursorState @ 0x00564630branches resolve through production DAT EnumIDMap table 6, with event-ordered global-default versus captured/hoveredMediaDescCursorlayering fromUIElementManager::CheckCursor @ 0x0045ABF0. OS fallback remains only AP-72 and emits a visible diagnostic. The live gate covered world/object, combat, targeted-use, text, move/resize, and drag contexts; combat-indicator mouse clicks and the quick key share the same toggle command. -
D.8 — Sound.Superseded — shipped as Phase E.2 (SoundTable/Sounddat decode, OpenAL 16-voice engine, per-entity 3D positional audio viaAudioHookSink). Entry kept here for history; see the shipped table. -
Missile/portal VFX campaign Step 6 implemented and independently reviewed 2026-07-14. Pure Core now owns the retail projectile clock/integration/sweep primitive: 0.2-second catch-up quanta, 50-unit/second clamp, final-state acceleration, world-space omega, complete 3-D AlignPath, scaled Setup-local collision spheres, terrain/BSP/object continuous sweeps, exact
OBJECTINFO::missile_ignore, self-shadow rejection, elastic/inelastic response, and correct full-cell rebasing across loaded landblocks. App live ownership and authoritative corrections remain Step 7. TS-2 is retired; ordinary missiles add PathClipped but not PerfectClip, so AP-83/AP-91 remain dormant. -
Missile/portal VFX campaign Step 7 implemented and independently reviewed 2026-07-14.
ProjectileControllernow attaches the Step 6 body to the canonicalLiveEntityRecordafter materialization and advances arrows, bolts, and spell missiles without another GUID map, renderer registration, or stale-spawn reconstruction. It commits predicted frames throughWorldEntity.SetPosition, republishes static effect roots, and keeps collision shadows plus canonical full-cell buckets synchronized. A predicted crossing into an unloaded bucket suspends the body and shadow in that same quantum; pending/pickup residence retains the shadow registration for exact re-entry, and hydration plus the 96-unit retail activity gate restart at the current clock without backlog. Fresh State/Vector/Position/Movement packets correct or stop the same body after retail timestamp gates; SetState reaches the canonical body before optional projectile acquisition, and a non-finite local receipt clock cannot consume first classification. Removing Missile retains ordinary active-body physics (delegating to MovementManager when present), while both owners share the body and incarnation-scoped live-record cell. CreateObject vectors are installed only at body construction, never replayed by later SetState. Delete, reset, and GUID reuse use normal logical teardown; ACE remains authoritative for impact, damage, effects, and deletion. -
Missile/portal VFX campaign Step 8 implemented and independently reviewed 2026-07-14.
LiveEntityRuntimenow distinguishes raw server PhysicsState from retail's side-effect-derived final state, beginning from constructor state0x00400C08and applyingset_statein Lighting → NoDraw → Hidden order.LiveEntityPresentationControllerkeeps Hidden objects logically alive while suppressing root mesh, collision, picking, radar, status, and new target acquisition; typedPS_Hidden/PS_UnHideplays remain DAT-driven, direct equipped children inherit NoDraw, particles/lights survive, and repeated/stale state cannot replay a transition. A normal visible CreateObject never fabricates UnHide. Effect-owner preparation is separate from pending F754/F755 replay so construction-state effects always run first. Remote fresh-teleport/cell-less placement now executes the exact orderedteleport_hookaction bundle and a full-cell hard placement before contact/airborne routing, preventing an airborne correction from restoring the old location. Projectile Hidden state pauses the retained body without consuming its active identity or accumulating a clock backlog. TS-43 is retired; AP-69 remains for exact ObjCell-PVS/preview-retention parity after the 2026-07-18 liveness port. -
Missile/portal VFX campaign Step 9 automated hardening implemented and independently reviewed 2026-07-14; final two-client visual gate pending. A deterministic 96-owner App fixture drives canonical missiles and effect owners through projectile updates, repeated DAT-effect scheduling, loaded↔pending↔loaded landblock churn, light/particle withdrawal and recovery, accepted deletes, same-GUID generation reuse, never-created F754 queues, and session reset. It asserts balanced logical render registration and zero residual records/bodies/projectiles, spatial buckets/rescues, shadows (including suspended registrations), effect profiles/packets, PhysicsScript FIFOs/anchors/delayed calls, particle bindings/logical IDs/render-pass owners, poses, light-controller/sink/manager owners, and stale record component references. A companion twelve-cycle gate drives exact recall motion
0x10000153through AnimationSequencer/CallPES, Hidden, deferred remote placement, hydration, and UnHide; a second 96-ownerEntitySpawnAdaptergate balances actual mesh-adapter reference counts without GL. The pass fixed undrained persistent rescue retention, delayed stale visibility edges, GUID-scoped teardown, create resurrection after a nested delete/reset, non-atomic resource registration, double teardown from a re-entrant session-clear callback, observer failure stranding later visibility edges, and stale outer projection transactions overwriting callback-created replacements. Teardown now removes exact projection references and generation/local-ID owners, uses per-GUID/session lifetime epochs plus per-record projection tokens, drains visibility fan-out before aggregating failures, and finishes or supersedes canonical commits before surfacing observer errors. Core remains GL/backend-free, panels remain on UI abstractions, and projectiles still draw through ordinaryWbDrawDispatcherlive-entity submission rather than a global projectile pass. AP-69 and TS-49 remain; AP-83/AP-91 now explicitly describe only a future mover that enables PerfectClip. -
Portal/resource lifetime correction 2026-07-18; connected stress gate passed, final visual gate pending.
LiveEntityLivenessControllerports retail's 25-second leave-visibility destruction queue over canonical live records, cancels expiry for spatially resident or owned objects, and uses holtburger's conservative 384-unit envelope where ACE supplies no client-visible PVS leave event. The broader integration balances generation-scoped appearance/live/landblock/effect/UI/portal owners, bounds DAT/decoded/standalone/composite/atlas/mesh residency, performs incremental mesh-arena migration, and defers GL deletion or slot reuse through three frames of GPU fences. Render/UI scratch storage is reused with hysteretic trimming. No visual range or quality setting was reduced. The final seven-destination connected route cut peak working/private memory from 2,954.5/3,502.3 MiB to 1,493.4/1,969.5 MiB, returned Caul to 1,030.6/1,638.2 MiB, held 125–153 FPS locally through the final dwell, and produced no WER/driver reset. AP-69 is narrowed rather than retired because exact ObjCell PVS and explicit preview lifetimes remain. -
Render-thread CPU and high-refresh pacing correction 2026-07-19; automated/review/connected gates passed, final translucency visual gate pending. Software pacing now uses one reusable Windows high-resolution waitable timer instead of scheduler-quantized
Thread.Sleep; the permanent profiler attributes that wait aspace. Wb keeps exact stable far-to-near alpha order while using a radix key, avoids immediate SSBO packing for deferred transparent objects, packs each eight-light set, caches immutable selection descriptors, and retires historical material groups only after one complete unused frame across every landscape/dynamics/paperdoll draw scope. A fixed dense-Caul sample improved from roughly 9–12 ms CPU to 5.3–6.2 ms without reducing view distance, particles, textures, streaming radius, or other quality. The corrected post-review uncapped route waited for every materialization and held a full visibility sweep through one dedicated profiler window at all seven destinations. Returned dense Caul (about 21,024 visible entities) measured 3.0/4.9 ms CPU p50/p95 and 2.0/3.4 ms GPU while turning, then 3.8/5.0 ms CPU and 2.6/3.2 ms GPU stationary (241 FPS title sample). First/return Caul memory was 939.8/1,438.3 and 1,034.8/1,630.5 MiB working/private; the highest stationary sample was 1,065.0/1,660.6 MiB in Holtburg, and the client exited gracefully. With pacing disabled (pace=0) and title samples reaching 844 FPS, RDP did not refresh-cap engine throughput. UI automation now uses a command-local compensated clock, preserving sub-millisecond frame deltas and exact long-duration sleep/timeout boundaries.
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.
AudioEngineviaSilk.NET.OpenAL, inverse-square CPU falloff, 16-voice pool. SoundTable + Wave dat decoders. Motion-hook wiring for footsteps + attacks. Seer05-audio-sound.md. - E.3 — Particle system.
ParticleSystemon Silk.NET GL (port WorldBuilder'sParticleBatcher). 13 motion-type integrators.PhysicsScriptdispatcher tied to motion-hooks. Seer04-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 withPlayerDescription (0x0013)+ property-update events. Seer08-network-protocol-atlas.md. - F.2 — Item + inventory model.
ItemInstance/Container/PropertyBundle. Appraise round-trip (0x00C8→0x00C9). Burden math. Seer06-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. Seer01-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.mdunder retail-ui. (TargetsAcDream.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}andItemRepository.Itemsinto minimal ImGui dev panels underACDREAM_DEVTOOLS=1so the parsed data is observable in-game without a real retail-skin panel. Establishes the binding pattern (AcDream.UI.AbstractionsViewModels → 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.
- F.5a — Visible-at-login dev panels. First deliverable on top of #13 (PD trailer parser shipped 2026-05-10): wire
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.TryFindContainingCellAABB containment with retail'sCObjMaint::HandleObjectEnterCellportal traversal. When the player crosses a cell portal boundary,CellIdpropagates through theCEnvCellportal connectivity graph. Prerequisite for wall collision from outside (#85) and the remaining #84 threshold symptom. PDB symbols andacclient.hCCellStructurerefs are in place (see #87). Unblocks G.3. - ✓ SHIPPED — G.3 — Dungeon streaming + portal space.
EnvCellStreamer, portal-visibility BFS,PlayerTeleport (0xF751)handling with post-transitionLoginComplete, and the retail DAT-authored portal-space CreatureMode viewport (Setup0x02000306, animation0x030005AC, exact camera/light/timing/view-plane transitions, forward-axis roll, direct portal→world viewport switch, projection warp, destination viewer reset, object/physics-before-network frame barrier, pre-network input movement/jump output, post-inbound AutonomousPosition, non-advancing spatial reconciliation, and retailset_hiddenPartArray timeline teardown on both Hidden and UnHide;/lsand spell-recall corrective visual gates passed 2026-07-16, with non-blocking visual polish deferred). Dungeons render, stream, teleport-in, collide, light, and their doors work — see the shipped-table rows (G.3, G.3a, #137 collision, A7.L1 lighting) below and the M1.5 section ofdocs/plans/2026-05-12-milestones.mdfor current gate status (one recorded end-to-end dungeon round-trip user gate outstanding; live residual = far-town teleport-OUT arrival cascade, #145-residual REOPENED). Seer09-dungeon-portal-space.mdanddocs/research/2026-07-15-retail-portal-space-pseudocode.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. Seer10-quest-dialogs.md. - H.4 — Character creation.
0xE000002 CharGendat + 13 heritages + templates + appearance picker + preview renderer. Seer07-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 —
IPanelRendererwidget extension. AddsTextColored,Checkbox,Combo,InputTextSubmit,BeginTable+ ~9 more widget signatures toIPanelRendererand matchingImGuiPanelRendererimpls. Foundation for I.2 (DebugPanel) and I.4 (chat input). Commitb131514. - ✓ SHIPPED — I.2 — DebugPanel migration. Replaces the 473-LOC StbTrueTypeSharp
DebugOverlaywithAcDream.UI.Abstractions/Panels/Debug/DebugPanel(collapsing-headers + diagnostics checkboxes + combat-event tail).DebugOverlay.csdeleted;TextRenderer+BitmapFontretained for the future world-space HUD (D.6 — damage floaters, name plates). Commit56037a4. - ✓ SHIPPED — I.3 —
LiveCommandBus+WorldSession.Send{Talk,Tell,Channel}. ReplacesNullCommandBus.Instancewith a real handler-registryICommandBus. NewSendChatCmdrecord +ChatChannelKindenum +ChannelResolverlegacy-id mapping (per holtburger).WorldSession.SendTalk/SendTell/SendChannelare 3-line wrappers around existingChatRequests.BuildTalk/Tell/ChatChannel. Commit8e6e5a0. - ✓ SHIPPED — I.4 —
ChatPanelinput field + slash commands. Enter-to-submit input field onChatPanel;ChatInputParserrecognises/say/t/tell/r/g/f/a/m/p/v/cv/lfg/trade/role/society/olthoi;ChatVM.LastIncomingTellSendertracks for/rreply.ImGui.WantCaptureKeyboardalready suppresses WASD on input focus. Commitf14296c. - ✓ SHIPPED — I.5 — Holtburger inbound chat parity + Windows-1252.
EmoteText (0x01E0),SoulEmote (0x01E2),ServerMessage (0xF7E0),PlayerKilled (0x019E)parsers +WeenieErrorrouting throughGameEventWiring. Global string codec switch fromEncoding.ASCIItoEncoding.GetEncoding(1252)so accented names round-trip per retail + holtburger. Commitff5ed9e. - ✓ SHIPPED — I.6 — TurbineChat codec +
ChatChannelInfo. Full0xF7DEcodec with three payload variants (EventSendToRoom,RequestSendToRoomById,Response), UTF-16LE strings with variable-length prefix,SetTurbineChatChannels (0x0295)parser, unifiedChatChannelInfo(Legacy + Turbine variants),TurbineChatState. ACE doesn't host a TurbineChat server — codec is ready when retail-emulating servers exist. Commitca968fc. - ✓ SHIPPED — I.7 —
CombatChatTranslator. Retail-faithful combat-text formatters intoChatLog("You hit drudge for 50 slashing damage (87%)"). Subscribes to visible damage/evasion/miss/kill events;AttackDonewas removed from chat after named retail + ACE proved its nonzero final status is control-only. Commit3d26c8e, corrected 2026-07-11. - ✓ SHIPPED — I.8 — Docs alignment. Roadmap (this file) +
docs/ISSUES.mdissues #14-#20 closed +memory/project_chat_pipeline.mdcrib +MEMORY.mdindex entry +CLAUDE.mdUI 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.csis deleted;TextRenderer/BitmapFontstill compile for D.6. /say helloin 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
InputDispatcherwith scope stack,KeyChord/Binding/KeyBindings, Silk.NET adapters, parallel to existing handlers — no behavior change. Commit84512d3. - ✓ SHIPPED — K.1b — Cut handlers over to dispatcher. Drop the
legacy mouse-X-character-yaw path, fix
WantCaptureMousegating, single input path. Commit256e962. - ✓ 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 toCtrl+F*to avoid retail conflicts. Commitda18910. - ✓ 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(). Commitaf74eac. - ✓ SHIPPED — K.3 — Settings panel + Keybindings UI + roadmap/issues
update.
SettingsPanelwith click-to-rebind UX (modal capture viaInputDispatcher.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/SelectLeftper 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.CurrentModeis 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
SetCyclevsPlayActionrouting, add missingMotionCommandconstants, and split deathSanctuaryaction from persistentDeadsubstate. (Partial, shipped 2026-06-30: dualIMotionCommandCatalogseam —AceModernCommandCatalogruntime default + full-extractionRetail2013CommandCatalogconformance; deleted the blind 0x016E–0x0197 override; exposed + filed theCombatAnimationPlanner2013-numbering bug as ISSUES #159 — fixed 2026-07-042de5a011, the constants now derive directly fromDatReaderWriter.Enums.MotionCommandby name. Spec:docs/superpowers/specs/2026-06-30-movement-wire-parity-design.md.)* (D6.2a, shipped 2026-07-01: portedCMotionInterp::adjust_motion/apply_run_to_command/apply_raw_movementintoMotionInterpreterand unified the local velocity + keyboard turn + jump onto one input-builtRawMotionState→get_state_velocity— backward/strafe-left no longer zero, strafe is now retail-exact (~1.56×runRate, clamped ≤3.75), turn omega from interpretedturn_speed, hand-mirrored formulas deleted. User smoke sign-off 2026-07-01 (strafe-left moves + symmetric, backward outpaces strafe, jump lateral, turn feel unchanged; no crash/rejection). Retired register TS-22 + UN-5; added TS-34 (IsCreature guard no-op).WalkAnimSpeedunified to the retail-exact 3.11999989f. Pseudocode:docs/research/2026-07-01-d6-motion-interp-pseudocode.md.)* (D6.2b, shipped 2026-07-01: the wire now sends the retail-faithful RAWforward_speed=1.0(omitted by default-difference packing) instead of runRate. Echo-test settled the open question: ACE RECOMPUTES the broadcast run speed from run skill and auto-upgrades WalkForward+HoldKey.Run → RunForward — sending 1.0 still broadcasts RunForward @ runRate, and a retail observer saw +Acdream run at full pace. The staleMovementData.cs-citing "ACE relays" comment was corrected. Wire + velocity are now both raw-1.0; threading them onto one shared RawMotionState (removing section-6's duplicate build) is a no-op-behavior cleanup follow-up.) - 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 buildanddotnet testgreen 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, andNegPolyHitdispatch behavior. - L.2d — Shape fidelity: sphere/cylsphere/building objects. Finish
CSphere/CCylSphereparity, live-entity object shapes, building object collision identity, andSetup.Radiusfallback audit. - L.2e — Cell ownership: outdoor seams,
CELLARRAY,cell_bsp. Update low outdoor cell id across 24m seams, port adjacent-cell checks, activatecell_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.
- Phase R — retail motion & animation ground-up reconstruction (2026-07-02,
user mandate: total overhaul, verbatim, all entity classes, no frozen
code). Plan of record:
docs/plans/2026-07-02-retail-motion-animation-rewrite.md— stages R1 (CSequence) → R8 (cutover audit). SUPERSEDES the L.2g S3–S6 slices below; L.2g S1/S2/S5 (shipped 2026-07-02: staleness gate, inbound funnel + 183-case live-retail conformance harness, pace-guesser deletion) are absorbed as components. NOTE: this Phase R is unrelated to the retired "R1→R8 refactor sketch" from early 2026 — same letter, new plan. R1 SHIPPED 2026-07-02 (verbatim CSequence core + adapter rehost,a987cad1tail). R2 SHIPPED 2026-07-02 (Q0–Q6; Fix B/fast-path/ stop-anim-fallback/G17/RemoteMotionSink deleted, AP-73 retired). R3 SHIPPED 2026-07-03 (W0–W7: pending_motions + MotionDone consumer, jump family, ground transitions with K-fix18 DELETED, the ONE DoInterpretedMotion + zero-tick flush, LOCAL PLAYER unification — UpdatePlayerAnimation + the synthesis layer deleted; trail in the plan doc). R4 SHIPPED 2026-07-03 (V0–V6: the verbatim MoveToManager, all 33 members + conformance harness; mt 6/7/8/9 wire completion; remote AND local-player cutovers — RemoteMoveToDriver, PlanMoveToStart, and the whole B.6 auto-walk DELETED; the P1 autonomous-echo gate ported verbatim; TS-36 bound — input/jump/teleport cancel movetos through the retail interrupt chain; retired AD-8/AD-9/AP-8/AP-9/AD-26/TS-36; trail in the plan doc). R2+R3+R4 visual pass PASSED 2026-07-03 (#161–#163 closed; #164–#166 filed). R5-V1 SHIPPED 2026-07-033d89446d— PositionManager facade + StickyManager + ConstraintManager + the full TargetManager voyeur system ported to Core, fully tested, UNWIRED (purely additive; renamed the misnamedPhysics.PositionManagercombiner →RemoteMotionCombiner); ConstraintManager unarmed (issue #167, TS-35 stays). Decomp/ACE/plan:docs/research/2026-07-03-r5-managers/. R5-V2 SHIPPED 2026-07-03fffe90b3— wired the voyeur system per-entity viaEntityPhysicsHost, retired AP-79; verified live (remote creatures chase). Same session fixed two PRE-EXISTING streaming bugs surfaced while visual-gating V2 (the user's "world doesn't load" report): #168 pending-bucket trap (315af02f) + #169 cold-spawn streaming hole (9b06a9b8) — both verified live. Filed #170 (remote creature chase+attack animation divergence vs retail — glide/over-frequency/ uniform attacks; decomposes into #159 + pending_motions loop + dead-reckon glide). #170 CLOSED + gated 2026-07-04 (427332acre-dispatch flood +1051fc83armed-moveto-always-ticks). R5-V3 SHIPPED + gated 2026-07-04 (#171 sticky melee, three slices5bd2b8bc/7a823176/69966950— TS-39 retired, TS-43/TS-44/AP-82 filed). R5-V4 behavioral items SHIPPED + gated 2026-07-04f423884b(head style-on-change all mt types, #164 autonomy bit, mt-0 wire flags consumed). R5-V5 SHIPPED 2026-07-05 — the R5 arc is DONE: theMovementManagerfacade (retail acclient.h/* 3463 */, methods 0x00524000-0x00524790) now owns each entity's interp+moveto pair — ONE per entity (RemoteMotion.Movement,PlayerMovementController.Movement, chase-harness mirror); the three wiring sites construct throughMoveToFactory+MakeMoveToManager(0x00524000) and the relay shapes (UseTime0x005242f0,HitGround0x00524300,HandleExitWorld0x00524350,CancelMoveTo0x005241b0,HandleUpdateTarget0x00524790,PerformMovement0x005240d0) replace the loose-pair call sites. Structural, zero behavior change; 15 facade conformance tests; suite 4052 green with the 183-case/funnel/moveto/chase/sticky suites unmodified. Arc close-out:docs/research/2026-07-03-r5-managers/r5-wiring-handoff.md. Open follow-ons carried: #167 (ConstraintManager arming). TS-42's per-tick completion/ manager order retired in the first R6 cutover on 2026-07-19. The second R6 cutover now carries complete CSequence Frames (translation + DAT omega), one incarnation-stable live-object quantum clock across local, remote, Hidden, and projectile components, complete interpolation orientation, and no ObservedOmega/synthetic-turn side channel. It also ports the 96-unit PartArray Active gate plus immediate-activeprepare_to_enter_worldrebasing and preserves live identity across loaded/pending rebucketing. Semantic AnimationDone is in retail's pre-transition manager slot; TS-50 explicitly carries the remaining pose-dependent hook-router delivery delay. The Setup/state-drivenstatic_animating_objectsworkset is now separate as retail requires for both DAT and live physics-Static owners, including unconditional PartArray default installation, direct 30-fps DefaultAnimation, short residency-gap catch-up, and the greater-than-two- second discard. Live static entries reuse the canonical record PartArray and body;animate_static_objectapplies its raw omega without elapsed scaling and commits changed render/effect/collision roots together without zero-omega refloods or Hidden-shadow resurrection. No-animation live remotes remain in the canonical ordinary-object workset, and one animation-optional inbound dispatcher now owns their same retail UpdateMotion funnel and near-correction consumption.PerformMovementreactivates its owner before validation. A shared update-thread inbound FIFO now prevents synchronous App callbacks from interleaving packets or frame tails, while exact-record Position/State/Vector/Movement authority versions preserve retail's independent timestamp channels and a narrow velocity token resolves their one overlapping write. Projectile Begin/Complete prediction, teleport placement, landing callbacks, Hidden transitions, and deferred animation hooks all revalidate those exact ownership boundaries. Pose-owner lifetime versions are captured before AnimationDone and checked before each semantic completion and routed hook, closing delete/local-ID reuse during both capture and drain. Direct frame/packet FIFO dispatch and spatial animation update/render traversal are allocation-free after warmup; only real nested work allocates a retained queue node. TS-51 records the remaining shared particle/PhysicsScript tail timing. The ordinary live-object scheduler and generic body transition path now live outside GameWindow; body-backed animations, retained post-Missile bodies, visibility- edge clock/shadow residency (including initial pending materialization), callback-safe cross-cell rebucketing, and teardown all share the canonical live record. Local and authoritative remote collision publication now occurs only after exact-owner spatial revalidation; remote shadows refresh for translation, complete orientation, or cell change. Folding InterpolationManager into the owned PositionManager facade remains the final R6 ownership cleanup. Final 2026-07-20 gate: three independent retail/architecture/adversarial re-reviews are clean; Release build is warning-free; the repository suite is 6,446 passed / 5 skipped. A connected locomotion/collision/projectile/ teleport visual and performance rebaseline remains the next gate. - L.2g — Inbound motion interpretation (remote-entity CMotionInterp funnel).
ACTIVE 2026-07-02. Port retail's inbound motion pipeline verbatim for ALL
remote entities (players, NPCs, monsters — one funnel, user-approved):
move_to_interpreted_state(0x005289c0) +apply_interpreted_movement(0x00528600) +StopInterpretedMotionstop path +MotionTableManagerbookkeeping (remove_redundant_links/CheckForCompletedMotions/MotionDone), delete the non-retail UP-pace→cycle inference layer, restore the walk↔run link pose. Slice order S0–S6 + full deviation map (DEV-1..10, adversarially verified):docs/research/2026-07-02-inbound-motion-deviation-map.md. Acceptance: walk↔run toggle on an observed remote reacts at wire latency with no compounding animation/position desync; no stop-slide; user visual side-by-side vs retail observer.
Shipped (2026-06-30) — L.2b outbound wire parity (decomp-verbatim, tests-first):
- D1 —
MoveToStatenow packsRawMotionStateby retail default-difference (RawMotionState::Pack0x0051ed10): a field bit is set only when the value differs from its retail default, so over-sentforwardSpeed=1.0/holdKey=Noneare gone. NewAcDream.Core.Physics.RawMotionStatedata type +RawMotionStatePacker. - D3 — MoveToState trailing byte =
(standingLongjump?2:0)|(contact?1:0)(MoveToStatePack::Pack0x005168f0); explicitcontact/standingLongjumpparams. - D4 —
JumpActionrewritten to retailJumpPack(0x00516d10): extent · velocity · full Position · 4 timestamps; spurious objectGuid/spellId removed. - D5 — audited + confirmed divergent (MTS over-stamping vs retail
SendMovementEvent); left unchanged, recorded as register TS-33, deferred to a dedicated cadence-port slice. - Golden-byte tests (Core.Net.Tests) for all packers; full suite green.
- ACE smoke + user visual sign-off 2026-07-01: login → run/turn/RunLock all accepted (server echoes player motion, no rejection), NPCs animate incl. inbound MoveTo type-7 + TurnToHeading type-9 (no crash), graceful exit.
- Spec:
docs/superpowers/specs/2026-06-30-movement-wire-parity-design.md.
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 buildanddotnet testare 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
(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;
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
WorldSessioninto testable components:INetTransport,PacketCodec,ReliablePacketSession,FragmentSession,GameMessageSession, and the high-levelWorldSessionbehavior 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,RejectRetransmithandling, 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
GameMessageandGameActionrouting 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 buildanddotnet testare 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):
-
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.
-
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. -
"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.
-
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 atgithub.com/eriknihlen/WorldBuilder.gitregistered as git submodule atreferences/WorldBuilder/tracking theacdreambranch.AcDream.Core.csprojreferencesWorldBuilder.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 insideSceneryGenerator.Generate()with calls to WB'sSceneryHelpers+TerrainUtils. AdapterWbSceneryAdapterproducesTerrainEntry[]. Visual verification at Holtburg confirmed Issue #49's previously missing edge-vertex trees still visible after the migration; rotation bug fixed (our retail port'syawDeg = -(450-degrees)%360formula was ~180° off from retail's actualFrame::set_headingatan2 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/SampleSurfacePolygonwith WB'sTerrainUtils.GetHeight/GetNormal. Audit during N.3 follow-up uncovered that WB'sCalculateSplitDirectionuses a different formula than retail'sFSplitNESW(the AC2D-cited polynomial0x0CCAC033/0x421BE3BD/0x6C1AC587/0x519B8F25that 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'sGetNormalis 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, leaveTerrainSurfacealone. See ISSUE #51. - ✓ SHIPPED — N.3 — Texture decoding. Shipped 2026-05-08.
SurfaceDecodernow delegates INDEX16 / P8 / A8R8G8B8 / R8G8B8 / A8 to WB'sTextureHelpers.Fill*. The A8 divergence (our old code did R=G=B=A=val always; WB splits additive vs non-additive) was resolved by threading anisAdditiveparameter throughDecodeRenderSurface: terrain alpha masks passisAdditive: true(matches our prior behavior, preserves the shader's.rblend-weight read), entity surfaces passsurface.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; updatedSurfaceDecoderTeststo 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
ObjectMeshManageris integrated as the production mesh pipeline behindACDREAM_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, populatesAcSurfaceMetadataTablefor translucency / luminosity / fog),WbDrawDispatcher(production draw path — groups all visible (entity, batch) pairs, uploads matrices in a singleglBufferData, fires oneglDrawElementsInstancedBaseVertexBaseInstanceper group withBaseInstanceslicing the shared instance VBO), and theLandblockSpawnAdapter+EntitySpawnAdapterbridge that wires our streaming loader to WB'sIncrementRefCount/PrepareMeshDataAsynclifecycle (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. LegacyInstancedMeshRendererretained as flag-off fallback until N.6 fully retires it. Plan archived atdocs/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
WbDrawDispatcheronto 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-layerTexture2DArray+sampler2DArray; legacy callers retainTexture2Dvia the parallelTextureCachepath 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 atdocs/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
TerrainRenderManagerpattern but consumeLandblockMesh.Buildfor retail-formula compliance). Path A (substitute WB'sCalculateSplitDirection) killed during pre-implementation divergence test: WB's formula disagrees with retail'sFSplitNESW(addr00531d10) on 49.98% of cells acrosstests/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 viaLandblockMesh.Build→TerrainBlending.CalculateSplitDirection.TerrainModernRenderer+terrain_modern.vert/.fragshipped,TerrainChunkRenderer+TerrainRenderer+ legacyterrain.vert/.fragdeleted 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 oneglDrawElementsvia 16×16-LB chunking). Architectural wins (zeroglBindTexture/frame, constant-cost dispatch as A.5 raises radius, per-LB frustum cull) manifest at higher radius. Spec acceptance criterion #5 was wrong; amended viadocs/plans/2026-05-09-phase-n5b-perf-baseline.md. Plan archived atdocs/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 inTextureCachefor atlas-opportunity audit. Captured authoritative baseline at Holtburg radii 4 / 8 / 12 (standstill + walking) with the now-workinggpu_usdiagnostic. Plan + spec atdocs/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. - 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/sampler2Dpath inTextureCache(currently kept for Sky + Debug + particle paths now that Terrain has migrated); delete orphanmesh.frag(verify zero callers post-N.5 amendment); decide bindless-everywhere vs legacy-island for the remainingsampler2Dconsumers. 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+PortalRenderManageron 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+FrustumCullerwith WB'sVisibilityManager. Estimate: ~1 week (was 3-5 days, slight bump for streaming-loader interaction). - N.10 — GL infrastructure consolidation (optional). Replace our
Shader/TextureCache/SamplerCacheplumbing with WB'sManagedGL*wrappers +OpenGLGraphicsDevice. Largely subsumed by N.4 —OpenGLGraphicsDevicearrives as the host ofObjectMeshManagerand 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>=1flag. - 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
acdreambranch 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+ vendoredreferences/actestclient/. 1.4 M lines of named pseudo-C (99.6% function naming) + 70K lines of verbatim retail headers + 3,956 spells withFamilyfor buff stacking + machine-readable wire schema inmessages.xml. - ✓ SHIPPED — R.2 — pdb-extract tool + JSON sidecars. Shipped 2026-04-25
(commit
69d884a).tools/pdb-extract/pdb_extract.pyreadsrefs/acclient.pdb(Sept 2013 EoR build) and emitssymbols.json(18,366 named functions) +types.json(5,371 named struct types) todocs/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 byreferences/gitignore) includes the canonicalmessages.xmlAC-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.
Track MP — Modern Pipeline (performance side track; user-commissioned 2026-07-05)
⏸ PARKED 2026-07-05 (user decision) — WINS BANKED, REMAINDER DEFERRED TO THE FUTURE. The track's actual trigger — the FPS/ms counter fluctuating wildly (read by the user as inefficiency) — is RESOLVED: MP0 (frame profiler), MP1a (Content extraction), and MP-Alloc safe batch shipped and merged to local main (
093cdb6d, suite 4188 green, user-confirmed steady FPS). ECS (MP3) + the rest are DEFERRED: ECS is the throughput lever (raise avg FPS ~230→300), a DIFFERENT goal from steadiness — revisit ONLY if the user wants a higher raw FPS number and accepts the rewrite risk. Rust rejected. Parked (resume from the table below): MP1b pak/bake (needs the 865 GB EnvCell-dedup slice; loading-speed lever), MP-Alloc hard sites, MP1c, MP2. Full rationale:project_mp_track_findings.md. The 2026-07-18 portal/resource-lifetime repair does not resume MP's ECS, pak, or throughput redesign. It is corrective ownership/reclamation work for a reproduced crash-class regression in the mandatory renderer, using the current architecture and preserving pixels/ranges.
Spec: docs/superpowers/specs/2026-07-05-modern-pipeline-design.md (the
umbrella design — read it first). Goal: smoothness first (no frame over
~16 ms during any traversal), then 300+ FPS sustained in dense towns.
Perf-only per feedback_render_perf_not_faithfulness_gated — pixels and
game-feel stay identical; the simulation's retail-mirroring OO structure is
untouched. NOT part of M1.5 — runs in dedicated side-track sessions; M1.5
critical path wins every conflict.
Sequencing note (2026-07-05, evidence-driven): the steady-state allocation
triage (MP-Alloc) was pulled forward ahead of the pak cutover (MP1c) after a
54-site allocation audit proved the town GC churn is ~90–95% OUTSIDE the MP3 draw
surface (so MP3 won't fix it) and independent of the pak — it's the felt-most-often
smoothness tax and mostly easy faithfulness-neutral buffer-reuse work. See
project_mp_track_findings.md. Load-time smoothness (the pak, MP1c) is the rarer
hitch and follows.
| Phase | What | Status |
|---|---|---|
| MP0 | Honest frame profiler ([frame-prof]) + baseline capture |
✅ SHIPPED 2026-07-05 — gate PASSED (docs/research/2026-07-05-mp0-baseline.md); headline: town spikes = GC churn (1.5–3 MB/frame), teleport hitch 211 ms |
| MP1a | AcDream.Content extraction (GL-free MeshExtractor + boundary records out of App) |
✅ SHIPPED 2026-07-05 — user-gated (renders identical, zero tripwires, perf-neutral); 8 commits 651d041e..b0758d77 |
| MP1b | Pak format + acdream-bake CLI + mmap zero-copy PakReader + equivalence test |
🔵 code + review-fixes done; full-bake gate found 865 GB → EnvCell dedup slice REQUIRED before shipped (spec §6.6) |
| MP-Alloc (safe batch) | Reuse per-frame buffers: anim pose, particle draw-list, interior partition, animatedIds/drawableCells sets | ✅ SHIPPED + USER-GATED 2026-07-05 — dense-town frame-time spikes 20–87ms → 6–10ms, alloc spikes (30–75MB single-frame) eliminated, gen2 GC 5–11/window → ~0; user confirms FPS steady. 4 commits b8c05e2b..91afea24 |
| MP-Alloc (hard sites) | EnvCell settled-camera rebuild gate + physics Transition pooling — the residual steady ~1.6MB/frame |
⚪ OPTIONAL follow-up — would lower the median too, but the wild-swing complaint is already resolved; each needs its own careful gate (batch correctness / physics faithfulness) |
| MP1c | Streaming cutover to pak + hitch gate (vs the 211 ms baseline) | ⚪ after MP-Alloc |
| MP2 | Retail particle distance/cell-view degradation: DAT-authored range, exact infinite/finite off-view update branches, emitter-first render scan, user-requested 2× default range | ✅ SHIPPED 2026-07-17 — Aerlinthe hotspot 0x32000223 resolves to retail 64m and defaults to 128m; landscape/entity streaming distance unchanged |
| MP3 | Arch ECS render world + delta submission (the 300-FPS lever) | ⚪ — note: does NOT fix the steady-state GC churn (that's MP-Alloc); MP3 is the draw-submission throughput lever |
| MP4 | Zero-alloc frame loop + flat physics data (residual, post-MP-Alloc) | ⚪ hard-queued behind M1.5 #137 |
| MP5 | Job-system parallelism | ⚪ stretch, evidence-gated |
Track LH — Linux and headless automation (future; user-directed 2026-07-19)
PARKED / FUTURE — not part of active M3. Preserve this direction for deliberate architectural work after the current gameplay and UI milestones: a graphical Linux client, configurable retail-rooted distance LOD, and a true Linux headless host capable of running roughly 30 automated clients with minimal CPU and memory use.
The governing architectural rule is that a game session must not be a hidden
GameWindow. Authentication, networking, world state, movement, inventory,
combat, magic, chat, and gameplay commands/events become an instance-scoped,
presentation-independent runtime. The graphical client and bot host consume
that same runtime through explicit interfaces; they do not maintain separate
gameplay implementations.
| Phase | What | Future completion gate |
|---|---|---|
| LH0 | Extract the instance-scoped session/game runtime from presentation ownership | One session runs with renderer, UI, and audio either attached or absent; no static cross-session gameplay state |
| LH1 | Configurable graphical distance LOD, rooted in retail GfxObjDegradeInfo / viewer-distance behavior |
Retail/DAT-authored degradation is the fidelity baseline; users may configure detail bias or maximum-detail policy without reducing the streaming/view horizon. Integrate with MP2/MP3 ownership rather than creating a competing LOD system |
| LH2 | Linux graphical host and packaging | Portable frame pacing, paths, native dependencies, input, and audio; startup validates the mandatory modern-OpenGL extensions and supported driver matrix |
| LH3 | True Linux headless host | No window, X11/Wayland display, OpenGL context, textures, meshes, particles, audio, or gameplay UI; retain only protocol/game state and the collision/nearby semantic data required for navigation and decisions |
| LH4 | Multi-session bot host, targeting approximately 30 concurrent clients in one process | Sessions isolate credentials, GUID maps, clocks, queues, plugins/behaviors, and teardown while sharing only immutable/read-only DAT and collision content caches; one-session-per-process remains a diagnostic/isolation option |
| LH5 | Bot automation surface | Actions cover movement, targeting, use, combat, casting, looting, chat, and commands; events cover object/session/portal, inventory, damage, and target lifecycle; deterministic scheduling and server-safe rate limiting are testable |
Track acceptance requires 30 connected Linux headless sessions with no GPU or display-server dependency, bounded per-session CPU/memory growth, clean connect/disconnect/reconnect teardown, credential-safe diagnostics, and parity tests proving that GUI and headless hosts drive the same gameplay runtime. LOD has no role in headless mode because no scene is rendered.
Native macOS graphical support is not committed by this track. The current mandatory renderer requires modern OpenGL capabilities beyond Apple's native OpenGL ceiling; revisit macOS only if a supported graphics backend is chosen.
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.mdis updated when a phase ships.MEMORY.mdis the index. CLAUDE.mddiscipline. 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.
- Native macOS graphical support — not promised; Track LH covers future Linux graphical and headless hosts, while macOS needs a renderer backend compatible with Apple's platform limits.
- 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 — SHIPPED (render/stream/teleport-in/collide/light/doors all closed; M1.5 next step = #138 round-trip gate + #145-residual) |
| 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.