test(explore): 81 runs in an afternoon -- every failure is a DROP, not a squeeze

First real use of the commandlet, and it paid for itself immediately: 64
sweep runs + failure renders + origin probes, which at the old 73-minutes-
per-automation-run would have been days.

⭐ FINDING 1: THE WORLD IS FRAGMENTED VERTICALLY, NOT PINCHED.
Across all 64 runs: ZERO narrow-gap events. Every failed run has separate
arrival/departure fit components with a positive gap, 12.75 m to 72.98 m.
So corridors are not too tight anywhere -- the space is severed.

And for the first time in this project we LOOKED at a failure instead of
inferring from aggregates:
  FlatPlain/42   "a broad flat floor sheet with a sharp diagonal ledge
                  descending into black open space" -- a drop, gap 63.9 m
  SurfaceWorld/7 "terrain shelves and a large black void between them.
                  There is no connecting floor" -- gap 51.8 m
  Maze/42        "a massive solid rock wall/overhang fills the separation"
                  -- gap 27.7 m
I checked the images myself. FlatPlain shows two floor LEVELS 27 m apart
in Z with nothing joining them.

⇒ The missing thing is VERTICAL CONNECTION between floor levels. Ramps,
slopes, ledges, stairs. Every archetype builds floors; nothing builds a way
between them.

⭐ FINDING 2: THE ORIGIN HUB IS NOT REACHABLE, which contradicts 413c15d.
  agent's reachable set included the origin column:  0 / 64 runs
  origin column had ANY player-fit cell:            17 / 64 runs
  Maze slot 4, all 8 seeds:                          0 fit cells, unreachable
Even in the 17, the origin cells were not in the arrival component.

The reason is structural and obvious in hindsight: the origin spine is a
VERTICAL CYLINDER. A vertical shaft has no walkable floor except at its
bottom, so a scalar-density carve that guarantees open air guarantees
nothing standable. The landings join a hub you cannot stand in.

That is a finding about the §6.5 hub join as exposed to the player-fit
layer -- not walk tuning. Recorded rather than patched.

Also from Jahni, looking at the renders -- two things numbers missed:
- The render cameras sit INSIDE ROCK, viewing tunnel surfaces from the
  solid side. Cameras must be seeded from the player-fit set.
- ⚠️ THE MAZE IS STILL A LATTICE OF STRAIGHT PATHS. The spanning tree
  changed WHICH edges exist, not what an edge LOOKS like: every corridor is
  still a straight axis-aligned capsule between cell centres, every junction
  90 deg. So 15.9% dead ends and mean degree 2.504 measured maze-ness of the
  TOPOLOGY while the eye sees a sparser grid. Sixth time a metric answered a
  question adjacent to the one that mattered.

Two tool issues found: PowerShell needs -modes="render,walk" quoted or it
parses as render-only; and "showcase slot" is ambiguous (cards use 1/6, the
Maze diagnosis uses 4).

Determinism spot-checked: repeated arguments give identical SHA-256 JSON.
Measurement-only changes; no generation, parameter or threshold edits.
Editor build succeeded.

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-07 18:33:15 +02:00
1 parent bd547c35c4
commit 6b916699af
3 files changed
+592 -4

No files matched your search

