acdream/docs/research/2026-06-16-inventory-deep-dive.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

29 KiB
Raw Blame History

Inventory panel deep-dive — gmInventoryUI + gmBackpackUI

Date: 2026-06-16 Phase: D.2b core-panels research (report-only). Sibling of the action-bar and paperdoll deep-dives; builds on the UIElement_UIItem / icon / drag-drop spine research (see §1 note). Answers handoff §3 questions Q1 (this panel's LayoutDesc), Q7 (window layout), Q8 (full inventory wire-message set), Q9 (icon rendering states).

1. Summary + confidence legend

The retail inventory window is two cooperating dat windows. gmInventoryUI (class 0x10000023, LayoutDesc 0x21000023, 300×362) is the OUTER frame: a title bar, a chrome border, and three slots that host CHILD windows — gmPaperDollUI (the equipped-gear doll), gmBackpackUI (the pack list), and gm3DItemsUI (the 3D rotating-character viewport). gmBackpackUI (class 0x10000022, LayoutDesc 0x21000022, 61×339) is the left strip: a burden Meter (Type 7) + a %-burden text label, the main-pack item grid (UIElement_ItemList 0x10000031), and the side-pack tab column (a second UIElement_ItemList). Every cell in those grids is a UIElement_UIItem (class 0x10000032) — the shared spine widget. Items are server-spawned ACCWeenieObject weenies; the client learns container contents from CreateObject (0xF745) + PlayerDescription (0x0013) at login and from the 0xF7B0 GameEvent family (ViewContents 0x0196, InventoryPutObjInContainer 0x0022, WieldObject 0x0023, …) thereafter; it manipulates them with 0xF7B1 GameActions (PutItemInContainer 0x0019, DropItem 0x001B, GetAndWieldItem 0x001A, the Stackable* family, GiveObjectRequest 0x00CD).

acdream already has the outbound builders for most actions (InventoryActions.cs, InteractRequests.cs) and parsers for most inbound events (GameEvents.cs), plus a live ItemRepository. The gaps are concrete and enumerated in §4: a missing DropItem/GetAndWieldItem/ViewContents/ NoLongerViewingContents parser-or-builder, a 4th field on InventoryPutObjInContainer, and CreateObject not yet extracting IconId/WeenieClassId/StackSize/capacities.

Spine dependency. The handoff said the SPINE agent's doc would live at docs/research/2026-06-16-ui-item-slot-icon-dragdrop-spine-deep-dive.md. At the time of writing that file does NOT exist (only the handoff 2026-06-16-action-bar-inventory-equipment-handoff.md is present — verified by Glob docs/research/2026-06-16-*.md). I therefore derived the inventory-relevant UIElement_UIItem facts FIRST-HAND from the decomp and cite them here; where the spine doc later goes deeper (icon DBObj render, drag state machine), this doc should be read as the inventory-specific layer on top of it.

Confidence legend:

  • CONFIRMED — quoted from a source I opened (decomp class::method + line, or a real file:line).
  • LIKELY — inferred from a confirmed source; the inference is named.
  • UNVERIFIED — educated guess, flagged loudly; do not port without checking.

2. LayoutDesc / element map (Q1, Q7)

2.1 gmInventoryUI — outer frame, LayoutDesc 0x21000023 (300×362)

CONFIRMED Q1. gmInventoryUI::Register registers element class 0x10000023:

gmInventoryUI::Register (decomp line 176285): UIElement::RegisterElementClass(0x10000023, gmInventoryUI::Create);

The window is built from LayoutDesc 0x21000023 (pre-dump .layout-dumps/inventory-0x21000023.txt). The root element 0x100001CC (Type 268435491 = 0x10000023 = the gmInventoryUI class itself) is 300×362 at ZLevel 1000. gmInventoryUI::PostInit (decomp 176236) resolves its named children by id — these element ids match the dump 1:1, which is what confirms the map:

Dump element X,Y,W,H Type (resolved) PostInit binds to Role
0x100001CC (root) 0,0 300×362 0x10000023 gmInventoryUI window root
0x100001CD 0,23 224×214 0x10000024 (base 0x21000024) m_paperDollUI (DynamicCast 0x10000024) nested PaperDoll window
0x100001CE 239,23 61×339 0x10000022 (base 0x21000022) m_backpackUI (DynamicCast 0x10000022) nested Backpack strip
0x100001CF 0,237 234×120 0x10000021 (base 0x21000021) m_3DItemsUI (DynamicCast 0x10000021) nested 3D items viewport
0x100001D3 0,0 276×25 base 0x21000191 m_titleText (GetChildRecursive) title bar ("Inventory of %s")
0x100001D2 276,0 24×23 base 0x10000... 0x21000192 (button: chrome) close/X button (states Normal/pressed)
0x100001D1 0,361 300×1 Type 3 (Field/chrome) bottom rule line (sprite 0x06004D0B)
0x100001D0 0,0 300×362 Type 3 (Field/chrome) full-window backdrop (0x06004D0A, Alphablend) ZLevel 100

PostInit excerpt (CONFIRMED):

gmInventoryUI::PostInit (176240176259): m_titleText = GetChildRecursive(this, 0x100001d3); … = GetChildRecursive(this, 0x100001cd)->DynamicCast(0x10000024) [paperdoll]; … 0x100001ce ->DynamicCast(0x10000022) [backpack]; … 0x100001cf ->DynamicCast(0x10000021) [3DItems];

Implication for the toolkit (LIKELY): the inventory frame is mostly chrome

  • a title UIElement_Text + an X button — the real work is delegated to three NESTED LayoutDesc windows. The importer already recurses generic containers, but it has never instantiated a nested gm*UI window (an element whose Type is a high 0x10000xxx game class with its own BaseLayoutId). This is the "sub-window mount" gap (§6).

2.2 gmBackpackUI — pack strip, LayoutDesc 0x21000022 (61×339)

CONFIRMED Q1. gmBackpackUI::Register (decomp 176531):

UIElement::RegisterElementClass(0x10000022, gmBackpackUI::Create);

Built from LayoutDesc 0x21000022 (pre-dump .layout-dumps/backpack-0x21000022.txt). Root 0x100001C8 (Type 268435490 = 0x10000022) is 61×339. gmBackpackUI::PostInit (decomp 176596) binds the children — again matching the dump exactly:

Dump element X,Y,W,H Type PostInit binds to Role
0x100001C8 (root) 0,0 61×339 0x10000022 gmBackpackUI window root
0x100001D7 0,7 36×15 base 0x10000376/0x2100003F "Burden" caption text
0x100001D8 0,18 36×15 base 0x10000376/0x2100003F m_burdenText the %-load number text
0x100001D9 44,8 11×58 7 (Meter) m_burdenMeter (DynamicCast 7) the burden bar (vertical)
0x100001C9 6,32 36×36 0x10000031 ItemList m_topContainer (DynamicCast 0x10000031) main-pack first cell / list head
0x100001CA 6,73 36×252 0x10000031 ItemList m_containerList (DynamicCast 0x10000031) the item grid (main pack)
0x100001CB 41,73 16×252 base 0x10000... 0x2100003E side-pack tab column / scrollbar gutter

PostInit excerpt (CONFIRMED):

gmBackpackUI::PostInit (176600176629): m_burdenText = GetChildRecursive(this, 0x100001d8); m_burdenMeter = GetChildRecursive(0x100001d9)->DynamicCast(7); … m_topContainer = GetChildRecursive(0x100001c9)->DynamicCast(0x10000031); m_containerList = GetChildRecursive(0x100001ca)->DynamicCast(0x10000031);

The burden Meter (Q7 answer). Element 0x100001D9 is the Type-7 meter the backpack dump shows with back sprite 0x0600121C (grandchild 0x00000002) + fill sprite 0x0600121D. It is a VERTICAL 11×58 bar (the only meter in the window) — confirmed by gmBackpackUI::SetLoadLevel writing it:

gmBackpackUI::SetLoadLevel (176565176573): m_burdenMeter; …(float)arg2; var_10 = 0x69; UIElement::SetAttribute_Float();

That is the SAME meter-fill mechanism as vitals (property 0x69 = fill ratio, pushed at runtime — see 2026-06-15-layoutdesc-format.md §3). The fill value is load × 0.3333… clamped to [0,1] (CONFIRMED 176542: x87_r7_1 = arg2 * 0.33333333333333331), and the text is formatted %d%% from floor(load × 300) (CONFIRMED 176576176583: floor(arg2 * 300.0)SetText(m_burdenText, "%d%%")). So the bar is FULL at 100% load and the number reads 0300% (retail's encumbrance scale: 100% = your computed max burden, you can carry up to 300%).

Where is the VALUE total / coin total? NOT in gmBackpackUI — there is no value Meter or value text element in 0x21000022. The inventory window shows BURDEN only; the pyreal/coin total is the player's Coin Value displayed elsewhere (UNVERIFIED — likely a separate stat readout; the panel dump has no value field). Do not invent a value summary for this window.

The side-pack list. m_containerList (0x100001CA) is the main item grid; 0x100001CB is the narrow 16-wide column to its right (scrollbar gutter / tab strip). The retail "side packs" (sub-bags) are opened as ADDITIONAL container views — gmInventoryUI::RecvNotice_OpenContainedContainer (decomp 176290) routes a contained-container open into a second UIElement_ItemList:

RecvNotice_OpenContainedContainer (176318): UIElement_ItemList::ItemList_OpenContainer(*(…+0x608), arg2, 1); (offset +0x604 = the main/own list; +0x608 = the secondary/other-container list)

The two UIElement_ItemLists at member offsets +0x604 and +0x608 are the "my main pack" list and the "currently-open other container" list — CONFIRMED by the dual flush/open pattern in RecvNotice_SetDisplayInventory (176114/176123/176141) and RecvNotice_PlayerDescReceived (176374/176375 ItemList_SetChildList(+0x604, …); ItemList_SetChildList(+0x608, …)).


3. Container model for this panel (Q3 / cross-cutting, inventory slice)

Items are server weenies (ACCWeenieObject). CONFIRMED throughout the inventory code: ClientObjMaintSystem::GetWeenieObject(itemID) is the only way the panel resolves an item id to its data (e.g. UIItem_Update 230235, RecvNotice_OpenContainedContainer 176293). This matches claude-memory/feedback_weenie_vs_static.md (interactable items are server-spawned weenies). [CONFIRMED]

Container hierarchy = 2-deep. A character has a main pack (capacity ~102) + N side-packs (sub-bags); a side-pack cannot hold another side-pack. acdream's Container model already encodes this (ItemInstance.cs:154 Container with SidePacks + IsSidePack => SideCapacity == 0). [CONFIRMED in acdream; the 2-deep rule is retail-standard and matches ACE]

How the client learns contents:

  1. At loginPlayerDescription (0x0013) carries the player's full inventory + equipped lists; acdream already registers both into ItemRepository (GameEventWiring.cs:405432). [CONFIRMED]
  2. Per-item spawnCreateObject (0xF745) for each visible weenie; for an item in your pack the server sends the weenie (with IconId, capacities, stack size in the WeenieHeader). acdream's CreateObject.TryParse extracts guid/name/itemType but discards IconId, WeenieClassId, StackSize, Value, ItemCapacity, ContainerCapacity (it _ =-skips the IconId at CreateObject.cs:516 and never reads StackSize/Value). [CONFIRMED gap]
  3. Open a containerViewContents (0x0196) lists {guid, containerType} per slot; gmInventoryUI / UIElement_ItemList insert a UIElement_UIItem per entry. [CONFIRMED on ACE/holtburger side; acdream has NO ViewContents parser]
  4. Live movesInventoryPutObjInContainer (0x0022), WieldObject (0x0023), InventoryPutObjectIn3D (0x019A) relocate one weenie; gmInventoryUI::RecvNotice_ServerSaysMoveItem (176175) + the UIElement_ItemList rebuild the affected cells. [CONFIRMED]

The notice ids gmInventoryUI::PostInit registers (CONFIRMED 176269176277) — these are the internal client notice opcodes (NOT wire opcodes) the window listens to: 0x4dd1f0, 0x4dd1f1, 0x4dd1f2, 0x4dd1f6, 0x4dd266, 0x186ab, 0x186a8, 0x4dd25b, 0x4dd25d. They map (via the vftable, 980257980562) to RecvNotice_ItemAttributesChanged / ServerSaysMoveItem / EndPendingInPlayer / ShowPendingInPlayer / OpenContainedContainer / NewParentContainer / PlayerDescReceived / SetDisplayInventory / UpdateCharacterInformation. These are the controller hooks acdream's InventoryController (new, §6) must expose to drive the live grid.


4. Wire-message catalog (Q8)

All client→server ride the 0xF7B1 GameAction envelope (u32 0xF7B1; u32 seq; u32 subOpcode; …); all server→client item events ride the 0xF7B0 GameEvent envelope (u32 0xF7B0; u32 target; u32 seq; u32 eventOpcode; …). ACE handler = the file under ACE/Source/ACE.Server/Network/GameAction/Actions/ (C→S) or …/GameEvent/Events/ (S→C). Chorizite/holtburger field order verified; where I cite holtburger it is inventory/actions.rs or inventory/events.rs (both opened, with hex pack/unpack fixtures).

4.1 Client → server (GameActions, 0xF7B1)

Opcode Name Dir Trigger ACE handler Field order (holtburger/ACE) acdream parse status
0x0019 PutItemInContainer C→S drag item into pack / pick up ground item (container = self) GameActionPutItemInContainer.Handle u32 itemGuid, u32 containerGuid, i32 placement parsedInteractRequests.BuildPickUp (InteractRequests.cs:97)
0x001A GetAndWieldItem C→S equip an item from inventory onto the doll (GameActionType 0x001A; handler Player_Inventory) u32 itemGuid, u32 equipMask (holtburger actions.rs:8 GetAndWieldItemActionData) MISSING (no builder)
0x001B DropItem C→S drop an item on the ground GameActionDropItem.Handle u32 itemGuid (holtburger actions.rs:140) MISSING (no builder; acdream reuses 0x0019 for moves only)
0x0035 UseWithTarget C→S use src item on target (key→door) (Interact) u32 sourceGuid, u32 targetGuid parsedInteractRequests.BuildUseWithTarget
0x0036 UseItem C→S use/equip-by-doubleclick a single item GameActionUseItem u32 targetGuid parsedInteractRequests.BuildUse
0x0054 StackableMerge C→S drop stack A onto compatible stack B GameActionStackableMerge.Handle u32 mergeFromGuid, u32 mergeToGuid, i32 amount parsedInventoryActions.BuildStackableMerge
0x0055 StackableSplitToContainer C→S split N off a stack into a pack slot GameActionStackableSplitToContainer.Handle u32 stackGuid, u32 containerGuid, i32 place, i32 amount parsedInventoryActions.BuildStackableSplitToContainer
0x0056 StackableSplitTo3D C→S split N off a stack onto the ground GameActionStackableSplitTo3D.Handle u32 stackGuid, i32 amount parsedInventoryActions.BuildStackableSplitTo3D
0x019B StackableSplitToWield C→S split N off a stack into an equip slot (e.g. arrows) GameActionStackableSplitToWield u32 stackGuid, u32 equipMask, i32 amount parsedInventoryActions.BuildStackableSplitToWield
0x00CD GiveObjectRequest C→S give item (or N of a stack) to an NPC/player GameActionGiveObjectRequest.Handle u32 targetGuid, u32 itemGuid, i32 amount parsedInventoryActions.BuildGiveObjectRequest
0x0195 NoLongerViewingContents C→S close a side-pack / ground-container view (GameActionType 0x0195) u32 containerGuid (holtburger actions.rs:280) MISSING (no builder)
0x019C AddShortcut C→S pin to quickbar (toolbar phase, listed for completeness) (GameActionType) u32 slot, u32 objType, u32 targetId parsedInventoryActions.BuildAddShortcut
0x019D RemoveShortcut C→S unpin quickbar slot (GameActionType) u32 slot parsedInventoryActions.BuildRemoveShortcut

Opcode source (CONFIRMED): ACE/.../GameAction/GameActionType.cs:1376PutItemInContainer=0x0019, GetAndWieldItem=0x001A, DropItem=0x001B, UseWithTarget=0x0035, StackableMerge=0x0054, StackableSplitToContainer=0x0055, StackableSplitTo3D=0x0056, GiveObjectRequest=0x00CD, NoLongerViewingContents=0x0195, StackableSplitToWield=0x019B. ACE handler field order CONFIRMED by reading each GameAction*.Handle (DropItem reads 1 u32; PutItemInContainer reads 3; GiveObjectRequest reads 3; StackableMerge reads 3; SplitToContainer reads 4; SplitTo3D reads 2). holtburger hex fixtures (actions.rs test module) independently confirm every field layout.

acdream byte-order note: InteractRequests.BuildPickUp writes placement as i32 (InteractRequests.cs:106), matching ACE's ReadInt32(). The split builders write amount/placement as u32 — on the wire identical bytes, but ACE reads them as i32 (negative split amounts can't occur, so this is safe). [CONFIRMED, harmless]

4.2 Server → client (GameEvents, 0xF7B0)

Opcode Name Dir Trigger ACE handler Field order acdream parse status
0x0022 InventoryPutObjInContainer S→C server confirms item now in container at slot GameEventItemServerSaysContainId u32 itemGuid, u32 containerGuid, u32 placement, u32 containerType parsed (INCOMPLETE)GameEvents.ParsePutObjInContainer reads only 3 fields, drops containerType
0x0023 WieldObject S→C server confirms item equipped to slot GameEventWieldItem u32 objectId, i32 equipMask parsed + wiredGameEvents.ParseWieldObject, GameEventWiring.cs:231
0x0196 ViewContents S→C full contents list of a container you opened GameEventViewContents u32 containerGuid, u32 count, [u32 guid, u32 containerType]×count MISSING (no parser)
0x019A InventoryPutObjectIn3D S→C server confirms item dropped to world GameEventItemServerSaysMoveItem u32 objectGuid parsed (UNWIRED)GameEvents.ParsePutObjectIn3D exists, not in WireAll
0x00A0 InventoryServerSaveFailed S→C reject a speculative client move (roll back) GameEventInventoryServerSaveFailed u32 itemGuid, u32 weenieError parsed (UNWIRED, INCOMPLETE)GameEvents.ParseInventoryServerSaveFailed reads only the guid, drops error (holtburger reads both: events.rs:147)
0x0052 CloseGroundContainer S→C server closed a ground-container view GameEventCloseGroundContainer u32 containerGuid parsed (UNWIRED)GameEvents.ParseCloseGroundContainer exists, not in WireAll
0x00C9 IdentifyObjectResponse S→C appraise result (full property bundle) GameEventIdentifyObjectResponse u32 guid, u32 flags, u32 success, …property tables… parsed + wiredAppraiseInfoParser via GameEventWiring.cs:245
0xF745 CreateObject (GameMessage, not GameEvent) S→C spawn a weenie (incl. an item in your pack) GameMessageCreateObjectWorldObject.SerializeCreateObject weenie header (Name, WeenieClassId, IconId, ItemType, …) + ModelData + PhysicsData parsed (INCOMPLETE)CreateObject.TryParse skips IconId/WeenieClassId/StackSize/Value/capacities
SetStackSize (0x0197/UIQueue) SetStackSize S→C update a stack's count + value after merge/split GameMessageSetStackSize u32 seq, u32 guid, u32 stackSize, u32 value MISSING (no parser)
InventoryRemoveObject (UIQueue) InventoryRemoveObject S→C remove an item from inventory view (given/dropped/destroyed) GameMessageInventoryRemoveObject u32 guid MISSING (no parser)

Opcode + field-order sources (CONFIRMED):

  • 0x0022 four fields: GameEventItemServerSaysContainId.cs:1013 writes itemGuid, containerGuid, PlacementPosition, ContainerType; holtburger events.rs:65 reads item_guid, container_guid, slot, container_type (+ hex fixture events.rs:217 slot=3 type=1). acdream's parser (GameEvents.cs:352) stops after 3 u32s — containerType is dropped.
  • 0x0196 shape: GameEventViewContents.cs:1326 writes Guid, count, {guid, containerType}×n; holtburger events.rs:20 (+ fixture events.rs:195).
  • 0x0023: GameEventWieldItem.cs:1112 writes objectId, (int)newLocation.
  • 0x019A: GameEventItemServerSaysMoveItem.cs:11 writes only Guid.
  • 0x00A0: GameEventInventoryServerSaveFailed.cs (error code present; holtburger reads it).
  • SetStackSize: GameMessageSetStackSize.cs:1215 (seq, guid, stackSize, value).
  • InventoryRemoveObject: GameMessageInventoryRemoveObject.cs:11 (guid).

4.3 acdream wire gaps (concrete TODO list for the build session)

  • Add C→S builders: DropItem (0x001B), GetAndWieldItem (0x001A), NoLongerViewingContents (0x0195). (Equip + drop are core inventory verbs.)
  • Add S→C parsers: ViewContents (0x0196), SetStackSize, InventoryRemoveObject.
  • Fix ParsePutObjInContainer to read the 4th containerType u32.
  • Fix ParseInventoryServerSaveFailed to read the weenieError u32.
  • Wire (register in GameEventWiring.WireAll): ViewContents, InventoryPutObjectIn3D, CloseGroundContainer, InventoryServerSaveFailed (parsers exist or will, but WireAll doesn't register them today — CONFIRMED GameEventWiring.cs registers only WieldObject, InventoryPutObjInContainer, IdentifyObjectResponse, PlayerDescription).
  • Extend CreateObject.TryParse to capture IconId (already in the wire, currently _-discarded at CreateObject.cs:516), WeenieClassId, StackSize, Value, ItemCapacity, ContainerCapacity — the inventory cell needs all of these to draw an icon + quantity + capacity bar.

5. Drag-drop for inventory (Q5, this panel's slice)

The drag-drop machinery lives on UIElement_UIItem (the spine widget). The inventory-relevant parts I confirmed first-hand:

  • A slot accepts a drop via UIElement_UIItem::SetDragAcceptState(state), toggling the m_elem_Icon_DragAccept sub-element's STATE (0x10000040 = reject / 0x10000041 = accept; CONFIRMED SetDragAcceptState 229271229277, and call sites at 174307/174313, 201327/201333 flip between the two). [CONFIRMED]
  • A drag in progress uses m_dragIcon (a translucent copy of the icon, created in PostInit 229738229740 via UIElementManager::CreateChildElement with id 0x10000345, SetVisible(0) until a drag starts). [CONFIRMED]
  • The drop RESULT is a wire action, chosen by source→destination: inventory→pack slot = PutItemInContainer (0x0019); inventory→doll = GetAndWieldItem (0x001A); inventory→ground = DropItem (0x001B); stack→compatible stack = StackableMerge (0x0054); partial-stack drag = one of the StackableSplit* (the count picker dialog supplies amount); item→NPC = GiveObjectRequest (0x00CD). [LIKELY — inferred from the action set in §4 + the ACE handler names; the exact source/dest→opcode table is the spine doc's job, but these are the inventory verbs]
  • Speculative-then-confirm: the client may move the cell locally and wait; if the server rejects, InventoryServerSaveFailed (0x00A0) rolls it back (the slot's pending/ghost state is SetWaitingStatem_elem_Icon_Ghosted greys it; CONFIRMED SetWaitingState 229190229208 toggles m_elem_Icon_Ghosted visibility). acdream's ItemRepository already documents this revert path (ItemRepository.cs:30). [CONFIRMED mechanism]

For acdream's toolkit, the drop target is a UiItemSlot (§6) that reports a drop to the InventoryController, which picks the opcode and sends it via LiveCommandBus + the builders in §4 — mirroring the existing interaction pipeline (claude-memory/project_interaction_pipeline.md, B.4 WorldPicker→Use). The UiRoot already has drag-drop input plumbing (per project_d2b_retail_ui.md: "UiRoot already has full input (focus/capture/drag-drop/tooltip/click) — dormant until wired").


6. New toolkit widgets this introduces

The inventory panel needs four new pieces beyond the shipped spine widgets (Button/Menu/Meter/Scrollbar/Text/Field/UiDatElement):

Widget dat Type it registers at Leaf or container Purpose
UiItemSlot (port of UIElement_UIItem) 0x10000032 (UIElement_UIItem::Register line 229339); resolves to a UIElement_Field subclass ⇒ underlying Type 3 leaf (ConsumesDatChildren=>true) — it owns the icon + all overlay sub-elements (m_elem_Icon 0x1000033b, m_elem_Icon_Overlays …33c, m_elem_Icon_Selected …342, m_elem_Icon_Ghosted …349, m_elem_Icon_Quantity …4f5, m_elem_Icon_CapacityBar …347/StructureBar …348 Type-7 meters, cooldown ring …54f558) and reproduces them procedurally one item-in-a-slot: icon + quantity + capacity/structure bars + selection/ghost/drag-accept/open-container overlays. Shared by all 3 panels. (This is the spine widget; named here for the inventory's needs.)
UiItemList / UiItemGrid (port of UIElement_ItemList) 0x10000031 (UIElement_ItemList; the backpack root element is itself this class) container of UiItemSlots (it lays out an N-column grid + scroll) the main-pack grid + the side-pack list. Methods to port: ItemList_AddItem, ItemList_InsertItem, ItemList_Flush, ItemList_OpenContainer, ItemList_SetChildList, ItemList_SetParentContainer, ItemList_OpenFirstContainer (all CONFIRMED as called from gmInventoryUI/gmBackpackUI). Two instances per backpack (own list +0x604, other-container list +0x608).
Sub-window mount (importer capability, not a widget per se) element whose Type is a high 0x10000xxx game class WITH a non-zero BaseLayoutId (e.g. 0x100001CD→paperdoll 0x21000024) container lets LayoutImporter instantiate a NESTED LayoutDesc window inside a parent slot (paperdoll + backpack + 3DItems inside the inventory frame). The importer recurses generic children today but has never mounted another gm*UI window.
Window manager (the deferred Plan-2 piece) drives Dragbar (Type 2) + Resizebar (Type 9) + open/close/z-order/persist infra inventory/paperdoll/toolbar are pop-up windows; needs the faithful grip/dragbar drag (today vitals/chat use whole-window drag, accepted IA-12 approximation).

Plus a thin InventoryController (the gmInventoryUI::PostInit analogue): find-by-id binds m_titleText/m_paperDollUI/m_backpackUI/m_3DItemsUI, subscribes to ItemRepository events, and exposes the notice hooks (ServerSaysMoveItem, SetDisplayInventory, OpenContainedContainer, PlayerDescReceived) — exactly mirroring VitalsController/ChatWindowController.


7. Open questions / UNVERIFIED

  1. Value/coin total in the window. No value Meter or value text exists in 0x21000022 or 0x21000023. Retail likely shows pyreals elsewhere (the coin readout). UNVERIFIED — do not add a value summary to this window without finding its real home.
  2. Side-pack tabs vs. a single scrolling list. Element 0x100001CB (16×252, base 0x2100003E) is the narrow column right of the grid. Whether it renders side-pack TABS (one per sub-bag) or a SCROLLBAR is UNVERIFIED — I read the geometry + the dual-ItemList open pattern but did not decode 0x2100003E. Dump 0x2100003E to settle it.
  3. UIElement_ItemList grid geometry (columns, cell pitch). The cell template is 36×36 (from 0x100001C9); UIElement_UIItem 0x21000037 is 32×32 per the handoff. The exact column count + wrap is in ItemList_AddItem / ItemList_SetChildList (not fully read here). LIKELY a fixed-column grid; confirm by reading UIElement_ItemList::ItemList_AddItem.
  4. CreateObject IconId for pack items. I confirmed the IconId is on the wire and currently discarded, but did not byte-trace that ACE actually sets IconId on a contained (non-visible-in-3D) item's CreateObject vs. relying on PlayerDescription. LIKELY present (the spine icon path needs it); verify against a live capture before trusting it as the sole icon source.
  5. The icon composite layering (underlay/base/effects-overlay) — I anchored it from IconData::IconData (407532+) and the cache key (408842): underlay = pwd._iconUnderlayID OR type-default GetByEnum(0x10000004, LowestSetBit(itemType)+1); base = m_idIcon; effects overlay = GetByEnum(0x10000005, LowestSetBit(_effects)+1) (default 0x21). The exact blend/DBObj-render is the spine doc's territory — treat my §5/§6 citations as the inventory-state hooks, not the full render port. [CONFIRMED anchors, render detail deferred to spine]
  6. Q9 identified-vs-unidentified state. Retail does NOT gate the icon on appraise-state; the underlay/overlay come from the weenie's own _iconUnderlayID/_iconOverlayID/_effects (server-sent), and "unidentified" shows the same icon (the tooltip detail is what's gated by appraise, via IdentifyObjectResponse). LIKELY (no identified→icon-swap code seen in UIItem_Update); the only icon-affecting client states are selected/waiting(ghost)/open-container/drag-accept (all §5). Confirm there's no appraise-gated icon variant before claiming it.

8. MEMORY.md index line

  • 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.