diag(render): Phase A8.F — portal-frame visual-gate triage apparatus

Env-gated diagnostics (off by default; do not affect the default game):
- ACDREAM_A8_DUMP_PV=1: PortalVisibilityBuilder dumps local→NDC→clipped portal
  geometry + OutsideView poly count for the first 2 Build calls per camera cell.
- ACDREAM_PROBE_ENVCELL=1: [opaque] line dumps the opaque cell-render stats
  (cells/tris) BEFORE the per-cell transparent loop overwrites _envCellRenderer.Stats.
Used to diagnose the A8.F visual-gate failure (see handoff doc). Gated behind
ACDREAM_A8_INDOOR_BRANCH=1 like the rest of the indoor branch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-29 14:40:23 +02:00
parent 452ee5b9a1
commit 7c3ee438bd
2 changed files with 42 additions and 0 deletions

View file

@ -11069,6 +11069,9 @@ public sealed class GameWindow : IDisposable
currentEnvCellIds.Add(id);
gl.Disable(EnableCap.Blend);
_envCellRenderer!.Render(AcDream.App.Rendering.Wb.WbRenderPass.Opaque, currentEnvCellIds);
// TEMP A8.F triage (strip after): opaque wall-render stats BEFORE the transparent loop overwrites them.
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeEnvCellEnabled && _a8OpaqueDumped.Add(cameraCell.CellId))
Console.WriteLine($"[opaque] camCell=0x{cameraCell.CellId:X8} cells={_envCellRenderer.Stats.CellsRendered} tris={_envCellRenderer.Stats.TrianglesDrawn} filterCnt={currentEnvCellIds.Count}");
// Phase A8.F (#2): translucent cell geometry clipped to each cell's portal-chain
// region (stencil BIT 2). Opaque cells already clip correctly via depth (left
@ -11378,6 +11381,9 @@ public sealed class GameWindow : IDisposable
private readonly HashSet<(uint cellId, ulong gfxObjId)> _phaseA8AuditLogged = new();
// TEMP A8.F triage (strip after): one-shot-per-camCell guard for the [opaque] wall-render probe.
private static readonly System.Collections.Generic.HashSet<uint> _a8OpaqueDumped = new();
private void EmitEnvCellProbe(int ourBldgs, int otherBldgs, int filterCnt)
{
if (!AcDream.Core.Rendering.RenderingDiagnostics.ProbeEnvCellEnabled) return;

View file

@ -41,6 +41,12 @@ public static class PortalVisibilityBuilder
private const int MaxReprocessPerCell = 4;
private const float PortalSideEpsilon = 0.01f; // matches CellVisibility.PointInCellEpsilon
// TEMP diagnostic (Phase A8.F visual-gate triage; strip after): ACDREAM_A8_DUMP_PV=1 dumps the
// local→NDC→clipped portal geometry for the first 2 Build calls per distinct camera cell.
private static readonly bool s_pvDump =
Environment.GetEnvironmentVariable("ACDREAM_A8_DUMP_PV") == "1";
private static readonly Dictionary<uint, int> s_pvDumpCount = new();
/// <param name="lookup">Resolve a full cell id to its LoadedCell, or null if not loaded.</param>
/// <param name="buildingMembership">Optional: true if a cell id is in the camera building's cell
/// set. When provided, a neighbour OUTSIDE the set routes to CrossBuildingViews instead of
@ -64,6 +70,18 @@ public static class PortalVisibilityBuilder
var queue = new Queue<LoadedCell>();
queue.Enqueue(cameraCell);
bool pvDump = false;
if (s_pvDump)
{
lock (s_pvDumpCount)
{
s_pvDumpCount.TryGetValue(cameraCell.CellId, out int dc);
if (dc < 2) { s_pvDumpCount[cameraCell.CellId] = dc + 1; pvDump = true; }
}
if (pvDump)
Console.WriteLine($"[pv-dump] camCell=0x{cameraCell.CellId:X8} portals={cameraCell.Portals.Count} polyLists={cameraCell.PortalPolygons.Count} vp[M11={viewProj.M11:F3} M22={viewProj.M22:F3} M33={viewProj.M33:F3} M34={viewProj.M34:F3} M43={viewProj.M43:F3} M44={viewProj.M44:F3}]");
}
while (queue.Count > 0)
{
var cell = queue.Dequeue();
@ -80,15 +98,21 @@ public static class PortalVisibilityBuilder
var poly = cell.PortalPolygons[i];
if (poly == null || poly.Length < 3) continue;
bool dx = pvDump && cell.Portals[i].OtherCellId == 0xFFFF;
// Portal-side test: only traverse a portal the camera is on the interior side of
// (mirrors CellVisibility.GetVisibleCells + retail's 'seen' flag). Culls back-facing
// portals so we never feed a degenerate/wrong-facing projection downstream.
if (i < cell.ClipPlanes.Count && !CameraOnInteriorSide(cell, i, cameraPos))
{
if (dx) Console.WriteLine($"[pv-dump] EXIT-CULLED(side) cell=0x{cell.CellId:X8} p{i} localN={poly.Length} hasClipPlane={(i < cell.ClipPlanes.Count)}");
continue;
}
// Project to NDC, then normalize to CCW for the CCW-only ScreenPolygonClip
// (ProjectToNdc preserves input winding; portal dat polygons may be CW).
Vector2[] portalNdc = PortalProjection.ProjectToNdc(poly, cell.WorldTransform, viewProj);
if (dx) Console.WriteLine($"[pv-dump] EXIT-PROJ cell=0x{cell.CellId:X8} p{i} localN={poly.Length} ndcN={portalNdc.Length} local0=({poly[0].X:F2},{poly[0].Y:F2},{poly[0].Z:F2}) ndc=[{string.Join(" ", System.Array.ConvertAll(portalNdc, v => $"({v.X:F2},{v.Y:F2})"))}]");
if (portalNdc.Length < 3) continue;
EnsureCcw(portalNdc);
@ -99,12 +123,21 @@ public static class PortalVisibilityBuilder
var clipped = ScreenPolygonClip.Intersect(portalNdc, vp.Vertices);
if (clipped.Length >= 3) clippedRegion.Add(new ViewPolygon(clipped));
}
if (dx) Console.WriteLine($"[pv-dump] EXIT-CLIP cell=0x{cell.CellId:X8} p{i} currentViewPolys={currentView.Polygons.Count} clipResult={clippedRegion.Count}");
if (clippedRegion.Count == 0) continue; // portal not visible through this chain
var portal = cell.Portals[i];
if (portal.OtherCellId == 0xFFFF)
{
if (pvDump)
{
Console.WriteLine($"[pv-dump] EXIT cell=0x{cell.CellId:X8} p{i} localN={poly.Length} ndcN={portalNdc.Length} clipPolys={clippedRegion.Count}");
Console.WriteLine($"[pv-dump] local=[{string.Join(" ", System.Array.ConvertAll(poly, v => $"({v.X:F2},{v.Y:F2},{v.Z:F2})"))}]");
Console.WriteLine($"[pv-dump] ndc=[{string.Join(" ", System.Array.ConvertAll(portalNdc, v => $"({v.X:F3},{v.Y:F3})"))}]");
foreach (var cp in clippedRegion)
Console.WriteLine($"[pv-dump] clipped({cp.Vertices.Length})=[{string.Join(" ", System.Array.ConvertAll((Vector2[])cp.Vertices, v => $"({v.X:F3},{v.Y:F3})"))}]");
}
// Exit portal -> outdoors visible through this (clipped) opening.
foreach (var cp in clippedRegion) frame.OutsideView.Add(cp);
continue;
@ -135,6 +168,9 @@ public static class PortalVisibilityBuilder
}
}
if (pvDump)
Console.WriteLine($"[pv-dump] OUTSIDEVIEW polys={frame.OutsideView.Polygons.Count} bfsCellViews={frame.CellViews.Count} crossBldg={frame.CrossBuildingViews.Count}");
return frame;
}