Files
VoxelForge/Source/VoxelForge/Public/VoxelStrateMeasure.h
T
Fr0zkaandClaude Opus 5 55bf807f60 feat(descent): landing ROOMS and a walkable tunnel -- per Jahni's correction
Jahni corrected the design: "the connection between level is a tiny tunnel
connecting both strate, not vertical, it needs to be walkable" and "'the
spine' shouldn't be carved except optionally from the top first strate,
then it's used to have a landing room".

COMPOSER-NOTES §6.8 had said the spine "provides a clean landing space, not
a continuous open shaft" -- and the implementation carved a vertical
cylinder anyway. A vertical cylinder has no walkable floor except at its
bottom, so a carve guaranteeing open AIR guaranteed nothing STANDABLE.
That one omission is behind five of my dead hypotheses this week.

Now:
  origin spine   a finite rounded ROOM per strate, no vertical bore.
                 radius 14 -> 8 m wide, 7.5 m tall
  descent        a walkable tunnel, single-file by design: 3 m mouth,
                 2.5 m waist, gradient capped at a NAMED 15 deg
  exotic styles  VerticalShaft / SpiralDescent / CascadingDrops /
                 CrackCrevice preserved, no longer the default

⚠️ THE GEOMETRY BITES, and it is worth Jahni's eye: at 15 deg a 32 m drop
needs 119.4 m of horizontal run -- about 59.7 m per switchback leg. That is
a long walk between strates. It is the honest consequence of "walkable",
not a tuning artifact, and the alternative is a steeper number.

⭐ AND THE ROOT CAUSE GOT NAMED A THIRD TIME, by Codex mid-run: "the round
tube was hollow air but had no authored support strip, so 'open' still did
not mean 'standable'." Tunnels now carry a floor band, and the root join
terminates in the finite origin room rather than an annular shaft hub.

Measured, commandlet sweep, 8 archetypes x 8 seeds, 64/64 valid:
  origin column has player-fit cells   17/64 -> 63/64
  origin column reachable               0/64 ->  7/64
  arrival landing probes            315/320 fit AND in the arrival component
  tunnel audit    5,916/5,916 floor and capsule samples pass; zero gradient,
                  floor, capsule, geometry or seal failures
  narrow-gap events 0        box verdicts 14 checked, 0 violations

⚠️ arrival->departure is 6/64. The landings and tunnels are now provably
walkable; what remains is the INTRA-STRATE vertical connectivity problem
found in 6b91669 -- floors at different levels inside a strate with nothing
joining them. Separate and larger. Not tuned toward.

⚠️ PROCESS: this took 5.7 hours, of which Codex used 4 SECONDS of CPU. The
rest was polling the 73-minute automation suite at ~10% machine load. The
commandlet exists to avoid exactly that. New rule recorded: iterate with the
commandlet, full suite ONCE at the end.

Editor + Shipping builds clean. Automation 32/32, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
2026-09-08 02:14:37 +02:00

509 lines
23 KiB
C++

