feat(composer): it invents strates that work -- 59/64, and §6.2 is CLOSED

The corpus was starved: the loader followed the active settings pool and
found ONE vector, so all 64 candidates were bit-identical and every number
was a placeholder. It now loads every UVoxelStrateDefinition via the Asset
Registry (Saved/Autosaves and Saved/Cooked excluded -- including them would
silently weight the corpus toward whatever was autosaved last) plus the
per-archetype defaults from VoxelStrateTypes.h, which are themselves
hand-tuned known-good vectors. Blending stays WITHIN an archetype;
different archetypes read different subsets of the 73 fields.

⭐ THE COMPOSER WORKS. 64 candidates blended from real parents, every one
measured:

  survival 59/64 (92.2%)
  non-vacuous 60 · largest-share >= .50  60 · primordial law 63 · roll
  failures 0

and they are genuinely different worlds, not one world 64 times:
  air fraction   .0137 -> 1.000
  walkable       .000  -> .389
  feature scale  4     -> 376 voxels
  archetypes     TunnelNetwork, VerticalShafts, FlatPlain, CrystalChamber,
                 SurfaceWorld, Underwater, FloatingIslands, Maze

⭐⭐ §6.2 IS CLOSED, and it was the real risk. Box-verdict bounds were only
ever proved against the parameters Jahni typed; the whole worry was that
invented parameters would violate a bound nobody had checked -- silently,
leaving no geometry and no collision. Against ROLLED parameters:

  1,224 boxes proved (549 AllSolid, 675 AllAir), 1,336 Mixed
  1,629,144 voxels brute-forced
  0 VIOLATIONS

The bounds hold across the rolled range, not just at authored points. The
isolation work in 8e346a3 is what made that true.

The 5 rejections are a real finding, not noise: FloatingIslands can jitter
its islands out of existence and return an all-air strate (air 1.000,
walkable 0.000, feature scale 0). That is exactly what reject-and-resample
(§3.5) is for -- the measurement caught it without anyone anticipating it,
which is the whole argument for measuring rather than trusting.

Roll+measure 112.6 s for 64 candidates. §3.1's budget is "runs overnight",
so there is room for thousands.

Build clean, 23 succeeded / 0 warnings / 0 failed (the deliberate vacuity
warning is gone because the check is no longer vacuous). 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 08:32:01 +02:00
1 parent 358bd415a9
commit 190ff381c0
5 files changed
+1518 -264

No files matched your search

