diff --git a/docs/research/2026-08-30-fw-walk-oracle/posed/street-portal-gate-dump.txt b/docs/research/2026-08-30-fw-walk-oracle/posed/street-portal-gate-dump.txt
new file mode 100644
index 00000000..4786eec3
--- /dev/null
+++ b/docs/research/2026-08-30-fw-walk-oracle/posed/street-portal-gate-dump.txt
@@ -0,0 +1,30 @@
+pose cell=a9b40029 origin=<132.77141, 23.892355, 96.33006>
+building a9b4001a: dist=54.7 eff=0.0 eyeLocal=<47.290646, 28.046478, 2.3300629> portals=3
+ level BSP portal refs: 3
+ ref idx=2 other=a9b4016e rawSide=0 exact=True planeD@eye=-43.04 eyeSide=1 clipN=0 gate(side==rawSide)=reject
+ ref idx=1 other=a9b4016d rawSide=0 exact=True planeD@eye=51.54 eyeSide=0 clipN=0 gate(side==rawSide)=PASS
+ ref idx=0 other=a9b4016c rawSide=0 exact=True planeD@eye=33.35 eyeSide=0 clipN=0 gate(side==rawSide)=PASS
+building a9b4001e: dist=120.2 eff=20.2 eyeLocal=<107.64956, 48.678543, 30.330063> portals=10
+ level BSP portal refs: 10
+ ref idx=6 other=a9b40107 rawSide=0 exact=True planeD@eye=115.15 eyeSide=0 clipN=4 gate(side==rawSide)=PASS
+ ref idx=9 other=a9b4010b rawSide=0 exact=True planeD@eye=59.68 eyeSide=0 clipN=4 gate(side==rawSide)=PASS
+ ref idx=4 other=a9b40102 rawSide=0 exact=True planeD@eye=112.65 eyeSide=0 clipN=6 gate(side==rawSide)=PASS
+ ref idx=3 other=a9b40103 rawSide=0 exact=True planeD@eye=-102.65 eyeSide=1 clipN=6 gate(side==rawSide)=reject
+ ref idx=7 other=a9b40109 rawSide=0 exact=True planeD@eye=59.68 eyeSide=0 clipN=4 gate(side==rawSide)=PASS
+ ref idx=8 other=a9b4010a rawSide=0 exact=True planeD@eye=59.68 eyeSide=0 clipN=4 gate(side==rawSide)=PASS
+ ref idx=5 other=a9b40106 rawSide=0 exact=True planeD@eye=116.65 eyeSide=0 clipN=4 gate(side==rawSide)=PASS
+ ref idx=0 other=a9b40100 rawSide=0 exact=True planeD@eye=-25.33 eyeSide=1 clipN=4 gate(side==rawSide)=reject
+ ref idx=1 other=a9b40100 rawSide=0 exact=True planeD@eye=-25.33 eyeSide=1 clipN=4 gate(side==rawSide)=reject
+ ref idx=2 other=a9b40100 rawSide=0 exact=True planeD@eye=-25.33 eyeSide=1 clipN=4 gate(side==rawSide)=reject
+building a9b40022: dist=28.3 eff=0.0 eyeLocal=<12.107626, 25.2714, 2.3300629> portals=3
+ level BSP portal refs: 3
+ ref idx=2 other=a9b4015e rawSide=0 exact=True planeD@eye=17.11 eyeSide=0 clipN=0 gate(side==rawSide)=PASS
+ ref idx=0 other=a9b40164 rawSide=0 exact=True planeD@eye=-14.27 eyeSide=1 clipN=0 gate(side==rawSide)=reject
+ ref idx=1 other=a9b40165 rawSide=0 exact=True planeD@eye=35.27 eyeSide=0 clipN=0 gate(side==rawSide)=PASS
+building a9b40026: dist=117.7 eff=17.7 eyeLocal=<-28.271423, 111.60765, 30.330063> portals=5
+ level BSP portal refs: 5
+ ref idx=1 other=a9b40120 rawSide=1 exact=True planeD@eye=-117.26 eyeSide=1 clipN=4 gate(side==rawSide)=PASS
+ ref idx=2 other=a9b40122 rawSide=0 exact=True planeD@eye=117.26 eyeSide=0 clipN=4 gate(side==rawSide)=PASS
+ ref idx=4 other=a9b40125 rawSide=0 exact=True planeD@eye=117.26 eyeSide=0 clipN=4 gate(side==rawSide)=PASS
+ ref idx=3 other=a9b40124 rawSide=0 exact=True planeD@eye=-22.07 eyeSide=1 clipN=4 gate(side==rawSide)=reject
+ ref idx=0 other=a9b4011f rawSide=0 exact=True planeD@eye=-22.07 eyeSide=1 clipN=4 gate(side==rawSide)=reject
diff --git a/tests/AcDream.App.Tests/Rendering/Walk/WalkPortalGateDumpTests.cs b/tests/AcDream.App.Tests/Rendering/Walk/WalkPortalGateDumpTests.cs
new file mode 100644
index 00000000..2049bfb0
--- /dev/null
+++ b/tests/AcDream.App.Tests/Rendering/Walk/WalkPortalGateDumpTests.cs
@@ -0,0 +1,88 @@
+using AcDream.App.Tests.Rendering;
+using AcDream.App.Rendering.Walk;
+using DatReaderWriter;
+using DatReaderWriter.Options;
+using System.Numerics;
+using System.Text;
+
+namespace AcDream.App.Tests.Rendering.Walk;
+
+///
+/// FW1 look-in gate microscope: for the street fixture pose, dump every
+/// gate input for building a9b4001a (retail punches it) vs a9b4001e
+/// (retail does not): raw portal flags, the BSP-emitted polygon's plane,
+/// the eye's signed distance and side, and the active-view clip count.
+/// Writes the dump to the scratch directory; always passes.
+///
+[Trait("Lane", "InstalledDat")]
+public sealed class WalkPortalGateDumpTests
+{
+ [Fact]
+ public void Dump_the_gate_inputs_for_a_punching_and_a_silent_building()
+ {
+ string? datDir = CornerFloodReplayTests.ResolveDatDir();
+ if (datDir is null)
+ {
+ Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory.");
+ }
+ IReadOnlyList frames =
+ WalkOracleTrace.Load("posed/holtburg-street-outdoor");
+ WalkOracleFrame frame = frames[1];
+ Assert.NotNull(frame.Pose);
+ using var dats = new DatCollection(datDir!, DatAccessType.Read);
+ WalkLandscapeDatBuilder.BuiltWorld world =
+ WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId);
+ var ctx = new WalkTraceReplayContext(frame.Pose, world.Cells)
+ {
+ Buildings = world.Buildings,
+ };
+ // Install the outdoor default view as the active clip context.
+ var defaultView = new WalkPortalView();
+ WalkCopyView.AppendFullViewportQuad(
+ defaultView, ctx.Rays, ctx.WorldViewpoint,
+ ctx.ViewportWidth, ctx.ViewportHeight);
+ ctx.SetActiveView(defaultView, 0);
+
+ var dump = new StringBuilder();
+ Span clipped = stackalloc WalkScreenPoint[64];
+ dump.AppendLine($"pose cell={frame.Pose.CellId:x8} origin={frame.Pose.Origin}");
+ foreach (uint id in new[] { 0xA9B4001Au, 0xA9B4001Eu, 0xA9B40022u, 0xA9B40026u })
+ {
+ WalkBuilding building = world.Buildings.Keys.Single(b => b.PositionCellId == id);
+ float dist = ctx.ViewerDistanceTo(building);
+ Vector3 eyeInBuilding = ctx.ViewpointInBuilding(building);
+ dump.AppendLine(
+ $"building {id:x8}: dist={dist:f1} eff={MathF.Max(0f, dist - 100f):f1} "
+ + $"eyeLocal={eyeInBuilding} portals={building.Portals.Length}");
+ WalkBspNode? bsp = building.SelectDrawingBsp(dist);
+ var refs = new List();
+ Collect(bsp, refs);
+ dump.AppendLine($" level BSP portal refs: {refs.Count}");
+ foreach (WalkPortalRef portalRef in refs)
+ {
+ ref WalkBldPortal bp = ref building.Portals[portalRef.PortalIndex];
+ float d = Vector3.Dot(portalRef.Polygon.Plane.Normal, eyeInBuilding)
+ + portalRef.Polygon.Plane.D;
+ int side = d > WalkVisibilityMath.Epsilon ? 0
+ : d < -WalkVisibilityMath.Epsilon ? 1 : 2;
+ int n = ctx.ClipBuildingPolygon(building, portalRef.Polygon, side, clipped);
+ dump.AppendLine(
+ $" ref idx={portalRef.PortalIndex} other={bp.OtherCellId:x8} "
+ + $"rawSide={bp.PortalSide} exact={bp.ExactMatch} "
+ + $"planeD@eye={d:f2} eyeSide={side} clipN={n} "
+ + $"gate(side==rawSide)={(side == bp.PortalSide ? "PASS" : "reject")}");
+ }
+ }
+ string path = Path.Combine(Path.GetTempPath(), "fw1-portal-gate-dump.txt");
+ File.WriteAllText(path, dump.ToString());
+ Assert.True(true);
+ }
+
+ private static void Collect(WalkBspNode? node, List into)
+ {
+ if (node is null) return;
+ if (node.InPortals is not null) into.AddRange(node.InPortals);
+ Collect(node.PosNode, into);
+ Collect(node.NegNode, into);
+ }
+}