acdream/docs/plans/2026-04-11-roadmap.md
Erik 2ed790e007 docs: mark Phase G.1+G.2 full visual stack as shipped
Update the roadmap's 'shipped' table with the G.1+ entry covering the
end-to-end visual integration (sky renderer, weather system, particle
renderer, UBO-backed shader lighting, server time sync) — not just the
data-plumbing layer that went out yesterday.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 10:54:33 +02:00

22 KiB
Raw Blame History

acdream — strategic roadmap

Status: Living document. Updated 2026-04-11 after Phase 6, 7.1, 9.1, 9.2 landed. 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 collision engine — TerrainSurface (heightmap Z), CellSurface (indoor floor polygon projection), PhysicsEngine (resolver with step-height + cell transitions). Populated from streaming pipeline. 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 wire layer — Talk (0x0015) / Tell (0x005D) / ChatChannel (0x0147) outbound, HearSpeech (0x02BB local + 0x02BC ranged) inbound, ChatLog ring buffer with adapters for every chat source Tests ✓
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 ✓

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

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 collision engine. TerrainSurface (heightmap bilinear Z), CellSurface (indoor floor polygon projection via barycentric interpolation), PhysicsEngine (top-level resolver with step-height enforcement, outdoor↔indoor cell transitions, gravity reporting). Populated from streaming pipeline. 16 unit tests with fake data. Spec: docs/superpowers/specs/2026-04-12-physics-collision-engine-design.md.
  • B.4 — Use / UseWithTarget / PickUp. Outbound interaction messages. Drives opening doors, looting, talking to vendors.
  • B.5 — Chat. SendTell, SendChat outbound + receive/display inbound (display side depends on Phase D.1).

References:

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

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


Phase C — Polish / visuals

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

Sub-pieces:

  • C.1 — VFX / particle system. PhysicsScript parser, per-entity ParticleEmitter state, billboarded-quad particle renderer that lives in the Phase 9.1/9.2 translucent pass. Delivers portal swirls, chimney smoke, and fireplace flames in one implementation.
  • C.2 — Dynamic point lights. Fireplaces and lamps need local lighting; small upgrade to the mesh shader to accumulate N (e.g., 4) nearest point lights per draw. Uniform-buffer or UBO-friendly layout.
  • C.3 — Palette range tuning. Small per-range offset/length tweaks to match retail skin/hair/eye colors. Mostly diff and verify work, no architecture change.
  • C.4 — Double-sided translucent polys. Edge case left by Phase 9.2: neg-side translucent polys are culled because cull is always BACK. Fix by tracking per-sub-mesh CullMode and flipping GL state per draw (or drawing twice with opposite cull). Minor.
  • C.5 — Shadow mapping (optional). Deferred unless it becomes a bottleneck in screenshots — dynamic shadows are a known complexity trap.

References:

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

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


Phase D — UI / HUD + Sound

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

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).
  • D.2 — Retail UI framework + first panels. Research + scaffold landed 2026-04-17 (see docs/research/retail-ui/). Ships:
    • UiRoot + UiElement + UiPanel + UiHost with retail-faithful event codes (0x01 click, 0x15 drag-begin, 0x3E drop, 0x201 WM_LBUTTONDOWN, tooltip delay ~1000ms, etc.)
    • Focus / modal / capture / drag-drop / hover state machine
    • WorldMouseFallThrough / WorldKeyFallThrough preserves existing camera+player controls
    • First concrete panel (chat window) uses wire messages Phase 4.7 already parses
  • D.3 — AcFont from portal.dat. Replace stb_truetype system font with retail Font DBObjs (0x40000000..0x40000FFF) baked from RenderSurface source sheets — see research slice 03 §4. Preserves retail visual identity.
  • D.4 — Dat sprites + 9-slice panel backgrounds. Load RenderSurface (0x06xxxxxx) as GL textures; add DrawSprite to UiRenderContext. Enables retail panel art.
  • 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.
  • 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.
  • D.7 — Cursor manager. OS + dat-sourced custom cursors (FUN_0043c1c0 GDI HCURSOR builder pattern from slice 03).
  • D.8 — Sound. SoundTable parser, Sound dat decode, audio engine (OpenAL via Silk.NET.OpenAL), per-entity 3D positional audio, optional music.

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

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


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

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

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

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

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

Phase F — Fight + cast + gear

Core gameplay loop on top of Phase E.

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

  • F.1 — GameEvent envelope dispatcher. 94 sub-opcodes in 0xF7B0. Zero handled today. Start with PlayerDescription (0x0013) + property-update events. See r08-network-protocol-atlas.md.
  • F.2 — Item + inventory model. ItemInstance / Container / PropertyBundle. Appraise round-trip (0x00C80x00C9). Burden math. See r06-items-inventory.md + src/AcDream.Core/Items/.
  • F.3 — Combat math + damage flow. Damage formula, per-body-part AL, crit, hit-chance sigmoid. Server broadcasts damage events; client displays + HP bar. See r02-combat-system.md + src/AcDream.Core/Combat/.
  • F.4 — Spell cast state machine. SpellCastStateMachine + active buff tracking. Buffs + recalls first, projectile spells later. Fizzle sigmoid + mana conversion. See r01-spell-system.md + src/AcDream.Core/Spells/.
  • F.5 — Core panels. Attributes / Skills / Paperdoll / Inventory / Spellbook — using the retail-ui framework from Phase D.2. See 05-panels.md under retail-ui.

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 with LoginComplete re-send, "pink bubble" loading state. See r09-dungeon-portal-space.md.

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

Phase H — Social + progression

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

  • H.1 — Chat window. UI panel + all 6 wire opcodes (Channel, Tell, System, HearSpeech, HearRangedSpeech, TurbineChat).
  • H.2 — Allegiance. Tree model + XP pass-up math + 5 allegiance chat channels + MOTD. See r11-allegiance.md.
  • H.3 — Emote scripts + quests + dialogs. 122 EmoteType × 39 Trigger mini-VM. Contract tracker UI. NPC dialog rendered via chat with <Tell:…> markup. See r10-quest-dialogs.md.
  • H.4 — Character creation. 0xE000002 CharGen dat + 13 heritages + templates + appearance picker + preview renderer. See r07-character-creation.md.

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

Phase 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)

Cross-cutting work tracked in parallel

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

Explicitly out of scope

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

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

Observation Phase
Drudge statue in wrong pose 6.3 FIXED
Characters in T-pose / wrong idle 6.1 FIXED
No breathing on NPCs 6.4 + sentinel fix FIXED
Lifestone crystal has one side missing 9.2 FIXED
Ground floor flickering with terrain 7.1 FIXED
Houses missing second floors / walls 7.1 FIXED ✓ (interior mesh landed)
Character clothing missing / wrong 5 FIXED
Statue wrong color / wrong scale 5 FIXED
Holtburg sign half-buried 5 FIXED
Can't walk past the loaded 3×3 window A.1 FIXED ✓ (5×5 default, ACDREAM_STREAM_RADIUS to tune)
Frame hitch crossing landblock boundary Phase A.3 (synchronous loader for now; async returns when DatCollection is thread-safe)
Walking around doesn't move me on the server Phase B.3 FIXED
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
No sound Phase E.2
Dungeons / foundry interior missing Phase G.3
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.