#416 (char-select roster highlight never cleared on hover-leave): three decomp-grounded mechanisms replace the media-keyed _availableStates approximation. - UIElement_Button::UpdateState_ @0x00471CF0: the button machine commits ONLY states authored on the button's OWN ElementDesc (AccessStateDesc gate); unauthored requests no-op, preserving custom semantic states. - UIElement::SetState @0x00464E70: an unauthored state id is coerced to state 0 (the unnamed base state) and committed — ported into UiDatElement.TrySetRetailState with the base-descriptor PassToChildren cascade arm. - The SetState media rule @0x004651c0: a committed state replaces the playing media ONLY when its media array is non-empty. UiButton now keeps per-face-segment media states under that rule (segments model retail's PassToChildren children), and LayoutImporter records the raw MediaCount including the File=0 draw-nothing images the drawable filter drops — the roster bar children's base state is exactly such an image, and it is what clears the bar. The row template truth (probe, installed DAT): the row authors EMPTY Normal/rollover/Highlight descriptors with PassToChildren; the three bar children author rollover/Highlight media, NO Normal state, and a File=0 base image. An empty-media Normal_pressed still never blanks a Normal-art button (the media rule keeps the previous art — the exact behavior the old gate approximated), and the Appearance spins' property-only Highlight now genuinely commits: label recolors, arrow art lingers — the retail split AP-222 approximated with a requested-keyed label hack, now retired. Live-verified at char select: hover +alex shows the grey bar, moving off clears it, the selected row keeps its amber bar. #415 (probe wait world-* verbs dead): the filed snapshot-reset diagnosis was wrong — the automation bridge simply never bound without ACDREAM_AUTOMATION_ARTIFACT_DIR. A facts-only WorldRevealFactsAutomationRuntime now binds whenever the retained UI exists; checkpoint/screenshot verbs still require the artifact directory and now report that instead of a generic timeout. App tests 5568/3 skips, Runtime 1756/0, UI.Abstractions 926/0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| Layout | ||
| Testing | ||
| AutoWieldController.cs | ||
| ClientCommandController.cs | ||
| ControlsIni.cs | ||
| CursorFeedbackController.cs | ||
| GameplayConfirmationController.cs | ||
| IconComposer.cs | ||
| IItemListDragHandler.cs | ||
| IRetainedPanelController.cs | ||
| IRetainedWindowStateController.cs | ||
| ItemDragPayload.cs | ||
| ItemEquipRules.cs | ||
| ItemInteractionController.cs | ||
| IUiChildrenAttachedListener.cs | ||
| IUiDatStateful.cs | ||
| IUiGlobalTimeListener.cs | ||
| IUiViewportRenderer.cs | ||
| MarkupDocument.cs | ||
| README.md | ||
| RetailChromeSprites.cs | ||
| RetailCursorCatalog.cs | ||
| RetailItemConfirmationController.cs | ||
| RetailPanelCatalog.cs | ||
| RetailSkillTrainingConfirmationController.cs | ||
| RetailUiRuntime.cs | ||
| RetailUiRuntimeLease.cs | ||
| RetailWindowHandle.cs | ||
| RetailWindowLayoutPersistence.cs | ||
| RetailWindowManager.cs | ||
| RetailWindowOpacityController.cs | ||
| RetainedPanelControllerGroup.cs | ||
| RetainedUiInputBinding.cs | ||
| SpewBoxController.cs | ||
| UiButton.cs | ||
| UiButtonStateMachine.cs | ||
| UiCatalogSlot.cs | ||
| UiCheckboxBitfield64.cs | ||
| UiCollapsibleFrame.cs | ||
| UiDatFont.cs | ||
| UiDialogRoot.cs | ||
| UiElement.cs | ||
| UiEvent.cs | ||
| UiField.cs | ||
| UiHost.cs | ||
| UiItemList.cs | ||
| UiItemSlot.cs | ||
| UiLayoutPolicy.cs | ||
| UiMenu.cs | ||
| UiMeter.cs | ||
| UiNineSlicePanel.cs | ||
| UiOptionToggleSlider.cs | ||
| UiPanel.cs | ||
| UiRadar.cs | ||
| UiRenderContext.cs | ||
| UiResizeGrip.cs | ||
| UiRoot.cs | ||
| UiScrollable.cs | ||
| UiScrollablePanel.cs | ||
| UiScrollbar.cs | ||
| UiShortcutDigitGraphics.cs | ||
| UiSolidSpriteFill.cs | ||
| UiTabPanel.cs | ||
| UiTemplateListBox.cs | ||
| UiText.cs | ||
| UiTextureElement.cs | ||
| UiViewport.cs | ||
| WindowNames.cs | ||
| WorldDropDispatch.cs | ||
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 (0x01click,0x15drag-begin,0x3Edrop,0x201WM_LBUTTONDOWN, …)UiElement.cs— base widget withOnDraw/OnEvent/OnHitTest/OnTickvirtuals, children list, ZOrder, focus/capture flagsUiPanel.cs—UiPanel(rect + optional bg/border),UiLabel,UiButtonUiRenderContext.cs— per-frame draw context with translate stackUiRoot.cs— top-of-tree + "Device" responsibilities (mouse/keyboard state, focus, modal, capture, drag-drop, tooltip timer). Mirrors the retailDAT_00837ff4Device 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 theUiRoot, aTextRenderer, and a defaultBitmapFont. ProvidesWireMouse/WireKeyboardhelpers 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+TextRendererpipeline
To build next
AcFont+FontCache— loadFontDBObjs fromportal.datrange0x40000000..0x40000FFF, bake 256×256 glyph atlas from the referencedRenderSurface(0x06xxxxxx). See slice 03 §4.- Dat sprite loader — decode
RenderSurfacedats as GL textures; addDrawSprite(uint datId, Rectangle dest, uint rgba)toUiRenderContext. CursorManager— OS cursor + dat-sourced custom cursors via slice 03 §7.- Scissor clipping — for panels with scrollable interiors (chat,
inventory grid).
GL_SCISSOR_TESTwrapped inUiRenderContext.PushScissor/PopScissor. - First concrete panel —
ChatWindowsince we have all 6 wire messages parsed already. See slice 05 §1. - 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.