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>
This commit is contained in:
Erik 2026-04-25 00:43:46 +02:00
parent a7dbce3474
commit 55aaca7a14
13 changed files with 218 additions and 275 deletions

View file

@ -1,13 +1,51 @@
# UI framework plan
**Date:** 2026-04-24
**Status:** design — not yet implemented
**Date:** 2026-04-24 (design), shipped 2026-04-25
**Status:** **Phase D.2a shipped**`AcDream.UI.Abstractions` + ImGui backend
+ `VitalsPanel` gated on `ACDREAM_DEVTOOLS=1`. Backend pivoted from
`Hexa.NET.ImGui` to `ImGui.NET` + `Silk.NET.OpenGL.Extensions.ImGui` during
first-light integration — see the pivot note below. Phase D.2b (custom
retail-look backend) remains design-only.
**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.
## 2026-04-25 pivot: Hexa.NET.ImGui → ImGui.NET
The original choice (documented below) was `Hexa.NET.ImGui` +
`Hexa.NET.ImGui.Backends.OpenGL3`. It did not survive first-light
integration:
- First launch with Hexa's backend crashed with `0xC0000005` inside
`Hexa.NET.ImGui.Backends.OpenGL3.ImGuiImplOpenGL3.InitNative`.
- Root cause: Hexa's native OpenGL3 backend does its own GL function
resolution, looking up symbols via GLFW or SDL. Silk.NET uses neither,
so the resolved function pointers were null and the native code
dereferenced them on init.
- Hexa's Silk.NET examples rely on GLFW being co-loaded (its default
on Hexa's own scenes) — not applicable here.
**Mitigation path** was already written into this doc (§"What we give
up": *"switching to ImGui.NET later is a one-morning operation if Hexa
misbehaves"*) and taken:
- Packages swapped → `ImGui.NET 1.91.6.1` + `Silk.NET.OpenGL.Extensions.ImGui 2.23.0`.
- `Silk.NET.OpenGL.Extensions.ImGui.ImGuiController` handles the whole
integration (GL backend init against the Silk.NET GL binding,
keyboard + mouse IO event subscription). No hand-written input
bridge needed.
- `ImGuiBootstrapper` is a ~10-line `IDisposable` wrapping the
`ImGuiController` instance. `ImGuiPanelRenderer` wraps `ImGuiNET.ImGui.*`.
- Boundary discipline preserved — panels never import `ImGuiNET`
directly; they only use `IPanelRenderer`. The backend swap is
invisible above the abstraction layer, as designed.
Sections below from §"Choice: Hexa.NET.ImGui" onward are kept as the
historical design reasoning. They remain useful if we ever re-evaluate
native AOT / upstream-tracking tradeoffs.
## Goal
acdream needs a playable game UI: chat, vitals HUD, inventory, character