acdream/src/AcDream.App/UI
Erik 8f627cce0e fix(D.5.3a): selected-object meter visual-gate fixes (name, gate, flash, magenta)
Visual gate against retail surfaced several fidelity gaps in the selected-object
strip; all fixed and user-confirmed. Faithful to gmToolbarUI::HandleSelectionChanged
(acclient_2013_pseudo_c.txt:198635) + RecvNotice_UpdateObjectHealth (:196213).

- UiMeter.DrawHBar: guard each slice on `id != 0` BEFORE resolve. resolve(0)
  returns the 1x1 magenta placeholder with a non-zero GL handle, so the single-
  image meter (caps id=0) was drawing 1px magenta caps at the bar's ends. The
  3-slice vitals meter (all ids set) was unaffected. (the magenta-lines bug)
- SelectedObjectController: meter visibility is now UpdateHealth-driven (shown when
  health is known for the selected guid — HasHealth at select or HealthChanged),
  not shown-on-select; brief green selection flash via Tick revert; overlay floated
  above the meter so the flash isn't hidden by the bar; name top-aligned into the
  bar sprite's black band (NameBandHeight) with the bar below.
- GameWindow.IsHealthBarTarget: gate the health bar on the server PWD bits
  BF_ATTACKABLE (0x10) | BF_PLAYER (0x8) — friendly/vendor NPCs and attackable
  Doors (Misc type) are name-only; players/monsters get the bar. Replaces the
  too-loose IsLiveCreatureTarget. Wired SelectedObjectController.Tick in OnUpdate.
- CombatState.HasHealth(guid): distinguishes a known health value from the 1.0
  default, so a re-selected already-assessed target shows its bar immediately.
- TextureCache.GetOrUploadRenderSurface: resolve the surface's DefaultPaletteId
  so paletted (P8/INDEX16) UI sprites decode instead of falling to magenta.
- ToolbarController.HiddenIds: also hide 0x100001A3 (stack-entry box) — retail
  hides it in HandleSelectionChanged; it was rendering as a stray black box.

Divergence register: AP-47 (meter-visible timing) retired (now faithful); AP-46
rewritten to the BF_ATTACKABLE/BF_PLAYER gate approximation. Full suite green
(2,688 passed / 4 skipped). User-confirmed: name on top, NPC name-only, monster
bar on assess, green flash, no magenta.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 09:37:15 +02:00
..
Layout fix(D.5.3a): selected-object meter visual-gate fixes (name, gate, flash, magenta) 2026-06-20 09:37:15 +02:00
ControlsIni.cs feat(D.2b): controls.ini stylesheet loader + apply title color 2026-06-14 17:31:55 +02:00
IconComposer.cs fix(D.5.2): effect tint = per-pixel tile copy (surface ReplaceColor overload) 2026-06-18 10:21:33 +02:00
MarkupDocument.cs feat(D.2b): retail 3-slice vital bars + headless mockup verifier 2026-06-14 21:40:11 +02:00
README.md docs+feat(ui): retail UI deep-dive research + C# port scaffold 2026-04-17 19:13:02 +02:00
RetailChromeSprites.cs feat(D.2b): draw the window resize-grip overlay (gold ridges + corner studs) 2026-06-15 11:05:18 +02:00
TargetIndicatorPanel.cs feat(retail): Commit B — retail-faithful AP cadence + screen-rect picker 2026-05-16 13:56:08 +02:00
UiButton.cs fix(D.2b): behavioral widgets are leaf — ConsumesDatChildren (chat menu open) 2026-06-16 18:36:40 +02:00
UiDatFont.cs @ 2026-06-16 12:02:07 +02:00
UiElement.cs fix(D.5.1): draw window-frame border over content (OnDrawAfterChildren) 2026-06-17 16:05:50 +02:00
UiEvent.cs docs+feat(ui): retail UI deep-dive research + C# port scaffold 2026-04-17 19:13:02 +02:00
UiField.cs fix(D.2b): behavioral widgets are leaf — ConsumesDatChildren (chat menu open) 2026-06-16 18:36:40 +02:00
UiHost.cs feat(D.2b): UiText (Type 12) -- generic text + Type-12 flip; transcript factory-built (widget-generalization Task 5) 2026-06-16 17:39:02 +02:00
UiItemList.cs fix(D.5.1): UiItemList.Cell guards empty list with a diagnostic (review) 2026-06-16 22:32:53 +02:00
UiItemSlot.cs fix(D.5.1): occupancy-gated slot numbers (empty=0x1000005e bg digit) + bottom-right rect probe 2026-06-17 14:27:27 +02:00
UiMenu.cs fix(D.2b): behavioral widgets are leaf — ConsumesDatChildren (chat menu open) 2026-06-16 18:36:40 +02:00
UiMeter.cs fix(D.5.3a): selected-object meter visual-gate fixes (name, gate, flash, magenta) 2026-06-20 09:37:15 +02:00
UiNineSlicePanel.cs fix(D.5.1): draw window-frame border over content (OnDrawAfterChildren) 2026-06-17 16:05:50 +02:00
UiPanel.cs feat(D.2b): UiButton (Type 1) — Send + Max/Min as generic buttons (widget-generalization Task 3) 2026-06-16 17:07:58 +02:00
UiRenderContext.cs feat(D.2b): UI render infra — overlay layer, DrawFill, crisp text, write-mode focus 2026-06-16 15:23:48 +02:00
UiRoot.cs feat(D.2b): UI render infra — overlay layer, DrawFill, crisp text, write-mode focus 2026-06-16 15:23:48 +02:00
UiScrollable.cs feat(D.2b): UiText (Type 12) -- generic text + Type-12 flip; transcript factory-built (widget-generalization Task 5) 2026-06-16 17:39:02 +02:00
UiScrollbar.cs fix(D.2b): behavioral widgets are leaf — ConsumesDatChildren (chat menu open) 2026-06-16 18:36:40 +02:00
UiText.cs feat(D.2b): vitals numbers as UiText (widget-generalization Task 8) 2026-06-16 18:52:42 +02:00

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/:

Document Topic
00-master-synthesis.md Cross-slice synthesis + C# port plan
01-architecture-and-init.md Process entry, window, main loop
02-class-hierarchy.md CUIManager / CUIListener / CFont / CSurface
03-rendering.md Font atlas, 2D quad batch, cursor
04-input-events.md WndProc → Device → widget event routing
05-panels.md Chat, attributes, spells, paperdoll, inventory
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.csUiPanel (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

// 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.
  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.
  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.
  6. Vital orbs HUD once the server sends GameMessagePrivateUpdateVital. See slice 06 A.1.

Retail magic numbers the scaffold preserves

Because hand-ported panel code will copy the retail switch-case structure, we keep the magic constants:

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