fix(chat): #363 — retail 0x1A typing for command refusals via the interface-text seam

ChatVM gains an OnInterfaceText hook + ShowInterfaceText(text), the
App-layer composition wires it to RuntimeCommunicationState.AddText,
and ChatCommandRouter routes every retail-0x1A command refusal through
it instead of the chat log's 0x00 sink. UI.Abstractions still never
references Runtime directly; unwired hosts (headless, tests) fall back
to the chat log tagged ClientLocal so no text is ever silently lost.

Reclassified per register row AP-183 (DoChannelList/On/Off, DoAllegiance,
DoHouseAvailableList — the last also corrected to retail's own bad-house-
type string instead of a synthesized "Usage:" line) and newly wired two
sites that previously showed nothing at all (DoStupidChannelHack's bare
legacy-channel-verb refusal, DoReply's message-but-no-last-teller
refusal). The generic bad-args fallback now resolves WeenieErrorMessages
0x026 ("That is not a valid command.", retail's HandleFailureEvent(0x26))
instead of synthesizing "Usage: {Usage}". DoSpeaker/DoEndurance/DoTitle
are untouched — already correct at 0x00.

Also closes #367 (DoHelp's "Unknown command" fallback and the degenerate-
prefix refusal now reach the SpewBox too) and retires register row
AP-186, whose own filing proposed exactly this seam shape.

Full Release suite: 12,542 passed / 4 skipped / 0 failed (baseline
12,466/4/0 at ff2784ea).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-10 15:20:57 +02:00
parent ff2784eaa4
commit 09453ecae8
14 changed files with 702 additions and 106 deletions

View file

@ -96,30 +96,53 @@ slice CH4).
## #363 — Chat refusal/usage call sites are typed ClientLocal 0x00 where retail types several 0x1A
**Status:** OPEN — filed 2026-08-09, CH4 REJECT-review, SHOULD-FIX 9.
`ChatVM.ShowSystemMessage`'s single-typed sink (`LogTextType 0x00`,
informational) is correct for most of `ClientCommandController`'s output,
but Campaign CH slice CH4 added roughly 10 new refusal/usage call sites
that retail types `0x1A` (bright red / ClientLocal), not `0x00`:
`DoStupidChannelHack` (the "You must specify the text you wish to say!"
family, registered channel verbs only), `DoChannelList`/`DoChannelOn`/
`DoChannelOff` ("Please specify the channel name."), `DoAllegiance` (the
"Please see @help Allegiance..." refusal this session's Blocker 1 fix
added), `DoHouseAvailableList`, and `DoReply` ("Someone must @tell you
first!"). Three CH4 sites are ALREADY correct because retail itself types
them informational `0x00`: `DoSpeaker`, `DoEndurance`, `DoTitle`.
Separately, retail's own bad-args fallback
(`ClientCommunicationSystem::DoCommand @0x0057E46D`) answers a registered
handler that returns 0 with `HandleFailureEvent(0x26)`, not a local
"Usage: <usage>" line — `ChatCommandRouter.Submit` shows a synthesized
`"Usage: {clientCommand.Usage}"` string instead whenever a catalog
command's `InvalidArgumentsText` is null. Register row: AP-183.
Deliberately NOT fixed this session — re-plumbing every call site to a
typed sink (and porting `HandleFailureEvent(0x26)`'s real text) is larger
than a REJECT-review fix batch; CH5-or-later.
**Status:** CLOSED 2026-08-10. `ChatVM` gained a typed interface-text seam
(`OnInterfaceText` init hook + `ShowInterfaceText(text)`) that the App-layer
composition (`InteractionRetainedUiComposition.CreateRetainedUi`) wires to
`RuntimeCommunicationState.AddText(text, RetailLogTextType.ClientLocal)`
the same SpewBox chokepoint every other interface-text producer uses.
UI.Abstractions still never references Runtime directly (Code Structure
Rules); the hook is the seam. Unwired callers (headless, the automation
probe runner, plain test fixtures) fall back to the ordinary chat log
tagged `ClientLocal`, so no text is ever silently dropped.
Every site AP-183 named now routes through the seam: `DoStupidChannelHack`
("You must specify the text you wish to say!", newly wired — the six
legacy channel verbs previously fell through `ChatInputParser.Parse`'s pure
`return null` shape with NO message at all), `DoChannelList`/`DoChannelOn`/
`DoChannelOff` ("Please specify the channel name.", reclassified),
`DoAllegiance` ("Please see @help Allegiance...", reclassified),
`DoHouseAvailableList` (reclassified AND corrected to retail's own
"Please see @help hslist for more information on how to use this command"
string — verified at `acclient_2013_pseudo_c.txt:381481`/`1029383`,
replacing the acdream-synthesized "Usage: /hslist <house type>" fallback),
and `DoReply` ("Someone must @tell you first!", newly wired for the
"message but no last teller" branch — bare `/r` with no message at all is
a separate retail branch, deliberately out of scope, not named by AP-183).
`DoSpeaker`/`DoEndurance`/`DoTitle` are untouched — already correct at
`0x00` (their text is produced by `ClientCommandController`, not
`ChatCommandRouter`).
The generic bad-args fallback is also fixed: `ChatCommandRouter.Submit`'s
catalog dispatch now falls to `WeenieErrorMessages.Resolve(0x026u, null)`
("That is not a valid command.", the exact port of retail's
`HandleFailureEvent(0x26)`) instead of synthesizing `"Usage: {Usage}"`
verified 5 decompiled handlers (`DoDie`, `DoChannelList`/`On`/`Off`,
`DoAllegiance`, `DoHouseAvailableList`) are ALL `0x1A`, confirming the
uniform routing decision. This also closes #367 (the "Unknown command"
DoHelp fallback and the degenerate-prefix "Unknown command: {verb}."
refusal both now use the same seam) and retires register row AP-186 —
see that row's retirement note.
Tests: `tests/AcDream.UI.Abstractions.Tests/Panels/Chat/ChatVMRetellAndProvidersTests.cs`
(seam wired / null-fallback), `ChatInputParserTests.cs`
(`IsBareRegisteredChannelVerb`/`IsReplyMissingLastTeller` pure-predicate
coverage), `ChatCommandRouterTests.cs` (per-site routing pinned both ways
for every reclassified/newly-wired site, plus a Turbine-only-channel
negative case and a 0x00-site-stays-in-chat sanity check).
**Campaign:** `docs/plans/2026-08-09-chat-parity-campaign.md` (Campaign CH,
CH4 REJECT-review).
CH4 REJECT-review; closed in the goal-window follow-up).
## #365 — Headless host cannot move at head: session quarantines on the first advance tick; world never hydrates
@ -343,46 +366,19 @@ click — not decoded by CH6a.
## #367 — ChatCommandRouter's local-presentation fallbacks type-0x1A text still lands in the chat scroll, never the SpewBox
**Status:** OPEN — filed 2026-08-10, Campaign CH user-gate round 3, while
tracing `ClientCommunicationSystem::DoHelp @0x0057f9e0`'s complete print
sequence for findings (b)/(c). Retail's DoHelp fallback for an unresolved
`/help <verb>` is `AddTextToScroll(u"Unknown command", 0x1A, 1, 0)` — type
`0x1A` (`ClientLocal`) is HARDCODED to the SpewBox, never the chat window
(`docs/research/2026-08-09-chat-retail-interface-text.md` §2.1/§2.2, the
same routing rule Campaign CH user-gate round 2 item 2 already ported for
the portal-space notice). `RuntimeCommunicationState.AddText` (Runtime
layer) implements this rule correctly — `type == ClientLocal` routes to
`SpewBox` only. But `ChatCommandRouter`/`ChatVM` live in
`AcDream.UI.Abstractions`, a layer beneath Runtime that must stay
presentation/Runtime-independent (Code Structure Rules), so they have no
path to the SpewBox at all — every local-presentation fallback
(`RetailCommandHelpTable.UnknownCommand` now; also the pre-existing
"Unknown command: {verb}." command-shaped-input refusal in
`ChatCommandRouter.Submit`'s main body) still writes through
`ChatVM.ShowSystemMessage`, which only ever reaches `ChatLog`. Not a
regression this round — the fallback text was already wrong AND
already routed to the chat window before this round's fix; this round
corrected the TEXT ("Unknown command", byte-exact) and traced the
routing divergence clearly enough to file it. Register row AP-186.
**Fix shape:** either (a) give `ChatVM` (or a sibling in UI.Abstractions)
an optional `Action<string>? OnClientLocalText` hook the App-layer host
wires to `RuntimeCommunicationState.AddText(text, RetailLogTextType.ClientLocal)`
the same way `ChatWindowController`/other retained-UI controllers already
receive delegates from the composition layer, or (b) accept the
divergence permanently as an acdream simplification (all local-presentation
refusals show in the chat window instead of splitting across two surfaces)
and retire AP-186 as an accepted Intentional Architecture row instead. Needs
a product decision, not just an implementation — small either way once
decided.
**Where:** `src/AcDream.UI.Abstractions/Panels/Chat/ChatCommandRouter.cs`;
`src/AcDream.UI.Abstractions/Panels/Chat/ChatVM.cs`;
`src/AcDream.Runtime/Gameplay/RuntimeCommunicationState.cs` (`AddText`, the
correctly-implemented Runtime-layer oracle this should eventually reach).
**Status:** CLOSED 2026-08-10, closed as a side effect of #363's
interface-text seam (fix shape (a) from this issue's own filing).
`ChatVM.OnInterfaceText` is exactly the hook this issue asked for; both
named fallbacks (`RetailCommandHelpTable.UnknownCommand` in
`ChatCommandRouter.EmitVerbHelp`, and the degenerate-prefix "Unknown
command: {verb}." refusal in `ChatCommandRouter.Submit`'s main body) now
call `vm.ShowInterfaceText(...)` instead of `vm.ShowSystemMessage(...)`,
reaching the SpewBox through `RuntimeCommunicationState.AddText` via the
App-layer composition wiring. See #363's closure note for the full
mechanism and test list. Register row AP-186 retired in the same commit.
**Campaign:** `docs/plans/2026-08-09-chat-parity-campaign.md` (Campaign CH,
user gate round 3).
user gate round 3; closed in the goal-window follow-up).
## #364 — Three `/help` group topics still partial: HelpStupidChannelHack unresolved

File diff suppressed because one or more lines are too long

View file

@ -288,6 +288,7 @@ implementer per slice against a pinned contract (per
| User gate round 3 | `98de4f5a` | Debug (all projects): 12,329 passed / 4 skipped / 1 failed (pre-existing #351 Debug-only flake — reproduces identically on the pristine pre-round-3 commit, not a regression); Release (every project reachable while a live `AcDream.App.exe` client — PID 15064, must not be killed per project policy — holds its own Release binaries locked, blocking `AcDream.App`/`AcDream.App.Tests`/`AcDream.Core.Tests` specifically): `AcDream.UI.Abstractions.Tests` (the layer this round's `/help` fix lives in) 867/867, plus `Core.Net.Tests` 823/823, `Runtime.Tests` 1,323/1,323, `Content.Tests` 130/130, `Headless.Tests` 89/89, `Bake.Tests` 15/15, `Cli.Tests` 4/4 — all 0 failed | — | findings (a)-(c) fixed this commit — SpewBox flush-top + retail dat font, `/help`/`/help death` exact retail print sequence (see "User gate — round 3" below) |
| CH6b floating windows 14 | `22020ef2`, reworked `1aa77099` | 12,420 passed / 4 skipped / 0 failed | REJECT (docs/research/2026-08-10-ch6ab-review-findings.md) → reworked `1aa77099` — SHOULD-FIXES 2/3/4/5 + NITs 1-5 applied | pending — no client launches this session (hard constraint); needs the next connected round for keybind/mirror/filter visual confirmation, plus the new 0x2100005B fixture's resolved-type assumptions |
| CH6c opacity | `a819687c` | 12,459 passed / 4 skipped / 0 failed | APPROVE-WITH-FIXES; fixed this commit — BLOCKER (out-of-box `DefaultOpacity` 0.5→1.0), AP-190 reworded + two new decomp-verified clauses (retail's per-tick ease, retail's entry-field-specific focus predicate), NITs (`UiElement.cs` stale comment, `WindowUnregistered` detach, post-Dispose `Set*` guards, `DrawString`/outline-pass alpha tests) | pending — needs the next connected round for visual confirmation (window fade on focus change, Settings slider live-apply) |
| Goal-window #363/#367 interface-text seam | `e822888a` | 12,542 passed / 4 skipped / 0 failed | implementer-only, no subagent review this session (hard constraint) | pending — needs the next connected round to confirm the SpewBox now flashes for the reclassified refusals (see "Goal-window follow-up" below) |
### CH4 closeout (2026-08-09)
@ -855,3 +856,87 @@ entirely against the FIX list, not the research.
Full Release suite: 12,420 passed / 4 skipped / 0 failed (baseline
12,392/4/0 at `22020ef2`; net +28 tests, all new coverage from this
rework, zero regressions).
## Goal-window follow-up — #363/#367 interface-text seam (2026-08-10)
Closed issues #363 and #367 (register rows AP-183 and AP-186, both
RETIRED) under the goal-window's hard constraints: no subagents, no
client launches, one commit.
`ChatVM` (`AcDream.UI.Abstractions`) gained an `OnInterfaceText`
(`Action<string>?`) init property and a `ShowInterfaceText(text)` method —
the seam #367's own filing proposed as fix shape (a). The App-layer
composition (`InteractionRetainedUiComposition.CreateRetainedUi`) wires it
to `RuntimeCommunicationState.AddText(text, RetailLogTextType.ClientLocal)`,
the same SpewBox chokepoint every other interface-text producer (item
examine, magic feedback, the portal wait cue) already uses. Unwired hosts
(headless has no `ChatVM` at all; plain test fixtures) fall back to the
ordinary chat log tagged `ClientLocal`, so no text is ever silently
dropped — only the SURFACE degrades, never the message.
Every site AP-183 named now routes through the seam at its correct retail
type, cross-checked against the named-retail decomp (5 handlers spot-
verified: `DoDie`, `DoChannelList`/`On`/`Off`, `DoAllegiance`,
`DoHouseAvailableList` — all `0x1A`):
- `DoStupidChannelHack` ("You must specify the text you wish to say!") —
**newly wired**, not merely reclassified: the six legacy channel verbs
(Fellowship/Allegiance/AllegianceBroadcast/Vassals/Patron/CoVassals)
previously fell through `ChatInputParser.Parse`'s pure `return null`
shape with NO message shown at all. New `ChatInputParser
.IsBareRegisteredChannelVerb` predicate (pure, no side effects) plus a
`ChatCommandRouter.Submit` check ahead of `Parse`.
- `DoChannelList`/`DoChannelOn`/`DoChannelOff` ("Please specify the
channel name.") — reclassified (was already wired via
`InvalidArgumentsText`, just typed `0x00`).
- `DoAllegiance` ("Please see @help Allegiance...") — reclassified, same
shape.
- `DoHouseAvailableList` — reclassified AND corrected: retail's own
bad-house-type text is "Please see @help hslist for more information on
how to use this command" (`acclient_2013_pseudo_c.txt:381481`/`1029383`,
`AddTextToScroll(..., 0x1a, ...)`), not the acdream-synthesized "Usage:
/hslist <house type>" line the catalog fell back to.
- `DoReply` ("Someone must @tell you first!") — **newly wired** for the
message-but-no-last-teller branch only (`gmCCommunicationSystem
::GetLastTeller() == 0`). New `ChatInputParser.IsReplyMissingLastTeller`
predicate. Bare `/r` with no message at all is retail's OWN separate
copy of the "you must specify text" string (a different call site) and
is deliberately still unported — not named by AP-183, out of scope.
- `DoSpeaker`/`DoEndurance`/`DoTitle` — untouched, confirmed still correct
at `0x00` (their text is produced by `ClientCommandController`, not
`ChatCommandRouter`).
The generic bad-args fallback is also fixed: `ChatCommandRouter.Submit`'s
catalog dispatch now resolves `WeenieErrorMessages.Resolve(0x026u, null)`
("That is not a valid command.", the exact port of `DoCommand
@0x0057E46D`'s `HandleFailureEvent(0x26)`) instead of synthesizing
`"Usage: {Usage}"` — the row already carried the `0x026` template from
CH2's `HandleFailureEvent` port, so no new retail text needed extracting.
This closes #367 too: `ChatCommandRouter`'s two other local-presentation
fallbacks (`RetailCommandHelpTable.UnknownCommand` in `EmitVerbHelp`, and
the degenerate-prefix "Unknown command: {verb}." refusal in `Submit`'s
main body) now call `ShowInterfaceText` instead of `ShowSystemMessage`.
The test-script doc (`docs/research/2026-08-09-campaign-ch-test-script.md`)
is updated to drop the "known, tracked gap" notes for both — the next
connected round should observe both fallbacks flash on the SpewBox instead
of landing in the chat window.
Tests: per-site routing pinned both ways (seam wired → reaches the
SpewBox capture; seam unwired → falls back to chat, tagged `ClientLocal`)
for every reclassified/newly-wired site in
`tests/AcDream.UI.Abstractions.Tests/Panels/Chat/ChatCommandRouterTests.cs`,
plus a Turbine-only-channel negative case and a 0x00-site-stays-in-chat
sanity check; pure-predicate coverage for `IsBareRegisteredChannelVerb`/
`IsReplyMissingLastTeller` in `ChatInputParserTests.cs`; the seam itself
in `ChatVMRetellAndProvidersTests.cs`.
Full Release suite: 12,542 passed / 4 skipped / 0 failed (baseline 12,466/4/0
at `ff2784ea`; net +76 tests, all new coverage from this follow-up, zero
regressions). No subagent review this session (hard constraint); no
connected user gate (hard constraint — no client launches). The next
connected round should confirm: bare `/g`/`/a`/etc. now flash the SpewBox
red instead of doing nothing; `/hslist badtype` and a bad-args catalog
command (e.g. `/ls now`) now flash red SpewBox text instead of showing a
green "Usage:" line in chat; `/help nonsenseverb` and a bare `/`/`@` now
flash on the SpewBox instead of appearing in the chat scroll.

View file

@ -73,10 +73,33 @@ Side-by-side vs retail if possible:
- `/help death` → the SAME "Note:" line first, then a SECOND line starting
"For more information, type @help <command>." immediately followed by
the 8-line corpse/death command listing — not just the 8 lines alone.
- `/help somenonsenseverb` → "Unknown command" (no "Note:" line before it;
retail's fallback skips the wrapper). This still shows in the CHAT
window rather than the SpewBox — a known, tracked gap (#367/AP-186), not
a new bug to report.
- `/help somenonsenseverb` → **goal-window fix (2026-08-10, #367 closed):**
"Unknown command" now flashes on the SpewBox (top of viewport), NOT the
chat window — the SAME surface as section 1's "jump in the air" refusal.
Chat gets no line at all.
## 6. Goal-window fix — SpewBox refusal typing (2026-08-10, #363/#367)
- `/g` (or `/f`/`/a`/`/m`/`/p`/`/v`/`/c` etc.) with NO message → **"You
must specify the text you wish to say!"** flashes on the SpewBox.
Previously this silently did nothing.
- `/r hi there` (or `/reply`) with no prior incoming Tell → **"Someone
must @tell you first!"** flashes on the SpewBox. Previously silent.
- A bad-args catalog command, e.g. `/ls now` or `/marketplace foo`**"That
is not a valid command."** flashes on the SpewBox. Previously showed a
green "Usage: /lifestone" line in CHAT — both the color/surface AND the
wording changed.
- `/hslist badtype`**"Please see @help hslist for more information on
how to use this command"** flashes on the SpewBox. Previously showed a
green "Usage: /hslist <house type>" line in CHAT.
- `/clist`/`/on`/`/off` with no channel name, and `@allegiance boot Bob`
from section 4 above — SAME text as before, but now flash on the
SpewBox instead of showing in chat.
- A bare `/` or `@x` (no letter verb) and `/help somenonsenseverb` (section
5) both now flash "Unknown command[: x]" on the SpewBox too.
- Sanity: `/endurance`, `@version`, `@loc` and other informational command
output must STILL show in chat (green/default), not the SpewBox — only
genuine refusals moved.
## Known-open, do not report as new
@ -84,9 +107,6 @@ Side-by-side vs retail if possible:
- SpewBox line lifetime is still a placeholder (AP-177) pending a retail
measurement session; position/font are now best-available
approximations (AP-178), not confirmed retail pixel values.
- `/help <verb>` fallback ("Unknown command") shows in the chat window,
not the SpewBox — retail types it for the SpewBox exclusively, but
`ChatCommandRouter` has no path there yet (#367/AP-186).
- Allegiance management subcommands print the help refusal instead of
executing (#360); `@day`/`@log`/`@render` deferred (#361); four request
commands send but responses aren't rendered yet (#362).
commands send but responses aren't rendered yet (#362, already closed).