From 702058f7d479a34c2bea40c54e5a8fdf49c8a506 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 22 Jun 2026 21:03:44 +0200 Subject: [PATCH] docs(D.2b): paperdoll (Sub-phase C) handoff + new-session prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- docs/research/2026-06-22-paperdoll-handoff.md | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 docs/research/2026-06-22-paperdoll-handoff.md diff --git a/docs/research/2026-06-22-paperdoll-handoff.md b/docs/research/2026-06-22-paperdoll-handoff.md new file mode 100644 index 00000000..5e892362 --- /dev/null +++ b/docs/research/2026-06-22-paperdoll-handoff.md @@ -0,0 +1,80 @@ +# 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 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`; `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.