acdream/src/AcDream.App/UI/README.md
Erik 7230c1590f docs+feat(ui): retail UI deep-dive research + C# port scaffold
Deep investigation of the retail AC client's GUI subsystem, driven by 6
parallel Opus research agents, plus the first cut of a retail-faithful
retained-mode widget toolkit that scaffolds Phase D.

Research (docs/research/retail-ui/):
- 00-master-synthesis.md        — cross-slice synthesis + port plan
- 01-architecture-and-init.md   — WinMain, CreateMainWindow, frame loop,
                                  Keystone bring-up (7 globals mapped)
- 02-class-hierarchy.md         — key finding: UI lives in keystone.dll,
                                  not acclient.exe; CUIManager + CUIListener
                                  MI pattern, CFont + CSurface + CString
- 03-rendering.md               — 24-byte XYZRHW+UV verts, per-font
                                  256x256 atlas baked from RenderSurface,
                                  TEXTUREFACTOR coloring, DrawPrimitiveUP
- 04-input-events.md            — Win32 WndProc → Device (DAT_00837ff4)
                                  → widget OnEvent(+0x128); full event-type
                                  table (0x01 click, 0x07 tooltip ~1000ms,
                                  0x15 drag-begin, 0x21 enter, 0x3E drop)
- 05-panels.md                  — chat, attributes, skills, spells, paperdoll
                                  (25-slot layout), inventory, fellowship,
                                  allegiance — with wire-message bindings
- 06-hud-and-assets.md          — vital orbs (scissor fill), radar
                                  (0x06001388/0x06004CC1, 1.18× shrink),
                                  compass strip, dat asset catalog

Key insight: keystone.dll owns the actual widget toolkit — we cannot
port a class hierarchy from the decompile because it's not there.
Instead we implement our own retained-mode toolkit with retail-faithful
behavior (event codes, focus/modal/capture, drag-drop state machine)
and will consume the same portal.dat fonts + sprites so the visual
identity is preserved.

C# scaffold (src/AcDream.App/UI/):
- UiEvent          — 24-byte event struct + retail event-type constants
                     (0x01 click, 0x15 drag-begin, 0x201 WM_LBUTTONDOWN,
                     etc.) matching retail decompile switches
- UiElement        — base widget: children, ZOrder, focus/capture flags,
                     virtual OnDraw/OnEvent/OnHitTest/OnTick; children-
                     first hit test + back-to-front composite
- UiPanel          — panel, label, button primitives
- UiRenderContext  — 2D draw context with translate stack
- UiRoot           — top-of-tree + Device responsibilities (mouse/
                     keyboard state, focus, modal, capture, drag-drop,
                     tooltip timer); WorldMouseFallThrough/
                     WorldKeyFallThrough preserves existing camera
                     controls when no widget consumes
- UiHost           — packages UiRoot + TextRenderer + input wiring
                     helpers for one-line integration into GameWindow
- README.md        — orientation for future agents

Roadmap (docs/plans/2026-04-11-roadmap.md):
- D.1 marked shipped (debug overlay from 2026-04-17)
- D.2 expanded to include the retail UI framework landed here
- D.3-D.7 added: AcFont, dat sprites, core panels, HUD, CursorManager
- D.8 remains sound

All existing 470 tests pass. 0 warnings, 0 errors.
2026-04-17 19:13:02 +02:00

