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); + } +}