parent
e0ba3e7f2c
commit
3ebb120dd2
20 changed files with 700 additions and 100 deletions
|
|
@ -75,8 +75,19 @@ public sealed class WalkLandscapeAssembler
|
|||
RefreshSlotIfWindowed(bx, by);
|
||||
}
|
||||
|
||||
/// <summary>Near-tier retirement retains terrain and its authored bounds.
|
||||
/// A late building cleanup must not recreate a fully retired block.</summary>
|
||||
public void ClearBuildings(uint landblockId)
|
||||
{
|
||||
(int bx, int by) = BlockCoords(landblockId);
|
||||
if (!_blocks.TryGetValue((bx, by), out BlockData? data))
|
||||
return;
|
||||
data.Buildings = Array.Empty<WalkBuildingFactory.Entry>();
|
||||
RefreshSlotIfWindowed(bx, by);
|
||||
}
|
||||
|
||||
/// <summary>Landblock retirement (<c>LandblockRenderPublisher
|
||||
/// .RemoveBuildingRegistry</c>): drop this landblock's data. Safe to call
|
||||
/// .RemoveTerrain</c>): drop this landblock's data. Safe to call
|
||||
/// on a landblock that never published (no-op).</summary>
|
||||
public void RetireLandblock(uint landblockId)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ using AcDream.Core.World;
|
|||
|
||||
namespace AcDream.App.Streaming;
|
||||
|
||||
/// <summary>Authored terrain z-slab, independent of near-tier cell/building data.</summary>
|
||||
public readonly record struct LandblockTerrainBounds(float MaxZ, float MinZ);
|
||||
|
||||
/// <summary>
|
||||
/// Complete CPU-side output of one streaming job. The worker owns this object
|
||||
/// until it posts the corresponding completion; the render thread then applies
|
||||
|
|
@ -12,7 +15,8 @@ public sealed record LandblockBuild(
|
|||
LoadedLandblock Landblock,
|
||||
EnvCellLandblockBuild? EnvCells = null,
|
||||
LandblockBuildOrigin Origin = default,
|
||||
LandblockCollisionBuild? Collisions = null)
|
||||
LandblockCollisionBuild? Collisions = null,
|
||||
LandblockTerrainBounds TerrainBounds = default)
|
||||
{
|
||||
public uint LandblockId => Landblock.LandblockId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,13 +132,15 @@ public sealed class LandblockBuildFactory
|
|||
{
|
||||
var heightmapOnly = _dats.Get<DatReaderWriter.DBObjs.LandBlock>(landblockId);
|
||||
if (heightmapOnly is null) return null;
|
||||
(float maxZ, float minZ) = ComputeWalkZSlab(heightmapOnly.Height);
|
||||
return new AcDream.App.Streaming.LandblockBuild(
|
||||
new AcDream.Core.World.LoadedLandblock(
|
||||
landblockId,
|
||||
heightmapOnly,
|
||||
System.Array.Empty<AcDream.Core.World.WorldEntity>(),
|
||||
AcDream.Core.World.PhysicsDatBundle.Empty),
|
||||
Origin: request.Origin); // far tier: no cells/buildings/entities
|
||||
Origin: request.Origin,
|
||||
TerrainBounds: new(maxZ, minZ)); // far tier: no cells/buildings/entities
|
||||
}
|
||||
|
||||
var baseLoaded = AcDream.Core.World.LandblockLoader.Load(_dats, landblockId);
|
||||
|
|
@ -179,7 +181,7 @@ public sealed class LandblockBuildFactory
|
|||
_heightTable));
|
||||
var envCellBuild = new AcDream.App.Rendering.Wb.EnvCellLandblockBuildBuilder(landblockId);
|
||||
// Campaign FW3.1: the walk landscape's per-block z-slab
|
||||
// (CLandBlock::calc_lighting-adjacent unpack @0x0052f1d0). Every
|
||||
// (CLandBlock::get_land_limits @0x0052f1d0). Every
|
||||
// near-tier landblock has outdoor terrain heights regardless of
|
||||
// whether it has interior cells or buildings, so this is
|
||||
// unconditional — unlike WalkBuildings (added below, inside
|
||||
|
|
@ -208,7 +210,8 @@ public sealed class LandblockBuildFactory
|
|||
merged,
|
||||
physicsDats),
|
||||
completedEnvCells,
|
||||
request.Origin);
|
||||
request.Origin,
|
||||
TerrainBounds: new(walkMaxZ, walkMinZ));
|
||||
}
|
||||
/// <summary>
|
||||
/// Phase A.1 Task 8: generate scenery (trees, rocks, bushes) for a single
|
||||
|
|
@ -673,7 +676,7 @@ public sealed class LandblockBuildFactory
|
|||
|
||||
/// <summary>Campaign FW3.1: retail's per-block z-slab
|
||||
/// (<c>WalkLandscapeDatBuilder</c>'s port target —
|
||||
/// <c>CLandBlock::calc_lighting</c>-adjacent unpack @0x0052f1d0):
|
||||
/// <c>CLandBlock::get_land_limits</c> @0x0052f1d0):
|
||||
/// <c>max_zval = heightTable[maxByte] + 200</c>,
|
||||
/// <c>min_zval = heightTable[minByte] - 1</c>, over the landblock's own
|
||||
/// 81-byte heightmap. Uses <see cref="_heightTable"/> — already a
|
||||
|
|
|
|||
|
|
@ -479,18 +479,18 @@ public sealed class LandblockPresentationPipeline
|
|||
completed.Heightmap,
|
||||
Array.Empty<WorldEntity>(),
|
||||
PhysicsDatBundle.Empty);
|
||||
LandblockBuild farBuild = new(
|
||||
farLandblock,
|
||||
Origin: completedBuild.Origin,
|
||||
TerrainBounds: completedBuild.TerrainBounds);
|
||||
transaction = new PublicationTransaction
|
||||
{
|
||||
Kind = PublicationKind.Far,
|
||||
Build = new LandblockBuild(
|
||||
farLandblock,
|
||||
Origin: completedBuild.Origin),
|
||||
Build = farBuild,
|
||||
MeshData = meshData,
|
||||
LandblockId = farLandblock.LandblockId,
|
||||
Cost = LandblockStreamResultCost.Estimate(
|
||||
new LandblockBuild(
|
||||
farLandblock,
|
||||
Origin: completedBuild.Origin),
|
||||
farBuild,
|
||||
meshData),
|
||||
Tier = LandblockStreamTier.Far,
|
||||
Timing = PublicationTimingProbe.CreateTimings(),
|
||||
|
|
@ -534,7 +534,8 @@ public sealed class LandblockPresentationPipeline
|
|||
PhysicsDatBundle.Empty);
|
||||
LandblockBuild farBuild = new(
|
||||
farLandblock,
|
||||
Origin: completedBuild.Origin);
|
||||
Origin: completedBuild.Origin,
|
||||
TerrainBounds: completedBuild.TerrainBounds);
|
||||
transaction = new PublicationTransaction
|
||||
{
|
||||
Kind = PublicationKind.Far,
|
||||
|
|
|
|||
|
|
@ -103,15 +103,9 @@ public sealed class LandblockRenderPublisher
|
|||
private readonly Action<EnvCellLandblockBuild>? _prepareEnvCells;
|
||||
private readonly Action<uint>? _removeEnvCells;
|
||||
private readonly Dictionary<uint, BuildingRegistry> _buildingRegistries = new();
|
||||
// Campaign FW3.1: the walk's production world-data siblings of
|
||||
// _buildingRegistries. Owned here (not injected) exactly like
|
||||
// _buildingRegistries — no caller constructs a LandblockRenderPublisher
|
||||
// with pre-existing walk state. Committed/retired at the same points as
|
||||
// the building registry (see AdvanceCompleteOne / RemoveBuildingRegistry
|
||||
// below) because both come from the same landblock's BuildingInfo array
|
||||
// at the same commit. No frame reads these yet (FW3.2 wires the walk
|
||||
// into the render loop) — publication happens now so FW3.1's
|
||||
// conformance gate exercises the production commit path.
|
||||
// The frame walk's sole terrain/building owners. Both publish in the
|
||||
// retained render suffix; near-layer retirement clears only buildings,
|
||||
// while full terrain retirement removes the landscape entry as well.
|
||||
private readonly WalkBuildingRegistry _walkBuildingRegistry = new();
|
||||
private readonly WalkLandscapeAssembler _walkLandscape = new();
|
||||
|
||||
|
|
@ -360,21 +354,19 @@ public sealed class LandblockRenderPublisher
|
|||
_buildingRegistries[registryKey] =
|
||||
completedBuildings.Registry;
|
||||
}
|
||||
// Campaign FW3.1: publish this landblock's walk world data in
|
||||
// the same step — build.EnvCells carries WalkBuildings/WalkMaxZ
|
||||
// /WalkMinZ (computed worker-side by LandblockBuildFactory) for
|
||||
// every near-tier build, even one with zero interior cells
|
||||
// (the z-slab is unconditional; WalkBuildings is naturally empty
|
||||
// when LandBlockInfo has none).
|
||||
// Terrain exists at both tiers; only near builds supply buildings.
|
||||
// Keep both publications in this retained receipt's existing step.
|
||||
if (build.EnvCells is { } walkEnvCells)
|
||||
{
|
||||
_walkBuildingRegistry.Publish(landblockId, walkEnvCells.WalkBuildings);
|
||||
_walkLandscape.PublishLandblock(
|
||||
landblockId,
|
||||
walkEnvCells.WalkMaxZ,
|
||||
walkEnvCells.WalkMinZ,
|
||||
walkEnvCells.WalkBuildings);
|
||||
}
|
||||
else
|
||||
_walkBuildingRegistry.Retire(landblockId);
|
||||
_walkLandscape.PublishLandblock(
|
||||
landblockId,
|
||||
build.TerrainBounds.MaxZ,
|
||||
build.TerrainBounds.MinZ,
|
||||
build.EnvCells is { } cells
|
||||
? cells.WalkBuildings
|
||||
: Array.Empty<WalkBuildingFactory.Entry>());
|
||||
publication.BuildingRegistryCommitted = true;
|
||||
}
|
||||
else if (publication.EnvCellPublication is { } envCellPublication
|
||||
|
|
@ -421,6 +413,7 @@ public sealed class LandblockRenderPublisher
|
|||
public void RemoveTerrain(uint landblockId)
|
||||
{
|
||||
_removeTerrain(landblockId);
|
||||
_walkLandscape.RetireLandblock(landblockId);
|
||||
_terrainRemovalCount++;
|
||||
}
|
||||
|
||||
|
|
@ -433,14 +426,9 @@ public sealed class LandblockRenderPublisher
|
|||
public void RemoveBuildingRegistry(uint landblockId)
|
||||
{
|
||||
_buildingRegistries.Remove(landblockId & 0xFFFF0000u);
|
||||
// Campaign FW3.1: retire the walk's siblings at the SAME retirement
|
||||
// stage — they were committed together above, so they retire
|
||||
// together (no new LandblockRetirementStage; folding into the
|
||||
// existing BuildingRegistry stage keeps the retirement ticket state
|
||||
// machine unchanged, which is the minimal/additive choice for a
|
||||
// slice that does no frame wiring yet).
|
||||
// Demotion retires buildings, not the retained far-tier terrain.
|
||||
_walkBuildingRegistry.Retire(landblockId);
|
||||
_walkLandscape.RetireLandblock(landblockId);
|
||||
_walkLandscape.ClearBuildings(landblockId);
|
||||
_buildingRegistryRemovalCount++;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -615,7 +615,10 @@ public sealed class LandblockStreamer : IDisposable, ILandblockCompletionSource
|
|||
lb.Heightmap,
|
||||
System.Array.Empty<AcDream.Core.World.WorldEntity>(),
|
||||
PhysicsDatBundle.Empty);
|
||||
build = new LandblockBuild(lb, Origin: build.Origin);
|
||||
build = new LandblockBuild(
|
||||
lb,
|
||||
Origin: build.Origin,
|
||||
TerrainBounds: build.TerrainBounds);
|
||||
}
|
||||
PublishResult(new LandblockStreamResult.Loaded(
|
||||
load.LandblockId, tier, build, mesh, load.Generation));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue