feat(render) Campaign FW3.1: production walk world data behind the seam

The retail frame walk's world model now materializes from production
landblock-build owners through the legal IDatReaderWriter seam, with
zero frame wiring (FW3.2 roots the frame):

- WalkCellFactory: WalkCell built in the SAME pass as LoadedCell
  (EnvCellLandblockBuild.BuildVisibilityCell) from the raw portal
  Flags/polygons/planes/stab lists already parsed there; stored as
  LoadedCell.Walk, committed atomically with the cell. The
  fixture-pinned decodes (inverse-0x2 portal side, 0xFFFF->0xFFFFFFFF
  exit widening) live here.
- WalkBuildingFactory + WalkBuildingRegistry: the production
  WalkBuilding build (drawing BSP with PORT nodes, degrade ladder,
  portal sides/stab lists, sort center, model frame) from the SAME
  LandBlockInfo the streaming build already fetches, under the
  factory's existing DAT lock - closing the gap where BuildingLoader
  drops every walk field at load.
- WalkLandscapeAssembler: the retail 51x51 viewer-centred grid
  (mid_radius 25) fed incrementally from landblock publish/retire;
  per-block z-slab (heightTable[max]+200 / [min]-1) computed
  worker-side in LandblockBuildFactory from the heights already in
  hand. O(1) SetViewer on same-block frames.
- WalkProductionFrameContext: the walk's frame contexts over
  CellVisibility + WalkBuildingRegistry with a generic
  inverse-view-projection ray caster (rays feed cross products only -
  scale-free) and the znear=0.1 CY plane.
- Publication: LandblockRenderPublisher owns both walk registries,
  publishing in the same AdvanceCompleteOne step as BuildingRegistry
  and retiring in RemoveBuildingRegistry - same commit, same
  retirement, no new ticket stage.

Conformance: ALL TEN oracle fixtures replay identically through the
PRODUCTION builders (WalkProductionWorldConformanceTests) - same
signatures as the test adapter, first run. Known gap documented for
FW3.2: far-tier landblocks carry no EnvCell transaction, so their
z-slab never reaches the assembler.

Suites: full Release build 0 warnings; Walk lane 186/1 skip;
hermetic 6,738/0 (+24); RuntimeDatAccessArchitectureTests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-30 13:19:22 +02:00
parent b95850defe
commit b10ad662b0
14 changed files with 1711 additions and 2 deletions

View file

@ -0,0 +1,75 @@
using System.Numerics;
using AcDream.App.Rendering.Walk;
namespace AcDream.App.Tests.Rendering.Walk;
/// <summary>Campaign FW3.1 — hermetic (no DAT) coverage of the walk's
/// production building registry: landblock-keyed publish/retire and the
/// O(1) reverse index <see cref="WalkProductionFrameContext"/> relies on.</summary>
public sealed class WalkBuildingRegistryTests
{
private static WalkBuildingFactory.Entry Entry(uint positionCellId) =>
new(new WalkBuilding { PositionCellId = positionCellId }, Matrix4x4.Identity, Matrix4x4.Identity);
[Fact]
public void Publish_MakesBuildingsFindableByLandblockAndByReference()
{
var registry = new WalkBuildingRegistry();
WalkBuildingFactory.Entry entry = Entry(0xA9B40001u);
registry.Publish(0xA9B4FFFFu, new[] { entry });
Assert.Same(entry, Assert.Single(registry.GetBuildings(0xA9B40100u)));
Assert.True(registry.TryGetEntry(entry.Building, out var found));
Assert.Same(entry, found);
Assert.Equal(1, registry.LandblockCount);
}
[Fact]
public void Publish_ReplacesThePreviousLandblockAndDropsItsReverseIndexEntries()
{
var registry = new WalkBuildingRegistry();
WalkBuildingFactory.Entry first = Entry(0xA9B40001u);
WalkBuildingFactory.Entry second = Entry(0xA9B40002u);
registry.Publish(0xA9B4FFFFu, new[] { first });
registry.Publish(0xA9B4FFFFu, new[] { second });
Assert.Same(second, Assert.Single(registry.GetBuildings(0xA9B4FFFFu)));
Assert.False(registry.TryGetEntry(first.Building, out _));
Assert.True(registry.TryGetEntry(second.Building, out _));
Assert.Equal(1, registry.LandblockCount);
}
[Fact]
public void Retire_RemovesTheLandblockAndItsReverseIndexEntries()
{
var registry = new WalkBuildingRegistry();
WalkBuildingFactory.Entry entry = Entry(0xA9B40001u);
registry.Publish(0xA9B4FFFFu, new[] { entry });
registry.Retire(0xA9B40100u); // any id sharing the landblock prefix
Assert.Empty(registry.GetBuildings(0xA9B4FFFFu));
Assert.False(registry.TryGetEntry(entry.Building, out _));
Assert.Equal(0, registry.LandblockCount);
}
[Fact]
public void Retire_UnknownLandblockIsANoOp()
{
var registry = new WalkBuildingRegistry();
registry.Retire(0xA9B4FFFFu);
Assert.Equal(0, registry.LandblockCount);
}
[Fact]
public void GetBuildings_UnpublishedLandblockReturnsEmpty()
{
var registry = new WalkBuildingRegistry();
Assert.Empty(registry.GetBuildings(0xA9B4FFFFu));
}
}

