Records a new Phase A sub-piece: split the single ACDREAM_STREAM_RADIUS into separate terrain + entity radii so terrain renders to a much further horizon (WB-style) while entities/scenery stay at the current closer radius. Motivated by perf at ACDREAM_STREAM_RADIUS=5 dropping from ~810 fps to ~200-300 fps because everything stays full-detail. Both retail and WorldBuilder render terrain way out and strip entities at distance. Estimate: 3-5 days for the radius split + fog tuning; +1 week if terrain LOD via mesh decimation is included. Not yet brainstormed. N.8 (sky + particles via WB's SkyboxRenderManager + ParticleEmitterRenderer) was already on the roadmap; user confirmed they want it tracked there. No edit needed for N.8 — already at the right level of detail. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
67 KiB
acdream — strategic roadmap
Status: Living document. Updated 2026-05-08 for Phase N.5 shipping (bindless textures + glMultiDrawElementsIndirect on top of N.4's foundation; CPU dispatcher 1.23ms/frame at Holtburg, ~810 fps) + N.6 becomes the new in-flight phase (retire legacy renderers + perf polish).
Purpose: One source of truth for where the project is and where it's going. Every observed defect or missing feature has a named phase that owns it; when something looks wrong in-game, look here to find the phase that'll address it. Implementation details live in per-phase specs under docs/superpowers/specs/, not in this file.
Phases already shipped
| Phase | What landed | Verification |
|---|---|---|
| 1 | Terrain rendering, plugin host scaffold | Visual ✓ |
| 2a | Static stabs/buildings (126 entities) | Visual ✓ |
| 2b | Textured 3×3 landblock grid + FlyCamera + IGameState | Visual ✓ |
| 2c | Procedural scenery (419 trees/rocks/bushes) | Visual ✓ |
| 2d | Interior EnvCell walker (475 static interior objects) | Visual ✓ |
| 3a/3b | Directional sun lighting + per-vertex terrain normals | Visual ✓ |
| 3c | Per-cell terrain texture blending (alpha atlas) | Visual ✓ |
| 4 | Full UDP codec + handshake + character login + WorldSession | Live ✓ |
| 5 | ObjDesc: AnimPartChange + TextureChanges + SubPalettes + ObjScale + Placement.Resting | Live ✓ |
| 6.1 | Idle motion frame resolution (MotionResolver MVP) | Live ✓ |
| 6.2 | Server-sent MovementData stance + forward command honored |
Live ✓ |
| 6.3 | Server-supplied MotionTableId override (fixes drudge statue) |
Live ✓ |
| 6.4 | Per-frame animation playback (breathing, idle cycles) | Live ✓ |
| 6.5 | Slerp between keyframes for smooth animation | Live ✓ |
| 6.6 | UpdateMotion (0xF74C) parser + dispatch to animation tick |
Live ✓ |
| 6.7 | UpdatePosition (0xF748) parser + position reseating |
Live ✓ |
| 7.1 | EnvCell room geometry — walls/floors/ceilings via CellStruct + Environment dats | Visual ✓ |
| 9.1 | Translucent render pass (AlphaBlend / Additive / InvAlpha + per-kind blend funcs) | Visual ✓ |
| 9.2 | Back-face culling in translucent pass (fixes lifestone crystal) | Visual ✓ |
| A.1 | Streaming landblock loader — runtime-configurable visible window (default 5×5, ACDREAM_STREAM_RADIUS), camera-centered offline / player-centered live, hysteresis-based unloads, pending-spawn list for late CreateObject events |
Live ✓ |
| A.2 | Frustum culling — per-landblock AABB test (Gribb-Hartmann), terrain + static-mesh renderers skip culled landblocks, perf overlay in window title | Visual ✓ |
| A.3 | Background net receive thread — dedicated daemon thread buffers UDP into Channel, render thread drains | Visual ✓ |
| B.3 | Physics MVP resolver foundation — terrain contact, CellSurface prototype, streaming-populated collision inputs, and first PhysicsEngine resolver path. Not the complete retail collision system. |
Tests ✓ |
| B.2 | Player movement mode — Tab-toggled WASD ground walking, walk/run/idle animations, third-person chase camera, MoveToState + AutonomousPosition outbound, portal entry. Outdoor-only MVP. | Live ✓ |
| D.1 | 2D ortho overlay + font rendering (StbTrueTypeSharp atlas + TextRenderer + DebugOverlay) | Visual ✓ |
| E.1 | Motion-hook expansion — AnimationSequencer fires all 27 hook types per crossed frame; PosFrames root motion + vel/omega exposure; IAnimationHookSink + AnimationHookRouter fan-out | Tests ✓ |
| E.2 | Audio engine — OpenAL 16-voice 3D pool with retail-faithful quieter-slot eviction, SoundTable cookbook (probability-weighted variant picking), Wave PCM decoder, AudioHookSink wiring | Tests ✓ |
| E.3 | Particle system (data layer) — ParticleSystem with 13 motion integrators, EmitterDescRegistry, ParticleHookSink wiring all CreateParticle / DestroyParticle / StopParticle hooks | Tests ✓ |
| E.4 | Combat notifications + outbound — AttackTargetRequest (0x0008), 7 combat notification parsers (Victim/Defender/Attacker/Evasion/AttackDone/UpdateHealth), CombatState per-entity health tracker | Tests ✓ |
| E.5 | Spell cast wire — CastSpellRequest targeted (0x004A) + untargeted (0x0048), Spellbook (learned spells + active-enchantment layers), 5 enchantment GameEvent parsers | Tests ✓ |
| F.1 | GameEvent (0xF7B0) envelope dispatcher — all 94 sub-opcodes routed, exception-isolated handler registry, unhandled-count diagnostic bag; 18 event-payload parsers | Tests ✓ |
| F.2 | Item model + Appraise — ItemRepository with move/equip/property events, AppraiseRequest (0x00C8), IdentifyObjectResponse header, WieldObject + InventoryPutObjInContainer | Tests ✓ |
| G.1 | Sky + day/night — DerethDateTime (retail-exact 7620-tick calendar + 16-hour names + PY year), SkyStateProvider (4-keyframe default with angular-wrap lerp), WorldTimeService (server-synced clock with real-time advance) | Tests ✓ |
| G.2 | Dynamic lighting (selection) — LightSource + LightManager with retail 8-light cap, range-squared with 1.1× slack, slot 0 reserved for Sun, OwnerId-keyed unregister | Tests ✓ |
| G.1+ | Full sky visuals + weather + dynamic-light shader — SkyDescLoader parses Region 0x13000000 dat keyframes with retail fog fields (start/end/mode); WeatherSystem picks Clear/Overcast/Rain/Snow/Storm deterministically per in-game day with 10s fade; SkyRenderer draws far-plane-1e6 celestial meshes with UV scroll; SceneLightingUbo binds at std140 location=1 with 8 Light slots + fog + lightning flash; terrain.vert + mesh.frag + mesh_instanced.frag + sky.frag all consume the shared UBO; LightingHookSink auto-registers Setup.Lights per entity + flips IsLit on SetLightHook; ParticleRenderer renders rain/snow billboards; F7 cycles day time override, F10 cycles weather; WorldSession surfaces server time via ServerTimeUpdated (ConnectRequest + TimeSync flag) | Tests ✓ |
| H.1 | Chat window — wire layer + panel + outbound input + holtburger inbound parity all shipped (I.1-I.7). Talk (0x0015) / Tell (0x005D) / ChatChannel (0x0147) outbound + EmoteText (0x01E0) / SoulEmote (0x01E2) / ServerMessage (0xF7E0) / PlayerKilled (0x019E) / TurbineChat (0xF7DE) / SetTurbineChatChannels (0x0295) inbound; ChatPanel with Enter-to-submit input + slash commands; CombatChatTranslator posts combat events as chat lines. |
Live ✓ |
| Glue | GameEventWiring.WireAll — single-call registration mapping parsed GameEvents → Core state classes (ChatLog, CombatState, Spellbook, ItemRepository); GameWindow exposes state classes + wires them to live session | Tests ✓ |
| D.2a | UI scaffold — AcDream.UI.Abstractions stable contract (IPanel / IPanelHost / IPanelRenderer / ICommandBus + VitalsVM / VitalsPanel); AcDream.UI.ImGui backend on ImGui.NET + Silk.NET.OpenGL.Extensions.ImGui (pivoted from Hexa.NET.ImGui on 2026-04-25 — Hexa's native OpenGL3 backend resolves GL via GLFW/SDL and crashed 0xC0000005 without them); VitalsPanel wired into GameWindow behind ACDREAM_DEVTOOLS=1 with ImGui.WantCaptureKeyboard WASD suppression. 11 new tests. |
Live ✓ |
| I.1 | IPanelRenderer widget extension — TextColored / Checkbox / Combo / InputTextSubmit / BeginTable + ~9 more widget signatures on IPanelRenderer; matching ImGuiPanelRenderer impls. Foundation for I.2 + I.4. |
Tests ✓ |
| I.2 | DebugPanel migration — replaced 473-LOC StbTrueTypeSharp DebugOverlay with AcDream.UI.Abstractions/Panels/Debug/DebugPanel (collapsing-headers + diagnostics checkboxes + combat-event tail). DebugOverlay.cs deleted; TextRenderer + BitmapFont retained for the future world-space HUD (D.6). |
Live ✓ |
| I.3 | LiveCommandBus + WorldSession.SendTalk / SendTell / SendChannel — replaces NullCommandBus.Instance with a real handler-registry ICommandBus. New SendChatCmd record + ChannelResolver legacy-id mapping (per holtburger). 3-line wrappers around existing ChatRequests.BuildTalk/Tell/ChatChannel. |
Tests ✓ |
| I.4 | ChatPanel input field + slash commands — Enter-to-submit input field; ChatInputParser recognises /say /t /tell /r /g /f /a /m /p /v /cv /lfg /trade /role /society /olthoi; ChatVM.LastIncomingTellSender tracks for /r reply. ImGui.WantCaptureKeyboard already suppresses WASD on focus. |
Live ✓ |
| I.5 | Holtburger inbound chat parity + Windows-1252 codec — EmoteText (0x01E0), SoulEmote (0x01E2), ServerMessage (0xF7E0), PlayerKilled (0x019E) parsers + WeenieError routing through GameEventWiring. Global string codec switch from Encoding.ASCII to Encoding.GetEncoding(1252) so accented names round-trip per retail + holtburger. |
Tests ✓ |
| I.6 | TurbineChat codec + ChatChannelInfo — full 0xF7DE codec with three payload variants (EventSendToRoom, RequestSendToRoomById, Response), UTF-16LE strings with variable-length prefix, SetTurbineChatChannels (0x0295) parser, unified ChatChannelInfo (Legacy + Turbine variants), TurbineChatState. ACE doesn't host a TurbineChat server — codec is ready when retail-emulating servers exist. |
Tests ✓ |
| I.7 | CombatChatTranslator — retail-faithful combat-text formatters into ChatLog ("You hit drudge for 50 slashing damage (87%)"). Subscribes to CombatState's DamageTaken / DamageDealtAccepted / EvadedIncoming / MissedOutgoing / AttackDone / KillLanded; templates ported verbatim from holtburger panels/chat.rs:221-308. |
Tests ✓ |
| K | Input architecture — Action enum, KeyChord, KeyBindings, multicast InputDispatcher with scope-stack + modal capture, retail-default keymap (152 bindings), keybinds.json persistence, F11 Settings panel with click-to-rebind + conflict detection, main menu bar + View menu |
Live ✓ |
| L.0 | Full retail-style Settings interface — F11 tabbed panel with 6 tabs (Keybinds + Display + Audio + Gameplay + Chat + Character). settings.json at %LOCALAPPDATA%\acdream\, per-toon Character keying (swapped on EnterWorld). Display GL knobs (Resolution / Fullscreen / VSync / FOV / ShowFps) + Audio (Master / SFX) live-wired; Gameplay / Chat / Character settings persist for server-sync wiring later. Tab API extension to IPanelRenderer; chat Copy mode (read-only multi-line); per-panel layout reset; FramebufferResize handler keeps GL viewport + camera aspect + panel positions in sync. |
Live ✓ |
| C.1 | PES particle system + sky-pass refinements — retail-faithful ParticleEmitterInfo unpack with all 13 motion integrators (Particle::Init/Update ports of 0x0051c290/0x0051c930), PhysicsScriptRunner with CallPES self-loop semantics, ParticleHookSink with EmitterDied cleanup, instanced billboard ParticleRenderer with material-derived blend (DAT emitters never default additive — pulled from particle GfxObj surface), global back-to-front sort, BC clipmap alpha-keying, AttachLocal is_parent_local=1 live-parent follow via UpdateEmitterAnchor. Sky pass: Translucent+ClipMap → alpha-blend cloud sheet (matches D3DPolyRender::SetSurface 0x0059c4d0), raw-Additive fog-skip (matches 0x0059c882), per-keyframe SkyObjectReplace Translucency/Luminosity/MaxBright divide-by-100, bit 0x01 pre/post-scene split (matches GameSky::CreateDeletePhysicsObjects 0x005073c0), Setup-backed (0x020xxxxx) sky objects via SetupMesh.Flatten, persistent GL sampler objects (Wrap + ClampToEdge) replace per-frame wrap-mode mutation (ported from WorldBuilder's OpenGLGraphicsDevice), post-scene Z-offset gated on (Properties & 4) != 0 && (Properties & 8) == 0 per GameSky::UpdatePosition 0x00506dd0. Sky-PES playback disabled by default (named-retail proves GameSky drops pes_id); ACDREAM_ENABLE_SKY_PES=1 opens the experimental path. 1325 → 1331 tests. |
Live ✓ |
| N.1 | WorldBuilder-backed scenery (Chorizite/WorldBuilder fork as submodule, SceneryHelpers + TerrainUtils replace our inline ports) | Live ✓ |
| N.3 | WorldBuilder-backed texture decode — SurfaceDecoder delegates INDEX16 / P8 / A8R8G8B8 / R8G8B8 / A8(+Additive) to TextureHelpers.Fill*; isAdditive threaded through (terrain alpha → FillA8Additive, non-additive entity surfaces → FillA8). R5G6B5 + A4R4G4B4 newly handled (previously magenta). X8R8G8B8, DXT1/3/5, SolidColor remain ours (no WB equivalent). 9 conformance tests prove byte-identical equivalence per format. |
Live ✓ |
| N.4 | Rendering pipeline foundation — adopted WB's ObjectMeshManager as the production mesh pipeline behind ACDREAM_USE_WB_FOUNDATION (default-on). WbMeshAdapter is the single seam (owns ObjectMeshManager, drains the staged-upload queue per frame, populates AcSurfaceMetadataTable with per-batch translucency / luminosity / fog metadata). WbDrawDispatcher is the production draw path: groups all visible (entity, batch) pairs, single-uploads the matrix buffer, fires one glDrawElementsInstancedBaseVertexBaseInstance per group with BaseInstance slicing into the shared instance VBO. LandblockSpawnAdapter + EntitySpawnAdapter bridge spawn lifecycle to WB ref-counts (atlas tier vs per-instance). Perf wins shipped as part of N.4: per-entity frustum cull, opaque front-to-back sort, palette-hash memoization (compute once per entity, reuse across batches). Visual verification at Holtburg passed: scenery + connected characters with full close-detail geometry (Issue #47 regression resolved). Legacy InstancedMeshRenderer retained as ACDREAM_USE_WB_FOUNDATION=0 escape hatch until N.6 (retired early in N.5 ship amendment). |
Live ✓ |
| N.5 | Modern rendering path — lifted WbDrawDispatcher onto bindless textures (GL_ARB_bindless_texture) + glMultiDrawElementsIndirect. Per-frame entity rendering: 3 SSBO uploads (instance matrices @ binding=0, batch data @ binding=1, indirect commands) + 2 indirect draw calls (opaque + transparent). ~12-15 GL calls per frame regardless of group count, down from hundreds-of-per-group in N.4. CPU dispatcher: 1.23 ms/frame median at Holtburg courtyard (1662 groups, ~810 fps sustained). All textures on the WB modern path use 1-layer Texture2DArray + sampler2DArray. Legacy callers keep Texture2D / sampler2D via the parallel TextureCache path until N.6 retires them. Three gotchas captured in memory: texture target lock-in, bindless Dispose order (two-phase non-resident before delete), GL_TIME_ELAPSED double-buffering. Ship amendment 2026-05-08: legacy renderers (InstancedMeshRenderer, StaticMeshRenderer, WbFoundationFlag) retired within N.5 — modern path is mandatory; missing bindless throws NotSupportedException at startup. N.6 scope narrowed accordingly. Plan archived at docs/superpowers/plans/2026-05-08-phase-n5-modern-rendering.md. |
Live ✓ |
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 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.
- A.5 — Two-tier streaming + terrain horizon LOD. Split
ACDREAM_STREAM_RADIUSinto two:ACDREAM_TERRAIN_RADIUS(large, 8-12 cells = 1.5-2.3km) for terrain mesh +ACDREAM_ENTITY_RADIUS(small, 2-3 cells, current default) for entities + scenery. Distant landblocks render terrain only — no NPCs, no procedural scenery, no static objects. TuneSceneLightingUbo'suFogParamsso the far edge fades into sky color (eliminates the hard streaming boundary visible at higher radii). Optional: terrain LOD via mesh decimation for very distant chunks (combine 2×2 landblocks into one decimated mesh; cribs fromreferences/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/TerrainRenderManager.cs). Motivation: at radius=5 today, perf scales from ~810 fps → ~200-300 fps because everything stays full-detail; both retail and WorldBuilder render terrain way out and strip entities/scenery at distance. Enables WB-style horizon visibility. Estimate: 3-5 days for the radius split + fog tuning; +1 week if terrain LOD is included. Not yet brainstormed.
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.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. - D.2b — Custom retail-look backend. Implements the same
IPanel/IPanelRenderercontracts using a custom retained-mode toolkit sourced from retail dat assets. Requires D.2a shipped. Panels get reskinned one at a time; ImGui stays as theACDREAM_DEVTOOLS=1overlay forever. The original 2026-04-17 scaffold research (UiRoot/UiElement/UiPanel/UiHost+ retail event codes + focus / drag-drop state machine +WorldMouseFallThrough) is the implementation foundation here — seedocs/research/retail-ui/. - 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.) - D.4 — Dat sprites + 9-slice panel backgrounds. Load
RenderSurface(0x06xxxxxx) as GL textures; addDrawSpritetoUiRenderContext. Enables retail panel art. (D.2b dependency.) - D.5 — Core panels. Attributes (
chunk_00470000.c:FUN_0047ba70), Skills (same), Paperdoll (chunk_004A0000.c:FUN_004A5200), Inventory, Spellbook (chunk_004C0000.c), Fellowship, Allegiance. Each uses the port sketches in slice 05. (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. - D.6 — HUD. Vital orbs (scissor-rect partial fill, dat sprites
0x060013B2), radar (0x06001388/0x06004CC1, 1.18× range factor), compass strip (scrolling U), target name plate, damage floaters, selection indicator. See slice 06. (TargetsAcDream.UI.Abstractions— ships with D.2a; reskinned by D.2b.) Phase I.2 retired the StbTrueTypeSharpDebugOverlaybut keptTextRenderer+BitmapFontalive specifically for D.6's world-space HUD elements (damage floaters, name plates) — they need raw GL text drawing that ImGui can't reach into the 3D scene. - D.7 — Cursor manager. OS + dat-sourced custom cursors (
FUN_0043c1c0GDI HCURSOR builder pattern from slice 03). (D.2b dependency.) 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.
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.)
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. - G.3 — Dungeon streaming + portal space.
EnvCellStreamer, portal-visibility BFS,PlayerTeleport (0xF751)handling withLoginCompletere-send, "pink bubble" loading state. Blocked on L.2e for trustworthycell_bsp, indoor/outdoor portal transit, adjacent-cell ownership, and building entry/exit collision boundaries. Seer09-dungeon-portal-space.md.
Acceptance: walk outside at dusk, see the sky gradient + sun moving; enter a torch-lit dungeon via portal; leave back to daylight.
Phase H — Social + progression
Research: R7 + R10 + R11 + UI slice 05.
- ✓ SHIPPED — H.1 — Chat window. UI panel + all 6 wire opcodes (Channel, Tell, System, HearSpeech, HearRangedSpeech, TurbineChat). Wire layer + panel + outbound input + holtburger inbound parity + combat translator all shipped across I.1-I.7 on 2026-04-25. Targets
AcDream.UI.Abstractions; will be reskinned when D.2b's custom retail-look toolkit lands. - H.2 — Allegiance. Tree model + XP pass-up math + 5 allegiance chat channels + MOTD. See
r11-allegiance.md. - H.3 — Emote scripts + quests + dialogs. 122 EmoteType × 39 Trigger mini-VM. Contract tracker UI. NPC dialog rendered via chat with
<Tell:…>markup. 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 toCombatState'sDamageTaken/DamageDealtAccepted/EvadedIncoming/MissedOutgoing/AttackDone/KillLanded; templates ported verbatim from holtburgerpanels/chat.rs:221-308. Commit3d26c8e. - ✓ 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. - 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.
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: create
docs/superpowers/specs/2026-05-02-network-stack-conformance.md before
implementation starts. Treat holtburger as the client-behavior oracle for this
phase; cross-check wire details against named retail, ACE, Chorizite, and AC2D
before porting.
Sub-lanes:
- M.1 — Audit & parity map. Produce a source-by-source comparison of
acdream
AcDream.Core.Netand holtburgerholtburger-session,holtburger-protocol, andholtburger-corenetworking code. Inventory each packet flag, optional header, session transition, control packet, fragment path, game message, and game action. Mark each asparity,partial,missing, orintentional divergence. - 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. - N.5b — Terrain rendering on N.5 path. Wire WB's
TerrainRenderManager+LandSurfaceManager+TerrainGeometryGeneratoronto the modern rendering path. Closes N.2's deferred terrain math substitution: visual mesh and physics both switch to WB'sCalculateSplitDirection+GetHeight+GetNormalin lockstep, resolving ISSUE #51. Estimate: 1-2 weeks (was 2-3 — modern path primitives already in place from N.5). - N.6 — Perf polish. Currently in flight.
Builds on N.5. Legacy renderer retirement was pulled forward into N.5
ship amendment —
InstancedMeshRenderer,StaticMeshRenderer, andWbFoundationFlagare already gone. N.6 scope: WB atlas adoption for memory savings on shared content, persistent-mapped buffers ifglBufferDatashows up in profiling, GPU-side culling via compute pre-pass, GL_TIME_ELAPSED query double-buffering (deferred from N.5 — diagnostic showsgpu_us=0/0underACDREAM_WB_DIAG=1), direct N.4 vs N.5 perf measurement, retire the legacyTexture2D/sampler2Dpath inTextureCache(currently kept for Sky + Terrain + Debug). Plan + spec written when work begins. Estimate: 1-2 weeks. - 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.
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.
- Cross-platform support — Windows-only; the dat path assumptions depend on retail Windows install layout. Silk.NET is cross-platform but we don't promise.
- Custom game content — this is a client for existing AC data, not a toolchain.
"When will my specific complaint be fixed?" — quick lookup
| Observation | Phase |
|---|---|
| Drudge statue in wrong pose | 6.3 FIXED ✓ |
| Characters in T-pose / wrong idle | 6.1 FIXED ✓ |
| No breathing on NPCs | 6.4 + sentinel fix FIXED ✓ |
| Lifestone crystal has one side missing | 9.2 FIXED ✓ |
| Ground floor flickering with terrain | 7.1 FIXED ✓ |
| Houses missing second floors / walls | 7.1 FIXED ✓ (interior mesh landed) |
| Character clothing missing / wrong | 5 FIXED ✓ |
| Statue wrong color / wrong scale | 5 FIXED ✓ |
| Holtburg sign half-buried | 5 FIXED ✓ |
| Can't walk past the loaded 3×3 window | A.1 FIXED ✓ (5×5 default, ACDREAM_STREAM_RADIUS to tune) |
| Frame hitch crossing landblock boundary | Phase A.3 (synchronous loader for now; async returns when DatCollection is thread-safe) |
| Walking around doesn't move me on the server | Phase B.2/B.3 FIXED ✓ for coarse server movement; fine retail collision parity is Phase L.2 |
| Packet loss / out-of-order UDP causes stale or missing world updates | Phase M.3 |
| Server asks for retransmit and client doesn't resend cached packets | Phase M.3 |
| ACKs are standalone-only instead of piggybacked like a full client | Phase M.4 |
| Echo / idle keepalive / reconnect behavior is incomplete | Phase M.4 + M.7 |
| Large outbound game messages exceed the current single-fragment path | Phase M.5 |
| Network protocol coverage is spread across ad-hoc builders/parsers | Phase M.6 |
| Sliding along buildings / walls feels wrong | Phase L.2c + L.2d |
| Roof edge / cliff / precipice blocks or slides wrong | Phase L.2c |
| Crossing outdoor cell seams reports the wrong cell | Phase L.2e |
| Can't talk to NPCs | Phase H.3 (emote scripts + dialogs) |
| Can't open a door | Phase F (object-use action) |
| Portals render as a rotating black disk | Phase E.3 (particle system) |
| Chimneys have no smoke | Phase E.3 |
| Houses have no fireplace fire | Phase E.3 |
| No fireplace / torch lighting | Phase G.2 (shipped; Setup.Lights auto-register, 8-light cap with hard-cutoff) |
| Skin/hair color slightly off | Phase C.3 |
| No chat window | Phase H.1 / I.1-I.7 FIXED ✓ |
| Chat? You can finally type and send messages | I.4 FIXED ✓ (shipped 2026-04-25) |
| Combat doesn't show in the chat log | I.7 FIXED ✓ |
Accented character names show as ? or garbled |
I.5 FIXED ✓ (Windows-1252 codec) |
| No sound | Phase E.2 |
| Dungeons / foundry interior missing | Phase G.3 after L.2e cell/building ownership |
| Can't fight monsters | Phase F.3 (combat math + damage) |
| Can't cast spells | Phase F.4 |
| No inventory panel | Phase F.2 + F.5 |
| No character creation — must use ACE admin | Phase H.4 |
| Sky is a flat color | Phase G.1 (shipped; F7 cycles time, F10 cycles weather) |
| Can't join allegiance | Phase H.2 |
| No quest tracker | Phase H.3 |
If you see something not on this list, add it here and assign a phase.