feat(runtime): the composed world can BE the game world
Jahni: "can you not implement those settings? as in, have them be live in
game?" This is that. The composer stops being a lab tool.
THE ARCHITECTURAL LINE, which is the whole design (COMPOSER-NOTES §3.1):
COMPOSITION stays editor-only, EVALUATION becomes runtime. Rolling,
measuring, preview, selection, promotion, the corpus -- none of it reaches
a player's machine. What crosses into the runtime is exactly two things:
reading a season manifest, and materialising a recipe into an op stack.
UVoxelSeasonAsset a cookable UPrimaryDataAsset holding the manifest,
so a season ships in a packaged game. The JSON
stays the reviewable, git-diffable artefact.
UVoxelSettings::Season optional. Unset => behaviour is exactly as today.
GetEffective*() season seed / spine radius / world radius / season
number override the authored values when assigned.
The test deliberately sets DIFFERENT authored
values, so "the season is authoritative" is proved
rather than coincidental.
⭐ MULTIPLAYER DETERMINISM, PROVED: two independent runtime managers built
the same season and compared 393,216 density samples BIT-IDENTICALLY. This
is the property the whole multiplayer model rests on -- replicate seed +
layout + diff, never geometry.
⭐ AND A STALE SEASON IS DETECTABLE, NOT SILENT. Initialize() now returns
bool and an assigned-but-invalid season NEVER falls back to the authored
pool. Falling back would hand a client a world that looks fine and is not
the season -- silent divergence, the worst failure mode in a shared world.
GetCurrentSeasonContentHash() is exposed BlueprintPure to compare at join.
HasComposerRecipeOverride() is gone. It force-classified every custom recipe
as Mixed, which was correct while box verdicts were only proved for
hand-written stacks -- but 8e346a3 made EffectOverBox correct in ISOLATION,
proved on a stack no builder produces. Keeping the force would have cost
tile skipping on every composed world forever.
VF_RollStrateRegionCount moved behind WITH_EDITOR while the partition-seed
helpers stayed runtime: rolling out, evaluation in. Lateral regions remain
gated off.
⚠️ Sol hit its usage limit mid-task, so the edits arrived unvalidated. I
built and ran them myself rather than assume -- and they did not compile:
VoxelForgeComposerSeasonTest.cpp was missing UObject/Package.h, which every
other test picks up transitively through the shared fixture it does not use.
One include; every NewObject/GetTransientPackage in the file was failing.
Editor build clean. SHIPPING BUILD SUCCEEDS -- the invariant that proves no
composer roller is linked into a packaged game. 30 succeeded / 0 failed.
⚠️ UNVERIFIED: whether it looks right on screen. Nothing headless can tell
us that; it needs Jahni to assign a season asset and press play.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU
This commit is contained in:
1 parent
088b40216d
commit
faa62d419e
21 files changed
+1268
-287
No files matched your search
@@ -423,6 +423,7 @@ void AVoxelWorld::RegenerateAllChunks()
|
||||
|
||||
void AVoxelWorld::RebuildStrates()
|
||||
{
|
||||
bool bLayoutRebuilt = true;
|
||||
{
|
||||
FScopedGenerationPause Guard(this);
|
||||
if (!Guard.Acquired())
|
||||
@@ -434,12 +435,20 @@ void AVoxelWorld::RebuildStrates()
|
||||
if (StrateManager && Settings)
|
||||
{
|
||||
// Re-applies layout + inter-strate gap + passage/spine settings from VoxelSettings.
|
||||
StrateManager->Initialize(Settings, Settings->Seed);
|
||||
bLayoutRebuilt = StrateManager->Initialize(
|
||||
Settings, Settings->GetEffectiveWorldSeed());
|
||||
}
|
||||
if (AtmosphereManager) AtmosphereManager->Reset();
|
||||
if (ContentManager) ContentManager->ClearAll();
|
||||
}
|
||||
|
||||
if (!bLayoutRebuilt)
|
||||
{
|
||||
UE_LOG(LogTemp, Error,
|
||||
TEXT("[VoxelWorld] RebuildStrates refused an invalid season; loaded tiles were not regenerated."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Reload all chunks against the rebuilt strate data.
|
||||
RegenerateAllChunks();
|
||||
|
||||
@@ -831,7 +840,7 @@ void AVoxelWorld::OnObjectModifiedInEditor(UObject* ModifiedObject)
|
||||
|
||||
if (StrateManager)
|
||||
{
|
||||
StrateManager->Initialize(Settings, Settings->Seed);
|
||||
StrateManager->Initialize(Settings, Settings->GetEffectiveWorldSeed());
|
||||
}
|
||||
if (Generator)
|
||||
{
|
||||
@@ -959,11 +968,19 @@ void AVoxelWorld::BeginPlay()
|
||||
Mesher->SkirtCells = Settings->SkirtCells;
|
||||
Mesher->LODOctaveDrop = Settings->LODOctaveDrop; // T2.b — 0 = off
|
||||
|
||||
// Système de strates — piloté par le pool et les fixed entries dans Settings.
|
||||
if (Settings->StratePool.Num() > 0)
|
||||
// Système de strates — a cooked season takes precedence; otherwise the authored pool path is
|
||||
// exactly the legacy one.
|
||||
if (!Settings->Season.IsNull() || Settings->StratePool.Num() > 0)
|
||||
{
|
||||
StrateManager = NewObject<UVoxelStrateManager>(this);
|
||||
StrateManager->Initialize(Settings, Settings->Seed);
|
||||
if (!StrateManager->Initialize(Settings, Settings->GetEffectiveWorldSeed()))
|
||||
{
|
||||
UE_LOG(LogTemp, Error,
|
||||
TEXT("[VoxelWorld] World generation disabled because its assigned season is invalid or incomplete."));
|
||||
bShuttingDown.store(true, std::memory_order_release);
|
||||
SetActorTickEnabled(false);
|
||||
return;
|
||||
}
|
||||
Generator->SetStrateManager(StrateManager);
|
||||
UE_LOG(LogTemp, Log, TEXT("[VoxelWorld] Strate system initialized with %d strates"),
|
||||
StrateManager->GetNumStrates());
|
||||
@@ -977,7 +994,8 @@ void AVoxelWorld::BeginPlay()
|
||||
|
||||
// Content manager — distance-based decoration grid (no LOD pop) + level-0 water planes.
|
||||
ContentManager = NewObject<UVoxelContentManager>(this);
|
||||
ContentManager->Initialize(this, StrateManager, Generator, Settings, Settings->Seed);
|
||||
ContentManager->Initialize(
|
||||
this, StrateManager, Generator, Settings, Settings->GetEffectiveWorldSeed());
|
||||
|
||||
// Atmosphere manager — per-strate fog + ambient + persistent ceiling/floor layers.
|
||||
if (bManageAtmosphere && StrateManager)
|
||||
@@ -2641,6 +2659,12 @@ void AVoxelWorld::ChangeSeed(int32 NewSeed)
|
||||
UE_LOG(LogTemp, Error, TEXT("[VoxelWorld] ChangeSeed failed — no Settings assigned"));
|
||||
return;
|
||||
}
|
||||
if (!Settings->Season.IsNull())
|
||||
{
|
||||
UE_LOG(LogTemp, Error,
|
||||
TEXT("[VoxelWorld] ChangeSeed rejected: a cooked Season owns the seed. Assign another season asset instead."));
|
||||
return;
|
||||
}
|
||||
|
||||
const int32 OldSeed = Settings->Seed;
|
||||
const int32 OldSeason = Settings->CurrentSeason;
|
||||
@@ -2702,12 +2726,17 @@ void AVoxelWorld::ChangeSeed(int32 NewSeed)
|
||||
|
||||
int32 AVoxelWorld::GetCurrentSeed() const
|
||||
{
|
||||
return Settings ? Settings->Seed : 0;
|
||||
return Settings ? Settings->GetEffectiveWorldSeed() : 0;
|
||||
}
|
||||
|
||||
int32 AVoxelWorld::GetCurrentSeason() const
|
||||
{
|
||||
return Settings ? Settings->CurrentSeason : 0;
|
||||
return Settings ? Settings->GetEffectiveSeasonNumber() : 0;
|
||||
}
|
||||
|
||||
FString AVoxelWorld::GetCurrentSeasonContentHash() const
|
||||
{
|
||||
return StrateManager ? StrateManager->GetSeasonContentHash() : FString();
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
|
||||
Reference in new issue
Block a user