// CellVisibilityFromRootTests.cs — UCG W2 Task 2 + Stage 3: tests for // CellVisibility.ComputeVisibilityFromRoot. // // Acceptance criteria (Stage 3 — W2 null-fallback deleted): // (a) ComputeVisibilityFromRoot(null, pos) returns NULL (outdoor root), regardless // of whether any cells are registered. The AABB FindCameraCell fallback is gone. // (b) ComputeVisibilityFromRoot(root, pos) with a registered root returns // a result whose CameraCell is that root, regardless of whether 'pos' // is geometrically inside it. // // CellVisibility is intentionally free of GL types — it can be unit-tested // without a GPU context (confirmed: only System.Numerics dependency). using System.Numerics; using AcDream.App.Rendering; using Xunit; namespace AcDream.App.Tests.Rendering; public class CellVisibilityFromRootTests { // ------------------------------------------------------------------ // Helpers // ------------------------------------------------------------------ /// /// Build a minimal LoadedCell with an axis-aligned bounding box and identity /// transform so PointInCell works for a position inside the box. /// private static LoadedCell MakeCell(uint cellId, Vector3 boundsMin, Vector3 boundsMax) { return new LoadedCell { CellId = cellId, WorldTransform = Matrix4x4.Identity, InverseWorldTransform = Matrix4x4.Identity, LocalBoundsMin = boundsMin, LocalBoundsMax = boundsMax, Portals = new(), ClipPlanes = new(), PortalPolygons = new(), }; } // ------------------------------------------------------------------ // (a) Stage 3: null root → null (outdoor root), not a position fallback // ------------------------------------------------------------------ [Fact] public void ComputeVisibilityFromRoot_NullRoot_ReturnsNull_WhenCellExists() { // Stage 3: null root → outdoor root → null result, even when a cell covers the // fallback position. Pre-Stage 3 this called FindCameraCell(pos); now the caller // must supply the root (physics CellGraph.CurrCell). Retail: CellManager::ChangePosition // reads the transition-owned curr_cell — it does not re-derive from a static position. var cv = new CellVisibility(); var cell = MakeCell(0xA9B40101u, Vector3.Zero, new Vector3(10, 10, 10)); cv.AddCell(cell); var pos = new Vector3(5, 5, 5); // inside the cell — null root overrides var fromNull = cv.ComputeVisibilityFromRoot(null, pos); // Stage 3: null root → null (outdoor root path). Assert.Null(fromNull); } [Fact] public void ComputeVisibilityFromRoot_NullRoot_NoCells_ReturnsNull() { // With no cells registered and null root: always null (outdoor root). var cv = new CellVisibility(); var posOutdoors = new Vector3(100, 100, 100); var fromNull = cv.ComputeVisibilityFromRoot(null, posOutdoors); Assert.Null(fromNull); } [Fact] public void ComputeVisibilityFromRoot_NullRoot_PositionOutsideAllCells_ReturnsNull() { // Cell exists but null root: always null regardless of position. var cv = new CellVisibility(); var cell = MakeCell(0xA9B40102u, Vector3.Zero, new Vector3(5, 5, 5)); cv.AddCell(cell); var posOutside = new Vector3(100, 100, 100); var fromNull = cv.ComputeVisibilityFromRoot(null, posOutside); Assert.Null(fromNull); } // ------------------------------------------------------------------ // (b) Supplied root is used as BFS root → CameraCell == root // ------------------------------------------------------------------ [Fact] public void ComputeVisibilityFromRoot_RegisteredRoot_CameraCellIsSuppliedRoot() { // Arrange: cell registered in CellVisibility. var cv = new CellVisibility(); var cell = MakeCell(0xA9B40103u, Vector3.Zero, new Vector3(10, 10, 10)); cv.AddCell(cell); // The position can be OUTSIDE the cell — physics already determined membership // via BSP, we just trust that answer. var posAnywhere = new Vector3(999, 999, 999); // Act var result = cv.ComputeVisibilityFromRoot(cell, posAnywhere); // Assert: CameraCell is the supplied root. Assert.NotNull(result); Assert.Same(cell, result!.CameraCell); } [Fact] public void ComputeVisibilityFromRoot_RegisteredRoot_IncludesRootInVisibleCells() { var cv = new CellVisibility(); var cell = MakeCell(0xA9B40104u, Vector3.Zero, new Vector3(10, 10, 10)); cv.AddCell(cell); var result = cv.ComputeVisibilityFromRoot(cell, Vector3.Zero); Assert.NotNull(result); Assert.Contains(cell.CellId, result!.VisibleCellIds); } [Fact] public void ComputeVisibilityFromRoot_RegisteredRoot_LastVisibilityResultUpdated() { var cv = new CellVisibility(); var cell = MakeCell(0xA9B40105u, Vector3.Zero, new Vector3(10, 10, 10)); cv.AddCell(cell); var result = cv.ComputeVisibilityFromRoot(cell, Vector3.Zero); Assert.Same(result, cv.LastVisibilityResult); } }