acdream/tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateContinuationExecutorTests.cs
Erik 67f63e85e5 feat(runtime): bridge executor completion to the placement stream
Cutover slice C0 (docs/plans/2026-08-02-placement-cutover.md): the seam
work that lets C3 flip hosts onto a complete receipt stream instead of
growing one mid-cutover. The executor's Released exit now publishes an
acknowledge-only ExecutorCompleted receipt through the one placement
projection stream — registered before observer dispatch, correlated to
the full execution receipt, reaped exactly once on acknowledgement/
discard/session-clear, and counted in the convergence ledger. All three
production placement sinks acknowledge-and-ignore the new kind via early
returns proven behavior-preserving for every existing kind; without them
the first such receipt at cutover would permanently wedge the exact-head
FIFO behind sinks that return false. Provably inert today: the publisher
has no production caller.

Execute's live inputs now derive from Runtime's own owners bound at
GameRuntime construction: UsePositionFromServer is retail's exact
autonomy_level != 2 (CommandInterpreter::UsePositionFromServer
0x006B3B40, startup-only knob), and PlayerDistance uses the live movement
controller's position with a null-safe fallback to the caller struct —
never a fabricated origin. TryPrepareAndSubmitAuthoredPlacement chains
the prepared-collision Setup read through PrepareMover to submission with
zero validation-semantics changes. TryCommitParent and CommitWithdrawal
gain the sibling cancellation flow (residence + ordinary placement
family); TryCommitParent deliberately omits LeaveWorld — retail's
set_parent performs its single gated leave_world (0x00515A90) and a
second would have no counterpart.

Not fully dormant: the two cancellation fixes change Runtime paths
production already calls (today as no-op-adjacent hardening, since
nothing upstream begins a residence yet); everything else is reachable
only by tests. Reviewed: retail-conformance PASS + architecture/
adversarial PASS after one fix round (sink wedge, completion-receipt
lifecycle, null-controller distance). Runtime 921/921; complete Release
solution 10,716 passed / 4 intentional skips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 05:22:37 +02:00

4946 lines
247 KiB
C#

using System.Collections.Immutable;
using System.Numerics;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Physics;
namespace AcDream.Runtime.Tests.Entities;
public sealed class RuntimeInitialCreateContinuationExecutorTests
{
private const uint Landblock = 0xA9B50000u;
private const uint Cell = Landblock | 0x0001u;
private static readonly RuntimeInitialCreateExecutionInputs NoContact =
new(UsePositionFromServer: false, PlayerDistance: 0f);
// ---------------------------------------------------------------
// A. Basic
// ---------------------------------------------------------------
[Fact]
public void CompletedTopLevelCreateAdoptsOnceEmitsHookAndConvergesEveryLedger()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 1UL);
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(0x70020001u, 1), isLocalPlayer: true)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
Assert.Equal(Cell, receipt.FullCellId);
Assert.Equal(RuntimeTeleportHookPhase.AfterEnterWorld, receipt.TeleportHookPhase);
Assert.Equal(0, receipt.ReplayedDeferredChildCount);
Assert.Equal(
[
RuntimeInitialCreateExecutedActionKind.InitialAdoption,
RuntimeInitialCreateExecutedActionKind.TeleportHookRequest,
],
receipt.Trace.Select(static a => a.Kind));
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
Assert.Equal(
0,
lifetime.Physics.SetPosition.CaptureOwnership().AcknowledgedPlacementCompletionCount);
Assert.False(lifetime.TryGetInitialCreateResidence(canonical, out _));
// Retrying with the now-stale token is a distinct, safe no-op.
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedToken,
lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out _));
}
[Fact]
public void MixedSimpleContinuationsDrainInExactSequenceOrderAndMutateSnapshot()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 2UL);
const uint parentGuid = 0x70021100u;
const uint guid = 0x70021000u;
_ = lifetime.RegisterEntity(Spawn(parentGuid, 1));
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
Assert.False(lease.Placement.IsValid); // parented -> AwaitFreshPosition, no placement
var appearance = new ObjDescEvent.Parsed(
guid,
new CreateObject.ModelData(
0x04000002u,
Array.Empty<CreateObject.SubPaletteSwap>(),
Array.Empty<CreateObject.TextureChange>(),
Array.Empty<CreateObject.AnimPartChange>()),
InstanceSequence: 1,
ObjDescSequence: 2);
Assert.True(lifetime.TryApplyObjDesc(appearance, null, out _));
var vector = new VectorUpdate.Parsed(
guid, new Vector3(1f, 2f, 3f), Vector3.Zero, InstanceSequence: 1, VectorSequence: 2);
Assert.True(lifetime.TryApplyVector(vector, null, out _));
var state = new SetState.Parsed(
guid,
(uint)PhysicsStateFlags.Gravity,
InstanceSequence: 1,
StateSequence: 2);
Assert.True(lifetime.TryApplyState(state, null, out _, out _));
var observed = new List<RuntimeEntityChange>();
using IDisposable subscription = lifetime.Events.Subscribe(
new EntityObserver(delta => observed.Add(delta.Change)));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
Assert.Equal(
[
RuntimeInitialCreateExecutedActionKind.InitialAdoption,
RuntimeInitialCreateExecutedActionKind.ObjDesc,
RuntimeInitialCreateExecutedActionKind.Vector,
RuntimeInitialCreateExecutedActionKind.State,
],
receipt.Trace.Select(static a => a.Kind));
Assert.Equal([1UL, 2UL, 3UL],
receipt.Trace
.Where(static a => a.Sequence != 0UL)
.Select(static a => a.Sequence));
Assert.Equal(
[RuntimeEntityChange.Updated, RuntimeEntityChange.Updated, RuntimeEntityChange.Updated],
observed);
Assert.Equal(0x04000002u, canonical.Snapshot.BasePaletteId);
Assert.Equal(new Vector3(1f, 2f, 3f), canonical.Snapshot.Physics!.Value.Velocity);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
// Round 4 R4-4 (mandated regression test): a FIFO of ObjDesc then Vector
// - NEITHER touches any of the four executor-tracked baseline fields
// (PositionAuthorityVersion/CreateIntegrationVersion/FullCellId/
// PlacementCommitVersion), so NEITHER may call AdvanceExecutorBaseline
// at all. An observer bumps PositionAuthorityVersion externally during
// ObjDesc's own publish; because ObjDesc's apply never rebaselines that
// field, the STALE Expected value survives into the next check. The
// drain must detect this external race at ConsumeExecuted (the "next
// step") and abandon - NOT silently absorb it and report Released, which
// a blanket four-field rebaseline (the pre-R4-4 shape) would have done.
[Fact]
public void FieldMaskedBaselinePrecisionDetectsAnExternalPositionRaceDuringAnUnrelatedObjDescPublish()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 96UL);
const uint parentGuid = 0x70038000u;
const uint guid = 0x70038001u;
_ = lifetime.RegisterEntity(Spawn(parentGuid, 1));
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
var appearance = new ObjDescEvent.Parsed(
guid,
new CreateObject.ModelData(
0x07000002u,
Array.Empty<CreateObject.SubPaletteSwap>(),
Array.Empty<CreateObject.TextureChange>(),
Array.Empty<CreateObject.AnimPartChange>()),
InstanceSequence: 1,
ObjDescSequence: 2);
Assert.True(lifetime.TryApplyObjDesc(appearance, null, out _));
var vector = new VectorUpdate.Parsed(
guid, new Vector3(1f, 2f, 3f), Vector3.Zero, InstanceSequence: 1, VectorSequence: 2);
Assert.True(lifetime.TryApplyVector(vector, null, out _));
bool bumped = false;
using IDisposable subscription = lifetime.Events.Subscribe(new EntityObserver(delta =>
{
if (bumped || delta.Change is not RuntimeEntityChange.Updated)
return;
bumped = true;
// External, non-executor mutation of PositionAuthorityVersion
// during the ObjDesc stage's OWN publish.
lifetime.Entities.AdvancePositionAuthority(canonical);
}));
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedAuthority,
lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt receipt));
Assert.True(bumped);
Assert.Equal(default, receipt);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
// Round 3 A1 (mandated regression test): the executor's applies must
// write InboundPhysicsStateController's OWN _snapshots[guid] - the
// legacy merge base - in lockstep with RuntimeEntityRecord.Snapshot. If
// they only wrote the record's own Snapshot (as before A1), the FIRST
// legacy wire apply reached after the residence drain would re-merge
// against a STALE _snapshots[guid] base and silently revert every fact
// the drain just committed.
[Fact]
public void DrainedAppearanceAndPoseSurviveTheNextLegacyWireApply()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 30UL);
const uint parentGuid = 0x7002D100u;
const uint guid = 0x7002D000u;
_ = lifetime.RegisterEntity(Spawn(parentGuid, 1));
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
// Drain an ObjDesc continuation that changes appearance (a fresh
// BasePaletteId) as part of the residence FIFO.
var appearance = new ObjDescEvent.Parsed(
guid,
new CreateObject.ModelData(
0x06000002u,
Array.Empty<CreateObject.SubPaletteSwap>(),
Array.Empty<CreateObject.TextureChange>(),
Array.Empty<CreateObject.AnimPartChange>()),
InstanceSequence: 1,
ObjDescSequence: 2);
Assert.True(lifetime.TryApplyObjDesc(appearance, null, out _));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
Assert.Contains(
RuntimeInitialCreateExecutedActionKind.ObjDesc,
receipt.Trace.Select(static a => a.Kind));
Assert.Equal(0x06000002u, canonical.Snapshot.BasePaletteId);
// Now run an ORDINARY legacy wire apply (Vector) on a channel the
// drain never touched. Its merge base is InboundPhysicsStateController's
// OWN _snapshots[guid] - if that store still held the pre-drain
// appearance (A1's bug), this apply's `old with { ... }` merge would
// carry the STALE BasePaletteId back into canonical.Snapshot via
// RefreshSnapshot, silently reverting the drained fact.
var vector = new VectorUpdate.Parsed(
guid, new Vector3(4f, 5f, 6f), Vector3.Zero, InstanceSequence: 1, VectorSequence: 2);
Assert.True(lifetime.TryApplyVector(vector, null, out _));
Assert.Equal(0x06000002u, canonical.Snapshot.BasePaletteId);
Assert.Equal(new Vector3(4f, 5f, 6f), canonical.Snapshot.Physics!.Value.Velocity);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
// Round 3 B12: the WeenieDescription tail action must drive the object
// table the same way RuntimeLiveEntitySessionController.OnSpawned does
// for the non-residence direct-host Create path (ApplyAcceptedSpawn) -
// the prior "zero callers" claim for this wiring was false. A
// residence-pending admission deliberately never wires the object table
// at the initial Create (RegisterEntityCore's beginInitialResidence
// branch); the FIRST time this guid's entry can appear is exactly here.
[Fact]
public void WeenieDescriptionStageWiresTheObjectTableExactlyOnce()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 34UL);
const uint guid = 0x7002E400u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
int objectCountBeforeDrain = lifetime.Objects.ObjectCount;
WorldSession.EntitySpawn sameCreate = Spawn(
guid, 1, includePosition: false, positionSequence: 2);
PhysicsSpawnData physics = sameCreate.Physics!.Value;
sameCreate = sameCreate with
{
Physics = physics with
{
Timestamps = physics.Timestamps with { State = 2, Vector = 2 },
},
};
_ = lifetime.RegisterEntityWithInitialResidence(sameCreate, isLocalPlayer: false);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
Assert.Contains(
RuntimeInitialCreateExecutedActionKind.WeenieDescription,
receipt.Trace.Select(static a => a.Kind));
Assert.Equal(objectCountBeforeDrain + 1, lifetime.Objects.ObjectCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
// Round 4 R4-3: ApplyWeenieDescriptionAction's object-table apply window.
// ClientObjectTable.Ingest publishes ObjectAdded/ObjectUpdated
// SYNCHRONOUSLY - a subscriber may re-enter a wire apply for the SAME
// entity from inside that dispatch. Because AdvanceExecutorBaseline now
// runs BEFORE _applyAcceptedSpawn (not after), the residence's baseline
// is already current at the moment the reentrant call runs; the
// residence is not retired and the envelope still completes.
[Fact]
public void ObjectTableSubscriberReenteringAWireApplyDuringIngestDoesNotRetireTheResidenceAndTheEnvelopeCompletes()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 94UL);
const uint guid = 0x70037000u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
WorldSession.EntitySpawn sameCreate = Spawn(
guid, 1, includePosition: false, positionSequence: 2);
PhysicsSpawnData physics = sameCreate.Physics!.Value;
sameCreate = sameCreate with
{
Physics = physics with
{
Timestamps = physics.Timestamps with { State = 2, Vector = 2 },
},
};
_ = lifetime.RegisterEntityWithInitialResidence(sameCreate, isLocalPlayer: false);
bool reentered = false;
lifetime.Objects.ObjectAdded += addedObject =>
{
if (reentered)
return;
reentered = true;
// A reentrant wire apply for a channel this envelope does NOT
// itself carry a dedicated stage for at this admission
// (Vector IS one of this envelope's own stages here, but the
// residence is still "pending" mid-drain, so this legitimately
// defers into the SAME FIFO rather than applying immediately -
// proving the residence survives the reentrant call).
var vector = new VectorUpdate.Parsed(
guid, new Vector3(9f, 8f, 7f), Vector3.Zero, InstanceSequence: 1, VectorSequence: 3);
Assert.True(lifetime.TryApplyVector(vector, null, out _));
};
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
Assert.True(reentered);
Assert.Contains(
RuntimeInitialCreateExecutedActionKind.WeenieDescription,
receipt.Trace.Select(static a => a.Kind));
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
// Round 4 R4-3: a NESTED REPLACEMENT (a newer incarnation Create for the
// SAME guid) arriving from within Ingest's own synchronous dispatch
// invalidates the exact canonical incarnation the drain is still
// executing against. ApplyAcceptedSpawn re-checks currency AFTER its own
// object-table apply (mirroring RuntimeLiveEntitySessionController.cs:87's
// gate on that same call's result) and returns false; the executor must
// treat this as a typed abandonment - the remaining tail (ResidentCellCleanup)
// never runs.
[Fact]
public void NestedReplacementDuringObjectTableIngestAbandonsTheWeenieDescriptionStageWithNoFurtherStages()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 95UL);
const uint guid = 0x70037100u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
WorldSession.EntitySpawn sameCreate = Spawn(
guid, 1, includePosition: false, positionSequence: 2);
PhysicsSpawnData physics = sameCreate.Physics!.Value;
sameCreate = sameCreate with
{
Physics = physics with
{
Timestamps = physics.Timestamps with { State = 2, Vector = 2 },
},
};
_ = lifetime.RegisterEntityWithInitialResidence(sameCreate, isLocalPlayer: false);
bool reentered = false;
lifetime.Objects.ObjectAdded += addedObject =>
{
if (reentered)
return;
reentered = true;
_ = lifetime.RegisterEntity(Spawn(guid, 2, includePosition: false));
};
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedAuthority,
lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt receipt));
Assert.True(reentered);
Assert.Equal(default, receipt);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
// Round 3 A2 (mandated regression test): a standalone ObjDesc
// continuation admits a fresh palette (bumping the gate's ObjDesc
// channel and proving A1's snapshot lockstep feeds entry 2's merge
// base), THEN a same-incarnation Create arrives whose OWN raw
// WeenieDescription packet carries a DIFFERENT MotionTableId. Appearance
// fields (BasePaletteId etc.) are legitimately re-applied by the
// envelope's OWN dedicated ObjDesc stage (retail-faithful: a same-
// generation Create's tail always re-runs its own ObjDesc first) - that
// is NOT what A2 fixes. MotionTableId has NO dedicated envelope stage;
// WeenieDescription's merge is the ONLY place it can move, which
// isolates MergeUntimestampedCreate's "retained wins" rule cleanly: the
// prior wholesale RefreshSnapshot bug would have silently overwritten it
// with the incoming raw packet's value instead.
[Fact]
public void SameIncarnationWeenieDescriptionMergePreservesRetainedMotionTableId()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 31UL);
const uint parentGuid = 0x7002D300u;
const uint guid = 0x7002D200u;
_ = lifetime.RegisterEntity(Spawn(parentGuid, 1));
WorldSession.EntitySpawn initial = Spawn(guid, 1, includePosition: false, parentGuid: parentGuid);
PhysicsSpawnData initialPhysics = initial.Physics!.Value;
initial = initial with
{
MotionTableId = 0x12345678u,
Physics = initialPhysics with { MotionTableId = 0x12345678u },
};
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(initial, isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
// FIFO entry 1: standalone ObjDesc with a NEW palette, bumping the
// gate's ObjDesc channel to 2.
var newAppearance = new ObjDescEvent.Parsed(
guid,
new CreateObject.ModelData(
0x07000002u,
Array.Empty<CreateObject.SubPaletteSwap>(),
Array.Empty<CreateObject.TextureChange>(),
Array.Empty<CreateObject.AnimPartChange>()),
InstanceSequence: 1,
ObjDescSequence: 2);
Assert.True(lifetime.TryApplyObjDesc(newAppearance, null, out _));
// FIFO entry 2: a same-incarnation Create whose OWN raw
// WeenieDescription packet carries a DIFFERENT MotionTableId and a
// NEWER-still ObjDesc channel stamp (3).
WorldSession.EntitySpawn sameCreate = Spawn(
guid, 1, includePosition: false, parentGuid: parentGuid, positionSequence: 2);
PhysicsSpawnData samePhysics = sameCreate.Physics!.Value;
sameCreate = sameCreate with
{
MotionTableId = 0x99999999u,
Physics = samePhysics with
{
MotionTableId = 0x99999999u,
Timestamps = samePhysics.Timestamps with { ObjDesc = 3, State = 2, Vector = 2 },
},
};
RuntimeEntityRegistrationResult same = lifetime
.RegisterEntityWithInitialResidence(sameCreate, isLocalPlayer: false);
Assert.Equal(CreateObjectTimestampDisposition.ExistingGeneration, same.Inbound.Disposition);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
Assert.Contains(
RuntimeInitialCreateExecutedActionKind.WeenieDescription,
receipt.Trace.Select(static a => a.Kind));
// The envelope's OWN ObjDesc stage correctly re-applies THIS
// Create's own appearance (sameCreate never set its own palette, so
// it legitimately reverts to null - retail-faithful, not what A2
// fixes). Entry 1's transient fresh palette was always going to be
// superseded by entry 2's OWN ObjDesc stage; that stage - not
// WeenieDescription - owns this field.
Assert.Null(canonical.Snapshot.BasePaletteId);
// MotionTableId has no dedicated envelope stage - WeenieDescription's
// merge must keep the RETAINED value, never adopt incoming's raw
// packet wholesale.
Assert.Equal(0x12345678u, canonical.Snapshot.Physics!.Value.MotionTableId);
Assert.Equal(0x12345678u, canonical.Snapshot.MotionTableId);
// ObjDesc's timestamp DOES have a dedicated stage that legitimately
// advances it further as part of entry 2's own admission (unlike
// MotionTableId).
Assert.Equal(3, canonical.Snapshot.Physics!.Value.Timestamps.ObjDesc);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
// ---------------------------------------------------------------
// B. Standalone continuations (Movement/Pickup/Parent) + enqueue-
// during-drain (ConsumeExecuted's Revised arm).
// ---------------------------------------------------------------
[Fact]
public void StandaloneMovementContinuationAppliesPayloadAndPublishesUpdated()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 40UL);
const uint guid = 0x7002F000u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
var motion = new WorldSession.EntityMotionUpdate(
guid,
new CreateObject.ServerMotionState(0x3d, 0x11),
InstanceSequence: 1,
MovementSequence: 2,
ServerControlSequence: 1,
IsAutonomous: false);
// Movement (2) advances and ServerControl (1) is equal-not-stale, so
// the gate accepts the payload outright.
Assert.True(lifetime.TryApplyMotion(motion, retainPayload: true, null, out _, out _));
var observed = new List<RuntimeEntityChange>();
using IDisposable subscription = lifetime.Events.Subscribe(
new EntityObserver(delta => observed.Add(delta.Change)));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
Assert.Equal(
[
RuntimeInitialCreateExecutedActionKind.InitialAdoption,
RuntimeInitialCreateExecutedActionKind.Movement,
],
receipt.Trace.Select(static a => a.Kind));
Assert.Equal(new CreateObject.ServerMotionState(0x3d, 0x11),
canonical.Snapshot.MotionState);
Assert.Equal(2, canonical.Snapshot.Physics!.Value.Timestamps.Movement);
Assert.Equal([RuntimeEntityChange.Updated], observed);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
[Fact]
public void StandaloneMovementContinuationTimestampOnlyStampsWithoutPayloadOrPublish()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 41UL);
const uint guid = 0x7002F100u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
Assert.Null(canonical.Snapshot.MotionState);
var motion = new WorldSession.EntityMotionUpdate(
guid,
new CreateObject.ServerMotionState(0x3d, 0x11),
InstanceSequence: 1,
MovementSequence: 2,
// Retail consumes MOVEMENT_TS before discovering a stale
// SERVER_CONTROLLED_MOVE_TS (0, older than the gate's seeded 1) -
// Movement itself still advances (hasTimestampMutation) even
// though the overall event, and thus the payload, is rejected.
ServerControlSequence: 0,
IsAutonomous: false);
Assert.False(lifetime.TryApplyMotion(motion, retainPayload: true, null, out _, out _));
Assert.True(lifetime.InitialCreateResidences.TryGetTransaction(
canonical, out RuntimeInitialCreateResidenceLease retained));
Assert.Single(retained.Continuations);
var observed = new List<RuntimeEntityChange>();
using IDisposable subscription = lifetime.Events.Subscribe(
new EntityObserver(delta => observed.Add(delta.Change)));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
Assert.Contains(
RuntimeInitialCreateExecutedActionKind.Movement,
receipt.Trace.Select(static a => a.Kind));
// Timestamp landed; no payload, no publish (matches the legacy
// timestamp-only branch).
Assert.Equal(2, canonical.Snapshot.Physics!.Value.Timestamps.Movement);
Assert.Null(canonical.Snapshot.MotionState);
Assert.Empty(observed);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
[Fact]
public void StandalonePickupContinuationLeavesWorldThroughTheDrainAndResidenceStillReleases()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 42UL);
const uint guid = 0x7002F200u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
Assert.Equal(Cell, canonical.FullCellId);
// A committed parent projection so Pickup's own EndChildProjection
// has something real to tear down.
var relation = new ParentAttachmentRelation(
0x7002F300u, guid, ParentLocation: 1u, PlacementId: 0u,
ParentInstanceSequence: 1, ChildPositionSequence: 1);
lifetime.Entities.ParentAttachments.AcceptCreateObjectRelation(relation);
Assert.True(lifetime.Entities.ParentAttachments.CommitProjection(relation));
Assert.True(lifetime.Entities.ParentAttachments.HasCommittedParent(guid));
Assert.True(lifetime.TryApplyPickup(
new PickupEvent.Parsed(guid, InstanceSequence: 1, PositionSequence: 2),
null,
out _));
var observed = new List<RuntimeEntityChange>();
using IDisposable subscription = lifetime.Events.Subscribe(
new EntityObserver(delta => observed.Add(delta.Change)));
ulong clockEpochBefore = canonical.ObjectClockEpoch;
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
Assert.Equal(
[
RuntimeInitialCreateExecutedActionKind.InitialAdoption,
RuntimeInitialCreateExecutedActionKind.Pickup,
],
receipt.Trace.Select(static a => a.Kind));
Assert.Equal([RuntimeEntityChange.Withdrawn], observed);
Assert.Equal(0u, canonical.FullCellId);
// SuspendObjectClock bumps the epoch - the clock is no longer live.
Assert.NotEqual(clockEpochBefore, canonical.ObjectClockEpoch);
Assert.False(lifetime.Entities.ParentAttachments.HasCommittedParent(guid));
Assert.False(lifetime.Entities.ParentAttachments.TryGetRecoveryProjection(guid, out _));
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership().ActiveOperationCount);
// Residence still releases cleanly even though this continuation
// left the world mid-drain.
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
[Fact]
public void StandaloneParentContinuationAppliesPositionTimestampOnlyMergeWithNoReDefer()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 43UL);
const uint parentGuid = 0x7002F400u;
const uint childGuid = 0x7002F500u;
_ = lifetime.RegisterEntity(Spawn(parentGuid, 1));
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(childGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
var parentUpdate = new ParentEvent.Parsed(
parentGuid,
childGuid,
ParentLocation: 1u,
PlacementId: 0u,
ParentInstanceSequence: 1,
ChildPositionSequence: 2);
// The parent is alive at admission AND stays alive all the way
// through the drain - contrast with
// ParentContinuationRevalidatesLiveParentAtExecutionAndReDefersOnMismatch,
// which deletes the parent between admission and execution.
Assert.True(lifetime.TryApplyParent(parentUpdate, null, out _));
var observed = new List<RuntimeEntityChange>();
using IDisposable subscription = lifetime.Events.Subscribe(
new EntityObserver(delta => observed.Add(delta.Change)));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
Assert.Equal(
[
RuntimeInitialCreateExecutedActionKind.InitialAdoption,
RuntimeInitialCreateExecutedActionKind.Parent,
],
receipt.Trace.Select(static a => a.Kind));
// ApplyAcceptedParent is position-timestamp-only: the shared
// POSITION_TS channel advances but no pose/ParentGuid field moves
// (the actual attach commit is TryCommitParent's job, out of scope
// here).
Assert.Equal(2, canonical.Snapshot.Physics!.Value.Timestamps.Position);
Assert.Null(canonical.Snapshot.ParentGuid);
Assert.Equal([RuntimeEntityChange.Updated], observed);
// The successful path hands NOTHING to ParentAttachments' unresolved
// bucket - the direct contrast with the re-defer test's
// UnresolvedRelationCount == 1.
Assert.Equal(0, lifetime.Entities.ParentAttachments.UnresolvedRelationCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
// Round 3 arc-10 (enqueue-during-drain): a continuation enqueued from an
// observer mid-drain bumps the completed entry's Adoption.Revision in
// place; ConsumeExecuted must return Revised (not Released) so the SAME
// Execute() call's outer while(true) loop re-fetches via Complete and
// drains only the newly-appended tail. The already-applied prefix must
// never replay.
[Fact]
public void EnqueueDuringDrainExercisesConsumeExecutedRevisedArmAndDrainsTailOnlyOnce()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 44UL);
const uint guid = 0x7002F600u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
var appearance = new ObjDescEvent.Parsed(
guid,
new CreateObject.ModelData(
0x08000002u,
Array.Empty<CreateObject.SubPaletteSwap>(),
Array.Empty<CreateObject.TextureChange>(),
Array.Empty<CreateObject.AnimPartChange>()),
InstanceSequence: 1,
ObjDescSequence: 2);
Assert.True(lifetime.TryApplyObjDesc(appearance, null, out _));
var observed = new List<RuntimeEntityChange>();
bool enqueuedFromObserver = false;
using IDisposable subscription = lifetime.Events.Subscribe(new EntityObserver(delta =>
{
observed.Add(delta.Change);
if (enqueuedFromObserver)
return;
enqueuedFromObserver = true;
// Reentrant enqueue, mid-drain, from inside the ObjDesc
// continuation's own publish: the residence is still in
// RuntimeInitialCreateResidenceState's _completed dictionary
// (not yet ConsumeExecuted'd), so this routes through
// CanEnqueue's completed-entry branch and bumps Revision in
// place.
var vector = new VectorUpdate.Parsed(
guid, new Vector3(7f, 8f, 9f), Vector3.Zero,
InstanceSequence: 1, VectorSequence: 2);
Assert.True(lifetime.TryApplyVector(vector, null, out _));
}));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
// Each action appears exactly once - the Revised loop-around must
// not replay the already-applied ObjDesc prefix.
Assert.Equal(
[
RuntimeInitialCreateExecutedActionKind.InitialAdoption,
RuntimeInitialCreateExecutedActionKind.ObjDesc,
RuntimeInitialCreateExecutedActionKind.Vector,
],
receipt.Trace.Select(static a => a.Kind));
Assert.Equal(
[RuntimeEntityChange.Updated, RuntimeEntityChange.Updated],
observed);
Assert.Equal(0x08000002u, canonical.Snapshot.BasePaletteId);
Assert.Equal(new Vector3(7f, 8f, 9f), canonical.Snapshot.Physics!.Value.Velocity);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
// ---------------------------------------------------------------
// C. Envelope atomicity
// ---------------------------------------------------------------
[Fact]
public void SameIncarnationEnvelopePublishesNothingUntilEveryStageCommitsThenPublishesInStageOrder()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 3UL);
const uint guid = 0x70022000u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
WorldSession.EntitySpawn sameCreate = Spawn(
guid, 1, includePosition: false, positionSequence: 2) with
{
Name = "same-incarnation",
};
PhysicsSpawnData physics = sameCreate.Physics!.Value;
sameCreate = sameCreate with
{
Physics = physics with
{
Timestamps = physics.Timestamps with { ObjDesc = 2, State = 2, Vector = 2 },
},
};
RuntimeEntityRegistrationResult same = lifetime
.RegisterEntityWithInitialResidence(sameCreate, isLocalPlayer: false);
Assert.Equal(CreateObjectTimestampDisposition.ExistingGeneration, same.Inbound.Disposition);
var observed = new List<RuntimeEntityChange>();
int countAtFirstObservation = -1;
using IDisposable subscription = lifetime.Events.Subscribe(new EntityObserver(delta =>
{
observed.Add(delta.Change);
countAtFirstObservation = observed.Count;
}));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
// No event fired until the whole envelope committed: the very first
// observation must already carry every publish, not a partial subset
// trickling in one at a time.
Assert.True(observed.Count >= 1);
Assert.Equal(observed.Count, countAtFirstObservation);
RuntimeInitialCreateExecutedActionKind[] envelopeStages = receipt.Trace
.Where(static a => a.Sequence == 1UL)
.Select(static a => a.Kind)
.ToArray();
// A same-Create with neither a parent nor a position decomposes into
// a Pickup branch (retail priority: Parent > Position > Pickup - see
// InboundPhysicsStateController.BuildSameGenerationEvents), plus the
// AP-119 PreTailDescriptionAdaptation stage this envelope always
// carries when SameGenerationEvents is present.
Assert.Equal(
[
RuntimeInitialCreateExecutedActionKind.PreTailDescriptionAdaptation,
RuntimeInitialCreateExecutedActionKind.ObjDesc,
RuntimeInitialCreateExecutedActionKind.Pickup,
RuntimeInitialCreateExecutedActionKind.State,
RuntimeInitialCreateExecutedActionKind.Vector,
RuntimeInitialCreateExecutedActionKind.WeenieDescription,
RuntimeInitialCreateExecutedActionKind.ResidentCellCleanup,
],
envelopeStages);
// This entity never claimed a cell (PickedUp residence, then the
// envelope's own Pickup stage nulls the position again) - Gap 4(c),
// the no-cell-claimed destruction-mark disposition.
RuntimeInitialCreateExecutedAction cleanup = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.ResidentCellCleanup);
Assert.Equal(
RuntimeResidentCellCleanupDisposition.CelllessNoWeenieMarkUnreachable,
cleanup.ResidentCellCleanupDisposition);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
[Fact]
public void ResidentCellCleanupUnmarksWhenCellClaimedAndAlreadyResident()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 18UL);
const uint guid = 0x7002A000u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
Assert.Equal(Cell, canonical.FullCellId);
// A same-Create carrying its own position: PositionSource ==
// SameIncarnationCreate makes effectiveContact always true for the
// non-local classify branch, and the entity is already resident
// (FullCellId == Cell, unchanged since neither Position's own
// refreshPosition:false merge nor WeenieDescription's touch it), so
// this classifies to Interpolate - no new placement, no yield.
WorldSession.EntitySpawn sameCreate = Spawn(guid, 1, positionSequence: 2, positionX: 15f);
PhysicsSpawnData physics = sameCreate.Physics!.Value;
sameCreate = sameCreate with
{
Physics = physics with
{
Timestamps = physics.Timestamps with { ObjDesc = 2 },
},
};
_ = lifetime.RegisterEntityWithInitialResidence(sameCreate, isLocalPlayer: false);
var inputs = new RuntimeInitialCreateExecutionInputs(
UsePositionFromServer: false, PlayerDistance: 10f);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, inputs);
RuntimeInitialCreateExecutedAction cleanup = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.ResidentCellCleanup);
Assert.Equal(
RuntimeResidentCellCleanupDisposition.ResidentUnmarked,
cleanup.ResidentCellCleanupDisposition);
Assert.Equal(Cell, canonical.FullCellId);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
[Fact]
public void ResidentCellCleanupFailsClosedWhenClaimedCelllessOutcomeIsNotUnderLostCellOwnership()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 19UL);
const uint guid = 0x7002A100u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: true)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
Assert.False(lease.Placement.IsValid);
Assert.Equal(0u, canonical.FullCellId);
// The same-Create's own position exists on the wire (claims a
// cell), but with UsePositionFromServer=false this LocalPlayer
// classify branch resolves to NoPositionOperation - no SetPosition
// ever begins, so nothing tracks a lost-cell/deferred operation
// for this entity either. The entity was never resident (PickedUp
// initial, no placement ever ran) - claimed + celless + NOT under
// lost-cell ownership is the exact invariant violation this test
// requires. Round 3 B1: this is now a typed abandonment (Abandon
// retires the residence and discards progress), never a throw
// escaping Execute - the ledger must still fully converge.
WorldSession.EntitySpawn sameCreate = Spawn(guid, 1, positionSequence: 2, positionX: 15f);
PhysicsSpawnData physics = sameCreate.Physics!.Value;
sameCreate = sameCreate with
{
Physics = physics with
{
Timestamps = physics.Timestamps with { ObjDesc = 2 },
},
};
_ = lifetime.RegisterEntityWithInitialResidence(sameCreate, isLocalPlayer: true);
var inputs = new RuntimeInitialCreateExecutionInputs(
UsePositionFromServer: false, PlayerDistance: 0f);
RuntimeInitialCreateExecutionStatus status = lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, inputs, out RuntimeInitialCreateExecutionReceipt receipt);
Assert.Equal(RuntimeInitialCreateExecutionStatus.RejectedAuthority, status);
Assert.Equal(default, receipt);
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership().ActiveOperationCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
Assert.False(lifetime.TryGetInitialCreateResidence(canonical, out _));
// A retry with the now-retired token is a distinct, safe no-op -
// proving Abandon actually retired the residence rather than
// leaving it sitting fully current for a replay.
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedToken,
lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, inputs, out RuntimeInitialCreateExecutionReceipt retryReceipt));
Assert.Equal(default, retryReceipt);
}
[Fact]
public void EnvelopePositionStageRequiringSetPositionYieldsResumesAndPublishesOnceAfterCompletion()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 20UL);
const uint guid = 0x7002B000u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
Assert.False(lease.Placement.IsValid);
Assert.Equal(0u, canonical.FullCellId);
AttachDormantBody(lifetime, canonical);
// Cellless (CommittedCellId == canonical.FullCellId == 0) forces
// SetPosition regardless of contact/distance for the non-local
// classify branch - this same-Create's OWN Position stage will
// require a real placement and yield mid-envelope.
WorldSession.EntitySpawn sameCreate = Spawn(guid, 1, positionSequence: 2, positionX: 15f);
PhysicsSpawnData physics = sameCreate.Physics!.Value;
sameCreate = sameCreate with
{
Physics = physics with
{
Timestamps = physics.Timestamps with { ObjDesc = 2, State = 2, Vector = 2 },
},
};
_ = lifetime.RegisterEntityWithInitialResidence(sameCreate, isLocalPlayer: false);
var observed = new List<RuntimeEntityChange>();
using IDisposable subscription = lifetime.Events.Subscribe(
new EntityObserver(delta => observed.Add(delta.Change)));
RuntimeInitialCreateExecutionStatus status = lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt pending);
Assert.Equal(RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement, status);
Assert.Equal(default, pending);
// ObjDesc already committed (buffered) before Position began its
// own placement lifecycle - prove NOTHING has published yet, even
// though a stage before the yield point already ran.
Assert.Empty(observed);
RuntimeEntityKey key = canonical.Key!.Value;
Assert.True(lifetime.InitialCreateExecution
.TryGetPendingContinuationRoute(key, out RuntimeAuthoritativePositionRoute route));
Assert.Equal(RuntimeAuthoritativePositionDisposition.SetPosition, route.Disposition);
CompletePendingContinuationPlacement(lifetime, key, route);
Assert.Empty(observed);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
// Every buffered per-stage event from this ONE envelope publishes
// exactly once, only now that the envelope fully committed.
Assert.Equal(
[
RuntimeEntityChange.Updated,
RuntimeEntityChange.Updated,
RuntimeEntityChange.Updated,
RuntimeEntityChange.Updated,
RuntimeEntityChange.Updated,
],
observed);
RuntimeInitialCreateExecutedActionKind[] envelopeStages = receipt.Trace
.Where(static a => a.Sequence == 1UL)
.Select(static a => a.Kind)
.ToArray();
Assert.Equal(
[
RuntimeInitialCreateExecutedActionKind.PreTailDescriptionAdaptation,
RuntimeInitialCreateExecutedActionKind.ObjDesc,
RuntimeInitialCreateExecutedActionKind.Position,
RuntimeInitialCreateExecutedActionKind.State,
RuntimeInitialCreateExecutedActionKind.Vector,
RuntimeInitialCreateExecutedActionKind.WeenieDescription,
RuntimeInitialCreateExecutedActionKind.ResidentCellCleanup,
],
envelopeStages);
RuntimeInitialCreateExecutedAction positionAction = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.SetPosition,
positionAction.PositionDisposition);
// The continuation's own SetPosition committed a real cell -
// ResidentCellCleanup now sees a claimed, resident entity.
Assert.Equal(Cell, canonical.FullCellId);
RuntimeInitialCreateExecutedAction cleanup = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.ResidentCellCleanup);
Assert.Equal(
RuntimeResidentCellCleanupDisposition.ResidentUnmarked,
cleanup.ResidentCellCleanupDisposition);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership().ActiveOperationCount);
}
// Gap 2 (failure injection between every same-Create envelope stage):
// the Position stage is the ONLY boundary inside a SameIncarnationCreate
// envelope that this suite can interrupt via a REAL yield. Every other
// stage kind - PreTailDescriptionAdaptation, ObjDesc, CreateParent/
// Parent/Pickup, Movement, State, Vector, WeenieDescription,
// ResidentCellCleanup - is a pure, synchronous, in-memory
// RuntimeEntityRecord/InboundPhysicsStateController mutation with NO
// async placement lifecycle and NO event dispatched until the whole
// envelope's buffered publish flush at the very end (see ApplyEnvelope):
// there is no callback, no I/O, and no point where control ever returns
// to a caller (or where an event observer could reenter) mid-stage.
// Injecting a synthetic "failure" between two purely synchronous stages
// would require adding a diagnostic seam to production code purely to
// make a test possible, which this task's own instructions forbid. The
// Position stage is structurally different: it is the one action kind
// whose disposition can require a real RuntimeSetPositionState
// placement round-trip (Begin/Watch/yield/host-prepare-submit-
// acknowledge/resume) - the SAME mechanism a standalone (non-envelope)
// Position continuation uses. The two tests below exhaust that
// reachable boundary: a successful resume (see
// EnvelopePositionStageRequiringSetPositionYieldsResumesAndPublishesOnceAfterCompletion
// above) and an abandonment at that exact boundary (immediately below).
[Fact]
public void EnvelopeAbandonedDuringPositionStageYieldPublishesNothingAndConvergesEveryLedger()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 21UL);
const uint guid = 0x7002B100u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
Assert.False(lease.Placement.IsValid);
AttachDormantBody(lifetime, canonical);
// Same recipe as the successful-resume test: cellless forces
// SetPosition, guaranteeing a mid-envelope yield after ObjDesc (and
// Position's own snapshot-merge commit) already ran.
WorldSession.EntitySpawn sameCreate = Spawn(guid, 1, positionSequence: 2, positionX: 15f);
PhysicsSpawnData physics = sameCreate.Physics!.Value;
sameCreate = sameCreate with
{
Physics = physics with
{
Timestamps = physics.Timestamps with { ObjDesc = 2, State = 2, Vector = 2 },
},
};
_ = lifetime.RegisterEntityWithInitialResidence(sameCreate, isLocalPlayer: false);
var observed = new List<RuntimeEntityChange>();
using IDisposable subscription = lifetime.Events.Subscribe(
new EntityObserver(delta => observed.Add(delta.Change)));
RuntimeInitialCreateExecutionStatus status = lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out _);
Assert.Equal(RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement, status);
Assert.Empty(observed);
// Abandon between Execute retries at the yield: delete the entity
// rather than ever completing the continuation's placement.
Assert.True(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(guid, canonical.Incarnation),
isLocalPlayer: false,
removeRetainedObject: true,
out RuntimeEntityDeleteAcceptance acceptance));
lifetime.CompleteAcceptedDelete(acceptance);
// The delete's own Deleted is the only observed event - NONE of the
// envelope's buffered stages (ObjDesc, and Position's own snapshot
// merge, both of which had ALREADY committed before the yield) ever
// publish. TryAcceptDelete's own unconditional Physics.SetPosition.Forget
// already cancels the still-pending continuation placement here
// (same key, any token); DiscardProgress ALSO forgets it
// defensively (see its remarks) for callers that retire a
// residence without going through the full delete path.
Assert.Equal([RuntimeEntityChange.Deleted], observed);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership().ActiveOperationCount);
Assert.Equal(
0,
lifetime.Physics.SetPosition.CaptureOwnership().AcknowledgedPlacementCompletionCount);
// A stale Execute() with the original token, after the entity is
// gone, must not resurrect anything either.
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedToken,
lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt receipt));
Assert.Equal(default, receipt);
}
[Fact]
public void EnvelopeStageRetryDoesNotDuplicateAlreadyCommittedStages()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 4UL);
const uint guid = 0x70022100u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
WorldSession.EntitySpawn sameCreate = Spawn(
guid, 1, includePosition: false, positionSequence: 2);
PhysicsSpawnData physics = sameCreate.Physics!.Value;
sameCreate = sameCreate with
{
Physics = physics with
{
Timestamps = physics.Timestamps with { Vector = 2 },
},
};
_ = lifetime.RegisterEntityWithInitialResidence(sameCreate, isLocalPlayer: false);
// The first Execute call fully drains the envelope in one synchronous
// pass (no yield point exists in this envelope). A SECOND call with
// the same (now-stale, released) token must not resurrect or
// reapply anything.
RuntimeInitialCreateExecutionReceipt first = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
ulong vectorAuthorityAfterFirst = canonical.VectorAuthorityVersion;
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedToken,
lifetime.InitialCreateExecution.Execute(canonical, lease.Token, NoContact, out _));
Assert.Equal(vectorAuthorityAfterFirst, canonical.VectorAuthorityVersion);
Assert.NotEmpty(first.Trace);
}
// ---------------------------------------------------------------
// D. Position routes
// ---------------------------------------------------------------
[Fact]
public void LocalOrdinaryPositionInterpolatesWithoutWorldPlacement()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 5UL);
const uint guid = 0x70023000u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, positionSequence: 2, teleportSequence: 0, forcePositionSequence: 0, positionX: 15f);
Assert.True(lifetime.TryApplyPosition(
update, isLocalPlayer: true, null, null, false, null,
out PositionTimestampDisposition disposition, out _, out _));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
var inputs = new RuntimeInitialCreateExecutionInputs(
UsePositionFromServer: true, PlayerDistance: 0f);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, inputs);
RuntimeInitialCreateExecutedAction positionAction = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.Interpolate,
positionAction.PositionDisposition);
Assert.Equal(15f, canonical.Snapshot.Position!.Value.PositionX);
// Interpolate never runs a physics placement: the cell stays exactly
// what the initial SetPosition already committed.
Assert.Equal(Cell, canonical.FullCellId);
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership().ActiveOperationCount);
// Round 4 R4-8: local ordinary route trace flags - CONSTRAIN-BEFORE
// (retail-notes.md function 3 line ~93041: ConstrainTo runs
// unconditionally BEFORE InterpolateTo on this route), unparent
// always runs on a received Position, and no teleport hook.
Assert.Equal(RuntimePositionConstrainPhase.BeforePositionOperation, positionAction.ConstrainPhase);
Assert.True(positionAction.UnparentBeforeRouting);
Assert.Equal(RuntimeTeleportHookPhase.None, positionAction.HookPhase);
}
[Fact]
public void LocalTeleportContinuationDrivesItsOwnAuthoredPlacementLifecycle()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 6UL);
const uint guid = 0x70023100u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, positionSequence: 2, teleportSequence: 1, forcePositionSequence: 0, positionX: 40f);
Assert.True(lifetime.TryApplyPosition(
update, isLocalPlayer: true, null, null, true, null,
out PositionTimestampDisposition disposition, out _, out _));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
RuntimeInitialCreateExecutionStatus status = lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt pending);
Assert.Equal(RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement, status);
Assert.Equal(default, pending);
// The snapshot's own position field is refreshed even before the
// physics placement resolves; the derived cell residency is not.
Assert.Equal(40f, canonical.Snapshot.Position!.Value.PositionX);
Assert.Equal(Cell, canonical.FullCellId);
RuntimeEntityKey key = canonical.Key!.Value;
Assert.True(lifetime.InitialCreateExecution
.TryGetPendingContinuationRoute(key, out RuntimeAuthoritativePositionRoute route));
Assert.Equal(RuntimeAuthoritativePositionDisposition.SetPositionSimple, route.Disposition);
CompletePendingContinuationPlacement(lifetime, key, route);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
RuntimeInitialCreateExecutedAction positionAction = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
positionAction.PositionDisposition);
// Round 4 R4-8: local teleport route trace flags - ZeroVelocity,
// CONSTRAIN-AFTER (retail: TeleportPlayer runs first, ConstrainTo
// second), the AfterPositionOperation teleport hook, and unparent
// (a received Position always unsets parent).
Assert.True(positionAction.ZeroVelocity);
Assert.Equal(RuntimePositionConstrainPhase.AfterPositionOperation, positionAction.ConstrainPhase);
Assert.Equal(RuntimeTeleportHookPhase.AfterPositionOperation, positionAction.HookPhase);
Assert.True(positionAction.UnparentBeforeRouting);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership().ActiveOperationCount);
}
[Fact]
public void RemoteNearContactPositionInterpolatesAfterInitialResidency()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 7UL);
const uint guid = 0x70023200u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, positionSequence: 2, teleportSequence: 0, forcePositionSequence: 0, positionX: 25f, isGrounded: true);
Assert.True(lifetime.TryApplyPosition(
update, isLocalPlayer: false, null, null, false, null,
out PositionTimestampDisposition disposition, out _, out _));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
var inputs = new RuntimeInitialCreateExecutionInputs(
UsePositionFromServer: false, PlayerDistance: 10f);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, inputs);
RuntimeInitialCreateExecutedAction positionAction = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.Interpolate,
positionAction.PositionDisposition);
Assert.Equal(25f, canonical.Snapshot.Position!.Value.PositionX);
Assert.Equal(Cell, canonical.FullCellId);
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership().ActiveOperationCount);
// Round 4 R4-8: remote near route trace flag - constrain-after
// (remote route: MoveOrTeleport runs first, ConstrainTo second).
Assert.Equal(RuntimePositionConstrainPhase.AfterPositionOperation, positionAction.ConstrainPhase);
}
// Round 4 R4-7: wire IsGrounded=true disagrees with the body's OWN
// contact bit (forced false) - the local ordinary interpolate gate must
// follow the WIRE fact, not the body, per Round 3 A3.
[Fact]
public void LocalOrdinaryPositionRouteFollowsWireGroundedTrueWhenBodyContactIsFalse()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 97UL);
const uint guid = 0x70039000u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
// Force the body's OWN contact bit to DISAGREE with the wire fact
// below - the route must ignore this.
ForceContact(canonical, inContact: false);
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, positionSequence: 2, teleportSequence: 0, forcePositionSequence: 0, positionX: 15f,
isGrounded: true);
Assert.True(lifetime.TryApplyPosition(
update, isLocalPlayer: true, null, null, false, null,
out PositionTimestampDisposition disposition, out _, out _));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
var inputs = new RuntimeInitialCreateExecutionInputs(
UsePositionFromServer: true, PlayerDistance: 0f);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, inputs);
RuntimeInitialCreateExecutedAction positionAction = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.Interpolate,
positionAction.PositionDisposition);
}
// Round 4 R4-7: wire IsGrounded=false disagrees with the body's OWN
// contact bit (forced true) - the remote effective-contact gate must
// follow the WIRE fact, not the body.
[Fact]
public void RemotePositionRouteFollowsWireGroundedFalseWhenBodyContactIsTrue()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 98UL);
const uint guid = 0x70039100u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
// Force the body's OWN contact bit to DISAGREE with the wire fact
// below - the route must ignore this.
ForceContact(canonical, inContact: true);
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, positionSequence: 2, teleportSequence: 0, forcePositionSequence: 0, positionX: 25f,
isGrounded: false);
Assert.True(lifetime.TryApplyPosition(
update, isLocalPlayer: false, null, null, false, null,
out PositionTimestampDisposition disposition, out _, out _));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
var inputs = new RuntimeInitialCreateExecutionInputs(
UsePositionFromServer: false, PlayerDistance: 10f);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, inputs);
RuntimeInitialCreateExecutedAction positionAction = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.NoPositionOperation,
positionAction.PositionDisposition);
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership().ActiveOperationCount);
}
[Fact]
public void RemoteFarPositionStopsInterpolatingAndRunsSetPositionSimple()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 8UL);
const uint guid = 0x70023300u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, positionSequence: 2, teleportSequence: 0, forcePositionSequence: 0, positionX: 25f);
Assert.True(lifetime.TryApplyPosition(
update, isLocalPlayer: false, null, null, false, null,
out PositionTimestampDisposition disposition, out _, out _));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
var inputs = new RuntimeInitialCreateExecutionInputs(
UsePositionFromServer: false, PlayerDistance: 200f);
RuntimeInitialCreateExecutionStatus status = lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, inputs, out _);
Assert.Equal(RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement, status);
RuntimeEntityKey key = canonical.Key!.Value;
Assert.True(lifetime.InitialCreateExecution
.TryGetPendingContinuationRoute(key, out RuntimeAuthoritativePositionRoute route));
Assert.Equal(RuntimeAuthoritativePositionDisposition.SetPositionSimple, route.Disposition);
Assert.True(route.StopInterpolating);
CompletePendingContinuationPlacement(lifetime, key, route);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, inputs);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
RuntimeInitialCreateExecutedAction positionAction = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
// Round 4 R4-8: remote far route trace flags - StopInterpolating
// and constrain-after.
Assert.True(positionAction.StopInterpolating);
Assert.Equal(RuntimePositionConstrainPhase.AfterPositionOperation, positionAction.ConstrainPhase);
}
[Fact]
public void ParentedInitialCreateNeverRunsAWorldPlacementThroughTheExecutor()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 9UL);
const uint parentGuid = 0x70024100u;
const uint guid = 0x70024000u;
_ = lifetime.RegisterEntity(Spawn(parentGuid, 1));
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
Assert.False(lease.Placement.IsValid);
Assert.Equal(RuntimeAuthoritativePositionDisposition.AwaitFreshPosition, lease.Route.Disposition);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
Assert.Equal(0u, receipt.FullCellId);
Assert.Equal(RuntimeTeleportHookPhase.None, receipt.TeleportHookPhase);
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership().ActiveOperationCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
[Fact]
public void ForcePositionContinuationRecordsSetPositionSimpleWithPreservedHeadingAndNoParentClear()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 45UL);
const uint guid = 0x7002F700u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
// Round 4 R4-12: give the entity a parent attachment BEFORE the
// ForcePosition update arrives, to structurally pin the merge
// body's deliberate combined shape - retail's FORCE_POSITION Gate A
// returns before CPhysicsObj::unset_parent ever runs, so the
// parent attachment must survive alongside the newly-applied
// Position after this drain.
const uint parentGuid = 0x7002F701u;
const uint parentLocation = 3u;
Assert.True(lifetime.Entities.TryCommitParent(
guid, parentGuid, parentLocation, placementId: 0u, positionSequence: 1,
out WorldSession.EntitySpawn parented));
lifetime.Entities.RefreshSnapshot(canonical, parented);
// isLocalPlayer + a fresh FORCE_POSITION_TS (1, newer than the
// seeded 0) + an EQUAL teleport sequence (0 == gate's current 0)
// blips immediately per PhysicsTimestampGate.TryAcceptPositionEvent.
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, positionSequence: 2, teleportSequence: 0, forcePositionSequence: 1, positionX: 40f);
Assert.True(lifetime.TryApplyPosition(
update, isLocalPlayer: true, null, null, false, null,
out PositionTimestampDisposition disposition, out _, out _));
Assert.Equal(PositionTimestampDisposition.ForcePosition, disposition);
RuntimeInitialCreateExecutionStatus status = lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out _);
Assert.Equal(RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement, status);
RuntimeEntityKey key = canonical.Key!.Value;
Assert.True(lifetime.InitialCreateExecution
.TryGetPendingContinuationRoute(key, out RuntimeAuthoritativePositionRoute route));
Assert.Equal(RuntimeAuthoritativePositionDisposition.SetPositionSimple, route.Disposition);
CompletePendingContinuationPlacement(lifetime, key, route);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
RuntimeInitialCreateExecutedAction positionAction = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
positionAction.PositionDisposition);
Assert.True(positionAction.PreserveHeading);
Assert.True(positionAction.SendPositionImmediately);
Assert.False(positionAction.StopInterpolating);
Assert.False(positionAction.ZeroVelocity);
Assert.Equal(RuntimePositionConstrainPhase.None, positionAction.ConstrainPhase);
// The FORCE_POSITION branch precedes unset_parent in retail's
// MoveOrTeleport - no parent clearing here.
Assert.False(positionAction.UnparentBeforeRouting);
// Round 4 R4-12: structurally pin the deliberate combined shape -
// Position AND the pre-existing Parent attachment are BOTH
// non-null after this merge.
Assert.NotNull(canonical.Snapshot.Position);
Assert.Equal(40f, canonical.Snapshot.Position!.Value.PositionX);
Assert.Equal(parentGuid, canonical.Snapshot.ParentGuid);
Assert.Equal(parentLocation, canonical.Snapshot.ParentLocation);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership().ActiveOperationCount);
}
[Fact]
public void MissileFlaggedEntityPositionContinuationClassifiesToProjectileAuthoritativeOperationKind()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 46UL);
const uint guid = 0x7002F800u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, missile: true),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
Assert.Equal(
RuntimeSetPositionOperationKind.ProjectileAuthoritative,
lease.Route.OperationKind);
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
// Already resident + far distance forces SetPositionSimple (the
// same "remote-shaped" branch a Projectile entity kind shares with
// Remote - the classifier has no Projectile-specific branch, only a
// Projectile-specific OperationKind mapping).
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, positionSequence: 2, teleportSequence: 0, forcePositionSequence: 0, positionX: 25f);
Assert.True(lifetime.TryApplyPosition(
update, isLocalPlayer: false, null, null, false, null,
out PositionTimestampDisposition disposition, out _, out _));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
var inputs = new RuntimeInitialCreateExecutionInputs(
UsePositionFromServer: false, PlayerDistance: 200f);
RuntimeInitialCreateExecutionStatus status = lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, inputs, out _);
Assert.Equal(RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement, status);
RuntimeEntityKey key = canonical.Key!.Value;
Assert.True(lifetime.InitialCreateExecution
.TryGetPendingContinuationRoute(key, out RuntimeAuthoritativePositionRoute route));
Assert.Equal(RuntimeAuthoritativePositionDisposition.SetPositionSimple, route.Disposition);
Assert.Equal(RuntimeSetPositionOperationKind.ProjectileAuthoritative, route.OperationKind);
Assert.True(route.StopInterpolating);
CompletePendingContinuationPlacement(lifetime, key, route);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, inputs);
Assert.NotEmpty(receipt.Trace);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership().ActiveOperationCount);
// Round 4 R4-8: projectile route trace flags - same StopInterpolating
// + constrain-after shape as the remote-far branch (the classifier
// has no Projectile-specific branch, only an OperationKind mapping).
RuntimeInitialCreateExecutedAction positionAction = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
Assert.True(positionAction.StopInterpolating);
Assert.Equal(RuntimePositionConstrainPhase.AfterPositionOperation, positionAction.ConstrainPhase);
}
// Contrasts with ParentedInitialCreateNeverRunsAWorldPlacementThroughTheExecutor:
// the PickedUp residence kind (no parent AND no position) is the OTHER
// branch that classifies to AwaitFreshPosition at Begin/Create time (see
// RuntimeInitialCreateResidenceState.Begin's residence-kind switch).
// NOTE (mandated-case coverage gap): a standalone Position CONTINUATION
// (as opposed to this initial-lease route) can never itself classify to
// AwaitFreshPosition - RuntimeAuthoritativePositionRouteClassifier.
// ClassifyAcceptedPosition (the only classifier ApplyPositionAction ever
// calls) has no branch that returns AwaitFreshPosition; that disposition
// is produced exclusively by ClassifyCreate (Parented/PickedUp) and
// ClassifyLeaveWorld (neither of which ApplyPositionAction calls). A
// parented/picked entity's raw Position wire events are retained as
// Position continuations exactly like any other entity's and are
// classified with the SAME Remote/LocalPlayer logic once drained - the
// "picked" or "parented" fact plays no role in that classification. This
// is a structural property of the current classifier, not a gap in this
// test suite; the reachable form of "AwaitFreshPosition for a
// parented/picked entity" is the INITIAL LEASE route exercised here and
// by ParentedInitialCreateNeverRunsAWorldPlacementThroughTheExecutor.
[Fact]
public void PickedUpInitialCreateNeverRunsAWorldPlacementThroughTheExecutor()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 47UL);
const uint guid = 0x7002F900u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
Assert.False(lease.Placement.IsValid);
Assert.Equal(RuntimeAuthoritativePositionDisposition.AwaitFreshPosition, lease.Route.Disposition);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
Assert.Equal(0u, receipt.FullCellId);
Assert.Equal(RuntimeTeleportHookPhase.None, receipt.TeleportHookPhase);
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership().ActiveOperationCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
// Round 3 B10 (execution-time-rejected retained Position): admission
// ACCEPTED (disposition Apply - POSITION_TS genuinely advanced), but
// execution-time classification REJECTS on a nonfinite derived distance.
// The gate-consumed timestamps must still land in the snapshot; no pose
// ever applies.
[Fact]
public void ExecutionTimeRejectedPositionStampsRetainedTimestampsWithoutPoseApplication()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 48UL);
const uint guid = 0x7002FA00u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
Assert.Equal(Cell, canonical.FullCellId);
float originalPositionX = canonical.Snapshot.Position!.Value.PositionX;
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, positionSequence: 2, teleportSequence: 0, forcePositionSequence: 0, positionX: 25f);
Assert.True(lifetime.TryApplyPosition(
update, isLocalPlayer: false, null, null, false, null,
out PositionTimestampDisposition disposition, out _, out _));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
var inputs = new RuntimeInitialCreateExecutionInputs(
UsePositionFromServer: false, PlayerDistance: float.NaN);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, inputs);
RuntimeInitialCreateExecutedAction positionAction = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.RejectedData,
positionAction.PositionDisposition);
// The retained Position/Teleport/ForcePosition channels the gate
// actually moved land in the snapshot ...
Assert.Equal(2, canonical.Snapshot.Physics!.Value.Timestamps.Position);
Assert.Equal(0, canonical.Snapshot.Physics!.Value.Timestamps.Teleport);
Assert.Equal(0, canonical.Snapshot.Physics!.Value.Timestamps.ForcePosition);
// ... but no pose ever applies.
Assert.Equal(originalPositionX, canonical.Snapshot.Position!.Value.PositionX);
Assert.Equal(Cell, canonical.FullCellId);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
// ---------------------------------------------------------------
// E. Missing-parent raw deferred-create replay
// ---------------------------------------------------------------
[Fact]
public void MissingParentReplayConsumesExactAdmissionIdAndRegistersChildThroughCanonicalRoute()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 10UL);
const uint parentGuid = 0x70025100u;
const uint childGuid = 0x70025000u;
RuntimeEntityRegistrationResult deferred = lifetime.RegisterEntityWithInitialResidence(
Spawn(childGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false);
Assert.True(deferred.DeferredForParent);
Assert.Equal(1, lifetime.CaptureOwnership().DeferredParentCreateCount);
RuntimeEntityRecord parent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parent,
out RuntimeInitialCreateResidenceLease parentLease));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, parent, parentLease.Token, NoContact);
Assert.Equal(1, receipt.ReplayedDeferredChildCount);
RuntimeInitialCreateExecutedAction replay = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.DeferredChildReplay);
Assert.Equal(RuntimeDeferredChildReplayOutcome.Registered, replay.DeferredChildOutcome);
Assert.Equal(0, lifetime.CaptureOwnership().DeferredParentCreateCount);
Assert.True(lifetime.Entities.TryGetActive(childGuid, out RuntimeEntityRecord child));
Assert.True(lifetime.TryGetInitialCreateResidence(child, out _));
}
[Fact]
public void StaleAdmissionIdCannotConsumeAReplacementQueuedAfterThePeek()
{
var parents = new ParentAttachmentState();
const uint parentGuid = 0x70025200u;
WorldSession.EntitySpawn spawn = Spawn(
0x70025300u, 1, includePosition: false, parentGuid: parentGuid);
parents.EnqueueDeferredCreate(spawn, isLocalPlayer: false);
Assert.True(parents.TryPeekDeferredCreate(parentGuid, out DeferredParentCreate stale));
parents.Clear();
parents.EnqueueDeferredCreate(spawn, isLocalPlayer: false);
Assert.True(parents.TryPeekDeferredCreate(parentGuid, out DeferredParentCreate replacement));
Assert.NotEqual(stale.AdmissionId, replacement.AdmissionId);
Assert.False(parents.ConsumeDeferredCreate(parentGuid, stale));
Assert.Equal(1, parents.DeferredCreateCount);
Assert.True(parents.ConsumeDeferredCreate(parentGuid, replacement));
Assert.Equal(0, parents.DeferredCreateCount);
}
// Round 3 B7: multiple children queued behind the SAME missing parent
// all replay atomically through the executor's whole-bucket detach
// (ParentAttachmentState.DetachDeferredCreates), in FIFO admission
// order, exercised through the canonical RunInitialTail path rather than
// against ParentAttachmentState directly.
[Fact]
public void MultipleChildrenQueuedBehindOneMissingParentReplayInFifoOrderThroughTheExecutor()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 32UL);
const uint parentGuid = 0x7002E100u;
const uint firstChildGuid = 0x7002E000u;
const uint secondChildGuid = 0x7002E001u;
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(firstChildGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(secondChildGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
Assert.Equal(2, lifetime.CaptureOwnership().DeferredParentCreateCount);
RuntimeEntityRecord parent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parent, out RuntimeInitialCreateResidenceLease parentLease));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, parent, parentLease.Token, NoContact);
Assert.Equal(2, receipt.ReplayedDeferredChildCount);
RuntimeInitialCreateExecutedAction[] replays = receipt.Trace
.Where(static a => a.Kind is RuntimeInitialCreateExecutedActionKind.DeferredChildReplay)
.ToArray();
Assert.Equal(2, replays.Length);
Assert.All(
replays,
static a => Assert.Equal(RuntimeDeferredChildReplayOutcome.Registered, a.DeferredChildOutcome));
Assert.Equal(0, lifetime.CaptureOwnership().DeferredParentCreateCount);
Assert.True(lifetime.Entities.TryGetActive(firstChildGuid, out _));
Assert.True(lifetime.Entities.TryGetActive(secondChildGuid, out _));
}
// Round 4 R4-1: one child's registration THROWING must not strand the
// remaining siblings or escape Execute as an exception. Three children
// queued behind one missing parent; the SECOND child's own registration
// callback is made to throw (via a reflection-swapped
// _registerDeferredChild delegate - the standard fault-injection
// technique this file already uses for private-state pokes, e.g.
// SetCompletedAdoptionRevision). The third child must still register.
[Fact]
public void DeferredChildReplayContainsOneChildsThrowingRegistrationAndContinuesWithSiblings()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 92UL);
const uint parentGuid = 0x70035000u;
const uint firstChildGuid = 0x70035001u;
const uint secondChildGuid = 0x70035002u;
const uint thirdChildGuid = 0x70035003u;
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(firstChildGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(secondChildGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(thirdChildGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
Assert.Equal(3, lifetime.CaptureOwnership().DeferredParentCreateCount);
WrapDeferredChildRegistration(lifetime, (original, spawn, isLocalPlayer) =>
{
if (spawn.Guid == secondChildGuid)
{
throw new InvalidOperationException(
"Injected R4-1 containment test failure.");
}
return original(spawn, isLocalPlayer);
});
RuntimeEntityRecord parent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parent, out RuntimeInitialCreateResidenceLease parentLease));
// No exception escapes Execute - it returns a typed status.
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, parent, parentLease.Token, NoContact);
Assert.Equal(3, receipt.ReplayedDeferredChildCount);
RuntimeInitialCreateExecutedAction[] replays = receipt.Trace
.Where(static a => a.Kind is RuntimeInitialCreateExecutedActionKind.DeferredChildReplay)
.ToArray();
Assert.Equal(3, replays.Length);
Assert.Equal(RuntimeDeferredChildReplayOutcome.Registered, replays[0].DeferredChildOutcome);
Assert.Equal(RuntimeDeferredChildReplayOutcome.Rejected, replays[1].DeferredChildOutcome);
Assert.Equal(RuntimeDeferredChildReplayOutcome.Registered, replays[2].DeferredChildOutcome);
Assert.True(lifetime.Entities.TryGetActive(firstChildGuid, out _));
Assert.False(lifetime.Entities.TryGetActive(secondChildGuid, out _));
Assert.True(lifetime.Entities.TryGetActive(thirdChildGuid, out _));
Assert.Equal(0, lifetime.CaptureOwnership().DeferredParentCreateCount);
// Round 5 R5-3: the contained exception is recorded on the
// observable failure surface, not silently swallowed.
Assert.Equal(1, lifetime.CaptureOwnership().ReplayFailureCount);
Assert.True(lifetime.CaptureOwnership().HasLastReplayFailure);
}
// Round 4 R4-1: a mid-loop abandonment (the parent entity is no longer
// current) must RESTORE the unprocessed remainder rather than
// permanently destroy it. Two children queued behind one missing
// parent; the FIRST child's registration callback reentrantly deletes
// the PARENT (simulating a synchronous observer reaction fired from
// within registration). The second child's raw Create must be back in
// the deferred bucket afterward (observable via ContainsDeferredCreate),
// and every ledger must still converge.
[Fact]
public void DeferredChildReplayRestoresTheUnprocessedRemainderWhenTheParentIsDeletedReentrantlyMidReplay()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 93UL);
const uint parentGuid = 0x70036000u;
const uint firstChildGuid = 0x70036001u;
const uint secondChildGuid = 0x70036002u;
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(firstChildGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(secondChildGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
Assert.Equal(2, lifetime.CaptureOwnership().DeferredParentCreateCount);
WrapDeferredChildRegistration(lifetime, (original, spawn, isLocalPlayer) =>
{
RuntimeEntityRegistrationResult result = original(spawn, isLocalPlayer);
if (spawn.Guid == firstChildGuid)
{
Assert.True(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(parentGuid, 1),
isLocalPlayer: false,
removeRetainedObject: true,
out RuntimeEntityDeleteAcceptance acceptance));
lifetime.CompleteAcceptedDelete(acceptance);
}
return result;
});
RuntimeEntityRecord parent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parent, out RuntimeInitialCreateResidenceLease parentLease));
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedAuthority,
lifetime.InitialCreateExecution.Execute(
parent, parentLease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt receipt));
Assert.Equal(default, receipt);
// Child 1 legitimately registered before the reentrant delete
// fired; child 2's raw Create is restored to the deferred bucket
// rather than lost.
Assert.True(lifetime.Entities.TryGetActive(firstChildGuid, out RuntimeEntityRecord firstChild));
Assert.True(lifetime.Entities.ParentAttachments.ContainsDeferredCreate(secondChildGuid, 1));
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
// Only the PARENT's own residence/progress was touched by this
// abandoned Execute call (matching the existing
// DeleteDuringDeferredChildReplayAbandonsExecutionWithoutResurrection
// precedent) - child 1 is a distinct entity the deferred replay
// legitimately registered through the canonical route before the
// reentrant delete happened; its own (never executed) residence
// lease correctly remains open, accounting for the sole surviving
// lease count.
Assert.Equal(1, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.True(lifetime.TryGetInitialCreateResidence(firstChild, out _));
}
/// <summary>
/// Round 4 R4-1 test-only fault injection: swaps the executor's private
/// <c>_registerDeferredChild</c> delegate for one that wraps the
/// original, matching this file's existing reflection-based
/// private-state pokes (e.g. SetCompletedAdoptionRevision below).
/// </summary>
private static void WrapDeferredChildRegistration(
RuntimeEntityObjectLifetime lifetime,
Func<
Func<WorldSession.EntitySpawn, bool, RuntimeEntityRegistrationResult>,
WorldSession.EntitySpawn,
bool,
RuntimeEntityRegistrationResult> wrapper)
{
Type executorType = typeof(RuntimeInitialCreateContinuationExecutor);
System.Reflection.FieldInfo field = executorType.GetField(
"_registerDeferredChild",
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!;
var original = (Func<WorldSession.EntitySpawn, bool, RuntimeEntityRegistrationResult>)
field.GetValue(lifetime.InitialCreateExecution)!;
Func<WorldSession.EntitySpawn, bool, RuntimeEntityRegistrationResult> wrapped =
(spawn, isLocalPlayer) => wrapper(original, spawn, isLocalPlayer);
field.SetValue(lifetime.InitialCreateExecution, wrapped);
}
/// <summary>
/// Round 5 R5-1 test helper: both <c>RuntimeEntityObjectLifetime.
/// TryApplyParent</c> (line ~908) and <c>RegisterEntityCore</c>'s own
/// missing-parent gate divert an UNADDRESSABLE-at-admission parent
/// relation to a DIFFERENT mechanism entirely (the legacy unresolved-
/// wait queue, or a raw deferred Create) - neither ever reaches the
/// residence continuation FIFO this round's executor dispatch code
/// operates on. The scenarios this round's tests exercise (drain-time/
/// replay-time staleness) all require the parent to be ADDRESSABLE at
/// the moment of admission and only become stale/unaddressable
/// afterward - exactly the shape the original Round 3/4 test used.
/// This helper registers a real parent at
/// <paramref name="namedParentIncarnation"/>, admits the standalone
/// Parent continuation naming it, then removes the parent from the
/// ACTIVE directory directly (<c>RuntimeEntityDirectory.RemoveActive</c>)
/// rather than through the full DeleteObject-acceptance ceremony - a
/// real delete retains a permanent per-(guid,generation) teardown
/// tombstone (<c>RuntimeEntityDirectory.RetainTeardown</c>), which would
/// collide if a later test step re-registers the SAME guid at the SAME
/// incarnation (an invalid combination this scenario has no reason to
/// exercise - retail incarnations only ever increase). RemoveActive
/// makes the parent unaddressable for <c>TryGetActive</c> purposes
/// without that permanent marker, while the gate stays intact so a
/// LATER same-incarnation re-registration takes the codebase's own
/// existing "recovered CreateObject" ExistingGeneration path.
/// </summary>
private static void AdmitThenOrphanParentRelation(
RuntimeEntityObjectLifetime lifetime,
uint parentGuid,
uint childGuid,
ushort namedParentIncarnation,
uint parentLocation = 1u)
{
// includePosition:false - besides needing no physics engine, this
// also matters for the "same incarnation returns" scenario: a
// position-bearing retained snapshot would carry over through
// MergeUntimestampedCreate's Position=retained.Position copy when
// the SAME (guid, incarnation) is later re-registered on the
// "recovered CreateObject" ExistingGeneration path, reclassifying
// the recovered residence as TopLevel (SetPosition) instead of
// PickedUp (AwaitFreshPosition) and stalling RunToCompletion on an
// unrequested placement ack.
RuntimeEntityRecord parent = lifetime.RegisterEntity(
Spawn(parentGuid, namedParentIncarnation, includePosition: false)).Canonical!;
var parentUpdate = new ParentEvent.Parsed(
parentGuid,
childGuid,
ParentLocation: parentLocation,
PlacementId: 0u,
ParentInstanceSequence: namedParentIncarnation,
ChildPositionSequence: 2);
Assert.True(lifetime.TryApplyParent(parentUpdate, null, out _));
Assert.True(lifetime.Entities.RemoveActive(parent));
}
// Round 3 B9 revalidated a standalone Parent continuation's parent
// incarnation at EXECUTION time, not just admission time - admission
// succeeds while the parent is still active; the parent is then
// deleted before the drain reaches this continuation. Round 4 R4-5
// pinned the mismatch outcome as a DISCARD; Round 5 R5-1 OVERTURNS
// that with hard retail evidence (QueueBlobForObject, pseudo-C 92326;
// GUID-keyed CObjectMaint bucket, 271082-271088) - retail QUEUES a
// relation whose parent is unaddressable under the PARENT's guid and
// replays it when that guid is created; it never discards on this
// path. This test now covers R5-1's mandated "parent-returns -> queued
// relation applies exactly once" scenario end-to-end.
[Fact]
public void ParentContinuationRevalidatesLiveParentAtExecutionAndQueuesOnMismatchThenAppliesWhenTheParentArrives()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 33UL);
const uint parentGuid = 0x7002E200u;
const uint childGuid = 0x7002E300u;
_ = lifetime.RegisterEntity(Spawn(parentGuid, 1));
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(childGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
var parentUpdate = new ParentEvent.Parsed(
parentGuid,
childGuid,
ParentLocation: 1u,
PlacementId: 0u,
ParentInstanceSequence: 1,
ChildPositionSequence: 2);
// Admission succeeds - the parent is still active at this moment.
Assert.True(lifetime.TryApplyParent(parentUpdate, null, out _));
// The parent is deleted BEFORE the drain ever reaches this
// continuation - admission-time validation cannot see this.
Assert.True(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(parentGuid, 1),
isLocalPlayer: false,
removeRetainedObject: true,
out RuntimeEntityDeleteAcceptance acceptance));
lifetime.CompleteAcceptedDelete(acceptance);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
// Queued (retail-faithful), not discarded: the residence still
// converges cleanly and the child's own canonical record survives.
RuntimeInitialCreateExecutedAction parentAction = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Parent);
Assert.Equal(
RuntimeParentRelationOutcome.DeferredAwaitingParent,
parentAction.ParentRelationOutcome);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
Assert.True(lifetime.Entities.TryGetActive(childGuid, out _));
Assert.True(lifetime.Entities.ParentAttachments
.ContainsDeferredAcceptedRelation(childGuid, canonical.Key!.Value));
Assert.Null(canonical.Snapshot.ParentGuid);
}
// Round 5 R5-1 mandated: "parent-returns -> queued relation applies
// exactly once" (trace + attach), end-to-end. Uses
// AdmitThenOrphanParentRelation (RemoveActive, not a full
// DeleteObject-acceptance) specifically so the SAME (guid, incarnation)
// can validly reappear afterward through the codebase's own existing
// "recovered CreateObject" ExistingGeneration path, without an
// artificial teardown-tombstone collision the scenario has no reason
// to exercise.
[Fact]
public void DeferredAcceptedParentRelationAppliesExactlyOnceWhenTheSameParentIncarnationReturns()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 113UL);
const uint parentGuid = 0x70049000u;
const uint childGuid = 0x70049001u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(childGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
AdmitThenOrphanParentRelation(lifetime, parentGuid, childGuid, namedParentIncarnation: 1);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
RuntimeInitialCreateExecutedAction parentAction = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Parent);
Assert.Equal(RuntimeParentRelationOutcome.DeferredAwaitingParent, parentAction.ParentRelationOutcome);
Assert.True(lifetime.Entities.ParentAttachments
.ContainsDeferredAcceptedRelation(childGuid, canonical.Key!.Value));
// The SAME parent incarnation (1) reappears - the queued relation
// replays and applies exactly once.
RuntimeEntityRecord recreatedParent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
recreatedParent, out RuntimeInitialCreateResidenceLease parentLease));
RuntimeInitialCreateExecutionReceipt parentReceipt = RunToCompletion(
lifetime, recreatedParent, parentLease.Token, NoContact);
RuntimeInitialCreateExecutedAction replayAction = Assert.Single(
parentReceipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.ParentRelationReplay);
Assert.Equal(RuntimeParentRelationOutcome.Applied, replayAction.ParentRelationOutcome);
// Applied commits the SAME position-timestamp-only merge a live
// Parent continuation commits (already stamped at the relation's
// original drain, before it was queued) - it does not itself set
// ParentGuid/ParentLocation, matching
// StandaloneParentContinuationAppliesPositionTimestampOnlyMergeWithNoReDefer's
// established precedent that the actual attach commit is
// TryCommitParent's (App-layer EquippedChildRenderController's) job.
Assert.Equal(2, canonical.Snapshot.Physics!.Value.Timestamps.Position);
Assert.Null(canonical.Snapshot.ParentGuid);
Assert.False(lifetime.Entities.ParentAttachments
.ContainsDeferredAcceptedRelation(childGuid, canonical.Key!.Value));
Assert.Equal(0, lifetime.Entities.ParentAttachments.DeferredAcceptedRelationCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
// Round 5 R5-1 mandated: the arriving parent's incarnation is NEWER
// than the one the relation named - discard per Resolve's own rule
// (the current live parent supersedes the packet).
[Fact]
public void DeferredAcceptedParentRelationDiscardsWhenTheArrivingParentIsANewerIncarnationThanTheRelationNamed()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 104UL);
const uint parentGuid = 0x7003F000u;
const uint childGuid = 0x7003F001u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(childGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
// Admits the standalone Parent continuation while the parent IS
// addressable (required - see AdmitThenOrphanParentRelation's
// remarks), then deletes the parent so the child's OWN drain
// discovers it stale and queues the relation.
AdmitThenOrphanParentRelation(lifetime, parentGuid, childGuid, namedParentIncarnation: 1);
_ = RunToCompletion(lifetime, canonical, lease.Token, NoContact);
Assert.True(lifetime.Entities.ParentAttachments
.ContainsDeferredAcceptedRelation(childGuid, canonical.Key!.Value));
// Parent arrives directly at incarnation 2 - NEWER than the
// relation's named incarnation 1.
RuntimeEntityRecord parent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 2, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parent, out RuntimeInitialCreateResidenceLease parentLease));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, parent, parentLease.Token, NoContact);
RuntimeInitialCreateExecutedAction replayAction = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.ParentRelationReplay);
Assert.Equal(RuntimeParentRelationOutcome.DiscardedStaleParent, replayAction.ParentRelationOutcome);
Assert.False(lifetime.Entities.ParentAttachments
.ContainsDeferredAcceptedRelation(childGuid, canonical.Key!.Value));
Assert.Null(canonical.Snapshot.ParentGuid);
Assert.Equal(0, lifetime.Entities.ParentAttachments.DeferredAcceptedRelationCount);
}
// Round 5 R5-1 mandated: the arriving parent's incarnation is OLDER
// than the one the relation named - stays queued (wait), then applies
// on the NEXT matching incarnation. This scenario is not constructible
// through the normal admission ceremony: naming ParentInstanceSequence
// 5 in a Parent continuation requires the LIVE parent to already BE at
// instance 5 at admission time (TryApplyParent's own gate), which
// pins the parent's OWN PhysicsTimestampGate at 5 - a client can never
// subsequently see that SAME guid at an OLDER instance 3 afterward
// (retail incarnations are per-guid monotonic; the gate enforces it).
// This test instead enqueues the relation directly through
// ParentAttachmentState's own public API (the same technique the ABA
// test below uses) to isolate ApplyReplayedParentRelation's OWN
// incarnation-compare logic from that inapplicable precondition.
[Fact]
public void DeferredAcceptedParentRelationStaysQueuedWhenTheArrivingParentIsOlderThanTheRelationNamedAndAppliesOnTheNextMatchingIncarnation()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 105UL);
const uint parentGuid = 0x70040000u;
const uint childGuid = 0x70040001u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(childGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
_ = RunToCompletion(lifetime, canonical, lease.Token, NoContact);
var parentUpdate = new ParentEvent.Parsed(
parentGuid, childGuid, ParentLocation: 1u, PlacementId: 0u,
ParentInstanceSequence: 5, ChildPositionSequence: 2);
lifetime.Entities.ParentAttachments.EnqueueDeferredAcceptedRelation(
childGuid, canonical.Key!.Value, parentUpdate, null, default);
// Parent arrives at incarnation 3 - OLDER than the relation's
// named incarnation 5.
RuntimeEntityRecord parentAtThree = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 3, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parentAtThree, out RuntimeInitialCreateResidenceLease leaseThree));
RuntimeInitialCreateExecutionReceipt receiptThree = RunToCompletion(
lifetime, parentAtThree, leaseThree.Token, NoContact);
RuntimeInitialCreateExecutedAction replayThree = Assert.Single(
receiptThree.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.ParentRelationReplay);
Assert.Equal(RuntimeParentRelationOutcome.DeferredAwaitingParent, replayThree.ParentRelationOutcome);
Assert.True(lifetime.Entities.ParentAttachments
.ContainsDeferredAcceptedRelation(childGuid, canonical.Key!.Value));
// Parent recreated at incarnation 5 - now matches.
RuntimeEntityRecord parentAtFive = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 5, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parentAtFive, out RuntimeInitialCreateResidenceLease leaseFive));
RuntimeInitialCreateExecutionReceipt receiptFive = RunToCompletion(
lifetime, parentAtFive, leaseFive.Token, NoContact);
RuntimeInitialCreateExecutedAction replayFive = Assert.Single(
receiptFive.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.ParentRelationReplay);
Assert.Equal(RuntimeParentRelationOutcome.Applied, replayFive.ParentRelationOutcome);
// Applied never sets ParentGuid itself - see
// StandaloneParentContinuationAppliesPositionTimestampOnlyMergeWithNoReDefer's
// established precedent (the actual attach commit is
// TryCommitParent's job, out of scope for this residence drain).
Assert.Null(canonical.Snapshot.ParentGuid);
Assert.Equal(0, lifetime.Entities.ParentAttachments.DeferredAcceptedRelationCount);
}
// Round 5 R5-1 mandated: the child is deleted while its relation is
// still queued (parent never showed up) - the entry is cancelled and
// every ledger converges.
[Fact]
public void DeferredAcceptedParentRelationIsCancelledWhenTheChildIsDeletedWhileQueued()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 106UL);
const uint parentGuid = 0x70041000u;
const uint childGuid = 0x70041001u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(childGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
AdmitThenOrphanParentRelation(lifetime, parentGuid, childGuid, namedParentIncarnation: 1);
_ = RunToCompletion(lifetime, canonical, lease.Token, NoContact);
Assert.Equal(1, lifetime.Entities.ParentAttachments.DeferredAcceptedRelationCount);
Assert.True(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(childGuid, 1),
isLocalPlayer: false,
removeRetainedObject: true,
out RuntimeEntityDeleteAcceptance acceptance));
lifetime.CompleteAcceptedDelete(acceptance);
Assert.False(lifetime.Entities.ParentAttachments
.ContainsDeferredAcceptedRelation(childGuid, canonical.Key!.Value));
Assert.Equal(0, lifetime.Entities.ParentAttachments.DeferredAcceptedRelationCount);
}
// Round 5 R5-1 mandated: a full session reset while a relation is
// queued clears it and converges.
[Fact]
public void DeferredAcceptedParentRelationClearsOnSessionResetAndConverges()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 107UL);
const uint parentGuid = 0x70042000u;
const uint childGuid = 0x70042001u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(childGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
AdmitThenOrphanParentRelation(lifetime, parentGuid, childGuid, namedParentIncarnation: 1);
_ = RunToCompletion(lifetime, canonical, lease.Token, NoContact);
Assert.Equal(1, lifetime.Entities.ParentAttachments.DeferredAcceptedRelationCount);
IReadOnlyList<RuntimeEntityRecord> retirements = lifetime.BeginSessionClear();
Assert.Equal(0, lifetime.Entities.ParentAttachments.DeferredAcceptedRelationCount);
foreach (RuntimeEntityRecord record in retirements)
lifetime.CompleteSessionEntityRetirement(record);
Assert.True(lifetime.CompleteSessionClearIfConverged());
}
// Round 5 R5-1 mandated ABA coverage, tested directly against
// ParentAttachmentState (mirroring
// StaleAdmissionIdCannotConsumeAReplacementQueuedAfterThePeek's own
// direct-state style): a window token minted BEFORE a Clear() must not
// restore anything afterward, even though a NEW window for the same
// parent guid could otherwise reuse its Id space.
[Fact]
public void DeferredAcceptedParentRelationStaleWindowTokenCannotRestoreAfterClearAndRequeue()
{
var parents = new ParentAttachmentState();
const uint parentGuid = 0x70043000u;
const uint childGuid = 0x70043001u;
var childKey = new RuntimeEntityKey(1u, 1);
var parentUpdate = new ParentEvent.Parsed(
parentGuid, childGuid, ParentLocation: 1u, PlacementId: 0u,
ParentInstanceSequence: 1, ChildPositionSequence: 2);
parents.EnqueueDeferredAcceptedRelation(childGuid, childKey, parentUpdate, null, default);
ImmutableArray<DeferredAcceptedParentRelation> detached =
parents.DetachDeferredAcceptedRelations(parentGuid, out DeferredReplayWindowToken staleWindow);
DeferredAcceptedParentRelation stale = Assert.Single(detached);
// Reentrant Clear() (session reset) while the window is still open.
parents.Clear();
// A restore against the now-stale token must be a silent no-op.
parents.RestoreDeferredAcceptedRelations(staleWindow, [stale]);
Assert.Equal(0, parents.DeferredAcceptedRelationCount);
Assert.False(parents.ContainsDeferredAcceptedRelation(childGuid, childKey));
}
// Round 5 R5-1 mandated: the envelope's CreateParent stage, end-to-end,
// for the unaddressable-parent flavor (addressability-only, no
// incarnation to compare). RegisterEntityCore's OWN missing-parent gate
// (checked before PreviewCreateDisposition, for ANY beginInitialResidence
// call) diverts a same-incarnation update naming an UNADDRESSABLE parent
// to a raw deferred Create entirely, bypassing the envelope/CreateParent
// stage - so, exactly like the standalone Parent continuation, this
// scenario requires the parent to be ADDRESSABLE at the moment of
// admission and only orphaned afterward.
[Fact]
public void EnvelopeCreateParentQueuesForUnaddressableParentAndAppliesWhenTheParentArrives()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 108UL);
const uint parentGuid = 0x70044000u;
const uint childGuid = 0x70044001u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(childGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
// includePosition:false, matching AdmitThenOrphanParentRelation's own
// remarks - avoids both the physics-engine dependency and a
// position-bearing retained snapshot bleeding into the LATER
// same-incarnation "recovered CreateObject" merge below.
RuntimeEntityRecord orphanedParent = lifetime.RegisterEntity(
Spawn(parentGuid, 1, includePosition: false)).Canonical!;
WorldSession.EntitySpawn sameCreate = Spawn(
childGuid, 1, includePosition: false, positionSequence: 2, parentGuid: parentGuid);
PhysicsSpawnData physics = sameCreate.Physics!.Value;
sameCreate = sameCreate with
{
Physics = physics with
{
Timestamps = physics.Timestamps with { State = 2, Vector = 2 },
},
};
_ = lifetime.RegisterEntityWithInitialResidence(sameCreate, isLocalPlayer: false);
// The parent is now orphaned - removed from the active directory
// AFTER the envelope's CreateParent stage was admitted while it was
// still addressable. RemoveActive (not a full DeleteObject-acceptance)
// for the SAME reason AdmitThenOrphanParentRelation uses it: a real
// delete retains a permanent per-(guid,generation) teardown tombstone
// that would collide when the SAME incarnation is re-registered below.
Assert.True(lifetime.Entities.RemoveActive(orphanedParent));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
RuntimeInitialCreateExecutedAction createParentAction = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.CreateParent);
Assert.Equal(RuntimeParentRelationOutcome.DeferredAwaitingParent, createParentAction.ParentRelationOutcome);
Assert.True(lifetime.Entities.ParentAttachments
.ContainsDeferredAcceptedRelation(childGuid, canonical.Key!.Value));
RuntimeEntityRecord parent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parent, out RuntimeInitialCreateResidenceLease parentLease));
RuntimeInitialCreateExecutionReceipt parentReceipt = RunToCompletion(
lifetime, parent, parentLease.Token, NoContact);
RuntimeInitialCreateExecutedAction replayAction = Assert.Single(
parentReceipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.ParentRelationReplay);
Assert.Equal(RuntimeParentRelationOutcome.Applied, replayAction.ParentRelationOutcome);
// Applied never sets ParentGuid itself - see
// StandaloneParentContinuationAppliesPositionTimestampOnlyMergeWithNoReDefer's
// established precedent (the actual attach commit is
// TryCommitParent's job, out of scope for this residence drain).
Assert.Null(canonical.Snapshot.ParentGuid);
Assert.Equal(0, lifetime.Entities.ParentAttachments.DeferredAcceptedRelationCount);
}
// ---------------------------------------------------------------
// Round 5 R5-2: cancellation-aware detach/restore window, both
// deferred buckets.
// ---------------------------------------------------------------
// Reviewer scenario (a) for the CREATES bucket: two children queued
// behind one missing parent; C1's own registration callback delivers
// DeleteObject(C2) then DeleteObject(parent) - C2 must NOT be restored,
// the recreated parent drains nothing, and every ledger converges.
[Fact]
public void DeferredChildReplayWindowFiltersASiblingDeletedMidReplayFromTheRestoredRemainder()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 109UL);
const uint parentGuid = 0x70045000u;
const uint firstChildGuid = 0x70045001u;
const uint secondChildGuid = 0x70045002u;
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(firstChildGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(secondChildGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
RuntimeEntityRecord? firstChild = null;
WrapDeferredChildRegistration(lifetime, (original, spawn, isLocalPlayer) =>
{
RuntimeEntityRegistrationResult result = original(spawn, isLocalPlayer);
if (spawn.Guid == firstChildGuid)
{
firstChild = result.Canonical;
// C2 was never individually registered (still a raw
// deferred blob) - its own delete legitimately reports
// false (TryAcceptDelete's known-object gate never
// accepted it), but CancelDeferredChildGeneration still
// runs unconditionally before that gate and records the
// window filter.
_ = lifetime.TryAcceptDelete(
new DeleteObject.Parsed(secondChildGuid, 1), isLocalPlayer: false,
removeRetainedObject: true, out _);
Assert.True(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(parentGuid, 1), isLocalPlayer: false,
removeRetainedObject: true, out RuntimeEntityDeleteAcceptance parentAcceptance));
lifetime.CompleteAcceptedDelete(parentAcceptance);
}
return result;
});
RuntimeEntityRecord parent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 1, includePosition: false), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parent, out RuntimeInitialCreateResidenceLease parentLease));
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedAuthority,
lifetime.InitialCreateExecution.Execute(
parent, parentLease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt receipt));
Assert.Equal(default, receipt);
Assert.True(lifetime.Entities.TryGetActive(firstChildGuid, out _));
Assert.False(lifetime.Entities.ParentAttachments.ContainsDeferredCreate(secondChildGuid, 1));
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
Assert.Equal(0, lifetime.Entities.ParentAttachments.DeferredCreateCount);
// C1 was itself admitted as its own ("Parented") initial-residence
// registration - a REAL entity, not a raw blob - and that lease is
// independent of the parentGuid-keyed deferred-create window this
// test targets. Drain it too so every ledger genuinely converges
// (round5-fixes.md's own "ledgers converge" wording), rather than
// leaving an unrelated open lease that has nothing to do with the
// window/filter mechanism under test.
Assert.NotNull(firstChild);
Assert.True(lifetime.TryGetInitialCreateResidence(
firstChild!, out RuntimeInitialCreateResidenceLease firstChildLease));
_ = RunToCompletion(lifetime, firstChild!, firstChildLease.Token, NoContact);
RuntimeEntityRecord recreatedParent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 2, includePosition: false), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
recreatedParent, out RuntimeInitialCreateResidenceLease recreatedLease));
RuntimeInitialCreateExecutionReceipt recreatedReceipt = RunToCompletion(
lifetime, recreatedParent, recreatedLease.Token, NoContact);
Assert.Equal(0, recreatedReceipt.ReplayedDeferredChildCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
}
// Reviewer scenario (b) for the CREATES bucket: C1's callback drives a
// full session clear - restore no-ops, DeferredParentCreateCount stays
// 0, IsConverged holds after teardown.
[Fact]
public void DeferredChildReplayWindowReleasesSilentlyWhenSessionClearFiresMidReplay()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 110UL);
const uint parentGuid = 0x70046000u;
const uint firstChildGuid = 0x70046001u;
const uint secondChildGuid = 0x70046002u;
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(firstChildGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(secondChildGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
WrapDeferredChildRegistration(lifetime, (original, spawn, isLocalPlayer) =>
{
RuntimeEntityRegistrationResult result = original(spawn, isLocalPlayer);
if (spawn.Guid == firstChildGuid)
{
IReadOnlyList<RuntimeEntityRecord> retirements = lifetime.BeginSessionClear();
foreach (RuntimeEntityRecord record in retirements)
lifetime.CompleteSessionEntityRetirement(record);
}
return result;
});
RuntimeEntityRecord parent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 1, includePosition: false), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parent, out RuntimeInitialCreateResidenceLease parentLease));
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedAuthority,
lifetime.InitialCreateExecution.Execute(
parent, parentLease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt receipt));
Assert.Equal(default, receipt);
Assert.Equal(0, lifetime.CaptureOwnership().DeferredParentCreateCount);
Assert.True(lifetime.CompleteSessionClearIfConverged());
}
// Reviewer scenario (a), relation-queue flavor: two queued relations
// for the same parent; C1's own attach-commit publish delivers
// DeleteObject(C2) then DeleteObject(parent) - C2's relation must NOT
// be restored.
[Fact]
public void DeferredAcceptedRelationReplayWindowFiltersASiblingRelationDeletedMidReplay()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 111UL);
const uint parentGuid = 0x70047000u;
const uint firstChildGuid = 0x70047001u;
const uint secondChildGuid = 0x70047002u;
RuntimeEntityRecord firstChild = lifetime
.RegisterEntityWithInitialResidence(
Spawn(firstChildGuid, 1, includePosition: false), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(firstChild, out RuntimeInitialCreateResidenceLease firstLease));
RuntimeEntityRecord secondChild = lifetime
.RegisterEntityWithInitialResidence(
Spawn(secondChildGuid, 1, includePosition: false), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(secondChild, out RuntimeInitialCreateResidenceLease secondLease));
// Both continuations admit while the FIRST parent incarnation is
// addressable, then the parent is orphaned once for both.
// includePosition:false + RemoveActive (not a full DeleteObject-
// acceptance) for the same reason AdmitThenOrphanParentRelation uses
// them: a real delete retains a permanent per-(guid,generation)
// teardown tombstone and a position-bearing retained snapshot would
// bleed into the SAME-incarnation "recovered CreateObject" merge
// when parentGuid is re-registered at incarnation 1 again below.
RuntimeEntityRecord orphanedParent = lifetime.RegisterEntity(
Spawn(parentGuid, 1, includePosition: false)).Canonical!;
var firstParentUpdate = new ParentEvent.Parsed(
parentGuid, firstChildGuid, ParentLocation: 1u, PlacementId: 0u,
ParentInstanceSequence: 1, ChildPositionSequence: 2);
Assert.True(lifetime.TryApplyParent(firstParentUpdate, null, out _));
var secondParentUpdate = new ParentEvent.Parsed(
parentGuid, secondChildGuid, ParentLocation: 1u, PlacementId: 0u,
ParentInstanceSequence: 1, ChildPositionSequence: 2);
Assert.True(lifetime.TryApplyParent(secondParentUpdate, null, out _));
Assert.True(lifetime.Entities.RemoveActive(orphanedParent));
_ = RunToCompletion(lifetime, firstChild, firstLease.Token, NoContact);
_ = RunToCompletion(lifetime, secondChild, secondLease.Token, NoContact);
Assert.Equal(2, lifetime.Entities.ParentAttachments.DeferredAcceptedRelationCount);
using IDisposable subscription = lifetime.Events.Subscribe(new EntityObserver(delta =>
{
if (delta.Change is not RuntimeEntityChange.Updated
|| delta.Entity.Identity.ServerGuid != firstChildGuid)
{
return;
}
Assert.True(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(secondChildGuid, 1), isLocalPlayer: false,
removeRetainedObject: true, out RuntimeEntityDeleteAcceptance secondAcceptance));
lifetime.CompleteAcceptedDelete(secondAcceptance);
Assert.True(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(parentGuid, 1), isLocalPlayer: false,
removeRetainedObject: true, out RuntimeEntityDeleteAcceptance parentAcceptance));
lifetime.CompleteAcceptedDelete(parentAcceptance);
}));
RuntimeEntityRecord parent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 1, includePosition: false), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parent, out RuntimeInitialCreateResidenceLease parentLease));
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedAuthority,
lifetime.InitialCreateExecution.Execute(
parent, parentLease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt receipt));
Assert.Equal(default, receipt);
Assert.False(lifetime.Entities.ParentAttachments
.ContainsDeferredAcceptedRelation(secondChildGuid, secondChild.Key!.Value));
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
// Reviewer scenario (b), relation-queue flavor: C1's own attach-commit
// publish drives a full session clear - restore no-ops for C2's
// relation, converges.
[Fact]
public void DeferredAcceptedRelationReplayWindowReleasesSilentlyWhenSessionClearFiresMidReplay()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 112UL);
const uint parentGuid = 0x70048000u;
const uint firstChildGuid = 0x70048001u;
const uint secondChildGuid = 0x70048002u;
RuntimeEntityRecord firstChild = lifetime
.RegisterEntityWithInitialResidence(
Spawn(firstChildGuid, 1, includePosition: false), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(firstChild, out RuntimeInitialCreateResidenceLease firstLease));
RuntimeEntityRecord secondChild = lifetime
.RegisterEntityWithInitialResidence(
Spawn(secondChildGuid, 1, includePosition: false), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(secondChild, out RuntimeInitialCreateResidenceLease secondLease));
// includePosition:false + RemoveActive (not a full DeleteObject-
// acceptance) for the same reason AdmitThenOrphanParentRelation uses
// them: a real delete retains a permanent per-(guid,generation)
// teardown tombstone and a position-bearing retained snapshot would
// bleed into the SAME-incarnation "recovered CreateObject" merge
// when parentGuid is re-registered at incarnation 1 again below -
// and the reentrant BeginSessionClear below retires every currently
// active record (including the recreated parent), which would
// collide with a stale tombstone left by a full delete here.
RuntimeEntityRecord orphanedParent = lifetime.RegisterEntity(
Spawn(parentGuid, 1, includePosition: false)).Canonical!;
var firstParentUpdate = new ParentEvent.Parsed(
parentGuid, firstChildGuid, ParentLocation: 1u, PlacementId: 0u,
ParentInstanceSequence: 1, ChildPositionSequence: 2);
Assert.True(lifetime.TryApplyParent(firstParentUpdate, null, out _));
var secondParentUpdate = new ParentEvent.Parsed(
parentGuid, secondChildGuid, ParentLocation: 1u, PlacementId: 0u,
ParentInstanceSequence: 1, ChildPositionSequence: 2);
Assert.True(lifetime.TryApplyParent(secondParentUpdate, null, out _));
Assert.True(lifetime.Entities.RemoveActive(orphanedParent));
_ = RunToCompletion(lifetime, firstChild, firstLease.Token, NoContact);
_ = RunToCompletion(lifetime, secondChild, secondLease.Token, NoContact);
using IDisposable subscription = lifetime.Events.Subscribe(new EntityObserver(delta =>
{
if (delta.Change is not RuntimeEntityChange.Updated
|| delta.Entity.Identity.ServerGuid != firstChildGuid)
{
return;
}
IReadOnlyList<RuntimeEntityRecord> retirements = lifetime.BeginSessionClear();
foreach (RuntimeEntityRecord record in retirements)
lifetime.CompleteSessionEntityRetirement(record);
}));
RuntimeEntityRecord parent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 1, includePosition: false), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parent, out RuntimeInitialCreateResidenceLease parentLease));
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedAuthority,
lifetime.InitialCreateExecution.Execute(
parent, parentLease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt receipt));
Assert.Equal(default, receipt);
Assert.Equal(0, lifetime.Entities.ParentAttachments.DeferredAcceptedRelationCount);
Assert.True(lifetime.CompleteSessionClearIfConverged());
}
// Round 3 E-matrix (a)+(d): TRUE ParentAttachmentState semantics, verified
// by reading the source before writing this test. ParentAttachmentState.
// DeleteGeneration(parentGuid, oldIncarnation) - called from
// TryAcceptDelete when the PARENT is what's being deleted - only ever
// touches the _unresolvedByChild ParentEvent-relation queue (via
// FilterParentCandidates, keeping relations addressed to a STRICTLY
// newer parent incarnation). It never touches _deferredCreatesByParent,
// the raw-netblob bucket ReplayDeferredChildren/DetachDeferredCreates
// actually replays - that bucket is keyed ONLY by parent GUID, with no
// per-incarnation filtering at all (PhysicsAttachment carries no parent
// instance sequence to filter by). So a child queued while parent
// incarnation 1 was missing survives the parent's own delete untouched,
// and DOES replay against a same-GUID recreated incarnation 2 - this
// mirrors retail's own GUID-keyed netblob dispatch (see
// ReplayDeferredChildren's remarks: "a parent's own successful Create
// replays every blob queued waiting on ITS guid"), not a defect.
[Fact]
public void DeferredChildQueuedForDeletedParentIncarnationStillReplaysAgainstTheRecreatedParentGuid()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 50UL);
const uint parentGuid = 0x70030100u;
const uint childGuid = 0x70030000u;
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(childGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
Assert.Equal(1, lifetime.CaptureOwnership().DeferredParentCreateCount);
// Parent incarnation 1 arrives, then is deleted, WITHOUT ever
// completing a residence (simulating "gone before the child's
// replay").
RuntimeEntityRecord parentGen1 = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(parentGuid, 1),
isLocalPlayer: false,
removeRetainedObject: true,
out RuntimeEntityDeleteAcceptance acceptance));
lifetime.CompleteAcceptedDelete(acceptance);
Assert.False(lifetime.Entities.IsCurrent(parentGen1));
// The deferred child create survives the parent's delete untouched -
// the TRUE, verified behavior.
Assert.Equal(1, lifetime.CaptureOwnership().DeferredParentCreateCount);
// Parent GUID reused at a NEWER incarnation.
RuntimeEntityRecord parentGen2 = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 2, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parentGen2, out RuntimeInitialCreateResidenceLease parentLease));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, parentGen2, parentLease.Token, NoContact);
Assert.Equal(1, receipt.ReplayedDeferredChildCount);
Assert.Equal(0, lifetime.CaptureOwnership().DeferredParentCreateCount);
Assert.True(lifetime.Entities.TryGetActive(childGuid, out RuntimeEntityRecord child));
Assert.True(lifetime.TryGetInitialCreateResidence(child, out _));
}
// Round 3 E-matrix (b): a child's own exact Delete before the parent's
// replay cancels only that child (CancelDeferredChildGeneration's side
// effect runs even though TryAcceptDelete's overall return is false for
// a guid with no seeded gate - a purely-deferred child was never
// registered, so it never got one).
[Fact]
public void ChildExactDeleteBeforeParentReplayCancelsOnlyThatChild()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 51UL);
const uint parentGuid = 0x70030200u;
const uint firstChildGuid = 0x70030300u;
const uint secondChildGuid = 0x70030400u;
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(firstChildGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(secondChildGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
Assert.Equal(2, lifetime.CaptureOwnership().DeferredParentCreateCount);
// The purely-deferred child has no gate; TryAcceptDelete's overall
// gate-consuming path fails, but the deferred-create cancellation
// side effect at the top of the method already ran.
Assert.False(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(firstChildGuid, 1),
isLocalPlayer: false,
removeRetainedObject: true,
out _));
Assert.Equal(1, lifetime.CaptureOwnership().DeferredParentCreateCount);
RuntimeEntityRecord parent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parent, out RuntimeInitialCreateResidenceLease parentLease));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, parent, parentLease.Token, NoContact);
Assert.Equal(1, receipt.ReplayedDeferredChildCount);
Assert.False(lifetime.Entities.TryGetActive(firstChildGuid, out _));
Assert.True(lifetime.Entities.TryGetActive(secondChildGuid, out _));
Assert.Equal(0, lifetime.CaptureOwnership().DeferredParentCreateCount);
}
// Round 3 E-matrix (c): two raw Creates queued for the SAME child guid
// (instance 1 and instance 2, both waiting on the same missing parent).
// Deleting the child at instance 1 cancels only the older, same-or-older
// queued generation; the strictly-newer instance 2 survives and replays.
[Fact]
public void NewerDeferredChildGenerationSurvivesOlderGenerationCleanupAndReplays()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 52UL);
const uint parentGuid = 0x70030500u;
const uint childGuid = 0x70030600u;
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(childGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(childGuid, 2, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
Assert.Equal(2, lifetime.CaptureOwnership().DeferredParentCreateCount);
Assert.False(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(childGuid, 1),
isLocalPlayer: false,
removeRetainedObject: true,
out _));
Assert.Equal(1, lifetime.CaptureOwnership().DeferredParentCreateCount);
RuntimeEntityRecord parent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parent, out RuntimeInitialCreateResidenceLease parentLease));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, parent, parentLease.Token, NoContact);
Assert.Equal(1, receipt.ReplayedDeferredChildCount);
Assert.True(lifetime.Entities.TryGetActive(childGuid, out RuntimeEntityRecord child));
Assert.Equal(2, child.Incarnation);
Assert.Equal(0, lifetime.CaptureOwnership().DeferredParentCreateCount);
}
// Round 3 E-matrix (e): child GUID reuse across a COMPLETE lifecycle -
// register+replay+run-to-completion the first incarnation, delete it
// fully (gate removed), then reuse the SAME guid for a fresh incarnation
// that is AGAIN deferred (different missing parent) and replays cleanly
// with no leakage from the torn-down first generation.
[Fact]
public void ChildGuidReuseAfterFullLifecycleReplaysCleanlyWithoutStaleState()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 53UL);
const uint firstParentGuid = 0x70030700u;
const uint secondParentGuid = 0x70030800u;
const uint childGuid = 0x70030900u;
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(childGuid, 1, includePosition: false, parentGuid: firstParentGuid),
isLocalPlayer: false)
.DeferredForParent);
RuntimeEntityRecord firstParent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(firstParentGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
firstParent, out RuntimeInitialCreateResidenceLease firstParentLease));
_ = RunToCompletion(lifetime, firstParent, firstParentLease.Token, NoContact);
Assert.True(lifetime.Entities.TryGetActive(childGuid, out RuntimeEntityRecord firstChild));
Assert.True(lifetime.TryGetInitialCreateResidence(firstChild, out RuntimeInitialCreateResidenceLease childLease));
_ = RunToCompletion(lifetime, firstChild, childLease.Token, NoContact);
// Fully delete the first incarnation (its gate is removed too).
Assert.True(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(childGuid, 1),
isLocalPlayer: false,
removeRetainedObject: true,
out RuntimeEntityDeleteAcceptance deleteAcceptance));
lifetime.CompleteAcceptedDelete(deleteAcceptance);
Assert.False(lifetime.Entities.TryGetActive(childGuid, out _));
// The SAME guid reused for a fresh incarnation, deferred behind a
// DIFFERENT missing parent.
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(childGuid, 2, includePosition: false, parentGuid: secondParentGuid),
isLocalPlayer: false)
.DeferredForParent);
Assert.Equal(1, lifetime.CaptureOwnership().DeferredParentCreateCount);
RuntimeEntityRecord secondParent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(secondParentGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
secondParent, out RuntimeInitialCreateResidenceLease secondParentLease));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, secondParent, secondParentLease.Token, NoContact);
Assert.Equal(1, receipt.ReplayedDeferredChildCount);
Assert.True(lifetime.Entities.TryGetActive(childGuid, out RuntimeEntityRecord reusedChild));
Assert.Equal(2, reusedChild.Incarnation);
Assert.Equal(0, lifetime.CaptureOwnership().DeferredParentCreateCount);
}
// Round 3 E-matrix (f): a full session reset before the missing parent
// ever arrives clears the deferred-create bucket to zero.
[Fact]
public void SessionResetBeforeParentArrivalClearsDeferredBucket()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 54UL);
const uint parentGuid = 0x70030A00u;
const uint childGuid = 0x70030B00u;
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(childGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
Assert.Equal(1, lifetime.CaptureOwnership().DeferredParentCreateCount);
IReadOnlyList<RuntimeEntityRecord> retirements = lifetime.BeginSessionClear();
Assert.Equal(0, lifetime.CaptureOwnership().DeferredParentCreateCount);
foreach (RuntimeEntityRecord record in retirements)
lifetime.CompleteSessionEntityRetirement(record);
Assert.True(lifetime.CompleteSessionClearIfConverged());
}
// Round 3 E-matrix (g): a full session reset triggered REENTRANTLY from
// inside the deferred-child replay's own Registered callback. The
// parent's own in-flight Execute() must abandon cleanly rather than
// resurrect anything, and the session-clear transaction itself must
// still converge once its own retirements are drained.
[Fact]
public void SessionResetFromWithinReplayDrivenCallbackConverges()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 55UL);
const uint parentGuid = 0x70030C00u;
const uint childGuid = 0x70030D00u;
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(childGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
RuntimeEntityRecord parent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parent, out RuntimeInitialCreateResidenceLease parentLease));
IReadOnlyList<RuntimeEntityRecord>? retirements = null;
using IDisposable subscription = lifetime.Events.Subscribe(new EntityObserver(delta =>
{
if (delta.Change is not RuntimeEntityChange.Registered
|| delta.Entity.Identity.ServerGuid != childGuid
|| retirements is not null)
{
return;
}
retirements = lifetime.BeginSessionClear();
}));
RuntimeInitialCreateExecutionStatus status = lifetime.InitialCreateExecution.Execute(
parent, parentLease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt receipt);
// The parent's own in-flight execution is no longer current once the
// reentrant reset retires everything - a typed abandonment, not a
// resurrection or an escaping exception.
Assert.Equal(RuntimeInitialCreateExecutionStatus.RejectedAuthority, status);
Assert.Equal(default, receipt);
Assert.NotNull(retirements);
foreach (RuntimeEntityRecord record in retirements!)
lifetime.CompleteSessionEntityRetirement(record);
Assert.True(lifetime.CompleteSessionClearIfConverged());
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().DeferredParentCreateCount);
}
// Round 3 E-matrix (h): instance sequence zero is a normal retail
// timestamp, not an empty sentinel (see ParentAttachmentState's own
// remarks on CancelDeferredChildGeneration) - a deferred child at
// incarnation 0 must replay exactly like any other incarnation.
[Fact]
public void InstanceSequenceZeroChildReplaysCorrectly()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 56UL);
const uint parentGuid = 0x70030E00u;
const uint childGuid = 0x70030F00u;
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(childGuid, 0, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
Assert.Equal(1, lifetime.CaptureOwnership().DeferredParentCreateCount);
RuntimeEntityRecord parent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parent, out RuntimeInitialCreateResidenceLease parentLease));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, parent, parentLease.Token, NoContact);
Assert.Equal(1, receipt.ReplayedDeferredChildCount);
Assert.Equal(0, lifetime.CaptureOwnership().DeferredParentCreateCount);
Assert.True(lifetime.Entities.TryGetActive(childGuid, out RuntimeEntityRecord child));
Assert.Equal(0, child.Incarnation);
Assert.True(lifetime.TryGetInitialCreateResidence(child, out _));
}
// ---------------------------------------------------------------
// F. Failure boundaries.
//
// (b) delete from the hook-emission window (after InitialAdoption,
// before the FIFO drain) has NO reachable callback of its own:
// RunInitialTail's Adopted phase (InitialAdoption) and HookRecorded
// phase (the AfterEnterWorld teleport-hook trace entry) are both pure
// trace-only mutations with no event dispatch - there is no observer
// boundary between them. The ONLY publish inside RunInitialTail is
// ReplayDeferredChildren's per-child Registered event (the
// DeferredReplayed phase), which already has dedicated coverage:
// DeleteDuringDeferredChildReplayAbandonsExecutionWithoutResurrection
// (above, in section G) deletes reentrantly from exactly that callback
// and proves the abandonment converges without resurrecting anything.
// Adding a second, structurally-identical test here would only
// duplicate that coverage; the closest reachable boundary already has a
// test.
// ---------------------------------------------------------------
[Fact]
public void DeleteFromObserverBeforeInitialAdoptionAbandonsFirstExecuteCleanly()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 60UL);
const uint guid = 0x70031000u;
// RegisterEntityCore's InitializeAcceptedCreateResidence (Begin())
// runs BEFORE PublishEntity(Registered, ...) - a real lease/token
// already exists by the time this observer fires, so it can be
// captured and later handed to Execute() to observe that FIRST
// call's own outcome directly, rather than merely proving no lease
// survives.
RuntimeEntityRecord? capturedCanonical = null;
RuntimeInitialCreateResidenceLease capturedLease = default;
using IDisposable subscription = lifetime.Events.Subscribe(new EntityObserver(delta =>
{
if (delta.Change is not RuntimeEntityChange.Registered
|| delta.Entity.Identity.ServerGuid != guid)
{
return;
}
Assert.True(lifetime.Entities.TryGetActive(guid, out RuntimeEntityRecord active));
capturedCanonical = active;
Assert.True(lifetime.TryGetInitialCreateResidence(active, out capturedLease));
// Reentrant delete BEFORE Execute() is ever called for this
// entity at all - before RunInitialTail's InitialAdoption phase,
// the very first thing an Execute() call would otherwise do.
Assert.True(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(guid, active.Incarnation),
isLocalPlayer: false,
removeRetainedObject: true,
out RuntimeEntityDeleteAcceptance acceptance));
lifetime.CompleteAcceptedDelete(acceptance);
}));
// The reentrant delete bumps the lifetime-mutation counter for this
// guid DURING the Registered publish, so RegisterEntityCore's own
// post-publish currency check reports this registration as
// superseded (Canonical: null in the returned result) - the record
// captured from inside the observer, above, is the one to assert
// against.
_ = lifetime.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false);
Assert.NotNull(capturedCanonical);
RuntimeEntityRecord canonical = capturedCanonical!;
Assert.False(lifetime.Entities.IsCurrent(canonical));
// TryAcceptDelete's own ForgetInitialCreateResidence already retired
// the residence reentrantly - nothing was ever adopted.
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
var observed = new List<RuntimeEntityChange>();
using IDisposable secondSubscription = lifetime.Events.Subscribe(
new EntityObserver(delta => observed.Add(delta.Change)));
// The FIRST (and only) Execute() call for this entity, using the
// token captured before the reentrant delete: the residence is
// already gone (RejectedToken, not RejectedAuthority - there was
// never a Progress to Abandon), publishes nothing new, and
// converges.
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedToken,
lifetime.InitialCreateExecution.Execute(
canonical, capturedLease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt receipt));
Assert.Equal(default, receipt);
Assert.Empty(observed);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
[Fact]
public void DeleteFromObserverBetweenFifoEntriesAppliesFirstOnlyAndConverges()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 61UL);
const uint guid = 0x70031100u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
var appearance = new ObjDescEvent.Parsed(
guid,
new CreateObject.ModelData(
0x09000002u,
Array.Empty<CreateObject.SubPaletteSwap>(),
Array.Empty<CreateObject.TextureChange>(),
Array.Empty<CreateObject.AnimPartChange>()),
InstanceSequence: 1,
ObjDescSequence: 2);
Assert.True(lifetime.TryApplyObjDesc(appearance, null, out _));
var vector = new VectorUpdate.Parsed(
guid, new Vector3(11f, 12f, 13f), Vector3.Zero, InstanceSequence: 1, VectorSequence: 2);
Assert.True(lifetime.TryApplyVector(vector, null, out _));
var observed = new List<RuntimeEntityChange>();
bool deletedFromObserver = false;
using IDisposable subscription = lifetime.Events.Subscribe(new EntityObserver(delta =>
{
observed.Add(delta.Change);
if (deletedFromObserver || delta.Change is not RuntimeEntityChange.Updated)
return;
deletedFromObserver = true;
// Between FIFO entry 1 (ObjDesc, already applied+published) and
// entry 2 (Vector, not yet reached) - delete reentrantly.
Assert.True(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(guid, canonical.Incarnation),
isLocalPlayer: false,
removeRetainedObject: true,
out RuntimeEntityDeleteAcceptance acceptance));
lifetime.CompleteAcceptedDelete(acceptance);
}));
RuntimeInitialCreateExecutionStatus status = lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt receipt);
Assert.Equal(RuntimeInitialCreateExecutionStatus.RejectedAuthority, status);
Assert.Equal(default, receipt);
// ObjDesc applied+published exactly once; Vector never applied
// (canonical's velocity stays at its pre-drain value); the delete's
// own Deleted is the only other observed event.
Assert.Equal(
[RuntimeEntityChange.Updated, RuntimeEntityChange.Deleted],
observed);
Assert.Equal(0x09000002u, canonical.Snapshot.BasePaletteId);
Assert.Null(canonical.Snapshot.Physics!.Value.Velocity);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
// A stale retry never resurrects anything or reapplies Vector.
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedToken,
lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt retryReceipt));
Assert.Equal(default, retryReceipt);
Assert.Null(canonical.Snapshot.Physics!.Value.Velocity);
}
// Round 3 F(d): an observer that throws during a publish must not
// corrupt the drain or escape Execute() - RuntimeEntityObjectEventStream.
// Dispatch contains each observer's exception per-call (RecordDispatchFailure)
// and continues to the next observer/pending item. Pin that actual,
// verified behavior here rather than assuming propagation.
[Fact]
public void ObserverThrowDuringPublishIsContainedAndDrainConvergesWithNoDuplicateRetry()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 62UL);
const uint guid = 0x70031200u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
var vector = new VectorUpdate.Parsed(
guid, Vector3.One, Vector3.Zero, InstanceSequence: 1, VectorSequence: 2);
Assert.True(lifetime.TryApplyVector(vector, null, out _));
using IDisposable subscription = lifetime.Events.Subscribe(new EntityObserver(delta =>
throw new InvalidOperationException("Deliberate observer failure for F(d).")));
long failuresBefore = lifetime.Events.DispatchFailureCount;
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
Assert.Contains(
RuntimeInitialCreateExecutedActionKind.Vector,
receipt.Trace.Select(static a => a.Kind));
Assert.Equal(Vector3.One, canonical.Snapshot.Physics!.Value.Velocity);
Assert.True(lifetime.Events.DispatchFailureCount > failuresBefore);
Assert.NotNull(lifetime.Events.LastDispatchFailure);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
// A stale retry after the (contained) throw does not duplicate any
// side effect.
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedToken,
lifetime.InitialCreateExecution.Execute(canonical, lease.Token, NoContact, out _));
Assert.Equal(Vector3.One, canonical.Snapshot.Physics!.Value.Velocity);
}
// ---------------------------------------------------------------
// G. Reentrancy
// ---------------------------------------------------------------
[Fact]
public void ReentrantExecuteForTheSameEntityFailsClosedRatherThanInterleaving()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 11UL);
const uint guid = 0x70026000u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
var reentrantStatuses = new List<RuntimeInitialCreateExecutionStatus>();
using IDisposable subscription = lifetime.Events.Subscribe(new EntityObserver(delta =>
{
if (delta.Change is not RuntimeEntityChange.Updated || reentrantStatuses.Count != 0)
return;
// Firing a nested Execute from inside an event observer while the
// outer Execute is still on the stack must fail closed.
reentrantStatuses.Add(lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out _));
}));
var vector = new VectorUpdate.Parsed(
guid, Vector3.One, Vector3.Zero, InstanceSequence: 1, VectorSequence: 2);
Assert.True(lifetime.TryApplyVector(vector, null, out _));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
Assert.Equal(
[RuntimeInitialCreateExecutionStatus.RejectedAuthority],
reentrantStatuses);
Assert.NotEmpty(receipt.Trace);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
[Fact]
public void DeleteDuringDeferredChildReplayAbandonsExecutionWithoutResurrection()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 12UL);
const uint parentGuid = 0x70026100u;
const uint childGuid = 0x70026200u;
Assert.True(lifetime.RegisterEntityWithInitialResidence(
Spawn(childGuid, 1, includePosition: false, parentGuid: parentGuid),
isLocalPlayer: false)
.DeferredForParent);
RuntimeEntityRecord parent = lifetime
.RegisterEntityWithInitialResidence(
Spawn(parentGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
parent,
out RuntimeInitialCreateResidenceLease parentLease));
using IDisposable subscription = lifetime.Events.Subscribe(new EntityObserver(delta =>
{
if (delta.Change is not RuntimeEntityChange.Registered
|| delta.Entity.Identity.ServerGuid != childGuid)
{
return;
}
// A reentrant delete of the PARENT itself, triggered from inside
// the child-registration callback the deferred replay drives.
Assert.True(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(parentGuid, parent.Incarnation),
isLocalPlayer: false,
removeRetainedObject: true,
out RuntimeEntityDeleteAcceptance acceptance));
lifetime.CompleteAcceptedDelete(acceptance);
}));
RuntimeInitialCreateExecutionStatus status = lifetime.InitialCreateExecution.Execute(
parent, parentLease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt receipt);
Assert.Equal(RuntimeInitialCreateExecutionStatus.RejectedAuthority, status);
Assert.Equal(default, receipt);
Assert.False(lifetime.Entities.IsCurrent(parent));
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
// Only the PARENT's own residence/progress was ever touched by this
// abandoned Execute call. The CHILD is a distinct entity the
// deferred replay legitimately registered through the canonical
// route before the reentrant delete happened - its own (never
// executed) residence lease correctly remains open; deleting the
// parent does not cascade-tear-down an unrelated child.
Assert.Equal(1, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.True(lifetime.Entities.TryGetActive(childGuid, out RuntimeEntityRecord child));
Assert.True(lifetime.TryGetInitialCreateResidence(child, out _));
}
// Round 3 B2 regression (reentrancy from a publish callback): a wire
// apply (TryApplyMotion) reentrantly enqueued from inside an EARLIER
// continuation's own publish must NOT retire the residence, and the
// continuation that was ALREADY queued before the drain even started
// (Vector) must still drain - proving the reentrant enqueue never
// displaces or skips the pre-existing FIFO tail.
[Fact]
public void WireApplyDuringDrainPublishDoesNotRetireResidenceAndPreExistingFifoStillDrains()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 63UL);
const uint guid = 0x70031300u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
var appearance = new ObjDescEvent.Parsed(
guid,
new CreateObject.ModelData(
0x0A000002u,
Array.Empty<CreateObject.SubPaletteSwap>(),
Array.Empty<CreateObject.TextureChange>(),
Array.Empty<CreateObject.AnimPartChange>()),
InstanceSequence: 1,
ObjDescSequence: 2);
Assert.True(lifetime.TryApplyObjDesc(appearance, null, out _));
var vector = new VectorUpdate.Parsed(
guid, new Vector3(21f, 22f, 23f), Vector3.Zero, InstanceSequence: 1, VectorSequence: 2);
Assert.True(lifetime.TryApplyVector(vector, null, out _));
var observed = new List<RuntimeEntityChange>();
bool reentered = false;
using IDisposable subscription = lifetime.Events.Subscribe(new EntityObserver(delta =>
{
observed.Add(delta.Change);
if (reentered || delta.Change is not RuntimeEntityChange.Updated)
return;
reentered = true;
var motion = new WorldSession.EntityMotionUpdate(
guid,
new CreateObject.ServerMotionState(0x3d, 0x11),
InstanceSequence: 1,
MovementSequence: 2,
ServerControlSequence: 1,
IsAutonomous: false);
Assert.True(lifetime.TryApplyMotion(motion, retainPayload: true, null, out _, out _));
}));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
// Vector (already queued before the drain started) drains BEFORE
// the reentrantly-enqueued Motion, and both drain exactly once.
Assert.Equal(
[
RuntimeInitialCreateExecutedActionKind.InitialAdoption,
RuntimeInitialCreateExecutedActionKind.ObjDesc,
RuntimeInitialCreateExecutedActionKind.Vector,
RuntimeInitialCreateExecutedActionKind.Movement,
],
receipt.Trace.Select(static a => a.Kind));
Assert.Equal(
[RuntimeEntityChange.Updated, RuntimeEntityChange.Updated, RuntimeEntityChange.Updated],
observed);
Assert.Equal(new Vector3(21f, 22f, 23f), canonical.Snapshot.Physics!.Value.Velocity);
Assert.Equal(new CreateObject.ServerMotionState(0x3d, 0x11), canonical.Snapshot.MotionState);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
[Fact]
public void RegisterDifferentEntityFromCallbackDuringDrainConvergesBothIndependently()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 64UL);
const uint firstGuid = 0x70031400u;
const uint secondGuid = 0x70031500u;
RuntimeEntityRecord first = lifetime
.RegisterEntityWithInitialResidence(
Spawn(firstGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
first, out RuntimeInitialCreateResidenceLease firstLease));
var vector = new VectorUpdate.Parsed(
firstGuid, Vector3.One, Vector3.Zero, InstanceSequence: 1, VectorSequence: 2);
Assert.True(lifetime.TryApplyVector(vector, null, out _));
RuntimeEntityRecord? secondCanonical = null;
using IDisposable subscription = lifetime.Events.Subscribe(new EntityObserver(delta =>
{
if (delta.Change is not RuntimeEntityChange.Updated
|| delta.Entity.Identity.ServerGuid != firstGuid
|| secondCanonical is not null)
{
return;
}
// A completely unrelated entity registered reentrantly, from
// inside the FIRST entity's own drain publish.
secondCanonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(secondGuid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
}));
RuntimeInitialCreateExecutionReceipt firstReceipt = RunToCompletion(
lifetime, first, firstLease.Token, NoContact);
Assert.NotEmpty(firstReceipt.Trace);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
Assert.NotNull(secondCanonical);
Assert.True(lifetime.Entities.IsCurrent(secondCanonical!));
Assert.True(lifetime.TryGetInitialCreateResidence(secondCanonical!, out RuntimeInitialCreateResidenceLease secondLease));
// The second entity's own independent residence still drains
// normally afterward.
RuntimeInitialCreateExecutionReceipt secondReceipt = RunToCompletion(
lifetime, secondCanonical!, secondLease.Token, NoContact);
Assert.Contains(
RuntimeInitialCreateExecutedActionKind.InitialAdoption,
secondReceipt.Trace.Select(static a => a.Kind));
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
// Round 3 G(c)/I2: recreating the SAME guid at a NEWER incarnation from
// a reentrant callback mid-drain. The old execution must abandon
// cleanly (its own progress+residence converge) with NO FIFO transfer
// to the new incarnation; the new incarnation is a completely
// independent, unaffected registration. This is the same scenario the
// task's "replacement convergence" item names - one test covers both.
[Fact]
public void RecreateSameGuidNewerIncarnationFromCallbackAbandonsOldExecutionWithoutFifoTransfer()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 65UL);
const uint guid = 0x70031600u;
RuntimeEntityRecord canonicalGen1 = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonicalGen1, out RuntimeInitialCreateResidenceLease lease));
var appearance = new ObjDescEvent.Parsed(
guid,
new CreateObject.ModelData(
0x0B000002u,
Array.Empty<CreateObject.SubPaletteSwap>(),
Array.Empty<CreateObject.TextureChange>(),
Array.Empty<CreateObject.AnimPartChange>()),
InstanceSequence: 1,
ObjDescSequence: 2);
Assert.True(lifetime.TryApplyObjDesc(appearance, null, out _));
var vector = new VectorUpdate.Parsed(
guid, new Vector3(31f, 32f, 33f), Vector3.Zero, InstanceSequence: 1, VectorSequence: 2);
Assert.True(lifetime.TryApplyVector(vector, null, out _));
bool recreated = false;
using IDisposable subscription = lifetime.Events.Subscribe(new EntityObserver(delta =>
{
if (recreated || delta.Change is not RuntimeEntityChange.Updated)
return;
recreated = true;
// Same guid, NEWER incarnation, from inside the FIRST
// continuation's (ObjDesc's) own publish - a bare Create with no
// residence, to isolate the replacement mechanics from any
// second drain.
_ = lifetime.RegisterEntity(Spawn(guid, 2, includePosition: false));
}));
RuntimeInitialCreateExecutionStatus status = lifetime.InitialCreateExecution.Execute(
canonicalGen1, lease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt receipt);
Assert.Equal(RuntimeInitialCreateExecutionStatus.RejectedAuthority, status);
Assert.Equal(default, receipt);
Assert.False(lifetime.Entities.IsCurrent(canonicalGen1));
// Old progress+residence fully converge.
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
// The new incarnation is unaffected: no residence pending (a bare
// RegisterEntity never begins one), and none of generation 1's
// FIFO (Vector, still undrained) ever transferred to it.
Assert.True(lifetime.Entities.TryGetActive(guid, out RuntimeEntityRecord canonicalGen2));
Assert.Equal(2, canonicalGen2.Incarnation);
Assert.False(lifetime.TryGetInitialCreateResidence(canonicalGen2, out _));
Assert.Null(canonicalGen2.Snapshot.Physics!.Value.Velocity);
}
// Round 3 G(d): Dispose() called reentrantly from a publish callback.
// Pin the ACTUAL observed behavior (verified by running this test)
// rather than assuming either containment or propagation.
[Fact]
public void DisposeFromCallbackDuringDrainConvergesTheCompleteLedger()
{
var lifetime = new RuntimeEntityObjectLifetime();
Bind(lifetime, 66UL);
const uint guid = 0x70031700u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
var vector = new VectorUpdate.Parsed(
guid, Vector3.One, Vector3.Zero, InstanceSequence: 1, VectorSequence: 2);
Assert.True(lifetime.TryApplyVector(vector, null, out _));
using IDisposable subscription = lifetime.Events.Subscribe(new EntityObserver(delta =>
{
if (delta.Change is RuntimeEntityChange.Updated)
lifetime.Dispose();
}));
RuntimeInitialCreateExecutionStatus status = lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt receipt);
// Dispose() reentrantly retires everything (BeginSessionClear ->
// teardown -> converge) before the outer Execute() call's own
// currency check runs again; that check sees a no-longer-current
// canonical and abandons in the SAME typed way any other reentrant
// teardown does. No invalid enumeration/exception escapes.
Assert.Equal(RuntimeInitialCreateExecutionStatus.RejectedAuthority, status);
Assert.Equal(default, receipt);
Assert.True(lifetime.CaptureOwnership().IsConverged);
}
// ---------------------------------------------------------------
// H. Saturation / stale-progress
// ---------------------------------------------------------------
[Fact]
public void StaleProgressLeaseIdIsDiscardedAndFailsClosedThenRetrySucceedsCleanly()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 13UL);
const uint guid = 0x70027000u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
RuntimeEntityKey key = canonical.Key!.Value;
// Directly plant a stale Progress entry under a LeaseId that does
// NOT match the current lease's token - the exact "an old
// incarnation's progress leaking into a reused key" scenario the
// contract requires Execute to discard and fail closed on for this
// one call, never silently reusing it.
PlantStaleProgress(lifetime, key, lease.Token.LeaseId + 1_000UL);
Assert.Equal(1, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedAuthority,
lifetime.InitialCreateExecution.Execute(canonical, lease.Token, NoContact, out _));
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
Assert.NotEmpty(receipt.Trace);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
private static void PlantStaleProgress(
RuntimeEntityObjectLifetime lifetime,
RuntimeEntityKey key,
ulong staleLeaseId)
{
Type progressType = typeof(RuntimeInitialCreateContinuationExecutor)
.GetNestedType("Progress", System.Reflection.BindingFlags.NonPublic)!;
object stale = System.Runtime.CompilerServices.RuntimeHelpers
.GetUninitializedObject(progressType);
System.Reflection.PropertyInfo leaseIdProperty = progressType.GetProperty(
"LeaseId",
System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.NonPublic)!;
leaseIdProperty.SetValue(stale, staleLeaseId);
System.Reflection.FieldInfo progressField = typeof(RuntimeInitialCreateContinuationExecutor)
.GetField(
"_progress",
System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.NonPublic)!;
var dictionary = (System.Collections.IDictionary)progressField.GetValue(
lifetime.InitialCreateExecution)!;
dictionary[key] = stale;
}
// Round 3 H (saturation): adoption Revision pinned at ulong.MaxValue -
// CanEnqueue's completed-entry branch requires
// "Revision < ulong.MaxValue", so a NEW continuation fails closed BEFORE
// any wire consumption; whatever was ALREADY committed before saturation
// still drains and converges normally through the executor.
[Fact]
public void AdoptionRevisionSaturationFailsClosedBeforeAnyNewContinuationCanEnqueue()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 70UL);
const uint guid = 0x70032000u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
var firstVector = new VectorUpdate.Parsed(
guid, Vector3.One, Vector3.Zero, InstanceSequence: 1, VectorSequence: 2);
Assert.True(lifetime.TryApplyVector(firstVector, null, out _));
Assert.Equal(
RuntimeInitialCreateResidenceCompletionStatus.Completed,
lifetime.InitialCreateResidences.Complete(canonical, lease.Token, out _));
SetCompletedAdoptionRevision(lifetime, canonical.Key!.Value, ulong.MaxValue);
var secondVector = new VectorUpdate.Parsed(
guid, new Vector3(2f, 2f, 2f), Vector3.Zero, InstanceSequence: 1, VectorSequence: 3);
Assert.False(lifetime.TryApplyVector(secondVector, null, out _));
Assert.True(lifetime.InitialCreateResidences.TryGetTransaction(
canonical, out RuntimeInitialCreateResidenceLease afterFailedEnqueue));
Assert.Single(afterFailedEnqueue.Continuations);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
// Only the ONE continuation committed before saturation ever drains -
// exactly once, never duplicated by the failed second attempt.
Assert.Equal(
[
RuntimeInitialCreateExecutedActionKind.InitialAdoption,
RuntimeInitialCreateExecutedActionKind.Vector,
],
receipt.Trace.Select(static a => a.Kind));
Assert.Equal(Vector3.One, canonical.Snapshot.Physics!.Value.Velocity);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
/// <summary>
/// Local copy of RuntimeInitialCreateResidenceStateTests'
/// SetCompletedAdoptionRevision reflection helper (per this task's
/// explicit instruction to reuse the pattern locally rather than share
/// test-project internals across files).
/// </summary>
private static void SetCompletedAdoptionRevision(
RuntimeEntityObjectLifetime lifetime,
RuntimeEntityKey key,
ulong revision)
{
Type stateType = typeof(RuntimeInitialCreateResidenceState);
System.Reflection.FieldInfo completedField = stateType.GetField(
"_completed",
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!;
var completed = (System.Collections.IDictionary)completedField.GetValue(
lifetime.InitialCreateResidences)!;
object entry = completed[key]!;
System.Reflection.PropertyInfo receiptProperty = entry.GetType().GetProperty(
"Receipt",
System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.NonPublic)!;
var receipt = (RuntimeInitialCreateResidenceReceipt)receiptProperty.GetValue(entry)!;
receiptProperty.SetValue(
entry,
receipt with { Adoption = receipt.Adoption with { Revision = revision } });
}
// ---------------------------------------------------------------
// External-race detection on the executor's staleness baseline
// (regression coverage for the narrowed pre-Complete
// AdvanceExecutorBaseline guard - Finding 1)
// ---------------------------------------------------------------
[Fact]
public void ExternalPositionAuthorityMutationWithNoPendingPlacementFailsClosedAndConverges()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 16UL);
const uint guid = 0x70029000u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
// Drive the residence directly to "completed + adopted" - the exact
// state RunInitialTail leaves behind before draining any
// continuation, with NO pending continuation placement. This is
// the only state in which the narrowed pre-Complete
// AdvanceExecutorBaseline guard does NOT resync the baseline
// before the next Complete() call runs.
Assert.Equal(
RuntimeInitialCreateResidenceCompletionStatus.Completed,
lifetime.InitialCreateResidences.Complete(canonical, lease.Token, out _));
Assert.True(lifetime.InitialCreateResidences.AdoptCompletedPlacement(canonical, lease.Token));
var observed = new List<RuntimeEntityChange>();
using IDisposable subscription = lifetime.Events.Subscribe(
new EntityObserver(delta => observed.Add(delta.Change)));
// External, non-executor mutation of one of the four baseline
// fields (never through AdvanceExecutorBaseline) - simulating a
// genuine race between Execute calls with nothing in flight. Before
// Finding 1's fix, the OLD unconditional pre-Complete rebaseline
// would have silently absorbed this and returned Completed instead.
lifetime.Entities.AdvancePositionAuthority(canonical);
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedAuthority,
lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt receipt));
Assert.Equal(default, receipt);
Assert.Empty(observed);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
[Fact]
public void ExternalFullCellMutationWithNoPendingPlacementFailsClosedAndConverges()
{
using RuntimeEntityObjectLifetime lifetime = new();
Bind(lifetime, 17UL);
const uint guid = 0x70029100u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
Assert.Equal(
RuntimeInitialCreateResidenceCompletionStatus.Completed,
lifetime.InitialCreateResidences.Complete(canonical, lease.Token, out _));
Assert.True(lifetime.InitialCreateResidences.AdoptCompletedPlacement(canonical, lease.Token));
var observed = new List<RuntimeEntityChange>();
using IDisposable subscription = lifetime.Events.Subscribe(
new EntityObserver(delta => observed.Add(delta.Change)));
// FullCellId is the second field the reviewer explicitly named -
// exercise it independently of PositionAuthorityVersion.
lifetime.Entities.SetFullCell(canonical, Cell, Landblock);
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedAuthority,
lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt receipt));
Assert.Equal(default, receipt);
Assert.Empty(observed);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
}
// Round 4 R4-2 / R4-9(b): the reviewer's exact scenario - external
// SetFullCell DURING the AwaitingContinuationPlacement window (after the
// continuation's OWN placement has been acknowledged but before the
// executor consumes it), not merely with no placement in flight at all
// (that is the PRECEDING test above). Before the fix, both failure arms
// in ResumePendingPlacement cleared PendingContinuationPlacement and
// abandoned WITHOUT ever forgetting the retained acknowledged-placement
// entry - HasRetainedCompletion for this key would then stay true
// forever, blocking EVERY later placement begin (the runtime-surface.md
// 3.1 deadlock this executor exists to resolve).
[Fact]
public void ExternalFullCellMutationDuringAwaitingContinuationPlacementForgetsThePendingPlacementAndAllowsAFreshOneToBegin()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 90UL);
const uint guid = 0x70034000u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, positionSequence: 2, teleportSequence: 1, forcePositionSequence: 0, positionX: 40f);
Assert.True(lifetime.TryApplyPosition(
update, isLocalPlayer: true, null, null, true, null, out _, out _, out _));
RuntimeInitialCreateExecutionStatus status = lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out _);
Assert.Equal(RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement, status);
RuntimeEntityKey key = canonical.Key!.Value;
Assert.True(lifetime.InitialCreateExecution
.TryGetPendingContinuationRoute(key, out RuntimeAuthoritativePositionRoute route));
// Drives the continuation's own placement through prepare/submit/
// acknowledge - the acknowledged projection is now retained under
// this exact token, ready for the NEXT Execute call to consume.
CompletePendingContinuationPlacement(lifetime, key, route);
// External mutation of the record's cell AFTER the continuation's
// own placement was acknowledged but BEFORE the executor consumes
// it - ResumePendingPlacement's projection/record agreement check
// must catch this exactly like Complete() does for the initial
// placement.
lifetime.Entities.SetFullCell(canonical, canonical.FullCellId + 999u, Landblock);
Assert.Equal(
RuntimeInitialCreateExecutionStatus.RejectedAuthority,
lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out RuntimeInitialCreateExecutionReceipt receipt));
Assert.Equal(default, receipt);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
RuntimeSetPositionOwnershipSnapshot physicsOwnership =
lifetime.Physics.SetPosition.CaptureOwnership();
Assert.Equal(0, physicsOwnership.ActiveOperationCount);
// The central R4-2 claim: no retained-completion leak.
Assert.Equal(0, physicsOwnership.AcknowledgedPlacementCompletionCount);
// A subsequent FRESH authored placement for the SAME entity can
// begin - proves HasRetainedCompletion no longer blocks it.
RuntimeEntityPlacementToken fresh = lifetime.Physics.SetPosition
.TryBeginExclusiveAuthoredPlacement(
canonical,
canonical.PositionAuthorityVersion,
RuntimeSetPositionOperationKind.LocalAuthoritative);
Assert.True(fresh.IsValid);
}
// Round 4 R4-9(a): guards B3's residence-retirement notification edge
// for a placement STILL IN FLIGHT (not yet acknowledged) - a THIRD
// PARTY (not the executor) discovers residence staleness through the
// residence's own host-facing TryGetTransaction query, which internally
// Retires the completed entry. That Retire must notify the executor so
// it forgets its OWN separately-tracked pending continuation placement
// token, not just the residence's initial-lease placement.
[Fact]
public void ThirdPartyTryGetTransactionRetireWhileAwaitingContinuationPlacementDiscardsExecutorProgressAndForgetsThePendingPlacement()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 91UL);
const uint guid = 0x70034100u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, positionSequence: 2, teleportSequence: 1, forcePositionSequence: 0, positionX: 40f);
Assert.True(lifetime.TryApplyPosition(
update, isLocalPlayer: true, null, null, true, null, out _, out _, out _));
RuntimeInitialCreateExecutionStatus status = lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out _);
Assert.Equal(RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement, status);
Assert.Equal(1, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
RuntimeEntityKey key = canonical.Key!.Value;
Assert.True(lifetime.InitialCreateExecution
.TryGetPendingContinuationPlacement(key, out RuntimeEntityPlacementToken pendingPlacement));
Assert.True(lifetime.Physics.SetPosition.IsPlacementCurrent(pendingPlacement));
// External, non-executor mutation of one of the four executor-
// tracked baseline fields - simulates something OTHER than the
// executor discovering staleness through a THIRD-PARTY call to the
// residence's own host-facing TryGetTransaction, never through
// Execute at all.
lifetime.Entities.AdvancePositionAuthority(canonical);
Assert.False(lifetime.InitialCreateResidences.TryGetTransaction(canonical, out _));
// B3's notification edge: the residence's own Retire (triggered by
// a THIRD PARTY, not the executor) must notify the executor so it
// forgets the pending continuation placement, not just the
// residence's own initial-lease placement.
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.False(lifetime.Physics.SetPosition.IsPlacementCurrent(pendingPlacement));
RuntimeSetPositionOwnershipSnapshot physicsOwnership =
lifetime.Physics.SetPosition.CaptureOwnership();
Assert.Equal(0, physicsOwnership.ActiveOperationCount);
Assert.Equal(0, physicsOwnership.PlacementCompletionWatchCount);
Assert.Equal(0, physicsOwnership.AcknowledgedPlacementCompletionCount);
// A subsequent FRESH authored placement for the SAME entity can
// begin - no lingering block from the forgotten pending placement.
RuntimeEntityPlacementToken fresh = lifetime.Physics.SetPosition
.TryBeginExclusiveAuthoredPlacement(
canonical,
canonical.PositionAuthorityVersion,
RuntimeSetPositionOperationKind.LocalAuthoritative);
Assert.True(fresh.IsValid);
}
// ---------------------------------------------------------------
// I. Ownership convergence
// ---------------------------------------------------------------
[Fact]
public void ResetDuringAwaitingContinuationPlacementConvergesEveryLedger()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 14UL);
const uint guid = 0x70028000u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, positionSequence: 2, teleportSequence: 1, forcePositionSequence: 0, positionX: 40f);
Assert.True(lifetime.TryApplyPosition(
update, isLocalPlayer: true, null, null, true, null, out _, out _, out _));
RuntimeInitialCreateExecutionStatus status = lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, NoContact, out _);
Assert.Equal(RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement, status);
Assert.Equal(1, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
IReadOnlyList<RuntimeEntityRecord> retirements = lifetime.BeginSessionClear();
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership().ActiveOperationCount);
foreach (RuntimeEntityRecord record in retirements)
lifetime.CompleteSessionEntityRetirement(record);
Assert.True(lifetime.CompleteSessionClearIfConverged());
}
[Fact]
public void DisposalConvergesTheCompleteOwnershipLedgerAfterASuccessfulExecution()
{
var lifetime = new RuntimeEntityObjectLifetime();
Bind(lifetime, 15UL);
const uint guid = 0x70028100u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, includePosition: false),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
_ = RunToCompletion(lifetime, canonical, lease.Token, NoContact);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
lifetime.Dispose();
Assert.True(lifetime.CaptureOwnership().IsConverged);
}
// ---------------------------------------------------------------
// J. Gap closures (B1 operation-slot contention).
// ---------------------------------------------------------------
// Round 3 B1-gap: a Position continuation's own placement-begin can fail
// on TRANSIENT operation-slot contention (another operation already owns
// this entity's SetPosition slot) rather than genuine staleness. The
// merge+publish must already have committed by that point (this is NOT
// an abandonment); a retry after the slot frees must complete WITHOUT
// re-running the merge or re-publishing.
[Fact]
public void OperationSlotContentionYieldsRetryableWithoutAbandoningThenRetryCompletesWithNoDuplicatePublish()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 80UL);
const uint guid = 0x70033000u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical, out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
Assert.Equal(Cell, canonical.FullCellId);
// Entry 1: a Vector continuation whose own publish is the injection
// point for occupying the entity's SetPosition slot from OUTSIDE the
// executor - by the time this fires, RunInitialTail's
// AdoptCompletedPlacement has already freed the slot the INITIAL
// placement held, so an external begin here genuinely succeeds.
var vector = new VectorUpdate.Parsed(
guid, Vector3.One, Vector3.Zero, InstanceSequence: 1, VectorSequence: 2);
Assert.True(lifetime.TryApplyVector(vector, null, out _));
// Entry 2: a far-distance Remote Position continuation that will
// require its OWN real SetPosition placement.
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, positionSequence: 2, teleportSequence: 0, forcePositionSequence: 0, positionX: 25f);
Assert.True(lifetime.TryApplyPosition(
update, isLocalPlayer: false, null, null, false, null,
out PositionTimestampDisposition disposition, out _, out _));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
RuntimeEntityPlacementToken unrelatedOccupant = default;
var observed = new List<RuntimeEntityChange>();
using IDisposable subscription = lifetime.Events.Subscribe(new EntityObserver(delta =>
{
observed.Add(delta.Change);
if (unrelatedOccupant.IsValid || delta.Change is not RuntimeEntityChange.Updated)
return;
unrelatedOccupant = lifetime.Physics.SetPosition.TryBeginExclusiveAuthoredPlacement(
canonical,
canonical.PositionAuthorityVersion,
RuntimeSetPositionOperationKind.RemoteAuthoritative);
Assert.True(unrelatedOccupant.IsValid);
}));
var inputs = new RuntimeInitialCreateExecutionInputs(
UsePositionFromServer: false, PlayerDistance: 200f);
RuntimeInitialCreateExecutionStatus status = lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, inputs, out RuntimeInitialCreateExecutionReceipt receipt);
// Contention, not staleness: retryable, residence/progress both
// still open (no abandonment).
Assert.Equal(RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement, status);
Assert.Equal(default, receipt);
Assert.Equal(1, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(1, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
// The merge+publish for BOTH entries already committed before the
// contention was even discovered.
Assert.Equal(
[RuntimeEntityChange.Updated, RuntimeEntityChange.Updated],
observed);
Assert.Equal(25f, canonical.Snapshot.Position!.Value.PositionX);
// Free the slot; retry.
lifetime.Physics.SetPosition.PublishCancellation(
lifetime.Physics.SetPosition.ForgetExactPlacement(unrelatedOccupant));
RuntimeInitialCreateExecutionStatus retryStatus = lifetime.InitialCreateExecution.Execute(
canonical, lease.Token, inputs, out RuntimeInitialCreateExecutionReceipt retryReceipt);
Assert.Equal(RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement, retryStatus);
Assert.Equal(default, retryReceipt);
// No re-merge, no re-publish on the contention retry itself.
Assert.Equal(
[RuntimeEntityChange.Updated, RuntimeEntityChange.Updated],
observed);
RuntimeEntityKey key = canonical.Key!.Value;
Assert.True(lifetime.InitialCreateExecution
.TryGetPendingContinuationRoute(key, out RuntimeAuthoritativePositionRoute route));
Assert.Equal(RuntimeAuthoritativePositionDisposition.SetPositionSimple, route.Disposition);
CompletePendingContinuationPlacement(lifetime, key, route);
RuntimeInitialCreateExecutionReceipt finalReceipt = RunToCompletion(
lifetime, canonical, lease.Token, inputs);
Assert.Equal(
[
RuntimeInitialCreateExecutedActionKind.InitialAdoption,
RuntimeInitialCreateExecutedActionKind.Vector,
RuntimeInitialCreateExecutedActionKind.Position,
],
finalReceipt.Trace.Select(static a => a.Kind));
// Still exactly two publishes across the entire contention +
// retry + completion sequence.
Assert.Equal(
[RuntimeEntityChange.Updated, RuntimeEntityChange.Updated],
observed);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.CaptureOwnership().InitialCreateExecutorProgressCount);
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership().ActiveOperationCount);
}
// ---------------------------------------------------------------
// F. C0-1: executor-completion bridge / C0-2: live-input binding
// ---------------------------------------------------------------
[Fact]
public void ExecutorCompletion_PublishesOnTheSamePlacementStreamCorrelatedWithTheFullReceipt()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 400UL);
const uint guid = 0x70024000u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
var observed = new List<RuntimePlacementProjectionSnapshot>();
using IDisposable subscription = lifetime.Events.SubscribePlacement(
new PlacementObserver(delta => observed.Add(delta.Placement)));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
RuntimePlacementProjectionSnapshot completion = Assert.Single(observed);
Assert.Equal(
RuntimePlacementProjectionKind.ExecutorCompleted,
completion.Kind);
Assert.Equal(canonical.Key, completion.Token.Entity);
Assert.True(lifetime.InitialCreateExecution.TryGetCompletionReceipt(
completion.Token,
out RuntimeInitialCreateExecutionReceipt correlated));
Assert.Equal(receipt, correlated);
// Acknowledge-only, exact-head, same as every other Kind.
Assert.Equal(1, lifetime.Physics.SetPosition.PendingProjectionCount);
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
completion.Token));
Assert.Equal(0, lifetime.Physics.SetPosition.PendingProjectionCount);
}
[Fact]
public void ExecutorCompletion_ObservedOnlyAfterAnyContinuationPlacementInFifoOrder()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 401UL);
const uint guid = 0x70024001u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
// A teleport-advanced Position continuation performs its OWN
// authored SetPosition - the continuation placement C0-1's contract
// says already flows through the channel unchanged.
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, positionSequence: 2, teleportSequence: 1,
forcePositionSequence: 0, positionX: 40f);
Assert.True(lifetime.TryApplyPosition(
update, isLocalPlayer: true, null, null, true, null,
out PositionTimestampDisposition disposition, out _, out _));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
var observed = new List<RuntimePlacementProjectionSnapshot>();
using IDisposable subscription = lifetime.Events.SubscribePlacement(
new PlacementObserver(delta => observed.Add(delta.Placement)));
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
Assert.Equal(
[
RuntimePlacementProjectionKind.Place,
RuntimePlacementProjectionKind.ExecutorCompleted,
],
observed.Select(static s => s.Kind));
Assert.Equal(canonical.Key, observed[0].Token.Entity);
Assert.True(lifetime.InitialCreateExecution.TryGetCompletionReceipt(
observed[1].Token,
out RuntimeInitialCreateExecutionReceipt correlated));
Assert.Equal(receipt, correlated);
// The continuation Place receipt was already acknowledged by
// RunToCompletion's own CompletePendingContinuationPlacement helper
// before Execute ever reached Completed - only the fresh
// ExecutorCompleted receipt is still outstanding.
Assert.Equal(1, lifetime.Physics.SetPosition.PendingProjectionCount);
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
observed[1].Token));
}
[Fact]
public void ExecutorCompletion_ReceiptIsReadableFromWithinTheSameSynchronousOnPlacementDispatch()
{
// F2: registration must happen BEFORE PublishPlacement's synchronous
// observer dispatch - a subscriber reading the correlation back from
// inside its OWN OnPlacement callback must already find it, not only
// after RunToCompletion returns.
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 410UL);
const uint guid = 0x70024010u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
RuntimeInitialCreateExecutionReceipt? observedFromInsideDispatch = null;
using IDisposable subscription = lifetime.Events.SubscribePlacement(
new PlacementObserver(delta =>
{
if (delta.Placement.Kind
is not RuntimePlacementProjectionKind.ExecutorCompleted)
{
return;
}
Assert.True(lifetime.InitialCreateExecution.TryGetCompletionReceipt(
delta.Placement.Token,
out RuntimeInitialCreateExecutionReceipt receipt));
observedFromInsideDispatch = receipt;
}));
RuntimeInitialCreateExecutionReceipt receiptReturned = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
Assert.NotNull(observedFromInsideDispatch);
Assert.Equal(receiptReturned, observedFromInsideDispatch!.Value);
}
[Fact]
public void ExecutorCompletion_ConvergenceLedgerCountsAnUnacknowledgedReceiptAsOutstandingDebtUntilAcknowledged()
{
// F2: PendingCompletionReceiptCount mirrors
// RuntimeSetPositionOwnershipSnapshot.PendingProjectionAcknowledgementCount's
// existing "unacknowledged receipt is outstanding debt" shape for
// the SAME underlying receipt stream - non-zero while unacknowledged,
// reaped to zero exactly on acknowledge (never before, never left
// dangling after).
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 411UL);
const uint guid = 0x70024011u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
Assert.Equal(
0,
lifetime.CaptureOwnership().PendingCompletionReceiptCount);
RunToCompletion(lifetime, canonical, lease.Token, NoContact);
Assert.Equal(
1,
lifetime.CaptureOwnership().PendingCompletionReceiptCount);
Assert.True(lifetime.Physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot completion));
Assert.Equal(
RuntimePlacementProjectionKind.ExecutorCompleted,
completion.Kind);
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
completion.Token));
Assert.Equal(
0,
lifetime.CaptureOwnership().PendingCompletionReceiptCount);
Assert.False(lifetime.InitialCreateExecution.TryGetCompletionReceipt(
completion.Token,
out _));
}
[Fact]
public void ExecutorCompletion_CorrelationEntryIsReapedByDiscardProgress()
{
// F2: DiscardProgress (reached via ForgetInitialCreateResidence in
// production) must reap this correlation cache too - it is exactly
// the kind of executor-introduced state that method already owns
// cleaning up. The drain already removed _progress[key] before
// publishing the completion (see ExecuteCore's Released case), so
// this proves DiscardProgress reaps _completionReceipts
// UNCONDITIONALLY, not only when _progress still tracks the key.
// Left deliberately UNACKNOWLEDGED in _pendingProjection (a single-
// entity lifetime, so there is no exact-head contention to worry
// about) - proving DiscardProgress reaps the correlation cache
// independently of the normal acknowledge path.
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 413UL);
const uint guid = 0x70024013u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
RunToCompletion(lifetime, canonical, lease.Token, NoContact);
Assert.Equal(
1,
lifetime.CaptureOwnership().PendingCompletionReceiptCount);
lifetime.InitialCreateExecution.DiscardProgress(canonical.Key!.Value);
Assert.Equal(
0,
lifetime.CaptureOwnership().PendingCompletionReceiptCount);
}
[Fact]
public void ExecutorCompletion_CorrelationEntryIsReapedByDiscardAll()
{
// F2: a full session clear (DiscardAll, reached via
// RuntimeInitialCreateResidenceState.Clear's call site) must never
// carry this correlation cache across a reset.
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 414UL);
const uint guid = 0x70024014u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
RunToCompletion(lifetime, canonical, lease.Token, NoContact);
Assert.Equal(
1,
lifetime.CaptureOwnership().PendingCompletionReceiptCount);
lifetime.InitialCreateExecution.DiscardAll();
Assert.Equal(
0,
lifetime.CaptureOwnership().PendingCompletionReceiptCount);
}
[Fact]
public void BindLiveInputs_DrivesClassificationFromTheBoundSourcesInsteadOfTheCallerStruct()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 402UL);
const uint guid = 0x70024002u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(lifetime, canonical);
CompleteInitialPlacement(lifetime, lease);
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, positionSequence: 2, teleportSequence: 0,
forcePositionSequence: 0, positionX: 15f, isGrounded: true);
Assert.True(lifetime.TryApplyPosition(
update, isLocalPlayer: true, null, null, false, null,
out PositionTimestampDisposition disposition, out _, out _));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
bool usePositionFromServer = true;
// Position 15 units from a local player parked far away (100 units)
// is irrelevant here (PlayerDistance only matters for the
// Remote/Projectile near/far branch, not LocalPlayer's own
// interpolate gate) - it exists purely to prove the DISTANCE source
// is read at all.
lifetime.InitialCreateExecution.BindLiveInputs(
() => usePositionFromServer,
() => new Vector3(115f, 20f, 7f));
// The caller-supplied struct says UsePositionFromServer:false - if
// the bound source is actually driving classification, retail's
// "UsePositionFromServer && wire-contact" local-ordinary gate must
// still interpolate.
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
lifetime, canonical, lease.Token, NoContact);
RuntimeInitialCreateExecutedAction positionAction = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.Interpolate,
positionAction.PositionDisposition);
// C0-1: Completing the first entity's drain also published its own
// ExecutorCompleted receipt on the SAME exact-head stream - it must
// be acknowledged before a SECOND entity's own placement receipt can
// ever become the head.
Assert.True(lifetime.Physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot firstCompletion));
Assert.Equal(
RuntimePlacementProjectionKind.ExecutorCompleted,
firstCompletion.Kind);
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
firstCompletion.Token));
// Flip the bound source off and confirm the SAME struct now takes
// the non-interpolating branch - proves it is read live, not cached
// at bind time.
usePositionFromServer = false;
const uint secondGuid = 0x70024003u;
RuntimeEntityRecord second = lifetime
.RegisterEntityWithInitialResidence(Spawn(secondGuid, 1), isLocalPlayer: true)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
second,
out RuntimeInitialCreateResidenceLease secondLease));
AttachDormantBody(lifetime, second);
CompleteInitialPlacement(lifetime, secondLease);
WorldSession.EntityPositionUpdate secondUpdate = PositionUpdate(
secondGuid, positionSequence: 2, teleportSequence: 0,
forcePositionSequence: 0, positionX: 16f, isGrounded: true);
Assert.True(lifetime.TryApplyPosition(
secondUpdate, isLocalPlayer: true, null, null, false, null,
out PositionTimestampDisposition secondDisposition, out _, out _));
Assert.Equal(PositionTimestampDisposition.Apply, secondDisposition);
RuntimeInitialCreateExecutionReceipt secondReceipt = RunToCompletion(
lifetime, second, secondLease.Token, NoContact);
RuntimeInitialCreateExecutedAction secondPositionAction = Assert.Single(
secondReceipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.NoPositionOperation,
secondPositionAction.PositionDisposition);
}
[Fact]
public void BindLiveInputs_ThrowsOnASecondBindAndUnboundExecutorsUseTheCallerStructUnchanged()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
lifetime.InitialCreateExecution.BindLiveInputs(
static () => true, static () => Vector3.Zero);
Assert.Throws<InvalidOperationException>(() =>
lifetime.InitialCreateExecution.BindLiveInputs(
static () => false, static () => Vector3.Zero));
// A SEPARATE, never-bound lifetime still honors the caller-supplied
// struct verbatim - the existing bare-lifetime test contract is
// unchanged by this slice.
using RuntimeEntityObjectLifetime unbound = EngineLifetime();
Bind(unbound, 403UL);
const uint guid = 0x70024004u;
RuntimeEntityRecord canonical = unbound
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
.Canonical!;
Assert.True(unbound.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
AttachDormantBody(unbound, canonical);
CompleteInitialPlacement(unbound, lease);
WorldSession.EntityPositionUpdate update = PositionUpdate(
guid, positionSequence: 2, teleportSequence: 0,
forcePositionSequence: 0, positionX: 15f, isGrounded: true);
Assert.True(unbound.TryApplyPosition(
update, isLocalPlayer: true, null, null, false, null,
out PositionTimestampDisposition disposition, out _, out _));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
unbound, canonical, lease.Token, NoContact);
RuntimeInitialCreateExecutedAction positionAction = Assert.Single(
receipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
// NoContact (UsePositionFromServer:false) -> not interpolated.
Assert.Equal(
RuntimeAuthoritativePositionDisposition.NoPositionOperation,
positionAction.PositionDisposition);
}
[Fact]
public void BindLiveInputs_PlayerDistanceIsReadFromANonNullBoundSourceAndFallsBackToTheCallerStructWhenNull()
{
// F3: proves BOTH directions of the nullable local-player-position
// source. Entity 1: the bound source returns a REAL near position -
// the caller struct claims a FAR distance (200f), so if the bound
// source is genuinely read (not ignored), the entity's own near
// distance must win and classify Interpolate. Entity 2: the SAME
// bound source now returns null (e.g. the login-window drain before
// RuntimeLocalPlayerMovementState.Controller exists) - it must fall
// back to the caller struct's FAR distance exactly like an unbound
// source would, never fabricate Vector3.Zero (which would compute a
// small, misleadingly-near distance to the entity's own position and
// wrongly classify Interpolate instead of the far SetPositionSimple
// hard-snap).
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 421UL);
Vector3? boundPosition = new Vector3(30f, 20f, 7f);
lifetime.InitialCreateExecution.BindLiveInputs(
static () => false,
() => boundPosition);
const uint nearGuid = 0x70024021u;
RuntimeEntityRecord near = lifetime
.RegisterEntityWithInitialResidence(Spawn(nearGuid, 1), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
near, out RuntimeInitialCreateResidenceLease nearLease));
AttachDormantBody(lifetime, near);
CompleteInitialPlacement(lifetime, nearLease);
WorldSession.EntityPositionUpdate nearUpdate = PositionUpdate(
nearGuid, positionSequence: 2, teleportSequence: 0,
forcePositionSequence: 0, positionX: 25f, isGrounded: true);
Assert.True(lifetime.TryApplyPosition(
nearUpdate, isLocalPlayer: false, null, null, false, null,
out PositionTimestampDisposition nearDisposition, out _, out _));
Assert.Equal(PositionTimestampDisposition.Apply, nearDisposition);
var farStruct = new RuntimeInitialCreateExecutionInputs(
UsePositionFromServer: false, PlayerDistance: 200f);
RuntimeInitialCreateExecutionReceipt nearReceipt = RunToCompletion(
lifetime, near, nearLease.Token, farStruct);
RuntimeInitialCreateExecutedAction nearAction = Assert.Single(
nearReceipt.Trace,
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.Interpolate,
nearAction.PositionDisposition);
// C0-1: the completed near entity published its own ExecutorCompleted
// receipt on the SAME exact-head stream - acknowledge it before the
// far entity's own Place receipt can ever become the head.
Assert.True(lifetime.Physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot nearCompletion));
Assert.Equal(
RuntimePlacementProjectionKind.ExecutorCompleted,
nearCompletion.Kind);
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
nearCompletion.Token));
boundPosition = null;
const uint farGuid = 0x70024022u;
RuntimeEntityRecord far = lifetime
.RegisterEntityWithInitialResidence(Spawn(farGuid, 1), isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
far, out RuntimeInitialCreateResidenceLease farLease));
AttachDormantBody(lifetime, far);
CompleteInitialPlacement(lifetime, farLease);
WorldSession.EntityPositionUpdate farUpdate = PositionUpdate(
farGuid, positionSequence: 2, teleportSequence: 0,
forcePositionSequence: 0, positionX: 25f, isGrounded: true);
Assert.True(lifetime.TryApplyPosition(
farUpdate, isLocalPlayer: false, null, null, false, null,
out PositionTimestampDisposition farDisposition, out _, out _));
Assert.Equal(PositionTimestampDisposition.Apply, farDisposition);
RuntimeInitialCreateExecutionStatus farStatus = lifetime
.InitialCreateExecution.Execute(
far, farLease.Token, farStruct, out _);
Assert.Equal(
RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement,
farStatus);
RuntimeEntityKey farKey = far.Key!.Value;
Assert.True(lifetime.InitialCreateExecution.TryGetPendingContinuationRoute(
farKey, out RuntimeAuthoritativePositionRoute farRoute));
Assert.Equal(
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
farRoute.Disposition);
Assert.True(farRoute.StopInterpolating);
}
// ---------------------------------------------------------------
// Harness
// ---------------------------------------------------------------
private static RuntimeEntityObjectLifetime EngineLifetime()
{
var engine = new PhysicsEngine { DataCache = new PhysicsDataCache() };
engine.AddLandblock(
Landblock,
new TerrainSurface(new byte[81], new float[256]),
Array.Empty<CellSurface>(),
Array.Empty<PortalPlane>(),
worldOffsetX: 0f,
worldOffsetY: 0f);
return new RuntimeEntityObjectLifetime(engine);
}
private static RuntimeInitialCreateExecutionReceipt RunToCompletion(
RuntimeEntityObjectLifetime lifetime,
RuntimeEntityRecord canonical,
in RuntimeInitialCreateResidenceToken token,
RuntimeInitialCreateExecutionInputs inputs,
int maxSteps = 25)
{
for (int step = 0; step < maxSteps; step++)
{
RuntimeInitialCreateExecutionStatus status = lifetime.InitialCreateExecution.Execute(
canonical, token, inputs, out RuntimeInitialCreateExecutionReceipt receipt);
switch (status)
{
case RuntimeInitialCreateExecutionStatus.Completed:
return receipt;
case RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement:
{
RuntimeEntityKey key = canonical.Key!.Value;
Assert.True(lifetime.InitialCreateExecution
.TryGetPendingContinuationRoute(key, out RuntimeAuthoritativePositionRoute route));
CompletePendingContinuationPlacement(lifetime, key, route);
continue;
}
default:
throw new InvalidOperationException(
$"RunToCompletion hit unexpected status {status}; drive its precondition explicitly instead.");
}
}
throw new InvalidOperationException("RunToCompletion exceeded its step budget.");
}
private static void CompleteInitialPlacement(
RuntimeEntityObjectLifetime lifetime,
in RuntimeInitialCreateResidenceLease lease)
{
RuntimeSetPositionCommand command = Prepare(
lifetime,
lease.Placement,
lease.Route.OperationKind,
lease.Route.SetPositionFlags);
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition
.SubmitPreparedPlacement(lease.Placement, command);
Assert.Equal(
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
outcome.Status);
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(outcome.Projection));
}
private static void CompletePendingContinuationPlacement(
RuntimeEntityObjectLifetime lifetime,
RuntimeEntityKey key,
in RuntimeAuthoritativePositionRoute route)
{
Assert.True(lifetime.InitialCreateExecution
.TryGetPendingContinuationPlacement(key, out RuntimeEntityPlacementToken placement));
RuntimeSetPositionCommand command = Prepare(
lifetime, placement, route.OperationKind, route.SetPositionFlags);
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition
.SubmitPreparedPlacement(placement, command);
Assert.Equal(
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
outcome.Status);
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(outcome.Projection));
}
private static RuntimeSetPositionCommand Prepare(
RuntimeEntityObjectLifetime lifetime,
in RuntimeEntityPlacementToken placement,
RuntimeSetPositionOperationKind operationKind,
PhysicsSetPositionFlags flags)
{
var preparation = new RuntimeSetPositionMoverPreparation(
RuntimeSetPositionMoverSetup.ResolvedAbsent,
operationKind,
GameTime: 1d,
PhysicsPlacementClass.Ordinary,
flags);
Assert.Equal(
RuntimeSetPositionMoverPreparationStatus.Prepared,
lifetime.Physics.SetPosition.PrepareMover(
placement, preparation, out RuntimeSetPositionCommand command));
return command;
}
private static void AttachDormantBody(
RuntimeEntityObjectLifetime lifetime,
RuntimeEntityRecord canonical,
bool inContact = false)
{
var body = new PhysicsBody
{
State = canonical.FinalPhysicsState,
Orientation = Quaternion.Identity,
InWorld = false,
TransientState = inContact ? TransientStateFlags.Contact : TransientStateFlags.None,
};
lifetime.Entities.SetPhysicsBody(canonical, body);
}
/// <summary>
/// The real physics sweep an initial-placement commit runs against the
/// test landblock decides the body's own contact bit from scratch,
/// clobbering whatever <see cref="AttachDormantBody"/> set beforehand.
/// Call this AFTER the placement commits to pin the contact bit a
/// Position-route test actually needs.
/// </summary>
private static void ForceContact(RuntimeEntityRecord canonical, bool inContact)
{
if (canonical.PhysicsBody is not { } body)
return;
body.TransientState = inContact
? body.TransientState | TransientStateFlags.Contact
: body.TransientState & ~TransientStateFlags.Contact;
}
private static void Bind(RuntimeEntityObjectLifetime lifetime, ulong generation)
{
var token = new RuntimeGenerationToken(generation);
lifetime.BindEventContext(() => token, static () => 1UL);
}
private static WorldSession.EntityPositionUpdate PositionUpdate(
uint guid,
ushort positionSequence,
ushort teleportSequence,
ushort forcePositionSequence,
float positionX,
bool isGrounded = true)
{
return new WorldSession.EntityPositionUpdate(
guid,
new CreateObject.ServerPosition(Cell, positionX, 20f, 7f, 1f, 0f, 0f, 0f),
new Vector3(positionSequence, 2f, 3f),
PlacementId: positionSequence,
IsGrounded: isGrounded,
InstanceSequence: 1,
PositionSequence: positionSequence,
TeleportSequence: teleportSequence,
ForcePositionSequence: forcePositionSequence);
}
private static WorldSession.EntitySpawn Spawn(
uint guid,
ushort incarnation,
bool includePosition = true,
uint? parentGuid = null,
ushort positionSequence = 1,
float positionX = 10f,
bool missile = false,
ushort teleportSequence = 0,
ushort forcePositionSequence = 0,
ushort movementSequence = 1,
ushort serverControlSequence = 1)
{
CreateObject.ServerPosition? position = includePosition
? new CreateObject.ServerPosition(Cell, positionX, 20f, 7f, 1f, 0f, 0f, 0f)
: null;
uint rawState = (uint)(PhysicsStateFlags.Gravity
| (missile ? PhysicsStateFlags.Missile : 0));
var timestamps = new PhysicsTimestamps(
Position: positionSequence,
Movement: movementSequence,
State: 1,
Vector: 1,
Teleport: teleportSequence,
ServerControlledMove: serverControlSequence,
ForcePosition: forcePositionSequence,
ObjDesc: 1,
Instance: incarnation);
var physics = new PhysicsSpawnData(
rawState,
position,
Movement: null,
AnimationFrame: null,
SetupTableId: null,
MotionTableId: null,
SoundTableId: null,
PhysicsScriptTableId: null,
Parent: parentGuid is { } parent ? new PhysicsAttachment(parent, 1u) : null,
Children: null,
Scale: null,
Friction: null,
Elasticity: null,
Translucency: null,
Velocity: null,
Acceleration: null,
AngularVelocity: null,
DefaultScriptType: null,
DefaultScriptIntensity: null,
timestamps);
return new WorldSession.EntitySpawn(
guid,
position,
SetupTableId: null,
Array.Empty<CreateObject.AnimPartChange>(),
Array.Empty<CreateObject.TextureChange>(),
Array.Empty<CreateObject.SubPaletteSwap>(),
BasePaletteId: null,
ObjScale: null,
Name: "initial-create",
ItemType: null,
MotionState: null,
MotionTableId: null,
PhysicsState: rawState,
InstanceSequence: incarnation,
MovementSequence: timestamps.Movement,
ServerControlSequence: timestamps.ServerControlledMove,
PositionSequence: positionSequence,
ParentGuid: parentGuid,
ParentLocation: parentGuid is null ? null : 1u,
Physics: physics);
}
private sealed class EntityObserver(Action<RuntimeEntityDelta> onEntity)
: IRuntimeEntityObjectObserver
{
public void OnEntity(in RuntimeEntityDelta delta) => onEntity(delta);
public void OnInventory(in RuntimeInventoryDelta delta)
{
}
}
private sealed class PlacementObserver(
Action<RuntimePlacementDelta> onPlacement)
: IRuntimePlacementObserver
{
public void OnPlacement(in RuntimePlacementDelta delta) =>
onPlacement(delta);
}
}