acdream/tests/AcDream.App.Tests/Rendering/WorldRenderFrameBuilderTests.cs
Erik 0c5057c9ff fix #435 (part 1): delete 17 probes that outlived their closed investigations
Each of these was temporary apparatus added to chase one bug, and each was
supposed to be deleted in the commit that fixed it. Fourteen closed issues
later they were still here: #337's support/wire-mesh trio, #171's sticky
timeline, #119's viewer and entity dumps, #113's phantom probe, and a dozen
more. 3,493 lines removed; the client now reads 144 environment variables
instead of 161, and 47 temporary probes remain instead of 64.

This is not only tidying. Every probe leaves a branch on its hot path when
unset, several re-read the environment per call rather than caching, and
the volume buries the diagnostics that are actually load-bearing. It is
also a headless correctness matter: HeadlessStaticStateAudit reflects over
PhysicsDiagnostics' flags to refuse a multi-session host when any is set,
and cannot see probes that live outside that owner.

Four files went entirely — WalkMissDiagnostic.cs, CollisionMeshWireframe.cs
and two test files whose only subject was a deleted probe.
TransitionTypes.SetContactPlane also sheds its CallerMemberName /
CallerLineNumber parameters, which existed solely for #337's cpSrc=
attribution and carried the instruction to strip them with the probe
family; no call site passed them, so no behavior changes. F2's collision
overlay survives and reverts to its proxy-cylinder form, which is what
removing the ACDREAM_WIRE_MESH upgrade means.

LaunchOptionsDocumentationTests earned its keep here: it refused the
deletion until docs/launch-options.md moved the 17 rows into Retired and
the frozen direct-read counts came down (PhysicsEngine.cs to zero,
TransitionTypes.cs 3 to 2). The documentation could not drift during a
cleanup this wide.

The 14 probes that name no owning issue are deliberately NOT deleted.
Nothing records when they became safe to remove, and guessing is how a
future investigation loses apparatus it needed; #435 stays open for their
attribution.

Full hermetic suite 15,321 passed / 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 11:41:20 +02:00

535 lines
20 KiB
C#

