docs(headless-console): record implementation ledger and connected proof

Appends the plan's ledger with what shipped, the deliberate deviation
from the plan's illustrative bracket-prose example (Headless cannot
reference AcDream.UI.Abstractions, so the console's chat rendering is
its own terminal-shaped format using the same channel-name strings,
not a byte-for-byte port of ChatVM.FormatEntry), the chosen console
default and why, and the exact connected proof recipe for the owner to
run against a live ACE with +Acdream and MossTank (not run here per
the contract).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-07 06:57:05 +02:00
parent 4d5da2dcd5
commit 97b1c9f6ce

View file

@ -66,3 +66,106 @@ it or the lead may, it is not a visual gate.
## Ledger
- 2026-09-07 planned; implementer dispatched.
- 2026-09-07 IMPLEMENTED. The dispatch seam already existed:
`AcDream.Runtime.Chat.ChatCommandRouter.Submit` is the SAME presentation-
free pipeline `LoginCommandSequence` (headless) and every graphical chat
window (`ChatWindowController`, `FloatingChatWindowController`,
`RetailUiRuntime`) already call — no lift was needed. Added
`HeadlessSessionHost.SubmitConsoleLine` (`Hosting/HeadlessSessionHost.cs`)
as the one new call site, reusing the host's own retained
`LiveChatCommandSurface`/plugin registry (now promoted from ctor locals to
fields) instead of a second parser.
New files: `Configuration/HeadlessConsoleOptions.cs` (typed `--console` /
`ACDREAM_HEADLESS_CONSOLE=1` / terminal-default resolution),
`Hosting/HeadlessConsoleInputReader.cs` (background stdin thread → FIFO
queue, never executes handler code), `Hosting/HeadlessConsoleController.cs`
(drains the queue on the session tick via a new `HeadlessSessionHost.
ConsolePump` hook; owns `/quit`/`/status`), `Hosting/
HeadlessConsoleChatFormatter.cs` + `Hosting/HeadlessConsoleRenderer.cs`
(renders the K2 bot event stream — `IRuntimeEventObserver`, the same
interface a bot policy subscribes — as bracket-labelled lines:
`[Tell] Bob: hi`, `[Fellowship] …`, `[Local] …`), `Hosting/
HeadlessConsoleChatFeedback.cs` (decorates `RuntimeChatCommandFeedback` so
retail's transient SpewBox/`ClientLocal` interface text — which never
touches `ChatLog`, so it never reaches the K2 event stream — also reaches
the console). `/quit` cancels a `CancellationTokenSource` linked into the
scheduler's run token in `HeadlessProcessHost` (the SAME graceful-exit
path an external Ctrl+C/SIGTERM already takes); `/status` reports
generation, position (or "unknown" without a live movement controller),
and loaded-plugin count (no plugin today reports a richer macro-state
string). Console only attaches for a single-session `run` (per the plan's
"out of scope for the first cut" multi-session note); constructed AFTER
every session's own credential resolution so the reader thread never
races a `StandardInput`-provider password prompt on the same stream.
Chosen console default: on when `!Console.IsInputRedirected` (a real
operator at a terminal), off when redirected (scripts/CI/piped fixtures,
where a blocked `ReadLine` on a background thread would just sit idle) —
resolved once in `Program.cs`, the only place that can see the real
`Console`.
Deviation from the plan's illustrative example: retail's own transcript
never prefixes Tell/Local lines with a bracket (`ChatVM.FormatEntry`
renders "Bob tells you, ..."/"Bob says, ..." with no label) — Headless
cannot reference `AcDream.UI.Abstractions` (the dependency-boundary
test), so `HeadlessConsoleChatFormatter` is a deliberately DIFFERENT,
terminal-shaped "[Label] Sender: text" rendering using the SAME channel-
name strings (matching the plan's literal `[Tell] Bob: hi` example), not
a byte-for-byte port of the graphical prose.
Tests: `tests/AcDream.Headless.Tests/HeadlessConsoleTests.cs` (20 new
tests — options resolution, command-line flag parsing, reader-thread
ordering/never-on-reader-thread, controller drain/quit/status, chat
formatting, and full `/say`/plain-text/plugin-verb/unknown-verb dispatch
against a real `HeadlessSessionHost` + `FixtureSessionOperations`, no live
server) plus the existing `LaunchOptionsDocumentationTests` (4/4 green)
and `HeadlessDependencyBoundaryTests` (3/3 green, unchanged — Headless
still references only `AcDream.Runtime`). Every test in this batch was
mutation-checked to fail before the corresponding production line existed
(see the implementer's final report for the specific mutations run:
skipping the interface-text callback, skipping `_quitRequested.Cancel()`,
forcing `TryHandlePluginCommand` to always return false, dropping the
reader thread's `Enqueue`, and swapping `ChatChannelKind.Say` for `.Tell`
in `SubmitConsoleLine`).
Suites: `dotnet test tests/AcDream.Headless.Tests -c Release` → 193
passed / 1 pre-existing failure (`LinuxRejectsGroupOrOtherCredentialPermissions`,
a Linux-only lane test that cannot run on this Windows host — unrelated
to this change) / 194 total. `dotnet test tests/AcDream.Runtime.Tests -c
Release` → 1891/1891 passed. `dotnet test tests/AcDream.App.Tests -c
Release --filter "FullyQualifiedName~Chat|FullyQualifiedName~Command|
FullyQualifiedName~LaunchOptions"` → 414 passed / 2 pre-existing failures
(`ChatIndicatorButtonLiveMountProbeTests`/`OptionsPanelLiveMountProbeTests`
— both gated on `ACDREAM_PROBE_LIVE_MOUNT=1`, a manual live-DAT probe lane,
unrelated to this change) / 3 skipped / 419 total. `dotnet build
AcDream.slnx -c Release` green throughout.
### Connected proof recipe (owner runs; NOT run by the implementer)
Against a running local ACE at `127.0.0.1:9000` with MossTank loaded for
the second half:
```powershell
$env:ACDREAM_DAT_DIR = "$env:USERPROFILE\Documents\Asheron's Call"
dotnet run --project src\AcDream.Headless\AcDream.Headless.csproj --no-build -c Release -- `
run --config <path-to-a-one-session-config.json> `
-user testaccount -password testpassword --console
```
The referenced config's one session should target character `+Acdream`
(server guid `0x5000000A`) against `127.0.0.1:9000`, an `idle` bot policy,
and (for the second half) the MossTank plugin id under `plugins`. Once the
console prints `entered world`:
1. Type `/say hello` and press Enter — expect the SAME line ACE echoes back
to any other observer (a retail client or a second acdream session
watching `+Acdream`) to also print `[Local] You: hello` in this console
(the server's own HearSpeech echo, rendered through the normal chat
event stream).
2. Type `/status` — expect a line with `generation=`, `position=` (a real
cell/local-frame triple once in world), and `plugins=N loaded`.
3. With MossTank loaded, type `/vt start` (or whatever verb MossTank
registers) — expect MossTank's own handler to run (check its own
status/log output) and confirm NOTHING was sent to the wire for that
line (no `@vt` server command).
4. Type `/quit` — expect a graceful ACE logout (same as the existing
Ctrl+C behavior) and the process to exit 0.
This is not a visual gate; the owner (or the lead) runs it opportunistically
before considering the plan CLOSED.