refactor: resolved metre layout is the runtime authority (L41, migration step 3); stamp covers plugin sources; R8

UVoxelStrateManager::Initialize resolves the layout width to integer micrometres before passages or
scheduling; each slot stores physical [bottom, top) bounds, transition mode/width and passage settings;
chunk indices remain a floor-based compatibility projection. The Core adapter derives interiors and L29
bands from those resolved values. verify: full-tier cost baseline is HEAD (or --baseline), tier timeout
7200 s with 1800 s per process; the source stamp now includes the plugin Source tree compiled on Linux
(it previously missed VoxelStrateManager.cpp and friends). Owner hashes identical to L37; fast, medium
and full PASS on a clean rebuild. Unreal manager/adapter edits are uncompiled on Windows.
Research R8: inventory of unit-bearing UPROPERTYs and a custom-version asset migration plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-23 19:36:51 +02:00
1 parent efa9e89468
commit 1abee91721
11 files changed
+662 -215

No files matched your search

@@ -107,6 +107,14 @@ std::int64_t WorldAssemblyRoundRatio(std::int64_t Numerator, std::int64_t Denomi
return -((-Numerator + Denominator / 2) / Denominator);
}
std::int64_t WorldAssemblyMicrometresToSubvoxels(
std::int64_t Micrometres, std::uint32_t CoreVoxelMicrometres)
{
if (CoreVoxelMicrometres == 0) return 0;
return WorldAssemblyRoundRatio(Micrometres * WorldAssemblySubvoxelsPerVoxel,
static_cast<std::int64_t>(CoreVoxelMicrometres));
}
std::uint64_t WorldAssemblyRoundedSqrt(std::uint64_t Value)
{
std::uint64_t Root = 0;
@@ -312,17 +320,18 @@ VoxelForgeCore::WorldAssemblyBiomeProfileV1 BuildSurfaceProfile(
bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager,
const FStrateSlot& Slot,
VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
VoxelForgeCore::WorldAssemblyStratumPlanV1& Stratum, FString& OutError)
VoxelForgeCore::WorldAssemblyStratumPlanV1& Stratum,
std::uint32_t CoreVoxelMicrometres, FString& OutError)
{
using namespace VoxelForgeCore;
const FIntVector Chunk(0, 0, Slot.TopChunkZ);
const FSurfaceGenerationParams Surface = StrateManager->GetSurfaceParamsForChunk(Chunk);
const FBiomeContext BiomeContext = StrateManager->GetBiomeContextForChunk(Chunk);
const UVoxelStrateDefinition* Definition = Slot.Definition;
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 double BottomVoxel = static_cast<double>(Slot.BottomWorldMicrometres)
/ std::max<std::uint32_t>(1, CoreVoxelMicrometres);
const double HeightVoxels = static_cast<double>(Slot.HeightMicrometres)
/ std::max<std::uint32_t>(1, CoreVoxelMicrometres);
const FBiomeMapParams& Map = BiomeContext.Map;
Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
Recipe.biomeCellSizeSubvoxels = WorldAssemblyFixed(Map.CellSize);
@@ -454,9 +463,12 @@ bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager,
return false;
}
Shelf.minimumSubvoxels = {WorldAssemblyFixed(MinX), WorldAssemblyFixed(MinY),
std::max(WorldAssemblyFixed(BottomVoxel), LowestGround - WorldAssemblyFixed(4.0))};
std::max(WorldAssemblyMicrometresToSubvoxels(
Slot.BottomWorldMicrometres, CoreVoxelMicrometres),
LowestGround - WorldAssemblyFixed(4.0))};
Shelf.maximumSubvoxels = {WorldAssemblyFixed(MaxX), WorldAssemblyFixed(MaxY),
std::min(WorldAssemblyFixed((Slot.TopChunkZ + 1) * LayoutChunkVoxels),
std::min(WorldAssemblyMicrometresToSubvoxels(
Slot.TopWorldMicrometres, CoreVoxelMicrometres),
HighestGround + WorldAssemblyFixed(ShelfHeight + 4.0))};
Shelf.parametersSubvoxels = {WorldAssemblyFixed(DirX), WorldAssemblyFixed(DirY),
WorldAssemblyFixed(ShelfHeight), WorldAssemblyFixed(Reach)};
@@ -479,7 +491,7 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
using namespace VoxelForgeCore;
const FIntVector Chunk(0, 0, Slot.TopChunkZ);
const FFloatingIslandParams Params = StrateManager->GetFloatingIslandParamsForChunk(Chunk);
const FStratePassageConfig& Passage = Slot.Definition->PassageConfig;
const FStratePassageConfig& Passage = Slot.ResolvedPassageConfig;
WorldAssemblyFloatingIslandPlanV1 IslandPlan;
IslandPlan.strateIndex = Slot.StrateIndex;
IslandPlan.topChunkZ = Slot.TopChunkZ;
@@ -500,10 +512,10 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
}
Recipe.ownerToCoreScaleNumerator = Recipe.ownerVoxelSizeMicrons / ScaleGcd;
Recipe.ownerToCoreScaleDenominator = Recipe.coreVoxelSizeMicrons / ScaleGcd;
Recipe.layoutMinimumZSubvoxels = static_cast<std::int64_t>(Slot.BottomChunkZ)
* LayoutChunkWidthSubvoxels;
Recipe.layoutMaximumZSubvoxels = static_cast<std::int64_t>(Slot.TopChunkZ + 1)
* LayoutChunkWidthSubvoxels;
Recipe.layoutMinimumZSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.BottomWorldMicrometres, Recipe.coreVoxelSizeMicrons);
Recipe.layoutMaximumZSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.TopWorldMicrometres, Recipe.coreVoxelSizeMicrons);
Recipe.layoutCenterTopZSubvoxels = (Recipe.layoutMinimumZSubvoxels
+ Recipe.layoutMaximumZSubvoxels) / 2;
Recipe.islandDensityQ16 = WorldAssemblyQ16(FMath::Clamp(Params.IslandDensity, 0.0f, 1.0f));
@@ -524,20 +536,35 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
*WorldAssemblySubvoxelsPerVoxel/Recipe.coreVoxelSizeMicrons));
};
Recipe.anchorIslandOrdinal=Recipe.primaryIslandCount-1;
Recipe.anchorPlacementRadiusSubvoxels=OwnerVoxelsToCoreQ(Passage.DistanceMax);
const std::int64_t spiralRadius=OwnerVoxelsToCoreQ(Passage.SpiralRadius);
Recipe.anchorPlacementRadiusSubvoxels=WorldAssemblyMicrometresToSubvoxels(
Slot.PassageDistanceMaximumMicrometres, Recipe.coreVoxelSizeMicrons);
const std::int64_t spiralRadius=WorldAssemblyMicrometresToSubvoxels(
Slot.PassageSpiralRadiusMicrometres, Recipe.coreVoxelSizeMicrons);
// Reserve at least a 1.5 m mid-bore through the anchor root.
constexpr double AnchorPassageMinimumBoreOwnerVoxels=12.0;
const std::int64_t mouthBore=std::max(OwnerVoxelsToCoreQ(Passage.MouthRadius),
OwnerVoxelsToCoreQ(12.0));
const std::int64_t bore=std::max(OwnerVoxelsToCoreQ(Passage.MidRadius),
OwnerVoxelsToCoreQ(AnchorPassageMinimumBoreOwnerVoxels));
const std::int64_t wallReserve=OwnerVoxelsToCoreQ(12.0);
Recipe.anchorMouthOffsetSubvoxels=spiralRadius+mouthBore+OwnerVoxelsToCoreQ(4.0);
const std::int64_t excursion=std::max(OwnerVoxelsToCoreQ(Passage.Wander),spiralRadius);
const std::int64_t OwnerVoxelMicrometres = Recipe.ownerVoxelSizeMicrons;
const std::int64_t mouthBore=std::max(
WorldAssemblyMicrometresToSubvoxels(Slot.PassageMouthRadiusMicrometres,
Recipe.coreVoxelSizeMicrons),
WorldAssemblyMicrometresToSubvoxels(12 * OwnerVoxelMicrometres,
Recipe.coreVoxelSizeMicrons));
const std::int64_t bore=std::max(
WorldAssemblyMicrometresToSubvoxels(Slot.PassageMidRadiusMicrometres,
Recipe.coreVoxelSizeMicrons),
WorldAssemblyMicrometresToSubvoxels(
static_cast<std::int64_t>(AnchorPassageMinimumBoreOwnerVoxels * OwnerVoxelMicrometres),
Recipe.coreVoxelSizeMicrons));
const std::int64_t wallReserve=WorldAssemblyMicrometresToSubvoxels(
12 * OwnerVoxelMicrometres, Recipe.coreVoxelSizeMicrons);
const std::int64_t FourOwnerVoxelsSubvoxels=WorldAssemblyMicrometresToSubvoxels(
4 * OwnerVoxelMicrometres, Recipe.coreVoxelSizeMicrons);
Recipe.anchorMouthOffsetSubvoxels=spiralRadius+mouthBore+FourOwnerVoxelsSubvoxels;
const std::int64_t excursion=std::max(
WorldAssemblyMicrometresToSubvoxels(Slot.PassageWanderMicrometres,
Recipe.coreVoxelSizeMicrons), spiralRadius);
Recipe.anchorRootRadiusSubvoxels=excursion+bore+wallReserve;
Recipe.anchorIslandMinimumRadiusSubvoxels=std::max(Recipe.minimumRadiusSubvoxels,
Recipe.anchorMouthOffsetSubvoxels+mouthBore+OwnerVoxelsToCoreQ(4.0));
Recipe.anchorMouthOffsetSubvoxels+mouthBore+FourOwnerVoxelsSubvoxels);
Recipe.anchorRootBottomZSubvoxels=Recipe.layoutMinimumZSubvoxels
+Recipe.boundarySealSubvoxels;
std::string PlanError;
@@ -882,7 +909,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
SlotRecipe.layoutChunkWidthSubvoxels=LayoutChunkWidthSubvoxels;
VoxelForgeCore::WorldAssemblyStratumPlanV1 Stratum;
if (!BuildSurfaceStratumPlan(StrateManager, Slot,
SlotRecipe, Stratum, OutError))
SlotRecipe, Stratum, CoreVoxelMicrometres, OutError))
{
return false;
}
@@ -1048,41 +1075,47 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
return "Unknown";
};
const std::int64_t OwnerVoxelMicrons = Layout.ownerVoxelSizeMicrometres;
const auto FixedToMicrons = [ExpectedCoreVoxelMicrons](std::int64_t Fixed)
{
return static_cast<std::int64_t>(std::llround(static_cast<double>(Fixed)
* ExpectedCoreVoxelMicrons / VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
};
std::vector<VoxelForgeCore::ResolvedLayoutPassageConfigV1> PassageConfigs;
for (const FStrateSlot& Slot : StrateManager->GetLayout())
{
if (Slot.Definition == nullptr) continue;
const FStratePassageConfig& AuthoredPassage = Slot.Definition->PassageConfig;
const auto OwnerVoxelsToCoreQ = [OwnerVoxelMicrons, ExpectedCoreVoxelMicrons](double Value)
{
return static_cast<std::int64_t>(std::llround(Value * OwnerVoxelMicrons
* VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel / ExpectedCoreVoxelMicrons));
};
const FStratePassageConfig& ResolvedPassage = Slot.ResolvedPassageConfig;
VoxelForgeCore::ResolvedLayoutPassageConfigV1 PassageConfig;
PassageConfig.upperStrateIndex = Slot.StrateIndex;
PassageConfig.connections = AuthoredPassage.Connections;
PassageConfig.style = static_cast<std::uint32_t>(AuthoredPassage.Style);
PassageConfig.mouthRadiusSubvoxels = std::max(OwnerVoxelsToCoreQ(AuthoredPassage.MouthRadius),
OwnerVoxelsToCoreQ(12.0));
PassageConfig.midRadiusSubvoxels = std::max(OwnerVoxelsToCoreQ(AuthoredPassage.MidRadius),
OwnerVoxelsToCoreQ(12.0));
PassageConfig.reachMinimumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.ReachMin);
PassageConfig.reachMaximumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.ReachMax);
PassageConfig.distanceMinimumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.DistanceMin);
PassageConfig.distanceMaximumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.DistanceMax);
PassageConfig.wanderSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.Wander);
PassageConfig.segments = static_cast<std::uint32_t>(FMath::Clamp(AuthoredPassage.Segments,1,48));
PassageConfig.verticalWobbleSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.VerticalWobble);
PassageConfig.spiralRadiusSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.SpiralRadius);
PassageConfig.connections = ResolvedPassage.Connections;
PassageConfig.style = static_cast<std::uint32_t>(ResolvedPassage.Style);
PassageConfig.mouthRadiusSubvoxels = std::max(
WorldAssemblyMicrometresToSubvoxels(
Slot.PassageMouthRadiusMicrometres, ExpectedCoreVoxelMicrons),
WorldAssemblyMicrometresToSubvoxels(
12 * OwnerVoxelMicrons, ExpectedCoreVoxelMicrons));
PassageConfig.midRadiusSubvoxels = std::max(
WorldAssemblyMicrometresToSubvoxels(
Slot.PassageMidRadiusMicrometres, ExpectedCoreVoxelMicrons),
WorldAssemblyMicrometresToSubvoxels(
12 * OwnerVoxelMicrons, ExpectedCoreVoxelMicrons));
PassageConfig.reachMinimumSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageReachMinimumMicrometres, ExpectedCoreVoxelMicrons);
PassageConfig.reachMaximumSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageReachMaximumMicrometres, ExpectedCoreVoxelMicrons);
PassageConfig.distanceMinimumSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageDistanceMinimumMicrometres, ExpectedCoreVoxelMicrons);
PassageConfig.distanceMaximumSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageDistanceMaximumMicrometres, ExpectedCoreVoxelMicrons);
PassageConfig.wanderSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageWanderMicrometres, ExpectedCoreVoxelMicrons);
PassageConfig.segments = static_cast<std::uint32_t>(
FMath::Clamp(ResolvedPassage.Segments, 1, 48));
PassageConfig.verticalWobbleSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageVerticalWobbleMicrometres, ExpectedCoreVoxelMicrons);
PassageConfig.spiralRadiusSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageSpiralRadiusMicrometres, ExpectedCoreVoxelMicrons);
PassageConfig.spiralTurnsQ16 = static_cast<std::int64_t>(std::llround(
static_cast<double>(AuthoredPassage.SpiralTurns)*65536.0));
PassageConfig.cascadeSteps = static_cast<std::uint32_t>(FMath::Clamp(AuthoredPassage.CascadeSteps,1,16));
PassageConfig.cascadeLedgeSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.CascadeLedge);
static_cast<double>(ResolvedPassage.SpiralTurns) * 65536.0));
PassageConfig.cascadeSteps = static_cast<std::uint32_t>(
FMath::Clamp(ResolvedPassage.CascadeSteps, 1, 16));
PassageConfig.cascadeLedgeSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageCascadeLedgeMicrometres, ExpectedCoreVoxelMicrons);
PassageConfigs.push_back(PassageConfig);
const ECaveGeneratorType Type = StrateManager->GetGeneratorTypeForChunk(
FIntVector(0, 0, Slot.TopChunkZ));
@@ -1096,16 +1129,13 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
: std::string("legacy.") + generatorName(Type);
Item.recipeVersion = Type == ECaveGeneratorType::SurfaceWorld ? Manifest->recipe.schemaVersion : 0;
Item.voxelSizeMicrometres = ExpectedCoreVoxelMicrons;
Item.interiorBottomMicrometres = static_cast<std::int64_t>(Slot.BottomChunkZ)
* LayoutChunkSizeMicrometres;
Item.interiorTopMicrometres = static_cast<std::int64_t>(Slot.TopChunkZ + 1)
* LayoutChunkSizeMicrometres;
Item.interiorBottomMicrometres = Slot.BottomWorldMicrometres;
Item.interiorTopMicrometres = Slot.TopWorldMicrometres;
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)
* Item.managerChunkWidthMicrometres;
Item.transitionMode = transitionName(Slot.ResolvedTransitionType);
Item.transitionBlendChunks = Slot.ResolvedTransitionBlendChunks;
Item.transitionWidthMicrometres = Slot.TransitionWidthMicrometres;
Item.transitionWarpAmplitudeMicrometres = Item.transitionMode == "Interleaved"
? std::min(Item.transitionWidthMicrometres / 2, Item.managerChunkWidthMicrometres) : 0;
Item.transitionInterpenetrationMicrometres = Item.transitionMode == "Interleaved"
@@ -1161,11 +1191,8 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
Item.queryDispatchPath = "NativeFloatingIslandDensityAt selects floor(CoreZ / resolved layout chunk width) from ResolvedLayoutSnapshotV3";
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;
if (Plan != Manifest->floatingIslands.end())
{
Item.interiorBottomMicrometres = FixedToMicrons(Plan->manifest.recipe.layoutMinimumZSubvoxels);
Item.interiorTopMicrometres = FixedToMicrons(Plan->manifest.recipe.layoutMaximumZSubvoxels);
Item.recipeVersion = Plan->manifest.recipe.geometryVersion;
for (std::size_t IslandIndex = 0; IslandIndex < Plan->manifest.plan.islands.size(); ++IslandIndex)
{
@@ -1315,6 +1342,8 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
}
const int32 Seed = StrateManager->GetWorldSeed();
const std::uint32_t LayoutChunkSizeMicrometres =
StrateManager->GetStrateLayoutChunkSizeMicrometres();
const float OriginSpineRadius = Settings->GetEffectiveOriginSpineRadius();
const float WorldRadiusVoxels = Settings->GetEffectiveWorldRadiusVoxels();
const int32 CoreClipRadius = FMath::Clamp(ClipRadius, 1, 64);
@@ -1332,7 +1361,7 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
SettingsValue->AsObject()->SetNumberField(TEXT("ClipRadius"), CoreClipRadius);
SettingsValue->AsObject()->SetNumberField(TEXT("MaxClipLevel"), CoreMaxClipLevel);
SettingsValue->AsObject()->SetNumberField(TEXT("StrateLayoutChunkSizeMeters"),
Settings->StrateLayoutChunkSizeMeters);
static_cast<double>(StrateManager->GetStrateLayoutChunkSizeMicrometres()) / 1000000.0);
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
Root->SetNumberField(TEXT("schema_version"), 1);
@@ -1343,17 +1372,20 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0)));
Root->SetNumberField(TEXT("core_voxel_size_cm"), ResolvedCoreVoxelSizeCm);
Root->SetNumberField(TEXT("core_voxel_size_microns"), CoreVoxelSizeMicrons);
Root->SetNumberField(TEXT("owner_to_core_length_scale"),
OwnerVoxelSizeCm / ResolvedCoreVoxelSizeCm);
Root->SetNumberField(TEXT("seed"), Seed);
Root->SetBoolField(TEXT("open_surface_entry"), Settings->bOpenSurfaceEntry);
Root->SetNumberField(TEXT("origin_spine_radius"), OriginSpineRadius);
Root->SetNumberField(TEXT("world_radius_voxels"), WorldRadiusVoxels);
Root->SetNumberField(TEXT("edge_seal_thickness"), Settings->EdgeSealThickness);
Root->SetNumberField(TEXT("total_strates"), StrateManager->GetNumStrates());
Root->SetNumberField(TEXT("inter_strate_gap_chunks"), Settings->InterStrateGapChunks);
const std::int64_t ResolvedGapMicrometres =
StrateManager->GetResolvedInterStrateGapMicrometres();
Root->SetNumberField(TEXT("inter_strate_gap_chunks"),
LayoutChunkSizeMicrometres > 0
? ResolvedGapMicrometres / LayoutChunkSizeMicrometres : 0);
Root->SetNumberField(TEXT("inter_strate_gap_um"), ResolvedGapMicrometres);
Root->SetNumberField(TEXT("strate_layout_chunk_size_um"),
std::llround(Settings->StrateLayoutChunkSizeMeters*1000000.0));
LayoutChunkSizeMicrometres);
Root->SetNumberField(TEXT("clip_radius"), CoreClipRadius);
Root->SetNumberField(TEXT("max_clip_level"), CoreMaxClipLevel);
Root->SetNumberField(TEXT("full_res_clip_levels"), Settings->FullResClipLevels);
+218 -91
View File
@@ -25,6 +25,27 @@ namespace
{
std::atomic<uint64> GNextStrateManagerLifetimeId { 0 };
int64 VF_OwnerVoxelsToMicrometres(double OwnerVoxels)
{
return static_cast<int64>(std::llround(
OwnerVoxels * static_cast<double>(VOXEL_SIZE) * 10000.0));
}
float VF_ResolvedWorldMicrometresToOwnerVoxels(int64 WorldMicrometres)
{
return static_cast<float>(static_cast<double>(WorldMicrometres)
/ (static_cast<double>(VOXEL_SIZE) * 10000.0));
}
int64 VF_FloorDivide64(int64 Numerator, int64 Denominator)
{
if (Denominator <= 0) return 0;
int64 Quotient = Numerator / Denominator;
const int64 Remainder = Numerator % Denominator;
if (Remainder < 0) --Quotient;
return Quotient;
}
// Exact A/B switch for the repeated passage-floor projection item. The other field-
// preserving rewrites in this round are deliberately unconditional; this switch isolates
// the projection reuse without changing any density arithmetic.
@@ -1301,8 +1322,11 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
}
}
// Current Z position (in chunks). Starts at 0 and goes downward (negative).
int32 CurrentTopZ = 0;
// Resolve the stack in physical integer micrometres first. The historical chunk indices are
// projected from these half-open bounds only after each slot is complete.
int64 CurrentTopWorldMicrometres = StrateLayoutChunkSizeMicrometres;
const int64 ResolvedGapMicrometres = static_cast<int64>(LayoutGapChunks)
* StrateLayoutChunkSizeMicrometres;
for (int32 i = 0; i < TotalStrates; i++)
{
@@ -1357,16 +1381,28 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
const int32 LayoutChunkVoxels=FMath::RoundToInt(StrateLayoutChunkWidthVoxels);
Slot.TopChunkZ = SeasonStrate.TopWorldZ / LayoutChunkVoxels - 1;
Slot.BottomChunkZ = SeasonStrate.BottomWorldZ / LayoutChunkVoxels;
Slot.TopWorldMicrometres = (static_cast<int64>(Slot.TopChunkZ) + 1)
* StrateLayoutChunkSizeMicrometres;
Slot.BottomWorldMicrometres = static_cast<int64>(Slot.BottomChunkZ)
* StrateLayoutChunkSizeMicrometres;
Slot.HeightMicrometres = Slot.TopWorldMicrometres - Slot.BottomWorldMicrometres;
const int64 ExpectedTopChunk = VF_FloorDivide64(
CurrentTopWorldMicrometres, StrateLayoutChunkSizeMicrometres) - 1;
const int64 ExpectedBottomChunk = ExpectedTopChunk - (Slot.HeightInChunks - 1);
if (SeasonStrate.TopWorldZ % LayoutChunkVoxels != 0
|| SeasonStrate.BottomWorldZ % LayoutChunkVoxels != 0
|| Slot.TopChunkZ != CurrentTopZ
|| Slot.BottomChunkZ != CurrentTopZ - (Slot.HeightInChunks - 1))
|| Slot.TopChunkZ != ExpectedTopChunk
|| Slot.BottomChunkZ != ExpectedBottomChunk
|| Slot.TopWorldMicrometres != CurrentTopWorldMicrometres
|| Slot.HeightMicrometres != static_cast<int64>(Slot.HeightInChunks)
* StrateLayoutChunkSizeMicrometres)
{
UE_LOG(LogTemp, Error,
TEXT("[StrateManager] Season slot %d bounds do not describe the declared stacked layout."), i);
RestorePreviousLayout();
return false;
}
CurrentTopWorldMicrometres = Slot.BottomWorldMicrometres - ResolvedGapMicrometres;
SeasonStrates.Add(SeasonStrate);
}
else
@@ -1389,15 +1425,50 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
continue;
}
// Compute Z range from definition's height
// Resolve the authored chunk height into an exact physical interval.
Slot.HeightInChunks = Slot.Definition->StrateHeightInChunks;
Slot.TopChunkZ = CurrentTopZ;
Slot.BottomChunkZ = CurrentTopZ - (Slot.HeightInChunks - 1);
Slot.TopWorldMicrometres = CurrentTopWorldMicrometres;
Slot.HeightMicrometres = static_cast<int64>(Slot.HeightInChunks)
* StrateLayoutChunkSizeMicrometres;
Slot.BottomWorldMicrometres = Slot.TopWorldMicrometres - Slot.HeightMicrometres;
Slot.TopChunkZ = static_cast<int32>(VF_FloorDivide64(
Slot.TopWorldMicrometres, StrateLayoutChunkSizeMicrometres) - 1);
Slot.BottomChunkZ = static_cast<int32>(VF_FloorDivide64(
Slot.BottomWorldMicrometres, StrateLayoutChunkSizeMicrometres));
CurrentTopWorldMicrometres = Slot.BottomWorldMicrometres - ResolvedGapMicrometres;
}
// Move the cursor down for the next strate, leaving a solid-bedrock gap of
// InterStrateGapChunks chunks between this strate and the next.
CurrentTopZ = Slot.BottomChunkZ - 1 - LayoutGapChunks;
Slot.ResolvedTransitionType = Slot.Definition->TransitionType;
Slot.ResolvedTransitionBlendChunks = Slot.Definition->TransitionBlendChunks;
Slot.TransitionWidthMicrometres = static_cast<int64>(
Slot.ResolvedTransitionBlendChunks) * StrateLayoutChunkSizeMicrometres;
Slot.ResolvedPassageConfig = Slot.Definition->PassageConfig;
Slot.PassageMouthRadiusMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.MouthRadius);
Slot.PassageMidRadiusMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.MidRadius);
Slot.PassageReachMinimumMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.ReachMin);
Slot.PassageReachMaximumMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.ReachMax);
Slot.PassageDistanceMinimumMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.DistanceMin);
Slot.PassageDistanceMaximumMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.DistanceMax);
Slot.PassageWanderMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.Wander);
Slot.PassageVerticalWobbleMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.VerticalWobble);
Slot.PassageSpiralRadiusMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.SpiralRadius);
Slot.PassageCascadeLedgeMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.CascadeLedge);
Slot.LegacyTopWorldZVoxels = VF_ResolvedWorldMicrometresToOwnerVoxels(
Slot.TopWorldMicrometres);
Slot.LegacyBottomWorldZVoxels = VF_ResolvedWorldMicrometresToOwnerVoxels(
Slot.BottomWorldMicrometres);
Slot.LegacyHeightVoxels = VF_ResolvedWorldMicrometresToOwnerVoxels(
Slot.HeightMicrometres);
StrateLayout.Add(Slot);
@@ -1430,6 +1501,7 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
OriginSpineRadius = bUseSeason
? SeasonManifest.OriginSpineRadius : Settings->OriginSpineRadius;
InterStrateGapChunks = LayoutGapChunks;
ResolvedInterStrateGapMicrometres = ResolvedGapMicrometres;
if (bUseSeason)
{
ActiveSeasonContentHash = SeasonManifest.ContentHash;
@@ -1538,8 +1610,8 @@ bool UVoxelStrateManager::SetComposerOverrideForStrate(
}
VF_SetComposerRuntimeBounds(
Override->Params,
(float)(TargetSlot->TopChunkZ + 1) * StrateLayoutChunkWidthVoxels,
(float)TargetSlot->BottomChunkZ * StrateLayoutChunkWidthVoxels);
TargetSlot->LegacyTopWorldZVoxels,
TargetSlot->LegacyBottomWorldZVoxels);
Override->bUseRecipe = bUseRecipe;
if (Recipe != nullptr)
{
@@ -1558,8 +1630,8 @@ bool UVoxelStrateManager::SetComposerOverrideForStrate(
Override->bUseRegions = true;
Override->Regions = *InRegions;
VF_RekeyStrateRegionManifest(Override->Regions, CandidateSeed, StrateIndex);
Override->Regions.StrateTopWorldZ = (float)(TargetSlot->TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Override->Regions.StrateBottomWorldZ = (float)TargetSlot->BottomChunkZ * StrateLayoutChunkWidthVoxels;
Override->Regions.StrateTopWorldZ = TargetSlot->LegacyTopWorldZVoxels;
Override->Regions.StrateBottomWorldZ = TargetSlot->LegacyBottomWorldZVoxels;
VF_SetStrateArchetypeRuntimeBounds(Override->Params,
Override->Regions.StrateTopWorldZ,
Override->Regions.StrateBottomWorldZ);
@@ -1806,14 +1878,36 @@ void UVoxelStrateManager::GeneratePassages()
// that opens the next room rather than a second vertical spine.
const UVoxelStrateDefinition* UpperDef = Upper.Definition;
if (!UpperDef) continue;
const FStratePassageConfig& Cfg = UpperDef->PassageConfig;
// GeneratePassages is the legacy owner-voxel route adapter. Its immutable source is the
// physical passage record resolved during Initialize, converted back to owner voxels here.
FStratePassageConfig Cfg = Upper.ResolvedPassageConfig;
Cfg.MouthRadius = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageMouthRadiusMicrometres);
Cfg.MidRadius = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageMidRadiusMicrometres);
Cfg.ReachMin = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageReachMinimumMicrometres);
Cfg.ReachMax = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageReachMaximumMicrometres);
Cfg.DistanceMin = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageDistanceMinimumMicrometres);
Cfg.DistanceMax = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageDistanceMaximumMicrometres);
Cfg.Wander = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageWanderMicrometres);
Cfg.VerticalWobble = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageVerticalWobbleMicrometres);
Cfg.SpiralRadius = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageSpiralRadiusMicrometres);
Cfg.CascadeLedge = VF_ResolvedWorldMicrometresToOwnerVoxels(
Upper.PassageCascadeLedgeMicrometres);
// Upper strate floor and lower strate ceiling (differ when there's a bedrock gap).
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 UpperTopZ = Upper.LegacyTopWorldZVoxels;
const float UpperBottomZ = Upper.LegacyBottomWorldZVoxels;
const float LowerTopZ = Lower.LegacyTopWorldZVoxels;
const float UpperMax = Upper.LegacyHeightVoxels * 0.9f;
const float LowerMax = Lower.LegacyHeightVoxels * 0.9f;
const float DistLo = FMath::Min(Cfg.DistanceMin, Cfg.DistanceMax);
const float DistHi = FMath::Max(Cfg.DistanceMin, Cfg.DistanceMax);
@@ -1943,7 +2037,7 @@ void UVoxelStrateManager::GeneratePassages()
LowerDef->FloatingIslandParams,
LowerQuerySeed,
LowerTopZ,
(float)(Lower.BottomChunkZ) * StrateLayoutChunkWidthVoxels,
Lower.LegacyBottomWorldZVoxels,
PX,
PY,
MaxLateralSnap,
@@ -2003,7 +2097,7 @@ void UVoxelStrateManager::GeneratePassages()
if (bAimedLowerAtPlayerFitPoint)
{
const float LowerSealThickness = BoundarySealThicknessFor(*LowerDef);
const float LowerBottomZ = (float)(Lower.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
const float LowerBottomZ = Lower.LegacyBottomWorldZVoxels;
const float InnerBottomZ = LowerBottomZ + LowerSealThickness;
const float InnerTopZ = LowerTopZ - LowerSealThickness;
if (VoxelMath::IsFinite(SuggestedLowerPoint.X)
@@ -2171,7 +2265,7 @@ void UVoxelStrateManager::GeneratePassages()
Cfg.MouthRadius,
LowerDoorDirection,
LowerTopZ,
(float)(Lower.BottomChunkZ) * StrateLayoutChunkWidthVoxels,
Lower.LegacyBottomWorldZVoxels,
LowerDef ? BoundarySealThicknessFor(*LowerDef) : 0.0f,
bAimedLowerAtPlayerFitPoint);
Passage.UpperPoint = Passage.UpperLanding.StandingPoint;
@@ -2596,8 +2690,8 @@ void UVoxelStrateManager::GeneratePassages()
if (bOpenSurfaceEntry && OriginSpineRadius > 0.0f && StrateLayout.Num() > 0)
{
const FStrateSlot& Top = StrateLayout[0];
const float TopZ = (float)(Top.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
const float BottomZ = (float)Top.BottomChunkZ * StrateLayoutChunkWidthVoxels;
const float TopZ = Top.LegacyTopWorldZVoxels;
const float BottomZ = Top.LegacyBottomWorldZVoxels;
const float TopSeal = Top.Definition
? BoundarySealThicknessFor(*Top.Definition) : 0.0f;
const VoxelPassageGeometry::FOriginLandingGeometry OriginLanding =
@@ -5922,8 +6016,8 @@ bool UVoxelStrateManager::AnyLandingFloorAtLattice(
for (const FStrateSlot& Slot : StrateLayout)
{
if (Slot.Definition == nullptr) continue;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = Slot.LegacyBottomWorldZVoxels;
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
VoxelPassageGeometry::BuildOriginLandingGeometry(
@@ -5965,8 +6059,8 @@ bool UVoxelStrateManager::AnyOriginLandingNearBox(
for (const FStrateSlot& Slot : StrateLayout)
{
if (Slot.Definition == nullptr) continue;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = Slot.LegacyBottomWorldZVoxels;
if (VoxelPassageGeometry::OriginLandingRoomTouchesBox(
VoxelBox, TopZ, BottomZ,
VF_BoundarySealThicknessForDefinition(*Slot.Definition),
@@ -5986,8 +6080,8 @@ bool UVoxelStrateManager::AnyOriginLandingFloorNearBox(
for (const FStrateSlot& Slot : StrateLayout)
{
if (Slot.Definition == nullptr) continue;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = Slot.LegacyBottomWorldZVoxels;
if (VoxelPassageGeometry::OriginLandingFloorTouchesBox(
VoxelBox, TopZ, BottomZ,
VF_BoundarySealThicknessForDefinition(*Slot.Definition),
@@ -6011,8 +6105,8 @@ bool UVoxelStrateManager::AnyOriginLandingNearLattice(
for (const FStrateSlot& Slot : StrateLayout)
{
if (Slot.Definition == nullptr) continue;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = Slot.LegacyBottomWorldZVoxels;
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
if (VoxelPassageGeometry::OriginLandingRoomTouchesLattice(
VoxelBox, LatticeOrigin, Step, TopZ, BottomZ, Seal, OriginSpineRadius,
@@ -6049,8 +6143,8 @@ bool UVoxelStrateManager::AnyOriginLandingAirNearLattice(
{
continue;
}
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = Slot.LegacyBottomWorldZVoxels;
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
VoxelPassageGeometry::BuildOriginLandingGeometry(
@@ -6126,8 +6220,8 @@ bool UVoxelStrateManager::AnyOriginLandingFloorNearLattice(
for (const FStrateSlot& Slot : StrateLayout)
{
if (Slot.Definition == nullptr) continue;
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
const float TopZ = Slot.LegacyTopWorldZVoxels;
const float BottomZ = Slot.LegacyBottomWorldZVoxels;
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
if (VoxelPassageGeometry::OriginLandingFloorTouchesLattice(
VoxelBox, LatticeOrigin, Step, TopZ, BottomZ, Seal, OriginSpineRadius))
@@ -6144,15 +6238,36 @@ bool UVoxelStrateManager::AnyOriginLandingFloorNearLattice(
int32 UVoxelStrateManager::FindSlotIndexForChunkZ(int32 ChunkZ) const
{
// Linear search through strate layout.
// With ~10-20 strates this is fine. If we ever have hundreds,
// switch to binary search (layout is sorted by Z).
for (int32 i = 0; i < StrateLayout.Num(); i++)
// Legacy chunk callers use the projected interval cached during Initialize. The source bounds
// are the slot's physical micrometre interval; these indices do not define or stack strates.
for (int32 i = 0; i < StrateLayout.Num(); ++i)
{
const FStrateSlot& Slot = StrateLayout[i];
if (ChunkZ <= Slot.TopChunkZ && ChunkZ >= Slot.BottomChunkZ)
if (ChunkZ <= Slot.TopChunkZ && ChunkZ >= Slot.BottomChunkZ) return i;
}
return -1;
}
int32 UVoxelStrateManager::FindSlotIndexForWorldZCentimetres(double WorldZCentimetres) const
{
const double WorldZMicrometres = WorldZCentimetres * 10000.0;
int32 Low = 0;
int32 High = StrateLayout.Num() - 1;
while (Low <= High)
{
const int32 Mid = Low + (High - Low) / 2;
const FStrateSlot& Slot = StrateLayout[Mid];
if (WorldZMicrometres >= static_cast<double>(Slot.TopWorldMicrometres))
{
return i;
High = Mid - 1;
}
else if (WorldZMicrometres < static_cast<double>(Slot.BottomWorldMicrometres))
{
Low = Mid + 1;
}
else
{
return Mid;
}
}
return -1;
@@ -6160,9 +6275,8 @@ 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) / StrateLayoutChunkWidthVoxels);
int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ);
if (!FMath::IsFinite(WorldZ)) return nullptr;
const int32 SlotIdx = FindSlotIndexForWorldZCentimetres(WorldZ);
if (SlotIdx >= 0)
{
return StrateLayout[SlotIdx].Definition;
@@ -6172,8 +6286,8 @@ UVoxelStrateDefinition* UVoxelStrateManager::GetStrateAt(float WorldZ) const
int32 UVoxelStrateManager::GetStrateIndex(float WorldZ) const
{
int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / StrateLayoutChunkWidthVoxels);
int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ);
if (!FMath::IsFinite(WorldZ)) return -1;
const int32 SlotIdx = FindSlotIndexForWorldZCentimetres(WorldZ);
if (SlotIdx >= 0)
{
return StrateLayout[SlotIdx].StrateIndex;
@@ -6286,9 +6400,13 @@ bool UVoxelStrateManager::IsGapChunk(const FIntVector& ChunkCoord) const
if (StrateLayout.Num() == 0) return false;
// Above the top strate or below the bottom strate = open air, NOT a gap.
const int32 StackTop = StrateLayout[0].TopChunkZ;
const int32 StackBottom = StrateLayout.Last().BottomChunkZ;
if (ChunkCoord.Z > StackTop || ChunkCoord.Z < StackBottom) return false;
const int64 ChunkCenterMicrometres = static_cast<int64>(ChunkCoord.Z)
* StrateLayoutChunkSizeMicrometres + StrateLayoutChunkSizeMicrometres / 2;
if (ChunkCenterMicrometres >= StrateLayout[0].TopWorldMicrometres
|| ChunkCenterMicrometres < StrateLayout.Last().BottomWorldMicrometres)
{
return false;
}
// Inside the stack's Z span but not in any strate slot → it's a bedrock gap.
return FindSlotIndexForChunkZ(ChunkCoord.Z) < 0;
@@ -6332,8 +6450,8 @@ 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)*StrateLayoutChunkWidthVoxels.
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
return Result;
}
@@ -6366,8 +6484,8 @@ FMazeGenerationParams UVoxelStrateManager::GetMazeParamsForChunk(const FIntVecto
}
}
#endif
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
return Result;
}
@@ -6393,8 +6511,8 @@ FSurfaceGenerationParams UVoxelStrateManager::GetSurfaceParamsForChunk(const FIn
}
}
#endif
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
return Result;
}
@@ -6420,8 +6538,8 @@ FVerticalShaftParams UVoxelStrateManager::GetVerticalShaftParamsForChunk(const F
}
}
#endif
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
return Result;
}
@@ -6447,8 +6565,8 @@ FFloatingIslandParams UVoxelStrateManager::GetFloatingIslandParamsForChunk(const
}
}
#endif
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
return Result;
}
@@ -6482,14 +6600,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) / StrateLayoutChunkWidthVoxels);
const int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ);
if (!FMath::IsFinite(WorldZ)) return false;
const int32 SlotIdx = FindSlotIndexForWorldZCentimetres(WorldZ);
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) * StrateLayoutChunkWidthVoxels * VOXEL_SIZE;
OutBottomZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels * VOXEL_SIZE;
// Return the resolved physical bounds in Unreal centimeters.
OutTopZ = static_cast<float>(static_cast<double>(Slot.TopWorldMicrometres) / 10000.0);
OutBottomZ = static_cast<float>(static_cast<double>(Slot.BottomWorldMicrometres) / 10000.0);
return true;
}
@@ -6502,8 +6620,8 @@ FStrateDisturbanceParams UVoxelStrateManager::GetDisturbanceParamsForChunk(const
}
const FStrateSlot& Slot = StrateLayout[SlotIdx];
FStrateDisturbanceParams Result = Slot.Definition->Disturbances;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
return Result;
}
@@ -6535,8 +6653,8 @@ float UVoxelStrateManager::GetWaterLevelWorldZForChunk(const FIntVector& ChunkCo
}
if (Rel <= 0.0f) return -FLT_MAX;
const float BottomZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
const float TopZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
const float BottomZ = Slot.LegacyBottomWorldZVoxels;
const float TopZ = Slot.LegacyTopWorldZVoxels;
return FMath::Lerp(BottomZ, TopZ, FMath::Clamp(Rel, 0.0f, 1.0f));
}
@@ -6580,8 +6698,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
FindComposerOverride(Slot.StrateIndex))
{
FStrateGenerationParams Result = Override->Params.TunnelNetworkParams;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
// 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);
@@ -6591,8 +6709,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (SeasonStrates.IsValidIndex(SlotIdx))
{
FStrateGenerationParams Result = SeasonStrates[SlotIdx].Params.TunnelNetworkParams;
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
Result.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
Result.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
// 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);
@@ -6609,8 +6727,8 @@ 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) * StrateLayoutChunkWidthVoxels;
BaseParams.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
BaseParams.StrateTopWorldZ = Slot.LegacyTopWorldZVoxels;
BaseParams.StrateBottomWorldZ = Slot.LegacyBottomWorldZVoxels;
// Core currently dispatches one generator field per resolved strate. Gradient and
// interleaved metadata therefore use the explicitly declared Hard fallback for every
@@ -6657,18 +6775,23 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
//---------------------------------------------------------------------
// CHECK BOTTOM BOUNDARY (transitioning to strate below)
//---------------------------------------------------------------------
// DistFromBottom = how many chunks above the bottom edge of this strate.
// Legacy chunk callers project their sample to a layout interval; physical distance and
// transition width are both resolved in micrometres before projecting the blend fraction.
// When 0, we're right at the boundary. When == TransitionBlendChunks, we're at
// the outer edge of the transition zone.
int32 DistFromBottom = ChunkCoord.Z - Slot.BottomChunkZ;
const int64 DistFromBottomMicrometres = static_cast<int64>(ChunkCoord.Z)
* StrateLayoutChunkSizeMicrometres - Slot.BottomWorldMicrometres;
const int32 DistFromBottom = static_cast<int32>(VF_FloorDivide64(
DistFromBottomMicrometres, StrateLayoutChunkSizeMicrometres));
if (SlotIdx + 1 < StrateLayout.Num())
{
// The upper strate (this one) controls the transition type at its lower edge
const EVoxelStrateTransition TransType = Slot.Definition->TransitionType;
const EVoxelStrateTransition TransType = Slot.ResolvedTransitionType;
// Per-definition blend distance.
const int32 EffectiveBlend = Slot.Definition->TransitionBlendChunks;
const int32 EffectiveBlend = static_cast<int32>(VF_FloorDivide64(
Slot.TransitionWidthMicrometres, StrateLayoutChunkSizeMicrometres));
// Prepare the neighbor's params (only used for Gradient and Interleaved)
const FStrateSlot& BelowSlot = StrateLayout[SlotIdx + 1];
@@ -6692,8 +6815,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (EffectiveBlend > 0 && DistFromBottom < EffectiveBlend)
{
FStrateGenerationParams BelowParams = BuildParamsFromDefinition(BelowSlot.Definition);
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
BelowParams.StrateTopWorldZ = BelowSlot.LegacyTopWorldZVoxels;
BelowParams.StrateBottomWorldZ = BelowSlot.LegacyBottomWorldZVoxels;
// Linear alpha: 0 at EffectiveBlend chunks away, 1 at the boundary
float Alpha = 1.0f - ((float)DistFromBottom / (float)EffectiveBlend);
@@ -6730,8 +6853,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (DistFromBottom < CheckRange)
{
FStrateGenerationParams BelowParams = BuildParamsFromDefinition(BelowSlot.Definition);
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
BelowParams.StrateTopWorldZ = BelowSlot.LegacyTopWorldZVoxels;
BelowParams.StrateBottomWorldZ = BelowSlot.LegacyBottomWorldZVoxels;
// Sample 3D Perlin noise to warp the boundary position.
// XY frequency 0.15 gives medium-scale variation (~6-7 chunks per cycle).
@@ -6769,14 +6892,18 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
//---------------------------------------------------------------------
// Mirror logic: the ABOVE strate's TransitionType controls its lower edge,
// which is this strate's upper edge. So we read from StrateLayout[SlotIdx-1].
int32 DistFromTop = Slot.TopChunkZ - ChunkCoord.Z;
const int64 DistFromTopMicrometres = Slot.TopWorldMicrometres
- (static_cast<int64>(ChunkCoord.Z) + 1) * StrateLayoutChunkSizeMicrometres;
const int32 DistFromTop = static_cast<int32>(VF_FloorDivide64(
DistFromTopMicrometres, StrateLayoutChunkSizeMicrometres));
if (SlotIdx > 0)
{
// The strate ABOVE controls the transition at its lower edge (= our upper edge)
const FStrateSlot& AboveSlot = StrateLayout[SlotIdx - 1];
const EVoxelStrateTransition TransType = AboveSlot.Definition->TransitionType;
const int32 EffectiveBlend = AboveSlot.Definition->TransitionBlendChunks;
const EVoxelStrateTransition TransType = AboveSlot.ResolvedTransitionType;
const int32 EffectiveBlend = static_cast<int32>(VF_FloorDivide64(
AboveSlot.TransitionWidthMicrometres, StrateLayoutChunkSizeMicrometres));
switch (TransType)
{
@@ -6791,8 +6918,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (EffectiveBlend > 0 && DistFromTop < EffectiveBlend)
{
FStrateGenerationParams AboveParams = BuildParamsFromDefinition(AboveSlot.Definition);
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
AboveParams.StrateTopWorldZ = AboveSlot.LegacyTopWorldZVoxels;
AboveParams.StrateBottomWorldZ = AboveSlot.LegacyBottomWorldZVoxels;
float Alpha = 1.0f - ((float)DistFromTop / (float)EffectiveBlend);
Alpha = FMath::Clamp(Alpha, 0.0f, 1.0f);
@@ -6813,8 +6940,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
if (DistFromTop < CheckRange)
{
FStrateGenerationParams AboveParams = BuildParamsFromDefinition(AboveSlot.Definition);
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
AboveParams.StrateTopWorldZ = AboveSlot.LegacyTopWorldZVoxels;
AboveParams.StrateBottomWorldZ = AboveSlot.LegacyBottomWorldZVoxels;
// Same noise function but with a different seed offset to avoid
// symmetry between top and bottom boundaries of adjacent strates
+35 -4
View File
@@ -121,8 +121,8 @@ struct FVoxelPassage
* FStrateSlot — Runtime info for one strate in the layout.
*
* Created during Initialize() and stored in the StrateLayout array.
* Each slot knows its definition, its Z-range in chunk coordinates,
* and its index in the sequence.
* Each slot owns its resolved half-open physical Z interval. Chunk coordinates and legacy
* authoring values below are projections kept for existing callers and assets.
*/
USTRUCT()
struct FStrateSlot
@@ -136,6 +136,33 @@ struct FStrateSlot
// Strate index (0 = topmost, increases downward)
int32 StrateIndex = 0;
// Resolved physical bounds, in micrometres, with interval [bottom, top). These are computed
// once during Initialize from the configured metre chunk width and legacy asset dimensions.
int64 TopWorldMicrometres = 0;
int64 BottomWorldMicrometres = 0;
int64 HeightMicrometres = 0;
float LegacyTopWorldZVoxels = 0.0f;
float LegacyBottomWorldZVoxels = 0.0f;
float LegacyHeightVoxels = 0.0f;
// Immutable transition and passage inputs copied when this slot is resolved. The width is
// physical; chunk counts and raw owner-voxel values remain compatibility projections for
// legacy parameter and route adapters.
EVoxelStrateTransition ResolvedTransitionType = EVoxelStrateTransition::Gradient;
int32 ResolvedTransitionBlendChunks = 2;
int64 TransitionWidthMicrometres = 0;
FStratePassageConfig ResolvedPassageConfig;
int64 PassageMouthRadiusMicrometres = 0;
int64 PassageMidRadiusMicrometres = 0;
int64 PassageReachMinimumMicrometres = 0;
int64 PassageReachMaximumMicrometres = 0;
int64 PassageDistanceMinimumMicrometres = 0;
int64 PassageDistanceMaximumMicrometres = 0;
int64 PassageWanderMicrometres = 0;
int64 PassageVerticalWobbleMicrometres = 0;
int64 PassageSpiralRadiusMicrometres = 0;
int64 PassageCascadeLedgeMicrometres = 0;
// Z chunk range (inclusive). TopZ > BottomZ since Z decreases downward.
// Example: TopZ = 0, BottomZ = -3 means this strate spans chunks Z=0 to Z=-3
int32 TopChunkZ = 0;
@@ -248,6 +275,8 @@ public:
// This is independent of Core's 32^3 tile width.
float GetStrateLayoutChunkWidthVoxels() const { return StrateLayoutChunkWidthVoxels; }
uint32 GetStrateLayoutChunkSizeMicrometres() const { return StrateLayoutChunkSizeMicrometres; }
int64 GetResolvedInterStrateGapMicrometres() const
{ return ResolvedInterStrateGapMicrometres; }
// Called by the Core adapter through a const manager pointer; the flag is runtime routing
// state, not layout data, hence const + mutable.
void SetHardTransitionFallbackForAssembly(bool bEnabled) const
@@ -530,9 +559,10 @@ protected:
// Copied from VoxelSettings::OriginSpineRadius.
float OriginSpineRadius = 28.0f;
// Solid-bedrock gap between consecutive strates, in chunks. Copied from
// VoxelSettings::InterStrateGapChunks during Initialize().
// Compatibility gap projection, in layout chunks. Physical authority is the micrometre value
// resolved during Initialize below.
int32 InterStrateGapChunks = 0;
int64 ResolvedInterStrateGapMicrometres = 0;
// Resolved once from VoxelSettings::StrateLayoutChunkSizeMeters during Initialize().
float StrateLayoutChunkWidthVoxels = 32.0f;
@@ -554,6 +584,7 @@ protected:
// Find which slot a chunk Z coordinate falls into.
// Returns INDEX into StrateLayout, or -1 if not found.
int32 FindSlotIndexForChunkZ(int32 ChunkZ) const;
int32 FindSlotIndexForWorldZCentimetres(double WorldZCentimetres) const;
#if WITH_EDITOR
const FVoxelStrateComposerSlotOverride* FindComposerOverride(int32 StrateIndex) const;