175 lines
6.7 KiB
C#
175 lines
6.7 KiB
C#
using System.Text.Json.Serialization;
|
|
|
|
namespace AcDream.App.Configuration;
|
|
|
|
/// <summary>
|
|
/// Campaign LA slice LA1: the graphical host's reader for the pinned
|
|
/// session-config document shape shared with
|
|
/// <c>AcDream.Headless.Configuration.HeadlessConfiguration</c> — see
|
|
/// <c>docs/plans/2026-08-14-launcher-campaign.md</c> LA1 and
|
|
/// <c>docs/superpowers/specs/2026-08-14-launcher-campaign-design.md</c> §6.
|
|
///
|
|
/// <para>
|
|
/// This is a DELIBERATELY independent DTO set, not a shared type reused from
|
|
/// <c>AcDream.Headless</c> — Headless's config types are internal, tied to
|
|
/// its own OP7 <c>characterOptions</c> allow-list semantics, and Headless is
|
|
/// not a project App references. The two readers are cross-checked instead
|
|
/// by a shared fixture document both test suites parse
|
|
/// (<c>SessionConfigurationSharedFixtureTests</c> /
|
|
/// <c>HeadlessConfigurationSharedFixtureTests</c>).
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Differences from the Headless reader, all intentional per the pinned
|
|
/// contract: <see cref="SessionDescriptor.Character"/> is OPTIONAL here
|
|
/// (absent = today's first-available fallback; the character-select screen
|
|
/// is LA7, not this slice); <see cref="SessionDescriptor.Policy"/> is parsed
|
|
/// but never consulted (App has no bot-policy concept); exactly ONE session
|
|
/// is required, not "one or more".
|
|
/// </para>
|
|
/// </summary>
|
|
internal sealed class SessionConfiguration
|
|
{
|
|
[JsonRequired]
|
|
public int Version { get; init; }
|
|
|
|
public SessionProcessSettings? Process { get; init; }
|
|
|
|
[JsonRequired]
|
|
public List<SessionDescriptor?> Sessions { get; init; } = [];
|
|
}
|
|
|
|
internal sealed class SessionProcessSettings
|
|
{
|
|
public SessionContentDescriptor? Content { get; init; }
|
|
|
|
/// <summary>Campaign LA slice LA1 review fix (F2): accepted so the SAME
|
|
/// document also satisfies the Headless loader's own
|
|
/// <c>process.paths</c> member (<c>HeadlessPathOverrides</c>) — parsed
|
|
/// and ignored here, exactly like <see cref="SessionDescriptor.Policy"/>
|
|
/// and <see cref="SessionDescriptor.CharacterOptions"/> below. App has
|
|
/// no config/data/cache directory override concept of its own (those
|
|
/// come from <c>ApplicationPathSet</c>/env vars on this host); only the
|
|
/// Headless host consumes overrides composed under this key.</summary>
|
|
public SessionProcessPathOverrides? Paths { get; init; }
|
|
}
|
|
|
|
/// <summary>Accepted-but-ignored mirror of Headless's
|
|
/// <c>HeadlessPathOverrides</c> shape — see
|
|
/// <see cref="SessionProcessSettings.Paths"/>.</summary>
|
|
internal sealed class SessionProcessPathOverrides
|
|
{
|
|
public string? ConfigDirectory { get; init; }
|
|
public string? DataDirectory { get; init; }
|
|
public string? CacheDirectory { get; init; }
|
|
}
|
|
|
|
internal sealed class SessionContentDescriptor
|
|
{
|
|
[JsonRequired]
|
|
public string DatDirectory { get; init; } = string.Empty;
|
|
|
|
[JsonRequired]
|
|
public string PreparedAssetPath { get; init; } = string.Empty;
|
|
}
|
|
|
|
internal sealed record SessionDescriptor
|
|
{
|
|
[JsonRequired]
|
|
public string Id { get; init; } = string.Empty;
|
|
|
|
[JsonRequired]
|
|
public SessionEndpointDescriptor Endpoint { get; init; } = new();
|
|
|
|
[JsonRequired]
|
|
public string Account { get; init; } = string.Empty;
|
|
|
|
/// <summary>Optional for the graphical host: absent means today's
|
|
/// existing first-available fallback stays in effect. The retail
|
|
/// character-select screen (LA7) is what actually consumes "no
|
|
/// selector" as "stop and let the user pick".</summary>
|
|
public SessionCharacterSelectorDescriptor? Character { get; init; }
|
|
|
|
/// <summary>Accepted so the SAME document also satisfies the Headless
|
|
/// loader's <c>JsonRequired</c> policy field — parsed and ignored here;
|
|
/// App has no bot-policy concept.</summary>
|
|
public SessionPolicyDescriptor? Policy { get; init; }
|
|
|
|
/// <summary>Campaign LA slice LA1 review fix (F2): pinned-contract
|
|
/// mode discriminator. ABSENT means today's ONLY App behavior — an
|
|
/// ordinary play session — so every document written before this field
|
|
/// existed keeps parsing unchanged. <c>"probe"</c> (LA2's connect
|
|
/// ▸ characterList ▸ graceful-disconnect flow, no EnterWorld) is
|
|
/// HEADLESS-ONLY; the App loader rejects it with an explicit message
|
|
/// naming the field rather than the caller ever seeing a raw unmapped-
|
|
/// member <see cref="System.Text.Json.JsonException"/>. Any other value
|
|
/// is a configuration error — the pinned contract defines no other
|
|
/// mode literal, so a document is either silent about mode (play) or
|
|
/// says "probe" exactly.</summary>
|
|
public string? Mode { get; init; }
|
|
|
|
[JsonRequired]
|
|
public SessionCredentialDescriptor Credential { get; init; } = new();
|
|
|
|
/// <summary>Accepted-but-ignored by App; Headless's own loader owns the
|
|
/// allow-list semantics for this field (OP7 D8).</summary>
|
|
public Dictionary<string, bool>? CharacterOptions { get; init; }
|
|
|
|
/// <summary>LA1/LA5: plugin ids to load. Absent = load all; explicit
|
|
/// empty = load none.</summary>
|
|
public List<string>? Plugins { get; init; }
|
|
|
|
/// <summary>LA1/LA6: ordered chat-typed strings run through the shared
|
|
/// Runtime parser/router after entering world.</summary>
|
|
public List<string>? LoginCommands { get; init; }
|
|
|
|
/// <summary>LA1: inter-command delay for <see cref="LoginCommands"/>,
|
|
/// milliseconds. Matches the pinned contract default of 500 ms.</summary>
|
|
public int LoginCommandDelayMs { get; init; } = 500;
|
|
|
|
/// <summary>LA1: absolute path for the status-event JSONL stream.
|
|
/// Absent = no writer constructed.</summary>
|
|
public string? StatusFile { get; init; }
|
|
}
|
|
|
|
internal sealed class SessionEndpointDescriptor
|
|
{
|
|
[JsonRequired]
|
|
public string Host { get; init; } = string.Empty;
|
|
|
|
[JsonRequired]
|
|
public int Port { get; init; }
|
|
}
|
|
|
|
internal sealed class SessionCharacterSelectorDescriptor
|
|
{
|
|
public int? Index { get; init; }
|
|
public uint? Id { get; init; }
|
|
public string? Name { get; init; }
|
|
}
|
|
|
|
/// <summary>Loose by design: App never inspects the policy's shape beyond
|
|
/// "does this document parse" — <c>Id</c>/<c>Role</c> stay untyped strings so
|
|
/// this DTO never has to track Headless's own policy-id/role vocabulary.</summary>
|
|
internal sealed class SessionPolicyDescriptor
|
|
{
|
|
public string? Id { get; init; }
|
|
public string? Role { get; init; }
|
|
}
|
|
|
|
[JsonConverter(typeof(JsonStringEnumConverter<SessionCredentialProviderKind>))]
|
|
internal enum SessionCredentialProviderKind
|
|
{
|
|
Environment,
|
|
StandardInput,
|
|
File,
|
|
}
|
|
|
|
internal sealed class SessionCredentialDescriptor
|
|
{
|
|
[JsonRequired]
|
|
public SessionCredentialProviderKind Provider { get; init; }
|
|
|
|
[JsonRequired]
|
|
public string Reference { get; init; } = string.Empty;
|
|
}
|