diff --git a/tests/AcDream.App.Tests/Rendering/Walk/WalkBuildingJoinDiagnosticTests.cs b/tests/AcDream.App.Tests/Rendering/Walk/WalkBuildingJoinDiagnosticTests.cs
new file mode 100644
index 00000000..98c00214
--- /dev/null
+++ b/tests/AcDream.App.Tests/Rendering/Walk/WalkBuildingJoinDiagnosticTests.cs
@@ -0,0 +1,78 @@
+using AcDream.App.Tests.Rendering;
+using AcDream.App.Rendering.Walk;
+using DatReaderWriter;
+using DatReaderWriter.Options;
+using System.Text;
+
+namespace AcDream.App.Tests.Rendering.Walk;
+
+///
+/// FW1 look-in adjudication instrument: retail's street-outdoor trace
+/// punched look-ins ONLY at buildings a9b4001a (cells 16e,16a,16c) and
+/// a9b40022 (164,162,167,169). This pins the data-side join: those cells
+/// must be reachable through the respective building's portal table, and
+/// every drawing-BSP PortalRef must index INTO that table. A failure
+/// message carries the full dump for adjudication.
+///
+[Trait("Lane", "InstalledDat")]
+public sealed class WalkBuildingJoinDiagnosticTests
+{
+ [Theory]
+ [InlineData(0xA9B4001Au, new uint[] { 0xA9B4016Eu, 0xA9B4016Au, 0xA9B4016Cu })]
+ [InlineData(0xA9B40022u, new uint[] { 0xA9B40164u, 0xA9B40162u, 0xA9B40167u, 0xA9B40169u })]
+ public void Traced_lookin_cells_are_reachable_through_the_building_portal_table(
+ uint buildingCellId, uint[] tracedLookInCells)
+ {
+ string? datDir = CornerFloodReplayTests.ResolveDatDir();
+ if (datDir is null)
+ {
+ Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory.");
+ }
+ using var dats = new DatCollection(datDir!, DatAccessType.Read);
+ List entries =
+ WalkWorldDatAdapter.BuildBuildings(dats, 0xA9B40000u);
+ WalkBuilding building = Assert.Single(
+ entries, e => e.Building.PositionCellId == buildingCellId).Building;
+
+ var dump = new StringBuilder();
+ dump.AppendLine($"building {buildingCellId:x8}: {building.Portals.Length} portals");
+ var directCells = new HashSet();
+ for (int i = 0; i < building.Portals.Length; i++)
+ {
+ ref WalkBldPortal p = ref building.Portals[i];
+ directCells.Add(p.OtherCellId);
+ dump.AppendLine(
+ $" portal[{i}]: side={p.PortalSide} exact={p.ExactMatch} "
+ + $"other={p.OtherCellId:x8} otherPortal={p.OtherPortalId} "
+ + $"stabs=[{string.Join(',', p.StabList.Select(s => s.ToString("x8")))}]");
+ }
+ var bspJoins = new List();
+ CollectPortalIndices(building.DrawingBsp, bspJoins);
+ dump.AppendLine($" BSP PortalRef indices: [{string.Join(',', bspJoins)}]");
+
+ // Every BSP portal ref must join into the portal table.
+ Assert.All(bspJoins, i => Assert.InRange(i, 0, building.Portals.Length - 1));
+
+ // Every traced look-in cell must be directly behind a portal or in a
+ // portal's stab list (the flood reaches deeper cells through those).
+ var reachable = new HashSet(directCells);
+ foreach (WalkBldPortal p in building.Portals)
+ foreach (uint stab in p.StabList)
+ reachable.Add(stab);
+ foreach (uint traced in tracedLookInCells)
+ {
+ Assert.True(
+ reachable.Contains(traced),
+ $"traced look-in cell {traced:x8} is not reachable\n{dump}");
+ }
+ }
+
+ private static void CollectPortalIndices(WalkBspNode? node, List into)
+ {
+ if (node is null) return;
+ if (node.InPortals is not null)
+ into.AddRange(node.InPortals.Select(p => p.PortalIndex));
+ CollectPortalIndices(node.PosNode, into);
+ CollectPortalIndices(node.NegNode, into);
+ }
+}
diff --git a/tests/AcDream.App.Tests/Rendering/Walk/WalkLookInGateSweepTests.cs b/tests/AcDream.App.Tests/Rendering/Walk/WalkLookInGateSweepTests.cs
new file mode 100644
index 00000000..fa393784
--- /dev/null
+++ b/tests/AcDream.App.Tests/Rendering/Walk/WalkLookInGateSweepTests.cs
@@ -0,0 +1,115 @@
+using AcDream.App.Tests.Rendering;
+using AcDream.App.Rendering.Walk;
+using DatReaderWriter;
+using DatReaderWriter.Options;
+using System.Text;
+
+namespace AcDream.App.Tests.Rendering.Walk;
+
+///
+/// FW1 look-in gate adjudication sweep: with data joins proven correct,
+/// the wrong-building punches must come from the sidedness gate's two
+/// unvalidated inputs — the GfxObj portal-poly plane sign and the
+/// BuildingPortal side-flag decode. This sweeps all combinations against
+/// the street-outdoor fixture and reports which (if any) reproduces
+/// retail's punch set {001a, 0022}. Diagnostic: always passes; the result
+/// lands in the campaign record via the assert message when no arm matches.
+///
+[Trait("Lane", "InstalledDat")]
+public sealed class WalkLookInGateSweepTests
+{
+ private sealed class Recorder : IWalkEventSink
+ {
+ public readonly List Events = new();
+ public void Emit(in WalkEvent walkEvent) => Events.Add(walkEvent);
+ }
+
+ [Fact(Skip = "Adjudicated 2026-08-30: NO gate decode reproduces retail, and "
+ + "the join diagnostic proves the data is right — the missing mechanism "
+ + "is CEnvCell::GetVisible's LOADED-interior-cell gate (retail punched "
+ + "only the two buildings nearest the player; their interiors were "
+ + "loaded, farther ones were not; the replay loads everything). Port "
+ + "the interior load radius (landcell stab-list pull around the "
+ + "player) and re-run this sweep to pin the plane/side decode.")]
+ public void Sweep_the_lookin_gate_decodes_against_the_street_fixture()
+ {
+ 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);
+
+ // Retail's ground truth for this frame: which buildings punched, and
+ // which cells each punch listed.
+ string expectedPunches = PunchSignature(
+ frame.Events.Select(e => e.Kind == WalkOracleEventKind.Building
+ ? ("BLD", e.CellId!.Value, Array.Empty())
+ : e.Kind == WalkOracleEventKind.DrawCells
+ ? ("DC", 0u, e.Cells.ToArray())
+ : ("", 0u, Array.Empty())));
+
+ var report = new StringBuilder();
+ report.AppendLine($"RETAIL: {expectedPunches}");
+ string? winner = null;
+
+ foreach (bool flipPlanes in new[] { false, true })
+ {
+ foreach (int sideMode in new[] { 0, 1, 2, 3 })
+ {
+ // sideMode: 0 = (Flags & 0x2), 1 = inverted 0x2,
+ // 2 = (Flags & 0x1), 3 = inverted 0x1.
+ using var dats = new DatCollection(datDir!, DatAccessType.Read);
+ WalkWorldDatAdapter.FlipGfxPolygonPlanes = flipPlanes;
+ WalkWorldDatAdapter.BuildingSideMode = sideMode;
+ try
+ {
+ WalkLandscapeDatBuilder.BuiltWorld world =
+ WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId);
+ var ctx = new WalkTraceReplayContext(frame.Pose, world.Cells)
+ {
+ Buildings = world.Buildings,
+ };
+ var walk = new RetailFrameWalk();
+ var recorder = new Recorder();
+ walk.WalkFrame(frame.Pose.CellId, null, world.Landscape, ctx, recorder);
+ string actual = PunchSignature(
+ recorder.Events.Select(e => e.Kind == WalkEventKind.Building
+ ? ("BLD", e.CellId, Array.Empty())
+ : e.Kind == WalkEventKind.DrawCells
+ ? ("DC", 0u, e.Cells.ToArray())
+ : ("", 0u, Array.Empty())));
+ string arm = $"flip={flipPlanes} side={sideMode}";
+ report.AppendLine($"{arm}: {actual}");
+ if (actual == expectedPunches)
+ winner ??= arm;
+ }
+ finally
+ {
+ WalkWorldDatAdapter.FlipGfxPolygonPlanes = false;
+ WalkWorldDatAdapter.BuildingSideMode = 0;
+ }
+ }
+ }
+
+ Assert.True(
+ winner is not null,
+ $"no gate decode reproduces retail's punch pattern\n{report}");
+ }
+
+ private static string PunchSignature(
+ IEnumerable<(string Kind, uint Id, uint[] Cells)> events)
+ {
+ var parts = new List();
+ foreach ((string kind, uint id, uint[] cells) in events)
+ {
+ if (kind == "BLD") parts.Add($"B{id:x8}");
+ else if (kind == "DC")
+ parts.Add($"D[{string.Join(',', cells.Select(c => (c & 0xFFFF).ToString("x3")))}]");
+ }
+ return string.Join("|", parts);
+ }
+}
diff --git a/tests/AcDream.App.Tests/Rendering/Walk/WalkWorldDatAdapter.cs b/tests/AcDream.App.Tests/Rendering/Walk/WalkWorldDatAdapter.cs
index dc7f0fcc..907e31bc 100644
--- a/tests/AcDream.App.Tests/Rendering/Walk/WalkWorldDatAdapter.cs
+++ b/tests/AcDream.App.Tests/Rendering/Walk/WalkWorldDatAdapter.cs
@@ -137,7 +137,7 @@ public static class WalkWorldDatAdapter
BuildingPortal portal = buildingInfo.Portals[i];
portals[i] = new WalkBldPortal
{
- PortalSide = ((ushort)portal.Flags & 0x2) != 0 ? 1 : 0,
+ PortalSide = DecodeBuildingSide((ushort)portal.Flags),
ExactMatch = ((ushort)portal.Flags & 0x1) != 0,
OtherCellId = portal.OtherCellId == 0xFFFF
? 0xFFFFFFFFu
@@ -217,6 +217,19 @@ public static class WalkWorldDatAdapter
/// cross convention admits.
public static bool FlipGfxPolygonPlanes;
+ /// Adjudication toggle: the BuildingPortal side-flag decode.
+ /// 0 = (Flags & 0x2), 1 = inverted 0x2, 2 = (Flags & 0x1),
+ /// 3 = inverted 0x1.
+ public static int BuildingSideMode;
+
+ private static int DecodeBuildingSide(ushort flags) => BuildingSideMode switch
+ {
+ 0 => (flags & 0x2) != 0 ? 1 : 0,
+ 1 => (flags & 0x2) != 0 ? 0 : 1,
+ 2 => (flags & 0x1) != 0 ? 1 : 0,
+ _ => (flags & 0x1) != 0 ? 0 : 1,
+ };
+
private static WalkPolygon? BuildGfxPolygon(GfxObj gfxObj, ushort polygonId)
{
if (!gfxObj.Polygons.TryGetValue(polygonId, out Polygon? poly)