The user's "thin strip of background color along the TOP outer edge of a
doorway, looking out from inside" is the landscape-slice scissor box, not
the W=0 clip port.
Mechanism (pinned headlessly, Issue130DoorwayStripTests, 147 eye/gaze
combos at the real Holtburg A9B4 0x0170 exit door):
- BeginDoorwayScissor converted the slice NDC AABB to pixels as
Floor(origin) + Ceiling(size). The far edge floor(min)+ceil(max-min)
lands up to ONE PIXEL SHORT of the true top/right edge at unlucky
fractional alignments (captured: top edge y=0.7938 @1080p -> row 968
cut; right edge column 1296 @1920 cut).
- The scissor brackets the ENTIRE landscape slice (sky, terrain, outdoor
statics, weather). The exit-portal SEAL stamps the full raw aperture at
true depth and the shell wall ends at the aperture edge, so the cut row
never receives any color write -> clear color, flickering with eye
movement as the fractional alignment shifts.
- This violated AD-17's own invariant (over-inclusion is safe,
UNDER-inclusion is the bug class). No register change: the fix restores
the row's documented doctrine.
Lead 1 (987313a W=0 clip port regression) REFUTED by the same harness:
the CPU polygon pipeline (ProjectToClip -> ClipToRegion merges ->
ClipPlaneSet planes) is sub-pixel exact against the raw aperture
projection (worst 0.54 px, 0.00 px aligned). For an all-in-front doorway
polygon the port is bit-identical to the old 1e-4 path by construction.
The EyeInsidePortalOpening rescue stays deleted.
Fix: conservative outer bound floor(min)/ceil(max) extracted to
NdcScissorRect.ToPixels (GL-free; containment property proven in the
header comment); BeginDoorwayScissor delegates.
Pins:
- NdcScissorRectTests: center-inside containment across 251 fractional
alignments x 2 framebuffer sizes + both captured regression cases.
- Issue130DoorwayStripTests: production flood + assembler at the real
exit door; asserts the scissor never cuts a plane-admitted fragment
(worstScissorGap 0.00 px post-fix, was 10.8 px capped) and the CPU
pipeline stays sub-pixel exact (canary 1.2 px).
Suites: App 252+1skip / Core 1439+2skip / UI 420 / Net 294 green.
Awaiting the user visual gate at a cottage doorway.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
80 lines
3.7 KiB
C#
80 lines
3.7 KiB
C#
using System;
|
||
using System.Numerics;
|
||
using AcDream.App.Rendering;
|
||
using Xunit;
|
||
|
||
namespace AcDream.App.Tests.Rendering;
|
||
|
||
/// <summary>
|
||
/// #130: the doorway-slice scissor must be a CONSERVATIVE outer bound of its
|
||
/// NDC AABB (AD-17: over-inclusion safe, under-inclusion is the bug class).
|
||
/// The old Floor(origin)+Ceiling(size) form put the far edge at
|
||
/// floor(min)+ceil(max−min), up to one pixel short of the true max edge —
|
||
/// the doorway top-edge background strip.
|
||
/// </summary>
|
||
public class NdcScissorRectTests
|
||
{
|
||
/// <summary>Containment property: every pixel whose CENTER lies inside the
|
||
/// NDC box is inside the scissor box, across a dense grid of fractional
|
||
/// alignments at two framebuffer sizes.</summary>
|
||
[Theory]
|
||
[InlineData(1920, 1080)]
|
||
[InlineData(2560, 1440)]
|
||
public void EveryCenterInsidePixel_IsInsideTheBox(int fbW, int fbH)
|
||
{
|
||
for (int i = 0; i < 251; i++)
|
||
{
|
||
// Sweep fractional alignments of all four edges.
|
||
float f = i / 251f;
|
||
float minX = -0.83f + f * 0.0031f;
|
||
float minY = -0.71f + f * 0.0047f;
|
||
float maxX = 0.339f + f * 0.0043f;
|
||
float maxY = 0.7938f + f * 0.0029f;
|
||
var box = NdcScissorRect.ToPixels(new Vector4(minX, minY, maxX, maxY), fbW, fbH);
|
||
|
||
// Pixel-space extremes of center-inside pixels.
|
||
float x0 = (minX * 0.5f + 0.5f) * fbW, x1 = (maxX * 0.5f + 0.5f) * fbW;
|
||
float y0 = (minY * 0.5f + 0.5f) * fbH, y1 = (maxY * 0.5f + 0.5f) * fbH;
|
||
int loX = (int)MathF.Ceiling(x0 - 0.5f), hiX = (int)MathF.Floor(x1 - 0.5f);
|
||
int loY = (int)MathF.Ceiling(y0 - 0.5f), hiY = (int)MathF.Floor(y1 - 0.5f);
|
||
|
||
Assert.True(box.X <= loX, $"left cut: box.X={box.X} > loX={loX} (minX={minX})");
|
||
Assert.True(box.Y <= loY, $"bottom cut: box.Y={box.Y} > loY={loY} (minY={minY})");
|
||
Assert.True(box.X + box.Width > hiX, $"right cut: box ends {box.X + box.Width} <= hiX={hiX} (maxX={maxX})");
|
||
Assert.True(box.Y + box.Height > hiY, $"top cut: box ends {box.Y + box.Height} <= hiY={hiY} (maxY={maxY})");
|
||
// Over-inclusion stays bounded (≤1 px per edge).
|
||
Assert.True(box.X >= loX - 1 && box.Y >= loY - 1);
|
||
Assert.True(box.X + box.Width <= hiX + 2 && box.Y + box.Height <= hiY + 2);
|
||
}
|
||
}
|
||
|
||
[Fact]
|
||
public void CapturedRegression_TopEdgeRow968_At1080p()
|
||
{
|
||
// Issue130DoorwayStripTests live capture: aperture top y=0.7938 →
|
||
// pixel row 968 (center 968.5 < 968.65). The old formula ended the box
|
||
// at row 967 — the visible strip.
|
||
var box = NdcScissorRect.ToPixels(new Vector4(-0.339f, -0.743f, 0.339f, 0.7938f), 1920, 1080);
|
||
Assert.True(box.Y + box.Height > 968, $"top row 968 cut: box ends at {box.Y + box.Height}");
|
||
}
|
||
|
||
[Fact]
|
||
public void CapturedRegression_RightColumn1296_At1920()
|
||
{
|
||
// Issue130DoorwayStripTests live capture: gate right edge x=0.3507 →
|
||
// pixel column 1296 admitted by the plane gate; the old formula ended
|
||
// the box at column 1295.
|
||
var box = NdcScissorRect.ToPixels(new Vector4(-0.2845f, -1.0f, 0.3507f, 0.2630f), 1920, 1080);
|
||
Assert.True(box.X + box.Width > 1296, $"right column 1296 cut: box ends at {box.X + box.Width}");
|
||
}
|
||
|
||
[Fact]
|
||
public void DegenerateAndOffscreenBoxes_StayValid()
|
||
{
|
||
// Past-the-edge regions clamp to the screen and keep min 1 px size.
|
||
var box = NdcScissorRect.ToPixels(new Vector4(0.999f, 0.999f, 1.5f, 1.5f), 1920, 1080);
|
||
Assert.True(box.Width >= 1 && box.Height >= 1);
|
||
var inverted = NdcScissorRect.ToPixels(new Vector4(1f, 1f, -1f, -1f), 1920, 1080);
|
||
Assert.True(inverted.Width >= 1 && inverted.Height >= 1);
|
||
}
|
||
}
|