fix(measure): a blocked route is not a disconnected pair

VF_AreConnected took the shortest coarse BFS route, re-walked it at full
resolution, and on hitting a wall returned false and gave up -- never
looking for another way round. That is a false NEGATIVE, and it was
inflating the failure count of a bug we were actively trying to fix.

On refutation it now excludes that specific coarse step and re-searches,
up to a bounded budget (16). Outcomes are distinguished honestly:
  Connected                   a route survived full-resolution verification
  NotConnectedAtThisResolution no coarse route remains after exclusions
  CoarseLiedBudgetExhausted    UNKNOWN -- routes kept being refuted
The third is deliberately NOT folded into either other verdict. Collapsing
"unknown" into "disconnected" is precisely what misled us.

  arrival->departure   7/16 -> 14/16
  All 7 former COARSE_LIED became CONNECTED.
  arrival->spine   15/16 connected      departure->spine 15/16 connected
  per-archetype law: 6/6 applicable routes pass

Retries needed are small -- max 3 per query, 0 budget hits -- so 16 is
generous rather than load-bearing. Runtime slightly IMPROVED (45.3 -> 44.9 s
on the 16-seed sweep) because a satisfied query stops early.

The false-POSITIVE guard is untouched and is now stronger in meaning:
every Connected still requires a fully re-walked, air-verified route, and
it now says "this specific route is open" rather than "the first route I
tried was open". The controlled wall still returns BudgetExhausted after
exactly 16 retries, and a repeat query is bit-identical in verdict, snap
flags and retry count.

⚠️ REMAINING: seeds 6 and 14 are genuinely NOT_CONNECTED. Real or a
step-2 resolution limit is not yet settled -- refine before touching
generation again, per COMPOSER-NOTES §13.

No generation, placement or parameter changed. Verifier only.
Build clean, 20 succeeded / 0 warnings / 0 failed, confirmed in VoxelM.log.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-09-03 23:56:25 +02:00
1 parent 271bbdd034
commit 44938f3f98
4 files changed
+343 -68

No files matched your search

