diff --git a/docs/plans/2026-04-11-roadmap.md b/docs/plans/2026-04-11-roadmap.md index 4b56a6e..ddbced3 100644 --- a/docs/plans/2026-04-11-roadmap.md +++ b/docs/plans/2026-04-11-roadmap.md @@ -120,6 +120,26 @@ Plus polish that doesn't get its own phase number: **Goal:** chat window, nameplates, inventory, and audio. Can run concurrently with Phase B or C because it doesn't touch gameplay/net/rendering surfaces. +> **Updated 2026-04-24 — staged backend strategy.** We split Phase D into +> two stages so game logic can be validated quickly without waiting for +> the full retail-look custom toolkit. See +> [`docs/plans/2026-04-24-ui-framework.md`](2026-04-24-ui-framework.md) +> for the full design. Short version: +> +> 1. **D.2a — Hexa.NET.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. +> 2. **Stable `AcDream.UI.Abstractions` layer** — ViewModels + Commands + +> `IPanel` / `IPanelRenderer` interfaces. Backend-agnostic. Plugin API +> publishes against this layer and never sees ImGui. +> 3. **D.2b onward — custom retail-look backend** using dat assets (icons, +> panels, fonts). Swap one panel at a time; ImGui stays forever as the +> `ACDREAM_DEVTOOLS=1` overlay for packet trace / state dump / dat browser. +> +> Every sub-piece below (D.3 AcFont, D.4 dat sprites, D.5 core panels, +> D.6 HUD, D.7 cursors) ships against the abstraction layer, so it +> doesn't matter which backend is drawing when the port lands. + **Sub-pieces:** - **D.1 — 2D ortho overlay + font rendering.** ✅ SHIPPED 2026-04-17 as the dev-facing debug overlay (StbTrueTypeSharp system-font atlas + `TextRenderer` + `DebugOverlay`). - **D.2 — Retail UI framework + first panels.** Research + scaffold landed 2026-04-17 (see `docs/research/retail-ui/`). Ships: diff --git a/docs/plans/2026-04-24-ui-framework.md b/docs/plans/2026-04-24-ui-framework.md new file mode 100644 index 0000000..85d8a41 --- /dev/null +++ b/docs/plans/2026-04-24-ui-framework.md @@ -0,0 +1,257 @@ +# UI framework plan + +**Date:** 2026-04-24 +**Status:** design — not yet implemented +**Owner:** lead engineer (erik) + Claude + +Captures the UI strategy agreed via discussion on 2026-04-24. Documents +the choices AND the alternatives considered so future sessions can +re-evaluate with the same context. + +## Goal + +acdream needs a playable game UI: chat, vitals HUD, inventory, character +panel, skills, spellbook, fellowship, allegiance, trade, options, map, +quest log, tooltips — and a first-class plugin API so plugin authors can +ship their own panels. + +## Strategy: two-phase, one stable interface + +``` +┌─────────────────────────────────────────┐ +│ UI backend ─ ImGui (short-term) │ ← swappable +│ ─ Custom retail (later) │ +├─────────────────────────────────────────┤ +│ ViewModels + Commands (per panel) │ ← stable contracts +├─────────────────────────────────────────┤ +│ Game state + events + net (existing) │ ← unchanged +└─────────────────────────────────────────┘ +``` + +- **Near term:** wire an ImGui-based overlay so we can iterate on game + logic fast — chat that actually sends + receives, inventory that + reflects real state, vitals bar that reads real HP/stam/mana. Looks + like a debugger, that's fine for now. +- **Later:** replace the visual layer with a custom toolkit that uses + retail dat assets (icons, panels, fonts) and matches retail's feel. +- **Always:** ViewModels and Commands stay stable across the swap. The + game logic never learns which backend is drawing it. + +## Choice: Hexa.NET.ImGui for the short-term backend + +Decision: **`Hexa.NET.ImGui`** + its bundled `Hexa.NET.ImGui.Backends.OpenGL3`. + +### Why Hexa over ImGui.NET + Silk.NET.OpenGL.Extensions.ImGui + +- **Auto-generated from cimgui, tracks upstream ImGui closely** — docking, + viewports, tables current within days of release. +- **Native-AOT first-class** — single-file publish is painless; aligns + with where we want acdream to land long-term for distribution. +- **Per-RID native-lib bundling** is cleaner — no separate `cimgui.dll` + side-loading to manage. +- Ships its own Silk.NET-compatible OpenGL3 backend; the Silk.NET official + extension is unnecessary. + +### What we give up + +- `Silk.NET.OpenGL.Extensions.ImGui` is battle-tested with hundreds of + Silk.NET sample projects. Hexa's backend is newer, slightly higher risk + of edge-case bugs. +- Mitigation: keep integration tight (~50 lines) so switching to + ImGui.NET later is a one-morning operation if Hexa misbehaves. + +### Why ImGui at all (vs. going straight to custom) + +- Get game logic validated end-to-end in weeks, not months. +- ImGui stays forever as the **devtools layer** (`ACDREAM_DEVTOOLS=1`): + packet trace inspector, state dump, dat browser. Having a working + ImGui integration is a permanent asset even after game UI moves off it. +- Lets us design the **plugin API and ViewModel contracts against a + real running panel** rather than designing in the abstract. + +## The three layers in detail + +### Layer 1 — Game state (unchanged) + +Already exists: `IGameState`, `IEvents` (plugin-host interfaces), +`WorldSession`, `PlayerWeenie`, `Inventory`, `SpellBook`, `LightManager`, +the live-session wire code. The UI reads from these; we add nothing. + +### Layer 2 — ViewModels + Commands + +New module: `src/AcDream.UI.Abstractions/`. Backend-agnostic. + +**ViewModels** — per-panel data contracts. Example: + +```csharp +public sealed record VitalsVM( + int HpCurrent, int HpMax, + int StamCurrent, int StamMax, + int ManaCurrent, int ManaMax, + float HpRegenRate, + bool Stunned, + bool LowHpWarning); + +public sealed record InventoryVM( + IReadOnlyList Items, + int BurdenCurrent, + int BurdenCapacity); + +public sealed record ChatVM( + IReadOnlyList Recent, + int UnreadCount, + ChatChannel ActiveInputChannel); +``` + +Built from `IGameState` each frame (cheap record allocation). Observable +via `IEvents` subscription for panels that want push updates instead of +pull. + +**Commands** — user actions going back. + +```csharp +public sealed record UseItemCmd(uint ItemGuid); +public sealed record SendChatCmd(ChatChannel Channel, string Text); +public sealed record CastSpellCmd(uint SpellId, uint? TargetGuid); +public sealed record DragItemCmd(uint ItemGuid, uint DestContainerGuid, int Slot); +public sealed record EquipItemCmd(uint ItemGuid, EquipSlot Slot); +``` + +Dispatched to an `ICommandBus` that routes to the appropriate subsystem +(`WorldSession.SendSelect`, `ChatService.Send`, etc.). + +### Layer 3 — UI backend + +New module: `src/AcDream.UI.ImGui/`. References `AcDream.UI.Abstractions`. + +- Thin adapter: each panel implements an `IPanel` interface — `Draw(VM)` + method, emits commands on interaction. +- Panels registered into an `IPanelHost` which the backend iterates + per frame. +- Keyboard / mouse / focus handled by ImGui natively. + +Later: `src/AcDream.UI.Retail/` references the same `AcDream.UI.Abstractions` +and implements the same `IPanel` / `IPanelHost` interfaces — but draws +with our own retained-mode toolkit + retail dat assets. + +## Plugin API (must be backend-agnostic) + +```csharp +public interface IPanel +{ + string Id { get; } + string Title { get; } + void Draw(in PanelContext ctx); +} + +public readonly ref struct PanelContext +{ + public readonly IGameState State; + public readonly ICommandBus Commands; + public readonly IPanelRenderer Renderer; // drawing primitives + // (widget calls flow through Renderer so the panel never references + // ImGuiNET / Hexa / our custom widget namespaces directly) +} +``` + +`IPanelRenderer` exposes a retail-UI-friendly primitive set: `Panel`, +`Label`, `Button`, `TextField`, `ScrollView`, `ListView`, `Icon`, `Tab`, +`ProgressBar`, `DragSource`, `DropTarget`. ImGui implementation wraps +ImGui calls; custom implementation uses our retained-mode toolkit. + +**Key discipline:** no panel references Hexa.NET.ImGui directly. If a +panel needs a feature the abstraction doesn't expose, add it to +`IPanelRenderer`, don't reach through. + +## Implementation order + +### Sprint 1 — Infrastructure + first visible panel + +1. `AcDream.UI.Abstractions`: `IPanel`, `IPanelHost`, `IPanelRenderer`, + `ICommandBus`, base ViewModels (start with `VitalsVM` only). +2. `AcDream.UI.ImGui`: Hexa.NET.ImGui wired, `ImGuiPanelRenderer` + implementation of `IPanelRenderer` (Label, Button, Panel, ProgressBar + is enough for vitals). +3. `GameWindow`: host the `IPanelHost`; render on top of scene when + `ACDREAM_DEVTOOLS=1`. +4. `VitalsPanel` — first real panel. Reads HP/stam/mana from + `IGameState`, renders three progress bars. + +**Success criteria:** launch the client, see three coloured bars in the +top-left that actually reflect the character's current vitals as they +walk around / take damage / regen. + +### Sprint 2 — Interaction panels + +- `ChatPanel` — reads `ChatVM`, emits `SendChatCmd`. `ICommandBus` + routes to `WorldSession`. +- `InventoryPanel` — reads `InventoryVM`, click-to-select, double-click + to equip, drag target for future move. +- `CharacterPanel` — attributes, skills, XP. + +### Sprint 3 — Plugin API hardening + +- Document the `IPanel` contract. +- Port the smoke plugin to register a demo panel via the API. +- Confirm plugins can subscribe to game events AND draw UI through the + same interface. + +### Sprint 4+ — More panels + +Spellbook, allegiance, fellowship, trade, map, quest log, options. +Continue to expand `InventoryPanel` with drag-drop, split, appraise. + +### Later — Custom retail-look backend + +`AcDream.UI.Retail` implements `IPanelRenderer` with our own toolkit + +retail dat assets. Swap panels one at a time. ImGui overlay remains for +devtools. + +## Non-goals for this first pass + +- **Not** going to theme ImGui to look retail. Waste of effort when we'll + swap the backend. Devtools aesthetic is fine. +- **Not** porting retail's widget code. We use their ASSETS later, not + their widget implementation. +- **Not** building layout DSL / XAML-like markup. Panels register and + draw procedurally, same as ImGui. + +## Alternatives considered + +| Option | Pros | Cons | Why not picked | +|---|---|---|---| +| ImGui.NET + Silk.NET.OpenGL.Extensions.ImGui | Official Silk.NET path, battle-tested | Lags upstream ImGui, AOT story has sharp edges | Hexa tracks upstream faster, cleaner AOT | +| Myra | Retained-mode, less-debug-y look | Needs a custom Silk.NET backend (~300 LOC), slower iteration | ImGui is faster to first pixel; aesthetics will move to custom anyway | +| Avalonia | Mature, XAML designer, great devtools | Hostile to Silk.NET render loop, huge dep | Integration cost too high, aesthetics wrong | +| NoesisGUI | Slick, XAML-like, production-quality | Commercial license, big dep | Premature optimization | +| RmlUi | HTML/CSS mental model | Bindings immature, own render backend needed | Too much glue | +| Pure custom on Silk.NET from day one | Full control, retail look immediately | Months of work before first visible panel | Can't validate game logic fast enough | + +## Risks + mitigations + +- **Risk:** `IPanelRenderer` grows to leak ImGui-isms. + **Mitigation:** code review every addition; if a feature only exists + in ImGui and the retail toolkit can't express it, don't add it. + +- **Risk:** Swap to custom backend breaks a dozen panels simultaneously. + **Mitigation:** swap one panel at a time, keep ImGui rendering the + rest until all are ported. + +- **Risk:** Plugin authors write panels that only work in ImGui. + **Mitigation:** smoke plugin registers a panel early; use it as a + canary whenever backend changes. + +- **Risk:** Hexa.NET.ImGui stops being maintained. + **Mitigation:** integration is small (<100 LOC), switching to + ImGui.NET is a one-morning operation. + +## Open questions (defer to implementation) + +- Where does input focus live — ImGui captures keyboard by default when + a text field is active, does our game-side input system need to check + "did ImGui want this event"? (Yes, standard pattern. Wire + `io.WantCaptureKeyboard` gate.) +- Do devtools panels ship in release builds? (Yes, gated on env var, cost + is negligible when disabled.) +- Modal dialogs? Drag-drop? Fleshed out in Sprint 2 when we have + inventory actually working.