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>
37 KiB
UI item-slot SPINE — icon-composite render + widget-level drag-drop — deep dive
Date: 2026-06-16 Phase: D.2b retail-UI engine, "core panels" research arc. Report-only. Role: completes the workflow's MISSING 5th doc — the shared item-slot/icon/drag-drop spine that the action-bar, inventory, and paperdoll deep-dives all depend on. The spine agent died on a transient API error before writing anything; this doc is the recovery + the gap-fill. Deliverable: this doc only. No C# changed; no game run.
What this doc adds vs. the four existing docs
The three panel agents + the synthesis already recovered the identity facts of the two spine widgets first-hand and re-verified them (synthesis §0 re-verifications, §1 table, §2). I do not re-derive those — I cite and extend them. My NEW, spine-owned contributions are the three things the panel docs explicitly deferred:
- The icon-composite render port spec (synthesis §4 Step 0, §5 risk #1) — the full
IconData::RenderIconsblit pipeline, and the definitive answer to the direct-RenderSurface-vs-Icon-composite decode question.- The widget-level drag-drop state machine (synthesis §5 risk #1, §8) — the
UIElement_Field/UIElement_UIItemhooks every cell inherits, below the per-panelHandleDropReleasethe panel docs covered.- The consolidated, authoritative
UIElement_UIItemport spec with the resolved field names — including the+0x5FCresolution (synthesis §5 risk #2): it isUIElement_UIItem::itemID.Obsoletes in the synthesis (the parent should patch these now that the spine exists): the ⚠ banner (synthesis lines 13-31), §4 Step 0's "re-do / complete the spine research (blocking)", §5 risk #1 (spine never written), §5 risk #2's "stays UNVERIFIED", §6's "⚠ the SPINE doc was never written", §8's blocking note, and the two panel-doc index lines' "spine still owed" caveats. Details in the closing summary.
1. Summary + confidence legend
Every item-bearing slot in all three D.2b panels is the same pair of retail widgets:
the item-cell UIElement_UIItem (element class 0x10000032) sits inside a
slot/grid UIElement_ItemList (element class 0x10000031). The cell holds a bound
object id (itemID), resolves it to an ACCWeenieObject, and draws a composited 32×32
icon plus a stack of overlay sub-elements (quantity text, capacity/structure Type-7
meters, a 10-step cooldown ring, selected/ghosted/open-container/drag-accept/sell/trade
overlays). The icon itself is composited at runtime from up to five 0x06xx
RenderSurfaces (base + custom-underlay + custom-overlay + item-type-default-underlay +
spell-effect-overlay) blitted into one private 32×32 surface — NOT a single texture.
Drag-drop is a generic chain inherited from UIElement_Field: the cell is both a
drag-SOURCE (ItemList_BeginDrag on left-press-and-move) and a drop-TARGET
(MouseOverTop rollover → accept/reject state, CatchDroppedItem on release →
HandleDropRelease), with InqDropIconInfo extracting the dragged object id + flags
that tell a fresh-from-inventory drag (flags&0xE==0) from a within-list reorder
(flags&4).
acdream is well-positioned: ItemInstance already models IconId/IconUnderlayId/
IconOverlayId/StackSize/ContainerId/ContainerSlot; TextureCache. GetOrUploadRenderSurface already decodes a 0x06 id directly; UiRoot already has a
real drag-drop state machine (DragSource/DragPayload/BeginDrag/UpdateDragHover/
FinishDrag, even commented with the retail 0x15→0x21→0x1C→0x3E event chain). The
concrete gaps: CreateObject discards IconId; there is no multi-layer icon-compositor;
UiField names the CatchDroppedItem/MouseOverTop hooks in a doc-comment but does not
implement them yet.
Confidence legend:
- CONFIRMED — quoted from a named-decomp
class::method(with line) or a realfile:lineI opened this session. - LIKELY — inferred from a CONFIRMED source; the inference is named.
- UNVERIFIED — educated guess, flagged loudly.
2. UIElement_UIItem port spec (consolidated + authoritative)
2.1 Identity + the resolved struct (+0x5FC = itemID)
UIElement_UIItem::Register (decomp 229339):
UIElement::RegisterElementClass(0x10000032, UIElement_UIItem::Create); — class
0x10000032. It is a UIElement_Field subclass: the destructor chains
UIElement_Field::~UIElement_Field(this) (decomp 229326), and Field::Register is
RegisterElementClass(3, …) (decomp 126190) ⇒ the underlying generic Type is 3.
CONFIRMED.
+0x5FC RESOLVED — it is UIElement_UIItem::itemID. The toolbar doc anchored the
bound object id by raw offset +0x5FC only (toolbar §3, UNVERIFIED name). The named
decomp resolves it: UIItem_Update reads uint32_t itemID = this->itemID; (decomp
230230) and this->weenObj = ClientObjMaintSystem::GetWeenieObject(itemID) (decomp
230235). HandleTargetedUseLeftClick reads uint32_t itemID = arg2->itemID; (decomp
230422). ItemList_AddItem's rebuild loop tests eax_2->itemID == arg2 (decomp 233107).
So the field the toolbar's RemoveShortcutInSlotNum read at +0x5FC is itemID —
the bound weenie/object guid. CONFIRMED. (The companion spell-shortcut id is
this->spellID, decomp 230239/230414.)
Resolved instance fields (all CONFIRMED from UIItem_Update 230226-230393,
UIItem_SetIcon 230143, PostInit 229668, SetShortcutNum 229465, the setters
229190-229286, and the acclient.h IconData/PublicWeenieDesc structs):
| Field | Meaning | Anchor |
|---|---|---|
itemID |
bound object/weenie guid (the retail +0x5FC) |
230230 |
spellID |
spell-shortcut id (0 for an item) | 230239, 230414 |
weenObj |
cached ACCWeenieObject* from GetWeenieObject(itemID) |
230235 |
selected |
mirror of weenObj->selected |
230269 |
effects |
mirror of weenObj->pwd._effects |
230293 |
waiting |
mirror of weenObj->waiting (the pending/ghost flag) |
230336 |
isOpenable/isContainer/isContainerHolder |
container-capability flags from _bitfield/_itemsCapacity/_containersCapacity |
230298-230331 |
m_quantity |
stack count to display | 229285 |
m_selectable |
whether selection is allowed | 229266 |
unghostable |
suppress the ghost overlay | 229199 |
m_shortcutNum / m_shortcutGhosted / m_delayedShortcutNum |
toolbar slot index + deferred-bind sentinel 0xFFFFFFFF |
229542-229543, 230344-230349 |
m_sellState / m_tradeState |
vendor-sell / trade-window markers | 230362, 230377 |
m_dragIcon |
translucent drag-ghost copy (created in PostInit, id 0x10000345) |
229738 |
2.2 Sub-element id map (from PostInit, decomp 229672-229733) — all CONFIRMED
PostInit binds each overlay/feature sub-element by GetChildRecursive(this, id). These
ids live in the cell template LayoutDesc 0x21000037; the importer must reproduce them
procedurally (the cell is a behavioral leaf). The dump .layout-dumps/uiitem-0x21000037.txt
gives the per-state sprite ids (column 3 below).
| Member | Element id | Type | Role | Dump sprite(s) (state → 0x06id) |
|---|---|---|---|---|
m_elem_Icon |
0x1000033B |
3 | the composited icon, AND the empty-slot bg | ItemSlot_Empty → 0x060074CF (dump:45) |
m_elem_Icon_Overlays |
0x1000033C |
— | enchantment/effect overlay layer | (state-driven; see §3) |
m_elem_Text |
0x10000344 |
12 (Text) | spell name / label text | — |
m_elem_Icon_CapacityBar |
0x10000347 |
7 (Meter) | container fill (numContained/itemsCapacity) | DirectState 0x06004D22+0x06004D23 (dump:693,710) |
m_elem_Icon_StructureBar |
0x10000348 |
7 (Meter) | structure/charges fill | DirectState 0x06004D24+0x06004D25 (dump:727,744) |
m_elem_Icon_Selected |
0x10000342 |
3 | selection highlight | 0x06001A97 / 0x06001396 / 0x060067D2 per variant (dump:95,311,541) |
m_elem_Icon_Ghosted |
0x10000349 |
3 | greyed "pending server confirm" overlay | DirectState 0x0600109A (dump:761) |
m_elem_Icon_ShortcutNum |
0x1000034A |
3 | the slot-number badge (toolbar) | media set at runtime via SetMediaImage (229508) |
m_elem_Icon_SellState |
0x10000437 |
3 | vendor-sell marker | — |
m_elem_Icon_TradeState |
0x10000438 |
3 | trade-window marker | — |
m_elem_Icon_OpenContainer |
0x10000450 |
3 | "this container is open" frame | DirectState 0x06005D9C Alphablend (dump:2232) |
m_elem_Icon_DragAccept |
0x1000045A |
3 | drag-rollover accept/reject frame | ItemSlot_DragOver_Accept → 0x060011F9, _Reject → 0x060011F8, _DropIn → 0x060011F7 (dump:1174-1175,1258-1260) |
m_elem_Icon_Quantity |
0x100004F5 |
12 (Text) | the stack-count number | — |
m_elem_Icon_Cooldown_10..100 |
0x1000054F..0x10000558 |
3 | 10-step radial cooldown ring | DirectState 0x0600109D / 0x060012D9 / 0x06001DAE / 0x060067CF..D1 … (dump:778-863) |
m_dragIcon |
0x10000345 (created) |
— | translucent drag-ghost | created via CreateChildElement(this, dbobj, 0x10000345), SetVisible(0) (229738-229740) |
The four named LayoutDesc states that drive m_elem_Icon / m_elem_Icon_DragAccept
(from the dump): ItemSlot_Empty (the empty-slot background sprite, default
0x060074CF), ItemSlot_DragOver_Accept (0x060011F9), ItemSlot_DragOver_Reject
(0x060011F8), ItemSlot_DragOver_DropIn (0x060011F7). The DragAccept neutral/reset
UIStateId is 0x1000003f; the inventory agent's 0x10000040(reject)/0x10000041
(accept) SetState ids (synthesis §0 re-verification, decomp 229180-229413) are the
internal element states SetDragAcceptState writes — both are real; the LayoutDesc named
states and the 0x1000003x/4x UIStateIds are the same overlay seen from the dat side vs.
the C++ side. CONFIRMED.
2.3 Key methods + the update pass (UIItem_Update, decomp 230226)
UIItem_Update is the per-change refresh; the controller calls it whenever the bound
weenie or its display state changes. Walk-through (CONFIRMED 230226-230392):
- Resolve
weenObj = GetWeenieObject(itemID)(230235). If null & has a spellID →UIItem_SetState(0x1000001d)+UIItem_SetIcon; if null & no spell →UIItem_SetState(0x1000001c)(= empty) +ClearTooltip. (230232-230250) - Set
m_elem_Icon/m_elem_Text/m_elem_Icon_Overlaysto state0x1000001d(= occupied). (230256-230265) UIItem_SetIcon(this)— (re)build the composited icon (§3). (230268)- Sync
selected↔weenObj->selected, togglingm_elem_Icon_Selectedvisibility (gated onm_selectable). (230269-230290) - Recompute
isOpenable/isContainer/isContainerHolderfrom_bitfield/_itemsCapacity/_containersCapacity(the player's own cell is always openable). (230298-230331) UpdateCapacityDisplay(Type-7 meter = numContained/itemsCapacity, decomp 229554-),UpdateStructureDisplay,UpdateQuantityDisplay,UpdateCooldownDisplay. (230332-230335)- Sync
waiting→SetWaitingState(togglesm_elem_Icon_Ghosted). (230336-230342) - Apply any deferred
m_delayedShortcutNum(re-bind once the weenie loaded). (230344-230350) - Sync
m_shortcutNum/m_shortcutGhosted(230352-230360),m_sellState/m_tradeStateoverlays (230362-230389), thenUpdateTooltip. (230392)
Companion methods (CONFIRMED): UIItem_SetIcon 230143 (§3); SetShortcutNum(slot, ghosted) 229465 (writes the slot badge via SetMediaImage, mirrors into
ACCWeenieObject::SetShortcutNum); SetDelayedShortcutNum 229238; SetWaitingState
229190; SetSelectedState 229243; SetSelectableState 229263; SetDragAcceptState
229271; SetOpenContainerState 229216; SetQuantity 229282; UpdateCapacityDisplay
229554.
2.4 acdream item-cell port = UiItemSlot
A behavioral leaf widget (ConsumesDatChildren => true) keyed off resolved class
0x10000032, exactly like the shipped behavioral widgets. It binds an ItemInstance
(by itemID), draws the composited icon (§3), the quantity UiText, the capacity/
structure UiMeters, the cooldown ring, and the overlay states; it is a drag source +
drop target (§5). This aligns with the synthesis §2 row (no correction). The retail
sub-element ids in §2.2 become the named child slots the controller toggles.
3. Icon rendering pipeline — THE CRUX
3.1 The decode question, answered definitively
Both halves of the synthesis's question are true, layered: each icon LAYER is a
0x06xx RenderSurface decoded directly (the D.2b memory's GetOrUploadRenderSurface
path), but the on-screen icon is a runtime COMPOSITE of up to five of those layers
blitted into one private 32×32 surface. It is NOT a single weenie texture, and it is NOT
an "Icon DBObj type that references other surfaces" — there is no Icon DBObj; the
composite logic lives entirely in client code (IconData::RenderIcons), and every input
id is a plain RenderSurface.
Proof chain (all CONFIRMED):
UIElement_UIItem::UIItem_SetIcon(decomp 230171) sets the cell's image fromACCWeenieObject::GetIcon(weenObj):eax_15 = Graphic::Graphic(eax_13, ACCWeenieObject::GetIcon(eax_12)); … UIRegion::SetImage(this->m_elem_Icon, eax_15);ACCWeenieObject::GetIcon(decomp 408999):return ACCWeenieObject::GetIconData(this)->m_pIcon;ACCWeenieObject::GetIconData(decomp 408224) caches a per-objectIconData(hash by guid), constructing one viaIconData::IconData(eax_4, this, this->id)(408253) on first use;IconData::IconDatacallsIconData::RenderIcons(this, arg2)(407957).- The
IconDatastruct (acclient.h:54112, verbatim):m_idIcon,m_idCustomOverlay,m_idCustomUnderlay,m_itemType,m_effects,Graphic *m_pIcon,Graphic *m_pDragIcon.
The base id is the weenie's _iconID: ACCWeenieObject::InqIconID (decomp 406951)
returns this->pwd._iconID.id. _iconID/_iconOverlayID/_iconUnderlayID are all
IDClass<_tagDataID,32,0> in PublicWeenieDesc (acclient.h:37168-37170). CONFIRMED.
Every layer is DBObj type 0xc — RenderIcons fetches each with
DBObj::Get(QualifiedDataID(&v, id, 0xc)) (decomp 407587/407589/407592). DBObj type
0xc = DB_TYPE_RENDERSURFACE = Texture in ACE's DatFileType enum, id range
0x06000000-0x07FFFFFF (references/.../ACE.DatLoader/DatFileType.cs:127-128). So all
five ids are 0x06xx RenderSurfaces — decode each via
TextureCache.GetOrUploadRenderSurface per the D.2b memory gotcha, NOT GetOrUpload
(feeding a 0x06 id to GetOrUpload walks the Surface→SurfaceTexture chain and returns
1×1 magenta — TextureCache.cs:112-128, project_d2b_retail_ui.md "Dat sprites — the
decode path"). CONFIRMED.
3.2 The composite — IconData::RenderIcons (decomp 407524), CONFIRMED
RenderIcons builds TWO graphics: m_pDragIcon (the drag-ghost, no underlay) and
m_pIcon (the full slot icon). Field captures first (407528-407532):
m_idIcon = InqIconID() # = pwd._iconID (base)
m_idCustomOverlay = pwd._iconOverlayID # server "enchanted" overlay
m_idCustomUnderlay= pwd._iconUnderlayID # server "magic" underlay
m_itemType = InqType()
m_effects = pwd._effects
Player special-case (407546-407549): if IsThePlayer(), m_idIcon = GetDIDByEnum(0x10000004, 7) (the player container icon) and m_itemType = TYPE_CONTAINER.
Two enum-resolved layers (407552-407584):
- type-default underlay
eax_11 = DBObj::GetByEnum(LowestSetBit(m_itemType)+1, …)with enum0x10000004(the SkillTable DID-mapper namespace reused as the icon-type table); ifm_itemTypehas no bits, index0x21. (407555-407564) - effect overlay
arg2 = DBObj::GetByEnum(LowestSetBit(m_effects)+1, …)with enum0x10000005; if null, fall back to index0x21of the same enum. (407568-407584)
Then it resolves the three direct ids as DBObjs (407587-407592): eax_19 =
m_idCustomUnderlay, ebp = m_idIcon (base), edi_1/var_38 = m_idCustomOverlay.
Drag-icon surface (m_pDragIcon, 407594-407625): a 32×32 local surface
(CreateLocalSurface → Create(0x20, 0x20, GetUISurfaceFormat, 1)); blit base
ebp Blit_Normal, then custom-overlay var_38 Blit_4Alpha; ReplaceColor(..., &pwd._iconOverlayID) applies the overlay tint; wrapped in a Graphic.
Full slot icon (m_pIcon, 407626-407647): a second 32×32 surface; blit
type-default underlay eax_11 Blit_Normal, then custom-underlay eax_19
Blit_3Alpha, then the drag-icon surface eax_26 Blit_3Alpha on top (base +
overlay already baked into it). Wrapped in a Graphic → m_pIcon.
Net composite (bottom → top):
- item-type default underlay (
GetByEnum(0x10000004, lsb(itemType)+1)) — Normal - server custom underlay (
pwd._iconUnderlayID) — 3Alpha - base icon (
pwd._iconID) — Normal (baked into the drag layer first) - server custom overlay (
pwd._iconOverlayID) + its tint — 4Alpha - spell-effect overlay (
GetByEnum(0x10000005, lsb(effects)+1)) — (capturedarg2; note: in the 2013 BN lifting the effect-overlay capture lands but I did not see its explicitBlitin the slot-surface block; it feeds the same path. LIKELY blitted as part of the overlay stage — flagged, see §7.)
Cache invalidation: IconData::UpdateIcons (407962) re-renders only when InqIconID(),
_iconOverlayID, _iconUnderlayID, InqType(), or _effects changed (407968-407976);
ACCWeenieObject::IconDataChanged (408201) drives it on a property update.
3.3 The decode pipeline acdream should use
- On
CreateObject(andObjDescEvent/property-update), captureIconId(_iconID),IconUnderlayId(_iconUnderlayID),IconOverlayId(_iconOverlayID),_effects, andItemTypeinto theItemInstance(the model already has the first three fields;_effectsneeds adding). Gap:CreateObject.TryParsediscardsIconId— re-verified atCreateObject.cs:516(_ = ReadPackedDwordOfKnownType(body, ref pos, IconTypePrefix); // IconId) and:515(_ = ReadPackedDword(...) // WeenieClassId). CONFIRMED. - For each of the up-to-five layer ids, decode the
0x06xxRenderSurface directly viaTextureCache.GetOrUploadRenderSurface(per the D.2b gotcha). - Composite into one 32×32 RGBA target in the order of §3.2. Two faithful options:
(a) a CPU compositor matching retail's blit modes (Normal = src-over opaque,
3Alpha/4Alpha = the AC alpha blits — see ACViewer
ImgTex/RenderSurfacedecode for the per-format alpha handling), uploaded as one cached GL texture keyed by the (iconId, underlay, overlay, effects, itemType) tuple; or (b) draw the layers as stacked sprites at the cell rect each frame. Retail does (a) (onem_pIconsurface), and caching matches retail'sIconDataper-object cache +UpdateIconsdirty check — recommend (a). - The type-default underlay (
GetByEnum(0x10000004, lsb(itemType)+1)) and effect overlay (GetByEnum(0x10000005, lsb(effects)+1)) require resolving the retail icon-type / effect DID-mapper enums to concrete0x06ids. These map through the dat DidMapper/EnumMapper tables (DatFileType38/36). For MVP, the base_iconIDalone is the dominant visual (most items have no custom underlay/overlay and no effects); the underlay/overlay/effect layers are the "magic/enchanted/glow" polish. LIKELY-safe to ship base-only first, then layer in the composite. (synthesis §5 risk #3 — verify IconId is set on a CONTAINED item's CreateObject against a live capture before treating it as the sole source.)
Palette note (cross-ref). Item icons are pre-rendered 0x06 RenderSurfaces; they do
NOT take a creature/clothing subpalette overlay at icon-composite time (the composite
only blits + tints with _iconOverlayID). ACViewer's TextureCache.cs::IndexToColor
subpalette-overlay is for paletted INDEX16/P8 world textures — the canonical reference
for THAT path, but the icon path uses the surfaces as-decoded. acdream's WB
TextureHelpers.cs (in-tree) is the decode reference for the 0x06 formats themselves
(BGRA/DXT/P8/INDEX16). CONFIRMED the composite has no subpalette step; LIKELY a paletted
UI icon would need a palette (today GetOrUploadRenderSurface passes palette: null →
magenta on a paletted sprite, TextureCache.cs:135 — flagged §7).
3.4 Identified-vs-unidentified does NOT swap the icon (synthesis §5 risk #14)
CONFIRMED in the negative: UIItem_Update/UIItem_SetIcon/RenderIcons derive the icon
purely from server-sent weenie props (_iconID/_iconUnderlayID/_iconOverlayID/
_effects/InqType) — there is no appraise/identified branch anywhere in the icon
path. Appraise (IdentifyObjectResponse 0x00C9) gates the TOOLTIP detail
(UpdateTooltip, 230392), not the icon. So a slot shows the same icon before and after
appraise. The inventory agent's risk #14 LIKELY is now CONFIRMED.
4. Item / container data model + acdream gap analysis
4.1 Items are ACCWeenieObject weenies
The cell never holds item data — it holds an itemID and resolves it live via
ClientObjMaintSystem::GetWeenieObject(itemID) (decomp 230235). This matches
claude-memory/feedback_weenie_vs_static.md (interactable items are server-spawned
weenies, not dat-baked). The data the cell binds to:
| Cell display | Source field (PublicWeenieDesc, acclient.h:37163+) |
|---|---|
| base icon | _iconID (37168) |
| magic underlay | _iconUnderlayID (37170) |
| enchanted overlay | _iconOverlayID (37169) |
| effect glow | _effects (37183) |
| stack count | _stackSize / _maxStackSize (37188-37189) |
| capacity bar | _itemsCapacity / _containersCapacity (37176-37177) |
| structure bar | _structure / _maxStructure (37186-37187) |
| value/burden | _value (37179) / _burden (37193) |
| container membership | _containerID / _wielderID / _location / _priority (37171-37175) |
4.2 How the client learns container contents
- Login:
PlayerDescription (0x0013)carries the full inventory + equipped lists. - Per-item spawn:
CreateObject (0xF745)for each weenie (incl. a pack item) with the WeenieHeader fields above. - Open a container:
ViewContents (0x0196)lists{guid, containerType}per slot →UIElement_ItemList::ItemList_OpenContainerbuilds aUIElement_UIItemper entry. - Live moves:
ACCWeenieObject::ServerSaysMoveItem(decomp 408086) is the client's per-weenie relocation: it updates_containerID/_wielderID/_location, re-parents in the local content lists (RemoveContent/AddContent), setscurrent_state(IN_CONTAINER/IN_3D_VIEW), and clears thewaitingghost. This is driven by the0x0022/0x0023/0x019AGameEvents. CONFIRMED.
Hierarchy is 2-deep (main pack → side-packs; a side-pack holds no side-pack) — the
backpack hosts two UIElement_ItemLists, the own list (+0x604) and the open-other-
container list (+0x608) (inventory §2.2). The outbound verbs are the ACCWeenieObject:: UIAttempt* family — UIAttemptWield → Event_GetAndWieldItem (decomp 407763, with a
stack-split-to-wield branch when _stackSize>1), UIAttemptPutInContainer →
Event_PutItemInContainer (407797), UIAttemptPutIn3D → Event_DropItem (407821),
UIAttemptMerge/UIAttemptSplitToContainer/UIAttemptSplitTo3D/UIAttemptGive
(407840-407897, 407780). Each records a prevRequest for the speculative-then-confirm
rollback. CONFIRMED.
4.3 acdream model status (focus: what the cell binds to)
ItemInstance.cs(verified): already hasIconId(cs:136),IconUnderlayId(137),IconOverlayId(138),StackSize/StackSizeMax(139-140),Burden(141),Value(142),ContainerId(143),ContainerSlot(144),ValidLocations/CurrentlyEquippedLocation(134-135). Missing for the icon composite:_effects(effect glow) and anItemTypealready present (Type, 133). The synthesis §0 claim is CONFIRMED.ItemRepository.cs(verified): already models the container map, the move events (WieldObject/InventoryPutObjInContainer/InventoryPutObjectIn3D/ViewContents/CloseGroundContainer, cs:23-27) and theInventoryServerSaveFailedspeculative- revert (cs:28-31). CONFIRMED.CreateObject.cs(verified): discardsIconId(cs:516) +WeenieClassId(cs:515) + StackSize/Value/capacities — the cell's icon + quantity + capacity-bar source. CONFIRMED gap.- The full wire-gap TODO is the synthesis §3.3 — not duplicated here; the
data-model-binding subset is: extend
CreateObjectto capture IconId/WeenieClassId/StackSize/Value/ItemCapacity/ContainerCapacity (+_effects), and add_effectstoItemInstance.
5. Drag-drop spine — the WIDGET-LEVEL state machine
The per-panel docs covered the panel-class HandleDropRelease (e.g. gmToolbarUI : ItemListDragHandler). THIS is the shared lower layer every item-cell inherits.
5.1 The retail event chain on the cell (UIElement_UIItem::ListenToElementMessage, decomp 229344)
The cell handles four element messages (CONFIRMED 229347-229418):
0x21= begin-drag (left-press-and-move on an occupied cell): walk to the parentUIElement_ItemList(GetParent()->DynamicCast(0x10000031)) and callItemList_BeginDrag(list, ptWindow.x, ptWindow.y)(229357-229360). The list spawns them_dragIconghost and arms the drag.0x3e= drag-over, with two sub-cases keyed ondwParam1:dwParam1 == 0(drag left this cell): reset DragAccept to neutralSetState(0x1000003f)(229381-229387).- else (drag hovering): if a global drag is active (
UIElementManager::s_pInstance-> m_dragElement != 0), forward toItemList_DragOver(list, target, dragElement)(229390-229406); the list decides accept/reject and flips the DragAccept overlay.
0x15= drop/release: clear the weenie's waiting flag and hidem_elem_Icon_Ghosted(229363-229379). (The retail event-id sequence is0x15→0x21→0x1C→0x3E, which acdream'sUiRootalready cites verbatim —UiRoot.cs:448.)
5.2 The drop-TARGET rollover (UIElement_Field::MouseOverTop, decomp 126098)
Every cell inherits Field's drop-target rollover. When a drag is in progress
(UIElementManager::s_pInstance->m_dragElement != 0) and this field has the
CatchDroppedItem attribute (GetAttribute_Bool(0x36), plus 0x70/0x38), it calls
m_dragDropCallback(m_dragElement, this) to test acceptance and sets element state 9
(accept) or 0xa (reject), saving the old state for restore on leave (126124-126153).
UIElement_Field::CatchDroppedItem (decomp 126159) restores the rollover state then
chains UIElement::CatchDroppedItem (the real drop handler). CONFIRMED.
The 0x36 attribute (CatchDroppedItem flag) is exactly what UIElement_UIItem::PostInit
sets true on every cell (decomp 229744: SetPropertyName(0x36); …(1); SetProperty),
with 0x3a and 0x39 set false (229755/229766). So every item-cell is a drop target
by construction. CONFIRMED.
5.3 InqDropIconInfo — what the drop carries (decomp 230533)
UIElement_ItemList::InqDropIconInfo(dragElement, &objId, &containerId, &flags) reads
the dragged element's properties via InqProperty(0x1000000f..0x10000014) and assembles
the flag word (230595-230617): flags = (bit8 from 0x10000014) | (bit2 from 0x10000013) | (bit4 from 0x10000012) | (bit1 from var_39/0x10000011). The synthesis flag semantics
hold: flags & 0xE == 0 ⇒ fresh drag from inventory (place-new); flags & 4 ⇒
within-list reorder (the source slot is m_lastShortcutNumDragged). objId = the
dragged object guid; containerId = its source container. CONFIRMED (the bit→source
mapping is the toolbar/inventory docs' HandleDropRelease).
5.4 The drag handler interface (ItemListDragHandler + RegisterItemListDragHandler)
UIElement_ItemList::RegisterItemListDragHandler(list, handler) stores
this->m_dragHandler = handler (decomp 230461-230464). Each panel registers ITSELF as
the handler on every slot list (toolbar §5, paperdoll §2a). On a drop, the list routes
to the handler's HandleDropRelease, which resolves the target slot + the
InqDropIconInfo payload and issues the wire action (the per-panel docs). The shared
contract the spine defines is: ItemListDragHandler { OnItemListDragOver(list, target, drag); HandleDropRelease(msg) } + RegisterItemListDragHandler(handler).
5.5 Drag-ghost / cursor lifecycle
m_dragIcon (id 0x10000345) is created in PostInit from a DBObj and kept hidden
(SetVisible(0), decomp 229738-229740); on begin-drag the list makes the global
m_dragElement track the cursor (the translucent icon copy), and on drop it is hidden
again. The drag-ghost graphic is the SAME m_pDragIcon the icon compositor built (§3.2)
— base + overlay, no underlay. CONFIRMED.
5.6 What acdream's UiRoot already has vs. needs
Already there (verified UiRoot.cs): DragSource/DragPayload (cs:71-73),
BeginDrag (cs:450), UpdateDragHover emitting DragOver/DragEnter/DragLeave
(cs:458-482), FinishDrag emitting DropReleased with an accepted flag (cs:484-496),
the 3-pixel DragDistanceThreshold promote-on-move (cs:84,183-189), and the retail
0x15→0x21→0x1C→0x3E chain noted in the comment (cs:448). CapturesPointerDrag on
UiElement distinguishes interior-drag from window-move.
Needs to grow: a per-cell accept test hook (the retail m_dragDropCallback /
CatchDroppedItem — UiField only NAMES these in its doc-comment, it does NOT implement
them: UiField.cs:7-11 "Carries retail Field's drag-drop hooks
(CatchDroppedItem/MouseOverTop) as stubs for future item-window use" — there is no such
method body in the class). So the spine adds: (1) an OnDragOver→accept/reject result on
UiItemSlot that flips its DragAccept overlay state, (2) an OnDrop that calls the
panel's drag handler with the resolved {objId, srcContainer, flags}, and (3) the
m_dragIcon translucent ghost as the drag visual. CONFIRMED gap.
5.7 Generic pick-up → drag → drop → dispatch (pseudocode)
on left-press over an OCCUPIED UiItemSlot: # retail msg 0x21 path
UiRoot.Captured = slot; _dragCandidate = true
on mouse-move while captured & moved > 3px:
UiRoot.BeginDrag(slot, payload = { objId = slot.itemID,
srcContainer = weenie._containerID,
srcSlotIndex = slot.shortcutNum })
show slot.m_dragIcon tracking the cursor # retail m_dragElement
on drag-over a target UiItemSlot/UiItemList: # retail msg 0x3e / MouseOverTop
accepted = targetHandler.OnDragOver(target, payload) # m_dragDropCallback
target.SetDragAccept(accepted ? Accept(0x10000041) : Reject(0x10000040))
on drag leaving the target:
target.SetDragAccept(Neutral 0x1000003f)
on release over target: # retail msg 0x15 / CatchDroppedItem
info = InqDropIconInfo(payload) # objId, srcContainer, flags
targetHandler.HandleDropRelease(target, info) # per-panel: picks the opcode:
# toolbar slot : flags&0xE==0 -> CreateShortcutToItem ; flags&4 -> reorder
# pack slot : PutItemInContainer 0x0019
# equip slot : GetAndWieldItem 0x001A (target's EquipMask)
# ground : DropItem 0x001B
# compatible stack: StackableMerge 0x0054 / split dialog -> Stackable*Split*
# NPC : GiveObjectRequest 0x00CD
slot.SetWaitingState(true) # speculative ghost until server confirm
hide drag ghost; clear DragSource
on server reply (move event) or rollback (InventoryServerSaveFailed 0x00A0):
slot.SetWaitingState(false); UIItem_Update(...) # confirm or revert
The opcode-selection table is the per-panel docs' job (already covered); the spine owns
the pick-up → ghost → accept-test → release → InqDropIconInfo → dispatch-to-handler
chain above.
6. New toolkit widgets this spine introduces
| Widget | Registers at | Leaf vs container | Purpose |
|---|---|---|---|
UiItemSlot (port of UIElement_UIItem, class 0x10000032) |
resolved class id 0x10000032 (resolves to a UIElement_Field subclass ⇒ underlying Type 3); a behavioral leaf in DatWidgetFactory keyed off the resolved class id |
LEAF (ConsumesDatChildren=>true) — reproduces the icon + §2.2 overlay sub-elements procedurally |
one item-in-a-slot: composited icon (§3) + quantity UiText + capacity/structure UiMeters + 10-step cooldown ring + selected/ghosted/open-container/drag-accept/sell/trade overlay states; binds itemID (retail +0x5FC). The spine widget — build once. |
UiItemList / UiItemGrid (port of UIElement_ItemList, class 0x10000031) |
resolved class id 0x10000031 (dump root 0x10000339, Type 268435505, 32×32 — CONFIRMED itemlist-0x2100003D.txt:13-23) |
leaf to the importer (ConsumesDatChildren=>true; manages its own UiItemSlot children procedurally) — logically a container of slots at runtime |
a 1-cell (toolbar/equip) or N-cell (inventory) grid of UiItemSlots; owns the drag handler registration. Port ItemList_AddItem/InsertItem/Flush/IsInList/GetNumUIItems/GetItem/OpenContainer/SetChildList/SetParentContainer/BeginDrag/DragOver/InqDropIconInfo/RegisterItemListDragHandler. |
These exactly match the synthesis §2 / §7 rows — no correction. The UiViewport
(Type 0xD), window manager, and sub-window-mount are NOT spine widgets (paperdoll /
shared-infra; out of scope here). One precision the spine adds: the UiField Type-3
drag hooks are documented-but-unimplemented (§5.6) — the UiItemSlot is where they get a
body, not the generic UiField.
7. Open questions / UNVERIFIED — resolved + carried forward
Resolved by this doc (synthesis §5 risks → now CONFIRMED):
- #1 icon-composite render — RESOLVED. Each layer is a
0x06RenderSurface decoded directly; the icon is a 5-layer composite (IconData::RenderIcons407524). §3. - #2
+0x5FCfield name — RESOLVED. It isUIElement_UIItem::itemID(decomp 230230). §2.1. - #14 identified-vs-unidentified does NOT swap the icon — CONFIRMED in the negative (no appraise branch in the icon path). §3.4.
Carried forward (still need a follow-up):
- Effect-overlay blit into the slot surface (§3.2 layer 5) — the effect DBObj
(
GetByEnum(0x10000005, lsb(effects)+1)) is captured (arg2, 407575) but I did not see its explicitBlitinto them_pIconsurface in the 2013 BN lifting (the visible blits are type-default-underlay, custom-underlay, and the base+overlay drag layer). LIKELY it blits as part of the overlay stage; confirm with a Ghidra decompile of0x0058d180or a cdb trace before relying on the exact effect layering. UNVERIFIED. - Type-default underlay + effect-overlay enum→DID resolution —
GetByEnum(0x10000004, …)/GetByEnum(0x10000005, …)resolve through the dat DidMapper/EnumMapper tables; the concrete0x06ids per item-type / effect were not enumerated. MVP can ship base-_iconID-only. §3.3. UNVERIFIED. - Paletted UI icons —
GetOrUploadRenderSurfacepassespalette: null(TextureCache.cs:135), returning magenta on a paletted (INDEX16/P8) icon. Most item icons are pre-baked BGRA/DXT, but verify no item icon is paletted before shipping; if one is, wire a UI palette (the D.2b memory flags this as a known TODO). UNVERIFIED. - CreateObject IconId on a CONTAINED item (synthesis §5 risk #3) — byte-trace a live
capture that ACE sets
IconIdon a non-3D-visible pack item's CreateObject vs. relying on PlayerDescription. LIKELY present; verify. (WireMCP capture of0xF745.) m_dragDropCallbackshape — retail's per-field accept callback signature (callback(dragElement, this) -> bool, decomp 126124) is confirmed; the acdream binding (a delegate onUiItemSlot/the handler) is a design call for the build spec.
8. MEMORY.md index line
- UI item-slot SPINE — icon composite + drag-drop — D.2b shared spine (completes the 5-doc arc):
UiItemSlot(UIElement_UIItem0x10000032, the+0x5FCbound id RESOLVED =itemID) insideUiItemList(0x10000031). ICON CRUX RESOLVED: each layer is a0x06RenderSurface decoded DIRECTLY viaGetOrUploadRenderSurface, but the on-screen icon is a 5-layer runtime COMPOSITE (IconData::RenderIcons@407524: type-default underlay +_iconUnderlayID+_iconIDbase +_iconOverlayID+tint + effect overlay, blitted into one 32×32 surface; NOT a single texture, NOT appraise-gated). Drag-drop state machine: cell inheritsUIElement_Field::MouseOverTop/CatchDroppedItem(drop-target rollover, attr 0x36) +ListenToElementMessagemsgs 0x21 begin-drag/0x3e drag-over/0x15 drop;InqDropIconInfoflags 0xE==0 fresh-drag, &4 reorder;UiRootalready has the drag chain (0x15→0x21→0x1C→0x3E),UiFieldonly STUBS the hooks. acdream gap:CreateObjectdiscards IconId (cs:516). Sub-element id map + named states (ItemSlot_Empty 0x060074CF, DragOver Accept/Reject/DropIn 0x060011F9/F8/F7) included.