test: replace frame orchestration source freezes

This commit is contained in:
Erik 2026-08-18 16:40:56 +02:00
parent 9bd5d47c47
commit 9b94050229
3 changed files with 501 additions and 466 deletions

View file

@ -1391,3 +1391,59 @@ Verification:
attributed methods, and reduces direct/total source readers from 39/55 to
32/46. The remaining 46 reconcile to the 22 approved retained
policies/contracts and 24 staged replacements.
## Batch AD frame orchestration and resource-preparation replacement
Batch AD converts all fourteen approved source readers in
`RenderFrameResourceControllerTests` and `UpdateFrameOrchestratorTests`. No
product source changes and no test is removed.
The three render-resource checks now inspect built call and field-store order.
They preserve the texture/dispatcher/environment/portal/clip/terrain/lighting
begin sequence, mesh publication before reveal evaluation and particle begin,
and weather evaluation before the elapsed-clock commit. The existing recording
phase test continues to exercise the public prepare sequence and one GPU-slot
read behaviorally; source comments and local expressions are no longer part of
the oracle.
The update-frame replacements retain the full accepted phase graph and its
existing behavioral fake trace, then use compiled metadata only for production
wiring that cannot safely be invoked without a real session or window:
- the physics script clock has one typed publisher behind `FrameRootCompositionPhase`
and no `GameWindow` publication edge;
- live-object, static-animation, effect, particle/script, and spatial-reconcile
calls preserve their registered adaptation order and exact-once tails;
- streaming precedes the live coordinator, whose inbound session and placement
retry precede command and spatial reconciliation on both available and
quiesced behavioral paths;
- session/frame composition constructs the live-object, spatial, streaming,
gameplay-input, teleport, liveness, auto-entry, camera, and update owners,
while reflected fields and compiled window calls reject the displaced bodies;
- focus loss, player-mode exit, and teleport start retain their typed
mouse-look cleanup edges, including freshness validation before input cleanup
and Runtime transit commit afterward;
- player presentation attachment retains Runtime-publication validation,
camera/shadow/host/mode order, and both cleanup routes; mouse filtering and
typed movement-input seams are checked from method and constructor metadata;
and
- camera sampling/reconciliation and the thin `OnUpdate` profile-to-graph
handoff remain ordered, while live-session event and world-origin consumers
stay behind their focused typed owners without `GameWindow` back-references.
Where construction happens inside acquisition or transfer delegates, the
tests follow the compiled delegate target before inspecting its child edge.
This preserves real execution ownership without reverting to lexical source
order across nested expressions.
Verification follows the user-approved proportionate policy for the remaining
R3 work:
- all 34 focused frame/resource methods pass;
- the complete `AcDream.App.Tests` hermetic lane passes 5,381/5,381 with zero
skips or failures;
- the complete locked Release build covers all 44 projects with zero warnings
and zero errors; and
- a targeted scan confirms neither owning test file retains a source reader or
its source-slicing helper. The final full inventory and twelve-assembly
Release gate are intentionally deferred to the one R3 closeout run.

View file

