feat(runtime): seal dormant SetPosition evaluations

This commit is contained in:
Erik 2026-08-01 11:31:58 +02:00
parent 22651c823d
commit 99f867f053
16 changed files with 2298 additions and 62 deletions

View file

@ -1,6 +1,8 @@
using System.Collections.Immutable;
using System.Numerics;
using AcDream.Core.Physics;
using DatReaderWriter.Enums;
using DatReaderWriter.Types;
namespace AcDream.Core.Tests.Physics;
@ -32,7 +34,7 @@ public sealed class PhysicsSetPositionTests
var local = new Vector3(193f, 12f, 7f);
uint expectedCell = Cell;
Vector3 expectedLocal = local;
Assert.True(LandDefs.AdjustToOutside(
Assert.True(AcDream.Core.Physics.LandDefs.AdjustToOutside(
ref expectedCell,
ref expectedLocal));
@ -46,6 +48,9 @@ public sealed class PhysicsSetPositionTests
Assert.Equal(expectedCell, result.CellId);
Assert.Equal(expectedLocal, result.CellLocalPosition);
Assert.Equal(new Vector3(193f, 12f, 7f), result.Position);
Assert.Equal(
new[] { Cell, expectedCell }.Distinct(),
result.QueriedCellIds);
}
[Fact]
@ -82,6 +87,194 @@ public sealed class PhysicsSetPositionTests
Assert.Equal(0u, result.CellId);
Assert.Equal(local, result.CellLocalPosition);
Assert.Equal(local, result.Position);
Assert.Equal(new[] { southWestCell, 0u }, result.QueriedCellIds);
}
[Fact]
public void QueryFootprintIncludesAdjustPositionVisibleChildProbes()
{
const uint start = Landblock | 0x0101u;
const uint sibling = Landblock | 0x0102u;
var engine = new PhysicsEngine { DataCache = new PhysicsDataCache() };
engine.DataCache.RegisterCellStructForTest(
start,
ContainmentCell(
new Plane(new Vector3(0f, -1f, 0f), 3f),
[sibling]));
engine.DataCache.RegisterCellStructForTest(
sibling,
ContainmentCell(
new Plane(new Vector3(0f, 1f, 0f), -7f),
[]));
PhysicsSetPositionResult result = engine.SetPosition(
Request(
start,
new Vector3(0f, 8f, 1f),
new Vector3(0f, 8f, 1f)) with
{
MoverPhysicsState = PhysicsStateFlags.Missile,
});
Assert.Contains(start, result.QueriedCellIds);
Assert.Contains(sibling, result.QueriedCellIds);
Assert.True(
result.QueriedCellIds.IndexOf(start)
< result.QueriedCellIds.IndexOf(sibling));
}
[Fact]
public void LateralVisibleChildRecoveryRecordsRejectedAndWinningSiblingsUnionOnly()
{
const uint start = Landblock | 0x0101u;
const uint rejected = Landblock | 0x0102u;
const uint winner = Landblock | 0x0103u;
var cache = new PhysicsDataCache();
cache.RegisterCellStructForTest(
start,
ContainmentCell(
new Plane(new Vector3(0f, -1f, 0f), 3f),
[rejected, winner]));
cache.RegisterCellStructForTest(
rejected,
ContainmentCell(
new Plane(new Vector3(0f, 1f, 0f), -20f),
[]));
cache.RegisterCellStructForTest(
winner,
ContainmentCell(
new Plane(new Vector3(0f, 1f, 0f), -7f),
[]));
var candidates = new CellArray();
var queryFootprint = new CellArray();
candidates.UnionTarget = queryFootprint;
var spheres = new[]
{
new Sphere
{
Origin = new Vector3(0f, 8f, 1f),
Radius = 0.48f,
},
};
uint containing = CellTransit.FindCellSet(
cache,
spheres,
spheres.Length,
start,
candidates);
Assert.Equal(winner, containing);
Assert.Equal(new[] { start }, candidates.OrderedIds);
Assert.Equal(
new[] { start, rejected, winner },
queryFootprint.OrderedIds);
}
[Fact]
public void RejectedPortalContainmentProbeIsRecordedUnionOnly()
{
const uint start = Landblock | 0x0101u;
const uint rejected = Landblock | 0x0102u;
const ushort portalPolygonId = 10;
var portalPolygon = new ResolvedPolygon
{
Id = portalPolygonId,
Vertices =
[
new Vector3(10f, -1f, 0f),
new Vector3(10f, 1f, 0f),
new Vector3(10f, 1f, 2f),
],
Plane = new Plane(Vector3.UnitX, -10f),
NumPoints = 3,
SidesType = CullMode.None,
};
var startCell = new CellPhysics
{
BSP = new PhysicsBSPTree
{
Root = new PhysicsBSPNode { Type = BSPNodeType.Leaf },
},
WorldTransform = Matrix4x4.Identity,
InverseWorldTransform = Matrix4x4.Identity,
Resolved = new Dictionary<ushort, ResolvedPolygon>(),
CellBSP = new CellBSPTree
{
Root = new CellBSPNode { Type = BSPNodeType.Leaf },
},
Portals =
[
new PortalInfo(
(ushort)(rejected & 0xFFFFu),
portalPolygonId,
0),
],
PortalPolygons = new Dictionary<ushort, ResolvedPolygon>
{
[portalPolygonId] = portalPolygon,
},
};
var cache = new PhysicsDataCache();
cache.RegisterCellStructForTest(start, startCell);
cache.RegisterCellStructForTest(
rejected,
ContainmentCell(
new Plane(Vector3.UnitX, -100f),
[]));
var candidates = new CellArray();
var queryFootprint = new CellArray();
candidates.UnionTarget = queryFootprint;
var spheres = new[]
{
new Sphere
{
Origin = Vector3.Zero,
Radius = 0.48f,
},
};
uint containing = CellTransit.FindCellSet(
cache,
spheres,
spheres.Length,
start,
candidates);
Assert.Equal(start, containing);
Assert.Equal(new[] { start }, candidates.OrderedIds);
Assert.Equal(new[] { start, rejected }, queryFootprint.OrderedIds);
}
[Fact]
public void RejectedBuildingContainmentProbeIsRecordedUnionOnly()
{
const uint rejected = Landblock | 0x0102u;
var building = new BuildingPhysics
{
WorldTransform = Matrix4x4.Identity,
InverseWorldTransform = Matrix4x4.Identity,
Portals =
[
new BldPortalInfo(
rejected,
otherPortalId: 0,
flags: 0),
],
};
var candidates = new CellArray();
var queryFootprint = new CellArray();
candidates.UnionTarget = queryFootprint;
CellTransit.CheckBuildingTransit(
new PhysicsDataCache(),
building,
Vector3.Zero,
sphereRadius: 0.48f,
candidates);
Assert.Empty(candidates);
Assert.Equal(new[] { rejected }, queryFootprint.OrderedIds);
}
[Fact]
@ -867,6 +1060,91 @@ public sealed class PhysicsSetPositionTests
Assert.Equal(new Vector3(11f, 11f, 10f), result.Position);
}
[Fact]
public void NormalThenScatterUnionsEveryLandblockProbeInStableOrder()
{
PhysicsEngine engine = FlatEngine();
const uint eastLandblock = 0xAAB40000u;
AddFlatLandblock(engine, eastLandblock, worldOffsetX: 192f);
int pass = 0;
engine.TransitionCellCollisionTestHook =
(_, phase, _, observed) => phase
is TransitionCellCollisionPhase.Environment
&& pass++ == 0
? TransitionState.Collided
: observed;
var random = new Queue<double>([1d, 0.5d]);
engine.SetPositionRandomUnit = random.Dequeue;
PhysicsSetPositionResult result = engine.SetPosition(
Request(
Cell,
new Vector3(180f, 12f, 7f),
new Vector3(180f, 12f, 7f)) with
{
MoverPhysicsState = PhysicsStateFlags.Missile,
Flags = PhysicsSetPositionFlags.Placement
| PhysicsSetPositionFlags.Scatter,
ScatterRadiusX = 20f,
ScatterAttempts = 1u,
});
Assert.True(result.IsCommitted);
int source = IndexOfLandblock(result.QueriedCellIds, Landblock);
int east = IndexOfLandblock(
result.QueriedCellIds,
eastLandblock);
Assert.True(source >= 0);
Assert.True(east > source);
}
[Fact]
public void MultiScatterUnionsFailedAndSuccessfulAttemptLandblocks()
{
PhysicsEngine engine = FlatEngine();
const uint westLandblock = 0xA8B40000u;
const uint eastLandblock = 0xAAB40000u;
AddFlatLandblock(engine, westLandblock, worldOffsetX: -192f);
AddFlatLandblock(engine, eastLandblock, worldOffsetX: 192f);
int pass = 0;
engine.TransitionCellCollisionTestHook =
(_, phase, _, observed) => phase
is TransitionCellCollisionPhase.Environment
&& pass++ == 0
? TransitionState.Collided
: observed;
var random = new Queue<double>([0d, 0.5d, 1d, 0.5d]);
engine.SetPositionRandomUnit = random.Dequeue;
PhysicsSetPositionResult result = engine.SetPosition(
Request(
Cell,
new Vector3(10f, 12f, 7f),
new Vector3(10f, 12f, 7f)) with
{
MoverPhysicsState = PhysicsStateFlags.Missile,
Flags = PhysicsSetPositionFlags.RandomScatter,
ScatterRadiusX = 200f,
ScatterAttempts = 2u,
});
Assert.True(result.IsCommitted);
int west = IndexOfLandblock(
result.QueriedCellIds,
westLandblock);
int east = IndexOfLandblock(
result.QueriedCellIds,
eastLandblock);
Assert.True(west >= 0);
Assert.True(east > west);
Assert.DoesNotContain(
result.CrossCellIds,
cell => (cell & 0xFFFF0000u) == westLandblock);
Assert.Contains(
result.CrossCellIds,
cell => (cell & 0xFFFF0000u) == eastLandblock);
}
[Fact]
public void Scatter_ReusesOneTransitionAndFailedInnerProbeCannotLeakIntoSuccess()
{
@ -1038,4 +1316,43 @@ public sealed class PhysicsSetPositionTests
worldOffsetX,
worldOffsetY);
}
private static CellPhysics ContainmentCell(
Plane plane,
uint[] visibleCells) => new()
{
BSP = new PhysicsBSPTree
{
Root = new PhysicsBSPNode { Type = BSPNodeType.Leaf },
},
WorldTransform = Matrix4x4.Identity,
InverseWorldTransform = Matrix4x4.Identity,
Resolved = new Dictionary<ushort, ResolvedPolygon>(),
CellBSP = new CellBSPTree
{
Root = new CellBSPNode
{
SplittingPlane = plane,
PosNode = new CellBSPNode { Type = BSPNodeType.Leaf },
},
},
Portals = [new PortalInfo(0xFFFF, 0, 0)],
PortalPolygons = new Dictionary<ushort, ResolvedPolygon>(),
VisibleCellIds = new HashSet<uint>(visibleCells),
};
private static int IndexOfLandblock(
ImmutableArray<uint> cells,
uint landblock)
{
for (int index = 0; index < cells.Length; index++)
{
if ((cells[index] & 0xFFFF0000u)
== (landblock & 0xFFFF0000u))
{
return index;
}
}
return -1;
}
}