diff --git a/docs/launch-options.md b/docs/launch-options.md index 39484a95a..f2b1630c1 100644 --- a/docs/launch-options.md +++ b/docs/launch-options.md @@ -93,6 +93,7 @@ dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Release | `ACDREAM_DAT_DIR` | `=` | 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` | `=` | 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` | `=1` enables | 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 — the launcher's multi-session mode is unaffected. | 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, which in turn always wins over the terminal-shaped default. | unset → on when stdin is a real console, off when redirected (`!Console.IsInputRedirected`, checked once in `Program.cs`) | `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` | `=` | 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` | `=` (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 +144,7 @@ config without connecting; `run` connects. | `--config ` | The versioned headless session-configuration document. Required. | — | | `--config-dir` / `--data-dir` / `--cache-dir` `` | 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` diff --git a/src/AcDream.Headless/Configuration/HeadlessCommandLine.cs b/src/AcDream.Headless/Configuration/HeadlessCommandLine.cs index 380095124..3feff26ed 100644 --- a/src/AcDream.Headless/Configuration/HeadlessCommandLine.cs +++ b/src/AcDream.Headless/Configuration/HeadlessCommandLine.cs @@ -4,7 +4,8 @@ internal sealed record HeadlessCommandLine( string Command, string ConfigurationPath, HeadlessPathOverrides Paths, - HeadlessDirectCredentials? DirectCredentials) + HeadlessDirectCredentials? DirectCredentials, + bool Console = false) { internal static HeadlessCommandLine Parse( IReadOnlyList arguments) @@ -23,15 +24,26 @@ internal sealed record HeadlessCommandLine( string? cacheDirectory = null; string? user = null; string? password = null; - for (int index = 1; index < arguments.Count; index += 2) + bool console = false; + int index = 1; + while (index < arguments.Count) { + string name = arguments[index]; + // --console is a bare flag (no value token) — the interactive + // console for the run command (see HeadlessConsoleOptions). + if (name == "--console") + { + console = true; + index += 1; + continue; + } + if (index + 1 >= arguments.Count) { throw new HeadlessCommandLineException( "Every command option requires a value."); } - string name = arguments[index]; string value = arguments[index + 1]; if (string.IsNullOrWhiteSpace(value)) { @@ -65,6 +77,7 @@ internal sealed record HeadlessCommandLine( throw new HeadlessCommandLineException( "Unknown command option."); } + index += 2; } if (configurationPath is null) @@ -92,7 +105,8 @@ internal sealed record HeadlessCommandLine( cacheDirectory), user is null ? null - : new HeadlessDirectCredentials(user, password!)); + : new HeadlessDirectCredentials(user, password!), + console); } private static void SetOnce(ref string? destination, string value) diff --git a/src/AcDream.Headless/Configuration/HeadlessConsoleOptions.cs b/src/AcDream.Headless/Configuration/HeadlessConsoleOptions.cs new file mode 100644 index 000000000..fd1aed15f --- /dev/null +++ b/src/AcDream.Headless/Configuration/HeadlessConsoleOptions.cs @@ -0,0 +1,38 @@ +namespace AcDream.Headless.Configuration; + +/// +/// Typed resolution for the headless interactive console (docs/plans/ +/// 2026-09-07-headless-console.md). Three inputs, first match wins: +/// the --console command-line flag, the +/// ACDREAM_HEADLESS_CONSOLE=1 environment variable, and finally a +/// terminal-shaped default — on when stdin is a real console (an operator +/// typing at a keyboard), off when it is redirected (a script, CI runner, or +/// piped fixture, where a background reader thread blocked on +/// ReadLine would never see input and would just sit idle). See +/// docs/launch-options.md for the documented row this owns. +/// +internal static class HeadlessConsoleOptions +{ + internal const string EnvironmentVariable = "ACDREAM_HEADLESS_CONSOLE"; + + internal static bool Resolve( + bool commandLineFlag, + bool standardInputIsTerminal) => + Resolve( + commandLineFlag, + Environment.GetEnvironmentVariable, + standardInputIsTerminal); + + internal static bool Resolve( + bool commandLineFlag, + Func environment, + bool standardInputIsTerminal) + { + ArgumentNullException.ThrowIfNull(environment); + if (commandLineFlag) + return true; + if (environment(EnvironmentVariable) == "1") + return true; + return standardInputIsTerminal; + } +}