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

18 KiB
Raw Blame History

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.mdthe 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.mdgmToolbarUI 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 QueryHealthSocialActions.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 Actions 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 LayoutImportergmInventoryUI (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.

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/12129stale. 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) — 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.