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>
75 lines
3.7 KiB
C#
75 lines
3.7 KiB
C#
using System.Diagnostics.CodeAnalysis;
|
|
|
|
namespace AcDream.App.Rendering.Walk;
|
|
|
|
/// <summary>
|
|
/// Campaign FW3.1 — the walk's per-landblock <see cref="WalkBuilding"/>
|
|
/// registry: the production sibling of
|
|
/// <see cref="AcDream.App.Rendering.Wb.BuildingRegistry"/>. Publish/retire
|
|
/// mirror that registry's landblock lifecycle exactly — both are committed
|
|
/// together in <c>LandblockRenderPublisher.AdvanceCompleteOne</c>'s
|
|
/// <c>BuildingRegistryCommitted</c> step and retired together from
|
|
/// <c>LandblockRenderPublisher.RemoveBuildingRegistry</c> — because a walk
|
|
/// building and its BFS-derived <c>Wb.Building</c> counterpart come from the
|
|
/// SAME <c>BuildingInfo</c> array at the SAME landblock commit.
|
|
///
|
|
/// The reverse index (<see cref="TryGetEntry"/>) exists because
|
|
/// <see cref="WalkProductionFrameContext"/> resolves a building's placement
|
|
/// by REFERENCE during the portal pass — <c>ViewpointInBuilding</c>,
|
|
/// <c>ViewerDistanceTo</c>, and <c>ClipBuildingPolygon</c> are called many
|
|
/// times per building per frame once FW3.2 wires the walk into the render
|
|
/// loop, so this must be O(1), not a per-landblock scan.
|
|
/// </summary>
|
|
public sealed class WalkBuildingRegistry
|
|
{
|
|
private readonly Dictionary<uint, IReadOnlyList<WalkBuildingFactory.Entry>> _byLandblock = new();
|
|
private readonly Dictionary<WalkBuilding, WalkBuildingFactory.Entry> _byBuilding = new();
|
|
|
|
/// <summary>Atomically replaces one landblock's complete building set.
|
|
/// Mirrors <c>Wb.BuildingRegistry</c>'s "no partial landblock" commit
|
|
/// discipline — <paramref name="entries"/> is the immutable, fully-built
|
|
/// list carried by the streaming worker's
|
|
/// <c>Wb.EnvCellLandblockBuild.WalkBuildings</c>.</summary>
|
|
public void Publish(uint landblockId, IReadOnlyList<WalkBuildingFactory.Entry> entries)
|
|
{
|
|
uint key = landblockId & 0xFFFF0000u;
|
|
if (_byLandblock.TryGetValue(key, out IReadOnlyList<WalkBuildingFactory.Entry>? previous))
|
|
{
|
|
foreach (WalkBuildingFactory.Entry entry in previous)
|
|
_byBuilding.Remove(entry.Building);
|
|
}
|
|
_byLandblock[key] = entries;
|
|
foreach (WalkBuildingFactory.Entry entry in entries)
|
|
_byBuilding[entry.Building] = entry;
|
|
}
|
|
|
|
/// <summary>Removes every building of one landblock. Safe to call on a
|
|
/// landblock that never published (no-op).</summary>
|
|
public void Retire(uint landblockId)
|
|
{
|
|
uint key = landblockId & 0xFFFF0000u;
|
|
if (_byLandblock.Remove(key, out IReadOnlyList<WalkBuildingFactory.Entry>? previous))
|
|
{
|
|
foreach (WalkBuildingFactory.Entry entry in previous)
|
|
_byBuilding.Remove(entry.Building);
|
|
}
|
|
}
|
|
|
|
/// <summary>The buildings of one landblock, or empty when none are
|
|
/// published (unloaded, far-tier, or no <c>BuildingInfo</c> entries).</summary>
|
|
public IReadOnlyList<WalkBuildingFactory.Entry> GetBuildings(uint landblockId) =>
|
|
_byLandblock.TryGetValue(landblockId & 0xFFFF0000u, out IReadOnlyList<WalkBuildingFactory.Entry>? list)
|
|
? list
|
|
: Array.Empty<WalkBuildingFactory.Entry>();
|
|
|
|
/// <summary>O(1) reverse lookup: this building's committed placement.
|
|
/// False means the building is not (or no longer) committed — the walk
|
|
/// must treat that as a hard desync, never a silent skip (the FW
|
|
/// fail-loud rule).</summary>
|
|
public bool TryGetEntry(
|
|
WalkBuilding building, [MaybeNullWhen(false)] out WalkBuildingFactory.Entry entry) =>
|
|
_byBuilding.TryGetValue(building, out entry);
|
|
|
|
/// <summary>Number of landblocks with committed buildings (diagnostics).</summary>
|
|
public int LandblockCount => _byLandblock.Count;
|
|
}
|