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

80 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 `0x10000031``UiItemList`; 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 `EntitySpawnAdapter``AnimatedEntityState` (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 source** — `CurrentlyEquippedLocation` (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.