From 00ab4a2a645099d3d85fcf64573a489e72acc02c Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 10 Jul 2026 16:47:40 +0200 Subject: [PATCH] docs(ui): plan retail UI fidelity completion --- ...026-07-10-retail-ui-fidelity-completion.md | 644 ++++++++++++++++++ 1 file changed, 644 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-10-retail-ui-fidelity-completion.md diff --git a/docs/superpowers/plans/2026-07-10-retail-ui-fidelity-completion.md b/docs/superpowers/plans/2026-07-10-retail-ui-fidelity-completion.md new file mode 100644 index 00000000..5998c178 --- /dev/null +++ b/docs/superpowers/plans/2026-07-10-retail-ui-fidelity-completion.md @@ -0,0 +1,644 @@ +# D.2b / D.5 Retail UI Fidelity Completion Implementation Plan + +> **Execution model:** implement one wave at a time. Every behavioral slice starts +> from named-retail/DAT evidence, lands with automated conformance, and stops at +> its own live visual gate. Do not combine waves into a big-bang rewrite. + +**Goal:** Bring the existing in-world retained GUI to retail behavioral parity, +close every confirmed audit gap, and establish an architecture that future retail +panels can extend without adding feature bodies to `GameWindow.cs` or duplicating +window/widget policy. + +**Scope:** the retained `UiRoot` tree, LayoutDesc importer, widget semantics, +window management, vitals, chat, toolbar/selected object, inventory, paperdoll, +character sheet, cursors, radar social markers, and the missing in-world retail +panels/HUD named in the current D.5/D.6 roadmap. Login, character selection, and +character creation remain a separate frontend milestone. + +**Retail oracle:** `docs/research/named-retail/acclient_2013_pseudo_c.txt`, +`docs/research/named-retail/acclient.h`, production LayoutDesc/Font/RenderSurface +DAT objects, and user-confirmed retail/live screenshots. Ghidra is fallback only +when the named decomp lacks a body. + +--- + +## 1. Non-negotiable architecture and code-style rules + +1. **Modern code, retail behavior.** Port retail control flow, constants, state + precedence, and boundary conditions. Modernize ownership and representation, + not observable behavior. +2. **No new feature bodies in `GameWindow.cs`.** It may construct typed binding + records and mount one `RetailUiRuntime`. Panel logic belongs in controllers; + pure AC rules belong in Core; GL-only viewport/cursor presentation remains in + App/Rendering. +3. **One owner per state.** Selection, target-use mode, shortcuts, open-container + state, window lifecycle, and pending optimistic actions each get one owner and + one event stream. Widgets display state; they do not create competing truth. +4. **Generic policy stays generic.** Text roles, button states, layout reflow, + focus, timers, window lifecycle, and persistence are widget/window services. + Never patch them separately in `ChatWindowController`, `InventoryController`, + or another panel. +5. **Bind existing DAT nodes.** Controllers use required/optional role binding by + element id. They do not synthesize a second data-bearing widget when the DAT + already contains the retail node. Runtime-created nodes are allowed only when + retail creates them dynamically and the code cites that method. +6. **Typed behavior, not stringly behavior.** State names remain DAT data, but C# + policy uses enums/records (`UiTextInteractionMode`, button phase, cursor role, + shortcut kind, window event). Controllers must not implement precedence by + scattered state-name string lists. +7. **Controllers have lifetimes.** Every subscribing controller implements + `IDisposable`; subscriptions use removable named handlers or a small + `SubscriptionBag`. `RetailUiRuntime.Dispose` tears down the complete tree. +8. **Required bindings fail loudly.** Add a `LayoutBinding` helper with + `Require(id, role)` and `Optional(id)`. Missing required roles disable the + affected window with one diagnostic instead of silently producing a partial UI. +9. **No raw game-state writes.** All mutations go through Core-owned eventful + APIs and, where retail predicts locally, a pending/confirm/rollback ledger. +10. **Every divergence is auditable.** Add, amend, retire, or renumber the + divergence row in the same commit as the behavior change. + +### Layer ownership + +| Layer | Owns | +|---|---| +| `AcDream.Core` | Selection state, shortcut model/plans, item-use and wield legality, social/title state, pure retail math/state machines | +| `AcDream.Core.Net` | Wire parsers/builders, sequence handling, authoritative event delivery | +| `AcDream.UI.Abstractions` | Backend-neutral VMs, commands, settings DTOs/persistence contracts | +| `AcDream.App/UI` | Retained widgets, layout/window runtime, panel controllers, snapshot adapters | +| `AcDream.App/Rendering` | DAT texture/font/cursor resolution and paperdoll/world-HUD GL rendering | +| `GameWindow` | Construction, dependency delegates, render/tick calls, disposal only | + +--- + +## 2. Target runtime shape + +Add a `RetailUiRuntime` composition owner under `AcDream.App/UI`. It receives the +`UiHost`, DAT resolvers, and small per-subsystem binding records such as +`VitalsRuntimeBindings`, `ChatRuntimeBindings`, and `InventoryRuntimeBindings`. +It owns: + +- `RetailWindowManager` -- named handles, show/hide/raise, global lock, + lifecycle events, clamping, and persistence; +- all panel controllers and their disposal; +- the retained input/cursor action router; +- window mount factories which all use `RetailWindowFrame`; +- the only App-layer subscriptions from game state into retained UI. + +`RetailWindowHandle` contains the outer frame, imported content root, controller, +and typed lifecycle methods. Controllers resize/move the handle's **outer frame**, +never an arbitrary imported child. `UiRoot` continues to own hit testing, capture, +focus, and drawing; it does not read settings or send game actions. + +The importer carries a typed `UiPropertyBag` which distinguishes an absent property +from an explicit `false` or `0`, merges base properties before derived properties, +and preserves numeric state ids alongside names/media. No widget or shared runtime +contains element-id exceptions; those stay in window descriptors and controllers. + +Complex panels use immutable snapshots at their draw seam, following the radar +pattern. Simple widgets may use scalar providers. A controller must not rebuild a +large domain model through dozens of unrelated per-frame closures. + +--- + +## 3. Wave 0 -- oracle, fixtures, and ledger repair + +This wave changes no runtime behavior. + +### 0.1 Consolidate missing retail pseudocode + +- [ ] Write focused pseudocode notes for: + - `UIElement_Text` interaction/property modes; + - `UIElement_Button::UpdateState_`, mouse transitions, enabled/toggle behavior; + - `UIElement::UpdateForParentSizeChange` raw edge modes 0-4; + - Device timer delivery and focus notifications; + - `gmToolbarUI` global messages, drop branches, mana/stack behavior; + - `ItemHolder::UseObject` / `AttemptPlaceIn3D` decision trees; + - chat maximize, alpha, tabs, talk-focus and squelch; + - paperdoll priority, Aetheria, hit testing and race updates; + - character titles, luminance, and raise acknowledgement. +- [ ] Correct the roadmap premise around shortcuts: `ShortCutData` is + `index_`, `objectID_`, `spellID_`, while `PlayerModule::favorite_spells_[8]` + is a separate subsystem. This retail toolbar restores only nonzero + `objectID_`; it must not grow invented spell-slot behavior. +- [ ] Pin every method address and DAT property id before implementation. + +### 0.2 Expand committed DAT conformance fixtures + +- [ ] Add skip-by-default generators and committed `ElementInfo` JSON fixtures for + toolbar `0x21000016`, inventory `0x21000023` plus mounted sublayouts, + paperdoll `0x21000024`, and character `0x2100002E`. +- [ ] Include state media, cursor media, font ids, raw edge modes, default state, + min/max attributes, numeric state ids, and runtime-relevant scalar/property arrays. +- [ ] Add inheritance cases proving derived explicit `false` and `0` override base + values rather than being mistaken for absence. +- [ ] Add fixture assertions for every required controller role. + +### 0.3 Repair the audit trail + +- [ ] Retire stale AP-38 (chat wrapping) and close/update stale issue #162. +- [ ] Rewrite AP-37, AP-40, IA-15, and AP-66 to match current code. +- [ ] Renumber the overwritten paperdoll AP-67/AP-68 rows and duplicate AP ids. +- [ ] Add rows/issues for every confirmed unregistered gap from this plan. +- [ ] Mark each row with its retirement wave below. + +**Gate:** docs/fixtures review, App tests green, no production-code diff. + +--- + +## 4. Wave 1 -- exact retained-widget foundation + +### 1.1 Give Type-12 text an explicit interaction mode + +- [ ] Add `UiTextInteractionMode { Display, Selectable, Editable }`; default to + `Display` for factory-imported DAT text. +- [ ] Import retail Editable `0x16`, OneLine `0x20`, and Selectable `0x27` properties + through the typed property bag where the DAT supplies them. +- [ ] Make focus, key capture, selection, clipboard, and pointer-drag behavior derive + exclusively from this mode. +- [ ] Set the chat transcript to `Selectable`; keep the chat input explicitly + `Editable`; all labels remain `Display`. +- [ ] Bind the imported vitals labels `0x100000EB/ED/EF` directly and delete the + synthesized duplicates. +- [ ] Add tests proving display labels cannot steal focus, capture, or window drag. + +**Oracle:** `UIElement_Text::OnSetAttribute @ 0x0046A640`, mouse/key methods +`0x00469370..0x0046A4A0`, `gmVitalsUI::PostInit @ 0x004BFCE0`. + +### 1.2 Port the button state machine + +- [ ] Extract a GL-free `UiButtonStateMachine` over enabled/hovered/pressed/ + captured/toggled state and available DAT states. +- [ ] Port retail transition precedence exactly; controller code supplies semantic + click/toggle callbacks only. +- [ ] Preserve retail state ids `1/2/3`, selected `6/7/8`, and ghosted `13`; if the + requested state is absent, retain the current custom semantic state rather than + overwriting states such as `Maximized`, `Minimized`, or radar lock. +- [ ] Make cursor media follow the active button state. +- [ ] Cover normal, rollover, pressed, released-outside, disabled, toggle-on/off, + and capture-loss tests using real fixture state names. + +**Oracle:** `UIElement_Button::UpdateState_ @ 0x00471CF0`, `MouseDown +@ 0x00471FF0`, `MouseUp @ 0x004720E0`, `MouseOverTop @ 0x004721F0`. + +### 1.3 Replace lossy anchors with the retail layout policy + +- [ ] Introduce `UiLayoutPolicy` containing the five raw edge modes `0..4`, original child + rect, and original parent rect. Preserve raw modes through `ElementReader`. +- [ ] Port `UpdateForParentSizeChange` as a pure function, including right-track, + center mode `3`, proportional mode `4`, and inclusive-pixel behavior for all four + edges. +- [ ] Keep `AnchorEdges` only as a compatibility adapter for programmatic widgets; + imported DAT elements use `UiLayoutPolicy` directly. +- [ ] Test every raw mode against hand-translated decomp vectors at 800x600, + widescreen, parent grow/shrink, and nested layouts. + +**Oracle:** `UIElement::UpdateForParentSizeChange @ 0x00462640`. + +### 1.4 Finish Device-level timing/focus behavior + +- [ ] Drain registered timers from `UiRoot.Tick` in deterministic due-time/order. +- [ ] Remove a timer before dispatch for reentrancy safety and cancel subtree timers + when a window/widget is removed so stale callbacks cannot fire. +- [ ] Add typed focus/capture/window events; eliminate polling where retail reacts to + a transition. +- [ ] Pin double-click, tooltip, capture-loss, modal, and timer ordering tests. + +### 1.5 Add a narrow DAT named-state bridge + +- [ ] Bind imported named states such as `ShowDetail`, `HideDetail`, enabled/ + disabled button roles, and empty-cell art without globally re-registering Type 3 + elements or teaching controllers how the importer stores states. +- [ ] Prove state changes preserve the imported rect, anchors, dimensions, and media + using production LayoutDesc fixtures. + +**Gate:** vitals/chat/radar/toolbar UI Studio fixtures unchanged visually; buttons +show retail hover/press; vitals bars drag the window instead of selecting invisible +text; full suite green; user visual gate. + +--- + +## 5. Wave 2 -- retained window runtime, lifecycle, and persistence + +### 2.1 Introduce `RetailWindowManager` and typed handles + +- [ ] Add `RetailWindowHandle` and lifecycle events: `Shown`, `Hidden`, `Moved`, + `Resized`, `Closed`, `LockChanged`. +- [ ] Move named-window registry/raise policy behind `RetailWindowManager`; retain + thin `UiHost` forwarding APIs for callers. +- [ ] Define `IRetainedPanelController : IDisposable` with idempotent `OnShown`, + `OnHidden`, and optional descendant-focus notification, without coupling `UiRoot` + to game/network semantics. +- [ ] Make `UiRoot` emit visibility, descendant-focus, and pointer-capture changes; + the handle owns outer-frame geometry/opacity and the controller owns panel-specific + reactions. +- [ ] On hide/unregister, clear descendant focus, capture, modal ownership, drag + state, and default text input. Remove the max/min button from any shared close-id + convention; each controller binds close and maximize explicitly. + +### 2.2 Use one mount path for every production window + +- [ ] Extend `RetailWindowFrame.Mount` to return a handle and accept exact resize, + chrome, opacity, visibility and constraint policy. +- [ ] Support both imported roots that already own retail chrome and shared + nine-slice wrappers, with independent X/Y resize policy, so migrations cannot + double-frame or double-inset content. +- [ ] Migrate vitals, chat, toolbar, inventory, radar, character, and Studio one at + a time. Delete all inline `UiNineSlicePanel` mount recipes from `GameWindow` and + `MockupDesktop`. +- [ ] Make chat maximize operate on its handle's outer frame and import retail + min/max attributes and button states; remove hardcoded `320f`. +- [ ] Correct chat scrollbar art from the actual inherited DAT roles, not literals. + +### 2.3 Persist the complete UI layout + +- [ ] Replace radar-only persistence with `UiWindowLayout` containing position, + size, visibility, collapsed/maximized state, and any retail-persisted panel state. +- [ ] Key layouts by character and resolution, matching retail `saveui/loadui` + semantics; add migration from existing radar entries. +- [ ] Restore only after the character context and screen size are known; clamp + safely on resolution changes. +- [ ] Save move/resize on interaction completion, visibility on transition, and + never let the temporary `default` login key overwrite character-specific state. +- [ ] Apply global LockUI through the window manager to every window. + +### 2.4 Enforce controller teardown + +- [ ] Apply the same `IRetainedPanelController : IDisposable` contract; allow plain + `IDisposable` only for controllers that genuinely have no visibility/focus reaction. +- [ ] Convert inline event lambdas to removable handlers/subscription tokens. +- [ ] Dispose controllers on runtime shutdown/rebind and test that relog/recreate + does not double-subscribe. +- [ ] Dispose controller subscriptions before session/data sources and before the + UI host; make every disposal path idempotent. + +### 2.5 Extract the composition block + +- [ ] Create `RetailUiRuntime` and focused binding records. +- [ ] Move DAT import, window creation, controller construction, registry, and + persistence wiring out of `GameWindow.OnLoad`. +- [ ] Keep GL resolver construction and delegate supply in `GameWindow`; target a + short, declarative mount call and one tick/draw/dispose path. + +**Gate:** move/resize/toggle every window, restart at two resolutions, switch +characters, verify exact restore and global lock; chat maximize/minimize must resize +chrome and content together; full suite and user visual gate. + +--- + +## 6. Wave 3 -- single-owner selection, item interaction, and cursors + +### 3.1 Centralize selection + +- [ ] Add Core `SelectionState` with selected guid, previous guid, reason/source, + and one change event. +- [ ] Route world pick, radar click, inventory selection, paperdoll selection, + toolbar selected strip, target indicator, examine/use, and plugins through it. +- [ ] Delete panel-local competing selection state except transient hover/row focus. +- [ ] Add one App interaction-state owner for `None`, `Use`, `Examine`, and + `UseItemOnTarget(sourceGuid)`; Core selection remains session truth while target + mode remains UI orchestration state. + +### 3.2 Port item-use/drop legality into Core + +- [ ] Translate `ItemHolder::UseObject`, `DetermineUseResult`, and + `AttemptPlaceIn3D` to pure/testable Core policy records. +- [ ] Include ready/busy, ownership, vendor/trade, PK/rare confirmation, stack, + ground/drop, and target-type gates. +- [ ] Keep App `ItemInteractionController` as orchestration: ask policy, show modal/ + toast, send the selected wire action, and manage pending state. + +### 3.3 Add one item primary-click chokepoint + +- [ ] Define a controller-level activation router: target-mode acquisition first, + then window-specific fallback (select/open/use). +- [ ] Return a typed result (`NotActive`, `ConsumedSuccess`, `ConsumedRejected`) so + both valid and rejected target attempts are consumed and cannot accidentally fall + through to selection, double-click use, or container opening. +- [ ] Route toolbar, inventory grid, bags, paperdoll slots/body, main pack and world + objects through it. Widgets expose events; they do not depend on game controllers. +- [ ] Add end-to-end tests for health-kit target use on self, inventory item, worn + item, and world entity, including invalid-target behavior. + +### 3.4 Complete retail cursor precedence and catalog + +- [ ] Port every reachable branch of `ClientUISystem::UpdateCursorState`, not only + target enums `0x27/28/29`. +- [ ] Separate global world-interaction cursors from widget-local cursor media; + apply retail precedence so a slot hover cannot replace the global target bullseye. +- [ ] Cover default/combat/use/examine/busy, drag accept/reject, resize/move/text, + and target states with golden EnumIDMap tests. +- [ ] Keep OS cursor fallback only as an explicit registered adaptation with a + visible diagnostic. + +**Gate:** live use/drop/target matrix with retail cursor art and selection updates; +no click path bypasses the router; full suite and user visual gate. + +--- + +## 7. Wave 4 -- toolbar and selected-object completion + +### 4.1 Wire retail quick-slot input + +- [ ] Add a focused retained-UI input action controller which maps + `UseQuickSlot_1..9`, `UseQuickSlot_14..18`, and `CreateShortcut` to + `ToolbarController` methods. +- [ ] Port use-vs-select semantics from `gmToolbarUI::ListenToGlobalMessage`. +- [ ] Keep `GameWindow.OnInputAction` to one delegation seam. + +### 4.2 Preserve the exact shortcut model; keep the toolbar object-only + +- [ ] Model the 18 raw `ShortCutData` entries exactly as signed `index_`, unsigned + `objectID_`, and unsigned `spellID_`; verify packing against the retail packer and + protocol captures before changing the existing builder. +- [ ] Preserve all three fields through parse, session storage, mutation, and wire + serialization even though `gmToolbarUI::UpdateFromPlayerDesc @ 0x004BF810` + restores only entries with a nonzero `objectID_`. +- [ ] Keep the main toolbar item/object-only. Feed `PlayerModule::favorite_spells_[8]` + and the already parsed `HotbarSpells` into the separate spell-bar subsystem in + Wave 10. + +### 4.3 Port every drag/drop branch + +- [ ] Add a pure `ShortcutDropPlanner` keyed by source kind: toolbar reorder, + inventory fresh add, occupied target, inventory-button target, + off-bar removal, and full-stack merge replacement. +- [ ] For an inventory-to-occupied-slot drop, place the displaced shortcut at + retail's first-empty-to-the-right slot, never at the inventory grid index. +- [ ] Validate the complete mutation before changing state, apply it atomically, and + emit exact Remove/Add wire ordering. Use pending/confirm/rollback only where the + protocol actually exposes rejection; never leave a partially mutated bar. +- [ ] On full-stack merge, rekey every matching object shortcut and preserve the + non-object fields of its raw `ShortCutData` entry. + +### 4.4 Finish toolbar controls + +- [ ] Bind all seven retail panel buttons by their DAT panel-id attribute plus combat, + Use, and Examine actions; dispatch panel buttons through the window registry, + ghosting unavailable panels rather than constructing placeholders. +- [ ] Make open/closed states event-driven from `RetailWindowManager`. +- [ ] Complete selected-object item mana, formatted stack count, stack entry and + split slider; port `RecvNotice_UpdateItemMana`, `RecvNotice_SplitStack`, and + `HandleSelectionChanged` precedence. +- [ ] Replace the PWD-only health gate with the exact attackable/player/pet policy. +- [ ] Add item-mana query/cancel state, health query cancellation, and one shared + split-quantity owner for the entry, slider, and item drag payload. + +**Oracles:** `gmToolbarUI @ 0x004BD0C0..0x004BF380`, +`UIElement_UIItem::SetShortcutNum`, shortcut wire builders. + +**Gate:** bare/Ctrl keys 1-9, Alt+5..9, item shortcuts, occupied-slot +replacement, inventory-button drop, peace/war digits, player/monster/item/stack/ +mana selections; full suite and user visual gate. + +--- + +## 8. Wave 5 -- vitals and chat parity + +### 5.1 Finish vitals state behavior + +- [ ] Bind `0x100004A9` and port root `HideDetail/ShowDetail` toggling through the + generic state/timer system. +- [ ] Verify normal/detail dimensions, art, values, resize and hit behavior. + +### 5.2 Use one ChatVM/runtime model + +- [ ] Construct one ChatVM with FPS/position providers and share it between retained + chat and ImGui devtools. +- [ ] Keep devtools visibility independent; do not duplicate chat state. + +### 5.3 Port chat window mechanics + +- [ ] Complete exact maximize/minimize, focus alpha transition, opacity bounds, + send/max button states, scrollbar orientation and resizing. +- [ ] Port half-parent-height growth, imported min/max constraints, saved geometry, + bottom pinning, and restore behavior on the **outer frame**. +- [ ] Drive alpha from focus/lifecycle events and retail timing, not a fixed `0.75`. + +### 5.4 Implement tabs and filters + +- [ ] Add a Core/VM chat-line classification carrying retail message type/channel. +- [ ] Implement numbered tab models, active tab, display masks, unread state and + retail-backed persistence if the PlayerModule/options payload proves it. If the + masks are not persisted by this build, register that fact instead of inventing a + per-character format. +- [ ] Route each transcript through its active filter without duplicating ChatLog. + +### 5.5 Complete talk-focus and moderation actions + +- [ ] Enable fellowship/allegiance/patron/vassal/monarch/society options from live + social state rather than a hardcoded allow-list. +- [ ] Put availability behind one provider backed by Turbine room membership, + Fellowship, Allegiance, and Society/Olthoi state; use it for both menu enablement + and outbound-send validation. +- [ ] Implement Tell-to-Selected, selection notices, squelch list/filter, and + clickable-name actions. +- [ ] Preserve exact retail word wrapping and color mapping; add rich runs where + retail renders mixed styles. + +**Oracles:** `gmVitalsUI @ 0x004BFC00`, `gmMainChatUI +@ 0x004CCCC0..0x004CE2A0`, `UIElement_Text`, ChatFilter methods. + +**Gate:** vitals detail toggle; chat maximize/fade; every tab/filter; Say/Tell/ +Reply/channel; social enable/disable; squelch and copy/select; full suite and user +visual gate. + +--- + +## 9. Wave 6 -- inventory completion + +- [ ] On inventory `Hidden/Closed`, have the controller send + `NoLongerViewingContents` exactly once for the retail-eligible side/external + container, clear the viewed-container and panel drag state, and restore main-pack + presentation. Main-pack close sends nothing; repeated hiding is idempotent. +- [ ] Route grid/bag clicks through target mode and global `SelectionState`. +- [ ] Retire capacity/burden fallbacks only after wire captures prove + `ContainersCapacity`, `ItemsCapacity`, `EncumbranceVal`, and augmentation delivery. +- [ ] Port source dimming, closed-full-bag prediction, capacity direction from DAT, + selection/open overlays, and full-stack merge reconciliation. +- [ ] Replace procedural constants with imported properties where retail DAT carries + them; keep visually confirmed literals only with an explicit registered row. + +**Oracles:** `gmInventoryUI`, `gmBackpackUI`, `gm3DItemsUI`, +`UIElement_ItemList @ 0x004E16E0..0x004E4EF5`, inventory game events. + +**Gate:** main pack + every side bag; close/reopen; full/empty containers; target +use; select/selected strip; reorder/merge/drop/unwield; resize/scroll at capacity; +full suite and user visual gate. + +--- + +## 10. Wave 7 -- paperdoll completion + +- [ ] Replace the ad-hoc slot array with a complete retail slot descriptor table, + including Aetheria `0x10000595/596/597` and exact equip masks. +- [ ] Drive Aetheria visibility from authoritative unlock/player state, not an + unconditional layout toggle. +- [ ] Port `GetUpperInvObj` priority via Core `InventoryPlacement` rules so layered + equipment is deterministic and retail-correct. +- [ ] Port `AutoWieldIsLegal` and the dual-wield shield-slot special before accepting + a drop; share legality with double-click auto-wield. +- [ ] Implement doll body-part hit testing, worn-item lookup, select/examine, + drag-icon preparation, and part-selection lighting. +- [ ] Resolve empty-slot art/visibility separately for doll and slot views from DAT; + remove inventory-grid art from equip slots unless retail uses it in that state. +- [ ] Port `UpdateForRace` and retain the cdb-confirmed Horan default; keep the RTT/FBO + renderer as an explicit equivalent adaptation. +- [ ] Resolve an immutable `PaperdollPresentation` from heritage, gender, and body + type, including retail camera, heading, pose, and DAT-selected assets. + +**Oracles:** `gmPaperDollUI @ 0x004A3590..0x004A5F90`, +`InventoryPlacement::DetermineHigherPriority`, `AutoWieldIsLegal`. + +**Gate:** armor/non-armor toggle; layered clothing; Aetheria; dual wield; invalid +wield; body hover/select/examine/drag/highlight; at least two races; full suite and +user visual gate. + +--- + +## 11. Wave 8 -- character window completion and decomposition + +### 8.1 Decompose before adding behavior + +- [ ] Replace the 1,900-line static `CharacterStatController.Bind` with an instance + `CharacterWindowController` orchestrating focused `AttributesTabController`, + `SkillsTabController`, `TitlesTabController`, and `CharacterFooterController`. +- [ ] Share typed row/footer view models; no mutable one-element closure arrays. +- [ ] Preserve the current accepted Attributes/Skills output during the refactor. + +### 8.2 Complete missing state + +- [ ] Add Core title collection/current-title state and net event delivery. +- [ ] Implement the Titles tab refresh, selection and current-title action. +- [ ] Populate live identity title and level-200+ luminance fields/progress from the + exact Int64 properties and CharacterTitleTable. +- [ ] Port every attribute/vital/skill footer state and button availability branch. + +### 8.3 Reconcile stat raises + +- [ ] Trace whether retail waits for authoritative state or predicts a raise before + choosing the implementation. If it waits, remove optimistic mutation. If it + predicts, add per-target pending snapshots for XP, credits, ranks, and advancement + class, then confirm/rollback on the exact retail/ACE paths. +- [ ] Test multiple in-flight raises, reordered echoes, rejection, caps, and relog/ + disconnect cleanup without retry loops. + +**Oracles:** `gmStatManagementUI`, `gmAttributeUI`, `gmSkillUI`, +`gmCharacterTitleUI @ 0x0049A610`, `UpdateExperience @ 0x004F0A70`. + +**Gate:** attributes, all skill buckets, train/raise/reject, Titles, live title, +luminance at a level-200 fixture, resize/scroll; full suite and user visual gate. + +--- + +## 12. Wave 9 -- social state, radar completion, and social panels + +- [ ] Add Core fellowship and allegiance state stores driven by the exact full/ + incremental game events. +- [ ] Feed fellowship leader/member and allegiance membership into + `RadarSnapshotProvider.relationshipFor`, retiring AP-90. +- [ ] Reuse the same state for chat availability/filtering and retained Fellowship/ + Allegiance panels; never infer relationships from names or chat text. +- [ ] Port membership lists, leader state, vitals, tree expansion, and actions from + their named retail controllers/DAT layouts. + +**Gate:** join/leave/leader change and allegiance update live; radar shapes/colors, +chat menu availability, and both panels update from the same state; full suite and +user visual gate. + +--- + +## 13. Wave 10 -- missing retained panels and remaining HUD + +Each item below gets its own focused pseudocode/spec/implementation plan and visual +gate, but must use the completed runtime/widget/window architecture rather than add +new infrastructure locally. + +1. **Main panel host, combat and powerbar** -- retail panel switching, combat state, + attack height and power timing using layouts `0x21000073/72`. +2. **Spellbook, effects and separate favorite-spell bars** -- learned spells, + schools/tabs, eight favorite lists, spell-bar drag/reorder, cast targeting, active + effects, mana/reagent/error states. Never route these through `gmToolbarUI`. +3. **Social and examine residuals** -- Friends/Squelch plus shared Fellowship/ + Allegiance state, complete selected-target/examine data and actions. +4. **Floating chat** -- reuse the same provider-backed `ChatVM`, shared log, filters, + availability and squelch state; do not create another chat model. +5. **Quests, map, vitae, options and smartbox** -- retail layouts, state and actions, + including key-binding capture and settings apply/revert without an ImGui runtime + dependency. +6. **Vendor, trade, salvage and tinkering** -- only after their authoritative + gameplay models exist; dual inventories, price/value, quantities, + confirmation and authoritative transaction reconciliation. +7. **Remaining D.6 HUD** -- target name/health presentation, damage floaters, world + name plates and status text through dedicated Core snapshot + App renderer seams. + +**Gate per panel:** golden DAT fixture, controller tests, complete wire/action +scenario, UI Studio fixture, live user visual gate, divergence cleanup. + +--- + +## 14. Coverage map for confirmed audit findings + +| Confirmed gap | Retirement wave | +|---|---:| +| Imported display text steals focus/drag; duplicate vitals labels | 1 | +| Buttons lack automatic retail states | 1 | +| Edge mode 3 recenter and mode 4 policy lost | 1 | +| Device timers never fire | 1 | +| Chat maximize changes content, not outer frame | 2 / 5 | +| Only radar persists position; windows lack lifecycle | 2 | +| Controller subscriptions are not disposed | 2 | +| GameWindow owns repeated mount/composition logic | 2 | +| Item interaction is only a subset of retail policy | 3 | +| Target cursor precedence/catalog incomplete | 3 | +| Inventory target click and panel-local selection | 3 / 6 | +| Toolbar hotkeys dead | 4 | +| Inventory-to-occupied toolbar drop corrupts placement | 4 | +| Toolbar raw spell field discarded / separate favorite spell bars missing | 4 / 10 | +| Use/Examine/combat/panel buttons missing | 4 | +| Mana/stack selected-object behavior missing | 4 | +| Vitals detail state missing | 5 | +| Duplicate ChatVM lacks `/loc` and `/framerate` providers | 5 | +| Chat tabs, opacity, channel availability, squelch/actions incomplete | 5 | +| Chat scrollbar arrows reversed | 2 / 5 | +| Inventory close skips `NoLongerViewingContents` | 2 / 6 | +| Inventory capacity/burden/drag residuals | 6 | +| Paperdoll Aetheria, priority, legality, dual wield, body interactions | 7 | +| Paperdoll empty art and per-race behavior | 7 | +| Character Titles inert, live title/luminance missing | 8 | +| Character raise authoritative/prediction semantics are unreconciled | 8 | +| Radar fellowship/allegiance state missing | 9 | +| Combat/powerbar/spellbook/effects/social residuals/floating chat/quests/map/vitae/options/smartbox/vendor/trade/salvage/tinkering/world HUD absent | 10 | +| Stale/missing/colliding divergence rows | 0, then every wave | + +--- + +## 15. Verification and commit discipline + +For every behavioral task: + +1. Read/write the pseudocode and cite the retail address. +2. Add a failing pure/widget/controller test. +3. Implement the smallest coherent layer change. +4. Run the focused project tests, `dotnet build AcDream.slnx --no-restore`, then + `dotnet test AcDream.slnx --no-build --no-restore`. +5. Update the divergence register and relevant issue in the same commit. +6. Render the production-DAT UI Studio fixture. +7. Launch with `ACDREAM_RETAIL_UI=1` and stop at the wave's user visual gate. +8. Commit one behavior/refactor slice; never mix unrelated cleanup into it. + +Safe parallel work: isolated Core models, net parsers, fixture generators, and +widget unit tests. Integration, `RetailUiRuntime`, GameWindow extraction, input +arbitration, and GL/paperdoll wiring stay with the lead agent that holds full +context. + +### Final parity seal + +- [ ] Every production retained layout has a committed conformance fixture. +- [ ] Every required controller binding is asserted. +- [ ] Every reachable named retail branch is either ported or has a current, + uniquely-numbered divergence row with an approved equivalence argument. +- [ ] All windows persist correctly per character and resolution. +- [ ] Full automated suite is green with no new warnings. +- [ ] UI Studio screenshot set is reviewed at reference and widescreen sizes. +- [ ] One live regression tour covers every window, action and cross-panel flow. +- [ ] The user passes the final side-by-side retail visual gate.