wip(tunnels): checkpoint -- floor parameters exposed, round cut short by Codex credit limit

⚠️ THIS IS A CHECKPOINT, NOT A FINISHED CHANGE. The agent hit its usage limit
mid-round (locked out until 2026-09-15) after compacting its context, and never
filed a report or capability numbers. Committing so 1,344 lines are not left
uncommitted for five days. Verified by me only to the extent stated below.

WHAT LANDED
The tunnel floor controls are now real strate parameters under
"Cave Morphology|Tunnels|Floor", which is what the owner asked for -- hand-made
assets can tune them and the composer can roll them later:

  bTunnelFloorEnabled                true
  bTunnelFloorTerracingEnabled       true
  TunnelFloorTerraceStepHeight       1.71 voxels   (the existing 42.75 cm riser)
  TunnelFloorMaxLedgeHeight         12.0  voxels   (3 m -- climbable, not walkable)
  TunnelFloorGentleSlopeThreshold    tan(44 deg)
  TunnelFloorLedgeCountPreference    0             (0 = old per-segment staircase;
                                                    >0 = whole-chain ledge policy)
  TunnelFloorMaxLedges               4096

⭐ So the staircase-vs-ledges question the owner raised is now answerable IN THE
EDITOR without a rebuild: set LedgeCountPreference to 2-3 and the whole-chain
policy takes over from the per-segment one.

VERIFIED BY ME
  build            0 errors
  export, 2 runs   1.321 / 1.357 s   (ce53552 was 1.384 / 1.405 / 1.415)  ~-4%
  triangles        153,382           (was 153,338, +44)
  geometry hash    9815c9a2..ebeb9   -- CHANGED, so behaviour is live beyond the
                                       stated compatibility defaults

⚠️ CAPABILITY DIPPED SLIGHTLY, and I am not calling this an improvement:
  player-fit cells   20,835 -> 20,800   (-35,  -0.17%)
  reachable cells    10,901 -> 10,886   (-15,  -0.14%)
  largest component  10,886 = reachable, still one connected mass

Small enough to be a consequence of the 44-triangle geometry shift rather than a
real regression, but it is a drop, and my own rule for this work is that fit and
reachable must not fall. Treat it as unresolved until the round can be finished.

NOT DONE (the substance of the brief):
- terracing objective still minimises riser height, not step count
- the room does not yet clip the tunnel floor where they overlap (A > B)
- the traversal validator still cannot climb, so it cannot score real ledges
- the floor profile is still derived per sample, not authored at build time
- the support-column backstop is still present, i.e. the shape is still not
  correct by construction
- dig cost and the streaming-cap audit untouched

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-09-10 22:01:32 +02:00
1 parent ce53552543
commit e91f1c44bb
10 files changed
+1344 -91

No files matched your search

