acdream/memory/project_ui_architecture.md
Erik 55aaca7a14 feat(ui): Phase D.2a — VitalsPanel wired into GameWindow + backend pivot
Closes Phase D.2a. Launch with ACDREAM_DEVTOOLS=1 now shows a live
ImGui "Vitals" window whose HP bar reads CombatState.GetHealthPercent
for the local player. Without the env var the branches are dead code,
no ImGui context is created, and behaviour is identical to before.

GameWindow hunks:
  - fields: _imguiBootstrap / _panelHost / _vitalsVm + DevToolsEnabled
  - init (OnLoad): construct bootstrap + host, register VitalsPanel
  - GUID push: _vitalsVm?.SetLocalPlayerGuid(chosen.Id) at live-connect
  - frame begin: _imguiBootstrap.BeginFrame(dt) after GL clear
  - frame end: _panelHost.RenderAll(ctx) + _imguiBootstrap.Render() after debug overlay
  - input gating: skip WASD when ImGui.GetIO().WantCaptureKeyboard

Backend pivot: Hexa.NET.ImGui → ImGui.NET + Silk.NET.OpenGL.Extensions.ImGui.

First-light integration with the Hexa backend crashed 0xC0000005 inside
Hexa.NET.ImGui.Backends.OpenGL3.ImGuiImplOpenGL3.InitNative. Root cause:
Hexa's native OpenGL3 backend resolves GL function pointers via GLFW or
SDL internally; with Silk.NET (which uses neither) the pointers are null
and the native code crashes on first use. The mitigation path was
already planned — the design doc's Risk section called a pivot to
ImGui.NET a "one-morning operation" — and that's exactly what happened.

  - Packages: Hexa.NET.ImGui 2.2.9 + Hexa.NET.ImGui.Backends 1.0.18
    → ImGui.NET 1.91.6.1 + Silk.NET.OpenGL.Extensions.ImGui 2.23.0
  - ImGuiBootstrapper: was static Initialize(gl)+Shutdown() wrapping
    Hexa's OpenGL3 init; now an IDisposable wrapping Silk.NET's
    ImGuiController instance which handles GL backend init + input
    subscription in one go.
  - SilkInputBridge.cs deleted (~190 LOC): ImGuiController subscribes
    IKeyboard / IMouse events itself, we don't need a bespoke bridge.
  - ImGuiPanelRenderer: ImGuiNET.ImGui.* calls instead of
    Hexa.NET.ImGui.ImGui.*. Widget surface unchanged.

Boundary discipline is preserved — no panel imports ImGuiNET; only
ImGuiPanelRenderer does. The D.2b custom toolkit will implement the
same IPanelRenderer contract without touching panel code.

Out of scope (tracked for follow-up):
  - Stam/Mana currently return float? null (VitalsVM). Absolute values
    need LocalPlayerState + PlayerDescription (0x0013) parsing to be
    stored rather than discarded — filed as a post-D.2a issue.
  - Mouse-capture gating (WorldMouseFallThrough-style click-through
    tests) — not needed until we add clickable inventory items.

Roadmap + memory + architecture doc + UI framework plan updated in the
same commit per CLAUDE.md roadmap-discipline rules. 753 tests pass
(550 Core + 192 Core.Net + 11 new UI.Abstractions), 0 build warnings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 00:43:46 +02:00

4.1 KiB

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.

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.
  • 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.