feat(ui): complete retail cursor state machine
Port every reachable ClientUISystem cursor branch through the production DAT enum map, preserve global-default versus widget-local event ordering, and surface the registered OS fallback. Route all four toolbar stance indicators through the same combat toggle command as the key binding, with golden DAT and transition conformance coverage. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
05f6222865
commit
6fcc510d5d
16 changed files with 535 additions and 164 deletions
|
|
@ -204,8 +204,7 @@ AP-94..AP-110 for the confirmed retail-UI completion gaps.
|
|||
| ~~AP-97~~ | **RETIRED 2026-07-10 (Wave 1 retained-widget foundation)** — the prior generic Device-timer premise was a decomp misread. Named retail polls the hovered element's tooltip deadline, then broadcasts global UI time message `3`; it does not expose the assumed arbitrary timer queue. `UiRoot.Tick` now preserves that order and subtree removal clears input/time ownership before another pulse. | `src/AcDream.App/UI/UiRoot.cs`; `IUiGlobalTimeListener.cs` | — | — | `UIElementManager::UseTime` and hover/focus paths, pinned in `docs/research/2026-07-10-retained-widget-foundations-pseudocode.md` |
|
||||
| ~~AP-98~~ | **RETIRED 2026-07-10 (Wave 2.3)** — typed handles/manager own lifecycle; every production/Studio window uses `RetailWindowFrame`; schema-v2 `UiWindowLayout` persists outer bounds, visibility, toolbar collapse, and chat maximize by character/resolution with legacy-radar migration, nearest-resolution fallback, clamping, and a pre-login `default` write guard. Chat maximize uses DAT 100–360 limits and preserves its lower edge when growing upward. | `src/AcDream.App/UI/RetailWindowManager.cs`; `RetailWindowLayoutPersistence.cs`; `RetailWindowHandle.cs`; `Layout/RetailWindowFrame.cs`; `ChatWindowController.cs`; `SettingsStore.cs` | — | — | `gmMainChatUI::HandleMaximizeButton @ 0x004CCE50`; retail `saveui/loadui` behavior |
|
||||
| ~~AP-99~~ | **RETIRED 2026-07-11 (Wave 3.2)** — Core `ItemInteractionPolicy` ports the complete ordered `DetermineUseResult`/`UseObject`/`AttemptPlaceIn3D` matrix; PWD flags and `CombatUse` survive CreateObject; component-pack membership comes from portal.dat; App owns throttle, UseDone-balanced busy state, target mode, wire/optimistic dispatch, and typed confirmation/auxiliary seams. The two corrupt decompiler operands were pinned from matching x86 as `BF_REQUIRES_PACKSLOT` and `BF_VENDOR`. | `src/AcDream.Core/Items/ItemInteractionPolicy.cs`; `src/AcDream.App/UI/ItemInteractionController.cs` | — | — | `ItemHolder::DetermineUseResult @ 0x00588460`; `UseObject @ 0x00588A80`; `AttemptPlaceIn3D @ 0x00588600` |
|
||||
| AP-100 | Cursor semantics cover only a reachable subset of retail states, and widget cursor media can outrank authoritative global target mode | `src/AcDream.App/UI/CursorFeedbackController.cs`; `src/AcDream.App/Rendering/RetailCursorManager.cs` | Target-use art works for current flows; Wave 3 ports the full table and precedence | Hovering UI can replace the use/examine/busy/combat cursor; unsupported branches fall back to OS art | `ClientUISystem::UpdateCursorState @ 0x00564630` |
|
||||
| AP-101 | Toolbar quick-slot actions and most panel/Use/Examine/combat controls are not fully dispatched; selected-object mana and stack entry/slider are absent | `src/AcDream.App/UI/Layout/ToolbarController.cs`; `GameWindow.OnInputAction` | Item click/use, stance indicators, health/name, and some toggles are present | Bound retail keys/buttons do nothing and item/stack selections show incomplete state | `gmToolbarUI @ 0x004BD0C0..0x004BF380`; LayoutDesc `0x21000016` |
|
||||
| AP-101 | Toolbar quick-slot actions and most panel/Use/Examine controls are not fully dispatched; selected-object mana and stack entry/slider are absent | `src/AcDream.App/UI/Layout/ToolbarController.cs`; `GameWindow.OnInputAction` | Item click/use, health/name, some panel toggles, and all four combat indicator buttons work; combat clicks and the bound key share `ToggleLiveCombatMode` | Remaining bound retail keys/buttons do nothing and item/stack selections show incomplete state | `gmToolbarUI @ 0x004BD0C0..0x004BF380`; LayoutDesc `0x21000016` |
|
||||
| AP-102 | Inventory-to-occupied toolbar drop treats the inventory source index as though it were a vacated toolbar slot for the displaced shortcut | `src/AcDream.App/UI/Layout/ToolbarController.cs` drop planner | Normal empty-slot and toolbar-reorder paths work; Wave 4 makes the transaction source-aware | The displaced shortcut is lost or moved to an unrelated slot | `gmToolbarUI::HandleDropRelease @ 0x004BE7C0` |
|
||||
| AP-103 | Shortcut session storage keeps only object guid and discards raw non-object fields; parser/builder names the final uint as `u16 spellId/u16 layer` instead of preserving retail `spellID_` as raw data | `src/AcDream.Core/Items/ShortcutStore.cs`; `src/AcDream.Core.Net/Messages/PlayerDescriptionParser.cs`; `InventoryActions.cs` | `gmToolbarUI` itself displays only nonzero object ids, so current item shortcuts work | Round-trip mutation corrupts unknown/raw fields and encourages invented spell support in the item toolbar | `ShortCutData` in `acclient.h:36484`; `gmToolbarUI::UpdateFromPlayerDesc @ 0x004BF810` |
|
||||
| AP-104 | Vitals detail element `0x100004A9` and root `HideDetail`/`ShowDetail` transitions are not wired | `src/AcDream.App/UI/Layout/VitalsController.cs` | Compact vitals values/bars are correct | Detail click does nothing and expanded retail state is unreachable | `gmVitalsUI::ListenToElementMessage @ 0x004BFC00`; `PostInit @ 0x004BFCE0` |
|
||||
|
|
|
|||
|
|
@ -491,7 +491,7 @@ behavior. Estimated 17–26 days focused work, 3–5 weeks calendar.
|
|||
- **☐ D.5.3 — Toolbar selected-object display (issue #141) + exact shortcut preservation.** Wire unified selection to the hidden mana meter (`0x100001A2`), stack entry (`0x100001A3`), stack slider (`0x100001A4`), and complete health/name behavior. Preserve all raw `ShortCutData` fields while keeping `gmToolbarUI` object-only, matching `UpdateFromPlayerDesc @ 0x004BF810`. The eight `favorite_spells_` lists belong to the separate spellbook/spell-bar phase. (Click-to-use + peace/war + health/name already landed.)
|
||||
- **☐ D.5.5+ — Core panels.** Inventory (`gmInventoryUI`/`gmBackpackUI`), equipment/paperdoll (`gmPaperDollUI`/`gm3DItemsUI` + the `UiViewport` 3D doll), vendor, trade, spellbook. Research drop done (`docs/research/2026-06-16-*`). Depends on **D.5.4** (data model) + the item-slot/list/icon spine (D.5.1/D.5.2) + the **window manager** (Plan 2: open/close/z-order/persist + faithful grip/dragbar drag-resize) + the drag-drop spine wired (`UiRoot` has the chain; the per-cell accept/drop hooks are still stubs in `UiField`). Also deferred from D.5.1: drag/reorder + the `AddShortcut`/`RemoveShortcut` mutate wire.
|
||||
- **D.6 — HUD.** **Radar/compass IMPLEMENTED 2026-07-10; live-world visual gate pending.** It is the retained retail `gmRadarUI` (`LayoutDesc 0x21000074`): a 120×140 circular radar (`0x06004CC1`) with N/E/S/W token sprites orbiting the face, a fixed bright-green player marker, and the coordinate footer (`0x06004CC0`). Projection is `radarPixelRadius / radarRange` with 75 m outdoor / 25 m indoor ranges — there is no 1.18 factor or scrolling compass strip. It lives in `AcDream.App/UI` beside the other imported retail panels. Remaining D.6: target name plate, damage floaters, and other world-space HUD elements through the raw `TextRenderer` path. See slice 06 §A.2, `docs/research/2026-07-10-retail-radar-pseudocode.md`, and the named `gmRadarUI` decomp.
|
||||
- **D.7 — Cursor manager.** OS + dat-sourced custom cursors (`FUN_0043c1c0` GDI HCURSOR builder pattern from slice 03). **(D.2b dependency.)**
|
||||
- **✓ SHIPPED — D.7 — Cursor manager (user-confirmed 2026-07-11).** All reachable `ClientUISystem::UpdateCursorState @ 0x00564630` branches resolve through production DAT EnumIDMap table 6, with event-ordered global-default versus captured/hovered `MediaDescCursor` layering from `UIElementManager::CheckCursor @ 0x0045ABF0`. OS fallback remains only AP-72 and emits a visible diagnostic. The live gate covered world/object, combat, targeted-use, text, move/resize, and drag contexts; combat-indicator mouse clicks and the quick key share the same toggle command.
|
||||
- ~~**D.8 — Sound.**~~ **Superseded — shipped as Phase E.2** (`SoundTable`/`Sound` dat decode, OpenAL 16-voice engine, per-entity 3D positional audio via `AudioHookSink`). Entry kept here for history; see the shipped table.
|
||||
|
||||
**Reference docs:** `docs/research/retail-ui/00-master-synthesis.md` + slices 01-06. Every AC-specific behavior has a decompiled FUN_ / DAT_ citation.
|
||||
|
|
|
|||
|
|
@ -534,7 +534,8 @@ Open the inventory panel — retail-skinned with the right font, icons, and
|
|||
- **C.4** — Double-sided translucent polys.
|
||||
- **D.2b** — Custom retail-look UI backend. **In progress:** vitals + chat + toolbar + inventory (window manager, B-Grid, B-Controller, B-Wire, drag-drop, container-switching) + paperdoll (equip slots, 3-D doll UiViewport, Slots toggle) + **Character window** (Attributes tab, visually confirmed 2026-06-26) + **UI Studio** (standalone panel previewer + 26-window retail dump + interactive click-routing + headless screenshot) + **importer dat-fidelity** (Fix A/B/C: justification, FontColor, per-element FontDid resolver [studio-only; #157 tracks live-game path]) are shipped. Current campaign: shared retained-widget/window architecture, then selected-object mana/stack completion. **Retail correction:** the main toolbar is object-only; the eight favorite-spell lists drive separate spell bars. Execution plan: `docs/superpowers/plans/2026-07-10-retail-ui-fidelity-completion.md`.
|
||||
- **D.3–D.7** — AcFont + dat sprites + core panels reskinned + HUD orbs +
|
||||
cursor manager. **D.6 radar/compass shipped 2026-07-10** through the retained
|
||||
cursor manager. **D.6 radar/compass shipped 2026-07-10; D.7 complete retail
|
||||
cursor catalog/precedence shipped and user-confirmed 2026-07-11.** The radar uses the retained
|
||||
UI architecture using the retail DAT layout and recovered runtime behavior;
|
||||
live-game visual confirmation remains the user gate.
|
||||
- **L.1f** — NPC/monster + item-use animation coverage.
|
||||
|
|
|
|||
|
|
@ -907,16 +907,29 @@ UpdateCursorState(): // 0x00564630
|
|||
UIElementManager.SetCursor(did, hotspot, setAsDefault=true)
|
||||
```
|
||||
|
||||
Production DAT golden values already pinned in tests:
|
||||
Production DAT golden values pinned by `RetailCursorCatalogTests`:
|
||||
|
||||
| Enum | RenderSurface DID |
|
||||
|---:|---:|
|
||||
| `0x01` | `0x06004D68` |
|
||||
| `0x02` | `0x06004D69` |
|
||||
| `0x03` | `0x06004D6A` |
|
||||
| `0x04` | `0x06004D6B` |
|
||||
| `0x05` | `0x06004D6C` |
|
||||
| `0x06` | `0x06004D6D` |
|
||||
| `0x0A` | `0x06004D71` |
|
||||
| `0x0B` | `0x06004D71` |
|
||||
| `0x0C` | `0x06004D72` |
|
||||
| `0x0D` | `0x06004D72` |
|
||||
| `0x0E` | `0x06004D74` |
|
||||
| `0x0F` | `0x06004D75` |
|
||||
| `0x27` | `0x06004D73` |
|
||||
| `0x28` | `0x06005E6B` |
|
||||
| `0x29` | `0x06005E6A` |
|
||||
|
||||
The DIDs for `0x01..0x0F` should be committed as DAT conformance fixtures before
|
||||
implementation. The enum behavior above is exact even without those media IDs.
|
||||
DAT enums `0x07..0x09` resolve to `0x06004D6E..0x06004D70`, but no reachable
|
||||
`UpdateCursorState` branch selects them. They remain intentionally unnamed and
|
||||
outside the global cursor catalog until a retail call site supplies semantics.
|
||||
|
||||
### 10.1 Widget cursor precedence is separate and temporal
|
||||
|
||||
|
|
|
|||
|
|
@ -403,18 +403,39 @@ slot view. Full suite green at the final gate: 4,583 passed, 5 skipped.
|
|||
|
||||
### 3.4 Complete retail cursor precedence and catalog
|
||||
|
||||
- [ ] Port every reachable branch of `ClientUISystem::UpdateCursorState`, not only
|
||||
- [x] 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
|
||||
- [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
|
||||
|
|
@ -465,6 +486,14 @@ no click path bypasses the router; full suite and user visual gate.
|
|||
- [ ] Add item-mana query/cancel state, health query cancellation, and one shared
|
||||
split-quantity owner for the entry, slider, and item drag payload.
|
||||
|
||||
**Partial implementation (2026-07-11):** 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, quick slots, and selected
|
||||
object residuals remain in this wave.
|
||||
|
||||
**Oracles:** `gmToolbarUI @ 0x004BD0C0..0x004BF380`,
|
||||
`UIElement_UIItem::SetShortcutNum`, shortcut wire builders.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue