acdream/docs/research/2026-06-15-chat-window-redrive-handoff.md
Erik 50758d4795 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>
2026-06-15 19:07:05 +02:00

7.9 KiB
Raw Blame History

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-214a12continue 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/):

  • ElementReaderElementInfo POCO + Merge (BaseElement/BaseLayoutId inheritance) + ToAnchors (edge-flag → AnchorEdges, decomp-correct).
  • UiDatElement — generic per-DrawMode sprite renderer (the fallback widget).
  • DatWidgetFactoryType → 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.
  • LayoutImporterImport/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 + ToAnchorsRightEdge==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 internalsChorizite.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-planssuperpowers: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).