fix(shafts): the primordial law holds -- 0 trusted negatives across 16 seeds

Closes the VerticalShafts bug. Two remaining failures were investigated
and neither was a broken world.

PART A -- my measurement window was binding, not the world.
The window was a square centred on (0,0) with a radius, while mouths sit
up to ~200 voxels out in arbitrary directions, so a box holding both was
mostly empty space paid for at full price. Seed 6 at step 1 needed
16,515,072 cells and blew the cap; fitted to the two mouths it needs
6,553,344 and measures fine.

  seed  6  step 2  NOT_CONNECTED -> CONNECTED at doubled margin
  seed  6  step 1  NOT_CONNECTED -> CONNECTED at doubled margin
  seed 14  step 2  CONNECTED
  seed 14  step 1  CONNECTED

A fitted box can cut off a route that detours outside it, so every
negative is re-run at doubled margin. 5 of 16 were binding negatives and
all 5 flipped. TRUSTED NEGATIVES: 0. There is no evidence of a single
unescapable strate in 16 seeds.

PART B -- the drainage tree has no orphans, and a third cause existed.
  0 / 10,372 unreachable shaft paths (0% orphan rate)
  16 strict local minima, all caught by the direct-spine fallback
  neighbour fallback fired 0 times; emergency fallback 0
  91.8% of non-largest components are smaller than one shaft volume --
    they are roughness bubbles, not geometry

So a mouth could land in a noise bubble that is open but unreachable --
worse than useless, exactly the trap the law forbids.
VF_SuggestLandingPoint now returns the selected shaft AXIS, which is on
the drainage tree by construction.

⚠️ Codex found a third cause neither of us listed: tree capsules could
cross ledge bands, so a guaranteed connector could be blocked by a ledge.
Connector Z is now drawn from a deterministic ledge-free interval,
mirrored in the op stack.

⚠️ And an 8th error in my spec: "connect to any neighbour shaft" as a
fallback can create CYCLES -- a loop of shafts that drains nowhere and is
disconnected from the spine. The fallback must preserve strictly
decreasing origin-distance. Implemented that way.

  per-archetype law 6/6 · box-verdict violations 0 · stack equivalence
  bit-identical over 20,000 samples · sweep 45.3 -> 20.4 s
  cache 7.970 us rebuild, 0.073 us hot call (225-cell rolls on rebuild only)

Build clean, 21 succeeded / 0 failed, verified by me in VoxelM.log.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-09-04 03:57:30 +02:00
1 parent 9d2324879d
commit 4f11ce81a4
10 files changed
+1813 -174

No files matched your search

