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

@ -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);
}