merge: headless console — interactive chat/command CLI for the bot host (review-closed)
Owner direction 2026-09-07. Reader thread → tick-drained queue, the same ChatCommandRouter.Submit the chat box uses, event-stream renderer, SpewBox pump, --console / ACDREAM_HEADLESS_CONSOLE (=0 disables). Opus review APPROVE-WITH-FIXES, 12-item fix round, narrow re-check MERGE-READY. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
commit
8cb284d6f7
15 changed files with 1728 additions and 17 deletions
|
|
@ -40,12 +40,16 @@ Assume a flag has a side effect until its row says otherwise.
|
|||
- **Everything diagnostic is OFF by default.** Every probe, dump, capture,
|
||||
and measurement flag in this document is inert until its variable is
|
||||
explicitly set — an unset environment runs zero diagnostics. Exactly
|
||||
five flags default ON, and none is a diagnostic: `ACDREAM_RETAIL_CHASE`,
|
||||
six flags default ON, and none is a diagnostic: `ACDREAM_RETAIL_CHASE`,
|
||||
`ACDREAM_CAMERA_COLLIDE`, `ACDREAM_CAMERA_ALIGN_SLOPE`, and
|
||||
`ACDREAM_RETAIL_CLOSE_DEGRADES` are retail *behaviors* wearing an A/B
|
||||
off-switch (`=0` disables the behavior for a comparison run), while
|
||||
`ACDREAM_RETAIL_UI` is the product's only gameplay presentation and uses
|
||||
the same explicit diagnostic opt-out. That five-flag set is frozen by
|
||||
the same explicit diagnostic opt-out. `ACDREAM_HEADLESS_CONSOLE` is the
|
||||
sixth: its unset default is terminal-shaped (on when stdin is a real
|
||||
console, off when redirected — not unconditionally on like the other
|
||||
five), but once the variable is SET AT ALL it uses the identical `=0`
|
||||
override (any other value enables). That six-flag set is frozen by
|
||||
`LaunchOptionsDocumentationTests` — a new
|
||||
default-on flag fails the build.
|
||||
- `=1` means the code tests for exactly the string `1`. Setting `true`,
|
||||
|
|
@ -93,6 +97,7 @@ dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Release
|
|||
| `ACDREAM_DAT_DIR` | `=<path>` | Fallback dat-directory when no positional argument is given. App: single read at `Program.cs:58`. Cli: read independently per-subcommand (each subcommand does `args.ElementAtOrDefault(N) ?? Env.GetEnvironmentVariable("ACDREAM_DAT_DIR")`) plus once more for the default (no-subcommand) asset-inventory mode at line 152. | Two of the four `Program.cs` line numbers in the raw grep (91, 135) are **not reads** — they're the literal string `ACDREAM_DAT_DIR` inside `Log.Error` usage-text messages, not `GetEnvironmentVariable` calls. Only line 58 is a real read in `AcDream.App`. | none — hard usage error (exit 2) if unset and no positional arg | `Program.cs:58` (App); `Cli/Program.cs:24,35,47,59,71,84,113,125,137,152` (every Cli subcommand) |
|
||||
| `ACDREAM_DISPLAY_PROTOCOL` | `="auto"` / `"x11"` / `"wayland"` (case-insensitive, trimmed); any other value throws `InvalidOperationException` at startup | Linux-only: forces the GLFW 3.4 platform-init hint (X11 vs Wayland vs auto) before any window is created; ignored entirely on Windows (always `Windows` protocol) | An invalid value is fatal at startup (throws before any window exists), not a silent fallback | unset → auto-detected from `XDG_SESSION_TYPE`/`WAYLAND_DISPLAY`/`DISPLAY`, falling back to GLFW `Automatic` | `GraphicalWindowBackendSelection.Resolve` (`GraphicalWindowBackendSelection.cs:26-58`) |
|
||||
| `ACDREAM_FAR_RADIUS` | `=<int>` | Overrides preset's `FarRadius` (outer streaming/reveal window, landblocks) | Enlarging changes streaming memory budget and what's resident/rendered — CLAUDE.md: leave unset for measurement/gate runs (same family as legacy `ACDREAM_STREAM_RADIUS`) | preset's `FarRadius` (Low=5, Medium=8, High=12, Ultra=15) | `QualitySettings.WithEnvOverrides` (`QualityPreset.cs:47`) |
|
||||
| `ACDREAM_HEADLESS_CONSOLE` | `=0` disables (once set at all); any other value enables; unset falls through to the terminal-shaped default | Turns on the headless host's interactive console (docs/plans/2026-09-07-headless-console.md): a background thread reads stdin lines, each drained on the session tick through the SAME plugin-verb/client-slash-command pipeline the graphical chat box uses, with chat/lifecycle/portal output rendered to stdout. Only takes effect for `run` with a single configured session — a multi-session process reports `console: single-session only` via the diagnostics stream and does not attach one. | Starts a background stdin-reader thread and writes plain-text lines to the same stdout stream `HeadlessDiagnosticWriter` already uses for its JSON lines — the two interleave. Only applies to `run`; `--console` (bare flag, no value) always wins over this variable. S1 fix (2026-09-07): the variable itself now wins outright once SET AT ALL — `=0` disables even when stdin is a real terminal, matching every other `=0`-disables flag in this table; only an UNSET variable falls through to the terminal-shaped default. | unset → on when stdin is a real console, off when redirected (`!Console.IsInputRedirected`, checked once in `Program.cs`); set → `!= "0"` | `HeadlessConsoleOptions.Resolve` (`Configuration/HeadlessConsoleOptions.cs`) → `HeadlessEntryPoint.Run` → `HeadlessProcessHost`'s `consoleEnabled` |
|
||||
| `ACDREAM_LIVE` | `=1` (exactly the literal string `"1"`) | Core switch: connect to a live ACE server instead of running offline/no-connect. | The 4 non-`RuntimeOptions.cs` line numbers in the raw grep are **all comments or log-message text**, not reads — `SessionStartComposition.cs:39` is inside the string `"live: ACDREAM_LIVE set but TEST_USER/TEST_PASS missing; skipping"`; `Program.cs:126` is inside a `--session-config` override log line; `GameWindow.cs:614,627` are doc comments. The only actual parse is `RuntimeOptions.cs:141`. Requires `ACDREAM_TEST_USER`/`ACDREAM_TEST_PASS` too (`HasLiveCredentials`) or the session silently reports `MissingCredentials` and skips. Forced to effectively-on (LiveMode=true) unconditionally by `--session-config` launches regardless of this var. | `false` | `RuntimeOptions.LiveMode` → `SessionStartComposition.cs` (log text only), `Program.cs:126` (log text only), `GameWindow.cs:614,627` (comments only), consumed for real via `RuntimeOptions.HasLiveCredentials` and `WorldSession`/`GameRuntime` session-start gating |
|
||||
| `ACDREAM_MAX_COMPLETIONS_PER_FRAME` | `=<int>` | Overrides preset's per-frame streaming-completion throughput cap | Directly changes the streaming admission budget measured by perf/completion gates — do not vary during a measurement run | preset's value (Low=2, Medium=3, High=4, Ultra=6) | `QualitySettings.WithEnvOverrides` (`QualityPreset.cs:59`) |
|
||||
| `ACDREAM_MSAA_SAMPLES` | `=<int>` (0/2/4/8) | Overrides preset's MSAA sample count | Changes GPU multisample anti-aliasing (visual + GPU-cost change) | preset's `MsaaSamples` (Low=0, Medium=2, High/Ultra=4) | `QualitySettings.WithEnvOverrides` (`QualityPreset.cs:48`) |
|
||||
|
|
@ -143,6 +148,7 @@ config without connecting; `run` connects.
|
|||
| `--config <path>` | The versioned headless session-configuration document. Required. | — |
|
||||
| `--config-dir` / `--data-dir` / `--cache-dir` `<path>` | Override each portable path root. | Merged over the config document's own `process.paths`; the command line wins. |
|
||||
| `-user` / `--user`, `-password` / `--password` | Direct single-session credentials, bypassing the config's credential source. | Plaintext in the process command line — prefer the config's credential reference. |
|
||||
| `--console` | Forces the interactive console on for `run` (bare flag, no value) — see `ACDREAM_HEADLESS_CONSOLE`. | Same side effects as the environment variable; this flag always wins over it. |
|
||||
| `--help` / `-h` (or no args) | Prints usage, exits 0. | — |
|
||||
|
||||
### `AcDream.Launcher`
|
||||
|
|
|
|||
|
|
@ -66,3 +66,158 @@ 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.
|
||||
|
||||
- 2026-09-07 FIX ROUND (Opus review, APPROVE-WITH-FIXES). S1: `ACDREAM_
|
||||
HEADLESS_CONSOLE=0` now disables the console even when stdin is a real
|
||||
terminal — the prior `== "1"` test let `"0"` silently fall through to the
|
||||
terminal-shaped default; the flag is now the sixth entry in
|
||||
`LaunchOptionsDocumentationTests.DefaultOnBehaviorFlags` (a default-on
|
||||
behavior with an A/B off-switch, once set at all, like
|
||||
`ACDREAM_RETAIL_CHASE`). S2: the reader-thread pin is now falsifiable — a
|
||||
fixture `TextReader` records the actual thread id `ReadLine` ran on, and a
|
||||
new test asserts the controller's submit callback runs on neither that
|
||||
thread nor any other unexpected one, only the `DrainDue` caller's. S3: one
|
||||
`HeadlessProcessHost` end-to-end test proves a console line reaches the
|
||||
session's real `SubmitConsoleLine` pipeline and `/quit` returns
|
||||
`HeadlessExitCode.Success`. S4: `HeadlessConsoleController.Handle` now
|
||||
wraps `_submit` in try/catch (mirroring `LoginCommandSequence.DrainDue`)
|
||||
and prints a line for `UnknownCommand`/`Dropped`, so a console typo can
|
||||
never escape into the scheduler's per-session quarantine catch. S5:
|
||||
deleted the per-call `HeadlessConsoleChatFeedback` decorator — it only
|
||||
ever saw text produced by the console's OWN `SubmitConsoleLine` calls.
|
||||
The new `HeadlessConsoleSpewBoxPump` polls the shared `SpewBoxState` on
|
||||
the console's own per-tick pump instead, the SAME seam the graphical
|
||||
overlay's `SpewBoxController.Tick` reads, so server- and plugin-driven
|
||||
`ClientLocal` interface text prints too. S6: `Program.cs` now resolves
|
||||
`standardOutputIsTerminal` next to the stdin probe and threads it through
|
||||
`HeadlessEntryPoint.Run` → `HeadlessProcessHost`, which no longer reads
|
||||
`System.Console.IsOutputRedirected` itself. S7: a multi-session process
|
||||
launched with `--console` now reports `_diagnostics.Message("console",
|
||||
"single-session only")` instead of silently skipping console attachment.
|
||||
N1: corrected two stale dispatch-order doc comments
|
||||
(`HeadlessSessionHost.SubmitConsoleLine`, `HeadlessConsoleController`'s
|
||||
class remarks) to the real `ChatCommandRouter.Submit` order: retail's
|
||||
client-command catalog, local `/help`, plugin verbs, the unregistered-
|
||||
channel-tag fallback, an explicit server command, then plain chat. N2:
|
||||
`HeadlessCommandLine.Console` renamed to `ConsoleEnabled`. N3: `validate`
|
||||
mode now rejects `--console` outright rather than silently ignoring it.
|
||||
N4: **`/status` and `/quit` are console-intercepted verbs — they never
|
||||
reach `ChatCommandRouter`, unlike `@status`, which is a real server
|
||||
command and still passes through untouched.** N5: `HeadlessConsoleRenderer`
|
||||
now dims only lifecycle/command/portal lines; chat and interface text
|
||||
print at the terminal's default weight.
|
||||
Every new/changed test was shown to fail first against a targeted
|
||||
mutation of the corresponding production code (see each commit's own
|
||||
body for the specific mutation) before the fix landed; one commit per
|
||||
item, all with `Co-Authored-By: Claude Fable 5.1`.
|
||||
Suites (Release): `dotnet test tests/AcDream.Headless.Tests` → 207
|
||||
passed / 1 pre-existing Linux-lane failure
|
||||
(`LinuxRejectsGroupOrOtherCredentialPermissions`) / 208 total (up from
|
||||
193/1/194 before this round — 14 new/changed tests). `dotnet test
|
||||
tests/AcDream.App.Tests --filter "FullyQualifiedName~LaunchOptions"` →
|
||||
4/4 passed, including the corrected `OnlyTheSixProductBehaviorFlagsDefaultOn`
|
||||
(renamed from Five). `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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue