wip(assembler): round S2 (Sol) -- shared per-tile feature queries for every Core family; island placement 30x faster

Island-only worker context replaced by a shared worker-local candidate domain
(WorldAssemblyFeatureTileListV1 / ScopedWorldAssemblyFeatureQueriesV1) serving
SurfaceWorld, FloatingIslands and far-shell queries; a per-domain cost hint keeps the
full scan on small plans. Content placement prepares the same queries and skips proven
air: owner island cell p95 7.07 -> 0.24 ms; populated fixture 572 exact placements,
3.27 s -> 29 ms. Full/candidate hashes equal with dropped-feature controls on both
families; S1 default W1/W8/W16 hashes reproduced; Core-off and M2 fixtures pass.
Default contract for future families in ARCHITECTURE 8.23. Run on gpt-6.1-sol medium.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-18 19:50:45 +02:00
1 parent d6e69420e8
commit 6ddb834b1c
19 files changed
+1849 -132

No files matched your search

+210 -10
View File
@@ -133,6 +133,7 @@ struct FCoreFloatingIslandFieldSnapshot
VoxelForgeCore::FloatingIslandRecipeV1 Recipe;
VoxelForgeCore::FloatingIslandPlanV1 Plan;
TUniquePtr<VoxelForgeCore::FloatingIslandFieldV1> Field;
std::vector<VoxelForgeCore::WorldAssemblyFeatureBoundsV1> SolidSupportBounds;
};
struct FCoreFloatingIslandSnapshot
@@ -231,6 +232,49 @@ struct FCoreFloatingIslandSnapshot
}
};
// Shared consumer preparation; snapshots own all pointers for the entire worker lifetime.
// Preparation partagee : les snapshots gardent les pointeurs vivants durant toute la tache.
static std::vector<VoxelForgeCore::WorldAssemblyFeatureQueryV1> VF_ContentFeatureQueries(
const FCoreSurfaceSnapshot* Surface, const FCoreFloatingIslandSnapshot* Islands,
int32 StrateIndex, float MinX, float MinY, float MinZ, float MaxX, float MaxY, float MaxZ)
{
using namespace VoxelForgeCore;
constexpr double Q = WorldAssemblySubvoxelsPerVoxel;
const std::array<int64,3> Min{static_cast<int64>(std::floor(MinX*Q)),
static_cast<int64>(std::floor(MinY*Q)),static_cast<int64>(std::floor(MinZ*Q))};
const std::array<int64,3> Max{static_cast<int64>(std::ceil(MaxX*Q)),
static_cast<int64>(std::ceil(MaxY*Q)),static_cast<int64>(std::ceil(MaxZ*Q))};
std::vector<WorldAssemblyFeatureQueryV1> Queries;
if (Surface)
if (const auto* Plan = Surface->FindPlan(StrateIndex))
{
const Heightfield2D Field(Surface->Recipe,*Plan);
Queries.push_back({Field.recipe(),Field.plan(),Field.buildFeatureTileList(Min,Max)});
}
if (Islands)
if (const auto* Entry = Islands->FindField(StrateIndex))
Queries.push_back({&Entry->Recipe,&Entry->Plan,Entry->Field->buildFeatureTileList(Min,Max)});
return Queries;
}
// Preserve the original march lattice and crossing refinement, skipping only certified air.
// Garder la phase du pas et le raffinement d'origine; sauter uniquement l'air prouve.
static bool VF_ConstrainContentMarch(std::span<const VoxelForgeCore::WorldAssemblyFeatureBoundsV1> Bounds,
float X, float Y, float Step, float& Top, float& Bottom)
{
constexpr double Q = VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
const auto Range = VoxelForgeCore::BuildWorldAssemblyFeatureColumnRangeV1(Bounds,
static_cast<int64>(std::llround(X*Q)),static_cast<int64>(std::llround(Y*Q)));
if (!Range.known) return true;
if (Range.empty) return false;
const float SupportTop = static_cast<float>(Range.maximumZSubvoxels/Q);
const float SupportBottom = static_cast<float>(Range.minimumZSubvoxels/Q);
const float SkipSteps = FMath::Max(0.0f,FMath::FloorToFloat((Top-SupportTop)/Step)-1.0f);
Top -= SkipSteps*Step;
Bottom = FMath::Max(Bottom,SupportBottom-Step);
return Top >= Bottom;
}
// Global safety cap on spawned decoration ACTORS per cell (across all entries).
// HISM instances are exempt — they're batched render data, capped per entry by MaxPerChunk.
static constexpr int32 GMaxDecorationActorsPerCell = 400;
@@ -330,6 +374,7 @@ void UVoxelContentManager::Initialize(AActor* InOwner, UVoxelStrateManager* InSt
IslandPlan.strateIndex);
continue;
}
Entry->SolidSupportBounds = Entry->Field->solidSupportBounds();
IslandSnapshot->Fields.Add(IslandPlan.strateIndex, Entry);
}
if (!IslandSnapshot->Fields.IsEmpty())
@@ -799,7 +844,7 @@ bool UVoxelContentManager::MeasurePlacementCostForValidation(
const int32 Step = FMath::Max(1, Settings->DecorationMarchStepVoxels);
const int32 MaxCrossings = FMath::Max(1, Settings->DecorationMaxCrossingsPerColumn);
const float ColumnDepth = static_cast<float>(FMath::Max(8, Settings->DecorationColumnDepthVoxels));
TArray<double> CellMilliseconds;
TArray<double> CellMilliseconds, FullScanCellMilliseconds;
CellMilliseconds.Reserve(SampleCellCount);
int64 TotalColumns = 0;
int64 TotalDensityQueries = 0;
@@ -810,7 +855,7 @@ bool UVoxelContentManager::MeasurePlacementCostForValidation(
const int32 X = Sample % GridSide - GridSide / 2;
const int32 Y = Sample / GridSide - GridSide / 2;
const FIntPoint Cell(PlayerCell.X + X, PlayerCell.Y + Y);
const double CellStart = FPlatformTime::Seconds();
double CellElapsed = 0.0, FullScanCellElapsed = 0.0;
for (const auto& GridAudit : {
std::make_pair(&NearGrid, &NearEntriesAudit),
std::make_pair(&FarGrid, &FarEntriesAudit)})
@@ -822,9 +867,26 @@ bool UVoxelContentManager::MeasurePlacementCostForValidation(
if (Entries.IsEmpty()) continue;
TArray<FDecoSpawn> Spawns;
FPlacementBuildCounters Counters;
TArray<FDecoSpawn> FullScanSpawns;
const double FullStart = FPlatformTime::Seconds();
BuildCellSpawns(Generator, OwnerXf, Cell, CurrentCtx, Entries, EntryBiome,
static_cast<uint32>(Seed), Grid.Spacing, static_cast<float>(Step),
MaxCrossings, ColumnDepth, FullScanSpawns, nullptr, false);
FullScanCellElapsed += (FPlatformTime::Seconds()-FullStart)*1000.0;
const double CandidateStart = FPlatformTime::Seconds();
BuildCellSpawns(Generator, OwnerXf, Cell, CurrentCtx, Entries, EntryBiome,
static_cast<uint32>(Seed), Grid.Spacing, static_cast<float>(Step),
MaxCrossings, ColumnDepth, Spawns, &Counters);
CellElapsed += (FPlatformTime::Seconds()-CandidateStart)*1000.0;
if (Spawns.Num()!=FullScanSpawns.Num())
{ OutError=TEXT("Feature-query placement count changed from full scan."); return false; }
for (int32 Index=0;Index<Spawns.Num();++Index)
{
if (Spawns[Index].EntryIdx!=FullScanSpawns[Index].EntryIdx
|| !Spawns[Index].Xf.Equals(FullScanSpawns[Index].Xf,0.0f))
{ OutError=TEXT("Feature-query placement transform changed from full scan."); return false; }
++OutAudit.ExactPlacementComparisons;
}
TotalColumns += Counters.Columns;
TotalDensityQueries += Counters.DensityQueries;
TotalSurfaceQueries += Counters.SurfaceQueries;
@@ -875,7 +937,10 @@ bool UVoxelContentManager::MeasurePlacementCostForValidation(
}
}
}
CellMilliseconds.Add((FPlatformTime::Seconds() - CellStart) * 1000.0);
CellMilliseconds.Add(CellElapsed);
FullScanCellMilliseconds.Add(FullScanCellElapsed);
OutAudit.ActiveBatchMilliseconds += CellElapsed;
OutAudit.FullScanActiveBatchMilliseconds += FullScanCellElapsed;
}
auto MedianAndP95 = [](TArray<double> Samples, double& OutMedian, double& OutP95)
@@ -899,6 +964,117 @@ bool UVoxelContentManager::MeasurePlacementCostForValidation(
MedianAndP95(MoveTemp(CellMilliseconds), OutAudit.CellMedianMilliseconds,
OutAudit.CellP95Milliseconds);
double UnusedMedian = 0.0;
MedianAndP95(MoveTemp(FullScanCellMilliseconds),UnusedMedian,OutAudit.FullScanCellP95Milliseconds);
// Deliberately populated fixture alongside the unmodified authored palette. No actors are spawned.
// Fixture volontairement peuplee, a cote de la palette auteur intacte. Aucun acteur cree.
UStaticMesh* FixtureMesh=nullptr;
for (const auto* Entries : {&NearEntriesAudit,&FarEntriesAudit})
for (const auto& Entry : *Entries)
if (!FixtureMesh && Entry.Profile.InstancedMesh) FixtureMesh=Entry.Profile.InstancedMesh;
if (!FixtureMesh)
FixtureMesh=LoadObject<UStaticMesh>(nullptr,TEXT("/Engine/BasicShapes/Cube.Cube"));
TMap<FIntPoint,FDecoRegionBuild> FixtureBuilds;
TArray<FStrateDecoration> FixtureEntries;
TArray<int32> FixtureBiomes;
const int32 BiomeCount = CurrentCtx.BiomeCtx.IsValid() ? CurrentCtx.BiomeCtx.Biomes.Num() : 0;
for (int32 Biome=0;Biome<FMath::Max(1,BiomeCount);++Biome)
for (int32 Wet=0;Wet<2;++Wet)
{
FStrateDecoration Entry;
Entry.Profile.ActorClass=AActor::StaticClass();
Entry.Profile.InstancedMesh=FixtureMesh;
Entry.Profile.SurfacePlacement=ESurfaceType::Floor;
Entry.Profile.bPlaceBelowWater=Wet!=0;
Entry.SpawnDensity=1.0f;
Entry.MaxPerChunk=64;
FixtureEntries.Add(Entry);
FixtureBiomes.Add(BiomeCount ? Biome : -1);
}
TArray<double> PopulatedTimes, FullPopulatedTimes;
for (int32 Sample=0;Sample<SampleCellCount;++Sample)
{
const FIntPoint Cell(PlayerCell.X+Sample%GridSide-GridSide/2,
PlayerCell.Y+Sample/GridSide-GridSide/2);
TArray<FDecoSpawn> FullSpawns, Spawns;
const double FullStart=FPlatformTime::Seconds();
BuildCellSpawns(Generator,OwnerXf,Cell,CurrentCtx,FixtureEntries,FixtureBiomes,
static_cast<uint32>(Seed),4,static_cast<float>(Step),MaxCrossings,ColumnDepth,
FullSpawns,nullptr,false);
const double FullElapsed=(FPlatformTime::Seconds()-FullStart)*1000.0;
const double CandidateStart=FPlatformTime::Seconds();
BuildCellSpawns(Generator,OwnerXf,Cell,CurrentCtx,FixtureEntries,FixtureBiomes,
static_cast<uint32>(Seed),4,static_cast<float>(Step),MaxCrossings,ColumnDepth,Spawns);
const double Elapsed=(FPlatformTime::Seconds()-CandidateStart)*1000.0;
OutAudit.PopulatedBatchMilliseconds+=Elapsed;
OutAudit.FullScanPopulatedBatchMilliseconds+=FullElapsed;
if (Spawns.Num()!=FullSpawns.Num())
{ OutError=TEXT("Populated fixture count differs from full scan."); return false; }
const double MergeStart=FPlatformTime::Seconds();
if (FixtureMesh && !Spawns.IsEmpty())
{
auto& Build=FixtureBuilds.FindOrAdd(CellToRegion(Cell,RegionSize()));
auto& Bucket=Build.MeshBuckets.FindOrAdd(FixtureMesh);
for (const auto& Spawn : Spawns)
{
Bucket.Profile=FixtureEntries[Spawn.EntryIdx].Profile;
Bucket.Xforms.Add(Spawn.Xf);
}
}
OutAudit.PopulatedMergeMilliseconds+=(FPlatformTime::Seconds()-MergeStart)*1000.0;
for (int32 Index=0;Index<Spawns.Num();++Index)
{
if (!Spawns[Index].Xf.Equals(FullSpawns[Index].Xf,0.0f))
{ OutError=TEXT("Populated fixture anchor differs from full scan."); return false; }
++OutAudit.ExactPlacementComparisons;
const FVector Local=OwnerXf.InverseTransformPosition(Spawns[Index].Xf.GetLocation());
float Density=0.0f;
if (CurrentCtx.bNativeFloatingIslands && CurrentCtx.NativeIslands.IsValid()
&& CurrentCtx.NativeIslands->GetDensity(CurrentCtx.StrateIndex,
Local.X/CurrentCtx.VoxelSizeCm,Local.Y/CurrentCtx.VoxelSizeCm,
Local.Z/CurrentCtx.VoxelSizeCm,Density))
{
OutAudit.MaximumDensityResidual=FMath::Max(OutAudit.MaximumDensityResidual,
FMath::Abs(static_cast<double>(Density)));
++OutAudit.AnchorChecks;
if (FMath::Abs(Density)>0.25f)
{ OutError=TEXT("Populated fixture is not anchored on the canonical field."); return false; }
}
}
if (!Spawns.IsEmpty())
{
++OutAudit.PopulatedCells;
OutAudit.PopulatedPlacements+=Spawns.Num();
PopulatedTimes.Add(Elapsed);
FullPopulatedTimes.Add(FullElapsed);
}
}
if ((CurrentCtx.bNativeSurface||CurrentCtx.bNativeFloatingIslands) && OutAudit.PopulatedPlacements==0)
{ OutError=TEXT("Populated content fixture produced no placements."); return false; }
if (FixtureMesh && OutAudit.PopulatedPlacements>0)
{
// Exercise actual region application, then close this isolated fixture immediately.
// Appliquer une vraie region puis detruire immediatement cette fixture isolee.
FDecoGrid FixtureGrid;
const double ApplyStart=FPlatformTime::Seconds();
// Fixture footprints are isolated from live landmark suppression. / Isoler les empreintes de fixture.
for (auto& Pair : FixtureBuilds) ApplyRegion(FixtureGrid,Pair.Key,Pair.Value,false);
OutAudit.PopulatedApplyMilliseconds=(FPlatformTime::Seconds()-ApplyStart)*1000.0;
for (const auto& Pair : FixtureGrid.Regions)
for (const auto& WeakHISM : Pair.Value.Instances)
if (const auto* HISM=WeakHISM.Get()) OutAudit.AppliedInstances+=HISM->GetInstanceCount();
for (const auto& Pair : FixtureBuilds) ClearDecorationRegion(FixtureGrid,Pair.Key);
if (OutAudit.AppliedInstances!=OutAudit.PopulatedPlacements)
{
OutError=FString::Printf(TEXT("Populated HISM fixture applied %d of %d placements (%d regions, mesh %s)."),
OutAudit.AppliedInstances,OutAudit.PopulatedPlacements,FixtureBuilds.Num(),*FixtureMesh->GetPathName());
return false;
}
}
MedianAndP95(MoveTemp(PopulatedTimes),UnusedMedian,OutAudit.PopulatedCellP95Milliseconds);
MedianAndP95(MoveTemp(FullPopulatedTimes),UnusedMedian,OutAudit.FullScanPopulatedCellP95Milliseconds);
// The game-thread update cost is sampled with task dispatch disabled; worker-cell cost is reported
// separately above, so the frame figure is not polluted by waiting for asynchronous work.
UpdateDecorations(PlayerWorldPos);
@@ -1092,7 +1268,7 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
const TArray<int32>& EntryBiome, uint32 InSeed,
int32 Spacing, float Step, int32 MaxCrossings, float ColumnDepth,
TArray<FDecoSpawn>& OutSpawns,
FPlacementBuildCounters* OutCounters)
FPlacementBuildCounters* OutCounters, bool bUseFeatureQueries)
{
if (OutCounters != nullptr) *OutCounters = FPlacementBuildCounters();
if (!Gen || Entries.Num() == 0) return;
@@ -1101,6 +1277,14 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
const int32 CellOriginVX = Cell.X * CHUNK_SIZE;
const int32 CellOriginVY = Cell.Y * CHUNK_SIZE;
const auto FeatureQueries = bUseFeatureQueries ? VF_ContentFeatureQueries(
Ctx.bNativeSurface ? Ctx.NativeSurface.Get() : nullptr,
Ctx.bNativeFloatingIslands ? Ctx.NativeIslands.Get() : nullptr,Ctx.StrateIndex,
CellOriginVX-1.0f,CellOriginVY-1.0f,Ctx.BottomVoxelZ-1.0f,
CellOriginVX+CHUNK_SIZE+1.0f,CellOriginVY+CHUNK_SIZE+1.0f,Ctx.TopVoxelZ+1.0f)
: std::vector<VoxelForgeCore::WorldAssemblyFeatureQueryV1>{};
const VoxelForgeCore::ScopedWorldAssemblyFeatureQueriesV1 FeatureScope(FeatureQueries);
TArray<int32> EntryCount; EntryCount.Init(0, Entries.Num());
int32 TotalActors = 0;
@@ -1437,12 +1621,17 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
}
// NON-SURFACE archetypes (caves/shafts/islands): ray-march the density column for crossings.
float PrevD = D(VX, VY, Ctx.TopVoxelZ);
float MarchTop = Ctx.TopVoxelZ, MarchBottom = Ctx.BottomVoxelZ;
if (bUseFeatureQueries && Ctx.bNativeFloatingIslands && Ctx.NativeIslands.IsValid())
if (const auto* Entry = Ctx.NativeIslands->FindField(Ctx.StrateIndex))
if (!VF_ConstrainContentMarch(Entry->SolidSupportBounds,VX,VY,Step,MarchTop,MarchBottom))
continue;
float PrevD = D(VX, VY, MarchTop);
int32 Crossings = 0;
bool bSeenAir = (PrevD >= 0.0f); // GetDensityAt >= 0 == air
float SolidRun = 0.0f; // contiguous solid voxels since the last open air
for (float Z = Ctx.TopVoxelZ - Step; Z >= Ctx.BottomVoxelZ && Crossings < MaxCrossings; Z -= Step)
for (float Z = MarchTop - Step; Z >= MarchBottom && Crossings < MaxCrossings; Z -= Step)
{
const float Dz = D(VX, VY, Z);
if ((PrevD >= 0.0f) != (Dz >= 0.0f)) // straddles IsoLevel 0 (air ↔ solid)
@@ -1615,7 +1804,8 @@ void UVoxelContentManager::MarkCellDone(FDecoGrid& G, const FIntPoint& Region, c
// Build the region's components: one HISM per mesh (all cells merged → one batched AddInstances), actors
// spawned inline. Moves the region into G.Regions; the build is removed by the caller.
void UVoxelContentManager::ApplyRegion(FDecoGrid& G, const FIntPoint& Region, FDecoRegionBuild& Build)
void UVoxelContentManager::ApplyRegion(FDecoGrid& G, const FIntPoint& Region, FDecoRegionBuild& Build,
bool bApplyLandmarkFootprints)
{
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_DecoApply); // total game-thread cost to apply one region
@@ -1714,7 +1904,7 @@ void UVoxelContentManager::ApplyRegion(FDecoGrid& G, const FIntPoint& Region, FD
// but this region just streamed in FRESH → re-clear under any loaded suppressing landmark overlapping it
// (the per-HISM spatial query early-outs for the non-overlapping majority). Keeps temple floors clear as
// you leave and return.
if (Content.Instances.Num() > 0)
if (bApplyLandmarkFootprints && Content.Instances.Num() > 0)
{
for (const TPair<FIntVector, FLandmarkInstance>& LP : LandmarkInstances)
{
@@ -1781,6 +1971,11 @@ bool UVoxelContentManager::FindLandmarkColumn(const UVoxelGenerator* Gen, const
{
if (!Gen) return false;
const auto FeatureQueries = VF_ContentFeatureQueries(
Ctx.bNativeSurface ? Ctx.NativeSurface.Get() : nullptr,
Ctx.bNativeFloatingIslands ? Ctx.NativeIslands.Get() : nullptr,Ctx.StrateIndex,
VX-1.0f,VY-1.0f,Ctx.BottomVoxelZ-1.0f,VX+1.0f,VY+1.0f,Ctx.TopVoxelZ+1.0f);
const VoxelForgeCore::ScopedWorldAssemblyFeatureQueriesV1 FeatureScope(FeatureQueries);
const auto Density = [&](float SampleX, float SampleY, float SampleZ)
{
if (Ctx.bNativeFloatingIslands && Ctx.NativeIslands.IsValid())
@@ -1852,10 +2047,15 @@ bool UVoxelContentManager::FindLandmarkColumn(const UVoxelGenerator* Gen, const
// Bounded by ColDepth like the decoration march (this runs SYNCHRONOUSLY on the game thread):
// once past open air, a solid run longer than ColDepth means bedrock down to the strate floor —
// stop instead of paying GetDensityAt across the whole remaining band.
float PrevD = Density(VX, VY, Ctx.TopVoxelZ);
float MarchTop = Ctx.TopVoxelZ, MarchBottom = Ctx.BottomVoxelZ;
if (Ctx.bNativeFloatingIslands && Ctx.NativeIslands.IsValid())
if (const auto* Entry = Ctx.NativeIslands->FindField(Ctx.StrateIndex))
if (!VF_ConstrainContentMarch(Entry->SolidSupportBounds,VX,VY,Step,MarchTop,MarchBottom))
return false;
float PrevD = Density(VX, VY, MarchTop);
bool bSeenAir = (PrevD >= 0.0f);
float SolidRun = 0.0f;
for (float Z = Ctx.TopVoxelZ - Step; Z >= Ctx.BottomVoxelZ; Z -= Step)
for (float Z = MarchTop - Step; Z >= MarchBottom; Z -= Step)
{
const float Dz = Density(VX, VY, Z);
if ((PrevD >= 0.0f) != (Dz >= 0.0f)) // air ↔ solid crossing
+16 -2
View File
@@ -137,6 +137,19 @@ public:
double MaximumDensityResidual = 0.0;
double DensityQueriesPerCell = 0.0;
double SurfaceQueriesPerCell = 0.0;
double FullScanCellP95Milliseconds = 0.0;
double ActiveBatchMilliseconds = 0.0;
double FullScanActiveBatchMilliseconds = 0.0;
double PopulatedBatchMilliseconds = 0.0;
double FullScanPopulatedBatchMilliseconds = 0.0;
double PopulatedMergeMilliseconds = 0.0;
double PopulatedApplyMilliseconds = 0.0;
int32 AppliedInstances = 0;
double PopulatedCellP95Milliseconds = 0.0;
double FullScanPopulatedCellP95Milliseconds = 0.0;
int32 PopulatedPlacements = 0;
int32 PopulatedCells = 0;
int32 ExactPlacementComparisons = 0;
double CellMedianMilliseconds = 0.0;
double CellP95Milliseconds = 0.0;
double UpdateMedianMilliseconds = 0.0;
@@ -338,7 +351,7 @@ private:
const TArray<int32>& EntryBiome, uint32 InSeed,
int32 Spacing, float Step, int32 MaxCrossings, float ColumnDepth,
TArray<FDecoSpawn>& OutSpawns,
FPlacementBuildCounters* OutCounters = nullptr);
FPlacementBuildCounters* OutCounters = nullptr, bool bUseFeatureQueries = true);
// Each step operates on ONE grid (G = NearGrid or FarGrid). LaunchDecoTasks throttles against the
// COMBINED in-flight count (OtherInFlight = the other grid's in-flight cells) so the two grids share
@@ -348,7 +361,8 @@ private:
void ProcessDecoResults(const FIntPoint& PlayerCell);
void MergeCellResult(FDecoGrid& G, const FDecoCellResult& Result); // fold one cell's spawns into its region build
void MarkCellDone(FDecoGrid& G, const FIntPoint& Region, const FIntPoint& Cell, uint32 BuildId); // idempotent per-cell accounting
void ApplyRegion(FDecoGrid& G, const FIntPoint& Region, FDecoRegionBuild& Build);
void ApplyRegion(FDecoGrid& G, const FIntPoint& Region, FDecoRegionBuild& Build,
bool bApplyLandmarkFootprints = true);
void RebuildDesiredCells(FDecoGrid& G, const FIntPoint& PlayerCell);
void ClearDecorationRegion(FDecoGrid& G, const FIntPoint& Region);
void ClearAllDecorations();