using System.Numerics;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Walk;
namespace AcDream.App.Tests.Rendering.Walk;
///
/// S3 review fix round 1 (F2, major): the exit-seal CPU/GPU equivalence pin,
/// rewritten to drive the PRODUCER that actually runs. The prior pin (still
/// at ClipFrameLayoutTests.GetSlotPlanes_ExitSealPath_... before this
/// commit) drove ClipFrame.AppendSlot(ClipPlaneSet) directly — an
/// overload with ZERO production callers. Production seal planes come from
/// , which resolves
/// through the driver's private CaptureViews/AppendClipSlot (the
/// SAME pixel-space capture
/// runs during a real interior flood — RetailPViewPassExecutor.
/// DrawExitPortalMask reads it back through exactly this accessor). This
/// file drives that real path directly (
/// + the cast, the same minimal-harness pattern
/// WalkFrameDriverTranscriptTests uses) — reusing 's own private fixture types via the shared
/// partial class.
///
public sealed partial class WalkFrameDriverTests
{
private static WalkScreenPoint[] NdcToPixelPoints(Vector2[] ndcVerts, float width, float height)
{
var points = new WalkScreenPoint[ndcVerts.Length];
for (int i = 0; i < ndcVerts.Length; i++)
{
float px = (ndcVerts[i].X + 1f) * width / 2f;
float py = (1f - ndcVerts[i].Y) * height / 2f;
points[i] = new WalkScreenPoint(px, py, 0f, 1f);
}
return points;
}
///
/// KEEP item 1 — exit seals, rewritten through the real producer.
/// AppendClipSlot's per-edge plane formula (normalize + inward
/// perpendicular, winding-corrected) is IDENTICAL to 's own — two independent
/// implementations of the same retail idea — so an exact plane-for-plane
/// match against ClipPlaneSet.From's output proves the walk's own
/// capture reproduces the CPU polygon's true edge planes, not merely
/// bytes that happen to satisfy a looser geometric check.
/// MUTATION: flip the winding selection (ccw = area2 < 0f
/// instead of >= 0f) — every plane's sign inverts, failing the
/// exact-match assertion. Drop the normalize (Vector2.Normalize
/// removed from the per-edge formula) — the plane magnitude scales by
/// the (unnormalized) edge length, failing the exact-match assertion.
/// See
/// for the third mutation (deleting the AABB overflow branch).
///
[Fact]
public void ExitSealPath_PlanesMatchClipPlaneSetsIndependentComputation_ThroughCaptureViewsAndAppendClipSlot()
{
using var fx = new DispatcherFixture();
var ctx = new TestContext();
using ClipFrame clipFrame = ClipFrame.NoClip();
var driver = new WalkFrameDriver(
fx.Dispatcher, new RecordingLeafRenderer(new List()), new FakeWorldData(), clipFrame: clipFrame);
const uint cellId = 0x100u;
var cell = new WalkCell { CellId = cellId };
cell.PushView();
// A non-axis-aligned convex quad, CCW in NDC — distinct from the
// punch-fan KEEP-2 pin's own verts (ClipFrameLayoutTests).
Vector2[] verts =
[
new(-0.3f, 0.5f), new(-0.6f, -0.2f), new(0.2f, -0.5f), new(0.6f, 0.3f),
];
Assert.True(WalkCopyView.Append(
cell.TopView,
NdcToPixelPoints(verts, ctx.ViewportWidth, ctx.ViewportHeight),
ctx.Rays,
ctx.WorldViewpoint));
ctx.Cells[cellId] = cell;
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero);
((IWalkEventSink)driver).OnInteriorFloodDrawTurn(new[] { cellId }, outsideViewCount: 1);
driver.EndFrame();
Assert.Equal(1, driver.InteriorFloodViewSliceCountAt(0));
ReadOnlySpan planes = driver.InteriorFloodViewClipPlanesAt(0, 0);
Vector4[] expected = ClipPlaneSet.From(new ViewPolygon(verts)).PlaneArray;
Assert.Equal(expected.Length, planes.Length);
for (int i = 0; i < expected.Length; i++)
{
Assert.Equal(expected[i].X, planes[i].X, 4);
Assert.Equal(expected[i].Y, planes[i].Y, 4);
Assert.Equal(expected[i].Z, planes[i].Z, 4);
Assert.Equal(expected[i].W, planes[i].W, 4);
}
}
///
/// F2's first overflow case: a 9-vertex portal view (over = 8) exit-seals to exactly the 4 AABB
/// planes that CONTAIN every one of the 9 source vertices (over-include,
/// never under-include) — AppendClipSlot's conservative fallback
/// for a view too complex for the convex half-plane budget, rather than
/// falling back to slot 0 (which would erase the aperture entirely).
/// MUTATION: delete the if (ndc.Length > ClipFrame.MaxPlanes)
/// branch — the per-edge loop below stack-allocates exactly
/// (8) plane slots, so a 9-edge polygon
/// indexes past the end and throws, failing this test loudly.
///
[Fact]
public void ExitSealPath_NineVertexView_UsesFourAabbPlanesContainingEveryVertex()
{
using var fx = new DispatcherFixture();
var ctx = new TestContext();
using ClipFrame clipFrame = ClipFrame.NoClip();
var driver = new WalkFrameDriver(
fx.Dispatcher, new RecordingLeafRenderer(new List()), new FakeWorldData(), clipFrame: clipFrame);
const uint cellId = 0x100u;
var cell = new WalkCell { CellId = cellId };
cell.PushView();
const int n = 9;
var verts = new Vector2[n];
for (int i = 0; i < n; i++)
{
float angle = i * MathF.Tau / n;
verts[i] = new Vector2(0.7f * MathF.Cos(angle), 0.7f * MathF.Sin(angle));
}
Assert.True(WalkCopyView.Append(
cell.TopView,
NdcToPixelPoints(verts, ctx.ViewportWidth, ctx.ViewportHeight),
ctx.Rays,
ctx.WorldViewpoint));
// No collinear-merge stole a vertex — a genuine 9-gon reached AppendClipSlot.
Assert.Equal(n, cell.TopView.View.Polys[0].VertexCount);
ctx.Cells[cellId] = cell;
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero);
((IWalkEventSink)driver).OnInteriorFloodDrawTurn(new[] { cellId }, outsideViewCount: 1);
driver.EndFrame();
ReadOnlySpan planes = driver.InteriorFloodViewClipPlanesAt(0, 0);
Assert.Equal(4, planes.Length);
float minX = verts.Min(v => v.X), maxX = verts.Max(v => v.X);
float minY = verts.Min(v => v.Y), maxY = verts.Max(v => v.Y);
// Over-include: every source vertex satisfies every plane.
foreach (Vector2 v in verts)
{
var clip = new Vector4(v.X, v.Y, 0f, 1f);
for (int p = 0; p < planes.Length; p++)
Assert.True(Vector4.Dot(planes[p], clip) >= -1e-3f);
}
// Exactly the four axis-aligned NDC bounds, matching AppendClipSlot's own formula.
Vector4[] expected =
[
new(1f, 0f, 0f, -minX), new(-1f, 0f, 0f, maxX),
new(0f, 1f, 0f, -minY), new(0f, -1f, 0f, maxY),
];
for (int i = 0; i < 4; i++)
{
Assert.Equal(expected[i].X, planes[i].X, 3);
Assert.Equal(expected[i].Y, planes[i].Y, 3);
Assert.Equal(expected[i].Z, planes[i].Z, 3);
Assert.Equal(expected[i].W, planes[i].W, 2);
}
}
}