+102 -20
View File
@@ -97,7 +97,14 @@ namespace VoxelStrateMeasurePrivate
{
return Refuse(OutReason, TEXT("SampleStep must be greater than zero."));
}
if (Settings.RadiusInVoxels <= 0)
const bool bHasCoverPointA = Settings.CoverPointA.IsSet();
const bool bHasCoverPointB = Settings.CoverPointB.IsSet();
if (bHasCoverPointA != bHasCoverPointB)
{
return Refuse(OutReason, TEXT("CoverPointA and CoverPointB must be set together."));
}
const bool bUseFittedWindow = bHasCoverPointA && bHasCoverPointB;
if (!bUseFittedWindow && Settings.RadiusInVoxels <= 0)
{
return Refuse(OutReason, TEXT("RadiusInVoxels must be greater than zero."));
}
@@ -109,10 +116,26 @@ namespace VoxelStrateMeasurePrivate
{
return Refuse(OutReason, TEXT("HeadroomCells cannot be negative."));
}
if (!FMath::IsFinite(Settings.CenterXY.X) || !FMath::IsFinite(Settings.CenterXY.Y))
if (!bUseFittedWindow
&& (!FMath::IsFinite(Settings.CenterXY.X) || !FMath::IsFinite(Settings.CenterXY.Y)))
{
return Refuse(OutReason, TEXT("CenterXY must contain finite voxel coordinates."));
}
if (bUseFittedWindow)
{
const FVector2D& PointA = Settings.CoverPointA.GetValue();
const FVector2D& PointB = Settings.CoverPointB.GetValue();
if (!FMath::IsFinite(PointA.X) || !FMath::IsFinite(PointA.Y)
|| !FMath::IsFinite(PointB.X) || !FMath::IsFinite(PointB.Y))
{
return Refuse(OutReason, TEXT("Cover points must contain finite voxel coordinates."));
}
if (!FMath::IsFinite(Settings.CoverMarginVoxels)
|| Settings.CoverMarginVoxels < 0.0f)
{
return Refuse(OutReason, TEXT("CoverMarginVoxels must be finite and non-negative."));
}
}
if (Slot.TopChunkZ <= Slot.BottomChunkZ)
{
return Refuse(OutReason, TEXT("The requested strate has no positive vertical extent."));
@@ -128,13 +151,52 @@ namespace VoxelStrateMeasurePrivate
// Resolve the margin from the same blended parameter query used by generation. The
// representative chunk is deliberately in the middle of this slot and uses the sample
// centre's XY chunk, so a future non-Hard transition resolves through the manager exactly
// as generation does at that representative location.
// window centre's XY chunk, so a future non-Hard transition resolves through the manager
// exactly as generation does at that representative location.
FVector2D WindowCenter = Settings.CenterXY;
double MinX64 = 0.0;
double MaxX64 = 0.0;
double MinY64 = 0.0;
double MaxY64 = 0.0;
if (bUseFittedWindow)
{
const FVector2D& PointA = Settings.CoverPointA.GetValue();
const FVector2D& PointB = Settings.CoverPointB.GetValue();
const double Margin = static_cast<double>(Settings.CoverMarginVoxels);
MinX64 = FMath::Min(static_cast<double>(PointA.X), static_cast<double>(PointB.X)) - Margin;
MaxX64 = FMath::Max(static_cast<double>(PointA.X), static_cast<double>(PointB.X)) + Margin;
MinY64 = FMath::Min(static_cast<double>(PointA.Y), static_cast<double>(PointB.Y)) - Margin;
MaxY64 = FMath::Max(static_cast<double>(PointA.Y), static_cast<double>(PointB.Y)) + Margin;
WindowCenter = FVector2D(
static_cast<float>(0.5 * (MinX64 + MaxX64)),
static_cast<float>(0.5 * (MinY64 + MaxY64)));
}
else
{
MinX64 = static_cast<double>(Settings.CenterXY.X)
- static_cast<double>(Settings.RadiusInVoxels);
MaxX64 = static_cast<double>(Settings.CenterXY.X)
+ static_cast<double>(Settings.RadiusInVoxels);
MinY64 = static_cast<double>(Settings.CenterXY.Y)
- static_cast<double>(Settings.RadiusInVoxels);
MaxY64 = static_cast<double>(Settings.CenterXY.Y)
+ static_cast<double>(Settings.RadiusInVoxels);
}
const double XYExtentX = MaxX64 - MinX64;
const double XYExtentY = MaxY64 - MinY64;
if (!FMath::IsFinite(MinX64) || !FMath::IsFinite(MaxX64)
|| !FMath::IsFinite(MinY64) || !FMath::IsFinite(MaxY64)
|| !FMath::IsFinite(XYExtentX) || !FMath::IsFinite(XYExtentY)
|| XYExtentX < 1.0 || XYExtentY < 1.0)
{
return Refuse(OutReason, TEXT("The requested XY window is not a finite positive voxel box."));
}
const int32 MidChunkZ = Slot.BottomChunkZ
+ (Slot.TopChunkZ - Slot.BottomChunkZ) / 2;
const FIntVector RepresentativeChunk(
FMath::FloorToInt(Settings.CenterXY.X / static_cast<float>(CHUNK_SIZE)),
FMath::FloorToInt(Settings.CenterXY.Y / static_cast<float>(CHUNK_SIZE)),
FMath::FloorToInt(WindowCenter.X / static_cast<float>(CHUNK_SIZE)),
FMath::FloorToInt(WindowCenter.Y / static_cast<float>(CHUNK_SIZE)),
MidChunkZ);
const FStrateGenerationParams RepresentativeParams =
Manager.GetGenerationParams(RepresentativeChunk);
@@ -188,10 +250,18 @@ namespace VoxelStrateMeasurePrivate
return Refuse(OutReason, TEXT("The sampled Z window is outside the metrics struct's int32 range."));
}
const int64 XYExtent = static_cast<int64>(Settings.RadiusInVoxels) * 2;
const int64 Step = static_cast<int64>(Settings.SampleStep);
const int64 NumX64 = CeilDivPositive(XYExtent, Step);
const int64 NumY64 = NumX64;
const double NumXReal = XYExtentX / static_cast<double>(Step);
const double NumYReal = XYExtentY / static_cast<double>(Step);
if (!FMath::IsFinite(NumXReal) || !FMath::IsFinite(NumYReal)
|| NumXReal <= 0.0 || NumYReal <= 0.0
|| NumXReal > static_cast<double>(INT32_MAX)
|| NumYReal > static_cast<double>(INT32_MAX))
{
return Refuse(OutReason, TEXT("A sample-grid dimension exceeds the supported array size."));
}
const int64 NumX64 = FMath::CeilToInt64(NumXReal);
const int64 NumY64 = FMath::CeilToInt64(NumYReal);
const int64 NumZ64 = CeilDivPositive(InteriorHeight, Step);
const int64 MaxCells = static_cast<int64>(Settings.MaxCells);
@@ -216,14 +286,6 @@ namespace VoxelStrateMeasurePrivate
return Refuse(OutReason, TEXT("The requested sample grid exceeds the bounded cell count."));
}
const double MinX64 = static_cast<double>(Settings.CenterXY.X)
- static_cast<double>(Settings.RadiusInVoxels);
const double MaxX64 = static_cast<double>(Settings.CenterXY.X)
+ static_cast<double>(Settings.RadiusInVoxels);
const double MinY64 = static_cast<double>(Settings.CenterXY.Y)
- static_cast<double>(Settings.RadiusInVoxels);
const double MaxY64 = static_cast<double>(Settings.CenterXY.Y)
+ static_cast<double>(Settings.RadiusInVoxels);
const float MinX = static_cast<float>(MinX64);
const float MaxX = static_cast<float>(MaxX64);
const float MinY = static_cast<float>(MinY64);
@@ -301,7 +363,8 @@ namespace VoxelStrateMeasurePrivate
int64 NumAir,
int64& OutLargestComponentCells,
int32& OutLargestComponentLowestCell,
int32& OutNumComponentsAtLeast1Pct)
int32& OutNumComponentsAtLeast1Pct,
TArray<int64>* OutComponentCells)
{
OutComponents.SetNumUninitialized(Grid.CellCount);
for (int32 Index = 0; Index < Grid.CellCount; ++Index)
@@ -315,6 +378,10 @@ namespace VoxelStrateMeasurePrivate
OutLargestComponentCells = 0;
OutLargestComponentLowestCell = INDEX_NONE;
OutNumComponentsAtLeast1Pct = 0;
if (OutComponentCells != nullptr)
{
OutComponentCells->Reset();
}
for (int32 Start = 0; Start < Grid.CellCount; ++Start)
{
@@ -359,6 +426,10 @@ namespace VoxelStrateMeasurePrivate
}
const int64 ComponentCells64 = static_cast<int64>(ComponentCells);
if (OutComponentCells != nullptr)
{
OutComponentCells->Add(ComponentCells64);
}
if (NumAir > 0 && ComponentCells64 * 100 >= NumAir)
{
++OutNumComponentsAtLeast1Pct;
@@ -457,6 +528,7 @@ namespace VoxelStrateMeasurePrivate
int64 LargestComponentCells = 0;
int32 LargestComponentLowestCell = INDEX_NONE;
int32 NumComponentsAtLeast1Pct = 0;
TArray<int64> ComponentCells;
FloodFillAir(
Grid,
Components,
@@ -464,7 +536,8 @@ namespace VoxelStrateMeasurePrivate
NumAir,
LargestComponentCells,
LargestComponentLowestCell,
NumComponentsAtLeast1Pct);
NumComponentsAtLeast1Pct,
&ComponentCells);
InOutMetrics.NumSampled = Grid.CellCount;
InOutMetrics.NumAir = NumAir;
@@ -472,6 +545,7 @@ namespace VoxelStrateMeasurePrivate
InOutMetrics.AirFraction = static_cast<float>(
static_cast<double>(NumAir) / static_cast<double>(Grid.CellCount));
InOutMetrics.NumAirComponents = NumComponents;
InOutMetrics.AirComponentCells = MoveTemp(ComponentCells);
InOutMetrics.LargestComponentCells = LargestComponentCells;
InOutMetrics.NumComponentsAtLeast1Pct = NumComponentsAtLeast1Pct;
InOutMetrics.LargestComponentShare = NumAir > 0
@@ -1005,7 +1079,8 @@ namespace VoxelStrateMeasurePrivate
NumAir,
LargestComponentCells,
LargestComponentLowestCell,
NumComponentsAtLeast1Pct);
NumComponentsAtLeast1Pct,
nullptr);
const int32 StartComponent = Components[Start];
const int32 GoalComponent = Components[Goal];
@@ -1141,6 +1216,13 @@ FVoxelStrateMetrics VF_MeasureStrate(
Result.ResolvedMarginVoxels = Grid.ResolvedMarginVoxels;
Result.SampledMinZ = Grid.SampledMinZ;
Result.SampledMaxZ = Grid.SampledMaxZ;
Result.SampledNumX = Grid.NumX;
Result.SampledNumY = Grid.NumY;
Result.SampledNumZ = Grid.NumZ;
Result.SampledMinX = Grid.MinX;
Result.SampledMaxX = Grid.MaxX;
Result.SampledMinY = Grid.MinY;
Result.SampledMaxY = Grid.MaxY;
VoxelStrateMeasurePrivate::BuildMetricsFromGrid(Grid, Settings, Result);
Result.bValid = Result.NumSampled > 0;
if (!Result.bValid)