using System.Reflection; using AcDream.App.Rendering; using AcDream.App.Net; using AcDream.Core.Net; using AcDream.Runtime; using AcDream.Runtime.Session; namespace AcDream.App.Tests.Net; public sealed class GameWindowLiveSessionOwnershipTests { private const BindingFlags PrivateInstance = BindingFlags.Instance | BindingFlags.NonPublic; [Fact] public void GameWindowRetainsCanonicalRuntimeAndFocusedHostButNoMirroredSession() { FieldInfo[] fields = typeof(GameWindow).GetFields(PrivateInstance); Assert.Contains( fields, field => field.Name == "_runtime" && field.FieldType == typeof(GameRuntime)); Assert.Contains( fields, field => field.Name == "_liveSessionHost" && field.FieldType == typeof(LiveSessionHost)); Assert.DoesNotContain( fields, field => field.Name == "_liveSessionController" || field.FieldType == typeof(LiveSessionController)); Assert.DoesNotContain(fields, field => field.Name == "_liveSession"); Assert.DoesNotContain(fields, field => field.FieldType == typeof(WorldSession)); Assert.DoesNotContain(fields, field => field.FieldType == typeof(LiveSessionResetPlan)); Assert.DoesNotContain(fields, field => field.Name == "_liveSessionEvents"); Assert.DoesNotContain(fields, field => field.Name == "_liveSessionCommands"); } [Fact] public void GraphicalSessionSourceBorrowsCanonicalRuntimeState() { FieldInfo[] fields = typeof(LiveSessionAppSource).GetFields(PrivateInstance); Assert.Equal(2, fields.Length); Assert.Contains( fields, field => field.Name == "_session" && field.FieldType == typeof(LiveSessionController)); Assert.Contains( fields, field => field.Name == "_commands" && field.FieldType == typeof(LiveSessionCommandSurface)); Assert.DoesNotContain(fields, field => field.FieldType == typeof(WorldSession)); Assert.DoesNotContain(fields, field => field.FieldType == typeof(bool)); Assert.DoesNotContain( fields, field => field.FieldType == typeof(RuntimeGenerationToken) || field.FieldType == typeof(ulong)); } [Fact] public void ProductionWindowConstructsOnlyTheCanonicalRuntimeRoot() { string root = FindRepositoryRoot(); string source = File.ReadAllText(Path.Combine( root, "src", "AcDream.App", "Rendering", "GameWindow.cs")); Assert.Equal( 1, CountOccurrences(source, "new GameRuntime(")); Assert.Contains( "private readonly GameRuntime _runtime;", source, StringComparison.Ordinal); Assert.Contains( "_runtimeHostLease = _runtime.AcquireHostLease(", source, StringComparison.Ordinal); string[] forbidden = [ "new RuntimeEntityObjectLifetime(", "new RuntimeInventoryState(", "new RuntimeCharacterState(", "new RuntimeCommunicationState(", "new RuntimeActionState(", "new RuntimeLocalPlayerMovementState(", "new RuntimeWorldTransitState(", "new LiveSessionController(", "new GameRuntimeClock(", ]; Assert.All( forbidden, value => Assert.DoesNotContain( value, source, StringComparison.Ordinal)); } [Fact] public void ProductionSourceConstructsOnlyOneLiveSessionCommandSurface() { // CH3 review S5(b): LiveSessionCommandSurface has no dependencies of // its own — CH3 deliberately hoisted its single construction site // (SessionPlayerComposition.cs) so RuntimeSettingsTargets and the // retained UI's chat/inventory panels share the SAME generation- // gated command route. A second construction site anywhere under // src/AcDream.App would silently split that route into two, each // with its own activation/dispose lifecycle. string root = FindRepositoryRoot(); string appRoot = Path.Combine(root, "src", "AcDream.App"); int total = Directory .EnumerateFiles(appRoot, "*.cs", SearchOption.AllDirectories) .Sum(path => CountOccurrences( File.ReadAllText(path), "new LiveSessionCommandSurface(")); Assert.Equal(1, total); } [Theory] [InlineData("TryStartLiveSession")] [InlineData("ClearInboundEntityState")] [InlineData("WireLiveSessionEvents")] [InlineData("DisposeLiveSessionRouting")] [InlineData("CreateLiveSessionBinding")] [InlineData("ApplyLiveSessionSelection")] [InlineData("ApplyLiveSessionEnteredWorld")] public void DisplacedLifecycleBodiesAreAbsent(string methodName) { Assert.Null(typeof(GameWindow).GetMethod(methodName, PrivateInstance)); } /// /// Campaign CC CC7 review-fix round, F5 (2026-08-16): the reviewer /// found that deleting the CharacterCreated/CreationFailed /// delegate assignments from /// (the App-layer wiring that forwards those two Runtime events to /// SessionStatusWriter, feeding the launcher's status-payload /// cycle) leaves every test suite green. LiveSessionRuntimeFactory /// has exactly one production construction site /// (SessionPlayerComposition.cs), buried inside the full /// GameWindow composition graph, and no test in this repository /// constructs it directly — there is no practical seam to exercise the /// wiring behaviorally without a . This test /// follows the SAME source-text-pin pattern the rest of this file /// already uses for wiring that can't otherwise be unit-tested /// (, /// ): it fails if either /// delegate assignment is removed or its argument mapping changes. The /// exact PAYLOAD shape these calls must produce is pinned separately, /// at SessionStatusWriterTests.CharacterCreatedAndCreationFailed_WriteThePinnedShape /// (tests/AcDream.Runtime.Tests/Session/SessionStatusWriterTests.cs) /// — together the two tests cover "the delegates are bound" (here) and /// "they produce §LA1's exact payload" (there). /// [Fact] public void LiveSessionRuntimeFactoryBindsCharacterCreatedAndCreationFailedToTheStatusWriter() { string root = FindRepositoryRoot(); string source = File.ReadAllText(Path.Combine( root, "src", "AcDream.App", "Net", "LiveSessionRuntimeFactory.cs")); Assert.Contains( "CharacterCreated: identity => _statusWriter.CharacterCreated(", source, StringComparison.Ordinal); Assert.Contains( "identity.Guid,", source, StringComparison.Ordinal); Assert.Contains( "identity.Name),", source, StringComparison.Ordinal); Assert.Contains( "CreationFailed: rejection => _statusWriter.CreationFailed(", source, StringComparison.Ordinal); Assert.Contains( "rejection.RawCode,", source, StringComparison.Ordinal); Assert.Contains( "rejection.Reason,", source, StringComparison.Ordinal); Assert.Contains( "rejection.AttemptedName)),", source, StringComparison.Ordinal); } private static int CountOccurrences(string source, string value) { int count = 0; int cursor = 0; while ((cursor = source.IndexOf( value, cursor, StringComparison.Ordinal)) >= 0) { count++; cursor += value.Length; } return count; } private static string FindRepositoryRoot() { var current = new DirectoryInfo(AppContext.BaseDirectory); while (current is not null) { if (File.Exists(Path.Combine(current.FullName, "AcDream.slnx"))) return current.FullName; current = current.Parent; } throw new DirectoryNotFoundException("AcDream.slnx was not found."); } }