feat(composer): F1 -- the candidates are VISIBLE, and detail is invisible

PART B: the preview. Jahni, asked what "good" means for ranking a season:
"good is hard to describe, i believe it'll be easier once i'm able to see
the strates candidates." So the ranking policy was blocked on this, not on
more metrics. COMPOSER-NOTES §3.4 promised the preview comes nearly free
once the measurement exists; collected.

Per candidate, rendered from the grid the measurement ALREADY built (no
second sampling of the world): a vertical XZ section and a plan XY slice,
solid dark, air light, walkable cells highlighted, with a chunk scale bar
so a corridor is distinguishable from a cathedral. Plus a self-contained
index.html contact sheet of all 64 -- survivors first, outliers first
within them, recipes, metrics, and every rejected candidate labelled with
WHY (vacuous 3, fragmented 2, law failed 19).

  Saved/ComposerPreview/<runid>/index.html   64 candidates, 128 PNGs

Two honesty rules are on the page itself, not buried in a comment: every
number states the window that produced it (margin/step/Z span), and a
2D slice CANNOT show connectivity -- two regions that look joined in one
slice may not be.

Plan-Z is chosen by highest solid/air boundary-transition count, not
hardcoded mid-height; a slab strate would otherwise render as a blank
sheet. Codex flagged the ambiguity in my spec and picked correctly.

PART A: bootstrap jitter for zero-spread fields. ±25% of a field's own
magnitude when corpus spread is ~0, ±15% of measured spread otherwise,
clamps last. Marked in code as a BOOTSTRAP for a starved corpus that
promotion (Tier 5) should retire. 1,422 applications over 168 fields in
the parameter roll; 6,848 over 107 in the structure roll.

⚠️ AND IT DID NOT BROADEN THE DISTRIBUTIONS. Feature scale 0-376 either
way; walkable actually NARROWED (.389 -> .287). Codex's conclusion, and I
agree: the measurement is INSENSITIVE to these fields, not merely
corpus-starved.

The reason is now obvious and worth writing down: the grid samples every
4 voxels, and cave DETAIL -- terraces, scallops, ribbing -- lives at 1-3
voxels. It is below the sampling resolution. A step-4 grid structurally
cannot see feel; it sees size. So "vary the detail" and "score the detail"
need different instruments, and the preview images inherit the same limit.

Box proofs stayed sound under wider jitter: parameter roll 1,204 proved /
1,602,524 voxels / 0 violations; structure roll 659 proved / 877,129 /
0 violations. §6.2 did not reopen.

fable-idea.md F1 ticked (measurement + preview).

Build clean, 25 succeeded / 0 failed, verified by me.

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-04 17:35:07 +02:00
1 parent f088c19f88
commit 993512f0bc
11 files changed
+1294 -38

No files matched your search

@@ -387,6 +387,29 @@ namespace VoxelStrateMeasurePrivate
return true;
}
void ExportSampleGrid(FSampleGrid& Source, FVoxelStrateSampleGrid& Destination)
{
Destination = FVoxelStrateSampleGrid();
Destination.bValid = true;
Destination.NumX = Source.NumX;
Destination.NumY = Source.NumY;
Destination.NumZ = Source.NumZ;
Destination.SampleStep = Source.SampleStep;
Destination.CellCount = Source.CellCount;
Destination.MinX = Source.MinX;
Destination.MinY = Source.MinY;
Destination.MinZ = Source.MinZ;
Destination.MaxX = Source.MaxX;
Destination.MaxY = Source.MaxY;
Destination.MaxZ = Source.MaxZ;
Destination.ResolvedMarginVoxels = Source.ResolvedMarginVoxels;
Destination.SampledMinZ = Source.SampledMinZ;
Destination.SampledMaxZ = Source.SampledMaxZ;
// Move, rather than copy, the already-built polarity grid. The caller owns this buffer
// only when it explicitly requested a capture.
Destination.Air = MoveTemp(Source.Air);
}
void DecodeIndex(const FSampleGrid& Grid, int32 Index, int32& OutX, int32& OutY, int32& OutZ)
{
const int32 Plane = Grid.NumX * Grid.NumY;
@@ -1257,9 +1280,14 @@ FVoxelStrateMetrics VF_MeasureStrate(
const UVoxelGenerator& Generator,
const UVoxelStrateManager& Manager,
int32 StrateIndex,
const FVoxelStrateMeasureSettings& Settings)
const FVoxelStrateMeasureSettings& Settings,
FVoxelStrateSampleGrid* OutSampleGrid)
{
FVoxelStrateMetrics Result;
if (OutSampleGrid != nullptr)
{
*OutSampleGrid = FVoxelStrateSampleGrid();
}
VoxelStrateMeasurePrivate::FSampleGrid Grid;
if (!VoxelStrateMeasurePrivate::BuildSampleGrid(
&Generator, &Manager, nullptr, StrateIndex, 0, 0, 0.0f, false,
@@ -1284,6 +1312,10 @@ FVoxelStrateMetrics VF_MeasureStrate(
{
Result.RefusalReason = TEXT("The measurement grid was empty.");
}
if (OutSampleGrid != nullptr && Result.bValid)
{
VoxelStrateMeasurePrivate::ExportSampleGrid(Grid, *OutSampleGrid);
}
return Result;
}
@@ -1292,9 +1324,14 @@ FVoxelStrateMetrics VF_MeasureStrateWithSampler(
int32 StrateBottomWorldZ,
int32 StrateTopWorldZ,
float BoundarySealThickness,
const FVoxelStrateMeasureSettings& Settings)
const FVoxelStrateMeasureSettings& Settings,
FVoxelStrateSampleGrid* OutSampleGrid)
{
FVoxelStrateMetrics Result;
if (OutSampleGrid != nullptr)
{
*OutSampleGrid = FVoxelStrateSampleGrid();
}
VoxelStrateMeasurePrivate::FSampleGrid Grid;
if (!VoxelStrateMeasurePrivate::BuildSampleGrid(
nullptr, nullptr, &Sampler, INDEX_NONE, StrateBottomWorldZ, StrateTopWorldZ,
@@ -1319,6 +1356,10 @@ FVoxelStrateMetrics VF_MeasureStrateWithSampler(
{
Result.RefusalReason = TEXT("The measurement grid was empty.");
}
if (OutSampleGrid != nullptr && Result.bValid)
{
VoxelStrateMeasurePrivate::ExportSampleGrid(Grid, *OutSampleGrid);
}
return Result;
}