test(measure): VerticalShafts breaks the primordial law in 15 of 16 seeds

The margin is exonerated. Every earlier run excluded 8 voxels at each
strate boundary, and voxels 4-8 of those are ordinary interior, not seal
-- the same window mistake that produced "FlatPlain is 99.95% void" at
margin 32. So the query was re-run at margin 0, 2, 4 and 8. All four stay
negative; margin 0 includes the seal itself and still reports negative.
(The implementation had clamped explicit margins to >= 1, which made the
margin-0 check impossible; Codex found and fixed that.)

Diagnosis -- this is not a near-miss wall:
  arrival   LowerPoint (65.8,  95.8, -1047.25)
  departure UpperPoint (88.4, -104.6,  -991.58)
  XY separation 201.7 voxels (~50 m), Z delta 55.7
  arrival component   22,531 cells (22.0% of air)
  departure component 21,621 cells (21.1% of air)
  nearest approach between the two components: 135-179 voxels

Two large, healthy, entirely separate shaft networks half a strate apart.
Nothing to breach; the archetype has no lateral connection at all.

16-seed sweep: 1 pass, 15 fail. Not one unlucky world -- structural.

So: descend into a VerticalShafts strate and you probably cannot reach
its exit. This is the "live hole in the shipping game" COMPOSER-NOTES
Tier 1 predicted, now measured instead of assumed.

Stated precisely, because it names the fix: VF_SuggestLandingPoint makes
each mouth land in open space, but NOTHING makes the two mouths of one
strate land in the SAME open space.

New: VF_DiagnoseConnectivity returns component sizes and the spatial
separation between the two mouths' components, so a near-miss and a
never-meant-to-connect read differently instead of both being "false".

No generation change, no threshold tuning. The fix is a design decision.

Build clean, 19 succeeded / 0 warnings / 0 failed. Sweeps 3.4 s + 6.9 s.

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:42:47 +02:00
1 parent cd31dec869
commit 0bbfdeb7a7
4 files changed
+526 -27

No files matched your search

