fix(measure): ask the primordial law the right question
Three defects, two of them mine. 1. The anchor was a guess. LargestComponentAnchor() returned a hardcoded point near the (0,0) spine and the comment admitted it, so NOT_CONNECTED meant "not connected to a guessed point". The flood fill already knows the answer; it now returns LargestComponentPoint (tie- broken by lowest cell index, so it stays deterministic), LargestComponentCells and NumComponentsAtLeast1Pct. 2. "Connected to the largest component" is NOT the primordial law. The law is that a player must not descend into a place they cannot leave -- a question about MOUTHS. A strate can be 57 disconnected shafts and be fine, provided the shaft you arrive in is the shaft you leave from. The test now asks arrival -> departure per strate: the LowerPoint of the passage arriving, to the UpperPoint of the passage departing. 3. A false verdict and a true one were indistinguishable. VF_AreConnected returned false both when no route existed and when the endpoint's own coarse cell sampled solid -- and a passage mouth is a carved tube whose cell centre lands in adjacent rock easily. It now returns EVoxelConnectivityResult and snaps to an adjacent air cell first, reporting the repair. RESULT: the earlier "5 of 15 endpoints disconnected" was an ARTIFACT of the guessed anchor plus the wrong question. Corrected: 13 of 15 connected, 0 StartCellSolid, 0 GoalCellSolid. One real finding survives. arrival -> departure passes for 5 of 6 applicable strates; VerticalShafts FAILS -- its arrival mouth is in the largest component, its departure mouth is not. NOT yet reported as a bug: a coarse grid can falsely SEPARATE as well as falsely join, and a connecting corridor thinner than SampleStep vanishes. Next task settles it at a finer step. Also noted: GetPassages() contains a self-referential surface entry (0 -> 0), excluded from the chain law. Build clean, 18 succeeded / 0 warnings / 0 failed, verified in VoxelM.log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bp7FxoQcSoiuhxjuLr6aBM
This commit is contained in:
1 parent
13399529b5
commit
efcc8fcb65
4 files changed
+634
-148
No files matched your search
@@ -298,7 +298,10 @@ namespace VoxelStrateMeasurePrivate
|
||||
const FSampleGrid& Grid,
|
||||
TArray<int32>& OutComponents,
|
||||
int32& OutNumComponents,
|
||||
int32& OutLargestComponentCells)
|
||||
int64 NumAir,
|
||||
int64& OutLargestComponentCells,
|
||||
int32& OutLargestComponentLowestCell,
|
||||
int32& OutNumComponentsAtLeast1Pct)
|
||||
{
|
||||
OutComponents.SetNumUninitialized(Grid.CellCount);
|
||||
for (int32 Index = 0; Index < Grid.CellCount; ++Index)
|
||||
@@ -310,6 +313,8 @@ namespace VoxelStrateMeasurePrivate
|
||||
Queue.SetNumUninitialized(Grid.CellCount);
|
||||
OutNumComponents = 0;
|
||||
OutLargestComponentCells = 0;
|
||||
OutLargestComponentLowestCell = INDEX_NONE;
|
||||
OutNumComponentsAtLeast1Pct = 0;
|
||||
|
||||
for (int32 Start = 0; Start < Grid.CellCount; ++Start)
|
||||
{
|
||||
@@ -353,7 +358,20 @@ namespace VoxelStrateMeasurePrivate
|
||||
if (Z > 0) Visit(Grid.Index(X, Y, Z - 1));
|
||||
}
|
||||
|
||||
OutLargestComponentCells = FMath::Max(OutLargestComponentCells, ComponentCells);
|
||||
const int64 ComponentCells64 = static_cast<int64>(ComponentCells);
|
||||
if (NumAir > 0 && ComponentCells64 * 100 >= NumAir)
|
||||
{
|
||||
++OutNumComponentsAtLeast1Pct;
|
||||
}
|
||||
|
||||
if (ComponentCells64 > OutLargestComponentCells
|
||||
|| (ComponentCells64 == OutLargestComponentCells
|
||||
&& (OutLargestComponentLowestCell == INDEX_NONE
|
||||
|| Start < OutLargestComponentLowestCell)))
|
||||
{
|
||||
OutLargestComponentCells = ComponentCells64;
|
||||
OutLargestComponentLowestCell = Start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,8 +447,6 @@ namespace VoxelStrateMeasurePrivate
|
||||
{
|
||||
TArray<int32> Components;
|
||||
int32 NumComponents = 0;
|
||||
int32 LargestComponentCells = 0;
|
||||
FloodFillAir(Grid, Components, NumComponents, LargestComponentCells);
|
||||
|
||||
int64 NumAir = 0;
|
||||
for (const uint8 bAir : Grid.Air)
|
||||
@@ -438,16 +454,38 @@ namespace VoxelStrateMeasurePrivate
|
||||
NumAir += bAir != 0u ? 1 : 0;
|
||||
}
|
||||
|
||||
int64 LargestComponentCells = 0;
|
||||
int32 LargestComponentLowestCell = INDEX_NONE;
|
||||
int32 NumComponentsAtLeast1Pct = 0;
|
||||
FloodFillAir(
|
||||
Grid,
|
||||
Components,
|
||||
NumComponents,
|
||||
NumAir,
|
||||
LargestComponentCells,
|
||||
LargestComponentLowestCell,
|
||||
NumComponentsAtLeast1Pct);
|
||||
|
||||
InOutMetrics.NumSampled = Grid.CellCount;
|
||||
InOutMetrics.NumAir = NumAir;
|
||||
InOutMetrics.NumSolid = static_cast<int64>(Grid.CellCount) - NumAir;
|
||||
InOutMetrics.AirFraction = static_cast<float>(
|
||||
static_cast<double>(NumAir) / static_cast<double>(Grid.CellCount));
|
||||
InOutMetrics.NumAirComponents = NumComponents;
|
||||
InOutMetrics.LargestComponentCells = LargestComponentCells;
|
||||
InOutMetrics.NumComponentsAtLeast1Pct = NumComponentsAtLeast1Pct;
|
||||
InOutMetrics.LargestComponentShare = NumAir > 0
|
||||
? static_cast<float>(static_cast<double>(LargestComponentCells)
|
||||
/ static_cast<double>(NumAir))
|
||||
: 0.0f;
|
||||
if (NumAir > 0 && LargestComponentLowestCell != INDEX_NONE)
|
||||
{
|
||||
int32 LargestX = 0;
|
||||
int32 LargestY = 0;
|
||||
int32 LargestZ = 0;
|
||||
DecodeIndex(Grid, LargestComponentLowestCell, LargestX, LargestY, LargestZ);
|
||||
InOutMetrics.LargestComponentPoint = Grid.CellCenter(LargestX, LargestY, LargestZ);
|
||||
}
|
||||
|
||||
TArray<int32> ClearanceValues;
|
||||
ClearanceValues.Reserve(static_cast<int32>(NumAir));
|
||||
@@ -537,6 +575,93 @@ namespace VoxelStrateMeasurePrivate
|
||||
return true;
|
||||
}
|
||||
|
||||
enum class EEndpointCellResult : uint8
|
||||
{
|
||||
OutOfWindow,
|
||||
Air,
|
||||
Solid
|
||||
};
|
||||
|
||||
EEndpointCellResult ResolveEndpointCell(
|
||||
const FSampleGrid& Grid,
|
||||
const FVector& Point,
|
||||
int32& OutCell,
|
||||
bool& bOutSnapped)
|
||||
{
|
||||
bOutSnapped = false;
|
||||
int32 OriginalCell = -1;
|
||||
if (!FindCellForPoint(Grid, Point, OriginalCell))
|
||||
{
|
||||
return EEndpointCellResult::OutOfWindow;
|
||||
}
|
||||
if (Grid.Air[OriginalCell] != 0u)
|
||||
{
|
||||
OutCell = OriginalCell;
|
||||
return EEndpointCellResult::Air;
|
||||
}
|
||||
|
||||
int32 OriginalX = 0;
|
||||
int32 OriginalY = 0;
|
||||
int32 OriginalZ = 0;
|
||||
DecodeIndex(Grid, OriginalCell, OriginalX, OriginalY, OriginalZ);
|
||||
|
||||
int32 BestCell = INDEX_NONE;
|
||||
float BestDistanceSquared = FLT_MAX;
|
||||
const int32 MaxX = Grid.NumX - 1;
|
||||
const int32 MaxY = Grid.NumY - 1;
|
||||
const int32 MaxZ = Grid.NumZ - 1;
|
||||
const int32 MinX = OriginalX > 0 ? OriginalX - 1 : 0;
|
||||
const int32 MinY = OriginalY > 0 ? OriginalY - 1 : 0;
|
||||
const int32 MinZ = OriginalZ > 0 ? OriginalZ - 1 : 0;
|
||||
const int32 CandidateMaxX = OriginalX < MaxX ? OriginalX + 1 : MaxX;
|
||||
const int32 CandidateMaxY = OriginalY < MaxY ? OriginalY + 1 : MaxY;
|
||||
const int32 CandidateMaxZ = OriginalZ < MaxZ ? OriginalZ + 1 : MaxZ;
|
||||
for (int32 Z = MinZ;
|
||||
Z <= CandidateMaxZ;
|
||||
++Z)
|
||||
{
|
||||
for (int32 Y = MinY;
|
||||
Y <= CandidateMaxY;
|
||||
++Y)
|
||||
{
|
||||
for (int32 X = MinX;
|
||||
X <= CandidateMaxX;
|
||||
++X)
|
||||
{
|
||||
if (X == OriginalX && Y == OriginalY && Z == OriginalZ)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const int32 Candidate = Grid.Index(X, Y, Z);
|
||||
if (Grid.Air[Candidate] == 0u)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const float DistanceSquared = FVector::DistSquared(
|
||||
Point, Grid.CellCenter(X, Y, Z));
|
||||
if (DistanceSquared < BestDistanceSquared
|
||||
|| (DistanceSquared == BestDistanceSquared
|
||||
&& (BestCell == INDEX_NONE || Candidate < BestCell)))
|
||||
{
|
||||
BestDistanceSquared = DistanceSquared;
|
||||
BestCell = Candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (BestCell == INDEX_NONE)
|
||||
{
|
||||
return EEndpointCellResult::Solid;
|
||||
}
|
||||
|
||||
OutCell = BestCell;
|
||||
bOutSnapped = true;
|
||||
return EEndpointCellResult::Air;
|
||||
}
|
||||
|
||||
bool FullResolutionAirOnSegment(
|
||||
const UVoxelGenerator& Generator,
|
||||
const FVector& Start,
|
||||
@@ -706,20 +831,22 @@ FVoxelStrateMetrics VF_MeasureStrate(
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool VF_AreConnected(
|
||||
EVoxelConnectivityResult VF_AreConnected(
|
||||
const UVoxelGenerator& Generator,
|
||||
const UVoxelStrateManager& Manager,
|
||||
int32 StrateIndex,
|
||||
const FVector& AVoxel,
|
||||
const FVector& BVoxel,
|
||||
const FVoxelStrateMeasureSettings& Settings,
|
||||
bool& bOutCoarseLied)
|
||||
bool& bOutStartSnapped,
|
||||
bool& bOutGoalSnapped)
|
||||
{
|
||||
bOutCoarseLied = false;
|
||||
bOutStartSnapped = false;
|
||||
bOutGoalSnapped = false;
|
||||
if (!FMath::IsFinite(AVoxel.X) || !FMath::IsFinite(AVoxel.Y) || !FMath::IsFinite(AVoxel.Z)
|
||||
|| !FMath::IsFinite(BVoxel.X) || !FMath::IsFinite(BVoxel.Y) || !FMath::IsFinite(BVoxel.Z))
|
||||
{
|
||||
return false;
|
||||
return EVoxelConnectivityResult::OutOfWindow;
|
||||
}
|
||||
|
||||
VoxelStrateMeasurePrivate::FSampleGrid Grid;
|
||||
@@ -727,21 +854,35 @@ bool VF_AreConnected(
|
||||
if (!VoxelStrateMeasurePrivate::BuildSampleGrid(
|
||||
Generator, Manager, StrateIndex, Settings, Grid, RefusalReason))
|
||||
{
|
||||
return false;
|
||||
return EVoxelConnectivityResult::OutOfWindow;
|
||||
}
|
||||
|
||||
int32 Start = -1;
|
||||
int32 Goal = -1;
|
||||
if (!VoxelStrateMeasurePrivate::FindCellForPoint(Grid, AVoxel, Start)
|
||||
|| !VoxelStrateMeasurePrivate::FindCellForPoint(Grid, BVoxel, Goal))
|
||||
const VoxelStrateMeasurePrivate::EEndpointCellResult StartCell =
|
||||
VoxelStrateMeasurePrivate::ResolveEndpointCell(
|
||||
Grid, AVoxel, Start, bOutStartSnapped);
|
||||
const VoxelStrateMeasurePrivate::EEndpointCellResult GoalCell =
|
||||
VoxelStrateMeasurePrivate::ResolveEndpointCell(
|
||||
Grid, BVoxel, Goal, bOutGoalSnapped);
|
||||
if (StartCell == VoxelStrateMeasurePrivate::EEndpointCellResult::OutOfWindow
|
||||
|| GoalCell == VoxelStrateMeasurePrivate::EEndpointCellResult::OutOfWindow)
|
||||
{
|
||||
return false;
|
||||
return EVoxelConnectivityResult::OutOfWindow;
|
||||
}
|
||||
if (StartCell == VoxelStrateMeasurePrivate::EEndpointCellResult::Solid)
|
||||
{
|
||||
return EVoxelConnectivityResult::StartCellSolid;
|
||||
}
|
||||
if (GoalCell == VoxelStrateMeasurePrivate::EEndpointCellResult::Solid)
|
||||
{
|
||||
return EVoxelConnectivityResult::GoalCellSolid;
|
||||
}
|
||||
|
||||
TArray<int32> Path;
|
||||
if (!VoxelStrateMeasurePrivate::FindCoarsePath(Grid, Start, Goal, Path))
|
||||
{
|
||||
return false;
|
||||
return EVoxelConnectivityResult::NotConnected;
|
||||
}
|
||||
|
||||
// The coarse route is evidence, not a pass. Re-check this one BFS route at one-voxel spacing
|
||||
@@ -749,8 +890,7 @@ bool VF_AreConnected(
|
||||
if (!VoxelStrateMeasurePrivate::FullResolutionPathIsAir(
|
||||
Generator, Grid, AVoxel, BVoxel, Path))
|
||||
{
|
||||
bOutCoarseLied = true;
|
||||
return false;
|
||||
return EVoxelConnectivityResult::CoarseLied;
|
||||
}
|
||||
return true;
|
||||
return EVoxelConnectivityResult::Connected;
|
||||
}
|
||||
Reference in new issue
Block a user