using AcDream.App.Composition; using AcDream.App.Streaming; using DatReaderWriter.DBObjs; namespace AcDream.App.Tests.Composition; public sealed class SessionPlayerCompositionTests { [Fact] public void RuntimeBindingsReleaseInReverseAndRetryOnlyFailedEdges() { var calls = new List(); var bindings = new SessionPlayerRuntimeBindings(); bindings.Adopt("first", new RetryBinding("first", calls, 0)); bindings.Adopt("second", new RetryBinding("second", calls, 1)); Assert.Throws(bindings.Dispose); Assert.Equal(["second", "first"], calls); bindings.Dispose(); bindings.Dispose(); Assert.Equal(["second", "first", "second"], calls); Assert.Throws(() => bindings.Adopt("late", new RetryBinding("late", calls, 0))); } [Fact] public void SpawnClaimClassifierPreservesIndoorBoundaryMemoAndReset() { int lookups = 0; uint lastDid = 0; var info = new LandBlockInfo { NumCells = 2 }; var classifier = new DatSpawnClaimHydrationClassifier(did => { lookups++; lastDid = did; return info; }); Assert.False(classifier.IsUnhydratable(0x123400FFu)); Assert.Equal(0, lookups); Assert.False(classifier.IsUnhydratable(0x12340100u)); Assert.Equal(0x1234FFFEu, lastDid); Assert.False(classifier.IsUnhydratable(0x12340100u)); Assert.Equal(1, lookups); Assert.False(classifier.IsUnhydratable(0x12340101u)); Assert.True(classifier.IsUnhydratable(0x12340102u)); classifier.Reset(); Assert.True(classifier.IsUnhydratable(0x12340102u)); Assert.Equal(4, lookups); } [Fact] public void MissingOrEmptyLandblockMakesIndoorClaimUnhydratable() { var missing = new DatSpawnClaimHydrationClassifier(_ => null); var empty = new DatSpawnClaimHydrationClassifier( _ => new LandBlockInfo { NumCells = 0 }); Assert.True(missing.IsUnhydratable(0x12340100u)); Assert.True(empty.IsUnhydratable(0x12340100u)); } [Fact] public void GameWindowUsesSessionPhaseAndContainsNoPhaseSevenBody() { string root = FindRepoRoot(); string window = File.ReadAllText(Path.Combine( root, "src", "AcDream.App", "Rendering", "GameWindow.cs")); string phase = File.ReadAllText(Path.Combine( root, "src", "AcDream.App", "Composition", "SessionPlayerComposition.cs")); Assert.Contains("new SessionPlayerCompositionPhase(", window); Assert.DoesNotContain("LandblockStreamer.CreateForRequests(", window); Assert.DoesNotContain("new AcDream.App.Net.LiveSessionController()", window); Assert.DoesNotContain( "new AcDream.App.Streaming.LocalPlayerTeleportController(", window); Assert.DoesNotContain("IsSpawnClaimUnhydratable", window); Assert.Contains("LandblockStreamer.CreateForRequests(", phase); Assert.Contains("new LiveSessionController()", phase); Assert.Contains("new LocalPlayerTeleportController(", phase); Assert.Contains("new DatSpawnClaimHydrationClassifier(", phase); } [Fact] public void ProductionPhaseStartsStreamerBeforeSessionAndTransfersPortalLast() { string phase = File.ReadAllText(Path.Combine( FindRepoRoot(), "src", "AcDream.App", "Composition", "SessionPlayerComposition.cs")); AssertAppearsInOrder( phase, "LandblockStreamer.CreateForRequests(", "streamerLease.Resource.Start();", "new StreamingController(", "new WorldRevealCoordinator(", "new LiveSessionController()", "new LiveEntityHydrationController(", "new LiveEntityNetworkUpdateController(", "new GameplayInputFrameController(", "new PlayerModeController(", "d.PortalTunnelFallback.Transfer(", "d.TeleportSink.BindOwned(localTeleport)", "sessionRuntimeFactory.Create(liveSession)", "d.CombatModeCommands.BindOwned(combatCommand)", "d.RuntimeDiagnosticCommands.BindOwned(runtimeDiagnostics)", "GameplayInputActionRouter.Create(", "gameplayActions.Attach();", "_publication.PublishSessionPlayer(result);"); Assert.DoesNotContain("GameWindow", File.ReadAllText(Path.Combine( FindRepoRoot(), "src", "AcDream.App", "Net", "LiveSessionRuntimeFactory.cs")), StringComparison.Ordinal); } private sealed class RetryBinding( string name, List calls, int failures) : IDisposable { private int _failures = failures; public void Dispose() { calls.Add(name); if (_failures-- > 0) throw new InvalidOperationException("retry"); } } private static void AssertAppearsInOrder(string source, params string[] values) { int cursor = 0; foreach (string value in values) { int found = source.IndexOf(value, cursor, StringComparison.Ordinal); Assert.True(found >= 0, $"Missing expected source fragment: {value}"); cursor = found + value.Length; } } private static string FindRepoRoot() { DirectoryInfo? directory = new(AppContext.BaseDirectory); while (directory is not null) { if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx"))) return directory.FullName; directory = directory.Parent; } throw new DirectoryNotFoundException("Could not find AcDream.slnx."); } }