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:
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
@@ -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);
|
||||
Reference in new issue
Block a user