using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Core.Physics; using AcDream.Runtime.Entities; using AcDream.Runtime.Physics; using AcDream.Core.Physics.Motion; using AcDream.Core.World.Cells; using System.Numerics; namespace AcDream.Runtime.Tests.Physics; public sealed class RuntimePhysicsStateTests { [Fact] public void EntityLifetimeConstructsOneCanonicalProductionPhysicsWorld() { using var lifetime = new RuntimeEntityObjectLifetime(); Assert.Same( lifetime.Physics.DataCache, lifetime.Physics.Engine.DataCache); Assert.True(lifetime.Physics.CaptureOwnership().OwnsProductionDataCache); Assert.Equal(0, lifetime.Physics.CaptureOwnership().LandblockCount); Assert.False(lifetime.Physics.CaptureOwnership().IsDisposed); } [Fact] public void ConcurrentRuntimeLifetimesDoNotShareMutablePhysicsState() { using var first = new RuntimeEntityObjectLifetime(); using var second = new RuntimeEntityObjectLifetime(); Assert.NotSame(first.Physics, second.Physics); Assert.NotSame(first.Physics.Engine, second.Physics.Engine); Assert.NotSame(first.Physics.DataCache, second.Physics.DataCache); Assert.NotSame( first.Physics.Engine.ShadowObjects, second.Physics.Engine.ShadowObjects); Assert.NotSame( first.Physics.DataCache.CellGraph, second.Physics.DataCache.CellGraph); } [Fact] public void PhysicsOwnerDisposesWithItsEntityLifetime() { var lifetime = new RuntimeEntityObjectLifetime(); var physics = lifetime.Physics; lifetime.Dispose(); Assert.True(physics.CaptureOwnership().IsDisposed); } [Fact] public void CanonicalRecordAndPhysicsOwnerOwnRemoteComponentAndWorksets() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimeEntityRecord record = lifetime.Entities.AddActive(Spawn(0x70000001u, 1)); var remote = new TestRemoteMotion(); lifetime.Entities.SetPhysicsBody(record, remote.Body); lifetime.Entities.SetRemoteMotion(record, remote); lifetime.Physics.AcknowledgeSpatialProjection(record, spatial: true); Assert.Same(remote, record.RemoteMotion); Assert.Same(remote.Body, record.PhysicsBody); Assert.True(lifetime.Physics.IsSpatialRoot(record)); Assert.True(lifetime.Physics.IsSpatialRemote(record, remote)); Assert.Equal(1, lifetime.Physics.SpatialRootCount); Assert.Equal(1, lifetime.Physics.SpatialRemoteCount); var roots = new List(); var remotes = new List(); lifetime.Physics.CopySpatialRootsTo(roots); lifetime.Physics.CopySpatialRemotesTo(remotes); Assert.Same(record, Assert.Single(roots)); Assert.Same(record, Assert.Single(remotes)); lifetime.Entities.SetRemoteMotion(record, null); lifetime.Physics.RefreshRemoteComponent(record); Assert.True(lifetime.Physics.IsSpatialRoot(record)); Assert.Equal(0, lifetime.Physics.SpatialRemoteCount); lifetime.Physics.RemoveSpatialProjection(record); Assert.Equal(0, lifetime.Physics.SpatialRootCount); } [Fact] public void CanonicalRecordAndPhysicsOwnerOwnProjectileComponentAndWorkset() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimeEntityRecord record = lifetime.Entities.AddActive(Spawn(0x70000031u, 1)); lifetime.Entities.SetFullCell(record, 0x01010001u, 0x0101FFFFu); lifetime.Entities.SetFinalPhysicsState( record, PhysicsStateFlags.ReportCollisions | PhysicsStateFlags.Missile | PhysicsStateFlags.AlignPath | PhysicsStateFlags.PathClipped); var body = new PhysicsBody { Position = new Vector3(10f, 20f, 5f), Orientation = Quaternion.Identity, InWorld = true, TransientState = TransientStateFlags.Active, }; body.SnapToCell( record.FullCellId, body.Position, body.Position); lifetime.Entities.SetPhysicsBody(record, body); var sphere = new ProjectileCollisionSphere( new Vector3(0.1f, 0f, 0f), 0.25f); IRuntimeProjectile projectile = lifetime.Physics.BindProjectile( record, body, sphere); lifetime.Physics.AcknowledgeSpatialProjection( record, spatial: true); Assert.Same(projectile, record.Projectile); Assert.Same(body, projectile.Body); Assert.Equal(sphere, projectile.CollisionSphere); Assert.True( lifetime.Physics.IsSpatialProjectile(record, projectile)); Assert.Equal(1, lifetime.Physics.SpatialProjectileCount); Assert.Equal( 1, lifetime.Physics.CaptureOwnership().SpatialProjectileCount); Assert.Same( projectile, lifetime.Physics.BindProjectile(record, body, sphere)); Assert.Throws(() => lifetime.Physics.BindProjectile( record, new PhysicsBody(), sphere)); lifetime.Physics.RemoveSpatialProjection(record); Assert.Equal(0, lifetime.Physics.SpatialProjectileCount); Assert.Same(projectile, record.Projectile); Assert.True(lifetime.Physics.ClearProjectile(record)); Assert.Null(record.Projectile); } [Fact] public void ProjectileAuthoritativeMutationInvalidatesSplitPrediction() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimeEntityRecord record = lifetime.Entities.AddActive(Spawn(0x70000032u, 1)); lifetime.Entities.SetFullCell(record, 0x01010001u, 0x0101FFFFu); lifetime.Entities.SetFinalPhysicsState( record, PhysicsStateFlags.ReportCollisions | PhysicsStateFlags.Missile | PhysicsStateFlags.AlignPath | PhysicsStateFlags.PathClipped); var body = new PhysicsBody { Position = new Vector3(10f, 20f, 5f), Orientation = Quaternion.Identity, InWorld = true, TransientState = TransientStateFlags.Active, }; body.set_velocity(new Vector3(10f, 0f, 0f)); body.SnapToCell( record.FullCellId, body.Position, body.Position); lifetime.Entities.SetPhysicsBody(record, body); IRuntimeProjectile projectile = lifetime.Physics.BindProjectile( record, body, new ProjectileCollisionSphere(Vector3.Zero, 0.25f)); lifetime.Physics.AcknowledgeSpatialProjection( record, spatial: true); var updater = new RuntimeProjectilePhysicsUpdater(lifetime.Physics); Assert.True(updater.TryBegin( record, quantum: 0.05f, record.ObjectClockEpoch, externalOwnerValid: null, out RuntimeProjectilePhysicsCommit commit)); lifetime.Entities.AdvanceVectorAuthority(record); Vector3 correction = new(7f, 8f, 9f); Assert.True(updater.ApplyAuthoritativeVector( record, record.VectorAuthorityVersion, record.VelocityAuthorityVersion, correction, Vector3.UnitZ, currentTime: 1.0)); Assert.True( projectile.PredictionAuthorityVersion > commit.PredictionAuthorityVersion); Assert.False(updater.Complete( commit, liveCenterX: 1, liveCenterY: 1, _ => true)); Assert.Equal(correction, body.Velocity); Assert.Equal(Vector3.UnitZ, body.Omega); } [Fact] public void ProjectileQuantumPublishesOneImmutableRuntimeFrame() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimeEntityRecord record = lifetime.Entities.AddActive(Spawn(0x70000033u, 1)); lifetime.Entities.SetFullCell(record, 0x01010001u, 0x0101FFFFu); lifetime.Entities.SetFinalPhysicsState( record, PhysicsStateFlags.ReportCollisions | PhysicsStateFlags.Missile | PhysicsStateFlags.PathClipped); var body = new PhysicsBody { Position = new Vector3(10f, 20f, 5f), Orientation = Quaternion.Identity, InWorld = true, TransientState = TransientStateFlags.Active, }; body.SnapToCell( record.FullCellId, body.Position, body.Position); lifetime.Entities.SetPhysicsBody(record, body); lifetime.Physics.BindProjectile( record, body, new ProjectileCollisionSphere(Vector3.Zero, 0.25f)); lifetime.Physics.AcknowledgeSpatialProjection( record, spatial: true); var updater = new RuntimeProjectilePhysicsUpdater(lifetime.Physics); Assert.True(updater.TryBegin( record, quantum: 0.05f, record.ObjectClockEpoch, externalOwnerValid: null, out RuntimeProjectilePhysicsCommit commit)); int acknowledgements = 0; RuntimePhysicsFrameSnapshot published = default; Assert.True(updater.Complete( commit, liveCenterX: 1, liveCenterY: 1, snapshot => { published = snapshot; acknowledgements++; return true; })); Assert.Equal(1, acknowledgements); Assert.Equal(body.Position, published.Position); Assert.Equal(body.Orientation, published.Orientation); Assert.Equal(record.FullCellId, published.FullCellId); Assert.Throws(() => updater.Complete( commit, liveCenterX: 1, liveCenterY: 1, _ => true)); } [Fact] public void EqualLocalKeysInConcurrentRuntimesDoNotShareWorksets() { using var first = new RuntimeEntityObjectLifetime(); using var second = new RuntimeEntityObjectLifetime(); RuntimeEntityRecord firstRecord = first.Entities.AddActive(Spawn(0x70000002u, 1)); RuntimeEntityRecord secondRecord = second.Entities.AddActive(Spawn(0x70000003u, 1)); Assert.Equal(firstRecord.Key, secondRecord.Key); first.Physics.AcknowledgeSpatialProjection(firstRecord, spatial: true); Assert.True(first.Physics.IsSpatialRoot(firstRecord)); Assert.False(second.Physics.IsSpatialRoot(secondRecord)); Assert.Equal(1, first.Physics.SpatialRootCount); Assert.Equal(0, second.Physics.SpatialRootCount); } [Fact] public void CollisionAdmissionIsExactRuntimeScopedAndWithdrawalIsTyped() { using var first = new RuntimeEntityObjectLifetime(); using var second = new RuntimeEntityObjectLifetime(); RuntimeCollisionAdmission admission = first.Physics.BeginCollisionAdmission(0xA9B4FFFFu); RuntimeCollisionAdmission newer = first.Physics.BeginCollisionAdmission(0xA9B4FFFFu); Assert.Throws(() => first.Physics.PrepareCollisionGeneration(admission)); Assert.Throws(() => second.Physics.PrepareCollisionGeneration(newer)); using PreparedLandblockCollisionGeneration prepared = first.Physics.PrepareCollisionGeneration(newer); first.Physics.StageCollisionAssets( newer, prepared, CollisionAssets(0xA9B4FFFFu)); RuntimeCollisionGenerationCommit commit = CommitPrepared(first.Physics, newer, prepared); RuntimeCollisionAcknowledgement completed = commit.Acknowledgement; Assert.True(commit.Committed); Assert.True(completed.WasResident); Assert.Equal(1, first.Physics.Engine.LandblockCount); Assert.Equal( 0, first.Physics.CaptureOwnership().CollisionAdmissionCount); Assert.Throws(() => CommitPrepared(first.Physics, newer, prepared)); RuntimeCollisionAcknowledgement withdrawn = CompleteWithdrawal(first.Physics, 0xA9B4FFFFu) .Acknowledgement; Assert.True(withdrawn.WasResident); Assert.True(withdrawn.Generation > completed.Generation); Assert.Equal(0, first.Physics.Engine.LandblockCount); } [Fact] public void CollisionGenerationKeepsPreviousWorldVisibleUntilOneCommitNotification() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; RuntimeCollisionAdmission firstAdmission = physics.BeginCollisionAdmission(0xA9B4FFFFu); using (PreparedLandblockCollisionGeneration first = physics.PrepareCollisionGeneration(firstAdmission)) { physics.StageCollisionAssets( firstAdmission, first, CollisionAssets(0xA9B4FFFFu, terrainHeight: 10f)); Assert.True(CommitPrepared(physics, firstAdmission, first).Committed); } int notifications = 0; physics.CollisionGenerationCommitted += _ => notifications++; RuntimeCollisionAdmission replacementAdmission = physics.BeginCollisionAdmission(0xA9B4FFFFu); using PreparedLandblockCollisionGeneration replacement = physics.PrepareCollisionGeneration(replacementAdmission); physics.StageCollisionAssets( replacementAdmission, replacement, CollisionAssets(0xA9B4FFFFu, terrainHeight: 25f)); Assert.Equal(10f, physics.Engine.SampleTerrainZ(1f, 1f)); Assert.Equal(0, notifications); RuntimeCollisionGenerationCommit committed = CommitPrepared(physics, replacementAdmission, replacement); Assert.True(committed.Committed); Assert.Equal(25f, physics.Engine.SampleTerrainZ(1f, 1f)); Assert.Equal(1, notifications); } [Fact] public void CollisionGenerationRejectsStaleReplacementWithoutMutatingActiveWorld() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; RuntimeCollisionAdmission stale = physics.BeginCollisionAdmission(0xA9B4FFFFu); using PreparedLandblockCollisionGeneration preparedStale = physics.PrepareCollisionGeneration(stale); physics.StageCollisionAssets( stale, preparedStale, CollisionAssets(0xA9B4FFFFu, terrainHeight: 10f)); RuntimeCollisionAdmission current = physics.BeginCollisionAdmission(0xA9B4FFFFu); Assert.Throws(() => CommitPrepared(physics, stale, preparedStale)); physics.CancelCollisionGeneration(stale, preparedStale); Assert.Equal( 1, physics.CaptureOwnership().CollisionAdmissionCount); Assert.False(physics.Engine.IsLandblockTerrainResident(0xA9B4FFFFu)); using PreparedLandblockCollisionGeneration preparedCurrent = physics.PrepareCollisionGeneration(current); physics.StageCollisionAssets( current, preparedCurrent, CollisionAssets(0xA9B4FFFFu, terrainHeight: 20f)); Assert.True(CommitPrepared(physics, current, preparedCurrent).Committed); Assert.Equal(20f, physics.Engine.SampleTerrainZ(1f, 1f)); } [Fact] public void ArmedWithdrawnOwnerStateChangeWritesThroughSealedGeneration() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; RuntimeCollisionAdmission initialAdmission = physics.BeginCollisionAdmission(0x0101FFFFu); using (PreparedLandblockCollisionGeneration initial = physics.PrepareCollisionGeneration(initialAdmission)) { physics.StageCollisionAssets( initialAdmission, initial, CollisionAssets(0x0101FFFFu, terrainHeight: 5f)); Assert.True(CommitPrepared(physics, initialAdmission, initial).Committed); } physics.Engine.ShadowObjects.Register( 42u, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, 0x0101FFFFu, seedCellId: 0x01010001u, isStatic: false); physics.Engine.ShadowObjects.RemoveLandblock(0x0101FFFFu); Assert.Equal(0, physics.Engine.ShadowObjects.TotalRegistered); Assert.Equal(1, physics.Engine.ShadowObjects.RetainedRegistrationCount); RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(0x0101FFFFu); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(0x0101FFFFu, terrainHeight: 15f)); uint owner = Assert.Single(SealPrepared(physics, admission, prepared)); physics.Engine.ShadowObjects.UpdatePhysicsState(owner, 0x14u); Assert.True(CompleteSealedCommit( physics, admission, prepared).Committed); Assert.Equal(15f, physics.Engine.SampleTerrainZ(1f, 1f)); ShadowEntry restored = Assert.Single( physics.Engine.ShadowObjects.AllEntriesForDebug()); Assert.Equal(owner, restored.EntityId); Assert.Equal(0x14u, restored.State); } [Fact] public void DenseReplacementSealsOneWorkUnitPerStepAndActivatesWithoutAllocation() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint landblockId = 0x0101FFFFu; RuntimeCollisionAdmission initialAdmission = physics.BeginCollisionAdmission(landblockId); using (PreparedLandblockCollisionGeneration initial = physics.PrepareCollisionGeneration(initialAdmission)) { physics.StageCollisionAssets( initialAdmission, initial, CollisionAssets(landblockId, terrainHeight: 5f)); Assert.True(CommitPrepared( physics, initialAdmission, initial).Committed); } const int ownerCount = 256; for (uint index = 0; index < ownerCount; index++) { physics.Engine.ShadowObjects.Register( entityId: 1000u + index, gfxObjId: 0x01000001u, worldPos: new Vector3( 8f + (index % 16u) * 0.25f, 8f + (index / 16u) * 0.25f, 0f), rotation: Quaternion.Identity, radius: 0.5f, worldOffsetX: 0f, worldOffsetY: 0f, landblockId, seedCellId: 0x01010001u, isStatic: false); } RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(landblockId); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(landblockId, terrainHeight: 15f)); int captureSteps = 0; RuntimeCollisionOwnerCaptureStep capture; do { capture = physics.AdvanceCollisionRetainedOwnerCapture( admission, prepared); captureSteps++; } while (!capture.Completed); Assert.True(captureSteps >= ownerCount); Assert.Equal(ownerCount, prepared.RetainedOwnerIds.Count); foreach (uint ownerId in prepared.RetainedOwnerIds) { physics.RefreshCollisionRetainedOwner( admission, prepared, ownerId); } int sealSteps = 0; int workUnits = 0; RuntimeCollisionSealStep seal; do { seal = physics.AdvanceCollisionGenerationSeal( admission, prepared); sealSteps++; Assert.InRange(seal.WorkUnits, 0, 1); workUnits += seal.WorkUnits; } while (!seal.Completed); Assert.False(seal.Restarted); Assert.True(sealSteps > ownerCount); Assert.True(workUnits > ownerCount); Assert.False(physics.CommitCollisionGeneration( admission, prepared).Committed); _ = SealPrepared(physics, admission, prepared); _ = GC.GetAllocatedBytesForCurrentThread(); long before = GC.GetAllocatedBytesForCurrentThread(); RuntimeCollisionGenerationCommit commit = physics.CommitCollisionGeneration(admission, prepared); long allocated = GC.GetAllocatedBytesForCurrentThread() - before; Assert.True(commit.Committed); Assert.Equal(0L, allocated); Assert.Equal(15f, physics.Engine.SampleTerrainZ(1f, 1f)); Assert.Equal(ownerCount, physics.Engine.ShadowObjects.TotalRegistered); } [Fact] public void UnrelatedOwnerMutationEveryStepCannotRestartTargetCaptureOrSeal() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; RuntimeCollisionAdmission initialAdmission = physics.BeginCollisionAdmission(target); using (PreparedLandblockCollisionGeneration initial = physics.PrepareCollisionGeneration(initialAdmission)) { physics.StageCollisionAssets( initialAdmission, initial, CollisionAssets(target, terrainHeight: 5f)); Assert.True(CommitPrepared( physics, initialAdmission, initial).Committed); } physics.Engine.ShadowObjects.Register( 42u, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, target, seedCellId: 0x01010001u, isStatic: false); physics.Engine.ShadowObjects.Register( 99u, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, 0x0303FFFFu, seedCellId: 0x03030001u, isStatic: false); RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(target, terrainHeight: 15f)); int steps = 0; RuntimeCollisionOwnerCaptureStep capture; do { physics.Engine.ShadowObjects.UpdatePhysicsState( 99u, (uint)(steps + 1)); capture = physics.AdvanceCollisionRetainedOwnerCapture( admission, prepared); Assert.False(capture.Restarted); // Admission now materializes its O(1) root snapshot one leaf per // step before the target-owner scan. Unrelated owner churn must // not restart either cursor. Assert.True(++steps < 512); } while (!capture.Completed); Assert.Equal(42u, Assert.Single(prepared.RetainedOwnerIds)); physics.RefreshCollisionRetainedOwner(admission, prepared, 42u); RuntimeCollisionSealStep seal; do { physics.Engine.ShadowObjects.UpdatePhysicsState( 99u, (uint)(steps + 1)); seal = physics.AdvanceCollisionGenerationSeal( admission, prepared); Assert.False(seal.Restarted); Assert.InRange(seal.WorkUnits, 0, 1); Assert.True(++steps < 256); } while (!seal.Completed); Assert.True(CompleteSealedCommit( physics, admission, prepared).Committed); Assert.Equal(15f, physics.Engine.SampleTerrainZ(1f, 1f)); } [Fact] public void TwoUnrelatedOwnersMovingEverySealStepCannotStarveActivation() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; foreach (uint ownerId in new[] { 99u, 100u }) { physics.Engine.ShadowObjects.Register( ownerId, 0x01000001u, new Vector3(10f + ownerId - 99u, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, 0x0303FFFFu, seedCellId: 0x03030001u, isStatic: false); } RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(target, terrainHeight: 15f)); int steps = 0; RuntimeCollisionOwnerCaptureStep capture; do { foreach (uint ownerId in new[] { 99u, 100u }) { physics.Engine.ShadowObjects.UpdatePhysicsState( ownerId, (uint)(steps + ownerId)); } capture = physics.AdvanceCollisionRetainedOwnerCapture( admission, prepared); Assert.False(capture.Restarted); Assert.True(++steps < 512); } while (!capture.Completed); while (true) { foreach (uint ownerId in new[] { 99u, 100u }) { physics.Engine.ShadowObjects.UpdatePosition( ownerId, new Vector3(10f + ownerId - 99u + steps, 10f, 0f), Quaternion.Identity, 0f, 0f, 0x0303FFFFu, seedCellId: 0x03030001u); physics.Engine.ShadowObjects.UpdatePhysicsState( ownerId, (uint)(steps + ownerId)); } RuntimeCollisionSealStep seal = physics.AdvanceCollisionGenerationSeal(admission, prepared); Assert.False(seal.Restarted); Assert.InRange(seal.WorkUnits, 0, 1); Assert.True(++steps < 512); if (seal.Completed) break; } Assert.True(CompleteSealedCommit( physics, admission, prepared).Committed); ShadowEntry[] entries = physics.Engine.ShadowObjects .AllEntriesForDebug() .OrderBy(entry => entry.EntityId) .ToArray(); Assert.Equal(new[] { 99u, 100u }, entries.Select(entry => entry.EntityId)); foreach (ShadowEntry entry in entries) { Assert.Equal((uint)(steps - 1) + entry.EntityId, entry.State); Assert.Equal( 10f + entry.EntityId - 99u + steps - 1, entry.Position.X); } } [Fact] public void TwoRelevantOwnersMovingEverySealStepConvergeThroughWriteThrough() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; RuntimeCollisionAdmission initialAdmission = physics.BeginCollisionAdmission(target); using (PreparedLandblockCollisionGeneration initial = physics.PrepareCollisionGeneration(initialAdmission)) { physics.StageCollisionAssets( initialAdmission, initial, CollisionAssets(target, terrainHeight: 5f)); Assert.True(CommitPrepared( physics, initialAdmission, initial).Committed); } foreach (uint ownerId in new[] { 42u, 43u }) { physics.Engine.ShadowObjects.Register( ownerId, 0x01000001u, new Vector3(10f + ownerId - 42u, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, target, seedCellId: 0x01010001u, isStatic: false); } RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(target, terrainHeight: 15f)); while (!physics.AdvanceCollisionRetainedOwnerCapture( admission, prepared).Completed) { } foreach (uint ownerId in prepared.RetainedOwnerIds) physics.RefreshCollisionRetainedOwner(admission, prepared, ownerId); int step = 0; RuntimeCollisionSealStep seal; do { step++; physics.Engine.ShadowObjects.UpdatePosition( 42u, new Vector3(10f + step * 0.01f, 10f, 0f), Quaternion.Identity, 0f, 0f, target, seedCellId: 0x01010001u); physics.Engine.ShadowObjects.UpdatePosition( 43u, new Vector3(11f + step * 0.01f, 10f, 0f), Quaternion.Identity, 0f, 0f, target, seedCellId: 0x01010001u); seal = physics.AdvanceCollisionGenerationSeal( admission, prepared); Assert.False(seal.Restarted); Assert.InRange(seal.WorkUnits, 0, 1); Assert.True(step < 256); } while (!seal.Completed); Assert.True(CompleteSealedCommit( physics, admission, prepared).Committed); ShadowEntry[] entries = physics.Engine.ShadowObjects .AllEntriesForDebug() .OrderBy(entry => entry.EntityId) .ToArray(); Assert.Equal(2, entries.Length); Assert.Equal(10f + step * 0.01f, entries[0].Position.X, 4); Assert.Equal(11f + step * 0.01f, entries[1].Position.X, 4); } [Fact] public void FirstLoadWithNewStaticBucketActivatesWithZeroAllocation() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(target, terrainHeight: 12f)); prepared.Engine.ShadowObjects.Register( 500u, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, target, seedCellId: 0x01010001u, isStatic: true); _ = SealPrepared(physics, admission, prepared); PhysicsDataCache cacheFacade = physics.DataCache; CellGraph graphFacade = physics.DataCache.CellGraph; ShadowObjectRegistry shadowFacade = physics.Engine.ShadowObjects; int notifications = 0; physics.CollisionGenerationCommitted += _ => notifications++; Assert.False(physics.CommitCollisionGeneration( admission, prepared).Committed); _ = SealPrepared(physics, admission, prepared); _ = GC.GetAllocatedBytesForCurrentThread(); long before = GC.GetAllocatedBytesForCurrentThread(); RuntimeCollisionGenerationCommit commit = physics.CommitCollisionGeneration(admission, prepared); long allocated = GC.GetAllocatedBytesForCurrentThread() - before; Assert.True(commit.Committed); Assert.Equal(0L, allocated); Assert.Equal(1, notifications); Assert.Same(cacheFacade, physics.DataCache); Assert.Same(graphFacade, physics.DataCache.CellGraph); Assert.Same(shadowFacade, physics.Engine.ShadowObjects); Assert.Equal(12f, physics.Engine.SampleTerrainZ(1f, 1f)); Assert.Equal(500u, Assert.Single( physics.Engine.ShadowObjects.AllEntriesForDebug()).EntityId); } [Fact] public void DenseResidentWorldAdmissionIsConstantAndMaterializesOneLeafPerStep() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const int residentLandblocks = 32; for (int index = 0; index < residentLandblocks; index++) { uint prefix = (uint)(0x10 + index) << 24 | 0x010000u; uint landblockId = prefix | 0xFFFFu; RuntimeLandblockCollisionAssets assets = CollisionAssets( landblockId, terrainHeight: index); physics.Engine.AddLandblock( assets.LandblockId, assets.Terrain, assets.CellSurfaces, assets.PortalPlanes, assets.WorldOffsetX, assets.WorldOffsetY); AddSyntheticCell(physics.DataCache, prefix | 0x0100u); physics.DataCache.RegisterBuildingForTest( prefix | 1u, SyntheticBuilding(Matrix4x4.Identity)); physics.Engine.ShadowObjects.Register( (uint)(10_000 + index), 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, landblockId, seedCellId: prefix | 1u, isStatic: false); } const uint target = 0x4001FFFFu; RuntimeCollisionAdmission warmAdmission = physics.BeginCollisionAdmission(0x3F01FFFFu); PreparedLandblockCollisionGeneration warm = physics.PrepareCollisionGeneration(warmAdmission); physics.CancelCollisionGeneration(warmAdmission, warm); RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); _ = GC.GetAllocatedBytesForCurrentThread(); long before = GC.GetAllocatedBytesForCurrentThread(); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); long admissionAllocation = GC.GetAllocatedBytesForCurrentThread() - before; Assert.InRange(admissionAllocation, 1L, 128L * 1024L); Assert.Equal(0, prepared.Engine.LandblockCount); int advances = 0; RuntimeCollisionPreparationStep step; do { step = physics.AdvanceCollisionGenerationPreparation( admission, prepared); Assert.InRange(step.WorkUnits, 0, 1); Assert.True(++advances < 10_000); } while (!step.Completed); Assert.True(advances > residentLandblocks); Assert.Equal(residentLandblocks, prepared.Engine.LandblockCount); physics.CancelCollisionGeneration(admission, prepared); } [Fact] public void ChangedEnvCellsBuildingsAndStaticBucketActivateWithZeroAllocation() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; const uint oldCell = 0x01010100u; const uint newCell = 0x01010101u; const uint oldBuilding = 0x01010001u; const uint newBuilding = 0x01010002u; RuntimeCollisionAdmission initialAdmission = physics.BeginCollisionAdmission(target); using (PreparedLandblockCollisionGeneration initial = physics.PrepareCollisionGeneration(initialAdmission)) { physics.StageCollisionAssets( initialAdmission, initial, CollisionAssets(target, terrainHeight: 5f)); AddSyntheticCell(initial.DataCache, oldCell); initial.DataCache.RegisterBuildingForTest( oldBuilding, SyntheticBuilding(Matrix4x4.Identity)); initial.Engine.ShadowObjects.Register( 600u, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, target, seedCellId: 0x01010001u, isStatic: true); Assert.True(CommitPrepared( physics, initialAdmission, initial).Committed); } RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); prepared.DataCache.RemoveCellsForLandblock(target); prepared.DataCache.RemoveBuildingsForLandblock(target); prepared.DataCache.CellGraph.RemoveEnvCellsForLandblock(target); prepared.Engine.ShadowObjects.DeregisterStaticOwnersForLandblock(target); physics.StageCollisionAssets( admission, prepared, CollisionAssets(target, terrainHeight: 15f)); AddSyntheticCell(prepared.DataCache, newCell); prepared.DataCache.RegisterBuildingForTest( newBuilding, SyntheticBuilding(Matrix4x4.CreateTranslation(1f, 0f, 0f))); prepared.Engine.ShadowObjects.Register( 601u, 0x01000001u, new Vector3(11f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, target, seedCellId: 0x01010002u, isStatic: true); _ = SealPrepared(physics, admission, prepared); PhysicsDataCache cacheFacade = physics.DataCache; CellGraph graphFacade = physics.DataCache.CellGraph; ShadowObjectRegistry shadowFacade = physics.Engine.ShadowObjects; Assert.False(physics.CommitCollisionGeneration( admission, prepared).Committed); _ = SealPrepared(physics, admission, prepared); _ = GC.GetAllocatedBytesForCurrentThread(); long before = GC.GetAllocatedBytesForCurrentThread(); RuntimeCollisionGenerationCommit commit = physics.CommitCollisionGeneration(admission, prepared); long allocated = GC.GetAllocatedBytesForCurrentThread() - before; Assert.True(commit.Committed); Assert.Equal(0L, allocated); Assert.Same(cacheFacade, physics.DataCache); Assert.Same(graphFacade, physics.DataCache.CellGraph); Assert.Same(shadowFacade, physics.Engine.ShadowObjects); Assert.Null(physics.DataCache.GetCellStruct(oldCell)); Assert.NotNull(physics.DataCache.GetCellStruct(newCell)); Assert.Null(physics.DataCache.GetBuilding(oldBuilding)); Assert.NotNull(physics.DataCache.GetBuilding(newBuilding)); Assert.Equal(601u, Assert.Single( physics.Engine.ShadowObjects.AllEntriesForDebug()).EntityId); } [Fact] public void ConcurrentPreparedLandblocksRebaseBeforeSecondActivation() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint firstLandblock = 0x0101FFFFu; const uint secondLandblock = 0x0202FFFFu; RuntimeCollisionAdmission firstAdmission = physics.BeginCollisionAdmission(firstLandblock); RuntimeCollisionAdmission secondAdmission = physics.BeginCollisionAdmission(secondLandblock); using PreparedLandblockCollisionGeneration first = physics.PrepareCollisionGeneration(firstAdmission); using PreparedLandblockCollisionGeneration second = physics.PrepareCollisionGeneration(secondAdmission); physics.StageCollisionAssets( firstAdmission, first, CollisionAssets(firstLandblock, terrainHeight: 5f)); physics.StageCollisionAssets( secondAdmission, second, CollisionAssets(secondLandblock, terrainHeight: 15f)); first.Engine.ShadowObjects.Register( 700u, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, firstLandblock, seedCellId: 0x01010001u, isStatic: true); _ = SealPrepared(physics, firstAdmission, first); _ = SealPrepared(physics, secondAdmission, second); Assert.False(physics.CommitCollisionGeneration( firstAdmission, first).Committed); _ = SealPrepared(physics, firstAdmission, first); _ = GC.GetAllocatedBytesForCurrentThread(); long firstBefore = GC.GetAllocatedBytesForCurrentThread(); Assert.True(CompleteSealedCommit( physics, firstAdmission, first).Committed); long firstAllocated = GC.GetAllocatedBytesForCurrentThread() - firstBefore; Assert.Equal(0L, firstAllocated); Assert.True(second.IsSealed); Assert.True(physics.Engine.IsLandblockTerrainResident(firstLandblock)); Assert.False(physics.Engine.IsLandblockTerrainResident(secondLandblock)); _ = SealPrepared(physics, secondAdmission, second); Assert.False(physics.CommitCollisionGeneration( secondAdmission, second).Committed); _ = SealPrepared(physics, secondAdmission, second); _ = GC.GetAllocatedBytesForCurrentThread(); long secondBefore = GC.GetAllocatedBytesForCurrentThread(); Assert.True(CompleteSealedCommit( physics, secondAdmission, second).Committed); long secondAllocated = GC.GetAllocatedBytesForCurrentThread() - secondBefore; Assert.Equal(0L, secondAllocated); Assert.Equal(2, physics.Engine.LandblockCount); Assert.True(physics.Engine.IsLandblockTerrainResident(firstLandblock)); Assert.True(physics.Engine.IsLandblockTerrainResident(secondLandblock)); Assert.Equal(700u, Assert.Single( physics.Engine.ShadowObjects.AllEntriesForDebug()).EntityId); } [Fact] public void PostCommitOwnerMutationWinsOverQueuedPeerRebase() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint firstLandblock = 0x0101FFFFu; const uint secondLandblock = 0x0202FFFFu; RuntimeCollisionAdmission firstAdmission = physics.BeginCollisionAdmission(firstLandblock); using PreparedLandblockCollisionGeneration first = physics.PrepareCollisionGeneration(firstAdmission); physics.StageCollisionAssets( firstAdmission, first, CollisionAssets(firstLandblock)); first.Engine.ShadowObjects.Register( 701u, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, firstLandblock, seedCellId: 0x01010001u, isStatic: false); RuntimeCollisionAdmission secondAdmission = physics.BeginCollisionAdmission(secondLandblock); using PreparedLandblockCollisionGeneration second = physics.PrepareCollisionGeneration(secondAdmission); physics.StageCollisionAssets( secondAdmission, second, CollisionAssets(secondLandblock)); _ = SealPrepared(physics, firstAdmission, first); _ = SealPrepared(physics, secondAdmission, second); Assert.True(CompleteSealedCommit( physics, firstAdmission, first).Committed); physics.Engine.ShadowObjects.UpdatePosition( 701u, new Vector3(12f, 10f, 0f), Quaternion.Identity, 0f, 0f, firstLandblock, seedCellId: 0x01010001u); _ = SealPrepared(physics, secondAdmission, second); Assert.True(CompleteSealedCommit( physics, secondAdmission, second).Committed); Assert.Equal(12f, Assert.Single( physics.Engine.ShadowObjects.AllEntriesForDebug()).Position.X); } [Fact] public void ConcurrentSeamStaticRefloodsAgainstLaterTopology() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint north = 0xA9B4FFFFu; const uint south = 0xA9B3FFFFu; foreach ((uint landblock, float offsetY) in new[] { (north, 0f), (south, -192f), }) { RuntimeCollisionAdmission seed = physics.BeginCollisionAdmission(landblock); using PreparedLandblockCollisionGeneration initial = physics.PrepareCollisionGeneration(seed); physics.StageCollisionAssets( seed, initial, CollisionAssets(landblock) with { WorldOffsetY = offsetY, }); Assert.True(CommitPrepared(physics, seed, initial).Committed); } RuntimeCollisionAdmission northAdmission = physics.BeginCollisionAdmission(north); using PreparedLandblockCollisionGeneration northPrepared = physics.PrepareCollisionGeneration(northAdmission); physics.StageCollisionAssets( northAdmission, northPrepared, CollisionAssets(north)); northPrepared.Engine.ShadowObjects.Register( 702u, 0x01000001u, new Vector3(150f, 0.2f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, north, seedCellId: 0xA9B40031u, isStatic: true); Assert.True(northPrepared.Engine.ShadowObjects.HasOwnerRowsInLandblock( 702u, south)); RuntimeCollisionAdmission southAdmission = physics.BeginCollisionAdmission(south); using PreparedLandblockCollisionGeneration southPrepared = physics.PrepareCollisionGeneration(southAdmission); physics.StageCollisionAssets( southAdmission, southPrepared, CollisionAssets(south) with { WorldOffsetY = -192f, }); _ = SealPrepared(physics, northAdmission, northPrepared); _ = SealPrepared(physics, southAdmission, southPrepared); Assert.True(CompleteSealedCommit( physics, northAdmission, northPrepared).Committed); _ = SealPrepared(physics, southAdmission, southPrepared); Assert.True(CompleteSealedCommit( physics, southAdmission, southPrepared).Committed); Assert.True(physics.Engine.ShadowObjects.HasOwnerRowsInLandblock( 702u, south)); } [Fact] public void CancelledOlderPreparationNeverLeaksIntoLaterDraft() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint cancelledLandblock = 0x0101FFFFu; const uint survivingLandblock = 0x0202FFFFu; RuntimeCollisionAdmission cancelledAdmission = physics.BeginCollisionAdmission(cancelledLandblock); PreparedLandblockCollisionGeneration cancelled = physics.PrepareCollisionGeneration(cancelledAdmission); physics.StageCollisionAssets( cancelledAdmission, cancelled, CollisionAssets(cancelledLandblock, terrainHeight: 11f)); RuntimeCollisionAdmission survivingAdmission = physics.BeginCollisionAdmission(survivingLandblock); using PreparedLandblockCollisionGeneration surviving = physics.PrepareCollisionGeneration(survivingAdmission); physics.StageCollisionAssets( survivingAdmission, surviving, CollisionAssets(survivingLandblock, terrainHeight: 22f)); _ = SealPrepared(physics, cancelledAdmission, cancelled); physics.CancelCollisionGeneration(cancelledAdmission, cancelled); Assert.True(CommitPrepared( physics, survivingAdmission, surviving).Committed); Assert.False(physics.Engine.IsLandblockTerrainResident( cancelledLandblock)); Assert.True(physics.Engine.IsLandblockTerrainResident( survivingLandblock)); } [Fact] public void OutgoingTargetStaticMutationCannotResurrectOmittedOwner() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; RuntimeCollisionAdmission initialAdmission = physics.BeginCollisionAdmission(target); using (PreparedLandblockCollisionGeneration initial = physics.PrepareCollisionGeneration(initialAdmission)) { physics.StageCollisionAssets( initialAdmission, initial, CollisionAssets(target)); initial.Engine.ShadowObjects.Register( 500u, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, target, seedCellId: 0x01010001u, isStatic: true); Assert.True(CommitPrepared( physics, initialAdmission, initial).Committed); } RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); prepared.Engine.ShadowObjects.DeregisterStaticOwnersForLandblock(target); physics.StageCollisionAssets( admission, prepared, CollisionAssets(target, terrainHeight: 3f)); physics.Engine.ShadowObjects.UpdatePhysicsState(500u, 0x44u); Assert.True(CommitPrepared(physics, admission, prepared).Committed); Assert.Empty(physics.Engine.ShadowObjects.AllEntriesForDebug()); } [Fact] public void SamePrefixCurrentCellMoveAfterSealRebindsToNewRoot() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; const uint firstCell = 0x01010100u; const uint secondCell = 0x01010101u; RuntimeCollisionAdmission initialAdmission = physics.BeginCollisionAdmission(target); using (PreparedLandblockCollisionGeneration initial = physics.PrepareCollisionGeneration(initialAdmission)) { physics.StageCollisionAssets( initialAdmission, initial, CollisionAssets(target)); AddSyntheticCell(initial.DataCache, firstCell); AddSyntheticCell(initial.DataCache, secondCell); Assert.True(CommitPrepared( physics, initialAdmission, initial).Committed); } physics.Engine.UpdatePlayerCurrCell(firstCell); RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); prepared.DataCache.RemoveCellsForLandblock(target); prepared.DataCache.CellGraph.RemoveEnvCellsForLandblock(target); physics.StageCollisionAssets( admission, prepared, CollisionAssets(target, terrainHeight: 5f)); AddSyntheticCell(prepared.DataCache, firstCell); AddSyntheticCell(prepared.DataCache, secondCell); _ = SealPrepared(physics, admission, prepared); physics.Engine.UpdatePlayerCurrCell(secondCell); ObjCell oldRootCell = physics.DataCache.CellGraph.CurrCell!; Assert.True(CompleteSealedCommit( physics, admission, prepared).Committed); ObjCell? rebound = physics.DataCache.CellGraph.GetVisible(secondCell); Assert.NotNull(rebound); Assert.NotSame(oldRootCell, rebound); Assert.Same(rebound, physics.DataCache.CellGraph.CurrCell); } [Fact] public void ArmedOwnerLeavingTargetRemainsLiveInItsNewPrefix() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; const uint destination = 0x0202FFFFu; foreach (uint landblock in new[] { target, destination }) { RuntimeCollisionAdmission seed = physics.BeginCollisionAdmission(landblock); using PreparedLandblockCollisionGeneration initial = physics.PrepareCollisionGeneration(seed); physics.StageCollisionAssets( seed, initial, CollisionAssets(landblock, terrainHeight: 5f)); Assert.True(CommitPrepared(physics, seed, initial).Committed); } physics.Engine.ShadowObjects.Register( 42u, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, target, seedCellId: 0x01010001u, isStatic: false); RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(target, terrainHeight: 9f)); _ = SealPrepared(physics, admission, prepared); physics.Engine.ShadowObjects.UpdatePosition( 42u, new Vector3(11f, 10f, 0f), Quaternion.Identity, 0f, 0f, destination, seedCellId: 0x02020001u); Assert.True(CompleteSealedCommit( physics, admission, prepared).Committed); Assert.False(physics.Engine.ShadowObjects.HasOwnerRowsInLandblock( 42u, target)); Assert.True(physics.Engine.ShadowObjects.HasOwnerRowsInLandblock( 42u, destination)); Assert.Equal(11f, Assert.Single( physics.Engine.ShadowObjects.AllEntriesForDebug()).Position.X); } [Fact] public void ArmedOwnerIdReuseOutsideTargetKeepsNewIncarnationRows() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; const uint destination = 0x0202FFFFu; foreach (uint landblock in new[] { target, destination }) { RuntimeCollisionAdmission seed = physics.BeginCollisionAdmission(landblock); using PreparedLandblockCollisionGeneration initial = physics.PrepareCollisionGeneration(seed); physics.StageCollisionAssets( seed, initial, CollisionAssets(landblock)); Assert.True(CommitPrepared(physics, seed, initial).Committed); } physics.Engine.ShadowObjects.Register( 42u, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, target, seedCellId: 0x01010001u, isStatic: false); RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(target, terrainHeight: 8f)); _ = SealPrepared(physics, admission, prepared); physics.Engine.ShadowObjects.Deregister(42u); physics.Engine.ShadowObjects.Register( 42u, 0x01000002u, new Vector3(12f, 10f, 0f), Quaternion.Identity, 0.75f, 0f, 0f, destination, seedCellId: 0x02020001u, isStatic: false); Assert.True(CompleteSealedCommit( physics, admission, prepared).Committed); ShadowEntry entry = Assert.Single( physics.Engine.ShadowObjects.AllEntriesForDebug()); Assert.Equal(0x01000002u, entry.GfxObjId); Assert.Equal(12f, entry.Position.X); Assert.True(physics.Engine.ShadowObjects.HasOwnerRowsInLandblock( 42u, destination)); } [Fact] public void UnrelatedStateMutationIsJournaledAfterAllRowsChange() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; const uint unrelated = 0x0202FFFFu; foreach (uint landblock in new[] { target, unrelated }) { RuntimeCollisionAdmission seed = physics.BeginCollisionAdmission(landblock); using PreparedLandblockCollisionGeneration initial = physics.PrepareCollisionGeneration(seed); physics.StageCollisionAssets( seed, initial, CollisionAssets(landblock)); Assert.True(CommitPrepared(physics, seed, initial).Committed); } physics.Engine.ShadowObjects.Register( 99u, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, unrelated, state: 1u, seedCellId: 0x02020001u, isStatic: false); RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(target, terrainHeight: 6f)); physics.Engine.ShadowObjects.UpdatePhysicsState(99u, 0x1234u); Assert.True(CommitPrepared(physics, admission, prepared).Committed); Assert.All( physics.Engine.ShadowObjects.AllEntriesForDebug(), entry => Assert.Equal(0x1234u, entry.State)); } [Fact] public void PrefixOwnerSlotsReuseTombstonesUnderGuidChurn() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using (PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission)) { physics.StageCollisionAssets( admission, prepared, CollisionAssets(target)); Assert.True(CommitPrepared( physics, admission, prepared).Committed); } for (uint ownerId = 1u; ownerId <= 2_000u; ownerId++) { physics.Engine.ShadowObjects.Register( ownerId, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, target, seedCellId: 0x01010001u, isStatic: false); physics.Engine.ShadowObjects.Deregister(ownerId); } Assert.Equal(0, physics.Engine.ShadowObjects .PrefixOwnerSlotCapacityForDiagnostics(target)); Assert.Equal(0, physics.Engine.ShadowObjects .OwnerVersionCountForDiagnostics); } [Fact] public void CommittedPreparationRevokesItsStagingCollisionRoot() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(0x0101FFFFu); PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(0x0101FFFFu)); Assert.True(CommitPrepared(physics, admission, prepared).Committed); Assert.Throws( () => _ = prepared.Engine.LandblockCount); prepared.Dispose(); } [Fact] public void UnrelatedDemotionAndWithdrawalCannotBeResurrectedByDraft() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint demoted = 0x0101FFFFu; const uint withdrawn = 0x0202FFFFu; const uint replacement = 0x0303FFFFu; RuntimeCollisionAdmission first = physics.BeginCollisionAdmission(demoted); using (PreparedLandblockCollisionGeneration preparedFirst = physics.PrepareCollisionGeneration(first)) { physics.StageCollisionAssets( first, preparedFirst, CollisionAssets(demoted)); AddSyntheticCell(preparedFirst.DataCache, 0x01010100u); Assert.True(CommitPrepared( physics, first, preparedFirst).Committed); } RuntimeCollisionAdmission second = physics.BeginCollisionAdmission(withdrawn); using (PreparedLandblockCollisionGeneration preparedSecond = physics.PrepareCollisionGeneration(second)) { physics.StageCollisionAssets( second, preparedSecond, CollisionAssets(withdrawn)); Assert.True(CommitPrepared( physics, second, preparedSecond).Committed); } RuntimeCollisionAdmission third = physics.BeginCollisionAdmission(replacement); using PreparedLandblockCollisionGeneration preparedThird = physics.PrepareCollisionGeneration(third); physics.StageCollisionAssets( third, preparedThird, CollisionAssets(replacement)); Assert.True(CompleteDemotion(physics, demoted).WasResident); Assert.True(CompleteWithdrawal(physics, withdrawn).WasResident); Assert.True(CommitPrepared( physics, third, preparedThird).Committed); Assert.True(physics.Engine.IsLandblockTerrainResident(demoted)); Assert.False(physics.DataCache.CellGraph.Contains(0x01010100u)); Assert.False(physics.Engine.IsLandblockTerrainResident(withdrawn)); Assert.True(physics.Engine.IsLandblockTerrainResident(replacement)); } [Theory] [InlineData(false, false)] [InlineData(false, true)] [InlineData(true, false)] [InlineData(true, true)] public void PendingOrActivePeerRebaseCannotResurrectRetiredLandblock( bool withdraw, bool beginRebase) { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint retired = 0x0101FFFFu; const uint survivor = 0x0202FFFFu; const uint retiredCell = 0x01010100u; RuntimeCollisionAdmission retiredAdmission = physics.BeginCollisionAdmission(retired); using PreparedLandblockCollisionGeneration retiredPrepared = physics.PrepareCollisionGeneration(retiredAdmission); physics.StageCollisionAssets( retiredAdmission, retiredPrepared, CollisionAssets(retired, terrainHeight: 7f)); AddSyntheticCell(retiredPrepared.DataCache, retiredCell); RuntimeCollisionAdmission survivorAdmission = physics.BeginCollisionAdmission(survivor); using PreparedLandblockCollisionGeneration survivorPrepared = physics.PrepareCollisionGeneration(survivorAdmission); physics.StageCollisionAssets( survivorAdmission, survivorPrepared, CollisionAssets(survivor, terrainHeight: 9f)); while (!physics.AdvanceCollisionGenerationPreparation( survivorAdmission, survivorPrepared).Completed) { } _ = SealPrepared(physics, retiredAdmission, retiredPrepared); Assert.True(CompleteSealedCommit( physics, retiredAdmission, retiredPrepared).Committed); if (beginRebase) { for (int step = 0; step < 12; step++) { RuntimeCollisionSealStep started = physics.AdvanceCollisionGenerationSeal( survivorAdmission, survivorPrepared); Assert.False(started.Completed); Assert.InRange(started.WorkUnits, 0, 1); } } if (withdraw) Assert.True(CompleteWithdrawal(physics, retired).WasResident); else Assert.True(CompleteDemotion(physics, retired).WasResident); Assert.True(CommitPrepared( physics, survivorAdmission, survivorPrepared).Committed); Assert.Equal(!withdraw, physics.Engine.IsLandblockTerrainResident(retired)); Assert.Null(physics.DataCache.GetCellStruct(retiredCell)); Assert.False(physics.DataCache.CellGraph.Contains(retiredCell)); Assert.True(physics.Engine.IsLandblockTerrainResident(survivor)); } [Theory] [InlineData(0)] [InlineData(1)] [InlineData(2)] public void OutgoingStaticDeletionCannotEraseSameIdAuthoredReplacement( int deletionPhase) { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; const uint ownerId = 812u; RuntimeCollisionAdmission seedAdmission = physics.BeginCollisionAdmission(target); using (PreparedLandblockCollisionGeneration seed = physics.PrepareCollisionGeneration(seedAdmission)) { physics.StageCollisionAssets( seedAdmission, seed, CollisionAssets(target)); seed.Engine.ShadowObjects.Register( ownerId, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, target, seedCellId: 0x01010001u, isStatic: true); Assert.True(CommitPrepared( physics, seedAdmission, seed).Committed); } RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(target, terrainHeight: 4f)); prepared.Engine.ShadowObjects.Register( ownerId, 0x01000002u, new Vector3(13f, 10f, 0f), Quaternion.Identity, 0.75f, 0f, 0f, target, state: 0x55u, seedCellId: 0x01010002u, isStatic: true); if (deletionPhase == 1) { _ = physics.AdvanceCollisionGenerationPreparation( admission, prepared); } else if (deletionPhase == 2) { while (!physics.AdvanceCollisionGenerationPreparation( admission, prepared).Completed) { } } physics.Engine.ShadowObjects.Deregister(ownerId); Assert.True(CommitPrepared(physics, admission, prepared).Committed); ShadowEntry authored = Assert.Single( physics.Engine.ShadowObjects.AllEntriesForDebug(), entry => entry.EntityId == ownerId); Assert.Equal(0x01000002u, authored.GfxObjId); Assert.Equal(13f, authored.Position.X); Assert.Equal(0x55u, authored.State); } [Fact] public void OwnerJournalCoalescesUnrelatedChurnAcrossManyDrafts() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint ownerId = 900u; physics.Engine.ShadowObjects.Register( ownerId, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, 0x0101FFFFu, seedCellId: 0x01010001u, isStatic: false); physics.Engine.ShadowObjects.UpdatePhysicsState(ownerId, 1u); _ = GC.GetAllocatedBytesForCurrentThread(); long baselineBefore = GC.GetAllocatedBytesForCurrentThread(); for (uint version = 2u; version <= 10_001u; version++) physics.Engine.ShadowObjects.UpdatePhysicsState(ownerId, version); long baselineAllocated = GC.GetAllocatedBytesForCurrentThread() - baselineBefore; var admissions = new List(); var preparations = new List(); for (int index = 0; index < 32; index++) { uint landblock = ((uint)(0x20 + index) << 24) | 0x0001FFFFu; RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(landblock); admissions.Add(admission); preparations.Add(physics.PrepareCollisionGeneration(admission)); } physics.Engine.ShadowObjects.UpdatePhysicsState(ownerId, 10_002u); _ = GC.GetAllocatedBytesForCurrentThread(); long before = GC.GetAllocatedBytesForCurrentThread(); for (uint version = 10_003u; version <= 20_002u; version++) physics.Engine.ShadowObjects.UpdatePhysicsState(ownerId, version); long allocated = GC.GetAllocatedBytesForCurrentThread() - before; Assert.Equal(baselineAllocated, allocated); Assert.Equal(1, physics.CollisionOwnerJournalEntryCountForDiagnostics); for (int index = 0; index < admissions.Count; index++) { physics.CancelCollisionGeneration( admissions[index], preparations[index]); } Assert.Equal(0, physics.CollisionOwnerJournalEntryCountForDiagnostics); } [Fact] public void ManyUniqueOwnerMutationsReconcileOnePerSealAndCommitWithoutAllocation() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; const uint unrelated = 0x0202FFFFu; const int ownerCount = 512; RuntimeCollisionAdmission unrelatedAdmission = physics.BeginCollisionAdmission(unrelated); using (PreparedLandblockCollisionGeneration unrelatedPrepared = physics.PrepareCollisionGeneration(unrelatedAdmission)) { physics.StageCollisionAssets( unrelatedAdmission, unrelatedPrepared, CollisionAssets(unrelated)); Assert.True(CommitPrepared( physics, unrelatedAdmission, unrelatedPrepared).Committed); } for (uint index = 0; index < ownerCount; index++) { physics.Engine.ShadowObjects.Register( 30_000u + index, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, unrelated, seedCellId: 0x02020001u, isStatic: false); } RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(target)); _ = SealPrepared(physics, admission, prepared); for (uint index = 0; index < ownerCount; index++) { physics.Engine.ShadowObjects.UpdatePhysicsState( 30_000u + index, index + 1u); } Assert.False(physics.CommitCollisionGeneration( admission, prepared).Committed); int worked = 0; RuntimeCollisionSealStep seal; do { seal = physics.AdvanceCollisionGenerationSeal( admission, prepared); Assert.InRange(seal.WorkUnits, 0, 1); worked += seal.WorkUnits; } while (!seal.Completed); Assert.Equal(ownerCount, worked); Assert.False(physics.CommitCollisionGeneration( admission, prepared).Committed); _ = SealPrepared(physics, admission, prepared); _ = GC.GetAllocatedBytesForCurrentThread(); long before = GC.GetAllocatedBytesForCurrentThread(); RuntimeCollisionGenerationCommit commit = physics.CommitCollisionGeneration(admission, prepared); long allocated = GC.GetAllocatedBytesForCurrentThread() - before; Assert.True(commit.Committed); Assert.Equal(0L, allocated); Assert.Equal(0, physics.CollisionOwnerJournalEntryCountForDiagnostics); } [Fact] public void CompactedJournalSupersessionPreservesTheMissedSuffix() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint oldestTarget = 0x0101FFFFu; const uint survivingTarget = 0x0202FFFFu; RuntimeCollisionAdmission oldestAdmission = physics.BeginCollisionAdmission(oldestTarget); PreparedLandblockCollisionGeneration oldest = physics.PrepareCollisionGeneration(oldestAdmission); physics.Engine.ShadowObjects.Register( 41_000u, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, 0x0303FFFFu, seedCellId: 0x03030001u, isStatic: false); RuntimeCollisionAdmission survivingAdmission = physics.BeginCollisionAdmission(survivingTarget); using PreparedLandblockCollisionGeneration surviving = physics.PrepareCollisionGeneration(survivingAdmission); physics.StageCollisionAssets( survivingAdmission, surviving, CollisionAssets(survivingTarget)); physics.CancelCollisionGeneration(oldestAdmission, oldest); _ = SealPrepared(physics, survivingAdmission, surviving); RuntimeCollisionSealStep compacted = physics.AdvanceCollisionGenerationSeal( survivingAdmission, surviving); Assert.True(compacted.Completed); Assert.Equal(1, compacted.WorkUnits); Assert.Equal(0, physics.CollisionOwnerJournalEntryCountForDiagnostics); physics.Engine.ShadowObjects.Register( 42_000u, 0x01000001u, new Vector3(12f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, survivingTarget, seedCellId: 0x02020001u, isStatic: false); Assert.True(CompleteSealedCommit( physics, survivingAdmission, surviving).Committed); Assert.Contains( physics.Engine.ShadowObjects.AllEntriesForDebug(), entry => entry.EntityId == 42_000u); } [Fact] public void UnrelatedOwnerEnteringTargetAfterJournalScanWritesThroughExactly() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; const uint outside = 0x0202FFFFu; const uint ownerId = 43_000u; physics.Engine.ShadowObjects.Register( ownerId, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, outside, seedCellId: 0x02020001u, isStatic: false); RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(target)); physics.Engine.ShadowObjects.UpdatePhysicsState(ownerId, 1u); _ = SealPrepared(physics, admission, prepared); physics.Engine.ShadowObjects.UpdatePosition( ownerId, new Vector3(12f, 10f, 0f), Quaternion.Identity, 0f, 0f, target, seedCellId: 0x01010001u); Assert.True(CompleteSealedCommit( physics, admission, prepared).Committed); Assert.True(physics.Engine.ShadowObjects.HasOwnerRowsInLandblock( ownerId, target)); } [Fact] public void LateSamePrefixMutationOfScannedUnrelatedOwnerWritesThroughExactly() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; const uint outside = 0x0202FFFFu; const uint ownerId = 43_500u; physics.Engine.ShadowObjects.Register( ownerId, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, outside, state: 1u, seedCellId: 0x02020001u, isStatic: false); RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(target)); physics.Engine.ShadowObjects.UpdatePhysicsState(ownerId, 2u); _ = SealPrepared(physics, admission, prepared); physics.Engine.ShadowObjects.UpdatePosition( ownerId, new Vector3(14f, 10f, 0f), Quaternion.Identity, 0f, 0f, outside, seedCellId: 0x02020001u); physics.Engine.ShadowObjects.UpdatePhysicsState(ownerId, 0x55u); Assert.True(CompleteSealedCommit( physics, admission, prepared).Committed); ShadowEntry entry = Assert.Single( physics.Engine.ShadowObjects.AllEntriesForDebug(), item => item.EntityId == ownerId); Assert.Equal(14f, entry.Position.X); Assert.Equal(0x55u, entry.State); } [Fact] public void JournalTailCompactionRetiresItsFreeSlotMetadataIncrementally() { var journal = new CollisionOwnerMutationJournal(); const int ownerCount = 4_096; for (uint ownerId = 1u; ownerId <= ownerCount; ownerId++) _ = journal.Record(ownerId, ownerId, journal.NextSequence); journal.RequestCompactionBefore(journal.NextSequence); int steps = 0; while (journal.AdvanceCompaction()) Assert.True(++steps <= ownerCount * 2); Assert.Equal(0, journal.Count); Assert.Equal(0, journal.ActiveCount); CollisionOwnerMutationJournal.MutationRecord next = journal.Record( 99_999u, 1u, journal.NextSequence); Assert.Equal(0, next.SlotIndex); } [Fact] public void RetirementAfterSealBlocksCommitUntilItsCursorCompletes() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint retired = 0x0101FFFFu; const uint replacement = 0x0202FFFFu; const uint retiredCell = 0x01010100u; RuntimeCollisionAdmission retiredAdmission = physics.BeginCollisionAdmission(retired); using (PreparedLandblockCollisionGeneration retiredPrepared = physics.PrepareCollisionGeneration(retiredAdmission)) { physics.StageCollisionAssets( retiredAdmission, retiredPrepared, CollisionAssets(retired)); AddSyntheticCell(retiredPrepared.DataCache, retiredCell); Assert.True(CommitPrepared( physics, retiredAdmission, retiredPrepared).Committed); } RuntimeCollisionAdmission replacementAdmission = physics.BeginCollisionAdmission(replacement); using PreparedLandblockCollisionGeneration replacementPrepared = physics.PrepareCollisionGeneration(replacementAdmission); physics.StageCollisionAssets( replacementAdmission, replacementPrepared, CollisionAssets(replacement)); _ = SealPrepared( physics, replacementAdmission, replacementPrepared); Assert.True(CompleteWithdrawal(physics, retired).WasResident); Assert.False(physics.CommitCollisionGeneration( replacementAdmission, replacementPrepared).Committed); _ = SealPrepared( physics, replacementAdmission, replacementPrepared); Assert.True(CompleteSealedCommit( physics, replacementAdmission, replacementPrepared).Committed); Assert.False(physics.Engine.IsLandblockTerrainResident(retired)); Assert.Null(physics.DataCache.GetCellStruct(retiredCell)); } [Fact] public void MoreThanFixedRingWorthOfRetirementsRemainMeteredAndLossless() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(target)); _ = SealPrepared(physics, admission, prepared); for (uint index = 0; index < 300u; index++) { uint ordinal = index + 0x1000u; uint x = ordinal & 0xFFu; uint y = (ordinal >> 8) & 0xFFu; _ = CompleteWithdrawal( physics, (x << 24) | (y << 16) | 0xFFFFu); } Assert.False(physics.CommitCollisionGeneration( admission, prepared).Committed); int steps = 0; RuntimeCollisionSealStep seal; do { seal = physics.AdvanceCollisionGenerationSeal( admission, prepared); Assert.InRange(seal.WorkUnits, 0, 1); Assert.True(++steps < 100_000); } while (!seal.Completed); Assert.True(CompleteSealedCommit( physics, admission, prepared).Committed); } [Fact] public void EmptyOwnerPrefixContainersAreReclaimedAcrossUniquePrefixes() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; for (uint index = 1u; index <= 2_000u; index++) { uint x = index & 0xFFu; uint y = (index >> 8) & 0xFFu; uint prefix = (x << 24) | (y << 16); uint ownerId = 20_000u + index; physics.Engine.ShadowObjects.Register( ownerId, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, prefix | 0xFFFFu, seedCellId: prefix | 1u, isStatic: false); physics.Engine.ShadowObjects.Deregister(ownerId); } Assert.Equal(0, physics.Engine.ShadowObjects .PrefixOwnerContainerCountForDiagnostics); Assert.Equal(0, physics.Engine.ShadowObjects .OwnerVersionCountForDiagnostics); } [Fact] public void SealCursorRetainsCapturedSlotListsWhenPrefixContainerIsReclaimed() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; const uint ownerId = 41_000u; RuntimeCollisionAdmission seedAdmission = physics.BeginCollisionAdmission(target); using (PreparedLandblockCollisionGeneration seed = physics.PrepareCollisionGeneration(seedAdmission)) { physics.StageCollisionAssets( seedAdmission, seed, CollisionAssets(target)); Assert.True(CommitPrepared( physics, seedAdmission, seed).Committed); } physics.Engine.ShadowObjects.Register( ownerId, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, target, seedCellId: 0x01010001u, isStatic: false); RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(target, terrainHeight: 2f)); while (!physics.AdvanceCollisionRetainedOwnerCapture( admission, prepared).Completed) { } physics.RefreshCollisionRetainedOwner(admission, prepared, ownerId); RuntimeCollisionSealStep started; do { started = physics.AdvanceCollisionGenerationSeal( admission, prepared); Assert.False(started.Completed); } while (started.WorkUnits == 0); Assert.Equal(1, started.WorkUnits); physics.Engine.ShadowObjects.Deregister(ownerId); Assert.Equal(0, physics.Engine.ShadowObjects .PrefixOwnerContainerCountForDiagnostics); RuntimeCollisionSealStep seal; do { seal = physics.AdvanceCollisionGenerationSeal( admission, prepared); Assert.InRange(seal.WorkUnits, 0, 1); } while (!seal.Completed); Assert.True(CompleteSealedCommit( physics, admission, prepared).Committed); Assert.Empty(physics.Engine.ShadowObjects.AllEntriesForDebug()); } [Fact] public void LiveCurrentCellIsNotRolledBackByTopologyActivation() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint first = 0x0101FFFFu; const uint second = 0x0202FFFFu; foreach (uint landblock in new[] { first, second }) { RuntimeCollisionAdmission seed = physics.BeginCollisionAdmission(landblock); using PreparedLandblockCollisionGeneration initial = physics.PrepareCollisionGeneration(seed); physics.StageCollisionAssets( seed, initial, CollisionAssets(landblock)); Assert.True(CommitPrepared(physics, seed, initial).Committed); } RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(first); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(first, terrainHeight: 7f)); physics.Engine.UpdatePlayerCurrCell(0x02020001u); Assert.Equal(0x02020001u, physics.DataCache.CellGraph.CurrCell?.Id); Assert.True(CommitPrepared(physics, admission, prepared).Committed); Assert.Equal(0x02020001u, physics.DataCache.CellGraph.CurrCell?.Id); } [Fact] public void SpawnAndDeleteDuringStagingAreBothGenerationGated() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(0x0101FFFFu); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(0x0101FFFFu, terrainHeight: 12f)); Assert.Empty(SealPrepared(physics, admission, prepared)); physics.Engine.ShadowObjects.Register( 77u, 0x01000001u, new Vector3(10f, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, 0x0101FFFFu, seedCellId: 0x01010001u, isStatic: false); physics.Engine.ShadowObjects.Deregister(77u); Assert.True(CompleteSealedCommit( physics, admission, prepared).Committed); Assert.Empty(physics.Engine.ShadowObjects.AllEntriesForDebug()); } [Fact] public void TwoPostSealOwnersWriteThroughBeforeActivation() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimePhysicsState physics = lifetime.Physics; const uint target = 0x0101FFFFu; RuntimeCollisionAdmission admission = physics.BeginCollisionAdmission(target); using PreparedLandblockCollisionGeneration prepared = physics.PrepareCollisionGeneration(admission); physics.StageCollisionAssets( admission, prepared, CollisionAssets(target)); Assert.Empty(SealPrepared(physics, admission, prepared)); foreach (uint ownerId in new[] { 71u, 72u }) { physics.Engine.ShadowObjects.Register( ownerId, 0x01000001u, new Vector3(10f + ownerId - 71u, 10f, 0f), Quaternion.Identity, 0.5f, 0f, 0f, target, seedCellId: 0x01010001u, isStatic: false); } Assert.True(CompleteSealedCommit( physics, admission, prepared).Committed); Assert.Equal( new[] { 71u, 72u }, physics.Engine.ShadowObjects.AllEntriesForDebug() .Select(entry => entry.EntityId) .Order() .ToArray()); } [Fact] public void TerminalDisposalClearsLandblocksShadowsAndWorksets() { var lifetime = new RuntimeEntityObjectLifetime(); RuntimeEntityRecord record = lifetime.Entities.AddActive(Spawn(0x70000041u, 1)); lifetime.Physics.AcknowledgeSpatialProjection( record, spatial: true); RuntimeCollisionAdmission admission = lifetime.Physics.BeginCollisionAdmission(0x0101FFFFu); using PreparedLandblockCollisionGeneration prepared = lifetime.Physics.PrepareCollisionGeneration(admission); lifetime.Physics.StageCollisionAssets( admission, prepared, CollisionAssets(0x0101FFFFu)); _ = CommitPrepared(lifetime.Physics, admission, prepared); lifetime.Physics.Engine.ShadowObjects.Register( entityId: record.LocalEntityId!.Value, gfxObjId: 0x01000001u, worldPos: new Vector3(10f, 20f, 5f), rotation: Quaternion.Identity, radius: 0.5f, worldOffsetX: 0f, worldOffsetY: 0f, landblockId: 0x0101FFFFu, seedCellId: 0x01010001u, isStatic: false); Assert.False(lifetime.Physics.CaptureOwnership().IsConverged); Assert.True( lifetime.Physics.Engine.ShadowObjects .RetainedRegistrationCount > 0); lifetime.Dispose(); RuntimePhysicsOwnershipSnapshot retired = lifetime.Physics.CaptureOwnership(); Assert.True(retired.IsConverged); Assert.Equal(0, retired.LandblockCount); Assert.Equal(0, retired.RetainedShadowRegistrationCount); Assert.Equal(0, retired.SpatialRootCount); } [Fact] public void RemoteBindingOwnsCanonicalBodyCellAndTypedCellPublication() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimeEntityRecord record = lifetime.Entities.AddActive(Spawn(0x70000021u, 1)); var remote = new RemoteMotion(); var commits = new List(); lifetime.Physics.CellCommitted += commits.Add; lifetime.Physics.SetRemoteMotion(record, remote); lifetime.Physics.AcknowledgeSpatialProjection(record, spatial: true); remote.CellId = 0x02020001u; Assert.Same(remote, record.RemoteMotion); Assert.Same(remote.Body, record.PhysicsBody); Assert.Equal(0x02020001u, record.FullCellId); RuntimePhysicsCellCommit commit = Assert.Single(commits); Assert.Same(record, commit.Record); Assert.Equal(0x0101FFFFu, commit.PreviousFullCellId); Assert.Equal(0x02020001u, commit.FullCellId); Assert.True(lifetime.Physics.IsSpatialRemote(record, remote)); Assert.True(lifetime.Physics.ClearRemoteMotion(record)); Assert.Null(record.RemoteMotion); Assert.Equal(0, lifetime.Physics.SpatialRemoteCount); } [Fact] public void BuiltInRemoteConstructionInstallsCreateVectorsOnlyOnce() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimeEntityRecord record = lifetime.Entities.AddActive( Spawn( 0x70000042u, 1, velocity: new Vector3(40f, 0f, 0f), angularVelocity: new Vector3(0f, 0f, 2f))); RemoteMotion remote = lifetime.Physics.GetOrCreateRemoteMotion(record); Assert.Equal(new Vector3(40f, 0f, 0f), remote.Body.Velocity); Assert.Equal(new Vector3(0f, 0f, 2f), remote.Body.Omega); remote.Body.set_velocity(new Vector3(8f, 0f, 0f)); remote.Body.Omega = new Vector3(0f, 0f, 3f); Assert.Same( remote, lifetime.Physics.GetOrCreateRemoteMotion(record)); Assert.Equal(new Vector3(8f, 0f, 0f), remote.Body.Velocity); Assert.Equal(new Vector3(0f, 0f, 3f), remote.Body.Omega); } [Fact] public void BuiltInMovementActivationUsesExactRuntimeIncarnation() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimeEntityRecord ordinary = lifetime.Entities.AddActive(Spawn(0x70000043u, 1)); RemoteMotion ordinaryRemote = lifetime.Physics.GetOrCreateRemoteMotion(ordinary); ordinary.ObjectClock.Deactivate(); ordinaryRemote.Body.TransientState &= ~TransientStateFlags.Active; ordinaryRemote.Movement.ActivatePhysicsObject!(); Assert.True(ordinary.ObjectClock.IsActive); Assert.True(ordinaryRemote.Body.IsActive); RuntimeEntityRecord staticRecord = lifetime.Entities.AddActive(Spawn(0x70000044u, 1)); lifetime.Entities.SetFinalPhysicsState( staticRecord, PhysicsStateFlags.Static); RemoteMotion staticRemote = lifetime.Physics.GetOrCreateRemoteMotion(staticRecord); staticRecord.ObjectClock.Deactivate(); staticRemote.Body.TransientState &= ~TransientStateFlags.Active; staticRemote.Movement.ActivatePhysicsObject!(); Assert.False(staticRecord.ObjectClock.IsActive); Assert.False(staticRemote.Body.IsActive); } [Fact] public void RemotePhysicsTickUsesCanonicalRuntimeAndPublishesPoseSnapshot() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimeEntityRecord record = lifetime.Entities.AddActive(Spawn(0x70000022u, 1)); var remote = new RemoteMotion(); remote.Body.Position = new Vector3(10f, 20f, 5f); remote.Body.Orientation = Quaternion.Identity; remote.Body.TransientState = TransientStateFlags.Active | TransientStateFlags.Contact | TransientStateFlags.OnWalkable; lifetime.Physics.SetRemoteMotion(record, remote); lifetime.Physics.AcknowledgeSpatialProjection(record, spatial: true); var updater = new RuntimeRemotePhysicsUpdater(lifetime.Physics); RuntimeRemotePhysicsSnapshot snapshot = default; int publishes = 0; Assert.True(updater.Tick( record, remote, objectScale: 1f, sequencer: null, dt: 0.1f, objectClockEpoch: record.ObjectClockEpoch, new MotionDeltaFrame { Origin = Vector3.UnitX, Orientation = Quaternion.Identity, }, radius: 0.48f, height: 1.835f, liveCenterX: 1, liveCenterY: 1, acknowledgeProjection: value => { snapshot = value; publishes++; return true; })); Assert.Equal(1, publishes); Assert.True(snapshot.Position.X > 10f); Assert.Equal(remote.Body.Position, snapshot.Position); Assert.Equal(record.FullCellId, snapshot.FullCellId); } [Fact] public void RemoteVelocityStalenessUsesInjectedRuntimeClock() { var time = new ManualTimeProvider( DateTimeOffset.UnixEpoch.AddSeconds(100d)); using var lifetime = new RuntimeEntityObjectLifetime( timeProvider: time); RuntimeEntityRecord record = lifetime.Entities.AddActive(Spawn(0x70000025u, 1)); var remote = new RemoteMotion { HasServerVelocity = true, LastServerPosTime = 99d, ServerVelocity = Vector3.UnitX, }; remote.Body.Position = new Vector3(10f, 20f, 5f); remote.Body.TransientState = TransientStateFlags.Active | TransientStateFlags.Contact | TransientStateFlags.OnWalkable; lifetime.Physics.SetRemoteMotion(record, remote); lifetime.Physics.AcknowledgeSpatialProjection(record, spatial: true); var updater = new RuntimeRemotePhysicsUpdater(lifetime.Physics); Vector3? cycle = null; Assert.True(updater.Tick( record, remote, objectScale: 1f, sequencer: null, dt: 0.1f, objectClockEpoch: record.ObjectClockEpoch, new MotionDeltaFrame { Orientation = Quaternion.Identity, }, radius: 0.48f, height: 1.835f, liveCenterX: 1, liveCenterY: 1, applyStaleVelocityCycle: value => cycle = value)); Assert.False(remote.HasServerVelocity); Assert.Equal(Vector3.Zero, remote.ServerVelocity); Assert.Equal(Vector3.Zero, cycle); } // Campaign P Slice P5 (2026-07-30, #167): the remote per-tick pump pushes // ConstraintManager.IsFullyConstrained onto PhysicsBody.IsFullyConstrained // exactly like the local player's per-tick pump // (PlayerMovementControllerTests). The App-layer arm site // (LiveEntityNetworkUpdateController's remote UpdatePosition acceptance) // isn't reachable from here, so this test arms the leash directly through // the bound host's PositionManager — exactly the call shape that site // makes — and proves RuntimeRemotePhysicsUpdater.Tick's push keeps // Body.IsFullyConstrained current. [Fact] public void RemotePhysicsTickPushesIsFullyConstrainedFromTheArmedLeash() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimeEntityRecord record = lifetime.Entities.AddActive(Spawn(0x70000027u, 1)); var remote = new RemoteMotion(); remote.Body.Position = new Vector3(10f, 20f, 5f); remote.Body.Orientation = Quaternion.Identity; remote.Body.TransientState = TransientStateFlags.Active | TransientStateFlags.Contact | TransientStateFlags.OnWalkable; // SetRemoteMotion binds the reader that resolves record.PhysicsHost — // do not also call BindCanonicalRuntime here, it is already bound. lifetime.Physics.SetRemoteMotion(record, remote); lifetime.Physics.AcknowledgeSpatialProjection(record, spatial: true); EntityPhysicsHost host = new( record.ServerGuid, getPosition: () => new Position( record.FullCellId, remote.Body.Position, remote.Body.Orientation), getVelocity: () => remote.Body.Velocity, getRadius: () => 0.48f, inContact: () => remote.Body.InContact, minterpMaxSpeed: () => null, curTime: () => 0d, physicsTimerTime: () => 0d, getObjectA: _ => null, handleUpdateTarget: _ => { }, interruptCurrentMovement: () => { }); // Mirrors EntityPhysicsHostComposition.InstallOrRebind + the // production MarkFullPhysicsHostBound call // (LiveEntityMotionRuntimeController.EnsureRemoteMotionBindings). lifetime.Physics.InstallOrRebindPhysicsHost(record, host); remote.MarkFullPhysicsHostBound(); Assert.Same(host, remote.Host); // Same call shape as the LiveEntityNetworkUpdateController arm site // this slice adds: anchored to the object's own position, tight // synthetic band so a single tick's raw root-motion pass-through (the // interp queue is empty, so RemoteMotionCombiner.ComposeOffset leaves // it unmodified) overshoots 90% of max. host.PositionManager.ConstrainTo(host.Position, startDistance: 1f, maxDistance: 2f); Assert.False(remote.Body.IsFullyConstrained); // stub default, not yet pushed var updater = new RuntimeRemotePhysicsUpdater(lifetime.Physics); Assert.True(updater.Tick( record, remote, objectScale: 1f, sequencer: null, dt: 0.1f, objectClockEpoch: record.ObjectClockEpoch, new MotionDeltaFrame { Origin = new Vector3(10f, 0f, 0f), // one huge tick, past max Orientation = Quaternion.Identity, }, radius: 0.48f, height: 1.835f, liveCenterX: 1, liveCenterY: 1)); Assert.True(host.PositionManager.IsFullyConstrained()); Assert.True(remote.Body.IsFullyConstrained); } [Fact] public void PhysicsBodyAcquisitionIsCanonicalAndRejectsGuidReuse() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimeEntityRecord first = lifetime.Entities.AddActive(Spawn(0x70000023u, 1)); var body = new PhysicsBody(); Assert.Same( body, lifetime.Physics.GetOrCreatePhysicsBody(first, _ => body)); Assert.Same( body, lifetime.Physics.GetOrCreatePhysicsBody( first, _ => throw new InvalidOperationException( "The factory must not replay."))); RuntimeEntityRecord stale = lifetime.Entities.AddActive(Spawn(0x70000024u, 1)); Assert.Throws(() => lifetime.Physics.GetOrCreatePhysicsBody( stale, _ => { Assert.True(lifetime.Entities.RemoveActive(stale)); lifetime.Entities.AddActive(Spawn(0x70000024u, 2)); return new PhysicsBody(); })); Assert.Null(stale.PhysicsBody); Assert.False(stale.PhysicsBodyAcquisitionInProgress); } private sealed class ManualTimeProvider(DateTimeOffset utcNow) : TimeProvider { public override DateTimeOffset GetUtcNow() => utcNow; } [Fact] public void PhysicsHostIdentityAndLookupBelongToExactRuntimeIncarnation() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimeEntityRecord record = lifetime.Entities.AddActive(Spawn(0x70000025u, 1)); EntityPhysicsHost initial = Host(record.ServerGuid, 1f); EntityPhysicsHost replacement = Host(record.ServerGuid, 2f); Assert.Same( initial, lifetime.Physics.InstallOrRebindPhysicsHost(record, initial)); Assert.True( lifetime.Physics.TryGetPhysicsHost( record.ServerGuid, out IPhysicsObjHost resolved)); Assert.Same(initial, resolved); Assert.Same( initial, lifetime.Physics.InstallOrRebindPhysicsHost(record, replacement)); Assert.Equal(2f, initial.Position.Frame.Origin.X); Assert.True(lifetime.Entities.RemoveActive(record)); RuntimeEntityRecord next = lifetime.Entities.AddActive(Spawn(record.ServerGuid, 2)); Assert.False( lifetime.Physics.TryGetPhysicsHost( record.ServerGuid, out _)); Assert.Null(next.PhysicsHost); } [Fact] public void OrdinaryCellCommitIsCanonicalBeforePublication() { using var lifetime = new RuntimeEntityObjectLifetime(); RuntimeEntityRecord record = lifetime.Entities.AddActive(Spawn(0x70000026u, 1)); var body = new PhysicsBody(); lifetime.Entities.SetPhysicsBody(record, body); lifetime.Physics.AcknowledgeSpatialProjection(record, spatial: true); RuntimePhysicsCellCommit observed = default; lifetime.Physics.CellCommitted += value => { Assert.Equal(value.FullCellId, value.Record.FullCellId); observed = value; }; Assert.True(lifetime.Physics.CommitOrdinaryCell( record, body, record.ObjectClockEpoch, 0x02020001u, externalOwnerValid: null)); Assert.Equal(0x02020001u, record.FullCellId); Assert.Same(record, observed.Record); Assert.Equal(record.SpatialAuthorityVersion, observed.SpatialAuthorityVersion); } private static RuntimeCollisionGenerationCommit CommitPrepared( RuntimePhysicsState physics, RuntimeCollisionAdmission admission, PreparedLandblockCollisionGeneration prepared) { _ = SealPrepared(physics, admission, prepared); return CompleteSealedCommit(physics, admission, prepared); } private static RuntimeCollisionGenerationCommit CompleteSealedCommit( RuntimePhysicsState physics, RuntimeCollisionAdmission admission, PreparedLandblockCollisionGeneration prepared) { for (int poll = 0; poll < 10_000; poll++) { RuntimeCollisionGenerationCommit commit = physics.CommitCollisionGeneration(admission, prepared); if (commit.Committed) return commit; if (!commit.EngineCommitted) _ = SealPrepared(physics, admission, prepared); } throw new InvalidOperationException( "Collision generation did not complete its Runtime mutation transaction."); } private static RuntimeCollisionMutationResult CompleteDemotion( RuntimePhysicsState physics, uint landblockId) { for (int poll = 0; poll < 10_000; poll++) { RuntimeCollisionMutationResult result = physics.DemoteCollisionToTerrain(landblockId); if (result.Completed) return result; } throw new InvalidOperationException( "Collision demotion did not complete its Runtime mutation transaction."); } private static RuntimeCollisionMutationResult CompleteWithdrawal( RuntimePhysicsState physics, uint landblockId) { for (int poll = 0; poll < 10_000; poll++) { RuntimeCollisionMutationResult result = physics.WithdrawCollision(landblockId); if (result.Completed) return result; } throw new InvalidOperationException( "Collision withdrawal did not complete its Runtime mutation transaction."); } private static uint[] SealPrepared( RuntimePhysicsState physics, RuntimeCollisionAdmission admission, PreparedLandblockCollisionGeneration prepared) { while (true) { while (!physics.AdvanceCollisionRetainedOwnerCapture( admission, prepared).Completed) { } foreach (uint ownerId in prepared.RetainedOwnerIds) { physics.RefreshCollisionRetainedOwner( admission, prepared, ownerId); } RuntimeCollisionSealStep seal; do { seal = physics.AdvanceCollisionGenerationSeal( admission, prepared); Assert.InRange(seal.WorkUnits, 0, 1); } while (!seal.Completed && !seal.Restarted); if (seal.Completed) break; } return [.. prepared.RetainedOwnerIds]; } private static void AddSyntheticCell( PhysicsDataCache cache, uint cellId) { cache.RegisterCellStructForTest( cellId, new CellPhysics { Resolved = new Dictionary(), }); cache.CellGraph.Add(new EnvCell( cellId, Matrix4x4.Identity, Matrix4x4.Identity, Vector3.Zero, Vector3.One, Array.Empty(), Array.Empty(), seenOutside: false, containmentBsp: null)); } private static BuildingPhysics SyntheticBuilding(Matrix4x4 transform) { Matrix4x4.Invert(transform, out Matrix4x4 inverse); return new BuildingPhysics { WorldTransform = transform, InverseWorldTransform = inverse, Portals = Array.Empty(), }; } private static RuntimeLandblockCollisionAssets CollisionAssets( uint landblockId, float terrainHeight = 0f) { var heights = new byte[81]; var table = new float[256]; table[0] = terrainHeight; return new RuntimeLandblockCollisionAssets( landblockId, new TerrainSurface(heights, table), Array.Empty(), Array.Empty(), 0f, 0f, 0u); } private static WorldSession.EntitySpawn Spawn( uint guid, ushort instance, Vector3? velocity = null, Vector3? angularVelocity = null) { var position = new CreateObject.ServerPosition( 0x0101FFFFu, 10f, 20f, 5f, 1f, 0f, 0f, 0f); var timestamps = new PhysicsTimestamps( Position: 1, Movement: 1, State: 1, Vector: 1, Teleport: 0, ServerControlledMove: 1, ForcePosition: 0, ObjDesc: 1, Instance: instance); var physics = new PhysicsSpawnData( RawState: 0x408u, Position: position, Movement: null, AnimationFrame: null, SetupTableId: 0x02000001u, MotionTableId: 0x09000001u, SoundTableId: null, PhysicsScriptTableId: null, Parent: null, Children: null, Scale: null, Friction: null, Elasticity: null, Translucency: null, Velocity: velocity, Acceleration: null, AngularVelocity: angularVelocity, DefaultScriptType: null, DefaultScriptIntensity: null, Timestamps: timestamps); return new WorldSession.EntitySpawn( guid, position, 0x02000001u, Array.Empty(), Array.Empty(), Array.Empty(), null, null, "fixture", null, null, 0x09000001u, PhysicsState: 0x408u, InstanceSequence: instance, MovementSequence: 1, ServerControlSequence: 1, PositionSequence: 1, Physics: physics); } private static EntityPhysicsHost Host(uint guid, float x) => new( guid, getPosition: () => new Position( 0x0101FFFFu, new Vector3(x, 0f, 0f), Quaternion.Identity), getVelocity: () => Vector3.Zero, getRadius: () => 0.48f, inContact: () => true, minterpMaxSpeed: () => null, curTime: () => 0d, physicsTimerTime: () => 0d, getObjectA: _ => null, handleUpdateTarget: _ => { }, interruptCurrentMovement: () => { }); private sealed class TestRemoteMotion : IRuntimeRemoteMotion { public PhysicsBody Body { get; } = new(); } }