feat(composer): Tier 4c -- a season that can actually ship, with a veto page
The composer could invent, measure and show strates. What did not exist was an ARTEFACT a season could ship. Now it does. VF_ComposeSeason: generate N candidates -> measure -> reject failures -> select a descent sequence -> emit a JSON manifest + a review page. ⭐ THE PROPERTY THAT MATTERS: round-trip bit-identity. The manifest was loaded, every selected stack rebuilt, and 393,216 density samples compared BIT-IDENTICALLY. Re-composing the same seed yields byte-identical JSON. A manifest that cannot reproduce its own world is worthless, and that is the one thing that could not be assumed. Floats serialise as readable values PLUS exact IEEE bits, so a season survives a text round trip. Test season: 24 generated, 19 survived the hard gates, 6 selected. 18 not selected -- 4 vacuous, 1 primordial-law failure, 13 policy non-selections. 4 grounded, 2 outliers. EVERY selected slot passes the primordial law, asserted per slot rather than sampled. Selection policy is explicit, named and PROVISIONAL, because Jahni has not defined "good" yet and said so: "it'll be easier once i'm able to see the strates candidates." So no quality score was invented and presented as settled. The knobs are 75/25 grounded-to-outlier (outlier axis = distance from corpus centroid), adjacent-similarity threshold .20 with penalty 1.0, new-archetype bonus .10, new-recipe bonus .05, boss cadence every 5th non-terminal slot. All of them are his to turn. The veto is a review page in DESCENT ORDER -- the season read top to bottom the way it is played, not the survivor/outlier ordering the candidate sheet uses. Reuses VoxelStratePreview rather than a second renderer. Saved/VoxelForge/SeasonTest/season_0000_seed_9320/season_manifest.json .../review/index.html ⚠️ FIVE GAPS CODEX RAISED, recorded not buried: 1. §4's Tier A is strates PLUS passages; only the strate spine is persisted. Manager-generated passage geometry and content placement are not. 2. Authored fixed strates keep scalar vectors + asset paths, not every asset-level passage/biome/disturbance/content field. 3. Boss slots are MARKERS, not arena or content guarantees. 4. WorldRadiusVoxels == 0 is enforced as specced but contradicts a production bounded world -- Jahni's call, still unset by design. 5. Bit identity is proven for the same build and platform. A frozen season spanning engine versions needs an explicit schema contract. Real risk, since seasons freeze generation for months. Build clean, 28 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
0b3ec18b43
commit
42e1f0ec8c
8 files changed
+3351
-5
No files matched your search
@@ -0,0 +1,208 @@
|
||||
// Tier 4c — offline season composition, manifest determinism, and exact rebuild.
|
||||
|
||||
#if WITH_DEV_AUTOMATION_TESTS && WITH_EDITOR
|
||||
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "Misc/Paths.h"
|
||||
|
||||
#include "VoxelDensityOpStack.h"
|
||||
#include "VoxelSeasonManifest.h"
|
||||
#include "VoxelStrateComposer.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool SameDensityBits(float A, float B)
|
||||
{
|
||||
return FMemory::Memcmp(&A, &B, sizeof(float)) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FVoxelForgeComposerSeasonTest,
|
||||
"VoxelForge.Composer.Season",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
|
||||
|
||||
bool FVoxelForgeComposerSeasonTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
(void)Parameters;
|
||||
|
||||
FVoxelStrateCorpus Corpus;
|
||||
FString CorpusReport;
|
||||
TestTrue(TEXT("season test loads a valid corpus"),
|
||||
Corpus.LoadFromAssetRegistry(CorpusReport) && Corpus.IsValid());
|
||||
if (!Corpus.IsValid())
|
||||
{
|
||||
AddError(CorpusReport);
|
||||
return false;
|
||||
}
|
||||
|
||||
FVoxelSeasonCompositionSettings Settings;
|
||||
Settings.Corpus = &Corpus;
|
||||
Settings.CandidateCount = 24;
|
||||
Settings.SelectedStrateCount = 6;
|
||||
Settings.StrateHeightInChunks = 4;
|
||||
Settings.InterStrateGapChunks = 0;
|
||||
Settings.OriginSpineRadius = 14.0f;
|
||||
Settings.WorldRadiusVoxels = 0.0f;
|
||||
Settings.OutputDirectory = FPaths::Combine(
|
||||
FPaths::ProjectSavedDir(), TEXT("VoxelForge"), TEXT("SeasonTest"));
|
||||
Settings.bWriteArtifacts = true;
|
||||
Settings.bWriteReviewPage = true;
|
||||
Settings.bIncludeCandidateAudit = true;
|
||||
|
||||
const int32 SeasonSeed = 0x2468;
|
||||
const FVoxelSeasonManifest Manifest = VF_ComposeSeason(SeasonSeed, Settings);
|
||||
TestTrue(TEXT("composition produces a usable season manifest"), Manifest.IsUsable());
|
||||
if (!Manifest.IsUsable())
|
||||
{
|
||||
AddError(Manifest.Error);
|
||||
return false;
|
||||
}
|
||||
TestEqual(TEXT("candidate count is the configured number of attempts"),
|
||||
Manifest.CandidateCount, Settings.CandidateCount);
|
||||
TestEqual(TEXT("selected count is the configured spine length"),
|
||||
Manifest.SelectedCount, Settings.SelectedStrateCount);
|
||||
TestEqual(TEXT("candidate audit contains every attempt"),
|
||||
Manifest.CandidateAudit.Num(), Settings.CandidateCount);
|
||||
TestEqual(TEXT("WorldRadiusVoxels remains zero in the artifact"),
|
||||
Manifest.WorldRadiusVoxels, 0.0f);
|
||||
TestEqual(TEXT("selected grounded/outlier counts add to generated slots"),
|
||||
Manifest.GroundedSelectedCount + Manifest.OutlierSelectedCount,
|
||||
Manifest.SelectedCount - Manifest.FixedSelectedCount);
|
||||
|
||||
for (int32 Index = 0; Index < Manifest.Strates.Num(); ++Index)
|
||||
{
|
||||
const FVoxelSeasonStrate& Strate = Manifest.Strates[Index];
|
||||
TestEqual(FString::Printf(TEXT("slot %d keeps descent order"), Index),
|
||||
Strate.DepthIndex, Index);
|
||||
TestTrue(FString::Printf(TEXT("slot %d passed non-vacuous gate"), Index),
|
||||
Strate.bPassedNonVacuous);
|
||||
TestTrue(FString::Printf(TEXT("slot %d passed largest-component gate"), Index),
|
||||
Strate.bPassedLargestComponent);
|
||||
TestTrue(FString::Printf(TEXT("slot %d passed primordial law"), Index),
|
||||
Strate.bPassedPrimordialLaw
|
||||
&& Strate.PrimordialLawResult == EVoxelConnectivityResult::Connected);
|
||||
TestTrue(FString::Printf(TEXT("slot %d has zero world radius"), Index),
|
||||
Manifest.WorldRadiusVoxels == 0.0f);
|
||||
}
|
||||
|
||||
TestTrue(TEXT("season manifest JSON was emitted"), !Manifest.SerializedJson.IsEmpty());
|
||||
TestTrue(TEXT("season manifest file was emitted"), FPaths::FileExists(Manifest.ManifestPath));
|
||||
TestTrue(TEXT("selected-season review page was emitted"),
|
||||
FPaths::FileExists(Manifest.ReviewPagePath));
|
||||
FString ReviewHtml;
|
||||
if (FFileHelper::LoadFileToString(ReviewHtml, *Manifest.ReviewPagePath))
|
||||
{
|
||||
TestTrue(TEXT("review page explains why each selected slot was chosen"),
|
||||
ReviewHtml.Contains(TEXT("Why selected:")));
|
||||
TestTrue(TEXT("review page marks boss slots"), ReviewHtml.Contains(TEXT("Boss slot:")));
|
||||
}
|
||||
else
|
||||
{
|
||||
AddError(FString::Printf(TEXT("could not read review page %s"), *Manifest.ReviewPagePath));
|
||||
}
|
||||
|
||||
// Compose again with the same inputs. SerializedJson excludes derived filesystem paths, so
|
||||
// this is the byte-level determinism assertion for the portable artifact itself.
|
||||
const FVoxelSeasonManifest RepeatManifest = VF_ComposeSeason(SeasonSeed, Settings);
|
||||
TestTrue(TEXT("repeat composition is usable"), RepeatManifest.IsUsable());
|
||||
TestEqual(TEXT("same season seed produces byte-identical JSON"),
|
||||
RepeatManifest.SerializedJson, Manifest.SerializedJson);
|
||||
|
||||
FVoxelSeasonManifest LoadedManifest;
|
||||
FString LoadReport;
|
||||
TestTrue(TEXT("manifest loads from its JSON file"),
|
||||
VF_LoadVoxelSeasonManifest(Manifest.ManifestPath, LoadedManifest, LoadReport));
|
||||
if (!LoadedManifest.IsUsable())
|
||||
{
|
||||
AddError(LoadReport);
|
||||
return false;
|
||||
}
|
||||
TestEqual(TEXT("loaded manifest has the same number of selected strates"),
|
||||
LoadedManifest.Strates.Num(), Manifest.Strates.Num());
|
||||
|
||||
int64 ComparedDensitySamples = 0;
|
||||
bool bAllDensityBitsIdentical = true;
|
||||
for (int32 Index = 0; Index < Manifest.Strates.Num(); ++Index)
|
||||
{
|
||||
const FVoxelSeasonStrate& OriginalStrate = Manifest.Strates[Index];
|
||||
const FVoxelSeasonStrate& LoadedStrate = LoadedManifest.Strates[Index];
|
||||
TestTrue(FString::Printf(TEXT("slot %d parameter vector survives JSON bit-identically"), Index),
|
||||
VF_AreStrateArchetypeParamsBitIdentical(
|
||||
OriginalStrate.Params, LoadedStrate.Params, OriginalStrate.Archetype));
|
||||
TestTrue(FString::Printf(TEXT("slot %d recipe survives JSON identically"), Index),
|
||||
VF_AreStrateStructureRecipesIdentical(
|
||||
OriginalStrate.Recipe, LoadedStrate.Recipe));
|
||||
|
||||
FVoxelOpStack OriginalStack;
|
||||
FVoxelOpContext OriginalContext;
|
||||
FVoxelOpStack LoadedStack;
|
||||
FVoxelOpContext LoadedContext;
|
||||
FString BuildError;
|
||||
TestTrue(FString::Printf(TEXT("slot %d original stack rebuilds"), Index),
|
||||
VF_RebuildVoxelSeasonStrate(
|
||||
OriginalStrate, Manifest.OriginSpineRadius, nullptr,
|
||||
OriginalStack, OriginalContext, &BuildError));
|
||||
TestTrue(FString::Printf(TEXT("slot %d loaded stack rebuilds"), Index),
|
||||
VF_RebuildVoxelSeasonStrate(
|
||||
LoadedStrate, LoadedManifest.OriginSpineRadius, nullptr,
|
||||
LoadedStack, LoadedContext, &BuildError));
|
||||
if (OriginalStack.Num() == 0 || LoadedStack.Num() == 0)
|
||||
{
|
||||
bAllDensityBitsIdentical = false;
|
||||
continue;
|
||||
}
|
||||
TestEqual(FString::Printf(TEXT("slot %d original/rebuilt stack operation count"), Index),
|
||||
LoadedStack.Num(), OriginalStack.Num());
|
||||
TestEqual(FString::Printf(TEXT("slot %d original context radius"), Index),
|
||||
OriginalContext.WorldRadiusVoxels, 0.0f);
|
||||
TestEqual(FString::Printf(TEXT("slot %d loaded context radius"), Index),
|
||||
LoadedContext.WorldRadiusVoxels, 0.0f);
|
||||
OriginalStack.PrepareChunk(OriginalContext);
|
||||
LoadedStack.PrepareChunk(LoadedContext);
|
||||
|
||||
// Compare the complete bounded probe lattice for each selected field at two-voxel
|
||||
// spacing, not a handful of favorable points. Every sampled scalar must retain its
|
||||
// exact IEEE-754 bits after JSON load and stack reconstruction.
|
||||
constexpr int32 ProbeHalfExtent = 32;
|
||||
constexpr int32 ProbeStep = 2;
|
||||
for (int32 Z = OriginalStrate.BottomWorldZ;
|
||||
Z < OriginalStrate.TopWorldZ && bAllDensityBitsIdentical;
|
||||
Z += ProbeStep)
|
||||
{
|
||||
for (int32 Y = -ProbeHalfExtent;
|
||||
Y < ProbeHalfExtent && bAllDensityBitsIdentical;
|
||||
Y += ProbeStep)
|
||||
{
|
||||
for (int32 X = -ProbeHalfExtent;
|
||||
X < ProbeHalfExtent;
|
||||
X += ProbeStep)
|
||||
{
|
||||
const float OriginalDensity = OriginalStack.EvalMC(
|
||||
static_cast<float>(X), static_cast<float>(Y), static_cast<float>(Z));
|
||||
const float LoadedDensity = LoadedStack.EvalMC(
|
||||
static_cast<float>(X), static_cast<float>(Y), static_cast<float>(Z));
|
||||
++ComparedDensitySamples;
|
||||
if (!SameDensityBits(OriginalDensity, LoadedDensity))
|
||||
{
|
||||
bAllDensityBitsIdentical = false;
|
||||
AddError(FString::Printf(
|
||||
TEXT("slot %d density mismatch at (%d,%d,%d): 0x%08x vs 0x%08x"),
|
||||
Index, X, Y, Z,
|
||||
*reinterpret_cast<const uint32*>(&OriginalDensity),
|
||||
*reinterpret_cast<const uint32*>(&LoadedDensity)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AddInfo(FString::Printf(TEXT("round-trip density probe compared %lld bit-identical samples"),
|
||||
ComparedDensitySamples));
|
||||
TestTrue(TEXT("manifest round-trip density field is bit-identical"),
|
||||
bAllDensityBitsIdentical);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -979,6 +979,18 @@ bool VF_WriteStratePreviewIndex(
|
||||
SortedCandidates.Sort([](const FVoxelStratePreviewCandidate& A,
|
||||
const FVoxelStratePreviewCandidate& B)
|
||||
{
|
||||
if (A.bSeasonOrder != B.bSeasonOrder)
|
||||
{
|
||||
return A.bSeasonOrder;
|
||||
}
|
||||
if (A.bSeasonOrder && B.bSeasonOrder)
|
||||
{
|
||||
if (A.DepthIndex != B.DepthIndex)
|
||||
{
|
||||
return A.DepthIndex < B.DepthIndex;
|
||||
}
|
||||
return A.CandidateIndex < B.CandidateIndex;
|
||||
}
|
||||
if (A.bRejected != B.bRejected)
|
||||
{
|
||||
return !A.bRejected;
|
||||
@@ -1071,11 +1083,23 @@ bool VF_WriteStratePreviewIndex(
|
||||
const FString CardClass = Candidate.bRejected ? TEXT("candidate rejected") : TEXT("candidate");
|
||||
const FString MetricWindow = Candidate.Window.IsValid()
|
||||
? Candidate.Window.Describe() : WindowDescription;
|
||||
const int32 DisplayIndex = Candidate.bSeasonOrder && Candidate.DepthIndex != INDEX_NONE
|
||||
? Candidate.DepthIndex : Candidate.CandidateIndex;
|
||||
Html += FString::Printf(
|
||||
TEXT("<section class=\"%s\"><h2>#%d <span class=\"badge %s\">%s</span></h2>\n"),
|
||||
*CardClass, Candidate.CandidateIndex, *StatusClass, *Status);
|
||||
*CardClass, DisplayIndex, *StatusClass, *Status);
|
||||
Html += FString::Printf(TEXT("<div class=\"recipe\"><code>%s</code></div>\n"),
|
||||
*VF_HtmlEscape(Candidate.RecipeString));
|
||||
if (Candidate.bSeasonOrder)
|
||||
{
|
||||
Html += FString::Printf(
|
||||
TEXT("<p class=\"reason\" style=\"color:#d7e3f4\"><strong>Why selected:</strong> %s"
|
||||
"<br><strong>Boss slot:</strong> %s</p>\n"),
|
||||
*VF_HtmlEscape(Candidate.SelectionReason.IsEmpty()
|
||||
? TEXT("selected by the provisional season policy")
|
||||
: Candidate.SelectionReason),
|
||||
Candidate.bBossSlot ? TEXT("yes") : TEXT("no"));
|
||||
}
|
||||
if (Candidate.bRejected)
|
||||
{
|
||||
Html += FString::Printf(TEXT("<p class=\"reason\"><strong>Why rejected:</strong> %s</p>\n"),
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
// Offline Tier 4c season composition and the diffable Tier A manifest.
|
||||
//
|
||||
// This header deliberately describes an editor/build-box artifact. The normal runtime path still
|
||||
// receives its existing settings and builds density exactly as before; no runtime code consumes a
|
||||
// season manifest as a hidden generation fallback.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "VoxelStrateComposer.h"
|
||||
#include "VoxelStrateMeasure.h"
|
||||
|
||||
class UVoxelSettings;
|
||||
|
||||
/** Why the provisional season selector put one surviving candidate in a slot. */
|
||||
enum class EVoxelSeasonSelectionReason : uint8
|
||||
{
|
||||
Grounded,
|
||||
Outlier,
|
||||
Fixed,
|
||||
};
|
||||
|
||||
/**
|
||||
* Explicit, reviewable selection knobs for one season.
|
||||
*
|
||||
* None of these values is a settled definition of quality. They are the first policy Jahni can
|
||||
* change after walking the review page. Hard validation remains separate from this policy.
|
||||
*/
|
||||
struct VOXELFORGE_API FVoxelSeasonSelectionPolicy
|
||||
{
|
||||
// Placeholder mix only: ordinary strates give an outlier contrast. Revisit after the first
|
||||
// season review; this is not a learned or final quality score.
|
||||
float GroundedFraction = 0.75f;
|
||||
float OutlierFraction = 0.25f;
|
||||
|
||||
// Below this measured distance two neighbours look too similar. The selector subtracts a
|
||||
// penalty proportional to the gap; provisional because the useful contrast is visual.
|
||||
double AdjacentSimilarityDistanceThreshold = 0.20;
|
||||
double AdjacentSimilarityPenaltyWeight = 1.0;
|
||||
|
||||
// Rewards an unseen archetype/recipe in the already chosen prefix. These are variety
|
||||
// tie-breakers, not claims that one archetype or recipe is better. Provisional review knobs.
|
||||
double NewArchetypeBonus = 0.10;
|
||||
double NewRecipeBonus = 0.05;
|
||||
|
||||
// GDD §6 says roughly every five levels, and a boss is intermediary rather than terminal.
|
||||
// This interval is therefore an explicit provisional layout knob, not a quality score.
|
||||
int32 BossSlotInterval = 5;
|
||||
|
||||
// Tier 2 hard gate: a surviving air component must retain at least this share. Provisional
|
||||
// only in its threshold; the existence of the gate is non-negotiable for a shippable season.
|
||||
float MinimumLargestComponentShare = 0.50f;
|
||||
|
||||
// Normalizers for the two unbounded measured dimensions in the adjacency distance. Fractions
|
||||
// already live in [0,1]. These physical scales are provisional and visible in the manifest.
|
||||
float FeatureScaleNormalizationVoxels = 256.0f;
|
||||
float ClearanceNormalizationVoxels = 64.0f;
|
||||
};
|
||||
|
||||
/** A caller-supplied fixed slot for tests/tools that already have a complete manifest record. */
|
||||
struct VOXELFORGE_API FVoxelSeasonFixedStrate
|
||||
{
|
||||
int32 Seed = 0;
|
||||
int32 HeightInChunks = 4;
|
||||
ECaveGeneratorType Archetype = ECaveGeneratorType::TunnelNetwork;
|
||||
FVoxelStrateArchetypeParams Params;
|
||||
FVoxelOpStackRecipe Recipe;
|
||||
bool bUsesRecipe = true;
|
||||
FString SourceDefinitionPath;
|
||||
};
|
||||
|
||||
/** Settings for the offline build-box pass. No member is consulted by normal runtime generation. */
|
||||
struct VOXELFORGE_API FVoxelSeasonCompositionSettings
|
||||
{
|
||||
// Optional live-world settings. TotalStrates, spine/gap values, and FixedStrates are read
|
||||
// from it when present. The pointer is borrowed for the duration of VF_ComposeSeason only.
|
||||
const UVoxelSettings* WorldSettings = nullptr;
|
||||
|
||||
// Optional preloaded corpus. With nullptr the composer loads the project asset corpus on the
|
||||
// build box, exactly like the existing offline composer tests.
|
||||
const FVoxelStrateCorpus* Corpus = nullptr;
|
||||
|
||||
// 24 gives a useful minute-scale automation pass at the default coarse measurement window.
|
||||
// Production can raise this to the overnight budget without changing the policy.
|
||||
int32 CandidateCount = 24;
|
||||
int32 SelectedStrateCount = 10;
|
||||
|
||||
// Generated candidates use one stable height. Fixed authored slots may provide their own
|
||||
// height. World layout bounds are written into every selected parameter family below.
|
||||
int32 StrateHeightInChunks = 4;
|
||||
int32 InterStrateGapChunks = 0;
|
||||
float OriginSpineRadius = 14.0f;
|
||||
float WorldRadiusVoxels = 0.0f;
|
||||
|
||||
// Coarse measurement is deliberately bounded. Tests may reduce the window; production may
|
||||
// spend the overnight budget on a finer pass after the selector has been judged.
|
||||
FVoxelStrateMeasureSettings MeasureSettings;
|
||||
FVoxelSeasonSelectionPolicy SelectionPolicy;
|
||||
|
||||
// Explicit composed fixed slots take precedence. When empty, the composer adapts the
|
||||
// existing UVoxelSettings::FixedStrates map, preserving its absolute slot indices.
|
||||
TMap<int32, FVoxelSeasonFixedStrate> FixedStrates;
|
||||
|
||||
// Empty means ProjectSavedDir()/VoxelForge/Seasons. Files are never written to the runtime
|
||||
// content directory by this API.
|
||||
FString OutputDirectory;
|
||||
bool bWriteArtifacts = true;
|
||||
bool bWriteReviewPage = true;
|
||||
bool bIncludeCandidateAudit = true;
|
||||
|
||||
// Informational only; when WorldSettings is present CurrentSeason supplies the default.
|
||||
int32 SeasonNumber = 0;
|
||||
|
||||
FVoxelSeasonCompositionSettings()
|
||||
{
|
||||
// The existing Tier 4b test uses step 4. Season composition defaults to step 8 so the
|
||||
// default automation artifact is measured in minutes, while MaxCells remains bounded.
|
||||
MeasureSettings.SampleStep = 8;
|
||||
MeasureSettings.RadiusInVoxels = 256;
|
||||
MeasureSettings.MaxCells = 8000000;
|
||||
MeasureSettings.MaxRouteRetries = 16;
|
||||
MeasureSettings.HeadroomCells = 2;
|
||||
MeasureSettings.InteriorMarginVoxels = -1;
|
||||
}
|
||||
};
|
||||
|
||||
// Friendly aliases for callers that use the task's shorter naming.
|
||||
using FVoxelSeasonComposerSettings = FVoxelSeasonCompositionSettings;
|
||||
using FVoxelSeasonComposeSettings = FVoxelSeasonCompositionSettings;
|
||||
|
||||
/** Compact audit row; the full vectors live only for selected strates. */
|
||||
struct VOXELFORGE_API FVoxelSeasonCandidateAudit
|
||||
{
|
||||
int32 CandidateIndex = INDEX_NONE;
|
||||
int32 Seed = 0;
|
||||
ECaveGeneratorType Archetype = ECaveGeneratorType::TunnelNetwork;
|
||||
uint32 RecipeHash = 0;
|
||||
double DistanceFromCorpusCentroid = 0.0;
|
||||
bool bPassedHardGates = false;
|
||||
bool bSelected = false;
|
||||
FString RejectionReason;
|
||||
};
|
||||
|
||||
/** Count of one explicit hard-gate or final-placement rejection reason. */
|
||||
struct VOXELFORGE_API FVoxelSeasonRejectionCount
|
||||
{
|
||||
FString Reason;
|
||||
int32 Count = 0;
|
||||
};
|
||||
|
||||
/** One ordered Tier A spine entry. */
|
||||
struct VOXELFORGE_API FVoxelSeasonStrate
|
||||
{
|
||||
// DepthIndex is the absolute world-layout slot. CandidateIndex is the offline attempt id.
|
||||
int32 DepthIndex = INDEX_NONE;
|
||||
int32 CandidateIndex = INDEX_NONE;
|
||||
int32 Seed = 0;
|
||||
int32 HeightInChunks = 0;
|
||||
int32 TopWorldZ = 0;
|
||||
int32 BottomWorldZ = 0;
|
||||
|
||||
ECaveGeneratorType Archetype = ECaveGeneratorType::TunnelNetwork;
|
||||
FVoxelOpStackRecipe Recipe;
|
||||
FVoxelStrateArchetypeParams Params;
|
||||
FVoxelStrateMeasuredMetrics Metrics;
|
||||
|
||||
double DistanceFromCorpusCentroid = 0.0;
|
||||
FString SelectionReason;
|
||||
FString SourceDefinitionPath;
|
||||
|
||||
bool bGrounded = false;
|
||||
bool bOutlier = false;
|
||||
bool bBossSlot = false;
|
||||
bool bFixed = false;
|
||||
bool bUsesRecipe = true;
|
||||
|
||||
bool bPassedNonVacuous = false;
|
||||
bool bPassedLargestComponent = false;
|
||||
bool bPassedPrimordialLaw = false;
|
||||
EVoxelConnectivityResult PrimordialLawResult = EVoxelConnectivityResult::OutOfWindow;
|
||||
};
|
||||
|
||||
/**
|
||||
* The stored Tier A season artifact.
|
||||
*
|
||||
* Tier B is intentionally absent: its hash-lattice field remains an arithmetic/enumeration API,
|
||||
* not a second stored table. Strates is ordered from the surface down and is the only finite spine
|
||||
* persisted by this task.
|
||||
*/
|
||||
struct VOXELFORGE_API FVoxelSeasonManifest
|
||||
{
|
||||
static constexpr int32 CurrentSchemaVersion = 1;
|
||||
|
||||
bool bValid = false;
|
||||
int32 SchemaVersion = CurrentSchemaVersion;
|
||||
FString Error;
|
||||
|
||||
int32 Season = 0;
|
||||
int32 Seed = 0;
|
||||
uint32 InputCorpusHash = 0;
|
||||
int32 CandidateCount = 0;
|
||||
int32 SurvivorCount = 0;
|
||||
int32 SelectedCount = 0;
|
||||
int32 RejectedCount = 0;
|
||||
int32 GroundedSelectedCount = 0;
|
||||
int32 OutlierSelectedCount = 0;
|
||||
int32 FixedSelectedCount = 0;
|
||||
int32 TotalStrates = 0;
|
||||
int32 InterStrateGapChunks = 0;
|
||||
float OriginSpineRadius = 0.0f;
|
||||
float WorldRadiusVoxels = 0.0f;
|
||||
|
||||
FVoxelSeasonSelectionPolicy SelectionPolicy;
|
||||
FString PolicyNote;
|
||||
|
||||
TArray<FVoxelSeasonStrate> Strates;
|
||||
TArray<FVoxelSeasonCandidateAudit> CandidateAudit;
|
||||
TArray<FVoxelSeasonRejectionCount> RejectionCounts;
|
||||
|
||||
// Derived artifact locations; intentionally not serialized into the portable JSON.
|
||||
FString ManifestPath;
|
||||
FString ReviewPagePath;
|
||||
FString SerializedJson;
|
||||
|
||||
bool IsUsable() const
|
||||
{
|
||||
return bValid && Error.IsEmpty() && WorldRadiusVoxels == 0.0f
|
||||
&& Strates.Num() == SelectedCount && SelectedCount > 0;
|
||||
}
|
||||
};
|
||||
|
||||
/** Human-readable enum names used by JSON and the review page. */
|
||||
VOXELFORGE_API const TCHAR* VF_GetVoxelSeasonSelectionReasonName(
|
||||
EVoxelSeasonSelectionReason Reason);
|
||||
|
||||
/** Compose, validate, select, serialize, and (by default) write one season. Editor/build-box only. */
|
||||
VOXELFORGE_API FVoxelSeasonManifest VF_ComposeSeason(
|
||||
int32 SeasonSeed, const FVoxelSeasonCompositionSettings& Settings);
|
||||
|
||||
/** Convenience overload that adapts the existing runtime settings asset. */
|
||||
VOXELFORGE_API FVoxelSeasonManifest VF_ComposeSeason(
|
||||
int32 SeasonSeed, const UVoxelSettings* WorldSettings);
|
||||
|
||||
/** Serialize/read the complete Tier A manifest as stable pretty JSON. */
|
||||
VOXELFORGE_API FString VF_SerializeVoxelSeasonManifest(
|
||||
const FVoxelSeasonManifest& Manifest);
|
||||
VOXELFORGE_API bool VF_SaveVoxelSeasonManifest(
|
||||
const FString& ManifestPath, const FVoxelSeasonManifest& Manifest, FString& OutReport);
|
||||
VOXELFORGE_API bool VF_LoadVoxelSeasonManifest(
|
||||
const FString& ManifestPath, FVoxelSeasonManifest& OutManifest, FString& OutReport);
|
||||
|
||||
/** Rebuild exactly the stack represented by one manifest entry. */
|
||||
VOXELFORGE_API bool VF_BuildSeasonStrateStack(
|
||||
const FVoxelSeasonStrate& Strate,
|
||||
float SpineRadius,
|
||||
const UVoxelStrateManager* StrateManager,
|
||||
FVoxelOpStack& OutStack,
|
||||
FVoxelOpContext& OutContext,
|
||||
FString* OutError = nullptr);
|
||||
|
||||
/** Verbose alias for tests/tools that want the round-trip operation named explicitly. */
|
||||
VOXELFORGE_API bool VF_RebuildVoxelSeasonStrate(
|
||||
const FVoxelSeasonStrate& Strate,
|
||||
float SpineRadius,
|
||||
const UVoxelStrateManager* StrateManager,
|
||||
FVoxelOpStack& OutStack,
|
||||
FVoxelOpContext& OutContext,
|
||||
FString* OutError = nullptr);
|
||||
|
||||
@@ -18,6 +18,16 @@ class UVoxelSettings;
|
||||
class UVoxelStrateDefinition;
|
||||
class UVoxelStrateManager;
|
||||
|
||||
#if WITH_EDITOR
|
||||
// The complete definitions live in VoxelSeasonManifest.h. Keeping this forward declaration here
|
||||
// makes the Tier 4c entry point discoverable from the composer header without creating a circular
|
||||
// include (the manifest header includes this one for the existing vector/recipe types).
|
||||
struct FVoxelSeasonManifest;
|
||||
struct FVoxelSeasonCompositionSettings;
|
||||
VOXELFORGE_API FVoxelSeasonManifest VF_ComposeSeason(
|
||||
int32 SeasonSeed, const FVoxelSeasonCompositionSettings& Settings);
|
||||
#endif
|
||||
|
||||
/** The one-bit identity choice at the root of a rolled structure. */
|
||||
UENUM(BlueprintType)
|
||||
enum class EVoxelStrateRootPolarity : uint8
|
||||
|
||||
@@ -71,6 +71,12 @@ struct VOXELFORGE_API FVoxelStrateFinePreviewSettings
|
||||
struct VOXELFORGE_API FVoxelStratePreviewCandidate
|
||||
{
|
||||
int32 CandidateIndex = INDEX_NONE;
|
||||
// Season pages deliberately opt into descent order. The ordinary candidate contact sheet
|
||||
// keeps its survivor/outlier ordering; a season review must read top-to-bottom like play.
|
||||
bool bSeasonOrder = false;
|
||||
int32 DepthIndex = INDEX_NONE;
|
||||
FString SelectionReason;
|
||||
bool bBossSlot = false;
|
||||
FVoxelStratePreviewWindow Window;
|
||||
FString RecipeString;
|
||||
FString ArrivalDepartureVerdict;
|
||||
|
||||
Reference in new issue
Block a user