acdream/src/AcDream.App/UI
Erik c51b07ef95 test(gates): sequence the two-client remote-shadow gate with harness signals
Imported from the codex worktree's uncommitted work
(C:/Users/erikn/.codex/worktrees/bd98/acdream on
codex/atmospheric-rendering-campaign), on top of its 8b7b601b.

The remote-player shadow gate ran two clients on fixed sleeps, so the
observer could move before the primary had taken its 'before' screenshot,
or the primary could take its 'after' shot before the observer had moved.
Now the harness publishes named signal files into each client's artifact
directory and the routes block on them:

- IRetailUiAutomationRuntime.TryIsAutomationSignalPublished, implemented
  by WorldLifecycleAutomationController over <artifactDir>/signals/
  <name>.signal (names validated by AutomationArtifactName, so no path
  escape).
- 'wait signal <name> [timeoutMs]' in RetailUiAutomationScriptRunner.
- run-connected-render-pack-remote-player-gate.ps1 publishes
  'primary-before' to the observer after the primary's before-shot
  completes, then 'observer-moved' to the primary after the observer's
  remote-observer-moved checkpoint.
- Both routes teleport with an explicit heading and wait 12 s to settle.

Build green; the three touched App test classes pass 58/58 including the
two new signal tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 17:44:13 +02:00
..
Layout feat(render): implement Campaign AR and terrain fidelity 2026-08-22 13:13:29 +02:00
Testing test(gates): sequence the two-client remote-shadow gate with harness signals 2026-08-22 17:44:13 +02:00
AutoWieldController.cs fix(items): port retail useability and autowear rejection 2026-07-23 08:33:31 +02:00
ChatTranscriptLogWriter.cs feat(chat): CT-B4 — @log, and the research block that was a wrong question 2026-08-21 11:28:20 +02:00
ClientCommandController.cs feat(chat): CT-B4 — @log, and the research block that was a wrong question 2026-08-21 11:28:20 +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): restore retail vitals and window interactions 2026-08-20 13:26:35 +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
JournalPersistence.cs feat(journal): QJ3/QJ4/QJ5 — both remaining tabs are live 2026-08-21 15:42:05 +02:00
MarkupDocument.cs fix(mosstank): unclickable button, empty skill list, dev font, and chat output 2026-08-20 18:27: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(quest): QT5/QT6 — the Journal panel, and the button that was already there 2026-08-21 15:12:05 +02:00
RetailSkillTrainingConfirmationController.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
RetailUiRuntime.cs feat(render): implement Campaign AR and terrain fidelity 2026-08-22 13:13:29 +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(ui): restore retail vitals and window interactions 2026-08-20 13:26:35 +02:00
RetailWindowLockPresentationController.cs fix(ui): restore retail vitals and window interactions 2026-08-20 13:26:35 +02:00
RetailWindowManager.cs fix(ui): restore retail vitals and window interactions 2026-08-20 13:26:35 +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(ui): button property 0x0D was never "disabled", and it killed every Journal button 2026-08-21 15:58:03 +02:00
UiButtonStateMachine.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiCatalogSlot.cs feat: regen buffs, wand aura, spellbook assess, indicator press flash 2026-08-20 20:38:28 +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 feat(mosstank): buff trained skills and attributes, pick tiers by skill, manage mana 2026-08-20 18:09:02 +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 feat(chat): CT-C2 Escape leaves the entry; CT-C3 the timestamp is grey 2026-08-21 08:28:45 +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(ui): night-round review — F9/F10/F12 register + structure riders 2026-08-17 04:56:16 +02:00
UiLayoutPolicy.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiMenu.cs feat(render): implement Campaign AR and terrain fidelity 2026-08-22 13:13:29 +02:00
UiMeter.cs fix(ui): gate — no void frames around the login wormhole; vitals icons centered 2026-08-17 12:30:51 +02:00
UiNineSlicePanel.cs fix(ui): restore retail vitals and window interactions 2026-08-20 13:26:35 +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 fix(mosstank): unclickable button, empty skill list, dev font, and chat output 2026-08-20 18:27: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): morning gate — world tooltips ride retail's mouse-idle dwell, not the found edge 2026-08-17 08:49:11 +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(render): implement Campaign AR and terrain fidelity 2026-08-22 13:13:29 +02:00
UiText.cs feat(chat): CT-A5 — clicking a speaker's name opens a tell 2026-08-21 07:34:58 +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(quest): QT5/QT6 — the Journal panel, and the button that was already there 2026-08-21 15:12:05 +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.