feat(runtime): construct remote bodies at Create per retail set_description

Cutover slice C3b: residence-route remote/creature/projectile Creates now
get their canonical PhysicsBody at Create time — retail's order, closing
the C3 flip's Finding C (production builds bodies at first motion; retail
builds them in ACCObjectMaint::CreateObject). RuntimeRemoteBodyDescription
walks set_description 0x00514F40 exactly: the motion-table gate (zero id
PASSES — verified at 0051871f/005127ca), the frame-vs-movement branch
keyed on retail's movement_buffer != 0 (an empty-buffer movement payload
takes the PLACEMENT branch and writes no autonomy — the wire-shape defect
the retail review caught), set_state, the byte-certain friction gate
(inclusive [0,1]; NaN deliberately skipped per the gates doc's sanctioned
deviation), the set_elasticity clamp with retail's unordered-to-zero NaN
routing (ACE diverges to 0.1 on that edge), the translucency gate
(!= 0.0f, original always recorded), velocity via setter, omega raw, and
ctor-defaults for absent wire fields (0.95f/0.05f/0 — the fresh-desc-per-
message flow verified at both UnPack call sites). InWorld stays false
until submission, the enter_world analog.

RuntimeRemoteFirstEntryState sequences mover-prep -> body construction ->
placement -> acknowledgement -> Execute with every C3a hardening
inherited: exactly-once stages, the shared acknowledge-stage
discriminator (extracted to RuntimeFirstEntryAcknowledgement, one body
for both conductors), typed Contention against in-flight remote-motion
binds, never-clobber body binding through the canonical writer (foreign
body fails closed — provably safe coexistence with today's
build-at-first-motion path in both directions), automatic convergence
through the retirement fan-out, and the construction receipt riding the
terminal Advance. Dormant: no production caller; C3c wires both hosts.

Reviewed: retail-conformance PASS (the construction order, both gate
boundary/NaN semantics, the motion-table and autonomy verdicts all
re-derived from the pseudo-C) + architecture/adversarial PASS after one
fix round. Runtime 982/982; complete Release solution 10,777 passed / 4
intentional skips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-02 10:20:48 +02:00
parent d62b99509e
commit 0934a12111
6 changed files with 2083 additions and 41 deletions

View file

@ -0,0 +1,980 @@
using System.Collections.Immutable;
using System.Numerics;
using AcDream.Content;
using AcDream.Content.Pak;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Physics;
using AcDream.Runtime;
namespace AcDream.Runtime.Tests.Entities;
public sealed class RuntimeRemoteFirstEntryStateTests
{
private const uint Landblock = 0xA9B60000u;
private const uint Cell = Landblock | 0x0001u;
private const uint SetupId = 0x02000001u;
private static readonly RuntimeInitialCreateExecutionInputs NoContact =
new(UsePositionFromServer: false, PlayerDistance: 0f);
// ---------------------------------------------------------------
// Happy path
// ---------------------------------------------------------------
[Fact]
public void FullSequenceRemoteEntryConstructsGatedBodyPlacesAndCompletes()
{
using var fixture = new Fixture(residentWorld: true);
RuntimeRemoteFirstEntryStatus status = fixture.Advance(
out RuntimeInitialCreateExecutionReceipt receipt,
out RuntimeRemoteBodyConstructionReceipt construction);
Assert.Equal(RuntimeRemoteFirstEntryStatus.Completed, status);
Assert.Equal(Cell, receipt.FullCellId);
// Remote Creates carry NO teleport hook (classifier: AfterEnterWorld
// is local-player-only).
Assert.Equal(RuntimeTeleportHookPhase.None, receipt.TeleportHookPhase);
// M1: the terminal Advance delivers the construction receipt in the
// same call that reaps its retained storage — the C3c host's one
// natural consumption point.
Assert.True(construction.MotionTableGatePassed);
Assert.Equal(0x09000001u, construction.MotionTableId);
Assert.False(construction.MovementBranch);
Assert.True(construction.PlacementFrameStaged);
Assert.True(construction.FrictionApplied);
Assert.Equal(0.5f, construction.Friction);
Assert.False(construction.TranslucencyApplied);
Assert.Equal(0f, construction.TranslucencyOriginal);
Assert.True(construction.VelocityApplied);
Assert.True(construction.OmegaApplied);
PhysicsBody body = Assert.IsType<PhysicsBody>(fixture.Record.PhysicsBody);
Assert.True(body.InWorld);
Assert.Equal(Cell, fixture.Record.FullCellId);
// set_description field application from the frozen wire desc:
// friction 0.5 passed gates 1+2; elasticity 0.05 inside the
// set_elasticity clamp; state is the record's retail-transitioned
// view; omega was the raw step-11 write.
Assert.Equal(0.5f, body.Friction);
Assert.Equal(0.05f, body.Elasticity);
Assert.Equal(fixture.Record.FinalPhysicsState, body.State);
Assert.Equal(new Vector3(0f, 0f, 0.25f), body.Omega);
Assert.True(fixture.Lifetime.Physics.IsSpatialRoot(fixture.Record));
Assert.Equal(0, fixture.Conductor.CaptureOwnership().ActiveCount);
Assert.False(fixture.Conductor.TryGetConstruction(
fixture.Key, out _));
Assert.False(fixture.Lifetime.TryGetInitialCreateResidence(
fixture.Record, out _));
Assert.Equal(0, fixture.Lifetime.CaptureOwnership()
.InitialCreateResidenceLeaseCount);
Assert.Equal(0, fixture.Lifetime.CaptureOwnership()
.InitialCreateExecutorProgressCount);
Assert.Equal(0, fixture.Lifetime.CaptureOwnership()
.RemoteFirstEntryActiveCount);
// Retrying with the now-stale residence token is a distinct, safe
// no-op — nothing left to resume.
Assert.Equal(
RuntimeRemoteFirstEntryStatus.RejectedToken,
fixture.Advance(out _));
}
[Fact]
public void ProjectileFlavorFullSequenceCompletesWithMissileState()
{
using var fixture = new Fixture(
residentWorld: true,
rawState: (uint)(PhysicsStateFlags.Gravity
| PhysicsStateFlags.ReportCollisions
| PhysicsStateFlags.Missile
| PhysicsStateFlags.Inelastic
| PhysicsStateFlags.PathClipped
| PhysicsStateFlags.AlignPath));
// The Missile flag classified the Create to ProjectileAuthoritative
// (RuntimeInitialCreateResidenceState.Begin) — the conductor accepts
// that flavor through the identical stages.
Assert.True(fixture.Lifetime.TryGetInitialCreateResidence(
fixture.Record, out RuntimeInitialCreateResidenceLease lease));
Assert.Equal(
RuntimeSetPositionOperationKind.ProjectileAuthoritative,
lease.Route.OperationKind);
RuntimeRemoteFirstEntryStatus status = fixture.Advance(
out RuntimeInitialCreateExecutionReceipt receipt);
Assert.Equal(RuntimeRemoteFirstEntryStatus.Completed, status);
Assert.Equal(Cell, receipt.FullCellId);
PhysicsBody body = Assert.IsType<PhysicsBody>(fixture.Record.PhysicsBody);
Assert.True(body.InWorld);
Assert.True(body.State.HasFlag(PhysicsStateFlags.Missile));
Assert.Equal(fixture.Record.FinalPhysicsState, body.State);
Assert.Equal(0, fixture.Conductor.CaptureOwnership().ActiveCount);
}
// ---------------------------------------------------------------
// The three float gates, proven both ways on the pure construction
// (docs/research/2026-08-02-set-description-float-gates.md is law)
// ---------------------------------------------------------------
[Theory]
[InlineData(-1f, false, 0.95f)]
[InlineData(0f, true, 0f)]
[InlineData(0.5f, true, 0.5f)]
[InlineData(1f, true, 1f)]
[InlineData(1.5f, false, 0.95f)]
// R2b: NaN is SKIPPED (body keeps its default) — the gates doc's
// sanctioned deviation from retail's unordered-goes-to-apply codegen
// quirk; see the friction comment in RuntimeRemoteBodyDescription.
[InlineData(float.NaN, false, 0.95f)]
public void FrictionGateAppliesOnlyInsideClosedUnitInterval(
float wireFriction,
bool expectedApplied,
float expectedBodyFriction)
{
(PhysicsBody body, RuntimeRemoteBodyConstructionReceipt receipt) =
ConstructDirect(friction: wireFriction);
Assert.Equal(expectedApplied, receipt.FrictionApplied);
Assert.Equal(expectedBodyFriction, body.Friction);
Assert.Equal(body.Friction, receipt.Friction);
}
[Fact]
public void FrictionAbsentOnWireTakesTheDescConstructorDefault()
{
// PhysicsDesc ctor 0x0051D4D0 seeds friction 0.95f; it passes the
// gate, so an absent wire friction still records an APPLIED write.
(PhysicsBody body, RuntimeRemoteBodyConstructionReceipt receipt) =
ConstructDirect(friction: null);
Assert.True(receipt.FrictionApplied);
Assert.Equal(0.95f, body.Friction);
}
[Theory]
[InlineData(0f, false)]
[InlineData(0.5f, true)]
[InlineData(-0.25f, true)]
// R2: NaN lands in the apply bucket — matching retail's unordered case
// exactly (gates doc conditional 3: JNP fires ONLY on exact equality
// with 0.0f).
[InlineData(float.NaN, true)]
public void TranslucencyGateAppliesOnlyWhenNonZeroAndOriginalIsAlwaysRecorded(
float wireTranslucency,
bool expectedApplied)
{
(_, RuntimeRemoteBodyConstructionReceipt receipt) =
ConstructDirect(translucency: wireTranslucency);
// translucencyOriginal is written UNCONDITIONALLY (0x00515097,
// before the gate); the live apply fires for every value except
// exact 0.0f (gate 3's JNP fires only on equality).
Assert.Equal(wireTranslucency, receipt.TranslucencyOriginal);
Assert.Equal(expectedApplied, receipt.TranslucencyApplied);
}
[Fact]
public void TranslucencyAbsentOnWireIsZeroOriginalAndNoLiveApply()
{
(_, RuntimeRemoteBodyConstructionReceipt receipt) =
ConstructDirect(translucency: null);
Assert.Equal(0f, receipt.TranslucencyOriginal);
Assert.False(receipt.TranslucencyApplied);
}
[Theory]
[InlineData(-0.5f, 0f)]
[InlineData(0f, 0f)]
[InlineData(0.05f, 0.05f)]
[InlineData(0.1f, 0.1f)]
[InlineData(0.2f, 0.1f)]
// R2a: retail's FIRST x87 compare sends unordered to the zeroing arm
// (0x0050FD51) — NaN -> 0f, NOT ACE's divergent NaN -> 0.1f.
[InlineData(float.NaN, 0f)]
public void ElasticityClampMatchesRetailSetter(
float wireElasticity,
float expectedBodyElasticity)
{
// CPhysicsObj::set_elasticity 0x0050FD40: < 0 -> 0; <= 0.1 -> value;
// > 0.1 -> 0.1 (ACE PhysicsGlobals.MaxElasticity = 0.1f agrees).
(PhysicsBody body, RuntimeRemoteBodyConstructionReceipt receipt) =
ConstructDirect(elasticity: wireElasticity);
Assert.Equal(expectedBodyElasticity, body.Elasticity);
Assert.Equal(body.Elasticity, receipt.Elasticity);
}
// ---------------------------------------------------------------
// Placement-frame-vs-movement branch (set_description step 4)
// ---------------------------------------------------------------
[Fact]
public void MovementPayloadSuppressesPlacementFrameAndWritesAutonomy()
{
(PhysicsBody body, RuntimeRemoteBodyConstructionReceipt receipt) =
ConstructDirect(movement: new PhysicsMovementData(
RawData: new byte[] { 0x01 },
MotionState: null,
IsAutonomous: true));
Assert.True(receipt.MovementBranch);
Assert.True(receipt.LastMoveWasAutonomous);
Assert.True(body.LastMoveWasAutonomous);
Assert.False(receipt.PlacementFrameStaged);
// No frame staged: the body's cell identity stays default-detached.
Assert.Equal(0u, body.CellPosition.ObjCellId);
Assert.False(body.InWorld);
}
[Fact]
public void MovementFlagWithEmptyBufferTakesThePlacementBranch()
{
// R1: retail's branch discriminator is `movement_buffer != 0` —
// PhysicsDesc::UnPack (0x0051DDD0) assigns the buffer only inside
// `if (buff_length != 0)` (0051DE1F-0051DE2A), so a movement flag
// with a ZERO-LENGTH buffer reaches set_description with a null
// buffer and takes the PLACEMENT branch; the autonomy write (in the
// movement else-branch only) never runs. Our parser materializes
// exactly this wrapper shape (CreateObject.cs: non-null
// PhysicsMovementData with empty RawData). IsAutonomous is set true
// here deliberately — the BRANCH, never the wrapper's value, must
// decide.
(PhysicsBody body, RuntimeRemoteBodyConstructionReceipt receipt) =
ConstructDirect(movement: new PhysicsMovementData(
RawData: ReadOnlyMemory<byte>.Empty,
MotionState: null,
IsAutonomous: true));
Assert.False(receipt.MovementBranch);
Assert.True(receipt.PlacementFrameStaged);
Assert.Equal(Cell, body.CellPosition.ObjCellId);
Assert.False(body.InWorld);
Assert.False(receipt.LastMoveWasAutonomous);
Assert.False(body.LastMoveWasAutonomous);
}
[Fact]
public void NoMovementPayloadStagesTheDormantPlacementFrame()
{
(PhysicsBody body, RuntimeRemoteBodyConstructionReceipt receipt) =
ConstructDirect(movement: null);
Assert.False(receipt.MovementBranch);
Assert.True(receipt.PlacementFrameStaged);
Assert.Equal(Cell, body.CellPosition.ObjCellId);
// SetPlacementFrameInternal is NOT enter_world — the submission owns
// world residence.
Assert.False(body.InWorld);
Assert.False(body.LastMoveWasAutonomous);
}
// ---------------------------------------------------------------
// Yield flavors + retry idempotency per stage
// ---------------------------------------------------------------
[Fact]
public void AwaitingCollisionSourceRetriesThenResumesOnceSetupLands()
{
using var fixture = new Fixture(residentWorld: true, setupTableId: SetupId);
fixture.CollisionSource.Status = PreparedAssetReadStatus.Missing;
RuntimeRemoteFirstEntryStatus first = fixture.Advance(out _);
Assert.Equal(RuntimeRemoteFirstEntryStatus.AwaitingCollisionSource, first);
// No progress entry, no body, nothing mutated while the Setup is
// outstanding.
Assert.Equal(0, fixture.Conductor.CaptureOwnership().ActiveCount);
Assert.Null(fixture.Record.PhysicsBody);
RuntimeRemoteFirstEntryStatus second = fixture.Advance(out _);
Assert.Equal(RuntimeRemoteFirstEntryStatus.AwaitingCollisionSource, second);
Assert.True(fixture.CollisionSource.ReadCount >= 2);
Assert.Equal(0, fixture.Conductor.CaptureOwnership().ActiveCount);
Assert.Null(fixture.Record.PhysicsBody);
fixture.CollisionSource.Status = PreparedAssetReadStatus.Loaded;
Assert.Equal(RuntimeRemoteFirstEntryStatus.Completed,
fixture.Advance(out RuntimeInitialCreateExecutionReceipt receipt));
Assert.Equal(Cell, receipt.FullCellId);
Assert.NotNull(fixture.Record.PhysicsBody);
}
[Fact]
public void DeferredPlacementRetainsOneBodyIdentityAcrossRetriesThenWakesAndCompletes()
{
// Non-resident world: the ordinary submission defers (the
// destination landblock's collision world was never added), parking
// the operation behind a Withdraw the conductor drains itself.
using var fixture = new Fixture(residentWorld: false);
RuntimeRemoteFirstEntryStatus first = fixture.Advance(out _);
Assert.Equal(RuntimeRemoteFirstEntryStatus.AwaitingPlacement, first);
PhysicsBody body = Assert.IsType<PhysicsBody>(fixture.Record.PhysicsBody);
Assert.False(body.InWorld);
Assert.Equal(0u, fixture.Record.FullCellId);
// The construction is still inspectable mid-flight, and the
// set_description writes are already on the body.
Assert.True(fixture.Conductor.TryGetConstruction(
fixture.Key, out RuntimeRemoteBodyConstructionReceipt construction));
Assert.True(construction.FrictionApplied);
Assert.Equal(0.5f, body.Friction);
Assert.True(construction.VelocityApplied);
Assert.Equal(new Vector3(1f, 2f, 0.5f), body.Velocity);
Assert.True(construction.OmegaApplied);
Assert.Equal(1, fixture.Conductor.CaptureOwnership().ActiveCount);
// Retry idempotency: the SAME body identity survives every retry —
// no duplicate construction, no re-submission churn.
for (int i = 0; i < 3; i++)
{
Assert.Equal(RuntimeRemoteFirstEntryStatus.AwaitingPlacement,
fixture.Advance(out _));
Assert.Same(body, fixture.Record.PhysicsBody);
Assert.Equal(1, fixture.Conductor.CaptureOwnership().ActiveCount);
}
const ulong generation = 1UL;
fixture.Lifetime.Physics.SetPosition.BeginCollisionGeneration(
Landblock, generation);
fixture.Lifetime.Physics.Engine.AddLandblock(
Landblock,
new TerrainSurface(new byte[81], new float[256]),
Array.Empty<CellSurface>(),
Array.Empty<PortalPlane>(),
worldOffsetX: 0f,
worldOffsetY: 0f);
fixture.Lifetime.Physics.SetPosition.CommitCollisionGeneration(
Landblock, generation, ready: true);
Assert.Equal(RuntimeRemoteFirstEntryStatus.Completed,
fixture.Advance(out RuntimeInitialCreateExecutionReceipt receipt));
Assert.Same(body, fixture.Record.PhysicsBody);
Assert.True(body.InWorld);
Assert.Equal(Cell, receipt.FullCellId);
Assert.Equal(Cell, fixture.Record.FullCellId);
Assert.Equal(0, fixture.Conductor.CaptureOwnership().ActiveCount);
}
[Fact]
public void ReentrantAdvanceDuringCollisionCallbackFailsClosedWithContentionAndOuterCallStillCompletes()
{
using var fixture = new Fixture(residentWorld: true);
bool reentered = false;
RuntimeRemoteFirstEntryStatus? innerStatus = null;
fixture.Lifetime.Physics.Engine.TransitionCellCollisionTestHook =
(_, phase, _, observed) =>
{
if (!reentered && phase is TransitionCellCollisionPhase.Environment)
{
reentered = true;
innerStatus = fixture.Advance(out _);
}
return observed;
};
RuntimeRemoteFirstEntryStatus outerStatus = fixture.Advance(
out RuntimeInitialCreateExecutionReceipt receipt);
Assert.True(reentered);
Assert.Equal(RuntimeRemoteFirstEntryStatus.Contention, innerStatus);
Assert.Equal(RuntimeRemoteFirstEntryStatus.Completed, outerStatus);
Assert.Equal(Cell, receipt.FullCellId);
Assert.Equal(0, fixture.Conductor.CaptureOwnership().ActiveCount);
}
[Fact]
public void AwaitingReceiptAcknowledgementWhileAnotherEntityHoldsTheFifoHeadThenResumes()
{
using var fixture = new Fixture(residentWorld: true);
(RuntimeEntityRecord other, RuntimePlacementProjectionToken otherToken) =
BeginPendingOrdinaryPlacement(fixture, 0x70099001u);
// Our submission commits, but its Place sits BEHIND the other
// entity's unacknowledged receipt in the one ordered FIFO.
RuntimeRemoteFirstEntryStatus first = fixture.Advance(out _);
Assert.Equal(
RuntimeRemoteFirstEntryStatus.AwaitingReceiptAcknowledgement,
first);
Assert.True(fixture.Record.PhysicsBody!.InWorld);
RuntimeRemoteFirstEntryStatus second = fixture.Advance(out _);
Assert.Equal(
RuntimeRemoteFirstEntryStatus.AwaitingReceiptAcknowledgement,
second);
Assert.True(fixture.Lifetime.Physics.SetPosition
.AcknowledgeProjection(otherToken));
Assert.Equal(RuntimeRemoteFirstEntryStatus.Completed,
fixture.Advance(out RuntimeInitialCreateExecutionReceipt receipt));
Assert.Equal(Cell, receipt.FullCellId);
_ = other;
}
[Fact]
public void DeleteWhileAwaitingReceiptAcknowledgementAbandonsInsteadOfRetryingForeverAndConverges()
{
// M2a — the exact C3a-shaped scenario: the placement has ALREADY
// committed (body in world) but acknowledgement is blocked behind
// another entity's unacknowledged Place. The delete-path mechanism
// (Physics.SetPosition.Forget with releasePreparedMover — the exact
// inner call TryAcceptDelete makes) rewrites our still-pending Place
// slot to Discard with a bumped revision, so the cached
// progress.Projection can never match the FIFO head again. Without
// the shared IsAcknowledgementStillPending re-check, Advance would
// report AwaitingReceiptAcknowledgement forever and ownership would
// never converge.
using var fixture = new Fixture(residentWorld: true);
(RuntimeEntityRecord other, RuntimePlacementProjectionToken otherToken) =
BeginPendingOrdinaryPlacement(fixture, 0x70099002u);
Assert.Equal(
RuntimeRemoteFirstEntryStatus.AwaitingReceiptAcknowledgement,
fixture.Advance(out _));
Assert.Equal(1, fixture.Conductor.CaptureOwnership().ActiveCount);
Assert.True(fixture.Record.PhysicsBody!.InWorld);
Assert.True(fixture.Lifetime.TryGetInitialCreateResidence(
fixture.Record, out _));
RuntimePlacementCancellationReceipt cancellation = fixture.Lifetime
.Physics.SetPosition.Forget(
fixture.Record,
releasePreparedMover: true);
fixture.Lifetime.Physics.SetPosition.PublishCancellation(cancellation);
Assert.Equal(RuntimeRemoteFirstEntryStatus.RejectedAuthority,
fixture.Advance(out _));
Assert.Equal(0, fixture.Conductor.CaptureOwnership().ActiveCount);
// The unrelated entity's own placement is untouched and still
// acknowledgeable — the abandonment never reached past our own
// entity's projection.
Assert.True(fixture.Lifetime.Physics.SetPosition
.AcknowledgeProjection(otherToken));
_ = other;
}
[Fact]
public void AwaitingContinuationPlacementPropagatesExecutorYieldThenResumes()
{
using var fixture = new Fixture(residentWorld: true);
// A fresh Position arrives while the initial residence is still
// pending — enqueued as a continuation, classified only at drain.
WorldSession.EntityPositionUpdate update = new(
fixture.Record.ServerGuid,
new CreateObject.ServerPosition(Cell, 40f, 20f, 7f, 1f, 0f, 0f, 0f),
Velocity: null,
PlacementId: 2,
IsGrounded: true,
InstanceSequence: 1,
PositionSequence: 2,
TeleportSequence: 1,
ForcePositionSequence: 0);
Assert.True(fixture.Lifetime.TryApplyPosition(
update,
isLocalPlayer: false,
forcePositionRotation: null,
currentLocalVelocity: null,
projectionRequiresTeleportHook: false,
acknowledgeProjection: null,
out PositionTimestampDisposition disposition,
out _,
out _));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
RuntimeRemoteFirstEntryStatus status = fixture.Advance(out _);
Assert.Equal(
RuntimeRemoteFirstEntryStatus.AwaitingContinuationPlacement,
status);
// The conductor's own sequence reached its terminal Acknowledged
// stage; the retained progress entry lets a retry skip straight to
// re-calling Execute.
Assert.Equal(1, fixture.Conductor.CaptureOwnership().ActiveCount);
RuntimeEntityKey key = fixture.Key;
Assert.True(fixture.Lifetime.InitialCreateExecution
.TryGetPendingContinuationPlacement(
key, out RuntimeEntityPlacementToken placement));
Assert.True(fixture.Lifetime.InitialCreateExecution
.TryGetPendingContinuationRoute(
key, out RuntimeAuthoritativePositionRoute route));
CompleteOrdinaryPlacement(fixture, placement, route);
RuntimeRemoteFirstEntryStatus resumed = fixture.Advance(
out RuntimeInitialCreateExecutionReceipt receipt);
Assert.Equal(RuntimeRemoteFirstEntryStatus.Completed, resumed);
Assert.Contains(receipt.Trace,
a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
Assert.Equal(0, fixture.Conductor.CaptureOwnership().ActiveCount);
}
// ---------------------------------------------------------------
// Domain refusal + never-clobber
// ---------------------------------------------------------------
[Fact]
public void LocalPlayerLeaseIsRefusedWithoutTrackingAnything()
{
using var fixture = new Fixture(residentWorld: true, isLocalPlayer: true);
Assert.Equal(RuntimeRemoteFirstEntryStatus.RejectedToken,
fixture.Advance(out _));
Assert.Equal(0, fixture.Conductor.CaptureOwnership().ActiveCount);
Assert.Null(fixture.Record.PhysicsBody);
// The lease itself is untouched — the C3a conductor still owns it.
Assert.True(fixture.Lifetime.TryGetInitialCreateResidence(
fixture.Record, out RuntimeInitialCreateResidenceLease lease));
Assert.Equal(fixture.Lease.Token, lease.Token);
}
[Fact]
public void ForeignBodyBoundOutOfBandAbandonsWithoutClobbering()
{
using var fixture = new Fixture(residentWorld: true);
var foreign = new PhysicsBody
{
Position = new Vector3(1f, 2f, 3f),
State = fixture.Record.FinalPhysicsState,
};
fixture.Lifetime.Entities.SetPhysicsBody(fixture.Record, foreign);
Assert.Equal(RuntimeRemoteFirstEntryStatus.RejectedAuthority,
fixture.Advance(out _));
// The foreign body was never replaced or mutated toward ours.
Assert.Same(foreign, fixture.Record.PhysicsBody);
Assert.Equal(0, fixture.Conductor.CaptureOwnership().ActiveCount);
}
// ---------------------------------------------------------------
// Delete / reset mid-flight convergence
// ---------------------------------------------------------------
[Fact]
public void DeleteWhileAwaitingPlacementConvergesAutomaticallyThroughTheRetirementFanOut()
{
using var fixture = new Fixture(residentWorld: false);
Assert.Equal(RuntimeRemoteFirstEntryStatus.AwaitingPlacement,
fixture.Advance(out _));
Assert.Equal(1, fixture.Conductor.CaptureOwnership().ActiveCount);
DeleteEntity(fixture);
Assert.Null(fixture.Record.Key);
// TryAcceptDelete's ForgetInitialCreateResidence fired the multicast
// retirement notification synchronously — this conductor's Forget is
// bound into that same fan-out, so convergence is complete before
// delete returns.
Assert.Equal(0, fixture.Conductor.CaptureOwnership().ActiveCount);
// Retrying Advance afterward is a safe, distinct no-op.
Assert.Equal(RuntimeRemoteFirstEntryStatus.RejectedToken,
fixture.Advance(out _));
}
[Fact]
public void SessionClearMidFlightConvergesOwnership()
{
using var fixture = new Fixture(residentWorld: false);
Assert.Equal(RuntimeRemoteFirstEntryStatus.AwaitingPlacement,
fixture.Advance(out _));
Assert.Equal(1, fixture.Conductor.CaptureOwnership().ActiveCount);
_ = fixture.Lifetime.BeginSessionClear();
Assert.Equal(0, fixture.Conductor.CaptureOwnership().ActiveCount);
Assert.Equal(0, fixture.Lifetime.CaptureOwnership()
.RemoteFirstEntryActiveCount);
}
[Fact]
public void DeleteAndSameGuidReincarnationStartsAFreshSequence()
{
using var fixture = new Fixture(residentWorld: false);
Assert.Equal(RuntimeRemoteFirstEntryStatus.AwaitingPlacement,
fixture.Advance(out _));
RuntimeEntityKey staleKey = fixture.Key;
uint guid = fixture.Record.ServerGuid;
DeleteEntity(fixture);
Assert.Equal(0, fixture.Conductor.CaptureOwnership().ActiveCount);
RuntimeEntityRecord reincarnated = fixture.Lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, incarnation: 2),
isLocalPlayer: false)
.Canonical!;
Assert.NotEqual(staleKey, reincarnated.Key!.Value);
Assert.True(fixture.Lifetime.TryGetInitialCreateResidence(
reincarnated,
out RuntimeInitialCreateResidenceLease freshLease));
fixture.Record = reincarnated;
fixture.Lease = freshLease;
Assert.Equal(RuntimeRemoteFirstEntryStatus.AwaitingPlacement,
fixture.Advance(out _));
const ulong generation = 1UL;
fixture.Lifetime.Physics.SetPosition.BeginCollisionGeneration(
Landblock, generation);
fixture.Lifetime.Physics.Engine.AddLandblock(
Landblock,
new TerrainSurface(new byte[81], new float[256]),
Array.Empty<CellSurface>(),
Array.Empty<PortalPlane>(),
worldOffsetX: 0f,
worldOffsetY: 0f);
fixture.Lifetime.Physics.SetPosition.CommitCollisionGeneration(
Landblock, generation, ready: true);
RuntimeRemoteFirstEntryStatus status = fixture.Advance(
out RuntimeInitialCreateExecutionReceipt receipt);
Assert.Equal(RuntimeRemoteFirstEntryStatus.Completed, status);
Assert.Equal(Cell, receipt.FullCellId);
Assert.NotNull(reincarnated.PhysicsBody);
Assert.True(reincarnated.PhysicsBody!.InWorld);
Assert.Equal(0, fixture.Conductor.CaptureOwnership().ActiveCount);
}
// ---------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------
private static (PhysicsBody Body, RuntimeRemoteBodyConstructionReceipt Receipt)
ConstructDirect(
float? friction = 0.5f,
float? elasticity = 0.05f,
float? translucency = null,
PhysicsMovementData? movement = null)
{
var record = new RuntimeEntityRecordFactory();
RuntimeEntityRecord canonical = record.Lifetime
.RegisterEntity(
Spawn(
0x70090077u,
incarnation: 1,
friction: friction,
elasticity: elasticity,
translucency: translucency,
movement: movement))
.Canonical!;
var command = new RuntimeSetPositionCommand(
new PhysicsSetPositionRequest(
new Vector3(1f, 2f, 3f),
Quaternion.Identity,
Cell,
new Vector3(1f, 2f, 3f),
ImmutableArray<FlatCollisionSphere>.Empty,
Scale: 1f,
StepUpHeight: 0f,
StepDownHeight: 0f,
canonical.FinalPhysicsState,
ObjectInfoState.None,
canonical.Key?.LocalEntityId ?? 0u,
PhysicsPlacementClass.Ordinary,
PhysicsSetPositionFlags.Placement | PhysicsSetPositionFlags.Slide),
RuntimeSetPositionOperationKind.RemoteAuthoritative,
GameTime: 1d,
ExpectedVelocityAuthorityVersion: 0UL);
PhysicsBody body = RuntimeRemoteBodyDescription.Construct(
canonical,
canonical.Snapshot.Physics,
command,
out RuntimeRemoteBodyConstructionReceipt receipt);
record.Dispose();
return (body, receipt);
}
private sealed class RuntimeEntityRecordFactory : IDisposable
{
internal RuntimeEntityObjectLifetime Lifetime { get; } = new();
internal RuntimeEntityRecordFactory()
{
var generation = new RuntimeGenerationToken(1UL);
Lifetime.BindEventContext(() => generation, static () => 1UL);
}
public void Dispose() => Lifetime.Dispose();
}
private static (RuntimeEntityRecord Record, RuntimePlacementProjectionToken Token)
BeginPendingOrdinaryPlacement(Fixture fixture, uint guid)
{
RuntimeEntityRecord record = fixture.Lifetime.RegisterEntity(
Spawn(guid, incarnation: 1)).Canonical!;
var body = new PhysicsBody
{
Position = new Vector3(50f, 50f, 3f),
Orientation = Quaternion.Identity,
State = record.FinalPhysicsState,
};
body.SnapToCell(Cell, body.Position, body.Position);
fixture.Lifetime.Entities.SetPhysicsBody(record, body);
RuntimeEntityPlacementToken placement = fixture.Lifetime.Physics
.SetPosition.BeginAuthoredPlacement(
record,
record.PositionAuthorityVersion,
RuntimeSetPositionOperationKind.RemoteAuthoritative);
Assert.True(placement.IsValid);
var preparation = new RuntimeSetPositionMoverPreparation(
RuntimeSetPositionMoverSetup.ResolvedAbsent,
RuntimeSetPositionOperationKind.RemoteAuthoritative,
GameTime: 1d,
PhysicsPlacementClass.Ordinary,
PhysicsSetPositionFlags.Placement | PhysicsSetPositionFlags.Slide);
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
fixture.Lifetime.Physics.SetPosition.PrepareMover(
placement, preparation, out RuntimeSetPositionCommand command));
RuntimeSetPositionOutcome outcome = fixture.Lifetime.Physics.SetPosition
.SubmitPreparedPlacement(placement, command);
Assert.Equal(RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
outcome.Status);
return (record, outcome.Projection);
}
private static void CompleteOrdinaryPlacement(
Fixture fixture,
in RuntimeEntityPlacementToken placement,
in RuntimeAuthoritativePositionRoute route)
{
var preparation = new RuntimeSetPositionMoverPreparation(
RuntimeSetPositionMoverSetup.ResolvedAbsent,
route.OperationKind,
GameTime: 1d,
PhysicsPlacementClass.Ordinary,
route.SetPositionFlags);
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
fixture.Lifetime.Physics.SetPosition.PrepareMover(
placement, preparation, out RuntimeSetPositionCommand command));
RuntimeSetPositionOutcome outcome = fixture.Lifetime.Physics.SetPosition
.SubmitPreparedPlacement(placement, command);
Assert.Equal(RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
outcome.Status);
Assert.True(fixture.Lifetime.Physics.SetPosition
.AcknowledgeProjection(outcome.Projection));
}
private static void DeleteEntity(Fixture fixture)
{
Assert.True(fixture.Lifetime.TryAcceptDelete(
new DeleteObject.Parsed(
fixture.Record.ServerGuid, fixture.Record.Incarnation),
isLocalPlayer: false,
removeRetainedObject: false,
out RuntimeEntityDeleteAcceptance acceptance));
fixture.Lifetime.CompleteAcceptedDelete(acceptance);
Assert.Null(fixture.Lifetime.RetireCanonicalOnly(fixture.Record));
}
private static WorldSession.EntitySpawn Spawn(
uint guid,
ushort incarnation,
bool includePosition = true,
uint setupTableId = 0u,
uint rawState = (uint)(PhysicsStateFlags.Gravity
| PhysicsStateFlags.ReportCollisions),
float? friction = 0.5f,
float? elasticity = 0.05f,
float? translucency = null,
PhysicsMovementData? movement = null)
{
CreateObject.ServerPosition? position = includePosition
? new CreateObject.ServerPosition(Cell, 1f, 2f, 3f, 1f, 0f, 0f, 0f)
: null;
var timestamps = new PhysicsTimestamps(
Position: 1,
Movement: 1,
State: 1,
Vector: 1,
Teleport: 0,
ServerControlledMove: 1,
ForcePosition: 0,
ObjDesc: 1,
Instance: incarnation);
var physics = new PhysicsSpawnData(
RawState: rawState,
Position: position,
Movement: movement,
AnimationFrame: null,
SetupTableId: setupTableId == 0u ? null : setupTableId,
MotionTableId: 0x09000001u,
SoundTableId: null,
PhysicsScriptTableId: null,
Parent: null,
Children: null,
Scale: 1f,
Friction: friction,
Elasticity: elasticity,
Translucency: translucency,
Velocity: new Vector3(1f, 2f, 0.5f),
Acceleration: null,
AngularVelocity: new Vector3(0f, 0f, 0.25f),
DefaultScriptType: null,
DefaultScriptIntensity: null,
Timestamps: timestamps);
return new WorldSession.EntitySpawn(
Guid: guid,
Position: position,
SetupTableId: setupTableId == 0u ? null : setupTableId,
AnimPartChanges: Array.Empty<CreateObject.AnimPartChange>(),
TextureChanges: Array.Empty<CreateObject.TextureChange>(),
SubPalettes: Array.Empty<CreateObject.SubPaletteSwap>(),
BasePaletteId: null,
ObjScale: 1f,
Name: "remote-entry-fixture",
ItemType: null,
MotionState: null,
MotionTableId: 0x09000001u,
PhysicsState: physics.RawState,
ObjectDescriptionFlags: 0x8u,
Friction: friction,
Elasticity: elasticity,
InstanceSequence: incarnation,
MovementSequence: 1,
ServerControlSequence: 1,
PositionSequence: 1,
Physics: physics);
}
private sealed class FakeCollisionSource(
uint expectedSetupTableId,
FlatSetupCollision setup) : IPreparedCollisionSource
{
internal int ReadCount { get; private set; }
internal PreparedAssetReadStatus Status { get; set; } =
PreparedAssetReadStatus.Loaded;
public PreparedAssetPresence ProbeCollision(
PakAssetType type, uint sourceFileId) =>
PreparedAssetPresence.Available;
public PreparedCollisionReadResult<FlatSetupCollision> ReadSetupCollision(
uint sourceFileId,
CancellationToken cancellationToken = default)
{
ReadCount++;
Assert.Equal(expectedSetupTableId, sourceFileId);
return Status switch
{
PreparedAssetReadStatus.Loaded =>
PreparedCollisionReadResult<FlatSetupCollision>.Loaded(setup),
PreparedAssetReadStatus.Corrupt =>
PreparedCollisionReadResult<FlatSetupCollision>.Corrupt,
_ => PreparedCollisionReadResult<FlatSetupCollision>.Missing,
};
}
public PreparedCollisionReadResult<FlatGfxObjCollisionAsset>
ReadGfxObjCollision(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException(
"Only ReadSetupCollision is exercised by these tests.");
public PreparedCollisionReadResult<FlatCellStructureCollisionAsset>
ReadCellStructureCollision(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException(
"Only ReadSetupCollision is exercised by these tests.");
public PreparedCollisionReadResult<FlatEnvCellTopology>
ReadEnvCellTopology(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException(
"Only ReadSetupCollision is exercised by these tests.");
public PreparedCollisionSourceStats CollisionStats => default;
public void Dispose()
{
}
}
private sealed class Fixture : IDisposable
{
internal Fixture(
bool residentWorld,
uint setupTableId = 0u,
uint rawState = (uint)(PhysicsStateFlags.Gravity
| PhysicsStateFlags.ReportCollisions),
bool isLocalPlayer = false)
{
if (residentWorld)
{
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);
Lifetime = new RuntimeEntityObjectLifetime(engine);
}
else
{
Lifetime = new RuntimeEntityObjectLifetime();
}
var generation = new RuntimeGenerationToken(1UL);
Lifetime.BindEventContext(() => generation, static () => 1UL);
// The SAME conductor instance RuntimeEntityObjectLifetime itself
// constructs and wires into the residence's multicast retirement
// fan-out and BeginSessionClear — never a standalone copy — so
// these tests exercise the real production wiring.
Conductor = Lifetime.RemoteFirstEntry;
Record = Lifetime.RegisterEntityWithInitialResidence(
Spawn(
0x70090002u,
incarnation: 1,
setupTableId: setupTableId,
rawState: rawState),
isLocalPlayer).Canonical!;
Assert.True(Lifetime.TryGetInitialCreateResidence(
Record, out RuntimeInitialCreateResidenceLease lease));
Lease = lease;
CollisionSource = new FakeCollisionSource(
setupTableId,
new FlatSetupCollision(
ImmutableArray<FlatCollisionCylinder>.Empty,
[new FlatCollisionSphere(Vector3.Zero, 0.48f)],
height: 0f,
radius: 0f,
stepUpHeight: 0.4f,
stepDownHeight: 0.4f));
}
internal RuntimeEntityObjectLifetime Lifetime { get; }
internal RuntimeRemoteFirstEntryState Conductor { get; }
internal RuntimeEntityRecord Record { get; set; }
internal RuntimeInitialCreateResidenceLease Lease { get; set; }
internal FakeCollisionSource CollisionSource { get; }
internal RuntimeEntityKey Key => Record.Key!.Value;
internal RuntimeRemoteFirstEntryStatus Advance(
out RuntimeInitialCreateExecutionReceipt receipt) =>
Advance(out receipt, out _);
internal RuntimeRemoteFirstEntryStatus Advance(
out RuntimeInitialCreateExecutionReceipt receipt,
out RuntimeRemoteBodyConstructionReceipt construction) =>
Conductor.Advance(
Record,
Lease.Token,
CollisionSource,
gameTime: 10d,
NoContact,
out receipt,
out construction);
public void Dispose() => Lifetime.Dispose();
}
}