merge: integrate main (D.2b paperdoll/inventory UI line) into the physics/collision branch
Brings the 134 D.2b UI commits onto the physics/collision development line so main can fast-forward. Today's #149 (BSP-less static collision) + #150 (open doors fully passable) + the full collision/streaming/dense-town-FPS arc meet the paperdoll/inventory work. # Conflicts: # docs/ISSUES.md # docs/architecture/retail-divergence-register.md
This commit is contained in:
commit
01594b4cfd
95 changed files with 17201 additions and 167 deletions
200
docs/research/2026-06-20-window-manager-inventory-handoff.md
Normal file
200
docs/research/2026-06-20-window-manager-inventory-handoff.md
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
# Handoff — window manager → inventory window → paperdoll (the next D.2b core-panels arc)
|
||||
|
||||
**Date:** 2026-06-20
|
||||
**From:** the D.5.3 session that shipped the drag-drop spine (B.1), toolbar shortcut reorder/remove +
|
||||
wire (B.2), and toolbar collapse-to-one-row — all **merged to `main`** (merge commit `abbd97b`;
|
||||
branch `claude/hopeful-maxwell-214a12`, tip `14443e5`). Full suite green (2752) at merge.
|
||||
**Line numbers below WILL drift — grep the symbol, don't trust the line.**
|
||||
|
||||
---
|
||||
|
||||
## 0. What just shipped (the foundation the next work builds on)
|
||||
|
||||
This is now on `main` and visually confirmed:
|
||||
|
||||
- **Drag-drop spine (B.1)** — the shared widget-level drag machine. `UiRoot` holds the device-level
|
||||
drag (BeginDrag/UpdateDragHover/FinishDrag, ghost snapshotted at begin, drop delivered only on a real
|
||||
hit). `UiElement` has `GetDragPayload()`/`GetDragGhost()`/`IsDragSource` virtuals (UiRoot stays
|
||||
item-agnostic). `UiItemSlot` is drag source + drop target + accept/reject overlay; `UiItemList` owns
|
||||
a registered `IItemListDragHandler { OnDragLift; OnDragOver; HandleDropRelease }`. Payload =
|
||||
`ItemDragPayload(ObjId, SourceKind, SourceSlot, SourceCell)`. Spec/plan:
|
||||
`docs/superpowers/{specs,plans}/2026-06-20-d2b-drag-drop-spine*.md`.
|
||||
- **Toolbar shortcut drag (B.2)** — retail **remove-on-lift / place-on-drop / no-restore**.
|
||||
`ToolbarController : IItemListDragHandler` drives a `ShortcutStore` (`AcDream.Core.Items`, 18 slots)
|
||||
+ the wire: `InventoryActions.BuildAddShortcut(seq,index,objectGuid,spellId,layer)` /
|
||||
`BuildRemoveShortcut`, sent via `WorldSession.SendAddShortcut`/`SendRemoveShortcut`. Green-cross
|
||||
accept sprite `0x060011FA`. Spec/plan: `docs/superpowers/{specs,plans}/2026-06-20-d2b-toolbar-shortcut-drag*.md`.
|
||||
- **Toolbar collapse** — `UiCollapsibleFrame` bottom-edge snap resize (1-row ↔ 2-row). Spec:
|
||||
`docs/superpowers/specs/2026-06-20-d2b-toolbar-collapse-design.md`.
|
||||
|
||||
**The one B-stream piece still owed:** *drag FROM inventory onto the bar.* B.2 did the within-bar
|
||||
reorder/remove (`SourceKind == ShortcutBar`). The fresh-from-inventory branch (retail `flags & 0xE == 0`
|
||||
→ `CreateShortcutToItem`) needs the inventory window as a drag SOURCE — so it lands WITH Stream C
|
||||
below, not before it.
|
||||
|
||||
---
|
||||
|
||||
## 1. Read first
|
||||
|
||||
- This doc.
|
||||
- `docs/research/2026-06-16-ui-panels-synthesis.md` §4 — **the build plan** for the core panels
|
||||
(build order, widget list, cross-panel wire table). Stream C follows it.
|
||||
- `docs/research/2026-06-16-inventory-deep-dive.md` — `gmInventoryUI 0x21000023` nests paperdoll
|
||||
(`0x21000024`) + backpack (`0x21000022`) + 3D-items (`0x21000021`); backpack burden Meter
|
||||
(`SetLoadLevel`→fill `0x69`); full inventory wire catalog + acdream parse-status.
|
||||
- `docs/research/2026-06-16-equipment-paperdoll-deep-dive.md` — the doll = `UIElement_Viewport`
|
||||
(Type `0xD`) hosting a re-dressed player clone; ~25 equip slots; wield = `GetAndWieldItem 0x001A`
|
||||
(builder MISSING); needs a Core→App `IUiViewportRenderer` seam.
|
||||
- `docs/research/2026-06-18-d53-bar-finish-and-inventory-handoff.md` §C — the predecessor's
|
||||
current-code readiness for the inventory window (most still accurate; deltas noted below).
|
||||
- `claude-memory/project_d2b_retail_ui.md` (the toolkit, now incl. B.1/B.2/collapse) +
|
||||
`claude-memory/project_object_item_model.md` (the D.5.4 `ClientObjectTable`).
|
||||
|
||||
**Mandatory workflow** (CLAUDE.md): grep `docs/research/named-retail/acclient_2013_pseudo_c.txt` by
|
||||
`class::method` → cross-ref ACE/holtburger → pseudocode → port; conformance tests throughout. Each
|
||||
sub-phase gets its own brainstorm → spec → plan → subagent-driven (the flow this arc has been using).
|
||||
|
||||
---
|
||||
|
||||
## 2. Scope — three sub-phases, in order
|
||||
|
||||
| # | Sub-phase | One-liner |
|
||||
|---|---|---|
|
||||
| **A** | **Window manager (open/close + `I`-key)** | The small shared infra to open/close a window + a keybind to toggle the inventory window. Prerequisite for everything below. |
|
||||
| **B** | **Inventory window (Stream C / D.5.5)** | `gmInventoryUI` nesting paperdoll + backpack + 3D-items. The drag SOURCE that completes "drag-from-inventory." |
|
||||
| **C** | **Paperdoll** (`UiViewport`) | The 3D dressed-doll viewport — the heaviest piece, its own Core→App seam. Last. |
|
||||
|
||||
**Out of scope:** the spell bar (a separate spell-casting feature, NOT the action-bar spell shortcuts);
|
||||
the faithful dragbar/resizebar window resize (IA-12 whole-window-drag + the new bottom-edge collapse
|
||||
are the accepted approximations).
|
||||
|
||||
---
|
||||
|
||||
## 3. Sub-phase A — window manager (open/close + `I`-key) — DO THIS FIRST
|
||||
|
||||
**Current state:** every retail-UI window is **always-on at a hardcoded position** — the vitals,
|
||||
chat, and toolbar are added to `_uiHost.Root` unconditionally at startup under `ACDREAM_RETAIL_UI=1`
|
||||
(grep `_uiHost.Root.AddChild` in `GameWindow.cs`; the mounts are in the `_options.RetailUi` block).
|
||||
`UiHost` has **no open/close API** (`src/AcDream.App/UI/UiHost.cs` — it exposes `Root`, `Tick`,
|
||||
`Draw`, `WireMouse/Keyboard` only). Input flows through `InputDispatcher` (Phase K) —
|
||||
`keybinds.json` + `KeyBindings`; the `I` key needs an action + binding.
|
||||
|
||||
**Minimum deliverable:** open/close a top-level window + toggle the inventory window with `I`.
|
||||
Concretely:
|
||||
- A small open/close concept on `UiHost`/`UiRoot` (e.g. register a named window + Show/Hide/Toggle that
|
||||
flips `Visible` and brings it to front via `ZOrder`). Today windows are always `Visible=true`; the
|
||||
manager makes a window default-hidden and toggleable. (z-order/persist can be minimal — `Visible` +
|
||||
a top-most `ZOrder` bump on open.)
|
||||
- An `InputAction` (e.g. `ToggleInventory`) + the retail default `I` keybind (cross-check
|
||||
`docs/research/named-retail/retail-default.keymap.txt`), wired through the existing
|
||||
`InputDispatcher` (see `claude-memory/project_input_pipeline.md`), gated by `WantsKeyboard` so it
|
||||
doesn't fire while a chat input is focused.
|
||||
|
||||
**Brainstorm questions (A):** Is the window manager a property on `UiRoot` (a registry of named
|
||||
top-level windows + Show/Hide/Toggle), or a thin `UiHost` API? How is z-order handled on open (bring
|
||||
to front)? Does `I` toggle (open↔close) or only open (Esc/close-button to close)? Persist open-state
|
||||
across the session (in-memory) only — no disk persistence yet (that's the deferred Plan-2)?
|
||||
|
||||
Faithful retail note: retail windows are opened by the radar/menu buttons + hotkeys and managed by
|
||||
keystone.dll (no decomp) — so this is a toolkit-defined manager (IA-12/IA-15 umbrella), kept minimal.
|
||||
|
||||
---
|
||||
|
||||
## 4. Sub-phase B — inventory window (Stream C / D.5.5) — follow the synthesis §4
|
||||
|
||||
**The design is already written — follow `2026-06-16-ui-panels-synthesis.md` §4.** This section is the
|
||||
**current-code readiness** + what's missing. Don't re-derive the design.
|
||||
|
||||
**READY (and stronger now, post-B.1/B.2):**
|
||||
- `UiItemSlot` + `UiItemList` + `IconComposer` (`src/AcDream.App/UI/`) — the shared item-cell spine,
|
||||
now with the full drag-drop machine (B.1). An inventory grid is `UiItemList` cells + an
|
||||
`IItemListDragHandler` on the controller.
|
||||
- `DatWidgetFactory` registers `0x10000031 → UiItemList` (grep `0x10000031` in
|
||||
`src/AcDream.App/UI/Layout/DatWidgetFactory.cs`).
|
||||
- The data path: `ClientObjectTable.GetContents(containerGuid)` → ordered guids → `Get(guid)` → full
|
||||
icon fields (`src/AcDream.Core/Items/ClientObjectTable.cs`). The object/container model shipped in
|
||||
D.5.4 (`project_object_item_model.md`).
|
||||
- The shortcut wire + `ShortcutStore` (B.2) — so **drag-from-inventory-onto-the-bar** is now a small
|
||||
addition: the inventory cell is a drag SOURCE (its handler's `OnDragLift` does NOT remove — an
|
||||
inventory→bar drag creates a shortcut REFERENCE; the item stays in the pack), and `ToolbarController.
|
||||
HandleDropRelease` gains the `SourceKind == Inventory` branch (place a new shortcut at the target via
|
||||
`SendAddShortcut`, no source-bump) — retail's `flags & 0xE == 0` `CreateShortcutToItem` path.
|
||||
|
||||
**MISSING (the build, in synthesis order — see the 2026-06-18 handoff §C.1-6 for the detailed list):**
|
||||
1. **Window manager** — §3 above (do first).
|
||||
2. **`UiItemList` N-cell grid mode** — currently single-cell (`UiItemList.cs`; `Flush`/`AddItem`
|
||||
skeleton exists, no column-count/pitch/wrap). LIKELY ~6 cols; confirm from
|
||||
`UIElement_ItemList::ItemList_AddItem`.
|
||||
3. **Sub-window mount in `LayoutImporter`** — `gmInventoryUI 0x21000023` nests `gm*UI` children with
|
||||
their own `BaseLayoutId`; the importer only does TEMPLATE inheritance today
|
||||
(`src/AcDream.App/UI/Layout/LayoutImporter.cs`) — instantiating a nested `gm*UI` window is new.
|
||||
4. **Wire gaps** (inventory deep-dive §4.3): builders `DropItem 0x001B`, `GetAndWieldItem 0x001A`,
|
||||
`NoLongerViewingContents 0x0195`; parsers `ViewContents 0x0196`, `SetStackSize 0x0197`,
|
||||
`InventoryRemoveObject`; fix `ParsePutObjInContainer` (drops the 4th `containerType`) +
|
||||
`ParseInventoryServerSaveFailed` (drops `weenieError`); register `ViewContents`/`0x019A`/`0x0052`/
|
||||
`0x00A0` in `GameEventWiring`. (Grep these symbols — none landed this session.)
|
||||
5. **`InventoryController`** (`gm*UI::PostInit` find-by-id pattern): backpack burden Meter
|
||||
(`SetLoadLevel`→fill `0x69`), own-pack list + side-pack list, `ObjDescEvent 0xF625` → re-dress.
|
||||
|
||||
**Brainstorm questions (B):** Sub-window mount — recursive `Import()` in `LayoutImporter`, or an
|
||||
external stitch by the controller? Grid column count (confirm 6 from decomp)? Open by `I`-key only, or
|
||||
also a toolbar inventory button? The inventory cell's drag semantics (lift does NOT remove from the
|
||||
pack — it's a copy-to-shortcut for the bar, but a real MOVE between packs DOES relocate via
|
||||
`PutItemInContainer 0x0019`) — pin the SourceKind→action matrix (deep-dive §5.7 opcode table).
|
||||
|
||||
---
|
||||
|
||||
## 5. Sub-phase C — paperdoll (`UiViewport`, Type 0xD) — heaviest, last
|
||||
|
||||
**The single biggest new piece.** No widget, no factory registration, no renderer today. Needs an
|
||||
`IUiViewportRenderer` **Core→App seam** (structure Rule 2) for a scissored single-entity GL pass — the
|
||||
doll is the local player's ObjDesc-dressed entity in a fixed viewport. ~25 equip slots; the
|
||||
element-id→`EquipMask` map; wield = `GetAndWieldItem 0x001A` (builder still MISSING). Brainstorm
|
||||
separately (it's substantial). Follow `2026-06-16-equipment-paperdoll-deep-dive.md`.
|
||||
|
||||
**Brainstorm questions (C):** Does the doll clone the player `WorldEntity` or build a fresh
|
||||
ObjDesc-dressed `AnimatedEntityState` (the player is the camera, so there's no player-as-renderable
|
||||
today)? `IUiViewportRenderer` timing (post-world pass vs pre-pass)? Scissor infra (the toolkit has no
|
||||
GL scissor yet — see the collapse spec's clipping discussion).
|
||||
|
||||
---
|
||||
|
||||
## 6. Build order + dependency graph
|
||||
|
||||
```
|
||||
A. window manager (open/close + I-key) ← do first; unblocks B
|
||||
│
|
||||
▼
|
||||
B. inventory window (grid + sub-window mount + wire gaps + InventoryController)
|
||||
│ └─ also completes B.2's drag-from-inventory (inventory cell = drag source;
|
||||
│ ToolbarController.HandleDropRelease gains the SourceKind==Inventory branch)
|
||||
▼
|
||||
C. paperdoll (UiViewport + IUiViewportRenderer seam + PaperDollController) ← heaviest, last
|
||||
```
|
||||
|
||||
Critical-path note: A is small and unblocks B; the inventory window (B) is the big one and the drag
|
||||
*source* that closes the B-stream; the paperdoll (C) is the heaviest and independent enough to come
|
||||
last.
|
||||
|
||||
---
|
||||
|
||||
## 7. ⚠ State notes for the fresh session
|
||||
|
||||
- **Start from `main`** (merge `abbd97b`) — it has all of D.5.2 + D.5.4 + this session's D.5.3 B.1/B.2
|
||||
+ the collapse, plus the indoor-lighting handoff (`f7f3e08`, issues `#142`/`#143` are LIGHTING, not
|
||||
UI). Create a new worktree off `main`.
|
||||
- **ISSUES:** `#144` is the (LOW, latent) empty-item-slot click note from B.1; `#141` is the toolbar
|
||||
selected-object display (D.5.3a done; mana/stack deferred). `#142`/`#143` are the lighting issues.
|
||||
- The drag spine's `IItemListDragHandler` + `ItemDragPayload` + `ShortcutStore` + the shortcut wire all
|
||||
exist now — reuse them; don't rebuild.
|
||||
- Don't re-port what WorldBuilder/the toolkit already gives (read `worldbuilder-inventory.md` first for
|
||||
any dat/render work).
|
||||
|
||||
---
|
||||
|
||||
## 8. New-session prompt (paste into a fresh session)
|
||||
|
||||
> Continue acdream's D.2b retail-UI track. **Read `docs/research/2026-06-20-window-manager-inventory-handoff.md` first**, then the 2026-06-16 UI deep-dives + synthesis §4 it references. The drag-drop spine (B.1), toolbar shortcut reorder/remove + wire (B.2), and toolbar collapse all shipped + merged to `main` (`abbd97b`). Next, in order: **(A)** a minimal **window manager** — open/close + an `I`-key toggle for the inventory window (today every retail-UI window is always-on at a hardcoded position; `UiHost` has no open/close API); **(B)** the **inventory window** (`gmInventoryUI` nesting paperdoll/backpack/3D-items — `UiItemList` N-cell grid + sub-window mount in `LayoutImporter` + the inventory wire gaps + `InventoryController`; it's the drag SOURCE that completes B.2's drag-from-inventory via a `SourceKind==Inventory` branch in `ToolbarController.HandleDropRelease`); **(C)** the **paperdoll** `UiViewport` (Type 0xD) 3D doll, with a Core→App `IUiViewportRenderer` seam — heaviest, last. Spell bar DEFERRED. Use the full brainstorm → spec → plan → subagent-driven flow per sub-phase; mandatory grep-named→cross-ref→pseudocode→port for any wire format; conformance tests throughout. Reuse the shipped spine (`IItemListDragHandler`/`ItemDragPayload`/`ShortcutStore`/the AddShortcut/RemoveShortcut wire) — don't rebuild it. Resolve objects via `ClientObjectTable.Get(guid)` / `GetContents(containerGuid)`. Start a new worktree off `main`.
|
||||
|
||||
**MEMORY.md index line:**
|
||||
- [Handoff: window manager → inventory → paperdoll (2026-06-20)](research/2026-06-20-window-manager-inventory-handoff.md) — next D.2b-UI arc after B.1/B.2/collapse (all merged to main `abbd97b`). 3 sub-phases: (A) window manager (open/close + I-key — UiHost has no open/close API today; windows are always-on); (B) inventory window (Stream C, synthesis §4 — UiItemList grid + sub-window mount + wire gaps DropItem 0x1B/GetAndWieldItem 0x1A/ViewContents 0x196 + InventoryController; the drag SOURCE that closes B.2's drag-from-inventory via a SourceKind==Inventory branch); (C) paperdoll UiViewport Type 0xD + Core→App IUiViewportRenderer seam (heaviest). Reuse the shipped drag spine + ShortcutStore + shortcut wire. Spell bar DEFERRED.
|
||||
167
docs/research/2026-06-21-d2b-bcontroller-shipped-next-handoff.md
Normal file
167
docs/research/2026-06-21-d2b-bcontroller-shipped-next-handoff.md
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
# Handoff — D.2b inventory: B-Controller shipped + visually confirmed; B-Wire next
|
||||
|
||||
**Date:** 2026-06-21 (later than the B-Grid handoff of the same date)
|
||||
**From:** the session that shipped **B-Controller** (inventory population + burden meter +
|
||||
captions) and root-caused/fixed the **backdrop wash-out** (a #145 continuation) at the visual gate.
|
||||
**Predecessor:** `docs/research/2026-06-21-d2b-bgrid-shipped-bcontroller-next-handoff.md`
|
||||
(window manager + B-Grid). This doc supersedes it for "what's next."
|
||||
**Branch:** `claude/hopeful-maxwell-214a12`, tip **`c38f098`**. `main` is a clean
|
||||
fast-forward ancestor — ff `main` + branch fresh, or continue this branch.
|
||||
**Line numbers drift — grep the symbol.**
|
||||
|
||||
---
|
||||
|
||||
## 0. What shipped this session (all committed, build + tests green, VISUALLY CONFIRMED)
|
||||
|
||||
**B-Controller** — `InventoryController` (the `gm*UI::PostInit` analogue) binds the imported
|
||||
`gmInventoryUI 0x21000023` tree by id and populates it from `ClientObjectTable`. F12 (with
|
||||
`ACDREAM_RETAIL_UI=1`) now shows your live inventory on the dark backdrop: the "Contents of
|
||||
Backpack" grid, the pack-selector strip, the vertical burden bar ("Burden 17%"), and the captions.
|
||||
|
||||
Commits (after the input handoff's `4e23a7b`):
|
||||
- `5875ac8` `BurdenMath` encumbrance ports (Core)
|
||||
- `fb050ae` `ClientObjectTable.SumCarriedBurden` (Core)
|
||||
- `5e75d2a` `UiMeter` vertical fill (`Vertical`/`FillFromBottom`/`DrawVBar`)
|
||||
- `89c640a` `InventoryController` bind + populate + burden + captions
|
||||
- `383e8b7` follow-up (Populate uses `GetContents` only — dropped a subagent's index-fallback workaround)
|
||||
- `03fbf44` GameWindow wiring (the `InventoryController.Bind` in the inventory-init block)
|
||||
- `7bb4bd3` divergence rows AP-48..51 + ISSUES/roadmap
|
||||
- `1ccf07b` Opus phase-boundary review fixes (burden % saturation + equipped-item filter)
|
||||
- `417b137` **render fixes** (backdrop wash-out [#145 continuation] + captions) — the visual-gate fix
|
||||
- `c38f098` docs (ISSUES/roadmap: visually confirmed + #145 continuation + overflow follow-up)
|
||||
|
||||
Spec: `docs/superpowers/specs/2026-06-21-d2b-inventory-controller-design.md`.
|
||||
Plan: `docs/superpowers/plans/2026-06-21-d2b-inventory-controller.md`.
|
||||
Tests: **App 532 / Core 1526 green.** `InventoryFrameImportProbe` is a real-dat smoke test
|
||||
(gated on `ACDREAM_DAT_DIR`) that **locks the ZLevel fix** (asserts each mounted panel's
|
||||
ZOrder > the backdrop's).
|
||||
|
||||
---
|
||||
|
||||
## 1. Read first
|
||||
|
||||
- This doc.
|
||||
- `claude-memory/project_d2b_retail_ui.md` — the B-Controller SHIPPED entry + DO-NOT-RETRY (updated this session).
|
||||
- `docs/superpowers/specs/2026-06-21-d2b-inventory-controller-design.md` — the B-Controller spec.
|
||||
- `docs/research/2026-06-16-inventory-deep-dive.md` §4 — the inventory wire catalog (for **B-Wire**).
|
||||
- `docs/research/2026-06-16-ui-panels-synthesis.md` §3.3 — the de-duped wire-gap TODO.
|
||||
- `docs/ISSUES.md` — the **#145** entry (now closed via the continuation), the **D.2b-B SHIPPED** entry, and the new **contents-grid overflow** issue.
|
||||
|
||||
---
|
||||
|
||||
## 2. Key discoveries (durable, non-obvious — DO-NOT-RETRY)
|
||||
|
||||
1. **THE BIG ONE — a mounted sub-window slot must keep its OWN frame ZLevel, NOT inherit the
|
||||
base layout root's.** The gm*UI sub-window roots (`0x100001C8` backpack, `0x100001C4`
|
||||
3D-items) carry **ZLevel 1000**. `ElementReader.Merge`'s zero-wins-base rule (`ElementReader.cs:161`,
|
||||
`ZLevel = derived.ZLevel != 0 ? derived.ZLevel : base_.ZLevel`) made the mounted slots
|
||||
(own ZLevel 0) inherit that 1000, and the #145 ZOrder fold (`ReadOrder − ZLevel·10000`)
|
||||
turned 1000 into ZOrder ≈ −10,000,000 — *behind* the frame's Alphablend backdrop
|
||||
(`0x100001D0`, ZLevel 100 → ≈ −1,000,000). The backdrop then **overpainted/washed out** the
|
||||
panels' captions + burden meter + item cells. The **paperdoll** root happens to be ZLevel 0,
|
||||
so it survived — which is why the B-Grid session believed #145 was "done." Fix
|
||||
(`417b137`): `LayoutImporter.Resolve` sets `result.ZLevel = self.ZLevel` inside the
|
||||
`ShouldMountBaseChildren` block. **Root-caused via a one-shot `TextRenderer` sprite-segment-order
|
||||
dump** (the backdrop's segment was drawing AFTER the panel content) + a live ZLevel probe — when
|
||||
"drawn but invisible," dump the actual paint order, don't keep theorizing z-math.
|
||||
|
||||
2. **Burden formula (ported into `Core.Items.BurdenMath`):**
|
||||
`capacity = Str × (150 + clamp(aug×30, 0, 150))` (`EncumbranceSystem::EncumbranceCapacity`
|
||||
`0x004fcc00`); `load = burden / capacity` (`EncumbranceSystem::Load` `0x004fcc40` — the
|
||||
decompiler MANGLED the FP divide to `return burden`; cross-checked vs the existing `ComputeMax`
|
||||
+ the r06 doc); `gmBackpackUI::SetLoadLevel` (`0x004a6ea0`) → fill = `clamp(load/3, 0, 1)`,
|
||||
%text = `floor(load × 100)` **SATURATING at 300%** (computed from the CLAMPED fill, not
|
||||
unclamped — caught in review). `CACQualities::InqLoad` `0x0058f130`. **acdream has no wire
|
||||
`EncumbranceVal` yet** → client-side `SumCarriedBurden` fallback (divergence **AP-48**;
|
||||
B-Wire pins the real wire value).
|
||||
|
||||
3. **`DatWidgetFactory.BuildMeter`'s single-image path is ALREADY correct for the burden meter**
|
||||
(meter-own DirectState `0x0600121D` = back/track; its one Type-3 child `0x0600121C` = fill)
|
||||
per `UIElement_Meter::DrawChildren` `0x0046fbd0` (the child = `m_pcChildImage = GetChildRecursive(this, 2)`).
|
||||
The inventory deep-dive's "0x0600121C = back" label was backwards re: draw role. The only
|
||||
meter change was a **vertical** fill path (`UiMeter.Vertical`/`FillFromBottom`/`DrawVBar`).
|
||||
A near-empty fill at low load is CORRECT (Str 290 → 17% → ~3px of a 58px bar), not a bug.
|
||||
|
||||
4. **Captions: drive the HOST `UiText` directly.** The caption elements (`0x100001D7` "Burden",
|
||||
`0x100001D8` %, `0x100001C5` "Contents of Backpack") resolve to `UiText`. A nested *child*
|
||||
`UiText` does NOT paint — `AttachCaption` now sets the host UiText's `LinesProvider`/`Centered`/`DatFont`.
|
||||
|
||||
5. **Equipped items excluded** from the pack grid + selector (`CurrentlyEquippedLocation != None`)
|
||||
— a mid-session self-wield routes them through `MoveItem(item, WielderGuid=player)` into
|
||||
`GetContents(player)`; retail's gm3DItemsUI shows pack contents only.
|
||||
|
||||
---
|
||||
|
||||
## 3. Current visual state (F12, `ACDREAM_RETAIL_UI=1`)
|
||||
|
||||
Renders correctly: dark backdrop BEHIND; paperdoll equip slots (generic blue border); backpack
|
||||
strip ("Burden" + % + vertical bar + side-bag cell + main-pack cell); 3D-items "Contents of
|
||||
Backpack" grid populated (6-col). **Expected gaps (all are the follow-ups below):**
|
||||
- Contents grid OVERFLOWS the 96px panel (no scroll) — "part hanging off the window."
|
||||
- Paperdoll equip slots show a generic blue `UiDatElement` border, not per-slot silhouettes → Sub-phase C.
|
||||
- The inventory window starts HIDDEN; F12 toggles it (the auto-open debug hack was reverted).
|
||||
|
||||
---
|
||||
|
||||
## 4. What's next (build order; each gets its own brainstorm → spec → plan → subagent flow)
|
||||
|
||||
**(a) B-Wire — RECOMMENDED NEXT.** Parse the wire `EncumbranceVal` (ACE `PropertyInt 5`) for the
|
||||
player so the burden bar reads the server value (retires **AP-48**, the client-side sum). Plus the
|
||||
other inventory wire gaps catalogued in `2026-06-16-inventory-deep-dive.md §4` + the de-duped TODO
|
||||
in `2026-06-16-ui-panels-synthesis.md §3.3`: builders `DropItem 0x001B`, `GetAndWieldItem 0x001A`,
|
||||
`NoLongerViewingContents 0x0195`; parsers `ViewContents 0x0196`, `SetStackSize`, `InventoryRemoveObject`;
|
||||
fix the dropped 4th field on `0x0022` + the dropped error on `0x00A0`; register the unwired parsers;
|
||||
extend `CreateObject` if any icon/stack fields are still discarded. **Mandatory grep-named →
|
||||
cross-ref → pseudocode → port** for each wire format; ACE handlers + holtburger fixtures are the oracles.
|
||||
|
||||
**(b) Contents-grid scroll polish.** The 6-col grid shows ALL loose items, overflowing the 96px
|
||||
panel. Wire the gutter `UiScrollbar` (`0x100001C7`) to the grid + clip the grid to the panel +
|
||||
scroll the overflow. Filed as an OPEN issue in `docs/ISSUES.md`; was out-of-scope for B-Controller
|
||||
(spec §10). Small, self-contained.
|
||||
|
||||
**(c) B-Drag.** Inventory cell as a drag SOURCE (reuse the shipped spine — `UiItemSlot` is already a
|
||||
full drag source/target) + the `SourceKind == Inventory` branch in `ToolbarController.HandleDropRelease`.
|
||||
|
||||
**(d) Sub-phase C — the biggest piece.** Paperdoll: register `0x10000032` (`UiItemSlot`) so equip
|
||||
slots draw per-slot silhouettes (fixes the generic-blue-slot art) + the `UiViewport` (Type `0xD`)
|
||||
3D character doll, which needs a new Core→App `IUiViewportRenderer` seam (Code-Structure Rule 2).
|
||||
Research: `docs/research/2026-06-16-equipment-paperdoll-deep-dive.md`.
|
||||
|
||||
(Then D.5.3-remaining: the toolbar mana meter + stack slider + spell shortcuts; vendor/trade/spellbook.)
|
||||
|
||||
---
|
||||
|
||||
## 5. Caveats / open items
|
||||
|
||||
- **Chat + toolbar #145 z-order eyeball (the input handoff's first ask):** analytically cleared
|
||||
this session — chat's only non-zero-ZLevel element (`0x1000000E`, ZLevel 900) was already the
|
||||
backmost top-level sibling, so the #145 fold can't change its order; the toolbar now honors
|
||||
ZLevel (retail-faithful). No regression seen across ~10 launches. Not separately user-confirmed
|
||||
in isolation, but low-risk — glance once if convenient.
|
||||
- The `InventoryFrameImportProbe` real-dat test SKIPS in CI (no dat). It only asserts when
|
||||
`ACDREAM_DAT_DIR` (or the default `~/Documents/Asheron's Call`) exists.
|
||||
- `ClientObjectTable.GetContents(playerGuid)` returns `IReadOnlyList<uint>` (guids), NOT
|
||||
`ClientObject`s — a prior research agent's claim was wrong; trust the source.
|
||||
- The main-pack cell (`m_topContainer 0x100001C9`) uses a placeholder icon (the main pack ≡ the
|
||||
player, no item IconId) — AP-51. Pin a backpack RenderSurface DID if it bothers you.
|
||||
|
||||
---
|
||||
|
||||
## 6. New-session prompt (paste into a fresh session)
|
||||
|
||||
> Continue acdream's D.2b retail-UI inventory arc. **Read
|
||||
> `docs/research/2026-06-21-d2b-bcontroller-shipped-next-handoff.md` first.** B-Controller
|
||||
> (inventory population + burden meter + captions) shipped + visually confirmed on
|
||||
> `claude/hopeful-maxwell-214a12` (tip `c38f098`; `main` is a clean ff ancestor — ff + branch
|
||||
> fresh, or continue). The backdrop wash-out (a #145 continuation: mounted sub-window slots were
|
||||
> inheriting the base root's ZLevel 1000 → behind the backdrop) is fixed. **Next: B-Wire** — parse
|
||||
> the player's wire `EncumbranceVal` (PropertyInt 5) to retire the client-side burden-sum fallback
|
||||
> (AP-48), plus the inventory wire gaps in `2026-06-16-inventory-deep-dive.md §4` /
|
||||
> `2026-06-16-ui-panels-synthesis.md §3.3`. Use the full brainstorm → spec → plan → subagent-driven
|
||||
> flow; mandatory grep-named → cross-ref → pseudocode → port for every wire format; conformance
|
||||
> tests throughout. After B-Wire: the contents-grid scroll polish (wire gutter `0x100001C7`),
|
||||
> B-Drag (inventory drag SOURCE), then Sub-phase C (paperdoll `UiViewport` doll + `0x10000032`
|
||||
> UiItemSlot per-slot art).
|
||||
|
||||
**MEMORY.md index line** (already added this session):
|
||||
- [Project: D.2b retail UI engine](project_d2b_retail_ui.md) — … Inventory B-Controller SHIPPED 2026-06-21. DO-NOT-RETRY: a mounted sub-window slot must keep its OWN frame ZLevel — inheriting the base root's ZLevel 1000 sinks the panel behind the Alphablend backdrop (the #145-continuation wash-out).
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
# Handoff — D.2b inventory: A + B-Grid shipped, #145 fixed, B-Controller next
|
||||
|
||||
**Date:** 2026-06-21
|
||||
**From:** the session that shipped the **window manager (Sub-phase A)**, **B-Grid** (inventory
|
||||
sub-window mount + grid), and fixed **#145** (ZLevel z-order). All committed on branch
|
||||
`claude/hopeful-maxwell-214a12`, tip `4904ff4`. Full suite green (2767).
|
||||
**Predecessor handoff:** `docs/research/2026-06-20-window-manager-inventory-handoff.md` (the
|
||||
A→B→C arc). This doc supersedes it for "what's next."
|
||||
**Line numbers drift — grep the symbol.**
|
||||
|
||||
---
|
||||
|
||||
## 0. What shipped this session
|
||||
|
||||
- **Sub-phase A — window manager.** `UiRoot` named-window registry (`RegisterWindow` /
|
||||
`ShowWindow` / `HideWindow` / `ToggleWindow` / `BringToFront`), raise-on-click, and F12 →
|
||||
toggle via the **existing** `InputAction.ToggleInventoryPanel`. `UiHost` forwarders. Spec
|
||||
`docs/superpowers/specs/2026-06-20-d2b-window-manager-design.md`; plan
|
||||
`…/plans/2026-06-20-d2b-window-manager.md`. Commits `6409038`/`036db8b`/`882b4dd`/`e3152ad`.
|
||||
Visually confirmed (F12 toggles, click raises, chat-focus gate).
|
||||
- **Cross-check correction:** retail's inventory key is **F12** (`ToggleInventoryPanel`),
|
||||
NOT `I` (`I` = the `Laugh` emote per `retail-default.keymap.txt:246`). The handoff guessed I.
|
||||
|
||||
- **Sub-phase B-Grid — inventory frame.** `UiItemList` N-cell **grid mode**
|
||||
(`Columns`/`CellWidth`/`CellHeight`/`CellOffset`/`LayoutCells`; single-cell toolbar preserved)
|
||||
+ the **sub-window mount** in `LayoutImporter.Resolve` + the `GameWindow` swap of the A
|
||||
placeholder for `LayoutImporter.Import(0x21000023)`. Spec
|
||||
`…/specs/2026-06-20-d2b-inventory-grid-mount-design.md`; plan
|
||||
`…/plans/2026-06-20-d2b-inventory-grid-mount.md`. Commits `4fd4b09`/`85098f5`/`7d971a2`.
|
||||
|
||||
- **#145 — ZLevel z-order (DONE `45a5cc5`).** The importer mapped `ZOrder ← ReadOrder` only, so
|
||||
the gmInventoryUI full-window backdrop (`0x100001D0`, ReadOrder 4) painted over the panels
|
||||
(ReadOrder 1-3). Now `ZOrder = ReadOrder − ZLevel·10000` (higher ZLevel = further back). See §3.
|
||||
|
||||
---
|
||||
|
||||
## 1. Read first
|
||||
|
||||
- This doc + the two B-Grid spec/plan files above.
|
||||
- `docs/research/2026-06-16-ui-panels-synthesis.md` §4 (the core-panels build plan).
|
||||
- `docs/research/2026-06-16-inventory-deep-dive.md` (gmInventoryUI nesting + wire catalog).
|
||||
- `claude-memory/project_d2b_retail_ui.md` (toolkit crib — updated this session).
|
||||
- `claude-memory/project_object_item_model.md` (the `ClientObjectTable` — B-Controller's data source).
|
||||
|
||||
---
|
||||
|
||||
## 2. Decomposition + status
|
||||
|
||||
Sub-phase B was decomposed into four steps (approved this session):
|
||||
|
||||
| Step | Status |
|
||||
|---|---|
|
||||
| **B-Grid** — UiItemList grid + sub-window mount | ✅ **SHIPPED** (mount works for all 3 panels; paperdoll equip-slot positions visually confirmed) |
|
||||
| **B-Controller** — populate grids + burden meter + captions | ⬜ **NEXT** (see §5) |
|
||||
| **B-Wire** — wire gaps (DropItem/ViewContents/…) | ⬜ pending |
|
||||
| **B-Drag** — inventory cell as drag SOURCE (reuse the shipped spine) | ⬜ pending |
|
||||
| **Sub-phase C** — paperdoll doll + per-slot equip art | ⬜ pending (the wrong-slot-graphics fix lives here) |
|
||||
|
||||
---
|
||||
|
||||
## 3. Key discoveries (durable, non-obvious)
|
||||
|
||||
1. **The nested panels are Type-0 inheritors, not game-class Types.** `gmInventoryUI 0x21000023`'s
|
||||
three panels — paperdoll `0x100001CD`, backpack `0x100001CE`, 3D-items `0x100001CF` — are
|
||||
**Type-0, media-less, childless leaves** that nest via `BaseElement`+`BaseLayoutId`
|
||||
(BaseLayoutId → `0x21000024`/`0x21000022`/`0x21000021`). The research agent's "game-class Type
|
||||
+ recursive Import" model was WRONG — confirmed by dumping `0x21000023`.
|
||||
|
||||
2. **The mount = inheritance carrying base children.** `ElementReader.Merge` dropped the base's
|
||||
children. The mount (`LayoutImporter.ShouldMountBaseChildren` + the `Resolve` tail) attaches a
|
||||
base's resolved subtree when the derived element is a pure container (no own children, no own
|
||||
media) inheriting from a base WITH children. Inert for media-bearing inheritors (close
|
||||
button/title) and childless style prototypes (vitals/chat/toolbar).
|
||||
|
||||
3. **ZLevel z-order: higher = further back.** `DatWidgetFactory` folds `ZLevel` into `ZOrder`
|
||||
(`ReadOrder − ZLevel·10000`). ZLevels observed: vitals all **0**; toolbar **0,1,2**; chat
|
||||
**0,900**; inventory **0,10,50,100** (backdrop 100). Vitals unchanged; **chat + toolbar shifted
|
||||
to their dat layering — needs a visual regression confirm (see §6).**
|
||||
|
||||
4. **The paperdoll equip slots need `0x10000032` (UiItemSlot) registration.** They resolve to type
|
||||
`0x10000032`, which the factory does NOT register, so they fall back to a generic `UiDatElement`
|
||||
sprite (the uniform blue border seen on screen) instead of per-slot silhouettes. This + the
|
||||
dressed doll is **Sub-phase C**.
|
||||
|
||||
---
|
||||
|
||||
## 4. Current visual state (F12 with ACDREAM_RETAIL_UI=1)
|
||||
|
||||
The inventory window opens and renders the nested frame: outer chrome + title + the **paperdoll
|
||||
equip slots correctly positioned** over the backdrop. **Gaps (all expected — next sub-steps):**
|
||||
- Equip slots draw a generic border, not per-slot silhouettes → **C**.
|
||||
- Backpack strip + 3D-items contents look empty (the panels mounted; their content is unpopulated)
|
||||
→ **B-Controller**.
|
||||
- Item cells empty everywhere (no items bound) → **B-Controller**.
|
||||
|
||||
---
|
||||
|
||||
## 5. B-Controller — the next step
|
||||
|
||||
**Goal:** make the inventory show your live contents — populate the backpack/3D-items grids from
|
||||
`ClientObjectTable`, drive the burden meter, render the captions. The `gm*UI::PostInit` analogue.
|
||||
|
||||
**READY:**
|
||||
- `ClientObjectTable.GetContents(containerGuid)` → ordered guids; `Get(guid)` → icon fields
|
||||
(`src/AcDream.Core/Items/ClientObjectTable.cs`). The player's own pack is already in the table
|
||||
from the CreateObject stream — **no new wire needed for the own-pack path** (B-Wire is for other
|
||||
containers + live deltas).
|
||||
- `UiItemList` grid mode (B-Grid) — set `Columns`/`CellWidth`/`CellHeight` (read the cell pitch
|
||||
from the nested layout's item template) then `Flush()` + `AddItem()` per item.
|
||||
- `IconComposer` for the cell icons; `UiItemSlot.SetItem(guid, iconTex)`.
|
||||
- `ImportedLayout.FindElement(id)` to bind by id (the burden meter, the lists).
|
||||
- The burden meter is element `0x100001D9` (Type 7 → `UiMeter`); drive fill via
|
||||
`SetAttribute_Float`-equivalent (`UiMeter.Fill`); load ratio = burden/maxBurden.
|
||||
|
||||
**MISSING / to build:**
|
||||
- `InventoryController`: find-by-id bind of the burden meter + the backpack/3D-items lists from
|
||||
`invLayout` (the `ImportedLayout` built in `GameWindow`); subscribe to `ClientObjectTable`
|
||||
`ObjectAdded/Moved/Removed`; rebuild cells.
|
||||
- **Grid params:** dump `0x21000022` (backpack) + `0x21000021` (3D-items) to read the item-list
|
||||
element sizes + cell template (geometry suggests backpack ~1 col, 3D-items ~6 cols of 36×36).
|
||||
- **Type-0 text captions** ("Burden", "Contents of Backpack") don't render today — `UiDatElement`
|
||||
doesn't draw text. Decide: promote those Type-0 elements to `UiText`, or a caption pass.
|
||||
- Burden meter: verify `BuildMeter` produces a valid meter for the backpack meter's child shape
|
||||
(it may differ from the vitals 3-slice shape).
|
||||
|
||||
**Brainstorm Qs (B-Controller):** Where does `InventoryController` get `invLayout` (expose the
|
||||
`ImportedLayout` from `GameWindow`, or import inside the controller)? Cell pitch source (dump vs
|
||||
hardcode 36)? Type-0 caption rendering (UiText promotion vs a caption helper)?
|
||||
|
||||
---
|
||||
|
||||
## 6. Open items / caveats
|
||||
|
||||
- **⚠ Confirm chat + toolbar didn't regress from the #145 ZLevel fix** (chat ZLevel 900, toolbar
|
||||
1,2). The user looked but didn't explicitly confirm; first thing to eyeball next session.
|
||||
- Type-0 text captions don't render (see §5).
|
||||
- The B-Drag step reuses the **already-shipped** drag spine (`UiItemSlot` is a full drag source +
|
||||
drop target; `IItemListDragHandler`/`ItemDragPayload`/`ShortcutStore`/the AddShortcut wire all
|
||||
exist) — do NOT rebuild it. It needs the inventory cell as a SOURCE + the `SourceKind==Inventory`
|
||||
branch in `ToolbarController.HandleDropRelease`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Branch state
|
||||
|
||||
All work is on `claude/hopeful-maxwell-214a12` (tip `4904ff4`), which is `main` (`a391b86`) +
|
||||
this session's A + B-Grid + #145 commits (a clean fast-forward chain — `main` is an ancestor).
|
||||
Decide at the start of the next session: **fast-forward `main` to `4904ff4` and start a fresh
|
||||
worktree off `main`** (matches the prior-arc pattern), or continue this branch.
|
||||
|
||||
---
|
||||
|
||||
## 8. New-session prompt (paste into a fresh session)
|
||||
|
||||
> Continue acdream's D.2b retail-UI inventory arc. **Read
|
||||
> `docs/research/2026-06-21-d2b-bgrid-shipped-bcontroller-next-handoff.md` first.** Sub-phase A
|
||||
> (window manager, F12) and B-Grid (inventory sub-window mount + UiItemList grid) shipped, and #145
|
||||
> (ZLevel z-order — the backdrop now sits behind the panels) is fixed — all on
|
||||
> `claude/hopeful-maxwell-214a12` (tip `4904ff4`; `main` is an ancestor, so ff `main` and branch
|
||||
> fresh, or continue). F12 now shows the nested inventory frame with the paperdoll equip slots
|
||||
> correctly positioned, but the backpack/3D-items panels are empty and the equip slots show a
|
||||
> generic border. **Next: B-Controller** — bind the imported `gmInventoryUI` tree by id and
|
||||
> populate the backpack/3D-items grids from `ClientObjectTable.GetContents(player)` + drive the
|
||||
> burden meter (`0x100001D9`) + render the Type-0 captions. Use the full brainstorm → spec → plan →
|
||||
> subagent-driven flow; mandatory grep-named→cross-ref→pseudocode→port for any wire format;
|
||||
> conformance tests throughout. Reuse the shipped grid + window manager + drag spine — don't
|
||||
> rebuild. **First, eyeball chat + toolbar for any z-order regression from the #145 ZLevel fix.**
|
||||
> After B-Controller: B-Wire (wire gaps), B-Drag (inventory drag SOURCE), then Sub-phase C
|
||||
> (paperdoll doll + per-slot equip art — the wrong-graphics fix; needs `0x10000032` UiItemSlot
|
||||
> registration).
|
||||
|
||||
**MEMORY.md index line:**
|
||||
- [Handoff: D.2b A+B-Grid shipped, #145 fixed, B-Controller next (2026-06-21)](research/2026-06-21-d2b-bgrid-shipped-bcontroller-next-handoff.md) — window manager (F12) + inventory sub-window mount (inheritance-children, NOT game-class Type) + UiItemList grid all shipped; #145 ZLevel z-order fixed (backdrop behind panels). Next B-Controller (populate grids + burden meter from ClientObjectTable). Paperdoll equip slots need 0x10000032; chat/toolbar ZLevel-regression unconfirmed.
|
||||
130
docs/research/2026-06-21-d2b-inventory-finish-handoff.md
Normal file
130
docs/research/2026-06-21-d2b-inventory-finish-handoff.md
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# Handoff — D.2b inventory window finish (Stage 1) shipped + visually confirmed; slot-art + paperdoll next
|
||||
|
||||
**Date:** 2026-06-21 (after B-Wire)
|
||||
**From:** the session that shipped **B-Wire** (inventory wire layer) **and Stage 1 of the
|
||||
inventory window finish** (scroll + frame + vertical resize + 102-slot grid), both visually
|
||||
confirmed at the live gate.
|
||||
**Branch:** `claude/hopeful-maxwell-214a12`, tip **`1be7e65`** (+ this docs commit). `main` is a
|
||||
clean ff ancestor — ff `main` + branch fresh, or continue this branch.
|
||||
**Line numbers drift — grep the symbol.**
|
||||
|
||||
---
|
||||
|
||||
## 0. What shipped this session (all committed, build + full suite green, VISUALLY CONFIRMED)
|
||||
|
||||
Full suite green (`--no-build`, with the client running): **Core.Net 334 / App 543 / UI 425 /
|
||||
Core 1530**, 0 failures.
|
||||
|
||||
### B-Wire — inventory wire layer (`b56087b`→`7c006d1`)
|
||||
The burden bar now reads the server's wire `EncumbranceVal` (PropertyInt 5). Root cause was
|
||||
**delivery, not binding**: login PD dropped the player's int table, live `0x02CD` was unparsed, and
|
||||
`ObjectTableWiring` gated non-UiEffects ints out. Plus the full inventory wire pass (DropItem 0x001B
|
||||
/ GetAndWieldItem 0x001A / NoLongerViewingContents 0x0195 builders; ViewContents 0x0196 /
|
||||
SetStackSize 0x0197 / InventoryRemoveObject 0x0024 parsers; 0x0022 + 0x00A0 field fixes). Spec/plan
|
||||
`docs/superpowers/{specs,plans}/2026-06-21-d2b-inventory-wire*.md`. Detail: the B-Wire SHIPPED entry
|
||||
in `claude-memory/project_d2b_retail_ui.md`.
|
||||
|
||||
### Inventory window finish Stage 1 (`366af0c`→`1be7e65`)
|
||||
Spec/plan `docs/superpowers/{specs,plans}/2026-06-21-d2b-inventory-window-finish*.md`.
|
||||
- **Scroll** — `UiItemList` clip+scroll via the shared `UiScrollable`; gutter scrollbar `0x100001C7`
|
||||
bound like `ChatWindowController`; mouse-wheel.
|
||||
- **Frame** — wrapped in the 8-piece bevel chrome (`UiNineSlicePanel`), like vitals/chat/toolbar.
|
||||
- **Vertical resize** — bottom-edge only (horizontal blocked); the grid + sub-window + scrollbar +
|
||||
backdrop stretch, paperdoll + side-bags pinned; scrollbar thumb reflects view/content.
|
||||
- **102-slot grid** — contents grid pads empty frames to the main-pack capacity (default 102);
|
||||
side-bag column pads to 7.
|
||||
|
||||
---
|
||||
|
||||
## 1. Read first
|
||||
|
||||
- This doc.
|
||||
- `claude-memory/project_d2b_retail_ui.md` — the **Stage 1 SHIPPED** entry + DO-NOT-RETRY (updated this session).
|
||||
- `docs/ISSUES.md` — the **OPEN** "Inventory + equipment slots show the wrong empty-slot background art" issue (the immediate next task) + the two **SHIPPED** entries.
|
||||
- `docs/architecture/retail-divergence-register.md` — AP-52 (side-bag 7 fallback), AP-53 (main-pack 102 fallback), AP-54 (resize approximation), AP-51 (main-pack icon, still open).
|
||||
- `docs/research/2026-06-16-equipment-paperdoll-deep-dive.md` — for Stage 2 (paperdoll).
|
||||
- `.layout-dumps/uiitem-0x21000037.txt` — the UIItem cell template states (for the correct empty-slot sprite).
|
||||
|
||||
---
|
||||
|
||||
## 2. Key discoveries (durable, non-obvious — DO-NOT-RETRY)
|
||||
|
||||
1. **Grid cells must be `Anchors = None`.** `UiElement.DrawSelfAndChildren` runs `ApplyAnchor` on
|
||||
every child AFTER `OnDraw`; with default `Left|Top` anchors it captured each cell's scroll-0
|
||||
position and reset `Top` to it every frame, fighting `UiItemList.LayoutCells`' scroll offset →
|
||||
the grid "escaped the window" when scrolled. `AddItem` now sets `cell.Anchors = None` so
|
||||
`LayoutCells` is the sole authority. **The unit tests missed it by calling `LayoutCells` in
|
||||
isolation, never the draw traversal** — the regression test now drives the `ApplyAnchor`
|
||||
interaction. (Fix `14ea938`.)
|
||||
2. **Scroll reuses `UiScrollable` + a whole-row logical clip (no GL scissor)** — mirrors
|
||||
`UiText.cs:198` (`cell.Visible = top >= 0 && top + h <= Height`). The gutter scrollbar `0x100001C7`
|
||||
is a factory Type-11 `UiScrollbar` inheriting base `0x2100003E`; bind its `Model` to `grid.Scroll`
|
||||
+ the same scrollbar sprite ids the chat scrollbar uses.
|
||||
3. **Vertical-resize cascade via anchors.** Frame: `Resizable=true; ResizeX=false;
|
||||
ResizableEdges=Bottom`. The stretch elements (grid `0x100001C6`, its sub-window `0x100001CF`,
|
||||
scrollbar `0x100001C7`, backdrop `0x100001D0`, + the content root) get `Anchors=Left|Top|Bottom`
|
||||
(fixed width, height tracks parent); paperdoll `0x100001CD` + side-bag `0x100001CE` pinned
|
||||
`Left|Top`. `ComputeAnchoredRect`: Top|Bottom ⇒ `h = parentH − mB − mT`.
|
||||
4. **Capacity-padding pattern.** The contents grid + side-bag column pad empty `UiItemSlot`s up to
|
||||
the pack capacity (player `ItemsCapacity` default 102; `ContainersCapacity` default 7) after the
|
||||
loose-item loop in `InventoryController.Populate`. Padding changed several existing tests' counts
|
||||
(intended — update, don't revert).
|
||||
5. **DO-NOT auto-kill the client.** The user manages client lifecycle: launch with a **plain
|
||||
`dotnet run --no-build`** (no `Get-Process … CloseMainWindow/Stop-Process` preamble). If a rebuild
|
||||
is blocked by a running client (`MSB3021`/`MSB3027` "locked by AcDream.App"), **ASK the user to
|
||||
close it** — do not kill it. (`feedback_dont_kill_clients_before_launch`.) This overrides the
|
||||
CLAUDE.md graceful-close snippet.
|
||||
|
||||
---
|
||||
|
||||
## 3. Current visual state (F12, `ACDREAM_RETAIL_UI=1`)
|
||||
|
||||
Renders correctly + confirmed: 8-piece bevel frame; vertical bottom-edge resize (expand-only,
|
||||
372..560 px); contents grid clips to its panel, scrolls (wheel + gutter scrollbar), shows the full
|
||||
**102-slot** main pack (items + empty frames); side-bag column with bags + empty slots; vertical
|
||||
burden bar; backdrop covers the window. **Known-wrong (next task):**
|
||||
- **Empty-slot background art is wrong** — inventory + side-bag empty cells use the TOOLBAR empty
|
||||
sprite (`UiItemSlot.EmptySprite = 0x060074CF`); paperdoll equip slots show a generic blue
|
||||
`UiDatElement` border. (User-flagged.)
|
||||
- **Main-pack cell has no backpack icon** (AP-51, placeholder `tex=0`).
|
||||
|
||||
---
|
||||
|
||||
## 4. What's next (build order)
|
||||
|
||||
**(a) FIX the empty-slot background art — RECOMMENDED NEXT (the OPEN ISSUE).** Inventory + side-bag
|
||||
empty cells should use the retail pack-slot frame, not the toolbar's `0x060074CF`. Find the correct
|
||||
sprite from the UIItem cell template `0x21000037` (`.layout-dumps/uiitem-0x21000037.txt`) empty
|
||||
state; set it on the cells `InventoryController` creates (an `EmptySprite` override per slot kind, or
|
||||
a controller-set default distinct from the toolbar's). The paperdoll equip slots' per-slot
|
||||
silhouettes are Sub-phase C (below). Small, self-contained; brainstorm → spec → plan as usual.
|
||||
|
||||
**(b) Main-pack backpack icon (AP-51).** The main-pack cell (`m_topContainer 0x100001C9`) should show
|
||||
the equipped-backpack art. Pin the backpack RenderSurface DID (or the equipped-pack `CreateObject`
|
||||
icon path).
|
||||
|
||||
**(c) Sub-phase C — paperdoll.** The 3D character doll via a Core→App `IUiViewportRenderer` seam
|
||||
(`UiViewport`, Type `0xD`) + the `0x10000032` `UiItemSlot` registration so equip slots draw per-slot
|
||||
silhouettes. Research: `2026-06-16-equipment-paperdoll-deep-dive.md`. The heaviest piece.
|
||||
|
||||
**(d) Deferred polish:** container-switching (select a side pack → show its 24-slot contents via the
|
||||
already-parsed `ViewContents 0x0196`); the side-bag column scrollbar (`0x100001CB`, inert — 7 fit);
|
||||
shrink-below-default resize (currently expand-only); the selected-target bar + stack split slider
|
||||
(per the wiki notes the user pasted). B-Drag (drag items between slots) also remains.
|
||||
|
||||
Each gets the full brainstorm → spec → plan → execute flow.
|
||||
|
||||
---
|
||||
|
||||
## 5. New-session prompt (paste into a fresh session)
|
||||
|
||||
> Continue acdream's D.2b retail-UI inventory arc. **Read
|
||||
> `docs/research/2026-06-21-d2b-inventory-finish-handoff.md` first.** B-Wire + the inventory window
|
||||
> finish Stage 1 (scroll, 8-piece frame, vertical resize, 102-slot grid) shipped + visually confirmed
|
||||
> on `claude/hopeful-maxwell-214a12` (tip `1be7e65`; `main` is a clean ff ancestor). **Next: fix the
|
||||
> wrong empty-slot background art** — inventory + side-bag empty cells use the toolbar empty sprite
|
||||
> `0x060074CF`; they need the retail pack-slot frame from the UIItem cell template `0x21000037` (see
|
||||
> `.layout-dumps/uiitem-0x21000037.txt`). Then the main-pack backpack icon (AP-51), then Sub-phase C
|
||||
> (paperdoll `UiViewport` doll + `0x10000032` per-slot equip silhouettes). Use the full brainstorm →
|
||||
> spec → plan → subagent/inline flow. **DO NOT auto-kill the running client** — launch with plain
|
||||
> `dotnet run --no-build`; if a rebuild is locked by a running client, ask the user to close it.
|
||||
54
docs/research/2026-06-22-container-switching-handoff.md
Normal file
54
docs/research/2026-06-22-container-switching-handoff.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Handoff — D.2b inventory container-switching (next clean win)
|
||||
|
||||
**Date:** 2026-06-22 (banked at the end of the empty-slot-art session, per [[feedback_session_handoff]])
|
||||
**Branch:** `claude/hopeful-maxwell-214a12`, tip `1f8dd7a` (+ this docs commit). `main` is a clean ff ancestor (68 commits behind).
|
||||
**Status:** SCOPED, not started. Brainstorm explored the wire + controller; this captures the design sketch so a fresh session executes it with the live-wire round-trip getting proper attention. Run the full brainstorm → spec → plan → execute flow.
|
||||
**Line numbers drift — grep the symbol.**
|
||||
|
||||
---
|
||||
|
||||
## Why this is the next pick
|
||||
|
||||
After the empty-slot-art arc (inventory contents/side-bag/main-pack empty cells now dat-resolve their art — see [[project_d2b_retail_ui]] + the empty-slot handoff), the side-bag column is **rendered but inert**: clicking a bag does nothing. Container-switching makes it functional AND closes the **AP-56** deferral (the selected-container triangle + green/yellow selection square live here). The user picked this over the paperdoll (Sub-phase C) because the paperdoll's empty equip slots are entangled with the heavy 3D doll viewport (retail empties show the doll), whereas container-switching is self-contained.
|
||||
|
||||
This is the **first inventory feature with live two-way wire interaction** (a `Use` → `ViewContents` round-trip), a step up from the read-only display work so far — hence executing it fresh rather than tired.
|
||||
|
||||
## Read first
|
||||
|
||||
- This doc.
|
||||
- `claude-memory/project_d2b_retail_ui.md` — the D.2b SSOT + the empty-slot-art SHIPPED entry (the resolver + AP-55/AP-56).
|
||||
- `claude-memory/reference_retail_inventory_paperdoll.md` — the player-facing spec: the "preferred pack" auto-fit rules + the 7-side-bag / 102-main-pack capacities.
|
||||
- `docs/architecture/retail-divergence-register.md` — **AP-56** (the selected-container indicator deferred — this phase retires it) + AP-53 (container-switch noted as deferred).
|
||||
|
||||
## What's already in place (the wire layer is DONE)
|
||||
|
||||
- **`InteractRequests.BuildUse` (`0x0036`)** — open/use a container by guid (`src/AcDream.Core.Net/Messages/InteractRequests.cs:38`). This is how retail opens a container.
|
||||
- **`GameEvents.ParseViewContents` (`0x0196`)** — the server's full contents list `(ContainerGuid, [guid, containerType]×N)` (`src/AcDream.Core.Net/Messages/GameEvents.cs:372`). Handler at `GameEventWiring.cs:260`.
|
||||
- **`WorldSession.SendNoLongerViewingContents` (`0x0195`)** — close a container view (`WorldSession.cs:1212`).
|
||||
- **`ClientObjectTable.GetContents(containerId)`** — the per-container item index (`src/AcDream.Core/Items/ClientObjectTable.cs:313`).
|
||||
- **The standing TODO** at `GameEventWiring.cs:256`: `ViewContents` currently does an **additive merge**; container-open must treat it as a **full REPLACE** (flush the container's prior membership first) so server-side removals don't linger. **This phase consumes that TODO.**
|
||||
|
||||
## Design sketch (to be confirmed in the brainstorm)
|
||||
|
||||
1. **Open-container state in `InventoryController`.** Add an `_openContainer` field (default = the player guid / main pack). `Populate` shows `GetContents(_openContainer)` instead of always the player. Repaint when the open container's membership changes (extend `Concerns`).
|
||||
2. **Click a side-bag cell → open it.** The side-bag `UiItemSlot`s already carry the bag guid (bound in `Populate`). On click: set `_openContainer = bagGuid`; `SendUse(bagGuid)` (opens it server-side); when the previous container was a side bag, `SendNoLongerViewingContents(prevGuid)`. Click the main-pack cell (`0x100001C9`) → reset `_openContainer` to the player.
|
||||
3. **`ViewContents 0x0196` → full REPLACE.** Flush the container's membership in `ClientObjectTable` before recording the entries (the TODO). Then `InventoryController` repaints the grid for `_openContainer`.
|
||||
4. **Selected-container indicator (retires AP-56).** Draw the open-container triangle `0x06005D9C` + the green/yellow selection square on the selected side-bag cell — a new `UiItemSlot` "selected" overlay (model it on the existing `DragAccept` overlay in `UiItemSlot.OnDraw`; guard `id != 0` per [[feedback_ui_resolve_zero_magenta]]).
|
||||
5. **Caption.** "Contents of Backpack" → the open container's name when a side bag is selected (the caption host is already driven by `InventoryController.AttachCaption`).
|
||||
|
||||
## Open questions for the brainstorm
|
||||
|
||||
- **The green/yellow selection-square sprite id** — the triangle is `0x06005D9C` (from the container prototype `0x1000033F` child `0x10000450`); the selection square's id is NOT yet found. Dump `0x21000037` / the gmBackpackUI layout (`0x21000022`) for the selected-container state media, or cdb a live retail client with a side bag open.
|
||||
- **Are a side bag's contents pre-known, or do they arrive only on open?** Likely the latter (retail sends contents on open via `ViewContents`, not upfront) — so the `Use` round-trip is required, and `GetContents(bagGuid)` is empty until the reply lands. Confirm against a live capture (WireMCP on `127.0.0.1:9000`, or watch the `ViewContents` handler at the live gate).
|
||||
- **Preferred-pack semantics** (from `reference_retail_inventory_paperdoll`): opening a pack makes it the "preferred" pack for auto-fit. Out of scope for the first cut (display-only switching), but note it.
|
||||
- **Empty-container display** — a side bag with no contents shows an empty 24-slot grid (its `ItemsCapacity`, default 24 per AP-52/the wiki). Pad like the main pack does.
|
||||
|
||||
## Acceptance / scope
|
||||
|
||||
- First cut: click a side bag → its contents show in the grid; the selected bag shows the triangle + selection square; click the main pack → back. `ViewContents` is a full replace. Build + full suite green; **visual gate** (F12, the user clicks bags and confirms the grid switches + the selected indicator).
|
||||
- Retires **AP-56** (selected-container indicator) in the same commit that draws it.
|
||||
- Out of scope: preferred-pack auto-fit, drag-INTO a side bag (that's B-Drag), the side-bag column scrollbar (`0x100001CB`, inert — 7 fit).
|
||||
|
||||
## New-session prompt
|
||||
|
||||
> Continue acdream's D.2b retail-UI inventory arc. **Read `docs/research/2026-06-22-container-switching-handoff.md` first.** The empty-slot art shipped + was visually confirmed (`claude/hopeful-maxwell-214a12`, tip `1f8dd7a`). **Next: inventory container-switching** — click a side bag → show its contents in the grid (via `Use 0x0036` → `ViewContents 0x0196` full-replace), draw the selected-container triangle `0x06005D9C` + green/yellow selection square (retires AP-56), click the main pack to go back. The wire builders/parsers all exist (`BuildUse`, `ParseViewContents`, `SendNoLongerViewingContents`); consume the `GameEventWiring.cs:256` full-REPLACE TODO. Run the full brainstorm → spec → plan → subagent-driven → visual-gate flow. **DO NOT auto-kill the running client** — launch with plain `dotnet run`; if a rebuild is locked, ask the user to close it.
|
||||
91
docs/research/2026-06-22-paperdoll-handoff.md
Normal file
91
docs/research/2026-06-22-paperdoll-handoff.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# 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.**
|
||||
|
||||
> **⚠ SUPERSEDED / CORRECTED 2026-06-23 — Slice 1 SHIPPED (`a4c6852`).** This handoff's "⚠ Correction"
|
||||
> section below ("empty equip slots are TRANSPARENT, no silhouettes") **was WRONG** and cost a visual-gate
|
||||
> iteration. The user-axiom truth (his retail screenshots): the paperdoll has a **"Slots" toggle button
|
||||
> (`0x100005BE` = `m_SlotCheckbox`)**. Button OFF (default) = the **live 3-D character** (the doll — can be
|
||||
> naked) + the non-armor slots (weapon/shield/wand/jewelry/under-clothes); button ON = the doll **hides** and
|
||||
> the **armor slots** become visible. **The "figure"/"silhouette" IS the doll (live render), NOT per-slot
|
||||
> silhouette sprites — there are none.** So the doll `UiViewport` (Type 0xD, `0x100001D5`) + the Slots toggle
|
||||
> = **Slice 2**. Slice 1 (equip-slot bind + wield/unwield + **visible empty-slot frames** + the `EquipMask`
|
||||
> correction + the `PickupEvent` unwield fix) SHIPPED. **The corrected model + the full DO-NOT-RETRY list live
|
||||
> in `claude-memory/project_d2b_retail_ui.md` (the Slice 1 entry) — read that, not the §"⚠ Correction" below.**
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
89
docs/research/2026-06-23-paperdoll-slice2-handoff.md
Normal file
89
docs/research/2026-06-23-paperdoll-slice2-handoff.md
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# Handoff — D.2b Sub-phase C, Slice 2: the paperdoll 3-D doll + the "Slots" toggle
|
||||
|
||||
**Date:** 2026-06-23 (end of the Slice 1 session)
|
||||
**Branch:** `claude/hopeful-maxwell-214a12`. **Slice 1 SHIPPED + merged** — `main` is fast-forwarded to the branch tip (`main == branch` at `6897148`). Slice 2 continues on this branch.
|
||||
**Status:** SCOPED, not started. Run the full **brainstorm → spec → plan → subagent-driven → visual-gate** flow.
|
||||
**Line numbers drift — grep the symbol.**
|
||||
|
||||
---
|
||||
|
||||
## Read first (in this order)
|
||||
|
||||
1. **This doc.**
|
||||
2. **`claude-memory/project_d2b_retail_ui.md`** — the D.2b SSOT. The **"SLICE 1 … SHIPPED 2026-06-23"** entry holds the CORRECTED paperdoll model + the full DO-NOT-RETRY. **It supersedes the visual/look claims in the older docs below.**
|
||||
3. **`docs/research/2026-06-16-equipment-paperdoll-deep-dive.md`** — the AUTHORITATIVE element-id / wire / viewport research: §5 (the viewport mechanism, CONFIRMED), §5c (reuse analysis), §5d + §7 (camera/light immediates + the UNVERIFIED list). **⚠ Caveat:** its §2a "empty slot shows the doll body behind it" and the 2026-06-22 handoff's "transparent / per-slot silhouettes" framing were **WRONG about the LOOK** — see the corrected model below. The element ids, opcodes, and viewport plumbing in it are still good.
|
||||
4. **`docs/architecture/code-structure.md` Rule 2** — Core defines interfaces, App implements; never the reverse. Governs the `IUiViewportRenderer` seam.
|
||||
5. **`src/AcDream.App/UI/Layout/PaperdollController.cs`** — Slice 1's controller (the 21 slot bindings + wield/unwield drag handler you'll extend, not rewrite).
|
||||
6. **`memory/reference_modern_rendering_pipeline.md`** + `EntitySpawnAdapter`/`AnimatedEntityState` — the in-world animated-character path the doll reuses.
|
||||
|
||||
## The CORRECTED paperdoll model (USER AXIOM — confirmed against his retail screenshots, 2026-06-23)
|
||||
|
||||
**There are NO per-slot silhouette sprites.** The "figure" in the paperdoll **IS the live 3-D character** (the doll — it can be naked if nothing is equipped). The panel has **two views toggled by the "Slots" button** (`0x100005BE` = `m_SlotCheckbox`, a `UIElement_Button`):
|
||||
|
||||
- **Button OFF (default) — "doll view":** show the **3-D character** + the **non-armor** slots (under-clothes / weapon / wand / shield / jewelry). The armor slots are hidden.
|
||||
- **Button ON — "slot view":** the **character disappears** and the **armor slots become visible** (the grid).
|
||||
|
||||
It is a **TOGGLE (mutually exclusive: doll XOR armor-slots)**, NOT slots-overlaid-on-the-doll. Verify the exact show/hide split against the decomp (below) + the user's screenshots before coding — do not re-derive the look from the old deep-dive prose.
|
||||
|
||||
**What Slice 1 already did (don't redo):** bound all ~21 equip slots (`PaperdollController`, element-id→`EquipMask` map), wield (`GetAndWieldItem 0x001A`, optimistic + rollback) + unwield (the inventory-grid path + the `PickupEvent` two-table fix), and gave empty slots a **visible frame** (`emptySlotSprite = 0x06004D20`) so all positions are usable. Slice 1 shows **all** slots at once (no toggle, no doll). Slice 2 adds the doll + the toggle on top.
|
||||
|
||||
## Slice 2 — two pieces (each its own spec → plan → implement; the viewport is the crux)
|
||||
|
||||
### A. The "Slots" toggle (the lighter piece — do FIRST, it de-risks the layout)
|
||||
|
||||
Wire the `m_SlotCheckbox` button (`0x100005BE`) to switch between doll-view and slot-view:
|
||||
- **Map each slot as ARMOR vs NON-ARMOR.** Retail's `RemakeCharacterInventory` (decomp `0x004a65c0`) builds `clothingPriorityMask` from the mask **`0x8007fff`** (line 176016) — clothing/jewelry/misc; the armor slots are the `*Armor` `EquipMask` bits (`ChestArmor 0x200 … LowerLegArmor 0x4000`). Confirm the exact partition from `GetLocationInfoFromElementID` (the §3a table) + the toggle handler.
|
||||
- **READ THE TOGGLE HANDLER FIRST:** `gmPaperDollUI::ListenToElementMessage` (decomp `0x004a5c30`, ~line 175593) — the `idMessage == 1` branch (button click, ~line 175628+, NOT yet read this session) is the show/hide logic. The init does `SetAttribute_Bool(m_SlotCheckbox, 0xe, 0)` (line 175585) = start unchecked (doll view). Port the exact `SetVisible` toggling it does on the armor slots + the viewport.
|
||||
- In acdream: `PaperdollController` keeps refs to the armor slots (toggle-hidden in doll view) + the viewport widget; the button's click flips their `Visible`. The non-armor slots stay visible in both views (verify).
|
||||
- **Slice-1 caveat to revisit:** AP-66 (empty slots show a frame) — decide whether the frame stays in slot-view (likely yes) and what an empty NON-ARMOR slot shows in doll-view. The "frames flip to transparent / doll-through" idea in AP-66 was written under the overlay assumption; the TOGGLE model means there is no doll behind the armor slots in slot-view, so the frame likely **stays**. Re-word AP-66 once the toggle behavior is confirmed.
|
||||
|
||||
### B. The 3-D doll `UiViewport` (the heavy piece — the UI↔3D bridge, the real crux)
|
||||
|
||||
A `UiViewport` widget (registers at dat **Type `0xD`**, element **`0x100001D5`**) that renders a re-dressed clone of the local player into the widget's screen rect (a scissored single-entity 3-D pass), shown in doll-view.
|
||||
|
||||
- **The Core→App seam (Code-Structure Rule 2):** define `IUiViewportRenderer` in `AcDream.Core` (or the UI.Abstractions layer), implement in `AcDream.App` (it has GL + `EntitySpawnAdapter`). The widget's `OnDraw` only has a 2-D `UiRenderContext`; the 3-D pass needs a dedicated overlay hook the `UiHost`/`GameWindow` invokes for any `UiViewport` present (NOT inside `OnDraw`). **The exact integration point (after the world pass vs a UI overlay) is DESIGN-OPEN — settle it in the brainstorm** (deep-dive §5d/§7).
|
||||
- **Reuse the existing char path (deep-dive §5c, CONFIRMED):** build a `WorldEntity` from the local player's Setup + current ObjDesc, feed it through `EntitySpawnAdapter`/`AnimatedEntityState` (palette/part/hidden-part overrides), draw it with a fixed camera + one distant light into the rect. Re-dress on `ObjDescEvent 0xF625` (already PARSED) = rebuild the entity's overrides — the C# analog of retail `RedressCreature` (decomp `0x004a5c90`?/line 173990 / 175535).
|
||||
- **Camera/light/heading immediates** (retail `gmPaperDollUI::PostInit`, decomp ~175509-535; raw hex read this session, **decode + VERIFY**):
|
||||
- `SetCamera(viewport, &dir, &pos)` with `dir ≈ (0x3df5c28f=0.12, 0xc019999a=-2.4, 0x3f6147ae=0.88)`, `pos = (0,0,0)`. (Arg order pos-vs-dir UNVERIFIED — `UIElement_Viewport::SetCamera` → `CreatureMode::SetCameraPosition/Direction`.)
|
||||
- `SetLight(viewport, DISTANT_LIGHT, 2.0f, &dir)` with `dir ≈ (0x3e99999a=0.3, 0x3ff33333=1.9, <3rd component is a strncpy/lifter artifact — recover from Ghidra/re-decompile>)`.
|
||||
- `set_heading(191.367905°)` so the doll faces the viewer; idle animation via `m_didAnimation` (`UpdateForRace` `0x004a…`/line 174129 swaps the idle DID per body-type via `DBObj::GetDIDByEnum`); `CreatureMode::UseSharpMode` (sharper mip bias).
|
||||
- **Player-clone vs fresh WorldEntity (UNVERIFIED, deep-dive §7):** retail clones the player `CPhysicsObj` (`makeObject(GetPhysicsObject(player_id))`). acdream's local player is NOT a renderable `WorldEntity` (it's the camera), so LIKELY build a dedicated `WorldEntity` from the player's Setup + ObjDesc and host it in a private viewport scene. Confirm the player's Setup id + current ObjDesc are available client-side (PlayerDescription / CreateObject / the equipped ObjDescEvents).
|
||||
- **Polish, NOT MVP:** part-selection lighting (`ApplyPartSelectionLighting` / `GetSelectionMaskFromObject` / `CreateClickMap`, lines 174034/174762/174636) — the "which armor piece is this?" highlight + the doll click-map; the Aetheria sigil slots (`0x10000595/96/97`, `SetVisible(0)` by default). Defer.
|
||||
|
||||
## What's already in place (reuse, don't rebuild)
|
||||
|
||||
- **`PaperdollController`** — the slot bindings + wield/unwield + the element-id→`EquipMask` map. Extend it with the armor/non-armor partition + the viewport ref + the toggle.
|
||||
- **The mount + import** — the gmPaperDollUI subtree (`0x21000024`) is imported under `0x100001CD` in the inventory frame; the viewport element `0x100001D5` is in the tree (currently skipped — `DatWidgetFactory` Type 12→skip; Type `0xD` is NOT registered yet → register it to `UiViewport`).
|
||||
- **`EntitySpawnAdapter` / `AnimatedEntityState` / `WorldEntity`** — the per-instance animated-character render path (palette/part/hidden-part overrides). The doll's model pipeline.
|
||||
- **`ObjDescEvent 0xF625`** parse + `CreateObject.ReadModelData` (palette/sub-palette/texture/anim-part) — the re-dress input.
|
||||
- **`ClientObjectTable` + the wield wire** (`GetAndWieldItem`, `WieldObject 0x0023` + `ConfirmMove`, `PickupEvent` two-table fix) — all shipped in Slice 1.
|
||||
|
||||
## Open questions / UNVERIFIED (settle in the brainstorm)
|
||||
|
||||
1. **Overlay vs replace** — confirmed REPLACE (toggle) per the user, but verify the exact `SetVisible` set the `ListenToElementMessage idMessage==1` handler toggles (armor slots + viewport; do the non-armor slots stay shown in slot-view?).
|
||||
2. **The `IUiViewportRenderer` integration point** — after the world pass, or a dedicated UI-overlay 3-D pass? Scissor + viewport from the widget's screen rect. Code-Structure Rule 2 = Core interface, App impl.
|
||||
3. **Camera/light immediates** — decode + verify the floats above; recover the corrupted 3rd light component from Ghidra.
|
||||
4. **Player-clone vs fresh `WorldEntity`** — acdream has no player `WorldEntity`; build one from Setup+ObjDesc. Confirm the data is available client-side.
|
||||
5. **AP-66 fate** — does the empty-slot frame stay in slot-view (likely) or change? Re-word the register row once the toggle behavior is confirmed.
|
||||
6. **`0x100001E0 = MissileAmmo 0x800000`** still LIKELY (AP-62) — gate-verify the ammo slot.
|
||||
|
||||
## Decomp anchors (named-retail `acclient_2013_pseudo_c.txt`)
|
||||
|
||||
- `gmPaperDollUI::PostInit` (~175232; the slot/viewport/button init I read this session — `SetVisible(0)` per slot, `SetCamera`/`SetLight`/`UseSharpMode`/`RedressCreature` at 175509-535, `m_SlotCheckbox = 0x100005BE` at 175557).
|
||||
- `gmPaperDollUI::ListenToElementMessage` (~175593; **read the `idMessage==1` branch ~175628+ for the toggle**).
|
||||
- `gmPaperDollUI::RedressCreature` (173990 / 175535; clone + `set_heading(191.37°)` + `set_sequence_animation` + `DoObjDescChangesFromDefault`).
|
||||
- `gmPaperDollUI::UpdateForRace` (174129; per-body-type camera + idle DID).
|
||||
- `gmPaperDollUI::RemakeCharacterInventory` (175983) + `SetUIItemIntoLocation` (175713/175950; populate equipped items — Slice 1 does the equivalent).
|
||||
- `UIElement_Viewport::Create/SetCamera/SetLight/PostInit` + `CreatureMode::Render` (deep-dive §5a/§5b, lines 119029/91665).
|
||||
|
||||
## Acceptance (Slice 2)
|
||||
|
||||
- The "Slots" button toggles: doll-view (3-D character + non-armor slots) ↔ slot-view (armor slots). Matches the user's two screenshots.
|
||||
- The doll renders the re-dressed local player (correct race/gender/equipped gear; naked if nothing equipped), faces the viewer, idles; updates live on equip/unequip via `ObjDescEvent 0xF625`.
|
||||
- Build + full suite green; **visual gate** (user compares to retail). Divergence rows for any approximation; the `IUiViewportRenderer` seam respects Code-Structure Rule 2.
|
||||
|
||||
## New-session prompt
|
||||
|
||||
> Continue acdream's D.2b retail-UI inventory arc on branch `claude/hopeful-maxwell-214a12` (Slice 1 shipped; `main` is ff-merged to the tip). **READ FIRST:** `docs/research/2026-06-23-paperdoll-slice2-handoff.md`, then its "Read first" list — especially the **Slice 1 entry in `claude-memory/project_d2b_retail_ui.md`** (the CORRECTED paperdoll model) and the deep-dive `docs/research/2026-06-16-equipment-paperdoll-deep-dive.md` §5.
|
||||
>
|
||||
> **NEXT: Sub-phase C — the paperdoll, Slice 2.** The corrected model (user axiom): the paperdoll "figure" IS the **live 3-D character** (not silhouettes); the **"Slots" button (`0x100005BE`)** TOGGLES between **doll-view** (3-D character + non-armor slots) and **slot-view** (armor slots) — mutually exclusive, NOT overlaid. Build (A) the **toggle** first — read `gmPaperDollUI::ListenToElementMessage`'s `idMessage==1` branch (~decomp 175628+) for the exact `SetVisible` show/hide of the armor slots + viewport; partition armor vs non-armor slots (armor = the `*Armor` EquipMask bits; clothing/jewelry/weapon = non-armor, cf. `clothingPriorityMask` `0x8007fff`). Then (B) the **3-D doll `UiViewport`** (register dat **Type `0xD`**, element `0x100001D5`): a Core `IUiViewportRenderer` seam (Code-Structure Rule 2 — Core defines, App implements) driving a scissored single-entity 3-D pass keyed to the widget rect; reuse `EntitySpawnAdapter`/`AnimatedEntityState` by building a `WorldEntity` from the local player's Setup + current ObjDesc, re-dressed on `ObjDescEvent 0xF625` (the C# analog of `RedressCreature`); fixed camera + one distant light + `set_heading(191.37°)` + idle anim (decode the `PostInit` immediates ~175509-535 + verify). **Extend the shipped `PaperdollController` — do NOT rewrite the slot bindings/wield/unwield.** The UI↔3-D integration point is DESIGN-OPEN — settle it in the brainstorm. Run the full **brainstorm → spec → plan → subagent-driven → visual-gate** flow. **DO NOT auto-kill the running client** — launch with plain `dotnet run`; if a rebuild is locked, ask the user to close it.
|
||||
Loading…
Add table
Add a link
Reference in a new issue