View file

@ -0,0 +1,148 @@
using System.Numerics;
using AcDream.App.Rendering.Walk;
namespace AcDream.App.Tests.Rendering.Walk;
/// <summary>Campaign FW3.1 — hermetic (no DAT) coverage of the production
/// landscape assembler: publish/retire lifecycle, the LOD ring pyramid, and
/// the viewer-recentre grid reflow. The DAT-backed equivalence to the FW1
/// test builder is proven by <c>WalkProductionWorldConformanceTests</c>
/// (Lane=InstalledDat); this class covers the incremental publish/retire
/// machinery that harness doesn't exercise (it publishes and calls
/// SetViewer exactly once per fixture).</summary>
public sealed class WalkLandscapeAssemblerTests
{
private const uint LandblockId = 0xA9B4FFFFu; // block (0xA9, 0xB4)
private const uint CameraCellId = 0xA9B40001u; // same block, outdoor landcell 1
private static int GridIndex(int gx, int gy) => gx * WalkLandscapeAssembler.GridWidth + gy;
private static int CenterIndex() =>
GridIndex(WalkLandscapeAssembler.MidRadius, WalkLandscapeAssembler.MidRadius);
[Fact]
public void PublishBeforeSetViewer_IsVisibleOnceSetViewerRuns()
{
var assembler = new WalkLandscapeAssembler();
assembler.PublishLandblock(LandblockId, maxZ: 100f, minZ: -5f, Array.Empty<WalkBuildingFactory.Entry>());
assembler.SetViewer(CameraCellId, Vector3.Zero);
WalkLandBlock? block = assembler.Landscape.Blocks[CenterIndex()];
Assert.NotNull(block);
Assert.Equal(8, block!.SideCellCount);
Assert.Equal(100f, block.MaxZ);
Assert.Equal(-5f, block.MinZ);
}
[Fact]
public void PublishAfterSetViewer_RefreshesTheAlreadyWindowedSlotImmediately()
{
var assembler = new WalkLandscapeAssembler();
assembler.SetViewer(CameraCellId, Vector3.Zero);
assembler.PublishLandblock(LandblockId, maxZ: 42f, minZ: 3f, Array.Empty<WalkBuildingFactory.Entry>());
Assert.Equal(42f, assembler.Landscape.Blocks[CenterIndex()]!.MaxZ);
}
[Fact]
public void RingPyramid_FarBlockDegradesSideCellCountAndNeverAttachesBuildings()
{
var assembler = new WalkLandscapeAssembler();
var building = new WalkBuildingFactory.Entry(
new WalkBuilding { PositionCellId = (LandblockId & 0xFFFF0000u) | 1u },
Matrix4x4.Identity, Matrix4x4.Identity);
assembler.PublishLandblock(LandblockId, 1f, 0f, new[] { building });
// Three blocks north of the camera's own block -> ring 3 -> SideCellCount 2.
const uint FarLandblockId = 0xA9B7FFFFu;
assembler.PublishLandblock(FarLandblockId, 1f, 0f, new[] { building });
assembler.SetViewer(CameraCellId, Vector3.Zero);
WalkLandBlock center = assembler.Landscape.Blocks[CenterIndex()]!;
WalkLandBlock far = assembler.Landscape.Blocks[
GridIndex(WalkLandscapeAssembler.MidRadius, WalkLandscapeAssembler.MidRadius + 3)]!;
Assert.Equal(8, center.SideCellCount);
Assert.Contains(center.CellBuildings, b => b is not null);
Assert.Equal(2, far.SideCellCount);
Assert.All(far.CellBuildings, Assert.Null);
}
[Fact]
public void RetireLandblock_NullsTheWindowedSlot()
{
var assembler = new WalkLandscapeAssembler();
assembler.PublishLandblock(LandblockId, 1f, 0f, Array.Empty<WalkBuildingFactory.Entry>());
assembler.SetViewer(CameraCellId, Vector3.Zero);
Assert.NotNull(assembler.Landscape.Blocks[CenterIndex()]);
assembler.RetireLandblock(LandblockId);
Assert.Null(assembler.Landscape.Blocks[CenterIndex()]);
}
[Fact]
public void RetireLandblock_UnpublishedLandblockIsANoOp()
{
var assembler = new WalkLandscapeAssembler();
assembler.RetireLandblock(LandblockId);
Assert.Null(assembler.Landscape.Blocks[CenterIndex()]);
}
[Fact]
public void SetViewer_RecentresTheWindowWhenTheCameraCrossesIntoAnotherBlock()
{
var assembler = new WalkLandscapeAssembler();
assembler.PublishLandblock(LandblockId, 7f, -1f, Array.Empty<WalkBuildingFactory.Entry>());
assembler.SetViewer(CameraCellId, Vector3.Zero);
Assert.NotNull(assembler.Landscape.Blocks[CenterIndex()]);
// Move the camera one block east; the published landblock should now
// sit one grid slot WEST of center instead of at center.
assembler.SetViewer(0xAAB40001u, Vector3.Zero);
Assert.Null(assembler.Landscape.Blocks[CenterIndex()]);
WalkLandBlock? shifted = assembler.Landscape.Blocks[
GridIndex(WalkLandscapeAssembler.MidRadius - 1, WalkLandscapeAssembler.MidRadius)];
Assert.NotNull(shifted);
Assert.Equal(7f, shifted!.MaxZ);
}
[Fact]
public void SetViewer_LandcellIndexDerivesViewerCellFromLowWord()
{
var assembler = new WalkLandscapeAssembler();
// Low word 10 -> landcell index 9 -> (9/8, 9%8) = (1, 1).
assembler.SetViewer(0xA9B4000Au, Vector3.Zero);
Assert.Equal(1, assembler.Landscape.ViewerCellX);
Assert.Equal(1, assembler.Landscape.ViewerCellY);
}
[Fact]
public void SetViewer_InteriorCameraDerivesViewerCellFromOrigin()
{
var assembler = new WalkLandscapeAssembler();
assembler.SetViewer(0xA9B40105u, new Vector3(50f, 74f, 0f));
Assert.Equal(2, assembler.Landscape.ViewerCellX); // floor(50 / 24) = 2
Assert.Equal(3, assembler.Landscape.ViewerCellY); // floor(74 / 24) = 3
}
[Fact]
public void SetViewer_SameBlockRepeatCallDoesNotClearAlreadyPublishedSlots()
{
var assembler = new WalkLandscapeAssembler();
assembler.PublishLandblock(LandblockId, 1f, 0f, Array.Empty<WalkBuildingFactory.Entry>());
assembler.SetViewer(CameraCellId, Vector3.Zero);
assembler.SetViewer(CameraCellId, new Vector3(5f, 5f, 0f));
Assert.NotNull(assembler.Landscape.Blocks[CenterIndex()]);
}
}

View file

@ -0,0 +1,131 @@
using System.Numerics;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Walk;
namespace AcDream.App.Tests.Rendering.Walk;
/// <summary>Campaign FW3.1 — hermetic (no DAT) coverage of the production
/// frame context: cell resolution through <see cref="CellVisibility"/>,
/// building resolution through <see cref="WalkBuildingRegistry"/>, and the
/// CyPlane/ray-cast wiring. This proves the SEAM (does the context read the
/// right registries the right way); the ten-fixture conformance gate proves
/// the WORLD DATA those registries are fed is correct.</summary>
public sealed class WalkProductionFrameContextTests
{
private static Matrix4x4 SimpleViewProjection() =>
Matrix4x4.CreateLookAt(Vector3.Zero, Vector3.UnitY, Vector3.UnitZ)
* Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 3f, 4f / 3f, 0.1f, 1000f);
[Fact]
public void GetVisible_ResolvesThroughTheCommittedCellVisibilityRegistry()
{
var cellVisibility = new CellVisibility();
var walkCell = new WalkCell { CellId = 0xA9B40100u };
var loaded = new LoadedCell { CellId = 0xA9B40100u, Walk = walkCell };
cellVisibility.CommitLandblock(0xA9B4FFFFu, new[] { loaded });
var ctx = new WalkProductionFrameContext(
cellVisibility, new WalkBuildingRegistry(), Vector3.Zero, Vector3.UnitY,
SimpleViewProjection(), 1024f, 768f);
Assert.Same(walkCell, ctx.GetVisible(0xA9B40100u));
Assert.Null(ctx.GetVisible(0xA9B40101u));
}
[Fact]
public void GetVisible_ReturnsNullWhenTheCommittedCellHasNoWalkModel()
{
// A hand-built LoadedCell that never went through
// EnvCellLandblockBuildBuilder.BuildVisibilityCell (test-only
// shortcut some existing fixtures take) — Walk stays null.
var cellVisibility = new CellVisibility();
var loaded = new LoadedCell { CellId = 0xA9B40100u };
cellVisibility.CommitLandblock(0xA9B4FFFFu, new[] { loaded });
var ctx = new WalkProductionFrameContext(
cellVisibility, new WalkBuildingRegistry(), Vector3.Zero, Vector3.UnitY,
SimpleViewProjection(), 1024f, 768f);
Assert.Null(ctx.GetVisible(0xA9B40100u));
}
[Fact]
public void ObjectToClipAndViewpointIn_UseTheCellsOwnTransforms()
{
var cell = new WalkCell
{
CellId = 1,
WorldTransform = Matrix4x4.CreateTranslation(10f, 0f, 0f),
InverseWorldTransform = Matrix4x4.CreateTranslation(-10f, 0f, 0f),
};
Matrix4x4 vp = SimpleViewProjection();
var ctx = new WalkProductionFrameContext(
new CellVisibility(), new WalkBuildingRegistry(), new Vector3(10f, 0f, 0f), Vector3.UnitY,
vp, 1024f, 768f);
Assert.Equal(cell.WorldTransform * vp, ctx.ObjectToClip(cell));
Assert.Equal(Vector3.Zero, ctx.ViewpointIn(cell));
}
[Fact]
public void ViewpointInBuilding_ResolvesThroughWalkBuildingRegistry()
{
var registry = new WalkBuildingRegistry();
var building = new WalkBuilding { PositionCellId = 1 };
Matrix4x4 world = Matrix4x4.CreateTranslation(5f, 0f, 0f);
Matrix4x4.Invert(world, out Matrix4x4 inverse);
registry.Publish(0xA9B4FFFFu, new[] { new WalkBuildingFactory.Entry(building, world, inverse) });
var ctx = new WalkProductionFrameContext(
new CellVisibility(), registry, new Vector3(5f, 0f, 0f), Vector3.UnitY,
SimpleViewProjection(), 1024f, 768f);
Assert.Equal(Vector3.Zero, ctx.ViewpointInBuilding(building));
}
[Fact]
public void ViewpointInBuilding_ThrowsWhenTheBuildingIsNotCommitted()
{
// Fail loud (the PV3 post-mortem rule): a walk/registry desync must
// never resolve to a silently-skipped building.
var ctx = new WalkProductionFrameContext(
new CellVisibility(), new WalkBuildingRegistry(), Vector3.Zero, Vector3.UnitY,
SimpleViewProjection(), 1024f, 768f);
var unregistered = new WalkBuilding { PositionCellId = 1 };
Assert.Throws<InvalidOperationException>(() => ctx.ViewpointInBuilding(unregistered));
}
[Fact]
public void ViewerDistanceTo_MeasuresToTheBuildingsTransformedSortCenter()
{
var registry = new WalkBuildingRegistry();
var building = new WalkBuilding { PositionCellId = 1, SortCenter = new Vector3(0f, 3f, 0f) };
Matrix4x4 world = Matrix4x4.CreateTranslation(0f, 10f, 0f);
Matrix4x4.Invert(world, out Matrix4x4 inverse);
registry.Publish(0xA9B4FFFFu, new[] { new WalkBuildingFactory.Entry(building, world, inverse) });
var ctx = new WalkProductionFrameContext(
new CellVisibility(), registry, Vector3.Zero, Vector3.UnitY,
SimpleViewProjection(), 1024f, 768f);
Assert.Equal(13f, ctx.ViewerDistanceTo(building));
}
[Fact]
public void CyPlane_MatchesTheRetailNearPlaneFormula()
{
Vector3 forward = Vector3.UnitY;
var eye = new Vector3(0f, 5f, 0f);
var ctx = new WalkProductionFrameContext(
new CellVisibility(), new WalkBuildingRegistry(), eye, forward,
SimpleViewProjection(), 1024f, 768f);
Assert.Equal(forward, ctx.CyPlane.Normal);
Assert.Equal(-Vector3.Dot(eye, forward) - WalkProductionFrameContext.ZNear, ctx.CyPlane.D);
}
[Fact]
public void Constructor_RejectsANonInvertibleViewProjection()
{
Assert.Throws<ArgumentException>(() => new WalkProductionFrameContext(
new CellVisibility(), new WalkBuildingRegistry(), Vector3.Zero, Vector3.UnitY,
default, 1024f, 768f));
}
}

