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
@@ -7,6 +7,7 @@
|
||||
#include "HAL/FileManager.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "Misc/SecureHash.h"
|
||||
#include "Serialization/JsonReader.h"
|
||||
#include "Serialization/JsonSerializer.h"
|
||||
#include "Serialization/JsonWriter.h"
|
||||
@@ -1428,56 +1429,85 @@ const TCHAR* VF_GetVoxelSeasonSelectionReasonName(EVoxelSeasonSelectionReason Re
|
||||
return TEXT("unknown");
|
||||
}
|
||||
|
||||
FString VF_SerializeVoxelSeasonManifest(const FVoxelSeasonManifest& Manifest)
|
||||
namespace
|
||||
{
|
||||
using namespace VoxelSeasonManifestPrivate;
|
||||
TSharedPtr<FJsonObject> Root = MakeShared<FJsonObject>();
|
||||
SetCommonManifestFields(Root, Manifest);
|
||||
|
||||
TArray<TSharedPtr<FJsonValue>> Strates;
|
||||
for (const FVoxelSeasonStrate& Strate : Manifest.Strates)
|
||||
FString SerializeVoxelSeasonManifestInternal(
|
||||
const FVoxelSeasonManifest& Manifest, bool bIncludeContentHash)
|
||||
{
|
||||
Strates.Add(MakeShared<FJsonValueObject>(SerializeStrate(Strate)));
|
||||
}
|
||||
Root->SetArrayField(TEXT("strates"), MoveTemp(Strates));
|
||||
using namespace VoxelSeasonManifestPrivate;
|
||||
TSharedPtr<FJsonObject> Root = MakeShared<FJsonObject>();
|
||||
SetCommonManifestFields(Root, Manifest);
|
||||
|
||||
TArray<TSharedPtr<FJsonValue>> Rejections;
|
||||
for (const FVoxelSeasonRejectionCount& Rejection : Manifest.RejectionCounts)
|
||||
{
|
||||
TSharedPtr<FJsonObject> Object = MakeShared<FJsonObject>();
|
||||
Object->SetStringField(TEXT("reason"), Rejection.Reason);
|
||||
Object->SetNumberField(TEXT("count"), Rejection.Count);
|
||||
Rejections.Add(MakeShared<FJsonValueObject>(Object));
|
||||
}
|
||||
Root->SetArrayField(TEXT("rejection_counts"), MoveTemp(Rejections));
|
||||
TArray<TSharedPtr<FJsonValue>> Strates;
|
||||
for (const FVoxelSeasonStrate& Strate : Manifest.Strates)
|
||||
{
|
||||
Strates.Add(MakeShared<FJsonValueObject>(SerializeStrate(Strate)));
|
||||
}
|
||||
Root->SetArrayField(TEXT("strates"), MoveTemp(Strates));
|
||||
|
||||
if (!Manifest.CandidateAudit.IsEmpty())
|
||||
{
|
||||
TArray<TSharedPtr<FJsonValue>> Audit;
|
||||
for (const FVoxelSeasonCandidateAudit& Row : Manifest.CandidateAudit)
|
||||
TArray<TSharedPtr<FJsonValue>> Rejections;
|
||||
for (const FVoxelSeasonRejectionCount& Rejection : Manifest.RejectionCounts)
|
||||
{
|
||||
TSharedPtr<FJsonObject> Object = MakeShared<FJsonObject>();
|
||||
Object->SetNumberField(TEXT("candidate_index"), Row.CandidateIndex);
|
||||
Object->SetNumberField(TEXT("seed"), Row.Seed);
|
||||
Object->SetStringField(TEXT("archetype"), ArchetypeName(Row.Archetype));
|
||||
Object->SetNumberField(TEXT("recipe_hash"), static_cast<double>(Row.RecipeHash));
|
||||
Object->SetNumberField(TEXT("distance_from_corpus_centroid"), Row.DistanceFromCorpusCentroid);
|
||||
Object->SetBoolField(TEXT("passed_hard_gates"), Row.bPassedHardGates);
|
||||
Object->SetBoolField(TEXT("selected"), Row.bSelected);
|
||||
Object->SetStringField(TEXT("rejection_reason"), Row.RejectionReason);
|
||||
Audit.Add(MakeShared<FJsonValueObject>(Object));
|
||||
Object->SetStringField(TEXT("reason"), Rejection.Reason);
|
||||
Object->SetNumberField(TEXT("count"), Rejection.Count);
|
||||
Rejections.Add(MakeShared<FJsonValueObject>(Object));
|
||||
}
|
||||
Root->SetArrayField(TEXT("candidate_audit"), MoveTemp(Audit));
|
||||
}
|
||||
Root->SetArrayField(TEXT("rejection_counts"), MoveTemp(Rejections));
|
||||
|
||||
FString JsonText;
|
||||
TSharedRef<TJsonWriter<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>> Writer =
|
||||
TJsonWriterFactory<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>::Create(&JsonText);
|
||||
if (!FJsonSerializer::Serialize(Root.ToSharedRef(), Writer) || !Writer->Close())
|
||||
{
|
||||
return FString();
|
||||
if (!Manifest.CandidateAudit.IsEmpty())
|
||||
{
|
||||
TArray<TSharedPtr<FJsonValue>> Audit;
|
||||
for (const FVoxelSeasonCandidateAudit& Row : Manifest.CandidateAudit)
|
||||
{
|
||||
TSharedPtr<FJsonObject> Object = MakeShared<FJsonObject>();
|
||||
Object->SetNumberField(TEXT("candidate_index"), Row.CandidateIndex);
|
||||
Object->SetNumberField(TEXT("seed"), Row.Seed);
|
||||
Object->SetStringField(TEXT("archetype"), ArchetypeName(Row.Archetype));
|
||||
Object->SetNumberField(TEXT("recipe_hash"), static_cast<double>(Row.RecipeHash));
|
||||
Object->SetNumberField(TEXT("distance_from_corpus_centroid"), Row.DistanceFromCorpusCentroid);
|
||||
Object->SetBoolField(TEXT("passed_hard_gates"), Row.bPassedHardGates);
|
||||
Object->SetBoolField(TEXT("selected"), Row.bSelected);
|
||||
Object->SetStringField(TEXT("rejection_reason"), Row.RejectionReason);
|
||||
Audit.Add(MakeShared<FJsonValueObject>(Object));
|
||||
}
|
||||
Root->SetArrayField(TEXT("candidate_audit"), MoveTemp(Audit));
|
||||
}
|
||||
|
||||
if (bIncludeContentHash)
|
||||
{
|
||||
const FString Payload = SerializeVoxelSeasonManifestInternal(Manifest, false);
|
||||
if (Payload.IsEmpty()) return FString();
|
||||
FTCHARToUTF8 Utf8(*Payload);
|
||||
uint8 Digest[FSHA1::DigestSize];
|
||||
FSHA1::HashBuffer(Utf8.Get(), Utf8.Length(), Digest);
|
||||
Root->SetStringField(TEXT("content_hash"), BytesToHex(Digest, FSHA1::DigestSize));
|
||||
}
|
||||
|
||||
FString JsonText;
|
||||
TSharedRef<TJsonWriter<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>> Writer =
|
||||
TJsonWriterFactory<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>::Create(&JsonText);
|
||||
if (!FJsonSerializer::Serialize(Root.ToSharedRef(), Writer) || !Writer->Close())
|
||||
{
|
||||
return FString();
|
||||
}
|
||||
return JsonText;
|
||||
}
|
||||
return JsonText;
|
||||
}
|
||||
|
||||
FString VF_ComputeVoxelSeasonManifestContentHash(const FVoxelSeasonManifest& Manifest)
|
||||
{
|
||||
const FString Payload = SerializeVoxelSeasonManifestInternal(Manifest, false);
|
||||
if (Payload.IsEmpty()) return FString();
|
||||
FTCHARToUTF8 Utf8(*Payload);
|
||||
uint8 Digest[FSHA1::DigestSize];
|
||||
FSHA1::HashBuffer(Utf8.Get(), Utf8.Length(), Digest);
|
||||
return BytesToHex(Digest, FSHA1::DigestSize);
|
||||
}
|
||||
|
||||
FString VF_SerializeVoxelSeasonManifest(const FVoxelSeasonManifest& Manifest)
|
||||
{
|
||||
return SerializeVoxelSeasonManifestInternal(Manifest, true);
|
||||
}
|
||||
|
||||
bool VF_SaveVoxelSeasonManifest(const FString& ManifestPath,
|
||||
@@ -1511,24 +1541,25 @@ bool VF_SaveVoxelSeasonManifest(const FString& ManifestPath,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VF_LoadVoxelSeasonManifest(const FString& ManifestPath,
|
||||
FVoxelSeasonManifest& OutManifest,
|
||||
FString& OutReport)
|
||||
bool VF_DeserializeVoxelSeasonManifest(const FString& ManifestJson,
|
||||
FVoxelSeasonManifest& OutManifest,
|
||||
FString& OutReport)
|
||||
{
|
||||
using namespace VoxelSeasonManifestPrivate;
|
||||
OutManifest = FVoxelSeasonManifest();
|
||||
OutReport.Reset();
|
||||
if (ManifestPath.IsEmpty())
|
||||
if (ManifestJson.IsEmpty())
|
||||
{
|
||||
OutReport = TEXT("season manifest path is empty");
|
||||
OutReport = TEXT("season manifest JSON is empty");
|
||||
OutManifest.Error = OutReport;
|
||||
return false;
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> Root;
|
||||
FString OriginalJson;
|
||||
if (!LoadJson(ManifestPath, Root, OutReport, &OriginalJson))
|
||||
const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ManifestJson);
|
||||
if (!FJsonSerializer::Deserialize(Reader, Root) || !Root.IsValid())
|
||||
{
|
||||
OutReport = TEXT("season manifest JSON is malformed");
|
||||
OutManifest.Error = OutReport;
|
||||
return false;
|
||||
}
|
||||
@@ -1556,6 +1587,8 @@ bool VF_LoadVoxelSeasonManifest(const FString& ManifestPath,
|
||||
bool bFieldsValid = true;
|
||||
OutManifest.bValid = bValid;
|
||||
OutManifest.SchemaVersion = SchemaVersion;
|
||||
bFieldsValid = ReadJsonString(Root, TEXT("content_hash"), OutManifest.ContentHash)
|
||||
&& bFieldsValid;
|
||||
bFieldsValid = ReadJsonInt32(Root, TEXT("season"), OutManifest.Season) && bFieldsValid;
|
||||
bFieldsValid = ReadJsonInt32(Root, TEXT("seed"), OutManifest.Seed) && bFieldsValid;
|
||||
double CorpusHash = 0.0;
|
||||
@@ -1669,7 +1702,7 @@ bool VF_LoadVoxelSeasonManifest(const FString& ManifestPath,
|
||||
const FVoxelSeasonStrate& Strate = OutManifest.Strates[Index];
|
||||
const float StoredTop = static_cast<float>(Strate.TopWorldZ);
|
||||
const float StoredBottom = static_cast<float>(Strate.BottomWorldZ);
|
||||
if (Strate.DepthIndex != Index || !Strate.bPassedPrimordialLaw
|
||||
if (Strate.DepthIndex != Index || Strate.bUsesRegions || !Strate.bPassedPrimordialLaw
|
||||
|| Strate.PrimordialLawResult != EVoxelConnectivityResult::Connected)
|
||||
{
|
||||
bFieldsValid = false;
|
||||
@@ -1695,17 +1728,99 @@ bool VF_LoadVoxelSeasonManifest(const FString& ManifestPath,
|
||||
}
|
||||
if (!bFieldsValid)
|
||||
{
|
||||
OutReport = FString::Printf(TEXT("season manifest %s contains invalid fields"), *ManifestPath);
|
||||
OutReport = TEXT("season manifest contains invalid fields");
|
||||
OutManifest.Error = OutReport;
|
||||
OutManifest.bValid = false;
|
||||
return false;
|
||||
}
|
||||
OutManifest.ManifestPath = ManifestPath;
|
||||
OutManifest.SerializedJson = MoveTemp(OriginalJson);
|
||||
OutReport = FString::Printf(TEXT("loaded season manifest from %s"), *ManifestPath);
|
||||
const FString ComputedHash = VF_ComputeVoxelSeasonManifestContentHash(OutManifest);
|
||||
if (ComputedHash.IsEmpty()
|
||||
|| !ComputedHash.Equals(OutManifest.ContentHash, ESearchCase::IgnoreCase))
|
||||
{
|
||||
OutReport = FString::Printf(
|
||||
TEXT("season manifest content hash mismatch (stored %s, computed %s)"),
|
||||
*OutManifest.ContentHash, *ComputedHash);
|
||||
OutManifest.Error = OutReport;
|
||||
OutManifest.bValid = false;
|
||||
return false;
|
||||
}
|
||||
OutManifest.SerializedJson = ManifestJson;
|
||||
OutReport = FString::Printf(TEXT("loaded season manifest hash %s"), *OutManifest.ContentHash);
|
||||
return OutManifest.IsUsable();
|
||||
}
|
||||
|
||||
bool VF_LoadVoxelSeasonManifest(const FString& ManifestPath,
|
||||
FVoxelSeasonManifest& OutManifest,
|
||||
FString& OutReport)
|
||||
{
|
||||
FString JsonText;
|
||||
if (ManifestPath.IsEmpty())
|
||||
{
|
||||
OutReport = TEXT("season manifest path is empty");
|
||||
OutManifest = FVoxelSeasonManifest();
|
||||
OutManifest.Error = OutReport;
|
||||
return false;
|
||||
}
|
||||
if (!FFileHelper::LoadFileToString(JsonText, *ManifestPath))
|
||||
{
|
||||
OutReport = FString::Printf(TEXT("could not read season manifest %s"), *ManifestPath);
|
||||
OutManifest = FVoxelSeasonManifest();
|
||||
OutManifest.Error = OutReport;
|
||||
return false;
|
||||
}
|
||||
if (!VF_DeserializeVoxelSeasonManifest(JsonText, OutManifest, OutReport))
|
||||
{
|
||||
OutReport = FString::Printf(TEXT("season manifest %s: %s"), *ManifestPath, *OutReport);
|
||||
OutManifest.Error = OutReport;
|
||||
return false;
|
||||
}
|
||||
OutManifest.ManifestPath = ManifestPath;
|
||||
OutReport = FString::Printf(TEXT("loaded season manifest from %s (hash %s)"),
|
||||
*ManifestPath, *OutManifest.ContentHash);
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace VoxelSeasonRuntimePrivate
|
||||
{
|
||||
float ActiveTop(const FVoxelStrateArchetypeParams& Params, ECaveGeneratorType Archetype)
|
||||
{
|
||||
switch (Archetype)
|
||||
{
|
||||
case ECaveGeneratorType::TunnelNetwork:
|
||||
case ECaveGeneratorType::Underwater: return Params.TunnelNetworkParams.StrateTopWorldZ;
|
||||
case ECaveGeneratorType::FlatPlain:
|
||||
case ECaveGeneratorType::CrystalChamber: return Params.SlabParams.StrateTopWorldZ;
|
||||
case ECaveGeneratorType::Maze: return Params.MazeParams.StrateTopWorldZ;
|
||||
case ECaveGeneratorType::SurfaceWorld: return Params.SurfaceParams.StrateTopWorldZ;
|
||||
case ECaveGeneratorType::VerticalShafts: return Params.VerticalShaftParams.StrateTopWorldZ;
|
||||
case ECaveGeneratorType::FloatingIslands: return Params.FloatingIslandParams.StrateTopWorldZ;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float ActiveBottom(const FVoxelStrateArchetypeParams& Params, ECaveGeneratorType Archetype)
|
||||
{
|
||||
switch (Archetype)
|
||||
{
|
||||
case ECaveGeneratorType::TunnelNetwork:
|
||||
case ECaveGeneratorType::Underwater: return Params.TunnelNetworkParams.StrateBottomWorldZ;
|
||||
case ECaveGeneratorType::FlatPlain:
|
||||
case ECaveGeneratorType::CrystalChamber: return Params.SlabParams.StrateBottomWorldZ;
|
||||
case ECaveGeneratorType::Maze: return Params.MazeParams.StrateBottomWorldZ;
|
||||
case ECaveGeneratorType::SurfaceWorld: return Params.SurfaceParams.StrateBottomWorldZ;
|
||||
case ECaveGeneratorType::VerticalShafts: return Params.VerticalShaftParams.StrateBottomWorldZ;
|
||||
case ECaveGeneratorType::FloatingIslands: return Params.FloatingIslandParams.StrateBottomWorldZ;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
bool SameFloatBits(float A, float B)
|
||||
{
|
||||
return FMemory::Memcmp(&A, &B, sizeof(float)) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
namespace VoxelSeasonCompositionPrivate
|
||||
{
|
||||
float ActiveTop(const FVoxelStrateArchetypeParams& Params, ECaveGeneratorType Archetype);
|
||||
@@ -1920,7 +2035,10 @@ namespace VoxelSeasonCompositionPrivate
|
||||
OutStrate.Params = Candidate.ArchetypeParams;
|
||||
OutStrate.Regions = Candidate.Regions;
|
||||
OutStrate.bUsesRecipe = true;
|
||||
OutStrate.bUsesRegions = Candidate.Regions.RegionCount > 1;
|
||||
// Lateral composition remains a laboratory feature until its cross-seam law is perfect.
|
||||
// The season artifact must therefore stay on the proved single-stack runtime contract.
|
||||
OutStrate.bUsesRegions = VF_LateralRegionsAreShippable()
|
||||
&& Candidate.Regions.RegionCount > 1;
|
||||
SetRuntimeBounds(OutStrate.Params, static_cast<float>(TopWorldZ),
|
||||
static_cast<float>(BottomWorldZ));
|
||||
if (OutStrate.Regions.IsValid())
|
||||
@@ -2235,6 +2353,7 @@ namespace VoxelSeasonCompositionPrivate
|
||||
return VoxelSeasonManifestPrivate::ConnectivityName(Result);
|
||||
}
|
||||
}
|
||||
#endif // WITH_EDITOR — composition, measurement and review helpers never ship
|
||||
|
||||
namespace VoxelSeasonCompositionPrivate
|
||||
{
|
||||
@@ -2791,6 +2910,11 @@ FVoxelSeasonManifest VF_ComposeSeason(int32 SeasonSeed,
|
||||
|
||||
Manifest.bValid = true;
|
||||
Manifest.Error.Reset();
|
||||
Manifest.ContentHash = VF_ComputeVoxelSeasonManifestContentHash(Manifest);
|
||||
if (Manifest.ContentHash.IsEmpty())
|
||||
{
|
||||
return Fail(TEXT("could not hash composed season manifest"));
|
||||
}
|
||||
Manifest.SerializedJson = VF_SerializeVoxelSeasonManifest(Manifest);
|
||||
if (Manifest.SerializedJson.IsEmpty())
|
||||
{
|
||||
@@ -2807,20 +2931,9 @@ FVoxelSeasonManifest VF_ComposeSeason(int32 SeasonSeed,
|
||||
return Manifest;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
FVoxelSeasonManifest VF_ComposeSeason(int32 SeasonSeed,
|
||||
const FVoxelSeasonCompositionSettings& Settings)
|
||||
{
|
||||
(void)SeasonSeed;
|
||||
(void)Settings;
|
||||
FVoxelSeasonManifest Manifest;
|
||||
Manifest.Error = TEXT("season composition is editor/build-box only");
|
||||
return Manifest;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if WITH_EDITOR
|
||||
FVoxelSeasonManifest VF_ComposeSeason(int32 SeasonSeed, const UVoxelSettings* WorldSettings)
|
||||
{
|
||||
FVoxelSeasonCompositionSettings Settings;
|
||||
@@ -2835,6 +2948,7 @@ FVoxelSeasonManifest VF_ComposeSeason(int32 SeasonSeed, const UVoxelSettings* Wo
|
||||
}
|
||||
return VF_ComposeSeason(SeasonSeed, Settings);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool VF_BuildSeasonStrateStack(const FVoxelSeasonStrate& Strate,
|
||||
float SpineRadius,
|
||||
@@ -2843,7 +2957,7 @@ bool VF_BuildSeasonStrateStack(const FVoxelSeasonStrate& Strate,
|
||||
FVoxelOpContext& OutContext,
|
||||
FString* OutError)
|
||||
{
|
||||
using namespace VoxelSeasonCompositionPrivate;
|
||||
using namespace VoxelSeasonRuntimePrivate;
|
||||
auto Fail = [&](const FString& Reason) -> bool
|
||||
{
|
||||
if (OutError != nullptr)
|
||||
@@ -2890,8 +3004,12 @@ bool VF_BuildSeasonStrateStack(const FVoxelSeasonStrate& Strate,
|
||||
bool bBuilt = false;
|
||||
if (Strate.bUsesRegions)
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
bBuilt = VF_BuildStrateRegionStack(Strate.Regions, SpineRadius, StrateManager,
|
||||
OutStack, OutContext, OutError);
|
||||
#else
|
||||
return Fail(TEXT("Lateral region manifests are not shippable."));
|
||||
#endif
|
||||
}
|
||||
else if (Strate.bUsesRecipe)
|
||||
{
|
||||
@@ -2902,7 +3020,8 @@ bool VF_BuildSeasonStrateStack(const FVoxelSeasonStrate& Strate,
|
||||
else
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
const float Seal = ActiveSeal(Strate.Params, Strate.Archetype);
|
||||
const float Seal = VoxelSeasonCompositionPrivate::ActiveSeal(
|
||||
Strate.Params, Strate.Archetype);
|
||||
bBuilt = VF_BuildNativeStrateStackForCandidate(
|
||||
Strate.Archetype, Strate.Params, Strate.Seed, SpineRadius,
|
||||
0.0f, Seal, StrateManager, OutStack, OutContext);
|
||||
@@ -2940,6 +3059,7 @@ bool VF_RebuildVoxelSeasonStrate(const FVoxelSeasonStrate& Strate,
|
||||
OutStack, OutContext, OutError);
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
namespace VoxelSeasonCompositionPrivate
|
||||
{
|
||||
bool WriteReviewPage(const FVoxelSeasonManifest& Manifest,
|
||||
@@ -3021,3 +3141,4 @@ namespace VoxelSeasonCompositionPrivate
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif // WITH_EDITOR — review rendering never ships
|
||||
Reference in new issue
Block a user