docs(ui): plan the staged UI-backend strategy
Two-stage rollout, one stable abstraction layer:
1. Short-term: Hexa.NET.ImGui as the backend. Wire up in days, iterate
game logic (chat, inventory, vitals) in weeks. Looks like a debugger,
acceptable while we prove the interaction logic end-to-end.
2. `AcDream.UI.Abstractions` — ViewModels + Commands + `IPanel` /
`IPanelRenderer` interfaces. Backend-agnostic. Plugin API targets
this layer; plugins never see ImGui.
3. Long-term: custom retail-look backend using dat assets. Swap panel
by panel. ImGui stays forever as the `ACDREAM_DEVTOOLS=1` overlay.
The new doc (`2026-04-24-ui-framework.md`) captures:
- Full design of the three-layer split
- Why Hexa.NET.ImGui over ImGui.NET + Silk.NET.OpenGL.Extensions.ImGui
(AOT readiness, tracks upstream ImGui faster, cleaner native-lib
bundling)
- Alternatives considered and ruled out (Myra, Avalonia, NoesisGUI,
RmlUi, pure custom from day one)
- Implementation order (Sprint 1 vitals HUD → Sprint 2 interaction
panels → Sprint 3 plugin API → Sprint 4+ more panels → later
custom retail-look)
- Risks + mitigations and open questions deferred to implementation
Roadmap Phase D updated with a pointer to the new plan so future
sessions start from the latest strategy, not the original
all-custom-from-day-one Phase D description.
No code changes yet. Ready to start Sprint 1 when approved.
This commit is contained in:
parent
593b76fda1
commit
99ce541fd7
2 changed files with 277 additions and 0 deletions
|
|
@ -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:
|
||||
|
|
|
|||
257
docs/plans/2026-04-24-ui-framework.md
Normal file
257
docs/plans/2026-04-24-ui-framework.md
Normal file
|
|
@ -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<InventoryItemVM> Items,
|
||||
int BurdenCurrent,
|
||||
int BurdenCapacity);
|
||||
|
||||
public sealed record ChatVM(
|
||||
IReadOnlyList<ChatLineVM> 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue