133 lines
6 KiB
C#
133 lines
6 KiB
C#
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using AcDream.App.Tests.Architecture;
|
|
using AcDream.App.Physics;
|
|
using AcDream.Core.World;
|
|
using AcDream.Runtime.Session;
|
|
|
|
namespace AcDream.App.Tests.Physics;
|
|
|
|
public sealed class LiveEntityNetworkBranchRoutingTests
|
|
{
|
|
[Fact]
|
|
public void Vector_ProjectileStopsCanonicalAndOrdinaryRoutes()
|
|
{
|
|
var calls = new List<string>();
|
|
|
|
LiveEntityVectorRoute route = LiveEntityVectorRouter.Route(
|
|
() => { calls.Add("projectile"); return true; },
|
|
() => { calls.Add("canonical"); return true; },
|
|
() => calls.Add("ordinary"));
|
|
|
|
Assert.Equal(LiveEntityVectorRoute.Projectile, route);
|
|
Assert.Equal(["projectile"], calls);
|
|
}
|
|
|
|
[Fact]
|
|
public void Vector_CanonicalBodyRunsOnlyAfterProjectileDeclines()
|
|
{
|
|
var calls = new List<string>();
|
|
|
|
LiveEntityVectorRoute route = LiveEntityVectorRouter.Route(
|
|
() => { calls.Add("projectile"); return false; },
|
|
() => { calls.Add("canonical"); return true; },
|
|
() => calls.Add("ordinary"));
|
|
|
|
Assert.Equal(LiveEntityVectorRoute.CanonicalBody, route);
|
|
Assert.Equal(["projectile", "canonical"], calls);
|
|
}
|
|
|
|
[Fact]
|
|
public void Vector_OrdinaryRemoteIsTheLastFallback()
|
|
{
|
|
var calls = new List<string>();
|
|
|
|
LiveEntityVectorRoute route = LiveEntityVectorRouter.Route(
|
|
() => { calls.Add("projectile"); return false; },
|
|
() => { calls.Add("canonical"); return false; },
|
|
() => calls.Add("ordinary"));
|
|
|
|
Assert.Equal(LiveEntityVectorRoute.OrdinaryRemote, route);
|
|
Assert.Equal(["projectile", "canonical", "ordinary"], calls);
|
|
}
|
|
|
|
// C4 route 2 (2026-08-03): LocalForcePositionTransaction and its
|
|
// ForcePosition_* coverage here are RETIRED, not adapted — the class is
|
|
// deleted outright (docs/research/2026-08-03-c4-route-2-contract.md
|
|
// §"The contract" item 2). Its three jobs (ownership validation, the
|
|
// blip/commit, and the exactly-once ack including the displaced-
|
|
// authority case its trailing isCurrent() covered) are now properties of
|
|
// the Runtime-owned RuntimeAcceptedPositionDriveController and are tested
|
|
// there: tests/AcDream.Runtime.Tests/Session/RuntimeAcceptedPositionDriveControllerTests.cs.
|
|
|
|
/// <summary>
|
|
/// R8 review fix (2026-08-03): assembly checks for the generic-tail
|
|
/// double-write guard in <c>LiveEntityNetworkUpdateController.OnPosition</c>.
|
|
/// A full behavioral fixture is impractical here for the SAME reason
|
|
/// <c>C3cF1ProductionWiringTests</c> gives — the controller's dependency
|
|
/// set is composition-only (67+ collaborators wired only by
|
|
/// <c>SessionPlayerComposition</c>) — so this follows that file's exact
|
|
/// established pattern: assert the compiled call/return structure rather
|
|
/// than construct the class. The Runtime-level behavioral
|
|
/// coverage for the seam itself lives in
|
|
/// RuntimeAcceptedPositionDriveControllerTests; these pins are what stop
|
|
/// this App-layer call site from silently reintroducing the retired
|
|
/// duplicate-write authority (the deleted LocalForcePositionTransaction
|
|
/// pair + the generic render-tail writing the SAME accepted Position a
|
|
/// second time — 670f307c's divergence class).
|
|
/// </summary>
|
|
public sealed class LiveEntityNetworkUpdateControllerForcePositionWiringTests
|
|
{
|
|
[Fact]
|
|
public void GenericTailWriteIsNeverDuplicatedForTheLocalForcePositionPath()
|
|
{
|
|
// The generic render-tail's WorldEntity write is the ONE
|
|
// remaining writer of an accepted Position — it must serve
|
|
// remotes only, never a second local-player write alongside the
|
|
// Runtime-committed one.
|
|
MethodInfo genericTail = typeof(LiveEntityNetworkUpdateController)
|
|
.GetMethod(
|
|
"TryApplyGenericRemoteRenderPose",
|
|
BindingFlags.Static | BindingFlags.NonPublic)
|
|
?? throw new MissingMethodException(
|
|
typeof(LiveEntityNetworkUpdateController).FullName,
|
|
"TryApplyGenericRemoteRenderPose");
|
|
Assert.Single(
|
|
CompiledCallGraph.Read(genericTail),
|
|
call => call.Target.DeclaringType == typeof(WorldEntity)
|
|
&& call.Target.Name == nameof(WorldEntity.SetPosition));
|
|
}
|
|
|
|
[Fact]
|
|
public void CommittedOrDeferredCellReturnsBeforeReachingTheGenericTail()
|
|
{
|
|
// The Committed/DeferredCell branch must still return
|
|
// immediately after its two preserved side effects — a missing
|
|
// `return` here would fall through into the generic tail below
|
|
// and resurrect the double-write.
|
|
MethodInfo onPosition = typeof(LiveEntityNetworkUpdateController)
|
|
.GetMethod(
|
|
"OnPosition",
|
|
BindingFlags.Instance | BindingFlags.Public)
|
|
?? throw new MissingMethodException(
|
|
typeof(LiveEntityNetworkUpdateController).FullName,
|
|
"OnPosition");
|
|
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(onPosition);
|
|
CompiledCall drive = Assert.Single(calls, call =>
|
|
call.Target.DeclaringType
|
|
== typeof(RuntimeAcceptedPositionDriveController)
|
|
&& call.Target.Name == "TryExecuteAcceptedLocalPosition");
|
|
CompiledCall observe = calls.First(call =>
|
|
call.Offset > drive.Offset
|
|
&& call.Target.Name == "ObserveAcceptedLocalPosition");
|
|
IReadOnlyList<CompiledInstruction> instructions =
|
|
CompiledCallGraph.ReadInstructions(onPosition);
|
|
int observeInstruction = instructions
|
|
.Select((instruction, index) => (instruction, index))
|
|
.Single(pair => pair.instruction.Offset == observe.Offset)
|
|
.index;
|
|
|
|
Assert.Equal(OpCodes.Ret, instructions[observeInstruction + 1].OpCode);
|
|
}
|
|
}
|
|
}
|