using System.Numerics;
using System.Reflection;
using AcDream.App.Composition;
using AcDream.App.Input;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Wb;
using AcDream.App.Streaming;
using AcDream.App.Tests.Architecture;
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;
[Collection(CameraDiagnosticsCollection.Name)]
public sealed class WorldRenderFrameBuilderTests
{
[Fact]
public void Build_orders_world_preparation_and_combines_borrowed_results()
{
List<string> 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<uint> { 41u, 42u };
var buildings = new List<LoadedCell>();
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<uint> { 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<ArgumentNullException>(() =>
new WorldRenderFrameBuilder(null!, visibility, settings, roots, environment, animated, buildings));
Assert.Throws<ArgumentNullException>(() =>
new WorldRenderFrameBuilder(camera, null!, settings, roots, environment, animated, buildings));
Assert.Throws<ArgumentNullException>(() =>
new WorldRenderFrameBuilder(camera, visibility, null!, roots, environment, animated, buildings));
Assert.Throws<ArgumentNullException>(() =>
new WorldRenderFrameBuilder(camera, visibility, settings, null!, environment, animated, buildings));
Assert.Throws<ArgumentNullException>(() =>
new WorldRenderFrameBuilder(camera, visibility, settings, roots, null!, animated, buildings));
Assert.Throws<ArgumentNullException>(() =>
new WorldRenderFrameBuilder(camera, visibility, settings, roots, environment, null!, buildings));
Assert.Throws<ArgumentNullException>(() =>
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<WorldRenderRangeState>(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<LiveEntityAnimationState>(slot);
var source = new RuntimeWorldFrameAnimatedEntitySource(
live,
statics: null,
equipped: null);
HashSet<uint> first = source.Capture();
first.Add(0xDEADBEEFu);
HashSet<uint> 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<List<LoadedCell>>(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 RuntimeLocalPlayerMovementState(),
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 RuntimeLocalPlayerMovementState(),
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<uint> { 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 Environment_preparation_keeps_lighting_snapshot_before_ubo_upload()
{
MethodInfo prepare = typeof(RuntimeWorldFrameEnvironmentPreparation).GetMethod(
nameof(RuntimeWorldFrameEnvironmentPreparation.Prepare))!;
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(prepare);
Type[] ownerOrder =
[
typeof(RuntimeWorldFrameEnvironmentPreparation),
typeof(LightManager),
typeof(LightManager),
typeof(LightManager),
typeof(WbDrawDispatcher),
typeof(EnvCellRenderer),
typeof(SceneLightingUbo),
typeof(SceneLightingUboBinding),
];
string[] methodOrder =
[
"UpdateSunFromSky",
nameof(LightManager.UpdateViewerLight),
nameof(LightManager.Tick),
nameof(LightManager.BuildPointLightSnapshot),
nameof(WbDrawDispatcher.SetSceneLights),
nameof(EnvCellRenderer.SetPointSnapshot),
nameof(SceneLightingUbo.Build),
nameof(SceneLightingUboBinding.Upload),
];
AssertCompiledCallOrder(calls, ownerOrder, methodOrder);
}
[Fact]
public void World_scene_uses_the_typed_builder_and_orchestrator_owns_its_local_composition()
{
MethodInfo[] windowMethods = typeof(GameWindow).GetMethods(
BindingFlags.Instance
| BindingFlags.Static
| BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.DeclaredOnly);
Assert.DoesNotContain(
windowMethods,
method => method.Name is "UpdateSunFromSky" or "UpdateSkyPes" or "ParseEnvFloat");
FieldInfo[] windowFields = typeof(GameWindow).GetFields(
BindingFlags.Instance
| BindingFlags.Static
| BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.DeclaredOnly);
Assert.DoesNotContain(
windowFields,
field => field.FieldType == typeof(WorldRenderFrameBuilder)
|| field.FieldType == typeof(SkyPesFrameController));
// Gate-fix round (2026-08-17): the renderer's own IsWaitingForLogin
// short-circuit is deleted — the frame gate
// (LocalPlayerTeleportRenderStateSource) folds the waiting state
// into PortalViewportVisible, so the portal-visible return above
// covers every waiting frame (one gate computes, this phase
// enforces).
MethodInfo render = typeof(WorldSceneRenderer).GetMethod(
nameof(WorldSceneRenderer.Render))!;
IReadOnlyList<CompiledCall> renderCalls = CompiledCallGraph.Read(render);
AssertCompiledCallOrder(
renderCalls,
[
typeof(IWorldSceneAlphaFrame),
typeof(IWorldRenderFrameBuilder),
typeof(IWorldScenePassExecutor),
typeof(WorldRenderFrameOutcome),
],
[
nameof(IWorldSceneAlphaFrame.BeginFrame),
nameof(IWorldRenderFrameBuilder.Build),
nameof(IWorldScenePassExecutor.DrawFlatTerrain),
".ctor",
]);
Assert.Single(
renderCalls,
call => call.Target.DeclaringType == typeof(IWorldRenderFrameBuilder)
&& call.Target.Name == nameof(IWorldRenderFrameBuilder.Build));
MethodInfo compose = typeof(FrameRootCompositionPhase).GetMethod(
"ComposeCore",
BindingFlags.Instance | BindingFlags.NonPublic)!;
IReadOnlyList<CompiledCall> composeCalls = CompiledCallGraph.Read(compose);
AssertCompiledCallOrder(
composeCalls,
[
typeof(SkyPesFrameController),
typeof(WorldRenderFrameBuilder),
typeof(RenderFrameOrchestrator),
typeof(GameFrameGraphSlot),
],
[".ctor", ".ctor", ".ctor", nameof(GameFrameGraphSlot.PublishOwned)]);
// The former source assertion also mixed in teardown ordering. That
// distinct lifetime contract remains covered by the dedicated lifetime
// suite and is reconciled with the later host/lifetime R3 batch.
}
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<CellPortal>(),
Array.Empty<uint>(),
seenOutside,
containmentBsp: null);
private static void AssertCompiledCallOrder(
IReadOnlyList<CompiledCall> calls,
IReadOnlyList<Type> declaringTypes,
IReadOnlyList<string> methodNames)
{
Assert.Equal(declaringTypes.Count, methodNames.Count);
int cursor = -1;
for (int index = 0; index < declaringTypes.Count; index++)
{
int next = CompiledCallGraph.IndexOf(
calls,
declaringTypes[index],
methodNames[index],
cursor + 1);
Assert.True(
next > cursor,
$"Missing or out-of-order compiled call: {declaringTypes[index].Name}.{methodNames[index]}");
cursor = next;
}
}
private sealed class RecordingCamera(
List<string> calls,
WorldCameraFrame result) : IWorldFrameCameraSource
{
public WorldCameraFrame Resolve()
{
calls.Add("camera");
return result;
}
}
private sealed class RecordingVisibility(List<string> 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<string> calls)
: IWorldFrameSettingsPreview
{
public void Apply(in WorldCameraFrame camera) => calls.Add("settings");
}
private sealed class RecordingRoots(
List<string> calls,
WorldRootFrame result) : IWorldFrameRootSource
{
public WorldRootFrame Resolve(in WorldCameraFrame camera)
{
calls.Add("roots");
return result;
}
}
private sealed class RecordingEnvironment(List<string> calls)
: IWorldFrameEnvironmentPreparation
{
public RenderFrameFoundation Foundation { get; private set; }
public IReadOnlySet<uint>? 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<uint> drawableCells) =>
DrawableCells = drawableCells;
public void ClearDrawableCells() => ClearCount++;
}
private sealed class RecordingAnimated(
List<string> calls,
HashSet<uint> result) : IWorldFrameAnimatedEntitySource
{
public HashSet<uint> Capture()
{
calls.Add("animated");
return result;
}
}
private sealed class RecordingBuildings(
List<string> calls,
WorldBuildingFrame result) : IWorldFrameBuildingSource
{
public WorldBuildingFrame Gather(
LoadedCell? viewerRoot,
uint viewerCellId,
in FrustumPlanes frustum)
{
calls.Add("buildings");
return result;
}
}
}