fix(measure): ask the primordial law the right question

Three defects, two of them mine.

1. The anchor was a guess. LargestComponentAnchor() returned a hardcoded
   point near the (0,0) spine and the comment admitted it, so
   NOT_CONNECTED meant "not connected to a guessed point". The flood fill
   already knows the answer; it now returns LargestComponentPoint (tie-
   broken by lowest cell index, so it stays deterministic),
   LargestComponentCells and NumComponentsAtLeast1Pct.

2. "Connected to the largest component" is NOT the primordial law. The
   law is that a player must not descend into a place they cannot leave
   -- a question about MOUTHS. A strate can be 57 disconnected shafts and
   be fine, provided the shaft you arrive in is the shaft you leave from.
   The test now asks arrival -> departure per strate: the LowerPoint of
   the passage arriving, to the UpperPoint of the passage departing.

3. A false verdict and a true one were indistinguishable. VF_AreConnected
   returned false both when no route existed and when the endpoint's own
   coarse cell sampled solid -- and a passage mouth is a carved tube whose
   cell centre lands in adjacent rock easily. It now returns
   EVoxelConnectivityResult and snaps to an adjacent air cell first,
   reporting the repair.

RESULT: the earlier "5 of 15 endpoints disconnected" was an ARTIFACT of
the guessed anchor plus the wrong question. Corrected: 13 of 15 connected,
0 StartCellSolid, 0 GoalCellSolid.

One real finding survives. arrival -> departure passes for 5 of 6
applicable strates; VerticalShafts FAILS -- its arrival mouth is in the
largest component, its departure mouth is not. NOT yet reported as a bug:
a coarse grid can falsely SEPARATE as well as falsely join, and a
connecting corridor thinner than SampleStep vanishes. Next task settles
it at a finer step.

Also noted: GetPassages() contains a self-referential surface entry
(0 -> 0), excluded from the chain law.

Build clean, 18 succeeded / 0 warnings / 0 failed, verified in VoxelM.log.

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 14:06:43 +02:00
1 parent 13399529b5
commit efcc8fcb65
4 files changed
+636 -150

No files matched your search

