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`
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,
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
@@ -318,9 +318,10 @@ because a corridor thinner than `SampleStep` is invisible. No UObject state, cac
or PIE is required.
| Symbol | Role |
|--------|------|
| `FVoxelStrateMeasureSettings` / `FVoxelStrateMetrics` | Plain settings/result structs for bounded strate sampling and derived measurements; callers may override the interior margin, and results identify the resolved margin/Z window 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_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`
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`). |
| `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. |
| `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)
@@ -246,6 +246,7 @@ namespace
{
int32 SampleStep = 0;
int32 RadiusInVoxels = 0;
int32 RequestedMarginVoxels = -1;
FVoxelStrateMetrics Metrics;
FConnectivityProbe Probe;
};
@@ -1037,6 +1038,7 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
{
int32 SampleStep;
int32 RadiusInVoxels;
int32 InteriorMarginVoxels = -1;
};
static constexpr FRefinementCase Cases[] = {
{4, 256},
@@ -1056,10 +1058,12 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
FRefinementSweepRow& Row = Rows.AddDefaulted_GetRef();
Row.SampleStep = TestCase.SampleStep;
Row.RadiusInVoxels = TestCase.RadiusInVoxels;
Row.RequestedMarginVoxels = TestCase.InteriorMarginVoxels;
FVoxelStrateMeasureSettings RowSettings = BaseSettings;
RowSettings.SampleStep = TestCase.SampleStep;
RowSettings.RadiusInVoxels = TestCase.RadiusInVoxels;
RowSettings.InteriorMarginVoxels = TestCase.InteriorMarginVoxels;
Row.Metrics = VF_MeasureStrate(
*World.Generator,
@@ -1125,6 +1129,106 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
}
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(
"VerticalShafts arrival->departure refinement (seed 1337; source mouth pair; "
"derived interior window):\n");
@@ -1158,6 +1262,199 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
Summary += FString::Printf(
TEXT("VerticalShafts refinement sweep wall-clock: %.3f seconds.\n"),
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);
return bAllChecksPassed;
+183 -24
View File
@@ -164,14 +164,14 @@ namespace VoxelStrateMeasurePrivate
}
else if (DoubleSealThickness <= static_cast<float>(INT32_MIN))
{
RequestedMargin = 1;
RequestedMargin = 0;
}
else
{
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 InteriorMaxZ = StrateTopZ - static_cast<int64>(ResolvedMargin);
@@ -803,6 +803,94 @@ namespace VoxelStrateMeasurePrivate
}
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(
@@ -859,38 +947,109 @@ EVoxelConnectivityResult VF_AreConnected(
int32 Start = -1;
int32 Goal = -1;
const VoxelStrateMeasurePrivate::EEndpointCellResult StartCell =
VoxelStrateMeasurePrivate::ResolveEndpointCell(
Grid, AVoxel, Start, bOutStartSnapped);
const VoxelStrateMeasurePrivate::EEndpointCellResult GoalCell =
VoxelStrateMeasurePrivate::ResolveEndpointCell(
Grid, BVoxel, Goal, bOutGoalSnapped);
if (StartCell == VoxelStrateMeasurePrivate::EEndpointCellResult::OutOfWindow
|| GoalCell == VoxelStrateMeasurePrivate::EEndpointCellResult::OutOfWindow)
return VoxelStrateMeasurePrivate::EvaluateConnectivityOnGrid(
Generator,
Grid,
AVoxel,
BVoxel,
Start,
Goal,
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 Result;
}
VoxelStrateMeasurePrivate::FSampleGrid Grid;
FString RefusalReason;
if (!VoxelStrateMeasurePrivate::BuildSampleGrid(
Generator, Manager, StrateIndex, Settings, Grid, RefusalReason))
{
return EVoxelConnectivityResult::OutOfWindow;
return Result;
}
if (StartCell == VoxelStrateMeasurePrivate::EEndpointCellResult::Solid)
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)
{
return EVoxelConnectivityResult::GoalCellSolid;
NumAir += bAir != 0u ? 1 : 0;
}
FloodFillAir(
Grid,
Components,
NumComponents,
NumAir,
LargestComponentCells,
LargestComponentLowestCell,
NumComponentsAtLeast1Pct);
TArray<int32> Path;
if (!VoxelStrateMeasurePrivate::FindCoarsePath(Grid, Start, Goal, Path))
const int32 StartComponent = Components[Start];
const int32 GoalComponent = Components[Goal];
if (StartComponent < 0 || GoalComponent < 0)
{
return EVoxelConnectivityResult::NotConnectedAtThisResolution;
return Result;
}
TArray<int32> ComponentCells;
ComponentCells.Init(0, NumComponents);
for (const int32 Component : Components)
{
if (Component >= 0)
{
++ComponentCells[Component];
}
}
// 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 (!VoxelStrateMeasurePrivate::FullResolutionPathIsAir(
Generator, Grid, AVoxel, BVoxel, Path))
Result.NumAirCells = NumAir;
Result.StartComponentCells = ComponentCells[StartComponent];
Result.GoalComponentCells = ComponentCells[GoalComponent];
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.
int32 MaxCells = 8000000; // Refuse a grid larger than this many cells.
int32 HeadroomCells = 2; // Air cells above a floor cell required for walkable.
int32 InteriorMarginVoxels = -1; // <0 = derive from BoundarySealThickness (2x, clamped).
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. */
@@ -55,6 +55,33 @@ struct VOXELFORGE_API FVoxelStrateMetrics
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.
*
@@ -87,3 +114,17 @@ VOXELFORGE_API EVoxelConnectivityResult VF_AreConnected(
const FVoxelStrateMeasureSettings& Settings,
bool& bOutStartSnapped,
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);