using AcDream.App.Rendering; namespace AcDream.App.Tests.Rendering; public sealed class RenderFrameResourceControllerTests { [Fact] public void Prepare_runs_begin_clear_and_live_in_order_for_one_gpu_slot() { List calls = []; var clearResult = new RenderFrameFoundation( PortalViewportVisible: true, Sky: default, Atmosphere: default); var slots = new AdvancingSlotSource(2); var controller = new RenderFrameResourceController( slots, new RecordingBegin(calls), new RecordingClear(calls, clearResult), new RecordingLive(calls)); controller.Prepare(new RenderFrameInput(1d / 60d, 1280, 720)); Assert.Equal(["begin:2", "clear", "live:2"], calls); Assert.Equal(1, slots.ReadCount); Assert.Equal(clearResult, controller.Foundation); } [Fact] public void Required_phase_dependencies_fail_fast() { var slots = new AdvancingSlotSource(0); var begin = new RecordingBegin([]); var clear = new RecordingClear([], default); var live = new RecordingLive([]); Assert.Throws(() => new RenderFrameResourceController(null!, begin, clear, live)); Assert.Throws(() => new RenderFrameResourceController(slots, null!, clear, live)); Assert.Throws(() => new RenderFrameResourceController(slots, begin, null!, live)); Assert.Throws(() => new RenderFrameResourceController(slots, begin, clear, null!)); } [Fact] public void Production_begin_resources_preserve_the_exact_frame_order() { string source = ResourceSource(); AssertAppearsInOrder( source, "_textures?.BeginCompositeTextureFrame();", "_textures?.TickCompositeTextureCache();", "_dispatcher?.BeginFrame(gpuSlot);", "_environmentCells?.BeginFrame(gpuSlot);", "_portalDepth?.BeginFrame(gpuSlot);", "_worldText?.BeginFrame(gpuSlot);", "_uiText?.BeginFrame(gpuSlot);", "_clip?.BeginFrame(gpuSlot);", "_terrain?.BeginFrame(gpuSlot);", "_lighting?.BeginFrame(gpuSlot);", "_profiler.FrameBoundary(_gl);"); } [Fact] public void Production_live_preparation_publishes_meshes_before_reveal_and_particles() { string source = ResourceSource(); AssertAppearsInOrder( source, "_meshes?.Tick();", "_worldReveal?.PrepareAndEvaluate(revealCell);", "_particles?.BeginFrame(gpuSlot);"); } [Fact] public void Production_clear_phase_preserves_atmosphere_and_gl_state_order() { string source = ResourceSource(); AssertAppearsInOrder( source, "bool portalViewportVisible = _portal.IsPortalViewportVisible;", "_particleVisibility.Reset();", "SkyKeyframe sky = _worldTime.CurrentSky;", "AtmosphereSnapshot atmosphere = _weather.Snapshot(in sky);", "_frameGlState.RestoreFrameDefaults();", "_gl.Clear(", "_diagnostics.EmitGlStateTripwireIfChanged("); } [Fact] public void Weather_frame_clock_advances_only_after_the_weather_tick() { var controller = new RenderWeatherFrameController( new AcDream.Core.World.WorldTimeService( AcDream.Core.World.SkyStateProvider.Default()), new AcDream.Core.World.WeatherSystem()); controller.Tick(0.25d); controller.Tick(0.5d); Assert.Equal(0.75d, controller.ElapsedSeconds); AssertAppearsInOrder( ResourceSource(), "_weather.Tick(", "nowSeconds: _elapsedSeconds,", "_elapsedSeconds += deltaSeconds;"); } [Fact] public void Login_state_is_latched_by_the_shared_player_mode_source() { var mode = new AcDream.App.Input.LocalPlayerModeState(); var live = new AcDream.App.Rendering.RenderLoginStateSource( liveMode: true, mode); var offline = new AcDream.App.Rendering.RenderLoginStateSource( liveMode: false, mode); Assert.True(live.IsWaitingForLogin); Assert.False(offline.IsWaitingForLogin); mode.ChaseModeEverEntered = true; Assert.False(live.IsWaitingForLogin); } private static string ResourceSource() => File.ReadAllText(Path.Combine( FindRepoRoot(), "src", "AcDream.App", "Rendering", "RenderFrameResourceController.cs")); private static void AssertAppearsInOrder(string source, params string[] needles) { int cursor = -1; foreach (string needle in needles) { int next = source.IndexOf(needle, cursor + 1, StringComparison.Ordinal); Assert.True(next >= 0, $"Missing expected source fragment: {needle}"); Assert.True(next > cursor, $"Out-of-order source fragment: {needle}"); cursor = next; } } 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."); } private sealed class AdvancingSlotSource(int firstSlot) : IRenderFrameSlotSource { public int ReadCount { get; private set; } public int CurrentSlot => firstSlot + ReadCount++; } private sealed class RecordingBegin(List calls) : IRenderFrameBeginResources { public void Begin(int gpuSlot) => calls.Add($"begin:{gpuSlot}"); } private sealed class RecordingClear( List calls, RenderFrameFoundation result) : IRenderFrameClearPhase { public RenderFrameFoundation Clear() { calls.Add("clear"); return result; } } private sealed class RecordingLive(List calls) : IRenderFrameLivePreparation { public void Prepare(int gpuSlot) => calls.Add($"live:{gpuSlot}"); } }