acdream/docs/research/2026-06-16-ui-panels-synthesis.md
Erik a5c5126e8d docs(D.5): action bar / inventory / paperdoll research drop
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>
2026-06-16 21:04:57 +02:00

38 KiB
Raw Blame History

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):

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. 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-135088UIElement_ItemList::Register(); and UIElement_UIItem::Register(); are real adjacent symbols (0x0047a483/0x0047a488). CONFIRMED.
  • acclient_2013_pseudo_c.txt:135130-135132gmBackpackUI::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); GetUIElementType0x10000007 (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_itemListDynamicCast(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::RegisterRegisterElementClass(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 UiItemSlots 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* parsedInteractRequests.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) parsedInteractRequests.BuildUseWithTarget toolbar, inv
0x0036 UseItem C→S use/activate a single item (toolbar slot activation) GameActionUseItem parsedInteractRequests.BuildUse toolbar, inv
0x0054 StackableMerge C→S drop stack A onto compatible stack B GameActionStackableMerge.Handle (from,to,amount) parsedInventoryActions.BuildStackableMerge inv
0x0055 StackableSplitToContainer C→S split N off a stack into a pack slot GameActionStackableSplitToContainer.Handle (stack,container,place,amount) parsedInventoryActions.BuildStackableSplitToContainer inv
0x0056 StackableSplitTo3D C→S split N off a stack onto the ground GameActionStackableSplitTo3D.Handle (stack,amount) parsedInventoryActions.BuildStackableSplitTo3D inv
0x019B StackableSplitToWield C→S split N off a stack into an equip slot (arrows) GameActionStackableSplitToWield (stack,equipMask,amount) parsedInventoryActions.BuildStackableSplitToWield inv, paperdoll
0x00CD GiveObjectRequest C→S give item/N-of-stack to NPC/player GameActionGiveObjectRequest.Handle (target,item,amount) parsedInventoryActions.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.HandleCharacter.AddOrUpdateShortcut(Index,ObjectId) Character_AddShortCut { ShortCutData Shortcut } builder presentInventoryActions.BuildAddShortcut (fix field naming → Index/ObjectId/SpellId|Layer) toolbar
0x019D RemoveShortCut C→S unpin / evict / overwrite a toolbar slot GameActionRemoveShortcut.HandleCharacter.TryRemoveShortcut(index) Character_RemoveShortCut { uint Index } builder presentInventoryActions.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 INCOMPLETEGameEvents.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 + wiredGameEvents.ParseWieldObject, GameEventWiring.cs:231 paperdoll, inv
0x0052 CloseGroundContainer S→C server closed a ground-container view GameEventCloseGroundContainer (containerGuid) parsed UNWIREDGameEvents.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 + wiredAppraiseInfoParser 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 UNWIREDGameEvents.ParsePutObjectIn3D, not in WireAll inv
0xF625 ObjDescEvent S→C wield/unwield → full new appearance broadcast → RedressCreature GameMessageObjDescEventSerializeUpdateModelData (GameMessageObjDescEvent.cs:10-17) (ModelData block) parsedObjDescEvent.cs:33-73 (CreateObject.ReadModelData) paperdoll
0xF745 CreateObject S→C spawn a weenie incl. a pack item (IconId/WeenieClassId/StackSize/Value/capacities) GameMessageCreateObjectWorldObject.SerializeCreateObject Item_CreateObject parsed INCOMPLETECreateObject.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) parsedPlayerDescriptionParser.cs:345-356Parsed.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).

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.

  1. SPINE doc — RESOLVED (no longer blocking). Written: 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_SetIconIconData::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) — 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 — 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 — 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) — 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).