acdream/src/AcDream.App/UI
Erik 5f9ca18155 fix(ui): #409 live-failure round — tooltips read the RUNTIME text first
User gate on 1.0.3-tt.a: tooltips appeared NOWHERE in-world except one on
the paperdoll. Root-caused, fixed, and live-verified against a connected
client the same day. Two findings, both measured; neither is a broken
hover/hit-test.

1. DOMINANT ROOT CAUSE — RetailTooltipPresenter.OnTooltipShow gated on
   widget.AuthoredTooltipText (P0x49) alone. Retail's
   UIElement::StartTooltipAtMouse @0x00460D70 takes the RUNTIME m_TTText
   first (@0x00460DA3 IsValid -> @0x00460DAA verbatim) and only falls back
   to InqProperty(0x49) at @0x00460DDF. acdream ALREADY had the runtime
   layer — UiElement.GetTooltipText(), written by the Options/Chat/Config
   page controllers, KeyboardConfigController, the social pages and
   UiCheckboxBitfield64 — but nothing read it.

   Live-DAT measured: the Options toggle-row checkbox (0x2100002B template
   root 0x10000218, leaf 0x10000219) authors P0x47=0x10000397
   P0x48=0x21000041 P0x4B=true and an EMPTY P0x49 — the popup locator and
   the on-bit are authored; only the text arrives at runtime, exactly as
   UIOption_CheckboxBitfield64::CreateChildren @0x00485E65 stamps its
   siTooltip array. Re-measured client-wide: ALL 187 no-literal-text
   tooltip elements author both locator ids, i.e. the whole set is
   runtime-text targets.

   Fixed by ResolveTooltipText (retail's order), plus:
   - the P0x4B gate now applies only to the AUTHORED-text path, because
     retail's eight game-code SetTooltip sites set the on-bit themselves
     (__bitfield164 |= 0x20 at @0x004E1D5E/@0x004A52F4/@0x004C63AC/
     @0x004C67ED/@0x004C7000/@0x004C7218/@0x004D9617/@0x00467076);
   - the P0x48-absent fallback to the element's own LayoutDesc
     (@0x00460E7E, this->m_layout->m_DID) is ported via the new
     UiElement.SourceLayoutDid, threaded from LayoutImporter.Build's new
     sourceLayoutDid parameter and passed by Import + the four template
     resolvers.

2. THE "243 SHOWABLE" NUMBER WAS NEVER AN IN-WORLD NUMBER. Grouped
   re-sweep: all 243 sit in CHARACTER-CREATION layouts. The inventory
   window (0x21000023) and paperdoll (0x21000024) author exactly two
   between them — 0x100001D6 "Drag clothing and armor here to wear them"
   (the doll drag mask) and 0x100005BE (the Slots button). The first IS
   the user's single working tooltip, so the paperdoll was never a
   differential against a broken mechanism. Reachability was measured and
   is fine: 238/243 build as real non-ClickThrough hover targets.

LIVE VERIFICATION (connected testaccount/+Acdream, Release,
ACDREAM_RETAIL_UI=1): Options -> Character -> "Vivid Targeting Indicator"
now shows its full ID_PlayerOption_*_Help sentence; a temporary hover probe
confirmed the hover target is element 0x10000219 with runtime=True. The
paperdoll tooltip still shows. An inventory ITEM still shows nothing —
that is UIElement_UIItem::UpdateTooltip @0x004E1CB0 (retail shows the item
name, "%d %s"-prefixed when the stack is > 1), which stays deferred:
UiItemSlot is constructed programmatically at 6+ sites and carries neither
the P0x47 locator nor a name source, so it is its own slice.

Bookkeeping: register TS-85 narrowed (m_TTText READ side now ported; the
row now enumerates all 15 SetTooltip call sites split into ported vs
no-acdream-analog). #409's gate note rewritten to lead with the in-world
surfaces — the old note listed only chargen, which is why it could not
have caught this. Filed #411 for the hover-cursor scope addition: an
exhaustive raw scan of every ElementDesc found only 101 authored
MediaDescCursor entries, all on Dragbar/Resizebar with the 5 DIDs
RetailCursorCatalog already hardcodes, so retail has NO per-element cursor
for inventory items; the likely mechanism is the rollover STATE
(UIElement::MouseOverTop @0x004615D0) that UiItemSlot lacks entirely.

Gates: Release build 0 errors; App suite (live-DAT env) 5424/5421 passed/3
skips (was 5416/5413/3, +8 new tests); Runtime 1735/0; full solution (no
env) 14,631/14,561 passed/70 skipped/0 failed (was 14,623/14,554/69).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 21:44:44 +02:00
..
Layout fix(ui): #409 live-failure round — tooltips read the RUNTIME text first 2026-08-16 21:44:44 +02:00
Testing Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
AutoWieldController.cs fix(items): port retail useability and autowear rejection 2026-07-23 08:33:31 +02:00
ClientCommandController.cs fix(chat): CH4 re-review fixes — dialog-queue reentrancy, settings option-bit chokepoint 2026-08-09 22:24:43 +02:00
ControlsIni.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
CursorFeedbackController.cs fix(ui): interactive window moves must survive the per-frame anchor layout; lock the dragbar cursor 2026-07-29 21:24:49 +02:00
GameplayConfirmationController.cs fix(ui,runtime): Campaign LA gate-round-2 batch-review fixes F1,F3-F8; file #401 2026-08-15 12:28:06 +02:00
IconComposer.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
IItemListDragHandler.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
IRetainedPanelController.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
IRetainedWindowStateController.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
ItemDragPayload.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
ItemEquipRules.cs feat(ui): D.2b item interaction + retail cursors + live character sheet 2026-07-03 09:18:43 +02:00
ItemInteractionController.cs chore: secure-trade closeout - strip [trade] gate probes, roadmap ledger 2026-08-14 12:48:31 +02:00
IUiChildrenAttachedListener.cs feat(ui): Campaign OP slice OP2 — tab control, template ListBox, UIOption widget mappings 2026-08-11 00:06:43 +02:00
IUiDatStateful.cs fix(chargen): Campaign CC gate round 1 Batch B — authored selection states, label state, zoom/swatch feedback 2026-08-16 11:37:09 +02:00
IUiGlobalTimeListener.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
IUiViewportRenderer.cs feat(render): Campaign V slice V6l commit 3 - the offscreen viewports draw on Vulkan 2026-07-28 18:05:24 +02:00
MarkupDocument.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
README.md refactor(ui): extract retained runtime composition 2026-07-11 00:03:45 +02:00
RetailChromeSprites.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
RetailCursorCatalog.cs fix(ui): port retail window control feedback 2026-07-17 09:40:08 +02:00
RetailItemConfirmationController.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
RetailPanelCatalog.cs feat(ui): FA3 -- mount the social panel shell (Friends/Allegiance/Fellowship/Squelch) 2026-08-12 02:58:09 +02:00
RetailSkillTrainingConfirmationController.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
RetailUiRuntime.cs fix(ui): #409 live-failure round — tooltips read the RUNTIME text first 2026-08-16 21:44:44 +02:00
RetailUiRuntimeLease.cs refactor(app): extract focused window lifetime 2026-07-22 19:43:52 +02:00
RetailWindowHandle.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
RetailWindowLayoutPersistence.cs fix #390: retail display-change UI cascade — clamp + per-res reload 2026-08-13 17:42:50 +02:00
RetailWindowManager.cs fix(chat): CH6c review fixes — opaque default, opacity-transition register clauses 2026-08-10 14:48:39 +02:00
RetailWindowOpacityController.cs fix #379: chat opacity fade was scoped to every window, not just chat 2026-08-11 23:00:57 +02:00
RetainedPanelControllerGroup.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
RetainedUiInputBinding.cs refactor(input): own pointer and callback lifetime 2026-07-22 11:59:33 +02:00
SpewBoxController.cs feat(chat): retail text style — two-plane glyph outlines, authored SpewBox/chat styles 2026-08-10 19:28:34 +02:00
UiButton.cs fix(chargen): Campaign CC gate round 1 re-test 3 — R4-1..R4-4 2026-08-16 19:05:23 +02:00
UiButtonStateMachine.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiCatalogSlot.cs fix(ui): round-5 review polish — S1 block outline pass, S2 non-UiText outline paths, S3 citation fix 2026-08-10 22:15:20 +02:00
UiCheckboxBitfield64.cs feat(ui): Campaign OP slice OP5 — the Chat tab 2026-08-11 06:25:59 +02:00
UiCollapsibleFrame.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiDatFont.cs feat(chat): retail text style — two-plane glyph outlines, authored SpewBox/chat styles 2026-08-10 19:28:34 +02:00
UiDialogRoot.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiElement.cs fix(ui): #409 live-failure round — tooltips read the RUNTIME text first 2026-08-16 21:44:44 +02:00
UiEvent.cs fix(ui): OP5 re-check residuals R1/R2 (coordinator pass) — OP5 CLOSED 2026-08-11 08:13:24 +02:00
UiField.cs fix(ui): round-5 review polish — S1 block outline pass, S2 non-UiText outline paths, S3 citation fix 2026-08-10 22:15:20 +02:00
UiHost.cs feat(render): Vulkan campaign V11 step 2 — delete the OpenGL backend 2026-07-29 02:19:53 +02:00
UiItemList.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiItemSlot.cs fix: trade gate round 4 - "The trade has been cancelled." + retail's 2026-08-14 12:45:10 +02:00
UiLayoutPolicy.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiMenu.cs fix #385: Options dropdowns — white centered text + size-to-content popup 2026-08-13 08:51:57 +02:00
UiMeter.cs fix: jump-bar 'Height' right-aligned - per-STATE justification (user 2026-08-14 08:22:03 +02:00
UiNineSlicePanel.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiOptionToggleSlider.cs feat(ui): Campaign OP slice OP2 — tab control, template ListBox, UIOption widget mappings 2026-08-11 00:06:43 +02:00
UiPanel.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiRadar.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiRenderContext.cs feat(ui): mark store-only option rows dimmed (user-directed, gate 2) 2026-08-11 15:52:20 +02:00
UiResizeGrip.cs fix(chat): CH6a/b rework — grip media, retail window-id model, floaty fixture 2026-08-10 13:09:32 +02:00
UiRoot.cs fix(ui): #409 tooltip review fix round — F1-F11 2026-08-16 20:54:15 +02:00
UiScrollable.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiScrollablePanel.cs fix #371: straddling rows clip at the viewport edge instead of vanishing whole 2026-08-11 17:08:21 +02:00
UiScrollbar.cs fix(chargen): Campaign CC gate round 1 re-test 3 — R4-1..R4-4 2026-08-16 19:05:23 +02:00
UiShortcutDigitGraphics.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiSolidSpriteFill.cs fix #381: Options-panel footer needs an opaque backing field 2026-08-11 23:01:43 +02:00
UiTabPanel.cs feat(ui): Campaign OP slice OP4 — the Character tab 2026-08-11 04:31:34 +02:00
UiTemplateListBox.cs feat(ui): FA4 -- fellowship page fully live 2026-08-12 04:40:53 +02:00
UiText.cs fix(chargen): Campaign CC gate round 1 Batch E — text origin, caption escapes, value rects, scrollbars, name prefill 2026-08-16 14:07:19 +02:00
UiTextureElement.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiViewport.cs feat(render): Campaign V slice V6l commit 3 - the offscreen viewports draw on Vulkan 2026-07-28 18:05:24 +02:00
WindowNames.cs feat: secure trade with other players - wire, RuntimeTradeState, the 2026-08-14 11:49:13 +02:00
WorldDropDispatch.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +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.
  • RetailWindowManager.cs / RetailWindowHandle.cs — named top-level window registry, raise policy, visibility/focus/capture lifecycle, geometry events, lock propagation, and controller teardown.
  • IRetainedPanelController.cs — disposable lifecycle contract for panel-specific show/hide and descendant-focus behavior.
  • RetainedPanelControllerGroup.cs — one typed lifecycle owner for windows whose imported subtree is bound by multiple focused controllers; teardown is idempotent and runs in reverse construction order.
  • RetailWindowLayoutPersistence.cs / IRetainedWindowStateController.cs — per-character/per-resolution bounds, visibility, collapse/maximize, legacy migration, and safe resolution-clamped restore.
  • RetailUiRuntime.cs — production composition owner. Focused binding records carry state/action delegates and DAT/GL resolvers in; the runtime imports and mounts every gameplay window and owns controller, cursor, persistence, probe, tick, draw, restore, and disposal paths.
  • Layout/RetailWindowFrame.cs — the single production/Studio mount contract for imported-chrome and shared-wrapper windows.
  • UiHost.cs — one-shot wrapper: owns the UiRoot, a TextRenderer, and a default BitmapFont. Provides WireMouse / WireKeyboard helpers for Silk.NET plumbing and removes those device subscriptions before disposing the window manager/controllers and renderer.

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.