+4 -2
View File
@@ -304,7 +304,7 @@ This is **where terrain shape lives.**
### 3.6b Strate measurement — `Public/VoxelStrateMeasure.h` + `Private/VoxelStrateMeasure.cpp` ### 3.6b Strate measurement — `Public/VoxelStrateMeasure.h` + `Private/VoxelStrateMeasure.cpp`
Headless, read-only Tier 2 measurement pass. `VF_MeasureStrate` samples one strate between a Headless, read-only Tier 2 measurement pass. `VF_MeasureStrate` samples one strate between a
boundary-seal-derived (or explicitly overridden) interior margin into a bounded coarse grid, boundary-seal-derived (or explicitly overridden, including explicit zero) interior margin into a bounded coarse grid,
performs one deterministic 6-connected air flood fill, and derives fractions, components, 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 walkability, feature scale, and clearance from that grid. Metrics report the resolved margin and
the inclusive/exclusive voxel Z window used, plus the largest component's deterministic lowest-cell the inclusive/exclusive voxel Z window used, plus the largest component's deterministic lowest-cell
@@ -318,9 +318,10 @@ because a corridor thinner than `SampleStep` is invisible. No UObject state, cac
or PIE is required. or PIE is required.
| Symbol | Role | | 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 and deterministic component representatives. | | `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_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. | | `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. |
| `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` ### 3.7 Cave morphology (SDF rooms/tunnels) — `Public/VoxelCaveMorphology.h` + `.cpp`
Header is rich with inline docs. Two namespaces + a per-chunk cache system. Header is rich with inline docs. Two namespaces + a per-chunk cache system.
@@ -485,6 +486,7 @@ The plugin's first tests (`OPSTACK-PLAN.md` Phase 0.5). Run them from the editor
| `VoxelForgeStrateParamCoverageTest.cpp` | `VoxelForge.Determinism.StrateParamBlendCoverage` | **The X-macro guard** (added 2026-08-17). `FStrateGenerationParams::Lerp` blends the hand-written `VF_STRATE_PARAM_FIELDS` list, **not** the struct — so a field added to one and not the other compiles, tests green, and silently takes its **default** inside every Gradient/Interleaved transition band. This expands the X-macro a **third** way (after LERP and SNAP), into a name list, and diffs it against the struct's UObject reflection. Pure shape test: no fixture, no world, instant. `GExemptFieldNames` is **empty** — every reflected field is covered today, and any exemption must be written down as a decision. Stakes rise with the world composer, which intends to invent parameter sets through this same `Lerp` (`COMPOSER-NOTES.md`). | | `VoxelForgeStrateParamCoverageTest.cpp` | `VoxelForge.Determinism.StrateParamBlendCoverage` | **The X-macro guard** (added 2026-08-17). `FStrateGenerationParams::Lerp` blends the hand-written `VF_STRATE_PARAM_FIELDS` list, **not** the struct — so a field added to one and not the other compiles, tests green, and silently takes its **default** inside every Gradient/Interleaved transition band. This expands the X-macro a **third** way (after LERP and SNAP), into a name list, and diffs it against the struct's UObject reflection. Pure shape test: no fixture, no world, instant. `GExemptFieldNames` is **empty** — every reflected field is covered today, and any exemption must be written down as a decision. Stakes rise with the world composer, which intends to invent parameter sets through this same `Lerp` (`COMPOSER-NOTES.md`). |
| `VoxelForgeLayoutOrderIndependenceTest.cpp` | `VoxelForge.Determinism.LayoutOrderIndependence` | Builds a known transient soft-pointer pool, then rebuilds it in original, reversed, and swapped orders. Requires a non-empty layout and passage set, and compares every slot's definition/Z/height plus passage endpoints, radius, type, control geometry, and bounds bit-for-bit. | | `VoxelForgeLayoutOrderIndependenceTest.cpp` | `VoxelForge.Determinism.LayoutOrderIndependence` | Builds a known transient soft-pointer pool, then rebuilds it in original, reversed, and swapped orders. Requires a non-empty layout and passage set, and compares every slot's definition/Z/height plus passage endpoints, radius, type, control geometry, and bounds bit-for-bit. |
| `VoxelForgePassageOpenSpaceTest.cpp` | `VoxelForge.Determinism.PassageLandsInOpenSpace` | Uses the real fixture density path to check every generated inter-strate passage whose destination query answers: a 16-point ring outside the mouth's carve/blend band has at least half its samples in destination air, and the endpoint matches the pure open-point result within the mouth's float envelope. This is a connectivity proxy, not a flood-fill proof. Reports checked passages and false/unanswerable archetypes; fails if it inspects zero passages. | | `VoxelForgePassageOpenSpaceTest.cpp` | `VoxelForge.Determinism.PassageLandsInOpenSpace` | Uses the real fixture density path to check every generated inter-strate passage whose destination query answers: a 16-point ring outside the mouth's carve/blend band has at least half its samples in destination air, and the endpoint matches the pure open-point result within the mouth's float envelope. This is a connectivity proxy, not a flood-fill proof. Reports checked passages and false/unanswerable archetypes; fails if it inspects zero passages. |
| `VoxelForgeStrateConnectivityTest.cpp` | `VoxelForge.Generation.StrateConnectivity` / `VoxelForge.Generation.StrateConnectivityRefinement` | Bounded strate metrics with density-polarity and solid-gap controls, deterministic route rechecks, and a VerticalShafts refinement sweep. The refinement test explicitly checks margins 0/2/4/8 at step 2/radius 192, then conditionally reports mouth components/proximity and a 16-seed arrival→departure result. |
## 4. The density pipeline (most-edited hot path) ## 4. The density pipeline (most-edited hot path)
@@ -246,6 +246,7 @@ namespace
{ {
int32 SampleStep = 0; int32 SampleStep = 0;
int32 RadiusInVoxels = 0; int32 RadiusInVoxels = 0;
int32 RequestedMarginVoxels = -1;
FVoxelStrateMetrics Metrics; FVoxelStrateMetrics Metrics;
FConnectivityProbe Probe; FConnectivityProbe Probe;
}; };
@@ -1037,6 +1038,7 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
{ {
int32 SampleStep; int32 SampleStep;
int32 RadiusInVoxels; int32 RadiusInVoxels;
int32 InteriorMarginVoxels = -1;
}; };
static constexpr FRefinementCase Cases[] = { static constexpr FRefinementCase Cases[] = {
{4, 256}, {4, 256},
@@ -1056,10 +1058,12 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
FRefinementSweepRow& Row = Rows.AddDefaulted_GetRef(); FRefinementSweepRow& Row = Rows.AddDefaulted_GetRef();
Row.SampleStep = TestCase.SampleStep; Row.SampleStep = TestCase.SampleStep;
Row.RadiusInVoxels = TestCase.RadiusInVoxels; Row.RadiusInVoxels = TestCase.RadiusInVoxels;
Row.RequestedMarginVoxels = TestCase.InteriorMarginVoxels;
FVoxelStrateMeasureSettings RowSettings = BaseSettings; FVoxelStrateMeasureSettings RowSettings = BaseSettings;
RowSettings.SampleStep = TestCase.SampleStep; RowSettings.SampleStep = TestCase.SampleStep;
RowSettings.RadiusInVoxels = TestCase.RadiusInVoxels; RowSettings.RadiusInVoxels = TestCase.RadiusInVoxels;
RowSettings.InteriorMarginVoxels = TestCase.InteriorMarginVoxels;
Row.Metrics = VF_MeasureStrate( Row.Metrics = VF_MeasureStrate(
*World.Generator, *World.Generator,
@@ -1125,6 +1129,106 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
} }
const double SweepSeconds = FPlatformTime::Seconds() - SweepStartSeconds; const double SweepSeconds = FPlatformTime::Seconds() - SweepStartSeconds;
static constexpr int32 MarginCases[] = {0, 2, 4, 8};
TArray<FRefinementSweepRow> MarginRows;
MarginRows.Reserve(UE_ARRAY_COUNT(MarginCases));
const double MarginSweepStartSeconds = FPlatformTime::Seconds();
for (const int32 RequestedMargin : MarginCases)
{
FRefinementSweepRow& Row = MarginRows.AddDefaulted_GetRef();
Row.SampleStep = 2;
Row.RadiusInVoxels = 192;
Row.RequestedMarginVoxels = RequestedMargin;
FVoxelStrateMeasureSettings RowSettings = BaseSettings;
RowSettings.SampleStep = Row.SampleStep;
RowSettings.RadiusInVoxels = Row.RadiusInVoxels;
RowSettings.InteriorMarginVoxels = RequestedMargin;
Row.Metrics = VF_MeasureStrate(
*World.Generator,
*World.StrateManager,
VerticalShaftsIndex,
RowSettings);
Row.Probe.bChecked = true;
Row.Probe.Result = VF_AreConnected(
*World.Generator,
*World.StrateManager,
VerticalShaftsIndex,
ArrivalPoint,
DeparturePoint,
RowSettings,
Row.Probe.bStartSnapped,
Row.Probe.bGoalSnapped);
if (!Row.Metrics.bValid
|| Row.Metrics.NumSampled <= 0
|| Row.Metrics.NumSampled > BaseSettings.MaxCells
|| Row.Metrics.NumAir <= 0
|| Row.Metrics.NumSolid <= 0)
{
AddError(FString::Printf(
TEXT("HARD FAILURE: VerticalShafts margin row margin=%d did not produce a "
"bounded non-vacuous measurement (valid=%s, reason='%s', cells=%lld, "
"air=%lld, solid=%lld)."),
RequestedMargin,
Row.Metrics.bValid ? TEXT("true") : TEXT("false"),
*Row.Metrics.RefusalReason,
Row.Metrics.NumSampled,
Row.Metrics.NumAir,
Row.Metrics.NumSolid));
bAllChecksPassed = false;
}
if (Row.Metrics.ResolvedMarginVoxels != RequestedMargin)
{
AddError(FString::Printf(
TEXT("HARD FAILURE: VerticalShafts margin row requested margin=%d but the "
"measurement resolved margin=%d."),
RequestedMargin,
Row.Metrics.ResolvedMarginVoxels));
bAllChecksPassed = false;
}
if (Row.Probe.Result == EVoxelConnectivityResult::OutOfWindow
|| Row.Probe.Result == EVoxelConnectivityResult::StartCellSolid
|| Row.Probe.Result == EVoxelConnectivityResult::GoalCellSolid)
{
AddError(FString::Printf(
TEXT("HARD FAILURE: VerticalShafts margin row margin=%d could not query both "
"in-window air mouths: %s."),
RequestedMargin,
*ConnectivityProbeText(Row.Probe)));
bAllChecksPassed = false;
}
if (Row.Probe.bStartSnapped || Row.Probe.bGoalSnapped)
{
AddError(FString::Printf(
TEXT("HARD FAILURE: VerticalShafts margin row margin=%d snapped an endpoint; "
"the controlled comparison is not a mouth-to-mouth query."),
RequestedMargin));
bAllChecksPassed = false;
}
}
const double MarginSweepSeconds = FPlatformTime::Seconds() - MarginSweepStartSeconds;
bool bMarginArtifact = false;
bool bMarginSweepUsable = MarginRows.Num() == UE_ARRAY_COUNT(MarginCases);
for (const FRefinementSweepRow& Row : MarginRows)
{
bMarginArtifact |= Row.Probe.Result == EVoxelConnectivityResult::Connected;
bMarginSweepUsable &= Row.Metrics.bValid
&& Row.Metrics.ResolvedMarginVoxels == Row.RequestedMarginVoxels
&& Row.Probe.Result != EVoxelConnectivityResult::OutOfWindow
&& Row.Probe.Result != EVoxelConnectivityResult::StartCellSolid
&& Row.Probe.Result != EVoxelConnectivityResult::GoalCellSolid
&& !Row.Probe.bStartSnapped
&& !Row.Probe.bGoalSnapped;
}
FString Summary = TEXT( FString Summary = TEXT(
"VerticalShafts arrival->departure refinement (seed 1337; source mouth pair; " "VerticalShafts arrival->departure refinement (seed 1337; source mouth pair; "
"derived interior window):\n"); "derived interior window):\n");
@@ -1158,6 +1262,199 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
Summary += FString::Printf( Summary += FString::Printf(
TEXT("VerticalShafts refinement sweep wall-clock: %.3f seconds.\n"), TEXT("VerticalShafts refinement sweep wall-clock: %.3f seconds.\n"),
SweepSeconds); SweepSeconds);
Summary += TEXT(
"Part A — margin artifact check (SampleStep 2, RadiusInVoxels 192; explicit margins):\n");
Summary += TEXT(
" margin | verdict | components | largest share | sampled cells | Z window | endpoint snaps\n");
for (const FRefinementSweepRow& Row : MarginRows)
{
const FString VerdictText = ConnectivityProbeText(Row.Probe);
FString SnapText = TEXT("none");
if (Row.Probe.bStartSnapped || Row.Probe.bGoalSnapped)
{
SnapText = ConnectivityProbeText(Row.Probe);
}
Summary += FString::Printf(
TEXT(" %d | %s | %d | %.9g | %lld | [%d,%d) | %s\n"),
Row.RequestedMarginVoxels,
*VerdictText,
Row.Metrics.NumAirComponents,
Row.Metrics.LargestComponentShare,
Row.Metrics.NumSampled,
Row.Metrics.SampledMinZ,
Row.Metrics.SampledMaxZ,
*SnapText);
}
Summary += FString::Printf(
TEXT("Part A verdict: %s. Margin sweep wall-clock: %.3f seconds.\n"),
bMarginArtifact
? TEXT("ARTIFACT — at least one margin-0/2/4 query is CONNECTED; the default margin excluded the connection")
: TEXT("FINDING SURVIVES — no margin-0/2/4 query is CONNECTED; the full window is exonerated"),
MarginSweepSeconds);
if (!bMarginArtifact && bMarginSweepUsable)
{
FVoxelStrateMeasureSettings DiagnosticSettings = BaseSettings;
DiagnosticSettings.SampleStep = 2;
DiagnosticSettings.RadiusInVoxels = 192;
DiagnosticSettings.InteriorMarginVoxels = 0;
const FVoxelConnectivityDiagnostics Diagnostics = VF_DiagnoseConnectivity(
*World.Generator,
*World.StrateManager,
VerticalShaftsIndex,
ArrivalPoint,
DeparturePoint,
DiagnosticSettings);
if (!Diagnostics.bValid
|| Diagnostics.NumAirCells <= 0
|| Diagnostics.StartComponentCells <= 0
|| Diagnostics.GoalComponentCells <= 0
|| Diagnostics.StartToGoalComponentDistanceCells < 0
|| Diagnostics.GoalToStartComponentDistanceCells < 0)
{
AddError(TEXT("HARD FAILURE: the surviving VerticalShafts finding could not produce "
"component and separation diagnostics."));
bAllChecksPassed = false;
}
const float MouthXYSeparation = FMath::Sqrt(
FMath::Square(ArrivalPoint.X - DeparturePoint.X)
+ FMath::Square(ArrivalPoint.Y - DeparturePoint.Y));
const float MouthZDelta = DeparturePoint.Z - ArrivalPoint.Z;
const float MouthStraightLine = FVector::Dist(ArrivalPoint, DeparturePoint);
const float CoarseStraightLineCells = MouthStraightLine
/ static_cast<float>(DiagnosticSettings.SampleStep);
Summary += TEXT("Part B — VerticalShafts diagnosis (seed 1337; margin 0; step 2; radius 192):\n");
Summary += FString::Printf(
TEXT(" arrival LowerPoint: (%.3f, %.3f, %.3f); departure UpperPoint: "
"(%.3f, %.3f, %.3f); XY separation %.3f voxels; Z delta %.3f voxels.\n"),
ArrivalPoint.X,
ArrivalPoint.Y,
ArrivalPoint.Z,
DeparturePoint.X,
DeparturePoint.Y,
DeparturePoint.Z,
MouthXYSeparation,
MouthZDelta);
Summary += FString::Printf(
TEXT(" mouth components: air=%lld; arrival=%lld (share %.9g); departure=%lld "
"(share %.9g).\n"),
Diagnostics.NumAirCells,
Diagnostics.StartComponentCells,
Diagnostics.StartComponentShare,
Diagnostics.GoalComponentCells,
Diagnostics.GoalComponentShare);
Summary += FString::Printf(
TEXT(" coarse-grid separation: straight-line %.3f cells (%.3f voxels); "
"arrival to nearest departure-component cell %.3f cells (%.3f voxels); "
"departure to nearest arrival-component cell %.3f cells (%.3f voxels).\n"),
CoarseStraightLineCells,
MouthStraightLine,
Diagnostics.StartToGoalComponentDistanceCells,
Diagnostics.StartToGoalComponentDistanceCells * static_cast<float>(DiagnosticSettings.SampleStep),
Diagnostics.GoalToStartComponentDistanceCells,
Diagnostics.GoalToStartComponentDistanceCells * static_cast<float>(DiagnosticSettings.SampleStep));
Summary += FString::Printf(
TEXT(" diagnostic route verdict: %s; endpoint snaps: start=%s, goal=%s.\n"),
ConnectivityResultName(Diagnostics.Result),
Diagnostics.bStartSnapped ? TEXT("yes") : TEXT("no"),
Diagnostics.bGoalSnapped ? TEXT("yes") : TEXT("no"));
static constexpr int32 SeedCases[] = {
1337, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15,
};
int32 SeedPasses = 0;
int32 SeedFailures = 0;
int32 SeedSnapEvents = 0;
int32 SeedResultCounts[6] = {};
const double SeedSweepStartSeconds = FPlatformTime::Seconds();
for (const int32 Seed : SeedCases)
{
FTestWorld SeedWorld;
SeedWorld.Build(Seed, /*InGapChunks=*/2);
if (!SeedWorld.IsValid())
{
AddError(FString::Printf(
TEXT("HARD FAILURE: seed %d could not build the VerticalShafts fixture."),
Seed));
++SeedFailures;
continue;
}
FVector SeedArrivalPoint = FVector::ZeroVector;
FVector SeedDeparturePoint = FVector::ZeroVector;
if (!FindChainMouths(
SeedWorld.StrateManager->GetPassages(),
VerticalShaftsIndex,
SeedArrivalPoint,
SeedDeparturePoint))
{
AddError(FString::Printf(
TEXT("HARD FAILURE: seed %d did not produce exactly one VerticalShafts "
"arrival and departure mouth."),
Seed));
++SeedFailures;
continue;
}
bool bStartSnapped = false;
bool bGoalSnapped = false;
const EVoxelConnectivityResult SeedResult = VF_AreConnected(
*SeedWorld.Generator,
*SeedWorld.StrateManager,
VerticalShaftsIndex,
SeedArrivalPoint,
SeedDeparturePoint,
DiagnosticSettings,
bStartSnapped,
bGoalSnapped);
++SeedResultCounts[static_cast<int32>(SeedResult)];
const int32 SnapEvents = (bStartSnapped ? 1 : 0) + (bGoalSnapped ? 1 : 0);
SeedSnapEvents += SnapEvents;
if (SeedResult == EVoxelConnectivityResult::Connected
&& !bStartSnapped
&& !bGoalSnapped)
{
++SeedPasses;
}
else
{
++SeedFailures;
}
}
const double SeedSweepSeconds = FPlatformTime::Seconds() - SeedSweepStartSeconds;
Summary += FString::Printf(
TEXT(" multi-seed arrival->departure sweep (%d seeds; margin 0, step 2, radius 192): "
"%d pass, %d fail, %d snapped endpoint events; wall-clock %.3f seconds.\n"),
UE_ARRAY_COUNT(SeedCases),
SeedPasses,
SeedFailures,
SeedSnapEvents,
SeedSweepSeconds);
Summary += FString::Printf(
TEXT(" seed result breakdown: CONNECTED=%d, NOT_CONNECTED_AT_THIS_RESOLUTION=%d, "
"START_CELL_SOLID=%d, GOAL_CELL_SOLID=%d, OUT_OF_WINDOW=%d, COARSE_LIED=%d.\n"),
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::Connected)],
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::NotConnectedAtThisResolution)],
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::StartCellSolid)],
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::GoalCellSolid)],
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::OutOfWindow)],
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::CoarseLied)]);
}
else
{
Summary += FString::Printf(
TEXT("Part B diagnosis and multi-seed sweep: SKIPPED because Part A %s.\n"),
bMarginArtifact
? TEXT("found the connection in the previously excluded band")
: TEXT("did not produce a usable four-row measurement/query set"));
}
AddInfo(Summary); AddInfo(Summary);
return bAllChecksPassed; return bAllChecksPassed;
+183 -24
View File
@@ -164,14 +164,14 @@ namespace VoxelStrateMeasurePrivate
} }
else if (DoubleSealThickness <= static_cast<float>(INT32_MIN)) else if (DoubleSealThickness <= static_cast<float>(INT32_MIN))
{ {
RequestedMargin = 1; RequestedMargin = 0;
} }
else else
{ {
RequestedMargin = FMath::CeilToInt(DoubleSealThickness); RequestedMargin = FMath::CeilToInt(DoubleSealThickness);
} }
} }
const int32 ResolvedMargin = FMath::Clamp(RequestedMargin, 1, MaxMargin); const int32 ResolvedMargin = FMath::Clamp(RequestedMargin, 0, MaxMargin);
const int64 InteriorMinZ = StrateBottomZ + static_cast<int64>(ResolvedMargin); const int64 InteriorMinZ = StrateBottomZ + static_cast<int64>(ResolvedMargin);
const int64 InteriorMaxZ = StrateTopZ - static_cast<int64>(ResolvedMargin); const int64 InteriorMaxZ = StrateTopZ - static_cast<int64>(ResolvedMargin);
@@ -803,6 +803,94 @@ namespace VoxelStrateMeasurePrivate
} }
return true; return true;
} }
EVoxelConnectivityResult EvaluateConnectivityOnGrid(
const UVoxelGenerator& Generator,
const FSampleGrid& Grid,
const FVector& AVoxel,
const FVector& BVoxel,
int32& OutStart,
int32& OutGoal,
bool& bOutStartSnapped,
bool& bOutGoalSnapped)
{
OutStart = -1;
OutGoal = -1;
bOutStartSnapped = false;
bOutGoalSnapped = false;
const EEndpointCellResult StartCell = ResolveEndpointCell(
Grid, AVoxel, OutStart, bOutStartSnapped);
const EEndpointCellResult GoalCell = ResolveEndpointCell(
Grid, BVoxel, OutGoal, bOutGoalSnapped);
if (StartCell == EEndpointCellResult::OutOfWindow
|| GoalCell == EEndpointCellResult::OutOfWindow)
{
return EVoxelConnectivityResult::OutOfWindow;
}
if (StartCell == EEndpointCellResult::Solid)
{
return EVoxelConnectivityResult::StartCellSolid;
}
if (GoalCell == EEndpointCellResult::Solid)
{
return EVoxelConnectivityResult::GoalCellSolid;
}
TArray<int32> Path;
if (!FindCoarsePath(Grid, OutStart, OutGoal, Path))
{
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;
}
return EVoxelConnectivityResult::Connected;
}
float DistanceToComponent(
const FSampleGrid& Grid,
int32 StartCell,
int32 TargetComponent,
const TArray<int32>& Components)
{
if (Components[StartCell] == TargetComponent)
{
return 0.0f;
}
int32 StartX = 0;
int32 StartY = 0;
int32 StartZ = 0;
DecodeIndex(Grid, StartCell, StartX, StartY, StartZ);
int64 BestDistanceSquared = MAX_int64;
for (int32 Index = 0; Index < Grid.CellCount; ++Index)
{
if (Components[Index] != TargetComponent)
{
continue;
}
int32 X = 0;
int32 Y = 0;
int32 Z = 0;
DecodeIndex(Grid, Index, X, Y, Z);
const int64 DX = static_cast<int64>(X) - static_cast<int64>(StartX);
const int64 DY = static_cast<int64>(Y) - static_cast<int64>(StartY);
const int64 DZ = static_cast<int64>(Z) - static_cast<int64>(StartZ);
const int64 DistanceSquared = DX * DX + DY * DY + DZ * DZ;
BestDistanceSquared = FMath::Min(BestDistanceSquared, DistanceSquared);
}
return BestDistanceSquared == MAX_int64
? -1.0f
: FMath::Sqrt(static_cast<float>(BestDistanceSquared));
}
} }
FVoxelStrateMetrics VF_MeasureStrate( FVoxelStrateMetrics VF_MeasureStrate(
@@ -859,38 +947,109 @@ EVoxelConnectivityResult VF_AreConnected(
int32 Start = -1; int32 Start = -1;
int32 Goal = -1; int32 Goal = -1;
const VoxelStrateMeasurePrivate::EEndpointCellResult StartCell = return VoxelStrateMeasurePrivate::EvaluateConnectivityOnGrid(
VoxelStrateMeasurePrivate::ResolveEndpointCell( Generator,
Grid, AVoxel, Start, bOutStartSnapped); Grid,
const VoxelStrateMeasurePrivate::EEndpointCellResult GoalCell = AVoxel,
VoxelStrateMeasurePrivate::ResolveEndpointCell( BVoxel,
Grid, BVoxel, Goal, bOutGoalSnapped); Start,
if (StartCell == VoxelStrateMeasurePrivate::EEndpointCellResult::OutOfWindow Goal,
|| GoalCell == VoxelStrateMeasurePrivate::EEndpointCellResult::OutOfWindow) bOutStartSnapped,
bOutGoalSnapped);
}
FVoxelConnectivityDiagnostics VF_DiagnoseConnectivity(
const UVoxelGenerator& Generator,
const UVoxelStrateManager& Manager,
int32 StrateIndex,
const FVector& AVoxel,
const FVector& BVoxel,
const FVoxelStrateMeasureSettings& Settings)
{
FVoxelConnectivityDiagnostics Result;
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 EVoxelConnectivityResult::OutOfWindow; return Result;
} }
if (StartCell == VoxelStrateMeasurePrivate::EEndpointCellResult::Solid)
VoxelStrateMeasurePrivate::FSampleGrid Grid;
FString RefusalReason;
if (!VoxelStrateMeasurePrivate::BuildSampleGrid(
Generator, Manager, StrateIndex, Settings, Grid, RefusalReason))
{
return Result;
}
int32 Start = -1;
int32 Goal = -1;
Result.Result = VoxelStrateMeasurePrivate::EvaluateConnectivityOnGrid(
Generator,
Grid,
AVoxel,
BVoxel,
Start,
Goal,
Result.bStartSnapped,
Result.bGoalSnapped);
Result.bValid = Result.Result != EVoxelConnectivityResult::OutOfWindow
|| (Start >= 0 && Goal >= 0);
if (Start < 0 || Goal < 0)
{ {
return EVoxelConnectivityResult::StartCellSolid; return Result;
} }
if (GoalCell == VoxelStrateMeasurePrivate::EEndpointCellResult::Solid)
TArray<int32> Components;
int32 NumComponents = 0;
int64 LargestComponentCells = 0;
int32 LargestComponentLowestCell = INDEX_NONE;
int32 NumComponentsAtLeast1Pct = 0;
int64 NumAir = 0;
for (const uint8 bAir : Grid.Air)
{
NumAir += bAir != 0u ? 1 : 0;
}
FloodFillAir(
Grid,
Components,
NumComponents,
NumAir,
LargestComponentCells,
LargestComponentLowestCell,
NumComponentsAtLeast1Pct);
const int32 StartComponent = Components[Start];
const int32 GoalComponent = Components[Goal];
if (StartComponent < 0 || GoalComponent < 0)
{ {
return EVoxelConnectivityResult::GoalCellSolid; return Result;
} }
TArray<int32> Path; TArray<int32> ComponentCells;
if (!VoxelStrateMeasurePrivate::FindCoarsePath(Grid, Start, Goal, Path)) ComponentCells.Init(0, NumComponents);
for (const int32 Component : Components)
{ {
return EVoxelConnectivityResult::NotConnectedAtThisResolution; if (Component >= 0)
{
++ComponentCells[Component];
}
} }
// The coarse route is evidence, not a pass. Re-check this one BFS route at one-voxel spacing Result.NumAirCells = NumAir;
// in the original voxel field. A single solid sample is the dangerous coarse false positive. Result.StartComponentCells = ComponentCells[StartComponent];
if (!VoxelStrateMeasurePrivate::FullResolutionPathIsAir( Result.GoalComponentCells = ComponentCells[GoalComponent];
Generator, Grid, AVoxel, BVoxel, Path)) if (NumAir > 0)
{ {
return EVoxelConnectivityResult::CoarseLied; Result.StartComponentShare = static_cast<float>(
static_cast<double>(Result.StartComponentCells) / static_cast<double>(NumAir));
Result.GoalComponentShare = static_cast<float>(
static_cast<double>(Result.GoalComponentCells) / static_cast<double>(NumAir));
} }
return EVoxelConnectivityResult::Connected; Result.StartToGoalComponentDistanceCells =
VoxelStrateMeasurePrivate::DistanceToComponent(
Grid, Start, GoalComponent, Components);
Result.GoalToStartComponentDistanceCells =
VoxelStrateMeasurePrivate::DistanceToComponent(
Grid, Goal, StartComponent, Components);
return Result;
} }
+42 -1
View File
@@ -26,7 +26,7 @@ struct VOXELFORGE_API FVoxelStrateMeasureSettings
FVector2D CenterXY = FVector2D::ZeroVector; // Actor-space voxel coordinates. FVector2D CenterXY = FVector2D::ZeroVector; // Actor-space voxel coordinates.
int32 MaxCells = 8000000; // Refuse a grid larger than this many cells. int32 MaxCells = 8000000; // Refuse a grid larger than this many cells.
int32 HeadroomCells = 2; // Air cells above a floor cell required for walkable. int32 HeadroomCells = 2; // Air cells above a floor cell required for walkable.
int32 InteriorMarginVoxels = -1; // <0 = derive from BoundarySealThickness (2x, clamped). int32 InteriorMarginVoxels = -1; // <0 = derive from BoundarySealThickness (2x, clamped); explicit 0 includes the seal.
}; };
/** Metrics derived from one coarse grid and its single air flood fill. */ /** Metrics derived from one coarse grid and its single air flood fill. */
@@ -55,6 +55,33 @@ struct VOXELFORGE_API FVoxelStrateMetrics
int32 SampledMaxZ = 0; int32 SampledMaxZ = 0;
}; };
/**
* Component facts for one coarse mouth-to-mouth query.
*
* The component shares are fractions of the sampled air cells. The two component distances are
* exact spatial Euclidean distances in coarse-cell coordinates to the nearest cell of the other
* mouth's component. Solid cells do not participate in that proximity calculation because it
* measures geometric separation between air components, not an alternate route. A distance of
* zero means both mouths resolve to the same component. Negative distances mean that the
* requested grid or endpoint could not be analyzed.
*/
struct VOXELFORGE_API FVoxelConnectivityDiagnostics
{
bool bValid = false;
EVoxelConnectivityResult Result = EVoxelConnectivityResult::OutOfWindow;
bool bStartSnapped = false;
bool bGoalSnapped = false;
int64 NumAirCells = 0;
int64 StartComponentCells = 0;
int64 GoalComponentCells = 0;
float StartComponentShare = 0.0f;
float GoalComponentShare = 0.0f;
float StartToGoalComponentDistanceCells = -1.0f;
float GoalToStartComponentDistanceCells = -1.0f;
};
/** /**
* Measure one strate in actor-space voxel coordinates. * Measure one strate in actor-space voxel coordinates.
* *
@@ -87,3 +114,17 @@ VOXELFORGE_API EVoxelConnectivityResult VF_AreConnected(
const FVoxelStrateMeasureSettings& Settings, const FVoxelStrateMeasureSettings& Settings,
bool& bOutStartSnapped, bool& bOutStartSnapped,
bool& bOutGoalSnapped); bool& bOutGoalSnapped);
/**
* 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
* remain zero/negative when an endpoint has no air cell to analyze.
*/
VOXELFORGE_API FVoxelConnectivityDiagnostics VF_DiagnoseConnectivity(
const UVoxelGenerator& Generator,
const UVoxelStrateManager& Manager,
int32 StrateIndex,
const FVector& AVoxel,
const FVector& BVoxel,
const FVoxelStrateMeasureSettings& Settings);