The two gates that decide whether an accepted remote Position is interpolated
or hard-snapped read `Airborne`, which is `!Body.OnWalkable` — WALKABILITY.
Retail reads CONTACT: InterpolationManager::adjust_offset @0x00555D30 gates its
entire body on `transient_state & 1` @0x00555D52, so a retail body in contact
with a non-walkable face still interpolates.
The two predicates disagree in exactly one state — in contact, not on walkable
ground — which 204d0ae0 turned from unreachable into ordinary. Before it, the
per-tick forge made every non-airborne remote walkable by construction, so the
disagreement could not occur.
Both gates now read `!Body.InContact`: ApplyRemoteContactRouting's flight
carve-out and OnPosition's player-remote arm.
`Airborne` is deliberately NOT re-derived from CONTACT. That would perturb all
five of its writers and contradict a pinned assertion in
RemoteTeleportPlacementTests.Apply_PendingGroundToSteepContact_ (InContact:
true, OnWalkable: false -> Assert.True(remote.Airborne)); a previous
implementer attempted it and correctly backed out rather than editing the
assertion. This narrower shape touches no existing test.
AP-140's register row is retired in this commit, as the row itself specified.
Honest scope: this is a faithfulness fix, not a visible one. ACE derives its
IsGrounded flag with the same floor_z test, so during a slide it almost
certainly reports not-grounded, the classifier returns NoPositionOperation, and
neither arm is taken. Expect no observable change against ACE.
Suite 11,027 passed / 4 skipped / 0 failed (baseline 11,023).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
185 lines
7.8 KiB
C#
185 lines
7.8 KiB
C#
using System.Text.RegularExpressions;
|
|
using AcDream.App.Physics;
|
|
|
|
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): source pins 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 STRUCTURE of the production source
|
|
/// 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 LocalForcePositionTransactionIsNeverCalledFromThisFile()
|
|
{
|
|
string source = ReadSource("LiveEntityNetworkUpdateController.cs");
|
|
|
|
// The name may still appear in a comment explaining what
|
|
// replaced it (contract §"the deleted LocalForcePositionTransaction");
|
|
// what must be gone is any actual call into it.
|
|
Assert.DoesNotContain(
|
|
"LocalForcePositionTransaction.Apply(",
|
|
source,
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void GenericTailWriteIsNeverDuplicatedForTheLocalForcePositionPath()
|
|
{
|
|
string source = ReadSource("LiveEntityNetworkUpdateController.cs");
|
|
|
|
// 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.
|
|
Assert.Single(
|
|
Regex.Matches(source, @"entity\.SetPosition\(worldPos\);")
|
|
.Cast<Match>());
|
|
}
|
|
|
|
[Fact]
|
|
public void CommittedOrDeferredCellReturnsBeforeReachingTheGenericTail()
|
|
{
|
|
string source = ReadSource("LiveEntityNetworkUpdateController.cs");
|
|
|
|
// 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.
|
|
Assert.Matches(
|
|
new Regex(
|
|
@"ObserveAcceptedLocalPosition\(\s*"
|
|
+ @"update\.Position\.LandblockId\);\s*return;",
|
|
RegexOptions.Singleline),
|
|
source);
|
|
}
|
|
|
|
/// <summary>
|
|
/// AP-140 (retired 2026-08-04): the SECOND accepted-Position routing
|
|
/// gate — <c>OnPosition</c>'s player-remote landing block — must
|
|
/// select the hard snap on retail's CONTACT predicate
|
|
/// (<c>InterpolationManager::adjust_offset</c> @0x00555D30 gates its
|
|
/// whole body on <c>transient_state & 1</c> @0x00555D52, and bit 0
|
|
/// is <c>CONTACT_TS</c>), not on the client <c>Airborne</c> flag,
|
|
/// which is <c>!Body.OnWalkable</c> — WALKABILITY, a strictly wider
|
|
/// set that also captures a remote sliding on a steep face.
|
|
///
|
|
/// <para>
|
|
/// A source pin rather than a behavioural fixture for the reason this
|
|
/// class already documents: the controller's dependency set is
|
|
/// composition-only. The twin gate inside
|
|
/// <c>ApplyRemoteContactRouting</c> — a static method, so reachable —
|
|
/// IS covered behaviourally, in
|
|
/// <c>LiveEntityNetworkRemoteSteadyStateIntegrationTests</c>. Restore
|
|
/// <c>if (rmState.Airborne)</c> here and this test fails.
|
|
/// </para>
|
|
/// </summary>
|
|
[Fact]
|
|
public void PlayerRemoteLandingSnapSelectsOnContactNotOnWalkability()
|
|
{
|
|
string source = ReadSource("LiveEntityNetworkUpdateController.cs");
|
|
|
|
Assert.Contains(
|
|
"if (!rmState.Body.InContact)",
|
|
source,
|
|
StringComparison.Ordinal);
|
|
// `rmState.Airborne` survives as a WRITE target and in prose (the
|
|
// block's own comment explains why it is deliberately not cleared
|
|
// there); what must never come back is reading it as the gate.
|
|
Assert.DoesNotContain(
|
|
"if (rmState.Airborne)",
|
|
source,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain(
|
|
"if (remote.Airborne)",
|
|
source,
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
private static string ReadSource(string fileName)
|
|
{
|
|
DirectoryInfo? directory = new(AppContext.BaseDirectory);
|
|
while (directory is not null)
|
|
{
|
|
if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
|
|
{
|
|
return File.ReadAllText(Path.Combine(
|
|
directory.FullName,
|
|
"src",
|
|
"AcDream.App",
|
|
"Physics",
|
|
fileName));
|
|
}
|
|
|
|
directory = directory.Parent;
|
|
}
|
|
|
|
throw new DirectoryNotFoundException("Could not find AcDream.slnx.");
|
|
}
|
|
}
|
|
}
|