using System.Numerics; using AcDream.App.Input; using AcDream.App.Rendering; using AcDream.App.Streaming; using AcDream.App.World; using AcDream.Core.Lighting; using AcDream.Core.Physics; using AcDream.Core.Rendering; using AcDream.Core.World; using AcDream.Core.World.Cells; namespace AcDream.App.Tests.Rendering; public sealed class WorldRenderFrameBuilderTests { [Fact] public void Build_orders_world_preparation_and_combines_borrowed_results() { List calls = []; var camera = new FlyCamera { Position = new Vector3(11f, 22f, 33f) }; var cameraFrame = new WorldCameraFrame( camera, camera.Projection, camera.View * camera.Projection, default, Matrix4x4.Identity, camera.Position); WorldRootFrame roots = default; var animated = new HashSet { 41u, 42u }; var buildings = new List(); var foundation = new RenderFrameFoundation( PortalViewportVisible: false, Sky: default, Atmosphere: default); var visibility = new RecordingVisibility(calls); var environment = new RecordingEnvironment(calls); var builder = new WorldRenderFrameBuilder( new RecordingCamera(calls, cameraFrame), visibility, new RecordingSettings(calls), new RecordingRoots(calls, roots), environment, new RecordingAnimated(calls, animated), new RecordingBuildings(calls, new WorldBuildingFrame(null, buildings))); WorldRenderFrame result = builder.Build( in foundation, waitingForLogin: true, activeDayGroup: null); var drawableCells = new HashSet { 0x01010001u }; builder.ObserveDrawableCells(drawableCells); builder.ClearDrawableCells(); Assert.Equal( [ "camera", "visibility:begin", "settings", "roots", "environment", "visibility:projection", "animated", "buildings", ], calls); Assert.Same(camera, result.Camera.Camera); Assert.Same(animated, result.AnimatedEntityIds); Assert.Same(buildings, result.Buildings.NearbyBuildingCells); Assert.Null(result.ClipRoot); Assert.True(visibility.WaitingForLogin); Assert.Equal(foundation, environment.Foundation); Assert.Same(drawableCells, environment.DrawableCells); Assert.Equal(1, environment.ClearCount); } [Fact] public void Required_sources_fail_fast() { var camera = new RecordingCamera([], default); var visibility = new RecordingVisibility([]); var settings = new RecordingSettings([]); var roots = new RecordingRoots([], default); var environment = new RecordingEnvironment([]); var animated = new RecordingAnimated([], []); var buildings = new RecordingBuildings([], default); Assert.Throws(() => new WorldRenderFrameBuilder(null!, visibility, settings, roots, environment, animated, buildings)); Assert.Throws(() => new WorldRenderFrameBuilder(camera, null!, settings, roots, environment, animated, buildings)); Assert.Throws(() => new WorldRenderFrameBuilder(camera, visibility, null!, roots, environment, animated, buildings)); Assert.Throws(() => new WorldRenderFrameBuilder(camera, visibility, settings, null!, environment, animated, buildings)); Assert.Throws(() => new WorldRenderFrameBuilder(camera, visibility, settings, roots, null!, animated, buildings)); Assert.Throws(() => new WorldRenderFrameBuilder(camera, visibility, settings, roots, environment, null!, buildings)); Assert.Throws(() => new WorldRenderFrameBuilder(camera, visibility, settings, roots, environment, animated, null!)); } [Fact] public void Render_range_state_updates_both_tiers_without_replacing_the_source() { IWorldRenderRangeSource source = new WorldRenderRangeState(4, 12); var mutable = Assert.IsType(source); mutable.NearRadius = 7; mutable.FarRadius = 19; Assert.Equal(7, source.NearRadius); Assert.Equal(19, source.FarRadius); } [Fact] public void Runtime_animated_source_reuses_scratch_and_removes_stale_ids() { var slot = new LiveEntityRuntimeSlot(); var live = new LiveEntityAnimationRuntimeView(slot); var source = new RuntimeWorldFrameAnimatedEntitySource( live, statics: null, equipped: null); HashSet first = source.Capture(); first.Add(0xDEADBEEFu); HashSet second = source.Capture(); Assert.Same(first, second); Assert.Empty(second); } [Fact] public void Runtime_building_source_reuses_scratch_and_rebuilds_outdoor_root() { var pipeline = new LandblockPresentationPipeline( publishBeforeSpatialCommit: (_, _) => { }, state: new GpuWorldState()); var source = new RuntimeWorldFrameBuildingSource( pipeline, new CellVisibility()); FrustumPlanes frustum = default; WorldBuildingFrame first = source.Gather( viewerRoot: null, viewerCellId: 0x01010001u, in frustum); var scratch = Assert.IsType>(first.NearbyBuildingCells); scratch.Add(new LoadedCell { CellId = 0x01010100u }); WorldBuildingFrame second = source.Gather( viewerRoot: null, viewerCellId: 0x02020001u, in frustum); Assert.Same(scratch, second.NearbyBuildingCells); Assert.Empty(second.NearbyBuildingCells); Assert.Equal(0x01010001u, first.OutdoorNode?.CellId); Assert.Equal(0x02020001u, second.OutdoorNode?.CellId); } [Fact] public void Runtime_root_source_keeps_player_lighting_and_viewer_render_cells_distinct() { const uint playerCellId = 0x01010100u; const uint viewerCellId = 0x01010101u; var physics = new PhysicsEngine { DataCache = new PhysicsDataCache() }; physics.DataCache.CellGraph.Add(CoreCell(playerCellId, seenOutside: false)); physics.UpdatePlayerCurrCell(playerCellId); var cells = new CellVisibility(); var playerCell = new LoadedCell { CellId = playerCellId, SeenOutside = false }; var viewerCell = new LoadedCell { CellId = viewerCellId, SeenOutside = true }; cells.AddCell(playerCell); cells.AddCell(viewerCell); var retailChase = new RetailChaseCamera(); retailChase.Update( playerPosition: Vector3.Zero, playerYaw: 0f, playerVelocity: Vector3.Zero, isOnGround: true, contactPlaneNormal: Vector3.UnitZ, dt: 1f / 60f, cellId: viewerCellId); var mode = new LocalPlayerModeState { IsPlayerMode = true }; var chase = new ChaseCameraInputState { Retail = retailChase }; var origin = new LiveWorldOriginState(); origin.SetPlaceholder(0x01, 0x01); var source = new RuntimeWorldFrameRootSource( physics, cells, mode, chase, new LocalPlayerControllerSlot(), origin); var camera = new FlyCamera { Position = new Vector3(5f, 6f, 7f) }; WorldCameraFrame cameraFrame = CameraFrame(camera); bool previousRetailCamera = CameraDiagnostics.UseRetailChaseCamera; try { CameraDiagnostics.UseRetailChaseCamera = true; WorldRootFrame result = source.Resolve(in cameraFrame); Assert.Same(playerCell, result.PlayerRoot); Assert.Same(viewerCell, result.ViewerRoot); Assert.True(result.PlayerInsideCell); Assert.True(result.CameraInsideCell); Assert.False(result.PlayerSeenOutside); Assert.True(result.RootSeenOutside); Assert.True(result.RenderSky); Assert.Equal(playerCellId, result.PlayerCellId); Assert.Equal(viewerCellId, result.ViewerCellId); Assert.Equal(camera.Position, result.ViewerEyePosition); } finally { CameraDiagnostics.UseRetailChaseCamera = previousRetailCamera; } } [Fact] public void Runtime_root_source_preserves_null_root_fallback_before_player_membership() { var physics = new PhysicsEngine(); var source = new RuntimeWorldFrameRootSource( physics, new CellVisibility(), new LocalPlayerModeState(), new ChaseCameraInputState(), new LocalPlayerControllerSlot(), new LiveWorldOriginState()); WorldCameraFrame camera = CameraFrame(new FlyCamera()); WorldRootFrame result = source.Resolve(in camera); Assert.Null(result.PlayerRoot); Assert.Null(result.ViewerRoot); Assert.Equal(0u, result.PlayerCellId); Assert.Equal(0u, result.ViewerCellId); Assert.False(result.PlayerInsideCell); Assert.False(result.CameraInsideCell); Assert.True(result.RenderSky); } [Fact] public void Runtime_environment_copies_visible_cells_and_clear_restores_unscoped_lights() { const uint visibleCell = 0x01010100u; const uint hiddenCell = 0x01010101u; var lighting = new LightManager(); var visibleLight = new LightSource { Kind = LightKind.Point, CellId = visibleCell, }; var hiddenLight = new LightSource { Kind = LightKind.Point, CellId = hiddenCell, }; lighting.Register(visibleLight); lighting.Register(hiddenLight); var environment = new RuntimeWorldFrameEnvironmentPreparation( RuntimeOptions.Parse("test-dat", _ => null), new WorldTimeService(SkyStateProvider.Default()), lighting, dispatcher: null, environmentCells: null, lightingUbo: null, new WorldRenderRangeState(4, 12), skyPes: null); var borrowed = new HashSet { visibleCell }; environment.ObserveDrawableCells(borrowed); borrowed.Clear(); borrowed.Add(hiddenCell); WorldCameraFrame camera = CameraFrame(new FlyCamera()); WorldRootFrame roots = default; RenderFrameFoundation foundation = default; environment.Prepare(in camera, in roots, in foundation, activeDayGroup: null); Assert.Contains(visibleLight, lighting.PointSnapshot); Assert.DoesNotContain(hiddenLight, lighting.PointSnapshot); environment.ClearDrawableCells(); environment.Prepare(in camera, in roots, in foundation, activeDayGroup: null); Assert.Contains(visibleLight, lighting.PointSnapshot); Assert.Contains(hiddenLight, lighting.PointSnapshot); } [Fact] public void Production_builder_preserves_the_frame_preparation_order() { string source = BuilderSource(); AssertAppearsInOrder( source, "WorldCameraFrame camera = _camera.Resolve();", "_visibility.Begin(in camera, waitingForLogin);", "_settings.Apply(in camera);", "WorldRootFrame roots = _roots.Resolve(in camera);", "_environment.Prepare(in camera, in roots, in foundation, activeDayGroup);", "_visibility.PublishViewProjection(in camera);", "HashSet animated = _animated.Capture();", "WorldBuildingFrame buildings = _buildings.Gather("); } [Fact] public void Environment_preparation_keeps_lighting_snapshot_before_ubo_upload() { string source = BuilderSource(); AssertAppearsInOrder( source, "UpdateSunFromSky(foundation.Sky, roots.PlayerInsideCell);", "_lighting.UpdateViewerLight(roots.PlayerViewPosition);", "_lighting.Tick(camera.Position);", "_lighting.BuildPointLightSnapshot(", "_dispatcher?.SetSceneLights(_lighting.PointSnapshot);", "_environmentCells?.SetPointSnapshot(_lighting.PointSnapshot);", "SceneLightingUbo ubo = SceneLightingUbo.Build(", "_lightingUbo?.Upload(ubo);", "RenderingDiagnostics.EmitLight("); } [Fact] public void World_scene_uses_the_typed_builder_and_orchestrator_owns_its_local_composition() { string gameWindow = GameWindowSource(); string worldScene = File.ReadAllText(Path.Combine( FindRepoRoot(), "src", "AcDream.App", "Rendering", "WorldSceneRenderer.cs")); Assert.Equal( 1, CountOccurrences(worldScene, "_frames.Build(")); Assert.DoesNotContain("private void UpdateSunFromSky(", gameWindow, StringComparison.Ordinal); Assert.DoesNotContain("private void UpdateSkyPes(", gameWindow, StringComparison.Ordinal); Assert.DoesNotContain("private static float ParseEnvFloat(", gameWindow, StringComparison.Ordinal); Assert.DoesNotContain( "private AcDream.App.Rendering.WorldRenderFrameBuilder?", gameWindow, StringComparison.Ordinal); Assert.DoesNotContain( "private AcDream.App.Rendering.SkyPesFrameController?", gameWindow, StringComparison.Ordinal); AssertAppearsInOrder( worldScene, "_alpha.BeginFrame();", "_frames.Build(", "if (_login.IsWaitingForLogin)", "_passes.DrawFlatTerrain(", "NormalWorldDrawn: true"); AssertAppearsInOrder( gameWindow, "var skyPesFrame = new AcDream.App.Rendering.SkyPesFrameController(", "var worldRenderFrameBuilder =", "new AcDream.App.Rendering.RenderFrameOrchestrator(", "_frameGraphs.Withdraw();", "new(\"equipped children\"", "new(\"effect network state\"", "new(\"audio\"", "new(\"mesh draw dispatcher\"", "new(\"environment cells\"", "new(\"scene lighting\""); } private static string BuilderSource() => File.ReadAllText(Path.Combine( FindRepoRoot(), "src", "AcDream.App", "Rendering", "WorldRenderFrameBuilder.cs")); private static WorldCameraFrame CameraFrame(FlyCamera camera) => new( camera, camera.Projection, camera.View * camera.Projection, default, Matrix4x4.Identity, camera.Position); private static AcDream.Core.World.Cells.EnvCell CoreCell( uint id, bool seenOutside) => new( id, Matrix4x4.Identity, Matrix4x4.Identity, Vector3.Zero, Vector3.One, Array.Empty(), Array.Empty(), seenOutside, containmentBsp: null); private static string GameWindowSource() => File.ReadAllText(Path.Combine( FindRepoRoot(), "src", "AcDream.App", "Rendering", "GameWindow.cs")); private static int CountOccurrences(string source, string needle) { int count = 0; int cursor = 0; while ((cursor = source.IndexOf(needle, cursor, StringComparison.Ordinal)) >= 0) { count++; cursor += needle.Length; } return count; } 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 RecordingCamera( List calls, WorldCameraFrame result) : IWorldFrameCameraSource { public WorldCameraFrame Resolve() { calls.Add("camera"); return result; } } private sealed class RecordingVisibility(List calls) : IWorldFrameVisibilityPreparation { public bool WaitingForLogin { get; private set; } public void Begin(in WorldCameraFrame camera, bool waitingForLogin) { calls.Add("visibility:begin"); WaitingForLogin = waitingForLogin; } public void PublishViewProjection(in WorldCameraFrame camera) => calls.Add("visibility:projection"); } private sealed class RecordingSettings(List calls) : IWorldFrameSettingsPreview { public void Apply(in WorldCameraFrame camera) => calls.Add("settings"); } private sealed class RecordingRoots( List calls, WorldRootFrame result) : IWorldFrameRootSource { public WorldRootFrame Resolve(in WorldCameraFrame camera) { calls.Add("roots"); return result; } } private sealed class RecordingEnvironment(List calls) : IWorldFrameEnvironmentPreparation { public RenderFrameFoundation Foundation { get; private set; } public IReadOnlySet? DrawableCells { get; private set; } public int ClearCount { get; private set; } public void Prepare( in WorldCameraFrame camera, in WorldRootFrame roots, in RenderFrameFoundation foundation, DayGroupData? activeDayGroup) { calls.Add("environment"); Foundation = foundation; } public void ObserveDrawableCells(IReadOnlySet drawableCells) => DrawableCells = drawableCells; public void ClearDrawableCells() => ClearCount++; } private sealed class RecordingAnimated( List calls, HashSet result) : IWorldFrameAnimatedEntitySource { public HashSet Capture() { calls.Add("animated"); return result; } } private sealed class RecordingBuildings( List calls, WorldBuildingFrame result) : IWorldFrameBuildingSource { public WorldBuildingFrame Gather( LoadedCell? viewerRoot, uint viewerCellId, in FrustumPlanes frustum) { calls.Add("buildings"); return result; } } }