chore(render): S3 landing hygiene — scissor-era dead state, stale comments, the #130 harness, a conjunction-proof weather-gate pin

Chunk H of Campaign OVERHAUL v2's S3 walk-ownership program
(docs/research/2026-09-01-overhaul/s3-walk-ownership-map.md §10.6), landing
the three post-hoc three-lens findings on S3 chunk 4 round 2. Pure dead-state
deletion, comment truth, one test deletion, one stronger pin — no admission,
order, or behavior change; no new flag or probe; no register row (AD-17's
correction is the lead's own, already on the campaign branch).

H1 — ClipPlaneSet.cs: deleted ScissorNdcAabb (no production reader — only
AppendOutsideSlice consumes ClipPlaneSet, and it reads only
IsNothingVisible/Count/PlaneArray) and renamed UseScissorFallback ->
IsPlaneOverflow with a doc stating the true consumer contract: Count==0
overflow means the region draws UNCLIPPED via the no-clip slot, never a
scissor box. Rewrote the file header's convexity-rule and Count==0-states
sections to the two states that exist (Empty / overflow) instead of three,
and simplified the private Scissor(...) AABB-computing helpers into a
parameterless Overflow() factory since nothing reads the AABB any more.
ClipPlaneSetTests.cs: renamed every UseScissorFallback reference, deleted
the AABB assertions (From_MultiplePolygons's four ScissorNdcAabb.X/Y/Z/W
checks, Empty_StaticProperty_DrawsNothing's degenerate-box check,
ScissorFallback_IsNotNothingVisible's bounds check) that pinned the deleted
consumer contract.

H2 — TerrainModernRenderer.cs: deleted Draw's clipPlanes/ndcClipAabb
parameters (grep: TerrainModernRenderer.Draw has exactly one production
caller, WorldScenePassExecutor.DrawFlatTerrain, and it never passed
either), which made CollectVisibleCells's own clipPlanes/ndcClipAabb/
viewProjection parameters and the IsAabbVisibleThroughClipRegion +
IsAabbOutsideHomogeneousPlane helper pair they fed entirely dead — deleted
along with the stale "Retail publishes landcell in_view from the clipped
landscape view" comment; CollectVisibleCells is frustum-only now, matching
DrawLandCells's own "retail never view-clips terrain" doc. Deleted the
terrainUploadCount computation at RetailPViewRenderer.cs (the
DrawInside/DrawLandscapeDynamicsPhase caller) and PrepareClipFrame's
GL-era reservation-count parameter end to end:
IWorldPassSurface.PrepareClipFrame(int), RhiWorldPassSurface's matching
body and its now-pointless "_ = terrainUploadCount" discard,
RetailPViewPassExecutor.PrepareClipFrame's pass-through, and
WorldScenePassExecutor.PrepareFlatWorldClip's hardcoded `1` argument —
PrepareClipFrame() takes nothing on every arm.
TerrainParticleCellVisibilityTests.cs: deleted the three tests that only
existed to pin the removed clip-plane/scissor mechanism
(RejectsCellsOutsideDoorwayClipPlanes, RejectsCellsOutsideDoorwayScissorAabb,
UnionsCellsFromEveryLandscapeSlice) and updated the two surviving frustum
tests to the new signature.

H3 — deleted tests/AcDream.App.Tests/Rendering/Issue130DoorwayStripTests.cs
outright. #130 was a background-color strip along a doorway's top edge from
a since-fixed under-inclusion in the CPU clip pipeline; its scissor half was
already retired at S3 chunk 4 fix round 2 (L7), and its surviving plane-gap
half exercised only PortalVisibilityBuilder.Build and
ClipFrameAssembler.Assemble, both confirmed zero-production-caller legacy
paths (grep) — the one production stage it actually touched,
ClipPlaneSet.From, is already pinned through the real producers
(BeginWalkFrame/ReassembleOutsideViewFromWalk) by the K6/L4 punch-fan
equivalence pin in ClipFrameLayoutTests, so nothing production-relevant lost
coverage.

H4 — comment truth pass:
- ClipFrameAssembler.cs: rewrote the file-header "renderer uses scissor for
  passes that need that fallback" line and the TerrainClipMode summary's
  "that path never produces Scissor" clause. Deleted the TerrainClipMode
  enum's Scissor member — grepped every reader first
  (WorldRenderDiagnostics.cs, WorldSceneDiagnosticsController.cs,
  WorldSceneRendererTests.cs all only pass the value through to a string
  Append or a mock parameter; WorldSceneRenderer.cs, the only writer, sets
  only Planes (default) or Skip, never Scissor) — kept Planes/Skip for the
  flat path and corrected Skip's stale doc ("No outside_view slice is
  visible; skip landscape indoors", a leftover from the pre-round-2 walk
  meaning) to what the flat path's own writer actually means now ("the
  PView walk ran instead of the flat-world path this frame"). Corrected
  ClipViewSlice's doc: the AABB is retained for WorldRenderDiagnostics only,
  not for a scissor consumer.
- WorldPassSurface.cs: "Four concerns... the clip-frame publication, the
  doorway scissor, gl_ClipDistance enablement, and retail's interior depth
  clear" -> three concerns (the doorway scissor was retired at L2), with a
  pointer to ClearInteriorDepth's own truthful doc. EnableClipDistances'
  "all three world vertex shaders already write 1.0" -> only
  portal_depth.vert writes gl_ClipDistance now (grep: sky.vert and
  terrain_modern.vert/terrain_atmospheric.vert no longer declare it at all
  — L3 deleted the block outright; portal_depth.vert still writes 1.0 past
  its active clip count). RhiWorldPassSurface's class doc dropped "the
  scissor is dynamic state on the borrowed encoder" (no producer left).
- RetailPViewPassExecutorTests.cs: DrawLandscapeDynamicsPhase_CallsDraw
  WeatherOnceExactlyOnce's doc corrected "unconditionally" (false since K2)
  to "conditional on WalkFrameDriver.WeatherTurnFired, see the L1 pin".
  Added the switch-table scope note (matching L8's own note on
  DrawWalkSky_RenderSkyCallSiteHasNoEnclosingBackwardBranch) to the K1 pin's
  doc (DrawLandscapeDynamicsPhase_DrawWeatherOnceCallSiteHasNoEnclosing
  BackwardBranch).
- RetailPViewRenderer.cs: the DrawWeatherOnce citation cited the callee
  address (GameSky::Draw(sky,1) @0x00506ff0) where §10.1 cites the call
  site (@0x00506396) — corrected to cite the call site with the callee
  address alongside it.

H5 — MUTATION CHECK (mandatory, run against the production gate at
RetailPViewRenderer.DrawLandscapeDynamicsPhase):
  Added DrawLandscapeDynamicsPhase_ExactlyOneBranchGuardsDrawWeatherOnce,
  which widens the L1 pin's window to start at the
  DrawUnattachedSceneParticles call (the last call before the whole gate)
  and asserts EXACTLY ONE branch total in that window — the L1 pin's own
  window (getter-call to draw-call) misses a conjoined gate's extra,
  earlier condition because that condition's own brfalse lands BEFORE the
  getter call's offset.
  - Mutation 1 (the conjoined gate this pin exists to reject): changed the
    gate to `if (clipAssembly.OutsideViewSlices.Length != 0 &&
    walkDriver.WeatherTurnFired)`, built, ran the new pin — FAILED with:
      Assert.Single() Failure: The collection contained 2 matching items
      Expected:      (predicate expression)
      Collection:    [CompiledBranch { Offset = 7, OpCode = brfalse.s, TargetOffset = 17 }, CompiledBranch { Offset = 24, OpCode = brfalse.s, TargetOffset = 42 }, CompiledBranch { Offset = 33, OpCode = brfalse.s, TargetOffset = 42 }]
      Match indices: 1, 2
    (offset 7 = the DrawUnattachedSceneParticles block's own guard, correctly
    excluded since it precedes that call's offset; offsets 24 and 33 are the
    conjoined gate's two brfalse's, both inside the widened window — exactly
    the failure this pin exists to catch). Restored the single-flag gate;
    rebuilt; the new pin and all 13 sibling tests in
    RetailPViewPassExecutorTests pass again (14/14).
  - Mutation M1 (the existing round-2 regression: restore the pre-fix gate
    `if (clipAssembly.OutsideViewSlices.Length != 0)`), built, ran the full
    suite — the L1 pin
    (DrawLandscapeDynamicsPhase_GatesDrawWeatherOnceOnWalkDriverWeatherTurnFired)
    still FAILS, as required, with:
      Assert.Equal() Failure: Values differ
      Expected: typeof(AcDream.App.Rendering.Walk.WalkFrameDriver)
      Actual:   typeof(AcDream.App.Rendering.ClipFrameAssembly)
    (the new H5 pin does NOT fail under M1 — a single-flag gate still
    produces exactly one branch in the widened window, which is correct:
    catching "wrong condition source" is L1's job, catching "an extra
    conjoined condition" is H5's). Restored the correct gate; rebuilt;
    14/14 green again.

H6 grep sweep (extends §10.5's L6 sweep per the plan):
  grep -rnE "ScissorNdcAabb|UseScissorFallback|TerrainClipMode\.Scissor|ndcClipAabb|terrainUploadCount" --include=*.cs --include=*.vert --include=*.frag src tests
  -> empty (every mention, including in this commit's own explanatory
  comments, was rephrased to avoid the literal deleted identifiers).
  The base L6 sweep (SetTerrainClip|BeginDoorwayScissor|BindTerrainClip|
  TerrainBytes|active scissor|TerrainClipMode\.Scissor|NdcScissorRect) is
  also still empty.

Gates: dotnet build tests/AcDream.App.Tests -c Release -> 0 warnings / 0
errors. Hermetic lane -> 6825/6825 passed, 0 failed. InstalledDat lane
(ACDREAM_DAT_DIR set) -> 243 passed / 4 known failures (2x #383 layout
tests, TowerAscentReplayTests's TowerAscent KnownFailure,
WalkTraceConformanceTests.Oh_doorway_still_first_frame_diff #458
KnownFailure) / 1 skipped / 248 total — one fewer test than before H3, as
expected from Issue130DoorwayStripTests's deletion.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-03 18:14:38 +02:00
parent b3ef4027a2
commit 99d9c6d564
11 changed files with 209 additions and 640 deletions

View file

@ -47,9 +47,8 @@ public class ClipPlaneSetTests
var fromPolygon = ClipPlaneSet.From(polygon);
Assert.Equal(fromView.Count, fromPolygon.Count);
Assert.Equal(fromView.UseScissorFallback, fromPolygon.UseScissorFallback);
Assert.Equal(fromView.IsPlaneOverflow, fromPolygon.IsPlaneOverflow);
Assert.Equal(fromView.IsNothingVisible, fromPolygon.IsNothingVisible);
Assert.Equal(fromView.ScissorNdcAabb, fromPolygon.ScissorNdcAabb);
Assert.Equal(fromView.Planes, fromPolygon.Planes);
}
@ -75,14 +74,14 @@ public class ClipPlaneSetTests
}
[Fact]
public void From_NineEdgePolygon_FallsBackToScissor()
public void From_NineEdgePolygon_FallsBackToPlaneOverflow()
{
var poly = RegularNgonCellView(n: 9, radius: 0.6f);
var cps = ClipPlaneSet.From(poly);
Assert.True(cps.UseScissorFallback || cps.Count <= 8);
if (cps.UseScissorFallback)
Assert.True(cps.IsPlaneOverflow || cps.Count <= 8);
if (cps.IsPlaneOverflow)
{
Assert.Equal(0, cps.Count); // AABB carries the gate
Assert.Equal(0, cps.Count); // draws unclipped, no plane gate
}
}
@ -95,7 +94,7 @@ public class ClipPlaneSetTests
// --- Multi-polygon safety (the under-inclusion guard) ---------------------
[Fact]
public void From_MultiplePolygons_FallsBackToUnionScissor_NeverEmitsOnePolygonsPlanes()
public void From_MultiplePolygons_FallsBackToPlaneOverflow_NeverEmitsOnePolygonsPlanes()
{
// Two disjoint squares: a CONVEX plane set can never represent their union.
var cv = new CellView();
@ -111,29 +110,19 @@ public class ClipPlaneSetTests
var cps = ClipPlaneSet.From(cv);
// MUST NOT emit a single polygon's convex planes (that would hide the other).
Assert.True(cps.UseScissorFallback);
Assert.True(cps.IsPlaneOverflow);
Assert.Equal(0, cps.Count);
Assert.Empty(cps.Planes);
// Scissor AABB is the UNION of both polygons (over-include, safe).
Assert.Equal(-0.8f, cps.ScissorNdcAabb.X, 5); // minX
Assert.Equal(-0.8f, cps.ScissorNdcAabb.Y, 5); // minY
Assert.Equal(0.8f, cps.ScissorNdcAabb.Z, 5); // maxX
Assert.Equal(0.8f, cps.ScissorNdcAabb.W, 5); // maxY
// The far corner of the second square is inside the union AABB → not hidden.
Assert.True(0.7f >= cps.ScissorNdcAabb.X && 0.7f <= cps.ScissorNdcAabb.Z);
Assert.True(0.7f >= cps.ScissorNdcAabb.Y && 0.7f <= cps.ScissorNdcAabb.W);
}
// --- Distinguishing the three Count==0 states ----------------------------
// --- Distinguishing the two Count==0 states -------------------------------
[Fact]
public void Empty_IsNothingVisible_NotScissorFallback()
public void Empty_IsNothingVisible_NotPlaneOverflow()
{
var e = ClipPlaneSet.From(new CellView());
Assert.Equal(0, e.Count);
Assert.False(e.UseScissorFallback); // NOT "draw the AABB box"
Assert.False(e.IsPlaneOverflow); // NOT "draws unclipped"
Assert.True(e.IsNothingVisible); // "draw nothing"
Assert.Empty(e.Planes);
}
@ -143,23 +132,17 @@ public class ClipPlaneSetTests
{
var e = ClipPlaneSet.Empty;
Assert.Equal(0, e.Count);
Assert.False(e.UseScissorFallback);
Assert.False(e.IsPlaneOverflow);
Assert.True(e.IsNothingVisible);
// Degenerate AABB (min > max) so a consumer that naively scissors on it draws nothing.
Assert.True(e.ScissorNdcAabb.X > e.ScissorNdcAabb.Z);
Assert.True(e.ScissorNdcAabb.Y > e.ScissorNdcAabb.W);
}
[Fact]
public void ScissorFallback_IsNotNothingVisible()
public void PlaneOverflow_IsNotNothingVisible()
{
var poly = RegularNgonCellView(n: 9, radius: 0.6f);
var cps = ClipPlaneSet.From(poly);
Assert.True(cps.UseScissorFallback);
Assert.False(cps.IsNothingVisible); // "draw the AABB box", not "draw nothing"
// The fallback AABB bounds the 9-gon (radius 0.6).
Assert.True(cps.ScissorNdcAabb.Z - cps.ScissorNdcAabb.X > 0.5f);
Assert.True(cps.ScissorNdcAabb.W - cps.ScissorNdcAabb.Y > 0.5f);
Assert.True(cps.IsPlaneOverflow);
Assert.False(cps.IsNothingVisible); // "draws unclipped", not "draw nothing"
}
// --- Plane-sign correctness for every edge -------------------------------
@ -242,13 +225,13 @@ public class ClipPlaneSetTests
Assert.Equal(4, cps.Count); // the redundant collinear edge is merged away
}
// --- Exactly 8 edges fit (octagon); 9 spills to scissor (after merge fails to help).
// --- Exactly 8 edges fit (octagon); 9 spills to plane overflow (after merge fails to help).
[Fact]
public void From_RegularOctagon_FitsInEightPlanes()
{
var cps = ClipPlaneSet.From(RegularNgonCellView(n: 8, radius: 0.6f));
Assert.False(cps.UseScissorFallback);
Assert.False(cps.IsPlaneOverflow);
Assert.Equal(8, cps.Count);
var center = new Vector4(0, 0, 0, 1);
foreach (var p in cps.Planes)

View file

@ -1,326 +0,0 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using AcDream.App.Rendering;
using DatReaderWriter;
using DatReaderWriter.Options;
using Xunit;
using Xunit.Abstractions;
namespace AcDream.App.Tests.Rendering;
/// <summary>
/// #130 — background-color strip along the TOP outer edge of a doorway when
/// looking out from inside. Mechanism model (2026-06-12 evidence sweep): for
/// an interior root the SEAL stamps the FULL raw dat portal polygon at true
/// depth (PortalDepthMaskRenderer, root-cell slice = full screen), while
/// terrain/sky COLOR used to be gated per fragment by the OutsideView region —
/// the same dat polygon run through ProjectToClip → ClipToRegion (1-px
/// MergeSubPixelVertices) → ClipPlaneSet.From (0.5° collinear merge) → planes,
/// with a Floor/Ceil pixel scissor (the sky's own doorway scissor bracket)
/// on the slice AABB on top. Every one of those passes could only SHRINK the gate, so any
/// shave showed as a strip of clear color between the gate's top edge and the
/// aperture's rasterized top edge.
///
/// RETIRED MECHANISM (S3 chunk 4 fix round 2, L7): the scissor half of this
/// story no longer applies. Retail draws the sky and the landscape unclipped
/// (<c>LScape::draw</c> never installs a view before either — S3 chunk 3/4's
/// own findings), and acdream now matches that: the sky's own doorway
/// scissor bracket, <c>IWorldPassSurface.BeginScissor/EndScissor</c>, and
/// every per-slice terrain/sky/weather clip they used to bracket are deleted
/// outright. An
/// interior root's aperture exactness comes instead from the depth clear, the
/// exit seals, and the interior repaint (<c>WalkFrameDriver</c>'s interior
/// turn draws the real cell geometry back over whatever the unclipped
/// landscape painted through the doorway) — see the S3 chunk 4 plan section
/// for the retail citations.
///
/// What remains below still pins something production reads: the outside-view
/// polygon pipeline (<c>ProjectToClip</c> → <c>ClipToRegion</c> →
/// <c>ClipPlaneSet.From</c>) that the KEPT punch-fan clip still consumes
/// (<c>RetailPViewPassExecutor.DrawWalkPunchFan</c> reads
/// <c>clipAssembly.OutsideViewSlices[activeViewIndex].Planes</c> — see
/// <c>ClipFrameLayoutTests</c>'s punch-fan equivalence pin for the synthetic-
/// view version of this same proof). This harness measures the PLANE gap
/// headlessly at the real Holtburg corner building exit door (A9B4 0x0170,
/// the HouseExitWalkReplay door): project the aperture, run the production
/// flood + assembler, then walk sample points just inside the aperture's top
/// edge downward until the plane gate admits them.
///
/// VERDICT (2026-06-12, 147 eye/gaze combos, plane half only — the original
/// sweep also measured a scissor gap, since retired): the CPU polygon
/// pipeline is sub-pixel exact (worst 0.54 px) — the W=0 clip port 987313a
/// and both merge passes are EXONERATED. PIN 2 below still asserts that
/// bound.
/// </summary>
[Trait("Lane", "InstalledDat")]
public class Issue130DoorwayStripTests
{
private readonly ITestOutputHelper _out;
public Issue130DoorwayStripTests(ITestOutputHelper output) => _out = output;
private const uint ExitCellId = CornerFloodReplayTests.Landblock | 0x0170u;
// Production projection convention (CornerFloodReplayTests.ViewProjFor):
// FovY 1.2 rad, 1280x720 viewport, near 1, far 5000. The flood clip is
// near-independent so near/far exactness is not load-bearing.
private static Matrix4x4 ViewProjFor(Vector3 eye, Vector3 lookAt)
{
var view = Matrix4x4.CreateLookAt(eye, lookAt, Vector3.UnitZ);
var proj = Matrix4x4.CreatePerspectiveFieldOfView(1.2f, 1280f / 720f, 1f, 5000f);
return view * proj;
}
[Fact]
public void ExitDoorTopEdge_GateCoversTheDrawnApertureWithinPixelTolerance()
{
var datDir = CornerFloodReplayTests.ResolveDatDir();
if (datDir is null) { _out.WriteLine("SKIP: dats unavailable"); Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory; see docs/release-gate.md."); }
using var dats = new DatCollection(datDir, DatAccessType.Read);
var cells = CornerFloodReplayTests.LoadBuilding(dats);
var root = cells[ExitCellId];
LoadedCell? Lookup(uint id) => cells.TryGetValue(id, out var c) ? c : null;
// Find the exit portal (OtherCellId == 0xFFFF) and its world polygon.
int exitIdx = -1;
for (int i = 0; i < root.Portals.Count; i++)
{
if (root.Portals[i].OtherCellId == 0xFFFF && i < root.PortalPolygons.Count
&& root.PortalPolygons[i].Length >= 3)
{ exitIdx = i; break; }
}
Assert.True(exitIdx >= 0, "0x0170 has no exit portal polygon");
var localPoly = root.PortalPolygons[exitIdx];
// Campaign FW3.3: ShellDrawLiftZ is retired — shells, seal fans, and
// the gate all live in the ONE dat space, so the drawn aperture IS
// the physics polygon. This test survives as the coverage proof
// (gate covers the drawn hole within tolerance across the sweep);
// the historical lifted-vs-unlifted strip test's premise (two
// spaces) no longer exists and that test is deleted.
var worldPoly = new Vector3[localPoly.Length];
for (int i = 0; i < localPoly.Length; i++)
worldPoly[i] = Vector3.Transform(localPoly[i], root.WorldTransform);
Vector3 centroid = Vector3.Zero;
foreach (var w in worldPoly) centroid += w;
centroid /= worldPoly.Length;
// Inward direction: the portal plane normal signed toward the cell
// interior (ClipPlanes carries InsideSide from the load).
var plane = root.ClipPlanes[exitIdx];
var worldNormal = Vector3.TransformNormal(plane.Normal, root.WorldTransform);
var cellCenterWorld = Vector3.Transform(
(root.LocalBoundsMin + root.LocalBoundsMax) * 0.5f, root.WorldTransform);
if (Vector3.Dot(worldNormal, cellCenterWorld - centroid) < 0)
worldNormal = -worldNormal;
worldNormal = Vector3.Normalize(worldNormal);
_out.WriteLine(FormattableString.Invariant(
$"exit portal idx={exitIdx} verts={localPoly.Length} centroid=({centroid.X:F2},{centroid.Y:F2},{centroid.Z:F2}) inward=({worldNormal.X:F2},{worldNormal.Y:F2},{worldNormal.Z:F2})"));
for (int i = 0; i < worldPoly.Length; i++)
_out.WriteLine(FormattableString.Invariant(
$" poly[{i}] world=({worldPoly[i].X:F3},{worldPoly[i].Y:F3},{worldPoly[i].Z:F3})"));
float worstPlaneGapPx = 0f;
string worstDesc = "(none)";
// Eye sweep: back off the doorway along the inward normal at several
// distances/heights/lateral offsets; gaze at the centroid plus raised /
// lowered targets (NDC alignment of the top edge varies with gaze).
var lateral = Vector3.Normalize(Vector3.Cross(worldNormal, Vector3.UnitZ));
float[] dists = { 0.6f, 1.0f, 1.6f, 2.4f, 3.5f };
float[] heights = { 0.9f, 1.4f, 1.7f };
float[] laterals = { -0.8f, 0f, 0.8f };
float[] gazeRaise = { -0.4f, 0f, 0.4f, 0.9f };
int evaluated = 0;
foreach (float d in dists)
foreach (float h in heights)
foreach (float lat in laterals)
foreach (float gz in gazeRaise)
{
var eye = centroid + worldNormal * d + lateral * lat;
eye.Z = centroid.Z - 1.0f + h; // door centroid sits mid-opening; bias to floor-ish
var look = centroid + new Vector3(0, 0, gz);
var viewProj = ViewProjFor(eye, look);
// Aperture truth: the seal's footprint = the raw polygon's projection.
var clip = new Vector4[worldPoly.Length];
float minW = float.MaxValue;
for (int i = 0; i < worldPoly.Length; i++)
{
clip[i] = Vector4.Transform(new Vector4(worldPoly[i], 1f), viewProj);
minW = MathF.Min(minW, clip[i].W);
}
if (minW <= 0.05f) continue; // eye in/behind the door plane — out of #130's scenario
var aperture = new Vector2[clip.Length];
for (int i = 0; i < clip.Length; i++)
aperture[i] = new Vector2(clip[i].X / clip[i].W, clip[i].Y / clip[i].W);
var pv = PortalVisibilityBuilder.Build(root, eye, Lookup, viewProj,
buildingMembership: null);
var asm = ClipFrameAssembler.Assemble(ClipFrame.NoClip(), pv);
if (asm.OutsideViewSlices.Length == 0)
{
_out.WriteLine(FormattableString.Invariant(
$"d={d} h={h} lat={lat} gz={gz}: NO outside slice (outPolys={pv.OutsideView.Polygons.Count})"));
continue;
}
evaluated++;
(float planeGapPx, float atX) =
MeasureTopEdgeGap(aperture, asm.OutsideViewSlices, 1080);
if (planeGapPx > worstPlaneGapPx)
{
worstDesc = FormattableString.Invariant(
$"d={d} h={h} lat={lat} gz={gz} minW={minW:F2} atX={atX:F3} slices={asm.OutsideViewSlices.Length} outVerts={DescribePolys(pv.OutsideView)} apVerts={aperture.Length}");
worstPlaneGapPx = planeGapPx;
}
if (planeGapPx > 0.55f)
{
_out.WriteLine(FormattableString.Invariant(
$"GAP d={d} h={h} lat={lat} gz={gz}: planeGap={planeGapPx:F2}px atX={atX:F3} outVerts={DescribePolys(pv.OutsideView)}"));
float apTop = TopBoundaryY(aperture, atX);
foreach (var slice in asm.OutsideViewSlices)
_out.WriteLine(FormattableString.Invariant(
$" slice slot={slice.Slot} planes={slice.Planes.Length} aabb=({slice.NdcAabb.X:F4},{slice.NdcAabb.Y:F4},{slice.NdcAabb.Z:F4},{slice.NdcAabb.W:F4}) apTopAtX={apTop:F4}"));
foreach (var poly in pv.OutsideView.Polygons)
{
var sb = new System.Text.StringBuilder(" outPoly:");
foreach (var v in poly.Vertices)
sb.Append(FormattableString.Invariant($" ({v.X:F4},{v.Y:F4})"));
_out.WriteLine(sb.ToString());
}
}
}
_out.WriteLine(FormattableString.Invariant(
$"evaluated={evaluated} worstPlaneGapPx={worstPlaneGapPx:F2} @ {worstDesc}"));
Assert.True(evaluated > 100, $"sweep degenerated: only {evaluated} eye/gaze combos evaluated");
// PIN (canary): the CPU polygon pipeline (ProjectToClip → ClipToRegion
// merges → ClipPlaneSet planes) stays sub-pixel exact against the raw
// aperture projection. Observed 0.54 px worst (2026-06-12); the
// production vertex-merge floor is ~1 px — beyond 1.2 px means a new
// under-inclusion shaver entered the pipeline.
Assert.True(worstPlaneGapPx <= 1.2f, FormattableString.Invariant(
$"plane gate under-covers the aperture top edge by {worstPlaneGapPx:F2}px @ {worstDesc}"));
}
private static string DescribePolys(CellView view)
{
var parts = new List<string>();
foreach (var p in view.Polygons) parts.Add(p.Vertices.Length.ToString());
return $"[{string.Join(",", parts)}]";
}
/// <summary>
/// For sample x positions across the aperture's projected top edge, find the
/// aperture boundary's top y, then walk downward until the plane gate
/// admits the point. Returns the worst gap in 1080p pixels, and the x of
/// the worst gap. S3 chunk 4 fix round 2 (L7): the scissor-gap half this
/// used to measure alongside the plane gap is deleted — the scissor
/// mechanism it modeled no longer exists in production.
/// </summary>
private static (float planeGapPx, float atX) MeasureTopEdgeGap(
Vector2[] aperture, ClipViewSlice[] slices, int fbH,
ITestOutputHelper? debug = null)
{
const float Inset = 1e-4f; // dodge exact-boundary ambiguity
const float StepY = 0.0002f; // ~0.1 px at 1080p
const float CapY = 0.02f; // stop searching beyond ~10 px
float minX = float.MaxValue, maxX = float.MinValue;
foreach (var v in aperture) { minX = MathF.Min(minX, v.X); maxX = MathF.Max(maxX, v.X); }
float span = maxX - minX;
if (span <= 0.01f) return (0, 0);
float worstPlane = 0, atX = 0;
const int Samples = 160;
for (int s = 0; s <= Samples; s++)
{
float x = minX + span * (0.01f + 0.98f * s / Samples);
if (MathF.Abs(x) > 0.98f) continue; // off screen — no pixel exists there
float topY = TopBoundaryY(aperture, x);
if (float.IsNaN(topY) || MathF.Abs(topY) > 0.98f) continue; // off screen / no boundary
var p = new Vector2(x, topY - Inset);
float planeGap = GapBelow(p, q => AnySliceAdmitsPlanes(slices, q), StepY, CapY);
if (debug is not null && planeGap > 0.005f)
debug.WriteLine(FormattableString.Invariant(
$" sample x={x:F4} apTop={topY:F4} planeGap={planeGap * fbH / 2f:F2}px"));
if (planeGap > worstPlane) { worstPlane = planeGap; atX = x; }
}
// NDC y → pixels at the given framebuffer height.
return (worstPlane * fbH / 2f, atX);
}
private static float GapBelow(Vector2 start, Func<Vector2, bool> admitted, float step, float cap)
{
if (admitted(start)) return 0f;
for (float dy = step; dy <= cap; dy += step)
{
if (admitted(new Vector2(start.X, start.Y - dy)))
return dy;
}
return cap;
}
// Production semantics: each OutsideView polygon is one slice; the union of
// slices is drawn. A slice with planes gates per fragment via
// gl_ClipDistance (dot((nx,ny,0,d),(x,y,z,1)) >= 0 for an NDC point);
// a planeless slice (>8-edge zero-plane fallback, ClipFrameAssembler.cs) admits its whole NDC AABB.
private static bool AnySliceAdmitsPlanes(ClipViewSlice[] slices, Vector2 p)
{
foreach (var slice in slices)
{
if (slice.Planes.Length == 0)
{
if (p.X >= slice.NdcAabb.X && p.Y >= slice.NdcAabb.Y
&& p.X <= slice.NdcAabb.Z && p.Y <= slice.NdcAabb.W)
return true;
continue;
}
bool inside = true;
foreach (var pl in slice.Planes)
{
if (pl.X * p.X + pl.Y * p.Y + pl.W < 0f) { inside = false; break; }
}
if (inside) return true;
}
return false;
}
/// <summary>Highest boundary y of the polygon at vertical line x (NaN when
/// the line misses the polygon).</summary>
private static float TopBoundaryY(Vector2[] poly, float x)
{
float best = float.NaN;
for (int i = 0; i < poly.Length; i++)
{
var a = poly[i];
var b = poly[(i + 1) % poly.Length];
if (MathF.Abs(a.X - b.X) < 1e-9f)
{
if (MathF.Abs(a.X - x) < 1e-6f)
{
float hi = MathF.Max(a.Y, b.Y);
if (float.IsNaN(best) || hi > best) best = hi;
}
continue;
}
float t = (x - a.X) / (b.X - a.X);
if (t < 0f || t > 1f) continue;
float y = a.Y + t * (b.Y - a.Y);
if (float.IsNaN(best) || y > best) best = y;
}
return best;
}
}

View file

@ -159,7 +159,9 @@ public sealed class RetailPViewPassExecutorTests
/// <c>ClearClipRouting</c> + the old <c>DrawLandscapeSliceLate</c> leaf,
/// one call per active landscape view) is deleted — <c>DrawLandscapeDynamicsPhase</c> now calls
/// <see cref="RetailPViewPassExecutor.DrawWeatherOnce"/> exactly once,
/// unconditionally, with no loop of any kind around it. This
/// conditional on <see cref="AcDream.App.Rendering.Walk.WalkFrameDriver.WeatherTurnFired"/>
/// (see <see cref="DrawLandscapeDynamicsPhase_GatesDrawWeatherOnceOnWalkDriverWeatherTurnFired"/>
/// — the L1 pin), with no loop of any kind around it. This
/// <c>Assert.Single</c> alone proved insufficient at fix round 1 (K1):
/// it counts DISTINCT call-site offsets, so it stays green even with a
/// <c>foreach</c> wrapped around the one call site (the exact round-1
@ -201,7 +203,14 @@ public sealed class RetailPViewPassExecutorTests
/// the compiled <c>foreach</c> emits a backward branch (the
/// condition-check jump back to the loop body) whose span now contains
/// the call's offset, so this test fails; restore the single
/// unconditional call to make it pass again.
/// unconditional call to make it pass again. Note for reviewers (added
/// at S3 landing hygiene, H4, matching <see cref="DrawWalkSky_RenderSkyCallSiteHasNoEnclosingBackwardBranch"/>'s
/// own note): <see cref="CompiledCallGraph.ReadBranches"/> reads only
/// <c>br</c>/<c>brtrue</c>/<c>brfalse</c>-family single-target branches —
/// it does not decode a compiled <c>switch</c> jump table, but no C# loop
/// construct (<c>for</c>/<c>foreach</c>/<c>while</c>/<c>do</c>) ever
/// compiles to one, so this pin's blind spot is not a loop shape it
/// could miss.
/// </summary>
[Fact]
public void DrawLandscapeDynamicsPhase_DrawWeatherOnceCallSiteHasNoEnclosingBackwardBranch()
@ -283,6 +292,65 @@ public sealed class RetailPViewPassExecutorTests
&& branch.TargetOffset > drawOffset);
}
/// <summary>
/// S3 landing hygiene (H5): strengthens the L1 pin above against a
/// conjoined gate the post-hoc three-lens review found it does not
/// reject. L1's check (b) only looks at branches STRICTLY BETWEEN the
/// <c>WeatherTurnFired</c> getter call and the <c>DrawWeatherOnce</c>
/// call — so <c>if (clipAssembly.OutsideViewSlices.Length != 0 &amp;&amp;
/// walkDriver.WeatherTurnFired) passes.DrawWeatherOnce(ctx);</c> still
/// passes it: the compiler evaluates <c>OutsideViewSlices.Length != 0</c>
/// FIRST, so ITS OWN <c>brfalse</c> lands BEFORE the getter call's
/// offset — outside L1's window — while <c>calls[c-1]</c> is still the
/// getter (the added condition reads <c>OutsideViewSlices</c>, a
/// property getter, then <c>.Length</c>, a non-call <c>ldlen</c>, so no
/// OTHER call intervenes before the getter). This pin widens the window
/// to start at the call immediately before the whole gate —
/// <see cref="RetailPViewPassExecutor.DrawUnattachedSceneParticles"/>,
/// the outdoor-emitters call the method's own comment names as the last
/// call before the gate — and counts EVERY branch in that wider window
/// with <c>Assert.Single</c>: production has exactly one, the forward
/// <c>brfalse</c>/<c>brfalse.s</c> right after the getter. A conjoined
/// gate's extra, earlier condition adds a second branch this wider
/// window catches but L1's narrower one cannot.
/// MUTATION: change the gate to <c>if
/// (clipAssembly.OutsideViewSlices.Length != 0 &amp;&amp;
/// walkDriver.WeatherTurnFired)</c> — the branch count in the window
/// goes from 1 to 2 and <c>Assert.Single</c> fails (see the commit body
/// for the exact recorded failure text).
/// </summary>
[Fact]
public void DrawLandscapeDynamicsPhase_ExactlyOneBranchGuardsDrawWeatherOnce()
{
MethodInfo method = typeof(RetailPViewRenderer).GetMethod(
"DrawLandscapeDynamicsPhase",
BindingFlags.Instance | BindingFlags.NonPublic)!;
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(method);
int particlesIndex = RequiredCallIndex(
calls,
typeof(RetailPViewPassExecutor),
nameof(RetailPViewPassExecutor.DrawUnattachedSceneParticles));
int drawIndex = RequiredCallIndex(
calls,
typeof(RetailPViewPassExecutor),
nameof(RetailPViewPassExecutor.DrawWeatherOnce));
int particlesOffset = calls[particlesIndex].Offset;
int drawOffset = calls[drawIndex].Offset;
IReadOnlyList<CompiledBranch> branches = CompiledCallGraph.ReadBranches(method);
CompiledBranch onlyGuard = Assert.Single(
branches,
branch => branch.Offset > particlesOffset && branch.Offset < drawOffset);
Assert.True(
onlyGuard.OpCode == OpCodes.Brfalse || onlyGuard.OpCode == OpCodes.Brfalse_S,
$"Expected the sole branch between DrawUnattachedSceneParticles and "
+ $"DrawWeatherOnce to be a brfalse, was {onlyGuard.OpCode}.");
Assert.True(
onlyGuard.TargetOffset > drawOffset,
"Expected the guard branch to skip forward past DrawWeatherOnce.");
}
/// <summary>
/// S3 chunk 4 fix round 2 (L8): the identical loop-shape question K1
/// asked of <see cref="RetailPViewPassExecutor.DrawWeatherOnce"/>'s call

