# D.2b core panels — SYNTHESIS (toolbar + inventory + paperdoll) **Date:** 2026-06-16 **Phase:** D.2b retail-UI engine, "core panels" research arc. Report-only synthesis. **Role:** synthesis lead reconciling the three panel deep-dives into one authoritative build plan. The deliverable is this doc; no code was written. **Inputs (all read in full):** - toolbar: [`2026-06-16-action-bar-toolbar-deep-dive.md`](2026-06-16-action-bar-toolbar-deep-dive.md) - inventory: [`2026-06-16-inventory-deep-dive.md`](2026-06-16-inventory-deep-dive.md) - paperdoll: [`2026-06-16-equipment-paperdoll-deep-dive.md`](2026-06-16-equipment-paperdoll-deep-dive.md) - handoff: [`2026-06-16-action-bar-inventory-equipment-handoff.md`](2026-06-16-action-bar-inventory-equipment-handoff.md) > ## Note: the SPINE doc was completed in a follow-up pass > The handoff promised a "spine agent" doc covering the shared item-slot widget, icon > decode, and the full drag-drop state machine. During the original workflow run the > spine agent died on a transient API error, so this synthesis was first written against > a `null` spine digest. **The spine doc has since been written:** > [`2026-06-16-ui-item-slot-icon-dragdrop-spine-deep-dive.md`](2026-06-16-ui-item-slot-icon-dragdrop-spine-deep-dive.md). > It resolves the two items this synthesis had left open: (1) the **icon-composite > render path** — each icon LAYER is a `0x06` RenderSurface decoded DIRECTLY, but the > on-screen icon is a **5-layer runtime composite** blitted into one private 32×32 > surface (`IconData::RenderIcons` decomp 407524), NOT a single texture and NOT > appraise-gated; (2) the item-cell **bound-object field `+0x5FC` = `UIElement_UIItem::itemID`** > (decomp 230230). The shared `UIElement_UIItem` / `UIElement_ItemList` identity facts > below were first-hand-derived + re-verified by the panel agents and remain sound. > §4 Step 0 and the §5 risks below have been updated to reflect the completed spine doc. ## 0. Summary + confidence legend The three D.2b core panels are all built from the **same two reusable retail widgets**: the **item-slot** (`UIElement_UIItem`, class `0x10000032`) and the **item-list/grid** (`UIElement_ItemList`, class `0x10000031`). Every slot in every panel is one of these — the toolbar is 18 single-cell item-lists, the inventory is N-cell item-list grids, and the paperdoll is ~25 single-cell item-lists keyed to `EquipMask`. Build those two widgets once and all three panels fall out. The paperdoll adds one genuinely new piece: a **`UiViewport`** (`UIElement_Viewport`, Type `0xD`) that renders a live 3D character clone into a UI rect — the single biggest new engineering item. All three panels are pop-up windows, so they all need the deferred **window manager** (open/close/z-order/ persist + Dragbar Type 2 + Resizebar Type 9 drag-resize). On the wire, acdream is in good shape: most C→S builders and S→C parsers already exist; the concrete gaps are a handful of missing builders (`DropItem`, `GetAndWieldItem`, `NoLongerViewingContents`), missing parsers (`ViewContents`, `SetStackSize`, `InventoryRemoveObject`), two incomplete parsers (a dropped 4th field on `0x0022`; a dropped error code on `0x00A0`), and `CreateObject` discarding `IconId`/`StackSize`/capacities the cells need. **Confidence legend** (carried from the source docs, re-checked here): - **CONFIRMED** — quoted from a named-decomp `class::method` (with line) or a real `file:line` that I or a panel agent opened. - **LIKELY** — inferred from a confirmed source; the inference is named. - **UNVERIFIED** — educated guess, flagged loudly; needs a decomp/cdb follow-up before porting. **Synthesis-lead re-verifications (opened first-hand, this session):** - `CreateObject.cs:515-516` — `_ = ReadPackedDword(...) // WeenieClassId; _ = ReadPackedDwordOfKnownType(..., IconTypePrefix);` → **IconId and WeenieClassId are discarded**. CONFIRMED. - `acclient_2013_pseudo_c.txt:135087-135088` — `UIElement_ItemList::Register();` and `UIElement_UIItem::Register();` are real adjacent symbols (`0x0047a483`/`0x0047a488`). CONFIRMED. - `acclient_2013_pseudo_c.txt:135130-135132` — `gmBackpackUI::Register / gmInventoryUI::Register / gmPaperDollUI::Register` all real. CONFIRMED. - `acclient_2013_pseudo_c.txt:175242-175508` — the ~25 paperdoll equip slots each `DynamicCast(0x10000031)` (`m_neckSlot, m_headSlot, m_weaponReadySlot, m_ammoReadySlot, …`) + `RegisterItemListDragHandler`. CONFIRMED. - `acclient_2013_pseudo_c.txt:229180-229413` — the `m_elem_Icon_*` family (`_Ghosted`, `_OpenContainer`, `_Selected`, `_DragAccept`) and its `SetState` reject/accept/neutral states (`0x10000040` / `0x10000041` / `0x1000003f`) are real on `UIElement_UIItem`. CONFIRMED (corroborates the inventory agent's first-hand derivation). --- ## 1. Confirmed class ids + LayoutDesc ids + sizes All confirmed via `*::Register` (`RegisterElementClass`) in the decomp + the pre-dumped `.layout-dumps/` trees. The element-class id and the LayoutDesc id are distinct namespaces (`0x10000xxx` = element class registered in C++; `0x21000xxx` = the dat LayoutDesc that builds the window). | Panel / widget | Element class id | LayoutDesc id | Root element | Size (W×H) | Register anchor | |---|---|---|---|---|---| | `gmToolbarUI` (action bar) | `0x10000007` | `0x21000016` | `0x10000191` | 300×122 | `gmToolbarUI::Register` (decomp 196897); `GetUIElementType`→`0x10000007` (196707) | | `gmInventoryUI` (frame) | `0x10000023` | `0x21000023` | `0x100001CC` | 300×362 | `gmInventoryUI::Register` (decomp 176285 / `0x004a6a60`) | | `gmBackpackUI` (pack strip) | `0x10000022` | `0x21000022` | `0x100001C8` | 61×339 | `gmBackpackUI::Register` (decomp 176531 / `0x004a6e80`) | | `gmPaperDollUI` (equip doll) | `0x10000024` | `0x21000024` | `0x100001D4` | 224×214 | `gmPaperDollUI::Register` (decomp 174445 / `0x004a4560`) | | `gm3DItemsUI` ("Contents of Backpack") | `0x10000021` | `0x21000021` | `0x100001C4` | 234×120 | `gm3DItemsUI::Register` (decomp 176723) | | `UIElement_UIItem` (item-slot, shared) | `0x10000032` | `0x21000037` (32×32 cell template) | — | 32×32 | `UIElement_UIItem::Register` (decomp 229339 / `0x0047a488`) | | `UIElement_ItemList` (item-list/grid, shared) | `0x10000031` | `0x2100003D` (single 32×32 cell) | `0x10000339` | 32×32 cell | `UIElement_ItemList::Register` (decomp / `0x0047a483`) | **Nesting (CONFIRMED `gmInventoryUI::PostInit` 176236-176259):** the inventory FRAME (`0x21000023`) hosts three NESTED gm\*UI windows by id — `0x100001CD`→paperdoll (`DynamicCast 0x10000024`), `0x100001CE`→backpack (`DynamicCast 0x10000022`), `0x100001CF`→3D-items (`DynamicCast 0x10000021`). This "sub-window mount" (an element whose Type is a high `0x10000xxx` game class with its own `BaseLayoutId`) is a capability the importer does **not** have yet. **Note on `gm3DItemsUI`:** despite the "3D" name it is a 2D "Contents of Backpack" item-list (`gm3DItemsUI::PostInit` 176728 sets `m_contentsText`→"Contents of Backpack", `m_itemList`→`DynamicCast(0x10000031)`; its layout has NO Viewport). The 3D character doll is in `gmPaperDollUI`, not here. CONFIRMED. --- ## 2. CONSOLIDATED new toolkit widgets (the single authoritative list) This reconciles the four docs into one list. The shipped D.2b toolkit already has Button(1)/Dragbar(2)/Field(3)/Menu(6)/Meter(7)/Panel(8)/Scrollbar(0xB)/Text(0xC) plus `UiDatElement` for generic chrome — those are **reused**, not re-listed. **Type-registration model:** the shipped numeric Type registry (1=Button … 0x12=Proto) is the toolkit's generic-widget dispatch. The item-slot / item-list / viewport are NOT in that numeric table — in retail they are **`UIElement` subclasses registered by a full class id** via `RegisterElementClass(0x10000xxx, …)`, and in the dat their elements have `Type=0` and inherit the real class id through the `BaseElement` chain (resolved by `ElementReader.Merge`'s zero-wins-base rule). So in acdream's `DatWidgetFactory` they are **new behavioral leaf widgets keyed off the resolved class id**, exactly the same pattern as the existing behavioral widgets — they just key off `0x10000031`/`0x10000032`/`0xD` rather than a small numeric Type. (The numeric Type that `0xD`=Viewport occupies in the confirmed registry IS a generic toolkit Type, so `UiViewport` can register at Type `0xD` directly; the item-slot/item-list register at their class ids.) | Widget | Registers at | Leaf vs container | Panels that use it | Purpose | |---|---|---|---|---| | **`UiItemSlot`** (port of `UIElement_UIItem`, class `0x10000032`) | class id `0x10000032` (resolves to a `UIElement_Field` subclass ⇒ underlying **Type 3**). Behavioral leaf. | **LEAF** (`ConsumesDatChildren=>true`) — reproduces its icon + overlay sub-elements procedurally | **all three** (toolbar slots, inventory cells, paperdoll equip slots) | one item-in-a-slot: icon (underlay/base/effects-overlay) + quantity text + capacity/structure Type-7 bars + cooldown ring; holds the bound object id (retail `+0x5FC`); selection/ghost/drag-accept/open-container overlay states. **The spine widget — build once.** | | **`UiItemList` / `UiItemGrid`** (port of `UIElement_ItemList`, class `0x10000031`) | class id `0x10000031`. Behavioral widget. | **leaf wrt the importer** (manages its own `UiItemSlot` children procedurally) — but logically a **container** of slots | **all three** (toolbar = 1-cell instances; inventory = N-cell grids; paperdoll = 1-cell equip slots) | a 1-cell or N-cell grid of `UiItemSlot`s. Port `ItemList_AddItem/InsertItem/Flush/IsInList/GetNumUIItems/GetItem/OpenContainer/SetChildList/SetParentContainer/OpenFirstContainer`. Backpack uses **two** instances (own list `+0x604`, other-container list `+0x608`). | | **`UiViewport`** (port of `UIElement_Viewport`, Type `0xD`) | numeric Type **`0xD`** (confirmed registry; `UIElement_Viewport::Register`→`RegisterElementClass(0xd,…)` decomp 119126) | **LEAF** (`ConsumesDatChildren=>true`) | **paperdoll only** | hosts a single live 3D entity (the character clone) rendered into the widget's screen rect via a scissored mini 3D pass. Owns a fixed camera + one distant light + an `AnimatedEntityState`. **Needs a new Core→App render-into-rect seam (`IUiViewportRenderer`, Code-Structure Rule 2). The biggest new piece.** | | **Window manager** (shared infra; drives Dragbar Type 2 + Resizebar Type 9) | not a registered widget — infra that drives existing Type-2/Type-9 chrome + `UiElement.Draggable/Resizable` | n/a | **all three** (plus future pop-ups) | open/close/z-order/persist for pop-up windows + faithful grip/dragbar drag-resize. Today vitals/chat use whole-window drag (accepted IA-12 approximation). This is "the other deferred Plan-2 piece." | | **Sub-window mount** (LayoutImporter capability, not a widget) | n/a — an element whose Type is a high `0x10000xxx` game class WITH a non-zero `BaseLayoutId` | container | **inventory** (frame nests paperdoll + backpack + 3D-items) | lets `LayoutImporter` instantiate a NESTED `LayoutDesc` window inside a parent slot. The importer recurses generic children today but has never mounted another gm\*UI window. | | **Per-panel controllers** (`ToolbarController`, `InventoryController`, `PaperDollController`) | not widgets — controllers like `VitalsController`/`ChatWindowController` | n/a | one per panel | find-by-id binding + wire send/receive + model restore. The `gm*UI::PostInit` analogues. (Listed for completeness; each is panel-specific, not a shared widget.) | ### 2a. Reconciled disagreements between the agents The four docs were **consistent** on the big-three widget identities; the differences were wording, not substance. Reconciled: 1. **Item-slot Type — no real conflict.** Toolbar + inventory + paperdoll all call it `UIElement_UIItem`, class **`0x10000032`**, a `UIElement_Field` subclass (underlying Type 3), built as a behavioral **leaf**. The paperdoll doc's widget table named its equip-slot variant "`UiItemSlot` registering at `0x10000031`" — that is the *equip slot* (a single-cell `UIElement_ItemList`), NOT the inner item-cell. **Reconciliation: `UIElement_ItemList` (`0x10000031`) is the slot/grid container; `UIElement_UIItem` (`0x10000032`) is the item-cell inside it.** The paperdoll equip slot is a 1-cell `UIElement_ItemList` that holds at most one `UIElement_UIItem` — same two-widget spine as everywhere else, just constrained to one cell. (CONFIRMED: every paperdoll slot is `DynamicCast(0x10000031)`, decomp 175242-175508; the inner cell is the `UIElement_UIItem` `0x10000032` per the inventory agent's `UIItem_Update`/`m_elem_Icon` citations, re-verified at 229180-229413.) 2. **Item-list "leaf vs container".** Toolbar + paperdoll said **leaf** (the importer doesn't build its dat children; it reproduces cells procedurally); inventory said **container** (it lays out an N-column grid). **Reconciliation: it is a behavioral LEAF to the importer** (`ConsumesDatChildren=>true`, the importer must NOT recurse its dat children) but it is logically a **container of `UiItemSlot`s at runtime** (it creates/destroys cells procedurally as items arrive). Both descriptions are correct at different layers; the binding rule for the factory is `ConsumesDatChildren=>true`. 3. **`UiViewport` Type.** Only the paperdoll doc introduced it; **Type `0xD`**, confirmed against the registry (`0xD`=Viewport) and `RegisterElementClass(0xd,…)`. No conflict. 4. **Window manager.** All three docs named it identically (shared, drives Dragbar Type 2 + Resizebar Type 9, open/close/z-order/persist). No conflict. 5. **`+0x5FC` (bound object id on the item-cell).** The toolbar doc anchors this by OFFSET only (UNVERIFIED symbolic name). The inventory/spine-territory render of the cell would have named it; since the spine doc is missing, **it stays UNVERIFIED** — carried to §5. --- ## 3. Cross-panel wire-message catalog (de-duplicated) All C→S ride the `0xF7B1` GameAction envelope (`u32 0xF7B1; u32 seq; u32 subOpcode; …`); all S→C item events ride the `0xF7B0` GameEvent envelope (`u32 0xF7B0; u32 target; u32 seq; u32 eventOpcode; …`). De-duplicated across the three panels; the "Panels" column shows which panel(s) use each. acdream parse-status is the union of what the three agents found (each cross-checked against `src/AcDream.Core.Net/`). ### 3.1 Client → server (GameActions) | Opcode | Name | Dir | Trigger | ACE handler | Chorizite type | acdream status | Panels | |---|---|---|---|---|---|---|---| | `0x0019` | PutItemInContainer | C→S | drag item into pack / pick up (container=self) | `GameActionPutItemInContainer.Handle` | `Inventory_PutItemInContainer*` | **parsed** — `InteractRequests.BuildPickUp` (InteractRequests.cs:97) | inv, paperdoll (un-wield) | | `0x001A` | GetAndWieldItem | C→S | equip item from pack onto doll/equip slot | `GameActionGetAndWieldItem.Handle` (Actions/GameActionGetAndWieldItem.cs:7-14) → `Player.HandleActionGetAndWieldItem(itemGuid, EquipMask)` | `Inventory_GetAndWieldItem` (`uint ObjectId; EquipMask Slot`, generated.cs:14-42) | **MISSING** (no builder) | paperdoll, inv | | `0x001B` | DropItem | C→S | drop item on the ground | `GameActionDropItem.Handle` (1×u32 guid) | — | **MISSING** (no builder) | inv | | `0x0035` | UseWithTarget | C→S | use src item on target (toolbar target-mode / key→door) | (Interact) | — | **parsed** — `InteractRequests.BuildUseWithTarget` | toolbar, inv | | `0x0036` | UseItem | C→S | use/activate a single item (toolbar slot activation) | `GameActionUseItem` | — | **parsed** — `InteractRequests.BuildUse` | toolbar, inv | | `0x0054` | StackableMerge | C→S | drop stack A onto compatible stack B | `GameActionStackableMerge.Handle` (from,to,amount) | — | **parsed** — `InventoryActions.BuildStackableMerge` | inv | | `0x0055` | StackableSplitToContainer | C→S | split N off a stack into a pack slot | `GameActionStackableSplitToContainer.Handle` (stack,container,place,amount) | — | **parsed** — `InventoryActions.BuildStackableSplitToContainer` | inv | | `0x0056` | StackableSplitTo3D | C→S | split N off a stack onto the ground | `GameActionStackableSplitTo3D.Handle` (stack,amount) | — | **parsed** — `InventoryActions.BuildStackableSplitTo3D` | inv | | `0x019B` | StackableSplitToWield | C→S | split N off a stack into an equip slot (arrows) | `GameActionStackableSplitToWield` (stack,equipMask,amount) | — | **parsed** — `InventoryActions.BuildStackableSplitToWield` | inv, paperdoll | | `0x00CD` | GiveObjectRequest | C→S | give item/N-of-stack to NPC/player | `GameActionGiveObjectRequest.Handle` (target,item,amount) | — | **parsed** — `InventoryActions.BuildGiveObjectRequest` | inv | | `0x0195` | NoLongerViewingContents | C→S | close a side-pack / ground-container view | `GameActionType` 0x0195 (containerGuid) | — | **MISSING** (no builder) | inv | | `0x019C` | AddShortCut | C→S | pin item to toolbar slot (on drag-to-slot / add-selected) | `GameActionAddShortcut.Handle` → `Character.AddOrUpdateShortcut(Index,ObjectId)` | `Character_AddShortCut { ShortCutData Shortcut }` | **builder present** — `InventoryActions.BuildAddShortcut` *(fix field naming → Index/ObjectId/SpellId\|Layer)* | toolbar | | `0x019D` | RemoveShortCut | C→S | unpin / evict / overwrite a toolbar slot | `GameActionRemoveShortcut.Handle` → `Character.TryRemoveShortcut(index)` | `Character_RemoveShortCut { uint Index }` | **builder present** — `InventoryActions.BuildRemoveShortcut` | toolbar | ### 3.2 Server → client (GameEvents / GameMessages) | Opcode | Name | Dir | Trigger | ACE handler | Chorizite type | acdream status | Panels | |---|---|---|---|---|---|---|---| | `0x0022` | InventoryPutObjInContainer | S→C | confirm item in container at slot | `GameEventItemServerSaysContainId` (itemGuid,containerGuid,placement,**containerType**) | — | **parsed INCOMPLETE** — `GameEvents.ParsePutObjInContainer` reads 3 fields, **drops containerType**; wired (GameEventWiring.cs:239) | inv | | `0x0023` | WieldObject | S→C | confirm item equipped to slot | `GameEventWieldItem` (objectId, i32 equipMask) | — | **parsed + wired** — `GameEvents.ParseWieldObject`, GameEventWiring.cs:231 | paperdoll, inv | | `0x0052` | CloseGroundContainer | S→C | server closed a ground-container view | `GameEventCloseGroundContainer` (containerGuid) | — | **parsed UNWIRED** — `GameEvents.ParseCloseGroundContainer`, not in WireAll | inv | | `0x00A0` | InventoryServerSaveFailed | S→C | reject speculative client move (roll back) | `GameEventInventoryServerSaveFailed` (itemGuid, weenieError) | — | **parsed UNWIRED INCOMPLETE** — reads guid only, **drops error**; not in WireAll | inv (+toolbar/paperdoll rollback) | | `0x00C9` | IdentifyObjectResponse | S→C | appraise result (gates tooltip, not icon) | `GameEventIdentifyObjectResponse` (guid,flags,success,property tables) | — | **parsed + wired** — `AppraiseInfoParser` via GameEventWiring.cs:245 | inv, paperdoll, toolbar (tooltip) | | `0x0196` | ViewContents | S→C | full contents list of an opened container | `GameEventViewContents` (container,count,{guid,containerType}×n) | — | **MISSING** (no parser) | inv | | `0x0197` | SetStackSize | S→C | update a stack count+value after merge/split | `GameMessageSetStackSize` (seq,guid,stackSize,value) | — | **MISSING** (no parser) | inv, toolbar | | `0x019A` | InventoryPutObjectIn3D | S→C | confirm item dropped to world | `GameEventItemServerSaysMoveItem` (objectGuid) | — | **parsed UNWIRED** — `GameEvents.ParsePutObjectIn3D`, not in WireAll | inv | | `0xF625` | ObjDescEvent | S→C | wield/unwield → full new appearance broadcast → `RedressCreature` | `GameMessageObjDescEvent` → `SerializeUpdateModelData` (GameMessageObjDescEvent.cs:10-17) | (ModelData block) | **parsed** — `ObjDescEvent.cs:33-73` (`CreateObject.ReadModelData`) | paperdoll | | `0xF745` | CreateObject | S→C | spawn a weenie incl. a pack item (IconId/WeenieClassId/StackSize/Value/capacities) | `GameMessageCreateObject` → `WorldObject.SerializeCreateObject` | `Item_CreateObject` | **parsed INCOMPLETE** — `CreateObject.TryParse` **discards IconId (cs:516), WeenieClassId (cs:515), StackSize, Value, ItemCapacity, ContainerCapacity** | all (icon + quantity source) | | (UIQueue) | InventoryRemoveObject | S→C | remove item from inventory view (given/dropped/destroyed) | `GameMessageInventoryRemoveObject` (guid) | — | **MISSING** (no parser) | inv, toolbar | | `PlayerDescription` SHORTCUT block | persisted toolbar shortcut list | S→C | login (part of `0xF7B0`/0x0013 `PlayerDescription`) | `Player_Character.GetShortcuts()` | `ShortCutData` (Index,ObjectId,LayeredSpellId) | **parsed** — `PlayerDescriptionParser.cs:345-356` → `Parsed.Shortcuts` | toolbar | | `PlayerDescription` equipped `InventoryPlacement` list | persisted equipped-gear list | S→C | login | `GameEventPlayerDescription.WriteEventBody` | `Login_PlayerDescription` | **PARTIAL** — equipped section NOT surfaced (PlayerDescriptionParser.cs:70-77) | paperdoll | **Shared-message note:** `CreateObject (0xF745)` and `IdentifyObjectResponse (0x00C9)` are used by all three panels and de-duplicated above. `GetAndWieldItem (0x001A)` is shared by inventory (equip-from-grid) and paperdoll (drop-on-doll); `UseItem (0x0036)`/ `UseWithTarget (0x0035)` are shared by toolbar activation and inventory double-click. ### 3.3 acdream wire-gap TODO (the build session's concrete list) - **Add C→S builders:** `GetAndWieldItem (0x001A)`, `DropItem (0x001B)`, `NoLongerViewingContents (0x0195)`. - **Add S→C parsers:** `ViewContents (0x0196)`, `SetStackSize (0x0197)`, `InventoryRemoveObject`. - **Fix incomplete parsers:** `ParsePutObjInContainer` (read the 4th `containerType` u32); `ParseInventoryServerSaveFailed` (read the `weenieError` u32). - **Wire (register in `GameEventWiring.WireAll`):** `ViewContents`, `InventoryPutObjectIn3D (0x019A)`, `CloseGroundContainer (0x0052)`, `InventoryServerSaveFailed (0x00A0)`. - **Extend `CreateObject.TryParse`** to capture `IconId`, `WeenieClassId`, `StackSize`, `Value`, `ItemCapacity`, `ContainerCapacity` (cells need icon + quantity + capacity bar). **Re-verified discarded at `CreateObject.cs:515-516`.** - **Extend `PlayerDescriptionParser`** to surface the equipped `InventoryPlacement {iid, loc, priority}` list (paperdoll slot icons at login). - **Fix `InventoryActions.BuildAddShortcut` field naming** (currently `slotIndex/objectType/targetId`; wire layout is correct for item shortcuts but semantics should be `Index/ObjectId/SpellId|Layer`). --- ## 4. Recommended build order Ordered by dependency so the next session can go straight to brainstorm → spec → plan. Each step states why it must come where it does. **Step 0 — SPINE research (DONE — see the spine doc).** The spine doc is complete: [`2026-06-16-ui-item-slot-icon-dragdrop-spine-deep-dive.md`](2026-06-16-ui-item-slot-icon-dragdrop-spine-deep-dive.md). It specs the item-cell widget, the 5-layer icon composite (`IconData::RenderIcons` decomp 407524 — each layer a `0x06` RenderSurface decoded directly, blitted into one private 32×32 surface: type-default underlay / custom underlay / base `_iconID` / custom overlay+tint / effect overlay), `UIElement_UIItem::UIItem_Update`/`UIItem_SetIcon` (decomp 230226+), the overlay-state machine, and the widget-level drag-drop hooks (`UIElement_Field::MouseOverTop`/`CatchDroppedItem`). The `+0x5FC` field is resolved (`UIElement_UIItem::itemID`). Steps 2-7 can now proceed against a finished port spec; this is **no longer blocking**. (Note: WB `TextureHelpers.cs` / ACViewer `IndexToColor` are for WORLD textures — item icons take NO subpalette overlay at composite time; see the spine doc.) **Step 1 — Window manager foundation.** *Why first among code:* all three panels are pop-up windows that must open/close, stack (z-order), persist position, and (faithfully) drag/resize via Dragbar (Type 2) + Resizebar (Type 9). Vitals/chat shipped with whole-window drag (accepted IA-12 approximation); the panels need the real thing. Everything visible in Steps 5-7 mounts inside a managed window, so the manager is the substrate. It is independent of the wire work, so it can proceed in parallel with Step 0. **Step 2 — `UiItemSlot` widget + icon pipeline (`UIElement_UIItem` 0x10000032).** *Why here:* it is the atom of all three panels. Depends on Step 0 (icon render) and on the §3.3 `CreateObject` extension (IconId/StackSize) for real data. Build the leaf widget: icon composite, quantity text, capacity/structure Type-7 bars, cooldown ring, and the selection/ghost/drag-accept/open-container overlay states. **Step 3 — `UiItemList` / `UiItemGrid` widget (`UIElement_ItemList` 0x10000031).** *Why here:* it composes `UiItemSlot`s and is used by every panel (1-cell and N-cell). Depends on Step 2. Port `ItemList_AddItem/InsertItem/Flush/IsInList/GetNumUIItems/ GetItem/OpenContainer/SetChildList/SetParentContainer`. Register as a behavioral leaf (`ConsumesDatChildren=>true`). **Step 4 — Wire wiring (builders/parsers/wireup from §3.3).** *Why here:* the controllers in Steps 5-7 need the full send/receive surface, and this is independent of the widget rendering — it can run in parallel with Steps 1-3. Add the missing builders/parsers, fix the two incomplete parsers, register the unwired parsers, extend `CreateObject` + `PlayerDescriptionParser`, fix `BuildAddShortcut` naming. Each new deviation gets a divergence-register row in the same commit. **Step 5 — `ToolbarController` + the action bar (simplest panel).** *Why before the others:* the toolbar is the simplest consumer (18 single-cell lists, no nested sub-windows, no viewport) and exercises the full spine + window manager + wire path end-to-end. acdream already parses the SHORTCUT block and has both shortcut builders, so it's the fastest path to a working, testable panel. Bind the 18 slots, the hidden selected-object meters + stack slider, the panel-launcher buttons; restore from `Parsed.Shortcuts`; wire `UseShortcut`/`AddShortcut`/`RemoveShortcut` + `HandleDropRelease`. **Step 6 — `InventoryController` + the inventory/backpack panels + sub-window mount.** *Why here:* adds the N-cell grid (Step 3 at scale), the burden Meter (reuses Type-7 `SetLoadLevel`→fill 0x69), the dual-ItemList container model (own `+0x604` / other `+0x608`), and the **sub-window mount** importer capability (frame nests paperdoll + backpack + 3D-items). The hardest 2D panel; depends on Steps 1-4 and the new sub-window mount. **Step 7 — `UiViewport` + `PaperDollController` + the equipment doll (biggest new piece).** *Why last:* it depends on everything above (window manager, equip-slot `UiItemList` instances, `GetAndWieldItem` wire, `PlayerDescription` equipped list) AND introduces the single largest new engineering item: the **UI↔3D render seam** (`IUiViewportRenderer` Core interface, App impl, per Code-Structure Rule 2) that renders a re-dressed player clone into a scissored UI rect. It reuses acdream's existing `EntitySpawnAdapter`/ `AnimatedEntityState` character path, but the rect-scissored single-entity pass is new. Doing it last lets the 2D panels validate the spine first, so a 3D-render bug is isolated. **Parallelism summary:** Step 0 (spine research) + Step 1 (window manager) + Step 4 (wire) can all proceed independently; Steps 2→3→5→6→7 are the dependent spine→panels chain. --- ## 5. Open risks / UNVERIFIED — resolve BEFORE implementation Collated from all four docs; each needs a decomp or cdb follow-up before the cited step. 1. **SPINE doc — RESOLVED (no longer blocking).** Written: [`2026-06-16-ui-item-slot-icon-dragdrop-spine-deep-dive.md`](2026-06-16-ui-item-slot-icon-dragdrop-spine-deep-dive.md). Icon-composite render (`IconData::RenderIcons` 407524, 5 layers) + the widget-level drag-drop state machine (`UIElement_Field::MouseOverTop`/`CatchDroppedItem`, cell msgs `0x21`/`0x3e`/`0x15`, `InqDropIconInfo` flags) are now specced with anchors. 2. **`UIElement_UIItem +0x5FC` bound-object-id field name — RESOLVED = `itemID`.** `UIElement_UIItem::itemID`, anchored at `UIItem_Update` decomp 230230 (`uint32_t itemID = this->itemID; … GetWeenieObject(itemID)`), corroborated 230422/233107 (companion field `spellID`). See the spine doc. 3. **`CreateObject` IconId for CONTAINED (non-3D-visible) pack items — LIKELY.** Confirmed on the wire + currently discarded (`CreateObject.cs:516`), but not byte-traced that ACE sets IconId on a *contained* item's CreateObject vs. relying on PlayerDescription. Verify against a live capture before treating CreateObject as the sole icon source. 4. **Use-item opcode `ItemHolder::UseObject` sends (0x0035 vs 0x0036) — UNVERIFIED.** Throttle (0.2 s) + dispatch CONFIRMED (decomp 402923); the precise opcode branch (`DetermineUseResult`) not traced to the send. Both opcodes exist in acdream `InteractRequests.cs`; reconcile when wiring toolbar/inventory activation (Step 5/6). 5. **`UseShortcut` target-mode path — out of scope, file follow-up.** `ClientUISystem::ExecuteTargetModeForItem` (use-item-on-target) depends on the cursor target-mode subsystem; not part of the action-bar widget itself. 6. **`SetDelayedShortcutNum` deferral — needs a re-bind state machine.** When a slot's weenie isn't loaded yet (`AddShortcut` decomp 196867), the slot must re-bind once `CreateObject` for that guid arrives. Detail in the `ToolbarController` port (Step 5). 7. **Paperdoll `0x100001E0` = MissileAmmo `0x800000` — LIKELY only.** The decomp immediate is corrupted to a string-ptr (line 173676); inferred from the EquipMask gap + neighbors. Re-decompile `0x004a388a` in Ghidra to recover the real value (Step 7). 8. **Paperdoll viewport camera/light float immediates — UNVERIFIED (not byte-decoded).** Lines 175524-175526 / 174144-174146; the agent read the hex but did not convert all floats (`0x3df5c28f≈0.12`, `0xc019999a≈−2.4`, `0xc0400000=−3.0`, `0xc059999a≈−3.4`, `0x3f6147ae≈0.88`, `0x3f800000=1.0`). Decode precisely for faithful framing (Step 7). 9. **UI↔3D render seam — DESIGN-OPEN.** How a UI rect drives a scissored single-entity 3D pass (after the world pass vs. as a UI overlay), and the exact `IUiViewportRenderer` Core-interface shape (Code-Structure Rule 2). Brainstorm before Step 7. 10. **Does the doll clone the player `WorldEntity` or build a fresh one? — UNVERIFIED.** Retail clones the player `CPhysicsObj` (`makeObject(GetPhysicsObject(player_id))`, line 173999); acdream has no player-as-renderable today (player = camera). LIKELY a dedicated `WorldEntity` from the local player's Setup+ObjDesc fed to a private viewport host. Settle in Step 7 brainstorm. 11. **Inventory side-pack column `0x100001CB` (16×252, base `0x2100003E`) — UNVERIFIED.** Tabs (one per sub-bag) or a scrollbar gutter? Dump `0x2100003E` to settle (Step 6). 12. **`UIElement_ItemList` grid geometry (column count, cell pitch) — LIKELY.** Cell template 36×36 (`0x100001C9`); `UIElement_UIItem` `0x21000037` is 32×32. Confirm the fixed-column wrap by reading `UIElement_ItemList::ItemList_AddItem` (Step 3). 13. **Value/coin total NOT in the inventory window — UNVERIFIED home.** No value Meter/ text in `0x21000022`/`0x21000023`; the window shows BURDEN only. Do NOT invent a value summary; find its real home before adding one. 14. **Identified-vs-unidentified does NOT swap the icon — CONFIRMED (negative).** The spine doc confirms there is no appraise branch anywhere in the icon path (`UIItem_SetIcon` → `IconData::RenderIcons`); appraise gates `UpdateTooltip` only. 15. **`InventoryActions.BuildAddShortcut` field-naming bug — CONFIRMED file contents, LIKELY latent bug.** Wire layout is correct for item shortcuts; the param names (`slotIndex/objectType/targetId`) are misleading. Fix to `Index/ObjectId/ SpellId|Layer` + register a divergence row at port time (Step 4/5). --- ## 6. Proposed MEMORY.md index lines (for ALL 5 docs) The parent will append these; I do NOT edit MEMORY.md. - [UI core-panels SYNTHESIS (toolbar+inventory+paperdoll)](research/2026-06-16-ui-panels-synthesis.md) — D.2b build plan reconciling the 3 panel deep-dives. Big-three new widgets: `UiItemSlot`(`UIElement_UIItem` 0x10000032, shared leaf), `UiItemList/Grid`(`UIElement_ItemList` 0x10000031, shared leaf-to-importer), `UiViewport`(Type 0xD, paperdoll 3D doll), plus the shared **window manager** (Dragbar 2 + Resizebar 9) + sub-window-mount importer capability + per-panel controllers. De-duped cross-panel wire table; build order (window mgr → item-slot+icon → item-list → wire → toolbar → inventory → paperdoll; spine research DONE). - [UI item-slot SPINE — icon composite + drag-drop](research/2026-06-16-ui-item-slot-icon-dragdrop-spine-deep-dive.md) — D.2b shared spine (completes the 5-doc arc): `UiItemSlot`(`UIElement_UIItem` 0x10000032, `+0x5FC` RESOLVED = `itemID`) inside `UiItemList`(0x10000031). ICON CRUX: each layer is a `0x06` RenderSurface decoded DIRECTLY, but the icon is a 5-layer runtime COMPOSITE (`IconData::RenderIcons` @407524; NOT one texture, NOT appraise-gated). Drag-drop = `Field::MouseOverTop`/`CatchDroppedItem` + cell msgs 0x21/0x3e/0x15 + `InqDropIconInfo` flags; `UiRoot` already has the chain, `UiField` only stubs the hooks; gap = `CreateObject` discards IconId (cs:516). - [Action bar / quick slots (`gmToolbarUI`) deep dive](research/2026-06-16-action-bar-toolbar-deep-dive.md) — 18 item slots (2 rows of 9, ids `0x100001A7-AF`+`0x100006B7-BF`) = `UIElement_ItemList`(0x10000031) of one `UIElement_UIItem`(0x10000032); model `ShortCutManager::shortCuts_[18]` persisted in `PlayerDescription`'s SHORTCUT block (acdream already parses it); live mutate via `AddShortCut 0x019C`/`RemoveShortCut 0x019D` (acdream builders present — fix `BuildAddShortcut` field naming); activation = ordinary use-item (`ItemHolder::UseObject`, no special wire); the 2 Meters + Scrollbar in `0x21000016` are the hidden selected-object Health/Mana bars + stack-split slider, NOT paging; drag-drop via `gmToolbarUI : ItemListDragHandler::HandleDropRelease`. New widgets: `UiItemSlot` + `UiItemList` + `ToolbarController`. - [Inventory panel deep-dive (gmInventoryUI/gmBackpackUI)](research/2026-06-16-inventory-deep-dive.md) — D.2b: LayoutDesc 0x21000023 (frame: title + 3 nested sub-windows) + 0x21000022 (backpack: burden Meter 0x100001D9 via SetLoadLevel→fill 0x69, main-pack ItemList 0x100001CA); full inventory wire catalog (C→S 0x0019/1A/1B/54/55/56/19B/CD/195, S→C 0x0022/23/196/19A/A0/52 + SetStackSize/InventoryRemoveObject) with acdream parse-status (gaps: DropItem/GetAndWieldItem/ViewContents builders, 0x0022 4th field, CreateObject IconId); new widgets UiItemSlot(0x10000032)/UiItemGrid(0x10000031)+sub-window mount+window manager. - [Equipment/Paperdoll panel deep-dive](research/2026-06-16-equipment-paperdoll-deep-dive.md) — gmPaperDollUI 0x10000024/LayoutDesc 0x21000024: doll = UIElement_Viewport (Type 0xD, elem 0x100001D5) hosting a CreatureMode clone re-dressed via DoObjDescChangesFromDefault; ~25 equip slots are single-cell UIElement_ItemList (0x10000031) mapped element-id→EquipMask by GetLocationInfoFromElementID; wield = GetAndWieldItem (0x001A, item+EquipMask, acdream-MISSING), appearance reply = ObjDescEvent 0xF625 (acdream-PARSED) → RedressCreature; acdream's EntitySpawnAdapter/AnimatedEntityState char path is reusable; new widgets = UiViewport (0xD, the UI↔3D bridge), UiItemSlot (0x10000031), window manager. gm3DItemsUI 0x21000021 is a "Contents of Backpack" list, NOT the doll. - [Action-bar/inventory/equipment research handoff](research/2026-06-16-action-bar-inventory-equipment-handoff.md) — the §3 question list (Q1-Q12) + agent assignment that drove the toolbar/inventory/paperdoll/spine deep-dives. (All 5 research docs delivered; the spine doc was completed in a follow-up pass after a transient agent failure.) --- ## 7. New toolkit widgets this introduces (recap) | Widget | dat Type / class it registers at | leaf vs container | Purpose | |---|---|---|---| | `UiItemSlot` | class `0x10000032` (`UIElement_UIItem`) | leaf (`ConsumesDatChildren=>true`) | shared item-cell: icon + quantity + capacity/structure bars + overlay states + bound object id | | `UiItemList` / `UiItemGrid` | class `0x10000031` (`UIElement_ItemList`) | leaf to importer; container of slots at runtime | shared 1-cell/N-cell grid of `UiItemSlot`s | | `UiViewport` | numeric Type `0xD` (`UIElement_Viewport`) | leaf (`ConsumesDatChildren=>true`) | paperdoll 3D character doll via a scissored mini 3D pass; needs `IUiViewportRenderer` Core→App seam | | Window manager | infra (drives Dragbar Type 2 + Resizebar Type 9) | n/a | open/close/z-order/persist + faithful grip/dragbar drag-resize for all pop-up panels | | Sub-window mount | LayoutImporter capability (element whose Type is a high `0x10000xxx` class with a `BaseLayoutId`) | container | nest a `LayoutDesc` window inside a parent slot (inventory frame → paperdoll/backpack/3D-items) | ## 8. Open questions / UNVERIFIED (recap) See §5 for the full collated list with anchors. The former blocking item — the spine doc — is now written (icon-composite render path + widget-level drag-drop state machine specced with anchors; `+0x5FC` = `itemID` resolved). Remaining items are per-step follow-ups (decode the paperdoll camera floats, recover `0x100001E0`, dump `0x2100003E`, byte-trace CreateObject IconId for contained items). --- **Single MEMORY.md index line for THIS doc:** - [UI core-panels SYNTHESIS (toolbar+inventory+paperdoll)](research/2026-06-16-ui-panels-synthesis.md) — D.2b build plan reconciling the 3 panel deep-dives: shared `UiItemSlot`(0x10000032)+`UiItemList`(0x10000031) spine, `UiViewport`(Type 0xD) for the paperdoll 3D doll, window manager (Dragbar 2 + Resizebar 9) + sub-window-mount; de-duped cross-panel wire table; build order window-mgr→item-slot+icon→item-list→wire→toolbar→inventory→paperdoll (spine research DONE — see the spine deep-dive).