diag(render): Phase U.4c — ACDREAM_PROBE_FLAP per-frame convergence probe
Per-frame (not cell-change-throttled, so it catches the flicker at a stable root): [flap] line from the builder — root cell's per-portal side-test D + traverse/cull + NDC projection, plus OutsideView poly count + visible-cell count; localEye exposes when the eye has crossed an interior portal plane. Paired [flap-cam] line from the draw site — FindCameraCell resolution branch (CameraCellResolution enum, new), eyeInRoot AABB flag (stale-root signal), eye + player worldpos, and the frame's TerrainMode/OutdoorVisible outcome. Disambiguates side-cull vs empty-projection vs stale-root. Inert when off (gated). Throwaway apparatus to converge the flap fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8941d1e6e5
commit
1d47ede007
4 changed files with 118 additions and 0 deletions
|
|
@ -142,6 +142,25 @@ public struct PortalClipPlane
|
||||||
public int InsideSide;
|
public int InsideSide;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Phase U.4c flap probe (diagnostic): which branch of
|
||||||
|
/// <see cref="CellVisibility.FindCameraCell"/> resolved the camera cell.
|
||||||
|
/// </summary>
|
||||||
|
public enum CameraCellResolution
|
||||||
|
{
|
||||||
|
/// <summary>No cell contains the eye (outdoors), or not yet resolved.</summary>
|
||||||
|
None,
|
||||||
|
/// <summary>The eye is inside the previously-cached cell (fast path).</summary>
|
||||||
|
Cache,
|
||||||
|
/// <summary>The eye is inside a one-hop portal neighbour of the cached cell.</summary>
|
||||||
|
Neighbour,
|
||||||
|
/// <summary>The eye is inside a cell found by the full brute-force scan.</summary>
|
||||||
|
BruteForce,
|
||||||
|
/// <summary>The eye is inside NO cell, but the previous cell is kept alive for a
|
||||||
|
/// few grace frames — the "stale root" case the flap probe watches for.</summary>
|
||||||
|
Grace,
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Result of a portal-based visibility BFS from the camera cell.
|
/// Result of a portal-based visibility BFS from the camera cell.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -213,6 +232,14 @@ public sealed class CellVisibility
|
||||||
/// <summary>The last visibility result produced by <see cref="ComputeVisibility"/>.</summary>
|
/// <summary>The last visibility result produced by <see cref="ComputeVisibility"/>.</summary>
|
||||||
public VisibilityResult? LastVisibilityResult { get; private set; }
|
public VisibilityResult? LastVisibilityResult { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Phase U.4c flap probe (diagnostic): which <see cref="FindCameraCell"/> branch
|
||||||
|
/// resolved the camera cell on the most recent call. A <see cref="CameraCellResolution.Grace"/>
|
||||||
|
/// (or <see cref="CameraCellResolution.Cache"/>) result while the eye is NOT actually inside the
|
||||||
|
/// returned cell is the "stale root" signature the flap probe looks for.
|
||||||
|
/// </summary>
|
||||||
|
public CameraCellResolution LastCameraCellResolution { get; private set; } = CameraCellResolution.None;
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
// Registration
|
// Registration
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|
@ -330,7 +357,10 @@ public sealed class CellVisibility
|
||||||
{
|
{
|
||||||
// 1. Fast path: cached cell.
|
// 1. Fast path: cached cell.
|
||||||
if (_lastCameraCell != null && PointInCell(cameraPos, _lastCameraCell))
|
if (_lastCameraCell != null && PointInCell(cameraPos, _lastCameraCell))
|
||||||
|
{
|
||||||
|
LastCameraCellResolution = CameraCellResolution.Cache;
|
||||||
return _lastCameraCell;
|
return _lastCameraCell;
|
||||||
|
}
|
||||||
|
|
||||||
// 2. One-hop neighbours of the cached cell.
|
// 2. One-hop neighbours of the cached cell.
|
||||||
if (_lastCameraCell != null)
|
if (_lastCameraCell != null)
|
||||||
|
|
@ -347,6 +377,7 @@ public sealed class CellVisibility
|
||||||
{
|
{
|
||||||
_lastCameraCell = neighbour;
|
_lastCameraCell = neighbour;
|
||||||
_cellSwitchGraceFrames = CellSwitchGraceFrameCount;
|
_cellSwitchGraceFrames = CellSwitchGraceFrameCount;
|
||||||
|
LastCameraCellResolution = CameraCellResolution.Neighbour;
|
||||||
return neighbour;
|
return neighbour;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -361,6 +392,7 @@ public sealed class CellVisibility
|
||||||
{
|
{
|
||||||
_lastCameraCell = cell;
|
_lastCameraCell = cell;
|
||||||
_cellSwitchGraceFrames = CellSwitchGraceFrameCount;
|
_cellSwitchGraceFrames = CellSwitchGraceFrameCount;
|
||||||
|
LastCameraCellResolution = CameraCellResolution.BruteForce;
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -370,11 +402,13 @@ public sealed class CellVisibility
|
||||||
if (_lastCameraCell != null && _cellSwitchGraceFrames > 0)
|
if (_lastCameraCell != null && _cellSwitchGraceFrames > 0)
|
||||||
{
|
{
|
||||||
_cellSwitchGraceFrames--;
|
_cellSwitchGraceFrames--;
|
||||||
|
LastCameraCellResolution = CameraCellResolution.Grace;
|
||||||
return _lastCameraCell;
|
return _lastCameraCell;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Camera is outside all cells.
|
// 5. Camera is outside all cells.
|
||||||
_lastCameraCell = null;
|
_lastCameraCell = null;
|
||||||
|
LastCameraCellResolution = CameraCellResolution.None;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7315,6 +7315,22 @@ public sealed class GameWindow : IDisposable
|
||||||
clipAssembly.OutsidePlaneCount,
|
clipAssembly.OutsidePlaneCount,
|
||||||
clipAssembly.PerCellPlaneCounts,
|
clipAssembly.PerCellPlaneCounts,
|
||||||
clipAssembly.ScissorFallbacks);
|
clipAssembly.ScissorFallbacks);
|
||||||
|
|
||||||
|
// Phase U.4c flap probe (ACDREAM_PROBE_FLAP) — paired with the builder's
|
||||||
|
// per-frame [flap] line. res = which FindCameraCell branch chose the root;
|
||||||
|
// eyeInRoot = is the EYE actually inside clipRoot's AABB (n ⇒ stale root via
|
||||||
|
// cache/grace, the leading flap hypothesis); terrain/outVisible = the frame's
|
||||||
|
// outcome (Skip/false ⇒ terrain+shells flapped off this frame).
|
||||||
|
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeFlapEnabled)
|
||||||
|
{
|
||||||
|
var flapPlayer = _playerController?.Position ?? camPos;
|
||||||
|
bool eyeInRoot = CellVisibility.PointInCell(camPos, clipRoot);
|
||||||
|
Console.WriteLine(
|
||||||
|
$"[flap-cam] root=0x{clipRoot.CellId:X8} res={_cellVisibility.LastCameraCellResolution} " +
|
||||||
|
$"eyeInRoot={(eyeInRoot ? "Y" : "n")} eye=({camPos.X:F2},{camPos.Y:F2},{camPos.Z:F2}) " +
|
||||||
|
$"player=({flapPlayer.X:F2},{flapPlayer.Y:F2},{flapPlayer.Z:F2}) " +
|
||||||
|
$"terrain={clipAssembly.TerrainMode} outVisible={clipAssembly.OutdoorVisible}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -230,9 +230,59 @@ public static class PortalVisibilityBuilder
|
||||||
if (pvDump)
|
if (pvDump)
|
||||||
Console.WriteLine($"[pv-dump] OUTSIDEVIEW polys={frame.OutsideView.Polygons.Count} bfsCellViews={frame.CellViews.Count} crossBldg={frame.CrossBuildingViews.Count}");
|
Console.WriteLine($"[pv-dump] OUTSIDEVIEW polys={frame.OutsideView.Polygons.Count} bfsCellViews={frame.CellViews.Count} crossBldg={frame.CrossBuildingViews.Count}");
|
||||||
|
|
||||||
|
// Phase U.4c flap probe (ACDREAM_PROBE_FLAP) — read-only per-frame snapshot of the
|
||||||
|
// root cell's per-portal side-test + projection + the frame's exit/visible counts.
|
||||||
|
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeFlapEnabled)
|
||||||
|
EmitFlapProbe(cameraCell, cameraPos, viewProj, frame);
|
||||||
|
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase U.4c flap probe. One [flap] line per Build: the root cell's per-portal
|
||||||
|
// signed distance D (eye→portal plane), traverse/cull decision, and NDC projection
|
||||||
|
// vertex count, plus the frame's OutsideView polygon count + visible-cell count.
|
||||||
|
// `localEye` is the eye in root-local space — its component along an interior portal
|
||||||
|
// plane reveals when the eye has crossed past that plane (the stale-root region that
|
||||||
|
// makes the side test cull a still-needed portal). Read-only recompute; no effect on
|
||||||
|
// the returned frame. Throwaway apparatus — strip with the probe.
|
||||||
|
private static void EmitFlapProbe(
|
||||||
|
LoadedCell cameraCell, Vector3 cameraPos, Matrix4x4 viewProj, PortalVisibilityFrame frame)
|
||||||
|
{
|
||||||
|
var localEye = Vector3.Transform(cameraPos, cameraCell.InverseWorldTransform);
|
||||||
|
var sb = new System.Text.StringBuilder(220);
|
||||||
|
sb.Append("[flap] root=0x").Append(cameraCell.CellId.ToString("X8"));
|
||||||
|
sb.Append(" eye=(").Append(cameraPos.X.ToString("F2")).Append(',')
|
||||||
|
.Append(cameraPos.Y.ToString("F2")).Append(',').Append(cameraPos.Z.ToString("F2")).Append(')');
|
||||||
|
sb.Append(" localEye=(").Append(localEye.X.ToString("F2")).Append(',')
|
||||||
|
.Append(localEye.Y.ToString("F2")).Append(',').Append(localEye.Z.ToString("F2")).Append(')');
|
||||||
|
for (int i = 0; i < cameraCell.Portals.Count; i++)
|
||||||
|
{
|
||||||
|
var portal = cameraCell.Portals[i];
|
||||||
|
float d = float.NaN;
|
||||||
|
bool side = true;
|
||||||
|
if (i < cameraCell.ClipPlanes.Count && cameraCell.ClipPlanes[i].Normal.LengthSquared() >= 1e-8f)
|
||||||
|
{
|
||||||
|
var pl = cameraCell.ClipPlanes[i];
|
||||||
|
d = Vector3.Dot(pl.Normal, localEye) + pl.D;
|
||||||
|
side = CameraOnInteriorSide(cameraCell, i, cameraPos);
|
||||||
|
}
|
||||||
|
int projN = -1;
|
||||||
|
if (i < cameraCell.PortalPolygons.Count)
|
||||||
|
{
|
||||||
|
var poly = cameraCell.PortalPolygons[i];
|
||||||
|
if (poly != null && poly.Length >= 3)
|
||||||
|
projN = PortalProjection.ProjectToNdc(poly, cameraCell.WorldTransform, viewProj).Length;
|
||||||
|
}
|
||||||
|
sb.Append(" | p").Append(i).Append("->0x").Append(portal.OtherCellId.ToString("X4"));
|
||||||
|
sb.Append(" D=").Append(float.IsNaN(d) ? "na" : d.ToString("F2"));
|
||||||
|
sb.Append(side ? " TRV" : " CULL");
|
||||||
|
sb.Append(" proj=").Append(projN);
|
||||||
|
}
|
||||||
|
sb.Append(" || outPolys=").Append(frame.OutsideView.Polygons.Count);
|
||||||
|
sb.Append(" vis=").Append(frame.OrderedVisibleCells.Count);
|
||||||
|
Console.WriteLine(sb.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
// Mirrors CellVisibility's portal-side test (InsideSide convention).
|
// Mirrors CellVisibility's portal-side test (InsideSide convention).
|
||||||
private static bool CameraOnInteriorSide(LoadedCell cell, int portalIndex, Vector3 cameraPos)
|
private static bool CameraOnInteriorSide(LoadedCell cell, int portalIndex, Vector3 cameraPos)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,24 @@ public static class RenderingDiagnostics
|
||||||
public static bool ProbeVisibilityEnabled { get; set; } =
|
public static bool ProbeVisibilityEnabled { get; set; } =
|
||||||
Environment.GetEnvironmentVariable("ACDREAM_PROBE_VIS") == "1";
|
Environment.GetEnvironmentVariable("ACDREAM_PROBE_VIS") == "1";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Phase U.4c (2026-05-31) flap-convergence probe. When true, the portal
|
||||||
|
/// visibility pass emits, EVERY frame the camera root is an indoor cell, a
|
||||||
|
/// <c>[flap]</c> line (root cell's per-portal side-test D + traverse/cull +
|
||||||
|
/// projection, plus the frame's OutsideView/visible counts) and the call site
|
||||||
|
/// emits a paired <c>[flap-cam]</c> line (FindCameraCell resolution reason,
|
||||||
|
/// camera EYE worldpos, player worldpos, eye-in-root-AABB flag). Unlike the
|
||||||
|
/// cell-change-throttled <see cref="ProbeVisibilityEnabled"/> probe, this fires
|
||||||
|
/// per-frame so it captures the flicker (the exit cell dropping in/out at a
|
||||||
|
/// STABLE root). Pinpoints WHY the exit cell drops: side-test cull (eye past an
|
||||||
|
/// interior portal plane), empty projection, or a stale root (eye outside the
|
||||||
|
/// cell while FindCameraCell still reports it via cache/grace). Throwaway
|
||||||
|
/// apparatus — strip once the flap mechanism is confirmed.
|
||||||
|
/// Initial state from <c>ACDREAM_PROBE_FLAP=1</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static bool ProbeFlapEnabled { get; set; } =
|
||||||
|
Environment.GetEnvironmentVariable("ACDREAM_PROBE_FLAP") == "1";
|
||||||
|
|
||||||
// Cell-change gate for EmitVis. The probe fires once per distinct root cell
|
// Cell-change gate for EmitVis. The probe fires once per distinct root cell
|
||||||
// so launch.log stays readable under motion (the per-frame call is a no-op
|
// so launch.log stays readable under motion (the per-frame call is a no-op
|
||||||
// when the root is unchanged). Sentinel 0 = "no root yet" — the first real
|
// when the root is unchanged). Sentinel 0 = "no root yet" — the first real
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue