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>
7.9 KiB
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 atACDREAM_RETAIL_UI=1; the hand-authoredvitals.xml+ theACDREAM_RETAIL_UI_IMPORTERflag were retired.8aa643f— horizontal resize: edge-anchor mapping corrected to retailUIElement::UpdateForParentSizeChange @0x00462640(RightEdge==1=stretch).43064ba— stamina/mana numbers:TextRenderernow draws sprites in submission (painter) order (was per-texture batched → later bars overpainted their own numbers).34243f2— number sharpness:DrawStringDatpixel-snaps each glyph dest.
The importer toolkit to REUSE (all in src/AcDream.App/UI/Layout/):
ElementReader—ElementInfoPOCO +Merge(BaseElement/BaseLayoutId inheritance) +ToAnchors(edge-flag → AnchorEdges, decomp-correct).UiDatElement— generic per-DrawMode sprite renderer (the fallback widget).DatWidgetFactory—Type → widgethybrid: 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-guardedResolve.ImportedLayout.FindElement(id)for binding by id.VitalsController— binds live data to widgets by element id (mirrors retailgmVitalsUI::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.csin theif (_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.LinesProviderpolled each frame. - Data:
ChatVM(displayLimit: 200) →RecentLinesDetailed()→ per-ChatKindcolour viaRetailChatColor(...)(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 bygmMainChatUI/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)
- Scope. Re-drive the EXISTING read-only window (frame from dat + reuse
UiChatViewfor 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. - Behavioral widgets. The chat dat layout introduces the long-tail Types the
vitals didn't have — Type 5
ListBox, Type 0xBScrollbar, maybe Type 0xCText/edit. Two options:- (A, recommended) Hybrid reuse — like Type-7→
UiMeter: map the transcript region's Type → the existingUiChatView(which already scrolls/selects/copies); aChatControllerbinds the tail by element id. Minimal new code; fastest parity. - (B) Port faithful widgets — implement
UiScrollbar/UiListBoxper the decomp so the dat's scrollbar element drives scrolling. More faithful, more work; better as a later step.
- (A, recommended) Hybrid reuse — like Type-7→
- Dat font for the transcript. Switch
UiChatViewfrom 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 isBitmapFont-based, so a dat-font port is non-trivial (aUiDatFontmeasure/advance path + selection hit-test rework). Likely a follow-up, not the first cut.
Watchouts / lessons (don't regress these)
TextRendererdraws sprites in submission order (_spriteSegs). Do NOT revert to per-texture batching — it overpaints later same-atlas text (the stamina/mana bug).DrawStringDatpixel-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 correctedToAnchors. - 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.DatReaderWriteris a NuGet package (not inreferences/); verify member names via the dump tool / reflection.
Process for the next session
- Read
claude-memory/project_d2b_retail_ui.md, this handoff, anddocs/research/2026-06-15-layoutdesc-format.md. - Resume
superpowers:brainstorming— settle scope + behavioral-widget approach (the 3 questions above), present a design, write the spec. - Then
superpowers:writing-plans→superpowers:subagent-driven-development(same flow that shipped the vitals importer cleanly). - Stay on
claude/hopeful-maxwell-214a12. Visual checks: launch live (ACE on127.0.0.1:9000) withACDREAM_RETAIL_UI=1; test accountstestaccount2 / testpassword2ornotan / MittSnus81!(character+Je).