+8 -5
View File
@@ -307,15 +307,18 @@ Headless, read-only Tier 2 measurement pass. `VF_MeasureStrate` samples one stra
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.
the inclusive/exclusive voxel Z window used, plus the largest component's deterministic lowest-cell
representative point, cell count, and count of components holding at least 1% of the air.
`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.
only that route at full voxel resolution before returning `Connected`. It returns an explicit
`EVoxelConnectivityResult`; a solid endpoint cell is repaired by snapping to the nearest air cell
among its 26 neighbours when possible, and reports the snap through its out flags. 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; callers may override the interior margin, and results identify the resolved margin/Z window. |
| `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 and deterministic component representatives. |
| `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, with explicit endpoint-solid, out-of-window, coarse-lie, and connectivity outcomes. |
### 3.7 Cave morphology (SDF rooms/tunnels) — `Public/VoxelCaveMorphology.h` + `.cpp`
Header is rich with inline docs. Two namespaces + a per-chunk cache system.
@@ -25,16 +25,27 @@ namespace
FVoxelStrateMetrics DerivedWindowRepeat;
};
struct FPassageReport
struct FConnectivityProbe
{
bool bChecked = false;
EVoxelConnectivityResult Result = EVoxelConnectivityResult::OutOfWindow;
bool bStartSnapped = false;
bool bGoalSnapped = false;
};
struct FStrateConnectivityReport
{
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;
int32 ArrivalPassageIndex = INDEX_NONE;
int32 DeparturePassageIndex = INDEX_NONE;
FVector ArrivalPoint = FVector::ZeroVector;
FVector DeparturePoint = FVector::ZeroVector;
bool bHasArrival = false;
bool bHasDeparture = false;
FConnectivityProbe ArrivalToDeparture;
FConnectivityProbe ArrivalToLargest;
FConnectivityProbe DepartureToLargest;
};
FString ArchetypeName(const FStrateSlot& Slot)
@@ -55,6 +66,13 @@ namespace
return FMemory::Memcmp(&A, &B, sizeof(float)) == 0;
}
bool SameVectorBits(const FVector& A, const FVector& B)
{
return SameFloatBits(A.X, B.X)
&& SameFloatBits(A.Y, B.Y)
&& SameFloatBits(A.Z, B.Z);
}
bool MetricsAreBitIdentical(const FVoxelStrateMetrics& A, const FVoxelStrateMetrics& B)
{
return A.bValid == B.bValid
@@ -65,6 +83,9 @@ namespace
&& SameFloatBits(A.AirFraction, B.AirFraction)
&& A.NumAirComponents == B.NumAirComponents
&& SameFloatBits(A.LargestComponentShare, B.LargestComponentShare)
&& SameVectorBits(A.LargestComponentPoint, B.LargestComponentPoint)
&& A.LargestComponentCells == B.LargestComponentCells
&& A.NumComponentsAtLeast1Pct == B.NumComponentsAtLeast1Pct
&& SameFloatBits(A.WalkableFraction, B.WalkableFraction)
&& SameFloatBits(A.MedianFeatureScale, B.MedianFeatureScale)
&& A.MedianVerticalClearance == B.MedianVerticalClearance
@@ -73,34 +94,45 @@ namespace
&& A.SampledMaxZ == B.SampledMaxZ;
}
bool IsInsideMeasuredWindow(
const FVoxelStrateMetrics& Metrics,
const FVoxelStrateMeasureSettings& Settings,
const FVector& Point)
const TCHAR* ConnectivityResultName(EVoxelConnectivityResult Result)
{
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);
switch (Result)
{
case EVoxelConnectivityResult::Connected: return TEXT("CONNECTED");
case EVoxelConnectivityResult::NotConnected: return TEXT("NOT_CONNECTED");
case EVoxelConnectivityResult::StartCellSolid: return TEXT("START_CELL_SOLID");
case EVoxelConnectivityResult::GoalCellSolid: return TEXT("GOAL_CELL_SOLID");
case EVoxelConnectivityResult::OutOfWindow: return TEXT("OUT_OF_WINDOW");
case EVoxelConnectivityResult::CoarseLied: return TEXT("COARSE_LIED");
default: return TEXT("UNKNOWN");
}
}
FVector LargestComponentAnchor(
const FStrateSlot& Slot,
const FVector2D& CenterXY,
int32 SampleStep)
FString ConnectivityProbeText(const FConnectivityProbe& Probe)
{
// 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.
// 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);
if (!Probe.bChecked)
{
return TEXT("NOT_CHECKED");
}
FString Text = ConnectivityResultName(Probe.Result);
if (Probe.bStartSnapped || Probe.bGoalSnapped)
{
Text += TEXT(" [");
bool bNeedSeparator = false;
if (Probe.bStartSnapped)
{
Text += TEXT("start snapped");
bNeedSeparator = true;
}
if (Probe.bGoalSnapped)
{
if (bNeedSeparator) Text += TEXT(", ");
Text += TEXT("goal snapped");
}
Text += TEXT("]");
}
return Text;
}
bool FullResolutionDirectSegmentHasSolid(
@@ -239,8 +271,8 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
* 2. Vacuity: a main measurement with no samples, no air, or no solid measures nothing.
* 3. Range sanity: fractions/components must describe an actual air field.
* 4. Determinism: identical inputs must produce bit-identical metrics.
* 5. Coarse-lie guard: a coarse route must never be reported connected after a full-resolution
* solid sample; the controlled 3-voxel wall below must make bOutCoarseLied fire.
* 5. Coarse-lie guard: a coarse route must never be reported CONNECTED after a
* full-resolution solid sample; the controlled 3-voxel wall below must return COARSE_LIED.
*/
int32 GapTopChunkZ = 0;
@@ -349,6 +381,15 @@ 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.LargestComponentCells < 0
|| Metrics.LargestComponentCells > Metrics.NumAir
|| Metrics.NumComponentsAtLeast1Pct < 0
|| Metrics.NumComponentsAtLeast1Pct > Metrics.NumAirComponents
|| (Metrics.NumAir > 0 && Metrics.LargestComponentCells == 0)
|| (Metrics.NumAir > 0
&& (!FMath::IsFinite(Metrics.LargestComponentPoint.X)
|| !FMath::IsFinite(Metrics.LargestComponentPoint.Y)
|| !FMath::IsFinite(Metrics.LargestComponentPoint.Z)))
|| (Metrics.LargestComponentShare == 0.0f && Metrics.AirFraction > 0.05f)
|| Metrics.ResolvedMarginVoxels < 1
|| Metrics.SampledMinZ >= Metrics.SampledMaxZ)
@@ -408,20 +449,22 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
GapAirSamples,
GapSamples);
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");
Summary += TEXT(" name | old margin | old Z span [min,max) | old Air | old Largest | old LargestCells | old >=1% | 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 LargestCells | derived >=1% | derived Walkable | derived FeatureScale vox | derived Clearance vox | derived Components | derived Samples | derived Solid\n");
for (const FArchetypeReport& Report : ArchetypeReports)
{
const FVoxelStrateMetrics& Old = Report.OldWindow;
const FVoxelStrateMetrics& Derived = Report.DerivedWindow;
Summary += FString::Printf(
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"),
TEXT(" %s | %d | [%d,%d) | %.9g | %.9g | %lld | %d | %.9g | %.9g | %d | %d | %lld | %lld | "
"%d | [%d,%d) | %.9g | %.9g | %lld | %d | %.9g | %.9g | %d | %d | %lld | %lld\n"),
*Report.Name,
Old.ResolvedMarginVoxels,
Old.SampledMinZ,
Old.SampledMaxZ,
Old.AirFraction,
Old.LargestComponentShare,
Old.LargestComponentCells,
Old.NumComponentsAtLeast1Pct,
Old.WalkableFraction,
Old.MedianFeatureScale,
Old.MedianVerticalClearance,
@@ -433,6 +476,8 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
Derived.SampledMaxZ,
Derived.AirFraction,
Derived.LargestComponentShare,
Derived.LargestComponentCells,
Derived.NumComponentsAtLeast1Pct,
Derived.WalkableFraction,
Derived.MedianFeatureScale,
Derived.MedianVerticalClearance,
@@ -443,6 +488,81 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
int32 NumRoutesChecked = 0;
int32 NumRoutesRefuted = 0;
int32 NumGuardRoutesChecked = 0;
int32 NumGuardRoutesRefuted = 0;
int32 NumSnappedEndpointEvents = 0;
int32 NumArrivalDepartureRoutesChecked = 0;
int32 NumArrivalDepartureSnappedEndpointEvents = 0;
int32 NumAnchorProbes = 0;
int32 NumAnchorConnected = 0;
int32 NumAnchorNotConnected = 0;
int32 NumAnchorStartCellSolid = 0;
int32 NumAnchorGoalCellSolid = 0;
int32 NumAnchorOutOfWindow = 0;
int32 NumAnchorCoarseLied = 0;
int32 NumAnchorSnappedEndpointEvents = 0;
auto ProbeRoute = [&](int32 StrateIndex,
const FVector& Start,
const FVector& Goal,
bool bCountInGuard,
bool bCountAsAnchorProbe,
bool bCountAsArrivalDeparture) -> FConnectivityProbe
{
FConnectivityProbe Probe;
Probe.bChecked = true;
Probe.Result = VF_AreConnected(
*World.Generator,
*World.StrateManager,
StrateIndex,
Start,
Goal,
DerivedWindowSettings,
Probe.bStartSnapped,
Probe.bGoalSnapped);
++NumRoutesChecked;
if (Probe.Result == EVoxelConnectivityResult::CoarseLied)
{
++NumRoutesRefuted;
}
if (bCountInGuard)
{
++NumGuardRoutesChecked;
if (Probe.Result == EVoxelConnectivityResult::CoarseLied)
{
++NumGuardRoutesRefuted;
}
}
const int32 SnappedHere = (Probe.bStartSnapped ? 1 : 0)
+ (Probe.bGoalSnapped ? 1 : 0);
NumSnappedEndpointEvents += SnappedHere;
if (bCountAsArrivalDeparture)
{
++NumArrivalDepartureRoutesChecked;
NumArrivalDepartureSnappedEndpointEvents += SnappedHere;
}
if (bCountAsAnchorProbe)
{
++NumAnchorProbes;
NumAnchorSnappedEndpointEvents += SnappedHere;
switch (Probe.Result)
{
case EVoxelConnectivityResult::Connected: ++NumAnchorConnected; break;
case EVoxelConnectivityResult::NotConnected: ++NumAnchorNotConnected; break;
case EVoxelConnectivityResult::StartCellSolid: ++NumAnchorStartCellSolid; break;
case EVoxelConnectivityResult::GoalCellSolid: ++NumAnchorGoalCellSolid; break;
case EVoxelConnectivityResult::OutOfWindow: ++NumAnchorOutOfWindow; break;
case EVoxelConnectivityResult::CoarseLied: ++NumAnchorCoarseLied; break;
default: break;
}
}
return Probe;
};
for (int32 StrateIndex = 0; StrateIndex < Layout.Num(); ++StrateIndex)
{
const FStrateSlot& Slot = Layout[StrateIndex];
@@ -455,22 +575,15 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
const FVector B(Settings.CenterXY.X + 2.0f, Settings.CenterXY.Y + 2.0f,
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,
DerivedWindowSettings,
bCoarseLied);
++NumRoutesChecked;
if (bCoarseLied) ++NumRoutesRefuted;
if (bConnected && FullResolutionDirectSegmentHasSolid(*World.Generator, A, B))
const FConnectivityProbe Probe = ProbeRoute(
StrateIndex, A, B, /*bCountInGuard=*/true, /*bCountAsAnchorProbe=*/false,
/*bCountAsArrivalDeparture=*/false);
if (Probe.Result == EVoxelConnectivityResult::Connected
&& FullResolutionDirectSegmentHasSolid(*World.Generator, A, B))
{
AddError(FString::Printf(
TEXT("HARD FAILURE: VF_AreConnected returned true for %s while its full-resolution "
"direct walk contained a solid sample."),
TEXT("HARD FAILURE: VF_AreConnected returned CONNECTED for %s while its "
"full-resolution direct walk contained a solid sample."),
*ArchetypeName(Slot)));
bAllChecksPassed = false;
}
@@ -498,102 +611,315 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
bAllChecksPassed = false;
}
bool bWallCoarseLied = false;
const bool bWallConnected = VF_AreConnected(
*World.Generator,
*World.StrateManager,
0,
WallA,
WallB,
DerivedWindowSettings,
bWallCoarseLied);
++NumRoutesChecked;
if (bWallCoarseLied) ++NumRoutesRefuted;
if (bWallConnected)
const FConnectivityProbe WallProbe = ProbeRoute(
0, WallA, WallB, /*bCountInGuard=*/true, /*bCountAsAnchorProbe=*/false,
/*bCountAsArrivalDeparture=*/false);
if (WallProbe.Result != EVoxelConnectivityResult::CoarseLied)
{
AddError(TEXT("HARD FAILURE: VF_AreConnected reported true across a full-resolution solid 3-voxel wall."));
bAllChecksPassed = false;
}
if (!bWallCoarseLied)
{
AddError(TEXT("HARD FAILURE: the controlled coarse-connectivity lie did not set bOutCoarseLied."));
AddError(FString::Printf(
TEXT("HARD FAILURE: the controlled coarse-connectivity lie returned %s instead of "
"COARSE_LIED."),
ConnectivityResultName(WallProbe.Result)));
bAllChecksPassed = false;
}
World.DiffLayer->Clear();
TArray<FPassageReport> PassageReports;
const TArray<FVoxelPassage>& Passages = World.StrateManager->GetPassages();
PassageReports.Reserve(Passages.Num());
TArray<int32> ArrivalPassageByStrate;
TArray<int32> DeparturePassageByStrate;
ArrivalPassageByStrate.Init(INDEX_NONE, Layout.Num());
DeparturePassageByStrate.Init(INDEX_NONE, Layout.Num());
int32 SurfaceEntryPassageIndex = INDEX_NONE;
for (int32 PassageIndex = 0; PassageIndex < Passages.Num(); ++PassageIndex)
{
const FVoxelPassage& Passage = Passages[PassageIndex];
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 (Passage.UpperStrateIndex == Passage.LowerStrateIndex)
{
if (!Layout.IsValidIndex(EndpointStrateIndex)) return TEXT("invalid-strate");
const FVoxelStrateMetrics& EndpointMetrics =
DerivedMetricsByIndex[EndpointStrateIndex];
if (!EndpointMetrics.bValid) return TEXT("invalid-window");
if (!IsInsideMeasuredWindow(EndpointMetrics, DerivedWindowSettings, Endpoint))
if (SurfaceEntryPassageIndex == INDEX_NONE)
{
return TEXT("out-of-window");
SurfaceEntryPassageIndex = PassageIndex;
}
continue;
}
const FStrateSlot& Slot = Layout[EndpointStrateIndex];
const FVector Anchor = LargestComponentAnchor(
Slot,
DerivedWindowSettings.CenterXY,
DerivedWindowSettings.SampleStep);
bool bCoarseLied = false;
const bool bConnected = VF_AreConnected(
*World.Generator,
*World.StrateManager,
EndpointStrateIndex,
Endpoint,
Anchor,
DerivedWindowSettings,
bCoarseLied);
++NumRoutesChecked;
if (bCoarseLied) ++NumRoutesRefuted;
return bConnected ? TEXT("CONNECTED_TO_LARGEST") : TEXT("NOT_CONNECTED");
};
Report.UpperResult = CheckEndpoint(Passage.UpperStrateIndex, Passage.UpperPoint);
Report.LowerResult = CheckEndpoint(Passage.LowerStrateIndex, Passage.LowerPoint);
if (Passage.LowerStrateIndex != Passage.UpperStrateIndex + 1)
{
AddError(FString::Printf(
TEXT("HARD FAILURE: passage %d is not between consecutive strates (%d -> %d)."),
PassageIndex,
Passage.UpperStrateIndex,
Passage.LowerStrateIndex));
bAllChecksPassed = false;
continue;
}
PassageReports.Sort([](const FPassageReport& A, const FPassageReport& B)
if (!Layout.IsValidIndex(Passage.UpperStrateIndex)
|| !Layout.IsValidIndex(Passage.LowerStrateIndex))
{
return A.Index < B.Index;
});
AddError(FString::Printf(
TEXT("HARD FAILURE: passage %d references a strate outside the layout (%d -> %d)."),
PassageIndex,
Passage.UpperStrateIndex,
Passage.LowerStrateIndex));
bAllChecksPassed = false;
continue;
}
if (ArrivalPassageByStrate[Passage.LowerStrateIndex] != INDEX_NONE)
{
AddError(FString::Printf(
TEXT("HARD FAILURE: strate %d has more than one arrival passage."),
Passage.LowerStrateIndex));
bAllChecksPassed = false;
}
else
{
ArrivalPassageByStrate[Passage.LowerStrateIndex] = PassageIndex;
}
if (DeparturePassageByStrate[Passage.UpperStrateIndex] != INDEX_NONE)
{
AddError(FString::Printf(
TEXT("HARD FAILURE: strate %d has more than one departure passage."),
Passage.UpperStrateIndex));
bAllChecksPassed = false;
}
else
{
DeparturePassageByStrate[Passage.UpperStrateIndex] = PassageIndex;
}
}
for (int32 StrateIndex = 0; StrateIndex < Layout.Num(); ++StrateIndex)
{
if (StrateIndex > 0 && ArrivalPassageByStrate[StrateIndex] == INDEX_NONE)
{
AddError(FString::Printf(
TEXT("HARD FAILURE: non-topmost strate %d has no arrival passage."),
StrateIndex));
bAllChecksPassed = false;
}
if (StrateIndex + 1 < Layout.Num()
&& DeparturePassageByStrate[StrateIndex] == INDEX_NONE)
{
AddError(FString::Printf(
TEXT("HARD FAILURE: non-bottom-most strate %d has no departure passage."),
StrateIndex));
bAllChecksPassed = false;
}
}
TArray<FStrateConnectivityReport> StrateReports;
StrateReports.SetNum(Layout.Num());
for (int32 StrateIndex = 0; StrateIndex < Layout.Num(); ++StrateIndex)
{
FStrateConnectivityReport& Report = StrateReports[StrateIndex];
Report.Index = StrateIndex;
Report.Name = ArchetypeName(Layout[StrateIndex]);
if (ArrivalPassageByStrate[StrateIndex] != INDEX_NONE)
{
Report.bHasArrival = true;
Report.ArrivalPassageIndex = ArrivalPassageByStrate[StrateIndex];
Report.ArrivalPoint = Passages[Report.ArrivalPassageIndex].LowerPoint;
}
if (DeparturePassageByStrate[StrateIndex] != INDEX_NONE)
{
Report.bHasDeparture = true;
Report.DeparturePassageIndex = DeparturePassageByStrate[StrateIndex];
Report.DeparturePoint = Passages[Report.DeparturePassageIndex].UpperPoint;
}
}
// The actual largest-component comparison uses the representative point returned by the
// measurement API. The surface-entry lower mouth remains an auxiliary guard probe so this
// task continues to re-report the established 15 endpoint comparison probes; it is not the
// topmost strate's chain arrival.
for (FStrateConnectivityReport& Report : StrateReports)
{
const FVoxelStrateMetrics& Metrics = DerivedMetricsByIndex[Report.Index];
if (!Metrics.bValid) continue;
if (Report.bHasArrival)
{
Report.ArrivalToLargest = ProbeRoute(
Report.Index,
Report.ArrivalPoint,
Metrics.LargestComponentPoint,
/*bCountInGuard=*/true,
/*bCountAsAnchorProbe=*/true,
/*bCountAsArrivalDeparture=*/false);
}
if (Report.bHasDeparture)
{
Report.DepartureToLargest = ProbeRoute(
Report.Index,
Report.DeparturePoint,
Metrics.LargestComponentPoint,
/*bCountInGuard=*/true,
/*bCountAsAnchorProbe=*/true,
/*bCountAsArrivalDeparture=*/false);
}
}
if (SurfaceEntryPassageIndex != INDEX_NONE && Layout.IsValidIndex(0)
&& DerivedMetricsByIndex[0].bValid)
{
const FVoxelPassage& SurfaceEntry = Passages[SurfaceEntryPassageIndex];
ProbeRoute(
0,
SurfaceEntry.LowerPoint,
DerivedMetricsByIndex[0].LargestComponentPoint,
/*bCountInGuard=*/true,
/*bCountAsAnchorProbe=*/true,
/*bCountAsArrivalDeparture=*/false);
}
else
{
AddError(TEXT("HARD FAILURE: the surface-entry auxiliary endpoint probe is missing."));
bAllChecksPassed = false;
}
int32 NumArrivalDeparturePasses = 0;
TArray<FString> FailedStrates;
for (FStrateConnectivityReport& Report : StrateReports)
{
if (!Report.bHasArrival || !Report.bHasDeparture)
{
continue;
}
Report.ArrivalToDeparture = ProbeRoute(
Report.Index,
Report.ArrivalPoint,
Report.DeparturePoint,
/*bCountInGuard=*/false,
/*bCountAsAnchorProbe=*/false,
/*bCountAsArrivalDeparture=*/true);
if (Report.ArrivalToDeparture.Result == EVoxelConnectivityResult::Connected)
{
++NumArrivalDeparturePasses;
}
else
{
const FString ResultText = ConnectivityProbeText(Report.ArrivalToDeparture);
FailedStrates.Add(FString::Printf(
TEXT("%d:%s (%s)"),
Report.Index,
*Report.Name,
*ResultText));
}
}
FString FailedStrateText = TEXT("none");
if (FailedStrates.Num() > 0)
{
FailedStrateText = FString::Join(FailedStrates, TEXT(", "));
}
Summary += FString::Printf(
TEXT("Connectivity guard (derived window): %d routes checked, %d coarse routes refuted at full resolution.\n"),
TEXT("Coarse-lie guard (8 interior routes + wall control + 15 endpoint->largest probes): "
"%d routes checked, %d coarse routes refuted at full resolution.\n"),
NumGuardRoutesChecked,
NumGuardRoutesRefuted);
Summary += FString::Printf(
TEXT("All connectivity probes (including arrival->departure law queries): %d routes "
"checked, %d coarse routes refuted at full resolution.\n"),
NumRoutesChecked,
NumRoutesRefuted);
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: UpperPoint strate=%d (%.3f,%.3f,%.3f) => %s; "
"LowerPoint strate=%d (%.3f,%.3f,%.3f) => %s\n"),
TEXT("Endpoint snap repair: %d snapped endpoint events across all probes; %d in "
"%d arrival->departure probes.\n"),
NumSnappedEndpointEvents,
NumArrivalDepartureSnappedEndpointEvents,
NumArrivalDepartureRoutesChecked);
Summary += FString::Printf(
TEXT("Correct largest-component endpoint probes (%d, including the surface-entry auxiliary): "
"connected=%d, not-connected=%d, start-cell-solid=%d, goal-cell-solid=%d, "
"out-of-window=%d, coarse-lied=%d, snapped endpoint events=%d.\n"),
NumAnchorProbes,
NumAnchorConnected,
NumAnchorNotConnected,
NumAnchorStartCellSolid,
NumAnchorGoalCellSolid,
NumAnchorOutOfWindow,
NumAnchorCoarseLied,
NumAnchorSnappedEndpointEvents);
Summary += TEXT("Previous \"5 of 15 disconnected\" verdict: ARTIFACT as a claim about sealed "
"pockets; it used a guessed anchor and asked the wrong (largest-component) law.\n");
Summary += FString::Printf(
TEXT("arrival->departure: %d of %d applicable strates pass; failures: %s.\n"),
NumArrivalDeparturePasses,
NumArrivalDepartureRoutesChecked,
*FailedStrateText);
Summary += TEXT("Strates (chain order; derived window; largest point is the deterministic "
"lowest-cell representative):\n");
Summary += TEXT(" Strate | arrival->departure | arrival->largest | departure->largest | components | largest share | largest cells | >=1% | largest point\n");
for (const FStrateConnectivityReport& Report : StrateReports)
{
const FVoxelStrateMetrics& Metrics = DerivedMetricsByIndex[Report.Index];
FString ArrivalToDeparture;
if (Report.bHasArrival && Report.bHasDeparture)
{
ArrivalToDeparture = ConnectivityProbeText(Report.ArrivalToDeparture);
}
else if (!Report.bHasArrival && Report.Index == 0)
{
ArrivalToDeparture = TEXT("N/A (topmost; no arrival)");
}
else if (!Report.bHasDeparture && Report.Index + 1 == StrateReports.Num())
{
ArrivalToDeparture = TEXT("N/A (bottom-most; no departure)");
}
else
{
ArrivalToDeparture = TEXT("MISSING_CHAIN_MOUTH");
}
FString ArrivalToLargest;
if (Report.bHasArrival)
{
ArrivalToLargest = ConnectivityProbeText(Report.ArrivalToLargest);
}
else if (Report.Index == 0)
{
ArrivalToLargest = TEXT("N/A (topmost; no arrival)");
}
else
{
ArrivalToLargest = TEXT("MISSING_ARRIVAL");
}
FString DepartureToLargest;
if (Report.bHasDeparture)
{
DepartureToLargest = ConnectivityProbeText(Report.DepartureToLargest);
}
else if (Report.Index + 1 == StrateReports.Num())
{
DepartureToLargest = TEXT("N/A (bottom-most; no departure)");
}
else
{
DepartureToLargest = TEXT("MISSING_DEPARTURE");
}
Summary += FString::Printf(
TEXT(" %d %s | %s | %s | %s | %d | %.9g | %lld | %d | (%.3f,%.3f,%.3f)\n"),
Report.Index,
*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);
*ArrivalToDeparture,
*ArrivalToLargest,
*DepartureToLargest,
Metrics.NumAirComponents,
Metrics.LargestComponentShare,
Metrics.LargestComponentCells,
Metrics.NumComponentsAtLeast1Pct,
Metrics.LargestComponentPoint.X,
Metrics.LargestComponentPoint.Y,
Metrics.LargestComponentPoint.Z);
}
AddInfo(Summary);
+156 -16
View File
@@ -298,7 +298,10 @@ namespace VoxelStrateMeasurePrivate
const FSampleGrid& Grid,
TArray<int32>& OutComponents,
int32& OutNumComponents,
int32& OutLargestComponentCells)
int64 NumAir,
int64& OutLargestComponentCells,
int32& OutLargestComponentLowestCell,
int32& OutNumComponentsAtLeast1Pct)
{
OutComponents.SetNumUninitialized(Grid.CellCount);
for (int32 Index = 0; Index < Grid.CellCount; ++Index)
@@ -310,6 +313,8 @@ namespace VoxelStrateMeasurePrivate
Queue.SetNumUninitialized(Grid.CellCount);
OutNumComponents = 0;
OutLargestComponentCells = 0;
OutLargestComponentLowestCell = INDEX_NONE;
OutNumComponentsAtLeast1Pct = 0;
for (int32 Start = 0; Start < Grid.CellCount; ++Start)
{
@@ -353,7 +358,20 @@ namespace VoxelStrateMeasurePrivate
if (Z > 0) Visit(Grid.Index(X, Y, Z - 1));
}
OutLargestComponentCells = FMath::Max(OutLargestComponentCells, ComponentCells);
const int64 ComponentCells64 = static_cast<int64>(ComponentCells);
if (NumAir > 0 && ComponentCells64 * 100 >= NumAir)
{
++OutNumComponentsAtLeast1Pct;
}
if (ComponentCells64 > OutLargestComponentCells
|| (ComponentCells64 == OutLargestComponentCells
&& (OutLargestComponentLowestCell == INDEX_NONE
|| Start < OutLargestComponentLowestCell)))
{
OutLargestComponentCells = ComponentCells64;
OutLargestComponentLowestCell = Start;
}
}
}
@@ -429,8 +447,6 @@ namespace VoxelStrateMeasurePrivate
{
TArray<int32> Components;
int32 NumComponents = 0;
int32 LargestComponentCells = 0;
FloodFillAir(Grid, Components, NumComponents, LargestComponentCells);
int64 NumAir = 0;
for (const uint8 bAir : Grid.Air)
@@ -438,16 +454,38 @@ namespace VoxelStrateMeasurePrivate
NumAir += bAir != 0u ? 1 : 0;
}
int64 LargestComponentCells = 0;
int32 LargestComponentLowestCell = INDEX_NONE;
int32 NumComponentsAtLeast1Pct = 0;
FloodFillAir(
Grid,
Components,
NumComponents,
NumAir,
LargestComponentCells,
LargestComponentLowestCell,
NumComponentsAtLeast1Pct);
InOutMetrics.NumSampled = Grid.CellCount;
InOutMetrics.NumAir = NumAir;
InOutMetrics.NumSolid = static_cast<int64>(Grid.CellCount) - NumAir;
InOutMetrics.AirFraction = static_cast<float>(
static_cast<double>(NumAir) / static_cast<double>(Grid.CellCount));
InOutMetrics.NumAirComponents = NumComponents;
InOutMetrics.LargestComponentCells = LargestComponentCells;
InOutMetrics.NumComponentsAtLeast1Pct = NumComponentsAtLeast1Pct;
InOutMetrics.LargestComponentShare = NumAir > 0
? static_cast<float>(static_cast<double>(LargestComponentCells)
/ static_cast<double>(NumAir))
: 0.0f;
if (NumAir > 0 && LargestComponentLowestCell != INDEX_NONE)
{
int32 LargestX = 0;
int32 LargestY = 0;
int32 LargestZ = 0;
DecodeIndex(Grid, LargestComponentLowestCell, LargestX, LargestY, LargestZ);
InOutMetrics.LargestComponentPoint = Grid.CellCenter(LargestX, LargestY, LargestZ);
}
TArray<int32> ClearanceValues;
ClearanceValues.Reserve(static_cast<int32>(NumAir));
@@ -537,6 +575,93 @@ namespace VoxelStrateMeasurePrivate
return true;
}
enum class EEndpointCellResult : uint8
{
OutOfWindow,
Air,
Solid
};
EEndpointCellResult ResolveEndpointCell(
const FSampleGrid& Grid,
const FVector& Point,
int32& OutCell,
bool& bOutSnapped)
{
bOutSnapped = false;
int32 OriginalCell = -1;
if (!FindCellForPoint(Grid, Point, OriginalCell))
{
return EEndpointCellResult::OutOfWindow;
}
if (Grid.Air[OriginalCell] != 0u)
{
OutCell = OriginalCell;
return EEndpointCellResult::Air;
}
int32 OriginalX = 0;
int32 OriginalY = 0;
int32 OriginalZ = 0;
DecodeIndex(Grid, OriginalCell, OriginalX, OriginalY, OriginalZ);
int32 BestCell = INDEX_NONE;
float BestDistanceSquared = FLT_MAX;
const int32 MaxX = Grid.NumX - 1;
const int32 MaxY = Grid.NumY - 1;
const int32 MaxZ = Grid.NumZ - 1;
const int32 MinX = OriginalX > 0 ? OriginalX - 1 : 0;
const int32 MinY = OriginalY > 0 ? OriginalY - 1 : 0;
const int32 MinZ = OriginalZ > 0 ? OriginalZ - 1 : 0;
const int32 CandidateMaxX = OriginalX < MaxX ? OriginalX + 1 : MaxX;
const int32 CandidateMaxY = OriginalY < MaxY ? OriginalY + 1 : MaxY;
const int32 CandidateMaxZ = OriginalZ < MaxZ ? OriginalZ + 1 : MaxZ;
for (int32 Z = MinZ;
Z <= CandidateMaxZ;
++Z)
{
for (int32 Y = MinY;
Y <= CandidateMaxY;
++Y)
{
for (int32 X = MinX;
X <= CandidateMaxX;
++X)
{
if (X == OriginalX && Y == OriginalY && Z == OriginalZ)
{
continue;
}
const int32 Candidate = Grid.Index(X, Y, Z);
if (Grid.Air[Candidate] == 0u)
{
continue;
}
const float DistanceSquared = FVector::DistSquared(
Point, Grid.CellCenter(X, Y, Z));
if (DistanceSquared < BestDistanceSquared
|| (DistanceSquared == BestDistanceSquared
&& (BestCell == INDEX_NONE || Candidate < BestCell)))
{
BestDistanceSquared = DistanceSquared;
BestCell = Candidate;
}
}
}
}
if (BestCell == INDEX_NONE)
{
return EEndpointCellResult::Solid;
}
OutCell = BestCell;
bOutSnapped = true;
return EEndpointCellResult::Air;
}
bool FullResolutionAirOnSegment(
const UVoxelGenerator& Generator,
const FVector& Start,
@@ -706,20 +831,22 @@ FVoxelStrateMetrics VF_MeasureStrate(
return Result;
}
bool VF_AreConnected(
EVoxelConnectivityResult VF_AreConnected(
const UVoxelGenerator& Generator,
const UVoxelStrateManager& Manager,
int32 StrateIndex,
const FVector& AVoxel,
const FVector& BVoxel,
const FVoxelStrateMeasureSettings& Settings,
bool& bOutCoarseLied)
bool& bOutStartSnapped,
bool& bOutGoalSnapped)
{
bOutCoarseLied = false;
bOutStartSnapped = false;
bOutGoalSnapped = false;
if (!FMath::IsFinite(AVoxel.X) || !FMath::IsFinite(AVoxel.Y) || !FMath::IsFinite(AVoxel.Z)
|| !FMath::IsFinite(BVoxel.X) || !FMath::IsFinite(BVoxel.Y) || !FMath::IsFinite(BVoxel.Z))
{
return false;
return EVoxelConnectivityResult::OutOfWindow;
}
VoxelStrateMeasurePrivate::FSampleGrid Grid;
@@ -727,21 +854,35 @@ bool VF_AreConnected(
if (!VoxelStrateMeasurePrivate::BuildSampleGrid(
Generator, Manager, StrateIndex, Settings, Grid, RefusalReason))
{
return false;
return EVoxelConnectivityResult::OutOfWindow;
}
int32 Start = -1;
int32 Goal = -1;
if (!VoxelStrateMeasurePrivate::FindCellForPoint(Grid, AVoxel, Start)
|| !VoxelStrateMeasurePrivate::FindCellForPoint(Grid, BVoxel, Goal))
const VoxelStrateMeasurePrivate::EEndpointCellResult StartCell =
VoxelStrateMeasurePrivate::ResolveEndpointCell(
Grid, AVoxel, Start, bOutStartSnapped);
const VoxelStrateMeasurePrivate::EEndpointCellResult GoalCell =
VoxelStrateMeasurePrivate::ResolveEndpointCell(
Grid, BVoxel, Goal, bOutGoalSnapped);
if (StartCell == VoxelStrateMeasurePrivate::EEndpointCellResult::OutOfWindow
|| GoalCell == VoxelStrateMeasurePrivate::EEndpointCellResult::OutOfWindow)
{
return false;
return EVoxelConnectivityResult::OutOfWindow;
}
if (StartCell == VoxelStrateMeasurePrivate::EEndpointCellResult::Solid)
{
return EVoxelConnectivityResult::StartCellSolid;
}
if (GoalCell == VoxelStrateMeasurePrivate::EEndpointCellResult::Solid)
{
return EVoxelConnectivityResult::GoalCellSolid;
}
TArray<int32> Path;
if (!VoxelStrateMeasurePrivate::FindCoarsePath(Grid, Start, Goal, Path))
{
return false;
return EVoxelConnectivityResult::NotConnected;
}
// The coarse route is evidence, not a pass. Re-check this one BFS route at one-voxel spacing
@@ -749,8 +890,7 @@ bool VF_AreConnected(
if (!VoxelStrateMeasurePrivate::FullResolutionPathIsAir(
Generator, Grid, AVoxel, BVoxel, Path))
{
bOutCoarseLied = true;
return false;
return EVoxelConnectivityResult::CoarseLied;
}
return true;
return EVoxelConnectivityResult::Connected;
}
+21 -4
View File
@@ -7,6 +7,17 @@
class UVoxelGenerator;
class UVoxelStrateManager;
/** Explicit outcome of a coarse connectivity query plus its full-resolution route re-check. */
enum class EVoxelConnectivityResult : uint8
{
Connected,
NotConnected,
StartCellSolid,
GoalCellSolid,
OutOfWindow,
CoarseLied
};
/** Settings for the deterministic coarse grid used by the strate measurement pass. */
struct VOXELFORGE_API FVoxelStrateMeasureSettings
{
@@ -31,6 +42,9 @@ struct VOXELFORGE_API FVoxelStrateMetrics
float AirFraction = 0.f;
int32 NumAirComponents = 0;
float LargestComponentShare = 0.f;
FVector LargestComponentPoint = FVector::ZeroVector; // Actor-space voxel coords; valid iff NumAir > 0.
int64 LargestComponentCells = 0;
int32 NumComponentsAtLeast1Pct = 0;
float WalkableFraction = 0.f;
float MedianFeatureScale = 0.f;
int32 MedianVerticalClearance = 0;
@@ -57,14 +71,17 @@ VOXELFORGE_API FVoxelStrateMetrics VF_MeasureStrate(
/**
* Test coarse connectivity and then re-check the one recovered coarse route at full resolution.
*
* bOutCoarseLied is true only when the coarse route existed but a full-resolution sample on that
* route was solid (or non-finite), in which case this function always returns false.
* If an endpoint's own coarse cell is solid, the query searches the 26 neighbouring cells and
* uses the nearest air cell when one exists. The snap flags report those repairs. Solid-cell
* results are returned only when no adjacent air cell exists. A coarse route that fails its
* full-resolution re-check returns CoarseLied.
*/
VOXELFORGE_API bool VF_AreConnected(
VOXELFORGE_API EVoxelConnectivityResult VF_AreConnected(
const UVoxelGenerator& Generator,
const UVoxelStrateManager& Manager,
int32 StrateIndex,
const FVector& AVoxel,
const FVector& BVoxel,
const FVoxelStrateMeasureSettings& Settings,
bool& bOutCoarseLied);
bool& bOutStartSnapped,
bool& bOutGoalSnapped);