using System.Collections.Generic; using System.Numerics; using AcDream.App.Rendering; using Xunit; namespace AcDream.App.Tests.Rendering; /// /// R-A2 conformance: the per-building flood () /// is the robustness mechanism for the doorway flap. These are SOUNDNESS gates against the measured /// retail values (handoff 2026-06-08 OPTION-A §3.4): a per-building flood touches ≈2 cells and its /// membership is stable under the eye's ~36 µm rest jitter. They prove the per-building path is built /// correctly and does not regress; the live doorway is the visual acceptance test for "the flap is /// gone" (a synthetic fixture cannot reproduce the cottage's exact knife-edge geometry). /// public class PortalVisibilityRobustnessTests { private static Matrix4x4 ViewProj() { var view = Matrix4x4.CreateLookAt(Vector3.Zero, new Vector3(0, 0, -1), Vector3.UnitY); var proj = Matrix4x4.CreatePerspectiveFieldOfView(1.2f, 1.0f, 0.1f, 1000f); return view * proj; } private static Vector3[] Quad(float cx, float cy, float halfW, float halfH, float z) => new[] { new Vector3(cx - halfW, cy - halfH, z), new Vector3(cx + halfW, cy - halfH, z), new Vector3(cx + halfW, cy + halfH, z), new Vector3(cx - halfW, cy + halfH, z), }; private static LoadedCell Cell(uint id, params CellPortalInfo[] portals) => new LoadedCell { CellId = id, WorldTransform = Matrix4x4.Identity, InverseWorldTransform = Matrix4x4.Identity, Portals = new List(portals), }; // A realistic 2-cell building viewed from OUTSIDE: a vestibule (0x0170) with an entrance portal to // the outdoors plus an interior portal to a back room (0x0171). The entrance clip plane puts the // origin eye on the EXTERIOR side (D=3, InsideSide=1 → dot=3 > ε), so the exterior seed fires — // exactly the BuildFromExterior seeding contract (mirrors the existing BuildFromExterior_* fixtures). private static (LoadedCell[] cells, Dictionary lookup) TwoCellBuilding() { const uint VEST = 0x0170, ROOM = 0x0171; var vest = Cell(VEST, new CellPortalInfo(0xFFFF, PolygonId: 0, Flags: 0, OtherPortalId: 0), // entrance to outdoors new CellPortalInfo((ushort)ROOM, PolygonId: 1, Flags: 0, OtherPortalId: 0)); // interior to room vest.PortalPolygons.Add(Quad(0f, 0f, 0.5f, 0.5f, -2f)); // entrance opening, in front of eye vest.PortalPolygons.Add(Quad(0f, 0f, 0.6f, 0.6f, -4f)); // vestibule -> room, deeper vest.ClipPlanes.Add(new PortalClipPlane { Normal = new Vector3(0, 0, 1), D = 3f, InsideSide = 1 }); var room = Cell(ROOM, new CellPortalInfo((ushort)VEST, PolygonId: 0, Flags: 0, OtherPortalId: 1)); room.PortalPolygons.Add(Quad(0f, 0f, 0.6f, 0.6f, -4f)); // reciprocal back to vestibule var all = new Dictionary { [VEST] = vest, [ROOM] = room }; return (new[] { vest, room }, all); } [Fact] public void ConstructViewBuilding_FloodsTheBuildingFromItsEntrance() { var (cells, lookup) = TwoCellBuilding(); var frame = PortalVisibilityBuilder.ConstructViewBuilding( cells, Vector3.Zero, id => lookup.TryGetValue(id, out var c) ? c : null, ViewProj()); Assert.Contains(0x0170u, frame.OrderedVisibleCells); // entrance cell seeded Assert.Contains(0x0171u, frame.OrderedVisibleCells); // back room reached through the interior portal } [Fact] public void ConstructViewBuilding_TouchesAboutTwoCells() { // Conformance to §3.4: each retail per-building flood has cell_draw_num ≈ 2. var (cells, lookup) = TwoCellBuilding(); var frame = PortalVisibilityBuilder.ConstructViewBuilding( cells, Vector3.Zero, id => lookup.TryGetValue(id, out var c) ? c : null, ViewProj()); Assert.InRange(frame.OrderedVisibleCells.Count, 1, 3); } [Fact] public void ConstructViewBuilding_MembershipStableUnderMicrometreEyeJitter() { // Conformance to §3.4: retail's per-building membership is stable while the eye jitters ~36 µm // at rest (measured X≈15 µm, Y≈36 µm, Z≈8 µm). The entrance-bounded seed must return the SAME // OrderedVisibleCells for the eye and the eye + that per-axis jitter — no flap. var (cells, lookup) = TwoCellBuilding(); var vp = ViewProj(); System.Func lk = id => lookup.TryGetValue(id, out var c) ? c : null; var a = PortalVisibilityBuilder.ConstructViewBuilding(cells, Vector3.Zero, lk, vp); var b = PortalVisibilityBuilder.ConstructViewBuilding( cells, new Vector3(15e-6f, 36e-6f, 8e-6f), lk, vp); Assert.Equal(a.OrderedVisibleCells, b.OrderedVisibleCells); } }