@@ -213,6 +213,16 @@ namespace VoxelStrateMeasurePrivate
MinY64 = FMath::Min(MinY64, 0.0);
MaxY64 = FMath::Max(MaxY64, 0.0);
}
if (Settings.bForceOriginColumnInCoverWindow)
{
// Upper bounds are exclusive. The ordinary origin policy can therefore stop at
// exactly X=0/Y=0 when both mouths are on one side; a diagnostic column probe
// needs one real sampled column straddling the origin instead.
MinX64 = FMath::Min(MinX64, -1.0);
MaxX64 = FMath::Max(MaxX64, 1.0);
MinY64 = FMath::Min(MinY64, -1.0);
MaxY64 = FMath::Max(MaxY64, 1.0);
}
WindowCenter = FVector2D(
static_cast<float>(0.5 * (MinX64 + MaxX64)),
static_cast<float>(0.5 * (MinY64 + MaxY64)));
@@ -2092,9 +2102,19 @@ namespace VoxelStrateMeasurePrivate
bool bGoalSnapped,
int32 NumRouteRetries,
FVoxelConnectivityDiagnostics& OutDiagnostics,
const TArray<uint8>* EligibilityMask = nullptr)
const TArray<uint8>* EligibilityMask = nullptr,
TArray<int32>* OutComponents = nullptr,
int32* OutNumComponents = nullptr)
{
OutDiagnostics = FVoxelConnectivityDiagnostics();
if (OutComponents != nullptr)
{
OutComponents->Reset();
}
if (OutNumComponents != nullptr)
{
*OutNumComponents = 0;
}
OutDiagnostics.Result = ConnectivityResult;
OutDiagnostics.bPlayerFitRestricted = EligibilityMask != nullptr;
OutDiagnostics.bPlayerFitResolved = EligibilityMask != nullptr;
@@ -2131,6 +2151,15 @@ namespace VoxelStrateMeasurePrivate
nullptr,
EligibilityMask);
if (OutComponents != nullptr)
{
*OutComponents = Components;
}
if (OutNumComponents != nullptr)
{
*OutNumComponents = NumComponents;
}
const int32 StartComponent = Components[Start];
const int32 GoalComponent = Components[Goal];
if (StartComponent < 0 || GoalComponent < 0)
@@ -2435,14 +2464,21 @@ namespace VoxelStrateMeasurePrivate
int32 Start,
int32 Goal,
float NarrowGapThresholdVoxels,
FVoxelPlayerFitWalkReport& InOutReport)
FVoxelPlayerFitWalkReport& InOutReport,
int32& OutFinalCell,
int32& OutLastReachedCell)
{
OutFinalCell = INDEX_NONE;
OutLastReachedCell = INDEX_NONE;
if (!IsEligiblePlayerFitCell(Grid, PlayerFitMask, Start)
|| !IsEligiblePlayerFitCell(Grid, PlayerFitMask, Goal))
{
return;
}
OutFinalCell = Start;
OutLastReachedCell = Start;
TArray<uint8> Visited;
Visited.Init(0u, Grid.CellCount);
TArray<uint8> NarrowCell;
@@ -2480,6 +2516,7 @@ namespace VoxelStrateMeasurePrivate
while (Stack.Num() > 0)
{
const int32 Current = Stack.Last();
OutFinalCell = Current;
if (Current == Goal)
{
InOutReport.bCanReachDeparture = true;
@@ -2501,6 +2538,7 @@ namespace VoxelStrateMeasurePrivate
RecordTraversal(Current, Neighbour);
Stack.Add(Neighbour);
NextDirection.Add(0);
OutLastReachedCell = Neighbour;
bAdvanced = true;
break;
}
@@ -2913,6 +2951,8 @@ bool VF_MeasurePlayerFitWalkWithSampler(
&PlayerFitMask);
FVoxelConnectivityDiagnostics Diagnostics;
TArray<int32> Components;
int32 NumComponents = 0;
PopulateConnectivityDiagnostics(
Grid,
Start,
@@ -2922,7 +2962,9 @@ bool VF_MeasurePlayerFitWalkWithSampler(
OutReport.bGoalSnapped,
NumRouteRetries,
Diagnostics,
&PlayerFitMask);
&PlayerFitMask,
&Components,
&NumComponents);
OutReport.bValid = true;
OutReport.Result = Result;
@@ -2943,9 +2985,16 @@ bool VF_MeasurePlayerFitWalkWithSampler(
? static_cast<float>(static_cast<double>(OutReport.ReachablePlayerFitCells)
/ static_cast<double>(OutReport.PlayerFitVolumeCells))
: 0.0f;
OutReport.PlayerFitComponents = NumComponents;
OutReport.LargestPlayerFitComponentCells = Diagnostics.LargestComponentCells;
OutReport.ArrivalComponentCells = Diagnostics.StartComponentCells;
OutReport.DepartureComponentCells = Diagnostics.GoalComponentCells;
OutReport.MouthComponentGapVoxels = Diagnostics.StartToGoalComponentDistanceCells;
OutReport.StraightLineMeters = FVector::Dist(AVoxel, BVoxel)
* FVoxelPlayerCapsuleConstants::VoxelSizeMeters;
int32 AgentFinalCell = INDEX_NONE;
int32 LastReachedCell = INDEX_NONE;
if (Start >= 0 && Goal >= 0)
{
const float NarrowGapThresholdVoxels = OutReport.NarrowGapThresholdMeters
@@ -2956,7 +3005,127 @@ bool VF_MeasurePlayerFitWalkWithSampler(
Start,
Goal,
NarrowGapThresholdVoxels,
OutReport);
OutReport,
AgentFinalCell,
LastReachedCell);
}
auto RecordCellPosition = [&Grid](
int32 Cell,
bool& bOutHasPosition,
FVector& OutPosition)
{
if (Cell >= 0 && Cell < Grid.CellCount)
{
int32 X = 0;
int32 Y = 0;
int32 Z = 0;
DecodeIndex(Grid, Cell, X, Y, Z);
bOutHasPosition = true;
OutPosition = Grid.CellCenter(X, Y, Z);
}
};
RecordCellPosition(
AgentFinalCell,
OutReport.bHasAgentFinalPosition,
OutReport.AgentFinalVoxels);
RecordCellPosition(
LastReachedCell,
OutReport.bHasLastReachedPosition,
OutReport.LastReachedVoxels);
const int32 StartComponent = Start >= 0 && Components.IsValidIndex(Start)
? Components[Start] : INDEX_NONE;
const int32 GoalComponent = Goal >= 0 && Components.IsValidIndex(Goal)
? Components[Goal] : INDEX_NONE;
if (StartComponent >= 0)
{
float BestOriginDistanceSquared = FLT_MAX;
for (int32 Cell = 0; Cell < Grid.CellCount; ++Cell)
{
if (!Components.IsValidIndex(Cell) || Components[Cell] != StartComponent)
{
continue;
}
int32 X = 0;
int32 Y = 0;
int32 Z = 0;
DecodeIndex(Grid, Cell, X, Y, Z);
const FVector Position = Grid.CellCenter(X, Y, Z);
BestOriginDistanceSquared = FMath::Min(
BestOriginDistanceSquared,
Position.X * Position.X + Position.Y * Position.Y);
}
if (BestOriginDistanceSquared < FLT_MAX)
{
OutReport.ReachableSetToOriginColumnVoxels = FMath::Sqrt(
BestOriginDistanceSquared);
}
}
const bool bOriginPointInWindow = 0.0f >= Grid.MinX && 0.0f < Grid.MaxX
&& 0.0f >= Grid.MinY && 0.0f < Grid.MaxY;
if (bOriginPointInWindow)
{
const int32 OriginX = FMath::Clamp(
FMath::FloorToInt((0.0f - Grid.MinX) / static_cast<float>(Grid.SampleStep)),
0,
Grid.NumX - 1);
const int32 OriginY = FMath::Clamp(
FMath::FloorToInt((0.0f - Grid.MinY) / static_cast<float>(Grid.SampleStep)),
0,
Grid.NumY - 1);
OutReport.bOriginColumnInSampledWindow = true;
for (int32 Z = 0; Z < Grid.NumZ; ++Z)
{
const int32 Cell = Grid.Index(OriginX, OriginY, Z);
if (PlayerFitMask[Cell] == 0u)
{
continue;
}
++OutReport.OriginColumnPlayerFitCells;
OutReport.bOriginColumnHasPlayerFit = true;
if (Components.IsValidIndex(Cell) && Components[Cell] == StartComponent)
{
OutReport.bOriginColumnReachable = true;
}
}
}
if (AgentFinalCell >= 0 && AgentFinalCell < Grid.CellCount && GoalComponent >= 0)
{
const FVector AgentPosition = OutReport.AgentFinalVoxels;
float BestTargetDistanceSquared = FLT_MAX;
int32 BestTargetCell = INDEX_NONE;
for (int32 Cell = 0; Cell < Grid.CellCount; ++Cell)
{
if (!Components.IsValidIndex(Cell) || Components[Cell] != GoalComponent)
{
continue;
}
int32 X = 0;
int32 Y = 0;
int32 Z = 0;
DecodeIndex(Grid, Cell, X, Y, Z);
const float DistanceSquared = FVector::DistSquared(
AgentPosition, Grid.CellCenter(X, Y, Z));
if (DistanceSquared < BestTargetDistanceSquared
|| (DistanceSquared == BestTargetDistanceSquared
&& (BestTargetCell == INDEX_NONE || Cell < BestTargetCell)))
{
BestTargetDistanceSquared = DistanceSquared;
BestTargetCell = Cell;
}
}
if (BestTargetCell >= 0)
{
RecordCellPosition(
BestTargetCell,
OutReport.bHasTargetComponentNearestCell,
OutReport.TargetComponentNearestVoxels);
OutReport.AgentToTargetComponentGapVoxels = FMath::Sqrt(
BestTargetDistanceSquared);
}
}
OutReport.bCanReachDeparture = Result == EVoxelConnectivityResult::Connected;
@@ -73,6 +73,10 @@ struct VOXELFORGE_API FVoxelStrateMeasureSettings
// 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.
@@ -308,6 +312,34 @@ struct VOXELFORGE_API FVoxelPlayerFitWalkReport
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;
// 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;
};
/**
@@ -165,6 +165,7 @@ struct FExploreArguments
bool bRender = true;
bool bWalk = true;
bool bExport = true;
bool bFailureFocusRender = false;
FString OutDirectory;
int32 RenderWidth = DefaultRenderWidth;
@@ -205,6 +206,9 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
FParse::Value(*Params, TEXT("rendermaxdistance="), OutArguments.RenderMaxDistanceVoxels);
FParse::Value(*Params, TEXT("exportsize="), OutArguments.ExportSize);
FParse::Value(*Params, TEXT("maxwalkcells="), OutArguments.MaxWalkCells);
int32 FailureFocusRender = 0;
FParse::Value(*Params, TEXT("failurefocus="), FailureFocusRender);
OutArguments.bFailureFocusRender = FailureFocusRender != 0;
if (!ParseArchetype(ArchetypeText, OutArguments.Archetype))
{
@@ -467,10 +471,12 @@ private:
struct FExploreRenderFrame
{
FString FileName;
FString Purpose = TEXT("overview");
FVector CameraVoxels = FVector::ZeroVector;
FVector TargetVoxels = FVector::ZeroVector;
int32 HitPixels = 0;
bool bScaleMarkerProjected = false;
bool bFailureMarkersProjected = false;
};
struct FExploreRenderOutput
@@ -500,6 +506,10 @@ struct FExploreWalkOutput
FVector DepartureVoxels = FVector::ZeroVector;
FString WindowPolicy;
FVoxelPlayerFitWalkReport Report;
bool bOriginCheckAttempted = false;
bool bOriginCheckAvailable = false;
FString OriginCheckRefusalReason;
FVoxelPlayerFitWalkReport OriginCheckReport;
};
struct FExploreExportOutput
@@ -990,6 +1000,257 @@ bool RunRender(
return true;
}
void DrawPointMarker(
TArray<FColor>& Pixels,
int32 Width,
int32 Height,
const FVector& Point,
const FVector& CameraPosition,
const FExploreCameraBasis& Basis,
const FColor& Color,
bool& bOutProjected)
{
FIntPoint Pixel = FIntPoint::ZeroValue;
bOutProjected = ProjectPoint(
Point, CameraPosition, Basis, Width, Height, Pixel);
if (!bOutProjected)
{
return;
}
constexpr int32 MarkerRadius = 7;
DrawLine(
Pixels, Width, Height,
Pixel.X - MarkerRadius, Pixel.Y,
Pixel.X + MarkerRadius, Pixel.Y,
Color);
DrawLine(
Pixels, Width, Height,
Pixel.X, Pixel.Y - MarkerRadius,
Pixel.X, Pixel.Y + MarkerRadius,
Color);
DrawLine(
Pixels, Width, Height,
Pixel.X - 4, Pixel.Y - 4,
Pixel.X + 4, Pixel.Y + 4,
Color);
DrawLine(
Pixels, Width, Height,
Pixel.X - 4, Pixel.Y + 4,
Pixel.X + 4, Pixel.Y - 4,
Color);
}
bool RunFailureBoundaryRender(
const FExploreArguments& Arguments,
const FExploreWorld& World,
const FExploreWalkOutput& Walk,
FExploreRenderOutput& InOutOutput)
{
const FVoxelPlayerFitWalkReport& Report = Walk.Report;
if (!Report.bHasAgentFinalPosition || !Report.bHasTargetComponentNearestCell)
{
return false;
}
const FVector AgentFinal = Report.AgentFinalVoxels;
const FVector TargetComponentCell = Report.TargetComponentNearestVoxels;
const FVector LastReached = Report.LastReachedVoxels;
const bool bHaveLastReached = Report.bHasLastReachedPosition;
const FVector Focus = (AgentFinal + TargetComponentCell) * 0.5f;
const FVector Segment = TargetComponentCell - AgentFinal;
const FVector SegmentDirection = Segment.GetSafeNormal();
if (SegmentDirection.IsNearlyZero())
{
return false;
}
FVector ViewSide = FVector::CrossProduct(SegmentDirection, FVector::UpVector).GetSafeNormal();
if (ViewSide.IsNearlyZero())
{
ViewSide = FVector::CrossProduct(SegmentDirection, FVector::RightVector).GetSafeNormal();
}
const float SegmentLength = Segment.Size();
const float ViewSideOffset = FMath::Min(64.0f, FMath::Max(16.0f, 0.25f * SegmentLength));
const FVector CameraCandidates[][3] = {
{
AgentFinal - SegmentDirection * 4.0f,
bHaveLastReached ? LastReached : AgentFinal,
AgentFinal,
},
{
TargetComponentCell + SegmentDirection * 4.0f,
TargetComponentCell,
TargetComponentCell,
},
};
const FVector ViewTargets[] = {
Focus + ViewSide * ViewSideOffset,
Focus - ViewSide * ViewSideOffset,
};
for (int32 ViewIndex = 0; ViewIndex < UE_ARRAY_COUNT(ViewTargets); ++ViewIndex)
{
FVector CameraPosition = CameraCandidates[ViewIndex][2];
for (int32 CandidateIndex = 0; CandidateIndex < 3; ++CandidateIndex)
{
const FVector Candidate = CameraCandidates[ViewIndex][CandidateIndex];
const float Density = World.Generator->GetDensityAt(Candidate.X, Candidate.Y, Candidate.Z);
if (FMath::IsFinite(Density) && Density > 0.0f)
{
CameraPosition = Candidate;
break;
}
}
const float FailureMaxDistanceVoxels = FMath::Max(
Arguments.RenderMaxDistanceVoxels,
FMath::Max(
FVector::Dist(CameraPosition, AgentFinal),
FVector::Dist(CameraPosition, TargetComponentCell)) + 32.0f);
FExploreCameraBasis Basis;
if (!BuildCameraBasis(
CameraPosition,
ViewTargets[ViewIndex],
70.0f,
Arguments.RenderWidth,
Arguments.RenderHeight,
Basis))
{
return false;
}
TArray<FColor> Pixels;
Pixels.Init(FColor(7, 11, 18, 255), Arguments.RenderWidth * Arguments.RenderHeight);
int32 HitPixels = 0;
const FVector LightDirection = FVector(-0.35f, -0.45f, 0.82f).GetSafeNormal();
for (int32 PixelY = 0; PixelY < Arguments.RenderHeight; ++PixelY)
{
for (int32 PixelX = 0; PixelX < Arguments.RenderWidth; ++PixelX)
{
const FVector RayDirection = MakeRayDirection(
Basis,
PixelX,
PixelY,
Arguments.RenderWidth,
Arguments.RenderHeight);
FVector HitPoint;
FVector Normal;
if (!TraceDensityRay(
*World.Generator,
CameraPosition,
RayDirection,
Arguments.RenderStepVoxels,
FailureMaxDistanceVoxels,
InOutOutput.BisectionIterations,
HitPoint,
Normal))
{
continue;
}
++HitPixels;
const float Diffuse = FMath::Max(0.0f, FVector::DotProduct(Normal, LightDirection));
const float Brightness = 0.18f + 0.82f * Diffuse;
const uint8 RockR = static_cast<uint8>(FMath::Clamp(54.0f * Brightness, 0.0f, 255.0f));
const uint8 RockG = static_cast<uint8>(FMath::Clamp(72.0f * Brightness, 0.0f, 255.0f));
const uint8 RockB = static_cast<uint8>(FMath::Clamp(92.0f * Brightness, 0.0f, 255.0f));
PutPixel(
Pixels,
Arguments.RenderWidth,
Arguments.RenderHeight,
PixelX,
PixelY,
FColor(RockR, RockG, RockB, 255));
}
}
bool bAgentProjected = false;
bool bTargetProjected = false;
bool bLastReachedProjected = false;
DrawPointMarker(
Pixels,
Arguments.RenderWidth,
Arguments.RenderHeight,
AgentFinal,
CameraPosition,
Basis,
FColor(242, 72, 72, 255),
bAgentProjected);
DrawPointMarker(
Pixels,
Arguments.RenderWidth,
Arguments.RenderHeight,
TargetComponentCell,
CameraPosition,
Basis,
FColor(72, 242, 112, 255),
bTargetProjected);
if (bHaveLastReached)
{
DrawPointMarker(
Pixels,
Arguments.RenderWidth,
Arguments.RenderHeight,
LastReached,
CameraPosition,
Basis,
FColor(242, 176, 56, 255),
bLastReachedProjected);
}
FIntPoint AgentPixel = FIntPoint::ZeroValue;
FIntPoint TargetPixel = FIntPoint::ZeroValue;
if (ProjectPoint(
AgentFinal,
CameraPosition,
Basis,
Arguments.RenderWidth,
Arguments.RenderHeight,
AgentPixel)
&& ProjectPoint(
TargetComponentCell,
CameraPosition,
Basis,
Arguments.RenderWidth,
Arguments.RenderHeight,
TargetPixel))
{
DrawLine(
Pixels,
Arguments.RenderWidth,
Arguments.RenderHeight,
AgentPixel.X,
AgentPixel.Y,
TargetPixel.X,
TargetPixel.Y,
FColor(244, 214, 72, 255));
}
FExploreRenderFrame& Frame = InOutOutput.Frames.AddDefaulted_GetRef();
Frame.FileName = FString::Printf(TEXT("failure_boundary_%02d.png"), ViewIndex);
Frame.Purpose = TEXT("agent_final_red_target_component_nearest_green_last_reached_orange");
Frame.CameraVoxels = CameraPosition;
Frame.TargetVoxels = ViewTargets[ViewIndex];
Frame.HitPixels = HitPixels;
Frame.bFailureMarkersProjected = bAgentProjected && bTargetProjected
&& (!bHaveLastReached || bLastReachedProjected);
FString Error;
if (!SavePng(
FPaths::Combine(Arguments.OutDirectory, Frame.FileName),
Pixels,
Arguments.RenderWidth,
Arguments.RenderHeight,
Error))
{
InOutOutput.Status = TEXT("error");
InOutOutput.RefusalReason = Error;
return false;
}
}
return true;
}
bool RunWalk(
const FExploreArguments& Arguments,
const FExploreWorld& World,
@@ -1054,6 +1315,30 @@ bool RunWalk(
return false;
}
// Part C is a separate observation window. Keep the Part A mouth-sized result untouched,
// then repeat the same exact fit walk with the route window expanded to include (0,0). This
// is measurement-only: it changes neither the density field nor any generation parameter.
OutOutput.bOriginCheckAttempted = true;
FVoxelStrateMeasureSettings OriginSettings = Settings;
OriginSettings.bIncludeOriginInCoverWindow = true;
OriginSettings.bForceOriginColumnInCoverWindow = true;
if (VF_MeasurePlayerFitWalkWithSampler(
Sampler,
World.TargetBottomWorldZ,
World.TargetTopWorldZ,
World.TargetBoundarySealThickness,
OutOutput.ArrivalVoxels,
OutOutput.DepartureVoxels,
OriginSettings,
OutOutput.OriginCheckReport))
{
OutOutput.bOriginCheckAvailable = true;
}
else
{
OutOutput.OriginCheckRefusalReason = OutOutput.OriginCheckReport.RefusalReason;
}
OutOutput.Status = TEXT("ok");
return true;
}
@@ -1431,6 +1716,7 @@ FString BuildExploreJson(
if (Arguments.bWalk) Writer->WriteValue(TEXT("walk"));
if (Arguments.bExport) Writer->WriteValue(TEXT("export"));
Writer->WriteArrayEnd();
Writer->WriteValue(TEXT("failure_focus_render"), Arguments.bFailureFocusRender);
Writer->WriteValue(TEXT("out_directory"), Arguments.OutDirectory);
Writer->WriteValue(TEXT("canonical_invocation"), FString::Printf(
TEXT("UnrealEditor-Cmd VoxelM.uproject -run=VoxelForgeExplore -seed=%d -archetype=%s "
@@ -1477,11 +1763,13 @@ FString BuildExploreJson(
for (const FExploreRenderFrame& Frame : Output.Render.Frames)
{
Writer->WriteObjectStart();
Writer->WriteValue(TEXT("purpose"), Frame.Purpose);
Writer->WriteValue(TEXT("file"), Frame.FileName);
WriteJsonVector(*Writer, TEXT("camera_voxels"), Frame.CameraVoxels, 1.0f);
WriteJsonVector(*Writer, TEXT("target_voxels"), Frame.TargetVoxels, 1.0f);
Writer->WriteValue(TEXT("hit_pixels"), Frame.HitPixels);
Writer->WriteValue(TEXT("scale_marker_projected"), Frame.bScaleMarkerProjected);
Writer->WriteValue(TEXT("failure_markers_projected"), Frame.bFailureMarkersProjected);
Writer->WriteValue(TEXT("scale_marker_height_m"), 1.76);
Writer->WriteObjectEnd();
}
@@ -1550,6 +1838,90 @@ FString BuildExploreJson(
Writer->WriteValue(TEXT("narrow_gap_fraction"), static_cast<double>(Report.NarrowGapFraction));
Writer->WriteValue(TEXT("narrow_gap_events_per_100m"), static_cast<double>(Report.NarrowGapEventsPer100m));
Writer->WriteValue(TEXT("narrow_gap_definition"), Report.NarrowGapDefinition);
Writer->WriteValue(TEXT("player_fit_components"), Report.PlayerFitComponents);
Writer->WriteValue(TEXT("largest_player_fit_component_cells"), Report.LargestPlayerFitComponentCells);
Writer->WriteValue(TEXT("arrival_component_cells"), Report.ArrivalComponentCells);
Writer->WriteValue(TEXT("departure_component_cells"), Report.DepartureComponentCells);
Writer->WriteValue(TEXT("mouth_component_gap_voxels"), static_cast<double>(Report.MouthComponentGapVoxels));
Writer->WriteValue(TEXT("mouth_component_gap_m"), static_cast<double>(Report.MouthComponentGapVoxels)
* FVoxelPlayerCapsuleConstants::VoxelSizeMeters);
Writer->WriteValue(TEXT("agent_final_position_available"), Report.bHasAgentFinalPosition);
if (Report.bHasAgentFinalPosition)
{
WriteJsonVector(*Writer, TEXT("agent_final_position_voxels"), Report.AgentFinalVoxels, 1.0f);
WriteJsonVector(*Writer, TEXT("agent_final_position_metres"), Report.AgentFinalVoxels, 0.25f);
}
Writer->WriteValue(TEXT("last_reached_position_available"), Report.bHasLastReachedPosition);
if (Report.bHasLastReachedPosition)
{
WriteJsonVector(*Writer, TEXT("last_reached_position_voxels"), Report.LastReachedVoxels, 1.0f);
WriteJsonVector(*Writer, TEXT("last_reached_position_metres"), Report.LastReachedVoxels, 0.25f);
}
Writer->WriteValue(TEXT("target_component_nearest_cell_available"), Report.bHasTargetComponentNearestCell);
if (Report.bHasTargetComponentNearestCell)
{
WriteJsonVector(*Writer, TEXT("target_component_nearest_cell_voxels"),
Report.TargetComponentNearestVoxels, 1.0f);
WriteJsonVector(*Writer, TEXT("target_component_nearest_cell_metres"),
Report.TargetComponentNearestVoxels, 0.25f);
}
Writer->WriteValue(TEXT("agent_to_target_component_gap_voxels"),
static_cast<double>(Report.AgentToTargetComponentGapVoxels));
Writer->WriteValue(TEXT("agent_to_target_component_gap_m"),
static_cast<double>(Report.AgentToTargetComponentGapVoxels)
* FVoxelPlayerCapsuleConstants::VoxelSizeMeters);
Writer->WriteValue(TEXT("origin_column_in_sampled_window"), Report.bOriginColumnInSampledWindow);
Writer->WriteValue(TEXT("origin_column_has_player_fit"), Report.bOriginColumnHasPlayerFit);
Writer->WriteValue(TEXT("origin_column_player_fit_cells"), Report.OriginColumnPlayerFitCells);
Writer->WriteValue(TEXT("origin_column_reachable_from_arrival"), Report.bOriginColumnReachable);
Writer->WriteValue(TEXT("reachable_set_to_origin_column_voxels"),
static_cast<double>(Report.ReachableSetToOriginColumnVoxels));
Writer->WriteValue(TEXT("reachable_set_to_origin_column_m"),
static_cast<double>(Report.ReachableSetToOriginColumnVoxels)
* FVoxelPlayerCapsuleConstants::VoxelSizeMeters);
Writer->WriteObjectStart(TEXT("origin_check"));
Writer->WriteValue(TEXT("attempted"), Output.Walk.bOriginCheckAttempted);
Writer->WriteValue(TEXT("available"), Output.Walk.bOriginCheckAvailable);
Writer->WriteValue(TEXT("refusal_or_error"), Output.Walk.OriginCheckRefusalReason);
if (Output.Walk.bOriginCheckAvailable)
{
const FVoxelPlayerFitWalkReport& OriginReport = Output.Walk.OriginCheckReport;
Writer->WriteValue(TEXT("window_policy"), TEXT("origin-inclusive mouth-AABB + margin"));
Writer->WriteValue(TEXT("connectivity_result"), ConnectivityResultName(OriginReport.Result));
Writer->WriteValue(TEXT("can_reach_departure"), OriginReport.bCanReachDeparture);
Writer->WriteValue(TEXT("sampled_num_x"), OriginReport.SampledNumX);
Writer->WriteValue(TEXT("sampled_num_y"), OriginReport.SampledNumY);
Writer->WriteValue(TEXT("sampled_num_z"), OriginReport.SampledNumZ);
Writer->WriteValue(TEXT("sampled_min_x"), static_cast<double>(OriginReport.SampledMinX));
Writer->WriteValue(TEXT("sampled_max_x_exclusive"), static_cast<double>(OriginReport.SampledMaxX));
Writer->WriteValue(TEXT("sampled_min_y"), static_cast<double>(OriginReport.SampledMinY));
Writer->WriteValue(TEXT("sampled_max_y_exclusive"), static_cast<double>(OriginReport.SampledMaxY));
Writer->WriteValue(TEXT("sampled_min_z"), OriginReport.SampledMinZ);
Writer->WriteValue(TEXT("sampled_max_z_exclusive"), OriginReport.SampledMaxZ);
Writer->WriteValue(TEXT("player_fit_cells"), OriginReport.PlayerFitVolumeCells);
Writer->WriteValue(TEXT("reachable_player_fit_cells"), OriginReport.ReachablePlayerFitCells);
Writer->WriteValue(TEXT("reachable_player_fit_fraction"),
static_cast<double>(OriginReport.ReachablePlayerFitFraction));
Writer->WriteValue(TEXT("player_fit_components"), OriginReport.PlayerFitComponents);
Writer->WriteValue(TEXT("arrival_component_cells"), OriginReport.ArrivalComponentCells);
Writer->WriteValue(TEXT("departure_component_cells"), OriginReport.DepartureComponentCells);
Writer->WriteValue(TEXT("mouth_component_gap_voxels"),
static_cast<double>(OriginReport.MouthComponentGapVoxels));
Writer->WriteValue(TEXT("origin_column_in_sampled_window"),
OriginReport.bOriginColumnInSampledWindow);
Writer->WriteValue(TEXT("origin_column_has_player_fit"),
OriginReport.bOriginColumnHasPlayerFit);
Writer->WriteValue(TEXT("origin_column_player_fit_cells"),
OriginReport.OriginColumnPlayerFitCells);
Writer->WriteValue(TEXT("origin_column_reachable_from_arrival"),
OriginReport.bOriginColumnReachable);
Writer->WriteValue(TEXT("reachable_set_to_origin_column_voxels"),
static_cast<double>(OriginReport.ReachableSetToOriginColumnVoxels));
Writer->WriteValue(TEXT("reachable_set_to_origin_column_m"),
static_cast<double>(OriginReport.ReachableSetToOriginColumnVoxels)
* FVoxelPlayerCapsuleConstants::VoxelSizeMeters);
}
Writer->WriteObjectEnd();
Writer->WriteObjectEnd();
}
@@ -1652,6 +2024,21 @@ int32 UVoxelForgeExploreCommandlet::Main(const FString& Params)
UE_LOG(LogTemp, Display, TEXT("[VoxelForgeExplore] walk %.3fs (%s)"),
FPlatformTime::Seconds() - Start, *Output.Walk.Status);
}
if (Arguments.bFailureFocusRender && Arguments.bRender && Arguments.bWalk)
{
const double Start = FPlatformTime::Seconds();
if (!RunFailureBoundaryRender(Arguments, World, Output.Walk, Output.Render))
{
bRequestedModeFailed = true;
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeExplore] failure-focus render could not produce a boundary image."));
}
else
{
UE_LOG(LogTemp, Display, TEXT("[VoxelForgeExplore] failure-focus render %.3fs (ok)"),
FPlatformTime::Seconds() - Start);
}
}
if (Arguments.bExport)
{
const double Start = FPlatformTime::Seconds();