fix(measure): measure the strate, not the band above its floor

The window excluded a whole chunk (32 voxels) at each end to avoid a
4-voxel seal band -- 8x too much. On 4-chunk strates that discarded half
of every strate, floor included, so the pass was measuring the open air
above the ground and calling it the strate.

The margin now derives from the seal that actually exists:
ceil(2 x BoundarySealThickness), clamped to [1, height/4], overridable
via InteriorMarginVoxels. The fixture resolves to 8 voxels.

FlatPlain/CrystalChamber walkable fraction: 0.00044 -> 0.0578, ~132x.
The "99.95% empty void" reading was the window, not the world.

Metrics now carry ResolvedMarginVoxels, SampledMinZ and SampledMaxZ, and
the test prints them. A measurement that does not state the volume it
measured is exactly how the previous defect survived review.

VerticalShafts largest-component share rose only 0.074 -> 0.119 across
57 components: the window explained part of the swiss-cheese signal, not
all of it. That one is at least partly real.

Coarse-lie guard still load-bearing: 24 routes checked, 3 refuted at full
resolution (was 21 / 2).

Build clean, 18 succeeded / 0 warnings / 0 failed, confirmed in
Saved/Logs/VoxelM.log rather than from the agent's summary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bp7FxoQcSoiuhxjuLr6aBM
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-08-30 13:46:28 +02:00
1 parent 6988e5ace1
commit 13399529b5
4 files changed
+245 -69

No files matched your search

