fix(physics): validate retail cell containment roots

This commit is contained in:
Erik 2026-07-31 14:48:26 +02:00
parent 7716c2ee89
commit 3e0f3b6206
23 changed files with 429 additions and 197 deletions

View file

@ -172,7 +172,7 @@ public class CellTransitFindCellSetTests
// ──────────────────────────────────────────────────────────────────
[Fact]
public void OutdoorSeed_CrossesLandblockBoundary_South()
public void OutdoorSeed_CrossesLandblockBoundary_South_AfterDestinationHydrates()
{
// The #106 acceptance golden: walking south out of A9B4, the outdoor
// cell must advance to the southern neighbour block's cell. Origin of
@ -183,14 +183,25 @@ public class CellTransitFindCellSetTests
var cache = new PhysicsDataCache();
cache.CellGraph.RegisterTerrain(0xA9B40000u, new TerrainSurface(new byte[81], new float[256]), Vector3.Zero);
uint containing = CellTransit.FindCellSet(
uint unavailable = CellTransit.FindCellSet(
cache, new Vector3(150f, -0.2f, 0f), sphereRadius: 0.5f,
currentCellId: 0xA9B40031u,
out var cellSet);
Assert.Equal(0xA9B30038u, containing);
Assert.Equal(0xA9B40031u, unavailable);
Assert.Contains(0xA9B30038u, cellSet);
Assert.Contains(0xA9B40031u, cellSet); // +Y neighbour still in the set
cache.CellGraph.RegisterTerrain(
0xA9B30000u,
new TerrainSurface(new byte[81], new float[256]),
new Vector3(0f, -192f, 0f));
uint containing = CellTransit.FindCellSet(
cache, new Vector3(150f, -0.2f, 0f), sphereRadius: 0.5f,
currentCellId: 0xA9B40031u,
out _);
Assert.Equal(0xA9B30038u, containing);
}
[Fact]
@ -227,6 +238,10 @@ public class CellTransitFindCellSetTests
0xA9B30000u,
new TerrainSurface(new byte[81], new float[256]),
new Vector3(0f, -192f, 0f));
cache.CellGraph.RegisterTerrain(
0xA9B40000u,
new TerrainSurface(new byte[81], new float[256]),
Vector3.Zero);
uint containing = CellTransit.FindCellSet(
cache, new Vector3(150f, 1f, 0f), sphereRadius: 0.5f,
@ -315,27 +330,29 @@ public class CellTransitFindCellSetTests
}
[Fact]
public void IndoorSeed_LoadedNullRoot_IsUniversallyInside_StaysCurrent()
public void FindVisibleChildCell_RootlessContainment_IsUnavailable()
{
// Retail distinguishes a failed cell lookup from a loaded CellStruct
// whose containment root is null. The latter is the BSP query's
// universally-inside base case, so the current cell wins immediately.
// Bypass production cache validation to pin the traversal boundary:
// a rootless fixture cannot claim a point even though the recursive
// helper's missing-positive-child base case returns inside.
Matrix4x4.Invert(Matrix4x4.Identity, out var inv);
var cellNoBsp = new CellPhysics
{
WorldTransform = Matrix4x4.Identity,
InverseWorldTransform = inv,
Resolved = new Dictionary<ushort, ResolvedPolygon>(),
Portals = [new PortalInfo(0x0101, 0, 0)],
};
var cache = new PhysicsDataCache();
cache.RegisterCellStructForTest(0xA9B40150u, cellNoBsp);
uint containing = CellTransit.FindCellSet(
cache, new Vector3(-10f, 12f, 0f), sphereRadius: 0.5f,
currentCellId: 0xA9B40150u,
out _);
uint containing = CellTransit.FindVisibleChildCell(
cache,
0xA9B40150u,
new Vector3(-10f, 12f, 0f),
useStabList: true);
Assert.Equal(0xA9B40150u, containing);
Assert.Equal(0u, containing);
}
// ──────────────────────────────────────────────────────────────────