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:
Fr0zkaandClaude Opus 5.5 committed 2026-10-10 20:53:03 +02:00
1 parent 4afea7c802
commit 48fa028086
37 files changed
+3775 -94

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). // 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. // HISM instances are exempt — they're batched render data, capped per entry by MaxPerChunk.
static constexpr int32 GMaxDecorationActorsPerCell = 400; static constexpr int32 GMaxDecorationActorsPerCell = 400;
@@ -174,6 +278,7 @@ void UVoxelContentManager::Initialize(AActor* InOwner, UVoxelStrateManager* InSt
Settings = InSettings; Settings = InSettings;
Seed = InSeed; Seed = InSeed;
NativeSurfaceSnapshot.Reset(); NativeSurfaceSnapshot.Reset();
NativeFloatingIslandSnapshot.Reset();
if (Settings) if (Settings)
{ {
@@ -196,6 +301,33 @@ void UVoxelContentManager::Initialize(AActor* InOwner, UVoxelStrateManager* InSt
Snapshot->Plans.Add(Plan.strateIndex, Plan); Snapshot->Plans.Add(Plan.strateIndex, Plan);
} }
NativeSurfaceSnapshot = Snapshot; 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 else
{ {
@@ -244,6 +376,66 @@ bool UVoxelContentManager::QueryNativeSurfaceAnchors(
return true; 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() void UVoxelContentManager::BeginDestroy()
{ {
// Backstop: stop any worker from touching us. EndPlay → NotifyShutdown should have already drained. // 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.bSurfaceWorld
&& CurrentCtx.NativeSurface.IsValid() && CurrentCtx.NativeSurface.IsValid()
&& CurrentCtx.NativeSurface->FindPlan(StrateIndex) != nullptr; && 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 bHasNativePlan = false;
bool bNativeWaterEnabled = false; bool bNativeWaterEnabled = false;
@@ -710,7 +907,17 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
FChunkBiomeCache BiomeCache; FChunkBiomeCache BiomeCache;
const bool bHasBiomes = Ctx.BiomeCtx.IsValid(); 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) auto GetSurface = [&](float VX, float VY, float& OutGround, float& OutCap)
{ {
if (Ctx.bNativeSurface && Ctx.NativeSurface.IsValid()) if (Ctx.bNativeSurface && Ctx.NativeSurface.IsValid())
@@ -1338,6 +1545,21 @@ bool UVoxelContentManager::FindLandmarkColumn(const UVoxelGenerator* Gen, const
{ {
if (!Gen) return false; 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) if (Ctx.bSurfaceWorld)
{ {
float hC, cC; float hC, cC;
@@ -1368,7 +1590,7 @@ bool UVoxelContentManager::FindLandmarkColumn(const UVoxelGenerator* Gen, const
} }
// Floor / Wall / Any → the terrain top. // Floor / Wall / Any → the terrain top.
if (!(hC >= Ctx.BottomVoxelZ && hC <= Ctx.TopVoxelZ)) return false; 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; float d, hXp, hXm, hYp, hYm;
GetSurface(VX + 1.0f, VY, hXp, d); GetSurface(VX + 1.0f, VY, hXp, d);
GetSurface(VX - 1.0f, VY, hXm, 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): // 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 — // 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. // 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); bool bSeenAir = (PrevD >= 0.0f);
float SolidRun = 0.0f; float SolidRun = 0.0f;
for (float Z = Ctx.TopVoxelZ - Step; Z >= Ctx.BottomVoxelZ; Z -= Step) 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 if ((PrevD >= 0.0f) != (Dz >= 0.0f)) // air ↔ solid crossing
{ {
float ZLo = Z, ZHi = Z + Step, DHi = PrevD, DLo = Dz; float ZLo = Z, ZHi = Z + Step, DHi = PrevD, DLo = Dz;
for (int32 It = 0; It < 4; ++It) for (int32 It = 0; It < 4; ++It)
{ {
const float ZM = 0.5f * (ZLo + ZHi); 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; } if ((DM >= 0.0f) == (DHi >= 0.0f)) { ZHi = ZM; DHi = DM; }
else { ZLo = ZM; DLo = 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 float ZC = ZLo + (ZHi - ZLo) * T;
const FVector LocalGrad( const FVector LocalGrad(
Gen->GetDensityAt(VX + 1.0f, VY, ZC) - Gen->GetDensityAt(VX - 1.0f, VY, ZC), Density(VX + 1.0f, VY, ZC) - Density(VX - 1.0f, VY, ZC),
Gen->GetDensityAt(VX, VY + 1.0f, ZC) - Gen->GetDensityAt(VX, VY - 1.0f, ZC), Density(VX, VY + 1.0f, ZC) - Density(VX, VY - 1.0f, ZC),
Gen->GetDensityAt(VX, VY, ZC + 1.0f) - Gen->GetDensityAt(VX, VY, ZC - 1.0f)); Density(VX, VY, ZC + 1.0f) - Density(VX, VY, ZC - 1.0f));
FVector N = OwnerXf.TransformVectorNoScale(LocalGrad).GetSafeNormal(); FVector N = OwnerXf.TransformVectorNoScale(LocalGrad).GetSafeNormal();
if (N.IsNearlyZero()) N = FVector::UpVector; if (N.IsNearlyZero()) N = FVector::UpVector;
@@ -18,6 +18,7 @@ namespace
{ {
using FCoreJsonWriter = TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>; using FCoreJsonWriter = TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>;
constexpr double CoreVoxelSizeCm = 25.0; constexpr double CoreVoxelSizeCm = 25.0;
constexpr double OwnerVoxelSizeCm = 12.5;
TSharedPtr<FJsonValue> SerializeCoreStruct( TSharedPtr<FJsonValue> SerializeCoreStruct(
const UStruct* Type, const void* Data, TSet<const UObject*>& ActiveObjects, const UStruct* Type, const void* Data, TSet<const UObject*>& ActiveObjects,
@@ -309,20 +310,32 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
} }
int32 SurfaceSlotCount = 0; int32 SurfaceSlotCount = 0;
int32 FloatingIslandSlotCount = 0;
for (const FStrateSlot& Slot : StrateManager->GetLayout()) for (const FStrateSlot& Slot : StrateManager->GetLayout())
{ {
if (Slot.Definition != nullptr if (Slot.Definition == nullptr)
&& Slot.Definition->GeneratorType == ECaveGeneratorType::SurfaceWorld) {
continue;
}
const ECaveGeneratorType Archetype = StrateManager->GetGeneratorTypeForChunk(
FIntVector(0, 0, Slot.TopChunkZ));
if (Archetype == ECaveGeneratorType::SurfaceWorld)
{ {
++SurfaceSlotCount; ++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( OutError = FString::Printf(
TEXT("Core world assembly has %d plans for %d resolved SurfaceWorld strates."), 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()), SurfaceSlotCount); static_cast<int32>(Manifest->plan.strates.size()),
static_cast<int32>(Manifest->floatingIslands.size()),
SurfaceSlotCount, FloatingIslandSlotCount);
return false; return false;
} }
@@ -332,7 +345,8 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
const FStrateSlot* MatchingSlot = StrateManager->GetLayout().FindByPredicate( const FStrateSlot* MatchingSlot = StrateManager->GetLayout().FindByPredicate(
[&Plan](const FStrateSlot& Slot) { return Slot.StrateIndex == Plan.strateIndex; }); [&Plan](const FStrateSlot& Slot) { return Slot.StrateIndex == Plan.strateIndex; });
if (MatchingSlot == nullptr || MatchingSlot->Definition == nullptr 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->TopChunkZ != Plan.topChunkZ
|| MatchingSlot->BottomChunkZ != Plan.bottomChunkZ || MatchingSlot->BottomChunkZ != Plan.bottomChunkZ
|| MatchedStrateIndices.Contains(Plan.strateIndex)) || MatchedStrateIndices.Contains(Plan.strateIndex))
@@ -345,6 +359,25 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
MatchedStrateIndices.Add(Plan.strateIndex); 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(); const std::string CanonicalJson = Manifest->ToJson();
if (CanonicalJson.empty()) if (CanonicalJson.empty())
{ {
@@ -403,6 +436,9 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
Root->SetNumberField(TEXT("schema_version"), 1); Root->SetNumberField(TEXT("schema_version"), 1);
Root->SetStringField(TEXT("source"), TEXT("VoxelForge Runtime Core snapshot")); Root->SetStringField(TEXT("source"), TEXT("VoxelForge Runtime Core snapshot"));
Root->SetNumberField(TEXT("voxel_size_cm"), CoreVoxelSizeCm); 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->SetNumberField(TEXT("seed"), Seed);
Root->SetBoolField(TEXT("open_surface_entry"), Settings->bOpenSurfaceEntry); Root->SetBoolField(TEXT("open_surface_entry"), Settings->bOpenSurfaceEntry);
Root->SetNumberField(TEXT("origin_spine_radius"), OriginSpineRadius); Root->SetNumberField(TEXT("origin_spine_radius"), OriginSpineRadius);
+4 -2
View File
@@ -96,8 +96,10 @@ bool UVoxelSettings::SetWorldAssemblyManifestJson(const FString& InJson, FString
WorldAssemblyManifestHash = FString::Printf(TEXT("%016llx"), WorldAssemblyManifestHash = FString::Printf(TEXT("%016llx"),
static_cast<unsigned long long>(Manifest->contentHash)); static_cast<unsigned long long>(Manifest->contentHash));
MarkPackageDirty(); MarkPackageDirty();
OutReport = FString::Printf(TEXT("imported native world assembly %s (%d stratum plans)"), OutReport = FString::Printf(
*WorldAssemblyManifestHash, static_cast<int32>(Manifest->plan.strates.size())); 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; return true;
} }
+5 -4
View File
@@ -7264,11 +7264,12 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld()
if (const VoxelForgeCore::WorldAssemblyManifest* Assembly = Snapshot->worldAssembly()) if (const VoxelForgeCore::WorldAssemblyManifest* Assembly = Snapshot->worldAssembly())
{ {
UE_LOG(LogTemp, Display, 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->contentHash),
static_cast<unsigned long long>(Assembly->recipe.contentHash), static_cast<unsigned long long>(Assembly->recipe.contentHash),
static_cast<unsigned long long>(Assembly->plan.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 = TSharedPtr<FVoxelForgeCoreRuntimeState, ESPMode::ThreadSafe> State =
@@ -13285,8 +13286,8 @@ int32 AVoxelWorld::GetCurrentSeason() const
FString AVoxelWorld::GetCurrentSeasonContentHash() const FString AVoxelWorld::GetCurrentSeasonContentHash() const
{ {
const FString SeasonHash = StrateManager ? StrateManager->GetSeasonContentHash() : FString(); const FString SeasonHash = StrateManager ? StrateManager->GetSeasonContentHash() : FString();
// Only active Core generation includes the optional native surface manifest in world identity. // Only active Core generation includes the optional native world assembly in world identity.
// Seul le Core actif inclut le manifeste de surface natif dans l'identite du monde. // Seul le Core actif inclut l'assemblage natif facultatif dans l'identite du monde.
if (!Settings || !bUseVoxelForgeCore) if (!Settings || !bUseVoxelForgeCore)
{ {
return SeasonHash; return SeasonHash;
@@ -34,6 +34,9 @@ public:
* dominant-biome change. */ * dominant-biome change. */
void UpdateForPlayer(const FVector& PlayerWorldPos); 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). */ /** Tear down spawned layer actors + reset (season reset / shutdown). */
void Reset(); void Reset();
@@ -43,6 +43,8 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "Containers/Queue.h" #include "Containers/Queue.h"
struct FCoreFloatingIslandSnapshot;
#include "VoxelTypes.h" #include "VoxelTypes.h"
#include "VoxelStrateTypes.h" // FStrateDecoration (resolved per dominant biome) #include "VoxelStrateTypes.h" // FStrateDecoration (resolved per dominant biome)
#include "VoxelBiomeTypes.h" // FBiomeContext (per-column biome resolve on the worker) #include "VoxelBiomeTypes.h" // FBiomeContext (per-column biome resolve on the worker)
@@ -103,6 +105,16 @@ public:
float& OutGroundLocalZCm, bool& bOutHasWater, float& OutGroundLocalZCm, bool& bOutHasWater,
float& OutWaterLocalZCm) const; 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) ---------------- //--- DECORATIONS (distance-based world grid, async march) ----------------
/** Stream decoration cells around the player: recompute the desired set on cell/strate change, /** 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. */ * 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 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. 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; 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 // 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 // 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. // 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. // Instantane immuable du manifeste Core pour les workers de decoration; aucun worker ne touche aux UObjects.
TSharedPtr<const FCoreSurfaceSnapshot, ESPMode::ThreadSafe> NativeSurfaceSnapshot; TSharedPtr<const FCoreSurfaceSnapshot, ESPMode::ThreadSafe> NativeSurfaceSnapshot;
TSharedPtr<const FCoreFloatingIslandSnapshot, ESPMode::ThreadSafe> NativeFloatingIslandSnapshot;
int32 Seed = 0; int32 Seed = 0;
+4 -4
View File
@@ -373,10 +373,10 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Voxel|Strates") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Voxel|Strates")
TSoftObjectPtr<UVoxelSeasonAsset> Season; TSoftObjectPtr<UVoxelSeasonAsset> Season;
// Optional reviewed Core surface plan. The canonical JSON stays in this cooked settings asset; // Optional reviewed Core SurfaceWorld and FloatingIslands plans. Canonical JSON stays in this
// its Core FNV digest is included in GetCurrentSeasonContentHash when present. // cooked settings asset; its Core FNV digest enters the season hash only while Core is enabled.
// Plan de surface Core facultatif; le JSON canonique est cuit avec les reglages et son hash // Plans Core SurfaceWorld et FloatingIslands facultatifs. Le JSON canonique est cuit ici ;
// Core est inclus dans GetCurrentSeasonContentHash lorsqu'il est present. // son hash FNV Core entre dans le hash de saison uniquement lorsque Core est actif.
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Voxel|Strates", meta = (MultiLine = true)) UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Voxel|Strates", meta = (MultiLine = true))
FString WorldAssemblyManifestJson; FString WorldAssemblyManifestJson;
+4 -2
View File
@@ -1741,7 +1741,8 @@ struct VOXELFORGE_API FFloatingIslandParams
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0", ClampMax = "1.0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0", ClampMax = "1.0"))
float IslandDensity = 0.5f; 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")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "2.0"))
float IslandMinRadius = 24.0f; float IslandMinRadius = 24.0f;
@@ -1765,7 +1766,8 @@ struct VOXELFORGE_API FFloatingIslandParams
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0"))
float SurfaceRoughness = 3.0f; 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")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0"))
float SDFBlendRadius = 6.0f; float SDFBlendRadius = 6.0f;
File diff suppressed because it is too large. Load diff
@@ -278,23 +278,6 @@ namespace VoxelForgeCore
std::uint64_t contentHash = 0; std::uint64_t contentHash = 0;
}; };
struct VFCORE_API WorldAssemblyManifest
{
std::uint32_t schemaVersion = WorldAssemblySchemaVersion;
WorldAssemblyRecipeV1 recipe;
WorldAssemblyPlanV1 plan;
std::uint64_t contentHash = 0;
// JSON is the reviewed season-manifest payload. Hashes use canonical ordered fields,
// little-endian integers and FNV-1a 64; textual key order and whitespace do not matter.
// Le JSON est le payload de saison valide; les entiers little-endian sont haches en FNV-1a 64, sans dependance a la mise en forme.
static std::optional<WorldAssemblyManifest> FromJson(
std::string_view json, std::string& error);
bool finalize(std::string& error);
bool validate(std::string& error) const;
std::string ToJson() const;
};
// Reusable finite 3D island features. The recipe owns fixed-scale shape ranges; the plan // Reusable finite 3D island features. The recipe owns fixed-scale shape ranges; the plan
// serializes the seeded anchors, connected cluster graph, route and sharp-ready connectors. // serializes the seeded anchors, connected cluster graph, route and sharp-ready connectors.
// Reutilisable dans toute recette : le plan porte les ancres, le graphe, la route et les plans nets. // Reutilisable dans toute recette : le plan porte les ancres, le graphe, la route et les plans nets.
@@ -305,6 +288,12 @@ namespace VoxelForgeCore
// bounded lobed roots and arched spans without changing the feature-family schema. // bounded lobed roots and arched spans without changing the feature-family schema.
std::uint32_t geometryVersion = 2; std::uint32_t geometryVersion = 2;
std::uint32_t unitsPerVoxel = static_cast<std::uint32_t>(WorldAssemblySubvoxelsPerVoxel); std::uint32_t unitsPerVoxel = static_cast<std::uint32_t>(WorldAssemblySubvoxelsPerVoxel);
// Import scale is explicit: legacy owner voxels are 12.5 cm and Core voxels are 25 cm.
// L'echelle d'import est explicite : voxel owner de 12,5 cm et voxel Core de 25 cm.
std::uint32_t ownerVoxelSizeMicrons = 125000;
std::uint32_t coreVoxelSizeMicrons = 250000;
std::uint32_t ownerToCoreScaleNumerator = 1;
std::uint32_t ownerToCoreScaleDenominator = 2;
std::uint32_t seed = 0; std::uint32_t seed = 0;
std::int64_t layoutOriginXSubvoxels = 0; std::int64_t layoutOriginXSubvoxels = 0;
std::int64_t layoutOriginYSubvoxels = 0; std::int64_t layoutOriginYSubvoxels = 0;
@@ -327,9 +316,9 @@ namespace VoxelForgeCore
std::int64_t rootDepthRatioQ16 = 49152; // 0.75 radius, profile 2 only std::int64_t rootDepthRatioQ16 = 49152; // 0.75 radius, profile 2 only
std::int64_t blendRadiusSubvoxels = 3 * WorldAssemblySubvoxelsPerVoxel; std::int64_t blendRadiusSubvoxels = 3 * WorldAssemblySubvoxelsPerVoxel;
std::int64_t contourWarpSubvoxels = 6 * WorldAssemblySubvoxelsPerVoxel + 77; // 6.3 std::int64_t contourWarpSubvoxels = 6 * WorldAssemblySubvoxelsPerVoxel + 77; // 6.3
std::int64_t roughnessSubvoxels = 3 * WorldAssemblySubvoxelsPerVoxel / 2; std::int64_t roughnessSubvoxels = WorldAssemblySubvoxelsPerVoxel;
std::int64_t boundarySealSubvoxels = 2 * WorldAssemblySubvoxelsPerVoxel; std::int64_t boundarySealSubvoxels = 2 * WorldAssemblySubvoxelsPerVoxel;
std::int64_t bridgeHalfWidthSubvoxels = 6 * WorldAssemblySubvoxelsPerVoxel; std::int64_t bridgeHalfWidthSubvoxels = 3 * WorldAssemblySubvoxelsPerVoxel;
std::int64_t bridgeThicknessSubvoxels = 4 * WorldAssemblySubvoxelsPerVoxel; std::int64_t bridgeThicknessSubvoxels = 4 * WorldAssemblySubvoxelsPerVoxel;
WorldAssemblyRouteConstraintsV1 routeConstraints; WorldAssemblyRouteConstraintsV1 routeConstraints;
std::uint64_t contentHash = 0; std::uint64_t contentHash = 0;
@@ -384,6 +373,32 @@ namespace VoxelForgeCore
std::string ToJson() const; std::string ToJson() const;
}; };
struct WorldAssemblyFloatingIslandPlanV1
{
std::int32_t strateIndex = -1;
std::int32_t topChunkZ = 0;
std::int32_t bottomChunkZ = 0;
FloatingIslandManifestV1 manifest;
};
struct VFCORE_API WorldAssemblyManifest
{
std::uint32_t schemaVersion = WorldAssemblySchemaVersion;
WorldAssemblyRecipeV1 recipe;
WorldAssemblyPlanV1 plan;
std::vector<WorldAssemblyFloatingIslandPlanV1> floatingIslands;
std::uint64_t contentHash = 0;
// JSON is the reviewed season-manifest payload. Hashes use canonical ordered fields,
// little-endian integers and FNV-1a 64; textual key order and whitespace do not matter.
// Le JSON est le payload de saison valide; les entiers little-endian sont haches en FNV-1a 64, sans dependance a la mise en forme.
static std::optional<WorldAssemblyManifest> FromJson(
std::string_view json, std::string& error);
bool finalize(std::string& error);
bool validate(std::string& error) const;
std::string ToJson() const;
};
struct FloatingIslandBoundsAuditResult struct FloatingIslandBoundsAuditResult
{ {
std::uint64_t boxesTested = 0; std::uint64_t boxesTested = 0;
+213 -22
View File
@@ -158,6 +158,10 @@ namespace floating_island_detail
std::uint64_t hash = StartHash(); std::uint64_t hash = StartHash();
HashWord(hash, recipe.schemaVersion, 4); HashWord(hash, recipe.schemaVersion, 4);
HashWord(hash, recipe.unitsPerVoxel, 4); HashWord(hash, recipe.unitsPerVoxel, 4);
HashWord(hash, recipe.ownerVoxelSizeMicrons, 4);
HashWord(hash, recipe.coreVoxelSizeMicrons, 4);
HashWord(hash, recipe.ownerToCoreScaleNumerator, 4);
HashWord(hash, recipe.ownerToCoreScaleDenominator, 4);
HashWord(hash, recipe.seed, 4); HashWord(hash, recipe.seed, 4);
HashWord(hash, static_cast<std::uint64_t>(recipe.layoutOriginXSubvoxels), 8); HashWord(hash, static_cast<std::uint64_t>(recipe.layoutOriginXSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.layoutOriginYSubvoxels), 8); HashWord(hash, static_cast<std::uint64_t>(recipe.layoutOriginYSubvoxels), 8);
@@ -258,6 +262,10 @@ namespace floating_island_detail
|| (recipe.geometryVersion == 1 || (recipe.geometryVersion == 1
&& (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0)) && (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0))
|| recipe.unitsPerVoxel != Q || recipe.unitsPerVoxel != Q
|| recipe.ownerVoxelSizeMicrons != 125000
|| recipe.coreVoxelSizeMicrons != 250000
|| recipe.ownerToCoreScaleNumerator != 1
|| recipe.ownerToCoreScaleDenominator != 2
|| recipe.primaryIslandCount < 3 || recipe.primaryIslandCount > 9 || recipe.primaryIslandCount < 3 || recipe.primaryIslandCount > 9
|| recipe.layoutMaximumZSubvoxels <= recipe.layoutMinimumZSubvoxels || recipe.layoutMaximumZSubvoxels <= recipe.layoutMinimumZSubvoxels
|| recipe.layoutMaximumZSubvoxels - recipe.layoutMinimumZSubvoxels > 2048 * Q || recipe.layoutMaximumZSubvoxels - recipe.layoutMinimumZSubvoxels > 2048 * Q
@@ -339,6 +347,30 @@ namespace floating_island_detail
return static_cast<double>(recipe.contourWarpSubvoxels) * (0.50 * a + 0.30 * b + 0.20 * c); return static_cast<double>(recipe.contourWarpSubvoxels) * (0.50 * a + 0.30 * b + 0.20 * c);
} }
inline double LobePhase(const FloatingIslandRecipeV1& recipe,
const FloatingIslandAnchorV1& island)
{
return static_cast<double>(Roll(recipe.seed, island.stableId, 0x4c4f4245ull)
& 0xffffffffull) * (2.0 * Pi / 4294967296.0);
}
inline double LobeOffsetWithPhase(const FloatingIslandRecipeV1& recipe,
double angle,double zSubvoxels,double phase)
{
const double z = zSubvoxels / static_cast<double>(Q);
const double a = std::sin(3.0 * angle + phase + z * 0.017);
const double b = std::sin(5.0 * angle - phase * 0.73 - z * 0.023);
const double c = std::sin(8.0 * angle + phase * 1.41 + z * 0.011);
return static_cast<double>(recipe.contourWarpSubvoxels) * (0.50 * a + 0.30 * b + 0.20 * c);
}
inline double LobeOffsetAtAngle(const FloatingIslandRecipeV1& recipe,
const FloatingIslandAnchorV1& island,
double angle,double zSubvoxels)
{
return LobeOffsetWithPhase(recipe,angle,zSubvoxels,LobePhase(recipe,island));
}
inline double Roughness(const FloatingIslandRecipeV1& recipe, inline double Roughness(const FloatingIslandRecipeV1& recipe,
std::uint64_t featureId, double x, double y, double z) std::uint64_t featureId, double x, double y, double z)
{ {
@@ -382,6 +414,83 @@ namespace floating_island_detail
return static_cast<double>(island.topZSubvoxels) - drop; return static_cast<double>(island.topZSubvoxels) - drop;
} }
struct FIslandColumnCacheEntry
{
bool bValid=false;
std::uint32_t Seed=0;
std::uint64_t StableId=0;
std::int64_t SampleX=0,SampleY=0,CenterX=0,CenterY=0,TopZ=0,Radius=0,TopHalf=0;
std::int64_t TopFlattenQ16=0,ContourWarp=0;
double DeltaX=0.0,DeltaY=0.0,Radial=0.0,Angle=0.0,LobePhase=0.0,TopSurface=0.0;
};
inline const FIslandColumnCacheEntry& IslandColumnCache(
const FloatingIslandRecipeV1& recipe,const FloatingIslandAnchorV1& island,
double x,double y)
{
// Density samples arrive on integer Core lattice columns; reuse the exact column metrics
// across Z samples without changing the field value or touching shared world state.
// Les echantillons suivent les colonnes Core entieres; reutiliser les memes metriques XY.
constexpr std::size_t Ways=4;
constexpr std::size_t SetCount=2048;
static_assert((SetCount&(SetCount-1))==0);
static thread_local std::array<FIslandColumnCacheEntry,SetCount*Ways> Cache{};
const std::int64_t SampleX=static_cast<std::int64_t>(x);
const std::int64_t SampleY=static_cast<std::int64_t>(y);
std::uint64_t IndexKey=island.stableId
^(static_cast<std::uint64_t>(SampleX)*0x9e3779b97f4a7c15ull)
^(static_cast<std::uint64_t>(SampleY)*0xc2b2ae3d27d4eb4full)
^(static_cast<std::uint64_t>(recipe.seed)<<23);
IndexKey^=IndexKey>>33;
IndexKey*=0xff51afd7ed558ccdull;
IndexKey^=IndexKey>>33;
const std::size_t Base=static_cast<std::size_t>(IndexKey&(SetCount-1))*Ways;
FIslandColumnCacheEntry* Replacement=&Cache[Base+((IndexKey>>11)&(Ways-1))];
for(std::size_t Way=0;Way<Ways;++Way)
{
FIslandColumnCacheEntry& Entry=Cache[Base+Way];
const bool bKeyMatches=Entry.bValid&&Entry.Seed==recipe.seed
&&Entry.StableId==island.stableId
&&Entry.SampleX==SampleX&&Entry.SampleY==SampleY
&&Entry.CenterX==island.centerSubvoxels[0]
&&Entry.CenterY==island.centerSubvoxels[1]
&&Entry.TopZ==island.topZSubvoxels
&&Entry.Radius==island.radiusSubvoxels
&&Entry.TopHalf==island.topHalfSubvoxels
&&Entry.TopFlattenQ16==recipe.topFlattenQ16
&&Entry.ContourWarp==recipe.contourWarpSubvoxels;
if(bKeyMatches)return Entry;
if(!Entry.bValid)Replacement=&Entry;
}
FIslandColumnCacheEntry& Entry=*Replacement;
Entry.bValid=true;
Entry.Seed=recipe.seed;
Entry.StableId=island.stableId;
Entry.SampleX=SampleX;
Entry.SampleY=SampleY;
Entry.CenterX=island.centerSubvoxels[0];
Entry.CenterY=island.centerSubvoxels[1];
Entry.TopZ=island.topZSubvoxels;
Entry.Radius=island.radiusSubvoxels;
Entry.TopHalf=island.topHalfSubvoxels;
Entry.TopFlattenQ16=recipe.topFlattenQ16;
Entry.ContourWarp=recipe.contourWarpSubvoxels;
Entry.DeltaX=x-static_cast<double>(island.centerSubvoxels[0]);
Entry.DeltaY=y-static_cast<double>(island.centerSubvoxels[1]);
Entry.Radial=std::hypot(Entry.DeltaX,Entry.DeltaY);
Entry.Angle=std::atan2(Entry.DeltaY,Entry.DeltaX);
Entry.LobePhase=LobePhase(recipe,island);
const double EffectiveRadius=std::max(1.0,static_cast<double>(island.radiusSubvoxels)
+LobeOffsetWithPhase(recipe,Entry.Angle,static_cast<double>(island.topZSubvoxels),
Entry.LobePhase));
const double Edge=std::clamp(Entry.Radial/EffectiveRadius,0.0,1.0);
const double Drop=(1.0-Q16ToDouble(recipe.topFlattenQ16))
*static_cast<double>(island.topHalfSubvoxels)*2.0*Edge*Edge;
Entry.TopSurface=static_cast<double>(island.topZSubvoxels)-Drop;
return Entry;
}
inline std::int64_t ScaledQ16(std::int64_t value, std::int64_t scaleQ16) inline std::int64_t ScaledQ16(std::int64_t value, std::int64_t scaleQ16)
{ return (value * scaleQ16 + Q16 / 2) / Q16; } { return (value * scaleQ16 + Q16 / 2) / Q16; }
@@ -406,16 +515,58 @@ namespace floating_island_detail
inline std::int32_t RootCount(const FloatingIslandRecipeV1& recipe, inline std::int32_t RootCount(const FloatingIslandRecipeV1& recipe,
const FloatingIslandAnchorV1& island) const FloatingIslandAnchorV1& island)
{ {
return 3 + static_cast<std::int32_t>(Roll(recipe.seed, island.stableId, // Keep four separate side lobes and a full-depth central buttress to close the measured
0x524f4f54434e54ull) & 1ull); // underside cavity without driving the outer roots to one point.
// Garder quatre lobes lateraux et un renfort central profond pour fermer la cavite mesuree.
return 5;
} }
inline double UnitHash(std::uint64_t hash) inline double UnitHash(std::uint64_t hash)
{ return static_cast<double>(hash & 0xffffull) / 65535.0; } { return static_cast<double>(hash & 0xffffull) / 65535.0; }
struct FRootLobeParameters
{
double RootX=0.0,RootY=0.0,CenterZ=0.0;
double RadiusXSquared=1.0,RadiusYSquared=1.0,VerticalRadiusSquared=1.0;
double MinimumRadius=1.0;
};
struct FRootLobeCacheEntry
{
bool bValid=false;
std::uint32_t Seed=0;
std::uint64_t StableId=0;
std::int32_t RootIndex=0,GeometryVersion=0;
std::array<std::int64_t,3> Center{};
std::int64_t TopZ=0,Radius=0,TaperEndQ16=0;
std::int64_t ThicknessRatioQ16=0,CrustDepthBonusQ16=0,RootDepthRatioQ16=0;
FRootLobeParameters Parameters{};
};
inline double RootLobeDensity(const FloatingIslandRecipeV1& recipe, inline double RootLobeDensity(const FloatingIslandRecipeV1& recipe,
const FloatingIslandAnchorV1& island, const FloatingIslandAnchorV1& island,
std::int32_t rootIndex, double x, double y, double z) std::int32_t rootIndex, double x, double y, double z)
{
// Cache derived root constants per worker. The complete immutable input key keeps this
// value-only cache safe across worlds, epochs and worker schedules.
// Mettre en cache les constantes par worker; la cle complete evite tout partage d'etat.
constexpr std::size_t CacheSize=1024;
static_assert((CacheSize&(CacheSize-1))==0);
static thread_local std::array<FRootLobeCacheEntry,CacheSize> Cache{};
const std::uint64_t IndexKey=island.stableId^(island.stableId>>33)
^(static_cast<std::uint64_t>(recipe.seed)<<17)
^(static_cast<std::uint64_t>(static_cast<std::uint32_t>(rootIndex))*0x9e3779b97f4a7c15ull);
FRootLobeCacheEntry& Entry=Cache[static_cast<std::size_t>(IndexKey)&(CacheSize-1)];
const bool bKeyMatches=Entry.bValid&&Entry.Seed==recipe.seed
&&Entry.StableId==island.stableId&&Entry.RootIndex==rootIndex
&&Entry.GeometryVersion==recipe.geometryVersion
&&Entry.Center==island.centerSubvoxels
&&Entry.TopZ==island.topZSubvoxels&&Entry.Radius==island.radiusSubvoxels
&&Entry.TaperEndQ16==island.taperEndQ16
&&Entry.ThicknessRatioQ16==recipe.thicknessRatioQ16
&&Entry.CrustDepthBonusQ16==recipe.crustDepthBonusQ16
&&Entry.RootDepthRatioQ16==recipe.rootDepthRatioQ16;
if(!bKeyMatches)
{ {
constexpr std::array<std::array<double, 2>, 4> pattern{{ constexpr std::array<std::array<double, 2>, 4> pattern{{
{{-0.92, -0.12}}, {{0.28, 0.98}}, {{0.83, -0.42}}, {{-0.16, -0.94}}}}; {{-0.92, -0.12}}, {{0.28, 0.98}}, {{0.83, -0.42}}, {{-0.16, -0.94}}}};
@@ -424,24 +575,44 @@ namespace floating_island_detail
const double radius = static_cast<double>(island.radiusSubvoxels); const double radius = static_cast<double>(island.radiusSubvoxels);
const double rootDepth = radius * Q16ToDouble(recipe.rootDepthRatioQ16); const double rootDepth = radius * Q16ToDouble(recipe.rootDepthRatioQ16);
const double crustBottom = static_cast<double>(IslandCrustBottom(recipe, island)); const double crustBottom = static_cast<double>(IslandCrustBottom(recipe, island));
const double orbit = radius * (0.17 + 0.07 * UnitHash(roll)); const bool centralButtress = rootIndex == 4;
const double orbit = centralButtress ? 0.0
: radius * (0.17 + 0.07 * UnitHash(roll));
const double rootX = static_cast<double>(island.centerSubvoxels[0]) const double rootX = static_cast<double>(island.centerSubvoxels[0])
+ pattern[static_cast<std::size_t>(rootIndex)][0] * orbit; + (centralButtress ? 0.0 : pattern[static_cast<std::size_t>(rootIndex)][0] * orbit);
const double rootY = static_cast<double>(island.centerSubvoxels[1]) const double rootY = static_cast<double>(island.centerSubvoxels[1])
+ pattern[static_cast<std::size_t>(rootIndex)][1] * orbit; + (centralButtress ? 0.0 : pattern[static_cast<std::size_t>(rootIndex)][1] * orbit);
const double shortRootVariation = UnitHash(Roll(recipe.seed, island.stableId, salt ^ 0x4c454eull)); const double shortRootVariation = UnitHash(Roll(recipe.seed, island.stableId, salt ^ 0x4c454eull));
const double lengthRatio = rootIndex == 0 ? 1.0 const double lengthRatio = centralButtress ? 1.0 : (rootIndex == 0 ? 1.0
: Q16ToDouble(island.taperEndQ16) * (0.82 + 0.18 * shortRootVariation); : Q16ToDouble(island.taperEndQ16) * (0.82 + 0.18 * shortRootVariation));
const double lowerDepth = rootDepth * lengthRatio; const double lowerDepth = rootDepth * lengthRatio;
const double embedDepth = rootDepth * (rootIndex == 0 ? 0.30 : 0.22); const double embedDepth = rootDepth * (centralButtress ? 0.22 : (rootIndex == 0 ? 0.30 : 0.22));
const double verticalRadius = 0.5 * (lowerDepth + embedDepth); const double verticalRadius = 0.5 * (lowerDepth + embedDepth);
const double centerZ = crustBottom + 0.5 * (embedDepth - lowerDepth); const double centerZ = crustBottom + 0.5 * (embedDepth - lowerDepth);
const double rx = radius * (0.15 + 0.055 * UnitHash(Roll(recipe.seed, island.stableId, salt ^ 0x5258ull))); const double rx = radius * (centralButtress ? 0.25
const double ry = radius * (0.15 + 0.065 * UnitHash(Roll(recipe.seed, island.stableId, salt ^ 0x5259ull))); : 0.15 + 0.055 * UnitHash(Roll(recipe.seed, island.stableId, salt ^ 0x5258ull)));
const double q = (x - rootX) * (x - rootX) / (rx * rx) const double ry = radius * (centralButtress ? 0.25
+ (y - rootY) * (y - rootY) / (ry * ry) : 0.15 + 0.065 * UnitHash(Roll(recipe.seed, island.stableId, salt ^ 0x5259ull)));
+ (z - centerZ) * (z - centerZ) / (verticalRadius * verticalRadius); Entry.bValid=true;
return (std::sqrt(std::max(q, 0.0)) - 1.0) * std::min({rx, ry, verticalRadius}); Entry.Seed=recipe.seed;
Entry.StableId=island.stableId;
Entry.RootIndex=rootIndex;
Entry.GeometryVersion=recipe.geometryVersion;
Entry.Center=island.centerSubvoxels;
Entry.TopZ=island.topZSubvoxels;
Entry.Radius=island.radiusSubvoxels;
Entry.TaperEndQ16=island.taperEndQ16;
Entry.ThicknessRatioQ16=recipe.thicknessRatioQ16;
Entry.CrustDepthBonusQ16=recipe.crustDepthBonusQ16;
Entry.RootDepthRatioQ16=recipe.rootDepthRatioQ16;
Entry.Parameters={rootX,rootY,centerZ,rx*rx,ry*ry,
verticalRadius*verticalRadius,std::min({rx,ry,verticalRadius})};
}
const FRootLobeParameters& Parameters=Entry.Parameters;
const double q = (x - Parameters.RootX) * (x - Parameters.RootX) / Parameters.RadiusXSquared
+ (y - Parameters.RootY) * (y - Parameters.RootY) / Parameters.RadiusYSquared
+ (z - Parameters.CenterZ) * (z - Parameters.CenterZ) / Parameters.VerticalRadiusSquared;
return (std::sqrt(std::max(q, 0.0)) - 1.0) * Parameters.MinimumRadius;
} }
inline double ApplySurfaceDetail(const FloatingIslandRecipeV1& recipe, inline double ApplySurfaceDetail(const FloatingIslandRecipeV1& recipe,
@@ -482,19 +653,18 @@ namespace floating_island_detail
std::max({z - top, radial - envelope, bottomZ - z}), x, y, z); std::max({z - top, radial - envelope, bottomZ - z}), x, y, z);
} }
const double dx = x - static_cast<double>(island.centerSubvoxels[0]); const FIslandColumnCacheEntry& Column=IslandColumnCache(recipe,island,x,y);
const double dy = y - static_cast<double>(island.centerSubvoxels[1]); const double dx=Column.DeltaX;
const double radial = std::hypot(dx, dy); const double dy=Column.DeltaY;
const double radial=Column.Radial;
const double topZ = static_cast<double>(island.topZSubvoxels); const double topZ = static_cast<double>(island.topZSubvoxels);
const double radius = static_cast<double>(island.radiusSubvoxels); const double radius = static_cast<double>(island.radiusSubvoxels);
const double effectiveRadius = std::max(0.0, const double effectiveRadius = std::max(0.0,
radius + LobeOffset(recipe, island, dx, dy, z)); radius + LobeOffsetWithPhase(recipe,Column.Angle,z,Column.LobePhase));
const double edge = std::clamp(radial / std::max(effectiveRadius, 1.0), 0.0, 1.0); const double edge = std::clamp(radial / std::max(effectiveRadius, 1.0), 0.0, 1.0);
const double lowerSurface = static_cast<double>(IslandCrustBottom(recipe, island)) const double lowerSurface = static_cast<double>(IslandCrustBottom(recipe, island))
+ radius * 0.16 * edge * edge; + radius * 0.16 * edge * edge;
const double top = IslandTopSurface(recipe, island, const double top = Column.TopSurface;
static_cast<double>(island.centerSubvoxels[0]) + dx,
static_cast<double>(island.centerSubvoxels[1]) + dy);
const double topDistance = z - top; const double topDistance = z - top;
const double sideDistance = radial - effectiveRadius; const double sideDistance = radial - effectiveRadius;
const double undersideDistance = lowerSurface - z; const double undersideDistance = lowerSurface - z;
@@ -774,7 +944,9 @@ namespace floating_island_detail
// Land each connector well inside the broad top so the full capsule footprint remains // Land each connector well inside the broad top so the full capsule footprint remains
// supported across the bridge-to-island transition, including bounded surface roughness. // supported across the bridge-to-island transition, including bounded surface roughness.
// Les connecteurs recouvrent le plateau pour soutenir toute la capsule au raccord. // Les connecteurs recouvrent le plateau pour soutenir toute la capsule au raccord.
const double landingFraction = recipe.geometryVersion >= 2 ? 0.38 : 0.62; // Keep the narrow span exposed to open sky; a smaller fraction buries most of it in the island tops.
// Garder la passerelle etroite visible dans le vide ; une fraction plus faible l'enfouit dans les plateaux.
const double landingFraction = recipe.geometryVersion >= 2 ? 0.52 : 0.62;
const double fromReach = static_cast<double>(from.radiusSubvoxels) * landingFraction; const double fromReach = static_cast<double>(from.radiusSubvoxels) * landingFraction;
const double toReach = static_cast<double>(to.radiusSubvoxels) * landingFraction; const double toReach = static_cast<double>(to.radiusSubvoxels) * landingFraction;
const double sx = from.centerSubvoxels[0] + ux * fromReach; const double sx = from.centerSubvoxels[0] + ux * fromReach;
@@ -912,6 +1084,10 @@ std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
|| !floating_island_detail::ReadHash(root.Get("content_hash"), manifestHash) || !floating_island_detail::ReadHash(root.Get("content_hash"), manifestHash)
|| !floating_island_detail::ReadU32(recipeValue.Get("schema_version"), manifest.recipe.schemaVersion) || !floating_island_detail::ReadU32(recipeValue.Get("schema_version"), manifest.recipe.schemaVersion)
|| !floating_island_detail::ReadU32(recipeValue.Get("units_per_voxel"), manifest.recipe.unitsPerVoxel) || !floating_island_detail::ReadU32(recipeValue.Get("units_per_voxel"), manifest.recipe.unitsPerVoxel)
|| !floating_island_detail::ReadU32(recipeValue.Get("owner_voxel_size_microns"), manifest.recipe.ownerVoxelSizeMicrons)
|| !floating_island_detail::ReadU32(recipeValue.Get("core_voxel_size_microns"), manifest.recipe.coreVoxelSizeMicrons)
|| !floating_island_detail::ReadU32(recipeValue.Get("owner_to_core_scale_numerator"), manifest.recipe.ownerToCoreScaleNumerator)
|| !floating_island_detail::ReadU32(recipeValue.Get("owner_to_core_scale_denominator"), manifest.recipe.ownerToCoreScaleDenominator)
|| !floating_island_detail::ReadU32(recipeValue.Get("seed"), manifest.recipe.seed) || !floating_island_detail::ReadU32(recipeValue.Get("seed"), manifest.recipe.seed)
|| !floating_island_detail::ReadInteger(recipeValue.Get("origin_x_q"), manifest.recipe.layoutOriginXSubvoxels) || !floating_island_detail::ReadInteger(recipeValue.Get("origin_x_q"), manifest.recipe.layoutOriginXSubvoxels)
|| !floating_island_detail::ReadInteger(recipeValue.Get("origin_y_q"), manifest.recipe.layoutOriginYSubvoxels) || !floating_island_detail::ReadInteger(recipeValue.Get("origin_y_q"), manifest.recipe.layoutOriginYSubvoxels)
@@ -1026,6 +1202,10 @@ std::string FloatingIslandManifestV1::ToJson() const
<< floating_island_detail::HashText(value.contentHash) << "\",\"recipe\":{" << floating_island_detail::HashText(value.contentHash) << "\",\"recipe\":{"
<< "\"schema_version\":" << recipe.schemaVersion << "\"schema_version\":" << recipe.schemaVersion
<< ",\"units_per_voxel\":" << recipe.unitsPerVoxel << ",\"units_per_voxel\":" << recipe.unitsPerVoxel
<< ",\"owner_voxel_size_microns\":" << recipe.ownerVoxelSizeMicrons
<< ",\"core_voxel_size_microns\":" << recipe.coreVoxelSizeMicrons
<< ",\"owner_to_core_scale_numerator\":" << recipe.ownerToCoreScaleNumerator
<< ",\"owner_to_core_scale_denominator\":" << recipe.ownerToCoreScaleDenominator
<< ",\"seed\":" << recipe.seed << ",\"seed\":" << recipe.seed
<< ",\"origin_x_q\":" << recipe.layoutOriginXSubvoxels << ",\"origin_x_q\":" << recipe.layoutOriginXSubvoxels
<< ",\"origin_y_q\":" << recipe.layoutOriginYSubvoxels << ",\"origin_y_q\":" << recipe.layoutOriginYSubvoxels
@@ -1190,6 +1370,17 @@ std::optional<FloatingIslandManifestV1> PlanFloatingIslandCluster(
static_cast<std::int64_t>(std::ceil(riseLimit(static_cast<std::uint32_t>(i), static_cast<std::int64_t>(std::ceil(riseLimit(static_cast<std::uint32_t>(i),
static_cast<std::uint32_t>(i + 1))))); static_cast<std::uint32_t>(i + 1)))));
} }
if (recipe.geometryVersion >= 2)
{
// Step the outer route islands around the middle island so the connected path still reads as separate floating masses.
// Decaler les iles externes autour de l'ile centrale pour distinguer les masses tout en gardant la route continue.
constexpr std::int64_t VerticalStagger = 3 * floating_island_detail::Q;
for (std::uint32_t i = 0; i < count; ++i)
{
if (i < middle) tops[i] -= VerticalStagger;
else if (i > middle) tops[i] += VerticalStagger;
}
}
for (std::uint32_t i = 0; i < count; ++i) for (std::uint32_t i = 0; i < count; ++i)
{ {
const std::uint64_t site = 0x100000000ull + i; const std::uint64_t site = 0x100000000ull + i;
+154 -10
View File
@@ -936,6 +936,12 @@ public:
// edge interpolation and normals to the existing mesher. // edge interpolation and normals to the existing mesher.
float DensityAt(float X,float Y,float Z) const float DensityAt(float X,float Y,float Z) const
{ {
float IslandDensity=0.0f;
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
{
++ThreadClassificationStats().NativeFloatFieldCalls;
return ApplyEditBrushes(IslandDensity,X,Y,Z);
}
float NativeDensity=0.0f; float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity)) if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
{ {
@@ -954,6 +960,13 @@ public:
float DensityAtLattice(int32 X,int32 Y,int32 Z) const float DensityAtLattice(int32 X,int32 Y,int32 Z) const
{ {
float IslandDensity=0.0f;
if(NativeFloatingIslandDensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z),IslandDensity))
{
++ThreadClassificationStats().NativeLatticeFieldCalls;
return ApplyEditBrushes(IslandDensity,X,Y,Z);
}
float NativeDensity=0.0f; float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y), if(NativeSurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z),NativeDensity)) static_cast<float>(Z),NativeDensity))
@@ -975,6 +988,12 @@ public:
float DensityAtUnclamped(float X,float Y,float Z) const float DensityAtUnclamped(float X,float Y,float Z) const
{ {
float IslandDensity=0.0f;
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
{
++ThreadClassificationStats().NativeUnclampedFieldCalls;
return ApplyEditBrushes(IslandDensity,X,Y,Z);
}
float NativeDensity=0.0f; float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity)) if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
{ {
@@ -1007,11 +1026,18 @@ public:
Count=std::clamp(Count,0,8); Count=std::clamp(Count,0,8);
if(Count==0)return; if(Count==0)return;
bool bHasLegacySurfaceSample=false; bool bHasLegacySurfaceSample=false;
bool bHasNativeIslandSample=false;
for(int32 Lane=0;Lane<Count;++Lane) for(int32 Lane=0;Lane<Count;++Lane)
bHasLegacySurfaceSample|=UsesLegacySurfaceField( {
static_cast<float>(XStart+Lane*XStep),static_cast<float>(Y), const float SampleX=static_cast<float>(XStart+Lane*XStep);
static_cast<float>(Z)); const float SampleY=static_cast<float>(Y);
if(bHasLegacySurfaceSample) const float SampleZ=static_cast<float>(Z);
bHasLegacySurfaceSample|=UsesLegacySurfaceField(SampleX,SampleY,SampleZ);
float IslandDensity=0.0f;
bHasNativeIslandSample|=NativeFloatingIslandDensityAt(
SampleX,SampleY,SampleZ,IslandDensity);
}
if(bHasLegacySurfaceSample||bHasNativeIslandSample)
{ {
for(int32 Lane=0;Lane<Count;++Lane) for(int32 Lane=0;Lane<Count;++Lane)
OutDensity[Lane]=DensityAtLattice(XStart+Lane*XStep,Y,Z); OutDensity[Lane]=DensityAtLattice(XStart+Lane*XStep,Y,Z);
@@ -1100,6 +1126,9 @@ private:
if(OutDetailDisplacement)*OutDetailDisplacement=0.0f; if(OutDetailDisplacement)*OutDetailDisplacement=0.0f;
if(OutCaveInfluence)*OutCaveInfluence=0.0f; if(OutCaveInfluence)*OutCaveInfluence=0.0f;
if(OutFloorInfluence)*OutFloorInfluence=0.0f; if(OutFloorInfluence)*OutFloorInfluence=0.0f;
float IslandDensity=0.0f;
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
return bApplyEdits?ApplyEditBrushes(IslandDensity,X,Y,Z):IslandDensity;
if(UsesLegacySurfaceField(X,Y,Z)) if(UsesLegacySurfaceField(X,Y,Z))
{ {
float NativeDensity=0.0f; float NativeDensity=0.0f;
@@ -1191,17 +1220,22 @@ public:
int32 BMin[3]={Origin.X-Pad,Origin.Y-Pad,Origin.Z-Pad}; int32 BMin[3]={Origin.X-Pad,Origin.Y-Pad,Origin.Z-Pad};
int32 BMax[3]={Origin.X+(Cells+1)*Step,Origin.Y+(Cells+1)*Step,Origin.Z+(Cells+1)*Step}; int32 BMax[3]={Origin.X+(Cells+1)*Step,Origin.Y+(Cells+1)*Step,Origin.Z+(Cells+1)*Step};
const VoxelForgeCore::WorldAssemblyStratumPlanV1* SurfacePlan=nullptr; const VoxelForgeCore::WorldAssemblyStratumPlanV1* SurfacePlan=nullptr;
bool bSurfaceOverlap=false,bOtherStrateOverlap=false,bBadPlan=false; const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1* IslandPlan=nullptr;
bool bSurfaceOverlap=false,bIslandOverlap=false,bOtherStrateOverlap=false;
bool bIslandOtherStrateOverlap=false,bBadPlan=false;
if(Owner&&Owner->Manager) if(Owner&&Owner->Manager)
{ {
// Keep the delivered SurfaceWorld chunk mapping unchanged.
// Conserver le mapping de chunks SurfaceWorld deja livre.
const int32 FirstChunk=FMath::FloorToInt(static_cast<float>(BMin[2]) const int32 FirstChunk=FMath::FloorToInt(static_cast<float>(BMin[2])
/static_cast<float>(CHUNK_SIZE)); /static_cast<float>(CHUNK_SIZE));
const int32 LastChunk=FMath::FloorToInt(static_cast<float>(BMax[2]) const int32 LastChunk=FMath::FloorToInt(static_cast<float>(BMax[2])
/static_cast<float>(CHUNK_SIZE)); /static_cast<float>(CHUNK_SIZE));
for(int32 ChunkZ=FirstChunk;ChunkZ<=LastChunk;++ChunkZ) for(int32 ChunkZ=FirstChunk;ChunkZ<=LastChunk;++ChunkZ)
{ {
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ)) const ECaveGeneratorType Type=Owner->Manager->GetGeneratorTypeForChunk(
==ECaveGeneratorType::SurfaceWorld) FIntVector(0,0,ChunkZ));
if(Type==ECaveGeneratorType::SurfaceWorld)
{ {
bSurfaceOverlap=true; bSurfaceOverlap=true;
const int32 StrateIndex=Owner->Manager->GetStrateIndex( const int32 StrateIndex=Owner->Manager->GetStrateIndex(
@@ -1211,12 +1245,39 @@ public:
bBadPlan=true; bBadPlan=true;
SurfacePlan=Candidate; SurfacePlan=Candidate;
} }
else bOtherStrateOverlap=true; else if(Type!=ECaveGeneratorType::FloatingIslands)
bOtherStrateOverlap=true;
}
// Floating islands use the explicit 12.5 cm owner to 25 cm Core mapping:
// one legacy 4 m chunk occupies 16 Core voxels.
// Les iles flottantes suivent l'echelle explicite proprietaire/Core: 16 voxels Core.
constexpr int32 CoreVoxelsPerIslandChunk=CHUNK_SIZE/2;
const int32 FirstIslandChunk=FMath::FloorToInt(static_cast<float>(BMin[2])
/static_cast<float>(CoreVoxelsPerIslandChunk));
const int32 LastIslandChunk=FMath::FloorToInt(static_cast<float>(BMax[2])
/static_cast<float>(CoreVoxelsPerIslandChunk));
for(int32 ChunkZ=FirstIslandChunk;ChunkZ<=LastIslandChunk;++ChunkZ)
{
const ECaveGeneratorType Type=Owner->Manager->GetGeneratorTypeForChunk(
FIntVector(0,0,ChunkZ));
if(Type==ECaveGeneratorType::FloatingIslands)
{
bIslandOverlap=true;
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
static_cast<float>((static_cast<double>(ChunkZ*CHUNK_SIZE+CHUNK_SIZE/2))*VOXEL_SIZE));
const auto* Candidate=FindWorldAssemblyFloatingIslandStratum(StrateIndex,ChunkZ);
if(!Candidate||(IslandPlan&&IslandPlan!=Candidate))bBadPlan=true;
IslandPlan=Candidate;
}
else bIslandOtherStrateOverlap=true;
} }
} }
const auto Finish=[&](EVoxelTileClass Verdict,bool bUnknown=false) const auto Finish=[&](EVoxelTileClass Verdict,bool bUnknown=false)
{ {
FBoxClassification Result{Verdict,bSurfaceOverlap&&!bOtherStrateOverlap,bUnknown}; const bool bNative=(bSurfaceOverlap&&!bOtherStrateOverlap)
||(bIslandOverlap&&!bIslandOtherStrateOverlap);
FBoxClassification Result{Verdict,bNative,bUnknown};
auto& Counts=ThreadClassificationStats(); auto& Counts=ThreadClassificationStats();
Counts.Milliseconds+=std::chrono::duration<double,std::milli>(Clock::now()-Start).count(); Counts.Milliseconds+=std::chrono::duration<double,std::milli>(Clock::now()-Start).count();
if(!Result.bNativeSurface||bUnknown)++Counts.UnknownBoxes; if(!Result.bNativeSurface||bUnknown)++Counts.UnknownBoxes;
@@ -1227,7 +1288,9 @@ public:
}; };
// A box crossing recipes cannot use one field interval; unknown composition stays Mixed. // A box crossing recipes cannot use one field interval; unknown composition stays Mixed.
// Une boite qui traverse plusieurs recettes reste Mixed, car l'intervalle d'un seul champ ne suffit pas. // Une boite qui traverse plusieurs recettes reste Mixed, car l'intervalle d'un seul champ ne suffit pas.
if(bBadPlan|| (bSurfaceOverlap&&bOtherStrateOverlap)) if(bBadPlan||(bSurfaceOverlap&&(bIslandOverlap||bOtherStrateOverlap))
||(bIslandOverlap&&(bIslandOtherStrateOverlap
||(!bSurfaceOverlap&&bOtherStrateOverlap))))
return Finish(EVoxelTileClass::Mixed,true); return Finish(EVoxelTileClass::Mixed,true);
if(!EditBrushes.empty()&&EditBoundsMin.X<=BMax[0]&&EditBoundsMax.X>=BMin[0] if(!EditBrushes.empty()&&EditBoundsMin.X<=BMax[0]&&EditBoundsMax.X>=BMin[0]
&&EditBoundsMin.Y<=BMax[1]&&EditBoundsMax.Y>=BMin[1] &&EditBoundsMin.Y<=BMax[1]&&EditBoundsMax.Y>=BMin[1]
@@ -1273,6 +1336,38 @@ public:
default:return Finish(EVoxelTileClass::Mixed,true); default:return Finish(EVoxelTileClass::Mixed,true);
} }
} }
if(bIslandOverlap)
{
if(!IslandPlan)return Finish(EVoxelTileClass::Mixed,true);
constexpr std::int64_t Scale=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
constexpr std::int64_t CoreVoxelsPerLegacyChunk=CHUNK_SIZE/2;
const std::int64_t Bottom=static_cast<std::int64_t>(IslandPlan->bottomChunkZ)
*CoreVoxelsPerLegacyChunk*Scale;
const std::int64_t Top=(static_cast<std::int64_t>(IslandPlan->topChunkZ)+1)
*CoreVoxelsPerLegacyChunk*Scale;
const std::int64_t Seal=IslandPlan->manifest.recipe.boundarySealSubvoxels;
const std::int64_t BoxBottom=static_cast<std::int64_t>(BMin[2])*Scale;
const std::int64_t BoxTop=static_cast<std::int64_t>(BMax[2])*Scale;
const bool bBottomSealOverlap=BoxBottom<Bottom+Seal&&BoxTop>=Bottom;
const bool bTopSealOverlap=BoxBottom<=Top&&BoxTop>Top-Seal;
if(bBottomSealOverlap)
return Finish(BoxBottom>=Bottom&&BoxTop<=Bottom+Seal
?EVoxelTileClass::AllSolid:EVoxelTileClass::Mixed);
if(bTopSealOverlap)
return Finish(BoxBottom>=Top-Seal&&BoxTop<=Top
?EVoxelTileClass::AllSolid:EVoxelTileClass::Mixed);
const VoxelForgeCore::FloatingIslandFieldV1 Block(
IslandPlan->manifest.recipe,IslandPlan->manifest.plan);
if(!Block.isValid())return Finish(EVoxelTileClass::Mixed,true);
const auto Interval=Block.interval(static_cast<int64>(BMin[0])*Scale,
static_cast<int64>(BMin[1])*Scale,static_cast<int64>(BMin[2])*Scale,
static_cast<int64>(BMax[0])*Scale,static_cast<int64>(BMax[1])*Scale,
static_cast<int64>(BMax[2])*Scale);
if(!Interval.known)return Finish(EVoxelTileClass::Mixed,true);
if(Interval.minimumDensitySubvoxels>=0)return Finish(EVoxelTileClass::AllAir);
if(Interval.maximumDensitySubvoxels<0)return Finish(EVoxelTileClass::AllSolid);
return Finish(EVoxelTileClass::Mixed);
}
const bool bRock=EvaluateFast((BMin[0]+BMax[0])*0.5, const bool bRock=EvaluateFast((BMin[0]+BMax[0])*0.5,
(BMin[1]+BMax[1])*0.5,(BMin[2]+BMax[2])*0.5); (BMin[1]+BMax[1])*0.5,(BMin[2]+BMax[2])*0.5);
return Finish(bRock?EVoxelTileClass::AllSolid:EVoxelTileClass::AllAir); return Finish(bRock?EVoxelTileClass::AllSolid:EVoxelTileClass::AllAir);
@@ -1753,6 +1848,16 @@ private:
return It==WorldAssembly->plan.strates.end()?nullptr:&*It; return It==WorldAssembly->plan.strates.end()?nullptr:&*It;
} }
const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1* FindWorldAssemblyFloatingIslandStratum(
int32 StrateIndex,int32 ChunkZ) const
{
if(!WorldAssembly||StrateIndex<0)return nullptr;
const auto It=std::find_if(WorldAssembly->floatingIslands.begin(),
WorldAssembly->floatingIslands.end(),[StrateIndex,ChunkZ](const auto& Entry)
{return Entry.strateIndex==StrateIndex&&ChunkZ<=Entry.topChunkZ&&ChunkZ>=Entry.bottomChunkZ;});
return It==WorldAssembly->floatingIslands.end()?nullptr:&*It;
}
bool NativeSurfaceDensityAt(float X,float Y,float Z,float& OutDensity) const bool NativeSurfaceDensityAt(float X,float Y,float Z,float& OutDensity) const
{ {
if(!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator if(!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator
@@ -1778,6 +1883,45 @@ private:
return true; return true;
} }
bool NativeFloatingIslandDensityAt(float X,float Y,float Z,float& OutDensity) const
{
if(!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator
||!std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z))return false;
// Convert Core query Z back to the owner's legacy chunk coordinate.
// Convertir Z du Core vers la coordonnee de chunk legacy du proprietaire.
constexpr int32 CoreVoxelsPerLegacyChunk=CHUNK_SIZE/2;
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(CoreVoxelsPerLegacyChunk));
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
!=ECaveGeneratorType::FloatingIslands)return false;
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
static_cast<float>((static_cast<double>(ChunkZ*CHUNK_SIZE+CHUNK_SIZE/2))*VOXEL_SIZE));
const auto* Plan=FindWorldAssemblyFloatingIslandStratum(StrateIndex,ChunkZ);
if(!Plan)return false;
constexpr double Scale=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
constexpr double Maximum=1000000000000.0/Scale;
if(std::abs(static_cast<double>(X))>Maximum||std::abs(static_cast<double>(Y))>Maximum
||std::abs(static_cast<double>(Z))>Maximum)return false;
const auto ToFixed=[](float Value)
{return static_cast<int64>(std::llround(static_cast<double>(Value)
*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));};
const VoxelForgeCore::FloatingIslandFieldV1 Field(
Plan->manifest.recipe,Plan->manifest.plan);
if(!Field.isValid())return false;
std::int64_t Density=Field.densitySubvoxels(ToFixed(X),ToFixed(Y),ToFixed(Z));
// A legacy 4 m chunk is 16 Core voxels at the published 12.5 cm to 25 cm scale.
// Un chunk legacy de 4 m represente 16 voxels Core avec l'echelle publiee.
const std::int64_t Bottom=static_cast<std::int64_t>(Plan->bottomChunkZ)
*CoreVoxelsPerLegacyChunk*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
const std::int64_t Top=(static_cast<std::int64_t>(Plan->topChunkZ)+1)
*CoreVoxelsPerLegacyChunk*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
const std::int64_t Seal=Plan->manifest.recipe.boundarySealSubvoxels;
const std::int64_t Zq=ToFixed(Z);
if(Zq>=Bottom&&Zq<Bottom+Seal)Density=std::min(Density,-std::max<std::int64_t>(1,Zq-Bottom));
if(Zq<=Top&&Zq>Top-Seal)Density=std::min(Density,-std::max<std::int64_t>(1,Top-Zq));
OutDensity=static_cast<float>(static_cast<double>(Density)/Scale);
return std::isfinite(OutDensity);
}
bool UsesLegacySurfaceField(float X,float Y,float Z) const bool UsesLegacySurfaceField(float X,float Y,float Z) const
{ {
if(!Owner||!Owner->Manager||!Owner->Generator)return false; if(!Owner||!Owner->Manager||!Owner->Generator)return false;
+150 -4
View File
@@ -8,6 +8,7 @@
#include <charconv> #include <charconv>
#include <chrono> #include <chrono>
#include <cmath> #include <cmath>
#include <iomanip>
#include <limits> #include <limits>
#include <sstream> #include <sstream>
@@ -403,9 +404,91 @@ namespace world_assembly_detail
HashWord(hash, manifest.schemaVersion, 4); HashWord(hash, manifest.schemaVersion, 4);
HashWord(hash, manifest.recipe.contentHash, 8); HashWord(hash, manifest.recipe.contentHash, 8);
HashWord(hash, manifest.plan.contentHash, 8); HashWord(hash, manifest.plan.contentHash, 8);
// Preserve the historical SurfaceWorld manifest digest when no island plan is attached.
if (!manifest.floatingIslands.empty())
{
HashWord(hash, 0x464c4f415449534cull, 8);
HashWord(hash, manifest.floatingIslands.size(), 4);
for (const auto& islands : manifest.floatingIslands)
{
HashWord(hash, static_cast<std::uint32_t>(islands.strateIndex), 4);
HashWord(hash, static_cast<std::uint32_t>(islands.topChunkZ), 4);
HashWord(hash, static_cast<std::uint32_t>(islands.bottomChunkZ), 4);
HashWord(hash, islands.manifest.contentHash, 8);
}
}
return hash; return hash;
} }
inline void AppendJsonString(std::ostringstream& out, std::string_view value)
{
out << '"';
for (const unsigned char ch : value)
{
switch (ch)
{
case '"': out << "\\\""; break;
case '\\': out << "\\\\"; break;
case '\b': out << "\\b"; break;
case '\f': out << "\\f"; break;
case '\n': out << "\\n"; break;
case '\r': out << "\\r"; break;
case '\t': out << "\\t"; break;
default:
if (ch < 0x20) out << "\\u00" << std::hex << std::setw(2)
<< std::setfill('0') << static_cast<unsigned int>(ch) << std::dec;
else out << static_cast<char>(ch);
break;
}
}
out << '"';
}
inline void AppendJsonValue(std::ostringstream& out, const VFJson::Value& value)
{
if (std::holds_alternative<std::nullptr_t>(value.Data)) out << "null";
else if (const auto* boolean = std::get_if<bool>(&value.Data)) out << (*boolean ? "true" : "false");
else if (const auto* number = std::get_if<double>(&value.Data))
{
char buffer[64];
const auto result = std::to_chars(buffer, buffer + sizeof(buffer), *number,
std::chars_format::general, std::numeric_limits<double>::max_digits10);
if (result.ec == std::errc{}) out.write(buffer, result.ptr - buffer);
else out << '0';
}
else if (const auto* string = std::get_if<std::string>(&value.Data)) AppendJsonString(out, *string);
else if (const auto* array = std::get_if<VFJson::Value::Array>(&value.Data))
{
out << '[';
for (std::size_t i = 0; i < array->size(); ++i)
{ if (i) out << ','; AppendJsonValue(out, (*array)[i]); }
out << ']';
}
else
{
const auto& object = std::get<VFJson::Value::Object>(value.Data);
std::vector<std::string_view> keys;
keys.reserve(object.size());
for (const auto& entry : object) keys.push_back(entry.first);
std::sort(keys.begin(), keys.end());
out << '{';
for (std::size_t i = 0; i < keys.size(); ++i)
{
if (i) out << ',';
AppendJsonString(out, keys[i]); out << ':';
AppendJsonValue(out, object.at(std::string(keys[i])));
}
out << '}';
}
}
inline std::string JsonText(const VFJson::Value& value)
{
std::ostringstream out;
AppendJsonValue(out, value);
return out.str();
}
inline bool ParseRecipe(const VFJson::Value& value, WorldAssemblyRecipeV1& recipe, inline bool ParseRecipe(const VFJson::Value& value, WorldAssemblyRecipeV1& recipe,
std::string& error) std::string& error)
{ {
@@ -566,6 +649,26 @@ namespace world_assembly_detail
if (error.empty()) error = "world assembly manifest is missing a required field"; if (error.empty()) error = "world assembly manifest is missing a required field";
return std::nullopt; return std::nullopt;
} }
const VFJson::Value& islandPlans = value.Get("floating_islands");
if (!std::holds_alternative<std::nullptr_t>(islandPlans.Data))
{
if (!islandPlans.IsArray() || islandPlans.AsArray().size() > MaxStrateCount)
{ error = "world assembly floating-island plans must be an array of at most sixty-four entries"; return std::nullopt; }
for (const VFJson::Value& islandValue : islandPlans.AsArray())
{
WorldAssemblyFloatingIslandPlanV1 entry;
if (!islandValue.IsObject()
|| !ReadI32(islandValue.Get("strate_index"), entry.strateIndex)
|| !ReadI32(islandValue.Get("top_chunk_z"), entry.topChunkZ)
|| !ReadI32(islandValue.Get("bottom_chunk_z"), entry.bottomChunkZ))
{ error = "world assembly floating-island slot is missing its resolved strate bounds"; return std::nullopt; }
const std::string nestedJson = JsonText(islandValue.Get("manifest"));
auto parsedIsland = FloatingIslandManifestV1::FromJson(nestedJson, error);
if (!parsedIsland) return std::nullopt;
entry.manifest = std::move(*parsedIsland);
manifest.floatingIslands.push_back(std::move(entry));
}
}
if (!manifest.validate(error)) return std::nullopt; if (!manifest.validate(error)) return std::nullopt;
return manifest; return manifest;
} }
@@ -1292,8 +1395,10 @@ bool WorldAssemblyManifest::finalize(std::string& error)
if (schemaVersion != WorldAssemblySchemaVersion || plan.schemaVersion != WorldAssemblySchemaVersion if (schemaVersion != WorldAssemblySchemaVersion || plan.schemaVersion != WorldAssemblySchemaVersion
|| !world_assembly_detail::ValidateRecipe(recipe, error)) || !world_assembly_detail::ValidateRecipe(recipe, error))
{ if (error.empty()) error = "world assembly manifest or plan version is unsupported"; return false; } { if (error.empty()) error = "world assembly manifest or plan version is unsupported"; return false; }
if (plan.strates.empty() || plan.strates.size() > world_assembly_detail::MaxStrateCount) if ((plan.strates.empty() && floatingIslands.empty())
{ error = "world assembly plan must contain one to sixty-four finite strates"; return false; } || plan.strates.size() > world_assembly_detail::MaxStrateCount
|| floatingIslands.size() > world_assembly_detail::MaxStrateCount)
{ error = "world assembly must contain SurfaceWorld or floating-island plans within the sixty-four-strate limit"; return false; }
std::vector<std::int32_t> strateIds; std::vector<std::int32_t> strateIds;
for (auto& stratum : plan.strates) for (auto& stratum : plan.strates)
{ {
@@ -1323,6 +1428,25 @@ bool WorldAssemblyManifest::finalize(std::string& error)
strateIds.push_back(stratum.strateIndex); strateIds.push_back(stratum.strateIndex);
stratum.contentHash = world_assembly_detail::HashStratum(stratum); stratum.contentHash = world_assembly_detail::HashStratum(stratum);
} }
std::vector<std::int32_t> islandStrateIds;
for (auto& islands : floatingIslands)
{
if (islands.strateIndex < 0 || islands.topChunkZ < islands.bottomChunkZ
|| std::find(islandStrateIds.begin(), islandStrateIds.end(), islands.strateIndex) != islandStrateIds.end())
{ error = "world assembly contains invalid or duplicate floating-island strate bounds"; return false; }
if (!islands.manifest.validate(error)) return false;
if (islands.manifest.recipe.seed != recipe.seed)
{ error = "floating-island seed does not match the world assembly season seed"; return false; }
constexpr std::int64_t CoreVoxelsPerLegacyChunk = 16;
const std::int64_t bottomQ = static_cast<std::int64_t>(islands.bottomChunkZ)
* CoreVoxelsPerLegacyChunk * WorldAssemblySubvoxelsPerVoxel;
const std::int64_t topQ = (static_cast<std::int64_t>(islands.topChunkZ) + 1)
* CoreVoxelsPerLegacyChunk * WorldAssemblySubvoxelsPerVoxel;
if (islands.manifest.recipe.layoutMinimumZSubvoxels < bottomQ
|| islands.manifest.recipe.layoutMaximumZSubvoxels > topQ)
{ error = "floating-island recipe layout escapes the resolved 12.5 cm legacy strate bounds"; return false; }
islandStrateIds.push_back(islands.strateIndex);
}
recipe.contentHash = world_assembly_detail::HashRecipe(recipe); recipe.contentHash = world_assembly_detail::HashRecipe(recipe);
plan.contentHash = world_assembly_detail::HashPlan(plan); plan.contentHash = world_assembly_detail::HashPlan(plan);
contentHash = world_assembly_detail::HashManifest(*this); contentHash = world_assembly_detail::HashManifest(*this);
@@ -1337,13 +1461,19 @@ bool WorldAssemblyManifest::validate(std::string& error) const
const std::uint64_t expectedManifest = contentHash; const std::uint64_t expectedManifest = contentHash;
std::vector<std::uint64_t> expectedStrates; std::vector<std::uint64_t> expectedStrates;
for (const auto& stratum : plan.strates) expectedStrates.push_back(stratum.contentHash); for (const auto& stratum : plan.strates) expectedStrates.push_back(stratum.contentHash);
std::vector<std::uint64_t> expectedIslandManifests;
for (const auto& islands : floatingIslands) expectedIslandManifests.push_back(islands.manifest.contentHash);
if (!copy.finalize(error)) return false; if (!copy.finalize(error)) return false;
if (expectedRecipe != copy.recipe.contentHash || expectedPlan != copy.plan.contentHash if (expectedRecipe != copy.recipe.contentHash || expectedPlan != copy.plan.contentHash
|| expectedManifest != copy.contentHash || expectedStrates.size() != copy.plan.strates.size()) || expectedManifest != copy.contentHash || expectedStrates.size() != copy.plan.strates.size()
|| expectedIslandManifests.size() != copy.floatingIslands.size())
{ error = "world assembly recipe, plan or manifest content hash does not match its fields"; return false; } { error = "world assembly recipe, plan or manifest content hash does not match its fields"; return false; }
for (std::size_t i = 0; i < expectedStrates.size(); ++i) for (std::size_t i = 0; i < expectedStrates.size(); ++i)
if (expectedStrates[i] != copy.plan.strates[i].contentHash) if (expectedStrates[i] != copy.plan.strates[i].contentHash)
{ error = "world assembly stratum content hash does not match its fields"; return false; } { error = "world assembly stratum content hash does not match its fields"; return false; }
for (std::size_t i = 0; i < expectedIslandManifests.size(); ++i)
if (expectedIslandManifests[i] != copy.floatingIslands[i].manifest.contentHash)
{ error = "world assembly floating-island content hash does not match its fields"; return false; }
return true; return true;
} }
@@ -1474,7 +1604,23 @@ std::string WorldAssemblyManifest::ToJson() const
} }
out << "]}"; out << "]}";
} }
out << "]}}"; if (value.floatingIslands.empty()) out << "]}}";
else
{
out << "]},\"floating_islands\":[";
for (std::size_t i = 0; i < value.floatingIslands.size(); ++i)
{
const auto& islands = value.floatingIslands[i];
if (i) out << ',';
const std::string nested = islands.manifest.ToJson();
if (nested.empty()) return {};
out << "{\"strate_index\":" << islands.strateIndex
<< ",\"top_chunk_z\":" << islands.topChunkZ
<< ",\"bottom_chunk_z\":" << islands.bottomChunkZ
<< ",\"manifest\":" << nested << '}';
}
out << "]}";
}
return out.str(); return out.str();
} }
+50 -1
View File
@@ -542,6 +542,55 @@ what the shape list alone misses: site selection, cluster connectivity, landings
route. The support ranges do not promise that every roll is pretty or walkable; measurement rejects route. The support ranges do not promise that every roll is pretty or walkable; measurement rejects
bad candidates and records which constraint failed. bad candidates and records which constraint failed.
#### M3 round 2 — look pass and standalone gates
The legacy settings describe a stochastic site process, not a fixed island count: `IslandDensity=0.5`
is the chance that each hashed XY cell exists. The owner settings are spacing 112 legacy voxels,
radii 24–48 legacy voxels, thickness ratio 0.6, vertical jitter/top flatten 0.6, roughness 3,
blend 6 and boundary seal 4. The production legacy field uses 12.5 cm voxels and Core uses 25 cm, so
the round-2 Core recipe halves length-valued settings: 56 spacing, 12–24 radii, 1.5 roughness, 3
blend, 2 seal and 6.3 contour warp. Those values preserve 14 m spacing and 3–6 m radii under the
production source scale. The legacy struct's radius comment instead assumes 25 cm, and the frozen
probe metadata reports 25 cm while production `Source` and the plugin `CLAUDE.md` report 12.5 cm.
The next round must reconcile that fixture metadata before publishing the importer contract.
Geometry version 2 adds a broad crust and 3–4 deterministic tapering root lobes per island. Its
measured total depth is 0.75 of island diameter; the old thin profile control measures 0.30 and is
rejected by the 0.70 thickness gate. Full bounded roughness remains on cliffs and roots, while top
detail is reduced where the route needs stable footing. The three primary islands guarantee an
entrance-to-exit route; density 0.5 selected one optional side island for this seed, giving four
islands and three sharp-tag-ready arched spans. The side island is not part of the route.
[`M3 Round 2 report`](Research/WorldAssemblerM3/Round2/REPORT.md) records the five matched views,
owner-parameter conversion, the full run ledger, and Round 3 delivery plan. The contact sheet is
[`contact_sheet.jpg`](Research/WorldAssemblerM3/Round2/results/captures/contact_sheet.jpg). Bounds
had zero misses, the route passed 254 capsule centers and 69,088 field samples, the raised-route and
thin-pad controls failed as intended, and W1/W8/W16 field hashes matched. On the same 32,768 physical
query locations, Core took 38.06 ms versus 64.64 ms for the legacy evaluator. The local sample still
contains 10.6× as many native solid points (1,380 vs 130), due to the guaranteed route backbone and
deeper root profile; this is a known local density difference, not an occupancy-parity claim.
#### M3 round 3 — Unreal delivery plan
1. Resolve the legacy/Core voxel-scale conflict using the production field constant and the owner
snapshot producer; record a single conversion rule in the imported manifest and its preview notes.
2. Add the floating-island recipe and plan to the existing optional `world_assembly` import pattern.
Parse through `ResolvedWorldSnapshot::FromJson`, validate recipe/plan hashes and stable feature
IDs, and reject malformed or scale-mismatched payloads before generation.
3. Prove the imported plan in a null-RHI BuildHost commandlet. Check the canonical hashes and W1/W8/W16
queries, then compare generated tiles against the reviewed standalone field without changing the
existing Core world or SurfaceWorld hashes.
4. Carry island and sharp-tag-ready span IDs into the feature-aware mesher path. Audit same-LOD and
LOD0/LOD1 borders around the crust, roots and arch landings.
5. Cook the rendered collision, trace it on the Pawn channel, and capsule-sweep the same serialized
route. Compare collision support points to analytic Core support at the island tops and spans.
6. Hand the owner the manifest, five-view sheet, import steps, route coordinates, and collision
results. Use a duplicated test settings asset for preview only after the owner authorizes project
asset work; keep shipped assets untouched during the standalone proof.
Water remains a separate fixed-season body and is not part of the round-2 field or the round-3
collision claim.
## E. Milestones that end in owner-visible artifacts ## E. Milestones that end in owner-visible artifacts
These estimates are engineering rounds, not elapsed-time promises. Each milestone ends with a These estimates are engineering rounds, not elapsed-time promises. Each milestone ends with a
@@ -551,7 +600,7 @@ standalone viewer/contact sheet, data report or playable inspection the owner ca
|---|---:|---|---| |---|---:|---|---|
| M1 — sharp-mesher decision spike | 2 rounds | 25 cm contact sheet of corner, one-metre step, cliff ledge and doorway: current MC beside feature-aware output; include LOD/tile seam and collision overlays | Measure surface error, normal splits, determinism and seam behavior before building architectural recipes on top | | M1 — sharp-mesher decision spike | 2 rounds | 25 cm contact sheet of corner, one-metre step, cliff ledge and doorway: current MC beside feature-aware output; include LOD/tile seam and collision overlays | Measure surface error, normal splits, determinism and seam behavior before building architectural recipes on top |
| M2 — SurfaceWorld native vertical slice | 3 rounds | Matched-camera native surface with two biomes, a coast/beach, cliff/overhang, cap, route markers and timing beside the exact legacy bridge | No missed bound; the matched full-view workload materially beats the 23.085 s bridge; local collision/query readiness is reported separately; route candidate survives final composition | | M2 — SurfaceWorld native vertical slice | 3 rounds | Matched-camera native surface with two biomes, a coast/beach, cliff/overhang, cap, route markers and timing beside the exact legacy bridge | No missed bound; the matched full-view workload materially beats the 23.085 s bridge; local collision/query readiness is reported separately; route candidate survives final composition |
| M3 — FloatingIslands volumetric proof | 2 rounds | Walkable island cluster with underside/cliff views, water/body overlay, tile/LOD seam views and bound counters | Stable field/feature IDs; no false interval proof; route support is visible and capsule-swept | | M3 — FloatingIslands volumetric proof and Unreal delivery | 3 rounds | Walkable island cluster with underside/cliff views, tile/LOD seam views, imported manifest, BuildHost proof and cooked route collision | Stable field/feature IDs; no false interval proof; route support is visible and capsule-swept |
| M4 — route, edit and water law | 2 rounds | Owner can inspect before/after edit views and traverse the tested route; report shows protect-or-replace transaction and unchanged seasonal water IDs | Final-field capsule clearance/support passes; edit either preserves route or atomically installs a replacement; water remains fixed | | M4 — route, edit and water law | 2 rounds | Owner can inspect before/after edit views and traverse the tested route; report shows protect-or-replace transaction and unchanged seasonal water IDs | Final-field capsule clearance/support passes; edit either preserves route or atomically installs a replacement; water remains fixed |
M1 goes first because the owner needs to judge crisp geometry before a slow legacy/native comparison M1 goes first because the owner needs to judge crisp geometry before a slow legacy/native comparison
@@ -1063,3 +1063,8 @@ match; the M2 mesher hash matrix was not replayed. No editor/viewer, project bui
New source file this round: `RenderFloatingIslandRound2.py`. New source file this round: `RenderFloatingIslandRound2.py`.
— Luna — Luna
## World assembler M3 round 3 — 2026-10-10
The owner's scale is settled at 12.5 cm per legacy voxel and 25 cm per Core voxel (owner-to-Core length ratio 0.5). The Round 3 shape review reads as four separated masses; sampled mid-height open sky is 91.89% of the XY bounding box. The underside center is solid, and the crust rim no longer shows the earlier sawtooth appearance in the matched offline captures.
The manifest import and Core route passed the recorded BuildHost checks, along with SurfaceWorld M1 and HybridV1/V2 M2. Island/span collision contact, anchors/height/strate, Blueprint carve/fill, Core-off legacy behavior, W1/W8/W16 determinism, and tile hash parity passed on the verified source state. The recorded LOD0 island and mixed-tile sprint p95s still exceeded the 133.333 ms deadline by about 24 ms and 20 ms. A follow-up XY-column cache was added to target meshing/density cost but could not be compiled after the Windows command runner began returning `0xC0000142`; therefore I am not calling the current tree verified. The detailed measurements, hand-off, and run ledger are in `Standalone/Research/WorldAssemblerM3/Round3/REPORT.md`.
@@ -0,0 +1,118 @@
[CmdletBinding()]
param(
[string]$ProbePath = (Join-Path $PSScriptRoot '..\..\OwnerShapeWorld\core_strate_coverage\probes\type_6_FloatingIslands.json'),
[string]$OutputDirectory = (Join-Path $PSScriptRoot 'results')
)
$ErrorActionPreference = 'Stop'
$ScriptProcess = Get-Process -Id $PID
$ScriptProcess.PriorityClass = 'BelowNormal'
$StandaloneRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
$BuildRoot = Join-Path $StandaloneRoot 'Build\VoxelForgeCore'
$CoreLibrary = Join-Path $BuildRoot 'VoxelForgeCore.lib'
$AuditSource = Join-Path $PSScriptRoot 'FloatingIslandsAudit.cpp'
$AuditExe = Join-Path $BuildRoot 'FloatingIslandsAudit.exe'
$ProbePath = [IO.Path]::GetFullPath($ProbePath)
$OutputDirectory = [IO.Path]::GetFullPath($OutputDirectory)
$VcVars = 'E:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat'
if (-not (Test-Path -LiteralPath $CoreLibrary -PathType Leaf)) { throw "Core library is missing: $CoreLibrary" }
if (-not (Test-Path -LiteralPath $ProbePath -PathType Leaf)) { throw "Legacy probe is missing: $ProbePath" }
if (-not (Test-Path -LiteralPath $VcVars -PathType Leaf)) { throw "MSVC environment script is missing: $VcVars" }
[IO.Directory]::CreateDirectory($OutputDirectory) | Out-Null
$IncludeArgs = @(
('/I"{0}"' -f (Join-Path $StandaloneRoot 'Core\include')),
('/I"{0}"' -f (Join-Path $StandaloneRoot 'Core\src')),
('/I"{0}"' -f (Join-Path $StandaloneRoot 'Compat')),
('/I"{0}"' -f (Join-Path $StandaloneRoot 'Source')),
('/I"{0}"' -f (Join-Path $StandaloneRoot 'Source\VoxelForge\Public')),
('/I"{0}"' -f (Join-Path $StandaloneRoot 'Source\VoxelForge\Private'))
)
$LegacyObjectNames = @(
'UnrealPerlin.obj','StandaloneDataAssetGlue.obj','VoxelBiomeDefinition.obj','VoxelCaveMorphology.obj',
'VoxelDensityAblation.obj','VoxelDensityOpStack.obj','VoxelDensityProfile.obj','VoxelDiffLayer.obj',
'VoxelGenerator.obj','VoxelHeightOpStack.obj','VoxelMarchingCubesMesher.obj','VoxelSettings.obj',
'VoxelStrateComposer.obj','VoxelStrateManager.obj','VoxelStrateMeasure.obj','VoxelTerrainOpDefinition.obj',
'VoxelWormField.obj','GpuFarViewerLinkStubs.obj'
)
foreach ($ObjectName in $LegacyObjectNames) {
if (-not (Test-Path -LiteralPath (Join-Path $BuildRoot $ObjectName) -PathType Leaf)) {
throw "Required legacy bridge object is missing: $ObjectName; run Standalone/Build.ps1 -Target All first."
}
}
$ResponsePath = Join-Path $BuildRoot 'floating-islands-audit.rsp'
$ResponseLines = @('/nologo','/std:c++20','/O2','/arch:AVX2','/fp:precise','/EHsc','/D_CRT_SECURE_NO_WARNINGS') +
$IncludeArgs + @(('/Fe"{0}"' -f $AuditExe),('"{0}"' -f $AuditSource)) +
@($LegacyObjectNames | ForEach-Object { '"{0}"' -f $_ }) + @(
('"{0}"' -f $CoreLibrary),'/link','d3d11.lib','dxgi.lib','d3dcompiler.lib','user32.lib','gdi32.lib'
)
[IO.File]::WriteAllLines($ResponsePath,$ResponseLines,[Text.UTF8Encoding]::new($false))
Push-Location $BuildRoot
try {
$CompileCommand = 'call "{0}" >nul && cl.exe @"{1}"' -f $VcVars,$ResponsePath
& $env:ComSpec /d /s /c $CompileCommand
if ($LASTEXITCODE -eq -1073741502) { throw 'Audit build exited with 0xC0000142; retry once using this same wrapper.' }
if ($LASTEXITCODE -ne 0) { throw "Floating-island audit build failed with exit code $LASTEXITCODE" }
if (-not (Test-Path -LiteralPath $AuditExe -PathType Leaf)) { throw "Audit executable was not produced: $AuditExe" }
} finally { Pop-Location }
$RunLedger = Join-Path $OutputDirectory 'run_ledger.txt'
$RunRecords = [Collections.Generic.List[string]]::new()
$Succeeded = $false
for ($Attempt = 1; $Attempt -le 2 -and -not $Succeeded; ++$Attempt) {
$StartInfo = [Diagnostics.ProcessStartInfo]::new()
$StartInfo.FileName = $AuditExe
$StartInfo.Arguments = '"{0}" "{1}"' -f $ProbePath.Replace('"','\"'),$OutputDirectory.Replace('"','\"')
$StartInfo.WorkingDirectory = $BuildRoot
$StartInfo.UseShellExecute = $false
$StartInfo.CreateNoWindow = $true
$StartInfo.RedirectStandardOutput = $true
$StartInfo.RedirectStandardError = $true
$Process = [Diagnostics.Process]::new()
$Process.StartInfo = $StartInfo
[void]$Process.Start()
try { $Process.PriorityClass = 'BelowNormal' } catch { }
$StdoutTask = $Process.StandardOutput.ReadToEndAsync()
$StderrTask = $Process.StandardError.ReadToEndAsync()
$Clock = [Diagnostics.Stopwatch]::StartNew()
[int64]$PeakPrivateBytes = 0
$LimitFailure = $null
while (-not $Process.WaitForExit(250)) {
$Process.Refresh()
$PeakPrivateBytes = [Math]::Max($PeakPrivateBytes,$Process.PrivateMemorySize64)
if ($PeakPrivateBytes -gt 8GB) {
$LimitFailure = 'process private bytes exceeded 8 GiB'
$Process.Kill()
break
}
if ($Clock.Elapsed.TotalMinutes -ge 15.0) {
$LimitFailure = 'audit test exceeded the 15-minute time box'
$Process.Kill()
break
}
}
$Process.WaitForExit()
$Clock.Stop()
$Process.Refresh()
$PeakPrivateBytes = [Math]::Max($PeakPrivateBytes,$Process.PrivateMemorySize64)
$Stdout = $StdoutTask.GetAwaiter().GetResult()
$Stderr = $StderrTask.GetAwaiter().GetResult()
if ($Stdout) { Write-Output $Stdout.TrimEnd() }
if ($Stderr) { [Console]::Error.WriteLine($Stderr.TrimEnd()) }
$ExitCode = $Process.ExitCode
$Status = if ($LimitFailure) { $LimitFailure } elseif ($ExitCode -eq 0) { 'passed' } else { "failed exit=$ExitCode" }
$RunRecords.Add("attempt=$Attempt status=$Status elapsed_s=$([Math]::Round($Clock.Elapsed.TotalSeconds,2)) peak_private_MiB=$([Math]::Round($PeakPrivateBytes/1MB,1)) exit=$ExitCode")
if ($LimitFailure) { break }
if ($ExitCode -eq 0) { $Succeeded = $true; break }
if ($ExitCode -eq -1073741502 -and $Attempt -eq 1) {
$RunRecords.Add('retry_reason=0xC0000142 single permitted retry')
continue
}
break
}
[IO.File]::WriteAllLines($RunLedger,$RunRecords,[Text.UTF8Encoding]::new($false))
Get-Content -LiteralPath $RunLedger | Write-Output
if (-not $Succeeded) { throw "Floating-island audit run failed or exceeded a process limit. See $RunLedger" }
Write-Output "audit_executable=$AuditExe"
Write-Output "audit_results=$OutputDirectory"
@@ -0,0 +1,923 @@
#include "CoreMinimal.h"
#include "OwnerWorldLoader.h"
#include "VoxelForgeCore/VoxelForgeCore.h"
#include <algorithm>
#include <array>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <limits>
#include <numeric>
#include <sstream>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
#if defined(_WIN32)
#ifdef TEXT
#undef TEXT
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h>
#ifdef TEXT
#undef TEXT
#endif
#endif
namespace
{
using namespace VoxelForgeCore;
constexpr std::int64_t Q = WorldAssemblySubvoxelsPerVoxel;
using Clock = std::chrono::steady_clock;
using Point = std::array<std::int64_t, 3>;
struct OracleAnchor
{
Point surfaceQ{};
std::uint32_t supportScore = 0;
};
struct SampleStats
{
std::string hash;
double legacyMilliseconds = 0.0;
double nativeMilliseconds = 0.0;
std::uint64_t signMismatches = 0;
std::uint64_t legacySolidSamples = 0;
std::uint64_t nativeSolidSamples = 0;
std::uint64_t legacyOnlySolidSamples = 0;
std::uint64_t nativeOnlySolidSamples = 0;
double legacyChecksum = 0.0;
double nativeChecksum = 0.0;
};
struct Triangle
{
std::array<float, 12> values{}; // Three xyz vertices followed by one flat normal.
};
struct MeshResult
{
std::uint64_t gridSamples = 0;
std::uint64_t triangles = 0;
std::uint64_t bytes = 0;
};
struct ExistingHashes
{
std::string round1Manifest;
std::string surfaceWorldRound4Manifest;
};
struct OpenSkyAudit
{
std::array<std::int64_t, 2> minimumXY{};
std::array<std::int64_t, 2> maximumXY{};
std::int64_t midHeight = 0;
std::uint64_t openSamples = 0;
std::uint64_t totalSamples = 0;
};
std::uint64_t Mix(std::uint64_t value)
{
value += 0x9e3779b97f4a7c15ull;
value = (value ^ (value >> 30)) * 0xbf58476d1ce4e5b9ull;
value = (value ^ (value >> 27)) * 0x94d049bb133111ebull;
return value ^ (value >> 31);
}
std::uint64_t Next(std::uint64_t& state)
{
state += 0x9e3779b97f4a7c15ull;
return Mix(state);
}
std::int64_t VoxQ(double value)
{ return static_cast<std::int64_t>(std::llround(value * static_cast<double>(Q))); }
std::string Hex64(std::uint64_t value)
{
std::ostringstream output;
output << std::hex << std::setfill('0') << std::setw(16) << value;
return output.str();
}
std::string HashValues(const std::vector<std::int64_t>& values)
{
std::uint64_t hash = 14695981039346656037ull;
for (const auto value : values)
for (int byte = 0; byte < 8; ++byte)
{
hash ^= static_cast<std::uint8_t>(static_cast<std::uint64_t>(value) >> (byte * 8));
hash *= 1099511628211ull;
}
return Hex64(hash);
}
ExistingHashes VerifyExistingManifests(const std::filesystem::path& standaloneRoot)
{
const auto read = [](const std::filesystem::path& path)
{
std::ifstream input(path, std::ios::binary);
if (!input) throw std::runtime_error("cannot read existing manifest: " + path.string());
return std::string(std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>());
};
ExistingHashes hashes;
std::string error;
const auto round1 = WorldAssemblyManifest::FromJson(read(
standaloneRoot / "Research/WorldAssemblerM1/round1-manifest.json"), error);
if (!round1 || !round1->validate(error)
|| Hex64(round1->contentHash) != "c87ae279b0e26f53")
throw std::runtime_error("existing M1 manifest hash changed or failed validation: " + error);
hashes.round1Manifest = Hex64(round1->contentHash);
error.clear();
const auto surface = WorldAssemblyManifest::FromJson(read(
standaloneRoot / "Research/WorldAssemblerM1/round4/world_assembly.json"), error);
if (!surface || !surface->validate(error)
|| Hex64(surface->contentHash) != "8933693d9560ba9f")
throw std::runtime_error("existing SurfaceWorld round-4 hash changed or failed validation: " + error);
hashes.surfaceWorldRound4Manifest = Hex64(surface->contentHash);
return hashes;
}
float LegacyDensity(const FStandaloneOwnerWorld& owner, const Point& q)
{
constexpr double CoreToLegacyVoxelScale = 2.0;
return owner.Generator->GetDensityAt(
static_cast<float>(CoreToLegacyVoxelScale * q[0] / static_cast<double>(Q)),
static_cast<float>(CoreToLegacyVoxelScale * q[1] / static_cast<double>(Q)),
static_cast<float>(CoreToLegacyVoxelScale * q[2] / static_cast<double>(Q)));
}
OracleAnchor FindLegacyAnchor(const FStandaloneOwnerWorld& owner)
{
OracleAnchor best;
const float highZ = -560.0f;
const float lowZ = -2480.0f;
constexpr float ScanStep = 8.0f;
for (int y = -448; y <= 448; y += 16)
for (int x = -448; x <= 448; x += 16)
{
float airZ = highZ;
float above = owner.Generator->GetDensityAt(static_cast<float>(x),
static_cast<float>(y), airZ);
if (above < 0.0f) continue;
for (float solidZ = highZ - ScanStep; solidZ >= lowZ; solidZ -= ScanStep)
{
const float density = owner.Generator->GetDensityAt(static_cast<float>(x),
static_cast<float>(y), solidZ);
if (density < 0.0f)
{
float outside = airZ, inside = solidZ;
for (int iteration = 0; iteration < 12; ++iteration)
{
const float middle = (outside + inside) * 0.5f;
if (owner.Generator->GetDensityAt(static_cast<float>(x),
static_cast<float>(y), middle) < 0.0f) inside = middle;
else outside = middle;
}
const std::int64_t topQ = VoxQ(outside);
std::uint32_t score = 0;
for (int dy = -16; dy <= 16; dy += 8)
for (int dx = -16; dx <= 16; dx += 8)
if (owner.Generator->GetDensityAt(static_cast<float>(x + dx),
static_cast<float>(y + dy),
static_cast<float>((topQ - 4 * Q) / static_cast<double>(Q))) < 0.0f)
++score;
if (score > best.supportScore
|| (score == best.supportScore
&& std::abs(x) + std::abs(y)
< std::abs(best.surfaceQ[0] / Q) + std::abs(best.surfaceQ[1] / Q)))
{
best.surfaceQ = {static_cast<std::int64_t>(x) * Q,
static_cast<std::int64_t>(y) * Q, topQ};
best.supportScore = score;
}
break; // The first air-to-solid crossing is the island top.
}
airZ = solidZ;
}
}
return best;
}
std::vector<Point> MakeSamples(const FloatingIslandInfluenceBounds& bounds,
std::size_t count, std::uint64_t seed)
{
std::vector<Point> points(count);
std::uint64_t state = seed;
for (auto& point : points)
for (std::size_t axis = 0; axis < 3; ++axis)
{
const std::uint64_t span = static_cast<std::uint64_t>(
bounds.maximumSubvoxels[axis] - bounds.minimumSubvoxels[axis] + 1);
point[axis] = bounds.minimumSubvoxels[axis]
+ static_cast<std::int64_t>(Next(state) % std::max<std::uint64_t>(span, 1));
}
return points;
}
OpenSkyAudit MeasureOpenSky(const FloatingIslandFieldV1& field)
{
OpenSkyAudit result;
const FloatingIslandInfluenceBounds bounds = field.influenceBounds();
if (!bounds.finite) throw std::runtime_error("floating-island cluster has no finite bounds");
result.minimumXY = {bounds.minimumSubvoxels[0], bounds.minimumSubvoxels[1]};
result.maximumXY = {bounds.maximumSubvoxels[0], bounds.maximumSubvoxels[1]};
result.midHeight = (bounds.minimumSubvoxels[2] + bounds.maximumSubvoxels[2]) / 2;
constexpr std::int64_t SampleStep = 4 * Q;
for (std::int64_t y = result.minimumXY[1]; y <= result.maximumXY[1]; y += SampleStep)
for (std::int64_t x = result.minimumXY[0]; x <= result.maximumXY[0]; x += SampleStep)
{
if (field.densitySubvoxels(x, y, result.midHeight) >= 0) ++result.openSamples;
++result.totalSamples;
}
return result;
}
void WriteIslandImportManifest(const std::filesystem::path& standaloneRoot,
const FloatingIslandManifestV1& islands, const FStrateSlot& slot,
const std::filesystem::path& outputDir)
{
const auto parentPath = standaloneRoot / "Research/WorldAssemblerM1/round4/world_assembly.json";
std::ifstream parentInput(parentPath, std::ios::binary);
if (!parentInput) throw std::runtime_error("cannot read seed-matched parent world assembly recipe");
const std::string parentText{std::istreambuf_iterator<char>(parentInput),
std::istreambuf_iterator<char>()};
std::string error;
auto parsedParent = WorldAssemblyManifest::FromJson(parentText, error);
if (!parsedParent) throw std::runtime_error("cannot parse parent world assembly recipe: " + error);
parsedParent->plan.strates.clear();
parsedParent->floatingIslands.clear();
WorldAssemblyFloatingIslandPlanV1 entry;
entry.strateIndex = slot.StrateIndex;
entry.topChunkZ = slot.TopChunkZ;
entry.bottomChunkZ = slot.BottomChunkZ;
entry.manifest = islands;
parsedParent->floatingIslands.push_back(std::move(entry));
if (!parsedParent->finalize(error))
throw std::runtime_error("combined island import manifest is invalid: " + error);
const std::string canonical = parsedParent->ToJson();
const auto roundTrip = WorldAssemblyManifest::FromJson(canonical, error);
if (canonical.empty() || !roundTrip || roundTrip->ToJson() != canonical)
throw std::runtime_error("combined island import manifest failed canonical round-trip: " + error);
std::ofstream nested(outputDir / "floating_island_manifest.json", std::ios::trunc);
nested << islands.ToJson() << '\n';
if (!nested) throw std::runtime_error("cannot write nested floating-island manifest");
std::ofstream combined(outputDir / "world_assembly_floating_islands.json", std::ios::trunc);
combined << canonical << '\n';
if (!combined) throw std::runtime_error("cannot write combined floating-island import manifest");
}
SampleStats HashWithWorkers(const FloatingIslandFieldV1& field,
const FStandaloneOwnerWorld& owner, const std::vector<Point>& points,
std::uint32_t workers, bool measure)
{
std::vector<std::int64_t> values(points.size());
std::vector<std::thread> threads;
threads.reserve(workers);
const auto evaluate = [&](std::uint32_t worker)
{
for (std::size_t i = worker; i < points.size(); i += workers)
values[i] = field.densitySubvoxels(points[i][0], points[i][1], points[i][2]);
};
for (std::uint32_t worker = 0; worker < workers; ++worker)
threads.emplace_back(evaluate, worker);
for (auto& thread : threads) thread.join();
SampleStats stats;
stats.hash = HashValues(values);
if (!measure) return stats;
std::vector<float> legacyValues(points.size());
const auto beginLegacy = Clock::now();
double legacyChecksum = 0.0;
for (std::size_t i = 0; i < points.size(); ++i)
{
const float value = LegacyDensity(owner, points[i]);
legacyValues[i] = value;
legacyChecksum += value;
}
const auto endLegacy = Clock::now();
const auto beginNative = Clock::now();
double nativeChecksum = 0.0;
for (const Point& point : points)
nativeChecksum += static_cast<double>(field.densitySubvoxels(
point[0], point[1], point[2])) / static_cast<double>(Q);
const auto endNative = Clock::now();
for (std::size_t i = 0; i < values.size(); ++i)
{
const bool legacySolid = legacyValues[i] < 0.0f;
const bool nativeSolid = values[i] < 0;
if (legacySolid) ++stats.legacySolidSamples;
if (nativeSolid) ++stats.nativeSolidSamples;
if (legacySolid && !nativeSolid) ++stats.legacyOnlySolidSamples;
if (nativeSolid && !legacySolid) ++stats.nativeOnlySolidSamples;
if (legacySolid != nativeSolid) ++stats.signMismatches;
}
stats.legacyMilliseconds = std::chrono::duration<double, std::milli>(endLegacy - beginLegacy).count();
stats.nativeMilliseconds = std::chrono::duration<double, std::milli>(endNative - beginNative).count();
stats.legacyChecksum = legacyChecksum;
stats.nativeChecksum = nativeChecksum;
return stats;
}
using D3 = std::array<double, 3>;
D3 Add(D3 a, D3 b) { return {a[0] + b[0], a[1] + b[1], a[2] + b[2]}; }
D3 Sub(D3 a, D3 b) { return {a[0] - b[0], a[1] - b[1], a[2] - b[2]}; }
D3 Mul(D3 a, double s) { return {a[0] * s, a[1] * s, a[2] * s}; }
double Dot(D3 a, D3 b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; }
D3 Cross(D3 a, D3 b)
{ return {a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]}; }
double Length(D3 a) { return std::sqrt(Dot(a, a)); }
D3 Normalize(D3 a)
{
const double length = Length(a);
return length > 1e-12 ? Mul(a, 1.0 / length) : D3{0.0, 0.0, 1.0};
}
double SampleNative(const FloatingIslandFieldV1& field, const Point& q)
{ return static_cast<double>(field.densitySubvoxels(q[0], q[1], q[2])) / static_cast<double>(Q); }
double SampleLegacy(const FStandaloneOwnerWorld& owner, const Point& q)
{ return static_cast<double>(LegacyDensity(owner, q)); }
double TetGradient(const std::array<D3, 4>& points, const std::array<double, 4>& values, D3& output)
{
const D3 a = Sub(points[1], points[0]);
const D3 b = Sub(points[2], points[0]);
const D3 c = Sub(points[3], points[0]);
const D3 bc = Cross(b, c), ca = Cross(c, a), ab = Cross(a, b);
const double determinant = Dot(a, bc);
if (std::abs(determinant) < 1e-12) { output = {0.0, 0.0, 1.0}; return 0.0; }
output = Mul(Add(Add(Mul(bc, values[1] - values[0]),
Mul(ca, values[2] - values[0])), Mul(ab, values[3] - values[0])), 1.0 / determinant);
return Length(output);
}
MeshResult ExtractMesh(const std::filesystem::path& outputPath,
Point minimumQ, Point maximumQ, std::int64_t stepQ,
const std::function<double(const Point&)>& density)
{
const std::size_t nx = static_cast<std::size_t>((maximumQ[0] - minimumQ[0]) / stepQ + 1);
const std::size_t ny = static_cast<std::size_t>((maximumQ[1] - minimumQ[1]) / stepQ + 1);
const std::size_t nz = static_cast<std::size_t>((maximumQ[2] - minimumQ[2]) / stepQ + 1);
const std::size_t gridCount = nx * ny * nz;
if (gridCount > 4'000'000) throw std::runtime_error("mesh grid exceeds the 4M-sample cap");
std::vector<float> grid(gridCount);
const auto gridIndex = [=](std::size_t x, std::size_t y, std::size_t z)
{ return (z * ny + y) * nx + x; };
for (std::size_t z = 0; z < nz; ++z)
for (std::size_t y = 0; y < ny; ++y)
for (std::size_t x = 0; x < nx; ++x)
{
const Point q{minimumQ[0] + static_cast<std::int64_t>(x) * stepQ,
minimumQ[1] + static_cast<std::int64_t>(y) * stepQ,
minimumQ[2] + static_cast<std::int64_t>(z) * stepQ};
grid[gridIndex(x, y, z)] = static_cast<float>(density(q));
}
constexpr std::array<std::array<int, 3>, 8> Corner = {{{0,0,0},{1,0,0},{1,1,0},{0,1,0},
{0,0,1},{1,0,1},{1,1,1},{0,1,1}}};
constexpr std::array<std::array<int, 4>, 6> Tets = {{{0,5,1,6},{0,1,2,6},{0,2,3,6},
{0,3,7,6},{0,7,4,6},{0,4,5,6}}};
constexpr std::array<std::array<int, 2>, 6> TetEdges = {{{0,1},{0,2},{0,3},{1,2},{1,3},{2,3}}};
std::vector<Triangle> triangles;
triangles.reserve(8192);
for (std::size_t z = 0; z + 1 < nz; ++z)
for (std::size_t y = 0; y + 1 < ny; ++y)
for (std::size_t x = 0; x + 1 < nx; ++x)
{
std::array<D3, 8> cubePoints{};
std::array<double, 8> cubeValues{};
double low = std::numeric_limits<double>::infinity();
double high = -std::numeric_limits<double>::infinity();
for (std::size_t corner = 0; corner < Corner.size(); ++corner)
{
const auto& offset = Corner[corner];
cubePoints[corner] = {
static_cast<double>(minimumQ[0] + static_cast<std::int64_t>(x + offset[0]) * stepQ) / Q,
static_cast<double>(minimumQ[1] + static_cast<std::int64_t>(y + offset[1]) * stepQ) / Q,
static_cast<double>(minimumQ[2] + static_cast<std::int64_t>(z + offset[2]) * stepQ) / Q};
cubeValues[corner] = grid[gridIndex(x + offset[0], y + offset[1], z + offset[2])];
low = std::min(low, cubeValues[corner]);
high = std::max(high, cubeValues[corner]);
}
if (low >= 0.0 || high < 0.0) continue;
for (const auto& tet : Tets)
{
std::array<D3, 4> points{};
std::array<double, 4> values{};
for (std::size_t i = 0; i < 4; ++i)
{ points[i] = cubePoints[tet[i]]; values[i] = cubeValues[tet[i]]; }
std::array<D3, 4> polygon{};
std::size_t polygonCount = 0;
for (const auto& edge : TetEdges)
{
const int ia = edge[0], ib = edge[1];
if ((values[ia] < 0.0) == (values[ib] < 0.0)) continue;
const double t = values[ia] / (values[ia] - values[ib]);
const D3 point = Add(points[ia], Mul(Sub(points[ib], points[ia]), t));
bool duplicate = false;
for (std::size_t i = 0; i < polygonCount; ++i)
if (Length(Sub(polygon[i], point)) < 1e-7) { duplicate = true; break; }
if (!duplicate && polygonCount < polygon.size()) polygon[polygonCount++] = point;
}
if (polygonCount < 3) continue;
D3 normal{};
TetGradient(points, values, normal);
normal = Normalize(normal);
D3 center{};
for (std::size_t i = 0; i < polygonCount; ++i) center = Add(center, polygon[i]);
center = Mul(center, 1.0 / polygonCount);
const D3 axis = std::abs(normal[2]) < 0.85 ? D3{0,0,1} : D3{0,1,0};
const D3 right = Normalize(Cross(axis, normal));
const D3 up = Normalize(Cross(normal, right));
std::array<std::pair<double, D3>, 4> ordered{};
for (std::size_t i = 0; i < polygonCount; ++i)
{
const D3 delta = Sub(polygon[i], center);
ordered[i] = {std::atan2(Dot(delta, up), Dot(delta, right)), polygon[i]};
}
std::sort(ordered.begin(), ordered.begin() + polygonCount,
[](const auto& a, const auto& b) { return a.first < b.first; });
for (std::size_t i = 1; i + 1 < polygonCount; ++i)
{
D3 a = ordered[0].second, b = ordered[i].second, c = ordered[i + 1].second;
if (Dot(Cross(Sub(b, a), Sub(c, a)), normal) < 0.0) std::swap(b, c);
Triangle triangle;
for (std::size_t axisIndex = 0; axisIndex < 3; ++axisIndex)
{
triangle.values[axisIndex] = static_cast<float>(a[axisIndex]);
triangle.values[3 + axisIndex] = static_cast<float>(b[axisIndex]);
triangle.values[6 + axisIndex] = static_cast<float>(c[axisIndex]);
triangle.values[9 + axisIndex] = static_cast<float>(normal[axisIndex]);
}
triangles.push_back(triangle);
}
}
}
std::ofstream output(outputPath, std::ios::binary | std::ios::trunc);
if (!output) throw std::runtime_error("cannot create mesh capture: " + outputPath.string());
const std::uint32_t count = static_cast<std::uint32_t>(triangles.size());
output.write("VFMI", 4);
output.write(reinterpret_cast<const char*>(&count), sizeof(count));
for (const auto& triangle : triangles)
output.write(reinterpret_cast<const char*>(triangle.values.data()),
static_cast<std::streamsize>(sizeof(triangle.values)));
output.close();
MeshResult result;
result.gridSamples = gridCount;
result.triangles = triangles.size();
result.bytes = std::filesystem::file_size(outputPath);
return result;
}
void WriteJson(const std::filesystem::path& path, const FloatingIslandManifestV1& manifest,
const FloatingIslandBoundsAuditResult& bounds, const FloatingIslandRouteAuditResult& route,
const FloatingIslandRouteAuditResult& raised, const SampleStats& sample,
const FloatingIslandThicknessAuditResult& thickness,
const FloatingIslandThicknessAuditResult& thinThickness,
const OracleAnchor& anchor, const std::array<MeshResult, 4>& meshes,
const std::array<std::string, 3>& fieldHashes, const ExistingHashes& oldHashes,
const OpenSkyAudit& openSky)
{
const Point ownerAnchorQ = anchor.surfaceQ;
const Point coreAnchorQ{ownerAnchorQ[0] / 2, ownerAnchorQ[1] / 2, ownerAnchorQ[2] / 2};
std::ofstream out(path, std::ios::trunc);
if (!out) throw std::runtime_error("cannot create audit result JSON");
out << std::setprecision(12);
out << "{\n \"recipe_hash\":\"" << Hex64(manifest.recipe.contentHash)
<< "\",\n \"plan_hash\":\"" << Hex64(manifest.plan.contentHash)
<< "\",\n \"manifest_hash\":\"" << Hex64(manifest.contentHash)
<< "\",\n \"unchanged_existing_hashes\":{\"m1_round1_manifest\":\""
<< oldHashes.round1Manifest << "\",\"surfaceworld_round4_manifest\":\""
<< oldHashes.surfaceWorldRound4Manifest << "\"}"
<< ",\n \"geometry_version\":" << manifest.recipe.geometryVersion
<< ",\n \"owner_voxel_size_cm\":12.5,\n \"core_voxel_size_cm\":25"
<< ",\n \"owner_to_core_length_scale\":0.5"
<< ",\n \"plan_islands\":" << manifest.plan.islands.size()
<< ",\n \"mid_height_open_sky\":{\"sampling_step_m\":1.0,\"z_core_voxels\":"
<< openSky.midHeight / static_cast<double>(Q)
<< ",\"bounds_xy_core_voxels\":["
<< openSky.minimumXY[0] / static_cast<double>(Q) << ','
<< openSky.minimumXY[1] / static_cast<double>(Q) << ','
<< openSky.maximumXY[0] / static_cast<double>(Q) << ','
<< openSky.maximumXY[1] / static_cast<double>(Q)
<< "],\"open_samples\":" << openSky.openSamples
<< ",\"total_samples\":" << openSky.totalSamples
<< ",\"open_fraction\":" << (openSky.totalSamples
? static_cast<double>(openSky.openSamples) / openSky.totalSamples : 0.0) << "}"
<< ",\n \"plan_connectors\":" << manifest.plan.clusterEdges.size()
<< ",\n \"route_points\":" << manifest.plan.route.size()
<< ",\n \"legacy_anchor_owner_voxels\":[" << ownerAnchorQ[0] / static_cast<double>(Q)
<< ',' << ownerAnchorQ[1] / static_cast<double>(Q) << ','
<< ownerAnchorQ[2] / static_cast<double>(Q) << ']'
<< ",\n \"comparison_anchor_core_voxels\":[" << coreAnchorQ[0] / static_cast<double>(Q)
<< ',' << coreAnchorQ[1] / static_cast<double>(Q) << ','
<< coreAnchorQ[2] / static_cast<double>(Q) << "],\n \"legacy_anchor_plateau_samples\":"
<< anchor.supportScore << ",\n \"recipe_parameters\":{\"seed\":" << manifest.recipe.seed
<< ",\"primary_island_count\":" << manifest.recipe.primaryIslandCount
<< ",\"density_q16\":" << manifest.recipe.islandDensityQ16
<< ",\"spacing_voxels\":" << manifest.recipe.spacingSubvoxels / static_cast<double>(Q)
<< ",\"radius_voxels\":[" << manifest.recipe.minimumRadiusSubvoxels / static_cast<double>(Q)
<< ',' << manifest.recipe.maximumRadiusSubvoxels / static_cast<double>(Q)
<< "],\"thickness_ratio_q16\":" << manifest.recipe.thicknessRatioQ16
<< ",\"vertical_jitter_q16\":" << manifest.recipe.verticalJitterQ16
<< ",\"top_flatten_q16\":" << manifest.recipe.topFlattenQ16
<< ",\"contour_warp_voxels\":" << manifest.recipe.contourWarpSubvoxels / static_cast<double>(Q)
<< ",\"crust_depth_bonus_q16\":" << manifest.recipe.crustDepthBonusQ16
<< ",\"root_depth_ratio_q16\":" << manifest.recipe.rootDepthRatioQ16
<< ",\"roughness_voxels\":" << manifest.recipe.roughnessSubvoxels / static_cast<double>(Q)
<< ",\"blend_voxels\":" << manifest.recipe.blendRadiusSubvoxels / static_cast<double>(Q)
<< ",\"bridge_half_width_voxels\":" << manifest.recipe.bridgeHalfWidthSubvoxels / static_cast<double>(Q)
<< ",\"bridge_thickness_voxels\":" << manifest.recipe.bridgeThicknessSubvoxels / static_cast<double>(Q)
<< "},\n \"stable_island_ids\":[";
for (std::size_t i = 0; i < manifest.plan.islands.size(); ++i)
{ if (i) out << ','; out << '\"' << Hex64(manifest.plan.islands[i].stableId) << '\"'; }
out << "],\n \"stable_connector_ids\":[";
for (std::size_t i = 0; i < manifest.plan.clusterEdges.size(); ++i)
{ if (i) out << ','; out << '\"' << Hex64(manifest.plan.clusterEdges[i].stableId) << '\"'; }
out << "],\n \"island_profiles\":[";
for (std::size_t i = 0; i < manifest.plan.islands.size(); ++i)
{
if (i) out << ',';
const auto& island = manifest.plan.islands[i];
const double ratio = static_cast<double>(island.topZSubvoxels - island.bottomZSubvoxels)
/ (2.0 * static_cast<double>(island.radiusSubvoxels));
out << "{\"id\":\"" << Hex64(island.stableId) << "\",\"ordinal\":" << island.ordinal
<< ",\"center_voxels\":[" << island.centerSubvoxels[0] / static_cast<double>(Q)
<< ',' << island.centerSubvoxels[1] / static_cast<double>(Q)
<< ',' << island.centerSubvoxels[2] / static_cast<double>(Q)
<< "],\"radius_voxels\":" << island.radiusSubvoxels / static_cast<double>(Q)
<< ",\"top_voxels\":" << island.topZSubvoxels / static_cast<double>(Q)
<< ",\"bottom_voxels\":" << island.bottomZSubvoxels / static_cast<double>(Q)
<< ",\"depth_to_diameter\":" << ratio << '}';
}
out << "],\n \"bounds\":{\"passed\":" << (bounds.passed ? "true" : "false")
<< ",\"negative_control_detected\":" << (bounds.negativeControlDetected ? "true" : "false")
<< ",\"boxes\":" << bounds.boxesTested << ",\"samples\":" << bounds.pointSamples
<< ",\"unknown\":" << bounds.unknownBounds << ",\"missed\":" << bounds.missedBounds
<< ",\"island_boxes\":" << bounds.islandBoundsTested
<< ",\"connector_boxes\":" << bounds.connectorBoundsTested
<< ",\"void_boxes_proved_air\":" << bounds.voidBoxesProvedAir << "},\n \"thickness\":{\"passed\":"
<< (thickness.passed ? "true" : "false")
<< ",\"islands_tested\":" << thickness.islandsTested
<< ",\"islands_below_minimum\":" << thickness.islandsBelowMinimum
<< ",\"minimum_depth_to_diameter_ratio\":" << thickness.minimumDepthToDiameterRatio
<< ",\"required_depth_to_diameter_ratio\":" << thickness.requiredDepthToDiameterRatio
<< ",\"minimum_island_id\":\"" << Hex64(thickness.minimumIslandStableId) << "\"}"
<< ",\n \"thin_pad_negative_control\":{\"detected\":"
<< ((!thinThickness.passed && thinThickness.islandsBelowMinimum == thinThickness.islandsTested)
? "true" : "false")
<< ",\"failed_islands\":" << thinThickness.islandsBelowMinimum
<< ",\"minimum_depth_to_diameter_ratio\":" << thinThickness.minimumDepthToDiameterRatio << "}"
<< ",\n \"route\":{\"passed\":"
<< (route.passed ? "true" : "false") << ",\"segments\":" << route.segmentsTested
<< ",\"centers\":" << route.centerSamples << ",\"capsule_samples\":"
<< route.capsuleFieldSamples << ",\"blocked\":" << route.blockedCapsuleSamples
<< ",\"support_samples\":" << route.supportProbeSamples << ",\"unsupported\":"
<< route.unsupportedCenterSamples << ",\"grade_failures\":" << route.gradeFailures
<< ",\"minimum_clearance_subvoxels\":" << route.minimumClearanceSubvoxels
<< "},\n \"raised_route_negative_control\":{\"detected\":"
<< ((!raised.passed && (raised.unsupportedCenterSamples || raised.blockedCapsuleSamples)) ? "true" : "false")
<< ",\"passed\":" << (raised.passed ? "true" : "false")
<< ",\"unsupported\":" << raised.unsupportedCenterSamples
<< ",\"blocked\":" << raised.blockedCapsuleSamples << "},\n \"determinism\":{\"W1\":\""
<< fieldHashes[0] << "\",\"W8\":\"" << fieldHashes[1] << "\",\"W16\":\""
<< fieldHashes[2] << "\",\"plan_repeat\":true,\"hash_samples\":32768},\n \"timing\":{\"same_queries\":32768,\"legacy_ms\":"
<< sample.legacyMilliseconds << ",\"native_ms\":" << sample.nativeMilliseconds
<< ",\"native_over_legacy\":" << (sample.nativeMilliseconds / std::max(sample.legacyMilliseconds, 1e-9))
<< ",\"sign_mismatches\":" << sample.signMismatches << ",\"sign_mismatch_percent\":"
<< (100.0 * sample.signMismatches / 32768.0)
<< ",\"legacy_solid_samples\":" << sample.legacySolidSamples
<< ",\"native_solid_samples\":" << sample.nativeSolidSamples
<< ",\"legacy_only_solid_samples\":" << sample.legacyOnlySolidSamples
<< ",\"native_only_solid_samples\":" << sample.nativeOnlySolidSamples << "},\n \"mesh\":[";
constexpr std::array<const char*, 4> names{"cluster_legacy", "cluster_native", "detail_legacy", "detail_native"};
for (std::size_t i = 0; i < meshes.size(); ++i)
{
if (i) out << ',';
out << "{\"name\":\"" << names[i] << "\",\"grid_samples\":" << meshes[i].gridSamples
<< ",\"triangles\":" << meshes[i].triangles << ",\"bytes\":" << meshes[i].bytes << '}';
}
out << "],\n \"route_points_core_voxels\":[";
for (std::size_t i = 0; i < manifest.plan.route.size(); ++i)
{
if (i) out << ',';
const auto& point = manifest.plan.route[i];
out << '[' << point.positionSubvoxels[0] / static_cast<double>(Q) << ','
<< point.positionSubvoxels[1] / static_cast<double>(Q) << ','
<< point.positionSubvoxels[2] / static_cast<double>(Q) << ']';
}
out << "]\n}\n";
}
}
int main(int argc, char** argv)
{
#if defined(_WIN32)
SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS);
SetProcessAffinityMask(GetCurrentProcess(), static_cast<DWORD_PTR>(0xFFFF));
#endif
try
{
if (argc != 3)
{ std::cerr << "usage: FloatingIslandsAudit <legacy-probe.json> <output-dir>\n"; return 2; }
const std::filesystem::path probePath = argv[1];
const std::filesystem::path outputDir = argv[2];
std::filesystem::create_directories(outputDir);
const auto standaloneRoot = probePath.parent_path().parent_path().parent_path()
.parent_path().parent_path();
const ExistingHashes oldHashes = VerifyExistingManifests(standaloneRoot);
FStandaloneOwnerWorld owner;
std::string error;
if (!StandaloneOwnerLoader::Load(probePath.string(), owner, error))
throw std::runtime_error("legacy probe load failed: " + error);
const OracleAnchor anchor = FindLegacyAnchor(owner);
if (anchor.supportScore < 9)
throw std::runtime_error("legacy scan found no broad FloatingIslands top in the frozen strate range");
const Point coreAnchorQ{anchor.surfaceQ[0] / 2,
anchor.surfaceQ[1] / 2, anchor.surfaceQ[2] / 2};
FloatingIslandRecipeV1 recipe;
recipe.seed = 0;
recipe.layoutOriginXSubvoxels = coreAnchorQ[0];
recipe.layoutOriginYSubvoxels = coreAnchorQ[1];
recipe.layoutCenterTopZSubvoxels = coreAnchorQ[2];
recipe.geometryVersion = 2;
recipe.layoutMinimumZSubvoxels = coreAnchorQ[2] - 80 * Q;
recipe.layoutMaximumZSubvoxels = coreAnchorQ[2] + 48 * Q;
recipe.primaryIslandCount = 3;
recipe.islandDensityQ16 = 32768;
recipe.spacingSubvoxels = 56 * Q;
recipe.minimumRadiusSubvoxels = 12 * Q;
recipe.maximumRadiusSubvoxels = 24 * Q;
recipe.thicknessRatioQ16 = 39322;
recipe.verticalJitterQ16 = 39322;
recipe.topFlattenQ16 = 39322;
recipe.taperEndMinimumQ16 = 29491;
recipe.taperEndMaximumQ16 = 45875;
recipe.blendRadiusSubvoxels = 3 * Q;
recipe.contourWarpSubvoxels = VoxQ(6.3);
recipe.roughnessSubvoxels = Q;
recipe.boundarySealSubvoxels = 2 * Q;
recipe.bridgeHalfWidthSubvoxels = VoxQ(2.5);
recipe.bridgeThicknessSubvoxels = 4 * Q;
const auto beginPlanning = Clock::now();
auto planned = PlanFloatingIslandCluster(recipe, error);
const auto endPlanning = Clock::now();
if (!planned) throw std::runtime_error("native island planner failed: " + error);
const std::string canonical = planned->ToJson();
for (int repeat = 0; repeat < 2; ++repeat)
{
std::string repeatError;
const auto repeated = PlanFloatingIslandCluster(recipe, repeatError);
if (!repeated || repeated->contentHash != planned->contentHash
|| repeated->plan.contentHash != planned->plan.contentHash
|| repeated->ToJson() != canonical)
throw std::runtime_error("repeated native plan hash or canonical JSON changed");
}
const auto parsed = FloatingIslandManifestV1::FromJson(canonical, error);
if (!parsed || parsed->ToJson() != canonical || !parsed->validate(error))
throw std::runtime_error("native manifest did not validate after round-trip: " + error);
FloatingIslandFieldV1 field(planned->recipe, planned->plan);
if (!field.isValid()) throw std::runtime_error("native field rejected the planned island graph");
const auto& middleIsland = planned->plan.islands.at(1);
const std::int64_t crustBottom = middleIsland.topZSubvoxels
- middleIsland.radiusSubvoxels * (planned->recipe.thicknessRatioQ16
+ planned->recipe.crustDepthBonusQ16) / 65536;
std::cout << "underside_center_density_q=";
for (int sample = 0; sample <= 8; ++sample)
{
if (sample) std::cout << ',';
const std::int64_t z = crustBottom - middleIsland.radiusSubvoxels * sample / 8;
std::cout << field.densitySubvoxels(middleIsland.centerSubvoxels[0],
middleIsland.centerSubvoxels[1], z);
}
std::cout << '\n';
const auto& ownerSlots = owner.Manager->GetLayout();
const auto islandSlot = std::find_if(ownerSlots.begin(), ownerSlots.end(),
[](const FStrateSlot& slot)
{ return slot.Definition != nullptr
&& slot.Definition->GeneratorType == ECaveGeneratorType::FloatingIslands; });
if (islandSlot == ownerSlots.end())
throw std::runtime_error("owner snapshot has no resolved FloatingIslands strate slot");
if (planned->recipe.seed != static_cast<std::uint32_t>(owner.Seed))
throw std::runtime_error("native island recipe seed does not match the owner season");
const OpenSkyAudit openSky = MeasureOpenSky(field);
const auto bounds = AuditFloatingIslandBounds(field, 512);
const auto route = AuditFloatingIslandRoute(field);
const auto raisedRoute = AuditFloatingIslandRoute(field, 8 * Q);
const auto thickness = AuditFloatingIslandThickness(planned->plan);
auto thinPlan = planned->plan;
for (auto& island : thinPlan.islands)
island.bottomZSubvoxels = island.topZSubvoxels
- island.radiusSubvoxels * planned->recipe.thicknessRatioQ16 / 65536;
const auto thinThickness = AuditFloatingIslandThickness(thinPlan);
const auto influence = field.influenceBounds();
const auto points = MakeSamples(influence, 32768, 0x4d33524f554e4431ull);
const auto w1 = HashWithWorkers(field, owner, points, 1, false);
const auto w8 = HashWithWorkers(field, owner, points, 8, false);
const auto w16 = HashWithWorkers(field, owner, points, 16, true);
const std::array<std::string, 3> hashes{w1.hash, w8.hash, w16.hash};
if (w1.hash != w8.hash || w1.hash != w16.hash)
throw std::runtime_error("native field sample hash differs between W1/W8/W16");
if (!bounds.passed) throw std::runtime_error("native island bounds audit or negative control failed");
if (!thickness.passed || thinThickness.passed
|| thinThickness.islandsBelowMinimum != thinThickness.islandsTested)
throw std::runtime_error("native island thickness audit or thin-pad negative control failed");
std::cout << "route_diagnostics pass=" << route.passed << " segments=" << route.segmentsTested
<< " centers=" << route.centerSamples << " capsule_samples=" << route.capsuleFieldSamples
<< " blocked=" << route.blockedCapsuleSamples << " support_samples=" << route.supportProbeSamples
<< " unsupported=" << route.unsupportedCenterSamples << " grade_failures=" << route.gradeFailures
<< " minimum_clearance_q=" << route.minimumClearanceSubvoxels
<< " first_blocked_segment=" << route.firstBlockedSegment
<< " first_unsupported_segment=" << route.firstUnsupportedSegment
<< " first_grade_segment=" << route.firstGradeFailureSegment
<< " unsupported_center_voxels=" << route.firstUnsupportedCenterSubvoxels[0] / static_cast<double>(Q)
<< ',' << route.firstUnsupportedCenterSubvoxels[1] / static_cast<double>(Q)
<< ',' << route.firstUnsupportedCenterSubvoxels[2] / static_cast<double>(Q) << '\n';
if (!route.passed)
{
const auto& routePoints = planned->plan.route;
if (route.firstBlockedSegment != ~std::uint64_t{0})
std::cout << "first_blocked_position_voxels="
<< route.firstBlockedPositionSubvoxels[0] / static_cast<double>(Q) << ','
<< route.firstBlockedPositionSubvoxels[1] / static_cast<double>(Q) << ','
<< route.firstBlockedPositionSubvoxels[2] / static_cast<double>(Q) << '\n';
const std::size_t debugBegin = route.firstUnsupportedSegment == ~std::uint64_t{0}
? 0 : static_cast<std::size_t>(route.firstUnsupportedSegment);
for (std::size_t i = debugBegin > 1 ? debugBegin - 1 : 0;
i < std::min(routePoints.size(), debugBegin + 3); ++i)
{
const auto& point = routePoints[i].positionSubvoxels;
std::cout << "route_point=" << i << ' '
<< point[0] / static_cast<double>(Q) << ','
<< point[1] / static_cast<double>(Q) << ','
<< point[2] / static_cast<double>(Q) << '\n';
}
const double maxGrade = static_cast<double>(planned->recipe.routeConstraints.maximumGradeQ16) / 65536.0;
for (std::size_t i = 0; i + 1 < routePoints.size(); ++i)
{
const auto& a = routePoints[i].positionSubvoxels;
const auto& b = routePoints[i + 1].positionSubvoxels;
const double horizontal = std::hypot(static_cast<double>(b[0] - a[0]),
static_cast<double>(b[1] - a[1]));
const double grade = horizontal > 0.0 ? std::abs(static_cast<double>(b[2] - a[2])) / horizontal : 1e9;
if (grade > maxGrade + 1e-7)
std::cout << "grade_segment=" << i << " ratio=" << grade << " limit=" << maxGrade
<< " from=" << a[0] / static_cast<double>(Q) << ',' << a[1] / static_cast<double>(Q)
<< ',' << a[2] / static_cast<double>(Q) << " to=" << b[0] / static_cast<double>(Q)
<< ',' << b[1] / static_cast<double>(Q) << ',' << b[2] / static_cast<double>(Q) << '\n';
}
for (std::size_t i = 0; i < planned->plan.islands.size(); ++i)
{
const auto& island = planned->plan.islands[i];
std::cout << "island=" << i << " center=" << island.centerSubvoxels[0] / static_cast<double>(Q)
<< ',' << island.centerSubvoxels[1] / static_cast<double>(Q) << ','
<< island.topZSubvoxels / static_cast<double>(Q) << " radius="
<< island.radiusSubvoxels / static_cast<double>(Q) << '\n';
}
const auto& constraints = planned->recipe.routeConstraints;
const auto& unsupported = route.firstUnsupportedCenterSubvoxels;
const double foot = unsupported[2] / static_cast<double>(Q)
- constraints.capsuleHeightSubvoxels / (2.0 * Q);
std::cout << "unsupported_route_probe=" << unsupported[0] / static_cast<double>(Q)
<< ',' << unsupported[1] / static_cast<double>(Q) << ','
<< unsupported[2] / static_cast<double>(Q) << " foot=" << foot << '\n';
const double probeRadius = constraints.supportProbeRadiusSubvoxels / static_cast<double>(Q);
const std::array<std::array<double, 2>, 5> supportPoints{{
{0.0,0.0},{probeRadius,0.0},{-probeRadius,0.0},{0.0,probeRadius},{0.0,-probeRadius}}};
for (std::size_t probeIndex = 0; probeIndex < supportPoints.size(); ++probeIndex)
{
double firstSolidDepth = -1.0;
for (double depth = (constraints.minimumClearanceSubvoxels + 0.5 * Q) / static_cast<double>(Q);
depth <= (constraints.minimumClearanceSubvoxels + constraints.supportSearchDepthSubvoxels)
/ static_cast<double>(Q) + 0.001; depth += 0.5)
{
const auto density = field.densitySubvoxels(
std::llround(unsupported[0] + supportPoints[probeIndex][0] * Q),
std::llround(unsupported[1] + supportPoints[probeIndex][1] * Q),
std::llround((foot - depth) * Q));
if (density < 0) { firstSolidDepth = depth; break; }
}
std::cout << "support_probe=" << probeIndex << " first_solid_depth_vox=" << firstSolidDepth << '\n';
std::cout << "support_profile=" << probeIndex << ':';
for (double depth = 0.5; depth <= 5.0; depth += 0.5)
{
const auto density = field.densitySubvoxels(
std::llround(unsupported[0] + supportPoints[probeIndex][0] * Q),
std::llround(unsupported[1] + supportPoints[probeIndex][1] * Q),
std::llround((foot - depth) * Q));
std::cout << ' ' << depth << '=' << density;
}
std::cout << '\n';
}
for (std::size_t i = 0; i < planned->plan.clusterEdges.size(); ++i)
{
const auto& edge = planned->plan.clusterEdges[i];
std::cout << "connector=" << i << " start=" << edge.startSubvoxels[0] / static_cast<double>(Q)
<< ',' << edge.startSubvoxels[1] / static_cast<double>(Q) << ','
<< edge.startSubvoxels[2] / static_cast<double>(Q) << " end="
<< edge.endSubvoxels[0] / static_cast<double>(Q) << ','
<< edge.endSubvoxels[1] / static_cast<double>(Q) << ','
<< edge.endSubvoxels[2] / static_cast<double>(Q) << '\n';
}
}
if (!route.passed) throw std::runtime_error("native route capsule sweep failed");
if (raisedRoute.passed || (raisedRoute.unsupportedCenterSamples == 0
&& raisedRoute.blockedCapsuleSamples == 0))
throw std::runtime_error("raised-route negative control did not detect unsupported traversal");
std::array<MeshResult, 4> meshes{};
const auto clusterBounds = field.influenceBounds();
if (!clusterBounds.finite) throw std::runtime_error("native cluster mesh bounds are not finite");
const Point clusterMin{clusterBounds.minimumSubvoxels[0] - 8 * Q,
clusterBounds.minimumSubvoxels[1] - 8 * Q, clusterBounds.minimumSubvoxels[2] - 8 * Q};
const Point clusterMax{clusterBounds.maximumSubvoxels[0] + 8 * Q,
clusterBounds.maximumSubvoxels[1] + 8 * Q, clusterBounds.maximumSubvoxels[2] + 8 * Q};
const Point detailMin{coreAnchorQ[0] - 64 * Q, coreAnchorQ[1] - 64 * Q, coreAnchorQ[2] - 80 * Q};
const Point detailMax{coreAnchorQ[0] + 64 * Q, coreAnchorQ[1] + 64 * Q, coreAnchorQ[2] + 16 * Q};
meshes[0] = ExtractMesh(outputDir / "cluster_legacy.vfmesh", clusterMin, clusterMax, 4 * Q,
[&](const Point& q) { return SampleLegacy(owner, q); });
meshes[1] = ExtractMesh(outputDir / "cluster_native.vfmesh", clusterMin, clusterMax, 4 * Q,
[&](const Point& q) { return SampleNative(field, q); });
meshes[2] = ExtractMesh(outputDir / "detail_legacy.vfmesh", detailMin, detailMax, 2 * Q,
[&](const Point& q) { return SampleLegacy(owner, q); });
meshes[3] = ExtractMesh(outputDir / "detail_native.vfmesh", detailMin, detailMax, 2 * Q,
[&](const Point& q) { return SampleNative(field, q); });
if (meshes[0].triangles == 0 || meshes[1].triangles == 0
|| meshes[2].triangles == 0 || meshes[3].triangles == 0)
throw std::runtime_error("matched legacy/native mesh extraction produced an empty field");
WriteJson(outputDir / "audit.json", *planned, bounds, route, raisedRoute,
w16, thickness, thinThickness, anchor, meshes, hashes, oldHashes, openSky);
WriteIslandImportManifest(standaloneRoot, *planned, *islandSlot, outputDir);
std::cout << std::setprecision(4)
<< "legacy_anchor_owner_voxels=" << anchor.surfaceQ[0] / static_cast<double>(Q) << ','
<< anchor.surfaceQ[1] / static_cast<double>(Q) << ','
<< anchor.surfaceQ[2] / static_cast<double>(Q)
<< " comparison_anchor_core_voxels=" << coreAnchorQ[0] / static_cast<double>(Q) << ','
<< coreAnchorQ[1] / static_cast<double>(Q) << ','
<< coreAnchorQ[2] / static_cast<double>(Q)
<< " plateau_samples=" << anchor.supportScore << '\n'
<< "recipe_hash=" << Hex64(planned->recipe.contentHash)
<< " plan_hash=" << Hex64(planned->plan.contentHash)
<< " manifest_hash=" << Hex64(planned->contentHash)
<< " prior_m1_hash=" << oldHashes.round1Manifest
<< " prior_surfaceworld_hash=" << oldHashes.surfaceWorldRound4Manifest
<< " islands=" << planned->plan.islands.size()
<< " connectors=" << planned->plan.clusterEdges.size()
<< " route_points=" << planned->plan.route.size()
<< " planning_ms=" << std::chrono::duration<double, std::milli>(endPlanning - beginPlanning).count() << '\n'
<< "bounds_pass=" << bounds.passed << " boxes=" << bounds.boxesTested
<< " point_samples=" << bounds.pointSamples << " unknown=" << bounds.unknownBounds
<< " missed=" << bounds.missedBounds << " void_air=" << bounds.voidBoxesProvedAir
<< " negative_control=" << bounds.negativeControlDetected << '\n'
<< "route_pass=" << route.passed << " segments=" << route.segmentsTested
<< " centers=" << route.centerSamples << " capsule_samples=" << route.capsuleFieldSamples
<< " blocked=" << route.blockedCapsuleSamples << " support_probes=" << route.supportProbeSamples
<< " unsupported=" << route.unsupportedCenterSamples << " grade_failures=" << route.gradeFailures
<< " clearance_q=" << route.minimumClearanceSubvoxels << '\n'
<< "raised_route_detected=" << (!raisedRoute.passed && raisedRoute.unsupportedCenterSamples > 0)
<< " unsupported=" << raisedRoute.unsupportedCenterSamples
<< " blocked=" << raisedRoute.blockedCapsuleSamples << '\n'
<< "thickness_pass=" << thickness.passed << " islands=" << thickness.islandsTested
<< " min_depth_diameter=" << thickness.minimumDepthToDiameterRatio
<< " threshold=" << thickness.requiredDepthToDiameterRatio
<< " thin_pad_detected=" << (!thinThickness.passed
&& thinThickness.islandsBelowMinimum == thinThickness.islandsTested)
<< " thin_ratio=" << thinThickness.minimumDepthToDiameterRatio << '\n'
<< "field_hashes W1=" << w1.hash << " W8=" << w8.hash << " W16=" << w16.hash
<< " legacy_ms=" << w16.legacyMilliseconds << " native_ms=" << w16.nativeMilliseconds
<< " mismatch_percent=" << (100.0 * w16.signMismatches / 32768.0)
<< " legacy_solid=" << w16.legacySolidSamples << " native_solid=" << w16.nativeSolidSamples
<< " legacy_only=" << w16.legacyOnlySolidSamples << " native_only=" << w16.nativeOnlySolidSamples << '\n'
<< "mesh_samples=" << meshes[0].gridSamples << ',' << meshes[1].gridSamples
<< ',' << meshes[2].gridSamples << ',' << meshes[3].gridSamples
<< " triangles=" << meshes[0].triangles << ',' << meshes[1].triangles
<< ',' << meshes[2].triangles << ',' << meshes[3].triangles << '\n'
<< "audit_json=" << (outputDir / "audit.json").string() << '\n';
return 0;
}
catch (const std::exception& exception)
{
std::cerr << "FLOATING_ISLAND_AUDIT_FAIL: " << exception.what() << '\n';
return 1;
}
}
@@ -0,0 +1,83 @@
# World assembler M3 round 3
Date: 2026-10-10
## Status
The island recipe, manifest import, BuildHost world proof, and matched offline captures are in place. The final profiling pass found a remaining LOD0 sprint budget miss, and a follow-up XY-column cache was then added to `FloatingIslandsInternal.h`. That last source edit has **not** been compiled or exercised: the command runner began failing to start with Windows status `0xC0000142` after a completed profile run. Therefore the verified BuildHost results below apply to the root-parameter-cache version immediately before the XY-column-cache edit, not to the current tree. Treat round 3 as **not fully verified / do not hand off the current binary yet** until that edit is either verified or reverted and rebuilt.
## Scale contract
The owner's legacy voxel size is **12.5 cm** (`Source/VoxelForge/Public/VoxelTypes.h`, `VOXEL_SIZE`). Core operates at **25 cm**, so owner-to-Core lengths multiply by **0.5** and Core-to-owner lengths multiply by **2**. Island radii are 24–48 owner voxels = 3–6 m = 12–24 Core voxels. Site spacing is 112 owner voxels = 14 m = 56 Core voxels. Standalone's old 25 cm metadata was not authoritative; the import validator and audit metadata now pin 12.5/25 cm and the 1:2 ratio. The radius comment is corrected to this contract.
## Shape review and measurements
Round 3's downscaled, matched offline captures show four distinct masses with sky visible between them; the three connectors remain traversable and read as narrow spans. The underside center is a shaded solid buttress/dimple, not a through-hole. The edge capture does not show the former sawtooth rim; the crust edge is smooth at the displayed scale. These are offline CPU renders, not captures from the owner's Unreal viewport.
At the sampled mid-height plane, the open-sky measure is **1,462 / 1,591 = 91.89%** of the cluster XY bounding box. Sampling is at 1 m spacing, Core Z `-964.039`. Island radii are 3–6 m; connector full width is 1.25 m, thickness 1 m, blend 0.75 m, and roughness 0.25 m. The plan has four islands, three connectors, and 28 route segments. The route audit checks 68,816 capsule samples and reports no blocked, unsupported, or over-grade samples; minimum clearance is 99 subvoxel samples.
The mass audit checked 515 bounds / 13,851 samples with no missed island bounds. The thin-pad negative control was caught on all four islands (measured ratio 0.2999 against required 0.70; passing minimum was 0.75). The raised-route negative control was also caught. The underside audit samples the center column at nine points; rerun the audit after resolving the unverified cache edit to retain that numeric output in `results/audit.json`.
## Import and season hashing
Import `Standalone/Research/WorldAssemblerM3/Round3/results/world_assembly_floating_islands.json` through the existing `UVoxelSettings` manifest JSON import (`SourceWorldAssemblyManifestJson` / **Import World Assembly Manifest JSON**). Then assign that settings asset to `AVoxelWorld` and enable **Voxel World → Core → Use VoxelForge Core** (`bUseVoxelForgeCore`). The recipe remains on the Core world-assembly route. Its assembly content hash enters the season hash only when Core is enabled; Core-off keeps the legacy season identity. Water remains fixed for the season and player edits remain unrestricted.
Verified artifact hashes:
- Plan: `61fd58f65c5fe157`
- Recipe: `ef1340d32b072ba3`
- Nested floating-island manifest: `54160b4d25d0279f`
- World assembly content: `94b563f1ce622def`
- W1/W8/W16 standalone determinism: `5764b702482740e8`
## BuildHost proof recorded before the final unverified edit
BuildHost runs used `Tools/BuildVoxelForgeCoreHost.ps1` and the test scripts with a 900 second timeout, BelowNormal priority, and at most four parallel build actions. No owner project/editor was opened or built. Measured process peaks stayed below 4 GB. The passing island run recorded:
- 2,553 standalone tile hashes matched; Core reported 512 LOD0 tiles, 448 LOD1 tiles, 432 LOD transitions, 573 local-coverage checks, and 3,438 neighbor-face checks.
- Aggregate hashes: density `e46d4280f73f03f3`, mesh `a7c92c6ce22ba08b3`, payload `040e800d58e1c2f1`, topology `da689102032f94da`.
- Coverage completed; collision was cooked and capsule contact passed on both an island top and a span.
- Height/anchor agreement passed for the island and span; content anchors reported 14 trees and no floaters. Corrected strate and atmosphere strate both reported 1; water was enabled at local `-49,376 cm` and unchanged.
- Blueprint edit passed: carve/fill on an island with legacy radius 6 (=75 cm) and strength 10; the 90 cm outside sample had zero density delta.
- Core-off passed with legacy density unchanged; the Core-only hash was omitted while Core was off.
- SurfaceWorld M1 regression test passed with the original worker hash `0x8cc18e2981c50c4e`; HybridV1 and HybridV2 M2 recipe fixtures passed.
Logs are in `results/m1_surfaceworld_buildhost.*`, `results/m2_hybrid_v1_buildhost.*`, `results/m2_hybrid_v2_buildhost.*`, and `results/m3_floating_islands_buildhost.*`. These pre-profile logs do not prove the final XY-column-cache edit.
## Streaming profile and remaining work
The final instrumented BuildHost commandlet completed and its internal validator printed PASS. The outer PowerShell wrapper then exited 1 because its console-title channel failed (`0xE9`); this is recorded as a wrapper failure despite the commandlet pass marker. The timing profile showed density/meshing dominates; classifier and column work were negligible.
The same measured per-tile p95 is compared with the available time at each movement speed: at 600 cm/s the deadlines are 1,333.333 ms for LOD0 and 2,666.667 ms for LOD1; at 6,000 cm/s they are 133.333 ms and 266.667 ms, respectively. Core and legacy timings from the instrumented run were:
| LOD | Tile kind | Core p95 | Legacy p95 | Walk deadline / result | Sprint deadline / result |
|---|---|---:|---:|---|---|
| 0 | Air | 0.017 ms | 38.842 ms | 1,333.333 ms / pass | 133.333 ms / pass |
| 0 | Island | 157.671 ms | 43.702 ms | 1,333.333 ms / pass | 133.333 ms / 24.338 ms late |
| 0 | Mixed/span | 153.082 ms | 42.146 ms | 1,333.333 ms / pass | 133.333 ms / 19.749 ms late |
| 1 | Air | 0.009 ms | 46.549 ms | 2,666.667 ms / pass | 266.667 ms / pass |
| 1 | Mixed/span | 151.685 ms | 42.701 ms | 2,666.667 ms / pass | 266.667 ms / pass |
LOD1 mixed was 151.685 ms against its 266.667 ms sprint deadline; LOD1 air was 0.009 ms. The root-parameter cache preserved hashes but did not clear the LOD0 sprint miss. The XY-column cache was added afterward to target the measured mesher/density hot path; it has not been compiled or measured. Resolve that before claiming the streaming goal complete.
## Run ledger and incidents
- SurfaceWorld M1, HybridV1 M2, HybridV2 M2, and the island BuildHost proof all passed on the verified source state described above.
- Earlier island BuildHost attempts, in order, failed as follows: (1) desired tiles were 2,227 instead of 2,553 because the route player was absent; (2) the GameMode spawned an origin pawn; (3) possession caused 2,557 desired tiles because dynamic collision support was active; (4) the support tile key did not match; (5) disabling the collision gate restored 2,553 tiles but exposed a hash mismatch at `(-2,0,-31)`; (6) `CreateLocalPlayer(true)` hung and was manually stopped after roughly 3–4 minutes; (7) the base UPlayer fixture lacked controller registration/root transform and failed preflight after 5:10; (8) explicit `AddController`, root setup, and `SetActorLocation` fixed the fixture and the island proof passed. The manual stop was not a crash.
- Earlier compile failures included an invalid `DuplicateObject` overload, private helper access, an unresolved `GetEffectiveWorldSeed` export, and integer narrowing. They were corrected; subsequent builds passed.
- One initial standalone build used the wrong `-Workers` switch and failed immediately; `-CompileWorkers 4` succeeded.
- M1 threshold experiments before restoring the original chunk mapping failed at LOD8 edge bounds: threshold 4.0 measured 52,075.827 > 51,200 at tile `(2,0,-1)`; 4.125 measured 53,960.427 > 52,800; 4.25 measured 54,806.919 > 54,400. Threshold 4.5 passed that edge check but left no full-span LOD0 candidate. Restoring SurfaceWorld's original 32-voxel chunk mapping, using the separate 16-Core-voxel island mapping, and restoring threshold 4.0 made M1 pass.
- The last commandlet itself passed, but the wrapper returned 1 on console error `0xE9`. Subsequent PowerShell and `cmd.exe` launches returned `0xC0000142`; the requested retry was made, then additional shell inspection launches also failed. No further build was attempted. No new `UECC-*` folder was reported by the latest BuildHost crash check; only historical folders dated 2026-09-17 and 2026-10-08 were present.
- No `0xC0000142` BuildHost retry was observed in the completed runs. Peak recorded BuildHost test process memory was about 3.98 GB.
## Owner hand-off
After the final cache edit is verified, import the manifest above into `UVoxelSettings`, assign the settings asset to the world, and turn on `bUseVoxelForgeCore`. To find the cluster, start at the actor-local route point `(-380, 631.8, -23,837) cm` and follow the route toward the middle island (roughly +X / -Y). The center island is near actor-local `(600, -306, -23,872) cm`; the span contact is near `(113, 161, -23,860) cm`.
For an underside view, use a free camera near `(600, -306, -24,750) cm`, looking upward toward `(600, -306, -24,300) cm`. For an overview, start near `(680, -5,000, -21,500) cm` and look toward `(678, -556, -24,100) cm`. These are actor-local suggestions; apply the actor transform in the level. The contact sheet and matched captures are under this Round3 `results/` directory and are downscaled offline renders; BuildHost validation uses null RHI.
Likely Unreal compile spots are `VoxelSettings.h/.cpp`, `VoxelContentManager.h/.cpp`, `VoxelWorld.cpp`, `VoxelForgeCoreAdapter.cpp`, and `VoxelForgeExploreCommandlet.cpp`; canonical Core changes are in `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`, `Standalone/Core/src/ShapeWorldInternal.h`, `FloatingIslandsInternal.h`, and `WorldAssemblerInternal.h`.
## Source and artifact inventory
Round 3 adds these research/support sources: `Standalone/Research/WorldAssemblerM3/Round3/FloatingIslandsAudit.cpp`, `BuildFloatingIslandsAudit.ps1`, and `RenderFloatingIslandRound3.py`. Existing source edits include `Standalone/Source/OwnerWorldLoader.h`, `Standalone/Core/src/FloatingIslandsInternal.h`, `Standalone/Core/src/ShapeWorldInternal.h`, and `Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp`. There is no new Unreal runtime source file for the manifest path. BuildHost scripts and `.log`/capture/JSON outputs are evidence/support artifacts.
@@ -0,0 +1,306 @@
#!/usr/bin/env python3
"""Small CPU-only matched views of the legacy and native FloatingIslands fields."""
from __future__ import annotations
import ctypes
import json
import math
import struct
from pathlib import Path
from PIL import Image, ImageDraw
ROOT = Path(__file__).resolve().parent
RESULTS = ROOT / "results"
CAPTURES = RESULTS / "captures"
CAPTURES.mkdir(parents=True, exist_ok=True)
WIDTH, HEIGHT = 320, 226
BACKGROUND = (236, 240, 242)
LEGACY_COLOR = (133, 127, 105)
NATIVE_COLOR = (78, 135, 151)
ROUTE_COLOR = (223, 72, 45)
TILE_COLOR = (178, 60, 52)
LOD_COLOR = (211, 136, 45)
Q = 256.0
def lower_priority() -> None:
if hasattr(ctypes, "windll"):
ctypes.windll.kernel32.SetPriorityClass(ctypes.windll.kernel32.GetCurrentProcess(), 0x00004000)
def read_mesh(path: Path):
raw = path.read_bytes()
if len(raw) < 8 or raw[:4] != b"VFMI":
raise RuntimeError(f"invalid field mesh: {path}")
count = struct.unpack_from("<I", raw, 4)[0]
stride = struct.calcsize("<12f")
if len(raw) != 8 + count * stride:
raise RuntimeError(f"truncated field mesh: {path}")
triangles = [struct.unpack_from("<12f", raw, 8 + i * stride) for i in range(count)]
# Average the sampled field gradients at shared vertices so the review image does not turn
# marching-tetrahedra face normals into false sawteeth. Lissage des normales partagees pour
# separer l'aspect de l'ombrage de la forme reelle du champ.
shared = {}
for tri in triangles:
normal = tri[9:12]
for offset in (0, 3, 6):
key = tuple(round(value, 5) for value in tri[offset:offset + 3])
previous = shared.get(key, (0.0, 0.0, 0.0))
shared[key] = tuple(previous[axis] + normal[axis] for axis in range(3))
smooth_triangles = []
for tri in triangles:
vertex_normals = []
for offset in (0, 3, 6):
key = tuple(round(value, 5) for value in tri[offset:offset + 3])
vertex_normals.extend(normalize(shared[key]))
smooth_triangles.append(tuple(tri[:9]) + tuple(vertex_normals))
triangles = smooth_triangles
vertices = [tri[0:3] for tri in triangles]
vertices.extend(tri[3:6] for tri in triangles)
vertices.extend(tri[6:9] for tri in triangles)
return triangles, vertices
def normalize(vector):
size = math.sqrt(sum(value * value for value in vector))
return tuple(value / size for value in vector) if size > 1.0e-12 else (0.0, 0.0, 1.0)
def dot(a, b):
return sum(x * y for x, y in zip(a, b))
def cross(a, b):
return (a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0])
def project(points, target, view_dir, scale=None):
direction = normalize(view_dir)
forward = tuple(-value for value in direction)
reference_up = (0.0, 1.0, 0.0) if abs(forward[2]) > 0.98 else (0.0, 0.0, 1.0)
right = normalize(cross(forward, reference_up))
up = normalize(cross(right, forward))
camera_points = []
for point in points:
delta = tuple(point[i] - target[i] for i in range(3))
camera_points.append((dot(delta, right), dot(delta, up), dot(delta, forward)))
if scale is None:
span_x = max((p[0] for p in camera_points), default=1.0) - min((p[0] for p in camera_points), default=0.0)
span_y = max((p[1] for p in camera_points), default=1.0) - min((p[1] for p in camera_points), default=0.0)
scale = 0.86 * min((WIDTH - 30) / max(span_x, 1.0e-6), (HEIGHT - 30) / max(span_y, 1.0e-6))
pixels = [
(WIDTH * 0.5 + point[0] * scale, 31 + (HEIGHT - 31) * 0.5 - point[1] * scale, point[2])
for point in camera_points
]
return pixels, scale, right, up, forward
def common_scale(mesh_pair, target, view_dir):
vertices = mesh_pair[0][1] + mesh_pair[1][1]
_, scale, _, _, _ = project(vertices, target, view_dir)
return scale
def projected_line(a, b, target, basis, scale):
right, up, forward = basis
points = []
for point in (a, b):
delta = tuple(point[i] - target[i] for i in range(3))
points.append((WIDTH * 0.5 + dot(delta, right) * scale,
31 + (HEIGHT - 31) * 0.5 - dot(delta, up) * scale))
return points
def render(mesh, mesh_pair, method, target, view_dir, title, subtitle, *, route=None,
guide=None, camera_eye=None, framing_radius=None):
triangles, vertices = mesh
perspective = camera_eye is not None
if perspective:
forward = normalize(tuple(target[i] - camera_eye[i] for i in range(3)))
reference_up = (0.0, 1.0, 0.0) if abs(forward[2]) > 0.98 else (0.0, 0.0, 1.0)
right = normalize(cross(forward, reference_up))
up = normalize(cross(right, forward))
scale = WIDTH * 0.91
origin = camera_eye
else:
if framing_radius is None:
scale = common_scale(mesh_pair, target, view_dir)
else:
scale = 0.86 * min((WIDTH - 30) / (2.0 * framing_radius),
(HEIGHT - 30) / (2.0 * framing_radius))
_, _, right, up, forward = project((), target, view_dir, scale)
origin = target
def screen(point):
delta = tuple(point[i] - origin[i] for i in range(3))
cx, cy, depth = dot(delta, right), dot(delta, up), dot(delta, forward)
if perspective:
if depth <= 1.0:
return None
return (WIDTH * 0.5 + cx * scale / depth,
31 + (HEIGHT - 31) * 0.5 - cy * scale / depth, depth)
return (WIDTH * 0.5 + cx * scale,
31 + (HEIGHT - 31) * 0.5 - cy * scale, depth)
background = Image.new("RGB", (WIDTH, HEIGHT + 32), BACKGROUND)
draw = ImageDraw.Draw(background)
draw.rectangle((0, 0, WIDTH - 1, 31), fill=(34, 46, 54))
draw.text((8, 4), title, fill=(250, 252, 253))
draw.text((8, 17), subtitle, fill=(190, 207, 215))
pixels = background.load()
zbuffer = [float("inf")] * (WIDTH * (HEIGHT - 31))
camera_fill = forward if perspective else normalize(view_dir)
light = normalize((-0.25 + 1.2 * camera_fill[0],
-0.35 + 1.2 * camera_fill[1],
0.45 + 1.2 * camera_fill[2]))
base = LEGACY_COLOR if method == "legacy" else NATIVE_COLOR
for tri in triangles:
points = [screen(tri[offset:offset + 3]) for offset in (0, 3, 6)]
if any(point is None for point in points):
continue
x0 = max(0, int(math.floor(min(p[0] for p in points))))
x1 = min(WIDTH - 1, int(math.ceil(max(p[0] for p in points))))
y0 = max(31, int(math.floor(min(p[1] for p in points))))
y1 = min(HEIGHT - 1, int(math.ceil(max(p[1] for p in points))))
if x0 > x1 or y0 > y1:
continue
(ax, ay, az), (bx, by, bz), (cx, cy, cz) = points
denominator = (by - cy) * (ax - cx) + (cx - bx) * (ay - cy)
if abs(denominator) < 1.0e-10:
continue
normals = [normalize(tri[offset:offset + 3]) for offset in (9, 12, 15)]
for y in range(y0, y1 + 1):
row = (y - 31) * WIDTH
for x in range(x0, x1 + 1):
sx, sy = x + 0.5, y + 0.5
u = ((by - cy) * (sx - cx) + (cx - bx) * (sy - cy)) / denominator
v = ((cy - ay) * (sx - cx) + (ax - cx) * (sy - cy)) / denominator
w = 1.0 - u - v
if u < -1e-8 or v < -1e-8 or w < -1e-8:
continue
if perspective:
depth = 1.0 / (u / az + v / bz + w / cz)
else:
depth = u * az + v * bz + w * cz
normal = normalize(tuple(u * normals[0][axis] + v * normals[1][axis]
+ w * normals[2][axis] for axis in range(3)))
shade = 0.50 + 0.50 * max(0.0, dot(normal, light))
color = tuple(max(0, min(255, int(channel * shade))) for channel in base)
index = row + x
if depth < zbuffer[index]:
zbuffer[index] = depth
pixels[x, y] = color
basis = (right, up, forward)
if guide:
origin = guide["origin_voxels"]
top = guide["top_voxels"]
for x_delta, color, label in ((32.0, TILE_COLOR, "32v tile"), (64.0, LOD_COLOR, "64v LOD1")):
line = projected_line((origin[0] + x_delta, origin[1] - 190.0, top - 65.0),
(origin[0] + x_delta, origin[1] + 190.0, top + 14.0),
target, basis, scale)
draw.line(line, fill=color, width=2)
draw.text((max(2, min(WIDTH - 55, int(line[1][0]) + 2)),
max(33, min(HEIGHT - 11, int(line[1][1]) - 12))), label, fill=color)
if route:
points = [screen(point) for point in route]
points = [point[:2] for point in points if point is not None]
if len(points) >= 2:
draw.line(points, fill=(250, 248, 239), width=5, joint="curve")
draw.line(points, fill=ROUTE_COLOR, width=3, joint="curve")
for index in (0, len(points) - 1):
x, y = points[index]
draw.ellipse((x - 4, y - 4, x + 4, y + 4), fill=(250, 248, 239), outline=ROUTE_COLOR, width=2)
return background
def titled_pair(left, right, category):
pair = Image.new("RGB", (WIDTH * 2, HEIGHT + 32), (220, 226, 229))
pair.paste(left, (0, 0))
pair.paste(right, (WIDTH, 0))
draw = ImageDraw.Draw(pair)
draw.rectangle((0, 0, WIDTH * 2 - 1, HEIGHT + 31), outline=(72, 84, 91), width=1)
draw.text((8, HEIGHT + 14), f"{category} | same target, camera and scale", fill=(45, 55, 61))
return pair
def main():
lower_priority()
data = json.loads((RESULTS / "audit.json").read_text(encoding="utf-8"))
anchor = data["comparison_anchor_core_voxels"]
route = data["route_points_core_voxels"]
profiles = data["island_profiles"]
middle_island = profiles[1]
cluster = {
"legacy": read_mesh(RESULTS / "cluster_legacy.vfmesh"),
"native": read_mesh(RESULTS / "cluster_native.vfmesh"),
}
detail = {
"legacy": read_mesh(RESULTS / "detail_legacy.vfmesh"),
"native": read_mesh(RESULTS / "detail_native.vfmesh"),
}
a = anchor
scenes = (
("overview", "Cluster overview", cluster, (a[0], a[1], a[2] - 28.0), (8.0, -10.0, 7.0), None, None),
("underside", "Cluster underside and lobed roots", detail,
(middle_island["center_voxels"][0], middle_island["center_voxels"][1],
middle_island["bottom_voxels"] - 6.0), (0.12, -0.08, -1.0), None, None),
("edge_cliff", "Thick crust and bounded rock detail", detail,
(middle_island["center_voxels"][0], middle_island["center_voxels"][1],
middle_island["top_voxels"] - middle_island["radius_voxels"] * 0.65),
(1.0, -0.25, 0.18), None, None),
("route", "Supported route across the cluster", cluster,
(middle_island["center_voxels"][0], middle_island["center_voxels"][1],
middle_island["top_voxels"] - 18.0), (8.0, -10.0, 7.0), route, None),
)
pairs = []
for key, title, meshes, target, camera, path, grid in scenes:
framing_radius = 28.0 if key in ("underside", "edge_cliff") else None
left = render(meshes["legacy"], (meshes["legacy"], meshes["native"]), "legacy",
target, camera, title, "Legacy bridge | exact owner field", route=None,
guide=grid, framing_radius=framing_radius)
route_caption = "Core native | planner route" if path else "Core native | matched field view"
right = render(meshes["native"], (meshes["legacy"], meshes["native"]), "native",
target, camera, title, route_caption, route=path, guide=grid,
framing_radius=framing_radius)
pair = titled_pair(left, right, title)
out = CAPTURES / f"{key}_matched.jpg"
pair.save(out, quality=82, optimize=True)
pairs.append(pair)
print(f"capture={out.name} bytes={out.stat().st_size} size={pair.size[0]}x{pair.size[1]}")
eye_index = 5
eye_route = route[eye_index]
eye = (eye_route[0], eye_route[1], eye_route[2] + 3.68)
next_island = profiles[1]
look = (next_island["center_voxels"][0], next_island["center_voxels"][1],
next_island["top_voxels"] + 5.0)
perspective_pair = []
for method, meshes in (("legacy", cluster), ("native", cluster)):
perspective_pair.append(render(meshes[method], (meshes["legacy"], meshes["native"]), method,
look, (0.0, 0.0, -1.0), "Island-edge player view", "Eye at 1.8 m | route span to next island",
route=None, camera_eye=eye))
eye_pair = titled_pair(perspective_pair[0], perspective_pair[1], "Island-edge player view")
eye_path = CAPTURES / "player_eye_matched.jpg"
eye_pair.save(eye_path, quality=80, optimize=True)
pairs.insert(3, eye_pair)
print(f"capture={eye_path.name} bytes={eye_path.stat().st_size} size={eye_pair.size[0]}x{eye_pair.size[1]}")
sheet = Image.new("RGB", (WIDTH * 2, (HEIGHT + 32) * len(pairs)), (218, 225, 228))
for row, pair in enumerate(pairs):
sheet.paste(pair, (0, row * (HEIGHT + 32)))
sheet_path = CAPTURES / "contact_sheet.jpg"
sheet.save(sheet_path, quality=76, optimize=True)
oversized = [(path.name, path.stat().st_size) for path in sorted(CAPTURES.glob("*.jpg"))
if path.stat().st_size >= 300_000]
for path in sorted(CAPTURES.glob("*.jpg")):
print(f"capture_size={path.name} bytes={path.stat().st_size}")
if oversized:
raise RuntimeError(f"captures exceed the 300 KB limit: {oversized}")
print(f"contact_sheet={sheet_path.name} bytes={sheet_path.stat().st_size} views=5 matched_pairs=5")
if __name__ == "__main__":
main()
@@ -0,0 +1,30 @@
{
"recipe_hash":"ef1340d32b072ba3",
"plan_hash":"61fd58f65c5fe157",
"manifest_hash":"54160b4d25d0279f",
"unchanged_existing_hashes":{"m1_round1_manifest":"c87ae279b0e26f53","surfaceworld_round4_manifest":"8933693d9560ba9f"},
"geometry_version":2,
"owner_voxel_size_cm":12.5,
"core_voxel_size_cm":25,
"owner_to_core_length_scale":0.5,
"plan_islands":4,
"mid_height_open_sky":{"sampling_step_m":1.0,"z_core_voxels":-964.0390625,"bounds_xy_core_voxels":[-45.58203125,-107.33984375,99.73828125,62.82421875],"open_samples":1462,"total_samples":1591,"open_fraction":0.918918918919},
"plan_connectors":3,
"route_points":29,
"legacy_anchor_owner_voxels":[48,16,-1904.03515625],
"comparison_anchor_core_voxels":[24,8,-952.015625],
"legacy_anchor_plateau_samples":25,
"recipe_parameters":{"seed":0,"primary_island_count":3,"density_q16":32768,"spacing_voxels":56,"radius_voxels":[12,24],"thickness_ratio_q16":39322,"vertical_jitter_q16":39322,"top_flatten_q16":39322,"contour_warp_voxels":6.30078125,"crust_depth_bonus_q16":9830,"root_depth_ratio_q16":49152,"roughness_voxels":1,"blend_voxels":3,"bridge_half_width_voxels":2.5,"bridge_thickness_voxels":4},
"stable_island_ids":["e6e461e18b3a9c87","2bc1820baba047f6","adc259cbe2a395e9","7855fb022cc561c9"],
"stable_connector_ids":["eaa5dd310e846520","034023599b466c51","4962fa885545fb26"],
"island_profiles":[{"id":"e6e461e18b3a9c87","ordinal":0,"center_voxels":[-15.19921875,25.2734375,-960.5078125],"radius_voxels":14.08203125,"top_voxels":-957.69140625,"bottom_voxels":-978.81640625,"depth_to_diameter":0.750069348128},{"id":"2bc1820baba047f6","ordinal":1,"center_voxels":[24,-12.2265625,-954.8984375],"radius_voxels":14.42578125,"top_voxels":-952.015625,"bottom_voxels":-973.65625,"depth_to_diameter":0.75006769564},{"id":"adc259cbe2a395e9","ordinal":2,"center_voxels":[63.19921875,26.28515625,-955.57421875],"radius_voxels":20.23828125,"top_voxels":-951.52734375,"bottom_voxels":-981.88671875,"depth_to_diameter":0.750048253233},{"id":"7855fb022cc561c9","ordinal":3,"center_voxels":[-1.19921875,-68.2265625,-950.75390625],"radius_voxels":22.8125,"top_voxels":-946.19140625,"bottom_voxels":-980.41015625,"depth_to_diameter":0.75}],
"bounds":{"passed":true,"negative_control_detected":true,"boxes":515,"samples":13851,"unknown":72,"missed":0,"island_boxes":4,"connector_boxes":3,"void_boxes_proved_air":1},
"thickness":{"passed":true,"islands_tested":4,"islands_below_minimum":0,"minimum_depth_to_diameter_ratio":0.75,"required_depth_to_diameter_ratio":0.7,"minimum_island_id":"7855fb022cc561c9"},
"thin_pad_negative_control":{"detected":true,"failed_islands":4,"minimum_depth_to_diameter_ratio":0.299891686975},
"route":{"passed":true,"segments":28,"centers":253,"capsule_samples":68816,"blocked":0,"support_samples":1344,"unsupported":0,"grade_failures":0,"minimum_clearance_subvoxels":99},
"raised_route_negative_control":{"detected":true,"passed":false,"unsupported":253,"blocked":0},
"determinism":{"W1":"5764b702482740e8","W8":"5764b702482740e8","W16":"5764b702482740e8","plan_repeat":true,"hash_samples":32768},
"timing":{"same_queries":32768,"legacy_ms":64.3305,"native_ms":46.2981,"native_over_legacy":0.71969128174,"sign_mismatches":1473,"sign_mismatch_percent":4.49523925781,"legacy_solid_samples":100,"native_solid_samples":1399,"legacy_only_solid_samples":87,"native_only_solid_samples":1386},
"mesh":[{"name":"cluster_legacy","grid_samples":34686,"triangles":1512,"bytes":72584},{"name":"cluster_native","grid_samples":34686,"triangles":8312,"bytes":398984},{"name":"detail_legacy","grid_samples":207025,"triangles":4124,"bytes":197960},{"name":"detail_native","grid_samples":207025,"triangles":26122,"bytes":1253864}],
"route_points_core_voxels":[[-15.19921875,25.2734375,-953.48046875],[-12.3984375,22.59375,-953.57421875],[-9.59765625,19.91796875,-952.78515625],[-6.80078125,17.23828125,-952.28515625],[-4,14.55859375,-951.765625],[-1.19921875,11.87890625,-951.25],[1.6015625,9.203125,-950.66015625],[4.40234375,6.5234375,-950.12890625],[7.19921875,3.84375,-949.6015625],[10,1.16796875,-948.97265625],[12.80078125,-1.51171875,-948.3203125],[15.6015625,-4.19140625,-947.796875],[18.3984375,-6.87109375,-947.4375],[21.19921875,-9.546875,-947.9296875],[24,-12.2265625,-947.890625],[26.80078125,-9.4765625,-948.04296875],[29.6015625,-6.7265625,-947.046875],[32.3984375,-3.97265625,-947.21875],[35.19921875,-1.22265625,-947.36328125],[38,1.52734375,-947.37890625],[40.80078125,4.27734375,-947.3125],[43.6015625,7.03125,-947.15234375],[46.3984375,9.78125,-947.1953125],[49.19921875,12.53125,-947.16015625],[52,15.28125,-947.03125],[54.80078125,18.03125,-946.94921875],[57.59765625,20.78515625,-947.55078125],[60.3984375,23.53515625,-947.46875],[63.19921875,26.28515625,-947.34765625]]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@@ -0,0 +1 @@
{"schema_version":1,"content_hash":"54160b4d25d0279f","recipe":{"schema_version":1,"units_per_voxel":256,"owner_voxel_size_microns":125000,"core_voxel_size_microns":250000,"owner_to_core_scale_numerator":1,"owner_to_core_scale_denominator":2,"seed":0,"origin_x_q":6144,"origin_y_q":2048,"center_top_z_q":-243716,"layout_min_z_q":-264196,"layout_max_z_q":-231428,"primary_island_count":3,"island_density_q16":32768,"spacing_q":14336,"minimum_radius_q":3072,"maximum_radius_q":6144,"thickness_ratio_q16":39322,"vertical_jitter_q16":39322,"top_flatten_q16":39322,"taper_end_min_q16":29491,"taper_end_max_q16":45875,"geometry_version":2,"crust_depth_bonus_q16":9830,"root_depth_ratio_q16":49152,"blend_radius_q":768,"contour_warp_q":1613,"roughness_q":256,"boundary_seal_q":512,"bridge_half_width_q":640,"bridge_thickness_q":1024,"route_constraints":{"version":1,"capsule_radius_q":348,"capsule_height_q":1802,"maximum_grade_q16":17560,"sweep_spacing_q":128,"support_probe_radius_q":230,"support_search_depth_q":512,"minimum_clearance_q":128,"minimum_support_probes":3},"content_hash":"ef1340d32b072ba3"},"plan":{"content_hash":"61fd58f65c5fe157","islands":[{"stable_id":"e6e461e18b3a9c87","ordinal":0,"center_q":[-3891,6470,-245890],"radius_q":3605,"top_half_q":721,"top_z_q":-245169,"bottom_z_q":-250577,"taper_end_q16":41663,"bounds_min_q":[-11669,-1308,-253137],"bounds_max_q":[3887,14248,-242609]},{"stable_id":"2bc1820baba047f6","ordinal":1,"center_q":[6144,-3130,-244454],"radius_q":3693,"top_half_q":738,"top_z_q":-243716,"bottom_z_q":-249256,"taper_end_q16":33506,"bounds_min_q":[-1722,-10996,-251816],"bounds_max_q":[14010,4736,-241156]},{"stable_id":"adc259cbe2a395e9","ordinal":2,"center_q":[16179,6729,-244627],"radius_q":5181,"top_half_q":1036,"top_z_q":-243591,"bottom_z_q":-251363,"taper_end_q16":39164,"bounds_min_q":[6825,-2625,-253923],"bounds_max_q":[25533,16083,-241031]},{"stable_id":"7855fb022cc561c9","ordinal":3,"center_q":[-307,-17466,-243393],"radius_q":5840,"top_half_q":1168,"top_z_q":-242225,"bottom_z_q":-250985,"taper_end_q16":33440,"bounds_min_q":[-10320,-27479,-253545],"bounds_max_q":[9706,-7453,-239665]}],"cluster_edges":[{"stable_id":"eaa5dd310e846520","from_island_id":"e6e461e18b3a9c87","to_island_id":"2bc1820baba047f6","start_q":[-2536,5174,-245089],"end_q":[4756,-1803,-243568],"half_width_q":640,"thickness_q":1024,"bounds_min_q":[-4200,-3467,-249185],"bounds_max_q":[6420,6838,-242544],"sharp_tag_ready":true},{"stable_id":"034023599b466c51","from_island_id":"2bc1820baba047f6","to_island_id":"adc259cbe2a395e9","start_q":[7514,-1784,-243592],"end_q":[14257,4841,-243505],"half_width_q":640,"thickness_q":1024,"bounds_min_q":[5850,-3448,-247688],"bounds_max_q":[15921,6505,-242481],"sharp_tag_ready":true},{"stable_id":"4962fa885545fb26","from_island_id":"2bc1820baba047f6","to_island_id":"7855fb022cc561c9","start_q":[5356,-4881,-243607],"end_q":[939,-14697,-242382],"half_width_q":640,"thickness_q":1024,"bounds_min_q":[-725,-16361,-247703],"bounds_max_q":[7020,-3217,-241358],"sharp_tag_ready":true}],"route":[{"feature_id":"2e805a1f1f778cbb","position_q":[-3891,6470,-244091],"radius_q":1024},{"feature_id":"5be3b2b1e01722ea","position_q":[-3174,5784,-244115],"radius_q":1024},{"feature_id":"ab4eda612c1f03b5","position_q":[-2457,5099,-243913],"radius_q":1024},{"feature_id":"ba2fd2b050d0dd8c","position_q":[-1741,4413,-243785],"radius_q":1024},{"feature_id":"fc5829ee129fdc4f","position_q":[-1024,3727,-243652],"radius_q":1024},{"feature_id":"1393bb00329a9d5e","position_q":[-307,3041,-243520],"radius_q":1024},{"feature_id":"1dabc9715c93e2c9","position_q":[410,2356,-243369],"radius_q":1024},{"feature_id":"0ea2008a85e73050","position_q":[1127,1670,-243233],"radius_q":1024},{"feature_id":"754f55a5f1465cf3","position_q":[1843,984,-243098],"radius_q":1024},{"feature_id":"1b6752776b4a7d22","position_q":[2560,299,-242937],"radius_q":1024},{"feature_id":"0ab3b704db76084d","position_q":[3277,-387,-242770],"radius_q":1024},{"feature_id":"bd6980911e0879c4","position_q":[3994,-1073,-242636],"radius_q":1024},{"feature_id":"ad164d7d0c049d87","position_q":[4710,-1759,-242544],"radius_q":1024},{"feature_id":"9661463acbada2d6","position_q":[5427,-2444,-242670],"radius_q":1024},{"feature_id":"280a96547fadb0a1","position_q":[6144,-3130,-242660],"radius_q":1024},{"feature_id":"531455b42fc13108","position_q":[6861,-2426,-242699],"radius_q":1024},{"feature_id":"55d04c20849d7fcb","position_q":[7578,-1722,-242444],"radius_q":1024},{"feature_id":"ac4ebe80860dbdfa","position_q":[8294,-1017,-242488],"radius_q":1024},{"feature_id":"ff942855bf8e3fc5","position_q":[9011,-313,-242525],"radius_q":1024},{"feature_id":"e6b8bba6c6acaf9c","position_q":[9728,391,-242529],"radius_q":1024},{"feature_id":"ae47379bbd4f7fdf","position_q":[10445,1095,-242512],"radius_q":1024},{"feature_id":"98779442384a30ee","position_q":[11162,1800,-242471],"radius_q":1024},{"feature_id":"f4c7757b2ee9fe19","position_q":[11878,2504,-242482],"radius_q":1024},{"feature_id":"3ea33fae0fLine truncated
@@ -0,0 +1,12 @@
attempt=1 status=passed elapsed_s=1.74 peak_private_MiB=45 exit=0
Round 3 supplemental run ledger (2026-10-10):
PASS: M1 SurfaceWorld BuildHost after restoring the original 32-voxel mapping and 4.0 edge threshold; original worker hash preserved.
PASS: M2 HybridV1 and HybridV2 BuildHost fixtures.
PASS: M3 floating-island BuildHost on the root-parameter-cache source state; see m3_floating_islands_buildhost.*. Peak observed process memory about 3.44 GB. Tile hashes, coverage/LOD, collision/capsule contact, content anchors, height/strate/atmosphere/water agreement, Blueprint carve/fill, W1/W8/W16, and Core-off legacy checks passed.
PASS: Instrumented M3 commandlet after the root-cache change printed PASS and pass-complete=1; wrapper exited 1 because console-title channel failed with 0xE9. Timings are in REPORT.md. This is recorded as a wrapper failure as well as an internal commandlet pass.
FAIL: Island fixture attempts in order: desired tiles 2227/2553 (route player absent); origin pawn from GameMode; 2557 desired tiles after possession (dynamic collision support); support tile-key mismatch; collision gate off restored 2553 but hash differed at (-2,0,-31); CreateLocalPlayer(true) hung and was manually stopped after ~3–4 minutes; base UPlayer fixture lacked controller registration/root transform and failed preflight after 5:10. Corrected explicit controller/root/location fixture passed.
FAIL: M1 edge-threshold experiments: 4.0 -> 52075.827 > 51200 at LOD8 tile (2,0,-1); 4.125 -> 53960.427 > 52800; 4.25 -> 54806.919 > 54400; 4.5 passed edge bounds but yielded no full-span LOD0 candidate. Restoring the correct split chunk mapping and baseline 4.0 threshold passed.
FAIL: Earlier compile errors were corrected: DuplicateObject overload, private helper access, unresolved GetEffectiveWorldSeed export, and uint32-to-int32 narrowing. Initial standalone invocation with -Workers failed; -CompileWorkers 4 succeeded.
RETRY/ENVIRONMENT: One requested retry followed a 0xC0000142 shell-start failure; additional PowerShell/cmd inspection launches also failed with 0xC0000142. No subsequent BuildHost attempt was made. No 0xC0000142 BuildHost retry occurred during the completed runs.
CRASH CHECK: No new UECC-* folder reported; only historical folders dated 2026-09-17 and 2026-10-08. The CreateLocalPlayer hang was manually stopped, not a crash. Latest BuildHost test peak was below 4 GB; all completed test/build steps stayed within 15 minutes.
CURRENT TREE CAVEAT: XY-column cache was edited after the passing/profiled BuildHost run. It remains uncompiled and unmeasured due shell startup failures; current tree is not verified until that edit is built/tested or reverted.
@@ -0,0 +1 @@
{"schema_version":1,"content_hash":"94b563f1ce622def","recipe":{"schema_version":1,"evaluator_version":4,"units_per_voxel":256,"seed":0,"biome_cell_size_q":204800,"biome_warp_q":64000,"biome_warp_wavelength_q":142222,"biome_border_blend_q":30720,"climate_relief_wavelength_q":853333,"climate_moisture_wavelength_q":640000,"climate_relief_contrast_q16":131072,"content_hash":"c37de78b759c0a4c","biomes":[{"id":4097,"base_height_q":-516096,"continent_amplitude_q":128000,"continent_wavelength_q":256000,"mountain_amplitude_q":0,"mountain_wavelength_q":128000,"detail_amplitude_q":512,"detail_wavelength_q":6400,"relief_min_q16":0,"relief_max_q16":19661,"moisture_min_q16":0,"moisture_max_q16":19661,"relief_strength_q16":6554,"relief_contrast_q16":104858,"relief_wavelength_q":2560000,"height_warp_strength_q":12288,"height_warp_wavelength_q":32000,"beach_width_q":3072,"cliff_strength_q16":0,"cliff_slope_threshold_q16":32768,"cliff_sharpness_q16":131072,"cliff_sample_distance_q":512,"cliff_max_displacement_q":0,"cap_enabled":true,"cap_height_q":-149094,"cap_warp_strength_q":0,"cap_warp_wavelength_q":25600,"cap_undulation_q":256000,"cap_wavelength_q":1280000,"cap_detail_amplitude_q":2560,"cap_detail_wavelength_q":25600,"cap_ridge_amplitude_q":0,"cap_ridge_wavelength_q":12800},{"id":4098,"base_height_q":-484639,"continent_amplitude_q":128000,"continent_wavelength_q":256000,"mountain_amplitude_q":128000,"mountain_wavelength_q":512000,"detail_amplitude_q":512,"detail_wavelength_q":25600,"relief_min_q16":52429,"relief_max_q16":65536,"moisture_min_q16":52429,"moisture_max_q16":65536,"relief_strength_q16":45875,"relief_contrast_q16":104858,"relief_wavelength_q":170667,"height_warp_strength_q":12288,"height_warp_wavelength_q":25600,"beach_width_q":3072,"cliff_strength_q16":65536,"cliff_slope_threshold_q16":19661,"cliff_sharpness_q16":327680,"cliff_sample_distance_q":5120,"cliff_max_displacement_q":2570240,"cap_enabled":true,"cap_height_q":-149094,"cap_warp_strength_q":0,"cap_warp_wavelength_q":25600,"cap_undulation_q":230400,"cap_wavelength_q":1280000,"cap_detail_amplitude_q":2560,"cap_detail_wavelength_q":25600,"cap_ridge_amplitude_q":0,"cap_ridge_wavelength_q":12800},{"id":4099,"base_height_q":-516096,"continent_amplitude_q":20480,"continent_wavelength_q":85333,"mountain_amplitude_q":0,"mountain_wavelength_q":128000,"detail_amplitude_q":512,"detail_wavelength_q":6400,"relief_min_q16":6554,"relief_max_q16":26214,"moisture_min_q16":32768,"moisture_max_q16":52429,"relief_strength_q16":0,"relief_contrast_q16":16384,"relief_wavelength_q":170752,"height_warp_strength_q":16640,"height_warp_wavelength_q":32000,"beach_width_q":3072,"cliff_strength_q16":0,"cliff_slope_threshold_q16":32768,"cliff_sharpness_q16":131072,"cliff_sample_distance_q":512,"cliff_max_displacement_q":0,"cap_enabled":true,"cap_height_q":-149094,"cap_warp_strength_q":0,"cap_warp_wavelength_q":25600,"cap_undulation_q":256000,"cap_wavelength_q":1280000,"cap_detail_amplitude_q":2560,"cap_detail_wavelength_q":25600,"cap_ridge_amplitude_q":0,"cap_ridge_wavelength_q":12800},{"id":4100,"base_height_q":-385024,"continent_amplitude_q":20480,"continent_wavelength_q":256000,"mountain_amplitude_q":20480,"mountain_wavelength_q":512000,"detail_amplitude_q":512,"detail_wavelength_q":25600,"relief_min_q16":64881,"relief_max_q16":65536,"moisture_min_q16":52429,"moisture_max_q16":65536,"relief_strength_q16":45875,"relief_contrast_q16":104858,"relief_wavelength_q":170667,"height_warp_strength_q":12288,"height_warp_wavelength_q":25600,"beach_width_q":3072,"cliff_strength_q16":0,"cliff_slope_threshold_q16":32768,"cliff_sharpness_q16":131072,"cliff_sample_distance_q":512,"cliff_max_displacement_q":0,"cap_enabled":true,"cap_height_q":-149094,"cap_warp_strength_q":0,"cap_warp_wavelength_q":25600,"cap_undulation_q":230400,"cap_wavelength_q":1280000,"cap_detail_amplitude_q":2560,"cap_detail_wavelength_q":25600,"cap_ridge_amplitude_q":0,"cap_ridge_wavelength_q":12800}]},"plan":{"schema_version":1,"content_hash":"89cd31291d2aefa4","strates":[]},"floating_islands":[{"strate_index":1,"top_chunk_z":-16,"bottom_chunk_z":-79,"manifest":{"schema_version":1,"content_hash":"54160b4d25d0279f","recipe":{"schema_version":1,"units_per_voxel":256,"owner_voxel_size_microns":125000,"core_voxel_size_microns":250000,"owner_to_core_scale_numerator":1,"owner_to_core_scale_denominator":2,"seed":0,"origin_x_q":6144,"origin_y_q":2048,"center_top_z_q":-243716,"layout_min_z_q":-264196,"layout_max_z_q":-231428,"primary_island_count":3,"island_density_q16":32768,"spacing_q":14336,"minimum_radius_q":3072,"maximum_radius_q":6144,"thickness_ratio_q16":39322,"vertical_jitter_q16":39322,"top_flatten_q16":39322,"taper_end_min_q16":29491,"taper_end_max_q16":45875,"geometry_version":2,"crust_depth_bonus_q16":9830,"root_depth_ratio_q16":49152,"blend_radius_q":768,"contour_warp_q":1613,"roughness_q":256,"boundary_seal_q":512,"bridge_half_width_q":640,"bridge_thickness_q":1024,"route_constraints":{"version":1,"capsule_radius_q":Line truncated
+11
View File
@@ -299,6 +299,17 @@ namespace StandaloneOwnerLoader
Error = "export voxel_size_cm does not match the standalone VOXEL_SIZE"; Error = "export voxel_size_cm does not match the standalone VOXEL_SIZE";
return false; return false;
} }
const VFJson::Value& OwnerVoxelSize = Root.Get("owner_voxel_size_cm");
const VFJson::Value& CoreVoxelSize = Root.Get("core_voxel_size_cm");
const VFJson::Value& OwnerToCoreScale = Root.Get("owner_to_core_length_scale");
if (!std::holds_alternative<std::nullptr_t>(OwnerVoxelSize.Data)
&& (!OwnerVoxelSize.IsNumber() || OwnerVoxelSize.Number() != 12.5
|| !CoreVoxelSize.IsNumber() || CoreVoxelSize.Number() != 25.0
|| !OwnerToCoreScale.IsNumber() || OwnerToCoreScale.Number() != 0.5))
{
Error = "owner-to-Core import scale must be 12.5 cm to 25 cm at a 0.5 length ratio";
return false;
}
Out.Seed = static_cast<int32>(Root.Get("seed").Integer()); Out.Seed = static_cast<int32>(Root.Get("seed").Integer());
Out.ClipRadius = static_cast<int32>(Root.Get("clip_radius").Integer(6)); Out.ClipRadius = static_cast<int32>(Root.Get("clip_radius").Integer(6));
Out.MaxClipLevel = static_cast<int32>(Root.Get("max_clip_level").Integer(8)); Out.MaxClipLevel = static_cast<int32>(Root.Get("max_clip_level").Integer(8));
@@ -1741,7 +1741,8 @@ struct VOXELFORGE_API FFloatingIslandParams
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0", ClampMax = "1.0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0", ClampMax = "1.0"))
float IslandDensity = 0.5f; 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")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "2.0"))
float IslandMinRadius = 24.0f; float IslandMinRadius = 24.0f;
@@ -1765,7 +1766,8 @@ struct VOXELFORGE_API FFloatingIslandParams
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0"))
float SurfaceRoughness = 3.0f; 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")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0"))
float SDFBlendRadius = 6.0f; float SDFBlendRadius = 6.0f;
+2 -2
View File
@@ -130,11 +130,11 @@ $buildSucceeded = $false
for ($attempt = 1; $attempt -le 2 -and -not $buildSucceeded; ++$attempt) { for ($attempt = 1; $attempt -le 2 -and -not $buildSucceeded; ++$attempt) {
$attemptStdout = if ($attempt -eq 1) { $BuildStdout } else { Join-Path $LogRoot 'UBT-CoreIntegration.retry.stdout.log' } $attemptStdout = if ($attempt -eq 1) { $BuildStdout } else { Join-Path $LogRoot 'UBT-CoreIntegration.retry.stdout.log' }
$attemptStderr = if ($attempt -eq 1) { $BuildStderr } else { Join-Path $LogRoot 'UBT-CoreIntegration.retry.stderr.log' } $attemptStderr = if ($attempt -eq 1) { $BuildStderr } else { Join-Path $LogRoot 'UBT-CoreIntegration.retry.stderr.log' }
Write-Host "BuildHost UBT attempt $attempt of 2; max parallel actions=$MaxParallelActions; normal priority." Write-Host "BuildHost UBT attempt $attempt of 2; max parallel actions=$MaxParallelActions; BelowNormal priority."
$process = Start-Process -FilePath $dotnet -ArgumentList $argumentList -WorkingDirectory $PluginRoot ` $process = Start-Process -FilePath $dotnet -ArgumentList $argumentList -WorkingDirectory $PluginRoot `
-RedirectStandardOutput $attemptStdout -RedirectStandardError $attemptStderr ` -RedirectStandardOutput $attemptStdout -RedirectStandardError $attemptStderr `
-WindowStyle Hidden -PassThru -WindowStyle Hidden -PassThru
$process.PriorityClass = [Diagnostics.ProcessPriorityClass]::Normal $process.PriorityClass = [Diagnostics.ProcessPriorityClass]::BelowNormal
$started = [datetime]::UtcNow $started = [datetime]::UtcNow
while (-not $process.HasExited) { while (-not $process.HasExited) {
Start-Sleep -Seconds 5 Start-Sleep -Seconds 5
+67 -6
View File
@@ -5,7 +5,8 @@ param(
[ValidateSet('MarchingCubesV1', 'WorldAssemblerHybridV1', 'WorldAssemblerHybridV2')][string]$CoreMesherRecipe = 'MarchingCubesV1', [ValidateSet('MarchingCubesV1', 'WorldAssemblerHybridV1', 'WorldAssemblerHybridV2')][string]$CoreMesherRecipe = 'MarchingCubesV1',
[switch]$EnableHybridFixturePlacement, [switch]$EnableHybridFixturePlacement,
[switch]$HybridCliffOnly, [switch]$HybridCliffOnly,
[switch]$HybridFixturesOnly [switch]$HybridFixturesOnly,
[switch]$FloatingIslandsOnly
) )
# Exercise AVoxelWorld Core streaming, collision, anchor movement, and carving in BuildHost only. # Exercise AVoxelWorld Core streaming, collision, anchor movement, and carving in BuildHost only.
@@ -41,7 +42,11 @@ function Assert-UnderPath([string]$Candidate, [string]$Root) {
Assert-UnderPath $HostRoot (Join-Path $PluginRoot 'Saved\BuildHost') Assert-UnderPath $HostRoot (Join-Path $PluginRoot 'Saved\BuildHost')
Assert-UnderPath $OutputRoot (Join-Path $StagePlugin 'Saved') Assert-UnderPath $OutputRoot (Join-Path $StagePlugin 'Saved')
if ([string]::IsNullOrWhiteSpace($WorldAssemblyManifestPath)) { if ([string]::IsNullOrWhiteSpace($WorldAssemblyManifestPath)) {
$WorldAssemblyManifestPath = Join-Path $PluginRoot 'Standalone\Research\WorldAssemblerM1\round4\world_assembly.json' $WorldAssemblyManifestPath = if ($FloatingIslandsOnly) {
Join-Path $PluginRoot 'Standalone\Research\WorldAssemblerM3\Round3\results\world_assembly_floating_islands.json'
} else {
Join-Path $PluginRoot 'Standalone\Research\WorldAssemblerM1\round4\world_assembly.json'
}
} }
$WorldAssemblyManifestPath = [IO.Path]::GetFullPath($WorldAssemblyManifestPath) $WorldAssemblyManifestPath = [IO.Path]::GetFullPath($WorldAssemblyManifestPath)
Assert-UnderPath $WorldAssemblyManifestPath $PluginRoot Assert-UnderPath $WorldAssemblyManifestPath $PluginRoot
@@ -84,7 +89,13 @@ foreach ($directory in @($OutputDirectory, $InputDirectory, $LogDirectory, $Loca
} }
Copy-Item -LiteralPath $WorldAssemblyManifestPath -Destination $StagedWorldAssembly -Force Copy-Item -LiteralPath $WorldAssemblyManifestPath -Destination $StagedWorldAssembly -Force
$ManifestData = Get-Content -LiteralPath $StagedWorldAssembly -Raw | ConvertFrom-Json $ManifestData = Get-Content -LiteralPath $StagedWorldAssembly -Raw | ConvertFrom-Json
if ($ManifestData.content_hash -ne '8933693d9560ba9f') { if ($FloatingIslandsOnly) {
if ($ManifestData.content_hash -ne '94b563f1ce622def' -or
@($ManifestData.floating_islands).Count -ne 1 -or
@($ManifestData.plan.strates).Count -ne 0) {
throw "BuildHost world assembly input is not the reviewed round-3 island manifest: $($ManifestData.content_hash)"
}
} elseif ($ManifestData.content_hash -ne '8933693d9560ba9f') {
throw "BuildHost world assembly input is not the reviewed round-4 manifest: $($ManifestData.content_hash)" throw "BuildHost world assembly input is not the reviewed round-4 manifest: $($ManifestData.content_hash)"
} }
if ($EnableHybridFixturePlacement -and $CoreMesherRecipe -eq 'MarchingCubesV1') { if ($EnableHybridFixturePlacement -and $CoreMesherRecipe -eq 'MarchingCubesV1') {
@@ -102,6 +113,10 @@ if ($HybridFixturesOnly -and $CoreMesherRecipe -eq 'MarchingCubesV1') {
if ($HybridFixturesOnly -and ($HybridCliffOnly -or $EnableHybridFixturePlacement)) { if ($HybridFixturesOnly -and ($HybridCliffOnly -or $EnableHybridFixturePlacement)) {
throw '-HybridFixturesOnly cannot be combined with the other focused hybrid modes.' throw '-HybridFixturesOnly cannot be combined with the other focused hybrid modes.'
} }
if ($FloatingIslandsOnly -and ($CoreMesherRecipe -ne 'MarchingCubesV1' -or
$EnableHybridFixturePlacement -or $HybridCliffOnly -or $HybridFixturesOnly)) {
throw '-FloatingIslandsOnly uses the reviewed MC island manifest and cannot be combined with hybrid modes.'
}
$CrashBefore = @{} $CrashBefore = @{}
foreach ($CrashRoot in $CrashRoots) { foreach ($CrashRoot in $CrashRoots) {
@@ -134,6 +149,7 @@ $HybridMesherFlag = switch ($CoreMesherRecipe) {
$HybridFixturePlacementFlag = if ($EnableHybridFixturePlacement) { 1 } else { 0 } $HybridFixturePlacementFlag = if ($EnableHybridFixturePlacement) { 1 } else { 0 }
$HybridCliffOnlyFlag = if ($HybridCliffOnly) { 1 } else { 0 } $HybridCliffOnlyFlag = if ($HybridCliffOnly) { 1 } else { 0 }
$HybridFixturesOnlyFlag = if ($HybridFixturesOnly) { 1 } else { 0 } $HybridFixturesOnlyFlag = if ($HybridFixturesOnly) { 1 } else { 0 }
$FloatingIslandsOnlyFlag = if ($FloatingIslandsOnly) { 1 } else { 0 }
$BaseArguments = @( $BaseArguments = @(
('-project="{0}"' -f $HostProject), ('-project="{0}"' -f $HostProject),
'-nullrhi', '-unattended', '-nop4', '-nosplash', '-NoSound', '-nullrhi', '-unattended', '-nop4', '-nosplash', '-NoSound',
@@ -144,6 +160,7 @@ $BaseArguments = @(
('-LocalDataCachePath="{0}"' -f $DerivedDataCache), ('-LocalDataCachePath="{0}"' -f $DerivedDataCache),
'-run=VoxelForgeExplore', '-seed=0', '-archetype=TunnelNetwork', '-slot=0', '-run=VoxelForgeExplore', '-seed=0', '-archetype=TunnelNetwork', '-slot=0',
'-modes=export', '-gameconfig=1', '-coreworldtest=1', '-modes=export', '-gameconfig=1', '-coreworldtest=1',
("-coreislandtest=$FloatingIslandsOnlyFlag"),
'-settingsref=/Game/VoxelForge/DA_Settings.DA_Settings', '-settingsref=/Game/VoxelForge/DA_Settings.DA_Settings',
('-coreworldassembly="{0}"' -f $StagedWorldAssembly), ('-coreworldassembly="{0}"' -f $StagedWorldAssembly),
("-corehybridmesher=$HybridMesherFlag"), ("-corehybridmesher=$HybridMesherFlag"),
@@ -152,6 +169,11 @@ $BaseArguments = @(
("-corehybridfixturesonly=$HybridFixturesOnlyFlag"), ("-corehybridfixturesonly=$HybridFixturesOnlyFlag"),
('-out="{0}"' -f $OutputDirectory) ('-out="{0}"' -f $OutputDirectory)
) )
if ($FloatingIslandsOnly) {
# Keep this stationary route fixture out of the movement gate; island/span cooked contacts are probed directly.
# Garder ce fixture immobile hors du gate de mouvement ; les contacts cuisines ile/pont sont verifies directement.
$BaseArguments += '-voxel.CollisionGate=0'
}
$OldLocalAppData = $env:LOCALAPPDATA $OldLocalAppData = $env:LOCALAPPDATA
$env:LOCALAPPDATA = $LocalAppData $env:LOCALAPPDATA = $LocalAppData
@@ -171,11 +193,11 @@ try {
$StdoutPath = Join-Path $LogDirectory $StdoutFileName $StdoutPath = Join-Path $LogDirectory $StdoutFileName
$StderrPath = Join-Path $LogDirectory $StderrFileName $StderrPath = Join-Path $LogDirectory $StderrFileName
$Arguments = @($BaseArguments) + ('-abslog="{0}"' -f $LogPath) $Arguments = @($BaseArguments) + ('-abslog="{0}"' -f $LogPath)
Write-Host "BuildHost Core actor commandlet attempt $Attempt of 2; normal priority." Write-Host "BuildHost Core actor commandlet attempt $Attempt of 2; BelowNormal priority."
$Process = Start-Process -FilePath $EditorCmd -ArgumentList $Arguments -WorkingDirectory $HostRoot ` $Process = Start-Process -FilePath $EditorCmd -ArgumentList $Arguments -WorkingDirectory $HostRoot `
-RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath ` -RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath `
-WindowStyle Hidden -PassThru -WindowStyle Hidden -PassThru
$Process.PriorityClass = [Diagnostics.ProcessPriorityClass]::Normal $Process.PriorityClass = [Diagnostics.ProcessPriorityClass]::BelowNormal
$StartedUtc = [datetime]::UtcNow $StartedUtc = [datetime]::UtcNow
$PeakPrivateBytes = [int64]0 $PeakPrivateBytes = [int64]0
while (-not $Process.HasExited) { while (-not $Process.HasExited) {
@@ -198,7 +220,8 @@ try {
$ExitCode = $Process.ExitCode $ExitCode = $Process.ExitCode
if ([string]::IsNullOrEmpty([string]$ExitCode) -and (Test-Path -LiteralPath $LogPath -PathType Leaf)) { if ([string]::IsNullOrEmpty([string]$ExitCode) -and (Test-Path -LiteralPath $LogPath -PathType Leaf)) {
$CompletedLog = Get-Content -LiteralPath $LogPath -Raw $CompletedLog = Get-Content -LiteralPath $LogPath -Raw
if ($CompletedLog -match '\[VoxelForgeCoreWorldTest\] pass complete=1' -and if (($CompletedLog -match '\[VoxelForgeCoreWorldTest\] pass complete=1' -or
$CompletedLog -match '\[VoxelForgeCoreIslandTest\] pass complete=1') -and
$CompletedLog -match 'Commandlet VoxelForgeExploreCommandlet_\d+ finished execution \(result 0\)') { $CompletedLog -match 'Commandlet VoxelForgeExploreCommandlet_\d+ finished execution \(result 0\)') {
$ExitCode = 0 $ExitCode = 0
} elseif ($CompletedLog -match 'Commandlet VoxelForgeExploreCommandlet_\d+ finished execution \(result (-?\d+)\)') { } elseif ($CompletedLog -match 'Commandlet VoxelForgeExploreCommandlet_\d+ finished execution \(result (-?\d+)\)') {
@@ -230,6 +253,44 @@ if (-not (Test-Path -LiteralPath $FinalLog -PathType Leaf)) {
$FinalLog = Join-Path $LogDirectory 'CoreWorldValidation.retry.log' $FinalLog = Join-Path $LogDirectory 'CoreWorldValidation.retry.log'
} }
$Text = Get-Content -LiteralPath $FinalLog -Raw $Text = Get-Content -LiteralPath $FinalLog -Raw
if ($FloatingIslandsOnly) {
foreach ($Marker in @(
'[VoxelForgeCoreIslandTest] manifest_import=PASS',
'[VoxelForgeCoreIslandTest] standalone_reference=PASS',
'[VoxelForgeCoreIslandTest] worker_hash_matrix=1/8/16 stable=1',
'[VoxelForgeCoreIslandTest] pass phase=initial',
'[VoxelForgeCoreIslandTest] height_anchor_agreement=PASS',
'[VoxelForgeCoreIslandTest] content_anchor=PASS',
'[VoxelForgeCoreIslandTest] blueprint_edit=PASS',
'[VoxelForgeCoreIslandTest] switch_off_legacy=PASS',
'[VoxelForgeCoreIslandTest] pass complete=1',
'[VoxelForgePawnSpeedFixture] source=staged_blueprints walk_cm_s=600.000 sprint_cm_s=6000.000'
)) {
if ($Text.IndexOf($Marker, [StringComparison]::Ordinal) -lt 0) {
throw "BuildHost floating-island log is missing '$Marker'. See $FinalLog."
}
}
foreach ($Surface in @('island_top', 'span')) {
if ($Text -notmatch ('\[VoxelForgeCoreIslandTest\] capsule_contact=PASS surface=' +
[regex]::Escape($Surface) + ' .*collision_ready=1')) {
throw "BuildHost island log is missing cooked capsule contact for '$Surface'. See $FinalLog."
}
}
foreach ($Lod in @(0, 1)) {
if ($Text -notmatch ('\[VoxelForgeIslandStreaming\] timing lod=' + $Lod + ' kind=.*air .*core_p95_ms=') -or
$Text -notmatch ('\[VoxelForgeIslandStreaming\] timing lod=' + $Lod + ' kind=.*(island|mixed|span) .*legacy_p95_ms=')) {
throw "BuildHost island log is missing matched air and surface timing at LOD$Lod. See $FinalLog."
}
}
$NewCrashes = @(Get-NewCrashFolders)
if ($NewCrashes.Count -gt 0) {
throw "New UECC crash folders appeared during the island actor test: $($NewCrashes -join '; ')"
}
Write-Host "VOXELFORGE_CORE_FLOATING_ISLANDS_PASS manifest=$($ManifestData.content_hash) hashes=standalone water_fixed=1 lod=0,1 workers=1,8,16"
Write-Host "Core actor log: $FinalLog"
Write-Host "Peak commandlet private bytes sampled: $PeakPrivateBytes"
return
}
if ($HybridFixturesOnly) { if ($HybridFixturesOnly) {
foreach ($Fixture in @('corner90', 'doorway', 'riser1m', 'cliff_ledge', 'organic_cave_chamber')) { foreach ($Fixture in @('corner90', 'doorway', 'riser1m', 'cliff_ledge', 'organic_cave_chamber')) {
$FixtureMarker = '\[WorldAssemblerHybridPluginFixture\] name=' + $FixtureMarker = '\[WorldAssemblerHybridPluginFixture\] name=' +