test(measure): the window was NOT the problem -- my sixth hypothesis dies

I thought the fitted mouth-AABB window was cropping an origin-routed path
and producing a false NotConnected. Codex tested it three ways at step 1 on
the Maze candidate Jahni walked:

  mouth-AABB + margin      842,996 cells    NotConnected
  origin-inclusive       3,251,556 cells    NotConnected
  WHOLE STRATE          31,881,528 cells    NotConnected

115,578 fit cells, 2,903 components, largest 11,418 -- and the mouths sit
in components of 172 and FOUR cells, 274 voxels apart. The window was never
the problem. Sixth measurement hypothesis, first one that was wrong in the
other direction: this time the instrument was fine and I doubted it.

The window rule still improved: bIncludeOriginInCoverWindow now covers the
route an origin-rooted topology actually implies (Maze, VerticalShafts,
TunnelNetwork, Underwater), expansion happens BEFORE MaxCells, and an
over-cap window is REFUSED honestly instead of silently cropping back to a
mouth-only box. VerticalShafts refused at the 4M production cap and said so.

Corrected law across eight: 0/7 measured Connected, 1 unknown
(CoarseLiedBudgetExhausted), 1 refused.

⇒ FIVE CAUSES NOW ELIMINATED: size (Jahni walked it), the fit stencil
(fixed, 309 pockets -> 96), placement (mouths land on legal poses),
corridor connectivity (spanning tree, 0 disconnected cells over 64 seeds),
and the measurement window (whole-strate says the same thing).

⭐ AND THE REMAINING CAUSE IS NAMED BY ITS OWN NUMBER: the departure mouth
sits in a FOUR-CELL player-fit component. A passage tube is a bore through
rock -- sloped or vertical, no floor within the walkable angle -- so the
mouth point is not a standing position and nothing around it is either.
You arrive and there is nowhere to stand.

That is precisely COMPOSER-NOTES §6.5 / Tier 1's unbuilt item: "the passage
mouth becomes a room plus a distinctive landmark". It was never started.
The law cannot pass while a mouth opens into a bore instead of a landing.

Build clean, 32/32 successful, 0 failed, same two known warnings.
WorldRadiusVoxels=0, lateral gating, determinism, equivalence all intact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8V9my1PL68pmQb2gutZZV
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-09-06 12:58:56 +02:00
1 parent 2eaceb5984
commit e4e159f0cd
7 files changed
+444 -39

No files matched your search

