feat(measure): a player does not fit. The law was passing impossible routes.

Jahni walked composed strates for the first time: "'maze' is way too tight
tunnel radius wise, i can't enter nor anything". The candidates he could not
enter were the ones our metrics ranked BEST -- Maze had the highest walkable
fraction (.285) and highest connected surface (.99) of all eight.

⭐ THE HEADLINE, AND IT IS BAD NEWS WORTH HAVING:

  old arrival->departure law     passing on 7 of 8 candidates
  player-fit-restricted law      CONNECTED ON 0 OF 8

**The primordial law has been passing on routes no player fits through.**
Every "the world is traversable" claim in this project measured air, not
passage. Player-fit fraction across the eight showcase candidates ranges
.0000 (FloatingIslands -- literally zero) to .0117. The season composer now
returns 0 survivors for 6 required slots, and the showcase gate rejects
8 of 8.

That is the correct outcome. The gate is not tuned to a threshold; it
requires a resolved player-fit volume and a connected player-fit route.

Capsule confirmed by Jahni as the base ACharacter: 34 cm radius, 88 cm
half-height = 1.36 voxels radius, 7.04 tall. Named constant with the
derivation beside it.

Two traps avoided, both of which would have produced confident nonsense:
- THE COARSE GRID CANNOT RESOLVE A PLAYER. SampleStep 4 makes a cell 4
  voxels, ~3x the player's radius, so fit is below the resolution of every
  metric built so far -- which is exactly why nothing flagged an impassable
  maze. Fit runs on step 1 and REFUSES (bPlayerFitResolved=false + reason)
  when the step is too coarse.
- Manhattan distance >= Euclidean, so reusing the existing transform for
  clearance would OVERESTIMATE free space and report fits that do not
  exist. Clearance uses a separate Chebyshev transform; Manhattan stays
  only on the legacy feature-scale metric.

⚠️ MY ROUGHNESS HYPOTHESIS WAS WRONG, and Codex disproved it rather than
confirming it. Forcing SurfaceRoughness=0 on the Maze moved PlayerFit
.0105 -> .0138 and the restricted law stayed NotConnected. Minimum corridor
clearance is 1.00 voxel (25 cm) with or without roughness -- the corridors
are simply too tight for a 34 cm capsule, full stop.

Roughness IS still a real pinch risk where reach exceeds corridor radius:
Underwater 2.21x, FlatPlain 2.31x, CrystalChamber 1.81x, TunnelNetwork
1.44x on corridors, VerticalShafts 1.30x on connectors. The 5.625-voxel
default bound stays dangerous.

Islands measured, not changed (feel is Jahni's call): 0/2 touch either
seal, median clear gap 47.8 above / 9.0 below -- but effective radius
spans 27-258 voxels at H=128 with ThicknessRatio .546, so the worst case
has zero guaranteed separation. Recommendation recorded: radius back to
18-42, StrateHeightInChunks 4 -> 6, ThicknessRatio <= .40.

Build clean, 30 succeeded / 0 failed, verified in the namespace log.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-09-05 12:33:58 +02:00
1 parent faa62d419e
commit eb47efdec9
16 files changed
+1600 -91

No files matched your search

@@ -2285,6 +2285,14 @@ FVoxelStrateMeasuredMetrics VF_SummarizeStrateMetrics(
Result.LargestWalkableSurfaceShare = Metrics.LargestWalkableSurfaceShare;
Result.MedianFeatureScale = Metrics.MedianFeatureScale;
Result.MedianVerticalClearance = Metrics.MedianVerticalClearance;
Result.bPlayerFitResolved = Metrics.bPlayerFitResolved;
Result.PlayerFitRefusalReason = Metrics.PlayerFitRefusalReason;
Result.NumPlayerFitCells = Metrics.NumPlayerFitCells;
Result.PlayerFitFraction = Metrics.PlayerFitFraction;
Result.NumTraversableComponents = Metrics.NumTraversableComponents;
Result.LargestTraversableComponentCells = Metrics.LargestTraversableComponentCells;
Result.TraversableComponentShare = Metrics.TraversableComponentShare;
Result.MinimumPlayerClearanceVoxels = Metrics.MinimumPlayerClearanceVoxels;
Result.ResolvedMarginVoxels = Metrics.ResolvedMarginVoxels;
Result.SampledMinZ = Metrics.SampledMinZ;
Result.SampledMaxZ = Metrics.SampledMaxZ;
@@ -2778,6 +2786,19 @@ namespace
Metrics.LargestWalkableSurfaceShare);
Object->SetNumberField(TEXT("median_feature_scale"), Metrics.MedianFeatureScale);
Object->SetNumberField(TEXT("median_vertical_clearance"), Metrics.MedianVerticalClearance);
Object->SetBoolField(TEXT("player_fit_resolved"), Metrics.bPlayerFitResolved);
Object->SetStringField(TEXT("player_fit_refusal_reason"), Metrics.PlayerFitRefusalReason);
Object->SetNumberField(TEXT("num_player_fit_cells"),
static_cast<double>(Metrics.NumPlayerFitCells));
Object->SetNumberField(TEXT("player_fit_fraction"), Metrics.PlayerFitFraction);
Object->SetNumberField(TEXT("num_traversable_components"),
Metrics.NumTraversableComponents);
Object->SetNumberField(TEXT("largest_traversable_component_cells"),
static_cast<double>(Metrics.LargestTraversableComponentCells));
Object->SetNumberField(TEXT("traversable_component_share"),
Metrics.TraversableComponentShare);
Object->SetNumberField(TEXT("minimum_player_clearance_voxels"),
Metrics.MinimumPlayerClearanceVoxels);
Object->SetNumberField(TEXT("resolved_margin_voxels"), Metrics.ResolvedMarginVoxels);
Object->SetNumberField(TEXT("sampled_min_z"), Metrics.SampledMinZ);
Object->SetNumberField(TEXT("sampled_max_z"), Metrics.SampledMaxZ);
@@ -2852,6 +2873,25 @@ namespace
OutMetrics.MedianFeatureScale) && bValid;
bValid = VF_ReadJsonInt32(Object, TEXT("median_vertical_clearance"),
OutMetrics.MedianVerticalClearance) && bValid;
if (Object->HasField(TEXT("player_fit_resolved")))
{
bValid = VF_ReadJsonBool(Object, TEXT("player_fit_resolved"),
OutMetrics.bPlayerFitResolved) && bValid;
bValid = VF_ReadJsonString(Object, TEXT("player_fit_refusal_reason"),
OutMetrics.PlayerFitRefusalReason) && bValid;
bValid = VF_ReadJsonInt64(Object, TEXT("num_player_fit_cells"),
OutMetrics.NumPlayerFitCells) && bValid;
bValid = VF_ReadJsonFloat(Object, TEXT("player_fit_fraction"),
OutMetrics.PlayerFitFraction) && bValid;
bValid = VF_ReadJsonInt32(Object, TEXT("num_traversable_components"),
OutMetrics.NumTraversableComponents) && bValid;
bValid = VF_ReadJsonInt64(Object, TEXT("largest_traversable_component_cells"),
OutMetrics.LargestTraversableComponentCells) && bValid;
bValid = VF_ReadJsonFloat(Object, TEXT("traversable_component_share"),
OutMetrics.TraversableComponentShare) && bValid;
bValid = VF_ReadJsonFloat(Object, TEXT("minimum_player_clearance_voxels"),
OutMetrics.MinimumPlayerClearanceVoxels) && bValid;
}
bValid = VF_ReadJsonInt32(Object, TEXT("resolved_margin_voxels"),
OutMetrics.ResolvedMarginVoxels) && bValid;
bValid = VF_ReadJsonInt32(Object, TEXT("sampled_min_z"), OutMetrics.SampledMinZ) && bValid;
@@ -2995,6 +3035,14 @@ namespace
&& A.LargestWalkableSurfaceShare == B.LargestWalkableSurfaceShare
&& A.MedianFeatureScale == B.MedianFeatureScale
&& A.MedianVerticalClearance == B.MedianVerticalClearance
&& A.bPlayerFitResolved == B.bPlayerFitResolved
&& A.PlayerFitRefusalReason == B.PlayerFitRefusalReason
&& A.NumPlayerFitCells == B.NumPlayerFitCells
&& A.PlayerFitFraction == B.PlayerFitFraction
&& A.NumTraversableComponents == B.NumTraversableComponents
&& A.LargestTraversableComponentCells == B.LargestTraversableComponentCells
&& A.TraversableComponentShare == B.TraversableComponentShare
&& A.MinimumPlayerClearanceVoxels == B.MinimumPlayerClearanceVoxels
&& A.ResolvedMarginVoxels == B.ResolvedMarginVoxels
&& A.SampledMinZ == B.SampledMinZ
&& A.SampledMaxZ == B.SampledMaxZ
@@ -3075,6 +3123,10 @@ namespace
&& FMath::IsFinite(Metrics.WalkableFloorAreaFraction)
&& FMath::IsFinite(Metrics.LargestWalkableSurfaceShare)
&& FMath::IsFinite(Metrics.MedianFeatureScale)
&& (!Metrics.bPlayerFitResolved
|| (FMath::IsFinite(Metrics.PlayerFitFraction)
&& FMath::IsFinite(Metrics.TraversableComponentShare)
&& FMath::IsFinite(Metrics.MinimumPlayerClearanceVoxels)))
&& FMath::IsFinite(Metrics.LargestComponentPoint.X)
&& FMath::IsFinite(Metrics.LargestComponentPoint.Y)
&& FMath::IsFinite(Metrics.LargestComponentPoint.Z);