acdream/tests/AcDream.App.Tests/Rendering/Walk/WalkProductionWorldConformanceTests.cs

415 lines
19 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.Rendering;
using AcDream.App.Tests.Rendering;
using AcDream.App.Rendering.Walk;
using AcDream.Content;
using DatReaderWriter;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Options;
using System.Numerics;
namespace AcDream.App.Tests.Rendering.Walk;
/// <summary>
/// Campaign FW3.1's load-bearing deliverable: the ten pose-stamped oracle
/// fixtures (docs/research/2026-08-30-fw-walk-oracle/), replayed through the
/// PRODUCTION world-data builders
/// (<see cref="WalkCellFactory"/>/<see cref="WalkBuildingFactory"/>
/// /<see cref="WalkLandscapeAssembler"/> — the same code
/// <c>EnvCellLandblockBuildBuilder</c>/<c>LandblockBuildFactory</c> run at
/// real landblock-build time, consuming ONLY the legal
/// <see cref="IDatReaderWriter"/> seam), must reproduce the identical walk
/// output <see cref="WalkTraceConformanceTests"/> already proves against the
/// FW1 test adapter (<c>WalkWorldDatAdapter</c>/<c>WalkLandscapeDatBuilder</c>).
///
/// This class deliberately does NOT touch <see cref="WalkTraceConformanceTests"/>
/// (frozen — must stay green untouched) but reuses its driver/signature
/// helpers verbatim: <see cref="WalkTraceReplayContext"/>,
/// <see cref="WalkOracleTrace"/>, <see cref="WalkOraclePose"/>,
/// <see cref="WalkOracleFrame"/>. <see cref="WalkTraceReplayContext.Buildings"/>
/// is typed against the TEST adapter's <c>WalkWorldDatAdapter.BuildingEntry</c>
/// record — structurally identical to <see cref="WalkBuildingFactory.Entry"/>
/// (same three fields) — so <see cref="BuildProductionWorld"/> below adapts
/// one into the other rather than touching the shared context type.
///
/// <see cref="BuildProductionWorld"/> mirrors the FW1 harness's
/// <c>WalkLandscapeDatBuilder.Build</c> loop structure and ring math
/// EXACTLY (51×51 grid, <c>WalkLandscapeAssembler.MidRadius</c> = 25,
/// buildings/stab-cells only at full resolution), but every DATA-BUILDING
/// step below it — cells, buildings, the drawing BSP, z-slab — calls the
/// PRODUCTION functions. That is the whole conformance claim: the grid/ring
/// assembly is proven once (FW1's existing gate, via
/// <see cref="WalkLandscapeAssembler"/>'s own port of that same math); this
/// class proves the DATA those slots are filled with is identical to the
/// FW1 test adapter's.
/// </summary>
[Trait("Lane", "InstalledDat")]
public sealed class WalkProductionWorldConformanceTests
{
private sealed class Recorder : IWalkEventSink
{
public readonly List<WalkEvent> Events = new();
public Action<WalkEvent>? OnEmit { get; init; }
public void Emit(in WalkEvent walkEvent)
{
Events.Add(walkEvent);
OnEmit?.Invoke(walkEvent);
}
}
private static DatCollection OpenDats()
{
string? datDir = CornerFloodReplayTests.ResolveDatDir();
if (datDir is null)
{
Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory; see docs/release-gate.md.");
}
return new DatCollection(datDir!, DatAccessType.Read);
}
/// <summary>Builds the walk's world data (landscape + interior cells +
/// buildings) for a camera pose through the PRODUCTION builders, in the
/// exact shape <see cref="WalkTraceReplayContext"/> expects. Mirrors
/// <c>WalkLandscapeDatBuilder.Build</c>'s grid loop; every cell/building
/// constructed inside it comes from <see cref="WalkCellFactory"/> /
/// <see cref="WalkBuildingFactory"/>.</summary>
private static (WalkLandscapeAssembler Assembler, Dictionary<uint, WalkCell> Cells,
Dictionary<WalkBuilding, WalkWorldDatAdapter.BuildingEntry> Buildings)
BuildProductionWorld(IDatReaderWriter dats, uint cameraCellId, Vector3 cameraOrigin)
{
int cameraBlockX = (int)(cameraCellId >> 24);
int cameraBlockY = (int)((cameraCellId >> 16) & 0xFF);
var assembler = new WalkLandscapeAssembler();
var cells = new Dictionary<uint, WalkCell>();
var buildings = new Dictionary<WalkBuilding, WalkWorldDatAdapter.BuildingEntry>();
Region region = (Region)dats.Get<Region>(0x13000000u)!;
float[] heightTable = region.LandDefs.LandHeightTable;
for (int gx = 0; gx < WalkLandscapeAssembler.GridWidth; gx++)
{
for (int gy = 0; gy < WalkLandscapeAssembler.GridWidth; gy++)
{
int blockX = cameraBlockX + gx - WalkLandscapeAssembler.MidRadius;
int blockY = cameraBlockY + gy - WalkLandscapeAssembler.MidRadius;
if (blockX < 0 || blockX > 0xFF || blockY < 0 || blockY > 0xFF)
continue;
uint landblockId = (uint)((blockX << 24) | (blockY << 16));
if (dats.Get<LandBlock>(landblockId | 0xFFFFu) is not LandBlock landBlock)
continue;
byte maxByte = 0, minByte = 255;
foreach (byte h in landBlock.Height)
{
if (h > maxByte) maxByte = h;
if (h < minByte) minByte = h;
}
float maxZ = heightTable[maxByte] + 200f;
float minZ = heightTable[minByte] - 1f;
var blockOffset = new Vector3(
(gx - WalkLandscapeAssembler.MidRadius) * WalkLandscape.BlockLength,
(gy - WalkLandscapeAssembler.MidRadius) * WalkLandscape.BlockLength,
0f);
var blockBuildings = new List<WalkBuildingFactory.Entry>();
if (WalkLandscapeAssembler.SideCellCountForRing(WalkLandscapeAssembler.RingOf(gx, gy)) == 8)
{
LandBlockInfo? info = dats.Get<LandBlockInfo>(landblockId | 0xFFFEu);
blockBuildings = WalkBuildingFactory.Build(dats, landblockId, info?.Buildings, blockOffset);
foreach (WalkBuildingFactory.Entry entry in blockBuildings)
{
buildings[entry.Building] = new WalkWorldDatAdapter.BuildingEntry(
entry.Building, entry.WorldTransform, entry.InverseWorldTransform);
// Retail's loaded-interior rule (CLandBlock::init_buildings
// @0052fd80 -> add_to_stablist -> grab_visible_cells): a
// full-res block loads exactly its buildings' portal stab
// cells, matching WalkLandscapeDatBuilder's harness scope.
foreach (WalkBldPortal portal in entry.Building.Portals)
{
if (portal.OtherCellId != 0xFFFFFFFFu && !cells.ContainsKey(portal.OtherCellId))
{
WalkCell? c = WalkCellFactory.BuildCell(dats, portal.OtherCellId, blockOffset);
if (c is not null) cells[c.CellId] = c;
}
foreach (uint stab in portal.StabList)
{
if (cells.ContainsKey(stab)) continue;
WalkCell? c = WalkCellFactory.BuildCell(dats, stab, blockOffset);
if (c is not null) cells[c.CellId] = c;
}
}
}
}
assembler.PublishLandblock(landblockId, maxZ, minZ, blockBuildings);
}
}
assembler.SetViewer(cameraCellId, cameraOrigin);
return (assembler, cells, buildings);
}
[Fact]
public void Street_outdoor_first_frame_diff()
{
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Load("posed/holtburg-street-outdoor");
Assert.NotEmpty(frames);
using DatCollection dats = OpenDats();
using var adapter = new DatCollectionAdapter(dats);
WalkOracleFrame frame = frames[1];
Assert.NotNull(frame.Pose);
(WalkLandscapeAssembler assembler, Dictionary<uint, WalkCell> cells,
Dictionary<WalkBuilding, WalkWorldDatAdapter.BuildingEntry> buildings) =
BuildProductionWorld(adapter, frame.Pose!.CellId, frame.Pose.Origin);
var ctx = new WalkTraceReplayContext(frame.Pose, cells) { Buildings = buildings };
var walk = new RetailFrameWalk();
var recorder = new Recorder();
walk.WalkFrame(frame.Pose.CellId, null, assembler.Landscape, ctx, recorder);
string expected = WalkTraceReplayContext.Signature(frame);
string actual = WalkTraceReplayContext.Signature(recorder.Events);
Assert.True(
expected == actual,
$"production walk diverged from the FW1 test adapter\nEXPECTED: {expected}\nACTUAL: {actual}");
}
[Fact]
public void Doorway_still_first_frame_diff()
{
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Load("posed/holtburg-doorway-still");
Assert.NotEmpty(frames);
using DatCollection dats = OpenDats();
using var adapter = new DatCollectionAdapter(dats);
WalkOracleFrame frame = frames[1];
Assert.NotNull(frame.Pose);
(WalkLandscapeAssembler assembler, Dictionary<uint, WalkCell> cells,
Dictionary<WalkBuilding, WalkWorldDatAdapter.BuildingEntry> buildings) =
BuildProductionWorld(adapter, frame.Pose!.CellId, frame.Pose.Origin);
var ctx = new WalkTraceReplayContext(frame.Pose, cells) { Buildings = buildings };
WalkCell camera = Assert.Contains(frame.Pose.CellId, cells);
// Matches WalkTraceConformanceTests.Doorway_still_first_frame_diff:
// this capture ran under cdb load with Render::deg_mul depressed to
// the portless-arm threshold. Same environment pin, same reason.
var walk = new RetailFrameWalk { DegradeMultiplier = 0f };
var recorder = new Recorder();
walk.WalkFrame(frame.Pose.CellId, camera, assembler.Landscape, ctx, recorder);
string expected = WalkTraceReplayContext.Signature(frame);
string actual = WalkTraceReplayContext.Signature(recorder.Events);
Assert.True(
expected == actual,
$"production walk diverged from the FW1 test adapter\nEXPECTED: {expected}\nACTUAL: {actual}");
}
[Theory]
[InlineData("posed/terrace-center")]
[InlineData("posed/terrace-edge")]
[InlineData("posed/cathedral-arrival")]
public void Still_fixture_first_frame_reproduces_exactly(string fixture)
{
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Load(fixture);
Assert.NotEmpty(frames);
using DatCollection dats = OpenDats();
using var adapter = new DatCollectionAdapter(dats);
WalkOracleFrame frame = frames[1];
Assert.NotNull(frame.Pose);
(WalkLandscapeAssembler assembler, Dictionary<uint, WalkCell> cells,
Dictionary<WalkBuilding, WalkWorldDatAdapter.BuildingEntry> buildings) =
BuildProductionWorld(adapter, frame.Pose!.CellId, frame.Pose.Origin);
var ctx = new WalkTraceReplayContext(frame.Pose, cells) { Buildings = buildings };
WalkCell? camera = (frame.Pose.CellId & 0xFFFFu) >= 0x100
? Assert.Contains(frame.Pose.CellId, cells)
: null;
var walk = new RetailFrameWalk();
var recorder = new Recorder();
walk.WalkFrame(frame.Pose.CellId, camera, assembler.Landscape, ctx, recorder);
string expected = WalkTraceReplayContext.Signature(frame);
string actual = WalkTraceReplayContext.Signature(recorder.Events);
Assert.True(
expected == actual,
$"production walk diverged from the FW1 test adapter ({fixture})\nEXPECTED: {expected}\nACTUAL: {actual}");
}
[Theory]
[InlineData(0xF4180100u, 36.166267f, 79.828407f)]
[InlineData(0xF4180101u, 36.391270f, 72.167931f)]
public void Cathedral_transition_keeps_south_hall_admission_bounded_for_depth_occlusion(
uint cameraCellId,
float x,
float y)
{
// Owner's exact 2026-08-31 repro: the remote player and special NPC
// are parented in 0xF4180112, behind opaque cathedral walls. Retail
// hides them on BOTH sides of the 0x100 <-> 0x101 transition. The walk
// legitimately reaches 0x112 through one authored building aperture.
// Retail uses that cone only for coarse sphere admission, then draws
// each accepted shell/object whole; the complete intervening wall
// shell hides the remote actors through ordinary depth. Preserve the
// installed-DAT fact this depends on: every admitted route is a real,
// bounded portal polygon, never a pass-all zero-plane route.
var pose = new WalkOraclePose(
cameraCellId,
new Vector3(x, y, 169.804993f),
Q0: -0.004591f,
Q1: 0f,
Q2: 0f,
Q3: 0.999989f);
using DatCollection dats = OpenDats();
using var adapter = new DatCollectionAdapter(dats);
(WalkLandscapeAssembler assembler, Dictionary<uint, WalkCell> cells,
Dictionary<WalkBuilding, WalkWorldDatAdapter.BuildingEntry> buildings) =
BuildProductionWorld(adapter, pose.CellId, pose.Origin);
var ctx = new WalkTraceReplayContext(pose, cells) { Buildings = buildings };
WalkCell camera = Assert.Contains(cameraCellId, cells);
var remotePlayer = new Vector3(36.299465f, 18.594580f, 169.804993f);
var southHallAdmission = new List<string>();
var recorder = new Recorder
{
OnEmit = e =>
{
if (e.Kind != WalkEventKind.DrawCells
|| !e.Cells.Contains(0xF4180112u))
{
return;
}
WalkPortalView views = cells[0xF4180112u].TopView;
for (int viewIndex = 0; viewIndex < views.ViewCount; viewIndex++)
{
WalkViewPoly poly = views.View.Polys[viewIndex];
var planes = new WalkPlane[poly.VertexCount];
for (int edge = 0; edge < poly.VertexCount; edge++)
{
planes[edge] = views.View.Vertices[
poly.VertexIndex + edge].Plane;
}
WalkBoundingType verdict = WalkVisibilityMath.ViewconeCheck(
remotePlayer,
radius: 1.5f,
ctx.CyPlane,
planes);
float minDistance = planes
.Select(plane => Vector3.Dot(plane.Normal, remotePlayer) + plane.D)
.Append(Vector3.Dot(ctx.CyPlane.Normal, remotePlayer) + ctx.CyPlane.D)
.Min();
southHallAdmission.Add(
$"view={viewIndex} planes={poly.VertexCount} verdict={verdict} min={minDistance:F4}");
}
},
};
new RetailFrameWalk().WalkFrame(
cameraCellId,
camera,
assembler.Landscape,
ctx,
recorder);
WalkEvent[] drawCells = recorder.Events
.Where(static e => e.Kind == WalkEventKind.DrawCells)
.ToArray();
Assert.NotEmpty(drawCells);
Assert.Contains(cameraCellId, drawCells[0].Cells);
Assert.NotEmpty(southHallAdmission);
Assert.Contains(
southHallAdmission,
static verdict => verdict.Contains("verdict=PartiallyInside")
&& !verdict.Contains("planes=0"));
}
[Fact]
public void Foundry_entry_reproduces_every_frame_before_the_f67_order_segment()
{
// Same F67-F79 parked segment as WalkTraceConformanceTests (the
// building-a9b40036-root-plane viewpoint question is a walk/ported-
// algorithm question, not a world-data question — out of scope for
// this class). Frames 1-66 must still reproduce exactly.
MovingFixtureReplay("posed/foundry-entry", stopBeforeFrame: 67);
}
[Theory]
[InlineData("posed/holtburg-walkout")]
[InlineData("posed/holtburg-transitions")]
[InlineData("posed/holtburg-walkabout")]
public void Moving_fixture_reproduces_every_pairable_frame(string fixture)
=> MovingFixtureReplay(fixture);
private void MovingFixtureReplay(string fixture, int stopBeforeFrame = int.MaxValue)
{
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Load(fixture);
Assert.True(frames.Count >= 3);
using DatCollection dats = OpenDats();
using var adapter = new DatCollectionAdapter(dats);
WalkOraclePose anchor = frames[1].Pose!;
(WalkLandscapeAssembler assembler, Dictionary<uint, WalkCell> cells,
Dictionary<WalkBuilding, WalkWorldDatAdapter.BuildingEntry> buildings) =
BuildProductionWorld(adapter, anchor.CellId, anchor.Origin);
var walk = new RetailFrameWalk();
for (int n = 1; n < frames.Count - 1; n++)
{
WalkOracleFrame frame = frames[n];
if (frame.Number >= stopBeforeFrame) break;
string expected = WalkTraceReplayContext.Signature(frame);
string? firstActual = null;
bool matched = false;
foreach (WalkOraclePose pose in new[] { frames[n + 1].Pose!, frame.Pose! })
{
Assert.NotNull(pose);
assembler.SetViewer(pose.CellId, pose.Origin);
var ctx = new WalkTraceReplayContext(pose, cells) { Buildings = buildings };
WalkCell? camera = null;
if ((pose.CellId & 0xFFFFu) >= 0x100)
{
Assert.True(
cells.TryGetValue(pose.CellId, out camera),
$"frame {frame.Number}: interior camera cell {pose.CellId:x8} not loaded");
}
var recorder = new Recorder();
walk.WalkFrame(pose.CellId, camera, assembler.Landscape, ctx, recorder);
string actual = WalkTraceReplayContext.Signature(recorder.Events);
firstActual ??= actual;
if (actual == expected)
{
matched = true;
break;
}
}
Assert.True(
matched,
$"frame {frame.Number} diverged under both adjacent poses ({fixture})\n"
+ $"EXPECTED: {expected}\nACTUAL: {firstActual}");
}
}
[Fact]
public void Foundry_deep_reproduces_every_complete_frame_exactly()
{
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Load("posed/foundry-deep");
Assert.NotEmpty(frames);
using DatCollection dats = OpenDats();
using var adapter = new DatCollectionAdapter(dats);
Dictionary<uint, WalkCell> cells =
WalkCellFactory.BuildInteriorCells(adapter, 0xA9B40000u, Vector3.Zero);
var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] };
foreach (WalkOracleFrame frame in frames)
{
Assert.NotNull(frame.Pose);
WalkCell camera = Assert.Contains(frame.Pose!.CellId, cells);
var ctx = new WalkTraceReplayContext(frame.Pose, cells);
var walk = new RetailFrameWalk();
var recorder = new Recorder();
walk.WalkFrame(frame.Pose.CellId, camera, landscape, ctx, recorder);
Assert.Equal(
WalkTraceReplayContext.Signature(frame),
WalkTraceReplayContext.Signature(recorder.Events));
}
}
}