acdream/docs/research/2026-06-18-d53-bar-finish-and-inventory-handoff.md
Erik d400bc6105 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>
2026-06-18 21:29:04 +02:00

239 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.