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:
parent
1955a0d5f1
commit
05240d2cab
12 changed files with 55 additions and 196 deletions
|
|
@ -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>();
|
||||
|
|
|
|||
|
|
@ -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}"));
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue