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 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 = first.Physics.WithdrawCollision(0xA9B4FFFFu); 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 WithdrawnOwnerStateChangeRejectsSealUntilRefreshed() { 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); RuntimeCollisionGenerationCommit rejected = physics.CommitCollisionGeneration(admission, prepared); Assert.False(rejected.Committed); Assert.Equal(5f, physics.Engine.SampleTerrainZ(1f, 1f)); Assert.Equal(owner, Assert.Single(SealPrepared( physics, admission, prepared))); Assert.True(physics.CommitCollisionGeneration( 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); _ = 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 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); RuntimeCollisionGenerationCommit spawned = physics.CommitCollisionGeneration(admission, prepared); Assert.False(spawned.Committed); Assert.Equal(77u, Assert.Single(SealPrepared( physics, admission, prepared))); physics.Engine.ShadowObjects.Deregister(77u); RuntimeCollisionGenerationCommit deleted = physics.CommitCollisionGeneration(admission, prepared); Assert.False(deleted.Committed); Assert.Empty(SealPrepared(physics, admission, prepared)); Assert.True(physics.CommitCollisionGeneration( admission, prepared).Committed); Assert.Empty(physics.Engine.ShadowObjects.AllEntriesForDebug()); } [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 physics.CommitCollisionGeneration(admission, prepared); } 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 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(); } }