Flips the SetPositionSimple classification (contact, PlayerDistance >= 96 m) for remotes onto 4b-1's drive controller and deletes both legacy far blocks, both duplicated 96f/4f constant pairs, and both `?? Vector3.Zero` fabrications. The 4 m constant now exists exactly once. Teleport and cell-less stay legacy for 4b-3. Retail: MoveOrTeleport @0x00516330's far branch runs StopInterpolating @0x005163CB before SetPositionSimple @0x005163D9 and returns 1 @0x005163E8 regardless — the SetPositionError is discarded — so HandleReceivedPosition arms ConstrainTo @0x00454272 post-move on commit AND on failure. The x87 parity decode at @0x00516393-@0x0051639E puts exactly 96.0 on the far branch. SetPositionSimple @0x005162B0 builds flags 0x1012 at @0x005162C4. Non-commit outcomes still advance the body, because retail's SetPositionInternal @0x00515BD0 commits the destination via store_position @0x00515CE2 when no cell resolves. The partition is by STAGE, not heuristic, enforced by an exhaustive switch: Refused/Contention/NotApplicable/RejectedPreparation store (the placement never executed); Committed/Deferred/RejectedByPlacement do not (the engine ran and refused, matching retail's non-storing returns @0x00515CB2 and @0x00515CD5). Without this a refused far snap froze the remote with an emptied queue. Also fixes a shipped defect this route made live: ParkDeferred's quiescence parks withdrew the entity (InWorld=false, clock suspended, residency removed) and were never restorable, while Forget(restoreCancelledPark: true) runs for every accepted Position on every entity. The restorable decision now lives inside ParkDeferred AFTER SnapToCell, reading body.CellPosition.ObjCellId — the value RestoreParkWithdrawal actually restores at — against every live quiescence rather than one minimum-OperationId token. The three pre-snap fields are hoisted into locals because SnapToCell ends with InWorld = true. ParkCollisionResidents passes restorableOnCancel: false explicitly; the plain unplaceable park is provably unchanged. RestoreParkWithdrawal re-tests the prefix at restore time so a retained route-2 park cannot re-admit into a prefix that began quiescing during the park. CanAttemptDestination is retained as an OPTIMISATION only, with the two Core predicates it cannot reproduce written down at the pre-flight, plus the two properties that depend on it staying there. Four fix rounds and eight Opus reviews. The slice was fully green at 10,990, 10,997 and 11,004 while containing real defects — a frozen remote pinned as correct by its own test, a fallback that over-wrote on the exact retail paths that decline to store, and a park guard incomplete on two independent axes. Register: AP-137 (leftover classifications take AP-87's catch-up; states the cell-less enqueue-vs-place delta deferred to 4b-3, that RejectedData is applied anyway, and the headless divergence), AP-138 (the refusable far placement), AP-136 narrowed to match the relocation. #309's acceptance steps rewritten — step 5 previously asserted a recovery the code does not perform — and gated on a new ACDREAM_PROBE_PARK=1 signal so the check cannot pass while broken. Suite 11,009 passed / 4 skipped / 0 failed against a measured 10,968 baseline. The 10,973 figure recorded earlier was wrong and is corrected here. Connected gate outstanding: the two-client far-snap walk and #309. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
312 lines
14 KiB
C#
312 lines
14 KiB
C#
using System.Numerics;
|
|
using AcDream.Core.Net.Messages;
|
|
using AcDream.Core.Physics;
|
|
using AcDream.Runtime.Entities;
|
|
using AcDream.Runtime.Physics;
|
|
using AcDream.Runtime.Session;
|
|
|
|
namespace AcDream.Runtime.Tests.Physics;
|
|
|
|
/// <summary>
|
|
/// C4 route 4b-2: the arm-selection half of the remote far snap. Every route
|
|
/// here is produced by the REAL classifier from a real request, including the
|
|
/// negative cases the classifier genuinely emits from other entry points (a
|
|
/// local-player <c>SetPositionSimple</c>, a remote top-level Create) and which
|
|
/// the far arm must decline. Exactly ONE route is hand-shaped — the
|
|
/// flags/disposition mismatch in
|
|
/// <see cref="OwnsFarSnap_IsAStrictSubsetOfThePlacementOwnersPredicate"/>,
|
|
/// which no classifier path produces but a caller could express.
|
|
///
|
|
/// <para>
|
|
/// Each test was verified to discriminate by temporarily reverting the
|
|
/// corresponding guard in <see cref="RuntimeRemoteFarSnapPosition"/> and
|
|
/// confirming the matching test failed, then restoring it.
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed class RuntimeRemoteFarSnapPositionTests
|
|
{
|
|
private const uint Cell = 0x0101FFFFu;
|
|
|
|
[Fact]
|
|
public void OwnsFarSnap_TrueForTheRemoteFarBranch()
|
|
{
|
|
RuntimeAuthoritativePositionRoute far =
|
|
Classify(hasContact: true, playerDistance: 200f);
|
|
|
|
// Retail: player_distance >= 96f -> StopInterpolating @0x005163CB +
|
|
// SetPositionSimple @0x005163D9, whose arg3 != 0 builds flags 0x1012
|
|
// (Teleport|Slide|SendPositionEvent) @0x005162C4.
|
|
Assert.Equal(
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
far.Disposition);
|
|
Assert.True(far.StopInterpolating);
|
|
Assert.True(RuntimeRemoteFarSnapPosition.OwnsFarSnap(far));
|
|
}
|
|
|
|
[Fact]
|
|
public void OwnsFarSnap_TrueExactlyAtTheRetailBoundary()
|
|
{
|
|
// Retail compares player_distance against 96f and takes the
|
|
// InterpolateTo branch only when strictly LESS (@0x00516393-@0x0051639E,
|
|
// the `x87_r7 < temp1` test). 96.0 itself is the far branch.
|
|
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(
|
|
Classify(hasContact: true, playerDistance: 95.99f)));
|
|
Assert.True(RuntimeRemoteFarSnapPosition.OwnsFarSnap(
|
|
Classify(hasContact: true, playerDistance: 96f)));
|
|
}
|
|
|
|
[Fact]
|
|
public void OwnsFarSnap_FalseForEveryOtherRemoteClassification()
|
|
{
|
|
// Near InterpolateTo @0x005163AF.
|
|
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(
|
|
Classify(hasContact: true, playerDistance: 10f)));
|
|
// Airborne no-op @0x0051636D.
|
|
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(
|
|
Classify(hasContact: false, playerDistance: 10f)));
|
|
// Cell-less: retail's `this_1->cell == 0` branch @0x00516386 —
|
|
// SetPosition, NOT the far snap. 4b-3 owns it.
|
|
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(
|
|
Classify(hasContact: true, playerDistance: 200f, committedCellId: 0u)));
|
|
// Fresh TELEPORT_TS: the same @0x00516386 branch. 4b-3 owns it.
|
|
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(
|
|
Classify(
|
|
hasContact: true,
|
|
playerDistance: 200f,
|
|
previousTeleport: 10,
|
|
acceptedTeleport: 11)));
|
|
// Rejections and "no classification at all".
|
|
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(
|
|
Classify(
|
|
hasContact: true,
|
|
playerDistance: 200f,
|
|
disposition: PositionTimestampDisposition.Rejected)));
|
|
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(
|
|
Classify(hasContact: true, playerDistance: float.NaN)));
|
|
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(null));
|
|
}
|
|
|
|
/// <summary>
|
|
/// The disposition alone is not the discriminator: the classifier emits
|
|
/// <c>SetPositionSimple</c> for the LOCAL PLAYER's FORCE_POSITION branch
|
|
/// too (<c>RuntimeAuthoritativePositionRouteClassifier.cs:332</c>). Route
|
|
/// 4b-2 is a remote route; claiming that one would run a remote arm over
|
|
/// route 2's local-player transaction.
|
|
/// </summary>
|
|
[Fact]
|
|
public void OwnsFarSnap_FalseForTheLocalPlayerForcePositionRoute()
|
|
{
|
|
RuntimeAuthoritativePositionRoute force = ClassifyKind(
|
|
RuntimePositionEntityKind.LocalPlayer,
|
|
hasContact: true,
|
|
playerDistance: 200f,
|
|
disposition: PositionTimestampDisposition.ForcePosition);
|
|
|
|
Assert.Equal(
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
force.Disposition);
|
|
Assert.Equal(
|
|
RuntimeSetPositionOperationKind.LocalAuthoritative,
|
|
force.OperationKind);
|
|
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(force));
|
|
}
|
|
|
|
/// <summary>
|
|
/// A REMOTE top-level initial Create is also <c>RemoteAuthoritative</c>;
|
|
/// it is excluded here by its disposition (<c>SetPosition</c>), which
|
|
/// route 4b-3 owns.
|
|
/// </summary>
|
|
[Fact]
|
|
public void OwnsFarSnap_FalseForARemoteTopLevelCreateRoute()
|
|
{
|
|
RuntimeAuthoritativePositionRoute create =
|
|
RuntimeAuthoritativePositionRouteClassifier.ClassifyCreate(
|
|
new RuntimeCreatePositionRouteRequest(
|
|
Authority(PositionTimestampDisposition.Apply, 10, 10),
|
|
RuntimePositionEntityKind.Remote,
|
|
RuntimeCreateResidenceKind.TopLevel,
|
|
new CreateObject.ServerPosition(
|
|
Cell, 10f, 20f, 30f, 1f, 0f, 0f, 0f),
|
|
default));
|
|
|
|
Assert.Equal(
|
|
RuntimeSetPositionOperationKind.RemoteAuthoritative,
|
|
create.OperationKind);
|
|
Assert.Equal(
|
|
PhysicsSetPositionFlags.Placement | PhysicsSetPositionFlags.Slide,
|
|
create.SetPositionFlags);
|
|
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(create));
|
|
}
|
|
|
|
/// <summary>
|
|
/// The invariant the <c>Teleport</c>-flag term actually carries:
|
|
/// <see cref="RuntimeRemoteFarSnapPosition.OwnsFarSnap"/> must be a strict
|
|
/// SUBSET of
|
|
/// <see cref="RuntimeRemotePlacementDriveController.OwnsPlacement"/>. The
|
|
/// far arm hands its route straight to that controller, so any route the
|
|
/// arm claims but the controller declines would execute
|
|
/// <c>StopInterpolating</c> and then silently do nothing
|
|
/// (<c>NotApplicable</c>) — a remote that stops tracking the server with
|
|
/// no diagnostic. The flags term is what keeps the two predicates
|
|
/// aligned; without it, the hand-shaped route below is claimed here and
|
|
/// declined there.
|
|
/// </summary>
|
|
[Fact]
|
|
public void OwnsFarSnap_IsAStrictSubsetOfThePlacementOwnersPredicate()
|
|
{
|
|
// Every route the real classifier produces.
|
|
foreach (RuntimeAuthoritativePositionRoute route in new[]
|
|
{
|
|
Classify(hasContact: true, playerDistance: 200f),
|
|
Classify(hasContact: true, playerDistance: 10f),
|
|
Classify(hasContact: false, playerDistance: 10f),
|
|
Classify(hasContact: true, playerDistance: 200f, committedCellId: 0u),
|
|
Classify(hasContact: true, playerDistance: float.NaN),
|
|
ClassifyKind(
|
|
RuntimePositionEntityKind.LocalPlayer,
|
|
hasContact: true,
|
|
playerDistance: 200f,
|
|
disposition: PositionTimestampDisposition.ForcePosition),
|
|
})
|
|
{
|
|
if (RuntimeRemoteFarSnapPosition.OwnsFarSnap(route))
|
|
{
|
|
Assert.True(
|
|
RuntimeRemotePlacementDriveController.OwnsPlacement(route));
|
|
}
|
|
}
|
|
|
|
// …and the shape a caller could construct that the classifier does
|
|
// not: the far disposition and operation kind with a Create's flags.
|
|
RuntimeAuthoritativePositionRoute mismatched =
|
|
Classify(hasContact: true, playerDistance: 200f) with
|
|
{
|
|
SetPositionFlags = PhysicsSetPositionFlags.Placement
|
|
| PhysicsSetPositionFlags.Slide,
|
|
};
|
|
Assert.False(
|
|
RuntimeRemotePlacementDriveController.OwnsPlacement(mismatched));
|
|
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(mismatched));
|
|
}
|
|
|
|
// ── Arm selection is total, and the leftovers are named ─────────────────
|
|
|
|
[Fact]
|
|
public void ResolveArm_MapsEveryRemoteClassificationToExactlyOneArm()
|
|
{
|
|
Assert.Equal(
|
|
RuntimeRemoteAcceptedPositionArm.AirborneNoOperation,
|
|
RuntimeRemoteFarSnapPosition.ResolveArm(
|
|
Classify(hasContact: false, playerDistance: 10f)));
|
|
Assert.Equal(
|
|
RuntimeRemoteAcceptedPositionArm.NearInterpolate,
|
|
RuntimeRemoteFarSnapPosition.ResolveArm(
|
|
Classify(hasContact: true, playerDistance: 10f)));
|
|
Assert.Equal(
|
|
RuntimeRemoteAcceptedPositionArm.FarSnapPlacement,
|
|
RuntimeRemoteFarSnapPosition.ResolveArm(
|
|
Classify(hasContact: true, playerDistance: 200f)));
|
|
|
|
// The acdream-only leftovers, all four shapes. This is the stated
|
|
// policy: they are NOT far. Reading "not Interpolate" as "far" is
|
|
// route 4a's own review finding, one level up.
|
|
Assert.Equal(
|
|
RuntimeRemoteAcceptedPositionArm.UnroutedCatchUp,
|
|
RuntimeRemoteFarSnapPosition.ResolveArm(
|
|
Classify(hasContact: true, playerDistance: 10f, committedCellId: 0u)));
|
|
Assert.Equal(
|
|
RuntimeRemoteAcceptedPositionArm.UnroutedCatchUp,
|
|
RuntimeRemoteFarSnapPosition.ResolveArm(
|
|
Classify(
|
|
hasContact: true,
|
|
playerDistance: 10f,
|
|
disposition: PositionTimestampDisposition.Rejected)));
|
|
Assert.Equal(
|
|
RuntimeRemoteAcceptedPositionArm.UnroutedCatchUp,
|
|
RuntimeRemoteFarSnapPosition.ResolveArm(
|
|
Classify(hasContact: true, playerDistance: float.NaN)));
|
|
Assert.Equal(
|
|
RuntimeRemoteAcceptedPositionArm.UnroutedCatchUp,
|
|
RuntimeRemoteFarSnapPosition.ResolveArm(null));
|
|
}
|
|
|
|
// ── The single ConstrainTo site's gate ──────────────────────────────────
|
|
|
|
[Fact]
|
|
public void OwnsAfterOperationConstraint_CoversTheThreeArmsThatRunAnOperation()
|
|
{
|
|
// Retail arms @0x00454272 for every nonzero MoveOrTeleport return.
|
|
// The airborne no-op returns 0 @0x0051636D, so it is excluded — and
|
|
// that exclusion is route 4a's, carried forward unchanged.
|
|
Assert.True(RuntimeRemoteFarSnapPosition.OwnsAfterOperationConstraint(
|
|
Classify(hasContact: true, playerDistance: 10f)));
|
|
Assert.True(RuntimeRemoteFarSnapPosition.OwnsAfterOperationConstraint(
|
|
Classify(hasContact: true, playerDistance: 200f)));
|
|
Assert.True(RuntimeRemoteFarSnapPosition.OwnsAfterOperationConstraint(
|
|
Classify(hasContact: false, playerDistance: 10f)));
|
|
|
|
// The leftovers still arm through the App's legacy pre-operation
|
|
// site; claiming them here would double-arm them.
|
|
Assert.False(RuntimeRemoteFarSnapPosition.OwnsAfterOperationConstraint(
|
|
Classify(hasContact: true, playerDistance: 10f, committedCellId: 0u)));
|
|
Assert.False(RuntimeRemoteFarSnapPosition.OwnsAfterOperationConstraint(
|
|
Classify(hasContact: true, playerDistance: float.NaN)));
|
|
Assert.False(
|
|
RuntimeRemoteFarSnapPosition.OwnsAfterOperationConstraint(null));
|
|
}
|
|
|
|
private static RuntimeAuthoritativePositionAuthority Authority(
|
|
PositionTimestampDisposition disposition,
|
|
ushort previousTeleport,
|
|
ushort acceptedTeleport) =>
|
|
new(
|
|
new RuntimeGenerationToken(7),
|
|
new RuntimeEntityKey(0x70000001u, 3),
|
|
PositionAuthorityVersion: 11UL,
|
|
AcceptedPositionSequence: 20,
|
|
previousTeleport,
|
|
acceptedTeleport,
|
|
disposition);
|
|
|
|
private static RuntimeAuthoritativePositionRoute Classify(
|
|
bool hasContact,
|
|
float playerDistance,
|
|
uint committedCellId = Cell,
|
|
ushort previousTeleport = 10,
|
|
ushort acceptedTeleport = 10,
|
|
PositionTimestampDisposition disposition =
|
|
PositionTimestampDisposition.Apply) =>
|
|
ClassifyKind(
|
|
RuntimePositionEntityKind.Remote,
|
|
hasContact,
|
|
playerDistance,
|
|
committedCellId,
|
|
previousTeleport,
|
|
acceptedTeleport,
|
|
disposition);
|
|
|
|
private static RuntimeAuthoritativePositionRoute ClassifyKind(
|
|
RuntimePositionEntityKind kind,
|
|
bool hasContact,
|
|
float playerDistance,
|
|
uint committedCellId = Cell,
|
|
ushort previousTeleport = 10,
|
|
ushort acceptedTeleport = 10,
|
|
PositionTimestampDisposition disposition =
|
|
PositionTimestampDisposition.Apply) =>
|
|
RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition(
|
|
new RuntimeAcceptedPositionRouteRequest(
|
|
Authority(disposition, previousTeleport, acceptedTeleport),
|
|
kind,
|
|
RuntimeAcceptedPositionSource.PositionEvent,
|
|
new CreateObject.ServerPosition(
|
|
Cell, 10f, 20f, 30f, 1f, 0f, 0f, 0f),
|
|
PlacementFrame: 0u,
|
|
PositionPackVelocity: Vector3.Zero,
|
|
committedCellId,
|
|
hasContact,
|
|
playerDistance,
|
|
UsePositionFromServer: false,
|
|
HasAnimations: false,
|
|
default));
|
|
}
|