Preserve canonical live-object ownership across Hidden transitions and remote teleport placement so effects, collision, streaming, and targeting remain synchronized.
166 lines
5.5 KiB
C#
166 lines
5.5 KiB
C#
using System.Numerics;
|
|
using AcDream.App.Physics;
|
|
using AcDream.App.Rendering;
|
|
using AcDream.App.Rendering.Vfx;
|
|
using AcDream.Core.Physics;
|
|
using AcDream.Core.World;
|
|
|
|
namespace AcDream.App.Tests.Physics;
|
|
|
|
public sealed class RemotePhysicsUpdaterTests
|
|
{
|
|
[Fact]
|
|
public void TickHidden_AppliesPositionManagerOffsetWithoutAdvancingPhysics()
|
|
{
|
|
var motion = new GameWindow.RemoteMotion();
|
|
motion.Body.Position = Vector3.Zero;
|
|
motion.Body.Orientation = Quaternion.Identity;
|
|
motion.Body.Velocity = new Vector3(4f, 0f, 5f);
|
|
motion.Body.Acceleration = new Vector3(0f, 0f, -9.8f);
|
|
motion.CellId = 0x01010001u;
|
|
motion.Interp.Enqueue(
|
|
new Vector3(1f, 0f, 0f),
|
|
heading: 0f,
|
|
isMovingTo: false,
|
|
currentBodyPosition: Vector3.Zero);
|
|
var entity = new WorldEntity
|
|
{
|
|
Id = 1u,
|
|
ServerGuid = 0x70000001u,
|
|
SourceGfxObjOrSetupId = 0x02000001u,
|
|
Position = Vector3.Zero,
|
|
Rotation = Quaternion.Identity,
|
|
MeshRefs = Array.Empty<MeshRef>(),
|
|
};
|
|
|
|
var updater = new RemotePhysicsUpdater(
|
|
new PhysicsEngine(),
|
|
(_, _) => (0.48f, 1.835f),
|
|
(_, _, _, _) => { });
|
|
var poses = new EntityEffectPoseRegistry();
|
|
poses.Publish(entity, Array.Empty<Matrix4x4>());
|
|
updater.TickHidden(motion, entity, 0.1f);
|
|
Assert.True(poses.UpdateRoot(entity));
|
|
|
|
Assert.InRange(motion.Body.Position.X, 0.01f, 1f);
|
|
Assert.Equal(0f, motion.Body.Position.Z);
|
|
Assert.Equal(new Vector3(4f, 0f, 5f), motion.Body.Velocity);
|
|
Assert.Equal(motion.Body.Position, entity.Position);
|
|
Assert.Equal(motion.CellId, entity.ParentCellId);
|
|
Assert.True(poses.TryGetRootPose(entity.Id, out Matrix4x4 root));
|
|
Assert.Equal(entity.Position, root.Translation);
|
|
}
|
|
|
|
[Fact]
|
|
public void TickHidden_CrossCellCommitUpdatesCanonicalCellBeforeUnhide()
|
|
{
|
|
var engine = BuildBoundaryEngine();
|
|
var updater = new RemotePhysicsUpdater(
|
|
engine,
|
|
(_, _) => (0.48f, 1.835f),
|
|
(_, _, _, _) => { });
|
|
var motion = new GameWindow.RemoteMotion();
|
|
motion.Body.Position = new Vector3(191f, 10f, 50f);
|
|
motion.Body.Orientation = Quaternion.Identity;
|
|
motion.Body.TransientState = TransientStateFlags.Contact
|
|
| TransientStateFlags.OnWalkable;
|
|
uint canonicalCell = 0xA9B40039u;
|
|
int canonicalWrites = 0;
|
|
motion.BindCanonicalCell(
|
|
() => canonicalCell,
|
|
value =>
|
|
{
|
|
canonicalCell = value;
|
|
canonicalWrites++;
|
|
});
|
|
motion.CellId = canonicalCell;
|
|
canonicalWrites = 0;
|
|
motion.Interp.Enqueue(
|
|
new Vector3(193f, 10f, 50f),
|
|
heading: 0f,
|
|
isMovingTo: false,
|
|
currentBodyPosition: motion.Body.Position);
|
|
var entity = new WorldEntity
|
|
{
|
|
Id = 1u,
|
|
ServerGuid = 0x70000002u,
|
|
SourceGfxObjOrSetupId = 0x02000001u,
|
|
Position = motion.Body.Position,
|
|
Rotation = Quaternion.Identity,
|
|
MeshRefs = Array.Empty<MeshRef>(),
|
|
};
|
|
|
|
updater.TickHidden(motion, entity, 2f);
|
|
|
|
Assert.Equal(0xAAB40001u, canonicalCell);
|
|
Assert.Equal(canonicalCell, entity.ParentCellId);
|
|
Assert.True(canonicalWrites > 0);
|
|
Assert.True(entity.Position.X > 192f);
|
|
}
|
|
|
|
[Fact]
|
|
public void TickHidden_LandingSynchronizesRemoteAirborneState()
|
|
{
|
|
var engine = BuildBoundaryEngine();
|
|
var updater = new RemotePhysicsUpdater(
|
|
engine,
|
|
(_, _) => (0.48f, 1.835f),
|
|
(_, _, _, _) => { });
|
|
var motion = new GameWindow.RemoteMotion
|
|
{
|
|
Airborne = true,
|
|
};
|
|
motion.Body.Position = new Vector3(10f, 10f, 50f);
|
|
motion.Body.Orientation = Quaternion.Identity;
|
|
motion.Body.TransientState = TransientStateFlags.Active;
|
|
motion.CellId = 0xA9B40001u;
|
|
motion.Interp.Enqueue(
|
|
new Vector3(11f, 10f, 50f),
|
|
heading: 0f,
|
|
isMovingTo: false,
|
|
currentBodyPosition: motion.Body.Position);
|
|
var entity = new WorldEntity
|
|
{
|
|
Id = 3u,
|
|
ServerGuid = 0x70000003u,
|
|
SourceGfxObjOrSetupId = 0x02000001u,
|
|
Position = motion.Body.Position,
|
|
Rotation = Quaternion.Identity,
|
|
MeshRefs = Array.Empty<MeshRef>(),
|
|
};
|
|
|
|
updater.TickHidden(motion, entity, 0.1f);
|
|
|
|
Assert.True(motion.Body.InContact);
|
|
Assert.True(motion.Body.OnWalkable);
|
|
Assert.False(motion.Airborne);
|
|
}
|
|
|
|
private static PhysicsEngine BuildBoundaryEngine()
|
|
{
|
|
static TerrainSurface FlatTerrain()
|
|
{
|
|
var heights = new byte[81];
|
|
var table = new float[256];
|
|
Array.Fill(table, 50f);
|
|
return new TerrainSurface(heights, table);
|
|
}
|
|
|
|
var engine = new PhysicsEngine { DataCache = new PhysicsDataCache() };
|
|
engine.AddLandblock(
|
|
0xA9B4FFFFu,
|
|
FlatTerrain(),
|
|
Array.Empty<CellSurface>(),
|
|
Array.Empty<PortalPlane>(),
|
|
0f,
|
|
0f);
|
|
engine.AddLandblock(
|
|
0xAAB4FFFFu,
|
|
FlatTerrain(),
|
|
Array.Empty<CellSurface>(),
|
|
Array.Empty<PortalPlane>(),
|
|
192f,
|
|
0f);
|
|
return engine;
|
|
}
|
|
}
|