diff --git a/src/AcDream.App/Rendering/PortalVisibilityBuilder.cs b/src/AcDream.App/Rendering/PortalVisibilityBuilder.cs index e421e3a4..52e5c364 100644 --- a/src/AcDream.App/Rendering/PortalVisibilityBuilder.cs +++ b/src/AcDream.App/Rendering/PortalVisibilityBuilder.cs @@ -112,6 +112,12 @@ public static class PortalVisibilityBuilder var popCounts = new Dictionary(); // per-cell pop count for the MaxReprocessPerCell cap var trace = PortalBuildTrace.Start(cameraCell, cameraPos); + // [portal-churn] apparatus (2026-06-08): when ProbePortalChurnEnabled, accumulate re-enqueue churn + // + reciprocal pre/post region counts, emitted as one summary line at end of Build. Inert when off. + bool churnProbe = AcDream.Core.Rendering.RenderingDiagnostics.ProbePortalChurnEnabled; + int churnReenqueues = 0; + var churnReciprocal = churnProbe ? new System.Text.StringBuilder(256) : null; + bool pvDump = false; if (s_pvDump) { @@ -295,6 +301,9 @@ public static class PortalVisibilityBuilder var preReciprocalClip = eyeInsideOpening ? CloneViewPolygons(clippedRegion) : null; int preReciprocalCount = clippedRegion.Count; ApplyReciprocalClip(clippedRegion, portal.OtherPortalId, neighbour, viewProj); + if (churnProbe) + churnReciprocal!.Append(System.FormattableString.Invariant( + $" recip[0x{neighbourId:X8} {preReciprocalCount}->{clippedRegion.Count}]")); if (clippedRegion.Count == 0) { if (preReciprocalClip is null) @@ -324,6 +333,7 @@ public static class PortalVisibilityBuilder dist = NearestPortalVertexDistance(poly, cell.WorldTransform, cameraPos); todo.Insert(neighbour, dist); inserted = true; + if (churnProbe) churnReenqueues++; } trace?.Add($"portal cell=0x{cell.CellId:X8} p{i}->0x{neighbourId:X8} addCell polys={clippedRegion.Count} clipVerts={clipVerts} recip={preReciprocalCount}->{clippedRegion.Count} grew={grew} queued={inserted} dist={(float.IsNaN(dist) ? "na" : dist.ToString("F2"))}"); } @@ -338,6 +348,18 @@ public static class PortalVisibilityBuilder EmitFlapProbe(cameraCell, cameraPos, viewProj, frame); trace?.Emit(frame); + if (churnProbe) + { + int maxPop = 0; uint maxCell = 0; int rePopped = 0; + foreach (var kv in popCounts) + { + if (kv.Value > maxPop) { maxPop = kv.Value; maxCell = kv.Key; } + if (kv.Value > 1) rePopped++; + } + Console.WriteLine(System.FormattableString.Invariant( + $"[portal-churn] root=0x{cameraCell.CellId:X8} cells={frame.OrderedVisibleCells.Count} reEnqueues={churnReenqueues} rePoppedCells={rePopped} maxPop=0x{maxCell:X8}:{maxPop}") + churnReciprocal); + } + return frame; }