# Project: UI architecture — three-layer stable pattern **Agreed 2026-04-24.** Read once per session. Full design: [`docs/plans/2026-04-24-ui-framework.md`](../docs/plans/2026-04-24-ui-framework.md). ## The separation ``` ┌─────────────────────────────────────────┐ │ UI backend (ImGui today / custom later)│ ← swappable ├─────────────────────────────────────────┤ │ ViewModels + Commands (per-panel) │ ← stable contracts ├─────────────────────────────────────────┤ │ Game state + events + net (unchanged) │ ← game logic └─────────────────────────────────────────┘ ``` - **UI backend** (bottom swap axis): **`ImGui.NET` + `Silk.NET.OpenGL.Extensions.ImGui`** for Phase D.2a (short-term, debugger-look, validates game logic fast). Custom retail-look toolkit for Phase D.2b (long-term, uses dat assets). ImGui stays **forever** as the `ACDREAM_DEVTOOLS=1` overlay. (Pivoted from `Hexa.NET.ImGui` on 2026-04-25 — Hexa's native OpenGL3 backend resolves GL via GLFW/SDL internally and crashed `0xC0000005` against Silk.NET; the Silk.NET extension is purpose-built for this stack.) - **ViewModels + Commands** (the stable contract): per-panel data records (`VitalsVM`, `InventoryVM`, `ChatVM`, …) and action records (`UseItemCmd`, `SendChatCmd`, `CastSpellCmd`, …). Lives in `AcDream.UI.Abstractions`. **Does not change across the backend swap.** - **Game state + events + net**: existing `IGameState`, `IEvents`, `WorldSession`, etc. Unchanged. UI only reads/subscribes; never touches these. ## Module layout (future) - `src/AcDream.UI.Abstractions/` — `IPanel`, `IPanelHost`, `IPanelRenderer`, `ICommandBus`, all ViewModels + Commands. Backend- agnostic. - `src/AcDream.UI.ImGui/` — `ImGui.NET` + `Silk.NET.OpenGL.Extensions.ImGui` implementation of `IPanelRenderer` + ImGui bootstrap. Phase D.2a. `ImGuiController` (from the Silk.NET extension) handles GL backend + input event subscription; `ImGuiBootstrapper` is a thin IDisposable wrapper; `ImGuiPanelRenderer` wraps the widgets `IPanelRenderer` needs. - `src/AcDream.UI.Retail/` (later) — custom retained-mode toolkit using dat assets, same `IPanelRenderer` contract. Phase D.2b. ## Hard rules 1. **No panel references a backend namespace.** If a panel imports `ImGuiNET` / `Silk.NET.OpenGL.Extensions.ImGui` or a custom-toolkit widget class directly, it's a bug — extend `IPanelRenderer` instead. 2. **Plugin API targets the abstraction layer only.** Plugins define `IPanel` instances; they never see which backend draws them. 3. **Features that only ImGui can express → not in `IPanelRenderer`.** If a feature can't be expressed by a future retained-mode toolkit with dat-sourced sprites/fonts, don't expose it in the abstraction. 4. **D.3 AcFont / D.4 dat sprites / D.7 cursor manager are D.2b dependencies** — they plug into the custom backend only. 5. **D.5 core panels / D.6 HUD ship against the abstraction**, not against a specific backend. They render via ImGui first, reskin under custom later. ## Why staged (not pure custom from day one) - Gets the whole game-logic loop validated in weeks: chat-send, inventory-click, vitals-bar-read-from-real-state. Requires zero widget art. - Forces the abstraction design to pass a real backend test before we commit to the custom toolkit. If `IPanelRenderer` is wrong, we discover it during Phase D.2a, not after Phase D.2b lands with a dozen broken panels. - Custom retail-look toolkit then delivers the aesthetic layer without the plugin API or game logic needing to change. ## Links - Full design: `docs/plans/2026-04-24-ui-framework.md` - Roadmap entry: `docs/plans/2026-04-11-roadmap.md` Phase D (see the 2026-04-24 callout block). - Custom-backend research (applies to D.2b, NOT D.2a): `docs/research/retail-ui/00-master-synthesis.md` + slices 01-06.