diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs
index 9881d15b..97b22e5f 100644
--- a/src/AcDream.App/Rendering/GameWindow.cs
+++ b/src/AcDream.App/Rendering/GameWindow.cs
@@ -9669,6 +9669,10 @@ public sealed class GameWindow : IDisposable
DisableClipDistances();
}
+ // #119-residual [viewer] probe state: print-on-change signature so a
+ // stable climb is silent and every flood/root flip emits exactly one line.
+ private string? _lastViewerProbeSig;
+
private void EmitRetailPViewDiagnostics(
AcDream.App.Rendering.RetailPViewFrameResult result,
LoadedCell clipRoot,
@@ -9677,6 +9681,21 @@ public sealed class GameWindow : IDisposable
System.Numerics.Vector3 camPos,
System.Numerics.Vector3 playerViewPos)
{
+ // #119-residual: the capture half of the tower capture→replay loop.
+ // One line per change of (root, flood, outPolys, playerCell); the eye
+ // at mm precision rides every line so the harness can replay the
+ // exact production (eye, root) pairs. ACDREAM_PROBE_VIEWER=1.
+ if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeViewerEnabled)
+ {
+ string sig = System.FormattableString.Invariant(
+ $"root=0x{clipRoot.CellId:X8}{(clipRoot.IsOutdoorNode ? "(OUT)" : "")} flood={result.PortalFrame.OrderedVisibleCells.Count} outPolys={result.PortalFrame.OutsideView.Polygons.Count} pCell=0x{playerCellId:X8}");
+ if (sig != _lastViewerProbeSig)
+ {
+ _lastViewerProbeSig = sig;
+ Console.WriteLine(System.FormattableString.Invariant(
+ $"[viewer] {sig} eye=({camPos.X:F3},{camPos.Y:F3},{camPos.Z:F3}) viewerCell=0x{viewerCellId:X8}"));
+ }
+ }
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeVisibilityEnabled)
AcDream.Core.Rendering.RenderingDiagnostics.EmitVis(
clipRoot.CellId,
diff --git a/src/AcDream.Core/Rendering/RenderingDiagnostics.cs b/src/AcDream.Core/Rendering/RenderingDiagnostics.cs
index 7e77496b..49302d9c 100644
--- a/src/AcDream.Core/Rendering/RenderingDiagnostics.cs
+++ b/src/AcDream.Core/Rendering/RenderingDiagnostics.cs
@@ -96,6 +96,19 @@ public static class RenderingDiagnostics
public static bool ProbeVisibilityEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_VIS") == "1";
+ ///
+ /// #119-residual viewer/flood capture (2026-06-11): one [viewer]
+ /// line per CHANGE of (root cell, flood size, OutsideView poly count,
+ /// player cell), with the projection EYE at mm precision on every line —
+ /// the capture half of the tower-ascent capture→replay loop
+ /// (TowerAscentReplayTests replays the captured pairs deterministically).
+ /// Light: silent while the visibility state is stable; a tower climb
+ /// emits a few dozen lines. Initial state from
+ /// ACDREAM_PROBE_VIEWER=1.
+ ///
+ public static bool ProbeViewerEnabled { get; set; } =
+ Environment.GetEnvironmentVariable("ACDREAM_PROBE_VIEWER") == "1";
+
///
/// 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