Campaign FA slice FA3 dual-lens review fix round. Blast MUST-FIX 1: SocialFriendsPageController/SocialSquelchPageController never wired their ListBox's own sibling scrollbar (Friends 0x10000517->0x10000518, Squelch 0x1000053E->0x10000543) — the two lists had NO scroll driver at all (no wheel fallback exists on UiScrollablePanel), making any roster past the visible extent completely unreachable, not just awkward to scroll. Both controllers now wire `scrollbar.Model = listBox.Scroll` scoped to their own page root, exactly like the four existing UiTemplateListBox consumers (Character/Chat/Config/KeyboardConfig). New tests prove the wiring AND that a >panel-height roster is actually reachable through it. Blast SHOULD-FIX 2/3: the Friends/Squelch resolver re-ran LayoutImporter.ImportInfos (a full DAT tree walk) under the shared DAT lock on EVERY row, every revision, even while the panel was closed — RetailUiRuntime.MountSocialPanel's TemplateResolver now caches each row template's ElementInfo the first time it is resolved and never re-Imports for that template id again. SocialPanelController additionally gates the Friends/Squelch Tick-driven rebuild on the panel's own visibility via the (previously unused) IRetainedPanelController OnShown/OnHidden hooks, so no DAT-locked rebuild work runs at all while the panel is closed. SocialFriendsPageController/SocialSquelchPageController also now only advance _lastRevision after every row resolves — a transient resolver miss no longer latches an empty roster until the next server-side change; it retries on the next Tick instead. Mechanism SHOULD-FIX 8: SocialPanelController.Tick() now returns early once disposed, matching every other J-slice teardown discipline (it was being ticked unconditionally forever since RetailUiRuntime never nulls the field). Mechanism SHOULD-FIX 1: IsShowingAllegiance's doc claimed the F3/F4 close-on-second-press semantics were "retail's Toggle-action semantics" — re-derived and confirmed NO retail OnAction consumer exists for either action anywhere in the binary. Relabeled as acdream's own OpenSpellbook-precedent convention; no register row added, following the same no-row precedent OpenSpellbook and every other non-toolbar Toggle panel already sets. Unknown filed as U11 in the panel-structure research doc's §8 table. Tests: 5 new (2 scrollbar-wiring pins, 2 long-roster-reachable-via- scrollbar, 1 hidden-panel-does-not-rebuild/shown-panel-catches-up); 2 existing tests updated for the new visibility gate. 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.