feat(render) Campaign FW1: doorway-still conformant - deg_mul is dynamic
Render::deg_mul is auto-tuned by frame load and SWUNG between oracle captures: doorway-still ran right after the heavy terrace-edge capture with the multiplier depressed (mul <= 0 puts thresholds at/below ideal, so 001e/0026/002f select the portless level 1 - retail's zero look-in floods, reproduced exactly at mul = 0), while every other fixture pins ~ +0.99 (thresholds at max). The recon session's "-0.99" live dump was real - taken under the same cdb load. RetailFrameWalk now exposes the multiplier; the doorway test pins 0, the rest use the default. foundry-entry: frames 1-66 reproduce exactly; the F67-F79 standing segment diverges ONLY in building 0036's intra-building DC order (retail 116,118,11d vs replay 11d,116,118). The BSP-traversal microscope pins the flip to 0036's ROOT plane (N=(0,0,-1) D=2.8): replay eye z 2.33 (d=+0.47, NEG-first) vs retail behaving as d<0 (POS-first) - a structural ~0.5 m frame question (positionPush(2)/part-scale), only adjudicable live. Turnkey probe: tools/walk-oracle/fw1-f67-viewpoint-probe.cdb. Fixture status: nine of ten fully conformant; foundry-entry exact through F66 with the 13-frame order segment parked on the probe. Suites: Walk 127/1 skip; hermetic 6,687/0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
c1a029edf9
commit
77f5342b62
4 changed files with 148 additions and 25 deletions
|
|
@ -59,6 +59,15 @@ public sealed class RetailFrameWalk
|
|||
/// regardless of per-cell visibility.</summary>
|
||||
public bool AlwaysDrawObjects = true;
|
||||
|
||||
/// <summary>Retail global <c>Render::deg_mul</c> — DYNAMIC: the
|
||||
/// auto-tuner (<c>auto_update_deg_mul</c>) 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).</summary>
|
||||
public float DegradeMultiplier = WalkBuilding.DefaultDegradeMultiplier;
|
||||
|
||||
/// <summary>The per-frame root (<c>SmartBox::RenderNormalMode</c>).
|
||||
/// <paramref name="cameraCell"/> may be null only when the camera is
|
||||
/// outdoors.</summary>
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -107,12 +107,16 @@ public sealed class WalkPortalGateDumpTests
|
|||
{
|
||||
Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory.");
|
||||
}
|
||||
IReadOnlyList<WalkOracleFrame> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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|.
|
||||
/// </summary>
|
||||
[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<WalkOracleFrame> 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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
28
tools/walk-oracle/fw1-f67-viewpoint-probe.cdb
Normal file
28
tools/walk-oracle/fw1-f67-viewpoint-probe.cdb
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue