docs: handoff — finish the action bar (selected-object meter + shortcut drag) + start the inventory/paperdoll window
Next D.2b-UI work after D.5.4. 3 streams (spell bar deferred): selected-object meter, shortcut drag/add/reorder/remove, inventory+paperdoll window. Current-code anchors + dependency graph + build order + brainstorm questions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6eb0fbde46
commit
d400bc6105
1 changed files with 239 additions and 0 deletions
239
docs/research/2026-06-18-d53-bar-finish-and-inventory-handoff.md
Normal file
239
docs/research/2026-06-18-d53-bar-finish-and-inventory-handoff.md
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
# Handoff — finish the action bar + start the inventory/paperdoll window
|
||||
|
||||
**Date:** 2026-06-18
|
||||
**From:** the D.5.4 object/item-model session (SHIPPED `b506f53..6eb0fbde`, 2672 tests green, visually
|
||||
confirmed on Barris/Coldeve). The data model is now solid — every server object lives in
|
||||
`ClientObjectTable`, resolvable by guid. This handoff frames the NEXT work on the D.2b retail-UI track.
|
||||
**Branch:** `claude/hopeful-maxwell-214a12` (kept, unmerged — carries D.5.2 + D.5.4).
|
||||
**Line numbers below are as of HEAD `6eb0fbde` and WILL drift — grep the symbol, don't trust the line.**
|
||||
|
||||
---
|
||||
|
||||
## 0. Scope (settled with the user)
|
||||
|
||||
Three work streams. **The spell bar is explicitly DEFERRED** (it is a separate feature — a dedicated
|
||||
spell-casting bar — NOT the action-bar spell *shortcuts*; do not build spell-glyph rendering/casting here).
|
||||
|
||||
| Stream | What | Roadmap |
|
||||
|---|---|---|
|
||||
| **A. Selected-object meter** | The action bar's bottom strip: the player's currently-**selected** world object's Health/Mana meter + name (+ stack slider, deferred). Currently hidden. | D.5.3 (issue #140) |
|
||||
| **B. Shortcut drag / add / reorder / remove** | Drag an item from the inventory window onto a hotbar slot; reorder slots; remove. The `AddShortcut`/`RemoveShortcut` wire. Item shortcuts already RENDER + click-to-use (D.5.1/D.5.4); this is the interactive management. | D.5.3 / D.5.5 |
|
||||
| **C. Paperdoll + inventory window** | One combined window (`gmInventoryUI` nests paperdoll + backpack + 3D-items). It is the **drag SOURCE** that Stream B needs. | D.5.5 |
|
||||
|
||||
**Out of scope:** the spell bar; the stack-split UI (entry box `0x100001A3` + slider `0x100001A4`);
|
||||
the faithful Dragbar/Resizebar window resize (the IA-12 whole-window-drag approximation stays for now).
|
||||
|
||||
**Dependency reality:** Stream B's drag-*from-inventory* needs Stream C (the inventory window) as the
|
||||
drag source, and both B and C need the **drag-drop spine completed** (shared infra, §B.1). So this is
|
||||
really 2-3 sub-phases — see the build order in §4. Each gets its own brainstorm → spec → plan.
|
||||
|
||||
---
|
||||
|
||||
## 1. Read first
|
||||
|
||||
- This doc.
|
||||
- `docs/research/2026-06-16-ui-panels-synthesis.md` — **the build plan** for the core panels (build order, widget list, cross-panel wire table). Stream C follows it.
|
||||
- `docs/research/2026-06-16-ui-item-slot-icon-dragdrop-spine-deep-dive.md` — the drag-drop spine design (§5 pseudocode is the spec for Stream B's widget hooks).
|
||||
- `docs/research/2026-06-16-inventory-deep-dive.md` + `docs/research/2026-06-16-equipment-paperdoll-deep-dive.md` — the two panels' LayoutDesc maps + wire catalog.
|
||||
- `docs/research/2026-06-16-action-bar-toolbar-deep-dive.md` — `gmToolbarUI` shortcut model + the `HandleDropRelease` drag flags.
|
||||
- `claude-memory/project_object_item_model.md` (D.5.4) + `claude-memory/project_d2b_retail_ui.md` (D.2b/D.5.1/D.5.2 toolkit).
|
||||
|
||||
**Mandatory workflow** (CLAUDE.md): grep `docs/research/named-retail/acclient_2013_pseudo_c.txt` by
|
||||
`class::method` → cross-ref ACE/holtburger → pseudocode → port. Conformance tests throughout.
|
||||
The named-decomp anchors for each stream are inline below.
|
||||
|
||||
---
|
||||
|
||||
## 2. Stream A — selected-object meter (the smallest, mostly self-contained)
|
||||
|
||||
**Goal:** when the player selects a world object (LMB pick or Tab/Q combat-target), the action bar's
|
||||
bottom strip shows that object's **Health meter** + **name**; **Mana meter** for owned items.
|
||||
|
||||
**Retail lifecycle** (the oracle): `gmToolbarUI::HandleSelectionChanged`
|
||||
(`acclient_2013_pseudo_c.txt:198635`) — on selection it `SetVisible(1)`s the right meter and fires
|
||||
`CM_Combat::Event_QueryHealth(guid)` (creatures/players) or `CM_Item::Event_QueryItemMana(guid)`
|
||||
(owned items). The server replies `UpdateHealth (0x01C0)` / `UpdateItemMana`, and
|
||||
`RecvNotice_UpdateObjectHealth` (`:196213`) / `RecvNotice_UpdateItemMana` (`:196188`) call
|
||||
`SetAttribute_Float(meter, 0x69, pct)` — **property `0x69` is the fill ratio**. `UIElement_Meter`'s
|
||||
fill element is child id `2` (`UIElement_Meter::Initialize :123328`; `OnSetAttribute :123712`).
|
||||
Mana is gated on `IsOwnedByPlayer` (`:198763`).
|
||||
|
||||
**LayoutDesc elements** (toolbar `0x21000016`, `.layout-dumps/toolbar-0x21000016.txt:621-811`):
|
||||
container `0x1000019E`; name text `0x1000019F` (Type 0) + state overlay `0x100001A0`
|
||||
(states `ObjectSelected 0x06001937` / `StackedItemSelected 0x06004CF4`); **health meter `0x100001A1`**
|
||||
(Type 7); **mana meter `0x100001A2`** (Type 7); stack entry `0x100001A3`; stack slider `0x100001A4`
|
||||
(Type 11). All currently in `ToolbarController` `HiddenIds` (~`ToolbarController.cs:41`),
|
||||
`SetVisible(false)` at Bind (~`:100`).
|
||||
|
||||
**Work items:**
|
||||
1. **Fix the meter render bug** (the launch-log `meter 0x100001A1/A2: 1 Type-3 slice container
|
||||
(expected 2)` warning). `DatWidgetFactory.BuildMeter` (~`DatWidgetFactory.cs:135-154`) assumes 2
|
||||
Type-3 slice containers (back + fill). The toolbar meters have **1** container (the fill, child
|
||||
id `0x00000002`); the **back-track sprite is on the meter element's own DirectState**
|
||||
(e.g. health `0x0600193E`). Fix `BuildMeter` to detect the 1-container case and read the back
|
||||
track from the element's `StateMedia[""]`, fill from the child. (Vitals meters `0x2100006C` have 2
|
||||
containers and work — use them as the contrast.)
|
||||
2. **`SelectedObjectController`** (analogue of `VitalsController` — see the working bind pattern at
|
||||
`VitalsController.cs:61-97`): on selection-change, `SetVisible(true)` on `0x100001A1`(/`A2` for owned
|
||||
items), bind `UiMeter.Fill` to `() => combat.GetHealthPercent(selGuid)`, bind the name text
|
||||
`0x1000019F` to `ClientObjectTable.Get(selGuid)?.Name`, set the `0x100001A0` overlay state; on
|
||||
deselect `SetVisible(false)`.
|
||||
3. **Selection notification:** there is no `SelectionChanged` event today — `_selectedGuid` is a raw
|
||||
`uint?` on `GameWindow` (~`GameWindow.cs:844`), written by `PickAndStoreSelection` (LMB) and
|
||||
`SelectClosestCombatTarget` (Tab/Q), cleared on despawn. Either add an event or poll-and-diff a
|
||||
`Func<uint?>` (the `TargetIndicatorPanel` pattern). **Brainstorm: event vs poll.**
|
||||
4. **Health is ready:** `CombatState.GetHealthPercent(guid)` + `CombatState.HealthChanged`
|
||||
(`CombatState.cs:92,45`), wired from `UpdateHealth 0x01C0` (`GameEventWiring.cs:155`).
|
||||
To force a fresh value on selection, retail sends `QueryHealth` — `SocialActions.BuildQueryHealth`
|
||||
(0x01BF) already exists (`SocialActions.cs:49`). **Brainstorm: send QueryHealth on select, or rely
|
||||
on server broadcasts for now?**
|
||||
5. **Mana is NOT ready** (the harder half): no remote-target mana anywhere (`CombatState` is
|
||||
health-only; `LocalPlayerState.ManaPercent` is self-only). `QueryItemManaResponse (0x0264)` is
|
||||
*parsed* (`GameEvents.cs:416`) but **unregistered** in `GameEventWiring`, and there is **no
|
||||
outbound `QueryItemMana` builder** (its C→S opcode is unknown — `0x0264` is the reply).
|
||||
**Brainstorm/decide: defer mana entirely for D.5.3 (health-only, matching that mana is owned-item-only
|
||||
anyway), or do the full mana path?** Recommend deferring mana → ship health-meter + name first.
|
||||
6. **Stack slider/entry (`0x100001A3/A4`):** deferred (stack-split UI).
|
||||
|
||||
**Why A is mostly standalone:** it doesn't need the drag-drop spine, the window manager, or the
|
||||
inventory window. It's the quickest win and finishes the bar's *display*. Good first chunk.
|
||||
|
||||
---
|
||||
|
||||
## 3. Stream B — shortcut drag / add / reorder / remove
|
||||
|
||||
**Item shortcuts already render + click-to-use** (D.5.1 + D.5.4). This stream is the interactive
|
||||
management: drag an item from inventory onto a slot, reorder, remove.
|
||||
|
||||
### B.1 — the drag-drop spine (SHARED infra, also needed by Stream C)
|
||||
`UiRoot` has the **complete** retail drag state machine, LIVE-wired to Silk.NET input:
|
||||
`BeginDrag`/`UpdateDragHover`/`FinishDrag` firing `DragBegin 0x15`/`DragEnter 0x21`/`DragOver 0x1C`/
|
||||
`DropReleased 0x3E` (`UiRoot.cs:450-496`), promoted on >3px move, bridged via `UiHost.WireMouse`
|
||||
(`UiHost.cs:78-88`, called at `GameWindow.cs:1769`). **But:**
|
||||
- `BeginDrag` always passes `payload: null` (`UiRoot.cs:188`); `DragPayload` has a private setter
|
||||
(`UiRoot.cs:73`) → needs a `SetDragPayload(object)` escape hatch (or a source-payload callback).
|
||||
- `UiItemSlot.OnEvent` handles only `MouseDown→Clicked` (`UiItemSlot.cs:101-105`) — **no
|
||||
DragBegin/DragEnter/DragOver/DropReleased cases**. (`UiItemSlot.ItemId` `:19` is the payload source.)
|
||||
- `UiField`'s `CatchDroppedItem`/`MouseOverTop` are **doc-comment only** (`UiField.cs:10-11`) — the
|
||||
bodies belong on `UiItemSlot`, per the spine doc §5.6.
|
||||
- No `IItemListDragHandler` interface exists; no drag ghost renderer; no `InqDropIconInfo` helper.
|
||||
|
||||
**Build (spine doc §5.7 is the spec):** (1) payload injection in `UiItemSlot` on DragBegin
|
||||
(`{objId=ItemId, srcContainer, srcSlot}`); (2) a cursor-following **drag ghost** (the icon is already
|
||||
in `UiItemSlot.IconTexture`); (3) drop-target hooks on `UiItemSlot` (DragEnter/Over→accept/reject
|
||||
overlay `0x10000041`/`0x10000040`/`0x1000003f`; DropReleased→`HandleDropRelease`); (4)
|
||||
`IItemListDragHandler { bool OnDragOver(...); void HandleDropRelease(...) }` that panels implement +
|
||||
register on their `UiItemList`.
|
||||
|
||||
### B.2 — the shortcut model + wire
|
||||
- **Mutable store missing.** Shortcuts are a **read-only** `IReadOnlyList<ShortcutEntry>`
|
||||
(`GameWindow.Shortcuts ~:600`, set once from PlayerDescription via `onShortcuts` at
|
||||
`GameEventWiring.cs:415`). Port retail `ShortCutManager::shortCuts_[18]` (`acclient.h:36492`) as a
|
||||
small mutable `ShortcutStore` (18 slots; `Load`/`AddOrReplace(slot,guid)→displaced`/`Remove(slot)`).
|
||||
- **Wire builders exist with a naming bug.** `InventoryActions.BuildAddShortcut` (0x019C,
|
||||
`InventoryActions.cs:99`) — param `objectType` should be `objectGuid`; the trailing field is packed
|
||||
`spellId(u16)|layer(u16)` (0 for items). Byte layout is already correct for item-only callers; **fix
|
||||
the names before wiring.** Field order confirmed by ACE `Shortcut.cs:33`, holtburger
|
||||
`shortcuts.rs:37`, retail `ShortCutData` `acclient.h:36484`. `BuildRemoveShortcut` (0x019D) is fine.
|
||||
- **No `SendAddShortcut`/`SendRemoveShortcut` on `WorldSession`** — wrap the builders (pattern =
|
||||
`SendChangeCombatMode`: `NextGameActionSequence()` + `Build*()` + `SendGameAction()`, `:1064`).
|
||||
- **Drop flow** (retail `gmToolbarUI::HandleDropRelease :197971`): `InqDropIconInfo` flags
|
||||
`&0xE==0` = fresh-from-inventory (place), `&4` = reorder. On drop: remove target if occupied (0x019D)
|
||||
→ update store → add (0x019C) → `Populate()`. Reorder also puts the displaced item back in the source
|
||||
slot. `ToolbarController` implements `IItemListDragHandler` + gets `Action`s for the two sends.
|
||||
|
||||
**Reorder-within-bar needs no inventory; drag-from-inventory needs Stream C.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Stream C — paperdoll + inventory window (one window)
|
||||
|
||||
**The design is already written — follow `2026-06-16-ui-panels-synthesis.md` §4.** This section is the
|
||||
**current-code readiness** + what's missing. Don't re-derive the design.
|
||||
|
||||
**READY (post-D.5.1/D.5.4):** `UiItemSlot` + `UiItemList` + `IconComposer` (`src/AcDream.App/UI/`),
|
||||
`DatWidgetFactory` registers `0x10000031→UiItemList` (`:70`); the data path is
|
||||
`ClientObjectTable.GetContents(containerGuid)` → ordered guids → `Get(guid)` → full icon fields
|
||||
(`ClientObjectTable.cs:273,188`). The toolkit + data model are in place.
|
||||
|
||||
**MISSING (the build, in synthesis order):**
|
||||
1. **Window manager** (deferred Plan-2): open/close/z-order/persist. Today every window is **always-on
|
||||
at a hardcoded position** (`ACDREAM_RETAIL_UI=1`, `GameWindow.cs:1906`); `UiHost` has no
|
||||
open/close API (`UiHost.cs:37`). Needs at minimum an **`I`-key toggle** to open/close the inventory
|
||||
window. (Faithful Dragbar/Resizebar resize stays deferred — IA-12 whole-window-drag is fine.)
|
||||
2. **`UiItemList` N-cell grid mode** — currently single-cell (`UiItemList.cs:12`, only sizes
|
||||
`_cells[0]`); `Flush`/`AddItem` skeleton exists but no column-count/pitch/wrap (LIKELY 6 cols × 36px;
|
||||
confirm from `UIElement_ItemList::ItemList_AddItem`).
|
||||
3. **Sub-window mount in `LayoutImporter`** — `gmInventoryUI` (`0x21000023`) nests paperdoll
|
||||
(`0x21000024`), backpack (`0x21000022`), 3D-items (`0x21000021`) as child elements whose class id
|
||||
has its own `BaseLayoutId`. The importer only does TEMPLATE inheritance today
|
||||
(`LayoutImporter.cs:196-228`) — it has never instantiated a nested `gm*UI` window. New capability.
|
||||
4. **Wire gaps** (inventory deep-dive §4.3): builders `DropItem 0x001B`, `GetAndWieldItem 0x001A`,
|
||||
`NoLongerViewingContents 0x0195` (all absent); parsers `ViewContents 0x0196`, `SetStackSize 0x0197`,
|
||||
`InventoryRemoveObject` (all absent); fix `ParsePutObjInContainer` (drops the 4th `containerType`,
|
||||
`GameEvents.cs:352`) + `ParseInventoryServerSaveFailed` (drops `weenieError`, `:377`); register
|
||||
`ViewContents`/`0x019A`/`0x0052`/`0x00A0` in `GameEventWiring`.
|
||||
5. **`UiViewport` (Type 0xD)** for the paperdoll 3D doll — **the single biggest new piece.** No widget,
|
||||
no factory registration, no renderer. Needs an `IUiViewportRenderer` **Core→App seam** (Rule 2) for a
|
||||
scissored single-entity GL pass. The doll is the local player's ObjDesc-dressed entity in a fixed
|
||||
viewport. **Heavy — brainstorm separately (see §5 open questions).**
|
||||
6. **`InventoryController` + `PaperDollController`** (the `gm*UI::PostInit` find-by-id pattern):
|
||||
backpack burden Meter (`SetLoadLevel`→fill `0x69`), own-pack list + side-pack list, the
|
||||
element-id→`EquipMask` map for paperdoll slots, `ObjDescEvent 0xF625` → re-dress.
|
||||
|
||||
---
|
||||
|
||||
## 5. Recommended build order + the dependency graph
|
||||
|
||||
This spans **2-3 sub-phases**. Suggested sequence (each its own brainstorm → spec → plan):
|
||||
|
||||
1. **D.5.3a — selected-object meter** (Stream A). Standalone, quickest, finishes the bar's display.
|
||||
No spine/window-manager dependency. Recommend health-meter + name first; defer mana.
|
||||
2. **Drag-drop spine completion** (§B.1) — shared infra for B and C. Build once.
|
||||
3. **Window manager (open/close)** (§C.1) — enough to toggle the inventory window open.
|
||||
4. **D.5.5 — inventory window** (§C, grid + sub-window mount + wire gaps + `InventoryController`).
|
||||
This gives the drag **source**.
|
||||
5. **D.5.3b — shortcut drag-to-add/reorder/remove** (Stream B) — now that the spine + inventory source
|
||||
+ `ShortcutStore` + the `BuildAddShortcut` fix are in place. (Reorder-within-bar could land earlier
|
||||
with just steps 2 + the store.)
|
||||
6. **Paperdoll** (`UiViewport` + `PaperDollController`, §C.5/6) — the 3D doll, the heaviest piece.
|
||||
|
||||
**Critical-path note:** the drag-drop spine (step 2) is the lynchpin — both shortcut drag and inventory
|
||||
drag depend on it. Do it early and well (it has its own spine deep-dive as the spec).
|
||||
|
||||
---
|
||||
|
||||
## 6. Open questions for the brainstorm(s)
|
||||
|
||||
- **A:** SelectionChanged event vs poll-and-diff? Send `QueryHealth (0x01BF)` on select, or rely on
|
||||
server broadcasts? Defer mana (health-only) for D.5.3 — confirm. The meter render-bug fix:
|
||||
back-track from the element's own DirectState — verify the sprite ids (`0x0600193E` health) against the
|
||||
dump.
|
||||
- **B:** `DragPayload` shape (a `record ItemDragPayload(objId, srcContainer, srcSlot, flags)` vs the
|
||||
slot itself)? Where does the drag ghost render (UiRoot.OnDraw vs UiItemSlot overlay)? Is `UiItemList`
|
||||
or `UiItemSlot` the drop-target unit? Fire-and-forget vs optimistic-then-confirm for the shortcut wire?
|
||||
- **C:** Sub-window mount — recursive `Import()` in `LayoutImporter`, or external stitch by the
|
||||
controller? Inventory grid column count (confirm 6 from decomp)? Does the paperdoll doll clone the
|
||||
player `WorldEntity` or build a fresh ObjDesc-dressed `AnimatedEntityState` (player = camera, so there's
|
||||
no player-as-renderable today)? `IUiViewportRenderer` timing (post-world pass vs pre-pass)? Open the
|
||||
inventory by `I`-key only, or also the toolbar's inventory button?
|
||||
|
||||
---
|
||||
|
||||
## 7. ⚠ Corrections to the grounding research (verify against source)
|
||||
|
||||
- **`_liveEntityInfoByGuid` is GONE** (retired in D.5.4 Task 10, `a9d40ad`). A research agent's notes
|
||||
reference it as the selected-object name source at `GameWindow.cs:835/2559/12129` — **stale.**
|
||||
Post-D.5.4 the name resolves via `ClientObjectTable.Get(guid)?.Name`, or the `GameWindow.LiveName(guid)`
|
||||
/ `DescribeLiveEntity(guid)` helpers (which now read the table). Likewise "`ClientObjectTable` does not
|
||||
exist yet" is wrong — it shipped in D.5.4. Trust the table, not the dict.
|
||||
- **Line numbers throughout drift** (D.5.4 removed ~75 lines from `GameWindow`). Grep the symbol.
|
||||
|
||||
---
|
||||
|
||||
## 8. New-session prompt (paste into a fresh session)
|
||||
|
||||
> Continue acdream's D.2b retail-UI track. **Read `docs/research/2026-06-18-d53-bar-finish-and-inventory-handoff.md` first**, then the 2026-06-16 UI deep-dives it references. Three work streams (spell bar DEFERRED — it is a separate feature, not the action-bar spell shortcuts): **(A)** the action bar's selected-object meter (Health + name; mana deferred — issue #140); **(B)** shortcut drag/add/reorder/remove (the `AddShortcut 0x019C`/`RemoveShortcut 0x019D` wire + the drag-drop spine completion; item shortcuts already render+click); **(C)** start the paperdoll+inventory window (one window — `gmInventoryUI` nests paperdoll/backpack/3D-items). The drag-drop spine (UiRoot has the machine; UiItemSlot lacks the hooks) is shared infra for B and C — build it early. Suggested order: A (standalone quick win) → drag-drop spine → window manager (open/close) → inventory window → shortcut drag → paperdoll (UiViewport). Use the full brainstorm → spec → plan → subagent-driven flow per stream; mandatory grep-named→cross-ref→pseudocode→port for any wire format; conformance tests throughout. Data model is solid post-D.5.4: resolve every object via `ClientObjectTable.Get(guid)` / `GetContents(containerGuid)`. Branch `claude/hopeful-maxwell-214a12` (kept, unmerged).
|
||||
|
||||
**MEMORY.md index line:**
|
||||
- [Handoff: finish the bar + inventory/paperdoll window (2026-06-18)](research/2026-06-18-d53-bar-finish-and-inventory-handoff.md) — next D.2b-UI work after D.5.4. 3 streams (spell bar DEFERRED): (A) selected-object meter (health+name, mana deferred; fix DatWidgetFactory 1-slice-container meter bug; SelectedObjectController like VitalsController), (B) shortcut drag/add/reorder/remove (UiRoot has the drag machine, UiItemSlot lacks hooks; mutable ShortcutStore missing; BuildAddShortcut naming bug), (C) inventory+paperdoll window (needs window-manager open/close + UiItemList grid mode + sub-window mount + wire gaps + UiViewport). Build order + per-stream anchors + brainstorm questions inside. ⚠ _liveEntityInfoByGuid is GONE (D.5.4) — name via ClientObjectTable.Get.
|
||||
Loading…
Add table
Add a link
Reference in a new issue