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>
2509 lines
116 KiB
C#
2509 lines
116 KiB
C#
using System.Numerics;
|
|
using AcDream.Content;
|
|
using AcDream.Content.Pak;
|
|
using AcDream.Core.Net;
|
|
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.Session;
|
|
|
|
/// <summary>
|
|
/// C4 route 4b-1/4b-2: focused tests for the Runtime-owned, per-entity remote
|
|
/// placement infrastructure. 4b-1 wired no production caller at all; 4b-2 gave
|
|
/// the far arm one (<c>LiveEntityNetworkUpdateController.ApplyRemoteContactRouting</c>,
|
|
/// exercised end to end by <c>LiveEntityNetworkRemoteFarSnapIntegrationTests</c>
|
|
/// — correcting this comment's earlier "This route wires NO production
|
|
/// caller"). Every test in THIS file still drives the seam directly, because
|
|
/// each one constructs an already-classified
|
|
/// <see cref="RuntimeAuthoritativePositionRoute"/>
|
|
/// directly (the same shape
|
|
/// <see cref="RuntimeEntityObjectLifetime.ClassifyRemoteAcceptedPosition"/>
|
|
/// would hand a real caller) so each scenario is exercised precisely, mirroring
|
|
/// <c>RuntimeSetPositionStateTests</c>' bare-<see cref="RuntimeEntityObjectLifetime"/>
|
|
/// fixture rather than the full <c>GameRuntime</c>/<c>LiveSessionHost</c>
|
|
/// harness route 2's tests use — this controller has no local-player
|
|
/// controller, outbound session, or generation dependency to bootstrap.
|
|
///
|
|
/// Round 3 (correction n3) — the earlier version of this paragraph asserted
|
|
/// that "each test was verified to actually discriminate its own fix", which
|
|
/// is a claim about a procedure run outside the tree with no per-test mapping
|
|
/// anywhere in it. Reduced to what this file can actually show: every test
|
|
/// here names, in its own doc, the specific guard or omission it fails
|
|
/// without. Whether that revert was run, and what the run measured, belongs
|
|
/// in the slice's review record, not in a comment that cannot be checked
|
|
/// against the code beside it.
|
|
/// </summary>
|
|
public sealed class RuntimeRemotePlacementDriveControllerTests
|
|
{
|
|
private const uint SourceLandblock = 0xB1000000u;
|
|
private const uint SourceCell = SourceLandblock | 0x0001u;
|
|
private const uint DestinationLandblock = 0xB2000000u;
|
|
private const uint DestinationCell = DestinationLandblock | 0x0001u;
|
|
|
|
/// <summary>
|
|
/// The destination landblock's +X neighbour. Landblock ids pack the block
|
|
/// X index in bits 24-31, so 0xB2 → 0xB3 is one block east, and
|
|
/// <c>LandDefs.LcoordToGid</c> re-derives exactly this prefix for a global
|
|
/// lcoord one cell past the 192 m seam.
|
|
/// </summary>
|
|
private const uint NeighbourLandblock = 0xB3000000u;
|
|
|
|
/// <summary>
|
|
/// Cell (7, 0) of the destination landblock — block-local X in [168, 192),
|
|
/// Y in [0, 24). <c>LandDefs.GidToLcoord</c>'s inverse:
|
|
/// <c>low = (ly & 7) + ((lx & 7) << 3) + 1 = 0 + 56 + 1 = 57</c>.
|
|
/// </summary>
|
|
private const uint DestinationSeamCell = DestinationLandblock | 57u;
|
|
private const float SpawnHeight = 7f;
|
|
|
|
[Fact]
|
|
public void NotApplicable_WhenDispositionIsNotOwnedByThisRoute()
|
|
{
|
|
foreach (RuntimeAuthoritativePositionDisposition disposition in
|
|
new[]
|
|
{
|
|
RuntimeAuthoritativePositionDisposition.Interpolate,
|
|
RuntimeAuthoritativePositionDisposition.NoPositionOperation,
|
|
RuntimeAuthoritativePositionDisposition.RejectedAuthority,
|
|
RuntimeAuthoritativePositionDisposition.RejectedData,
|
|
RuntimeAuthoritativePositionDisposition.AwaitFreshPosition,
|
|
})
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003001u);
|
|
AttachBody(lifetime, record, SourceCell);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record, disposition, DestinationCell);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.NotApplicable,
|
|
drive.TryExecuteAcceptedRemotePosition(record, route));
|
|
Assert.Equal(0, drive.PendingCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void NotApplicable_WhenTheEntityHasNoCanonicalBody()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003002u);
|
|
// Deliberately never attach a body.
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.NotApplicable,
|
|
drive.TryExecuteAcceptedRemotePosition(record, route));
|
|
}
|
|
|
|
/// <summary>
|
|
/// B6 review fix: <c>OwnsPlacement</c> keyed on <c>Disposition</c> alone
|
|
/// would claim this route too — the classifier emits
|
|
/// <c>SetPositionSimple</c> for the LOCAL PLAYER's FORCE_POSITION/
|
|
/// teleport branches (<c>RuntimeSetPositionOperationKind.LocalAuthoritative</c>),
|
|
/// not only for remotes. The static predicate itself is exercised
|
|
/// directly (no entity/body needed) since it takes only the route.
|
|
/// </summary>
|
|
[Fact]
|
|
public void OwnsPlacement_FalseWhenOperationKindIsNotRemoteAuthoritative()
|
|
{
|
|
// RuntimeSetPositionOperationKind is internal, so a public [Theory]
|
|
// cannot take it as a parameter (CS0051) — iterate directly instead,
|
|
// mirroring NotApplicable_WhenDispositionIsNotOwnedByThisRoute's own
|
|
// foreach-over-internal-enum shape.
|
|
foreach (RuntimeSetPositionOperationKind operationKind in
|
|
new[]
|
|
{
|
|
RuntimeSetPositionOperationKind.InitialLogin,
|
|
RuntimeSetPositionOperationKind.LocalAuthoritative,
|
|
RuntimeSetPositionOperationKind.ProjectileAuthoritative,
|
|
})
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x7000300Fu);
|
|
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
DestinationCell,
|
|
operationKind: operationKind);
|
|
|
|
Assert.False(
|
|
RuntimeRemotePlacementDriveController.OwnsPlacement(route));
|
|
|
|
// End to end: this controller must decline the SAME route as
|
|
// NotApplicable, not merely the static predicate in isolation.
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive =
|
|
CreateDrive(lifetime, window);
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.NotApplicable,
|
|
drive.TryExecuteAcceptedRemotePosition(record, route));
|
|
Assert.Equal(0, drive.PendingCount);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// C2-4 review fix (delta round): the <c>OperationKind is RemoteAuthoritative</c>
|
|
/// guard alone is STILL not exact — <c>RuntimeAuthoritativePositionRouteClassifier
|
|
/// .ClassifyCreate</c> emits <c>Disposition = SetPosition</c> AND
|
|
/// <c>OperationKind = RemoteAuthoritative</c> for a REMOTE top-level
|
|
/// initial Create too (only the LOCAL PLAYER's Create maps to
|
|
/// <c>InitialLogin</c>), so both the disposition and operation-kind
|
|
/// guards pass for a Create-shaped route. Retail's own flag choice is
|
|
/// what actually distinguishes them: a Create carries
|
|
/// <c>InitialCreateFlags</c> (<c>Placement|Slide</c>, no
|
|
/// <c>Teleport</c>); every remote accepted-Position SetPosition/
|
|
/// SetPositionSimple route carries <c>AuthoritativeTeleportFlags</c>
|
|
/// (<c>Teleport|Slide|SendPositionEvent</c>). Route 4b-1 is a
|
|
/// POSITION-only route — the first-entry conductor owns every Create —
|
|
/// so a Create-shaped route reaching this controller must be declined.
|
|
/// </summary>
|
|
[Fact]
|
|
public void OwnsPlacement_FalseForARemoteTopLevelCreateShapedRoute()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003014u);
|
|
|
|
RuntimeAuthoritativePositionRoute createRoute = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell,
|
|
operationKind: RuntimeSetPositionOperationKind.RemoteAuthoritative,
|
|
setPositionFlags: PhysicsSetPositionFlags.Placement
|
|
| PhysicsSetPositionFlags.Slide);
|
|
|
|
Assert.False(
|
|
RuntimeRemotePlacementDriveController.OwnsPlacement(createRoute));
|
|
|
|
// End to end: this controller must decline the SAME route as
|
|
// NotApplicable, not merely the static predicate in isolation.
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive =
|
|
CreateDrive(lifetime, window);
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.NotApplicable,
|
|
drive.TryExecuteAcceptedRemotePosition(record, createRoute));
|
|
Assert.Equal(0, drive.PendingCount);
|
|
|
|
// The genuine remote accepted-Position shape (SAME disposition, SAME
|
|
// operation kind, Teleport-flagged) must still be owned — the fix
|
|
// must not have over-corrected into refusing everything.
|
|
RuntimeAuthoritativePositionRoute positionRoute = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell);
|
|
Assert.True(
|
|
RuntimeRemotePlacementDriveController.OwnsPlacement(positionRoute));
|
|
}
|
|
|
|
/// <summary>
|
|
/// The central decision's core pin: a destination this host's service
|
|
/// window does not currently cover must be refused, not parked — no
|
|
/// operation may open at all, so the body's own <c>InWorld</c>/object
|
|
/// clock are never touched.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Refused_WhenDestinationIsNotWithinServiceWindow_NoOperationOpensAndBodyStaysInWorld()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003003u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
// The service window allows nothing — mirrors a destination
|
|
// landblock this host has never collision-published.
|
|
var window = new FakeServiceWindow();
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell);
|
|
|
|
RuntimeRemotePlacementExecutionStatus status =
|
|
drive.TryExecuteAcceptedRemotePosition(record, route);
|
|
|
|
Assert.Equal(RuntimeRemotePlacementExecutionStatus.Refused, status);
|
|
Assert.True(body.InWorld);
|
|
Assert.True(record.ObjectClock.IsActive);
|
|
Assert.Equal(
|
|
0,
|
|
lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The exact §"central decision" sequence the contract's acceptance
|
|
/// section pins: attempt a placement whose destination is refused, then
|
|
/// deliver the entity's NEXT accepted Position — which, whatever it
|
|
/// classifies to, ALWAYS runs <c>RuntimeSetPositionState.Forget</c> for
|
|
/// this entity first
|
|
/// (<see cref="RuntimeEntityObjectLifetime.TryApplyPosition"/>, called
|
|
/// unconditionally regardless of disposition). Because nothing was ever
|
|
/// parked in the first step, that unconditional Forget has nothing to
|
|
/// discard, and the entity is STILL in the world afterward — the
|
|
/// invisible-and-intangible failure this route exists to prevent.
|
|
/// </summary>
|
|
[Fact]
|
|
public void CentralDecision_RefusedPlacementThenNextAcceptedPositionForgetsNothing_EntityStaysInWorld()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003004u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
var window = new FakeServiceWindow();
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell);
|
|
|
|
// Packet N: destination not placeable now.
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Refused,
|
|
drive.TryExecuteAcceptedRemotePosition(record, route));
|
|
Assert.True(body.InWorld);
|
|
Assert.True(record.ObjectClock.IsActive);
|
|
|
|
// Packet N+1 ~150 ms later: whatever it classifies to (here,
|
|
// Interpolate — the caller never reaches this controller at all for
|
|
// that disposition), production's merge unconditionally Forgets any
|
|
// in-flight operation for this entity FIRST. Simulated directly
|
|
// (mirrors RuntimeAcceptedPositionDriveControllerTests'
|
|
// Equal_ClearsPendingWithoutReissuing... fixture, which drives the
|
|
// same production Forget call without a full second network
|
|
// round-trip).
|
|
RuntimePlacementCancellationReceipt cancellation =
|
|
lifetime.Physics.SetPosition.Forget(record);
|
|
if (cancellation.IsValid)
|
|
lifetime.Physics.SetPosition.PublishCancellation(cancellation);
|
|
|
|
Assert.True(body.InWorld);
|
|
Assert.True(record.ObjectClock.IsActive);
|
|
Assert.Equal(SourceCell, record.FullCellId);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
[Fact]
|
|
public void Committed_WhenDestinationIsWithinServiceWindowAndCollisionGenerationCommitted()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003005u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell,
|
|
destination);
|
|
|
|
RuntimeRemotePlacementExecutionStatus status =
|
|
drive.TryExecuteAcceptedRemotePosition(record, route);
|
|
|
|
Assert.Equal(RuntimeRemotePlacementExecutionStatus.Committed, status);
|
|
// Runtime's world-frame resolution shifts an authored landblock-local
|
|
// position into Runtime's continuous world frame
|
|
// (resolveWorldOffsetFromRuntimeFrame: true) — DestinationLandblock
|
|
// sits +192m on X from SourceLandblock per CommitLandblockCollision.
|
|
Assert.Equal(destination + new Vector3(192f, 0f, 0f), body.Position);
|
|
Assert.Equal(0, drive.PendingCount);
|
|
// No host subscription is wired in this bare fixture — drain the
|
|
// Place receipt exactly like production's placement-projection
|
|
// subscription would, so the operation itself (not this drive's own
|
|
// ledger) also converges to zero.
|
|
DrainPlacementFifo(lifetime);
|
|
Assert.Equal(
|
|
0, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// B4 review fix: <c>SubmitAndResolve</c>'s Committed branch returns and
|
|
/// retains nothing in <c>_pending</c>, but when nothing synchronously
|
|
/// consumes-and-acknowledges the Place receipt (this bare fixture has no
|
|
/// host subscription wired — production's declined-sink retry scenario
|
|
/// is the identical shape), the operation stays live in Core's
|
|
/// <c>_operations</c> map until something later calls
|
|
/// <c>AcknowledgeProjection</c>. Before this fix
|
|
/// <c>RemotePlacementDrivePendingCount</c> was blind to that live
|
|
/// operation the instant <c>Committed</c> was returned. This proves it is
|
|
/// now visible, and that it still converges to zero once the receipt is
|
|
/// actually acknowledged.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Committed_UnacknowledgedOperationStaysVisibleInTheLedgerUntilAcknowledged()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003010u);
|
|
AttachBody(lifetime, record, SourceCell);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell,
|
|
destination);
|
|
|
|
RuntimeRemotePlacementExecutionStatus status =
|
|
drive.TryExecuteAcceptedRemotePosition(record, route);
|
|
|
|
Assert.Equal(RuntimeRemotePlacementExecutionStatus.Committed, status);
|
|
// No host subscription is wired in this bare fixture, so the Place
|
|
// receipt is still genuinely unacknowledged — the ledger must SEE it.
|
|
Assert.Equal(
|
|
1, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
Assert.Equal(
|
|
1,
|
|
lifetime.CaptureOwnership().RemotePlacementDrivePendingCount);
|
|
|
|
DrainPlacementFifo(lifetime);
|
|
|
|
Assert.Equal(
|
|
0, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
[Fact]
|
|
public void Contention_WhenTheEntityAlreadyOwnsAnActiveOperation()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003006u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
Vector3 positionBefore = body.Position;
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
// An external placement authority (portal/teleport/another route)
|
|
// already owns this entity's SetPosition operation.
|
|
RuntimeEntityPlacementToken displaced = lifetime.Physics.SetPosition
|
|
.TryBeginExclusiveAuthoredPlacement(
|
|
record,
|
|
record.PositionAuthorityVersion,
|
|
RuntimeSetPositionOperationKind.RemoteAuthoritative);
|
|
Assert.True(displaced.IsValid);
|
|
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Contention,
|
|
drive.TryExecuteAcceptedRemotePosition(record, route));
|
|
Assert.Equal(positionBefore, body.Position);
|
|
Assert.Equal(0, drive.PendingCount);
|
|
|
|
RuntimePlacementCancellationReceipt cancellation = lifetime.Physics
|
|
.SetPosition.ForgetExactPlacement(displaced);
|
|
if (cancellation.IsValid)
|
|
lifetime.Physics.SetPosition.PublishCancellation(cancellation);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Per-entity independence: two remotes interleaved must not share any
|
|
/// state — one entity's Contention must never block the other's Begin,
|
|
/// and each converges to zero independently.
|
|
/// </summary>
|
|
[Fact]
|
|
public void PerEntityIndependence_TwoRemotesInterleavedDoNotBlockEachOther()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord first = CreateRemoteRecord(lifetime, 0x70003007u);
|
|
RuntimeEntityRecord second = CreateRemoteRecord(lifetime, 0x70003008u);
|
|
PhysicsBody firstBody = AttachBody(lifetime, first, SourceCell);
|
|
PhysicsBody secondBody = AttachBody(lifetime, second, SourceCell);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
// First entity's own operation is still outstanding (an external
|
|
// authority holds it) when the second entity's Position arrives.
|
|
RuntimeEntityPlacementToken firstDisplaced = lifetime.Physics.SetPosition
|
|
.TryBeginExclusiveAuthoredPlacement(
|
|
first,
|
|
first.PositionAuthorityVersion,
|
|
RuntimeSetPositionOperationKind.RemoteAuthoritative);
|
|
Assert.True(firstDisplaced.IsValid);
|
|
|
|
RuntimeAuthoritativePositionRoute firstRoute = MakeRoute(
|
|
first, RuntimeAuthoritativePositionDisposition.SetPosition, DestinationCell);
|
|
RuntimeAuthoritativePositionRoute secondRoute = MakeRoute(
|
|
second, RuntimeAuthoritativePositionDisposition.SetPosition, DestinationCell,
|
|
new Vector3(20f, 22f, SpawnHeight));
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Contention,
|
|
drive.TryExecuteAcceptedRemotePosition(first, firstRoute));
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Committed,
|
|
drive.TryExecuteAcceptedRemotePosition(second, secondRoute));
|
|
|
|
Assert.Equal(
|
|
new Vector3(20f, 22f, SpawnHeight) + new Vector3(192f, 0f, 0f),
|
|
secondBody.Position);
|
|
Assert.Equal(0, drive.PendingCount);
|
|
|
|
RuntimePlacementCancellationReceipt cancellation = lifetime.Physics
|
|
.SetPosition.ForgetExactPlacement(firstDisplaced);
|
|
if (cancellation.IsValid)
|
|
lifetime.Physics.SetPosition.PublishCancellation(cancellation);
|
|
DrainPlacementFifo(lifetime);
|
|
_ = firstBody;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Per-entity independence, the shape route 2's single <c>_pending</c>
|
|
/// slot cannot express: TWO entities each hold their OWN outstanding
|
|
/// preparation retry at the same time. Route 2's
|
|
/// <c>RetainPending</c> throws if a second live entry would displace the
|
|
/// first; this per-key map must track both simultaneously and let each
|
|
/// resolve (or die) independently.
|
|
/// </summary>
|
|
[Fact]
|
|
public void PerEntityIndependence_TwoConcurrentPreparationRetriesDoNotCollide()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord first = CreateRemoteRecord(
|
|
lifetime, 0x7000300Du, setupTableId: 0x02000001u);
|
|
RuntimeEntityRecord second = CreateRemoteRecord(
|
|
lifetime, 0x7000300Eu, setupTableId: 0x02000001u);
|
|
AttachBody(lifetime, first, SourceCell);
|
|
AttachBody(lifetime, second, SourceCell);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
RuntimeAuthoritativePositionRoute firstRoute = MakeRoute(
|
|
first, RuntimeAuthoritativePositionDisposition.SetPosition, DestinationCell);
|
|
RuntimeAuthoritativePositionRoute secondRoute = MakeRoute(
|
|
second, RuntimeAuthoritativePositionDisposition.SetPosition, DestinationCell);
|
|
|
|
// Both entities' Setup assets are unresolved (Missing) — both retry.
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Contention,
|
|
drive.TryExecuteAcceptedRemotePosition(first, firstRoute));
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Contention,
|
|
drive.TryExecuteAcceptedRemotePosition(second, secondRoute));
|
|
Assert.Equal(2, drive.PendingCount);
|
|
|
|
// The FIRST entity's retry dies (an unrelated accepted Position for
|
|
// IT ALONE forgets it); the second must remain completely
|
|
// unaffected — a shared/overwriting single slot could not represent
|
|
// this.
|
|
RuntimePlacementCancellationReceipt cancellation =
|
|
lifetime.Physics.SetPosition.Forget(first);
|
|
if (cancellation.IsValid)
|
|
lifetime.Physics.SetPosition.PublishCancellation(cancellation);
|
|
|
|
drive.Advance();
|
|
|
|
Assert.Equal(1, drive.PendingCount);
|
|
// The second entity's own operation is still tracked — the first's
|
|
// cancellation removed exactly one operation, not both.
|
|
Assert.Equal(
|
|
1, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
|
|
// Cleanup: retire the second entity's still-live retry too.
|
|
RuntimePlacementCancellationReceipt secondCancellation =
|
|
lifetime.Physics.SetPosition.Forget(second);
|
|
if (secondCancellation.IsValid)
|
|
{
|
|
lifetime.Physics.SetPosition
|
|
.PublishCancellation(secondCancellation);
|
|
}
|
|
drive.Advance();
|
|
Assert.Equal(0, drive.PendingCount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// B3 review fix: a retained preparation retry must re-check the SAME
|
|
/// service-window guard the entry point uses before <c>Advance()</c>
|
|
/// resubmits it — a destination that retired its collision publication in
|
|
/// the frames since the retry was retained must be dropped, not
|
|
/// resubmitted. Discriminates from the OLD unconditional resubmit:
|
|
/// resubmitting against the SAME always-<c>Missing</c> collision source
|
|
/// returns the SAME retryable status, so without the re-check the entry
|
|
/// would simply be re-retained (<c>PendingCount</c> stays 1) even though
|
|
/// the window already forbids the destination — never converging.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Advance_DropsRetainedRetryWhenTheServiceWindowNoLongerCoversItsDestination()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
RuntimeEntityRecord record = CreateRemoteRecord(
|
|
lifetime, 0x70003011u, setupTableId: 0x02000001u);
|
|
AttachBody(lifetime, record, SourceCell);
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record, RuntimeAuthoritativePositionDisposition.SetPosition, DestinationCell);
|
|
|
|
// The Setup asset is unresolved (Missing) — the first attempt
|
|
// retries rather than commits, retaining the entry.
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Contention,
|
|
drive.TryExecuteAcceptedRemotePosition(record, route));
|
|
Assert.Equal(1, drive.PendingCount);
|
|
Assert.Equal(
|
|
1, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
|
|
// The remote wandered back out of range between this retry being
|
|
// retained and the next host cadence pump.
|
|
window.Forbid(DestinationLandblock);
|
|
|
|
drive.Advance();
|
|
|
|
Assert.Equal(0, drive.PendingCount);
|
|
Assert.Equal(
|
|
0, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Currency: a preparation-retry entry left tracking a token some OTHER
|
|
/// caller already Forgot must not leak once this entity's NEXT packet
|
|
/// takes a path that itself never touches <c>_pending</c> (here, a
|
|
/// service-window refusal) — proving the self-heal is doing real work,
|
|
/// not merely being masked by the retry branch's own dictionary
|
|
/// overwrite (which would hide the leak if this test only ever re-hit
|
|
/// the SAME retryable status).
|
|
/// </summary>
|
|
[Fact]
|
|
public void StalePreparationRetry_SelfHealsRatherThanLeakingWhenTheNextPacketNeverTouchesPending()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
RuntimeEntityRecord record = CreateRemoteRecord(
|
|
lifetime, 0x70003009u, setupTableId: 0x02000001u);
|
|
AttachBody(lifetime, record, SourceCell);
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
// A Setup asset that never resolves (Missing) — the first attempt
|
|
// through this collision source retries rather than commits.
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record, RuntimeAuthoritativePositionDisposition.SetPosition, DestinationCell);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Contention,
|
|
drive.TryExecuteAcceptedRemotePosition(record, route));
|
|
Assert.Equal(1, drive.PendingCount);
|
|
|
|
// An unrelated accepted Position for this SAME entity Forgets the
|
|
// retained retry before it ever resolves — the mundane 5-10 Hz case.
|
|
RuntimePlacementCancellationReceipt cancellation =
|
|
lifetime.Physics.SetPosition.Forget(record);
|
|
if (cancellation.IsValid)
|
|
lifetime.Physics.SetPosition.PublishCancellation(cancellation);
|
|
|
|
// The destination is no longer serviceable by the NEXT packet (the
|
|
// remote wandered back out of range) — the Refused branch never
|
|
// assigns or clears _pending itself, so only the entry-point
|
|
// self-heal can retire the dead entry left behind above.
|
|
window.Forbid(DestinationLandblock);
|
|
RuntimeAuthoritativePositionRoute refusedRoute = MakeRoute(
|
|
record, RuntimeAuthoritativePositionDisposition.SetPosition, DestinationCell);
|
|
|
|
RuntimeRemotePlacementExecutionStatus status =
|
|
drive.TryExecuteAcceptedRemotePosition(record, refusedRoute);
|
|
Assert.Equal(RuntimeRemotePlacementExecutionStatus.Refused, status);
|
|
Assert.Equal(0, drive.PendingCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// C2-1 review fix (delta round): the retained <c>_pending</c> entry
|
|
/// holds a LIVE Core operation (already begun via
|
|
/// <c>TryBeginExclusiveAuthoredPlacement</c>, sitting at
|
|
/// <c>AwaitingPreparation</c>) — clearing this controller's own map on
|
|
/// detach is not enough; the operation itself must be cancelled or it
|
|
/// pins its landblock prefix forever (docs/ISSUES.md #310). Asserting
|
|
/// <c>SetPositionOperationCount == 0</c> in addition to the ledger going
|
|
/// to zero is what actually proves the Core operation died, not just
|
|
/// that this controller stopped watching it — sibling tests in this file
|
|
/// (<c>Committed_...</c>, <c>ParkCollisionResidents_...</c>) already
|
|
/// check this dimension; this one previously did not, and would have
|
|
/// passed even with the old clear-only <c>DetachRoute</c>.
|
|
/// </summary>
|
|
[Fact]
|
|
public void LedgerConverges_AfterDetachRouteClearsTrackedEntries()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
RuntimeEntityRecord record = CreateRemoteRecord(
|
|
lifetime, 0x7000300Au, setupTableId: 0x02000001u);
|
|
AttachBody(lifetime, record, SourceCell);
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
var route = new object();
|
|
drive.AttachRoute(route);
|
|
|
|
RuntimeAuthoritativePositionRoute positionRoute = MakeRoute(
|
|
record, RuntimeAuthoritativePositionDisposition.SetPosition, DestinationCell);
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Contention,
|
|
drive.TryExecuteAcceptedRemotePosition(record, positionRoute));
|
|
Assert.Equal(1, drive.PendingCount);
|
|
Assert.Equal(
|
|
1, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
|
|
drive.DetachRoute(route);
|
|
|
|
Assert.Equal(0, drive.PendingCount);
|
|
Assert.Equal(0, lifetime.CaptureOwnership().RemotePlacementDrivePendingCount);
|
|
Assert.Equal(
|
|
0, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// C2-1 review fix (delta round): the OTHER map — <c>_awaitingAcknowledgement</c>
|
|
/// — holds an equally live Core operation (a published, unacknowledged
|
|
/// <c>Place</c> sitting at <c>AwaitingCommitAcknowledgement</c>), and the
|
|
/// prior <c>DetachRoute</c> left it live the same way. Mirrors the
|
|
/// <c>_pending</c> case above for the SECOND map DetachRoute must cancel.
|
|
/// </summary>
|
|
[Fact]
|
|
public void LedgerConverges_AfterDetachRouteCancelsAnUnacknowledgedCommit()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003012u);
|
|
AttachBody(lifetime, record, SourceCell);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
var route = new object();
|
|
drive.AttachRoute(route);
|
|
|
|
RuntimeAuthoritativePositionRoute positionRoute = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell,
|
|
new Vector3(12f, 14f, SpawnHeight));
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Committed,
|
|
drive.TryExecuteAcceptedRemotePosition(record, positionRoute));
|
|
// No host subscription is wired in this bare fixture, so the Place
|
|
// receipt is still genuinely unacknowledged — the SAME shape the
|
|
// production declined-sink FIFO retry class leaves behind.
|
|
Assert.Equal(
|
|
1, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
Assert.Equal(
|
|
1, lifetime.CaptureOwnership().RemotePlacementDrivePendingCount);
|
|
|
|
drive.DetachRoute(route);
|
|
|
|
Assert.Equal(
|
|
0, lifetime.CaptureOwnership().RemotePlacementDrivePendingCount);
|
|
Assert.Equal(
|
|
0, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// C2-1 review fix (delta round), disposal safety:
|
|
/// <c>CountLiveAwaitingAcknowledgement</c> calls
|
|
/// <c>RuntimeSetPositionState.IsPlacementCurrent</c>, whose first
|
|
/// statement is <c>EnsureNotDisposed</c> — it THROWS once disposed. A
|
|
/// post-<c>Dispose()</c> <c>CaptureOwnership()</c> read is the designed
|
|
/// contract (<c>GameWindowLifetime.DisposeGameRuntime</c>:
|
|
/// <c>runtime.Dispose(); runtime.CaptureOwnership();</c>), so this
|
|
/// dimension must survive it too. Deliberately leaves the entry
|
|
/// UNDRAINED at dispose time — a genuine leak, exactly what this ledger
|
|
/// exists to report — to prove the disposed branch reports it rather
|
|
/// than hiding it behind a thrown exception.
|
|
/// </summary>
|
|
[Fact]
|
|
public void CountLiveAwaitingAcknowledgement_SurvivesReadAfterDisposeWithoutThrowing()
|
|
{
|
|
var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003013u);
|
|
AttachBody(lifetime, record, SourceCell);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
RuntimeAuthoritativePositionRoute positionRoute = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell,
|
|
new Vector3(12f, 14f, SpawnHeight));
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Committed,
|
|
drive.TryExecuteAcceptedRemotePosition(record, positionRoute));
|
|
|
|
lifetime.Dispose();
|
|
int pendingAfterDispose = -1;
|
|
Exception? thrown = Record.Exception(() =>
|
|
pendingAfterDispose =
|
|
lifetime.CaptureOwnership().RemotePlacementDrivePendingCount);
|
|
|
|
Assert.Null(thrown);
|
|
Assert.Equal(1, pendingAfterDispose);
|
|
}
|
|
|
|
[Fact]
|
|
public void AttachRoute_ThrowsForADifferentRouteWhileTheFirstIsStillLive()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
var window = new FakeServiceWindow();
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
var firstRoute = new object();
|
|
var secondRoute = new object();
|
|
drive.AttachRoute(firstRoute);
|
|
|
|
Assert.Throws<InvalidOperationException>(
|
|
() => drive.AttachRoute(secondRoute));
|
|
|
|
drive.DetachRoute(firstRoute);
|
|
drive.AttachRoute(secondRoute);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gate item: <c>ParkCollisionResidents</c> throws on overlap for every
|
|
/// spatial root in a retiring prefix that holds an active operation
|
|
/// (<c>RuntimeSetPositionState.ParkCollisionResidents</c> — cited by
|
|
/// symbol, not by line: the delta review found line citations in this
|
|
/// slice going stale within a single review round). This is the concrete
|
|
/// proof it stays unreachable under this route's design — both after a
|
|
/// refusal (no operation ever opened) and after a normal commit
|
|
/// (operation retired to zero), the SAME prefix can be "retired" without
|
|
/// throwing.
|
|
/// </summary>
|
|
[Fact]
|
|
public void ParkCollisionResidents_StaysUnreachable_AfterRefusalAndAfterCommit()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord refusedEntity = CreateRemoteRecord(lifetime, 0x7000300Bu);
|
|
AttachBody(lifetime, refusedEntity, DestinationCell);
|
|
RuntimeEntityRecord committedEntity = CreateRemoteRecord(lifetime, 0x7000300Cu);
|
|
AttachBody(lifetime, committedEntity, SourceCell);
|
|
|
|
var refusingWindow = new FakeServiceWindow();
|
|
RuntimeRemotePlacementDriveController refusingDrive =
|
|
CreateDrive(lifetime, refusingWindow);
|
|
RuntimeAuthoritativePositionRoute refusedRoute = MakeRoute(
|
|
refusedEntity,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell);
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Refused,
|
|
refusingDrive.TryExecuteAcceptedRemotePosition(refusedEntity, refusedRoute));
|
|
|
|
var allowingWindow = new FakeServiceWindow();
|
|
allowingWindow.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController committingDrive =
|
|
CreateDrive(lifetime, allowingWindow);
|
|
RuntimeAuthoritativePositionRoute committedRoute = MakeRoute(
|
|
committedEntity,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell,
|
|
new Vector3(30f, 30f, SpawnHeight));
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Committed,
|
|
committingDrive.TryExecuteAcceptedRemotePosition(
|
|
committedEntity, committedRoute));
|
|
// No host subscription is wired in this bare fixture — drain the
|
|
// Place receipt exactly like production's placement-projection
|
|
// subscription would, converging the operation to zero the same way
|
|
// a live host's synchronous consumption does.
|
|
DrainPlacementFifo(lifetime);
|
|
|
|
// Both entities now sit resident in the destination prefix with NO
|
|
// active operation. Retiring that prefix must not throw.
|
|
Exception? thrown = Record.Exception(() =>
|
|
lifetime.Physics.SetPosition.ParkCollisionResidents(
|
|
DestinationLandblock, includeOutdoorCells: true));
|
|
Assert.Null(thrown);
|
|
}
|
|
|
|
// ── C4 route 4b-2: the far-snap arm ──────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// The queue clear does not depend on the placement having run: refuse
|
|
/// the placement and the queue is still empty afterwards.
|
|
///
|
|
/// <para>
|
|
/// R7 review fix — <b>what this does and does not prove.</b> Its earlier
|
|
/// name and doc claimed proof of retail's ORDER (<c>StopInterpolating</c>
|
|
/// @0x005163CB strictly before <c>SetPositionSimple</c> @0x005163D9).
|
|
/// It does not have that, and neither can any acdream test: the
|
|
/// interpolation queue is not an input to the canonical placement (the
|
|
/// destination comes from the merged snapshot), and the placement does
|
|
/// not read or write the queue, so swapping the two statements leaves
|
|
/// every observable identical. The order is preserved in
|
|
/// <c>ApplyAcceptedRemoteFarSnap</c> because retail has it, and is
|
|
/// asserted only by reading. What IS pinned here is the strictly weaker
|
|
/// and still load-bearing property that ruled out the natural
|
|
/// mis-implementation "clear the queue once the placement succeeds":
|
|
/// under that shape a refusal would leave a stale near waypoint behind to
|
|
/// drag the body back on the next catch-up.
|
|
/// </para>
|
|
/// </summary>
|
|
[Fact]
|
|
public void FarSnap_ClearsTheInterpolationQueue_IndependentlyOfThePlacementOutcome()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
// Publishes the world frame as well, so the refusal's store_position
|
|
// fallback below has a coordinate system to write into — a live
|
|
// session always has one before any remote can classify far.
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003020u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
Vector3 positionBefore = body.Position;
|
|
remote.Interp.Enqueue(
|
|
new Vector3(40f, 40f, SpawnHeight),
|
|
Quaternion.Identity,
|
|
isMovingTo: false,
|
|
currentBodyPosition: body.Position,
|
|
currentBodyOrientation: body.Orientation);
|
|
Assert.True(remote.Interp.IsActive);
|
|
|
|
// The service window allows nothing, so the placement refuses.
|
|
RuntimeRemotePlacementDriveController drive =
|
|
CreateDrive(lifetime, new FakeServiceWindow());
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
DestinationCell,
|
|
destination,
|
|
stopInterpolating: true);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Refused,
|
|
drive.ApplyAcceptedRemoteFarSnap(record, remote, route));
|
|
|
|
Assert.False(remote.Interp.IsActive);
|
|
// Refusal is retail's no-resolvable-cell state, and that branch still
|
|
// commits the destination — store_position @0x00515CE2. Leaving the
|
|
// body at positionBefore with an emptied queue is the freeze both
|
|
// reviews found.
|
|
Assert.Equal(destination + new Vector3(192f, 0f, 0f), body.Position);
|
|
Assert.NotEqual(positionBefore, body.Position);
|
|
Assert.True(body.InWorld);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The second non-commit outcome: an entity whose SetPosition operation is
|
|
/// already owned by another placement authority contends, so no operation
|
|
/// of this route's opens at all — and the body still advances.
|
|
/// </summary>
|
|
[Fact]
|
|
public void FarSnap_ContendedByAnotherAuthority_StillStoresTheDestinationPose()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003024u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
Vector3 positionBefore = body.Position;
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
RuntimeEntityPlacementToken displaced = lifetime.Physics.SetPosition
|
|
.TryBeginExclusiveAuthoredPlacement(
|
|
record,
|
|
record.PositionAuthorityVersion,
|
|
RuntimeSetPositionOperationKind.RemoteAuthoritative);
|
|
Assert.True(displaced.IsValid);
|
|
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
DestinationCell,
|
|
destination,
|
|
stopInterpolating: true);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Contention,
|
|
drive.ApplyAcceptedRemoteFarSnap(record, remote, route));
|
|
Assert.Equal(destination + new Vector3(192f, 0f, 0f), body.Position);
|
|
Assert.NotEqual(positionBefore, body.Position);
|
|
Assert.Equal(0, drive.PendingCount);
|
|
|
|
RuntimePlacementCancellationReceipt cancellation = lifetime.Physics
|
|
.SetPosition.ForgetExactPlacement(displaced);
|
|
if (cancellation.IsValid)
|
|
lifetime.Physics.SetPosition.PublishCancellation(cancellation);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The retryable-preparation outcome (an unresolved Setup collision) is
|
|
/// also reported as <c>Contention</c>, and it RETAINS an operation for the
|
|
/// cadence pump — but the body must not wait for that pump. Retail's
|
|
/// analogue never waits: its Setup is always resident, and the object is
|
|
/// committed to the destination the moment the packet is handled.
|
|
/// </summary>
|
|
[Fact]
|
|
public void FarSnap_RetryablePreparation_StoresThePoseAndStillRetainsTheRetry()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(
|
|
lifetime, 0x70003025u, setupTableId: 0x02000001u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
Vector3 positionBefore = body.Position;
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
DestinationCell,
|
|
destination,
|
|
stopInterpolating: true);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Contention,
|
|
drive.ApplyAcceptedRemoteFarSnap(record, remote, route));
|
|
Assert.Equal(1, drive.PendingCount);
|
|
Assert.Equal(destination + new Vector3(192f, 0f, 0f), body.Position);
|
|
Assert.NotEqual(positionBefore, body.Position);
|
|
|
|
// The retained retry converges on its own terms; the pose write above
|
|
// is independent of it.
|
|
drive.Advance();
|
|
Assert.Equal(1, drive.PendingCount);
|
|
RuntimePlacementCancellationReceipt cancellation =
|
|
lifetime.Physics.SetPosition.Forget(record);
|
|
if (cancellation.IsValid)
|
|
lifetime.Physics.SetPosition.PublishCancellation(cancellation);
|
|
drive.Advance();
|
|
Assert.Equal(0, drive.PendingCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// R8 review fix, part 1 — the RESTORABLE <c>DeferredCell</c> park
|
|
/// (<c>SubmitAndResolve</c>'s <c>RuntimeSetPositionStatus.DeferredCell</c>
|
|
/// case), which had ZERO coverage in 4b-1 or 4b-2 while the far snap is
|
|
/// the first production path that can provoke one. Provoked by letting
|
|
/// the service window say "yes" for a destination whose collision
|
|
/// generation was never committed, so the engine itself defers.
|
|
///
|
|
/// <para>
|
|
/// The park must be cancelled AND rolled back — <c>CancelToken</c> passes
|
|
/// <c>restoreCancelledPark: true</c> and this park opts in with
|
|
/// <c>restorableOnCancel: true</c>, so <c>RestoreParkWithdrawal</c>
|
|
/// returns <c>InWorld</c>, the object clock, and canonical residency
|
|
/// (AP-136). Without the rollback the entity would be left invisible and
|
|
/// intangible.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Delta review: the pose assertion below is the PARK's own
|
|
/// <c>store_position</c> (<c>ParkDeferred</c> snaps the body to the
|
|
/// deferred result before withdrawing, and the restore deliberately leaves
|
|
/// that pose alone), NOT the far arm's fallback — a <c>Deferred</c> status
|
|
/// does not store, precisely so the post-sweep park's collision-settled
|
|
/// pose cannot be overwritten with the raw destination.
|
|
/// </para>
|
|
/// </summary>
|
|
[Fact]
|
|
public void FarSnap_DeferredCellPark_IsCancelledAndRolledBackAtTheDestination()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
// The world frame only — the DESTINATION's collision generation is
|
|
// deliberately never committed, which is what makes the engine defer.
|
|
lifetime.Physics.ObserveLocalWorldFrame(
|
|
SourceCell, teleportAdvanced: false);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003026u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
DestinationCell,
|
|
destination,
|
|
stopInterpolating: true);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Deferred,
|
|
drive.ApplyAcceptedRemoteFarSnap(record, remote, route));
|
|
|
|
// The park was rolled back, not left standing.
|
|
Assert.True(body.InWorld);
|
|
Assert.True(record.ObjectClock.IsActive);
|
|
Assert.NotEqual(0u, record.FullCellId);
|
|
// …and the body still tracked the server.
|
|
Assert.Equal(destination + new Vector3(192f, 0f, 0f), body.Position);
|
|
Assert.Equal(
|
|
0, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// R8 review fix, part 2 — the NON-restorable park, and why the pre-flight
|
|
/// now refuses ahead of it.
|
|
///
|
|
/// <para>
|
|
/// A live in-place collision-prefix quiescence is the residual the class
|
|
/// doc names: the tier/residency service window still reads "published",
|
|
/// but <c>RuntimeSetPositionState.TryGetBlockingQuiescence</c> defers.
|
|
/// When it is the DESTINATION'S OWN prefix that is quiescing — this test —
|
|
/// the park stays non-restorable, because AP-136's stated reason applies
|
|
/// exactly: <c>RestoreParkWithdrawal</c> would re-admit a spatial root
|
|
/// into the prefix that is trying to quiesce, and <c>ParkDeferred</c>
|
|
/// declines the rollback whenever the post-snap restore cell is itself
|
|
/// quiescing. <c>CanAttemptDestination</c> therefore reads
|
|
/// Core's own <c>IsCollisionPrefixQuiescing</c> as part of the SAME
|
|
/// pre-flight and refuses instead — and the store_position fallback keeps
|
|
/// the remote tracking the server while the prefix mutates.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Delta review MAJOR B: this is the ONE quiescence shape the pre-flight
|
|
/// can see. The source-landblock and swept-neighbour shapes reach Core
|
|
/// anyway and are covered by the two tests below, which is why the
|
|
/// pre-flight is now an optimisation rather than the correctness
|
|
/// mechanism.
|
|
/// </para>
|
|
/// </summary>
|
|
[Fact]
|
|
public void FarSnap_QuiescingDestinationPrefix_RefusesWithoutOpeningANonRestorablePark()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003028u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
lifetime.Physics.SetPosition.BeginCollisionPrefixQuiescence(
|
|
DestinationLandblock,
|
|
collisionGeneration: 1UL,
|
|
includeOutdoorCells: true);
|
|
// The window is unchanged and still says yes — this is precisely the
|
|
// case it cannot see.
|
|
Assert.True(window.IsWithinServiceWindow(DestinationCell));
|
|
Assert.True(
|
|
lifetime.Physics.SetPosition.IsCollisionPrefixQuiescing(
|
|
DestinationLandblock));
|
|
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
DestinationCell,
|
|
destination,
|
|
stopInterpolating: true);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Refused,
|
|
drive.ApplyAcceptedRemoteFarSnap(record, remote, route));
|
|
|
|
// No park was ever opened, so there is nothing to fail to restore.
|
|
Assert.True(body.InWorld);
|
|
Assert.True(record.ObjectClock.IsActive);
|
|
Assert.Equal(destination + new Vector3(192f, 0f, 0f), body.Position);
|
|
Assert.Equal(
|
|
0, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delta review MAJOR B, route 1 of 2 — the SOURCE landblock quiesces.
|
|
///
|
|
/// <para>
|
|
/// <c>CanAttemptDestination</c> tests the DESTINATION prefix and passes,
|
|
/// but Core's <c>PlacementTouchesPrefix</c> also matches the request's
|
|
/// <c>CurrentCellId</c>, populated from <c>record.FullCellId</c> whenever
|
|
/// the body is in world — so a far snap OUT of a quiescing landblock
|
|
/// parks at the pre-sweep site no pre-flight can see.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// <b>Round 3 — reachability caveat, measured.</b> The earlier text called
|
|
/// this "the likelier of the two shapes". It is not reached by the
|
|
/// graphical remote caller at all: that path commits the accepted WIRE
|
|
/// cell to <c>record.FullCellId</c> in its shared prologue
|
|
/// (<c>LiveEntityRuntime.RebucketLiveEntity</c> →
|
|
/// <c>RuntimeEntityObjectLifetime.CommitRebucket</c>) before it routes, so
|
|
/// <c>CurrentCellId</c> names the DESTINATION by submit time and the
|
|
/// source prefix never matches. This fixture reaches the shape because it
|
|
/// drives the seam directly and never rebuckets. The test still earns its
|
|
/// place — it is the only coverage of the pre-sweep quiescence park's
|
|
/// restore, which a future caller that submits without rebucketing (or a
|
|
/// prologue reordering) would reach — but it must not be cited as the
|
|
/// production-likely case. The reachable production shapes are the
|
|
/// quiescing DESTINATION (refused by the pre-flight here, parked
|
|
/// non-restorably on route 2) and the quiescing SWEPT NEIGHBOUR (the test
|
|
/// below).
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// With the park non-restorable (the shipped state before this fix) the
|
|
/// remote is left <c>InWorld = false</c>, clock suspended,
|
|
/// <c>FullCellId = 0</c>, with the only operation able to wake it
|
|
/// destroyed by <c>CancelToken</c>. <c>ParkDeferred</c> now makes it
|
|
/// restorable because the cell it snapped to — the DESTINATION, which is
|
|
/// the cell <c>RestoreParkWithdrawal</c> restores residency into — is not
|
|
/// quiescing, so nothing is re-admitted into the retiring source.
|
|
/// </para>
|
|
/// </summary>
|
|
[Fact]
|
|
public void FarSnap_QuiescingSourceLandblock_RestoresTheRemoteIntoTheWorld()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003030u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
lifetime.Physics.SetPosition.BeginCollisionPrefixQuiescence(
|
|
SourceLandblock,
|
|
collisionGeneration: 1UL,
|
|
includeOutdoorCells: true);
|
|
// Both halves of the pre-flight pass — it reads only the destination,
|
|
// and cannot see the source at all.
|
|
Assert.True(window.IsWithinServiceWindow(DestinationCell));
|
|
Assert.False(
|
|
lifetime.Physics.SetPosition.IsCollisionPrefixQuiescing(
|
|
DestinationLandblock));
|
|
Assert.True(
|
|
lifetime.Physics.SetPosition.IsCollisionPrefixQuiescing(
|
|
SourceLandblock));
|
|
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
DestinationCell,
|
|
destination,
|
|
stopInterpolating: true);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Deferred,
|
|
drive.ApplyAcceptedRemoteFarSnap(record, remote, route));
|
|
|
|
Assert.True(body.InWorld);
|
|
Assert.True(record.ObjectClock.IsActive);
|
|
Assert.NotEqual(0u, record.FullCellId);
|
|
Assert.Equal(destination + new Vector3(192f, 0f, 0f), body.Position);
|
|
Assert.Equal(
|
|
0, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delta review MAJOR B route 2 of 2, and MAJOR C — a merely-SWEPT
|
|
/// NEIGHBOUR landblock quiesces, on a placement that would otherwise have
|
|
/// COMMITTED.
|
|
///
|
|
/// <para>
|
|
/// The destination sits one sphere radius inside the seam between
|
|
/// <c>DestinationLandblock</c> and its +X neighbour, so
|
|
/// <c>CellTransit.AddAllOutsideCells</c> adds the neighbour's cells to the
|
|
/// sweep footprint — <c>AddOutsideCell</c> re-derives the block id from
|
|
/// the global lcoord and states outright that there is no same-block
|
|
/// filter. Core's <c>ResultTouchesPrefix</c> scans every
|
|
/// <c>QueriedCellIds</c> entry, so the POST-sweep quiescence check fires
|
|
/// on a footprint that did not exist until the sweep ran. No pre-flight
|
|
/// can reproduce that, which is the structural half of MAJOR B.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// MAJOR C: that check is <c>result.IsSuccessful &&
|
|
/// TryGetBlockingQuiescence(result, …)</c> and sits AHEAD of the
|
|
/// restorable <c>result.IsDeferred</c> park, so a healthy, resident,
|
|
/// about-to-commit far snap near a seam was rewritten to
|
|
/// <c>DeferredCell</c> and parked non-restorably. The
|
|
/// <c>Assert.NotEqual(0u, record.FullCellId)</c> below is what fails
|
|
/// without the fix.
|
|
/// </para>
|
|
/// </summary>
|
|
[Fact]
|
|
public void FarSnap_QuiescingSweptNeighbour_RestoresTheRemoteIntoTheWorld()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
CommitLandblockCollision(
|
|
lifetime, NeighbourLandblock, worldOffsetX: 384f);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003031u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
lifetime.Physics.SetPosition.BeginCollisionPrefixQuiescence(
|
|
NeighbourLandblock,
|
|
collisionGeneration: 1UL,
|
|
includeOutdoorCells: true);
|
|
// Neither half of the pre-flight can see the neighbour.
|
|
Assert.True(window.IsWithinServiceWindow(DestinationSeamCell));
|
|
Assert.False(
|
|
lifetime.Physics.SetPosition.IsCollisionPrefixQuiescing(
|
|
DestinationLandblock));
|
|
|
|
// Block-local X = 191.95 m: inside cell (7, 0) of the destination
|
|
// landblock, 0.05 m from the 192 m seam — closer than the 0.1 m dummy
|
|
// sphere radius, which is the `pointX > CellLength - radius` test
|
|
// AddAllOutsideCells applies before adding lx + 1.
|
|
// Z is deliberately 1 m BELOW the flat terrain so the placement sweep
|
|
// lifts the sphere back to the surface: the parked result then carries
|
|
// a settled Z that differs from the raw accepted one, which is what
|
|
// makes the "Deferred does not store" arm (delta review N2)
|
|
// discriminating rather than latent.
|
|
var destination = new Vector3(191.95f, 10f, SpawnHeight - 1f);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
DestinationSeamCell,
|
|
destination,
|
|
stopInterpolating: true);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Deferred,
|
|
drive.ApplyAcceptedRemoteFarSnap(record, remote, route));
|
|
|
|
Assert.True(body.InWorld);
|
|
Assert.True(record.ObjectClock.IsActive);
|
|
Assert.NotEqual(0u, record.FullCellId);
|
|
// ParkDeferred already performed retail's store_position, at the
|
|
// SETTLED pose; the far arm must not write the raw destination over it.
|
|
Assert.Equal(SpawnHeight, body.Position.Z);
|
|
Assert.Equal(
|
|
0, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Round-3 correction A5 — why <c>CanAttemptDestination</c> is
|
|
/// load-bearing and must not be deleted as "just an optimisation".
|
|
///
|
|
/// <para>
|
|
/// Two live quiescences at once, SOURCE opened first so its
|
|
/// <c>OperationId</c> is the lower one. Core files the park under the
|
|
/// source, but the cell the rollback would restore into is the
|
|
/// DESTINATION, which is retiring — so <c>ParkDeferred</c> would
|
|
/// (correctly) refuse the rollback and the remote would be left withdrawn
|
|
/// until some later packet commits.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// The pre-flight is what keeps that shape off the remote path entirely:
|
|
/// it reads the destination prefix, refuses BEFORE any park is opened,
|
|
/// and the <c>store_position</c> fallback keeps the remote tracking the
|
|
/// server while both prefixes mutate.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// <b>Round 4 — cross-reference corrected.</b> The earlier text cited a
|
|
/// route-2 sibling named
|
|
/// <c>ConcurrentQuiescences_ParkIsNotRestoredIntoTheQuiescingDestination</c>.
|
|
/// No such test exists, and it cannot: this two-quiescence shape needs
|
|
/// the park to be filed under a prefix that is NOT the restore cell's,
|
|
/// which needs <c>PlacementTouchesPrefix</c>'s <c>CurrentCellId</c> arm to
|
|
/// name a different landblock than the request — and route 2's merge
|
|
/// commits the accepted wire cell to <c>record.FullCellId</c> before the
|
|
/// drive runs, so on that route the two are the same prefix by
|
|
/// construction. (This fixture reaches the shape only because it drives
|
|
/// the seam directly and never rebuckets — the same caveat
|
|
/// <c>FarSnap_QuiescingSourceLandblock_RestoresTheRemoteIntoTheWorld</c>
|
|
/// carries.) The route-2 test that pins the restore decision itself is
|
|
/// <c>RuntimeAcceptedPositionDriveControllerTests
|
|
/// .QuiescingOwnPrefix_SeamCrossingParkIsRestoredAtTheReDerivedNeighbourCell</c>,
|
|
/// which discriminates on the OTHER half of the same predicate: the
|
|
/// post-snap restore cell versus the caller's pre-snap
|
|
/// <c>result.CellId</c>.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// <b>Scope of this test's own evidence (round 4, N2).</b> It is
|
|
/// behaviourally redundant with
|
|
/// <c>FarSnap_QuiescingDestinationPrefix_RefusesWithoutOpeningANonRestorablePark</c>
|
|
/// — both fail identically if the pre-flight's
|
|
/// <c>IsCollisionPrefixQuiescing</c> half is removed — and the
|
|
/// <c>OperationId</c> ordering asserted below never influences an
|
|
/// observed outcome here, because the pre-flight refuses before any
|
|
/// blocking token is selected. It documents the concurrent-quiescence
|
|
/// shape and pins the refusal; it does not prove which token Core would
|
|
/// have chosen.
|
|
/// </para>
|
|
/// </summary>
|
|
[Fact]
|
|
public void FarSnap_ConcurrentQuiescences_RefusesBeforeOpeningAPark()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003037u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
RuntimeCollisionPrefixQuiescenceToken sourceQuiescence =
|
|
lifetime.Physics.SetPosition.BeginCollisionPrefixQuiescence(
|
|
SourceLandblock,
|
|
collisionGeneration: 2UL,
|
|
includeOutdoorCells: true);
|
|
RuntimeCollisionPrefixQuiescenceToken destinationQuiescence =
|
|
lifetime.Physics.SetPosition.BeginCollisionPrefixQuiescence(
|
|
DestinationLandblock,
|
|
collisionGeneration: 2UL,
|
|
includeOutdoorCells: true);
|
|
Assert.True(
|
|
sourceQuiescence.OperationId < destinationQuiescence.OperationId);
|
|
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
DestinationCell,
|
|
destination,
|
|
stopInterpolating: true);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Refused,
|
|
drive.ApplyAcceptedRemoteFarSnap(record, remote, route));
|
|
|
|
// No park was opened, so nothing was ever withdrawn — and nothing was
|
|
// re-admitted into either retiring prefix.
|
|
Assert.True(body.InWorld);
|
|
Assert.True(record.ObjectClock.IsActive);
|
|
Assert.Equal(destination + new Vector3(192f, 0f, 0f), body.Position);
|
|
Assert.Equal(
|
|
0, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delta review MAJOR A, the do-NOT-store side. The engine's own sweep
|
|
/// refuses the destination — retail
|
|
/// <c>CPhysicsObj::SetPositionInternal</c> @0x00515BD0 reaches
|
|
/// <c>CheckPositionInternal == 0</c> @0x00515C85,
|
|
/// <c>handle_all_collisions</c> @0x00515CC2 and
|
|
/// <c>return ((eax_14 - eax_14) & 2) + 2</c> @0x00515CD5 WITHOUT ever
|
|
/// calling <c>store_position</c>. The shipped fix round routed this
|
|
/// through the fallback, which teleported the canonical body into a
|
|
/// destination the engine had just refused.
|
|
/// </summary>
|
|
[Fact]
|
|
public void FarSnap_EngineRefusedTheDestination_LeavesTheBodyWhereItWas()
|
|
{
|
|
PhysicsEngine engine = FlatEngine();
|
|
using var lifetime = new RuntimeEntityObjectLifetime(engine);
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003032u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
Vector3 positionBefore = body.Position;
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
engine.TransitionCellCollisionTestHook =
|
|
static (_, _, _, _) => TransitionState.Collided;
|
|
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
DestinationCell,
|
|
destination,
|
|
stopInterpolating: true);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.RejectedByPlacement,
|
|
drive.ApplyAcceptedRemoteFarSnap(record, remote, route));
|
|
|
|
// Retail leaves the object where it was on 2/3/4.
|
|
Assert.Equal(positionBefore, body.Position);
|
|
Assert.NotEqual(destination + new Vector3(192f, 0f, 0f), body.Position);
|
|
Assert.Equal(
|
|
0, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delta review MAJOR A, the worse sub-case:
|
|
/// <c>SubmitPreparedPlacementCore</c> returns <c>Cancelled</c> AFTER
|
|
/// <c>CommitCanonical</c> succeeded (F1 — "retail lets that physical
|
|
/// commit land regardless"), so the body carries a freshly SETTLED pose
|
|
/// while this caller's own operation is no longer canonical. The shipped
|
|
/// fix round mapped that to the fallback and overwrote the settle with the
|
|
/// raw accepted destination.
|
|
///
|
|
/// <para>
|
|
/// The displacement is provoked the same way
|
|
/// <c>ReentrantGroundEdgePlacementCannotBeCancelledByDisplacedOperation</c>
|
|
/// provokes it — through the ground-edge <c>HitGround</c> callback
|
|
/// <c>CommitSetPositionContactTransition</c> invokes mid-commit — and the
|
|
/// callback also merges a NEWER accepted Position into the snapshot,
|
|
/// which is the production shape (packet N+1 arriving while packet N is
|
|
/// still committing). That merge is what makes the assertion discriminate:
|
|
/// if the fallback fires it reads the snapshot at store time and lands the
|
|
/// body on the newer destination instead of the settled pose.
|
|
/// </para>
|
|
/// </summary>
|
|
[Fact]
|
|
public void FarSnap_CancelledAfterTheCommitSettled_KeepsTheSettledPose()
|
|
{
|
|
PhysicsEngine engine = FlatEngine();
|
|
engine.TransitionCellCollisionTestHook =
|
|
(transition, phase, _, observed) =>
|
|
{
|
|
if (phase == TransitionCellCollisionPhase.Environment)
|
|
{
|
|
transition.CollisionInfo.SetContactPlane(
|
|
new Plane(Vector3.UnitZ, 0f),
|
|
DestinationCell);
|
|
}
|
|
return observed;
|
|
};
|
|
using var lifetime = new RuntimeEntityObjectLifetime(engine);
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003033u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
var supersedingDestination = new Vector3(77f, 88f, SpawnHeight);
|
|
Vector3 settled = Vector3.Zero;
|
|
RuntimeEntityPlacementToken displaced = default;
|
|
remote.Movement.Minterp.RemoveLinkAnimations = () =>
|
|
{
|
|
settled = body.Position;
|
|
record.Snapshot = record.Snapshot with
|
|
{
|
|
Position = new CreateObject.ServerPosition(
|
|
DestinationCell,
|
|
supersedingDestination.X,
|
|
supersedingDestination.Y,
|
|
supersedingDestination.Z,
|
|
1f,
|
|
0f,
|
|
0f,
|
|
0f),
|
|
};
|
|
displaced = lifetime.Physics.SetPosition.BeginAcceptedPlacement(
|
|
record,
|
|
record.PositionAuthorityVersion,
|
|
RuntimeSetPositionOperationKind.RemoteAuthoritative);
|
|
};
|
|
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
DestinationCell,
|
|
destination,
|
|
stopInterpolating: true);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.RejectedByPlacement,
|
|
drive.ApplyAcceptedRemoteFarSnap(record, remote, route));
|
|
|
|
// The ground-edge callback really did run mid-commit, and the commit
|
|
// really did settle the body before it.
|
|
Assert.True(displaced.IsValid);
|
|
Assert.NotEqual(Vector3.Zero, settled);
|
|
Assert.Equal(settled, body.Position);
|
|
Assert.NotEqual(
|
|
supersedingDestination + new Vector3(192f, 0f, 0f),
|
|
body.Position);
|
|
|
|
RuntimePlacementCancellationReceipt cancellation = lifetime.Physics
|
|
.SetPosition.ForgetExactPlacement(displaced);
|
|
if (cancellation.IsValid)
|
|
lifetime.Physics.SetPosition.PublishCancellation(cancellation);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delta review MAJOR A, the store side one stage later than the
|
|
/// pre-flight: preparation refuses TERMINALLY
|
|
/// (<c>RuntimeSetPositionMoverPreparationStatus.InvalidData</c>, here from
|
|
/// a destination cell id <c>PositionFrameValidation</c> rejects), so the
|
|
/// engine was never called and retail's no-transition branch — the one
|
|
/// that DOES <c>store_position</c> @0x00515CE2 — is what this state
|
|
/// corresponds to.
|
|
/// </summary>
|
|
[Fact]
|
|
public void FarSnap_PreparationRejected_StillStoresTheDestinationPose()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003034u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
Vector3 positionBefore = body.Position;
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
// Cell low 0 is outside LandDefs.CellLowInRange, so
|
|
// IsStructurallyValid refuses the prepared frame — while the landblock
|
|
// prefix is still the one the pre-flight and the world frame resolve.
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
DestinationLandblock,
|
|
destination,
|
|
stopInterpolating: true);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.RejectedPreparation,
|
|
drive.ApplyAcceptedRemoteFarSnap(record, remote, route));
|
|
|
|
Assert.Equal(destination + new Vector3(192f, 0f, 0f), body.Position);
|
|
Assert.NotEqual(positionBefore, body.Position);
|
|
Assert.Equal(0, drive.PendingCount);
|
|
Assert.Equal(
|
|
0, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delta review MAJOR D: the <c>store_position</c> fallback must
|
|
/// re-validate position ownership before it writes, exactly as
|
|
/// <c>RuntimeSetPositionState.RestoreParkWithdrawal</c> does and exactly
|
|
/// as this route's own remarks demand of every caller "on EVERY placement
|
|
/// status, before writing anything else for the packet".
|
|
///
|
|
/// <para>
|
|
/// The production producer is <c>CancelToken</c>'s SYNCHRONOUS
|
|
/// cancellation receipt: the placement-projection sink can delete or
|
|
/// replace the incarnation from inside the publish, so the record can stop
|
|
/// being canonical between the placement returning and the fallback
|
|
/// running, and the App-side re-validation only happens after the seam
|
|
/// returns. This test reaches that state deterministically by dropping the
|
|
/// record from the active directory while every other field it holds — the
|
|
/// key, the canonical body, the accepted snapshot — stays exactly as the
|
|
/// packet left it. That isolation matters: a full incarnation replacement
|
|
/// also clears the key and the body, so it would be caught by the null
|
|
/// checks and would prove nothing about the currency test itself.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// <b>What this does and does NOT pin (round 3, correction m3).</b> It
|
|
/// pins that the guard exists, that its predicate is
|
|
/// <c>Entities.IsCurrent</c> and not a null check, and that it runs on
|
|
/// the storing side. It does NOT pin the ORDERING that made the defect
|
|
/// reachable in production — the guard sitting AFTER
|
|
/// <c>CancelToken</c>'s synchronous cancellation receipt — because the
|
|
/// status it drives to is <see cref="RuntimeRemotePlacementExecutionStatus.Refused"/>,
|
|
/// which never calls <c>CancelToken</c> at all. Pinning the ordering
|
|
/// needs a placement-projection sink that supersedes the incarnation from
|
|
/// inside the publish; that sink is production infrastructure this bare
|
|
/// <see cref="RuntimeEntityObjectLifetime"/> fixture does not install,
|
|
/// and building a stand-in here would test the stand-in.
|
|
/// </para>
|
|
/// </summary>
|
|
[Fact]
|
|
public void FarSnap_SupersededIncarnation_DoesNotStoreThroughTheStaleRecord()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003036u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
Vector3 positionBefore = body.Position;
|
|
// The service window refuses, so the status is Refused — squarely on
|
|
// the STORING side of the partition. The currency guard inside
|
|
// StoreAcceptedDestinationPose is therefore the only thing standing
|
|
// between this packet and a pose written through a record that is no
|
|
// longer the canonical owner.
|
|
RuntimeRemotePlacementDriveController drive =
|
|
CreateDrive(lifetime, new FakeServiceWindow());
|
|
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
DestinationCell,
|
|
destination,
|
|
stopInterpolating: true);
|
|
|
|
Assert.True(lifetime.Entities.RemoveActive(record));
|
|
Assert.False(lifetime.Entities.IsCurrent(record));
|
|
Assert.NotNull(record.PhysicsBody);
|
|
Assert.NotNull(record.Key);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Refused,
|
|
drive.ApplyAcceptedRemoteFarSnap(record, remote, route));
|
|
|
|
Assert.Equal(positionBefore, body.Position);
|
|
Assert.NotEqual(destination + new Vector3(192f, 0f, 0f), body.Position);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delta review N1: <see cref="RuntimeRemotePlacementDriveController.Advance"/>'s
|
|
/// window-drop path is the entry point's <c>Refused</c> semantics one
|
|
/// cadence pump later, so it owes the same <c>store_position</c>. Without
|
|
/// it a retained retry whose destination leaves the window reproduces a
|
|
/// smaller version of the freeze this round exists to fix — the remote
|
|
/// stops at whatever pose the FIRST packet stored and never advances
|
|
/// again.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Advance_DestinationLeavesTheWindow_StoresTheNewestDestinationPose()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(
|
|
lifetime, 0x70003035u, setupTableId: 0x02000001u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
// The Setup collision never resolves, so the entry point retains a
|
|
// preparation retry for the cadence pump.
|
|
var firstDestination = new Vector3(12f, 14f, SpawnHeight);
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Contention,
|
|
drive.ApplyAcceptedRemoteFarSnap(
|
|
record,
|
|
remote,
|
|
MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
DestinationCell,
|
|
firstDestination,
|
|
stopInterpolating: true)));
|
|
Assert.Equal(1, drive.PendingCount);
|
|
Assert.Equal(
|
|
firstDestination + new Vector3(192f, 0f, 0f), body.Position);
|
|
|
|
// The server keeps broadcasting while the retry sits retained: the
|
|
// accepted snapshot moves on, and the destination's collision
|
|
// publication retires out from under the retry.
|
|
var newestDestination = new Vector3(40f, 50f, SpawnHeight);
|
|
record.Snapshot = record.Snapshot with
|
|
{
|
|
Position = new CreateObject.ServerPosition(
|
|
DestinationCell,
|
|
newestDestination.X,
|
|
newestDestination.Y,
|
|
newestDestination.Z,
|
|
1f,
|
|
0f,
|
|
0f,
|
|
0f),
|
|
};
|
|
window.Forbid(DestinationLandblock);
|
|
|
|
drive.Advance();
|
|
|
|
Assert.Equal(0, drive.PendingCount);
|
|
Assert.Equal(
|
|
newestDestination + new Vector3(192f, 0f, 0f), body.Position);
|
|
Assert.Equal(
|
|
0, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// R9 review fix: contract item 7 names currency across GUID reuse and
|
|
/// incarnation, and 4b-2 shipped only the interleaving and teardown
|
|
/// dimensions. <see cref="RuntimeRemotePlacementDriveController"/>'s two
|
|
/// maps are keyed by <c>RuntimeEntityKey</c> (guid + incarnation), so a
|
|
/// reused GUID produces a NEW key and cannot displace the old entry by
|
|
/// itself — a dead-incarnation entry would accumulate until
|
|
/// <c>DetachRoute</c>.
|
|
///
|
|
/// <para>
|
|
/// The convergence must therefore be IN-SESSION and must not need a
|
|
/// teardown: this test retires incarnation 1, re-creates the same GUID at
|
|
/// incarnation 2, and asserts the ledger reads zero with the route still
|
|
/// attached.
|
|
/// </para>
|
|
/// </summary>
|
|
[Fact]
|
|
public void RemotePlacementLedger_ConvergesAcrossGuidReuse_WithoutTeardown()
|
|
{
|
|
const uint reusedGuid = 0x70003027u;
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
var route = new object();
|
|
drive.AttachRoute(route);
|
|
|
|
// Incarnation 1: a far snap whose Setup never resolves, so the
|
|
// controller retains a preparation retry keyed by (guid, 1).
|
|
RuntimeEntityRecord first = CreateRemoteRecord(
|
|
lifetime, reusedGuid, setupTableId: 0x02000001u);
|
|
AttachBody(lifetime, first, SourceCell);
|
|
RemoteMotion firstRemote = lifetime.Physics.GetOrCreateRemoteMotion(first);
|
|
uint firstIncarnation = first.Incarnation;
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Contention,
|
|
drive.ApplyAcceptedRemoteFarSnap(
|
|
first,
|
|
firstRemote,
|
|
MakeRoute(
|
|
first,
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
DestinationCell,
|
|
new Vector3(12f, 14f, SpawnHeight),
|
|
stopInterpolating: true)));
|
|
Assert.Equal(1, drive.PendingCount);
|
|
|
|
// The incarnation is replaced and the SAME guid comes back — the exact
|
|
// shape ACE produces when an entity leaves and re-enters radar range.
|
|
RuntimeEntityRecord second = CreateRemoteRecord(
|
|
lifetime, reusedGuid, instanceSequence: 1);
|
|
Assert.NotEqual(firstIncarnation, second.Incarnation);
|
|
AttachBody(lifetime, second, SourceCell);
|
|
|
|
// Delta review N7 — the assertions below are ORDER-DEPENDENT and the
|
|
// order is now deliberate and documented, because the previous version
|
|
// presented them as two independent facts when they are not.
|
|
// `PendingCount` is the RAW map size (its own doc says so) and
|
|
// `_pending` still holds the dead incarnation-1 entry at this point;
|
|
// `CountLivePending`, the ledger's provider, is what prunes it. So:
|
|
//
|
|
// 1. The LEDGER read is the R9 claim, and it must converge with the
|
|
// route still attached, no teardown, no Advance() pump.
|
|
// 2. The raw count is then asserted as the POST-heal state — proof
|
|
// that the read pruned rather than merely reporting a filtered
|
|
// view while the map grew unbounded (the leak R9 describes).
|
|
Assert.Equal(
|
|
0,
|
|
lifetime.CaptureOwnership().RemotePlacementDrivePendingCount);
|
|
Assert.Equal(0, drive.PendingCount);
|
|
Assert.Equal(
|
|
0, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
AssertConverged(lifetime);
|
|
|
|
drive.DetachRoute(route);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The classifier's <c>StopInterpolating</c> flag
|
|
/// (<c>:467</c>, <c>!nearby</c>) is what gates the queue clear — the
|
|
/// retail condition is READ from the route, never restated. A route
|
|
/// carrying <see langword="false"/> must leave the queue alone.
|
|
/// </summary>
|
|
[Fact]
|
|
public void FarSnap_HonoursTheRoutesOwnStopInterpolatingFlag()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003021u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
remote.Interp.Enqueue(
|
|
new Vector3(40f, 40f, SpawnHeight),
|
|
Quaternion.Identity,
|
|
isMovingTo: false,
|
|
currentBodyPosition: body.Position,
|
|
currentBodyOrientation: body.Orientation);
|
|
|
|
RuntimeRemotePlacementDriveController drive =
|
|
CreateDrive(lifetime, new FakeServiceWindow());
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
DestinationCell,
|
|
stopInterpolating: false);
|
|
|
|
_ = drive.ApplyAcceptedRemoteFarSnap(record, remote, route);
|
|
|
|
Assert.True(remote.Interp.IsActive);
|
|
}
|
|
|
|
[Fact]
|
|
public void FarSnap_CommitsThroughTheCanonicalPlacementOwner()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003022u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
DestinationCell,
|
|
destination,
|
|
stopInterpolating: true);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Committed,
|
|
drive.ApplyAcceptedRemoteFarSnap(record, remote, route));
|
|
|
|
// RemoteMotion shares the canonical body, so the placement's own
|
|
// committed pose IS the remote's body pose — no second write.
|
|
Assert.Same(body, remote.Body);
|
|
Assert.Equal(destination + new Vector3(192f, 0f, 0f), body.Position);
|
|
Assert.Equal(DestinationCell, record.FullCellId);
|
|
DrainPlacementFifo(lifetime);
|
|
Assert.Equal(
|
|
0, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The far arm must never be handed a route it does not own — the caller
|
|
/// selects with <c>RuntimeRemoteFarSnapPosition.ResolveArm</c>. Failing
|
|
/// loudly is the alternative to silently clearing an interpolation queue
|
|
/// for a classification (cell-less, rejected, near) that has no business
|
|
/// stopping it.
|
|
/// </summary>
|
|
[Fact]
|
|
public void FarSnap_ThrowsForARouteThisArmDoesNotOwn()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003023u);
|
|
AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
RuntimeRemotePlacementDriveController drive =
|
|
CreateDrive(lifetime, new FakeServiceWindow());
|
|
|
|
foreach (RuntimeAuthoritativePositionDisposition disposition in
|
|
new[]
|
|
{
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
RuntimeAuthoritativePositionDisposition.Interpolate,
|
|
RuntimeAuthoritativePositionDisposition.NoPositionOperation,
|
|
RuntimeAuthoritativePositionDisposition.RejectedData,
|
|
})
|
|
{
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record, disposition, DestinationCell, stopInterpolating: true);
|
|
Assert.Throws<ArgumentException>(
|
|
() => drive.ApplyAcceptedRemoteFarSnap(record, remote, route));
|
|
}
|
|
}
|
|
|
|
// ── C4 route 4b-3: the teleport/cell-less arm ───────────────────────────
|
|
// TryExecuteAcceptedRemotePosition/SubmitAndResolve's own mechanics
|
|
// (commit/park/reject dispatch, currency, ledger convergence) are already
|
|
// exhaustively proven above for SetPosition-disposition routes — several
|
|
// far-snap tests already construct SetPosition routes because
|
|
// TryExecuteAcceptedRemotePosition is disposition-agnostic. What is new
|
|
// here is ApplyAcceptedRemoteTeleport's OWN behaviour: the route guard,
|
|
// the store_position fallback wired for the teleport disposition
|
|
// specifically, and (D3) that it does NOT clear the interpolation queue
|
|
// itself — unlike the far arm, retail's clear for this branch lives
|
|
// inside teleport_hook, not in MoveOrTeleport.
|
|
|
|
/// <summary>
|
|
/// Mirrors <see cref="Committed_WhenDestinationIsWithinServiceWindowAndCollisionGenerationCommitted"/>
|
|
/// through the teleport arm specifically.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Teleport_Committed_PlacesFromCanonicalDestination()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003040u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell,
|
|
destination);
|
|
|
|
RuntimeRemotePlacementExecutionStatus status =
|
|
drive.ApplyAcceptedRemoteTeleport(record, remote, route);
|
|
|
|
Assert.Equal(RuntimeRemotePlacementExecutionStatus.Committed, status);
|
|
Assert.Equal(destination + new Vector3(192f, 0f, 0f), body.Position);
|
|
DrainPlacementFifo(lifetime);
|
|
Assert.Equal(
|
|
0, lifetime.Physics.CaptureOwnership().SetPositionOperationCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invariant 1: a teleport whose destination the service window declines
|
|
/// still advances the body to the accepted destination — retail's
|
|
/// no-transition <c>store_position</c> branch, identical to the far arm's
|
|
/// own fallback.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Teleport_RefusedByServiceWindow_StillStoresTheDestinationPose()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
// The world frame must still be published (store_position resolves
|
|
// through it); the service window is what refuses — mirrors
|
|
// FarSnap_ClearsTheInterpolationQueue_IndependentlyOfThePlacementOutcome's
|
|
// setup, which is the far arm's own Refused-fallback test.
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003041u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
Vector3 positionBefore = body.Position;
|
|
// Deliberately does not Allow(DestinationLandblock) — the service
|
|
// window refuses.
|
|
var window = new FakeServiceWindow();
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell,
|
|
destination);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Refused,
|
|
drive.ApplyAcceptedRemoteTeleport(record, remote, route));
|
|
|
|
Assert.NotEqual(positionBefore, body.Position);
|
|
Assert.True(body.InWorld);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The non-storing half of retail's partition, through the teleport arm:
|
|
/// the engine's own sweep refused the destination
|
|
/// (<c>RejectedByPlacement</c>), so the body must be left exactly where
|
|
/// it was — mirrors
|
|
/// <see cref="FarSnap_EngineRefusedTheDestination_LeavesTheBodyWhereItWas"/>.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Teleport_EngineRefusedTheDestination_LeavesTheBodyWhereItWas()
|
|
{
|
|
PhysicsEngine engine = FlatEngine();
|
|
using var lifetime = new RuntimeEntityObjectLifetime(engine);
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003042u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
Vector3 positionBefore = body.Position;
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
engine.TransitionCellCollisionTestHook =
|
|
static (_, _, _, _) => TransitionState.Collided;
|
|
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell,
|
|
destination);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.RejectedByPlacement,
|
|
drive.ApplyAcceptedRemoteTeleport(record, remote, route));
|
|
|
|
Assert.Equal(positionBefore, body.Position);
|
|
Assert.NotEqual(destination + new Vector3(192f, 0f, 0f), body.Position);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// D3: unlike the far arm, <c>ApplyAcceptedRemoteTeleport</c> must NOT
|
|
/// clear the interpolation queue itself — the classifier's teleport
|
|
/// branch carries <c>StopInterpolating: false</c> on purpose, because
|
|
/// retail's clear for this branch lives inside <c>teleport_hook</c>'s
|
|
/// <c>PositionManager::StopInterpolating</c> @0x00514EFD, which the
|
|
/// CALLER (<c>ApplyRemoteContactRouting</c>) runs before this method. If
|
|
/// this method also cleared the queue, the two would race on which side
|
|
/// "owns" the retail action.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Teleport_DoesNotClearTheInterpolationQueueItself()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003043u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
remote.Interp.Enqueue(
|
|
new Vector3(40f, 40f, SpawnHeight),
|
|
Quaternion.Identity,
|
|
isMovingTo: false,
|
|
currentBodyPosition: body.Position,
|
|
currentBodyOrientation: body.Orientation);
|
|
Assert.True(remote.Interp.IsActive);
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell,
|
|
new Vector3(12f, 14f, SpawnHeight));
|
|
Assert.False(route.StopInterpolating);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Committed,
|
|
drive.ApplyAcceptedRemoteTeleport(record, remote, route));
|
|
|
|
Assert.True(remote.Interp.IsActive);
|
|
DrainPlacementFifo(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The teleport arm must never be handed a route it does not own — the
|
|
/// caller selects with
|
|
/// <c>RuntimeRemoteTeleportPosition.OwnsTeleportPlacement</c>. Mirrors
|
|
/// <see cref="FarSnap_ThrowsForARouteThisArmDoesNotOwn"/>.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Teleport_ThrowsForARouteThisArmDoesNotOwn()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003044u);
|
|
AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
RuntimeRemotePlacementDriveController drive =
|
|
CreateDrive(lifetime, new FakeServiceWindow());
|
|
|
|
foreach (RuntimeAuthoritativePositionDisposition disposition in
|
|
new[]
|
|
{
|
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
|
RuntimeAuthoritativePositionDisposition.Interpolate,
|
|
RuntimeAuthoritativePositionDisposition.NoPositionOperation,
|
|
RuntimeAuthoritativePositionDisposition.RejectedData,
|
|
})
|
|
{
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record, disposition, DestinationCell);
|
|
Assert.Throws<ArgumentException>(
|
|
() => drive.ApplyAcceptedRemoteTeleport(record, remote, route));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test-plan item 7 / contract D3's currency rule, "now for the teleport
|
|
/// arm" — the R5 shape
|
|
/// <see cref="FarSnap_SupersededIncarnation_DoesNotStoreThroughTheStaleRecord"/>
|
|
/// already pins for <c>ApplyAcceptedRemoteFarSnap</c>. Retail's
|
|
/// <c>store_position</c> fallback is the SAME method
|
|
/// (<c>StoreAcceptedDestinationPose</c>) both arms call through, but that
|
|
/// sharing is exactly why it needs its own pin: a future edit could special-
|
|
/// case one arm's call site without the other, and only a same-shaped test
|
|
/// for each caller catches that. Reaches the stale-record state the same
|
|
/// deterministic way — dropping the record from the active directory while
|
|
/// its body/key/snapshot stay exactly as the packet left them, so the
|
|
/// currency guard (not a null check) is what's under test.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Teleport_SupersededIncarnation_DoesNotStoreThroughTheStaleRecord()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(lifetime, 0x70003045u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
Vector3 positionBefore = body.Position;
|
|
// Deliberately does not Allow(DestinationLandblock) — the service
|
|
// window refuses, landing on the SAME store_position fallback the far
|
|
// arm's currency test exercises.
|
|
RuntimeRemotePlacementDriveController drive =
|
|
CreateDrive(lifetime, new FakeServiceWindow());
|
|
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute route = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell,
|
|
destination);
|
|
|
|
Assert.True(lifetime.Entities.RemoveActive(record));
|
|
Assert.False(lifetime.Entities.IsCurrent(record));
|
|
Assert.NotNull(record.PhysicsBody);
|
|
Assert.NotNull(record.Key);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Refused,
|
|
drive.ApplyAcceptedRemoteTeleport(record, remote, route));
|
|
|
|
Assert.Equal(positionBefore, body.Position);
|
|
Assert.NotEqual(destination + new Vector3(192f, 0f, 0f), body.Position);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test-plan item 8 / proof obligation 2: teardown, session reset, and
|
|
/// generation change all converge <c>RemotePlacementDrivePendingCount</c>
|
|
/// to zero — driven through <see cref="ApplyAcceptedRemoteTeleport"/>
|
|
/// itself, not assumed transitively from
|
|
/// <see cref="LedgerConverges_AfterDetachRouteClearsTrackedEntries"/>
|
|
/// (which seeds its retained entry through the lower shared
|
|
/// <c>TryExecuteAcceptedRemotePosition</c> entry point, bypassing the
|
|
/// teleport arm's own route-ownership check entirely). <c>DetachRoute</c>
|
|
/// is the one production convergence hook this controller exposes —
|
|
/// <c>GameRuntime</c>'s teardown, session reset, and generation-change
|
|
/// paths all funnel through it, exactly as they do for the far arm's own
|
|
/// already-covered case; there is no separate per-cause API to test
|
|
/// independently at this layer.
|
|
///
|
|
/// <para>
|
|
/// Retains via the SAME "retryable preparation" shape
|
|
/// <see cref="FarSnap_RetryablePreparation_StoresThePoseAndStillRetainsTheRetry"/>
|
|
/// uses (an unresolved Setup collision reports <c>Contention</c> and
|
|
/// parks an entry in <c>_pending</c> for the cadence pump) — so the
|
|
/// convergence this test proves is genuinely draining a LIVE retained
|
|
/// teleport retry, not an already-empty ledger.
|
|
/// </para>
|
|
/// </summary>
|
|
[Fact]
|
|
public void Teleport_LedgerConverges_AfterDetachRouteClearsARetainedRetry()
|
|
{
|
|
using var lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
|
|
CommitLandblockCollision(lifetime, DestinationLandblock);
|
|
RuntimeEntityRecord record = CreateRemoteRecord(
|
|
lifetime, 0x70003046u, setupTableId: 0x02000001u);
|
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
|
RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record);
|
|
Vector3 positionBefore = body.Position;
|
|
var window = new FakeServiceWindow();
|
|
window.Allow(DestinationLandblock);
|
|
RuntimeRemotePlacementDriveController drive = CreateDrive(lifetime, window);
|
|
var route = new object();
|
|
drive.AttachRoute(route);
|
|
|
|
var destination = new Vector3(12f, 14f, SpawnHeight);
|
|
RuntimeAuthoritativePositionRoute teleportRoute = MakeRoute(
|
|
record,
|
|
RuntimeAuthoritativePositionDisposition.SetPosition,
|
|
DestinationCell,
|
|
destination,
|
|
stopInterpolating: true);
|
|
|
|
Assert.Equal(
|
|
RuntimeRemotePlacementExecutionStatus.Contention,
|
|
drive.ApplyAcceptedRemoteTeleport(record, remote, teleportRoute));
|
|
// The retained retry is live (not assumed) — a second call through
|
|
// the arm proves it, mirroring the far arm's own
|
|
// FarSnap_RetryablePreparation test's shape.
|
|
Assert.Equal(1, drive.PendingCount);
|
|
Assert.Equal(
|
|
1, lifetime.CaptureOwnership().RemotePlacementDrivePendingCount);
|
|
Assert.Equal(
|
|
destination + new Vector3(192f, 0f, 0f), body.Position);
|
|
Assert.NotEqual(positionBefore, body.Position);
|
|
|
|
drive.DetachRoute(route);
|
|
|
|
Assert.Equal(0, drive.PendingCount);
|
|
Assert.Equal(
|
|
0, lifetime.CaptureOwnership().RemotePlacementDrivePendingCount);
|
|
AssertConverged(lifetime);
|
|
}
|
|
|
|
// ── Fixture ──────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Stands in for the production placement-projection subscription this
|
|
/// bare fixture never wires — a committed placement's <c>Place</c>
|
|
/// receipt would otherwise sit unacknowledged forever, exactly like
|
|
/// <c>RuntimeAcceptedPositionDriveControllerTests.DrainPlacementFifo</c>.
|
|
/// </summary>
|
|
private static void DrainPlacementFifo(RuntimeEntityObjectLifetime lifetime)
|
|
{
|
|
while (lifetime.Physics.SetPosition.TryPeekProjection(
|
|
out RuntimePlacementProjectionSnapshot head))
|
|
{
|
|
if (!lifetime.Physics.SetPosition.AcknowledgeProjection(head.Token))
|
|
break;
|
|
}
|
|
}
|
|
|
|
private static void AssertConverged(RuntimeEntityObjectLifetime lifetime)
|
|
{
|
|
Assert.Equal(
|
|
0,
|
|
lifetime.CaptureOwnership().RemotePlacementDrivePendingCount);
|
|
}
|
|
|
|
private static RuntimeRemotePlacementDriveController CreateDrive(
|
|
RuntimeEntityObjectLifetime lifetime,
|
|
IRuntimeRemotePlacementServiceWindow window) =>
|
|
new(
|
|
lifetime,
|
|
new GameRuntimeClock(),
|
|
new UnusedCollisionSource(),
|
|
window);
|
|
|
|
private static RuntimeAuthoritativePositionRoute MakeRoute(
|
|
RuntimeEntityRecord record,
|
|
RuntimeAuthoritativePositionDisposition disposition,
|
|
uint destinationCellId,
|
|
Vector3? destinationPosition = null,
|
|
RuntimeSetPositionOperationKind operationKind =
|
|
RuntimeSetPositionOperationKind.RemoteAuthoritative,
|
|
PhysicsSetPositionFlags? setPositionFlags = null,
|
|
bool stopInterpolating = false)
|
|
{
|
|
// Publishes the destination into the record's accepted Snapshot —
|
|
// exactly what the upstream merge
|
|
// (RuntimeEntityObjectLifetime.TryApplyPosition) already did before a
|
|
// real caller would ever reach this controller — so the controller's
|
|
// own service-window read (record.Snapshot.Position.LandblockId)
|
|
// observes the SAME destination the route was classified against.
|
|
Vector3 position = destinationPosition ?? new Vector3(10f, 10f, SpawnHeight);
|
|
record.Snapshot = record.Snapshot with
|
|
{
|
|
Position = new CreateObject.ServerPosition(
|
|
destinationCellId,
|
|
position.X,
|
|
position.Y,
|
|
position.Z,
|
|
1f,
|
|
0f,
|
|
0f,
|
|
0f),
|
|
};
|
|
|
|
var authority = new RuntimeAuthoritativePositionAuthority(
|
|
new RuntimeGenerationToken(1),
|
|
record.Key!.Value,
|
|
record.PositionAuthorityVersion,
|
|
AcceptedPositionSequence: 2,
|
|
PreviousTeleportSequence: 0,
|
|
AcceptedTeleportSequence: 0,
|
|
PositionTimestampDisposition.Apply);
|
|
|
|
bool performsSetPosition = disposition is
|
|
RuntimeAuthoritativePositionDisposition.SetPosition
|
|
or RuntimeAuthoritativePositionDisposition.SetPositionSimple;
|
|
|
|
return new RuntimeAuthoritativePositionRoute(
|
|
authority,
|
|
disposition,
|
|
operationKind,
|
|
setPositionFlags ?? (performsSetPosition
|
|
? PhysicsSetPositionFlags.Teleport
|
|
| PhysicsSetPositionFlags.Slide
|
|
: PhysicsSetPositionFlags.None),
|
|
PlacementFrame: 0u,
|
|
UnparentBeforeRouting: true,
|
|
ApplyPlacementFrameBeforeRouting: false,
|
|
LeaveWorld: false,
|
|
TeleportHookPhase: RuntimeTeleportHookPhase.None,
|
|
stopInterpolating,
|
|
ConstrainPhase: RuntimePositionConstrainPhase.AfterPositionOperation,
|
|
PreserveHeading: false,
|
|
ZeroVelocity: false,
|
|
SendPositionImmediately: false,
|
|
CollisionBatchEligible: true);
|
|
}
|
|
|
|
private static RuntimeEntityRecord CreateRemoteRecord(
|
|
RuntimeEntityObjectLifetime lifetime,
|
|
uint guid,
|
|
uint? setupTableId = null,
|
|
ushort instanceSequence = 0)
|
|
{
|
|
RuntimeEntityRecord record = lifetime.RegisterEntity(
|
|
Spawn(guid, setupTableId, instanceSequence)).Canonical!;
|
|
lifetime.Entities.SetFinalPhysicsState(record, PhysicsStateFlags.Gravity);
|
|
return record;
|
|
}
|
|
|
|
private static PhysicsBody AttachBody(
|
|
RuntimeEntityObjectLifetime lifetime,
|
|
RuntimeEntityRecord record,
|
|
uint cellId)
|
|
{
|
|
lifetime.Entities.SetFullCell(
|
|
record, cellId, (cellId & 0xFFFF0000u) | 0xFFFFu);
|
|
var body = new PhysicsBody
|
|
{
|
|
Position = new Vector3(10f, 10f, SpawnHeight),
|
|
Orientation = Quaternion.Identity,
|
|
LastUpdateTime = 1d,
|
|
State = PhysicsStateFlags.Gravity,
|
|
TransientState = TransientStateFlags.Active,
|
|
};
|
|
body.SnapToCell(cellId, body.Position, body.Position);
|
|
lifetime.Entities.SetPhysicsBody(record, body);
|
|
record.ObjectClock.Activate();
|
|
lifetime.Physics.AcknowledgeSpatialProjection(record, spatial: true);
|
|
return body;
|
|
}
|
|
|
|
private static WorldSession.EntitySpawn Spawn(
|
|
uint guid,
|
|
uint? setupTableId,
|
|
ushort instanceSequence = 0) =>
|
|
new(
|
|
guid,
|
|
new CreateObject.ServerPosition(
|
|
SourceCell, 10f, 10f, SpawnHeight, 1f, 0f, 0f, 0f),
|
|
setupTableId,
|
|
AnimPartChanges: Array.Empty<CreateObject.AnimPartChange>(),
|
|
TextureChanges: Array.Empty<CreateObject.TextureChange>(),
|
|
SubPalettes: Array.Empty<CreateObject.SubPaletteSwap>(),
|
|
BasePaletteId: null,
|
|
ObjScale: null,
|
|
Name: "remote",
|
|
ItemType: null,
|
|
MotionState: null,
|
|
MotionTableId: 0x09000001u,
|
|
InstanceSequence: instanceSequence);
|
|
|
|
private static PhysicsEngine FlatEngine()
|
|
{
|
|
var engine = new PhysicsEngine
|
|
{
|
|
DataCache = new PhysicsDataCache(),
|
|
};
|
|
engine.AddLandblock(
|
|
SourceLandblock,
|
|
new TerrainSurface(new byte[81], new float[256]),
|
|
Array.Empty<CellSurface>(),
|
|
Array.Empty<PortalPlane>(),
|
|
worldOffsetX: 0f,
|
|
worldOffsetY: 0f);
|
|
return engine;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mirrors <c>RuntimeAcceptedPositionDriveControllerTests.CommitLandblockCollision</c>
|
|
/// exactly, against a bare <see cref="RuntimeEntityObjectLifetime"/>
|
|
/// instead of a full <c>GameRuntime</c>. Also observes the SOURCE
|
|
/// landblock's world frame once — Runtime's world-frame resolution is
|
|
/// arithmetic thereafter, so a destination landblock never needs its own
|
|
/// call (route 2's own fixture comment).
|
|
/// </summary>
|
|
private static void CommitLandblockCollision(
|
|
RuntimeEntityObjectLifetime lifetime,
|
|
uint landblockId,
|
|
float worldOffsetX = 192f)
|
|
{
|
|
var heights = new byte[81];
|
|
Array.Fill(heights, (byte)SpawnHeight);
|
|
var heightTable = new float[256];
|
|
for (int index = 0; index < heightTable.Length; index++)
|
|
heightTable[index] = index;
|
|
lifetime.Physics.ObserveLocalWorldFrame(
|
|
SourceCell, teleportAdvanced: false);
|
|
lifetime.Physics.SetPosition.BeginCollisionGeneration(landblockId, 1UL);
|
|
lifetime.Physics.Engine.AddLandblock(
|
|
landblockId,
|
|
new TerrainSurface(heights, heightTable),
|
|
Array.Empty<CellSurface>(),
|
|
Array.Empty<PortalPlane>(),
|
|
worldOffsetX,
|
|
worldOffsetY: 0f);
|
|
lifetime.Physics.SetPosition.CommitCollisionGeneration(
|
|
landblockId, 1UL, ready: true);
|
|
}
|
|
|
|
private sealed class FakeServiceWindow : IRuntimeRemotePlacementServiceWindow
|
|
{
|
|
private readonly HashSet<uint> _within = [];
|
|
|
|
internal void Allow(uint landblockId) =>
|
|
_within.Add(Canonical(landblockId));
|
|
|
|
internal void Forbid(uint landblockId) =>
|
|
_within.Remove(Canonical(landblockId));
|
|
|
|
public bool IsWithinServiceWindow(uint landblockId) =>
|
|
_within.Contains(Canonical(landblockId));
|
|
|
|
private static uint Canonical(uint landblockId) =>
|
|
(landblockId & 0xFFFF0000u) | 0xFFFFu;
|
|
}
|
|
|
|
private sealed class UnusedCollisionSource : IPreparedCollisionSource
|
|
{
|
|
public PreparedAssetPresence ProbeCollision(
|
|
PakAssetType type,
|
|
uint sourceFileId) =>
|
|
PreparedAssetPresence.Available;
|
|
|
|
public PreparedCollisionReadResult<FlatSetupCollision> ReadSetupCollision(
|
|
uint sourceFileId,
|
|
CancellationToken cancellationToken = default) =>
|
|
PreparedCollisionReadResult<FlatSetupCollision>.Missing;
|
|
|
|
public PreparedCollisionReadResult<FlatGfxObjCollisionAsset> ReadGfxObjCollision(
|
|
uint sourceFileId,
|
|
CancellationToken cancellationToken = default) =>
|
|
throw new NotSupportedException();
|
|
|
|
public PreparedCollisionReadResult<FlatCellStructureCollisionAsset> ReadCellStructureCollision(
|
|
uint sourceFileId,
|
|
CancellationToken cancellationToken = default) =>
|
|
throw new NotSupportedException();
|
|
|
|
public PreparedCollisionReadResult<FlatEnvCellTopology> ReadEnvCellTopology(
|
|
uint sourceFileId,
|
|
CancellationToken cancellationToken = default) =>
|
|
throw new NotSupportedException();
|
|
|
|
public PreparedCollisionSourceStats CollisionStats => default;
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
}
|
|
}
|