@@ -4,10 +4,15 @@
#include "Misc/AutomationTest.h"
#include "HAL/PlatformTime.h"
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Modules/ModuleManager.h"
#include "VoxelForgeTestFixture.h"
#include "VoxelSettings.h"
#include "VoxelStrateComposer.h"
#include "VoxelStrateMeasure.h"
#include "VoxelTerrainOpDefinition.h"
namespace
{
@@ -53,6 +58,124 @@ namespace
*VF_FormatSpreadValue(Spread.ClampMax));
}
bool VF_IsProjectPackagePath(const FString& PackagePath)
{
return PackagePath == TEXT("/Game")
|| PackagePath.StartsWith(TEXT("/Game/"), ESearchCase::IgnoreCase);
}
bool VF_IsIgnoredSavedCopy(const FString& PackagePath)
{
return PackagePath.Contains(TEXT("/Saved/Autosaves"), ESearchCase::IgnoreCase)
|| PackagePath.Contains(TEXT("/Saved/Cooked"), ESearchCase::IgnoreCase);
}
TArray<FString> VF_GetProjectAssetPaths(UClass* AssetClass)
{
TArray<FString> Paths;
if (AssetClass == nullptr)
{
return Paths;
}
FAssetRegistryModule& AssetRegistryModule =
FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
TArray<FAssetData> Assets;
AssetRegistryModule.Get().GetAssetsByClass(AssetClass->GetClassPathName(), Assets, true);
TSet<FString> SeenPaths;
for (const FAssetData& Asset : Assets)
{
const FString PackagePath = Asset.PackageName.ToString();
const FString ObjectPath = Asset.GetObjectPathString();
if (!VF_IsProjectPackagePath(PackagePath)
|| VF_IsIgnoredSavedCopy(PackagePath)
|| VF_IsIgnoredSavedCopy(ObjectPath)
|| SeenPaths.Contains(ObjectPath))
{
continue;
}
SeenPaths.Add(ObjectPath);
Paths.Add(ObjectPath);
}
Paths.Sort();
return Paths;
}
FString VF_FormatSettingsAudit(const UVoxelSettings* Settings)
{
TArray<FString> Paths;
if (Settings != nullptr)
{
TSet<FString> UniquePaths;
for (const TPair<int32, TSoftObjectPtr<UVoxelStrateDefinition>>& Pair : Settings->FixedStrates)
{
const FString Path = Pair.Value.ToSoftObjectPath().ToString();
if (!Path.IsEmpty())
{
UniquePaths.Add(Path);
}
}
for (const TSoftObjectPtr<UVoxelStrateDefinition>& Reference : Settings->StratePool)
{
const FString Path = Reference.ToSoftObjectPath().ToString();
if (!Path.IsEmpty())
{
UniquePaths.Add(Path);
}
}
for (const FString& Path : UniquePaths)
{
Paths.Add(Path);
}
Paths.Sort();
}
FString Result = FString::Printf(TEXT("Settings corpus audit: %d unique fixed/pool paths"),
Paths.Num());
for (const FString& Path : Paths)
{
Result += FString::Printf(TEXT(" [%s]"), *Path);
}
Result += TEXT("; these settings references are not the corpus source.");
return Result;
}
bool VF_RollRuntimeZIsZero(const FVoxelStrateRollInfo& Roll)
{
switch (Roll.Archetype)
{
case ECaveGeneratorType::TunnelNetwork:
case ECaveGeneratorType::Underwater:
return Roll.ArchetypeParams.TunnelNetworkParams.StrateTopWorldZ == 0.0f
&& Roll.ArchetypeParams.TunnelNetworkParams.StrateBottomWorldZ == 0.0f;
case ECaveGeneratorType::FlatPlain:
case ECaveGeneratorType::CrystalChamber:
return Roll.ArchetypeParams.SlabParams.StrateTopWorldZ == 0.0f
&& Roll.ArchetypeParams.SlabParams.StrateBottomWorldZ == 0.0f;
case ECaveGeneratorType::Maze:
return Roll.ArchetypeParams.MazeParams.StrateTopWorldZ == 0.0f
&& Roll.ArchetypeParams.MazeParams.StrateBottomWorldZ == 0.0f;
case ECaveGeneratorType::SurfaceWorld:
return Roll.ArchetypeParams.SurfaceParams.StrateTopWorldZ == 0.0f
&& Roll.ArchetypeParams.SurfaceParams.StrateBottomWorldZ == 0.0f;
case ECaveGeneratorType::VerticalShafts:
return Roll.ArchetypeParams.VerticalShaftParams.StrateTopWorldZ == 0.0f
&& Roll.ArchetypeParams.VerticalShaftParams.StrateBottomWorldZ == 0.0f;
case ECaveGeneratorType::FloatingIslands:
return Roll.ArchetypeParams.FloatingIslandParams.StrateTopWorldZ == 0.0f
&& Roll.ArchetypeParams.FloatingIslandParams.StrateBottomWorldZ == 0.0f;
default:
return false;
}
}
bool VF_IsComposerDefault(const FVoxelStrateCorpusEntry& Entry)
{
return Entry.SourcePath.StartsWith(TEXT("/VoxelForge/ComposerDefaults/"),
ESearchCase::IgnoreCase);
}
FString VF_FormatParents(const FVoxelStrateCorpus& Corpus,
const FVoxelStrateRollInfo& Roll)
{
@@ -233,21 +356,81 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
FVoxelStrateCorpus Corpus;
FString CorpusReport;
const double CorpusStartSeconds = FPlatformTime::Seconds();
const bool bCorpusLoaded = Corpus.LoadFromSettings(AuthoredSettings, CorpusReport);
const bool bCorpusLoaded = Corpus.LoadFromAssetRegistry(CorpusReport);
const double CorpusSeconds = FPlatformTime::Seconds() - CorpusStartSeconds;
AddInfo(FString::Printf(TEXT("%s Load time %.3fs; contents hash 0x%08x; clamp metadata at "
"build=%s, promised at cooked runtime=%s."),
*CorpusReport, CorpusSeconds, Corpus.GetContentsHash(),
Corpus.AreClampMetadataAvailableAtBuild() ? TEXT("yes") : TEXT("no"),
Corpus.AreClampMetadataAvailableAtRuntime() ? TEXT("yes") : TEXT("no")));
AddInfo(VF_FormatSettingsAudit(AuthoredSettings));
const TArray<FString> ProjectStrateAssets =
VF_GetProjectAssetPaths(UVoxelStrateDefinition::StaticClass());
const TArray<FString> ProjectTerrainOpAssets =
VF_GetProjectAssetPaths(UVoxelTerrainOpDefinition::StaticClass());
int32 NumProjectCorpusEntries = 0;
int32 NumDefaultCorpusEntries = 0;
FString CorpusMembership = TEXT("CORPUS MEMBERSHIP\n");
for (const FVoxelStrateCorpusEntry& Entry : Corpus.GetEntries())
{
if (VF_IsComposerDefault(Entry))
{
++NumDefaultCorpusEntries;
}
else
{
++NumProjectCorpusEntries;
}
CorpusMembership += FString::Printf(TEXT("%s | %s | %s\n"),
VF_IsComposerDefault(Entry) ? TEXT("default") : TEXT("project"),
VF_GetStrateArchetypeName(Entry.Archetype),
*Entry.SourcePath);
}
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."),
ProjectStrateAssets.Num(), NumProjectCorpusEntries, NumDefaultCorpusEntries, Corpus.Num(),
ProjectTerrainOpAssets.Num()));
AddInfo(CorpusMembership);
for (const ECaveGeneratorType Archetype : {
ECaveGeneratorType::TunnelNetwork,
ECaveGeneratorType::FlatPlain,
ECaveGeneratorType::CrystalChamber,
ECaveGeneratorType::Maze,
ECaveGeneratorType::SurfaceWorld,
ECaveGeneratorType::VerticalShafts,
ECaveGeneratorType::FloatingIslands,
ECaveGeneratorType::Underwater})
{
int32 NumDefaultsForArchetype = 0;
for (const FVoxelStrateCorpusEntry& Entry : Corpus.GetEntries())
{
if (Entry.Archetype == Archetype && VF_IsComposerDefault(Entry))
{
++NumDefaultsForArchetype;
}
}
TestEqual(FString::Printf(TEXT("one hand-tuned default for %s"),
VF_GetStrateArchetypeName(Archetype)),
NumDefaultsForArchetype, 1);
TestTrue(FString::Printf(TEXT("%s has a same-archetype corpus group"),
VF_GetStrateArchetypeName(Archetype)),
Corpus.NumForArchetype(Archetype) > 0);
}
TestEqual(TEXT("all project strate assets plus eight defaults are corpus members"),
Corpus.Num(), ProjectStrateAssets.Num() + 8);
FString SpreadTable = TEXT(
"CORPUS SPREAD (population stddev; jitter range = ±15% of max-min; clamps are safety only)\n"
"field | kind | excluded | reflected | samples | min | max | mean | stddev | clamp\n");
"archetype | struct | field | kind | excluded | reflected | samples | min | max | mean | stddev | clamp\n");
for (const FVoxelStrateFieldSpread& Spread : Corpus.GetFieldSpreads())
{
SpreadTable += FString::Printf(
TEXT("%s | %s | %s | %s | %d | %s | %s | %s | %s | %s\n"),
TEXT("%s | %s | %s | %s | %s | %s | %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"),
@@ -270,9 +453,10 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
AddInfo(ExclusionTable);
AddInfo(TEXT(
"Bool policy: bTunnelsFlowTowardOrigin is inherited from the dominant weighted parent; "
"int/enum SNAP fields use FStrateGenerationParams::Lerp and are not jittered. Parent "
"selection is weight-1.0 (equal) because UVoxelStrateDefinition has no corpus-weight field; "
"parents are selected only within one archetype group."));
"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."));
TestTrue(TEXT("the reflected parameter schema is covered or explicitly excluded"),
Corpus.IsSchemaValid());
@@ -281,7 +465,7 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
{
AddError(FString::Printf(
TEXT("Cannot run the 64-candidate measurement: %s"),
Corpus.IsSchemaValid() ? TEXT("the settings asset exposed no TunnelNetwork/Underwater GenerationParams")
Corpus.IsSchemaValid() ? TEXT("the Asset Registry exposed no usable strate vectors")
: *Corpus.GetSchemaError()));
return false;
}
@@ -326,6 +510,7 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
int64 TotalBoxCheckedVoxels = 0;
int32 TotalBoxViolations = 0;
TArray<int32> BoxViolationCandidates;
FString FirstBoxViolation;
const double RollAndMeasureStartSeconds = FPlatformTime::Seconds();
for (int32 CandidateIndex = 0; CandidateIndex < NumCandidates; ++CandidateIndex)
@@ -349,23 +534,47 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
continue;
}
bool bParentsMatchArchetype = true;
for (const int32 ParentIndex : Roll.ParentEntryIndices)
{
if (!Corpus.GetEntries().IsValidIndex(ParentIndex)
|| Corpus.GetEntries()[ParentIndex].Archetype != Roll.Archetype)
{
bParentsMatchArchetype = false;
break;
}
}
TestTrue(FString::Printf(TEXT("candidate %d parents stay within %s"),
CandidateIndex, VF_GetStrateArchetypeName(Roll.Archetype)),
bParentsMatchArchetype);
const FVoxelStrateRollInfo RepeatRoll = VF_RollStrateParamsDetailed(
Corpus, AuthoredSettings->Seed, CandidateIndex);
TestTrue(FString::Printf(TEXT("candidate %d is bit-identical on deterministic reroll"),
CandidateIndex),
RepeatRoll.bValid && RepeatRoll.Archetype == Roll.Archetype
&& VF_AreStrateArchetypeParamsBitIdentical(
RepeatRoll.ArchetypeParams, Roll.ArchetypeParams, Roll.Archetype)
&& RepeatRoll.ParentEntryIndices == Roll.ParentEntryIndices
&& RepeatRoll.ParentWeights == Roll.ParentWeights);
// Put the invented vector into a transient fixture definition. This is an offline test
// world only; the runtime manager/generation code and authored assets are untouched.
UVoxelStrateDefinition* CandidateDefinition =
World.Definitions[CandidateStrateIndex].Get();
CandidateDefinition->GeneratorType = Roll.Archetype;
CandidateDefinition->GenerationParams = Roll.Params;
CandidateDefinition->GenerationParams = Roll.ArchetypeParams.TunnelNetworkParams;
CandidateDefinition->SlabParams = Roll.ArchetypeParams.SlabParams;
CandidateDefinition->MazeParams = Roll.ArchetypeParams.MazeParams;
CandidateDefinition->SurfaceParams = Roll.ArchetypeParams.SurfaceParams;
CandidateDefinition->VerticalShaftParams = Roll.ArchetypeParams.VerticalShaftParams;
CandidateDefinition->FloatingIslandParams = Roll.ArchetypeParams.FloatingIslandParams;
CandidateDefinition->bUseOperatorStack = true;
CandidateDefinition->TransitionType = EVoxelStrateTransition::Hard;
World.Reinitialize();
const FStrateGenerationParams RuntimeParams = World.StrateManager->GetGenerationParams(
FIntVector(0, 0,
World.StrateManager->GetLayout()[CandidateStrateIndex].BottomChunkZ
+ (World.StrateManager->GetLayout()[CandidateStrateIndex].TopChunkZ
- World.StrateManager->GetLayout()[CandidateStrateIndex].BottomChunkZ) / 2));
TestTrue(FString::Printf(TEXT("candidate %d keeps runtime Z out of the rolled vector"), CandidateIndex),
Roll.Params.StrateTopWorldZ == 0.0f && Roll.Params.StrateBottomWorldZ == 0.0f);
VF_RollRuntimeZIsZero(Roll));
const FVoxelStrateMetrics Metrics = VF_MeasureStrate(
*World.Generator, *World.StrateManager, CandidateStrateIndex, MeasureSettings);
@@ -438,6 +647,10 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
if (BoxReport.Violations > 0)
{
BoxViolationCandidates.Add(CandidateIndex);
if (FirstBoxViolation.IsEmpty())
{
FirstBoxViolation = BoxReport.FirstViolation;
}
}
CandidateTable += FString::Printf(
@@ -481,8 +694,8 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
AddError(FString::Printf(
TEXT("ROLLED BOX-VERDICT VIOLATION: %d candidate(s) produced a false AllSolid/AllAir "
"claim; first candidate index %d. This closes the test red because §6.2 bounds "
"were not sound for the rolled parameter space."),
BoxViolationCandidates.Num(), BoxViolationCandidates[0]));
"were not sound for the rolled parameter space. First violation: %s"),
BoxViolationCandidates.Num(), BoxViolationCandidates[0], *FirstBoxViolation));
}
if (TotalBoxProved == 0)
{
@@ -504,4 +717,3 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
}
#endif // WITH_DEV_AUTOMATION_TESTS
File diff suppressed because it is too large. Load diff
+47 -6
View File
@@ -28,9 +28,11 @@ struct VOXELFORGE_API FVoxelStrateFieldExclusion
FString Reason;
};
/** Measured statistics for one field across the currently loaded corpus. */
/** Measured statistics for one field in one archetype's corpus group. */
struct VOXELFORGE_API FVoxelStrateFieldSpread
{
ECaveGeneratorType Archetype = ECaveGeneratorType::TunnelNetwork;
FString ParamStructName;
FString FieldName;
EVoxelStrateFieldKind Kind = EVoxelStrateFieldKind::Continuous;
bool bExcluded = false;
@@ -50,12 +52,30 @@ struct VOXELFORGE_API FVoxelStrateFieldSpread
double ClampMax = 0.0;
};
/** Native parameter storage for every currently supported strate archetype. */
struct VOXELFORGE_API FVoxelStrateArchetypeParams
{
FStrateGenerationParams TunnelNetworkParams;
FSlabGenerationParams SlabParams;
FMazeGenerationParams MazeParams;
FSurfaceGenerationParams SurfaceParams;
FVerticalShaftParams VerticalShaftParams;
FFloatingIslandParams FloatingIslandParams;
};
/** One known-good authored vector and the archetype that gives it meaning. */
struct VOXELFORGE_API FVoxelStrateCorpusEntry
{
FString SourcePath;
FString SourceName;
ECaveGeneratorType Archetype = ECaveGeneratorType::TunnelNetwork;
// Native storage is selected by Archetype. Keeping every family here makes the corpus
// type-safe without changing any runtime generation parameter struct.
FVoxelStrateArchetypeParams ArchetypeParams;
// Compatibility view for callers of the original Tier 4a API. It is meaningful for
// TunnelNetwork/Underwater entries and is the same value as ArchetypeParams.TunnelNetworkParams.
FStrateGenerationParams Params;
// The current asset format has no corpus weight field. Loaded hand-authored entries therefore
@@ -71,6 +91,10 @@ struct VOXELFORGE_API FVoxelStrateRollInfo
FString FailureReason;
ECaveGeneratorType Archetype = ECaveGeneratorType::TunnelNetwork;
FVoxelStrateArchetypeParams ArchetypeParams;
// Compatibility view for the original tunnel-only API. For a non-tunnel roll, the native
// result is in ArchetypeParams and this member is not the active candidate vector.
FStrateGenerationParams Params;
TArray<int32> ParentEntryIndices;
@@ -81,17 +105,22 @@ struct VOXELFORGE_API FVoxelStrateRollInfo
/**
* Known-good strate vectors plus their measured field spread.
*
* `LoadFromSettings` walks both the pool and fixed-strate references, resolves each definition,
* de-duplicates it by asset path, and admits only TunnelNetwork/Underwater definitions because
* those are the two archetypes whose authored vector is `GenerationParams`. Other archetypes have
* different parameter structs; treating their default `GenerationParams` as authored data would
* manufacture a false corpus.
* `LoadFromAssetRegistry` enumerates every project UVoxelStrateDefinition through the Asset
* Registry, explicitly excluding Saved/Autosaves and Saved/Cooked copies. It also adds one
* known-good default vector for every archetype/family in VoxelStrateTypes.h. Parents are selected
* only inside the exact archetype group; no cross-archetype blend is attempted.
*/
class VOXELFORGE_API FVoxelStrateCorpus
{
public:
void Reset();
bool LoadFromAssetRegistry(FString& OutReport);
/**
* Compatibility alias retained for callers compiled against the first Tier 4a pass. The
* settings argument is audited only for diagnostics; it is not a corpus input.
*/
bool LoadFromSettings(const UVoxelSettings* Settings, FString& OutReport);
bool LoadFromDefinitions(const TArray<UVoxelStrateDefinition*>& Definitions, FString& OutReport);
@@ -99,6 +128,9 @@ public:
bool AddEntry(const FString& SourcePath, const FString& SourceName,
ECaveGeneratorType Archetype, const FStrateGenerationParams& Params,
float Weight = 1.0f);
bool AddEntry(const FString& SourcePath, const FString& SourceName,
ECaveGeneratorType Archetype, const FVoxelStrateArchetypeParams& Params,
float Weight = 1.0f);
bool IsValid() const { return bSchemaValid && Entries.Num() > 0; }
bool IsSchemaValid() const { return bSchemaValid; }
@@ -109,6 +141,10 @@ public:
const TArray<FVoxelStrateFieldSpread>& GetFieldSpreads() const { return FieldSpreads; }
const TArray<FString>& GetSkippedDefinitions() const { return SkippedDefinitions; }
int32 NumForArchetype(ECaveGeneratorType Archetype) const;
const FVoxelStrateFieldSpread* FindSpread(
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. */
@@ -149,3 +185,8 @@ VOXELFORGE_API FStrateGenerationParams VF_RollStrateParams(
VOXELFORGE_API bool VF_AreStrateParamsBitIdentical(
const FStrateGenerationParams& A, const FStrateGenerationParams& B);
/** Bitwise comparison of the native vector for one archetype family. */
VOXELFORGE_API bool VF_AreStrateArchetypeParamsBitIdentical(
const FVoxelStrateArchetypeParams& A,
const FVoxelStrateArchetypeParams& B,
ECaveGeneratorType Archetype);