feat(ui): port favorite spell bar overflow

Import the retail arrow-only spell bar scrollbar from LayoutDesc, preserve authored end-button extents and HideDisabled behavior in the shared retained widget, and bind each favorite list to its sole horizontal pixel-scroll model. Match retail selection exposure for off-screen spells and pin the behavior with real-fixture conformance tests.

Document the six-slice world-interaction completion program as the active pre-M4 work order.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-23 06:35:53 +02:00
parent 20e2998adb
commit 02c29e67c8
12 changed files with 582 additions and 45 deletions

View file

@ -0,0 +1,106 @@
# World interaction completion — pre-M4 program
**Status:** Slice 1 code-complete 2026-07-23; connected visual gate pending.
**Milestone:** M4 prerequisite/preamble.
**Architecture:** retained gameplay UI over shared selection, object, and
interaction state. `GameWindow` remains a composition/callback shell.
## Outcome
Close the remaining retail interaction surfaces before the larger M4
quest/emote/character-creation bodies begin:
1. Favorite spell bars expose their DAT-authored overflow arrows and scroll
through every server-persisted favorite.
2. The status bar's hand and magnifying-glass controls invoke the same Use and
Assess commands as their keyboard paths.
3. Assessing a creature, player, NPC, or object opens its retail information in
the canonical shared main-panel host.
4. World picking can resolve visible equipped children, such as a character's
wielded weapon, while selection markers remain anchored to the picked child.
5. Vendor use opens the authored vendor surface, publishes its inventory, and
supports retail selection/browsing.
6. Vendor buy/sell transactions, quantities, pending-state ownership, and
authoritative inventory reconciliation complete the loop.
The program reuses the existing retained-window host, `SelectionState`,
`ClientObjectTable`, interaction transaction owner, and server-authoritative
inventory updates. It does not create parallel panel positions, item tables,
selection state, or optimistic inventory outcomes.
## Ordered slices
| Slice | Deliverable | Principal owner |
|---|---|---|
| 1 | Spell-bar overflow arrows | `SpellcastingUiController` + generic retained scrollbar/list |
| 2 | Status Use/Assess commands | focused status-bar controller binding to the existing action router |
| 3 | Assessment information panel | new retained panel controller mounted by the canonical main-panel host |
| 4 | Equipped-child world picking | pure world-query/picking policy plus presentation anchor |
| 5 | Vendor browse lifecycle | vendor session/controller plus authored panel |
| 6 | Vendor transactions | server-authoritative buy/sell command and reconciliation owner |
Each slice begins with named-retail research, produces pseudocode and
conformance tests, updates the divergence register if required, and lands as a
separate bisectable commit. A visual gate follows each UI-bearing slice.
Slice 1 now imports both 23-pixel arrow buttons and HideDisabled from the real
combat fixture, shares the list's single pixel-scroll model, advances one
32-pixel cell per press, and exposes programmatically selected spells. The
focused 57-test set, all 3,466 App tests / 3 skips, the Release solution build,
and all 7,839 solution tests / 5 skips pass. Only the connected visual gate
remains before Slice 2 starts.
## Slice 1 — spell-bar overflow arrows
### Retail oracle
The authored Magic combat layout contains, inside every favorite-tab group:
- horizontal scrollbar `0x100000B5`, 685×36 at the 800-pixel design width;
- decrement button `0x10000071`, 23×36;
- increment button `0x10000072`, 23×36;
- item list `0x100000B6`, inset by 23 pixels on both sides and 32 pixels high.
The list references the scrollbar through `UIElement_Scrollable` property
`0x71`. The scrollbar references increment/decrement buttons through
properties `0x77`/`0x78`, and property `0x79` enables HideDisabled. There is no
track or thumb in this specific control: the two authored arrows are the whole
visible scrollbar.
Named retail references and executable pseudocode are recorded in
[`../research/2026-07-23-retail-spellbar-overflow-pseudocode.md`](../research/2026-07-23-retail-spellbar-overflow-pseudocode.md).
### Implementation plan
1. Generalize `DatWidgetFactory.BuildScrollbar` so horizontal and vertical
scrollbars both import the referenced decrement/increment children,
including their authored dimensions and HideDisabled property.
2. Generalize `UiScrollbar` to use distinct authored decrement/increment
extents for rendering, hit-testing, track geometry, and dragging. Preserve
the existing 16-pixel default for layouts whose button children are absent.
3. Reproduce retail disabled presentation: a model without overflow rejects
pointer input; when HideDisabled is authored, the scrollbar also draws
nothing and does not claim hit tests.
4. In `SpellcastingUiController`, bind each group's scrollbar to its
`UiItemList.Scroll`, enable horizontal scrolling, and retain one independent
pixel offset per favorite tab.
5. Match `SpellCastSubMenu::SetSelected`: selecting a spell through keyboard,
shortcut, or code scrolls that item into view.
6. Pin the importer, arrow hit extents, 32-pixel step, no-overflow behavior,
controller binding, and selection exposure with focused App tests.
7. Run the App Release suite, solution Release build, and complete Release
suite. Then update the roadmap/memory and request the connected visual gate:
place more favorites than fit, scroll both directions, change tabs, and
verify arrows disappear on a non-overflowing tab.
### Invariants
- DAT supplies the controls and their artwork; no new spell-bar texture or
overlay is invented.
- The scrollbar and list share one `UiScrollable`; there is no second offset.
- One arrow press moves one 32-pixel favorite cell, matching
`UIElement_ListBox::InqScrollDelta`.
- Hidden disabled arrows cannot intercept combat-page dragging or clicks.
- Existing stack, combat-power, chat, inventory, spellbook, and external
container scrollbars retain their current behavior.
- No substantial feature body enters `GameWindow`.