+402 -55
View File
@@ -468,6 +468,244 @@ namespace VoxelStrateMeasurePrivate
OutX = InPlane - OutY * Grid.NumX;
}
struct FPlayerTraversalNeighbour
{
int32 Cell = INDEX_NONE;
bool bClimb = false;
};
bool IsEligiblePlayerFitCell(
const FSampleGrid& Grid,
const TArray<uint8>& PlayerFitMask,
int32 Cell)
{
return Cell >= 0
&& Cell < Grid.CellCount
&& Grid.Air[Cell] != 0u
&& PlayerFitMask.IsValidIndex(Cell)
&& PlayerFitMask[Cell] != 0u;
}
bool IsClimbableSurfaceEdge(
const FSampleGrid& Grid,
int32 First,
int32 Second,
float FirstSupportHeight,
float SecondSupportHeight)
{
int32 FirstX = 0;
int32 FirstY = 0;
int32 FirstZ = 0;
DecodeIndex(Grid, First, FirstX, FirstY, FirstZ);
int32 SecondX = 0;
int32 SecondY = 0;
int32 SecondZ = 0;
DecodeIndex(Grid, Second, SecondX, SecondY, SecondZ);
if (FMath::Abs(FirstX - SecondX) + FMath::Abs(FirstY - SecondY) != 1)
{
return false;
}
const bool bFirstIsHigher = FirstSupportHeight > SecondSupportHeight;
const int32 LowerColumnX = bFirstIsHigher ? SecondX : FirstX;
const int32 LowerColumnY = bFirstIsHigher ? SecondY : FirstY;
const int32 HigherColumnX = bFirstIsHigher ? FirstX : SecondX;
const int32 HigherColumnY = bFirstIsHigher ? FirstY : SecondY;
const float LowerHeight = FMath::Min(FirstSupportHeight, SecondSupportHeight);
const float HigherHeight = FMath::Max(FirstSupportHeight, SecondSupportHeight);
const float Step = static_cast<float>(Grid.SampleStep);
if (!FMath::IsFinite(Step) || Step <= 0.0f
|| !FMath::IsFinite(LowerHeight) || !FMath::IsFinite(HigherHeight))
{
return false;
}
// A climb edge must meet a solid riser, not connect two independently supported
// floating poses across an empty gap. Exclude the top support cell itself: that cell is
// present for every supported platform and is not evidence of a climbable face.
const int32 FirstRiserZ = FMath::Max(
0,
FMath::FloorToInt((LowerHeight - Grid.MinZ) / Step));
const int32 LastRiserZ = FMath::Min(
Grid.NumZ - 1,
FMath::CeilToInt((HigherHeight - Grid.MinZ) / Step) - 2);
for (int32 Z = FirstRiserZ; Z <= LastRiserZ; ++Z)
{
if (Grid.Air[Grid.Index(HigherColumnX, HigherColumnY, Z)] == 0u
|| Grid.Air[Grid.Index(LowerColumnX, LowerColumnY, Z)] == 0u)
{
return true;
}
}
return false;
}
bool IsPlayerTraversalEdge(
const FSampleGrid& Grid,
const TArray<uint8>& PlayerFitMask,
const TArray<float>* PlayerFitSupportHeights,
const FVoxelStrateMeasureSettings* Settings,
int32 First,
int32 Second,
bool& bOutClimb)
{
bOutClimb = false;
if (!IsEligiblePlayerFitCell(Grid, PlayerFitMask, First)
|| !IsEligiblePlayerFitCell(Grid, PlayerFitMask, Second))
{
return false;
}
if (PlayerFitSupportHeights == nullptr || Settings == nullptr)
{
// Callers that do not carry support data retain the historical six-neighbour graph.
return true;
}
if (!PlayerFitSupportHeights->IsValidIndex(First)
|| !PlayerFitSupportHeights->IsValidIndex(Second))
{
return false;
}
int32 FirstX = 0;
int32 FirstY = 0;
int32 FirstZ = 0;
DecodeIndex(Grid, First, FirstX, FirstY, FirstZ);
int32 SecondX = 0;
int32 SecondY = 0;
int32 SecondZ = 0;
DecodeIndex(Grid, Second, SecondX, SecondY, SecondZ);
const int32 DeltaX = FMath::Abs(FirstX - SecondX);
const int32 DeltaY = FMath::Abs(FirstY - SecondY);
const int32 DeltaZ = FMath::Abs(FirstZ - SecondZ);
if (DeltaX + DeltaY == 0 && DeltaZ == 1)
{
return true;
}
if (DeltaX + DeltaY != 1)
{
return false;
}
const float FirstHeight = (*PlayerFitSupportHeights)[First];
const float SecondHeight = (*PlayerFitSupportHeights)[Second];
if (!FMath::IsFinite(FirstHeight) || !FMath::IsFinite(SecondHeight))
{
return false;
}
const float VoxelSize = FVoxelPlayerCapsuleConstants::VoxelSizeMeters;
const float StepHeightVoxels = Settings->PlayerMaxStepHeightMeters / VoxelSize;
const float ClimbHeightVoxels = Settings->PlayerMaxClimbHeightMeters / VoxelSize;
if (!FMath::IsFinite(StepHeightVoxels) || StepHeightVoxels < 0.0f
|| !FMath::IsFinite(ClimbHeightVoxels) || ClimbHeightVoxels < 0.0f)
{
return false;
}
const float HeightDelta = FMath::Abs(FirstHeight - SecondHeight);
if (HeightDelta <= StepHeightVoxels + KINDA_SMALL_NUMBER)
{
return true;
}
if (!Settings->bPlayerClimbingEnabled
|| HeightDelta > ClimbHeightVoxels + KINDA_SMALL_NUMBER)
{
return false;
}
bOutClimb = IsClimbableSurfaceEdge(
Grid, First, Second, FirstHeight, SecondHeight);
return bOutClimb;
}
void AppendPlayerTraversalNeighbours(
const FSampleGrid& Grid,
const TArray<uint8>& PlayerFitMask,
const TArray<float>* PlayerFitSupportHeights,
const FVoxelStrateMeasureSettings* Settings,
int32 Cell,
TArray<FPlayerTraversalNeighbour, TInlineAllocator<128>>& OutNeighbours)
{
OutNeighbours.Reset();
if (Cell < 0 || Cell >= Grid.CellCount)
{
return;
}
int32 X = 0;
int32 Y = 0;
int32 Z = 0;
DecodeIndex(Grid, Cell, X, Y, Z);
auto TryAdd = [&](int32 Neighbor)
{
if (Neighbor == INDEX_NONE || Neighbor == Cell)
{
return;
}
bool bClimb = false;
if (IsPlayerTraversalEdge(
Grid, PlayerFitMask, PlayerFitSupportHeights, Settings,
Cell, Neighbor, bClimb))
{
OutNeighbours.Add({ Neighbor, bClimb });
}
};
// Preserve the old direction order for ordinary movement. Extra climb edges are appended
// in a fixed direction/height order, so the route and all derived counts are stable.
if (X + 1 < Grid.NumX) TryAdd(Grid.Index(X + 1, Y, Z));
if (X > 0) TryAdd(Grid.Index(X - 1, Y, Z));
if (Y + 1 < Grid.NumY) TryAdd(Grid.Index(X, Y + 1, Z));
if (Y > 0) TryAdd(Grid.Index(X, Y - 1, Z));
if (Z + 1 < Grid.NumZ) TryAdd(Grid.Index(X, Y, Z + 1));
if (Z > 0) TryAdd(Grid.Index(X, Y, Z - 1));
if (PlayerFitSupportHeights == nullptr || Settings == nullptr
|| !Settings->bPlayerClimbingEnabled)
{
return;
}
const float VoxelSize = FVoxelPlayerCapsuleConstants::VoxelSizeMeters;
const float ClimbHeightVoxels = Settings->PlayerMaxClimbHeightMeters / VoxelSize;
if (!FMath::IsFinite(ClimbHeightVoxels) || ClimbHeightVoxels <= 0.0f)
{
return;
}
const int32 SearchCells = FMath::Min(
4096,
FMath::Max(1, FMath::CeilToInt(ClimbHeightVoxels)));
const int32 DirectionX[4] = { 1, -1, 0, 0 };
const int32 DirectionY[4] = { 0, 0, 1, -1 };
for (int32 Direction = 0; Direction < 4; ++Direction)
{
const int32 TargetX = X + DirectionX[Direction];
const int32 TargetY = Y + DirectionY[Direction];
if (TargetX < 0 || TargetX >= Grid.NumX
|| TargetY < 0 || TargetY >= Grid.NumY)
{
continue;
}
for (int32 OffsetZ = -SearchCells; OffsetZ <= SearchCells; ++OffsetZ)
{
if (OffsetZ == 0)
{
continue;
}
const int32 TargetZ = Z + OffsetZ;
if (TargetZ >= 0 && TargetZ < Grid.NumZ)
{
const int32 Neighbor = Grid.Index(TargetX, TargetY, TargetZ);
bool bClimb = false;
if (IsPlayerTraversalEdge(
Grid, PlayerFitMask, PlayerFitSupportHeights, Settings,
Cell, Neighbor, bClimb)
&& bClimb)
{
OutNeighbours.Add({ Neighbor, true });
}
}
}
}
}
void FloodFillAir(
const FSampleGrid& Grid,
TArray<int32>& OutComponents,
@@ -477,7 +715,9 @@ namespace VoxelStrateMeasurePrivate
int32& OutLargestComponentLowestCell,
int32& OutNumComponentsAtLeast1Pct,
TArray<int64>* OutComponentCells,
const TArray<uint8>* EligibilityMask = nullptr)
const TArray<uint8>* EligibilityMask = nullptr,
const TArray<float>* PlayerFitSupportHeights = nullptr,
const FVoxelStrateMeasureSettings* TraversalSettings = nullptr)
{
OutComponents.SetNumUninitialized(Grid.CellCount);
for (int32 Index = 0; Index < Grid.CellCount; ++Index)
@@ -533,13 +773,29 @@ namespace VoxelStrateMeasurePrivate
}
};
// Fixed order keeps every derived value independent of container iteration order.
if (X + 1 < Grid.NumX) Visit(Grid.Index(X + 1, Y, Z));
if (X > 0) Visit(Grid.Index(X - 1, Y, Z));
if (Y + 1 < Grid.NumY) Visit(Grid.Index(X, Y + 1, Z));
if (Y > 0) Visit(Grid.Index(X, Y - 1, Z));
if (Z + 1 < Grid.NumZ) Visit(Grid.Index(X, Y, Z + 1));
if (Z > 0) Visit(Grid.Index(X, Y, Z - 1));
if (EligibilityMask != nullptr
&& PlayerFitSupportHeights != nullptr
&& TraversalSettings != nullptr)
{
TArray<FPlayerTraversalNeighbour, TInlineAllocator<128>> Neighbours;
AppendPlayerTraversalNeighbours(
Grid, *EligibilityMask, PlayerFitSupportHeights,
TraversalSettings, Current, Neighbours);
for (const FPlayerTraversalNeighbour& Neighbour : Neighbours)
{
Visit(Neighbour.Cell);
}
}
else
{
// Fixed order keeps every derived value independent of container iteration order.
if (X + 1 < Grid.NumX) Visit(Grid.Index(X + 1, Y, Z));
if (X > 0) Visit(Grid.Index(X - 1, Y, Z));
if (Y + 1 < Grid.NumY) Visit(Grid.Index(X, Y + 1, Z));
if (Y > 0) Visit(Grid.Index(X, Y - 1, Z));
if (Z + 1 < Grid.NumZ) Visit(Grid.Index(X, Y, Z + 1));
if (Z > 0) Visit(Grid.Index(X, Y, Z - 1));
}
}
const int64 ComponentCells64 = static_cast<int64>(ComponentCells);
@@ -1079,10 +1335,15 @@ namespace VoxelStrateMeasurePrivate
const FVoxelStrateMeasureSettings& Settings,
TArray<uint8>& OutPlayerFit,
int64& OutNumPlayerFitCells,
FString& OutReason)
FString& OutReason,
TArray<float>* OutPlayerFitSupportHeights = nullptr)
{
OutPlayerFit.Reset();
OutNumPlayerFitCells = 0;
if (OutPlayerFitSupportHeights != nullptr)
{
OutPlayerFitSupportHeights->Reset();
}
if (Grid.SampleStep != 1)
{
return Refuse(
@@ -1155,6 +1416,10 @@ namespace VoxelStrateMeasurePrivate
}
OutPlayerFit.Init(0u, Grid.CellCount);
if (OutPlayerFitSupportHeights != nullptr)
{
OutPlayerFitSupportHeights->Init(-FLT_MAX, Grid.CellCount);
}
TArray<int64> PlayerFitCounts;
PlayerFitCounts.Init(0, Grid.NumZ);
@@ -1261,6 +1526,10 @@ namespace VoxelStrateMeasurePrivate
continue;
}
OutPlayerFit[Current] = 1u;
if (OutPlayerFitSupportHeights != nullptr)
{
(*OutPlayerFitSupportHeights)[Current] = SupportHeight;
}
++NumPlayerFitInSlice;
}
}
@@ -1411,7 +1680,8 @@ namespace VoxelStrateMeasurePrivate
const FSampleGrid& Grid,
const FVoxelStrateMeasureSettings& Settings,
FVoxelStrateMetrics& InOutMetrics,
TArray<uint8>* OutPlayerFitMask = nullptr)
TArray<uint8>* OutPlayerFitMask = nullptr,
TArray<float>* OutPlayerFitSupportHeights = nullptr)
{
InOutMetrics.bPlayerFitResolved = false;
InOutMetrics.PlayerFitRefusalReason.Reset();
@@ -1423,9 +1693,11 @@ namespace VoxelStrateMeasurePrivate
InOutMetrics.MinimumPlayerClearanceVoxels = 0.0f;
TArray<uint8> PlayerFit;
TArray<float> PlayerFitSupportHeights;
if (!BuildPlayerFitMask(
Grid, Settings, PlayerFit, InOutMetrics.NumPlayerFitCells,
InOutMetrics.PlayerFitRefusalReason))
InOutMetrics.PlayerFitRefusalReason,
&PlayerFitSupportHeights))
{
if (OutPlayerFitMask != nullptr)
{
@@ -1453,7 +1725,9 @@ namespace VoxelStrateMeasurePrivate
LargestComponentLowestCell,
NumComponentsAtLeast1Pct,
nullptr,
&PlayerFit);
&PlayerFit,
&PlayerFitSupportHeights,
&Settings);
InOutMetrics.NumTraversableComponents = NumComponents;
InOutMetrics.LargestTraversableComponentCells = LargestComponentCells;
InOutMetrics.TraversableComponentShare = InOutMetrics.NumPlayerFitCells > 0
@@ -1466,13 +1740,18 @@ namespace VoxelStrateMeasurePrivate
{
*OutPlayerFitMask = MoveTemp(PlayerFit);
}
if (OutPlayerFitSupportHeights != nullptr)
{
*OutPlayerFitSupportHeights = MoveTemp(PlayerFitSupportHeights);
}
}
void BuildPlayerFitConnectivityMetrics(
const FSampleGrid& Grid,
const FVoxelStrateMeasureSettings& Settings,
FVoxelStrateMetrics& OutMetrics,
TArray<uint8>& OutPlayerFitMask)
TArray<uint8>& OutPlayerFitMask,
TArray<float>& OutPlayerFitSupportHeights)
{
OutMetrics = FVoxelStrateMetrics();
OutMetrics.ResolvedMarginVoxels = Grid.ResolvedMarginVoxels;
@@ -1492,7 +1771,9 @@ namespace VoxelStrateMeasurePrivate
? static_cast<float>(static_cast<double>(OutMetrics.NumAir)
/ static_cast<double>(OutMetrics.NumSampled))
: 0.0f;
BuildPlayerFitMetrics(Grid, Settings, OutMetrics, &OutPlayerFitMask);
BuildPlayerFitMetrics(
Grid, Settings, OutMetrics, &OutPlayerFitMask,
&OutPlayerFitSupportHeights);
OutMetrics.bValid = OutMetrics.NumSampled > 0;
}
@@ -1951,7 +2232,9 @@ namespace VoxelStrateMeasurePrivate
const FVector& B,
const TArray<int32>& Path,
FCoarseEdge& OutFailedEdge,
const TArray<uint8>* EligibilityMask = nullptr)
const TArray<uint8>* EligibilityMask = nullptr,
const TArray<float>* PlayerFitSupportHeights = nullptr,
const FVoxelStrateMeasureSettings* TraversalSettings = nullptr)
{
OutFailedEdge = FCoarseEdge();
@@ -1972,6 +2255,20 @@ namespace VoxelStrateMeasurePrivate
}
return false;
}
if (PathIndex > 0
&& PlayerFitSupportHeights != nullptr
&& TraversalSettings != nullptr)
{
bool bClimb = false;
if (!IsPlayerTraversalEdge(
Grid, *EligibilityMask, PlayerFitSupportHeights,
TraversalSettings, Path[PathIndex - 1], Path[PathIndex], bClimb))
{
OutFailedEdge = MakeCoarseEdge(
Path[PathIndex - 1], Path[PathIndex]);
return false;
}
}
}
int32 StartX = 0;
int32 StartY = 0;
@@ -2045,7 +2342,9 @@ namespace VoxelStrateMeasurePrivate
int32 Goal,
const TArray<FCoarseEdge>& BlockedEdges,
TArray<int32>& OutPath,
const TArray<uint8>* EligibilityMask = nullptr)
const TArray<uint8>* EligibilityMask = nullptr,
const TArray<float>* PlayerFitSupportHeights = nullptr,
const FVoxelStrateMeasureSettings* TraversalSettings = nullptr)
{
const auto IsEligible = [&, EligibilityMask](int32 Cell)
{
@@ -2090,14 +2389,30 @@ namespace VoxelStrateMeasurePrivate
}
};
// The order is fixed so the recovered route is deterministic. This is a real BFS
// parent chain, not just a component-membership answer.
if (X + 1 < Grid.NumX) Visit(Grid.Index(X + 1, Y, Z));
if (X > 0) Visit(Grid.Index(X - 1, Y, Z));
if (Y + 1 < Grid.NumY) Visit(Grid.Index(X, Y + 1, Z));
if (Y > 0) Visit(Grid.Index(X, Y - 1, Z));
if (Z + 1 < Grid.NumZ) Visit(Grid.Index(X, Y, Z + 1));
if (Z > 0) Visit(Grid.Index(X, Y, Z - 1));
if (EligibilityMask != nullptr
&& PlayerFitSupportHeights != nullptr
&& TraversalSettings != nullptr)
{
TArray<FPlayerTraversalNeighbour, TInlineAllocator<128>> Neighbours;
AppendPlayerTraversalNeighbours(
Grid, *EligibilityMask, PlayerFitSupportHeights,
TraversalSettings, Current, Neighbours);
for (const FPlayerTraversalNeighbour& Neighbour : Neighbours)
{
Visit(Neighbour.Cell);
}
}
else
{
// The order is fixed so the recovered route is deterministic. This is a real BFS
// parent chain, not just a component-membership answer.
if (X + 1 < Grid.NumX) Visit(Grid.Index(X + 1, Y, Z));
if (X > 0) Visit(Grid.Index(X - 1, Y, Z));
if (Y + 1 < Grid.NumY) Visit(Grid.Index(X, Y + 1, Z));
if (Y > 0) Visit(Grid.Index(X, Y - 1, Z));
if (Z + 1 < Grid.NumZ) Visit(Grid.Index(X, Y, Z + 1));
if (Z > 0) Visit(Grid.Index(X, Y, Z - 1));
}
}
if (Parent[Goal] == -2)
@@ -2137,7 +2452,9 @@ namespace VoxelStrateMeasurePrivate
bool& bOutGoalSnapped,
int32& OutNumRouteRetries,
int32 MaxRouteRetries,
const TArray<uint8>* EligibilityMask = nullptr)
const TArray<uint8>* EligibilityMask = nullptr,
const TArray<float>* PlayerFitSupportHeights = nullptr,
const FVoxelStrateMeasureSettings* TraversalSettings = nullptr)
{
OutStart = -1;
OutGoal = -1;
@@ -2175,7 +2492,8 @@ namespace VoxelStrateMeasurePrivate
for (;;)
{
if (!FindCoarsePath(
Grid, OutStart, OutGoal, BlockedEdges, Path, EligibilityMask))
Grid, OutStart, OutGoal, BlockedEdges, Path, EligibilityMask,
PlayerFitSupportHeights, TraversalSettings))
{
// This is a negative verdict only after all edges excluded by earlier
// full-resolution failures leave no coarse route at this resolution.
@@ -2188,7 +2506,7 @@ namespace VoxelStrateMeasurePrivate
FCoarseEdge FailedEdge;
if (FullResolutionPathIsAir(
Generator, Sampler, Grid, AVoxel, BVoxel, Path, FailedEdge,
EligibilityMask))
EligibilityMask, PlayerFitSupportHeights, TraversalSettings))
{
return EVoxelConnectivityResult::Connected;
}
@@ -2259,7 +2577,9 @@ namespace VoxelStrateMeasurePrivate
FVoxelConnectivityDiagnostics& OutDiagnostics,
const TArray<uint8>* EligibilityMask = nullptr,
TArray<int32>* OutComponents = nullptr,
int32* OutNumComponents = nullptr)
int32* OutNumComponents = nullptr,
const TArray<float>* PlayerFitSupportHeights = nullptr,
const FVoxelStrateMeasureSettings* TraversalSettings = nullptr)
{
OutDiagnostics = FVoxelConnectivityDiagnostics();
if (OutComponents != nullptr)
@@ -2298,7 +2618,9 @@ namespace VoxelStrateMeasurePrivate
LargestComponentLowestCell,
NumComponentsAtLeast1Pct,
nullptr,
EligibilityMask);
EligibilityMask,
PlayerFitSupportHeights,
TraversalSettings);
if (OutComponents != nullptr)
{
@@ -2449,6 +2771,7 @@ namespace VoxelStrateMeasurePrivate
}
TArray<uint8> PlayerFitMask;
TArray<float> PlayerFitSupportHeights;
FVoxelStrateMetrics PlayerMetrics;
if (bPlayerFitRestricted)
{
@@ -2457,7 +2780,8 @@ namespace VoxelStrateMeasurePrivate
// Manhattan feature transform here: those values belong to the separate legacy
// measurement pass, and recomputing them for every fine candidate made the gate
// needlessly expensive without changing its answer.
BuildPlayerFitConnectivityMetrics(Grid, Settings, PlayerMetrics, PlayerFitMask);
BuildPlayerFitConnectivityMetrics(
Grid, Settings, PlayerMetrics, PlayerFitMask, PlayerFitSupportHeights);
if (OutPlayerMetrics != nullptr)
{
*OutPlayerMetrics = PlayerMetrics;
@@ -2483,7 +2807,9 @@ namespace VoxelStrateMeasurePrivate
bOutGoalSnapped,
OutNumRouteRetries,
Settings.MaxRouteRetries,
bPlayerFitRestricted ? &PlayerFitMask : nullptr);
bPlayerFitRestricted ? &PlayerFitMask : nullptr,
bPlayerFitRestricted ? &PlayerFitSupportHeights : nullptr,
bPlayerFitRestricted ? &Settings : nullptr);
if (OutDiagnostics != nullptr)
{
PopulateConnectivityDiagnostics(
@@ -2495,7 +2821,11 @@ namespace VoxelStrateMeasurePrivate
bOutGoalSnapped,
OutNumRouteRetries,
*OutDiagnostics,
bPlayerFitRestricted ? &PlayerFitMask : nullptr);
bPlayerFitRestricted ? &PlayerFitMask : nullptr,
nullptr,
nullptr,
bPlayerFitRestricted ? &PlayerFitSupportHeights : nullptr,
bPlayerFitRestricted ? &Settings : nullptr);
if (bPlayerFitRestricted &&
(Result == EVoxelConnectivityResult::StartCellNotPlayerFit
|| Result == EVoxelConnectivityResult::GoalCellNotPlayerFit))
@@ -2526,30 +2856,20 @@ namespace VoxelStrateMeasurePrivate
}
}
bool IsEligiblePlayerFitCell(
const FSampleGrid& Grid,
const TArray<uint8>& PlayerFitMask,
int32 Cell)
{
return Cell >= 0
&& Cell < Grid.CellCount
&& Grid.Air[Cell] != 0u
&& PlayerFitMask[Cell] != 0u;
}
int32 CountPlayerFitNeighbours(
const FSampleGrid& Grid,
const TArray<uint8>& PlayerFitMask,
const TArray<float>* PlayerFitSupportHeights,
const FVoxelStrateMeasureSettings* Settings,
int32 Cell)
{
TArray<FPlayerTraversalNeighbour, TInlineAllocator<128>> Neighbours;
AppendPlayerTraversalNeighbours(
Grid, PlayerFitMask, PlayerFitSupportHeights, Settings, Cell, Neighbours);
int32 Count = 0;
for (int32 Direction = 0; Direction < 6; ++Direction)
for (const FPlayerTraversalNeighbour& Neighbour : Neighbours)
{
if (IsEligiblePlayerFitCell(
Grid, PlayerFitMask, GetSixNeighbour(Grid, Cell, Direction)))
{
++Count;
}
++Count;
}
return Count;
}
@@ -2616,6 +2936,8 @@ namespace VoxelStrateMeasurePrivate
void WalkPlayerFitGraph(
const FSampleGrid& Grid,
const TArray<uint8>& PlayerFitMask,
const TArray<float>* PlayerFitSupportHeights,
const FVoxelStrateMeasureSettings* TraversalSettings,
int32 Start,
int32 Goal,
float NarrowGapThresholdVoxels,
@@ -2651,6 +2973,15 @@ namespace VoxelStrateMeasurePrivate
auto RecordTraversal = [&](int32 From, int32 To)
{
++InOutReport.AgentGraphTraversals;
bool bClimb = false;
if (PlayerFitSupportHeights != nullptr && TraversalSettings != nullptr
&& IsPlayerTraversalEdge(
Grid, PlayerFitMask, PlayerFitSupportHeights, TraversalSettings,
From, To, bClimb)
&& bClimb)
{
++InOutReport.ClimbTraversals;
}
auto IsNarrowCell = [&](int32 Cell)
{
if (NarrowCell[Cell] == 255u)
@@ -2678,11 +3009,15 @@ namespace VoxelStrateMeasurePrivate
break;
}
TArray<FPlayerTraversalNeighbour, TInlineAllocator<128>> Neighbours;
AppendPlayerTraversalNeighbours(
Grid, PlayerFitMask, PlayerFitSupportHeights, TraversalSettings,
Current, Neighbours);
bool bAdvanced = false;
while (NextDirection.Last() < 6)
while (NextDirection.Last() < Neighbours.Num())
{
const int32 Direction = NextDirection.Last()++;
const int32 Neighbour = GetSixNeighbour(Grid, Current, Direction);
const int32 Neighbour = Neighbours[Direction].Cell;
if (!IsEligiblePlayerFitCell(Grid, PlayerFitMask, Neighbour)
|| Visited[Neighbour] != 0u)
{
@@ -2706,7 +3041,9 @@ namespace VoxelStrateMeasurePrivate
// A degree-one pose is an actual topological dead end. A higher-degree pose with all
// branches already visited is merely a DFS backtrack through a loop/junction.
if (Current != Start && Current != Goal
&& CountPlayerFitNeighbours(Grid, PlayerFitMask, Current) <= 1)
&& CountPlayerFitNeighbours(
Grid, PlayerFitMask, PlayerFitSupportHeights,
TraversalSettings, Current) <= 1)
{
++InOutReport.DeadEndsEncountered;
}
@@ -3048,6 +3385,8 @@ bool VF_MeasurePlayerFitWalkWithSampler(
OutReport.CapsuleWidthMeters = 2.0f * OutReport.CapsuleRadiusMeters;
OutReport.CapsuleHeightMeters = 2.0f * Settings.PlayerCapsuleHalfHeightVoxels
* FVoxelPlayerCapsuleConstants::VoxelSizeMeters;
OutReport.bClimbingEnabled = Settings.bPlayerClimbingEnabled;
OutReport.MaxClimbHeightMeters = Settings.PlayerMaxClimbHeightMeters;
OutReport.NarrowGapThresholdMeters = 1.5f * OutReport.CapsuleWidthMeters;
OutReport.NarrowGapDefinition = TEXT(
"widest axis-aligned horizontal air span at each traversed fit-graph cell");
@@ -3096,13 +3435,15 @@ bool VF_MeasurePlayerFitWalkWithSampler(
}
TArray<uint8> PlayerFitMask;
TArray<float> PlayerFitSupportHeights;
int64 NumPlayerFitCells = 0;
if (!BuildPlayerFitMask(
Grid,
Settings,
PlayerFitMask,
NumPlayerFitCells,
RefusalReason))
RefusalReason,
&PlayerFitSupportHeights))
{
OutReport.RefusalReason = RefusalReason;
return false;
@@ -3123,7 +3464,9 @@ bool VF_MeasurePlayerFitWalkWithSampler(
OutReport.bGoalSnapped,
NumRouteRetries,
Settings.MaxRouteRetries,
&PlayerFitMask);
&PlayerFitMask,
&PlayerFitSupportHeights,
&Settings);
FVoxelConnectivityDiagnostics Diagnostics;
TArray<int32> Components;
@@ -3139,7 +3482,9 @@ bool VF_MeasurePlayerFitWalkWithSampler(
Diagnostics,
&PlayerFitMask,
&Components,
&NumComponents);
&NumComponents,
&PlayerFitSupportHeights,
&Settings);
OutReport.bValid = true;
OutReport.Result = Result;
@@ -3177,6 +3522,8 @@ bool VF_MeasurePlayerFitWalkWithSampler(
WalkPlayerFitGraph(
Grid,
PlayerFitMask,
&PlayerFitSupportHeights,
&Settings,
Start,
Goal,
NarrowGapThresholdVoxels,