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);
}