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, 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 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);
}
private sealed class NullSink : IWalkEventSink
{
public void Emit(in WalkEvent walkEvent) { }
}
///
/// 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).
///
[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();
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);
}
///
/// 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);
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;
}
}