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 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)); } // ---- DrawPortalPolyInternal @0x0059bc90's degenerate-input guard // (S4-c1 C1, T2 — quantifier corrected at fix round 1 F1): the polygon // is rejected only when EVERY local vertex lies on the SAME one of the // four planes x=+12, x=-12, y=+12, y=-12. A polygon with merely ONE // vertex on a bound is an ordinary polygon and is kept. ---- [Fact] public void Boundary_guard_rejects_a_polygon_whose_every_vertex_sits_on_the_same_plusX_plane() { // MUTATION: delete the guard (always return false) — this case, // and every other all-on-plane case below, fails to reject. Vector3[] polygon = [new Vector3(12f, -3f, 3f), new Vector3(12f, 0f, 3f), new Vector3(12f, 5f, 3f)]; Assert.True(WalkVisibilityMath.IsRejectedByPortalPolygonBoundaryGuard(polygon)); } [Fact] public void Boundary_guard_rejects_a_polygon_whose_every_vertex_sits_on_the_same_minusY_plane() { // The y=-12 plane specifically (§7 F1's fourth case) — not just x. Vector3[] polygon = [new Vector3(-3f, -12f, 3f), new Vector3(0f, -12f, 3f), new Vector3(5f, -12f, 3f)]; Assert.True(WalkVisibilityMath.IsRejectedByPortalPolygonBoundaryGuard(polygon)); } [Fact] public void Boundary_guard_admits_a_polygon_with_only_one_vertex_on_plusX12_the_rest_inside() { // The round-1 defect: the round-0 "any vertex" reading would have // rejected this. Retail draws it (punched/sealed, counted) — only // the DEGENERATE all-on-one-plane polygon is dropped. // MUTATION: restore the any-vertex form (any vertex == +/-12 // rejects) — this case starts failing (wrongly rejected) even // though the guard still correctly rejects the all-on-plane cases. Vector3[] polygon = [new Vector3(0f, 0f, 3f), new Vector3(12f, 0f, 3f), new Vector3(5f, 5f, 3f)]; Assert.False(WalkVisibilityMath.IsRejectedByPortalPolygonBoundaryGuard(polygon)); } [Fact] public void Boundary_guard_admits_a_polygon_whose_every_vertex_is_just_inside_11_999() { Vector3[] polygon = [new Vector3(11.999f, 0f, 3f), new Vector3(11.999f, 5f, 3f), new Vector3(11.999f, -5f, 3f)]; Assert.False(WalkVisibilityMath.IsRejectedByPortalPolygonBoundaryGuard(polygon)); } [Fact] public void Boundary_guard_admits_a_polygon_split_across_plusX12_and_plusY12_no_common_plane() { // Two different vertices sit on two DIFFERENT bounding planes; no // single plane holds every vertex, so nothing rejects it. // MUTATION: OR the four per-vertex hits instead of the four // per-plane accumulators (i.e. revert to "any vertex on any plane") // — this case starts failing (wrongly rejected). Vector3[] polygon = [new Vector3(12f, 0f, 3f), new Vector3(0f, 12f, 3f), new Vector3(-5f, -5f, 3f)]; Assert.False(WalkVisibilityMath.IsRejectedByPortalPolygonBoundaryGuard(polygon)); } [Fact] public void Boundary_guard_admits_a_polygon_whose_every_vertex_is_on_SOME_plane_but_not_the_SAME_one() { // S4-c1 fix round 2, R2-3: the existing split-plane case above // (12,0,3)/(0,12,3)/(-5,-5,3) does NOT discriminate retail's real // quantifier ("every vertex on the SAME plane") from the weaker // "every vertex on SOME plane" (per-vertex OR across the four // planes, ANDed across vertices) — its third vertex sits on NO // plane at all, so both readings admit it for the same reason. This // case closes that gap: EVERY vertex here touches a plane — // (12,0,3) and (12,5,3) sit on x=+12, (0,12,3) sits on y=+12 — yet // no SINGLE plane holds all three: x=+12 is cleared by vertex 2 // (x=0), y=+12 is cleared by vertex 1 (y=0). Retail's four // independent per-plane accumulators admit it (neither survives to // the end of the loop). // MUTATION: replace the four per-plane accumulators with a single // per-vertex "is this vertex on ANY of the four planes" test ANDed // across vertices — this case starts failing (wrongly rejected): // every vertex here IS on some plane, so the per-vertex-OR form // rejects it. Vector3[] polygon = [new Vector3(12f, 0f, 3f), new Vector3(0f, 12f, 3f), new Vector3(12f, 5f, 3f)]; Assert.False(WalkVisibilityMath.IsRejectedByPortalPolygonBoundaryGuard(polygon)); } [Fact] public void Boundary_guard_ignores_the_vertical_z_component() { // Retail's guard reads only the two horizontal FIELDS the decomp // names (arg1->vertices[i] as the x source, ecx_1[1] as the y // source) — a vertex whose HEIGHT happens to be +/-12 is an entirely // ordinary local coordinate and must not trip the guard. Vector3[] polygon = [new Vector3(0, 0, 12f), new Vector3(1, 1, -12f), new Vector3(2, 0, 0)]; Assert.False(WalkVisibilityMath.IsRejectedByPortalPolygonBoundaryGuard(polygon)); } [Fact] public void Boundary_guard_rejects_the_empty_polygon_because_every_plane_predicate_survives_vacuously() { // S4-c1 fix round 1 F1 consequence, not an explicit §7 case: retail // wraps the whole predicate+count block in `if (num_pts > 0)` // (0x0059bcc9) — for num_pts==0 NOTHING runs: no count, no clip, no // draw. The four-accumulator port reproduces that "nothing happens" // effect for free: with no vertex to clear any of them, all four // per-plane predicates stay at their initial `true`, so the guard // rejects (bails before transform/clip/count) — the same net effect // as retail's outer num_pts>0 gate. Real dat portal polygons are // never empty (the doc comment's DAT scan enumerates only >=3-vertex // polygons); this is a defensive corner, not a live path. Assert.True(WalkVisibilityMath.IsRejectedByPortalPolygonBoundaryGuard([])); } }