using System.Numerics; using AcDream.App.Physics; using AcDream.App.Rendering; using AcDream.App.Rendering.Vfx; using AcDream.App.Streaming; using AcDream.App.World; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Core.Physics; using AcDream.Core.World; using DatReaderWriter.DBObjs; using DatReaderWriter.Types; namespace AcDream.App.Tests.Physics; public sealed class ProjectileControllerTests { private const uint Guid = 0x7000A001u; private const uint CellA = 0x01010001u; private const uint CellB = 0x01020001u; private static readonly PhysicsStateFlags MissileState = PhysicsStateFlags.ReportCollisions | PhysicsStateFlags.Missile | PhysicsStateFlags.AlignPath | PhysicsStateFlags.PathClipped; private sealed class RecordingResources : ILiveEntityResourceLifecycle { public int Registers { get; private set; } public int Unregisters { get; private set; } public void Register(WorldEntity entity) => Registers++; public void Unregister(WorldEntity entity) => Unregisters++; } [Fact] public void Tick_MovesCanonicalProjectionWithoutRecreatingResources() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); WorldEntity entity = record.WorldEntity!; uint localId = entity.Id; Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 10.0)); fixture.Controller.Tick(10.1, liveCenterX: 1, liveCenterY: 1, playerWorldPosition: null); Assert.Same(entity, record.WorldEntity); Assert.Equal(localId, entity.Id); Assert.True(entity.Position.X > 10f); Assert.Equal(1, fixture.Resources.Registers); Assert.Equal(0, fixture.Resources.Unregisters); Assert.Equal(1, fixture.Controller.Count); Assert.Same(record.ProjectileRuntime!.Body, record.PhysicsBody); } [Fact] public void Hidden_PausesProjectileWithoutClearingActiveOrReplayingClockBacklog() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); WorldEntity entity = record.WorldEntity!; fixture.Engine.ShadowObjects.Register( entity.Id, entity.SourceGfxObjOrSetupId, entity.Position, entity.Rotation, radius: 0.5f, worldOffsetX: 0f, worldOffsetY: 0f, landblockId: 0x01010000u, state: (uint)MissileState, seedCellId: CellA); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0, 1, 1)); PhysicsBody body = record.PhysicsBody!; Assert.True(body.IsActive); record.FinalPhysicsState = MissileState | PhysicsStateFlags.Hidden | PhysicsStateFlags.IgnoreCollisions; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, record.FinalPhysicsState, 1.1, 1, 1)); Vector3 hiddenPosition = entity.Position; fixture.Controller.Tick(5.0, 1, 1, playerWorldPosition: null); Assert.Equal(hiddenPosition, entity.Position); Assert.True(body.InWorld); Assert.True(body.IsActive); Assert.Equal(5.0, body.LastUpdateTime, 8); Assert.Equal(0, fixture.Engine.ShadowObjects.TotalRegistered); record.FinalPhysicsState = MissileState; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, record.FinalPhysicsState, 5.0, 1, 1)); fixture.Controller.Tick(5.1, 1, 1, playerWorldPosition: null); Assert.True(entity.Position.X > hiddenPosition.X); Assert.True(entity.Position.X < hiddenPosition.X + 2f); Assert.Equal(1, fixture.Engine.ShadowObjects.TotalRegistered); } [Fact] public void HiddenSharedRemoteProjectile_UsesPositionManagerNarrowTickExactlyOnce() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); WorldEntity entity = record.WorldEntity!; Assert.Null(record.AnimationRuntime); var remote = new GameWindow.RemoteMotion(); RemotePhysicsBodyInitializer.Initialize(remote.Body, record); remote.Body.Position = entity.Position; remote.Body.Orientation = entity.Rotation; fixture.Live.SetRemoteMotionRuntime(Guid, remote); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0, 1, 1)); Assert.Same(remote.Body, record.ProjectileRuntime!.Body); record.FinalPhysicsState = MissileState | PhysicsStateFlags.Hidden | PhysicsStateFlags.IgnoreCollisions; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, record.FinalPhysicsState, 1.1, 1, 1)); remote.Interp.Enqueue( entity.Position + Vector3.UnitX, heading: 0f, isMovingTo: false, currentBodyPosition: remote.Body.Position); var updater = new RemotePhysicsUpdater( fixture.Engine, (_, _) => (0.48f, 1.835f), (_, _, _, _) => { }); int publishedRoots = 0; updater.TickHiddenEntities( fixture.Live, localPlayerServerGuid: 0x50000001u, dt: 0.1f, _ => publishedRoots++); Vector3 afterNarrowTick = entity.Position; fixture.Controller.Tick(1.2, 1, 1, playerWorldPosition: null); Assert.True(afterNarrowTick.X > 10f); Assert.Equal(afterNarrowTick, entity.Position); Assert.Equal(1, publishedRoots); Assert.True(fixture.Controller.HandlesMovement(Guid)); } [Fact] public void PendingDestinationAndLaterHydration_RetainIdentityAndProjectileOwner() { var fixture = new Fixture(loadInitialLandblock: true); LiveEntityRecord record = fixture.Spawn(instance: 1); WorldEntity entity = record.WorldEntity!; Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 2.0)); ILiveEntityProjectileRuntime runtime = record.ProjectileRuntime!; Assert.True(fixture.Controller.ApplyAuthoritativePosition( Guid, worldPosition: new Vector3(202f, 10f, 5f), cellLocalPosition: new Vector3(10f, 10f, 5f), orientation: Quaternion.Identity, fullCellId: CellB, currentTime: 2.1, liveCenterX: 1, liveCenterY: 1)); Assert.False(record.IsSpatiallyVisible); Assert.True(record.IsSpatiallyProjected); Assert.Same(entity, record.WorldEntity); Assert.Same(runtime, record.ProjectileRuntime); Vector3 pendingPosition = entity.Position; fixture.Controller.Tick( 2.4, liveCenterX: 1, liveCenterY: 1, playerWorldPosition: null); Assert.Equal(pendingPosition, entity.Position); Assert.False(runtime.Body.InWorld); Assert.False(runtime.Body.IsActive); fixture.Spatial.AddLandblock(EmptyLandblock(0x0102FFFFu)); fixture.Controller.Tick( 2.5, liveCenterX: 1, liveCenterY: 1, playerWorldPosition: null); Assert.True(record.IsSpatiallyVisible); Assert.Same(entity, record.WorldEntity); Assert.Same(runtime, record.ProjectileRuntime); Assert.Equal(1, fixture.Resources.Registers); Assert.Equal(pendingPosition, entity.Position); fixture.Controller.Tick( 2.6, liveCenterX: 1, liveCenterY: 1, playerWorldPosition: null); Assert.True(entity.Position.X > pendingPosition.X); } [Fact] public void LandblockUnload_SuspendsProjectileAtVisibilityEdgeWithoutFrameScan() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); WorldEntity entity = record.WorldEntity!; fixture.Engine.ShadowObjects.Register( entity.Id, entity.SourceGfxObjOrSetupId, entity.Position, entity.Rotation, radius: 0.5f, worldOffsetX: 0f, worldOffsetY: 0f, landblockId: 0x01010000u, state: (uint)MissileState, seedCellId: CellA); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0, 1, 1)); Assert.True(record.PhysicsBody!.InWorld); Assert.True(record.PhysicsBody.IsActive); Assert.Single(fixture.Live.SpatialProjectileRuntimes); fixture.Spatial.RemoveLandblock(0x0101FFFFu); Assert.False(record.IsSpatiallyVisible); Assert.Empty(fixture.Live.SpatialProjectileRuntimes); Assert.False(record.PhysicsBody.InWorld); Assert.False(record.PhysicsBody.IsActive); Assert.Equal(0, fixture.Engine.ShadowObjects.TotalRegistered); fixture.Spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); Assert.Single(fixture.Live.SpatialProjectileRuntimes); fixture.Controller.Tick(1.1, 1, 1, playerWorldPosition: null); Assert.True(record.PhysicsBody.InWorld); Assert.True(record.PhysicsBody.IsActive); Assert.Equal(1, fixture.Engine.ShadowObjects.TotalRegistered); } [Fact] public void LoadedLandblockCrossing_RebucketsSameProjection() { const uint startCell = 0xA9B40039u; const uint destinationCell = 0xAAB40001u; var fixture = new Fixture(loadInitialLandblock: false, BuildBoundaryEngine()); fixture.Spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu)); fixture.Spatial.AddLandblock(EmptyLandblock(0xAAB4FFFFu)); LiveEntityRecord record = fixture.Spawn( instance: 1, cellId: startCell, worldPosition: new Vector3(191f, 10f, 50f), cellLocalPosition: new Vector3(191f, 10f, 50f), velocity: new Vector3(40f, 0f, 0f)); WorldEntity entity = record.WorldEntity!; Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(radius: 0.5f), 0.0)); fixture.Controller.Tick( 0.05, liveCenterX: 0xA9, liveCenterY: 0xB4, playerWorldPosition: null); Assert.Equal(destinationCell, record.FullCellId); Assert.Equal(destinationCell, entity.ParentCellId); Assert.Equal(193f, entity.Position.X, 3); Assert.Same(entity, record.WorldEntity); Assert.Equal(1, fixture.Resources.Registers); } [Fact] public void PredictedCrossingIntoPendingLandblock_SuspendsInSameTick() { const uint startCell = 0xA9B40039u; var fixture = new Fixture(loadInitialLandblock: false, BuildBoundaryEngine()); fixture.Spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu)); LiveEntityRecord record = fixture.Spawn( instance: 1, cellId: startCell, worldPosition: new Vector3(191f, 10f, 50f), cellLocalPosition: new Vector3(191f, 10f, 50f), velocity: new Vector3(40f, 0f, 0f)); WorldEntity entity = record.WorldEntity!; fixture.Engine.ShadowObjects.Register( entity.Id, entity.SourceGfxObjOrSetupId, entity.Position, entity.Rotation, radius: 0.5f, worldOffsetX: 0f, worldOffsetY: 0f, landblockId: 0xA9B4FFFFu, seedCellId: startCell, isStatic: false); Assert.True(fixture.Controller.TryBind( record, ProjectileSetup(radius: 0.5f), 0.0, liveCenterX: 0xA9, liveCenterY: 0xB4)); fixture.Controller.Tick( 0.05, liveCenterX: 0xA9, liveCenterY: 0xB4, playerWorldPosition: null); Assert.Equal(0xAAB40001u, record.FullCellId); Assert.False(record.IsSpatiallyVisible); Assert.True(record.IsSpatiallyProjected); Assert.False(record.PhysicsBody!.InWorld); Assert.False(record.PhysicsBody.IsActive); Assert.Empty(fixture.Engine.ShadowObjects.GetObjectsInCell(startCell)); Assert.Empty(fixture.Engine.ShadowObjects.GetObjectsInCell(0xAAB40001u)); Assert.Same(entity, record.WorldEntity); Assert.Equal(1, fixture.Controller.Count); record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, record.FinalPhysicsState, 0.06, 0xA9, 0xB4)); fixture.Spatial.AddLandblock(EmptyLandblock(0xAAB4FFFFu)); fixture.Controller.Tick(0.07, 0xA9, 0xB4, playerWorldPosition: null); Assert.True(record.IsSpatiallyVisible); Assert.True(record.PhysicsBody.InWorld); Assert.False(record.PhysicsBody.IsActive); Assert.Contains( fixture.Engine.ShadowObjects.GetObjectsInCell(0xAAB40001u), entry => entry.EntityId == entity.Id); } [Fact] public void FreshVectorAndPositionCorrectionsMutateSameBody() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 3); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 4.0)); PhysicsBody body = record.PhysicsBody!; Assert.True(fixture.Controller.ApplyAuthoritativeVector( Guid, new Vector3(7f, 8f, 9f), new Vector3(0f, 0f, 2f), currentTime: 4.5)); Assert.Equal(new Vector3(7f, 8f, 9f), body.Velocity); Assert.Equal(new Vector3(0f, 0f, 2f), body.Omega); var correction = new Vector3(30f, 31f, 32f); Assert.True(fixture.Controller.ApplyAuthoritativePosition( Guid, correction, new Vector3(30f, 31f, 32f), Quaternion.Identity, CellA, currentTime: 5.0, liveCenterX: 1, liveCenterY: 1)); Assert.Same(body, record.PhysicsBody); Assert.Equal(correction, body.Position); Assert.Equal(correction, record.WorldEntity!.Position); Assert.Equal(new Vector3(7f, 8f, 9f), body.Velocity); } [Fact] public void StaleVectorGateCannotOverwritePredictedBody() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 5, vectorSequence: 10); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0)); PhysicsBody body = record.PhysicsBody!; Vector3 original = body.Velocity; var stale = new VectorUpdate.Parsed( Guid, new Vector3(99f, 0f, 0f), Vector3.Zero, InstanceSequence: 5, VectorSequence: 9); Assert.False(fixture.Live.TryApplyVector(stale, out _)); Assert.Equal(original, body.Velocity); } [Fact] public void MissileStateRemovalAndReaddition_ReclassifiesWithoutReplacingEntity() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0)); Vector3 before = record.WorldEntity!.Position; ILiveEntityProjectileRuntime runtime = record.ProjectileRuntime!; PhysicsBody body = runtime.Body; record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, PhysicsStateFlags.ReportCollisions, currentTime: 1.1, liveCenterX: 1, liveCenterY: 1)); fixture.Controller.Tick( 1.2, liveCenterX: 1, liveCenterY: 1, playerWorldPosition: null); Assert.Same(runtime, record.ProjectileRuntime); Assert.Same(body, record.PhysicsBody); Assert.True(body.IsActive); Assert.Equal(1, fixture.Controller.Count); Assert.True(record.WorldEntity.Position.X > before.X); record.FinalPhysicsState = MissileState; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, MissileState, currentTime: 1.3, liveCenterX: 1, liveCenterY: 1)); Assert.Same(runtime, record.ProjectileRuntime); Assert.Same(body, record.PhysicsBody); Assert.True(body.IsActive); // State preserves an already-active body. Assert.Same(record.WorldEntity, fixture.Live.MaterializedWorldEntities[Guid]); Assert.Equal(1, fixture.Resources.Registers); } [Fact] public void StateReclassificationAfterPredictedCrossing_KeepsCurrentCellAndBody() { const uint startCell = 0xA9B40039u; const uint destinationCell = 0xAAB40001u; var fixture = new Fixture(loadInitialLandblock: false, BuildBoundaryEngine()); fixture.Spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu)); fixture.Spatial.AddLandblock(EmptyLandblock(0xAAB4FFFFu)); LiveEntityRecord record = fixture.Spawn( instance: 1, cellId: startCell, worldPosition: new Vector3(191f, 10f, 50f), cellLocalPosition: new Vector3(191f, 10f, 50f), velocity: new Vector3(40f, 0f, 0f)); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(0.5f), 0.0)); ILiveEntityProjectileRuntime runtime = record.ProjectileRuntime!; fixture.Controller.Tick(0.05, 0xA9, 0xB4, playerWorldPosition: null); Assert.Equal(destinationCell, record.FullCellId); Assert.NotEqual(record.Snapshot.Position!.Value.LandblockId, record.FullCellId); Assert.True(fixture.Live.TryApplyState( new SetState.Parsed( Guid, (uint)PhysicsStateFlags.ReportCollisions, InstanceSequence: 1, StateSequence: 2), out _)); Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, record.FinalPhysicsState, 0.06, 0xA9, 0xB4)); Assert.Equal(destinationCell, record.FullCellId); Assert.True(fixture.Live.TryApplyState( new SetState.Parsed( Guid, (uint)MissileState, InstanceSequence: 1, StateSequence: 3), out _)); Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, MissileState, 0.07, 0xA9, 0xB4)); Assert.Same(runtime, record.ProjectileRuntime); Assert.Same(runtime.Body, record.PhysicsBody); Assert.Equal(destinationCell, record.FullCellId); Assert.Equal(destinationCell, record.WorldEntity!.ParentCellId); } [Fact] public void FreshVector_ClampsImmediatelyAndRestartsInactiveClock() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0)); PhysicsBody body = record.PhysicsBody!; body.TransientState &= ~TransientStateFlags.Active; body.LastUpdateTime = 1.0; Assert.True(fixture.Controller.ApplyAuthoritativeVector( Guid, new Vector3(100f, 0f, 0f), Vector3.Zero, currentTime: 20.0)); Assert.Equal(50f, body.Velocity.Length(), 4); Assert.Equal(20.0, body.LastUpdateTime, 8); fixture.Controller.Tick( 20.1, liveCenterX: 1, liveCenterY: 1, playerWorldPosition: null); Assert.Equal(15f, record.WorldEntity!.Position.X, 3); } [Fact] public void StateOnlyUpdate_DoesNotRestartStoppedProjectile() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0)); PhysicsBody body = record.PhysicsBody!; body.TransientState &= ~TransientStateFlags.Active; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, MissileState | PhysicsStateFlags.Inelastic, currentTime: 2.0, liveCenterX: 1, liveCenterY: 1)); Assert.False(body.IsActive); record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, record.FinalPhysicsState, 2.1, 1, 1)); Assert.False(body.IsActive); record.FinalPhysicsState = MissileState; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, MissileState, 2.2, 1, 1)); Assert.False(body.IsActive); } [Fact] public void VectorAfterMissileRemoval_StillMutatesRetainedRetailBody() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0)); PhysicsBody body = record.PhysicsBody!; record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, record.FinalPhysicsState, 1.01, 1, 1)); Assert.True(fixture.Controller.ApplyAuthoritativeVector( Guid, new Vector3(100f, 0f, 0f), new Vector3(0f, 0f, 3f), 1.02)); Assert.Same(body, record.PhysicsBody); Assert.Equal(50f, body.Velocity.Length(), 4); Assert.Equal(new Vector3(0f, 0f, 3f), body.Omega); Vector3 before = body.Position; fixture.Controller.Tick(1.1, 1, 1, playerWorldPosition: null); Assert.True(body.Position.X > before.X); Assert.Equal(body.Position, record.WorldEntity!.Position); } [Fact] public void VectorAfterMissileRemoval_WithRemoteMotionDefersToOrdinaryFunnel() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0)); PhysicsBody body = record.PhysicsBody!; fixture.Live.SetRemoteMotionRuntime(Guid, new GameWindow.RemoteMotion(body)); record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, record.FinalPhysicsState, 2.0, 1, 1)); Vector3 before = body.Velocity; Assert.False(fixture.Controller.ApplyAuthoritativeVector( Guid, new Vector3(0f, 0f, 12f), new Vector3(0f, 0f, 2f), 3.0)); Assert.Equal(before, body.Velocity); Assert.Same(body, record.RemoteMotionRuntime!.Body); } [Fact] public void MissileReacquisition_AlignsClockWithoutStateOnlyMotion() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0)); PhysicsBody body = record.PhysicsBody!; Vector3 position = body.Position; fixture.Live.SetRemoteMotionRuntime(Guid, new GameWindow.RemoteMotion(body)); record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, record.FinalPhysicsState, 2.0, 1, 1)); body.TransientState |= TransientStateFlags.Active; body.LastUpdateTime = 9.5; // generic remote interval used another clock owner body.set_velocity(new Vector3(40f, 0f, 0f)); record.FinalPhysicsState = MissileState; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, MissileState, 10.0, 1, 1)); Assert.Equal(10.0, body.LastUpdateTime, 8); Assert.True(body.IsActive); fixture.Controller.Tick(10.1, 1, 1, playerWorldPosition: null); Assert.Equal(position.X + 4f, body.Position.X, 3); Assert.Equal(body.Position, record.WorldEntity!.Position); } [Fact] public void MissileReacquisition_AppliesStateWithoutPoisoningClock() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0)); PhysicsBody body = record.PhysicsBody!; record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, record.FinalPhysicsState, 2.0, 1, 1)); PhysicsStateFlags stateBefore = body.State; double clockBefore = body.LastUpdateTime; record.FinalPhysicsState = MissileState; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, MissileState, double.NaN, 1, 1)); Assert.Equal(record.FinalPhysicsState, body.State); Assert.NotEqual(stateBefore, body.State); Assert.NotEqual(clockBefore, body.LastUpdateTime); Assert.Equal(2.0, body.LastUpdateTime, 8); Assert.True(double.IsFinite(body.LastUpdateTime)); Assert.True(body.IsActive); } [Fact] public void Tick_PublishesMovedRootToEffectPoseSource() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); WorldEntity entity = record.WorldEntity!; Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0)); Assert.True(fixture.Poses.TryGetRootPose(entity.Id, out Matrix4x4 before)); fixture.Controller.Tick( 1.1, liveCenterX: 1, liveCenterY: 1, playerWorldPosition: null); Assert.True(fixture.Poses.TryGetRootPose(entity.Id, out Matrix4x4 after)); Assert.NotEqual(before.Translation, after.Translation); Assert.Equal(entity.Position, after.Translation); } [Fact] public void Tick_PartArrayUsesRetailNinetySixUnitActivationBoundary() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1, hasRenderablePart: true); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0)); fixture.Controller.Tick( 1.1, liveCenterX: 1, liveCenterY: 1, playerWorldPosition: new Vector3(106f, 10f, 5f)); Assert.Equal(11f, record.WorldEntity!.Position.X, 3); fixture.Controller.Tick( 1.2, liveCenterX: 1, liveCenterY: 1, playerWorldPosition: new Vector3(107.1f, 10f, 5f)); Assert.Equal(11f, record.WorldEntity.Position.X, 3); Assert.False(record.PhysicsBody!.IsActive); } [Fact] public void Bind_UsesCanonicalOutdoorCellAndRehydrationCellTracksPrediction() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn( instance: 1, cellId: CellA, worldPosition: new Vector3(30f, 10f, 5f), cellLocalPosition: new Vector3(30f, 10f, 5f)); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0)); Assert.Equal(record.PhysicsBody!.CellPosition.ObjCellId, record.FullCellId); Assert.Equal(record.FullCellId, record.WorldEntity!.ParentCellId); Assert.Equal(record.FullCellId, record.ProjectionCellId); Assert.NotEqual(record.Snapshot.Position!.Value.LandblockId, record.FullCellId); } [Fact] public void MalformedFreshUpdates_DoNotPoisonCanonicalBodyOrPose() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0)); PhysicsBody body = record.PhysicsBody!; Vector3 velocity = body.Velocity; Vector3 position = body.Position; Assert.True(fixture.Controller.ApplyAuthoritativeVector( Guid, new Vector3(float.NaN, 0f, 0f), Vector3.Zero, currentTime: 2.0)); Assert.Equal(velocity, body.Velocity); Assert.True(fixture.Controller.ApplyAuthoritativePosition( Guid, new Vector3(float.PositiveInfinity, 0f, 0f), new Vector3(float.PositiveInfinity, 0f, 0f), Quaternion.Identity, CellA, currentTime: 2.1, liveCenterX: 1, liveCenterY: 1)); Assert.Equal(position, body.Position); Assert.Equal(position, record.WorldEntity!.Position); } [Fact] public void MalformedPayloadGate_RejectsBeforeAuthoritativeSnapshotAdvances() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1, vectorSequence: 4); record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions; WorldSession.EntitySpawn before = record.Snapshot; Assert.False(fixture.Controller.CanAcceptVectorPayload( Guid, new Vector3(float.NaN, 0f, 0f), Vector3.Zero)); Assert.False(fixture.Controller.CanAcceptPositionPayload( Guid, new CreateObject.ServerPosition( CellA, float.PositiveInfinity, 0f, 0f, 1f, 0f, 0f, 0f), velocity: null)); var malformedPosition = new WorldSession.EntityPositionUpdate( Guid, new CreateObject.ServerPosition( CellA, 20f, 20f, 5f, 1f, 0f, 0f, 0f), Velocity: new Vector3(float.NaN, 0f, 0f), PlacementId: null, IsGrounded: true, InstanceSequence: 1, PositionSequence: 2, TeleportSequence: 0, ForcePositionSequence: 0); Assert.False(fixture.Controller.CanAcceptPositionPayload( Guid, malformedPosition.Position, malformedPosition.Velocity)); Assert.Equal(before, record.Snapshot); Assert.Equal((ushort)1, record.Snapshot.Physics!.Value.Timestamps.Position); Assert.Equal((ushort)4, record.Snapshot.Physics!.Value.Timestamps.Vector); record.FinalPhysicsState = MissileState; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, MissileState, 2.0, 1, 1)); Assert.NotNull(record.ProjectileRuntime); Assert.True(float.IsFinite(record.PhysicsBody!.Velocity.X)); Assert.True(float.IsFinite(record.PhysicsBody.Velocity.Y)); Assert.True(float.IsFinite(record.PhysicsBody.Velocity.Z)); } [Fact] public void StateUpdate_AlwaysMutatesExistingCanonicalBodyBeforeProjectileAcquisition() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions; var remote = new GameWindow.RemoteMotion(); RemotePhysicsBodyInitializer.Initialize(remote.Body, record); fixture.Live.SetRemoteMotionRuntime(Guid, remote); PhysicsStateFlags ordinaryState = PhysicsStateFlags.Gravity | PhysicsStateFlags.IgnoreCollisions; record.FinalPhysicsState = ordinaryState; Assert.False(fixture.Controller.ApplyAuthoritativeState( Guid, ordinaryState, 1.0, 1, 1)); Assert.Equal(ordinaryState, remote.Body.State); Assert.Null(record.ProjectileRuntime); fixture.ResolvedSetup = new Setup(); PhysicsStateFlags unsupportedMissile = ordinaryState | PhysicsStateFlags.Missile; record.FinalPhysicsState = unsupportedMissile; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, unsupportedMissile, 1.1, 1, 1)); Assert.Equal(unsupportedMissile, remote.Body.State); Assert.Null(record.ProjectileRuntime); Assert.Same(remote.Body, record.PhysicsBody); } [Fact] public void FirstMissileStateWithInvalidLocalClock_RetainsClassificationAndBindsOnce() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions; record.FinalPhysicsState = MissileState; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, MissileState, double.NaN, 1, 1)); Assert.NotNull(record.ProjectileRuntime); Assert.Equal(MissileState, record.PhysicsBody!.State); Assert.True(double.IsFinite(record.PhysicsBody.LastUpdateTime)); ILiveEntityProjectileRuntime runtime = record.ProjectileRuntime!; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, MissileState, 0.1, 1, 1)); Assert.Same(runtime, record.ProjectileRuntime); } [Fact] public void FirstRemoteMissileStateWithInvalidLocalClock_AdoptsExistingBody() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions; var remote = new GameWindow.RemoteMotion(); RemotePhysicsBodyInitializer.Initialize(remote.Body, record); fixture.Live.SetRemoteMotionRuntime(Guid, remote); record.FinalPhysicsState = MissileState; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, MissileState, double.PositiveInfinity, 1, 1)); Assert.NotNull(record.ProjectileRuntime); Assert.Same(remote.Body, record.ProjectileRuntime!.Body); Assert.Equal(MissileState, remote.Body.State); Assert.True(double.IsFinite(remote.Body.LastUpdateTime)); } [Fact] public void Bind_AppliesRetailFrictionValidationAndElasticityClamp() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn( instance: 1, friction: -0.1f, elasticity: 2f); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0)); Assert.Equal(PhysicsBody.DefaultFriction, record.PhysicsBody!.Friction); Assert.Equal(0.1f, record.PhysicsBody.Elasticity); } [Fact] public void Bind_AcceptsRetailZeroFrictionBoundary() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn( instance: 1, friction: 0f, elasticity: 0f); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0)); Assert.Equal(0f, record.PhysicsBody!.Friction); Assert.Equal(0f, record.PhysicsBody.Elasticity); } [Fact] public void RemoteMotionFirst_StateAcquisitionPreservesCurrentBodyAndCanonicalCell() { const uint startCell = 0xA9B40039u; const uint destinationCell = 0xAAB40001u; var fixture = new Fixture(loadInitialLandblock: false, BuildBoundaryEngine()); fixture.Spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu)); fixture.Spatial.AddLandblock(EmptyLandblock(0xAAB4FFFFu)); LiveEntityRecord record = fixture.Spawn( instance: 1, cellId: startCell, worldPosition: new Vector3(191f, 10f, 50f), cellLocalPosition: new Vector3(191f, 10f, 50f), velocity: new Vector3(40f, 0f, 0f), angularVelocity: new Vector3(0f, 0f, 2f)); WorldEntity entity = record.WorldEntity!; fixture.Engine.ShadowObjects.Register( entity.Id, entity.SourceGfxObjOrSetupId, entity.Position, entity.Rotation, radius: 0.5f, worldOffsetX: 0f, worldOffsetY: 0f, landblockId: 0xA9B4FFFFu, seedCellId: startCell, isStatic: false); record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions; var remote = new GameWindow.RemoteMotion(); RemotePhysicsBodyInitializer.Initialize(remote.Body, 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); remote.Body.Position = entity.Position; remote.Body.State = record.FinalPhysicsState; fixture.Live.SetRemoteMotionRuntime(Guid, remote); PhysicsBody body = remote.Body; record.FinalPhysicsState = MissileState; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, MissileState, currentTime: 1.2, liveCenterX: 0xA9, liveCenterY: 0xB4)); Assert.Same(body, record.ProjectileRuntime!.Body); Assert.Same(body, record.RemoteMotionRuntime!.Body); Assert.Same(body, record.PhysicsBody); Assert.Equal(new Vector3(8f, 0f, 0f), body.Velocity); Assert.Equal(new Vector3(0f, 0f, 3f), body.Omega); Assert.True(body.IsActive); fixture.Controller.Tick(1.45, 0xA9, 0xB4, playerWorldPosition: null); Assert.Equal(destinationCell, record.FullCellId); Assert.Equal(destinationCell, remote.CellId); record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, record.FinalPhysicsState, 1.5, 0xA9, 0xB4)); Assert.Equal(destinationCell, remote.CellId); Assert.Equal(destinationCell, record.FullCellId); body.Position = new Vector3(191f, 10f, 50f); entity.SetPosition(body.Position); remote.CellId = startCell; Assert.True(fixture.Controller.LeaveWorld(record)); fixture.Controller.Tick(1.6, 0xA9, 0xB4, playerWorldPosition: null); Assert.True(body.InWorld); Assert.Contains( fixture.Engine.ShadowObjects.GetObjectsInCell(startCell), entry => entry.EntityId == entity.Id); Assert.DoesNotContain( fixture.Engine.ShadowObjects.GetObjectsInCell(destinationCell), entry => entry.EntityId == entity.Id); record.FinalPhysicsState = MissileState; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, MissileState, 1.7, 0xA9, 0xB4)); Assert.Equal(startCell, record.FullCellId); Assert.Equal(startCell, remote.CellId); Assert.Equal(startCell, body.CellPosition.ObjCellId); } [Fact] public void RemoteMotionFirst_ValidatesAdoptedBodyInsteadOfStaleCreateVectors() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn( instance: 1, velocity: new Vector3(float.NaN, 0f, 0f)); WorldEntity entity = record.WorldEntity!; record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions; var remote = new GameWindow.RemoteMotion(); RemotePhysicsBodyInitializer.Initialize(remote.Body, record); remote.Body.Position = entity.Position; remote.Body.Orientation = entity.Rotation; fixture.Live.SetRemoteMotionRuntime(Guid, remote); Assert.True(float.IsNaN(record.Snapshot.Physics!.Value.Velocity!.Value.X)); Assert.Equal(Vector3.Zero, remote.Body.Velocity); record.FinalPhysicsState = MissileState; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, MissileState, 1.0, 1, 1)); Assert.NotNull(record.ProjectileRuntime); Assert.Same(remote.Body, record.ProjectileRuntime!.Body); Assert.Equal(Vector3.Zero, remote.Body.Velocity); Assert.Equal(entity.Position, remote.Body.Position); } [Fact] public void RemoteMotionFirst_RejectsNonFiniteCurrentBodyDespiteValidCreateSnapshot() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); WorldEntity entity = record.WorldEntity!; record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions; var remote = new GameWindow.RemoteMotion(); RemotePhysicsBodyInitializer.Initialize(remote.Body, record); remote.Body.Position = entity.Position; remote.Body.Orientation = entity.Rotation; remote.Body.Velocity = new Vector3(float.PositiveInfinity, 0f, 0f); fixture.Live.SetRemoteMotionRuntime(Guid, remote); record.FinalPhysicsState = MissileState; Assert.True(fixture.Controller.ApplyAuthoritativeState( Guid, MissileState, 1.0, 1, 1)); Assert.Null(record.ProjectileRuntime); Assert.Same(remote.Body, record.PhysicsBody); Assert.True(float.IsPositiveInfinity(remote.Body.Velocity.X)); Assert.Equal(MissileState, remote.Body.State); } [Fact] public void SharedRemoteMotionCell_DelegatesToCanonicalLiveRecord() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0)); PhysicsBody body = record.PhysicsBody!; var remote = new GameWindow.RemoteMotion(body); fixture.Live.SetRemoteMotionRuntime(Guid, remote); fixture.Live.RebucketLiveEntity(Guid, CellB); Assert.Equal(CellB, remote.CellId); remote.CellId = CellA; Assert.Equal(CellA, record.FullCellId); Assert.Equal(CellA, remote.CellId); Assert.Same(body, record.RemoteMotionRuntime!.Body); Assert.Same(body, record.ProjectileRuntime!.Body); var replacement = new GameWindow.RemoteMotion(body); fixture.Live.SetRemoteMotionRuntime(Guid, replacement); remote.CellId = CellB; Assert.Equal(CellA, record.FullCellId); Assert.Equal(0u, remote.CellId); replacement.CellId = CellB; Assert.Equal(CellB, record.FullCellId); } [Fact] public void PickupLeaveWorldAndPositionReentry_RestoresShadowWithoutClockBacklog() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 4); WorldEntity entity = record.WorldEntity!; Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0)); fixture.Engine.ShadowObjects.Register( entity.Id, entity.SourceGfxObjOrSetupId, entity.Position, entity.Rotation, radius: 0.5f, worldOffsetX: 0f, worldOffsetY: 0f, landblockId: CellA & 0xFFFF0000u, seedCellId: CellA, isStatic: false); Assert.Contains( fixture.Engine.ShadowObjects.GetObjectsInCell(CellA), entry => entry.EntityId == entity.Id); Assert.True(fixture.Live.TryApplyPickup( new PickupEvent.Parsed(Guid, InstanceSequence: 4, PositionSequence: 2), out _)); Assert.True(fixture.Live.WithdrawLiveEntityProjection(Guid)); Assert.True(fixture.Controller.LeaveWorld(record)); Assert.DoesNotContain( fixture.Engine.ShadowObjects.GetObjectsInCell(CellA), entry => entry.EntityId == entity.Id); var correction = new CreateObject.ServerPosition( CellA, 12f, 10f, 5f, 1f, 0f, 0f, 0f); var update = new WorldSession.EntityPositionUpdate( Guid, correction, Velocity: null, PlacementId: null, IsGrounded: true, InstanceSequence: 4, PositionSequence: 3, TeleportSequence: 0, ForcePositionSequence: 0); Assert.True(fixture.Live.TryApplyPosition( update, isLocalPlayer: false, forcePositionRotation: null, currentLocalVelocity: null, out PositionTimestampDisposition disposition, out WorldSession.EntitySpawn accepted, out _)); Assert.Equal(PositionTimestampDisposition.Apply, disposition); Assert.Same( entity, fixture.Live.MaterializeLiveEntity( Guid, accepted.Position!.Value.LandblockId, _ => throw new InvalidOperationException("re-entry recreated the entity"))); Assert.True(fixture.Controller.ApplyAuthoritativePosition( Guid, worldPosition: new Vector3(12f, 10f, 5f), cellLocalPosition: new Vector3(12f, 10f, 5f), orientation: Quaternion.Identity, fullCellId: CellA, currentTime: 10.0, liveCenterX: 1, liveCenterY: 1)); Assert.Equal(10.0, record.PhysicsBody!.LastUpdateTime, 8); Assert.Contains( fixture.Engine.ShadowObjects.GetObjectsInCell(CellA), entry => entry.EntityId == entity.Id); fixture.Controller.Tick( 10.1, liveCenterX: 1, liveCenterY: 1, playerWorldPosition: null); Assert.Equal(13f, entity.Position.X, 3); } [Fact] public void DeleteAndGuidReuse_IsolateProjectileGenerations() { var fixture = new Fixture(); LiveEntityRecord first = fixture.Spawn(instance: 7); Assert.True(fixture.Controller.TryBind(first, ProjectileSetup(), 1.0)); WorldEntity firstEntity = first.WorldEntity!; ILiveEntityProjectileRuntime firstRuntime = first.ProjectileRuntime!; Assert.True(fixture.Live.UnregisterLiveEntity( new DeleteObject.Parsed(Guid, InstanceSequence: 7), isLocalPlayer: false)); LiveEntityRecord second = fixture.Spawn(instance: 8); Assert.True(fixture.Controller.TryBind(second, ProjectileSetup(), 2.0)); Assert.NotSame(firstEntity, second.WorldEntity); Assert.NotSame(firstRuntime, second.ProjectileRuntime); Assert.Equal(1, fixture.Controller.Count); Assert.Equal(2, fixture.Resources.Registers); Assert.Equal(1, fixture.Resources.Unregisters); } [Fact] public void MissingRetailSphere_FailsWithoutFabricatingCollisionBody() { var fixture = new Fixture(); LiveEntityRecord record = fixture.Spawn(instance: 1); string? diagnostic = null; fixture.Controller.DiagnosticSink = text => diagnostic = text; Assert.False(fixture.Controller.TryBind(record, new Setup(), 1.0)); Assert.Null(record.ProjectileRuntime); Assert.Null(record.PhysicsBody); Assert.Contains("one-sphere", diagnostic); } private sealed class Fixture { internal Fixture( bool loadInitialLandblock = true, PhysicsEngine? physicsEngine = null) { if (loadInitialLandblock) Spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); Live = new LiveEntityRuntime(Spatial, Resources); Engine = physicsEngine ?? new PhysicsEngine(); Controller = new ProjectileController( Live, Engine, _ => ResolvedSetup, entity => Poses.UpdateRoot(entity)); } internal GpuWorldState Spatial { get; } = new(); internal RecordingResources Resources { get; } = new(); internal LiveEntityRuntime Live { get; } internal PhysicsEngine Engine { get; } internal EntityEffectPoseRegistry Poses { get; } = new(); internal Setup ResolvedSetup { get; set; } = ProjectileSetup(); internal ProjectileController Controller { get; } internal LiveEntityRecord Spawn( ushort instance, ushort vectorSequence = 1, uint cellId = CellA, Vector3? worldPosition = null, Vector3? cellLocalPosition = null, Vector3? velocity = null, Vector3? angularVelocity = null, float friction = 0f, float elasticity = 0f, bool hasRenderablePart = false) { Vector3 world = worldPosition ?? new Vector3(10f, 10f, 5f); Vector3 cellLocal = cellLocalPosition ?? world; WorldSession.EntitySpawn spawn = ProjectileSpawn( instance, vectorSequence, cellId, cellLocal, velocity ?? new Vector3(10f, 0f, 0f), angularVelocity ?? Vector3.Zero, friction, elasticity); LiveEntityRegistrationResult registration = Live.RegisterLiveEntity(spawn); Assert.NotNull(registration.Record); WorldEntity? entity = Live.MaterializeLiveEntity( Guid, cellId, localId => new WorldEntity { Id = localId, ServerGuid = Guid, SourceGfxObjOrSetupId = 0x02000124u, Position = world, Rotation = Quaternion.Identity, MeshRefs = hasRenderablePart ? new[] { new MeshRef(0x01000001u, Matrix4x4.Identity) } : Array.Empty(), ParentCellId = cellId, }); Assert.NotNull(entity); Poses.PublishMeshRefs(entity); return registration.Record!; } } private static Setup ProjectileSetup(float radius = 0.102f) => new() { Spheres = { new Sphere { Origin = new Vector3(0f, -0.165f, 0.1045f), Radius = radius, }, }, }; private static WorldSession.EntitySpawn ProjectileSpawn( ushort instance, ushort vectorSequence, uint cellId, Vector3 cellLocalPosition, Vector3 velocity, Vector3 angularVelocity, float friction, float elasticity) { var position = new CreateObject.ServerPosition( cellId, cellLocalPosition.X, cellLocalPosition.Y, cellLocalPosition.Z, 1f, 0f, 0f, 0f); var timestamps = new PhysicsTimestamps( Position: 1, Movement: 1, State: 1, Vector: vectorSequence, Teleport: 0, ServerControlledMove: 1, ForcePosition: 0, ObjDesc: 1, Instance: instance); var physics = new PhysicsSpawnData( RawState: (uint)MissileState, Position: position, Movement: null, AnimationFrame: null, SetupTableId: 0x02000124u, MotionTableId: null, SoundTableId: null, PhysicsScriptTableId: 0x3400002Bu, Parent: null, Children: null, Scale: 1f, Friction: friction, Elasticity: elasticity, Translucency: null, Velocity: velocity, Acceleration: new Vector3(999f, 999f, 999f), AngularVelocity: angularVelocity, DefaultScriptType: null, DefaultScriptIntensity: null, Timestamps: timestamps); return new WorldSession.EntitySpawn( Guid, position, 0x02000124u, Array.Empty(), Array.Empty(), Array.Empty(), BasePaletteId: null, ObjScale: 1f, Name: "Projectile fixture", ItemType: null, MotionState: null, MotionTableId: null, PhysicsState: (uint)MissileState, Friction: friction, Elasticity: elasticity, InstanceSequence: instance, MovementSequence: 1, ServerControlSequence: 1, PositionSequence: 1, Physics: physics); } private static LoadedLandblock EmptyLandblock(uint canonicalId) => new(canonicalId, new LandBlock(), Array.Empty()); private static PhysicsEngine BuildBoundaryEngine() { static TerrainSurface EmptyTerrain() { var heights = new byte[81]; var table = new float[256]; Array.Fill(table, -1000f); return new TerrainSurface(heights, table); } var engine = new PhysicsEngine { DataCache = new PhysicsDataCache() }; engine.AddLandblock( 0xA9B4FFFFu, EmptyTerrain(), Array.Empty(), Array.Empty(), 0f, 0f); engine.AddLandblock( 0xAAB4FFFFu, EmptyTerrain(), Array.Empty(), Array.Empty(), 192f, 0f); return engine; } }