diff --git a/docs/reviews/2026-08-18-r3-test-truth-ledger.md b/docs/reviews/2026-08-18-r3-test-truth-ledger.md
index 240a090c..d3b65536 100644
--- a/docs/reviews/2026-08-18-r3-test-truth-ledger.md
+++ b/docs/reviews/2026-08-18-r3-test-truth-ledger.md
@@ -38,12 +38,12 @@ pwsh ./tools/audit-test-inventory.ps1
The generated JSON lives under ignored `artifacts/test-audit/`; it is not a
second 6.8 MB checked-in source of truth. The script and this reviewed ledger
are durable, while paths and line numbers regenerate after every batch. The
-inventory refreshed through Batch U reports:
+inventory refreshed through Batch W reports:
| Syntax signal | Current count |
|---|---:|
-| Tracked/parsed C# test files | 1,256 / 1,256 |
-| Attributed test methods (not expanded theory cases) | 11,447 |
+| Tracked/parsed C# test files | 1,254 / 1,254 |
+| Attributed test methods (not expanded theory cases) | 11,416 |
| Exact duplicate-data rows | 0 |
| Reviewed body-equivalent groups / methods | 11 / 27 |
| Methods containing at least one empty `return;` | 138 |
@@ -56,7 +56,7 @@ inventory refreshed through Batch U reports:
| Methods directly using `Thread.Sleep` / `Task.Delay` | 15 / 14 |
| Cancellable-infinite-only / elapsed-time methods | 5 / 24 |
| Methods directly reading environment variables | 47 |
-| Methods directly / through same-file helpers reading `.cs` source text | 67 / 107 |
+| Methods directly / through same-file helpers reading `.cs` source text | 63 / 98 |
The three remaining prerequisite-return candidates are all reviewed branch
false positives: the two Windows/Linux factory assertions and the launcher's
@@ -140,7 +140,7 @@ identifiable in the lane report.
| T-009 wall-clock double-click tests | resolved in batch E | Four sleeps were replaced by a deterministic test clock behind an internal factory overload. The production factory still reads `Environment.TickCount64` exactly as before. |
| T-010 two useless cases | high-confidence cleanup batch A | Delete `SmokeTest.TestProject_IsWired` and `ChaseCameraTests.ImplementsICamera`; compilation already proves both claims. |
| T-011 diagnostic-only methods | resolved in batches C, L, and R | The reviewed current set is 82 methods / 103 cases. All carry `Purpose=Diagnostic`, preserving the apparatus while removing it from release pass totals. Batch R catches investigations whose only assertion validates fixture/DAT availability, which the original mechanical output-only scan could not distinguish from an oracle. |
-| T-012 source-text freezes | direct-read map completed in batch I, helper-mediated gap corrected in batch U, and staged replacement approved | Seventeen whole-tree architecture rules and five cross-artifact contracts stay. The other 85 are literal implementation or test-model freezes; approval authorizes retiring each only beside its semantic/behavioral replacement. |
+| T-012 source-text freezes | direct-read map completed in batch I, helper-mediated gap corrected in batch U, and staged replacement active | Seventeen whole-tree architecture rules and five cross-artifact contracts stay. Batch W retires the first nine literal implementation freezes beside stronger behavior or compiled-metadata evidence; 76 staged replacements remain. |
| T-013 controller self-comparison | high-confidence cleanup batch A | Capture the first controller next to the first body and compare every retry with that reference. |
| T-014 seven load-sensitive tests | six mechanisms repaired in batch F; one product defect classified | Virtual/wall-clock mixing, tiered-JIT allocation noise, a live controller clock, and a ThreadPool-start timing oracle were removed without weakening behavioral contracts. `DatSoundCache` #321 is a real in-flight-entry race and now runs as `Status=KnownFailure` pending a product fix. |
| T-015 four non-prerequisite skips | resolved in batches A/B | PVS scaffold deleted with rationale preserved; redundant chat/radar generators deleted in favor of the comprehensive Manual lane; tower oracle is `Status=KnownFailure`. |
@@ -1027,3 +1027,61 @@ Verification:
and
- the no-retry complete hermetic Release gate passes 14,351/14,351 with zero
skips or failures across all 12 test assemblies, exactly 28 below Batch U.
+
+## Batch W render delegation and cleanup source-freeze replacement
+
+Batch W retires the first nine of the 85 approved implementation-text freezes
+without changing product code. Six test methods now inspect runtime behavior,
+reflection metadata, or compiled call/new-object edges; three redundant source
+tests are removed because an existing behavioral trace already asserts the
+same contract more strongly.
+
+The exact decisions are:
+
+- `TextRendererConstructorOwnsExactlyOneDeviceResource` no longer counts
+ constructor strings. `TextRendererConstructionCreatesAndDisposesOnlyOnePipeline`
+ constructs the real renderer through `RecordingGpuDevice`, proves that only
+ one pipeline is added (no buffer, texture, sampler, or texture slot), and
+ proves that exact pipeline is disposed. The V4a/V6d rollback rationale stays
+ beside the test. The recording device gained read-only created-resource lists
+ solely so tests can observe those device calls.
+- `Renderer_source_preserves_retail_stage_order_through_typed_operations` is
+ removed. The existing outdoor and interior `DrawInside` tests already drive
+ the real renderer and assert the typed operation sequence, including early
+ and late landscape, particle flush, interior clear, masks, shells, and entity
+ routes.
+- `Production_uses_retained_routes_without_rebuilding_legacy_partition` is
+ removed. The existing production-product test is renamed to state this
+ contract and continues to prove one actual retained candidate with zero
+ compare-only referee work. Exact private selector spellings are not a product
+ oracle.
+- `Production_builder_preserves_the_frame_preparation_order` is removed. The
+ existing builder test already supplies recording implementations of every
+ typed source and asserts the complete call order plus the borrowed result.
+- executor reset/diagnostic bracketing, the one-executor/one-PView composition,
+ lighting snapshot-before-UBO order, typed world-frame build, local frame-root
+ composition, and GameWindow delegation now inspect compiled call/new-object
+ edges and reflected owner fields. `CompiledCallGraph` is shared test-only
+ infrastructure that parses the built method body; formatting, comments, local
+ variable names, and source paths cannot satisfy or break these contracts.
+
+Two mixed source assertions were deliberately narrowed rather than silently
+carried forward. Absence of `Console.WriteLine` is an implementation-style
+check with no output oracle and is not treated as render behavior. The old
+world-scene source test also embedded a separate teardown-order claim; that
+claim remains with the dedicated lifetime suite and is reconciled in the
+approved host/lifetime replacement batch instead of coupling teardown to frame
+construction.
+
+Verification:
+
+- the four directly affected App suites pass 39/39;
+- the complete locked Release build covers all 44 projects with zero warnings
+ and zero errors;
+- the no-retry complete hermetic Release gate passes 14,348/14,348 with zero
+ skips or failures across all 12 test assemblies; the exact three-case
+ reduction is the three redundant source tests above; and
+- the regenerated 1,254-file inventory parses every file and reduces direct
+ source readers from 67 to 63 and total direct/helper readers from 107 to 98.
+ The remaining 98 reconcile to the 22 approved retained policies/contracts
+ and 76 staged replacements.
diff --git a/tests/AcDream.App.Tests/Architecture/CompiledCallGraph.cs b/tests/AcDream.App.Tests/Architecture/CompiledCallGraph.cs
new file mode 100644
index 00000000..29e10362
--- /dev/null
+++ b/tests/AcDream.App.Tests/Architecture/CompiledCallGraph.cs
@@ -0,0 +1,109 @@
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace AcDream.App.Tests.Architecture;
+
+internal readonly record struct CompiledCall(int Offset, MethodBase Target);
+
+///
+/// Reads compiled call/new-object edges from a method body. Architecture tests
+/// use this when the contract is an ownership or ordering edge that cannot be
+/// exercised through a public result, avoiding formatting- and comment-sensitive
+/// source-string assertions.
+///
+internal static class CompiledCallGraph
+{
+ private static readonly IReadOnlyDictionary OpCodesByValue =
+ typeof(OpCodes)
+ .GetFields(BindingFlags.Public | BindingFlags.Static)
+ .Where(field => field.FieldType == typeof(OpCode))
+ .Select(field => (OpCode)field.GetValue(null)!)
+ .ToDictionary(opCode => opCode.Value);
+
+ public static IReadOnlyList Read(MethodBase method)
+ {
+ ArgumentNullException.ThrowIfNull(method);
+ byte[] il = method.GetMethodBody()?.GetILAsByteArray()
+ ?? throw new InvalidOperationException(
+ $"{method.DeclaringType?.FullName}.{method.Name} has no compiled body.");
+ Type[]? declaringArguments = method.DeclaringType?.IsGenericType == true
+ ? method.DeclaringType.GetGenericArguments()
+ : null;
+ Type[]? methodArguments = method.IsGenericMethod
+ ? method.GetGenericArguments()
+ : null;
+ var calls = new List();
+
+ for (int cursor = 0; cursor < il.Length;)
+ {
+ int instructionOffset = cursor;
+ OpCode opCode = ReadOpCode(il, ref cursor);
+ if (opCode.OperandType == OperandType.InlineMethod)
+ {
+ int token = BitConverter.ToInt32(il, cursor);
+ MethodBase? target = method.Module.ResolveMethod(
+ token,
+ declaringArguments,
+ methodArguments);
+ if (target is not null && opCode is { Value: var value }
+ && value is 0x28 or 0x6F or 0x73)
+ {
+ calls.Add(new CompiledCall(instructionOffset, target));
+ }
+ }
+
+ cursor += OperandSize(opCode.OperandType, il, cursor);
+ }
+
+ return calls;
+ }
+
+ public static int IndexOf(
+ IReadOnlyList calls,
+ Type declaringType,
+ string methodName,
+ int startIndex = 0) =>
+ Enumerable.Range(startIndex, calls.Count - startIndex)
+ .FirstOrDefault(
+ index => calls[index].Target.DeclaringType == declaringType
+ && calls[index].Target.Name == methodName,
+ -1);
+
+ private static OpCode ReadOpCode(byte[] il, ref int cursor)
+ {
+ byte first = il[cursor++];
+ short value = first == 0xFE
+ ? unchecked((short)(0xFE00 | il[cursor++]))
+ : first;
+ return OpCodesByValue.TryGetValue(value, out OpCode opCode)
+ ? opCode
+ : throw new InvalidOperationException(
+ $"Unknown IL opcode 0x{unchecked((ushort)value):X4}.");
+ }
+
+ private static int OperandSize(OperandType operandType, byte[] il, int cursor) =>
+ operandType switch
+ {
+ OperandType.InlineNone => 0,
+ OperandType.ShortInlineBrTarget or
+ OperandType.ShortInlineI or
+ OperandType.ShortInlineVar => 1,
+ OperandType.InlineVar => 2,
+ OperandType.InlineBrTarget or
+ OperandType.InlineField or
+ OperandType.InlineI or
+ OperandType.InlineMethod or
+ OperandType.InlineSig or
+ OperandType.InlineString or
+ OperandType.InlineTok or
+ OperandType.InlineType or
+ OperandType.ShortInlineR => 4,
+ OperandType.InlineI8 or OperandType.InlineR => 8,
+ OperandType.InlineSwitch =>
+ sizeof(int) + (BitConverter.ToInt32(il, cursor) * sizeof(int)),
+ _ => throw new ArgumentOutOfRangeException(
+ nameof(operandType),
+ operandType,
+ "Unsupported IL operand type."),
+ };
+}
diff --git a/tests/AcDream.App.Tests/Rendering/Gpu/RecordingGpuDevice.cs b/tests/AcDream.App.Tests/Rendering/Gpu/RecordingGpuDevice.cs
index ff582e45..3dafc000 100644
--- a/tests/AcDream.App.Tests/Rendering/Gpu/RecordingGpuDevice.cs
+++ b/tests/AcDream.App.Tests/Rendering/Gpu/RecordingGpuDevice.cs
@@ -84,6 +84,9 @@ internal sealed class RecordingGpuDevice : IGpuDevice
private readonly List _calls = [];
private readonly List _queuedActions = [];
+ private readonly List _createdBuffers = [];
+ private readonly List _createdPipelines = [];
+ private readonly List _createdSamplers = [];
private readonly Dictionary _samplers = [];
private readonly byte[] _ring;
private readonly Stack _freeTextureSlots = new();
@@ -151,8 +154,18 @@ internal sealed class RecordingGpuDevice : IGpuDevice
public void Clear() => _calls.Clear();
- public IGpuBuffer CreateBuffer(in GpuBufferDescription description) =>
- new RecordingGpuBuffer(description);
+ public IReadOnlyList CreatedBuffers => _createdBuffers;
+
+ public IReadOnlyList CreatedPipelines => _createdPipelines;
+
+ public IReadOnlyList CreatedSamplers => _createdSamplers;
+
+ public IGpuBuffer CreateBuffer(in GpuBufferDescription description)
+ {
+ var buffer = new RecordingGpuBuffer(description);
+ _createdBuffers.Add(buffer);
+ return buffer;
+ }
///
/// Campaign V slice V6i-2: every image this device made, in creation order.
@@ -185,13 +198,16 @@ internal sealed class RecordingGpuDevice : IGpuDevice
RecordingGpuSampler created = new(description);
_samplers.Add(description, created);
+ _createdSamplers.Add(created);
return created;
}
public IGpuPipeline CreatePipeline(GpuPipelineDescription description)
{
ArgumentNullException.ThrowIfNull(description);
- return new RecordingGpuPipeline(description);
+ var pipeline = new RecordingGpuPipeline(description);
+ _createdPipelines.Add(pipeline);
+ return pipeline;
}
public IGpuRenderTarget CreateRenderTarget(in GpuRenderTargetDescription description) =>
diff --git a/tests/AcDream.App.Tests/Rendering/ResourceCleanupGroupTests.cs b/tests/AcDream.App.Tests/Rendering/ResourceCleanupGroupTests.cs
index 736ac2b0..885d749c 100644
--- a/tests/AcDream.App.Tests/Rendering/ResourceCleanupGroupTests.cs
+++ b/tests/AcDream.App.Tests/Rendering/ResourceCleanupGroupTests.cs
@@ -1,4 +1,6 @@
using AcDream.App.Rendering;
+using AcDream.App.Rendering.Gpu;
+using AcDream.App.Tests.Rendering.Gpu;
namespace AcDream.App.Tests.Rendering;
@@ -88,48 +90,34 @@ public sealed class ResourceCleanupGroupTests
/// re-growing a second resource without re-growing the rollback fails here.
///
[Fact]
- public void TextRendererConstructorOwnsExactlyOneDeviceResource()
+ public void TextRendererConstructionCreatesAndDisposesOnlyOnePipeline()
{
- string source = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Rendering",
- "TextRenderer.cs"));
+ using var device = new RecordingGpuDevice();
+ int buffersBefore = device.CreatedBuffers.Count;
+ int pipelinesBefore = device.CreatedPipelines.Count;
+ int samplersBefore = device.CreatedSamplers.Count;
+ int texturesBefore = device.CreatedTextures.Count;
+ int slotsBefore = device.LiveTextureSlotCount;
- Assert.Equal(1, CountOccurrences(source, "device.CreatePipeline("));
- Assert.Equal(0, CountOccurrences(source, "device.CreateTexture("));
- Assert.Equal(0, CountOccurrences(source, "device.CreateBuffer("));
- Assert.Equal(0, CountOccurrences(source, "device.CreateSampler("));
- Assert.Equal(0, CountOccurrences(source, "device.RegisterTexture("));
+ var renderer = new TextRenderer(
+ device,
+ new NullGpuFrameSource(),
+ shaderDir: "unused");
- // And the one resource is released.
- Assert.Contains("public void Dispose() => _pipeline.Dispose();", source, StringComparison.Ordinal);
+ RecordingGpuPipeline pipeline = Assert.Single(
+ device.CreatedPipelines.Skip(pipelinesBefore));
+ Assert.Equal(buffersBefore, device.CreatedBuffers.Count);
+ Assert.Equal(samplersBefore, device.CreatedSamplers.Count);
+ Assert.Equal(texturesBefore, device.CreatedTextures.Count);
+ Assert.Equal(slotsBefore, device.LiveTextureSlotCount);
+
+ renderer.Dispose();
+
+ Assert.True(pipeline.IsDisposed);
}
- private static int CountOccurrences(string source, string needle)
+ private sealed class NullGpuFrameSource : ICurrentGpuFrameSource
{
- int count = 0;
- for (int i = source.IndexOf(needle, StringComparison.Ordinal);
- i >= 0;
- i = source.IndexOf(needle, i + needle.Length, StringComparison.Ordinal))
- {
- count++;
- }
-
- return count;
- }
-
- 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.");
+ public IGpuFrame? CurrentFrame => null;
}
}
diff --git a/tests/AcDream.App.Tests/Rendering/RetailPViewPassExecutorTests.cs b/tests/AcDream.App.Tests/Rendering/RetailPViewPassExecutorTests.cs
index ac1047f6..5cb62a18 100644
--- a/tests/AcDream.App.Tests/Rendering/RetailPViewPassExecutorTests.cs
+++ b/tests/AcDream.App.Tests/Rendering/RetailPViewPassExecutorTests.cs
@@ -1,7 +1,9 @@
using System.Numerics;
using System.Reflection;
+using AcDream.App.Composition;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Scene;
+using AcDream.App.Tests.Architecture;
using AcDream.Core.World;
namespace AcDream.App.Tests.Rendering;
@@ -219,7 +221,7 @@ public sealed class RetailPViewPassExecutorTests
}
[Fact]
- public void DrawInside_production_product_does_not_require_compare_only_referee()
+ public void DrawInside_production_product_uses_retained_routes_without_legacy_partition_when_diagnostics_are_disabled()
{
LoadedCell interior = InteriorWithExit(0xA9B40100u);
WorldEntity dynamic = Entity(
@@ -274,70 +276,6 @@ public sealed class RetailPViewPassExecutorTests
Assert.Contains("look-in-punch", executor.Operations);
}
- [Fact]
- public void Renderer_source_preserves_retail_stage_order_through_typed_operations()
- {
- string source = RendererSource();
- string drawInside = MethodBody(source, "public RetailPViewFrameResult DrawInside(");
- AssertAppearsInOrder(
- drawInside,
- "passes.BeginFrame();",
- "passes.EmitDiagnostics(ctx, result);",
- "DrawLandscapeThroughOutsideView(",
- "DrawExitPortalMasks(ctx, passes",
- "DrawEnvCellShells(passes, pvFrame);",
- "DrawCellObjectLists(",
- "DrawDynamicsLast(");
-
- string landscape = MethodBody(
- source,
- "private void DrawLandscapeThroughOutsideView(");
- AssertAppearsInOrder(
- landscape,
- "passes.SetTerrainClip(slice.Planes);",
- "passes.DrawLandscapeSlice(",
- "DrawBuildingLookIns(",
- "passes.DrawLandscapeSliceLate(",
- "passes.DrawUnattachedSceneParticles(ctx);",
- "passes.FlushLandscapeAlpha();",
- "passes.ClearInteriorDepth();");
-
- string lookIns = MethodBody(source, "private void DrawBuildingLookIns(");
- AssertAppearsInOrder(
- lookIns,
- "passes.DrawLookInPortalPunch(ctx",
- "passes.DrawOpaqueCellShells(_shellBatch);",
- "passes.DrawCellParticles(ctx");
- }
-
- [Fact]
- public void Production_uses_retained_routes_without_rebuilding_legacy_partition()
- {
- string source = RendererSource();
- string drawInside = MethodBody(
- source,
- "public RetailPViewFrameResult DrawInside(");
-
- Assert.Contains(
- "if (_sceneFrameProduct is null || LegacyPartitionDiagnosticsEnabled)",
- drawInside,
- StringComparison.Ordinal);
- AssertAppearsInOrder(
- drawInside,
- "_sceneFrameProduct.BuildAndBorrow(",
- "if (_sceneFrameProduct is null || LegacyPartitionDiagnosticsEnabled)",
- "InteriorEntityPartition.Partition(");
-
- Assert.Contains(
- "RenderFrameRouteOwnerSelector.Replace(",
- source,
- StringComparison.Ordinal);
- Assert.Contains(
- "RenderFrameRouteOwnerSelector.ExceptRoute(",
- source,
- StringComparison.Ordinal);
- }
-
[Fact]
public void Extracted_contracts_retain_no_window_callbacks_or_visibility_owner()
{
@@ -360,61 +298,61 @@ public sealed class RetailPViewPassExecutorTests
[Fact]
public void Concrete_executor_forwards_frame_reset_and_brackets_terrain_diagnostics()
{
- string source = ExecutorClassSource();
- string begin = MethodBody(source, "public void BeginFrame()");
- Assert.Contains("_particleClassifications.BeginFrame();", begin);
+ MethodInfo begin = typeof(RetailPViewPassExecutor).GetMethod(
+ nameof(RetailPViewPassExecutor.BeginFrame))!;
+ IReadOnlyList beginCalls = CompiledCallGraph.Read(begin);
+ Assert.True(
+ CompiledCallGraph.IndexOf(
+ beginCalls,
+ typeof(RetailPViewParticleClassifications),
+ nameof(RetailPViewParticleClassifications.BeginFrame)) >= 0);
- string landscape = MethodBody(source, "public void DrawLandscapeSlice(");
- AssertAppearsInOrder(
- landscape,
- "_terrainDiagnostics.Begin();",
- "_terrain?.Draw(",
- "_terrainDiagnostics.Complete();");
+ MethodInfo landscape = typeof(RetailPViewPassExecutor).GetMethod(
+ nameof(RetailPViewPassExecutor.DrawLandscapeSlice))!;
+ IReadOnlyList landscapeCalls = CompiledCallGraph.Read(landscape);
+ int diagnosticsBegin = RequiredCallIndex(
+ landscapeCalls,
+ typeof(TerrainDrawDiagnosticsController),
+ nameof(TerrainDrawDiagnosticsController.Begin));
+ int terrainDraw = RequiredCallIndex(
+ landscapeCalls,
+ typeof(TerrainModernRenderer),
+ nameof(TerrainModernRenderer.Draw));
+ int diagnosticsComplete = RequiredCallIndex(
+ landscapeCalls,
+ typeof(TerrainDrawDiagnosticsController),
+ nameof(TerrainDrawDiagnosticsController.Complete));
- Assert.DoesNotContain("Console.WriteLine", source);
- Assert.DoesNotContain("Console.WriteLine", RendererSource());
+ Assert.True(diagnosticsBegin < terrainDraw);
+ Assert.True(terrainDraw < diagnosticsComplete);
}
[Fact]
public void GameWindow_composes_one_executor_instead_of_a_draw_callback_bag()
{
- string source = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "FrameRootComposition.cs"));
+ MethodInfo compose = typeof(FrameRootCompositionPhase).GetMethod(
+ "ComposeCore",
+ BindingFlags.Instance | BindingFlags.NonPublic)!;
+ IReadOnlyList calls = CompiledCallGraph.Read(compose);
- Assert.Contains("new RetailPViewPassExecutor(", source);
- Assert.Contains("new WorldScenePViewRenderer(", source);
- Assert.DoesNotContain("new AcDream.App.Rendering.RetailPViewFrameInput", source);
- Assert.DoesNotContain("RetailPViewDrawContext", source);
- Assert.DoesNotContain("DrawLandscapeSlice =", source);
- Assert.DoesNotContain("DrawExitPortalMasks =", source);
- }
+ int executor = RequiredCallIndex(
+ calls,
+ typeof(RetailPViewPassExecutor),
+ ".ctor");
+ int renderer = RequiredCallIndex(
+ calls,
+ typeof(WorldScenePViewRenderer),
+ ".ctor");
- private static string RendererSource() => File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Rendering",
- "RetailPViewRenderer.cs"));
-
- private static string ExecutorSource() => File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Rendering",
- "RetailPViewPassExecutor.cs"));
-
- private static string ExecutorClassSource()
- {
- string source = ExecutorSource();
- int start = source.IndexOf(
- "internal sealed class RetailPViewPassExecutor",
- StringComparison.Ordinal);
- Assert.True(start >= 0, "Missing concrete RetailPViewPassExecutor.");
- return source[start..];
+ Assert.True(executor < renderer);
+ Assert.Single(
+ calls,
+ call => call.Target.DeclaringType == typeof(RetailPViewPassExecutor)
+ && call.Target.Name == ".ctor");
+ Assert.Single(
+ calls,
+ call => call.Target.DeclaringType == typeof(WorldScenePViewRenderer)
+ && call.Target.Name == ".ctor");
}
private static RetailPViewFrameInput Frame(
@@ -565,23 +503,6 @@ public sealed class RetailPViewPassExecutorTests
ParentCellId = parentCellId,
};
- private static string MethodBody(string source, string signature)
- {
- int start = source.IndexOf(signature, StringComparison.Ordinal);
- Assert.True(start >= 0, $"Missing method: {signature}");
- int open = source.IndexOf('{', start);
- int depth = 0;
- for (int index = open; index < source.Length; index++)
- {
- if (source[index] == '{')
- depth++;
- else if (source[index] == '}' && --depth == 0)
- return source[start..(index + 1)];
- }
-
- throw new InvalidOperationException($"Unterminated method: {signature}");
- }
-
private static void AssertAppearsInOrder(string source, params string[] needles)
{
int cursor = -1;
@@ -593,17 +514,14 @@ public sealed class RetailPViewPassExecutorTests
}
}
- private static string FindRepoRoot()
+ private static int RequiredCallIndex(
+ IReadOnlyList calls,
+ Type declaringType,
+ string methodName)
{
- 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.");
+ int index = CompiledCallGraph.IndexOf(calls, declaringType, methodName);
+ Assert.True(index >= 0, $"Missing compiled call: {declaringType.Name}.{methodName}");
+ return index;
}
private sealed class DictionaryCellSource(
diff --git a/tests/AcDream.App.Tests/Rendering/WorldRenderFrameBuilderTests.cs b/tests/AcDream.App.Tests/Rendering/WorldRenderFrameBuilderTests.cs
index ca519a80..e00587f2 100644
--- a/tests/AcDream.App.Tests/Rendering/WorldRenderFrameBuilderTests.cs
+++ b/tests/AcDream.App.Tests/Rendering/WorldRenderFrameBuilderTests.cs
@@ -1,7 +1,11 @@
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;
@@ -290,113 +294,111 @@ public sealed class WorldRenderFrameBuilderTests
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();
+ MethodInfo prepare = typeof(RuntimeWorldFrameEnvironmentPreparation).GetMethod(
+ nameof(RuntimeWorldFrameEnvironmentPreparation.Prepare))!;
+ IReadOnlyList calls = CompiledCallGraph.Read(prepare);
- 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(");
+ Type[] ownerOrder =
+ [
+ typeof(RuntimeWorldFrameEnvironmentPreparation),
+ typeof(LightManager),
+ typeof(LightManager),
+ typeof(LightManager),
+ typeof(WbDrawDispatcher),
+ typeof(EnvCellRenderer),
+ typeof(SceneLightingUbo),
+ typeof(SceneLightingUboBinding),
+ typeof(RenderingDiagnostics),
+ ];
+ string[] methodOrder =
+ [
+ "UpdateSunFromSky",
+ nameof(LightManager.UpdateViewerLight),
+ nameof(LightManager.Tick),
+ nameof(LightManager.BuildPointLightSnapshot),
+ nameof(WbDrawDispatcher.SetSceneLights),
+ nameof(EnvCellRenderer.SetPointSnapshot),
+ nameof(SceneLightingUbo.Build),
+ nameof(SceneLightingUboBinding.Upload),
+ nameof(RenderingDiagnostics.EmitLight),
+ ];
+
+ AssertCompiledCallOrder(calls, ownerOrder, methodOrder);
}
[Fact]
public void World_scene_uses_the_typed_builder_and_orchestrator_owns_its_local_composition()
{
- string gameWindow = GameWindowSource();
- string lifetime = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Rendering",
- "GameWindowLifetime.cs"));
- string frameRoot = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "FrameRootComposition.cs"));
- string worldScene = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Rendering",
- "WorldSceneRenderer.cs"));
+ 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");
- 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);
+ FieldInfo[] windowFields = typeof(GameWindow).GetFields(
+ BindingFlags.Instance
+ | BindingFlags.Static
+ | BindingFlags.Public
+ | BindingFlags.NonPublic
+ | BindingFlags.DeclaredOnly);
Assert.DoesNotContain(
- "private AcDream.App.Rendering.WorldRenderFrameBuilder?",
- gameWindow,
- StringComparison.Ordinal);
- Assert.DoesNotContain(
- "private AcDream.App.Rendering.SkyPesFrameController?",
- gameWindow,
- StringComparison.Ordinal);
+ 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).
- AssertAppearsInOrder(
- worldScene,
- "_alpha.BeginFrame();",
- "_frames.Build(",
- "_passes.DrawFlatTerrain(",
- "NormalWorldDrawn: true");
- AssertAppearsInOrder(
- frameRoot,
- "var skyPesFrame = new SkyPesFrameController(",
- "var worldRenderFrameBuilder =",
- "new RenderFrameOrchestrator(",
- "d.FrameGraphs.PublishOwned(");
- AssertAppearsInOrder(
- lifetime,
- "frame.FrameGraphPublication?.Dispose()",
- "Hard(\"equipped children\"",
- "Hard(\"effect network state\"",
- "Hard(\"audio\"",
- "Hard(\"mesh draw dispatcher\"",
- "Hard(\"environment cells\"",
- "Hard(\"scene lighting\"");
- }
+ MethodInfo render = typeof(WorldSceneRenderer).GetMethod(
+ nameof(WorldSceneRenderer.Render))!;
+ IReadOnlyList 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));
- private static string BuilderSource() => File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Rendering",
- "WorldRenderFrameBuilder.cs"));
+ MethodInfo compose = typeof(FrameRootCompositionPhase).GetMethod(
+ "ComposeCore",
+ BindingFlags.Instance | BindingFlags.NonPublic)!;
+ IReadOnlyList 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,
@@ -419,51 +421,27 @@ public sealed class WorldRenderFrameBuilderTests
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)
+ private static void AssertCompiledCallOrder(
+ IReadOnlyList calls,
+ IReadOnlyList declaringTypes,
+ IReadOnlyList methodNames)
{
+ Assert.Equal(declaringTypes.Count, methodNames.Count);
int cursor = -1;
- foreach (string needle in needles)
+ for (int index = 0; index < declaringTypes.Count; index++)
{
- 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}");
+ 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 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
diff --git a/tests/AcDream.App.Tests/Rendering/WorldSceneRendererTests.cs b/tests/AcDream.App.Tests/Rendering/WorldSceneRendererTests.cs
index 1be9e085..dd620f14 100644
--- a/tests/AcDream.App.Tests/Rendering/WorldSceneRendererTests.cs
+++ b/tests/AcDream.App.Tests/Rendering/WorldSceneRendererTests.cs
@@ -1,9 +1,11 @@
using System.Numerics;
using System.Reflection;
+using AcDream.App.Composition;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Selection;
using AcDream.App.Rendering.Vfx;
using AcDream.App.Streaming;
+using AcDream.App.Tests.Architecture;
using AcDream.Core.Rendering;
using AcDream.Core.World;
using AcDream.Runtime;
@@ -326,20 +328,46 @@ public sealed class WorldSceneRendererTests
[Fact]
public void GameWindow_DelegatesWorldRenderingWithoutOwningDrawBranches()
{
- string source = File.ReadAllText(Path.Combine(
- FindRepoRoot(),
- "src",
- "AcDream.App",
- "Composition",
- "FrameRootComposition.cs"));
+ MethodInfo compose = typeof(FrameRootCompositionPhase).GetMethod(
+ "ComposeCore",
+ BindingFlags.Instance | BindingFlags.NonPublic)!;
+ IReadOnlyList calls = CompiledCallGraph.Read(compose);
- Assert.Contains("new WorldSceneRenderer(", source);
- Assert.Contains("new RenderFrameOrchestrator(", source);
- Assert.DoesNotContain("_worldSceneRenderer", source);
- Assert.DoesNotContain("_retailPViewRenderer.DrawInside(", source);
- Assert.DoesNotContain("_retailAlphaQueue.BeginFrame();", source);
- Assert.DoesNotContain("_terrainDrawDiagnostics!.Begin();", source);
- Assert.DoesNotContain("SkipWorldGeometry:", source);
+ int worldRenderer = RequiredCallIndex(
+ calls,
+ typeof(WorldSceneRenderer),
+ ".ctor");
+ int frameOrchestrator = RequiredCallIndex(
+ calls,
+ typeof(RenderFrameOrchestrator),
+ ".ctor");
+ Assert.True(worldRenderer < frameOrchestrator);
+ Assert.Single(
+ calls,
+ call => call.Target.DeclaringType == typeof(WorldSceneRenderer)
+ && call.Target.Name == ".ctor");
+ Assert.Single(
+ calls,
+ call => call.Target.DeclaringType == typeof(RenderFrameOrchestrator)
+ && call.Target.Name == ".ctor");
+
+ FieldInfo[] windowFields = typeof(GameWindow).GetFields(
+ BindingFlags.Instance
+ | BindingFlags.Public
+ | BindingFlags.NonPublic
+ | BindingFlags.DeclaredOnly);
+ Assert.DoesNotContain(
+ windowFields,
+ field => field.FieldType == typeof(WorldSceneRenderer)
+ || field.FieldType == typeof(RetailPViewRenderer)
+ || field.FieldType == typeof(TerrainDrawDiagnosticsController)
+ || field.FieldType == typeof(RenderFrameOrchestrator));
+
+ FieldInfo worldPhase = Assert.Single(
+ typeof(RenderFrameOrchestrator).GetFields(
+ BindingFlags.Instance | BindingFlags.NonPublic),
+ field => field.FieldType == typeof(IWorldSceneFramePhase));
+ Assert.Equal("_world", worldPhase.Name);
}
[Fact]
@@ -771,28 +799,13 @@ public sealed class WorldSceneRendererTests
[]);
}
- private static int CountOccurrences(string source, string value)
+ private static int RequiredCallIndex(
+ IReadOnlyList calls,
+ Type declaringType,
+ string methodName)
{
- int count = 0;
- int cursor = 0;
- while ((cursor = source.IndexOf(value, cursor, StringComparison.Ordinal)) >= 0)
- {
- count++;
- cursor += value.Length;
- }
- return count;
- }
-
- 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.");
+ int index = CompiledCallGraph.IndexOf(calls, declaringType, methodName);
+ Assert.True(index >= 0, $"Missing compiled call: {declaringType.Name}.{methodName}");
+ return index;
}
}