# 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(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 - [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 - [x] Replace radar-only persistence with `UiWindowLayout` containing position, size, visibility, collapsed/maximized state, and any retail-persisted panel state. - [x] Key layouts by character and resolution, matching retail `saveui/loadui` semantics; add migration from existing radar entries. - [x] Restore only after the character context and screen size are known; clamp safely on resolution changes. - [x] Save move/resize on interaction completion, visibility on transition, and never let the temporary `default` login key overwrite character-specific state. - [x] Apply global LockUI through the window manager to every window. **Acceptance (2026-07-10):** schema-v2 per-character/resolution restore passed a live Debug restart with every retained window persisted. The gate also caught and closed hidden-inventory baseline capture (expanded main-pack rows) and chat's clamped maximize jump (lower edge now remains fixed). Full suite green: 4,545 passed, 5 skipped. ### 2.4 Enforce controller teardown - [x] Apply the same `IRetainedPanelController : IDisposable` contract; allow plain `IDisposable` only for controllers that genuinely have no visibility/focus reaction. - [x] Convert inline event lambdas to removable handlers/subscription tokens. - [x] Dispose controllers on runtime shutdown/rebind and test that relog/recreate does not double-subscribe. - [x] Dispose controller subscriptions before session/data sources and before the UI host; make every disposal path idempotent. **Acceptance (2026-07-10):** every subscribing retained controller is owned by its typed window handle (toolbar/inventory use reverse-order controller groups), late attachment is lifecycle-safe, Silk input handlers are removable, and shutdown disposes the retained runtime before session sources. Recreate/disposal regression tests pass; full suite green: 4,549 passed, 5 skipped. ### 2.5 Extract the composition block - [x] Create `RetailUiRuntime` and focused binding records. - [x] Move DAT import, window creation, controller construction, registry, and persistence wiring out of `GameWindow.OnLoad`. - [x] Keep GL resolver construction and delegate supply in `GameWindow`; target a short, declarative mount call and one tick/draw/dispose path. **Acceptance (2026-07-11):** the Debug live-world gate passed after two graceful restart cycles. Vitals, radar, chat, toolbar, character, inventory/paperdoll, saved layouts, cursor/input, and window controls remained responsive with no double dispatch. `GameWindow` now supplies one typed mount graph and delegates retained tick/draw/restore/dispose to `RetailUiRuntime`. Full suite green: 4,549 passed, 5 skipped. **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 - [x] Add Core `SelectionState` with selected guid, previous guid, reason/source, and one change event. - [x] Route world pick, radar click, inventory selection, paperdoll selection, toolbar selected strip, target indicator, examine/use, and plugins through it. - [x] Delete panel-local competing selection state except transient hover/row focus. - [x] 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. **Implementation (2026-07-11):** Core `SelectionState` ports `ACCWeenieObject::SetSelectedObject @ 0x0058C2E0`, including deduplication, previous/previous-valid tracking, object-removal clear, and plugin callbacks. World pick, radar, combat targeting, inventory, paperdoll slots, the selected toolbar strip, target indicator, use/pickup, PreviousSelection, and the new `IPluginHost.Selection` contract share that owner. `InteractionState` separately owns `None`/`Use`/`Examine`/`UseItemOnTarget`; `ItemInteractionController` now projects its pending source from that state. AP-58 retired. **Acceptance (2026-07-11):** Release live-world gate passed. World clicks, radar clicks, inventory items, and equipped paperdoll items stayed synchronized across toolbar name, target brackets, radar highlight, and selection squares; `PreviousSelection` restored the prior target. Movement, doors, armor removal, and retained window controls remained unregressed. Full suite green: 4,560 passed, 5 skipped. ### 3.2 Port item-use/drop legality into Core - [x] Translate `ItemHolder::UseObject`, `DetermineUseResult`, and `AttemptPlaceIn3D` to pure/testable Core policy records. - [x] Include ready/busy, ownership, vendor/trade, PK/rare confirmation, stack, ground/drop, and target-type gates. - [x] Keep App `ItemInteractionController` as orchestration: ask policy, show modal/ toast, send the selected wire action, and manage pending state. **Implementation (2026-07-11):** Core `ItemInteractionPolicy` is the pure port of all three named-retail decision trees and returns ordered typed actions (plus the deliberately independent placement return value). Matching-executable x86 resolved the two damaged masks as `BF_REQUIRES_PACKSLOT (0x00800000)` and `BF_VENDOR (0x00000200)`. `CreateObject` now retains PWD flags and `CombatUse`; the App snapshot resolves component packs from portal.dat. The controller owns the 200 ms pre-legality throttle, ready/busy lifecycle (released by `UseDone`), target state, policy action dispatch, confirmation/auxiliary action seams, and ground-drop optimistic state. Vendor/trade/salvage/confirmation retained panels consume the typed action seam when those surfaces land; until then the policy refuses unsafe wire sends rather than bypassing their gates. **Acceptance (2026-07-11):** Non-debug Release live-world gate passed for ordinary item/container activation, equip/inventory synchronization, grounded world drop, and targeted healing. ACE command forwarding provisioned WCID 632 with `/ci 632`; `/harmself` then proved the main-pack and paperdoll self-target paths plus authoritative health-bar drain/recovery. Full suite green at the final gate: 4,583 passed, 5 skipped. ### 3.3 Add one item primary-click chokepoint - [x] Define a controller-level activation router: target-mode acquisition first, then window-specific fallback (select/open/use). - [x] 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. - [x] 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. - [x] Add end-to-end tests for health-kit target use on self, inventory item, worn item, and world entity, including invalid-target behavior. **Implementation (2026-07-11):** `ItemInteractionController.OfferPrimaryClick` is the sole typed target-mode offer. `ConsumedRejected` is intentionally terminal: invalid clicks clear retail target mode but never fall through to selection, container opening, or ordinary activation. Inventory contents/bags/main pack, paperdoll equipment/body, toolbar items, radar blips, and world picks all use the router; retained widgets remain controller-agnostic event sources. End-to-end retained tests pin self targeting and rejected inventory/worn targets without selection drift, while controller tests pin valid world/self and invalid target results. The consumed target is retained for retail's 500 ms double-click window, so the second Click/DoubleClick pair cannot reactivate or equip the rejected object after target mode clears. Issue #197 and AP-107 are retired. **Acceptance (2026-07-11):** User-confirmed the non-debug Release fallback matrix for inactive target mode across inventory, paperdoll, toolbar, radar, and world clicks, then the active targeted-heal flow through both the main pack and 3-D paperdoll. The production DAT resolves the authored doll hit mask `0x100001D6` as a `UiButton`; binding the viewport beneath it cannot receive a live click. The mask now owns self-target clicks and hides with the viewport in slot view. Full suite green at the final gate: 4,583 passed, 5 skipped. ### 3.4 Complete retail cursor precedence and catalog - [x] Port every reachable branch of `ClientUISystem::UpdateCursorState`, not only target enums `0x27/28/29`. - [x] Separate global world-interaction cursors from widget-local cursor media; preserve retail's event order: a global state change reasserts the default, while a later captured/hovered widget cursor transition may override it. - [x] Cover default/combat/use/examine/busy/target states with golden EnumIDMap tests, and cover drag accept/reject, resize/move/text as widget-local semantic states without inventing global enum meanings for unreachable `0x07..0x09`. - [x] Keep OS cursor fallback only as an explicit registered adaptation with a visible diagnostic. **Implementation (2026-07-11):** `RetailGlobalCursorKind` now models all 15 reachable enum branches from `ClientUISystem::UpdateCursorState @ 0x00564630`. Production DAT fixtures pin `0x01..0x06`, `0x0A..0x0F`, and `0x27..0x29` to their exact RenderSurface DIDs. `CursorFeedbackController` owns the pure global decision tree and reports only the active captured/hovered widget cursor; `RetailCursorManager` applies global-change then widget-change transitions in the same default/local layering used by `UIElementManager::CheckCursor @ 0x0045ABF0`. The old semantic-state alias search (`TargetValid` borrowing `Drag_rollover_accept`, etc.) is removed. AP-100 is retired; AP-72 remains the single explicit DAT-failure adaptation and now emits one visible diagnostic per failed global kind. **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. **Acceptance (2026-07-11):** User-confirmed the complete non-debug Release cursor matrix. A follow-up gate exposed that the four stance indicators were display-only; wiring their exact retail click branch passed immediately. Mouse click and quick key now both toggle peace/combat, update the visible indicator, and drive the corresponding DAT cursor state. Final suite: 4,618 passed, 5 skipped. --- ## 7. Wave 4 -- toolbar and selected-object completion ### 4.1 Wire retail quick-slot input - [x] Add a focused retained-UI input action controller which maps `UseQuickSlot_1..9`, `UseQuickSlot_14..18`, and `CreateShortcut` to `ToolbarController` methods. - [x] Port use-vs-select semantics from `gmToolbarUI::ListenToGlobalMessage`. - [x] Keep `GameWindow.OnInputAction` to one delegation seam. ### 4.2 Preserve the exact shortcut model; keep the toolbar object-only - [x] 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. - [x] Preserve all three fields through parse, session storage, mutation, and wire serialization even though `gmToolbarUI::UpdateFromPlayerDesc @ 0x004BF810` restores only entries with a nonzero `objectID_`. - [x] 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. **Wave 4.2 implementation (2026-07-11):** Core-owned `ShortcutEntry` now models the exact signed index, object id, and raw 32-bit spell word. PlayerDescription parsing, the 18-slot nullable store, retained-cell drag snapshots, displaced-item reindexing, runtime delegates, WorldSession, and AddShortcut serialization carry the same lossless value. The visible toolbar continues to bind only entries with nonzero object ids. AP-103 is retired; automated conformance is green and the live relog/persistence gate passed: ACE restored the mutated shortcut in the same slot after a clean reconnect. ### 4.3 Port every drag/drop branch - [x] 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. - [x] 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. - [x] 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. - [x] On full-stack merge, rekey the matching object shortcut and preserve the non-object fields of its raw `ShortCutData` entry. **Wave 4.3 partial implementation (2026-07-11):** drag-over feedback is now an explicit neutral/accept/reject result instead of a Boolean. Inventory and paperdoll enforce retail's `(DropItemFlags & 0xE) == 0` physical-item gate, so a toolbar alias released on either surface only leaves its remove-on-lift shortcut removal in place; it cannot move, unwield, or wield the underlying object. The raw-field preservation landed in Wave 4.2. Live gate passed with an equipped helmet: dropping its toolbar alias onto inventory removed only the shortcut and left the helmet worn. **Wave 4.3b implementation (2026-07-11):** a pure Core `ShortcutDropPlanner` now plans the complete post-lift mutation before state changes. Fresh inventory drops use the exact cyclic-right search; aliases use only the vacated toolbar source; duplicate-object removal, wraparound, a full bar, visually empty non-object records, exact wire order, and raw full-stack rekeying have conformance fixtures. `ToolbarController` applies the validated local transaction atomically, then emits its ordered Remove/Add events. AP-102 is retired; 4,646 tests pass. The live inventory-to-occupied displacement plus toolbar-reorder gate passed 2026-07-11. Wave 4.3c completes the umbrella: retained buttons can own item-drop callbacks; the toolbar inventory button uses retail's fresh-item gate and exact green-arrow overlay, while aliases stay neutral. Inventory now tries the pure named-retail stack-merge legality/amount planner before ordinary insertion, sends `0x0054`, broadcasts `FullMergingItem` immediately from the attempt owner, rekeys the toolbar with existing-destination removal, and selects the target. The notice is not a server confirmation despite its name. Release build is warning-free and 4,660 tests pass. The compatible-stack live gate passed 2026-07-11 and exposed the expected Wave 4.4/AP-101 selected-stack gap. Wave 4.4a now implements that gap: exact count-first name formatting, authored entry/slider media, one Core split-quantity owner, live stack-size refresh, and selected-source item-holder amounts. Controls/polish passed the live gate. Partial container/ground transfers now send retail `0x0055/0x0056`; their live action gate remains pending. The inventory-button gate remains pending. ### 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 and port `RecvNotice_UpdateItemMana`. - [x] Complete formatted stack count, stack entry and split slider, the applicable `RecvNotice_SplitStack` activation behavior, and `HandleSelectionChanged` stack precedence. Vendor-specific initial quantity remains naturally dormant until a vendor panel owns an active vendor id. - [ ] Replace the PWD-only health gate with the exact attackable/player/pet policy. - [ ] Add item-mana query/cancel state and health query cancellation. - [x] Add one shared split-quantity owner for the entry, slider, and selected-source item-holder merge, container-split, and ground-split payloads. **Wave 4.4a implementation (2026-07-11):** static cdb against the matching retail binary pinned stack-name `"%d %hs"`, numeric entry `"%d"`, and the slider's exact 1000-step truncation formula. `StackSplitQuantityState` is the single Core owner; `SelectedObjectController` binds authored elements `0x100001A3/A4`, and `InventoryController` consumes the value only when the dragged source is selected. The horizontal scrollbar remains the generic retained `UiScrollbar` in scalar mode, using the DAT track/thumb rather than panel-local drawing. The warning-free App Release build and 4,697-test suite are green. Gate polish preserves CreateObject `PluralName` through Core.Net into `ClientObject.GetAppropriateName`, honors the entry's authored right alignment, and lets the pointer-owned thumb remain at exact minimum/maximum endpoints instead of snapping back from the quantized amount. The controls/polish and partial-transfer live gates passed. `ItemHolder::GetObjectSplitSize @ 0x00586F00` now scopes that quantity to the selected object for all transfers. Partial inventory moves send `StackableSplitToContainer 0x0055`; partial drag-out drops send `StackableSplitTo3D 0x0056`; neither path moves the original object optimistically because the authoritative destination stack receives a server-assigned guid. The partial inventory and ground transfers were live-confirmed 2026-07-11. **Wave 4.1 implementation (2026-07-11):** bare `1..9` now use slots 0..8, Ctrl+`1..9` selects them, Alt+`5..9` uses slots 13..17, and `0` creates a shortcut to the selected owned/eligible object in the first empty slot. A focused `ToolbarInputController` maps semantic actions; `ToolbarController` owns the retail target-before-use/select ordering and auto-slot eligibility; and `GameWindow.OnInputAction` has one retained-runtime delegation seam. Keybinding schema v2 migrates only the exact old default Ctrl+number bindings, preserving custom user bindings. The four stance-specific combat indicator buttons `0x10000192..0x10000195` now all dispatch one injected combat toggle command. The key binding and retained toolbar therefore converge on `GameWindow.ToggleLiveCombatMode`, matching `gmToolbarUI::ListenToElementMessage @ 0x004BEE90`; the visible indicator continues to update from authoritative `CombatState`. Use, Examine, remaining panel buttons, raw shortcut preservation, drag/drop edge cases, and selected-object residuals remain in this wave. **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.