111 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# AcDream.App.UI — Retail-style UI toolkit
This is acdream's retained-mode UI toolkit. It mirrors the **behavior**
of the retail AC client (hit-testing, modal, capture, drag-drop,
tooltip delay, focus routing, event type codes) without trying to
byte-match the retail binary — because the retail widgets live in
`keystone.dll`, which we don't decompile.
## Research
All design decisions in this directory are grounded in the master
synthesis + six deep-dive docs under
[`docs/research/retail-ui/`](../../../docs/research/retail-ui/):
| Document | Topic |
|---|---|
| [`00-master-synthesis.md`](../../../docs/research/retail-ui/00-master-synthesis.md) | Cross-slice synthesis + C# port plan |
| [`01-architecture-and-init.md`](../../../docs/research/retail-ui/01-architecture-and-init.md) | Process entry, window, main loop |
| [`02-class-hierarchy.md`](../../../docs/research/retail-ui/02-class-hierarchy.md) | CUIManager / CUIListener / CFont / CSurface |
| [`03-rendering.md`](../../../docs/research/retail-ui/03-rendering.md) | Font atlas, 2D quad batch, cursor |
| [`04-input-events.md`](../../../docs/research/retail-ui/04-input-events.md) | WndProc → Device → widget event routing |
| [`05-panels.md`](../../../docs/research/retail-ui/05-panels.md) | Chat, attributes, spells, paperdoll, inventory |
| [`06-hud-and-assets.md`](../../../docs/research/retail-ui/06-hud-and-assets.md) | Vital orbs, radar, compass + dat asset catalog |
## Files
- `UiEvent.cs` — 24-byte event struct + retail-faithful type constants
(`0x01` click, `0x15` drag-begin, `0x3E` drop, `0x201` WM_LBUTTONDOWN, …)
- `UiElement.cs` — base widget with `OnDraw` / `OnEvent` / `OnHitTest` /
`OnTick` virtuals, children list, ZOrder, focus/capture flags
- `UiPanel.cs``UiPanel` (rect + optional bg/border), `UiLabel`, `UiButton`
- `UiRenderContext.cs` — per-frame draw context with translate stack
- `UiRoot.cs` — top-of-tree + "Device" responsibilities (mouse/keyboard
state, focus, modal, capture, drag-drop, tooltip timer). Mirrors the
retail `DAT_00837ff4` Device object's vtable.
- `UiHost.cs` — one-shot wrapper: owns the `UiRoot`, a `TextRenderer`,
and a default `BitmapFont`. Provides `WireMouse` / `WireKeyboard`
helpers for Silk.NET plumbing.
## Integration pattern
```csharp
// GameWindow.OnLoad
_uiHost = new UiHost(_gl!, shadersDir, _debugFont);
_uiHost.Root.WorldMouseFallThrough += (btn, x, y, flags) => HandleWorldClick(btn, x, y, flags);
_uiHost.Root.WorldKeyFallThrough += (vk, lp) => HandleHotkey(vk);
foreach (var mouse in _input.Mice) _uiHost.WireMouse(mouse);
foreach (var kb in _input.Keyboards) _uiHost.WireKeyboard(kb);
// Add panels
var chat = new Panels.ChatWindow { Left = 10, Top = 400, Width = 500, Height = 250 };
_uiHost.Root.AddChild(chat);
// GameWindow.OnRender — after the 3D scene
_uiHost.Tick(deltaSeconds);
_uiHost.Draw(new Vector2(_window!.Size.X, _window.Size.Y));
```
## What's scaffolded vs what still needs building
### Shipped in the scaffold (this session)
- UI tree + event routing + focus + modal + capture + drag-drop
- Hit-testing (children-first, Z-order tie-break)
- Tooltip timer (~1000ms)
- Hover enter/leave, click vs right-click, scroll, keyboard
- World fall-through so existing camera/player controls still work
- Simple text/rect drawing through the existing `BitmapFont` +
`TextRenderer` pipeline
### To build next
1. **`AcFont`** + **`FontCache`** — load `Font` DBObjs from
`portal.dat` range `0x40000000..0x40000FFF`, bake 256×256 glyph
atlas from the referenced `RenderSurface` (`0x06xxxxxx`). See
[slice 03 §4](../../../docs/research/retail-ui/03-rendering.md#4-fonts-in-the-dat-files).
2. **Dat sprite loader** — decode `RenderSurface` dats as GL textures;
add `DrawSprite(uint datId, Rectangle dest, uint rgba)` to
`UiRenderContext`.
3. **`CursorManager`** — OS cursor + dat-sourced custom cursors via
[slice 03 §7](../../../docs/research/retail-ui/03-rendering.md#7-cursor).
4. **Scissor clipping** — for panels with scrollable interiors (chat,
inventory grid). `GL_SCISSOR_TEST` wrapped in
`UiRenderContext.PushScissor` / `PopScissor`.
5. **First concrete panel — `ChatWindow`** since we have all 6 wire
messages parsed already. See
[slice 05 §1](../../../docs/research/retail-ui/05-panels.md#1-chat-window).
6. **Vital orbs HUD** once the server sends
`GameMessagePrivateUpdateVital`. See
[slice 06 A.1](../../../docs/research/retail-ui/06-hud-and-assets.md#a1-health--stamina--mana-globes).
## Retail magic numbers the scaffold preserves
Because hand-ported panel code will copy the retail switch-case
structure, we keep the magic constants:
```csharp
// Event types
UiEventType.Click == 0x01 // chunk_00470000.c ~11140
UiEventType.Tooltip == 0x07 // chunk_00460000.c ~6253 (~1000ms delay)
UiEventType.DragBegin == 0x15 // chunk_004A0000.c ~2707
UiEventType.DragEnter == 0x21 // chunk_004A0000.c ~2714
UiEventType.DragOver == 0x1C // chunk_004A0000.c ~2723
UiEventType.DropReleased == 0x3E // chunk_004A0000.c ~2754
UiEventType.MouseDown == 0x201 // WM_LBUTTONDOWN
UiEventType.MouseUp == 0x202 // WM_LBUTTONUP
// Event IDs
// Widget event IDs live in the 0x10000000+ range (retail convention).
// UiRoot auto-assigns EventIds starting at 0x10000001.
```