wip(assembler): round L22 -- one 8 m layout chunk for every strate; real ceiling -> content -> bedrock

StrateLayoutChunkSizeMeters (world setting, default 8 m; owner decision) is resolved once in
the director/adapter and standalone loader; every generator reads the resolved width (no
CHUNK_SIZE / Core-tile inference). Every strate gets solid ceiling and bedrock bands from its
BoundarySealThickness in the shared composition path (diggable by default); faces come from the
shared boundary path. Owner world: FloatingIslands +8..-120 m (128 m, now with a bedrock floor),
SurfaceWorld -120..-632 m (unchanged; full-detail and exact-query hashes equal L21); the 60 m gap is
gone. The strict layout validator passes on the real world, with 0 endpoint dispatch
disagreements. Spawn/benchmark camera derived from route.front. Ledges under the islands gone
(reviewer capture). Evaluator 7, recipe schema 2, layout snapshot 2, FI geometry 4.
Open: SW cap profiles exceed the ceiling band (owner call); transitions still Hard; Unreal
setting/manager/adapter edits uncompiled here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-20 12:03:14 +02:00
1 parent 8753f6dbe4
commit 55c26a05a7
28 files changed
+1432 -579

No files matched your search

@@ -32,6 +32,38 @@ namespace
using FCoreJsonWriter = TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>;
constexpr double OwnerVoxelSizeCm = VOXEL_SIZE;
float StrateBoundarySealThickness(const UVoxelStrateDefinition& Definition)
{
switch(Definition.GeneratorType)
{
case ECaveGeneratorType::SurfaceWorld:return Definition.SurfaceParams.BoundarySealThickness;
case ECaveGeneratorType::FloatingIslands:return Definition.FloatingIslandParams.BoundarySealThickness;
case ECaveGeneratorType::Maze:return Definition.MazeParams.BoundarySealThickness;
case ECaveGeneratorType::VerticalShafts:return Definition.VerticalShaftParams.BoundarySealThickness;
case ECaveGeneratorType::TunnelNetwork:
case ECaveGeneratorType::FlatPlain:
case ECaveGeneratorType::CrystalChamber:
case ECaveGeneratorType::Underwater:return Definition.GenerationParams.BoundarySealThickness;
}
return Definition.GenerationParams.BoundarySealThickness;
}
float StrateBoundarySolidDensity(const UVoxelStrateDefinition& Definition)
{
switch(Definition.GeneratorType)
{
case ECaveGeneratorType::SurfaceWorld:return Definition.SurfaceParams.BaseDensity;
case ECaveGeneratorType::FloatingIslands:return Definition.FloatingIslandParams.BaseDensity;
case ECaveGeneratorType::Maze:return Definition.MazeParams.BaseDensity;
case ECaveGeneratorType::VerticalShafts:return Definition.VerticalShaftParams.BaseDensity;
case ECaveGeneratorType::TunnelNetwork:
case ECaveGeneratorType::FlatPlain:
case ECaveGeneratorType::CrystalChamber:
case ECaveGeneratorType::Underwater:return Definition.GenerationParams.BaseDensity;
}
return Definition.GenerationParams.BaseDensity;
}
std::int64_t WorldAssemblyFixed(double Voxels)
{
return static_cast<std::int64_t>(std::llround(
@@ -287,8 +319,10 @@ bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager,
const FSurfaceGenerationParams Surface = StrateManager->GetSurfaceParamsForChunk(Chunk);
const FBiomeContext BiomeContext = StrateManager->GetBiomeContextForChunk(Chunk);
const UVoxelStrateDefinition* Definition = Slot.Definition;
const double BottomVoxel = static_cast<double>(Slot.BottomChunkZ) * CHUNK_SIZE;
const double HeightVoxels = static_cast<double>(Slot.TopChunkZ - Slot.BottomChunkZ + 1) * CHUNK_SIZE;
const double LayoutChunkVoxels = static_cast<double>(Recipe.layoutChunkWidthSubvoxels)
/ WorldAssemblySubvoxelsPerVoxel;
const double BottomVoxel = static_cast<double>(Slot.BottomChunkZ) * LayoutChunkVoxels;
const double HeightVoxels = static_cast<double>(Slot.TopChunkZ - Slot.BottomChunkZ + 1) * LayoutChunkVoxels;
const FBiomeMapParams& Map = BiomeContext.Map;
Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
Recipe.biomeCellSizeSubvoxels = WorldAssemblyFixed(Map.CellSize);
@@ -422,7 +456,7 @@ bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager,
Shelf.minimumSubvoxels = {WorldAssemblyFixed(MinX), WorldAssemblyFixed(MinY),
std::max(WorldAssemblyFixed(BottomVoxel), LowestGround - WorldAssemblyFixed(4.0))};
Shelf.maximumSubvoxels = {WorldAssemblyFixed(MaxX), WorldAssemblyFixed(MaxY),
std::min(WorldAssemblyFixed((Slot.TopChunkZ + 1) * CHUNK_SIZE),
std::min(WorldAssemblyFixed((Slot.TopChunkZ + 1) * LayoutChunkVoxels),
HighestGround + WorldAssemblyFixed(ShelfHeight + 4.0))};
Shelf.parametersSubvoxels = {WorldAssemblyFixed(DirX), WorldAssemblyFixed(DirY),
WorldAssemblyFixed(ShelfHeight), WorldAssemblyFixed(Reach)};
@@ -440,7 +474,7 @@ bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager,
bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
const FStrateSlot& Slot, VoxelForgeCore::WorldAssemblyManifest& Manifest,
double CoreVoxelSizeCm, FString& OutError)
double CoreVoxelSizeCm, std::int64_t LayoutChunkWidthSubvoxels, FString& OutError)
{
using namespace VoxelForgeCore;
const FIntVector Chunk(0, 0, Slot.TopChunkZ);
@@ -450,7 +484,8 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
IslandPlan.topChunkZ = Slot.TopChunkZ;
IslandPlan.bottomChunkZ = Slot.BottomChunkZ;
auto& Recipe = IslandPlan.manifest.recipe;
Recipe.geometryVersion = 3;
Recipe.geometryVersion = 4;
Recipe.layoutChunkWidthSubvoxels = LayoutChunkWidthSubvoxels;
Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
Recipe.ownerVoxelSizeMicrons = static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0));
Recipe.coreVoxelSizeMicrons = static_cast<std::uint32_t>(std::llround(CoreVoxelSizeCm * 10000.0));
@@ -463,12 +498,10 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
}
Recipe.ownerToCoreScaleNumerator = Recipe.ownerVoxelSizeMicrons / ScaleGcd;
Recipe.ownerToCoreScaleDenominator = Recipe.coreVoxelSizeMicrons / ScaleGcd;
const double CoreVoxelsPerOwnerChunk = static_cast<double>(CHUNK_SIZE)
* Recipe.ownerToCoreScaleNumerator / Recipe.ownerToCoreScaleDenominator;
Recipe.layoutMinimumZSubvoxels = WorldAssemblyFixed(
static_cast<double>(Slot.BottomChunkZ) * CoreVoxelsPerOwnerChunk);
Recipe.layoutMaximumZSubvoxels = WorldAssemblyFixed(
static_cast<double>(Slot.TopChunkZ + 1) * CoreVoxelsPerOwnerChunk);
Recipe.layoutMinimumZSubvoxels = static_cast<std::int64_t>(Slot.BottomChunkZ)
* LayoutChunkWidthSubvoxels;
Recipe.layoutMaximumZSubvoxels = static_cast<std::int64_t>(Slot.TopChunkZ + 1)
* LayoutChunkWidthSubvoxels;
Recipe.layoutCenterTopZSubvoxels = (Recipe.layoutMinimumZSubvoxels
+ Recipe.layoutMaximumZSubvoxels) / 2;
Recipe.islandDensityQ16 = WorldAssemblyQ16(FMath::Clamp(Params.IslandDensity, 0.0f, 1.0f));
@@ -758,6 +791,12 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
const TSharedRef<FJsonObject>& Root,
FString& OutError)
{
const std::uint32_t LayoutChunkSizeMicrometres=StrateManager->GetStrateLayoutChunkSizeMicrometres();
const std::uint32_t CoreVoxelMicrometres=static_cast<std::uint32_t>(std::llround(CoreVoxelSizeCm*10000.0));
const std::int64_t LayoutChunkWidthSubvoxels=static_cast<std::int64_t>(std::llround(
static_cast<double>(LayoutChunkSizeMicrometres)*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
/std::max<std::uint32_t>(1,CoreVoxelMicrometres)));
StrateManager->SetHardTransitionFallbackForAssembly(true);
FString ManifestJson;
FString ManifestHash;
if (!Settings->GetWorldAssemblyManifestJson(ManifestJson, ManifestHash, OutError))
@@ -783,6 +822,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
{
VoxelForgeCore::WorldAssemblyManifest Generated;
Generated.recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
Generated.recipe.layoutChunkWidthSubvoxels=LayoutChunkWidthSubvoxels;
int32 SurfaceSlotCount = 0;
int32 IslandSlotCount = 0;
for (const FStrateSlot& Slot : StrateManager->GetLayout())
@@ -815,6 +855,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
{
VoxelForgeCore::WorldAssemblyRecipeV1 SlotRecipe;
SlotRecipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
SlotRecipe.layoutChunkWidthSubvoxels=LayoutChunkWidthSubvoxels;
VoxelForgeCore::WorldAssemblyStratumPlanV1 Stratum;
if (!BuildSurfaceStratumPlan(StrateManager, Slot,
SlotRecipe, Stratum, OutError))
@@ -829,7 +870,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
else if (Type == ECaveGeneratorType::FloatingIslands)
{
if (!BuildFloatingIslandStratumPlan(StrateManager, Slot, Generated,
CoreVoxelSizeCm, OutError))
CoreVoxelSizeCm, LayoutChunkWidthSubvoxels, OutError))
{
return false;
}
@@ -873,7 +914,11 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
OutError = TEXT("floating-island override uses a different Core voxel size; regenerate the plan.");
return false;
}
if(Island.manifest.recipe.layoutChunkWidthSubvoxels!=LayoutChunkWidthSubvoxels)
{OutError=TEXT("floating-island override uses a different strate-layout chunk width; regenerate the plan.");return false;}
}
if(Manifest->recipe.layoutChunkWidthSubvoxels!=LayoutChunkWidthSubvoxels)
{OutError=TEXT("world assembly manifest uses a different strate-layout chunk width; regenerate the plan.");return false;}
int32 SurfaceSlotCount = 0;
int32 FloatingIslandSlotCount = 0;
@@ -946,11 +991,13 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
// Keep the inspectable physical-layout export separate from the manifest identity: it is
// assembled from the same resolved slots/plans, but adding it must not salt evaluation hashes.
VoxelForgeCore::ResolvedLayoutSnapshotV1 Layout;
VoxelForgeCore::ResolvedLayoutSnapshotV2 Layout;
Layout.worldIdentity = std::string("owner-world/seed/")
+ std::to_string(StrateManager->GetWorldSeed());
Layout.ownerVoxelSizeMicrometres = static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0));
Layout.coreVoxelSizeMicrometres = ExpectedCoreVoxelMicrons;
Layout.strateLayoutChunkSizeMicrometres=LayoutChunkSizeMicrometres;
Layout.strateLayoutChunkWidthSubvoxels=LayoutChunkWidthSubvoxels;
const auto generatorName = [](ECaveGeneratorType Type) -> const char*
{
switch (Type)
@@ -987,7 +1034,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
if (Slot.Definition == nullptr) continue;
const ECaveGeneratorType Type = StrateManager->GetGeneratorTypeForChunk(
FIntVector(0, 0, Slot.TopChunkZ));
VoxelForgeCore::ResolvedLayoutStratumV1 Item;
VoxelForgeCore::ResolvedLayoutStratumV2 Item;
Item.strateIndex = Slot.StrateIndex;
Item.generatorKind = generatorName(Type);
Item.stableId = Layout.worldIdentity + "/strate/" + std::to_string(Slot.StrateIndex)
@@ -998,12 +1045,11 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
Item.recipeVersion = Type == ECaveGeneratorType::SurfaceWorld ? Manifest->recipe.schemaVersion : 0;
Item.voxelSizeMicrometres = ExpectedCoreVoxelMicrons;
Item.interiorBottomMicrometres = static_cast<std::int64_t>(Slot.BottomChunkZ)
* CHUNK_SIZE * ExpectedCoreVoxelMicrons;
* LayoutChunkSizeMicrometres;
Item.interiorTopMicrometres = static_cast<std::int64_t>(Slot.TopChunkZ + 1)
* CHUNK_SIZE * ExpectedCoreVoxelMicrons;
Item.managerChunkWidthMicrometres = CHUNK_SIZE * OwnerVoxelMicrons;
Item.dispatchChunkWidthMicrometres = Type == ECaveGeneratorType::FloatingIslands
? CHUNK_SIZE * OwnerVoxelMicrons : CHUNK_SIZE * ExpectedCoreVoxelMicrons;
* LayoutChunkSizeMicrometres;
Item.managerChunkWidthMicrometres = LayoutChunkSizeMicrometres;
Item.dispatchChunkWidthMicrometres = LayoutChunkSizeMicrometres;
Item.transitionMode = transitionName(Slot.Definition->TransitionType);
Item.transitionBlendChunks = Slot.Definition->TransitionBlendChunks;
Item.transitionWidthMicrometres = static_cast<std::int64_t>(Item.transitionBlendChunks)
@@ -1011,13 +1057,16 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
Item.transitionWarpAmplitudeMicrometres = Item.transitionMode == "Interleaved"
? 2 * Item.managerChunkWidthMicrometres : 0;
Item.transitionInterpenetrationMicrometres = 0;
Item.transitionHonouredInCore = false;
Item.transitionDispatchPath = "upper strate owns lower interface; manager GetGenerationParams can blend legacy vectors, but Core's compiled field selects per-strate/native recipe fields without transition blending";
Item.transitionHonouredInCore = Slot.Definition->TransitionType == EVoxelStrateTransition::Hard;
Item.transitionFallsBackToHard = Slot.Definition->TransitionType != EVoxelStrateTransition::Hard;
Item.transitionDispatchPath = Item.transitionFallsBackToHard
? "declared Gradient/Interleaved recorded; Core uses an explicit Hard transition fallback with zero interpenetration"
: "declared Hard transition is applied at this interface";
Item.managerLookup = "UVoxelStrateManager::GetGeneratorTypeForChunk; transition is controlled by upper strate";
if (Type == ECaveGeneratorType::SurfaceWorld)
{
Item.boundDispatchPath = "BuildSurfaceStratumPlan uses slot chunkZ * 32 Core voxels";
Item.queryDispatchPath = "NativeSurfaceDensityAt uses floor(CoreZ / 32), then GetGeneratorTypeForChunk/GetStrateIndex";
Item.boundDispatchPath = "BuildSurfaceStratumPlan uses resolved layout chunk width from ResolvedLayoutSnapshotV2";
Item.queryDispatchPath = "NativeSurfaceDensityAt selects floor(CoreZ / resolved layout chunk width) from ResolvedLayoutSnapshotV2";
const auto Plan = std::find_if(Manifest->plan.strates.begin(), Manifest->plan.strates.end(),
[&Slot](const auto& Entry) { return Entry.strateIndex == Slot.StrateIndex; });
if (Plan != Manifest->plan.strates.end())
@@ -1025,7 +1074,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
for (const auto& Profile : Manifest->recipe.biomes)
if (Profile.hasCapProfile)
{
VoxelForgeCore::ResolvedLayoutAnchorV1 Anchor;
VoxelForgeCore::ResolvedLayoutAnchorV2 Anchor;
Anchor.kind = "cap_profile";
Anchor.stableId = "biome/" + std::to_string(Profile.stableId);
Anchor.units = "1/256 Core voxel";
@@ -1037,7 +1086,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
for (std::size_t RouteIndex = 0; RouteIndex < Plan->route.size(); ++RouteIndex)
{
const auto& Point = Plan->route[RouteIndex];
VoxelForgeCore::ResolvedLayoutAnchorV1 Anchor;
VoxelForgeCore::ResolvedLayoutAnchorV2 Anchor;
Anchor.kind = "route_point"; Anchor.stableId = "route/" + std::to_string(RouteIndex);
Anchor.units = "1/256 Core voxel";
Anchor.values = { Point.positionSubvoxels[0], Point.positionSubvoxels[1],
@@ -1045,17 +1094,11 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
Item.anchors.push_back(std::move(Anchor));
}
}
Item.ceiling = { "ceiling", Item.interiorTopMicrometres, Item.interiorTopMicrometres,
false, true, false, "no separate volumetric boundary band; cap profile remains content",
"none", "Heightfield2D cap + AppendStratumBoundaryCeilings fallback mesh" };
Item.bedrock = { "bedrock", Item.interiorBottomMicrometres,
Item.interiorBottomMicrometres, false, true, false,
"no lower bedrock band", "none", "none" };
}
else if (Type == ECaveGeneratorType::FloatingIslands)
{
Item.boundDispatchPath = "BuildFloatingIslandStratumPlan uses slot chunkZ * 32 * owner/Core voxel ratio";
Item.queryDispatchPath = "NativeFloatingIslandDensityAt uses floor(CoreZ / (32 * owner/Core ratio)), then manager lookup";
Item.boundDispatchPath = "BuildFloatingIslandStratumPlan uses resolved layout chunk width from ResolvedLayoutSnapshotV2";
Item.queryDispatchPath = "NativeFloatingIslandDensityAt selects floor(CoreZ / resolved layout chunk width) from ResolvedLayoutSnapshotV2";
const auto Plan = std::find_if(Manifest->floatingIslands.begin(), Manifest->floatingIslands.end(),
[&Slot](const auto& Entry) { return Entry.strateIndex == Slot.StrateIndex; });
std::int64_t SealMicrometres = 0;
@@ -1063,12 +1106,11 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
{
Item.interiorBottomMicrometres = FixedToMicrons(Plan->manifest.recipe.layoutMinimumZSubvoxels);
Item.interiorTopMicrometres = FixedToMicrons(Plan->manifest.recipe.layoutMaximumZSubvoxels);
SealMicrometres = FixedToMicrons(Plan->manifest.recipe.boundarySealSubvoxels);
Item.recipeVersion = Plan->manifest.recipe.geometryVersion;
for (std::size_t IslandIndex = 0; IslandIndex < Plan->manifest.plan.islands.size(); ++IslandIndex)
{
const auto& Island = Plan->manifest.plan.islands[IslandIndex];
VoxelForgeCore::ResolvedLayoutAnchorV1 Anchor;
VoxelForgeCore::ResolvedLayoutAnchorV2 Anchor;
Anchor.kind = "island_anchor"; Anchor.stableId = "island/" + std::to_string(IslandIndex);
Anchor.units = "1/256 Core voxel";
Anchor.values = { Island.centerSubvoxels[0], Island.centerSubvoxels[1],
@@ -1076,38 +1118,35 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
Item.anchors.push_back(std::move(Anchor));
}
}
Item.ceiling = { "ceiling", Item.interiorTopMicrometres - SealMicrometres,
Item.interiorTopMicrometres, SealMicrometres > 0, true, false,
"v3 shared upper boundary seal; one-sided and inset from upper edge",
"upper/interior side", "VF_ApplyUpperBoundarySeal" };
Item.bedrock = { "bedrock", Item.interiorBottomMicrometres,
Item.interiorBottomMicrometres, false, true, false,
"no lower seal in FloatingIsland geometryVersion 3", "none", "none" };
}
else
{
Item.boundDispatchPath = "resolved manager slot scaled by Core chunk width";
Item.queryDispatchPath = "legacy Core route; manager chunk selected by floor(CoreZ / 32)";
const std::int64_t SealMicrometres = static_cast<std::int64_t>(std::llround(
std::max(0.0f, Slot.Definition->GenerationParams.BoundarySealThickness)
* OwnerVoxelMicrons));
Item.ceiling = { "ceiling", Item.interiorTopMicrometres,
Item.interiorTopMicrometres + SealMicrometres, false, true, false,
"manager configures a two-sided legacy seal, but Core's compiled analytic field does not apply this density band",
"configured upper side", "legacy VF_ApplyBoundarySeal; absent from current Core density path" };
Item.bedrock = { "bedrock", Item.interiorBottomMicrometres - SealMicrometres,
Item.interiorBottomMicrometres, false, true, false,
"manager configures a two-sided legacy seal, but Core's compiled analytic field does not apply this density band; breakable by default",
"configured lower side", "legacy VF_ApplyBoundarySeal; absent from current Core density path" };
Item.boundDispatchPath = "resolved manager slot scaled by ResolvedLayoutSnapshotV2 chunk width";
Item.queryDispatchPath = "legacy Core route selects manager chunk by floor(CoreZ / resolved layout chunk width)";
}
const std::int64_t SealMicrometres=static_cast<std::int64_t>(std::llround(
std::max(0.0f,StrateBoundarySealThickness(*Slot.Definition))*OwnerVoxelMicrons));
if(SealMicrometres<=0)
{OutError=FString::Printf(TEXT("Strate %d has no positive BoundarySealThickness for shared ceiling and bedrock bands."),Slot.StrateIndex);return false;}
const std::int64_t SolidDensityQ16=std::max<std::int64_t>(1,WorldAssemblyQ16(
std::max(0.05f,StrateBoundarySolidDensity(*Slot.Definition))));
Item.ceiling={"ceiling",Item.interiorTopMicrometres-SealMicrometres,
Item.interiorTopMicrometres,true,true,false,"solid ceiling from strate BoundarySealThickness; breakable by default",
"interior-facing lower surface","shared resolved-layout density band",SolidDensityQ16};
Item.bedrock={"bedrock",Item.interiorBottomMicrometres,
Item.interiorBottomMicrometres+SealMicrometres,true,true,false,
"solid bedrock floor from strate BoundarySealThickness; breakable by default",
"interior-facing upper surface","shared resolved-layout density band",SolidDensityQ16};
Item.content = { "content", Item.interiorBottomMicrometres,
Item.interiorTopMicrometres, true, true, false, "resolved generator interior",
"none", "none" };
Item.content.bottomMicrometres+=SealMicrometres;
Item.content.topMicrometres-=SealMicrometres;
for (std::size_t PassageIndex = 0; PassageIndex < StrateManager->GetPassages().Num(); ++PassageIndex)
{
const FVoxelPassage& Passage = StrateManager->GetPassages()[static_cast<int32>(PassageIndex)];
if (Passage.UpperStrateIndex != Slot.StrateIndex && Passage.LowerStrateIndex != Slot.StrateIndex) continue;
VoxelForgeCore::ResolvedLayoutAnchorV1 Anchor;
VoxelForgeCore::ResolvedLayoutAnchorV2 Anchor;
Anchor.kind = "passage"; Anchor.stableId = "passage/" + std::to_string(PassageIndex);
Anchor.units = "milli owner voxels";
Anchor.values = { static_cast<std::int64_t>(std::llround(Passage.UpperPoint.X * 1000.0)),
@@ -1127,15 +1166,23 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
return false;
}
const std::string LayoutJson = Layout.ToJson();
const FString LayoutText = UTF8_TO_TCHAR(LayoutJson.c_str());
TSharedPtr<FJsonObject> LayoutObject;
const TSharedRef<TJsonReader<>> LayoutReader = TJsonReaderFactory<>::Create(LayoutText);
if (!FJsonSerializer::Deserialize(LayoutReader, LayoutObject) || !LayoutObject.IsValid())
Root->SetStringField(TEXT("resolved_layout_json"),UTF8_TO_TCHAR(LayoutJson.c_str()));
const auto SurfaceSpawnPlan=std::find_if(Manifest->plan.strates.begin(),Manifest->plan.strates.end(),
[](const auto& Plan){return !Plan.route.empty();});
if(SurfaceSpawnPlan!=Manifest->plan.strates.end())
{
OutError = TEXT("Core resolved-layout canonical JSON could not be embedded in the resolved snapshot.");
return false;
const auto& Spawn=SurfaceSpawnPlan->route.front();
TSharedRef<FJsonObject> ViewerStart=MakeShared<FJsonObject>();
ViewerStart->SetBoolField(TEXT("found_open_space"),true);
ViewerStart->SetNumberField(TEXT("x"),static_cast<double>(Spawn.positionSubvoxels[0])
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
ViewerStart->SetNumberField(TEXT("y"),static_cast<double>(Spawn.positionSubvoxels[1])
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
ViewerStart->SetNumberField(TEXT("z"),static_cast<double>(Spawn.positionSubvoxels[2])
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
Root->SetObjectField(TEXT("viewer_start"),ViewerStart);
}
Root->SetObjectField(TEXT("resolved_layout"), LayoutObject.ToSharedRef());
const std::string CanonicalJson = Manifest->ToJson();
if (CanonicalJson.empty())
@@ -1206,6 +1253,8 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
// Override only the temporary schema copy. Clipmap tuning on the owner's asset is preserved.
SettingsValue->AsObject()->SetNumberField(TEXT("ClipRadius"), CoreClipRadius);
SettingsValue->AsObject()->SetNumberField(TEXT("MaxClipLevel"), CoreMaxClipLevel);
SettingsValue->AsObject()->SetNumberField(TEXT("StrateLayoutChunkSizeMeters"),
Settings->StrateLayoutChunkSizeMeters);
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
Root->SetNumberField(TEXT("schema_version"), 1);
@@ -1225,6 +1274,8 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
Root->SetNumberField(TEXT("edge_seal_thickness"), Settings->EdgeSealThickness);
Root->SetNumberField(TEXT("total_strates"), StrateManager->GetNumStrates());
Root->SetNumberField(TEXT("inter_strate_gap_chunks"), Settings->InterStrateGapChunks);
Root->SetNumberField(TEXT("strate_layout_chunk_size_um"),
std::llround(Settings->StrateLayoutChunkSizeMeters*1000000.0));
Root->SetNumberField(TEXT("clip_radius"), CoreClipRadius);
Root->SetNumberField(TEXT("max_clip_level"), CoreMaxClipLevel);
Root->SetNumberField(TEXT("full_res_clip_levels"), Settings->FullResClipLevels);
@@ -1292,12 +1343,15 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
return false;
}
Root->SetArrayField(TEXT("density_samples"), {});
TSharedRef<FJsonObject> ViewerStart = MakeShared<FJsonObject>();
ViewerStart->SetBoolField(TEXT("found_open_space"), false);
ViewerStart->SetNumberField(TEXT("x"), 0.0);
ViewerStart->SetNumberField(TEXT("y"), 0.0);
ViewerStart->SetNumberField(TEXT("z"), 0.0);
Root->SetObjectField(TEXT("viewer_start"), ViewerStart);
if(!Root->HasField(TEXT("viewer_start")))
{
TSharedRef<FJsonObject> ViewerStart = MakeShared<FJsonObject>();
ViewerStart->SetBoolField(TEXT("found_open_space"), false);
ViewerStart->SetNumberField(TEXT("x"), 0.0);
ViewerStart->SetNumberField(TEXT("y"), 0.0);
ViewerStart->SetNumberField(TEXT("z"), 0.0);
Root->SetObjectField(TEXT("viewer_start"), ViewerStart);
}
FString WriterBuffer;
TSharedRef<FCoreJsonWriter> Writer = TJsonWriterFactory<TCHAR,
@@ -321,7 +321,7 @@ namespace
const FVector ChunkCenter(
(ChunkCoord.X + 0.5f) * (float)CHUNK_SIZE,
(ChunkCoord.Y + 0.5f) * (float)CHUNK_SIZE,
(ChunkCoord.Z + 0.5f) * (float)CHUNK_SIZE);
(ChunkCoord.Z + 0.5f) * Manager->GetStrateLayoutChunkWidthVoxels());
const float ChunkRadius = (float)CHUNK_SIZE * 0.8660254f + 3.0f;
const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
Cache.FloorProjections.SetNum(Passages.Num());
@@ -1103,7 +1103,7 @@ static bool VF_FindWalkableTunnelAir(
const FIntVector ChunkCoord(
FMath::FloorToInt(Position.X / (float)CHUNK_SIZE),
FMath::FloorToInt(Position.Y / (float)CHUNK_SIZE),
FMath::FloorToInt(Position.Z / (float)CHUNK_SIZE));
FMath::FloorToInt(Position.Z / Manager->GetStrateLayoutChunkWidthVoxels()));
const TArray<int32>& Nearby = VF_GetNearbyPassages(Manager, ChunkCoord);
const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
const FVoxelPassage* PassageData = Passages.GetData();
@@ -1138,7 +1138,7 @@ static bool VF_IsAnyPassageFloorAt(
const FIntVector ChunkCoord(
FMath::FloorToInt(Position.X / (float)CHUNK_SIZE),
FMath::FloorToInt(Position.Y / (float)CHUNK_SIZE),
FMath::FloorToInt(Position.Z / (float)CHUNK_SIZE));
FMath::FloorToInt(Position.Z / Manager->GetStrateLayoutChunkWidthVoxels()));
const TArray<int32>& Nearby = VF_GetNearbyPassages(Manager, ChunkCoord);
const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
const FVoxelPassage* PassageData = Passages.GetData();
@@ -1169,6 +1169,17 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
UE_LOG(LogTemp, Error, TEXT("[StrateManager] No settings provided!"));
return false;
}
if (!FMath::IsFinite(Settings->StrateLayoutChunkSizeMeters)
|| Settings->StrateLayoutChunkSizeMeters < 0.25f
|| Settings->StrateLayoutChunkSizeMeters > 64.0f)
{
UE_LOG(LogTemp, Error, TEXT("[StrateManager] StrateLayoutChunkSizeMeters must be in [0.25, 64] m."));
return false;
}
StrateLayoutChunkSizeMicrometres = static_cast<uint32>(std::llround(
static_cast<double>(Settings->StrateLayoutChunkSizeMeters) * 1000000.0));
StrateLayoutChunkWidthVoxels = static_cast<float>(
static_cast<double>(StrateLayoutChunkSizeMicrometres) / (10000.0 * VOXEL_SIZE));
FVoxelSeasonManifest SeasonManifest;
bool bUseSeason = false;
@@ -1342,10 +1353,11 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
Slot.Definition->FloatingIslandParams = SeasonStrate.Params.FloatingIslandParams;
Slot.HeightInChunks = SeasonStrate.HeightInChunks;
Slot.TopChunkZ = SeasonStrate.TopWorldZ / CHUNK_SIZE - 1;
Slot.BottomChunkZ = SeasonStrate.BottomWorldZ / CHUNK_SIZE;
if (SeasonStrate.TopWorldZ % CHUNK_SIZE != 0
|| SeasonStrate.BottomWorldZ % CHUNK_SIZE != 0
const int32 LayoutChunkVoxels=FMath::RoundToInt(StrateLayoutChunkWidthVoxels);
Slot.TopChunkZ = SeasonStrate.TopWorldZ / LayoutChunkVoxels - 1;
Slot.BottomChunkZ = SeasonStrate.BottomWorldZ / LayoutChunkVoxels;
if (SeasonStrate.TopWorldZ % LayoutChunkVoxels != 0
|| SeasonStrate.BottomWorldZ % LayoutChunkVoxels != 0
|| Slot.TopChunkZ != CurrentTopZ
|| Slot.BottomChunkZ != CurrentTopZ - (Slot.HeightInChunks - 1))
{
@@ -1525,8 +1537,8 @@ bool UVoxelStrateManager::SetComposerOverrideForStrate(
}
VF_SetComposerRuntimeBounds(
Override->Params,
(float)(TargetSlot->TopChunkZ + 1) * CHUNK_SIZE,
(float)TargetSlot->BottomChunkZ * CHUNK_SIZE);
(float)(TargetSlot->TopChunkZ + 1) * StrateLayoutChunkWidthVoxels,
(float)TargetSlot->BottomChunkZ * StrateLayoutChunkWidthVoxels);
Override->bUseRecipe = bUseRecipe;
if (Recipe != nullptr)
{
@@ -1545,8 +1557,8 @@ bool UVoxelStrateManager::SetComposerOverrideForStrate(
Override->bUseRegions = true;
Override->Regions = *InRegions;
VF_RekeyStrateRegionManifest(Override->Regions, CandidateSeed, StrateIndex);
Override->Regions.StrateTopWorldZ = (float)(TargetSlot->TopChunkZ + 1) * CHUNK_SIZE;
Override->Regions.StrateBottomWorldZ = (float)TargetSlot->BottomChunkZ * CHUNK_SIZE;
Override->Regions.StrateTopWorldZ = (float)(TargetSlot->TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Override->Regions.StrateBottomWorldZ = (float)TargetSlot->BottomChunkZ * StrateLayoutChunkWidthVoxels;
VF_SetStrateArchetypeRuntimeBounds(Override->Params,
Override->Regions.StrateTopWorldZ,
Override->Regions.StrateBottomWorldZ);
@@ -1796,11 +1808,11 @@ void UVoxelStrateManager::GeneratePassages()
const FStratePassageConfig& Cfg = UpperDef->PassageConfig;
// Upper strate floor and lower strate ceiling (differ when there's a bedrock gap).
const float UpperTopZ = (float)(Upper.TopChunkZ + 1) * CHUNK_SIZE;
const float UpperBottomZ = (float)(Upper.BottomChunkZ) * CHUNK_SIZE;
const float LowerTopZ = (float)(Lower.TopChunkZ + 1) * CHUNK_SIZE;
const float UpperMax = (float)Upper.HeightInChunks * CHUNK_SIZE * 0.9f;
const float LowerMax = (float)Lower.HeightInChunks * CHUNK_SIZE * 0.9f;
const float UpperTopZ = (float)(Upper.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
const float UpperBottomZ = (float)(Upper.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
const float LowerTopZ = (float)(Lower.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
const float UpperMax = (float)Upper.HeightInChunks * StrateLayoutChunkWidthVoxels * 0.9f;
const float LowerMax = (float)Lower.HeightInChunks * StrateLayoutChunkWidthVoxels * 0.9f;
const float DistLo = FMath::Min(Cfg.DistanceMin, Cfg.DistanceMax);
const float DistHi = FMath::Max(Cfg.DistanceMin, Cfg.DistanceMax);
@@ -1930,7 +1942,7 @@ void UVoxelStrateManager::GeneratePassages()
LowerDef->FloatingIslandParams,
LowerQuerySeed,
LowerTopZ,
(float)(Lower.BottomChunkZ) * CHUNK_SIZE,
(float)(Lower.BottomChunkZ) * StrateLayoutChunkWidthVoxels,
PX,
PY,
MaxLateralSnap,
@@ -1990,7 +2002,7 @@ void UVoxelStrateManager::GeneratePassages()
if (bAimedLowerAtPlayerFitPoint)
{
const float LowerSealThickness = BoundarySealThicknessFor(*LowerDef);
const float LowerBottomZ = (float)(Lower.BottomChunkZ) * CHUNK_SIZE;
const float LowerBottomZ = (float)(Lower.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
const float InnerBottomZ = LowerBottomZ + LowerSealThickness;
const float InnerTopZ = LowerTopZ - LowerSealThickness;
if (VoxelMath::IsFinite(SuggestedLowerPoint.X)
@@ -2158,7 +2170,7 @@ void UVoxelStrateManager::GeneratePassages()
Cfg.MouthRadius,
LowerDoorDirection,
LowerTopZ,
(float)(Lower.BottomChunkZ) * CHUNK_SIZE,
(float)(Lower.BottomChunkZ) * StrateLayoutChunkWidthVoxels,
LowerDef ? BoundarySealThicknessFor(*LowerDef) : 0.0f,
bAimedLowerAtPlayerFitPoint);
Passage.UpperPoint = Passage.UpperLanding.StandingPoint;
@@ -2583,8 +2595,8 @@ void UVoxelStrateManager::GeneratePassages()
if (bOpenSurfaceEntry && OriginSpineRadius > 0.0f && StrateLayout.Num() > 0)
{
const FStrateSlot& Top = StrateLayout[0];
const float TopZ = (float)(Top.TopChunkZ + 1) * CHUNK_SIZE;
const float BottomZ = (float)Top.BottomChunkZ * CHUNK_SIZE;
const float TopZ = (float)(Top.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
const float BottomZ = (float)Top.BottomChunkZ * StrateLayoutChunkWidthVoxels;
const float TopSeal = Top.Definition
? BoundarySealThicknessFor(*Top.Definition) : 0.0f;
const VoxelPassageGeometry::FOriginLandingGeometry OriginLanding =
@@ -2607,7 +2619,7 @@ void UVoxelStrateManager::GeneratePassages()
// the top seal into the finite landing room; lower strate seals remain untouched.
Entry.Radius = FMath::Max(OriginSpineRadius,
VoxelPassageGeometry::MetersToVoxels(0.25f));
Entry.UpperPoint = FVector(0.0f, 0.0f, TopZ + CHUNK_SIZE);
Entry.UpperPoint = FVector(0.0f, 0.0f, TopZ + StrateLayoutChunkWidthVoxels);
// The capsule's lower tangent meets the room ceiling; it never bores through the
// room's floor or creates an origin column in strate 0.
Entry.LowerPoint = FVector(
@@ -2671,7 +2683,7 @@ float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float
const FIntVector ChunkCoord(
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
// The shortlist is rebuilt once for a (manager, version, chunk) and is shared by the tube,
// landing, and floor paths. No source-fit stencil or topology search is allowed below it.
@@ -2817,7 +2829,7 @@ void UVoxelStrateManager::ApplyPassageModifier(
const FIntVector ChunkCoord(
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ);
@@ -2910,7 +2922,7 @@ void UVoxelStrateManager::ApplyPassageLandingAir(
const FIntVector ChunkCoord(
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
if (Nearby.Num() == 0) return;
@@ -3021,7 +3033,7 @@ void UVoxelStrateManager::ApplyPassageLandingFloorMC(
const FIntVector ChunkCoord(
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ);
@@ -3117,7 +3129,7 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
const FIntVector ChunkCoord(
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
if (Nearby.Num() == 0) return;
@@ -3244,7 +3256,7 @@ void UVoxelStrateManager::ApplyPassageNativeFloorMC(
const FIntVector ChunkCoord(
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
if (Nearby.Num() == 0
|| VoxelPassageGeometry::VerticalShaftConnectorAirMarker())
@@ -3435,7 +3447,7 @@ void UVoxelStrateManager::ApplyPassageLandingRoomFloorMC(
const FIntVector ChunkCoord(
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ);
@@ -5909,8 +5921,8 @@ bool UVoxelStrateManager::AnyLandingFloorAtLattice(
for (const FStrateSlot& Slot : StrateLayout)
{
if (Slot.Definition == nullptr) continue;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
VoxelPassageGeometry::BuildOriginLandingGeometry(
@@ -5952,8 +5964,8 @@ bool UVoxelStrateManager::AnyOriginLandingNearBox(
for (const FStrateSlot& Slot : StrateLayout)
{
if (Slot.Definition == nullptr) continue;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
if (VoxelPassageGeometry::OriginLandingRoomTouchesBox(
VoxelBox, TopZ, BottomZ,
VF_BoundarySealThicknessForDefinition(*Slot.Definition),
@@ -5973,8 +5985,8 @@ bool UVoxelStrateManager::AnyOriginLandingFloorNearBox(
for (const FStrateSlot& Slot : StrateLayout)
{
if (Slot.Definition == nullptr) continue;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
if (VoxelPassageGeometry::OriginLandingFloorTouchesBox(
VoxelBox, TopZ, BottomZ,
VF_BoundarySealThicknessForDefinition(*Slot.Definition),
@@ -5998,8 +6010,8 @@ bool UVoxelStrateManager::AnyOriginLandingNearLattice(
for (const FStrateSlot& Slot : StrateLayout)
{
if (Slot.Definition == nullptr) continue;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
if (VoxelPassageGeometry::OriginLandingRoomTouchesLattice(
VoxelBox, LatticeOrigin, Step, TopZ, BottomZ, Seal, OriginSpineRadius,
@@ -6036,8 +6048,8 @@ bool UVoxelStrateManager::AnyOriginLandingAirNearLattice(
{
continue;
}
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
VoxelPassageGeometry::BuildOriginLandingGeometry(
@@ -6113,8 +6125,8 @@ bool UVoxelStrateManager::AnyOriginLandingFloorNearLattice(
for (const FStrateSlot& Slot : StrateLayout)
{
if (Slot.Definition == nullptr) continue;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
if (VoxelPassageGeometry::OriginLandingFloorTouchesLattice(
VoxelBox, LatticeOrigin, Step, TopZ, BottomZ, Seal, OriginSpineRadius))
@@ -6148,7 +6160,7 @@ int32 UVoxelStrateManager::FindSlotIndexForChunkZ(int32 ChunkZ) const
UVoxelStrateDefinition* UVoxelStrateManager::GetStrateAt(float WorldZ) const
{
// Convert world Z to chunk Z coordinate
int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / CHUNK_SIZE);
int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / StrateLayoutChunkWidthVoxels);
int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ);
if (SlotIdx >= 0)
{
@@ -6159,7 +6171,7 @@ UVoxelStrateDefinition* UVoxelStrateManager::GetStrateAt(float WorldZ) const
int32 UVoxelStrateManager::GetStrateIndex(float WorldZ) const
{
int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / CHUNK_SIZE);
int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / StrateLayoutChunkWidthVoxels);
int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ);
if (SlotIdx >= 0)
{
@@ -6318,9 +6330,9 @@ FSlabGenerationParams UVoxelStrateManager::GetSlabParamsForChunk(const FIntVecto
// Fill in the runtime Z bounds (voxel coordinates, same convention as
// FStrateGenerationParams::StrateTopWorldZ / StrateBottomWorldZ).
// TopChunkZ+1 because the top chunk's CEILING is at (TopChunkZ+1)*CHUNK_SIZE.
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE;
// TopChunkZ+1 because the top chunk's CEILING is at (TopChunkZ+1)*StrateLayoutChunkWidthVoxels.
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
return Result;
}
@@ -6353,8 +6365,8 @@ FMazeGenerationParams UVoxelStrateManager::GetMazeParamsForChunk(const FIntVecto
}
}
#endif
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
return Result;
}
@@ -6380,8 +6392,8 @@ FSurfaceGenerationParams UVoxelStrateManager::GetSurfaceParamsForChunk(const FIn
}
}
#endif
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
return Result;
}
@@ -6407,8 +6419,8 @@ FVerticalShaftParams UVoxelStrateManager::GetVerticalShaftParamsForChunk(const F
}
}
#endif
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
return Result;
}
@@ -6434,8 +6446,8 @@ FFloatingIslandParams UVoxelStrateManager::GetFloatingIslandParamsForChunk(const
}
}
#endif
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
return Result;
}
@@ -6469,14 +6481,14 @@ FBiomeContext UVoxelStrateManager::GetBiomeContextForChunk(const FIntVector& Chu
bool UVoxelStrateManager::GetStrateUnrealZRange(float WorldZ, float& OutTopZ, float& OutBottomZ) const
{
const int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / CHUNK_SIZE);
const int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / StrateLayoutChunkWidthVoxels);
const int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ);
if (SlotIdx < 0) return false;
const FStrateSlot& Slot = StrateLayout[SlotIdx];
// Voxel-space Z bounds → Unreal units. Ceiling = top chunk's upper edge.
OutTopZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE * VOXEL_SIZE;
OutBottomZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE * VOXEL_SIZE;
OutTopZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels * VOXEL_SIZE;
OutBottomZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels * VOXEL_SIZE;
return true;
}
@@ -6489,8 +6501,8 @@ FStrateDisturbanceParams UVoxelStrateManager::GetDisturbanceParamsForChunk(const
}
const FStrateSlot& Slot = StrateLayout[SlotIdx];
FStrateDisturbanceParams Result = Slot.Definition->Disturbances;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
return Result;
}
@@ -6522,8 +6534,8 @@ float UVoxelStrateManager::GetWaterLevelWorldZForChunk(const FIntVector& ChunkCo
}
if (Rel <= 0.0f) return -FLT_MAX;
const float BottomZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE;
const float TopZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
const float BottomZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
const float TopZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
return FMath::Lerp(BottomZ, TopZ, FMath::Clamp(Rel, 0.0f, 1.0f));
}
@@ -6567,8 +6579,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
FindComposerOverride(Slot.StrateIndex))
{
FStrateGenerationParams Result = Override->Params.TunnelNetworkParams;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
// A candidate is a hard replacement of this slot. Do not blend its rolled vector with an
// authored neighbour at a boundary; this is also how the offline fixture measures it.
return ResolveFinalParams(Result);
@@ -6578,8 +6590,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (SeasonStrates.IsValidIndex(SlotIdx))
{
FStrateGenerationParams Result = SeasonStrates[SlotIdx].Params.TunnelNetworkParams;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
// Season vectors are already final, measured slot records. Vertical structural posts own
// their boundary; blending them with another selected recipe would describe neither one.
return ResolveFinalParams(Result);
@@ -6596,8 +6608,17 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
//
// TopChunkZ=0, CHUNK_SIZE=32: top of the strate = chunk 0's top edge = voxel Z=32
// BottomChunkZ=-3: bottom of the strate = chunk -3's bottom edge = voxel Z=-3*32 = -96
BaseParams.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
BaseParams.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE;
BaseParams.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
BaseParams.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
// Core currently dispatches one generator field per resolved strate. Gradient and
// interleaved metadata therefore use the explicitly declared Hard fallback for every
// legacy parameter lookup as well; mixing parameter vectors here would disagree with the
// shared ceiling/content/bedrock composition layer.
if (bHardTransitionFallbackForAssembly)
{
return ResolveFinalParams(BaseParams);
}
//=========================================================================
// BOUNDARY BLENDING (transition-type aware)
@@ -6670,8 +6691,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (EffectiveBlend > 0 && DistFromBottom < EffectiveBlend)
{
FStrateGenerationParams BelowParams = BuildParamsFromDefinition(BelowSlot.Definition);
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * CHUNK_SIZE;
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * CHUNK_SIZE;
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
// Linear alpha: 0 at EffectiveBlend chunks away, 1 at the boundary
float Alpha = 1.0f - ((float)DistFromBottom / (float)EffectiveBlend);
@@ -6708,8 +6729,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (DistFromBottom < CheckRange)
{
FStrateGenerationParams BelowParams = BuildParamsFromDefinition(BelowSlot.Definition);
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * CHUNK_SIZE;
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * CHUNK_SIZE;
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
// Sample 3D Perlin noise to warp the boundary position.
// XY frequency 0.15 gives medium-scale variation (~6-7 chunks per cycle).
@@ -6769,8 +6790,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (EffectiveBlend > 0 && DistFromTop < EffectiveBlend)
{
FStrateGenerationParams AboveParams = BuildParamsFromDefinition(AboveSlot.Definition);
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * CHUNK_SIZE;
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * CHUNK_SIZE;
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
float Alpha = 1.0f - ((float)DistFromTop / (float)EffectiveBlend);
Alpha = FMath::Clamp(Alpha, 0.0f, 1.0f);
@@ -6791,8 +6812,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (DistFromTop < CheckRange)
{
FStrateGenerationParams AboveParams = BuildParamsFromDefinition(AboveSlot.Definition);
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * CHUNK_SIZE;
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * CHUNK_SIZE;
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
// Same noise function but with a different seed offset to avoid
// symmetry between top and bottom boundaries of adjacent strates
+7
View File
@@ -419,6 +419,13 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates", meta = (ClampMin = "0", ClampMax = "16"))
int32 InterStrateGapChunks = 0;
// Physical width of one vertical strate-layout chunk. This is independent of the
// Core tile width (32^3 voxels) and of the source generator's voxel resolution.
// 8 m preserves the owner's current SurfaceWorld extent at 25 cm Core voxels.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates|World Assembly",
meta = (ClampMin = "0.25", ClampMax = "64.0", Units = "m"))
float StrateLayoutChunkSizeMeters = 8.0f;
//=========================================================================
// SPINE & INTER-STRATE CONNECTIONS (the (0,0) descent axis)
//=========================================================================
@@ -244,6 +244,13 @@ public:
UFUNCTION(BlueprintCallable, Category = "Strate")
int32 GetStrateIndex(float WorldZ) const;
// Director-resolved physical strate chunk width expressed in owner voxel units.
// This is independent of Core's 32^3 tile width.
float GetStrateLayoutChunkWidthVoxels() const { return StrateLayoutChunkWidthVoxels; }
uint32 GetStrateLayoutChunkSizeMicrometres() const { return StrateLayoutChunkSizeMicrometres; }
void SetHardTransitionFallbackForAssembly(bool bEnabled)
{ bHardTransitionFallbackForAssembly = bEnabled; }
/** Layout/passage generation counter (bumped by every Initialize → GeneratePassages).
* Hot-path callers key thread_local memos on this so an editor rebuild (RebuildStrates /
* live edit) can never serve a stale strate index or passage shortlist. */
@@ -525,6 +532,11 @@ protected:
// VoxelSettings::InterStrateGapChunks during Initialize().
int32 InterStrateGapChunks = 0;
// Resolved once from VoxelSettings::StrateLayoutChunkSizeMeters during Initialize().
float StrateLayoutChunkWidthVoxels = 32.0f;
uint32 StrateLayoutChunkSizeMicrometres = 8000000;
bool bHardTransitionFallbackForAssembly = false;
// Production keeps the legacy writer available only as a fail-closed fallback for a
// non-native/malformed passage; native D-floors bypass it. This switch never changes the
// passage carve or air contract.