Five report-only deep-dives + synthesis for the next D.2b UI panels, built on the shipped widget toolkit. Confirms LayoutDesc ids (toolbar 0x21000016, inventory 0x21000023, backpack 0x21000022, paperdoll 0x21000024, 3ditems 0x21000021), the shared item-slot/item-list spine (UIElement_UIItem 0x10000032 / UIElement_ItemList 0x10000031), the 5-layer icon composite (IconData::RenderIcons @407524), the cross-panel wire catalog with acdream parse-status, and the dependency-ordered build plan. Produced via a multi-agent research workflow; the spine agent died on a transient API error and was re-run as a focused follow-up with its decomp anchors verified against source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
407 lines
38 KiB
Markdown
407 lines
38 KiB
Markdown
# 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).
|