+6 -4
View File
@@ -303,15 +303,17 @@ This is **where terrain shape lives.**
> XY-field noise (heightfield/ceiling/relief/moisture) deliberately stays un-biased. §8.10. > XY-field noise (heightfield/ceiling/relief/moisture) deliberately stays un-biased. §8.10.
### 3.6b Strate measurement — `Public/VoxelStrateMeasure.h` + `Private/VoxelStrateMeasure.cpp` ### 3.6b Strate measurement — `Public/VoxelStrateMeasure.h` + `Private/VoxelStrateMeasure.cpp`
Headless, read-only Tier 2 measurement pass. `VF_MeasureStrate` samples one strate's strict Headless, read-only Tier 2 measurement pass. `VF_MeasureStrate` samples one strate between a
interior into a bounded coarse grid, performs one deterministic 6-connected air flood fill, and boundary-seal-derived (or explicitly overridden) interior margin into a bounded coarse grid,
derives fractions, components, walkability, feature scale, and clearance from that grid. performs one deterministic 6-connected air flood fill, and derives fractions, components,
walkability, feature scale, and clearance from that grid. Metrics report the resolved margin and
the inclusive/exclusive voxel Z window used.
`VF_AreConnected` recovers a deterministic BFS parent path from the same kind of grid and checks `VF_AreConnected` recovers a deterministic BFS parent path from the same kind of grid and checks
only that route at full voxel resolution before returning true; a coarse false positive sets only that route at full voxel resolution before returning true; a coarse false positive sets
`bOutCoarseLied`. No UObject state, cache, actor, world, or PIE is required. `bOutCoarseLied`. No UObject state, cache, actor, world, or PIE is required.
| Symbol | Role | | Symbol | Role |
|--------|------| |--------|------|
| `FVoxelStrateMeasureSettings` / `FVoxelStrateMetrics` | Plain settings/result structs for bounded strate sampling and derived measurements. | | `FVoxelStrateMeasureSettings` / `FVoxelStrateMetrics` | Plain settings/result structs for bounded strate sampling and derived measurements; callers may override the interior margin, and results identify the resolved margin/Z window. |
| `VF_MeasureStrate` | One-grid/one-flood-fill strate metrics; refuses invalid bounds or a grid over `MaxCells`. | | `VF_MeasureStrate` | One-grid/one-flood-fill strate metrics; refuses invalid bounds or a grid over `MaxCells`. |
| `VF_AreConnected` | Coarse 6-connected BFS plus one full-resolution recheck of its recovered route. | | `VF_AreConnected` | Coarse 6-connected BFS plus one full-resolution recheck of its recovered route. |
@@ -19,14 +19,20 @@ namespace
{ {
FString Name; FString Name;
int32 Index = INDEX_NONE; int32 Index = INDEX_NONE;
FVoxelStrateMetrics First; FVoxelStrateMetrics OldWindow;
FVoxelStrateMetrics Second; FVoxelStrateMetrics OldWindowRepeat;
FVoxelStrateMetrics DerivedWindow;
FVoxelStrateMetrics DerivedWindowRepeat;
}; };
struct FPassageReport struct FPassageReport
{ {
int32 Index = INDEX_NONE; int32 Index = INDEX_NONE;
FString Name; FString Name;
int32 UpperStrateIndex = INDEX_NONE;
int32 LowerStrateIndex = INDEX_NONE;
FVector UpperPoint = FVector::ZeroVector;
FVector LowerPoint = FVector::ZeroVector;
FString UpperResult; FString UpperResult;
FString LowerResult; FString LowerResult;
}; };
@@ -61,22 +67,39 @@ namespace
&& SameFloatBits(A.LargestComponentShare, B.LargestComponentShare) && SameFloatBits(A.LargestComponentShare, B.LargestComponentShare)
&& SameFloatBits(A.WalkableFraction, B.WalkableFraction) && SameFloatBits(A.WalkableFraction, B.WalkableFraction)
&& SameFloatBits(A.MedianFeatureScale, B.MedianFeatureScale) && SameFloatBits(A.MedianFeatureScale, B.MedianFeatureScale)
&& A.MedianVerticalClearance == B.MedianVerticalClearance; && A.MedianVerticalClearance == B.MedianVerticalClearance
&& A.ResolvedMarginVoxels == B.ResolvedMarginVoxels
&& A.SampledMinZ == B.SampledMinZ
&& A.SampledMaxZ == B.SampledMaxZ;
} }
bool IsStrictInteriorPoint(const FStrateSlot& Slot, const FVector& Point) bool IsInsideMeasuredWindow(
const FVoxelStrateMetrics& Metrics,
const FVoxelStrateMeasureSettings& Settings,
const FVector& Point)
{ {
const float MinZ = (static_cast<float>(Slot.BottomChunkZ) + 1.0f) * CHUNK_SIZE; const float MinX = Settings.CenterXY.X - static_cast<float>(Settings.RadiusInVoxels);
const float MaxZ = static_cast<float>(Slot.TopChunkZ) * CHUNK_SIZE; const float MaxX = Settings.CenterXY.X + static_cast<float>(Settings.RadiusInVoxels);
return Point.Z >= MinZ && Point.Z < MaxZ; const float MinY = Settings.CenterXY.Y - static_cast<float>(Settings.RadiusInVoxels);
const float MaxY = Settings.CenterXY.Y + static_cast<float>(Settings.RadiusInVoxels);
return Point.X >= MinX && Point.X < MaxX
&& Point.Y >= MinY && Point.Y < MaxY
&& Point.Z >= static_cast<float>(Metrics.SampledMinZ)
&& Point.Z < static_cast<float>(Metrics.SampledMaxZ);
} }
FVector LargestComponentAnchor(const FStrateSlot& Slot, const FVector2D& CenterXY) FVector LargestComponentAnchor(
const FStrateSlot& Slot,
const FVector2D& CenterXY,
int32 SampleStep)
{ {
// The fixture's origin spine is the deterministic hub/void anchor. The measurement API // The fixture's origin spine is the deterministic hub/void anchor. The measurement API
// intentionally returns metrics rather than a component coordinate, so this report probes // intentionally returns metrics rather than a component coordinate, so this report probes
// the known spine point that represents the largest reachable component in this fixture. // the known spine point that represents the largest reachable component in this fixture.
const float AnchorZ = (static_cast<float>(Slot.BottomChunkZ) + 1.0f) * CHUNK_SIZE + 2.0f; // Keep the established anchor one full chunk above the strate floor. The derived window
// includes it, while the first derived sample cell may still be floor rock for slab fields.
const float AnchorZ = (static_cast<float>(Slot.BottomChunkZ) + 1.0f) * CHUNK_SIZE
+ 0.5f * static_cast<float>(SampleStep);
return FVector(CenterXY.X + 2.0f, CenterXY.Y + 2.0f, AnchorZ); return FVector(CenterXY.X + 2.0f, CenterXY.Y + 2.0f, AnchorZ);
} }
@@ -200,6 +223,11 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
Settings.MaxCells = 8000000; Settings.MaxCells = 8000000;
Settings.HeadroomCells = 2; Settings.HeadroomCells = 2;
FVoxelStrateMeasureSettings OldWindowSettings = Settings;
OldWindowSettings.InteriorMarginVoxels = CHUNK_SIZE;
FVoxelStrateMeasureSettings DerivedWindowSettings = Settings;
DerivedWindowSettings.InteriorMarginVoxels = -1;
bool bAllChecksPassed = true; bool bAllChecksPassed = true;
FString Summary = TEXT("VoxelForge strate measurement summary (seed 1337, step 4, radius 256):\n"); FString Summary = TEXT("VoxelForge strate measurement summary (seed 1337, step 4, radius 256):\n");
@@ -258,8 +286,8 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
// deliberately separate from the gap probe above: this catches a sign inversion in the new // deliberately separate from the gap probe above: this catches a sign inversion in the new
// measurement pass even though the generator-level gap probe uses the correct MC predicate. // measurement pass even though the generator-level gap probe uses the correct MC predicate.
const FStrateSlot& SolidControlSlot = World.StrateManager->GetLayout()[0]; const FStrateSlot& SolidControlSlot = World.StrateManager->GetLayout()[0];
const int32 SolidControlMinZ = (SolidControlSlot.BottomChunkZ + 1) * CHUNK_SIZE; const int32 SolidControlMinZ = SolidControlSlot.BottomChunkZ * CHUNK_SIZE;
const int32 SolidControlMaxZ = SolidControlSlot.TopChunkZ * CHUNK_SIZE; const int32 SolidControlMaxZ = (SolidControlSlot.TopChunkZ + 1) * CHUNK_SIZE;
FVoxelModification SolidControl; FVoxelModification SolidControl;
SolidControl.Shape = EVoxelBrushShape::Box; SolidControl.Shape = EVoxelBrushShape::Box;
SolidControl.Center = FVector( SolidControl.Center = FVector(
@@ -275,7 +303,7 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
SolidControl.Strength = 100.0f; SolidControl.Strength = 100.0f;
const TArray<FIntVector> SolidControlChunks = World.DiffLayer->ApplyModification(SolidControl); const TArray<FIntVector> SolidControlChunks = World.DiffLayer->ApplyModification(SolidControl);
const FVoxelStrateMetrics SolidControlMetrics = VF_MeasureStrate( const FVoxelStrateMetrics SolidControlMetrics = VF_MeasureStrate(
*World.Generator, *World.StrateManager, 0, Settings); *World.Generator, *World.StrateManager, 0, DerivedWindowSettings);
if (SolidControlChunks.Num() == 0 || !SolidControlMetrics.bValid if (SolidControlChunks.Num() == 0 || !SolidControlMetrics.bValid
|| SolidControlMetrics.AirFraction > 0.05f) || SolidControlMetrics.AirFraction > 0.05f)
{ {
@@ -291,22 +319,16 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
TArray<FArchetypeReport> ArchetypeReports; TArray<FArchetypeReport> ArchetypeReports;
const TArray<FStrateSlot>& Layout = World.StrateManager->GetLayout(); const TArray<FStrateSlot>& Layout = World.StrateManager->GetLayout();
ArchetypeReports.Reserve(Layout.Num()); ArchetypeReports.Reserve(Layout.Num());
for (int32 StrateIndex = 0; StrateIndex < Layout.Num(); ++StrateIndex) TArray<FVoxelStrateMetrics> DerivedMetricsByIndex;
{ DerivedMetricsByIndex.SetNum(Layout.Num());
FArchetypeReport& Report = ArchetypeReports.AddDefaulted_GetRef();
Report.Index = StrateIndex;
Report.Name = ArchetypeName(Layout[StrateIndex]);
Report.First = VF_MeasureStrate(
*World.Generator, *World.StrateManager, StrateIndex, Settings);
Report.Second = VF_MeasureStrate(
*World.Generator, *World.StrateManager, StrateIndex, Settings);
const FVoxelStrateMetrics& Metrics = Report.First; auto ValidateMeasurement = [&](const FString& Label, const FVoxelStrateMetrics& Metrics)
{
if (!Metrics.bValid) if (!Metrics.bValid)
{ {
AddError(FString::Printf( AddError(FString::Printf(
TEXT("HARD FAILURE: %s measurement refused: %s."), TEXT("HARD FAILURE: %s measurement refused: %s."),
*Report.Name, *Label,
*Metrics.RefusalReason)); *Metrics.RefusalReason));
bAllChecksPassed = false; bAllChecksPassed = false;
} }
@@ -314,7 +336,7 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
{ {
AddError(FString::Printf( AddError(FString::Printf(
TEXT("HARD FAILURE: vacuous %s measurement — sampled=%lld, air=%lld, solid=%lld."), TEXT("HARD FAILURE: vacuous %s measurement — sampled=%lld, air=%lld, solid=%lld."),
*Report.Name, *Label,
Metrics.NumSampled, Metrics.NumSampled,
Metrics.NumAir, Metrics.NumAir,
Metrics.NumSolid)); Metrics.NumSolid));
@@ -327,19 +349,49 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|| !FMath::IsFinite(Metrics.WalkableFraction) || !FMath::IsFinite(Metrics.WalkableFraction)
|| Metrics.WalkableFraction < 0.0f || Metrics.WalkableFraction > 1.0f || Metrics.WalkableFraction < 0.0f || Metrics.WalkableFraction > 1.0f
|| (Metrics.NumAir > 0 && Metrics.NumAirComponents == 0) || (Metrics.NumAir > 0 && Metrics.NumAirComponents == 0)
|| (Metrics.LargestComponentShare == 0.0f && Metrics.AirFraction > 0.05f)) || (Metrics.LargestComponentShare == 0.0f && Metrics.AirFraction > 0.05f)
|| Metrics.ResolvedMarginVoxels < 1
|| Metrics.SampledMinZ >= Metrics.SampledMaxZ)
{ {
AddError(FString::Printf( AddError(FString::Printf(
TEXT("HARD FAILURE: range/component sanity failed for %s."), *Report.Name)); TEXT("HARD FAILURE: range/component/window sanity failed for %s."), *Label));
bAllChecksPassed = false; bAllChecksPassed = false;
} }
if (!MetricsAreBitIdentical(Report.First, Report.Second)) };
for (int32 StrateIndex = 0; StrateIndex < Layout.Num(); ++StrateIndex)
{
FArchetypeReport& Report = ArchetypeReports.AddDefaulted_GetRef();
Report.Index = StrateIndex;
Report.Name = ArchetypeName(Layout[StrateIndex]);
Report.OldWindow = VF_MeasureStrate(
*World.Generator, *World.StrateManager, StrateIndex, OldWindowSettings);
Report.OldWindowRepeat = VF_MeasureStrate(
*World.Generator, *World.StrateManager, StrateIndex, OldWindowSettings);
Report.DerivedWindow = VF_MeasureStrate(
*World.Generator, *World.StrateManager, StrateIndex, DerivedWindowSettings);
Report.DerivedWindowRepeat = VF_MeasureStrate(
*World.Generator, *World.StrateManager, StrateIndex, DerivedWindowSettings);
ValidateMeasurement(
FString::Printf(TEXT("%s old-window"), *Report.Name), Report.OldWindow);
ValidateMeasurement(
FString::Printf(TEXT("%s derived-window"), *Report.Name), Report.DerivedWindow);
if (!MetricsAreBitIdentical(Report.OldWindow, Report.OldWindowRepeat))
{ {
AddError(FString::Printf( AddError(FString::Printf(
TEXT("HARD FAILURE: %s measurement was not bit-identical on the second call."), TEXT("HARD FAILURE: %s old-window measurement was not bit-identical on the second call."),
*Report.Name)); *Report.Name));
bAllChecksPassed = false; bAllChecksPassed = false;
} }
if (!MetricsAreBitIdentical(Report.DerivedWindow, Report.DerivedWindowRepeat))
{
AddError(FString::Printf(
TEXT("HARD FAILURE: %s derived-window measurement was not bit-identical on the second call."),
*Report.Name));
bAllChecksPassed = false;
}
DerivedMetricsByIndex[StrateIndex] = Report.DerivedWindow;
} }
ArchetypeReports.Sort([](const FArchetypeReport& A, const FArchetypeReport& B) ArchetypeReports.Sort([](const FArchetypeReport& A, const FArchetypeReport& B)
@@ -355,22 +407,38 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
GapAirFraction, GapAirFraction,
GapAirSamples, GapAirSamples,
GapSamples); GapSamples);
Summary += TEXT("Archetypes (sorted):\n"); Summary += TEXT("Archetypes (old one-chunk vs derived window; derived InteriorMarginVoxels<0 => 2x BoundarySealThickness, clamped):\n");
Summary += TEXT(" name | old margin | old Z span [min,max) | old Air | old Largest | old Walkable | old FeatureScale vox | old Clearance vox | old Components | old Samples | old Solid | derived margin | derived Z span [min,max) | derived Air | derived Largest | derived Walkable | derived FeatureScale vox | derived Clearance vox | derived Components | derived Samples | derived Solid\n");
for (const FArchetypeReport& Report : ArchetypeReports) for (const FArchetypeReport& Report : ArchetypeReports)
{ {
const FVoxelStrateMetrics& Metrics = Report.First; const FVoxelStrateMetrics& Old = Report.OldWindow;
const FVoxelStrateMetrics& Derived = Report.DerivedWindow;
Summary += FString::Printf( Summary += FString::Printf(
TEXT(" %s: Air=%.9g, Largest=%.9g, Walkable=%.9g, FeatureScale=%.9g vox, " TEXT(" %s | %d | [%d,%d) | %.9g | %.9g | %.9g | %.9g | %d | %d | %lld | %lld | "
"Clearance=%d vox, Components=%d, Samples=%lld, Solid=%lld\n"), "%d | [%d,%d) | %.9g | %.9g | %.9g | %.9g | %d | %d | %lld | %lld\n"),
*Report.Name, *Report.Name,
Metrics.AirFraction, Old.ResolvedMarginVoxels,
Metrics.LargestComponentShare, Old.SampledMinZ,
Metrics.WalkableFraction, Old.SampledMaxZ,
Metrics.MedianFeatureScale, Old.AirFraction,
Metrics.MedianVerticalClearance, Old.LargestComponentShare,
Metrics.NumAirComponents, Old.WalkableFraction,
Metrics.NumSampled, Old.MedianFeatureScale,
Metrics.NumSolid); Old.MedianVerticalClearance,
Old.NumAirComponents,
Old.NumSampled,
Old.NumSolid,
Derived.ResolvedMarginVoxels,
Derived.SampledMinZ,
Derived.SampledMaxZ,
Derived.AirFraction,
Derived.LargestComponentShare,
Derived.WalkableFraction,
Derived.MedianFeatureScale,
Derived.MedianVerticalClearance,
Derived.NumAirComponents,
Derived.NumSampled,
Derived.NumSolid);
} }
int32 NumRoutesChecked = 0; int32 NumRoutesChecked = 0;
@@ -378,17 +446,24 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
for (int32 StrateIndex = 0; StrateIndex < Layout.Num(); ++StrateIndex) for (int32 StrateIndex = 0; StrateIndex < Layout.Num(); ++StrateIndex)
{ {
const FStrateSlot& Slot = Layout[StrateIndex]; const FStrateSlot& Slot = Layout[StrateIndex];
const int32 InteriorMinZ = (Slot.BottomChunkZ + 1) * CHUNK_SIZE; const FVoxelStrateMetrics& Window = DerivedMetricsByIndex[StrateIndex];
const int32 InteriorMaxZ = Slot.TopChunkZ * CHUNK_SIZE; if (!Window.bValid || Window.SampledMinZ >= Window.SampledMaxZ) continue;
if (InteriorMinZ >= InteriorMaxZ) continue;
const FVector A(Settings.CenterXY.X + 2.0f, Settings.CenterXY.Y + 2.0f, const FVector A(Settings.CenterXY.X + 2.0f, Settings.CenterXY.Y + 2.0f,
static_cast<float>(InteriorMinZ + 2)); static_cast<float>(Window.SampledMinZ)
+ 0.5f * static_cast<float>(DerivedWindowSettings.SampleStep));
const FVector B(Settings.CenterXY.X + 2.0f, Settings.CenterXY.Y + 2.0f, const FVector B(Settings.CenterXY.X + 2.0f, Settings.CenterXY.Y + 2.0f,
static_cast<float>(InteriorMaxZ - 2)); static_cast<float>(Window.SampledMaxZ)
- 0.5f * static_cast<float>(DerivedWindowSettings.SampleStep));
bool bCoarseLied = false; bool bCoarseLied = false;
const bool bConnected = VF_AreConnected( const bool bConnected = VF_AreConnected(
*World.Generator, *World.StrateManager, StrateIndex, A, B, Settings, bCoarseLied); *World.Generator,
*World.StrateManager,
StrateIndex,
A,
B,
DerivedWindowSettings,
bCoarseLied);
++NumRoutesChecked; ++NumRoutesChecked;
if (bCoarseLied) ++NumRoutesRefuted; if (bCoarseLied) ++NumRoutesRefuted;
if (bConnected && FullResolutionDirectSegmentHasSolid(*World.Generator, A, B)) if (bConnected && FullResolutionDirectSegmentHasSolid(*World.Generator, A, B))
@@ -403,9 +478,9 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
// Controlled negative route: at SampleStep 4, the two coarse centres at X=-2 and X=+2 // Controlled negative route: at SampleStep 4, the two coarse centres at X=-2 and X=+2
// remain air while the three full-resolution voxels at X=-1,0,+1 are filled solid. // remain air while the three full-resolution voxels at X=-1,0,+1 are filled solid.
const FStrateSlot& WallSlot = Layout[0]; const FVoxelStrateMetrics& WallWindow = DerivedMetricsByIndex[0];
const int32 WallInteriorMinZ = (WallSlot.BottomChunkZ + 1) * CHUNK_SIZE; const float WallZ = static_cast<float>(WallWindow.SampledMinZ)
const float WallZ = static_cast<float>(WallInteriorMinZ + 2); + 0.5f * static_cast<float>(DerivedWindowSettings.SampleStep);
const FVector WallA(Settings.CenterXY.X - 2.0f, Settings.CenterXY.Y + 2.0f, WallZ); const FVector WallA(Settings.CenterXY.X - 2.0f, Settings.CenterXY.Y + 2.0f, WallZ);
const FVector WallB(Settings.CenterXY.X + 2.0f, Settings.CenterXY.Y + 2.0f, WallZ); const FVector WallB(Settings.CenterXY.X + 2.0f, Settings.CenterXY.Y + 2.0f, WallZ);
@@ -430,7 +505,7 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
0, 0,
WallA, WallA,
WallB, WallB,
Settings, DerivedWindowSettings,
bWallCoarseLied); bWallCoarseLied);
++NumRoutesChecked; ++NumRoutesChecked;
if (bWallCoarseLied) ++NumRoutesRefuted; if (bWallCoarseLied) ++NumRoutesRefuted;
@@ -455,14 +530,27 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
FPassageReport& Report = PassageReports.AddDefaulted_GetRef(); FPassageReport& Report = PassageReports.AddDefaulted_GetRef();
Report.Index = PassageIndex; Report.Index = PassageIndex;
Report.Name = FString::Printf(TEXT("%03d"), PassageIndex); Report.Name = FString::Printf(TEXT("%03d"), PassageIndex);
Report.UpperStrateIndex = Passage.UpperStrateIndex;
Report.LowerStrateIndex = Passage.LowerStrateIndex;
Report.UpperPoint = Passage.UpperPoint;
Report.LowerPoint = Passage.LowerPoint;
auto CheckEndpoint = [&](int32 EndpointStrateIndex, const FVector& Endpoint) -> FString auto CheckEndpoint = [&](int32 EndpointStrateIndex, const FVector& Endpoint) -> FString
{ {
if (!Layout.IsValidIndex(EndpointStrateIndex)) return TEXT("invalid-strate"); if (!Layout.IsValidIndex(EndpointStrateIndex)) return TEXT("invalid-strate");
const FStrateSlot& Slot = Layout[EndpointStrateIndex]; const FVoxelStrateMetrics& EndpointMetrics =
if (!IsStrictInteriorPoint(Slot, Endpoint)) return TEXT("out-of-band"); DerivedMetricsByIndex[EndpointStrateIndex];
if (!EndpointMetrics.bValid) return TEXT("invalid-window");
if (!IsInsideMeasuredWindow(EndpointMetrics, DerivedWindowSettings, Endpoint))
{
return TEXT("out-of-window");
}
const FVector Anchor = LargestComponentAnchor(Slot, Settings.CenterXY); const FStrateSlot& Slot = Layout[EndpointStrateIndex];
const FVector Anchor = LargestComponentAnchor(
Slot,
DerivedWindowSettings.CenterXY,
DerivedWindowSettings.SampleStep);
bool bCoarseLied = false; bool bCoarseLied = false;
const bool bConnected = VF_AreConnected( const bool bConnected = VF_AreConnected(
*World.Generator, *World.Generator,
@@ -470,11 +558,11 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
EndpointStrateIndex, EndpointStrateIndex,
Endpoint, Endpoint,
Anchor, Anchor,
Settings, DerivedWindowSettings,
bCoarseLied); bCoarseLied);
++NumRoutesChecked; ++NumRoutesChecked;
if (bCoarseLied) ++NumRoutesRefuted; if (bCoarseLied) ++NumRoutesRefuted;
return bConnected ? TEXT("connected") : TEXT("not-connected"); return bConnected ? TEXT("CONNECTED_TO_LARGEST") : TEXT("NOT_CONNECTED");
}; };
Report.UpperResult = CheckEndpoint(Passage.UpperStrateIndex, Passage.UpperPoint); Report.UpperResult = CheckEndpoint(Passage.UpperStrateIndex, Passage.UpperPoint);
@@ -486,16 +574,25 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
return A.Index < B.Index; return A.Index < B.Index;
}); });
Summary += FString::Printf( Summary += FString::Printf(
TEXT("Connectivity guard: %d routes checked, %d coarse routes refuted at full resolution.\n"), TEXT("Connectivity guard (derived window): %d routes checked, %d coarse routes refuted at full resolution.\n"),
NumRoutesChecked, NumRoutesChecked,
NumRoutesRefuted); NumRoutesRefuted);
Summary += TEXT("Passages (endpoint -> largest-component spine anchor, sorted):\n"); Summary += TEXT("Passages (derived window; each endpoint -> its strate's largest-component spine anchor, sorted):\n");
for (const FPassageReport& Report : PassageReports) for (const FPassageReport& Report : PassageReports)
{ {
Summary += FString::Printf( Summary += FString::Printf(
TEXT(" passage %s: upper=%s, lower=%s\n"), TEXT(" passage %s: UpperPoint strate=%d (%.3f,%.3f,%.3f) => %s; "
"LowerPoint strate=%d (%.3f,%.3f,%.3f) => %s\n"),
*Report.Name, *Report.Name,
Report.UpperStrateIndex,
Report.UpperPoint.X,
Report.UpperPoint.Y,
Report.UpperPoint.Z,
*Report.UpperResult, *Report.UpperResult,
Report.LowerStrateIndex,
Report.LowerPoint.X,
Report.LowerPoint.Y,
Report.LowerPoint.Z,
*Report.LowerResult); *Report.LowerResult);
} }
AddInfo(Summary); AddInfo(Summary);
@@ -26,6 +26,10 @@ namespace VoxelStrateMeasurePrivate
float ExtentY = 0.0f; float ExtentY = 0.0f;
float ExtentZ = 0.0f; float ExtentZ = 0.0f;
int32 ResolvedMarginVoxels = 0;
int32 SampledMinZ = 0;
int32 SampledMaxZ = 0;
// 1 = air, 0 = solid. The polarity is deliberately explicit at the sampling site below. // 1 = air, 0 = solid. The polarity is deliberately explicit at the sampling site below.
TArray<uint8> Air; TArray<uint8> Air;
@@ -114,14 +118,74 @@ namespace VoxelStrateMeasurePrivate
return Refuse(OutReason, TEXT("The requested strate has no positive vertical extent.")); return Refuse(OutReason, TEXT("The requested strate has no positive vertical extent."));
} }
// Exclude the complete top and bottom chunks. Those outer chunks contain the seal bands; const int64 StrateBottomZ = static_cast<int64>(Slot.BottomChunkZ) * CHUNK_SIZE;
// the measurement is about the strate interior, not the boundary bedrock. const int64 StrateTopZ = (static_cast<int64>(Slot.TopChunkZ) + 1) * CHUNK_SIZE;
const int64 InteriorMinZ = (static_cast<int64>(Slot.BottomChunkZ) + 1) * CHUNK_SIZE; const int64 StrateHeight = StrateTopZ - StrateBottomZ;
const int64 InteriorMaxZ = static_cast<int64>(Slot.TopChunkZ) * CHUNK_SIZE; if (StrateHeight <= 0)
{
return Refuse(OutReason, TEXT("The requested strate has no positive voxel height."));
}
// 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.
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)),
MidChunkZ);
const FStrateGenerationParams RepresentativeParams =
Manager.GetGenerationParams(RepresentativeChunk);
const int64 MaxMargin64 = FMath::Max<int64>(1, StrateHeight / 4);
const int32 MaxMargin = static_cast<int32>(
FMath::Min<int64>(MaxMargin64, static_cast<int64>(INT32_MAX)));
int32 RequestedMargin = Settings.InteriorMarginVoxels;
if (RequestedMargin < 0)
{
const float DoubleSealThickness = 2.0f * RepresentativeParams.BoundarySealThickness;
if (FMath::IsNaN(RepresentativeParams.BoundarySealThickness))
{
return Refuse(OutReason, TEXT("BoundarySealThickness is NaN; the interior margin cannot be derived."));
}
// Avoid converting an infinite value to int. A positive overflow is still an
// intentionally absurd seal request and therefore resolves to the safe maximum;
// negative infinity resolves to the minimum.
if (!FMath::IsFinite(DoubleSealThickness))
{
RequestedMargin = DoubleSealThickness > 0.0f ? MaxMargin : 1;
}
else if (DoubleSealThickness >= static_cast<float>(INT32_MAX))
{
RequestedMargin = MaxMargin;
}
else if (DoubleSealThickness <= static_cast<float>(INT32_MIN))
{
RequestedMargin = 1;
}
else
{
RequestedMargin = FMath::CeilToInt(DoubleSealThickness);
}
}
const int32 ResolvedMargin = FMath::Clamp(RequestedMargin, 1, MaxMargin);
const int64 InteriorMinZ = StrateBottomZ + static_cast<int64>(ResolvedMargin);
const int64 InteriorMaxZ = StrateTopZ - static_cast<int64>(ResolvedMargin);
const int64 InteriorHeight = InteriorMaxZ - InteriorMinZ; const int64 InteriorHeight = InteriorMaxZ - InteriorMinZ;
if (InteriorHeight <= 0) if (InteriorHeight <= 0)
{ {
return Refuse(OutReason, TEXT("The strate is too short after excluding its top and bottom chunks.")); return Refuse(OutReason, TEXT("The strate has no measurable height after applying its interior margin."));
}
if (InteriorMinZ < static_cast<int64>(INT32_MIN)
|| InteriorMinZ > static_cast<int64>(INT32_MAX)
|| InteriorMaxZ < static_cast<int64>(INT32_MIN)
|| InteriorMaxZ > static_cast<int64>(INT32_MAX))
{
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 XYExtent = static_cast<int64>(Settings.RadiusInVoxels) * 2;
@@ -188,6 +252,9 @@ namespace VoxelStrateMeasurePrivate
OutGrid.ExtentX = MaxX - MinX; OutGrid.ExtentX = MaxX - MinX;
OutGrid.ExtentY = MaxY - MinY; OutGrid.ExtentY = MaxY - MinY;
OutGrid.ExtentZ = MaxZ - MinZ; OutGrid.ExtentZ = MaxZ - MinZ;
OutGrid.ResolvedMarginVoxels = ResolvedMargin;
OutGrid.SampledMinZ = static_cast<int32>(InteriorMinZ);
OutGrid.SampledMaxZ = static_cast<int32>(InteriorMaxZ);
OutGrid.Air.SetNumUninitialized(OutGrid.CellCount); OutGrid.Air.SetNumUninitialized(OutGrid.CellCount);
bool bSawNonFiniteDensity = false; bool bSawNonFiniteDensity = false;
@@ -627,6 +694,9 @@ FVoxelStrateMetrics VF_MeasureStrate(
return Result; return Result;
} }
Result.ResolvedMarginVoxels = Grid.ResolvedMarginVoxels;
Result.SampledMinZ = Grid.SampledMinZ;
Result.SampledMaxZ = Grid.SampledMaxZ;
VoxelStrateMeasurePrivate::BuildMetricsFromGrid(Grid, Settings, Result); VoxelStrateMeasurePrivate::BuildMetricsFromGrid(Grid, Settings, Result);
Result.bValid = Result.NumSampled > 0; Result.bValid = Result.NumSampled > 0;
if (!Result.bValid) if (!Result.bValid)
@@ -15,6 +15,7 @@ struct VOXELFORGE_API FVoxelStrateMeasureSettings
FVector2D CenterXY = FVector2D::ZeroVector; // Actor-space voxel coordinates. FVector2D CenterXY = FVector2D::ZeroVector; // Actor-space voxel coordinates.
int32 MaxCells = 8000000; // Refuse a grid larger than this many cells. int32 MaxCells = 8000000; // Refuse a grid larger than this many cells.
int32 HeadroomCells = 2; // Air cells above a floor cell required for walkable. int32 HeadroomCells = 2; // Air cells above a floor cell required for walkable.
int32 InteriorMarginVoxels = -1; // <0 = derive from BoundarySealThickness (2x, clamped).
}; };
/** Metrics derived from one coarse grid and its single air flood fill. */ /** Metrics derived from one coarse grid and its single air flood fill. */
@@ -33,13 +34,19 @@ struct VOXELFORGE_API FVoxelStrateMetrics
float WalkableFraction = 0.f; float WalkableFraction = 0.f;
float MedianFeatureScale = 0.f; float MedianFeatureScale = 0.f;
int32 MedianVerticalClearance = 0; int32 MedianVerticalClearance = 0;
// The exact vertical window used by the measurement: [SampledMinZ, SampledMaxZ).
int32 ResolvedMarginVoxels = 0;
int32 SampledMinZ = 0;
int32 SampledMaxZ = 0;
}; };
/** /**
* Measure one strate in actor-space voxel coordinates. * Measure one strate in actor-space voxel coordinates.
* *
* The pass samples only the interior chunks of the requested strate, flood-fills its air once, * The pass samples the requested strate between a resolved margin inside its top and bottom
* and derives all returned metrics from that same grid. It never mutates either input object. * boundaries, flood-fills its air once, and derives all returned metrics from that same grid.
* It never mutates either input object.
*/ */
VOXELFORGE_API FVoxelStrateMetrics VF_MeasureStrate( VOXELFORGE_API FVoxelStrateMetrics VF_MeasureStrate(
const UVoxelGenerator& Generator, const UVoxelGenerator& Generator,