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:
1 parent
271bbdd034
commit
44938f3f98
4 files changed
+343
-68
No files matched your search
+12
-7
@@ -311,17 +311,22 @@ walkability, feature scale, and clearance from that grid. Metrics report the res
|
||||
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 `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. A missing coarse
|
||||
route returns `NotConnectedAtThisResolution`: that is evidence only at the grid's resolution,
|
||||
because a corridor thinner than `SampleStep` is invisible. No UObject state, cache, actor, world,
|
||||
or PIE is required.
|
||||
candidate routes at full voxel resolution before returning `Connected`. When a candidate is
|
||||
refuted, its specific coarse cell edge is added to a deterministic array blocklist and BFS is
|
||||
retried up to `MaxRouteRetries` (default 16). 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. A missing coarse route after edge
|
||||
exclusions returns `NotConnectedAtThisResolution`: that is evidence only at the grid's
|
||||
resolution, because a corridor thinner than `SampleStep` is invisible. Exhausting the retry
|
||||
budget (or finding a refutation in an endpoint segment with no coarse edge to exclude) returns
|
||||
`CoarseLiedBudgetExhausted`, which is unknown rather than disconnected. Every `Connected` result
|
||||
has a full-resolution air-verified route, and the query reports the alternate route retry count.
|
||||
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 (including zero to sample the seal), 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, with explicit endpoint-solid, out-of-window, `CoarseLied`, `NotConnectedAtThisResolution`, and connected outcomes; the diagnostics overload returns endpoint component facts from that same grid. |
|
||||
| `VF_AreConnected` | Coarse 6-connected BFS plus deterministic blocked-edge retries, with a full-resolution air recheck for every candidate route and explicit endpoint-solid, out-of-window, `NotConnectedAtThisResolution`, `CoarseLiedBudgetExhausted` (unknown), and connected outcomes; diagnostics include endpoint component facts and the retry count from that same grid. |
|
||||
| `VF_DiagnoseConnectivity` | The same query plus each mouth's air-component size/share and exact geometric distance to the nearest cell of the other component; proximity is measured in coarse-cell coordinates and does not claim a route through solid. |
|
||||
|
||||
### 3.7 Cave morphology (SDF rooms/tunnels) — `Public/VoxelCaveMorphology.h` + `.cpp`
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user