docs(handoff): D.2b B-Controller shipped + visually confirmed; B-Wire next

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-21 18:08:05 +02:00
parent c38f098ec0
commit 1230c07af3

View 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).