# 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 [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`).