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>
29 KiB
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 handoff2026-06-16-action-bar-inventory-equipment-handoff.mdis present — verified byGlob docs/research/2026-06-16-*.md). I therefore derived the inventory-relevantUIElement_UIItemfacts 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 realfile: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 (176240–176259): 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 NESTEDLayoutDescwindows. The importer already recurses generic containers, but it has never instantiated a nested gm*UI window (an element whose Type is a high0x10000xxxgame class with its ownBaseLayoutId). 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 (176600–176629): 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 (176565–176573): 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 176576–176583:
floor(arg2 * 300.0) → SetText(m_burdenText, "%d%%")). So the bar is FULL
at 100% load and the number reads 0–300% (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 in0x21000022. 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:
- At login —
PlayerDescription (0x0013)carries the player's full inventory + equipped lists; acdream already registers both intoItemRepository(GameEventWiring.cs:405–432). [CONFIRMED] - Per-item spawn —
CreateObject (0xF745)for each visible weenie; for an item in your pack the server sends the weenie (withIconId, capacities, stack size in the WeenieHeader). acdream'sCreateObject.TryParseextracts guid/name/itemType but discards IconId, WeenieClassId, StackSize, Value, ItemCapacity, ContainerCapacity (it_ =-skips the IconId atCreateObject.cs:516and never reads StackSize/Value). [CONFIRMED gap] - Open a container —
ViewContents (0x0196)lists{guid, containerType}per slot;gmInventoryUI/UIElement_ItemListinsert aUIElement_UIItemper entry. [CONFIRMED on ACE/holtburger side; acdream has NO ViewContents parser] - Live moves —
InventoryPutObjInContainer (0x0022),WieldObject (0x0023),InventoryPutObjectIn3D (0x019A)relocate one weenie;gmInventoryUI::RecvNotice_ServerSaysMoveItem(176175) + theUIElement_ItemListrebuild the affected cells. [CONFIRMED]
The notice ids gmInventoryUI::PostInit registers (CONFIRMED 176269–176277)
— 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, 980257–980562) 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 |
parsed — InteractRequests.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 |
parsed — InteractRequests.BuildUseWithTarget |
0x0036 |
UseItem | C→S | use/equip-by-doubleclick a single item | GameActionUseItem |
u32 targetGuid |
parsed — InteractRequests.BuildUse |
0x0054 |
StackableMerge | C→S | drop stack A onto compatible stack B | GameActionStackableMerge.Handle |
u32 mergeFromGuid, u32 mergeToGuid, i32 amount |
parsed — InventoryActions.BuildStackableMerge |
0x0055 |
StackableSplitToContainer | C→S | split N off a stack into a pack slot | GameActionStackableSplitToContainer.Handle |
u32 stackGuid, u32 containerGuid, i32 place, i32 amount |
parsed — InventoryActions.BuildStackableSplitToContainer |
0x0056 |
StackableSplitTo3D | C→S | split N off a stack onto the ground | GameActionStackableSplitTo3D.Handle |
u32 stackGuid, i32 amount |
parsed — InventoryActions.BuildStackableSplitTo3D |
0x019B |
StackableSplitToWield | C→S | split N off a stack into an equip slot (e.g. arrows) | GameActionStackableSplitToWield |
u32 stackGuid, u32 equipMask, i32 amount |
parsed — InventoryActions.BuildStackableSplitToWield |
0x00CD |
GiveObjectRequest | C→S | give item (or N of a stack) to an NPC/player | GameActionGiveObjectRequest.Handle |
u32 targetGuid, u32 itemGuid, i32 amount |
parsed — InventoryActions.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 |
parsed — InventoryActions.BuildAddShortcut |
0x019D |
RemoveShortcut | C→S | unpin quickbar slot | (GameActionType) |
u32 slot |
parsed — InventoryActions.BuildRemoveShortcut |
Opcode source (CONFIRMED): ACE/.../GameAction/GameActionType.cs:13–76 —
PutItemInContainer=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.BuildPickUpwritesplacementasi32(InteractRequests.cs:106), matching ACE'sReadInt32(). The split builders writeamount/placementasu32— on the wire identical bytes, but ACE reads them asi32(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 + wired — GameEvents.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 + wired — AppraiseInfoParser via GameEventWiring.cs:245 |
0xF745 |
CreateObject (GameMessage, not GameEvent) | S→C | spawn a weenie (incl. an item in your pack) | GameMessageCreateObject → WorldObject.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):
0x0022four fields:GameEventItemServerSaysContainId.cs:10–13writesitemGuid, containerGuid, PlacementPosition, ContainerType; holtburgerevents.rs:65readsitem_guid, container_guid, slot, container_type(+ hex fixtureevents.rs:217slot=3 type=1). acdream's parser (GameEvents.cs:352) stops after 3 u32s —containerTypeis dropped.0x0196shape:GameEventViewContents.cs:13–26writesGuid, count, {guid, containerType}×n; holtburgerevents.rs:20(+ fixtureevents.rs:195).0x0023:GameEventWieldItem.cs:11–12writesobjectId, (int)newLocation.0x019A:GameEventItemServerSaysMoveItem.cs:11writes onlyGuid.0x00A0:GameEventInventoryServerSaveFailed.cs(error code present; holtburger reads it).SetStackSize:GameMessageSetStackSize.cs:12–15(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
ParsePutObjInContainerto read the 4thcontainerTypeu32. - Fix
ParseInventoryServerSaveFailedto read theweenieErroru32. - Wire (register in
GameEventWiring.WireAll):ViewContents,InventoryPutObjectIn3D,CloseGroundContainer,InventoryServerSaveFailed(parsers exist or will, butWireAlldoesn't register them today — CONFIRMEDGameEventWiring.csregisters onlyWieldObject,InventoryPutObjInContainer,IdentifyObjectResponse,PlayerDescription). - Extend
CreateObject.TryParseto captureIconId(already in the wire, currently_-discarded atCreateObject.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 them_elem_Icon_DragAcceptsub-element's STATE (0x10000040= reject /0x10000041= accept; CONFIRMEDSetDragAcceptState229271–229277, 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 inPostInit229738–229740 viaUIElementManager::CreateChildElementwith id0x10000345,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 theStackableSplit*(the count picker dialog suppliesamount); 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 isSetWaitingState→m_elem_Icon_Ghostedgreys it; CONFIRMEDSetWaitingState229190–229208 togglesm_elem_Icon_Ghostedvisibility). acdream'sItemRepositoryalready 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 …54f–558) 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
- Value/coin total in the window. No value Meter or value text exists in
0x21000022or0x21000023. Retail likely shows pyreals elsewhere (the coin readout). UNVERIFIED — do not add a value summary to this window without finding its real home. - Side-pack tabs vs. a single scrolling list. Element
0x100001CB(16×252, base0x2100003E) 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 decode0x2100003E. Dump0x2100003Eto settle it. UIElement_ItemListgrid geometry (columns, cell pitch). The cell template is 36×36 (from0x100001C9); UIElement_UIItem0x21000037is 32×32 per the handoff. The exact column count + wrap is inItemList_AddItem/ItemList_SetChildList(not fully read here). LIKELY a fixed-column grid; confirm by readingUIElement_ItemList::ItemList_AddItem.CreateObjectIconId 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.- The icon composite layering (underlay/base/effects-overlay) — I anchored
it from
IconData::IconData(407532+) and the cache key (408842): underlay =pwd._iconUnderlayIDOR type-defaultGetByEnum(0x10000004, LowestSetBit(itemType)+1); base =m_idIcon; effects overlay =GetByEnum(0x10000005, LowestSetBit(_effects)+1)(default0x21). 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] - 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, viaIdentifyObjectResponse). LIKELY (no identified→icon-swap code seen inUIItem_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.