Move camera/root resolution, live settings and listener preview, sky/lighting/fog preparation, animated classification, and building visibility scratch behind a typed WorldRenderFrameBuilder while preserving retail frame order and borrowed lifetimes. Co-authored-by: Codex <codex@openai.com>
132 lines
5.9 KiB
C#
132 lines
5.9 KiB
C#
using System;
|
|
using System.Globalization;
|
|
|
|
namespace AcDream.App;
|
|
|
|
/// <summary>
|
|
/// Typed bundle of startup-time configuration read from the process
|
|
/// environment. Built once in <c>Program.cs</c> and passed to
|
|
/// <c>GameWindow</c> so the rest of the app reads its config through
|
|
/// strongly-typed fields instead of scattered
|
|
/// <c>Environment.GetEnvironmentVariable</c> calls.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// <strong>Scope:</strong> startup-time only — values that don't change
|
|
/// once the window is up. Runtime diagnostic toggles
|
|
/// (e.g. <c>ACDREAM_DUMP_MOTION</c>, <c>ACDREAM_PROBE_*</c>) belong in
|
|
/// diagnostic owner classes (see <c>AcDream.Core.Physics.PhysicsDiagnostics</c>
|
|
/// for the template), not here.
|
|
/// </para>
|
|
/// <para>
|
|
/// See <c>docs/architecture/code-structure.md</c> §2 Rule 4 for the
|
|
/// rule that drove this extraction, and §4 Step 1 for the broader
|
|
/// extraction sequence this is the first cut of.
|
|
/// </para>
|
|
/// </remarks>
|
|
public sealed record RuntimeOptions(
|
|
string DatDir,
|
|
bool LiveMode,
|
|
string LiveHost,
|
|
int LivePort,
|
|
string? LiveUser,
|
|
string? LivePass,
|
|
bool DevTools,
|
|
bool UncappedRendering,
|
|
bool DumpMoveTruth,
|
|
bool NoAudio,
|
|
bool EnableSkyPesDebug,
|
|
int HidePartIndex,
|
|
bool RetailCloseDegrades,
|
|
bool DumpSceneryZ,
|
|
bool DumpLiveSpawns,
|
|
bool DumpClothing,
|
|
int? LegacyStreamRadius,
|
|
bool RetailUi,
|
|
string? AcDir,
|
|
bool UiProbeDump,
|
|
string? UiProbeScript,
|
|
string? AutomationArtifactDirectory,
|
|
float FogStartMultiplier,
|
|
float FogEndMultiplier)
|
|
{
|
|
/// <summary>
|
|
/// Build options from the process environment. Used by
|
|
/// <c>Program.cs</c> at startup.
|
|
/// </summary>
|
|
public static RuntimeOptions FromEnvironment(string datDir)
|
|
=> Parse(datDir, Environment.GetEnvironmentVariable);
|
|
|
|
/// <summary>
|
|
/// Build options from a custom environment getter. Used by tests to
|
|
/// inject controlled env values without touching the process
|
|
/// environment.
|
|
/// </summary>
|
|
/// <param name="datDir">Resolved dat-file directory.</param>
|
|
/// <param name="env">Function returning the value for an env-var
|
|
/// name, or <c>null</c> when unset.</param>
|
|
public static RuntimeOptions Parse(string datDir, Func<string, string?> env)
|
|
{
|
|
if (datDir is null) throw new ArgumentNullException(nameof(datDir));
|
|
if (env is null) throw new ArgumentNullException(nameof(env));
|
|
|
|
return new RuntimeOptions(
|
|
DatDir: datDir,
|
|
LiveMode: IsExactlyOne(env("ACDREAM_LIVE")),
|
|
LiveHost: env("ACDREAM_TEST_HOST") ?? "127.0.0.1",
|
|
LivePort: TryParseInt(env("ACDREAM_TEST_PORT")) ?? 9000,
|
|
LiveUser: NullIfEmpty(env("ACDREAM_TEST_USER")),
|
|
LivePass: NullIfEmpty(env("ACDREAM_TEST_PASS")),
|
|
DevTools: IsExactlyOne(env("ACDREAM_DEVTOOLS")),
|
|
// Normal presentation is always bounded by VSync or a
|
|
// refresh-rate software pacer. This explicit diagnostic is the
|
|
// sole way to measure truly uncapped renderer throughput.
|
|
UncappedRendering: IsExactlyOne(env("ACDREAM_UNCAPPED_RENDER")),
|
|
DumpMoveTruth: IsExactlyOne(env("ACDREAM_DUMP_MOVE_TRUTH")),
|
|
NoAudio: IsExactlyOne(env("ACDREAM_NO_AUDIO")),
|
|
EnableSkyPesDebug: IsExactlyOne(env("ACDREAM_ENABLE_SKY_PES")),
|
|
HidePartIndex: TryParseInt(env("ACDREAM_HIDE_PART")) ?? -1,
|
|
// Default-on: any value other than the literal string "0" enables
|
|
// retail close-detail degrades. Set ACDREAM_RETAIL_CLOSE_DEGRADES=0
|
|
// only for before/after diagnostic comparisons.
|
|
RetailCloseDegrades: !string.Equals(env("ACDREAM_RETAIL_CLOSE_DEGRADES"), "0", StringComparison.Ordinal),
|
|
DumpSceneryZ: IsExactlyOne(env("ACDREAM_DUMP_SCENERY_Z")),
|
|
DumpLiveSpawns: IsExactlyOne(env("ACDREAM_DUMP_LIVE_SPAWNS")),
|
|
DumpClothing: IsExactlyOne(env("ACDREAM_DUMP_CLOTHING")),
|
|
// Legacy override for ACDREAM_STREAM_RADIUS. Caller applies it on
|
|
// top of the quality preset's radii. Null when unset or invalid.
|
|
LegacyStreamRadius: TryParseNonNegativeInt(env("ACDREAM_STREAM_RADIUS")),
|
|
RetailUi: IsExactlyOne(env("ACDREAM_RETAIL_UI")),
|
|
AcDir: NullIfEmpty(env("ACDREAM_AC_DIR")),
|
|
UiProbeDump: IsExactlyOne(env("ACDREAM_UI_PROBE_DUMP")),
|
|
UiProbeScript: NullIfEmpty(env("ACDREAM_UI_PROBE_SCRIPT")),
|
|
AutomationArtifactDirectory:
|
|
NullIfEmpty(env("ACDREAM_AUTOMATION_ARTIFACT_DIR")),
|
|
FogStartMultiplier: TryParseFloat(env("ACDREAM_FOG_START_MULT")) ?? 0.7f,
|
|
FogEndMultiplier: TryParseFloat(env("ACDREAM_FOG_END_MULT")) ?? 0.95f);
|
|
}
|
|
|
|
/// <summary>True iff live-mode credentials are present and valid for connecting.</summary>
|
|
public bool HasLiveCredentials =>
|
|
LiveMode && !string.IsNullOrEmpty(LiveUser) && !string.IsNullOrEmpty(LivePass);
|
|
|
|
/// <summary>True when the opt-in retail UI automation probe should be constructed.</summary>
|
|
public bool UiProbeEnabled => UiProbeDump || !string.IsNullOrEmpty(UiProbeScript);
|
|
|
|
private static bool IsExactlyOne(string? s)
|
|
=> string.Equals(s, "1", StringComparison.Ordinal);
|
|
|
|
private static string? NullIfEmpty(string? s)
|
|
=> string.IsNullOrEmpty(s) ? null : s;
|
|
|
|
private static int? TryParseInt(string? s)
|
|
=> int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var v) ? v : null;
|
|
|
|
private static int? TryParseNonNegativeInt(string? s)
|
|
=> TryParseInt(s) is { } v && v >= 0 ? v : null;
|
|
|
|
private static float? TryParseFloat(string? s)
|
|
=> float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out float value)
|
|
? value
|
|
: null;
|
|
}
|