@ -1,4 +1,13 @@
using System.Reflection;
using System.Reflection.Emit;
using AcDream.App.Diagnostics;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Vfx;
using AcDream.App.Rendering.Wb;
using AcDream.App.Streaming;
using AcDream.App.Tests.Architecture;
using AcDream.App.World;
using AcDream.Core.World;
namespace AcDream.App.Tests.Rendering;
@ -47,36 +56,40 @@ public sealed class RenderFrameResourceControllerTests
[Fact]
public void Production_begin_resources_preserve_the_exact_frame_order()
{
string source = ResourceSource();
// Campaign V slice V4a: the world-HUD and retained-UI TextRenderers no
// longer take a per-slot begin call — they now read the shared
// IGpuFrame ring reset once per frame by IGpuDevice.BeginFrame() (see
// GpuDeviceFrameLifetime), so _worldText/_uiText are gone from this
// sequence.
AssertAppearsInOrder(
source,
"_textures?.BeginCompositeTextureFrame();",
"_textures?.TickCompositeTextureCache();",
"_dispatcher?.BeginFrame(gpuSlot);",
"_environmentCells?.BeginFrame(gpuSlot);",
"_portalDepth?.BeginFrame(gpuSlot);",
"_clip?.BeginFrame(gpuSlot);",
"_terrain?.BeginFrame(gpuSlot);",
"_lighting?.BeginFrame(gpuSlot);");
Assert.DoesNotContain("_profiler.FrameBoundary(", source);
MethodInfo begin = RequiredMethod(
typeof(RuntimeRenderFrameBeginResources),
nameof(RuntimeRenderFrameBeginResources.Begin));
AssertCallOrder(
begin,
(typeof(TextureCache), nameof(TextureCache.BeginCompositeTextureFrame)),
(typeof(TextureCache), nameof(TextureCache.TickCompositeTextureCache)),
(typeof(WbDrawDispatcher), nameof(WbDrawDispatcher.BeginFrame)),
(typeof(EnvCellRenderer), nameof(EnvCellRenderer.BeginFrame)),
(typeof(PortalDepthMaskRenderer), nameof(PortalDepthMaskRenderer.BeginFrame)),
(typeof(ClipFrame), nameof(ClipFrame.BeginFrame)),
(typeof(TerrainModernRenderer), nameof(TerrainModernRenderer.BeginFrame)),
(typeof(SceneLightingUboBinding), nameof(SceneLightingUboBinding.BeginFrame)));
Assert.DoesNotContain(
CompiledCallGraph.Read(begin),
call => call.Target.DeclaringType == typeof(FrameProfiler)
&& call.Target.Name == "FrameBoundary");
}
[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);");
AssertCallOrder(
RequiredMethod(
typeof(RuntimeRenderFrameLivePreparation),
nameof(RuntimeRenderFrameLivePreparation.Prepare)),
(typeof(WbMeshAdapter), nameof(WbMeshAdapter.Tick)),
(typeof(WorldRevealCoordinator), nameof(WorldRevealCoordinator.PrepareAndEvaluate)),
(typeof(ParticleRenderer), nameof(ParticleRenderer.BeginFrame)));
}
// Campaign V slice V11 deleted RuntimeRenderFrameClearPhase (the raw-GL
@ -101,11 +114,18 @@ public sealed class RenderFrameResourceControllerTests
controller.Tick(0.5d);
Assert.Equal(0.75d, controller.ElapsedSeconds);
AssertAppearsInOrder(
ResourceSource(),
"_weather.Tick(",
"nowSeconds: _elapsedSeconds,",
"_elapsedSeconds += deltaSeconds;");
MethodInfo tick = RequiredMethod(
typeof(RenderWeatherFrameController),
nameof(RenderWeatherFrameController.Tick));
CompiledCall weather = Assert.Single(
CompiledCallGraph.Read(tick),
call => call.Target.DeclaringType == typeof(WeatherSystem)
&& call.Target.Name == nameof(WeatherSystem.Tick));
CompiledFieldReference elapsedStore = Assert.Single(
CompiledCallGraph.ReadFieldReferences(tick),
reference => reference.Field.Name == "_elapsedSeconds"
&& reference.OpCode == OpCodes.Stfld);
Assert.True(weather.Offset < elapsedStore.Offset);
}
[Fact]
@ -127,38 +147,30 @@ public sealed class RenderFrameResourceControllerTests
Assert.False(live.IsWaitingForLogin);
}
private static string ResourceSource() => File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"RenderFrameResourceController.cs"));
private static MethodInfo RequiredMethod(Type owner, string name) =>
owner.GetMethod(
name,
BindingFlags.Instance | BindingFlags.Static
| BindingFlags.Public | BindingFlags.NonPublic)
?? throw new MissingMethodException(owner.FullName, name);
private static void AssertAppearsInOrder(string source, params string[] needles)
private static void AssertCallOrder(
MethodBase method,
params (Type Type, string Method)[] expected)
{
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(method);
int cursor = -1;
foreach (string needle in needles)
foreach ((Type type, string name) in expected)
{
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;
int found = Enumerable.Range(cursor + 1, calls.Count - cursor - 1)
.FirstOrDefault(index => calls[index].Target.DeclaringType == type
&& calls[index].Target.Name == name, -1);
Assert.True(found > cursor,
$"Missing compiled edge after {cursor}: {type.FullName}.{name}.");
cursor = found;
}
}
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; }

View file

