using System.Numerics; using AcDream.Core.Physics; namespace AcDream.Core.Tests.Physics; /// /// C3c-F5: moved from tests/AcDream.App.Tests/Physics/ /// RemoteSpawnPlacementSettlerTests.cs when the #270 settler moved to Core /// (App -> ) so the local player's /// Runtime first-entry activation can share it. Test bodies unchanged. /// public sealed class SpawnPlacementSettlerTests { private const uint Landblock = 0xA9B40000u; private const uint Cell = Landblock | 0x0001u; private const float Radius = 0.48f; private const float Height = 1.835f; [Fact] public void FloorWithinFirstGravityFrame_CommitsGroundContactOnce() { PhysicsEngine engine = BuildFlatEngine(); PhysicsBody body = AirborneBody(new Vector3(12f, 12f, 0.25f)); int hitGround = 0; int leaveGround = 0; bool settled = SpawnPlacementSettler.TrySettle( engine, body, body.Position, Cell, Radius, Height, ObjectInfoState.EdgeSlide, movingEntityId: 0x70000001u, () => hitGround++, () => leaveGround++); Assert.True(settled); Assert.True(body.InContact); Assert.True(body.OnWalkable); Assert.Equal(1, hitGround); Assert.Equal(0, leaveGround); } [Fact] public void MissingCell_CanRetryAfterHydrationWithoutReconstructingBody() { var engine = new PhysicsEngine { DataCache = new PhysicsDataCache() }; PhysicsBody body = AirborneBody(new Vector3(12f, 12f, 0.25f)); Vector3 originalPosition = body.Position; Assert.False(TrySettle(engine, body)); Assert.False(body.InContact); Assert.Equal(originalPosition, body.Position); AddFlatLandblock(engine); Assert.True(TrySettle(engine, body)); Assert.True(body.InContact); Assert.True(body.OnWalkable); } [Fact] public void FloorOutsideSettleDistance_LeavesGenuinelyAirborneBodyUnchanged() { PhysicsEngine engine = BuildFlatEngine(); PhysicsBody body = AirborneBody(new Vector3(12f, 12f, 1.25f)); Vector3 originalPosition = body.Position; Assert.False(TrySettle(engine, body)); Assert.False(body.InContact); Assert.False(body.OnWalkable); Assert.Equal(originalPosition, body.Position); } /// /// #276: the settle must adopt the transition's RESOLVED cell, not only /// its position. Retail's /// CPhysicsObj::SetPositionInternal(CTransition const*) /// (0x00515330) commits both curr_pos.objcell_id and the /// frame. /// /// /// The body is seeded with an ENVCELL id over plain outdoor terrain, /// which is what makes this discriminating. A bare /// body.Position = settle.Position mirrors the world delta into /// the local frame and lets AdjustToOutside recompute the cell — /// but only for outdoor ids; the mirror deliberately PRESERVES an /// EnvCell id, because one cannot be derived from a position. So the old /// code leaves the stale EnvCell in place and only the resolved-cell /// adoption reaches the outdoor cell the body physically settled in. /// This is the "outdoor/EnvCell seam" half of #276, in the outward /// direction. /// /// [Fact] public void SettleLeavingAnEnvCell_AdoptsTheTransitionsResolvedOutdoorCell() { const uint StaleEnvCell = Landblock | 0x0100u; PhysicsEngine engine = BuildFlatEngine(); PhysicsBody body = AirborneBody(new Vector3(12f, 12f, 0.25f)); body.StageDormantCellFrame( StaleEnvCell, body.Position, body.Position); Assert.Equal(StaleEnvCell, body.CellPosition.ObjCellId); bool settled = SpawnPlacementSettler.TrySettle( engine, body, body.Position, Cell, Radius, Height, ObjectInfoState.EdgeSlide, movingEntityId: 0x70000001u, static () => { }, static () => { }); Assert.True(settled); Assert.True(body.InContact); // The discriminating assertion: the body no longer claims the EnvCell // it never physically occupied, and its cell is an OUTDOOR landcell // (low word 1..0x40) of the same landblock it settled in. Assert.NotEqual(StaleEnvCell, body.CellPosition.ObjCellId); Assert.Equal(Landblock, body.CellPosition.ObjCellId & 0xFFFF0000u); Assert.InRange(body.CellPosition.ObjCellId & 0xFFFFu, 1u, 0x40u); } private static bool TrySettle(PhysicsEngine engine, PhysicsBody body) => SpawnPlacementSettler.TrySettle( engine, body, body.Position, Cell, Radius, Height, ObjectInfoState.EdgeSlide, movingEntityId: 0x70000001u, static () => { }, static () => { }); private static PhysicsBody AirborneBody(Vector3 position) => new() { Position = position, Orientation = Quaternion.Identity, State = PhysicsStateFlags.Gravity | PhysicsStateFlags.ReportCollisions, TransientState = TransientStateFlags.None, }; private static PhysicsEngine BuildFlatEngine() { var engine = new PhysicsEngine { DataCache = new PhysicsDataCache() }; AddFlatLandblock(engine); return engine; } private static void AddFlatLandblock(PhysicsEngine engine) => engine.AddLandblock( Landblock, new TerrainSurface(new byte[81], new float[256]), Array.Empty(), Array.Empty(), worldOffsetX: 0f, worldOffsetY: 0f); }