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.
### 3.6b Strate measurement — `Public/VoxelStrateMeasure.h` + `Private/VoxelStrateMeasure.cpp`
Headless, read-only Tier 2 measurement pass. `VF_MeasureStrate` samples one strate's strict
interior into a bounded coarse grid, performs one deterministic 6-connected air flood fill, and
derives fractions, components, walkability, feature scale, and clearance from that grid.
Headless, read-only Tier 2 measurement pass. `VF_MeasureStrate` samples one strate between a
boundary-seal-derived (or explicitly overridden) interior margin into a bounded coarse 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
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.
| 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_AreConnected` | Coarse 6-connected BFS plus one full-resolution recheck of its recovered route. |
@@ -19,14 +19,20 @@ namespace
{
FString Name;
int32 Index = INDEX_NONE;
FVoxelStrateMetrics First;
FVoxelStrateMetrics Second;
FVoxelStrateMetrics OldWindow;
FVoxelStrateMetrics OldWindowRepeat;
FVoxelStrateMetrics DerivedWindow;
FVoxelStrateMetrics DerivedWindowRepeat;
};
struct FPassageReport
{
int32 Index = INDEX_NONE;
FString Name;
int32 UpperStrateIndex = INDEX_NONE;
int32 LowerStrateIndex = INDEX_NONE;
FVector UpperPoint = FVector::ZeroVector;
FVector LowerPoint = FVector::ZeroVector;
FString UpperResult;
FString LowerResult;
};
@@ -61,22 +67,39 @@ namespace
&& SameFloatBits(A.LargestComponentShare, B.LargestComponentShare)
&& SameFloatBits(A.WalkableFraction, B.WalkableFraction)
&& 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 MaxZ = static_cast<float>(Slot.TopChunkZ) * CHUNK_SIZE;
return Point.Z >= MinZ && Point.Z < MaxZ;
const float MinX = Settings.CenterXY.X - static_cast<float>(Settings.RadiusInVoxels);
const float MaxX = Settings.CenterXY.X + static_cast<float>(Settings.RadiusInVoxels);
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
// 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.
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);
}
@@ -200,6 +223,11 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
Settings.MaxCells = 8000000;
Settings.HeadroomCells = 2;
FVoxelStrateMeasureSettings OldWindowSettings = Settings;
OldWindowSettings.InteriorMarginVoxels = CHUNK_SIZE;
FVoxelStrateMeasureSettings DerivedWindowSettings = Settings;
DerivedWindowSettings.InteriorMarginVoxels = -1;
bool bAllChecksPassed = true;
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
// measurement pass even though the generator-level gap probe uses the correct MC predicate.
const FStrateSlot& SolidControlSlot = World.StrateManager->GetLayout()[0];
const int32 SolidControlMinZ = (SolidControlSlot.BottomChunkZ + 1) * CHUNK_SIZE;
const int32 SolidControlMaxZ = SolidControlSlot.TopChunkZ * CHUNK_SIZE;
const int32 SolidControlMinZ = SolidControlSlot.BottomChunkZ * CHUNK_SIZE;
const int32 SolidControlMaxZ = (SolidControlSlot.TopChunkZ + 1) * CHUNK_SIZE;
FVoxelModification SolidControl;
SolidControl.Shape = EVoxelBrushShape::Box;
SolidControl.Center = FVector(
@@ -275,7 +303,7 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
SolidControl.Strength = 100.0f;
const TArray<FIntVector> SolidControlChunks = World.DiffLayer->ApplyModification(SolidControl);
const FVoxelStrateMetrics SolidControlMetrics = VF_MeasureStrate(
*World.Generator, *World.StrateManager, 0, Settings);
*World.Generator, *World.StrateManager, 0, DerivedWindowSettings);
if (SolidControlChunks.Num() == 0 || !SolidControlMetrics.bValid
|| SolidControlMetrics.AirFraction > 0.05f)
{
@@ -291,22 +319,16 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
TArray<FArchetypeReport> ArchetypeReports;
const TArray<FStrateSlot>& Layout = World.StrateManager->GetLayout();
ArchetypeReports.Reserve(Layout.Num());
for (int32 StrateIndex = 0; StrateIndex < Layout.Num(); ++StrateIndex)
{
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);
TArray<FVoxelStrateMetrics> DerivedMetricsByIndex;
DerivedMetricsByIndex.SetNum(Layout.Num());
const FVoxelStrateMetrics& Metrics = Report.First;
auto ValidateMeasurement = [&](const FString& Label, const FVoxelStrateMetrics& Metrics)
{
if (!Metrics.bValid)
{
AddError(FString::Printf(
TEXT("HARD FAILURE: %s measurement refused: %s."),
*Report.Name,
*Label,
*Metrics.RefusalReason));
bAllChecksPassed = false;
}
@@ -314,7 +336,7 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
{
AddError(FString::Printf(
TEXT("HARD FAILURE: vacuous %s measurement — sampled=%lld, air=%lld, solid=%lld."),
*Report.Name,
*Label,
Metrics.NumSampled,
Metrics.NumAir,
Metrics.NumSolid));
@@ -327,19 +349,49 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|| !FMath::IsFinite(Metrics.WalkableFraction)
|| Metrics.WalkableFraction < 0.0f || Metrics.WalkableFraction > 1.0f
|| (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(
TEXT("HARD FAILURE: range/component sanity failed for %s."), *Report.Name));
TEXT("HARD FAILURE: range/component/window sanity failed for %s."), *Label));
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(
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));
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)
@@ -355,22 +407,38 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
GapAirFraction,
GapAirSamples,
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)
{
const FVoxelStrateMetrics& Metrics = Report.First;
const FVoxelStrateMetrics& Old = Report.OldWindow;
const FVoxelStrateMetrics& Derived = Report.DerivedWindow;
Summary += FString::Printf(
TEXT(" %s: Air=%.9g, Largest=%.9g, Walkable=%.9g, FeatureScale=%.9g vox, "
"Clearance=%d vox, Components=%d, Samples=%lld, Solid=%lld\n"),
TEXT(" %s | %d | [%d,%d) | %.9g | %.9g | %.9g | %.9g | %d | %d | %lld | %lld | "
"%d | [%d,%d) | %.9g | %.9g | %.9g | %.9g | %d | %d | %lld | %lld\n"),
*Report.Name,
Metrics.AirFraction,
Metrics.LargestComponentShare,
Metrics.WalkableFraction,
Metrics.MedianFeatureScale,
Metrics.MedianVerticalClearance,
Metrics.NumAirComponents,
Metrics.NumSampled,
Metrics.NumSolid);
Old.ResolvedMarginVoxels,
Old.SampledMinZ,
Old.SampledMaxZ,
Old.AirFraction,
Old.LargestComponentShare,
Old.WalkableFraction,
Old.MedianFeatureScale,
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;
@@ -378,17 +446,24 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
for (int32 StrateIndex = 0; StrateIndex < Layout.Num(); ++StrateIndex)
{
const FStrateSlot& Slot = Layout[StrateIndex];
const int32 InteriorMinZ = (Slot.BottomChunkZ + 1) * CHUNK_SIZE;
const int32 InteriorMaxZ = Slot.TopChunkZ * CHUNK_SIZE;
if (InteriorMinZ >= InteriorMaxZ) continue;
const FVoxelStrateMetrics& Window = DerivedMetricsByIndex[StrateIndex];
if (!Window.bValid || Window.SampledMinZ >= Window.SampledMaxZ) continue;
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,
static_cast<float>(InteriorMaxZ - 2));
static_cast<float>(Window.SampledMaxZ)
- 0.5f * static_cast<float>(DerivedWindowSettings.SampleStep));
bool bCoarseLied = false;
const bool bConnected = VF_AreConnected(
*World.Generator, *World.StrateManager, StrateIndex, A, B, Settings, bCoarseLied);
*World.Generator,
*World.StrateManager,
StrateIndex,
A,
B,
DerivedWindowSettings,
bCoarseLied);
++NumRoutesChecked;
if (bCoarseLied) ++NumRoutesRefuted;
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
// remain air while the three full-resolution voxels at X=-1,0,+1 are filled solid.
const FStrateSlot& WallSlot = Layout[0];
const int32 WallInteriorMinZ = (WallSlot.BottomChunkZ + 1) * CHUNK_SIZE;
const float WallZ = static_cast<float>(WallInteriorMinZ + 2);
const FVoxelStrateMetrics& WallWindow = DerivedMetricsByIndex[0];
const float WallZ = static_cast<float>(WallWindow.SampledMinZ)
+ 0.5f * static_cast<float>(DerivedWindowSettings.SampleStep);
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);
@@ -430,7 +505,7 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
0,
WallA,
WallB,
Settings,
DerivedWindowSettings,
bWallCoarseLied);
++NumRoutesChecked;
if (bWallCoarseLied) ++NumRoutesRefuted;
@@ -455,14 +530,27 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
FPassageReport& Report = PassageReports.AddDefaulted_GetRef();
Report.Index = 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
{
if (!Layout.IsValidIndex(EndpointStrateIndex)) return TEXT("invalid-strate");
const FStrateSlot& Slot = Layout[EndpointStrateIndex];
if (!IsStrictInteriorPoint(Slot, Endpoint)) return TEXT("out-of-band");
const FVoxelStrateMetrics& EndpointMetrics =
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;
const bool bConnected = VF_AreConnected(
*World.Generator,
@@ -470,11 +558,11 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
EndpointStrateIndex,
Endpoint,
Anchor,
Settings,
DerivedWindowSettings,
bCoarseLied);
++NumRoutesChecked;
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);
@@ -486,16 +574,25 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
return A.Index < B.Index;
});
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,
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)
{
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.UpperStrateIndex,
Report.UpperPoint.X,
Report.UpperPoint.Y,
Report.UpperPoint.Z,
*Report.UpperResult,
Report.LowerStrateIndex,
Report.LowerPoint.X,
Report.LowerPoint.Y,
Report.LowerPoint.Z,
*Report.LowerResult);
}
AddInfo(Summary);
@@ -26,6 +26,10 @@ namespace VoxelStrateMeasurePrivate
float ExtentY = 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.
TArray<uint8> Air;
@@ -114,14 +118,74 @@ namespace VoxelStrateMeasurePrivate
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;
// the measurement is about the strate interior, not the boundary bedrock.
const int64 InteriorMinZ = (static_cast<int64>(Slot.BottomChunkZ) + 1) * CHUNK_SIZE;
const int64 InteriorMaxZ = static_cast<int64>(Slot.TopChunkZ) * CHUNK_SIZE;
const int64 StrateBottomZ = static_cast<int64>(Slot.BottomChunkZ) * CHUNK_SIZE;
const int64 StrateTopZ = (static_cast<int64>(Slot.TopChunkZ) + 1) * CHUNK_SIZE;
const int64 StrateHeight = StrateTopZ - StrateBottomZ;
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;
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;
@@ -188,6 +252,9 @@ namespace VoxelStrateMeasurePrivate
OutGrid.ExtentX = MaxX - MinX;
OutGrid.ExtentY = MaxY - MinY;
OutGrid.ExtentZ = MaxZ - MinZ;
OutGrid.ResolvedMarginVoxels = ResolvedMargin;
OutGrid.SampledMinZ = static_cast<int32>(InteriorMinZ);
OutGrid.SampledMaxZ = static_cast<int32>(InteriorMaxZ);
OutGrid.Air.SetNumUninitialized(OutGrid.CellCount);
bool bSawNonFiniteDensity = false;
@@ -627,6 +694,9 @@ FVoxelStrateMetrics VF_MeasureStrate(
return Result;
}
Result.ResolvedMarginVoxels = Grid.ResolvedMarginVoxels;
Result.SampledMinZ = Grid.SampledMinZ;
Result.SampledMaxZ = Grid.SampledMaxZ;
VoxelStrateMeasurePrivate::BuildMetricsFromGrid(Grid, Settings, Result);
Result.bValid = Result.NumSampled > 0;
if (!Result.bValid)
@@ -15,6 +15,7 @@ struct VOXELFORGE_API FVoxelStrateMeasureSettings
FVector2D CenterXY = FVector2D::ZeroVector; // Actor-space voxel coordinates.
int32 MaxCells = 8000000; // Refuse a grid larger than this many cells.
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. */
@@ -33,13 +34,19 @@ struct VOXELFORGE_API FVoxelStrateMetrics
float WalkableFraction = 0.f;
float MedianFeatureScale = 0.f;
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.
*
* The pass samples only the interior chunks of the requested strate, flood-fills its air once,
* and derives all returned metrics from that same grid. It never mutates either input object.
* The pass samples the requested strate between a resolved margin inside its top and bottom
* 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(
const UVoxelGenerator& Generator,