@ -1,9 +1,12 @@
using System.Reflection;
using AcDream.App.Composition;
using AcDream.App.Input;
using AcDream.App.Rendering;
using AcDream.App.Net;
using AcDream.App.Streaming;
using AcDream.App.Update;
using AcDream.App.World;
using AcDream.App.Tests.Architecture;
using AcDream.Runtime;
using AcDream.Runtime.Session;
using AcDream.Runtime.World;
@ -318,104 +321,84 @@ public sealed class UpdateFrameOrchestratorTests
[Fact]
public void ProductionFrame_PublishesPhysicsScriptTimeExactlyOnce()
{
string root = FindRepoRoot();
string source = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
string adapters = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Update",
"UpdateFrameRuntimeAdapters.cs"));
string frameRoot = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Composition",
"FrameRootComposition.cs"));
Assert.DoesNotContain(
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
call => call.Target.Name == nameof(PhysicsScriptClockPublisher.PublishTime));
Assert.Equal(0, CountOccurrences(source, "PublishTime("));
Assert.Equal(1, CountOccurrences(adapters, "_runner.PublishTime("));
Assert.Contains("new PhysicsScriptClockPublisher(", frameRoot,
StringComparison.Ordinal);
MethodInfo publish = RequiredMethod(
typeof(PhysicsScriptClockPublisher),
nameof(PhysicsScriptClockPublisher.PublishTime));
Assert.Single(
CompiledCallGraph.Read(publish),
call => call.Target.Name == "PublishTime");
Assert.Contains(
CompiledCallGraph.Read(RequiredMethod(
typeof(FrameRootCompositionPhase),
"ComposeCore")),
call => call.Target.DeclaringType == typeof(PhysicsScriptClockPublisher)
&& call.Target.IsConstructor);
}
[Fact]
public void ExtractedLiveObjectSource_PinsRetailAndRegisteredAdaptationOrder()
{
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Update",
"LiveObjectFrameController.cs"));
MethodInfo live = RequiredMethod(typeof(LiveObjectFrameController), "TickCore");
AssertNamedCallOrder(
live,
("RetailLocalPlayerFrameController", "AdvanceBeforeNetwork"),
("SelectionInteractionController", "DrainOutbound"),
("LiveEntityAnimationScheduler", "Tick"),
("RetailStaticAnimatingObjectScheduler", "Tick"),
("LiveEntityAnimationPresenter", "Present"),
("EquippedChildRenderController", "Tick"),
("RetailStaticAnimatingObjectScheduler", "ProcessHooks"),
("LiveEffectFrameController", "Tick"));
AssertNamedCallOrder(
RequiredMethod(typeof(LiveEffectFrameController), "Tick"),
("TranslucencyFadeManager", "AdvanceAll"),
("AnimationHookFrameQueue", "Drain"),
("EntityEffectController", "RefreshLiveOwnerPoses"),
("ParticleHookSink", "RefreshAttachedEmitters"),
("LiveEntityLightController", "Refresh"),
("ParticleVisibilityController", "Apply"),
("ParticleSystem", "Tick"),
("PhysicsScriptRunner", "Tick"));
AssertNamedCallOrder(
RequiredMethod(typeof(LiveSpatialPresentationReconciler), "Reconcile"),
("EntityEffectController", "RefreshLiveOwnerPoses"),
("EquippedChildRenderController", "ReconcileSpatialMutations"),
("ParticleHookSink", "RefreshAttachedEmitters"),
("LiveEntityLightController", "Refresh"));
AssertAppearsInOrder(
source,
"_localPlayerFrame.AdvanceBeforeNetwork",
"_selectionInteractions?.DrainOutbound",
"_animations.Tick",
"_staticAnimations.Tick",
"_animationPresenter.Present",
"_equippedChildren.Tick",
"_staticAnimations.ProcessHooks",
"_effects.Tick");
AssertAppearsInOrder(
source,
"_translucencyFades.AdvanceAll",
"_animationHooks.Drain",
"_entityEffects.RefreshLiveOwnerPoses",
"_particleSink.RefreshAttachedEmitters",
"_lights.Refresh",
"_particleVisibility.Apply",
"_particles.Tick",
"_scripts.Tick");
AssertAppearsInOrder(
source,
"public void Reconcile()",
"_entityEffects.RefreshLiveOwnerPoses",
"_equippedChildren.ReconcileSpatialMutations",
"_particleSink.RefreshAttachedEmitters",
"_lights.Refresh");
Assert.Equal(1, CountOccurrences(source, "_staticAnimations.Tick("));
Assert.Equal(1, CountOccurrences(source, "_staticAnimations.ProcessHooks("));
Assert.Equal(1, CountOccurrences(source, "_effects.Tick("));
Assert.Equal(1, CountOccurrences(source, "_particles.Tick("));
Assert.Equal(1, CountOccurrences(source, "_scripts.Tick("));
AssertSingleNamedCall(live, "RetailStaticAnimatingObjectScheduler", "Tick");
AssertSingleNamedCall(live, "RetailStaticAnimatingObjectScheduler", "ProcessHooks");
AssertSingleNamedCall(live, "LiveEffectFrameController", "Tick");
AssertSingleNamedCall(
RequiredMethod(typeof(LiveEffectFrameController), "Tick"),
"ParticleSystem",
"Tick");
AssertSingleNamedCall(
RequiredMethod(typeof(LiveEffectFrameController), "Tick"),
"PhysicsScriptRunner",
"Tick");
}
[Fact]
public void PlacementRetryRunsAfterStreamingAndInboundBeforeCommandReconcile()
{
string root = FindRepoRoot();
string outer = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Update",
"UpdateFrameOrchestrator.cs"));
string live = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"World",
"RetailLiveFrameCoordinator.cs"));
AssertAppearsInOrder(
outer,
"_streaming.Tick();",
"_liveFrame.Tick(");
AssertAppearsInOrder(
live,
"_session.Tick();",
"_placementProjectionRetry?.RetryPending();",
"_localPlayer.RunPostNetworkCommandPhase();",
"_spatialReconciler.Reconcile();");
AssertCallOrder(
RequiredMethod(typeof(UpdateFrameOrchestrator), nameof(UpdateFrameOrchestrator.Tick)),
(typeof(IStreamingFramePhase), nameof(IStreamingFramePhase.Tick)),
(typeof(IRetailLiveFramePhase), nameof(IRetailLiveFramePhase.Tick)));
AssertCallOrder(
RequiredMethod(typeof(RetailLiveFrameCoordinator), nameof(RetailLiveFrameCoordinator.Tick)),
(typeof(IRuntimeLiveSessionFramePhase), nameof(IRuntimeLiveSessionFramePhase.Tick)),
(typeof(IRuntimePlacementProjectionRetryPhase),
nameof(IRuntimePlacementProjectionRetryPhase.RetryPending)),
(typeof(IPostNetworkCommandFramePhase),
nameof(IPostNetworkCommandFramePhase.RunPostNetworkCommandPhase)),
(typeof(ILiveSpatialReconcilePhase), nameof(ILiveSpatialReconcilePhase.Reconcile)));
}
[Theory]
@ -451,205 +434,162 @@ public sealed class UpdateFrameOrchestratorTests
[Fact]
public void GameWindow_ComposesTheLiveFrameOwnersWithoutOwningTheirBodies()
{
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
string livePresentation = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"LivePresentationComposition.cs"));
string sessionPlayer = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"SessionPlayerComposition.cs"));
string frameRoot = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"FrameRootComposition.cs"));
IReadOnlyList<CompiledCall> session = CompiledCallGraph.Read(
RequiredMethod(typeof(SessionPlayerCompositionPhase), "CompleteSessionPlayer"));
Assert.Contains(session, call =>
call.Target.DeclaringType == typeof(LiveObjectFrameController)
&& call.Target.IsConstructor);
Assert.Contains(session, call =>
call.Target.DeclaringType == typeof(LiveSpatialPresentationReconciler)
&& call.Target.IsConstructor);
Assert.Contains(
CompiledCallGraph.Read(RequiredMethod(
typeof(FrameRootCompositionPhase),
"ComposeCore")),
call => call.Target.DeclaringType == typeof(RetailLiveFrameCoordinator)
&& call.Target.IsConstructor);
Assert.Contains("new LiveObjectFrameController(", sessionPlayer);
Assert.Contains("new LiveSpatialPresentationReconciler(", sessionPlayer);
Assert.Contains("new RetailLiveFrameCoordinator(", frameRoot);
Assert.DoesNotContain("AdvanceLiveObjectRuntime", source, StringComparison.Ordinal);
Assert.DoesNotContain("ReconcileLiveObjectSpatialPresentation", source,
StringComparison.Ordinal);
Assert.DoesNotContain("ILiveAnimationPresentationContext", source,
StringComparison.Ordinal);
IReadOnlyList<CompiledCall> windowCalls =
CompiledCallGraph.ReadDeclared(typeof(GameWindow));
Assert.DoesNotContain(windowCalls, call =>
call.Target.Name is "AdvanceLiveObjectRuntime"
or "ReconcileLiveObjectSpatialPresentation");
Assert.DoesNotContain(
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
field => field.FieldType == typeof(ILiveAnimationPresentationContext));
}
[Fact]
public void GameWindow_DelegatesTheCompleteStreamingFrameBody()
{
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
string sessionPlayer = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"SessionPlayerComposition.cs"));
Assert.Contains("new StreamingFrameController(", sessionPlayer);
Assert.DoesNotContain("_streamingFrame", source, StringComparison.Ordinal);
Assert.Equal(1, CountOccurrences(
source,
"_frameGraphs.Tick("));
Assert.DoesNotContain("_streamingController.Tick(observerCx", source,
StringComparison.Ordinal);
Assert.DoesNotContain("DungeonStreamingGate.Compute", source,
StringComparison.Ordinal);
Assert.DoesNotContain("DrainRescued()", source, StringComparison.Ordinal);
Assert.Contains(
CompiledCallGraph.Read(RequiredMethod(
typeof(SessionPlayerCompositionPhase),
"CompleteSessionPlayer")),
call => call.Target.DeclaringType == typeof(StreamingFrameController)
&& call.Target.IsConstructor);
Assert.DoesNotContain(
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_streamingFrame");
MethodInfo onUpdate = RequiredMethod(typeof(GameWindow), "OnUpdate");
Assert.Single(
CompiledCallGraph.Read(onUpdate),
call => call.Target.DeclaringType == typeof(GameFrameGraphSlot)
&& call.Target.Name == nameof(GameFrameGraphSlot.Tick));
Assert.DoesNotContain(
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
call => call.Target.Name is "Compute" or "DrainRescued"
|| call.Target.DeclaringType == typeof(StreamingController)
&& call.Target.Name == nameof(StreamingController.Tick));
}
[Fact]
public void GameWindow_DelegatesTheCompleteGameplayInputFrameBody()
{
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
string sessionPlayer = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"SessionPlayerComposition.cs"));
Assert.Contains(
CompiledCallGraph.Read(RequiredMethod(
typeof(SessionPlayerCompositionPhase),
"CompleteSessionPlayer")),
call => call.Target.DeclaringType == typeof(GameplayInputFrameController)
&& call.Target.IsConstructor);
Assert.Contains("new GameplayInputFrameController(", sessionPlayer);
Assert.DoesNotContain("_gameplayInputFrame!.Tick", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_inputDispatcher?.Tick()", source,
StringComparison.Ordinal);
Assert.DoesNotContain("TryTakeRawSample", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_combatAttackController?.Tick()", source,
StringComparison.Ordinal);
Assert.DoesNotContain("CaptureMovementInput", source,
StringComparison.Ordinal);
Assert.DoesNotContain("EndMouseLookAndRestoreCursor", source,
StringComparison.Ordinal);
Assert.DoesNotContain("HideCursorForMouseLook", source,
StringComparison.Ordinal);
Assert.DoesNotContain("RestoreCursorAfterMouseLook", source,
StringComparison.Ordinal);
Assert.DoesNotContain("CanStartLiveCombatAttack", source,
StringComparison.Ordinal);
Assert.DoesNotContain("SendLiveCombatAttack", source,
StringComparison.Ordinal);
Assert.DoesNotContain("PreparePlayerForAttackRequest", source,
StringComparison.Ordinal);
Assert.DoesNotContain("DumpMovementTruthOutbound", source,
StringComparison.Ordinal);
Assert.DoesNotContain("DumpMovementTruthServerEcho", source,
StringComparison.Ordinal);
Assert.DoesNotContain("wantCaptureMouse: ()", source,
StringComparison.Ordinal);
HashSet<string> displacedWindowCalls =
[
"TryTakeRawSample",
"CaptureMovementInput",
"EndMouseLookAndRestoreCursor",
"HideCursorForMouseLook",
"RestoreCursorAfterMouseLook",
"CanStartLiveCombatAttack",
"SendLiveCombatAttack",
"PreparePlayerForAttackRequest",
"DumpMovementTruthOutbound",
"DumpMovementTruthServerEcho",
"EndMouseLook",
];
Assert.DoesNotContain(
"_gameplayInputFrame?.EndMouseLook",
source,
StringComparison.Ordinal);
string pointerSource = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Input",
"CameraPointerInputController.cs"));
Assert.Contains("_gameplayFrame?.EndMouseLook();", pointerSource,
StringComparison.Ordinal);
string teleportSource = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Streaming",
"LocalPlayerTeleportController.cs"));
AssertAppearsInOrder(
teleportSource,
"_transit.CanQueueTeleportStart(teleportSequence)",
"_input.EndMouseLook();",
"_transit.TryQueueTeleportStart(teleportSequence)");
string playerModeSource = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Input",
"PlayerModeController.cs"));
Assert.Contains("_input.EndMouseLook();", playerModeSource,
StringComparison.Ordinal);
AssertAppearsInOrder(
source,
"private void OnFocusChanged(bool focused)",
"_cameraPointerInput?.HandleFocusChanged(focused);");
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
call => displacedWindowCalls.Contains(call.Target.Name)
|| call.Target.DeclaringType == typeof(GameplayInputFrameController)
&& call.Target.Name == nameof(GameplayInputFrameController.Tick));
Assert.Contains(
CompiledCallGraph.Read(RequiredMethod(
typeof(CameraPointerInputController),
nameof(CameraPointerInputController.HandleFocusChanged))),
call => call.Target.DeclaringType == typeof(GameplayInputFrameController)
&& call.Target.Name == nameof(GameplayInputFrameController.EndMouseLook));
AssertCallOrder(
RequiredMethod(
typeof(LocalPlayerTeleportController),
nameof(LocalPlayerTeleportController.OnTeleportStarted)),
(typeof(RuntimeWorldTransitState),
nameof(RuntimeWorldTransitState.CanQueueTeleportStart)),
(typeof(ILocalPlayerTeleportInputLifetime),
nameof(ILocalPlayerTeleportInputLifetime.EndMouseLook)),
(typeof(RuntimeWorldTransitState),
nameof(RuntimeWorldTransitState.TryQueueTeleportStart)));
Assert.Contains(
CompiledCallGraph.Read(RequiredMethod(typeof(PlayerModeController), "Exit")),
call => call.Target.Name == "EndMouseLook");
MethodInfo focus = RequiredMethod(typeof(GameWindow), "OnFocusChanged");
Assert.Single(
CompiledCallGraph.Read(focus),
call => call.Target.DeclaringType == typeof(CameraPointerInputController)
&& call.Target.Name == nameof(CameraPointerInputController.HandleFocusChanged));
}
[Fact]
public void GameWindow_DelegatesTheCompleteLocalTeleportLifetime()
{
string root = FindRepoRoot();
string source = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
string networkSource = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Physics",
"LiveEntityNetworkUpdateController.cs"));
string sessionPlayer = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Composition",
"SessionPlayerComposition.cs"));
string frameRoot = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Composition",
"FrameRootComposition.cs"));
MethodInfo complete = RequiredMethod(
typeof(SessionPlayerCompositionPhase),
"CompleteSessionPlayer");
MethodBase presentationFactory = ReferencedMethodConstructing(
complete,
typeof(LocalPlayerTeleportPresentation));
Assert.NotNull(ReferencedMethodConstructing(
presentationFactory,
typeof(LocalPlayerTeleportController)));
Assert.Contains(
"new LocalPlayerTeleportController(",
sessionPlayer,
StringComparison.Ordinal);
Assert.DoesNotContain("_localPlayerTeleport!.Tick", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_teleportTransit", source, StringComparison.Ordinal);
Assert.DoesNotContain("_teleportAnim", source, StringComparison.Ordinal);
Assert.DoesNotContain("_teleportViewPlane", source, StringComparison.Ordinal);
Assert.DoesNotContain("_pendingTeleport", source, StringComparison.Ordinal);
Assert.DoesNotContain("AimTeleportDestination", source, StringComparison.Ordinal);
Assert.DoesNotContain("ResetTeleportTransitState", source, StringComparison.Ordinal);
Assert.DoesNotContain("PlaceTeleportArrival", source, StringComparison.Ordinal);
Assert.DoesNotContain("TryActivatePendingTeleportPresentation", source,
StringComparison.Ordinal);
Assert.DoesNotContain("Action<WorldSession.EntityPositionUpdate>", networkSource,
StringComparison.Ordinal);
Assert.Contains("ILocalPlayerTeleportNetworkSink", networkSource,
StringComparison.Ordinal);
AssertAppearsInOrder(
frameRoot,
"new LiveEntityLivenessFramePhase(",
"session.LocalTeleport,",
"new PlayerModeAutoEntryFramePhase(",
"cameraFrame,",
"live.WorldAvailability);");
HashSet<string> removedFields =
[
"_teleportTransit",
"_teleportAnim",
"_teleportViewPlane",
"_pendingTeleport",
];
Assert.DoesNotContain(
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
field => removedFields.Contains(field.Name));
Assert.DoesNotContain(
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
call => call.Target.DeclaringType == typeof(LocalPlayerTeleportController)
&& call.Target.Name == nameof(LocalPlayerTeleportController.Tick)
|| call.Target.Name is "AimTeleportDestination"
or "ResetTeleportTransitState"
or "PlaceTeleportArrival"
or "TryActivatePendingTeleportPresentation");
FieldInfo teleport = Assert.Single(
typeof(AcDream.App.Physics.LiveEntityNetworkUpdateController).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.FieldType == typeof(ILocalPlayerTeleportNetworkSink));
Assert.Equal(typeof(ILocalPlayerTeleportNetworkSink), teleport.FieldType);
Assert.DoesNotContain(
typeof(AcDream.App.Physics.LiveEntityNetworkUpdateController).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
AssertCallOrder(
RequiredMethod(typeof(FrameRootCompositionPhase), "ComposeCore"),
(typeof(LiveEntityLivenessFramePhase), ".ctor"),
(typeof(SessionPlayerResult), "get_LocalTeleport"),
(typeof(PlayerModeAutoEntryFramePhase), ".ctor"),
(typeof(LivePresentationResult), "get_WorldAvailability"),
(typeof(UpdateFrameOrchestrator), ".ctor"));
}
[Fact]
@ -734,12 +674,6 @@ public sealed class UpdateFrameOrchestratorTests
BindingFlags.Instance | BindingFlags.NonPublic),
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
string playerModeSource = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Input",
"PlayerModeController.cs"));
// C3c (clause 4 — sealed-setter lifecycle routing): the movement
// controller, physics body, host, and committed placement are
// Runtime-owned, published by the first-entry conductor's
@ -750,40 +684,45 @@ public sealed class UpdateFrameOrchestratorTests
// CommitPreparedPosition / `_controllerSlot.Controller =`) were
// exactly the App-side controller construction+commit this flip
// deleted.
AssertAppearsInOrder(
playerModeSource,
"controller.IsRuntimePublished",
"_camera.EnterChaseMode(legacyCamera, retailCamera);",
"_shadow.SyncPose(",
"_hostSlot.Host = playerHost;",
"_mode.IsPlayerMode = true;");
Assert.Contains("_shadow.Restore(playerEntity, priorShadow);", playerModeSource,
StringComparison.Ordinal);
Assert.Contains("_camera.RestoreState(priorCamera);", playerModeSource,
StringComparison.Ordinal);
MethodInfo attach = RequiredMethod(typeof(PlayerModeController), "BuildControllerAndCamera");
AssertNamedCallOrder(
attach,
("PlayerMovementController", "get_IsRuntimePublished"),
("CameraController", "EnterChaseMode"),
("LocalPlayerShadowSynchronizer", "SyncPose"),
("LocalPlayerPhysicsHostSlot", "set_Host"),
("LocalPlayerModeState", "set_IsPlayerMode"));
Assert.Contains(
CompiledCallGraph.Read(attach),
call => call.Target.DeclaringType?.Name == "LocalPlayerShadowSynchronizer"
&& call.Target.Name == "Restore");
Assert.Contains(
CompiledCallGraph.Read(attach),
call => call.Target.DeclaringType?.Name == "CameraController"
&& call.Target.Name == "RestoreState");
string mouseLookSource = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Input",
"MouseLookController.cs"));
AssertAppearsInOrder(
mouseLookSource,
"controller?.StopMouseDrift",
"retail.FilterMouseDelta",
"_state.ApplyDelta");
AssertNamedCallOrder(
RequiredMethod(typeof(MouseLookController), nameof(MouseLookController.Tick)),
("PlayerMovementController", "StopMouseDrift"),
("RetailChaseCamera", "FilterMouseDelta"),
("MouseLookState", "ApplyDelta"));
string localPlayerSource = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Input",
"RetailLocalPlayerFrameController.cs"));
Assert.DoesNotContain("Func<MovementInput>", localPlayerSource,
StringComparison.Ordinal);
Assert.Contains("IMovementInputSource", localPlayerSource,
StringComparison.Ordinal);
ConstructorInfo[] localFrameConstructors =
typeof(RetailLocalPlayerFrameController).GetConstructors(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
Assert.NotEmpty(localFrameConstructors);
Assert.All(
localFrameConstructors,
constructor =>
{
Type[] parameters = constructor.GetParameters()
.Select(parameter => parameter.ParameterType)
.ToArray();
Assert.Contains(typeof(IMovementInputSource), parameters);
Assert.DoesNotContain(parameters, type =>
type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(Func<>));
});
}
[Fact]
@ -809,66 +748,56 @@ public sealed class UpdateFrameOrchestratorTests
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
}
string root = FindRepoRoot();
string source = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
Assert.Equal(1, CountOccurrences(
source,
"_frameGraphs.Tick("));
Assert.DoesNotContain("CanAdvanceLocalPlayer", source, StringComparison.Ordinal);
Assert.DoesNotContain("GetCombatCameraTargetPoint()", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_cameraController.Fly.Update(", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_localPlayerFrame.TryGetPresentationAfterNetwork", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_cameraFrame", source, StringComparison.Ordinal);
MethodInfo onUpdate = RequiredMethod(typeof(GameWindow), "OnUpdate");
Assert.Single(
CompiledCallGraph.Read(onUpdate),
call => call.Target.DeclaringType == typeof(GameFrameGraphSlot)
&& call.Target.Name == nameof(GameFrameGraphSlot.Tick));
HashSet<string> displacedCalls =
[
"CanAdvanceLocalPlayer",
"GetCombatCameraTargetPoint",
"TryGetPresentationAfterNetwork",
];
Assert.DoesNotContain(
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
call => displacedCalls.Contains(call.Target.Name)
|| call.Target.DeclaringType?.Name == "FlyCamera"
&& call.Target.Name == "Update");
Assert.DoesNotContain(
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_cameraFrame");
string cameraSource = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Rendering",
"CameraFrameController.cs"));
AssertAppearsInOrder(
cameraSource,
"_localFrame.TryGetPresentationAfterNetwork",
"_spatialReconciler.Reconcile();",
"legacy.Update(",
"retail?.Update(");
AssertNamedCallOrder(
RequiredMethod(typeof(CameraFrameController), nameof(CameraFrameController.Tick)),
("RetailLocalPlayerFrameController", "TryGetPresentationAfterNetwork"),
("ILiveSpatialReconcilePhase", "Reconcile"),
("ChaseCamera", "Update"),
("RetailChaseCamera", "Update"));
}
[Fact]
public void GameWindow_OnUpdateOwnsOnlyProfilingAndOneOrchestratorTick()
{
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
Assert.Equal(1, CountOccurrences(
source,
"_frameGraphs.Tick("));
Assert.DoesNotContain("_updateFrameClock.Advance(", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_liveFrameCoordinator", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_liveEntityLiveness?.Tick", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_playerModeAutoEntry?.TryEnter", source,
StringComparison.Ordinal);
AssertAppearsInOrder(
source,
"private void OnUpdate(double dt)",
"_frameProfiler.BeginStage(",
"_frameGraphs.Tick(",
"private void OnRender(double deltaSeconds)");
MethodInfo update = RequiredMethod(typeof(GameWindow), "OnUpdate");
AssertCallOrder(
update,
(typeof(AcDream.App.Diagnostics.FrameProfiler), "BeginStage"),
(typeof(GameFrameGraphSlot), nameof(GameFrameGraphSlot.Tick)));
Assert.Single(
CompiledCallGraph.Read(update),
call => call.Target.DeclaringType == typeof(GameFrameGraphSlot)
&& call.Target.Name == nameof(GameFrameGraphSlot.Tick));
Assert.DoesNotContain(
CompiledCallGraph.Read(update),
call => call.Target.DeclaringType == typeof(UpdateFrameClock)
&& call.Target.Name == nameof(UpdateFrameClock.Advance)
|| call.Target.Name is "TryEnter"
|| call.Target.DeclaringType == typeof(LiveEntityLivenessFramePhase)
&& call.Target.Name == nameof(LiveEntityLivenessFramePhase.Tick));
Assert.DoesNotContain(
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_liveFrameCoordinator");
}
[Fact]
@ -931,49 +860,50 @@ public sealed class UpdateFrameOrchestratorTests
typeof(AcDream.App.Input.ILocalPlayerIdentitySource),
originIdentity.FieldType);
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
string livePresentation = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"LivePresentationComposition.cs"));
string sessionRuntime = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Net",
"LiveSessionRuntimeFactory.cs"));
Assert.DoesNotContain("PublishLocalPhysicsTimestamps", source,
StringComparison.Ordinal);
Assert.DoesNotContain("LoginWorldReady", source,
StringComparison.Ordinal);
Assert.DoesNotContain("candidate => _liveEntityHydration.OnPrune", source,
StringComparison.Ordinal);
Assert.DoesNotContain("CreateLiveEntitySessionSink", source,
StringComparison.Ordinal);
Assert.DoesNotContain("private System.Numerics.Vector3 CellLocalForSeed", source,
StringComparison.Ordinal);
Assert.DoesNotContain("OnPlayScriptReceived", source,
StringComparison.Ordinal);
Assert.Contains("d.WorldOrigin.GetCenter", livePresentation,
StringComparison.Ordinal);
HashSet<string> displacedWindowCalls =
[
"PublishLocalPhysicsTimestamps",
"LoginWorldReady",
"OnPrune",
"CreateLiveEntitySessionSink",
"CreateLiveSessionEventRouter",
"CellLocalForSeed",
"OnPlayScriptReceived",
];
Assert.DoesNotContain(
CompiledCallGraph.ReadDeclared(typeof(GameWindow)),
call => displacedWindowCalls.Contains(call.Target.Name));
Assert.DoesNotContain(
typeof(GameWindow).GetMethods(
BindingFlags.Instance | BindingFlags.Static
| BindingFlags.Public | BindingFlags.NonPublic),
method => displacedWindowCalls.Contains(method.Name));
MethodBase presentationFactory = ReferencedMethodConstructing(
RequiredMethod(typeof(LivePresentationCompositionPhase), "ComposeCore"),
typeof(LiveEntityPresentationController));
Assert.Contains(
CompiledCallGraph.ReadMethodReferences(presentationFactory),
call => call.Target.DeclaringType == typeof(LiveWorldOriginState)
&& call.Target.Name == nameof(LiveWorldOriginState.GetCenter));
// C4 route 4b-3 deleted the sole consumer of
// d.WorldOrigin.CellLocalForSeed in this file — the standalone
// RemoteTeleportController construction. LiveWorldOriginState.
// CellLocalForSeed itself is not deleted (LocalPlayerTeleportController
// still uses it).
Assert.DoesNotContain("CreateLiveSessionEventRouter", source,
StringComparison.Ordinal);
Assert.Contains("_world.EntitySession.CreateSink()", sessionRuntime,
StringComparison.Ordinal);
Assert.DoesNotContain("GameWindow", sessionRuntime,
StringComparison.Ordinal);
MethodInfo eventRouter = RequiredMethod(typeof(LiveSessionRuntimeFactory), "CreateEventRouter");
Assert.Contains(
CompiledCallGraph.Read(eventRouter),
call => call.Target.DeclaringType == typeof(LiveEntitySessionController)
&& call.Target.Name == nameof(LiveEntitySessionController.CreateSink));
Assert.DoesNotContain(
typeof(LiveSessionRuntimeFactory).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.FieldType == typeof(GameWindow));
Assert.DoesNotContain(
typeof(LiveSessionRuntimeFactory).GetConstructors(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.SelectMany(constructor => constructor.GetParameters()),
parameter => parameter.ParameterType == typeof(GameWindow));
}
private static UpdateFrameOrchestrator Create(
@ -1151,31 +1081,68 @@ public sealed class UpdateFrameOrchestratorTests
public List<UpdateFrameTiming> Camera { get; } = [];
}
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;
}
private static MethodInfo RequiredMethod(Type owner, string name) =>
owner.GetMethod(
name,
BindingFlags.Instance | BindingFlags.Static
| BindingFlags.Public | BindingFlags.NonPublic)
?? throw new MissingMethodException(owner.FullName, name);
throw new DirectoryNotFoundException("Could not find AcDream.slnx.");
}
private static MethodBase ReferencedMethodConstructing(
MethodBase owner,
Type constructed) =>
Assert.Single(
CompiledCallGraph.ReadMethodReferences(owner)
.Select(reference => reference.Target)
.Where(method => method.GetMethodBody() is not null)
.Distinct(),
method => Constructs(method, constructed));
private static void AssertAppearsInOrder(string source, params string[] markers)
private static bool Constructs(MethodBase method, Type type) =>
method.GetMethodBody() is not null
&& CompiledCallGraph.Read(method).Any(call =>
call.Target.DeclaringType == type && call.Target.IsConstructor);
private static void AssertCallOrder(
MethodBase method,
params (Type Type, string Method)[] expected)
{
int previous = -1;
foreach (string marker in markers)
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(method);
int cursor = -1;
foreach ((Type type, string name) in expected)
{
int current = source.IndexOf(marker, previous + 1, StringComparison.Ordinal);
Assert.True(current >= 0, $"Missing source marker: {marker}");
Assert.True(current > previous, $"Out-of-order source marker: {marker}");
previous = current;
int found = Enumerable.Range(cursor + 1, calls.Count - cursor - 1)
.FirstOrDefault(index => calls[index].Target.DeclaringType == type
&& calls[index].Target.Name == name, -1);
Assert.True(found > cursor,
$"Missing compiled edge after {cursor}: {type.FullName}.{name}.");
cursor = found;
}
}
private static int CountOccurrences(string source, string marker) =>
source.Split(marker, StringSplitOptions.None).Length - 1;
private static void AssertNamedCallOrder(
MethodBase method,
params (string Type, string Method)[] expected)
{
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(method);
int cursor = -1;
foreach ((string type, string name) in expected)
{
int found = Enumerable.Range(cursor + 1, calls.Count - cursor - 1)
.FirstOrDefault(index => calls[index].Target.DeclaringType?.Name == type
&& calls[index].Target.Name == name, -1);
Assert.True(found > cursor,
$"Missing compiled edge after {cursor}: {type}.{name}.");
cursor = found;
}
}
private static void AssertSingleNamedCall(
MethodBase method,
string type,
string name) =>
Assert.Single(
CompiledCallGraph.Read(method),
call => call.Target.DeclaringType?.Name == type
&& call.Target.Name == name);
}