docs(D.2b): chat-window re-drive session handoff (Plan 2 chat piece)

Captures: current hand-authored chat window (UiNineSlicePanel + UiChatView,
read-only, debug font), the importer toolkit to reuse, the retail gmMainChatUI
oracles, the open design questions (scope / behavioral widgets / dat font), and
the first research step (find the chat LayoutDesc id). Resume via brainstorming.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-15 19:07:05 +02:00
parent 0474feb6ca
commit 50758d4795

View file

@ -0,0 +1,135 @@
# Chat-window re-drive — session handoff (2026-06-15)
**Status:** brainstorm STARTED (context gathered, design questions open) — not yet
designed or implemented. Resume with `superpowers:brainstorming`.
**Branch:** `claude/hopeful-maxwell-214a12`**continue UI work HERE** (the user's
call: UI stays on this branch; dungeon lighting / M1.5 goes to a *separate* branch
off `main`, it's unrelated and easy to merge). This branch is already current with
`main` (merged `5ac9d8c`).
---
## Where we are (what shipped this session)
**D.2b LayoutDesc importer — Plan 1 SHIPPED + flipped to default + post-flip fixes.**
The vitals window is now data-driven from the dat `LayoutDesc 0x2100006C` (no
per-window graphics code). Read **`claude-memory/project_d2b_retail_ui.md`** (the
SSOT crib) FIRST — it has the full architecture + every correction. Key commits:
- `bf77a23` — flip: importer is the default vitals at `ACDREAM_RETAIL_UI=1`; the
hand-authored `vitals.xml` + the `ACDREAM_RETAIL_UI_IMPORTER` flag were retired.
- `8aa643f` — horizontal resize: edge-anchor mapping corrected to retail
`UIElement::UpdateForParentSizeChange @0x00462640` (`RightEdge==1`=stretch).
- `43064ba` — stamina/mana numbers: `TextRenderer` now draws sprites in
**submission (painter) order** (was per-texture batched → later bars overpainted
their own numbers).
- `34243f2` — number sharpness: `DrawStringDat` **pixel-snaps** each glyph dest.
**The importer toolkit to REUSE (all in `src/AcDream.App/UI/Layout/`):**
- `ElementReader``ElementInfo` POCO + `Merge` (BaseElement/BaseLayoutId
inheritance) + `ToAnchors` (edge-flag → AnchorEdges, decomp-correct).
- `UiDatElement` — generic per-DrawMode sprite renderer (the fallback widget).
- `DatWidgetFactory``Type → widget` hybrid: Type 7→`UiMeter`, 12→skip, else
generic; sets rect + anchors + `ZOrder=ReadOrder`. **Behavioral Types map to a
dedicated widget; the widget CONSUMES the element's children (leaf — importer
does not recurse them).** This is the pattern the chat re-drive extends.
- `LayoutImporter``Import`/`ImportInfos`/`Build`/`BuildFromInfos` + cycle-guarded
`Resolve`. `ImportedLayout.FindElement(id)` for binding by id.
- `VitalsController` — binds live data to widgets by element id (mirrors retail
`gmVitalsUI::PostInit`). The chat controller will mirror this.
- Format reference: **`docs/research/2026-06-15-layoutdesc-format.md`** (ElementDesc
API, Type table, DrawMode, inheritance). NOTE its §4 edge-flag history: the FIRST
reading was inverted; the CORRECT model (per `@0x00462640`) is now in the doc +
`ToAnchors``RightEdge==1`=stretch, `LeftEdge==2`=track-right.
---
## Next task: re-drive the chat window through the importer (Plan 2 chat piece)
Today the chat window is **hand-authored**, not data-driven. The goal mirrors the
vitals re-drive: read the chat window's dat `LayoutDesc`, build it via
`LayoutImporter`, and bind the live chat through a `ChatController`.
### Current chat window (what to reproduce / replace)
- Built in `src/AcDream.App/Rendering/GameWindow.cs` in the `if (_options.RetailUi)`
block (~line 1836, "Retail chat window").
- `UiNineSlicePanel` (hand-authored 8-piece chrome) at `(10,432)`, `440×184`,
`MinWidth 180 / MinHeight 80`, draggable + resizable.
- Hosts a `UiChatView` (`src/AcDream.App/UI/UiChatView.cs`): scrollable transcript,
**bottom-pinned**, mouse-wheel scrollback, **drag-select + Ctrl+C copy + Ctrl+A**,
whole-line vertical clipping. **READ-ONLY** (no input box). Uses the **debug
bitmap font** (`_debugFont`), NOT the dat font. `LinesProvider` polled each frame.
- Data: `ChatVM` (`displayLimit: 200`) → `RecentLinesDetailed()` → per-`ChatKind`
colour via `RetailChatColor(...)` (local static in GameWindow).
### Chat pipeline (already shipped, Phase I — reuse, don't rebuild)
`ChatLog (Core) → ChatVM (UI.Abstractions) → view`; outbound `input →
ChatInputParser → LiveCommandBus → WorldSession`. See
`claude-memory/project_chat_pipeline.md`. The re-drive is a VIEW/layout change; the
pipeline stays.
### Retail chat UI classes (decomp oracles — analogous to gmVitalsUI)
`gmMainChatUI`, `gmFloatyMainChatUI`, `gmFloatyChatUI`, `gmChatOptionsUI`
(`docs/research/named-retail/acclient.h` ~line 54923; `symbols.json` has
`gmMainChatUI::Register` etc.). Chat-layout notes:
`docs/research/retail-ui/05-panels.md:120` (chat window layout) +
`06-hud-and-assets.md:651` (every chat window layout is a `LayoutDesc`).
### FIRST research step (the Task-1 analogue): identify the chat `LayoutDesc` id
The vitals id was `0x2100006C`; the chat window's id is **NOT yet known**. Find it:
- `dump-vitals-layout <datdir> [0xId]` enumerates LayoutDescs (it already lists all
layouts containing given element ids). Use it to scan for the chat window, or grep
the decomp for the layout id referenced by `gmMainChatUI`/`gmFloatyMainChatUI`.
- Then dump it and enumerate its element Types (expect a scroll/list region +
scrollbar, maybe a text-input/edit element + channel tabs) — this drives the
factory/widget work.
---
## Open design questions (resume the brainstorm here)
1. **Scope.** Re-drive the EXISTING read-only window (frame from dat + reuse
`UiChatView` for the transcript, parity with today), OR expand to the FULL retail
chat (input box for typing, channel tabs)? Recommendation to discuss: do the
frame re-drive + transcript first (parity), defer input/tabs to a follow-up —
but confirm with the user.
2. **Behavioral widgets.** The chat dat layout introduces the long-tail Types the
vitals didn't have — Type 5 `ListBox`, Type 0xB `Scrollbar`, maybe Type 0xC
`Text`/edit. Two options:
- **(A, recommended) Hybrid reuse** — like Type-7→`UiMeter`: map the transcript
region's Type → the existing `UiChatView` (which already scrolls/selects/copies);
a `ChatController` binds the tail by element id. Minimal new code; fastest parity.
- **(B) Port faithful widgets** — implement `UiScrollbar`/`UiListBox` per the
decomp so the dat's scrollbar element drives scrolling. More faithful, more work;
better as a later step.
3. **Dat font for the transcript.** Switch `UiChatView` from the debug bitmap font
to the dat font (`UiDatFont`, faithful + now pixel-snapped) — OR keep the debug
font for parity first? `UiChatView`'s measure/selection logic is `BitmapFont`-based,
so a dat-font port is non-trivial (a `UiDatFont` measure/advance path + selection
hit-test rework). Likely a follow-up, not the first cut.
---
## Watchouts / lessons (don't regress these)
- **`TextRenderer` draws sprites in submission order** (`_spriteSegs`). Do NOT revert
to per-texture batching — it overpaints later same-atlas text (the stamina/mana bug).
- **`DrawStringDat` pixel-snaps glyphs.** Keep it (sharp text on resize).
- **Edge-flag/anchor model is `@0x00462640`** (`RightEdge==1`=stretch). The format
doc §4's first reading was inverted; trust the corrected `ToAnchors`.
- **Behavioral widgets are leaf** — the factory's widget consumes the element's dat
children; the importer doesn't recurse into them. Apply the same to the chat
transcript widget.
- **Don't fabricate dat reader internals**`Chorizite.DatReaderWriter` is a NuGet
package (not in `references/`); verify member names via the dump tool / reflection.
## Process for the next session
1. Read `claude-memory/project_d2b_retail_ui.md`, this handoff, and
`docs/research/2026-06-15-layoutdesc-format.md`.
2. Resume `superpowers:brainstorming` — settle scope + behavioral-widget approach
(the 3 questions above), present a design, write the spec.
3. Then `superpowers:writing-plans``superpowers:subagent-driven-development`
(same flow that shipped the vitals importer cleanly).
4. Stay on `claude/hopeful-maxwell-214a12`. Visual checks: launch live (ACE on
`127.0.0.1:9000`) with `ACDREAM_RETAIL_UI=1`; test accounts `testaccount2 /
testpassword2` or `notan / MittSnus81!` (character `+Je`).