View file

@ -0,0 +1,321 @@
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 void Emit(in WalkEvent walkEvent) => Events.Add(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}");
}
[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));
}
}
}

View file

@ -89,6 +89,74 @@ public sealed class LandblockBuildFactoryTests
Assert.Empty(result.Collisions.EnvCells);
}
[Fact]
public void BuildNear_PopulatesWalkZSlabUnconditionallyAndWalkBuildingsFromLandBlockInfo()
{
// Campaign FW3.1: EnvCellLandblockBuild.WalkMaxZ/WalkMinZ/WalkBuildings
// must be populated by the production streaming build — hermetic
// (no installed DAT needed), mirroring the RecordingDatProxy fixture
// pattern the rest of this file already uses.
var dat = CreateDat(out RecordingDatProxy proxy);
AddNearFixture(proxy, LandblockId, environmentId: 1);
var heights = new byte[81];
Array.Fill(heights, (byte)10);
heights[0] = 200; // one outlier byte -> a distinct maxByte from the rest.
proxy.Add(LandblockId, new LandBlock { Id = LandblockId, Height = heights });
proxy.Add(
(LandblockId & 0xFFFF0000u) | 0xFFFEu,
new LandBlockInfo
{
NumCells = 1,
Buildings = new List<BuildingInfo>
{
new BuildingInfo
{
ModelId = 0x01234567u,
Frame = new Frame
{
Origin = new Vector3(12f, 12f, 0f),
Orientation = Quaternion.Identity,
},
Portals = new List<BuildingPortal>(),
},
},
});
var heightTable = new float[256];
heightTable[10] = 5f;
heightTable[200] = 40f;
var factory = new LandblockBuildFactory(
dat, TestPreparedCollisionSource.Instance, new object(), heightTable);
LandblockBuild? result = factory.Build(Request(LandblockStreamJobKind.LoadNear));
Assert.NotNull(result);
var envCells = Assert.IsType<AcDream.App.Rendering.Wb.EnvCellLandblockBuild>(
result.EnvCells);
Assert.Equal(240f, envCells.WalkMaxZ); // heightTable[200] + 200
Assert.Equal(4f, envCells.WalkMinZ); // heightTable[10] - 1
AcDream.App.Rendering.Walk.WalkBuildingFactory.Entry buildingEntry =
Assert.Single(envCells.WalkBuildings);
// origin (12,12) -> cellX=cellY=0 -> low word 0*8+0+1 = 1.
Assert.Equal((LandblockId & 0xFFFF0000u) | 1u, buildingEntry.Building.PositionCellId);
}
[Fact]
public void BuildFar_NeverPopulatesWalkDataBecauseEnvCellsIsNull()
{
// Documents the known FW3.1 scope gap: far-tier (LoadFar) landblocks
// carry no EnvCellLandblockBuild transaction at all (terrain-only),
// so their z-slab/buildings never reach WalkLandscapeAssembler until
// a follow-up wires the far-tier path too.
var dat = CreateDat(out RecordingDatProxy proxy);
proxy.Add(LandblockId, new LandBlock { Id = LandblockId });
var factory = Factory(dat, new object());
LandblockBuild? result = factory.Build(Request(LandblockStreamJobKind.LoadFar));
Assert.NotNull(result);
Assert.Null(result.EnvCells);
}
[Fact]
public void NearPreparedFaultRejectsWholeGenerationWhileFarNeverReadsCollision()
{