wip(assembler): M3 round 4 -- plans built inside Unreal from the owner's strates; no import needed

SurfaceWorld and FloatingIslands plans generate at startup from the resolved seed,
strate types and heights (hashed, cached). Imported JSON is an opt-in override (off by
default); an invalid override shows an on-screen notice and falls back to legacy.
BuildHost on staged owner assets: SurfaceWorld cross-system checks, islands at owner
height (route, collision, anchors, BP edits), seed 137501, W1/W8/W16, Core-off legacy.
XY-column cache verified (hashes kept); island LOD0 p95 91 ms vs 133 ms sprint deadline.
Not exercised: season-seed path (no Season in staged settings), rendered notice pixels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-10 22:48:46 +02:00
1 parent 48fa028086
commit 111cf3d262
21 files changed
+1934 -391

No files matched your search

@@ -283,8 +283,14 @@ void UVoxelContentManager::Initialize(AActor* InOwner, UVoxelStrateManager* InSt
if (Settings)
{
FString ManifestJson, ManifestHash, ManifestError;
if (Settings->GetWorldAssemblyManifestJson(ManifestJson, ManifestHash, ManifestError)
&& !ManifestJson.IsEmpty())
const AVoxelWorld* VoxelWorld = Cast<AVoxelWorld>(Owner.Get());
const bool bHasRuntimeAssembly = VoxelWorld != nullptr
&& VoxelWorld->GetVoxelForgeCoreWorldAssembly(ManifestJson, ManifestHash);
if (!bHasRuntimeAssembly)
{
Settings->GetWorldAssemblyManifestJson(ManifestJson, ManifestHash, ManifestError);
}
if (!ManifestJson.IsEmpty())
{
FTCHARToUTF8 Utf8Json(*ManifestJson);
std::string CoreError;
@@ -9,16 +9,477 @@
#include "UObject/TextProperty.h"
#include <VoxelForgeCore/VoxelForgeCore.h>
#include "VoxelSeasonAsset.h"
#include "VoxelBiomeDefinition.h"
#include "VoxelBiomeTypes.h"
#include "VoxelSettings.h"
#include "VoxelStrateDefinition.h"
#include "VoxelStrateManager.h"
#include "VoxelTerrainOpDefinition.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <limits>
#include <string>
#include <utility>
#include <vector>
namespace
{
using FCoreJsonWriter = TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>;
constexpr double CoreVoxelSizeCm = 25.0;
constexpr double OwnerVoxelSizeCm = 12.5;
constexpr double OwnerToCoreScale = OwnerVoxelSizeCm / CoreVoxelSizeCm;
std::int64_t WorldAssemblyFixed(double Voxels)
{
return static_cast<std::int64_t>(std::llround(
Voxels * VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
}
std::int64_t WorldAssemblyQ16(double Value)
{
return static_cast<std::int64_t>(std::llround(Value * 65536.0));
}
std::int64_t WorldAssemblyWavelength(double Frequency, double FallbackVoxels)
{
const double Wavelength = FMath::IsFinite(Frequency) && Frequency > 0.0
? 1.0 / Frequency : FallbackVoxels;
return std::max<std::int64_t>(1, WorldAssemblyFixed(Wavelength));
}
std::uint64_t WorldAssemblyFeatureId(std::uint32_t Seed, std::int64_t X,
std::int64_t Y, std::uint32_t Kind)
{
std::uint64_t Hash = 14695981039346656037ull;
const auto Add = [&Hash](std::uint64_t Value, int Bytes)
{
for (int Index = 0; Index < Bytes; ++Index)
{
Hash ^= static_cast<std::uint8_t>(Value >> (Index * 8));
Hash *= 1099511628211ull;
}
};
Add(Seed, 4);
Add(static_cast<std::uint64_t>(X), 8);
Add(static_cast<std::uint64_t>(Y), 8);
Add(Kind, 4);
return Hash == 0 ? 1 : Hash;
}
std::int64_t WorldAssemblyRoundRatio(std::int64_t Numerator, std::int64_t Denominator)
{
if (Numerator >= 0) return (Numerator + Denominator / 2) / Denominator;
return -((-Numerator + Denominator / 2) / Denominator);
}
std::uint64_t WorldAssemblyRoundedSqrt(std::uint64_t Value)
{
std::uint64_t Root = 0;
std::uint64_t Bit = 1ull << 62;
while (Bit > Value) Bit >>= 2;
while (Bit != 0)
{
if (Value >= Root + Bit)
{
Value -= Root + Bit;
Root = (Root >> 1) + Bit;
}
else
{
Root >>= 1;
}
Bit >>= 2;
}
const std::uint64_t OriginalValue = Value + Root * Root;
if (4 * OriginalValue >= (2 * Root + 1) * (2 * Root + 1)) ++Root;
return Root;
}
bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
VoxelForgeCore::WorldAssemblyStratumPlanV1& Stratum, std::string& Error)
{
using namespace VoxelForgeCore;
constexpr std::int64_t PlayerRadiusQ = 348;
constexpr std::int64_t PlayerHeightQ = 1802;
constexpr std::int64_t HalfHeightQ = PlayerHeightQ / 2;
constexpr std::int64_t StepQ = 128;
constexpr std::int64_t ProbeRadiusQ = 174;
constexpr std::int64_t SupportDepthQ = 256;
constexpr std::int64_t GradeQ16 = 17560;
constexpr std::int64_t RouteRadiusQ = 4 * WorldAssemblySubvoxelsPerVoxel;
struct FDirection { std::int32_t X; std::int32_t Y; };
constexpr std::array<FDirection, 8> Directions{{
{65536, 0}, {46341, 46341}, {0, 65536}, {-46341, 46341},
{-65536, 0}, {-46341, -46341}, {0, -65536}, {46341, -46341}}};
struct FCandidate
{
std::int64_t Score = 0;
std::int32_t CenterX = 0;
std::int32_t CenterY = 0;
std::int32_t Direction = 0;
std::vector<WorldAssemblyRoutePointV1> Points;
};
auto& Constraints = Stratum.routeConstraints;
Constraints.version = 1;
Constraints.capsuleRadiusSubvoxels = PlayerRadiusQ;
Constraints.capsuleHeightSubvoxels = PlayerHeightQ;
Constraints.maximumGradeQ16 = GradeQ16;
Constraints.sweepSpacingSubvoxels = StepQ;
Constraints.supportProbeRadiusSubvoxels = ProbeRadiusQ;
Constraints.supportSearchDepthSubvoxels = SupportDepthQ;
Constraints.minimumClearanceSubvoxels = 8;
Constraints.minimumSupportProbes = 3;
const Heightfield2D BaseField(Recipe, Stratum);
if (!BaseField.isValid())
{
Error = "SurfaceWorld route planner received an invalid heightfield.";
return false;
}
std::vector<FCandidate> Candidates;
constexpr std::int32_t WaypointSpacing = 8;
for (std::int32_t CenterY = -256; CenterY <= 256; CenterY += 64)
for (std::int32_t CenterX = -256; CenterX <= 256; CenterX += 64)
for (std::int32_t DirectionIndex = 0;
DirectionIndex < static_cast<std::int32_t>(Directions.size()); ++DirectionIndex)
{
FCandidate Candidate;
Candidate.CenterX = CenterX;
Candidate.CenterY = CenterY;
Candidate.Direction = DirectionIndex;
Candidate.Points.reserve(33);
std::int64_t PreviousGround = 0;
std::int64_t PreviousX = 0;
std::int64_t PreviousY = 0;
bool bGradePass = true;
for (std::int32_t Index = 0; Index <= 32; ++Index)
{
const std::int64_t Along = (Index - 16) * WaypointSpacing;
const std::int64_t X = CenterX + WorldAssemblyRoundRatio(
static_cast<std::int64_t>(Along) * Directions[DirectionIndex].X, 65536);
const std::int64_t Y = CenterY + WorldAssemblyRoundRatio(
static_cast<std::int64_t>(Along) * Directions[DirectionIndex].Y, 65536);
const auto Column = BaseField.point(WorldAssemblyFixed(static_cast<double>(X)),
WorldAssemblyFixed(static_cast<double>(Y)), 0);
const std::int64_t DeltaX = X - PreviousX;
const std::int64_t DeltaY = Y - PreviousY;
const std::uint64_t HorizontalVoxels = WorldAssemblyRoundedSqrt(
static_cast<std::uint64_t>(DeltaX * DeltaX + DeltaY * DeltaY));
const std::int64_t HorizontalQ = static_cast<std::int64_t>(HorizontalVoxels)
* WorldAssemblySubvoxelsPerVoxel;
if (Index > 0)
{
const std::int64_t RiseQ = std::abs(Column.groundHeightSubvoxels - PreviousGround);
if (HorizontalQ == 0 || RiseQ * 65536 > HorizontalQ * GradeQ16)
{
bGradePass = false;
break;
}
Candidate.Score += RiseQ * 65536 / std::max<std::int64_t>(1, HorizontalQ);
}
WorldAssemblyRoutePointV1 Point;
Point.positionSubvoxels = {WorldAssemblyFixed(static_cast<double>(X)),
WorldAssemblyFixed(static_cast<double>(Y)),
Column.groundHeightSubvoxels + HalfHeightQ};
Point.radiusSubvoxels = RouteRadiusQ;
Point.stableFeatureId = WorldAssemblyFeatureId(Recipe.seed, X, Y,
0x52540000u + static_cast<std::uint32_t>(Index));
Candidate.Points.push_back(Point);
PreviousGround = Column.groundHeightSubvoxels;
PreviousX = X;
PreviousY = Y;
}
if (bGradePass) Candidates.push_back(std::move(Candidate));
}
std::stable_sort(Candidates.begin(), Candidates.end(), [](const FCandidate& A, const FCandidate& B)
{
if (A.Score != B.Score) return A.Score < B.Score;
if (A.CenterY != B.CenterY) return A.CenterY < B.CenterY;
if (A.CenterX != B.CenterX) return A.CenterX < B.CenterX;
return A.Direction < B.Direction;
});
for (const FCandidate& Candidate : Candidates)
{
Stratum.route = Candidate.Points;
std::int64_t LiftQ = 0;
constexpr std::int64_t RepairStepQ = 64;
constexpr std::int64_t MaximumRepairLiftQ = SupportDepthQ - RepairStepQ;
for (;;)
{
if (LiftQ > 0)
{
for (std::size_t PointIndex = 0; PointIndex < Stratum.route.size(); ++PointIndex)
{
Stratum.route[PointIndex].positionSubvoxels[2] =
Candidate.Points[PointIndex].positionSubvoxels[2] + LiftQ;
}
}
const Heightfield2D CandidateField(Recipe, Stratum);
if (AuditHeightfieldRoute(CandidateField).passed) return true;
if (LiftQ >= MaximumRepairLiftQ) break;
LiftQ += RepairStepQ;
}
}
Error = "SurfaceWorld route candidates failed the capsule and support sweep.";
return false;
}
VoxelForgeCore::WorldAssemblyBiomeProfileV1 BuildSurfaceProfile(
std::uint32_t StableId, const FSurfaceGenerationParams& Params,
const FBiomeResolved* Climate, double BottomVoxel, double HeightVoxels)
{
using namespace VoxelForgeCore;
WorldAssemblyBiomeProfileV1 Profile;
Profile.stableId = StableId;
Profile.baseHeightSubvoxels = WorldAssemblyFixed(BottomVoxel
+ HeightVoxels * Params.BaseGroundRelative);
Profile.continentAmplitudeSubvoxels = WorldAssemblyFixed(Params.ElevationRange);
Profile.continentWavelengthSubvoxels = WorldAssemblyWavelength(Params.ContinentFrequency, 1000.0);
Profile.mountainAmplitudeSubvoxels = WorldAssemblyFixed(Params.ElevationRange * Params.MountainStrength);
Profile.mountainWavelengthSubvoxels = WorldAssemblyWavelength(Params.MountainFrequency, 500.0);
Profile.detailAmplitudeSubvoxels = WorldAssemblyFixed(Params.SurfaceRoughness);
Profile.detailWavelengthSubvoxels = WorldAssemblyWavelength(Params.DetailFrequency, 25.0);
Profile.reliefMinimumQ16 = Climate ? WorldAssemblyQ16(Climate->ReliefMin) : 0;
Profile.reliefMaximumQ16 = Climate ? WorldAssemblyQ16(Climate->ReliefMax) : 65536;
Profile.moistureMinimumQ16 = Climate ? WorldAssemblyQ16(Climate->MoistureMin) : 0;
Profile.moistureMaximumQ16 = Climate ? WorldAssemblyQ16(Climate->MoistureMax) : 65536;
Profile.reliefStrengthQ16 = WorldAssemblyQ16(Params.ReliefStrength);
Profile.reliefContrastQ16 = WorldAssemblyQ16(Params.ReliefContrast);
Profile.reliefWavelengthSubvoxels = WorldAssemblyWavelength(Params.ReliefFrequency, 667.0);
Profile.heightWarpStrengthSubvoxels = WorldAssemblyFixed(Params.HeightWarpStrength);
Profile.heightWarpWavelengthSubvoxels = WorldAssemblyWavelength(Params.HeightWarpFrequency, 125.0);
Profile.beachWidthSubvoxels = WorldAssemblyFixed(Params.BeachWidth);
Profile.cliffStrengthQ16 = WorldAssemblyQ16(Params.CliffStrength);
Profile.cliffSlopeThresholdQ16 = std::max<std::int64_t>(1, WorldAssemblyQ16(Params.CliffSlopeThreshold));
Profile.cliffSharpnessQ16 = WorldAssemblyQ16(Params.CliffSharpness);
Profile.cliffSampleDistanceSubvoxels = WorldAssemblyFixed(std::max(0.5f, Params.CliffSampleDist));
const double StructuralAbsBound = static_cast<double>(Params.ElevationRange)
+ static_cast<double>(Params.ElevationRange) * Params.MountainStrength
+ 2.0 * Params.SurfaceRoughness;
const double CliffAbsBound = 2.0 * StructuralAbsBound * Params.CliffStrength * Params.CliffSharpness;
Profile.cliffMaximumDisplacementSubvoxels = WorldAssemblyFixed(Params.CliffStrength > 0.0f
? std::max(1.0, CliffAbsBound) : 0.0);
Profile.hasCapProfile = true;
Profile.capHeightSubvoxels = WorldAssemblyFixed(BottomVoxel + HeightVoxels * Params.CeilingRelative);
Profile.capWarpStrengthSubvoxels = WorldAssemblyFixed(Params.CeilingWarpStrength);
Profile.capWarpWavelengthSubvoxels = WorldAssemblyWavelength(Params.CeilingWarpFrequency, 100.0);
Profile.capUndulationSubvoxels = WorldAssemblyFixed(Params.CeilingUndulation);
Profile.capWavelengthSubvoxels = WorldAssemblyWavelength(Params.CeilingUndulationFrequency, 500.0);
Profile.capDetailAmplitudeSubvoxels = WorldAssemblyFixed(Params.CeilingRoughness);
Profile.capDetailWavelengthSubvoxels = WorldAssemblyWavelength(Params.CeilingRoughnessFrequency, 25.0);
Profile.capRidgeAmplitudeSubvoxels = WorldAssemblyFixed(Params.CeilingRidgeStrength);
Profile.capRidgeWavelengthSubvoxels = WorldAssemblyWavelength(Params.CeilingRidgeFrequency, 50.0);
return Profile;
}
bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager,
const FStrateSlot& Slot,
VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
VoxelForgeCore::WorldAssemblyStratumPlanV1& Stratum, 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 BottomVoxel = static_cast<double>(Slot.BottomChunkZ) * CHUNK_SIZE;
const double HeightVoxels = static_cast<double>(Slot.TopChunkZ - Slot.BottomChunkZ + 1) * CHUNK_SIZE;
const FBiomeMapParams& Map = BiomeContext.Map;
Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
Recipe.biomeCellSizeSubvoxels = WorldAssemblyFixed(Map.CellSize);
Recipe.biomeWarpSubvoxels = WorldAssemblyFixed(Map.WarpStrength);
Recipe.biomeWarpWavelengthSubvoxels = WorldAssemblyWavelength(Map.WarpFrequency, 556.0);
Recipe.biomeBorderBlendSubvoxels = WorldAssemblyFixed(Map.BorderBlend);
Recipe.biomeClimateReliefWavelengthSubvoxels = WorldAssemblyWavelength(Map.ReliefFrequency, 3333.0);
Recipe.biomeClimateMoistureWavelengthSubvoxels = WorldAssemblyWavelength(Map.MoistureFrequency, 2500.0);
Recipe.biomeClimateReliefContrastQ16 = WorldAssemblyQ16(Map.ReliefContrast);
Recipe.biomes.clear();
if (BiomeContext.IsValid() && Definition != nullptr)
{
for (const FBiomeResolved& Resolved : BiomeContext.Biomes)
{
const UVoxelBiomeDefinition* Biome = Definition->Biomes.IsValidIndex(Resolved.Index)
? Definition->Biomes[Resolved.Index] : nullptr;
FSurfaceGenerationParams Params = Surface;
if (Biome != nullptr && Biome->bOverrideTerrain
&& Biome->GeneratorType == ECaveGeneratorType::SurfaceWorld)
{
Params = Biome->SurfaceParams;
Params.StrateTopWorldZ = Surface.StrateTopWorldZ;
Params.StrateBottomWorldZ = Surface.StrateBottomWorldZ;
Params.BoundarySealThickness = Surface.BoundarySealThickness;
Params.BaseDensity = Surface.BaseDensity;
Params.WaterLevelRelative = Surface.WaterLevelRelative;
}
Recipe.biomes.push_back(BuildSurfaceProfile(
0x1001u + static_cast<std::uint32_t>(Resolved.Index), Params, &Resolved,
BottomVoxel, HeightVoxels));
}
}
if (Recipe.biomes.empty())
{
Recipe.biomes.push_back(BuildSurfaceProfile(0x1001u, Surface, nullptr,
BottomVoxel, HeightVoxels));
}
Stratum.strateIndex = Slot.StrateIndex;
Stratum.topChunkZ = Slot.TopChunkZ;
Stratum.bottomChunkZ = Slot.BottomChunkZ;
Stratum.capHeightSubvoxels = WorldAssemblyFixed(BottomVoxel + HeightVoxels * Surface.CeilingRelative);
Stratum.capUndulationSubvoxels = WorldAssemblyFixed(Surface.CeilingUndulation);
Stratum.capWavelengthSubvoxels = WorldAssemblyWavelength(Surface.CeilingUndulationFrequency, 250.0);
Stratum.capDetailAmplitudeSubvoxels = WorldAssemblyFixed(Surface.CeilingRoughness);
Stratum.capDetailWavelengthSubvoxels = WorldAssemblyWavelength(Surface.CeilingRoughnessFrequency, 25.0);
Stratum.capRidgeAmplitudeSubvoxels = WorldAssemblyFixed(Surface.CeilingRidgeStrength);
Stratum.capRidgeWavelengthSubvoxels = WorldAssemblyWavelength(Surface.CeilingRidgeFrequency, 50.0);
Stratum.waterSurfaceSubvoxels = WorldAssemblyFixed(BottomVoxel
+ HeightVoxels * Surface.WaterLevelRelative);
Stratum.waterEnabled = Definition != nullptr && Definition->bHasWater
&& Surface.WaterLevelRelative > 0.0f;
{
// The hashed plan must not depend on platform-specific legacy float sampling.
Heightfield2D BaseField(Recipe, Stratum);
if (!BaseField.isValid())
{
OutError = TEXT("SurfaceWorld automatic plan could not initialize its fixed-point field.");
return false;
}
constexpr int ProbeSide = 49;
constexpr int ProbeStep = 32;
std::array<std::int64_t, ProbeSide * ProbeSide> Heights{};
for (int Y = 0; Y < ProbeSide; ++Y)
for (int X = 0; X < ProbeSide; ++X)
{
const std::int64_t PX = static_cast<std::int64_t>(X - ProbeSide / 2) * ProbeStep;
const std::int64_t PY = static_cast<std::int64_t>(Y - ProbeSide / 2) * ProbeStep;
const std::size_t Index = static_cast<std::size_t>(Y) * ProbeSide + X;
Heights[Index] = BaseField.point(WorldAssemblyFixed(static_cast<double>(PX)),
WorldAssemblyFixed(static_cast<double>(PY)), 0).groundHeightSubvoxels;
}
std::int64_t SteepestNumerator = 0;
int CliffX = 0;
int CliffY = 0;
bool bXAxis = true;
int Downhill = 1;
for (int Y = 1; Y < ProbeSide - 1; ++Y)
for (int X = 1; X < ProbeSide - 1; ++X)
{
const std::size_t Center = static_cast<std::size_t>(Y) * ProbeSide + X;
const std::int64_t DX = Heights[Center + 1] - Heights[Center - 1];
const std::int64_t DY = Heights[Center + ProbeSide] - Heights[Center - ProbeSide];
const std::int64_t SlopeNumerator = std::abs(DX) + std::abs(DY);
if (SlopeNumerator > SteepestNumerator)
{
SteepestNumerator = SlopeNumerator;
CliffX = (X - ProbeSide / 2) * ProbeStep;
CliffY = (Y - ProbeSide / 2) * ProbeStep;
bXAxis = std::abs(DX) >= std::abs(DY);
Downhill = (bXAxis ? DX : DY) >= 0 ? -1 : 1;
}
}
if (SteepestNumerator * 20
> 4 * ProbeStep * WorldAssemblySubvoxelsPerVoxel)
{
WorldAssemblyFeatureV1 Shelf;
Shelf.stableId = WorldAssemblyFeatureId(Recipe.seed, CliffX, CliffY, 0x4f564552u);
Shelf.type = 4;
constexpr int Reach = 72;
constexpr int Width = 36;
constexpr int ShelfHeight = 64;
constexpr int Feather = 8;
const int DirX = bXAxis ? Downhill : 0;
const int DirY = bXAxis ? 0 : Downhill;
const int MinX = bXAxis ? (DirX > 0 ? CliffX - Feather : CliffX - Reach - Feather)
: CliffX - Width - Feather;
const int MaxX = bXAxis ? (DirX > 0 ? CliffX + Reach + Feather : CliffX + Feather)
: CliffX + Width + Feather;
const int MinY = bXAxis ? CliffY - Width - Feather
: (DirY > 0 ? CliffY - Feather : CliffY - Reach - Feather);
const int MaxY = bXAxis ? CliffY + Width + Feather
: (DirY > 0 ? CliffY + Reach + Feather : CliffY + Feather);
std::int64_t LowestGround = std::numeric_limits<std::int64_t>::max();
std::int64_t HighestGround = std::numeric_limits<std::int64_t>::min();
for (int SampleY = MinY; SampleY <= MaxY; SampleY += 4)
for (int SampleX = MinX; SampleX <= MaxX; SampleX += 4)
{
const auto Sample = BaseField.point(WorldAssemblyFixed(SampleX),
WorldAssemblyFixed(SampleY), 0);
LowestGround = std::min(LowestGround, Sample.groundHeightSubvoxels);
HighestGround = std::max(HighestGround, Sample.groundHeightSubvoxels);
}
if (LowestGround == std::numeric_limits<std::int64_t>::max()
|| HighestGround == std::numeric_limits<std::int64_t>::min())
{
OutError = TEXT("SurfaceWorld automatic plan could not bound its terrain shelf.");
return false;
}
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),
HighestGround + WorldAssemblyFixed(ShelfHeight + 4.0))};
Shelf.parametersSubvoxels = {WorldAssemblyFixed(DirX), WorldAssemblyFixed(DirY),
WorldAssemblyFixed(ShelfHeight), WorldAssemblyFixed(Reach)};
Stratum.features.push_back(Shelf);
}
}
std::string RouteError;
if (!BuildFiniteSurfaceRoute(Recipe, Stratum, RouteError))
{
OutError = UTF8_TO_TCHAR(RouteError.c_str());
return false;
}
return true;
}
bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
const FStrateSlot& Slot, VoxelForgeCore::WorldAssemblyManifest& Manifest,
FString& OutError)
{
using namespace VoxelForgeCore;
const FIntVector Chunk(0, 0, Slot.TopChunkZ);
const FFloatingIslandParams Params = StrateManager->GetFloatingIslandParamsForChunk(Chunk);
WorldAssemblyFloatingIslandPlanV1 IslandPlan;
IslandPlan.strateIndex = Slot.StrateIndex;
IslandPlan.topChunkZ = Slot.TopChunkZ;
IslandPlan.bottomChunkZ = Slot.BottomChunkZ;
auto& Recipe = IslandPlan.manifest.recipe;
Recipe.geometryVersion = 2;
Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
const std::int64_t BottomCoreVoxels = static_cast<std::int64_t>(Slot.BottomChunkZ) * 16;
const std::int64_t TopCoreVoxels = static_cast<std::int64_t>(Slot.TopChunkZ + 1) * 16;
Recipe.layoutMinimumZSubvoxels = BottomCoreVoxels * WorldAssemblySubvoxelsPerVoxel;
Recipe.layoutMaximumZSubvoxels = TopCoreVoxels * WorldAssemblySubvoxelsPerVoxel;
Recipe.layoutCenterTopZSubvoxels = (BottomCoreVoxels + TopCoreVoxels)
* WorldAssemblySubvoxelsPerVoxel / 2;
Recipe.islandDensityQ16 = WorldAssemblyQ16(FMath::Clamp(Params.IslandDensity, 0.0f, 1.0f));
Recipe.spacingSubvoxels = WorldAssemblyFixed(Params.IslandSpacing * OwnerToCoreScale);
Recipe.minimumRadiusSubvoxels = WorldAssemblyFixed(Params.IslandMinRadius * OwnerToCoreScale);
Recipe.maximumRadiusSubvoxels = WorldAssemblyFixed(Params.IslandMaxRadius * OwnerToCoreScale);
Recipe.thicknessRatioQ16 = WorldAssemblyQ16(Params.ThicknessRatio);
Recipe.verticalJitterQ16 = WorldAssemblyQ16(Params.VerticalJitter);
Recipe.topFlattenQ16 = WorldAssemblyQ16(Params.TopFlatten);
Recipe.roughnessSubvoxels = WorldAssemblyFixed(Params.SurfaceRoughness * OwnerToCoreScale);
Recipe.blendRadiusSubvoxels = WorldAssemblyFixed(Params.SDFBlendRadius * OwnerToCoreScale);
Recipe.boundarySealSubvoxels = WorldAssemblyFixed(Params.BoundarySealThickness * OwnerToCoreScale);
std::string PlanError;
const std::optional<FloatingIslandManifestV1> Planned = PlanFloatingIslandCluster(Recipe, PlanError);
if (!Planned)
{
OutError = FString::Printf(TEXT("FloatingIslands automatic plan for strate %d failed: %s"),
Slot.StrateIndex, UTF8_TO_TCHAR(PlanError.c_str()));
return false;
}
IslandPlan.manifest = *Planned;
Manifest.floatingIslands.push_back(std::move(IslandPlan));
return true;
}
TSharedPtr<FJsonValue> SerializeCoreStruct(
const UStruct* Type, const void* Data, TSet<const UObject*>& ActiveObjects,
@@ -287,21 +748,98 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
{
return false;
}
if (ManifestJson.IsEmpty())
std::string CoreError;
std::optional<VoxelForgeCore::WorldAssemblyManifest> Manifest;
const bool bImportedOverride = !ManifestJson.IsEmpty();
if (bImportedOverride)
{
return true;
FTCHARToUTF8 Utf8Json(*ManifestJson);
Manifest = VoxelForgeCore::WorldAssemblyManifest::FromJson(
std::string_view(Utf8Json.Get(), static_cast<std::size_t>(Utf8Json.Length())), CoreError);
if (!Manifest)
{
OutError = FString::Printf(TEXT("Core world assembly validation failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
}
else
{
VoxelForgeCore::WorldAssemblyManifest Generated;
Generated.recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
int32 SurfaceSlotCount = 0;
int32 IslandSlotCount = 0;
for (const FStrateSlot& Slot : StrateManager->GetLayout())
{
if (Slot.Definition == nullptr) continue;
const ECaveGeneratorType Type = StrateManager->GetGeneratorTypeForChunk(
FIntVector(0, 0, Slot.TopChunkZ));
SurfaceSlotCount += Type == ECaveGeneratorType::SurfaceWorld ? 1 : 0;
IslandSlotCount += Type == ECaveGeneratorType::FloatingIslands ? 1 : 0;
}
if (SurfaceSlotCount == 0 && IslandSlotCount == 0)
{
return true;
}
if (SurfaceSlotCount == 0)
{
VoxelForgeCore::WorldAssemblyBiomeProfileV1 CarrierProfile;
CarrierProfile.stableId = 0x1001u;
Generated.recipe.biomes.push_back(CarrierProfile);
}
int32 BuiltSurfaceCount = 0;
std::vector<VoxelForgeCore::WorldAssemblyRecipeV1> SurfaceRecipes;
for (const FStrateSlot& Slot : StrateManager->GetLayout())
{
if (Slot.Definition == nullptr) continue;
const ECaveGeneratorType Type = StrateManager->GetGeneratorTypeForChunk(
FIntVector(0, 0, Slot.TopChunkZ));
if (Type == ECaveGeneratorType::SurfaceWorld)
{
VoxelForgeCore::WorldAssemblyRecipeV1 SlotRecipe;
SlotRecipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
VoxelForgeCore::WorldAssemblyStratumPlanV1 Stratum;
if (!BuildSurfaceStratumPlan(StrateManager, Slot,
SlotRecipe, Stratum, OutError))
{
return false;
}
if (BuiltSurfaceCount == 0) Generated.recipe = SlotRecipe;
SurfaceRecipes.push_back(SlotRecipe);
Generated.plan.strates.push_back(std::move(Stratum));
++BuiltSurfaceCount;
}
else if (Type == ECaveGeneratorType::FloatingIslands)
{
if (!BuildFloatingIslandStratumPlan(StrateManager, Slot, Generated, OutError))
{
return false;
}
}
}
if (BuiltSurfaceCount > 1)
{
for (std::size_t Index = 0; Index < Generated.plan.strates.size(); ++Index)
{
Generated.plan.strates[Index].hasRecipeOverride = true;
Generated.plan.strates[Index].recipeOverride = SurfaceRecipes[Index];
}
}
if (!Generated.finalize(CoreError))
{
OutError = FString::Printf(TEXT("Core automatic world assembly planning failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
ManifestHash = FString::Printf(TEXT("%016llx"),
static_cast<unsigned long long>(Generated.contentHash));
Manifest = MoveTemp(Generated);
}
FTCHARToUTF8 Utf8Json(*ManifestJson);
std::string CoreError;
const std::optional<VoxelForgeCore::WorldAssemblyManifest> Manifest =
VoxelForgeCore::WorldAssemblyManifest::FromJson(
std::string_view(Utf8Json.Get(), static_cast<std::size_t>(Utf8Json.Length())), CoreError);
if (!Manifest)
if (!Manifest.has_value())
{
OutError = FString::Printf(TEXT("Core world assembly validation failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
return true;
}
if (Manifest->recipe.seed != static_cast<uint32>(StrateManager->GetWorldSeed()))
{
@@ -394,6 +932,12 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
}
Root->SetObjectField(TEXT("world_assembly"), ManifestObject.ToSharedRef());
Root->SetStringField(TEXT("world_assembly_content_hash"), ManifestHash);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreRuntime] world_assembly_plan=%s source=%s surface_strates=%d island_strates=%d hash=%s"),
bImportedOverride ? TEXT("imported") : TEXT("generated_at_startup"),
bImportedOverride ? TEXT("settings_override") : TEXT("resolved_settings_and_layout"),
static_cast<int32>(Manifest->plan.strates.size()),
static_cast<int32>(Manifest->floatingIslands.size()), *ManifestHash);
return true;
}
}
@@ -95,6 +95,7 @@ bool UVoxelSettings::SetWorldAssemblyManifestJson(const FString& InJson, FString
WorldAssemblyManifestJson = UTF8_TO_TCHAR(CanonicalJson.c_str());
WorldAssemblyManifestHash = FString::Printf(TEXT("%016llx"),
static_cast<unsigned long long>(Manifest->contentHash));
bUseWorldAssemblyManifestOverride = true;
MarkPackageDirty();
OutReport = FString::Printf(
TEXT("imported native world assembly %s (%d SurfaceWorld plans, %d floating-island plans)"),
@@ -109,8 +110,17 @@ bool UVoxelSettings::GetWorldAssemblyManifestJson(
OutCanonicalJson.Reset();
OutContentHash.Reset();
OutError.Reset();
if (!bUseWorldAssemblyManifestOverride)
{
return true;
}
if (WorldAssemblyManifestJson.IsEmpty())
{
if (bUseWorldAssemblyManifestOverride)
{
OutError = TEXT("world assembly override is enabled but its manifest JSON is empty");
return false;
}
if (!WorldAssemblyManifestHash.IsEmpty())
{
OutError = TEXT("world assembly hash is set but its manifest JSON is empty");
+122 -17
View File
@@ -152,6 +152,7 @@ public:
std::atomic<bool> bShuttingDown{false};
std::atomic<uint64> NextEditSequence{1};
int32 FullResolutionLodCount = 0;
FString InitialFailureReason;
};
#if WITH_EDITOR
@@ -2456,12 +2457,13 @@ void AVoxelWorld::BeginPlay()
DiffLayer->SetBudget(Settings->MaxModifications, Settings->MaxBrushRadius, Settings->MaxTotalVolume);
Generator->SetDiffLayer(DiffLayer);
if (bUseVoxelForgeCore && !InitializeVoxelForgeCoreWorld())
if (bUseVoxelForgeCore)
{
// A failed opt-in must stay visibly failed. Falling back to the legacy route here would
// make the switch appear to work while silently changing the generated world.
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeCoreRuntime] Core mode is enabled but initialization did not complete."));
FString CoreFailureReason;
if (!InitializeVoxelForgeCoreWorld(CoreFailureReason))
{
FallBackToLegacyAfterCoreInitializationFailure(CoreFailureReason);
}
}
if (bTestCeilingViewEnabled && IsParameterizedResearchFieldActive())
@@ -6596,6 +6598,12 @@ void AVoxelWorld::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_Tick); // game-thread streaming orchestration breakdown
if (bUseVoxelForgeCore && VoxelForgeCoreState.IsValid()
&& VoxelForgeCoreState->bInitialFailed.load(std::memory_order_acquire))
{
FallBackToLegacyAfterCoreInitializationFailure(
VoxelForgeCoreState->InitialFailureReason);
}
#if WITH_EDITOR
if (bTestResearchProfileSwitchConfigured
&& !bTestResearchProfileSwitchApplied
@@ -7217,6 +7225,14 @@ FVector AVoxelWorld::GetVoxelForgeCoreDefaultCenterWorld() const
return GetActorTransform().TransformPosition(CoreFallbackCenterLocalCm);
}
bool AVoxelWorld::GetVoxelForgeCoreWorldAssembly(
FString& OutCanonicalJson, FString& OutContentHash) const
{
OutCanonicalJson = VoxelForgeCoreWorldAssemblyJson;
OutContentHash = VoxelForgeCoreWorldAssemblyHash;
return bUseVoxelForgeCore && !OutCanonicalJson.IsEmpty() && !OutContentHash.IsEmpty();
}
bool AVoxelWorld::QueryVoxelForgeCoreDensityAt(FVector WorldLocation, float& OutDensity) const
{
OutDensity = 0.0f;
@@ -7230,13 +7246,15 @@ bool AVoxelWorld::QueryVoxelForgeCoreDensityAt(FVector WorldLocation, float& Out
return FMath::IsFinite(OutDensity);
}
bool AVoxelWorld::InitializeVoxelForgeCoreWorld()
bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
{
OutFailureReason.Reset();
VoxelForgeCoreWorldAssemblyJson.Reset();
VoxelForgeCoreWorldAssemblyHash.Reset();
if (!bUseVoxelForgeCore) return true;
if (!Settings || !StrateManager)
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeCoreRuntime] initialization failed: Settings or resolved StrateManager is missing."));
OutFailureReason = TEXT("Settings or the resolved strate layout is missing.");
return false;
}
@@ -7247,7 +7265,8 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld()
if (!VoxelForgeCoreAdapter::SerializeResolvedWorld(
Settings, StrateManager, ActiveCoreClipRadius, ActiveCoreMaxClipLevel, Json, ExportError))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeCoreRuntime] snapshot export failed: %s"), *ExportError);
OutFailureReason = FString::Printf(TEXT("snapshot export failed: %s"), *ExportError);
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeCoreRuntime] %s"), *OutFailureReason);
return false;
}
FTCHARToUTF8 Utf8Json(*Json);
@@ -7257,12 +7276,22 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld()
std::string_view(Utf8Json.Get(), static_cast<std::size_t>(Utf8Json.Length())), CoreError);
if (!Snapshot)
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeCoreRuntime] schema-v1 resolve failed: %s"),
OutFailureReason = FString::Printf(TEXT("schema-v1 snapshot resolve failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeCoreRuntime] %s"), *OutFailureReason);
return false;
}
if (const VoxelForgeCore::WorldAssemblyManifest* Assembly = Snapshot->worldAssembly())
{
const std::string CanonicalAssembly = Assembly->ToJson();
if (CanonicalAssembly.empty())
{
OutFailureReason = TEXT("the generated world assembly could not be serialized canonically.");
return false;
}
VoxelForgeCoreWorldAssemblyJson = UTF8_TO_TCHAR(CanonicalAssembly.c_str());
VoxelForgeCoreWorldAssemblyHash = FString::Printf(TEXT("%016llx"),
static_cast<unsigned long long>(Assembly->contentHash));
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreRuntime] world_assembly_hash=%016llx recipe_hash=%016llx plan_hash=%016llx surface_strates=%d island_strates=%d"),
static_cast<unsigned long long>(Assembly->contentHash),
@@ -7294,7 +7323,7 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld()
{CameraVoxels.X, CameraVoxels.Y, CameraVoxels.Z});
if (InitialDesired.empty())
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeCoreRuntime] Core returned an empty initial clipmap."));
OutFailureReason = TEXT("Core produced an empty initial clipmap.");
return false;
}
@@ -7327,7 +7356,7 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld()
}
if (FineMin.x == IntMax || ClipMin.x == IntMax)
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeCoreRuntime] Core initial clipmap bounds are invalid."));
OutFailureReason = TEXT("Core produced invalid initial clipmap bounds.");
return false;
}
@@ -7373,6 +7402,8 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld()
}
if (!bCompiled)
{
State->InitialFailureReason = FString::Printf(
TEXT("Core shape compilation failed: %s"), UTF8_TO_TCHAR(Error.c_str()));
State->bInitialFailed.store(true, std::memory_order_release);
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeCoreRuntime] shape compile failed: %s"),
UTF8_TO_TCHAR(Error.c_str()));
@@ -7394,6 +7425,49 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld()
return true;
}
void AVoxelWorld::FallBackToLegacyAfterCoreInitializationFailure(const FString& FailureReason)
{
if (!bUseVoxelForgeCore) return;
const FString Reason = FailureReason.IsEmpty()
? TEXT("Core initialization failed without a diagnostic reason.") : FailureReason;
if (VoxelForgeCoreState.IsValid())
{
VoxelForgeCoreState->bShuttingDown.store(true, std::memory_order_release);
VoxelForgeCoreState.Reset();
}
VoxelForgeCoreWorldAssemblyJson.Reset();
VoxelForgeCoreWorldAssemblyHash.Reset();
bUseVoxelForgeCore = false;
CoreFallbackCenterLocalCm = FVector::ZeroVector;
if (Mesher != nullptr)
{
Mesher->SetGeometryVoxelSizeCm(VOXEL_SIZE);
}
if (ContentManager != nullptr && Settings != nullptr)
{
ContentManager->Initialize(this, StrateManager, Generator, Settings,
Settings->GetEffectiveWorldSeed());
ContentManager->ClearAll();
}
if (AtmosphereManager != nullptr)
{
AtmosphereManager->Reset();
}
RegenerateAllChunks();
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeCoreRuntime] Core initialization failed; falling back to the legacy generator. reason=%s"),
*Reason);
if (GEngine != nullptr)
{
const FString ScreenMessage = FString::Printf(
TEXT("VoxelForge Core could not start: %s Legacy voxel generation is active."), *Reason);
GEngine->AddOnScreenDebugMessage(INDEX_NONE, 20.0f, FColor::Red, ScreenMessage);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreRuntime] fallback_notice=on_screen seconds=20 reason=%s"), *Reason);
}
}
bool AVoxelWorld::TryGetPlayerPosition(FVector& OutPosition, APawn** OutPawn) const
{
OutPosition = FVector::ZeroVector;
@@ -13293,12 +13367,43 @@ FString AVoxelWorld::GetCurrentSeasonContentHash() const
return SeasonHash;
}
FString AssemblyJson;
FString AssemblyHash;
FString AssemblyError;
if (!Settings->GetWorldAssemblyManifestJson(AssemblyJson, AssemblyHash, AssemblyError))
FString AssemblyHash = VoxelForgeCoreWorldAssemblyHash;
if (AssemblyHash.IsEmpty())
{
return FString();
FString AssemblyJson;
FString AssemblyError;
if (!Settings->GetWorldAssemblyManifestJson(AssemblyJson, AssemblyHash, AssemblyError))
{
return FString();
}
if (AssemblyHash.IsEmpty() && StrateManager != nullptr)
{
// Multiplayer/content-hash callers can ask before BeginPlay has cached the startup
// artifact. Resolve the same deterministic plan from the already-resolved settings
// and layout instead of temporarily returning an identity without that plan.
FString SnapshotJson;
if (!VoxelForgeCoreAdapter::SerializeResolvedWorld(Settings, StrateManager,
FMath::Clamp(CoreClipRadius, 1, 64),
FMath::Clamp(CoreMaxClipLevel, 0, 8), SnapshotJson, AssemblyError))
{
return FString();
}
FTCHARToUTF8 Utf8Snapshot(*SnapshotJson);
std::string CoreError;
const std::optional<VoxelForgeCore::ResolvedWorldSnapshot> Snapshot =
VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
std::string_view(Utf8Snapshot.Get(),
static_cast<std::size_t>(Utf8Snapshot.Length())), CoreError);
if (!Snapshot)
{
return FString();
}
if (const VoxelForgeCore::WorldAssemblyManifest* Assembly = Snapshot->worldAssembly())
{
AssemblyHash = FString::Printf(TEXT("%016llx"),
static_cast<unsigned long long>(Assembly->contentHash));
}
}
}
const bool bHybridMesher = CoreMesherRecipe != EVoxelForgeCoreMesherRecipe::MarchingCubesV1;
if (AssemblyHash.IsEmpty() && !bHybridMesher)
+5
View File
@@ -383,6 +383,11 @@ public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Voxel|Strates")
FString WorldAssemblyManifestHash;
/** Use imported JSON above instead of planning from the resolved settings and layout. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates|World Assembly",
meta = (DisplayName = "Use Imported World Assembly Override"))
bool bUseWorldAssemblyManifestOverride = false;
#if WITH_EDITORONLY_DATA
UPROPERTY(EditAnywhere, Category = "Voxel|Strates|World Assembly", meta = (FilePathFilter = "json"))
FFilePath SourceWorldAssemblyManifestJson;
+5 -1
View File
@@ -588,6 +588,7 @@ public:
bool IsVoxelForgeCoreReady() const;
FVector GetVoxelForgeCoreDefaultCenterWorld() const;
bool QueryVoxelForgeCoreDensityAt(FVector WorldLocation, float& OutDensity) const;
bool GetVoxelForgeCoreWorldAssembly(FString& OutCanonicalJson, FString& OutContentHash) const;
UFUNCTION(BlueprintPure, Category = "Voxel World|Strate")
int32 GetStrateAtPosition(FVector WorldPosition) const;
@@ -675,7 +676,8 @@ private:
void SetDensityVolumeMaterialParamsDisabled();
/** Runtime Core snapshot, world lifetime, and worker-side streaming bridge. */
bool InitializeVoxelForgeCoreWorld();
bool InitializeVoxelForgeCoreWorld(FString& OutFailureReason);
void FallBackToLegacyAfterCoreInitializationFailure(const FString& FailureReason);
void BuildCoreDesiredTiles(const FIntVector& Center, const FVector& PlayerPosition,
APawn* PlayerPawn, const FVector& PlayerHeading,
TArray<FVoxelTileKey>& OutLeavers);
@@ -740,6 +742,8 @@ private:
TWeakObjectPtr<UVolumeTexture> LastBoundVolTex0; // re-push MIDs if the L0 texture was recreated
TSharedPtr<FVoxelForgeCoreRuntimeState, ESPMode::ThreadSafe> VoxelForgeCoreState;
FString VoxelForgeCoreWorldAssemblyJson;
FString VoxelForgeCoreWorldAssemblyHash;
FVector CoreFallbackCenterLocalCm = FVector::ZeroVector;
bool bCoreRegionCompilePending = false;
TArray<FVoxelTileKey> PendingCoreRegionTiles;
File diff suppressed because it is too large. Load diff