docs(ui): pin retained widget retail oracles

This commit is contained in:
Erik 2026-07-10 17:55:16 +02:00
parent 5931a820ea
commit 44f9ec13d9
10 changed files with 2753 additions and 73 deletions

View file

@ -43,9 +43,9 @@ when the named decomp lacks a body.
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.
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.
@ -106,32 +106,32 @@ This wave changes no runtime behavior.
### 0.1 Consolidate missing retail pseudocode
- [ ] Write focused pseudocode notes for:
- [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;
- Device timer delivery and focus notifications;
- 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.
- [ ] Correct the roadmap premise around shortcuts: `ShortCutData` is
- [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.
- [ ] Pin every method address and DAT property id before implementation.
- [x] 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
- [x] 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,
- [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.
- [ ] Add inheritance cases proving derived explicit `false` and `0` override base
- [x] 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.
- [x] Add fixture assertions for every required controller role.
### 0.3 Repair the audit trail
@ -148,34 +148,35 @@ This wave changes no runtime behavior.
## 4. Wave 1 -- exact retained-widget foundation
### 1.1 Give Type-12 text an explicit interaction mode
### 1.1 Give Type-12 text independent retail capabilities
- [ ] Add `UiTextInteractionMode { Display, Selectable, Editable }`; default to
`Display` for factory-imported DAT text.
- [ ] Import retail Editable `0x16`, OneLine `0x20`, and Selectable `0x27` properties
- [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.
- [ ] 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
- [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`.
- [ ] Bind the imported vitals labels `0x100000EB/ED/EF` directly and delete the
- [x] 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.
- [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
- [ ] Extract a GL-free `UiButtonStateMachine` over enabled/hovered/pressed/
- [x] 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
- [x] 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
- [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.
- [ ] Make cursor media follow the active button state.
- [ ] Cover normal, rollover, pressed, released-outside, disabled, toggle-on/off,
- [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
@ -183,33 +184,35 @@ This wave changes no runtime behavior.
### 1.3 Replace lossy anchors with the retail layout policy
- [ ] Introduce `UiLayoutPolicy` containing the five raw edge modes `0..4`, original child
- [x] 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,
- [x] 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;
- [x] 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,
- [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 Device-level timing/focus behavior
### 1.4 Finish UI-manager 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
- [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.
- [ ] Pin double-click, tooltip, capture-loss, modal, and timer ordering tests.
- [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
- [ ] Bind imported named states such as `ShowDetail`, `HideDetail`, enabled/
- [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.
- [ ] Prove state changes preserve the imported rect, anchors, dimensions, and media
- [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
@ -415,14 +418,19 @@ mana selections; full suite and user visual gate.
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
### 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.
- [ ] 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.
- [ ] 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
@ -447,10 +455,12 @@ 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.
- [ ] 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.
@ -516,12 +526,14 @@ user visual gate.
### 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.
- [ ] 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`.
@ -584,7 +596,7 @@ scenario, UI Studio fixture, live user visual gate, divergence cleanup.
| 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 |
| 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 |
@ -599,14 +611,14 @@ scenario, UI Studio fixture, live user visual gate, divergence cleanup.
| 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 filter windows, opacity, channel availability, squelch/actions incomplete; numbered-tab premise unproven | 5 |
| Chat scrollbar arrows reversed | 2 / 5 |
| Inventory close skips `NoLongerViewingContents` | 2 / 6 |
| 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 raise authoritative/prediction semantics are unreconciled | 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 |