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>
135 lines
7.9 KiB
Markdown
135 lines
7.9 KiB
Markdown
# 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`).
|