fix(render): FW4 slice 1 - interior outside-view slices come from the walk
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>
This commit is contained in:
parent
8f8b0b9c57
commit
37febd1fe6
11 changed files with 468 additions and 44 deletions
|
|
@ -144,6 +144,18 @@ public sealed class ClipFrameAssembly
|
|||
|
||||
internal void SetOutsideViewSlices(ClipViewSlice[] slices) => OutsideViewSlices = slices;
|
||||
|
||||
/// <summary>FW4 slice 1: returns the outside-view slice array to the
|
||||
/// pool ahead of a same-frame reassembly from the walk's own views
|
||||
/// (<see cref="ClipFrameAssembler.ReassembleOutsideViewFromWalk"/>) —
|
||||
/// without this the replaced array would leak from the slice pool for
|
||||
/// the frame.</summary>
|
||||
internal void ReturnOutsideViewSlicesForReassembly()
|
||||
{
|
||||
if (OutsideViewSlices.Length != 0)
|
||||
ReturnSlices(OutsideViewSlices);
|
||||
OutsideViewSlices = System.Array.Empty<ClipViewSlice>();
|
||||
}
|
||||
|
||||
private ClipViewSlice[] RentSlices(int length)
|
||||
{
|
||||
if (_sliceArraysByLength.TryGetValue(length, out Stack<ClipViewSlice[]>? pool)
|
||||
|
|
@ -380,6 +392,143 @@ public static class ClipFrameAssembler
|
|||
return assembly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW4 slice 1 — the interior root's outside-view cutover.
|
||||
/// Replaces the assembly's outside-view block (slices, terrain mode,
|
||||
/// scissor/NDC bounds, plane count) with slices derived from THE WALK'S
|
||||
/// OWN <c>outside_view</c> (<see cref="Walk.RetailFrameWalk.InteriorOutsideView"/>),
|
||||
/// filled by the walk's <c>ConstructView</c> during Collect. Retail has
|
||||
/// exactly ONE visibility structure per frame: <c>LScape::draw</c>'s
|
||||
/// terrain clip, the punch fans' <c>building_view</c> planes, and the
|
||||
/// landscape turn's view count all read the views the walk itself
|
||||
/// installed. Feeding these from the old <c>PortalVisibilityBuilder</c>
|
||||
/// assembly let the two systems desynchronize on camera-transition
|
||||
/// boundary frames — terrain splashed through stale/fat old-apparatus
|
||||
/// exit views over interior pixels the walk's flood never repainted
|
||||
/// (the FW3 visual-gate stairwell/grass flash, probe-pinned
|
||||
/// 2026-08-30), and punch fans indexed the old slice array with walk
|
||||
/// view indices.
|
||||
///
|
||||
/// The walk stores view vertices as PIXEL screen points
|
||||
/// (<c>copy_view</c>'s post-divide viewport coordinates, origin
|
||||
/// top-left, +Y down — <see cref="Walk.WalkScreenClip.TransformToScreen"/>:
|
||||
/// x=(W/2)(x_c+w), y=(H/2)(w−y_c)); inverting that mapping yields the
|
||||
/// standard NDC this assembler's <see cref="ViewPolygon"/>s already use
|
||||
/// (ndcX = 2x/W − 1, ndcY = 1 − 2y/H). Winding is normalized inside
|
||||
/// <see cref="ClipPlaneSet.From(in ViewPolygon)"/>, and the closing
|
||||
/// duplicate vertex <c>copy_view</c> stores is merged there too.
|
||||
///
|
||||
/// Must run AFTER the walk's Collect and BEFORE
|
||||
/// <c>PrepareClipFrame</c> publishes the clip regions — appended slots
|
||||
/// join the same single publication.
|
||||
/// </summary>
|
||||
public static void ReassembleOutsideViewFromWalk(
|
||||
ClipFrameAssembly assembly,
|
||||
Walk.WalkPortalView outsideView,
|
||||
float viewportWidth,
|
||||
float viewportHeight)
|
||||
{
|
||||
System.ArgumentNullException.ThrowIfNull(assembly);
|
||||
System.ArgumentNullException.ThrowIfNull(outsideView);
|
||||
if (viewportWidth <= 0f || viewportHeight <= 0f)
|
||||
{
|
||||
throw new System.ArgumentOutOfRangeException(
|
||||
nameof(viewportWidth),
|
||||
$"viewport {viewportWidth}x{viewportHeight} — the walk projected its "
|
||||
+ "views through a real viewport; a non-positive extent here means the "
|
||||
+ "caller handed a different frame's context (fail-loud rule).");
|
||||
}
|
||||
|
||||
ClipFrame frame = assembly.Frame;
|
||||
int viewCount = outsideView.ViewCount;
|
||||
var polys = outsideView.View.Polys;
|
||||
var pool = outsideView.View.Vertices;
|
||||
if (polys.Count < viewCount)
|
||||
{
|
||||
throw new System.InvalidOperationException(
|
||||
$"walk outside_view holds {polys.Count} polys for ViewCount={viewCount} — "
|
||||
+ "the view set's append bookkeeping desynchronized (fail-loud rule).");
|
||||
}
|
||||
|
||||
assembly.ReturnOutsideViewSlicesForReassembly();
|
||||
|
||||
List<ClipViewSlice> outsideSlicesList = assembly.SliceScratch;
|
||||
outsideSlicesList.Clear();
|
||||
int outsideMaxPlaneCount = 0;
|
||||
bool outsideHasScissorFallback = false;
|
||||
int scissorFallbacks = assembly.ScissorFallbacks;
|
||||
float unionMinX = float.MaxValue, unionMinY = float.MaxValue;
|
||||
float unionMaxX = float.MinValue, unionMaxY = float.MinValue;
|
||||
|
||||
for (int v = 0; v < viewCount; v++)
|
||||
{
|
||||
Walk.WalkViewPoly walkPoly = polys[v];
|
||||
var vertices = new Vector2[walkPoly.VertexCount];
|
||||
for (int k = 0; k < walkPoly.VertexCount; k++)
|
||||
{
|
||||
Vector2 px = pool[walkPoly.VertexIndex + k].Point;
|
||||
vertices[k] = new Vector2(
|
||||
px.X / viewportWidth * 2f - 1f,
|
||||
1f - px.Y / viewportHeight * 2f);
|
||||
}
|
||||
var poly = new ViewPolygon(vertices);
|
||||
if (!poly.IsEmpty)
|
||||
{
|
||||
if (poly.MinX < unionMinX) unionMinX = poly.MinX;
|
||||
if (poly.MinY < unionMinY) unionMinY = poly.MinY;
|
||||
if (poly.MaxX > unionMaxX) unionMaxX = poly.MaxX;
|
||||
if (poly.MaxY > unionMaxY) unionMaxY = poly.MaxY;
|
||||
}
|
||||
|
||||
var cps = ClipPlaneSet.From(poly);
|
||||
if (cps.IsNothingVisible)
|
||||
continue;
|
||||
|
||||
int slot;
|
||||
Vector4[] planes;
|
||||
if (cps.Count > 0)
|
||||
{
|
||||
planes = cps.PlaneArray;
|
||||
slot = frame.AppendSlot(planes);
|
||||
if (cps.Count > outsideMaxPlaneCount)
|
||||
outsideMaxPlaneCount = cps.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
planes = System.Array.Empty<Vector4>();
|
||||
slot = 0;
|
||||
outsideHasScissorFallback = true;
|
||||
scissorFallbacks++;
|
||||
}
|
||||
|
||||
outsideSlicesList.Add(new ClipViewSlice(slot, AabbOf(poly), planes));
|
||||
}
|
||||
|
||||
ClipViewSlice[] outsideViewSlices = assembly.CopySlices(outsideSlicesList);
|
||||
bool outdoorVisible = outsideViewSlices.Length > 0;
|
||||
int outdoorSlot = outdoorVisible ? outsideViewSlices[0].Slot : 0;
|
||||
TerrainClipMode terrainMode = !outdoorVisible
|
||||
? TerrainClipMode.Skip
|
||||
: (outsideHasScissorFallback ? TerrainClipMode.Scissor : TerrainClipMode.Planes);
|
||||
|
||||
Vector4 outsideViewNdcAabb = outdoorVisible
|
||||
? new Vector4(unionMinX, unionMinY, unionMaxX, unionMaxY)
|
||||
: Vector4.Zero;
|
||||
Vector4 terrainScissor = terrainMode == TerrainClipMode.Scissor
|
||||
? outsideViewNdcAabb
|
||||
: Vector4.Zero;
|
||||
|
||||
assembly.SetOutsideViewSlices(outsideViewSlices);
|
||||
assembly.OutdoorSlot = outdoorSlot;
|
||||
assembly.OutdoorVisible = outdoorVisible;
|
||||
assembly.TerrainMode = terrainMode;
|
||||
assembly.TerrainScissorNdcAabb = terrainScissor;
|
||||
assembly.HasOutsideView = outdoorVisible;
|
||||
assembly.OutsideViewNdcAabb = outsideViewNdcAabb;
|
||||
assembly.OutsidePlaneCount = terrainMode == TerrainClipMode.Planes ? outsideMaxPlaneCount : 0;
|
||||
assembly.ScissorFallbacks = scissorFallbacks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends the cell views used by nested <c>DrawBuilding -> DrawPortal</c>
|
||||
/// PViews to the already assembled frame. Retail installs each nested
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue