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>
38 KiB
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 - inventory:
2026-06-16-inventory-deep-dive.md - paperdoll:
2026-06-16-equipment-paperdoll-deep-dive.md - handoff:
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
nullspine digest. The spine doc has since been written: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 a0x06RenderSurface decoded DIRECTLY, but the on-screen icon is a 5-layer runtime composite blitted into one private 32×32 surface (IconData::RenderIconsdecomp 407524), NOT a single texture and NOT appraise-gated; (2) the item-cell bound-object field+0x5FC=UIElement_UIItem::itemID(decomp 230230). The sharedUIElement_UIItem/UIElement_ItemListidentity 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 realfile:linethat 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();andUIElement_UIItem::Register();are real adjacent symbols (0x0047a483/0x0047a488). CONFIRMED.acclient_2013_pseudo_c.txt:135130-135132—gmBackpackUI::Register / gmInventoryUI::Register / gmPaperDollUI::Registerall real. CONFIRMED.acclient_2013_pseudo_c.txt:175242-175508— the ~25 paperdoll equip slots eachDynamicCast(0x10000031)(m_neckSlot, m_headSlot, m_weaponReadySlot, m_ammoReadySlot, …) +RegisterItemListDragHandler. CONFIRMED.acclient_2013_pseudo_c.txt:229180-229413— them_elem_Icon_*family (_Ghosted,_OpenContainer,_Selected,_DragAccept) and itsSetStatereject/accept/neutral states (0x10000040/0x10000041/0x1000003f) are real onUIElement_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 UiItemSlots. 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:
-
Item-slot Type — no real conflict. Toolbar + inventory + paperdoll all call it
UIElement_UIItem, class0x10000032, aUIElement_Fieldsubclass (underlying Type 3), built as a behavioral leaf. The paperdoll doc's widget table named its equip-slot variant "UiItemSlotregistering at0x10000031" — that is the equip slot (a single-cellUIElement_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-cellUIElement_ItemListthat holds at most oneUIElement_UIItem— same two-widget spine as everywhere else, just constrained to one cell. (CONFIRMED: every paperdoll slot isDynamicCast(0x10000031), decomp 175242-175508; the inner cell is theUIElement_UIItem0x10000032per the inventory agent'sUIItem_Update/m_elem_Iconcitations, re-verified at 229180-229413.) -
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 ofUiItemSlots at runtime (it creates/destroys cells procedurally as items arrive). Both descriptions are correct at different layers; the binding rule for the factory isConsumesDatChildren=>true. -
UiViewportType. Only the paperdoll doc introduced it; Type0xD, confirmed against the registry (0xD=Viewport) andRegisterElementClass(0xd,…). No conflict. -
Window manager. All three docs named it identically (shared, drives Dragbar Type 2 + Resizebar Type 9, open/close/z-order/persist). No conflict.
-
+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 4thcontainerTypeu32);ParseInventoryServerSaveFailed(read theweenieErroru32). - Wire (register in
GameEventWiring.WireAll):ViewContents,InventoryPutObjectIn3D (0x019A),CloseGroundContainer (0x0052),InventoryServerSaveFailed (0x00A0). - Extend
CreateObject.TryParseto captureIconId,WeenieClassId,StackSize,Value,ItemCapacity,ContainerCapacity(cells need icon + quantity + capacity bar). Re-verified discarded atCreateObject.cs:515-516. - Extend
PlayerDescriptionParserto surface the equippedInventoryPlacement {iid, loc, priority}list (paperdoll slot icons at login). - Fix
InventoryActions.BuildAddShortcutfield naming (currentlyslotIndex/objectType/targetId; wire layout is correct for item shortcuts but semantics should beIndex/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.
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 UiItemSlots 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.
- SPINE doc — RESOLVED (no longer blocking). Written:
2026-06-16-ui-item-slot-icon-dragdrop-spine-deep-dive.md. Icon-composite render (IconData::RenderIcons407524, 5 layers) + the widget-level drag-drop state machine (UIElement_Field::MouseOverTop/CatchDroppedItem, cell msgs0x21/0x3e/0x15,InqDropIconInfoflags) are now specced with anchors. UIElement_UIItem +0x5FCbound-object-id field name — RESOLVED =itemID.UIElement_UIItem::itemID, anchored atUIItem_Updatedecomp 230230 (uint32_t itemID = this->itemID; … GetWeenieObject(itemID)), corroborated 230422/233107 (companion fieldspellID). See the spine doc.CreateObjectIconId 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.- Use-item opcode
ItemHolder::UseObjectsends (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 acdreamInteractRequests.cs; reconcile when wiring toolbar/inventory activation (Step 5/6). UseShortcuttarget-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.SetDelayedShortcutNumdeferral — needs a re-bind state machine. When a slot's weenie isn't loaded yet (AddShortcutdecomp 196867), the slot must re-bind onceCreateObjectfor that guid arrives. Detail in theToolbarControllerport (Step 5).- Paperdoll
0x100001E0= MissileAmmo0x800000— LIKELY only. The decomp immediate is corrupted to a string-ptr (line 173676); inferred from the EquipMask gap- neighbors. Re-decompile
0x004a388ain Ghidra to recover the real value (Step 7).
- neighbors. Re-decompile
- 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). - 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
IUiViewportRendererCore-interface shape (Code-Structure Rule 2). Brainstorm before Step 7. - Does the doll clone the player
WorldEntityor build a fresh one? — UNVERIFIED. Retail clones the playerCPhysicsObj(makeObject(GetPhysicsObject(player_id)), line 173999); acdream has no player-as-renderable today (player = camera). LIKELY a dedicatedWorldEntityfrom the local player's Setup+ObjDesc fed to a private viewport host. Settle in Step 7 brainstorm. - Inventory side-pack column
0x100001CB(16×252, base0x2100003E) — UNVERIFIED. Tabs (one per sub-bag) or a scrollbar gutter? Dump0x2100003Eto settle (Step 6). UIElement_ItemListgrid geometry (column count, cell pitch) — LIKELY. Cell template 36×36 (0x100001C9);UIElement_UIItem0x21000037is 32×32. Confirm the fixed-column wrap by readingUIElement_ItemList::ItemList_AddItem(Step 3).- 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. - 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 gatesUpdateTooltiponly. InventoryActions.BuildAddShortcutfield-naming bug — CONFIRMED file contents, LIKELY latent bug. Wire layout is correct for item shortcuts; the param names (slotIndex/objectType/targetId) are misleading. Fix toIndex/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) — D.2b build plan reconciling the 3 panel deep-dives. Big-three new widgets:
UiItemSlot(UIElement_UIItem0x10000032, shared leaf),UiItemList/Grid(UIElement_ItemList0x10000031, 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 — D.2b shared spine (completes the 5-doc arc):
UiItemSlot(UIElement_UIItem0x10000032,+0x5FCRESOLVED =itemID) insideUiItemList(0x10000031). ICON CRUX: each layer is a0x06RenderSurface 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 +InqDropIconInfoflags;UiRootalready has the chain,UiFieldonly stubs the hooks; gap =CreateObjectdiscards IconId (cs:516). - Action bar / quick slots (
gmToolbarUI) deep dive — 18 item slots (2 rows of 9, ids0x100001A7-AF+0x100006B7-BF) =UIElement_ItemList(0x10000031) of oneUIElement_UIItem(0x10000032); modelShortCutManager::shortCuts_[18]persisted inPlayerDescription's SHORTCUT block (acdream already parses it); live mutate viaAddShortCut 0x019C/RemoveShortCut 0x019D(acdream builders present — fixBuildAddShortcutfield naming); activation = ordinary use-item (ItemHolder::UseObject, no special wire); the 2 Meters + Scrollbar in0x21000016are the hidden selected-object Health/Mana bars + stack-split slider, NOT paging; drag-drop viagmToolbarUI : ItemListDragHandler::HandleDropRelease. New widgets:UiItemSlot+UiItemList+ToolbarController. - Inventory panel deep-dive (gmInventoryUI/gmBackpackUI) — 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 — 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 — 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 UiItemSlots |
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) — 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).