@@ -42,6 +42,7 @@ namespace
EVoxelConnectivityResult Result = EVoxelConnectivityResult::OutOfWindow;
bool bStartSnapped = false;
bool bGoalSnapped = false;
int32 NumRouteRetries = 0;
};
struct FStrateConnectivityReport
@@ -115,11 +116,40 @@ namespace
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");
case EVoxelConnectivityResult::CoarseLiedBudgetExhausted:
return TEXT("COARSE_LIED_BUDGET_EXHAUSTED");
default: return TEXT("UNKNOWN");
}
}
constexpr int32 ConnectivityResultCount =
static_cast<int32>(EVoxelConnectivityResult::CoarseLiedBudgetExhausted) + 1;
int32 NumRefutedRoutes(const FConnectivityProbe& Probe)
{
switch (Probe.Result)
{
case EVoxelConnectivityResult::Connected:
case EVoxelConnectivityResult::NotConnectedAtThisResolution:
return Probe.NumRouteRetries;
case EVoxelConnectivityResult::CoarseLiedBudgetExhausted:
return Probe.NumRouteRetries + 1;
default:
return 0;
}
}
bool ConnectivityProbesAreBitIdentical(
const FConnectivityProbe& A,
const FConnectivityProbe& B)
{
return A.bChecked == B.bChecked
&& A.Result == B.Result
&& A.bStartSnapped == B.bStartSnapped
&& A.bGoalSnapped == B.bGoalSnapped
&& A.NumRouteRetries == B.NumRouteRetries;
}
FString ConnectivityProbeText(const FConnectivityProbe& Probe)
{
if (!Probe.bChecked)
@@ -127,7 +157,10 @@ namespace
return TEXT("NOT_CHECKED");
}
FString Text = ConnectivityResultName(Probe.Result);
FString Text = FString::Printf(
TEXT("%s [retries=%d]"),
ConnectivityResultName(Probe.Result),
Probe.NumRouteRetries);
if (Probe.bStartSnapped || Probe.bGoalSnapped)
{
Text += TEXT(" [");
@@ -557,6 +590,8 @@ namespace
OutReport.SpineComponentProbe.bStartSnapped,
OutReport.SpineComponentProbe.bGoalSnapped,
OutReport.SpineComponent);
OutReport.SpineComponentProbe.NumRouteRetries =
OutReport.SpineComponent.NumRouteRetries;
}
if (!FindDiagnosticMouths(Manager.GetPassages(), StrateIndex, OutReport))
@@ -584,6 +619,7 @@ namespace
OutProbe.bStartSnapped,
OutProbe.bGoalSnapped,
OutFacts);
OutProbe.NumRouteRetries = OutFacts.NumRouteRetries;
};
if (OutReport.bHasArrival)
@@ -613,7 +649,8 @@ namespace
OutReport.DeparturePoint,
Settings,
OutReport.ArrivalToDeparture.bStartSnapped,
OutReport.ArrivalToDeparture.bGoalSnapped);
OutReport.ArrivalToDeparture.bGoalSnapped,
OutReport.ArrivalToDeparture.NumRouteRetries);
}
return true;
}
@@ -791,7 +828,8 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
* 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 return COARSE_LIED.
* full-resolution solid sample; the controlled 3-voxel wall below must never return
* CONNECTED, and the bounded retry outcome must remain explicitly unknown.
*/
int32 GapTopChunkZ = 0;
@@ -1029,6 +1067,9 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
int32 NumRoutesChecked = 0;
int32 NumRoutesRefuted = 0;
int32 NumRouteRetriesUsed = 0;
int32 MaxRouteRetriesUsed = 0;
int32 NumBudgetExhausted = 0;
int32 NumGuardRoutesChecked = 0;
int32 NumGuardRoutesRefuted = 0;
int32 NumSnappedEndpointEvents = 0;
@@ -1041,7 +1082,7 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
int32 NumAnchorStartCellSolid = 0;
int32 NumAnchorGoalCellSolid = 0;
int32 NumAnchorOutOfWindow = 0;
int32 NumAnchorCoarseLied = 0;
int32 NumAnchorBudgetExhausted = 0;
int32 NumAnchorSnappedEndpointEvents = 0;
auto ProbeRoute = [&](int32 StrateIndex,
@@ -1061,20 +1102,19 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
Goal,
DerivedWindowSettings,
Probe.bStartSnapped,
Probe.bGoalSnapped);
Probe.bGoalSnapped,
Probe.NumRouteRetries);
++NumRoutesChecked;
if (Probe.Result == EVoxelConnectivityResult::CoarseLied)
{
++NumRoutesRefuted;
}
NumRoutesRefuted += NumRefutedRoutes(Probe);
NumRouteRetriesUsed += Probe.NumRouteRetries;
MaxRouteRetriesUsed = FMath::Max(MaxRouteRetriesUsed, Probe.NumRouteRetries);
NumBudgetExhausted +=
Probe.Result == EVoxelConnectivityResult::CoarseLiedBudgetExhausted ? 1 : 0;
if (bCountInGuard)
{
++NumGuardRoutesChecked;
if (Probe.Result == EVoxelConnectivityResult::CoarseLied)
{
++NumGuardRoutesRefuted;
}
NumGuardRoutesRefuted += NumRefutedRoutes(Probe);
}
const int32 SnappedHere = (Probe.bStartSnapped ? 1 : 0)
@@ -1099,7 +1139,9 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
case EVoxelConnectivityResult::StartCellSolid: ++NumAnchorStartCellSolid; break;
case EVoxelConnectivityResult::GoalCellSolid: ++NumAnchorGoalCellSolid; break;
case EVoxelConnectivityResult::OutOfWindow: ++NumAnchorOutOfWindow; break;
case EVoxelConnectivityResult::CoarseLied: ++NumAnchorCoarseLied; break;
case EVoxelConnectivityResult::CoarseLiedBudgetExhausted:
++NumAnchorBudgetExhausted;
break;
default: break;
}
}
@@ -1157,12 +1199,35 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
const FConnectivityProbe WallProbe = ProbeRoute(
0, WallA, WallB, /*bCountInGuard=*/true, /*bCountAsAnchorProbe=*/false,
/*bCountAsArrivalDeparture=*/false);
if (WallProbe.Result != EVoxelConnectivityResult::CoarseLied)
if (WallProbe.Result != EVoxelConnectivityResult::CoarseLiedBudgetExhausted
|| WallProbe.NumRouteRetries != DerivedWindowSettings.MaxRouteRetries)
{
AddError(FString::Printf(
TEXT("HARD FAILURE: the controlled coarse-connectivity lie returned %s instead of "
"COARSE_LIED."),
ConnectivityResultName(WallProbe.Result)));
TEXT("HARD FAILURE: the controlled solid wall returned %s after %d retries; "
"expected COARSE_LIED_BUDGET_EXHAUSTED after the MaxRouteRetries=%d cap."),
ConnectivityResultName(WallProbe.Result),
WallProbe.NumRouteRetries,
DerivedWindowSettings.MaxRouteRetries));
bAllChecksPassed = false;
}
FConnectivityProbe WallProbeRepeat;
WallProbeRepeat.bChecked = true;
WallProbeRepeat.Result = VF_AreConnected(
*World.Generator,
*World.StrateManager,
0,
WallA,
WallB,
DerivedWindowSettings,
WallProbeRepeat.bStartSnapped,
WallProbeRepeat.bGoalSnapped,
WallProbeRepeat.NumRouteRetries);
if (!ConnectivityProbesAreBitIdentical(WallProbe, WallProbeRepeat))
{
AddError(TEXT(
"HARD FAILURE: the blocked-edge retry order was not deterministic; repeating the "
"same wall query changed its verdict, snaps, or retry count."));
bAllChecksPassed = false;
}
World.DiffLayer->Clear();
@@ -1371,6 +1436,14 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
"checked, %d coarse routes refuted at full resolution.\n"),
NumRoutesChecked,
NumRoutesRefuted);
Summary += FString::Printf(
TEXT("Retry budget: MaxRouteRetries=%d; %d alternate routes checked across %d queries "
"(maximum %d retries in one query); budget exhausted on %d queries.\n"),
DerivedWindowSettings.MaxRouteRetries,
NumRouteRetriesUsed,
NumRoutesChecked,
MaxRouteRetriesUsed,
NumBudgetExhausted);
Summary += FString::Printf(
TEXT("Endpoint snap repair: %d snapped endpoint events across all probes; %d in "
"%d arrival->departure probes.\n"),
@@ -1380,14 +1453,14 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
Summary += FString::Printf(
TEXT("Correct largest-component endpoint probes (%d, including the surface-entry auxiliary): "
"connected=%d, not-connected-at-resolution=%d, start-cell-solid=%d, goal-cell-solid=%d, "
"out-of-window=%d, coarse-lied=%d, snapped endpoint events=%d.\n"),
"out-of-window=%d, coarse-lied-budget-exhausted=%d, snapped endpoint events=%d.\n"),
NumAnchorProbes,
NumAnchorConnected,
NumAnchorNotConnectedAtThisResolution,
NumAnchorStartCellSolid,
NumAnchorGoalCellSolid,
NumAnchorOutOfWindow,
NumAnchorCoarseLied,
NumAnchorBudgetExhausted,
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");
@@ -1557,7 +1630,8 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
DeparturePoint,
RowSettings,
Row.Probe.bStartSnapped,
Row.Probe.bGoalSnapped);
Row.Probe.bGoalSnapped,
Row.Probe.NumRouteRetries);
if (!Row.Metrics.bValid
|| Row.Metrics.NumSampled <= 0
@@ -1638,7 +1712,8 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
DeparturePoint,
RowSettings,
Row.Probe.bStartSnapped,
Row.Probe.bGoalSnapped);
Row.Probe.bGoalSnapped,
Row.Probe.NumRouteRetries);
if (!Row.Metrics.bValid
|| Row.Metrics.NumSampled <= 0
@@ -1789,7 +1864,9 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
int32 NumSpineLargestComponents = 0;
int32 NumMouthToSpineQueries = 0;
int32 NumMouthToSpineConnected = 0;
int32 NumMouthToSpineCoarseLied = 0;
int32 NumMouthToSpineBudgetExhausted = 0;
int32 NumMouthToSpineRetriesUsed = 0;
int32 MaxMouthToSpineRetries = 0;
for (const FSpineDiagnosisReport& Report : SpineReports)
{
if (Report.Spine.bFullWindowRun)
@@ -1817,7 +1894,11 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
}
++NumMouthToSpineQueries;
NumMouthToSpineConnected += Probe->Result == EVoxelConnectivityResult::Connected;
NumMouthToSpineCoarseLied += Probe->Result == EVoxelConnectivityResult::CoarseLied;
NumMouthToSpineBudgetExhausted +=
Probe->Result == EVoxelConnectivityResult::CoarseLiedBudgetExhausted;
NumMouthToSpineRetriesUsed += Probe->NumRouteRetries;
MaxMouthToSpineRetries = FMath::Max(
MaxMouthToSpineRetries, Probe->NumRouteRetries);
}
}
@@ -1959,7 +2040,8 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
bSpineDiagnosisUsable ? TEXT("yes") : TEXT("no"));
Summary += FString::Printf(
TEXT("Interpretation counts: spine full-window run=%d/%d, one uninterrupted open run=%d/%d, "
"spine component largest=%d/%d; mouth->spine CONNECTED=%d/%d, COARSE_LIED=%d.\n"),
"spine component largest=%d/%d; mouth->spine CONNECTED=%d/%d, "
"COARSE_LIED_BUDGET_EXHAUSTED=%d, retries=%d (max %d).\n"),
NumSpineFullWindowRuns,
SpineReports.Num(),
NumSpineOneOpenRuns,
@@ -1968,7 +2050,9 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
SpineReports.Num(),
NumMouthToSpineConnected,
NumMouthToSpineQueries,
NumMouthToSpineCoarseLied);
NumMouthToSpineBudgetExhausted,
NumMouthToSpineRetriesUsed,
MaxMouthToSpineRetries);
Summary += TEXT(
"Interpretation: the structural spine remains a continuous column. After the "
"VerticalShafts connector change, the seed-1337 spine is the largest component, but the "
@@ -2041,8 +2125,9 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
Diagnostics.GoalToStartComponentDistanceCells,
Diagnostics.GoalToStartComponentDistanceCells * static_cast<float>(DiagnosticSettings.SampleStep));
Summary += FString::Printf(
TEXT(" diagnostic route verdict: %s; endpoint snaps: start=%s, goal=%s.\n"),
TEXT(" diagnostic route verdict: %s; retries=%d; endpoint snaps: start=%s, goal=%s.\n"),
ConnectivityResultName(Diagnostics.Result),
Diagnostics.NumRouteRetries,
Diagnostics.bStartSnapped ? TEXT("yes") : TEXT("no"),
Diagnostics.bGoalSnapped ? TEXT("yes") : TEXT("no"));
@@ -2069,9 +2154,18 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
int32 SeedArrivalToSpineSnapEvents = 0;
int32 SeedDepartureToSpineSnapEvents = 0;
int32 SeedArrivalToDepartureSnapEvents = 0;
int32 SeedArrivalToSpineResultCounts[6] = {};
int32 SeedDepartureToSpineResultCounts[6] = {};
int32 SeedArrivalToDepartureResultCounts[6] = {};
int32 SeedArrivalToSpineResultCounts[ConnectivityResultCount] = {};
int32 SeedDepartureToSpineResultCounts[ConnectivityResultCount] = {};
int32 SeedArrivalToDepartureResultCounts[ConnectivityResultCount] = {};
int32 SeedArrivalToSpineRetries = 0;
int32 SeedDepartureToSpineRetries = 0;
int32 SeedArrivalToDepartureRetries = 0;
int32 SeedArrivalToSpineMaxRetries = 0;
int32 SeedDepartureToSpineMaxRetries = 0;
int32 SeedArrivalToDepartureMaxRetries = 0;
int32 SeedArrivalToSpineBudgetExhausted = 0;
int32 SeedDepartureToSpineBudgetExhausted = 0;
int32 SeedArrivalToDepartureBudgetExhausted = 0;
int32 SeedFullWindowRuns = 0;
int32 SeedOneOpenRuns = 0;
int32 SeedSpineLargest = 0;
@@ -2176,7 +2270,9 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
continue;
}
const auto CountSeedResult = [](const FConnectivityProbe& Probe, int32 (&Counts)[6])
const auto CountSeedResult = [](
const FConnectivityProbe& Probe,
int32 (&Counts)[ConnectivityResultCount])
{
if (Probe.bChecked)
{
@@ -2187,6 +2283,29 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
CountSeedResult(SeedReport.DepartureToSpine, SeedDepartureToSpineResultCounts);
CountSeedResult(SeedReport.ArrivalToDeparture, SeedArrivalToDepartureResultCounts);
SeedArrivalToSpineRetries += SeedReport.ArrivalToSpine.NumRouteRetries;
SeedDepartureToSpineRetries += SeedReport.DepartureToSpine.NumRouteRetries;
SeedArrivalToDepartureRetries +=
SeedReport.ArrivalToDeparture.NumRouteRetries;
SeedArrivalToSpineMaxRetries = FMath::Max(
SeedArrivalToSpineMaxRetries,
SeedReport.ArrivalToSpine.NumRouteRetries);
SeedDepartureToSpineMaxRetries = FMath::Max(
SeedDepartureToSpineMaxRetries,
SeedReport.DepartureToSpine.NumRouteRetries);
SeedArrivalToDepartureMaxRetries = FMath::Max(
SeedArrivalToDepartureMaxRetries,
SeedReport.ArrivalToDeparture.NumRouteRetries);
SeedArrivalToSpineBudgetExhausted +=
SeedReport.ArrivalToSpine.Result
== EVoxelConnectivityResult::CoarseLiedBudgetExhausted;
SeedDepartureToSpineBudgetExhausted +=
SeedReport.DepartureToSpine.Result
== EVoxelConnectivityResult::CoarseLiedBudgetExhausted;
SeedArrivalToDepartureBudgetExhausted +=
SeedReport.ArrivalToDeparture.Result
== EVoxelConnectivityResult::CoarseLiedBudgetExhausted;
SeedArrivalToSpineSnapEvents +=
(SeedReport.ArrivalToSpine.bStartSnapped ? 1 : 0)
+ (SeedReport.ArrivalToSpine.bGoalSnapped ? 1 : 0);
@@ -2245,6 +2364,20 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
SeedFailures,
SeedArrivalToDepartureSnapEvents,
SeedSweepSeconds);
Summary += FString::Printf(
TEXT(" seed retries used (total/max/budget-exhausted): arrival->spine=%d/%d/%d; "
"departure->spine=%d/%d/%d; arrival->departure=%d/%d/%d; "
"MaxRouteRetries=%d.\n"),
SeedArrivalToSpineRetries,
SeedArrivalToSpineMaxRetries,
SeedArrivalToSpineBudgetExhausted,
SeedDepartureToSpineRetries,
SeedDepartureToSpineMaxRetries,
SeedDepartureToSpineBudgetExhausted,
SeedArrivalToDepartureRetries,
SeedArrivalToDepartureMaxRetries,
SeedArrivalToDepartureBudgetExhausted,
DiagnosticSettings.MaxRouteRetries);
Summary += FString::Printf(
TEXT(" ACCEPTANCE arrival->departure: %s (%d/%d seeds pass).\n"),
SeedPasses == UE_ARRAY_COUNT(SeedCases) && SeedFailures == 0
@@ -2276,29 +2409,29 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
Summary += FString::Printf(
TEXT(" seed result breakdown — arrival->spine: CONNECTED=%d, "
"NOT_CONNECTED_AT_THIS_RESOLUTION=%d, START_CELL_SOLID=%d, GOAL_CELL_SOLID=%d, "
"OUT_OF_WINDOW=%d, COARSE_LIED=%d; departure->spine: CONNECTED=%d, "
"OUT_OF_WINDOW=%d, COARSE_LIED_BUDGET_EXHAUSTED=%d; departure->spine: CONNECTED=%d, "
"NOT_CONNECTED_AT_THIS_RESOLUTION=%d, START_CELL_SOLID=%d, GOAL_CELL_SOLID=%d, "
"OUT_OF_WINDOW=%d, COARSE_LIED=%d; arrival->departure: CONNECTED=%d, "
"OUT_OF_WINDOW=%d, COARSE_LIED_BUDGET_EXHAUSTED=%d; arrival->departure: CONNECTED=%d, "
"NOT_CONNECTED_AT_THIS_RESOLUTION=%d, START_CELL_SOLID=%d, GOAL_CELL_SOLID=%d, "
"OUT_OF_WINDOW=%d, COARSE_LIED=%d.\n"),
"OUT_OF_WINDOW=%d, COARSE_LIED_BUDGET_EXHAUSTED=%d.\n"),
SeedArrivalToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::Connected)],
SeedArrivalToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::NotConnectedAtThisResolution)],
SeedArrivalToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::StartCellSolid)],
SeedArrivalToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::GoalCellSolid)],
SeedArrivalToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::OutOfWindow)],
SeedArrivalToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::CoarseLied)],
SeedArrivalToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::CoarseLiedBudgetExhausted)],
SeedDepartureToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::Connected)],
SeedDepartureToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::NotConnectedAtThisResolution)],
SeedDepartureToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::StartCellSolid)],
SeedDepartureToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::GoalCellSolid)],
SeedDepartureToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::OutOfWindow)],
SeedDepartureToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::CoarseLied)],
SeedDepartureToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::CoarseLiedBudgetExhausted)],
SeedArrivalToDepartureResultCounts[static_cast<int32>(EVoxelConnectivityResult::Connected)],
SeedArrivalToDepartureResultCounts[static_cast<int32>(EVoxelConnectivityResult::NotConnectedAtThisResolution)],
SeedArrivalToDepartureResultCounts[static_cast<int32>(EVoxelConnectivityResult::StartCellSolid)],
SeedArrivalToDepartureResultCounts[static_cast<int32>(EVoxelConnectivityResult::GoalCellSolid)],
SeedArrivalToDepartureResultCounts[static_cast<int32>(EVoxelConnectivityResult::OutOfWindow)],
SeedArrivalToDepartureResultCounts[static_cast<int32>(EVoxelConnectivityResult::CoarseLied)]);
SeedArrivalToDepartureResultCounts[static_cast<int32>(EVoxelConnectivityResult::CoarseLiedBudgetExhausted)]);
Summary += FString::Printf(
TEXT(" seed endpoint snaps — arrival->spine=%d, departure->spine=%d, "
"arrival->departure=%d.\n"),
+134 -15
View File
@@ -662,6 +662,41 @@ namespace VoxelStrateMeasurePrivate
return EEndpointCellResult::Air;
}
struct FCoarseEdge
{
int32 First = INDEX_NONE;
int32 Second = INDEX_NONE;
bool IsValid() const
{
return First >= 0 && Second >= 0 && First != Second;
}
};
FCoarseEdge MakeCoarseEdge(int32 A, int32 B)
{
FCoarseEdge Edge;
Edge.First = FMath::Min(A, B);
Edge.Second = FMath::Max(A, B);
return Edge;
}
bool IsCoarseEdgeBlocked(
const TArray<FCoarseEdge>& BlockedEdges,
int32 A,
int32 B)
{
const FCoarseEdge Candidate = MakeCoarseEdge(A, B);
for (const FCoarseEdge& Blocked : BlockedEdges)
{
if (Blocked.First == Candidate.First && Blocked.Second == Candidate.Second)
{
return true;
}
}
return false;
}
bool FullResolutionAirOnSegment(
const UVoxelGenerator& Generator,
const FVector& Start,
@@ -689,8 +724,11 @@ namespace VoxelStrateMeasurePrivate
const FSampleGrid& Grid,
const FVector& A,
const FVector& B,
const TArray<int32>& Path)
const TArray<int32>& Path,
FCoarseEdge& OutFailedEdge)
{
OutFailedEdge = FCoarseEdge();
int32 StartX = 0;
int32 StartY = 0;
int32 StartZ = 0;
@@ -702,6 +740,9 @@ namespace VoxelStrateMeasurePrivate
if (!FullResolutionAirOnSegment(Generator, A, Grid.CellCenter(StartX, StartY, StartZ)))
{
// The endpoint-to-cell-center segment has no coarse cell pair to exclude. It is
// common to every route from this endpoint, so report unknown rather than claiming
// that the pair is disconnected at this resolution.
return false;
}
@@ -720,17 +761,25 @@ namespace VoxelStrateMeasurePrivate
Grid.CellCenter(PreviousX, PreviousY, PreviousZ),
Grid.CellCenter(CurrentX, CurrentY, CurrentZ)))
{
OutFailedEdge = MakeCoarseEdge(Path[PathIndex - 1], Path[PathIndex]);
return false;
}
}
return FullResolutionAirOnSegment(Generator, Grid.CellCenter(EndX, EndY, EndZ), B);
if (!FullResolutionAirOnSegment(Generator, Grid.CellCenter(EndX, EndY, EndZ), B))
{
// As above, the final endpoint segment is shared by every route that reaches the
// goal cell and therefore has no precise coarse edge to exclude.
return false;
}
return true;
}
bool FindCoarsePath(
const FSampleGrid& Grid,
int32 Start,
int32 Goal,
const TArray<FCoarseEdge>& BlockedEdges,
TArray<int32>& OutPath)
{
if (Grid.Air[Start] == 0u || Grid.Air[Goal] == 0u)
@@ -762,7 +811,9 @@ namespace VoxelStrateMeasurePrivate
auto Visit = [&](int32 Neighbor)
{
if (Grid.Air[Neighbor] != 0u && Parent[Neighbor] == -2)
if (Grid.Air[Neighbor] != 0u
&& Parent[Neighbor] == -2
&& !IsCoarseEdgeBlocked(BlockedEdges, Current, Neighbor))
{
Parent[Neighbor] = Current;
Queue[Tail++] = Neighbor;
@@ -812,12 +863,15 @@ namespace VoxelStrateMeasurePrivate
int32& OutStart,
int32& OutGoal,
bool& bOutStartSnapped,
bool& bOutGoalSnapped)
bool& bOutGoalSnapped,
int32& OutNumRouteRetries,
int32 MaxRouteRetries)
{
OutStart = -1;
OutGoal = -1;
bOutStartSnapped = false;
bOutGoalSnapped = false;
OutNumRouteRetries = 0;
const EEndpointCellResult StartCell = ResolveEndpointCell(
Grid, AVoxel, OutStart, bOutStartSnapped);
@@ -837,20 +891,40 @@ namespace VoxelStrateMeasurePrivate
return EVoxelConnectivityResult::GoalCellSolid;
}
TArray<FCoarseEdge> BlockedEdges;
TArray<int32> Path;
if (!FindCoarsePath(Grid, OutStart, OutGoal, Path))
for (;;)
{
return EVoxelConnectivityResult::NotConnectedAtThisResolution;
}
if (!FindCoarsePath(Grid, OutStart, OutGoal, BlockedEdges, Path))
{
// This is a negative verdict only after all edges excluded by earlier
// full-resolution failures leave no coarse route at this resolution.
return EVoxelConnectivityResult::NotConnectedAtThisResolution;
}
// The coarse route is evidence, not a pass. Re-check this one BFS route at one-voxel
// spacing in the original voxel field. A single solid sample is the dangerous coarse
// false positive.
if (!FullResolutionPathIsAir(Generator, Grid, AVoxel, BVoxel, Path))
{
return EVoxelConnectivityResult::CoarseLied;
// The coarse route is evidence, not a pass. Re-check every candidate at one-voxel
// spacing in the original voxel field. A single solid sample refutes only this
// route, so exclude its precise coarse edge and search again.
FCoarseEdge FailedEdge;
if (FullResolutionPathIsAir(Generator, Grid, AVoxel, BVoxel, Path, FailedEdge))
{
return EVoxelConnectivityResult::Connected;
}
if (OutNumRouteRetries >= MaxRouteRetries || !FailedEdge.IsValid())
{
// A route can be refuted without a retryable pair when the failure is in an
// endpoint-to-cell segment (or when the explicit retry budget is spent). Both
// cases are unknown, never evidence of disconnection.
return EVoxelConnectivityResult::CoarseLiedBudgetExhausted;
}
check(FailedEdge.First != FailedEdge.Second);
check(!IsCoarseEdgeBlocked(
BlockedEdges, FailedEdge.First, FailedEdge.Second));
BlockedEdges.Add(FailedEdge);
++OutNumRouteRetries;
}
return EVoxelConnectivityResult::Connected;
}
float DistanceToComponent(
@@ -899,12 +973,14 @@ namespace VoxelStrateMeasurePrivate
EVoxelConnectivityResult ConnectivityResult,
bool bStartSnapped,
bool bGoalSnapped,
int32 NumRouteRetries,
FVoxelConnectivityDiagnostics& OutDiagnostics)
{
OutDiagnostics = FVoxelConnectivityDiagnostics();
OutDiagnostics.Result = ConnectivityResult;
OutDiagnostics.bStartSnapped = bStartSnapped;
OutDiagnostics.bGoalSnapped = bGoalSnapped;
OutDiagnostics.NumRouteRetries = NumRouteRetries;
OutDiagnostics.bValid = ConnectivityResult != EVoxelConnectivityResult::OutOfWindow
|| (Start >= 0 && Goal >= 0);
if (Start < 0 || Goal < 0)
@@ -983,10 +1059,12 @@ namespace VoxelStrateMeasurePrivate
const FVoxelStrateMeasureSettings& Settings,
bool& bOutStartSnapped,
bool& bOutGoalSnapped,
int32& OutNumRouteRetries,
FVoxelConnectivityDiagnostics* OutDiagnostics)
{
bOutStartSnapped = false;
bOutGoalSnapped = false;
OutNumRouteRetries = 0;
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))
@@ -997,6 +1075,14 @@ namespace VoxelStrateMeasurePrivate
}
return EVoxelConnectivityResult::OutOfWindow;
}
if (Settings.MaxRouteRetries < 0)
{
if (OutDiagnostics != nullptr)
{
*OutDiagnostics = FVoxelConnectivityDiagnostics();
}
return EVoxelConnectivityResult::OutOfWindow;
}
FSampleGrid Grid;
FString RefusalReason;
@@ -1019,7 +1105,9 @@ namespace VoxelStrateMeasurePrivate
Start,
Goal,
bOutStartSnapped,
bOutGoalSnapped);
bOutGoalSnapped,
OutNumRouteRetries,
Settings.MaxRouteRetries);
if (OutDiagnostics != nullptr)
{
PopulateConnectivityDiagnostics(
@@ -1029,6 +1117,7 @@ namespace VoxelStrateMeasurePrivate
Result,
bOutStartSnapped,
bOutGoalSnapped,
OutNumRouteRetries,
*OutDiagnostics);
}
return Result;
@@ -1071,6 +1160,7 @@ EVoxelConnectivityResult VF_AreConnected(
bool& bOutStartSnapped,
bool& bOutGoalSnapped)
{
int32 NumRouteRetries = 0;
return VoxelStrateMeasurePrivate::QueryConnectivity(
Generator,
Manager,
@@ -1080,6 +1170,31 @@ EVoxelConnectivityResult VF_AreConnected(
Settings,
bOutStartSnapped,
bOutGoalSnapped,
NumRouteRetries,
nullptr);
}
EVoxelConnectivityResult VF_AreConnected(
const UVoxelGenerator& Generator,
const UVoxelStrateManager& Manager,
int32 StrateIndex,
const FVector& AVoxel,
const FVector& BVoxel,
const FVoxelStrateMeasureSettings& Settings,
bool& bOutStartSnapped,
bool& bOutGoalSnapped,
int32& OutNumRouteRetries)
{
return VoxelStrateMeasurePrivate::QueryConnectivity(
Generator,
Manager,
StrateIndex,
AVoxel,
BVoxel,
Settings,
bOutStartSnapped,
bOutGoalSnapped,
OutNumRouteRetries,
nullptr);
}
@@ -1094,6 +1209,7 @@ EVoxelConnectivityResult VF_AreConnected(
bool& bOutGoalSnapped,
FVoxelConnectivityDiagnostics& OutDiagnostics)
{
int32 NumRouteRetries = 0;
return VoxelStrateMeasurePrivate::QueryConnectivity(
Generator,
Manager,
@@ -1103,6 +1219,7 @@ EVoxelConnectivityResult VF_AreConnected(
Settings,
bOutStartSnapped,
bOutGoalSnapped,
NumRouteRetries,
&OutDiagnostics);
}
@@ -1115,6 +1232,7 @@ FVoxelConnectivityDiagnostics VF_DiagnoseConnectivity(
const FVoxelStrateMeasureSettings& Settings)
{
FVoxelConnectivityDiagnostics Result;
int32 NumRouteRetries = 0;
VoxelStrateMeasurePrivate::QueryConnectivity(
Generator,
Manager,
@@ -1124,6 +1242,7 @@ FVoxelConnectivityDiagnostics VF_DiagnoseConnectivity(
Settings,
Result.bStartSnapped,
Result.bGoalSnapped,
NumRouteRetries,
&Result);
return Result;
}
+26 -8
View File
@@ -15,7 +15,7 @@ enum class EVoxelConnectivityResult : uint8
StartCellSolid,
GoalCellSolid,
OutOfWindow,
CoarseLied
CoarseLiedBudgetExhausted // Unknown: retries are exhausted, or the refutation has no coarse edge to exclude.
};
/** Settings for the deterministic coarse grid used by the strate measurement pass. */
@@ -25,6 +25,7 @@ struct VOXELFORGE_API FVoxelStrateMeasureSettings
int32 RadiusInVoxels = 256; // Half-extent in X and Y around CenterXY.
FVector2D CenterXY = FVector2D::ZeroVector; // Actor-space voxel coordinates.
int32 MaxCells = 8000000; // Refuse a grid larger than this many cells.
int32 MaxRouteRetries = 16; // Alternate coarse routes to full-resolution-check after the first route.
int32 HeadroomCells = 2; // Air cells above a floor cell required for walkable.
int32 InteriorMarginVoxels = -1; // <0 = derive from BoundarySealThickness (2x, clamped); explicit 0 includes the seal.
};
@@ -71,6 +72,7 @@ struct VOXELFORGE_API FVoxelConnectivityDiagnostics
EVoxelConnectivityResult Result = EVoxelConnectivityResult::OutOfWindow;
bool bStartSnapped = false;
bool bGoalSnapped = false;
int32 NumRouteRetries = 0; // Alternate routes full-resolution-checked after the first route.
int64 NumAirCells = 0;
int64 StartComponentCells = 0;
@@ -100,14 +102,17 @@ VOXELFORGE_API FVoxelStrateMetrics VF_MeasureStrate(
const FVoxelStrateMeasureSettings& Settings);
/**
* Test coarse connectivity and then re-check the one recovered coarse route at full resolution.
* Test coarse connectivity and re-check recovered coarse routes at full resolution.
*
* 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; a missing coarse route returns
* NotConnectedAtThisResolution because a corridor thinner than SampleStep is invisible to the
* grid. A bare "not connected" result is evidence only at the grid's own resolution.
* results are returned only when no adjacent air cell exists. When a route fails its
* full-resolution re-check, the failed coarse edge is excluded from a deterministic local
* blocklist and BFS is retried up to MaxRouteRetries. A missing route after exclusions returns
* NotConnectedAtThisResolution. If the retry budget is exhausted while routes are still being
* refuted, the result is CoarseLiedBudgetExhausted: unknown, not disconnected. The same unknown
* result is used when a refutation is in an endpoint-to-cell segment with no coarse edge to
* exclude. Every Connected result has a fully re-walked air route.
*/
VOXELFORGE_API EVoxelConnectivityResult VF_AreConnected(
const UVoxelGenerator& Generator,
@@ -119,6 +124,18 @@ VOXELFORGE_API EVoxelConnectivityResult VF_AreConnected(
bool& bOutStartSnapped,
bool& bOutGoalSnapped);
/** The same query as the simple overload, with the number of alternate routes actually tried. */
VOXELFORGE_API EVoxelConnectivityResult VF_AreConnected(
const UVoxelGenerator& Generator,
const UVoxelStrateManager& Manager,
int32 StrateIndex,
const FVector& AVoxel,
const FVector& BVoxel,
const FVoxelStrateMeasureSettings& Settings,
bool& bOutStartSnapped,
bool& bOutGoalSnapped,
int32& OutNumRouteRetries);
/**
* The same connectivity query with component facts returned from the same sampled grid. This
* overload exists for diagnostics that need both the required route verdict and the component
@@ -137,8 +154,9 @@ VOXELFORGE_API EVoxelConnectivityResult VF_AreConnected(
/**
* Return the component sizes and spatial separation facts for the same query performed by
* VF_AreConnected. The returned Result uses the same full-resolution route re-check. bValid means
* the sample grid was built and both endpoint coordinates were in its bounds; component fields
* VF_AreConnected. The returned Result uses the same retrying full-resolution route re-check,
* and NumRouteRetries reports the number of alternate routes actually checked. bValid means the
* sample grid was built and both endpoint coordinates were in its bounds; component fields
* remain zero/negative when an endpoint has no air cell to analyze. Largest-component membership
* treats equal-sized components as tied for largest.
*/