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:
1 parent
f088c19f88
commit
993512f0bc
11 files changed
+1294
-38
No files matched your search
@@ -22,6 +22,9 @@
|
||||
namespace
|
||||
{
|
||||
constexpr float GJitterFraction = 0.15f;
|
||||
constexpr float GBootstrapJitterFraction = 0.25f;
|
||||
constexpr double GNearZeroSpread = 1.0e-6;
|
||||
constexpr double GBootstrapMagnitudeFloor = 1.0;
|
||||
|
||||
static const ECaveGeneratorType GAllArchetypes[] =
|
||||
{
|
||||
@@ -861,10 +864,41 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
void VF_JitterMeasuredField(double& Value,
|
||||
const FVoxelStrateFieldSpread& Spread,
|
||||
ECaveGeneratorType Archetype,
|
||||
FRandomStream& Rng,
|
||||
int32& OutBootstrapFieldCount,
|
||||
TArray<FString>& OutBootstrapFieldNames)
|
||||
{
|
||||
const double CorpusRange = FMath::Max(0.0, Spread.Max - Spread.Min);
|
||||
const bool bUseBootstrap = CorpusRange <= GNearZeroSpread;
|
||||
|
||||
// Spread-derived jitter is the real rule. This bootstrap is only for a starved corpus:
|
||||
// when season zero has supplied no useful measured range, use a fraction of this field's
|
||||
// own magnitude, with a one-native-unit floor near zero. Promotion (Tier 5) should retire
|
||||
// this fallback as the corpus grows.
|
||||
const double JitterScale = bUseBootstrap
|
||||
? FMath::Max(FMath::Abs(Value), GBootstrapMagnitudeFloor)
|
||||
: CorpusRange;
|
||||
const float JitterFraction = bUseBootstrap
|
||||
? GBootstrapJitterFraction : GJitterFraction;
|
||||
Value += static_cast<double>((Rng.FRand() * 2.0f - 1.0f) * JitterFraction)
|
||||
* JitterScale;
|
||||
|
||||
if (bUseBootstrap)
|
||||
{
|
||||
++OutBootstrapFieldCount;
|
||||
OutBootstrapFieldNames.AddUnique(VF_SpreadKey(Archetype, Spread.FieldName));
|
||||
}
|
||||
}
|
||||
|
||||
void VF_JitterNativeParams(FVoxelStrateArchetypeParams& Params,
|
||||
ECaveGeneratorType Archetype,
|
||||
const FVoxelStrateCorpus& Corpus,
|
||||
FRandomStream& Rng)
|
||||
FRandomStream& Rng,
|
||||
int32& OutBootstrapFieldCount,
|
||||
TArray<FString>& OutBootstrapFieldNames)
|
||||
{
|
||||
void* Memory = VF_GetParamMemory(Params, Archetype);
|
||||
UStruct* Struct = VF_GetParamStruct(Archetype);
|
||||
@@ -889,17 +923,11 @@ namespace
|
||||
continue;
|
||||
}
|
||||
|
||||
const double CorpusRange = FMath::Max(0.0, Spread.Max - Spread.Min);
|
||||
if (!(CorpusRange > 0.0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
double Value = 0.0;
|
||||
if (VF_ReadNamedField(Memory, Archetype, Spread.FieldName, Value))
|
||||
{
|
||||
Value += static_cast<double>((Rng.FRand() * 2.0f - 1.0f) * GJitterFraction)
|
||||
* CorpusRange;
|
||||
VF_JitterMeasuredField(Value, Spread, Archetype, Rng,
|
||||
OutBootstrapFieldCount, OutBootstrapFieldNames);
|
||||
VF_WriteNamedField(Memory, Archetype, Spread.FieldName, Value);
|
||||
}
|
||||
}
|
||||
@@ -920,17 +948,11 @@ namespace
|
||||
continue;
|
||||
}
|
||||
|
||||
const double CorpusRange = FMath::Max(0.0, Spread->Max - Spread->Min);
|
||||
if (!(CorpusRange > 0.0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
double Value = 0.0;
|
||||
if (VF_ReadPropertyValue(Pair.Value, Memory, Value))
|
||||
{
|
||||
Value += static_cast<double>((Rng.FRand() * 2.0f - 1.0f) * GJitterFraction)
|
||||
* CorpusRange;
|
||||
VF_JitterMeasuredField(Value, *Spread, Archetype, Rng,
|
||||
OutBootstrapFieldCount, OutBootstrapFieldNames);
|
||||
VF_WritePropertyValue(Pair.Value, Memory, Value);
|
||||
}
|
||||
}
|
||||
@@ -1247,7 +1269,9 @@ namespace
|
||||
VF_CopyDominantBooleans(Result.ArchetypeParams,
|
||||
Entries[DominantParent].ArchetypeParams,
|
||||
Result.Archetype);
|
||||
VF_JitterNativeParams(Result.ArchetypeParams, Result.Archetype, Corpus, Rng);
|
||||
VF_JitterNativeParams(Result.ArchetypeParams, Result.Archetype, Corpus, Rng,
|
||||
Result.BootstrapJitterFieldCount,
|
||||
Result.BootstrapJitterFieldNames);
|
||||
VF_ClampNativeParams(Result.ArchetypeParams, Result.Archetype, Corpus);
|
||||
VF_RepairNativeOrderedPairs(Result.ArchetypeParams, Result.Archetype);
|
||||
VF_ResetExcludedFields(Result.ArchetypeParams, Result.Archetype);
|
||||
@@ -2089,6 +2113,47 @@ bool VF_AreStrateArchetypeParamsBitIdentical(
|
||||
&& FMemory::Memcmp(&ABottom, &BBottom, sizeof(ABottom)) == 0;
|
||||
}
|
||||
|
||||
double VF_DistanceFromStrateCorpusCentroid(
|
||||
const FVoxelStrateCorpus& Corpus,
|
||||
const FVoxelStrateArchetypeParams& Params,
|
||||
ECaveGeneratorType Archetype)
|
||||
{
|
||||
const void* Memory = VF_GetParamMemory(Params, Archetype);
|
||||
if (Memory == nullptr)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double DistanceSquared = 0.0;
|
||||
for (const FVoxelStrateFieldSpread& Spread : Corpus.GetFieldSpreads())
|
||||
{
|
||||
if (Spread.Archetype != Archetype || Spread.bExcluded
|
||||
|| Spread.Kind == EVoxelStrateFieldKind::Boolean
|
||||
|| Spread.Kind == EVoxelStrateFieldKind::Enum)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
double Value = 0.0;
|
||||
if (!VF_ReadNamedField(Memory, Archetype, Spread.FieldName, Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const double CorpusRange = FMath::Max(0.0, Spread.Max - Spread.Min);
|
||||
const double Scale = CorpusRange > GNearZeroSpread
|
||||
? CorpusRange
|
||||
: FMath::Max(FMath::Abs(Spread.Mean) * GBootstrapJitterFraction,
|
||||
GBootstrapMagnitudeFloor);
|
||||
if (FMath::IsFinite(Value) && FMath::IsFinite(Spread.Mean) && Scale > 0.0)
|
||||
{
|
||||
const double Normalized = (Value - Spread.Mean) / Scale;
|
||||
DistanceSquared += Normalized * Normalized;
|
||||
}
|
||||
}
|
||||
return FMath::Sqrt(FMath::Max(0.0, DistanceSquared));
|
||||
}
|
||||
|
||||
FVoxelStrateRollInfo VF_RollStrateParamsDetailed(const FVoxelStrateCorpus& Corpus,
|
||||
int32 Seed, int32 Index)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user