# Campaign CT — complete chat parity (system + GUI) **Status:** PROPOSED (2026-08-21). Not started. **Goal, set by the user 2026-08-21: complete retail parity for the chat system AND the chat GUI.** Not "fix the green name" — that was the symptom that started the review. The bar is that a retail player sitting down in front of acdream's chat window finds nothing missing and nothing behaving differently. Campaign CH (2026-08-09, closed user-accepted) landed colours, side channels, the 152-verb command registry, the window shell and verbatim `/help`. CT is the pass that closes what CH did not reach. Research notes (all 2026-08-21): `chat-texttag-model.md`, `chat-tagged-name-composition.md`, `chat-tag-click-dispatch.md`, `retail-chat-window-ui.md`, `acdream-text-stack-audit.md`, `acdream-chat-ui-audit.md`. ## Definition of done 1. Every retail chat behaviour is either implemented, or has a divergence- register row saying why not. 2. Every user-visible chat surface has a test that would catch its regression. 3. The chat digest and `docs/ISSUES.md` describe reality (both are stale today). ## What the review established ### The green clickable name is a TEXT-STACK gap, not a chat gap The client sprintfs literal markup into the line — `{name}<\Tell> says, "{text}"`, closing marker a literal backslash — and `UIElement_Text::InqGlyphs @0x00468EA0` parses the brackets while appending, calling `TextTagFactory::MakeTag @0x00478480`. Tags attach **per glyph**; a "run" is emergent (adjacent glyphs with equal tag pointers). A glyph takes the tag colour (property `0x1D`) only when a tag is open AND its `m_type == 0x10000001`, else the line colour (`0x1B`). Only senders with a GUID in `0x50000001..0x6FFFFFFF` are tagged. Colour **measured** from the installed dats (`LayoutDump --colors`), chat `0x2100006F` / transcript `0x10000011`: `P0x1B` = RGB(204,204,204), `P0x1D` = **RGB(0,178,0)**. The tag colour is per-ELEMENT and authored, while the line colour on that same element comes from the runtime chat table — filing "tag green" into the LogTextType table would put it in the wrong place. Click: `UIElement_Text::MouseUp @0x004694F0` → `DeterminePositionFromXY @0x004688F0` → `GlyphList::InqGlyph @0x00473430` → virtual `HandleClick` at tag-vtable `+0x14` → `gmMainChatUI::RecvNotice_TextTag_IIDStringClick @0x004CCE10` → `ChatInterface::StartTell @0x004F41F0`, which writes `"@tell {Name}, "`, takes focus, and shows the entry bar. Clicking a name always opens a TELL — fellowship, allegiance, patron/vassal and named-channel lines all embed the same markup. No hover effect. ### Our side is closer than feared `UiText` **already** draws multi-coloured runs (`TextRun`/`RunsProvider`, used by the character stat panel); it is gated to `OneLine == true`. The draw path needs no renderer work — arbitrary pen X, substring measurement — and `UiText.HitChar` already resolves a click to (line, column). The blocker is that `ChatVM.RecentLinesDetailed()` drops `Sender`/`SenderGuid` one step before the renderer, though `ChatEntry` carries them the whole way. ### The command registry is already at parity All 13 verbs the CH3 research note lists as MISSING were closed by CH4 and verified present 2026-08-21 (`cg`, `soc`, `o`, `co-vassals`, `fellows`, `group`, `party`, `vassal`, `ab`, `guild`, `ct`, `clfg`, `crp`). `/g` correctly resolves to Fellowship, confirmed against the live retail client. That note's "acdream status" columns are stale and now carry a correction banner. ## Slices ### Group A — the tagged-text capability (strict chain, A1→A5) Nothing is user-visible until A4. - **CT-A1** Multi-line text elements carry coloured runs. Additive; the ~50 files using `Line` are untouched. No behaviour change. - **CT-A2** Parse the tag markup into runs with a tag payload, including retail's rule that an unparseable bracket closes the open tag. Pure, unit- testable, no UI. - **CT-A3** Stop flattening: carry sender name + guid through `ChatVM` into spans, and compose retail's markup in the speech handlers behind the player-GUID-range gate. - **CT-A4** Apply the authored `0x1D` tag colour when a tag is open and its type matches. **Names turn green.** - **CT-A5** Sub-line hit-testing and `StartTell`. **Names become clickable.** ### Group B — chat SYSTEM behaviours - **CT-B1** Bound the transcript: 10,000 chars, trim to ~7,500 preferring a newline boundary (`TruncateChatLog @0x004F4290`). Today it grows for the life of the session — a slow leak, not only a fidelity gap. - **CT-B2** Text-replacement macros: typing `/r `, `/t `, `/tell ` rewrites the input to `@tell {LastTeller}, ` on the space keypress (`HandleTextReplacements @0x004F50D0`). The commands already work; the visible expansion does not exist. - **CT-B3** `FilterLanguage`: retail runs chat text through a taboo table and substitutes (`PlayerModule::FilterLanguage` + `TabooTableAdaptor:: CheckCensorsW` inside `AddTextToScroll`). We store the option, ship the bit, show it in Options — and never filter. A decorative toggle. - **CT-B4** The plain-text session chat log (`ClientSystem::s_pLogFile`). We write none. Path and rotation are UNKNOWN — needs research or a live check. ### Group C — chat GUI - **CT-C1** Auto-scroll vs unread: retail samples "was at bottom" BEFORE the line lands; if you had scrolled up it leaves you there and lights the unread indicator (`0x1000048C`), which scrolls to bottom and clears on click. - **CT-C2** Escape in the chat input is a complete no-op — `UiField` has no `Escape` case, and a focused field also suppresses the input dispatcher's fallback, so there is no clear, no defocus and no hotkey passthrough. - **CT-C3** Option-gated timestamp prefix (`%#H:%M:%S `, colour index `0x0C`, grey), gated on `PlayerModule::DisplayTimeStamps()`. - **CT-C4** Input-bar editing parity: clipboard and selection paths (Ctrl+C/X/V, shift-selection) work but are untested; `ToggleMaximize` and the floating-window Close button have zero coverage. ### Group D — hygiene - **CT-D1** Delete the dead ImGui-era `ChatPanel` (never constructed since Campaign V deleted `AcDream.UI.ImGui`), and its three test files, which currently make the real input surface look better covered than it is. - **CT-D2** Reconcile the chat digest and `docs/ISSUES.md`: #358, #362, #363, #367, #372, #379, #380, #382 are DONE in code but still listed open. #359, #360, #361, #366 remain genuinely open. ## Research still owed before the affected slices - The tag-type roster behind `m_type == 0x10000001` — only "Tell" is confirmed; the full set lives in the DAT `EnumMapper` category `0x18`. Blocks nothing in Group A, but decides whether other tag shapes exist. - Whether retail's transcript supports text selection distinctly from the entry field (blocks CT-C4's scope). - The chat log file's path and rotation (blocks CT-B4). - Whether a chat-specific sound cue exists — a grep came back empty, which is weak evidence, not proof of absence. ## Deliberately NOT in scope Item links and the other three tag shapes (`DID`, `IID`, `IIDEnum`). They have no listener in the retail build we target, so porting them would be inventing behaviour. CT-A5's dispatch is generic, so they cost nothing to add later.