acdream/tests/AcDream.App.Tests/Rendering/Walk/WalkPortalGateDumpTests.cs
Erik 77f5342b62 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>
2026-08-30 11:49:27 +02:00

302 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// 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.
/// </summary>
[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<WalkOracleFrame> 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, frame.Pose.Origin);
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<WalkScreenPoint> 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<WalkPortalRef>();
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<WalkPortalRef> into)
{
if (node is null) return;
if (node.InPortals is not null) into.AddRange(node.InPortals);
Collect(node.PosNode, into);
Collect(node.NegNode, into);
}
private sealed class NullSink : IWalkEventSink
{
public void Emit(in WalkEvent walkEvent) { }
}
/// <summary>
/// Walkout-F2 microscope: retail floods buildings a9b4001e (×4) and
/// a9b40026 (×1) through the cottage's exit views; the replay floods
/// neither, while the SAME machinery against the root view passes the
/// street fixture. Differential per portal: clip count against the
/// full-viewport window vs each exit-view window, plus each window's
/// vertices and signed area (winding).
/// </summary>
[Fact]
public void Dump_the_exit_view_look_in_inputs_for_walkout_f2()
{
string? datDir = CornerFloodReplayTests.ResolveDatDir();
if (datDir is null)
{
Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory.");
}
var dump = new StringBuilder();
using var dats = new DatCollection(datDir!, DatAccessType.Read);
// 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);
var ctx = new WalkTraceReplayContext(pose, world.Cells)
{
Buildings = world.Buildings,
};
WalkCell camera = world.Cells[pose.CellId];
var walk = new RetailFrameWalk();
walk.WalkFrame(pose.CellId, camera, world.Landscape, ctx, new NullSink());
WalkPortalView outside = walk.InteriorPView.OutsideView;
dump.AppendLine(
$"pose cell={pose.CellId:x8} origin={pose.Origin} ov={outside.ViewCount}");
for (int v = 0; v < outside.ViewCount; v++)
{
WalkViewPoly poly = outside.View.Polys[v];
var verts = new Vector2[poly.VertexCount];
for (int k = 0; k < poly.VertexCount; k++)
verts[k] = outside.View.Vertices[poly.VertexIndex + k].Point;
dump.AppendLine(
$" exit view {v}: n={poly.VertexCount} area={SignedArea(verts):f1} "
+ $"verts={string.Join(' ', verts.Select(p => $"({p.X:f1},{p.Y:f1})"))}");
}
var defaultView = new WalkPortalView();
WalkCopyView.AppendFullViewportQuad(
defaultView, ctx.Rays, ctx.WorldViewpoint,
ctx.ViewportWidth, ctx.ViewportHeight);
{
WalkViewPoly rootPoly = defaultView.View.Polys[0];
var rootVerts = new Vector2[rootPoly.VertexCount];
for (int k = 0; k < rootPoly.VertexCount; k++)
rootVerts[k] = defaultView.View.Vertices[rootPoly.VertexIndex + k].Point;
dump.AppendLine(
$" root view: n={rootPoly.VertexCount} area={SignedArea(rootVerts):f1} "
+ $"verts={string.Join(' ', rootVerts.Select(p => $"({p.X:f1},{p.Y:f1})"))}");
}
foreach (uint id in new[] { 0xA9B4001Eu, 0xA9B40026u, 0xA9B4002Fu })
{
WalkBuilding building =
world.Buildings.Keys.Single(b => b.PositionCellId == id);
float dist = ctx.ViewerDistanceTo(building);
Vector3 eyeInBuilding = ctx.ViewpointInBuilding(building);
WalkBspNode? bsp = building.SelectDrawingBsp(dist);
dump.AppendLine(
$" building {id:x8}: dist={dist:f1} eff={MathF.Max(0f, dist - 100f):f1} "
+ $"bsp={(bsp is null ? "NULL" : "selected")} ports={CountPorts(bsp)}");
for (int li = 0; li < building.DegradeLevels.Length; li++)
{
WalkBuildingDegradeLevel lv = building.DegradeLevels[li];
dump.AppendLine(
$" level {li}: min={lv.MinDist:f1} ideal={lv.IdealDist:f1} "
+ $"max={lv.MaxDist:f1} ports={CountPorts(lv.DrawingBsp)} "
+ $"idealArmThr={lv.IdealDist:f1} "
+ $"negMulThr={lv.IdealDist - (lv.IdealDist - lv.MaxDist) * -0.99f:f1}");
}
if (bsp is null) continue;
var refs = new List<WalkPortalRef>();
WalkBuildingPortals.BuildDrawPortalsOnly(
bsp, 1, eyeInBuilding, (portalRef, _) => refs.Add(portalRef));
var clipped = new WalkScreenPoint[64];
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;
ctx.SetActiveView(defaultView, 0);
int rootN = ctx.ClipBuildingPolygon(
building, portalRef.Polygon, side, clipped);
string rootVerts = string.Join(
' ',
clipped.Take(rootN).Select(p => $"({p.X:f1},{p.Y:f1},w={p.W:f3})"));
var perView = new StringBuilder();
for (int v = 0; v < outside.ViewCount; v++)
{
ctx.SetActiveView(outside, v);
int n = ctx.ClipBuildingPolygon(
building, portalRef.Polygon, side, clipped);
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} "
+ $"rawSide={bp.PortalSide} eyeSide={side} "
+ $"gate={(side == bp.PortalSide ? "PASS" : "REJECT")} "
+ $"rootN={rootN}{perView}");
if (rootN > 0)
dump.AppendLine($" root-clipped: {rootVerts}");
}
}
}
string path = Path.Combine(Path.GetTempPath(), "fw1-walkout-f2-lookin-dump.txt");
File.WriteAllText(path, dump.ToString());
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);
private static float SignedArea(Vector2[] verts)
{
float sum = 0f;
for (int i = 0; i < verts.Length; i++)
{
Vector2 a = verts[i];
Vector2 b = verts[(i + 1) % verts.Length];
sum += a.X * b.Y - b.X * a.Y;
}
return 0.5f * sum;
}
}