fix(physics): restore retail cell availability semantics

This commit is contained in:
Erik 2026-07-31 14:22:45 +02:00
parent d3c0d9ec0e
commit 7716c2ee89
14 changed files with 393 additions and 62 deletions

View file

@ -8,16 +8,11 @@ namespace AcDream.Core.Tests.Physics;
public class CellTransitCheckBuildingTransitTests
{
[Fact]
public void BuildingPortalWithUnloadedCellBSP_NoCandidateAdded()
public void BuildingPortalWithLoadedNullRoot_CellIsAdmitted()
{
// Verifies the null-CellBSP guard: when the destination interior cell
// is cached but its CellBSP isn't yet loaded (or is structurally absent),
// CheckBuildingTransit must NOT add the cell to candidates — even though
// PointInsideCellBsp(null, _) returns true.
//
// Happy-path (CellBSP present, sphere inside) requires a synthetic
// CellBSPTree which is non-trivial to construct from DatReaderWriter
// types. Deferred to visual verification.
// Retail separates an unavailable CEnvCell lookup from an authored
// CellStruct whose cell_bsp root is null. The latter is loaded, and
// the null-root sphere query is the universal-inside base case.
// Building at world origin. One portal to interior cell 0xA9B40100.
var building = new BuildingPhysics
@ -33,14 +28,13 @@ public class CellTransitCheckBuildingTransitTests
},
};
// Interior cell with null CellBSP — PointInsideCellBsp(null, _) returns true,
// but CheckBuildingTransit guards on CellBSP?.Root being non-null, so this
// cell is skipped.
// Interior cell with an authored null containment root.
var interiorCell = new CellPhysics
{
WorldTransform = Matrix4x4.Identity,
InverseWorldTransform = Matrix4x4.Identity,
Resolved = new Dictionary<ushort, ResolvedPolygon>(),
CellBSP = new DatReaderWriter.Types.CellBSPTree { Root = null },
};
var cache = new PhysicsDataCache();
@ -53,8 +47,33 @@ public class CellTransitCheckBuildingTransitTests
sphereRadius: 0.5f,
candidates);
// CellBSP is null → containment guard (otherCell?.CellBSP?.Root is null)
// skips this cell. No candidate added.
Assert.Contains(0xA9B40100u, candidates);
}
[Fact]
public void BuildingPortalWithUnavailableCell_NoCandidateAdded()
{
var building = new BuildingPhysics
{
WorldTransform = Matrix4x4.Identity,
InverseWorldTransform = Matrix4x4.Identity,
Portals =
[
new BldPortalInfo(
otherCellId: 0xA9B40100u,
otherPortalId: 0,
flags: 0),
],
};
var candidates = new HashSet<uint>();
CellTransit.CheckBuildingTransit(
new PhysicsDataCache(),
building,
worldSphereCenter: Vector3.Zero,
sphereRadius: 0.5f,
candidates);
Assert.Empty(candidates);
}