Six parallel research lanes on retail's chat text and window behaviour, plus a plan. The headline: the green clickable speaker name is not a chat feature and not a colour, it is a missing capability in the TEXT stack. Retail's client sprintfs literal tag markup into the chat line, and the text element parses the brackets while appending, attaching a ref-counted tag PER GLYPH. A tagged run is emergent: adjacent glyphs whose tag pointers are equal. A glyph takes the tag colour (property 0x1D) only when a tag is open and its type is 0x10000001; otherwise the ordinary line colour (0x1B). The colour itself was the one thing the decomp could not settle — it is authored, not runtime-built — so it was MEASURED out of the installed dats rather than assumed from a screenshot: P0x1D = RGB(0,178,0). That also exposed a trap: the tag colour is per-ELEMENT and authored while the line colour on the same element comes from the runtime chat table, so filing "tag green" into the LogTextType table would put it in the wrong place. Our own audit found the gap is narrower than feared. UiText ALREADY draws multi-coloured runs (the character stat panel uses it); the path is just gated to single-line elements. The draw path needs no renderer work, and HitChar already resolves a click to line+column. The real blocker is that sender identity is destroyed before it reaches the renderer: ChatEntry carries Sender/SenderGuid the whole way, and ChatVM.RecentLinesDetailed drops both. Two findings beyond the original question. Retail BOUNDS its transcript (10,000 chars, trimmed to ~7,500 at a newline) and splits auto-scroll from an unread indicator by sampling "was at bottom" before the line lands — a naive port auto-scrolls forever and leaks for the life of a session. And the chat-UI audit turned up an untracked bug: Escape in the chat input does nothing at all, because UiField has no Escape case and a focused field also suppresses the input dispatcher's fallback. Every lane was instructed to write "UNKNOWN — needs X" rather than guess, and they did; the carried unknowns are listed in the plan rather than papered over. Seven slices proposed, nothing implemented yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
31 KiB
acdream chat UI audit — window controllers, input bar, menus, filters, window management
Scope: acdream's own chat UI implementation (window controllers, view models, input bar, menus, filters, window management). Explicitly OUT of scope per task boundary: retail's glyph tag system, tag click dispatch, and the text rendering stack (owned by a parallel audit) — not covered here beyond incidental mentions needed to explain routing.
All claims below are cited file.cs:line against the current worktree
(C:\Users\erikn\source\repos\acdream\.claude\worktrees\objective-leavitt-0cbd10).
This document supersedes nothing in claude-memory/project_chat_digest.md;
it verifies and extends it against the current code as of 2026-08-21.
1. Inventory — what exists and what each surface owns
| Surface | File | Owns |
|---|---|---|
| Main chat window | src/AcDream.App/UI/Layout/ChatWindowController.cs |
Binds LayoutDesc 0x2100006F (retail gmMainChatUI/ChatInterface, m_eWindowID==8). Transcript (UiText), input (UiField), scrollbar, talk-focus channel menu (UiMenu), Send button, max/min toggle, the four floating-window indicator LEDs (mirror + click), the 8 resize-grip locked/live cosmetic swap seed. |
| Floating chat windows ×4 | src/AcDream.App/UI/Layout/FloatingChatWindowController.cs |
Binds LayoutDesc 0x2100005B (retail gmFloatyChatUI, m_eWindowID 2-5) four times, one FloatingChatWindowController instance per WindowId 1-4. Transcript, input (channel hardcoded to Say), scrollbar, Send button, hardcoded "Chat {windowId}" title, Close button. |
| Chat view-model | src/AcDream.UI.Abstractions/Panels/Chat/ChatVM.cs |
Formats ChatLog entries to display lines (RecentLines/RecentLinesDetailed), owns /framerate//loc client-side output, the ShowSystemMessage/ShowInterfaceText (0x1A→SpewBox) split, and ChatCommandTargetState (last-tell-sender/target) via _commandTargets. |
| Chat submit pipeline | src/AcDream.Runtime/Chat/ChatCommandRouter.cs |
The one Submit chokepoint both ChatWindowController.Bind's Input.OnSubmit (ChatWindowController.cs:339-340) and FloatingChatWindowController.Bind's Input.OnSubmit (FloatingChatWindowController.cs:157) call. |
| Chat parsing / catalog | src/AcDream.Runtime/Chat/ChatInputParser.cs, RetailClientCommandCatalog.cs, RetailCommandHelpTable.cs, RetailChannelTagTable.cs |
Verb resolution, 152-verb registry, /help text. |
| Per-window filter/open state | src/AcDream.Core/Chat/ChatWindowState.cs |
The ONE canonical ChatWindowState (5 windows: id 0 main + 1-4 floaty) both controllers read live — filters, open/closed, ShouldDisplay/TypeIsActive (retail's ChatInterface::TypeIsActive/RecvNotice_DisplayFinalStringInfo). |
| Chat colors | src/AcDream.UI.Abstractions/Panels/Chat/RetailChatColorTable.cs |
34-value RetailLogTextType→RGBA table, hard-coded (matches retail; no user config — confirmed still true, see §2). |
| Input widget | src/AcDream.App/UI/UiField.cs |
Generic editable-field widget; the chat entry is one instance of this, built by DatWidgetFactory.BuildText for the DAT's Type-12 Editable element. |
| Talk-focus / dropdown menu | src/AcDream.App/UI/UiMenu.cs |
Generic dropdown popup widget; the chat channel selector is one instance. |
| Chat-tab Settings surface | src/AcDream.App/UI/Layout/ChatOptionsPageController.cs (+ ChatOptionsDatDefaults.cs) |
Options panel → Chat tab: two opacity sliders + 5 per-window 13-row text-type filter blocks, all live-writing ChatWindowState/RetailWindowOpacityController. |
| Persistence | src/AcDream.App/UI/RetailUiRuntime.cs (SaveChatWindowFilters, SaveChatOpacity, load path ~RetailUiRuntime.cs:1516-1518) + generic RetailWindowLayoutPersistence |
Filters (all 5 windows) + opacity persist to local settings.json; window geometry/open-state persist "for free" once registered under WindowNames.Chat/ChatWindow1..4 (src/AcDream.App/UI/WindowNames.cs:19-23). |
| Dead/legacy surface | src/AcDream.UI.Abstractions/Panels/Chat/ChatPanel.cs |
An IPanel (ImGui-era D.2a stack) chat panel. Never instantiated in src/ — see §6. |
Not in this lane (owned elsewhere per the task boundary): glyph tag
colouring/click dispatch, UiText rendering internals, SpewBox glyph
drawing. Where the input bar or router hands text to the SpewBox
(ChatVM.ShowInterfaceText, ChatVM.cs:177-183) that routing decision is
in-lane; the SpewBox's own rendering is not.
2. Window management
Multiple windows. Five windows total: 1 main (always open, ChatWindowState.MainWindowId=0, ChatWindowState.cs:68,169-175) + 4 floating (independently open/closed, ChatWindowState.cs:69-70,178-206). Confirmed working end-to-end: ChatWindowController.SetIndicatorOpen/BindIndicatorClicks (ChatWindowController.cs:671-700) mirror + drive floating-window visibility from the main window's 4 indicator LEDs, and RetailUiRuntime.ToggleFloatingChatWindow (RetailUiRuntime.cs:1142-1143) is the Alt+1..4 keybind's landing point (KeyBindings.cs:218-221).
Filters. Per-window 64-bit LogTextType bitmask filter (ChatWindowState.GetFilter/SetFilter/ShouldDisplay, ChatWindowState.cs:145-167,230-235), fully live: both controllers read it every transcript rebuild (ChatWindowController.cs:742,776-777; FloatingChatWindowController.cs:261,278-279), and it's user-editable through the Options→Chat tab (ChatOptionsPageController.cs:181-193 five FilterBlockSpec rows, :552-598 BuildFilterBlock). This is MORE complete than the chat digest implied — the digest's "Main's filter IS user-settable" note (digest line 118-119) is confirmed and the floaty filters are settable through the same UI, not just the main window.
Move/resize. The 8 authored resize grips (ChatWindowController.cs:38-43 doc) and the drag/move title-strip import generically via DatWidgetFactory/UiResizeGrip — no per-controller binding code needed (confirmed by the class doc; no resize-specific code exists in either controller beyond AttachWindow/ToggleMaximize).
Maximize/restore (main window only). ChatWindowController.ToggleMaximize (ChatWindowController.cs:537-580) is a faithful port of gmMainChatUI::HandleMaximizeButton @0x004CCE50 (save/restore Y+height, half-parent expansion, up/down growth choice, DAT-constraint clamping) plus CaptureWindowState/RestoreWindowState (ChatWindowController.cs:702-715) for session persistence. Floating windows have no maximize — matches retail (no max/min button authored on 0x2100005B; FloatingChatWindowController.cs has no MaxMinId/ToggleMaximize equivalent, and this is correct, not a gap).
Close (floating windows only). FloatingChatWindowController.Bind's Close-button wiring (FloatingChatWindowController.cs:211-214) calls c.WindowHandle?.Hide() — a straight port of gmFloatyChatUI::ListenToElementMessage @0x004CE330. Main window has no close button (matches retail; ChatWindowState.SetOpen/Toggle are explicit no-ops for window 0, ChatWindowState.cs:177-188,195-206).
Opacity. Two linked sliders (Default/Active), ported with retail's DualHash drag-the-other-value link (ChatOptionsPageController.cs:344-372 doc, :412-451), scoped to exactly the 5 chat windows per issue #379 (see §5 — DONE). Batched persistence (RetailUiRuntime.SaveChatOpacity, RetailUiRuntime.cs:1201-1210) flushes once per discrete edit, not per drag tick.
Persistence. Filters for all 5 windows + both opacity values write to local settings.json on every live edit (RetailUiRuntime.cs:1169-1210) and reload at startup (RetailUiRuntime.cs:1516-1518 for the main window's filter; the floaty load leg is the analogous call in MountFloatingChatWindows, cited by that same comment). Window geometry/open-state ride the generic RetailWindowLayoutPersistence path since all 5 windows are registered under distinct WindowNames entries (WindowNames.cs:19-23). No gap found here — window management persistence is comprehensive.
Known, already-registered divergences (not new findings, listed for completeness):
- AP-187/AP-189 (
docs/architecture/retail-divergence-register.md): floaty filters are local-settings.json-only, no0x1000008Cserver-side wire sync between installs; and the chat log's shared500-entry ring buffer /200-entry display tail (ChatLog.cs:21-22,447-448;InteractionRetainedUiComposition.cs:465) gives every window a shallower effective per-window scrollback than retail's own per-window 10,000-line log — a low-traffic window's messages can be evicted from the shared tail by unrelated high-traffic windows' spam before that window's own filter ever sees them. Behaviorally the accumulate-while-closed and independent-per-window-scroll-position mechanics are correctly reproduced; only the depth differs. - AP-188/#369 (OPEN): floating windows hardcode Send-channel to Say (
FloatingChatWindowController.cs:157) because the floaty LayoutDesc authors no talk-focus menu; whether retail's floaties actually share the main window's last-picked channel is UNRESEARCHED (see §5). - AP-190/#379 (#379 DONE, AP-190 partially retired): opacity scope-to-chat-only is fixed; the digest's "we snap, retail eases 5%-of-range per tick" easing-curve residual is unverified as still true today — UNKNOWN, needs re-check against
RetailWindowOpacityControllerif picked up.
3. Input bar — exactly what UiField supports
Source: src/AcDream.App/UI/UiField.cs, wired per-window at ChatWindowController.cs:329-372 (main) and FloatingChatWindowController.cs:151-175 (floaty). Both controllers configure the SAME widget class with near-identical wiring (the floaty path lacks the talk-focus channel, per §2/#369).
Supported:
- Typing / editing:
InsertChar,Backspace,DeleteForward, held-key auto-repeat for Backspace/Delete/Left/Right (UiField.cs:159-189,683-700, 0.40s delay / 25/s repeat). - Caret movement: Left/Right (
MoveCaret), Home/End (MoveCaretTo), all Shift-extendable whenSelectable(UiField.cs:791-811). No Ctrl+Left/Right word-jump, no Ctrl+Backspace delete-word. - Selection: mouse click+drag (
MouseDown/MouseMove,UiField.cs:749-763), Shift+arrow, Ctrl+A select-all — all three gated behindSelectable(UiField.cs:781,789), which is DAT-authored property0x27on element0x10000016. Confirmed live-DAT-true for the chat input specifically:ChatLayoutConformanceTests.ChatFixture_BuildsSelectableTranscriptAndEditableInputInPlace(tests/AcDream.App.Tests/UI/Layout/ChatLayoutConformanceTests.cs:220-228) assertsinput.Selectable == trueafterChatWindowController.Bind, and neither controller sets it explicitly (ChatWindowController.cs/FloatingChatWindowController.csgrep clean forInput.Selectable) — so this is the real DAT default, not an accidental pass. Not a gap. - Clipboard: Ctrl+C/Ctrl+X gated behind
Selectable(same as above); Ctrl+V (paste) is not gated and always works whenEditable(UiField.cs:265-303,784). Paste strips control characters and normalizes CR/LF for multi-line fields; the chat input is one-line so paste collapses to a single stripped line. - History: 100-entry cap (
UiField.cs:326, sentinel via_historyIndex=-1), Up/Down browse (HistoryPrev/HistoryNext,UiField.cs:330-350,809-810) — a faithful port per the class doc's citation ofChatInterface::ProcessCommand @0x4f5100. - Submit: Enter/KeypadEnter (
UiField.cs:792-802) callsSubmit()→OnSubmit→ clears (ClearOnSubmit, default true) → pushes history (RecordHistory, default true) → releases keyboard focus (FindRoot()?.SetKeyboardFocus(null)) — "exit write mode after sending," matching retail's read-mode/write-mode chat behavior. - Max length: DAT-authored via property
0x1E→UiField.MaxCharacters(DatWidgetFactory.cs:788-789); default0xFFFFif the DAT doesn't author one. Not hardcoded in the controller — correctly deferred to the imported layout. - Focus entry (keyboard):
UiRoot.OnKeyDownhas a special case — when nothing is focused, Tab or Enter/KeypadEnter focusesDefaultTextInput(UiRoot.cs:1059-1071), whichRetailUiRuntime.cs:1587sets to the bound chatInput. This is the actual, working mechanism for "press Enter/Tab to start typing" — it runs entirely insideUiRoot, independent of theInputDispatcher/InputActionsystem.
Missing / gaps found:
- Escape does nothing while the chat input is focused.
UiField.OnEvent'sKeyDownswitch (UiField.cs:790-811) has noKey.Escapecase, so it falls through toreturn false;(implicit end-of-block after the switch,UiField.cs:812). BecauseKeyboardFocus.IsEditControlis true for a focusedUiField(UiField.cs:149),UiRoot.OnKeyDown's modal/root fallback branch (UiRoot.cs:1083-1089) is skipped entirely, and the event falls toWorldKeyFallThrough(UiRoot.cs:1091) — an event nobody subscribes to in production (grep forWorldKeyFallThrough +=acrosssrc/finds only the class's own declaration and aREADME.mdcode sample,src/AcDream.App/UI/UiHost.cs:19,src/AcDream.App/UI/UiRoot.cs:408). Separately,InputDispatcher's own action-routing is gated off entirely whenever a widget holds keyboard focus (_mouse.WantCaptureKeyboard→SilkMouseSource.cs:193→Root.WantsKeyboard→KeyboardFocus is not null,UiRoot.cs:196), soGameplayInputCommandController.HandleEscape(GameplayInputCommandController.cs:238-248, cancel target mode / exit fly mode / close window) never fires either. Net effect: pressing Escape while the chat box has focus is a complete no-op in acdream today — no clear, no defocus, no fallback to a game hotkey. This is not tracked indocs/ISSUES.mdunder any existing chat issue. - No autocomplete / tab-completion of player names, channel tags, or command verbs. Confirmed by exhaustive grep (
autocomplete|tabcomplete|namecompletacrosssrc/) — zero hits.ChatCommandRouter.Submit(ChatCommandRouter.cs:30-179) is pure parse-and-dispatch with no partial-match suggestion path. Whether retail AC's chat box ever had tab-completion is UNKNOWN — not established either way in this pass; flagging the absence, not asserting it's a regression. @titleis a documented no-op (see §4) — the floating window's title bar (FloatingChatWindowController.cs:194-207) is permanently"Chat {windowId}", unaffected by the command that's supposed to set it.- No Ctrl+Left/Right word-jump or Ctrl+Backspace delete-word — minor editing convenience absent from
UiField'sKeyDownswitch entirely (not chat-specific, but the chat input is the surface a user would notice it on most).
4. Known no-ops and stubs (grepped, cited)
| Site | What's disabled |
|---|---|
src/AcDream.Runtime/Chat/ClientCommandId.cs:50-58 (SetChatTitle) + RetailClientCommandCatalog.cs:258-269 (SetTitle definition) |
@title <text> — retail sets the popup chat window's title bar (ClientCommunicationSystem::DoTitle @0x0057A640); acdream's binding "is a pure no-op (the value is neither stored nor consumed — no title-bar chrome exists to render it yet, AP-182)". Confirmed live: FloatingChatWindowController.cs:204 hardcodes $"Chat {windowId}" with no seam for an external override. |
src/AcDream.App/Input/GameplayInputCommandController.cs:208-213 |
InputAction.ToggleChatEntry (Tab, bound at KeyBindings.cs:251) — the switch case's own comment says "IDevToolsGameplayCommands.FocusChatInput() retired... Tab is still consumed here, matching the prior no-op's 'handled' contract." Harmless: UiRoot.OnKeyDown (§3) independently handles Tab-to-focus-chat before/alongside this path, so functionally nothing is lost — but the InputAction/keybind plumbing for it is dead weight that could mislead a future reader into thinking this is the live mechanism. |
src/AcDream.App/Input/GameplayInputCommandController.cs (whole file) |
InputAction.EnterChatMode (Enter, bound at KeyBindings.cs:252) has no case at all in Handle's switch (GameplayInputCommandController.cs:172-235) — falls to default: return false;. Same "harmless because UiRoot does it independently" caveat as ToggleChatEntry above. |
src/AcDream.Runtime/Chat/RetailClientCommandCatalog.cs:634-696 (TryMatchAllegiance) |
9 of 12 @allegiance subcommands (boot/ban/officer/title/motd/name/lock/house/chat/broadcast) unported — issue #360, still OPEN (see §5). |
src/AcDream.Runtime/Chat/RetailCommandHelpTable.cs:319-349 |
@day/@log/@render recognized only by /help <verb>; execution falls through to server passthrough — issue #361, still OPEN (see §5). |
src/AcDream.UI.Abstractions/Panels/Chat/ChatPanel.cs |
Entire IPanel-based class — see §6, dead code, not reachable from any production construction site. |
No other deferred/TODO/stub/placeholder hits inside ChatWindowController.cs, FloatingChatWindowController.cs, or the Panels/Chat/ directory resolve to a genuine behavioral gap beyond what's listed above and in §5 — the remaining grep hits in those files are either doc-comment cross-references to other code's no-ops (e.g. ChatWindowController.cs:230 explaining that the main filter is "not an inert no-op" — i.e. describing something that was FIXED) or historical narration.
5. Open issues — current code status
| Issue | One-line verdict | Evidence |
|---|---|---|
| #358 Ctrl+M mute chord never fires | STALE — DONE. KeyBindings.RetailDefaults() now binds Ctrl+M (KeyBindings.cs, per the fix note); root cause (binding added to the dead AcdreamCurrentDefaults() table) is fixed. Live-client verification of the actual mute effect was still owed at closure time — UNKNOWN whether that connected check ever ran. |
|
#359 0x019E PlayerKilled prints to participants |
STILL OPEN. ChatLog.OnPlayerKilled (ChatLog.cs:188-206) appends the death message unconditionally for every recipient — no player_id == victim || player_id == killer guard exists anywhere in the method or its call site. |
|
#360 @allegiance/@house only port simple subcommands |
STILL OPEN. RetailClientCommandCatalog.TryMatchAllegiance (RetailClientCommandCatalog.cs:656-696) only recognizes hometown/ho and info; every other subcommand falls to AllegianceUnrecognizedSubcommand's refusal text (:689-695). House subcommands correctly passthrough to ACE per the same file's TryMatchHouse comments (:643), but neither dispatcher executes the ~22 unported subcommands locally. |
|
#361 @day/@log/@render recognized in help only |
STILL OPEN. RetailCommandHelpTable.cs:319-349 still carries the "NOT YET IMPLEMENTED in acdream" meta-tail for all three; RetailClientCommandCatalog has no Day/Log/Render client-command definitions with real handlers (only chat verbs actually wired execute; these three fall through to server passthrough, which is a silent no-op against ACE). |
|
| #362 Four CH4 outbound requests had no inbound handler | STALE — DONE, closed 2026-08-09 (ClientCommandResponses.cs parses ChannelIndex/ChannelList/AvailableHouses/AllegianceInfoResponse). |
|
#363 Refusal sites typed 0x00 where retail types 0x1A |
STALE — CLOSED 2026-08-10. ChatVM.ShowInterfaceText/OnInterfaceText seam (ChatVM.cs:159-183) exists and is wired at InteractionRetainedUiComposition.cs:472-473; ChatCommandRouter routes every named site through it (confirmed at ChatCommandRouter.cs:85-98,119-121,154,162). |
|
#366 New-unseen-text indicator (0x1000048C) imports but unwired |
STILL OPEN (narrowed 2026-08-16 — the build/import half is fixed, the behavior half — what triggers it, what a click does — remains un-researched). No controller code references 0x1000048C in ChatWindowController.cs. |
|
| #367 Local-presentation fallbacks land in chat scroll, not SpewBox | STALE — CLOSED 2026-08-10, closed as a side effect of #363 (same seam). | |
| #369 Unconfirmed whether floaty windows share the main window's talk-focus channel | STILL OPEN, unresearched. FloatingChatWindowController.cs:157 hardcodes Say; whether that's retail-correct is not established either way — filed as a research task, not yet picked up. |
|
| #372 Options panel Character/Chat/Config tabs render blank | STALE — DONE (blank-tabs half fixed at the UiTemplateListBox viewport-anchor level, not chat-specific; the tangential "13 Chat-tab filter labels resolve blank" sub-note was fixed by the FilterStringTableId = 0x2300000D correction visible at ChatOptionsPageController.cs:96-105). |
|
| #379 Chat opacity applied to all windows, not just chat | STALE — DONE, RetailWindowOpacityController now scoped to the 5 chat windows only. |
|
| #380 Chat tab opacity sliders missing row captions | STALE — DONE, ChatOptionsPageController.cs:399-407,473-503 wires SetOpacityCaption from ChatOptionsDatCaptions. |
|
| #382 Floating-window indicator buttons invisible until hovered | STALE — DONE, UiButton.TrySetRetailState fix (unrelated file, general UiButton bug that happened to be discovered via the chat indicators). |
Net: of the 12 chat-tagged issues checked, 4 remain genuinely open in code (#359, #360, #361, #366) plus one unresearched design question (#369). The rest closed since the digest's 2026-08-09/10 snapshot but the digest's own "Open" section (still listing #358/#359/#360/#361/#362/#363) is now stale for #358/#362/#363 — worth a digest refresh independent of this audit.
6. Test coverage
Well covered (direct, behavior-level tests exist):
ChatWindowController:tests/AcDream.App.Tests/UI/Layout/ChatWindowControllerTests.cs— bind success/failure, talk-focus specials (Squelch/Tell-to-selected), transcript parent/mode, transcript layout caching + out-of-range LogTextType fallback, input submit →SendChatCmd, channel-change updates submit channel, input-field resize/reflow (both with and without an importedLayoutPolicy), indicator open/closed/cross-window-isolation/out-of-range.FloatingChatWindowController:tests/AcDream.App.Tests/UI/Layout/FloatingChatWindowControllerTests.cs— bind success/failure/invalid-window-id, transcript parent, input parent (floaty row vs main bar), input always-Say, per-window filter subset + filter-change reflection + cache reuse.ChatWindowState: has its own filter/open/ShouldDisplaylogic covered by construction (not independently verified in this pass, but the class is simple enough that the controller tests above exercise it transitively).UiField:tests/AcDream.App.Tests/UI/UiFieldTests.cs— insert/caret, backspace, submit/clear/history-push, empty-submit no-op, history up/down, history 100-cap, two multi-line-after-shrink regression tests (the 2026-07-29 crash class), character filter, select-all-on-focus, read-only field, multi-line Enter-inserts-newline.- Command routing:
ChatCommandRouterTests.cs,ChatInputParserTests.cs,ChatInputParserAtPrefixTests.cs,RetailClientCommandCatalogTests.cs,RetailCommandHelpTableTests.cs,RetailCommandRegistryConformanceTests.cs(bidirectional ownership-rule enforcement across the whole 152-verb registry). - Colors:
RetailChatColorTableTests.cs.
Gaps found — user-visible behaviors with zero automated coverage:
ChatWindowController.ToggleMaximize— no test anywhere calls it or exercisesCaptureWindowState/RestoreWindowState. Grep forToggleMaximize/Maximizacrosstests/returns nothing. The growUp/clamp/DAT-constraint logic (ChatWindowController.cs:537-580, a direct port ofgmMainChatUI::HandleMaximizeButton) is entirely unverified by automation — a regression here would only be caught by a human clicking the max/min button.- Floating window's Close button — no test exercises
FloatingChatWindowController.cs:211-214'sOnClickwiring (WindowHandle?.Hide()). Grep forCloseButtoninFloatingChatWindowControllerTests.csreturns nothing. UiFieldEscape handling (or lack thereof — see §3 finding 1) — no test exists for Escape at all inUiFieldTests.cs; the absence of behavior is untested, meaning it could silently "start working" or silently regress further with no signal either way.UiFieldclipboard (Ctrl+C/X/V) and Shift-selection — none ofUiFieldTests.cs's 13 tests exerciseCopySelection/CutSelection/Paste/Shift+arrow extension.Selectable-gating (§3) is only indirectly confirmed via the DAT-fixture conformance test (ChatLayoutConformanceTests.cs:220-228), which checks the property resolves true, not that copy/cut/select-all actually work once it's true.ChatPanel.csand its whole test suite are exercising dead code.ChatPanel(src/AcDream.UI.Abstractions/Panels/Chat/ChatPanel.cs) implements the old ImGui-eraIPanelcontract from the D.2a stack.AcDream.UI.ImGuino longer exists as a project (deleted at Campaign V slice V11 perCLAUDE.md), and a repo-wide grep fornew ChatPanel(finds only the class's own constructor declaration — no production code anywhere constructs aChatPanel. Its five test files (ChatPanelFocusTests.cs,ChatPanelInputTests.cs,ChatPanelLayoutTests.cs, plus the sharedChatVMCombatTests.cs/ChatVMLastTellSenderTests.cs/ChatVMRetellAndProvidersTests.csthat exerciseChatVMdirectly and remain legitimately live) still compile and pass, which gives a false impression of "chat input is covered" in a naive test-count read — the REAL live input surface isUiField+ChatWindowController, covered separately (and less deeply, per findings 1-4 above). This is worth flagging to whoever next touches chat tests:ChatPanel.csand its three panel-specific test files are candidates for deletion (dead code, not a functioning fallback), not maintenance targets.
7. Prioritized gap list (most user-visible first)
This is the ordering to plan slices from — judgment calls, not a flat dump.
- Escape does nothing in the chat input (§3 finding 1). Every retail player's muscle memory includes "Escape backs out of whatever I'm doing," and chat is the single most-used text-entry surface in the client. Right now it's a dead key while typing — worse than doing nothing wrong, because it silently swallows an action a user expects to work (defocus/clear), and if
WorldKeyFallThroughwere ever wired for something else, it would ALSO be swallowed by the exact-focus branch that already fails to handle it. This is a real, previously-untracked bug (no ISSUES.md entry), high frequency of exposure, small fix surface (add an Escape case toUiField.OnEvent'sKeyDownswitch, decide clear-vs-defocus-vs-both against retail). - #360 —
@allegiance/@housemanagement subcommands (22 of them unported). Highest-traffic gap by command surface area; already tracked, already scoped ("largest single item; deserves its own slice" per the issue's own text), needs byte-level wire verification before implementation (target-name/guid resolution, confirmation dialogs, multi-field payloads) rather than guessing. - #359 — PlayerKilled line double-prints for the victim/killer. Small, well-scoped, single-method fix (
ChatLog.OnPlayerKilledneeds the local-player-guid participant check) with a clear retail citation already in the issue. Low effort, directly visible to anyone who dies or gets a kill in acdream. - #369 — floaty-window channel-sharing research. Currently a design assumption (Say-always) shipped without verification. Low implementation cost either way once researched, but the research itself (
gmCCommunicationSystem's floaty send path) hasn't started. Worth resolving before more chat work builds on the current assumption. ToggleMaximize/Close-button test coverage gap (§6.1/6.2). Not a behavior bug — both features work per the code reading — but zero automated coverage on two interactive, DAT-geometry-dependent code paths (max/min clamping, close-then-reopen) is a latent regression risk given how much chat-adjacent layout churn this codebase has had (8+ chat-parity review rounds in the last two weeks alone).- #366 — new-unseen-text indicator inert. Cosmetic/discoverability only; retail's exact trigger condition is still unresearched, so this can't be fixed correctly without that research first, and its absence doesn't block any other chat behavior.
- #361 —
@day/@log/@render. Genuinely low-value:@dayneeds a renderer hook that doesn't exist yet (bigger than a chat fix),@logwas deliberately deferred (file-handle lifecycle risk across reconnects),@renderhas no acdream render-option surface to bind to. Correctly the lowest priority of the open command-registry gaps. @titleno-op + hardcoded floaty titles (§4). Cosmetic, single command, no other feature depends on it. Fine to bundle with a future title-bar-chrome pass rather than a standalone fix.ChatPanel.csdead-code cleanup (§6.5). Not a behavior gap at all — it's hygiene. Flagging here rather than fixing inline per this audit's report-only scope; worth a small follow-up to delete the class and its now-misleading test files so future coverage audits don't need to re-discover this.- Missing autocomplete / word-jump editing conveniences (§3 findings 2, 4). Lowest priority: unconfirmed whether retail even had these, and even if it did, they're minor efficiency features, not correctness or discoverability gaps.
Appendix: files read for this audit
src/AcDream.App/UI/Layout/ChatWindowController.cssrc/AcDream.App/UI/Layout/FloatingChatWindowController.cssrc/AcDream.Core/Chat/ChatWindowState.cssrc/AcDream.Core/Chat/ChatLog.cssrc/AcDream.UI.Abstractions/Panels/Chat/ChatVM.cssrc/AcDream.UI.Abstractions/Panels/Chat/ChatPanel.cssrc/AcDream.Runtime/Chat/ChatCommandRouter.cssrc/AcDream.Runtime/Chat/ClientCommandId.cssrc/AcDream.Runtime/Chat/RetailClientCommandCatalog.cs(partial)src/AcDream.Runtime/Chat/RetailCommandHelpTable.cs(partial, grep-targeted)src/AcDream.App/UI/UiField.cssrc/AcDream.App/UI/UiMenu.cs(partial)src/AcDream.App/UI/UiRoot.cs(partial — key dispatch + focus)src/AcDream.UI.Abstractions/Input/InputDispatcher.cs(partial)src/AcDream.App/Input/GameplayInputCommandController.cs(partial)src/AcDream.App/Input/InputCaptureSources.cs(partial)src/AcDream.App/UI/Layout/ChatOptionsPageController.cssrc/AcDream.App/UI/RetailUiRuntime.cs(partial — persistence + mount)src/AcDream.App/UI/WindowNames.cssrc/AcDream.App/Composition/InteractionRetainedUiComposition.cs(partial)src/AcDream.App/UI/Layout/DatWidgetFactory.cs(partial — Type-12 field build)tests/AcDream.App.Tests/UI/UiFieldTests.cstests/AcDream.App.Tests/UI/Layout/ChatWindowControllerTests.cstests/AcDream.App.Tests/UI/Layout/FloatingChatWindowControllerTests.cstests/AcDream.App.Tests/UI/Layout/ChatLayoutConformanceTests.cs(partial)docs/ISSUES.md(targeted sections: #358-#382 chat-tagged range)docs/architecture/retail-divergence-register.md(targeted: AP-185 through AP-191)C:\Users\erikn\.claude\projects\C--Users-erikn-source-repos-acdream\memory\project_chat_digest.md