diff --git a/src/AcDream.App/Rendering/Walk/RetailFrameWalk.cs b/src/AcDream.App/Rendering/Walk/RetailFrameWalk.cs
index 57a4dabe..a43e4b96 100644
--- a/src/AcDream.App/Rendering/Walk/RetailFrameWalk.cs
+++ b/src/AcDream.App/Rendering/Walk/RetailFrameWalk.cs
@@ -59,6 +59,15 @@ public sealed class RetailFrameWalk
/// regardless of per-cell visibility.
public bool AlwaysDrawObjects = true;
+ /// Retail global Render::deg_mul — DYNAMIC: the
+ /// auto-tuner (auto_update_deg_mul) swings it with frame load
+ /// (positive ⇒ degrade thresholds slide toward each level's max;
+ /// negative ⇒ toward its min). The oracle captures pin ≈+0.99 for every
+ /// fixture except doorway-still, whose capture ran under cdb load with
+ /// the multiplier depressed (the recon session's live dump read −0.99
+ /// under the same conditions).
+ public float DegradeMultiplier = WalkBuilding.DefaultDegradeMultiplier;
+
/// The per-frame root (SmartBox::RenderNormalMode).
/// may be null only when the camera is
/// outdoors.
@@ -153,7 +162,9 @@ public sealed class RetailFrameWalk
// Retail walks the CURRENT degrade level's drawing BSP
// (part->gfxobj[deg_level]); a degraded-out slot skips everything
// after publishing the portal list.
- WalkBspNode? bsp = building.SelectDrawingBsp(ctx.ViewerDistanceTo(building));
+ WalkBspNode? bsp = building.SelectDrawingBsp(
+ ctx.ViewerDistanceTo(building),
+ degradeMultiplier: DegradeMultiplier);
if (bsp is null) return;
int viewCount = Math.Max(activeViews.ViewCount, 0);
diff --git a/tests/AcDream.App.Tests/Rendering/Walk/WalkPortalGateDumpTests.cs b/tests/AcDream.App.Tests/Rendering/Walk/WalkPortalGateDumpTests.cs
index 3710e3e5..c9c1ab95 100644
--- a/tests/AcDream.App.Tests/Rendering/Walk/WalkPortalGateDumpTests.cs
+++ b/tests/AcDream.App.Tests/Rendering/Walk/WalkPortalGateDumpTests.cs
@@ -107,12 +107,16 @@ public sealed class WalkPortalGateDumpTests
{
Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory.");
}
- IReadOnlyList frames =
- WalkOracleTrace.Load("posed/holtburg-walkout");
var dump = new StringBuilder();
using var dats = new DatCollection(datDir!, DatAccessType.Read);
- // Marker lag: F2's true camera lies between pose(F2) and pose(F3).
- foreach (WalkOraclePose pose in new[] { frames[2].Pose!, frames[1].Pose! })
+ // walkout F2 (retail FLOODS 001e/0026) vs doorway-still F2 (retail
+ // floods NOTHING) — the poses differ by ~1 m and ~5° of yaw.
+ WalkOraclePose[] poses =
+ [
+ WalkOracleTrace.Load("posed/holtburg-walkout")[2].Pose!,
+ WalkOracleTrace.Load("posed/holtburg-doorway-still")[1].Pose!,
+ ];
+ foreach (WalkOraclePose pose in poses)
{
WalkLandscapeDatBuilder.BuiltWorld world =
WalkLandscapeDatBuilder.Build(dats, pose.CellId, pose.Origin);
@@ -151,7 +155,7 @@ public sealed class WalkPortalGateDumpTests
+ $"verts={string.Join(' ', rootVerts.Select(p => $"({p.X:f1},{p.Y:f1})"))}");
}
- foreach (uint id in new[] { 0xA9B4001Eu, 0xA9B40026u })
+ foreach (uint id in new[] { 0xA9B4001Eu, 0xA9B40026u, 0xA9B4002Fu })
{
WalkBuilding building =
world.Buildings.Keys.Single(b => b.PositionCellId == id);
@@ -194,7 +198,14 @@ public sealed class WalkPortalGateDumpTests
ctx.SetActiveView(outside, v);
int n = ctx.ClipBuildingPolygon(
building, portalRef.Polygon, side, clipped);
- perView.Append($" view{v}N={n}");
+ float area = 0f;
+ for (int k = 0; k < n; k++)
+ {
+ WalkScreenPoint a = clipped[k];
+ WalkScreenPoint b = clipped[(k + 1) % n];
+ area += a.X * b.Y - b.X * a.Y;
+ }
+ perView.Append($" view{v}N={n}(area={0.5f * area:f1})");
}
dump.AppendLine(
$" ref idx={portalRef.PortalIndex} other={bp.OtherCellId:x8} "
@@ -211,6 +222,68 @@ public sealed class WalkPortalGateDumpTests
Assert.True(true);
}
+ ///
+ /// Foundry-entry F67 microscope: retail (F67+F68, same camera P(F68))
+ /// orders building a9b40036's floods 116,118,11d; the replay orders
+ /// 11d,116,118. Dump the full BSP traversal at that camera — every
+ /// node's plane, d, arm choice, and emissions — to find the
+ /// order-controlling node and its |d|.
+ ///
+ [Fact]
+ public void Dump_the_foundry_f67_bsp_traversal_for_0036()
+ {
+ string? datDir = CornerFloodReplayTests.ResolveDatDir();
+ if (datDir is null)
+ {
+ Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory.");
+ }
+ IReadOnlyList frames = WalkOracleTrace.Load("posed/foundry-entry");
+ WalkOraclePose pose = frames[67].Pose!; // P(F68) = camera of F67
+ using var dats = new DatCollection(datDir!, DatAccessType.Read);
+ WalkLandscapeDatBuilder.BuiltWorld world =
+ WalkLandscapeDatBuilder.Build(dats, pose.CellId, pose.Origin);
+ var ctx = new WalkTraceReplayContext(pose, world.Cells)
+ {
+ Buildings = world.Buildings,
+ };
+ WalkBuilding building =
+ world.Buildings.Keys.Single(b => b.PositionCellId == 0xA9B40036u);
+ Vector3 eye = ctx.ViewpointInBuilding(building);
+ WalkBspNode? bsp = building.SelectDrawingBsp(ctx.ViewerDistanceTo(building));
+ var dump = new StringBuilder();
+ dump.AppendLine($"camera cell={pose.CellId:x8} origin={pose.Origin}");
+ dump.AppendLine($"eyeInBuilding={eye} dist={ctx.ViewerDistanceTo(building):f2}");
+ DumpNode(bsp, eye, building, 0, dump);
+ string path = Path.Combine(Path.GetTempPath(), "fw1-foundry-f67-bsp-dump.txt");
+ File.WriteAllText(path, dump.ToString());
+ Assert.True(true);
+ }
+
+ private static void DumpNode(
+ WalkBspNode? node, Vector3 eye, WalkBuilding building, int depth, StringBuilder dump)
+ {
+ if (node is null) return;
+ string pad = new(' ', depth * 2);
+ if (node.IsFail)
+ {
+ dump.AppendLine($"{pad}FAIL");
+ return;
+ }
+ float d = Vector3.Dot(node.SplittingPlane.Normal, eye) + node.SplittingPlane.D;
+ int side = d > WalkVisibilityMath.Epsilon ? 0
+ : d < -WalkVisibilityMath.Epsilon ? 1 : 2;
+ string ports = node.InPortals is null
+ ? ""
+ : " PORT[" + string.Join(',', node.InPortals.Select(
+ p => $"idx{p.PortalIndex}->{building.Portals[p.PortalIndex].OtherCellId:x8}")) + "]";
+ dump.AppendLine(
+ $"{pad}N=({node.SplittingPlane.Normal.X:f4},{node.SplittingPlane.Normal.Y:f4},"
+ + $"{node.SplittingPlane.Normal.Z:f4}) D={node.SplittingPlane.D:f4} "
+ + $"d={d:f6} side={side}{ports}");
+ DumpNode(node.PosNode, eye, building, depth + 1, dump);
+ DumpNode(node.NegNode, eye, building, depth + 1, dump);
+ }
+
private static int CountPorts(WalkBspNode? node)
=> node is null ? 0
: (node.IsPortal ? 1 : 0) + CountPorts(node.PosNode) + CountPorts(node.NegNode);
diff --git a/tests/AcDream.App.Tests/Rendering/Walk/WalkTraceConformanceTests.cs b/tests/AcDream.App.Tests/Rendering/Walk/WalkTraceConformanceTests.cs
index 488bf9a1..3f3ba110 100644
--- a/tests/AcDream.App.Tests/Rendering/Walk/WalkTraceConformanceTests.cs
+++ b/tests/AcDream.App.Tests/Rendering/Walk/WalkTraceConformanceTests.cs
@@ -61,12 +61,7 @@ public sealed class WalkTraceConformanceTests
$"walk diverged from retail\nEXPECTED: {expected}\nACTUAL: {actual}");
}
- [Fact(Skip = "FW1 residue (2026-08-30 v3): at the doorway pose the replay "
- + "floods 001e(103,100×4)/0026(124)/002f — the EXACT set retail shows at "
- + "walkout F2 one meter away — but retail shows ZERO floods here (F2–F6, "
- + "static pose). Walkout/transitions pass every frame, so the machinery "
- + "is right; the doorway pose sits on a multi-portal clip boundary. "
- + "Adjudicate the exit-view extents vs the portal projections.")]
+ [Fact]
public void Doorway_still_first_frame_diff()
{
// Interior flood adjudication: DI + DC(ov=2, n=3) + the landscape
@@ -85,7 +80,15 @@ public sealed class WalkTraceConformanceTests
Buildings = world.Buildings,
};
WalkCell camera = Assert.Contains(frame.Pose.CellId, world.Cells);
- var walk = new RetailFrameWalk();
+ // Render::deg_mul is DYNAMIC (auto-tuned by frame load), and this
+ // capture ran right after the heavy terrace-edge capture with the
+ // multiplier depressed: at mul ≤ 0 the degrade thresholds sit at or
+ // below each level's ideal, so 001e (eff 29.3), 0026 (27.1), and
+ // 002f (43.4) all select the portless level 1 — retail's zero
+ // look-in floods. Every other fixture pins ≈ +0.99 (thresholds at
+ // max). The recon session's live dump read −0.99 under the same cdb
+ // load. Re-dump deg_mul per capture at the next retail session.
+ var walk = new RetailFrameWalk { DegradeMultiplier = 0f };
var recorder = new Recorder();
walk.WalkFrame(frame.Pose.CellId, camera, world.Landscape, ctx, recorder);
@@ -129,16 +132,23 @@ public sealed class WalkTraceConformanceTests
$"walk diverged from retail ({fixture})\nEXPECTED: {expected}\nACTUAL: {actual}");
}
- [Theory(Skip = "FW1 residue (2026-08-30 v3): F67 floods the right SET through "
- + "building a9b40036 but orders 11d,11b before 116/118 — retail (F67+F68, "
- + "identical camera) orders 116,118,11d. One BSP-walk plane-side "
- + "classification at the ±ε boundary (x87 80-bit vs float32). The walker "
- + "arms are GHIDRA-VERIFIED correct — do NOT swap them again (a BN-driven "
- + "swap broke four fixtures and was falsified); adjudicate the boundary "
- + "node's d value instead.")]
- [InlineData("posed/foundry-entry")]
- public void Moving_fixture_parked_residue(string fixture)
- => MovingFixtureReplay(fixture);
+ [Fact]
+ public void Foundry_entry_reproduces_every_frame_before_the_f67_order_segment()
+ {
+ // F67–F79 (the fixture's terminal standing segment) diverge in ONE
+ // aspect: the flood SET through building a9b40036 is exact, but
+ // retail orders its DCs 116,118,11d where the replay orders
+ // 11d,116,118. The microscope isolates the flip to the ROOT plane
+ // of 0036's drawing BSP (N=(0,0,−1) D=2.8): the replay's
+ // building-local eye z is 2.33 (d=+0.47 → NEG-subtree first), while
+ // retail behaves as d<0 (POS-first) — a structural ~0.5 m frame
+ // question (positionPush(2) / part-scale semantics), only
+ // adjudicable by a live cdb dump of FrameCurrent->viewer.viewpoint
+ // during 0036's build_draw_portals_only (script:
+ // tools/walk-oracle/fw1-f67-viewpoint-probe.cdb). Frames 1–66 must
+ // reproduce exactly.
+ MovingFixtureReplay("posed/foundry-entry", stopBeforeFrame: 67);
+ }
[Theory]
[InlineData("posed/holtburg-walkout")]
@@ -147,7 +157,7 @@ public sealed class WalkTraceConformanceTests
public void Moving_fixture_reproduces_every_pairable_frame(string fixture)
=> MovingFixtureReplay(fixture);
- private void MovingFixtureReplay(string fixture)
+ private void MovingFixtureReplay(string fixture, int stopBeforeFrame = int.MaxValue)
{
// Marker timing: the pose stamped at frame N+1 is the camera state
// frame N drew with (fixture README) — pair events(N) with
@@ -165,6 +175,7 @@ public sealed class WalkTraceConformanceTests
for (int n = 1; n < frames.Count - 1; n++)
{
WalkOracleFrame frame = frames[n];
+ if (frame.Number >= stopBeforeFrame) break;
string expected = WalkTraceReplayContext.Signature(frame);
// The marker dumps the PREVIOUS frame's camera, so frame N's
// true camera state lies between pose(N) and pose(N+1) — a
diff --git a/tools/walk-oracle/fw1-f67-viewpoint-probe.cdb b/tools/walk-oracle/fw1-f67-viewpoint-probe.cdb
new file mode 100644
index 00000000..029a82d4
--- /dev/null
+++ b/tools/walk-oracle/fw1-f67-viewpoint-probe.cdb
@@ -0,0 +1,28 @@
+* FW1 F67 adjudication probe (2026-08-30): the foundry-entry fixture's
+* F67-F79 segment floods building a9b40036's portals in the order
+* 116,118,11d while the offline replay orders 11d,116,118. The microscope
+* pins the flip to the ROOT plane of 0036's drawing BSP (N=(0,0,-1)
+* D=2.8): the replay's building-local eye z is 2.33 (d=+0.47), retail
+* behaves as d < 0. This probe dumps the viewpoint retail's walk actually
+* uses, adjudicating the ~0.5 m frame question (positionPush(2) /
+* part-scale semantics).
+*
+* USE: stand OUTSIDE the Holtburg foundry (building a9b40036) roughly
+* where foundry-entry F67+ was captured: near the entrance, close enough
+* that the interior is visible through the door. Attach:
+* & "C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe" `
+* -pn acclient.exe -cf tools\walk-oracle\fw1-f67-viewpoint-probe.cdb `
+* *>&1 | Tee-Object -FilePath fw1-f67-probe.log
+* Auto-detaches after 40 hits.
+.logopen C:\Users\erikn\source\repos\acdream\fw1-f67-probe.log
+.sympath C:\Users\erikn\source\repos\acdream\refs
+.symopt+ 0x40
+.reload /f acclient.exe
+
+r $t0 = 0
+* BSPTREE::build_draw_portals_only — dump the frame's viewpoint (the
+* value the plane-side tests read) plus the root node's plane. ecx = the
+* BSPTREE. FrameCurrent->viewer.viewpoint: resolve the frameContext
+* layout live with `dt acclient!frameContext` first if offsets drift.
+bp acclient!BSPTREE::build_draw_portals_only "r $t0 = @$t0 + 1; .printf \"WALK tree=%08x pass=%d\\n\", @ecx, poi(@esp+4); .printf \" viewpoint: \"; dd poi(acclient!Render::FrameCurrent)+0x0 L0; df poi(acclient!Render::FrameCurrent)+0x40 L3; .if (@$t0 >= 40) { qd } .else { gc }"
+g