Three-lens review of c7ab5b6d8 found the round-0 port of
DrawPortalPolyInternal's degenerate-input guard inverted retail's
quantifier: it rejected a polygon when ANY vertex sat on ANY of the
four +/-12 planes. The PDB-paired bytes (VA 0x59BCD6-0x59BD66: four
per-plane fld/fcomp 12.0/fnstsw/test ah,0x44/jnp sequences, each
accumulating a "every vertex on this plane" boolean, cleared by the
first vertex NOT on it) and the named decomp's own nested-if reading
of the post-loop branch (0x59bd42-0x59bd6c) agree: retail rejects only
when EVERY local vertex lies on the SAME one of the four planes — a
polygon degenerate onto a plane, not one that merely touches a plane
at one vertex.
F1 — the quantifier. WalkVisibilityMath.IsRejectedByPortalPolygonBoundaryGuard
now tracks four booleans (everyVertexOnPlusX/MinusX/PlusY/MinusY), each
starting true and cleared by the first vertex that fails its own plane
test; the guard rejects iff any of the four survives the whole vertex
loop. T2 (WalkVisibilityMathTests.cs) is rewritten to the five §7
cases: every vertex on x=+12 -> rejected; every vertex on y=-12 ->
rejected; one vertex on x=+12, rest inside -> KEPT; every vertex at
11.999 -> kept; vertices split across x=+12 and y=+12 (no common
plane) -> kept. WalkFrameDriverTests' punch-side pin
(OnPunchGeometry_RejectsOnlyWhenEveryVertexSharesOnePlane_ButPunchesAnyOtherShape)
carries the same correction — its old "one vertex on the plane"
polygon was wrongly rejected under round 0; it punches now, and a new
all-on-plane polygon is added as the actual rejected case. The doc
comment's "essentially never" sentence is deleted and replaced with a
fresh DAT-scan citation (see datScan below).
MUTATION (verified): restore the any-vertex form -> the one-vertex and
split-plane admit cases fail (Assert.False Expected:False Actual:True,
x2; the WalkFrameDriverTests punch pin's Assert.Equal Expected:2
Actual:1). Delete the guard (always return false) -> the all-on-plane
reject cases fail (Assert.True Expected:True Actual:False, x3 —
plusX-plane, minusY-plane, and the vacuous-empty-polygon pin; the
punch pin's Assert.Equal Expected:2 Actual:3).
datScan (DAT counts backing F1's doc comment): a throwaway xunit test
(deleted after this commit — not part of the diff) walked the full
0x0000-0xFFFF landblock-prefix space (LandBlockInfo/EnvCell carry no
usable DatCollection.GetAllIdsOfType range — both are landblock-
relative — matching the method CellStructSurfaceConstructionInstalledDatTests
already established), resolved each EnvCell's Environment/CellStruct,
and for every CellPortal's polygon tested each vertex's local x/y
against +/-12. Against the installed client_cell_1.dat: 3,405
landblocks with cells, 1,854,237 total portal polygons. 2,889 polygons
have at least one vertex on a +/-12 plane; 2,163 have EVERY vertex on
the SAME plane — all 2,163 are EXIT portals (OtherCellId==0xFFFF, out
of 16,939 exit portals total); 0 interior (non-exit) portals ever
qualify (interior: 1,837,298 total, 340 any-vertex, 0 all-on-plane).
Duration ~15s. This exactly reproduces the review's cited numbers.
F2 — seal-path count order. RetailPViewPassExecutor's exit-seal loop
dropped the `localVertices.Length < 3` pre-filter that ran BEFORE both
the boundary guard and the `submitted` increment (round 0's order:
length check -> guard -> transform -> draw -> count). Retail's real
order is guard -> transform -> clip -> count, with the counter
incremented BEFORE polyClipFinish runs (0x59BD70-0x59BD74 precedes
0x59BDB0) — the counter records accepted ATTEMPTS, not successful GPU
fans. `submitted++` now runs immediately after the guard-gated
transform+EmitSeamMask, before DrawDepthFan (whose own `< 3` guard is
this port's stand-in for retail's post-clip `var_4 >= 3` check). A new
pin (RetailPViewPassExecutorTests.DrawExitPortalMask_CountsAnUnclippableTwoVertexPolygon_ButDrawsNothing)
constructs a real PortalDepthMaskRenderer (RecordingGpuDevice-backed)
and a constructor-bypassed executor (RuntimeHelpers.GetUninitializedObject,
the same pattern WalkOutsideViewReassemblyTests already uses) with a
2-vertex ordinary-coordinate portal polygon: DrawExitPortalMask returns
submitted=1 while zero draw calls reach the device.
MUTATION (verified): restore the `< 3` pre-filter ahead of the guard
-> the pin's Assert.Equal fails, Expected:1 Actual:0.
F3 — the depth-event transcript gate, automated. New
WalkAlphaDepthTrace.cs (beside WalkOracleTrace.cs) parses the four
`PM poly=<ptr> mode=<0|1> counterBefore=<hex>` / `PC ov=<n>
counter=<hex> fc=<0|1>` lines per oh-capture/*.alphadepth.log frame,
using the same F-marker frame delimiting as WalkOracleTrace. New
WalkTraceConformanceTests.AlphaDepthTranscript.cs (beside
WalkTraceConformanceTests.cs, which is now `partial`) replays each
pose's frame 2 through the real WalkFrameDriver/RetailFrameWalk over
WalkLandscapeDatBuilder-built installed-DAT world data, with:
- AlphaDepthCollectSink: an IWalkEventSink decorator forwarding every
hook to the real driver unchanged, while recording mode=1 punch
events (OnPunchGeometry, counterBefore = driver.PortalsDrawnCount
at COLLECT time — Collect finishes entirely before Replay starts,
so reading the counter at Replay time would already see the SAME
turn's post-reset value; holtburg-doorway-still's own capture
proves this: its punches show counterBefore=2 immediately followed
by its seals' counterBefore=0/1 in the identical turn) and every
PC tuple (Emit, WalkEventKind.DrawCells — fires for the root's own
turn AND every building look-in's re-entrant call, matching
retail's breakpoint not distinguishing them).
- AlphaDepthLeaf: an IWalkFrameLeafRenderer whose DrawExitSeals
reproduces DrawPortalDepthWrite's real per-portal loop (F1's guard,
F2's count-before-clip order) directly over
driver.InteriorFloodCells / WalkCell.Portals / .PortalPolygons —
the identical data the render-side production path reads — since
the interface gives DrawExitSeals no cell/portal parameter at all.
- Two Collect+Replay passes per pose (a fresh driver reaches the SAME
steady-state a running session already carries by the time
retail's capture began — holtburg-doorway-still and foundry-deep
both show a stable per-frame accepted-seal count that regenerates
identically every captured frame): the first pass's own output is
discarded, only the second is compared.
Five Facts (one per pose — the spec's "four" undercounts the five
*.alphadepth.log files actually present in oh-capture/):
AlphaDepthTranscript_CathedralArrival_MatchesRetailFrame2 — PASS
AlphaDepthTranscript_CathedralLeak_MatchesRetailFrame2 — PASS
AlphaDepthTranscript_FoundryDeep_MatchesRetailFrame2 — PASS
AlphaDepthTranscript_HoltburgDoorwayStill_MatchesRetailFrame2 — PASS
(proves the incrementing 0/1/2 accepted-seal sequence AND the
pre-reset counterBefore=2 punches, end to end, against real DAT
portal data)
AlphaDepthTranscript_TerraceEdge_MatchesRetailFrame2 — KnownFailure
(tagged [Trait("Status","KnownFailure")]): terrace-edge never runs
a qualifying interior turn in its own capture (every PC line reads
ov=0) — its punches' counterBefore=2 is a session value carried
over from BEFORE the capture even started (the file's own pre-"F 1"
content already reads 2; nothing inside the captured frames ever
touches the counter). A fresh two-pass replay from
PortalsDrawnCount=0 has no mechanism to derive that leftover value.
Printed divergence: expected [(mode=1,counterBefore=2) x2], actual
[(mode=1,counterBefore=0) x2]; the PC sequence (ov=0 both times)
matches exactly. An initial-condition gap in the fixture, not a
guard/count defect — the identical harness reproduces holtburg-
doorway-still's and foundry-deep's own steady states exactly from
cold starts.
MUTATION (verified): swap AlphaDepthLeaf's recorded seal mode from 0
to 1 -> AlphaDepthTranscript_HoltburgDoorwayStill_MatchesRetailFrame2
fails (Assert.Equal Collections differ at pos 5: expected
(0,0),(0,1)... actual (1,0),(1,1)...).
DispatcherFixture and DrawScope (WalkFrameDriverTests.cs) are widened
from private to internal so the new gate can drive a real
WbDrawDispatcher without duplicating that fixture.
F4 — manifest comment truth. VulkanShaderManifestTests.cs's
portal_depth.vert.spv re-pin comment had the ULP direction backward
(claimed the OLD 0x3F7FFFFE literal was NEARER the camera than
retail's real 0x3F7FFFEF constant; portal_depth.vert's own shader-
source comment already had this correct — FARTHER, larger z/w) and
cited T1 as living in WalkVisibilityMathTests when
PortalDepthVert_FarPunchConstant_MatchesRetailExactBits is in this
same file. Both corrected.
Gates: dotnet build tests/AcDream.App.Tests -c Release -> 0 warnings/0
errors. Hermetic lane (Lane!=InstalledDat&...&Status!=KnownFailure):
6829/6829 passed. InstalledDat lane (real client_cell_1.dat): 253
total, 247 passed, 5 failed — exactly the two #383 layout tests
(LayoutImporterMediaBearingChildSweepTests/LayoutImporterInvisibleSweepTests),
TowerAscentReplayTests' KnownFailure, Oh_doorway_still_first_frame_diff
(#458), and this round's own AlphaDepthTranscript_TerraceEdge
KnownFailure — no other divergence. Shader tests (VulkanShaderDescriptorContractTests
+ VulkanShaderManifestTests + RenderPackSpirvValidatorTests +
SkyVertexLayoutTests): 35/35. Register/divergence tests: 52/52.
dotnet build src/AcDream.App -c Release: 0 warnings/0 errors.
Register: no new AD row. F1/F2 are straight bug fixes toward retail
fidelity (not deviations); AD-119 (the C2 blend-vs-write-mask row)
does not describe the count order, so its neighbour needed no
correction per F2's own instruction.
Deviation from the fix-round spec's literal text: §7 F1 said "swap the
any-vertex hits instead of the four per-plane accumulators" as the
split-plane case's mutation — the code actually ported uses per-vertex
`if (x != 12f) flag = false;` clears, not a per-vertex OR; the mutation
verified above (restore the any-vertex FORM entirely) exercises the
same defect class and is the mutation actually run. §7 also names
"four" alphadepth.log fixtures where five exist on disk
(cathedral-arrival, cathedral-leak, foundry-deep,
holtburg-doorway-still, terrace-edge); all five are gated rather than
four, with cathedral-leak an unremarked addition alongside the four
the spec names.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
300 lines
13 KiB
C#
300 lines
13 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));
|
|
}
|
|
|
|
// ---- 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_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([]));
|
|
}
|
|
}
|