feat(composer): lateral REGIONS -- built, measured, and gated OFF
Jahni's own idea: "archetype could even mix within the same strate at some
point, tunnel network leading to a big chamber". An archetype becomes a
spatial REGION inside a strate, not a label on it.
Built by extending the mechanism that already exists rather than inventing
one: the stack blends VERTICALLY between strates, so regions blend the same
way LATERALLY. Deterministic jittered Voronoi sites on a 256-voxel lattice,
1-3 regions per strate from (seed, strateIndex), 24-voxel symmetric blend
band, per-chunk lookup cache. Parameters are never blended -- a shaft's
ShaftSpacing means nothing to a slab -- only the resulting DENSITY is.
Structural posts stay global and unconditional, so an invented region
cannot switch off the primordial law.
single-region path BIT-IDENTICAL to legacy, 256 probes, native + recipe
cross-boundary boxes 20 of 40, 199,800 voxels brute-forced, 0 VIOLATIONS
(every seam-spanning box correctly returned Mixed)
blend-band cost 663 ns vs 338 ns per voxel, over 16.0% of voxels
~1.15x field-weighted
survival 16/16 valid, non-vacuous, largest share >= .50
⛔ AND IT IS GATED OFF, because the seam breaks the one law that matters:
arrival -> departure across a region boundary passes 9/16 seeds, against
11/16 same-region controls in the same harness. Density blending alone does
not guarantee a player can cross a seam. A strate that mixes archetypes and
strands you at the boundary is strictly worse than one that does not mix.
VF_LateralRegionsAreShippable() returns false and the test ASSERTS THE GATE
IS SHUT while the rate is imperfect -- so flipping it without fixing
connectivity turns the suite red by design. The rate is a warning, not an
assertion: it is the measured state of an off-by-default feature, not a
regression in a live world, and a permanently red suite stops being read,
which is how real failures get missed.
Everything that ships is still hard-guarded: single-region bit-identity,
zero cross-boundary box violations, and every shipping-path law test
untouched.
The fix is an explicit CROSS-SEAM CORRIDOR CONTRACT -- a design decision,
recorded rather than made unilaterally. Codex diagnosed this correctly and
declined to tune the threshold, which was the right call.
Build clean, 29 succeeded / 0 failed, run and verified by me.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU
This commit is contained in:
1 parent
42e1f0ec8c
commit
dd38832bb6
14 files changed
+3040
-48
No files matched your search
@@ -19,6 +19,8 @@ struct FVoxelStrateComposerSlotOverride
|
||||
FVoxelStrateArchetypeParams Params;
|
||||
bool bUseRecipe = false;
|
||||
FVoxelOpStackRecipe Recipe;
|
||||
bool bUseRegions = false;
|
||||
FVoxelStrateRegionManifest Regions;
|
||||
};
|
||||
|
||||
static void VF_SetComposerRuntimeBounds(
|
||||
@@ -286,7 +288,8 @@ const FVoxelStrateComposerSlotOverride* UVoxelStrateManager::FindComposerOverrid
|
||||
bool UVoxelStrateManager::SetComposerOverrideForStrate(
|
||||
int32 StrateIndex, int32 CandidateSeed, ECaveGeneratorType Archetype,
|
||||
const FVoxelStrateArchetypeParams& Params, bool bUseRecipe,
|
||||
const FVoxelOpStackRecipe* Recipe, FString& OutError)
|
||||
const FVoxelOpStackRecipe* Recipe, FString& OutError,
|
||||
const FVoxelStrateRegionManifest* InRegions)
|
||||
{
|
||||
OutError.Reset();
|
||||
|
||||
@@ -342,6 +345,32 @@ bool UVoxelStrateManager::SetComposerOverrideForStrate(
|
||||
Override->Recipe = *Recipe;
|
||||
}
|
||||
|
||||
if (InRegions != nullptr && InRegions->RegionCount > 1)
|
||||
{
|
||||
if (!InRegions->IsValid())
|
||||
{
|
||||
OutError = InRegions->FailureReason.IsEmpty()
|
||||
? TEXT("The composer returned an invalid lateral region manifest.")
|
||||
: InRegions->FailureReason;
|
||||
return false;
|
||||
}
|
||||
Override->bUseRegions = true;
|
||||
Override->Regions = *InRegions;
|
||||
VF_RekeyStrateRegionManifest(Override->Regions, CandidateSeed, StrateIndex);
|
||||
Override->Regions.StrateTopWorldZ = (float)(TargetSlot->TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Override->Regions.StrateBottomWorldZ = (float)TargetSlot->BottomChunkZ * CHUNK_SIZE;
|
||||
VF_SetStrateArchetypeRuntimeBounds(Override->Params,
|
||||
Override->Regions.StrateTopWorldZ,
|
||||
Override->Regions.StrateBottomWorldZ);
|
||||
for (FVoxelStrateRegion& Region : Override->Regions.Regions)
|
||||
{
|
||||
VF_SetStrateArchetypeRuntimeBounds(Region.ArchetypeParams,
|
||||
Override->Regions.StrateTopWorldZ,
|
||||
Override->Regions.StrateBottomWorldZ);
|
||||
}
|
||||
Override->Regions.bHasGlobalStructuralParams = true;
|
||||
}
|
||||
|
||||
// Only one slot is overridden at a time. Keeping this map small also makes the worker-side
|
||||
// copy-on-chunk-refetch cheap. Passage geometry is deliberately not regenerated: the layout
|
||||
// and its passages are unchanged, and this is the same manager state used by the offline
|
||||
@@ -381,6 +410,25 @@ bool UVoxelStrateManager::GetComposerOverrideForChunk(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UVoxelStrateManager::GetComposerRegionOverrideForChunk(
|
||||
const FIntVector& ChunkCoord, FVoxelStrateRegionManifest& OutRegions) const
|
||||
{
|
||||
const int32 SlotIdx = FindSlotIndexForChunkZ(ChunkCoord.Z);
|
||||
if (SlotIdx < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FVoxelStrateComposerSlotOverride* Override =
|
||||
FindComposerOverride(StrateLayout[SlotIdx].StrateIndex);
|
||||
if (Override == nullptr || !Override->bUseRegions)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
OutRegions = Override->Regions;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UVoxelStrateManager::HasComposerRecipeOverride() const
|
||||
{
|
||||
for (const TPair<int32, TSharedPtr<FVoxelStrateComposerSlotOverride>>& Pair : ComposerOverrides)
|
||||
|
||||
Reference in new issue
Block a user