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

@ -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()
{