wip(assembler): M3 round 3 -- separated islands + Unreal island route (owner build 18:46)
Islands read as separate (91.89% open sky at mid-height), underside cavity closed, rim teeth were capture shading. Island manifest import + BuildHost proof passed on the pre-cache tree; M1 SurfaceWorld and M2 V1/V2 gates passed. Known open: final XY-column cache compiled in the owner build but is unverified; island LOD0 p95 ~155 ms misses the 133 ms sprint deadline (legacy ~43 ms); the island manifest only fits the BuildHost fixture layout (owner's world: Core init fails, empty world) -> round 4 builds plans inside Unreal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
4afea7c802
commit
48fa028086
37 files changed
+3788
-107
No files matched your search
@@ -128,6 +128,110 @@ struct FCoreSurfaceSnapshot
|
||||
}
|
||||
};
|
||||
|
||||
struct FCoreFloatingIslandFieldSnapshot
|
||||
{
|
||||
VoxelForgeCore::FloatingIslandRecipeV1 Recipe;
|
||||
VoxelForgeCore::FloatingIslandPlanV1 Plan;
|
||||
TUniquePtr<VoxelForgeCore::FloatingIslandFieldV1> Field;
|
||||
};
|
||||
|
||||
struct FCoreFloatingIslandSnapshot
|
||||
{
|
||||
TMap<int32, TSharedPtr<const FCoreFloatingIslandFieldSnapshot, ESPMode::ThreadSafe>> Fields;
|
||||
|
||||
const FCoreFloatingIslandFieldSnapshot* FindField(int32 StrateIndex) const
|
||||
{
|
||||
const auto* Found = Fields.Find(StrateIndex);
|
||||
return Found != nullptr && Found->IsValid() ? Found->Get() : nullptr;
|
||||
}
|
||||
|
||||
bool GetDensity(int32 StrateIndex, float LegacyVoxelX, float LegacyVoxelY,
|
||||
float LegacyVoxelZ, float& OutDensity) const
|
||||
{
|
||||
OutDensity = 1.0f;
|
||||
const FCoreFloatingIslandFieldSnapshot* Entry = FindField(StrateIndex);
|
||||
if (Entry == nullptr || !Entry->Field.IsValid()
|
||||
|| !FMath::IsFinite(LegacyVoxelX) || !FMath::IsFinite(LegacyVoxelY)
|
||||
|| !FMath::IsFinite(LegacyVoxelZ))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// A legacy voxel is 128 subvoxels at 12.5 cm; Core field distances are converted back to legacy units.
|
||||
// Un voxel historique vaut 128 sous-voxels a 12,5 cm ; la distance Core revient en unites historiques.
|
||||
constexpr double LegacyVoxelSubvoxels = 128.0;
|
||||
constexpr double MaximumInput = static_cast<double>(MAX_int64) / LegacyVoxelSubvoxels;
|
||||
if (FMath::Abs(static_cast<double>(LegacyVoxelX)) > MaximumInput
|
||||
|| FMath::Abs(static_cast<double>(LegacyVoxelY)) > MaximumInput
|
||||
|| FMath::Abs(static_cast<double>(LegacyVoxelZ)) > MaximumInput)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const int64 Density = Entry->Field->densitySubvoxels(
|
||||
static_cast<int64>(std::llround(static_cast<double>(LegacyVoxelX) * LegacyVoxelSubvoxels)),
|
||||
static_cast<int64>(std::llround(static_cast<double>(LegacyVoxelY) * LegacyVoxelSubvoxels)),
|
||||
static_cast<int64>(std::llround(static_cast<double>(LegacyVoxelZ) * LegacyVoxelSubvoxels)));
|
||||
OutDensity = static_cast<float>(static_cast<double>(Density) / LegacyVoxelSubvoxels);
|
||||
return FMath::IsFinite(OutDensity);
|
||||
}
|
||||
|
||||
bool GetTopAnchor(int32 StrateIndex, const FVector2D& LocalXYCm,
|
||||
float& OutGroundLocalZCm) const
|
||||
{
|
||||
OutGroundLocalZCm = 0.0f;
|
||||
const FCoreFloatingIslandFieldSnapshot* Entry = FindField(StrateIndex);
|
||||
if (Entry == nullptr || !Entry->Field.IsValid()
|
||||
|| Entry->Plan.islands.empty()
|
||||
|| !FMath::IsFinite(LocalXYCm.X) || !FMath::IsFinite(LocalXYCm.Y))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
constexpr double SubvoxelsPerCoreVoxel = 256.0;
|
||||
constexpr double CentimetersPerCoreVoxel = 25.0;
|
||||
const double XSubvoxels = static_cast<double>(LocalXYCm.X)
|
||||
* SubvoxelsPerCoreVoxel / CentimetersPerCoreVoxel;
|
||||
const double YSubvoxels = static_cast<double>(LocalXYCm.Y)
|
||||
* SubvoxelsPerCoreVoxel / CentimetersPerCoreVoxel;
|
||||
constexpr double MaximumInput = static_cast<double>(MAX_int64) - 1024.0;
|
||||
if (FMath::Abs(XSubvoxels) > MaximumInput || FMath::Abs(YSubvoxels) > MaximumInput)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const int64 X = static_cast<int64>(std::llround(XSubvoxels));
|
||||
const int64 Y = static_cast<int64>(std::llround(YSubvoxels));
|
||||
int64 SearchTop = Entry->Plan.islands.front().topZSubvoxels;
|
||||
int64 SearchBottom = Entry->Plan.islands.front().bottomZSubvoxels;
|
||||
for (const auto& Island : Entry->Plan.islands)
|
||||
{
|
||||
SearchTop = FMath::Max(SearchTop, Island.topZSubvoxels);
|
||||
SearchBottom = FMath::Min(SearchBottom, Island.bottomZSubvoxels);
|
||||
}
|
||||
SearchTop += 4 * 256;
|
||||
SearchBottom -= 4 * 256;
|
||||
constexpr int64 ScanStepSubvoxels = 128;
|
||||
int64 PreviousZ = SearchTop;
|
||||
int64 PreviousDensity = Entry->Field->densitySubvoxels(X, Y, PreviousZ);
|
||||
if (PreviousDensity < 0) return false;
|
||||
for (int64 Z = SearchTop - ScanStepSubvoxels; Z >= SearchBottom; Z -= ScanStepSubvoxels)
|
||||
{
|
||||
const int64 Density = Entry->Field->densitySubvoxels(X, Y, Z);
|
||||
if (PreviousDensity >= 0 && Density < 0)
|
||||
{
|
||||
const double Denominator = static_cast<double>(PreviousDensity - Density);
|
||||
const double Alpha = Denominator > 0.0
|
||||
? static_cast<double>(PreviousDensity) / Denominator : 0.5;
|
||||
const double SurfaceSubvoxels = static_cast<double>(PreviousZ)
|
||||
+ static_cast<double>(Z - PreviousZ) * Alpha;
|
||||
OutGroundLocalZCm = static_cast<float>(SurfaceSubvoxels
|
||||
* CentimetersPerCoreVoxel / SubvoxelsPerCoreVoxel);
|
||||
return FMath::IsFinite(OutGroundLocalZCm);
|
||||
}
|
||||
PreviousZ = Z;
|
||||
PreviousDensity = Density;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Global safety cap on spawned decoration ACTORS per cell (across all entries).
|
||||
// HISM instances are exempt — they're batched render data, capped per entry by MaxPerChunk.
|
||||
static constexpr int32 GMaxDecorationActorsPerCell = 400;
|
||||
@@ -174,6 +278,7 @@ void UVoxelContentManager::Initialize(AActor* InOwner, UVoxelStrateManager* InSt
|
||||
Settings = InSettings;
|
||||
Seed = InSeed;
|
||||
NativeSurfaceSnapshot.Reset();
|
||||
NativeFloatingIslandSnapshot.Reset();
|
||||
|
||||
if (Settings)
|
||||
{
|
||||
@@ -196,6 +301,33 @@ void UVoxelContentManager::Initialize(AActor* InOwner, UVoxelStrateManager* InSt
|
||||
Snapshot->Plans.Add(Plan.strateIndex, Plan);
|
||||
}
|
||||
NativeSurfaceSnapshot = Snapshot;
|
||||
|
||||
if (!Manifest->floatingIslands.empty())
|
||||
{
|
||||
TSharedPtr<FCoreFloatingIslandSnapshot, ESPMode::ThreadSafe> IslandSnapshot =
|
||||
MakeShared<FCoreFloatingIslandSnapshot, ESPMode::ThreadSafe>();
|
||||
for (const auto& IslandPlan : Manifest->floatingIslands)
|
||||
{
|
||||
TSharedPtr<FCoreFloatingIslandFieldSnapshot, ESPMode::ThreadSafe> Entry =
|
||||
MakeShared<FCoreFloatingIslandFieldSnapshot, ESPMode::ThreadSafe>();
|
||||
Entry->Recipe = IslandPlan.manifest.recipe;
|
||||
Entry->Plan = IslandPlan.manifest.plan;
|
||||
Entry->Field = MakeUnique<VoxelForgeCore::FloatingIslandFieldV1>(
|
||||
Entry->Recipe, Entry->Plan);
|
||||
if (!Entry->Field->isValid())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning,
|
||||
TEXT("[VoxelContentManager] Core floating-island field rejected strate %d."),
|
||||
IslandPlan.strateIndex);
|
||||
continue;
|
||||
}
|
||||
IslandSnapshot->Fields.Add(IslandPlan.strateIndex, Entry);
|
||||
}
|
||||
if (!IslandSnapshot->Fields.IsEmpty())
|
||||
{
|
||||
NativeFloatingIslandSnapshot = IslandSnapshot;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -244,6 +376,66 @@ bool UVoxelContentManager::QueryNativeSurfaceAnchors(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UVoxelContentManager::QueryNativeFloatingIslandAnchor(
|
||||
int32 StrateIndex, const FVector2D& LocalXYCm, float& OutGroundLocalZCm) const
|
||||
{
|
||||
OutGroundLocalZCm = 0.0f;
|
||||
const AVoxelWorld* VoxelWorld = Cast<AVoxelWorld>(Owner.Get());
|
||||
if (VoxelWorld == nullptr || !VoxelWorld->bUseVoxelForgeCore
|
||||
|| !NativeFloatingIslandSnapshot.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return NativeFloatingIslandSnapshot->GetTopAnchor(
|
||||
StrateIndex, LocalXYCm, OutGroundLocalZCm);
|
||||
}
|
||||
|
||||
bool UVoxelContentManager::QueryStrateWaterAnchor(
|
||||
int32 StrateIndex, bool& bOutHasWater, float& OutWaterLocalZCm) const
|
||||
{
|
||||
bOutHasWater = false;
|
||||
OutWaterLocalZCm = -FLT_MAX;
|
||||
const AVoxelWorld* VoxelWorld = Cast<AVoxelWorld>(Owner.Get());
|
||||
if (VoxelWorld == nullptr || StrateManager == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (VoxelWorld->bUseVoxelForgeCore && NativeSurfaceSnapshot.IsValid())
|
||||
{
|
||||
bool bHasPlan = false;
|
||||
bool bEnabled = false;
|
||||
float NativeWaterLocalZCm = -FLT_MAX;
|
||||
if (NativeSurfaceSnapshot->GetWater(
|
||||
StrateIndex, bHasPlan, bEnabled, NativeWaterLocalZCm) && bHasPlan)
|
||||
{
|
||||
bOutHasWater = bEnabled;
|
||||
OutWaterLocalZCm = bEnabled ? NativeWaterLocalZCm : -FLT_MAX;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const FStrateSlot& Slot : StrateManager->GetLayout())
|
||||
{
|
||||
if (Slot.StrateIndex != StrateIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const int32 MidChunkZ = Slot.BottomChunkZ
|
||||
+ (Slot.TopChunkZ - Slot.BottomChunkZ) / 2;
|
||||
const float ManagerWaterVoxelZ = StrateManager->GetWaterLevelWorldZForChunk(
|
||||
FIntVector(0, 0, MidChunkZ));
|
||||
if (ManagerWaterVoxelZ != -FLT_MAX)
|
||||
{
|
||||
bOutHasWater = true;
|
||||
const float ManagerWaterLocalZCm = ManagerWaterVoxelZ * VOXEL_SIZE;
|
||||
OutWaterLocalZCm = VoxelWorld->FromStrateManagerLocalZ(ManagerWaterLocalZCm);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UVoxelContentManager::BeginDestroy()
|
||||
{
|
||||
// Backstop: stop any worker from touching us. EndPlay → NotifyShutdown should have already drained.
|
||||
@@ -395,6 +587,11 @@ void UVoxelContentManager::UpdateDecorations(const FVector& PlayerWorldPos)
|
||||
&& CurrentCtx.bSurfaceWorld
|
||||
&& CurrentCtx.NativeSurface.IsValid()
|
||||
&& CurrentCtx.NativeSurface->FindPlan(StrateIndex) != nullptr;
|
||||
CurrentCtx.NativeIslands = NativeFloatingIslandSnapshot;
|
||||
CurrentCtx.bNativeFloatingIslands = VoxelWorld != nullptr && VoxelWorld->bUseVoxelForgeCore
|
||||
&& StrateManager->GetGeneratorTypeForChunk(RepChunk) == ECaveGeneratorType::FloatingIslands
|
||||
&& CurrentCtx.NativeIslands.IsValid()
|
||||
&& CurrentCtx.NativeIslands->FindField(StrateIndex) != nullptr;
|
||||
{
|
||||
bool bHasNativePlan = false;
|
||||
bool bNativeWaterEnabled = false;
|
||||
@@ -710,7 +907,17 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
|
||||
FChunkBiomeCache BiomeCache;
|
||||
const bool bHasBiomes = Ctx.BiomeCtx.IsValid();
|
||||
|
||||
auto D = [&](float VX, float VY, float VZ) { return Gen->GetDensityAt(VX, VY, VZ); };
|
||||
auto D = [&](float VX, float VY, float VZ)
|
||||
{
|
||||
if (Ctx.bNativeFloatingIslands && Ctx.NativeIslands.IsValid())
|
||||
{
|
||||
float NativeDensity = 1.0f;
|
||||
if (Ctx.NativeIslands->GetDensity(Ctx.StrateIndex, VX, VY, VZ, NativeDensity))
|
||||
return NativeDensity;
|
||||
return 1.0f;
|
||||
}
|
||||
return Gen->GetDensityAt(VX, VY, VZ);
|
||||
};
|
||||
auto GetSurface = [&](float VX, float VY, float& OutGround, float& OutCap)
|
||||
{
|
||||
if (Ctx.bNativeSurface && Ctx.NativeSurface.IsValid())
|
||||
@@ -1338,6 +1545,21 @@ bool UVoxelContentManager::FindLandmarkColumn(const UVoxelGenerator* Gen, const
|
||||
{
|
||||
if (!Gen) return false;
|
||||
|
||||
const auto Density = [&](float SampleX, float SampleY, float SampleZ)
|
||||
{
|
||||
if (Ctx.bNativeFloatingIslands && Ctx.NativeIslands.IsValid())
|
||||
{
|
||||
float NativeDensity = 1.0f;
|
||||
if (Ctx.NativeIslands->GetDensity(
|
||||
Ctx.StrateIndex, SampleX, SampleY, SampleZ, NativeDensity))
|
||||
{
|
||||
return NativeDensity;
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
return Gen->GetDensityAt(SampleX, SampleY, SampleZ);
|
||||
};
|
||||
|
||||
if (Ctx.bSurfaceWorld)
|
||||
{
|
||||
float hC, cC;
|
||||
@@ -1368,7 +1590,7 @@ bool UVoxelContentManager::FindLandmarkColumn(const UVoxelGenerator* Gen, const
|
||||
}
|
||||
// Floor / Wall / Any → the terrain top.
|
||||
if (!(hC >= Ctx.BottomVoxelZ && hC <= Ctx.TopVoxelZ)) return false;
|
||||
if (!Ctx.bNativeSurface && Gen->GetDensityAt(VX, VY, hC) > 0.5f) return false;
|
||||
if (!Ctx.bNativeSurface && Density(VX, VY, hC) > 0.5f) return false;
|
||||
float d, hXp, hXm, hYp, hYm;
|
||||
GetSurface(VX + 1.0f, VY, hXp, d);
|
||||
GetSurface(VX - 1.0f, VY, hXm, d);
|
||||
@@ -1384,19 +1606,19 @@ bool UVoxelContentManager::FindLandmarkColumn(const UVoxelGenerator* Gen, const
|
||||
// Bounded by ColDepth like the decoration march (this runs SYNCHRONOUSLY on the game thread):
|
||||
// once past open air, a solid run longer than ColDepth means bedrock down to the strate floor —
|
||||
// stop instead of paying GetDensityAt across the whole remaining band.
|
||||
float PrevD = Gen->GetDensityAt(VX, VY, Ctx.TopVoxelZ);
|
||||
float PrevD = Density(VX, VY, Ctx.TopVoxelZ);
|
||||
bool bSeenAir = (PrevD >= 0.0f);
|
||||
float SolidRun = 0.0f;
|
||||
for (float Z = Ctx.TopVoxelZ - Step; Z >= Ctx.BottomVoxelZ; Z -= Step)
|
||||
{
|
||||
const float Dz = Gen->GetDensityAt(VX, VY, Z);
|
||||
const float Dz = Density(VX, VY, Z);
|
||||
if ((PrevD >= 0.0f) != (Dz >= 0.0f)) // air ↔ solid crossing
|
||||
{
|
||||
float ZLo = Z, ZHi = Z + Step, DHi = PrevD, DLo = Dz;
|
||||
for (int32 It = 0; It < 4; ++It)
|
||||
{
|
||||
const float ZM = 0.5f * (ZLo + ZHi);
|
||||
const float DM = Gen->GetDensityAt(VX, VY, ZM);
|
||||
const float DM = Density(VX, VY, ZM);
|
||||
if ((DM >= 0.0f) == (DHi >= 0.0f)) { ZHi = ZM; DHi = DM; }
|
||||
else { ZLo = ZM; DLo = DM; }
|
||||
}
|
||||
@@ -1405,9 +1627,9 @@ bool UVoxelContentManager::FindLandmarkColumn(const UVoxelGenerator* Gen, const
|
||||
const float ZC = ZLo + (ZHi - ZLo) * T;
|
||||
|
||||
const FVector LocalGrad(
|
||||
Gen->GetDensityAt(VX + 1.0f, VY, ZC) - Gen->GetDensityAt(VX - 1.0f, VY, ZC),
|
||||
Gen->GetDensityAt(VX, VY + 1.0f, ZC) - Gen->GetDensityAt(VX, VY - 1.0f, ZC),
|
||||
Gen->GetDensityAt(VX, VY, ZC + 1.0f) - Gen->GetDensityAt(VX, VY, ZC - 1.0f));
|
||||
Density(VX + 1.0f, VY, ZC) - Density(VX - 1.0f, VY, ZC),
|
||||
Density(VX, VY + 1.0f, ZC) - Density(VX, VY - 1.0f, ZC),
|
||||
Density(VX, VY, ZC + 1.0f) - Density(VX, VY, ZC - 1.0f));
|
||||
FVector N = OwnerXf.TransformVectorNoScale(LocalGrad).GetSafeNormal();
|
||||
if (N.IsNearlyZero()) N = FVector::UpVector;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace
|
||||
{
|
||||
using FCoreJsonWriter = TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>;
|
||||
constexpr double CoreVoxelSizeCm = 25.0;
|
||||
constexpr double OwnerVoxelSizeCm = 12.5;
|
||||
|
||||
TSharedPtr<FJsonValue> SerializeCoreStruct(
|
||||
const UStruct* Type, const void* Data, TSet<const UObject*>& ActiveObjects,
|
||||
@@ -309,20 +310,32 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
}
|
||||
|
||||
int32 SurfaceSlotCount = 0;
|
||||
int32 FloatingIslandSlotCount = 0;
|
||||
for (const FStrateSlot& Slot : StrateManager->GetLayout())
|
||||
{
|
||||
if (Slot.Definition != nullptr
|
||||
&& Slot.Definition->GeneratorType == ECaveGeneratorType::SurfaceWorld)
|
||||
if (Slot.Definition == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const ECaveGeneratorType Archetype = StrateManager->GetGeneratorTypeForChunk(
|
||||
FIntVector(0, 0, Slot.TopChunkZ));
|
||||
if (Archetype == ECaveGeneratorType::SurfaceWorld)
|
||||
{
|
||||
++SurfaceSlotCount;
|
||||
}
|
||||
else if (Archetype == ECaveGeneratorType::FloatingIslands)
|
||||
{
|
||||
++FloatingIslandSlotCount;
|
||||
}
|
||||
}
|
||||
if (SurfaceSlotCount == 0
|
||||
|| Manifest->plan.strates.size() != static_cast<std::size_t>(SurfaceSlotCount))
|
||||
if (Manifest->plan.strates.size() != static_cast<std::size_t>(SurfaceSlotCount)
|
||||
|| Manifest->floatingIslands.size() != static_cast<std::size_t>(FloatingIslandSlotCount))
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core world assembly has %d plans for %d resolved SurfaceWorld strates."),
|
||||
static_cast<int32>(Manifest->plan.strates.size()), SurfaceSlotCount);
|
||||
TEXT("Core world assembly has %d SurfaceWorld and %d floating-island plans for %d resolved SurfaceWorld and %d FloatingIslands strates."),
|
||||
static_cast<int32>(Manifest->plan.strates.size()),
|
||||
static_cast<int32>(Manifest->floatingIslands.size()),
|
||||
SurfaceSlotCount, FloatingIslandSlotCount);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -332,7 +345,8 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
const FStrateSlot* MatchingSlot = StrateManager->GetLayout().FindByPredicate(
|
||||
[&Plan](const FStrateSlot& Slot) { return Slot.StrateIndex == Plan.strateIndex; });
|
||||
if (MatchingSlot == nullptr || MatchingSlot->Definition == nullptr
|
||||
|| MatchingSlot->Definition->GeneratorType != ECaveGeneratorType::SurfaceWorld
|
||||
|| StrateManager->GetGeneratorTypeForChunk(FIntVector(0, 0, Plan.topChunkZ))
|
||||
!= ECaveGeneratorType::SurfaceWorld
|
||||
|| MatchingSlot->TopChunkZ != Plan.topChunkZ
|
||||
|| MatchingSlot->BottomChunkZ != Plan.bottomChunkZ
|
||||
|| MatchedStrateIndices.Contains(Plan.strateIndex))
|
||||
@@ -345,6 +359,25 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
MatchedStrateIndices.Add(Plan.strateIndex);
|
||||
}
|
||||
|
||||
for (const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1& Plan : Manifest->floatingIslands)
|
||||
{
|
||||
const FStrateSlot* MatchingSlot = StrateManager->GetLayout().FindByPredicate(
|
||||
[&Plan](const FStrateSlot& Slot) { return Slot.StrateIndex == Plan.strateIndex; });
|
||||
if (MatchingSlot == nullptr || MatchingSlot->Definition == nullptr
|
||||
|| StrateManager->GetGeneratorTypeForChunk(FIntVector(0, 0, Plan.topChunkZ))
|
||||
!= ECaveGeneratorType::FloatingIslands
|
||||
|| MatchingSlot->TopChunkZ != Plan.topChunkZ
|
||||
|| MatchingSlot->BottomChunkZ != Plan.bottomChunkZ
|
||||
|| MatchedStrateIndices.Contains(Plan.strateIndex))
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core floating-island plan for strate %d does not exactly match a unique resolved FloatingIslands slot."),
|
||||
Plan.strateIndex);
|
||||
return false;
|
||||
}
|
||||
MatchedStrateIndices.Add(Plan.strateIndex);
|
||||
}
|
||||
|
||||
const std::string CanonicalJson = Manifest->ToJson();
|
||||
if (CanonicalJson.empty())
|
||||
{
|
||||
@@ -403,6 +436,9 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
|
||||
Root->SetNumberField(TEXT("schema_version"), 1);
|
||||
Root->SetStringField(TEXT("source"), TEXT("VoxelForge Runtime Core snapshot"));
|
||||
Root->SetNumberField(TEXT("voxel_size_cm"), CoreVoxelSizeCm);
|
||||
Root->SetNumberField(TEXT("owner_voxel_size_cm"), OwnerVoxelSizeCm);
|
||||
Root->SetNumberField(TEXT("core_voxel_size_cm"), CoreVoxelSizeCm);
|
||||
Root->SetNumberField(TEXT("owner_to_core_length_scale"), OwnerVoxelSizeCm / CoreVoxelSizeCm);
|
||||
Root->SetNumberField(TEXT("seed"), Seed);
|
||||
Root->SetBoolField(TEXT("open_surface_entry"), Settings->bOpenSurfaceEntry);
|
||||
Root->SetNumberField(TEXT("origin_spine_radius"), OriginSpineRadius);
|
||||
|
||||
@@ -96,8 +96,10 @@ bool UVoxelSettings::SetWorldAssemblyManifestJson(const FString& InJson, FString
|
||||
WorldAssemblyManifestHash = FString::Printf(TEXT("%016llx"),
|
||||
static_cast<unsigned long long>(Manifest->contentHash));
|
||||
MarkPackageDirty();
|
||||
OutReport = FString::Printf(TEXT("imported native world assembly %s (%d stratum plans)"),
|
||||
*WorldAssemblyManifestHash, static_cast<int32>(Manifest->plan.strates.size()));
|
||||
OutReport = FString::Printf(
|
||||
TEXT("imported native world assembly %s (%d SurfaceWorld plans, %d floating-island plans)"),
|
||||
*WorldAssemblyManifestHash, static_cast<int32>(Manifest->plan.strates.size()),
|
||||
static_cast<int32>(Manifest->floatingIslands.size()));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -7264,11 +7264,12 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld()
|
||||
if (const VoxelForgeCore::WorldAssemblyManifest* Assembly = Snapshot->worldAssembly())
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreRuntime] world_assembly_hash=%016llx recipe_hash=%016llx plan_hash=%016llx strates=%d"),
|
||||
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),
|
||||
static_cast<unsigned long long>(Assembly->recipe.contentHash),
|
||||
static_cast<unsigned long long>(Assembly->plan.contentHash),
|
||||
static_cast<int32>(Assembly->plan.strates.size()));
|
||||
static_cast<int32>(Assembly->plan.strates.size()),
|
||||
static_cast<int32>(Assembly->floatingIslands.size()));
|
||||
}
|
||||
|
||||
TSharedPtr<FVoxelForgeCoreRuntimeState, ESPMode::ThreadSafe> State =
|
||||
@@ -13285,8 +13286,8 @@ int32 AVoxelWorld::GetCurrentSeason() const
|
||||
FString AVoxelWorld::GetCurrentSeasonContentHash() const
|
||||
{
|
||||
const FString SeasonHash = StrateManager ? StrateManager->GetSeasonContentHash() : FString();
|
||||
// Only active Core generation includes the optional native surface manifest in world identity.
|
||||
// Seul le Core actif inclut le manifeste de surface natif dans l'identite du monde.
|
||||
// Only active Core generation includes the optional native world assembly in world identity.
|
||||
// Seul le Core actif inclut l'assemblage natif facultatif dans l'identite du monde.
|
||||
if (!Settings || !bUseVoxelForgeCore)
|
||||
{
|
||||
return SeasonHash;
|
||||
|
||||
@@ -34,6 +34,9 @@ public:
|
||||
* dominant-biome change. */
|
||||
void UpdateForPlayer(const FVector& PlayerWorldPos);
|
||||
|
||||
/** Read the player's current atmosphere strate for isolated Core validation. / Lit la strate d'atmosphere active pour le test Core isole. */
|
||||
int32 GetCurrentStrateIndex() const { return CurrentStrateIndex; }
|
||||
|
||||
/** Tear down spawned layer actors + reset (season reset / shutdown). */
|
||||
void Reset();
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Containers/Queue.h"
|
||||
|
||||
struct FCoreFloatingIslandSnapshot;
|
||||
#include "VoxelTypes.h"
|
||||
#include "VoxelStrateTypes.h" // FStrateDecoration (resolved per dominant biome)
|
||||
#include "VoxelBiomeTypes.h" // FBiomeContext (per-column biome resolve on the worker)
|
||||
@@ -103,6 +105,16 @@ public:
|
||||
float& OutGroundLocalZCm, bool& bOutHasWater,
|
||||
float& OutWaterLocalZCm) const;
|
||||
|
||||
/** Find the top air-to-rock crossing in a Core FloatingIslands plan for content placement.
|
||||
* / Trouve le premier contact air-roche d'un plan Core FloatingIslands pour placer le contenu. */
|
||||
bool QueryNativeFloatingIslandAnchor(int32 StrateIndex, const FVector2D& LocalXYCm,
|
||||
float& OutGroundLocalZCm) const;
|
||||
|
||||
/** Read the effective water datum used by this strate's content plane.
|
||||
* / Lit le niveau d'eau effectif utilise par le plan de contenu de cette strate. */
|
||||
bool QueryStrateWaterAnchor(int32 StrateIndex, bool& bOutHasWater,
|
||||
float& OutWaterLocalZCm) const;
|
||||
|
||||
//--- DECORATIONS (distance-based world grid, async march) ----------------
|
||||
/** Stream decoration cells around the player: recompute the desired set on cell/strate change,
|
||||
* launch async march tasks (capped), and apply finished results budgeted. Call every Tick. */
|
||||
@@ -254,6 +266,8 @@ private:
|
||||
bool bSurfaceWorld = false; // heightfield archetype → use the GetSurfaceHeightAt oracle
|
||||
bool bNativeSurface = false; // Core manifest is the height oracle and water datum / Le manifeste Core definit le relief et le niveau d'eau.
|
||||
TSharedPtr<const FCoreSurfaceSnapshot, ESPMode::ThreadSafe> NativeSurface;
|
||||
bool bNativeFloatingIslands = false; // islands use the Core volume for column snaps / les iles utilisent le volume Core pour les ancres.
|
||||
TSharedPtr<const FCoreFloatingIslandSnapshot, ESPMode::ThreadSafe> NativeIslands;
|
||||
|
||||
// Strate biome field (PODs only → worker-safe). Empty ⇒ biomes disabled for this strate. The
|
||||
// worker resolves the dominant biome PER COLUMN (ResolveBiomeSampleAt) so decoration borders
|
||||
@@ -349,6 +363,7 @@ private:
|
||||
// Immutable Core manifest snapshot captured by decoration workers; workers never touch UObjects.
|
||||
// Instantane immuable du manifeste Core pour les workers de decoration; aucun worker ne touche aux UObjects.
|
||||
TSharedPtr<const FCoreSurfaceSnapshot, ESPMode::ThreadSafe> NativeSurfaceSnapshot;
|
||||
TSharedPtr<const FCoreFloatingIslandSnapshot, ESPMode::ThreadSafe> NativeFloatingIslandSnapshot;
|
||||
|
||||
int32 Seed = 0;
|
||||
|
||||
|
||||
@@ -373,10 +373,10 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Voxel|Strates")
|
||||
TSoftObjectPtr<UVoxelSeasonAsset> Season;
|
||||
|
||||
// Optional reviewed Core surface plan. The canonical JSON stays in this cooked settings asset;
|
||||
// its Core FNV digest is included in GetCurrentSeasonContentHash when present.
|
||||
// Plan de surface Core facultatif; le JSON canonique est cuit avec les reglages et son hash
|
||||
// Core est inclus dans GetCurrentSeasonContentHash lorsqu'il est present.
|
||||
// Optional reviewed Core SurfaceWorld and FloatingIslands plans. Canonical JSON stays in this
|
||||
// cooked settings asset; its Core FNV digest enters the season hash only while Core is enabled.
|
||||
// Plans Core SurfaceWorld et FloatingIslands facultatifs. Le JSON canonique est cuit ici ;
|
||||
// son hash FNV Core entre dans le hash de saison uniquement lorsque Core est actif.
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Voxel|Strates", meta = (MultiLine = true))
|
||||
FString WorldAssemblyManifestJson;
|
||||
|
||||
|
||||
@@ -1741,7 +1741,8 @@ struct VOXELFORGE_API FFloatingIslandParams
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float IslandDensity = 0.5f;
|
||||
|
||||
// Island horizontal radius range (voxels): 12-24 m landmass diameters by default.
|
||||
// Owner scale: 24–48 legacy voxels = 3–6 m radius at 12.5 cm/voxel (6–12 m diameter).
|
||||
// Echelle proprietaire : 24–48 voxels legacy = rayon de 3–6 m a 12,5 cm/voxel (diametre 6–12 m).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "2.0"))
|
||||
float IslandMinRadius = 24.0f;
|
||||
|
||||
@@ -1765,7 +1766,8 @@ struct VOXELFORGE_API FFloatingIslandParams
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0"))
|
||||
float SurfaceRoughness = 3.0f;
|
||||
|
||||
// SmoothMin blend radius for merging overlapping islands (voxels): 1.5 m by default.
|
||||
// SmoothMin blend radius: 6 legacy voxels = 0.75 m at the owner's 12.5 cm scale.
|
||||
// Rayon de fusion SmoothMin : 6 voxels legacy = 0,75 m a l'echelle proprietaire de 12,5 cm.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0"))
|
||||
float SDFBlendRadius = 6.0f;
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user