Detailed handoff for the next session. Key correction from the deep-dive: retail empty equip slots are TRANSPARENT (the doll shows through), NOT silhouettes — the current blue border is wrong. Decomposes into Slice 1 (equip slots: bind to CurrentlyEquippedLocation + drag-to-wield via the GetAndWieldItem 0x001A wire gap) and Slice 2 (the heavy 3D doll UiViewport Type 0xD + Core->App IUiViewportRenderer seam). Maps the reuse surface (UiItemSlot, drag spine, optimistic-move, EntitySpawnAdapter) + open questions + a paste-ready new-session prompt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
12 KiB
Handoff — D.2b Sub-phase C: the paperdoll (equip slots + 3D doll)
Date: 2026-06-22 (banked at the end of the inventory drag-drop session, per feedback_session_handoff)
Branch: claude/hopeful-maxwell-214a12, tip 82ab0e0. main is a clean fast-forward ancestor.
Status: SCOPED, not started. The decomposition + corrections + reuse map below are the design sketch; a fresh session runs the full brainstorm → spec → plan → subagent-driven → visual-gate flow per slice.
Line numbers drift — grep the symbol.
Read first (in this order)
- This doc.
docs/research/2026-06-16-equipment-paperdoll-deep-dive.md— the AUTHORITATIVE research. Full element-id →EquipMasktable (§3a, ~25 slots), the viewport mechanism (§5), the wield wire (§4), the reuse analysis (§5c), and the openUNVERIFIEDlist (§7). Do NOT re-derive what's already CONFIRMED there — cite it.claude-memory/project_d2b_retail_ui.md— the D.2b SSOT (the shipped log: container-switching, icons, capacity bars, drag-drop; theUiItemSlot/drag-spine/importer facts you'll reuse).docs/architecture/retail-divergence-register.md— for the AP rows you'll add; anddocs/architecture/code-structure.mdRule 2 (the Core→App seam constraint that governs the doll viewport).
Why this is next
The inventory window is functionally complete in 2D (browse/switch bags, icons, capacity bars, drag-drop move). The last placeholder is the paperdoll: the ~25 equip slots render as generic blue UiDatElement borders, and there's no character doll. It's the standing roadmap NEXT and the most visible remaining gap when F12 is open.
⚠ Correction to carry in (the deep-dive settles this — don't repeat the old assumption)
The earlier brainstorm framed Slice 1 as "per-slot body-part silhouettes." That is wrong. Per the deep-dive §2a (decomp lines 99–102, CONFIRMED): retail equip slots default INVISIBLE and show only when occupied — an empty slot is transparent and the doll body shows through it; an occupied slot shows the item icon. There are no per-slot silhouette sprites. acdream's current blue border is simply the wrong rendering of an empty slot.
Consequence: the doll is the visual backdrop the empty slots reveal. Slice 1 (slots) and Slice 2 (doll) are therefore interdependent for the empty-slot look — decide the empty-slot treatment for a doll-less Slice 1 in the brainstorm (transparent-to-panel vs a temporary placeholder).
The decomposition — TWO slices (each its own spec → plan → implement)
Slice 1 — equip slots (functional; the lighter, independently-valuable first piece)
Bind the ~25 equip slots to live equipped-item data + make them drag-drop equip/unequip targets. No 3D doll yet. Delivers: you SEE your equipped gear (icons in the right slots) and can drag gear onto a slot to wield it / off to unwield.
- The slots already exist + are positioned (the inventory frame mounts gmPaperDollUI at
0x100001CD;DatWidgetFactorybuilds0x10000031→UiItemList; B-Grid confirmed the positions). The work is a newPaperdollController(thegmPaperDollUI::PostInitanalogue) that:- Maps each slot element-id →
EquipMaskvia the deep-dive §3a table (portGetLocationInfoFromElementID, decomp 173620 — it's a static switch; transcribe the table, don't call the decomp at runtime). - Populates each slot's icon from the item whose
ClientObject.CurrentlyEquippedLocation == that slot's EquipMask(acdream ALREADY tracksCurrentlyEquippedLocationper item viaWieldObject 0x0023+CreateObject;ClientObjectTable). So the equipped-state data is already there — no new parse needed for Slice 1 MVP. - Makes each slot a
UiItemSlot(the shipped shared widget) — single 32×32 cell, occupancy-gated icon, drag accept/reject overlay. Empty-slot rendering per the correction above. - Registers the controller as the
IItemListDragHandlerfor the equip slots (reuse the shipped drag-drop spine):OnDragOveraccepts iff the dragged item'sValidLocationsincludes the slot'sEquipMask;HandleDropRelease= wield (GetAndWieldItem 0x001A) optimistically + via wire; dragging a slot's item OUT to the grid = un-wield (PutItemInContainer 0x0019, the SHIPPED path).
- Maps each slot element-id →
- THE WIRE GAP (build it):
GetAndWieldItem(0x001A, payloaduint itemGuid; (uint)EquipMask) has no builder/sender in acdream (deep-dive §4a — only the unrelated UI font property0x1Aexists). AddInteractRequests.BuildGetAndWieldItem(seq, itemGuid, equipMask)(mirrorBuildPickUp) +WorldSession.SendGetAndWieldItem(...)(mirrorSendPutItemInContainer). ACE:GameActionGetAndWieldItem.Handle(item + EquipMask). holtburgercommands.rs:808confirms the shape. - The appearance reply (
ObjDescEvent 0xF625) is already PARSED (deep-dive §4a); it matters for the DOLL (Slice 2), not Slice 1's icons.
Slice 1 scope cuts: no doll, no part-selection highlight, no left/right-side disambiguation polish (the paired wrist/finger slots — handle via the SIDE column in §3a only if trivial), no Aetheria sigil slots (hidden by default).
Slice 2 — the 3D doll viewport (the heavy piece: the UI↔3D bridge)
The UIElement_Viewport (Type 0xD, element 0x100001D5 in gmPaperDollUI) hosting a re-dressed clone of the local player, drawn into the slot region behind the equip slots. This is the single biggest new piece — see deep-dive §5 (CONFIRMED viewport mechanism) + §5d (the design-open seam).
- Reuse the existing char path (deep-dive §5c, CONFIRMED): acdream already renders animated, equipped characters in-world via
EntitySpawnAdapter→AnimatedEntityState(palette/part/hidden-part overrides from ObjDesc). The doll = "build aWorldEntityfrom the local player's Setup + current ObjDesc, feed it through that pipeline, draw it with a fixed camera + one distant light into a UI rect." Re-dress onObjDescEvent 0xF625= rebuild the entity's overrides. - The new widget =
UiViewport(registers at dat Type0xD, leaf). It needs a render-into-rect seam: a CoreIUiViewportRendererinterface implemented in App (Code-Structure Rule 2 — Core defines, App implements; never the reverse), invoked as a scissored single-entity 3D pass keyed to the widget's screen rect. The exact integration (after the world pass vs a UI overlay) is DESIGN-OPEN — settle in the Slice 2 brainstorm (deep-dive §7). - Camera/light immediates, heading-toward-viewer (191.37°), idle animation, and the player-clone-vs-fresh-WorldEntity question are all in deep-dive §5d + §7 (some
UNVERIFIED— decode the float immediates there).
What's already in place (reuse, don't rebuild)
- The mount:
GameWindow.cs:~2208pins0x100001CD(paperdoll) in the inventory frame; the importer pulls in the nested gmPaperDollUI (0x21000024) subtree (InventoryFrameImportProbe.cs:25=PaperdollPanel). The equip-slot elements are already imported + positioned. UiItemSlot(shared item widget) — occupancy-gated icon,SpriteResolve, drag payload/ghost,DragAcceptSprite/DragRejectSprite,IsOpenContainer/Selected/CapacityFilloverlays. The equip slot is the single-cell case.- The drag-drop spine (
IItemListDragHandler,UiRootBeginDrag/FinishDrag,UiItemSlotdrag events) + the optimistic-move machinery (ClientObjectTable.MoveItemOptimistic/ConfirmMove/RollbackMove+ the gapless-insert + the pending-count) — wield/unwield reuses this (wield = an equip-target drop handler; unwield = the shippedPutItemInContainerpath). ClientObjectTable.CurrentlyEquippedLocationper item — the equipped-state source for Slice 1 (no new parse needed for MVP).IconComposer— item icons (incl. theIsThePlayerconstant-backpack special; deep-dive notes the paperdoll icon path).EntitySpawnAdapter/AnimatedEntityState— the char render path Slice 2 reuses.ObjDescEvent 0xF625parse +CreateObject.ReadModelData(palette/part/texture/anim-part) — Slice 2's re-dress input.
Open questions for the brainstorm(s)
- Slice 1 empty-slot look without the doll — transparent-to-panel vs a temporary placeholder (the doll is the real backdrop). The correction above means there's no silhouette sprite to use.
- Equipped-icon source —
CurrentlyEquippedLocation(have it) is enough for MVP; the deep-dive §3c/§7 notes the richerInventoryPlacementequipped list (PlayerDescription equipped section) is not surfaced byPlayerDescriptionParser(partial). Only extend the parser if MVP needs priority/coverage resolution (overlapping armor/clothing) — likely Slice-2-or-later. GetAndWieldItemplacement/auto-slot — does the EquipMask come purely from the drop-target slot (like the doll'sGetLocationInfoFromElementID), and how does auto-wield (drop on the doll body, not a specific slot) pick the slot? holtburgerresolve_and_clear_slots(§4a) is the reference.- The whole of Slice 2's UI↔3D seam (deep-dive §7) — the heaviest design call; its own brainstorm.
0x100001E0 = MissileAmmo 0x800000is LIKELY (corrupted decomp immediate, §7) — confirm by dump if the ammo slot matters for MVP.
Acceptance / scope per slice
- Slice 1: equip slots show your equipped gear's icons in the right slots; drag a wieldable item onto a slot → it wields (
GetAndWieldItem 0x001A, optimistic + rollback); drag a slot's item to the pack → it unwields. Build + full suite green; visual gate (F12 → equipped gear shows in the doll slots, drag-to-equip works). Divergence rows for any approximation. Retires the "generic blue border" gap. - Slice 2: the 3D doll renders the re-dressed player clone behind the slots; empty slots show the doll body through them; equipping updates the doll live (via
ObjDescEvent). Its own spec/plan; theIUiViewportRendererseam is the crux.
New-session prompt
Continue acdream's D.2b retail-UI inventory arc on branch
claude/hopeful-maxwell-214a12(tip82ab0e0;mainis a clean ff ancestor). READ FIRST:docs/research/2026-06-22-paperdoll-handoff.md, then its "Read first" list (esp. the deep-divedocs/research/2026-06-16-equipment-paperdoll-deep-dive.mdandclaude-memory/project_d2b_retail_ui.md).NEXT: Sub-phase C — the paperdoll, Slice 1 (equip slots), first. Bind the ~25 mounted equip slots (under
0x100001CD/ gmPaperDollUI0x21000024) to live equipped-item data and make them drag-drop wield/unwield targets — a newPaperdollController(thegmPaperDollUI::PostInitanalogue) that maps element-id →EquipMask(deep-dive §3a table), populates each slot's icon from the item whoseCurrentlyEquippedLocation == that slot's EquipMask, and registers as the equip-slots'IItemListDragHandler. Build the wire gap:GetAndWieldItem 0x001A(itemGuid + EquipMask) —InteractRequests.BuildGetAndWieldItem+WorldSession.SendGetAndWieldItem(mirrorBuildPickUp/SendPutItemInContainer); wield = optimistic + rollback (reuseMoveItemOptimistic); unwield = the shippedPutItemInContainer 0x0019path. Carry the correction: retail empty equip slots are TRANSPARENT (the doll shows through), NOT silhouettes — decide the doll-less empty-slot look in the brainstorm. Slice 2 (the 3D dollUiViewportType 0xD + the Core→AppIUiViewportRendererseam) is a SEPARATE, heavier cycle — do not start it in Slice 1.Run the full brainstorm → spec → plan → subagent-driven → visual-gate flow for Slice 1. DO NOT auto-kill the running client — launch with plain
dotnet run; if a rebuild is locked, ask the user to close it. Reuse the shippedUiItemSlot+ drag-drop spine + optimistic-move machinery; don't rebuild them.