acdream/docs/superpowers/plans/2026-07-10-retail-ui-fidelity-completion.md

661 lines
34 KiB
Markdown

# 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 typed capabilities/records (independent text `Editable` and
`Selectable` flags, 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<T>(id, role)` and `Optional<T>(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
- [x] 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;
- UI manager tooltip deadlines, global time 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.
- [x] 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.
- [x] Pin every method address and DAT property id before implementation.
### 0.2 Expand committed DAT conformance fixtures
- [x] Add skip-by-default generators and committed `ElementInfo` JSON fixtures for
toolbar `0x21000016`, inventory `0x21000023` plus mounted sublayouts,
paperdoll `0x21000024`, and character `0x2100002E`.
- [x] Include state media, cursor media, font ids, raw edge modes, default state,
min/max attributes, numeric state ids, and runtime-relevant scalar/property arrays.
- [x] Add inheritance cases proving derived explicit `false` and `0` override base
values rather than being mistaken for absence.
- [x] Add fixture assertions for every required controller role.
### 0.3 Repair the audit trail
- [x] Retire stale AP-38 (chat wrapping) and record UI issue #198 as closed by
`769ebef3` (the original #162 collided with a later movement issue).
- [x] Rewrite AP-37, AP-40, IA-15, and AP-66 to match current code.
- [x] Renumber the overwritten paperdoll AP-67/AP-68 rows and duplicate AP ids.
- [x] Add rows/issues for every confirmed unregistered gap from this plan.
- [x] 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 independent retail capabilities
- [x] Default factory-imported DAT text to display-only behavior. Model Editable
`0x16` and Selectable `0x27` as independent capabilities, matching retail; do
not collapse them into an exclusive interaction-mode enum.
- [x] Import retail Editable `0x16`, OneLine `0x20`, and Selectable `0x27` properties
through the typed property bag where the DAT supplies them.
- [x] Make focus, key capture, selection, clipboard, and pointer-drag behavior derive
exclusively from those capabilities.
- [x] Set the chat transcript to `Selectable`; keep the chat input explicitly
`Editable`; all labels remain `Display`.
- [x] Bind the imported vitals labels `0x100000EB/ED/EF` directly and delete the
synthesized duplicates.
- [x] 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
- [x] Extract a GL-free `UiButtonStateMachine` over enabled/hovered/pressed/
captured/toggled state and available DAT states.
- [x] Port retail transition precedence exactly; controller code supplies semantic
click/toggle callbacks only.
- [x] 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.
- [x] Make cursor media follow the active button state.
- [x] 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
- [x] Introduce `UiLayoutPolicy` containing the five raw edge modes `0..4`, original child
rect, and original parent rect. Preserve raw modes through `ElementReader`.
- [x] Port `UpdateForParentSizeChange` as a pure function, including right-track,
center mode `3`, proportional mode `4`, and inclusive-pixel behavior for all four
edges.
- [x] Keep `AnchorEdges` only as a compatibility adapter for programmatic widgets;
imported DAT elements use `UiLayoutPolicy` directly.
- [x] 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 UI-manager timing/focus behavior
- [x] Poll the hovered element's tooltip deadline in `UiRoot.Tick`, then broadcast
retail global time message `3` to listeners in deterministic tree order.
- [x] Tear down focus, capture, modal ownership, hover, drag, default input, and
window registration before a removed subtree can receive another time/input pulse.
- [x] Add typed focus/capture transition behavior; eliminate polling where retail reacts to
a transition.
- [x] Pin tooltip-before-global-time, capture-loss, modal, and subtree-removal
ordering tests. Keep double-click coverage with the existing input suite.
### 1.5 Add a narrow DAT named-state bridge
- [x] 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.
- [x] 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
- [x] Add `RetailWindowHandle` and lifecycle events: `Shown`, `Hidden`, `Moved`,
`Resized`, `Closed`, `LockChanged`.
- [x] Move named-window registry/raise policy behind `RetailWindowManager`; retain
thin `UiHost` forwarding APIs for callers.
- [x] Define `IRetainedPanelController : IDisposable` with idempotent `OnShown`,
`OnHidden`, and optional descendant-focus notification, without coupling `UiRoot`
to game/network semantics.
- [x] Make `UiRoot` emit visibility, descendant-focus, and pointer-capture changes;
the handle owns outer-frame geometry/opacity and the controller owns panel-specific
reactions.
- [x] 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
- [x] Extend `RetailWindowFrame.Mount` to return a handle and accept exact resize,
chrome, opacity, visibility and constraint policy.
- [x] 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.
- [x] Migrate vitals, chat, toolbar, inventory, radar, character, and Studio one at
a time. Delete all inline `UiNineSlicePanel` mount recipes from `GameWindow` and
`MockupDesktop`.
- [x] Make chat maximize operate on its handle's outer frame and import retail
min/max attributes and button states; remove hardcoded `320f`.
- [x] Correct chat scrollbar art from the actual inherited DAT roles, not literals.
**Acceptance (2026-07-10):** full suite green (4,538 passed, 5 skipped); live
Debug gate passed for shared chrome, resize/collapse, lock/raise, chat maximize,
scrollbar buttons, track/thumb dragging, wheel scrolling, and bottom-pin behavior.
### 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 Confirm the retail chat surface, then port its filters
The named `gmMainChatUI` build proves independently filtered chat/floaty windows;
it does **not** prove that DAT elements `0x10000522..0x10000525` are an in-window
numbered-tab model. Do not implement tab switching from the older inference.
- [ ] Confirm the role of `0x10000522..0x10000525` from live retail behavior or a
stronger DAT/call-site trace before assigning tab semantics.
- [ ] Add a Core/VM chat-line classification carrying retail message type/channel.
- [ ] Port the proven per-window display masks, unread state, and persistence only
where the PlayerModule/options payload proves it; otherwise register the absence.
- [ ] Route every retained/floating transcript through its 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
- [ ] Split the owned inventory window from the external/ground-container window.
The proven `ClientUISystem.groundObject` lifecycle sends
`NoLongerViewingContents` exactly once when an external view is replaced or
closed. Owned side-bag navigation/closing sends nothing unless a live packet
trace proves otherwise. Both lifecycles clear their own panel drag/view state;
repeated hide 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
- [ ] Remove `ApplyLocalRaise`: named retail sends one request, ghosts the clicked
button, and leaves XP/credits/ranks unchanged until the authoritative quality
update arrives.
- [ ] Permit only one raise request in flight; clear the awaiting/ghosted state on
the matching authoritative quality-change path. Pin the exact rejection cleanup
with a live ACE trace before adding any extra timeout or recovery behavior.
- [ ] Test one-in-flight enforcement, authoritative acknowledgement, rejection,
caps, and relog/disconnect cleanup without optimistic mutation or 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 |
| Tooltip/global UI time ordering and removed-subtree cleanup | 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 filter windows, opacity, channel availability, squelch/actions incomplete; numbered-tab premise unproven | 5 |
| Chat scrollbar arrows reversed | 2 / 5 |
| External-container close lifecycle missing; owned-side-bag `0x0195` premise disproved | 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 raises mutate optimistically instead of using proven server-authoritative flow | 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.