@@ -13,6 +13,7 @@
#include "VoxelStrateComposer.h"
#include "VoxelStrateMeasure.h"
#include "VoxelStratePreview.h"
#include "VoxelForgePlayerFitWindow.h"
#include "VoxelTypes.h"
namespace
@@ -113,6 +114,7 @@ namespace
FVoxelStrateMetrics FineMetrics;
FVoxelConnectivityDiagnostics LegacyLaw;
FVoxelConnectivityDiagnostics PlayerFitLaw;
FString FineWindowSummary;
double SelectionScore = -1.0;
FString LawText;
int32 TargetStrateIndex = INDEX_NONE;
@@ -247,22 +249,15 @@ namespace
const FVoxelStrateMeasureSettings& BaseSettings,
const FVoxelStrateFinePreviewSettings& FinePreviewSettings,
const FVector& ArrivalPoint,
const FVector& DeparturePoint)
const FVector& DeparturePoint,
ECaveGeneratorType Archetype)
{
FVoxelStrateMeasureSettings Settings =
FinePreviewSettings.MakeMeasureSettings(BaseSettings);
// The old fine preview was centred on the coarse largest-component representative. That
// is useful for a picture, but it can put both mouths outside the ROI and turn a real
// player-fit route into a meaningless OutOfWindow result. Fit the fine ROI to the two
// existing mouth XY points instead; the small explicit margin leaves room for the
// capsule's horizontal stencil at either endpoint without changing placement.
Settings.CenterXY = FVector2D(
0.5f * (ArrivalPoint.X + DeparturePoint.X),
0.5f * (ArrivalPoint.Y + DeparturePoint.Y));
Settings.CoverPointA = FVector2D(ArrivalPoint.X, ArrivalPoint.Y);
Settings.CoverPointB = FVector2D(DeparturePoint.X, DeparturePoint.Y);
Settings.CoverMarginVoxels = FMath::CeilToFloat(
Settings.PlayerCapsuleRadiusVoxels) + 2.0f;
// The fine preview follows the measured route contract. Local topologies use the two
// mouths plus a capsule margin; origin-rooted topologies also cover the (0,0) spine.
VoxelForgePlayerFitWindow::ConfigureMouthWindow(
Settings, ArrivalPoint, DeparturePoint, Archetype);
return Settings;
}
@@ -366,7 +361,8 @@ namespace
FinePreviewSettings.MaxCells = FinePreviewMaxCells;
FVoxelStrateMeasureSettings FineMeasureSettings =
VF_MakeFinePlayerFitSettings(
MeasureSettings, FinePreviewSettings, ArrivalPoint, DeparturePoint);
MeasureSettings, FinePreviewSettings, ArrivalPoint, DeparturePoint,
Candidate.Archetype);
FVoxelStrateMetrics FineMetrics;
const FVoxelConnectivityDiagnostics PlayerFitLaw =
VF_DiagnosePlayerFitConnectivityWithSampler(
@@ -374,6 +370,8 @@ namespace
ArrivalPoint, DeparturePoint, FineMeasureSettings, &FineMetrics);
OutSample.FineMetrics = FineMetrics;
OutSample.PlayerFitLaw = PlayerFitLaw;
OutSample.FineWindowSummary = VoxelForgePlayerFitWindow::DescribeResolvedWindow(
FineMetrics, FineMeasureSettings.SampleStep, Candidate.Archetype);
OutSample.LawText = FString::Printf(
TEXT("legacy=%s%s; player_fit=%s%s"),
VF_ShowcaseConnectivityName(Law.Result),
@@ -948,7 +946,8 @@ namespace
FString Report = FString::Printf(
TEXT("%s (player-fit fields are fitted SampleStep=1; walkable floor area and "
"median vertical clearance are coarse SampleStep=4 proxies)\n"
"archetype | player_fit_fraction_step1 | traversable_share_step1 | "
"archetype | player_fit_window | player_fit_fraction_step1 | "
"traversable_share_step1 | "
"fit_cells_step1 | components_step1 | largest_component_cells_step1 | "
"arrival_component_cells_step1 | departure_component_cells_step1 | "
"mouth_component_gap_voxels_step1 | restricted_law_step1 | "
@@ -965,6 +964,7 @@ namespace
{
Report += FString::Printf(
TEXT("%s | no measured sample | no measured sample | no measured sample | "
"no measured sample | "
"no measured sample | no measured sample | no measured sample | "
"no measured sample | no measured sample | no measured sample | "
"n/a | n/a | n/a\n"),
@@ -979,9 +979,10 @@ namespace
const float FloorAreaM2 = Found->Metrics.WalkableFloorAreaFraction
* 128.0f * 128.0f;
Report += FString::Printf(
TEXT("%s | %.6f | %.6f | %lld | %d | %lld | %lld | %lld | %.3f | %s | "
TEXT("%s | %s | %.6f | %.6f | %lld | %d | %lld | %lld | %lld | %.3f | %s | "
"%.1f (%.6f footprint) | %.2f | %.3f\n"),
VF_GetStrateArchetypeName(Archetype),
*Found->FineWindowSummary,
M.bPlayerFitResolved ? M.PlayerFitFraction : 0.0f,
M.bPlayerFitResolved ? M.TraversableComponentShare : 0.0f,
static_cast<long long>(M.NumPlayerFitCells),
@@ -1042,7 +1043,8 @@ namespace
Stack.PrepareChunk(Context);
FShowcaseStackSampler Sampler(Stack);
const FVoxelStrateMeasureSettings FineSettings = VF_MakeFinePlayerFitSettings(
BaseSettings, FinePreviewSettings, ArrivalPoint, DeparturePoint);
BaseSettings, FinePreviewSettings, ArrivalPoint, DeparturePoint,
MazeSample.Candidate.Archetype);
FVoxelStrateMetrics ZeroMetrics;
const FVoxelConnectivityDiagnostics ZeroLaw =
VF_DiagnosePlayerFitConnectivityWithSampler(
@@ -1096,7 +1098,8 @@ namespace
}
const FVoxelStrateMeasureSettings FineSettings = VF_MakeFinePlayerFitSettings(
BaseSettings, FinePreviewSettings, ArrivalPoint, DeparturePoint);
BaseSettings, FinePreviewSettings, ArrivalPoint, DeparturePoint,
MazeSample.Candidate.Archetype);
const float RadiusSweep[] = { 6.0f, 8.0f, 10.0f, 12.0f, 12.5f };
OutReport = TEXT(
"Part A Maze corridor-radius sweep (same seed/index, fitted step-1 ROI; "
@@ -1889,12 +1892,13 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
}
const FString FineSummary = Sample.FineMetrics.bPlayerFitResolved
? FString::Printf(
TEXT("fit_step1=%.6f traversable_step1=%.6f fit_cells_step1=%lld "
TEXT("window=%s; fit_step1=%.6f traversable_step1=%.6f fit_cells_step1=%lld "
"components_step1=%d largest_component_cells_step1=%lld "
"arrival_component_cells_step1=%lld departure_component_cells_step1=%lld "
"mouth_component_gap_step1=%.3f min_player_clearance_step1=%.2f "
"restricted_law_step1=%s; coarse_step4_walkable=%.6f "
"coarse_step4_clearance=%d voxels"),
*Sample.FineWindowSummary,
Sample.FineMetrics.PlayerFitFraction,
Sample.FineMetrics.TraversableComponentShare,
static_cast<long long>(Sample.FineMetrics.NumPlayerFitCells),
@@ -1908,8 +1912,9 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
Sample.Metrics.WalkableFraction,
Sample.Metrics.MedianVerticalClearance)
: FString::Printf(
TEXT("fit_step1=UNRESOLVED reason=%s; coarse_step4_walkable=%.6f "
TEXT("window=%s; fit_step1=UNRESOLVED reason=%s; coarse_step4_walkable=%.6f "
"coarse_step4_clearance=%d voxels"),
*Sample.FineWindowSummary,
Sample.FineMetrics.PlayerFitRefusalReason.IsEmpty()
? TEXT("not measured") : *Sample.FineMetrics.PlayerFitRefusalReason,
Sample.Metrics.WalkableFraction,
@@ -2114,7 +2119,8 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
World, Selected.TargetStrateIndex, FineArrivalPoint, FineDeparturePoint));
const FVoxelStrateMeasureSettings FineMeasureSettings =
VF_MakeFinePlayerFitSettings(
MeasureSettings, FinePreviewSettings, FineArrivalPoint, FineDeparturePoint);
MeasureSettings, FinePreviewSettings, FineArrivalPoint, FineDeparturePoint,
Candidate.Archetype);
FVoxelStrateSampleGrid FineGrid;
const FVoxelStrateMetrics FineMetrics = VF_MeasureStrateWithSampler(
Sampler, Selected.BottomVoxelZ, Selected.TopVoxelZ + 1, Context.EdgeSealThickness,
@@ -2224,8 +2230,10 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
{
const FShowcaseSample& Selected = PreviewSelections[ArchetypeIndex];
Summaries[ArchetypeIndex].Window = PreviewCandidates[ArchetypeIndex].Window;
Summaries[ArchetypeIndex].WindowSummary = VF_FormatShowcaseSummaryWindow(
Stats[ArchetypeIndex]);
Summaries[ArchetypeIndex].WindowSummary = FString::Printf(
TEXT("coarse_window=%s; player_fit_window=%s"),
*VF_FormatShowcaseSummaryWindow(Stats[ArchetypeIndex]),
*Selected.FineWindowSummary);
Summaries[ArchetypeIndex].PlayerFitFractionSummary = FString::Printf(
TEXT("%.6f (step=1)"), Selected.FineMetrics.PlayerFitFraction);
Summaries[ArchetypeIndex].TraversableComponentShareSummary = FString::Printf(
@@ -2239,7 +2247,7 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
"components_step1=%d, largest_component_cells_step1=%lld, "
"arrival_component_cells_step1=%lld, departure_component_cells_step1=%lld, "
"mouth_component_gap_voxels_step1=%.3f, player_clearance_step1=%.2f voxels, "
"restricted_law_step1=%s, window=%s"),
"restricted_law_step1=%s, coarse_window=%s, player_fit_window=%s"),
VF_GetStrateArchetypeName(GShowcaseArchetypes[ArchetypeIndex]),
Stats[ArchetypeIndex].Survivors.Num(),
Selected.Candidate.Seed, Selected.Candidate.Index,
@@ -2258,7 +2266,8 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
Selected.PlayerFitLaw.StartToGoalComponentDistanceCells,
Selected.FineMetrics.MinimumPlayerClearanceVoxels,
VF_ShowcaseConnectivityName(Selected.PlayerFitLaw.Result),
*PreviewCandidates[ArchetypeIndex].Window.Describe()));
*PreviewCandidates[ArchetypeIndex].Window.Describe(),
*Selected.FineWindowSummary));
}
FString IndexPath;
@@ -0,0 +1,101 @@
// Shared measurement-window policy for player-fit diagnostics.
//
// This is deliberately test/editor-side policy. The density generators do not know which two
// points a measurement is asking about, so the caller that knows the archetype must declare
// whether its topology can require the origin spine.
#pragma once
#if WITH_DEV_AUTOMATION_TESTS
#include "CoreMinimal.h"
#include "VoxelStrateMeasure.h"
#include "VoxelStrateTypes.h"
namespace VoxelForgePlayerFitWindow
{
/**
* The four families below have an origin-rooted connectivity implication:
*
* - Maze: every lattice parent chain terminates at (0,0,0).
* - VerticalShafts: every drainage-tree parent chain terminates at the origin spine.
* - TunnelNetwork/Underwater: their origin-flowing room graph terminates at the origin hub.
*
* Slabs, SurfaceWorld, and FloatingIslands have local/independent interior topology for
* this law. The global structural spine is still generated for them, but it is not the
* topology that their mouth-to-mouth fit law promises to follow.
*/
inline bool NeedsOriginInWindow(ECaveGeneratorType Archetype)
{
switch (Archetype)
{
case ECaveGeneratorType::Maze:
case ECaveGeneratorType::VerticalShafts:
case ECaveGeneratorType::TunnelNetwork:
case ECaveGeneratorType::Underwater:
return true;
case ECaveGeneratorType::FlatPlain:
case ECaveGeneratorType::CrystalChamber:
case ECaveGeneratorType::SurfaceWorld:
case ECaveGeneratorType::FloatingIslands:
default:
return false;
}
}
inline const TCHAR* RouteWindowName(ECaveGeneratorType Archetype)
{
return NeedsOriginInWindow(Archetype)
? TEXT("origin-inclusive mouth-AABB + margin")
: TEXT("mouth-AABB + margin");
}
inline FString DescribeResolvedWindow(
const FVoxelStrateMetrics& Metrics,
int32 SampleStep,
ECaveGeneratorType Archetype)
{
if (!Metrics.bValid)
{
return FString::Printf(
TEXT("%s step=%d REFUSED: %s"),
RouteWindowName(Archetype),
SampleStep,
Metrics.RefusalReason.IsEmpty() ? TEXT("unknown") : *Metrics.RefusalReason);
}
return FString::Printf(
TEXT("%s step=%d X[%.1f,%.1f) Y[%.1f,%.1f) Z[%d,%d) "
"grid=%dx%dx%d cells=%lld"),
RouteWindowName(Archetype),
SampleStep,
Metrics.SampledMinX,
Metrics.SampledMaxX,
Metrics.SampledMinY,
Metrics.SampledMaxY,
Metrics.SampledMinZ,
Metrics.SampledMaxZ,
Metrics.SampledNumX,
Metrics.SampledNumY,
Metrics.SampledNumZ,
static_cast<long long>(Metrics.NumSampled));
}
/** Configure the exact fitted XY window used by the player-fit law. */
inline void ConfigureMouthWindow(
FVoxelStrateMeasureSettings& InOutSettings,
const FVector& ArrivalPoint,
const FVector& DeparturePoint,
ECaveGeneratorType Archetype)
{
InOutSettings.CenterXY = FVector2D(
0.5f * (ArrivalPoint.X + DeparturePoint.X),
0.5f * (ArrivalPoint.Y + DeparturePoint.Y));
InOutSettings.CoverPointA = FVector2D(ArrivalPoint.X, ArrivalPoint.Y);
InOutSettings.CoverPointB = FVector2D(DeparturePoint.X, DeparturePoint.Y);
InOutSettings.CoverMarginVoxels = FMath::CeilToFloat(
InOutSettings.PlayerCapsuleRadiusVoxels) + 2.0f;
InOutSettings.bIncludeOriginInCoverWindow = NeedsOriginInWindow(Archetype);
}
}
#endif // WITH_DEV_AUTOMATION_TESTS
@@ -13,6 +13,7 @@
#include "VoxelForgeTestFixture.h"
#include "VoxelStrateComposer.h"
#include "VoxelStrateMeasure.h"
#include "VoxelForgePlayerFitWindow.h"
#include "VoxelTypes.h"
namespace
@@ -703,17 +704,99 @@ namespace
}
}
int32 VF_MaxCenteredRadiusForCap(
int32 TopVoxelZ,
int32 BottomVoxelZ,
float BoundarySealThickness,
const FVoxelStrateMeasureSettings& Settings)
{
const int64 StrateHeight = static_cast<int64>(TopVoxelZ) + 1
- static_cast<int64>(BottomVoxelZ);
const int64 MaxMargin64 = FMath::Max<int64>(1, StrateHeight / 4);
const int32 MaxMargin = static_cast<int32>(FMath::Min<int64>(
MaxMargin64, static_cast<int64>(INT32_MAX)));
int32 RequestedMargin = Settings.InteriorMarginVoxels;
if (RequestedMargin < 0)
{
RequestedMargin = FMath::CeilToInt(2.0f * BoundarySealThickness);
}
const int32 ResolvedMargin = FMath::Clamp(RequestedMargin, 0, MaxMargin);
const int64 InteriorHeight = StrateHeight - 2 * static_cast<int64>(ResolvedMargin);
if (InteriorHeight <= 0 || Settings.SampleStep <= 0 || Settings.MaxCells <= 0)
{
return 0;
}
const int64 NumZ = (InteriorHeight + Settings.SampleStep - 1)
/ static_cast<int64>(Settings.SampleStep);
const int64 MaxAxisCells = FMath::Min<int64>(
static_cast<int64>(INT32_MAX),
static_cast<int64>(FMath::Sqrt(
static_cast<double>(Settings.MaxCells / FMath::Max<int64>(NumZ, 1)))));
int32 Radius = static_cast<int32>(FMath::Max<int64>(
1, (MaxAxisCells * static_cast<int64>(Settings.SampleStep)) / 2));
auto CellCountForRadius = [Settings, NumZ](int32 CandidateRadius) -> int64
{
const int64 NumXY = (2 * static_cast<int64>(CandidateRadius)
+ Settings.SampleStep - 1)
/ static_cast<int64>(Settings.SampleStep);
if (NumXY <= 0 || NumXY > INT64_MAX / NumXY
|| NumXY * NumXY > INT64_MAX / FMath::Max<int64>(NumZ, 1))
{
return INT64_MAX;
}
return NumXY * NumXY * NumZ;
};
while (Radius > 1 && CellCountForRadius(Radius) > Settings.MaxCells)
{
--Radius;
}
while (Radius < INT32_MAX
&& CellCountForRadius(Radius + 1) <= Settings.MaxCells)
{
++Radius;
}
return Radius;
}
void VF_ReportPlayerFitRow(
FAutomationTestBase& Test,
const TCHAR* Label,
const FString& WindowLabel,
int32 SampleStep,
const FVoxelStrateMetrics& Metrics,
const FVoxelConnectivityDiagnostics& Diagnostics)
{
if (!Metrics.bValid || !Metrics.bPlayerFitResolved)
{
const FString& Refusal = !Metrics.RefusalReason.IsEmpty()
? Metrics.RefusalReason : Metrics.PlayerFitRefusalReason;
Test.AddInfo(FString::Printf(
TEXT("PLAYER_FIT step=%d window=%s %s: REFUSED reason=%s"),
SampleStep,
*WindowLabel,
Label,
Refusal.IsEmpty() ? TEXT("player-fit metrics unresolved") : *Refusal));
return;
}
Test.AddInfo(FString::Printf(
TEXT("PLAYER_FIT step=1 fitted-window %s: fit_cells=%lld components=%d "
TEXT("PLAYER_FIT step=%d window=%s bounds=X[%.1f,%.1f) Y[%.1f,%.1f) "
"Z[%d,%d) grid=%dx%dx%d sampled=%lld %s: fit_cells=%lld components=%d "
"largest_component_cells=%lld arrival_component_cells=%lld "
"departure_component_cells=%lld mouth_component_gap_voxels=%.3f "
"fit_fraction=%.9f traversable_share=%.9f law=%s"),
SampleStep,
*WindowLabel,
Metrics.SampledMinX,
Metrics.SampledMaxX,
Metrics.SampledMinY,
Metrics.SampledMaxY,
Metrics.SampledMinZ,
Metrics.SampledMaxZ,
Metrics.SampledNumX,
Metrics.SampledNumY,
Metrics.SampledNumZ,
static_cast<long long>(Metrics.NumSampled),
Label,
static_cast<long long>(Metrics.NumPlayerFitCells),
Metrics.NumTraversableComponents,
@@ -1016,7 +1099,115 @@ bool FVoxelForgeScaleDiagnosisTest::RunTest(const FString& Parameters)
ControlSettings.PlayerWalkableFloorAngleDegrees,
ControlSettings.HeadroomCells));
// H3 — the eight fixed showcase identities, measured at step 1 on a mouth-fitted window.
// PART A — direct route-window experiment on the requested Maze identity. The first row is
// intentionally the old mouth-only box; the second turns on the topology-aware origin
// coverage; the third uses the largest centered origin window that the same MaxCells cap can
// hold while retaining enough resolution to contain both mouths.
{
const int32 PartAMazeSlot = FTestWorld::SlotMaze;
VF_SetCandidateOnSlot(World, MazeCandidate, PartAMazeSlot);
FVector ArrivalPoint = FVector::ZeroVector;
FVector DeparturePoint = FVector::ZeroVector;
int32 ArrivalCount = 0;
int32 DepartureCount = 0;
for (const FVoxelPassage& Passage : World.StrateManager->GetPassages())
{
if (Passage.LowerStrateIndex == PartAMazeSlot
&& Passage.UpperStrateIndex + 1 == PartAMazeSlot)
{
ArrivalPoint = Passage.LowerPoint;
++ArrivalCount;
}
if (Passage.UpperStrateIndex == PartAMazeSlot
&& Passage.LowerStrateIndex == PartAMazeSlot + 1)
{
DeparturePoint = Passage.UpperPoint;
++DepartureCount;
}
}
TestEqual(TEXT("PART A Maze has one arrival mouth"), ArrivalCount, 1);
TestEqual(TEXT("PART A Maze has one departure mouth"), DepartureCount, 1);
int32 MazeTopVoxelZ = 0;
int32 MazeBottomVoxelZ = 0;
TestTrue(TEXT("PART A Maze has a vertical measurement range"),
World.GetSlotVoxelZRange(PartAMazeSlot, MazeTopVoxelZ, MazeBottomVoxelZ));
if (ArrivalCount == 1 && DepartureCount == 1
&& World.GetSlotVoxelZRange(PartAMazeSlot, MazeTopVoxelZ, MazeBottomVoxelZ))
{
FVoxelStrateMeasureSettings PartABase = CharacterFitSettings;
PartABase.SampleStep = 1;
PartABase.RadiusInVoxels = 64;
PartABase.MaxCells = FineDiagnosisMaxCells;
PartABase.MaxRouteRetries = 16;
PartABase.HeadroomCells = 2;
PartABase.InteriorMarginVoxels = -1;
FGeneratorSampler GeneratorSampler(*World.Generator);
auto RunPartAWindow =
[&](const FString& WindowLabel, const FVoxelStrateMeasureSettings& Settings)
{
FVoxelStrateMetrics Metrics;
const FVoxelConnectivityDiagnostics Diagnostics =
VF_DiagnosePlayerFitConnectivityWithSampler(
GeneratorSampler, MazeBottomVoxelZ, MazeTopVoxelZ + 1,
VF_BoundarySealForCandidate(MazeCandidate),
ArrivalPoint, DeparturePoint, Settings, &Metrics);
VF_ReportPlayerFitRow(
*this, TEXT("Maze seed=0 index=50 slot=4"), WindowLabel,
Settings.SampleStep, Metrics, Diagnostics);
return TTuple<FVoxelStrateMetrics, FVoxelConnectivityDiagnostics>(
MoveTemp(Metrics), Diagnostics);
};
FVoxelStrateMeasureSettings CurrentSettings = PartABase;
VoxelForgePlayerFitWindow::ConfigureMouthWindow(
CurrentSettings, ArrivalPoint, DeparturePoint, ECaveGeneratorType::Maze);
CurrentSettings.bIncludeOriginInCoverWindow = false;
const auto CurrentResult = RunPartAWindow(
TEXT("current mouth-AABB + margin"), CurrentSettings);
FVoxelStrateMeasureSettings OriginSettings = PartABase;
VoxelForgePlayerFitWindow::ConfigureMouthWindow(
OriginSettings, ArrivalPoint, DeparturePoint, ECaveGeneratorType::Maze);
const auto OriginResult = RunPartAWindow(
TEXT("origin-inclusive mouth-AABB + margin"), OriginSettings);
FVoxelStrateMeasureSettings WholeSettings = PartABase;
WholeSettings.MaxCells = FineDiagnosisMaxCells * 8;
WholeSettings.CenterXY = FVector2D::ZeroVector;
WholeSettings.CoverPointA.Reset();
WholeSettings.CoverPointB.Reset();
WholeSettings.bIncludeOriginInCoverWindow = false;
const float MouthMargin = FMath::CeilToFloat(
WholeSettings.PlayerCapsuleRadiusVoxels) + 2.0f;
const int32 MouthReach = FMath::CeilToInt(FMath::Max(
FMath::Max(FMath::Abs(ArrivalPoint.X), FMath::Abs(DeparturePoint.X)),
FMath::Max(FMath::Abs(ArrivalPoint.Y), FMath::Abs(DeparturePoint.Y)))
+ MouthMargin);
WholeSettings.SampleStep = 1;
WholeSettings.RadiusInVoxels = VF_MaxCenteredRadiusForCap(
MazeTopVoxelZ, MazeBottomVoxelZ,
VF_BoundarySealForCandidate(MazeCandidate), WholeSettings);
const FString WholeWindowLabel = FString::Printf(
TEXT("whole-strate centered origin cap window (radius=%d; mouth_reach=%d; "
"MaxCells=%d)"),
WholeSettings.RadiusInVoxels, MouthReach, WholeSettings.MaxCells);
const auto WholeResult = RunPartAWindow(WholeWindowLabel, WholeSettings);
AddInfo(FString::Printf(
TEXT("PART_A verdict: current=%s; origin-inclusive=%s; whole-strate-cap=%s; "
"origin flip=%s"),
VF_DiagnosisConnectivityName(CurrentResult.Get<1>().Result),
VF_DiagnosisConnectivityName(OriginResult.Get<1>().Result),
VF_DiagnosisConnectivityName(WholeResult.Get<1>().Result),
OriginResult.Get<1>().Result == EVoxelConnectivityResult::Connected
? TEXT("CONFIRMED") : TEXT("NOT_CONFIRMED")));
}
}
// H3 — the eight fixed showcase identities, measured at step 1 on the topology-aware fitted
// window. Origin-rooted families include (0,0) before the MaxCells refusal check.
// The Maze identity is intentionally the requested seed 0 / index 50 / fourth one-based slot.
struct FShowcaseSelection
{
@@ -1065,6 +1256,7 @@ bool FVoxelForgeScaleDiagnosisTest::RunTest(const FString& Parameters)
int32 NumH3Connected = 0;
int32 NumH3DegenerateConnected = 0;
int32 NumH3Measured = 0;
int32 NumH3Refused = 0;
for (const FShowcaseSelection& Selection : Selections)
{
@@ -1109,13 +1301,8 @@ bool FVoxelForgeScaleDiagnosisTest::RunTest(const FString& Parameters)
FVoxelStrateMeasureSettings Settings = CharacterFitSettings;
Settings.SampleStep = 1;
Settings.RadiusInVoxels = 64;
Settings.CenterXY = FVector2D(
0.5f * (ArrivalPoint.X + DeparturePoint.X),
0.5f * (ArrivalPoint.Y + DeparturePoint.Y));
Settings.CoverPointA = FVector2D(ArrivalPoint.X, ArrivalPoint.Y);
Settings.CoverPointB = FVector2D(DeparturePoint.X, DeparturePoint.Y);
Settings.CoverMarginVoxels = FMath::CeilToFloat(
Settings.PlayerCapsuleRadiusVoxels) + 2.0f;
VoxelForgePlayerFitWindow::ConfigureMouthWindow(
Settings, ArrivalPoint, DeparturePoint, Selection.Archetype);
Settings.MaxCells = FineDiagnosisMaxCells;
Settings.MaxRouteRetries = 16;
Settings.HeadroomCells = 2;
@@ -1136,7 +1323,22 @@ bool FVoxelForgeScaleDiagnosisTest::RunTest(const FString& Parameters)
GeneratorSampler, BottomVoxelZ, TopVoxelZ + 1,
VF_BoundarySealForCandidate(Candidate), ArrivalPoint, DeparturePoint,
Settings, &Metrics);
VF_ReportPlayerFitRow(*this, Selection.Label, Metrics, Diagnostics);
VF_ReportPlayerFitRow(
*this, Selection.Label,
VoxelForgePlayerFitWindow::RouteWindowName(Selection.Archetype),
Settings.SampleStep, Metrics, Diagnostics);
if (!Metrics.bValid || !Metrics.bPlayerFitResolved)
{
++NumH3Refused;
const FString& Refusal = !Metrics.RefusalReason.IsEmpty()
? Metrics.RefusalReason : Metrics.PlayerFitRefusalReason;
AddInfo(FString::Printf(
TEXT("H3 %s law=REFUSED; window=%s; reason=%s"),
Selection.Label,
VoxelForgePlayerFitWindow::RouteWindowName(Selection.Archetype),
Refusal.IsEmpty() ? TEXT("player-fit metrics unresolved") : *Refusal));
continue;
}
++NumH3Measured;
if (Diagnostics.Result == EVoxelConnectivityResult::Connected)
{
@@ -1182,11 +1384,12 @@ bool FVoxelForgeScaleDiagnosisTest::RunTest(const FString& Parameters)
AddInfo(FString::Printf(
TEXT("LAW_TALLY restricted step1: before_connected=1/8 "
"before_connected_through_4_cells=1; after_connected=%d/%d "
"after_connected_through_4_cells=%d measured=%d/8"),
"after_connected_through_4_cells=%d measured=%d/8 refused=%d/8"),
NumH3Connected,
UE_ARRAY_COUNT(Selections),
NumH3Measured,
NumH3DegenerateConnected,
NumH3Measured));
NumH3Measured,
NumH3Refused));
return true;
}
@@ -202,6 +202,17 @@ namespace VoxelStrateMeasurePrivate
MaxX64 = FMath::Max(static_cast<double>(PointA.X), static_cast<double>(PointB.X)) + Margin;
MinY64 = FMath::Min(static_cast<double>(PointA.Y), static_cast<double>(PointB.Y)) - Margin;
MaxY64 = FMath::Max(static_cast<double>(PointA.Y), static_cast<double>(PointB.Y)) + Margin;
if (Settings.bIncludeOriginInCoverWindow)
{
// The fitted box is a route-coverage contract, not merely an endpoint box. An
// origin-rooted graph may have its only mouth-to-mouth path through this spine.
// Expand the requested bounds before the MaxCells check below; never crop the
// route after discovering that the full box is too large.
MinX64 = FMath::Min(MinX64, 0.0);
MaxX64 = FMath::Max(MaxX64, 0.0);
MinY64 = FMath::Min(MinY64, 0.0);
MaxY64 = FMath::Max(MaxY64, 0.0);
}
WindowCenter = FVector2D(
static_cast<float>(0.5 * (MinX64 + MaxX64)),
static_cast<float>(0.5 * (MinY64 + MaxY64)));
@@ -2169,6 +2180,7 @@ namespace VoxelStrateMeasurePrivate
}
if (OutPlayerMetrics != nullptr)
{
OutPlayerMetrics->RefusalReason = Reason;
OutPlayerMetrics->PlayerFitRefusalReason = Reason;
}
};
@@ -2216,6 +2228,7 @@ namespace VoxelStrateMeasurePrivate
}
if (OutPlayerMetrics != nullptr)
{
OutPlayerMetrics->RefusalReason = RefusalReason;
OutPlayerMetrics->PlayerFitRefusalReason = RefusalReason;
}
return EVoxelConnectivityResult::OutOfWindow;
@@ -68,6 +68,11 @@ struct VOXELFORGE_API FVoxelStrateMeasureSettings
TOptional<FVector2D> CoverPointA;
TOptional<FVector2D> CoverPointB;
float CoverMarginVoxels = 48.0f;
// Origin-rooted topologies can route from one mouth to the other through the (0,0) spine.
// 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;
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.