acdream/tests/AcDream.App.Tests/Rendering/Walk/WalkVisibilityMathTests.cs
Erik 368c480bc2 feat(render) Campaign FW1: flood decomp appendix + the visibility math port
The second decomp round (flood bookkeeping/propagation, view-clip
support, landscape visibility) is archived Ghidra-arbitrated - it caught
a load-bearing BN inversion (InsCellTodoList pops NEAREST-first, which
is what makes the draw list far-to-near) and three more traps (the 192m
elided constant, the min/max double positional swap, the copy_view
cross order - the walk doc section 6 is corrected). WalkVisibilityMath
ports get_pt_limit @0x0054b840, get_clip_height @0x0054cff0,
corner/block_plane_check @0x0054b930/@0x0054d060, block_check
@0x0054dc50, and viewconeCheck @0x0054c250 with retail boundary
semantics (strict cull, inclusive partial, touch-out=Outside /
touch-in=EntirelyInside) under 23 focused tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 09:44:49 +02:00

218 lines
8.4 KiB
C#

using System.Numerics;
using AcDream.App.Rendering.Walk;
namespace AcDream.App.Tests.Rendering.Walk;
public sealed class WalkVisibilityMathTests
{
// ---- get_pt_limit @0x0054b840 ----
[Fact]
public void Up_normal_plane_encodes_inside_above_as_negative_height()
{
// Plane z >= 5 at the origin column: N=(0,0,1), d=-5 → h=5, inside above.
var plane = new WalkPlane(new Vector3(0, 0, 1), -5f);
Assert.Equal(-5f, WalkVisibilityMath.GetPointLimit(0, 0, plane));
}
[Fact]
public void Up_normal_plane_at_or_above_sky_height_is_outside()
{
var plane = new WalkPlane(new Vector3(0, 0, 1), -1000f);
Assert.Equal(
WalkVisibilityMath.OutsideColumn,
WalkVisibilityMath.GetPointLimit(0, 0, plane));
}
[Fact]
public void Up_normal_plane_with_nonpositive_height_is_wholly_inside()
{
var plane = new WalkPlane(new Vector3(0, 0, 1), 3f); // z >= -3
Assert.Equal(
WalkVisibilityMath.InsideColumn,
WalkVisibilityMath.GetPointLimit(0, 0, plane));
}
[Fact]
public void Down_normal_plane_encodes_inside_below_as_positive_height()
{
// Plane z <= 7: N=(0,0,-1), d=7 → h=7, inside below.
var plane = new WalkPlane(new Vector3(0, 0, -1), 7f);
Assert.Equal(7f, WalkVisibilityMath.GetPointLimit(0, 0, plane));
}
[Fact]
public void Down_normal_plane_with_nonpositive_height_is_outside()
{
var plane = new WalkPlane(new Vector3(0, 0, -1), -2f); // z <= -2: nothing above ground
Assert.Equal(
WalkVisibilityMath.OutsideColumn,
WalkVisibilityMath.GetPointLimit(0, 0, plane));
}
[Fact]
public void Vertical_plane_uses_the_side_of_the_ground_point()
{
var plane = new WalkPlane(new Vector3(1, 0, 0), -10f); // x >= 10
Assert.Equal(
WalkVisibilityMath.OutsideColumn,
WalkVisibilityMath.GetPointLimit(5f, 0, plane));
Assert.Equal(
WalkVisibilityMath.InsideColumn,
WalkVisibilityMath.GetPointLimit(15f, 0, plane));
// ON the plane (within epsilon) counts inside.
Assert.Equal(
WalkVisibilityMath.InsideColumn,
WalkVisibilityMath.GetPointLimit(10f, 0, plane));
}
// ---- corner_plane_check @0x0054b930 ----
[Theory]
[InlineData(1001f, 0f, 10f, WalkBoundingType.Outside)] // sentinel outside
[InlineData(0f, 0f, 10f, WalkBoundingType.EntirelyInside)] // sentinel inside
[InlineData(-5f, 6f, 10f, WalkBoundingType.EntirelyInside)] // inside above 5; slab [6,10] wholly above
[InlineData(-5f, 2f, 10f, WalkBoundingType.PartiallyInside)] // slab straddles 5
[InlineData(-5f, 2f, 4f, WalkBoundingType.Outside)] // slab wholly below 5
[InlineData(7f, 2f, 6f, WalkBoundingType.EntirelyInside)] // inside below 7; slab wholly below
[InlineData(7f, 2f, 10f, WalkBoundingType.PartiallyInside)] // slab straddles 7
[InlineData(7f, 8f, 10f, WalkBoundingType.Outside)] // slab wholly above 7
public void Corner_check_classifies_the_slab(
float bound, float minZ, float maxZ, WalkBoundingType expected)
=> Assert.Equal(expected, WalkVisibilityMath.CornerPlaneCheck(bound, minZ, maxZ));
[Fact]
public void Corner_check_boundary_touch_is_out_on_the_out_side_and_in_on_the_in_side()
{
// Retail equality edges (flood appendix report 4): maxZ == h with
// inside-above rejects; minZ == h with inside-above accepts entirely.
Assert.Equal(
WalkBoundingType.Outside,
WalkVisibilityMath.CornerPlaneCheck(-5f, 2f, 5f));
Assert.Equal(
WalkBoundingType.EntirelyInside,
WalkVisibilityMath.CornerPlaneCheck(-5f, 5f, 10f));
// Inside-below: minZ == h rejects; maxZ == h accepts entirely.
Assert.Equal(
WalkBoundingType.Outside,
WalkVisibilityMath.CornerPlaneCheck(5f, 5f, 10f));
Assert.Equal(
WalkBoundingType.EntirelyInside,
WalkVisibilityMath.CornerPlaneCheck(5f, 2f, 5f));
}
// ---- block_plane_check @0x0054d060 ----
[Fact]
public void Plane_check_requires_unanimity_for_outside_and_entirely_inside()
{
Assert.Equal(
WalkBoundingType.Outside,
WalkVisibilityMath.BlockPlaneCheck(1001f, 1001f, 1001f, 1001f, 0f, 10f));
Assert.Equal(
WalkBoundingType.EntirelyInside,
WalkVisibilityMath.BlockPlaneCheck(0f, 0f, 0f, 0f, 0f, 10f));
// 3-of-4 outside is still PARTIAL (the block may straddle the plane).
Assert.Equal(
WalkBoundingType.PartiallyInside,
WalkVisibilityMath.BlockPlaneCheck(1001f, 1001f, 1001f, 0f, 0f, 10f));
Assert.Equal(
WalkBoundingType.PartiallyInside,
WalkVisibilityMath.BlockPlaneCheck(0f, 0f, 0f, 1001f, 0f, 10f));
}
// ---- block_check @0x0054dc50 ----
[Fact]
public void Block_check_culls_on_any_single_fully_outside_plane()
{
// Plane 0 (CY) inside everywhere; plane 1 outside at all four corners.
float[] c = [0f, 1001f];
Assert.Equal(
WalkBoundingType.Outside,
WalkVisibilityMath.BlockCheck(c, c, c, c, planeCount: 1, maxZ: 10f, minZ: 0f));
}
[Fact]
public void Block_check_demotion_is_sticky_across_planes()
{
// CY entirely inside; plane 1 partial at one corner; plane 2 entirely
// inside — the partial must survive to the final result.
float[] cornerA = [0f, -5f, 0f]; // straddles h=5 for slab [2,10]
float[] cornerB = [0f, 0f, 0f];
Assert.Equal(
WalkBoundingType.PartiallyInside,
WalkVisibilityMath.BlockCheck(
cornerA, cornerB, cornerB, cornerB, planeCount: 2, maxZ: 10f, minZ: 2f));
}
[Fact]
public void Block_check_is_entirely_inside_only_with_unanimity_on_every_plane()
{
float[] c = [0f, 0f, 0f];
Assert.Equal(
WalkBoundingType.EntirelyInside,
WalkVisibilityMath.BlockCheck(c, c, c, c, planeCount: 2, maxZ: 10f, minZ: 0f));
}
// ---- FillClipHeights (get_clip_height @0x0054cff0) ----
[Fact]
public void Clip_heights_write_the_cy_plane_then_every_edge_plane()
{
var cy = new WalkPlane(new Vector3(0, 0, 1), -5f);
WalkPlane[] edges =
[
new(new Vector3(0, 0, -1), 20f),
new(new Vector3(1, 0, 0), -100f),
];
Span<float> bounds = stackalloc float[3];
WalkVisibilityMath.FillClipHeights(0f, 0f, cy, edges, bounds);
Assert.Equal(-5f, bounds[0]);
Assert.Equal(20f, bounds[1]);
Assert.Equal(WalkVisibilityMath.OutsideColumn, bounds[2]);
}
// ---- viewconeCheck @0x0054c250 ----
private static readonly WalkPlane Cy = new(new Vector3(0, 1, 0), 0f); // forward = +Y, eye at origin
[Fact]
public void Sphere_fully_behind_the_near_plane_is_outside()
=> Assert.Equal(
WalkBoundingType.Outside,
WalkVisibilityMath.ViewconeCheck(new Vector3(0, -5, 0), 1f, Cy, []));
[Fact]
public void Cull_is_strict_and_partial_is_inclusive_at_the_boundary()
{
// d == -r exactly: NOT culled (strict d < -r), and partial (d <= r).
Assert.Equal(
WalkBoundingType.PartiallyInside,
WalkVisibilityMath.ViewconeCheck(new Vector3(0, -1, 0), 1f, Cy, []));
// d == +r exactly: tangent from inside counts PARTIAL, not entirely.
Assert.Equal(
WalkBoundingType.PartiallyInside,
WalkVisibilityMath.ViewconeCheck(new Vector3(0, 1, 0), 1f, Cy, []));
Assert.Equal(
WalkBoundingType.EntirelyInside,
WalkVisibilityMath.ViewconeCheck(new Vector3(0, 1.01f, 0), 1f, Cy, []));
}
[Fact]
public void Edge_planes_cull_and_demote_like_the_cy_plane()
{
WalkPlane[] edges = [new(new Vector3(1, 0, 0), 0f)]; // inside is x >= 0
Assert.Equal(
WalkBoundingType.Outside,
WalkVisibilityMath.ViewconeCheck(new Vector3(-3, 5, 0), 1f, Cy, edges));
Assert.Equal(
WalkBoundingType.PartiallyInside,
WalkVisibilityMath.ViewconeCheck(new Vector3(0.5f, 5, 0), 1f, Cy, edges));
Assert.Equal(
WalkBoundingType.EntirelyInside,
WalkVisibilityMath.ViewconeCheck(new Vector3(3, 5, 0), 1f, Cy, edges));
}
}