acdream/docs/research/2026-06-22-paperdoll-handoff.md
Erik 702058f7d4 docs(D.2b): paperdoll (Sub-phase C) handoff + new-session prompt
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>
2026-06-22 21:03:44 +02:00

12 KiB
Raw Blame History

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)

  1. This doc.
  2. docs/research/2026-06-16-equipment-paperdoll-deep-dive.md — the AUTHORITATIVE research. Full element-id → EquipMask table (§3a, ~25 slots), the viewport mechanism (§5), the wield wire (§4), the reuse analysis (§5c), and the open UNVERIFIED list (§7). Do NOT re-derive what's already CONFIRMED there — cite it.
  3. claude-memory/project_d2b_retail_ui.md — the D.2b SSOT (the shipped log: container-switching, icons, capacity bars, drag-drop; the UiItemSlot/drag-spine/importer facts you'll reuse).
  4. docs/architecture/retail-divergence-register.md — for the AP rows you'll add; and docs/architecture/code-structure.md Rule 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 99102, 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; DatWidgetFactory builds 0x10000031UiItemList; B-Grid confirmed the positions). The work is a new PaperdollController (the gmPaperDollUI::PostInit analogue) that:
    • Maps each slot element-id → EquipMask via the deep-dive §3a table (port GetLocationInfoFromElementID, 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 tracks CurrentlyEquippedLocation per item via WieldObject 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 IItemListDragHandler for the equip slots (reuse the shipped drag-drop spine): OnDragOver accepts iff the dragged item's ValidLocations includes the slot's EquipMask; HandleDropRelease = wield (GetAndWieldItem 0x001A) optimistically + via wire; dragging a slot's item OUT to the grid = un-wield (PutItemInContainer 0x0019, the SHIPPED path).
  • THE WIRE GAP (build it): GetAndWieldItem (0x001A, payload uint itemGuid; (uint)EquipMask) has no builder/sender in acdream (deep-dive §4a — only the unrelated UI font property 0x1A exists). Add InteractRequests.BuildGetAndWieldItem(seq, itemGuid, equipMask) (mirror BuildPickUp) + WorldSession.SendGetAndWieldItem(...) (mirror SendPutItemInContainer). ACE: GameActionGetAndWieldItem.Handle (item + EquipMask). holtburger commands.rs:808 confirms 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 EntitySpawnAdapterAnimatedEntityState (palette/part/hidden-part overrides from ObjDesc). The doll = "build a WorldEntity from 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 on ObjDescEvent 0xF625 = rebuild the entity's overrides.
  • The new widget = UiViewport (registers at dat Type 0xD, leaf). It needs a render-into-rect seam: a Core IUiViewportRenderer interface 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:~2208 pins 0x100001CD (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/CapacityFill overlays. The equip slot is the single-cell case.
  • The drag-drop spine (IItemListDragHandler, UiRoot BeginDrag/FinishDrag, UiItemSlot drag 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 shipped PutItemInContainer path).
  • ClientObjectTable.CurrentlyEquippedLocation per item — the equipped-state source for Slice 1 (no new parse needed for MVP).
  • IconComposer — item icons (incl. the IsThePlayer constant-backpack special; deep-dive notes the paperdoll icon path).
  • EntitySpawnAdapter / AnimatedEntityState — the char render path Slice 2 reuses.
  • ObjDescEvent 0xF625 parse + 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 sourceCurrentlyEquippedLocation (have it) is enough for MVP; the deep-dive §3c/§7 notes the richer InventoryPlacement equipped list (PlayerDescription equipped section) is not surfaced by PlayerDescriptionParser (partial). Only extend the parser if MVP needs priority/coverage resolution (overlapping armor/clothing) — likely Slice-2-or-later.
  • GetAndWieldItem placement/auto-slot — does the EquipMask come purely from the drop-target slot (like the doll's GetLocationInfoFromElementID), and how does auto-wield (drop on the doll body, not a specific slot) pick the slot? holtburger resolve_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 0x800000 is 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; the IUiViewportRenderer seam is the crux.

New-session prompt

Continue acdream's D.2b retail-UI inventory arc on branch claude/hopeful-maxwell-214a12 (tip 82ab0e0; main is a clean ff ancestor). READ FIRST: docs/research/2026-06-22-paperdoll-handoff.md, then its "Read first" list (esp. the deep-dive docs/research/2026-06-16-equipment-paperdoll-deep-dive.md and claude-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 / gmPaperDollUI 0x21000024) to live equipped-item data and make them drag-drop wield/unwield targets — a new PaperdollController (the gmPaperDollUI::PostInit analogue) that maps element-id → EquipMask (deep-dive §3a table), populates each slot's icon from the item whose CurrentlyEquippedLocation == that slot's EquipMask, and registers as the equip-slots' IItemListDragHandler. Build the wire gap: GetAndWieldItem 0x001A (itemGuid + EquipMask) — InteractRequests.BuildGetAndWieldItem + WorldSession.SendGetAndWieldItem (mirror BuildPickUp/SendPutItemInContainer); wield = optimistic + rollback (reuse MoveItemOptimistic); unwield = the shipped PutItemInContainer 0x0019 path. 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 doll UiViewport Type 0xD + the Core→App IUiViewportRenderer seam) 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 shipped UiItemSlot + drag-drop spine + optimistic-move machinery; don't rebuild them.