test(render): Phase 2 — Build floods from the outdoor node into buildings (+cycle guard)
TDD characterisation test proving PortalVisibilityBuilder.Build correctly roots at the outdoor cell node (OutdoorCellNode) and floods into adjacent buildings through their entrance portals. No changes to Build or OutdoorCellNode were needed. Key finding: the task spec's building fixture used InsideSide=0 for an exit portal whose building interior is at Y>=5 (Normal=(0,-1,0), D=5). The correct InsideSide is 1 (interior where dot<=0 -> Y>=5); with InsideSide=0 the outdoor camera (Y=-3, dot=8) incorrectly passes as "interior" of the building so OutdoorCellNode.Build's InsideSide flip (0->1) puts the outdoor camera on the wrong side of the gate. Corrected fixture uses InsideSide=1 matching OutdoorCellNodeTests geometry convention (building interior = POSITIVE dot side, outdoor = negative dot side; flip makes outdoor negative-dot side the traversable direction). Both tests pass; full suite 214/214. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2a2cc97d28
commit
c5b4f77fe4
1 changed files with 63 additions and 0 deletions
63
tests/AcDream.App.Tests/Rendering/UnifiedFloodTests.cs
Normal file
63
tests/AcDream.App.Tests/Rendering/UnifiedFloodTests.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
public class UnifiedFloodTests
|
||||
{
|
||||
// Shared building fixture: a building cell whose interior is at Y >= 5.
|
||||
// The exit portal faces -Y (Normal=(0,-1,0)); interior is where dot<=0 -> Y>=5 (InsideSide=1).
|
||||
// The outdoor camera is at Y=-3, which is the OUTDOOR side (Y<5).
|
||||
// OutdoorCellNode.Build flips InsideSide to 0 so the outdoor camera (dot>=0 i.e. Y<5) passes
|
||||
// the side test and the flood reaches the building.
|
||||
private static LoadedCell MakeBuildingCell(uint cellId)
|
||||
{
|
||||
var building = new LoadedCell { CellId = cellId, SeenOutside = true };
|
||||
building.WorldTransform = Matrix4x4.Identity;
|
||||
building.InverseWorldTransform = Matrix4x4.Identity;
|
||||
building.Portals.Add(new CellPortalInfo(0xFFFF, 0, 0, 0));
|
||||
// InsideSide=1: interior where (0,-1,0)·p+5 <= 0 -> Y>=5 (the building body is at Y>=5).
|
||||
building.ClipPlanes.Add(new PortalClipPlane { Normal = new Vector3(0, -1, 0), D = 5f, InsideSide = 1 });
|
||||
building.PortalPolygons.Add(new[]
|
||||
{
|
||||
new Vector3(-1, 5, 0), new Vector3(1, 5, 0), new Vector3(1, 5, 2), new Vector3(-1, 5, 2)
|
||||
});
|
||||
return building;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_RootedAtOutdoorNode_FloodsIntoBuilding()
|
||||
{
|
||||
var building = MakeBuildingCell(0xA9B40170);
|
||||
var node = OutdoorCellNode.Build(0xA9B40031, new[] { building });
|
||||
LoadedCell? Lookup(uint id) => (id & 0xFFFFu) == 0x0170 ? building : null;
|
||||
|
||||
var eye = new Vector3(0, -3, 1);
|
||||
var view = Matrix4x4.CreateLookAt(eye, new Vector3(0, 5, 1), Vector3.UnitZ);
|
||||
var proj = Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 3f, 16f / 9f, 1f, 5000f);
|
||||
|
||||
var frame = PortalVisibilityBuilder.Build(node, eye, Lookup, view * proj);
|
||||
|
||||
Assert.Contains(0xA9B40031u, frame.OrderedVisibleCells); // the outdoor node itself
|
||||
Assert.Contains(0xA9B40170u, frame.OrderedVisibleCells); // flooded into the building
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_OutdoorBuildingCycle_Terminates()
|
||||
{
|
||||
// Building's exit portal reciprocally points back near the node; assert Build
|
||||
// returns (does not hang) and the visible set is bounded/small.
|
||||
var building = MakeBuildingCell(0xA9B40170);
|
||||
var node = OutdoorCellNode.Build(0xA9B40031, new[] { building });
|
||||
LoadedCell? Lookup(uint id) => (id & 0xFFFFu) == 0x0170 ? building
|
||||
: (id & 0xFFFFu) == 0x0031 ? node : null;
|
||||
var eye = new Vector3(0, -3, 1);
|
||||
var view = Matrix4x4.CreateLookAt(eye, new Vector3(0, 5, 1), Vector3.UnitZ);
|
||||
var proj = Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 3f, 16f / 9f, 1f, 5000f);
|
||||
|
||||
var frame = PortalVisibilityBuilder.Build(node, eye, Lookup, view * proj);
|
||||
Assert.True(frame.OrderedVisibleCells.Count < 10); // bounded, no runaway
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue