feat(render) Campaign FW3.3: ShellDrawLiftZ is RETIRED - cells draw at the dat origin

Retail draws cell geometry at the dat EnvCell origin verbatim; the
0.02 m lift was our z-fight stand-in (register row AP-32, deleted in
this commit). With the walk owning retail draw ORDER under
WorldDepthContract Less (first-drawn-wins: DrawBlock terrain-then-
objects per cell, DrawCells shells-then-contents), the coplanar
tie-breaks the lift compensated for are now resolved the way retail
resolves them.

Deleted at every site: the PortalVisibilityBuilder const + the
drawLiftZ Build parameter and its lifted exit-portal projection branch
(gate and drawn geometry now share ONE space); the seal/punch fan
lifts (DrawPortalDepthWrite + the walk's DrawWalkPunchFan); the
LandblockBuildFactory drawn-cell-transform lift (render and physics
share the one verbatim transform).

The #130 proof flipped exactly as its own doc predicted:
UnliftedGate_LeavesTheStripAtTheDrawnTopEdge is deleted (its premise -
gate space != drawn space - no longer exists), and the renamed
ExitDoorTopEdge_GateCoversTheDrawnApertureWithinPixelTolerance sweep
(147 eye/gaze combos at the Holtburg corner door) passes with both in
the same unlifted space (worst plane gap <= 1.2 px, scissor <= 0.15 px
- unchanged tolerances). Ten more replay-test call sites swept to the
new Build signature.

Suites: full Release build 0 warnings; hermetic 6,750/0; the 21
affected InstalledDat replay tests green; Walk conformance 40/1
untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-30 16:08:52 +02:00
parent 1955a0d5f1
commit 05240d2cab
12 changed files with 55 additions and 196 deletions

View file

@ -57,7 +57,7 @@ public class Issue130DoorwayStripTests
}
[Fact]
public void ExitDoorTopEdge_GateCoversLiftedApertureWithinPixelTolerance()
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."); }
@ -78,16 +78,15 @@ public class Issue130DoorwayStripTests
Assert.True(exitIdx >= 0, "0x0170 has no exit portal polygon");
var localPoly = root.PortalPolygons[exitIdx];
// DRAWN space: the shell that rasterizes the aperture (and the seal fan)
// draws +ShellDrawLiftZ above the physics transform — the gate must be
// compared against the drawn hole, not the physics polygon (#130: the
// unlifted gate left a 2 cm background strip under the drawn lintel).
// 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);
worldPoly[i].Z += PortalVisibilityBuilder.ShellDrawLiftZ;
}
Vector3 centroid = Vector3.Zero;
foreach (var w in worldPoly) centroid += w;
@ -146,7 +145,7 @@ public class Issue130DoorwayStripTests
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, drawLiftZ: PortalVisibilityBuilder.ShellDrawLiftZ);
buildingMembership: null);
var asm = ClipFrameAssembler.Assemble(ClipFrame.NoClip(), pv);
if (asm.OutsideViewSlices.Length == 0)
{
@ -203,103 +202,6 @@ public class Issue130DoorwayStripTests
$"plane gate under-covers the aperture top edge by {worstPlaneGapPx:F2}px @ {worstDesc}"));
}
/// <summary>Sensitivity proof + regression documentation: a gate built in
/// PHYSICS space (drawLiftZ 0) against the DRAWN (lifted) aperture shows a
/// multi-pixel strip at a close doorway — the user-visible #130 strip
/// (f35cb8b split the lift out of the visibility transform; the OutsideView
/// kept gating drawn color in unlifted space). If this stops failing-by-gap,
/// the lift is gone and the production drawLiftZ plumbing can go too.</summary>
[Fact]
public void UnliftedGate_LeavesTheStripAtTheDrawnTopEdge()
{
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;
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);
var localPoly = root.PortalPolygons[exitIdx];
var worldPoly = new Vector3[localPoly.Length];
Vector3 centroid = Vector3.Zero;
for (int i = 0; i < localPoly.Length; i++)
{
worldPoly[i] = Vector3.Transform(localPoly[i], root.WorldTransform);
worldPoly[i].Z += PortalVisibilityBuilder.ShellDrawLiftZ; // drawn space
centroid += worldPoly[i];
}
centroid /= worldPoly.Length;
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);
// d=2.4 m, eye low (0.9 m above the opening's base), gaze at the
// centroid — the main sweep's clean case, where the aperture top edge
// projects ON SCREEN (y≈0.79; a closer/higher eye pushes the lintel
// past the screen top and the seam becomes unmeasurable).
var eye = centroid + worldNormal * 2.4f;
eye.Z = centroid.Z - 1.0f + 0.9f;
var viewProj = ViewProjFor(eye, centroid);
var clip = new Vector4[worldPoly.Length];
for (int i = 0; i < worldPoly.Length; i++)
clip[i] = Vector4.Transform(new Vector4(worldPoly[i], 1f), viewProj);
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 pvUnlifted = PortalVisibilityBuilder.Build(root, eye, Lookup, viewProj); // drawLiftZ 0
var asmUnlifted = ClipFrameAssembler.Assemble(ClipFrame.NoClip(), pvUnlifted);
Assert.True(asmUnlifted.OutsideViewSlices.Length > 0);
(float unliftedGapPx, _, _) = MeasureTopEdgeGap(aperture, asmUnlifted.OutsideViewSlices, 1920, 1080);
var pvLifted = PortalVisibilityBuilder.Build(root, eye, Lookup, viewProj,
buildingMembership: null, drawLiftZ: PortalVisibilityBuilder.ShellDrawLiftZ);
var asmLifted = ClipFrameAssembler.Assemble(ClipFrame.NoClip(), pvLifted);
Assert.True(asmLifted.OutsideViewSlices.Length > 0);
(float liftedGapPx, _, _) = MeasureTopEdgeGap(aperture, asmLifted.OutsideViewSlices, 1920, 1080);
_out.WriteLine(FormattableString.Invariant(
$"top-edge gap vs the DRAWN aperture at d=2.4 m: unliftedGate={unliftedGapPx:F2}px liftedGate={liftedGapPx:F2}px"));
var dbg = new System.Text.StringBuilder(" aperture(LIFTED):");
foreach (var v in aperture) dbg.Append(FormattableString.Invariant($" ({v.X:F4},{v.Y:F4})"));
_out.WriteLine(dbg.ToString());
foreach (var poly in pvUnlifted.OutsideView.Polygons)
{
var sb = new System.Text.StringBuilder(" unliftedGatePoly:");
foreach (var v in poly.Vertices) sb.Append(FormattableString.Invariant($" ({v.X:F4},{v.Y:F4})"));
_out.WriteLine(sb.ToString());
}
foreach (var poly in pvLifted.OutsideView.Polygons)
{
var sb = new System.Text.StringBuilder(" liftedGatePoly:");
foreach (var v in poly.Vertices) sb.Append(FormattableString.Invariant($" ({v.X:F4},{v.Y:F4})"));
_out.WriteLine(sb.ToString());
}
// The strip the user saw: physics-space gate vs drawn hole, several px.
Assert.True(unliftedGapPx > 2.0f, FormattableString.Invariant(
$"expected the unlifted gate to show the strip (>2px), got {unliftedGapPx:F2}px"));
// The fix: a gate in drawn space covers the drawn hole.
Assert.True(liftedGapPx <= 1.2f, FormattableString.Invariant(
$"lifted gate still under-covers by {liftedGapPx:F2}px"));
}
private static string DescribePolys(CellView view)
{
var parts = new List<string>();

View file

@ -48,8 +48,7 @@ public class Issue131SetupProbeTests
var root = cells[0xA9B40171u];
var pv = AcDream.App.Rendering.PortalVisibilityBuilder.Build(
root, eye, Lookup, viewProj,
buildingMembership: null,
drawLiftZ: AcDream.App.Rendering.PortalVisibilityBuilder.ShellDrawLiftZ);
buildingMembership: null);
_out.WriteLine(FormattableString.Invariant(
$"main flood={pv.OrderedVisibleCells.Count} outPolys={pv.OutsideView.Polygons.Count}"));

View file

@ -19,7 +19,7 @@ namespace AcDream.App.Tests.Rendering;
/// where its geometry was; stair cells failing admission = the pop).
///
/// Production-matched inputs: Build(root, eye, lookup, viewProj,
/// buildingMembership: null, drawLiftZ: ShellDrawLiftZ) — the drawLiftZ
/// buildingMembership: null) — the production call shape (FW3.3 retired drawLiftZ)
/// mirrors RetailPViewRenderer.DrawInside.
///
/// Scenarios:
@ -54,8 +54,7 @@ public class Issue176177FacilityHubFloodReplayTests
Func<uint, LoadedCell?> lookup = id => cells.TryGetValue(id, out var c) ? c : null;
var frame = PortalVisibilityBuilder.Build(
cells[rootId], eye, lookup, ViewProjFor(eye, gazeDir),
buildingMembership: null,
drawLiftZ: PortalVisibilityBuilder.ShellDrawLiftZ);
buildingMembership: null);
var result = new List<uint>(frame.OrderedVisibleCells);
result.Sort();
return result;

View file

@ -207,7 +207,7 @@ public class Issue177StairDescentCameraFloodTests
string admit = "?";
if (viewer != 0u && renderCells.TryGetValue(viewer, out var rc))
{
var f = PortalVisibilityBuilder.Build(rc, eye, lookup, cam.View * cam.Projection, null, PortalVisibilityBuilder.ShellDrawLiftZ);
var f = PortalVisibilityBuilder.Build(rc, eye, lookup, cam.View * cam.Projection, null);
var s = f.OrderedVisibleCells;
admit = string.Concat(stair.Select(low => (s.Contains(FacilityHub | low) ? "1" : "0")));
}
@ -259,7 +259,7 @@ public class Issue177StairDescentCameraFloodTests
for (int i = 0; i < 140; i++) cam.Update(playerPos, yaw, Vector3.Zero, true, Vector3.UnitZ, 1f / 60f, pcell, 0x5000000A);
uint viewer = cam.ViewerCellId;
if (viewer == 0u || !renderCells.TryGetValue(viewer, out var rc)) continue;
var f = PortalVisibilityBuilder.Build(rc, cam.Position, lookup, cam.View * cam.Projection, null, PortalVisibilityBuilder.ShellDrawLiftZ);
var f = PortalVisibilityBuilder.Build(rc, cam.Position, lookup, cam.View * cam.Projection, null);
int c = f.OrderedVisibleCells.Count; sum += c; n++;
if (c > maxCells) { maxCells = c; bestViewer = viewer; bestSet = CellSet(f.OrderedVisibleCells.OrderBy(v => v)); }
}
@ -326,7 +326,7 @@ public class Issue177StairDescentCameraFloodTests
float d = PortalPlaneDistance(root, upIdx, eye);
var frame = PortalVisibilityBuilder.Build(
root, eye, lookup, ViewProj(eye, gaze),
buildingMembership: null, drawLiftZ: PortalVisibilityBuilder.ShellDrawLiftZ);
buildingMembership: null);
var vis = frame.OrderedVisibleCells.OrderBy(v => v).ToList();
bool up = vis.Contains(upperId);
string admit = up ? "ADMITTED" : "DROPPED";
@ -339,8 +339,8 @@ public class Issue177StairDescentCameraFloodTests
// spiral, root 0x01C8, portals off-screen/sliver, camera NOT collision-jammed).
// Kept as a mechanism pin: an eye exactly in the ceiling-portal plane drops the upper
// cell (edge-on → <3 clip), an eye 0.4 m off admits it via the normal clip.
var fEdge = PortalVisibilityBuilder.Build(root, eyeEdgeOn, lookup, ViewProj(eyeEdgeOn, gaze), null, PortalVisibilityBuilder.ShellDrawLiftZ);
var fOff = PortalVisibilityBuilder.Build(root, eyeOffPlane, lookup, ViewProj(eyeOffPlane, gaze), null, PortalVisibilityBuilder.ShellDrawLiftZ);
var fEdge = PortalVisibilityBuilder.Build(root, eyeEdgeOn, lookup, ViewProj(eyeEdgeOn, gaze), null);
var fOff = PortalVisibilityBuilder.Build(root, eyeOffPlane, lookup, ViewProj(eyeOffPlane, gaze), null);
Assert.DoesNotContain(upperId, fEdge.OrderedVisibleCells); // edge-on drops (mechanism)
Assert.Contains(upperId, fOff.OrderedVisibleCells); // off-plane admits (control)
}
@ -438,7 +438,7 @@ public class Issue177StairDescentCameraFloodTests
var vp = cam.View * cam.Projection;
if (viewer != 0u && renderCells.TryGetValue(viewer, out var rootCell))
{
var f = PortalVisibilityBuilder.Build(rootCell, eye, lookup, vp, null, PortalVisibilityBuilder.ShellDrawLiftZ);
var f = PortalVisibilityBuilder.Build(rootCell, eye, lookup, vp, null);
var vis = f.OrderedVisibleCells.OrderBy(v => v).ToList();
string flags = $"0178={(vis.Contains(FacilityHub | 0x0178u) ? "Y" : "-")} 0182={(vis.Contains(FacilityHub | 0x0182u) ? "Y" : "-")} 0183={(vis.Contains(FacilityHub | 0x0183u) ? "Y" : "-")} 0181={(vis.Contains(FacilityHub | 0x0181u) ? "Y" : "-")}";
_out.WriteLine(FormattableString.Invariant(
@ -519,7 +519,7 @@ public class Issue177StairDescentCameraFloodTests
{
var frame = PortalVisibilityBuilder.Build(
rootCell, eye, lookup, viewProj,
buildingMembership: null, drawLiftZ: PortalVisibilityBuilder.ShellDrawLiftZ);
buildingMembership: null);
var vis = frame.OrderedVisibleCells.OrderBy(v => v).ToList();
floodStr = $"{vis.Count,2} [{CellSet(vis)}]";
bool ramp = vis.Contains(FacilityHub | 0x0182u);

View file

@ -46,8 +46,7 @@ public class Issue181VisFlapReplayTests
Func<uint, LoadedCell?> lookup = id => cells.TryGetValue(id, out var c) ? c : null;
var frame = PortalVisibilityBuilder.Build(
cells[rootId], eye, lookup, ViewProjFor(eye, gazeDir),
buildingMembership: null,
drawLiftZ: PortalVisibilityBuilder.ShellDrawLiftZ);
buildingMembership: null);
var result = new List<uint>(frame.OrderedVisibleCells);
result.Sort();
return result;
@ -88,8 +87,7 @@ public class Issue181VisFlapReplayTests
{
var frame = PortalVisibilityBuilder.Build(
cells[LiveRoot], eye, lookup, ViewProjFor(eye, gaze),
buildingMembership: null,
drawLiftZ: PortalVisibilityBuilder.ShellDrawLiftZ);
buildingMembership: null);
if (!frame.CellViews.TryGetValue(flapper, out var view) || view.IsEmpty)
{