using System.Net; using AcDream.App.Net; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.UI.Abstractions; namespace AcDream.App.Tests.Net; public sealed class LiveSessionControllerTests { private sealed class TestTransport : IWorldSessionTransport { public void Send(ReadOnlySpan datagram) { } public void Send(IPEndPoint remote, ReadOnlySpan datagram) { } public byte[]? Receive(TimeSpan timeout, out IPEndPoint? from) { from = null; return null; } public void Dispose() { } } private sealed class TestCommandBus : ICommandBus { public bool Active { get; set; } public int PublishCount { get; private set; } public void Publish(T command) where T : notnull { if (Active) PublishCount++; } } private sealed class TestOperations(List calls) : ILiveSessionOperations { public CharacterList.Parsed? Characters { get; set; } = AvailableCharacters(); public Action? OnResolve { get; set; } public Action? OnCreate { get; set; } public Action? OnConnect { get; set; } public Action? OnCharacters { get; set; } public Action? OnEnterWorld { get; set; } public Action? OnTick { get; set; } public Action? OnDispose { get; set; } public bool ThrowOnConnect { get; set; } public bool ThrowOnCreate { get; set; } public bool ThrowOnCharacters { get; set; } public bool ThrowOnEnterWorld { get; set; } public bool ThrowOnTick { get; set; } public bool FailDisposeOnce { get; set; } public List Sessions { get; } = []; public Dictionary DisposeCounts { get; } = []; public int EnterWorldCount { get; private set; } public int TickCount { get; private set; } public IPEndPoint ResolveEndpoint(string host, int port) { calls.Add("resolve"); OnResolve?.Invoke(); return new IPEndPoint(IPAddress.Loopback, port); } public WorldSession CreateSession(IPEndPoint endpoint) { calls.Add("create"); OnCreate?.Invoke(); if (ThrowOnCreate) throw new InvalidOperationException("create failure"); var session = new WorldSession(endpoint, new TestTransport()); Sessions.Add(session); return session; } public void Connect(WorldSession session, string user, string password) { calls.Add("connect"); OnConnect?.Invoke(); if (ThrowOnConnect) throw new InvalidOperationException("connect failure"); } public CharacterList.Parsed? GetCharacters(WorldSession session) { OnCharacters?.Invoke(); if (ThrowOnCharacters) throw new InvalidOperationException("characters failure"); return Characters; } public void EnterWorld(WorldSession session, int activeCharacterIndex) { calls.Add($"enter:{activeCharacterIndex}"); EnterWorldCount++; OnEnterWorld?.Invoke(); if (ThrowOnEnterWorld) throw new InvalidOperationException("enter failure"); } public void Tick(WorldSession session) { calls.Add("tick"); TickCount++; OnTick?.Invoke(); if (ThrowOnTick) throw new InvalidOperationException("tick failure"); } public void DisposeSession(WorldSession session) { calls.Add("dispose-session"); OnDispose?.Invoke(); if (FailDisposeOnce) { FailDisposeOnce = false; throw new InvalidOperationException("dispose failure"); } DisposeCounts[session] = DisposeCounts.GetValueOrDefault(session) + 1; } } private sealed class TestHost(List calls) : ILiveSessionLifecycleHost { public Action? OnBind { get; set; } public Action? OnReset { get; set; } public Action? OnConnecting { get; set; } public Action? OnConnected { get; set; } public Action? OnSelected { get; set; } public Action? OnActivate { get; set; } public Action? OnEntered { get; set; } public Action? OnDetach { get; set; } public bool FailResetOnce { get; set; } public bool FailDetachOnce { get; set; } public bool FailDeactivateOnce { get; set; } public bool FailEventDetachOnce { get; set; } public bool ThrowOnBind { get; set; } public bool ThrowOnConnecting { get; set; } public bool ThrowOnConnected { get; set; } public bool ThrowOnSelected { get; set; } public bool ThrowOnActivate { get; set; } public bool ThrowOnEntered { get; set; } public WorldSession? BindingSessionOverride { get; set; } public int ResetCount { get; private set; } public int DetachCount { get; private set; } public int DeactivateCount { get; private set; } public int EventDetachCount { get; private set; } public int ActivateCount { get; private set; } public List CommandBuses { get; } = []; public List Selections { get; } = []; public LiveSessionBinding BindSession(WorldSession session) { calls.Add("bind"); OnBind?.Invoke(); if (ThrowOnBind) throw new InvalidOperationException("bind failure"); var commands = new TestCommandBus(); CommandBuses.Add(commands); return new LiveSessionBinding( BindingSessionOverride ?? session, commands, activateCommands: () => { calls.Add("activate"); ActivateCount++; commands.Active = true; OnActivate?.Invoke(); if (ThrowOnActivate) throw new InvalidOperationException("activate failure"); }, deactivateCommands: () => { calls.Add("deactivate"); DeactivateCount++; commands.Active = false; if (FailDeactivateOnce) { FailDeactivateOnce = false; throw new InvalidOperationException("deactivate failure"); } }, detachEvents: () => { calls.Add("detach-events"); EventDetachCount++; if (FailEventDetachOnce) { FailEventDetachOnce = false; throw new InvalidOperationException("event detach failure"); } }); } public void ResetSessionState() { calls.Add("reset"); ResetCount++; OnReset?.Invoke(); if (FailResetOnce) { FailResetOnce = false; throw new InvalidOperationException("reset failure"); } } public void ReportConnecting(string host, int port, string user) { calls.Add("report-connecting"); OnConnecting?.Invoke(); if (ThrowOnConnecting) throw new InvalidOperationException("connecting failure"); } public void ReportConnected() { calls.Add("report-connected"); OnConnected?.Invoke(); if (ThrowOnConnected) throw new InvalidOperationException("connected failure"); } public void ApplySelectedCharacter(LiveSessionCharacterSelection selection) { calls.Add("selected"); Selections.Add(selection); OnSelected?.Invoke(); if (ThrowOnSelected) throw new InvalidOperationException("selected failure"); } public void ApplyEnteredWorld(LiveSessionCharacterSelection selection) { calls.Add("entered"); OnEntered?.Invoke(); if (ThrowOnEntered) throw new InvalidOperationException("entered failure"); } public void DetachSession(WorldSession session) { calls.Add("detach-session"); DetachCount++; OnDetach?.Invoke(); if (FailDetachOnce) { FailDetachOnce = false; throw new InvalidOperationException("detach failure"); } } } public enum ReentrantStopPoint { Reset, Resolve, Create, Bind, Connecting, Connect, Connected, Selected, EnterWorld, Activate, Entered, } [Fact] public void Start_BindsBeforeConnectAndPublishesCanonicalSelectionAfterEnterWorld() { var calls = new List(); var operations = new TestOperations(calls); var host = new TestHost(calls); var controller = new LiveSessionController(operations); LiveSessionStartResult result = controller.Start(LiveOptions(), host); Assert.Equal(LiveSessionStartStatus.Connected, result.Status); Assert.Equal( [ "reset", "resolve", "create", "bind", "report-connecting", "connect", "report-connected", "selected", "enter:1", "activate", "entered", ], calls); Assert.Equal( new LiveSessionCharacterSelection(1, 0x50000002u, "Ready", "Canonical"), result.Selection); Assert.True(controller.IsInWorld); Assert.Same(operations.Sessions[0], controller.CurrentSession); Assert.Same(host.CommandBuses[0], controller.Commands); Assert.True(host.CommandBuses[0].Active); } [Fact] public void Start_DisabledAndMissingCredentialsResetButNeverConstructSession() { var calls = new List(); var operations = new TestOperations(calls); var host = new TestHost(calls); var controller = new LiveSessionController(operations); Assert.Equal( LiveSessionStartStatus.Disabled, controller.Start(LiveOptions(live: false), host).Status); Assert.Equal( LiveSessionStartStatus.MissingCredentials, controller.Start(LiveOptions(user: null), host).Status); Assert.Equal(2, host.ResetCount); Assert.Empty(operations.Sessions); Assert.Equal(NullCommandBus.Instance, controller.Commands); } [Fact] public void Start_NoAvailableCharacterTearsDownExactScope() { var calls = new List(); var operations = new TestOperations(calls) { Characters = new CharacterList.Parsed( 0u, [new CharacterList.Character(0x50000001u, "Grey", 5u)], [], 11, "Canonical", true, true), }; var host = new TestHost(calls); var controller = new LiveSessionController(operations); LiveSessionStartResult result = controller.Start(LiveOptions(), host); Assert.Equal(LiveSessionStartStatus.NoCharacters, result.Status); Assert.False(controller.IsInWorld); Assert.Null(controller.CurrentSession); Assert.Equal(1, host.DeactivateCount); Assert.Equal(1, host.EventDetachCount); Assert.Equal(1, host.DetachCount); Assert.Equal(2, host.ResetCount); Assert.Equal(1, operations.DisposeCounts[operations.Sessions[0]]); } [Fact] public void Start_ConnectFailureConvergesOffline() { var calls = new List(); var operations = new TestOperations(calls) { ThrowOnConnect = true }; var host = new TestHost(calls); var controller = new LiveSessionController(operations); LiveSessionStartResult result = controller.Start(LiveOptions(), host); Assert.Equal(LiveSessionStartStatus.Failed, result.Status); Assert.Contains("connect failure", result.Error!.ToString()); Assert.False(controller.IsInWorld); Assert.Null(controller.CurrentSession); Assert.Equal(1, operations.DisposeCounts[operations.Sessions[0]]); Assert.Equal(1, host.DetachCount); } [Theory] [InlineData("create")] [InlineData("bind")] [InlineData("enter")] public void Start_OtherConstructionAndEntryFailuresConvergeOffline(string phase) { var calls = new List(); var operations = new TestOperations(calls) { ThrowOnCreate = phase == "create", ThrowOnEnterWorld = phase == "enter", }; var host = new TestHost(calls) { ThrowOnBind = phase == "bind" }; var controller = new LiveSessionController(operations); LiveSessionStartResult result = controller.Start(LiveOptions(), host); Assert.Equal(LiveSessionStartStatus.Failed, result.Status); Assert.Contains($"{phase} failure", result.Error!.ToString()); Assert.False(controller.IsInWorld); Assert.Null(controller.CurrentSession); if (phase == "create") { Assert.Empty(operations.Sessions); Assert.Equal(1, host.ResetCount); } else { Assert.Single(operations.Sessions); Assert.Equal(1, operations.DisposeCounts[operations.Sessions[0]]); Assert.Equal(2, host.ResetCount); } Assert.Equal(phase == "enter" ? 1 : 0, host.DetachCount); } [Fact] public void Start_HealthyDuplicateIsIdempotent() { var calls = new List(); var operations = new TestOperations(calls); var host = new TestHost(calls); var controller = new LiveSessionController(operations); LiveSessionStartResult first = controller.Start(LiveOptions(), host); LiveSessionStartResult duplicate = controller.Start(LiveOptions(), host); Assert.Equal(LiveSessionStartStatus.Connected, duplicate.Status); Assert.Equal(first.Selection, duplicate.Selection); Assert.Single(operations.Sessions); Assert.Equal(1, host.ResetCount); Assert.Equal(1, host.ActivateCount); } [Fact] public void Reconnect_QuiescesAThenDisposesDetachesResetsBeforeConstructingB() { var calls = new List(); var operations = new TestOperations(calls); var host = new TestHost(calls); var controller = new LiveSessionController(operations); controller.Start(LiveOptions(), host); WorldSession sessionA = operations.Sessions[0]; TestCommandBus commandsA = host.CommandBuses[0]; calls.Clear(); LiveSessionStartResult result = controller.Reconnect(LiveOptions(), host); Assert.Equal(LiveSessionStartStatus.Connected, result.Status); Assert.Equal( [ "deactivate", "detach-events", "dispose-session", "detach-session", "reset", "resolve", "create", "bind", "report-connecting", "connect", "report-connected", "selected", "enter:1", "activate", "entered", ], calls); commandsA.Publish(new object()); Assert.Equal(0, commandsA.PublishCount); Assert.Equal(1, operations.DisposeCounts[sessionA]); Assert.Equal(2, operations.Sessions.Count); Assert.Same(operations.Sessions[1], controller.CurrentSession); } [Theory] [InlineData("detach", "stop")] [InlineData("detach", "dispose")] [InlineData("detach", "reconnect")] [InlineData("reset", "stop")] [InlineData("reset", "dispose")] [InlineData("reset", "reconnect")] public void Reconnect_ReentrantTeardownRequestNeverPublishesSupersededB( string callbackPhase, string request) { var calls = new List(); var operations = new TestOperations(calls); var host = new TestHost(calls); var controller = new LiveSessionController(operations); controller.Start(LiveOptions(), host); bool requested = false; Action callback = () => { if (requested) return; requested = true; switch (request) { case "stop": controller.Stop(); break; case "dispose": controller.Dispose(); break; case "reconnect": Assert.Equal( LiveSessionStartStatus.Deferred, controller.Reconnect(LiveOptions(), host).Status); break; } }; if (callbackPhase == "detach") host.OnDetach = callback; else host.OnReset = callback; LiveSessionStartResult outer = controller.Reconnect(LiveOptions(), host); Assert.Equal(LiveSessionStartStatus.Deferred, outer.Status); Assert.Equal(request == "reconnect" ? 2 : 1, operations.Sessions.Count); Assert.Equal(request == "reconnect", controller.IsInWorld); if (request == "dispose") Assert.Throws(() => controller.Start(LiveOptions(), host)); controller.Dispose(); } [Fact] public void MismatchedBindingWithInterruptedDetachRetainsCleanupOwnershipForRetry() { var calls = new List(); var operations = new TestOperations(calls); using var mismatchedSession = new WorldSession( new IPEndPoint(IPAddress.Loopback, 9001), new TestTransport()); var host = new TestHost(calls) { BindingSessionOverride = mismatchedSession, FailEventDetachOnce = true, }; var controller = new LiveSessionController(operations); LiveSessionStartResult result = controller.Start(LiveOptions(), host); Assert.Equal(LiveSessionStartStatus.Failed, result.Status); Assert.Contains("different session", result.Error!.ToString()); Assert.Equal(0, operations.DisposeCounts.GetValueOrDefault(operations.Sessions[0])); Assert.Equal(1, host.DeactivateCount); Assert.Equal(1, host.EventDetachCount); controller.Stop(); Assert.Equal(1, operations.DisposeCounts[operations.Sessions[0]]); Assert.Equal(1, host.DeactivateCount); Assert.Equal(2, host.EventDetachCount); Assert.Equal(1, host.DetachCount); controller.Dispose(); } [Theory] [InlineData(ReentrantStopPoint.Reset)] [InlineData(ReentrantStopPoint.Resolve)] [InlineData(ReentrantStopPoint.Create)] [InlineData(ReentrantStopPoint.Bind)] [InlineData(ReentrantStopPoint.Connecting)] [InlineData(ReentrantStopPoint.Connect)] [InlineData(ReentrantStopPoint.Connected)] [InlineData(ReentrantStopPoint.Selected)] [InlineData(ReentrantStopPoint.EnterWorld)] [InlineData(ReentrantStopPoint.Activate)] [InlineData(ReentrantStopPoint.Entered)] public void ReentrantStop_InvalidatesOuterGenerationAndCannotResurrect( ReentrantStopPoint point) { var calls = new List(); var operations = new TestOperations(calls); var host = new TestHost(calls); var controller = new LiveSessionController(operations); Action stop = controller.Stop; switch (point) { case ReentrantStopPoint.Reset: host.OnReset = stop; break; case ReentrantStopPoint.Resolve: operations.OnResolve = stop; break; case ReentrantStopPoint.Create: operations.OnCreate = stop; break; case ReentrantStopPoint.Bind: host.OnBind = stop; break; case ReentrantStopPoint.Connecting: host.OnConnecting = stop; break; case ReentrantStopPoint.Connect: operations.OnConnect = stop; break; case ReentrantStopPoint.Connected: host.OnConnected = stop; break; case ReentrantStopPoint.Selected: host.OnSelected = stop; break; case ReentrantStopPoint.EnterWorld: operations.OnEnterWorld = stop; break; case ReentrantStopPoint.Activate: host.OnActivate = stop; break; case ReentrantStopPoint.Entered: host.OnEntered = stop; break; } LiveSessionStartResult result = controller.Start(LiveOptions(), host); Assert.Equal(LiveSessionStartStatus.Deferred, result.Status); Assert.False(controller.IsInWorld); Assert.Null(controller.CurrentSession); Assert.Same(NullCommandBus.Instance, controller.Commands); if (operations.Sessions.Count == 0) { Assert.Contains( point, new[] { ReentrantStopPoint.Reset, ReentrantStopPoint.Resolve }); } else { Assert.Equal(1, operations.DisposeCounts[operations.Sessions[0]]); } Assert.Equal( point is ReentrantStopPoint.Reset or ReentrantStopPoint.Resolve or ReentrantStopPoint.Create ? 0 : 1, host.DetachCount); } [Fact] public void ReentrantDuplicateStartIsDeferredWithoutDisturbingOuterAttempt() { var calls = new List(); var operations = new TestOperations(calls); var host = new TestHost(calls); var controller = new LiveSessionController(operations); LiveSessionStartResult? nested = null; host.OnSelected = () => nested = controller.Start(LiveOptions(), host); LiveSessionStartResult outer = controller.Start(LiveOptions(), host); Assert.Equal(LiveSessionStartStatus.Deferred, nested!.Status); Assert.Equal(LiveSessionStartStatus.Connected, outer.Status); Assert.True(controller.IsInWorld); Assert.Single(operations.Sessions); } [Fact] public void ReentrantStartFromFailingEnteredCallbackCannotObserveUncommittedSession() { var calls = new List(); var operations = new TestOperations(calls); var host = new TestHost(calls); var controller = new LiveSessionController(operations); LiveSessionStartResult? nested = null; host.OnEntered = () => { nested = controller.Start(LiveOptions(), host); throw new InvalidOperationException("entered failure"); }; LiveSessionStartResult outer = controller.Start(LiveOptions(), host); Assert.Equal(LiveSessionStartStatus.Deferred, nested!.Status); Assert.Equal(LiveSessionStartStatus.Failed, outer.Status); Assert.False(controller.IsInWorld); Assert.Null(controller.CurrentSession); controller.Dispose(); } [Theory] [InlineData("reconnect")] [InlineData("dispose")] public void ReentrantLifecycleRequestDuringActivationDoesNotLeakActiveCommands( string request) { var calls = new List(); var operations = new TestOperations(calls); var host = new TestHost(calls); var controller = new LiveSessionController(operations); bool requested = false; host.OnActivate = () => { if (requested) return; requested = true; if (request == "reconnect") controller.Reconnect(LiveOptions(), host); else controller.Dispose(); }; LiveSessionStartResult outer = controller.Start(LiveOptions(), host); Assert.Equal(LiveSessionStartStatus.Deferred, outer.Status); Assert.False(host.CommandBuses[0].Active); Assert.Equal(request == "reconnect" ? 2 : 1, operations.Sessions.Count); Assert.Equal(request == "reconnect", controller.IsInWorld); controller.Dispose(); } [Theory] [InlineData("connecting")] [InlineData("connected")] [InlineData("characters")] [InlineData("selected")] [InlineData("activate")] [InlineData("entered")] public void Start_CallbackFailureConvergesOffline(string phase) { var calls = new List(); var operations = new TestOperations(calls); var host = new TestHost(calls); switch (phase) { case "connecting": host.ThrowOnConnecting = true; break; case "connected": host.ThrowOnConnected = true; break; case "characters": operations.ThrowOnCharacters = true; break; case "selected": host.ThrowOnSelected = true; break; case "activate": host.ThrowOnActivate = true; break; case "entered": host.ThrowOnEntered = true; break; } var controller = new LiveSessionController(operations); LiveSessionStartResult result = controller.Start(LiveOptions(), host); Assert.Equal(LiveSessionStartStatus.Failed, result.Status); Assert.Contains($"{phase} failure", result.Error!.ToString()); Assert.False(controller.IsInWorld); Assert.Null(controller.CurrentSession); Assert.Equal(1, operations.DisposeCounts[operations.Sessions[0]]); Assert.Same(NullCommandBus.Instance, controller.Commands); controller.Dispose(); } [Theory] [InlineData("deactivate")] [InlineData("events")] [InlineData("session")] [InlineData("detach")] [InlineData("reset")] public void Stop_RetriesExactFailedTeardownStageWithoutRepeatingCompletedWork( string phase) { var calls = new List(); var operations = new TestOperations(calls); var host = new TestHost(calls); var controller = new LiveSessionController(operations); controller.Start(LiveOptions(), host); WorldSession session = operations.Sessions[0]; switch (phase) { case "deactivate": host.FailDeactivateOnce = true; break; case "events": host.FailEventDetachOnce = true; break; case "session": operations.FailDisposeOnce = true; break; case "detach": host.FailDetachOnce = true; break; case "reset": host.FailResetOnce = true; break; } Assert.Throws(controller.Stop); controller.Stop(); Assert.False(controller.IsInWorld); Assert.Null(controller.CurrentSession); Assert.Equal(1, operations.DisposeCounts[session]); Assert.Equal(phase == "detach" ? 2 : 1, host.DetachCount); Assert.Equal(phase == "reset" ? 3 : 2, host.ResetCount); controller.Dispose(); } [Fact] public void ReconnectRequestedDuringTickRunsAfterTickAndReplacesExactGeneration() { var calls = new List(); var operations = new TestOperations(calls); var host = new TestHost(calls); var controller = new LiveSessionController(operations); controller.Start(LiveOptions(), host); WorldSession sessionA = operations.Sessions[0]; bool requested = false; operations.OnTick = () => { if (requested) return; requested = true; LiveSessionStartResult nested = controller.Reconnect(LiveOptions(), host); Assert.Equal(LiveSessionStartStatus.Deferred, nested.Status); }; controller.Tick(); Assert.True(controller.IsInWorld); Assert.Equal(2, operations.Sessions.Count); Assert.Equal(1, operations.DisposeCounts[sessionA]); Assert.Same(operations.Sessions[1], controller.CurrentSession); } [Fact] public void ResetFailureBlocksConstructionUntilRetryConverges() { var calls = new List(); var operations = new TestOperations(calls); var host = new TestHost(calls) { FailResetOnce = true }; var controller = new LiveSessionController(operations); LiveSessionStartResult first = controller.Start(LiveOptions(), host); LiveSessionStartResult second = controller.Start(LiveOptions(), host); Assert.Equal(LiveSessionStartStatus.Failed, first.Status); Assert.Equal(LiveSessionStartStatus.Connected, second.Status); Assert.Equal(2, host.ResetCount); Assert.Single(operations.Sessions); } [Fact] public void FailedDetachRetainsRetiredScopeAndBlocksBFactoryUntilRetry() { var calls = new List(); var operations = new TestOperations(calls); var host = new TestHost(calls) { FailDetachOnce = true }; var controller = new LiveSessionController(operations); controller.Start(LiveOptions(), host); WorldSession sessionA = operations.Sessions[0]; LiveSessionStartResult first = controller.Reconnect(LiveOptions(), host); Assert.Equal(LiveSessionStartStatus.Failed, first.Status); Assert.Single(operations.Sessions); Assert.Equal(1, operations.DisposeCounts[sessionA]); Assert.False(controller.IsInWorld); LiveSessionStartResult retry = controller.Reconnect(LiveOptions(), host); Assert.Equal(LiveSessionStartStatus.Connected, retry.Status); Assert.Equal(2, operations.Sessions.Count); Assert.Equal(1, operations.DisposeCounts[sessionA]); Assert.Equal(2, host.DetachCount); } [Fact] public void TickFailureCleansScopeBeforeRethrowing() { var calls = new List(); var operations = new TestOperations(calls) { ThrowOnTick = true }; var host = new TestHost(calls); var controller = new LiveSessionController(operations); controller.Start(LiveOptions(), host); WorldSession session = operations.Sessions[0]; Assert.Throws(controller.Tick); Assert.False(controller.IsInWorld); Assert.Null(controller.CurrentSession); Assert.Equal(1, operations.DisposeCounts[session]); } [Fact] public void DisposeIsIdempotentAndMakesOldCommandsInert() { var calls = new List(); var operations = new TestOperations(calls); var host = new TestHost(calls); var controller = new LiveSessionController(operations); controller.Start(LiveOptions(), host); WorldSession session = operations.Sessions[0]; TestCommandBus commands = host.CommandBuses[0]; controller.Dispose(); controller.Dispose(); commands.Publish(new object()); Assert.Equal(1, operations.DisposeCounts[session]); Assert.Equal(0, commands.PublishCount); Assert.False(controller.IsInWorld); Assert.Same(NullCommandBus.Instance, controller.Commands); Assert.Throws(() => controller.Start(LiveOptions(), host)); } private static RuntimeOptions LiveOptions( bool live = true, string? user = "user") { var environment = new Dictionary { ["ACDREAM_LIVE"] = live ? "1" : "0", ["ACDREAM_TEST_HOST"] = "127.0.0.1", ["ACDREAM_TEST_PORT"] = "9000", ["ACDREAM_TEST_USER"] = user, ["ACDREAM_TEST_PASS"] = "password", }; return RuntimeOptions.Parse( "dat", name => environment.GetValueOrDefault(name)); } private static CharacterList.Parsed AvailableCharacters() => new( 0u, [ new CharacterList.Character(0x50000001u, "Grey", 10u), new CharacterList.Character(0x50000002u, "Ready", 0u), ], [new CharacterList.Character(0x50000003u, "Deleted", 0u)], 11, "Canonical", true, true); }