Flips the last remote classification (SetPosition: teleport-advanced and
cell-less) onto 4b-1's RuntimeRemotePlacementDriveController, runs retail's
teleport_hook before the placement, and deletes the legacy remote-teleport
machinery. Contract: docs/research/2026-08-04-c4-route-4b-3-contract.md.
Retail: MoveOrTeleport @0x00516330's branch @0x00516386 -> teleport_hook
@0x005163EF -> SetFlags(0x1012) @0x00516414 -> SetPosition @0x00516420 ->
return 1 @0x00516438. The hook @0x00514ED0 runs BEFORE the placement and
regardless of its outcome. Retail places this branch unconditionally, at any
distance and any contact state (arg4 is read only @0x0051638E, after the
branch) — which is what retires AP-137's cell-less enqueue-vs-place delta.
D1 — the classifier's cell-less input is now the PRE-merge committed cell.
Retail's predicate is `this_1->cell == 0`, the BODY's own cell at
MoveOrTeleport entry (this_1 is assigned from this @0x00516334). acdream fed
the POST-merge canonical.FullCellId, which RefreshSnapshot ->
RefreshDerivedState -> SetFullCell has already stamped with the accepted wire
cell; a zero wire cell fails validation into RejectedData first. The shipped
remote cell-less predicate was therefore dead code, not merely different from
remotePlacementRequired. Threaded via a builder overload; route 1's overload
is untouched. The graphical !IsSpatiallyVisible arm of
projectionRequiresTeleportHook is deleted — a presentation predicate with no
retail analogue that fired the teleport machinery on a routine hot path.
Deleted: RemoteTeleportController (605), RemoteTeleportPlacement (85),
RemoteShadowPlacementSynchronizer (49), their 1,709 lines of tests, the
remotePlacementRequired predicate, the TeleportHookRequired plumbing, the
legacy pre-operation ConstrainTo fallback, and the player arm's legacy
!IsGrounded fallback. Net -2,030 lines.
Structural fix (two independent Opus reviews, round 1 FAIL/FAIL): three of the
four MAJORs were one defect — OnPosition carried two parallel inline copies of
the routing tail (player-guid, NPC-guid) that had drifted. Extracted
RunRemoteArmTail (3 call sites) and ApplyWireAirborneLeftoverBookkeeping (2),
both branches now share one implementation.
A1 ToConstraintArm mapped AirborneSnap -> AirborneNoOperation, so the NPC
arm armed ConstrainTo ZERO times for an out-of-contact wire-grounded
creature — a regression this slice introduced while closing a
structurally identical hole. Now maps to NearInterpolate; switch made
total with a throwing default proven unreachable.
R1 D2's write-nothing shape existed on the player arm only; NPC packets
fell through and wrote the body. Retail makes no player/NPC distinction.
R2 report_collision_end(this,1) @0x00514F31 was bound to
ShadowObjects.Suspend, a port of a DIFFERENT retail function
(remove_shadows_from_cells) that teleport_hook never calls. Now routes
to RuntimeCollisionReportingState.LeaveWorld, which wraps the private
ForceEnd in an admission-blocking transaction so a DoCollisionEnd
callback cannot recreate the contact table.
R3/A2 A teleported NPC synthesized ServerVelocity from the teleport distance
(~1,000+ m/s) and planned a run cycle from it. Both the install and
RemoteServerControlledVelocityCycle.Apply now gate on !isTeleportRoute.
BISECT HAZARD — A1's fix is correct only BECAUSE R1 landed. AirborneSnap is
reachable wire-airborne on the NPC arm only while D2's shape is missing there.
Reverting R1 alone silently inverts A1 into the opposite divergence: arming
where retail returns 0. Revert both or neither.
Also in the velocity hunk: the NPC block's two !IsPlayerGuid(update.Guid)
guards were dropped when it was wrapped in `if (!isTeleportRoute)`. Safe — all
five exit paths of the enclosing IsPlayerGuid block return, so the predicate is
unconditionally false below it — but it was unremarked by both reviews.
Register: AP-137 REWRITTEN (not deleted) to the surviving acdream-only
divergences — null classification during the login window and Rejected*
through UnroutedCatchUp keep a row. AD-42's RemoteTeleportController citation
retired; AP-136/AP-138 writer lists corrected to the two surviving non-Position
rebucket writers; AP-138 gains the teleport arm as a second producer of the
visible-without-collision residual (retirement path remains #309). AP-135 is
untouched and its two airborne bookkeeping writes are preserved on both arms.
AP-131 does not retire; #276 does not close.
Proof obligation 1: ParkCollisionResidents' overlap throw stays unreachable —
the teleport arm adds packets to the same TryBeginExclusiveAuthoredPlacement
one-operation-per-key machinery the far arm uses, opens no new operation shape,
and every DeferredCell outcome cancels synchronously with
restoreCancelledPark: true. The guarded property remains
HasOldPrefixPlacementDebt's stall, not a throw (4b-1's B2 caveat stands).
Correction to an earlier claim: LiveEntityPresentationController's
_activePlacementOwners was NOT write-never at HEAD —
remotePlacementRequired -> BeginPlacement -> Begin -> BeginAuthoritativePlacement
was a live writer chain. It becomes write-never BECAUSE this slice deletes that
chain, which is why deleting the dead half is behaviour-preserving.
Probe: ACDREAM_PROBE_REMOTE_TELEPORT=1 emits one [remote-teleport] line per
routed arm (guid, cause, hook-ran, placement status). TEMPORARY, strip with the
probe family.
Carried, disclosed not fixed: no dedicated bidirectional collision-partner test
for R2 (the wiring, not LeaveWorld itself, is what lacks coverage); the
stress test's teleport step drives hand-written field assignments rather than
the canonical arm; the per-packet runTeleportHook closure allocation (network
path, not the resolve path Slice I's 0 B discipline governs — file before
route 5 adds a fourth call site). B2: IRuntimeCollisionReportObserver has zero
production implementations, so retail's bidirectional DoCollisionEnd half still
reaches no gameplay consumer — this fix closes the wrong-function binding, not
that nobody listens.
Complete Release suite MEASURED at 11,013 passed / 4 skipped / 0 failed
(baseline 11,027/4/0; net -14 = ~33 deleted test cases against ~19 added).
Neither known flake fired (#302 PortalProjectionTests GC-allocation, #308
NakEmissionTests wall-clock).
STILL OWED: the two-client connected gate, which MUST use an NPC/creature
teleport target. Both round-1 MAJORs lived on the NPC arm and the velocity
cycle early-returns for 0x50xxxxxx guids, so a player target structurally
cannot observe A1, A2, or R3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1181 lines
44 KiB
C#
1181 lines
44 KiB
C#
using System.Reflection;
|
|
using AcDream.App.Rendering;
|
|
using AcDream.App.Net;
|
|
using AcDream.App.Streaming;
|
|
using AcDream.App.Update;
|
|
using AcDream.App.World;
|
|
using AcDream.Runtime;
|
|
using AcDream.Runtime.Session;
|
|
using AcDream.Runtime.World;
|
|
|
|
namespace AcDream.App.Tests.World;
|
|
|
|
public sealed class UpdateFrameOrchestratorTests
|
|
{
|
|
[Fact]
|
|
public void Tick_PreservesTheCompleteAcceptedPhaseGraph()
|
|
{
|
|
var calls = new List<string>();
|
|
UpdateFrameOrchestrator frame = Create(calls);
|
|
|
|
frame.Tick(new UpdateFrameInput(1.0 / 60.0));
|
|
|
|
Assert.Equal(
|
|
[
|
|
"teardown",
|
|
"clock",
|
|
"streaming",
|
|
"input",
|
|
"objects",
|
|
"network",
|
|
"commands",
|
|
"ordinary-reconcile",
|
|
"liveness",
|
|
"teleport",
|
|
"auto-entry",
|
|
"camera",
|
|
"commit",
|
|
],
|
|
calls);
|
|
}
|
|
|
|
[Fact]
|
|
public void ConditionalReconciles_RemainAtTheirOwningEdges()
|
|
{
|
|
// The outer ownership oracle is complemented by the production
|
|
// teleport and camera tests that pin both conditional reconcile edges.
|
|
var calls = new List<string>();
|
|
UpdateFrameOrchestrator frame = Create(
|
|
calls,
|
|
teleportPlace: true,
|
|
inboundCreatedPlayer: true);
|
|
|
|
frame.Tick(new UpdateFrameInput(1.0 / 60.0));
|
|
|
|
Assert.Equal(
|
|
[
|
|
"teardown",
|
|
"clock",
|
|
"streaming",
|
|
"input",
|
|
"objects",
|
|
"network",
|
|
"commands",
|
|
"ordinary-reconcile",
|
|
"liveness",
|
|
"teleport-place",
|
|
"teleport-reconcile",
|
|
"teleport-reveal",
|
|
"auto-entry",
|
|
"inbound-player-projection",
|
|
"inbound-player-reconcile",
|
|
"camera",
|
|
"commit",
|
|
],
|
|
calls);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(double.NaN)]
|
|
[InlineData(double.NegativeInfinity)]
|
|
[InlineData(double.PositiveInfinity)]
|
|
[InlineData(-1.0)]
|
|
[InlineData(0.0)]
|
|
[InlineData(double.MaxValue)]
|
|
public void InvalidSimulationDelta_BecomesZeroWithoutSuppressingOuterPhases(
|
|
double hostDelta)
|
|
{
|
|
var calls = new List<string>();
|
|
var observed = new FrameObservations();
|
|
UpdateFrameOrchestrator frame = Create(calls, observed: observed);
|
|
|
|
frame.Tick(new UpdateFrameInput(hostDelta));
|
|
|
|
Assert.Equal(
|
|
[
|
|
"teardown", "clock", "streaming", "input", "objects",
|
|
"network", "commands", "ordinary-reconcile", "liveness",
|
|
"teleport", "auto-entry", "camera", "commit",
|
|
],
|
|
calls);
|
|
Assert.Equal([0.0], observed.PublishedTimes);
|
|
Assert.Equal([0.0], observed.Input.Select(value => value.SimulationDeltaSeconds));
|
|
Assert.Equal([0f], observed.LiveDeltas);
|
|
Assert.Equal([0f], observed.TeleportDeltas);
|
|
Assert.Equal([0.0], observed.Camera.Select(value => value.SimulationDeltaSeconds));
|
|
Assert.All(
|
|
observed.Input.Concat(observed.Camera),
|
|
timing => Assert.Equal(0.0, timing.ScriptTime));
|
|
}
|
|
|
|
[Fact]
|
|
public void Clock_AdvancesOnceAndPublishesTheSameTimeToEveryConsumer()
|
|
{
|
|
var calls = new List<string>();
|
|
var observed = new FrameObservations();
|
|
UpdateFrameOrchestrator frame = Create(calls, observed: observed);
|
|
|
|
frame.Tick(new UpdateFrameInput(0.25));
|
|
frame.Tick(new UpdateFrameInput(double.NaN));
|
|
frame.Tick(new UpdateFrameInput(0.5));
|
|
|
|
Assert.Equal([0.25, 0.25, 0.75], observed.PublishedTimes);
|
|
Assert.Equal(
|
|
[0.25, 0.25, 0.75],
|
|
observed.Input.Select(value => value.ScriptTime));
|
|
Assert.Equal(
|
|
[0.25, 0.25, 0.75],
|
|
observed.Camera.Select(value => value.ScriptTime));
|
|
Assert.Equal([0.25f, 0f, 0.5f], observed.LiveDeltas);
|
|
Assert.Equal([0.25f, 0f, 0.5f], observed.TeleportDeltas);
|
|
Assert.Equal(
|
|
[0.25, 0.0, 0.5],
|
|
observed.Input.Select(value => value.SimulationDeltaSeconds));
|
|
Assert.Equal(
|
|
[0.25, 0.0, 0.5],
|
|
observed.Camera.Select(value => value.SimulationDeltaSeconds));
|
|
}
|
|
|
|
[Fact]
|
|
public void QuiescedWorld_FreezesScriptClockAndLivenessWhilePortalPhasesContinue()
|
|
{
|
|
var calls = new List<string>();
|
|
var observed = new FrameObservations();
|
|
var transit = new RuntimeWorldTransitState();
|
|
var availability = new WorldGenerationAvailabilityState(transit);
|
|
long generation =
|
|
RuntimeWorldTransitTestDriver.BeginPortal(
|
|
transit,
|
|
0x11340021u);
|
|
UpdateFrameOrchestrator frame = Create(
|
|
calls,
|
|
observed: observed,
|
|
availability: availability);
|
|
|
|
frame.Tick(new UpdateFrameInput(0.25));
|
|
frame.Tick(new UpdateFrameInput(0.25));
|
|
Assert.True(transit.AcknowledgeDestinationReadiness(
|
|
new RuntimeDestinationReadiness(
|
|
generation,
|
|
0x11340021u,
|
|
IsIndoor: false,
|
|
IsUnhydratable: false,
|
|
RequiredRenderRadius: 1,
|
|
IsRenderNeighborhoodReady: true,
|
|
AreCompositeTexturesReady: true,
|
|
IsCollisionReady: true)));
|
|
Assert.True(RuntimeWorldTransitTestDriver.MaterializePortal(
|
|
transit,
|
|
generation,
|
|
0x11340021u));
|
|
transit.Complete(generation);
|
|
frame.Tick(new UpdateFrameInput(0.25));
|
|
|
|
Assert.Equal([0.0, 0.0, 0.25], observed.PublishedTimes);
|
|
Assert.Equal(1, calls.Count(call => call == "liveness"));
|
|
Assert.Equal(3, calls.Count(call => call == "streaming"));
|
|
Assert.Equal(3, calls.Count(call => call == "network"));
|
|
Assert.Equal(3, calls.Count(call => call == "teleport"));
|
|
Assert.Equal(3, calls.Count(call => call == "camera"));
|
|
Assert.Equal(3, calls.Count(call => call == "commit"));
|
|
Assert.Equal([0.25f, 0.25f, 0.25f], observed.LiveDeltas);
|
|
Assert.Equal([0.25f, 0.25f, 0.25f], observed.TeleportDeltas);
|
|
}
|
|
|
|
[Fact]
|
|
public void Clock_RejectsFiniteDeltaAboveTheSinglePrecisionConsumerRange()
|
|
{
|
|
double tooLarge = (double)float.MaxValue * 2.0;
|
|
|
|
Assert.True(double.IsFinite(tooLarge));
|
|
Assert.Equal(0.0, UpdateFrameClock.NormalizeDeltaSeconds(tooLarge));
|
|
}
|
|
|
|
[Fact]
|
|
public void AggregateTeardownFailure_IsReportedAndRetriedNextFrame()
|
|
{
|
|
var calls = new List<string>();
|
|
var teardown = new RecordingTeardown(calls)
|
|
{
|
|
Failure = new AggregateException(new InvalidOperationException("transient")),
|
|
};
|
|
var failures = new RecordingFailureSink();
|
|
UpdateFrameOrchestrator frame = Create(
|
|
calls,
|
|
teardown: teardown,
|
|
failureSink: failures);
|
|
|
|
frame.Tick(new UpdateFrameInput(0.1));
|
|
teardown.Failure = null;
|
|
frame.Tick(new UpdateFrameInput(0.1));
|
|
|
|
Assert.Equal(2, teardown.Attempts);
|
|
Assert.Single(failures.Errors);
|
|
string[] oneFrame =
|
|
[
|
|
"teardown", "clock", "streaming", "input", "objects", "network",
|
|
"commands", "ordinary-reconcile", "liveness", "teleport",
|
|
"auto-entry", "camera", "commit",
|
|
];
|
|
Assert.Equal(oneFrame.Concat(oneFrame), calls);
|
|
}
|
|
|
|
[Fact]
|
|
public void NonAggregateTeardownFailure_PropagatesBeforeTimeOrLaterPhases()
|
|
{
|
|
var calls = new List<string>();
|
|
var teardown = new RecordingTeardown(calls)
|
|
{
|
|
Failure = new InvalidOperationException("fatal"),
|
|
};
|
|
UpdateFrameOrchestrator frame = Create(calls, teardown: teardown);
|
|
|
|
InvalidOperationException error = Assert.Throws<InvalidOperationException>(
|
|
() => frame.Tick(new UpdateFrameInput(0.1)));
|
|
|
|
Assert.Equal("fatal", error.Message);
|
|
Assert.Equal(["teardown"], calls);
|
|
}
|
|
|
|
[Fact]
|
|
public void OrchestrationTypes_UseOnlyTheExplicitTypedOwnerGraph()
|
|
{
|
|
Type[] expectedFieldTypes =
|
|
[
|
|
typeof(IUpdateFrameTeardownPhase),
|
|
typeof(IUpdateFrameFailureSink),
|
|
typeof(UpdateFrameClock),
|
|
typeof(IUpdateFrameScriptClockPublisher),
|
|
typeof(IStreamingFramePhase),
|
|
typeof(IGameplayInputFramePhase),
|
|
typeof(IRetailLiveFramePhase),
|
|
typeof(ILiveEntityLivenessFramePhase),
|
|
typeof(ILocalPlayerTeleportFramePhase),
|
|
typeof(IPlayerModeAutoEntryFramePhase),
|
|
typeof(ICameraFramePhase),
|
|
typeof(IUpdateFrameCommitPhase),
|
|
typeof(IWorldGenerationAvailability),
|
|
];
|
|
|
|
FieldInfo[] fields = typeof(UpdateFrameOrchestrator).GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
|
Assert.Equal(
|
|
expectedFieldTypes.OrderBy(type => type.FullName),
|
|
fields.Select(field => field.FieldType).OrderBy(type => type.FullName));
|
|
Assert.All(fields, field =>
|
|
{
|
|
Assert.NotEqual(typeof(GameWindow), field.FieldType);
|
|
Assert.False(typeof(Delegate).IsAssignableFrom(field.FieldType));
|
|
});
|
|
|
|
Type[] phaseInterfaces = expectedFieldTypes.Where(type => type.IsInterface).ToArray();
|
|
Assert.DoesNotContain(
|
|
phaseInterfaces,
|
|
phaseInterface => phaseInterface.IsAssignableFrom(typeof(GameWindow)));
|
|
|
|
Type[] productionPhaseOwners = typeof(GameWindow).Assembly.GetTypes()
|
|
.Where(type => !type.IsAbstract && !type.IsInterface)
|
|
.Where(type => phaseInterfaces.Any(contract => contract.IsAssignableFrom(type)))
|
|
.ToArray();
|
|
foreach (Type owner in productionPhaseOwners)
|
|
{
|
|
FieldInfo[] ownerFields = owner.GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
|
Assert.DoesNotContain(ownerFields, field => field.FieldType == typeof(GameWindow));
|
|
Assert.DoesNotContain(
|
|
ownerFields,
|
|
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
|
|
}
|
|
|
|
Assert.DoesNotContain(
|
|
typeof(GameWindow).GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
|
field => field.Name == "_physicsScriptGameTime");
|
|
}
|
|
|
|
[Fact]
|
|
public void PhysicsScriptClockConsumers_RetainTheTypedSourceNotGameWindowClosures()
|
|
{
|
|
Type[] consumers =
|
|
[
|
|
typeof(DatLiveEntityProjectionMaterializer),
|
|
typeof(AcDream.App.Physics.LiveEntityNetworkUpdateController),
|
|
];
|
|
|
|
foreach (Type consumer in consumers)
|
|
{
|
|
FieldInfo clock = Assert.Single(
|
|
consumer.GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
|
field => field.Name == "_gameTime");
|
|
Assert.Equal(typeof(IPhysicsScriptTimeSource), clock.FieldType);
|
|
Assert.DoesNotContain(
|
|
consumer.GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
|
field => field.FieldType == typeof(GameWindow));
|
|
}
|
|
|
|
Assert.False(typeof(IPhysicsScriptTimeSource).IsAssignableFrom(typeof(GameWindow)));
|
|
}
|
|
|
|
[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.Equal(0, CountOccurrences(source, "PublishTime("));
|
|
Assert.Equal(1, CountOccurrences(adapters, "_runner.PublishTime("));
|
|
Assert.Contains("new PhysicsScriptClockPublisher(", frameRoot,
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExtractedLiveObjectSource_PinsRetailAndRegisteredAdaptationOrder()
|
|
{
|
|
string source = File.ReadAllText(Path.Combine(
|
|
FindRepoRoot(),
|
|
"src",
|
|
"AcDream.App",
|
|
"Update",
|
|
"LiveObjectFrameController.cs"));
|
|
|
|
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("));
|
|
}
|
|
|
|
[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();");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void LiveFrameRetriesPlacementAfterInboundEvenWhenWorldIsQuiesced(
|
|
bool worldAvailable)
|
|
{
|
|
var calls = new List<string>();
|
|
var phases = new RecordingLivePhases(calls)
|
|
{
|
|
IsWorldAvailable = worldAvailable,
|
|
};
|
|
var frame = new RetailLiveFrameCoordinator(
|
|
phases,
|
|
new GpuWorldState(),
|
|
phases,
|
|
phases,
|
|
phases,
|
|
phases,
|
|
phases,
|
|
phases);
|
|
|
|
frame.Tick(1f / 60f);
|
|
|
|
Assert.Equal(
|
|
worldAvailable
|
|
? ["objects", "network", "placement-retry", "commands", "reconcile"]
|
|
: ["network", "placement-retry", "commands", "render-sync"],
|
|
calls);
|
|
}
|
|
|
|
[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"));
|
|
|
|
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);
|
|
}
|
|
|
|
[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);
|
|
}
|
|
|
|
[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("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);
|
|
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);");
|
|
}
|
|
|
|
[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"));
|
|
|
|
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);");
|
|
}
|
|
|
|
[Fact]
|
|
public void GameplayInputOwnersUseTypedSeamsWithoutGameWindowBackReferences()
|
|
{
|
|
Type[] owners =
|
|
[
|
|
typeof(AcDream.App.Input.GameplayInputFrameController),
|
|
typeof(AcDream.App.Input.DispatcherMovementInputSource),
|
|
typeof(AcDream.App.Input.MouseLookController),
|
|
];
|
|
|
|
foreach (Type owner in owners)
|
|
{
|
|
FieldInfo[] fields = owner.GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
|
Assert.DoesNotContain(fields, field => field.FieldType == typeof(GameWindow));
|
|
Assert.DoesNotContain(
|
|
fields,
|
|
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
|
|
}
|
|
|
|
Assert.DoesNotContain(
|
|
typeof(AcDream.App.Input.MouseLookController).GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
|
field => field.FieldType == typeof(Silk.NET.Input.IMouse));
|
|
|
|
FieldInfo combatOperations = Assert.Single(
|
|
typeof(AcDream.Runtime.Gameplay.RuntimeCombatAttackState).GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
|
field => field.Name == "_operations");
|
|
Assert.Equal(
|
|
typeof(AcDream.Runtime.Gameplay.IRuntimeCombatAttackOperations),
|
|
combatOperations.FieldType);
|
|
Assert.DoesNotContain(
|
|
typeof(AcDream.App.Combat.LiveCombatAttackOperations).GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
|
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
|
|
FieldInfo combatTargets = Assert.Single(
|
|
typeof(AcDream.App.Combat.LiveCombatAttackOperations).GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
|
field => field.Name == "_targets");
|
|
Assert.Equal(
|
|
typeof(AcDream.App.Combat.ICombatAttackTargetSource),
|
|
combatTargets.FieldType);
|
|
Assert.DoesNotContain(
|
|
typeof(AcDream.App.Combat.CombatAttackTargetSource).GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
|
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
|
|
Assert.DoesNotContain(
|
|
typeof(AcDream.App.Combat.CombatAttackTargetSource).GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
|
field => field.FieldType
|
|
== typeof(AcDream.App.Interaction.SelectionInteractionController));
|
|
|
|
FieldInfo outboundDiagnostics = Assert.Single(
|
|
typeof(LocalPlayerOutboundController).GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
|
field => field.Name == "_diagnostic");
|
|
Assert.Equal(
|
|
typeof(IMovementTruthDiagnosticSink),
|
|
outboundDiagnostics.FieldType);
|
|
Assert.DoesNotContain(
|
|
typeof(AcDream.App.Input.MovementTruthDiagnosticController).GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
|
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
|
|
|
|
FieldInfo approachCompletions = Assert.Single(
|
|
typeof(AcDream.App.Input.PlayerModeController).GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
|
field => field.Name == "_approachCompletions");
|
|
Assert.Equal(
|
|
typeof(AcDream.App.Interaction.IPlayerApproachCompletionLifetimeOwner),
|
|
approachCompletions.FieldType);
|
|
Assert.DoesNotContain(
|
|
typeof(AcDream.App.Input.PlayerModeController).GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
|
field => field.FieldType
|
|
== typeof(AcDream.App.Interaction.SelectionInteractionController));
|
|
Assert.DoesNotContain(
|
|
typeof(AcDream.App.Interaction.PlayerApproachCompletionState).GetFields(
|
|
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
|
|
// publication transaction. Player-mode entry attaches presentation
|
|
// only: it gates on the Runtime-published controller, then wires
|
|
// camera -> shadow -> host slot -> mode flag. The old pinned
|
|
// markers (PreparePositionForCommit / InstallOrRebind /
|
|
// 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);
|
|
|
|
string mouseLookSource = File.ReadAllText(Path.Combine(
|
|
FindRepoRoot(),
|
|
"src",
|
|
"AcDream.App",
|
|
"Input",
|
|
"MouseLookController.cs"));
|
|
AssertAppearsInOrder(
|
|
mouseLookSource,
|
|
"controller?.StopMouseDrift",
|
|
"retail.FilterMouseDelta",
|
|
"_state.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);
|
|
}
|
|
|
|
[Fact]
|
|
public void CameraAndLocalPlayerFrameOwnersUseTypedSeamsWithoutWindowCallbacks()
|
|
{
|
|
Type[] owners =
|
|
[
|
|
typeof(AcDream.App.Rendering.CameraFrameController),
|
|
typeof(AcDream.App.Input.RetailLocalPlayerFrameController),
|
|
typeof(AcDream.App.Input.LocalPlayerProjectionController),
|
|
typeof(AcDream.App.Input.LiveLocalPlayerFrameRuntime),
|
|
typeof(AcDream.App.Input.LiveLocalPlayerProjectionRuntime),
|
|
typeof(AcDream.App.Combat.CombatCameraTargetSource),
|
|
];
|
|
|
|
foreach (Type owner in owners)
|
|
{
|
|
FieldInfo[] fields = owner.GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
|
Assert.DoesNotContain(fields, field => field.FieldType == typeof(GameWindow));
|
|
Assert.DoesNotContain(
|
|
fields,
|
|
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);
|
|
|
|
string cameraSource = File.ReadAllText(Path.Combine(
|
|
root,
|
|
"src",
|
|
"AcDream.App",
|
|
"Rendering",
|
|
"CameraFrameController.cs"));
|
|
AssertAppearsInOrder(
|
|
cameraSource,
|
|
"_localFrame.TryGetPresentationAfterNetwork",
|
|
"_spatialReconciler.Reconcile();",
|
|
"legacy.Update(",
|
|
"retail?.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)");
|
|
}
|
|
|
|
[Fact]
|
|
public void ProductionFrameAdaptersRetainTypedOwnersWithoutWindowCallbacks()
|
|
{
|
|
Type[] adapters =
|
|
[
|
|
typeof(LiveEntityTeardownFramePhase),
|
|
typeof(ConsoleUpdateFrameFailureSink),
|
|
typeof(PhysicsScriptClockPublisher),
|
|
typeof(LiveEntityLivenessFramePhase),
|
|
typeof(PlayerModeAutoEntryFramePhase),
|
|
];
|
|
|
|
foreach (Type adapter in adapters)
|
|
{
|
|
FieldInfo[] fields = adapter.GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
|
Assert.DoesNotContain(fields, field => field.FieldType == typeof(GameWindow));
|
|
Assert.DoesNotContain(
|
|
fields,
|
|
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
|
|
}
|
|
|
|
FieldInfo clock = Assert.Single(
|
|
typeof(LiveEntityLivenessFramePhase).GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
|
field => field.Name == "_clock");
|
|
Assert.Equal(typeof(IClientMonotonicTimeSource), clock.FieldType);
|
|
|
|
FieldInfo teardownRuntime = Assert.Single(
|
|
typeof(LiveEntityTeardownFramePhase).GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic));
|
|
Assert.Equal(typeof(LiveEntityRuntime), teardownRuntime.FieldType);
|
|
|
|
Type[] typedProductionOwners =
|
|
[
|
|
typeof(LiveEntityLivenessController),
|
|
typeof(AcDream.App.Input.LivePlayerModeAutoEntryContext),
|
|
typeof(LiveSessionLocalPhysicsTimestampPublisher),
|
|
typeof(AcDream.App.Physics.LiveEntityNetworkUpdateController),
|
|
typeof(AcDream.App.Rendering.LiveEntityPartArrayLifecycle),
|
|
typeof(AcDream.App.Net.LiveEntitySessionController),
|
|
];
|
|
foreach (Type owner in typedProductionOwners)
|
|
{
|
|
FieldInfo[] fields = owner.GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
|
Assert.DoesNotContain(fields, field => field.FieldType == typeof(GameWindow));
|
|
Assert.DoesNotContain(
|
|
fields,
|
|
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
|
|
}
|
|
|
|
FieldInfo originIdentity = Assert.Single(
|
|
typeof(LiveEntityWorldOriginCoordinator).GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic),
|
|
field => field.Name == "_identity");
|
|
Assert.Equal(
|
|
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);
|
|
// 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);
|
|
}
|
|
|
|
private static UpdateFrameOrchestrator Create(
|
|
List<string> calls,
|
|
RecordingTeardown? teardown = null,
|
|
RecordingFailureSink? failureSink = null,
|
|
FrameObservations? observed = null,
|
|
bool teleportPlace = false,
|
|
bool inboundCreatedPlayer = false,
|
|
IWorldGenerationAvailability? availability = null)
|
|
{
|
|
teardown ??= new RecordingTeardown(calls);
|
|
failureSink ??= new RecordingFailureSink();
|
|
return new UpdateFrameOrchestrator(
|
|
teardown,
|
|
failureSink,
|
|
new UpdateFrameClock(),
|
|
new RecordingClockPublisher(calls, observed),
|
|
new RecordingStreaming(calls),
|
|
new RecordingInput(calls, observed),
|
|
new RecordingLiveFrame(calls, observed),
|
|
new RecordingLiveness(calls),
|
|
new RecordingTeleport(calls, observed, teleportPlace),
|
|
new RecordingAutoEntry(calls),
|
|
new RecordingCamera(calls, observed, inboundCreatedPlayer),
|
|
new RecordingCommit(calls),
|
|
availability);
|
|
}
|
|
|
|
private sealed class RecordingTeardown(List<string> calls)
|
|
: IUpdateFrameTeardownPhase
|
|
{
|
|
public Exception? Failure { get; set; }
|
|
public int Attempts { get; private set; }
|
|
|
|
public void RetryPendingTeardowns()
|
|
{
|
|
calls.Add("teardown");
|
|
Attempts++;
|
|
if (Failure is not null)
|
|
throw Failure;
|
|
}
|
|
}
|
|
|
|
private sealed class RecordingFailureSink : IUpdateFrameFailureSink
|
|
{
|
|
public List<AggregateException> Errors { get; } = [];
|
|
public void ReportTeardownFailure(AggregateException error) => Errors.Add(error);
|
|
}
|
|
|
|
private sealed class RecordingClockPublisher(
|
|
List<string> calls,
|
|
FrameObservations? observed) : IUpdateFrameScriptClockPublisher
|
|
{
|
|
public void PublishTime(double scriptTime)
|
|
{
|
|
calls.Add("clock");
|
|
observed?.PublishedTimes.Add(scriptTime);
|
|
}
|
|
}
|
|
|
|
private sealed class RecordingStreaming(List<string> calls) : IStreamingFramePhase
|
|
{
|
|
public void Tick() => calls.Add("streaming");
|
|
}
|
|
|
|
private sealed class RecordingInput(
|
|
List<string> calls,
|
|
FrameObservations? observed) : IGameplayInputFramePhase
|
|
{
|
|
public void Tick(UpdateFrameTiming timing)
|
|
{
|
|
calls.Add("input");
|
|
observed?.Input.Add(timing);
|
|
}
|
|
}
|
|
|
|
private sealed class RecordingLiveFrame(
|
|
List<string> calls,
|
|
FrameObservations? observed) : IRetailLiveFramePhase
|
|
{
|
|
public void Tick(float deltaSeconds)
|
|
{
|
|
observed?.LiveDeltas.Add(deltaSeconds);
|
|
calls.Add("objects");
|
|
calls.Add("network");
|
|
calls.Add("commands");
|
|
calls.Add("ordinary-reconcile");
|
|
}
|
|
}
|
|
|
|
private sealed class RecordingLiveness(List<string> calls)
|
|
: ILiveEntityLivenessFramePhase
|
|
{
|
|
public void Tick() => calls.Add("liveness");
|
|
}
|
|
|
|
private sealed class RecordingTeleport(
|
|
List<string> calls,
|
|
FrameObservations? observed,
|
|
bool place)
|
|
: ILocalPlayerTeleportFramePhase
|
|
{
|
|
public void Tick(float deltaSeconds)
|
|
{
|
|
observed?.TeleportDeltas.Add(deltaSeconds);
|
|
if (!place)
|
|
{
|
|
calls.Add("teleport");
|
|
return;
|
|
}
|
|
|
|
calls.Add("teleport-place");
|
|
calls.Add("teleport-reconcile");
|
|
calls.Add("teleport-reveal");
|
|
}
|
|
}
|
|
|
|
private sealed class RecordingAutoEntry(List<string> calls)
|
|
: IPlayerModeAutoEntryFramePhase
|
|
{
|
|
public void TryEnter() => calls.Add("auto-entry");
|
|
}
|
|
|
|
private sealed class RecordingCamera(
|
|
List<string> calls,
|
|
FrameObservations? observed,
|
|
bool inboundCreatedPlayer) : ICameraFramePhase
|
|
{
|
|
public void Tick(UpdateFrameTiming timing)
|
|
{
|
|
if (inboundCreatedPlayer)
|
|
{
|
|
calls.Add("inbound-player-projection");
|
|
calls.Add("inbound-player-reconcile");
|
|
}
|
|
|
|
calls.Add("camera");
|
|
observed?.Camera.Add(timing);
|
|
}
|
|
}
|
|
|
|
private sealed class RecordingLivePhases(List<string> calls) :
|
|
ILiveObjectFramePhase,
|
|
IRuntimeLiveSessionFramePhase,
|
|
IPostNetworkCommandFramePhase,
|
|
ILiveSpatialReconcilePhase,
|
|
IRenderProjectionSyncPhase,
|
|
IWorldGenerationAvailability,
|
|
IRuntimePlacementProjectionRetryPhase
|
|
{
|
|
public bool IsWorldAvailable { get; set; }
|
|
public long QuiescedGeneration => IsWorldAvailable ? 0L : 1L;
|
|
|
|
public void Tick(float deltaSeconds) => calls.Add("objects");
|
|
public void Tick() => calls.Add("network");
|
|
public void RunPostNetworkCommandPhase() => calls.Add("commands");
|
|
public void Reconcile() => calls.Add("reconcile");
|
|
public void SynchronizeActiveSources() => calls.Add("render-sync");
|
|
public void RetryPending() => calls.Add("placement-retry");
|
|
}
|
|
|
|
private sealed class RecordingCommit(List<string> calls)
|
|
: IUpdateFrameCommitPhase
|
|
{
|
|
public void Commit() => calls.Add("commit");
|
|
}
|
|
|
|
private sealed class FrameObservations
|
|
{
|
|
public List<double> PublishedTimes { get; } = [];
|
|
public List<UpdateFrameTiming> Input { get; } = [];
|
|
public List<float> LiveDeltas { get; } = [];
|
|
public List<float> TeleportDeltas { get; } = [];
|
|
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;
|
|
}
|
|
|
|
throw new DirectoryNotFoundException("Could not find AcDream.slnx.");
|
|
}
|
|
|
|
private static void AssertAppearsInOrder(string source, params string[] markers)
|
|
{
|
|
int previous = -1;
|
|
foreach (string marker in markers)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
private static int CountOccurrences(string source, string marker) =>
|
|
source.Split(marker, StringSplitOptions.None).Length - 1;
|
|
}
|