The FW3 visual gate's stairwell/grass transition flash (grass briefly covering floor openings at doorway crossings - the #119 family) was the FW3 dual path leaking: the walk decided WHETHER terrain draws while the old PortalVisibilityBuilder assembly decided WHERE (slice planes, count, scissor), and punch fans indexed the old slice array with walk view indices. The new ACDREAM_PROBE_WALK_ROOT apparatus pinned the boundary frames: fat/degenerate old-apparatus exit views splash terrain over interior pixels, the interior depth-clear preserves color, and cells absent from the walk's flood never repaint. Retail has ONE visibility structure and cannot produce this. ClipFrameAssembler.ReassembleOutsideViewFromWalk now materializes the walk's own outside_view (pixel screen points -> standard NDC -> the existing ClipPlaneSet machinery) into the assembly's outside-view block after Collect, ahead of the single PrepareClipFrame publication (moved below the walk block). The Landscape event carries the walk's active view count on the record's existing OutsideViewCount field (trace mapping compares kind only - zero oracle-fixture churn) and the driver fans exactly that many terrain slices; activeTerrainSliceCount is deleted end to end. Outdoor roots keep the assembler's single full-screen slice, asserted ==1. Hermetic 6,762/0 (4 new materializer tests pin the y-flip and plane-sign conventions), Walk lane 209/1, InstalledDat walk conformance 40/1. Seals/cell slices/look-in seeding stay on the old per-cell views for the rest of FW4 (identical dat polygons; only the visible set can differ). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
147 lines
5.6 KiB
C#
147 lines
5.6 KiB
C#
using System.Numerics;
|
||
using AcDream.App.Rendering;
|
||
using AcDream.App.Rendering.Walk;
|
||
using Xunit;
|
||
|
||
namespace AcDream.App.Tests.Rendering;
|
||
|
||
/// <summary>
|
||
/// Campaign FW4 slice 1 — <see cref="ClipFrameAssembler.ReassembleOutsideViewFromWalk"/>:
|
||
/// an interior root's outside-view slices come from the walk's OWN
|
||
/// <c>outside_view</c> (pixel screen points, origin top-left, +Y down),
|
||
/// converted to the assembler's standard-NDC <see cref="ViewPolygon"/>s.
|
||
/// </summary>
|
||
public class WalkOutsideViewReassemblyTests
|
||
{
|
||
private const float W = 1024f, H = 720f;
|
||
|
||
private sealed class StubRays : IWalkRayCaster
|
||
{
|
||
public Vector3 RayThrough(float screenX, float screenY) =>
|
||
Vector3.Normalize(new Vector3(screenX - W / 2f, screenY - H / 2f, 1000f));
|
||
}
|
||
|
||
private static WalkPortalView WalkViewOfPixelQuads(params Vector2[][] pixelQuads)
|
||
{
|
||
var view = new WalkPortalView();
|
||
var rays = new StubRays();
|
||
foreach (Vector2[] quad in pixelQuads)
|
||
{
|
||
var pts = new WalkScreenPoint[quad.Length];
|
||
for (int i = 0; i < quad.Length; i++)
|
||
pts[i] = new WalkScreenPoint(quad[i].X, quad[i].Y, 0f, 1f);
|
||
Assert.True(WalkCopyView.Append(view, pts, rays, Vector3.Zero));
|
||
}
|
||
return view;
|
||
}
|
||
|
||
private static ClipFrameAssembly AssembledWithOldOutside()
|
||
{
|
||
var pv = new PortalVisibilityFrame();
|
||
pv.OutsideView.Add(new ViewPolygon(new[]
|
||
{
|
||
new Vector2(-0.9f, -0.9f), new Vector2(0.9f, -0.9f),
|
||
new Vector2(0.9f, 0.9f), new Vector2(-0.9f, 0.9f),
|
||
}));
|
||
return ClipFrameAssembler.Assemble(ClipFrame.NoClip(), pv);
|
||
}
|
||
|
||
[Fact]
|
||
public void PixelDoorway_MapsToExpectedNdcAabbAndPlanes()
|
||
{
|
||
ClipFrameAssembly asm = AssembledWithOldOutside();
|
||
// Pixel quad x∈[256,512], y∈[180,360] → NDC x∈[-0.5,0], y∈[0,0.5]
|
||
// (yNdc = 1 − 2·py/H flips the axis: py=180 → +0.5, py=360 → 0).
|
||
WalkPortalView walkView = WalkViewOfPixelQuads(new[]
|
||
{
|
||
new Vector2(256f, 180f), new Vector2(512f, 180f),
|
||
new Vector2(512f, 360f), new Vector2(256f, 360f),
|
||
});
|
||
|
||
ClipFrameAssembler.ReassembleOutsideViewFromWalk(asm, walkView, W, H);
|
||
|
||
ClipViewSlice slice = Assert.Single(asm.OutsideViewSlices);
|
||
Assert.Equal(TerrainClipMode.Planes, asm.TerrainMode);
|
||
Assert.True(asm.OutdoorVisible);
|
||
Assert.Equal(4, asm.OutsidePlaneCount);
|
||
Assert.Equal(slice.Slot, asm.OutdoorSlot);
|
||
Assert.NotEqual(0, slice.Slot);
|
||
Assert.Equal(-0.5f, slice.NdcAabb.X, 3);
|
||
Assert.Equal(0f, slice.NdcAabb.Y, 3);
|
||
Assert.Equal(0f, slice.NdcAabb.Z, 3);
|
||
Assert.Equal(0.5f, slice.NdcAabb.W, 3);
|
||
Assert.Equal(slice.NdcAabb, asm.OutsideViewNdcAabb);
|
||
|
||
// Every corner of the doorway satisfies every inward plane
|
||
// (n·p + d >= 0), and a point far outside fails at least one.
|
||
Vector2[] ndcCorners =
|
||
{
|
||
new(-0.5f, 0f), new(0f, 0f), new(0f, 0.5f), new(-0.5f, 0.5f),
|
||
};
|
||
foreach (Vector2 corner in ndcCorners)
|
||
{
|
||
foreach (Vector4 plane in slice.Planes)
|
||
Assert.True(plane.X * corner.X + plane.Y * corner.Y + plane.W >= -1e-4f);
|
||
}
|
||
bool outsideFails = false;
|
||
foreach (Vector4 plane in slice.Planes)
|
||
outsideFails |= plane.X * 0.9f + plane.Y * -0.9f + plane.W < 0f;
|
||
Assert.True(outsideFails);
|
||
}
|
||
|
||
[Fact]
|
||
public void FullViewportWalkQuad_CoversFullNdc()
|
||
{
|
||
ClipFrameAssembly asm = AssembledWithOldOutside();
|
||
var walkView = new WalkPortalView();
|
||
Assert.True(WalkCopyView.AppendFullViewportQuad(
|
||
walkView, new StubRays(), Vector3.Zero, W, H));
|
||
|
||
ClipFrameAssembler.ReassembleOutsideViewFromWalk(asm, walkView, W, H);
|
||
|
||
ClipViewSlice slice = Assert.Single(asm.OutsideViewSlices);
|
||
Assert.Equal(new Vector4(-1f, -1f, 1f, 1f), slice.NdcAabb);
|
||
Assert.Equal(TerrainClipMode.Planes, asm.TerrainMode);
|
||
}
|
||
|
||
[Fact]
|
||
public void EmptyWalkView_YieldsSkipMode()
|
||
{
|
||
ClipFrameAssembly asm = AssembledWithOldOutside();
|
||
Assert.True(asm.OutdoorVisible); // the old view was visible pre-cutover
|
||
|
||
ClipFrameAssembler.ReassembleOutsideViewFromWalk(asm, new WalkPortalView(), W, H);
|
||
|
||
Assert.Empty(asm.OutsideViewSlices);
|
||
Assert.False(asm.OutdoorVisible);
|
||
Assert.False(asm.HasOutsideView);
|
||
Assert.Equal(TerrainClipMode.Skip, asm.TerrainMode);
|
||
Assert.Equal(0, asm.OutsidePlaneCount);
|
||
Assert.Equal(0, asm.OutdoorSlot);
|
||
}
|
||
|
||
[Fact]
|
||
public void TwoWalkViews_ProduceTwoSlicesWithDistinctSlots()
|
||
{
|
||
ClipFrameAssembly asm = AssembledWithOldOutside();
|
||
WalkPortalView walkView = WalkViewOfPixelQuads(
|
||
new[]
|
||
{
|
||
new Vector2(100f, 100f), new Vector2(300f, 100f),
|
||
new Vector2(300f, 300f), new Vector2(100f, 300f),
|
||
},
|
||
new[]
|
||
{
|
||
new Vector2(600f, 400f), new Vector2(900f, 400f),
|
||
new Vector2(900f, 650f), new Vector2(600f, 650f),
|
||
});
|
||
|
||
ClipFrameAssembler.ReassembleOutsideViewFromWalk(asm, walkView, W, H);
|
||
|
||
Assert.Equal(2, asm.OutsideViewSlices.Length);
|
||
Assert.NotEqual(asm.OutsideViewSlices[0].Slot, asm.OutsideViewSlices[1].Slot);
|
||
// The union AABB spans both doorways.
|
||
Assert.True(asm.OutsideViewNdcAabb.X < asm.OutsideViewSlices[0].NdcAabb.Z);
|
||
Assert.True(asm.OutsideViewNdcAabb.Z >= asm.OutsideViewSlices[1].NdcAabb.X);
|
||
}
|
||
}
|