Task 6 registered Type 3 -> UiField globally, which broke acdream's Type-3 dat elements: in these layouts Type 3 is sprite-bearing CHROME (the 8-piece bevel corners, e.g. vitals 0x10000633 -> sprite 0x060074C3) and the transcript/input CONTAINER panels — NOT editable fields. UiField draws no dat sprite, so the vitals bevel corners would render empty; the regression was masked by weakening VitalsTree_ChromeCornerHasExpectedSprite (UiDatElement+sprite -> UiField+exists). Retail Type 3 IS UIElement_Field, but retail draws those chrome elements as inert media-bearing Fields, which our UiDatElement reproduces pixel-for-pixel without a spurious focus/edit affordance. The one true editable field — the chat input 0x10000016 — resolves to Type 12 and is controller-placed as a UiField (Variant B, kept). So Type 3 stays on the generic fallback; register it as UiField only when a window carries a factory-built editable Type-3 field (and UiField grows a background-media draw + an opt-in editable flag then). Restored the chrome-corner conformance test (asserts UiDatElement + sprite, an early warning if Type 3 is ever wrongly routed to UiField). Kept the good Task-6 work: UiField rename + the Variant-B input wiring (stray Type-12 placeholder removed). Full suite: 404 passed, 2 skipped, 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| Layout | ||
| ControlsIni.cs | ||
| MarkupDocument.cs | ||
| README.md | ||
| RetailChromeSprites.cs | ||
| TargetIndicatorPanel.cs | ||
| UiButton.cs | ||
| UiDatFont.cs | ||
| UiElement.cs | ||
| UiEvent.cs | ||
| UiField.cs | ||
| UiHost.cs | ||
| UiMenu.cs | ||
| UiMeter.cs | ||
| UiNineSlicePanel.cs | ||
| UiPanel.cs | ||
| UiRenderContext.cs | ||
| UiRoot.cs | ||
| UiScrollable.cs | ||
| UiScrollbar.cs | ||
| UiText.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.UiHost.cs— one-shot wrapper: owns theUiRoot, aTextRenderer, and a defaultBitmapFont. ProvidesWireMouse/WireKeyboardhelpers 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+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.