View file

@ -3,6 +3,19 @@ using AcDream.App.Rendering;
namespace AcDream.App.Tests.Rendering;
/// <summary>
/// S3 landing hygiene (H2): <see cref="TerrainModernRenderer.CollectVisibleCells"/>
/// is frustum-only now — its clip-plane-list and NDC-AABB scissor
/// parameters and the CPU/GPU clip-region equivalence check they fed had
/// exactly one production caller (<see cref="TerrainModernRenderer.Draw"/>),
/// and that caller never actually passed either (grep). The doorway-clip/scissor
/// rejection cases this file used to pin (<c>RejectsCellsOutsideDoorwayClipPlanes</c>,
/// <c>RejectsCellsOutsideDoorwayScissorAabb</c>, <c>UnionsCellsFromEveryLandscapeSlice</c>)
/// pinned a mechanism nothing in production ever fed — deleted with the
/// parameters. What remains still pins the one thing this method's callers
/// actually rely on: it publishes the visible terrain cell set per landblock,
/// respecting an explicit frustum when one is given.
/// </summary>
public sealed class TerrainParticleCellVisibilityTests
{
[Fact]
@ -16,9 +29,7 @@ public sealed class TerrainParticleCellVisibilityTests
Vector3.Zero,
zMin: 0f,
zMax: 20f,
frustum: null,
Matrix4x4.Identity,
ReadOnlySpan<Vector4>.Empty);
frustum: null);
Assert.Equal(64, cells.Count);
Assert.Contains(0xA9B40001u, cells);
@ -40,80 +51,8 @@ public sealed class TerrainParticleCellVisibilityTests
new Vector3(100f, 100f, 100f),
zMin: 100f,
zMax: 120f,
frustum,
Matrix4x4.Identity,
ReadOnlySpan<Vector4>.Empty);
frustum);
Assert.Empty(cells);
}
[Fact]
public void CollectVisibleCells_RejectsCellsOutsideDoorwayClipPlanes()
{
var cells = new HashSet<uint>();
var planes = new[] { new Vector4(-1f, 0f, 0f, -2f) };
TerrainModernRenderer.CollectVisibleCells(
cells,
0xA9B4FFFFu,
Vector3.Zero,
zMin: 0f,
zMax: 20f,
frustum: null,
Matrix4x4.Identity,
planes);
Assert.Empty(cells);
}
[Fact]
public void CollectVisibleCells_RejectsCellsOutsideDoorwayScissorAabb()
{
var cells = new HashSet<uint>();
TerrainModernRenderer.CollectVisibleCells(
cells,
0xA9B4FFFFu,
Vector3.Zero,
zMin: 0f,
zMax: 20f,
frustum: null,
Matrix4x4.Identity,
ReadOnlySpan<Vector4>.Empty,
ndcClipAabb: new Vector4(-4f, -4f, -2f, -2f));
Assert.Empty(cells);
}
[Fact]
public void CollectVisibleCells_UnionsCellsFromEveryLandscapeSlice()
{
var cells = new HashSet<uint>();
TerrainModernRenderer.CollectVisibleCells(
cells,
0xA9B4FFFFu,
Vector3.Zero,
zMin: 0f,
zMax: 20f,
frustum: null,
Matrix4x4.Identity,
ReadOnlySpan<Vector4>.Empty,
ndcClipAabb: new Vector4(1f, -1_000f, 23f, 1_000f));
TerrainModernRenderer.CollectVisibleCells(
cells,
0xA9B4FFFFu,
Vector3.Zero,
zMin: 0f,
zMax: 20f,
frustum: null,
Matrix4x4.Identity,
ReadOnlySpan<Vector4>.Empty,
ndcClipAabb: new Vector4(49f, -1_000f, 71f, 1_000f));
Assert.Equal(16, cells.Count);
Assert.Contains(0xA9B40001u, cells);
Assert.Contains(0xA9B40011u, cells);
Assert.DoesNotContain(0xA9B40009u, cells);
}
}