// Read-only, headless measurements over a coarse sample of one strate.
#pragma once
#include "CoreMinimal.h"
#include "VoxelPassageGeometry.h"
class UVoxelGenerator;
class UVoxelStrateManager;
/**
* The pinned metric capsule dimensions in voxel-space.
*
* 25 cm/voxel is the authored world scale: radius = 34 cm / 25 = 1.36 voxels and
* half-height = 88 cm / 25 = 3.52 voxels, hence a 176 cm / 25 = 7.04 voxel total height.
* The measure settings copy these values so a future character can change the query without
* changing generation. The diagnosis test separately reads movement limits from the project's
* character CDO.
*/
struct VOXELFORGE_API FVoxelPlayerCapsuleConstants
{
static constexpr float VoxelSizeCentimeters = 25.0f;
static constexpr float VoxelSizeMeters = VoxelPassageGeometry::VoxelSizeMeters;
static constexpr float RadiusCentimeters = 34.0f;
static constexpr float HalfHeightCentimeters = 88.0f;
// UCharacterMovementComponent's UE 5.7 default. Callers that have a character CDO should
// copy its authored value into PlayerMaxStepHeightMeters.
static constexpr float MaxStepHeightCentimeters = 45.0f;
static constexpr float MaxStepHeightMeters = MaxStepHeightCentimeters / 100.0f;
// UE 5.7's WalkableFloorZ=0.71 resolves to about 44.8 degrees; a character CDO may expose
// the precise angle. The diagnosis test copies that precise project value when available.
static constexpr float WalkableFloorAngleDegrees = 44.8f;
static constexpr float RadiusVoxels = VoxelPassageGeometry::PlayerRadiusVoxels;
static constexpr float HalfHeightVoxels = VoxelPassageGeometry::PlayerHalfHeightVoxels;
static constexpr float HeightVoxels = VoxelPassageGeometry::PlayerHeightVoxels;
};
/** Read-only density source used by the offline Tier 2 pass for a materialised custom stack. */
class VOXELFORGE_API IVoxelStrateDensitySampler
{
public:
virtual ~IVoxelStrateDensitySampler() = default;
virtual float SampleDensity(float WorldX, float WorldY, float WorldZ) const = 0;
};
/** Explicit outcome of a coarse connectivity query plus its full-resolution route re-check. */
enum class EVoxelConnectivityResult : uint8
{
Connected,
NotConnectedAtThisResolution, // Evidence only at this grid's resolution; corridors thinner than SampleStep are invisible.
StartCellSolid,
GoalCellSolid,
OutOfWindow,
CoarseLiedBudgetExhausted, // Unknown: retries are exhausted, or the refutation has no coarse edge to exclude.
// Appended to preserve the underlying values of the legacy result codes above.
StartCellNotPlayerFit,
GoalCellNotPlayerFit
};
/** Settings for the deterministic coarse grid used by the strate measurement pass. */
struct VOXELFORGE_API FVoxelStrateMeasureSettings
{
int32 SampleStep = 4; // Voxels between samples.
int32 RadiusInVoxels = 256; // Half-extent in X and Y around CenterXY.
FVector2D CenterXY = FVector2D::ZeroVector; // Actor-space voxel coordinates.
// When both points are set, replace the square centered on CenterXY with the XY AABB of the
// two points expanded by CoverMarginVoxels. This keeps a mouth-to-mouth measurement sized by
// the mouths' separation instead of by their distance from the world origin.
TOptional<FVector2D> CoverPointA;
TOptional<FVector2D> CoverPointB;
float CoverMarginVoxels = 48.0f;
// Origin-rooted topologies can route from one mouth to the other through the finite (0,0)
// landing room and its explicitly carved connector network.
// When true, include (0,0) in the fitted AABB before applying MaxCells. A caller must keep
// this enabled for that topology; an over-cap route window is then refused honestly instead
// of being cropped back to a mouth-only box.
bool bIncludeOriginInCoverWindow = false;
// Diagnostic-only strengthening of the preceding policy: ensure the sampled box contains an
// actual one-voxel column around (0,0), rather than ending exactly at the exclusive upper
// bound when both mouths lie on one side of the origin.
bool bForceOriginColumnInCoverWindow = false;
int32 MaxCells = 8000000; // Refuse a grid larger than this many cells.
int32 MaxRouteRetries = 16; // Alternate coarse routes to full-resolution-check after the first route.
int32 HeadroomCells = 2; // Legacy walkable-column metric only.
int32 InteriorMarginVoxels = -1; // <0 = derive from BoundarySealThickness (2x, clamped); explicit 0 includes the seal.
// Base ACharacter defaults at 25 cm/voxel: 34 cm radius / 25 = 1.36 voxels and
// 88 cm half-height / 25 = 3.52 voxels (7.04 voxels total). These are parameters, not
// generation values, so another playable capsule can be measured without a code change.
float PlayerCapsuleRadiusVoxels = FVoxelPlayerCapsuleConstants::RadiusVoxels;
float PlayerCapsuleHalfHeightVoxels = FVoxelPlayerCapsuleConstants::HalfHeightVoxels;
// Character-derived fit parameters. MaxStepHeight is deliberately named and stored in
// metres, matching UCharacterMovementComponent's authored unit. The measurement converts it
// to voxels only for the bounded downward support search.
float PlayerMaxStepHeightMeters = FVoxelPlayerCapsuleConstants::MaxStepHeightMeters;
float PlayerWalkableFloorAngleDegrees = FVoxelPlayerCapsuleConstants::WalkableFloorAngleDegrees;
// A support patch is sampled at the integer columns covered by the capsule radius. 0.75 with
// the default 1.36-voxel radius requires 4 of 5 columns, including the centre column.
float PlayerSupportPatchMinCoverageFraction = 0.75f;
};
/**
* Optional capture of the exact grid used by one measurement.
*
* Air is deliberately stored as a polarity bit: 1 means density > 0 (air), 0 means solid.
* Density retains the scalar sample that produced that bit, so an editor preview can draw a
* density=0 contour without inventing geometry by blurring the binary view. Both arrays are
* opt-in so ordinary metrics callers do not retain a multi-million-cell buffer.
* It is an editor/automation hand-off for tools such as the composer preview, not a runtime
* generation cache.
*/
struct VOXELFORGE_API FVoxelStrateSampleGrid
{
bool bValid = false;
int32 NumX = 0;
int32 NumY = 0;
int32 NumZ = 0;
int32 SampleStep = 1;
int32 CellCount = 0;
float MinX = 0.0f;
float MinY = 0.0f;
float MinZ = 0.0f;
float MaxX = 0.0f;
float MaxY = 0.0f;
float MaxZ = 0.0f;
int32 ResolvedMarginVoxels = 0;
int32 SampledMinZ = 0;
int32 SampledMaxZ = 0;
// 1 = air (density > 0), 0 = solid (density <= 0), matching the measurement polarity.
TArray<uint8> Air;
// Exact scalar density at the same cell centre as Air. Present on measurement captures;
// empty on legacy/binary-only callers. This is never used by generation.
TArray<float> Density;
FORCEINLINE int32 Index(int32 X, int32 Y, int32 Z) const
{
return static_cast<int32>(
static_cast<int64>(X)
+ static_cast<int64>(NumX) * (static_cast<int64>(Y)
+ static_cast<int64>(NumY) * static_cast<int64>(Z)));
}
bool IsValid() const
{
return bValid && NumX > 0 && NumY > 0 && NumZ > 0
&& static_cast<int64>(CellCount)
== static_cast<int64>(NumX) * static_cast<int64>(NumY)
* static_cast<int64>(NumZ)
&& Air.Num() == CellCount;
}
bool HasScalarDensity() const
{
return IsValid() && Density.Num() == CellCount;
}
};
/** Metrics derived from one coarse grid and its single air flood fill. */
struct VOXELFORGE_API FVoxelStrateMetrics
{
bool bValid = false;
FString RefusalReason;
int64 NumSampled = 0;
int64 NumAir = 0;
int64 NumSolid = 0;
float AirFraction = 0.f;
int32 NumAirComponents = 0;
float LargestComponentShare = 0.f;
FVector LargestComponentPoint = FVector::ZeroVector; // Actor-space voxel coords; valid iff NumAir > 0.
int64 LargestComponentCells = 0;
int32 NumComponentsAtLeast1Pct = 0;
float WalkableFraction = 0.f;
// Distinct sampled XY columns that contain at least one walkable cell, divided by the
// sampled XY footprint. This is a floor-area proxy, not a volume ratio: a large chamber
// can have a small WalkableFraction while retaining a substantial walkable floor.
int64 WalkableFloorColumns = 0;
float WalkableFloorAreaFraction = 0.f;
// Four-neighbour connected components of the projected walkable columns. This deliberately
// remains a 2D surface indicator; it does not prove that floors at different Z values are
// traversable in 3D. The separate arrival/departure route check is the traversal gate.
int32 NumWalkableSurfaceComponents = 0;
int64 LargestWalkableSurfaceColumns = 0;
float LargestWalkableSurfaceShare = 0.f;
float MedianFeatureScale = 0.f;
int32 MedianVerticalClearance = 0;
// Player-fit metrics are answered only by a one-voxel grid. A coarse grid refuses to infer
// support or clearance because SampleStep=4 cannot resolve a 1.36-voxel radius capsule.
bool bPlayerFitResolved = false;
FString PlayerFitRefusalReason;
int64 NumPlayerFitCells = 0;
float PlayerFitFraction = 0.0f; // Accepted free supported poses / all air cells.
int32 NumTraversableComponents = 0;
int64 LargestTraversableComponentCells = 0;
float TraversableComponentShare = 0.0f; // Largest player-fit component / player-fit cells.
// Minimum horizontal Chebyshev clearance, in voxels, among accepted supported poses. This is
// a diagnostic only; player occupancy itself uses a direct capsule stencil, never the old
// Manhattan distance transform.
float MinimumPlayerClearanceVoxels = 0.0f;
// The exact vertical window used by the measurement: [SampledMinZ, SampledMaxZ).
int32 ResolvedMarginVoxels = 0;
int32 SampledMinZ = 0;
int32 SampledMaxZ = 0;
// The exact sampled XY box and dimensions. For a fitted window these are the AABB-derived
// dimensions; for the legacy window they describe CenterXY +/- RadiusInVoxels.
int32 SampledNumX = 0;
int32 SampledNumY = 0;
int32 SampledNumZ = 0;
float SampledMinX = 0.0f;
float SampledMaxX = 0.0f;
float SampledMinY = 0.0f;
float SampledMaxY = 0.0f;
// Air-component sizes in deterministic flood-fill discovery order. Keeping these facts from
// the one required flood fill lets diagnostics classify small non-largest components without
// sampling the source a second time.
TArray<int64> AirComponentCells;
};
/**
* 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 bPlayerFitRestricted = false;
bool bPlayerFitResolved = false;
FString RefusalReason;
bool bStartSnapped = false;
bool bGoalSnapped = false;
int32 NumRouteRetries = 0; // Alternate routes full-resolution-checked after the first route.
int64 NumAirCells = 0;
int64 StartComponentCells = 0;
int64 GoalComponentCells = 0;
int64 LargestComponentCells = 0;
float StartComponentShare = 0.0f;
float GoalComponentShare = 0.0f;
float LargestComponentShare = 0.0f;
bool bStartComponentIsLargest = false;
bool bGoalComponentIsLargest = false;
float StartToGoalComponentDistanceCells = -1.0f;
float GoalToStartComponentDistanceCells = -1.0f;
};
/**
* Experiential traversal facts over the exact VF player-fit mask.
*
* This is intentionally a report, not a new movement implementation. The mask is built by the
* same direct capsule stencil used by VF_DiagnosePlayerFitConnectivityWithSampler; the explorer's
* deterministic graph walk only instruments that already-resolved six-neighbour pose graph.
* Distances are metres so the result can be read beside a human-scale screenshot.
*/
struct VOXELFORGE_API FVoxelPlayerFitWalkReport
{
bool bValid = false;
FString RefusalReason;
EVoxelConnectivityResult Result = EVoxelConnectivityResult::OutOfWindow;
bool bCanReachDeparture = false;
bool bStartSnapped = false;
bool bGoalSnapped = false;
int32 NumRouteRetries = 0;
int64 PlayerFitVolumeCells = 0;
int64 ReachablePlayerFitCells = 0;
float ReachablePlayerFitFraction = 0.0f;
int32 SampledNumX = 0;
int32 SampledNumY = 0;
int32 SampledNumZ = 0;
int32 SampledMinZ = 0;
int32 SampledMaxZ = 0;
float SampledMinX = 0.0f;
float SampledMaxX = 0.0f;
float SampledMinY = 0.0f;
float SampledMaxY = 0.0f;
// The agent follows a fixed-order depth-first walk over the already-resolved fit graph. A
// branch edge is counted twice when the agent backtracks, which makes this an instrument of
// traversal experience rather than a shortest-path length.
int64 AgentGraphTraversals = 0;
int64 DeadEndsEncountered = 0;
float DeadEndsPer100m = 0.0f;
float DistanceTravelledMeters = 0.0f;
float StraightLineMeters = 0.0f;
float Tortuosity = 0.0f;
// A narrow-gap event is a traversed graph edge touching a route cell whose widest
// axis-aligned horizontal air span is below the threshold. This deliberately reports the
// operational proxy instead of pretending the voxel mask contains an oriented corridor width.
int64 NarrowGapTraversals = 0;
float NarrowGapFraction = 0.0f;
float NarrowGapThresholdMeters = 0.0f;
float NarrowGapEventsPer100m = 0.0f;
float CapsuleRadiusMeters = 0.0f;
float CapsuleWidthMeters = 0.0f;
float CapsuleHeightMeters = 0.0f;
FString NarrowGapDefinition;
// Component facts are retained from the same fit-mask flood fill that drives the route
// verdict. They are exposed here so an editor diagnostic does not need to sample the field
// a second time just to explain a failed mouth-to-mouth walk.
int32 PlayerFitComponents = 0;
int64 LargestPlayerFitComponentCells = 0;
int64 ArrivalComponentCells = 0;
int64 DepartureComponentCells = 0;
float MouthComponentGapVoxels = -1.0f;
// Optional room probes evaluated against this same grid and start component. The arrays are
// empty for ordinary callers; when supplied, each entry is 1 only when the probe is both a
// player-fit cell and in the arrival/start component.
TArray<uint8> ComponentProbePlayerFit;
TArray<uint8> ComponentProbeInStartComponent;
// The DFS returns to its root when the target component is unreachable. Keep both the
// physical end of that walk and the last newly reached pose: the latter is the useful
// failure-frontier point for a render, while the former is the literal final pose.
bool bHasAgentFinalPosition = false;
FVector AgentFinalVoxels = FVector::ZeroVector;
bool bHasLastReachedPosition = false;
FVector LastReachedVoxels = FVector::ZeroVector;
bool bHasTargetComponentNearestCell = false;
FVector TargetComponentNearestVoxels = FVector::ZeroVector;
float AgentToTargetComponentGapVoxels = -1.0f;
// Origin checks are facts about the sampled fit mask. An origin-inclusive secondary probe
// can populate these even when the primary mouth-sized window deliberately excludes (0,0).
bool bOriginColumnInSampledWindow = false;
bool bOriginColumnHasPlayerFit = false;
int64 OriginColumnPlayerFitCells = 0;
bool bOriginColumnReachable = false;
float ReachableSetToOriginColumnVoxels = -1.0f;
};
/**
* Measure one strate in actor-space voxel coordinates.
*
* The pass samples the requested strate between a resolved margin inside its top and bottom
* boundaries, flood-fills its air once, and derives all returned metrics from that same grid.
* It never mutates either input object.
*/
VOXELFORGE_API FVoxelStrateMetrics VF_MeasureStrate(
const UVoxelGenerator& Generator,
const UVoxelStrateManager& Manager,
int32 StrateIndex,
const FVoxelStrateMeasureSettings& Settings,
FVoxelStrateSampleGrid* OutSampleGrid = nullptr);
/** The same Tier 2 pass over an explicit voxel Z window and a custom read-only sampler. */
VOXELFORGE_API FVoxelStrateMetrics VF_MeasureStrateWithSampler(
const IVoxelStrateDensitySampler& Sampler,
int32 StrateBottomWorldZ,
int32 StrateTopWorldZ,
float BoundarySealThickness,
const FVoxelStrateMeasureSettings& Settings,
FVoxelStrateSampleGrid* OutSampleGrid = nullptr);
/**
* Test coarse connectivity and re-check recovered coarse routes at full resolution.
*
* If an endpoint's own coarse cell is solid, the query searches the 26 neighbouring cells and
* uses the nearest air cell when one exists. The snap flags report those repairs. Solid-cell
* results are returned only when no adjacent air cell exists. When a route fails its
* full-resolution re-check, the failed coarse edge is excluded from a deterministic local
* blocklist and BFS is retried up to MaxRouteRetries. A missing route after exclusions returns
* NotConnectedAtThisResolution. If the retry budget is exhausted while routes are still being
* refuted, the result is CoarseLiedBudgetExhausted: unknown, not disconnected. The same unknown
* result is used when a refutation is in an endpoint-to-cell segment with no coarse edge to
* exclude. Every Connected result has a fully re-walked air route.
*/
VOXELFORGE_API EVoxelConnectivityResult VF_AreConnected(
const UVoxelGenerator& Generator,
const UVoxelStrateManager& Manager,
int32 StrateIndex,
const FVector& AVoxel,
const FVector& BVoxel,
const FVoxelStrateMeasureSettings& Settings,
bool& bOutStartSnapped,
bool& bOutGoalSnapped);
/** The same query as the simple overload, with the number of alternate routes actually tried. */
VOXELFORGE_API EVoxelConnectivityResult VF_AreConnected(
const UVoxelGenerator& Generator,
const UVoxelStrateManager& Manager,
int32 StrateIndex,
const FVector& AVoxel,
const FVector& BVoxel,
const FVoxelStrateMeasureSettings& Settings,
bool& bOutStartSnapped,
bool& bOutGoalSnapped,
int32& OutNumRouteRetries);
/**
* The same connectivity query with component facts returned from the same sampled grid. This
* overload exists for diagnostics that need both the required route verdict and the component
* containing one endpoint without sampling the strate a second time.
*/
VOXELFORGE_API EVoxelConnectivityResult VF_AreConnected(
const UVoxelGenerator& Generator,
const UVoxelStrateManager& Manager,
int32 StrateIndex,
const FVector& AVoxel,
const FVector& BVoxel,
const FVoxelStrateMeasureSettings& Settings,
bool& bOutStartSnapped,
bool& bOutGoalSnapped,
FVoxelConnectivityDiagnostics& OutDiagnostics);
/**
* Return the component sizes and spatial separation facts for the same query performed by
* VF_AreConnected. The returned Result uses the same retrying full-resolution route re-check,
* and NumRouteRetries reports the number of alternate routes actually checked. bValid means the
* sample grid was built and both endpoint coordinates were in its bounds; component fields
* remain zero/negative when an endpoint has no air cell to analyze. Largest-component membership
* treats equal-sized components as tied for largest.
*/
VOXELFORGE_API FVoxelConnectivityDiagnostics VF_DiagnoseConnectivity(
const UVoxelGenerator& Generator,
const UVoxelStrateManager& Manager,
int32 StrateIndex,
const FVector& AVoxel,
const FVector& BVoxel,
const FVoxelStrateMeasureSettings& Settings);
/** Connectivity variant for the explicit custom-stack measurement window. */
VOXELFORGE_API EVoxelConnectivityResult VF_AreConnectedWithSampler(
const IVoxelStrateDensitySampler& Sampler,
int32 StrateBottomWorldZ,
int32 StrateTopWorldZ,
float BoundarySealThickness,
const FVector& AVoxel,
const FVector& BVoxel,
const FVoxelStrateMeasureSettings& Settings,
bool& bOutStartSnapped,
bool& bOutGoalSnapped,
FVoxelConnectivityDiagnostics* OutDiagnostics = nullptr);
/** Full diagnostics variant for a custom stack. */
VOXELFORGE_API FVoxelConnectivityDiagnostics VF_DiagnoseConnectivityWithSampler(
const IVoxelStrateDensitySampler& Sampler,
int32 StrateBottomWorldZ,
int32 StrateTopWorldZ,
float BoundarySealThickness,
const FVector& AVoxel,
const FVector& BVoxel,
const FVoxelStrateMeasureSettings& Settings);
/**
* Player-fit connectivity over a custom stack. This is deliberately a separate API from the
* legacy air route: it refuses unless Settings.SampleStep == 1, resolves endpoints against the
* direct capsule-occupancy/floor mask, and flood-fills/routes only through player-fit cells.
*/
VOXELFORGE_API EVoxelConnectivityResult VF_ArePlayerFitConnectedWithSampler(
const IVoxelStrateDensitySampler& Sampler,
int32 StrateBottomWorldZ,
int32 StrateTopWorldZ,
float BoundarySealThickness,
const FVector& AVoxel,
const FVector& BVoxel,
const FVoxelStrateMeasureSettings& Settings,
bool& bOutStartSnapped,
bool& bOutGoalSnapped,
FVoxelConnectivityDiagnostics* OutDiagnostics = nullptr);
/** Full diagnostics variant for the player-fit custom-stack route. */
VOXELFORGE_API FVoxelConnectivityDiagnostics VF_DiagnosePlayerFitConnectivityWithSampler(
const IVoxelStrateDensitySampler& Sampler,
int32 StrateBottomWorldZ,
int32 StrateTopWorldZ,
float BoundarySealThickness,
const FVector& AVoxel,
const FVector& BVoxel,
const FVoxelStrateMeasureSettings& Settings,
FVoxelStrateMetrics* OutPlayerMetrics = nullptr);
/**
* Instrument a deterministic agent over the same exact player-fit graph used by the route gate.
* The return value is false only when the bounded fine grid/stencil could not be built; a valid
* report may still say NotConnectedAtThisResolution or another explicit connectivity result.
*/
VOXELFORGE_API bool VF_MeasurePlayerFitWalkWithSampler(
const IVoxelStrateDensitySampler& Sampler,
int32 StrateBottomWorldZ,
int32 StrateTopWorldZ,
float BoundarySealThickness,
const FVector& AVoxel,
const FVector& BVoxel,
const FVoxelStrateMeasureSettings& Settings,
FVoxelPlayerFitWalkReport& OutReport,
const TArray<FVector>* ComponentProbePoints = nullptr);