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:
parent
b95850defe
commit
b10ad662b0
14 changed files with 1711 additions and 2 deletions
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Walk;
|
||||
using AcDream.Core.Rendering.Wb;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Enums;
|
||||
|
|
@ -34,22 +35,48 @@ public sealed class EnvCellLandblockBuild
|
|||
public EnvCellLandblockBuild(
|
||||
uint landblockId,
|
||||
IEnumerable<LoadedCell> visibilityCells,
|
||||
IEnumerable<EnvCellShellPlacement> shells)
|
||||
IEnumerable<EnvCellShellPlacement> shells,
|
||||
IEnumerable<WalkBuildingFactory.Entry>? walkBuildings = null,
|
||||
float walkMaxZ = 0f,
|
||||
float walkMinZ = 0f)
|
||||
{
|
||||
LandblockId = landblockId;
|
||||
VisibilityCells = visibilityCells.ToImmutableArray();
|
||||
Shells = shells.ToImmutableArray();
|
||||
WalkBuildings = (walkBuildings ?? Enumerable.Empty<WalkBuildingFactory.Entry>()).ToImmutableArray();
|
||||
WalkMaxZ = walkMaxZ;
|
||||
WalkMinZ = walkMinZ;
|
||||
|
||||
uint expectedPrefix = landblockId & 0xFFFF0000u;
|
||||
if (VisibilityCells.Any(cell => (cell.CellId & 0xFFFF0000u) != expectedPrefix))
|
||||
throw new ArgumentException("A visibility cell belongs to a different landblock.", nameof(visibilityCells));
|
||||
if (Shells.Any(shell => (shell.CellId & 0xFFFF0000u) != expectedPrefix))
|
||||
throw new ArgumentException("A render shell belongs to a different landblock.", nameof(shells));
|
||||
if (WalkBuildings.Any(entry => (entry.Building.PositionCellId & 0xFFFF0000u) != expectedPrefix))
|
||||
throw new ArgumentException("A walk building belongs to a different landblock.", nameof(walkBuildings));
|
||||
}
|
||||
|
||||
public uint LandblockId { get; }
|
||||
public ImmutableArray<LoadedCell> VisibilityCells { get; }
|
||||
public ImmutableArray<EnvCellShellPlacement> Shells { get; }
|
||||
|
||||
/// <summary>Campaign FW3.1: this landblock's walk building placements
|
||||
/// (<c>WalkBuildingFactory.Build</c>, computed worker-side alongside
|
||||
/// <see cref="VisibilityCells"/>). Committed atomically with the rest of
|
||||
/// this transaction — see <c>WalkBuildingRegistry.Publish</c> at
|
||||
/// <c>LandblockRenderPublisher.AdvanceCompleteOne</c>.</summary>
|
||||
public ImmutableArray<WalkBuildingFactory.Entry> WalkBuildings { get; }
|
||||
|
||||
/// <summary>Campaign FW3.1: this landblock's retail z-slab
|
||||
/// (<c>heightTable[maxByte] + 200</c>) — the walk landscape's per-block
|
||||
/// visibility bound (<c>WalkLandscapeAssembler.PublishLandblock</c>).
|
||||
/// Computed once, worker-side, from the SAME heightmap bytes + height
|
||||
/// table the terrain mesh build already reads — no extra DAT access.</summary>
|
||||
public float WalkMaxZ { get; }
|
||||
|
||||
/// <summary>Campaign FW3.1: this landblock's retail z-slab
|
||||
/// (<c>heightTable[minByte] − 1</c>). See <see cref="WalkMaxZ"/>.</summary>
|
||||
public float WalkMinZ { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -62,6 +89,9 @@ public sealed class EnvCellLandblockBuildBuilder
|
|||
private readonly uint _landblockId;
|
||||
private readonly List<LoadedCell> _visibilityCells = new();
|
||||
private readonly List<EnvCellShellPlacement> _shells = new();
|
||||
private readonly List<WalkBuildingFactory.Entry> _walkBuildings = new();
|
||||
private float _walkMaxZ;
|
||||
private float _walkMinZ;
|
||||
private bool _built;
|
||||
|
||||
public EnvCellLandblockBuildBuilder(uint landblockId)
|
||||
|
|
@ -69,6 +99,31 @@ public sealed class EnvCellLandblockBuildBuilder
|
|||
_landblockId = landblockId;
|
||||
}
|
||||
|
||||
/// <summary>Campaign FW3.1: registers this landblock's walk building
|
||||
/// placements (<c>WalkBuildingFactory.Build</c>'s output). Additive to
|
||||
/// the existing cell/shell accumulation — called once per landblock from
|
||||
/// <c>LandblockBuildFactory.BuildInteriorEntitiesForStreaming</c>, where
|
||||
/// <c>LandBlockInfo</c> is already in hand.</summary>
|
||||
public void AddWalkBuildings(IEnumerable<WalkBuildingFactory.Entry> entries)
|
||||
{
|
||||
if (_built)
|
||||
throw new InvalidOperationException("This landblock cell build is already complete.");
|
||||
_walkBuildings.AddRange(entries);
|
||||
}
|
||||
|
||||
/// <summary>Campaign FW3.1: registers this landblock's retail z-slab
|
||||
/// (<see cref="EnvCellLandblockBuild.WalkMaxZ"/>/<see cref="EnvCellLandblockBuild.WalkMinZ"/>).
|
||||
/// Independent of interior-cell/building presence — every near-tier
|
||||
/// landblock has outdoor terrain heights, so
|
||||
/// <c>LandblockBuildFactory.BuildLocked</c> calls this unconditionally.</summary>
|
||||
public void SetWalkZSlab(float maxZ, float minZ)
|
||||
{
|
||||
if (_built)
|
||||
throw new InvalidOperationException("This landblock cell build is already complete.");
|
||||
_walkMaxZ = maxZ;
|
||||
_walkMinZ = minZ;
|
||||
}
|
||||
|
||||
public void AddCell(
|
||||
uint envCellId,
|
||||
EnvCell envCell,
|
||||
|
|
@ -116,7 +171,8 @@ public sealed class EnvCellLandblockBuildBuilder
|
|||
if (_built)
|
||||
throw new InvalidOperationException("This landblock cell build is already complete.");
|
||||
_built = true;
|
||||
return new EnvCellLandblockBuild(_landblockId, _visibilityCells, _shells);
|
||||
return new EnvCellLandblockBuild(
|
||||
_landblockId, _visibilityCells, _shells, _walkBuildings, _walkMaxZ, _walkMinZ);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -290,6 +346,20 @@ public sealed class EnvCellLandblockBuildBuilder
|
|||
PortalPolygons = portalPolygons,
|
||||
VisibleCells = visibleCells,
|
||||
SeenOutside = envCell.Flags.HasFlag(EnvCellFlags.SeenOutside),
|
||||
// Campaign FW3.1: the walk's model of this cell, built from the
|
||||
// SAME EnvCell/CellStruct/transform this LoadedCell was just
|
||||
// built from — WalkCellFactory.FromParsed does its own portal
|
||||
// pass (independent of the clipPlanes/portalPolygons above; the
|
||||
// two consumers decode the same dat fields for different needs)
|
||||
// so it stays a direct, auditable port of the FW1 test adapter
|
||||
// rather than reshaping LoadedCell's own list layout around it.
|
||||
// cellTransform/inverse here are UNLIFTED (this method's
|
||||
// cellOrigin/cellTransform params are the caller's
|
||||
// physicsCellOrigin/physicsCellTransform — see
|
||||
// LandblockBuildFactory.BuildInteriorEntitiesForStreaming, which
|
||||
// keeps the +0.02 m ShellDrawLiftZ out of this transform) —
|
||||
// exactly what the walk wants.
|
||||
Walk = WalkCellFactory.FromParsed(envCellId, envCell, cellStruct, cellTransform, inverse),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue