feat(composer): cave FEEL is rollable -- 23 of 34 fields were live all along
Jahni: "we need to be able to have everything tuned, so feeling can be
quite different." So the 34 excluded terrain-detail fields had to be
settled -- they are the difference between varying how BIG a cave is and
varying how it FEELS.
Settled by EXPERIMENT, not by reading, because the exclusion rested on the
same kind of structural claim that produced the withdrawn fuse assertion
and the "99.95% void" reading. Sample GetDensityAt over a lattice, change
ONLY those fields, re-sample, per operation group:
LIVE (23 fields) terrace, layer-line, ribbing, overhang, cliff,
scallop, arch, dome, pinch -> now rolled
DEAD (11 fields) column, pit, chimney -> pre-baked into the chunk cache
from Definition->TerrainOperations, so the values in
FStrateGenerationParams are transport, not tunables
The call chain for the dead ones: BuildParamsFromDefinition returns only
the base params (VoxelStrateManager.cpp:1399); VoxelGenerator separately
reads Definition->TerrainOperations (VoxelGenerator.cpp:1099);
BuildChunkCache calls RoomOp->ApplyTo and pre-bakes pits/chimneys/columns
(VoxelCaveMorphology.cpp:1741).
⚠️ AND THE TRAP FIRED, exactly as specced against: these are native
unreflected fields, so REMOVING THEM FROM THE EXCLUSION LIST WOULD HAVE
ROLLED NOTHING. A knob that looks like it works and does not is worse than
an honest exclusion. An explicit jitter path was needed. This is why the
task was specced as an experiment with a liveness assertion (24 group/path
rows, 0 mismatches) rather than as a list edit.
⚠️ HONEST NEGATIVE: rolling detail did NOT materially broaden the measured
distributions. Feature scale 4-376 -> 0-376; walkable unchanged. All 17/17
TunnelNetwork/Underwater candidates activated live detail and every one of
the nine live groups appeared, so it is not that the fields are inert --
it is that most of them have ZERO CORPUS SPREAD, because the four authored
assets leave them at header defaults. Jitter is a fraction of measured
spread, and a fraction of zero is zero.
⇒ Real limitation, recorded not patched: "ranges come from corpus spread"
(§3.3) is right in principle and DEGENERATES on a homogeneous corpus. The
composer needs a bootstrap for zero-spread fields. Next.
Box verdicts with detail rolled: 1,228 proved, 1,634,468 voxels
brute-forced, 0 violations. Survival 59/64.
Recommendation recorded for later: roll terrain-operation parameter
STRUCTS directly into a transient candidate pool rather than corpus-ing
over assets -- there is exactly one such asset in the project, so an asset
corpus would starve like the strate corpus did.
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
859a9b25c1
commit
f088c19f88
5 files changed
+781
-128
No files matched your search
@@ -330,6 +330,251 @@ namespace
|
||||
}
|
||||
return Report;
|
||||
}
|
||||
|
||||
enum class ETerrainDetailProbeGroup : uint8
|
||||
{
|
||||
Terrace,
|
||||
LayerLines,
|
||||
Overhang,
|
||||
Ribbing,
|
||||
Cliff,
|
||||
Scallop,
|
||||
Arch,
|
||||
Column,
|
||||
Pit,
|
||||
Chimney,
|
||||
Dome,
|
||||
Pinch,
|
||||
};
|
||||
|
||||
struct FTerrainDetailProbeSpec
|
||||
{
|
||||
ETerrainDetailProbeGroup Group;
|
||||
const TCHAR* Name;
|
||||
bool bExpectedLive;
|
||||
};
|
||||
|
||||
static const FTerrainDetailProbeSpec GTerrainDetailProbeSpecs[] =
|
||||
{
|
||||
{ ETerrainDetailProbeGroup::Terrace, TEXT("Terrace"), true },
|
||||
{ ETerrainDetailProbeGroup::LayerLines,TEXT("LayerLines"),true },
|
||||
{ ETerrainDetailProbeGroup::Overhang, TEXT("Overhang"), true },
|
||||
{ ETerrainDetailProbeGroup::Ribbing, TEXT("Ribbing"), true },
|
||||
{ ETerrainDetailProbeGroup::Cliff, TEXT("Cliff"), true },
|
||||
{ ETerrainDetailProbeGroup::Scallop, TEXT("Scallop"), true },
|
||||
{ ETerrainDetailProbeGroup::Arch, TEXT("Arch"), true },
|
||||
{ ETerrainDetailProbeGroup::Column, TEXT("Column"), false },
|
||||
{ ETerrainDetailProbeGroup::Pit, TEXT("Pit"), false },
|
||||
{ ETerrainDetailProbeGroup::Chimney, TEXT("Chimney"), false },
|
||||
{ ETerrainDetailProbeGroup::Dome, TEXT("Dome"), true },
|
||||
{ ETerrainDetailProbeGroup::Pinch, TEXT("Pinch"), true },
|
||||
};
|
||||
|
||||
void VF_ClearTerrainDetailFields(FStrateGenerationParams& Params)
|
||||
{
|
||||
Params.TerraceStepHeight = 0.0f;
|
||||
Params.TerraceHardness = 0.0f;
|
||||
Params.TerraceNoiseDisplacement = 0.0f;
|
||||
Params.LayerLineSpacing = 0.0f;
|
||||
Params.LayerLineDepth = 0.0f;
|
||||
Params.OverhangStrength = 0.0f;
|
||||
Params.OverhangDepth = 0.0f;
|
||||
Params.OverhangFrequency = 0.0f;
|
||||
Params.RibbingSpacing = 0.0f;
|
||||
Params.RibbingDepth = 0.0f;
|
||||
Params.CliffStrength = 0.0f;
|
||||
Params.ScallopStrength = 0.0f;
|
||||
Params.ScallopFrequency = 0.0f;
|
||||
Params.ArchDensity = 0.0f;
|
||||
Params.ArchMinRadius = 0.0f;
|
||||
Params.ArchMaxRadius = 0.0f;
|
||||
Params.ColumnDensity = 0.0f;
|
||||
Params.ColumnMinRadius = 0.0f;
|
||||
Params.ColumnMaxRadius = 0.0f;
|
||||
Params.PitDensity = 0.0f;
|
||||
Params.PitMinRadius = 0.0f;
|
||||
Params.PitMaxRadius = 0.0f;
|
||||
Params.PitDepth = 0.0f;
|
||||
Params.ChimneyDensity = 0.0f;
|
||||
Params.ChimneyMinRadius = 0.0f;
|
||||
Params.ChimneyMaxRadius = 0.0f;
|
||||
Params.ChimneyHeight = 0.0f;
|
||||
Params.DomeDensity = 0.0f;
|
||||
Params.DomeMinRadius = 0.0f;
|
||||
Params.DomeMaxRadius = 0.0f;
|
||||
Params.DomeHeightRatio = 0.0f;
|
||||
Params.PinchDensity = 0.0f;
|
||||
Params.PinchStrength = 0.0f;
|
||||
Params.PinchLength = 0.0f;
|
||||
}
|
||||
|
||||
void VF_SetTerrainDetailProbe(FStrateGenerationParams& Params,
|
||||
ETerrainDetailProbeGroup Group)
|
||||
{
|
||||
switch (Group)
|
||||
{
|
||||
case ETerrainDetailProbeGroup::Terrace:
|
||||
Params.TerraceStepHeight = 12.0f;
|
||||
Params.TerraceHardness = 0.9f;
|
||||
Params.TerraceNoiseDisplacement = 1.0f;
|
||||
break;
|
||||
case ETerrainDetailProbeGroup::LayerLines:
|
||||
Params.LayerLineSpacing = 12.0f;
|
||||
Params.LayerLineDepth = 1.0f;
|
||||
break;
|
||||
case ETerrainDetailProbeGroup::Overhang:
|
||||
Params.OverhangStrength = 1.0f;
|
||||
Params.OverhangDepth = 12.0f;
|
||||
Params.OverhangFrequency = 0.03f;
|
||||
break;
|
||||
case ETerrainDetailProbeGroup::Ribbing:
|
||||
Params.RibbingSpacing = 10.0f;
|
||||
Params.RibbingDepth = 1.5f;
|
||||
break;
|
||||
case ETerrainDetailProbeGroup::Cliff:
|
||||
Params.CliffStrength = 1.0f;
|
||||
break;
|
||||
case ETerrainDetailProbeGroup::Scallop:
|
||||
Params.ScallopStrength = 2.0f;
|
||||
Params.ScallopFrequency = 0.04f;
|
||||
break;
|
||||
case ETerrainDetailProbeGroup::Arch:
|
||||
Params.ArchDensity = 1.0f;
|
||||
Params.ArchMinRadius = 8.0f;
|
||||
Params.ArchMaxRadius = 14.0f;
|
||||
break;
|
||||
case ETerrainDetailProbeGroup::Column:
|
||||
Params.ColumnDensity = 1.0f;
|
||||
Params.ColumnMinRadius = 8.0f;
|
||||
Params.ColumnMaxRadius = 14.0f;
|
||||
break;
|
||||
case ETerrainDetailProbeGroup::Pit:
|
||||
Params.PitDensity = 1.0f;
|
||||
Params.PitMinRadius = 8.0f;
|
||||
Params.PitMaxRadius = 14.0f;
|
||||
Params.PitDepth = 64.0f;
|
||||
break;
|
||||
case ETerrainDetailProbeGroup::Chimney:
|
||||
Params.ChimneyDensity = 1.0f;
|
||||
Params.ChimneyMinRadius = 8.0f;
|
||||
Params.ChimneyMaxRadius = 14.0f;
|
||||
Params.ChimneyHeight = 64.0f;
|
||||
break;
|
||||
case ETerrainDetailProbeGroup::Dome:
|
||||
Params.DomeDensity = 1.0f;
|
||||
Params.DomeMinRadius = 14.0f;
|
||||
Params.DomeMaxRadius = 24.0f;
|
||||
Params.DomeHeightRatio = 1.25f;
|
||||
break;
|
||||
case ETerrainDetailProbeGroup::Pinch:
|
||||
Params.PinchDensity = 1.0f;
|
||||
Params.PinchStrength = 12.0f;
|
||||
Params.PinchLength = 32.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VF_BuildTerrainDetailLattice(const VoxelForgeTest::FTestWorld& World,
|
||||
TArray<FVector>& OutPoints)
|
||||
{
|
||||
int32 TopVoxelZ = 0;
|
||||
int32 BottomVoxelZ = 0;
|
||||
OutPoints.Reset();
|
||||
if (!World.GetSlotVoxelZRange(VoxelForgeTest::FTestWorld::SlotTunnelNetwork,
|
||||
TopVoxelZ, BottomVoxelZ))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr int32 Side = 16;
|
||||
constexpr int32 Step = 4;
|
||||
const int32 CentreZ = (TopVoxelZ + BottomVoxelZ) / 2;
|
||||
OutPoints.Reserve(Side * Side * Side);
|
||||
for (int32 IZ = 0; IZ < Side; ++IZ)
|
||||
for (int32 IY = 0; IY < Side; ++IY)
|
||||
for (int32 IX = 0; IX < Side; ++IX)
|
||||
{
|
||||
OutPoints.Add(FVector(
|
||||
(float)(-32 + IX * Step),
|
||||
(float)(-32 + IY * Step),
|
||||
(float)(CentreZ - 32 + IZ * Step)));
|
||||
}
|
||||
}
|
||||
|
||||
struct FTerrainDetailDensitySummary
|
||||
{
|
||||
uint32 Digest = 2166136261u;
|
||||
double Mean = 0.0;
|
||||
double Min = 0.0;
|
||||
double Max = 0.0;
|
||||
};
|
||||
|
||||
FTerrainDetailDensitySummary VF_SampleTerrainDetailLattice(
|
||||
const VoxelForgeTest::FTestWorld& World,
|
||||
const TArray<FVector>& Points,
|
||||
TArray<float>& OutDensities)
|
||||
{
|
||||
FTerrainDetailDensitySummary Summary;
|
||||
OutDensities.Reset(Points.Num());
|
||||
if (Points.Num() == 0 || !World.Generator.IsValid())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
Summary.Min = FLT_MAX;
|
||||
Summary.Max = -FLT_MAX;
|
||||
double Sum = 0.0;
|
||||
for (const FVector& Point : Points)
|
||||
{
|
||||
const float Density = World.Generator->GetDensityAt(Point.X, Point.Y, Point.Z);
|
||||
OutDensities.Add(Density);
|
||||
uint32 Bits = 0;
|
||||
FMemory::Memcpy(&Bits, &Density, sizeof(Bits));
|
||||
Summary.Digest ^= Bits;
|
||||
Summary.Digest *= 16777619u;
|
||||
Summary.Min = FMath::Min(Summary.Min, (double)Density);
|
||||
Summary.Max = FMath::Max(Summary.Max, (double)Density);
|
||||
Sum += Density;
|
||||
}
|
||||
Summary.Mean = Sum / (double)Points.Num();
|
||||
return Summary;
|
||||
}
|
||||
|
||||
struct FTerrainDetailDensityDelta
|
||||
{
|
||||
int32 Changed = 0;
|
||||
int32 SignChanged = 0;
|
||||
double SumAbs = 0.0;
|
||||
double MaxAbs = 0.0;
|
||||
};
|
||||
|
||||
FTerrainDetailDensityDelta VF_CompareTerrainDetailSamples(
|
||||
const TArray<float>& Baseline,
|
||||
const TArray<float>& Variant)
|
||||
{
|
||||
FTerrainDetailDensityDelta Delta;
|
||||
if (Baseline.Num() != Variant.Num())
|
||||
{
|
||||
return Delta;
|
||||
}
|
||||
for (int32 Index = 0; Index < Baseline.Num(); ++Index)
|
||||
{
|
||||
const float A = Baseline[Index];
|
||||
const float B = Variant[Index];
|
||||
if (!VoxelForgeTest::BitEqual(A, B))
|
||||
{
|
||||
++Delta.Changed;
|
||||
}
|
||||
if ((A < 0.0f) != (B < 0.0f))
|
||||
{
|
||||
++Delta.SignChanged;
|
||||
}
|
||||
const double AbsDelta = FMath::Abs((double)B - (double)A);
|
||||
Delta.SumAbs += AbsDelta;
|
||||
Delta.MaxAbs = FMath::Max(Delta.MaxAbs, AbsDelta);
|
||||
}
|
||||
return Delta;
|
||||
}
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
@@ -390,7 +635,7 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("Corpus membership: %d project strate assets discovered, %d project vectors loaded, "
|
||||
"%d defaults, %d total members. Terrain-op Asset Registry count (not rolled in this "
|
||||
"task): %d."),
|
||||
"task; live fields use header defaults instead): %d."),
|
||||
ProjectStrateAssets.Num(), NumProjectCorpusEntries, NumDefaultCorpusEntries, Corpus.Num(),
|
||||
ProjectTerrainOpAssets.Num()));
|
||||
AddInfo(CorpusMembership);
|
||||
@@ -422,19 +667,57 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
||||
TestEqual(TEXT("all project strate assets plus eight defaults are corpus members"),
|
||||
Corpus.Num(), ProjectStrateAssets.Num() + 8);
|
||||
|
||||
static const TCHAR* LiveTerrainDetailFields[] =
|
||||
{
|
||||
TEXT("TerraceStepHeight"), TEXT("TerraceHardness"), TEXT("TerraceNoiseDisplacement"),
|
||||
TEXT("LayerLineSpacing"), TEXT("LayerLineDepth"),
|
||||
TEXT("OverhangStrength"), TEXT("OverhangDepth"), TEXT("OverhangFrequency"),
|
||||
TEXT("RibbingSpacing"), TEXT("RibbingDepth"), TEXT("CliffStrength"),
|
||||
TEXT("ScallopStrength"), TEXT("ScallopFrequency"), TEXT("ArchDensity"),
|
||||
TEXT("ArchMinRadius"), TEXT("ArchMaxRadius"),
|
||||
TEXT("DomeDensity"), TEXT("DomeMinRadius"), TEXT("DomeMaxRadius"),
|
||||
TEXT("DomeHeightRatio"), TEXT("PinchDensity"), TEXT("PinchStrength"),
|
||||
TEXT("PinchLength"),
|
||||
};
|
||||
static const TCHAR* DeadTerrainDetailFields[] =
|
||||
{
|
||||
TEXT("ColumnDensity"), TEXT("ColumnMinRadius"), TEXT("ColumnMaxRadius"),
|
||||
TEXT("PitDensity"), TEXT("PitMinRadius"), TEXT("PitMaxRadius"), TEXT("PitDepth"),
|
||||
TEXT("ChimneyDensity"), TEXT("ChimneyMinRadius"), TEXT("ChimneyMaxRadius"),
|
||||
TEXT("ChimneyHeight"),
|
||||
};
|
||||
for (const TCHAR* FieldName : LiveTerrainDetailFields)
|
||||
{
|
||||
const FVoxelStrateFieldSpread* Spread = Corpus.FindSpread(
|
||||
ECaveGeneratorType::TunnelNetwork, FString(FieldName));
|
||||
TestTrue(FString::Printf(TEXT("live terrain field %s is no longer excluded"), FieldName),
|
||||
Spread != nullptr && !Spread->bExcluded);
|
||||
}
|
||||
for (const TCHAR* FieldName : DeadTerrainDetailFields)
|
||||
{
|
||||
const FVoxelStrateFieldSpread* Spread = Corpus.FindSpread(
|
||||
ECaveGeneratorType::TunnelNetwork, FString(FieldName));
|
||||
TestTrue(FString::Printf(TEXT("dead terrain field %s remains excluded"), FieldName),
|
||||
Spread != nullptr && Spread->bExcluded);
|
||||
}
|
||||
TestEqual(TEXT("exactly eleven direct terrain transport fields remain excluded"),
|
||||
static_cast<int32>(UE_ARRAY_COUNT(DeadTerrainDetailFields)), 11);
|
||||
|
||||
FString SpreadTable = TEXT(
|
||||
"CORPUS SPREAD (population stddev; jitter range = ±15% of max-min; clamps are safety only)\n"
|
||||
"archetype | struct | field | kind | excluded | reflected | samples | min | max | mean | stddev | clamp\n");
|
||||
"archetype | struct | field | kind | excluded | reflected | authored | op-defaults | samples | min | max | mean | stddev | clamp\n");
|
||||
for (const FVoxelStrateFieldSpread& Spread : Corpus.GetFieldSpreads())
|
||||
{
|
||||
SpreadTable += FString::Printf(
|
||||
TEXT("%s | %s | %s | %s | %s | %s | %d | %s | %s | %s | %s | %s\n"),
|
||||
TEXT("%s | %s | %s | %s | %s | %s | %d | %d | %d | %s | %s | %s | %s | %s\n"),
|
||||
VF_GetStrateArchetypeName(Spread.Archetype),
|
||||
*Spread.ParamStructName,
|
||||
*Spread.FieldName,
|
||||
VF_FieldKindName(Spread.Kind),
|
||||
Spread.bExcluded ? TEXT("yes") : TEXT("no"),
|
||||
Spread.bReflected ? TEXT("yes") : TEXT("no"),
|
||||
Spread.AuthoredSampleCount,
|
||||
Spread.DefaultSeedCount,
|
||||
Spread.SampleCount,
|
||||
*VF_FormatSpreadValue(Spread.Min),
|
||||
*VF_FormatSpreadValue(Spread.Max),
|
||||
@@ -455,8 +738,10 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
||||
"Bool policy: bTunnelsFlowTowardOrigin is inherited from the dominant weighted parent; "
|
||||
"int/enum SNAP fields use the native family blend and are not jittered. Parent selection "
|
||||
"is weight-1.0 (equal) because UVoxelStrateDefinition has no corpus-weight field; parents "
|
||||
"are selected only within one exact archetype group. FStrateGenerationParams terrain-op "
|
||||
"transport fields remain excluded; the sibling families' authored fields are rolled."));
|
||||
"are selected only within one exact archetype group. The 23 empirically live terrain-detail "
|
||||
"fields are rolled, with one per-operation header-default sample added to their spread; "
|
||||
"only the 11 empirically dead Column/Pit/Chimney transport fields remain excluded. The "
|
||||
"sibling families' authored fields are rolled."));
|
||||
|
||||
TestTrue(TEXT("the reflected parameter schema is covered or explicitly excluded"),
|
||||
Corpus.IsSchemaValid());
|
||||
@@ -509,6 +794,9 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
||||
int32 TotalBoxProved = 0;
|
||||
int64 TotalBoxCheckedVoxels = 0;
|
||||
int32 TotalBoxViolations = 0;
|
||||
int32 NumTunnelLikeCandidates = 0;
|
||||
int32 NumCandidatesWithLiveDetail = 0;
|
||||
int32 LiveActivationVariationCounts[9] = {};
|
||||
TArray<int32> BoxViolationCandidates;
|
||||
FString FirstBoxViolation;
|
||||
|
||||
@@ -534,6 +822,41 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Roll.Archetype == ECaveGeneratorType::TunnelNetwork
|
||||
|| Roll.Archetype == ECaveGeneratorType::Underwater)
|
||||
{
|
||||
const FStrateGenerationParams& DetailParams =
|
||||
Roll.ArchetypeParams.TunnelNetworkParams;
|
||||
const float LiveActivationValues[] =
|
||||
{
|
||||
DetailParams.TerraceStepHeight,
|
||||
DetailParams.LayerLineSpacing,
|
||||
DetailParams.OverhangStrength,
|
||||
DetailParams.RibbingSpacing,
|
||||
DetailParams.CliffStrength,
|
||||
DetailParams.ScallopStrength,
|
||||
DetailParams.ArchDensity,
|
||||
DetailParams.DomeDensity,
|
||||
DetailParams.PinchDensity,
|
||||
};
|
||||
++NumTunnelLikeCandidates;
|
||||
bool bHasLiveDetail = false;
|
||||
for (int32 DetailIndex = 0;
|
||||
DetailIndex < static_cast<int32>(UE_ARRAY_COUNT(LiveActivationValues));
|
||||
++DetailIndex)
|
||||
{
|
||||
if (LiveActivationValues[DetailIndex] != 0.0f)
|
||||
{
|
||||
bHasLiveDetail = true;
|
||||
++LiveActivationVariationCounts[DetailIndex];
|
||||
}
|
||||
}
|
||||
if (bHasLiveDetail)
|
||||
{
|
||||
++NumCandidatesWithLiveDetail;
|
||||
}
|
||||
}
|
||||
|
||||
bool bParentsMatchArchetype = true;
|
||||
for (const int32 ParentIndex : Roll.ParentEntryIndices)
|
||||
{
|
||||
@@ -682,6 +1005,31 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
||||
LargestComponentSurvivalThreshold, NumNonVacuous, NumLargestEnough, NumLawPasses,
|
||||
NumRollFailures, RollAndMeasureSeconds));
|
||||
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("Live detail activation coverage: %d/%d TunnelNetwork/Underwater candidates had at "
|
||||
"least one non-zero live activation; Terrace/Layer/Overhang/Ribbing/Cliff/Scallop/"
|
||||
"Arch/Dome/Pinch non-zero candidate counts = %d/%d/%d/%d/%d/%d/%d/%d/%d."),
|
||||
NumCandidatesWithLiveDetail, NumTunnelLikeCandidates,
|
||||
LiveActivationVariationCounts[0], LiveActivationVariationCounts[1],
|
||||
LiveActivationVariationCounts[2], LiveActivationVariationCounts[3],
|
||||
LiveActivationVariationCounts[4], LiveActivationVariationCounts[5],
|
||||
LiveActivationVariationCounts[6], LiveActivationVariationCounts[7],
|
||||
LiveActivationVariationCounts[8]));
|
||||
TestTrue(TEXT("at least one TunnelNetwork/Underwater candidate carries live detail variation"),
|
||||
NumCandidatesWithLiveDetail > 0);
|
||||
static const TCHAR* LiveActivationNames[] =
|
||||
{
|
||||
TEXT("Terrace"), TEXT("LayerLines"), TEXT("Overhang"), TEXT("Ribbing"), TEXT("Cliff"),
|
||||
TEXT("Scallop"), TEXT("Arch"), TEXT("Dome"), TEXT("Pinch"),
|
||||
};
|
||||
for (int32 DetailIndex = 0;
|
||||
DetailIndex < static_cast<int32>(UE_ARRAY_COUNT(LiveActivationNames)); ++DetailIndex)
|
||||
{
|
||||
TestTrue(FString::Printf(TEXT("live %s activation appears in a rolled candidate"),
|
||||
LiveActivationNames[DetailIndex]),
|
||||
LiveActivationVariationCounts[DetailIndex] > 0);
|
||||
}
|
||||
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("Rolled §6.2 box verdict check: mixed=%d, AllSolid=%d, AllAir=%d, proved=%d, "
|
||||
"brute-force lattice samples=%lld, violations=%d; violating candidates=%s. "
|
||||
@@ -716,4 +1064,137 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FVoxelForgeTerrainDetailLivenessTest,
|
||||
"VoxelForge.Composer.TerrainDetailLiveness",
|
||||
EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::EngineFilter)
|
||||
|
||||
bool FVoxelForgeTerrainDetailLivenessTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
using namespace VoxelForgeTest;
|
||||
(void)Parameters;
|
||||
|
||||
constexpr int32 ExpectedLatticeSamples = 16 * 16 * 16;
|
||||
FString Table = TEXT(
|
||||
"TERRAIN DETAIL LIVENESS (same 4096-point lattice; only one group changed per row)\n"
|
||||
"path | group | samples | baseline digest | variant digest | baseline mean | variant mean | "
|
||||
"changed | sign changes | sum abs delta | max abs delta | verdict\n");
|
||||
int32 TotalRows = 0;
|
||||
int32 FailedRows = 0;
|
||||
|
||||
for (const bool bUseOperatorStack : { false, true })
|
||||
{
|
||||
FTestWorld World;
|
||||
World.Build(1337, 2, bUseOperatorStack);
|
||||
if (!World.IsValid())
|
||||
{
|
||||
AddError(World.WhyInvalid());
|
||||
return false;
|
||||
}
|
||||
|
||||
UVoxelStrateDefinition* Definition =
|
||||
World.Definitions[FTestWorld::SlotTunnelNetwork].Get();
|
||||
if (Definition == nullptr)
|
||||
{
|
||||
AddError(TEXT("The fixture has no TunnelNetwork definition for the liveness experiment."));
|
||||
return false;
|
||||
}
|
||||
Definition->TerrainOperations.Reset();
|
||||
TestEqual(
|
||||
FString::Printf(TEXT("%s fixture has no terrain-op asset pool"),
|
||||
bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy")),
|
||||
Definition->TerrainOperations.Num(), 0);
|
||||
|
||||
TArray<FVector> Points;
|
||||
VF_BuildTerrainDetailLattice(World, Points);
|
||||
TestEqual(
|
||||
FString::Printf(TEXT("%s experiment uses the fixed lattice"),
|
||||
bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy")),
|
||||
Points.Num(), ExpectedLatticeSamples);
|
||||
|
||||
FStrateGenerationParams BaselineParams = Definition->GenerationParams;
|
||||
VF_ClearTerrainDetailFields(BaselineParams);
|
||||
TArray<float> FirstBaseline;
|
||||
bool bHaveFirstBaseline = false;
|
||||
|
||||
for (const FTerrainDetailProbeSpec& Spec : GTerrainDetailProbeSpecs)
|
||||
{
|
||||
Definition->GenerationParams = BaselineParams;
|
||||
World.Reinitialize();
|
||||
|
||||
TArray<float> BaselineSamples;
|
||||
const FTerrainDetailDensitySummary BaselineSummary =
|
||||
VF_SampleTerrainDetailLattice(World, Points, BaselineSamples);
|
||||
if (!bHaveFirstBaseline)
|
||||
{
|
||||
FirstBaseline = BaselineSamples;
|
||||
bHaveFirstBaseline = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool bSameBaseline = FirstBaseline.Num() == BaselineSamples.Num();
|
||||
for (int32 SampleIndex = 0; bSameBaseline && SampleIndex < FirstBaseline.Num(); ++SampleIndex)
|
||||
{
|
||||
bSameBaseline = BitEqual(FirstBaseline[SampleIndex], BaselineSamples[SampleIndex]);
|
||||
}
|
||||
TestTrue(
|
||||
FString::Printf(TEXT("%s baseline is stable before %s probe"),
|
||||
bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy"),
|
||||
Spec.Name),
|
||||
bSameBaseline);
|
||||
}
|
||||
|
||||
FStrateGenerationParams VariantParams = BaselineParams;
|
||||
VF_SetTerrainDetailProbe(VariantParams, Spec.Group);
|
||||
Definition->GenerationParams = VariantParams;
|
||||
World.Reinitialize();
|
||||
|
||||
TArray<float> VariantSamples;
|
||||
const FTerrainDetailDensitySummary VariantSummary =
|
||||
VF_SampleTerrainDetailLattice(World, Points, VariantSamples);
|
||||
const FTerrainDetailDensityDelta Delta =
|
||||
VF_CompareTerrainDetailSamples(BaselineSamples, VariantSamples);
|
||||
|
||||
const bool bObservedLive = Delta.Changed > 0;
|
||||
const bool bVerdictMatches = bObservedLive == Spec.bExpectedLive;
|
||||
++TotalRows;
|
||||
if (!bVerdictMatches)
|
||||
{
|
||||
++FailedRows;
|
||||
}
|
||||
TestTrue(
|
||||
FString::Printf(TEXT("%s %s field group has the measured liveness verdict"),
|
||||
bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy"),
|
||||
Spec.Name),
|
||||
bVerdictMatches);
|
||||
|
||||
Table += FString::Printf(
|
||||
TEXT("%s | %s | %d | 0x%08x | 0x%08x | %.9g | %.9g | %d | %d | %.9g | %.9g | %s\n"),
|
||||
bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy"),
|
||||
Spec.Name,
|
||||
Points.Num(),
|
||||
BaselineSummary.Digest,
|
||||
VariantSummary.Digest,
|
||||
BaselineSummary.Mean,
|
||||
VariantSummary.Mean,
|
||||
Delta.Changed,
|
||||
Delta.SignChanged,
|
||||
Delta.SumAbs,
|
||||
Delta.MaxAbs,
|
||||
bObservedLive ? TEXT("LIVE") : TEXT("DEAD"));
|
||||
}
|
||||
}
|
||||
|
||||
AddInfo(Table);
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("Terrain detail liveness: %d group/path rows measured, %d mismatches; LIVE groups "
|
||||
"must have at least one bit-changed density and DEAD groups must have none. No "
|
||||
"terrain-operation assets were present in the fixture pool."),
|
||||
TotalRows, FailedRows));
|
||||
TestEqual(TEXT("all terrain detail liveness rows matched their measured verdict"), FailedRows, 0);
|
||||
TestEqual(TEXT("both density paths measured every terrain detail group"), TotalRows,
|
||||
static_cast<int32>(2 * UE_ARRAY_COUNT(GTerrainDetailProbeSpecs)));
|
||||
return FailedRows == 0;
|
||||
}
|
||||
|
||||
#endif // WITH_DEV_AUTOMATION_TESTS
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "VoxelSettings.h"
|
||||
#include "VoxelStrateDefinition.h"
|
||||
#include "VoxelTerrainOpDefinition.h"
|
||||
#include "VoxelDensityOpStack.h"
|
||||
|
||||
#include <type_traits>
|
||||
@@ -547,6 +548,88 @@ namespace
|
||||
return VF_WriteRuntimeField(Memory, Archetype, FieldName, Value);
|
||||
}
|
||||
|
||||
const TCHAR* VF_GetTerrainOperationFieldPrefix(EVoxelTerrainOpType Type)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case EVoxelTerrainOpType::Terrace: return TEXT("Terrace");
|
||||
case EVoxelTerrainOpType::LayerLines: return TEXT("LayerLine");
|
||||
case EVoxelTerrainOpType::Ribbing: return TEXT("Ribbing");
|
||||
case EVoxelTerrainOpType::Cliff: return TEXT("Cliff");
|
||||
case EVoxelTerrainOpType::Scallop: return TEXT("Scallop");
|
||||
case EVoxelTerrainOpType::Overhang: return TEXT("Overhang");
|
||||
case EVoxelTerrainOpType::Arch: return TEXT("Arch");
|
||||
case EVoxelTerrainOpType::Column: return TEXT("Column");
|
||||
case EVoxelTerrainOpType::Pit: return TEXT("Pit");
|
||||
case EVoxelTerrainOpType::Chimney: return TEXT("Chimney");
|
||||
case EVoxelTerrainOpType::Dome: return TEXT("Dome");
|
||||
case EVoxelTerrainOpType::Pinch: return TEXT("Pinch");
|
||||
default: return TEXT("");
|
||||
}
|
||||
}
|
||||
|
||||
void VF_AddTerrainOperationDefaultSamples(
|
||||
TArray<TArray<double>>& Values,
|
||||
const TArray<FVoxelStrateFieldSpread>& Spreads)
|
||||
{
|
||||
if (Values.Num() != Spreads.Num())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UVoxelTerrainOpDefinition* Operation = NewObject<UVoxelTerrainOpDefinition>(
|
||||
GetTransientPackage(), NAME_None, RF_Transient);
|
||||
if (Operation == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static const EVoxelTerrainOpType OperationTypes[] =
|
||||
{
|
||||
EVoxelTerrainOpType::Terrace,
|
||||
EVoxelTerrainOpType::LayerLines,
|
||||
EVoxelTerrainOpType::Ribbing,
|
||||
EVoxelTerrainOpType::Cliff,
|
||||
EVoxelTerrainOpType::Scallop,
|
||||
EVoxelTerrainOpType::Overhang,
|
||||
EVoxelTerrainOpType::Arch,
|
||||
EVoxelTerrainOpType::Column,
|
||||
EVoxelTerrainOpType::Pit,
|
||||
EVoxelTerrainOpType::Chimney,
|
||||
EVoxelTerrainOpType::Dome,
|
||||
EVoxelTerrainOpType::Pinch,
|
||||
};
|
||||
|
||||
for (const EVoxelTerrainOpType Type : OperationTypes)
|
||||
{
|
||||
const TCHAR* Prefix = VF_GetTerrainOperationFieldPrefix(Type);
|
||||
Operation->Type = Type;
|
||||
FStrateGenerationParams Defaults;
|
||||
Operation->ApplyTo(Defaults);
|
||||
|
||||
// Seed only the fields owned by this operation. The values come from the actual
|
||||
// UVoxelTerrainOpDefinition UPROPERTY initializers through ApplyTo; no second asset
|
||||
// corpus and no duplicated default constants are introduced here.
|
||||
for (int32 FieldIndex = 0; FieldIndex < Spreads.Num(); ++FieldIndex)
|
||||
{
|
||||
const FVoxelStrateFieldSpread& Spread = Spreads[FieldIndex];
|
||||
if (!VF_IsTunnelArchetype(Spread.Archetype)
|
||||
|| Spread.bExcluded
|
||||
|| !Spread.FieldName.StartsWith(Prefix))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
double Value = 0.0;
|
||||
if (VF_ReadNamedField(&Defaults, Spread.Archetype, Spread.FieldName, Value)
|
||||
&& FMath::IsFinite(Value))
|
||||
{
|
||||
Values[FieldIndex].Add(Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VF_AddPropertyMetadata(FVoxelStrateFieldSpread& Spread, const FProperty* Property)
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
@@ -607,26 +690,11 @@ namespace
|
||||
OutIndices.Add(VF_SpreadKey(Archetype, Name), OutSpreads.Num() - 1);
|
||||
}
|
||||
|
||||
void VF_ResetNonTunableFields(FStrateGenerationParams& Params)
|
||||
void VF_ResetDeadTerrainTransportFields(FStrateGenerationParams& Params)
|
||||
{
|
||||
// These are terrain-op transport values (written per room by UVoxelTerrainOpDefinition)
|
||||
// plus manager-owned runtime Z bounds. They are not rolled in this composer pass.
|
||||
Params.TerraceStepHeight = 0.0f;
|
||||
Params.TerraceHardness = 0.0f;
|
||||
Params.TerraceNoiseDisplacement = 0.0f;
|
||||
Params.LayerLineSpacing = 0.0f;
|
||||
Params.LayerLineDepth = 0.0f;
|
||||
Params.OverhangStrength = 0.0f;
|
||||
Params.OverhangDepth = 0.0f;
|
||||
Params.OverhangFrequency = 0.0f;
|
||||
Params.RibbingSpacing = 0.0f;
|
||||
Params.RibbingDepth = 0.0f;
|
||||
Params.CliffStrength = 0.0f;
|
||||
Params.ScallopStrength = 0.0f;
|
||||
Params.ScallopFrequency = 0.0f;
|
||||
Params.ArchDensity = 0.0f;
|
||||
Params.ArchMinRadius = 0.0f;
|
||||
Params.ArchMaxRadius = 0.0f;
|
||||
// Empirical liveness proved these direct FStrate slots are dead: the runtime bakes these
|
||||
// three operation families from UVoxelTerrainOpDefinition values into room caches before
|
||||
// sampling. Keep them at their neutral values until terrain-op structs are rolled too.
|
||||
Params.ColumnDensity = 0.0f;
|
||||
Params.ColumnMinRadius = 0.0f;
|
||||
Params.ColumnMaxRadius = 0.0f;
|
||||
@@ -638,13 +706,6 @@ namespace
|
||||
Params.ChimneyMinRadius = 0.0f;
|
||||
Params.ChimneyMaxRadius = 0.0f;
|
||||
Params.ChimneyHeight = 0.0f;
|
||||
Params.DomeDensity = 0.0f;
|
||||
Params.DomeMinRadius = 0.0f;
|
||||
Params.DomeMaxRadius = 0.0f;
|
||||
Params.DomeHeightRatio = 0.0f;
|
||||
Params.PinchDensity = 0.0f;
|
||||
Params.PinchStrength = 0.0f;
|
||||
Params.PinchLength = 0.0f;
|
||||
Params.StrateTopWorldZ = 0.0f;
|
||||
Params.StrateBottomWorldZ = 0.0f;
|
||||
}
|
||||
@@ -662,7 +723,7 @@ namespace
|
||||
{
|
||||
if (VF_IsTunnelArchetype(Archetype))
|
||||
{
|
||||
VF_ResetNonTunableFields(Params.TunnelNetworkParams);
|
||||
VF_ResetDeadTerrainTransportFields(Params.TunnelNetworkParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -690,7 +751,12 @@ namespace
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Spread != nullptr && !Spread->bReflected && !Spread->bExcluded)
|
||||
double NativeValue = 0.0;
|
||||
FStrateGenerationParams NativeDefaults;
|
||||
const bool bNativeFStrateField = VF_IsTunnelArchetype(Archetype)
|
||||
&& VF_ReadFStrateField(NativeDefaults, FieldName, NativeValue);
|
||||
if (Spread != nullptr && !Spread->bReflected && !Spread->bExcluded
|
||||
&& !bNativeFStrateField)
|
||||
{
|
||||
bSchemaValid = false;
|
||||
VF_AppendSchemaError(SchemaError,
|
||||
@@ -807,6 +873,38 @@ namespace
|
||||
return;
|
||||
}
|
||||
|
||||
// FStrateGenerationParams deliberately keeps the terrain-detail transport fields as
|
||||
// native, non-UPROPERTY members. Handle those fields through the same measured spread
|
||||
// policy as reflected fields; otherwise removing an exclusion would only change the
|
||||
// report while the candidate value stayed at its blended parent value.
|
||||
if (VF_IsTunnelArchetype(Archetype))
|
||||
{
|
||||
for (const FVoxelStrateFieldSpread& Spread : Corpus.GetFieldSpreads())
|
||||
{
|
||||
if (Spread.Archetype != Archetype
|
||||
|| Spread.bReflected
|
||||
|| Spread.bExcluded
|
||||
|| Spread.Kind != EVoxelStrateFieldKind::Continuous)
|
||||
{
|
||||
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_WriteNamedField(Memory, Archetype, Spread.FieldName, Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TMap<FString, FProperty*> Properties;
|
||||
VF_CollectProperties(Struct, Properties);
|
||||
for (const TPair<FString, FProperty*>& Pair : Properties)
|
||||
@@ -1327,44 +1425,21 @@ const TArray<FVoxelStrateFieldExclusion>& FVoxelStrateCorpus::GetNonTunableField
|
||||
{
|
||||
TArray<FVoxelStrateFieldExclusion> Result;
|
||||
|
||||
const TCHAR* TerrainOpReason = TEXT(
|
||||
"Transport slot only: generation overlays it per room from a UVoxelTerrainOpDefinition "
|
||||
"selected through the active strate's TerrainOperations pool; it is not the authored "
|
||||
"strate-generation source.");
|
||||
VF_AddExclusion(Result, TEXT("TerraceStepHeight"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("TerraceHardness"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("TerraceNoiseDisplacement"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("LayerLineSpacing"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("LayerLineDepth"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("OverhangStrength"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("OverhangDepth"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("OverhangFrequency"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("RibbingSpacing"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("RibbingDepth"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("CliffStrength"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ScallopStrength"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ScallopFrequency"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ArchDensity"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ArchMinRadius"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ArchMaxRadius"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ColumnDensity"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ColumnMinRadius"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ColumnMaxRadius"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("PitDensity"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("PitMinRadius"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("PitMaxRadius"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("PitDepth"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ChimneyDensity"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ChimneyMinRadius"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ChimneyMaxRadius"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ChimneyHeight"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("DomeDensity"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("DomeMinRadius"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("DomeMaxRadius"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("DomeHeightRatio"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("PinchDensity"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("PinchStrength"), TerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("PinchLength"), TerrainOpReason);
|
||||
const TCHAR* DeadTerrainOpReason = TEXT(
|
||||
"Empirically dead when only this FStrateGenerationParams slot changes: the runtime "
|
||||
"bakes Column/Pit/Chimney geometry from UVoxelTerrainOpDefinition values into the "
|
||||
"room cache, so this direct strata slot does not feed GetDensityAt.");
|
||||
VF_AddExclusion(Result, TEXT("ColumnDensity"), DeadTerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ColumnMinRadius"), DeadTerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ColumnMaxRadius"), DeadTerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("PitDensity"), DeadTerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("PitMinRadius"), DeadTerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("PitMaxRadius"), DeadTerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("PitDepth"), DeadTerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ChimneyDensity"), DeadTerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ChimneyMinRadius"), DeadTerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ChimneyMaxRadius"), DeadTerrainOpReason);
|
||||
VF_AddExclusion(Result, TEXT("ChimneyHeight"), DeadTerrainOpReason);
|
||||
|
||||
VF_AddExclusion(Result, TEXT("StrateTopWorldZ"), TEXT(
|
||||
"Runtime Z bound supplied by UVoxelStrateManager for the active layout slot; not a tunable."));
|
||||
@@ -1500,10 +1575,17 @@ void FVoxelStrateCorpus::RebuildSpreads()
|
||||
}
|
||||
}
|
||||
|
||||
for (int32 FieldIndex = 0; FieldIndex < FieldSpreads.Num(); ++FieldIndex)
|
||||
{
|
||||
FieldSpreads[FieldIndex].AuthoredSampleCount = Values[FieldIndex].Num();
|
||||
}
|
||||
VF_AddTerrainOperationDefaultSamples(Values, FieldSpreads);
|
||||
|
||||
for (int32 FieldIndex = 0; FieldIndex < FieldSpreads.Num(); ++FieldIndex)
|
||||
{
|
||||
FVoxelStrateFieldSpread& Spread = FieldSpreads[FieldIndex];
|
||||
const TArray<double>& Samples = Values[FieldIndex];
|
||||
Spread.DefaultSeedCount = Samples.Num() - Spread.AuthoredSampleCount;
|
||||
Spread.SampleCount = Samples.Num();
|
||||
if (Samples.Num() == 0)
|
||||
{
|
||||
|
||||
@@ -120,7 +120,7 @@ enum class EVoxelStrateFieldKind : uint8
|
||||
Enum,
|
||||
};
|
||||
|
||||
/** A field deliberately kept out of the parameter roll. */
|
||||
/** A field deliberately kept out of the parameter roll after the measured liveness audit. */
|
||||
struct VOXELFORGE_API FVoxelStrateFieldExclusion
|
||||
{
|
||||
FString FieldName;
|
||||
@@ -137,6 +137,10 @@ struct VOXELFORGE_API FVoxelStrateFieldSpread
|
||||
bool bExcluded = false;
|
||||
bool bReflected = false;
|
||||
|
||||
// Actual authored corpus samples and synthetic per-operation default samples are kept
|
||||
// separate in the report. SampleCount is their effective total used for the statistics.
|
||||
int32 AuthoredSampleCount = 0;
|
||||
int32 DefaultSeedCount = 0;
|
||||
int32 SampleCount = 0;
|
||||
double Min = 0.0;
|
||||
double Max = 0.0;
|
||||
@@ -246,10 +250,10 @@ public:
|
||||
ECaveGeneratorType Archetype, const FString& FieldName) const;
|
||||
const FVoxelStrateFieldSpread* FindSpread(const FString& FieldName) const;
|
||||
|
||||
/** Stable hash of source paths, archetypes, weights, and every listed scalar field. */
|
||||
/** Stable hash of source paths, archetypes, weights, and every listed scalar corpus field. */
|
||||
uint32 GetContentsHash() const;
|
||||
|
||||
/** Explicit exclusion list discovered from the struct reflection/use audit. */
|
||||
/** Explicit exclusion list retained after the fixed-lattice terrain-detail liveness audit. */
|
||||
static const TArray<FVoxelStrateFieldExclusion>& GetNonTunableFields();
|
||||
|
||||
/** True in an editor build where UPROPERTY metadata was available while spreads were built. */
|
||||
|
||||
Reference in new issue
Block a user