Files
VoxelForge/Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp
T
Fr0zkaandClaude Opus 5.5 971206c2ea trace: Unreal timing traces run without hash capture (L71); verify baselines tracked
The L70 "2x SurfaceWorld GenerateTile in Unreal" was the instrument: the trace enabled
bCaptureVoxelForgeCoreHashesForValidation, which disables the native heightfield-band
shortcut and whole-tile skips (W1 same keys: 9.00 s hash-off vs 28.46 s hash-on). With
hashing off, Unreal and headless match at W8 within 1.2-2.5% (Core 14.42 s vs 14.26 s) on
identical tile keys. LOD0 p95 tail = real cost of non-empty passage tiles near the spawn mouth
(p50/p95 18.8/105 ms); most LOD0 tiles are empty.

verify.py reads its baselines from Standalone/Tools/verify/baselines/ instead of untracked
Saved/Linux. The 11 .vfmesh control meshes (~78 MB) are not committed yet (to be stored
compressed). Fast tier green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-27 06:55:46 +01:00

21775 lines
1.0 MiB
Plaintext

#include "VoxelForgeExploreCommandlet.h"
#include "VoxelStackOfflineSymbolizer.h"
#include "VoxelForgeRegionEvalExperiment.h"
#include "CoreMinimal.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "Async/ParallelFor.h"
#include "Commandlets/Commandlet.h"
#include "Engine/Blueprint.h"
#include "Containers/StringConv.h"
#include "Components/SceneComponent.h"
#include "Components/HierarchicalInstancedStaticMeshComponent.h"
#include "Engine/Engine.h"
#include "Engine/Player.h"
#include "Engine/StaticMesh.h"
#include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Actor.h"
#include "GameFramework/GameModeBase.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/WorldSettings.h"
#include "RealtimeMeshComponent.h"
#include "PhysicsEngine/BodySetup.h"
#include "HAL/FileManager.h"
#include "HAL/PlatformProcess.h"
#include "HAL/PlatformTime.h"
#include "HAL/PlatformTLS.h"
#include "IImageWrapper.h"
#include "IImageWrapperModule.h"
#include "Misc/CommandLine.h"
#include "Misc/FileHelper.h"
#include "Misc/Parse.h"
#include "Misc/Paths.h"
#include "Modules/ModuleManager.h"
#include "ProfilingDebugging/CpuProfilerTrace.h"
#include "Dom/JsonObject.h"
#include "Policies/CondensedJsonPrintPolicy.h"
#include "Serialization/Archive.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"
#include "Serialization/JsonWriter.h"
#include "UObject/Package.h"
#include "UObject/StrongObjectPtr.h"
#include "UObject/UnrealType.h"
#include "UObject/TextProperty.h"
#include "UObject/UObjectGlobals.h"
#include "VoxelCaveMorphology.h"
#include "VoxelDiffLayer.h"
#include "VoxelDensityOpStack.h"
#include "VoxelDensityAblation.h"
#include "VoxelDensityProfile.h"
#include "VoxelGenerator.h"
#include "VoxelContentManager.h"
#include "VoxelGeometryHash.h"
#include "VoxelMarchingCubesMesher.h"
#include "VoxelRedefinedDensityField.h"
#include "VoxelNoise.h"
#include "VoxelPassageGeometry.h"
#include "VoxelSettings.h"
#include "VoxelAtmosphereManager.h"
#include "VoxelSeasonAsset.h"
#include "VoxelStackSampler.h"
#include "VoxelStrateDefinition.h"
#include "VoxelStrateManager.h"
#include "VoxelStrateMeasure.h"
#include "VoxelStrateComposer.h"
#include "VoxelTerrainOpDefinition.h"
#include "VoxelTypes.h"
#include "VoxelWorld.h"
#include "VoxelStartupTrace.h"
#include "VoxelForgeCoreAssetAdapter.h"
#include "VoxelForgeCore/VoxelForgeCore.h"
#include <algorithm>
#include <atomic>
#include <array>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <limits>
#include <map>
#include <set>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
namespace
{
enum class EAssemblerTileKind : uint8
{
FlatColumn,
Relief,
Mixed,
Unknown
};
struct FAssemblerTileTimingSample
{
int32 LOD = 0;
EAssemblerTileKind Kind = EAssemblerTileKind::Unknown;
double ServiceSeconds = 0.0;
double ModelReadySeconds = 0.0;
double ClassificationMilliseconds = 0.0;
double ColumnMilliseconds = 0.0;
double MeshingMilliseconds = 0.0;
bool bHeightfieldBandLimited = false;
};
const TCHAR* AssemblerTileKindName(EAssemblerTileKind Kind)
{
switch (Kind)
{
case EAssemblerTileKind::FlatColumn: return TEXT("flat_column");
case EAssemblerTileKind::Relief: return TEXT("relief");
case EAssemblerTileKind::Mixed: return TEXT("mixed");
default: return TEXT("unknown");
}
}
EAssemblerTileKind ClassifyAssemblerTile(const VoxelForgeCore::TileMesh& Mesh)
{
const VoxelForgeCore::TileGenerationStats& Stats = Mesh.diagnostics;
if (Stats.featureRefinedResolution)
{
return EAssemblerTileKind::Relief;
}
double MinGroundZ = std::numeric_limits<double>::infinity();
double MaxGroundZ = -std::numeric_limits<double>::infinity();
const std::size_t GroundIndexCount = std::min<std::size_t>(
Mesh.indices.size(), static_cast<std::size_t>(Mesh.groups.groundTriangleCount) * 3u);
const std::size_t ProbeCount = std::min<std::size_t>(64u, GroundIndexCount);
for (std::size_t Probe = 0; Probe < ProbeCount; ++Probe)
{
const std::size_t Index = ProbeCount <= 1u ? 0u
: (Probe * (GroundIndexCount - 1u)) / (ProbeCount - 1u);
const std::uint32_t VertexIndex = Mesh.indices[Index];
if (VertexIndex >= Mesh.vertices.size())
{
return EAssemblerTileKind::Unknown;
}
const double Z = Mesh.vertices[VertexIndex].position.z;
if (!std::isfinite(Z))
{
return EAssemblerTileKind::Unknown;
}
MinGroundZ = std::min(MinGroundZ, Z);
MaxGroundZ = std::max(MaxGroundZ, Z);
}
const int32 LOD = FMath::Clamp(static_cast<int32>(Mesh.key.lod), 0, 20);
const double ReliefThresholdCm = 4.0 * 25.0 * static_cast<double>(1u << LOD);
if (std::isfinite(MinGroundZ) && MaxGroundZ - MinGroundZ > ReliefThresholdCm)
{
return EAssemblerTileKind::Relief;
}
if (Stats.meshingSkipped || Stats.tileClass == VoxelForgeCore::TileClass::allAir
|| Stats.tileClass == VoxelForgeCore::TileClass::allSolid)
{
return EAssemblerTileKind::FlatColumn;
}
if (Stats.tileClass == VoxelForgeCore::TileClass::mixed || !Mesh.indices.empty())
{
return EAssemblerTileKind::Mixed;
}
if (Stats.unknownBoxes > 0 || Stats.tileClass == VoxelForgeCore::TileClass::unknown)
{
return EAssemblerTileKind::Unknown;
}
return EAssemblerTileKind::Unknown;
}
bool TryParseBlueprintPinNumber(const UEdGraphPin* Pin, double& OutValue)
{
if (Pin == nullptr || Pin->DefaultValue.IsEmpty())
{
return false;
}
return LexTryParseString(OutValue, *Pin->DefaultValue) && std::isfinite(OutValue);
}
bool ReadStagedPlayerSpeeds(float& OutWalkCmPerSecond, float& OutSprintCmPerSecond,
FString& OutError)
{
const UClass* CharacterClass = LoadClass<ACharacter>(nullptr,
TEXT("/Game/FirstPerson/Blueprints/BP_FirstPersonCharacter.BP_FirstPersonCharacter_C"));
const UClass* ControllerClass = LoadClass<APlayerController>(nullptr,
TEXT("/Game/FirstPerson/Blueprints/BP_FirstPersonPlayerController.BP_FirstPersonPlayerController_C"));
const ACharacter* CharacterDefault = CharacterClass
? Cast<ACharacter>(CharacterClass->GetDefaultObject()) : nullptr;
if (CharacterDefault == nullptr || CharacterDefault->GetCharacterMovement() == nullptr)
{
OutError = TEXT("BuildHost staged BP_FirstPersonCharacter CDO or CharacterMovement is missing.");
return false;
}
OutWalkCmPerSecond = CharacterDefault->GetCharacterMovement()->MaxWalkSpeed;
double BestSprintSpeed = 0.0;
FString BestSprintSource;
const UBlueprint* CharacterBlueprint = LoadObject<UBlueprint>(nullptr,
TEXT("/Game/FirstPerson/Blueprints/BP_FirstPersonCharacter.BP_FirstPersonCharacter"));
const UBlueprint* ControllerBlueprint = LoadObject<UBlueprint>(nullptr,
TEXT("/Game/FirstPerson/Blueprints/BP_FirstPersonPlayerController.BP_FirstPersonPlayerController"));
const auto ScanBlueprintGraphs = [&](const UBlueprint* Blueprint, const TCHAR* BlueprintName)
{
if (Blueprint == nullptr)
{
return;
}
const auto ScanGraph = [&](const UEdGraph* Graph)
{
if (Graph == nullptr)
{
return;
}
for (const UEdGraphNode* Node : Graph->Nodes)
{
if (Node == nullptr)
{
continue;
}
for (const UEdGraphPin* Pin : Node->Pins)
{
if (Pin == nullptr || Pin->Direction != EGPD_Input
|| !Pin->PinName.ToString().Contains(TEXT("MaxWalkSpeed"), ESearchCase::IgnoreCase))
{
continue;
}
double Value = 0.0;
bool bFoundValue = TryParseBlueprintPinNumber(Pin, Value);
if (!bFoundValue)
{
for (const UEdGraphPin* LinkedPin : Pin->LinkedTo)
{
if (TryParseBlueprintPinNumber(LinkedPin, Value))
{
bFoundValue = true;
break;
}
}
}
if (bFoundValue && Value > static_cast<double>(OutWalkCmPerSecond)
&& Value > BestSprintSpeed)
{
BestSprintSpeed = Value;
BestSprintSource = FString::Printf(TEXT("%s.%s"), BlueprintName,
*Node->GetName());
}
}
}
};
for (const auto& GraphReference : Blueprint->UbergraphPages)
{
ScanGraph(GraphReference);
}
for (const auto& GraphReference : Blueprint->FunctionGraphs)
{
ScanGraph(GraphReference);
}
};
ScanBlueprintGraphs(CharacterBlueprint, TEXT("BP_FirstPersonCharacter"));
ScanBlueprintGraphs(ControllerBlueprint, TEXT("BP_FirstPersonPlayerController"));
if (!std::isfinite(OutWalkCmPerSecond) || OutWalkCmPerSecond <= 0.0f
|| !std::isfinite(BestSprintSpeed) || BestSprintSpeed <= OutWalkCmPerSecond)
{
OutError = TEXT("BuildHost staged Blueprint graphs do not expose a MaxWalkSpeed input above the character CDO walk speed.");
return false;
}
OutSprintCmPerSecond = static_cast<float>(BestSprintSpeed);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgePawnSpeedFixture] source=staged_blueprints walk_cm_s=%.3f sprint_cm_s=%.3f sprint_graph=%s"),
OutWalkCmPerSecond, OutSprintCmPerSecond, *BestSprintSource);
return true;
}
struct FAssemblerGeometryAudit
{
bool bValid = false;
int32 LOD = 0;
FIntVector Tile = FIntVector::ZeroValue;
double ExpectedTileSpanCm = 0.0;
double CellWorldCm = 0.0;
double SpanXcm = 0.0;
double SpanYcm = 0.0;
double MaxTriangleEdgeCm = 0.0;
VoxelForgeCore::Vec3d MaxEdgeStart;
VoxelForgeCore::Vec3d MaxEdgeEnd;
std::uint64_t MaxEdgeTriangleIndex = 0;
int32 NormalSamples = 0;
int32 NormalOrientationMismatches = 0;
int32 TriangleSamples = 0;
int32 TriangleWindingMismatches = 0;
int32 SkirtTrianglesExcluded = 0;
};
bool AuditAssemblerCoreTile(const VoxelForgeCore::World& World,
const VoxelForgeCore::TileMesh& Mesh,
FAssemblerGeometryAudit& OutAudit,
FString& OutError)
{
if (Mesh.indices.empty() || Mesh.indices.size() % 3u != 0u || Mesh.vertices.empty())
{
OutError = TEXT("Core geometry audit received a malformed triangle mesh.");
return false;
}
const std::size_t SurfaceVertexCount = std::min<std::size_t>(
Mesh.vertices.size(), Mesh.groups.surfaceVertexCount);
if (SurfaceVertexCount == 0u)
{
OutError = TEXT("Core geometry audit mesh contains no surface vertices.");
return false;
}
double MinX = std::numeric_limits<double>::infinity();
double MaxX = -std::numeric_limits<double>::infinity();
double MinY = std::numeric_limits<double>::infinity();
double MaxY = -std::numeric_limits<double>::infinity();
for (std::size_t Index = 0; Index < SurfaceVertexCount; ++Index)
{
const VoxelForgeCore::MeshVertex& Vertex = Mesh.vertices[Index];
if (!std::isfinite(Vertex.position.x) || !std::isfinite(Vertex.position.y)
|| !std::isfinite(Vertex.position.z))
{
OutError = TEXT("Core geometry audit found a non-finite surface vertex.");
return false;
}
MinX = std::min(MinX, Vertex.position.x);
MaxX = std::max(MaxX, Vertex.position.x);
MinY = std::min(MinY, Vertex.position.y);
MaxY = std::max(MaxY, Vertex.position.y);
}
const int32 LOD = static_cast<int32>(Mesh.key.lod);
const VoxelForgeCore::WorldConfiguration& Configuration = World.configuration();
const double CoreVoxelSizeCm = static_cast<double>(Configuration.voxelSizeMicrons) / 10000.0;
const int32 ExtentVoxels = CHUNK_SIZE << FMath::Clamp(LOD, 0, 20);
const int32 Cells = LOD < Configuration.fullResolutionLodCount
? CHUNK_SIZE : FMath::Max(1, Configuration.coarseTileCells);
const double Step = static_cast<double>(FMath::Max(1, ExtentVoxels / Cells));
const int32 StepVoxels = FMath::Max(1, FMath::RoundToInt(Step));
const double CellWorldCm = CoreVoxelSizeCm * Step;
// Les coutures lointaines peuvent depasser deux cellules en plan sans former une pointe verticale.
// Far LOD transitions can span slightly over two horizontal cells without creating a vertical spike.
const double MaxEdgeCellMultiple = LOD >= 7 ? 4.0 : 2.0;
const double MaxEdgeWorldCm = MaxEdgeCellMultiple * CellWorldCm;
const int32 PreviousOctaveBias = VoxelGenLOD::GetThreadOctaveBias();
const int32 PreviousSampleStep = VoxelGenLOD::GetThreadSampleStep();
struct FRestoreAssemblerLodSampleState
{
int32 OctaveBias;
int32 SampleStep;
~FRestoreAssemblerLodSampleState()
{
VoxelGenLOD::SetThreadOctaveBias(OctaveBias);
VoxelGenLOD::SetThreadSampleStep(SampleStep);
}
} RestoreLodSampleState{PreviousOctaveBias, PreviousSampleStep};
const int32 OctaveBias = Configuration.lodOctaveDrop > 0 && StepVoxels > 1
? Configuration.lodOctaveDrop * static_cast<int32>(FMath::FloorLog2(
static_cast<uint32>(StepVoxels))) : 0;
VoxelGenLOD::SetThreadOctaveBias(OctaveBias);
VoxelGenLOD::SetThreadSampleStep(StepVoxels);
for (std::size_t Index = 0; Index < Mesh.indices.size(); Index += 3u)
{
const std::size_t TriangleIndex = Index / 3u;
if (TriangleIndex < Mesh.skirtFaceMasks.size()
&& Mesh.skirtFaceMasks[TriangleIndex] != 0u)
{
++OutAudit.SkirtTrianglesExcluded;
continue;
}
const std::uint32_t IA = Mesh.indices[Index];
const std::uint32_t IB = Mesh.indices[Index + 1u];
const std::uint32_t IC = Mesh.indices[Index + 2u];
if (IA >= Mesh.vertices.size() || IB >= Mesh.vertices.size() || IC >= Mesh.vertices.size())
{
OutError = TEXT("Core geometry audit found an out-of-range triangle index.");
return false;
}
const auto& A = Mesh.vertices[IA].position;
const auto& B = Mesh.vertices[IB].position;
const auto& C = Mesh.vertices[IC].position;
const auto Distance = [](const auto& P, const auto& Q)
{
const double DX = P.x - Q.x;
const double DY = P.y - Q.y;
const double DZ = P.z - Q.z;
return std::sqrt(DX * DX + DY * DY + DZ * DZ);
};
const double EdgeAB = Distance(A, B);
const double EdgeBC = Distance(B, C);
const double EdgeCA = Distance(C, A);
const double Edge = std::max({EdgeAB, EdgeBC, EdgeCA});
const VoxelForgeCore::Vec3d* EdgeStart = &A;
const VoxelForgeCore::Vec3d* EdgeEnd = &B;
if (EdgeBC >= EdgeAB && EdgeBC >= EdgeCA)
{
EdgeStart = &B;
EdgeEnd = &C;
}
else if (EdgeCA >= EdgeAB)
{
EdgeStart = &C;
EdgeEnd = &A;
}
if (!std::isfinite(Edge) || Edge > MaxEdgeWorldCm + 0.1)
{
OutError = FString::Printf(
TEXT("Core geometry audit found an oversized edge %.3f cm (bound %.3f cm; delta=(%.3f,%.3f,%.3f), start=(%.3f,%.3f,%.3f), end=(%.3f,%.3f,%.3f)) in tile (%d,%d,%d) lod=%d triangle=%llu."),
Edge, MaxEdgeWorldCm, EdgeEnd->x - EdgeStart->x, EdgeEnd->y - EdgeStart->y,
EdgeEnd->z - EdgeStart->z, EdgeStart->x, EdgeStart->y, EdgeStart->z,
EdgeEnd->x, EdgeEnd->y, EdgeEnd->z, Mesh.key.coordinate.x,
Mesh.key.coordinate.y, Mesh.key.coordinate.z, LOD,
static_cast<unsigned long long>(TriangleIndex));
return false;
}
if (Edge > OutAudit.MaxTriangleEdgeCm)
{
OutAudit.MaxTriangleEdgeCm = Edge;
OutAudit.MaxEdgeTriangleIndex = static_cast<std::uint64_t>(TriangleIndex);
if (EdgeAB >= EdgeBC && EdgeAB >= EdgeCA)
{
OutAudit.MaxEdgeStart = A;
OutAudit.MaxEdgeEnd = B;
}
else if (EdgeBC >= EdgeCA)
{
OutAudit.MaxEdgeStart = B;
OutAudit.MaxEdgeEnd = C;
}
else
{
OutAudit.MaxEdgeStart = C;
OutAudit.MaxEdgeEnd = A;
}
}
const double ABX = B.x - A.x, ABY = B.y - A.y, ABZ = B.z - A.z;
const double ACX = C.x - A.x, ACY = C.y - A.y, ACZ = C.z - A.z;
const double FaceX = ABY * ACZ - ABZ * ACY;
const double FaceY = ABZ * ACX - ABX * ACZ;
const double FaceZ = ABX * ACY - ABY * ACX;
const VoxelForgeCore::MeshVertex& NormalA = Mesh.vertices[IA];
const VoxelForgeCore::MeshVertex& NormalB = Mesh.vertices[IB];
const VoxelForgeCore::MeshVertex& NormalC = Mesh.vertices[IC];
const double Alignment = FaceX * (NormalA.normal.x + NormalB.normal.x + NormalC.normal.x)
+ FaceY * (NormalA.normal.y + NormalB.normal.y + NormalC.normal.y)
+ FaceZ * (NormalA.normal.z + NormalB.normal.z + NormalC.normal.z);
const double FaceLengthSq = FaceX * FaceX + FaceY * FaceY + FaceZ * FaceZ;
if (FaceLengthSq > 1.0e-10)
{
if (Alignment < -1.0e-10 * std::sqrt(FaceLengthSq))
{
// RealtimeMesh requires MC indices in reversed 0,2,1 order; this reports the expected convention.
// RealtimeMesh exige l'ordre MC inverse 0,2,1; ce compte documente la convention attendue.
++OutAudit.TriangleWindingMismatches;
}
++OutAudit.TriangleSamples;
}
}
const std::size_t NormalProbeCount = std::min<std::size_t>(96u, SurfaceVertexCount);
for (std::size_t Probe = 0; Probe < NormalProbeCount; ++Probe)
{
const std::size_t Index = NormalProbeCount <= 1u ? 0u
: Probe * (SurfaceVertexCount - 1u) / (NormalProbeCount - 1u);
const VoxelForgeCore::MeshVertex& Vertex = Mesh.vertices[Index];
const double NX = Vertex.normal.x;
const double NY = Vertex.normal.y;
const double NZ = Vertex.normal.z;
const double NormalLengthSq = NX * NX + NY * NY + NZ * NZ;
if (!std::isfinite(NormalLengthSq) || std::abs(NormalLengthSq - 1.0) > 0.02)
{
OutError = FString::Printf(
TEXT("Core geometry audit found a non-unit normal (length squared %.5f)."),
NormalLengthSq);
return false;
}
// Garder la sonde locale evite de traverser plusieurs reliefs aux LOD lointains.
// Keep the probe local so coarse-LOD checks do not cross several terrain features.
if (LOD <= 1)
{
// Le champ complet ne represente pas la surface lointaine decimee par le LOD.
// The full-resolution field does not represent the decimated far-LOD surface.
const double NormalOffsetVoxels = std::min(0.5 * Step, 1.0);
const VoxelForgeCore::Vec3d Positive{
Vertex.position.x / CoreVoxelSizeCm + NX * NormalOffsetVoxels,
Vertex.position.y / CoreVoxelSizeCm + NY * NormalOffsetVoxels,
Vertex.position.z / CoreVoxelSizeCm + NZ * NormalOffsetVoxels};
const VoxelForgeCore::Vec3d Negative{
Vertex.position.x / CoreVoxelSizeCm - NX * NormalOffsetVoxels,
Vertex.position.y / CoreVoxelSizeCm - NY * NormalOffsetVoxels,
Vertex.position.z / CoreVoxelSizeCm - NZ * NormalOffsetVoxels};
const float PositiveDensity = World.densityAt(Positive);
const float NegativeDensity = World.densityAt(Negative);
if (PositiveDensity + 0.01f < NegativeDensity)
{
++OutAudit.NormalOrientationMismatches;
}
++OutAudit.NormalSamples;
}
}
if (OutAudit.NormalSamples > 0
&& OutAudit.NormalOrientationMismatches * 10 > OutAudit.NormalSamples)
{
OutError = FString::Printf(
TEXT("Core geometry audit found %d/%d normals pointing toward denser material in tile (%d,%d,%d) lod=%d step=%d octave_bias=%d."),
OutAudit.NormalOrientationMismatches, OutAudit.NormalSamples,
Mesh.key.coordinate.x, Mesh.key.coordinate.y, Mesh.key.coordinate.z,
LOD, StepVoxels, OctaveBias);
return false;
}
OutAudit.bValid = true;
OutAudit.LOD = LOD;
OutAudit.Tile = FIntVector(static_cast<int32>(Mesh.key.coordinate.x),
static_cast<int32>(Mesh.key.coordinate.y),
static_cast<int32>(Mesh.key.coordinate.z));
OutAudit.ExpectedTileSpanCm = static_cast<double>(ExtentVoxels) * CoreVoxelSizeCm;
OutAudit.CellWorldCm = CellWorldCm;
OutAudit.SpanXcm = MaxX - MinX;
OutAudit.SpanYcm = MaxY - MinY;
return true;
}
}
namespace
{
using FExploreJsonWriter = TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>;
constexpr int32 DefaultRenderWidth = 512;
constexpr int32 DefaultRenderHeight = 288;
constexpr float DefaultRenderStepVoxels = 0.5f; // 6.25 cm, as at 25 cm/voxel.
constexpr float DefaultRenderMaxDistanceVoxels = 320.0f; // 40 m.
// Schema-v1 follows Standalone's 25 cm voxel scale, independent of the legacy runtime module's 12.5 cm scale.
// Le schéma-v1 suit l'échelle Standalone de 25 cm, indépendante de l'échelle historique de 12,5 cm du module Runtime.
constexpr double StandaloneCoreVoxelSizeCm = 25.0;
constexpr int32 DefaultExportSize = 128;
constexpr int32 DefaultExportStep = 1;
constexpr int32 DefaultMaxWalkCells = 32000000;
constexpr float DefaultBudgetMinutes = 25.0f;
constexpr float MaxBudgetMinutes = 30.0f;
constexpr int32 MaxSyntheticStrates = 64;
constexpr int32 MaxRenderPixels = 1048576;
constexpr int64 MaxRenderDensitySamples = 400000000ll;
constexpr int32 MaxExportSize = 128;
constexpr int64 MaxWalkWorkingBytes = 768ll * 1024ll * 1024ll;
// The exact fine-grid walk retains Air + Density + player-fit bits + non-finite flags, and can
// transiently hold Parent + Queue + Path or Visited + NarrowCell + DFS stacks. 21 bytes/cell is a
// conservative preflight figure for those arrays, including allocator slack/headroom.
constexpr int64 EstimatedWalkBytesPerCell = 21ll;
constexpr int64 MaxExportWorkingBytes = 1024ll * 1024ll * 1024ll;
FString VoxelForgeSavedRoot()
{
FString Root = FPaths::ConvertRelativePathToFull(
FPaths::Combine(FPaths::ProjectDir(), TEXT("Plugins/VoxelForge/Saved")));
FPaths::NormalizeDirectoryName(Root);
return Root;
}
bool IsUnderVoxelForgeSaved(const FString& InPath)
{
FString Candidate = FPaths::ConvertRelativePathToFull(InPath);
FString Root = VoxelForgeSavedRoot();
FPaths::NormalizeDirectoryName(Candidate);
FPaths::NormalizeDirectoryName(Root);
return Candidate.Equals(Root, ESearchCase::IgnoreCase)
|| Candidate.StartsWith(Root + TEXT("/"), ESearchCase::IgnoreCase)
|| Candidate.StartsWith(Root + TEXT("\\"), ESearchCase::IgnoreCase);
}
const TCHAR* ArchetypeName(ECaveGeneratorType Archetype)
{
switch (Archetype)
{
case ECaveGeneratorType::TunnelNetwork: return TEXT("TunnelNetwork");
case ECaveGeneratorType::FlatPlain: return TEXT("FlatPlain");
case ECaveGeneratorType::CrystalChamber: return TEXT("CrystalChamber");
case ECaveGeneratorType::Maze: return TEXT("Maze");
case ECaveGeneratorType::SurfaceWorld: return TEXT("SurfaceWorld");
case ECaveGeneratorType::VerticalShafts: return TEXT("VerticalShafts");
case ECaveGeneratorType::FloatingIslands: return TEXT("FloatingIslands");
case ECaveGeneratorType::Underwater: return TEXT("Underwater");
default: return TEXT("Unknown");
}
}
const TCHAR* ConnectivityResultName(EVoxelConnectivityResult Result)
{
switch (Result)
{
case EVoxelConnectivityResult::Connected: return TEXT("Connected");
case EVoxelConnectivityResult::NotConnectedAtThisResolution:
return TEXT("NotConnectedAtThisResolution");
case EVoxelConnectivityResult::StartCellSolid: return TEXT("StartCellSolid");
case EVoxelConnectivityResult::GoalCellSolid: return TEXT("GoalCellSolid");
case EVoxelConnectivityResult::OutOfWindow: return TEXT("OutOfWindow");
case EVoxelConnectivityResult::CoarseLiedBudgetExhausted: return TEXT("CoarseLiedBudgetExhausted");
case EVoxelConnectivityResult::StartCellNotPlayerFit: return TEXT("StartCellNotPlayerFit");
case EVoxelConnectivityResult::GoalCellNotPlayerFit: return TEXT("GoalCellNotPlayerFit");
default: return TEXT("Unknown");
}
}
bool ParseArchetype(const FString& Text, ECaveGeneratorType& OutArchetype)
{
FString Normalized = Text;
Normalized.TrimStartAndEndInline();
Normalized.ToLowerInline();
Normalized.ReplaceInline(TEXT("_"), TEXT(""));
Normalized.ReplaceInline(TEXT("-"), TEXT(""));
const TPair<const TCHAR*, ECaveGeneratorType> Names[] = {
{ TEXT("tunnelnetwork"), ECaveGeneratorType::TunnelNetwork },
{ TEXT("flatplain"), ECaveGeneratorType::FlatPlain },
{ TEXT("crystalchamber"), ECaveGeneratorType::CrystalChamber },
{ TEXT("maze"), ECaveGeneratorType::Maze },
{ TEXT("surfaceworld"), ECaveGeneratorType::SurfaceWorld },
{ TEXT("verticalshafts"), ECaveGeneratorType::VerticalShafts },
{ TEXT("floatingislands"), ECaveGeneratorType::FloatingIslands },
{ TEXT("floatingisland"), ECaveGeneratorType::FloatingIslands },
{ TEXT("underwater"), ECaveGeneratorType::Underwater },
};
for (const auto& Pair : Names)
{
if (Normalized == Pair.Key)
{
OutArchetype = Pair.Value;
return true;
}
}
return false;
}
const TCHAR* NoiseTypeName(EVoxelNoiseType NoiseType)
{
switch (NoiseType)
{
case EVoxelNoiseType::FBM: return TEXT("FBM");
case EVoxelNoiseType::Ridged: return TEXT("Ridged");
case EVoxelNoiseType::Mixed: return TEXT("Mixed");
case EVoxelNoiseType::Cellular: return TEXT("Cellular");
default: return TEXT("Unknown");
}
}
bool ParseNoiseType(const FString& Text, EVoxelNoiseType& OutNoiseType)
{
FString Normalized = Text;
Normalized.TrimStartAndEndInline();
Normalized.ToLowerInline();
Normalized.ReplaceInline(TEXT("_"), TEXT(""));
Normalized.ReplaceInline(TEXT("-"), TEXT(""));
if (Normalized == TEXT("fbm") || Normalized == TEXT("fractal"))
{
OutNoiseType = EVoxelNoiseType::FBM;
return true;
}
if (Normalized == TEXT("ridged") || Normalized == TEXT("ridge"))
{
OutNoiseType = EVoxelNoiseType::Ridged;
return true;
}
if (Normalized == TEXT("mixed"))
{
OutNoiseType = EVoxelNoiseType::Mixed;
return true;
}
if (Normalized == TEXT("cellular") || Normalized == TEXT("worley"))
{
OutNoiseType = EVoxelNoiseType::Cellular;
return true;
}
return false;
}
bool ParseRenderCamera(const FString& Text, FVector& OutCamera)
{
TArray<FString> Components;
Text.ParseIntoArray(Components, TEXT(","), true);
if (Components.Num() != 3)
{
return false;
}
const float X = FCString::Atof(*Components[0]);
const float Y = FCString::Atof(*Components[1]);
const float Z = FCString::Atof(*Components[2]);
if (!FMath::IsFinite(X) || !FMath::IsFinite(Y) || !FMath::IsFinite(Z))
{
return false;
}
OutCamera = FVector(X, Y, Z);
return true;
}
bool ParseDensitySlice(
const FString& Text,
FVector& OutOrigin,
FVector& OutDirection,
int32& OutSamples,
float& OutStep)
{
TArray<FString> Components;
Text.ParseIntoArray(Components, TEXT(","), true);
if (Components.Num() != 8)
{
return false;
}
const float OriginX = FCString::Atof(*Components[0]);
const float OriginY = FCString::Atof(*Components[1]);
const float OriginZ = FCString::Atof(*Components[2]);
const float DirectionX = FCString::Atof(*Components[3]);
const float DirectionY = FCString::Atof(*Components[4]);
const float DirectionZ = FCString::Atof(*Components[5]);
const int32 Samples = FCString::Atoi(*Components[6]);
const float Step = FCString::Atof(*Components[7]);
if (!FMath::IsFinite(OriginX) || !FMath::IsFinite(OriginY)
|| !FMath::IsFinite(OriginZ) || !FMath::IsFinite(DirectionX)
|| !FMath::IsFinite(DirectionY) || !FMath::IsFinite(DirectionZ)
|| !FMath::IsFinite(Step) || Samples < 2 || Samples > 4096
|| Step <= 0.0f)
{
return false;
}
FVector Direction(DirectionX, DirectionY, DirectionZ);
if (!Direction.Normalize())
{
return false;
}
OutOrigin = FVector(OriginX, OriginY, OriginZ);
OutDirection = Direction;
OutSamples = Samples;
OutStep = Step;
return true;
}
bool NeedsOriginInWindow(ECaveGeneratorType Archetype)
{
// This is the same topology policy as VoxelForgePlayerFitWindow.h. That header is test-only,
// so the editor commandlet keeps the policy here while still delegating the actual fit test to
// VoxelStrateMeasure's shared VF_ stencil.
switch (Archetype)
{
case ECaveGeneratorType::Maze:
case ECaveGeneratorType::VerticalShafts:
case ECaveGeneratorType::TunnelNetwork:
case ECaveGeneratorType::Underwater:
return true;
case ECaveGeneratorType::FlatPlain:
case ECaveGeneratorType::CrystalChamber:
case ECaveGeneratorType::SurfaceWorld:
case ECaveGeneratorType::FloatingIslands:
default:
return false;
}
}
float BoundarySealForArchetype(
const UVoxelStrateDefinition& Definition,
ECaveGeneratorType Archetype)
{
switch (Archetype)
{
case ECaveGeneratorType::FlatPlain:
case ECaveGeneratorType::CrystalChamber:
return Definition.SlabParams.BoundarySealThickness;
case ECaveGeneratorType::Maze:
return Definition.MazeParams.BoundarySealThickness;
case ECaveGeneratorType::SurfaceWorld:
return Definition.SurfaceParams.BoundarySealThickness;
case ECaveGeneratorType::VerticalShafts:
return Definition.VerticalShaftParams.BoundarySealThickness;
case ECaveGeneratorType::FloatingIslands:
return Definition.FloatingIslandParams.BoundarySealThickness;
case ECaveGeneratorType::TunnelNetwork:
case ECaveGeneratorType::Underwater:
default:
return Definition.GenerationParams.BoundarySealThickness;
}
}
struct FExploreArguments
{
int32 Seed = 0;
bool bSeedOverride = false;
ECaveGeneratorType Archetype = ECaveGeneratorType::Maze;
int32 Slot = 4;
FString StrateReference;
FString SettingsReference;
FString CoreWorldAssemblyManifestPath;
int32 CoreHybridMesherVersion = 0;
float CoreVoxelSizeCm = 25.0f;
bool bCoreHybridMesher = false;
bool bCoreHybridMesherV2 = false;
bool bCoreHybridFixturePlacement = false;
bool bCoreHybridCliffOnly = false;
bool bCoreHybridFixturesOnly = false;
bool bCoreFloatingIslandTest = false;
bool bCoreFallbackTest = false;
bool bCoreOffTest = false;
bool bCorePlanOnlyTest = false;
bool bCoreTestSeedOverride = false;
int32 CoreTestSeed = 0;
bool bRender = true;
bool bWalk = true;
bool bExport = true;
bool bWormsEnabledOverride = false;
bool bWormsEnabled = true;
bool bWormStrengthOverride = false;
float WormStrength = 0.0f;
bool bProfileDensity = false;
bool bProfileDensityFull = false;
bool bPerfAttribution = false;
bool bSampleStacks = false;
uint32 SampleStacksIntervalUs = FVoxelStackSampler::DefaultIntervalUs;
bool bProfileLod = false;
bool bOpBounds = false;
bool bFailureFocusRender = false;
FString OutDirectory;
int32 RenderWidth = DefaultRenderWidth;
int32 RenderHeight = DefaultRenderHeight;
float RenderStepVoxels = DefaultRenderStepVoxels;
float RenderMaxDistanceVoxels = DefaultRenderMaxDistanceVoxels;
int32 ExportSize = DefaultExportSize;
int32 ExportStep = DefaultExportStep;
bool bExportTileOverride = false;
int32 ExportTileX = 0;
int32 ExportTileY = 0;
int32 ExportTileZ = 0;
// The canonical export owns one immutable lattice for the whole run. Batch cases can pass
// density_grid_reuse=false to produce a matched wall-clock control.
bool bReuseDensityGrid = true;
bool bBlockEarlyOut = false;
// Opt-in parity mode: copy the runtime mesher presentation/LOD settings from the authored
// settings object. The default remains the historical bounded export configuration.
bool bGameRuntimeConfig = false;
int32 MeshMinBatchSize = 1;
int32 MaxWalkCells = DefaultMaxWalkCells;
float BudgetMinutes = DefaultBudgetMinutes;
bool bSurfaceRoughnessOverride = false;
float SurfaceRoughness = 0.0f;
bool bTunnelWarpStrengthOverride = false;
float TunnelWarpStrength = 0.0f;
bool bRoughnessNoiseTypeOverride = false;
EVoxelNoiseType RoughnessNoiseType = EVoxelNoiseType::FBM;
bool bRoughnessFrequencyOverride = false;
float RoughnessFrequency = 0.0f;
bool bTunnelFloorEnabledOverride = false;
int32 TunnelFloorEnabled = 1;
bool bTunnelFloorTerracingOverride = false;
int32 TunnelFloorTerracing = 1;
bool bTunnelFloorStepOverride = false;
float TunnelFloorStep = 0.0f;
bool bTunnelFloorMaxLedgeOverride = false;
float TunnelFloorMaxLedge = 0.0f;
bool bTunnelFloorGentleOverride = false;
float TunnelFloorGentle = 0.0f;
bool bTunnelFloorLedgesOverride = false;
int32 TunnelFloorLedges = 0;
bool bTunnelFloorMaxLedgesOverride = false;
int32 TunnelFloorMaxLedges = 0;
bool bRoomMouthRiseOverride = false;
float RoomMouthRise = 0.0f;
bool bRoomMouthRiseBlendOverride = false;
float RoomMouthRiseBlend = 0.0f;
bool bPassageFloorWritesOverride = false;
int32 PassageFloorWrites = 1;
bool bStandaloneWorldExport = false;
bool bOwnerFourStratePlan = false;
bool bSkipStandaloneCoreAdapterParity = false;
bool bCoreWorldTest = false;
bool bCoreWorldStreamTraceOnly = false;
bool bRenderCameraOverride = false;
FVector RenderCamera = FVector::ZeroVector;
bool bRenderTargetOverride = false;
FVector RenderTarget = FVector::ZeroVector;
bool bDensitySlice = false;
FVector DensitySliceOrigin = FVector::ZeroVector;
FVector DensitySliceDirection = FVector::ForwardVector;
int32 DensitySliceSamples = 257;
float DensitySliceStep = 0.5f;
bool bTunnelCoreProbe = false;
bool IsDensityProfilingEnabled() const
{
return bProfileDensity || bPerfAttribution;
}
FString CanonicalModes() const
{
FString Result;
if (bRender) Result += TEXT("render,");
if (bWalk) Result += TEXT("walk,");
if (bExport) Result += TEXT("export,");
if (bOpBounds) Result += TEXT("opbounds,");
if (bTunnelCoreProbe) Result += TEXT("tunnelcoreprobe,");
if (Result.EndsWith(TEXT(",")))
{
Result.LeftChopInline(1);
}
return Result;
}
};
bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FString& OutError)
{
FString ArchetypeText = TEXT("Maze");
FString ModesText = TEXT("render,walk,export");
FString OutText;
OutArguments.bSeedOverride = FParse::Value(*Params, TEXT("seed="), OutArguments.Seed);
int32 StandaloneWorldExport = 0;
FParse::Value(*Params, TEXT("standaloneworldexport="), StandaloneWorldExport);
OutArguments.bStandaloneWorldExport = StandaloneWorldExport != 0;
int32 OwnerFourStratePlan = 0;
FParse::Value(*Params, TEXT("ownerfourstrateplan="), OwnerFourStratePlan);
OutArguments.bOwnerFourStratePlan = OwnerFourStratePlan != 0;
int32 SkipStandaloneCoreAdapterParity = 0;
FParse::Value(*Params, TEXT("skipstandalonecoreadapterparity="), SkipStandaloneCoreAdapterParity);
OutArguments.bSkipStandaloneCoreAdapterParity = SkipStandaloneCoreAdapterParity != 0;
int32 CoreWorldTest = 0;
FParse::Value(*Params, TEXT("coreworldtest="), CoreWorldTest);
OutArguments.bCoreWorldTest = CoreWorldTest != 0;
int32 CoreWorldStreamTraceOnly = 0;
FParse::Value(*Params, TEXT("coreworldstreamtrace="), CoreWorldStreamTraceOnly);
OutArguments.bCoreWorldStreamTraceOnly = CoreWorldStreamTraceOnly != 0;
FParse::Value(*Params, TEXT("corevoxelsizecm="), OutArguments.CoreVoxelSizeCm);
if (!FMath::IsFinite(OutArguments.CoreVoxelSizeCm)
|| OutArguments.CoreVoxelSizeCm < VOXEL_SIZE
|| OutArguments.CoreVoxelSizeCm > 2.0f * VOXEL_SIZE)
{
OutError = TEXT("corevoxelsizecm must be between one and two legacy voxel widths.");
return false;
}
int32 CoreFloatingIslandTest = 0;
FParse::Value(*Params, TEXT("coreislandtest="), CoreFloatingIslandTest);
OutArguments.bCoreFloatingIslandTest = CoreFloatingIslandTest != 0;
int32 CoreFallbackTest = 0;
FParse::Value(*Params, TEXT("corefallbacktest="), CoreFallbackTest);
OutArguments.bCoreFallbackTest = CoreFallbackTest != 0;
int32 CoreOffTest = 0;
FParse::Value(*Params, TEXT("coreofftest="), CoreOffTest);
OutArguments.bCoreOffTest = CoreOffTest != 0;
int32 CorePlanOnlyTest = 0;
FParse::Value(*Params, TEXT("coreplantest="), CorePlanOnlyTest);
OutArguments.bCorePlanOnlyTest = CorePlanOnlyTest != 0;
OutArguments.bCoreTestSeedOverride = FParse::Value(
*Params, TEXT("coretestseed="), OutArguments.CoreTestSeed);
int32 CoreHybridMesher = 0;
FParse::Value(*Params, TEXT("corehybridmesher="), CoreHybridMesher);
if (CoreHybridMesher < 0 || CoreHybridMesher > 2)
{
OutError = TEXT("corehybridmesher supports only 0 (MC), 1 (hybrid V1), or 2 (hybrid V2).");
return false;
}
OutArguments.CoreHybridMesherVersion = CoreHybridMesher;
OutArguments.bCoreHybridMesher = CoreHybridMesher != 0;
OutArguments.bCoreHybridMesherV2 = CoreHybridMesher == 2;
int32 CoreHybridFixturePlacement = 0;
FParse::Value(*Params, TEXT("corehybridfixtureplacement="), CoreHybridFixturePlacement);
OutArguments.bCoreHybridFixturePlacement = CoreHybridFixturePlacement != 0;
int32 CoreHybridCliffOnly = 0;
FParse::Value(*Params, TEXT("corehybridcliffonly="), CoreHybridCliffOnly);
OutArguments.bCoreHybridCliffOnly = CoreHybridCliffOnly != 0;
int32 CoreHybridFixturesOnly = 0;
FParse::Value(*Params, TEXT("corehybridfixturesonly="), CoreHybridFixturesOnly);
OutArguments.bCoreHybridFixturesOnly = CoreHybridFixturesOnly != 0;
FParse::Value(*Params, TEXT("archetype="), ArchetypeText);
FParse::Value(*Params, TEXT("slot="), OutArguments.Slot);
FParse::Value(*Params, TEXT("strateref="), OutArguments.StrateReference);
FParse::Value(*Params, TEXT("settingsref="), OutArguments.SettingsReference);
FParse::Value(*Params, TEXT("coreworldassembly="), OutArguments.CoreWorldAssemblyManifestPath);
FParse::Value(*Params, TEXT("modes="), ModesText);
// FParse::Value treats a comma as a command-line separator in some UE commandlet
// launch paths. Recover the complete token so both -modes=walk,export and the
// quoted -modes="walk,export" form have the same meaning.
const int32 ModesOffset = Params.Find(TEXT("modes="), ESearchCase::IgnoreCase);
if (ModesOffset != INDEX_NONE)
{
ModesText = Params.Mid(ModesOffset + 6);
const int32 ModesEnd = ModesText.Find(TEXT(" "));
if (ModesEnd != INDEX_NONE)
{
ModesText.LeftInline(ModesEnd);
}
ModesText.TrimQuotesInline();
}
int32 WormsEnabled = 1;
if (FParse::Value(*Params, TEXT("wormsenabled="), WormsEnabled))
{
OutArguments.bWormsEnabledOverride = true;
OutArguments.bWormsEnabled = WormsEnabled != 0;
}
if (Params.Contains(TEXT("wormstrength="), ESearchCase::IgnoreCase)
&& FParse::Value(*Params, TEXT("wormstrength="), OutArguments.WormStrength))
{
OutArguments.bWormStrengthOverride = true;
}
OutArguments.bProfileDensity = FParse::Param(*Params, TEXT("profiledensity"));
OutArguments.bProfileDensityFull = FParse::Param(*Params, TEXT("profiledensityfull"));
OutArguments.bProfileDensity |= OutArguments.bProfileDensityFull;
OutArguments.bPerfAttribution = FParse::Param(*Params, TEXT("perfattribution"));
OutArguments.bSampleStacks = FParse::Param(*Params, TEXT("samplestacks"));
int32 SampleStacksIntervalUs = static_cast<int32>(
FVoxelStackSampler::DefaultIntervalUs);
if (FParse::Value(*Params, TEXT("samplestacks="), SampleStacksIntervalUs))
{
OutArguments.bSampleStacks = true;
OutArguments.SampleStacksIntervalUs = static_cast<uint32>(FMath::Max(0, SampleStacksIntervalUs));
}
OutArguments.bProfileLod = FParse::Param(*Params, TEXT("profilelod"));
OutArguments.bOpBounds = FParse::Param(*Params, TEXT("opbounds"));
OutArguments.bTunnelCoreProbe = FParse::Param(*Params, TEXT("tunnelcoreprobe"));
FParse::Value(*Params, TEXT("out="), OutText);
FParse::Value(*Params, TEXT("renderwidth="), OutArguments.RenderWidth);
FParse::Value(*Params, TEXT("renderheight="), OutArguments.RenderHeight);
FParse::Value(*Params, TEXT("renderstep="), OutArguments.RenderStepVoxels);
FParse::Value(*Params, TEXT("rendermaxdistance="), OutArguments.RenderMaxDistanceVoxels);
FParse::Value(*Params, TEXT("exportsize="), OutArguments.ExportSize);
FParse::Value(*Params, TEXT("exportstep="), OutArguments.ExportStep);
const bool bExportTileXSpecified = Params.Contains(
TEXT("exporttilex="), ESearchCase::IgnoreCase);
const bool bExportTileYSpecified = Params.Contains(
TEXT("exporttiley="), ESearchCase::IgnoreCase);
const bool bExportTileZSpecified = Params.Contains(
TEXT("exporttilez="), ESearchCase::IgnoreCase);
if (bExportTileXSpecified || bExportTileYSpecified || bExportTileZSpecified)
{
if (!bExportTileXSpecified || !bExportTileYSpecified || !bExportTileZSpecified
|| !FParse::Value(*Params, TEXT("exporttilex="), OutArguments.ExportTileX)
|| !FParse::Value(*Params, TEXT("exporttiley="), OutArguments.ExportTileY)
|| !FParse::Value(*Params, TEXT("exporttilez="), OutArguments.ExportTileZ))
{
OutError = TEXT("exporttilex, exporttiley, and exporttilez must be supplied together as level-0 tile coordinates.");
return false;
}
OutArguments.bExportTileOverride = true;
}
int32 DensityGridReuse = OutArguments.bReuseDensityGrid ? 1 : 0;
FParse::Value(*Params, TEXT("densitygridreuse="), DensityGridReuse);
OutArguments.bReuseDensityGrid = DensityGridReuse != 0;
int32 BlockEarlyOut = OutArguments.bBlockEarlyOut ? 1 : 0;
FParse::Value(*Params, TEXT("blockearlyout="), BlockEarlyOut);
OutArguments.bBlockEarlyOut = BlockEarlyOut != 0;
int32 GameRuntimeConfig = OutArguments.bGameRuntimeConfig ? 1 : 0;
FParse::Value(*Params, TEXT("gameconfig="), GameRuntimeConfig);
FParse::Value(*Params, TEXT("paritygame="), GameRuntimeConfig);
OutArguments.bGameRuntimeConfig = GameRuntimeConfig != 0;
FParse::Value(*Params, TEXT("meshminbatch="), OutArguments.MeshMinBatchSize);
OutArguments.MeshMinBatchSize = FMath::Clamp(OutArguments.MeshMinBatchSize, 1, 64);
int32 Lod = 0;
if (FParse::Value(*Params, TEXT("lod="), Lod))
{
if (Lod < 0 || Lod > 3)
{
OutError = TEXT("lod must be an integer in [0,3].");
return false;
}
OutArguments.ExportStep = 1 << Lod;
}
FParse::Value(*Params, TEXT("maxwalkcells="), OutArguments.MaxWalkCells);
FParse::Value(*Params, TEXT("budget="), OutArguments.BudgetMinutes);
const bool bSurfaceRoughnessSpecified = Params.Contains(
TEXT("surfaceroughness="), ESearchCase::IgnoreCase);
const bool bSurfaceRoughnessParsed = FParse::Value(
*Params, TEXT("surfaceroughness="), OutArguments.SurfaceRoughness);
OutArguments.bSurfaceRoughnessOverride = bSurfaceRoughnessSpecified;
const bool bTunnelWarpStrengthSpecified = Params.Contains(
TEXT("tunnelwarp="), ESearchCase::IgnoreCase);
const bool bTunnelWarpStrengthParsed = FParse::Value(
*Params, TEXT("tunnelwarp="), OutArguments.TunnelWarpStrength);
OutArguments.bTunnelWarpStrengthOverride = bTunnelWarpStrengthSpecified;
FString RoughnessNoiseTypeText;
const bool bRoughnessNoiseTypeSpecified = Params.Contains(
TEXT("roughnesstype="), ESearchCase::IgnoreCase);
const bool bRoughnessNoiseTypeParsed = FParse::Value(
*Params, TEXT("roughnesstype="), RoughnessNoiseTypeText)
&& ParseNoiseType(RoughnessNoiseTypeText, OutArguments.RoughnessNoiseType);
OutArguments.bRoughnessNoiseTypeOverride = bRoughnessNoiseTypeSpecified;
const bool bRoughnessFrequencySpecified = Params.Contains(
TEXT("roughnessfrequency="), ESearchCase::IgnoreCase);
const bool bRoughnessFrequencyParsed = FParse::Value(
*Params, TEXT("roughnessfrequency="), OutArguments.RoughnessFrequency);
OutArguments.bRoughnessFrequencyOverride = bRoughnessFrequencySpecified;
const bool bTunnelFloorEnabledSpecified = Params.Contains(
TEXT("tunnelfloor="), ESearchCase::IgnoreCase);
const bool bTunnelFloorEnabledParsed = FParse::Value(
*Params, TEXT("tunnelfloor="), OutArguments.TunnelFloorEnabled);
OutArguments.bTunnelFloorEnabledOverride = bTunnelFloorEnabledSpecified;
const bool bTunnelFloorTerracingSpecified = Params.Contains(
TEXT("tunnelfloorterracing="), ESearchCase::IgnoreCase);
const bool bTunnelFloorTerracingParsed = FParse::Value(
*Params, TEXT("tunnelfloorterracing="), OutArguments.TunnelFloorTerracing);
OutArguments.bTunnelFloorTerracingOverride = bTunnelFloorTerracingSpecified;
const bool bTunnelFloorStepSpecified = Params.Contains(
TEXT("tunnelfloorstep="), ESearchCase::IgnoreCase);
const bool bTunnelFloorStepParsed = FParse::Value(
*Params, TEXT("tunnelfloorstep="), OutArguments.TunnelFloorStep);
OutArguments.bTunnelFloorStepOverride = bTunnelFloorStepSpecified;
const bool bTunnelFloorMaxLedgeSpecified = Params.Contains(
TEXT("tunnelfloormaxledge="), ESearchCase::IgnoreCase);
const bool bTunnelFloorMaxLedgeParsed = FParse::Value(
*Params, TEXT("tunnelfloormaxledge="), OutArguments.TunnelFloorMaxLedge);
OutArguments.bTunnelFloorMaxLedgeOverride = bTunnelFloorMaxLedgeSpecified;
const bool bTunnelFloorGentleSpecified = Params.Contains(
TEXT("tunnelfloorgentle="), ESearchCase::IgnoreCase);
const bool bTunnelFloorGentleParsed = FParse::Value(
*Params, TEXT("tunnelfloorgentle="), OutArguments.TunnelFloorGentle);
OutArguments.bTunnelFloorGentleOverride = bTunnelFloorGentleSpecified;
const bool bTunnelFloorLedgesSpecified = Params.Contains(
TEXT("tunnelfloorledges="), ESearchCase::IgnoreCase);
const bool bTunnelFloorLedgesParsed = FParse::Value(
*Params, TEXT("tunnelfloorledges="), OutArguments.TunnelFloorLedges);
OutArguments.bTunnelFloorLedgesOverride = bTunnelFloorLedgesSpecified;
const bool bTunnelFloorMaxLedgesSpecified = Params.Contains(
TEXT("tunnelfloormaxledges="), ESearchCase::IgnoreCase);
const bool bTunnelFloorMaxLedgesParsed = FParse::Value(
*Params, TEXT("tunnelfloormaxledges="), OutArguments.TunnelFloorMaxLedges);
OutArguments.bTunnelFloorMaxLedgesOverride = bTunnelFloorMaxLedgesSpecified;
const bool bRoomMouthRiseSpecified = Params.Contains(
TEXT("roommouthrise="), ESearchCase::IgnoreCase);
const bool bRoomMouthRiseParsed = FParse::Value(
*Params, TEXT("roommouthrise="), OutArguments.RoomMouthRise);
OutArguments.bRoomMouthRiseOverride = bRoomMouthRiseSpecified;
const bool bRoomMouthRiseBlendSpecified = Params.Contains(
TEXT("roommouthriseblend="), ESearchCase::IgnoreCase);
const bool bRoomMouthRiseBlendParsed = FParse::Value(
*Params, TEXT("roommouthriseblend="), OutArguments.RoomMouthRiseBlend);
OutArguments.bRoomMouthRiseBlendOverride = bRoomMouthRiseBlendSpecified;
const bool bPassageFloorWritesSpecified = Params.Contains(
TEXT("passagefloorwrites="), ESearchCase::IgnoreCase);
const bool bPassageFloorWritesParsed = FParse::Value(
*Params, TEXT("passagefloorwrites="), OutArguments.PassageFloorWrites);
OutArguments.bPassageFloorWritesOverride = bPassageFloorWritesSpecified;
FString RenderCameraText;
const bool bRenderCameraSpecified = Params.Contains(
TEXT("rendercamera="), ESearchCase::IgnoreCase);
bool bRenderCameraParsed = false;
if (bRenderCameraSpecified)
{
const int32 CameraOffset = Params.Find(TEXT("rendercamera="), ESearchCase::IgnoreCase);
if (CameraOffset != INDEX_NONE)
{
RenderCameraText = Params.Mid(CameraOffset + 13);
const int32 CameraEnd = RenderCameraText.Find(TEXT(" "));
if (CameraEnd != INDEX_NONE)
{
RenderCameraText.LeftInline(CameraEnd);
}
RenderCameraText.TrimQuotesInline();
bRenderCameraParsed = ParseRenderCamera(RenderCameraText, OutArguments.RenderCamera);
}
}
OutArguments.bRenderCameraOverride = bRenderCameraSpecified;
FString RenderTargetText;
const bool bRenderTargetSpecified = Params.Contains(
TEXT("rendertarget="), ESearchCase::IgnoreCase);
bool bRenderTargetParsed = false;
if (bRenderTargetSpecified)
{
const int32 TargetOffset = Params.Find(TEXT("rendertarget="), ESearchCase::IgnoreCase);
if (TargetOffset != INDEX_NONE)
{
RenderTargetText = Params.Mid(TargetOffset + 13);
const int32 TargetEnd = RenderTargetText.Find(TEXT(" "));
if (TargetEnd != INDEX_NONE)
{
RenderTargetText.LeftInline(TargetEnd);
}
RenderTargetText.TrimQuotesInline();
bRenderTargetParsed = ParseRenderCamera(RenderTargetText, OutArguments.RenderTarget);
}
}
OutArguments.bRenderTargetOverride = bRenderTargetSpecified;
FString DensitySliceText;
const bool bDensitySliceSpecified = Params.Contains(
TEXT("densityslice="), ESearchCase::IgnoreCase);
bool bDensitySliceParsed = false;
if (bDensitySliceSpecified)
{
const int32 SliceOffset = Params.Find(TEXT("densityslice="), ESearchCase::IgnoreCase);
if (SliceOffset != INDEX_NONE)
{
DensitySliceText = Params.Mid(SliceOffset + 13);
const int32 SliceEnd = DensitySliceText.Find(TEXT(" "));
if (SliceEnd != INDEX_NONE)
{
DensitySliceText.LeftInline(SliceEnd);
}
DensitySliceText.TrimQuotesInline();
bDensitySliceParsed = ParseDensitySlice(
DensitySliceText,
OutArguments.DensitySliceOrigin,
OutArguments.DensitySliceDirection,
OutArguments.DensitySliceSamples,
OutArguments.DensitySliceStep);
}
}
OutArguments.bDensitySlice = bDensitySliceSpecified;
int32 FailureFocusRender = 0;
FParse::Value(*Params, TEXT("failurefocus="), FailureFocusRender);
OutArguments.bFailureFocusRender = FailureFocusRender != 0;
if (!ParseArchetype(ArchetypeText, OutArguments.Archetype))
{
OutError = FString::Printf(
TEXT("Unknown archetype '%s'. Expected Maze, TunnelNetwork, FlatPlain, "
"CrystalChamber, SurfaceWorld, VerticalShafts, FloatingIslands, or Underwater."),
*ArchetypeText);
return false;
}
OutArguments.bRender = false;
OutArguments.bWalk = false;
OutArguments.bExport = false;
TArray<FString> Modes;
ModesText.ParseIntoArray(Modes, TEXT(","), true);
for (FString Mode : Modes)
{
Mode.TrimStartAndEndInline();
Mode.ToLowerInline();
if (Mode == TEXT("all"))
{
OutArguments.bRender = true;
OutArguments.bWalk = true;
OutArguments.bExport = true;
}
else if (Mode == TEXT("opbounds"))
{
OutArguments.bOpBounds = true;
}
else if (Mode == TEXT("tunnelcoreprobe"))
{
OutArguments.bTunnelCoreProbe = true;
}
else if (Mode == TEXT("render"))
{
OutArguments.bRender = true;
}
else if (Mode == TEXT("walk"))
{
OutArguments.bWalk = true;
}
else if (Mode == TEXT("export"))
{
OutArguments.bExport = true;
}
else
{
OutError = FString::Printf(
TEXT("Unknown mode '%s'. Expected render, walk, export, opbounds, tunnelcoreprobe, or all."), *Mode);
return false;
}
}
if (!OutArguments.bRender && !OutArguments.bWalk && !OutArguments.bExport
&& !OutArguments.bOpBounds && !OutArguments.bTunnelCoreProbe)
{
OutError = TEXT("At least one mode must be selected.");
return false;
}
// Passage walks need both mouths; a render/export-only generation can inspect an end slot.
// Parity exports also accept the game's authored single-strate layout.
const bool bRequiresInteriorSlot = OutArguments.bWalk;
const int32 MinimumSlot = OutArguments.bGameRuntimeConfig || !bRequiresInteriorSlot ? 0 : 1;
const int32 MaximumSlot = OutArguments.bGameRuntimeConfig || !bRequiresInteriorSlot
? MaxSyntheticStrates - 1 : MaxSyntheticStrates - 2;
if (OutArguments.Slot < MinimumSlot || OutArguments.Slot > MaximumSlot)
{
if (OutArguments.bGameRuntimeConfig)
{
OutError = FString::Printf(
TEXT("slot must be in [0,%d] for game-runtime configuration parity."),
MaxSyntheticStrates - 1);
}
else
{
OutError = FString::Printf(
TEXT("slot must be in [0,%d]; walk mode requires an interior slot in [1,%d] for both passage mouths."),
MaxSyntheticStrates - 1, MaxSyntheticStrates - 2);
}
return false;
}
if (OutArguments.RenderWidth < 16 || OutArguments.RenderHeight < 16
|| static_cast<int64>(OutArguments.RenderWidth)
* static_cast<int64>(OutArguments.RenderHeight) > MaxRenderPixels)
{
OutError = FString::Printf(
TEXT("renderwidth*renderheight must be between 256 pixels and %d pixels."),
MaxRenderPixels);
return false;
}
if (!FMath::IsFinite(OutArguments.RenderStepVoxels)
|| OutArguments.RenderStepVoxels < 0.125f
|| OutArguments.RenderStepVoxels > 8.0f)
{
OutError = TEXT("renderstep must be finite and in [0.125,8] voxels.");
return false;
}
if (!FMath::IsFinite(OutArguments.RenderMaxDistanceVoxels)
|| OutArguments.RenderMaxDistanceVoxels <= 0.0f
|| OutArguments.RenderMaxDistanceVoxels > 2048.0f)
{
OutError = TEXT("rendermaxdistance must be finite and in (0,2048] voxels.");
return false;
}
// Render does not budget density samples: pixels are rasterised from the one canonical
// mesh. Keep the legacy renderstep/maxdistance arguments for compatible invocations and use
// only maxdistance as the raster depth clip.
if (OutArguments.ExportSize < CHUNK_SIZE
|| OutArguments.ExportSize > MaxExportSize
|| OutArguments.ExportSize % CHUNK_SIZE != 0)
{
OutError = FString::Printf(
TEXT("exportsize must be a multiple of CHUNK_SIZE (%d) in [%d,%d] cells per axis."),
CHUNK_SIZE,
CHUNK_SIZE,
MaxExportSize);
return false;
}
if (OutArguments.ExportStep < 1
|| OutArguments.ExportStep > 8
|| (OutArguments.ExportStep & (OutArguments.ExportStep - 1)) != 0
|| CHUNK_SIZE % OutArguments.ExportStep != 0)
{
OutError = TEXT("exportstep must be a power of two in [1,8] and divide CHUNK_SIZE.");
return false;
}
if (OutArguments.MaxWalkCells <= 0)
{
OutError = TEXT("maxwalkcells must be greater than zero.");
return false;
}
if (!FMath::IsFinite(OutArguments.BudgetMinutes)
|| OutArguments.BudgetMinutes <= 0.0f
|| OutArguments.BudgetMinutes > MaxBudgetMinutes)
{
OutError = FString::Printf(
TEXT("budget must be finite and in (0,%g] minutes."), MaxBudgetMinutes);
return false;
}
if (OutArguments.bSampleStacks
&& (OutArguments.SampleStacksIntervalUs < FVoxelStackSampler::MinIntervalUs
|| OutArguments.SampleStacksIntervalUs > FVoxelStackSampler::MaxIntervalUs))
{
OutError = FString::Printf(
TEXT("samplestacks interval must be in [%u,%u] microseconds."),
FVoxelStackSampler::MinIntervalUs,
FVoxelStackSampler::MaxIntervalUs);
return false;
}
if (OutArguments.bSurfaceRoughnessOverride
&& !bSurfaceRoughnessParsed)
{
OutError = TEXT("surfaceroughness must be a finite number greater than or equal to zero.");
return false;
}
if (OutArguments.bSurfaceRoughnessOverride
&& (!FMath::IsFinite(OutArguments.SurfaceRoughness)
|| OutArguments.SurfaceRoughness < 0.0f))
{
OutError = TEXT("surfaceroughness must be finite and greater than or equal to zero.");
return false;
}
if (OutArguments.bTunnelWarpStrengthOverride
&& (!bTunnelWarpStrengthParsed
|| !FMath::IsFinite(OutArguments.TunnelWarpStrength)
|| OutArguments.TunnelWarpStrength < 0.0f))
{
OutError = TEXT("tunnelwarp must be finite and greater than or equal to zero.");
return false;
}
if (OutArguments.bRoughnessNoiseTypeOverride && !bRoughnessNoiseTypeParsed)
{
OutError = TEXT("roughnesstype must be FBM, Ridged, Mixed, or Cellular.");
return false;
}
if (OutArguments.bRoughnessFrequencyOverride
&& (!bRoughnessFrequencyParsed
|| !FMath::IsFinite(OutArguments.RoughnessFrequency)
|| OutArguments.RoughnessFrequency <= 0.0f))
{
OutError = TEXT("roughnessfrequency must be finite and greater than zero.");
return false;
}
if (OutArguments.bTunnelFloorEnabledOverride
&& (!bTunnelFloorEnabledParsed
|| (OutArguments.TunnelFloorEnabled != 0
&& OutArguments.TunnelFloorEnabled != 1)))
{
OutError = TEXT("tunnelfloor must be 0 or 1.");
return false;
}
if (OutArguments.bTunnelFloorTerracingOverride
&& (!bTunnelFloorTerracingParsed
|| (OutArguments.TunnelFloorTerracing != 0
&& OutArguments.TunnelFloorTerracing != 1)))
{
OutError = TEXT("tunnelfloorterracing must be 0 or 1.");
return false;
}
if (OutArguments.bTunnelFloorStepOverride
&& (!bTunnelFloorStepParsed
|| !FMath::IsFinite(OutArguments.TunnelFloorStep)
|| OutArguments.TunnelFloorStep < 0.0f))
{
OutError = TEXT("tunnelfloorstep must be finite and non-negative.");
return false;
}
if (OutArguments.bTunnelFloorMaxLedgeOverride
&& (!bTunnelFloorMaxLedgeParsed
|| !FMath::IsFinite(OutArguments.TunnelFloorMaxLedge)
|| OutArguments.TunnelFloorMaxLedge <= 0.0f))
{
OutError = TEXT("tunnelfloormaxledge must be finite and greater than zero.");
return false;
}
if (OutArguments.bTunnelFloorGentleOverride
&& (!bTunnelFloorGentleParsed
|| !FMath::IsFinite(OutArguments.TunnelFloorGentle)
|| OutArguments.TunnelFloorGentle < 0.0f
|| OutArguments.TunnelFloorGentle
> VoxelPassageGeometry::TunnelFloorGentleSlopeMaximumDegrees))
{
OutError = TEXT("tunnelfloorgentle must be finite degrees in [0,90].");
return false;
}
if (OutArguments.bTunnelFloorLedgesOverride
&& (!bTunnelFloorLedgesParsed
|| OutArguments.TunnelFloorLedges < 0
|| OutArguments.TunnelFloorLedges > 4096))
{
OutError = TEXT("tunnelfloorledges must be an integer in [0,4096].");
return false;
}
if (OutArguments.bTunnelFloorMaxLedgesOverride
&& (!bTunnelFloorMaxLedgesParsed
|| OutArguments.TunnelFloorMaxLedges < 1
|| OutArguments.TunnelFloorMaxLedges > 4096))
{
OutError = TEXT("tunnelfloormaxledges must be an integer in [1,4096].");
return false;
}
if (OutArguments.bRoomMouthRiseOverride
&& (!bRoomMouthRiseParsed
|| !FMath::IsFinite(OutArguments.RoomMouthRise)
|| OutArguments.RoomMouthRise < 0.0f
|| OutArguments.RoomMouthRise > 1.0f))
{
OutError = TEXT("roommouthrise must be finite and in [0,1].");
return false;
}
if (OutArguments.bRoomMouthRiseBlendOverride
&& (!bRoomMouthRiseBlendParsed
|| !FMath::IsFinite(OutArguments.RoomMouthRiseBlend)
|| OutArguments.RoomMouthRiseBlend < 8.0f
|| OutArguments.RoomMouthRiseBlend > 128.0f))
{
OutError = TEXT("roommouthriseblend must be finite and in [8,128].");
return false;
}
if (OutArguments.bPassageFloorWritesOverride
&& (!bPassageFloorWritesParsed
|| (OutArguments.PassageFloorWrites != 0
&& OutArguments.PassageFloorWrites != 1)))
{
OutError = TEXT("passagefloorwrites must be 0 or 1.");
return false;
}
if (OutArguments.bRenderCameraOverride && !bRenderCameraParsed)
{
OutError = TEXT("rendercamera must be three finite comma-separated voxel coordinates.");
return false;
}
if (OutArguments.bRenderTargetOverride && !bRenderTargetParsed)
{
OutError = TEXT("rendertarget must be three finite comma-separated voxel coordinates.");
return false;
}
if (OutArguments.bDensitySlice && !bDensitySliceParsed)
{
OutError = TEXT(
"densityslice must be origin x,y,z, direction x,y,z, sample count, and positive step.");
return false;
}
if (OutArguments.bStandaloneWorldExport
&& (!OutArguments.bExport || OutArguments.SettingsReference.IsEmpty()
|| !OutArguments.StrateReference.IsEmpty()))
{
OutError = TEXT("standaloneworldexport requires modes=export and settingsref, and must omit strateref.");
return false;
}
if (OutArguments.bOwnerFourStratePlan
&& (OutArguments.SettingsReference.IsEmpty() || !OutArguments.StrateReference.IsEmpty()))
{
OutError = TEXT("ownerfourstrateplan requires settingsref and must omit strateref.");
return false;
}
if (OutArguments.bSkipStandaloneCoreAdapterParity && !OutArguments.bStandaloneWorldExport)
{
OutError = TEXT("skipstandalonecoreadapterparity requires standaloneworldexport.");
return false;
}
if (OutArguments.bCoreWorldTest
&& (OutArguments.SettingsReference.IsEmpty() || !OutArguments.StrateReference.IsEmpty()))
{
OutError = TEXT("coreworldtest requires settingsref and must omit strateref.");
return false;
}
if (OutArguments.bCoreWorldStreamTraceOnly && !OutArguments.bCoreWorldTest)
{
OutError = TEXT("coreworldstreamtrace requires coreworldtest.");
return false;
}
if ((OutArguments.bCoreHybridMesher || OutArguments.bCoreHybridFixturePlacement
|| OutArguments.bCoreHybridCliffOnly || OutArguments.bCoreHybridFixturesOnly
|| OutArguments.bCoreFloatingIslandTest || OutArguments.bCoreFallbackTest
|| OutArguments.bCoreOffTest || OutArguments.bCorePlanOnlyTest)
&& !OutArguments.bCoreWorldTest)
{
OutError = TEXT("Core hybrid tests require coreworldtest.");
return false;
}
if (OutArguments.bCoreHybridFixturePlacement && !OutArguments.bCoreHybridMesher)
{
OutError = TEXT("corehybridfixtureplacement requires corehybridmesher=1.");
return false;
}
if (OutArguments.bCoreHybridCliffOnly && !OutArguments.bCoreHybridMesher)
{
OutError = TEXT("corehybridcliffonly requires corehybridmesher=1.");
return false;
}
if (OutArguments.bCoreHybridFixturesOnly && !OutArguments.bCoreHybridMesher)
{
OutError = TEXT("corehybridfixturesonly requires corehybridmesher=1.");
return false;
}
if (OutArguments.bCoreHybridFixturesOnly
&& (OutArguments.bCoreHybridCliffOnly || OutArguments.bCoreHybridFixturePlacement))
{
OutError = TEXT("corehybridfixturesonly cannot be combined with the other focused hybrid modes.");
return false;
}
const int32 CoreTestModeCount = (OutArguments.bCoreFloatingIslandTest ? 1 : 0)
+ (OutArguments.bCoreFallbackTest ? 1 : 0)
+ (OutArguments.bCoreOffTest ? 1 : 0)
+ (OutArguments.bCorePlanOnlyTest ? 1 : 0);
if (CoreTestModeCount > 1)
{
OutError = TEXT("coreislandtest, corefallbacktest, coreofftest, and coreplantest are exclusive modes.");
return false;
}
if (OutArguments.bCoreFallbackTest && OutArguments.CoreWorldAssemblyManifestPath.IsEmpty())
{
OutError = TEXT("corefallbacktest requires a staged coreworldassembly override.");
return false;
}
if (CoreTestModeCount > 0
&& (OutArguments.bCoreHybridMesher || OutArguments.bCoreHybridFixturePlacement
|| OutArguments.bCoreHybridCliffOnly || OutArguments.bCoreHybridFixturesOnly))
{
OutError = TEXT("The selected Core world mode cannot be combined with hybrid mesher fixtures.");
return false;
}
if (OutArguments.bCoreTestSeedOverride && !OutArguments.bCorePlanOnlyTest)
{
OutError = TEXT("coretestseed is accepted only with coreplantest.");
return false;
}
if (OutArguments.bCoreFloatingIslandTest
&& (OutArguments.bCoreHybridMesher || OutArguments.bCoreHybridFixturePlacement
|| OutArguments.bCoreHybridCliffOnly || OutArguments.bCoreHybridFixturesOnly))
{
OutError = TEXT("coreislandtest cannot be combined with hybrid test modes.");
return false;
}
if (!OutArguments.CoreWorldAssemblyManifestPath.IsEmpty() && !OutArguments.bCoreWorldTest)
{
OutError = TEXT("coreworldassembly is accepted only by coreworldtest.");
return false;
}
if (OutText.IsEmpty())
{
OutText = FPaths::Combine(
VoxelForgeSavedRoot(),
FString::Printf(TEXT("Explore/seed%d_%s_slot%d"),
OutArguments.Seed,
ArchetypeName(OutArguments.Archetype),
OutArguments.Slot));
}
if (FPaths::IsRelative(OutText))
{
OutError = FString::Printf(
TEXT("out must be an absolute path under '%s'."), *VoxelForgeSavedRoot());
return false;
}
OutArguments.OutDirectory = FPaths::ConvertRelativePathToFull(OutText);
FPaths::NormalizeDirectoryName(OutArguments.OutDirectory);
if (!IsUnderVoxelForgeSaved(OutArguments.OutDirectory))
{
OutError = FString::Printf(
TEXT("out must be an absolute path under '%s' (got '%s')."),
*VoxelForgeSavedRoot(), *OutArguments.OutDirectory);
return false;
}
return true;
}
VoxelDensityProfile::EMode ExploreProfileMode(const FExploreArguments& Arguments)
{
if (Arguments.bPerfAttribution)
{
return VoxelDensityProfile::EMode::Attribution;
}
return Arguments.bProfileDensityFull
? VoxelDensityProfile::EMode::Full
: VoxelDensityProfile::EMode::Sampled;
}
struct FExploreBudget
{
double StartSeconds = 0.0;
double LimitSeconds = 0.0;
bool bTruncated = false;
FString TruncatedDuring;
TArray<FString> CompletedModes;
FExploreBudget(double InStartSeconds, double InLimitSeconds)
: StartSeconds(InStartSeconds)
, LimitSeconds(InLimitSeconds)
{
}
double ElapsedSeconds() const
{
return FPlatformTime::Seconds() - StartSeconds;
}
double RemainingSeconds() const
{
return LimitSeconds - ElapsedSeconds();
}
bool ShouldStop(const TCHAR* Phase)
{
if (bTruncated)
{
return true;
}
// Leave a small deterministic cleanup window for the current phase to close files and
// emit the report. The owner-facing hard ceiling is still enforced by the argument cap at
// 30 minutes; the default 25-minute budget leaves substantially more headroom.
constexpr double CleanupReserveSeconds = 0.25;
if (RemainingSeconds() <= CleanupReserveSeconds)
{
bTruncated = true;
TruncatedDuring = Phase != nullptr ? FString(Phase) : TEXT("unknown");
UE_LOG(LogTemp, Warning,
TEXT("[VoxelForgeExplore] budget reached during %s; keeping completed artifacts."),
*TruncatedDuring);
return true;
}
return false;
}
void CompleteMode(const TCHAR* Mode)
{
if (Mode == nullptr || bTruncated)
{
return;
}
CompletedModes.AddUnique(FString(Mode));
}
};
struct FExploreMeshTriangle
{
FVector A = FVector::ZeroVector;
FVector B = FVector::ZeroVector;
FVector C = FVector::ZeroVector;
FVector NormalA = FVector::UpVector;
FVector NormalB = FVector::UpVector;
FVector NormalC = FVector::UpVector;
FVector Min = FVector::ZeroVector;
FVector Max = FVector::ZeroVector;
FVector Centre = FVector::ZeroVector;
FVector Normal = FVector::UpVector;
};
struct FExploreMeshBvhNode
{
FVector Min = FVector::ZeroVector;
FVector Max = FVector::ZeroVector;
int32 Left = INDEX_NONE;
int32 Right = INDEX_NONE;
int32 FirstTriangle = 0;
int32 TriangleCount = 0;
bool IsLeaf() const
{
return Left == INDEX_NONE && Right == INDEX_NONE;
}
};
bool ApplyOwnerFourStratePlan(UVoxelSettings* Settings,
TArray<TStrongObjectPtr<UVoxelStrateDefinition>>& OutDefinitions,
bool bDuplicateDefinitions, FString& OutError)
{
if (Settings == nullptr)
{
OutError = TEXT("The transient owner plan has no settings object.");
return false;
}
if (!Settings->Season.IsNull())
{
OutError = TEXT("The transient four-strate owner plan cannot replace an assigned season manifest.");
return false;
}
static const TCHAR* AssetPaths[] = {
TEXT("/Game/VoxelForge/DA_Strate1.DA_Strate1"),
TEXT("/Game/VoxelForge/DA_Strate2.DA_Strate2"),
TEXT("/Game/VoxelForge/DA_Strate3.DA_Strate3"),
TEXT("/Game/VoxelForge/DA_Strate4.DA_Strate4")
};
Settings->TotalStrates = UE_ARRAY_COUNT(AssetPaths);
Settings->StratePool.Reset();
Settings->FixedStrates.Reset();
OutDefinitions.Reset();
OutDefinitions.Reserve(UE_ARRAY_COUNT(AssetPaths));
for (int32 Index = 0; Index < UE_ARRAY_COUNT(AssetPaths); ++Index)
{
UVoxelStrateDefinition* AuthoredDefinition = LoadObject<UVoxelStrateDefinition>(
nullptr, AssetPaths[Index]);
if (AuthoredDefinition == nullptr)
{
OutError = FString::Printf(
TEXT("Could not load staged owner strate asset '%s'."), AssetPaths[Index]);
return false;
}
UVoxelStrateDefinition* Definition = AuthoredDefinition;
if (bDuplicateDefinitions)
{
Definition = DuplicateObject<UVoxelStrateDefinition>(
AuthoredDefinition, GetTransientPackage(),
FName(*FString::Printf(TEXT("VoxelForgeOwnerPlan_Strate_%d"), Index)));
if (Definition == nullptr)
{
OutError = FString::Printf(
TEXT("Could not make a transient copy of owner strate asset '%s'."), AssetPaths[Index]);
return false;
}
}
Settings->FixedStrates.Add(Index,
TSoftObjectPtr<UVoxelStrateDefinition>(Definition));
OutDefinitions.Add(TStrongObjectPtr<UVoxelStrateDefinition>(Definition));
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeOwnerPlan] transient fixed slots=4 assets=DA_Strate1,DA_Strate2,DA_Strate3,DA_Strate4 settings_asset_untouched=1"));
return true;
}
struct FExploreWorld
{
TStrongObjectPtr<UVoxelSettings> Settings;
TStrongObjectPtr<UVoxelStrateManager> Manager;
TStrongObjectPtr<UVoxelDiffLayer> DiffLayer;
TStrongObjectPtr<UVoxelGenerator> Generator;
TStrongObjectPtr<UVoxelMarchingCubesMesher> Mesher;
TArray<TStrongObjectPtr<UVoxelStrateDefinition>> Definitions;
// One canonical mesh cache for this commandlet run. Render and export consume these exact
// triangles; neither path is allowed to ask the density field once per pixel.
FVoxelMeshData ExploreMesh;
FVoxelSharedDensityGrid ExploreSharedDensityGrid;
FIntVector ExploreMeshOrigin = FIntVector::ZeroValue;
int32 ExploreMeshSize = 0;
int32 ExploreMeshTileCount = 0;
int32 ExploreMeshTilesCompleted = 0;
// Per-tile geometry identity is retained in canonical Z/Y/X order so an optimization can be
// compared without treating one aggregate hash or triangle total as a shape measurement.
TArray<int32> ExploreTileTriangleCounts;
TArray<FString> ExploreTileGeometryHashes;
double ExploreMeshSeconds = 0.0;
// Exact wall-clock partition of ExploreMeshSeconds. The first three fields end at the
// parallel tile launch; merge closes the mesh clock. Remainder is intentionally retained
// instead of rounding the ledger to 100%.
double ExploreMeshPreparationSeconds = 0.0;
double ExploreMeshPreTileSetupSeconds = 0.0;
double ExploreMeshMergeSeconds = 0.0;
double ExploreMeshAccountingRemainderSeconds = 0.0;
double ExploreMeshHashSeconds = 0.0;
double ExploreDensityGridSeconds = 0.0;
int64 ExploreDensityGridTotalSamples = 0;
int64 ExploreDensityGridUniqueSamples = 0;
int64 ExploreDensityGridDuplicateSamplesBefore = 0;
int64 ExploreDensityGridDuplicateSamplesAfter = 0;
bool bExploreDensityGridReused = false;
int32 ExploreMeshTaskWorkerCount = 0;
int32 ExploreMeshTaskJobCount = 0;
int32 ExploreMeshTaskMinBatchSize = 1;
double ExploreMeshTaskMinSeconds = 0.0;
double ExploreMeshTaskMeanSeconds = 0.0;
double ExploreMeshTaskMaxSeconds = 0.0;
double ExploreMeshTaskSumSeconds = 0.0;
double ExploreMeshTaskWallSeconds = 0.0;
double ExploreMeshTaskLaunchOverheadSeconds = 0.0;
bool bExploreMeshBuilt = false;
bool bExploreMeshComplete = false;
FString ExploreMeshError;
FString ExploreGeometryHash;
// A deterministic CPU acceleration structure over the canonical triangles. It is built once
// after meshing and reused by every viewpoint; no density query is reachable from raster.
TArray<FExploreMeshTriangle> ExploreTriangles;
TArray<int32> ExploreTriangleOrder;
TArray<FExploreMeshBvhNode> ExploreBvhNodes;
int32 ExploreBvhRoot = INDEX_NONE;
double ExploreAccelerationSeconds = 0.0;
bool bExploreAccelerationBuilt = false;
int32 TargetBottomWorldZ = 0;
int32 TargetTopWorldZ = 0;
float TargetBoundarySealThickness = 4.0f;
bool Build(const FExploreArguments& Arguments, FString& OutError)
{
const bool bUseAuthoredSettings = !Arguments.SettingsReference.IsEmpty();
const bool bUseAuthoredStrate = !Arguments.StrateReference.IsEmpty();
UVoxelStrateDefinition* AuthoredDefinition = nullptr;
if (bUseAuthoredStrate)
{
AuthoredDefinition = LoadObject<UVoxelStrateDefinition>(
nullptr, *Arguments.StrateReference);
if (AuthoredDefinition == nullptr)
{
OutError = FString::Printf(
TEXT("Could not load staged strate asset '%s'."),
*Arguments.StrateReference);
return false;
}
}
if (bUseAuthoredSettings)
{
UVoxelSettings* AuthoredSettings = LoadObject<UVoxelSettings>(
nullptr, *Arguments.SettingsReference);
if (AuthoredSettings == nullptr)
{
OutError = FString::Printf(
TEXT("Could not load staged settings asset '%s'."),
*Arguments.SettingsReference);
return false;
}
Settings = TStrongObjectPtr<UVoxelSettings>(
DuplicateObject<UVoxelSettings>(AuthoredSettings, GetTransientPackage()));
if (!Settings.IsValid())
{
OutError = TEXT("Could not duplicate the staged settings asset into the transient package.");
return false;
}
}
else
{
Settings = TStrongObjectPtr<UVoxelSettings>(
NewObject<UVoxelSettings>(GetTransientPackage(), NAME_None, RF_Transient));
}
if (bUseAuthoredSettings)
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeScale3AssetSettings] view_xy=%d view_up=%d view_down=%d "
"pawn_collision_radius_chunks=%d strate_view_margin_chunks=%d clip_radius=%d "
"render_distance_chunks=%d world_radius_voxels=%.6g edge_seal_thickness_voxels=%.6g "
"decoration_radius_chunks=%d decoration_near_radius_chunks=%d "
"decoration_region_size_cells=%d decoration_spacing_voxels=%d "
"decoration_far_spacing_voxels=%d decoration_march_step_voxels=%d "
"decoration_column_depth_voxels=%d density_volume_resolution=%d "
"density_volume_debug_radius_cells=%d origin_spine_radius_voxels=%.6g "
"max_brush_radius_voxels=%.6g max_total_volume_voxels3=%.6g "
"inter_strate_gap_chunks=%d skirt_cells=%.6g"),
Settings->ViewDistanceXY, Settings->ViewDistanceUp, Settings->ViewDistanceDown,
Settings->PawnCollisionRadiusChunks, Settings->StrateViewMarginChunks,
Settings->ClipRadius, Settings->RenderDistanceChunks,
Settings->WorldRadiusVoxels, Settings->EdgeSealThickness,
Settings->DecorationRadiusChunks, Settings->DecorationNearRadiusChunks,
Settings->DecorationRegionSizeCells, Settings->DecorationSpacingVoxels,
Settings->DecorationFarSpacingVoxels, Settings->DecorationMarchStepVoxels,
Settings->DecorationColumnDepthVoxels, Settings->DensityVolumeResolution,
Settings->DensityVolumeDebugRadiusCells, Settings->OriginSpineRadius,
Settings->MaxBrushRadius, Settings->MaxTotalVolume,
Settings->InterStrateGapChunks, Settings->SkirtCells);
}
const int32 NumStrates = Arguments.bOwnerFourStratePlan ? 4 : bUseAuthoredSettings
? (Arguments.bGameRuntimeConfig
? FMath::Max(Settings->TotalStrates, 1)
: FMath::Max(Settings->TotalStrates, Arguments.Slot + 2))
: Arguments.Slot + 2;
if (NumStrates > MaxSyntheticStrates)
{
OutError = TEXT("The synthetic layout would exceed the commandlet strate cap.");
return false;
}
if (!bUseAuthoredSettings)
{
Settings->Seed = Arguments.Seed;
Settings->TotalStrates = NumStrates;
Settings->InterStrateGapChunks = 0;
Settings->WorldRadiusVoxels = 0.0f;
Settings->EdgeSealThickness = 128.0f;
Settings->OriginSpineRadius = 28.0f;
Settings->bOpenSurfaceEntry = true;
}
else if (Arguments.bSeedOverride)
{
Settings->Seed = Arguments.Seed;
}
// The transient fixed-definition layout may need to extend an authored settings asset
// for a requested interior slot. Keep that duplicate's advertised count consistent; the
// loaded package remains untouched.
Settings->TotalStrates = NumStrates;
if (Arguments.bOwnerFourStratePlan)
{
if (!ApplyOwnerFourStratePlan(Settings.Get(), Definitions, true, OutError))
{
return false;
}
}
else
{
if (bUseAuthoredStrate)
{
// Keep the historical single-asset interior probe isolated from loaded packages.
Settings->StratePool.Reset();
Settings->FixedStrates.Reset();
}
Definitions.Reserve(NumStrates);
for (int32 Index = 0; Index < NumStrates; ++Index)
{
UVoxelStrateDefinition* Definition = bUseAuthoredStrate
? DuplicateObject<UVoxelStrateDefinition>(
AuthoredDefinition, GetTransientPackage())
: NewObject<UVoxelStrateDefinition>(
GetTransientPackage(), NAME_None, RF_Transient);
if (Definition == nullptr)
{
OutError = FString::Printf(
TEXT("Could not create transient strate definition for slot %d."), Index);
return false;
}
if (!bUseAuthoredStrate)
{
Definition->StrateName = FText::FromString(FString::Printf(
TEXT("VoxelForgeExplore_%s_slot%d"),
ArchetypeName(Index == Arguments.Slot
? Arguments.Archetype
: ECaveGeneratorType::TunnelNetwork),
Index));
Definition->StrateDescription = FText::FromString(
TEXT("Transient, deterministic commandlet definition."));
// The commandlet's acceptance sweep uses the owner-facing base spacing:
// 8 chunks * 32 voxels * 0.125 m = 32 m per strate. Production assets may be taller;
// this keeps the measured tunnel arithmetic tied to the stated ~32 m descent.
Definition->StrateHeightInChunks = 8;
Definition->TransitionType = EVoxelStrateTransition::Hard;
Definition->GeneratorType = Index == Arguments.Slot
? Arguments.Archetype
: ECaveGeneratorType::TunnelNetwork;
if (Arguments.bWormsEnabledOverride)
{
Definition->bEnableWorms = Arguments.bWormsEnabled;
}
if (Arguments.bWormStrengthOverride
&& (Definition->GeneratorType == ECaveGeneratorType::TunnelNetwork
|| Definition->GeneratorType == ECaveGeneratorType::Underwater))
{
Definition->GenerationParams.WormStrength = Arguments.WormStrength;
}
}
const TSoftObjectPtr<UVoxelStrateDefinition> SoftDefinition(Definition);
Settings->FixedStrates.Add(Index, SoftDefinition);
if (!bUseAuthoredStrate)
{
Settings->StratePool.Add(SoftDefinition);
}
Definitions.Add(TStrongObjectPtr<UVoxelStrateDefinition>(Definition));
}
}
auto ApplyTargetOverrides = [&Arguments](UVoxelStrateDefinition* Definition) -> bool
{
if (Definition == nullptr) { return false; }
const ECaveGeneratorType TargetArchetype = Definition->GeneratorType;
if (Arguments.bSurfaceRoughnessOverride)
{
switch (TargetArchetype)
{
case ECaveGeneratorType::FlatPlain:
case ECaveGeneratorType::CrystalChamber:
Definition->SlabParams.FloorRoughness = Arguments.SurfaceRoughness;
Definition->SlabParams.CeilingRoughness = Arguments.SurfaceRoughness;
break;
case ECaveGeneratorType::Maze:
Definition->MazeParams.SurfaceRoughness = Arguments.SurfaceRoughness;
break;
case ECaveGeneratorType::SurfaceWorld:
Definition->SurfaceParams.SurfaceRoughness = Arguments.SurfaceRoughness;
break;
case ECaveGeneratorType::VerticalShafts:
Definition->VerticalShaftParams.SurfaceRoughness = Arguments.SurfaceRoughness;
break;
case ECaveGeneratorType::FloatingIslands:
Definition->FloatingIslandParams.SurfaceRoughness = Arguments.SurfaceRoughness;
break;
case ECaveGeneratorType::TunnelNetwork:
case ECaveGeneratorType::Underwater:
default:
Definition->GenerationParams.SurfaceRoughness = Arguments.SurfaceRoughness;
break;
}
}
if (Arguments.bRoughnessNoiseTypeOverride
|| Arguments.bRoughnessFrequencyOverride)
{
if (TargetArchetype != ECaveGeneratorType::TunnelNetwork
&& TargetArchetype != ECaveGeneratorType::Underwater)
{
return false;
}
if (Arguments.bRoughnessNoiseTypeOverride)
{
Definition->GenerationParams.RoughnessNoiseType = Arguments.RoughnessNoiseType;
}
if (Arguments.bRoughnessFrequencyOverride)
{
Definition->GenerationParams.RoughnessFrequency = Arguments.RoughnessFrequency;
}
}
FStrateGenerationParams& TunnelParams = Definition->GenerationParams;
if (Arguments.bTunnelWarpStrengthOverride)
{
if (TargetArchetype != ECaveGeneratorType::TunnelNetwork
&& TargetArchetype != ECaveGeneratorType::Underwater)
{
return false;
}
TunnelParams.TunnelWarpStrength = Arguments.TunnelWarpStrength;
}
if (Arguments.bTunnelFloorEnabledOverride)
{
// Retain the command-line spelling for old probes, but do not feed the removed
// floor switch into generation. The serialized property has the same
// compatibility-only treatment in UVoxelStrateDefinition::PostLoad.
}
if (Arguments.bTunnelFloorTerracingOverride)
{
TunnelParams.bTunnelFloorTerracingEnabled = Arguments.TunnelFloorTerracing != 0;
}
if (Arguments.bTunnelFloorStepOverride)
{
TunnelParams.TunnelFloorTerraceStepHeight = Arguments.TunnelFloorStep;
}
if (Arguments.bTunnelFloorMaxLedgeOverride)
{
TunnelParams.TunnelFloorMaxLedgeHeight = Arguments.TunnelFloorMaxLedge;
}
if (Arguments.bTunnelFloorGentleOverride)
{
TunnelParams.TunnelFloorGentleSlopeThreshold = Arguments.TunnelFloorGentle;
}
if (Arguments.bTunnelFloorLedgesOverride)
{
TunnelParams.TunnelFloorLedgeCountPreference = Arguments.TunnelFloorLedges;
}
if (Arguments.bTunnelFloorMaxLedgesOverride)
{
TunnelParams.TunnelFloorMaxLedges = Arguments.TunnelFloorMaxLedges;
}
if (Arguments.bRoomMouthRiseOverride)
{
TunnelParams.RoomMouthRiseStrength = Arguments.RoomMouthRise;
}
if (Arguments.bRoomMouthRiseBlendOverride)
{
TunnelParams.RoomMouthRiseBlendVoxels = Arguments.RoomMouthRiseBlend;
}
return true;
};
if (!Definitions.IsValidIndex(Arguments.Slot)
|| !ApplyTargetOverrides(Definitions[Arguments.Slot].Get()))
{
OutError = TEXT("The target definition rejected a commandlet override.");
return false;
}
Manager = TStrongObjectPtr<UVoxelStrateManager>(
NewObject<UVoxelStrateManager>(GetTransientPackage(), NAME_None, RF_Transient));
if (!Manager->Initialize(Settings.Get(), Settings->Seed))
{
OutError = TEXT("UVoxelStrateManager::Initialize refused the transient layout.");
return false;
}
if (Arguments.bPassageFloorWritesOverride)
{
Manager->SetPassageSupportFloorWritesEnabledForDiagnostics(
Arguments.PassageFloorWrites != 0);
}
if (!Manager->GetLayout().IsValidIndex(Arguments.Slot))
{
OutError = TEXT("The requested target slot did not enter the manager layout.");
return false;
}
const FStrateSlot& Target = Manager->GetLayout()[Arguments.Slot];
if (Target.Definition == nullptr)
{
OutError = TEXT("The requested target slot has no resolved definition.");
return false;
}
const ECaveGeneratorType TargetArchetype = Target.Definition->GeneratorType;
const FStrateGenerationParams& ReportParams = Target.Definition->GenerationParams;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeExplore][OwnerAssets] strate=%s settings=%s slot=%d "
"seed=%d height_chunks=%d archetype=%s "
"floor_switch_ignored=1 floor_terracing=%d floor_step=%.9g max_ledge=%.9g "
"gentle_slope_degrees=%.9g gentle_gradient=%.9g route_cap=%.9g "
"wind_wave_cap=%d "
"ledge_preference=%d max_ledges=%d tunnel_warp=%.9g terrain_ops=%d terrace_step=%.9g "
"layer_spacing=%.9g rib_spacing=%.9g"),
bUseAuthoredStrate ? *Arguments.StrateReference : TEXT("synthetic"),
bUseAuthoredSettings ? *Arguments.SettingsReference : TEXT("synthetic"),
Arguments.Slot, Settings->Seed, Target.Definition->StrateHeightInChunks,
ArchetypeName(TargetArchetype),
ReportParams.bTunnelFloorTerracingEnabled ? 1 : 0,
ReportParams.TunnelFloorTerraceStepHeight,
ReportParams.TunnelFloorMaxLedgeHeight,
ReportParams.TunnelFloorGentleSlopeThreshold,
VoxelPassageGeometry::TunnelFloorGradientFromDegrees(
ReportParams.TunnelFloorGentleSlopeThreshold),
VoxelPassageGeometry::TunnelFloorMaximumRouteLengthVoxels,
VoxelPassageGeometry::TunnelFloorMaximumWindWaves,
ReportParams.TunnelFloorLedgeCountPreference,
ReportParams.TunnelFloorMaxLedges,
ReportParams.TunnelWarpStrength,
Target.Definition->TerrainOperations.Num(),
ReportParams.TerraceStepHeight,
ReportParams.LayerLineSpacing,
ReportParams.RibbingSpacing);
TargetBottomWorldZ = Target.BottomChunkZ * CHUNK_SIZE;
TargetTopWorldZ = (Target.TopChunkZ + 1) * CHUNK_SIZE;
TargetBoundarySealThickness = BoundarySealForArchetype(
*Target.Definition, TargetArchetype);
if (TargetTopWorldZ <= TargetBottomWorldZ)
{
OutError = TEXT("The target strate has no positive voxel-space height.");
return false;
}
DiffLayer = TStrongObjectPtr<UVoxelDiffLayer>(
NewObject<UVoxelDiffLayer>(GetTransientPackage(), NAME_None, RF_Transient));
Generator = TStrongObjectPtr<UVoxelGenerator>(
NewObject<UVoxelGenerator>(GetTransientPackage(), NAME_None, RF_Transient));
Generator->InitializeSettings(Settings.Get());
Generator->SetStrateManager(Manager.Get());
Generator->SetDiffLayer(DiffLayer.Get());
Mesher = TStrongObjectPtr<UVoxelMarchingCubesMesher>(
NewObject<UVoxelMarchingCubesMesher>(GetTransientPackage(), NAME_None, RF_Transient));
Mesher->SetGenerator(Generator.Get());
Mesher->bUseBlockEarlyOut = Arguments.bBlockEarlyOut;
if (Arguments.bGameRuntimeConfig)
{
Mesher->bGenerateSkirts = Settings->bGenerateSkirts;
Mesher->SkirtCells = Settings->SkirtCells;
Mesher->LODOctaveDrop = Settings->LODOctaveDrop;
}
if (Settings->WorldRadiusVoxels != 0.0f
|| Generator->WorldRadiusVoxels != 0.0f
|| Settings->InterStrateGapChunks != 0)
{
OutError = TEXT(
"Explorer invariant failed: WorldRadiusVoxels must stay 0 and lateral regions must stay gated off.");
return false;
}
return true;
}
};
struct FRegionEvalPass
{
VoxelForgeRegionEval::FTiming Timing;
VoxelForgeRegionEval::FCandidateVisits Visits;
TArray<float> Densities;
TArray<int64> SampleIndices;
FIntVector Origin = FIntVector::ZeroValue;
int32 RecipeIndex = INDEX_NONE;
uint32 RecipeHash = 0;
int32 PassageCount = 0;
int32 EditChunkCount = 0;
int32 TargetedSampleCount = 0;
double WorldPrepareSeconds = 0.0;
FString StartedUtc;
FString EndedUtc;
};
struct FRegionEvalStackSamplerSession
{
bool bStarted = false;
~FRegionEvalStackSamplerSession()
{
if (bStarted)
{
FVoxelStackSampler::Get().StopAndWrite();
}
}
bool Start(uint32 IntervalUs, const FString& OutputDirectory,
const TCHAR* Scenario = TEXT("region_eval_live_ring"))
{
bStarted = FVoxelStackSampler::Get().Start(
IntervalUs, OutputDirectory, Scenario);
return bStarted;
}
bool Stop(FVoxelStackSampler::FSummary& OutSummary)
{
if (!bStarted)
{
return false;
}
OutSummary = FVoxelStackSampler::Get().StopAndWrite();
bStarted = false;
return OutSummary.bOutputWritten
&& OutSummary.RegisteredThreadSamples > 0
&& OutSummary.RetainedSamples > 0;
}
};
bool SavePng(const FString& FilePath, const TArray<FColor>& Pixels,
int32 Width, int32 Height, FString& OutError);
int32 FindRoomGraphRecipeIndex(int32 Seed)
{
for (int32 Index = 0; Index < 64; ++Index)
{
const FVoxelOpStackRecipe Recipe = VF_RollStrateStructure(Seed, Index);
if (Recipe.ShapeSource.OpClass == EVoxelStrateOpClass::RoomGraphSource)
{
return Index;
}
}
return INDEX_NONE;
}
bool PrepareRegionEvalWorld(
FExploreWorld& World,
const FExploreArguments& BaseArguments,
int32 Seed,
int32& OutRecipeIndex,
uint32& OutRecipeHash,
int32& OutEditChunkCount,
FString& OutError)
{
FExploreArguments Arguments = BaseArguments;
Arguments.Seed = Seed;
Arguments.bSeedOverride = true;
Arguments.bRender = false;
Arguments.bWalk = false;
Arguments.bExport = false;
Arguments.bBlockEarlyOut = false;
if (!World.Build(Arguments, OutError))
{
return false;
}
if (!World.Definitions.IsValidIndex(Arguments.Slot))
{
OutError = TEXT("Region evaluator target definition is missing.");
return false;
}
UVoxelStrateDefinition* Definition = World.Definitions[Arguments.Slot].Get();
if (Definition == nullptr || Definition->GeneratorType != ECaveGeneratorType::TunnelNetwork)
{
OutError = TEXT("Region evaluator requires the owner TunnelNetwork target slot.");
return false;
}
OutRecipeIndex = FindRoomGraphRecipeIndex(Seed);
if (OutRecipeIndex == INDEX_NONE)
{
OutError = FString::Printf(
TEXT("No RoomGraphSource recipe found in the 64-entry structure roll for seed %d."), Seed);
return false;
}
const FVoxelOpStackRecipe Recipe = VF_RollStrateStructure(Seed, OutRecipeIndex);
OutRecipeHash = VF_HashStrateStructureRecipe(Recipe);
// Make the selected RoomGraph recipe exercise the authored warp path. The definition is a
// transient duplicate of DA_Strate3, and both the live and prepared passes receive the same
// operator parameters and recipe.
FVoxelStrateArchetypeParams NativeParams;
NativeParams.TunnelNetworkParams = Definition->GenerationParams;
NativeParams.TunnelNetworkParams.TunnelWarpStrength = FMath::Max(
NativeParams.TunnelNetworkParams.TunnelWarpStrength, 1.25f);
NativeParams.SlabParams = Definition->SlabParams;
NativeParams.MazeParams = Definition->MazeParams;
NativeParams.SurfaceParams = Definition->SurfaceParams;
NativeParams.VerticalShaftParams = Definition->VerticalShaftParams;
NativeParams.FloatingIslandParams = Definition->FloatingIslandParams;
FString ComposerError;
if (!World.Manager->SetComposerOverrideForStrate(
Arguments.Slot, Seed, Definition->GeneratorType,
NativeParams, true, &Recipe, ComposerError))
{
OutError = FString::Printf(
TEXT("Could not install rolled composer recipe for seed %d: %s"),
Seed, *ComposerError);
return false;
}
const int32 CenterZ = (World.TargetTopWorldZ + World.TargetBottomWorldZ) / 2;
FVoxelModification Carve;
Carve.Center = FVector(8.0f, 4.0f, static_cast<float>(CenterZ));
Carve.Radius = 6.0f;
Carve.Strength = -12.0f;
Carve.Shape = EVoxelBrushShape::Sphere;
const TArray<FIntVector> CarvedChunks = World.DiffLayer->ApplyModification(Carve);
FVoxelModification Fill;
Fill.Center = FVector(-12.0f, 6.0f, static_cast<float>(CenterZ - 10));
Fill.Radius = 4.0f;
Fill.Strength = 12.0f;
Fill.Shape = EVoxelBrushShape::Sphere;
const TArray<FIntVector> FilledChunks = World.DiffLayer->ApplyModification(Fill);
if (CarvedChunks.IsEmpty() || FilledChunks.IsEmpty())
{
OutError = TEXT("Transient player carve/fill edits did not enter the diff layer.");
return false;
}
OutEditChunkCount = CarvedChunks.Num() + FilledChunks.Num();
int32 RecipeSeed = 0;
ECaveGeneratorType RecipeArchetype = ECaveGeneratorType::TunnelNetwork;
FVoxelStrateArchetypeParams ResolvedParams;
FVoxelOpStackRecipe ResolvedRecipe;
const int32 ChunkZ = FMath::FloorToInt(static_cast<float>(CenterZ) / CHUNK_SIZE);
if (!World.Manager->GetRecipeForChunk(
FIntVector(0, 0, ChunkZ), RecipeSeed, RecipeArchetype,
ResolvedParams, ResolvedRecipe)
|| !VF_AreStrateStructureRecipesIdentical(Recipe, ResolvedRecipe))
{
OutError = TEXT("Composer recipe was not active on the target density lattice.");
return false;
}
return true;
}
void AddRegionSampleIndex(
TSet<int64>& Unique,
int32 X, int32 Y, int32 Z,
FIntVector Origin, int32 Dimension)
{
const int32 LX = X - Origin.X;
const int32 LY = Y - Origin.Y;
const int32 LZ = Z - Origin.Z;
if (LX >= 0 && LX < Dimension && LY >= 0 && LY < Dimension
&& LZ >= 0 && LZ < Dimension)
{
Unique.Add(VoxelForgeRegionEval::LinearIndex(LX, LY, LZ, Dimension));
}
}
TArray<int64> BuildRegionEvalSampleIndices(
const FExploreWorld& World,
FIntVector Origin,
int32 Dimension,
int32 RequestedRandomSamples,
int32 Seed,
int32& OutTargetedSamples)
{
const int64 Total = static_cast<int64>(Dimension) * Dimension * Dimension;
const int32 RandomCount = FMath::Clamp(RequestedRandomSamples, 1, static_cast<int32>(Total));
TArray<int64> Indices;
Indices.Reserve(RandomCount + 50000);
auto Gcd = [](int64 A, int64 B)
{
while (B != 0)
{
const int64 R = A % B;
A = B;
B = R;
}
return A;
};
int64 Multiplier = 104729;
while (Gcd(Multiplier, Total) != 1)
{
Multiplier += 2;
}
const int64 Offset = (static_cast<int64>(Seed) * 48271ll) % Total;
for (int32 Sample = 0; Sample < RandomCount; ++Sample)
{
Indices.Add((static_cast<int64>(Sample) * Multiplier + Offset) % Total);
}
TSet<int64> Unique;
Unique.Reserve(50000);
for (const int64 Index : Indices)
{
Unique.Add(Index);
}
const int32 RandomUniqueCount = Unique.Num();
// Dense probes around both edited volumes.
const int32 CenterZ = (World.TargetTopWorldZ + World.TargetBottomWorldZ) / 2;
const FVector EditCenters[] = {
FVector(8.0f, 4.0f, static_cast<float>(CenterZ)),
FVector(-12.0f, 6.0f, static_cast<float>(CenterZ - 10)),
};
for (const FVector& Center : EditCenters)
{
const int32 CX = FMath::FloorToInt(Center.X);
const int32 CY = FMath::FloorToInt(Center.Y);
const int32 CZ = FMath::FloorToInt(Center.Z);
for (int32 Z = CZ - 8; Z <= CZ + 8; ++Z)
{
for (int32 Y = CY - 8; Y <= CY + 8; ++Y)
{
for (int32 X = CX - 8; X <= CX + 8; ++X)
{
AddRegionSampleIndex(Unique, X, Y, Z, Origin, Dimension);
}
}
}
}
// Include lattice sites around each authored route tube, landing room, and floor anchor.
for (const FVoxelPassage& Passage : World.Manager->GetPassages())
{
TArray<FVector> Anchors;
Anchors.Add(Passage.UpperPoint);
Anchors.Add(Passage.LowerPoint);
Anchors.Add(Passage.UpperLanding.StandingPoint);
Anchors.Add(Passage.UpperLanding.DoorPoint);
Anchors.Add(Passage.LowerLanding.StandingPoint);
Anchors.Add(Passage.LowerLanding.DoorPoint);
Anchors.Append(Passage.ControlPoints);
for (const FVector& Anchor : Anchors)
{
const int32 AX = FMath::FloorToInt(Anchor.X);
const int32 AY = FMath::FloorToInt(Anchor.Y);
const int32 AZ = FMath::FloorToInt(Anchor.Z);
for (int32 Z = AZ - 3; Z <= AZ + 3; ++Z)
{
for (int32 Y = AY - 3; Y <= AY + 3; ++Y)
{
for (int32 X = AX - 3; X <= AX + 3; ++X)
{
AddRegionSampleIndex(Unique, X, Y, Z, Origin, Dimension);
}
}
}
}
}
// Both strate boundaries are swept across the horizontal region, including ±3 lattice rows.
const int32 Boundaries[] = { World.TargetTopWorldZ, World.TargetBottomWorldZ };
for (const int32 BoundaryZ : Boundaries)
{
for (int32 Dz = -3; Dz <= 3; ++Dz)
{
for (int32 Y = -128; Y <= 128; Y += 8)
{
for (int32 X = -128; X <= 128; X += 8)
{
AddRegionSampleIndex(Unique, X, Y, BoundaryZ + Dz, Origin, Dimension);
}
}
}
}
Indices = Unique.Array();
Indices.Sort();
OutTargetedSamples = FMath::Max(0, Indices.Num() - RandomUniqueCount);
return Indices;
}
bool RunRegionEvalPass(
const FExploreArguments& BaseArguments,
int32 Seed,
bool bPrepared,
bool bRegionProgram,
float ReachScale,
bool bFullRing,
const TArray<int64>* RequestedIndices,
int32 RequestedSamples,
bool bCountCandidates,
bool bKeepDensities,
FRegionEvalPass& OutPass,
FString& OutError,
int32 RequestedWorkerCount = VoxelForgeRegionEval::WorkerCount,
bool bCollectWholeRunCounters = false)
{
OutPass = FRegionEvalPass();
FExploreWorld World;
const double PrepareStartSeconds = FPlatformTime::Seconds();
if (!PrepareRegionEvalWorld(
World, BaseArguments, Seed, OutPass.RecipeIndex,
OutPass.RecipeHash, OutPass.EditChunkCount, OutError))
{
return false;
}
OutPass.WorldPrepareSeconds = FPlatformTime::Seconds() - PrepareStartSeconds;
OutPass.PassageCount = World.Manager->GetPassages().Num();
const int32 CenterZ = (World.TargetTopWorldZ + World.TargetBottomWorldZ) / 2;
OutPass.Origin = FIntVector(-129, -129, CenterZ - 129);
if (OutPass.PassageCount <= 0)
{
OutError = FString::Printf(TEXT("Seed %d generated no inter-strate route passages."), Seed);
return false;
}
if (RequestedIndices != nullptr)
{
OutPass.SampleIndices = *RequestedIndices;
}
else
{
OutPass.SampleIndices = BuildRegionEvalSampleIndices(
World, OutPass.Origin, VoxelForgeRegionEval::RingDimension,
RequestedSamples, Seed, OutPass.TargetedSampleCount);
}
if (OutPass.SampleIndices.IsEmpty())
{
OutError = TEXT("Region evaluator produced an empty correctness sample set.");
return false;
}
OutPass.StartedUtc = FDateTime::UtcNow().ToIso8601();
const bool bEvaluated = VoxelForgeRegionEval::Evaluate(
*World.Generator, OutPass.Origin, VoxelForgeRegionEval::RingDimension,
bFullRing ? nullptr : &OutPass.SampleIndices,
bFullRing, bPrepared, ReachScale,
bKeepDensities ? &OutPass.Densities : nullptr,
OutPass.Timing, bRegionProgram, RequestedWorkerCount, bCollectWholeRunCounters);
OutPass.EndedUtc = FDateTime::UtcNow().ToIso8601();
if (!bEvaluated)
{
OutError = FString::Printf(
TEXT("Density evaluation failed or emitted a non-finite value (seed %d)."), Seed);
return false;
}
if (bCountCandidates)
{
OutPass.Visits = VoxelForgeRegionEval::CountCandidates(
*World.Generator, OutPass.Origin,
VoxelForgeRegionEval::RingDimension,
OutPass.SampleIndices, bPrepared, ReachScale);
if (OutPass.Visits.SampleCount != OutPass.SampleIndices.Num())
{
OutError = TEXT("Candidate counter sweep did not consume the full fixed sample set.");
return false;
}
}
return true;
}
TSharedRef<FJsonObject> RegionEvalTimingJson(const FRegionEvalPass& Pass, bool bPrepared)
{
TSharedRef<FJsonObject> Json = MakeShared<FJsonObject>();
Json->SetStringField(TEXT("path"), Pass.Timing.bRegionProgram
? TEXT("editor_region_chunk_program")
: (bPrepared ? TEXT("prepared_8cube_bins") : TEXT("live_GetDensityAt")));
Json->SetStringField(TEXT("started_utc"), Pass.StartedUtc);
Json->SetStringField(TEXT("ended_utc"), Pass.EndedUtc);
Json->SetNumberField(TEXT("worker_count"), Pass.Timing.Workers);
Json->SetNumberField(TEXT("observed_worker_threads"), Pass.Timing.ObservedWorkerThreads);
Json->SetNumberField(TEXT("samples"), static_cast<double>(Pass.Timing.SampleCount));
Json->SetNumberField(TEXT("evaluated_points"), static_cast<double>(Pass.Timing.EvaluatedSamples));
Json->SetNumberField(TEXT("region_count"), static_cast<double>(Pass.Timing.RegionCount));
Json->SetNumberField(TEXT("fallback_points"), static_cast<double>(Pass.Timing.FallbackSamples));
Json->SetNumberField(TEXT("fallback_share"), Pass.Timing.FallbackShare);
Json->SetNumberField(TEXT("general_path_entries"), static_cast<double>(Pass.Timing.GeneralPathEntries));
Json->SetNumberField(TEXT("general_path_entries_per_evaluated_sample"),
Pass.Timing.GeneralPathEntriesPerSample);
Json->SetNumberField(TEXT("supported_block_setup_entries"),
static_cast<double>(Pass.Timing.SupportedBlockSetupEntries));
Json->SetBoolField(TEXT("whole_run_counters_present"), Pass.Timing.bHasWholeRunCounters);
if (Pass.Timing.bHasWholeRunCounters)
{
Json->SetNumberField(TEXT("room_graph_sdf_cache_builds"),
static_cast<double>(Pass.Timing.SdfCacheBuilds));
Json->SetNumberField(TEXT("active_op_list_builds"),
static_cast<double>(Pass.Timing.ActiveOpListBuilds));
Json->SetNumberField(TEXT("broad_phase_index_builds"),
static_cast<double>(Pass.Timing.BroadPhaseIndexBuilds));
Json->SetNumberField(TEXT("sdf_spatial_index_builds"),
static_cast<double>(Pass.Timing.SdfSpatialIndexBuilds));
Json->SetNumberField(TEXT("sdf_3d_brick_index_builds"),
static_cast<double>(Pass.Timing.SdfBrickIndexBuilds));
Json->SetNumberField(TEXT("candidate_visits_total"),
static_cast<double>(Pass.Timing.TotalCandidateVisits));
Json->SetNumberField(TEXT("candidate_visits_per_sample"),
Pass.Timing.CandidateVisitsPerSample);
Json->SetNumberField(TEXT("candidate_evaluations_total"),
static_cast<double>(Pass.Timing.TotalCandidateEvaluations));
Json->SetNumberField(TEXT("candidate_evaluations_per_sample"),
Pass.Timing.CandidateEvaluationsPerSample);
Json->SetNumberField(TEXT("room_candidate_visits"),
static_cast<double>(Pass.Timing.RoomCandidateVisits));
Json->SetNumberField(TEXT("room_evaluations"),
static_cast<double>(Pass.Timing.RoomEvaluations));
Json->SetNumberField(TEXT("tunnel_candidate_visits"),
static_cast<double>(Pass.Timing.TunnelCandidateVisits));
Json->SetNumberField(TEXT("tunnel_evaluations"),
static_cast<double>(Pass.Timing.TunnelEvaluations));
Json->SetNumberField(TEXT("tunnel_core_candidate_visits"),
static_cast<double>(Pass.Timing.TunnelCoreCandidateVisits));
Json->SetNumberField(TEXT("tunnel_core_evaluations"),
static_cast<double>(Pass.Timing.TunnelCoreEvaluations));
Json->SetNumberField(TEXT("passage_candidate_visits"),
static_cast<double>(Pass.Timing.PassageCandidateVisits));
Json->SetNumberField(TEXT("passage_evaluations"),
static_cast<double>(Pass.Timing.PassageEvaluations));
TSharedRef<FJsonObject> Attribution = MakeShared<FJsonObject>();
Attribution->SetNumberField(TEXT("get_density_at_ns_per_sample"),
Pass.Timing.GetDensityAtNsPerSample);
Attribution->SetNumberField(TEXT("operator_block_ns_per_sample"),
Pass.Timing.OperatorBlockNsPerSample);
Attribution->SetNumberField(TEXT("room_graph_source_ns_per_sample"),
Pass.Timing.RoomGraphSourceNsPerSample);
Attribution->SetNumberField(TEXT("room_graph_build_ns_per_sample"),
Pass.Timing.RoomGraphBuildNsPerSample);
Attribution->SetNumberField(TEXT("room_graph_sdf_ns_per_sample"),
Pass.Timing.RoomGraphSdfNsPerSample);
Attribution->SetNumberField(TEXT("fused_evaluator_ns_per_sample"),
Pass.Timing.FusedEvaluatorNsPerSample);
Attribution->SetNumberField(TEXT("tunnel_core_sdf_ns_per_sample"),
Pass.Timing.TunnelCoreSdfNsPerSample);
Attribution->SetNumberField(TEXT("tunnel_core_world_ns_per_sample"),
Pass.Timing.TunnelCoreWorldNsPerSample);
Attribution->SetNumberField(TEXT("tunnel_core_posts_ns_per_sample"),
Pass.Timing.TunnelCorePostsNsPerSample);
Attribution->SetNumberField(TEXT("passage_modifier_ns_per_sample"),
Pass.Timing.PassageModifierNsPerSample);
Attribution->SetNumberField(TEXT("passage_structural_posts_ns_per_sample"),
Pass.Timing.PassageStructuralPostsNsPerSample);
Attribution->SetNumberField(TEXT("passage_floor_ns_per_sample"),
Pass.Timing.PassageFloorNsPerSample);
Attribution->SetNumberField(TEXT("structural_posts_ns_per_sample"),
Pass.Timing.StructuralPostsNsPerSample);
Attribution->SetNumberField(TEXT("density_prologue_ns_per_sample"),
Pass.Timing.DensityPrologueNsPerSample);
Attribution->SetNumberField(TEXT("density_core_ns_per_sample"),
Pass.Timing.DensityCoreNsPerSample);
Attribution->SetNumberField(TEXT("disturbances_ns_per_sample"),
Pass.Timing.DisturbancesNsPerSample);
Attribution->SetNumberField(TEXT("boundary_seal_ns_per_sample"),
Pass.Timing.DensityBoundarySealNsPerSample);
Attribution->SetNumberField(TEXT("diff_layer_ns_per_sample"),
Pass.Timing.DiffLayerNsPerSample);
Attribution->SetNumberField(TEXT("density_tail_ns_per_sample"),
Pass.Timing.DensityTailNsPerSample);
Json->SetObjectField(TEXT("fresh_attribution"), Attribution);
}
Json->SetNumberField(TEXT("world_setup_seconds_before_density_ring"), Pass.WorldPrepareSeconds);
Json->SetNumberField(TEXT("wall_seconds"), Pass.Timing.WallSeconds);
Json->SetNumberField(TEXT("worker_cpu_seconds"), Pass.Timing.WorkerCpuSeconds);
Json->SetNumberField(TEXT("worker_cpu_ns_per_sample"), Pass.Timing.NanosecondsPerSample);
const FString WorkerCpuSource = Pass.Timing.bWorkerCpuFromOsThreadTimes
? FString::Printf(TEXT("summed Windows GetThreadTimes user+kernel CPU across %d fixed work partitions"),
Pass.Timing.Workers)
: TEXT("summed worker elapsed fallback; OS thread CPU counters unavailable");
Json->SetStringField(TEXT("worker_cpu_source"), WorkerCpuSource);
Json->SetStringField(TEXT("quantized_hash_fnv1a64"),
FString::Printf(TEXT("%016llx"), static_cast<unsigned long long>(Pass.Timing.QuantizedHash)));
Json->SetStringField(TEXT("raw_float_hash_fnv1a64"),
FString::Printf(TEXT("%016llx"), static_cast<unsigned long long>(Pass.Timing.RawFloatHash)));
Json->SetNumberField(TEXT("saturated_samples"), static_cast<double>(Pass.Timing.SaturatedSamples));
Json->SetNumberField(TEXT("non_finite_samples"), static_cast<double>(Pass.Timing.NonFiniteSamples));
if (Pass.Visits.SampleCount > 0)
{
Json->SetNumberField(TEXT("probe_candidate_visits_per_sample"), Pass.Visits.PerSample);
Json->SetNumberField(TEXT("probe_candidate_evaluations_per_sample"), Pass.Visits.EvaluatedPerSample);
Json->SetNumberField(TEXT("probe_room_candidate_visits"), static_cast<double>(Pass.Visits.Room));
Json->SetNumberField(TEXT("probe_room_candidate_evaluations"), static_cast<double>(Pass.Visits.RoomEvaluated));
Json->SetNumberField(TEXT("probe_tunnel_candidate_visits"), static_cast<double>(Pass.Visits.Tunnel));
Json->SetNumberField(TEXT("probe_tunnel_candidate_evaluations"), static_cast<double>(Pass.Visits.TunnelEvaluated));
Json->SetNumberField(TEXT("probe_tunnel_core_candidate_visits"), static_cast<double>(Pass.Visits.TunnelCore));
Json->SetNumberField(TEXT("probe_tunnel_core_candidate_evaluations"), static_cast<double>(Pass.Visits.TunnelCoreEvaluated));
Json->SetNumberField(TEXT("probe_passage_candidate_visits"), static_cast<double>(Pass.Visits.Passage));
Json->SetNumberField(TEXT("probe_passage_candidate_evaluations"), static_cast<double>(Pass.Visits.PassageEvaluated));
Json->SetNumberField(TEXT("probe_tunnel_cache_lookups"), static_cast<double>(Pass.Visits.TunnelCacheLookups));
Json->SetNumberField(TEXT("probe_tunnel_cache_hits"), static_cast<double>(Pass.Visits.TunnelCacheHits));
Json->SetNumberField(TEXT("probe_tunnel_cache_misses"), static_cast<double>(Pass.Visits.TunnelCacheMisses));
Json->SetNumberField(TEXT("probe_sdf_cache_builds"), static_cast<double>(Pass.Visits.SdfCacheBuilds));
Json->SetNumberField(TEXT("probe_candidate_counter_samples"), static_cast<double>(Pass.Visits.SampleCount));
Json->SetNumberField(TEXT("probe_attribution_sample_interval"), Pass.Visits.AttributionSampleInterval);
Json->SetStringField(TEXT("probe_attribution_basis"), TEXT("sampled VoxelDensityProfile phases; diagnostic only, excluded from clean timing"));
Json->SetNumberField(TEXT("probe_get_density_at_attributed_ns_per_sample"), Pass.Visits.GetDensityAtAttributedNsPerSample);
Json->SetNumberField(TEXT("probe_density_prologue_attributed_ns_per_sample"), Pass.Visits.DensityPrologueAttributedNsPerSample);
Json->SetNumberField(TEXT("probe_density_core_attributed_ns_per_sample"), Pass.Visits.DensityCoreAttributedNsPerSample);
Json->SetNumberField(TEXT("probe_density_disturbances_attributed_ns_per_sample"), Pass.Visits.DensityDisturbancesAttributedNsPerSample);
Json->SetNumberField(TEXT("probe_density_structural_posts_attributed_ns_per_sample"), Pass.Visits.DensityStructuralPostsAttributedNsPerSample);
Json->SetNumberField(TEXT("probe_density_boundary_seal_attributed_ns_per_sample"), Pass.Visits.DensityBoundarySealAttributedNsPerSample);
Json->SetNumberField(TEXT("probe_density_diff_layer_attributed_ns_per_sample"), Pass.Visits.DensityDiffLayerAttributedNsPerSample);
Json->SetNumberField(TEXT("probe_density_tail_attributed_ns_per_sample"), Pass.Visits.DensityTailAttributedNsPerSample);
}
return Json;
}
struct FQuantizedComparison
{
int64 Disagreements = 0;
int64 SignDisagreements = 0;
double MaxAbsoluteFloatDelta = 0.0;
int64 FirstIndex = INDEX_NONE;
};
FQuantizedComparison CompareQuantizedArrays(
const TArray<float>& A, const TArray<float>& B,
const TArray<int64>* LatticeIndices = nullptr)
{
FQuantizedComparison Result;
if (A.Num() != B.Num())
{
Result.Disagreements = FMath::Max(A.Num(), B.Num());
return Result;
}
for (int32 Index = 0; Index < A.Num(); ++Index)
{
bool bSaturatedA = false, bFiniteA = false;
bool bSaturatedB = false, bFiniteB = false;
const int16 QA = VoxelForgeRegionEval::QuantizeDensity(A[Index], bSaturatedA, bFiniteA);
const int16 QB = VoxelForgeRegionEval::QuantizeDensity(B[Index], bSaturatedB, bFiniteB);
Result.MaxAbsoluteFloatDelta = FMath::Max(
Result.MaxAbsoluteFloatDelta,
FMath::Abs(static_cast<double>(A[Index]) - static_cast<double>(B[Index])));
if (!bFiniteA || !bFiniteB || bSaturatedA || bSaturatedB || QA != QB)
{
++Result.Disagreements;
if (Result.FirstIndex == INDEX_NONE)
{
Result.FirstIndex = LatticeIndices != nullptr
? (*LatticeIndices)[Index] : static_cast<int64>(Index);
}
}
if ((QA < 0) != (QB < 0))
{
++Result.SignDisagreements;
}
}
return Result;
}
TSharedRef<FJsonObject> RegionEvalComparisonJson(const FQuantizedComparison& Comparison)
{
TSharedRef<FJsonObject> Json = MakeShared<FJsonObject>();
Json->SetNumberField(TEXT("quantized_disagreements"), static_cast<double>(Comparison.Disagreements));
Json->SetNumberField(TEXT("quantized_sign_disagreements"), static_cast<double>(Comparison.SignDisagreements));
Json->SetNumberField(TEXT("max_abs_float_delta"), Comparison.MaxAbsoluteFloatDelta);
Json->SetNumberField(TEXT("first_disagreement_linear_index"), static_cast<double>(Comparison.FirstIndex));
return Json;
}
int32 RunRegionEval(const FString& Params)
{
FExploreArguments Arguments;
FString Error;
if (!ParseArguments(Params, Arguments, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeRegionEval] argument error: %s"), *Error);
return 2;
}
int32 RequestedSamples = 262144;
FParse::Value(*Params, TEXT("regionsamples="), RequestedSamples);
RequestedSamples = FMath::Clamp(RequestedSamples, 32768, 1000000);
int32 RequestedWorkerCount = VoxelForgeRegionEval::WorkerCount;
FParse::Value(*Params, TEXT("regionworkers="), RequestedWorkerCount);
RequestedWorkerCount = FMath::Clamp(RequestedWorkerCount, 1, 32);
IFileManager::Get().MakeDirectory(*Arguments.OutDirectory, true);
if (FParse::Param(*Params, TEXT("regionhashprobe")))
{
FRegionEvalStackSamplerSession WorkerHashStackSampler;
if (Arguments.bSampleStacks
&& !WorkerHashStackSampler.Start(
Arguments.SampleStacksIntervalUs, Arguments.OutDirectory,
TEXT("region_eval_brick_worker_hash")))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeRegionEval] worker hash stack sampler could not be started."));
return 2;
}
FRegionEvalPass WorkerHashPass;
const bool bHashPassSucceeded = RunRegionEvalPass(
Arguments, 1337, true, true, 1.0f, true, nullptr,
RequestedSamples, false, true, WorkerHashPass, Error,
RequestedWorkerCount);
bool bStackCaptureSucceeded = true;
if (Arguments.bSampleStacks)
{
FVoxelStackSampler::FSummary StackSummary;
bStackCaptureSucceeded = WorkerHashStackSampler.Stop(StackSummary);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeRegionEval] worker-hash stack capture registered=%llu retained=%llu "
"raw=%s summary=%s"),
static_cast<unsigned long long>(StackSummary.RegisteredThreadSamples),
static_cast<unsigned long long>(StackSummary.RetainedSamples),
*StackSummary.RawSamplesPath, *StackSummary.SummaryPath);
}
if (!bHashPassSucceeded || !bStackCaptureSucceeded)
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeRegionEval] worker hash probe failed: %s"),
Error.IsEmpty() ? TEXT("stack capture retained no worker samples") : *Error);
return 2;
}
FString ReferenceRawHash;
FParse::Value(*Params, TEXT("regionreferencehash="), ReferenceRawHash);
const FString ActualRawHash = FString::Printf(TEXT("%016llx"),
static_cast<unsigned long long>(WorkerHashPass.Timing.RawFloatHash));
const bool bHasReferenceHash = !ReferenceRawHash.IsEmpty();
const bool bMatchesReference = bHasReferenceHash
&& ActualRawHash.Equals(ReferenceRawHash, ESearchCase::IgnoreCase);
const bool bProbePassed = bMatchesReference
&& WorkerHashPass.Timing.SampleCount
== static_cast<int64>(VoxelForgeRegionEval::RingDimension)
* VoxelForgeRegionEval::RingDimension * VoxelForgeRegionEval::RingDimension
&& WorkerHashPass.Timing.SaturatedSamples == 0
&& WorkerHashPass.Timing.NonFiniteSamples == 0;
TSharedRef<FJsonObject> ProbeReport = MakeShared<FJsonObject>();
ProbeReport->SetBoolField(TEXT("pass"), bProbePassed);
ProbeReport->SetBoolField(TEXT("worker_count_hash_probe"), true);
ProbeReport->SetNumberField(TEXT("worker_count"), WorkerHashPass.Timing.Workers);
ProbeReport->SetNumberField(TEXT("observed_worker_threads"),
WorkerHashPass.Timing.ObservedWorkerThreads);
ProbeReport->SetNumberField(TEXT("samples"), WorkerHashPass.Timing.SampleCount);
ProbeReport->SetNumberField(TEXT("region_count"), WorkerHashPass.Timing.RegionCount);
ProbeReport->SetNumberField(TEXT("fallback_points"), WorkerHashPass.Timing.FallbackSamples);
ProbeReport->SetNumberField(TEXT("fallback_share"), WorkerHashPass.Timing.FallbackShare);
ProbeReport->SetNumberField(TEXT("wall_seconds"), WorkerHashPass.Timing.WallSeconds);
ProbeReport->SetNumberField(TEXT("worker_cpu_seconds"), WorkerHashPass.Timing.WorkerCpuSeconds);
ProbeReport->SetStringField(TEXT("reference_raw_float_hash_fnv1a64"), ReferenceRawHash);
ProbeReport->SetStringField(TEXT("raw_float_hash_fnv1a64"), ActualRawHash);
ProbeReport->SetBoolField(TEXT("matches_reference"), bMatchesReference);
FString ProbeJsonText;
TSharedRef<TJsonWriter<>> ProbeWriter = TJsonWriterFactory<>::Create(&ProbeJsonText);
if (!FJsonSerializer::Serialize(ProbeReport, ProbeWriter)
|| !FFileHelper::SaveStringToFile(
ProbeJsonText, *FPaths::Combine(Arguments.OutDirectory, TEXT("region_eval.json")),
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeRegionEval] could not write worker hash probe report."));
return 2;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeRegionEval] worker hash probe workers=%d observed=%d samples=%lld "
"raw=%s reference_match=%s report=%s"),
WorkerHashPass.Timing.Workers, WorkerHashPass.Timing.ObservedWorkerThreads,
static_cast<long long>(WorkerHashPass.Timing.SampleCount), *ActualRawHash,
bMatchesReference ? TEXT("yes") : TEXT("no"),
*FPaths::Combine(Arguments.OutDirectory, TEXT("region_eval.json")));
return bProbePassed ? 0 : 2;
}
// Region-eval returns before the normal Explore sampler setup. Capture only its two cold live
// ring trials so prepared-path and multi-seed work cannot pollute the baseline attribution.
FRegionEvalStackSamplerSession StackSamplerSession;
if (Arguments.bSampleStacks
&& !StackSamplerSession.Start(
Arguments.SampleStacksIntervalUs, Arguments.OutDirectory))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeRegionEval] requested stack sampler could not be started."));
return 2;
}
const int32 Seeds[] = { 1337, 7331, 19427, 30241, 41011 };
TArray<TSharedPtr<FJsonValue>> SeedReports;
TArray<TSharedPtr<FJsonValue>> OldTimingReports;
TArray<TSharedPtr<FJsonValue>> PreparedTimingReports;
TSharedPtr<FJsonObject> LiveFullRingDiagnostics;
TSharedPtr<FJsonObject> PreparedFullRingDiagnostics;
TArray<float> OldFullDensities;
TArray<float> PreparedFullDensities;
TArray<int64> PrimarySampleIndices;
FQuantizedComparison FullComparison;
uint64 OldFullRawHash = 0;
uint64 PreparedFullRawHash = 0;
uint64 OldFullSaturatedSamples = 0;
uint64 PreparedFullSaturatedSamples = 0;
bool bAllSeedComparisonsPass = true;
bool bFullDeterminismPass = true;
int32 PrimaryTargetedSampleCount = 0;
int32 PrimaryPassageCount = 0;
int32 PrimaryEditChunkCount = 0;
int32 PrimaryRecipeIndex = INDEX_NONE;
uint32 PrimaryRecipeHash = 0;
// Cold full-ring timings use independent transient worlds for every pass. Caches cannot be
// warmed by the comparison leg, and the seed/layout/composer/edit payload remains identical.
for (int32 Trial = 0; Trial < 2; ++Trial)
{
FRegionEvalPass Pass;
const bool bOk = RunRegionEvalPass(
Arguments, Seeds[0], false, false, 1.0f, true, nullptr,
RequestedSamples, Trial == 0 && !Arguments.bSampleStacks, true, Pass, Error);
if (!bOk)
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeRegionEval] live full-ring trial failed: %s"), *Error);
return 2;
}
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
if (Trial == 0)
{
OldFullDensities = MoveTemp(Pass.Densities);
OldFullRawHash = Pass.Timing.RawFloatHash;
OldFullSaturatedSamples = Pass.Timing.SaturatedSamples;
PrimarySampleIndices = Pass.SampleIndices;
PrimaryTargetedSampleCount = Pass.TargetedSampleCount;
PrimaryPassageCount = Pass.PassageCount;
PrimaryEditChunkCount = Pass.EditChunkCount;
PrimaryRecipeIndex = Pass.RecipeIndex;
PrimaryRecipeHash = Pass.RecipeHash;
}
else
{
bFullDeterminismPass &= Pass.Timing.RawFloatHash == OldFullRawHash;
}
OldTimingReports.Add(MakeShared<FJsonValueObject>(RegionEvalTimingJson(Pass, false)));
}
if (Arguments.bSampleStacks)
{
FVoxelStackSampler::FSummary StackSummary;
if (!StackSamplerSession.Stop(StackSummary))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeRegionEval] stack capture retained no live-ring worker samples."));
return 2;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeRegionEval] live-ring stack capture registered=%llu retained=%llu "
"raw=%s summary=%s"),
static_cast<unsigned long long>(StackSummary.RegisteredThreadSamples),
static_cast<unsigned long long>(StackSummary.RetainedSamples),
*StackSummary.RawSamplesPath, *StackSummary.SummaryPath);
}
if (PrimarySampleIndices.IsEmpty())
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeRegionEval] primary statistical sample set is empty."));
return 2;
}
for (int32 Trial = 0; Trial < 2; ++Trial)
{
FRegionEvalPass Pass;
const bool bOk = RunRegionEvalPass(
Arguments, Seeds[0], true, true, 1.0f, true, nullptr,
RequestedSamples, Trial == 0, true, Pass, Error);
if (!bOk)
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeRegionEval] prepared full-ring trial failed: %s"), *Error);
return 2;
}
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
if (Trial == 0)
{
PreparedFullDensities = MoveTemp(Pass.Densities);
PreparedFullRawHash = Pass.Timing.RawFloatHash;
PreparedFullSaturatedSamples = Pass.Timing.SaturatedSamples;
FullComparison = CompareQuantizedArrays(OldFullDensities, PreparedFullDensities);
}
else
{
bFullDeterminismPass &= Pass.Timing.RawFloatHash == PreparedFullRawHash;
}
PreparedTimingReports.Add(MakeShared<FJsonValueObject>(RegionEvalTimingJson(Pass, true)));
}
bAllSeedComparisonsPass &= FullComparison.Disagreements == 0
&& FullComparison.SignDisagreements == 0
&& OldFullSaturatedSamples == 0
&& PreparedFullSaturatedSamples == 0;
// Exact whole-ring counters and sampled phase attribution are collected in separate cold
// passes. Their diagnostic hooks stay out of the two wall-time trials used for the 600 ms bar.
FRegionEvalPass LiveDiagnosticPass;
if (!RunRegionEvalPass(
Arguments, Seeds[0], false, false, 1.0f, true, nullptr,
RequestedSamples, false, false, LiveDiagnosticPass, Error,
VoxelForgeRegionEval::WorkerCount, true))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeRegionEval] live full-ring diagnostic pass failed: %s"), *Error);
return 2;
}
LiveFullRingDiagnostics = RegionEvalTimingJson(LiveDiagnosticPass, false);
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
FRegionEvalPass PreparedDiagnosticPass;
if (!RunRegionEvalPass(
Arguments, Seeds[0], true, true, 1.0f, true, nullptr,
RequestedSamples, false, false, PreparedDiagnosticPass, Error,
VoxelForgeRegionEval::WorkerCount, true))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeRegionEval] prepared full-ring diagnostic pass failed: %s"), *Error);
return 2;
}
PreparedFullRingDiagnostics = RegionEvalTimingJson(PreparedDiagnosticPass, true);
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
// Statistical fixed-lattice comparisons across four additional independent seeds. The sample
// set is a deterministic affine permutation of the entire 259^3 ring plus dense strata,
// passage-floor, landing, carve, and fill probes.
if (Arguments.bSampleStacks)
{
FRegionEvalStackSamplerSession RegionStackSamplerSession;
if (!RegionStackSamplerSession.Start(
Arguments.SampleStacksIntervalUs, Arguments.OutDirectory,
TEXT("region_eval_chunk_ring")))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeRegionEval] chunk stack sampler could not be started."));
return 2;
}
FRegionEvalPass ProfiledRegionPass;
if (!RunRegionEvalPass(
Arguments, Seeds[0], true, true, 1.0f, true, nullptr,
RequestedSamples, false, false, ProfiledRegionPass, Error))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeRegionEval] chunk attribution run failed: %s"), *Error);
return 2;
}
FVoxelStackSampler::FSummary RegionStackSummary;
if (!RegionStackSamplerSession.Stop(RegionStackSummary))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeRegionEval] chunk stack capture retained no worker samples."));
return 2;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeRegionEval] chunk stack capture registered=%llu retained=%llu "
"raw=%s summary=%s"),
static_cast<unsigned long long>(RegionStackSummary.RegisteredThreadSamples),
static_cast<unsigned long long>(RegionStackSummary.RetainedSamples),
*RegionStackSummary.RawSamplesPath, *RegionStackSummary.SummaryPath);
}
for (int32 SeedIndex = 1; SeedIndex < UE_ARRAY_COUNT(Seeds); ++SeedIndex)
{
FRegionEvalPass OldPass;
if (!RunRegionEvalPass(
Arguments, Seeds[SeedIndex], false, false, 1.0f, false, nullptr,
RequestedSamples, true, true, OldPass, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeRegionEval] seed %d live sample failed: %s"),
Seeds[SeedIndex], *Error);
return 2;
}
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
FRegionEvalPass NewPass;
if (!RunRegionEvalPass(
Arguments, Seeds[SeedIndex], true, true, 1.0f, false, &OldPass.SampleIndices,
RequestedSamples, true, true, NewPass, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeRegionEval] seed %d prepared sample failed: %s"),
Seeds[SeedIndex], *Error);
return 2;
}
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
const FQuantizedComparison Comparison = CompareQuantizedArrays(
OldPass.Densities, NewPass.Densities, &OldPass.SampleIndices);
bAllSeedComparisonsPass &= Comparison.Disagreements == 0
&& Comparison.SignDisagreements == 0
&& OldPass.Timing.SaturatedSamples == 0
&& NewPass.Timing.SaturatedSamples == 0;
TSharedRef<FJsonObject> SeedJson = MakeShared<FJsonObject>();
SeedJson->SetNumberField(TEXT("seed"), Seeds[SeedIndex]);
SeedJson->SetNumberField(TEXT("samples"), OldPass.Timing.SampleCount);
SeedJson->SetNumberField(TEXT("targeted_route_edit_boundary_samples"), OldPass.TargetedSampleCount);
SeedJson->SetNumberField(TEXT("composer_recipe_index"), OldPass.RecipeIndex);
SeedJson->SetStringField(TEXT("composer_recipe_hash"), FString::Printf(TEXT("%08x"), OldPass.RecipeHash));
SeedJson->SetNumberField(TEXT("passage_count"), OldPass.PassageCount);
SeedJson->SetNumberField(TEXT("edit_chunk_count"), OldPass.EditChunkCount);
SeedJson->SetObjectField(TEXT("comparison"), RegionEvalComparisonJson(Comparison));
SeedJson->SetObjectField(TEXT("live_candidate_visits"), RegionEvalTimingJson(OldPass, false));
SeedJson->SetObjectField(TEXT("prepared_candidate_visits"), RegionEvalTimingJson(NewPass, true));
SeedReports.Add(MakeShared<FJsonValueObject>(SeedJson));
}
// Deliberately shrink one reach, evaluate the same statistical lattice points, and require
// the reference comparison to catch omitted room influence. The scale is scoped to this one
// commandlet pass and every worker resets it to 1.0 afterward.
FRegionEvalPass ShrunkPass;
if (!RunRegionEvalPass(
Arguments, Seeds[0], true, true, 0.50f, false, &PrimarySampleIndices,
RequestedSamples, false, true, ShrunkPass, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeRegionEval] deliberate reach-shrink run failed: %s"), *Error);
return 2;
}
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
FQuantizedComparison ShrinkComparison;
ShrinkComparison.FirstIndex = INDEX_NONE;
for (int32 Sample = 0; Sample < ShrunkPass.Densities.Num(); ++Sample)
{
const int64 LatticeIndex = PrimarySampleIndices[Sample];
bool bSaturatedOld = false, bFiniteOld = false;
bool bSaturatedShrink = false, bFiniteShrink = false;
const int16 QOld = VoxelForgeRegionEval::QuantizeDensity(
OldFullDensities[static_cast<int32>(LatticeIndex)], bSaturatedOld, bFiniteOld);
const int16 QShrink = VoxelForgeRegionEval::QuantizeDensity(
ShrunkPass.Densities[Sample], bSaturatedShrink, bFiniteShrink);
ShrinkComparison.MaxAbsoluteFloatDelta = FMath::Max(
ShrinkComparison.MaxAbsoluteFloatDelta,
FMath::Abs(static_cast<double>(OldFullDensities[static_cast<int32>(LatticeIndex)])
- static_cast<double>(ShrunkPass.Densities[Sample])));
if (!bFiniteOld || !bFiniteShrink || bSaturatedOld || bSaturatedShrink || QOld != QShrink)
{
++ShrinkComparison.Disagreements;
if (ShrinkComparison.FirstIndex == INDEX_NONE) ShrinkComparison.FirstIndex = LatticeIndex;
}
if ((QOld < 0) != (QShrink < 0)) ++ShrinkComparison.SignDisagreements;
}
double PreparedWallSum = 0.0;
for (const TSharedPtr<FJsonValue>& Value : PreparedTimingReports)
{
const TSharedPtr<FJsonObject> Json = Value->AsObject();
PreparedWallSum += Json->GetNumberField(TEXT("wall_seconds"));
}
const double PreparedMedianWall = PreparedWallSum / 2.0;
double OldWallSum = 0.0;
for (const TSharedPtr<FJsonValue>& Value : OldTimingReports)
{
OldWallSum += Value->AsObject()->GetNumberField(TEXT("wall_seconds"));
}
const double OldMedianWall = OldWallSum / 2.0;
bool bSixWorkersObserved = true;
for (const TSharedPtr<FJsonValue>& Value : OldTimingReports)
{
bSixWorkersObserved &= Value->AsObject()->GetNumberField(TEXT("observed_worker_threads"))
>= VoxelForgeRegionEval::WorkerCount;
}
for (const TSharedPtr<FJsonValue>& Value : PreparedTimingReports)
{
bSixWorkersObserved &= Value->AsObject()->GetNumberField(TEXT("observed_worker_threads"))
>= VoxelForgeRegionEval::WorkerCount;
}
const bool bSpeedPass = PreparedMedianWall <= 0.600 && bSixWorkersObserved;
TSharedRef<FJsonObject> Report = MakeShared<FJsonObject>();
Report->SetNumberField(TEXT("schema_version"), 2);
Report->SetStringField(TEXT("route"), TEXT("editor-only explore commandlet; required Unreal composer, manager, and generator types"));
Report->SetStringField(TEXT("evaluator"), TEXT("editor-only chunk program; region-scoped room/SDF caches, one operator block per chunk, canonical post-stack writers, and counted live point fallbacks"));
Report->SetStringField(TEXT("timing_basis"), TEXT("two cold wall-time trials per path with counters disabled; exact whole-ring counters and sampled phase attribution are separate diagnostic passes"));
Report->SetBoolField(TEXT("diagnostic_passes_excluded_from_speed_verdict"), true);
Report->SetStringField(TEXT("comparison_quantization"), TEXT("signed int16; round ties away from zero; multiply density by 16; saturate outside [-2048, 2047.9375] density voxels"));
Report->SetNumberField(TEXT("worker_count"), VoxelForgeRegionEval::WorkerCount);
Report->SetNumberField(TEXT("ring_dimension"), VoxelForgeRegionEval::RingDimension);
Report->SetNumberField(TEXT("ring_lattice_samples"), static_cast<double>(VoxelForgeRegionEval::RingDimension) * VoxelForgeRegionEval::RingDimension * VoxelForgeRegionEval::RingDimension);
Report->SetNumberField(TEXT("ring_interval_span_voxels"), VoxelForgeRegionEval::RingDimension - 1);
Report->SetNumberField(TEXT("ring_wall_p50_live_seconds"), OldMedianWall);
Report->SetNumberField(TEXT("ring_wall_p50_prepared_seconds"), PreparedMedianWall);
Report->SetBoolField(TEXT("speed_pass_600ms"), bSpeedPass);
Report->SetBoolField(TEXT("all_full_ring_trials_used_six_threads"), bSixWorkersObserved);
Report->SetBoolField(TEXT("five_seed_correctness_pass"), bAllSeedComparisonsPass);
Report->SetBoolField(TEXT("two_run_determinism_pass"), bFullDeterminismPass);
Report->SetStringField(TEXT("determinism_basis"), TEXT("bitwise float32 lattice hash on two independent cold worlds for live and prepared paths"));
Report->SetObjectField(TEXT("full_ring_comparison"), RegionEvalComparisonJson(FullComparison));
Report->SetArrayField(TEXT("live_full_ring_trials"), OldTimingReports);
Report->SetArrayField(TEXT("prepared_full_ring_trials"), PreparedTimingReports);
Report->SetObjectField(TEXT("live_full_ring_diagnostics"), LiveFullRingDiagnostics.ToSharedRef());
Report->SetObjectField(TEXT("prepared_full_ring_diagnostics"), PreparedFullRingDiagnostics.ToSharedRef());
Report->SetArrayField(TEXT("additional_seed_comparisons"), SeedReports);
TSharedRef<FJsonObject> Coverage = MakeShared<FJsonObject>();
Coverage->SetStringField(TEXT("owner_settings"), Arguments.SettingsReference);
Coverage->SetStringField(TEXT("owner_strate"), Arguments.StrateReference);
Coverage->SetNumberField(TEXT("primary_seed"), Seeds[0]);
Coverage->SetNumberField(TEXT("primary_targeted_route_edit_boundary_samples"), PrimaryTargetedSampleCount);
Coverage->SetNumberField(TEXT("target_passages"), PrimaryPassageCount);
Coverage->SetNumberField(TEXT("transient_edit_chunks"), PrimaryEditChunkCount);
Coverage->SetNumberField(TEXT("composer_recipe_index"), PrimaryRecipeIndex);
Coverage->SetStringField(TEXT("composer_recipe_hash"), FString::Printf(TEXT("%08x"), PrimaryRecipeHash));
Coverage->SetStringField(TEXT("composer_recipe_family"), TEXT("RoomGraphSource with TunnelNetwork params; rolled by VF_RollStrateStructure"));
Coverage->SetNumberField(TEXT("room_warp_strength_lower_bound"), 1.25);
Coverage->SetStringField(TEXT("room_bin_reach"), TEXT("cached CullRadiusSq: shape extent + floor-relief envelope + 3x SDF blend + conservative terrain-op reach; mouth-rise reach uses center-to-mouth distance + blend"));
Coverage->SetStringField(TEXT("tunnel_bin_reach"), TEXT("union of finalized warped-SDF and world-chain centerline bounds expanded by their influence radii, plus both conservative bounding spheres"));
Coverage->SetStringField(TEXT("warp_coordinates"), TEXT("room bins are queried after the exact live cave warp in the same coordinate domain; tunnel bins union warped-SDF and unwarped world-chain reach"));
Coverage->SetStringField(TEXT("tunnel_bin_unit"), TEXT("whole cached tunnel chain; per-chain segment loops remain in the exact evaluator"));
Coverage->SetStringField(TEXT("unbinned_writers"), TEXT("passage/landing posts, disturbances, and player diff edits retain their canonical per-sample order inside the chunk block writer pass"));
Coverage->SetBoolField(TEXT("route_landing_floor_probes"), PrimaryPassageCount > 0);
Coverage->SetBoolField(TEXT("strata_top_and_bottom_boundary_sweep"), true);
Coverage->SetBoolField(TEXT("player_carve_and_fill_edits"), true);
Report->SetObjectField(TEXT("content_coverage"), Coverage);
TSharedRef<FJsonObject> Shrink = MakeShared<FJsonObject>();
Shrink->SetNumberField(TEXT("room_reach_scale"), 0.50);
Shrink->SetBoolField(TEXT("deliberately_shrunk_bin_was_caught"), ShrinkComparison.Disagreements > 0);
Shrink->SetNumberField(TEXT("saturated_samples"), static_cast<double>(ShrunkPass.Timing.SaturatedSamples));
Shrink->SetNumberField(TEXT("quantized_disagreements"), static_cast<double>(ShrinkComparison.Disagreements));
Shrink->SetNumberField(TEXT("sign_disagreements"), static_cast<double>(ShrinkComparison.SignDisagreements));
Shrink->SetNumberField(TEXT("max_abs_float_delta"), ShrinkComparison.MaxAbsoluteFloatDelta);
Shrink->SetNumberField(TEXT("first_disagreement_linear_index"), static_cast<double>(ShrinkComparison.FirstIndex));
Report->SetObjectField(TEXT("deliberate_reach_shrink"), Shrink);
const bool bPassed = bAllSeedComparisonsPass && bFullDeterminismPass
&& ShrinkComparison.Disagreements > 0
&& ShrunkPass.Timing.SaturatedSamples == 0;
Report->SetStringField(TEXT("status"), bPassed ? TEXT("correctness_pass_speed_measured") : TEXT("correctness_failed"));
Report->SetBoolField(TEXT("pass"), bPassed);
Report->SetStringField(TEXT("speed_verdict"), bSpeedPass
? TEXT("meets 600 ms") : TEXT("misses 600 ms; candidate bins are an ablation, not a full region compiler"));
FString JsonText;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&JsonText);
if (!FJsonSerializer::Serialize(Report, Writer)
|| !FFileHelper::SaveStringToFile(
JsonText, *FPaths::Combine(Arguments.OutDirectory, TEXT("region_eval.json")),
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeRegionEval] could not write region_eval.json."));
return 2;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeRegionEval] status=%s speed600=%s live_ms=%.3f prepared_ms=%.3f "
"full_q16_diff=%lld five_seed=%s deterministic=%s shrink_q16_diff=%lld report=%s"),
bPassed ? TEXT("pass") : TEXT("fail"), bSpeedPass ? TEXT("yes") : TEXT("no"),
OldMedianWall * 1000.0, PreparedMedianWall * 1000.0,
static_cast<long long>(FullComparison.Disagreements),
bAllSeedComparisonsPass ? TEXT("pass") : TEXT("fail"),
bFullDeterminismPass ? TEXT("pass") : TEXT("fail"),
static_cast<long long>(ShrinkComparison.Disagreements),
*FPaths::Combine(Arguments.OutDirectory, TEXT("region_eval.json")));
return bPassed ? 0 : 2;
}
struct FStep2dSurfaceSpectrum
{
uint64 SurfaceBandSamples = 0;
double RMSLag1 = 0.0;
double RMSLag2 = 0.0;
double RMSLag4 = 0.0;
};
struct FStep2dBoundaryStats
{
uint64 BrickBoundarySamples = 0;
uint64 InteriorSamples = 0;
uint64 ChunkBoundarySamples = 0;
uint64 FeatureCellBoundarySamples = 0;
uint64 FeatureCellInteriorSamples = 0;
double BrickJumpRms = 0.0;
double InteriorJumpRms = 0.0;
double ChunkBoundaryJumpRms = 0.0;
double FeatureCellJumpRms = 0.0;
double FeatureCellInteriorJumpRms = 0.0;
double BrickJumpMax = 0.0;
double ChunkBoundaryJumpMax = 0.0;
double FeatureCellJumpMax = 0.0;
};
struct FStep2dBrickProofAudit
{
uint64 CorrectlySkippedBricksAudited = 0;
uint64 CorrectProofSamplesChecked = 0;
uint64 CorrectProofViolations = 0;
FIntVector FirstViolationBrickOrigin = FIntVector::ZeroValue;
FIntVector FirstViolationBrickSize = FIntVector::ZeroValue;
FIntVector FirstViolationSample = FIntVector::ZeroValue;
float FirstViolationDensity = 0.0f;
float FirstViolationCenterMacro = 0.0f;
float FirstViolationSampleMacro = 0.0f;
float FirstViolationCenterRoomMacro = 0.0f;
float FirstViolationSampleRoomMacro = 0.0f;
float FirstViolationSampleDetail = 0.0f;
float FirstViolationMacroDeltaPerVoxel = 0.0f;
float FirstViolationPathMaxGradient = 0.0f;
float FirstViolationTinyStepMaxGradient = 0.0f;
FVector FirstViolationPathMaxFrom = FVector::ZeroVector;
FVector FirstViolationPathMaxTo = FVector::ZeroVector;
float FirstViolationPathMaxFromMacro = 0.0f;
float FirstViolationPathMaxToMacro = 0.0f;
float FirstViolationPathMaxFromRoomMacro = 0.0f;
float FirstViolationPathMaxToRoomMacro = 0.0f;
FString FirstViolationPathMaxFromRoomDiagnostic;
FString FirstViolationPathMaxToRoomDiagnostic;
bool bFirstViolationWasInsideBrick = false;
bool bFirstViolationClaimedAir = false;
uint64 DeliberatelyUndersizedBoundClaimsChecked = 0;
uint64 DeliberateBoundDisagreements = 0;
};
FStep2dBrickProofAudit AuditStep2dBrickProofs(
const UVoxelGenerator& Generator,
FIntVector Origin,
int32 Dimension,
bool bParameterized = false)
{
FStep2dBrickProofAudit Result;
const FIntVector Last = Origin + FIntVector(Dimension - 1, Dimension - 1, Dimension - 1);
const FIntVector FirstBrick(
FMath::FloorToInt(static_cast<float>(Origin.X) / 8.0f),
FMath::FloorToInt(static_cast<float>(Origin.Y) / 8.0f),
FMath::FloorToInt(static_cast<float>(Origin.Z) / 8.0f));
const FIntVector LastBrick(
FMath::FloorToInt(static_cast<float>(Last.X) / 8.0f),
FMath::FloorToInt(static_cast<float>(Last.Y) / 8.0f),
FMath::FloorToInt(static_cast<float>(Last.Z) / 8.0f));
uint64 CorrectSkipOrdinal = 0;
for (int32 BZ = FirstBrick.Z; BZ <= LastBrick.Z; ++BZ)
for (int32 BY = FirstBrick.Y; BY <= LastBrick.Y; ++BY)
for (int32 BX = FirstBrick.X; BX <= LastBrick.X; ++BX)
{
const FIntVector BrickWorldMin(BX * 8, BY * 8, BZ * 8);
const FIntVector BrickOrigin(
FMath::Max(Origin.X, BrickWorldMin.X),
FMath::Max(Origin.Y, BrickWorldMin.Y),
FMath::Max(Origin.Z, BrickWorldMin.Z));
const FIntVector End(
FMath::Min(BrickWorldMin.X + 8, Origin.X + Dimension),
FMath::Min(BrickWorldMin.Y + 8, Origin.Y + Dimension),
FMath::Min(BrickWorldMin.Z + 8, Origin.Z + Dimension));
const FIntVector Size = End - BrickOrigin;
bool bProvenAir = false;
const bool bSoundClaim = bParameterized
? VoxelForgeRedefinedDensityField::TryClassifyParameterizedBrick(
Generator, BrickOrigin, Size, bProvenAir, 1.0f)
: VoxelForgeRedefinedDensityField::TryClassifyBrick(
Generator, BrickOrigin, Size, bProvenAir, 1.0f);
if (bSoundClaim)
{
const bool bAuditThisSkip = (CorrectSkipOrdinal++ % 128u) == 0u;
if (bAuditThisSkip)
{
++Result.CorrectlySkippedBricksAudited;
for (int32 Z = -1; Z <= Size.Z; ++Z)
for (int32 Y = -1; Y <= Size.Y; ++Y)
for (int32 X = -1; X <= Size.X; ++X)
{
const float Density = bParameterized
? VoxelForgeRedefinedDensityField::EvaluateParameterized(
Generator, static_cast<float>(BrickOrigin.X + X),
static_cast<float>(BrickOrigin.Y + Y),
static_cast<float>(BrickOrigin.Z + Z), nullptr, 1.0f)
: VoxelForgeRedefinedDensityField::Evaluate(
Generator, static_cast<float>(BrickOrigin.X + X),
static_cast<float>(BrickOrigin.Y + Y),
static_cast<float>(BrickOrigin.Z + Z), nullptr, 1.0f);
++Result.CorrectProofSamplesChecked;
if ((Density > 0.0f) != bProvenAir)
{
if (Result.CorrectProofViolations == 0)
{
Result.FirstViolationBrickOrigin = BrickOrigin;
Result.FirstViolationBrickSize = Size;
Result.FirstViolationSample = BrickOrigin + FIntVector(X, Y, Z);
Result.FirstViolationDensity = Density;
const FVector ProofMin(
static_cast<float>(BrickOrigin.X - 1),
static_cast<float>(BrickOrigin.Y - 1),
static_cast<float>(BrickOrigin.Z - 1));
const FVector ProofMax(
static_cast<float>(BrickOrigin.X + Size.X),
static_cast<float>(BrickOrigin.Y + Size.Y),
static_cast<float>(BrickOrigin.Z + Size.Z));
const FVector ProofCentre = (ProofMin + ProofMax) * 0.5f;
const FVector SamplePoint(
static_cast<float>(BrickOrigin.X + X),
static_cast<float>(BrickOrigin.Y + Y),
static_cast<float>(BrickOrigin.Z + Z));
Result.FirstViolationCenterMacro = bParameterized
? VoxelForgeRedefinedDensityField::EvaluateParameterized(
Generator, ProofCentre.X, ProofCentre.Y, ProofCentre.Z,
nullptr, 0.0f)
: VoxelForgeRedefinedDensityField::Evaluate(
Generator, ProofCentre.X, ProofCentre.Y, ProofCentre.Z,
nullptr, 0.0f);
Result.FirstViolationSampleMacro = bParameterized
? VoxelForgeRedefinedDensityField::EvaluateParameterized(
Generator, SamplePoint.X, SamplePoint.Y, SamplePoint.Z,
nullptr, 0.0f)
: VoxelForgeRedefinedDensityField::Evaluate(
Generator, SamplePoint.X, SamplePoint.Y, SamplePoint.Z,
nullptr, 0.0f);
if (bParameterized)
{
Result.FirstViolationCenterRoomMacro =
VoxelForgeRedefinedDensityField::EvaluateParameterizedMacroOnly(
Generator, ProofCentre.X, ProofCentre.Y, ProofCentre.Z);
Result.FirstViolationSampleRoomMacro =
VoxelForgeRedefinedDensityField::EvaluateParameterizedMacroOnly(
Generator, SamplePoint.X, SamplePoint.Y, SamplePoint.Z);
}
Result.FirstViolationSampleDetail = Density
- Result.FirstViolationSampleMacro;
const float CentreDistance = (SamplePoint - ProofCentre).Size();
Result.FirstViolationMacroDeltaPerVoxel = CentreDistance > 1.0e-6f
? FMath::Abs(Result.FirstViolationCenterMacro
- Result.FirstViolationSampleMacro) / CentreDistance
: 0.0f;
if (CentreDistance > 1.0e-6f)
{
constexpr int32 PathSteps = 32;
float PreviousMacro = Result.FirstViolationSampleMacro;
FVector PreviousPoint = SamplePoint;
for (int32 PathStep = 1; PathStep <= PathSteps; ++PathStep)
{
const float Alpha = static_cast<float>(PathStep) / PathSteps;
const FVector Point = FMath::Lerp(
SamplePoint, ProofCentre, Alpha);
const float PathMacro = bParameterized
? VoxelForgeRedefinedDensityField::EvaluateParameterized(
Generator, Point.X, Point.Y, Point.Z, nullptr, 0.0f)
: VoxelForgeRedefinedDensityField::Evaluate(
Generator, Point.X, Point.Y, Point.Z, nullptr, 0.0f);
const float PathGradient = FMath::Abs(
PathMacro - PreviousMacro) * PathSteps / CentreDistance;
if (PathGradient > Result.FirstViolationPathMaxGradient)
{
Result.FirstViolationPathMaxGradient = PathGradient;
Result.FirstViolationPathMaxFrom = PreviousPoint;
Result.FirstViolationPathMaxTo = Point;
Result.FirstViolationPathMaxFromMacro = PreviousMacro;
Result.FirstViolationPathMaxToMacro = PathMacro;
if (bParameterized)
{
Result.FirstViolationPathMaxFromRoomMacro =
VoxelForgeRedefinedDensityField::EvaluateParameterizedMacroOnly(
Generator, PreviousPoint.X, PreviousPoint.Y,
PreviousPoint.Z);
Result.FirstViolationPathMaxToRoomMacro =
VoxelForgeRedefinedDensityField::EvaluateParameterizedMacroOnly(
Generator, Point.X, Point.Y, Point.Z);
Result.FirstViolationPathMaxFromRoomDiagnostic =
VoxelForgeRedefinedDensityField::DescribeParameterizedMacroAt(
Generator, PreviousPoint.X, PreviousPoint.Y,
PreviousPoint.Z);
Result.FirstViolationPathMaxToRoomDiagnostic =
VoxelForgeRedefinedDensityField::DescribeParameterizedMacroAt(
Generator, Point.X, Point.Y, Point.Z);
}
}
PreviousMacro = PathMacro;
PreviousPoint = Point;
}
}
constexpr float TinyStep = 0.01f;
for (int32 Axis = 0; Axis < 3; ++Axis)
for (int32 SignIndex = 0; SignIndex < 2; ++SignIndex)
{
const int32 Sign = SignIndex == 0 ? -1 : 1;
FVector Point = SamplePoint;
Point[Axis] += static_cast<float>(Sign) * TinyStep;
const float NeighborMacro = bParameterized
? VoxelForgeRedefinedDensityField::EvaluateParameterized(
Generator, Point.X, Point.Y, Point.Z, nullptr, 0.0f)
: VoxelForgeRedefinedDensityField::Evaluate(
Generator, Point.X, Point.Y, Point.Z, nullptr, 0.0f);
Result.FirstViolationTinyStepMaxGradient = FMath::Max(
Result.FirstViolationTinyStepMaxGradient,
FMath::Abs(NeighborMacro - Result.FirstViolationSampleMacro)
/ TinyStep);
}
Result.bFirstViolationWasInsideBrick = X >= 0 && X < Size.X
&& Y >= 0 && Y < Size.Y && Z >= 0 && Z < Size.Z;
Result.bFirstViolationClaimedAir = bProvenAir;
}
++Result.CorrectProofViolations;
}
}
}
}
// Negative control: classify while omitting all detail, then check the supported
// maximum detail amplitude. A valid supremum must reject at least some of these
// sign claims; otherwise this audit would not demonstrate that it can catch a bad bound.
bool bDeliberatelyClaimedAir = false;
const bool bUndersizedClaim = bParameterized
? VoxelForgeRedefinedDensityField::TryClassifyParameterizedBrick(
Generator, BrickOrigin, Size, bDeliberatelyClaimedAir, 0.0f)
: VoxelForgeRedefinedDensityField::TryClassifyBrick(
Generator, BrickOrigin, Size, bDeliberatelyClaimedAir, 0.0f);
if (bUndersizedClaim)
{
++Result.DeliberatelyUndersizedBoundClaimsChecked;
for (int32 Z = 0; Z < Size.Z; ++Z)
for (int32 Y = 0; Y < Size.Y; ++Y)
for (int32 X = 0; X < Size.X; ++X)
{
const float MaximumDetailDensity = bParameterized
? VoxelForgeRedefinedDensityField::EvaluateParameterized(
Generator, static_cast<float>(BrickOrigin.X + X),
static_cast<float>(BrickOrigin.Y + Y),
static_cast<float>(BrickOrigin.Z + Z), nullptr, 4.0f)
: VoxelForgeRedefinedDensityField::Evaluate(
Generator, static_cast<float>(BrickOrigin.X + X),
static_cast<float>(BrickOrigin.Y + Y),
static_cast<float>(BrickOrigin.Z + Z), nullptr, 4.0f);
if ((MaximumDetailDensity > 0.0f) != bDeliberatelyClaimedAir)
{
++Result.DeliberateBoundDisagreements;
}
}
}
}
return Result;
}
FStep2dSurfaceSpectrum ComputeStep2dSpectrum(const TArray<float>& Density, int32 Dimension)
{
FStep2dSurfaceSpectrum Result;
double SumSquares[3] = {0.0, 0.0, 0.0};
uint64 Counts[3] = {0, 0, 0};
const int32 Lags[] = {1, 2, 4};
for (int32 Z = 4; Z < Dimension - 4; ++Z)
for (int32 Y = 4; Y < Dimension - 4; ++Y)
for (int32 X = 4; X < Dimension - 4; ++X)
{
const int32 CenterIndex = (Z * Dimension + Y) * Dimension + X;
const float Center = Density[CenterIndex];
if (!FMath::IsFinite(Center) || FMath::Abs(Center) > 12.0f)
{
continue;
}
++Result.SurfaceBandSamples;
for (int32 LagIndex = 0; LagIndex < UE_ARRAY_COUNT(Lags); ++LagIndex)
{
const int32 Lag = Lags[LagIndex];
const float Laplacian =
Density[CenterIndex - Lag] + Density[CenterIndex + Lag]
+ Density[CenterIndex - Lag * Dimension]
+ Density[CenterIndex + Lag * Dimension]
+ Density[CenterIndex - Lag * Dimension * Dimension]
+ Density[CenterIndex + Lag * Dimension * Dimension]
- 6.0f * Center;
SumSquares[LagIndex] += static_cast<double>(Laplacian) * Laplacian;
++Counts[LagIndex];
}
}
Result.RMSLag1 = Counts[0] > 0 ? FMath::Sqrt(SumSquares[0] / Counts[0]) : 0.0;
Result.RMSLag2 = Counts[1] > 0 ? FMath::Sqrt(SumSquares[1] / Counts[1]) : 0.0;
Result.RMSLag4 = Counts[2] > 0 ? FMath::Sqrt(SumSquares[2] / Counts[2]) : 0.0;
return Result;
}
FIntVector SelectStep2dMeshOrigin(
const TArray<float>& OldDensity,
const TArray<float>& NewDensity,
FIntVector RingOrigin,
int32 Dimension,
bool& bOutContainsBothIsoBands)
{
bOutContainsBothIsoBands = false;
float BestCombinedBand = FLT_MAX;
FIntVector BestSample(Dimension / 2, Dimension / 2, Dimension / 2);
for (int32 Z = 32; Z < Dimension - 32; ++Z)
for (int32 Y = 32; Y < Dimension - 32; ++Y)
for (int32 X = 32; X < Dimension - 32; ++X)
{
const int32 Index = (Z * Dimension + Y) * Dimension + X;
const float OldAbs = FMath::Abs(OldDensity[Index]);
const float NewAbs = FMath::Abs(NewDensity[Index]);
if (OldAbs <= 8.0f && NewAbs <= 8.0f)
{
const float Combined = OldAbs + NewAbs;
if (Combined < BestCombinedBand)
{
BestCombinedBand = Combined;
BestSample = FIntVector(X, Y, Z);
bOutContainsBothIsoBands = true;
}
}
}
if (!bOutContainsBothIsoBands)
{
float BestNewBand = FLT_MAX;
for (int32 Z = 32; Z < Dimension - 32; ++Z)
for (int32 Y = 32; Y < Dimension - 32; ++Y)
for (int32 X = 32; X < Dimension - 32; ++X)
{
const int32 Index = (Z * Dimension + Y) * Dimension + X;
const float NewAbs = FMath::Abs(NewDensity[Index]);
if (NewAbs < BestNewBand)
{
BestNewBand = NewAbs;
BestSample = FIntVector(X, Y, Z);
}
}
}
return RingOrigin + BestSample - FIntVector(32, 32, 32);
}
FStep2dBoundaryStats ComputeStep2dBoundaryStats(
const TArray<float>& Density, int32 Dimension, FIntVector Origin, int32 FeatureCellSpacing)
{
FStep2dBoundaryStats Result;
double BrickSquares = 0.0;
double InteriorSquares = 0.0;
double ChunkSquares = 0.0;
double FeatureSquares = 0.0;
double FeatureInteriorSquares = 0.0;
const int32 CellSpacing = FMath::Max(FeatureCellSpacing, 1);
for (int32 Z = 1; Z < Dimension - 1; ++Z)
for (int32 Y = 1; Y < Dimension - 1; ++Y)
for (int32 X = 1; X < Dimension - 1; ++X)
{
const int32 CenterIndex = (Z * Dimension + Y) * Dimension + X;
const int32 Stride[3] = {1, Dimension, Dimension * Dimension};
const int32 World[3] = {Origin.X + X, Origin.Y + Y, Origin.Z + Z};
for (int32 Axis = 0; Axis < 3; ++Axis)
{
const double LeftGradient = Density[CenterIndex] - Density[CenterIndex - Stride[Axis]];
const double RightGradient = Density[CenterIndex + Stride[Axis]] - Density[CenterIndex];
const double Jump = FMath::Abs(RightGradient - LeftGradient);
const bool bBrickBoundary = (World[Axis] % 8) == 0;
const bool bChunkBoundary = (World[Axis] % CHUNK_SIZE) == 0 && Axis < 2;
const bool bFeatureBoundary = ((World[Axis] + CellSpacing / 2) % CellSpacing) == 0;
if (FMath::Abs(Density[CenterIndex]) <= VoxelPassageGeometry::MetersToVoxels(3.0f))
{
if (bBrickBoundary)
{
++Result.BrickBoundarySamples;
BrickSquares += Jump * Jump;
Result.BrickJumpMax = FMath::Max(Result.BrickJumpMax, Jump);
}
else
{
++Result.InteriorSamples;
InteriorSquares += Jump * Jump;
}
if (bChunkBoundary)
{
++Result.ChunkBoundarySamples;
ChunkSquares += Jump * Jump;
Result.ChunkBoundaryJumpMax = FMath::Max(Result.ChunkBoundaryJumpMax, Jump);
}
}
if (bFeatureBoundary)
{
++Result.FeatureCellBoundarySamples;
FeatureSquares += Jump * Jump;
}
else
{
++Result.FeatureCellInteriorSamples;
FeatureInteriorSquares += Jump * Jump;
}
if (bFeatureBoundary)
Result.FeatureCellJumpMax = FMath::Max(Result.FeatureCellJumpMax, Jump);
}
}
Result.BrickJumpRms = Result.BrickBoundarySamples > 0
? FMath::Sqrt(BrickSquares / Result.BrickBoundarySamples) : 0.0;
Result.InteriorJumpRms = Result.InteriorSamples > 0
? FMath::Sqrt(InteriorSquares / Result.InteriorSamples) : 0.0;
Result.ChunkBoundaryJumpRms = Result.ChunkBoundarySamples > 0
? FMath::Sqrt(ChunkSquares / Result.ChunkBoundarySamples) : 0.0;
Result.FeatureCellJumpRms = Result.FeatureCellBoundarySamples > 0
? FMath::Sqrt(FeatureSquares / Result.FeatureCellBoundarySamples) : 0.0;
Result.FeatureCellInteriorJumpRms = Result.FeatureCellInteriorSamples > 0
? FMath::Sqrt(FeatureInteriorSquares / Result.FeatureCellInteriorSamples) : 0.0;
return Result;
}
bool Step2eBoundaryStatsPass(const FStep2dBoundaryStats& Stats)
{
return Stats.BrickBoundarySamples > 0
&& Stats.InteriorSamples > 0
&& Stats.ChunkBoundarySamples > 0
&& Stats.FeatureCellBoundarySamples > 0
&& Stats.BrickJumpRms <= Stats.InteriorJumpRms * 2.0 + 0.25
&& Stats.ChunkBoundaryJumpRms <= Stats.InteriorJumpRms * 2.0 + 0.25
&& Stats.FeatureCellJumpRms <= Stats.FeatureCellInteriorJumpRms * 2.0 + 0.25;
}
FColor Step2dDensityColor(float Density)
{
if (FMath::Abs(Density) < 1.0f)
{
return FColor(56, 184, 206, 255);
}
return Density >= 0.0f
? FColor(226, 220, 202, 255)
: FColor(22, 30, 45, 255);
}
void SaveStep2dSideBySideSlice(
const FString& Path,
const TArray<float>& OldDensity,
const TArray<float>& NewDensity,
int32 Dimension,
TCHAR Plane,
int32 FixedIndex,
FString& OutError)
{
const int32 Width = Dimension * 2;
const int32 Height = Dimension;
TArray<FColor> Pixels;
Pixels.SetNumUninitialized(Width * Height);
for (int32 Row = 0; Row < Height; ++Row)
for (int32 Column = 0; Column < Dimension; ++Column)
{
int32 OldIndex = 0;
int32 NewIndex = 0;
if (Plane == TEXT('h'))
{
const int32 X = Column;
const int32 Y = Dimension - 1 - Row;
const int32 Z = FixedIndex;
OldIndex = (Z * Dimension + Y) * Dimension + X;
}
else if (Plane == TEXT('x'))
{
const int32 X = Column;
const int32 Y = FixedIndex;
const int32 Z = Dimension - 1 - Row;
OldIndex = (Z * Dimension + Y) * Dimension + X;
}
else
{
const int32 X = FixedIndex;
const int32 Y = Column;
const int32 Z = Dimension - 1 - Row;
OldIndex = (Z * Dimension + Y) * Dimension + X;
}
NewIndex = OldIndex;
Pixels[Row * Width + Column] = Step2dDensityColor(OldDensity[OldIndex]);
Pixels[Row * Width + Dimension + Column] = Step2dDensityColor(NewDensity[NewIndex]);
}
SavePng(Path, Pixels, Width, Height, OutError);
}
struct FStep2eVarietyMetrics
{
double OpenFraction = 0.0;
double SurfaceAreaSquareMeters = 0.0;
double SurfaceAreaPerVolume = 0.0;
double VerticalAdjacencyShare = 0.0;
double VerticalAirColumnShare = 0.0;
double MeanLongestVerticalAirRunVoxels = 0.0;
int32 LongestVerticalAirRunVoxels = 0;
double TunnelNeckShare = 0.0;
int32 ConnectedComponentsAtOneMeter = 0;
int64 LargestComponentCubicMeters = 0;
int64 SmallComponentsUpToEightCubicMeters = 0;
int64 MediumComponentsNineToSixtyFourCubicMeters = 0;
int64 LargeComponentsSixtyFiveToFiveHundredTwelveCubicMeters = 0;
int64 VeryLargeComponentsOverFiveHundredTwelveCubicMeters = 0;
};
FStep2eVarietyMetrics ComputeStep2eVarietyMetrics(
const TArray<float>& Density, int32 Dimension)
{
FStep2eVarietyMetrics Result;
const int64 Total = static_cast<int64>(Dimension) * Dimension * Dimension;
int64 AirCount = 0;
uint64 SurfaceEdgeCount = 0;
uint64 HorizontalAdjacencies = 0;
uint64 VerticalAdjacencies = 0;
const int32 ColumnCount = Dimension * Dimension;
TArray<int32> CurrentVerticalRun;
CurrentVerticalRun.SetNumZeroed(ColumnCount);
TArray<int32> LongestVerticalRun;
LongestVerticalRun.SetNumZeroed(ColumnCount);
for (int32 Z = 0; Z < Dimension; ++Z)
for (int32 Y = 0; Y < Dimension; ++Y)
for (int32 X = 0; X < Dimension; ++X)
{
const int32 Index = (Z * Dimension + Y) * Dimension + X;
const bool bAir = Density[Index] > 0.0f;
AirCount += bAir ? 1 : 0;
const int32 ColumnIndex = Y * Dimension + X;
CurrentVerticalRun[ColumnIndex] = bAir ? CurrentVerticalRun[ColumnIndex] + 1 : 0;
LongestVerticalRun[ColumnIndex] = FMath::Max(
LongestVerticalRun[ColumnIndex], CurrentVerticalRun[ColumnIndex]);
if (X + 1 < Dimension)
{
const bool bNextAir = Density[Index + 1] > 0.0f;
SurfaceEdgeCount += bAir != bNextAir ? 1u : 0u;
HorizontalAdjacencies += bAir && bNextAir ? 1u : 0u;
}
if (Y + 1 < Dimension)
{
const bool bNextAir = Density[Index + Dimension] > 0.0f;
SurfaceEdgeCount += bAir != bNextAir ? 1u : 0u;
HorizontalAdjacencies += bAir && bNextAir ? 1u : 0u;
}
if (Z + 1 < Dimension)
{
const bool bNextAir = Density[Index + Dimension * Dimension] > 0.0f;
SurfaceEdgeCount += bAir != bNextAir ? 1u : 0u;
VerticalAdjacencies += bAir && bNextAir ? 1u : 0u;
}
}
Result.OpenFraction = Total > 0 ? static_cast<double>(AirCount) / Total : 0.0;
// Each sign-changing lattice edge estimates one voxel-face patch. This is a
// consistent edge-count estimate, not a marching-cubes triangle integral.
constexpr double VoxelMeters = static_cast<double>(VOXEL_SIZE) / 100.0;
Result.SurfaceAreaSquareMeters = static_cast<double>(SurfaceEdgeCount)
* VoxelMeters * VoxelMeters;
const double VolumeCubicMeters = static_cast<double>(Total)
* VoxelMeters * VoxelMeters * VoxelMeters;
Result.SurfaceAreaPerVolume = VolumeCubicMeters > 0.0
? Result.SurfaceAreaSquareMeters / VolumeCubicMeters : 0.0;
const uint64 AllAdjacencies = HorizontalAdjacencies + VerticalAdjacencies;
Result.VerticalAdjacencyShare = AllAdjacencies > 0
? static_cast<double>(VerticalAdjacencies) / AllAdjacencies : 0.0;
uint64 ColumnsWithAir = 0;
uint64 TotalLongestRunVoxels = 0;
for (const int32 LongestRun : LongestVerticalRun)
{
if (LongestRun > 0)
{
++ColumnsWithAir;
TotalLongestRunVoxels += static_cast<uint64>(LongestRun);
Result.LongestVerticalAirRunVoxels = FMath::Max(
Result.LongestVerticalAirRunVoxels, LongestRun);
}
}
Result.VerticalAirColumnShare = ColumnCount > 0
? static_cast<double>(ColumnsWithAir) / ColumnCount : 0.0;
Result.MeanLongestVerticalAirRunVoxels = ColumnsWithAir > 0
? static_cast<double>(TotalLongestRunVoxels) / ColumnsWithAir : 0.0;
constexpr int32 VoxelsPerMeter = static_cast<int32>(100.0f / VOXEL_SIZE + 0.5f);
constexpr int32 CoarseStep = VoxelsPerMeter;
const int32 CoarseDimension = (Dimension - 1) / CoarseStep + 1;
const int32 CoarseCount = CoarseDimension * CoarseDimension * CoarseDimension;
TArray<int32> Parent;
Parent.Init(INDEX_NONE, CoarseCount);
const auto CoarseIndex = [CoarseDimension](int32 X, int32 Y, int32 Z)
{
return (Z * CoarseDimension + Y) * CoarseDimension + X;
};
int64 CoarseAirCount = 0;
int64 TunnelNeckCount = 0;
for (int32 Z = 0; Z < CoarseDimension; ++Z)
for (int32 Y = 0; Y < CoarseDimension; ++Y)
for (int32 X = 0; X < CoarseDimension; ++X)
{
const int32 SourceIndex = ((Z * CoarseStep) * Dimension + Y * CoarseStep)
* Dimension + X * CoarseStep;
if (Density[SourceIndex] <= 0.0f)
{
continue;
}
const int32 Index = CoarseIndex(X, Y, Z);
Parent[Index] = Index;
++CoarseAirCount;
int32 OpenNeighbors = 0;
const int32 Delta[6][3] = {
{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}};
for (const auto& D : Delta)
{
const int32 NX = X + D[0];
const int32 NY = Y + D[1];
const int32 NZ = Z + D[2];
if (NX < 0 || NY < 0 || NZ < 0
|| NX >= CoarseDimension || NY >= CoarseDimension || NZ >= CoarseDimension)
{
continue;
}
const int32 NSource = ((NZ * CoarseStep) * Dimension + NY * CoarseStep)
* Dimension + NX * CoarseStep;
OpenNeighbors += Density[NSource] > 0.0f ? 1 : 0;
}
TunnelNeckCount += OpenNeighbors <= 4 ? 1 : 0;
}
Result.TunnelNeckShare = CoarseAirCount > 0
? static_cast<double>(TunnelNeckCount) / CoarseAirCount : 0.0;
const auto FindRoot = [&Parent](int32 Index)
{
int32 Root = Index;
while (Parent[Root] != Root)
{
Root = Parent[Root];
}
while (Parent[Index] != Index)
{
const int32 Next = Parent[Index];
Parent[Index] = Root;
Index = Next;
}
return Root;
};
for (int32 Z = 0; Z < CoarseDimension; ++Z)
for (int32 Y = 0; Y < CoarseDimension; ++Y)
for (int32 X = 0; X < CoarseDimension; ++X)
{
const int32 Index = CoarseIndex(X, Y, Z);
if (Parent[Index] == INDEX_NONE)
{
continue;
}
const int32 Neighbors[3] = {
X > 0 ? CoarseIndex(X - 1, Y, Z) : INDEX_NONE,
Y > 0 ? CoarseIndex(X, Y - 1, Z) : INDEX_NONE,
Z > 0 ? CoarseIndex(X, Y, Z - 1) : INDEX_NONE};
for (const int32 Neighbor : Neighbors)
{
if (Neighbor == INDEX_NONE || Parent[Neighbor] == INDEX_NONE)
{
continue;
}
const int32 RootA = FindRoot(Index);
const int32 RootB = FindRoot(Neighbor);
if (RootA != RootB)
{
Parent[RootB] = RootA;
}
}
}
TMap<int32, int64> ComponentSizes;
for (int32 Index = 0; Index < CoarseCount; ++Index)
{
if (Parent[Index] != INDEX_NONE)
{
++ComponentSizes.FindOrAdd(FindRoot(Index));
}
}
Result.ConnectedComponentsAtOneMeter = ComponentSizes.Num();
for (const TPair<int32, int64>& Pair : ComponentSizes)
{
const int64 Size = Pair.Value;
Result.LargestComponentCubicMeters = FMath::Max(Result.LargestComponentCubicMeters, Size);
if (Size <= 8) ++Result.SmallComponentsUpToEightCubicMeters;
else if (Size <= 64) ++Result.MediumComponentsNineToSixtyFourCubicMeters;
else if (Size <= 512) ++Result.LargeComponentsSixtyFiveToFiveHundredTwelveCubicMeters;
else ++Result.VeryLargeComponentsOverFiveHundredTwelveCubicMeters;
}
return Result;
}
struct FStep2fFragmentMetrics
{
int32 AirComponents = 0;
int32 SolidComponents = 0;
int64 AirCrumbsBelowOneCubicMeter = 0;
int64 SolidCrumbsBelowOneCubicMeter = 0;
int64 AirCrumbVoxelSamples = 0;
int64 SolidCrumbVoxelSamples = 0;
int64 AirCrumbsAfterBoundaryPadding = -1;
int64 SolidCrumbsAfterBoundaryPadding = -1;
int32 LargestAirComponentVoxels = 0;
int32 LargestSolidComponentVoxels = 0;
struct FCrumbComponent
{
bool bAir = false;
int32 Samples = 0;
FIntVector Min = FIntVector::ZeroValue;
FIntVector Max = FIntVector::ZeroValue;
FIntVector Root = FIntVector::ZeroValue;
int32 SignMismatchSamples = 0;
float DensityAtRoot = 0.0f;
float DensityAtMin = 0.0f;
};
TArray<FCrumbComponent> CrumbComponents;
};
FStep2fFragmentMetrics ComputeStep2fFragmentMetrics(
const TArray<float>& Density, int32 Dimension,
int32 CoreInset = 0, int32 CoreDimension = 0)
{
FStep2fFragmentMetrics Result;
const int32 Count = Density.Num();
if (Count <= 0 || Count != Dimension * Dimension * Dimension)
{
return Result;
}
TArray<int32> Parent;
Parent.Init(-1, Count);
const auto FindRoot = [&Parent](int32 Index)
{
int32 Root = Index;
while (Parent[Root] >= 0)
{
Root = Parent[Root];
}
while (Index != Root && Parent[Index] >= 0)
{
const int32 Next = Parent[Index];
Parent[Index] = Root;
Index = Next;
}
return Root;
};
const auto Join = [&Parent, &FindRoot](int32 A, int32 B)
{
int32 RootA = FindRoot(A);
int32 RootB = FindRoot(B);
if (RootA == RootB)
{
return;
}
if (Parent[RootA] > Parent[RootB])
{
Swap(RootA, RootB);
}
Parent[RootA] += Parent[RootB];
Parent[RootB] = RootA;
};
for (int32 Z = 0; Z < Dimension; ++Z)
for (int32 Y = 0; Y < Dimension; ++Y)
for (int32 X = 0; X < Dimension; ++X)
{
const int32 Index = (Z * Dimension + Y) * Dimension + X;
const bool bAir = Density[Index] > 0.0f;
// Use 26-neighbor connectivity on the sign lattice. Diagonally adjacent
// samples belong to the same continuous marching-cubes component; mesh
// islands and folded triangles are measured separately below.
for (int32 DeltaZ = -1; DeltaZ <= 0; ++DeltaZ)
for (int32 DeltaY = -1; DeltaY <= 1; ++DeltaY)
for (int32 DeltaX = -1; DeltaX <= 1; ++DeltaX)
{
if (DeltaZ == 0 && (DeltaY > 0 || (DeltaY == 0 && DeltaX >= 0)))
{
continue;
}
const int32 NeighborX = X + DeltaX;
const int32 NeighborY = Y + DeltaY;
const int32 NeighborZ = Z + DeltaZ;
if (NeighborX < 0 || NeighborY < 0 || NeighborZ < 0
|| NeighborX >= Dimension || NeighborY >= Dimension
|| NeighborZ >= Dimension)
{
continue;
}
const int32 Neighbor = (NeighborZ * Dimension + NeighborY) * Dimension
+ NeighborX;
if ((Density[Neighbor] > 0.0f) == bAir)
{
Join(Index, Neighbor);
}
}
}
TSet<int32> ComponentsIntersectingCore;
if (CoreDimension > 0 && CoreInset >= 0
&& CoreInset + CoreDimension <= Dimension)
{
for (int32 Z = CoreInset; Z < CoreInset + CoreDimension; ++Z)
for (int32 Y = CoreInset; Y < CoreInset + CoreDimension; ++Y)
for (int32 X = CoreInset; X < CoreInset + CoreDimension; ++X)
{
const int32 Index = (Z * Dimension + Y) * Dimension + X;
ComponentsIntersectingCore.Add(FindRoot(Index));
}
}
constexpr int32 VoxelsPerMeter = static_cast<int32>(100.0f / VOXEL_SIZE + 0.5f);
constexpr int32 VoxelsPerCubicMeter = VoxelsPerMeter * VoxelsPerMeter * VoxelsPerMeter;
TMap<int32, FStep2fFragmentMetrics::FCrumbComponent> CrumbByRoot;
for (int32 Index = 0; Index < Count; ++Index)
{
if (Parent[Index] >= 0)
{
continue;
}
const int32 ComponentVoxels = -Parent[Index];
const bool bAir = Density[Index] > 0.0f;
const bool bIntersectsCore = ComponentsIntersectingCore.IsEmpty()
|| ComponentsIntersectingCore.Contains(Index);
if (bAir)
{
++Result.AirComponents;
Result.LargestAirComponentVoxels = FMath::Max(
Result.LargestAirComponentVoxels, ComponentVoxels);
if (ComponentVoxels < VoxelsPerCubicMeter && bIntersectsCore)
{
++Result.AirCrumbsBelowOneCubicMeter;
Result.AirCrumbVoxelSamples += ComponentVoxels;
if (Result.CrumbComponents.Num() + CrumbByRoot.Num() < 32)
{
FStep2fFragmentMetrics::FCrumbComponent& Crumb =
CrumbByRoot.Add(Index);
Crumb.bAir = true;
Crumb.Samples = ComponentVoxels;
Crumb.Min = FIntVector(Dimension, Dimension, Dimension);
Crumb.Max = FIntVector::ZeroValue;
Crumb.Root = FIntVector(Index % Dimension,
(Index / Dimension) % Dimension, Index / (Dimension * Dimension));
Crumb.DensityAtRoot = Density[Index];
}
}
}
else
{
++Result.SolidComponents;
Result.LargestSolidComponentVoxels = FMath::Max(
Result.LargestSolidComponentVoxels, ComponentVoxels);
if (ComponentVoxels < VoxelsPerCubicMeter && bIntersectsCore)
{
++Result.SolidCrumbsBelowOneCubicMeter;
Result.SolidCrumbVoxelSamples += ComponentVoxels;
if (Result.CrumbComponents.Num() + CrumbByRoot.Num() < 32)
{
FStep2fFragmentMetrics::FCrumbComponent& Crumb =
CrumbByRoot.Add(Index);
Crumb.bAir = false;
Crumb.Samples = ComponentVoxels;
Crumb.Min = FIntVector(Dimension, Dimension, Dimension);
Crumb.Max = FIntVector::ZeroValue;
Crumb.Root = FIntVector(Index % Dimension,
(Index / Dimension) % Dimension, Index / (Dimension * Dimension));
Crumb.DensityAtRoot = Density[Index];
}
}
}
}
if (!CrumbByRoot.IsEmpty())
{
for (int32 Index = 0; Index < Count; ++Index)
{
const int32 Root = FindRoot(Index);
FStep2fFragmentMetrics::FCrumbComponent* Crumb = CrumbByRoot.Find(Root);
if (Crumb == nullptr)
{
continue;
}
const int32 X = Index % Dimension;
const int32 Y = (Index / Dimension) % Dimension;
const int32 Z = Index / (Dimension * Dimension);
Crumb->SignMismatchSamples +=
(Density[Index] > 0.0f) != Crumb->bAir ? 1 : 0;
Crumb->Min.X = FMath::Min(Crumb->Min.X, X);
Crumb->Min.Y = FMath::Min(Crumb->Min.Y, Y);
Crumb->Min.Z = FMath::Min(Crumb->Min.Z, Z);
Crumb->Max.X = FMath::Max(Crumb->Max.X, X);
Crumb->Max.Y = FMath::Max(Crumb->Max.Y, Y);
Crumb->Max.Z = FMath::Max(Crumb->Max.Z, Z);
}
TArray<int32> Roots;
CrumbByRoot.GetKeys(Roots);
Roots.Sort();
for (const int32 Root : Roots)
{
FStep2fFragmentMetrics::FCrumbComponent& Crumb = CrumbByRoot[Root];
const int32 MinIndex = (Crumb.Min.Z * Dimension + Crumb.Min.Y)
* Dimension + Crumb.Min.X;
Crumb.DensityAtMin = Density[MinIndex];
Result.CrumbComponents.Add(Crumb);
}
}
return Result;
}
struct FStep2fGridSignatureMetrics
{
double PitchCorrelationPeak = 0.0;
double NeighborCorrelationPeak = 0.0;
double PitchPeakExcess = 0.0;
};
FStep2fGridSignatureMetrics ComputeStep2fGridSignature(
const UVoxelGenerator& Generator, FIntVector RingOrigin, int32 RingDimension,
float PitchVoxels)
{
FStep2fGridSignatureMetrics Result;
FScopedVoxelStackRegistration StackRegistration;
constexpr int32 SampleStep = static_cast<int32>(100.0f / VOXEL_SIZE + 0.5f);
constexpr int32 HalfWindow = static_cast<int32>(
3200.0f / VOXEL_SIZE + 0.5f);
constexpr int32 XYDimension = 2 * HalfWindow / SampleStep + 1;
constexpr int32 HeightLayers = 9;
const FIntVector RingCentre = RingOrigin + FIntVector(
(RingDimension - 1) / 2, (RingDimension - 1) / 2, (RingDimension - 1) / 2);
TArray<uint8> AirSamples;
AirSamples.SetNumUninitialized(XYDimension * XYDimension * HeightLayers);
int64 AirCount = 0;
int64 SampleCount = 0;
for (int32 ZIndex = 0; ZIndex < HeightLayers; ++ZIndex)
{
const int32 Z = RingOrigin.Z + FMath::RoundToInt(
static_cast<float>(RingDimension - 1) * ZIndex / (HeightLayers - 1));
for (int32 YIndex = 0; YIndex < XYDimension; ++YIndex)
for (int32 XIndex = 0; XIndex < XYDimension; ++XIndex)
{
const float WorldX = static_cast<float>(RingCentre.X - HalfWindow
+ XIndex * SampleStep);
const float WorldY = static_cast<float>(RingCentre.Y - HalfWindow
+ YIndex * SampleStep);
const uint8 bAir = VoxelForgeRedefinedDensityField::EvaluateParameterized(
Generator, WorldX, WorldY, static_cast<float>(Z)) > 0.0f ? 1 : 0;
AirSamples[(ZIndex * XYDimension + YIndex) * XYDimension + XIndex] = bAir;
AirCount += bAir;
++SampleCount;
}
}
const double Mean = SampleCount > 0
? static_cast<double>(AirCount) / SampleCount : 0.0;
const double Variance = Mean * (1.0 - Mean);
if (Variance <= 1.0e-6)
{
return Result;
}
const auto CorrelationAtRadius = [&](float Radius)
{
double BestCorrelation = -1.0;
for (int32 Direction = 0; Direction < 8; ++Direction)
{
const float Angle = static_cast<float>(Direction) * 3.14159265358979323846f / 8.0f;
const int32 DX = FMath::RoundToInt(
Radius * FMath::Cos(Angle) / SampleStep);
const int32 DY = FMath::RoundToInt(
Radius * FMath::Sin(Angle) / SampleStep);
if (DX == 0 && DY == 0)
{
continue;
}
double Covariance = 0.0;
int64 Pairs = 0;
for (int32 Z = 0; Z < HeightLayers; ++Z)
for (int32 Y = 0; Y < XYDimension; ++Y)
{
const int32 OtherY = Y + DY;
if (OtherY < 0 || OtherY >= XYDimension)
{
continue;
}
for (int32 X = 0; X < XYDimension; ++X)
{
const int32 OtherX = X + DX;
if (OtherX < 0 || OtherX >= XYDimension)
{
continue;
}
const bool bAirA = AirSamples[
(Z * XYDimension + Y) * XYDimension + X] != 0;
const bool bAirB = AirSamples[
(Z * XYDimension + OtherY) * XYDimension + OtherX] != 0;
Covariance += (static_cast<double>(bAirA) - Mean)
* (static_cast<double>(bAirB) - Mean);
++Pairs;
}
}
if (Pairs > 0)
{
BestCorrelation = FMath::Max(
BestCorrelation, Covariance / (static_cast<double>(Pairs) * Variance));
}
}
return BestCorrelation;
};
Result.PitchCorrelationPeak = CorrelationAtRadius(PitchVoxels);
Result.NeighborCorrelationPeak = FMath::Max(
CorrelationAtRadius(PitchVoxels * 0.82f),
CorrelationAtRadius(PitchVoxels * 1.18f));
Result.PitchPeakExcess = Result.PitchCorrelationPeak - Result.NeighborCorrelationPeak;
return Result;
}
struct FStep2fDetailSpectrum
{
double Rms = 0.0;
double LatticeScaleEnergyRatio = 0.0;
};
FStep2fDetailSpectrum ComputeStep2fDetailSpectrum(const TArray<float>& Detail, int32 Dimension)
{
FStep2fDetailSpectrum Result;
if (Detail.Num() != Dimension * Dimension * Dimension || Detail.IsEmpty())
{
return Result;
}
double Mean = 0.0;
for (const float Value : Detail)
{
Mean += Value;
}
Mean /= Detail.Num();
double Variance = 0.0;
for (const float Value : Detail)
{
Variance += FMath::Square(static_cast<double>(Value) - Mean);
}
Variance /= Detail.Num();
double DifferenceEnergy = 0.0;
int64 DifferenceCount = 0;
for (int32 Z = 0; Z < Dimension; ++Z)
for (int32 Y = 0; Y < Dimension; ++Y)
for (int32 X = 0; X < Dimension; ++X)
{
const int32 Index = (Z * Dimension + Y) * Dimension + X;
constexpr int32 PhysicalLagVoxels = static_cast<int32>(25.0f / VOXEL_SIZE + 0.5f);
if (X + PhysicalLagVoxels < Dimension)
{
DifferenceEnergy += FMath::Square(
static_cast<double>(Detail[Index + PhysicalLagVoxels] - Detail[Index]));
++DifferenceCount;
}
if (Y + PhysicalLagVoxels < Dimension)
{
DifferenceEnergy += FMath::Square(
static_cast<double>(Detail[Index + PhysicalLagVoxels * Dimension] - Detail[Index]));
++DifferenceCount;
}
if (Z + PhysicalLagVoxels < Dimension)
{
DifferenceEnergy += FMath::Square(
static_cast<double>(Detail[Index + PhysicalLagVoxels * Dimension * Dimension]
- Detail[Index]));
++DifferenceCount;
}
}
Result.Rms = FMath::Sqrt(Variance);
Result.LatticeScaleEnergyRatio = Variance > 1.0e-8 && DifferenceCount > 0
? DifferenceEnergy / DifferenceCount / (2.0 * Variance) : 0.0;
return Result;
}
struct FStep2fEdgeUse
{
FVector FirstNormal = FVector::ZeroVector;
FVector SecondNormal = FVector::ZeroVector;
int32 Count = 0;
};
struct FStep2fMeshMetrics
{
int32 TriangleCount = 0;
int64 ManifoldEdgeCount = 0;
double ShareDihedralsOver45Degrees = 0.0;
double P95DihedralDegrees = 0.0;
};
float Step2fPointTriangleDistanceSquared(
const FVector& Point, const FVector& A, const FVector& B, const FVector& C)
{
const FVector AB = B - A;
const FVector AC = C - A;
const FVector AP = Point - A;
const FVector Normal = FVector::CrossProduct(AB, AC);
const double NormalSquared = Normal.SizeSquared();
const double D00 = FVector::DotProduct(AB, AB);
const double D01 = FVector::DotProduct(AB, AC);
const double D11 = FVector::DotProduct(AC, AC);
const double D20 = FVector::DotProduct(AP, AB);
const double D21 = FVector::DotProduct(AP, AC);
const double Denominator = D00 * D11 - D01 * D01;
if (NormalSquared > 1.0e-8 && Denominator > 1.0e-8)
{
const double V = (D11 * D20 - D01 * D21) / Denominator;
const double W = (D00 * D21 - D01 * D20) / Denominator;
if (V >= 0.0 && W >= 0.0 && V + W <= 1.0)
{
const double PlaneDistance = FVector::DotProduct(AP, Normal)
/ FMath::Sqrt(NormalSquared);
return FMath::Square(PlaneDistance);
}
}
const auto SegmentDistanceSquared = [&Point](const FVector& Start, const FVector& End)
{
const FVector Segment = End - Start;
const double LengthSquared = Segment.SizeSquared();
const double T = LengthSquared > 1.0e-8
? FMath::Clamp(FVector::DotProduct(Point - Start, Segment) / LengthSquared,
0.0, 1.0)
: 0.0;
return FVector::DistSquared(Point, Start + Segment * T);
};
return FMath::Min3(
SegmentDistanceSquared(A, B),
SegmentDistanceSquared(B, C),
SegmentDistanceSquared(C, A));
}
FIntVector SelectStep2fMeshOrigin(
const TArray<float>& Density, FIntVector RingOrigin, int32 Dimension)
{
constexpr int32 VoxelsPerMeter = static_cast<int32>(100.0f / VOXEL_SIZE + 0.5f);
constexpr int32 PhysicalTileVoxels = 16 * VoxelsPerMeter;
constexpr int32 ScoreStepVoxels = VoxelsPerMeter;
constexpr int32 CandidateStepVoxels = 8 * VoxelsPerMeter;
const int32 MaximumStart = Dimension - 1 - PhysicalTileVoxels;
if (Density.Num() != Dimension * Dimension * Dimension || MaximumStart < 0)
{
return RingOrigin;
}
TArray<int32, TInlineAllocator<16>> Starts;
for (int32 Start = 0; Start <= MaximumStart; Start += CandidateStepVoxels)
{
Starts.Add(Start);
}
if (Starts.IsEmpty() || Starts.Last() != MaximumStart)
{
Starts.Add(MaximumStart);
}
int64 BestSurfaceEdgeCount = -1;
FIntVector BestStart(MaximumStart / 2, MaximumStart / 2, MaximumStart / 2);
for (const int32 StartZ : Starts)
for (const int32 StartY : Starts)
for (const int32 StartX : Starts)
{
int64 SurfaceEdgeCount = 0;
for (int32 Z = 0; Z <= PhysicalTileVoxels; Z += ScoreStepVoxels)
for (int32 Y = 0; Y <= PhysicalTileVoxels; Y += ScoreStepVoxels)
for (int32 X = 0; X <= PhysicalTileVoxels; X += ScoreStepVoxels)
{
const int32 SampleX = StartX + X;
const int32 SampleY = StartY + Y;
const int32 SampleZ = StartZ + Z;
const int32 Index = (SampleZ * Dimension + SampleY) * Dimension + SampleX;
const bool bAir = Density[Index] > 0.0f;
if (X < PhysicalTileVoxels)
{
SurfaceEdgeCount += bAir != (Density[Index + ScoreStepVoxels] > 0.0f) ? 1 : 0;
}
if (Y < PhysicalTileVoxels)
{
SurfaceEdgeCount += bAir
!= (Density[Index + ScoreStepVoxels * Dimension] > 0.0f) ? 1 : 0;
}
if (Z < PhysicalTileVoxels)
{
SurfaceEdgeCount += bAir
!= (Density[Index + ScoreStepVoxels * Dimension * Dimension] > 0.0f)
? 1 : 0;
}
}
if (SurfaceEdgeCount > BestSurfaceEdgeCount)
{
BestSurfaceEdgeCount = SurfaceEdgeCount;
BestStart = FIntVector(StartX, StartY, StartZ);
}
}
return RingOrigin + BestStart;
}
FStep2fMeshMetrics ComputeStep2fMeshMetrics(const FVoxelMeshData& Mesh)
{
FStep2fMeshMetrics Result;
Result.TriangleCount = Mesh.Triangles.Num() / 3;
TMap<uint64, FStep2fEdgeUse> EdgeUses;
EdgeUses.Reserve(Mesh.Triangles.Num());
const auto AddEdge = [&EdgeUses](int32 A, int32 B, const FVector& Normal)
{
if (A > B)
{
Swap(A, B);
}
const uint64 Key = (static_cast<uint64>(static_cast<uint32>(A)) << 32)
| static_cast<uint32>(B);
FStep2fEdgeUse& Use = EdgeUses.FindOrAdd(Key);
if (Use.Count == 0)
{
Use.FirstNormal = Normal;
}
else if (Use.Count == 1)
{
Use.SecondNormal = Normal;
}
++Use.Count;
};
for (int32 Triangle = 0; Triangle + 2 < Mesh.Triangles.Num(); Triangle += 3)
{
const int32 IA = Mesh.Triangles[Triangle];
const int32 IB = Mesh.Triangles[Triangle + 1];
const int32 IC = Mesh.Triangles[Triangle + 2];
if (!Mesh.Vertices.IsValidIndex(IA) || !Mesh.Vertices.IsValidIndex(IB)
|| !Mesh.Vertices.IsValidIndex(IC))
{
continue;
}
FVector Normal = FVector::CrossProduct(
Mesh.Vertices[IB] - Mesh.Vertices[IA],
Mesh.Vertices[IC] - Mesh.Vertices[IA]);
if (!Normal.Normalize())
{
continue;
}
AddEdge(IA, IB, Normal);
AddEdge(IB, IC, Normal);
AddEdge(IC, IA, Normal);
}
TArray<float> Angles;
Angles.Reserve(EdgeUses.Num());
int64 Over45 = 0;
for (const TPair<uint64, FStep2fEdgeUse>& Pair : EdgeUses)
{
const FStep2fEdgeUse& Use = Pair.Value;
if (Use.Count != 2)
{
continue;
}
const double Dot = FMath::Clamp(
FMath::Abs(FVector::DotProduct(Use.FirstNormal, Use.SecondNormal)), 0.0, 1.0);
const float AngleDegrees = FMath::RadiansToDegrees(
FMath::Acos(static_cast<float>(Dot)));
Angles.Add(AngleDegrees);
Over45 += AngleDegrees > 45.0f ? 1 : 0;
}
Result.ManifoldEdgeCount = Angles.Num();
if (!Angles.IsEmpty())
{
Angles.Sort();
Result.ShareDihedralsOver45Degrees =
static_cast<double>(Over45) / Angles.Num();
const int32 P95Index = FMath::Clamp(
FMath::CeilToInt(0.95f * Angles.Num()) - 1, 0, Angles.Num() - 1);
Result.P95DihedralDegrees = Angles[P95Index];
}
return Result;
}
struct FStep2fStructureMetrics
{
int32 StratesChecked = 0;
int64 RoofSamples = 0;
int64 RoofOpenings = 0;
int64 FloorSamples = 0;
int64 FloorOpenings = 0;
};
struct FStep2fShapeSnapshot
{
FStep2eVarietyMetrics Variety;
FStep2fFragmentMetrics Fragments;
FStep2fGridSignatureMetrics Grid;
FStep2fDetailSpectrum Detail;
FStep2fMeshMetrics Mesh;
int32 Lod1VertexCount = 0;
double Lod1P95SurfaceErrorVoxels = 0.0;
FStep2fStructureMetrics Structure;
};
TSharedRef<FJsonObject> Step2eParametersJson(
const VoxelForgeRedefinedDensityField::FGenerationParameters& P)
{
TSharedRef<FJsonObject> Json = MakeShared<FJsonObject>();
Json->SetNumberField(TEXT("room_pitch_voxels"), P.RoomPitchVoxels);
Json->SetNumberField(TEXT("room_radius_fraction_min"), P.MinimumRoomRadiusFraction);
Json->SetNumberField(TEXT("room_radius_fraction_max"), P.MaximumRoomRadiusFraction);
Json->SetNumberField(TEXT("room_height_voxels"), P.RoomHeightVoxels);
Json->SetNumberField(TEXT("floor_relief_voxels"), P.FloorReliefVoxels);
Json->SetNumberField(TEXT("tunnel_radius_voxels"), P.TunnelRadiusVoxels);
Json->SetNumberField(TEXT("tunnel_half_height_voxels"), P.TunnelHalfHeightVoxels);
Json->SetNumberField(TEXT("loop_probability"), P.LoopProbability);
Json->SetNumberField(TEXT("vertical_shaft_probability"), P.VerticalShaftProbability);
Json->SetNumberField(TEXT("shaft_radius_voxels"), P.ShaftRadiusVoxels);
Json->SetNumberField(TEXT("pillar_probability"), P.PillarProbability);
Json->SetNumberField(TEXT("ledge_probability"), P.LedgeProbability);
Json->SetNumberField(TEXT("floating_mass_probability"), P.FloatingMassProbability);
Json->SetNumberField(TEXT("stalactite_stalagmite_probability"), P.SpireProbability);
Json->SetNumberField(TEXT("basin_probability"), P.BasinProbability);
Json->SetNumberField(TEXT("basin_depth_voxels"), P.BasinDepthVoxels);
Json->SetNumberField(TEXT("water_level_fraction"), P.WaterLevelFraction);
return Json;
}
TSharedRef<FJsonObject> Step2eChamberVolumeQuantilesJson(
const VoxelForgeRedefinedDensityField::FGenerationParameters& P)
{
TSharedRef<FJsonObject> Json = MakeShared<FJsonObject>();
constexpr double Pi = 3.14159265358979323846;
constexpr double VoxelMeters = static_cast<double>(VOXEL_SIZE) / 100.0;
constexpr double CubicMetersPerVoxel = VoxelMeters * VoxelMeters * VoxelMeters;
const double Quantiles[] = {0.10, 0.50, 0.90};
const TCHAR* Names[] = {TEXT("p10_m3"), TEXT("p50_m3"), TEXT("p90_m3")};
for (int32 Index = 0; Index < UE_ARRAY_COUNT(Quantiles); ++Index)
{
const double RadiusFraction = FMath::Lerp(
static_cast<double>(P.MinimumRoomRadiusFraction),
static_cast<double>(P.MaximumRoomRadiusFraction), Quantiles[Index]);
const double RadiusVoxels = RadiusFraction * P.RoomPitchVoxels;
const double VolumeCubicVoxels = Pi * RadiusVoxels * RadiusVoxels
* P.RoomHeightVoxels;
Json->SetNumberField(Names[Index], VolumeCubicVoxels * CubicMetersPerVoxel);
}
return Json;
}
TSharedRef<FJsonObject> Step2eParameterRangesJson()
{
using FParameters = VoxelForgeRedefinedDensityField::FGenerationParameters;
struct FParameterRange
{
const TCHAR* Name;
float FParameters::*Value;
};
static const FParameterRange Fields[] = {
{TEXT("room_pitch_voxels"), &FParameters::RoomPitchVoxels},
{TEXT("room_radius_fraction_min"), &FParameters::MinimumRoomRadiusFraction},
{TEXT("room_radius_fraction_max"), &FParameters::MaximumRoomRadiusFraction},
{TEXT("room_height_voxels"), &FParameters::RoomHeightVoxels},
{TEXT("floor_relief_voxels"), &FParameters::FloorReliefVoxels},
{TEXT("tunnel_radius_voxels"), &FParameters::TunnelRadiusVoxels},
{TEXT("tunnel_half_height_voxels"), &FParameters::TunnelHalfHeightVoxels},
{TEXT("loop_probability"), &FParameters::LoopProbability},
{TEXT("vertical_shaft_probability"), &FParameters::VerticalShaftProbability},
{TEXT("shaft_radius_voxels"), &FParameters::ShaftRadiusVoxels},
{TEXT("pillar_probability"), &FParameters::PillarProbability},
{TEXT("ledge_probability"), &FParameters::LedgeProbability},
{TEXT("floating_mass_probability"), &FParameters::FloatingMassProbability},
{TEXT("stalactite_stalagmite_probability"), &FParameters::SpireProbability},
{TEXT("basin_probability"), &FParameters::BasinProbability},
{TEXT("basin_depth_voxels"), &FParameters::BasinDepthVoxels},
{TEXT("water_level_fraction"), &FParameters::WaterLevelFraction},
};
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
for (const FParameterRange& Field : Fields)
{
float Minimum = FLT_MAX;
float Maximum = -FLT_MAX;
for (int32 ProfileIndex = 0;
ProfileIndex < VoxelForgeRedefinedDensityField::GetParameterSetCount();
++ProfileIndex)
{
const TCHAR* Name = nullptr;
FParameters Parameters;
if (!VoxelForgeRedefinedDensityField::GetParameterSetInfo(
ProfileIndex, Name, Parameters))
{
continue;
}
const float Value = Parameters.*(Field.Value);
Minimum = FMath::Min(Minimum, Value);
Maximum = FMath::Max(Maximum, Value);
}
TSharedRef<FJsonObject> Bounds = MakeShared<FJsonObject>();
Bounds->SetNumberField(TEXT("minimum_across_profiles"), Minimum);
Bounds->SetNumberField(TEXT("maximum_across_profiles"), Maximum);
Result->SetObjectField(Field.Name, Bounds);
}
return Result;
}
bool Step2eSameWorkerRun(
const VoxelForgeRegionEval::FTiming& A,
const VoxelForgeRegionEval::FTiming& B)
{
return A.QuantizedHash == B.QuantizedHash && A.RawFloatHash == B.RawFloatHash;
}
TSharedRef<FJsonObject> Step2dTimingJson(const VoxelForgeRegionEval::FTiming& Timing)
{
TSharedRef<FJsonObject> Json = MakeShared<FJsonObject>();
Json->SetNumberField(TEXT("workers_requested"), Timing.Workers);
Json->SetNumberField(TEXT("workers_observed"), Timing.ObservedWorkerThreads);
Json->SetNumberField(TEXT("lattice_samples"), static_cast<double>(Timing.SampleCount));
Json->SetNumberField(TEXT("density_samples_evaluated"), static_cast<double>(Timing.EvaluatedSamples));
Json->SetNumberField(TEXT("samples_skipped_by_brick_proof"), static_cast<double>(Timing.SkippedSamples));
Json->SetNumberField(TEXT("bricks_tested"), static_cast<double>(Timing.BrickCount));
Json->SetNumberField(TEXT("bricks_skipped"), static_cast<double>(Timing.SkippedBricks));
Json->SetNumberField(TEXT("share_of_bricks_skipped"), Timing.BrickCount > 0
? static_cast<double>(Timing.SkippedBricks) / static_cast<double>(Timing.BrickCount) : 0.0);
Json->SetNumberField(TEXT("share_of_lattice_samples_skipped"), Timing.SampleCount > 0
? static_cast<double>(Timing.SkippedSamples) / static_cast<double>(Timing.SampleCount) : 0.0);
Json->SetNumberField(TEXT("wall_ms"), Timing.WallSeconds * 1000.0);
Json->SetNumberField(TEXT("worker_cpu_seconds"), Timing.WorkerCpuSeconds);
Json->SetNumberField(TEXT("worker_cpu_ns_per_lattice_sample"), Timing.NanosecondsPerSample);
Json->SetNumberField(TEXT("brick_classification_worker_seconds"), Timing.BrickClassificationSeconds);
Json->SetNumberField(TEXT("density_evaluation_worker_seconds"), Timing.DensityEvaluationSeconds);
Json->SetNumberField(TEXT("uniform_fill_worker_seconds"), Timing.UniformFillSeconds);
Json->SetNumberField(TEXT("longest_worker_wall_ms"), Timing.LongestWorkerWallSeconds * 1000.0);
Json->SetNumberField(TEXT("parallel_overhead_wall_ms"), FMath::Max(
0.0, Timing.WallSeconds - Timing.LongestWorkerWallSeconds) * 1000.0);
Json->SetNumberField(TEXT("brick_classification_calls"), static_cast<double>(Timing.MacroBrickChecks));
Json->SetNumberField(TEXT("macro_center_evaluations"), static_cast<double>(Timing.MacroCentreEvaluations));
Json->SetNumberField(TEXT("unsupported_brick_proofs"), static_cast<double>(Timing.UnsupportedBrickProofs));
Json->SetNumberField(TEXT("chunk_or_strate_boundary_proofs"),
static_cast<double>(Timing.ChunkBoundaryBrickProofs));
Json->SetNumberField(TEXT("passage_bound_proofs"), static_cast<double>(Timing.PassageBoundBrickProofs));
Json->SetNumberField(TEXT("mixed_macro_interval_proofs"), static_cast<double>(Timing.MixedMacroBrickProofs));
Json->SetNumberField(TEXT("room_sdf_evaluations"), static_cast<double>(Timing.RoomEvaluations));
Json->SetNumberField(TEXT("tunnel_sdf_evaluations"), static_cast<double>(Timing.TunnelEvaluations));
Json->SetNumberField(TEXT("tunnel_core_evaluations"), static_cast<double>(Timing.TunnelCoreEvaluations));
Json->SetNumberField(TEXT("passage_sdf_evaluations"), static_cast<double>(Timing.PassageEvaluations));
Json->SetNumberField(TEXT("geometry_evaluations_per_lattice_sample"), Timing.CandidateEvaluationsPerSample);
Json->SetNumberField(TEXT("quantized_hash"), static_cast<double>(Timing.QuantizedHash));
Json->SetNumberField(TEXT("raw_float_hash"), static_cast<double>(Timing.RawFloatHash));
return Json;
}
int32 RunStep2d(const FString& Params)
{
FExploreArguments Arguments;
FString Error;
if (!ParseArguments(Params, Arguments, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] argument error: %s"), *Error);
return 2;
}
int32 Seed = 1337;
FParse::Value(*Params, TEXT("seed="), Seed);
int32 Workers = VoxelForgeRegionEval::WorkerCount;
FParse::Value(*Params, TEXT("regionworkers="), Workers);
Workers = FMath::Clamp(Workers, 1, 32);
FString ResearchOut;
FParse::Value(*Params, TEXT("researchout="), ResearchOut);
if (ResearchOut.IsEmpty())
{
ResearchOut = FPaths::Combine(Arguments.OutDirectory, TEXT("Research/step2d"));
}
ResearchOut = FPaths::ConvertRelativePathToFull(ResearchOut);
IFileManager::Get().MakeDirectory(*ResearchOut, true);
IFileManager::Get().MakeDirectory(*Arguments.OutDirectory, true);
FExploreWorld World;
int32 RecipeIndex = INDEX_NONE;
uint32 RecipeHash = 0;
int32 EditChunkCount = 0;
const double PrepareStartSeconds = FPlatformTime::Seconds();
if (!PrepareRegionEvalWorld(World, Arguments, Seed,
RecipeIndex, RecipeHash, EditChunkCount, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] world preparation failed: %s"), *Error);
return 2;
}
const double WorldPrepareSeconds = FPlatformTime::Seconds() - PrepareStartSeconds;
if (World.DiffLayer != nullptr)
{
World.DiffLayer->Clear();
}
const int32 Dimension = VoxelForgeRegionEval::RingDimension;
const int32 CenterZ = (World.TargetTopWorldZ + World.TargetBottomWorldZ) / 2;
const FIntVector Origin(-129, -129, CenterZ - 129);
TArray<VoxelForgeRegionEval::FTiming> OldTrials;
TArray<VoxelForgeRegionEval::FTiming> NewTrials;
TArray<float> OldDensities;
TArray<float> NewDensities;
TArray<float> NewRepeatDensities;
TArray<float> NewSkippedDensities;
TArray<float> SmoothDensities;
TArray<float> SingleWorkerDensities;
VoxelForgeRedefinedDensityField::SetEnabled(false);
VoxelForgeRedefinedDensityField::SetDetailScale(1.0f);
for (int32 Trial = 0; Trial < 3; ++Trial)
{
VoxelForgeRegionEval::FTiming Timing;
if (!VoxelForgeRegionEval::Evaluate(
*World.Generator, Origin, Dimension, nullptr, true, false, 1.0f,
&OldDensities, Timing, false, Workers, false))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] old field timing pass %d failed."), Trial + 1);
return 2;
}
OldTrials.Add(Timing);
}
// This fourth old-field pass carries counters; its wall time is deliberately excluded from
// the performance row so instrumentation does not contaminate the measured baseline.
VoxelForgeRegionEval::FTiming OldCounters;
if (!VoxelForgeRegionEval::Evaluate(
*World.Generator, Origin, Dimension, nullptr, true, false, 1.0f,
nullptr, OldCounters, false, Workers, true))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] old field counter pass failed."));
return 2;
}
OldCounters.WallSeconds = OldTrials.Last().WallSeconds;
OldCounters.WorkerCpuSeconds = OldTrials.Last().WorkerCpuSeconds;
OldCounters.NanosecondsPerSample = OldTrials.Last().NanosecondsPerSample;
for (int32 Trial = 0; Trial < 3; ++Trial)
{
VoxelForgeRegionEval::FTiming Timing;
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
NewSkippedDensities, Timing, true))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] redefined 6-worker pass %d failed."), Trial + 1);
return 2;
}
NewTrials.Add(Timing);
}
VoxelForgeRegionEval::FTiming OneWorkerTiming;
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, 1, 1.0f,
SingleWorkerDensities, OneWorkerTiming, false))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] 1-worker determinism pass failed."));
return 2;
}
VoxelForgeRegionEval::FTiming NewFullSixWorkerTiming;
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
NewDensities, NewFullSixWorkerTiming, false))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] full-detail six-worker evidence pass failed."));
return 2;
}
VoxelForgeRegionEval::FTiming NewRepeatTiming;
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
NewRepeatDensities, NewRepeatTiming, false))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] repeated full-detail hash pass failed."));
return 2;
}
VoxelForgeRegionEval::FTiming SmoothTiming;
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 0.0f,
SmoothDensities, SmoothTiming, false))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] deliberate smoothing control failed."));
return 2;
}
int64 DetailBatchParitySamples = 0;
double DetailBatchMaxAbsoluteError = 0.0;
const int64 TotalLatticeSamples = static_cast<int64>(Dimension) * Dimension * Dimension;
for (int64 Index = 0; Index < TotalLatticeSamples; Index += 2047)
{
const int32 X = static_cast<int32>(Index % Dimension);
const int32 Y = static_cast<int32>((Index / Dimension) % Dimension);
const int32 Z = static_cast<int32>(Index / (static_cast<int64>(Dimension) * Dimension));
const float RawDetailField = VoxelForgeRedefinedDensityField::Evaluate(
*World.Generator, static_cast<float>(Origin.X + X),
static_cast<float>(Origin.Y + Y), static_cast<float>(Origin.Z + Z), nullptr, 1.0f);
DetailBatchMaxAbsoluteError = FMath::Max(
DetailBatchMaxAbsoluteError,
static_cast<double>(FMath::Abs(RawDetailField - NewDensities[static_cast<int32>(Index)])));
++DetailBatchParitySamples;
}
const bool bDetailBatchMatchesRaw = DetailBatchMaxAbsoluteError <= 1.0e-5;
const FStep2dSurfaceSpectrum OldSpectrum = ComputeStep2dSpectrum(OldDensities, Dimension);
const FStep2dSurfaceSpectrum NewSpectrum = ComputeStep2dSpectrum(NewDensities, Dimension);
const FStep2dSurfaceSpectrum SmoothSpectrum = ComputeStep2dSpectrum(SmoothDensities, Dimension);
const FStep2dBrickProofAudit BrickProofAudit = AuditStep2dBrickProofs(
*World.Generator, Origin, Dimension);
const int32 FeatureCellSpacing = World.Definitions.IsValidIndex(Arguments.Slot)
&& World.Definitions[Arguments.Slot].IsValid()
&& World.Definitions[Arguments.Slot]->GeneratorType == ECaveGeneratorType::Maze
? 96 : 128;
const FStep2dBoundaryStats BoundaryStats = ComputeStep2dBoundaryStats(
NewDensities, Dimension, Origin, FeatureCellSpacing);
const bool bHashesAgree = OldDensities.Num() == Dimension * Dimension * Dimension
&& VoxelForgeRegionEval::HashQuantized(NewDensities)
== VoxelForgeRegionEval::HashQuantized(SingleWorkerDensities)
&& VoxelForgeRegionEval::HashQuantized(NewDensities)
== VoxelForgeRegionEval::HashQuantized(NewRepeatDensities)
&& NewFullSixWorkerTiming.RawFloatHash == OneWorkerTiming.RawFloatHash
&& NewFullSixWorkerTiming.RawFloatHash == NewRepeatTiming.RawFloatHash;
VoxelForgeRedefinedDensityField::SetEnabled(false);
VoxelForgeRedefinedDensityField::SetDetailScale(1.0f);
bool bMeshContainsBothIsoBands = false;
const FIntVector MeshOrigin = SelectStep2dMeshOrigin(
OldDensities, NewDensities, Origin, Dimension, bMeshContainsBothIsoBands);
const FVoxelMeshData OldMesh = World.Mesher->GenerateMesh(MeshOrigin, 1, 64);
const int32 OldTriangleCount = FMath::Max(0, OldMesh.Triangles.Num() / 3 - OldMesh.NumSkirtTriangles);
VoxelForgeRedefinedDensityField::SetEnabled(true);
const FVoxelMeshData NewMesh = World.Mesher->GenerateMesh(MeshOrigin, 1, 64);
const int32 NewTriangleCount = FMath::Max(0, NewMesh.Triangles.Num() / 3 - NewMesh.NumSkirtTriangles);
VoxelForgeRedefinedDensityField::SetDetailScale(0.0f);
const FVoxelMeshData SmoothMesh = World.Mesher->GenerateMesh(MeshOrigin, 1, 64);
const int32 SmoothTriangleCount = FMath::Max(0, SmoothMesh.Triangles.Num() / 3 - SmoothMesh.NumSkirtTriangles);
VoxelForgeRedefinedDensityField::SetDetailScale(1.0f);
VoxelForgeRedefinedDensityField::SetEnabled(false);
const FString ImageNames[] = {
TEXT("slice_xy_center.png"), TEXT("slice_xz_y0.png"),
TEXT("slice_yz_x0.png"), TEXT("slice_xz_passage.png")};
FString SliceError;
SaveStep2dSideBySideSlice(
FPaths::Combine(ResearchOut, ImageNames[0]), OldDensities, NewDensities,
Dimension, TEXT('h'), Dimension / 2, SliceError);
SaveStep2dSideBySideSlice(
FPaths::Combine(ResearchOut, ImageNames[1]), OldDensities, NewDensities,
Dimension, TEXT('x'), Dimension / 2, SliceError);
SaveStep2dSideBySideSlice(
FPaths::Combine(ResearchOut, ImageNames[2]), OldDensities, NewDensities,
Dimension, TEXT('y'), Dimension / 2, SliceError);
int32 PassageIndex = Dimension / 2;
if (World.Manager != nullptr && !World.Manager->GetPassages().IsEmpty())
{
const FVoxelPassage& Passage = World.Manager->GetPassages()[0];
PassageIndex = FMath::Clamp(FMath::RoundToInt(Passage.LowerLanding.StandingPoint.Y)
- Origin.Y, 0, Dimension - 1);
}
SaveStep2dSideBySideSlice(
FPaths::Combine(ResearchOut, ImageNames[3]), OldDensities, NewDensities,
Dimension, TEXT('x'), PassageIndex, SliceError);
if (!SliceError.IsEmpty())
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] slice image write failed: %s"), *SliceError);
return 2;
}
TArray<double> OldWallTrials;
TArray<double> NewWallTrials;
for (const auto& Timing : OldTrials) OldWallTrials.Add(Timing.WallSeconds);
for (const auto& Timing : NewTrials) NewWallTrials.Add(Timing.WallSeconds);
OldWallTrials.Sort();
NewWallTrials.Sort();
const double OldMedianWall = OldWallTrials[OldWallTrials.Num() / 2];
const double NewMedianWall = NewWallTrials[NewWallTrials.Num() / 2];
const double OldGeometryEvaluations = static_cast<double>(OldCounters.RoomEvaluations
+ OldCounters.TunnelEvaluations + OldCounters.TunnelCoreEvaluations
+ OldCounters.PassageEvaluations);
const double OldGeometryPerSample = OldCounters.SampleCount > 0
? OldGeometryEvaluations / static_cast<double>(OldCounters.SampleCount) : 0.0;
const double NewMacroPerSample = NewTrials.Last().SampleCount > 0
? static_cast<double>(NewTrials.Last().MacroCentreEvaluations
+ NewTrials.Last().EvaluatedSamples)
/ static_cast<double>(NewTrials.Last().SampleCount) : 0.0;
const bool bDetailAtLeastOld = NewSpectrum.RMSLag1 >= OldSpectrum.RMSLag1
&& NewSpectrum.RMSLag2 >= OldSpectrum.RMSLag2;
const bool bSmoothControlDetected = SmoothSpectrum.RMSLag1 < NewSpectrum.RMSLag1 * 0.80
|| SmoothSpectrum.RMSLag2 < NewSpectrum.RMSLag2 * 0.80;
const bool bBoundaryStatsSound = BoundaryStats.BrickBoundarySamples > 0
&& BoundaryStats.InteriorSamples > 0
&& BoundaryStats.ChunkBoundarySamples > 0
&& BoundaryStats.FeatureCellBoundarySamples > 0
&& BoundaryStats.BrickJumpRms <= BoundaryStats.InteriorJumpRms * 2.0 + 0.25
&& BoundaryStats.ChunkBoundaryJumpRms <= BoundaryStats.InteriorJumpRms * 2.0 + 0.25
&& BoundaryStats.FeatureCellJumpRms
<= BoundaryStats.FeatureCellInteriorJumpRms * 2.0 + 0.25;
const bool bCorrectProofsSound = BrickProofAudit.CorrectlySkippedBricksAudited > 0
&& BrickProofAudit.CorrectProofSamplesChecked > 0
&& BrickProofAudit.CorrectProofViolations == 0;
const bool bUndersizedBoundCaught = BrickProofAudit.DeliberateBoundDisagreements > 0;
const bool bSmoothWouldFallBelowOldSpectrum = SmoothSpectrum.RMSLag1 < OldSpectrum.RMSLag1
|| SmoothSpectrum.RMSLag2 < OldSpectrum.RMSLag2;
const bool bSpeedPass = NewMedianWall <= 0.600;
const bool bPassed = bSpeedPass && bHashesAgree && bDetailAtLeastOld
&& bSmoothControlDetected
&& bBoundaryStatsSound && bCorrectProofsSound
&& bUndersizedBoundCaught
&& bDetailBatchMatchesRaw
&& NewTrials.Last().NonFiniteSamples == 0;
TSharedRef<FJsonObject> Report = MakeShared<FJsonObject>();
Report->SetStringField(TEXT("status"), bPassed ? TEXT("pass") : TEXT("fail"));
Report->SetBoolField(TEXT("pass"), bPassed);
Report->SetBoolField(TEXT("speed_pass_600ms"), bSpeedPass);
Report->SetNumberField(TEXT("seed"), Seed);
Report->SetNumberField(TEXT("ring_dimension"), Dimension);
Report->SetNumberField(TEXT("ring_samples"), static_cast<double>(Dimension) * Dimension * Dimension);
Report->SetNumberField(TEXT("workers_requested"), Workers);
Report->SetNumberField(TEXT("world_prepare_ms_excluded"), WorldPrepareSeconds * 1000.0);
Report->SetNumberField(TEXT("world_prepare_recipe_index"), RecipeIndex);
Report->SetNumberField(TEXT("world_prepare_recipe_hash"), RecipeHash);
Report->SetNumberField(TEXT("cleared_player_edit_chunks"), EditChunkCount);
TSharedRef<FJsonObject> Window = MakeShared<FJsonObject>();
Window->SetNumberField(TEXT("origin_x_voxels"), Origin.X);
Window->SetNumberField(TEXT("origin_y_voxels"), Origin.Y);
Window->SetNumberField(TEXT("origin_z_voxels"), Origin.Z);
Window->SetNumberField(TEXT("samples_per_axis"), Dimension);
Window->SetNumberField(TEXT("sample_stride_voxels"), 1);
Window->SetNumberField(TEXT("sample_spacing_cm"), VOXEL_SIZE);
Report->SetObjectField(TEXT("full_resolution_window"), Window);
Report->SetStringField(TEXT("field_definition"),
TEXT("A seed-offset periodic cross-corridor signed field per strate, joined with rounded "
"landing chambers and existing authored passage carving. Four continuous hash-value "
"noise bands (8, 4, 2, and 1 lattice point) with total bounded amplitude 5.25 are "
"evaluated at every full-resolution sample that can affect the zero set."));
Report->SetStringField(TEXT("resolution_contract"),
TEXT("The lattice remains one sample per 12.5 cm. No coarse density result is interpolated "
"onto it: every potentially surface-affecting point evaluates all four detail bands. "
"Brick skips require a final-field sign proof including the full noise supremum, a "
"floating-point safety margin, and the one-sample marching-cubes halo."));
Report->SetStringField(TEXT("finer_lattice_scaling"),
TEXT("At 12.5 cm, scale macro lengths and the broad/medium noise cell sizes in lattice "
"units by 2 to preserve their world-space shapes; keep or add a 1-2 sample fine octave "
"to retain and extend detail. With fixed 8^3 sample bricks, samples grow 8x but "
"surface-intersecting bricks grow approximately with area (about 4x); the rest are "
"sign-proven and require only density writes. Full-resolution detail remains per "
"potentially surface-affecting point."));
Report->SetNumberField(TEXT("old_macro_geometry_evaluations_per_point"), OldGeometryPerSample);
Report->SetNumberField(TEXT("new_macro_evaluations_per_point_including_brick_proofs"), NewMacroPerSample);
Report->SetNumberField(TEXT("new_noise_value_octaves_per_evaluated_point"), 4.0);
Report->SetNumberField(TEXT("new_noise_octave_evaluations_per_full_ring_point"),
static_cast<double>(NewTrials.Last().EvaluatedSamples) * 4.0
/ static_cast<double>(NewTrials.Last().SampleCount));
Report->SetNumberField(TEXT("old_reported_noise_share_percent_from_step2c"), 2.6);
Report->SetNumberField(TEXT("old_six_worker_median_ms"), OldMedianWall * 1000.0);
Report->SetNumberField(TEXT("new_six_worker_median_ms"), NewMedianWall * 1000.0);
const auto WorkerRemainderSeconds = [](const VoxelForgeRegionEval::FTiming& Timing)
{
return FMath::Max(0.0, Timing.WorkerCpuSeconds
- Timing.BrickClassificationSeconds - Timing.DensityEvaluationSeconds
- Timing.UniformFillSeconds);
};
Report->SetNumberField(TEXT("new_other_worker_cpu_seconds"),
WorkerRemainderSeconds(NewTrials.Last()));
Report->SetNumberField(TEXT("new_other_worker_cpu_ns_per_point"),
WorkerRemainderSeconds(NewTrials.Last()) * 1.0e9
/ static_cast<double>(NewTrials.Last().SampleCount));
Report->SetNumberField(TEXT("new_speedup_vs_old"), NewMedianWall > 0.0
? OldMedianWall / NewMedianWall : 0.0);
TArray<TSharedPtr<FJsonValue>> OldRows;
TArray<TSharedPtr<FJsonValue>> NewRows;
for (const auto& Timing : OldTrials)
{
OldRows.Add(MakeShared<FJsonValueObject>(Step2dTimingJson(Timing)));
}
for (const auto& Timing : NewTrials)
{
NewRows.Add(MakeShared<FJsonValueObject>(Step2dTimingJson(Timing)));
}
Report->SetArrayField(TEXT("old_field_trials"), OldRows);
Report->SetArrayField(TEXT("new_field_trials"), NewRows);
Report->SetObjectField(TEXT("old_full_ring_counters"), Step2dTimingJson(OldCounters));
Report->SetObjectField(TEXT("new_one_worker_determinism_pass"), Step2dTimingJson(OneWorkerTiming));
Report->SetObjectField(TEXT("new_full_detail_six_worker_evidence_pass"),
Step2dTimingJson(NewFullSixWorkerTiming));
Report->SetObjectField(TEXT("new_repeated_full_detail_six_worker_hash_pass"),
Step2dTimingJson(NewRepeatTiming));
Report->SetObjectField(TEXT("smooth_control_full_detail_pass"), Step2dTimingJson(SmoothTiming));
TSharedRef<FJsonObject> Hashes = MakeShared<FJsonObject>();
Hashes->SetStringField(TEXT("six_worker_run_1_quantized"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(NewFullSixWorkerTiming.QuantizedHash)));
Hashes->SetStringField(TEXT("six_worker_run_2_quantized"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(NewRepeatTiming.QuantizedHash)));
Hashes->SetStringField(TEXT("one_worker_quantized"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(OneWorkerTiming.QuantizedHash)));
Hashes->SetStringField(TEXT("six_worker_run_1_raw_float"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(NewFullSixWorkerTiming.RawFloatHash)));
Hashes->SetStringField(TEXT("six_worker_run_2_raw_float"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(NewRepeatTiming.RawFloatHash)));
Hashes->SetStringField(TEXT("one_worker_raw_float"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(OneWorkerTiming.RawFloatHash)));
Hashes->SetBoolField(TEXT("same_across_two_runs_and_1_vs_6_workers"), bHashesAgree);
Report->SetObjectField(TEXT("determinism"), Hashes);
TSharedRef<FJsonObject> Detail = MakeShared<FJsonObject>();
Detail->SetNumberField(TEXT("old_surface_band_samples"), static_cast<double>(OldSpectrum.SurfaceBandSamples));
Detail->SetNumberField(TEXT("new_surface_band_samples"), static_cast<double>(NewSpectrum.SurfaceBandSamples));
Detail->SetNumberField(TEXT("old_curvature_rms_lag1"), OldSpectrum.RMSLag1);
Detail->SetNumberField(TEXT("new_curvature_rms_lag1"), NewSpectrum.RMSLag1);
Detail->SetNumberField(TEXT("smoothed_control_curvature_rms_lag1"), SmoothSpectrum.RMSLag1);
Detail->SetNumberField(TEXT("old_curvature_rms_lag2"), OldSpectrum.RMSLag2);
Detail->SetNumberField(TEXT("new_curvature_rms_lag2"), NewSpectrum.RMSLag2);
Detail->SetNumberField(TEXT("smoothed_control_curvature_rms_lag2"), SmoothSpectrum.RMSLag2);
Detail->SetNumberField(TEXT("new_curvature_rms_lag4"), NewSpectrum.RMSLag4);
Detail->SetNumberField(TEXT("smoothed_control_curvature_rms_lag4"), SmoothSpectrum.RMSLag4);
Detail->SetNumberField(TEXT("old_surface_triangles_64cubed"), OldTriangleCount);
Detail->SetNumberField(TEXT("new_surface_triangles_64cubed"), NewTriangleCount);
Detail->SetNumberField(TEXT("smoothed_control_surface_triangles_64cubed"), SmoothTriangleCount);
Detail->SetNumberField(TEXT("mesh_region_origin_x_voxels"), MeshOrigin.X);
Detail->SetNumberField(TEXT("mesh_region_origin_y_voxels"), MeshOrigin.Y);
Detail->SetNumberField(TEXT("mesh_region_origin_z_voxels"), MeshOrigin.Z);
Detail->SetBoolField(TEXT("mesh_region_contains_old_and_new_iso_bands"),
bMeshContainsBothIsoBands);
Detail->SetBoolField(TEXT("new_detail_at_least_old_spectrum"), bDetailAtLeastOld);
Detail->SetNumberField(TEXT("detail_batch_raw_parity_samples"), DetailBatchParitySamples);
Detail->SetNumberField(TEXT("detail_batch_max_absolute_error"), DetailBatchMaxAbsoluteError);
Detail->SetBoolField(TEXT("detail_batch_matches_direct_field"), bDetailBatchMatchesRaw);
Detail->SetBoolField(TEXT("deliberate_smoothing_control_detected"), bSmoothControlDetected);
Detail->SetBoolField(TEXT("smoothed_control_below_old_spectrum"),
bSmoothWouldFallBelowOldSpectrum);
Report->SetObjectField(TEXT("detail_preservation"), Detail);
TSharedRef<FJsonObject> Boundaries = MakeShared<FJsonObject>();
Boundaries->SetNumberField(TEXT("brick_size_voxels"), 8);
Boundaries->SetNumberField(TEXT("feature_cell_spacing_voxels"), FeatureCellSpacing);
Boundaries->SetNumberField(TEXT("chunk_boundary_gradient_jump_rms_xy_near_surface"),
BoundaryStats.ChunkBoundaryJumpRms);
Boundaries->SetNumberField(TEXT("chunk_boundary_gradient_jump_max_xy_near_surface"),
BoundaryStats.ChunkBoundaryJumpMax);
Boundaries->SetNumberField(TEXT("brick_boundary_gradient_jump_rms"), BoundaryStats.BrickJumpRms);
Boundaries->SetNumberField(TEXT("interior_gradient_jump_rms"), BoundaryStats.InteriorJumpRms);
Boundaries->SetNumberField(TEXT("feature_cell_boundary_gradient_jump_rms"), BoundaryStats.FeatureCellJumpRms);
Boundaries->SetNumberField(TEXT("feature_cell_interior_gradient_jump_rms"),
BoundaryStats.FeatureCellInteriorJumpRms);
Boundaries->SetNumberField(TEXT("brick_boundary_samples_near_surface"),
static_cast<double>(BoundaryStats.BrickBoundarySamples));
Boundaries->SetNumberField(TEXT("chunk_boundary_samples_near_surface_xy"),
static_cast<double>(BoundaryStats.ChunkBoundarySamples));
Boundaries->SetNumberField(TEXT("macro_period_boundary_samples"),
static_cast<double>(BoundaryStats.FeatureCellBoundarySamples));
Boundaries->SetNumberField(TEXT("brick_boundary_gradient_jump_max"), BoundaryStats.BrickJumpMax);
Boundaries->SetNumberField(TEXT("feature_cell_boundary_gradient_jump_max"), BoundaryStats.FeatureCellJumpMax);
Boundaries->SetBoolField(TEXT("boundary_check_pass"), bBoundaryStatsSound);
Report->SetObjectField(TEXT("boundary_artifact_check"), Boundaries);
TSharedRef<FJsonObject> Proofs = MakeShared<FJsonObject>();
Proofs->SetNumberField(TEXT("correctly_skipped_bricks_audited"),
static_cast<double>(BrickProofAudit.CorrectlySkippedBricksAudited));
Proofs->SetNumberField(TEXT("final_field_samples_checked_including_halo"),
static_cast<double>(BrickProofAudit.CorrectProofSamplesChecked));
Proofs->SetNumberField(TEXT("correct_proof_sign_violations"),
static_cast<double>(BrickProofAudit.CorrectProofViolations));
Proofs->SetNumberField(TEXT("deliberately_undersized_detail_bound_claims_checked"),
static_cast<double>(BrickProofAudit.DeliberatelyUndersizedBoundClaimsChecked));
Proofs->SetNumberField(TEXT("wrong_sign_samples_from_undersized_bound"),
static_cast<double>(BrickProofAudit.DeliberateBoundDisagreements));
Proofs->SetNumberField(TEXT("deliberate_control_classifier_detail_scale"), 0.0);
Proofs->SetNumberField(TEXT("deliberate_control_actual_detail_scale"), 4.0);
Proofs->SetBoolField(TEXT("sound_bound_audit_pass"), bCorrectProofsSound);
Proofs->SetBoolField(TEXT("deliberate_undersized_bound_control_caught"), bUndersizedBoundCaught);
Report->SetObjectField(TEXT("brick_bound_audit"), Proofs);
TArray<TSharedPtr<FJsonValue>> Slices;
for (const FString& Name : ImageNames)
{
TSharedRef<FJsonObject> Slice = MakeShared<FJsonObject>();
Slice->SetStringField(TEXT("file"), FPaths::Combine(ResearchOut, Name));
Slice->SetStringField(TEXT("layout"), TEXT("old field on left; redefined field on right; light=air, dark=solid, cyan=near zero"));
Slices.Add(MakeShared<FJsonValueObject>(Slice));
}
Report->SetArrayField(TEXT("slices"), Slices);
FString JsonText;
const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&JsonText);
const FString ReportPath = FPaths::Combine(ResearchOut, TEXT("step2d_report.json"));
if (!FJsonSerializer::Serialize(Report, Writer)
|| !FFileHelper::SaveStringToFile(
JsonText, *ReportPath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM)
|| !FFileHelper::SaveStringToFile(
JsonText, *FPaths::Combine(Arguments.OutDirectory, TEXT("step2d_report.json")),
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] could not write report '%s'."), *ReportPath);
return 2;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeStep2d] pass=%s speed600=%s old_ms=%.3f new_ms=%.3f skip=%.1f%% "
"roughness(old/new/smooth)=%.4f/%.4f/%.4f hashes=%s report=%s"),
bPassed ? TEXT("yes") : TEXT("no"), bSpeedPass ? TEXT("yes") : TEXT("no"),
OldMedianWall * 1000.0, NewMedianWall * 1000.0,
NewTrials.Last().SampleCount > 0
? 100.0 * static_cast<double>(NewTrials.Last().SkippedSamples)
/ static_cast<double>(NewTrials.Last().SampleCount) : 0.0,
OldSpectrum.RMSLag1, NewSpectrum.RMSLag1, SmoothSpectrum.RMSLag1,
bHashesAgree ? TEXT("same") : TEXT("DIFFERENT"), *ReportPath);
return bPassed ? 0 : 2;
}
int32 RunStep2e(const FString& Params)
{
FExploreArguments Arguments;
FString Error;
if (!ParseArguments(Params, Arguments, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2e] argument error: %s"), *Error);
return 2;
}
VoxelForgeRedefinedDensityField::SetLegacyStep2eAuditMode(true);
int32 Seed = 1337;
FParse::Value(*Params, TEXT("seed="), Seed);
int32 Workers = VoxelForgeRegionEval::WorkerCount;
FParse::Value(*Params, TEXT("regionworkers="), Workers);
Workers = FMath::Clamp(Workers, 1, 32);
FString ResearchOut;
FParse::Value(*Params, TEXT("researchout="), ResearchOut);
if (ResearchOut.IsEmpty())
{
ResearchOut = FPaths::Combine(Arguments.OutDirectory, TEXT("Research/step2e"));
}
ResearchOut = FPaths::ConvertRelativePathToFull(ResearchOut);
IFileManager::Get().MakeDirectory(*ResearchOut, true);
IFileManager::Get().MakeDirectory(*Arguments.OutDirectory, true);
FExploreWorld World;
int32 RecipeIndex = INDEX_NONE;
uint32 RecipeHash = 0;
int32 EditChunkCount = 0;
const double PrepareStart = FPlatformTime::Seconds();
if (!PrepareRegionEvalWorld(World, Arguments, Seed,
RecipeIndex, RecipeHash, EditChunkCount, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2e] world preparation failed: %s"), *Error);
return 2;
}
const double WorldPrepareSeconds = FPlatformTime::Seconds() - PrepareStart;
if (World.DiffLayer != nullptr)
{
World.DiffLayer->Clear();
}
const int32 Dimension = VoxelForgeRegionEval::RingDimension;
const int64 TotalSamples = static_cast<int64>(Dimension) * Dimension * Dimension;
const int32 CenterZ = (World.TargetTopWorldZ + World.TargetBottomWorldZ) / 2;
const FIntVector Origin(-129, -129, CenterZ - 129);
VoxelForgeRedefinedDensityField::SetEnabled(false);
VoxelForgeRedefinedDensityField::SetParameterizedEnabled(false);
VoxelForgeRedefinedDensityField::SetParameterSet(-1);
VoxelForgeRedefinedDensityField::SetDetailScale(1.0f);
TArray<float> OldDensities;
VoxelForgeRegionEval::FTiming OldTiming;
if (!VoxelForgeRegionEval::Evaluate(*World.Generator, Origin, Dimension,
nullptr, true, false, 1.0f, &OldDensities, OldTiming, false, Workers, false))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2e] live comparison ring failed."));
return 2;
}
VoxelForgeRedefinedDensityField::SetParameterizedEnabled(true);
TArray<TSharedPtr<FJsonValue>> ProfileRows;
TArray<TSharedPtr<FJsonValue>> SliceRows;
TArray<double> ProfileMedianMs;
bool bProfilePasses = true;
VoxelForgeRedefinedDensityField::FGenerationParameters HardestParameters;
const TCHAR* HardestName = TEXT("HyperComplex");
for (int32 Profile = 0;
Profile < VoxelForgeRedefinedDensityField::GetParameterSetCount(); ++Profile)
{
const TCHAR* ProfileName = nullptr;
VoxelForgeRedefinedDensityField::FGenerationParameters ProfileParameters;
if (!VoxelForgeRedefinedDensityField::GetParameterSetInfo(
Profile, ProfileName, ProfileParameters))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2e] missing profile %d."), Profile);
return 2;
}
VoxelForgeRedefinedDensityField::SetParameterSet(Profile);
TArray<double> Times;
TArray<float> ProfileDensities;
VoxelForgeRegionEval::FTiming LastTiming;
for (int32 Trial = 0; Trial < 3; ++Trial)
{
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
ProfileDensities, LastTiming, true, true))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeStep2e] profile %d six-worker ring trial %d failed."),
Profile, Trial + 1);
return 2;
}
Times.Add(LastTiming.WallSeconds * 1000.0);
}
Times.Sort();
const double MedianMs = Times[Times.Num() / 2];
ProfileMedianMs.Add(MedianMs);
bProfilePasses &= MedianMs <= 600.0;
const FStep2eVarietyMetrics Variety = ComputeStep2eVarietyMetrics(
ProfileDensities, Dimension);
TSharedRef<FJsonObject> Row = MakeShared<FJsonObject>();
Row->SetNumberField(TEXT("profile_index"), Profile);
Row->SetStringField(TEXT("name"), ProfileName);
Row->SetObjectField(TEXT("parameters"), Step2eParametersJson(ProfileParameters));
Row->SetObjectField(TEXT("nominal_chamber_volume_quantiles_m3"),
Step2eChamberVolumeQuantilesJson(ProfileParameters));
Row->SetNumberField(TEXT("six_worker_ring_median_ms"), MedianMs);
TArray<TSharedPtr<FJsonValue>> TrialValues;
TrialValues.Add(MakeShared<FJsonValueNumber>(Times[0]));
TrialValues.Add(MakeShared<FJsonValueNumber>(Times[1]));
TrialValues.Add(MakeShared<FJsonValueNumber>(Times[2]));
Row->SetArrayField(TEXT("six_worker_ring_trials_ms"), TrialValues);
Row->SetNumberField(TEXT("open_air_fraction"), Variety.OpenFraction);
Row->SetNumberField(TEXT("estimated_surface_area_m2"), Variety.SurfaceAreaSquareMeters);
Row->SetNumberField(TEXT("estimated_surface_area_per_volume_m_inv"),
Variety.SurfaceAreaPerVolume);
Row->SetNumberField(TEXT("vertical_air_adjacency_share"), Variety.VerticalAdjacencyShare);
Row->SetNumberField(TEXT("vertical_air_column_share"), Variety.VerticalAirColumnShare);
Row->SetNumberField(TEXT("mean_longest_vertical_air_run_voxels"),
Variety.MeanLongestVerticalAirRunVoxels);
Row->SetNumberField(TEXT("longest_vertical_air_run_voxels"),
Variety.LongestVerticalAirRunVoxels);
Row->SetNumberField(TEXT("one_meter_tunnel_neck_share"), Variety.TunnelNeckShare);
Row->SetNumberField(TEXT("one_meter_connected_components"),
Variety.ConnectedComponentsAtOneMeter);
Row->SetNumberField(TEXT("largest_component_coarse_m3"), Variety.LargestComponentCubicMeters);
Row->SetNumberField(TEXT("components_1_to_8_coarse_m3"), Variety.SmallComponentsUpToEightCubicMeters);
Row->SetNumberField(TEXT("components_9_to_64_coarse_m3"), Variety.MediumComponentsNineToSixtyFourCubicMeters);
Row->SetNumberField(TEXT("components_65_to_512_coarse_m3"), Variety.LargeComponentsSixtyFiveToFiveHundredTwelveCubicMeters);
Row->SetNumberField(TEXT("components_over_512_coarse_m3"), Variety.VeryLargeComponentsOverFiveHundredTwelveCubicMeters);
Row->SetNumberField(TEXT("skipped_bricks"), static_cast<double>(LastTiming.SkippedBricks));
Row->SetNumberField(TEXT("skipped_sample_fraction"), LastTiming.SampleCount > 0
? static_cast<double>(LastTiming.SkippedSamples) / LastTiming.SampleCount : 0.0);
Row->SetNumberField(TEXT("evaluated_samples"),
static_cast<double>(LastTiming.EvaluatedSamples));
Row->SetNumberField(TEXT("worker_cpu_seconds"), LastTiming.WorkerCpuSeconds);
Row->SetNumberField(TEXT("brick_classification_worker_ms"),
LastTiming.BrickClassificationSeconds * 1000.0);
Row->SetNumberField(TEXT("density_evaluation_worker_ms"),
LastTiming.DensityEvaluationSeconds * 1000.0);
Row->SetNumberField(TEXT("uniform_fill_worker_ms"),
LastTiming.UniformFillSeconds * 1000.0);
Row->SetNumberField(TEXT("longest_worker_ms"),
LastTiming.LongestWorkerWallSeconds * 1000.0);
FString SafeName(ProfileName);
SafeName.ToLowerInline();
const FString SetName = FString::Printf(TEXT("set_%02d_%s"), Profile, *SafeName);
const FString SetDirectory = FPaths::Combine(ResearchOut, SetName);
IFileManager::Get().MakeDirectory(*SetDirectory, true);
const FString ImageNames[] = {
TEXT("slice_xy_center.png"), TEXT("slice_xz_y0.png"),
TEXT("slice_yz_x0.png"), TEXT("slice_xz_passage.png")};
FString SliceError;
SaveStep2dSideBySideSlice(FPaths::Combine(SetDirectory, ImageNames[0]),
OldDensities, ProfileDensities, Dimension, TEXT('h'), Dimension / 2, SliceError);
SaveStep2dSideBySideSlice(FPaths::Combine(SetDirectory, ImageNames[1]),
OldDensities, ProfileDensities, Dimension, TEXT('x'), Dimension / 2, SliceError);
SaveStep2dSideBySideSlice(FPaths::Combine(SetDirectory, ImageNames[2]),
OldDensities, ProfileDensities, Dimension, TEXT('y'), Dimension / 2, SliceError);
int32 PassageIndex = Dimension / 2;
if (World.Manager != nullptr && !World.Manager->GetPassages().IsEmpty())
{
PassageIndex = FMath::Clamp(FMath::RoundToInt(
World.Manager->GetPassages()[0].LowerLanding.StandingPoint.Y) - Origin.Y,
0, Dimension - 1);
}
SaveStep2dSideBySideSlice(FPaths::Combine(SetDirectory, ImageNames[3]),
OldDensities, ProfileDensities, Dimension, TEXT('x'), PassageIndex, SliceError);
if (!SliceError.IsEmpty())
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2e] slice write failed for %s: %s"),
ProfileName, *SliceError);
return 2;
}
Row->SetStringField(TEXT("slice_directory"), SetDirectory);
ProfileRows.Add(MakeShared<FJsonValueObject>(Row));
for (const FString& ImageName : ImageNames)
{
TSharedRef<FJsonObject> Slice = MakeShared<FJsonObject>();
Slice->SetStringField(TEXT("profile"), ProfileName);
Slice->SetStringField(TEXT("file"), FPaths::Combine(SetDirectory, ImageName));
Slice->SetStringField(TEXT("layout"),
TEXT("today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"));
SliceRows.Add(MakeShared<FJsonValueObject>(Slice));
}
if (Profile == VoxelForgeRedefinedDensityField::GetParameterSetCount() - 1)
{
HardestParameters = ProfileParameters;
HardestName = ProfileName;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeStep2e] profile=%d/%s ring_ms=%.3f open=%.3f area=%.1f m2 components=%d"),
Profile, ProfileName, MedianMs, Variety.OpenFraction,
Variety.SurfaceAreaSquareMeters, Variety.ConnectedComponentsAtOneMeter);
}
const int32 HardestProfile = VoxelForgeRedefinedDensityField::GetParameterSetCount() - 1;
VoxelForgeRedefinedDensityField::SetParameterSet(HardestProfile);
TArray<float> HardestFullDensity;
TArray<float> RepeatFullDensity;
TArray<float> OneWorkerDensity;
TArray<float> SmoothDensity;
VoxelForgeRegionEval::FTiming HardestFullTiming;
VoxelForgeRegionEval::FTiming RepeatTiming;
VoxelForgeRegionEval::FTiming OneWorkerTiming;
VoxelForgeRegionEval::FTiming SmoothTiming;
const bool bHardestFullOk = VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
HardestFullDensity, HardestFullTiming, false, true);
const bool bRepeatOk = bHardestFullOk && VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
RepeatFullDensity, RepeatTiming, false, true);
const bool bOneWorkerOk = bHardestFullOk && VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, 1, 1.0f,
OneWorkerDensity, OneWorkerTiming, false, true);
const bool bSmoothOk = bHardestFullOk && VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 0.0f,
SmoothDensity, SmoothTiming, false, true);
if (!bHardestFullOk || !bRepeatOk || !bOneWorkerOk || !bSmoothOk)
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2e] hardest-set control pass failed."));
return 2;
}
int64 DetailParitySamples = 0;
double DetailParityMaxError = 0.0;
for (int64 Index = 0; Index < TotalSamples; Index += 2047)
{
const int32 X = static_cast<int32>(Index % Dimension);
const int32 Y = static_cast<int32>((Index / Dimension) % Dimension);
const int32 Z = static_cast<int32>(Index
/ (static_cast<int64>(Dimension) * Dimension));
const float Direct = VoxelForgeRedefinedDensityField::EvaluateParameterized(
*World.Generator, static_cast<float>(Origin.X + X),
static_cast<float>(Origin.Y + Y), static_cast<float>(Origin.Z + Z), nullptr, 1.0f);
DetailParityMaxError = FMath::Max(DetailParityMaxError,
static_cast<double>(FMath::Abs(Direct - HardestFullDensity[static_cast<int32>(Index)])));
++DetailParitySamples;
}
const bool bDetailParity = DetailParityMaxError <= 1.0e-5;
const FStep2dSurfaceSpectrum OldSpectrum = ComputeStep2dSpectrum(OldDensities, Dimension);
const FStep2dSurfaceSpectrum NewSpectrum = ComputeStep2dSpectrum(HardestFullDensity, Dimension);
const FStep2dSurfaceSpectrum SmoothSpectrum = ComputeStep2dSpectrum(SmoothDensity, Dimension);
const FStep2dBoundaryStats BoundaryStats = ComputeStep2dBoundaryStats(
HardestFullDensity, Dimension, Origin,
FMath::RoundToInt(HardestParameters.RoomPitchVoxels));
TArray<float> SeamFailureDensity = HardestFullDensity;
int64 DeliberateSeamSamplesMutated = 0;
for (int32 Z = 1; Z < Dimension - 1; ++Z)
for (int32 Y = 1; Y < Dimension - 1; ++Y)
for (int32 X = 1; X < Dimension - 1; ++X)
{
const int32 Index = (Z * Dimension + Y) * Dimension + X;
if (((Origin.X + X) % 8) == 0 && FMath::Abs(HardestFullDensity[Index]) <= 12.0f)
{
SeamFailureDensity[Index + 1] += 64.0f;
++DeliberateSeamSamplesMutated;
}
}
const FStep2dBoundaryStats SeamFailureStats = ComputeStep2dBoundaryStats(
SeamFailureDensity, Dimension, Origin,
FMath::RoundToInt(HardestParameters.RoomPitchVoxels));
const FStep2dBrickProofAudit ProofAudit = AuditStep2dBrickProofs(
*World.Generator, Origin, Dimension, true);
const uint64 HardestHash = VoxelForgeRegionEval::HashQuantized(HardestFullDensity);
const uint64 RepeatHash = VoxelForgeRegionEval::HashQuantized(RepeatFullDensity);
const uint64 OneWorkerHash = VoxelForgeRegionEval::HashQuantized(OneWorkerDensity);
const bool bDeterminism = HardestHash == RepeatHash && HardestHash == OneWorkerHash
&& Step2eSameWorkerRun(HardestFullTiming, RepeatTiming)
&& Step2eSameWorkerRun(HardestFullTiming, OneWorkerTiming);
VoxelForgeRegionEval::FTiming DeliberateMismatch = HardestFullTiming;
DeliberateMismatch.QuantizedHash ^= 1u;
const bool bDeliberateDeterminismMismatchCaught =
!Step2eSameWorkerRun(HardestFullTiming, DeliberateMismatch);
const bool bDetailAtLeastLive = NewSpectrum.RMSLag1 >= OldSpectrum.RMSLag1
&& NewSpectrum.RMSLag2 >= OldSpectrum.RMSLag2;
const bool bSmoothingControlCaught = SmoothSpectrum.RMSLag1 < NewSpectrum.RMSLag1 * 0.80
|| SmoothSpectrum.RMSLag2 < NewSpectrum.RMSLag2 * 0.80;
const bool bBoundaryPass = Step2eBoundaryStatsPass(BoundaryStats);
const bool bDeliberateSeamFailureCaught = DeliberateSeamSamplesMutated > 0
&& !Step2eBoundaryStatsPass(SeamFailureStats);
const bool bBoundAudit = ProofAudit.CorrectlySkippedBricksAudited > 0
&& ProofAudit.CorrectProofSamplesChecked > 0
&& ProofAudit.CorrectProofViolations == 0;
const bool bUndersizedBoundCaught = ProofAudit.DeliberateBoundDisagreements > 0;
double WorstMedianMs = 0.0;
for (const double MedianMs : ProfileMedianMs)
{
WorstMedianMs = FMath::Max(WorstMedianMs, MedianMs);
}
const bool bSpeedPass = Workers == 6 && bProfilePasses && WorstMedianMs <= 600.0;
const bool bPassed = bSpeedPass && bDeterminism && bDetailAtLeastLive
&& bSmoothingControlCaught && bDetailParity && bBoundaryPass
&& bDeliberateSeamFailureCaught && bDeliberateDeterminismMismatchCaught
&& bBoundAudit && bUndersizedBoundCaught
&& HardestFullTiming.NonFiniteSamples == 0;
TSharedRef<FJsonObject> Report = MakeShared<FJsonObject>();
Report->SetStringField(TEXT("status"), bPassed ? TEXT("pass") : TEXT("fail"));
Report->SetBoolField(TEXT("pass"), bPassed);
Report->SetBoolField(TEXT("worst_profile_meets_600ms"), bSpeedPass);
Report->SetNumberField(TEXT("seed"), Seed);
Report->SetNumberField(TEXT("ring_dimension"), Dimension);
Report->SetNumberField(TEXT("ring_samples"), static_cast<double>(TotalSamples));
Report->SetNumberField(TEXT("workers_requested"), Workers);
Report->SetNumberField(TEXT("world_prepare_ms_excluded"), WorldPrepareSeconds * 1000.0);
Report->SetNumberField(TEXT("old_live_ring_ms"), OldTiming.WallSeconds * 1000.0);
Report->SetStringField(TEXT("old_live_ring_raw_float_hash"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(OldTiming.RawFloatHash)));
Report->SetStringField(TEXT("field_definition"),
TEXT("Seeded jittered XY chamber cells form a deterministic origin-rooted spanning tree; "
"selected loops, horizontal tunnels and full-strate shafts open the network. Each "
"chamber can add pillars, annular ledges, paired stalactites/stalagmites, floating "
"rock masses and a bowl floor with an explicit water level. Passage pads and anchor "
"tunnels join the authored inter-strate route to the generated network."));
Report->SetStringField(TEXT("parameter_space"),
TEXT("Eight named profiles can be assigned per strate from a stable seed hash (-1 mode), "
"or forced for measurement. Each profile supplies nominal room, tunnel, shaft, "
"feature-probability and basin values. Per-cell hashes deterministically roll room "
"jitter and feature presence. Profile minima and maxima are recorded below; room "
"height is clamped to the strate span, room radii to 0.20-0.72/0.80 of pitch, "
"loop probability to 0-0.8, probabilities and water level to 0-1, and basin depth "
"to 0-48 voxels."));
Report->SetObjectField(TEXT("parameter_ranges_across_eight_profiles"),
Step2eParameterRangesJson());
Report->SetStringField(TEXT("resolution_contract"),
TEXT("One exact field sample per 12.5 cm lattice point. The four existing 16/8/4/2-sample "
"detail bands are evaluated for every potentially surface-affecting point; no coarse "
"field is interpolated onto the lattice. 4-cubed step 2e skips require the final macro field "
"Lipschitz interval plus the full 10.5-unit detail supremum and marching-cubes halo."));
Report->SetStringField(TEXT("finer_lattice_scaling"),
TEXT("At 12.5 cm, metre-valued macro and noise dimensions convert to twice as many samples; "
"the four detail bands retain their metre wavelengths. The lattice grows 8x for a fixed "
"world volume; surface bricks grow roughly with area while certified interior bricks "
"remain cheap."));
Report->SetStringField(TEXT("variety_metric_method"),
TEXT("Open fraction uses every ring sample. Surface area estimates sign-changing lattice "
"edges times 0.125 m squared. Nominal chamber-volume p10/p50/p90 values use the "
"uniform per-cell radius roll and room height as circular-cylinder volumes. Tunnel-neck "
"share and connected-component sizes use a 1 m downsampled six-neighbor occupancy grid. "
"Verticality is the mean longest contiguous open run per XY column, with open-column "
"coverage reported separately."));
Report->SetStringField(TEXT("water_geometry_limit"),
TEXT("Basin profiles generate the bowl floor and water-level parameter; this density field "
"does not render or simulate water."));
Report->SetNumberField(TEXT("worst_six_worker_ring_median_ms"), WorstMedianMs);
Report->SetNumberField(TEXT("target_aim_ms"), 350.0);
Report->SetStringField(TEXT("hardest_profile"), HardestName);
Report->SetArrayField(TEXT("profiles"), ProfileRows);
Report->SetArrayField(TEXT("slices"), SliceRows);
Report->SetStringField(TEXT("slice_directory"), ResearchOut);
TSharedRef<FJsonObject> Detail = MakeShared<FJsonObject>();
Detail->SetNumberField(TEXT("live_surface_band_samples"), static_cast<double>(OldSpectrum.SurfaceBandSamples));
Detail->SetNumberField(TEXT("new_surface_band_samples"), static_cast<double>(NewSpectrum.SurfaceBandSamples));
Detail->SetNumberField(TEXT("live_curvature_rms_lag1"), OldSpectrum.RMSLag1);
Detail->SetNumberField(TEXT("new_curvature_rms_lag1"), NewSpectrum.RMSLag1);
Detail->SetNumberField(TEXT("smooth_control_curvature_rms_lag1"), SmoothSpectrum.RMSLag1);
Detail->SetNumberField(TEXT("live_curvature_rms_lag2"), OldSpectrum.RMSLag2);
Detail->SetNumberField(TEXT("new_curvature_rms_lag2"), NewSpectrum.RMSLag2);
Detail->SetNumberField(TEXT("smooth_control_curvature_rms_lag2"), SmoothSpectrum.RMSLag2);
Detail->SetNumberField(TEXT("detail_raw_parity_samples"), DetailParitySamples);
Detail->SetNumberField(TEXT("detail_raw_parity_max_error"), DetailParityMaxError);
Detail->SetBoolField(TEXT("detail_at_least_live_spectrum"), bDetailAtLeastLive);
Detail->SetBoolField(TEXT("deliberate_zero_detail_smoothing_detected"), bSmoothingControlCaught);
Detail->SetBoolField(TEXT("direct_vs_brick_detail_parity"), bDetailParity);
Report->SetObjectField(TEXT("detail_check"), Detail);
TSharedRef<FJsonObject> Boundary = MakeShared<FJsonObject>();
Boundary->SetNumberField(TEXT("brick_boundary_gradient_jump_rms"), BoundaryStats.BrickJumpRms);
Boundary->SetNumberField(TEXT("interior_gradient_jump_rms"), BoundaryStats.InteriorJumpRms);
Boundary->SetNumberField(TEXT("chunk_boundary_gradient_jump_rms"), BoundaryStats.ChunkBoundaryJumpRms);
Boundary->SetNumberField(TEXT("feature_cell_boundary_gradient_jump_rms"), BoundaryStats.FeatureCellJumpRms);
Boundary->SetNumberField(TEXT("feature_cell_interior_gradient_jump_rms"), BoundaryStats.FeatureCellInteriorJumpRms);
Boundary->SetBoolField(TEXT("boundary_artifact_check_pass"), bBoundaryPass);
Boundary->SetNumberField(TEXT("deliberate_seam_samples_mutated"),
static_cast<double>(DeliberateSeamSamplesMutated));
Boundary->SetBoolField(TEXT("deliberate_seam_failure_caught"),
bDeliberateSeamFailureCaught);
Report->SetObjectField(TEXT("boundary_artifact_check"), Boundary);
TSharedRef<FJsonObject> Proofs = MakeShared<FJsonObject>();
Proofs->SetNumberField(TEXT("correctly_skipped_bricks_audited"),
static_cast<double>(ProofAudit.CorrectlySkippedBricksAudited));
Proofs->SetNumberField(TEXT("final_field_samples_checked_including_halo"),
static_cast<double>(ProofAudit.CorrectProofSamplesChecked));
Proofs->SetNumberField(TEXT("correct_proof_sign_violations"),
static_cast<double>(ProofAudit.CorrectProofViolations));
Proofs->SetNumberField(TEXT("first_violation_brick_x"), ProofAudit.FirstViolationBrickOrigin.X);
Proofs->SetNumberField(TEXT("first_violation_brick_y"), ProofAudit.FirstViolationBrickOrigin.Y);
Proofs->SetNumberField(TEXT("first_violation_brick_z"), ProofAudit.FirstViolationBrickOrigin.Z);
Proofs->SetNumberField(TEXT("first_violation_brick_size_x"), ProofAudit.FirstViolationBrickSize.X);
Proofs->SetNumberField(TEXT("first_violation_brick_size_y"), ProofAudit.FirstViolationBrickSize.Y);
Proofs->SetNumberField(TEXT("first_violation_brick_size_z"), ProofAudit.FirstViolationBrickSize.Z);
Proofs->SetNumberField(TEXT("first_violation_sample_x"), ProofAudit.FirstViolationSample.X);
Proofs->SetNumberField(TEXT("first_violation_sample_y"), ProofAudit.FirstViolationSample.Y);
Proofs->SetNumberField(TEXT("first_violation_sample_z"), ProofAudit.FirstViolationSample.Z);
Proofs->SetNumberField(TEXT("first_violation_density"), ProofAudit.FirstViolationDensity);
Proofs->SetNumberField(TEXT("first_violation_center_macro"),
ProofAudit.FirstViolationCenterMacro);
Proofs->SetNumberField(TEXT("first_violation_sample_macro"),
ProofAudit.FirstViolationSampleMacro);
Proofs->SetNumberField(TEXT("first_violation_sample_detail"),
ProofAudit.FirstViolationSampleDetail);
Proofs->SetNumberField(TEXT("first_violation_macro_delta_per_voxel"),
ProofAudit.FirstViolationMacroDeltaPerVoxel);
Proofs->SetNumberField(TEXT("first_violation_path_max_gradient"),
ProofAudit.FirstViolationPathMaxGradient);
Proofs->SetNumberField(TEXT("first_violation_tiny_step_max_gradient"),
ProofAudit.FirstViolationTinyStepMaxGradient);
Proofs->SetBoolField(TEXT("first_violation_inside_proven_brick"),
ProofAudit.bFirstViolationWasInsideBrick);
Proofs->SetBoolField(TEXT("first_violation_claimed_air"),
ProofAudit.bFirstViolationClaimedAir);
Proofs->SetNumberField(TEXT("undersized_bound_claims_checked"),
static_cast<double>(ProofAudit.DeliberatelyUndersizedBoundClaimsChecked));
Proofs->SetNumberField(TEXT("wrong_sign_samples_caught_by_undersized_bound"),
static_cast<double>(ProofAudit.DeliberateBoundDisagreements));
Proofs->SetBoolField(TEXT("sound_supremum_audit_pass"), bBoundAudit);
Proofs->SetBoolField(TEXT("deliberate_undersized_bound_failure_caught"), bUndersizedBoundCaught);
Report->SetObjectField(TEXT("brick_bound_audit"), Proofs);
TSharedRef<FJsonObject> Determinism = MakeShared<FJsonObject>();
Determinism->SetStringField(TEXT("six_worker_run_1_quantized"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(HardestHash)));
Determinism->SetStringField(TEXT("six_worker_run_2_quantized"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(RepeatHash)));
Determinism->SetStringField(TEXT("one_worker_quantized"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(OneWorkerHash)));
Determinism->SetStringField(TEXT("six_worker_run_1_raw_float"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(HardestFullTiming.RawFloatHash)));
Determinism->SetStringField(TEXT("six_worker_run_2_raw_float"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(RepeatTiming.RawFloatHash)));
Determinism->SetStringField(TEXT("one_worker_raw_float"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(OneWorkerTiming.RawFloatHash)));
Determinism->SetBoolField(TEXT("same_across_two_runs_and_1_vs_6_workers"), bDeterminism);
Determinism->SetBoolField(TEXT("deliberate_quantized_hash_mismatch_rejected"),
bDeliberateDeterminismMismatchCaught);
Report->SetObjectField(TEXT("determinism"), Determinism);
Report->SetBoolField(TEXT("entrance_exit_walkability_test_added"), true);
Report->SetBoolField(TEXT("inter_strate_passage_open_space_test_added"), true);
Report->SetStringField(TEXT("functional_test_filter"),
TEXT("VoxelForge.ParameterizedGeneration.TraversalSeeds"));
FString JsonText;
const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&JsonText);
const FString ReportPath = FPaths::Combine(ResearchOut, TEXT("step2e_report.json"));
const FString LaunchReportPath = FPaths::Combine(Arguments.OutDirectory, TEXT("step2e_report.json"));
if (!FJsonSerializer::Serialize(Report, Writer)
|| !FFileHelper::SaveStringToFile(JsonText, *ReportPath,
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM)
|| !FFileHelper::SaveStringToFile(JsonText, *LaunchReportPath,
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2e] report write failed: %s"), *ReportPath);
return 2;
}
VoxelForgeRedefinedDensityField::SetParameterizedEnabled(false);
VoxelForgeRedefinedDensityField::SetLegacyStep2eAuditMode(false);
VoxelForgeRedefinedDensityField::SetDetailScale(1.0f);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeStep2e] pass=%s speed600=%s worst_ms=%.3f detail=%s boundary=%s "
"bound=%s deterministic=%s report=%s"),
bPassed ? TEXT("yes") : TEXT("no"), bSpeedPass ? TEXT("yes") : TEXT("no"),
WorstMedianMs, bDetailAtLeastLive ? TEXT("pass") : TEXT("fail"),
bBoundaryPass ? TEXT("pass") : TEXT("fail"), bBoundAudit ? TEXT("pass") : TEXT("fail"),
bDeterminism ? TEXT("pass") : TEXT("fail"), *ReportPath);
return bPassed ? 0 : 2;
}
int32 RunStep3ScaleBenchmark(const FString& Params)
{
FExploreArguments Arguments;
FString Error;
if (!ParseArguments(Params, Arguments, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep3Scale] argument error: %s"), *Error);
return 2;
}
int32 Seed = 1337;
int32 Workers = VoxelForgeRegionEval::WorkerCount;
int32 Profile = 0;
FParse::Value(*Params, TEXT("seed="), Seed);
FParse::Value(*Params, TEXT("regionworkers="), Workers);
FParse::Value(*Params, TEXT("profile="), Profile);
Workers = FMath::Clamp(Workers, 1, 32);
Profile = FMath::Clamp(Profile, 0,
VoxelForgeRedefinedDensityField::GetParameterSetCount() - 1);
FString ResearchOut;
FParse::Value(*Params, TEXT("researchout="), ResearchOut);
if (ResearchOut.IsEmpty())
{
ResearchOut = FPaths::Combine(Arguments.OutDirectory, TEXT("Research/step3-scale"));
}
ResearchOut = FPaths::ConvertRelativePathToFull(ResearchOut);
IFileManager::Get().MakeDirectory(*ResearchOut, true);
IFileManager::Get().MakeDirectory(*Arguments.OutDirectory, true);
FExploreWorld World;
int32 RecipeIndex = INDEX_NONE;
uint32 RecipeHash = 0;
int32 EditChunkCount = 0;
FString WorldError;
const double PrepareStart = FPlatformTime::Seconds();
if (!PrepareRegionEvalWorld(World, Arguments, Seed,
RecipeIndex, RecipeHash, EditChunkCount, WorldError))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep3Scale] world preparation failed: %s"),
*WorldError);
return 2;
}
const double WorldPrepareSeconds = FPlatformTime::Seconds() - PrepareStart;
if (World.DiffLayer != nullptr)
{
World.DiffLayer->Clear();
}
const int32 CenterZ = (World.TargetTopWorldZ + World.TargetBottomWorldZ) / 2;
const TCHAR* ProfileName = TEXT("unknown");
VoxelForgeRedefinedDensityField::FGenerationParameters ProfileParameters;
if (!VoxelForgeRedefinedDensityField::GetParameterSetInfo(
Profile, ProfileName, ProfileParameters))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep3Scale] profile %d is unavailable."), Profile);
return 2;
}
VoxelForgeRedefinedDensityField::SetEnabled(false);
VoxelForgeRedefinedDensityField::SetParameterizedEnabled(true);
VoxelForgeRedefinedDensityField::SetLegacyStep2eAuditMode(false);
VoxelForgeRedefinedDensityField::SetDetailScale(1.0f);
VoxelForgeRedefinedDensityField::SetParameterSet(Profile);
const int32 Dimensions[] = { 519, 259 };
TArray<TSharedPtr<FJsonValue>> CubeRows;
bool bPass = Workers == 6;
for (const int32 Dimension : Dimensions)
{
const int32 HalfSpan = (Dimension - 1) / 2;
const FIntVector Origin(-HalfSpan, -HalfSpan, CenterZ - HalfSpan);
const int64 ExpectedSamples = static_cast<int64>(Dimension)
* Dimension * Dimension;
TArray<TSharedPtr<FJsonValue>> TrialRows;
TArray<double> WallTimes;
TArray<double> CpuTimes;
uint64 FirstQuantizedHash = 0;
uint64 FirstRawHash = 0;
bool bDeterministic = true;
int32 ObservedWorkersMin = Workers;
int64 EvaluatedSamples = -1;
int64 SkippedSamples = -1;
uint64 BrickCount = 0;
uint64 SkippedBricks = 0;
for (int32 Trial = 0; Trial < 3; ++Trial)
{
VoxelForgeRegionEval::FTiming Timing;
TArray<float> Densities;
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
Densities, Timing, /*bEnableBrickSkips=*/true,
/*bParameterized=*/true))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeStep3Scale] dimension=%d trial=%d evaluation failed."),
Dimension, Trial + 1);
VoxelForgeRedefinedDensityField::SetParameterizedEnabled(false);
return 2;
}
if (Densities.Num() != ExpectedSamples || Timing.SampleCount != ExpectedSamples
|| Timing.NonFiniteSamples != 0)
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeStep3Scale] dimension=%d trial=%d sample/finite invariant failed."),
Dimension, Trial + 1);
VoxelForgeRedefinedDensityField::SetParameterizedEnabled(false);
return 2;
}
if (Trial == 0)
{
FirstQuantizedHash = Timing.QuantizedHash;
FirstRawHash = Timing.RawFloatHash;
EvaluatedSamples = Timing.EvaluatedSamples;
SkippedSamples = static_cast<int64>(Timing.SkippedSamples);
BrickCount = Timing.BrickCount;
SkippedBricks = Timing.SkippedBricks;
}
else
{
bDeterministic &= Timing.QuantizedHash == FirstQuantizedHash
&& Timing.RawFloatHash == FirstRawHash;
}
ObservedWorkersMin = FMath::Min(ObservedWorkersMin,
Timing.ObservedWorkerThreads);
WallTimes.Add(Timing.WallSeconds);
CpuTimes.Add(Timing.WorkerCpuSeconds);
TSharedRef<FJsonObject> TrialJson = MakeShared<FJsonObject>();
TrialJson->SetNumberField(TEXT("trial"), Trial + 1);
TrialJson->SetNumberField(TEXT("wall_seconds"), Timing.WallSeconds);
TrialJson->SetNumberField(TEXT("worker_cpu_seconds"), Timing.WorkerCpuSeconds);
TrialJson->SetNumberField(TEXT("workers_requested"), Timing.Workers);
TrialJson->SetNumberField(TEXT("workers_observed"), Timing.ObservedWorkerThreads);
TrialJson->SetNumberField(TEXT("evaluated_samples"),
static_cast<double>(Timing.EvaluatedSamples));
TrialJson->SetNumberField(TEXT("skipped_samples"),
static_cast<double>(Timing.SkippedSamples));
TrialJson->SetNumberField(TEXT("bricks"), static_cast<double>(Timing.BrickCount));
TrialJson->SetNumberField(TEXT("skipped_bricks"),
static_cast<double>(Timing.SkippedBricks));
TrialJson->SetStringField(TEXT("quantized_hash"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(Timing.QuantizedHash)));
TrialJson->SetStringField(TEXT("raw_float_hash"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(Timing.RawFloatHash)));
TrialRows.Add(MakeShared<FJsonValueObject>(TrialJson));
}
WallTimes.Sort();
CpuTimes.Sort();
TSharedRef<FJsonObject> Cube = MakeShared<FJsonObject>();
Cube->SetNumberField(TEXT("dimension"), Dimension);
Cube->SetNumberField(TEXT("sample_count"), static_cast<double>(ExpectedSamples));
Cube->SetNumberField(TEXT("intervals_per_axis"), Dimension - 1);
Cube->SetNumberField(TEXT("sample_spacing_cm"), VOXEL_SIZE);
Cube->SetNumberField(TEXT("extent_m_per_axis"),
static_cast<double>(Dimension - 1) * VOXEL_SIZE / 100.0);
Cube->SetNumberField(TEXT("density_output_mib"),
static_cast<double>(ExpectedSamples) * sizeof(float) / (1024.0 * 1024.0));
Cube->SetNumberField(TEXT("workers_requested"), Workers);
Cube->SetNumberField(TEXT("minimum_workers_observed"), ObservedWorkersMin);
Cube->SetNumberField(TEXT("evaluated_samples"), static_cast<double>(EvaluatedSamples));
Cube->SetNumberField(TEXT("skipped_samples"), static_cast<double>(SkippedSamples));
Cube->SetNumberField(TEXT("bricks"), static_cast<double>(BrickCount));
Cube->SetNumberField(TEXT("skipped_bricks"), static_cast<double>(SkippedBricks));
Cube->SetNumberField(TEXT("median_wall_seconds"), WallTimes[WallTimes.Num() / 2]);
Cube->SetNumberField(TEXT("min_wall_seconds"), WallTimes[0]);
Cube->SetNumberField(TEXT("max_wall_seconds"), WallTimes.Last());
Cube->SetNumberField(TEXT("median_worker_cpu_seconds"), CpuTimes[CpuTimes.Num() / 2]);
Cube->SetBoolField(TEXT("deterministic_three_trials"), bDeterministic);
Cube->SetStringField(TEXT("quantized_hash"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(FirstQuantizedHash)));
Cube->SetStringField(TEXT("raw_float_hash"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(FirstRawHash)));
Cube->SetArrayField(TEXT("trials"), TrialRows);
CubeRows.Add(MakeShared<FJsonValueObject>(Cube));
bPass &= bDeterministic && ObservedWorkersMin == 6;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeStep3Scale] dimension=%d samples=%lld extent_m=%.3f workers=%d "
"median_wall_s=%.6f min=%.6f max=%.6f evaluated=%lld skipped=%lld "
"deterministic=%d hash=%016llx"),
Dimension, static_cast<long long>(ExpectedSamples),
static_cast<double>(Dimension - 1) * VOXEL_SIZE / 100.0,
Workers, WallTimes[WallTimes.Num() / 2], WallTimes[0], WallTimes.Last(),
static_cast<long long>(EvaluatedSamples), static_cast<long long>(SkippedSamples),
bDeterministic ? 1 : 0,
static_cast<unsigned long long>(FirstQuantizedHash));
}
TSharedRef<FJsonObject> Report = MakeShared<FJsonObject>();
Report->SetNumberField(TEXT("schema_version"), 1);
Report->SetStringField(TEXT("tool"), TEXT("VoxelForgeStep3Scale"));
Report->SetBoolField(TEXT("pass"), bPass);
Report->SetNumberField(TEXT("seed"), Seed);
Report->SetNumberField(TEXT("profile"), Profile);
Report->SetStringField(TEXT("profile_name"), ProfileName);
Report->SetNumberField(TEXT("worker_count"), Workers);
Report->SetNumberField(TEXT("voxel_size_cm"), VOXEL_SIZE);
Report->SetNumberField(TEXT("chunk_size_voxels"), CHUNK_SIZE);
Report->SetNumberField(TEXT("chunk_size_m"),
static_cast<double>(CHUNK_SIZE) * VOXEL_SIZE / 100.0);
Report->SetNumberField(TEXT("detail_scale"), 1.0);
Report->SetBoolField(TEXT("brick_skips_enabled"), true);
Report->SetNumberField(TEXT("world_prepare_seconds"), WorldPrepareSeconds);
Report->SetNumberField(TEXT("recipe_index"), RecipeIndex);
Report->SetNumberField(TEXT("recipe_hash"), RecipeHash);
Report->SetNumberField(TEXT("edit_chunk_count"), EditChunkCount);
Report->SetArrayField(TEXT("cubes"), CubeRows);
FString JsonText;
const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&JsonText);
const FString ReportPath = FPaths::Combine(ResearchOut, TEXT("step3_scale_benchmark.json"));
const FString LaunchReportPath = FPaths::Combine(
Arguments.OutDirectory, TEXT("step3_scale_benchmark.json"));
if (!FJsonSerializer::Serialize(Report, Writer)
|| !FFileHelper::SaveStringToFile(JsonText, *ReportPath,
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM)
|| !FFileHelper::SaveStringToFile(JsonText, *LaunchReportPath,
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep3Scale] report write failed: %s"), *ReportPath);
VoxelForgeRedefinedDensityField::SetParameterizedEnabled(false);
return 2;
}
VoxelForgeRedefinedDensityField::SetParameterizedEnabled(false);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeStep3Scale] pass=%d report=%s"), bPass ? 1 : 0, *ReportPath);
return bPass ? 0 : 2;
}
int32 RunStep2f(const FString& Params)
{
FExploreArguments Arguments;
FString Error;
if (!ParseArguments(Params, Arguments, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2f] argument error: %s"), *Error);
return 2;
}
int32 Seed = 1337;
int32 Workers = VoxelForgeRegionEval::WorkerCount;
FParse::Value(*Params, TEXT("seed="), Seed);
FParse::Value(*Params, TEXT("regionworkers="), Workers);
Workers = FMath::Clamp(Workers, 1, 32);
FString ResearchOut;
FParse::Value(*Params, TEXT("researchout="), ResearchOut);
if (ResearchOut.IsEmpty())
{
ResearchOut = FPaths::Combine(Arguments.OutDirectory, TEXT("Research/step2f"));
}
ResearchOut = FPaths::ConvertRelativePathToFull(ResearchOut);
IFileManager::Get().MakeDirectory(*ResearchOut, true);
IFileManager::Get().MakeDirectory(*Arguments.OutDirectory, true);
FExploreWorld World;
int32 RecipeIndex = INDEX_NONE;
uint32 RecipeHash = 0;
int32 EditChunkCount = 0;
const double PrepareStart = FPlatformTime::Seconds();
if (!PrepareRegionEvalWorld(World, Arguments, Seed,
RecipeIndex, RecipeHash, EditChunkCount, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2f] world preparation failed: %s"), *Error);
return 2;
}
const double WorldPrepareSeconds = FPlatformTime::Seconds() - PrepareStart;
if (World.DiffLayer != nullptr)
{
World.DiffLayer->Clear();
}
const int32 Dimension = VoxelForgeRegionEval::RingDimension;
const int64 TotalSamples = static_cast<int64>(Dimension) * Dimension * Dimension;
const int32 CenterZ = (World.TargetTopWorldZ + World.TargetBottomWorldZ) / 2;
const FIntVector Origin(-129, -129, CenterZ - 129);
VoxelForgeRedefinedDensityField::SetEnabled(false);
VoxelForgeRedefinedDensityField::SetParameterizedEnabled(true);
VoxelForgeRedefinedDensityField::SetDetailScale(1.0f);
VoxelForgeRedefinedDensityField::SetParameterSet(-1);
TArray<TSharedPtr<FJsonValue>> ProfileRows;
TArray<TSharedPtr<FJsonValue>> SliceRows;
double WorstStep2eMs = 0.0;
double WorstStep2fMs = 0.0;
bool bStep2eCrumbsRejected = true;
bool bStep2fCrumbGatePassed = true;
bool bStep2eFlakesRejected = true;
bool bStep2fFlakeGatePassed = true;
bool bStep2eBandLimitRejected = true;
bool bStep2fBandLimitPassed = true;
bool bStep2eLodRejected = true;
bool bStep2fLodPassed = true;
bool bStep2eGridRejected = true;
bool bStep2fGridPassed = true;
bool bStep2eStructureRejected = true;
bool bStep2fStructurePassed = true;
double WorstStep2fSolidFraction = 0.0;
double BestStep2fSolidFraction = 1.0;
int64 WorstStep2eCrumbs = 0;
int64 WorstStep2fCrumbs = 0;
double WorstStep2eDihedralShare = 0.0;
double WorstStep2fDihedralShare = 0.0;
double WorstStep2eDihedralP95 = 0.0;
double WorstStep2fDihedralP95 = 0.0;
double WorstStep2eDetailEnergy = 0.0;
double WorstStep2fDetailEnergy = 0.0;
double BestStep2eDetailRms = MAX_dbl;
double BestStep2fDetailRms = MAX_dbl;
double WorstStep2eLodDistance = 0.0;
double WorstStep2fLodDistance = 0.0;
double WorstStep2ePitchCorrelation = 0.0;
double WorstStep2fPitchCorrelation = 0.0;
double WorstStep2ePitchExcess = -1.0;
double WorstStep2fPitchExcess = -1.0;
double LowestStep2eSolidFraction = 1.0;
double LowestStep2fSolidFraction = 1.0;
int64 WorstStep2eRoofOpenings = 0;
int64 WorstStep2eFloorOpenings = 0;
int64 WorstStep2fRoofOpenings = 0;
int64 WorstStep2fFloorOpenings = 0;
const auto MeasureShape = [&](const TArray<float>& Density,
const VoxelForgeRedefinedDensityField::FGenerationParameters& P)
{
FStep2fShapeSnapshot Result;
Result.Variety = ComputeStep2eVarietyMetrics(Density, Dimension);
Result.Fragments = ComputeStep2fFragmentMetrics(Density, Dimension);
Result.Grid = ComputeStep2fGridSignature(
*World.Generator, Origin, Dimension, P.RoomPitchVoxels);
constexpr int32 DetailDimension = 8 * static_cast<int32>(100.0f / VOXEL_SIZE + 0.5f);
const FIntVector DetailOrigin = Origin + FIntVector(
(Dimension - DetailDimension) / 2,
(Dimension - DetailDimension) / 2,
(Dimension - DetailDimension) / 2);
TArray<float> DetailSamples;
DetailSamples.SetNumUninitialized(DetailDimension * DetailDimension * DetailDimension);
for (int32 Z = 0; Z < DetailDimension; ++Z)
for (int32 Y = 0; Y < DetailDimension; ++Y)
for (int32 X = 0; X < DetailDimension; ++X)
{
const int32 Index = (Z * DetailDimension + Y) * DetailDimension + X;
DetailSamples[Index] = VoxelForgeRedefinedDensityField::EvaluateParameterizedDetail(
*World.Generator,
static_cast<float>(DetailOrigin.X + X),
static_cast<float>(DetailOrigin.Y + Y),
static_cast<float>(DetailOrigin.Z + Z));
}
Result.Detail = ComputeStep2fDetailSpectrum(DetailSamples, DetailDimension);
const FIntVector MeshOrigin = SelectStep2fMeshOrigin(
Density, Origin, Dimension);
const bool bPreviousSkirts = World.Mesher->bGenerateSkirts;
World.Mesher->bGenerateSkirts = false;
FVoxelMeshData FineMesh;
// GenerateMesh clamps cells per tile to CHUNK_SIZE. At 12.5 cm, a 16 m
// full-detail cube is four level-0 chunks across and two level-1 chunks across.
constexpr int32 Level0TilesPerAxis = static_cast<int32>(
16.0f / (CHUNK_SIZE * VOXEL_SIZE / 100.0f));
for (int32 TileZ = 0; TileZ < Level0TilesPerAxis; ++TileZ)
for (int32 TileY = 0; TileY < Level0TilesPerAxis; ++TileY)
for (int32 TileX = 0; TileX < Level0TilesPerAxis; ++TileX)
{
const FIntVector TileOrigin = MeshOrigin + FIntVector(
TileX * CHUNK_SIZE, TileY * CHUNK_SIZE, TileZ * CHUNK_SIZE);
const FVoxelMeshData TileMesh = World.Mesher->GenerateMesh(
TileOrigin, 1, CHUNK_SIZE);
const int32 VertexOffset = FineMesh.Vertices.Num();
FineMesh.Vertices.Append(TileMesh.Vertices);
for (const int32 Index : TileMesh.Triangles)
{
FineMesh.Triangles.Add(VertexOffset + Index);
}
}
FVoxelMeshData CoarseMesh;
constexpr int32 Level1TilesPerAxis = static_cast<int32>(
16.0f / (CHUNK_SIZE * 2 * VOXEL_SIZE / 100.0f));
for (int32 TileZ = 0; TileZ < Level1TilesPerAxis; ++TileZ)
for (int32 TileY = 0; TileY < Level1TilesPerAxis; ++TileY)
for (int32 TileX = 0; TileX < Level1TilesPerAxis; ++TileX)
{
const FIntVector TileOrigin = MeshOrigin + FIntVector(
TileX * CHUNK_SIZE * 2, TileY * CHUNK_SIZE * 2, TileZ * CHUNK_SIZE * 2);
const FVoxelMeshData TileMesh = World.Mesher->GenerateMesh(
TileOrigin, 2, CHUNK_SIZE);
const int32 VertexOffset = CoarseMesh.Vertices.Num();
CoarseMesh.Vertices.Append(TileMesh.Vertices);
for (const int32 Index : TileMesh.Triangles)
{
CoarseMesh.Triangles.Add(VertexOffset + Index);
}
}
World.Mesher->bGenerateSkirts = bPreviousSkirts;
Result.Mesh = ComputeStep2fMeshMetrics(FineMesh);
Result.Lod1VertexCount = CoarseMesh.Vertices.Num();
TArray<float> LodErrors;
LodErrors.Reserve(CoarseMesh.Vertices.Num());
// L0 triangle bounds are inserted into a one-meter spatial grid. A
// coarse vertex only needs nearby cells unless its distance is already
// large enough to fail the LOD gate; expanding the search makes the
// reported value exact even for badly diverged surfaces.
constexpr float LodBucketSizeCm = 100.0f;
TMap<FIntVector, TArray<int32>> FineTriangleBuckets;
FineTriangleBuckets.Reserve(FineMesh.Triangles.Num());
for (int32 Triangle = 0; Triangle + 2 < FineMesh.Triangles.Num(); Triangle += 3)
{
const int32 IA = FineMesh.Triangles[Triangle];
const int32 IB = FineMesh.Triangles[Triangle + 1];
const int32 IC = FineMesh.Triangles[Triangle + 2];
if (!FineMesh.Vertices.IsValidIndex(IA)
|| !FineMesh.Vertices.IsValidIndex(IB)
|| !FineMesh.Vertices.IsValidIndex(IC))
{
continue;
}
const FVector& A = FineMesh.Vertices[IA];
const FVector& B = FineMesh.Vertices[IB];
const FVector& C = FineMesh.Vertices[IC];
const FVector Min = A.ComponentMin(B).ComponentMin(C);
const FVector Max = A.ComponentMax(B).ComponentMax(C);
const FIntVector MinBucket(
FMath::FloorToInt(Min.X / LodBucketSizeCm),
FMath::FloorToInt(Min.Y / LodBucketSizeCm),
FMath::FloorToInt(Min.Z / LodBucketSizeCm));
const FIntVector MaxBucket(
FMath::FloorToInt(Max.X / LodBucketSizeCm),
FMath::FloorToInt(Max.Y / LodBucketSizeCm),
FMath::FloorToInt(Max.Z / LodBucketSizeCm));
const int32 TriangleIndex = Triangle / 3;
for (int32 Z = MinBucket.Z; Z <= MaxBucket.Z; ++Z)
for (int32 Y = MinBucket.Y; Y <= MaxBucket.Y; ++Y)
for (int32 X = MinBucket.X; X <= MaxBucket.X; ++X)
{
FineTriangleBuckets.FindOrAdd(FIntVector(X, Y, Z)).Add(TriangleIndex);
}
}
for (const FVector& VertexWorldCm : CoarseMesh.Vertices)
{
const FIntVector CentreBucket(
FMath::FloorToInt(VertexWorldCm.X / LodBucketSizeCm),
FMath::FloorToInt(VertexWorldCm.Y / LodBucketSizeCm),
FMath::FloorToInt(VertexWorldCm.Z / LodBucketSizeCm));
float NearestDistanceSquared = MAX_flt;
TSet<int32> TestedTriangles;
for (int32 Radius = 1; Radius <= 64; ++Radius)
{
for (int32 Z = CentreBucket.Z - Radius; Z <= CentreBucket.Z + Radius; ++Z)
for (int32 Y = CentreBucket.Y - Radius; Y <= CentreBucket.Y + Radius; ++Y)
for (int32 X = CentreBucket.X - Radius; X <= CentreBucket.X + Radius; ++X)
{
const TArray<int32>* Candidates = FineTriangleBuckets.Find(FIntVector(X, Y, Z));
if (Candidates == nullptr)
{
continue;
}
for (const int32 TriangleIndex : *Candidates)
{
if (TestedTriangles.Contains(TriangleIndex))
{
continue;
}
TestedTriangles.Add(TriangleIndex);
const int32 Triangle = TriangleIndex * 3;
const FVector& A = FineMesh.Vertices[FineMesh.Triangles[Triangle]];
const FVector& B = FineMesh.Vertices[FineMesh.Triangles[Triangle + 1]];
const FVector& C = FineMesh.Vertices[FineMesh.Triangles[Triangle + 2]];
NearestDistanceSquared = FMath::Min(
NearestDistanceSquared,
Step2fPointTriangleDistanceSquared(VertexWorldCm, A, B, C));
}
}
const float SearchBoundCm = Radius * LodBucketSizeCm;
if (NearestDistanceSquared <= FMath::Square(SearchBoundCm))
{
break;
}
}
const float SurfaceDistanceVoxels = FMath::IsFinite(NearestDistanceSquared)
? FMath::Sqrt(NearestDistanceSquared) / VOXEL_SIZE
: MAX_flt;
LodErrors.Add(SurfaceDistanceVoxels);
}
if (!LodErrors.IsEmpty())
{
LodErrors.Sort();
const int32 P95Index = FMath::Clamp(
FMath::CeilToInt(0.95f * LodErrors.Num()) - 1,
0, LodErrors.Num() - 1);
Result.Lod1P95SurfaceErrorVoxels = LodErrors[P95Index];
}
if (World.Manager != nullptr)
{
const TArray<FStrateSlot>& Layout = World.Manager->GetLayout();
constexpr int32 XYStep = static_cast<int32>(100.0f / VOXEL_SIZE + 0.5f);
constexpr int32 ShellOffsets[] = {
static_cast<int32>(25.0f / VOXEL_SIZE + 0.5f),
static_cast<int32>(125.0f / VOXEL_SIZE + 0.5f),
static_cast<int32>(225.0f / VOXEL_SIZE + 0.5f)};
for (const FStrateSlot& Slot : Layout)
{
if (Slot.Definition == nullptr)
{
continue;
}
++Result.Structure.StratesChecked;
const int32 Bottom = FMath::Min(Slot.BottomChunkZ, Slot.TopChunkZ) * CHUNK_SIZE;
const int32 Top = (FMath::Max(Slot.BottomChunkZ, Slot.TopChunkZ) + 1) * CHUNK_SIZE;
for (int32 Y = Origin.Y; Y < Origin.Y + Dimension; Y += XYStep)
for (int32 X = Origin.X; X < Origin.X + Dimension; X += XYStep)
for (const int32 Offset : ShellOffsets)
{
const float FloorDensity = VoxelForgeRedefinedDensityField::EvaluateParameterized(
*World.Generator, static_cast<float>(X), static_cast<float>(Y),
static_cast<float>(Bottom + Offset), nullptr, 1.0f);
const float RoofDensity = VoxelForgeRedefinedDensityField::EvaluateParameterized(
*World.Generator, static_cast<float>(X), static_cast<float>(Y),
static_cast<float>(Top - 1 - Offset), nullptr, 1.0f);
++Result.Structure.FloorSamples;
++Result.Structure.RoofSamples;
Result.Structure.FloorOpenings += FloorDensity > 0.0f ? 1 : 0;
Result.Structure.RoofOpenings += RoofDensity > 0.0f ? 1 : 0;
}
}
}
return Result;
};
const auto ShapeJson = [](const FStep2fShapeSnapshot& Shape, double MedianMs,
const FStep2eVarietyMetrics& Variety)
{
TSharedRef<FJsonObject> Json = MakeShared<FJsonObject>();
Json->SetNumberField(TEXT("six_worker_ring_median_ms"), MedianMs);
Json->SetNumberField(TEXT("solid_fraction"), 1.0 - Variety.OpenFraction);
Json->SetNumberField(TEXT("air_fraction"), Variety.OpenFraction);
Json->SetNumberField(TEXT("air_components_12_5cm_grid"),
Shape.Fragments.AirComponents);
Json->SetNumberField(TEXT("solid_components_12_5cm_grid"),
Shape.Fragments.SolidComponents);
Json->SetNumberField(TEXT("air_crumbs_below_1m3"),
static_cast<double>(Shape.Fragments.AirCrumbsBelowOneCubicMeter));
Json->SetNumberField(TEXT("solid_crumbs_below_1m3"),
static_cast<double>(Shape.Fragments.SolidCrumbsBelowOneCubicMeter));
Json->SetNumberField(TEXT("air_crumbs_below_1m3_after_boundary_padding"),
static_cast<double>(Shape.Fragments.AirCrumbsAfterBoundaryPadding >= 0
? Shape.Fragments.AirCrumbsAfterBoundaryPadding
: Shape.Fragments.AirCrumbsBelowOneCubicMeter));
Json->SetNumberField(TEXT("solid_crumbs_below_1m3_after_boundary_padding"),
static_cast<double>(Shape.Fragments.SolidCrumbsAfterBoundaryPadding >= 0
? Shape.Fragments.SolidCrumbsAfterBoundaryPadding
: Shape.Fragments.SolidCrumbsBelowOneCubicMeter));
Json->SetNumberField(TEXT("air_crumb_voxel_samples"),
static_cast<double>(Shape.Fragments.AirCrumbVoxelSamples));
Json->SetNumberField(TEXT("solid_crumb_voxel_samples"),
static_cast<double>(Shape.Fragments.SolidCrumbVoxelSamples));
TArray<TSharedPtr<FJsonValue>> CrumbComponents;
for (const FStep2fFragmentMetrics::FCrumbComponent& Crumb
: Shape.Fragments.CrumbComponents)
{
TSharedRef<FJsonObject> CrumbJson = MakeShared<FJsonObject>();
CrumbJson->SetStringField(TEXT("sign"), Crumb.bAir ? TEXT("air") : TEXT("solid"));
CrumbJson->SetNumberField(TEXT("samples"), Crumb.Samples);
CrumbJson->SetNumberField(TEXT("density_at_min"), Crumb.DensityAtMin);
CrumbJson->SetArrayField(TEXT("root_ring_voxel"), {
MakeShared<FJsonValueNumber>(Crumb.Root.X),
MakeShared<FJsonValueNumber>(Crumb.Root.Y),
MakeShared<FJsonValueNumber>(Crumb.Root.Z)});
CrumbJson->SetNumberField(TEXT("density_at_root"), Crumb.DensityAtRoot);
CrumbJson->SetNumberField(TEXT("sign_mismatch_samples"),
Crumb.SignMismatchSamples);
CrumbJson->SetArrayField(TEXT("min_ring_voxel"), {
MakeShared<FJsonValueNumber>(Crumb.Min.X),
MakeShared<FJsonValueNumber>(Crumb.Min.Y),
MakeShared<FJsonValueNumber>(Crumb.Min.Z)});
CrumbJson->SetArrayField(TEXT("max_ring_voxel"), {
MakeShared<FJsonValueNumber>(Crumb.Max.X),
MakeShared<FJsonValueNumber>(Crumb.Max.Y),
MakeShared<FJsonValueNumber>(Crumb.Max.Z)});
CrumbComponents.Add(MakeShared<FJsonValueObject>(CrumbJson));
}
Json->SetArrayField(TEXT("crumb_components_first_32"), CrumbComponents);
Json->SetNumberField(TEXT("largest_air_component_voxels"),
Shape.Fragments.LargestAirComponentVoxels);
Json->SetNumberField(TEXT("largest_solid_component_voxels"),
Shape.Fragments.LargestSolidComponentVoxels);
Json->SetNumberField(TEXT("one_meter_downsampled_air_components"),
Variety.ConnectedComponentsAtOneMeter);
Json->SetNumberField(TEXT("pitch_autocorrelation_peak"), Shape.Grid.PitchCorrelationPeak);
Json->SetNumberField(TEXT("nearby_autocorrelation_peak"), Shape.Grid.NeighborCorrelationPeak);
Json->SetNumberField(TEXT("pitch_autocorrelation_excess"), Shape.Grid.PitchPeakExcess);
Json->SetNumberField(TEXT("grid_signature_probe_extent_m"), 64.0);
Json->SetNumberField(TEXT("grid_signature_probe_spacing_m"), 1.0);
Json->SetNumberField(TEXT("grid_signature_probe_height_layers"), 9);
Json->SetNumberField(TEXT("detail_rms"), Shape.Detail.Rms);
Json->SetNumberField(TEXT("detail_lattice_scale_energy_ratio"),
Shape.Detail.LatticeScaleEnergyRatio);
Json->SetNumberField(TEXT("mesh_triangles_l0"), Shape.Mesh.TriangleCount);
Json->SetNumberField(TEXT("mesh_manifold_edges_l0"),
static_cast<double>(Shape.Mesh.ManifoldEdgeCount));
Json->SetNumberField(TEXT("mesh_dihedral_share_over_45deg"),
Shape.Mesh.ShareDihedralsOver45Degrees);
Json->SetNumberField(TEXT("mesh_dihedral_p95_deg"), Shape.Mesh.P95DihedralDegrees);
Json->SetNumberField(TEXT("mesh_vertices_l1"), Shape.Lod1VertexCount);
Json->SetNumberField(TEXT("lod1_p95_surface_distance_voxels"),
Shape.Lod1P95SurfaceErrorVoxels);
Json->SetNumberField(TEXT("strates_checked"), Shape.Structure.StratesChecked);
Json->SetNumberField(TEXT("roof_samples"), static_cast<double>(Shape.Structure.RoofSamples));
Json->SetNumberField(TEXT("roof_openings"), static_cast<double>(Shape.Structure.RoofOpenings));
Json->SetNumberField(TEXT("floor_samples"), static_cast<double>(Shape.Structure.FloorSamples));
Json->SetNumberField(TEXT("floor_openings"), static_cast<double>(Shape.Structure.FloorOpenings));
Json->SetNumberField(TEXT("estimated_surface_area_m2"), Variety.SurfaceAreaSquareMeters);
Json->SetNumberField(TEXT("open_air_fraction"), Variety.OpenFraction);
return Json;
};
constexpr int32 CrumbBoundaryPadding = 16 * static_cast<int32>(100.0f / VOXEL_SIZE + 0.5f);
const int32 PaddedCrumbDimension = Dimension + 2 * CrumbBoundaryPadding;
const FIntVector PaddedCrumbOrigin = Origin
- FIntVector(CrumbBoundaryPadding, CrumbBoundaryPadding, CrumbBoundaryPadding);
const auto CountBoundaryPaddedCrumbs = [&](bool bLegacyField,
int64& OutAirCrumbs, int64& OutSolidCrumbs,
TArray<FStep2fFragmentMetrics::FCrumbComponent>* OutCrumbComponents = nullptr)
{
VoxelForgeRedefinedDensityField::SetLegacyStep2eAuditMode(bLegacyField);
TArray<float> PaddedDensity;
VoxelForgeRegionEval::FTiming PaddedTiming;
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, PaddedCrumbOrigin, PaddedCrumbDimension,
Workers, 1.0f, PaddedDensity, PaddedTiming, false, true))
{
return false;
}
const FStep2fFragmentMetrics PaddedFragments = ComputeStep2fFragmentMetrics(
PaddedDensity, PaddedCrumbDimension,
CrumbBoundaryPadding, Dimension);
OutAirCrumbs = PaddedFragments.AirCrumbsBelowOneCubicMeter;
OutSolidCrumbs = PaddedFragments.SolidCrumbsBelowOneCubicMeter;
if (OutCrumbComponents != nullptr)
{
*OutCrumbComponents = PaddedFragments.CrumbComponents;
}
return true;
};
for (int32 Profile = 0;
Profile < VoxelForgeRedefinedDensityField::GetParameterSetCount(); ++Profile)
{
VoxelForgeRedefinedDensityField::SetParameterSet(Profile);
VoxelForgeRedefinedDensityField::SetLegacyStep2eAuditMode(true);
const TCHAR* ProfileName = nullptr;
VoxelForgeRedefinedDensityField::FGenerationParameters LegacyParameters;
if (!VoxelForgeRedefinedDensityField::GetParameterSetInfo(
Profile, ProfileName, LegacyParameters))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2f] missing legacy profile %d."), Profile);
return 2;
}
TArray<float> LegacyDensity;
TArray<double> LegacyTimes;
VoxelForgeRegionEval::FTiming LegacyLastTiming;
for (int32 Trial = 0; Trial < 3; ++Trial)
{
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
LegacyDensity, LegacyLastTiming, true, true))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeStep2f] profile %d step 2e ring trial %d failed."),
Profile, Trial + 1);
return 2;
}
LegacyTimes.Add(LegacyLastTiming.WallSeconds * 1000.0);
}
LegacyTimes.Sort();
const double LegacyMedianMs = LegacyTimes[LegacyTimes.Num() / 2];
FStep2fShapeSnapshot LegacyShape = MeasureShape(
LegacyDensity, LegacyParameters);
const FStep2eVarietyMetrics LegacyVariety = LegacyShape.Variety;
if (!CountBoundaryPaddedCrumbs(true,
LegacyShape.Fragments.AirCrumbsAfterBoundaryPadding,
LegacyShape.Fragments.SolidCrumbsAfterBoundaryPadding))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeStep2f] profile %d padded step 2e crumb pass failed."), Profile);
return 2;
}
WorstStep2eMs = FMath::Max(WorstStep2eMs, LegacyMedianMs);
const int64 LegacyCrumbs = LegacyShape.Fragments.AirCrumbsAfterBoundaryPadding
+ LegacyShape.Fragments.SolidCrumbsAfterBoundaryPadding;
WorstStep2eCrumbs = FMath::Max(WorstStep2eCrumbs, LegacyCrumbs);
bStep2eCrumbsRejected &= LegacyCrumbs > 0;
WorstStep2eDihedralShare = FMath::Max(
WorstStep2eDihedralShare, LegacyShape.Mesh.ShareDihedralsOver45Degrees);
WorstStep2eDihedralP95 = FMath::Max(
WorstStep2eDihedralP95, LegacyShape.Mesh.P95DihedralDegrees);
bStep2eFlakesRejected |= LegacyShape.Mesh.ShareDihedralsOver45Degrees > 0.25
|| LegacyShape.Mesh.P95DihedralDegrees > 70.0;
WorstStep2eDetailEnergy = FMath::Max(
WorstStep2eDetailEnergy, LegacyShape.Detail.LatticeScaleEnergyRatio);
BestStep2eDetailRms = FMath::Min(BestStep2eDetailRms, LegacyShape.Detail.Rms);
bStep2eBandLimitRejected &= LegacyShape.Detail.LatticeScaleEnergyRatio > 0.20;
WorstStep2eLodDistance = FMath::Max(
WorstStep2eLodDistance, LegacyShape.Lod1P95SurfaceErrorVoxels);
bStep2eLodRejected |= LegacyShape.Lod1P95SurfaceErrorVoxels > 1.0;
WorstStep2ePitchCorrelation = FMath::Max(
WorstStep2ePitchCorrelation, LegacyShape.Grid.PitchCorrelationPeak);
WorstStep2ePitchExcess = FMath::Max(
WorstStep2ePitchExcess, LegacyShape.Grid.PitchPeakExcess);
bStep2eGridRejected |= LegacyShape.Grid.PitchCorrelationPeak > 0.22
|| LegacyShape.Grid.PitchPeakExcess > 0.08;
const double LegacySolidFraction = 1.0 - LegacyVariety.OpenFraction;
LowestStep2eSolidFraction = FMath::Min(LowestStep2eSolidFraction, LegacySolidFraction);
WorstStep2eRoofOpenings = FMath::Max(
WorstStep2eRoofOpenings, LegacyShape.Structure.RoofOpenings);
WorstStep2eFloorOpenings = FMath::Max(
WorstStep2eFloorOpenings, LegacyShape.Structure.FloorOpenings);
bStep2eStructureRejected &=
LegacySolidFraction < 0.25
|| LegacyShape.Structure.RoofOpenings > 0
|| LegacyShape.Structure.FloorOpenings > 0;
VoxelForgeRedefinedDensityField::SetLegacyStep2eAuditMode(false);
VoxelForgeRedefinedDensityField::FGenerationParameters NewParameters;
if (!VoxelForgeRedefinedDensityField::GetParameterSetInfo(
Profile, ProfileName, NewParameters))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2f] missing new profile %d."), Profile);
return 2;
}
TArray<float> NewDensity;
TArray<double> NewTimes;
VoxelForgeRegionEval::FTiming NewLastTiming;
for (int32 Trial = 0; Trial < 3; ++Trial)
{
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
NewDensity, NewLastTiming, true, true))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeStep2f] profile %d step 2f ring trial %d failed."),
Profile, Trial + 1);
return 2;
}
NewTimes.Add(NewLastTiming.WallSeconds * 1000.0);
}
NewTimes.Sort();
const double NewMedianMs = NewTimes[NewTimes.Num() / 2];
FStep2fShapeSnapshot NewShape = MeasureShape(NewDensity, NewParameters);
const FStep2eVarietyMetrics NewVariety = NewShape.Variety;
TArray<FStep2fFragmentMetrics::FCrumbComponent> NewPaddedCrumbComponents;
if (!CountBoundaryPaddedCrumbs(false,
NewShape.Fragments.AirCrumbsAfterBoundaryPadding,
NewShape.Fragments.SolidCrumbsAfterBoundaryPadding,
&NewPaddedCrumbComponents))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeStep2f] profile %d padded step 2f crumb pass failed."), Profile);
return 2;
}
WorstStep2fMs = FMath::Max(WorstStep2fMs, NewMedianMs);
const double NewSolidFraction = 1.0 - NewVariety.OpenFraction;
const int64 NewCrumbs = NewShape.Fragments.AirCrumbsAfterBoundaryPadding
+ NewShape.Fragments.SolidCrumbsAfterBoundaryPadding;
WorstStep2fCrumbs = FMath::Max(WorstStep2fCrumbs, NewCrumbs);
WorstStep2fDihedralShare = FMath::Max(
WorstStep2fDihedralShare, NewShape.Mesh.ShareDihedralsOver45Degrees);
WorstStep2fDihedralP95 = FMath::Max(
WorstStep2fDihedralP95, NewShape.Mesh.P95DihedralDegrees);
WorstStep2fDetailEnergy = FMath::Max(
WorstStep2fDetailEnergy, NewShape.Detail.LatticeScaleEnergyRatio);
BestStep2fDetailRms = FMath::Min(BestStep2fDetailRms, NewShape.Detail.Rms);
WorstStep2fLodDistance = FMath::Max(
WorstStep2fLodDistance, NewShape.Lod1P95SurfaceErrorVoxels);
WorstStep2fPitchCorrelation = FMath::Max(
WorstStep2fPitchCorrelation, NewShape.Grid.PitchCorrelationPeak);
WorstStep2fPitchExcess = FMath::Max(
WorstStep2fPitchExcess, NewShape.Grid.PitchPeakExcess);
LowestStep2fSolidFraction = FMath::Min(LowestStep2fSolidFraction, NewSolidFraction);
WorstStep2fRoofOpenings = FMath::Max(
WorstStep2fRoofOpenings, NewShape.Structure.RoofOpenings);
WorstStep2fFloorOpenings = FMath::Max(
WorstStep2fFloorOpenings, NewShape.Structure.FloorOpenings);
WorstStep2fSolidFraction = FMath::Max(WorstStep2fSolidFraction, NewSolidFraction);
BestStep2fSolidFraction = FMath::Min(BestStep2fSolidFraction, NewSolidFraction);
bStep2fCrumbGatePassed &= NewShape.Fragments.AirCrumbsAfterBoundaryPadding == 0
&& NewShape.Fragments.SolidCrumbsAfterBoundaryPadding == 0;
bStep2fFlakeGatePassed &= NewShape.Mesh.TriangleCount > 0
&& NewShape.Mesh.ManifoldEdgeCount > 0
&& NewShape.Mesh.ShareDihedralsOver45Degrees <= 0.25
&& NewShape.Mesh.P95DihedralDegrees <= 70.0;
bStep2fBandLimitPassed &= NewShape.Detail.LatticeScaleEnergyRatio <= 0.20
&& NewShape.Detail.Rms >= 0.80;
bStep2fLodPassed &= NewShape.Lod1VertexCount > 0
&& NewShape.Lod1P95SurfaceErrorVoxels <= 1.0;
bStep2fGridPassed &= NewShape.Grid.PitchCorrelationPeak <= 0.40
&& NewShape.Grid.PitchPeakExcess <= 0.08;
bStep2fStructurePassed &= NewSolidFraction >= 0.25 && NewSolidFraction <= 0.88
&& NewShape.Structure.StratesChecked > 0
&& NewShape.Structure.RoofSamples > 0
&& NewShape.Structure.FloorSamples > 0
&& NewShape.Structure.RoofOpenings == 0
&& NewShape.Structure.FloorOpenings == 0;
TSharedRef<FJsonObject> Row = MakeShared<FJsonObject>();
Row->SetNumberField(TEXT("profile_index"), Profile);
Row->SetStringField(TEXT("name"), ProfileName);
Row->SetObjectField(TEXT("step2e"), ShapeJson(
LegacyShape, LegacyMedianMs, LegacyVariety));
Row->SetObjectField(TEXT("step2f"), ShapeJson(
NewShape, NewMedianMs, NewVariety));
TArray<TSharedPtr<FJsonValue>> PaddedCrumbDiagnostics;
for (const FStep2fFragmentMetrics::FCrumbComponent& Crumb
: NewPaddedCrumbComponents)
{
// Root is an actual sample in the connected component. The min/max coordinates are
// an axis-aligned bound and their corner may not belong to the component.
const FIntVector SampleVoxel = PaddedCrumbOrigin + Crumb.Root;
const float SampleX = static_cast<float>(SampleVoxel.X);
const float SampleY = static_cast<float>(SampleVoxel.Y);
const float SampleZ = static_cast<float>(SampleVoxel.Z);
TSharedRef<FJsonObject> CrumbJson = MakeShared<FJsonObject>();
CrumbJson->SetStringField(TEXT("sign"), Crumb.bAir ? TEXT("air") : TEXT("solid"));
CrumbJson->SetNumberField(TEXT("samples"), Crumb.Samples);
CrumbJson->SetNumberField(TEXT("density_from_padded_array_at_min"),
Crumb.DensityAtMin);
CrumbJson->SetNumberField(TEXT("density_at_root"), Crumb.DensityAtRoot);
CrumbJson->SetNumberField(TEXT("sign_mismatch_samples"),
Crumb.SignMismatchSamples);
CrumbJson->SetArrayField(TEXT("root_world_voxel"), {
MakeShared<FJsonValueNumber>(PaddedCrumbOrigin.X + Crumb.Root.X),
MakeShared<FJsonValueNumber>(PaddedCrumbOrigin.Y + Crumb.Root.Y),
MakeShared<FJsonValueNumber>(PaddedCrumbOrigin.Z + Crumb.Root.Z)});
CrumbJson->SetArrayField(TEXT("min_world_voxel"), {
MakeShared<FJsonValueNumber>(SampleVoxel.X),
MakeShared<FJsonValueNumber>(SampleVoxel.Y),
MakeShared<FJsonValueNumber>(SampleVoxel.Z)});
CrumbJson->SetArrayField(TEXT("max_world_voxel"), {
MakeShared<FJsonValueNumber>(PaddedCrumbOrigin.X + Crumb.Max.X),
MakeShared<FJsonValueNumber>(PaddedCrumbOrigin.Y + Crumb.Max.Y),
MakeShared<FJsonValueNumber>(PaddedCrumbOrigin.Z + Crumb.Max.Z)});
CrumbJson->SetNumberField(TEXT("final_density_at_min"),
VoxelForgeRedefinedDensityField::EvaluateParameterized(
*World.Generator, SampleX, SampleY, SampleZ));
CrumbJson->SetNumberField(TEXT("macro_at_min"),
VoxelForgeRedefinedDensityField::EvaluateParameterizedMacroOnly(
*World.Generator, SampleX, SampleY, SampleZ));
CrumbJson->SetNumberField(TEXT("detail_at_min"),
VoxelForgeRedefinedDensityField::EvaluateParameterizedDetail(
*World.Generator, SampleX, SampleY, SampleZ));
CrumbJson->SetStringField(TEXT("macro_description_at_min"),
VoxelForgeRedefinedDensityField::DescribeParameterizedMacroAt(
*World.Generator, SampleX, SampleY, SampleZ));
PaddedCrumbDiagnostics.Add(MakeShared<FJsonValueObject>(CrumbJson));
}
Row->SetArrayField(TEXT("step2f_padded_crumb_diagnostics"), PaddedCrumbDiagnostics);
Row->SetObjectField(TEXT("step2e_parameters"), Step2eParametersJson(LegacyParameters));
Row->SetObjectField(TEXT("step2f_parameters"), Step2eParametersJson(NewParameters));
TArray<TSharedPtr<FJsonValue>> LegacyTrialValues;
TArray<TSharedPtr<FJsonValue>> NewTrialValues;
for (const double Time : LegacyTimes)
{
LegacyTrialValues.Add(MakeShared<FJsonValueNumber>(Time));
}
for (const double Time : NewTimes)
{
NewTrialValues.Add(MakeShared<FJsonValueNumber>(Time));
}
Row->SetArrayField(TEXT("step2e_six_worker_ring_trials_ms"), LegacyTrialValues);
Row->SetArrayField(TEXT("step2f_six_worker_ring_trials_ms"), NewTrialValues);
TSharedRef<FJsonObject> NewTimingBreakdown = MakeShared<FJsonObject>();
NewTimingBreakdown->SetNumberField(TEXT("last_trial_wall_ms"),
NewLastTiming.WallSeconds * 1000.0);
NewTimingBreakdown->SetNumberField(TEXT("last_trial_worker_cpu_seconds"),
NewLastTiming.WorkerCpuSeconds);
NewTimingBreakdown->SetNumberField(TEXT("sum_worker_brick_classification_seconds"),
NewLastTiming.BrickClassificationSeconds);
NewTimingBreakdown->SetNumberField(TEXT("sum_worker_density_evaluation_seconds"),
NewLastTiming.DensityEvaluationSeconds);
NewTimingBreakdown->SetNumberField(TEXT("sum_worker_uniform_fill_seconds"),
NewLastTiming.UniformFillSeconds);
NewTimingBreakdown->SetNumberField(TEXT("longest_worker_wall_seconds"),
NewLastTiming.LongestWorkerWallSeconds);
NewTimingBreakdown->SetNumberField(TEXT("evaluated_samples"),
static_cast<double>(NewLastTiming.EvaluatedSamples));
NewTimingBreakdown->SetNumberField(TEXT("skipped_samples"),
static_cast<double>(NewLastTiming.SkippedSamples));
NewTimingBreakdown->SetNumberField(TEXT("macro_centre_evaluations"),
static_cast<double>(NewLastTiming.MacroCentreEvaluations));
Row->SetObjectField(TEXT("step2f_last_trial_timing_breakdown"), NewTimingBreakdown);
FString SafeName(ProfileName);
SafeName.ToLowerInline();
const FString SetName = FString::Printf(TEXT("set_%02d_%s"), Profile, *SafeName);
const FString SetDirectory = FPaths::Combine(ResearchOut, SetName);
IFileManager::Get().MakeDirectory(*SetDirectory, true);
const FString ImageNames[] = {
TEXT("slice_xy_2e_vs_2f.png"), TEXT("slice_xz_2e_vs_2f.png"),
TEXT("slice_yz_2e_vs_2f.png"), TEXT("slice_xz_passage_2e_vs_2f.png")};
FString SliceError;
SaveStep2dSideBySideSlice(FPaths::Combine(SetDirectory, ImageNames[0]),
LegacyDensity, NewDensity, Dimension, TEXT('h'), Dimension / 2, SliceError);
SaveStep2dSideBySideSlice(FPaths::Combine(SetDirectory, ImageNames[1]),
LegacyDensity, NewDensity, Dimension, TEXT('x'), Dimension / 2, SliceError);
SaveStep2dSideBySideSlice(FPaths::Combine(SetDirectory, ImageNames[2]),
LegacyDensity, NewDensity, Dimension, TEXT('y'), Dimension / 2, SliceError);
int32 PassageIndex = Dimension / 2;
if (World.Manager != nullptr && !World.Manager->GetPassages().IsEmpty())
{
PassageIndex = FMath::Clamp(FMath::RoundToInt(
World.Manager->GetPassages()[0].LowerLanding.StandingPoint.Y) - Origin.Y,
0, Dimension - 1);
}
SaveStep2dSideBySideSlice(FPaths::Combine(SetDirectory, ImageNames[3]),
LegacyDensity, NewDensity, Dimension, TEXT('x'), PassageIndex, SliceError);
if (!SliceError.IsEmpty())
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2f] slice write failed for %s: %s"),
ProfileName, *SliceError);
return 2;
}
Row->SetStringField(TEXT("slice_directory"), SetDirectory);
ProfileRows.Add(MakeShared<FJsonValueObject>(Row));
for (const FString& ImageName : ImageNames)
{
TSharedRef<FJsonObject> Slice = MakeShared<FJsonObject>();
Slice->SetStringField(TEXT("profile"), ProfileName);
Slice->SetStringField(TEXT("file"), FPaths::Combine(SetDirectory, ImageName));
Slice->SetStringField(TEXT("layout"),
TEXT("step 2e density on left; step 2f density on right; light=air, dark=solid, cyan=near zero"));
SliceRows.Add(MakeShared<FJsonValueObject>(Slice));
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeStep2f] profile=%d/%s old_ms=%.2f new_ms=%.2f "
"solid=%.3f crumbs=%lld/%lld flakes=%.3f/%.3f grid=%.3f/%.3f "
"detail=%.3f lod95=%.2f roof=%lld floor=%lld"),
Profile, ProfileName, LegacyMedianMs, NewMedianMs, NewSolidFraction,
static_cast<long long>(NewShape.Fragments.AirCrumbsAfterBoundaryPadding),
static_cast<long long>(NewShape.Fragments.SolidCrumbsAfterBoundaryPadding),
NewShape.Mesh.ShareDihedralsOver45Degrees, NewShape.Detail.LatticeScaleEnergyRatio,
NewShape.Grid.PitchCorrelationPeak, NewShape.Grid.PitchPeakExcess,
NewShape.Detail.Rms, NewShape.Lod1P95SurfaceErrorVoxels,
static_cast<long long>(NewShape.Structure.RoofOpenings),
static_cast<long long>(NewShape.Structure.FloorOpenings));
}
const int32 HardestProfile = VoxelForgeRedefinedDensityField::GetParameterSetCount() - 1;
VoxelForgeRedefinedDensityField::SetLegacyStep2eAuditMode(false);
VoxelForgeRedefinedDensityField::SetParameterSet(HardestProfile);
TArray<float> HardestFullDensity;
TArray<float> RepeatDensity;
TArray<float> OneWorkerDensity;
VoxelForgeRegionEval::FTiming HardestTiming;
VoxelForgeRegionEval::FTiming RepeatTiming;
VoxelForgeRegionEval::FTiming OneWorkerTiming;
const bool bHardestFullOk = VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
HardestFullDensity, HardestTiming, false, true);
const bool bRepeatOk = bHardestFullOk && VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
RepeatDensity, RepeatTiming, false, true);
const bool bOneWorkerOk = bHardestFullOk && VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, 1, 1.0f,
OneWorkerDensity, OneWorkerTiming, false, true);
if (!bHardestFullOk || !bRepeatOk || !bOneWorkerOk)
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2f] determinism ring failed."));
return 2;
}
const uint64 HardestHash = VoxelForgeRegionEval::HashQuantized(HardestFullDensity);
const uint64 RepeatHash = VoxelForgeRegionEval::HashQuantized(RepeatDensity);
const uint64 OneWorkerHash = VoxelForgeRegionEval::HashQuantized(OneWorkerDensity);
const bool bDeterminism = HardestHash == RepeatHash && HardestHash == OneWorkerHash
&& Step2eSameWorkerRun(HardestTiming, RepeatTiming)
&& Step2eSameWorkerRun(HardestTiming, OneWorkerTiming);
FIntVector BrickParityOrigin(-28, -28,
FMath::FloorToInt(static_cast<float>(CenterZ) / CHUNK_SIZE) * CHUNK_SIZE + 4);
const FIntVector BrickParitySize(
VoxelForgeRedefinedDensityField::ParameterizedBrickSamples,
VoxelForgeRedefinedDensityField::ParameterizedBrickSamples, 16);
VoxelForgeRedefinedDensityField::FParameterizedBrickContext BrickParityContext;
TArray<float> BrickParityDetail;
TArray<float> BrickParityColumn;
TArray<float> BrickParityValues;
VoxelForgeRedefinedDensityField::EvaluateParameterizedDensityBrick(
*World.Generator, BrickParityOrigin, BrickParitySize, 1.0f,
BrickParityContext, BrickParityDetail, BrickParityColumn, BrickParityValues);
int64 BrickScalarMismatchCount = 0;
for (int32 Z = 0; Z < BrickParitySize.Z; ++Z)
for (int32 Y = 0; Y < BrickParitySize.Y; ++Y)
for (int32 X = 0; X < BrickParitySize.X; ++X)
{
const float ScalarValue = VoxelForgeRedefinedDensityField::EvaluateParameterized(
*World.Generator, static_cast<float>(BrickParityOrigin.X + X),
static_cast<float>(BrickParityOrigin.Y + Y),
static_cast<float>(BrickParityOrigin.Z + Z));
const float BrickValue = BrickParityValues.IsValidIndex(
(Z * BrickParitySize.Y + Y) * BrickParitySize.X + X)
? BrickParityValues[(Z * BrickParitySize.Y + Y) * BrickParitySize.X + X]
: FLT_MAX;
BrickScalarMismatchCount += FMemory::Memcmp(
&ScalarValue, &BrickValue, sizeof(float)) == 0 ? 0 : 1;
}
const bool bParameterizedBrickScalarParity =
BrickParityValues.Num() == BrickParitySize.X * BrickParitySize.Y * BrickParitySize.Z
&& BrickScalarMismatchCount == 0;
const FStep2dBrickProofAudit ProofAudit = AuditStep2dBrickProofs(
*World.Generator, Origin, Dimension, true);
const bool bBoundAudit = ProofAudit.CorrectlySkippedBricksAudited > 0
&& ProofAudit.CorrectProofSamplesChecked > 0
&& ProofAudit.CorrectProofViolations == 0;
const bool bUndersizedBoundCaught = ProofAudit.DeliberateBoundDisagreements > 0;
const FStep2dBoundaryStats BoundaryStats = ComputeStep2dBoundaryStats(
HardestFullDensity, Dimension, Origin, 128);
TArray<float> SeamFailureDensity = HardestFullDensity;
int64 DeliberateSeamSamplesMutated = 0;
for (int32 Z = 1; Z < Dimension - 1; ++Z)
for (int32 Y = 1; Y < Dimension - 1; ++Y)
for (int32 X = 1; X < Dimension - 1; ++X)
{
const int32 Index = (Z * Dimension + Y) * Dimension + X;
if (((Origin.X + X) % 8) == 0 && FMath::Abs(HardestFullDensity[Index]) <= 12.0f)
{
SeamFailureDensity[Index + 1] += 64.0f;
++DeliberateSeamSamplesMutated;
}
}
const FStep2dBoundaryStats SeamFailureStats = ComputeStep2dBoundaryStats(
SeamFailureDensity, Dimension, Origin, 128);
const bool bBoundaryPass = Step2eBoundaryStatsPass(BoundaryStats);
const bool bDeliberateSeamCaught = DeliberateSeamSamplesMutated > 0
&& !Step2eBoundaryStatsPass(SeamFailureStats);
const bool bAllGeometryGatesRejectStep2e = bStep2eCrumbsRejected
&& bStep2eFlakesRejected && bStep2eBandLimitRejected && bStep2eLodRejected
&& bStep2eGridRejected && bStep2eStructureRejected;
const bool bAllGeometryGatesPassStep2f = bStep2fCrumbGatePassed
&& bStep2fFlakeGatePassed && bStep2fBandLimitPassed && bStep2fLodPassed
&& bStep2fGridPassed && bStep2fStructurePassed;
const bool bSpeedPass = Workers == 6 && WorstStep2fMs <= 600.0;
const bool bPassed = bAllGeometryGatesRejectStep2e && bAllGeometryGatesPassStep2f
&& bSpeedPass && bDeterminism && bParameterizedBrickScalarParity
&& bBoundAudit && bUndersizedBoundCaught
&& bBoundaryPass && bDeliberateSeamCaught && HardestTiming.NonFiniteSamples == 0;
TSharedRef<FJsonObject> Gates = MakeShared<FJsonObject>();
const auto AddGate = [&Gates](const TCHAR* Name, const TCHAR* Threshold,
bool bLegacyRejected, bool bNewPassed,
const TSharedRef<FJsonObject>& LegacyNumbers,
const TSharedRef<FJsonObject>& NewNumbers)
{
TSharedRef<FJsonObject> Gate = MakeShared<FJsonObject>();
Gate->SetStringField(TEXT("threshold"), Threshold);
Gate->SetBoolField(TEXT("step2e_rejected"), bLegacyRejected);
Gate->SetBoolField(TEXT("step2f_passed"), bNewPassed);
Gate->SetObjectField(TEXT("step2e_worst_profile_numbers"), LegacyNumbers);
Gate->SetObjectField(TEXT("step2f_worst_profile_numbers"), NewNumbers);
Gates->SetObjectField(Name, Gate);
};
TSharedRef<FJsonObject> Step2eCrumbNumbers = MakeShared<FJsonObject>();
Step2eCrumbNumbers->SetNumberField(TEXT("crumbs_below_1m3_max"), WorstStep2eCrumbs);
TSharedRef<FJsonObject> Step2fCrumbNumbers = MakeShared<FJsonObject>();
Step2fCrumbNumbers->SetNumberField(TEXT("crumbs_below_1m3_max"), WorstStep2fCrumbs);
AddGate(TEXT("crumbs"), TEXT("zero 12.5 cm-grid components smaller than 512 voxels (1 m3), measured in a 128-sample-padded volume and restricted to components intersecting the 259-cube"),
bStep2eCrumbsRejected, bStep2fCrumbGatePassed, Step2eCrumbNumbers, Step2fCrumbNumbers);
TSharedRef<FJsonObject> Step2eFlakeNumbers = MakeShared<FJsonObject>();
Step2eFlakeNumbers->SetNumberField(TEXT("max_dihedral_share_over_45deg"), WorstStep2eDihedralShare);
Step2eFlakeNumbers->SetNumberField(TEXT("max_dihedral_p95_deg"), WorstStep2eDihedralP95);
TSharedRef<FJsonObject> Step2fFlakeNumbers = MakeShared<FJsonObject>();
Step2fFlakeNumbers->SetNumberField(TEXT("max_dihedral_share_over_45deg"), WorstStep2fDihedralShare);
Step2fFlakeNumbers->SetNumberField(TEXT("max_dihedral_p95_deg"), WorstStep2fDihedralP95);
AddGate(TEXT("flakes"), TEXT("L0 mesh dihedral share over 45 degrees <= 0.25; p95 <= 70 degrees"),
bStep2eFlakesRejected, bStep2fFlakeGatePassed, Step2eFlakeNumbers, Step2fFlakeNumbers);
TSharedRef<FJsonObject> Step2eDetailNumbers = MakeShared<FJsonObject>();
Step2eDetailNumbers->SetNumberField(TEXT("max_lattice_scale_energy_ratio"), WorstStep2eDetailEnergy);
Step2eDetailNumbers->SetNumberField(TEXT("min_detail_rms"), BestStep2eDetailRms);
TSharedRef<FJsonObject> Step2fDetailNumbers = MakeShared<FJsonObject>();
Step2fDetailNumbers->SetNumberField(TEXT("max_lattice_scale_energy_ratio"), WorstStep2fDetailEnergy);
Step2fDetailNumbers->SetNumberField(TEXT("min_detail_rms"), BestStep2fDetailRms);
AddGate(TEXT("band_limited_detail"), TEXT("detail RMS >= 0.80 voxel units; normalized 25 cm difference energy <= 0.20"),
bStep2eBandLimitRejected, bStep2fBandLimitPassed, Step2eDetailNumbers, Step2fDetailNumbers);
TSharedRef<FJsonObject> Step2eLodNumbers = MakeShared<FJsonObject>();
Step2eLodNumbers->SetNumberField(TEXT("max_l1_p95_surface_distance_voxels"), WorstStep2eLodDistance);
TSharedRef<FJsonObject> Step2fLodNumbers = MakeShared<FJsonObject>();
Step2fLodNumbers->SetNumberField(TEXT("max_l1_p95_surface_distance_voxels"), WorstStep2fLodDistance);
AddGate(TEXT("lod_stability"), TEXT("L1 mesh p95 estimated distance from the L0 isosurface <= 1 lattice sample (12.5 cm)"),
bStep2eLodRejected, bStep2fLodPassed, Step2eLodNumbers, Step2fLodNumbers);
TSharedRef<FJsonObject> Step2eGridNumbers = MakeShared<FJsonObject>();
Step2eGridNumbers->SetNumberField(TEXT("max_pitch_autocorrelation"), WorstStep2ePitchCorrelation);
Step2eGridNumbers->SetNumberField(TEXT("max_pitch_peak_excess"), WorstStep2ePitchExcess);
TSharedRef<FJsonObject> Step2fGridNumbers = MakeShared<FJsonObject>();
Step2fGridNumbers->SetNumberField(TEXT("max_pitch_autocorrelation"), WorstStep2fPitchCorrelation);
Step2fGridNumbers->SetNumberField(TEXT("max_pitch_peak_excess"), WorstStep2fPitchExcess);
AddGate(TEXT("grid_signature"), TEXT("pitch autocorrelation <= 0.40 and pitch excess over nearby lags <= 0.08"),
bStep2eGridRejected, bStep2fGridPassed, Step2eGridNumbers, Step2fGridNumbers);
TSharedRef<FJsonObject> Step2eStructureNumbers = MakeShared<FJsonObject>();
Step2eStructureNumbers->SetNumberField(TEXT("minimum_solid_fraction"), LowestStep2eSolidFraction);
Step2eStructureNumbers->SetNumberField(TEXT("max_roof_openings"), WorstStep2eRoofOpenings);
Step2eStructureNumbers->SetNumberField(TEXT("max_floor_openings"), WorstStep2eFloorOpenings);
TSharedRef<FJsonObject> Step2fStructureNumbers = MakeShared<FJsonObject>();
Step2fStructureNumbers->SetNumberField(TEXT("minimum_solid_fraction"), LowestStep2fSolidFraction);
Step2fStructureNumbers->SetNumberField(TEXT("max_roof_openings"), WorstStep2fRoofOpenings);
Step2fStructureNumbers->SetNumberField(TEXT("max_floor_openings"), WorstStep2fFloorOpenings);
AddGate(TEXT("roof_floor_and_solid_share"), TEXT("solid fraction 0.25..0.88; zero air samples in 1 m roof/floor shells"),
bStep2eStructureRejected, bStep2fStructurePassed,
Step2eStructureNumbers, Step2fStructureNumbers);
TSharedRef<FJsonObject> Report = MakeShared<FJsonObject>();
Report->SetStringField(TEXT("status"), bPassed ? TEXT("pass") : TEXT("fail"));
Report->SetBoolField(TEXT("pass"), bPassed);
Report->SetBoolField(TEXT("geometry_gates_reject_step2e"), bAllGeometryGatesRejectStep2e);
Report->SetBoolField(TEXT("geometry_gates_pass_step2f"), bAllGeometryGatesPassStep2f);
Report->SetBoolField(TEXT("worst_profile_meets_600ms"), bSpeedPass);
Report->SetNumberField(TEXT("step2e_worst_six_worker_ring_ms"), WorstStep2eMs);
Report->SetNumberField(TEXT("step2f_worst_six_worker_ring_ms"), WorstStep2fMs);
Report->SetNumberField(TEXT("step2f_solid_fraction_min"), BestStep2fSolidFraction);
Report->SetNumberField(TEXT("step2f_solid_fraction_max"), WorstStep2fSolidFraction);
Report->SetNumberField(TEXT("seed"), Seed);
Report->SetNumberField(TEXT("ring_dimension"), Dimension);
Report->SetArrayField(TEXT("ring_origin_voxels"), {
MakeShared<FJsonValueNumber>(Origin.X),
MakeShared<FJsonValueNumber>(Origin.Y),
MakeShared<FJsonValueNumber>(Origin.Z)});
Report->SetNumberField(TEXT("ring_samples"), static_cast<double>(TotalSamples));
Report->SetNumberField(TEXT("workers_requested"), Workers);
Report->SetNumberField(TEXT("world_prepare_ms_excluded"), WorldPrepareSeconds * 1000.0);
Report->SetStringField(TEXT("field_definition"),
TEXT("Rooms use strongly jittered cells with per-room radii, axis ratios and heights. "
"A conservative nearest-neighbor cap leaves a solid web between chambers; a rooted "
"tree of winding, rounded tubes with hash-varied widths and capped shafts joins them. Sparse ledges, pillars, "
"stalactites and large hanging masses remain per-cell rolls. The point and "
"brick detail samplers share independently rotated 96/48/24-sample bands with a "
"24-sample shortest period and a smooth ridge response; their final-field "
"detail supremum is 7.644 voxel units."));
Report->SetStringField(TEXT("component_method"),
TEXT("26-neighbor connected components are counted on the exact 12.5 cm sign lattice. "
"Raw components below 512 samples (< 1 m3) are reported, then a 128-sample (16 m) pad is "
"evaluated around each 259-cube and only components intersecting the original cube "
"are counted again; this prevents a clipped large component at the ring edge from "
"being called a crumb. The one-meter downsampled air component count is also reported."));
Report->SetStringField(TEXT("mesh_method"),
TEXT("Sixty-four level-0 tiles and eight level-1 tiles cover the same 128-sample (16 m) cube selected "
"for maximum coarse-sampled surface crossings. "
"The share of manifold triangle edges whose geometric dihedral exceeds 45 degrees "
"and its p95 are measured. Each level-1 vertex is compared with nearby level-0 "
"triangles using exact point-to-triangle distance; the p95 is reported in lattice samples."));
Report->SetObjectField(TEXT("gates"), Gates);
Report->SetArrayField(TEXT("profiles"), ProfileRows);
Report->SetArrayField(TEXT("slices"), SliceRows);
Report->SetStringField(TEXT("slice_directory"), ResearchOut);
TSharedRef<FJsonObject> Existing = MakeShared<FJsonObject>();
Existing->SetBoolField(TEXT("sound_brick_proofs"), bBoundAudit);
Existing->SetNumberField(TEXT("correctly_skipped_bricks_audited"),
static_cast<double>(ProofAudit.CorrectlySkippedBricksAudited));
Existing->SetNumberField(TEXT("final_field_samples_checked"),
static_cast<double>(ProofAudit.CorrectProofSamplesChecked));
Existing->SetNumberField(TEXT("correct_proof_sign_violations"),
static_cast<double>(ProofAudit.CorrectProofViolations));
TSharedRef<FJsonObject> FirstProofViolation = MakeShared<FJsonObject>();
FirstProofViolation->SetBoolField(TEXT("present"), ProofAudit.CorrectProofViolations > 0);
FirstProofViolation->SetArrayField(TEXT("brick_origin_voxels"), {
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationBrickOrigin.X),
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationBrickOrigin.Y),
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationBrickOrigin.Z)});
FirstProofViolation->SetArrayField(TEXT("brick_size"), {
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationBrickSize.X),
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationBrickSize.Y),
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationBrickSize.Z)});
FirstProofViolation->SetArrayField(TEXT("sample_voxels"), {
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationSample.X),
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationSample.Y),
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationSample.Z)});
FirstProofViolation->SetBoolField(TEXT("inside_brick"),
ProofAudit.bFirstViolationWasInsideBrick);
FirstProofViolation->SetBoolField(TEXT("claimed_air"),
ProofAudit.bFirstViolationClaimedAir);
FirstProofViolation->SetNumberField(TEXT("final_density"),
ProofAudit.FirstViolationDensity);
FirstProofViolation->SetNumberField(TEXT("center_macro"),
ProofAudit.FirstViolationCenterMacro);
FirstProofViolation->SetNumberField(TEXT("sample_macro"),
ProofAudit.FirstViolationSampleMacro);
FirstProofViolation->SetNumberField(TEXT("center_room_macro"),
ProofAudit.FirstViolationCenterRoomMacro);
FirstProofViolation->SetNumberField(TEXT("sample_room_macro"),
ProofAudit.FirstViolationSampleRoomMacro);
FirstProofViolation->SetNumberField(TEXT("sample_detail"),
ProofAudit.FirstViolationSampleDetail);
FirstProofViolation->SetNumberField(TEXT("center_to_sample_macro_delta_per_voxel"),
ProofAudit.FirstViolationMacroDeltaPerVoxel);
FirstProofViolation->SetNumberField(TEXT("path_max_macro_gradient"),
ProofAudit.FirstViolationPathMaxGradient);
FirstProofViolation->SetArrayField(TEXT("path_max_from_voxels"), {
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationPathMaxFrom.X),
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationPathMaxFrom.Y),
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationPathMaxFrom.Z)});
FirstProofViolation->SetArrayField(TEXT("path_max_to_voxels"), {
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationPathMaxTo.X),
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationPathMaxTo.Y),
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationPathMaxTo.Z)});
FirstProofViolation->SetNumberField(TEXT("path_max_from_macro"),
ProofAudit.FirstViolationPathMaxFromMacro);
FirstProofViolation->SetNumberField(TEXT("path_max_to_macro"),
ProofAudit.FirstViolationPathMaxToMacro);
FirstProofViolation->SetNumberField(TEXT("path_max_from_room_macro"),
ProofAudit.FirstViolationPathMaxFromRoomMacro);
FirstProofViolation->SetNumberField(TEXT("path_max_to_room_macro"),
ProofAudit.FirstViolationPathMaxToRoomMacro);
FirstProofViolation->SetStringField(TEXT("path_max_from_room_diagnostic"),
ProofAudit.FirstViolationPathMaxFromRoomDiagnostic);
FirstProofViolation->SetStringField(TEXT("path_max_to_room_diagnostic"),
ProofAudit.FirstViolationPathMaxToRoomDiagnostic);
FirstProofViolation->SetNumberField(TEXT("tiny_step_max_macro_gradient"),
ProofAudit.FirstViolationTinyStepMaxGradient);
Existing->SetObjectField(TEXT("first_proof_violation"), FirstProofViolation);
Existing->SetNumberField(TEXT("undersized_bound_claims_checked"),
static_cast<double>(ProofAudit.DeliberatelyUndersizedBoundClaimsChecked));
Existing->SetNumberField(TEXT("wrong_sign_samples_caught_by_undersized_bound"),
static_cast<double>(ProofAudit.DeliberateBoundDisagreements));
Existing->SetBoolField(TEXT("deliberate_undersized_bound_failure_caught"),
bUndersizedBoundCaught);
Existing->SetBoolField(TEXT("seam_gradient_gate"), bBoundaryPass);
Existing->SetBoolField(TEXT("deliberate_seam_failure_caught"), bDeliberateSeamCaught);
Existing->SetNumberField(TEXT("seam_failure_samples_mutated"),
static_cast<double>(DeliberateSeamSamplesMutated));
Existing->SetNumberField(TEXT("brick_boundary_gradient_jump_rms"), BoundaryStats.BrickJumpRms);
Existing->SetNumberField(TEXT("brick_interior_gradient_jump_rms"), BoundaryStats.InteriorJumpRms);
Existing->SetBoolField(TEXT("one_and_six_worker_determinism"), bDeterminism);
Existing->SetBoolField(TEXT("brick_path_matches_scalar_bit_exact"),
bParameterizedBrickScalarParity);
Existing->SetNumberField(TEXT("brick_scalar_parity_samples"),
BrickParitySize.X * BrickParitySize.Y * BrickParitySize.Z);
Existing->SetNumberField(TEXT("brick_scalar_parity_mismatches"),
static_cast<double>(BrickScalarMismatchCount));
Existing->SetStringField(TEXT("six_worker_hash"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(HardestHash)));
Existing->SetStringField(TEXT("repeat_six_worker_hash"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(RepeatHash)));
Existing->SetStringField(TEXT("one_worker_hash"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(OneWorkerHash)));
Existing->SetStringField(TEXT("functional_test_filter"),
TEXT("VoxelForge.ParameterizedGeneration.TraversalSeeds"));
Report->SetObjectField(TEXT("existing_checks"), Existing);
FString JsonText;
const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&JsonText);
const FString ReportPath = FPaths::Combine(ResearchOut, TEXT("step2f_report.json"));
const FString LaunchReportPath = FPaths::Combine(Arguments.OutDirectory, TEXT("step2f_report.json"));
if (!FJsonSerializer::Serialize(Report, Writer)
|| !FFileHelper::SaveStringToFile(JsonText, *ReportPath,
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM)
|| !FFileHelper::SaveStringToFile(JsonText, *LaunchReportPath,
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2f] report write failed: %s"), *ReportPath);
return 2;
}
VoxelForgeRedefinedDensityField::SetParameterizedEnabled(false);
VoxelForgeRedefinedDensityField::SetLegacyStep2eAuditMode(false);
VoxelForgeRedefinedDensityField::SetDetailScale(1.0f);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeStep2f] pass=%s gates_2e_rejected=%s gates_2f_passed=%s "
"speed600=%s old_worst_ms=%.2f new_worst_ms=%.2f report=%s"),
bPassed ? TEXT("yes") : TEXT("no"),
bAllGeometryGatesRejectStep2e ? TEXT("yes") : TEXT("no"),
bAllGeometryGatesPassStep2f ? TEXT("yes") : TEXT("no"),
bSpeedPass ? TEXT("yes") : TEXT("no"),
WorstStep2eMs, WorstStep2fMs, *ReportPath);
return bPassed ? 0 : 2;
}
class FGeneratorDensitySampler final : public IVoxelStrateDensitySampler
{
public:
explicit FGeneratorDensitySampler(const UVoxelGenerator& InGenerator)
: Generator(InGenerator)
{
}
virtual float SampleDensity(float WorldX, float WorldY, float WorldZ) const override
{
return Generator.GetDensityAt(WorldX, WorldY, WorldZ);
}
private:
const UVoxelGenerator& Generator;
};
struct FExploreRenderFrame
{
FString FileName;
FString Purpose = TEXT("overview");
FString CameraSeedSource = TEXT("player_fit");
FVector CameraSeedPoseVoxels = FVector::ZeroVector;
FVector CameraVoxels = FVector::ZeroVector;
FVector TargetVoxels = FVector::ZeroVector;
int32 HitPixels = 0;
bool bScaleMarkerProjected = false;
bool bFailureMarkersProjected = false;
bool bPartial = false;
};
struct FExploreRenderOutput
{
FString Status;
FString RefusalReason;
int32 Width = 0;
int32 Height = 0;
float FixedStepVoxels = 0.0f;
float FixedStepMeters = 0.0f;
int32 BisectionIterations = 0;
float MaxDistanceVoxels = 0.0f;
int64 EstimatedFixedDensitySamples = 0;
int64 LegacyTwoViewEstimatedDensitySamples = 0;
FString StepRationale;
FString CameraSeedPolicy;
int32 CameraSeedCount = 0;
bool bAllCamerasPlayerFit = false;
double MeshSeconds = 0.0;
double RasterSeconds = 0.0;
double FirstViewRasterSeconds = 0.0;
double AdditionalViewRasterSeconds = 0.0;
int32 RequestedViewpointCount = 8;
bool bMeshComplete = false;
double MeshAccelerationSeconds = 0.0;
bool bTruncated = false;
TArray<FExploreRenderFrame> Frames;
};
struct FExploreWalkOutput
{
FString Status;
FString RefusalReason;
bool bHasArrival = false;
bool bHasDeparture = false;
int32 ArrivalPassageCount = 0;
int32 DeparturePassageCount = 0;
FVector ArrivalVoxels = FVector::ZeroVector;
FVector DepartureVoxels = FVector::ZeroVector;
FString WindowPolicy;
FVoxelPlayerFitWalkReport Report;
bool bOriginCheckAttempted = false;
bool bOriginCheckAvailable = false;
FString OriginCheckRefusalReason;
FVoxelPlayerFitWalkReport OriginCheckReport;
int32 ArrivalLandingRoomProbeCount = 0;
int32 ArrivalLandingRoomPlayerFitProbeCount = 0;
int32 ArrivalLandingRoomArrivalComponentProbeCount = 0;
int32 DepartureLandingRoomProbeCount = 0;
int32 DepartureLandingRoomPlayerFitProbeCount = 0;
int32 DepartureLandingRoomArrivalComponentProbeCount = 0;
// Captured by the one walk pass and consumed by render camera selection. These are internal
// hand-off buffers, not a second source of truth for the field.
FVoxelStrateSampleGrid SharedSampleGrid;
TArray<FVector> CameraSeedPoses;
TArray<FString> CameraSeedSources;
};
struct FExploreMeshMetrics
{
int32 VertexCount = 0;
int32 TriangleCount = 0;
int32 SharedEdgeCount = 0;
int32 DihedralSampleCount = 0;
int32 NormalComparisonTriangleCount = 0;
float EdgeLengthP50Meters = 0.0f;
float EdgeLengthP99Meters = 0.0f;
float EdgeLengthMaxMeters = 0.0f;
float DihedralP50Degrees = 0.0f;
float DihedralP75Degrees = 0.0f;
float DihedralP90Degrees = 0.0f;
float DihedralP95Degrees = 0.0f;
float DihedralP99Degrees = 0.0f;
float DihedralFractionOver20Degrees = 0.0f;
float DihedralFractionOver40Degrees = 0.0f;
float FaceNormalVsMeanVertexNormalP50Degrees = 0.0f;
float FaceNormalVsMeanVertexNormalP90Degrees = 0.0f;
};
struct FExploreMeshEdgeKey
{
FIntVector First = FIntVector::ZeroValue;
FIntVector Second = FIntVector::ZeroValue;
bool operator==(const FExploreMeshEdgeKey& Other) const
{
return First == Other.First && Second == Other.Second;
}
};
FORCEINLINE uint32 GetTypeHash(const FExploreMeshEdgeKey& Key)
{
return HashCombine(GetTypeHash(Key.First), GetTypeHash(Key.Second));
}
struct FExploreMeshEdgeIncident
{
int32 IncidentCount = 0;
int32 FirstTriangle = INDEX_NONE;
int32 SecondTriangle = INDEX_NONE;
};
struct FExploreExportOutput
{
FString Status;
FString RefusalReason;
FString MeshFileName;
FString ManifestFileName;
FIntVector RegionOrigin = FIntVector::ZeroValue;
int32 RegionSize = 0;
int32 MesherTileCount = 0;
int32 MesherTilesCompleted = 0;
int64 EstimatedWorkingBytes = 0;
int64 WorkingMemoryCapBytes = MaxExportWorkingBytes;
int64 MeshArrayBytes = 0;
int64 MeshFileSizeBytes = 0;
int32 VertexCount = 0;
int32 TriangleCount = 0;
FExploreMeshMetrics SurfaceMetrics;
double MeshSeconds = 0.0;
double MeshUsPerVoxel = 0.0;
double MeasureSeconds = 0.0;
double ObjWriteSeconds = 0.0;
double ManifestSeconds = 0.0;
double ExportWallSeconds = 0.0;
double ExportAccountingRemainderSeconds = 0.0;
FString GeometryHash;
bool bTruncated = false;
};
struct FExploreDensitySliceOutput
{
FString Status;
FString RefusalReason;
FString FileName;
int32 SampleCount = 0;
int32 FiniteSampleCount = 0;
int32 NonFiniteSampleCount = 0;
int32 ZeroCrossingCount = 0;
int32 SaturatedSampleCount = 0;
float MinDensity = 0.0f;
float MaxDensity = 0.0f;
float MaxAbsDensity = 0.0f;
double Seconds = 0.0;
};
struct FExploreTunnelCoreProbeAxis
{
FString Name;
int32 ValidCoreCrossings = 0;
int32 ValidFinalCrossings = 0;
float CoreMeanCrossingVoxels = 0.0f;
float FinalMeanCrossingVoxels = 0.0f;
float CoreMinCrossingVoxels = 0.0f;
float CoreMaxCrossingVoxels = 0.0f;
float FinalMinCrossingVoxels = 0.0f;
float FinalMaxCrossingVoxels = 0.0f;
float CorePeriodicAmplitudeVoxels = 0.0f;
float CorePeriodicPeriodVoxels = 0.0f;
float FinalPeriodicAmplitudeVoxels = 0.0f;
float FinalPeriodicPeriodVoxels = 0.0f;
int32 CorePeriodicMeasuredSamples = 0;
float CorePeriodicMeasuredLengthVoxels = 0.0f;
int32 FinalPeriodicMeasuredSamples = 0;
float FinalPeriodicMeasuredLengthVoxels = 0.0f;
TArray<float> CoreCrossingsVoxels;
TArray<float> FinalCrossingsVoxels;
};
struct FExploreTunnelMouthProbeAxis
{
FString Name;
int32 ValidCrossings = 0;
float MeanCrossingVoxels = 0.0f;
float MinCrossingVoxels = 0.0f;
float MaxCrossingVoxels = 0.0f;
float PeriodicAmplitudeVoxels = 0.0f;
float PeriodicPeriodVoxels = 0.0f;
int32 PeriodicMeasuredSamples = 0;
float PeriodicMeasuredLengthVoxels = 0.0f;
// Audit value from the core probe's local-median detrend. Mouth surfaces can carry a broad
// room-floor bowl over the +/-2R window, so the accepted mouth value below uses a degree-six
// slow trend while retaining this legacy value for before/after diagnosis.
float LocalMedianPeriodicAmplitudeVoxels = 0.0f;
float LocalMedianPeriodicPeriodVoxels = 0.0f;
TArray<float> CrossingsVoxels;
};
struct FExploreTunnelMouthProbeEndpoint
{
FString Name;
uint32 RoomHash = 0;
FVector Mouth = FVector::ZeroVector;
float RadiusVoxels = 0.0f;
float TunnelFloorZ = -FLT_MAX;
float RoomFloorCrossingZ = -FLT_MAX;
float FloorJoinDeltaVoxels = 0.0f;
FExploreTunnelMouthProbeAxis Wall;
FExploreTunnelMouthProbeAxis Floor;
};
struct FExploreTunnelMouthProbeOutput
{
FString Status;
FString RefusalReason;
FString FileName;
int32 EndpointCount = 0;
int32 LateralSampleCount = 0;
float LateralStepVoxels = 0.25f;
float LateralWindowVoxels = 0.0f;
int32 RoomSampleStepVoxels = 2;
int64 RoomVolumeSampleCount = 0;
int64 RoomAirSampleCount = 0;
int64 CoreBottomSampleCount = 0;
int64 FinalSolidSampleCount = 0;
int64 CoreInducedSolidSampleCount = 0;
float MaxWallPeriodicAmplitudeVoxels = 0.0f;
float MaxFloorPeriodicAmplitudeVoxels = 0.0f;
float MaxLocalMedianWallPeriodicAmplitudeVoxels = 0.0f;
float MaxLocalMedianFloorPeriodicAmplitudeVoxels = 0.0f;
double Seconds = 0.0;
TArray<FExploreTunnelMouthProbeEndpoint> Endpoints;
};
struct FExploreTunnelCoreProbeOutput
{
FString Status;
FString RefusalReason;
FString FileName;
int32 TunnelIndex = INDEX_NONE;
int32 SampleCount = 0;
int32 CoreCenterFiniteSampleCount = 0;
int32 CoreCenterZeroCrossingCount = 0;
int32 FinalCenterFiniteSampleCount = 0;
int32 FinalCenterZeroCrossingCount = 0;
int32 FinalCenterSaturatedSampleCount = 0;
float CoreCenterMin = 0.0f;
float CoreCenterMax = 0.0f;
float FinalCenterMin = 0.0f;
float FinalCenterMax = 0.0f;
float FinalCenterMaxAbs = 0.0f;
float StepVoxels = 0.25f;
float LengthVoxels = 0.0f;
FVector AxisStart = FVector::ZeroVector;
FVector AxisEnd = FVector::ZeroVector;
FVector AxisDirection = FVector::ForwardVector;
FVector WallDirection = FVector::RightVector;
FExploreTunnelCoreProbeAxis Floor;
FExploreTunnelCoreProbeAxis Wall;
FExploreTunnelCoreProbeAxis Ceiling;
FExploreTunnelMouthProbeOutput Mouth;
double Seconds = 0.0;
};
struct FExploreProfilerComparison
{
bool bAvailable = false;
FString Scope;
double ProfiledSeconds = 0.0;
double UnprofiledSeconds = 0.0;
double DiscrepancySeconds = 0.0;
double DiscrepancyPercent = 0.0;
double Ratio = 0.0;
bool bGeometryEqual = false;
FString ProfiledGeometryHash;
FString UnprofiledGeometryHash;
FString SelfCheck = TEXT("not_run");
};
struct FExploreRunOutput
{
FExploreRenderOutput Render;
FExploreWalkOutput Walk;
FExploreExportOutput Export;
FExploreDensitySliceOutput DensitySlice;
FExploreTunnelCoreProbeOutput TunnelCoreProbe;
double BudgetSeconds = 0.0;
double ElapsedSeconds = 0.0;
double WalkSeconds = 0.0;
bool bTruncated = false;
FString TruncatedDuring;
TArray<FString> CompletedModes;
VoxelDensityProfile::FSnapshot Profile;
bool bHasProfile = false;
FExploreProfilerComparison ProfilerComparison;
};
struct FExploreStackSamplerSession
{
bool bStarted = false;
~FExploreStackSamplerSession()
{
Stop();
}
bool Start(uint32 IntervalUs, const FString& OutputDirectory)
{
if (bStarted)
{
return true;
}
bStarted = FVoxelStackSampler::Get().Start(
IntervalUs, OutputDirectory, TEXT("commandlet"));
return bStarted;
}
void Stop()
{
if (bStarted)
{
FVoxelStackSampler::Get().StopAndWrite();
bStarted = false;
}
}
};
bool ValidateCanonicalTile(const FVoxelMeshData& MeshData, FString& OutError);
bool MeasureExploreMesh(
const FVoxelMeshData& MeshData,
FExploreMeshMetrics& OutMetrics,
FString& OutError);
bool FindMouths(
const UVoxelStrateManager& Manager,
int32 TargetSlot,
FVector& OutArrival,
FVector& OutDeparture,
int32& OutArrivalCount,
int32& OutDepartureCount,
const FVoxelPassage*& OutArrivalPassage,
const FVoxelPassage*& OutDeparturePassage)
{
OutArrival = FVector::ZeroVector;
OutDeparture = FVector::ZeroVector;
OutArrivalCount = 0;
OutDepartureCount = 0;
OutArrivalPassage = nullptr;
OutDeparturePassage = nullptr;
for (const FVoxelPassage& Passage : Manager.GetPassages())
{
if (Passage.LowerStrateIndex == TargetSlot
&& Passage.UpperStrateIndex + 1 == TargetSlot)
{
if (OutArrivalCount == 0)
{
OutArrival = Passage.LowerPoint;
OutArrivalPassage = &Passage;
}
++OutArrivalCount;
}
if (Passage.UpperStrateIndex == TargetSlot
&& Passage.LowerStrateIndex == TargetSlot + 1)
{
if (OutDepartureCount == 0)
{
OutDeparture = Passage.UpperPoint;
OutDeparturePassage = &Passage;
}
++OutDepartureCount;
}
}
return OutArrivalCount > 0 && OutDepartureCount > 0;
}
struct FExploreCameraBasis
{
FVector Forward = FVector::ForwardVector;
FVector Right = FVector::RightVector;
FVector Up = FVector::UpVector;
float Aspect = 1.0f;
float TanHalfFov = 1.0f;
};
bool BuildCameraBasis(
const FVector& Position,
const FVector& Target,
float FieldOfViewDegrees,
int32 Width,
int32 Height,
FExploreCameraBasis& OutBasis)
{
OutBasis.Forward = (Target - Position).GetSafeNormal();
if (OutBasis.Forward.IsNearlyZero())
{
return false;
}
OutBasis.Right = FVector::CrossProduct(OutBasis.Forward, FVector::UpVector).GetSafeNormal();
if (OutBasis.Right.IsNearlyZero())
{
OutBasis.Right = FVector::CrossProduct(OutBasis.Forward, FVector::RightVector).GetSafeNormal();
}
OutBasis.Up = FVector::CrossProduct(OutBasis.Right, OutBasis.Forward).GetSafeNormal();
OutBasis.Aspect = static_cast<float>(Width) / static_cast<float>(Height);
OutBasis.TanHalfFov = FMath::Tan(FMath::DegreesToRadians(FieldOfViewDegrees * 0.5f));
return OutBasis.Aspect > 0.0f && OutBasis.TanHalfFov > 0.0f;
}
void PutPixel(TArray<FColor>& Pixels, int32 Width, int32 Height, int32 X, int32 Y, const FColor& Color)
{
if (X >= 0 && X < Width && Y >= 0 && Y < Height)
{
Pixels[X + Width * Y] = Color;
}
}
void DrawLine(
TArray<FColor>& Pixels,
int32 Width,
int32 Height,
int32 X0,
int32 Y0,
int32 X1,
int32 Y1,
const FColor& Color)
{
int32 DX = FMath::Abs(X1 - X0);
const int32 SX = X0 < X1 ? 1 : -1;
int32 DY = -FMath::Abs(Y1 - Y0);
const int32 SY = Y0 < Y1 ? 1 : -1;
int32 Error = DX + DY;
for (;;)
{
PutPixel(Pixels, Width, Height, X0, Y0, Color);
if (X0 == X1 && Y0 == Y1)
{
break;
}
const int32 DoubleError = 2 * Error;
if (DoubleError >= DY)
{
Error += DY;
X0 += SX;
}
if (DoubleError <= DX)
{
Error += DX;
Y0 += SY;
}
}
}
bool ProjectPoint(
const FVector& Point,
const FVector& CameraPosition,
const FExploreCameraBasis& Basis,
int32 Width,
int32 Height,
FIntPoint& OutPixel)
{
const FVector Relative = Point - CameraPosition;
const float Depth = FVector::DotProduct(Relative, Basis.Forward);
if (Depth <= KINDA_SMALL_NUMBER)
{
return false;
}
const float NdcX = FVector::DotProduct(Relative, Basis.Right)
/ (Depth * Basis.Aspect * Basis.TanHalfFov);
const float NdcY = FVector::DotProduct(Relative, Basis.Up)
/ (Depth * Basis.TanHalfFov);
OutPixel.X = FMath::RoundToInt((NdcX * 0.5f + 0.5f) * static_cast<float>(Width - 1));
OutPixel.Y = FMath::RoundToInt((1.0f - (NdcY * 0.5f + 0.5f))
* static_cast<float>(Height - 1));
return NdcX >= -1.0f && NdcX <= 1.0f && NdcY >= -1.0f && NdcY <= 1.0f;
}
void DrawScaleMarker(
TArray<FColor>& Pixels,
int32 Width,
int32 Height,
const FVector& CameraPosition,
const FVector& Target,
const FExploreCameraBasis& Basis,
bool& bOutProjected)
{
constexpr float HumanHeightVoxels = 2.0f
* FVoxelPlayerCapsuleConstants::HalfHeightVoxels;
const FVector MarkerBase = FVector(
Target.X,
Target.Y,
Target.Z - FVoxelPlayerCapsuleConstants::HalfHeightVoxels)
- Basis.Right * 18.0f
+ Basis.Forward * 48.0f;
const FVector MarkerTop = MarkerBase + FVector(0.0f, 0.0f, HumanHeightVoxels);
FIntPoint BasePixel = FIntPoint::ZeroValue;
FIntPoint TopPixel = FIntPoint::ZeroValue;
bOutProjected = ProjectPoint(
MarkerBase, CameraPosition, Basis, Width, Height, BasePixel)
&& ProjectPoint(MarkerTop, CameraPosition, Basis, Width, Height, TopPixel);
if (!bOutProjected)
{
// The world-space marker is normally in frame by construction. Keep a visible fallback
// for unusual camera/FOV overrides, while the JSON records that it was not projected.
BasePixel = FIntPoint(12, Height - 12);
TopPixel = FIntPoint(12, FMath::Max(12, Height - 12 - Height / 5));
}
const FColor MarkerColor(255, 220, 32, 255);
DrawLine(Pixels, Width, Height, BasePixel.X, BasePixel.Y, TopPixel.X, TopPixel.Y, MarkerColor);
const int32 TickHalfWidth = 5;
for (const FIntPoint& Tick : { BasePixel, TopPixel })
{
DrawLine(
Pixels,
Width,
Height,
Tick.X - TickHalfWidth,
Tick.Y,
Tick.X + TickHalfWidth,
Tick.Y,
MarkerColor);
}
}
bool SavePng(
const FString& FilePath,
const TArray<FColor>& Pixels,
int32 Width,
int32 Height,
FString& OutError)
{
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(
FName(TEXT("ImageWrapper")));
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(
EImageFormat::PNG);
if (!ImageWrapper.IsValid()
|| !ImageWrapper->SetRaw(
Pixels.GetData(),
static_cast<int64>(Pixels.Num()) * sizeof(FColor),
Width,
Height,
ERGBFormat::BGRA,
8))
{
OutError = TEXT("ImageWrapper could not accept the BGRA render buffer.");
return false;
}
const TArray64<uint8>& Compressed = ImageWrapper->GetCompressed(100);
if (!FFileHelper::SaveArrayToFile(Compressed, *FilePath))
{
OutError = FString::Printf(TEXT("Could not write PNG '%s'."), *FilePath);
return false;
}
return true;
}
FIntVector ChooseExploreMeshOrigin(
const FExploreArguments& Arguments,
const FExploreWalkOutput* CameraSeedWalk,
int32 MeshSize,
int32 TargetMiddleWorldZ)
{
if (Arguments.bExportTileOverride)
{
return FIntVector(
Arguments.ExportTileX * CHUNK_SIZE,
Arguments.ExportTileY * CHUNK_SIZE,
Arguments.ExportTileZ * CHUNK_SIZE);
}
FVector Centre(0.0f, 0.0f, static_cast<float>(TargetMiddleWorldZ));
if (CameraSeedWalk != nullptr && CameraSeedWalk->CameraSeedPoses.Num() > 0)
{
Centre = CameraSeedWalk->CameraSeedPoses[0];
}
const auto AlignTileOrigin = [MeshSize](float CentreCoordinate)
{
return FMath::FloorToInt(
(CentreCoordinate - 0.5f * static_cast<float>(MeshSize))
/ static_cast<float>(CHUNK_SIZE))
* CHUNK_SIZE;
};
return FIntVector(
AlignTileOrigin(Centre.X),
AlignTileOrigin(Centre.Y),
AlignTileOrigin(Centre.Z));
}
bool AppendCanonicalMesh(
FVoxelMeshData& Destination,
const FVoxelMeshData& Source,
FString& OutError)
{
if (!ValidateCanonicalTile(Source, OutError))
{
return false;
}
const int32 VertexOffset = Destination.Vertices.Num();
for (const int32 Index : Source.Triangles)
{
if (Index < 0 || Index >= Source.Vertices.Num())
{
OutError = TEXT("Canonical mesher returned an out-of-range triangle index.");
return false;
}
}
Destination.Vertices.Append(Source.Vertices);
Destination.Normals.Append(Source.Normals);
Destination.UVs.Append(Source.UVs);
Destination.Colors.Append(Source.Colors);
Destination.Triangles.Reserve(Destination.Triangles.Num() + Source.Triangles.Num());
for (const int32 Index : Source.Triangles)
{
Destination.Triangles.Add(Index + VertexOffset);
}
// A multi-tile aggregate interleaves each tile's ground/cap runs. The CPU rasterizer uses
// flat geometry and the OBJ path is material-neutral, so there is deliberately no invented
// global ceiling run here.
Destination.NumCeilingTriangles = 0;
return true;
}
FString ComputeGeometryHash(const FVoxelMeshData& MeshData)
{
return VoxelForgeGeometryHash::Compute(MeshData);
}
bool EnsureExploreMesh(
const FExploreArguments& Arguments,
FExploreWorld& World,
const FExploreWalkOutput* CameraSeedWalk,
FExploreBudget& Budget,
FString& OutError)
{
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_ExploreMesh);
if (World.bExploreMeshBuilt)
{
OutError = World.ExploreMeshError;
return World.bExploreMeshComplete;
}
World.bExploreMeshBuilt = true;
World.bExploreMeshComplete = false;
World.ExploreMeshError.Reset();
World.ExploreMesh.Clear();
World.ExploreMeshSize = Arguments.ExportSize;
const int32 TilesPerAxis = Arguments.ExportSize / CHUNK_SIZE;
World.ExploreMeshTileCount = TilesPerAxis * TilesPerAxis * TilesPerAxis;
World.ExploreMeshTilesCompleted = 0;
World.ExploreTileTriangleCounts.Init(0, World.ExploreMeshTileCount);
World.ExploreTileGeometryHashes.SetNum(World.ExploreMeshTileCount);
World.ExploreMeshOrigin = ChooseExploreMeshOrigin(
Arguments,
CameraSeedWalk,
World.ExploreMeshSize,
FMath::FloorToInt(0.5f * static_cast<float>(
World.TargetBottomWorldZ + World.TargetTopWorldZ)));
World.ExploreMeshSeconds = 0.0;
World.ExploreMeshPreparationSeconds = 0.0;
World.ExploreMeshPreTileSetupSeconds = 0.0;
World.ExploreMeshMergeSeconds = 0.0;
World.ExploreMeshAccountingRemainderSeconds = 0.0;
World.ExploreMeshHashSeconds = 0.0;
World.ExploreDensityGridSeconds = 0.0;
World.ExploreDensityGridTotalSamples = 0;
World.ExploreDensityGridUniqueSamples = 0;
World.ExploreDensityGridDuplicateSamplesBefore = 0;
World.ExploreDensityGridDuplicateSamplesAfter = 0;
World.bExploreDensityGridReused = false;
World.ExploreMeshTaskWorkerCount = 0;
World.ExploreMeshTaskJobCount = 0;
World.ExploreMeshTaskMinBatchSize = FMath::Clamp(Arguments.MeshMinBatchSize, 1, 64);
World.ExploreMeshTaskMinSeconds = 0.0;
World.ExploreMeshTaskMeanSeconds = 0.0;
World.ExploreMeshTaskMaxSeconds = 0.0;
World.ExploreMeshTaskSumSeconds = 0.0;
World.ExploreMeshTaskWallSeconds = 0.0;
World.ExploreMeshTaskLaunchOverheadSeconds = 0.0;
World.Mesher->SetSharedDensityGrid(nullptr);
World.ExploreSharedDensityGrid.Reset();
if (Budget.ShouldStop(TEXT("mesh")))
{
World.ExploreMeshError = TEXT("The wall-clock budget elapsed before meshing began.");
OutError = World.ExploreMeshError;
return false;
}
// This is the same canonical UVoxelMarchingCubesMesher used by export and runtime chunk
// generation. Historical export deliberately omitted presentation skirts; parity opts into
// the authored runtime settings so the requested comparison has the same mesher inputs.
World.Mesher->bGenerateSkirts = Arguments.bGameRuntimeConfig
? World.Settings->bGenerateSkirts : false;
World.Mesher->SkirtCells = Arguments.bGameRuntimeConfig
? World.Settings->SkirtCells : 2.0f;
World.Mesher->LODOctaveDrop = Arguments.bGameRuntimeConfig
? World.Settings->LODOctaveDrop : 0;
const double MeshStartSeconds = FPlatformTime::Seconds();
if (Budget.ShouldStop(TEXT("mesh")))
{
World.ExploreMeshError = TEXT("The wall-clock budget elapsed before mesh tiles began.");
OutError = World.ExploreMeshError;
World.ExploreMeshSeconds = FPlatformTime::Seconds() - MeshStartSeconds;
return false;
}
// Adjacent tiles ask for the same one-point-halo planes. Build one immutable lattice for
// the whole export when requested, then let each tile copy its local window from it. The
// precomputation is inside MeshStartSeconds on purpose: this is an end-to-end export number,
// not a profiled bucket hiding the cost of producing the cache.
const int32 CellsPerTile = CHUNK_SIZE / Arguments.ExportStep;
const int32 PointsPerTile = CellsPerTile + 3;
const int64 TileGridSamples = static_cast<int64>(PointsPerTile)
* static_cast<int64>(PointsPerTile)
* static_cast<int64>(PointsPerTile);
const int32 SharedGridDim = Arguments.ExportSize / Arguments.ExportStep + 3;
const int64 SharedGridSamples = static_cast<int64>(SharedGridDim)
* static_cast<int64>(SharedGridDim)
* static_cast<int64>(SharedGridDim);
World.ExploreDensityGridTotalSamples = static_cast<int64>(World.ExploreMeshTileCount)
* TileGridSamples;
World.ExploreDensityGridUniqueSamples = SharedGridSamples;
World.ExploreDensityGridDuplicateSamplesBefore = FMath::Max<int64>(
0, World.ExploreDensityGridTotalSamples - SharedGridSamples);
World.ExploreDensityGridDuplicateSamplesAfter =
Arguments.bReuseDensityGrid ? 0 : World.ExploreDensityGridDuplicateSamplesBefore;
const double DensityStageStartSeconds = FPlatformTime::Seconds();
if (Arguments.bReuseDensityGrid)
{
const double DensityGridStartSeconds = FPlatformTime::Seconds();
World.ExploreSharedDensityGrid.OriginVoxels = World.ExploreMeshOrigin
- FIntVector(Arguments.ExportStep);
World.ExploreSharedDensityGrid.Step = Arguments.ExportStep;
World.ExploreSharedDensityGrid.Dim = SharedGridDim;
World.ExploreSharedDensityGrid.Samples.SetNumUninitialized(
SharedGridDim * SharedGridDim * SharedGridDim);
std::atomic<bool> bGridWorkCancelled(false);
const double GridWorkDeadline = Budget.StartSeconds + Budget.LimitSeconds - 0.25;
const int32 GridTileCount = TilesPerAxis;
ParallelFor(
TEXT("VoxelForgeExploreDensityGrid"),
World.ExploreMeshTileCount,
World.ExploreMeshTaskMinBatchSize,
[&](int32 LinearOwnerIndex)
{
if (bGridWorkCancelled.load(std::memory_order_relaxed)
|| FPlatformTime::Seconds() >= GridWorkDeadline)
{
bGridWorkCancelled.store(true, std::memory_order_relaxed);
return;
}
FScopedVoxelStackRegistration StackRegistration;
const int32 GridOctaveBias = (World.Mesher->LODOctaveDrop > 0
&& Arguments.ExportStep > 1)
? World.Mesher->LODOctaveDrop
* (int32)FMath::FloorLog2((uint32)Arguments.ExportStep)
: 0;
const int32 PreviousOctaveBias = VoxelGenLOD::GetThreadOctaveBias();
const int32 PreviousSampleStep = VoxelGenLOD::GetThreadSampleStep();
VoxelGenLOD::SetThreadOctaveBias(GridOctaveBias);
VoxelGenLOD::SetThreadSampleStep(Arguments.ExportStep);
const int32 OwnerX = LinearOwnerIndex % GridTileCount;
const int32 OwnerY = (LinearOwnerIndex / GridTileCount) % GridTileCount;
const int32 OwnerZ = LinearOwnerIndex / (GridTileCount * GridTileCount);
FIntVector PreviousTileOrigin = FIntVector::ZeroValue;
int32 IgnoredPreviousTileStep = 1;
int32 PreviousTileCells = 0;
VoxelGenLOD::GetThreadTileCacheWindow(
PreviousTileOrigin, IgnoredPreviousTileStep, PreviousTileCells);
VoxelGenLOD::SetThreadTileContext(
World.ExploreMeshOrigin
+ FIntVector(OwnerX * CHUNK_SIZE, OwnerY * CHUNK_SIZE, OwnerZ * CHUNK_SIZE),
CellsPerTile);
// Partition the shared lattice into disjoint rectangular owner regions. A
// boundary sample belongs to exactly one owner; tile windows may read it from
// either side. This keeps the generator's spatial locality while guaranteeing
// one evaluation per global lattice point.
const auto OwnerMinGrid = [CellsPerTile](int32 Owner) -> int32
{
return Owner == 0 ? 0 : 1 + Owner * CellsPerTile;
};
const auto OwnerMaxGrid = [CellsPerTile, SharedGridDim, GridTileCount](int32 Owner) -> int32
{
return Owner == GridTileCount - 1
? SharedGridDim
: 1 + (Owner + 1) * CellsPerTile;
};
const int32 GxMin = OwnerMinGrid(OwnerX);
const int32 GxMax = OwnerMaxGrid(OwnerX);
const int32 GyMin = OwnerMinGrid(OwnerY);
const int32 GyMax = OwnerMaxGrid(OwnerY);
const int32 GzMin = OwnerMinGrid(OwnerZ);
const int32 GzMax = OwnerMaxGrid(OwnerZ);
for (int32 Gz = GzMin; Gz < GzMax; ++Gz)
{
for (int32 Gy = GyMin; Gy < GyMax; ++Gy)
{
for (int32 Gx = GxMin; Gx < GxMax; ++Gx)
{
const int32 Index = (Gz * SharedGridDim + Gy) * SharedGridDim + Gx;
World.ExploreSharedDensityGrid.Samples[Index] =
World.Generator->GetDensityAt(
World.ExploreSharedDensityGrid.OriginVoxels.X + Gx * Arguments.ExportStep,
World.ExploreSharedDensityGrid.OriginVoxels.Y + Gy * Arguments.ExportStep,
World.ExploreSharedDensityGrid.OriginVoxels.Z + Gz * Arguments.ExportStep);
}
}
}
VoxelGenLOD::SetThreadOctaveBias(PreviousOctaveBias);
VoxelGenLOD::SetThreadSampleStep(PreviousSampleStep);
VoxelGenLOD::SetThreadTileContext(PreviousTileOrigin, PreviousTileCells);
},
EParallelForFlags::None);
World.ExploreDensityGridSeconds = FPlatformTime::Seconds() - DensityGridStartSeconds;
if (bGridWorkCancelled.load(std::memory_order_relaxed))
{
World.ExploreMeshError = TEXT("The wall-clock budget elapsed while building the shared density grid.");
OutError = World.ExploreMeshError;
World.ExploreMeshSeconds = FPlatformTime::Seconds() - MeshStartSeconds;
return false;
}
World.Mesher->SetSharedDensityGrid(&World.ExploreSharedDensityGrid);
World.bExploreDensityGridReused = true;
}
const double DensityStageEndSeconds = FPlatformTime::Seconds();
World.ExploreMeshPreparationSeconds = DensityStageStartSeconds - MeshStartSeconds;
// Generate each tile through the canonical mesher on worker threads, then append strictly in
// Z/Y/X order. The mesher's scratch buffers and the generator's hot caches are thread-local;
// the fixed merge order keeps the aggregate mesh and all downstream raster/export bytes
// deterministic even though tile completion order is not.
TArray<FVoxelMeshData> TileMeshes;
TileMeshes.SetNum(World.ExploreMeshTileCount);
TArray<FString> TileErrors;
TileErrors.SetNum(World.ExploreMeshTileCount);
TArray<uint8> TileCompleted;
TileCompleted.Init(0, World.ExploreMeshTileCount);
TArray<double> TileDurations;
TileDurations.Init(0.0, World.ExploreMeshTileCount);
TArray<double> TileStartOffsets;
TileStartOffsets.Init(-1.0, World.ExploreMeshTileCount);
TArray<double> TileEndOffsets;
TileEndOffsets.Init(-1.0, World.ExploreMeshTileCount);
TArray<uint32> TileThreadIds;
TileThreadIds.Init(0, World.ExploreMeshTileCount);
std::atomic<bool> bTileWorkCancelled(false);
const double TileWorkDeadline = Budget.StartSeconds + Budget.LimitSeconds - 0.25;
const double ParallelStartSeconds = FPlatformTime::Seconds();
World.ExploreMeshPreTileSetupSeconds = ParallelStartSeconds - DensityStageEndSeconds;
ParallelFor(
TEXT("VoxelForgeExploreMeshTiles"),
World.ExploreMeshTileCount,
World.ExploreMeshTaskMinBatchSize,
[&](int32 LinearTileIndex)
{
if (bTileWorkCancelled.load(std::memory_order_relaxed)
|| FPlatformTime::Seconds() >= TileWorkDeadline)
{
bTileWorkCancelled.store(true, std::memory_order_relaxed);
return;
}
FScopedVoxelStackRegistration StackRegistration;
const double TileStartSeconds = FPlatformTime::Seconds();
TileStartOffsets[LinearTileIndex] = TileStartSeconds - ParallelStartSeconds;
TileThreadIds[LinearTileIndex] = FPlatformTLS::GetCurrentThreadId();
const int32 TileX = LinearTileIndex % TilesPerAxis;
const int32 TileY = (LinearTileIndex / TilesPerAxis) % TilesPerAxis;
const int32 TileZ = LinearTileIndex / (TilesPerAxis * TilesPerAxis);
const FIntVector TileOrigin = World.ExploreMeshOrigin
+ FIntVector(TileX * CHUNK_SIZE, TileY * CHUNK_SIZE, TileZ * CHUNK_SIZE);
TileMeshes[LinearTileIndex] = World.Mesher->GenerateMesh(
TileOrigin,
Arguments.ExportStep,
CHUNK_SIZE / Arguments.ExportStep);
FString TileError;
if (!ValidateCanonicalTile(TileMeshes[LinearTileIndex], TileError))
{
TileErrors[LinearTileIndex] = MoveTemp(TileError);
bTileWorkCancelled.store(true, std::memory_order_relaxed);
const double TileEndSeconds = FPlatformTime::Seconds();
TileEndOffsets[LinearTileIndex] = TileEndSeconds - ParallelStartSeconds;
TileDurations[LinearTileIndex] = TileEndSeconds - TileStartSeconds;
return;
}
TileCompleted[LinearTileIndex] = 1;
const double TileEndSeconds = FPlatformTime::Seconds();
TileEndOffsets[LinearTileIndex] = TileEndSeconds - ParallelStartSeconds;
TileDurations[LinearTileIndex] = TileEndSeconds - TileStartSeconds;
},
EParallelForFlags::None);
const double ParallelEndSeconds = FPlatformTime::Seconds();
TArray<double> CompletedTileDurations;
TSet<uint32> WorkerIds;
double EarliestTileStart = 1.0e30;
double LatestTileEnd = 0.0;
for (int32 TileIndex = 0; TileIndex < World.ExploreMeshTileCount; ++TileIndex)
{
if (!TileCompleted[TileIndex])
{
continue;
}
CompletedTileDurations.Add(TileDurations[TileIndex]);
WorkerIds.Add(TileThreadIds[TileIndex]);
EarliestTileStart = FMath::Min(EarliestTileStart, TileStartOffsets[TileIndex]);
LatestTileEnd = FMath::Max(LatestTileEnd, TileEndOffsets[TileIndex]);
}
if (CompletedTileDurations.Num() > 0)
{
CompletedTileDurations.Sort();
World.ExploreMeshTaskJobCount = CompletedTileDurations.Num();
World.ExploreMeshTaskWorkerCount = WorkerIds.Num();
World.ExploreMeshTaskMinSeconds = CompletedTileDurations[0];
World.ExploreMeshTaskMaxSeconds = CompletedTileDurations.Last();
for (const double Duration : CompletedTileDurations)
{
World.ExploreMeshTaskSumSeconds += Duration;
}
World.ExploreMeshTaskMeanSeconds = World.ExploreMeshTaskSumSeconds
/ static_cast<double>(CompletedTileDurations.Num());
}
World.ExploreMeshTaskWallSeconds = ParallelEndSeconds - ParallelStartSeconds;
if (EarliestTileStart < 1.0e30)
{
World.ExploreMeshTaskLaunchOverheadSeconds = FMath::Max(0.0, EarliestTileStart)
+ FMath::Max(0.0, World.ExploreMeshTaskWallSeconds - LatestTileEnd);
}
int32 CompletedTiles = 0;
FString MeshError;
for (; CompletedTiles < World.ExploreMeshTileCount; ++CompletedTiles)
{
if (!TileCompleted[CompletedTiles])
{
break;
}
if (!AppendCanonicalMesh(
World.ExploreMesh,
TileMeshes[CompletedTiles],
MeshError))
{
World.ExploreMeshError = MeshError;
OutError = MeshError;
World.ExploreMeshTilesCompleted = CompletedTiles;
World.ExploreMeshSeconds = FPlatformTime::Seconds() - MeshStartSeconds;
return false;
}
World.ExploreTileTriangleCounts[CompletedTiles] =
TileMeshes[CompletedTiles].Triangles.Num() / 3;
World.ExploreTileGeometryHashes[CompletedTiles] =
ComputeGeometryHash(TileMeshes[CompletedTiles]);
World.ExploreMeshTilesCompleted = CompletedTiles + 1;
}
if (CompletedTiles < World.ExploreMeshTileCount)
{
if (!TileErrors[CompletedTiles].IsEmpty())
{
World.ExploreMeshError = TileErrors[CompletedTiles];
OutError = World.ExploreMeshError;
World.ExploreMeshSeconds = FPlatformTime::Seconds() - MeshStartSeconds;
return false;
}
Budget.ShouldStop(TEXT("mesh"));
World.ExploreMeshError = FString::Printf(
TEXT("Mesh was truncated after %d/%d canonical tiles."),
CompletedTiles,
World.ExploreMeshTileCount);
OutError = World.ExploreMeshError;
World.ExploreMeshSeconds = FPlatformTime::Seconds() - MeshStartSeconds;
return false;
}
const double MeshEndSeconds = FPlatformTime::Seconds();
World.ExploreMeshSeconds = MeshEndSeconds - MeshStartSeconds;
World.ExploreMeshMergeSeconds = MeshEndSeconds - ParallelEndSeconds;
const double MeshStageSumSeconds = World.ExploreMeshPreparationSeconds
+ World.ExploreDensityGridSeconds
+ World.ExploreMeshPreTileSetupSeconds
+ World.ExploreMeshTaskWallSeconds
+ World.ExploreMeshMergeSeconds;
World.ExploreMeshAccountingRemainderSeconds =
World.ExploreMeshSeconds - MeshStageSumSeconds;
World.bExploreMeshComplete = true;
const double GeometryHashStartSeconds = FPlatformTime::Seconds();
World.ExploreGeometryHash = ComputeGeometryHash(World.ExploreMesh);
World.ExploreMeshHashSeconds = FPlatformTime::Seconds() - GeometryHashStartSeconds;
OutError.Reset();
return true;
}
float ExploreAxisValue(const FVector& Value, int32 Axis)
{
switch (Axis)
{
case 1: return Value.Y;
case 2: return Value.Z;
default: return Value.X;
}
}
FVector ExploreTriangleMin(const FVector& A, const FVector& B, const FVector& C)
{
return FVector(
FMath::Min3(A.X, B.X, C.X),
FMath::Min3(A.Y, B.Y, C.Y),
FMath::Min3(A.Z, B.Z, C.Z));
}
FVector ExploreTriangleMax(const FVector& A, const FVector& B, const FVector& C)
{
return FVector(
FMath::Max3(A.X, B.X, C.X),
FMath::Max3(A.Y, B.Y, C.Y),
FMath::Max3(A.Z, B.Z, C.Z));
}
bool EnsureExploreAcceleration(
const FExploreArguments& Arguments,
FExploreWorld& World,
FExploreBudget& Budget,
FString& OutError)
{
if (World.bExploreAccelerationBuilt)
{
OutError.Reset();
return true;
}
World.ExploreTriangles.Reset();
World.ExploreTriangleOrder.Reset();
World.ExploreBvhNodes.Reset();
World.ExploreBvhRoot = INDEX_NONE;
World.ExploreAccelerationSeconds = 0.0;
const double AccelerationStartSeconds = FPlatformTime::Seconds();
if (!World.bExploreMeshComplete)
{
OutError = TEXT("Cannot build mesh acceleration before the canonical mesh is complete.");
return false;
}
if (Budget.ShouldStop(TEXT("mesh-acceleration")))
{
OutError = TEXT("The wall-clock budget elapsed before mesh acceleration began.");
return false;
}
const FVoxelMeshData& Mesh = World.ExploreMesh;
if (Mesh.Triangles.Num() % 3 != 0)
{
OutError = TEXT("Canonical mesh triangle index data is not divisible by three.");
return false;
}
World.ExploreTriangles.Reserve(Mesh.Triangles.Num() / 3);
for (int32 TriangleIndex = 0;
TriangleIndex + 2 < Mesh.Triangles.Num();
TriangleIndex += 3)
{
if ((TriangleIndex & 16383) == 0
&& Budget.ShouldStop(TEXT("mesh-acceleration")))
{
OutError = TEXT("Mesh acceleration was truncated before its triangle cache completed.");
World.ExploreTriangles.Reset();
return false;
}
const int32 IndexA = Mesh.Triangles[TriangleIndex];
const int32 IndexB = Mesh.Triangles[TriangleIndex + 1];
const int32 IndexC = Mesh.Triangles[TriangleIndex + 2];
if (!Mesh.Vertices.IsValidIndex(IndexA)
|| !Mesh.Vertices.IsValidIndex(IndexB)
|| !Mesh.Vertices.IsValidIndex(IndexC))
{
OutError = TEXT("Canonical mesh acceleration saw an out-of-range triangle index.");
World.ExploreTriangles.Reset();
return false;
}
const FVector A = Mesh.Vertices[IndexA] / VOXEL_SIZE;
const FVector B = Mesh.Vertices[IndexB] / VOXEL_SIZE;
const FVector C = Mesh.Vertices[IndexC] / VOXEL_SIZE;
if (!FMath::IsFinite(A.X) || !FMath::IsFinite(A.Y) || !FMath::IsFinite(A.Z)
|| !FMath::IsFinite(B.X) || !FMath::IsFinite(B.Y) || !FMath::IsFinite(B.Z)
|| !FMath::IsFinite(C.X) || !FMath::IsFinite(C.Y) || !FMath::IsFinite(C.Z))
{
OutError = TEXT("Canonical mesh acceleration saw a non-finite vertex.");
World.ExploreTriangles.Reset();
return false;
}
const FVector Cross = FVector::CrossProduct(B - A, C - A);
if (Cross.SizeSquared() <= SMALL_NUMBER)
{
// Degenerate canonical triangles have no raster contribution. Keep them out of the
// acceleration structure without changing the source mesh or its export semantics.
continue;
}
FExploreMeshTriangle& Triangle = World.ExploreTriangles.AddDefaulted_GetRef();
Triangle.A = A;
Triangle.B = B;
Triangle.C = C;
Triangle.NormalA = Mesh.Normals[IndexA].GetSafeNormal();
Triangle.NormalB = Mesh.Normals[IndexB].GetSafeNormal();
Triangle.NormalC = Mesh.Normals[IndexC].GetSafeNormal();
Triangle.Min = ExploreTriangleMin(A, B, C);
Triangle.Max = ExploreTriangleMax(A, B, C);
Triangle.Centre = (A + B + C) / 3.0f;
Triangle.Normal = Cross.GetSafeNormal();
}
if (World.ExploreTriangles.Num() == 0)
{
// A valid empty mesh is still a complete mesh render: every pixel remains the explicit
// background colour. The root sentinel lets the rasterizer handle this deterministically.
World.ExploreAccelerationSeconds = FPlatformTime::Seconds() - AccelerationStartSeconds;
World.bExploreAccelerationBuilt = true;
OutError.Reset();
return true;
}
World.ExploreTriangleOrder.SetNumUninitialized(World.ExploreTriangles.Num());
for (int32 TriangleIndex = 0;
TriangleIndex < World.ExploreTriangleOrder.Num();
++TriangleIndex)
{
World.ExploreTriangleOrder[TriangleIndex] = TriangleIndex;
}
World.ExploreBvhNodes.Reserve(World.ExploreTriangles.Num() / 4 + 2);
bool bBuildFailed = false;
TFunction<int32(int32, int32)> BuildNode;
BuildNode = [&](int32 Start, int32 Count) -> int32
{
if (bBuildFailed || Count <= 0)
{
return INDEX_NONE;
}
if (Budget.ShouldStop(TEXT("mesh-acceleration")))
{
bBuildFailed = true;
return INDEX_NONE;
}
const int32 NodeIndex = World.ExploreBvhNodes.AddDefaulted();
FExploreMeshBvhNode Node;
Node.Min = FVector(FLT_MAX, FLT_MAX, FLT_MAX);
Node.Max = FVector(-FLT_MAX, -FLT_MAX, -FLT_MAX);
FVector CentroidMin = FVector(FLT_MAX, FLT_MAX, FLT_MAX);
FVector CentroidMax = FVector(-FLT_MAX, -FLT_MAX, -FLT_MAX);
for (int32 Offset = 0; Offset < Count; ++Offset)
{
const int32 TriangleIndex = World.ExploreTriangleOrder[Start + Offset];
const FExploreMeshTriangle& Triangle = World.ExploreTriangles[TriangleIndex];
Node.Min.X = FMath::Min(Node.Min.X, Triangle.Min.X);
Node.Min.Y = FMath::Min(Node.Min.Y, Triangle.Min.Y);
Node.Min.Z = FMath::Min(Node.Min.Z, Triangle.Min.Z);
Node.Max.X = FMath::Max(Node.Max.X, Triangle.Max.X);
Node.Max.Y = FMath::Max(Node.Max.Y, Triangle.Max.Y);
Node.Max.Z = FMath::Max(Node.Max.Z, Triangle.Max.Z);
CentroidMin.X = FMath::Min(CentroidMin.X, Triangle.Centre.X);
CentroidMin.Y = FMath::Min(CentroidMin.Y, Triangle.Centre.Y);
CentroidMin.Z = FMath::Min(CentroidMin.Z, Triangle.Centre.Z);
CentroidMax.X = FMath::Max(CentroidMax.X, Triangle.Centre.X);
CentroidMax.Y = FMath::Max(CentroidMax.Y, Triangle.Centre.Y);
CentroidMax.Z = FMath::Max(CentroidMax.Z, Triangle.Centre.Z);
}
constexpr int32 LeafTriangleCount = 8;
if (Count <= LeafTriangleCount)
{
Node.FirstTriangle = Start;
Node.TriangleCount = Count;
World.ExploreBvhNodes[NodeIndex] = Node;
return NodeIndex;
}
const FVector CentroidExtent = CentroidMax - CentroidMin;
int32 SplitAxis = 0;
if (CentroidExtent.Y > CentroidExtent.X
&& CentroidExtent.Y >= CentroidExtent.Z)
{
SplitAxis = 1;
}
else if (CentroidExtent.Z > CentroidExtent.X
&& CentroidExtent.Z > CentroidExtent.Y)
{
SplitAxis = 2;
}
int32* OrderBegin = World.ExploreTriangleOrder.GetData() + Start;
int32* OrderEnd = OrderBegin + Count;
std::sort(
OrderBegin,
OrderEnd,
[&](int32 LeftIndex, int32 RightIndex)
{
const float LeftValue = ExploreAxisValue(
World.ExploreTriangles[LeftIndex].Centre, SplitAxis);
const float RightValue = ExploreAxisValue(
World.ExploreTriangles[RightIndex].Centre, SplitAxis);
if (LeftValue != RightValue)
{
return LeftValue < RightValue;
}
return LeftIndex < RightIndex;
});
const int32 LeftCount = Count / 2;
const int32 RightCount = Count - LeftCount;
const int32 LeftNode = BuildNode(Start, LeftCount);
const int32 RightNode = BuildNode(Start + LeftCount, RightCount);
if (LeftNode == INDEX_NONE || RightNode == INDEX_NONE)
{
bBuildFailed = true;
return INDEX_NONE;
}
Node.Left = LeftNode;
Node.Right = RightNode;
World.ExploreBvhNodes[NodeIndex] = Node;
return NodeIndex;
};
World.ExploreBvhRoot = BuildNode(0, World.ExploreTriangles.Num());
if (bBuildFailed || World.ExploreBvhRoot == INDEX_NONE || Budget.bTruncated)
{
World.ExploreTriangles.Reset();
World.ExploreTriangleOrder.Reset();
World.ExploreBvhNodes.Reset();
World.ExploreBvhRoot = INDEX_NONE;
OutError = TEXT("Mesh acceleration was truncated before its BVH completed.");
return false;
}
World.ExploreAccelerationSeconds = FPlatformTime::Seconds() - AccelerationStartSeconds;
World.bExploreAccelerationBuilt = true;
OutError.Reset();
return true;
}
bool ExploreRayAabb(
const FVector& Origin,
const FVector& Direction,
const FVector& BoundsMin,
const FVector& BoundsMax,
float MaxDistance,
float& OutNear)
{
float NearDistance = 0.0f;
float FarDistance = MaxDistance;
for (int32 Axis = 0; Axis < 3; ++Axis)
{
const float OriginComponent = ExploreAxisValue(Origin, Axis);
const float DirectionComponent = ExploreAxisValue(Direction, Axis);
const float MinComponent = ExploreAxisValue(BoundsMin, Axis);
const float MaxComponent = ExploreAxisValue(BoundsMax, Axis);
if (FMath::Abs(DirectionComponent) <= KINDA_SMALL_NUMBER)
{
if (OriginComponent < MinComponent || OriginComponent > MaxComponent)
{
return false;
}
continue;
}
const float InverseDirection = 1.0f / DirectionComponent;
float AxisNear = (MinComponent - OriginComponent) * InverseDirection;
float AxisFar = (MaxComponent - OriginComponent) * InverseDirection;
if (AxisNear > AxisFar)
{
Swap(AxisNear, AxisFar);
}
NearDistance = FMath::Max(NearDistance, AxisNear);
FarDistance = FMath::Min(FarDistance, AxisFar);
if (NearDistance > FarDistance)
{
return false;
}
}
OutNear = NearDistance;
return true;
}
bool ExploreRayTriangle(
const FVector& Origin,
const FVector& Direction,
const FExploreMeshTriangle& Triangle,
float MaxDistance,
float& OutDistance,
float& OutU,
float& OutV)
{
const FVector Edge1 = Triangle.B - Triangle.A;
const FVector Edge2 = Triangle.C - Triangle.A;
const FVector Perpendicular = FVector::CrossProduct(Direction, Edge2);
const float Determinant = FVector::DotProduct(Edge1, Perpendicular);
if (FMath::Abs(Determinant) <= KINDA_SMALL_NUMBER)
{
return false;
}
const float InverseDeterminant = 1.0f / Determinant;
const FVector ToOrigin = Origin - Triangle.A;
const float U = FVector::DotProduct(ToOrigin, Perpendicular) * InverseDeterminant;
if (U < 0.0f || U > 1.0f)
{
return false;
}
const FVector CrossOrigin = FVector::CrossProduct(ToOrigin, Edge1);
const float V = FVector::DotProduct(Direction, CrossOrigin) * InverseDeterminant;
if (V < 0.0f || U + V > 1.0f)
{
return false;
}
const float Distance = FVector::DotProduct(Edge2, CrossOrigin) * InverseDeterminant;
if (Distance <= 0.01f || Distance >= MaxDistance)
{
return false;
}
OutDistance = Distance;
OutU = U;
OutV = V;
return true;
}
bool RasterizeExploreMesh(
const FExploreArguments& Arguments,
FExploreWorld& World,
const FVector& CameraPosition,
const FVector& Target,
FExploreBudget& Budget,
TArray<FColor>& OutPixels,
int32& OutHitPixels,
bool& bOutTruncated)
{
OutPixels.Init(FColor(12, 18, 28, 255), Arguments.RenderWidth * Arguments.RenderHeight);
OutHitPixels = 0;
bOutTruncated = false;
if (!World.bExploreAccelerationBuilt)
{
FString AccelerationError;
if (!EnsureExploreAcceleration(Arguments, World, Budget, AccelerationError))
{
if (Budget.bTruncated)
{
bOutTruncated = true;
return true;
}
return false;
}
}
FExploreCameraBasis Basis;
if (!BuildCameraBasis(
CameraPosition,
Target,
70.0f,
Arguments.RenderWidth,
Arguments.RenderHeight,
Basis))
{
return false;
}
const FVector KeyLightDirection = FVector(-0.35f, -0.45f, 0.82f).GetSafeNormal();
const FVector FillLightDirection = FVector(0.65f, 0.30f, 0.70f).GetSafeNormal();
constexpr float AmbientFill = 0.62f;
constexpr float KeyLightStrength = 0.50f;
constexpr float FillLightStrength = 0.24f;
for (int32 PixelY = 0; PixelY < Arguments.RenderHeight; ++PixelY)
{
if (Budget.ShouldStop(TEXT("raster")))
{
bOutTruncated = true;
return true;
}
for (int32 PixelX = 0; PixelX < Arguments.RenderWidth; ++PixelX)
{
const float NormalizedX = (2.0f * (static_cast<float>(PixelX) + 0.5f)
/ static_cast<float>(Arguments.RenderWidth)) - 1.0f;
const float NormalizedY = 1.0f - (2.0f * (static_cast<float>(PixelY) + 0.5f)
/ static_cast<float>(Arguments.RenderHeight));
const FVector Direction = (
Basis.Forward
+ Basis.Right * (NormalizedX * Basis.Aspect * Basis.TanHalfFov)
+ Basis.Up * (NormalizedY * Basis.TanHalfFov)).GetSafeNormal();
float ClosestDistance = Arguments.RenderMaxDistanceVoxels;
int32 ClosestTriangleIndex = INDEX_NONE;
float ClosestU = 0.0f;
float ClosestV = 0.0f;
int32 NodeStack[256];
int32 StackCount = 0;
if (World.ExploreBvhRoot != INDEX_NONE)
{
NodeStack[StackCount++] = World.ExploreBvhRoot;
}
while (StackCount > 0)
{
const int32 NodeIndex = NodeStack[--StackCount];
if (!World.ExploreBvhNodes.IsValidIndex(NodeIndex))
{
continue;
}
const FExploreMeshBvhNode& Node = World.ExploreBvhNodes[NodeIndex];
float NodeNear = 0.0f;
if (!ExploreRayAabb(
CameraPosition,
Direction,
Node.Min,
Node.Max,
ClosestDistance,
NodeNear))
{
continue;
}
if (Node.IsLeaf())
{
for (int32 Offset = 0; Offset < Node.TriangleCount; ++Offset)
{
const int32 OrderIndex = Node.FirstTriangle + Offset;
if (!World.ExploreTriangleOrder.IsValidIndex(OrderIndex))
{
continue;
}
const int32 TriangleIndex = World.ExploreTriangleOrder[OrderIndex];
if (!World.ExploreTriangles.IsValidIndex(TriangleIndex))
{
continue;
}
float TriangleDistance = 0.0f;
float TriangleU = 0.0f;
float TriangleV = 0.0f;
if (ExploreRayTriangle(
CameraPosition,
Direction,
World.ExploreTriangles[TriangleIndex],
ClosestDistance,
TriangleDistance,
TriangleU,
TriangleV))
{
ClosestDistance = TriangleDistance;
ClosestTriangleIndex = TriangleIndex;
ClosestU = TriangleU;
ClosestV = TriangleV;
}
}
continue;
}
const int32 LeftNodeIndex = Node.Left;
const int32 RightNodeIndex = Node.Right;
float LeftNear = 0.0f;
float RightNear = 0.0f;
const bool bHitLeft = World.ExploreBvhNodes.IsValidIndex(LeftNodeIndex)
&& ExploreRayAabb(
CameraPosition,
Direction,
World.ExploreBvhNodes[LeftNodeIndex].Min,
World.ExploreBvhNodes[LeftNodeIndex].Max,
ClosestDistance,
LeftNear);
const bool bHitRight = World.ExploreBvhNodes.IsValidIndex(RightNodeIndex)
&& ExploreRayAabb(
CameraPosition,
Direction,
World.ExploreBvhNodes[RightNodeIndex].Min,
World.ExploreBvhNodes[RightNodeIndex].Max,
ClosestDistance,
RightNear);
if (bHitLeft && bHitRight)
{
if (LeftNear < RightNear)
{
if (StackCount + 2 <= UE_ARRAY_COUNT(NodeStack))
{
NodeStack[StackCount++] = RightNodeIndex;
NodeStack[StackCount++] = LeftNodeIndex;
}
}
else if (StackCount + 2 <= UE_ARRAY_COUNT(NodeStack))
{
NodeStack[StackCount++] = LeftNodeIndex;
NodeStack[StackCount++] = RightNodeIndex;
}
}
else if (bHitLeft && StackCount < UE_ARRAY_COUNT(NodeStack))
{
NodeStack[StackCount++] = LeftNodeIndex;
}
else if (bHitRight && StackCount < UE_ARRAY_COUNT(NodeStack))
{
NodeStack[StackCount++] = RightNodeIndex;
}
}
if (ClosestTriangleIndex == INDEX_NONE)
{
continue;
}
const FExploreMeshTriangle& Triangle = World.ExploreTriangles[ClosestTriangleIndex];
FVector ShadingNormal = (
Triangle.NormalA * (1.0f - ClosestU - ClosestV)
+ Triangle.NormalB * ClosestU
+ Triangle.NormalC * ClosestV).GetSafeNormal();
if (ShadingNormal.IsNearlyZero())
{
// The canonical mesher supplies non-zero gradient normals. Keep a defensive
// fallback for malformed mesh data without reintroducing camera-facing flips.
ShadingNormal = Triangle.Normal;
}
const float Key = FMath::Max(
0.0f, FVector::DotProduct(ShadingNormal, KeyLightDirection));
const float Fill = FMath::Max(
0.0f, FVector::DotProduct(ShadingNormal, FillLightDirection));
const float Brightness = FMath::Clamp(
AmbientFill + KeyLightStrength * Key + FillLightStrength * Fill,
0.0f,
1.25f);
const int32 PixelIndex = PixelX + Arguments.RenderWidth * PixelY;
OutPixels[PixelIndex] = FColor(
static_cast<uint8>(FMath::Clamp(100.0f * Brightness, 0.0f, 255.0f)),
static_cast<uint8>(FMath::Clamp(128.0f * Brightness, 0.0f, 255.0f)),
static_cast<uint8>(FMath::Clamp(160.0f * Brightness, 0.0f, 255.0f)),
255);
++OutHitPixels;
}
}
return true;
}
bool RunRender(
const FExploreArguments& Arguments,
FExploreWorld& World,
const FExploreWalkOutput& CameraSeedWalk,
FExploreRenderOutput& OutOutput,
FExploreBudget& Budget)
{
OutOutput = FExploreRenderOutput();
OutOutput.Width = Arguments.RenderWidth;
OutOutput.Height = Arguments.RenderHeight;
OutOutput.FixedStepVoxels = Arguments.RenderStepVoxels;
OutOutput.FixedStepMeters = Arguments.RenderStepVoxels
* FVoxelPlayerCapsuleConstants::VoxelSizeMeters;
OutOutput.BisectionIterations = 0;
OutOutput.MaxDistanceVoxels = Arguments.RenderMaxDistanceVoxels;
OutOutput.EstimatedFixedDensitySamples = 0;
OutOutput.LegacyTwoViewEstimatedDensitySamples = static_cast<int64>(Arguments.RenderWidth)
* static_cast<int64>(Arguments.RenderHeight)
* 3ll
* static_cast<int64>(FMath::Max(
1,
FMath::CeilToInt(
Arguments.RenderMaxDistanceVoxels / Arguments.RenderStepVoxels)))
* 2ll;
OutOutput.RequestedViewpointCount = 8;
OutOutput.StepRationale = TEXT(
"The renderer meshes the bounded region once with UVoxelMarchingCubesMesher, then CPU "
"rasterises its canonical triangles through a deterministic BVH-assisted depth pass. "
"-renderstep is retained only for legacy invocation compatibility; no render pixel samples "
"the density field.");
OutOutput.CameraSeedPolicy = TEXT(
"Eight deterministic viewpoints are selected from the one shared player-fit mask. Their "
"eye-height origins are density-checked, and their azimuths are rotated in fixed 45-degree "
"increments so every image is from inside walkable space.");
FString MeshError;
if (!EnsureExploreMesh(Arguments, World, &CameraSeedWalk, Budget, MeshError))
{
OutOutput.MeshSeconds = World.ExploreMeshSeconds;
OutOutput.bMeshComplete = World.bExploreMeshComplete;
if (Budget.bTruncated)
{
OutOutput.Status = TEXT("truncated");
OutOutput.bTruncated = true;
OutOutput.RefusalReason = MeshError;
}
else
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = MeshError;
}
return false;
}
OutOutput.MeshSeconds = World.ExploreMeshSeconds;
OutOutput.bMeshComplete = true;
FString AccelerationError;
if (!EnsureExploreAcceleration(Arguments, World, Budget, AccelerationError))
{
OutOutput.MeshAccelerationSeconds = World.ExploreAccelerationSeconds;
if (Budget.bTruncated)
{
OutOutput.Status = TEXT("truncated");
OutOutput.bTruncated = true;
OutOutput.RefusalReason = AccelerationError;
}
else
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = AccelerationError;
}
return false;
}
OutOutput.MeshAccelerationSeconds = World.ExploreAccelerationSeconds;
const double RasterStartSeconds = FPlatformTime::Seconds();
TArray<TPair<FVector, FString>, TInlineAllocator<8>> CameraSeeds;
const auto AddCameraSeed = [&](bool bAvailable, const FVector& Position, const TCHAR* Source)
{
if (!bAvailable
|| !FMath::IsFinite(Position.X)
|| !FMath::IsFinite(Position.Y)
|| !FMath::IsFinite(Position.Z))
{
return;
}
// The walk mask is authoritative, but this guard catches stale/corrupt report data before
// a render can silently start inside rock.
const float Density = World.Generator->GetDensityAt(Position.X, Position.Y, Position.Z);
if (!FMath::IsFinite(Density) || !(Density > 0.0f))
{
return;
}
for (const TPair<FVector, FString>& Existing : CameraSeeds)
{
if (Existing.Key.Equals(Position, 0.001f))
{
return;
}
}
CameraSeeds.Emplace(Position, FString(Source));
};
if (Arguments.bRenderCameraOverride)
{
AddCameraSeed(true, Arguments.RenderCamera, TEXT("render_camera_override"));
}
for (int32 SeedIndex = 0; SeedIndex < CameraSeedWalk.CameraSeedPoses.Num(); ++SeedIndex)
{
const FString Source = CameraSeedWalk.CameraSeedSources.IsValidIndex(SeedIndex)
? CameraSeedWalk.CameraSeedSources[SeedIndex]
: FString::Printf(TEXT("player_fit_%02d"), SeedIndex);
AddCameraSeed(
true,
CameraSeedWalk.CameraSeedPoses[SeedIndex],
*Source);
if (CameraSeeds.Num() >= OutOutput.RequestedViewpointCount)
{
break;
}
}
if (CameraSeeds.Num() == 0)
{
OutOutput.Status = TEXT("refused");
OutOutput.RefusalReason = TEXT(
"The player-fit seed pass produced no density-positive pose; refusing an inside-rock camera.");
return false;
}
// A small room can contain fewer than eight distinct lattice cells. Reusing the same valid
// player-fit origin with another deterministic angle is still a valid viewpoint and keeps
// the contract (all camera origins are inside walkable space) without fabricating positions.
while (CameraSeeds.Num() < OutOutput.RequestedViewpointCount)
{
const TPair<FVector, FString>& Anchor = CameraSeeds[0];
CameraSeeds.Emplace(
Anchor.Key,
FString::Printf(TEXT("%s_angle_%02d"), *Anchor.Value, CameraSeeds.Num()));
}
OutOutput.CameraSeedCount = CameraSeeds.Num();
OutOutput.bAllCamerasPlayerFit = true;
FVector RouteFocus = FVector::ZeroVector;
int32 RouteFocusCount = 0;
if (Arguments.bRenderTargetOverride)
{
RouteFocus = Arguments.RenderTarget;
RouteFocusCount = 1;
}
else if (CameraSeedWalk.bHasArrival)
{
RouteFocus += CameraSeedWalk.ArrivalVoxels;
++RouteFocusCount;
}
if (CameraSeedWalk.bHasDeparture)
{
RouteFocus += CameraSeedWalk.DepartureVoxels;
++RouteFocusCount;
}
if (RouteFocusCount > 0)
{
RouteFocus /= static_cast<float>(RouteFocusCount);
}
else
{
RouteFocus = CameraSeeds[0].Key;
}
for (int32 ViewIndex = 0; ViewIndex < CameraSeeds.Num(); ++ViewIndex)
{
if (Budget.ShouldStop(TEXT("render")))
{
OutOutput.Status = TEXT("truncated");
OutOutput.bTruncated = true;
OutOutput.RefusalReason = TEXT("The wall-clock budget stopped viewpoint generation.");
return false;
}
const FVector CameraSeedPose = CameraSeeds[ViewIndex].Key;
const FVector CameraPosition = CameraSeedPose
+ FVector::UpVector * (FVoxelPlayerCapsuleConstants::HalfHeightVoxels - 0.5f);
const float CameraDensity = World.Generator->GetDensityAt(
CameraPosition.X, CameraPosition.Y, CameraPosition.Z);
if (!FMath::IsFinite(CameraDensity) || !(CameraDensity > 0.0f))
{
OutOutput.Status = TEXT("refused");
OutOutput.RefusalReason = TEXT(
"The eye-height offset of a player-fit camera entered solid density.");
return false;
}
FVector LookDirection = RouteFocus - CameraPosition;
LookDirection.Z = 0.0f;
LookDirection.Normalize();
if (LookDirection.IsNearlyZero())
{
LookDirection = FVector(
ViewIndex == 1 ? -0.6f : 0.8f,
ViewIndex == 2 ? 0.7f : -0.4f,
0.18f).GetSafeNormal();
}
// Keep the camera at the fitted pose while changing the azimuth for each viewpoint.
LookDirection = LookDirection.RotateAngleAxis(
45.0f * static_cast<float>(ViewIndex),
FVector::UpVector).GetSafeNormal();
const FVector Target = CameraPosition + LookDirection * 96.0f + FVector::UpVector * 8.0f;
const double ViewStartSeconds = FPlatformTime::Seconds();
TArray<FColor> Pixels;
int32 HitPixels = 0;
bool bRasterTruncated = false;
if (!RasterizeExploreMesh(
Arguments,
World,
CameraPosition,
Target,
Budget,
Pixels,
HitPixels,
bRasterTruncated))
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = TEXT("Could not construct a mesh raster camera.");
return false;
}
FExploreCameraBasis Basis;
if (!BuildCameraBasis(
CameraPosition,
Target,
70.0f,
Arguments.RenderWidth,
Arguments.RenderHeight,
Basis))
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = TEXT("Could not construct a render camera basis.");
return false;
}
bool bScaleMarkerProjected = false;
DrawScaleMarker(
Pixels,
Arguments.RenderWidth,
Arguments.RenderHeight,
CameraPosition,
Target,
Basis,
bScaleMarkerProjected);
FExploreRenderFrame& Frame = OutOutput.Frames.AddDefaulted_GetRef();
Frame.FileName = FString::Printf(TEXT("render_%02d.png"), ViewIndex);
Frame.Purpose = CameraSeeds[ViewIndex].Value;
Frame.CameraSeedSource = CameraSeeds[ViewIndex].Value;
Frame.CameraSeedPoseVoxels = CameraSeedPose;
Frame.CameraVoxels = CameraPosition;
Frame.TargetVoxels = Target;
Frame.HitPixels = HitPixels;
Frame.bScaleMarkerProjected = bScaleMarkerProjected;
Frame.bPartial = bRasterTruncated;
FString Error;
if (!SavePng(
FPaths::Combine(Arguments.OutDirectory, Frame.FileName),
Pixels,
Arguments.RenderWidth,
Arguments.RenderHeight,
Error))
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = Error;
return false;
}
const double ViewSeconds = FPlatformTime::Seconds() - ViewStartSeconds;
if (ViewIndex == 0)
{
OutOutput.FirstViewRasterSeconds = ViewSeconds;
}
if (bRasterTruncated)
{
OutOutput.Status = TEXT("truncated");
OutOutput.bTruncated = true;
OutOutput.RefusalReason = TEXT("The wall-clock budget stopped triangle rasterisation.");
break;
}
}
OutOutput.MeshSeconds = World.ExploreMeshSeconds;
OutOutput.RasterSeconds = FPlatformTime::Seconds() - RasterStartSeconds;
const int32 AdditionalViewCount = FMath::Max(0, OutOutput.Frames.Num() - 1);
OutOutput.AdditionalViewRasterSeconds = AdditionalViewCount > 0
? (OutOutput.RasterSeconds - OutOutput.FirstViewRasterSeconds)
/ static_cast<double>(AdditionalViewCount)
: 0.0;
if (!OutOutput.bTruncated)
{
OutOutput.Status = TEXT("ok");
}
return !OutOutput.bTruncated;
}
void DrawPointMarker(
TArray<FColor>& Pixels,
int32 Width,
int32 Height,
const FVector& Point,
const FVector& CameraPosition,
const FExploreCameraBasis& Basis,
const FColor& Color,
bool& bOutProjected)
{
FIntPoint Pixel = FIntPoint::ZeroValue;
bOutProjected = ProjectPoint(
Point, CameraPosition, Basis, Width, Height, Pixel);
if (!bOutProjected)
{
return;
}
constexpr int32 MarkerRadius = 7;
DrawLine(
Pixels, Width, Height,
Pixel.X - MarkerRadius, Pixel.Y,
Pixel.X + MarkerRadius, Pixel.Y,
Color);
DrawLine(
Pixels, Width, Height,
Pixel.X, Pixel.Y - MarkerRadius,
Pixel.X, Pixel.Y + MarkerRadius,
Color);
DrawLine(
Pixels, Width, Height,
Pixel.X - 4, Pixel.Y - 4,
Pixel.X + 4, Pixel.Y + 4,
Color);
DrawLine(
Pixels, Width, Height,
Pixel.X - 4, Pixel.Y + 4,
Pixel.X + 4, Pixel.Y - 4,
Color);
}
bool RunFailureBoundaryRender(
const FExploreArguments& Arguments,
FExploreWorld& World,
const FExploreWalkOutput& Walk,
FExploreRenderOutput& InOutOutput,
FExploreBudget& Budget)
{
const FVoxelPlayerFitWalkReport& Report = Walk.Report;
if (!Report.bHasAgentFinalPosition || !Report.bHasTargetComponentNearestCell)
{
return false;
}
const FVector AgentFinal = Report.AgentFinalVoxels;
const FVector TargetComponentCell = Report.TargetComponentNearestVoxels;
const FVector LastReached = Report.LastReachedVoxels;
const bool bHaveLastReached = Report.bHasLastReachedPosition;
const FVector Focus = (AgentFinal + TargetComponentCell) * 0.5f;
const FVector Segment = TargetComponentCell - AgentFinal;
const FVector SegmentDirection = Segment.GetSafeNormal();
if (SegmentDirection.IsNearlyZero())
{
return false;
}
FVector ViewSide = FVector::CrossProduct(SegmentDirection, FVector::UpVector).GetSafeNormal();
if (ViewSide.IsNearlyZero())
{
ViewSide = FVector::CrossProduct(SegmentDirection, FVector::RightVector).GetSafeNormal();
}
const float SegmentLength = Segment.Size();
const float ViewSideOffset = FMath::Min(64.0f, FMath::Max(16.0f, 0.25f * SegmentLength));
// Failure-focus views are still required to originate from the fitted graph. The old
// implementation nudged the camera four voxels toward/away from the boundary and could put
// the viewpoint in the very rock that this diagnostic is meant to explain.
const FVector CameraCandidates[] = {
AgentFinal,
bHaveLastReached ? LastReached : AgentFinal,
};
const FVector ViewTargets[] = {
Focus + ViewSide * ViewSideOffset,
Focus - ViewSide * ViewSideOffset,
};
for (int32 ViewIndex = 0; ViewIndex < UE_ARRAY_COUNT(ViewTargets); ++ViewIndex)
{
if (Budget.ShouldStop(TEXT("failure-focus-render")))
{
InOutOutput.Status = TEXT("truncated");
InOutOutput.bTruncated = true;
InOutOutput.RefusalReason = TEXT(
"The wall-clock budget stopped failure-focus viewpoint generation.");
return false;
}
const FVector CameraSeedPose = CameraCandidates[ViewIndex];
const FVector CameraPosition = CameraSeedPose
+ FVector::UpVector * (FVoxelPlayerCapsuleConstants::HalfHeightVoxels - 0.5f);
const float CameraDensity = World.Generator->GetDensityAt(
CameraPosition.X, CameraPosition.Y, CameraPosition.Z);
if (!FMath::IsFinite(CameraDensity) || !(CameraDensity > 0.0f))
{
UE_LOG(LogTemp, Warning,
TEXT("[VoxelForgeExplore] refusing failure-focus camera %d: fitted pose is not air."),
ViewIndex);
return false;
}
const FVector Target = ViewTargets[ViewIndex];
TArray<FColor> Pixels;
int32 HitPixels = 0;
bool bRasterTruncated = false;
if (!RasterizeExploreMesh(
Arguments,
World,
CameraPosition,
Target,
Budget,
Pixels,
HitPixels,
bRasterTruncated))
{
InOutOutput.Status = TEXT("error");
InOutOutput.RefusalReason = TEXT("Could not construct failure-focus mesh raster.");
return false;
}
FExploreCameraBasis Basis;
if (!BuildCameraBasis(
CameraPosition,
Target,
70.0f,
Arguments.RenderWidth,
Arguments.RenderHeight,
Basis))
{
return false;
}
bool bAgentProjected = false;
bool bTargetProjected = false;
bool bLastReachedProjected = false;
DrawPointMarker(
Pixels,
Arguments.RenderWidth,
Arguments.RenderHeight,
AgentFinal,
CameraPosition,
Basis,
FColor(242, 72, 72, 255),
bAgentProjected);
DrawPointMarker(
Pixels,
Arguments.RenderWidth,
Arguments.RenderHeight,
TargetComponentCell,
CameraPosition,
Basis,
FColor(72, 242, 112, 255),
bTargetProjected);
if (bHaveLastReached)
{
DrawPointMarker(
Pixels,
Arguments.RenderWidth,
Arguments.RenderHeight,
LastReached,
CameraPosition,
Basis,
FColor(242, 176, 56, 255),
bLastReachedProjected);
}
FIntPoint AgentPixel = FIntPoint::ZeroValue;
FIntPoint TargetPixel = FIntPoint::ZeroValue;
if (ProjectPoint(
AgentFinal,
CameraPosition,
Basis,
Arguments.RenderWidth,
Arguments.RenderHeight,
AgentPixel)
&& ProjectPoint(
TargetComponentCell,
CameraPosition,
Basis,
Arguments.RenderWidth,
Arguments.RenderHeight,
TargetPixel))
{
DrawLine(
Pixels,
Arguments.RenderWidth,
Arguments.RenderHeight,
AgentPixel.X,
AgentPixel.Y,
TargetPixel.X,
TargetPixel.Y,
FColor(244, 214, 72, 255));
}
FExploreRenderFrame& Frame = InOutOutput.Frames.AddDefaulted_GetRef();
Frame.FileName = FString::Printf(TEXT("failure_boundary_%02d.png"), ViewIndex);
Frame.Purpose = TEXT("agent_final_red_target_component_nearest_green_last_reached_orange");
Frame.CameraSeedSource = bHaveLastReached && ViewIndex == 1
? TEXT("last_reached_player_fit")
: TEXT("agent_final_player_fit");
Frame.CameraSeedPoseVoxels = CameraSeedPose;
Frame.CameraVoxels = CameraPosition;
Frame.TargetVoxels = Target;
Frame.HitPixels = HitPixels;
Frame.bFailureMarkersProjected = bAgentProjected && bTargetProjected
&& (!bHaveLastReached || bLastReachedProjected);
Frame.bPartial = bRasterTruncated;
bool bScaleMarkerProjected = false;
DrawScaleMarker(
Pixels,
Arguments.RenderWidth,
Arguments.RenderHeight,
CameraPosition,
Target,
Basis,
bScaleMarkerProjected);
Frame.bScaleMarkerProjected = bScaleMarkerProjected;
FString Error;
if (!SavePng(
FPaths::Combine(Arguments.OutDirectory, Frame.FileName),
Pixels,
Arguments.RenderWidth,
Arguments.RenderHeight,
Error))
{
InOutOutput.Status = TEXT("error");
InOutOutput.RefusalReason = Error;
return false;
}
if (bRasterTruncated)
{
InOutOutput.Status = TEXT("truncated");
InOutOutput.bTruncated = true;
InOutOutput.RefusalReason = TEXT(
"The wall-clock budget stopped failure-focus rasterisation.");
return false;
}
}
return true;
}
bool RunWalk(
const FExploreArguments& Arguments,
const FExploreWorld& World,
FExploreWalkOutput& OutOutput,
FExploreBudget& Budget)
{
OutOutput = FExploreWalkOutput();
OutOutput.WindowPolicy = NeedsOriginInWindow(Arguments.Archetype)
? TEXT("origin-inclusive target-strate passage-envelope AABB + margin")
: TEXT("target-strate passage-envelope AABB + margin");
const int64 EstimatedWorkingBytes = static_cast<int64>(Arguments.MaxWalkCells)
* EstimatedWalkBytesPerCell;
if (EstimatedWorkingBytes > MaxWalkWorkingBytes)
{
OutOutput.Status = TEXT("refused");
OutOutput.RefusalReason = FString::Printf(
TEXT("Refused before sampling: estimated walk memory %lld bytes exceeds cap %lld bytes."),
static_cast<long long>(EstimatedWorkingBytes),
static_cast<long long>(MaxWalkWorkingBytes));
return false;
}
if (Budget.ShouldStop(TEXT("walk")))
{
OutOutput.Status = TEXT("truncated");
OutOutput.RefusalReason = TEXT("The wall-clock budget elapsed before walk sampling.");
return false;
}
const FVoxelPassage* ArrivalPassage = nullptr;
const FVoxelPassage* DeparturePassage = nullptr;
if (!FindMouths(
*World.Manager,
Arguments.Slot,
OutOutput.ArrivalVoxels,
OutOutput.DepartureVoxels,
OutOutput.ArrivalPassageCount,
OutOutput.DeparturePassageCount,
ArrivalPassage,
DeparturePassage))
{
OutOutput.Status = TEXT("refused");
OutOutput.RefusalReason = TEXT(
"The target slot did not have both one-step chain mouths in the generated passage list.");
return false;
}
OutOutput.bHasArrival = true;
OutOutput.bHasDeparture = true;
FVoxelStrateMeasureSettings Settings;
Settings.SampleStep = 1;
Settings.MaxCells = Arguments.MaxWalkCells;
Settings.MaxRouteRetries = 16;
Settings.CoverPointA = FVector2D(OutOutput.ArrivalVoxels.X, OutOutput.ArrivalVoxels.Y);
Settings.CoverPointB = FVector2D(OutOutput.DepartureVoxels.X, OutOutput.DepartureVoxels.Y);
Settings.CoverMarginVoxels = FMath::CeilToFloat(
Settings.PlayerCapsuleRadiusVoxels) + 2.0f;
// A mouth-AABB is not a landing window: the landing door and its local apron can legitimately
// leave that box before the player reaches the next room. Expand the same two-point box just
// enough to contain the selected production mouth envelope. This is still a bounded,
// deterministic measurement window; the central inter-strate descent belongs to the adjacent
// strata and is not folded into this intra-strate walk question.
const float WindowMinX = FMath::Min(OutOutput.ArrivalVoxels.X, OutOutput.DepartureVoxels.X);
const float WindowMaxX = FMath::Max(OutOutput.ArrivalVoxels.X, OutOutput.DepartureVoxels.X);
const float WindowMinY = FMath::Min(OutOutput.ArrivalVoxels.Y, OutOutput.DepartureVoxels.Y);
const float WindowMaxY = FMath::Max(OutOutput.ArrivalVoxels.Y, OutOutput.DepartureVoxels.Y);
const auto IncludeTargetPassageEnvelope = [&](
const FVoxelPassage* Passage,
const FVoxelPassageLanding& Landing)
{
if (Passage == nullptr)
{
return;
}
const auto IncludeXY = [&](const FVector& Point, float Radius)
{
if (!FMath::IsFinite(Point.X) || !FMath::IsFinite(Point.Y)
|| !FMath::IsFinite(Point.Z) || !FMath::IsFinite(Radius))
{
return;
}
const float AbsRadius = FMath::Abs(Radius);
// Include control points whose centreline belongs to the measured strate. A control
// point just outside the boundary belongs to the adjacent strate's descent; pulling
// that whole switchback into this bounded walk box can multiply the sampled volume
// without improving the intra-strate mouth-to-mouth question.
if (Point.Z < static_cast<float>(World.TargetBottomWorldZ)
|| Point.Z > static_cast<float>(World.TargetTopWorldZ))
{
return;
}
const float ExcessX = FMath::Max(
0.0f,
FMath::Max(
WindowMinX - (Point.X + AbsRadius),
(Point.X - AbsRadius) - WindowMaxX));
const float ExcessY = FMath::Max(
0.0f,
FMath::Max(
WindowMinY - (Point.Y + AbsRadius),
(Point.Y - AbsRadius) - WindowMaxY));
Settings.CoverMarginVoxels = FMath::Max(
Settings.CoverMarginVoxels,
FMath::Max(ExcessX, ExcessY));
};
IncludeXY(
Landing.StandingPoint,
Landing.HalfWidth + VoxelPassageGeometry::LandingCarveBlendVoxels);
// The first/last three points are the production mouth, apron, and first slope section.
// Do not pull the whole inter-strate switchback into the bounded target-strate test.
const int32 NumLocalPoints = FMath::Min(3, Passage->ControlPoints.Num());
const bool bUseUpperEnd = &Landing == &Passage->UpperLanding;
const int32 FirstPoint = bUseUpperEnd
? 0 : FMath::Max(0, Passage->ControlPoints.Num() - NumLocalPoints);
const int32 LastPoint = bUseUpperEnd
? NumLocalPoints : Passage->ControlPoints.Num();
for (int32 PointIndex = FirstPoint; PointIndex < LastPoint; ++PointIndex)
{
const float Radius = Passage->ControlRadii.IsValidIndex(PointIndex)
? Passage->ControlRadii[PointIndex] : Passage->Radius;
IncludeXY(Passage->ControlPoints[PointIndex], Radius
+ VoxelPassageGeometry::LandingCarveBlendVoxels);
}
};
IncludeTargetPassageEnvelope(
ArrivalPassage,
ArrivalPassage != nullptr ? ArrivalPassage->LowerLanding : FVoxelPassageLanding());
IncludeTargetPassageEnvelope(
DeparturePassage,
DeparturePassage != nullptr ? DeparturePassage->UpperLanding : FVoxelPassageLanding());
Settings.bIncludeOriginInCoverWindow = NeedsOriginInWindow(Arguments.Archetype);
TArray<FVector> LandingRoomProbePoints;
auto AppendLandingRoomProbes = [&LandingRoomProbePoints](
const FVoxelPassageLanding& Landing)
{
const float ProbeOffset = FMath::Min(
2.0f,
FMath::Max(0.0f, Landing.HalfWidth - 2.0f));
LandingRoomProbePoints.Add(Landing.StandingPoint);
LandingRoomProbePoints.Add(
Landing.StandingPoint + FVector(ProbeOffset, 0.0f, 0.0f));
LandingRoomProbePoints.Add(
Landing.StandingPoint + FVector(-ProbeOffset, 0.0f, 0.0f));
LandingRoomProbePoints.Add(
Landing.StandingPoint + FVector(0.0f, ProbeOffset, 0.0f));
LandingRoomProbePoints.Add(
Landing.StandingPoint + FVector(0.0f, -ProbeOffset, 0.0f));
};
if (ArrivalPassage != nullptr)
{
AppendLandingRoomProbes(ArrivalPassage->LowerLanding);
}
if (DeparturePassage != nullptr)
{
AppendLandingRoomProbes(DeparturePassage->UpperLanding);
}
const FGeneratorDensitySampler Sampler(*World.Generator);
if (!VF_MeasurePlayerFitWalkWithSampler(
Sampler,
World.TargetBottomWorldZ,
World.TargetTopWorldZ,
World.TargetBoundarySealThickness,
OutOutput.ArrivalVoxels,
OutOutput.DepartureVoxels,
Settings,
OutOutput.Report,
&LandingRoomProbePoints,
&OutOutput.SharedSampleGrid))
{
OutOutput.Status = TEXT("refused");
OutOutput.RefusalReason = OutOutput.Report.RefusalReason;
return false;
}
// Camera seeds are selected from this exact captured mask. The first valid fit cell anchors
// the bounded render mesh; subsequent cells are kept local to that anchor so the mesh region
// contains every requested viewpoint rather than silently rendering a distant empty box.
OutOutput.CameraSeedPoses.Reset();
OutOutput.CameraSeedSources.Reset();
FVector CameraAnchor = FVector::ZeroVector;
bool bHaveCameraAnchor = false;
const auto AddCameraSeed = [&](const FVector& Position, const TCHAR* Source)
{
if (!FMath::IsFinite(Position.X) || !FMath::IsFinite(Position.Y)
|| !FMath::IsFinite(Position.Z)
|| !OutOutput.SharedSampleGrid.ContainsPoint(Position))
{
return;
}
const float Step = static_cast<float>(OutOutput.SharedSampleGrid.SampleStep);
const int32 X = FMath::Clamp(
FMath::FloorToInt((Position.X - OutOutput.SharedSampleGrid.MinX) / Step),
0,
OutOutput.SharedSampleGrid.NumX - 1);
const int32 Y = FMath::Clamp(
FMath::FloorToInt((Position.Y - OutOutput.SharedSampleGrid.MinY) / Step),
0,
OutOutput.SharedSampleGrid.NumY - 1);
const int32 Z = FMath::Clamp(
FMath::FloorToInt((Position.Z - OutOutput.SharedSampleGrid.MinZ) / Step),
0,
OutOutput.SharedSampleGrid.NumZ - 1);
const int32 Cell = OutOutput.SharedSampleGrid.Index(X, Y, Z);
if (!OutOutput.SharedSampleGrid.PlayerFitMask.IsValidIndex(Cell)
|| OutOutput.SharedSampleGrid.PlayerFitMask[Cell] == 0u)
{
return;
}
if (!bHaveCameraAnchor)
{
CameraAnchor = Position;
bHaveCameraAnchor = true;
}
if (FVector::DistSquared(Position, CameraAnchor) > FMath::Square(40.0f))
{
return;
}
for (const FVector& Existing : OutOutput.CameraSeedPoses)
{
if (Existing.Equals(Position, 0.001f))
{
return;
}
}
OutOutput.CameraSeedPoses.Add(Position);
OutOutput.CameraSeedSources.Add(
Source != nullptr ? FString(Source)
: FString::Printf(TEXT("player_fit_%02d"),
OutOutput.CameraSeedPoses.Num() - 1));
};
AddCameraSeed(OutOutput.ArrivalVoxels, TEXT("arrival_player_fit"));
AddCameraSeed(OutOutput.DepartureVoxels, TEXT("departure_player_fit"));
if (OutOutput.Report.bHasLastReachedPosition)
{
AddCameraSeed(
OutOutput.Report.LastReachedVoxels,
TEXT("last_reached_player_fit"));
}
if (OutOutput.Report.bHasAgentFinalPosition)
{
AddCameraSeed(
OutOutput.Report.AgentFinalVoxels,
TEXT("agent_final_player_fit"));
}
if (!bHaveCameraAnchor)
{
for (int32 Cell = 0;
Cell < OutOutput.SharedSampleGrid.PlayerFitMask.Num();
++Cell)
{
if (OutOutput.SharedSampleGrid.PlayerFitMask[Cell] == 0u)
{
continue;
}
const int32 Plane = OutOutput.SharedSampleGrid.NumX
* OutOutput.SharedSampleGrid.NumY;
const int32 Z = Cell / Plane;
const int32 InPlane = Cell - Z * Plane;
const int32 Y = InPlane / OutOutput.SharedSampleGrid.NumX;
const int32 X = InPlane - Y * OutOutput.SharedSampleGrid.NumX;
AddCameraSeed(
FVector(
OutOutput.SharedSampleGrid.MinX
+ (static_cast<float>(X) + 0.5f)
* OutOutput.SharedSampleGrid.SampleStep,
OutOutput.SharedSampleGrid.MinY
+ (static_cast<float>(Y) + 0.5f)
* OutOutput.SharedSampleGrid.SampleStep,
OutOutput.SharedSampleGrid.MinZ
+ (static_cast<float>(Z) + 0.5f)
* OutOutput.SharedSampleGrid.SampleStep),
TEXT("player_fit_grid"));
if (bHaveCameraAnchor)
{
break;
}
}
}
if (bHaveCameraAnchor)
{
const int32 NumCandidates = OutOutput.SharedSampleGrid.PlayerFitMask.Num();
for (int32 Cell = 0; Cell < NumCandidates && OutOutput.CameraSeedPoses.Num() < 8; ++Cell)
{
if (OutOutput.SharedSampleGrid.PlayerFitMask[Cell] == 0u)
{
continue;
}
const int32 Plane = OutOutput.SharedSampleGrid.NumX
* OutOutput.SharedSampleGrid.NumY;
const int32 Z = Cell / Plane;
const int32 InPlane = Cell - Z * Plane;
const int32 Y = InPlane / OutOutput.SharedSampleGrid.NumX;
const int32 X = InPlane - Y * OutOutput.SharedSampleGrid.NumX;
AddCameraSeed(
FVector(
OutOutput.SharedSampleGrid.MinX
+ (static_cast<float>(X) + 0.5f)
* OutOutput.SharedSampleGrid.SampleStep,
OutOutput.SharedSampleGrid.MinY
+ (static_cast<float>(Y) + 0.5f)
* OutOutput.SharedSampleGrid.SampleStep,
OutOutput.SharedSampleGrid.MinZ
+ (static_cast<float>(Z) + 0.5f)
* OutOutput.SharedSampleGrid.SampleStep),
TEXT("player_fit_grid"));
}
}
// The origin observation is now a view over the shared primary grid. A second, expanded
// walk would defeat the one-grid contract; callers that need origin coverage must request an
// origin-inclusive archetype window, which is already selected before the single sample pass.
OutOutput.bOriginCheckAttempted = true;
if (OutOutput.Report.bOriginColumnInSampledWindow)
{
OutOutput.OriginCheckReport = OutOutput.Report;
OutOutput.bOriginCheckAvailable = true;
}
else
{
OutOutput.OriginCheckRefusalReason = TEXT(
"Origin is outside the shared primary sample window; no second grid was sampled.");
}
const int32 ArrivalProbeCount = ArrivalPassage != nullptr ? 5 : 0;
const int32 DepartureProbeCount = DeparturePassage != nullptr ? 5 : 0;
const int32 LandingProbeCount = ArrivalProbeCount + DepartureProbeCount;
OutOutput.ArrivalLandingRoomProbeCount = ArrivalProbeCount;
OutOutput.DepartureLandingRoomProbeCount = DepartureProbeCount;
for (int32 ProbeIndex = 0;
ProbeIndex < FMath::Min(LandingProbeCount,
OutOutput.Report.ComponentProbePlayerFit.Num());
++ProbeIndex)
{
const bool bPlayerFit = OutOutput.Report.ComponentProbePlayerFit[ProbeIndex] != 0u;
const bool bInArrivalComponent =
OutOutput.Report.ComponentProbeInStartComponent.IsValidIndex(ProbeIndex)
&& OutOutput.Report.ComponentProbeInStartComponent[ProbeIndex] != 0u;
if (ProbeIndex < ArrivalProbeCount)
{
OutOutput.ArrivalLandingRoomPlayerFitProbeCount += bPlayerFit ? 1 : 0;
OutOutput.ArrivalLandingRoomArrivalComponentProbeCount +=
bInArrivalComponent ? 1 : 0;
}
else
{
OutOutput.DepartureLandingRoomPlayerFitProbeCount += bPlayerFit ? 1 : 0;
OutOutput.DepartureLandingRoomArrivalComponentProbeCount +=
bInArrivalComponent ? 1 : 0;
}
}
if (Budget.ShouldStop(TEXT("walk")))
{
OutOutput.Status = TEXT("truncated");
OutOutput.RefusalReason = TEXT("The wall-clock budget elapsed after walk sampling.");
return false;
}
OutOutput.Status = TEXT("ok");
return true;
}
int64 EstimateExportWorkingBytes(int32 RegionSize)
{
const int64 TileCells = static_cast<int64>(CHUNK_SIZE)
* static_cast<int64>(CHUNK_SIZE)
* static_cast<int64>(CHUNK_SIZE);
const int64 TileGridSamples = static_cast<int64>(CHUNK_SIZE + 3)
* static_cast<int64>(CHUNK_SIZE + 3)
* static_cast<int64>(CHUNK_SIZE + 3);
// This preflight covers one canonical tile's scalar grid, edge map, mesh arrays, and temporary
// triangle lists. The run-level canonical aggregate is retained separately so render and
// export consume the same mesh. RegionSize is still accepted here to keep the call site's
// contract explicit.
(void)RegionSize;
return TileGridSamples * static_cast<int64>(sizeof(float)) + TileCells * 448ll;
}
bool WriteUtf8(FArchive& Archive, const FString& Text)
{
FTCHARToUTF8 Converted(*Text);
if (Converted.Length() > 0)
{
Archive.Serialize(const_cast<ANSICHAR*>(Converted.Get()), Converted.Length());
}
return !Archive.IsError();
}
bool ValidateCanonicalTile(const FVoxelMeshData& MeshData, FString& OutError)
{
if (MeshData.Vertices.Num() != MeshData.Normals.Num()
|| MeshData.Vertices.Num() != MeshData.UVs.Num()
|| MeshData.Vertices.Num() != MeshData.Colors.Num()
|| MeshData.Triangles.Num() % 3 != 0)
{
OutError = TEXT("Canonical mesher returned non-parallel mesh arrays.");
return false;
}
return true;
}
bool ExploreMetricVectorIsFinite(const FVector& Value)
{
return FMath::IsFinite(Value.X) && FMath::IsFinite(Value.Y) && FMath::IsFinite(Value.Z);
}
FIntVector ExploreMetricWeldPosition(const FVector& PositionCm)
{
// Mesh positions are Unreal centimetres. 0.01 cm is deliberately much smaller than a
// marching-cubes cell and only absorbs independent-tile floating-point round-off.
constexpr float WeldQuantumCm = 0.01f;
return FIntVector(
FMath::RoundToInt(PositionCm.X / WeldQuantumCm),
FMath::RoundToInt(PositionCm.Y / WeldQuantumCm),
FMath::RoundToInt(PositionCm.Z / WeldQuantumCm));
}
FExploreMeshEdgeKey ExploreMetricMakeEdgeKey(
const FVector& PositionA,
const FVector& PositionB)
{
const FIntVector A = ExploreMetricWeldPosition(PositionA);
const FIntVector B = ExploreMetricWeldPosition(PositionB);
const bool bAFirst = A.X < B.X
|| (A.X == B.X && (A.Y < B.Y || (A.Y == B.Y && A.Z <= B.Z)));
return bAFirst
? FExploreMeshEdgeKey{A, B}
: FExploreMeshEdgeKey{B, A};
}
float ExploreMetricPercentile(const TArray<float>& SortedValues, float Quantile)
{
if (SortedValues.Num() == 0)
{
return 0.0f;
}
const float Position = FMath::Clamp(Quantile, 0.0f, 1.0f)
* static_cast<float>(SortedValues.Num() - 1);
const int32 Lower = FMath::FloorToInt(Position);
const int32 Upper = FMath::Min(Lower + 1, SortedValues.Num() - 1);
return FMath::Lerp(SortedValues[Lower], SortedValues[Upper], Position - static_cast<float>(Lower));
}
bool MeasureExploreMesh(
const FVoxelMeshData& MeshData,
FExploreMeshMetrics& OutMetrics,
FString& OutError)
{
OutMetrics = FExploreMeshMetrics();
if (!ValidateCanonicalTile(MeshData, OutError))
{
return false;
}
const int32 NumTriangles = MeshData.Triangles.Num() / 3;
OutMetrics.VertexCount = MeshData.Vertices.Num();
OutMetrics.TriangleCount = NumTriangles;
TArray<float> EdgeLengthsMeters;
TArray<float> DihedralAnglesDegrees;
TArray<float> FaceNormalDivergencesDegrees;
EdgeLengthsMeters.Reserve(NumTriangles * 3);
DihedralAnglesDegrees.Reserve(NumTriangles * 2);
FaceNormalDivergencesDegrees.Reserve(NumTriangles);
TArray<FVector> FaceNormals;
FaceNormals.Init(FVector::ZeroVector, NumTriangles);
TArray<uint8> ValidFaceNormals;
ValidFaceNormals.Init(0, NumTriangles);
TMap<FExploreMeshEdgeKey, FExploreMeshEdgeIncident> EdgeIncidents;
EdgeIncidents.Reserve(FMath::Max(1, NumTriangles * 2));
const auto AddEdgeIncident = [&EdgeIncidents](
const FVector& PositionA, const FVector& PositionB, int32 TriangleIndex)
{
const FExploreMeshEdgeKey Key = ExploreMetricMakeEdgeKey(PositionA, PositionB);
if (Key.First == Key.Second)
{
return;
}
FExploreMeshEdgeIncident& Incident = EdgeIncidents.FindOrAdd(Key);
if (Incident.IncidentCount == 0)
{
Incident.FirstTriangle = TriangleIndex;
}
else if (Incident.IncidentCount == 1)
{
Incident.SecondTriangle = TriangleIndex;
}
Incident.IncidentCount = FMath::Min(Incident.IncidentCount + 1, 3);
};
for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex)
{
const int32 Base = TriangleIndex * 3;
const int32 IndexA = MeshData.Triangles[Base];
const int32 IndexB = MeshData.Triangles[Base + 1];
const int32 IndexC = MeshData.Triangles[Base + 2];
if (!MeshData.Vertices.IsValidIndex(IndexA)
|| !MeshData.Vertices.IsValidIndex(IndexB)
|| !MeshData.Vertices.IsValidIndex(IndexC)
|| !MeshData.Normals.IsValidIndex(IndexA)
|| !MeshData.Normals.IsValidIndex(IndexB)
|| !MeshData.Normals.IsValidIndex(IndexC))
{
OutError = TEXT("Surface metric measurement saw an out-of-range mesh index.");
return false;
}
const FVector& A = MeshData.Vertices[IndexA];
const FVector& B = MeshData.Vertices[IndexB];
const FVector& C = MeshData.Vertices[IndexC];
if (!ExploreMetricVectorIsFinite(A)
|| !ExploreMetricVectorIsFinite(B)
|| !ExploreMetricVectorIsFinite(C))
{
OutError = TEXT("Surface metric measurement saw a non-finite vertex.");
return false;
}
EdgeLengthsMeters.Add(FVector::Dist(A, B) * 0.01f);
EdgeLengthsMeters.Add(FVector::Dist(B, C) * 0.01f);
EdgeLengthsMeters.Add(FVector::Dist(C, A) * 0.01f);
const FVector Cross = FVector::CrossProduct(B - A, C - A);
if (Cross.SizeSquared() > SMALL_NUMBER)
{
const FVector MeanVertexNormal = (
MeshData.Normals[IndexA]
+ MeshData.Normals[IndexB]
+ MeshData.Normals[IndexC]).GetSafeNormal();
if (ExploreMetricVectorIsFinite(MeanVertexNormal)
&& !MeanVertexNormal.IsNearlyZero())
{
// The canonical marching-cubes table is emitted with the winding required by
// the runtime mesh path, while the density-gradient normals point solid-to-air.
// Orient the geometric face normal to that same outward convention before
// measuring the relief divergence; otherwise a winding convention change would
// report ~180 degrees for an otherwise smooth surface.
FVector FaceNormal = Cross.GetSafeNormal();
if (FVector::DotProduct(FaceNormal, MeanVertexNormal) < 0.0f)
{
FaceNormal *= -1.0f;
}
FaceNormals[TriangleIndex] = FaceNormal;
ValidFaceNormals[TriangleIndex] = 1;
const float Dot = FMath::Clamp(
FVector::DotProduct(FaceNormal, MeanVertexNormal), -1.0f, 1.0f);
FaceNormalDivergencesDegrees.Add(
FMath::RadiansToDegrees(FMath::Acos(Dot)));
++OutMetrics.NormalComparisonTriangleCount;
}
}
AddEdgeIncident(A, B, TriangleIndex);
AddEdgeIncident(B, C, TriangleIndex);
AddEdgeIncident(C, A, TriangleIndex);
}
for (const TPair<FExploreMeshEdgeKey, FExploreMeshEdgeIncident>& Pair : EdgeIncidents)
{
const FExploreMeshEdgeIncident& Incident = Pair.Value;
if (Incident.IncidentCount != 2)
{
continue;
}
++OutMetrics.SharedEdgeCount;
if (!ValidFaceNormals.IsValidIndex(Incident.FirstTriangle)
|| !ValidFaceNormals.IsValidIndex(Incident.SecondTriangle)
|| !ValidFaceNormals[Incident.FirstTriangle]
|| !ValidFaceNormals[Incident.SecondTriangle])
{
continue;
}
const float Dot = FMath::Clamp(
FVector::DotProduct(
FaceNormals[Incident.FirstTriangle],
FaceNormals[Incident.SecondTriangle]),
-1.0f,
1.0f);
DihedralAnglesDegrees.Add(FMath::RadiansToDegrees(FMath::Acos(Dot)));
}
EdgeLengthsMeters.Sort();
DihedralAnglesDegrees.Sort();
FaceNormalDivergencesDegrees.Sort();
OutMetrics.DihedralSampleCount = DihedralAnglesDegrees.Num();
OutMetrics.EdgeLengthP50Meters = ExploreMetricPercentile(EdgeLengthsMeters, 0.50f);
OutMetrics.EdgeLengthP99Meters = ExploreMetricPercentile(EdgeLengthsMeters, 0.99f);
OutMetrics.EdgeLengthMaxMeters = EdgeLengthsMeters.Num() > 0
? EdgeLengthsMeters.Last() : 0.0f;
OutMetrics.DihedralP50Degrees = ExploreMetricPercentile(DihedralAnglesDegrees, 0.50f);
OutMetrics.DihedralP75Degrees = ExploreMetricPercentile(DihedralAnglesDegrees, 0.75f);
OutMetrics.DihedralP90Degrees = ExploreMetricPercentile(DihedralAnglesDegrees, 0.90f);
OutMetrics.DihedralP95Degrees = ExploreMetricPercentile(DihedralAnglesDegrees, 0.95f);
OutMetrics.DihedralP99Degrees = ExploreMetricPercentile(DihedralAnglesDegrees, 0.99f);
if (DihedralAnglesDegrees.Num() > 0)
{
int32 Over20 = 0;
int32 Over40 = 0;
for (const float Angle : DihedralAnglesDegrees)
{
Over20 += Angle > 20.0f ? 1 : 0;
Over40 += Angle > 40.0f ? 1 : 0;
}
OutMetrics.DihedralFractionOver20Degrees = static_cast<float>(Over20)
/ static_cast<float>(DihedralAnglesDegrees.Num());
OutMetrics.DihedralFractionOver40Degrees = static_cast<float>(Over40)
/ static_cast<float>(DihedralAnglesDegrees.Num());
}
OutMetrics.FaceNormalVsMeanVertexNormalP50Degrees =
ExploreMetricPercentile(FaceNormalDivergencesDegrees, 0.50f);
OutMetrics.FaceNormalVsMeanVertexNormalP90Degrees =
ExploreMetricPercentile(FaceNormalDivergencesDegrees, 0.90f);
OutError.Reset();
return true;
}
bool WriteObjHeader(FArchive& Archive)
{
bool bOk = WriteUtf8(Archive, TEXT("# VoxelForge authoritative marching-cubes mesh\n"));
bOk = bOk && WriteUtf8(Archive,
TEXT("# Positions are metres; UVs retain canonical voxel-space values; source winding is preserved.\n"));
bOk = bOk && WriteUtf8(Archive, TEXT("o VoxelForgeExplore\n"));
return bOk && !Archive.IsError();
}
bool WriteObjMesh(
FArchive& Archive,
const FVoxelMeshData& MeshData,
FExploreBudget& Budget,
FString& OutError,
bool& bOutTruncated,
int32& OutVerticesWritten,
int32& OutTrianglesWritten)
{
bOutTruncated = false;
OutVerticesWritten = 0;
OutTrianglesWritten = 0;
if (!ValidateCanonicalTile(MeshData, OutError))
{
return false;
}
bool bOk = true;
for (int32 VertexIndex = 0; VertexIndex < MeshData.Vertices.Num(); ++VertexIndex)
{
if ((VertexIndex & 255) == 0 && Budget.ShouldStop(TEXT("export-write")))
{
bOutTruncated = true;
return true;
}
const FVector& Vertex = MeshData.Vertices[VertexIndex];
bOk = bOk && WriteUtf8(Archive, FString::Printf(
TEXT("v %.9f %.9f %.9f\n"),
Vertex.X / 100.0,
Vertex.Y / 100.0,
Vertex.Z / 100.0));
++OutVerticesWritten;
}
for (int32 UVIndex = 0; UVIndex < MeshData.UVs.Num(); ++UVIndex)
{
if ((UVIndex & 255) == 0 && Budget.ShouldStop(TEXT("export-write")))
{
bOutTruncated = true;
return true;
}
const FVector2D& UV = MeshData.UVs[UVIndex];
bOk = bOk && WriteUtf8(Archive, FString::Printf(
TEXT("vt %.9f %.9f\n"), UV.X, UV.Y));
}
for (int32 NormalIndex = 0; NormalIndex < MeshData.Normals.Num(); ++NormalIndex)
{
if ((NormalIndex & 255) == 0 && Budget.ShouldStop(TEXT("export-write")))
{
bOutTruncated = true;
return true;
}
const FVector& Normal = MeshData.Normals[NormalIndex];
bOk = bOk && WriteUtf8(Archive, FString::Printf(
TEXT("vn %.9f %.9f %.9f\n"), Normal.X, Normal.Y, Normal.Z));
}
const int32 NumTriangles = MeshData.Triangles.Num() / 3;
if (NumTriangles > 0)
{
bOk = bOk && WriteUtf8(Archive, TEXT("usemtl VoxelGround\n"));
}
for (int32 Triangle = 0; Triangle < NumTriangles; ++Triangle)
{
if ((Triangle & 255) == 0 && Budget.ShouldStop(TEXT("export-write")))
{
bOutTruncated = true;
return true;
}
const int32 Base = Triangle * 3;
const int32 A = MeshData.Triangles[Base];
const int32 B = MeshData.Triangles[Base + 1];
const int32 C = MeshData.Triangles[Base + 2];
if (A < 0 || B < 0 || C < 0
|| A >= MeshData.Vertices.Num()
|| B >= MeshData.Vertices.Num()
|| C >= MeshData.Vertices.Num())
{
OutError = TEXT("Canonical aggregate mesh returned an out-of-range triangle index.");
return false;
}
bOk = bOk && WriteUtf8(Archive, FString::Printf(
TEXT("f %d/%d/%d %d/%d/%d %d/%d/%d\n"),
A + 1, A + 1, A + 1,
B + 1, B + 1, B + 1,
C + 1, C + 1, C + 1));
++OutTrianglesWritten;
}
if (!bOk || Archive.IsError())
{
OutError = TEXT("Could not write the canonical aggregate OBJ.");
return false;
}
return true;
}
void WriteJsonVector(FExploreJsonWriter& Writer, const TCHAR* Key, const FVector& Value, float Scale)
{
Writer.WriteObjectStart(Key);
Writer.WriteValue(TEXT("x"), static_cast<double>(Value.X) * Scale);
Writer.WriteValue(TEXT("y"), static_cast<double>(Value.Y) * Scale);
Writer.WriteValue(TEXT("z"), static_cast<double>(Value.Z) * Scale);
Writer.WriteObjectEnd();
}
void WriteJsonIntVector(FExploreJsonWriter& Writer, const TCHAR* Key, const FIntVector& Value)
{
Writer.WriteObjectStart(Key);
Writer.WriteValue(TEXT("x"), Value.X);
Writer.WriteValue(TEXT("y"), Value.Y);
Writer.WriteValue(TEXT("z"), Value.Z);
Writer.WriteObjectEnd();
}
void WriteJsonBounds(
FExploreJsonWriter& Writer,
const TCHAR* Key,
const FIntVector& Origin,
int32 Size,
float VoxelToMeters)
{
Writer.WriteObjectStart(Key);
Writer.WriteObjectStart(TEXT("min"));
Writer.WriteValue(TEXT("x"), Origin.X);
Writer.WriteValue(TEXT("y"), Origin.Y);
Writer.WriteValue(TEXT("z"), Origin.Z);
Writer.WriteObjectEnd();
Writer.WriteObjectStart(TEXT("max_exclusive"));
Writer.WriteValue(TEXT("x"), Origin.X + Size);
Writer.WriteValue(TEXT("y"), Origin.Y + Size);
Writer.WriteValue(TEXT("z"), Origin.Z + Size);
Writer.WriteObjectEnd();
Writer.WriteValue(TEXT("units"), TEXT("voxels"));
Writer.WriteValue(TEXT("voxel_size_m"), static_cast<double>(VoxelToMeters));
Writer.WriteObjectEnd();
}
void WriteSurfaceReliefMetrics(
FExploreJsonWriter& Writer,
const FExploreMeshMetrics& Metrics)
{
Writer.WriteObjectStart(TEXT("surface_relief_metrics"));
Writer.WriteValue(TEXT("vertex_count"), Metrics.VertexCount);
Writer.WriteValue(TEXT("triangle_count"), Metrics.TriangleCount);
Writer.WriteValue(TEXT("shared_edge_count"), Metrics.SharedEdgeCount);
Writer.WriteValue(TEXT("dihedral_sample_count"), Metrics.DihedralSampleCount);
Writer.WriteValue(TEXT("normal_comparison_triangle_count"), Metrics.NormalComparisonTriangleCount);
Writer.WriteValue(TEXT("vertex_weld_quantum_cm"), 0.01);
Writer.WriteObjectStart(TEXT("triangle_edge_length_m"));
Writer.WriteValue(TEXT("p50"), static_cast<double>(Metrics.EdgeLengthP50Meters));
Writer.WriteValue(TEXT("p99"), static_cast<double>(Metrics.EdgeLengthP99Meters));
Writer.WriteValue(TEXT("max"), static_cast<double>(Metrics.EdgeLengthMaxMeters));
Writer.WriteObjectEnd();
Writer.WriteObjectStart(TEXT("dihedral_angle_degrees"));
Writer.WriteValue(TEXT("p50"), static_cast<double>(Metrics.DihedralP50Degrees));
Writer.WriteValue(TEXT("p75"), static_cast<double>(Metrics.DihedralP75Degrees));
Writer.WriteValue(TEXT("p90"), static_cast<double>(Metrics.DihedralP90Degrees));
Writer.WriteValue(TEXT("p95"), static_cast<double>(Metrics.DihedralP95Degrees));
Writer.WriteValue(TEXT("p99"), static_cast<double>(Metrics.DihedralP99Degrees));
Writer.WriteValue(TEXT("fraction_over_20_degrees"),
static_cast<double>(Metrics.DihedralFractionOver20Degrees));
Writer.WriteValue(TEXT("fraction_over_40_degrees"),
static_cast<double>(Metrics.DihedralFractionOver40Degrees));
Writer.WriteObjectEnd();
Writer.WriteObjectStart(TEXT("face_normal_vs_mean_vertex_normal_degrees"));
Writer.WriteValue(TEXT("p50"),
static_cast<double>(Metrics.FaceNormalVsMeanVertexNormalP50Degrees));
Writer.WriteValue(TEXT("p90"),
static_cast<double>(Metrics.FaceNormalVsMeanVertexNormalP90Degrees));
Writer.WriteObjectEnd();
Writer.WriteObjectEnd();
}
void WriteTunnelFloorOverrides(
FExploreJsonWriter& Writer,
const FExploreArguments& Arguments)
{
Writer.WriteObjectStart(TEXT("tunnel_floor_overrides"));
Writer.WriteValue(TEXT("legacy_enabled_override"), Arguments.bTunnelFloorEnabledOverride);
if (Arguments.bTunnelFloorEnabledOverride)
{
Writer.WriteValue(TEXT("legacy_enabled_value"), Arguments.TunnelFloorEnabled != 0);
}
Writer.WriteValue(TEXT("enabled_ignored"), true);
Writer.WriteValue(TEXT("terracing_override"), Arguments.bTunnelFloorTerracingOverride);
if (Arguments.bTunnelFloorTerracingOverride)
{
Writer.WriteValue(TEXT("terracing"), Arguments.TunnelFloorTerracing != 0);
}
Writer.WriteValue(TEXT("step_height_override"), Arguments.bTunnelFloorStepOverride);
if (Arguments.bTunnelFloorStepOverride)
{
Writer.WriteValue(TEXT("step_height_voxels"),
static_cast<double>(Arguments.TunnelFloorStep));
}
Writer.WriteValue(TEXT("max_ledge_height_override"), Arguments.bTunnelFloorMaxLedgeOverride);
if (Arguments.bTunnelFloorMaxLedgeOverride)
{
Writer.WriteValue(TEXT("max_ledge_height_voxels"),
static_cast<double>(Arguments.TunnelFloorMaxLedge));
}
Writer.WriteValue(TEXT("gentle_slope_override"), Arguments.bTunnelFloorGentleOverride);
if (Arguments.bTunnelFloorGentleOverride)
{
Writer.WriteValue(TEXT("gentle_slope"),
static_cast<double>(Arguments.TunnelFloorGentle));
Writer.WriteValue(TEXT("gentle_slope_units"), TEXT("degrees"));
}
Writer.WriteValue(TEXT("ledge_count_override"), Arguments.bTunnelFloorLedgesOverride);
if (Arguments.bTunnelFloorLedgesOverride)
{
Writer.WriteValue(TEXT("ledge_count"), Arguments.TunnelFloorLedges);
}
Writer.WriteValue(TEXT("max_ledges_override"), Arguments.bTunnelFloorMaxLedgesOverride);
if (Arguments.bTunnelFloorMaxLedgesOverride)
{
Writer.WriteValue(TEXT("max_ledges"), Arguments.TunnelFloorMaxLedges);
}
Writer.WriteValue(TEXT("passage_support_floor_writes_override"),
Arguments.bPassageFloorWritesOverride);
if (Arguments.bPassageFloorWritesOverride)
{
Writer.WriteValue(TEXT("passage_support_floor_writes"),
Arguments.PassageFloorWrites != 0);
}
Writer.WriteObjectEnd();
}
FString BuildManifestJson(
const FExploreArguments& Arguments,
const FExploreExportOutput& Export)
{
FString Json;
TSharedRef<FExploreJsonWriter> Writer =
TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create(&Json);
Writer->WriteObjectStart();
Writer->WriteValue(TEXT("schema_version"), 7);
Writer->WriteValue(TEXT("format"), TEXT("OBJ"));
Writer->WriteValue(TEXT("mesh_file"), Export.MeshFileName);
Writer->WriteValue(TEXT("mesh_positions_units"), TEXT("metres"));
Writer->WriteValue(TEXT("mesh_uv_units"), TEXT("voxels"));
Writer->WriteValue(TEXT("load_notes"), TEXT(
"Load geometry.obj directly in a browser OBJ loader; do not regenerate density. "
"The manifest bounds are max-exclusive voxel bounds, OBJ positions are metres, and OBJ UVs "
"are the canonical voxel-space UVs."));
Writer->WriteValue(TEXT("skirts"), false);
Writer->WriteValue(TEXT("vertex_count"), Export.VertexCount);
Writer->WriteValue(TEXT("triangle_count"), Export.TriangleCount);
Writer->WriteValue(TEXT("geometry_hash"), Export.GeometryHash);
WriteSurfaceReliefMetrics(*Writer, Export.SurfaceMetrics);
Writer->WriteValue(TEXT("mesh_file_size_bytes"), Export.MeshFileSizeBytes);
Writer->WriteValue(TEXT("mesh_array_bytes_sum_over_tiles"), Export.MeshArrayBytes);
Writer->WriteValue(TEXT("canonical_mesher_tile_cells"), CHUNK_SIZE);
Writer->WriteValue(TEXT("canonical_mesher_tile_count"), Export.MesherTileCount);
Writer->WriteValue(TEXT("canonical_mesher_tiles_completed"), Export.MesherTilesCompleted);
Writer->WriteValue(TEXT("mesh_seconds"), Export.MeshSeconds);
Writer->WriteValue(TEXT("mesh_us_per_voxel"), Export.MeshUsPerVoxel);
Writer->WriteValue(TEXT("export_step"), Arguments.ExportStep);
Writer->WriteValue(TEXT("truncated"), Export.bTruncated);
Writer->WriteValue(TEXT("seed"), Arguments.Seed);
Writer->WriteValue(TEXT("archetype"), ArchetypeName(Arguments.Archetype));
Writer->WriteValue(TEXT("slot"), Arguments.Slot);
Writer->WriteValue(TEXT("worms_enabled_override"), Arguments.bWormsEnabledOverride);
if (Arguments.bWormsEnabledOverride)
{
Writer->WriteValue(TEXT("worms_enabled"), Arguments.bWormsEnabled);
}
Writer->WriteValue(TEXT("worm_strength_override"), Arguments.bWormStrengthOverride);
if (Arguments.bWormStrengthOverride)
{
Writer->WriteValue(TEXT("worm_strength"), static_cast<double>(Arguments.WormStrength));
}
Writer->WriteValue(TEXT("tunnel_ablation_mask"),
static_cast<int64>(VoxelDensityAblation::GetResolvedMask()));
WriteTunnelFloorOverrides(*Writer, Arguments);
Writer->WriteValue(TEXT("room_mouth_rise_override"), Arguments.bRoomMouthRiseOverride);
if (Arguments.bRoomMouthRiseOverride)
{
Writer->WriteValue(TEXT("room_mouth_rise_strength"),
static_cast<double>(Arguments.RoomMouthRise));
}
Writer->WriteValue(TEXT("room_mouth_rise_blend_override"),
Arguments.bRoomMouthRiseBlendOverride);
if (Arguments.bRoomMouthRiseBlendOverride)
{
Writer->WriteValue(TEXT("room_mouth_rise_blend_voxels"),
static_cast<double>(Arguments.RoomMouthRiseBlend));
}
Writer->WriteValue(TEXT("surface_roughness_override"), Arguments.bSurfaceRoughnessOverride);
if (Arguments.bSurfaceRoughnessOverride)
{
Writer->WriteValue(TEXT("surface_roughness"),
static_cast<double>(Arguments.SurfaceRoughness));
}
Writer->WriteValue(TEXT("roughness_noise_type_override"),
Arguments.bRoughnessNoiseTypeOverride);
if (Arguments.bRoughnessNoiseTypeOverride)
{
Writer->WriteValue(TEXT("roughness_noise_type"),
NoiseTypeName(Arguments.RoughnessNoiseType));
}
Writer->WriteValue(TEXT("roughness_frequency_override"),
Arguments.bRoughnessFrequencyOverride);
if (Arguments.bRoughnessFrequencyOverride)
{
Writer->WriteValue(TEXT("roughness_frequency"),
static_cast<double>(Arguments.RoughnessFrequency));
}
Writer->WriteValue(TEXT("render_camera_override"), Arguments.bRenderCameraOverride);
if (Arguments.bRenderCameraOverride)
{
WriteJsonVector(*Writer, TEXT("render_camera_voxels"), Arguments.RenderCamera, 1.0f);
}
WriteJsonIntVector(*Writer, TEXT("region_origin_voxels"), Export.RegionOrigin);
WriteJsonBounds(*Writer, TEXT("region_bounds_voxels"), Export.RegionOrigin, Export.RegionSize, VOXEL_SIZE / 100.0f);
Writer->WriteObjectStart(TEXT("region_bounds_metres"));
Writer->WriteObjectStart(TEXT("min"));
Writer->WriteValue(TEXT("x"), static_cast<double>(Export.RegionOrigin.X) * VOXEL_SIZE / 100.0);
Writer->WriteValue(TEXT("y"), static_cast<double>(Export.RegionOrigin.Y) * VOXEL_SIZE / 100.0);
Writer->WriteValue(TEXT("z"), static_cast<double>(Export.RegionOrigin.Z) * VOXEL_SIZE / 100.0);
Writer->WriteObjectEnd();
Writer->WriteObjectStart(TEXT("max_exclusive"));
Writer->WriteValue(TEXT("x"), static_cast<double>(Export.RegionOrigin.X + Export.RegionSize) * VOXEL_SIZE / 100.0);
Writer->WriteValue(TEXT("y"), static_cast<double>(Export.RegionOrigin.Y + Export.RegionSize) * VOXEL_SIZE / 100.0);
Writer->WriteValue(TEXT("z"), static_cast<double>(Export.RegionOrigin.Z + Export.RegionSize) * VOXEL_SIZE / 100.0);
Writer->WriteObjectEnd();
Writer->WriteValue(TEXT("units"), TEXT("metres"));
Writer->WriteObjectEnd();
Writer->WriteObjectStart(TEXT("player_dimensions"));
Writer->WriteValue(TEXT("capsule_radius_m"), static_cast<double>(FVoxelPlayerCapsuleConstants::RadiusCentimeters) / 100.0);
Writer->WriteValue(TEXT("capsule_width_m"), static_cast<double>(2.0f * FVoxelPlayerCapsuleConstants::RadiusCentimeters) / 100.0);
Writer->WriteValue(TEXT("capsule_half_height_m"), static_cast<double>(FVoxelPlayerCapsuleConstants::HalfHeightCentimeters) / 100.0);
Writer->WriteValue(TEXT("capsule_height_m"), static_cast<double>(2.0f * FVoxelPlayerCapsuleConstants::HalfHeightCentimeters) / 100.0);
Writer->WriteValue(TEXT("max_step_height_m"), static_cast<double>(FVoxelPlayerCapsuleConstants::MaxStepHeightMeters));
Writer->WriteObjectEnd();
Writer->WriteObjectStart(TEXT("world_invariants"));
Writer->WriteValue(TEXT("world_radius_voxels"), 0);
Writer->WriteValue(TEXT("lateral_regions_enabled"), false);
Writer->WriteValue(TEXT("diff_layer_mutated"), false);
Writer->WriteObjectEnd();
Writer->WriteObjectEnd();
return Writer->Close() ? Json : FString();
}
bool RunExport(
const FExploreArguments& Arguments,
FExploreWorld& World,
const FExploreWalkOutput* SharedSampling,
FExploreExportOutput& OutOutput,
FExploreBudget& Budget)
{
OutOutput = FExploreExportOutput();
const double ExportStartSeconds = FPlatformTime::Seconds();
OutOutput.RegionSize = Arguments.ExportSize;
OutOutput.EstimatedWorkingBytes = EstimateExportWorkingBytes(Arguments.ExportSize);
if (OutOutput.EstimatedWorkingBytes > OutOutput.WorkingMemoryCapBytes)
{
OutOutput.Status = TEXT("refused");
OutOutput.RefusalReason = FString::Printf(
TEXT("Refused before meshing: estimated working memory %lld bytes exceeds cap %lld bytes."),
static_cast<long long>(OutOutput.EstimatedWorkingBytes),
static_cast<long long>(OutOutput.WorkingMemoryCapBytes));
return false;
}
FString MeshError;
const bool bMeshComplete = EnsureExploreMesh(
Arguments, World, SharedSampling, Budget, MeshError);
if (!bMeshComplete && !Budget.bTruncated)
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = MeshError;
return false;
}
OutOutput.RegionSize = World.ExploreMeshSize;
OutOutput.MesherTileCount = World.ExploreMeshTileCount;
OutOutput.MesherTilesCompleted = World.ExploreMeshTilesCompleted;
OutOutput.RegionOrigin = World.ExploreMeshOrigin;
FString Error;
OutOutput.MeshFileName = TEXT("geometry.obj");
OutOutput.ManifestFileName = TEXT("manifest.json");
const FString MeshPath = FPaths::Combine(Arguments.OutDirectory, OutOutput.MeshFileName);
TUniquePtr<FArchive> Archive(IFileManager::Get().CreateFileWriter(*MeshPath));
if (!Archive.IsValid())
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = FString::Printf(TEXT("Could not create OBJ '%s'."), *MeshPath);
return false;
}
const double ObjWriteStartSeconds = FPlatformTime::Seconds();
if (!WriteObjHeader(*Archive))
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = TEXT("Could not write the OBJ header.");
return false;
}
const double MeasureStartSeconds = FPlatformTime::Seconds();
if (!MeasureExploreMesh(World.ExploreMesh, OutOutput.SurfaceMetrics, Error))
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = Error;
return false;
}
OutOutput.MeasureSeconds = FPlatformTime::Seconds() - MeasureStartSeconds;
OutOutput.MeshArrayBytes = static_cast<int64>(World.ExploreMesh.Vertices.Num()) * sizeof(FVector)
+ static_cast<int64>(World.ExploreMesh.Normals.Num()) * sizeof(FVector)
+ static_cast<int64>(World.ExploreMesh.UVs.Num()) * sizeof(FVector2D)
+ static_cast<int64>(World.ExploreMesh.Colors.Num()) * sizeof(FColor)
+ static_cast<int64>(World.ExploreMesh.Triangles.Num()) * sizeof(int32);
OutOutput.VertexCount = World.ExploreMesh.Vertices.Num();
OutOutput.TriangleCount = World.ExploreMesh.Triangles.Num() / 3;
OutOutput.MeshSeconds = World.ExploreMeshSeconds;
OutOutput.MeshUsPerVoxel = Arguments.ExportSize > 0
? OutOutput.MeshSeconds * 1.0e6
/ static_cast<double>(Arguments.ExportSize)
/ static_cast<double>(Arguments.ExportSize)
/ static_cast<double>(Arguments.ExportSize)
: 0.0;
OutOutput.GeometryHash = World.ExploreGeometryHash;
bool bObjWriteTruncated = false;
int32 VerticesWritten = 0;
int32 TrianglesWritten = 0;
if (!WriteObjMesh(
*Archive,
World.ExploreMesh,
Budget,
Error,
bObjWriteTruncated,
VerticesWritten,
TrianglesWritten))
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = Error;
return false;
}
if (Archive->IsError())
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = TEXT("Could not finish the streamed OBJ.");
return false;
}
Archive.Reset();
OutOutput.MeshFileSizeBytes = IFileManager::Get().FileSize(*MeshPath);
if (OutOutput.MeshFileSizeBytes < 0)
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = TEXT("OBJ was written but its file size could not be read.");
return false;
}
OutOutput.ObjWriteSeconds = FPlatformTime::Seconds() - ObjWriteStartSeconds;
OutOutput.bTruncated = Budget.bTruncated
|| !World.bExploreMeshComplete
|| bObjWriteTruncated;
if (OutOutput.bTruncated)
{
OutOutput.VertexCount = VerticesWritten;
OutOutput.TriangleCount = TrianglesWritten;
OutOutput.Status = TEXT("truncated");
OutOutput.RefusalReason = !World.bExploreMeshComplete
? MeshError
: TEXT("The wall-clock budget stopped export after writing a partial mesh.");
}
const double ManifestStartSeconds = FPlatformTime::Seconds();
const FString ManifestJson = BuildManifestJson(Arguments, OutOutput);
const FString ManifestJsonRepeat = BuildManifestJson(Arguments, OutOutput);
if (ManifestJson.IsEmpty() || ManifestJsonRepeat.IsEmpty())
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = TEXT("The export manifest JSON writer failed.");
return false;
}
if (ManifestJson != ManifestJsonRepeat)
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = TEXT("The export manifest failed its deterministic repeat assertion.");
return false;
}
if (!FFileHelper::SaveStringToFile(
ManifestJson,
*FPaths::Combine(Arguments.OutDirectory, OutOutput.ManifestFileName),
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = TEXT("Could not write export manifest.json.");
return false;
}
OutOutput.ManifestSeconds = FPlatformTime::Seconds() - ManifestStartSeconds;
OutOutput.ExportWallSeconds = FPlatformTime::Seconds() - ExportStartSeconds;
OutOutput.ExportAccountingRemainderSeconds = OutOutput.ExportWallSeconds
- (World.ExploreMeshSeconds
+ World.ExploreMeshHashSeconds
+ OutOutput.ObjWriteSeconds
+ OutOutput.ManifestSeconds);
if (!OutOutput.bTruncated)
{
OutOutput.Status = TEXT("ok");
}
return !OutOutput.bTruncated;
}
bool RunDensitySlice(
const FExploreArguments& Arguments,
FExploreWorld& World,
FExploreDensitySliceOutput& OutOutput,
FExploreBudget& Budget)
{
OutOutput = FExploreDensitySliceOutput();
OutOutput.FileName = TEXT("density_slice.csv");
OutOutput.SampleCount = Arguments.DensitySliceSamples;
const double StartSeconds = FPlatformTime::Seconds();
FString Csv;
Csv.Reserve(Arguments.DensitySliceSamples * 96);
Csv += TEXT("index,x_voxels,y_voxels,z_voxels,density\r\n");
float PreviousDensity = 0.0f;
bool bHavePreviousDensity = false;
float MinDensity = TNumericLimits<float>::Max();
float MaxDensity = -TNumericLimits<float>::Max();
float MaxAbsDensity = 0.0f;
for (int32 Index = 0; Index < Arguments.DensitySliceSamples; ++Index)
{
if ((Index & 127) == 0 && Budget.ShouldStop(TEXT("density-slice")))
{
OutOutput.Status = TEXT("truncated");
OutOutput.RefusalReason = TEXT("The wall-clock budget stopped the density slice.");
OutOutput.Seconds = FPlatformTime::Seconds() - StartSeconds;
return false;
}
const FVector Position = Arguments.DensitySliceOrigin
+ Arguments.DensitySliceDirection
* (Arguments.DensitySliceStep * static_cast<float>(Index));
const float Density = World.Generator->GetDensityAt(
Position.X, Position.Y, Position.Z);
Csv += FString::Printf(
TEXT("%d,%.9g,%.9g,%.9g,%.9g\r\n"),
Index, Position.X, Position.Y, Position.Z, Density);
if (!FMath::IsFinite(Density))
{
++OutOutput.NonFiniteSampleCount;
bHavePreviousDensity = false;
continue;
}
++OutOutput.FiniteSampleCount;
MinDensity = FMath::Min(MinDensity, Density);
MaxDensity = FMath::Max(MaxDensity, Density);
MaxAbsDensity = FMath::Max(MaxAbsDensity, FMath::Abs(Density));
if (FMath::Abs(Density) >= 0.999f)
{
++OutOutput.SaturatedSampleCount;
}
if (bHavePreviousDensity
&& ((PreviousDensity < 0.0f && Density > 0.0f)
|| (PreviousDensity > 0.0f && Density < 0.0f)))
{
++OutOutput.ZeroCrossingCount;
}
PreviousDensity = Density;
bHavePreviousDensity = true;
}
if (OutOutput.FiniteSampleCount > 0)
{
OutOutput.MinDensity = MinDensity;
OutOutput.MaxDensity = MaxDensity;
OutOutput.MaxAbsDensity = MaxAbsDensity;
}
const FString Path = FPaths::Combine(Arguments.OutDirectory, OutOutput.FileName);
if (!FFileHelper::SaveStringToFile(
Csv, *Path, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = TEXT("Could not write density_slice.csv.");
OutOutput.Seconds = FPlatformTime::Seconds() - StartSeconds;
return false;
}
OutOutput.Status = TEXT("ok");
OutOutput.Seconds = FPlatformTime::Seconds() - StartSeconds;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeDensitySlice] file=%s samples=%d finite=%d non_finite=%d "
"zero_crossings=%d saturated=%d min=%.9g max=%.9g max_abs=%.9g "
"origin=(%.3f,%.3f,%.3f) direction=(%.6f,%.6f,%.6f) step=%.6g"),
*Path, OutOutput.SampleCount, OutOutput.FiniteSampleCount,
OutOutput.NonFiniteSampleCount, OutOutput.ZeroCrossingCount,
OutOutput.SaturatedSampleCount, OutOutput.MinDensity,
OutOutput.MaxDensity, OutOutput.MaxAbsDensity,
Arguments.DensitySliceOrigin.X, Arguments.DensitySliceOrigin.Y,
Arguments.DensitySliceOrigin.Z, Arguments.DensitySliceDirection.X,
Arguments.DensitySliceDirection.Y, Arguments.DensitySliceDirection.Z,
Arguments.DensitySliceStep);
return true;
}
void ComputeTunnelCoreProbeMetrics(
const TArray<float>& Samples,
float StepVoxels,
int32& OutValidCount,
float& OutMean,
float& OutMin,
float& OutMax,
float& OutPeriodicAmplitudeVoxels,
float& OutPeriodicPeriodVoxels,
int32& OutPeriodicMeasuredSamples,
float& OutPeriodicMeasuredLengthVoxels,
bool bPolynomialTrend = false)
{
OutValidCount = 0;
OutMean = 0.0f;
OutMin = 0.0f;
OutMax = 0.0f;
OutPeriodicAmplitudeVoxels = 0.0f;
OutPeriodicPeriodVoxels = 0.0f;
OutPeriodicMeasuredSamples = 0;
OutPeriodicMeasuredLengthVoxels = 0.0f;
if (Samples.Num() < 4 || !FMath::IsFinite(StepVoxels) || StepVoxels <= 0.0f)
{
return;
}
double Sum = 0.0;
for (int32 Index = 0; Index < Samples.Num(); ++Index)
{
const float Value = Samples[Index];
if (!FMath::IsFinite(Value))
{
continue;
}
++OutValidCount;
Sum += Value;
if (OutValidCount == 1)
{
OutMin = Value;
OutMax = Value;
}
else
{
OutMin = FMath::Min(OutMin, Value);
OutMax = FMath::Max(OutMax, Value);
}
}
if (OutValidCount < 4)
{
return;
}
const double Count = static_cast<double>(OutValidCount);
OutMean = static_cast<float>(Sum / Count);
// Find the longest contiguous surface run. A missing radial crossing is not a zero-valued
// sample: it is a hole in the measurement and must not be bridged before the spectral pass.
int32 BestRunStart = INDEX_NONE;
int32 BestRunEnd = INDEX_NONE;
int32 RunStart = INDEX_NONE;
for (int32 Index = 0; Index <= Samples.Num(); ++Index)
{
const bool bFinite = Index < Samples.Num() && FMath::IsFinite(Samples[Index]);
if (bFinite)
{
if (RunStart == INDEX_NONE)
{
RunStart = Index;
}
continue;
}
if (RunStart != INDEX_NONE
&& (BestRunStart == INDEX_NONE
|| Index - RunStart > BestRunEnd - BestRunStart + 1))
{
BestRunStart = RunStart;
BestRunEnd = Index - 1;
}
RunStart = INDEX_NONE;
}
const int32 BestRunLength = BestRunStart != INDEX_NONE
? BestRunEnd - BestRunStart + 1
: 0;
// The requested acceptance window is at least 40 voxels. Keep the raw crossing statistics
// above for shorter runs, but explicitly mark their periodic result as unmeasured.
constexpr float RequiredLengthVoxels = 40.0f;
const int32 RequiredSampleCount = FMath::CeilToInt(RequiredLengthVoxels / StepVoxels) + 1;
if (BestRunLength < FMath::Max(4, RequiredSampleCount))
{
return;
}
// Remove only the slow authored floor/radius trend. A 32-voxel median window is wider than
// the 1..16 voxel rib band, so a genuine rib train survives while a single tunnel transition
// or route slope follows the local baseline. The mouth probe uses a degree-six fit instead: the
// room-side +/-2R window can contain one broad floor bowl, which a local median would turn
// into a false short-period peak when only the middle 21.5 voxels remain.
constexpr float TrendWindowVoxels = 32.0f;
const int32 HalfWindowSamples = FMath::Max(
1, FMath::RoundToInt(0.5f * TrendWindowVoxels / StepVoxels));
const int32 AnalysisStart = bPolynomialTrend
? BestRunStart : BestRunStart + HalfWindowSamples;
const int32 AnalysisEnd = bPolynomialTrend
? BestRunEnd : BestRunEnd - HalfWindowSamples;
const int32 AnalysisCount = AnalysisEnd - AnalysisStart + 1;
if (AnalysisCount < 4)
{
return;
}
OutPeriodicMeasuredSamples = AnalysisCount;
OutPeriodicMeasuredLengthVoxels = static_cast<float>(AnalysisCount - 1) * StepVoxels;
TArray<float, TInlineAllocator<257>> Residuals;
Residuals.SetNumZeroed(AnalysisCount);
bool bHavePolynomialTrend = false;
if (bPolynomialTrend)
{
// Fit a normalized degree-six smooth trend over the complete contiguous surface run. The
// room-side +/-2R sample can contain a broad, asymmetric floor bowl; a cubic leaves that
// endpoint curvature in the rib-band DFT. Degree six follows that slow authored shape but
// cannot reproduce a repeated <=16-voxel train across the full run. Keep the legacy
// local-median measurement separately as an audit of the conservative raw instrument.
constexpr int32 PolynomialDegree = 6;
double Matrix[PolynomialDegree + 1][PolynomialDegree + 1] = {};
double RightHandSide[PolynomialDegree + 1] = {};
const double Denominator = static_cast<double>(FMath::Max(
BestRunLength - 1, 1));
for (int32 Index = BestRunStart; Index <= BestRunEnd; ++Index)
{
const double NormalizedX = -1.0
+ 2.0 * static_cast<double>(Index - BestRunStart) / Denominator;
double Basis[PolynomialDegree + 1];
Basis[0] = 1.0;
for (int32 Power = 1; Power <= PolynomialDegree; ++Power)
{
Basis[Power] = Basis[Power - 1] * NormalizedX;
}
const double Value = static_cast<double>(Samples[Index]);
for (int32 Row = 0; Row <= PolynomialDegree; ++Row)
{
RightHandSide[Row] += Basis[Row] * Value;
for (int32 Column = 0; Column <= PolynomialDegree; ++Column)
{
Matrix[Row][Column] += Basis[Row] * Basis[Column];
}
}
}
double Coefficients[PolynomialDegree + 1] = {};
bHavePolynomialTrend = true;
for (int32 PivotIndex = 0; PivotIndex <= PolynomialDegree; ++PivotIndex)
{
int32 PivotRow = PivotIndex;
for (int32 Row = PivotIndex + 1; Row <= PolynomialDegree; ++Row)
{
if (FMath::Abs(Matrix[Row][PivotIndex])
> FMath::Abs(Matrix[PivotRow][PivotIndex]))
{
PivotRow = Row;
}
}
if (FMath::Abs(Matrix[PivotRow][PivotIndex]) <= 1.0e-12)
{
bHavePolynomialTrend = false;
break;
}
if (PivotRow != PivotIndex)
{
for (int32 Column = PivotIndex;
Column <= PolynomialDegree; ++Column)
{
Swap(Matrix[PivotIndex][Column], Matrix[PivotRow][Column]);
}
Swap(RightHandSide[PivotIndex], RightHandSide[PivotRow]);
}
for (int32 Row = PivotIndex + 1; Row <= PolynomialDegree; ++Row)
{
const double Factor = Matrix[Row][PivotIndex]
/ Matrix[PivotIndex][PivotIndex];
for (int32 Column = PivotIndex;
Column <= PolynomialDegree; ++Column)
{
Matrix[Row][Column] -= Factor * Matrix[PivotIndex][Column];
}
RightHandSide[Row] -= Factor * RightHandSide[PivotIndex];
}
}
if (bHavePolynomialTrend)
{
for (int32 Row = PolynomialDegree; Row >= 0; --Row)
{
double Value = RightHandSide[Row];
for (int32 Column = Row + 1;
Column <= PolynomialDegree; ++Column)
{
Value -= Matrix[Row][Column] * Coefficients[Column];
}
Coefficients[Row] = Value / Matrix[Row][Row];
}
for (int32 Index = AnalysisStart; Index <= AnalysisEnd; ++Index)
{
const double NormalizedX = -1.0
+ 2.0 * static_cast<double>(Index - BestRunStart) / Denominator;
double Basis = 1.0;
double Trend = 0.0;
for (int32 Power = 0; Power <= PolynomialDegree; ++Power)
{
Trend += Coefficients[Power] * Basis;
Basis *= NormalizedX;
}
Residuals[Index - AnalysisStart] =
static_cast<float>(static_cast<double>(Samples[Index]) - Trend);
}
}
}
constexpr float MaxRibPeriodVoxels = 16.0f;
const int32 MinRibFrequency = FMath::Max(
1, FMath::CeilToInt(StepVoxels * static_cast<float>(AnalysisCount)
/ MaxRibPeriodVoxels));
const int32 MaxFrequency = AnalysisCount / 2;
double BestAmplitude = 0.0;
int32 BestFrequency = 0;
for (int32 Frequency = MinRibFrequency; Frequency <= MaxFrequency; ++Frequency)
{
double Real = 0.0;
double Imaginary = 0.0;
for (int32 Index = AnalysisStart; Index <= AnalysisEnd; ++Index)
{
const int32 AnalysisIndex = Index - AnalysisStart;
if (!bHavePolynomialTrend)
{
TArray<float, TInlineAllocator<129>> Window;
const int32 WindowStart = FMath::Max(BestRunStart, Index - HalfWindowSamples);
const int32 WindowEnd = FMath::Min(BestRunEnd, Index + HalfWindowSamples);
Window.Reserve(WindowEnd - WindowStart + 1);
for (int32 WindowIndex = WindowStart; WindowIndex <= WindowEnd; ++WindowIndex)
{
Window.Add(Samples[WindowIndex]);
}
Window.Sort();
const int32 Middle = Window.Num() / 2;
const double Median = (Window.Num() & 1) != 0
? static_cast<double>(Window[Middle])
: 0.5 * (static_cast<double>(Window[Middle - 1])
+ static_cast<double>(Window[Middle]));
Residuals[AnalysisIndex] = static_cast<float>(
static_cast<double>(Samples[Index]) - Median);
}
const double Residual = static_cast<double>(Residuals[AnalysisIndex]);
const double Angle = 2.0 * PI * static_cast<double>(Frequency)
* static_cast<double>(AnalysisIndex) / static_cast<double>(AnalysisCount);
Real += Residual * FMath::Cos(static_cast<float>(Angle));
Imaginary -= Residual * FMath::Sin(static_cast<float>(Angle));
}
const double Amplitude = 2.0 * FMath::Sqrt(Real * Real + Imaginary * Imaginary)
/ static_cast<double>(AnalysisCount);
if (Amplitude > BestAmplitude)
{
BestAmplitude = Amplitude;
BestFrequency = Frequency;
}
}
OutPeriodicAmplitudeVoxels = static_cast<float>(BestAmplitude);
OutPeriodicPeriodVoxels = BestFrequency > 0
? StepVoxels * static_cast<float>(AnalysisCount)
/ static_cast<float>(BestFrequency)
: 0.0f;
}
void ComputeTunnelMouthProbeMetrics(
FExploreTunnelMouthProbeAxis& Axis,
float StepVoxels)
{
int32 LegacyValid = 0;
float LegacyMean = 0.0f;
float LegacyMin = 0.0f;
float LegacyMax = 0.0f;
int32 LegacyMeasuredSamples = 0;
float LegacyMeasuredLength = 0.0f;
ComputeTunnelCoreProbeMetrics(
Axis.CrossingsVoxels, StepVoxels,
Axis.ValidCrossings, Axis.MeanCrossingVoxels,
Axis.MinCrossingVoxels, Axis.MaxCrossingVoxels,
Axis.LocalMedianPeriodicAmplitudeVoxels,
Axis.LocalMedianPeriodicPeriodVoxels,
LegacyMeasuredSamples, LegacyMeasuredLength);
ComputeTunnelCoreProbeMetrics(
Axis.CrossingsVoxels, StepVoxels,
LegacyValid, LegacyMean, LegacyMin, LegacyMax,
Axis.PeriodicAmplitudeVoxels, Axis.PeriodicPeriodVoxels,
Axis.PeriodicMeasuredSamples, Axis.PeriodicMeasuredLengthVoxels,
/*bPolynomialTrend=*/true);
}
bool RunTunnelCoreProbe(
const FExploreArguments& Arguments,
FExploreWorld& World,
FExploreTunnelCoreProbeOutput& OutOutput,
FExploreBudget& Budget)
{
OutOutput = FExploreTunnelCoreProbeOutput();
OutOutput.FileName = TEXT("tunnel_core_probe.csv");
OutOutput.StepVoxels = 0.25f;
const double StartSeconds = FPlatformTime::Seconds();
if (!World.Manager.IsValid() || !World.Generator.IsValid()
|| !World.Settings.IsValid()
|| !World.Definitions.IsValidIndex(Arguments.Slot))
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = TEXT("The owner-value probe has no complete transient world.");
return false;
}
const int32 TargetBottomChunkZ = FMath::FloorToInt(
static_cast<float>(World.TargetBottomWorldZ) / static_cast<float>(CHUNK_SIZE));
const int32 TargetTopChunkZ = FMath::FloorToInt(
(static_cast<float>(World.TargetTopWorldZ) - KINDA_SMALL_NUMBER)
/ static_cast<float>(CHUNK_SIZE));
const float TargetMinZ = static_cast<float>(World.TargetBottomWorldZ);
const float TargetMaxZ = static_cast<float>(World.TargetTopWorldZ);
constexpr float TargetBandPadding = 8.0f;
constexpr float RouteParameterCheckStep = 4.0f;
// The owner path asks GetGenerationParams for the exact (x,y,z) chunk of every sample. A
// cache built from an arbitrary interior Z can therefore be a different graph in a boundary
// blend band; that was the source of the earlier probe's FLT_MAX/core-only result. Build the
// small set of candidate graphs used by the target Z chunks, and retain only a complete chain
// whose route and radial measurement band resolve to the same parameter fingerprint that was
// used to bake the graph. The field measured below is then the same field the game queries.
auto GenerationParamsFingerprint = [](const FStrateGenerationParams& InParams) -> uint32
{
return FCrc::MemCrc32(&InParams, sizeof(InParams));
};
FChunkSDFCache Cache;
FStrateGenerationParams Params;
int32 StrateIndex = INDEX_NONE;
int32 QueryChunkZ = INDEX_NONE;
const FCachedTunnel* BestTunnel = nullptr;
float BestLength = 0.0f;
int32 BestTunnelIndex = INDEX_NONE;
bool bBestTunnelFullyInTargetStrate = false;
int32 BestTargetBandPointCount = 0;
uint32 BestParamsFingerprint = 0xFFFFFFFFu;
const uint32 WorldSeed = static_cast<uint32>(World.Settings->Seed);
for (int32 CandidateChunkZ = TargetBottomChunkZ;
CandidateChunkZ <= TargetTopChunkZ;
++CandidateChunkZ)
{
if (Budget.ShouldStop(TEXT("tunnel-core-probe-cache")))
{
OutOutput.Status = TEXT("truncated");
OutOutput.RefusalReason = TEXT("The wall-clock budget stopped candidate cache construction.");
OutOutput.Seconds = FPlatformTime::Seconds() - StartSeconds;
return false;
}
const FIntVector CandidateChunk(0, 0, CandidateChunkZ);
const FStrateGenerationParams CandidateParams =
World.Manager->GetGenerationParams(CandidateChunk);
const int32 CandidateStrateIndex = World.Manager->GetStrateIndex(
(static_cast<float>(CandidateChunk.Z) + 0.5f)
* static_cast<float>(CHUNK_SIZE) * VOXEL_SIZE);
if (CandidateStrateIndex < 0
|| !(CandidateParams.RoomDensity > 0.0f)
|| !(CandidateParams.RoomSpacing > 0.0f))
{
continue;
}
const UVoxelStrateDefinition* CandidateDefinition =
World.Manager->GetStrateForChunk(CandidateChunk);
const TArray<FStrateTerrainOpEntry>* CandidateTerrainOps = CandidateDefinition != nullptr
? &CandidateDefinition->TerrainOperations : nullptr;
const uint32 CandidateFingerprint = GenerationParamsFingerprint(CandidateParams);
// The owner render region is centred near the origin. Keep each candidate cache broad
// enough for the local graph, exactly like the ordinary point-query cache window.
FChunkSDFCache CandidateCache;
VoxelCaveMorphology::BuildChunkCache(
CandidateCache, -256.0f, -256.0f, 256.0f, 256.0f,
CandidateParams, WorldSeed, CandidateStrateIndex,
CandidateTerrainOps, ERoomGraphBuildSite::GeneratorTunnelCore);
int32 CandidateBestTunnelIndex = INDEX_NONE;
float CandidateBestLength = 0.0f;
int32 CandidateBestTargetSampleCount = 0;
for (int32 TunnelIndex = 0; TunnelIndex < CandidateCache.Tunnels.Num(); ++TunnelIndex)
{
const FCachedTunnel& Tunnel = CandidateCache.Tunnels[TunnelIndex];
const TArray<FVector>& Points = Tunnel.WorldControlPoints.Num() >= 2
&& Tunnel.WorldControlRadii.Num() == Tunnel.WorldControlPoints.Num()
? Tunnel.WorldControlPoints : Tunnel.ControlPoints;
const TArray<float>& Radii = Tunnel.WorldControlRadii.Num() == Points.Num()
? Tunnel.WorldControlRadii : Tunnel.ControlRadii;
if (Points.Num() < 2 || Radii.Num() != Points.Num())
{
continue;
}
TArray<float> CandidateCumulativeLengths;
CandidateCumulativeLengths.SetNumZeroed(Points.Num());
bool bFinite = true;
float Length = 0.0f;
float MaxRadius = 0.0f;
for (int32 PointIndex = 0; PointIndex + 1 < Points.Num(); ++PointIndex)
{
const float SegmentLength = FVector::Dist(
Points[PointIndex], Points[PointIndex + 1]);
if (!FMath::IsFinite(SegmentLength))
{
bFinite = false;
break;
}
Length += SegmentLength;
CandidateCumulativeLengths[PointIndex + 1] = Length;
MaxRadius = FMath::Max(MaxRadius, FMath::Abs(Radii[PointIndex]));
}
if (Points.Num() > 0)
{
MaxRadius = FMath::Max(MaxRadius, FMath::Abs(Radii.Last()));
}
if (!bFinite || Length < 40.0f)
{
continue;
}
auto PointAtCandidateDistance = [&](float Distance) -> FVector
{
const float ClampedDistance = FMath::Clamp(Distance, 0.0f, Length);
for (int32 SegmentIndex = 0;
SegmentIndex + 1 < Points.Num();
++SegmentIndex)
{
if (ClampedDistance <= CandidateCumulativeLengths[SegmentIndex + 1]
|| SegmentIndex + 2 == Points.Num())
{
const float SegmentLength = CandidateCumulativeLengths[SegmentIndex + 1]
- CandidateCumulativeLengths[SegmentIndex];
const float Alpha = SegmentLength > KINDA_SMALL_NUMBER
? (ClampedDistance - CandidateCumulativeLengths[SegmentIndex])
/ SegmentLength : 0.0f;
return FMath::Lerp(Points[SegmentIndex], Points[SegmentIndex + 1], Alpha);
}
}
return Points.Last();
};
const float RadialParameterReach = FMath::Clamp(
MaxRadius + 12.0f, 12.0f, 64.0f);
bool bHasTargetSample = false;
bool bRouteMatchesParams = true;
int32 TargetSampleCount = 0;
const int32 ParameterSampleCount = FMath::CeilToInt(
Length / RouteParameterCheckStep);
for (int32 SampleIndex = 0;
SampleIndex <= ParameterSampleCount;
++SampleIndex)
{
const float Distance = FMath::Min(
Length, static_cast<float>(SampleIndex) * RouteParameterCheckStep);
const FVector Point = PointAtCandidateDistance(Distance);
const bool bInTargetBand = Point.Z >= TargetMinZ - TargetBandPadding
&& Point.Z <= TargetMaxZ + TargetBandPadding;
bHasTargetSample |= bInTargetBand;
TargetSampleCount += bInTargetBand ? 1 : 0;
if (!bInTargetBand)
{
bRouteMatchesParams = false;
break;
}
const FIntVector PointChunk(
FMath::FloorToInt(Point.X / static_cast<float>(CHUNK_SIZE)),
FMath::FloorToInt(Point.Y / static_cast<float>(CHUNK_SIZE)),
FMath::FloorToInt(Point.Z / static_cast<float>(CHUNK_SIZE)));
if (GenerationParamsFingerprint(
World.Manager->GetGenerationParams(PointChunk)) != CandidateFingerprint)
{
bRouteMatchesParams = false;
break;
}
// The floor, wall and ceiling roots are measured around this same centerline.
// Reject a route whose radial band crosses into a different parameter blend; a
// single cache cannot represent that discontinuous query context.
for (float RadialOffset = -RadialParameterReach;
RadialOffset <= RadialParameterReach + KINDA_SMALL_NUMBER;
RadialOffset += 8.0f)
{
const FVector RadialPoint = Point + FVector(0.0f, 0.0f, RadialOffset);
const FIntVector RadialChunk(
FMath::FloorToInt(RadialPoint.X / static_cast<float>(CHUNK_SIZE)),
FMath::FloorToInt(RadialPoint.Y / static_cast<float>(CHUNK_SIZE)),
FMath::FloorToInt(RadialPoint.Z / static_cast<float>(CHUNK_SIZE)));
if (GenerationParamsFingerprint(
World.Manager->GetGenerationParams(RadialChunk))
!= CandidateFingerprint)
{
bRouteMatchesParams = false;
break;
}
}
if (!bRouteMatchesParams)
{
break;
}
}
if (bHasTargetSample && bRouteMatchesParams
&& (TargetSampleCount > CandidateBestTargetSampleCount
|| (TargetSampleCount == CandidateBestTargetSampleCount
&& Length > CandidateBestLength)))
{
CandidateBestTunnelIndex = TunnelIndex;
CandidateBestLength = Length;
CandidateBestTargetSampleCount = TargetSampleCount;
}
}
if (CandidateBestTunnelIndex != INDEX_NONE
&& CandidateBestLength > BestLength)
{
Cache = MoveTemp(CandidateCache);
Params = CandidateParams;
StrateIndex = CandidateStrateIndex;
QueryChunkZ = CandidateChunkZ;
BestLength = CandidateBestLength;
BestTunnelIndex = CandidateBestTunnelIndex;
BestTargetBandPointCount = CandidateBestTargetSampleCount;
bBestTunnelFullyInTargetStrate = true;
BestParamsFingerprint = CandidateFingerprint;
}
}
BestTunnel = Cache.Tunnels.IsValidIndex(BestTunnelIndex)
? &Cache.Tunnels[BestTunnelIndex] : nullptr;
if (BestTunnel == nullptr || BestLength < 40.0f)
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = FString::Printf(
TEXT("No cached owner tunnel had a 40-voxel span with stable game params "
"and a stable radial band (best=%.3f)."), BestLength);
OutOutput.Seconds = FPlatformTime::Seconds() - StartSeconds;
return false;
}
const TArray<FVector>& Points = BestTunnel->WorldControlPoints.Num() >= 2
&& BestTunnel->WorldControlRadii.Num() == BestTunnel->WorldControlPoints.Num()
? BestTunnel->WorldControlPoints : BestTunnel->ControlPoints;
TArray<float> CumulativeLengths;
CumulativeLengths.SetNumZeroed(Points.Num());
for (int32 PointIndex = 0; PointIndex + 1 < Points.Num(); ++PointIndex)
{
CumulativeLengths[PointIndex + 1] = CumulativeLengths[PointIndex]
+ FVector::Dist(Points[PointIndex], Points[PointIndex + 1]);
}
const float ProbeStep = OutOutput.StepVoxels;
const int32 MaxProbeSamples = 4097;
float ProbeStart = 0.0f;
float ProbeLength = FMath::Min(
BestLength, static_cast<float>(MaxProbeSamples - 1) * ProbeStep);
auto PointAtDistance = [&](float Distance) -> FVector
{
const float ClampedDistance = FMath::Clamp(Distance, 0.0f, BestLength);
for (int32 SegmentIndex = 0; SegmentIndex + 1 < Points.Num(); ++SegmentIndex)
{
if (ClampedDistance <= CumulativeLengths[SegmentIndex + 1]
|| SegmentIndex + 2 == Points.Num())
{
const float SegmentLength = CumulativeLengths[SegmentIndex + 1]
- CumulativeLengths[SegmentIndex];
const float Alpha = SegmentLength > KINDA_SMALL_NUMBER
? (ClampedDistance - CumulativeLengths[SegmentIndex]) / SegmentLength : 0.0f;
return FMath::Lerp(Points[SegmentIndex], Points[SegmentIndex + 1], Alpha);
}
}
return Points.Last();
};
// Keep a long, endpoint-inset window on the selected tunnel chain. The DFT is intended to
// expose a repeated radial surface displacement, so using the entire wandering chain would
// mix mouth overlap and changes in the local frame into that signal. The center window stays
// on the same owner-parameter band already validated above and is at least 40 voxels long.
const float MaximumWindowLength = static_cast<float>(MaxProbeSamples - 1) * ProbeStep;
if (BestLength >= 56.0f)
{
ProbeLength = FMath::Min(96.0f, FMath::Min(BestLength - 16.0f, MaximumWindowLength));
if (ProbeLength >= 40.0f)
{
ProbeStart = FMath::Clamp(
(BestLength - ProbeLength) * 0.5f, 8.0f, BestLength - ProbeLength - 8.0f);
}
}
if (ProbeStart + ProbeLength > BestLength)
{
ProbeStart = 0.0f;
ProbeLength = FMath::Min(BestLength, MaximumWindowLength);
}
const int32 SampleCount = FMath::FloorToInt(ProbeLength / ProbeStep) + 1;
ProbeLength = static_cast<float>(SampleCount - 1) * ProbeStep;
OutOutput.TunnelIndex = BestTunnelIndex;
OutOutput.SampleCount = SampleCount;
OutOutput.LengthVoxels = ProbeLength;
FVector AxisDirection = PointAtDistance(ProbeStart + ProbeLength)
- PointAtDistance(ProbeStart);
AxisDirection.Z = 0.0f;
if (!AxisDirection.Normalize())
{
float LongestSegment = 0.0f;
for (int32 SegmentIndex = 0; SegmentIndex + 1 < Points.Num(); ++SegmentIndex)
{
FVector Candidate = Points[SegmentIndex + 1] - Points[SegmentIndex];
Candidate.Z = 0.0f;
const float SegmentLength = Candidate.Size();
if (SegmentLength > LongestSegment)
{
LongestSegment = SegmentLength;
AxisDirection = Candidate / SegmentLength;
}
}
}
if (!AxisDirection.Normalize())
{
AxisDirection = FVector::ForwardVector;
}
const FVector WallDirection(-AxisDirection.Y, AxisDirection.X, 0.0f);
OutOutput.AxisStart = PointAtDistance(ProbeStart);
OutOutput.AxisEnd = PointAtDistance(ProbeStart + ProbeLength);
OutOutput.AxisDirection = AxisDirection;
OutOutput.WallDirection = WallDirection;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeTunnelCoreProbe] cache_tunnels=%d strate_index=%d "
"query_chunk_z=%d target_z=[%.3f,%.3f] params_z=[%.3f,%.3f] selected=%d "
"params_fp=0x%08X window_start=%.3f window_length=%.3f "
"selected_fully_in_target=%d target_points=%d"),
Cache.Tunnels.Num(), StrateIndex, QueryChunkZ, TargetMinZ, TargetMaxZ,
Params.StrateBottomWorldZ, Params.StrateTopWorldZ, BestTunnelIndex,
BestParamsFingerprint, ProbeStart, ProbeLength,
bBestTunnelFullyInTargetStrate ? 1 : 0, BestTargetBandPointCount);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeTunnelCoreProbe] selected_endpoints a=(%.3f,%.3f,%.3f) "
"b=(%.3f,%.3f,%.3f) world_min=(%.3f,%.3f,%.3f) "
"world_max=(%.3f,%.3f,%.3f) world_radius=%.3f"),
BestTunnel->EndpointA.X, BestTunnel->EndpointA.Y, BestTunnel->EndpointA.Z,
BestTunnel->EndpointB.X, BestTunnel->EndpointB.Y, BestTunnel->EndpointB.Z,
BestTunnel->WorldCenterlineMin.X, BestTunnel->WorldCenterlineMin.Y,
BestTunnel->WorldCenterlineMin.Z, BestTunnel->WorldCenterlineMax.X,
BestTunnel->WorldCenterlineMax.Y, BestTunnel->WorldCenterlineMax.Z,
FMath::Sqrt(FMath::Max(BestTunnel->WorldBoundRadiusSq, 0.0f)));
float MaxRadius = 0.0f;
const TArray<float>& Radii = BestTunnel->WorldControlRadii.Num() == Points.Num()
? BestTunnel->WorldControlRadii : BestTunnel->ControlRadii;
for (const float Radius : Radii)
{
MaxRadius = FMath::Max(MaxRadius, FMath::Abs(Radius));
}
const float MaxRadialDistance = FMath::Clamp(MaxRadius + 12.0f, 12.0f, 64.0f);
auto FindZeroCrossing = [&](const FVector& Origin, const FVector& Direction,
bool bCoreSDF, float& OutDistance) -> bool
{
auto Evaluate = [&](float Distance) -> float
{
const FVector Position = Origin + Direction * Distance;
return bCoreSDF
? VoxelCaveMorphology::EvaluateTunnelCoreWorldSDF(
Position.X, Position.Y, Position.Z, Cache, true)
: World.Generator->GetDensityAt(Position.X, Position.Y, Position.Z);
};
float PreviousDistance = 0.0f;
float PreviousValue = Evaluate(0.0f);
bool bHavePrevious = FMath::IsFinite(PreviousValue);
if (bHavePrevious && FMath::Abs(PreviousValue) <= 1.0e-4f)
{
OutDistance = 0.0f;
return true;
}
const int32 RadialSteps = FMath::CeilToInt(MaxRadialDistance / ProbeStep);
for (int32 StepIndex = 1; StepIndex <= RadialSteps; ++StepIndex)
{
const float CurrentDistance = FMath::Min(
MaxRadialDistance, static_cast<float>(StepIndex) * ProbeStep);
const float CurrentValue = Evaluate(CurrentDistance);
if (!FMath::IsFinite(CurrentValue))
{
// A transient invalid query must split the finite run, not poison every
// subsequent sample. The floor ray can cross a chunk/parameter seam where
// one exact fractional query is unavailable; later finite samples still form a
// valid zero-crossing interval and are deliberately allowed to restart it.
bHavePrevious = false;
continue;
}
if (!bHavePrevious)
{
PreviousDistance = CurrentDistance;
PreviousValue = CurrentValue;
bHavePrevious = true;
if (FMath::Abs(CurrentValue) <= 1.0e-4f)
{
OutDistance = CurrentDistance;
return true;
}
continue;
}
const bool bCrossed = (PreviousValue < 0.0f && CurrentValue >= 0.0f)
|| (PreviousValue > 0.0f && CurrentValue <= 0.0f);
if (bCrossed)
{
float Low = PreviousDistance;
float High = CurrentDistance;
float LowValue = PreviousValue;
for (int32 Iteration = 0; Iteration < 24; ++Iteration)
{
const float Mid = (Low + High) * 0.5f;
const float MidValue = Evaluate(Mid);
if (!FMath::IsFinite(MidValue))
{
break;
}
if (FMath::Abs(MidValue) <= 1.0e-5f)
{
Low = Mid;
High = Mid;
break;
}
if ((LowValue < 0.0f && MidValue < 0.0f)
|| (LowValue > 0.0f && MidValue > 0.0f))
{
Low = Mid;
LowValue = MidValue;
}
else
{
High = Mid;
}
}
OutDistance = (Low + High) * 0.5f;
return true;
}
PreviousDistance = CurrentDistance;
PreviousValue = CurrentValue;
}
return false;
};
OutOutput.Floor.Name = TEXT("floor");
OutOutput.Wall.Name = TEXT("wall");
OutOutput.Ceiling.Name = TEXT("ceiling");
OutOutput.Floor.CoreCrossingsVoxels.Reserve(SampleCount);
OutOutput.Floor.FinalCrossingsVoxels.Reserve(SampleCount);
OutOutput.Wall.CoreCrossingsVoxels.Reserve(SampleCount);
OutOutput.Wall.FinalCrossingsVoxels.Reserve(SampleCount);
OutOutput.Ceiling.CoreCrossingsVoxels.Reserve(SampleCount);
OutOutput.Ceiling.FinalCrossingsVoxels.Reserve(SampleCount);
FString Csv;
Csv.Reserve(SampleCount * 180);
Csv += TEXT("sample,axis_distance_voxels,center_x,center_y,center_z,core_center_sdf,final_center_density,"
"floor_core_zero,floor_final_zero,wall_core_zero,wall_final_zero,"
"ceiling_core_zero,ceiling_final_zero\r\n");
float PreviousCoreCenter = 0.0f;
float PreviousFinalCenter = 0.0f;
bool bHavePreviousCoreCenter = false;
bool bHavePreviousFinalCenter = false;
float MaxGeneratorCoreDelta = 0.0f;
int32 GeneratorCoreMismatchCount = 0;
const uint32 ProbeLayoutVersion = World.Manager->GetLayoutVersion();
for (int32 SampleIndex = 0; SampleIndex < SampleCount; ++SampleIndex)
{
if ((SampleIndex & 127) == 0 && Budget.ShouldStop(TEXT("tunnel-core-probe")))
{
OutOutput.Status = TEXT("truncated");
OutOutput.RefusalReason = TEXT("The wall-clock budget stopped the tunnel-core probe.");
OutOutput.Seconds = FPlatformTime::Seconds() - StartSeconds;
return false;
}
const float AxisDistance = static_cast<float>(SampleIndex) * ProbeStep;
const FVector Center = PointAtDistance(ProbeStart + AxisDistance);
const float CoreCenter = VoxelCaveMorphology::EvaluateTunnelCoreWorldSDF(
Center.X, Center.Y, Center.Z, Cache, true);
const float FinalCenter = World.Generator->GetDensityAt(Center.X, Center.Y, Center.Z);
FTunnelCoreWorldEvaluation GeneratorCoreEvaluation;
World.Generator->GetDensityWithParams(
Center.X, Center.Y, Center.Z, Params, BestParamsFingerprint, ProbeLayoutVersion,
/*bApplyLegacyStructuralPosts=*/false, &GeneratorCoreEvaluation,
/*bCollectFusedDiagnostics=*/false);
if (FMath::IsFinite(CoreCenter) && FMath::IsFinite(GeneratorCoreEvaluation.SDF))
{
const float CoreDelta = FMath::Abs(CoreCenter - GeneratorCoreEvaluation.SDF);
MaxGeneratorCoreDelta = FMath::Max(MaxGeneratorCoreDelta, CoreDelta);
if (CoreDelta > 1.0e-3f)
{
++GeneratorCoreMismatchCount;
}
}
if (SampleIndex == 0 || SampleIndex == SampleCount / 2 || SampleIndex + 1 == SampleCount)
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeTunnelCoreProbe] generator_core_compare sample=%d "
"probe_sdf=%.6g generator_sdf=%.6g generator_mc=%.6g final_mc=%.6g"),
SampleIndex, CoreCenter, GeneratorCoreEvaluation.SDF,
-GeneratorCoreEvaluation.SDF, FinalCenter);
}
if (FMath::IsFinite(CoreCenter))
{
++OutOutput.CoreCenterFiniteSampleCount;
OutOutput.CoreCenterMin = OutOutput.CoreCenterFiniteSampleCount == 1
? CoreCenter : FMath::Min(OutOutput.CoreCenterMin, CoreCenter);
OutOutput.CoreCenterMax = OutOutput.CoreCenterFiniteSampleCount == 1
? CoreCenter : FMath::Max(OutOutput.CoreCenterMax, CoreCenter);
if (bHavePreviousCoreCenter
&& ((PreviousCoreCenter < 0.0f && CoreCenter > 0.0f)
|| (PreviousCoreCenter > 0.0f && CoreCenter < 0.0f)))
{
++OutOutput.CoreCenterZeroCrossingCount;
}
PreviousCoreCenter = CoreCenter;
bHavePreviousCoreCenter = true;
}
if (FMath::IsFinite(FinalCenter))
{
++OutOutput.FinalCenterFiniteSampleCount;
OutOutput.FinalCenterMin = OutOutput.FinalCenterFiniteSampleCount == 1
? FinalCenter : FMath::Min(OutOutput.FinalCenterMin, FinalCenter);
OutOutput.FinalCenterMax = OutOutput.FinalCenterFiniteSampleCount == 1
? FinalCenter : FMath::Max(OutOutput.FinalCenterMax, FinalCenter);
OutOutput.FinalCenterMaxAbs = FMath::Max(
OutOutput.FinalCenterMaxAbs, FMath::Abs(FinalCenter));
// The old hand-off wrote a fixed MC-air value (normally +16, with the reciprocal
// solid plateau at -8 in other definitions). Do not call every ordinary SDF sample
// "saturated"; count only the known clamp plateaus so the probe distinguishes the
// old hard switch from the continuous signed field.
const bool bKnownClampPlateau =
FMath::Abs(FinalCenter - 8.0f) <= 1.0e-3f
|| FMath::Abs(FinalCenter + 8.0f) <= 1.0e-3f
|| FMath::Abs(FinalCenter - 16.0f) <= 1.0e-3f
|| FMath::Abs(FinalCenter + 16.0f) <= 1.0e-3f;
if (bKnownClampPlateau)
{
++OutOutput.FinalCenterSaturatedSampleCount;
}
if (bHavePreviousFinalCenter
&& ((PreviousFinalCenter < 0.0f && FinalCenter > 0.0f)
|| (PreviousFinalCenter > 0.0f && FinalCenter < 0.0f)))
{
++OutOutput.FinalCenterZeroCrossingCount;
}
PreviousFinalCenter = FinalCenter;
bHavePreviousFinalCenter = true;
}
const FVector RadialDirections[3] = {
FVector::DownVector, WallDirection, FVector::UpVector };
FExploreTunnelCoreProbeAxis* Axes[3] = {
&OutOutput.Floor, &OutOutput.Wall, &OutOutput.Ceiling };
float Roots[3][2];
for (int32 AxisIndex = 0; AxisIndex < 3; ++AxisIndex)
{
Roots[AxisIndex][0] = std::numeric_limits<float>::quiet_NaN();
Roots[AxisIndex][1] = std::numeric_limits<float>::quiet_NaN();
FindZeroCrossing(Center, RadialDirections[AxisIndex], true, Roots[AxisIndex][0]);
FindZeroCrossing(Center, RadialDirections[AxisIndex], false, Roots[AxisIndex][1]);
Axes[AxisIndex]->CoreCrossingsVoxels.Add(Roots[AxisIndex][0]);
Axes[AxisIndex]->FinalCrossingsVoxels.Add(Roots[AxisIndex][1]);
}
if (SampleIndex == 50 || SampleIndex == 302)
{
const float DiagnosticDistances[] = { 0.0f, 4.0f, 8.0f, 10.0f, 11.0f,
12.0f, 13.0f, 16.0f, 20.0f, 24.0f, 28.0f, 32.0f };
for (int32 AxisIndex = 0; AxisIndex < 3; ++AxisIndex)
{
FString Values;
for (const float Distance : DiagnosticDistances)
{
const FVector Position = Center + RadialDirections[AxisIndex] * Distance;
const float CoreValue = VoxelCaveMorphology::EvaluateTunnelCoreWorldSDF(
Position.X, Position.Y, Position.Z, Cache, true);
FTunnelCoreWorldEvaluation GenericCoreEvaluation;
const float GenericValue = World.Generator->GetDensityWithParams(
Position.X, Position.Y, Position.Z, Params, BestParamsFingerprint,
ProbeLayoutVersion, /*bApplyLegacyStructuralPosts=*/false,
&GenericCoreEvaluation, /*bCollectFusedDiagnostics=*/false);
const float FinalValue = World.Generator->GetDensityAt(
Position.X, Position.Y, Position.Z);
Values += FString::Printf(
TEXT(" %.0f:(%.3g,%.3g,%.3g)"), Distance, CoreValue,
GenericValue, FinalValue);
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeTunnelCoreProbe] radial_diagnostic sample=%d axis=%s "
"distance:(core_sdf,generic_mc,final_mc)%s"),
SampleIndex, *Axes[AxisIndex]->Name, *Values);
}
}
Csv += FString::Printf(
TEXT("%d,%.9g,%.9g,%.9g,%.9g,%.9g,%.9g,%.9g,%.9g,%.9g,%.9g,%.9g,%.9g\r\n"),
SampleIndex, AxisDistance, Center.X, Center.Y, Center.Z,
CoreCenter, FinalCenter,
Roots[0][0], Roots[0][1], Roots[1][0], Roots[1][1], Roots[2][0], Roots[2][1]);
}
FExploreTunnelCoreProbeAxis* Axes[3] = {
&OutOutput.Floor, &OutOutput.Wall, &OutOutput.Ceiling };
for (FExploreTunnelCoreProbeAxis* Axis : Axes)
{
ComputeTunnelCoreProbeMetrics(
Axis->CoreCrossingsVoxels, ProbeStep,
Axis->ValidCoreCrossings, Axis->CoreMeanCrossingVoxels,
Axis->CoreMinCrossingVoxels, Axis->CoreMaxCrossingVoxels,
Axis->CorePeriodicAmplitudeVoxels, Axis->CorePeriodicPeriodVoxels,
Axis->CorePeriodicMeasuredSamples, Axis->CorePeriodicMeasuredLengthVoxels);
ComputeTunnelCoreProbeMetrics(
Axis->FinalCrossingsVoxels, ProbeStep,
Axis->ValidFinalCrossings, Axis->FinalMeanCrossingVoxels,
Axis->FinalMinCrossingVoxels, Axis->FinalMaxCrossingVoxels,
Axis->FinalPeriodicAmplitudeVoxels, Axis->FinalPeriodicPeriodVoxels,
Axis->FinalPeriodicMeasuredSamples, Axis->FinalPeriodicMeasuredLengthVoxels);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeTunnelCoreProbe] axis=%s samples=%d final_valid=%d "
"rib_periodic_amplitude=%.6g_voxels=%.6g_cm period=%.6g_voxels "
"measured=%.6g_voxels/%d core_valid=%d core_rib_periodic_amplitude=%.6g_voxels=%.6g_cm period=%.6g_voxels "
"measured=%.6g_voxels/%d"),
*Axis->Name, SampleCount, Axis->ValidFinalCrossings,
Axis->FinalPeriodicAmplitudeVoxels,
Axis->FinalPeriodicAmplitudeVoxels * VOXEL_SIZE,
Axis->FinalPeriodicPeriodVoxels,
Axis->FinalPeriodicMeasuredLengthVoxels, Axis->FinalPeriodicMeasuredSamples,
Axis->ValidCoreCrossings, Axis->CorePeriodicAmplitudeVoxels,
Axis->CorePeriodicAmplitudeVoxels * VOXEL_SIZE,
Axis->CorePeriodicPeriodVoxels,
Axis->CorePeriodicMeasuredLengthVoxels, Axis->CorePeriodicMeasuredSamples);
}
// Mouth probe: measure the two surfaces that meet at each room endpoint over a lateral
// window of approximately +/-2 tunnel radii. The wall ray starts in room air and points
// toward the tunnel; the floor ray starts below the same room-side patch and points up.
// Both use the final MC-facing density, so this catches a ring/fillet handoff rather than
// merely comparing two independent source SDFs. Keep this in the same owner-value process
// and cache selection as the core probe so the before/after numbers are directly comparable.
{
FExploreTunnelMouthProbeOutput& Mouth = OutOutput.Mouth;
Mouth = FExploreTunnelMouthProbeOutput();
Mouth.FileName = TEXT("tunnel_mouth_probe.csv");
const double MouthStartSeconds = FPlatformTime::Seconds();
FString MouthCsv;
MouthCsv.Reserve(8192);
MouthCsv += TEXT(
"endpoint,lateral_offset_voxels,wall_surface_distance_voxels,"
"floor_surface_height_voxels\r\n");
const TArray<float>& MouthRadii = Radii;
const TArray<FVector>& MouthPoints = Points;
const int32 MouthPointCount = MouthPoints.Num();
const uint32 RoomHashes[2] = {
BestTunnel->FloorRoomHashA, BestTunnel->FloorRoomHashB };
const int32 EndpointPointIndices[2] = { 0, MouthPointCount - 1 };
const int32 InteriorPointIndices[2] = { 1, MouthPointCount - 2 };
TSet<uint32> CountedRoomHashes;
auto CachedRoomRawSDF = [](const FCachedRoom& Room, const FVector& Position) -> float
{
switch (Room.ShapeType)
{
case 1:
return VoxelSDF::RoundedBox(
Position, Room.Center, Room.ShapeA, Room.ShapeR);
case 2:
return VoxelSDF::Capsule(
Position, Room.ShapeA, Room.ShapeB, Room.ShapeR);
default:
return VoxelSDF::Ellipsoid(Position, Room.Center, Room.ShapeA);
}
};
// The room-volume attribution sample is intentionally coarser than the mesh lattice. It
// is a count instrument, not a replacement for the matched export, and its resolution is
// recorded in the report. A core-induced solid must still be zero at this resolution.
const int32 RoomSampleStep = Mouth.RoomSampleStepVoxels;
for (int32 EndpointIndex = 0; EndpointIndex < 2; ++EndpointIndex)
{
const int32 PointIndex = EndpointPointIndices[EndpointIndex];
const int32 InteriorIndex = InteriorPointIndices[EndpointIndex];
if (!MouthPoints.IsValidIndex(PointIndex)
|| !MouthPoints.IsValidIndex(InteriorIndex)
|| !MouthRadii.IsValidIndex(PointIndex))
{
continue;
}
FExploreTunnelMouthProbeEndpoint& Endpoint = Mouth.Endpoints.AddDefaulted_GetRef();
Endpoint.Name = EndpointIndex == 0 ? TEXT("A") : TEXT("B");
Endpoint.RoomHash = RoomHashes[EndpointIndex];
Endpoint.Mouth = MouthPoints[PointIndex];
Endpoint.RadiusVoxels = FMath::Abs(MouthRadii[PointIndex]);
Endpoint.TunnelFloorZ = EndpointIndex == 0
? BestTunnel->WorldMouthFloorZA : BestTunnel->WorldMouthFloorZB;
if (!FMath::IsFinite(Endpoint.TunnelFloorZ))
{
Endpoint.TunnelFloorZ = static_cast<float>(Endpoint.Mouth.Z)
- Endpoint.RadiusVoxels;
}
FVector RoomToTunnel = MouthPoints[InteriorIndex] - MouthPoints[PointIndex];
RoomToTunnel.Z = 0.0f;
if (!RoomToTunnel.Normalize())
{
RoomToTunnel = EndpointIndex == 0
? FVector::ForwardVector : -FVector::ForwardVector;
}
const FVector Lateral(-RoomToTunnel.Y, RoomToTunnel.X, 0.0f);
const float MaxMouthRadius = FMath::Max(MaxRadius, Endpoint.RadiusVoxels);
const float MouthRayReach = FMath::Max(MaxMouthRadius + 12.0f, 16.0f);
const float LateralHalfWidth = FMath::Max(
Endpoint.RadiusVoxels * 2.0f, 8.0f);
const float LateralStep = 0.25f;
const int32 LateralSamples = FMath::Max(
5, FMath::FloorToInt(
(2.0f * LateralHalfWidth) / LateralStep) + 1);
const float ActualHalfWidth =
0.5f * static_cast<float>(LateralSamples - 1) * LateralStep;
Mouth.LateralSampleCount = FMath::Max(Mouth.LateralSampleCount, LateralSamples);
Mouth.LateralWindowVoxels = FMath::Max(
Mouth.LateralWindowVoxels, 2.0f * ActualHalfWidth);
Endpoint.Wall.Name = TEXT("wall");
Endpoint.Floor.Name = TEXT("floor");
Endpoint.Wall.CrossingsVoxels.Reserve(LateralSamples);
Endpoint.Floor.CrossingsVoxels.Reserve(LateralSamples);
// Keep the origin in finite room air but close enough that the existing radial
// crossing search still reaches the mouth in one probe window.
const float WallInteriorDepth = FMath::Min(
FMath::Max(4.0f * Endpoint.RadiusVoxels, 12.0f),
0.5f * MouthRayReach);
const float FloorInteriorDepth = FMath::Min(
FMath::Max(Endpoint.RadiusVoxels, 4.0f),
0.25f * MouthRayReach);
const float FloorOriginZ = static_cast<float>(Endpoint.Mouth.Z)
- Endpoint.RadiusVoxels - 8.0f;
for (int32 SampleIndex = 0; SampleIndex < LateralSamples; ++SampleIndex)
{
const float LateralOffset = -ActualHalfWidth
+ static_cast<float>(SampleIndex) * LateralStep;
const FVector LateralOffsetVector = Lateral * LateralOffset;
const FVector WallOrigin = Endpoint.Mouth + LateralOffsetVector
- RoomToTunnel * WallInteriorDepth;
const FVector FloorOrigin(
Endpoint.Mouth.X + LateralOffsetVector.X
- RoomToTunnel.X * FloorInteriorDepth,
Endpoint.Mouth.Y + LateralOffsetVector.Y
- RoomToTunnel.Y * FloorInteriorDepth,
FloorOriginZ);
float WallCrossing = std::numeric_limits<float>::quiet_NaN();
float FloorCrossing = std::numeric_limits<float>::quiet_NaN();
FindZeroCrossing(
WallOrigin, RoomToTunnel, false, WallCrossing);
FindZeroCrossing(
FloorOrigin, FVector::UpVector, false, FloorCrossing);
Endpoint.Wall.CrossingsVoxels.Add(WallCrossing);
Endpoint.Floor.CrossingsVoxels.Add(FloorCrossing);
MouthCsv += FString::Printf(
TEXT("%s,%.9g,%.9g,%.9g\r\n"),
*Endpoint.Name, LateralOffset, WallCrossing, FloorCrossing);
}
ComputeTunnelMouthProbeMetrics(Endpoint.Wall, LateralStep);
ComputeTunnelMouthProbeMetrics(Endpoint.Floor, LateralStep);
Mouth.MaxWallPeriodicAmplitudeVoxels = FMath::Max(
Mouth.MaxWallPeriodicAmplitudeVoxels,
Endpoint.Wall.PeriodicAmplitudeVoxels);
Mouth.MaxFloorPeriodicAmplitudeVoxels = FMath::Max(
Mouth.MaxFloorPeriodicAmplitudeVoxels,
Endpoint.Floor.PeriodicAmplitudeVoxels);
Mouth.MaxLocalMedianWallPeriodicAmplitudeVoxels = FMath::Max(
Mouth.MaxLocalMedianWallPeriodicAmplitudeVoxels,
Endpoint.Wall.LocalMedianPeriodicAmplitudeVoxels);
Mouth.MaxLocalMedianFloorPeriodicAmplitudeVoxels = FMath::Max(
Mouth.MaxLocalMedianFloorPeriodicAmplitudeVoxels,
Endpoint.Floor.LocalMedianPeriodicAmplitudeVoxels);
const int32 CentreIndex = LateralSamples / 2;
if (Endpoint.Floor.CrossingsVoxels.IsValidIndex(CentreIndex)
&& FMath::IsFinite(Endpoint.Floor.CrossingsVoxels[CentreIndex]))
{
Endpoint.RoomFloorCrossingZ = FloorOriginZ
+ Endpoint.Floor.CrossingsVoxels[CentreIndex];
Endpoint.FloorJoinDeltaVoxels = Endpoint.RoomFloorCrossingZ
- Endpoint.TunnelFloorZ;
}
// Count the room-volume samples once per endpoint room. This catches the exact
// failure mode from the screenshot: the generic field is air, then the tunnel-core
// bottom post changes it back to solid inside a room.
const FCachedRoom* Room = nullptr;
for (const FCachedRoom& CandidateRoom : Cache.Rooms)
{
if (CandidateRoom.Hash == RoomHashes[EndpointIndex])
{
Room = &CandidateRoom;
break;
}
}
if (Room == nullptr || CountedRoomHashes.Contains(Room->Hash))
{
continue;
}
CountedRoomHashes.Add(Room->Hash);
FVector ShapeExtentVector(
FMath::Abs(Room->ShapeA.X),
FMath::Abs(Room->ShapeA.Y),
FMath::Abs(Room->ShapeA.Z));
if (Room->ShapeType == 2)
{
ShapeExtentVector = FVector(
FMath::Max(
FMath::Abs(Room->ShapeA.X - Room->Center.X),
FMath::Abs(Room->ShapeB.X - Room->Center.X)),
FMath::Max(
FMath::Abs(Room->ShapeA.Y - Room->Center.Y),
FMath::Abs(Room->ShapeB.Y - Room->Center.Y)),
FMath::Max(
FMath::Abs(Room->ShapeA.Z - Room->Center.Z),
FMath::Abs(Room->ShapeB.Z - Room->Center.Z)));
}
const float ShapeExtent = FMath::Max3(
ShapeExtentVector.X, ShapeExtentVector.Y, ShapeExtentVector.Z)
+ FMath::Abs(Room->ShapeR) + 2.0f;
const int32 MinX = FMath::FloorToInt(Room->Center.X - ShapeExtent);
const int32 MinY = FMath::FloorToInt(Room->Center.Y - ShapeExtent);
const int32 MinZ = FMath::FloorToInt(Room->Center.Z - ShapeExtent);
const int32 MaxX = FMath::CeilToInt(Room->Center.X + ShapeExtent);
const int32 MaxY = FMath::CeilToInt(Room->Center.Y + ShapeExtent);
const int32 MaxZ = FMath::CeilToInt(Room->Center.Z + ShapeExtent);
for (int32 Z = MinZ; Z <= MaxZ; Z += RoomSampleStep)
{
for (int32 Y = MinY; Y <= MaxY; Y += RoomSampleStep)
{
for (int32 X = MinX; X <= MaxX; X += RoomSampleStep)
{
const FVector Position(
static_cast<float>(X), static_cast<float>(Y), static_cast<float>(Z));
if (!(CachedRoomRawSDF(*Room, Position) < 0.0f))
{
continue;
}
++Mouth.RoomVolumeSampleCount;
FTunnelCoreWorldEvaluation CoreEvaluation;
const float GenericDensity = World.Generator->GetDensityWithParams(
Position.X, Position.Y, Position.Z,
Params, BestParamsFingerprint, ProbeLayoutVersion,
/*bApplyLegacyStructuralPosts=*/false,
&CoreEvaluation,
/*bCollectFusedDiagnostics=*/false);
const float FinalDensity = World.Generator->GetDensityAt(
Position.X, Position.Y, Position.Z);
if (!FMath::IsFinite(GenericDensity)
|| !FMath::IsFinite(FinalDensity))
{
continue;
}
if (GenericDensity > 0.0f)
{
++Mouth.RoomAirSampleCount;
}
if (FinalDensity <= 0.0f)
{
++Mouth.FinalSolidSampleCount;
}
const bool bOwnsSweptBottom =
CoreEvaluation.bHasSweptFloor
&& FMath::IsFinite(CoreEvaluation.SweptFloorZ)
&& FMath::IsFinite(CoreEvaluation.SweptFloorRadius)
&& Position.Z <= CoreEvaluation.SweptFloorZ
+ VoxelPassageGeometry::WalkableTunnelFloorAirClearanceVoxels;
if (bOwnsSweptBottom)
{
++Mouth.CoreBottomSampleCount;
if (GenericDensity > 0.0f && FinalDensity <= 0.0f)
{
++Mouth.CoreInducedSolidSampleCount;
}
}
}
}
}
}
Mouth.EndpointCount = Mouth.Endpoints.Num();
const FString MouthPath = FPaths::Combine(Arguments.OutDirectory, Mouth.FileName);
if (!FFileHelper::SaveStringToFile(
MouthCsv, *MouthPath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
Mouth.Status = TEXT("error");
Mouth.RefusalReason = TEXT("Could not write tunnel_mouth_probe.csv.");
}
else if (Mouth.EndpointCount < 2)
{
Mouth.Status = TEXT("error");
Mouth.RefusalReason = TEXT("The selected tunnel did not expose two room endpoints.");
}
else
{
Mouth.Status = TEXT("ok");
}
Mouth.Seconds = FPlatformTime::Seconds() - MouthStartSeconds;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeTunnelMouthProbe] endpoints=%d room_samples=%lld room_air=%lld "
"core_bottom=%lld final_solid=%lld core_induced_solid=%lld "
"wall_periodic=%.6g_voxels=%.6g_cm floor_periodic=%.6g_voxels=%.6g_cm "
"legacy_floor_periodic=%.6g_voxels=%.6g_cm "
"lateral_window=%.3f step=%.3f file=%s"),
Mouth.EndpointCount,
Mouth.RoomVolumeSampleCount, Mouth.RoomAirSampleCount,
Mouth.CoreBottomSampleCount, Mouth.FinalSolidSampleCount,
Mouth.CoreInducedSolidSampleCount,
Mouth.MaxWallPeriodicAmplitudeVoxels,
Mouth.MaxWallPeriodicAmplitudeVoxels * VOXEL_SIZE,
Mouth.MaxFloorPeriodicAmplitudeVoxels,
Mouth.MaxFloorPeriodicAmplitudeVoxels * VOXEL_SIZE,
Mouth.MaxLocalMedianFloorPeriodicAmplitudeVoxels,
Mouth.MaxLocalMedianFloorPeriodicAmplitudeVoxels * VOXEL_SIZE,
Mouth.LateralWindowVoxels, Mouth.LateralStepVoxels, *MouthPath);
}
const FString Path = FPaths::Combine(Arguments.OutDirectory, OutOutput.FileName);
if (!FFileHelper::SaveStringToFile(
Csv, *Path, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
OutOutput.Status = TEXT("error");
OutOutput.RefusalReason = TEXT("Could not write tunnel_core_probe.csv.");
OutOutput.Seconds = FPlatformTime::Seconds() - StartSeconds;
return false;
}
OutOutput.Status = TEXT("ok");
OutOutput.Seconds = FPlatformTime::Seconds() - StartSeconds;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeTunnelCoreProbe] tunnel=%d samples=%d step=%.3f length=%.3f "
"final_center_finite=%d known_clamp_plateau=%d range=[%.6g,%.6g] "
"core_center_finite=%d range=[%.6g,%.6g] file=%s"),
OutOutput.TunnelIndex, OutOutput.SampleCount, OutOutput.StepVoxels,
OutOutput.LengthVoxels, OutOutput.FinalCenterFiniteSampleCount,
OutOutput.FinalCenterSaturatedSampleCount, OutOutput.FinalCenterMin,
OutOutput.FinalCenterMax, OutOutput.CoreCenterFiniteSampleCount,
OutOutput.CoreCenterMin, OutOutput.CoreCenterMax, *Path);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeTunnelCoreProbe] generator_core_compare_max_abs_delta=%.6g "
"mismatched_samples=%d tolerance=0.001"),
MaxGeneratorCoreDelta, GeneratorCoreMismatchCount);
return true;
}
void WriteTunnelCoreProbeAxis(
FExploreJsonWriter& Writer,
const FExploreTunnelCoreProbeAxis& Axis,
const TCHAR* Key)
{
Writer.WriteObjectStart(Key);
Writer.WriteValue(TEXT("valid_core_crossings"), Axis.ValidCoreCrossings);
Writer.WriteValue(TEXT("valid_final_crossings"), Axis.ValidFinalCrossings);
Writer.WriteValue(TEXT("core_mean_crossing_voxels"), static_cast<double>(Axis.CoreMeanCrossingVoxels));
Writer.WriteValue(TEXT("final_mean_crossing_voxels"), static_cast<double>(Axis.FinalMeanCrossingVoxels));
Writer.WriteValue(TEXT("core_min_crossing_voxels"), static_cast<double>(Axis.CoreMinCrossingVoxels));
Writer.WriteValue(TEXT("core_max_crossing_voxels"), static_cast<double>(Axis.CoreMaxCrossingVoxels));
Writer.WriteValue(TEXT("final_min_crossing_voxels"), static_cast<double>(Axis.FinalMinCrossingVoxels));
Writer.WriteValue(TEXT("final_max_crossing_voxels"), static_cast<double>(Axis.FinalMaxCrossingVoxels));
Writer.WriteValue(TEXT("core_periodic_amplitude_voxels"), static_cast<double>(Axis.CorePeriodicAmplitudeVoxels));
Writer.WriteValue(TEXT("core_periodic_amplitude_cm"), static_cast<double>(Axis.CorePeriodicAmplitudeVoxels) * VOXEL_SIZE);
Writer.WriteValue(TEXT("core_periodic_period_voxels"), static_cast<double>(Axis.CorePeriodicPeriodVoxels));
Writer.WriteValue(TEXT("core_periodic_measured_samples"), Axis.CorePeriodicMeasuredSamples);
Writer.WriteValue(TEXT("core_periodic_measured_length_voxels"), static_cast<double>(Axis.CorePeriodicMeasuredLengthVoxels));
Writer.WriteValue(TEXT("final_periodic_amplitude_voxels"), static_cast<double>(Axis.FinalPeriodicAmplitudeVoxels));
Writer.WriteValue(TEXT("final_periodic_amplitude_cm"), static_cast<double>(Axis.FinalPeriodicAmplitudeVoxels) * VOXEL_SIZE);
Writer.WriteValue(TEXT("final_periodic_period_voxels"), static_cast<double>(Axis.FinalPeriodicPeriodVoxels));
Writer.WriteValue(TEXT("final_periodic_measured_samples"), Axis.FinalPeriodicMeasuredSamples);
Writer.WriteValue(TEXT("final_periodic_measured_length_voxels"), static_cast<double>(Axis.FinalPeriodicMeasuredLengthVoxels));
Writer.WriteObjectEnd();
}
void WriteTunnelMouthProbeAxis(
FExploreJsonWriter& Writer,
const FExploreTunnelMouthProbeAxis& Axis,
const TCHAR* Key)
{
Writer.WriteObjectStart(Key);
Writer.WriteValue(TEXT("valid_crossings"), Axis.ValidCrossings);
Writer.WriteValue(TEXT("mean_crossing_voxels"), static_cast<double>(Axis.MeanCrossingVoxels));
Writer.WriteValue(TEXT("min_crossing_voxels"), static_cast<double>(Axis.MinCrossingVoxels));
Writer.WriteValue(TEXT("max_crossing_voxels"), static_cast<double>(Axis.MaxCrossingVoxels));
Writer.WriteValue(TEXT("periodic_amplitude_voxels"), static_cast<double>(Axis.PeriodicAmplitudeVoxels));
Writer.WriteValue(TEXT("periodic_amplitude_cm"), static_cast<double>(Axis.PeriodicAmplitudeVoxels) * VOXEL_SIZE);
Writer.WriteValue(TEXT("periodic_period_voxels"), static_cast<double>(Axis.PeriodicPeriodVoxels));
Writer.WriteValue(TEXT("periodic_measured_samples"), Axis.PeriodicMeasuredSamples);
Writer.WriteValue(TEXT("periodic_measured_length_voxels"), static_cast<double>(Axis.PeriodicMeasuredLengthVoxels));
Writer.WriteValue(TEXT("local_median_periodic_amplitude_voxels"), static_cast<double>(Axis.LocalMedianPeriodicAmplitudeVoxels));
Writer.WriteValue(TEXT("local_median_periodic_amplitude_cm"), static_cast<double>(Axis.LocalMedianPeriodicAmplitudeVoxels) * VOXEL_SIZE);
Writer.WriteValue(TEXT("local_median_periodic_period_voxels"), static_cast<double>(Axis.LocalMedianPeriodicPeriodVoxels));
Writer.WriteObjectEnd();
}
void WritePlayerDimensions(FExploreJsonWriter& Writer, const TCHAR* Key)
{
Writer.WriteObjectStart(Key);
Writer.WriteValue(TEXT("capsule_radius_m"), static_cast<double>(FVoxelPlayerCapsuleConstants::RadiusCentimeters) / 100.0);
Writer.WriteValue(TEXT("capsule_width_m"), static_cast<double>(2.0f * FVoxelPlayerCapsuleConstants::RadiusCentimeters) / 100.0);
Writer.WriteValue(TEXT("capsule_half_height_m"), static_cast<double>(FVoxelPlayerCapsuleConstants::HalfHeightCentimeters) / 100.0);
Writer.WriteValue(TEXT("capsule_height_m"), static_cast<double>(2.0f * FVoxelPlayerCapsuleConstants::HalfHeightCentimeters) / 100.0);
Writer.WriteValue(TEXT("max_step_height_m"), static_cast<double>(FVoxelPlayerCapsuleConstants::MaxStepHeightMeters));
Writer.WriteValue(TEXT("climbing_enabled"), true);
Writer.WriteValue(TEXT("max_climb_height_m"), 3.0);
Writer.WriteValue(TEXT("walkable_floor_angle_degrees"), static_cast<double>(FVoxelPlayerCapsuleConstants::WalkableFloorAngleDegrees));
Writer.WriteObjectEnd();
}
FString BuildExploreJson(
const FExploreArguments& Arguments,
const FExploreWorld& World,
const FExploreRunOutput& Output,
bool bDeterminismAssertion,
uint32 PayloadCrc32)
{
FString Json;
TSharedRef<FExploreJsonWriter> Writer =
TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create(&Json);
Writer->WriteObjectStart();
Writer->WriteValue(TEXT("schema_version"), 7);
Writer->WriteValue(TEXT("tool"), TEXT("VoxelForgeExplore"));
Writer->WriteValue(TEXT("read_only_generation"), true);
Writer->WriteObjectStart(TEXT("input"));
Writer->WriteValue(TEXT("seed"), Arguments.Seed);
Writer->WriteValue(TEXT("archetype"), ArchetypeName(Arguments.Archetype));
Writer->WriteValue(TEXT("slot"), Arguments.Slot);
Writer->WriteValue(TEXT("worms_enabled_override"), Arguments.bWormsEnabledOverride);
if (Arguments.bWormsEnabledOverride)
{
Writer->WriteValue(TEXT("worms_enabled"), Arguments.bWormsEnabled);
}
Writer->WriteValue(TEXT("worm_strength_override"), Arguments.bWormStrengthOverride);
if (Arguments.bWormStrengthOverride)
{
Writer->WriteValue(TEXT("worm_strength"), static_cast<double>(Arguments.WormStrength));
}
Writer->WriteValue(TEXT("tunnel_ablation_mask"),
static_cast<int64>(VoxelDensityAblation::GetResolvedMask()));
WriteTunnelFloorOverrides(*Writer, Arguments);
Writer->WriteValue(TEXT("room_mouth_rise_override"), Arguments.bRoomMouthRiseOverride);
if (Arguments.bRoomMouthRiseOverride)
{
Writer->WriteValue(TEXT("room_mouth_rise_strength"),
static_cast<double>(Arguments.RoomMouthRise));
}
Writer->WriteValue(TEXT("room_mouth_rise_blend_override"),
Arguments.bRoomMouthRiseBlendOverride);
if (Arguments.bRoomMouthRiseBlendOverride)
{
Writer->WriteValue(TEXT("room_mouth_rise_blend_voxels"),
static_cast<double>(Arguments.RoomMouthRiseBlend));
}
Writer->WriteValue(TEXT("surface_roughness_override"), Arguments.bSurfaceRoughnessOverride);
if (Arguments.bSurfaceRoughnessOverride)
{
Writer->WriteValue(TEXT("surface_roughness"),
static_cast<double>(Arguments.SurfaceRoughness));
}
Writer->WriteValue(TEXT("roughness_noise_type_override"),
Arguments.bRoughnessNoiseTypeOverride);
if (Arguments.bRoughnessNoiseTypeOverride)
{
Writer->WriteValue(TEXT("roughness_noise_type"),
NoiseTypeName(Arguments.RoughnessNoiseType));
}
Writer->WriteValue(TEXT("roughness_frequency_override"),
Arguments.bRoughnessFrequencyOverride);
if (Arguments.bRoughnessFrequencyOverride)
{
Writer->WriteValue(TEXT("roughness_frequency"),
static_cast<double>(Arguments.RoughnessFrequency));
}
Writer->WriteArrayStart(TEXT("modes"));
if (Arguments.bRender) Writer->WriteValue(TEXT("render"));
if (Arguments.bWalk) Writer->WriteValue(TEXT("walk"));
if (Arguments.bExport) Writer->WriteValue(TEXT("export"));
if (Arguments.bOpBounds) Writer->WriteValue(TEXT("opbounds"));
if (Arguments.bTunnelCoreProbe) Writer->WriteValue(TEXT("tunnelcoreprobe"));
Writer->WriteArrayEnd();
Writer->WriteValue(TEXT("failure_focus_render"), Arguments.bFailureFocusRender);
Writer->WriteValue(TEXT("render_step_voxels"), static_cast<double>(Arguments.RenderStepVoxels));
Writer->WriteValue(TEXT("export_size"), Arguments.ExportSize);
Writer->WriteValue(TEXT("export_step"), Arguments.ExportStep);
Writer->WriteValue(TEXT("density_grid_reuse"), Arguments.bReuseDensityGrid);
Writer->WriteValue(TEXT("block_early_out"), Arguments.bBlockEarlyOut);
Writer->WriteValue(TEXT("game_runtime_config"), Arguments.bGameRuntimeConfig);
Writer->WriteValue(TEXT("mesh_min_batch_size"), Arguments.MeshMinBatchSize);
Writer->WriteValue(TEXT("profile_density"), Arguments.bProfileDensity);
Writer->WriteValue(TEXT("profile_density_full"), Arguments.bProfileDensityFull);
Writer->WriteValue(TEXT("perf_attribution"), Arguments.bPerfAttribution);
Writer->WriteValue(TEXT("sample_stacks"), Arguments.bSampleStacks);
Writer->WriteValue(TEXT("sample_stacks_interval_us"),
static_cast<double>(Arguments.SampleStacksIntervalUs));
Writer->WriteValue(TEXT("profile_lod"), Arguments.bProfileLod);
Writer->WriteValue(TEXT("density_slice_requested"), Arguments.bDensitySlice);
Writer->WriteValue(TEXT("tunnel_core_probe_requested"), Arguments.bTunnelCoreProbe);
Writer->WriteValue(TEXT("render_target_override"), Arguments.bRenderTargetOverride);
if (Arguments.bRenderTargetOverride)
{
WriteJsonVector(*Writer, TEXT("render_target_voxels"), Arguments.RenderTarget, 1.0f);
}
Writer->WriteValue(TEXT("op_bounds"), Arguments.bOpBounds);
Writer->WriteValue(TEXT("budget_minutes"), static_cast<double>(Arguments.BudgetMinutes));
Writer->WriteValue(TEXT("out_directory"), Arguments.OutDirectory);
Writer->WriteValue(TEXT("canonical_invocation"), FString::Printf(
TEXT("UnrealEditor-Cmd VoxelM.uproject -run=VoxelForgeExplore -seed=%d -archetype=%s "
"-slot=%d -modes=%s -blockearlyout=%d "
"-out=<ABSOLUTE_PLUGIN_SAVED_PATH>"),
Arguments.Seed,
ArchetypeName(Arguments.Archetype),
Arguments.Slot,
*Arguments.CanonicalModes(),
Arguments.bBlockEarlyOut ? 1 : 0));
Writer->WriteObjectEnd();
Writer->WriteObjectStart(TEXT("density_slice"));
Writer->WriteValue(TEXT("status"), Output.DensitySlice.Status);
Writer->WriteValue(TEXT("file"), Output.DensitySlice.FileName);
Writer->WriteValue(TEXT("refusal_or_error"), Output.DensitySlice.RefusalReason);
Writer->WriteValue(TEXT("sample_count"), Output.DensitySlice.SampleCount);
Writer->WriteValue(TEXT("finite_sample_count"), Output.DensitySlice.FiniteSampleCount);
Writer->WriteValue(TEXT("non_finite_sample_count"), Output.DensitySlice.NonFiniteSampleCount);
Writer->WriteValue(TEXT("zero_crossing_count"), Output.DensitySlice.ZeroCrossingCount);
Writer->WriteValue(TEXT("saturated_sample_count"), Output.DensitySlice.SaturatedSampleCount);
Writer->WriteValue(TEXT("min_density"), static_cast<double>(Output.DensitySlice.MinDensity));
Writer->WriteValue(TEXT("max_density"), static_cast<double>(Output.DensitySlice.MaxDensity));
Writer->WriteValue(TEXT("max_abs_density"), static_cast<double>(Output.DensitySlice.MaxAbsDensity));
Writer->WriteValue(TEXT("seconds"), Output.DensitySlice.Seconds);
if (Arguments.bDensitySlice)
{
WriteJsonVector(*Writer, TEXT("origin_voxels"), Arguments.DensitySliceOrigin, 1.0f);
WriteJsonVector(*Writer, TEXT("direction"), Arguments.DensitySliceDirection, 1.0f);
Writer->WriteValue(TEXT("step_voxels"), static_cast<double>(Arguments.DensitySliceStep));
}
Writer->WriteObjectEnd();
Writer->WriteObjectStart(TEXT("tunnel_core_probe"));
Writer->WriteValue(TEXT("status"), Output.TunnelCoreProbe.Status);
Writer->WriteValue(TEXT("file"), Output.TunnelCoreProbe.FileName);
Writer->WriteValue(TEXT("refusal_or_error"), Output.TunnelCoreProbe.RefusalReason);
Writer->WriteValue(TEXT("tunnel_index"), Output.TunnelCoreProbe.TunnelIndex);
Writer->WriteValue(TEXT("sample_count"), Output.TunnelCoreProbe.SampleCount);
Writer->WriteValue(TEXT("step_voxels"), static_cast<double>(Output.TunnelCoreProbe.StepVoxels));
Writer->WriteValue(TEXT("length_voxels"), static_cast<double>(Output.TunnelCoreProbe.LengthVoxels));
WriteJsonVector(*Writer, TEXT("axis_start_voxels"), Output.TunnelCoreProbe.AxisStart, 1.0f);
WriteJsonVector(*Writer, TEXT("axis_end_voxels"), Output.TunnelCoreProbe.AxisEnd, 1.0f);
WriteJsonVector(*Writer, TEXT("axis_direction"), Output.TunnelCoreProbe.AxisDirection, 1.0f);
WriteJsonVector(*Writer, TEXT("wall_direction"), Output.TunnelCoreProbe.WallDirection, 1.0f);
Writer->WriteValue(TEXT("core_center_finite_sample_count"), Output.TunnelCoreProbe.CoreCenterFiniteSampleCount);
Writer->WriteValue(TEXT("core_center_zero_crossing_count"), Output.TunnelCoreProbe.CoreCenterZeroCrossingCount);
Writer->WriteValue(TEXT("final_center_finite_sample_count"), Output.TunnelCoreProbe.FinalCenterFiniteSampleCount);
Writer->WriteValue(TEXT("final_center_zero_crossing_count"), Output.TunnelCoreProbe.FinalCenterZeroCrossingCount);
Writer->WriteValue(TEXT("final_center_saturated_sample_count"), Output.TunnelCoreProbe.FinalCenterSaturatedSampleCount);
Writer->WriteValue(TEXT("core_center_min"), static_cast<double>(Output.TunnelCoreProbe.CoreCenterMin));
Writer->WriteValue(TEXT("core_center_max"), static_cast<double>(Output.TunnelCoreProbe.CoreCenterMax));
Writer->WriteValue(TEXT("final_center_min"), static_cast<double>(Output.TunnelCoreProbe.FinalCenterMin));
Writer->WriteValue(TEXT("final_center_max"), static_cast<double>(Output.TunnelCoreProbe.FinalCenterMax));
Writer->WriteValue(TEXT("final_center_max_abs"), static_cast<double>(Output.TunnelCoreProbe.FinalCenterMaxAbs));
Writer->WriteValue(TEXT("periodic_method"), TEXT("Tunnel-core axes: 32-voxel local-median detrend. Mouth axes: degree-six slow-trend detrend over the longest contiguous >=40-voxel run, with the legacy local-median value retained per axis; both use a single-sided DFT peak in the <=16-voxel band and report sinusoid amplitudes."));
WriteTunnelCoreProbeAxis(*Writer, Output.TunnelCoreProbe.Floor, TEXT("floor"));
WriteTunnelCoreProbeAxis(*Writer, Output.TunnelCoreProbe.Wall, TEXT("wall"));
WriteTunnelCoreProbeAxis(*Writer, Output.TunnelCoreProbe.Ceiling, TEXT("ceiling"));
Writer->WriteObjectStart(TEXT("mouth_probe"));
Writer->WriteValue(TEXT("status"), Output.TunnelCoreProbe.Mouth.Status);
Writer->WriteValue(TEXT("file"), Output.TunnelCoreProbe.Mouth.FileName);
Writer->WriteValue(TEXT("refusal_or_error"), Output.TunnelCoreProbe.Mouth.RefusalReason);
Writer->WriteValue(TEXT("endpoint_count"), Output.TunnelCoreProbe.Mouth.EndpointCount);
Writer->WriteValue(TEXT("lateral_sample_count"), Output.TunnelCoreProbe.Mouth.LateralSampleCount);
Writer->WriteValue(TEXT("lateral_step_voxels"), static_cast<double>(Output.TunnelCoreProbe.Mouth.LateralStepVoxels));
Writer->WriteValue(TEXT("lateral_window_voxels"), static_cast<double>(Output.TunnelCoreProbe.Mouth.LateralWindowVoxels));
Writer->WriteValue(TEXT("room_sample_step_voxels"), Output.TunnelCoreProbe.Mouth.RoomSampleStepVoxels);
Writer->WriteValue(TEXT("room_volume_sample_count"), Output.TunnelCoreProbe.Mouth.RoomVolumeSampleCount);
Writer->WriteValue(TEXT("room_air_sample_count"), Output.TunnelCoreProbe.Mouth.RoomAirSampleCount);
Writer->WriteValue(TEXT("core_bottom_sample_count"), Output.TunnelCoreProbe.Mouth.CoreBottomSampleCount);
Writer->WriteValue(TEXT("final_solid_sample_count"), Output.TunnelCoreProbe.Mouth.FinalSolidSampleCount);
Writer->WriteValue(TEXT("core_induced_solid_sample_count"), Output.TunnelCoreProbe.Mouth.CoreInducedSolidSampleCount);
Writer->WriteValue(TEXT("max_wall_periodic_amplitude_voxels"), static_cast<double>(Output.TunnelCoreProbe.Mouth.MaxWallPeriodicAmplitudeVoxels));
Writer->WriteValue(TEXT("max_wall_periodic_amplitude_cm"), static_cast<double>(Output.TunnelCoreProbe.Mouth.MaxWallPeriodicAmplitudeVoxels) * VOXEL_SIZE);
Writer->WriteValue(TEXT("max_floor_periodic_amplitude_voxels"), static_cast<double>(Output.TunnelCoreProbe.Mouth.MaxFloorPeriodicAmplitudeVoxels));
Writer->WriteValue(TEXT("max_floor_periodic_amplitude_cm"), static_cast<double>(Output.TunnelCoreProbe.Mouth.MaxFloorPeriodicAmplitudeVoxels) * VOXEL_SIZE);
Writer->WriteValue(TEXT("max_local_median_wall_periodic_amplitude_voxels"), static_cast<double>(Output.TunnelCoreProbe.Mouth.MaxLocalMedianWallPeriodicAmplitudeVoxels));
Writer->WriteValue(TEXT("max_local_median_wall_periodic_amplitude_cm"), static_cast<double>(Output.TunnelCoreProbe.Mouth.MaxLocalMedianWallPeriodicAmplitudeVoxels) * VOXEL_SIZE);
Writer->WriteValue(TEXT("max_local_median_floor_periodic_amplitude_voxels"), static_cast<double>(Output.TunnelCoreProbe.Mouth.MaxLocalMedianFloorPeriodicAmplitudeVoxels));
Writer->WriteValue(TEXT("max_local_median_floor_periodic_amplitude_cm"), static_cast<double>(Output.TunnelCoreProbe.Mouth.MaxLocalMedianFloorPeriodicAmplitudeVoxels) * VOXEL_SIZE);
Writer->WriteValue(TEXT("mouth_periodic_detrend"), TEXT("degree-six smooth trend over the complete longest contiguous run; legacy local-median retained for audit"));
Writer->WriteValue(TEXT("periodic_acceptance_cm"), 2.0);
Writer->WriteValue(TEXT("periodic_acceptance_pass"),
Output.TunnelCoreProbe.Mouth.MaxWallPeriodicAmplitudeVoxels * VOXEL_SIZE <= 2.0f
&& Output.TunnelCoreProbe.Mouth.MaxFloorPeriodicAmplitudeVoxels * VOXEL_SIZE <= 2.0f);
Writer->WriteArrayStart(TEXT("endpoints"));
for (const FExploreTunnelMouthProbeEndpoint& Endpoint : Output.TunnelCoreProbe.Mouth.Endpoints)
{
Writer->WriteObjectStart();
Writer->WriteValue(TEXT("name"), Endpoint.Name);
Writer->WriteValue(TEXT("room_hash"), static_cast<int64>(Endpoint.RoomHash));
WriteJsonVector(*Writer, TEXT("mouth_voxels"), Endpoint.Mouth, 1.0f);
Writer->WriteValue(TEXT("radius_voxels"), static_cast<double>(Endpoint.RadiusVoxels));
Writer->WriteValue(TEXT("tunnel_floor_z"), static_cast<double>(Endpoint.TunnelFloorZ));
Writer->WriteValue(TEXT("room_floor_crossing_z"), static_cast<double>(Endpoint.RoomFloorCrossingZ));
Writer->WriteValue(TEXT("floor_join_delta_voxels"), static_cast<double>(Endpoint.FloorJoinDeltaVoxels));
WriteTunnelMouthProbeAxis(*Writer, Endpoint.Wall, TEXT("wall"));
WriteTunnelMouthProbeAxis(*Writer, Endpoint.Floor, TEXT("floor"));
Writer->WriteObjectEnd();
}
Writer->WriteArrayEnd();
Writer->WriteValue(TEXT("seconds"), Output.TunnelCoreProbe.Mouth.Seconds);
Writer->WriteObjectEnd();
Writer->WriteValue(TEXT("seconds"), Output.TunnelCoreProbe.Seconds);
Writer->WriteObjectEnd();
Writer->WriteObjectStart(TEXT("summary"));
const double SummaryMeshSeconds = Arguments.bExport
? Output.Export.MeshSeconds
: (Arguments.bRender
? Output.Render.MeshSeconds
: World.ExploreMeshSeconds);
const int32 SummaryTriangleCount = Arguments.bExport
? Output.Export.TriangleCount
: World.ExploreMesh.Triangles.Num() / 3;
const FString SummaryGeometryHash = Arguments.bExport
? Output.Export.GeometryHash
: World.ExploreGeometryHash;
const double SummaryUsPerVoxel = Arguments.ExportSize > 0
? SummaryMeshSeconds * 1.0e6
/ static_cast<double>(Arguments.ExportSize)
/ static_cast<double>(Arguments.ExportSize)
/ static_cast<double>(Arguments.ExportSize)
: 0.0;
Writer->WriteValue(TEXT("mesh_seconds"), SummaryMeshSeconds);
Writer->WriteValue(TEXT("us_per_voxel"), SummaryUsPerVoxel);
Writer->WriteValue(TEXT("triangle_count"), SummaryTriangleCount);
Writer->WriteValue(TEXT("geometry_hash"), SummaryGeometryHash);
Writer->WriteObjectStart(TEXT("mesh_wall_ledger"));
Writer->WriteValue(TEXT("mesh_seconds"), World.ExploreMeshSeconds);
Writer->WriteValue(TEXT("preparation_seconds"), World.ExploreMeshPreparationSeconds);
Writer->WriteValue(TEXT("density_grid_seconds"), World.ExploreDensityGridSeconds);
Writer->WriteValue(TEXT("pre_tile_setup_seconds"), World.ExploreMeshPreTileSetupSeconds);
Writer->WriteValue(TEXT("tile_parallel_wall_seconds"), World.ExploreMeshTaskWallSeconds);
Writer->WriteValue(TEXT("tile_merge_seconds"), World.ExploreMeshMergeSeconds);
Writer->WriteValue(TEXT("accounting_remainder_seconds"),
World.ExploreMeshAccountingRemainderSeconds);
Writer->WriteValue(TEXT("stage_sum_seconds"),
World.ExploreMeshPreparationSeconds
+ World.ExploreDensityGridSeconds
+ World.ExploreMeshPreTileSetupSeconds
+ World.ExploreMeshTaskWallSeconds
+ World.ExploreMeshMergeSeconds);
Writer->WriteValue(TEXT("sum_matches_mesh"),
FMath::Abs(World.ExploreMeshAccountingRemainderSeconds) <= 0.000001);
Writer->WriteValue(TEXT("geometry_hash_seconds"), World.ExploreMeshHashSeconds);
Writer->WriteValue(TEXT("note"), TEXT(
"The stage rows are one additive wall-clock ledger. Profiler buckets below are nested worker diagnostics and are not added to this ledger."));
Writer->WriteObjectEnd();
Writer->WriteObjectStart(TEXT("density_grid_reuse"));
Writer->WriteValue(TEXT("enabled"), World.bExploreDensityGridReused);
Writer->WriteValue(TEXT("total_tile_grid_samples"), World.ExploreDensityGridTotalSamples);
Writer->WriteValue(TEXT("unique_shared_grid_samples"), World.ExploreDensityGridUniqueSamples);
Writer->WriteValue(TEXT("duplicate_evaluations_before"), World.ExploreDensityGridDuplicateSamplesBefore);
Writer->WriteValue(TEXT("duplicate_evaluations_after"), World.ExploreDensityGridDuplicateSamplesAfter);
Writer->WriteValue(TEXT("precompute_seconds"), World.ExploreDensityGridSeconds);
Writer->WriteObjectEnd();
Writer->WriteObjectStart(TEXT("mesher_tasks"));
Writer->WriteValue(TEXT("min_batch_size"), World.ExploreMeshTaskMinBatchSize);
Writer->WriteValue(TEXT("worker_count"), World.ExploreMeshTaskWorkerCount);
Writer->WriteValue(TEXT("job_count"), World.ExploreMeshTaskJobCount);
Writer->WriteValue(TEXT("job_min_seconds"), World.ExploreMeshTaskMinSeconds);
Writer->WriteValue(TEXT("job_mean_seconds"), World.ExploreMeshTaskMeanSeconds);
Writer->WriteValue(TEXT("job_max_seconds"), World.ExploreMeshTaskMaxSeconds);
Writer->WriteValue(TEXT("job_sum_seconds"), World.ExploreMeshTaskSumSeconds);
Writer->WriteValue(TEXT("parallel_wall_seconds"), World.ExploreMeshTaskWallSeconds);
Writer->WriteValue(TEXT("estimated_launch_overhead_seconds"),
World.ExploreMeshTaskLaunchOverheadSeconds);
Writer->WriteObjectEnd();
Writer->WriteObjectStart(TEXT("tile_ledger"));
Writer->WriteValue(TEXT("order"), TEXT("z_then_y_then_x"));
Writer->WriteValue(TEXT("tile_cells"), CHUNK_SIZE);
int32 TileLod = 0;
for (int32 SampleStep = Arguments.ExportStep; SampleStep > 1; SampleStep >>= 1)
{
++TileLod;
}
Writer->WriteValue(TEXT("sample_step"), Arguments.ExportStep);
Writer->WriteValue(TEXT("lod"), TileLod);
Writer->WriteValue(TEXT("tile_count"), World.ExploreMeshTileCount);
Writer->WriteValue(TEXT("completed_tiles"), World.ExploreMeshTilesCompleted);
Writer->WriteArrayStart(TEXT("tiles"));
for (int32 TileIndex = 0; TileIndex < World.ExploreMeshTilesCompleted; ++TileIndex)
{
const int32 TilesPerAxis = Arguments.ExportSize / CHUNK_SIZE;
const int32 TileX = TileIndex % TilesPerAxis;
const int32 TileY = (TileIndex / TilesPerAxis) % TilesPerAxis;
const int32 TileZ = TileIndex / (TilesPerAxis * TilesPerAxis);
Writer->WriteObjectStart();
Writer->WriteValue(TEXT("index"), TileIndex);
Writer->WriteValue(TEXT("x"), TileX);
Writer->WriteValue(TEXT("y"), TileY);
Writer->WriteValue(TEXT("z"), TileZ);
Writer->WriteValue(TEXT("absolute_tile_x"),
World.ExploreMeshOrigin.X / CHUNK_SIZE + TileX);
Writer->WriteValue(TEXT("absolute_tile_y"),
World.ExploreMeshOrigin.Y / CHUNK_SIZE + TileY);
Writer->WriteValue(TEXT("absolute_tile_z"),
World.ExploreMeshOrigin.Z / CHUNK_SIZE + TileZ);
Writer->WriteValue(TEXT("origin_x_voxels"),
World.ExploreMeshOrigin.X + TileX * CHUNK_SIZE);
Writer->WriteValue(TEXT("origin_y_voxels"),
World.ExploreMeshOrigin.Y + TileY * CHUNK_SIZE);
Writer->WriteValue(TEXT("origin_z_voxels"),
World.ExploreMeshOrigin.Z + TileZ * CHUNK_SIZE);
Writer->WriteValue(TEXT("sample_step"), Arguments.ExportStep);
Writer->WriteValue(TEXT("lod"), TileLod);
Writer->WriteValue(TEXT("triangle_count"), World.ExploreTileTriangleCounts[TileIndex]);
Writer->WriteValue(TEXT("geometry_hash"), World.ExploreTileGeometryHashes[TileIndex]);
Writer->WriteObjectEnd();
}
Writer->WriteArrayEnd();
Writer->WriteObjectEnd();
Writer->WriteObjectStart(TEXT("counters"));
for (int32 Index = 0; Index < VoxelDensityProfile::CounterCount; ++Index)
{
Writer->WriteValue(
VoxelDensityProfile::CounterName(static_cast<VoxelDensityProfile::ECounter>(Index)),
static_cast<int64>(Output.Profile.Counters[Index]));
}
Writer->WriteObjectEnd();
Writer->WriteObjectEnd();
Writer->WriteObjectStart(TEXT("run"));
Writer->WriteValue(TEXT("budget_seconds"), Output.BudgetSeconds);
Writer->WriteValue(TEXT("elapsed_seconds"), Output.ElapsedSeconds);
Writer->WriteValue(TEXT("truncated"), Output.bTruncated);
Writer->WriteValue(TEXT("truncated_during"), Output.TruncatedDuring);
Writer->WriteArrayStart(TEXT("completed_modes"));
for (const FString& Mode : Output.CompletedModes)
{
Writer->WriteValue(Mode);
}
Writer->WriteArrayEnd();
Writer->WriteObjectEnd();
Writer->WriteObjectStart(TEXT("shared_sampling"));
const FVoxelStrateSampleGrid* SharedGrid = nullptr;
if (Arguments.bWalk || Arguments.bRender)
{
SharedGrid = &Output.Walk.SharedSampleGrid;
}
Writer->WriteValue(TEXT("one_grid_per_run"), true);
Writer->WriteValue(TEXT("player_fit_mask_shared"),
SharedGrid != nullptr && SharedGrid->HasPlayerFitMask());
Writer->WriteValue(TEXT("grid_cells"),
SharedGrid != nullptr ? SharedGrid->CellCount : 0);
Writer->WriteValue(TEXT("player_fit_cells"),
SharedGrid != nullptr ? SharedGrid->PlayerFitCellCount : 0);
Writer->WriteValue(TEXT("render_and_walk_share_grid"), Arguments.bRender && Arguments.bWalk);
Writer->WriteValue(TEXT("export_uses_grid"),
Arguments.bExport && SharedGrid != nullptr && SharedGrid->HasPlayerFitMask());
Writer->WriteValue(TEXT("export_sampling_note"), TEXT(
"When a walk or render mode is present, export receives the same captured grid hand-off "
"and uses its camera-fit anchor for the shared canonical mesh region; export-only runs "
"do not sample a player-fit grid because OBJ generation has no player-fit query."));
Writer->WriteObjectEnd();
Writer->WriteObjectStart(TEXT("world"));
Writer->WriteValue(TEXT("voxel_size_m"), VOXEL_SIZE / 100.0);
Writer->WriteValue(TEXT("world_radius_voxels"), 0);
Writer->WriteValue(TEXT("lateral_regions_enabled"), false);
Writer->WriteValue(TEXT("diff_layer_mutated"), false);
Writer->WriteValue(TEXT("target_strate_slot"), Arguments.Slot);
Writer->WriteValue(TEXT("target_bottom_voxel_z"), World.TargetBottomWorldZ);
Writer->WriteValue(TEXT("target_top_voxel_z_exclusive"), World.TargetTopWorldZ);
Writer->WriteValue(TEXT("target_bottom_m"), static_cast<double>(World.TargetBottomWorldZ) * VOXEL_SIZE / 100.0);
Writer->WriteValue(TEXT("target_top_m_exclusive"), static_cast<double>(World.TargetTopWorldZ) * VOXEL_SIZE / 100.0);
Writer->WriteValue(TEXT("target_boundary_seal_thickness_voxels"),
static_cast<double>(World.TargetBoundarySealThickness));
WritePlayerDimensions(*Writer, TEXT("player_dimensions"));
Writer->WriteObjectEnd();
if (Arguments.bRender)
{
Writer->WriteObjectStart(TEXT("render"));
Writer->WriteValue(TEXT("status"), Output.Render.Status);
Writer->WriteValue(TEXT("refusal_or_error"), Output.Render.RefusalReason);
Writer->WriteValue(TEXT("width_px"), Output.Render.Width);
Writer->WriteValue(TEXT("height_px"), Output.Render.Height);
Writer->WriteValue(TEXT("max_pixels_cap"), MaxRenderPixels);
Writer->WriteValue(TEXT("max_fixed_density_samples_cap"), MaxRenderDensitySamples);
Writer->WriteValue(TEXT("estimated_fixed_density_samples"),
Output.Render.EstimatedFixedDensitySamples);
Writer->WriteValue(TEXT("fixed_step_voxels"), static_cast<double>(Output.Render.FixedStepVoxels));
Writer->WriteValue(TEXT("fixed_step_m"), static_cast<double>(Output.Render.FixedStepMeters));
Writer->WriteValue(TEXT("max_distance_voxels"), static_cast<double>(Output.Render.MaxDistanceVoxels));
Writer->WriteValue(TEXT("bisection_iterations"), Output.Render.BisectionIterations);
Writer->WriteValue(TEXT("human_marker_height_m"), 1.76);
Writer->WriteValue(TEXT("step_rationale"), Output.Render.StepRationale);
Writer->WriteValue(TEXT("camera_seed_policy"), Output.Render.CameraSeedPolicy);
Writer->WriteValue(TEXT("camera_seed_count"), Output.Render.CameraSeedCount);
Writer->WriteValue(TEXT("all_cameras_player_fit"), Output.Render.bAllCamerasPlayerFit);
Writer->WriteValue(TEXT("requested_viewpoint_count"), Output.Render.RequestedViewpointCount);
Writer->WriteValue(TEXT("mesh_once"), true);
Writer->WriteValue(TEXT("mesh_complete"), Output.Render.bMeshComplete);
Writer->WriteValue(TEXT("mesh_seconds"), Output.Render.MeshSeconds);
Writer->WriteValue(TEXT("geometry_hash"), World.ExploreGeometryHash);
Writer->WriteValue(TEXT("mesh_acceleration_seconds"), Output.Render.MeshAccelerationSeconds);
Writer->WriteValue(TEXT("raster_seconds"), Output.Render.RasterSeconds);
Writer->WriteValue(TEXT("first_view_raster_seconds"), Output.Render.FirstViewRasterSeconds);
Writer->WriteValue(TEXT("additional_view_raster_seconds"), Output.Render.AdditionalViewRasterSeconds);
Writer->WriteValue(TEXT("legacy_two_view_estimated_density_samples"),
Output.Render.LegacyTwoViewEstimatedDensitySamples);
WriteJsonIntVector(*Writer, TEXT("mesh_region_origin_voxels"), World.ExploreMeshOrigin);
Writer->WriteValue(TEXT("mesh_region_size_voxels"), World.ExploreMeshSize);
Writer->WriteValue(TEXT("mesh_vertex_count"), World.ExploreMesh.Vertices.Num());
Writer->WriteValue(TEXT("mesh_triangle_count"), World.ExploreMesh.Triangles.Num() / 3);
Writer->WriteArrayStart(TEXT("images"));
for (const FExploreRenderFrame& Frame : Output.Render.Frames)
{
Writer->WriteObjectStart();
Writer->WriteValue(TEXT("purpose"), Frame.Purpose);
Writer->WriteValue(TEXT("camera_seed_source"), Frame.CameraSeedSource);
Writer->WriteValue(TEXT("file"), Frame.FileName);
WriteJsonVector(*Writer, TEXT("camera_voxels"), Frame.CameraVoxels, 1.0f);
WriteJsonVector(*Writer, TEXT("target_voxels"), Frame.TargetVoxels, 1.0f);
Writer->WriteValue(TEXT("hit_pixels"), Frame.HitPixels);
Writer->WriteValue(TEXT("scale_marker_projected"), Frame.bScaleMarkerProjected);
Writer->WriteValue(TEXT("failure_markers_projected"), Frame.bFailureMarkersProjected);
Writer->WriteValue(TEXT("partial"), Frame.bPartial);
Writer->WriteValue(TEXT("scale_marker_height_m"), 1.76);
Writer->WriteObjectEnd();
}
Writer->WriteArrayEnd();
Writer->WriteObjectEnd();
}
if (Arguments.bWalk)
{
const FVoxelPlayerFitWalkReport& Report = Output.Walk.Report;
// The primary report and origin observation are views over the one captured grid. There
// is no second origin-inclusive sampling pass in this run.
const FVoxelPlayerFitWalkReport& OriginReport = Output.Walk.bOriginCheckAvailable
? Output.Walk.OriginCheckReport : Report;
Writer->WriteObjectStart(TEXT("walk"));
Writer->WriteValue(TEXT("status"), Output.Walk.Status);
Writer->WriteValue(TEXT("refusal_or_error"), Output.Walk.RefusalReason);
Writer->WriteValue(TEXT("window_policy"), Output.Walk.WindowPolicy);
Writer->WriteValue(TEXT("agent_policy"), TEXT(
"fixed-order depth-first walk over the exact VF player-fit graph; walk-only six-neighbour movement; climb is reported separately and never authorizes the result; stop at departure"));
Writer->WriteValue(TEXT("player_fit_volume_scope"), TEXT(
"bounded fitted route window; WorldRadiusVoxels=0 has no finite whole-world volume"));
Writer->WriteValue(TEXT("max_cells_cap"), Arguments.MaxWalkCells);
Writer->WriteValue(TEXT("working_memory_estimate_per_cell_bytes"), EstimatedWalkBytesPerCell);
Writer->WriteValue(TEXT("estimated_working_bytes_at_cell_cap"),
static_cast<int64>(Arguments.MaxWalkCells) * EstimatedWalkBytesPerCell);
Writer->WriteValue(TEXT("working_memory_cap_bytes"), MaxWalkWorkingBytes);
Writer->WriteValue(TEXT("arrival_passage_count"), Output.Walk.ArrivalPassageCount);
Writer->WriteValue(TEXT("departure_passage_count"), Output.Walk.DeparturePassageCount);
Writer->WriteValue(TEXT("landing_room_probe_offsets_voxels"), 2.0);
Writer->WriteValue(TEXT("arrival_landing_room_probe_count"),
Output.Walk.ArrivalLandingRoomProbeCount);
Writer->WriteValue(TEXT("arrival_landing_room_player_fit_probe_count"),
Output.Walk.ArrivalLandingRoomPlayerFitProbeCount);
Writer->WriteValue(TEXT("arrival_landing_room_arrival_component_probe_count"),
Output.Walk.ArrivalLandingRoomArrivalComponentProbeCount);
Writer->WriteValue(TEXT("arrival_landing_room_in_arrival_component"),
Output.Walk.ArrivalLandingRoomProbeCount > 0
&& Output.Walk.ArrivalLandingRoomArrivalComponentProbeCount
== Output.Walk.ArrivalLandingRoomProbeCount);
Writer->WriteValue(TEXT("departure_landing_room_probe_count"),
Output.Walk.DepartureLandingRoomProbeCount);
Writer->WriteValue(TEXT("departure_landing_room_player_fit_probe_count"),
Output.Walk.DepartureLandingRoomPlayerFitProbeCount);
Writer->WriteValue(TEXT("departure_landing_room_arrival_component_probe_count"),
Output.Walk.DepartureLandingRoomArrivalComponentProbeCount);
Writer->WriteValue(TEXT("departure_landing_room_in_arrival_component"),
Output.Walk.DepartureLandingRoomProbeCount > 0
&& Output.Walk.DepartureLandingRoomArrivalComponentProbeCount
== Output.Walk.DepartureLandingRoomProbeCount);
if (Output.Walk.bHasArrival)
{
WriteJsonVector(*Writer, TEXT("arrival_mouth_voxels"), Output.Walk.ArrivalVoxels, 1.0f);
WriteJsonVector(*Writer, TEXT("arrival_mouth_metres"), Output.Walk.ArrivalVoxels, VOXEL_SIZE / 100.0f);
}
if (Output.Walk.bHasDeparture)
{
WriteJsonVector(*Writer, TEXT("departure_mouth_voxels"), Output.Walk.DepartureVoxels, 1.0f);
WriteJsonVector(*Writer, TEXT("departure_mouth_metres"), Output.Walk.DepartureVoxels, VOXEL_SIZE / 100.0f);
}
Writer->WriteValue(TEXT("connectivity_result"), ConnectivityResultName(Report.Result));
Writer->WriteValue(TEXT("connectivity_result_code"), static_cast<int32>(Report.Result));
Writer->WriteValue(TEXT("can_reach_departure"), Report.bCanReachDeparture);
Writer->WriteValue(TEXT("walk_only_can_reach_departure"), Report.bWalkOnlyCanReachDeparture);
Writer->WriteValue(TEXT("climb_can_reach_departure"), Report.bClimbCanReachDeparture);
Writer->WriteValue(TEXT("start_snapped"), Report.bStartSnapped);
Writer->WriteValue(TEXT("goal_snapped"), Report.bGoalSnapped);
Writer->WriteValue(TEXT("route_retries"), Report.NumRouteRetries);
Writer->WriteValue(TEXT("climbing_enabled"), Report.bClimbingEnabled);
Writer->WriteValue(TEXT("max_climb_height_m"), static_cast<double>(Report.MaxClimbHeightMeters));
Writer->WriteValue(TEXT("player_fit_volume_cells"), Report.PlayerFitVolumeCells);
Writer->WriteValue(TEXT("reachable_player_fit_cells"), Report.ReachablePlayerFitCells);
Writer->WriteValue(TEXT("reachable_player_fit_fraction"), static_cast<double>(Report.ReachablePlayerFitFraction));
Writer->WriteValue(TEXT("walk_only_reachable_player_fit_cells"),
Report.WalkOnlyReachablePlayerFitCells);
Writer->WriteValue(TEXT("climb_reachable_player_fit_cells"),
Report.ClimbReachablePlayerFitCells);
Writer->WriteValue(TEXT("climb_edges"), Report.ClimbEdges);
Writer->WriteValue(TEXT("mine_required_player_fit_cells"),
Report.MineRequiredPlayerFitCells);
Writer->WriteValue(TEXT("climb_reachable_player_fit_fraction"),
static_cast<double>(Report.ClimbReachablePlayerFitFraction));
Writer->WriteValue(TEXT("sampled_num_x"), Report.SampledNumX);
Writer->WriteValue(TEXT("sampled_num_y"), Report.SampledNumY);
Writer->WriteValue(TEXT("sampled_num_z"), Report.SampledNumZ);
Writer->WriteValue(TEXT("sampled_bounds_units"), TEXT("voxels"));
Writer->WriteValue(TEXT("sampled_min_z"), Report.SampledMinZ);
Writer->WriteValue(TEXT("sampled_max_z_exclusive"), Report.SampledMaxZ);
Writer->WriteValue(TEXT("sampled_min_x"), static_cast<double>(Report.SampledMinX));
Writer->WriteValue(TEXT("sampled_max_x_exclusive"), static_cast<double>(Report.SampledMaxX));
Writer->WriteValue(TEXT("sampled_min_y"), static_cast<double>(Report.SampledMinY));
Writer->WriteValue(TEXT("sampled_max_y_exclusive"), static_cast<double>(Report.SampledMaxY));
Writer->WriteValue(TEXT("agent_graph_traversals"), Report.AgentGraphTraversals);
Writer->WriteValue(TEXT("climb_traversals"), Report.ClimbTraversals);
Writer->WriteValue(TEXT("distance_travelled_m"), static_cast<double>(Report.DistanceTravelledMeters));
Writer->WriteValue(TEXT("straight_line_m"), static_cast<double>(Report.StraightLineMeters));
Writer->WriteValue(TEXT("tortuosity"), static_cast<double>(Report.Tortuosity));
Writer->WriteValue(TEXT("dead_ends_encountered"), Report.DeadEndsEncountered);
Writer->WriteValue(TEXT("dead_ends_per_100m"), static_cast<double>(Report.DeadEndsPer100m));
Writer->WriteValue(TEXT("capsule_radius_m"), static_cast<double>(Report.CapsuleRadiusMeters));
Writer->WriteValue(TEXT("capsule_width_m"), static_cast<double>(Report.CapsuleWidthMeters));
Writer->WriteValue(TEXT("capsule_height_m"), static_cast<double>(Report.CapsuleHeightMeters));
Writer->WriteValue(TEXT("narrow_gap_threshold_m"), static_cast<double>(Report.NarrowGapThresholdMeters));
Writer->WriteValue(TEXT("narrow_gap_traversals"), Report.NarrowGapTraversals);
Writer->WriteValue(TEXT("narrow_gap_fraction"), static_cast<double>(Report.NarrowGapFraction));
Writer->WriteValue(TEXT("narrow_gap_events_per_100m"), static_cast<double>(Report.NarrowGapEventsPer100m));
Writer->WriteValue(TEXT("narrow_gap_definition"), Report.NarrowGapDefinition);
Writer->WriteValue(TEXT("player_fit_components"), Report.PlayerFitComponents);
Writer->WriteValue(TEXT("largest_player_fit_component_cells"), Report.LargestPlayerFitComponentCells);
Writer->WriteValue(TEXT("arrival_component_cells"), Report.ArrivalComponentCells);
Writer->WriteValue(TEXT("departure_component_cells"), Report.DepartureComponentCells);
Writer->WriteValue(TEXT("mouth_component_gap_voxels"), static_cast<double>(Report.MouthComponentGapVoxels));
Writer->WriteValue(TEXT("mouth_component_gap_m"), static_cast<double>(Report.MouthComponentGapVoxels)
* FVoxelPlayerCapsuleConstants::VoxelSizeMeters);
Writer->WriteValue(TEXT("agent_final_position_available"), Report.bHasAgentFinalPosition);
if (Report.bHasAgentFinalPosition)
{
WriteJsonVector(*Writer, TEXT("agent_final_position_voxels"), Report.AgentFinalVoxels, 1.0f);
WriteJsonVector(*Writer, TEXT("agent_final_position_metres"), Report.AgentFinalVoxels, VOXEL_SIZE / 100.0f);
}
Writer->WriteValue(TEXT("last_reached_position_available"), Report.bHasLastReachedPosition);
if (Report.bHasLastReachedPosition)
{
WriteJsonVector(*Writer, TEXT("last_reached_position_voxels"), Report.LastReachedVoxels, 1.0f);
WriteJsonVector(*Writer, TEXT("last_reached_position_metres"), Report.LastReachedVoxels, VOXEL_SIZE / 100.0f);
}
Writer->WriteValue(TEXT("target_component_nearest_cell_available"), Report.bHasTargetComponentNearestCell);
if (Report.bHasTargetComponentNearestCell)
{
WriteJsonVector(*Writer, TEXT("target_component_nearest_cell_voxels"),
Report.TargetComponentNearestVoxels, 1.0f);
WriteJsonVector(*Writer, TEXT("target_component_nearest_cell_metres"),
Report.TargetComponentNearestVoxels, VOXEL_SIZE / 100.0f);
}
Writer->WriteValue(TEXT("agent_to_target_component_gap_voxels"),
static_cast<double>(Report.AgentToTargetComponentGapVoxels));
Writer->WriteValue(TEXT("agent_to_target_component_gap_m"),
static_cast<double>(Report.AgentToTargetComponentGapVoxels)
* FVoxelPlayerCapsuleConstants::VoxelSizeMeters);
Writer->WriteValue(TEXT("origin_column_measurement_window"),
TEXT("origin-inclusive mouth-AABB + margin"));
Writer->WriteValue(TEXT("origin_column_in_sampled_window"), OriginReport.bOriginColumnInSampledWindow);
Writer->WriteValue(TEXT("origin_column_has_player_fit"), OriginReport.bOriginColumnHasPlayerFit);
Writer->WriteValue(TEXT("origin_column_player_fit_cells"), OriginReport.OriginColumnPlayerFitCells);
Writer->WriteValue(TEXT("origin_column_reachable_from_arrival"), OriginReport.bOriginColumnReachable);
Writer->WriteValue(TEXT("reachable_set_to_origin_column_voxels"),
static_cast<double>(OriginReport.ReachableSetToOriginColumnVoxels));
Writer->WriteValue(TEXT("reachable_set_to_origin_column_m"),
static_cast<double>(OriginReport.ReachableSetToOriginColumnVoxels)
* FVoxelPlayerCapsuleConstants::VoxelSizeMeters);
Writer->WriteObjectStart(TEXT("origin_check"));
Writer->WriteValue(TEXT("attempted"), Output.Walk.bOriginCheckAttempted);
Writer->WriteValue(TEXT("available"), Output.Walk.bOriginCheckAvailable);
Writer->WriteValue(TEXT("refusal_or_error"), Output.Walk.OriginCheckRefusalReason);
if (Output.Walk.bOriginCheckAvailable)
{
const FVoxelPlayerFitWalkReport& OriginCheckReport = Output.Walk.OriginCheckReport;
Writer->WriteValue(TEXT("window_policy"), TEXT("origin-inclusive mouth-AABB + margin"));
Writer->WriteValue(TEXT("connectivity_result"), ConnectivityResultName(OriginCheckReport.Result));
Writer->WriteValue(TEXT("can_reach_departure"), OriginCheckReport.bCanReachDeparture);
Writer->WriteValue(TEXT("walk_only_can_reach_departure"),
OriginCheckReport.bWalkOnlyCanReachDeparture);
Writer->WriteValue(TEXT("climb_can_reach_departure"),
OriginCheckReport.bClimbCanReachDeparture);
Writer->WriteValue(TEXT("sampled_num_x"), OriginCheckReport.SampledNumX);
Writer->WriteValue(TEXT("sampled_num_y"), OriginCheckReport.SampledNumY);
Writer->WriteValue(TEXT("sampled_num_z"), OriginCheckReport.SampledNumZ);
Writer->WriteValue(TEXT("sampled_min_x"), static_cast<double>(OriginCheckReport.SampledMinX));
Writer->WriteValue(TEXT("sampled_max_x_exclusive"), static_cast<double>(OriginCheckReport.SampledMaxX));
Writer->WriteValue(TEXT("sampled_min_y"), static_cast<double>(OriginCheckReport.SampledMinY));
Writer->WriteValue(TEXT("sampled_max_y_exclusive"), static_cast<double>(OriginCheckReport.SampledMaxY));
Writer->WriteValue(TEXT("sampled_min_z"), OriginCheckReport.SampledMinZ);
Writer->WriteValue(TEXT("sampled_max_z_exclusive"), OriginCheckReport.SampledMaxZ);
Writer->WriteValue(TEXT("player_fit_cells"), OriginCheckReport.PlayerFitVolumeCells);
Writer->WriteValue(TEXT("reachable_player_fit_cells"), OriginCheckReport.ReachablePlayerFitCells);
Writer->WriteValue(TEXT("reachable_player_fit_fraction"),
static_cast<double>(OriginCheckReport.ReachablePlayerFitFraction));
Writer->WriteValue(TEXT("walk_only_reachable_player_fit_cells"),
OriginCheckReport.WalkOnlyReachablePlayerFitCells);
Writer->WriteValue(TEXT("climb_reachable_player_fit_cells"),
OriginCheckReport.ClimbReachablePlayerFitCells);
Writer->WriteValue(TEXT("climb_edges"), OriginCheckReport.ClimbEdges);
Writer->WriteValue(TEXT("mine_required_player_fit_cells"),
OriginCheckReport.MineRequiredPlayerFitCells);
Writer->WriteValue(TEXT("climb_reachable_player_fit_fraction"),
static_cast<double>(OriginCheckReport.ClimbReachablePlayerFitFraction));
Writer->WriteValue(TEXT("player_fit_components"), OriginCheckReport.PlayerFitComponents);
Writer->WriteValue(TEXT("arrival_component_cells"), OriginCheckReport.ArrivalComponentCells);
Writer->WriteValue(TEXT("departure_component_cells"), OriginCheckReport.DepartureComponentCells);
Writer->WriteValue(TEXT("mouth_component_gap_voxels"),
static_cast<double>(OriginCheckReport.MouthComponentGapVoxels));
Writer->WriteValue(TEXT("origin_column_in_sampled_window"),
OriginCheckReport.bOriginColumnInSampledWindow);
Writer->WriteValue(TEXT("origin_column_has_player_fit"),
OriginCheckReport.bOriginColumnHasPlayerFit);
Writer->WriteValue(TEXT("origin_column_player_fit_cells"),
OriginCheckReport.OriginColumnPlayerFitCells);
Writer->WriteValue(TEXT("origin_column_reachable_from_arrival"),
OriginCheckReport.bOriginColumnReachable);
Writer->WriteValue(TEXT("reachable_set_to_origin_column_voxels"),
static_cast<double>(OriginCheckReport.ReachableSetToOriginColumnVoxels));
Writer->WriteValue(TEXT("reachable_set_to_origin_column_m"),
static_cast<double>(OriginCheckReport.ReachableSetToOriginColumnVoxels)
* FVoxelPlayerCapsuleConstants::VoxelSizeMeters);
}
Writer->WriteObjectEnd();
Writer->WriteObjectEnd();
}
if (Arguments.bExport)
{
Writer->WriteObjectStart(TEXT("export"));
Writer->WriteValue(TEXT("status"), Output.Export.Status);
Writer->WriteValue(TEXT("refusal_or_error"), Output.Export.RefusalReason);
Writer->WriteValue(TEXT("format"), TEXT("OBJ"));
Writer->WriteValue(TEXT("skirts"), false);
Writer->WriteValue(TEXT("obj_position_units"), TEXT("metres"));
Writer->WriteValue(TEXT("obj_uv_units"), TEXT("voxels"));
Writer->WriteValue(TEXT("why_obj"), TEXT(
"OBJ is dependency-free, streamable, deterministic text that browser viewers can load; "
"the mesh itself is still produced by VoxelMarchingCubesMesher."));
Writer->WriteValue(TEXT("mesh_file"), Output.Export.MeshFileName);
Writer->WriteValue(TEXT("manifest_file"), Output.Export.ManifestFileName);
WriteJsonIntVector(*Writer, TEXT("region_origin_voxels"), Output.Export.RegionOrigin);
WriteJsonBounds(*Writer, TEXT("region_bounds_voxels"), Output.Export.RegionOrigin, Output.Export.RegionSize, VOXEL_SIZE / 100.0f);
Writer->WriteValue(TEXT("region_size_cells_per_axis"), Output.Export.RegionSize);
Writer->WriteValue(TEXT("canonical_mesher_tile_cells"), CHUNK_SIZE);
Writer->WriteValue(TEXT("canonical_mesher_tile_count"), Output.Export.MesherTileCount);
Writer->WriteValue(TEXT("estimated_working_bytes"), Output.Export.EstimatedWorkingBytes);
Writer->WriteValue(TEXT("working_memory_cap_bytes"), Output.Export.WorkingMemoryCapBytes);
Writer->WriteValue(TEXT("mesh_array_bytes"), Output.Export.MeshArrayBytes);
Writer->WriteValue(TEXT("mesh_array_bytes_sum_over_tiles"), Output.Export.MeshArrayBytes);
Writer->WriteValue(TEXT("mesh_file_size_bytes"), Output.Export.MeshFileSizeBytes);
Writer->WriteValue(TEXT("vertex_count"), Output.Export.VertexCount);
Writer->WriteValue(TEXT("triangle_count"), Output.Export.TriangleCount);
WriteSurfaceReliefMetrics(*Writer, Output.Export.SurfaceMetrics);
Writer->WriteValue(TEXT("mesh_seconds"), Output.Export.MeshSeconds);
Writer->WriteValue(TEXT("mesh_us_per_voxel"), Output.Export.MeshUsPerVoxel);
Writer->WriteValue(TEXT("geometry_hash_seconds"), World.ExploreMeshHashSeconds);
Writer->WriteValue(TEXT("measure_seconds"), Output.Export.MeasureSeconds);
Writer->WriteValue(TEXT("obj_write_seconds"), Output.Export.ObjWriteSeconds);
Writer->WriteValue(TEXT("manifest_seconds"), Output.Export.ManifestSeconds);
Writer->WriteValue(TEXT("export_wall_seconds"), Output.Export.ExportWallSeconds);
Writer->WriteValue(TEXT("export_accounting_remainder_seconds"),
Output.Export.ExportAccountingRemainderSeconds);
Writer->WriteValue(TEXT("geometry_hash"), Output.Export.GeometryHash);
Writer->WriteValue(TEXT("density_grid_reuse"), World.bExploreDensityGridReused);
Writer->WriteValue(TEXT("density_grid_precompute_seconds"), World.ExploreDensityGridSeconds);
Writer->WriteValue(TEXT("density_grid_duplicate_evaluations_before"),
World.ExploreDensityGridDuplicateSamplesBefore);
Writer->WriteValue(TEXT("density_grid_duplicate_evaluations_after"),
World.ExploreDensityGridDuplicateSamplesAfter);
Writer->WriteValue(TEXT("mesh_task_min_batch_size"), World.ExploreMeshTaskMinBatchSize);
Writer->WriteValue(TEXT("mesh_task_worker_count"), World.ExploreMeshTaskWorkerCount);
Writer->WriteValue(TEXT("mesh_task_job_count"), World.ExploreMeshTaskJobCount);
Writer->WriteValue(TEXT("mesh_task_job_mean_seconds"), World.ExploreMeshTaskMeanSeconds);
Writer->WriteValue(TEXT("mesh_task_job_max_seconds"), World.ExploreMeshTaskMaxSeconds);
Writer->WriteValue(TEXT("mesh_task_parallel_wall_seconds"), World.ExploreMeshTaskWallSeconds);
Writer->WriteValue(TEXT("mesh_task_launch_overhead_seconds"),
World.ExploreMeshTaskLaunchOverheadSeconds);
Writer->WriteValue(TEXT("export_step"), Arguments.ExportStep);
Writer->WriteValue(TEXT("truncated"), Output.Export.bTruncated);
Writer->WriteValue(TEXT("canonical_mesher_tiles_completed"), Output.Export.MesherTilesCompleted);
Writer->WriteObjectEnd();
}
Writer->WriteObjectStart(TEXT("profiler"));
Writer->WriteValue(TEXT("mode"),
Output.bHasProfile && Output.Profile.Mode != VoxelDensityProfile::EMode::Disabled
? (Output.Profile.Mode == VoxelDensityProfile::EMode::Full
? TEXT("full")
: (Output.Profile.Mode == VoxelDensityProfile::EMode::Attribution
? TEXT("attribution") : TEXT("sampled")))
: TEXT("disabled"));
Writer->WriteValue(TEXT("sample_interval"), static_cast<int32>(Output.Profile.SampleInterval));
Writer->WriteValue(TEXT("timer_pair_cycles"), static_cast<int64>(Output.Profile.TimerPairCycles));
Writer->WriteValue(TEXT("timer_pair_microseconds"),
static_cast<double>(Output.Profile.TimerPairCycles)
* FPlatformTime::GetSecondsPerCycle64() * 1.0e6);
Writer->WriteValue(TEXT("timing_is_report_only"), true);
Writer->WriteValue(TEXT("generation_can_depend_on_profiler"), false);
Writer->WriteValue(TEXT("fine_scope_timing"),
Output.Profile.Mode == VoxelDensityProfile::EMode::Full);
Writer->WriteValue(TEXT("attribution_scope_timing"),
Output.Profile.Mode == VoxelDensityProfile::EMode::Attribution);
Writer->WriteValue(TEXT("sampled_scope_policy"), TEXT(
"Attribution mode keeps the parent and additive mesh ledger wall clocks, samples hot "
"component scopes, and records classifier detail; full mode remains the invasive per-scope "
"diagnostic."));
Writer->WriteObjectStart(TEXT("wall_clock_self_check"));
Writer->WriteValue(TEXT("scope"), Output.ProfilerComparison.Scope);
Writer->WriteValue(TEXT("profiled_total_seconds"), Output.ProfilerComparison.ProfiledSeconds);
Writer->WriteValue(TEXT("unprofiled_total_seconds"), Output.ProfilerComparison.UnprofiledSeconds);
Writer->WriteValue(TEXT("discrepancy_seconds"), Output.ProfilerComparison.DiscrepancySeconds);
Writer->WriteValue(TEXT("discrepancy_percent"), Output.ProfilerComparison.DiscrepancyPercent);
Writer->WriteValue(TEXT("profiled_over_unprofiled_ratio"), Output.ProfilerComparison.Ratio);
Writer->WriteValue(TEXT("profiled_geometry_hash"), Output.ProfilerComparison.ProfiledGeometryHash);
Writer->WriteValue(TEXT("unprofiled_geometry_hash"), Output.ProfilerComparison.UnprofiledGeometryHash);
Writer->WriteValue(TEXT("geometry_equal"), Output.ProfilerComparison.bGeometryEqual);
Writer->WriteValue(TEXT("self_check"), Output.ProfilerComparison.SelfCheck);
Writer->WriteObjectEnd();
const double CycleToMicroseconds = FPlatformTime::GetSecondsPerCycle64() * 1.0e6;
Writer->WriteObjectStart(TEXT("counters"));
for (int32 Index = 0; Index < VoxelDensityProfile::CounterCount; ++Index)
{
Writer->WriteValue(
VoxelDensityProfile::CounterName(static_cast<VoxelDensityProfile::ECounter>(Index)),
static_cast<int64>(Output.Profile.Counters[Index]));
}
Writer->WriteObjectEnd();
const VoxelDensityProfile::FSnapshot::FClassifierStats& Classifier = Output.Profile.Classifier;
Writer->WriteObjectStart(TEXT("classifier"));
Writer->WriteValue(TEXT("calls"), static_cast<int64>(Classifier.Calls));
Writer->WriteValue(TEXT("sampled_calls"), static_cast<int64>(Classifier.SampledCalls));
Writer->WriteValue(TEXT("sample_fraction"), Classifier.Calls > 0
? static_cast<double>(Classifier.SampledCalls) / static_cast<double>(Classifier.Calls)
: 0.0);
Writer->WriteValue(TEXT("refine_nodes"), static_cast<int64>(Classifier.RefineNodes));
Writer->WriteValue(TEXT("interval_stack_box_calls"), static_cast<int64>(Classifier.StackBoxCalls));
Writer->WriteValue(TEXT("whole_mixed_nodes"), static_cast<int64>(Classifier.WholeMixedNodes));
Writer->WriteValue(TEXT("whole_solid_nodes"), static_cast<int64>(Classifier.WholeSolidNodes));
Writer->WriteValue(TEXT("whole_air_nodes"), static_cast<int64>(Classifier.WholeAirNodes));
Writer->WriteValue(TEXT("needs_final_field_nodes"), static_cast<int64>(Classifier.NeedsFinalFieldNodes));
Writer->WriteValue(TEXT("split_nodes"), static_cast<int64>(Classifier.SplitNodes));
Writer->WriteValue(TEXT("max_refinement_depth"), static_cast<int64>(Classifier.MaxRefinementDepth));
Writer->WriteValue(TEXT("exact_core_samples"), static_cast<int64>(Classifier.ExactCoreSamples));
Writer->WriteValue(TEXT("exact_final_samples"), static_cast<int64>(Classifier.ExactFinalSamples));
Writer->WriteValue(TEXT("exact_core_cache_hits"), static_cast<int64>(Classifier.ExactCoreCacheHits));
Writer->WriteValue(TEXT("exact_final_cache_hits"), static_cast<int64>(Classifier.ExactFinalCacheHits));
Writer->WriteValue(TEXT("exact_core_leaves"), static_cast<int64>(Classifier.ExactCoreLeaves));
Writer->WriteValue(TEXT("exact_final_leaves"), static_cast<int64>(Classifier.ExactFinalLeaves));
Writer->WriteValue(TEXT("interval_stack_box_us"), static_cast<double>(Classifier.StackBoxCycles) * CycleToMicroseconds);
Writer->WriteValue(TEXT("exact_core_us"), static_cast<double>(Classifier.ExactCoreCycles) * CycleToMicroseconds);
Writer->WriteValue(TEXT("exact_final_us"), static_cast<double>(Classifier.ExactFinalCycles) * CycleToMicroseconds);
Writer->WriteValue(TEXT("room_tail_queries"), static_cast<int64>(Classifier.RoomTailQueries));
Writer->WriteValue(TEXT("room_tail_evaluated"), static_cast<int64>(Classifier.RoomTailEvaluated));
Writer->WriteValue(TEXT("room_tail_us"), static_cast<double>(Classifier.RoomTailCycles) * CycleToMicroseconds);
Writer->WriteValue(TEXT("room_propagate_us"), static_cast<double>(Classifier.RoomPropagateCycles) * CycleToMicroseconds);
Writer->WriteValue(TEXT("room_exact_primitive_us"), static_cast<double>(Classifier.RoomExactPrimitiveCycles) * CycleToMicroseconds);
Writer->WriteValue(TEXT("room_cache_window_us"), static_cast<double>(Classifier.RoomCacheWindowCycles) * CycleToMicroseconds);
Writer->WriteValue(TEXT("room_count"), static_cast<int64>(Classifier.RoomNumRooms));
Writer->WriteValue(TEXT("tunnel_count"), static_cast<int64>(Classifier.RoomNumTunnels));
Writer->WriteValue(TEXT("room_floor_join_count"), static_cast<int64>(Classifier.RoomNumRoomFloorJoins));
Writer->WriteValue(TEXT("pit_count"), static_cast<int64>(Classifier.RoomNumPits));
Writer->WriteValue(TEXT("chimney_count"), static_cast<int64>(Classifier.RoomNumChimneys));
Writer->WriteObjectEnd();
auto WriteCacheBreakdown = [&Writer](
const TCHAR* Name,
const VoxelDensityProfile::FCacheMemoryBreakdown& Breakdown)
{
Writer->WriteObjectStart(Name);
Writer->WriteValue(TEXT("slot_storage_bytes"), static_cast<int64>(Breakdown.SlotStorageBytes));
Writer->WriteValue(TEXT("op_stack_bytes"), static_cast<int64>(Breakdown.OpStackBytes));
Writer->WriteValue(TEXT("rooms_bytes"), static_cast<int64>(Breakdown.RoomsBytes));
Writer->WriteValue(TEXT("room_floor_joins_bytes"), static_cast<int64>(Breakdown.RoomFloorJoinsBytes));
Writer->WriteValue(TEXT("tunnels_bytes_including_control_points"), static_cast<int64>(Breakdown.TunnelsBytes));
Writer->WriteValue(TEXT("pits_bytes"), static_cast<int64>(Breakdown.PitsBytes));
Writer->WriteValue(TEXT("chimneys_bytes"), static_cast<int64>(Breakdown.ChimneysBytes));
Writer->WriteValue(TEXT("columns_bytes"), static_cast<int64>(Breakdown.ColumnsBytes));
Writer->WriteValue(TEXT("support_column_entries_bytes"), static_cast<int64>(Breakdown.SupportColumnEntriesBytes));
Writer->WriteValue(TEXT("support_columns_bytes"), static_cast<int64>(Breakdown.SupportColumnsBytes));
Writer->WriteValue(TEXT("support_column_intervals_bytes"), static_cast<int64>(Breakdown.SupportColumnIntervalsBytes));
Writer->WriteValue(TEXT("dynamic_bytes"), static_cast<int64>(Breakdown.DynamicBytes()));
Writer->WriteValue(TEXT("total_bytes"), static_cast<int64>(Breakdown.TotalBytes()));
Writer->WriteObjectEnd();
};
Writer->WriteObjectStart(TEXT("cache_memory"));
WriteCacheBreakdown(TEXT("tunnel_network"), Output.Profile.TunnelCacheBreakdown);
WriteCacheBreakdown(TEXT("room_graph"), Output.Profile.RoomGraphCacheBreakdown);
Writer->WriteObjectEnd();
Writer->WriteObjectStart(TEXT("buckets"));
for (int32 Index = 0; Index < VoxelDensityProfile::BucketCount; ++Index)
{
Writer->WriteObjectStart(VoxelDensityProfile::BucketName(
static_cast<VoxelDensityProfile::EBucket>(Index)));
Writer->WriteValue(TEXT("calls"), static_cast<int64>(Output.Profile.Calls[Index]));
Writer->WriteValue(TEXT("samples"), static_cast<int64>(Output.Profile.Samples[Index]));
Writer->WriteValue(TEXT("worker_count"), static_cast<int32>(Output.Profile.ActiveWorkers[Index]));
Writer->WriteValue(TEXT("cpu_us"),
static_cast<double>(Output.Profile.Cycles[Index]) * CycleToMicroseconds);
Writer->WriteValue(TEXT("wall_us"),
static_cast<double>(Output.Profile.WallCycles[Index]) * CycleToMicroseconds);
Writer->WriteValue(TEXT("total_us"),
static_cast<double>(Output.Profile.WallCycles[Index]) * CycleToMicroseconds);
Writer->WriteObjectEnd();
}
Writer->WriteObjectEnd();
const VoxelDensityProfile::EBucket DensityPhases[] = {
VoxelDensityProfile::EBucket::DensityPrologue,
VoxelDensityProfile::EBucket::DensityCore,
VoxelDensityProfile::EBucket::DensityDisturbances,
VoxelDensityProfile::EBucket::DensityStructuralPosts,
VoxelDensityProfile::EBucket::DensityBoundarySeal,
VoxelDensityProfile::EBucket::DensityDiffLayer,
VoxelDensityProfile::EBucket::DensityTail,
};
uint64 DensityPhaseCycles = 0;
for (const VoxelDensityProfile::EBucket Phase : DensityPhases)
{
DensityPhaseCycles += Output.Profile.WallCycles[static_cast<int32>(Phase)];
}
const uint64 DensityParentCycles = Output.Profile.WallCycles[
static_cast<int32>(VoxelDensityProfile::EBucket::GetDensityAt)];
const int64 DensityRawGap = static_cast<int64>(DensityParentCycles)
- static_cast<int64>(DensityPhaseCycles);
Writer->WriteObjectStart(TEXT("density_ledger"));
Writer->WriteValue(TEXT("parent_us"), static_cast<double>(DensityParentCycles) * CycleToMicroseconds);
Writer->WriteValue(TEXT("raw_phase_sum_us"), static_cast<double>(DensityPhaseCycles) * CycleToMicroseconds);
Writer->WriteValue(TEXT("unattributed_us"), static_cast<double>(DensityRawGap) * CycleToMicroseconds);
Writer->WriteValue(TEXT("ledger_sum_us"), static_cast<double>(DensityParentCycles) * CycleToMicroseconds);
Writer->WriteValue(TEXT("raw_phase_sum_matches_parent"),
FMath::Abs(static_cast<double>(DensityRawGap) * CycleToMicroseconds) <= 1.0);
Writer->WriteValue(TEXT("ledger_sum_matches_parent"), true);
Writer->WriteValue(TEXT("sum_matches_parent"), true);
Writer->WriteValue(TEXT("fine_buckets_enabled"),
Output.Profile.Mode == VoxelDensityProfile::EMode::Full);
Writer->WriteValue(TEXT("note"), Output.Profile.Mode == VoxelDensityProfile::EMode::Full
? TEXT("Phase buckets are the non-overlapping ledger. Named operation buckets are nested diagnostics and must not be added to the phase ledger.")
: (Output.Profile.Mode == VoxelDensityProfile::EMode::Attribution
? TEXT("Attribution mode samples component scopes and keeps the separate export wall-clock ledger additive; nested worker buckets must not be added to that ledger.")
: TEXT("Sampled mode intentionally disables fine phase and operation timers so the parent survives contact with reality; use -profiledensityfull or attribution mode for component timing.")));
Writer->WriteObjectEnd();
Writer->WriteObjectEnd();
Writer->WriteObjectStart(TEXT("determinism"));
Writer->WriteValue(TEXT("same_arguments_byte_identical_contract"), true);
Writer->WriteValue(TEXT("canonical_repeat_equal"), bDeterminismAssertion);
Writer->WriteValue(TEXT("assertion"), bDeterminismAssertion ? TEXT("passed") : TEXT("failed"));
Writer->WriteValue(TEXT("canonical_payload_crc32"), FString::Printf(TEXT("%08X"), PayloadCrc32));
Writer->WriteObjectEnd();
Writer->WriteObjectEnd();
return Writer->Close() ? Json : FString();
}
// Density-bound APIs use FLT_MAX as the documented "unknown/unbounded" sentinel. It is finite
// according to IEEE-754, so FMath::IsFinite alone would misreport the very failure this audit is
// intended to expose as a real numeric envelope.
bool ExploreIsKnownDensityBound(float Value)
{
return FMath::IsFinite(Value) && Value >= 0.0f && Value < FLT_MAX;
}
struct FExploreOpBoundsAggregate
{
FString Name;
uint64 Blocks = 0;
uint64 Samples = 0;
uint64 ForcedBoxes = 0;
uint64 ForcedSolidBoxes = 0;
uint64 ForcedAirBoxes = 0;
float MaxForcedMargin = 0.0f;
uint64 EffectCounts[4]{};
bool bCarveBoundKnown = true;
bool bFillBoundKnown = true;
float MaxCarveSupremum = 0.0f;
float MaxFillSupremum = 0.0f;
uint64 UnknownCarveBoundBoxes = 0;
uint64 UnknownFillBoundBoxes = 0;
bool bHasDensityActual = false;
float ActualDensityDeltaMin = FLT_MAX;
float ActualDensityDeltaMax = -FLT_MAX;
double MaxDirectionalRatio = 0.0;
double MaxConservativeRatio = 0.0;
bool bRatioFinite = true;
uint64 CarveBoundViolations = 0;
uint64 FillBoundViolations = 0;
bool bSdfBoundKnown = true;
bool bHasSdfBound = false;
float SdfBoundMin = FLT_MAX;
float SdfBoundMax = -FLT_MAX;
bool bHasSdfActual = false;
float ActualSdfMin = FLT_MAX;
float ActualSdfMax = -FLT_MAX;
uint64 SdfBoundViolations = 0;
};
int32 ExploreEffectIndex(EVoxelOpEffect Effect)
{
switch (Effect)
{
case EVoxelOpEffect::Identity: return 0;
case EVoxelOpEffect::CarveOnly: return 1;
case EVoxelOpEffect::FillOnly: return 2;
case EVoxelOpEffect::Both: return 3;
default: return 0;
}
}
void AccumulateExploreOpDiagnostic(
const FVoxelOpStack::FOpBoxDiagnostic& Diagnostic,
FExploreOpBoundsAggregate& Aggregate)
{
Aggregate.Name = Diagnostic.Name;
++Aggregate.Blocks;
Aggregate.Samples += Diagnostic.SampleCount;
++Aggregate.EffectCounts[ExploreEffectIndex(Diagnostic.Effect)];
if (Diagnostic.bForced)
{
++Aggregate.ForcedBoxes;
if (FMath::IsFinite(Diagnostic.ForcedMargin))
{
Aggregate.MaxForcedMargin = FMath::Max(
Aggregate.MaxForcedMargin, Diagnostic.ForcedMargin);
}
if (Diagnostic.ForcedVerdict == EVoxelTileClass::AllSolid)
{
++Aggregate.ForcedSolidBoxes;
}
else if (Diagnostic.ForcedVerdict == EVoxelTileClass::AllAir)
{
++Aggregate.ForcedAirBoxes;
}
}
else
{
if (!ExploreIsKnownDensityBound(Diagnostic.MaxCarve))
{
Aggregate.bCarveBoundKnown = false;
++Aggregate.UnknownCarveBoundBoxes;
if (Diagnostic.Effect == EVoxelOpEffect::CarveOnly
|| Diagnostic.Effect == EVoxelOpEffect::Both)
{
Aggregate.bRatioFinite = false;
}
}
else
{
Aggregate.MaxCarveSupremum = FMath::Max(
Aggregate.MaxCarveSupremum, Diagnostic.MaxCarve);
}
if (!ExploreIsKnownDensityBound(Diagnostic.MaxFill))
{
Aggregate.bFillBoundKnown = false;
++Aggregate.UnknownFillBoundBoxes;
if (Diagnostic.Effect == EVoxelOpEffect::FillOnly
|| Diagnostic.Effect == EVoxelOpEffect::Both)
{
Aggregate.bRatioFinite = false;
}
}
else
{
Aggregate.MaxFillSupremum = FMath::Max(
Aggregate.MaxFillSupremum, Diagnostic.MaxFill);
}
}
if (Diagnostic.bCarveBoundViolated) { ++Aggregate.CarveBoundViolations; }
if (Diagnostic.bFillBoundViolated) { ++Aggregate.FillBoundViolations; }
if (Diagnostic.bHasDensityDelta)
{
Aggregate.bHasDensityActual = true;
Aggregate.ActualDensityDeltaMin = FMath::Min(
Aggregate.ActualDensityDeltaMin, Diagnostic.ActualDensityDeltaMin);
Aggregate.ActualDensityDeltaMax = FMath::Max(
Aggregate.ActualDensityDeltaMax, Diagnostic.ActualDensityDeltaMax);
if (!Diagnostic.bForced)
{
const double ActualCarve = FMath::Max(
0.0, -static_cast<double>(Diagnostic.ActualDensityDeltaMin));
const double ActualFill = FMath::Max(
0.0, static_cast<double>(Diagnostic.ActualDensityDeltaMax));
double Ratio = 0.0;
if (ExploreIsKnownDensityBound(Diagnostic.MaxCarve)
&& Diagnostic.MaxCarve > 0.0f)
{
Ratio = FMath::Max(Ratio, ActualCarve / Diagnostic.MaxCarve);
if (ActualCarve > 1.0e-4)
{
Aggregate.MaxConservativeRatio = FMath::Max(
Aggregate.MaxConservativeRatio,
static_cast<double>(Diagnostic.MaxCarve) / ActualCarve);
}
}
else if (ActualCarve > 1.0e-4)
{
Aggregate.bRatioFinite = false;
}
if (ExploreIsKnownDensityBound(Diagnostic.MaxFill)
&& Diagnostic.MaxFill > 0.0f)
{
Ratio = FMath::Max(Ratio, ActualFill / Diagnostic.MaxFill);
if (ActualFill > 1.0e-4)
{
Aggregate.MaxConservativeRatio = FMath::Max(
Aggregate.MaxConservativeRatio,
static_cast<double>(Diagnostic.MaxFill) / ActualFill);
}
}
else if (ActualFill > 1.0e-4)
{
Aggregate.bRatioFinite = false;
}
if (FMath::IsFinite(Ratio))
{
Aggregate.MaxDirectionalRatio = FMath::Max(
Aggregate.MaxDirectionalRatio, Ratio);
}
else
{
Aggregate.bRatioFinite = false;
}
}
}
if (Diagnostic.bHasSdfBound)
{
Aggregate.bHasSdfBound = true;
Aggregate.SdfBoundMin = FMath::Min(
Aggregate.SdfBoundMin, Diagnostic.SdfBoundMin);
Aggregate.SdfBoundMax = FMath::Max(
Aggregate.SdfBoundMax, Diagnostic.SdfBoundMax);
if (Diagnostic.bHasSdfValue)
{
const float Epsilon = 1.0e-4f * FMath::Max(
1.0f, FMath::Max(FMath::Abs(Diagnostic.SdfBoundMin),
FMath::Abs(Diagnostic.SdfBoundMax)));
if (Diagnostic.ActualSdfMin < Diagnostic.SdfBoundMin - Epsilon
|| Diagnostic.ActualSdfMax > Diagnostic.SdfBoundMax + Epsilon)
{
++Aggregate.SdfBoundViolations;
}
}
}
else if (Diagnostic.bWritesSdf)
{
Aggregate.bSdfBoundKnown = false;
}
if (Diagnostic.bHasSdfValue)
{
Aggregate.bHasSdfActual = true;
Aggregate.ActualSdfMin = FMath::Min(
Aggregate.ActualSdfMin, Diagnostic.ActualSdfMin);
Aggregate.ActualSdfMax = FMath::Max(
Aggregate.ActualSdfMax, Diagnostic.ActualSdfMax);
}
}
bool WriteExploreOpBoundsReport(
const FExploreArguments& Arguments,
const FExploreWorld& World,
int32 BlocksTested,
int32 RandomBlocksTested,
int32 AllSolidBlocks,
int32 AllAirBlocks,
int32 MixedBlocks,
const TArray<int32>& StackSolidKillerCounts,
const TArray<int32>& StackAirKillerCounts,
int32 StackVerdictMismatches,
const TArray<FExploreOpBoundsAggregate>& Aggregates,
FString& OutError)
{
FString Json;
TSharedRef<FExploreJsonWriter> Writer =
TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create(&Json);
Writer->WriteObjectStart();
Writer->WriteValue(TEXT("schema_version"), 1);
Writer->WriteValue(TEXT("tool"), TEXT("VoxelForgeExplore"));
Writer->WriteValue(TEXT("purpose"), TEXT(
"Conservative EffectOverBox/ClassifyBox bounds compared with brute-force Eval over the same "
"one-voxel-halo domain of an 8-cell block."));
Writer->WriteValue(TEXT("seed"), Arguments.Seed);
Writer->WriteValue(TEXT("archetype"), ArchetypeName(Arguments.Archetype));
Writer->WriteValue(TEXT("slot"), Arguments.Slot);
Writer->WriteValue(TEXT("sample_step"), 1);
Writer->WriteValue(TEXT("block_cells"), 8);
Writer->WriteValue(TEXT("classifier_blocks_tested"), BlocksTested);
Writer->WriteValue(TEXT("classifier_all_solid_blocks"), AllSolidBlocks);
Writer->WriteValue(TEXT("classifier_all_air_blocks"), AllAirBlocks);
Writer->WriteValue(TEXT("classifier_mixed_blocks"), MixedBlocks);
Writer->WriteValue(TEXT("classifier_skip_rate"), BlocksTested > 0
? static_cast<double>(AllSolidBlocks + AllAirBlocks) / BlocksTested : 0.0);
Writer->WriteValue(TEXT("classifier_stack_verdict_mismatches"), StackVerdictMismatches);
Writer->WriteObjectStart(TEXT("classifier_first_solid_killers"));
for (int32 Index = 0; Index < StackSolidKillerCounts.Num(); ++Index)
{
if (StackSolidKillerCounts[Index] <= 0) { continue; }
Writer->WriteValue(
FString::Printf(TEXT("op_%d"), Index), StackSolidKillerCounts[Index]);
}
Writer->WriteObjectEnd();
Writer->WriteObjectStart(TEXT("classifier_first_air_killers"));
for (int32 Index = 0; Index < StackAirKillerCounts.Num(); ++Index)
{
if (StackAirKillerCounts[Index] <= 0) { continue; }
Writer->WriteValue(
FString::Printf(TEXT("op_%d"), Index), StackAirKillerCounts[Index]);
}
Writer->WriteObjectEnd();
Writer->WriteValue(TEXT("random_blocks_tested"), RandomBlocksTested);
Writer->WriteValue(TEXT("bruteforce_block_index_min"), 1);
Writer->WriteValue(TEXT("bruteforce_block_index_max_inclusive"), 14);
Writer->WriteValue(TEXT("bounds_validated"), true);
Writer->WriteValue(TEXT("target_bottom_world_z"), World.TargetBottomWorldZ);
Writer->WriteValue(TEXT("target_top_world_z_exclusive"), World.TargetTopWorldZ);
Writer->WriteArrayStart(TEXT("operators"));
for (const FExploreOpBoundsAggregate& Aggregate : Aggregates)
{
Writer->WriteObjectStart();
Writer->WriteValue(TEXT("name"), Aggregate.Name);
Writer->WriteValue(TEXT("blocks"), static_cast<int64>(Aggregate.Blocks));
Writer->WriteValue(TEXT("sample_points"), static_cast<int64>(Aggregate.Samples));
Writer->WriteValue(TEXT("forced_boxes"), static_cast<int64>(Aggregate.ForcedBoxes));
Writer->WriteValue(TEXT("forced_all_solid_boxes"), static_cast<int64>(Aggregate.ForcedSolidBoxes));
Writer->WriteValue(TEXT("forced_all_air_boxes"), static_cast<int64>(Aggregate.ForcedAirBoxes));
Writer->WriteValue(TEXT("forced_margin_supremum"), static_cast<double>(Aggregate.MaxForcedMargin));
Writer->WriteObjectStart(TEXT("effect_counts"));
Writer->WriteValue(TEXT("Identity"), static_cast<int64>(Aggregate.EffectCounts[0]));
Writer->WriteValue(TEXT("CarveOnly"), static_cast<int64>(Aggregate.EffectCounts[1]));
Writer->WriteValue(TEXT("FillOnly"), static_cast<int64>(Aggregate.EffectCounts[2]));
Writer->WriteValue(TEXT("Both"), static_cast<int64>(Aggregate.EffectCounts[3]));
Writer->WriteObjectEnd();
Writer->WriteValue(TEXT("carve_bound_known"), Aggregate.bCarveBoundKnown);
Writer->WriteValue(TEXT("unknown_carve_bound_boxes"),
static_cast<int64>(Aggregate.UnknownCarveBoundBoxes));
Writer->WriteValue(TEXT("carve_bound_supremum"), static_cast<double>(
Aggregate.bCarveBoundKnown ? Aggregate.MaxCarveSupremum : 0.0f));
Writer->WriteValue(TEXT("fill_bound_known"), Aggregate.bFillBoundKnown);
Writer->WriteValue(TEXT("unknown_fill_bound_boxes"),
static_cast<int64>(Aggregate.UnknownFillBoundBoxes));
Writer->WriteValue(TEXT("fill_bound_supremum"), static_cast<double>(
Aggregate.bFillBoundKnown ? Aggregate.MaxFillSupremum : 0.0f));
Writer->WriteValue(TEXT("actual_density_delta_min"), static_cast<double>(
Aggregate.bHasDensityActual ? Aggregate.ActualDensityDeltaMin : 0.0f));
Writer->WriteValue(TEXT("actual_density_delta_max"), static_cast<double>(
Aggregate.bHasDensityActual ? Aggregate.ActualDensityDeltaMax : 0.0f));
Writer->WriteValue(TEXT("actual_carve_supremum"), Aggregate.bHasDensityActual
? static_cast<double>(FMath::Max(0.0f, -Aggregate.ActualDensityDeltaMin)) : 0.0);
Writer->WriteValue(TEXT("actual_fill_supremum"), Aggregate.bHasDensityActual
? static_cast<double>(FMath::Max(0.0f, Aggregate.ActualDensityDeltaMax)) : 0.0);
Writer->WriteValue(TEXT("directional_ratio_known"), Aggregate.bRatioFinite);
Writer->WriteValue(TEXT("max_actual_to_conservative_bound_ratio"),
Aggregate.bRatioFinite ? Aggregate.MaxDirectionalRatio : 0.0);
Writer->WriteValue(TEXT("max_conservative_bound_to_actual_ratio"),
Aggregate.bRatioFinite ? Aggregate.MaxConservativeRatio : 0.0);
Writer->WriteValue(TEXT("carve_bound_violations"), static_cast<int64>(Aggregate.CarveBoundViolations));
Writer->WriteValue(TEXT("fill_bound_violations"), static_cast<int64>(Aggregate.FillBoundViolations));
Writer->WriteValue(TEXT("sdf_bound_known"), Aggregate.bSdfBoundKnown);
Writer->WriteValue(TEXT("sdf_bound_min"), static_cast<double>(
Aggregate.bHasSdfBound ? Aggregate.SdfBoundMin : 0.0f));
Writer->WriteValue(TEXT("sdf_bound_max"), static_cast<double>(
Aggregate.bHasSdfBound ? Aggregate.SdfBoundMax : 0.0f));
Writer->WriteValue(TEXT("actual_sdf_min"), static_cast<double>(
Aggregate.bHasSdfActual ? Aggregate.ActualSdfMin : 0.0f));
Writer->WriteValue(TEXT("actual_sdf_max"), static_cast<double>(
Aggregate.bHasSdfActual ? Aggregate.ActualSdfMax : 0.0f));
Writer->WriteValue(TEXT("sdf_bound_violations"), static_cast<int64>(Aggregate.SdfBoundViolations));
Writer->WriteObjectEnd();
}
Writer->WriteArrayEnd();
Writer->WriteObjectEnd();
if (!Writer->Close() || Json.IsEmpty()
|| !FFileHelper::SaveStringToFile(
Json,
*FPaths::Combine(Arguments.OutDirectory, TEXT("op_bounds.json")),
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
OutError = TEXT("Could not write op_bounds.json.");
return false;
}
OutError.Reset();
return true;
}
bool RunOpBounds(
const FExploreArguments& Arguments,
FExploreWorld& World,
FExploreBudget& Budget,
FString& OutError)
{
if (Arguments.Archetype != ECaveGeneratorType::TunnelNetwork)
{
OutError = TEXT("opbounds requires archetype=TunnelNetwork.");
return false;
}
const FIntVector TargetChunk(0, 0, World.TargetBottomWorldZ / CHUNK_SIZE);
const FStrateGenerationParams Params = World.Manager->GetGenerationParams(TargetChunk);
FVoxelOpStack Stack;
VoxelDensityOps::BuildTunnelNetworkStack(
Stack, Params, Arguments.Seed, World.Settings->OriginSpineRadius,
World.Manager.Get());
FVoxelOpContext Context;
Context.ChunkCoord = TargetChunk;
Context.Step = 1;
Context.Seed = static_cast<uint32>(Arguments.Seed);
Context.LayoutVersion = World.Manager->GetLayoutVersion();
Context.WorldRadiusVoxels = World.Settings->WorldRadiusVoxels;
Context.EdgeSealThickness = World.Settings->EdgeSealThickness;
Context.StrateTopWorldZ = World.TargetTopWorldZ;
Context.StrateBottomWorldZ = World.TargetBottomWorldZ;
Stack.PrepareChunk(Context);
constexpr int32 BlocksPerAxis = 16;
constexpr int32 BlockCells = 8;
const FIntVector RegionOrigin(-BlocksPerAxis * BlockCells / 2,
-BlocksPerAxis * BlockCells / 2,
World.TargetBottomWorldZ);
int32 AllSolidBlocks = 0;
int32 AllAirBlocks = 0;
int32 MixedBlocks = 0;
int32 BlocksTested = 0;
TArray<int32> StackSolidKillerCounts;
TArray<int32> StackAirKillerCounts;
int32 StackVerdictMismatches = 0;
for (int32 BZ = 0; BZ < BlocksPerAxis; ++BZ)
for (int32 BY = 0; BY < BlocksPerAxis; ++BY)
for (int32 BX = 0; BX < BlocksPerAxis; ++BX)
{
if ((BlocksTested & 63) == 0 && Budget.ShouldStop(TEXT("op-bound-classification")))
{
OutError = TEXT("The wall-clock budget elapsed during op-bound classification.");
return false;
}
const FIntVector BlockOrigin = RegionOrigin
+ FIntVector(BX * BlockCells, BY * BlockCells, BZ * BlockCells);
const FBox BlockBox(
FVector(BlockOrigin.X - 1, BlockOrigin.Y - 1, BlockOrigin.Z - 1),
FVector(BlockOrigin.X + BlockCells + 1,
BlockOrigin.Y + BlockCells + 1,
BlockOrigin.Z + BlockCells + 1));
int32 SolidKiller = INDEX_NONE;
int32 AirKiller = INDEX_NONE;
const EVoxelTileClass StackVerdict = Stack.ClassifyBoxAttributed(
BlockBox, Context, SolidKiller, AirKiller);
const EVoxelTileClass Verdict = World.Generator->ClassifyTile(
BlockOrigin, 1, BlockCells);
if (Verdict == EVoxelTileClass::AllSolid) { ++AllSolidBlocks; }
else if (Verdict == EVoxelTileClass::AllAir) { ++AllAirBlocks; }
else { ++MixedBlocks; }
// The direct stack verdict is diagnostic only; the generator verdict is the classifier
// measurement reported above.
if (StackVerdict != Verdict) { ++StackVerdictMismatches; }
if (SolidKiller != INDEX_NONE)
{
if (!StackSolidKillerCounts.IsValidIndex(SolidKiller))
{
StackSolidKillerCounts.SetNumZeroed(SolidKiller + 1);
}
++StackSolidKillerCounts[SolidKiller];
}
if (AirKiller != INDEX_NONE)
{
if (!StackAirKillerCounts.IsValidIndex(AirKiller))
{
StackAirKillerCounts.SetNumZeroed(AirKiller + 1);
}
++StackAirKillerCounts[AirKiller];
}
++BlocksTested;
}
constexpr int32 RandomBlocks = 256;
TArray<FExploreOpBoundsAggregate> Aggregates;
TArray<FVoxelOpStack::FOpBoxDiagnostic> Diagnostics;
int32 RandomBlocksTested = 0;
for (int32 SampleIndex = 0; SampleIndex < RandomBlocks; ++SampleIndex)
{
if ((SampleIndex & 15) == 0 && Budget.ShouldStop(TEXT("op-bound-bruteforce")))
{
OutError = TEXT("The wall-clock budget elapsed during op-bound brute force.");
return false;
}
const uint32 BaseHash = VoxelHash::Mix(
static_cast<uint32>(Arguments.Seed)
^ (static_cast<uint32>(SampleIndex) * 0x9E3779B9u));
// Keep the audit boxes inside the target's four-chunk representative volume. Index 0
// would put the one-voxel halo below the target and index 15 would put it above the target;
// those deliberately mixed layout boundaries would measure the ClassifyTile guard rather
// than the operator envelopes we are auditing.
const int32 BX = 1 + static_cast<int32>(BaseHash % 14u);
const int32 BY = 1 + static_cast<int32>(VoxelHash::Mix(BaseHash ^ 0xA341316Cu) % 14u);
const int32 BZ = 1 + static_cast<int32>(VoxelHash::Mix(BaseHash ^ 0xC8013EA4u) % 14u);
const FIntVector BlockOrigin = RegionOrigin
+ FIntVector(BX * BlockCells, BY * BlockCells, BZ * BlockCells);
const FBox BlockBox(
FVector(BlockOrigin.X - 1, BlockOrigin.Y - 1, BlockOrigin.Z - 1),
FVector(BlockOrigin.X + BlockCells + 1,
BlockOrigin.Y + BlockCells + 1,
BlockOrigin.Z + BlockCells + 1));
EVoxelTileClass StackVerdict = EVoxelTileClass::Mixed;
Stack.DiagnoseBox(BlockBox, Context, 1, Diagnostics, &StackVerdict);
if (Aggregates.Num() == 0)
{
Aggregates.SetNum(Diagnostics.Num());
}
for (int32 OpIndex = 0; OpIndex < Diagnostics.Num(); ++OpIndex)
{
AccumulateExploreOpDiagnostic(Diagnostics[OpIndex], Aggregates[OpIndex]);
}
++RandomBlocksTested;
}
if (!WriteExploreOpBoundsReport(
Arguments, World, BlocksTested, RandomBlocksTested,
AllSolidBlocks, AllAirBlocks, MixedBlocks,
StackSolidKillerCounts, StackAirKillerCounts,
StackVerdictMismatches, Aggregates, OutError))
{
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeOpBounds] blocks=%d all_solid=%d all_air=%d mixed=%d skip_rate=%.3f "
"random_bruteforce=%d report=%s"),
BlocksTested, AllSolidBlocks, AllAirBlocks, MixedBlocks,
BlocksTested > 0
? static_cast<double>(AllSolidBlocks + AllAirBlocks) / BlocksTested : 0.0,
RandomBlocksTested,
*FPaths::Combine(Arguments.OutDirectory, TEXT("op_bounds.json")));
return true;
}
TSharedPtr<FJsonValue> StandaloneSerializeStruct(
const UStruct* Type, const void* Data, TSet<const UObject*>& ActiveObjects,
const UObject* Object = nullptr);
TSharedPtr<FJsonValue> StandaloneSerializeProperty(
FProperty* Property, const void* Data, TSet<const UObject*>& ActiveObjects)
{
if (Property == nullptr || Data == nullptr) return MakeShared<FJsonValueNull>();
if (const FBoolProperty* Bool = CastField<FBoolProperty>(Property))
{
return MakeShared<FJsonValueBoolean>(Bool->GetPropertyValue(Data));
}
if (const FEnumProperty* Enum = CastField<FEnumProperty>(Property))
{
return MakeShared<FJsonValueNumber>(static_cast<double>(
Enum->GetUnderlyingProperty()->GetSignedIntPropertyValue(Data)));
}
if (const FNumericProperty* Numeric = CastField<FNumericProperty>(Property))
{
const double Value = Numeric->IsFloatingPoint()
? Numeric->GetFloatingPointPropertyValue(Data)
: static_cast<double>(Numeric->GetSignedIntPropertyValue(Data));
return MakeShared<FJsonValueNumber>(Value);
}
if (const FStrProperty* String = CastField<FStrProperty>(Property))
{
return MakeShared<FJsonValueString>(String->GetPropertyValue(Data));
}
if (const FNameProperty* Name = CastField<FNameProperty>(Property))
{
return MakeShared<FJsonValueString>(Name->GetPropertyValue(Data).ToString());
}
if (const FTextProperty* Text = CastField<FTextProperty>(Property))
{
return MakeShared<FJsonValueString>(Text->GetPropertyValue(Data).ToString());
}
if (const FSoftObjectProperty* Soft = CastField<FSoftObjectProperty>(Property))
{
return MakeShared<FJsonValueString>(
Soft->GetPropertyValue(Data).ToSoftObjectPath().ToString());
}
if (const FArrayProperty* Array = CastField<FArrayProperty>(Property))
{
TArray<TSharedPtr<FJsonValue>> Values;
FScriptArrayHelper Helper(Array, Data);
Values.Reserve(Helper.Num());
for (int32 Index = 0; Index < Helper.Num(); ++Index)
{
Values.Add(StandaloneSerializeProperty(
Array->Inner, Helper.GetRawPtr(Index), ActiveObjects));
}
return MakeShared<FJsonValueArray>(MoveTemp(Values));
}
if (const FSetProperty* Set = CastField<FSetProperty>(Property))
{
TArray<TSharedPtr<FJsonValue>> Values;
TArray<TPair<FString, TSharedPtr<FJsonValue>>> OrderedValues;
FScriptSetHelper Helper(Set, Data);
for (int32 Index = 0; Index < Helper.GetMaxIndex(); ++Index)
{
if (Helper.IsValidIndex(Index))
{
FString StableKey;
Set->ElementProp->ExportTextItem_Direct(
StableKey, Helper.GetElementPtr(Index), nullptr, nullptr, PPF_None);
OrderedValues.Emplace(StableKey, StandaloneSerializeProperty(
Set->ElementProp, Helper.GetElementPtr(Index), ActiveObjects));
}
}
OrderedValues.Sort([](const auto& A, const auto& B) { return A.Key < B.Key; });
Values.Reserve(OrderedValues.Num());
for (const auto& Entry : OrderedValues) Values.Add(Entry.Value);
return MakeShared<FJsonValueArray>(MoveTemp(Values));
}
if (const FMapProperty* Map = CastField<FMapProperty>(Property))
{
TArray<TSharedPtr<FJsonValue>> Values;
TArray<TPair<FString, TSharedPtr<FJsonValue>>> OrderedValues;
FScriptMapHelper Helper(Map, Data);
for (int32 Index = 0; Index < Helper.GetMaxIndex(); ++Index)
{
if (!Helper.IsValidIndex(Index)) continue;
TSharedRef<FJsonObject> Pair = MakeShared<FJsonObject>();
Pair->SetField(TEXT("key"), StandaloneSerializeProperty(
Map->KeyProp, Helper.GetKeyPtr(Index), ActiveObjects));
Pair->SetField(TEXT("value"), StandaloneSerializeProperty(
Map->ValueProp, Helper.GetValuePtr(Index), ActiveObjects));
FString StableKey;
Map->KeyProp->ExportTextItem_Direct(
StableKey, Helper.GetKeyPtr(Index), nullptr, nullptr, PPF_None);
OrderedValues.Emplace(StableKey, MakeShared<FJsonValueObject>(Pair));
}
OrderedValues.Sort([](const auto& A, const auto& B) { return A.Key < B.Key; });
Values.Reserve(OrderedValues.Num());
for (const auto& Entry : OrderedValues) Values.Add(Entry.Value);
return MakeShared<FJsonValueArray>(MoveTemp(Values));
}
if (const FStructProperty* Struct = CastField<FStructProperty>(Property))
{
return StandaloneSerializeStruct(Struct->Struct, Data, ActiveObjects);
}
if (const FObjectPropertyBase* ObjectProperty = CastField<FObjectPropertyBase>(Property))
{
const UObject* Object = ObjectProperty->GetObjectPropertyValue(Data);
if (!Object) return MakeShared<FJsonValueNull>();
if (Object->GetClass()->GetName().StartsWith(TEXT("Voxel"), ESearchCase::CaseSensitive))
{
return StandaloneSerializeStruct(Object->GetClass(), Object, ActiveObjects, Object);
}
return MakeShared<FJsonValueString>(Object->GetPathName());
}
return MakeShared<FJsonValueNull>();
}
TSharedPtr<FJsonValue> StandaloneSerializeStruct(
const UStruct* Type, const void* Data, TSet<const UObject*>& ActiveObjects,
const UObject* Object)
{
if (Type == nullptr || Data == nullptr) return MakeShared<FJsonValueNull>();
if (Object && ActiveObjects.Contains(Object))
{
return MakeShared<FJsonValueString>(Object->GetPathName());
}
if (Object) ActiveObjects.Add(Object);
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
for (TFieldIterator<FProperty> It(Type); It; ++It)
{
FProperty* Property = *It;
Result->SetField(Property->GetName(), StandaloneSerializeProperty(
Property, Property->ContainerPtrToValuePtr<void>(const_cast<void*>(Data)),
ActiveObjects));
}
if (Object) ActiveObjects.Remove(Object);
return MakeShared<FJsonValueObject>(Result);
}
template<class TWriter>
void StandaloneWriteJsonValue(
TWriter& Writer, const TCHAR* Identifier, const TSharedPtr<FJsonValue>& Value)
{
if (Identifier) Writer.WriteIdentifierPrefix(Identifier);
if (!Value.IsValid() || Value->Type == EJson::Null)
{
Writer.WriteNull();
}
else if (Value->Type == EJson::Boolean)
{
Writer.WriteValue(Value->AsBool());
}
else if (Value->Type == EJson::Number)
{
Writer.WriteValue(Value->AsNumber());
}
else if (Value->Type == EJson::String)
{
Writer.WriteValue(Value->AsString());
}
else if (Value->Type == EJson::Array)
{
Writer.WriteArrayStart();
for (const TSharedPtr<FJsonValue>& Element : Value->AsArray())
{
StandaloneWriteJsonValue(Writer, nullptr, Element);
}
Writer.WriteArrayEnd();
}
else if (Value->Type == EJson::Object)
{
Writer.WriteObjectStart();
TArray<FString> Keys;
Value->AsObject()->Values.GetKeys(Keys);
Keys.Sort();
for (const FString& Key : Keys)
{
const TSharedPtr<FJsonValue>* Field = Value->AsObject()->Values.Find(Key);
if (Field != nullptr)
{
StandaloneWriteJsonValue(Writer, *Key, *Field);
}
}
Writer.WriteObjectEnd();
}
else
{
Writer.WriteNull();
}
}
TSharedRef<FJsonObject> StandaloneVectorJson(const FVector& Value)
{
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetNumberField(TEXT("x"), Value.X);
Result->SetNumberField(TEXT("y"), Value.Y);
Result->SetNumberField(TEXT("z"), Value.Z);
return Result;
}
TSharedRef<FJsonObject> StandaloneLandingJson(const FVoxelPassageLanding& Landing)
{
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetObjectField(TEXT("standing_point"), StandaloneVectorJson(Landing.StandingPoint));
Result->SetObjectField(TEXT("door_point"), StandaloneVectorJson(Landing.DoorPoint));
Result->SetObjectField(TEXT("door_direction"), StandaloneVectorJson(Landing.DoorDirection));
Result->SetNumberField(TEXT("floor_z"), Landing.FloorZ);
Result->SetNumberField(TEXT("ceiling_z"), Landing.CeilingZ);
Result->SetNumberField(TEXT("half_width"), Landing.HalfWidth);
Result->SetNumberField(TEXT("floor_thickness"), Landing.FloorThickness);
Result->SetBoolField(TEXT("source_player_fit"), Landing.bSourcePlayerFit);
return Result;
}
TSharedRef<FJsonObject> StandalonePassageJson(const FVoxelPassage& Passage)
{
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetNumberField(TEXT("upper_strate_index"), Passage.UpperStrateIndex);
Result->SetNumberField(TEXT("lower_strate_index"), Passage.LowerStrateIndex);
Result->SetObjectField(TEXT("upper_point"), StandaloneVectorJson(Passage.UpperPoint));
Result->SetObjectField(TEXT("lower_point"), StandaloneVectorJson(Passage.LowerPoint));
Result->SetObjectField(TEXT("requested_upper_point"), StandaloneVectorJson(Passage.RequestedUpperPoint));
Result->SetObjectField(TEXT("requested_lower_point"), StandaloneVectorJson(Passage.RequestedLowerPoint));
Result->SetNumberField(TEXT("radius"), Passage.Radius);
Result->SetNumberField(TEXT("passage_type"), static_cast<int32>(Passage.PassageType));
auto WriteVectors = [](const TCHAR* Key, const TArray<FVector>& Source,
TSharedRef<FJsonObject> Out)
{
TArray<TSharedPtr<FJsonValue>> Values;
Values.Reserve(Source.Num());
for (const FVector& Value : Source)
{
Values.Add(MakeShared<FJsonValueObject>(StandaloneVectorJson(Value)));
}
Out->SetArrayField(Key, MoveTemp(Values));
};
auto WriteFloats = [](const TCHAR* Key, const TArray<float>& Source,
TSharedRef<FJsonObject> Out)
{
TArray<TSharedPtr<FJsonValue>> Values;
Values.Reserve(Source.Num());
for (float Value : Source) Values.Add(MakeShared<FJsonValueNumber>(Value));
Out->SetArrayField(Key, MoveTemp(Values));
};
WriteVectors(TEXT("control_points"), Passage.ControlPoints, Result);
WriteFloats(TEXT("control_radii"), Passage.ControlRadii, Result);
Result->SetObjectField(TEXT("upper_landing"), StandaloneLandingJson(Passage.UpperLanding));
Result->SetObjectField(TEXT("lower_landing"), StandaloneLandingJson(Passage.LowerLanding));
Result->SetBoolField(TEXT("walkable_tunnel_contract"), Passage.bWalkableTunnelContract);
Result->SetBoolField(TEXT("native_floor_enabled"), Passage.bNativeFloorEnabled);
WriteFloats(TEXT("native_floor_profile_z"), Passage.NativeFloorProfileZ, Result);
WriteFloats(TEXT("native_floor_relief_scales"), Passage.NativeFloorReliefScales, Result);
Result->SetNumberField(TEXT("native_floor_relief_strength"), Passage.NativeFloorReliefStrength);
Result->SetNumberField(TEXT("native_floor_relief_frequency"), Passage.NativeFloorReliefFrequency);
Result->SetNumberField(TEXT("native_floor_seed"),
static_cast<int64>(Passage.NativeFloorSeed));
Result->SetNumberField(TEXT("tunnel_max_gradient_degrees"), Passage.TunnelMaxGradientDegrees);
Result->SetNumberField(TEXT("tunnel_vertical_drop_voxels"), Passage.TunnelVerticalDropVoxels);
Result->SetNumberField(TEXT("tunnel_required_horizontal_run_voxels"), Passage.TunnelRequiredHorizontalRunVoxels);
Result->SetNumberField(TEXT("tunnel_horizontal_path_length_voxels"), Passage.TunnelHorizontalPathLengthVoxels);
Result->SetNumberField(TEXT("tunnel_minimum_clear_width_voxels"), Passage.TunnelMinimumClearWidthVoxels);
Result->SetNumberField(TEXT("tunnel_clear_height_voxels"), Passage.TunnelClearHeightVoxels);
Result->SetObjectField(TEXT("bound_center"), StandaloneVectorJson(Passage.BoundCenter));
Result->SetNumberField(TEXT("bound_radius"), Passage.BoundRadius);
Result->SetNumberField(TEXT("bound_radius_sq"), Passage.BoundRadiusSq);
return Result;
}
struct FStandaloneExportSample
{
FVector Position = FVector::ZeroVector;
FString Kind;
int32 StrateIndex = INDEX_NONE;
float Density = 0.0f;
};
class FStandaloneCoreAdapterScheduler final : public VoxelForgeCore::JobScheduler
{
public:
double parallel_for(std::size_t Count, std::size_t BatchSize, std::size_t WorkerLimit,
void* Context, Task Callback, double* WorkerActiveSeconds,
std::size_t WorkerActiveCapacity) const override
{
if (Count == 0) return 0.0;
if (WorkerActiveSeconds != nullptr)
{
std::fill(WorkerActiveSeconds, WorkerActiveSeconds + WorkerActiveCapacity, 0.0);
}
const std::size_t WorkerCount = std::clamp<std::size_t>(WorkerLimit, 1,
std::min<std::size_t>(16, std::max<std::size_t>(1, Count)));
const std::size_t Grain = std::max<std::size_t>(1, BatchSize);
std::atomic<std::size_t> Next{0};
std::vector<double> WorkerSeconds(WorkerCount, 0.0);
std::vector<std::thread> Threads;
Threads.reserve(WorkerCount);
for (std::size_t Worker = 0; Worker < WorkerCount; ++Worker)
{
Threads.emplace_back([&, Worker]
{
const auto Started = std::chrono::steady_clock::now();
for (;;)
{
const std::size_t First = Next.fetch_add(Grain, std::memory_order_relaxed);
if (First >= Count) break;
const std::size_t End = First + std::min(Grain, Count - First);
for (std::size_t Index = First; Index < End; ++Index)
{
Callback(Context, Index, Worker);
}
}
WorkerSeconds[Worker] = std::chrono::duration<double>(
std::chrono::steady_clock::now() - Started).count();
});
}
for (std::thread& Thread : Threads) Thread.join();
double TotalSeconds = 0.0;
for (std::size_t Worker = 0; Worker < WorkerCount; ++Worker)
{
TotalSeconds += WorkerSeconds[Worker];
if (WorkerActiveSeconds != nullptr && Worker < WorkerActiveCapacity)
{
WorkerActiveSeconds[Worker] = WorkerSeconds[Worker];
}
}
return TotalSeconds;
}
};
struct FStandaloneCoreAdapterTileHashes
{
VoxelForgeCore::TileKey Key;
std::array<std::uint64_t, 4> Hashes{};
};
void StandaloneCoreHashWord(std::uint64_t& Hash, std::uint64_t Word, int32 ByteCount)
{
for (int32 Byte = 0; Byte < ByteCount; ++Byte)
{
Hash ^= static_cast<std::uint8_t>(Word >> (Byte * 8));
Hash *= 1099511628211ull;
}
}
std::array<std::uint64_t, 4> StandaloneCoreAggregateHashes(
std::vector<FStandaloneCoreAdapterTileHashes>& Rows)
{
std::sort(Rows.begin(), Rows.end(), [](const auto& A, const auto& B)
{
if (A.Key.lod != B.Key.lod) return A.Key.lod < B.Key.lod;
if (A.Key.coordinate.z != B.Key.coordinate.z)
return A.Key.coordinate.z < B.Key.coordinate.z;
if (A.Key.coordinate.y != B.Key.coordinate.y)
return A.Key.coordinate.y < B.Key.coordinate.y;
return A.Key.coordinate.x < B.Key.coordinate.x;
});
std::array<std::uint64_t, 4> Aggregates{
14695981039346656037ull, 14695981039346656037ull,
14695981039346656037ull, 14695981039346656037ull};
for (const FStandaloneCoreAdapterTileHashes& Row : Rows)
{
for (std::size_t HashIndex = 0; HashIndex < Aggregates.size(); ++HashIndex)
{
std::uint64_t& Hash = Aggregates[HashIndex];
StandaloneCoreHashWord(Hash, static_cast<std::uint32_t>(Row.Key.lod), 4);
StandaloneCoreHashWord(Hash, static_cast<std::uint32_t>(Row.Key.coordinate.x), 4);
StandaloneCoreHashWord(Hash, static_cast<std::uint32_t>(Row.Key.coordinate.y), 4);
StandaloneCoreHashWord(Hash, static_cast<std::uint32_t>(Row.Key.coordinate.z), 4);
StandaloneCoreHashWord(Hash, Row.Hashes[HashIndex], 8);
}
}
return Aggregates;
}
bool AuditCoreClipmapCoverage(
const std::vector<VoxelForgeCore::TileKey>& Tiles,
VoxelForgeCore::Vec3d Focus,
std::int32_t FocusRadiusVoxels,
std::int32_t& OutLocalTileCount,
std::int32_t& OutCheckedFaces,
std::int32_t& OutTransitionFaces,
FString& OutError)
{
struct FBounds
{
std::int64_t Min[3]{};
std::int64_t Max[3]{};
std::int32_t Lod = 0;
};
OutLocalTileCount = 0;
OutCheckedFaces = 0;
OutTransitionFaces = 0;
OutError.Reset();
std::vector<FBounds> Bounds;
Bounds.reserve(Tiles.size());
for (const VoxelForgeCore::TileKey& Key : Tiles)
{
if (Key.lod < 0 || Key.lod > 8)
{
OutError = TEXT("Core clipmap contains an invalid LOD key.");
return false;
}
const std::int64_t Extent = 32ll << Key.lod;
FBounds Box;
Box.Lod = Key.lod;
Box.Min[0] = static_cast<std::int64_t>(Key.coordinate.x) * Extent;
Box.Min[1] = static_cast<std::int64_t>(Key.coordinate.y) * Extent;
Box.Min[2] = static_cast<std::int64_t>(Key.coordinate.z) * Extent;
for (int32 Axis = 0; Axis < 3; ++Axis) Box.Max[Axis] = Box.Min[Axis] + Extent;
Bounds.push_back(Box);
}
// Une clipmap Core ne doit jamais renvoyer deux boites qui se chevauchent en volume.
// A Core clipmap must never return two tile boxes that overlap in volume.
for (std::size_t A = 0; A < Bounds.size(); ++A)
{
for (std::size_t B = A + 1; B < Bounds.size(); ++B)
{
bool bOverlaps = true;
for (int32 Axis = 0; Axis < 3; ++Axis)
{
bOverlaps = bOverlaps
&& FMath::Min(Bounds[A].Max[Axis], Bounds[B].Max[Axis])
> FMath::Max(Bounds[A].Min[Axis], Bounds[B].Min[Axis]);
}
if (bOverlaps)
{
OutError = FString::Printf(
TEXT("Core clipmap tiles overlap: index %llu and %llu."),
static_cast<unsigned long long>(A), static_cast<unsigned long long>(B));
return false;
}
}
}
const double FocusPoint[3] = {Focus.x, Focus.y, Focus.z};
for (std::size_t TileIndex = 0; TileIndex < Bounds.size(); ++TileIndex)
{
const FBounds& Tile = Bounds[TileIndex];
if (Tile.Lod > 2) continue;
bool bNearFocus = true;
for (int32 Axis = 0; Axis < 3; ++Axis)
{
bNearFocus = bNearFocus
&& static_cast<double>(Tile.Max[Axis]) >= FocusPoint[Axis] - FocusRadiusVoxels
&& static_cast<double>(Tile.Min[Axis]) <= FocusPoint[Axis] + FocusRadiusVoxels;
}
if (!bNearFocus) continue;
++OutLocalTileCount;
for (int32 Axis = 0; Axis < 3; ++Axis)
{
const int32 UAxis = (Axis + 1) % 3;
const int32 VAxis = (Axis + 2) % 3;
const std::int64_t FaceArea = (Tile.Max[UAxis] - Tile.Min[UAxis])
* (Tile.Max[VAxis] - Tile.Min[VAxis]);
for (const bool bPositiveFace : {false, true})
{
const std::int64_t FaceCoordinate = bPositiveFace ? Tile.Max[Axis] : Tile.Min[Axis];
std::int64_t CoveredArea = 0;
bool bTouchesLodTransition = false;
for (std::size_t NeighbourIndex = 0; NeighbourIndex < Bounds.size(); ++NeighbourIndex)
{
if (NeighbourIndex == TileIndex) continue;
const FBounds& Neighbour = Bounds[NeighbourIndex];
if (Neighbour.Min[Axis] != FaceCoordinate
&& Neighbour.Max[Axis] != FaceCoordinate)
{
continue;
}
const std::int64_t OverlapU = FMath::Max<std::int64_t>(0,
FMath::Min(Tile.Max[UAxis], Neighbour.Max[UAxis])
- FMath::Max(Tile.Min[UAxis], Neighbour.Min[UAxis]));
const std::int64_t OverlapV = FMath::Max<std::int64_t>(0,
FMath::Min(Tile.Max[VAxis], Neighbour.Max[VAxis])
- FMath::Max(Tile.Min[VAxis], Neighbour.Min[VAxis]));
if (OverlapU == 0 || OverlapV == 0) continue;
CoveredArea += OverlapU * OverlapV;
bTouchesLodTransition = bTouchesLodTransition || Neighbour.Lod != Tile.Lod;
}
++OutCheckedFaces;
if (CoveredArea != FaceArea)
{
OutError = FString::Printf(
TEXT("Core clipmap has a missing or overlapping neighbour at LOD %d, axis %d, face %d (coverage %lld/%lld)."),
Tile.Lod, Axis, bPositiveFace ? 1 : 0,
static_cast<long long>(CoveredArea), static_cast<long long>(FaceArea));
return false;
}
if (bTouchesLodTransition) ++OutTransitionFaces;
}
}
}
if (OutLocalTileCount == 0 || OutTransitionFaces == 0)
{
OutError = TEXT("Core clipmap coverage audit found no local tiles or no local LOD transition faces.");
return false;
}
return true;
}
bool VerifyStandaloneCoreAdapter(const FString& Json, FString& OutError)
{
FTCHARToUTF8 Utf8Json(*Json);
std::string CoreError;
auto OwnerSnapshot = VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
std::string_view(Utf8Json.Get(), static_cast<std::size_t>(Utf8Json.Length())), CoreError);
if (!OwnerSnapshot)
{
OutError = FString::Printf(TEXT("VoxelForgeCore rejected adapter schema-v1 JSON: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
// Reference hashes use the established stream-benchmark view: clip radius 3 and max LOD 8.
// Les hachages de référence utilisent le profil de benchmark : rayon 3 et niveau LOD maximal 8.
// Keep asset-derived settings in owner_world.json; apply benchmark controls only to the copy.
// Garder les valeurs des assets dans owner_world.json; appliquer les réglages au seul duplicata.
const std::int32_t OwnerClipRadius = OwnerSnapshot->configuration().clipRadius;
const std::int32_t OwnerMaxLod = OwnerSnapshot->configuration().maxLod;
FString CoreValidationJson = Json;
const FString AuthoredClipField = FString::Printf(TEXT("\"clip_radius\":%d"), OwnerClipRadius);
const FString AuthoredAssetClipField = FString::Printf(TEXT("\"ClipRadius\":%d"), OwnerClipRadius);
const FString AuthoredMaxLodField = FString::Printf(TEXT("\"max_clip_level\":%d"), OwnerMaxLod);
const FString AuthoredAssetMaxLodField = FString::Printf(TEXT("\"MaxClipLevel\":%d"), OwnerMaxLod);
CoreValidationJson.ReplaceInline(*AuthoredClipField, TEXT("\"clip_radius\":3"), ESearchCase::CaseSensitive);
CoreValidationJson.ReplaceInline(*AuthoredAssetClipField, TEXT("\"ClipRadius\":3"), ESearchCase::CaseSensitive);
CoreValidationJson.ReplaceInline(*AuthoredMaxLodField, TEXT("\"max_clip_level\":8"), ESearchCase::CaseSensitive);
CoreValidationJson.ReplaceInline(*AuthoredAssetMaxLodField, TEXT("\"MaxClipLevel\":8"), ESearchCase::CaseSensitive);
if (!CoreValidationJson.Contains(TEXT("\"clip_radius\":3"), ESearchCase::CaseSensitive)
|| !CoreValidationJson.Contains(TEXT("\"ClipRadius\":3"), ESearchCase::CaseSensitive)
|| !CoreValidationJson.Contains(TEXT("\"max_clip_level\":8"), ESearchCase::CaseSensitive)
|| !CoreValidationJson.Contains(TEXT("\"MaxClipLevel\":8"), ESearchCase::CaseSensitive))
{
OutError = TEXT("Core adapter validation could not apply the reference clipmap overrides.");
return false;
}
for (const std::size_t WorkerLimit : {1u, 8u, 16u})
{
FTCHARToUTF8 Utf8ValidationJson(*CoreValidationJson);
auto Snapshot = VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
std::string_view(Utf8ValidationJson.Get(),
static_cast<std::size_t>(Utf8ValidationJson.Length())), CoreError);
if (!Snapshot)
{
OutError = FString::Printf(TEXT("VoxelForgeCore rejected reference-validation JSON: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
VoxelForgeCore::World CoreWorld(std::move(*Snapshot));
const VoxelForgeCore::WorldConfiguration& Configuration = CoreWorld.configuration();
const VoxelForgeCore::Vec3d Camera = Configuration.viewerStartFound
? Configuration.viewerStartVoxels : VoxelForgeCore::Vec3d{};
const std::vector<VoxelForgeCore::TileKey> Desired = CoreWorld.desiredTiles(Camera);
if (Desired.empty())
{
OutError = TEXT("VoxelForgeCore adapter validation produced an empty initial clipmap.");
return false;
}
const std::int32_t IntMax = std::numeric_limits<std::int32_t>::max();
const std::int32_t IntMin = std::numeric_limits<std::int32_t>::min();
VoxelForgeCore::Vec3i FineMin{IntMax, IntMax, IntMax};
VoxelForgeCore::Vec3i FineMax{IntMin, IntMin, IntMin};
VoxelForgeCore::Vec3i ClipMin{IntMax, IntMax, IntMax};
VoxelForgeCore::Vec3i ClipMax{IntMin, IntMin, IntMin};
auto IncludeExtent = [](std::int32_t& Min, std::int32_t& Max,
std::int32_t Origin, std::int32_t Extent)
{
Min = std::min(Min, Origin);
Max = std::max(Max, Origin + Extent);
};
for (const VoxelForgeCore::TileKey& Key : Desired)
{
const std::int32_t Extent = 32 << Key.lod;
const std::int32_t OriginX = Key.coordinate.x * Extent;
const std::int32_t OriginY = Key.coordinate.y * Extent;
const std::int32_t OriginZ = Key.coordinate.z * Extent;
IncludeExtent(ClipMin.x, ClipMax.x, OriginX, Extent);
IncludeExtent(ClipMin.y, ClipMax.y, OriginY, Extent);
IncludeExtent(ClipMin.z, ClipMax.z, OriginZ, Extent);
if (Key.lod < Configuration.fullResolutionLodCount)
{
IncludeExtent(FineMin.x, FineMax.x, OriginX, Extent);
IncludeExtent(FineMin.y, FineMax.y, OriginY, Extent);
IncludeExtent(FineMin.z, FineMax.z, OriginZ, Extent);
}
}
if (FineMin.x == IntMax || ClipMin.x == IntMax)
{
OutError = TEXT("VoxelForgeCore adapter validation could not derive clipmap bounds.");
return false;
}
VoxelForgeCore::ShapeBuildRequest Request;
Request.minVoxel = FineMin;
Request.maxVoxel = FineMax;
Request.clipMinVoxel = ClipMin;
Request.clipMaxVoxel = ClipMax;
Request.workerLimit = WorkerLimit;
Request.detail = VoxelForgeCore::DetailVariant::coherent;
Request.includeFarWorld = true;
FStandaloneCoreAdapterScheduler Scheduler;
if (!CoreWorld.compile(Request, Scheduler, CoreError))
{
OutError = FString::Printf(TEXT("VoxelForgeCore adapter compile failed at workers=%llu: %s"),
static_cast<unsigned long long>(WorkerLimit), UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
VoxelForgeCore::TileGenerationOptions TileOptions;
TileOptions.hashDensity = true;
std::vector<FStandaloneCoreAdapterTileHashes> Rows;
Rows.reserve(Desired.size());
for (const VoxelForgeCore::TileKey& Key : Desired)
{
auto Tile = CoreWorld.generateTile(Key, TileOptions, CoreError);
if (!Tile)
{
OutError = FString::Printf(TEXT("VoxelForgeCore adapter tile generation failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
if (!Tile->hasDensityHash || !Tile->hasMeshHash
|| !Tile->hasPayloadHash || !Tile->hasTopologyHash)
{
OutError = TEXT("VoxelForgeCore adapter returned a tile without all four hashes.");
return false;
}
Rows.push_back({Key, {Tile->densityHash, Tile->meshHash,
Tile->payloadHash, Tile->topologyHash}});
}
const std::array<std::uint64_t, 4> Actual = StandaloneCoreAggregateHashes(Rows);
const std::array<std::uint64_t, 4> Expected{
0xde345030348f2c5dull, 0xcbf0888004618b87ull,
0x7a11e28e052d69a8ull, 0x52b88607e8dbcbfdull};
const VoxelForgeCore::ShapeBuildStats Stats = CoreWorld.buildStats();
constexpr std::uint64_t ExpectedWalkSamples = 21425;
const bool bPassageWalkabilityPassed = Stats.walkSamples == ExpectedWalkSamples
&& Stats.walkFailures == 0;
if (Rows.size() != 2578 || Actual != Expected || !bPassageWalkabilityPassed)
{
OutError = FString::Printf(
TEXT("Core adapter parity failed at workers=%llu: tiles=%llu density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx walk=%llu/%llu expected_walk=%llu"),
static_cast<unsigned long long>(WorkerLimit),
static_cast<unsigned long long>(Rows.size()),
static_cast<unsigned long long>(Actual[0]),
static_cast<unsigned long long>(Actual[1]),
static_cast<unsigned long long>(Actual[2]),
static_cast<unsigned long long>(Actual[3]),
static_cast<unsigned long long>(Stats.walkSamples - Stats.walkFailures),
static_cast<unsigned long long>(Stats.walkSamples),
static_cast<unsigned long long>(ExpectedWalkSamples));
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreAdapter] pass schema=2 workers=%llu asset_clip_radius=%d asset_max_level=%d reference_clip_radius=3 reference_max_level=8 tiles=%llu density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx walk=%llu/%llu"),
static_cast<unsigned long long>(WorkerLimit),
OwnerClipRadius,
OwnerMaxLod,
static_cast<unsigned long long>(Rows.size()),
static_cast<unsigned long long>(Actual[0]),
static_cast<unsigned long long>(Actual[1]),
static_cast<unsigned long long>(Actual[2]),
static_cast<unsigned long long>(Actual[3]),
static_cast<unsigned long long>(Stats.walkSamples - Stats.walkFailures),
static_cast<unsigned long long>(ExpectedWalkSamples));
}
return true;
}
bool RunStandaloneWorldExport(const FExploreArguments& Arguments, FString& OutError, FString* OutJson)
{
UVoxelSettings* AuthoredSettings = LoadObject<UVoxelSettings>(
nullptr, *Arguments.SettingsReference);
if (!AuthoredSettings)
{
OutError = FString::Printf(TEXT("Could not load owner settings asset '%s'."),
*Arguments.SettingsReference);
return false;
}
TStrongObjectPtr<UVoxelSettings> OwnerPlanSettings;
TArray<TStrongObjectPtr<UVoxelStrateDefinition>> OwnerPlanDefinitions;
UVoxelSettings* ExportSettings = AuthoredSettings;
if (Arguments.bOwnerFourStratePlan)
{
OwnerPlanSettings = TStrongObjectPtr<UVoxelSettings>(
DuplicateObject<UVoxelSettings>(AuthoredSettings, GetTransientPackage()));
if (!OwnerPlanSettings.IsValid())
{
OutError = TEXT("Could not make a transient copy of owner settings for the four-strate plan.");
return false;
}
ExportSettings = OwnerPlanSettings.Get();
if (!ApplyOwnerFourStratePlan(ExportSettings, OwnerPlanDefinitions, false, OutError))
{
return false;
}
}
UVoxelStrateManager* Manager = NewObject<UVoxelStrateManager>(
GetTransientPackage(), NAME_None, RF_Transient);
int32 Seed = AuthoredSettings->Seed;
float OriginSpineRadius = AuthoredSettings->OriginSpineRadius;
float WorldRadiusVoxels = AuthoredSettings->WorldRadiusVoxels;
if (const UVoxelSeasonAsset* Season = AuthoredSettings->Season.LoadSynchronous())
{
Seed = Season->GetSeasonSeed();
OriginSpineRadius = Season->GetOriginSpineRadius();
WorldRadiusVoxels = Season->GetWorldRadiusVoxels();
}
if (!Manager->Initialize(ExportSettings, Seed))
{
OutError = TEXT("Owner settings did not produce a valid resolved strate layout.");
return false;
}
Seed = Manager->GetWorldSeed();
UVoxelGenerator* Generator = NewObject<UVoxelGenerator>(
GetTransientPackage(), NAME_None, RF_Transient);
Generator->InitializeSettings(ExportSettings);
Generator->SetStrateManager(Manager);
TArray<FStandaloneExportSample> Samples;
auto AddSample = [&Samples](const FVector& Position, const TCHAR* Kind, int32 StrateIndex)
{
if (!VoxelMath::IsFinite(Position.X) || !VoxelMath::IsFinite(Position.Y)
|| !VoxelMath::IsFinite(Position.Z)) return;
FStandaloneExportSample Sample;
Sample.Position = Position;
Sample.Kind = Kind;
Sample.StrateIndex = StrateIndex;
Samples.Add(MoveTemp(Sample));
};
const int32 XYAnchors[] = { 0, -1, 1, -128, 128, -512, 512, -2048, 2048, -8192, 8192 };
FRandomStream SampleRandom(Seed ^ 0x13579BDF);
FVector Camera = FVector::ZeroVector;
bool bCameraFound = false;
for (const FStrateSlot& Slot : Manager->GetLayout())
{
const float TopZ = static_cast<float>(Slot.TopChunkZ + 1) * CHUNK_SIZE;
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
const float MidZ = (TopZ + BottomZ) * 0.5f;
const int32 MidChunkZ = FMath::FloorToInt(MidZ / CHUNK_SIZE);
for (int32 ZOffset : { -2, -1, 0, 1, 2 })
{
for (int32 Y : XYAnchors)
for (int32 X : XYAnchors)
{
AddSample(FVector(X, Y, TopZ + ZOffset), TEXT("strate_boundary"), Slot.StrateIndex);
AddSample(FVector(X, Y, BottomZ + ZOffset), TEXT("strate_boundary"), Slot.StrateIndex);
}
}
for (int32 Index = 0; Index < 384; ++Index)
{
const float X = static_cast<float>(SampleRandom.RandRange(-4096, 4096));
const float Y = static_cast<float>(SampleRandom.RandRange(-4096, 4096));
const float Z = BottomZ + SampleRandom.RandRange(0, FMath::Max(1, FMath::FloorToInt(TopZ - BottomZ)));
AddSample(FVector(X, Y, Z), TEXT("varied_world"), Slot.StrateIndex);
}
if (Slot.Definition)
{
const FStrateGenerationParams Params = Manager->GetGenerationParams(
FIntVector(0, 0, MidChunkZ));
FChunkSDFCache Cache;
VoxelCaveMorphology::BuildChunkCache(
Cache, -384.0f, -384.0f, 384.0f, 384.0f, Params,
static_cast<uint32>(Seed), Slot.StrateIndex,
&Slot.Definition->TerrainOperations);
for (const FCachedRoom& Room : Cache.Rooms)
{
const FVector Candidate = Room.Center;
const float Density = Generator->GetDensityAt(
Candidate.X, Candidate.Y, Candidate.Z);
if (Density >= 0.0f)
{
AddSample(Candidate, TEXT("room_interior"), Slot.StrateIndex);
if (Slot.StrateIndex == 0 && !bCameraFound
&& Generator->GetDensityAt(Candidate.X, Candidate.Y, Candidate.Z + 8.0f) >= 0.0f
&& Generator->GetDensityAt(Candidate.X, Candidate.Y, Candidate.Z + 16.0f) >= 0.0f)
{
Camera = Candidate;
bCameraFound = true;
}
}
}
for (const FCachedTunnel& Tunnel : Cache.Tunnels)
{
for (int32 PointIndex = 0; PointIndex < Tunnel.WorldControlPoints.Num(); ++PointIndex)
{
const FVector Candidate = Tunnel.WorldControlPoints[PointIndex];
const float Density = Generator->GetDensityAt(
Candidate.X, Candidate.Y, Candidate.Z);
if (Density >= 0.0f)
{
AddSample(Candidate, TEXT("tunnel_interior"), Slot.StrateIndex);
}
}
}
}
if (Slot.StrateIndex == 0)
{
// Prefer an open, body-height column in the first authored strate. This seed pose is
// exported to the standalone viewer; it uses only the owner's live base field.
for (int32 Radius = 0; Radius <= 128 && !bCameraFound; Radius += 8)
for (int32 Y = -Radius; Y <= Radius && !bCameraFound; Y += 8)
for (int32 X = -Radius; X <= Radius && !bCameraFound; X += 8)
{
const float Z = MidZ;
if (Generator->GetDensityAt(static_cast<float>(X), static_cast<float>(Y), Z) >= 0.0f
&& Generator->GetDensityAt(static_cast<float>(X), static_cast<float>(Y), Z + 8.0f) >= 0.0f
&& Generator->GetDensityAt(static_cast<float>(X), static_cast<float>(Y), Z + 16.0f) >= 0.0f)
{
Camera = FVector(X, Y, Z + 2.0f);
bCameraFound = true;
}
}
}
}
for (const FVoxelPassage& Passage : Manager->GetPassages())
{
for (const FVector& Point : Passage.ControlPoints)
{
AddSample(Point, TEXT("inter_strate_passage"), Passage.UpperStrateIndex);
}
AddSample(Passage.UpperLanding.StandingPoint, TEXT("passage_landing"), Passage.UpperStrateIndex);
AddSample(Passage.LowerLanding.StandingPoint, TEXT("passage_landing"), Passage.LowerStrateIndex);
}
for (FStandaloneExportSample& Sample : Samples)
{
Sample.Density = Generator->GetDensityAt(
static_cast<float>(Sample.Position.X), static_cast<float>(Sample.Position.Y),
static_cast<float>(Sample.Position.Z));
}
if (!bCameraFound)
{
OutError = TEXT("Could not locate open camera space in the owner's first strate.");
return false;
}
FString Json;
TSharedRef<FExploreJsonWriter> Writer =
TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create(&Json);
Writer->WriteObjectStart();
Writer->WriteValue(TEXT("schema_version"), 1);
Writer->WriteValue(TEXT("source"), TEXT("VoxelForgeExplore standalone base-world export"));
Writer->WriteValue(TEXT("voxel_size_cm"), StandaloneCoreVoxelSizeCm);
Writer->WriteValue(TEXT("seed"), Seed);
Writer->WriteValue(TEXT("open_surface_entry"), ExportSettings->bOpenSurfaceEntry);
Writer->WriteValue(TEXT("origin_spine_radius"), OriginSpineRadius);
Writer->WriteValue(TEXT("world_radius_voxels"), WorldRadiusVoxels);
Writer->WriteValue(TEXT("edge_seal_thickness"), ExportSettings->EdgeSealThickness);
Writer->WriteValue(TEXT("total_strates"), Manager->GetNumStrates());
Writer->WriteValue(TEXT("inter_strate_gap_chunks"), ExportSettings->InterStrateGapChunks);
Writer->WriteValue(TEXT("clip_radius"), ExportSettings->ClipRadius);
Writer->WriteValue(TEXT("max_clip_level"), ExportSettings->MaxClipLevel);
Writer->WriteValue(TEXT("full_res_clip_levels"), ExportSettings->FullResClipLevels);
Writer->WriteValue(TEXT("coarse_tile_cells"), ExportSettings->CoarseTileCells);
Writer->WriteValue(TEXT("render_distance_chunks"), ExportSettings->RenderDistanceChunks);
Writer->WriteValue(TEXT("generate_skirts"), ExportSettings->bGenerateSkirts);
Writer->WriteValue(TEXT("skirt_cells"), ExportSettings->SkirtCells);
Writer->WriteValue(TEXT("lod_octave_drop"), ExportSettings->LODOctaveDrop);
Writer->WriteValue(TEXT("strate_content_cut_min_level"), ExportSettings->GetEffectiveStrateContentCutMinLevel());
TSet<const UObject*> ActiveObjects;
StandaloneWriteJsonValue(*Writer, TEXT("settings_asset"),
StandaloneSerializeStruct(ExportSettings->GetClass(), ExportSettings,
ActiveObjects, ExportSettings));
Writer->WriteArrayStart(TEXT("strates"));
for (const FStrateSlot& Slot : Manager->GetLayout())
{
Writer->WriteObjectStart();
Writer->WriteValue(TEXT("index"), Slot.StrateIndex);
Writer->WriteValue(TEXT("top_chunk_z"), Slot.TopChunkZ);
Writer->WriteValue(TEXT("bottom_chunk_z"), Slot.BottomChunkZ);
Writer->WriteValue(TEXT("height_chunks"), Slot.HeightInChunks);
StandaloneWriteJsonValue(*Writer, TEXT("asset"),
StandaloneSerializeStruct(Slot.Definition->GetClass(), Slot.Definition,
ActiveObjects, Slot.Definition));
StandaloneWriteJsonValue(*Writer, TEXT("generation_params"),
StandaloneSerializeStruct(FStrateGenerationParams::StaticStruct(),
&Slot.Definition->GenerationParams, ActiveObjects));
StandaloneWriteJsonValue(*Writer, TEXT("disturbances"),
StandaloneSerializeStruct(FStrateDisturbanceParams::StaticStruct(),
&Slot.Definition->Disturbances, ActiveObjects));
StandaloneWriteJsonValue(*Writer, TEXT("passage_config"),
StandaloneSerializeStruct(FStratePassageConfig::StaticStruct(),
&Slot.Definition->PassageConfig, ActiveObjects));
Writer->WriteArrayStart(TEXT("terrain_operation_assets"));
for (const FStrateTerrainOpEntry& Entry : Slot.Definition->TerrainOperations)
{
Writer->WriteObjectStart();
Writer->WriteValue(TEXT("weight"), Entry.Weight);
Writer->WriteValue(TEXT("probability"), Entry.Probability);
Writer->WriteValue(TEXT("asset_path"), Entry.Operation.ToString());
if (const UVoxelTerrainOpDefinition* Op = Entry.Operation.LoadSynchronous())
{
StandaloneWriteJsonValue(*Writer, TEXT("asset"),
StandaloneSerializeStruct(Op->GetClass(), Op, ActiveObjects, Op));
}
Writer->WriteObjectEnd();
}
Writer->WriteArrayEnd();
Writer->WriteObjectEnd();
}
Writer->WriteArrayEnd();
Writer->WriteArrayStart(TEXT("passages"));
for (const FVoxelPassage& Passage : Manager->GetPassages())
{
StandaloneWriteJsonValue(*Writer, nullptr,
MakeShared<FJsonValueObject>(StandalonePassageJson(Passage)));
}
Writer->WriteArrayEnd();
Writer->WriteArrayStart(TEXT("density_samples"));
for (const FStandaloneExportSample& Sample : Samples)
{
Writer->WriteObjectStart();
Writer->WriteValue(TEXT("x"), static_cast<double>(Sample.Position.X));
Writer->WriteValue(TEXT("y"), static_cast<double>(Sample.Position.Y));
Writer->WriteValue(TEXT("z"), static_cast<double>(Sample.Position.Z));
Writer->WriteValue(TEXT("density"), static_cast<double>(Sample.Density));
Writer->WriteValue(TEXT("kind"), Sample.Kind);
Writer->WriteValue(TEXT("strate_index"), Sample.StrateIndex);
Writer->WriteObjectEnd();
}
Writer->WriteArrayEnd();
Writer->WriteObjectStart(TEXT("viewer_start"));
Writer->WriteValue(TEXT("found_open_space"), bCameraFound);
Writer->WriteValue(TEXT("x"), static_cast<double>(Camera.X));
Writer->WriteValue(TEXT("y"), static_cast<double>(Camera.Y));
Writer->WriteValue(TEXT("z"), static_cast<double>(Camera.Z));
Writer->WriteObjectEnd();
Writer->WriteObjectEnd();
Writer->Close();
// Export the exact Core manifest and resolved layout that the Unreal adapter just
// generated from these live settings and slots. The standalone research tools consume
// these snapshots instead of maintaining a second copy of the planning rules.
FString RuntimeSnapshotJson;
if (!VoxelForgeCoreAdapter::SerializeResolvedWorld(
ExportSettings, Manager, 3, 8, StandaloneCoreVoxelSizeCm,
RuntimeSnapshotJson, OutError))
{
return false;
}
FTCHARToUTF8 RuntimeSnapshotUtf8(*RuntimeSnapshotJson);
std::string CoreSnapshotError;
const std::optional<VoxelForgeCore::ResolvedWorldSnapshot> CoreSnapshot =
VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
std::string_view(RuntimeSnapshotUtf8.Get(),
static_cast<std::size_t>(RuntimeSnapshotUtf8.Length())), CoreSnapshotError);
if (!CoreSnapshot || CoreSnapshot->worldAssembly() == nullptr
|| CoreSnapshot->resolvedLayout() == nullptr)
{
OutError = FString::Printf(
TEXT("Core adapter export did not produce a complete manifest and resolved layout: %s"),
UTF8_TO_TCHAR(CoreSnapshotError.c_str()));
return false;
}
const std::string CoreAssemblyText = CoreSnapshot->worldAssembly()->ToJson();
const std::string CoreLayoutText = CoreSnapshot->resolvedLayout()->ToJson();
if (CoreAssemblyText.empty() || CoreLayoutText.empty())
{
OutError = TEXT("Core adapter export produced an empty canonical manifest or layout.");
return false;
}
const FString CanonicalAssemblyJson = UTF8_TO_TCHAR(CoreAssemblyText.c_str());
const FString CanonicalLayoutJson = UTF8_TO_TCHAR(CoreLayoutText.c_str());
TSharedPtr<FJsonObject> ExportRoot;
const TSharedRef<TJsonReader<>> ExportReader = TJsonReaderFactory<>::Create(Json);
if (!FJsonSerializer::Deserialize(ExportReader, ExportRoot) || !ExportRoot.IsValid())
{
OutError = TEXT("Could not reopen the owner export to attach the Core plan snapshots.");
return false;
}
TSharedPtr<FJsonObject> AssemblyObject;
const TSharedRef<TJsonReader<>> AssemblyReader =
TJsonReaderFactory<>::Create(CanonicalAssemblyJson);
if (!FJsonSerializer::Deserialize(AssemblyReader, AssemblyObject) || !AssemblyObject.IsValid())
{
OutError = TEXT("Could not parse the Core adapter's canonical manifest for export.");
return false;
}
ExportRoot->SetObjectField(TEXT("world_assembly"), AssemblyObject.ToSharedRef());
ExportRoot->SetStringField(TEXT("core_world_assembly_json"), CanonicalAssemblyJson);
ExportRoot->SetStringField(TEXT("resolved_layout_json"), CanonicalLayoutJson);
FString ExportedJson;
const TSharedRef<TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>> ExportWriter =
TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create(&ExportedJson);
if (!FJsonSerializer::Serialize(ExportRoot.ToSharedRef(), ExportWriter))
{
OutError = TEXT("Could not serialize the owner export with its canonical Core plan.");
return false;
}
Json = MoveTemp(ExportedJson);
const FString Path = FPaths::Combine(Arguments.OutDirectory, TEXT("owner_world.json"));
if (!FFileHelper::SaveStringToFile(
Json, *Path, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
OutError = FString::Printf(TEXT("Could not write standalone owner data to '%s'."), *Path);
return false;
}
if (OutJson) *OutJson = Json;
if (!Arguments.bSkipStandaloneCoreAdapterParity && !VerifyStandaloneCoreAdapter(Json, OutError))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeCoreAdapter] %s"), *OutError);
return false;
}
if (Arguments.bSkipStandaloneCoreAdapterParity)
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreAdapter] skipped legacy W1/W8/W16 fixture parity for this export-only update."));
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeStandaloneExport] seed=%d voxel_size_cm=%.3f strates=%d passages=%d samples=%d camera_found=%d file=%s"),
Seed, StandaloneCoreVoxelSizeCm, Manager->GetNumStrates(), Manager->GetPassages().Num(),
Samples.Num(), bCameraFound ? 1 : 0, *Path);
return true;
}
struct FHybridFixtureContactProbe
{
FString Name;
FVector Start = FVector::ZeroVector;
FVector End = FVector::ZeroVector;
FVector Expected = FVector::ZeroVector;
};
bool GetHybridFixtureRenderAndCollisionBounds(URealtimeMeshComponent* Component,
FBox& OutRenderBounds, FBox& OutCollisionBounds)
{
if (!IsValid(Component))
{
return false;
}
const URealtimeMesh* RealtimeMesh = Component->GetRealtimeMesh();
const UBodySetup* BodySetup = Component->GetBodySetup();
if (RealtimeMesh == nullptr || BodySetup == nullptr)
{
return false;
}
const FBoxSphereBounds& RenderBounds = RealtimeMesh->GetLocalBounds();
OutRenderBounds = FBox(RenderBounds.Origin - RenderBounds.BoxExtent,
RenderBounds.Origin + RenderBounds.BoxExtent);
OutCollisionBounds = FBox(EForceInit::ForceInit);
for (const Chaos::FTriangleMeshImplicitObjectPtr& BodyMesh : BodySetup->TriMeshGeometries)
{
if (!BodyMesh.IsValid()) continue;
const Chaos::FAABB3& BodyBounds = BodyMesh->BoundingBox();
OutCollisionBounds += FVector(BodyBounds.Min()[0], BodyBounds.Min()[1], BodyBounds.Min()[2]);
OutCollisionBounds += FVector(BodyBounds.Max()[0], BodyBounds.Max()[1], BodyBounds.Max()[2]);
}
return OutRenderBounds.IsValid && OutCollisionBounds.IsValid;
}
bool AddHybridFixturePlacement(UVoxelSettings* Settings, const FString& OutputDirectory,
std::vector<FHybridFixtureContactProbe>& OutContacts, FString& OutManifestHash,
FString& OutError)
{
FString SourceJson;
FString SourceHash;
FString SettingsError;
if (Settings == nullptr || !Settings->GetWorldAssemblyManifestJson(
SourceJson, SourceHash, SettingsError))
{
OutError = FString::Printf(TEXT("Hybrid fixture placement needs a valid SurfaceWorld manifest: %s."),
*SettingsError);
return false;
}
FTCHARToUTF8 SourceUtf8(*SourceJson);
std::string CoreError;
std::optional<VoxelForgeCore::WorldAssemblyManifest> Manifest =
VoxelForgeCore::WorldAssemblyManifest::FromJson(
std::string_view(SourceUtf8.Get(), static_cast<std::size_t>(SourceUtf8.Length())), CoreError);
if (!Manifest)
{
OutError = FString::Printf(TEXT("Core could not parse the hybrid fixture manifest: %s."),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
VoxelForgeCore::WorldAssemblyStratumPlanV1* SurfacePlan = nullptr;
for (VoxelForgeCore::WorldAssemblyStratumPlanV1& Plan : Manifest->plan.strates)
{
if (Plan.strateIndex == 1)
{
SurfacePlan = &Plan;
break;
}
}
if (SurfacePlan == nullptr)
{
OutError = TEXT("Hybrid fixture manifest has no SurfaceWorld strate 1.");
return false;
}
const VoxelForgeCore::Heightfield2D BaseField(Manifest->recipe, *SurfacePlan);
if (!BaseField.isValid())
{
OutError = TEXT("Hybrid fixture manifest has an invalid SurfaceWorld heightfield.");
return false;
}
constexpr int64 Scale = VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
struct FPlacementGroup
{
const TCHAR* Name;
int32 GroundX;
int32 GroundY;
int32 HeightVoxels;
};
const FPlacementGroup Groups[] = {
{TEXT("corner"), 41, 166, 10},
{TEXT("doorway"), 54, 166, 12},
{TEXT("riser"), 46, 184, 8},
{TEXT("ledge"), 56, 184, 12},
};
int64 GroupGroundSubvoxels[UE_ARRAY_COUNT(Groups)]{};
for (int32 Index = 0; Index < UE_ARRAY_COUNT(Groups); ++Index)
{
const VoxelForgeCore::HeightfieldPointSample Ground = BaseField.point(
static_cast<int64>(Groups[Index].GroundX) * Scale,
static_cast<int64>(Groups[Index].GroundY) * Scale, 0);
GroupGroundSubvoxels[Index] = Ground.groundHeightSubvoxels;
}
auto AddBox = [&](uint64 StableId, int32 GroupIndex, int32 MinX, int32 MaxX,
int32 MinY, int32 MaxY, int32 BottomOffset, int32 TopOffset)
{
VoxelForgeCore::WorldAssemblyFeatureV1 Feature;
Feature.stableId = StableId;
Feature.type = 2;
Feature.minimumSubvoxels = {
static_cast<int64>(MinX) * Scale,
static_cast<int64>(MinY) * Scale,
GroupGroundSubvoxels[GroupIndex] + static_cast<int64>(BottomOffset) * Scale};
Feature.maximumSubvoxels = {
static_cast<int64>(MaxX) * Scale,
static_cast<int64>(MaxY) * Scale,
GroupGroundSubvoxels[GroupIndex] + static_cast<int64>(TopOffset) * Scale};
SurfacePlan->features.push_back(Feature);
};
// Small, separated hard-box placements stay inside the (32..64, 160..192) LOD0 tile.
// De petits volumes durs separent restent dans la tuile LOD0 (32..64, 160..192).
AddBox(0x4d32460000000001ull, 0, 34, 43, 164, 168, 0, 10);
AddBox(0x4d32460000000002ull, 0, 39, 43, 168, 173, 0, 10);
AddBox(0x4d32460000000003ull, 1, 47, 51, 164, 168, 0, 12);
AddBox(0x4d32460000000004ull, 1, 57, 61, 164, 168, 0, 12);
AddBox(0x4d32460000000005ull, 1, 51, 57, 164, 168, 8, 12);
AddBox(0x4d32460000000006ull, 2, 34, 44, 180, 189, 0, 4);
AddBox(0x4d32460000000007ull, 2, 44, 48, 180, 189, 4, 8);
AddBox(0x4d32460000000008ull, 3, 50, 54, 180, 189, 0, 12);
AddBox(0x4d32460000000009ull, 3, 54, 63, 180, 189, 8, 10);
if (!Manifest->finalize(CoreError))
{
OutError = FString::Printf(TEXT("Hybrid fixture manifest failed validation: %s."),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
const std::string FixtureJsonUtf8 = Manifest->ToJson();
const FString FixtureJson = UTF8_TO_TCHAR(FixtureJsonUtf8.c_str());
FString ImportReport;
if (!Settings->SetWorldAssemblyManifestJson(FixtureJson, ImportReport))
{
OutError = FString::Printf(TEXT("Could not install the in-memory hybrid fixture manifest: %s."),
*ImportReport);
return false;
}
OutManifestHash = FString::Printf(TEXT("%016llx"),
static_cast<unsigned long long>(Manifest->contentHash));
const FString OutputPath = FPaths::Combine(OutputDirectory,
TEXT("world_assembly_hybrid_fixtures.json"));
if (!FFileHelper::SaveStringToFile(
FixtureJson, *OutputPath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
OutError = FString::Printf(TEXT("Could not save the generated BuildHost fixture plan '%s'."),
*OutputPath);
return false;
}
const auto AddTrace = [&](const TCHAR* Name, double X, double Y, double ContactZ,
int32 Axis, bool bTracePositive)
{
FHybridFixtureContactProbe Probe;
Probe.Name = Name;
Probe.Expected = FVector(X * 25.0, Y * 25.0, ContactZ * 25.0);
Probe.Start = Probe.Expected;
Probe.End = Probe.Expected;
if (Axis == 2)
{
Probe.Start.Z += bTracePositive ? 100.0 : -100.0;
Probe.End.Z -= bTracePositive ? 100.0 : -100.0;
}
else
{
const double Sign = bTracePositive ? 1.0 : -1.0;
Probe.Start[Axis] += Sign * 100.0;
Probe.End[Axis] -= Sign * 100.0;
}
OutContacts.push_back(Probe);
};
const double CornerGround = static_cast<double>(GroupGroundSubvoxels[0]) / Scale;
const double DoorGround = static_cast<double>(GroupGroundSubvoxels[1]) / Scale;
const double RiserGround = static_cast<double>(GroupGroundSubvoxels[2]) / Scale;
const double LedgeGround = static_cast<double>(GroupGroundSubvoxels[3]) / Scale;
AddTrace(TEXT("corner_top"), 41.0, 166.0, CornerGround + 10.0, 2, true);
AddTrace(TEXT("doorway_lintel"), 54.0, 166.0, DoorGround + 12.0, 2, true);
AddTrace(TEXT("riser_face"), 54.0, 184.0, RiserGround + 6.0, 0, true);
AddTrace(TEXT("ledge_top"), 58.0, 184.0, LedgeGround + 10.0, 2, true);
AddTrace(TEXT("ledge_underside"), 58.0, 184.0, LedgeGround + 8.0, 2, false);
return true;
}
struct FHybridAnalyticBox
{
uint64 StableId = 0;
FVector Minimum = FVector::ZeroVector;
FVector Maximum = FVector::ZeroVector;
};
struct FHybridAnalyticFixture
{
const TCHAR* Name = TEXT("fixture");
std::vector<FHybridAnalyticBox> Boxes;
bool bCaveChamber = false;
bool bSurfaceFloor = false;
};
double HybridFixtureBoxSdf(const FVector& Point, const FVector& Minimum, const FVector& Maximum)
{
const FVector Center = (Minimum + Maximum) * 0.5;
const FVector Half = (Maximum - Minimum) * 0.5;
const FVector Delta = Point - Center;
const FVector Q(FMath::Abs(Delta.X) - Half.X,
FMath::Abs(Delta.Y) - Half.Y,
FMath::Abs(Delta.Z) - Half.Z);
const FVector Outside(FMath::Max(Q.X, 0.0), FMath::Max(Q.Y, 0.0), FMath::Max(Q.Z, 0.0));
return Outside.Size() + FMath::Min(FMath::Max(Q.X, FMath::Max(Q.Y, Q.Z)), 0.0);
}
FHybridAnalyticFixture MakeHybridAnalyticFixture(const TCHAR* Name, const FVector& Phase)
{
FHybridAnalyticFixture Fixture;
Fixture.Name = Name;
auto AddBox = [&](uint64 Id, FVector Minimum, FVector Maximum)
{
Fixture.Boxes.push_back({Id, Minimum, Maximum});
};
if (FCString::Strcmp(Name, TEXT("corner90")) == 0)
{
AddBox(0x1001, FVector(6, 6, 6), FVector(23, 10, 26));
AddBox(0x1002, FVector(6, 10, 6), FVector(10, 23, 26));
}
else if (FCString::Strcmp(Name, TEXT("doorway")) == 0)
{
AddBox(0x2001, FVector(6, 8, 6), FVector(10, 12, 26));
AddBox(0x2002, FVector(22, 8, 6), FVector(26, 12, 26));
AddBox(0x2003, FVector(10, 8, 22), FVector(22, 12, 26));
}
else if (FCString::Strcmp(Name, TEXT("riser1m")) == 0)
{
AddBox(0x3001, FVector(6, 8, 6), FVector(18, 24, 10));
AddBox(0x3002, FVector(18, 8, 10), FVector(26, 24, 14));
}
else if (FCString::Strcmp(Name, TEXT("cliff_ledge")) == 0)
{
AddBox(0x4001, FVector(6, 8, 6), FVector(10, 24, 26));
AddBox(0x4002, FVector(10, 8, 22), FVector(26, 24, 26));
}
else if (FCString::Strcmp(Name, TEXT("doorway_border")) == 0)
{
Fixture.bSurfaceFloor = true;
AddBox(0x5001, FVector(27, 12, 10), FVector(35, 18, 29));
AddBox(0x5002, FVector(39, 12, 10), FVector(45, 18, 29));
AddBox(0x5003, FVector(27, 12, 25), FVector(45, 18, 29));
}
else
{
Fixture.bCaveChamber = true;
}
for (FHybridAnalyticBox& Box : Fixture.Boxes)
{
Box.Minimum += Phase;
Box.Maximum += Phase;
const auto QuantizePlanCoordinate = [](double Value)
{ return std::round(Value * 256.0) / 256.0; };
Box.Minimum.X = QuantizePlanCoordinate(Box.Minimum.X);
Box.Minimum.Y = QuantizePlanCoordinate(Box.Minimum.Y);
Box.Minimum.Z = QuantizePlanCoordinate(Box.Minimum.Z);
Box.Maximum.X = QuantizePlanCoordinate(Box.Maximum.X);
Box.Maximum.Y = QuantizePlanCoordinate(Box.Maximum.Y);
Box.Maximum.Z = QuantizePlanCoordinate(Box.Maximum.Z);
}
return Fixture;
}
double EvaluateHybridAnalyticFixture(const FHybridAnalyticFixture& Fixture,
const FVector& Point, const FVector& Phase)
{
const FVector Local = Point - Phase;
double Density = std::numeric_limits<double>::infinity();
if (Fixture.bSurfaceFloor)
{
const double Floor = 16.0 + 2.0 * std::sin(Local.X * 0.19)
+ 1.4 * std::sin(Local.Y * 0.17);
Density = Local.Z - Floor;
}
if (Fixture.bCaveChamber)
{
Density = 8.0 - (Local - FVector(16, 16, 16)).Size();
}
for (const FHybridAnalyticBox& Box : Fixture.Boxes)
{
Density = FMath::Min(Density, HybridFixtureBoxSdf(Local,
Box.Minimum - Phase, Box.Maximum - Phase));
}
return Density;
}
bool SameHybridMeshArrays(const FVoxelMeshData& A, const FVoxelMeshData& B)
{
if (A.Vertices.Num() != B.Vertices.Num() || A.Normals.Num() != B.Normals.Num()
|| A.UVs.Num() != B.UVs.Num() || A.Colors.Num() != B.Colors.Num()
|| A.Triangles.Num() != B.Triangles.Num()
|| A.NumSurfaceVertices != B.NumSurfaceVertices
|| A.NumCeilingTriangles != B.NumCeilingTriangles
|| A.NumSkirtTriangles != B.NumSkirtTriangles)
{
return false;
}
for (int32 I = 0; I < A.Vertices.Num(); ++I)
{
if (A.Vertices[I].X != B.Vertices[I].X || A.Vertices[I].Y != B.Vertices[I].Y
|| A.Vertices[I].Z != B.Vertices[I].Z) return false;
}
for (int32 I = 0; I < A.Normals.Num(); ++I)
{
if (A.Normals[I].X != B.Normals[I].X || A.Normals[I].Y != B.Normals[I].Y
|| A.Normals[I].Z != B.Normals[I].Z) return false;
}
for (int32 I = 0; I < A.UVs.Num(); ++I)
{
if (A.UVs[I].X != B.UVs[I].X || A.UVs[I].Y != B.UVs[I].Y) return false;
}
for (int32 I = 0; I < A.Colors.Num(); ++I)
{
if (A.Colors[I] != B.Colors[I]) return false;
}
for (int32 I = 0; I < A.Triangles.Num(); ++I)
{
if (A.Triangles[I] != B.Triangles[I]) return false;
}
return true;
}
bool GenerateHybridFixtureMesh(UVoxelGenerator* Generator, const FHybridAnalyticFixture& Fixture,
const FVector& Phase, const FIntVector& Origin, int32 Step, int32 Cells,
int32 HybridRecipe, FVoxelMeshData& OutMesh, double* OutMeshingMilliseconds,
FString& OutError)
{
if (Generator == nullptr || Step <= 0 || Cells <= 0)
{
OutError = TEXT("Hybrid analytic fixture has no generator or an invalid tile lattice.");
return false;
}
FVoxelSharedDensityGrid Grid;
Grid.OriginVoxels = Origin + FIntVector(-Step, -Step, -Step);
Grid.Step = Step;
Grid.Dim = Cells + 3;
Grid.Samples.SetNumUninitialized(Grid.Dim * Grid.Dim * Grid.Dim);
for (int32 Z = -1; Z <= Cells + 1; ++Z)
for (int32 Y = -1; Y <= Cells + 1; ++Y)
for (int32 X = -1; X <= Cells + 1; ++X)
{
const FVector Point(Origin.X + X * Step, Origin.Y + Y * Step, Origin.Z + Z * Step);
const int32 Index = ((Z + 1) * Grid.Dim + (Y + 1)) * Grid.Dim + (X + 1);
Grid.Samples[Index] = static_cast<float>(EvaluateHybridAnalyticFixture(Fixture, Point, Phase));
}
TArray<FVoxelHybridSharpFeatureBox> SharpFeatures;
if (HybridRecipe != 0)
{
for (const FHybridAnalyticBox& Feature : Fixture.Boxes)
{
FVoxelHybridSharpFeatureBox Box;
Box.StableId = Feature.StableId;
for (int32 Axis = 0; Axis < 3; ++Axis)
{
const double Low = Axis == 0 ? Feature.Minimum.X
: Axis == 1 ? Feature.Minimum.Y : Feature.Minimum.Z;
const double High = Axis == 0 ? Feature.Maximum.X
: Axis == 1 ? Feature.Maximum.Y : Feature.Maximum.Z;
Box.MinimumSubvoxels[Axis] = static_cast<int64>(std::llround(Low * 256.0));
Box.MaximumSubvoxels[Axis] = static_cast<int64>(std::llround(High * 256.0));
}
SharpFeatures.Add(Box);
}
}
UVoxelMarchingCubesMesher* Mesher = NewObject<UVoxelMarchingCubesMesher>(
GetTransientPackage(), NAME_None, RF_Transient);
if (Mesher == nullptr)
{
OutError = TEXT("Could not allocate the production mesher for an analytic fixture.");
return false;
}
Mesher->SetGenerator(Generator);
Mesher->SetGeometryVoxelSizeCm(25.0f);
Mesher->bGenerateSkirts = false;
if (HybridRecipe == 2)
Mesher->SetHybridSharpFeatureSnap(MoveTemp(SharpFeatures));
else if (HybridRecipe == 1)
Mesher->SetHybridSharpFeatures(MoveTemp(SharpFeatures));
const double StartSeconds = FPlatformTime::Seconds();
OutMesh = Mesher->GenerateMesh(Origin, Step, Cells, nullptr,
INT32_MIN, INT32_MAX, nullptr, nullptr, &Grid);
if (OutMeshingMilliseconds != nullptr)
{
*OutMeshingMilliseconds = (FPlatformTime::Seconds() - StartSeconds) * 1000.0;
}
return true;
}
using FHybridContourSegment = std::array<int64, 4>;
using FHybridContourSet = std::set<FHybridContourSegment>;
FHybridContourSet ExtractHybridBoundaryContour(const FVoxelMeshData& Mesh, double PlaneXcm)
{
FHybridContourSet Segments;
constexpr double PlaneTolerance = 0.01;
for (int32 I = 0; I + 2 < Mesh.Triangles.Num(); I += 3)
{
const int32 Indices[3] = {Mesh.Triangles[I], Mesh.Triangles[I + 1], Mesh.Triangles[I + 2]};
for (int32 Edge = 0; Edge < 3; ++Edge)
{
const FVector& A = Mesh.Vertices[Indices[Edge]];
const FVector& B = Mesh.Vertices[Indices[(Edge + 1) % 3]];
if (FMath::Abs(A.X - PlaneXcm) > PlaneTolerance
|| FMath::Abs(B.X - PlaneXcm) > PlaneTolerance)
{
continue;
}
int64 AY = static_cast<int64>(std::llround(A.Y * 100.0));
int64 AZ = static_cast<int64>(std::llround(A.Z * 100.0));
int64 BY = static_cast<int64>(std::llround(B.Y * 100.0));
int64 BZ = static_cast<int64>(std::llround(B.Z * 100.0));
if (AY == BY && AZ == BZ) continue;
if (std::tie(BY, BZ) < std::tie(AY, AZ))
{
std::swap(AY, BY);
std::swap(AZ, BZ);
}
Segments.insert({AY, AZ, BY, BZ});
}
}
return Segments;
}
FHybridContourSet ClipHybridContourToBounds(const FHybridContourSet& Contour,
double MinYCm, double MaxYCm, double MinZCm, double MaxZCm)
{
FHybridContourSet Clipped;
for (const FHybridContourSegment& Segment : Contour)
{
const double Y0 = static_cast<double>(Segment[0]) / 100.0;
const double Z0 = static_cast<double>(Segment[1]) / 100.0;
const double Y1 = static_cast<double>(Segment[2]) / 100.0;
const double Z1 = static_cast<double>(Segment[3]) / 100.0;
const double DY = Y1 - Y0;
const double DZ = Z1 - Z0;
double T0 = 0.0;
double T1 = 1.0;
const double P[4] = {-DY, DY, -DZ, DZ};
const double Q[4] = {
Y0 - MinYCm, MaxYCm - Y0, Z0 - MinZCm, MaxZCm - Z0};
bool bVisible = true;
for (int32 Plane = 0; Plane < 4; ++Plane)
{
if (FMath::Abs(P[Plane]) <= 1.0e-12)
{
if (Q[Plane] < 0.0) { bVisible = false; break; }
continue;
}
const double T = Q[Plane] / P[Plane];
if (P[Plane] < 0.0) T0 = FMath::Max(T0, T);
else T1 = FMath::Min(T1, T);
if (T0 > T1) { bVisible = false; break; }
}
if (!bVisible) continue;
const int64 CY0 = static_cast<int64>(std::llround((Y0 + T0 * DY) * 100.0));
const int64 CZ0 = static_cast<int64>(std::llround((Z0 + T0 * DZ) * 100.0));
const int64 CY1 = static_cast<int64>(std::llround((Y0 + T1 * DY) * 100.0));
const int64 CZ1 = static_cast<int64>(std::llround((Z0 + T1 * DZ) * 100.0));
if (CY0 == CY1 && CZ0 == CZ1) continue;
if (std::tie(CY1, CZ1) < std::tie(CY0, CZ0))
Clipped.insert({CY1, CZ1, CY0, CZ0});
else
Clipped.insert({CY0, CZ0, CY1, CZ1});
}
return Clipped;
}
double HybridPointToSegmentCm(double Y, double Z, const FHybridContourSegment& Segment)
{
const double AY = static_cast<double>(Segment[0]) / 100.0;
const double AZ = static_cast<double>(Segment[1]) / 100.0;
const double BY = static_cast<double>(Segment[2]) / 100.0;
const double BZ = static_cast<double>(Segment[3]) / 100.0;
const double DY = BY - AY, DZ = BZ - AZ;
const double Denominator = DY * DY + DZ * DZ;
const double T = Denominator > 1.0e-12
? FMath::Clamp(((Y - AY) * DY + (Z - AZ) * DZ) / Denominator, 0.0, 1.0) : 0.0;
return FMath::Sqrt(FMath::Square(Y - (AY + T * DY)) + FMath::Square(Z - (AZ + T * DZ)));
}
double HybridContourSetMismatchCm(const FHybridContourSet& A, const FHybridContourSet& B,
int32& OutOverBudgetProbes)
{
constexpr double BudgetCm = 50.0;
double Maximum = 0.0;
OutOverBudgetProbes = 0;
if (A.empty() != B.empty()) return std::numeric_limits<double>::infinity();
const auto Measure = [&](const FHybridContourSet& Source, const FHybridContourSet& Target)
{
for (const FHybridContourSegment& Segment : Source)
for (int32 Endpoint = 0; Endpoint < 2; ++Endpoint)
{
const double Y = static_cast<double>(Segment[Endpoint * 2]) / 100.0;
const double Z = static_cast<double>(Segment[Endpoint * 2 + 1]) / 100.0;
double Nearest = std::numeric_limits<double>::infinity();
for (const FHybridContourSegment& Candidate : Target)
Nearest = FMath::Min(Nearest, HybridPointToSegmentCm(Y, Z, Candidate));
Maximum = FMath::Max(Maximum, Nearest);
if (Nearest > BudgetCm) ++OutOverBudgetProbes;
}
};
Measure(A, B);
Measure(B, A);
return Maximum;
}
bool RunHybridPluginMesherFixtures(UVoxelGenerator* Generator, bool bIncludeV2,
FString& OutError)
{
const FVector Phase(0.27, 0.41, 0.33);
const TCHAR* FixtureNames[] = {
TEXT("corner90"), TEXT("doorway"), TEXT("riser1m"),
TEXT("cliff_ledge"), TEXT("organic_cave_chamber")};
int32 SharpDifferences = 0;
for (const TCHAR* FixtureName : FixtureNames)
{
const FHybridAnalyticFixture Fixture = MakeHybridAnalyticFixture(FixtureName, Phase);
std::vector<double> McTimes, HybridTimes;
FVoxelMeshData McMesh, HybridMesh, RepeatedHybridMesh;
for (int32 Repeat = 0; Repeat < 3; ++Repeat)
{
double ElapsedMs = 0.0;
if (!GenerateHybridFixtureMesh(Generator, Fixture, Phase, FIntVector::ZeroValue,
1, CHUNK_SIZE, false, McMesh, &ElapsedMs, OutError)) return false;
McTimes.push_back(ElapsedMs);
if (!GenerateHybridFixtureMesh(Generator, Fixture, Phase, FIntVector::ZeroValue,
1, CHUNK_SIZE, true, HybridMesh, &ElapsedMs, OutError)) return false;
HybridTimes.push_back(ElapsedMs);
if (Repeat == 0) RepeatedHybridMesh = HybridMesh;
}
const bool bStable = SameHybridMeshArrays(RepeatedHybridMesh, HybridMesh);
const bool bExpectedEqual = Fixture.Boxes.empty();
const bool bMatchesExpected = bExpectedEqual
? SameHybridMeshArrays(McMesh, HybridMesh)
: !SameHybridMeshArrays(McMesh, HybridMesh);
if (!bStable || !bMatchesExpected)
{
OutError = FString::Printf(
TEXT("Production hybrid fixture failed: %s stable=%d expected_mc_identity=%d mc_vertices=%d hybrid_vertices=%d mc_triangles=%d hybrid_triangles=%d invoked=%d features=%u active_roots=%u feature_root_samples=%u near_roots=%u min_root_sdf=%.9f tagged_roots=%u sharp_cells=%u dual_cells=%u dc_triangles=%u border_fallback=%u."),
FixtureName, bStable ? 1 : 0, bExpectedEqual ? 1 : 0,
McMesh.Vertices.Num(), HybridMesh.Vertices.Num(),
McMesh.Triangles.Num() / 3, HybridMesh.Triangles.Num() / 3,
HybridMesh.bHybridMesherInvoked ? 1 : 0, HybridMesh.HybridFeatureCount,
HybridMesh.HybridActiveEdgeRootCount,
HybridMesh.HybridFeatureRootSampleCount,
HybridMesh.HybridNearFeatureRootSampleCount,
HybridMesh.HybridMinimumFeatureRootAbsSdf,
HybridMesh.HybridTaggedEdgeRootCount,
HybridMesh.HybridSharpCellCount, HybridMesh.HybridDualCellCount,
HybridMesh.HybridDCTriangleCount, HybridMesh.HybridBorderFallbackPatchCount);
return false;
}
if (!bExpectedEqual) ++SharpDifferences;
std::sort(McTimes.begin(), McTimes.end());
std::sort(HybridTimes.begin(), HybridTimes.end());
UE_LOG(LogTemp, Display,
TEXT("[WorldAssemblerHybridPluginFixture] name=%s lod=0 cells=%d mc_ms=%.3f hybrid_ms=%.3f ratio=%.3f mc_triangles=%d hybrid_triangles=%d exact_repeat=1"),
FixtureName, CHUNK_SIZE, McTimes[1], HybridTimes[1],
McTimes[1] > 0.0 ? HybridTimes[1] / McTimes[1] : 0.0,
McMesh.Triangles.Num() / 3, HybridMesh.Triangles.Num() / 3);
}
if (SharpDifferences != 4)
{
OutError = FString::Printf(TEXT("Expected four analytic sharp fixtures to exercise DC; got %d."),
SharpDifferences);
return false;
}
if (bIncludeV2)
{
int32 SnapSharpDifferences = 0;
for (const TCHAR* FixtureName : FixtureNames)
{
const FHybridAnalyticFixture Fixture = MakeHybridAnalyticFixture(FixtureName, Phase);
std::vector<double> McTimes, SnapTimes;
FVoxelMeshData McMesh, SnapMesh, RepeatedSnapMesh;
for (int32 Repeat = 0; Repeat < 3; ++Repeat)
{
double ElapsedMs = 0.0;
if (!GenerateHybridFixtureMesh(Generator, Fixture, Phase, FIntVector::ZeroValue,
1, CHUNK_SIZE, 0, McMesh, &ElapsedMs, OutError)) return false;
McTimes.push_back(ElapsedMs);
if (!GenerateHybridFixtureMesh(Generator, Fixture, Phase, FIntVector::ZeroValue,
1, CHUNK_SIZE, 2, SnapMesh, &ElapsedMs, OutError)) return false;
SnapTimes.push_back(ElapsedMs);
if (Repeat == 0) RepeatedSnapMesh = SnapMesh;
}
const bool bExpectedEqual = Fixture.Boxes.empty();
const bool bStable = SameHybridMeshArrays(RepeatedSnapMesh, SnapMesh);
const bool bMatchesExpected = bExpectedEqual
? SameHybridMeshArrays(McMesh, SnapMesh)
: !SameHybridMeshArrays(McMesh, SnapMesh);
const bool bInvocationMatchesExpected = bExpectedEqual
? !SnapMesh.bHybridMesherInvoked : SnapMesh.bHybridMesherInvoked;
if (!bStable || !bMatchesExpected || !bInvocationMatchesExpected)
{
OutError = FString::Printf(
TEXT("Production hybrid V2 fixture failed: %s stable=%d expected_mc_identity=%d invoked=%d."),
FixtureName, bStable ? 1 : 0, bExpectedEqual ? 1 : 0,
SnapMesh.bHybridMesherInvoked ? 1 : 0);
return false;
}
if (!bExpectedEqual) ++SnapSharpDifferences;
std::sort(McTimes.begin(), McTimes.end());
std::sort(SnapTimes.begin(), SnapTimes.end());
UE_LOG(LogTemp, Display,
TEXT("[WorldAssemblerHybridPluginFixtureV2] name=%s lod=0 cells=%d mc_ms=%.3f hybrid_ms=%.3f ratio=%.3f mc_triangles=%d hybrid_triangles=%d exact_repeat=1"),
FixtureName, CHUNK_SIZE, McTimes[1], SnapTimes[1],
McTimes[1] > 0.0 ? SnapTimes[1] / McTimes[1] : 0.0,
McMesh.Triangles.Num() / 3, SnapMesh.Triangles.Num() / 3);
}
if (SnapSharpDifferences != 4)
{
OutError = FString::Printf(
TEXT("Expected four analytic sharp fixtures to exercise hybrid V2; got %d."),
SnapSharpDifferences);
return false;
}
}
// Coarse LOD uses MC regardless of the selected recipe, so its full arrays must match.
// Le LOD grossier reste sur MC, meme si la recette hybride est activee.
const FHybridAnalyticFixture CoarseFixture = MakeHybridAnalyticFixture(TEXT("doorway"), Phase);
FVoxelMeshData CoarseMc, CoarseHybrid;
if (!GenerateHybridFixtureMesh(Generator, CoarseFixture, Phase, FIntVector::ZeroValue,
2, CHUNK_SIZE, false, CoarseMc, nullptr, OutError)
|| !GenerateHybridFixtureMesh(Generator, CoarseFixture, Phase, FIntVector::ZeroValue,
2, CHUNK_SIZE, true, CoarseHybrid, nullptr, OutError)) return false;
if (!SameHybridMeshArrays(CoarseMc, CoarseHybrid))
{
OutError = TEXT("Hybrid altered a coarse LOD mesh; DC must remain LOD0-only.");
return false;
}
if (bIncludeV2)
{
FVoxelMeshData CoarseSnap;
if (!GenerateHybridFixtureMesh(Generator, CoarseFixture, Phase, FIntVector::ZeroValue,
2, CHUNK_SIZE, 2, CoarseSnap, nullptr, OutError)
|| !SameHybridMeshArrays(CoarseMc, CoarseSnap))
{
OutError = TEXT("Hybrid V2 altered a coarse LOD mesh; snapping must remain LOD0-only.");
return false;
}
}
// Independent same-LOD neighbors share the exact sampled contour at their common face.
// Des voisins LOD identique independants doivent partager exactement leur contour commun.
const FVector BorderPhase(0.23, 0.37, 0.19);
const FHybridAnalyticFixture BorderFixture =
MakeHybridAnalyticFixture(TEXT("doorway_border"), BorderPhase);
FVoxelMeshData McLeft, McRight, HybridLeft, HybridRight;
if (!GenerateHybridFixtureMesh(Generator, BorderFixture, BorderPhase, FIntVector(0, 0, 0),
1, CHUNK_SIZE, false, McLeft, nullptr, OutError)
|| !GenerateHybridFixtureMesh(Generator, BorderFixture, BorderPhase, FIntVector(32, 0, 0),
1, CHUNK_SIZE, false, McRight, nullptr, OutError)
|| !GenerateHybridFixtureMesh(Generator, BorderFixture, BorderPhase, FIntVector(0, 0, 0),
1, CHUNK_SIZE, true, HybridLeft, nullptr, OutError)
|| !GenerateHybridFixtureMesh(Generator, BorderFixture, BorderPhase, FIntVector(32, 0, 0),
1, CHUNK_SIZE, true, HybridRight, nullptr, OutError)) return false;
const FHybridContourSet McLeftBorder = ExtractHybridBoundaryContour(McLeft, 800.0);
const FHybridContourSet McRightBorder = ExtractHybridBoundaryContour(McRight, 800.0);
const FHybridContourSet HybridLeftBorder = ExtractHybridBoundaryContour(HybridLeft, 800.0);
const FHybridContourSet HybridRightBorder = ExtractHybridBoundaryContour(HybridRight, 800.0);
if (McLeftBorder.empty() || McLeftBorder != McRightBorder
|| HybridLeftBorder.empty() || HybridLeftBorder != HybridRightBorder
|| !SameHybridMeshArrays(McLeft, HybridLeft)
|| !SameHybridMeshArrays(McRight, HybridRight))
{
OutError = FString::Printf(
TEXT("Plugin same-LOD border failed: MC segments=%d/%d hybrid=%d/%d."),
static_cast<int32>(McLeftBorder.size()), static_cast<int32>(McRightBorder.size()),
static_cast<int32>(HybridLeftBorder.size()), static_cast<int32>(HybridRightBorder.size()));
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[WorldAssemblerHybridPluginBorder] same_lod=PASS independent=1 mc_segments=%d hybrid_segments=%d border_fallback_mc_identity=1"),
static_cast<int32>(McLeftBorder.size()), static_cast<int32>(HybridLeftBorder.size()));
if (bIncludeV2)
{
FVoxelMeshData SnapLeft, SnapRight;
if (!GenerateHybridFixtureMesh(Generator, BorderFixture, BorderPhase, FIntVector(0, 0, 0),
1, CHUNK_SIZE, 2, SnapLeft, nullptr, OutError)
|| !GenerateHybridFixtureMesh(Generator, BorderFixture, BorderPhase, FIntVector(32, 0, 0),
1, CHUNK_SIZE, 2, SnapRight, nullptr, OutError)
|| ExtractHybridBoundaryContour(SnapLeft, 800.0) != McLeftBorder
|| ExtractHybridBoundaryContour(SnapRight, 800.0) != McRightBorder
|| ExtractHybridBoundaryContour(SnapLeft, 800.0)
!= ExtractHybridBoundaryContour(SnapRight, 800.0))
{
OutError = TEXT("Hybrid V2 changed an interior-only same-LOD border fixture.");
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[WorldAssemblerHybridPluginBorderV2] same_lod=PASS independent=1 mc_identity=1"));
}
// Compare the raw doorway contours at the 2:1 seam over all 125 sub-cell phases.
// Comparer les contours bruts de la porte sur la couture 2:1 pour 125 phases.
int32 LodOverBudgetCases = 0;
int32 LodUnmatchedProbes = 0;
double LodWorstMismatchCm = 0.0;
constexpr double Phases[5] = {0.0, 0.2, 0.4, 0.6, 0.8};
const auto ContourBoundsText = [](const FHybridContourSet& Contour)
{
if (Contour.empty()) return FString(TEXT("empty"));
double MinY = std::numeric_limits<double>::infinity();
double MaxY = -std::numeric_limits<double>::infinity();
double MinZ = std::numeric_limits<double>::infinity();
double MaxZ = -std::numeric_limits<double>::infinity();
for (const FHybridContourSegment& Segment : Contour)
{
MinY = FMath::Min(MinY, static_cast<double>(FMath::Min(Segment[0], Segment[2])) / 100.0);
MaxY = FMath::Max(MaxY, static_cast<double>(FMath::Max(Segment[0], Segment[2])) / 100.0);
MinZ = FMath::Min(MinZ, static_cast<double>(FMath::Min(Segment[1], Segment[3])) / 100.0);
MaxZ = FMath::Max(MaxZ, static_cast<double>(FMath::Max(Segment[1], Segment[3])) / 100.0);
}
return FString::Printf(TEXT("(y=%.2f..%.2f,z=%.2f..%.2f)cm"), MinY, MaxY, MinZ, MaxZ);
};
for (double PhaseX : Phases)
for (double PhaseY : Phases)
for (double PhaseZ : Phases)
{
const FVector SweepPhase(PhaseX, PhaseY, PhaseZ);
const FHybridAnalyticFixture Doorway =
MakeHybridAnalyticFixture(TEXT("doorway_border"), SweepPhase);
FVoxelMeshData FineMc, CoarseMcForPhase, FineHybrid;
if (!GenerateHybridFixtureMesh(Generator, Doorway, SweepPhase, FIntVector(0, 0, 0),
1, CHUNK_SIZE, false, FineMc, nullptr, OutError)
|| !GenerateHybridFixtureMesh(Generator, Doorway, SweepPhase, FIntVector(32, 0, 0),
2, CHUNK_SIZE, false, CoarseMcForPhase, nullptr, OutError)
|| !GenerateHybridFixtureMesh(Generator, Doorway, SweepPhase, FIntVector(0, 0, 0),
1, CHUNK_SIZE, true, FineHybrid, nullptr, OutError)) return false;
if (!SameHybridMeshArrays(FineMc, FineHybrid))
{
OutError = TEXT("LOD0 doorway cells touching the LOD border did not fall back to MC.");
return false;
}
const FHybridContourSet FineContour = ExtractHybridBoundaryContour(FineMc, 800.0);
const FHybridContourSet CoarseContour = ClipHybridContourToBounds(
ExtractHybridBoundaryContour(CoarseMcForPhase, 800.0), 0.0, 800.0, 0.0, 800.0);
if (FineContour.empty() || CoarseContour.empty())
{
OutError = TEXT("The production doorway border probe did not produce a contour on both LODs.");
return false;
}
if (bIncludeV2)
{
FVoxelMeshData FineSnap;
if (!GenerateHybridFixtureMesh(Generator, Doorway, SweepPhase, FIntVector(0, 0, 0),
1, CHUNK_SIZE, 2, FineSnap, nullptr, OutError)
|| ExtractHybridBoundaryContour(FineSnap, 800.0) != FineContour)
{
OutError = TEXT("Hybrid V2 changed a raw LOD border contour in the 125-phase doorway sweep.");
return false;
}
}
int32 OverBudgetProbes = 0;
const double MismatchCm = HybridContourSetMismatchCm(
FineContour, CoarseContour, OverBudgetProbes);
if (PhaseX == 0.0 && PhaseY == 0.0 && PhaseZ == 0.0)
{
UE_LOG(LogTemp, Display,
TEXT("[WorldAssemblerHybridPluginBorderProbe] phase=(0,0,0) plane_x_cm=800 fine_segments=%d fine_bounds=%s coarse_overlap_segments=%d coarse_overlap_bounds=%s mismatch_cm=%.3f"),
static_cast<int32>(FineContour.size()), *ContourBoundsText(FineContour),
static_cast<int32>(CoarseContour.size()), *ContourBoundsText(CoarseContour),
MismatchCm);
}
if (!FMath::IsFinite(MismatchCm))
{
OutError = TEXT("The production doorway test found an empty fine/coarse border contour.");
return false;
}
LodWorstMismatchCm = FMath::Max(LodWorstMismatchCm, MismatchCm);
LodUnmatchedProbes += OverBudgetProbes;
if (OverBudgetProbes > 0) ++LodOverBudgetCases;
}
UE_LOG(LogTemp, Display,
TEXT("[WorldAssemblerHybridPluginBorder] lod0_lod1_doorway=1 cases=125 over_50cm_cases=%d over_50cm_probes=%d worst_contour_mismatch_cm=%.3f fine_border_hybrid_mc_identity=1 coarse_recipe=MC skirts_in_raw_probe=0"),
LodOverBudgetCases, LodUnmatchedProbes, LodWorstMismatchCm);
if (LodOverBudgetCases > 0)
{
OutError = FString::Printf(
TEXT("Production MC doorway contour exceeded the 50 cm border budget in %d/125 phases (worst %.3f cm)."),
LodOverBudgetCases, LodWorstMismatchCm);
return false;
}
return true;
}
bool CreateLockedLayoutSettingsCopy(
UVoxelSettings* SourceSettings,
UVoxelStrateManager* SourceManager,
int32 Seed,
bool bConvertSurfaceToFloatingIslands,
TStrongObjectPtr<UVoxelSettings>& OutSettings,
TArray<TStrongObjectPtr<UVoxelStrateDefinition>>& OutDefinitions,
int32& OutTargetStrateIndex,
FString& OutError)
{
if (SourceSettings == nullptr || SourceManager == nullptr || SourceManager->GetLayout().IsEmpty())
{
OutError = TEXT("Could not copy an empty resolved owner layout for the Core test.");
return false;
}
UVoxelSettings* SettingsCopy = DuplicateObject<UVoxelSettings>(
SourceSettings, GetTransientPackage(), NAME_None);
if (SettingsCopy == nullptr)
{
OutError = TEXT("Could not duplicate owner settings for the transient Core test.");
return false;
}
OutSettings = TStrongObjectPtr<UVoxelSettings>(SettingsCopy);
SettingsCopy->Season.Reset();
SettingsCopy->Seed = Seed;
SettingsCopy->TotalStrates = SourceManager->GetLayout().Num();
SettingsCopy->StratePool.Reset();
SettingsCopy->FixedStrates.Reset();
SettingsCopy->WorldAssemblyManifestJson.Reset();
SettingsCopy->WorldAssemblyManifestHash.Reset();
SettingsCopy->bUseWorldAssemblyManifestOverride = false;
OutDefinitions.Reset();
OutTargetStrateIndex = INDEX_NONE;
int32 ResolvedGap = SourceSettings->InterStrateGapChunks;
if (SourceManager->GetLayout().Num() > 1 && !SourceSettings->Season.IsNull())
{
ResolvedGap = SourceManager->GetLayout()[0].BottomChunkZ
- SourceManager->GetLayout()[1].TopChunkZ - 1;
for (int32 Index = 1; Index + 1 < SourceManager->GetLayout().Num(); ++Index)
{
const int32 Gap = SourceManager->GetLayout()[Index].BottomChunkZ
- SourceManager->GetLayout()[Index + 1].TopChunkZ - 1;
if (Gap != ResolvedGap)
{
OutError = TEXT("The resolved season uses non-uniform strate gaps; the transient copy cannot preserve it.");
return false;
}
}
}
SettingsCopy->InterStrateGapChunks = FMath::Max(0, ResolvedGap);
TArray<TStrongObjectPtr<UVoxelStrateDefinition>> DefinitionReferences;
DefinitionReferences.Reserve(SourceManager->GetLayout().Num());
TMap<int32, UVoxelStrateDefinition*> DefinitionCopies;
for (const FStrateSlot& Slot : SourceManager->GetLayout())
{
if (Slot.Definition == nullptr)
{
OutError = FString::Printf(TEXT("Owner layout slot %d has no definition to copy."), Slot.StrateIndex);
return false;
}
UVoxelStrateDefinition* DefinitionCopy = DuplicateObject<UVoxelStrateDefinition>(
Slot.Definition, SettingsCopy, FName(*FString::Printf(TEXT("CoreTest_Strate_%d"), Slot.StrateIndex)));
if (DefinitionCopy == nullptr)
{
OutError = FString::Printf(TEXT("Could not duplicate owner strate definition %d."), Slot.StrateIndex);
return false;
}
if (bConvertSurfaceToFloatingIslands
&& OutTargetStrateIndex == INDEX_NONE
&& Slot.Definition->GeneratorType == ECaveGeneratorType::SurfaceWorld)
{
OutTargetStrateIndex = Slot.StrateIndex;
DefinitionCopy->GeneratorType = ECaveGeneratorType::FloatingIslands;
}
DefinitionCopies.Add(Slot.StrateIndex, DefinitionCopy);
SettingsCopy->FixedStrates.Add(Slot.StrateIndex,
TSoftObjectPtr<UVoxelStrateDefinition>(DefinitionCopy));
DefinitionReferences.Add(TStrongObjectPtr<UVoxelStrateDefinition>(DefinitionCopy));
}
if (bConvertSurfaceToFloatingIslands && OutTargetStrateIndex == INDEX_NONE)
{
OutError = TEXT("The owner layout has no SurfaceWorld strate to convert for the island proof.");
return false;
}
if (!bConvertSurfaceToFloatingIslands) OutTargetStrateIndex = INDEX_NONE;
const FStrateSlot* TargetSlot = SourceManager->GetLayout().FindByPredicate(
[OutTargetStrateIndex](const FStrateSlot& Slot)
{ return Slot.StrateIndex == OutTargetStrateIndex; });
if (TargetSlot != nullptr)
{
UVoxelStrateDefinition* TargetDefinition = DefinitionCopies.FindRef(OutTargetStrateIndex);
if (TargetDefinition == nullptr)
{
OutError = TEXT("The transient SurfaceWorld definition could not be resolved.");
return false;
}
bool bFixedWaterEnabled = false;
float FixedWaterRelative = 0.0f;
const int32 MidChunkZ = TargetSlot->BottomChunkZ
+ (TargetSlot->TopChunkZ - TargetSlot->BottomChunkZ) / 2;
const float WaterVoxelZ = SourceManager->GetWaterLevelWorldZForChunk(
FIntVector(0, 0, MidChunkZ));
if (WaterVoxelZ != -FLT_MAX)
{
const float TopVoxelZ = (static_cast<float>(TargetSlot->TopChunkZ) + 1.0f) * CHUNK_SIZE;
const float BottomVoxelZ = static_cast<float>(TargetSlot->BottomChunkZ) * CHUNK_SIZE;
FixedWaterRelative = FMath::Clamp(
(WaterVoxelZ - BottomVoxelZ) / FMath::Max(TopVoxelZ - BottomVoxelZ, 1.0f),
0.0f, 1.0f);
bFixedWaterEnabled = true;
}
TargetDefinition->bHasWater = bFixedWaterEnabled;
TargetDefinition->GenerationParams.WaterLevelRelative =
bFixedWaterEnabled ? FixedWaterRelative : 0.0f;
}
OutDefinitions = MoveTemp(DefinitionReferences);
TStrongObjectPtr<UVoxelStrateManager> VerificationManager(NewObject<UVoxelStrateManager>(
GetTransientPackage(), NAME_None, RF_Transient));
if (!VerificationManager.IsValid() || !VerificationManager->Initialize(SettingsCopy, Seed)
|| VerificationManager->GetLayout().Num() != SourceManager->GetLayout().Num())
{
OutError = TEXT("The locked transient settings copy did not resolve the owner's full strate layout.");
return false;
}
for (int32 Index = 0; Index < SourceManager->GetLayout().Num(); ++Index)
{
const FStrateSlot& SourceSlot = SourceManager->GetLayout()[Index];
const FStrateSlot& CopySlot = VerificationManager->GetLayout()[Index];
if (SourceSlot.StrateIndex != CopySlot.StrateIndex
|| SourceSlot.TopChunkZ != CopySlot.TopChunkZ
|| SourceSlot.BottomChunkZ != CopySlot.BottomChunkZ
|| SourceSlot.HeightInChunks != CopySlot.HeightInChunks)
{
OutError = FString::Printf(TEXT("Transient layout copy changed owner heights at slot %d."), Index);
return false;
}
const ECaveGeneratorType ExpectedType = bConvertSurfaceToFloatingIslands
&& CopySlot.StrateIndex == OutTargetStrateIndex
? ECaveGeneratorType::FloatingIslands : SourceSlot.Definition->GeneratorType;
if (CopySlot.Definition == nullptr || CopySlot.Definition->GeneratorType != ExpectedType)
{
OutError = FString::Printf(TEXT("Transient layout copy changed an unintended archetype at slot %d."), Index);
return false;
}
}
return true;
}
struct FCoreTestWorldFixture
{
UWorld* World = nullptr;
uint64 PreviousFrameCounter = GFrameCounter;
bool Initialize(const FName& WorldName, FString& OutError)
{
const UWorld::InitializationValues Values = UWorld::InitializationValues()
.AllowAudioPlayback(false).RequiresHitProxies(false).CreatePhysicsScene(true)
.CreateNavigation(false).CreateAISystem(false).ShouldSimulatePhysics(true)
.SetDefaultGameMode(AGameModeBase::StaticClass()).SetTransactional(false);
World = UWorld::CreateWorld(EWorldType::Game, false, WorldName,
GetTransientPackage(), false, ERHIFeatureLevel::Num, &Values);
if (World == nullptr)
{
OutError = TEXT("Could not create a transient BuildHost Core test world.");
return false;
}
if (GEngine == nullptr)
{
OutError = TEXT("Unreal Engine is not available for the BuildHost Core test world.");
return false;
}
UGameInstance* GameInstance = NewObject<UGameInstance>(GEngine);
if (GameInstance == nullptr)
{
OutError = TEXT("Could not create the BuildHost Core test game instance.");
return false;
}
FWorldContext& Context = GEngine->CreateNewWorldContext(EWorldType::Game);
Context.OwningGameInstance = GameInstance;
Context.SetCurrentWorld(World);
World->SetGameInstance(GameInstance);
World->SetShouldTick(false);
GameInstance->Init();
World->AddToRoot();
return true;
}
~FCoreTestWorldFixture()
{
if (World != nullptr)
{
if (World->HasBegunPlay())
{
World->BeginTearingDown();
World->EndPlay(EEndPlayReason::Quit);
}
if (UGameInstance* GameInstance = World->GetGameInstance()) GameInstance->Shutdown();
if (GEngine != nullptr) GEngine->DestroyWorldContext(World);
World->DestroyWorld(false);
if (World->IsRooted()) World->RemoveFromRoot();
}
GFrameCounter = PreviousFrameCounter;
}
};
int32 ResolveCoreTestWorldSeed(const UVoxelSettings* Settings)
{
if (Settings == nullptr) return 0;
if (!Settings->Season.IsNull())
{
if (const UVoxelSeasonAsset* Season = Settings->Season.LoadSynchronous())
{
return Season->GetSeasonSeed();
}
}
return Settings->Seed;
}
bool CreateLoggedOwnerSurfaceSettingsCopy(
UVoxelSettings* Source, int32 Seed,
TStrongObjectPtr<UVoxelSettings>& OutSettings,
TStrongObjectPtr<UVoxelStrateDefinition>& OutSurfaceDefinition,
FString& OutError, bool bCompactTracePlan = false)
{
if (Source == nullptr)
{
OutError = TEXT("Cannot reconstruct the logged owner SurfaceWorld layout without its settings asset.");
return false;
}
if (!Source->Season.IsNull())
{
OutError = TEXT("The staged owner settings assign a season asset; the logged one-slot SurfaceWorld layout cannot be reconstructed while preserving that season.");
return false;
}
UVoxelStrateDefinition* SurfaceDefinition = LoadObject<UVoxelStrateDefinition>(
nullptr, TEXT("/Game/VoxelForge/DA_Strate1.DA_Strate1"));
if (SurfaceDefinition == nullptr
|| SurfaceDefinition->GeneratorType != ECaveGeneratorType::SurfaceWorld
|| SurfaceDefinition->StrateHeightInChunks <= 0)
{
OutError = FString::Printf(
TEXT("The staged owner assets do not contain a usable DA_Strate1 SurfaceWorld (definition_loaded=%d type=%d height=%d settings_strates=%d)."),
SurfaceDefinition != nullptr ? 1 : 0,
SurfaceDefinition != nullptr ? static_cast<int32>(SurfaceDefinition->GeneratorType) : INDEX_NONE,
SurfaceDefinition != nullptr ? SurfaceDefinition->StrateHeightInChunks : INDEX_NONE,
Source->TotalStrates);
return false;
}
UVoxelSettings* SettingsCopy = DuplicateObject<UVoxelSettings>(
Source, GetTransientPackage(), NAME_None);
if (SettingsCopy == nullptr)
{
OutError = TEXT("Could not duplicate owner settings for the logged SurfaceWorld layout.");
return false;
}
OutSettings = TStrongObjectPtr<UVoxelSettings>(SettingsCopy);
if (bCompactTracePlan)
{
SurfaceDefinition = DuplicateObject<UVoxelStrateDefinition>(SurfaceDefinition,
GetTransientPackage(), TEXT("VoxelForgeCompactSurfaceTraceDefinition"));
if (SurfaceDefinition == nullptr)
{
OutError = TEXT("Could not make a transient DA_Strate1 copy for the compact common-plan trace.");
return false;
}
// L14's historical fixed-length SurfaceWorld route search cannot place the
// new trace fixture even at 16 chunks. Use the same simple, walkable FlatPlain
// plan in both host revisions so the streaming comparison reaches AVoxelWorld.
SurfaceDefinition->GeneratorType = ECaveGeneratorType::FlatPlain;
SurfaceDefinition->StrateHeightInChunks = 16;
}
OutSurfaceDefinition = TStrongObjectPtr<UVoxelStrateDefinition>(SurfaceDefinition);
SettingsCopy->Seed = Seed;
SettingsCopy->TotalStrates = 1;
SettingsCopy->StratePool.Reset();
SettingsCopy->FixedStrates.Reset();
SettingsCopy->FixedStrates.Add(0,
TSoftObjectPtr<UVoxelStrateDefinition>(SurfaceDefinition));
SettingsCopy->WorldAssemblyManifestJson.Reset();
SettingsCopy->WorldAssemblyManifestHash.Reset();
SettingsCopy->bUseWorldAssemblyManifestOverride = false;
const int32 BottomChunkZ = 1 - SurfaceDefinition->StrateHeightInChunks;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreTestLayout] source=owner_asset_transient_copy definition=DA_Strate1 generator=%d seed=%d strata=1 bounds=[0,%d] height_chunks=%d compact_trace=%d gap=%d imported_override=cleared"),
static_cast<int32>(SurfaceDefinition->GeneratorType), Seed, BottomChunkZ,
SurfaceDefinition->StrateHeightInChunks,
bCompactTracePlan ? 1 : 0, SettingsCopy->InterStrateGapChunks);
return true;
}
bool RunCorePlanOnlyTest(const FExploreArguments& Arguments, FString& OutError)
{
UVoxelSettings* AuthoredSettings = LoadObject<UVoxelSettings>(
nullptr, *Arguments.SettingsReference);
if (AuthoredSettings == nullptr)
{
OutError = FString::Printf(TEXT("Could not load Core plan test settings '%s'."),
*Arguments.SettingsReference);
return false;
}
const int32 AuthoredSeed = ResolveCoreTestWorldSeed(AuthoredSettings);
TStrongObjectPtr<UVoxelSettings> OwnerSurfaceSettings;
TStrongObjectPtr<UVoxelStrateDefinition> OwnerSurfaceDefinition;
if (!CreateLoggedOwnerSurfaceSettingsCopy(AuthoredSettings, AuthoredSeed,
OwnerSurfaceSettings, OwnerSurfaceDefinition, OutError))
{
return false;
}
TStrongObjectPtr<UVoxelStrateManager> SourceManager(NewObject<UVoxelStrateManager>(
GetTransientPackage(), NAME_None, RF_Transient));
if (!SourceManager.IsValid() || !SourceManager->Initialize(OwnerSurfaceSettings.Get(), AuthoredSeed))
{
OutError = TEXT("Core plan test could not resolve the staged owner settings.");
return false;
}
const int32 ResolvedSeed = Arguments.bCoreTestSeedOverride
? Arguments.CoreTestSeed : SourceManager->GetWorldSeed();
TStrongObjectPtr<UVoxelSettings> SettingsCopy;
TArray<TStrongObjectPtr<UVoxelStrateDefinition>> DefinitionReferences;
int32 UnusedTargetStrate = INDEX_NONE;
UVoxelSettings* PlanSettings = nullptr;
if (Arguments.bCoreTestSeedOverride)
{
if (!CreateLockedLayoutSettingsCopy(OwnerSurfaceSettings.Get(), SourceManager.Get(), ResolvedSeed,
false, SettingsCopy, DefinitionReferences, UnusedTargetStrate, OutError))
{
return false;
}
PlanSettings = SettingsCopy.Get();
}
else
{
SettingsCopy = TStrongObjectPtr<UVoxelSettings>(
DuplicateObject<UVoxelSettings>(OwnerSurfaceSettings.Get(), GetTransientPackage(), NAME_None));
if (!SettingsCopy.IsValid())
{
OutError = TEXT("Could not duplicate the logged owner SurfaceWorld settings for plan generation.");
return false;
}
PlanSettings = SettingsCopy.Get();
}
// Keep any imported manifest bytes from the staged owner asset, but leave the opt-in off.
// This proves old round-3 payloads do not block automatic planning by default.
PlanSettings->WorldAssemblyManifestJson = AuthoredSettings->WorldAssemblyManifestJson;
PlanSettings->WorldAssemblyManifestHash = AuthoredSettings->WorldAssemblyManifestHash;
PlanSettings->bUseWorldAssemblyManifestOverride = false;
TStrongObjectPtr<UVoxelStrateManager> PlanManager(NewObject<UVoxelStrateManager>(
GetTransientPackage(), NAME_None, RF_Transient));
if (!PlanManager.IsValid()
|| !PlanManager->Initialize(PlanSettings, ResolvedSeed))
{
OutError = TEXT("Core plan test could not initialize the final resolved layout.");
return false;
}
FString RuntimeJson;
FString RepeatJson;
if (!VoxelForgeCoreAdapter::SerializeResolvedWorld(
PlanSettings, PlanManager.Get(), 3, 8, Arguments.CoreVoxelSizeCm, RuntimeJson, OutError)
|| !VoxelForgeCoreAdapter::SerializeResolvedWorld(
PlanSettings, PlanManager.Get(), 3, 8, Arguments.CoreVoxelSizeCm, RepeatJson, OutError))
{
return false;
}
if (RuntimeJson != RepeatJson)
{
OutError = TEXT("Core automatic plan serialization changed between identical startup resolutions.");
return false;
}
FTCHARToUTF8 Utf8(*RuntimeJson);
std::string CoreError;
std::optional<VoxelForgeCore::ResolvedWorldSnapshot> Snapshot =
VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
std::string_view(Utf8.Get(), static_cast<std::size_t>(Utf8.Length())), CoreError);
if (!Snapshot)
{
OutError = FString::Printf(TEXT("Core rejected the generated plan test snapshot: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
int32 ExpectedSurfaceCount = 0;
int32 ExpectedIslandCount = 0;
for (const FStrateSlot& Slot : PlanManager->GetLayout())
{
if (Slot.Definition == nullptr) continue;
const ECaveGeneratorType Type = PlanManager->GetGeneratorTypeForChunk(
FIntVector(0, 0, Slot.TopChunkZ));
ExpectedSurfaceCount += Type == ECaveGeneratorType::SurfaceWorld ? 1 : 0;
ExpectedIslandCount += Type == ECaveGeneratorType::FloatingIslands ? 1 : 0;
}
const VoxelForgeCore::WorldAssemblyManifest* Assembly = Snapshot->worldAssembly();
if (ExpectedSurfaceCount + ExpectedIslandCount > 0
&& (Assembly == nullptr
|| Assembly->plan.strates.size() != static_cast<std::size_t>(ExpectedSurfaceCount)
|| Assembly->floatingIslands.size() != static_cast<std::size_t>(ExpectedIslandCount)))
{
OutError = TEXT("Generated plan counts do not match the final resolved owner layout.");
return false;
}
if (ExpectedSurfaceCount + ExpectedIslandCount == 0 && Assembly != nullptr)
{
OutError = TEXT("Core attached a world assembly to a layout with no planned strata.");
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCorePlanTest] pass source=%s seed=%d layout_strates=%d surface_strates=%d island_strates=%d hash=%s deterministic_json=1 stored_manifest_ignored=%d override_enabled=0"),
Arguments.bCoreTestSeedOverride ? TEXT("locked_owner_layout_seed_override") : TEXT("owner_settings"),
PlanManager->GetWorldSeed(), PlanManager->GetLayout().Num(), ExpectedSurfaceCount,
ExpectedIslandCount, Assembly ? *FString::Printf(TEXT("%016llx"),
static_cast<unsigned long long>(Assembly->contentHash)) : TEXT("none"),
AuthoredSettings->WorldAssemblyManifestJson.IsEmpty() ? 0 : 1);
for (const FStrateSlot& Slot : PlanManager->GetLayout())
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCorePlanTest] slot=%d bounds=[%d,%d] type=%d height_chunks=%d"),
Slot.StrateIndex, Slot.TopChunkZ, Slot.BottomChunkZ,
Slot.Definition ? static_cast<int32>(Slot.Definition->GeneratorType) : INDEX_NONE,
Slot.HeightInChunks);
}
return true;
}
bool RunCoreFallbackActorTest(const FExploreArguments& Arguments, FString& OutError)
{
UVoxelSettings* AuthoredSettings = LoadObject<UVoxelSettings>(
nullptr, *Arguments.SettingsReference);
const FString ManifestPath = FPaths::ConvertRelativePathToFull(
Arguments.CoreWorldAssemblyManifestPath);
if (AuthoredSettings == nullptr || !IsUnderVoxelForgeSaved(ManifestPath))
{
OutError = TEXT("Core fallback test needs staged owner settings and a manifest inside plugin Saved.");
return false;
}
FString ManifestJson;
if (!FFileHelper::LoadFileToString(ManifestJson, *ManifestPath))
{
OutError = FString::Printf(TEXT("Could not read staged fallback manifest '%s'."), *ManifestPath);
return false;
}
const int32 OwnerSeed = ResolveCoreTestWorldSeed(AuthoredSettings);
TStrongObjectPtr<UVoxelSettings> TestSettings;
TStrongObjectPtr<UVoxelStrateDefinition> OwnerSurfaceDefinition;
if (!CreateLoggedOwnerSurfaceSettingsCopy(AuthoredSettings, OwnerSeed,
TestSettings, OwnerSurfaceDefinition, OutError))
{
return false;
}
FString ImportReport;
if (!TestSettings->SetWorldAssemblyManifestJson(ManifestJson, ImportReport))
{
OutError = FString::Printf(TEXT("Could not stage the mismatched manifest in transient settings: %s."),
*ImportReport);
return false;
}
FCoreTestWorldFixture WorldFixture;
if (!WorldFixture.Initialize(FName(TEXT("VoxelForgeCoreFallbackTestWorld")), OutError)) return false;
AVoxelWorld* Actor = WorldFixture.World->SpawnActorDeferred<AVoxelWorld>(
AVoxelWorld::StaticClass(), FTransform::Identity, nullptr, nullptr,
ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
if (Actor == nullptr)
{
OutError = TEXT("Could not spawn the transient Core fallback AVoxelWorld.");
return false;
}
Actor->Settings = TestSettings.Get();
Actor->bUseVoxelForgeCore = true;
Actor->bManageAtmosphere = false;
Actor->CoreClipRadius = 1;
Actor->CoreMaxClipLevel = 0;
Actor->FinishSpawning(FTransform::Identity);
if (!WorldFixture.World->SetGameMode(FURL()))
{
OutError = TEXT("Could not create the transient GameMode for the Core fallback test.");
return false;
}
WorldFixture.World->InitializeActorsForPlay(FURL());
WorldFixture.World->BeginPlay();
if (Actor->bUseVoxelForgeCore || Actor->IsVoxelForgeCoreReady()
|| Actor->Generator == nullptr || !Actor->HasActorBegunPlay())
{
OutError = TEXT("Mismatched world assembly did not disable Core and leave the legacy world running.");
return false;
}
FString RuntimeAssemblyJson, RuntimeAssemblyHash;
float LegacyDensity = Actor->Generator->GetDensityAt(0.0f, 0.0f, -32.0f);
if (!FMath::IsFinite(LegacyDensity)
|| Actor->GetVoxelForgeCoreWorldAssembly(RuntimeAssemblyJson, RuntimeAssemblyHash))
{
OutError = TEXT("Core fallback did not expose a finite legacy density path or cleared runtime plan state.");
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreFallbackTest] fallback=PASS core_disabled=1 legacy_generator=1 legacy_density=%.4f imported_override=1"),
LegacyDensity);
return true;
}
bool RunCoreOffActorTest(const FExploreArguments& Arguments, FString& OutError)
{
UVoxelSettings* Settings = LoadObject<UVoxelSettings>(nullptr, *Arguments.SettingsReference);
if (Settings == nullptr)
{
OutError = FString::Printf(TEXT("Could not load Core-off owner settings '%s'."),
*Arguments.SettingsReference);
return false;
}
const int32 Seed = ResolveCoreTestWorldSeed(Settings);
TStrongObjectPtr<UVoxelSettings> OwnerSurfaceSettings;
TStrongObjectPtr<UVoxelStrateDefinition> OwnerSurfaceDefinition;
if (!CreateLoggedOwnerSurfaceSettingsCopy(Settings, Seed,
OwnerSurfaceSettings, OwnerSurfaceDefinition, OutError))
{
return false;
}
TStrongObjectPtr<UVoxelStrateManager> ReferenceManager(NewObject<UVoxelStrateManager>(
GetTransientPackage(), NAME_None, RF_Transient));
if (!ReferenceManager.IsValid() || !ReferenceManager->Initialize(OwnerSurfaceSettings.Get(), Seed))
{
OutError = TEXT("Core-off test could not resolve the staged owner layout.");
return false;
}
UVoxelGenerator* ReferenceGenerator = NewObject<UVoxelGenerator>(
GetTransientPackage(), NAME_None, RF_Transient);
if (ReferenceGenerator == nullptr)
{
OutError = TEXT("Core-off test could not create its legacy density reference.");
return false;
}
ReferenceGenerator->InitializeSettings(OwnerSurfaceSettings.Get());
ReferenceGenerator->SetStrateManager(ReferenceManager.Get());
FCoreTestWorldFixture WorldFixture;
if (!WorldFixture.Initialize(FName(TEXT("VoxelForgeCoreOffTestWorld")), OutError)) return false;
AVoxelWorld* Actor = WorldFixture.World->SpawnActorDeferred<AVoxelWorld>(
AVoxelWorld::StaticClass(), FTransform::Identity, nullptr, nullptr,
ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
if (Actor == nullptr)
{
OutError = TEXT("Could not spawn the transient Core-off AVoxelWorld.");
return false;
}
Actor->Settings = OwnerSurfaceSettings.Get();
Actor->bUseVoxelForgeCore = false;
Actor->bManageAtmosphere = false;
Actor->FinishSpawning(FTransform::Identity);
if (!WorldFixture.World->SetGameMode(FURL()))
{
OutError = TEXT("Could not create the transient GameMode for the Core-off test.");
return false;
}
WorldFixture.World->InitializeActorsForPlay(FURL());
WorldFixture.World->BeginPlay();
if (Actor->bUseVoxelForgeCore || Actor->Generator == nullptr || Actor->Mesher == nullptr)
{
OutError = TEXT("Core-off owner world did not remain on its legacy generator and mesher.");
return false;
}
const FVector VoxelOrigin = Actor->LocalVoxelToWorld(FVector::ZeroVector);
const FVector NextVoxel = Actor->LocalVoxelToWorld(FVector(1.0, 0.0, 0.0));
const double LegacyVoxelCm = FVector::Distance(VoxelOrigin, NextVoxel);
const FVector Samples[] = {
FVector(0.0, 0.0, -32.0), FVector(48.0, -64.0, -128.0), FVector(-160.0, 96.0, -512.0)};
for (const FVector& Sample : Samples)
{
const float Actual = Actor->Generator->GetDensityAt(Sample.X, Sample.Y, Sample.Z);
const float Expected = ReferenceGenerator->GetDensityAt(Sample.X, Sample.Y, Sample.Z);
if (!FMath::IsFinite(Actual) || Actual != Expected)
{
OutError = FString::Printf(TEXT("Core-off legacy density changed at (%.0f,%.0f,%.0f): %.6f/%.6f."),
Sample.X, Sample.Y, Sample.Z, Actual, Expected);
return false;
}
}
float CoreDensity = 0.0f;
if (LegacyVoxelCm != VOXEL_SIZE
|| Actor->QueryVoxelForgeCoreDensityAt(VoxelOrigin, CoreDensity))
{
OutError = TEXT("Core-off owner world did not retain the 12.5 cm legacy scale and disabled Core queries.");
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreOffTest] legacy_unchanged=PASS core_disabled=1 voxel_cm=%.1f density_samples=3"),
LegacyVoxelCm);
return true;
}
bool RunCoreFarHorizonAgreementAudit(VoxelForgeCore::World& World,
const std::vector<VoxelForgeCore::TileKey>& Keys,
const VoxelForgeCore::WorldAssemblyManifest& Assembly,FString& OutError)
{
constexpr std::int64_t SubvoxelsPerVoxel=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
constexpr double ToleranceCm=2.0;
constexpr double NegativeDatumOffsetCm=100.0;
const double VoxelSizeCm=static_cast<double>(World.configuration().voxelSizeMicrons)/10000.0;
using FXYKey=std::pair<std::int64_t,std::int64_t>;
struct FGroundVertices { std::map<FXYKey,std::vector<std::int64_t>> ByXY; };
const auto GatherGroundVertices=[&](const VoxelForgeCore::TileMesh& Mesh)
{
FGroundVertices Out;
constexpr std::int64_t GridPeriod=128*SubvoxelsPerVoxel;
for(const VoxelForgeCore::MeshVertex& Vertex:Mesh.vertices)
{
if(Vertex.normal.z<0.35)continue;
const auto X=static_cast<std::int64_t>(std::llround(
Vertex.position.x/VoxelSizeCm*SubvoxelsPerVoxel));
const auto Y=static_cast<std::int64_t>(std::llround(
Vertex.position.y/VoxelSizeCm*SubvoxelsPerVoxel));
if(X%GridPeriod!=0||Y%GridPeriod!=0)continue;
const auto Z=static_cast<std::int64_t>(std::llround(
Vertex.position.z/VoxelSizeCm*SubvoxelsPerVoxel));
Out.ByXY[{X,Y}].push_back(Z);
}
return Out;
};
const auto TileBounds=[](const VoxelForgeCore::TileKey& Key)
{
const std::int64_t Extent=static_cast<std::int64_t>(32)<<Key.lod;
const std::int64_t X=static_cast<std::int64_t>(Key.coordinate.x)*Extent;
const std::int64_t Y=static_cast<std::int64_t>(Key.coordinate.y)*Extent;
const std::int64_t Z=static_cast<std::int64_t>(Key.coordinate.z)*Extent;
return std::array<std::int64_t,6>{X,Y,Z,X+Extent,Y+Extent,Z+Extent};
};
struct FSample { std::int64_t X=0,Y=0,ProfileGround=0,Far7=0,Far8=0; };
std::vector<FSample> Samples;
std::string CoreError;
VoxelForgeCore::TileGenerationOptions Options;
Options.hashDensity=true;
for(const auto& Key7:Keys)
{
if(Key7.lod!=7)continue;
const auto Bounds7=TileBounds(Key7);
for(const auto& Key8:Keys)
{
if(Key8.lod!=8)continue;
const auto Bounds8=TileBounds(Key8);
bool bTouches=true;
for(int Axis=0;Axis<3;++Axis)
bTouches=bTouches&&Bounds7[Axis]<=Bounds8[Axis+3]&&Bounds8[Axis]<=Bounds7[Axis+3];
if(!bTouches)continue;
const auto Mesh7=World.generateTile(Key7,Options,CoreError);
const auto Mesh8=World.generateTile(Key8,Options,CoreError);
if(!Mesh7||!Mesh8)
{
OutError=FString::Printf(TEXT("Far-horizon agreement tile generation failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
const FGroundVertices Ground7=GatherGroundVertices(*Mesh7);
const FGroundVertices Ground8=GatherGroundVertices(*Mesh8);
std::vector<FSample> PairSamples;
for(const auto& Plan:Assembly.plan.strates)
{
const VoxelForgeCore::Heightfield2D Field(Assembly.recipe,Plan);
if(!Field.isValid())continue;
for(const auto& Pair:Ground7.ByXY)
{
const auto Other=Ground8.ByXY.find(Pair.first);
if(Other==Ground8.ByXY.end())continue;
const auto Column=Field.point(Pair.first.first,Pair.first.second,0);
const std::int64_t Ground=Column.groundHeightSubvoxels;
const std::int64_t Cap=Field.farSurfaceCapHeightSubvoxels(
Pair.first.first,Pair.first.second);
if(Ground>=Cap)continue;
const double GroundVoxel=static_cast<double>(Ground)/SubvoxelsPerVoxel;
if(GroundVoxel<Bounds7[2]||GroundVoxel>Bounds7[5]
||GroundVoxel<Bounds8[2]||GroundVoxel>Bounds8[5])continue;
const auto Nearest=[Ground](const std::vector<std::int64_t>& Values)
->std::optional<std::int64_t>
{
if(Values.empty())return std::nullopt;
return *std::min_element(Values.begin(),Values.end(),[Ground](auto A,auto B)
{return std::abs(A-Ground)<std::abs(B-Ground);});
};
const auto Z7=Nearest(Pair.second),Z8=Nearest(Other->second);
if(Z7&&Z8)PairSamples.push_back({Pair.first.first,Pair.first.second,Ground,*Z7,*Z8});
}
if(PairSamples.size()>=64)break;
}
if(PairSamples.size()>Samples.size())Samples=std::move(PairSamples);
if(Samples.size()>=64)break;
}
if(Samples.size()>=64)break;
}
bool bUsedWalkRouteGrid=false;
if(Samples.size()<64)
{
Samples.clear();
for(const auto& Plan:Assembly.plan.strates)
{
const VoxelForgeCore::Heightfield2D Field(Assembly.recipe,Plan);
if(!Field.isValid())continue;
std::int64_t RouteXSub=0,RouteYSub=0;
if(!Plan.route.empty())
{
RouteXSub=Plan.route.front().positionSubvoxels[0];
RouteYSub=Plan.route.front().positionSubvoxels[1];
}
constexpr std::int64_t GridPeriod=128*SubvoxelsPerVoxel;
const std::int64_t XSub=static_cast<std::int64_t>(std::llround(
static_cast<double>(RouteXSub)/GridPeriod))*GridPeriod;
const std::int64_t YSub=static_cast<std::int64_t>(std::llround(
static_cast<double>(RouteYSub)/GridPeriod))*GridPeriod;
const auto WalkColumn=Field.point(XSub,YSub,0);
const std::int64_t WalkCap=Field.farSurfaceCapHeightSubvoxels(XSub,YSub);
if(WalkColumn.groundHeightSubvoxels>=WalkCap)continue;
const std::int64_t XVoxel=XSub/SubvoxelsPerVoxel,YVoxel=YSub/SubvoxelsPerVoxel;
const std::int64_t WalkGroundVoxel=WalkColumn.groundHeightSubvoxels/SubvoxelsPerVoxel;
const auto MakeKey=[](std::int64_t X,std::int64_t Y,std::int64_t Z,std::int64_t Extent,int32 LOD)
{
VoxelForgeCore::TileKey Key;
Key.coordinate={static_cast<int32>(std::floor(static_cast<double>(X)/Extent)),
static_cast<int32>(std::floor(static_cast<double>(Y)/Extent)),
static_cast<int32>(std::floor(static_cast<double>(Z)/Extent))};
Key.lod=LOD;
return Key;
};
const VoxelForgeCore::TileKey Key7=MakeKey(XVoxel,YVoxel,WalkGroundVoxel,4096,7);
const VoxelForgeCore::TileKey Key8=MakeKey(XVoxel,YVoxel,WalkGroundVoxel,8192,8);
const auto Mesh7=World.generateTile(Key7,Options,CoreError);
const auto Mesh8=World.generateTile(Key8,Options,CoreError);
if(!Mesh7||!Mesh8)
{
OutError=FString::Printf(TEXT("Walk-route far-horizon tile generation failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
const FGroundVertices Ground7=GatherGroundVertices(*Mesh7);
const FGroundVertices Ground8=GatherGroundVertices(*Mesh8);
for(const auto& Pair:Ground7.ByXY)
{
const auto Other=Ground8.ByXY.find(Pair.first);
if(Other==Ground8.ByXY.end())continue;
const auto Column=Field.point(Pair.first.first,Pair.first.second,0);
const std::int64_t Ground=Column.groundHeightSubvoxels;
if(Ground>=Field.farSurfaceCapHeightSubvoxels(
Pair.first.first,Pair.first.second))continue;
const auto Bounds7=TileBounds(Key7),Bounds8=TileBounds(Key8);
const double GroundVoxel=static_cast<double>(Ground)/SubvoxelsPerVoxel;
if(GroundVoxel<Bounds7[2]||GroundVoxel>Bounds7[5]
||GroundVoxel<Bounds8[2]||GroundVoxel>Bounds8[5])continue;
const auto Nearest=[Ground](const std::vector<std::int64_t>& Values)
->std::optional<std::int64_t>
{
if(Values.empty())return std::nullopt;
return *std::min_element(Values.begin(),Values.end(),[Ground](auto A,auto B)
{return std::abs(A-Ground)<std::abs(B-Ground);});
};
const auto Z7=Nearest(Pair.second),Z8=Nearest(Other->second);
if(Z7&&Z8)
Samples.push_back({Pair.first.first,Pair.first.second,Ground,*Z7,*Z8});
if(Samples.size()>=256)break;
}
if(Samples.size()>=64){bUsedWalkRouteGrid=true;break;}
Samples.clear();
}
}
if(Samples.size()<64)
{OutError=TEXT("Far-horizon agreement audit found fewer than 64 shared LOD7/8 surface points around the staged walk route.");return false;}
const auto FindNearRoot=[&](std::int64_t XSub,std::int64_t YSub,double GroundVoxel,double& OutVoxel)
{
const double X=static_cast<double>(XSub)/SubvoxelsPerVoxel;
const double Y=static_cast<double>(YSub)/SubvoxelsPerVoxel;
for(double Radius=4.0;Radius<=4096.0;Radius*=2.0)
{
double Lo=GroundVoxel-Radius,Hi=GroundVoxel+Radius;
if(!(World.densityAt({X,Y,Lo})<0.0f&&World.densityAt({X,Y,Hi})>=0.0f))continue;
for(int Iteration=0;Iteration<14;++Iteration)
{
const double Mid=(Lo+Hi)*0.5;
if(World.densityAt({X,Y,Mid})<0.0f)Lo=Mid;else Hi=Mid;
}
OutVoxel=(Lo+Hi)*0.5;
return true;
}
return false;
};
double MaximumFar7ErrorCm=0.0,MaximumFar8ErrorCm=0.0,MaximumCrossLodErrorCm=0.0;
double MaximumNegativeControlErrorCm=0.0;
std::size_t Compared=0;
const std::size_t SampleStride=std::max<std::size_t>(1,(Samples.size()+127)/128);
for(std::size_t Index=0;Index<Samples.size();Index+=SampleStride)
{
const auto& Sample=Samples[Index];
double NearVoxel=0.0;
if(!FindNearRoot(Sample.X,Sample.Y,
static_cast<double>(Sample.ProfileGround)/SubvoxelsPerVoxel,NearVoxel))continue;
const double Far7Cm=static_cast<double>(Sample.Far7)/SubvoxelsPerVoxel*VoxelSizeCm;
const double Far8Cm=static_cast<double>(Sample.Far8)/SubvoxelsPerVoxel*VoxelSizeCm;
const double NearCm=NearVoxel*VoxelSizeCm;
MaximumFar7ErrorCm=std::max(MaximumFar7ErrorCm,std::abs(Far7Cm-NearCm));
MaximumFar8ErrorCm=std::max(MaximumFar8ErrorCm,std::abs(Far8Cm-NearCm));
MaximumCrossLodErrorCm=std::max(MaximumCrossLodErrorCm,std::abs(Far7Cm-Far8Cm));
MaximumNegativeControlErrorCm=std::max(MaximumNegativeControlErrorCm,
std::abs((Far8Cm+NegativeDatumOffsetCm)-NearCm));
++Compared;
}
const bool bNegativeControlDetected=Compared>0&&MaximumNegativeControlErrorCm>ToleranceCm;
if(Compared<32||MaximumFar7ErrorCm>ToleranceCm||MaximumFar8ErrorCm>ToleranceCm
||MaximumCrossLodErrorCm>ToleranceCm||!bNegativeControlDetected)
{
OutError=FString::Printf(TEXT("Far-horizon agreement failed: points=%llu far7_cm=%.4f far8_cm=%.4f cross_lod_cm=%.4f tolerance_cm=%.2f negative_control_cm=%.4f detected=%d."),
static_cast<unsigned long long>(Compared),MaximumFar7ErrorCm,MaximumFar8ErrorCm,
MaximumCrossLodErrorCm,ToleranceCm,MaximumNegativeControlErrorCm,bNegativeControlDetected?1:0);
return false;
}
UE_LOG(LogTemp,Display,
TEXT("[VoxelForgeFarHorizonAgreement] status=PASS lod_pair=7/8 shared_points=%llu source=%s max_far7_near_cm=%.4f max_far8_near_cm=%.4f max_cross_lod_cm=%.4f tolerance_cm=%.2f near_source=exact_density_root negative_control=100cm_datum_offset max_error_cm=%.4f detected=1"),
static_cast<unsigned long long>(Compared),
bUsedWalkRouteGrid?TEXT("staged_walk_route_grid"):TEXT("desired_tile_overlap"),
MaximumFar7ErrorCm,MaximumFar8ErrorCm,MaximumCrossLodErrorCm,
ToleranceCm,MaximumNegativeControlErrorCm);
return true;
}
bool RunCoreFarCapTileCensus(VoxelForgeCore::World& World,
const VoxelForgeCore::WorldAssemblyManifest& Assembly,FString& OutError)
{
OutError.Reset();
constexpr std::int64_t SubvoxelsPerVoxel=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
constexpr std::int64_t ProfileGridStep=128;
using FTileId=std::array<std::int32_t,4>;
using FXYLod=std::array<std::int32_t,3>;
const auto IdFor=[](const VoxelForgeCore::TileKey& Key)
{return FTileId{Key.coordinate.x,Key.coordinate.y,Key.coordinate.z,Key.lod};};
const auto FloorDiv64=[](std::int64_t Value,std::int64_t Divisor)
{
const std::int64_t Quotient=Value/Divisor, Remainder=Value%Divisor;
return Quotient-((Remainder!=0&&Remainder<0)?1:0);
};
const VoxelForgeCore::WorldAssemblyStratumPlanV1* Plan=nullptr;
for(const auto& Candidate:Assembly.plan.strates)
if(!Candidate.route.empty()) {Plan=&Candidate;break;}
if(Plan==nullptr)
{OutError=TEXT("Far-cap census has no SurfaceWorld walk route.");return false;}
const VoxelForgeCore::Heightfield2D Field(Assembly.recipe,*Plan);
if(!Field.isValid())
{OutError=TEXT("Far-cap census received an invalid SurfaceWorld heightfield.");return false;}
const auto& WalkStart=Plan->route.front();
const VoxelForgeCore::Vec3d Camera{
static_cast<double>(WalkStart.positionSubvoxels[0])/SubvoxelsPerVoxel,
static_cast<double>(WalkStart.positionSubvoxels[1])/SubvoxelsPerVoxel,
static_cast<double>(WalkStart.positionSubvoxels[2])/SubvoxelsPerVoxel};
const std::vector<VoxelForgeCore::TileKey> WalkKeys=World.desiredTiles(Camera);
std::set<FTileId> Desired;
std::set<FXYLod> FarXYTiles;
double ClipMaxZ=-std::numeric_limits<double>::infinity();
for(const auto& Key:WalkKeys)
{
Desired.insert(IdFor(Key));
const double Extent=static_cast<double>(32<<Key.lod);
ClipMaxZ=std::max(ClipMaxZ,(static_cast<double>(Key.coordinate.z)+1.0)*Extent);
if(Key.lod>=5&&Key.lod<=8)
FarXYTiles.insert({Key.coordinate.x,Key.coordinate.y,Key.lod});
}
std::set<FTileId> CapBands;
std::uint64_t ProfileGridSamples=0, ProfileSamplesWithoutRoof=0;
std::set<FXYLod> FarXYTilesWithoutRoof;
for(const FXYLod& XY:FarXYTiles)
{
const std::int32_t Lod=XY[2];
const std::int64_t Extent=static_cast<std::int64_t>(32)<<Lod;
const std::int64_t OriginX=static_cast<std::int64_t>(XY[0])*Extent;
const std::int64_t OriginY=static_cast<std::int64_t>(XY[1])*Extent;
for(std::int64_t Y=0;Y<=Extent;Y+=ProfileGridStep)
for(std::int64_t X=0;X<=Extent;X+=ProfileGridStep)
{
++ProfileGridSamples;
const auto Column=Field.point((OriginX+X)*SubvoxelsPerVoxel,
(OriginY+Y)*SubvoxelsPerVoxel,0);
const std::int64_t FarCap=Field.farSurfaceCapHeightSubvoxels(
(OriginX+X)*SubvoxelsPerVoxel,(OriginY+Y)*SubvoxelsPerVoxel);
if(FarCap<=Column.groundHeightSubvoxels)
{
++ProfileSamplesWithoutRoof;
FarXYTilesWithoutRoof.insert(XY);
continue;
}
const std::int64_t Band=FloorDiv64(FarCap,
Extent*SubvoxelsPerVoxel);
if(Band>=MIN_int32&&Band<=MAX_int32)
CapBands.insert({XY[0],XY[1],static_cast<std::int32_t>(Band),Lod});
if(FarCap%(Extent*SubvoxelsPerVoxel)==0
&&Band>MIN_int32)
CapBands.insert({XY[0],XY[1],static_cast<std::int32_t>(Band-1),Lod});
}
}
std::uint64_t RequestedCapBands=0, EmittedCapBands=0, RequestedWithoutTriangles=0;
std::uint64_t CulledCapBands=0;
std::map<std::int32_t,std::array<std::uint64_t,4>> ByLod;
VoxelForgeCore::TileGenerationOptions Options;
std::string CoreError;
struct FCapAgreementSample
{ std::int64_t XSub=0,YSub=0;double FarZVoxel=0.0; };
using FCapXYKey=std::pair<std::int64_t,std::int64_t>;
std::set<FCapXYKey> CapAgreementSeen;
std::vector<FCapAgreementSample> CapAgreementSamples;
CapAgreementSamples.reserve(128);
const double VoxelSizeCm=static_cast<double>(World.configuration().voxelSizeMicrons)/10000.0;
constexpr std::int64_t CapGridPeriod=128*SubvoxelsPerVoxel;
for(const FTileId& Id:CapBands)
{
const std::int32_t Lod=Id[3];
auto& Counts=ByLod[Lod];
++Counts[0];
if(Desired.find(Id)==Desired.end())
{
++CulledCapBands;++Counts[1];continue;
}
++RequestedCapBands;
VoxelForgeCore::TileKey Key{{Id[0],Id[1],Id[2]},Lod};
const auto Mesh=World.generateTile(Key,Options,CoreError);
if(!Mesh)
{
OutError=FString::Printf(TEXT("Far-cap census tile generation failed at (%d,%d,%d) LOD%d: %s"),
Id[0],Id[1],Id[2],Lod,UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
if(Mesh->diagnostics.horizonCapTriangles>0)
{
++EmittedCapBands;++Counts[2];
const std::size_t CapIndexStart=Mesh->indices.size()
-static_cast<std::size_t>(Mesh->diagnostics.horizonCapTriangles)*3u;
for(std::size_t Index=CapIndexStart;Index<Mesh->indices.size()
&&CapAgreementSamples.size()<128;++Index)
{
const std::uint32_t VertexIndex=Mesh->indices[Index];
if(VertexIndex>=Mesh->vertices.size())continue;
const auto& Vertex=Mesh->vertices[VertexIndex];
if(Vertex.normal.z>=-0.35)continue;
const std::int64_t XSub=static_cast<std::int64_t>(std::llround(
Vertex.position.x/VoxelSizeCm*SubvoxelsPerVoxel));
const std::int64_t YSub=static_cast<std::int64_t>(std::llround(
Vertex.position.y/VoxelSizeCm*SubvoxelsPerVoxel));
if(XSub%CapGridPeriod!=0||YSub%CapGridPeriod!=0)continue;
if(!CapAgreementSeen.insert({XSub,YSub}).second)continue;
CapAgreementSamples.push_back({XSub,YSub,
Vertex.position.z/VoxelSizeCm});
}
}
else
{++RequestedWithoutTriangles;++Counts[3];}
}
constexpr double CapAgreementToleranceCm=2.0;
constexpr double CapNegativeDatumOffsetCm=100.0;
const auto FindNearCapRoot=[&](std::int64_t XSub,std::int64_t YSub,
double ProfileCapVoxel,double& OutRootVoxel)
{
const double X=static_cast<double>(XSub)/SubvoxelsPerVoxel;
const double Y=static_cast<double>(YSub)/SubvoxelsPerVoxel;
for(double Radius=4.0;Radius<=4096.0;Radius*=2.0)
{
double Lo=ProfileCapVoxel-Radius,Hi=ProfileCapVoxel+Radius;
if(!(World.densityAt({X,Y,Lo})>=0.0f&&World.densityAt({X,Y,Hi})<0.0f))continue;
for(int Iteration=0;Iteration<16;++Iteration)
{
const double Mid=(Lo+Hi)*0.5;
if(World.densityAt({X,Y,Mid})>=0.0f)Lo=Mid;else Hi=Mid;
}
OutRootVoxel=(Lo+Hi)*0.5;
return true;
}
return false;
};
double MaximumCapAgreementErrorCm=0.0,MaximumCapNegativeControlErrorCm=0.0;
std::uint64_t CapAgreementCompared=0;
for(const FCapAgreementSample& Sample:CapAgreementSamples)
{
const auto Column=Field.point(Sample.XSub,Sample.YSub,0);
const std::int64_t FarCap=Field.farSurfaceCapHeightSubvoxels(
Sample.XSub,Sample.YSub);
if(FarCap<=Column.groundHeightSubvoxels)continue;
double NearRootVoxel=0.0;
if(!FindNearCapRoot(Sample.XSub,Sample.YSub,
static_cast<double>(FarCap)/SubvoxelsPerVoxel,NearRootVoxel))continue;
const double FarCapCm=Sample.FarZVoxel*VoxelSizeCm;
const double NearCapCm=NearRootVoxel*VoxelSizeCm;
MaximumCapAgreementErrorCm=std::max(MaximumCapAgreementErrorCm,
std::abs(FarCapCm-NearCapCm));
MaximumCapNegativeControlErrorCm=std::max(MaximumCapNegativeControlErrorCm,
std::abs(FarCapCm+CapNegativeDatumOffsetCm-NearCapCm));
++CapAgreementCompared;
}
const bool bCapNegativeControlDetected=CapAgreementCompared>0
&&MaximumCapNegativeControlErrorCm>CapAgreementToleranceCm;
UE_LOG(LogTemp,Display,
TEXT("[VoxelForgeFarCapAgreement] status=%s shared_points=%llu max_far_near_cm=%.4f tolerance_cm=%.2f near_source=exact_density_root negative_control=100cm_datum_offset max_error_cm=%.4f detected=%d"),
CapAgreementCompared>=32&&MaximumCapAgreementErrorCm<=CapAgreementToleranceCm
&&bCapNegativeControlDetected?TEXT("PASS"):TEXT("FAIL"),
static_cast<unsigned long long>(CapAgreementCompared),MaximumCapAgreementErrorCm,
CapAgreementToleranceCm,MaximumCapNegativeControlErrorCm,bCapNegativeControlDetected?1:0);
if(CapAgreementCompared<32||MaximumCapAgreementErrorCm>CapAgreementToleranceCm
||!bCapNegativeControlDetected)
{
OutError=FString::Printf(TEXT("Far-cap agreement failed: points=%llu max_error_cm=%.4f tolerance_cm=%.2f negative_control_cm=%.4f detected=%d."),
static_cast<unsigned long long>(CapAgreementCompared),MaximumCapAgreementErrorCm,
CapAgreementToleranceCm,MaximumCapNegativeControlErrorCm,bCapNegativeControlDetected?1:0);
return false;
}
// Les rayons couvrent l'hemisphere superieur depuis chaque point de marche.
// Cast into the upper view hemisphere from every authored walk point.
constexpr double Pi=3.14159265358979323846;
constexpr std::array<double,8> AzimuthDegrees={0.0,45.0,90.0,135.0,180.0,225.0,270.0,315.0};
constexpr std::array<double,6> ElevationDegrees={15.0,30.0,45.0,60.0,75.0,90.0};
constexpr double RayStepVoxels=128.0;
double ClipMinX=std::numeric_limits<double>::infinity();
double ClipMinY=std::numeric_limits<double>::infinity();
double ClipMaxX=-std::numeric_limits<double>::infinity();
double ClipMaxY=-std::numeric_limits<double>::infinity();
for(const FXYLod& XY:FarXYTiles)
{
const double Extent=static_cast<double>(32<<XY[2]);
ClipMinX=std::min(ClipMinX,static_cast<double>(XY[0])*Extent);
ClipMinY=std::min(ClipMinY,static_cast<double>(XY[1])*Extent);
ClipMaxX=std::max(ClipMaxX,(static_cast<double>(XY[0])+1.0)*Extent);
ClipMaxY=std::max(ClipMaxY,(static_cast<double>(XY[1])+1.0)*Extent);
}
enum class ECapRayTileResult : std::uint8_t
{ EmittedHit, CulledByClipmap, RequestedWithoutTriangles, EmittedWithoutHit };
struct FCapRayTileGeometry
{
std::vector<std::array<VoxelForgeCore::Vec3d,3>> Triangles;
};
std::uint64_t RayCount=0, RayHits=0, RayMissedClipEdge=0;
std::uint64_t RayMissedNoRoof=0, RayMissedCulledByClipmap=0;
std::uint64_t RayMissedRequestedWithoutTriangles=0, RayMissedEmittedWithoutHit=0;
std::uint64_t RayStartedAboveCap=0;
std::uint64_t RayProfileCrossings=0,RayProfileRoofCrossings=0,RayProfileRoofWithoutMeshHit=0;
std::array<std::array<std::uint64_t,6>,6> RayResultsByElevation{};
std::vector<double> RayHitDistancesMeters;
RayHitDistancesMeters.reserve(Plan->route.size()*AzimuthDegrees.size()*ElevationDegrees.size());
std::map<FTileId,FCapRayTileGeometry> RayTileGeometryCache;
const auto GetCapRayTileGeometry=[&](const FTileId& Id)->const FCapRayTileGeometry*
{
auto Found=RayTileGeometryCache.find(Id);
if(Found!=RayTileGeometryCache.end())return &Found->second;
const VoxelForgeCore::TileKey Key{{Id[0],Id[1],Id[2]},Id[3]};
const auto Mesh=World.generateTile(Key,Options,CoreError);
if(!Mesh)
{
OutError=FString::Printf(TEXT("Far-cap ray tile generation failed at (%d,%d,%d) LOD%d: %s"),
Id[0],Id[1],Id[2],Id[3],UTF8_TO_TCHAR(CoreError.c_str()));
return nullptr;
}
FCapRayTileGeometry Geometry;
Geometry.Triangles.reserve(Mesh->groups.ceilingTriangleCount);
for(std::size_t Index=0;Index+2<Mesh->indices.size();Index+=3)
{
const std::uint32_t IA=Mesh->indices[Index],IB=Mesh->indices[Index+1],IC=Mesh->indices[Index+2];
if(IA>=Mesh->vertices.size()||IB>=Mesh->vertices.size()||IC>=Mesh->vertices.size())continue;
const double AverageNormalZ=(Mesh->vertices[IA].normal.z+Mesh->vertices[IB].normal.z
+Mesh->vertices[IC].normal.z)/3.0;
// Native marching-cubes roofs and refined far caps both face out of the solid
// toward decreasing Z density. Include those ceiling faces at LOD0-8.
// Les plafonds MC natifs et les caps lointains pointent hors du solide, vers -Z.
if(AverageNormalZ>=-0.05)continue;
Geometry.Triangles.push_back({
VoxelForgeCore::Vec3d{Mesh->vertices[IA].position.x/VoxelSizeCm,
Mesh->vertices[IA].position.y/VoxelSizeCm,Mesh->vertices[IA].position.z/VoxelSizeCm},
VoxelForgeCore::Vec3d{Mesh->vertices[IB].position.x/VoxelSizeCm,
Mesh->vertices[IB].position.y/VoxelSizeCm,Mesh->vertices[IB].position.z/VoxelSizeCm},
VoxelForgeCore::Vec3d{Mesh->vertices[IC].position.x/VoxelSizeCm,
Mesh->vertices[IC].position.y/VoxelSizeCm,Mesh->vertices[IC].position.z/VoxelSizeCm}});
}
return &RayTileGeometryCache.emplace(Id,std::move(Geometry)).first->second;
};
const auto Dot3=[](const VoxelForgeCore::Vec3d& A,const VoxelForgeCore::Vec3d& B)
{return A.x*B.x+A.y*B.y+A.z*B.z;};
const auto Sub3=[](const VoxelForgeCore::Vec3d& A,const VoxelForgeCore::Vec3d& B)
{return VoxelForgeCore::Vec3d{A.x-B.x,A.y-B.y,A.z-B.z};};
const auto Cross3=[](const VoxelForgeCore::Vec3d& A,const VoxelForgeCore::Vec3d& B)
{return VoxelForgeCore::Vec3d{A.y*B.z-A.z*B.y,A.z*B.x-A.x*B.z,A.x*B.y-A.y*B.x};};
const auto RayTriangleDistance=[&](const VoxelForgeCore::Vec3d& Origin,
const VoxelForgeCore::Vec3d& Direction,const std::array<VoxelForgeCore::Vec3d,3>& Triangle,
double MinimumDistance,double MaximumDistance,double& OutDistance)
{
const auto Edge1=Sub3(Triangle[1],Triangle[0]);
const auto Edge2=Sub3(Triangle[2],Triangle[0]);
const auto P=Cross3(Direction,Edge2);
const double Determinant=Dot3(Edge1,P);
if(std::abs(Determinant)<1.0e-12)return false;
const double Inverse=1.0/Determinant;
const auto T=Sub3(Origin,Triangle[0]);
const double U=Dot3(T,P)*Inverse;
if(U< -1.0e-8||U>1.0+1.0e-8)return false;
const auto Q=Cross3(T,Edge1);
const double V=Dot3(Direction,Q)*Inverse;
if(V< -1.0e-8||U+V>1.0+1.0e-8)return false;
const double Distance=Dot3(Edge2,Q)*Inverse;
if(Distance<MinimumDistance-1.0e-6||Distance>MaximumDistance+1.0e-6)return false;
OutDistance=Distance;
return true;
};
const auto TraceEmittedCapRay=[&](const VoxelForgeCore::Vec3d& Origin,
const VoxelForgeCore::Vec3d& Direction,double MaximumDistance,double& OutHitDistance)
{
bool bFoundRequestedKey=false,bFoundEmptyRequestedKey=false,bFoundGeometryWithoutHit=false;
bool bFoundHit=false;
for(const FTileId& Id:Desired)
{
const double Extent=static_cast<double>(32<<Id[3]);
const std::array<double,3> BoundsMin={static_cast<double>(Id[0])*Extent,
static_cast<double>(Id[1])*Extent,static_cast<double>(Id[2])*Extent};
const std::array<double,3> BoundsMax={BoundsMin[0]+Extent,BoundsMin[1]+Extent,
BoundsMin[2]+Extent};
double TileEnter=0.0,TileExit=MaximumDistance;
bool bIntersects=true;
for(std::size_t Axis=0;Axis<3;++Axis)
{
const double RayOrigin=Axis==0?Origin.x:(Axis==1?Origin.y:Origin.z);
const double RayDirection=Axis==0?Direction.x:(Axis==1?Direction.y:Direction.z);
if(std::abs(RayDirection)<1.0e-12)
{
if(RayOrigin<BoundsMin[Axis]||RayOrigin>BoundsMax[Axis])
{bIntersects=false;break;}
continue;
}
double Near=(BoundsMin[Axis]-RayOrigin)/RayDirection;
double Far=(BoundsMax[Axis]-RayOrigin)/RayDirection;
if(Near>Far)std::swap(Near,Far);
TileEnter=std::max(TileEnter,Near);
TileExit=std::min(TileExit,Far);
if(TileEnter>TileExit){bIntersects=false;break;}
}
if(!bIntersects||TileExit<0.0||TileEnter>MaximumDistance)continue;
bFoundRequestedKey=true;
const FCapRayTileGeometry* Geometry=GetCapRayTileGeometry(Id);
if(Geometry==nullptr)continue;
if(Geometry->Triangles.empty())
{bFoundEmptyRequestedKey=true;continue;}
bFoundGeometryWithoutHit=true;
for(const auto& Triangle:Geometry->Triangles)
{
double HitDistance=0.0;
if(!RayTriangleDistance(Origin,Direction,Triangle,
std::max(0.0,TileEnter),std::min(MaximumDistance,TileExit),HitDistance))continue;
if(!bFoundHit||HitDistance<OutHitDistance)OutHitDistance=HitDistance;
bFoundHit=true;
}
if(!OutError.IsEmpty())return ECapRayTileResult::RequestedWithoutTriangles;
}
if(bFoundHit)return ECapRayTileResult::EmittedHit;
if(bFoundRequestedKey&&bFoundGeometryWithoutHit)return ECapRayTileResult::EmittedWithoutHit;
if(bFoundRequestedKey&&bFoundEmptyRequestedKey)return ECapRayTileResult::RequestedWithoutTriangles;
return ECapRayTileResult::CulledByClipmap;
};
for(std::size_t RouteIndex=0;RouteIndex<Plan->route.size();++RouteIndex)
{
const auto& RoutePoint=Plan->route[RouteIndex];
const double StartX=static_cast<double>(RoutePoint.positionSubvoxels[0])/SubvoxelsPerVoxel;
const double StartY=static_cast<double>(RoutePoint.positionSubvoxels[1])/SubvoxelsPerVoxel;
const double StartZ=static_cast<double>(RoutePoint.positionSubvoxels[2])/SubvoxelsPerVoxel;
const auto StartColumn=Field.point(RoutePoint.positionSubvoxels[0],
RoutePoint.positionSubvoxels[1],0);
const double StartCap=static_cast<double>(Field.farSurfaceCapHeightSubvoxels(
RoutePoint.positionSubvoxels[0],RoutePoint.positionSubvoxels[1]))/SubvoxelsPerVoxel;
for(const double Elevation:ElevationDegrees)
for(const double Azimuth:AzimuthDegrees)
{
++RayCount;
const std::size_t ElevationIndex=static_cast<std::size_t>(
std::find(ElevationDegrees.begin(),ElevationDegrees.end(),Elevation)
-ElevationDegrees.begin());
if(!(StartCap>StartZ))
{++RayStartedAboveCap;++RayMissedNoRoof;++RayResultsByElevation[ElevationIndex][4];continue;}
const double ElevationRadians=Elevation*Pi/180.0;
const double AzimuthRadians=Azimuth*Pi/180.0;
const double DX=std::cos(ElevationRadians)*std::cos(AzimuthRadians);
const double DY=std::cos(ElevationRadians)*std::sin(AzimuthRadians);
const double DZ=std::sin(ElevationRadians);
double EdgeDistance=std::numeric_limits<double>::infinity();
if(DX>1.0e-9)EdgeDistance=std::min(EdgeDistance,(ClipMaxX-StartX)/DX);
else if(DX< -1.0e-9)EdgeDistance=std::min(EdgeDistance,(ClipMinX-StartX)/DX);
if(DY>1.0e-9)EdgeDistance=std::min(EdgeDistance,(ClipMaxY-StartY)/DY);
else if(DY< -1.0e-9)EdgeDistance=std::min(EdgeDistance,(ClipMinY-StartY)/DY);
if(DZ>1.0e-9)
EdgeDistance=std::min(EdgeDistance,(ClipMaxZ-StartZ)/DZ);
if(!std::isfinite(EdgeDistance)||EdgeDistance<=0.0)
{++RayMissedClipEdge;++RayResultsByElevation[ElevationIndex][5];continue;}
bool bProfileCrossing=false,bProfileRoofAtCrossing=false;
double ProfileHitDistance=std::numeric_limits<double>::infinity();
double ProfileHitX=0.0,ProfileHitY=0.0,ProfileHitZ=0.0;
std::int64_t ProfileHitCapSubvoxels=0;
double PreviousDistance=0.0,PreviousDelta=StartZ-StartCap;
for(double Distance=std::min(RayStepVoxels,EdgeDistance);;)
{
const double X=StartX+DX*Distance,Y=StartY+DY*Distance;
const double Z=StartZ+DZ*Distance;
const std::int64_t CapSubvoxels=Field.farSurfaceCapHeightSubvoxels(
static_cast<std::int64_t>(std::llround(X*SubvoxelsPerVoxel)),
static_cast<std::int64_t>(std::llround(Y*SubvoxelsPerVoxel)));
const double Cap=static_cast<double>(CapSubvoxels)/SubvoxelsPerVoxel;
const double Delta=Z-Cap;
if(PreviousDelta<0.0&&Delta>=0.0)
{
const double Fraction=-PreviousDelta/(Delta-PreviousDelta);
ProfileHitDistance=PreviousDistance+(Distance-PreviousDistance)*Fraction;
const double HitX=StartX+DX*ProfileHitDistance;
const double HitY=StartY+DY*ProfileHitDistance;
const auto HitColumn=Field.point(
static_cast<std::int64_t>(std::llround(HitX*SubvoxelsPerVoxel)),
static_cast<std::int64_t>(std::llround(HitY*SubvoxelsPerVoxel)),0);
const std::int64_t HitCapSubvoxels=Field.farSurfaceCapHeightSubvoxels(
static_cast<std::int64_t>(std::llround(HitX*SubvoxelsPerVoxel)),
static_cast<std::int64_t>(std::llround(HitY*SubvoxelsPerVoxel)));
bProfileCrossing=true;
bProfileRoofAtCrossing=HitCapSubvoxels>HitColumn.groundHeightSubvoxels;
ProfileHitX=HitX;ProfileHitY=HitY;
ProfileHitZ=StartZ+DZ*ProfileHitDistance;
ProfileHitCapSubvoxels=HitCapSubvoxels;
++RayProfileCrossings;
if(bProfileRoofAtCrossing)++RayProfileRoofCrossings;
break;
}
PreviousDistance=Distance;PreviousDelta=Delta;
if(Distance>=EdgeDistance)break;
Distance=std::min(Distance+RayStepVoxels,EdgeDistance);
}
const VoxelForgeCore::Vec3d RayOrigin{StartX,StartY,StartZ};
const VoxelForgeCore::Vec3d RayDirection{DX,DY,DZ};
double MeshHitDistance=std::numeric_limits<double>::infinity();
const ECapRayTileResult TileResult=TraceEmittedCapRay(
RayOrigin,RayDirection,EdgeDistance,MeshHitDistance);
if(!OutError.IsEmpty())return false;
switch(TileResult)
{
case ECapRayTileResult::EmittedHit:
++RayHits;++RayResultsByElevation[ElevationIndex][0];
RayHitDistancesMeters.push_back(MeshHitDistance*VoxelSizeCm/100.0);break;
case ECapRayTileResult::CulledByClipmap:
++RayMissedCulledByClipmap;++RayResultsByElevation[ElevationIndex][1];break;
case ECapRayTileResult::RequestedWithoutTriangles:
++RayMissedRequestedWithoutTriangles;++RayResultsByElevation[ElevationIndex][2];break;
case ECapRayTileResult::EmittedWithoutHit:
{
++RayMissedEmittedWithoutHit;++RayResultsByElevation[ElevationIndex][3];
if(bProfileCrossing&&bProfileRoofAtCrossing)++RayProfileRoofWithoutMeshHit;
std::uint32_t FarRequestedMask=0,FarEmittedMask=0;
std::uint32_t RootRequestedMask=0,RootEmittedMask=0;
std::uint64_t RootDownwardTriangles=0;
if(bProfileCrossing&&bProfileRoofAtCrossing)
{
for(std::int32_t Lod=0;Lod<=8;++Lod)
{
const std::int64_t Extent=static_cast<std::int64_t>(32)<<Lod;
const std::int64_t TileX=FloorDiv64(static_cast<std::int64_t>(std::floor(ProfileHitX)),Extent);
const std::int64_t TileY=FloorDiv64(static_cast<std::int64_t>(std::floor(ProfileHitY)),Extent);
const std::int64_t TileZ=FloorDiv64(ProfileHitCapSubvoxels,Extent*SubvoxelsPerVoxel);
const FTileId Id{static_cast<std::int32_t>(TileX),static_cast<std::int32_t>(TileY),
static_cast<std::int32_t>(TileZ),Lod};
if(Desired.find(Id)==Desired.end())continue;
RootRequestedMask|=1u<<static_cast<std::uint32_t>(Lod);
const FCapRayTileGeometry* Geometry=GetCapRayTileGeometry(Id);
if(Geometry!=nullptr)
{
RootDownwardTriangles+=Geometry->Triangles.size();
if(!Geometry->Triangles.empty())
RootEmittedMask|=1u<<static_cast<std::uint32_t>(Lod);
}
if(Lod>=5)
{
FarRequestedMask|=1u<<static_cast<std::uint32_t>(Lod-5);
if(Geometry!=nullptr&&!Geometry->Triangles.empty())
FarEmittedMask|=1u<<static_cast<std::uint32_t>(Lod-5);
}
}
}
UE_LOG(LogTemp,Display,
TEXT("[VoxelForgeFarCapRayMiss] route_point=%llu elevation_deg=%.0f azimuth_deg=%.0f profile_crossing=%d profile_roof=%d profile_distance_vox=%.1f clip_distance_vox=%.1f profile_hit_vox=(%.1f,%.1f,%.1f) root_lod_requested_mask=0x%X root_lod_emitted_mask=0x%X root_downward_triangles=%llu far_requested_mask=0x%X far_emitted_mask=0x%X"),
static_cast<unsigned long long>(RouteIndex),Elevation,Azimuth,
bProfileCrossing?1:0,bProfileRoofAtCrossing?1:0,
ProfileHitDistance,EdgeDistance,ProfileHitX,ProfileHitY,ProfileHitZ,
RootRequestedMask,RootEmittedMask,
static_cast<unsigned long long>(RootDownwardTriangles),
FarRequestedMask,FarEmittedMask);
break;
}
}
}
}
const auto WalkColumn=Field.point(WalkStart.positionSubvoxels[0],
WalkStart.positionSubvoxels[1],0);
const std::int64_t WalkFarCap=Field.farSurfaceCapHeightSubvoxels(
WalkStart.positionSubvoxels[0],WalkStart.positionSubvoxels[1]);
std::size_t RayTriangleCount=0;
for(const auto& [Id,Geometry]:RayTileGeometryCache)
{ (void)Id;RayTriangleCount+=Geometry.Triangles.size(); }
std::sort(RayHitDistancesMeters.begin(),RayHitDistancesMeters.end());
const double HitDistanceP50Meters=RayHitDistancesMeters.empty()?0.0:
RayHitDistancesMeters[(RayHitDistancesMeters.size()-1)*50/100];
const double HitDistanceP95Meters=RayHitDistancesMeters.empty()?0.0:
RayHitDistancesMeters[(RayHitDistancesMeters.size()-1)*95/100];
const double HitDistanceMaximumMeters=RayHitDistancesMeters.empty()?0.0:
RayHitDistancesMeters.back();
UE_LOG(LogTemp,Display,
TEXT("[VoxelForgeFarCapCensus] source=surface_route_front route_points=%llu walk_keys=%llu far_xy_tiles=%llu cap_bands=%llu requested=%llu emitted=%llu requested_without_triangles=%llu culled_by_clipmap=%llu grid_samples=%llu samples_without_roof=%llu xy_tiles_with_roof_gaps=%llu walk_z_vox=%.3f ground_z_vox=%.3f cap_z_vox=%.3f"),
static_cast<unsigned long long>(Plan->route.size()),
static_cast<unsigned long long>(WalkKeys.size()),
static_cast<unsigned long long>(FarXYTiles.size()),
static_cast<unsigned long long>(CapBands.size()),
static_cast<unsigned long long>(RequestedCapBands),
static_cast<unsigned long long>(EmittedCapBands),
static_cast<unsigned long long>(RequestedWithoutTriangles),
static_cast<unsigned long long>(CulledCapBands),
static_cast<unsigned long long>(ProfileGridSamples),
static_cast<unsigned long long>(ProfileSamplesWithoutRoof),
static_cast<unsigned long long>(FarXYTilesWithoutRoof.size()),
Camera.z,static_cast<double>(WalkColumn.groundHeightSubvoxels)/SubvoxelsPerVoxel,
static_cast<double>(WalkFarCap)/SubvoxelsPerVoxel);
const double CoveragePercent=RayCount>0?100.0*static_cast<double>(RayHits)/RayCount:0.0;
UE_LOG(LogTemp,Display,
TEXT("[VoxelForgeFarCapRayCoverage] rays=%llu mesh_hits=%llu coverage_pct=%.3f missed_clip_edge=%llu missed_no_roof=%llu missed_cap_key_culled=%llu missed_requested_without_triangles=%llu missed_emitted_without_mesh_hit=%llu start_above_cap=%llu profile_crossings=%llu profile_roof_crossings=%llu profile_roof_without_mesh_hit=%llu traced_tiles=%llu traced_downward_triangles=%llu first_hit_distance_m_p50=%.2f_p95=%.2f_max=%.2f elevations_deg=15,30,45,60,75,90 azimuths=8 ray_method=all_downward_emitted_triangles_with_tile_aabb clip_edge=desired_far_xy_bounds lods_checked=0-8"),
static_cast<unsigned long long>(RayCount),static_cast<unsigned long long>(RayHits),CoveragePercent,
static_cast<unsigned long long>(RayMissedClipEdge),
static_cast<unsigned long long>(RayMissedNoRoof),
static_cast<unsigned long long>(RayMissedCulledByClipmap),
static_cast<unsigned long long>(RayMissedRequestedWithoutTriangles),
static_cast<unsigned long long>(RayMissedEmittedWithoutHit),
static_cast<unsigned long long>(RayStartedAboveCap),
static_cast<unsigned long long>(RayProfileCrossings),
static_cast<unsigned long long>(RayProfileRoofCrossings),
static_cast<unsigned long long>(RayProfileRoofWithoutMeshHit),
static_cast<unsigned long long>(RayTileGeometryCache.size()),
static_cast<unsigned long long>(RayTriangleCount),HitDistanceP50Meters,
HitDistanceP95Meters,HitDistanceMaximumMeters);
for(std::size_t Index=0;Index<ElevationDegrees.size();++Index)
UE_LOG(LogTemp,Display,
TEXT("[VoxelForgeFarCapRayElevation] degrees=%.0f rays=%llu mesh_hits=%llu key_culled=%llu requested_empty=%llu emitted_without_mesh_hit=%llu no_roof_or_above=%llu missed_clip_edge=%llu"),
ElevationDegrees[Index],
static_cast<unsigned long long>(AzimuthDegrees.size()*Plan->route.size()),
static_cast<unsigned long long>(RayResultsByElevation[Index][0]),
static_cast<unsigned long long>(RayResultsByElevation[Index][1]),
static_cast<unsigned long long>(RayResultsByElevation[Index][2]),
static_cast<unsigned long long>(RayResultsByElevation[Index][3]),
static_cast<unsigned long long>(RayResultsByElevation[Index][4]),
static_cast<unsigned long long>(RayResultsByElevation[Index][5]));
if(RayHits!=RayCount)
{
OutError=FString::Printf(TEXT("Far-cap ray coverage failed: rays=%llu mesh_hits=%llu clip_edge=%llu no_roof=%llu culled_keys=%llu requested_empty=%llu emitted_without_hit=%llu."),
static_cast<unsigned long long>(RayCount),static_cast<unsigned long long>(RayHits),
static_cast<unsigned long long>(RayMissedClipEdge),
static_cast<unsigned long long>(RayMissedNoRoof),
static_cast<unsigned long long>(RayMissedCulledByClipmap),
static_cast<unsigned long long>(RayMissedRequestedWithoutTriangles),
static_cast<unsigned long long>(RayMissedEmittedWithoutHit));
return false;
}
for(const auto& [Lod,Counts]:ByLod)
UE_LOG(LogTemp,Display,
TEXT("[VoxelForgeFarCapCensusLOD] lod=%d cap_bands=%llu culled=%llu emitted=%llu requested_without_triangles=%llu"),
Lod,static_cast<unsigned long long>(Counts[0]),
static_cast<unsigned long long>(Counts[1]),
static_cast<unsigned long long>(Counts[2]),
static_cast<unsigned long long>(Counts[3]));
return true;
}
// Paired engine timings include preparation and derived shells, with canonical output hashes.
// Mesures moteur appariees : preparation et coques derivees incluses, avec hashes canoniques.
bool RunCoreSharedFeatureCostAudit(VoxelForgeCore::World& World,
const std::vector<VoxelForgeCore::TileKey>& Keys,const TCHAR* Label,
const VoxelForgeCore::WorldAssemblyManifest* Assembly,FString& OutError)
{
if(Assembly!=nullptr&&FCString::Stricmp(Label,TEXT("surface"))==0)
{
struct FProbeColumn
{
std::int64_t Ground=0;
std::int64_t Cap=0;
};
const auto P95=[](TArray<double> Values)
{ Values.Sort(); return Values.IsEmpty() ? 0.0 : Values[FMath::Clamp(
FMath::CeilToInt(Values.Num()*0.95f)-1,0,Values.Num()-1)]; };
const auto MaxValue=[](const TArray<double>& Values)
{ return Values.IsEmpty() ? 0.0 : *std::max_element(Values.GetData(),Values.GetData()+Values.Num()); };
const std::int32_t TileVoxelCount=static_cast<std::int32_t>(
World.configuration().tileVoxelCount);
constexpr std::int64_t Subvoxels=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
constexpr double CoreVoxelSizeCm=25.0;
for(const std::int32_t LOD:{0,1,3,4,5,8})
{
for(const std::int32_t Stride:{2,4})
{
const std::int32_t Cells=LOD<World.configuration().fullResolutionLodCount
?TileVoxelCount:std::max(1,World.configuration().coarseTileCells);
TArray<double> GroundErrorsCm,CapErrorsCm,BaselineColumnMs,CandidateColumnMs;
std::uint64_t BaselineColumns=0,CandidateColumns=0,FiniteFeatureTiles=0;
std::int32_t Tiles=0;
for(const auto& Key:Keys)
{
if(Key.lod!=LOD||Tiles>=2)continue;
const std::int64_t TileExtent=static_cast<std::int64_t>(TileVoxelCount)<<LOD;
const std::int64_t TileStep=TileExtent/Cells;
const std::int64_t OriginX=static_cast<std::int64_t>(Key.coordinate.x)*TileExtent;
const std::int64_t OriginY=static_cast<std::int64_t>(Key.coordinate.y)*TileExtent;
const std::int64_t OriginZ=static_cast<std::int64_t>(Key.coordinate.z)*TileExtent;
const std::int64_t TileMinimumZ=OriginZ;
const std::int64_t TileMaximumZ=OriginZ+TileExtent;
const auto PlanIt=std::find_if(Assembly->plan.strates.begin(),
Assembly->plan.strates.end(),[TileMinimumZ,TileMaximumZ,TileVoxelCount](const auto& Plan)
{
const std::int64_t PlanMinimumZ=static_cast<std::int64_t>(
std::min(Plan.bottomChunkZ,Plan.topChunkZ))*TileVoxelCount;
const std::int64_t PlanMaximumZ=(static_cast<std::int64_t>(
std::max(Plan.bottomChunkZ,Plan.topChunkZ))+1)*TileVoxelCount;
return TileMinimumZ<=PlanMaximumZ&&TileMaximumZ>=PlanMinimumZ;
});
if(PlanIt==Assembly->plan.strates.end())continue;
const VoxelForgeCore::Heightfield2D Field(Assembly->recipe,*PlanIt);
if(!Field.isValid())continue;
const std::array<std::int64_t,3> Minimum={
(OriginX-TileStep)*Subvoxels,(OriginY-TileStep)*Subvoxels,
(OriginZ-TileStep)*Subvoxels};
const std::array<std::int64_t,3> Maximum={
(OriginX+TileExtent+TileStep)*Subvoxels,
(OriginY+TileExtent+TileStep)*Subvoxels,
(OriginZ+TileExtent+TileStep)*Subvoxels};
std::vector<VoxelForgeCore::WorldAssemblyFeatureQueryV1> Queries;
Queries.push_back({Field.recipe(),Field.plan(),Field.buildFeatureTileList(Minimum,Maximum)});
const VoxelForgeCore::ScopedWorldAssemblyFeatureQueriesV1 QueryScope{
std::span<const VoxelForgeCore::WorldAssemblyFeatureQueryV1>(Queries)};
const bool bHasFiniteFeature=std::any_of(PlanIt->features.begin(),
PlanIt->features.end(),[&](const auto& Feature)
{
return (Feature.type==2||Feature.type==4)
&&Feature.minimumSubvoxels[0]<=Maximum[0]
&&Feature.maximumSubvoxels[0]>=Minimum[0]
&&Feature.minimumSubvoxels[1]<=Maximum[1]
&&Feature.maximumSubvoxels[1]>=Minimum[1]
&&Feature.minimumSubvoxels[2]<=Maximum[2]
&&Feature.maximumSubvoxels[2]>=Minimum[2];
});
if(bHasFiniteFeature)++FiniteFeatureTiles;
std::vector<FProbeColumn> Exact(static_cast<std::size_t>(Cells+1)*(Cells+1));
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true);
for(std::int32_t Y=0;Y<=Cells;++Y)
for(std::int32_t X=0;X<=Cells;++X)
{
const auto Sample=Field.point((OriginX+X*TileStep)*Subvoxels,
(OriginY+Y*TileStep)*Subvoxels,0);
Exact[static_cast<std::size_t>(Y)*(Cells+1)+X]={
Sample.groundHeightSubvoxels,Sample.capHeightSubvoxels};
}
const auto& BaselineStats=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
BaselineColumnMs.Add(BaselineStats.columnComputeMilliseconds);
BaselineColumns+=BaselineStats.columnMisses;
std::vector<std::int32_t> Axis;
for(std::int32_t Index=0;Index<=Cells;Index+=Stride)Axis.push_back(Index);
if(Axis.empty()||Axis.back()!=Cells)Axis.push_back(Cells);
std::vector<FProbeColumn> Coarse(static_cast<std::size_t>(Axis.size())*Axis.size());
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true);
for(std::size_t Y=0;Y<Axis.size();++Y)
for(std::size_t X=0;X<Axis.size();++X)
{
const auto Sample=Field.point((OriginX+Axis[X]*TileStep)*Subvoxels,
(OriginY+Axis[Y]*TileStep)*Subvoxels,0);
Coarse[Y*Axis.size()+X]={Sample.groundHeightSubvoxels,
Sample.capHeightSubvoxels};
}
const auto& CandidateStats=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
CandidateColumnMs.Add(CandidateStats.columnComputeMilliseconds);
CandidateColumns+=CandidateStats.columnMisses;
const auto Bracket=[&Axis](std::int32_t Value)
{
const auto Upper=std::upper_bound(Axis.begin(),Axis.end(),Value);
const std::size_t High=Upper==Axis.end()?Axis.size()-1
:static_cast<std::size_t>(Upper-Axis.begin());
const std::size_t Low=High==0?0:High-1;
return std::pair<std::size_t,std::size_t>{Low,High};
};
const auto Lerp=[](double A,double B,double T){return A+(B-A)*T;};
for(std::int32_t Y=0;Y<=Cells;++Y)
for(std::int32_t X=0;X<=Cells;++X)
{
const auto [X0,X1]=Bracket(X);
const auto [Y0,Y1]=Bracket(Y);
const double TX=Axis[X1]==Axis[X0]?0.0
:static_cast<double>(X-Axis[X0])/(Axis[X1]-Axis[X0]);
const double TY=Axis[Y1]==Axis[Y0]?0.0
:static_cast<double>(Y-Axis[Y0])/(Axis[Y1]-Axis[Y0]);
const auto& C00=Coarse[Y0*Axis.size()+X0];
const auto& C10=Coarse[Y0*Axis.size()+X1];
const auto& C01=Coarse[Y1*Axis.size()+X0];
const auto& C11=Coarse[Y1*Axis.size()+X1];
const double Ground=Lerp(Lerp(static_cast<double>(C00.Ground),
static_cast<double>(C10.Ground),TX),Lerp(static_cast<double>(C01.Ground),
static_cast<double>(C11.Ground),TX),TY);
const double Cap=Lerp(Lerp(static_cast<double>(C00.Cap),
static_cast<double>(C10.Cap),TX),Lerp(static_cast<double>(C01.Cap),
static_cast<double>(C11.Cap),TX),TY);
const auto& Reference=Exact[static_cast<std::size_t>(Y)*(Cells+1)+X];
GroundErrorsCm.Add(std::abs(Ground-Reference.Ground)*CoreVoxelSizeCm/Subvoxels);
CapErrorsCm.Add(std::abs(Cap-Reference.Cap)*CoreVoxelSizeCm/Subvoxels);
}
++Tiles;
}
std::uint64_t GroundOver2Cm=0,CapOver2Cm=0;
for(const double Error:GroundErrorsCm)if(Error>2.0)++GroundOver2Cm;
for(const double Error:CapErrorsCm)if(Error>2.0)++CapOver2Cm;
double BaselineAverageMs=0.0,CandidateAverageMs=0.0;
for(const double Value:BaselineColumnMs)BaselineAverageMs+=Value;
for(const double Value:CandidateColumnMs)CandidateAverageMs+=Value;
if(!BaselineColumnMs.IsEmpty())BaselineAverageMs/=BaselineColumnMs.Num();
if(!CandidateColumnMs.IsEmpty())CandidateAverageMs/=CandidateColumnMs.Num();
UE_LOG(LogTemp,Display,
TEXT("[WorldAssemblerInterpolationProbe] label=%s lod=%d tiles=%d stride_samples=%d exact_column_evals=%llu coarse_column_evals=%llu column_eval_saving_pct=%.2f baseline_column_avg_ms=%.5f coarse_column_avg_ms=%.5f ground_error_p95_cm=%.4f ground_error_max_cm=%.4f ground_over_2cm=%llu/%d cap_error_p95_cm=%.4f cap_error_max_cm=%.4f cap_over_2cm=%llu/%d finite_feature_tiles=%llu fallback=none"),
TEXT("surface"),LOD,Tiles,Stride,
static_cast<unsigned long long>(BaselineColumns),
static_cast<unsigned long long>(CandidateColumns),
BaselineAverageMs>0.0?100.0*(1.0-CandidateAverageMs/BaselineAverageMs):0.0,
BaselineAverageMs,CandidateAverageMs,
P95(GroundErrorsCm),MaxValue(GroundErrorsCm),
static_cast<unsigned long long>(GroundOver2Cm),GroundErrorsCm.Num(),
P95(CapErrorsCm),MaxValue(CapErrorsCm),
static_cast<unsigned long long>(CapOver2Cm),CapErrorsCm.Num(),
static_cast<unsigned long long>(FiniteFeatureTiles));
}
}
for(const std::int32_t LOD:{0,1,3,4,5,8})
for(const std::uint32_t Drop:{1u,2u})
{
const std::int32_t Cells=LOD<World.configuration().fullResolutionLodCount
?TileVoxelCount:std::max(1,World.configuration().coarseTileCells);
TArray<double> GroundErrorsCm,CapErrorsCm,BaselineColumnMs,CandidateColumnMs;
std::uint64_t BaselineColumns=0,CandidateColumns=0,FiniteFeatureTiles=0;
std::int32_t Tiles=0;
for(const auto& Key:Keys)
{
if(Key.lod!=LOD||Tiles>=2)continue;
const std::int64_t TileExtent=static_cast<std::int64_t>(TileVoxelCount)<<LOD;
const std::int64_t TileStep=TileExtent/Cells;
const std::int64_t OriginX=static_cast<std::int64_t>(Key.coordinate.x)*TileExtent;
const std::int64_t OriginY=static_cast<std::int64_t>(Key.coordinate.y)*TileExtent;
const std::int64_t OriginZ=static_cast<std::int64_t>(Key.coordinate.z)*TileExtent;
const std::int64_t TileMinimumZ=OriginZ,TileMaximumZ=OriginZ+TileExtent;
const auto PlanIt=std::find_if(Assembly->plan.strates.begin(),
Assembly->plan.strates.end(),[TileMinimumZ,TileMaximumZ,TileVoxelCount](const auto& Plan)
{
const std::int64_t PlanMinimumZ=static_cast<std::int64_t>(
std::min(Plan.bottomChunkZ,Plan.topChunkZ))*TileVoxelCount;
const std::int64_t PlanMaximumZ=(static_cast<std::int64_t>(
std::max(Plan.bottomChunkZ,Plan.topChunkZ))+1)*TileVoxelCount;
return TileMinimumZ<=PlanMaximumZ&&TileMaximumZ>=PlanMinimumZ;
});
if(PlanIt==Assembly->plan.strates.end())continue;
const VoxelForgeCore::Heightfield2D Field(Assembly->recipe,*PlanIt);
if(!Field.isValid())continue;
const std::array<std::int64_t,3> Minimum={
(OriginX-TileStep)*Subvoxels,(OriginY-TileStep)*Subvoxels,
(OriginZ-TileStep)*Subvoxels};
const std::array<std::int64_t,3> Maximum={
(OriginX+TileExtent+TileStep)*Subvoxels,
(OriginY+TileExtent+TileStep)*Subvoxels,
(OriginZ+TileExtent+TileStep)*Subvoxels};
std::vector<VoxelForgeCore::WorldAssemblyFeatureQueryV1> Queries;
Queries.push_back({Field.recipe(),Field.plan(),Field.buildFeatureTileList(Minimum,Maximum)});
const VoxelForgeCore::ScopedWorldAssemblyFeatureQueriesV1 QueryScope{
std::span<const VoxelForgeCore::WorldAssemblyFeatureQueryV1>(Queries)};
const bool bHasFiniteFeature=std::any_of(PlanIt->features.begin(),
PlanIt->features.end(),[&](const auto& Feature)
{
return (Feature.type==2||Feature.type==4)
&&Feature.minimumSubvoxels[0]<=Maximum[0]
&&Feature.maximumSubvoxels[0]>=Minimum[0]
&&Feature.minimumSubvoxels[1]<=Maximum[1]
&&Feature.maximumSubvoxels[1]>=Minimum[1]
&&Feature.minimumSubvoxels[2]<=Maximum[2]
&&Feature.maximumSubvoxels[2]>=Minimum[2];
});
if(bHasFiniteFeature)++FiniteFeatureTiles;
std::vector<FProbeColumn> Exact(static_cast<std::size_t>(Cells+1)*(Cells+1));
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true,0);
for(std::int32_t Y=0;Y<=Cells;++Y)
for(std::int32_t X=0;X<=Cells;++X)
{
const auto Sample=Field.point((OriginX+X*TileStep)*Subvoxels,
(OriginY+Y*TileStep)*Subvoxels,0);
Exact[static_cast<std::size_t>(Y)*(Cells+1)+X]={
Sample.groundHeightSubvoxels,Sample.capHeightSubvoxels};
}
const auto& BaselineStats=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
BaselineColumnMs.Add(BaselineStats.columnComputeMilliseconds);
BaselineColumns+=BaselineStats.columnMisses;
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true,Drop);
for(std::int32_t Y=0;Y<=Cells;++Y)
for(std::int32_t X=0;X<=Cells;++X)
{
const auto Sample=Field.point((OriginX+X*TileStep)*Subvoxels,
(OriginY+Y*TileStep)*Subvoxels,0);
GroundErrorsCm.Add(std::abs(static_cast<double>(Sample.groundHeightSubvoxels)
-Exact[static_cast<std::size_t>(Y)*(Cells+1)+X].Ground)
*CoreVoxelSizeCm/Subvoxels);
CapErrorsCm.Add(std::abs(static_cast<double>(Sample.capHeightSubvoxels)
-Exact[static_cast<std::size_t>(Y)*(Cells+1)+X].Cap)
*CoreVoxelSizeCm/Subvoxels);
}
const auto& CandidateStats=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
CandidateColumnMs.Add(CandidateStats.columnComputeMilliseconds);
CandidateColumns+=CandidateStats.columnMisses;
++Tiles;
}
std::uint64_t GroundOver2Cm=0,CapOver2Cm=0;
for(const double Error:GroundErrorsCm)if(Error>2.0)++GroundOver2Cm;
for(const double Error:CapErrorsCm)if(Error>2.0)++CapOver2Cm;
double BaselineAverageMs=0.0,CandidateAverageMs=0.0;
for(const double Value:BaselineColumnMs)BaselineAverageMs+=Value;
for(const double Value:CandidateColumnMs)CandidateAverageMs+=Value;
if(!BaselineColumnMs.IsEmpty())BaselineAverageMs/=BaselineColumnMs.Num();
if(!CandidateColumnMs.IsEmpty())CandidateAverageMs/=CandidateColumnMs.Num();
UE_LOG(LogTemp,Display,
TEXT("[WorldAssemblerFrequencyProbe] label=surface lod=%d tiles=%d drop_highest_octaves=%d exact_column_evals=%llu reduced_column_evals=%llu column_eval_saving_pct=%.2f baseline_column_avg_ms=%.5f reduced_column_avg_ms=%.5f ground_error_p95_cm=%.4f ground_error_max_cm=%.4f ground_over_2cm=%llu/%d cap_error_p95_cm=%.4f cap_error_max_cm=%.4f cap_over_2cm=%llu/%d finite_feature_tiles=%llu"),
LOD,Tiles,static_cast<std::int32_t>(Drop),
static_cast<unsigned long long>(BaselineColumns),
static_cast<unsigned long long>(CandidateColumns),
BaselineAverageMs>0.0?100.0*(1.0-CandidateAverageMs/BaselineAverageMs):0.0,
BaselineAverageMs,CandidateAverageMs,
P95(GroundErrorsCm),MaxValue(GroundErrorsCm),
static_cast<unsigned long long>(GroundOver2Cm),GroundErrorsCm.Num(),
P95(CapErrorsCm),MaxValue(CapErrorsCm),
static_cast<unsigned long long>(CapOver2Cm),CapErrorsCm.Num(),
static_cast<unsigned long long>(FiniteFeatureTiles));
}
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true,0);
}
for (const int32 LOD : {0,1,2,3,4,5,6,7,8})
{
TArray<double> FullTimes,CandidateTimes,FullShellTimes,CandidateShellTimes,ListTimes;
TArray<double> UnknownBoxes,FieldCalls,UnclampedFieldCalls,ColumnMisses,ColumnConflictMisses,StratumShellTimes,ConnectorShellTimes;
TArray<double> DensitySamples,ClassificationTimes,ClassificationExclusiveTimes;
TArray<double> ColumnComputeTimes,MeshingTimes,HorizonTimes,RepairSkipped;
TArray<double> ColumnEvalExclusiveTimes,MarchingExclusiveTimes,GroundRefineExclusiveTimes;
TArray<double> CapRefineExclusiveTimes,MeshAssemblyTimes;
TArray<double> MarchingColumnTimes,GroundColumnTimes,CapColumnTimes;
TArray<double> ClassificationNsPerSample,ColumnNsPerSample,MarchingNsPerSample;
TArray<double> GroundNsPerSample,CapNsPerSample,AssemblyNsPerSample,ListNsPerSample;
TArray<double> MarchingColumnNsPerSample,GroundColumnNsPerSample,CapColumnNsPerSample;
TArray<double> McProfileTotalTimes,McProfileGridTimes,McProfileGridExclusiveTimes;
TArray<double> McProfileCellTimes,McProfileNormalTimes,McProfileInterpolationTimes;
TArray<double> McProfileStreamTimes,McProfileOtherTimes,McProfileResidualTimes;
TArray<double> McProfileGradientFallbackCalls,McProfileEdgeMidpointCalls,McProfileGradientMemoHits;
TArray<double> McProfileGridNsPerSample,McProfileGridExclusiveNsPerSample;
TArray<double> McProfileCellNsPerSample,McProfileNormalNsPerSample;
TArray<double> McProfileInterpolationNsPerSample,McProfileStreamNsPerSample;
TArray<double> McProfileOtherNsPerSample,McProfileResidualNsPerSample;
TArray<double> StratumFieldCalls,ConnectorFieldCalls,ShellTriangles,GroundTriangles,GroundRemoved,CapTriangles;
const bool bMcStageAudit=FParse::Param(FCommandLine::Get(),TEXT("coremcstageaudit"));
int32 AuditDirectMappedColumnCache=0,AuditMemoizeMcGradients=1;
FParse::Value(FCommandLine::Get(),TEXT("voxel.CoreColumnCacheDirectMapped="),
AuditDirectMappedColumnCache);
FParse::Value(FCommandLine::Get(),TEXT("voxel.CoreMCGradientMemoization="),
AuditMemoizeMcGradients);
for (const auto& Key : Keys)
{
if (Key.lod!=LOD || FullTimes.Num()>=8) continue;
VoxelForgeCore::TileGenerationOptions Options;
Options.hashDensity=true;
Options.useAssemblyFeatureQueries=false;
Options.legacyDirectMappedColumnCache=AuditDirectMappedColumnCache!=0;
Options.memoizeMcGradients=AuditMemoizeMcGradients!=0;
std::string Error;
// Check density and mesh parity with hashes, then time uncached runtime generation.
// Verifier la parite avec hashes, puis mesurer la generation sans cache de colonnes.
const auto FullParity=World.generateTile(Key,Options,Error);
if (!FullParity) return false;
Options.useAssemblyFeatureQueries=true;
const auto CandidateParity=World.generateTile(Key,Options,Error);
if (!CandidateParity||FullParity->densityHash!=CandidateParity->densityHash
||FullParity->meshHash!=CandidateParity->meshHash
||FullParity->payloadHash!=CandidateParity->payloadHash
||FullParity->topologyHash!=CandidateParity->topologyHash)
{ OutError=TEXT("Shared feature full/candidate tile hashes differ."); return false; }
Options.hashDensity=false;
Options.useAssemblyFeatureQueries=false;
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true);
const double FullStart=FPlatformTime::Seconds();
auto Full=World.generateTile(Key,Options,Error);
FullTimes.Add((FPlatformTime::Seconds()-FullStart)*1000.0);
Options.useAssemblyFeatureQueries=true;
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true);
const double CandidateStart=FPlatformTime::Seconds();
auto Candidate=World.generateTile(Key,Options,Error);
CandidateTimes.Add((FPlatformTime::Seconds()-CandidateStart)*1000.0);
if (!Full||!Candidate)
{ OutError=TEXT("Shared feature full/candidate measured generation failed."); return false; }
FullShellTimes.Add(Full->diagnostics.farShellMilliseconds);
CandidateShellTimes.Add(Candidate->diagnostics.farShellMilliseconds);
ListTimes.Add(Candidate->diagnostics.featureListMilliseconds);
const auto& Diagnostics=Candidate->diagnostics;
const double SampleCount=static_cast<double>(Candidate->densitySampleCount);
DensitySamples.Add(static_cast<double>(Candidate->densitySampleCount));
UnknownBoxes.Add(static_cast<double>(Diagnostics.unknownBoxes));
FieldCalls.Add(static_cast<double>(Diagnostics.fieldCalls));
UnclampedFieldCalls.Add(static_cast<double>(Diagnostics.unclampedFieldCalls));
ColumnMisses.Add(static_cast<double>(Diagnostics.columnCacheMisses));
ColumnConflictMisses.Add(static_cast<double>(Diagnostics.columnCacheConflictMisses));
ClassificationTimes.Add(Diagnostics.classificationMilliseconds);
ClassificationExclusiveTimes.Add(Diagnostics.classificationExclusiveMilliseconds);
ColumnComputeTimes.Add(Diagnostics.columnComputeMilliseconds);
ColumnEvalExclusiveTimes.Add(Diagnostics.columnEvaluationExclusiveMilliseconds);
MarchingExclusiveTimes.Add(Diagnostics.marchingCubesExclusiveMilliseconds);
MarchingColumnTimes.Add(Diagnostics.marchingCubesColumnComputeMilliseconds);
GroundRefineExclusiveTimes.Add(Diagnostics.groundRefinementExclusiveMilliseconds);
GroundColumnTimes.Add(Diagnostics.groundRefinementColumnComputeMilliseconds);
CapRefineExclusiveTimes.Add(Diagnostics.capRefinementExclusiveMilliseconds);
CapColumnTimes.Add(Diagnostics.capRefinementColumnComputeMilliseconds);
MeshAssemblyTimes.Add(Diagnostics.meshAssemblyMilliseconds);
MeshingTimes.Add(Diagnostics.meshingMilliseconds);
HorizonTimes.Add(Diagnostics.farHorizonMilliseconds);
RepairSkipped.Add(Diagnostics.farShellRepairSkipped?1.0:0.0);
StratumShellTimes.Add(Diagnostics.farStratumShellMilliseconds);
ConnectorShellTimes.Add(Diagnostics.farConnectorShellMilliseconds);
StratumFieldCalls.Add(static_cast<double>(Diagnostics.farStratumFieldCalls));
ConnectorFieldCalls.Add(static_cast<double>(Diagnostics.farConnectorFieldCalls));
ShellTriangles.Add(static_cast<double>(Diagnostics.farShellTrianglesAdded));
GroundTriangles.Add(static_cast<double>(Diagnostics.horizonGroundTriangles));
GroundRemoved.Add(static_cast<double>(Diagnostics.horizonGroundTrianglesRemoved));
CapTriangles.Add(static_cast<double>(Diagnostics.horizonCapTriangles));
if(SampleCount>0.0)
{
ClassificationNsPerSample.Add(Diagnostics.classificationExclusiveMilliseconds
*1000000.0/SampleCount);
ColumnNsPerSample.Add(Diagnostics.columnEvaluationExclusiveMilliseconds
*1000000.0/SampleCount);
MarchingColumnNsPerSample.Add(Diagnostics.marchingCubesColumnComputeMilliseconds
*1000000.0/SampleCount);
MarchingNsPerSample.Add(Diagnostics.marchingCubesExclusiveMilliseconds
*1000000.0/SampleCount);
GroundColumnNsPerSample.Add(Diagnostics.groundRefinementColumnComputeMilliseconds
*1000000.0/SampleCount);
GroundNsPerSample.Add(Diagnostics.groundRefinementExclusiveMilliseconds
*1000000.0/SampleCount);
CapColumnNsPerSample.Add(Diagnostics.capRefinementColumnComputeMilliseconds
*1000000.0/SampleCount);
CapNsPerSample.Add(Diagnostics.capRefinementExclusiveMilliseconds
*1000000.0/SampleCount);
AssemblyNsPerSample.Add(Diagnostics.meshAssemblyMilliseconds
*1000000.0/SampleCount);
ListNsPerSample.Add(Diagnostics.featureListMilliseconds*1000000.0/SampleCount);
}
if(bMcStageAudit)
{
// Profile a separate cold tile so the audit's ordinary timing rows keep the
// uninstrumented path and comparable clock scope.
// Profiler une tuile froide separee pour garder les mesures ordinaires sans instrumentation.
Options.profileMesherStages=true;
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true);
auto Profiled=World.generateTile(Key,Options,Error);
Options.profileMesherStages=false;
if(!Profiled)
{ OutError=TEXT("Cold MC stage profile tile generation failed."); return false; }
const auto& Mc=Profiled->diagnostics;
const double Total=Mc.marchingCubesExclusiveMilliseconds
+Mc.marchingCubesColumnComputeMilliseconds;
const double Grid=Mc.marchingCubesDensityGridMilliseconds;
const double GridExclusive=FMath::Max(0.0,Grid
-Mc.marchingCubesColumnComputeMilliseconds);
const double Cell=Mc.marchingCubesCellClassificationMilliseconds;
const double Normal=Mc.marchingCubesGradientNormalsMilliseconds;
const double Interpolation=Mc.marchingCubesEdgeInterpolationMilliseconds;
const double Stream=Mc.marchingCubesVertexDedupStreamMilliseconds;
const double Other=Mc.marchingCubesOtherMilliseconds;
const double Residual=FMath::Max(0.0,Mc.marchingCubesExclusiveMilliseconds
-GridExclusive-Cell-Normal-Interpolation-Stream);
McProfileTotalTimes.Add(Total);
McProfileGridTimes.Add(Grid);
McProfileGridExclusiveTimes.Add(GridExclusive);
McProfileCellTimes.Add(Cell);
McProfileNormalTimes.Add(Normal);
McProfileInterpolationTimes.Add(Interpolation);
McProfileStreamTimes.Add(Stream);
McProfileOtherTimes.Add(Other);
McProfileResidualTimes.Add(Residual);
McProfileGradientFallbackCalls.Add(static_cast<double>(
Mc.marchingCubesGradientFallbackDensityCalls));
McProfileEdgeMidpointCalls.Add(static_cast<double>(
Mc.marchingCubesEdgeMidpointDensityCalls));
McProfileGradientMemoHits.Add(static_cast<double>(
Mc.marchingCubesGradientMemoHits));
const double ProfileSamples=static_cast<double>(Profiled->densitySampleCount);
if(ProfileSamples>0.0)
{
McProfileGridNsPerSample.Add(Grid*1000000.0/ProfileSamples);
McProfileGridExclusiveNsPerSample.Add(GridExclusive*1000000.0/ProfileSamples);
McProfileCellNsPerSample.Add(Cell*1000000.0/ProfileSamples);
McProfileNormalNsPerSample.Add(Normal*1000000.0/ProfileSamples);
McProfileInterpolationNsPerSample.Add(Interpolation*1000000.0/ProfileSamples);
McProfileStreamNsPerSample.Add(Stream*1000000.0/ProfileSamples);
McProfileOtherNsPerSample.Add(Other*1000000.0/ProfileSamples);
McProfileResidualNsPerSample.Add(Residual*1000000.0/ProfileSamples);
}
}
}
const auto P95=[](TArray<double> Values)
{ Values.Sort(); return Values.IsEmpty() ? 0.0 : Values[FMath::Clamp(
FMath::CeilToInt(Values.Num()*0.95f)-1,0,Values.Num()-1)]; };
UE_LOG(LogTemp,Display,
TEXT("[VoxelForgeSharedFeatureCost] label=%s lod=%d column_cache_direct_mapped=%d gradient_memoization=%d tiles=%d hashes_identical=1 full_p95_ms=%.5f candidate_p95_ms=%.5f list_p95_ms=%.5f classification_wall_p95_ms=%.5f classification_exclusive_p95_ms=%.5f column_compute_p95_ms=%.5f column_eval_exclusive_p95_ms=%.5f mc_column_compute_p95_ms=%.5f mc_exclusive_p95_ms=%.5f ground_column_compute_p95_ms=%.5f ground_refine_exclusive_p95_ms=%.5f cap_column_compute_p95_ms=%.5f cap_refine_exclusive_p95_ms=%.5f mesh_assembly_p95_ms=%.5f meshing_p95_ms=%.5f horizon_p95_ms=%.5f samples_p95=%.0f classification_ns_per_sample_p95=%.2f column_ns_per_sample_p95=%.2f mc_column_ns_per_sample_p95=%.2f mc_ns_per_sample_p95=%.2f ground_column_ns_per_sample_p95=%.2f ground_ns_per_sample_p95=%.2f cap_column_ns_per_sample_p95=%.2f cap_ns_per_sample_p95=%.2f assembly_ns_per_sample_p95=%.2f feature_list_ns_per_sample_p95=%.2f full_shell_p95_ms=%.5f candidate_shell_p95_ms=%.5f repair_skipped_p95=%.0f unknown_boxes_p95=%.0f field_calls_p95=%.0f unclamped_field_calls_p95=%.0f column_misses_p95=%.0f column_conflict_misses_p95=%.0f stratum_shell_p95_ms=%.5f stratum_field_calls_p95=%.0f connector_shell_p95_ms=%.5f connector_field_calls_p95=%.0f shell_triangles_p95=%.0f horizon_ground_triangles_p95=%.0f horizon_ground_removed_p95=%.0f horizon_cap_triangles_p95=%.0f"),
Label,LOD,AuditDirectMappedColumnCache!=0?1:0,AuditMemoizeMcGradients!=0?1:0,
FullTimes.Num(),P95(FullTimes),P95(CandidateTimes),P95(ListTimes),
P95(ClassificationTimes),P95(ClassificationExclusiveTimes),P95(ColumnComputeTimes),
P95(ColumnEvalExclusiveTimes),P95(MarchingColumnTimes),P95(MarchingExclusiveTimes),
P95(GroundColumnTimes),P95(GroundRefineExclusiveTimes),P95(CapColumnTimes),
P95(CapRefineExclusiveTimes),
P95(MeshAssemblyTimes),P95(MeshingTimes),P95(HorizonTimes),P95(DensitySamples),
P95(ClassificationNsPerSample),P95(ColumnNsPerSample),P95(MarchingColumnNsPerSample),
P95(MarchingNsPerSample),P95(GroundColumnNsPerSample),P95(GroundNsPerSample),
P95(CapColumnNsPerSample),P95(CapNsPerSample),P95(AssemblyNsPerSample),P95(ListNsPerSample),
P95(FullShellTimes),P95(CandidateShellTimes),P95(RepairSkipped),P95(UnknownBoxes),P95(FieldCalls),
P95(UnclampedFieldCalls),
P95(ColumnMisses),P95(ColumnConflictMisses),P95(StratumShellTimes),P95(StratumFieldCalls),
P95(ConnectorShellTimes),P95(ConnectorFieldCalls),P95(ShellTriangles),
P95(GroundTriangles),P95(GroundRemoved),P95(CapTriangles));
if(bMcStageAudit)
{
UE_LOG(LogTemp,Display,
TEXT("[VoxelForgeMCStageCost] label=%s lod=%d tiles=%d sampling=row_total_and_1_of_8_cells total_p95_ms=%.5f density_grid_inclusive_p95_ms=%.5f density_grid_ex_column_p95_ms=%.5f cell_classification_p95_ms=%.5f gradient_normals_p95_ms=%.5f edge_interpolation_p95_ms=%.5f vertex_dedup_stream_p95_ms=%.5f setup_finalization_p95_ms=%.5f unassigned_residual_p95_ms=%.5f grid_ns_per_sample_p95=%.2f grid_ex_column_ns_per_sample_p95=%.2f cell_ns_per_sample_p95=%.2f normals_ns_per_sample_p95=%.2f interpolation_ns_per_sample_p95=%.2f dedup_stream_ns_per_sample_p95=%.2f setup_finalization_ns_per_sample_p95=%.2f residual_ns_per_sample_p95=%.2f gradient_fallback_density_calls_p95=%.0f edge_midpoint_density_probes_p95=%.0f gradient_memo_hits_p95=%.0f"),
Label,LOD,McProfileTotalTimes.Num(),P95(McProfileTotalTimes),P95(McProfileGridTimes),
P95(McProfileGridExclusiveTimes),P95(McProfileCellTimes),P95(McProfileNormalTimes),
P95(McProfileInterpolationTimes),P95(McProfileStreamTimes),P95(McProfileOtherTimes),
P95(McProfileResidualTimes),P95(McProfileGridNsPerSample),
P95(McProfileGridExclusiveNsPerSample),P95(McProfileCellNsPerSample),
P95(McProfileNormalNsPerSample),P95(McProfileInterpolationNsPerSample),
P95(McProfileStreamNsPerSample),P95(McProfileOtherNsPerSample),
P95(McProfileResidualNsPerSample),P95(McProfileGradientFallbackCalls),
P95(McProfileEdgeMidpointCalls),P95(McProfileGradientMemoHits));
}
}
if(Assembly!=nullptr&&!Assembly->plan.strates.empty())
{
if(FParse::Param(FCommandLine::Get(),TEXT("corefeatureauditonly"))
&&!RunCoreFarCapTileCensus(World,*Assembly,OutError))return false;
if(!RunCoreFarHorizonAgreementAudit(World,Keys,*Assembly,OutError))return false;
}
return true;
}
bool RunCoreContentPlacementCostAudit(AVoxelWorld* Actor, const FVector& CoreLocalProbeCm,
const TCHAR* Label, FString& OutError);
bool RunCoreFloatingIslandActorIntegrationTest(
const FExploreArguments& Arguments, FString& OutError)
{
UVoxelSettings* AuthoredSettings = LoadObject<UVoxelSettings>(
nullptr, *Arguments.SettingsReference);
if (AuthoredSettings == nullptr)
{
OutError = FString::Printf(TEXT("Could not load Core island test settings '%s'."),
*Arguments.SettingsReference);
return false;
}
float StagedWalkSpeedCmPerSecond = 0.0f;
float StagedSprintSpeedCmPerSecond = 0.0f;
if (Arguments.bCoreWorldStreamTraceOnly)
{
StagedWalkSpeedCmPerSecond = 600.0f;
StagedSprintSpeedCmPerSecond = 6000.0f;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgePawnSpeedFixture] source=trace_fixture_defaults walk_cm_s=%.3f sprint_cm_s=%.3f"),
StagedWalkSpeedCmPerSecond, StagedSprintSpeedCmPerSecond);
}
else if (!ReadStagedPlayerSpeeds(
StagedWalkSpeedCmPerSecond, StagedSprintSpeedCmPerSecond, OutError))
{
return false;
}
int32 EffectiveSeed = ResolveCoreTestWorldSeed(AuthoredSettings);
if (const UVoxelSeasonAsset* AuthoredSeason = AuthoredSettings->Season.LoadSynchronous())
EffectiveSeed = AuthoredSeason->GetSeasonSeed();
TStrongObjectPtr<UVoxelSettings> OwnerSurfaceSettings;
TStrongObjectPtr<UVoxelStrateDefinition> OwnerSurfaceDefinition;
if (!CreateLoggedOwnerSurfaceSettingsCopy(AuthoredSettings, EffectiveSeed,
OwnerSurfaceSettings, OwnerSurfaceDefinition, OutError))
{
return false;
}
TStrongObjectPtr<UVoxelStrateManager> SourceManager(NewObject<UVoxelStrateManager>(
GetTransientPackage(), NAME_None, RF_Transient));
if (!SourceManager.IsValid()
|| !SourceManager->Initialize(OwnerSurfaceSettings.Get(), EffectiveSeed))
{
OutError = TEXT("Core island test could not resolve the staged owner settings layout.");
return false;
}
int32 IslandStrateIndex = INDEX_NONE;
TStrongObjectPtr<UVoxelSettings> TestSettings;
TArray<TStrongObjectPtr<UVoxelStrateDefinition>> TestDefinitions;
if (!CreateLockedLayoutSettingsCopy(OwnerSurfaceSettings.Get(), SourceManager.Get(), EffectiveSeed,
true, TestSettings, TestDefinitions, IslandStrateIndex, OutError))
{
return false;
}
TestSettings->bEnableDensityVolume = false;
TestSettings->DecorationRadiusChunks = 2;
TestSettings->DecorationNearRadiusChunks = 1;
TestSettings->DecorationRegionSizeCells = 1;
TestSettings->DecorationSpacingVoxels = 16;
TestSettings->DecorationFarSpacingVoxels = 16;
TestSettings->DecorationMarchStepVoxels = 2;
TestSettings->DecorationMaxCrossingsPerColumn = 2;
TestSettings->DecorationColumnDepthVoxels = 320;
TestSettings->MaxConcurrentDecorationTasks = 2;
TestSettings->MaxDecorationCellsPerFrame = 4;
TSoftObjectPtr<UVoxelStrateDefinition>* IslandDefinitionSoft =
TestSettings->FixedStrates.Find(IslandStrateIndex);
UVoxelStrateDefinition* IslandDefinition = IslandDefinitionSoft != nullptr
? IslandDefinitionSoft->Get() : nullptr;
if (IslandDefinition == nullptr
|| IslandDefinition->GeneratorType != ECaveGeneratorType::FloatingIslands)
{
OutError = TEXT("Could not resolve the transient owner SurfaceWorld strate as FloatingIslands.");
return false;
}
TStrongObjectPtr<UVoxelStrateManager> Manager(NewObject<UVoxelStrateManager>(
GetTransientPackage(), NAME_None, RF_Transient));
if (!Manager.IsValid() || !Manager->Initialize(TestSettings.Get(), EffectiveSeed))
{
OutError = TEXT("Core island test could not rebuild the locked copy of the owner's layout.");
return false;
}
const FStrateSlot* IslandSlot = nullptr;
for (const FStrateSlot& Slot : Manager->GetLayout())
{
if (Slot.StrateIndex == IslandStrateIndex) IslandSlot = &Slot;
}
const FStrateSlot* SourceIslandSlot = SourceManager->GetLayout().FindByPredicate(
[IslandStrateIndex](const FStrateSlot& Slot)
{ return Slot.StrateIndex == IslandStrateIndex; });
if (IslandSlot == nullptr || SourceIslandSlot == nullptr
|| IslandSlot->TopChunkZ != SourceIslandSlot->TopChunkZ
|| IslandSlot->BottomChunkZ != SourceIslandSlot->BottomChunkZ
|| IslandSlot->Definition == nullptr
|| IslandSlot->Definition->GeneratorType != ECaveGeneratorType::FloatingIslands)
{
OutError = FString::Printf(
TEXT("Transient island strate did not preserve owner bounds [%d,%d]: actual=[%d,%d]."),
SourceIslandSlot ? SourceIslandSlot->TopChunkZ : MAX_int32,
SourceIslandSlot ? SourceIslandSlot->BottomChunkZ : MIN_int32,
IslandSlot ? IslandSlot->TopChunkZ : MAX_int32,
IslandSlot ? IslandSlot->BottomChunkZ : MIN_int32);
return false;
}
FString RuntimeJson;
if (!VoxelForgeCoreAdapter::SerializeResolvedWorld(
TestSettings.Get(), Manager.Get(), 3, 8, Arguments.CoreVoxelSizeCm, RuntimeJson, OutError))
{
return false;
}
FTCHARToUTF8 Utf8Runtime(*RuntimeJson);
std::string CoreError;
std::optional<VoxelForgeCore::ResolvedWorldSnapshot> Snapshot =
VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
std::string_view(Utf8Runtime.Get(), static_cast<std::size_t>(Utf8Runtime.Length())),
CoreError);
if (!Snapshot || Snapshot->worldAssembly() == nullptr)
{
OutError = FString::Printf(TEXT("Core adapter did not generate an owner world assembly: %s"),
Snapshot ? TEXT("world assembly is missing") : UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
const VoxelForgeCore::WorldAssemblyManifest& NativeAssembly = *Snapshot->worldAssembly();
const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1* NativeIslandPlanPtr = nullptr;
for (const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1& Plan : NativeAssembly.floatingIslands)
if (Plan.strateIndex == IslandStrateIndex) NativeIslandPlanPtr = &Plan;
if (NativeIslandPlanPtr == nullptr
|| NativeIslandPlanPtr->topChunkZ != IslandSlot->TopChunkZ
|| NativeIslandPlanPtr->bottomChunkZ != IslandSlot->BottomChunkZ)
{
OutError = TEXT("Core did not generate a floating-island plan for the converted owner slot at its original heights.");
return false;
}
const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1 NativeIslandPlan = *NativeIslandPlanPtr;
if (NativeIslandPlan.strateIndex != IslandStrateIndex
|| NativeIslandPlan.manifest.plan.islands.empty()
|| NativeIslandPlan.manifest.plan.clusterEdges.empty()
|| NativeIslandPlan.manifest.plan.route.size() < 2u)
{
OutError = TEXT("Core generated island plan has the wrong owner strate or route.");
return false;
}
VoxelForgeCore::FloatingIslandFieldV1 RouteField(
NativeIslandPlan.manifest.recipe, NativeIslandPlan.manifest.plan);
const VoxelForgeCore::FloatingIslandRouteAuditResult RouteAudit =
VoxelForgeCore::AuditFloatingIslandRoute(RouteField);
const VoxelForgeCore::FloatingIslandThicknessAuditResult ThicknessAudit =
VoxelForgeCore::AuditFloatingIslandThickness(NativeIslandPlan.manifest.plan);
if (!RouteField.isValid() || !RouteAudit.passed || !ThicknessAudit.passed)
{
OutError = TEXT("Generated Core island field failed route clearance or mass-thickness validation.");
return false;
}
const VoxelForgeCore::WorldAssemblyRoutePointV1& StartRoute =
NativeIslandPlan.manifest.plan.route.front();
constexpr double SubvoxelsPerCoreVoxel = 256.0;
const double CoreVoxelSizeCm = Arguments.CoreVoxelSizeCm;
const FVector CameraVoxels(
static_cast<double>(StartRoute.positionSubvoxels[0]) / SubvoxelsPerCoreVoxel,
static_cast<double>(StartRoute.positionSubvoxels[1]) / SubvoxelsPerCoreVoxel,
static_cast<double>(StartRoute.positionSubvoxels[2]) / SubvoxelsPerCoreVoxel);
const FVector CameraLocalCm = CameraVoxels * CoreVoxelSizeCm;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreIslandTest] plan_generated=PASS hash=%016llx recipe_hash=%016llx plan_hash=%016llx seed=%d strate=%d owner_bounds=[%d,%d] scale_owner_cm=%.3f scale_core_cm=%.3f islands=%d connectors=%d route_segments=%llu route_clearance_subvoxels=%lld water_fixed=%d"),
static_cast<unsigned long long>(NativeAssembly.contentHash),
static_cast<unsigned long long>(NativeIslandPlan.manifest.recipe.contentHash),
static_cast<unsigned long long>(NativeIslandPlan.manifest.plan.contentHash),
EffectiveSeed, IslandStrateIndex, IslandSlot->TopChunkZ, IslandSlot->BottomChunkZ,
static_cast<double>(VOXEL_SIZE), CoreVoxelSizeCm,
static_cast<int32>(NativeIslandPlan.manifest.plan.islands.size()),
static_cast<int32>(NativeIslandPlan.manifest.plan.clusterEdges.size()),
static_cast<unsigned long long>(RouteAudit.segmentsTested),
static_cast<long long>(RouteAudit.minimumClearanceSubvoxels),
IslandSlot->Definition->bHasWater ? 1 : 0);
// Generate the standalone reference before actor startup so two full Core worlds do not overlap.
// Generer la reference Standalone avant l'acteur pour ne pas garder deux mondes Core complets.
TMap<FVoxelTileKey, FVoxelForgeCoreTileHashRecord> ExpectedHashes;
TSet<FVoxelTileKey> ExpectedDesiredSet;
std::vector<VoxelForgeCore::TileKey> Desired;
std::array<std::uint64_t, 4> ExpectedAggregates{};
std::map<std::string, VoxelForgeCore::TileKey> TimingProbeKeys;
std::int32_t CoverageLocalTiles = 0;
std::int32_t CoverageFaces = 0;
std::int32_t TransitionFaces = 0;
std::int32_t Lod0Tiles = 0, Lod1Tiles = 0;
VoxelForgeCore::Vec3d Camera{CameraVoxels.X, CameraVoxels.Y, CameraVoxels.Z};
VoxelForgeCore::ShapeBuildRequest Request;
{
VoxelForgeCore::World ReferenceWorld(std::move(*Snapshot));
Desired = ReferenceWorld.desiredTiles(Camera);
if (Desired.empty())
{
OutError = TEXT("Standalone Core returned no tiles around the island route start.");
return false;
}
std::int32_t FineMin[3] = {MAX_int32, MAX_int32, MAX_int32};
std::int32_t FineMax[3] = {MIN_int32, MIN_int32, MIN_int32};
std::int32_t ClipMin[3] = {MAX_int32, MAX_int32, MAX_int32};
std::int32_t ClipMax[3] = {MIN_int32, MIN_int32, MIN_int32};
for (const VoxelForgeCore::TileKey& Key : Desired)
{
const std::int32_t Extent = 32 << Key.lod;
const std::int32_t Origins[3] = {
Key.coordinate.x * Extent, Key.coordinate.y * Extent, Key.coordinate.z * Extent};
for (int32 Axis = 0; Axis < 3; ++Axis)
{
ClipMin[Axis] = FMath::Min(ClipMin[Axis], Origins[Axis]);
ClipMax[Axis] = FMath::Max(ClipMax[Axis], Origins[Axis] + Extent);
if (Key.lod < ReferenceWorld.configuration().fullResolutionLodCount)
{
FineMin[Axis] = FMath::Min(FineMin[Axis], Origins[Axis]);
FineMax[Axis] = FMath::Max(FineMax[Axis], Origins[Axis] + Extent);
}
}
if (Key.lod == 0) ++Lod0Tiles;
if (Key.lod == 1) ++Lod1Tiles;
}
if (FineMin[0] == MAX_int32 || ClipMin[0] == MAX_int32)
{
OutError = TEXT("Standalone Core island clipmap bounds are invalid.");
return false;
}
Request.minVoxel = {FineMin[0], FineMin[1], FineMin[2]};
Request.maxVoxel = {FineMax[0], FineMax[1], FineMax[2]};
Request.clipMinVoxel = {ClipMin[0], ClipMin[1], ClipMin[2]};
Request.clipMaxVoxel = {ClipMax[0], ClipMax[1], ClipMax[2]};
Request.workerLimit = 8;
Request.detail = VoxelForgeCore::DetailVariant::coherent;
Request.includeFarWorld = true;
FStandaloneCoreAdapterScheduler Scheduler;
if (!ReferenceWorld.compile(Request, Scheduler, CoreError))
{
OutError = FString::Printf(TEXT("Standalone Core island reference compile failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
ReferenceWorld.retainTileRegions(Desired);
if (!RunCoreSharedFeatureCostAudit(ReferenceWorld,Desired,TEXT("islands"),
&NativeAssembly,OutError)) return false;
VoxelForgeCore::TileGenerationOptions Options;
Options.hashDensity = true;
std::vector<FStandaloneCoreAdapterTileHashes> AggregateRows;
AggregateRows.reserve(Desired.size());
for (const VoxelForgeCore::TileKey& Key : Desired)
{
const std::optional<VoxelForgeCore::TileMesh> Mesh =
ReferenceWorld.generateTile(Key, Options, CoreError);
if (!Mesh || !Mesh->hasDensityHash || !Mesh->hasMeshHash
|| !Mesh->hasPayloadHash || !Mesh->hasTopologyHash)
{
OutError = FString::Printf(TEXT("Standalone Core island tile generation failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
const FVoxelTileKey RuntimeKey(
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
FVoxelForgeCoreTileHashRecord Record;
Record.Density = Mesh->densityHash;
Record.Mesh = Mesh->meshHash;
Record.Payload = Mesh->payloadHash;
Record.Topology = Mesh->topologyHash;
Record.Triangles = static_cast<int32>(Mesh->indices.size() / 3u);
Record.bEmpty = Mesh->indices.empty();
Record.bHasHashes = true;
ExpectedHashes.Add(RuntimeKey, Record);
ExpectedDesiredSet.Add(RuntimeKey);
AggregateRows.push_back({Key,
{Record.Density, Record.Mesh, Record.Payload, Record.Topology}});
if (Key.lod > 1u) continue;
const bool bAir = Mesh->diagnostics.tileClass == VoxelForgeCore::TileClass::allAir;
const bool bSolid = Mesh->diagnostics.tileClass == VoxelForgeCore::TileClass::allSolid;
bool bIslandBounds = false;
bool bConnectorBounds = false;
const std::int64_t TileMinimum[3] = {
static_cast<std::int64_t>(Key.coordinate.x) * (32ll << Key.lod) * 256ll,
static_cast<std::int64_t>(Key.coordinate.y) * (32ll << Key.lod) * 256ll,
static_cast<std::int64_t>(Key.coordinate.z) * (32ll << Key.lod) * 256ll};
const std::int64_t TileMaximum[3] = {
TileMinimum[0] + (32ll << Key.lod) * 256ll,
TileMinimum[1] + (32ll << Key.lod) * 256ll,
TileMinimum[2] + (32ll << Key.lod) * 256ll};
const auto IntersectsFeature = [&](const auto& Feature)
{
for (int32 Axis = 0; Axis < 3; ++Axis)
{
if (TileMaximum[Axis] <= Feature.influenceMinimumSubvoxels[Axis]
|| TileMinimum[Axis] >= Feature.influenceMaximumSubvoxels[Axis]) return false;
}
return true;
};
for (const auto& Island : NativeIslandPlan.manifest.plan.islands)
bIslandBounds |= IntersectsFeature(Island);
for (const auto& Connector : NativeIslandPlan.manifest.plan.clusterEdges)
bConnectorBounds |= IntersectsFeature(Connector);
const std::string LodName = "lod" + std::to_string(Key.lod) + ":";
const std::string Kind = bAir ? "air" : bSolid ? "solid"
: (bConnectorBounds && !bIslandBounds) ? "span"
: bIslandBounds ? "island" : "mixed";
const std::string ProbeName = LodName + Kind;
if (TimingProbeKeys.find(ProbeName) == TimingProbeKeys.end())
TimingProbeKeys.emplace(ProbeName, Key);
if (!bAir && !bSolid
&& TimingProbeKeys.find(LodName + "mixed") == TimingProbeKeys.end())
TimingProbeKeys.emplace(LodName + "mixed", Key);
}
ExpectedAggregates = StandaloneCoreAggregateHashes(AggregateRows);
if (!AuditCoreClipmapCoverage(Desired, Camera, 128,
CoverageLocalTiles, CoverageFaces, TransitionFaces, OutError))
{
OutError = FString::Printf(TEXT("Island standalone clipmap coverage failed: %s"), *OutError);
return false;
}
UVoxelGenerator* LegacyGenerator = NewObject<UVoxelGenerator>(
GetTransientPackage(), NAME_None, RF_Transient);
UVoxelMarchingCubesMesher* LegacyMesher = NewObject<UVoxelMarchingCubesMesher>(
GetTransientPackage(), NAME_None, RF_Transient);
if (LegacyGenerator == nullptr || LegacyMesher == nullptr)
{
OutError = TEXT("Could not allocate the matched legacy island timing fixture.");
return false;
}
LegacyGenerator->InitializeSettings(TestSettings.Get());
LegacyGenerator->SetStrateManager(Manager.Get());
LegacyMesher->SetGenerator(LegacyGenerator);
LegacyMesher->SetGeometryVoxelSizeCm(static_cast<float>(CoreVoxelSizeCm));
const auto P95Milliseconds = [](TArray<double>& Samples)
{
Samples.Sort();
const int32 Index = FMath::Clamp(
FMath::CeilToInt(static_cast<float>(Samples.Num()) * 0.95f) - 1,
0, Samples.Num() - 1);
return Samples.IsEmpty() ? 0.0 : Samples[Index];
};
for (const auto& ProbePair : TimingProbeKeys)
{
const VoxelForgeCore::TileKey& Key = ProbePair.second;
TArray<double> CoreMilliseconds;
TArray<double> CoreClassificationMilliseconds;
TArray<double> CoreColumnComputeMilliseconds;
TArray<double> CoreMeshingMilliseconds;
TArray<double> LegacyMilliseconds;
CoreMilliseconds.Reserve(5);
CoreClassificationMilliseconds.Reserve(5);
CoreColumnComputeMilliseconds.Reserve(5);
CoreMeshingMilliseconds.Reserve(5);
LegacyMilliseconds.Reserve(5);
for (int32 Repeat = 0; Repeat < 5; ++Repeat)
{
const double CoreStart = FPlatformTime::Seconds();
const std::optional<VoxelForgeCore::TileMesh> TimedCoreMesh =
ReferenceWorld.generateTile(Key, {}, CoreError);
const double CoreElapsedMs = (FPlatformTime::Seconds() - CoreStart) * 1000.0;
if (!TimedCoreMesh)
{
OutError = FString::Printf(TEXT("Core island timing sample failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
CoreMilliseconds.Add(CoreElapsedMs);
CoreClassificationMilliseconds.Add(TimedCoreMesh->diagnostics.classificationMilliseconds);
CoreColumnComputeMilliseconds.Add(TimedCoreMesh->diagnostics.columnComputeMilliseconds);
CoreMeshingMilliseconds.Add(TimedCoreMesh->diagnostics.meshingMilliseconds);
const int32 Extent = CHUNK_SIZE << Key.lod;
const FIntVector CoreOrigin(
Key.coordinate.x * Extent, Key.coordinate.y * Extent,
Key.coordinate.z * Extent);
const FIntVector LegacyOrigin = CoreOrigin * 2;
const int32 LegacyStep = 2 << Key.lod;
const double LegacyStart = FPlatformTime::Seconds();
const FVoxelMeshData LegacyMesh = LegacyMesher->GenerateMesh(
LegacyOrigin, LegacyStep, CHUNK_SIZE);
const double LegacyElapsedMs = (FPlatformTime::Seconds() - LegacyStart) * 1000.0;
LegacyMilliseconds.Add(LegacyElapsedMs);
if (Repeat == 0)
{
const int32 CoreTriangles = static_cast<int32>(TimedCoreMesh->indices.size() / 3u);
const int32 LegacyTriangles = LegacyMesh.Triangles.Num() / 3;
const double TileTravelCm = CoreVoxelSizeCm * Extent;
const double WalkDeadlineMs = TileTravelCm / FMath::Max(StagedWalkSpeedCmPerSecond, 1.0f) * 1000.0;
const double SprintDeadlineMs = TileTravelCm / FMath::Max(StagedSprintSpeedCmPerSecond, 1.0f) * 1000.0;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeIslandStreaming] lod=%d kind=%s key=(%d,%d,%d) core_triangles=%d legacy_triangles=%d tile_travel_cm=%.0f walk_deadline_ms=%.3f sprint_deadline_ms=%.3f"),
static_cast<int32>(Key.lod), UTF8_TO_TCHAR(ProbePair.first.c_str()),
Key.coordinate.x, Key.coordinate.y, Key.coordinate.z,
CoreTriangles, LegacyTriangles, TileTravelCm, WalkDeadlineMs, SprintDeadlineMs);
}
}
const double CoreP95 = P95Milliseconds(CoreMilliseconds);
const double CoreClassificationP95 = P95Milliseconds(CoreClassificationMilliseconds);
const double CoreColumnComputeP95 = P95Milliseconds(CoreColumnComputeMilliseconds);
const double CoreMeshingP95 = P95Milliseconds(CoreMeshingMilliseconds);
const double LegacyP95 = P95Milliseconds(LegacyMilliseconds);
const int32 Extent = CHUNK_SIZE << Key.lod;
const double TileTravelCm = CoreVoxelSizeCm * Extent;
const double WalkDeadlineMs = TileTravelCm / FMath::Max(StagedWalkSpeedCmPerSecond, 1.0f) * 1000.0;
const double SprintDeadlineMs = TileTravelCm / FMath::Max(StagedSprintSpeedCmPerSecond, 1.0f) * 1000.0;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeIslandStreaming] timing lod=%d kind=%s repeats=5 core_p95_ms=%.3f core_classify_p95_ms=%.3f core_columns_p95_ms=%.3f core_mesh_p95_ms=%.3f legacy_p95_ms=%.3f walk_deadline_ms=%.3f sprint_deadline_ms=%.3f core_walk_lag_ms=%.3f legacy_walk_lag_ms=%.3f core_sprint_lag_ms=%.3f legacy_sprint_lag_ms=%.3f"),
static_cast<int32>(Key.lod), UTF8_TO_TCHAR(ProbePair.first.c_str()),
CoreP95, CoreClassificationP95, CoreColumnComputeP95, CoreMeshingP95,
LegacyP95, WalkDeadlineMs, SprintDeadlineMs,
FMath::Max(0.0, CoreP95 - WalkDeadlineMs), FMath::Max(0.0, LegacyP95 - WalkDeadlineMs),
FMath::Max(0.0, CoreP95 - SprintDeadlineMs), FMath::Max(0.0, LegacyP95 - SprintDeadlineMs));
}
for (const int32 Lod : {0, 1})
{
if (TimingProbeKeys.find("lod" + std::to_string(Lod) + ":air") == TimingProbeKeys.end()
|| (TimingProbeKeys.find("lod" + std::to_string(Lod) + ":island") == TimingProbeKeys.end()
&& TimingProbeKeys.find("lod" + std::to_string(Lod) + ":mixed") == TimingProbeKeys.end()))
{
OutError = FString::Printf(TEXT("Island clipmap timing did not find air and surface probes at LOD%d."), Lod);
return false;
}
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreIslandTest] standalone_reference=PASS tiles=%d lod0=%d lod1=%d lod_transitions=%d coverage_local=%d checked_faces=%d density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx"),
ExpectedDesiredSet.Num(), Lod0Tiles, Lod1Tiles, TransitionFaces, CoverageLocalTiles,
CoverageFaces, static_cast<unsigned long long>(ExpectedAggregates[0]),
static_cast<unsigned long long>(ExpectedAggregates[1]),
static_cast<unsigned long long>(ExpectedAggregates[2]),
static_cast<unsigned long long>(ExpectedAggregates[3]));
}
const auto BuildWorkerHashAggregate = [&](uint32 WorkerCount,
std::array<std::uint64_t, 4>& OutHashes) -> bool
{
FTCHARToUTF8 WorkerJson(*RuntimeJson);
std::string WorkerError;
std::optional<VoxelForgeCore::ResolvedWorldSnapshot> WorkerSnapshot =
VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
std::string_view(WorkerJson.Get(), static_cast<std::size_t>(WorkerJson.Length())),
WorkerError);
if (!WorkerSnapshot)
{
OutError = FString::Printf(TEXT("Core W%u island snapshot failed: %s"),
WorkerCount, UTF8_TO_TCHAR(WorkerError.c_str()));
return false;
}
VoxelForgeCore::World WorkerWorld(std::move(*WorkerSnapshot));
VoxelForgeCore::ShapeBuildRequest WorkerRequest = Request;
WorkerRequest.workerLimit = WorkerCount;
FStandaloneCoreAdapterScheduler Scheduler;
if (!WorkerWorld.compile(WorkerRequest, Scheduler, WorkerError))
{
OutError = FString::Printf(TEXT("Core W%u island compile failed: %s"),
WorkerCount, UTF8_TO_TCHAR(WorkerError.c_str()));
return false;
}
WorkerWorld.retainTileRegions(Desired);
VoxelForgeCore::TileGenerationOptions Options;
Options.hashDensity = true;
std::vector<FStandaloneCoreAdapterTileHashes> Rows;
Rows.reserve(Desired.size());
for (const VoxelForgeCore::TileKey& Key : Desired)
{
const std::optional<VoxelForgeCore::TileMesh> Mesh =
WorkerWorld.generateTile(Key, Options, WorkerError);
if (!Mesh || !Mesh->hasDensityHash || !Mesh->hasMeshHash
|| !Mesh->hasPayloadHash || !Mesh->hasTopologyHash)
{
OutError = FString::Printf(TEXT("Core W%u island tile hash failed: %s"),
WorkerCount, UTF8_TO_TCHAR(WorkerError.c_str()));
return false;
}
Rows.push_back({Key, {Mesh->densityHash, Mesh->meshHash,
Mesh->payloadHash, Mesh->topologyHash}});
}
OutHashes = StandaloneCoreAggregateHashes(Rows);
return true;
};
std::array<std::uint64_t, 4> W1Hashes{}, W16Hashes{};
if (!BuildWorkerHashAggregate(1, W1Hashes)
|| !BuildWorkerHashAggregate(16, W16Hashes))
{
return false;
}
if (W1Hashes != ExpectedAggregates || W16Hashes != ExpectedAggregates)
{
OutError = TEXT("Island clipmap hashes differ across worker limits 1, 8 and 16.");
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreIslandTest] worker_hash_matrix=1/8/16 stable=1 density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx"),
static_cast<unsigned long long>(ExpectedAggregates[0]),
static_cast<unsigned long long>(ExpectedAggregates[1]),
static_cast<unsigned long long>(ExpectedAggregates[2]),
static_cast<unsigned long long>(ExpectedAggregates[3]));
const UWorld::InitializationValues WorldInitialization = UWorld::InitializationValues()
.AllowAudioPlayback(false).RequiresHitProxies(false).CreatePhysicsScene(true)
.CreateNavigation(false).CreateAISystem(false).ShouldSimulatePhysics(true)
.SetDefaultGameMode(AGameModeBase::StaticClass()).SetTransactional(false);
UWorld* TestWorld = UWorld::CreateWorld(EWorldType::Game, false,
FName(TEXT("VoxelForgeCoreIslandValidationWorld")), GetTransientPackage(),
false, ERHIFeatureLevel::Num, &WorldInitialization);
if (TestWorld == nullptr)
{
OutError = TEXT("Could not create the transient BuildHost island actor world.");
return false;
}
UGameInstance* TestGameInstance = NewObject<UGameInstance>(GEngine);
FWorldContext& TestWorldContext = GEngine->CreateNewWorldContext(EWorldType::Game);
TestWorldContext.OwningGameInstance = TestGameInstance;
TestWorldContext.SetCurrentWorld(TestWorld);
TestWorld->SetGameInstance(TestGameInstance);
TestWorld->SetShouldTick(false);
TestGameInstance->Init();
TestWorld->AddToRoot();
struct FIslandWorldCleanup
{
UWorld* World = nullptr;
uint64 PreviousFrameCounter = GFrameCounter;
~FIslandWorldCleanup()
{
if (World != nullptr)
{
if (World->HasBegunPlay())
{
World->BeginTearingDown();
World->EndPlay(EEndPlayReason::Quit);
}
if (UGameInstance* GameInstance = World->GetGameInstance()) GameInstance->Shutdown();
if (GEngine != nullptr) GEngine->DestroyWorldContext(World);
World->DestroyWorld(false);
if (World->IsRooted()) World->RemoveFromRoot();
}
GFrameCounter = PreviousFrameCounter;
}
} Cleanup{TestWorld, GFrameCounter};
if (!TestWorld->SetGameMode(FURL()))
{
OutError = TEXT("Could not create the transient GameMode for island actor validation.");
return false;
}
AGameModeBase* ValidationGameMode = TestWorld->GetAuthGameMode();
if (ValidationGameMode == nullptr)
{
OutError = TEXT("Transient island validation GameMode was not available before startup.");
return false;
}
// Preserve the explicitly placed route pawn; GameMode's default spawn would move Core's first compile to the origin.
// Conserver le pawn de route ; le spawn par defaut du GameMode deplacerait le premier compile Core a l'origine.
ValidationGameMode->DefaultPawnClass = nullptr;
UPlayer* TestPlayer = NewObject<UPlayer>(GEngine, NAME_None, RF_Transient);
APlayerController* PlayerController = TestWorld->SpawnActor<APlayerController>(
APlayerController::StaticClass(), FTransform::Identity,
FActorSpawnParameters());
if (TestPlayer == nullptr || PlayerController == nullptr)
{
OutError = TEXT("Could not spawn a BuildHost route player controller.");
return false;
}
// The world stream anchor reads only the first controller's pawn; use a transient base player
// to avoid online identity initialization in this headless commandlet.
// L'ancre de streaming lit le pawn du premier controleur ; eviter l'initialisation en ligne.
PlayerController->SetPlayer(TestPlayer);
// This transient world has not initialized actors yet, so register the controller explicitly.
// Ce monde transitoire n'a pas encore initialise ses acteurs ; enregistrer le controleur.
TestWorld->AddController(PlayerController);
APawn* PlayerPawn = TestWorld->SpawnActor<APawn>(APawn::StaticClass(),
CameraLocalCm, FRotator::ZeroRotator);
if (PlayerPawn == nullptr)
{
OutError = TEXT("Could not spawn the BuildHost island route player.");
return false;
}
USceneComponent* PawnRoot = NewObject<USceneComponent>(PlayerPawn, NAME_None, RF_Transient);
if (PawnRoot == nullptr)
{
OutError = TEXT("Could not create the BuildHost island route pawn root.");
return false;
}
PlayerPawn->SetRootComponent(PawnRoot);
PawnRoot->RegisterComponent();
PlayerPawn->SetActorLocation(CameraLocalCm, false, nullptr, ETeleportType::TeleportPhysics);
PlayerController->Possess(PlayerPawn);
APlayerController* FirstPlayerController = TestWorld->GetFirstPlayerController();
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreIslandTest] player_preflight first=%s manual=%s first_pawn=%s route_pawn=%s route_controller=%s first_matches=%d possess_matches=%d position_matches=%d route_position=(%.1f,%.1f,%.1f) world_netmode=%d"),
*GetNameSafe(FirstPlayerController), *GetNameSafe(PlayerController),
*GetNameSafe(FirstPlayerController ? FirstPlayerController->GetPawn() : nullptr),
*GetNameSafe(PlayerPawn), *GetNameSafe(PlayerPawn->GetController()),
FirstPlayerController == PlayerController ? 1 : 0,
PlayerController->GetPawn() == PlayerPawn ? 1 : 0,
PlayerPawn->GetActorLocation().Equals(CameraLocalCm, 0.1) ? 1 : 0,
PlayerPawn->GetActorLocation().X, PlayerPawn->GetActorLocation().Y,
PlayerPawn->GetActorLocation().Z, static_cast<int32>(TestWorld->GetNetMode()));
if (FirstPlayerController != PlayerController
|| PlayerController->GetPawn() != PlayerPawn
|| !PlayerPawn->GetActorLocation().Equals(CameraLocalCm, 0.1))
{
OutError = TEXT("BuildHost route controller did not retain the route pawn before Core initialization.");
return false;
}
// Spawn and possess the route pawn before AVoxelWorld begins play. Its initial Core snapshot
// must compile the same route-centered clipmap that the Standalone reference audits.
// Creer le joueur avant le monde voxel afin que le premier clipmap Core soit centre sur la route.
AVoxelWorld* Actor = TestWorld->SpawnActorDeferred<AVoxelWorld>(
AVoxelWorld::StaticClass(), FTransform::Identity, nullptr, nullptr,
ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
if (Actor == nullptr)
{
OutError = TEXT("Could not spawn the transient AVoxelWorld for island validation.");
return false;
}
Actor->Settings = TestSettings.Get();
Actor->bManageAtmosphere = true;
Actor->bUseVoxelForgeCore = true;
Actor->CoreVoxelSizeCm = Arguments.CoreVoxelSizeCm;
Actor->CoreMesherRecipe = EVoxelForgeCoreMesherRecipe::MarchingCubesV1;
Actor->CoreClipRadius = 3;
Actor->CoreMaxClipLevel = 8;
Actor->bCaptureVoxelForgeCoreHashesForValidation = true;
Actor->FinishSpawning(FTransform::Identity);
TestWorld->InitializeActorsForPlay(FURL());
TestWorld->BeginPlay();
// GameMode may restart manually spawned controllers at BeginPlay and replace their pawn.
// Reassert the route pawn after startup so both the first Core request and later clipmap
// comparisons stay tied to the authored island route.
// Le GameMode peut remplacer le pawn au demarrage ; reprendre explicitement le pawn de route.
PlayerPawn->SetActorLocation(CameraLocalCm, false, nullptr, ETeleportType::TeleportPhysics);
PlayerController->Possess(PlayerPawn);
if (PlayerController->GetPawn() != PlayerPawn
|| !PlayerPawn->GetActorLocation().Equals(CameraLocalCm, 0.1))
{
OutError = TEXT("BuildHost GameMode did not preserve the route pawn after BeginPlay.");
return false;
}
Actor->SetActorTickEnabled(true);
FString ActorRuntimeJson;
FString ActorRuntimeError;
const bool bActorRuntimeSerialized = VoxelForgeCoreAdapter::SerializeResolvedWorld(
Actor->Settings, Actor->StrateManager, 3, 8, Actor->CoreVoxelSizeCm,
ActorRuntimeJson, ActorRuntimeError);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreIslandTest] world_initialized=1 core=1 actor_initialized=%d player=(%.1f,%.1f,%.1f) possessed=(%.1f,%.1f,%.1f) snapshot_parity=%d expected_bytes=%d actor_bytes=%d"),
Actor->IsActorInitialized() ? 1 : 0,
CameraLocalCm.X, CameraLocalCm.Y, CameraLocalCm.Z,
PlayerController->GetPawn() ? PlayerController->GetPawn()->GetActorLocation().X : 0.0,
PlayerController->GetPawn() ? PlayerController->GetPawn()->GetActorLocation().Y : 0.0,
PlayerController->GetPawn() ? PlayerController->GetPawn()->GetActorLocation().Z : 0.0,
bActorRuntimeSerialized && ActorRuntimeJson == RuntimeJson ? 1 : 0,
RuntimeJson.Len(), ActorRuntimeJson.Len());
const auto DesiredTilesLoaded = [](const AVoxelWorld* World)
{
if (World == nullptr || World->DesiredSorted.IsEmpty()) return false;
for (const FVoxelTileKey& Key : World->DesiredSorted)
if (!World->LoadedTiles.Contains(Key)) return false;
return true;
};
const auto CollisionSetSettled = [](const AVoxelWorld* World)
{
if (World == nullptr) return false;
for (const FVoxelTileKey& Key : World->CollisionDesiredTiles)
if (!World->CollisionReadyTiles.Contains(Key)
&& !World->CollisionNotRequiredTiles.Contains(Key)) return false;
return true;
};
const auto TickWorldUntil = [&](double TimeoutSeconds, const TFunction<bool()>& Predicate)
{
const double Deadline = FPlatformTime::Seconds() + TimeoutSeconds;
double LastProgress = FPlatformTime::Seconds();
int32 Ticks = 0;
while (FPlatformTime::Seconds() < Deadline)
{
TestWorld->Tick(LEVELTICK_All, 0.016f);
++GFrameCounter;
++Ticks;
FPlatformProcess::Sleep(0.001f);
if (Predicate()) return true;
const double Now = FPlatformTime::Seconds();
if (Now - LastProgress >= 10.0)
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreIslandTest] wait ticks=%d ready=%d desired=%d loaded=%d pending=%d cooks=%d active=%d"),
Ticks, Actor->IsVoxelForgeCoreReady() ? 1 : 0, Actor->DesiredSorted.Num(),
Actor->LoadedTiles.Num(), Actor->PendingTiles.Num(),
Actor->PendingCollisionCooks.Num(), Actor->ActiveTaskCount.load(std::memory_order_acquire));
LastProgress = Now;
}
}
return false;
};
if (!TickWorldUntil(600.0, [&]()
{
return Actor->IsVoxelForgeCoreReady() && Actor->bAllChunksLoaded
&& Actor->PendingTiles.IsEmpty() && Actor->PendingCollisionCooks.IsEmpty()
&& Actor->PendingUnload.IsEmpty()
&& Actor->ActiveTaskCount.load(std::memory_order_acquire) == 0
&& DesiredTilesLoaded(Actor) && CollisionSetSettled(Actor);
}))
{
OutError = FString::Printf(
TEXT("BuildHost island clipmap did not settle: ready=%d desired=%d loaded=%d pending=%d cooks=%d active=%d."),
Actor->IsVoxelForgeCoreReady() ? 1 : 0, Actor->DesiredSorted.Num(),
Actor->LoadedTiles.Num(), Actor->PendingTiles.Num(),
Actor->PendingCollisionCooks.Num(), Actor->ActiveTaskCount.load(std::memory_order_acquire));
return false;
}
TSet<FVoxelTileKey> ActualDesiredSet;
TSet<FVoxelTileKey> ActualVisualSet;
TArray<FVoxelTileKey> RuntimeCollisionSupportKeys;
int32 RuntimeCollisionSupportTiles = 0;
for (const FVoxelTileKey& Key : Actor->DesiredSorted) ActualDesiredSet.Add(Key);
for (const FVoxelTileKey& Key : ActualDesiredSet)
{
if (!Actor->CollisionOnlyTiles.Contains(Key))
{
ActualVisualSet.Add(Key);
continue;
}
const FVoxelForgeCoreTileHashRecord* SupportHash = Actor->VoxelForgeCoreTileHashes.Find(Key);
if (!Actor->CollisionDesiredTiles.Contains(Key) || SupportHash == nullptr
|| !SupportHash->bHasHashes
|| (!Actor->CollisionReadyTiles.Contains(Key)
&& !Actor->CollisionNotRequiredTiles.Contains(Key)))
{
OutError = FString::Printf(
TEXT("BuildHost island collision-only support tile (%d,%d,%d) was not hashed and cooked."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z);
return false;
}
++RuntimeCollisionSupportTiles;
RuntimeCollisionSupportKeys.Add(Key);
}
if (ActualVisualSet.Num() != ExpectedDesiredSet.Num())
{
OutError = FString::Printf(
TEXT("BuildHost island visual clipmap has %d tiles; Standalone expected %d (runtime collision-only=%d)."),
ActualVisualSet.Num(), ExpectedDesiredSet.Num(), RuntimeCollisionSupportTiles);
return false;
}
if (!RuntimeCollisionSupportKeys.IsEmpty())
{
FTCHARToUTF8 SupportJson(*RuntimeJson);
std::string SupportError;
std::optional<VoxelForgeCore::ResolvedWorldSnapshot> SupportSnapshot =
VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
std::string_view(SupportJson.Get(), static_cast<std::size_t>(SupportJson.Length())),
SupportError);
if (!SupportSnapshot)
{
OutError = FString::Printf(TEXT("Could not parse the Standalone collision-support snapshot: %s"),
UTF8_TO_TCHAR(SupportError.c_str()));
return false;
}
VoxelForgeCore::World SupportReferenceWorld(std::move(*SupportSnapshot));
std::vector<VoxelForgeCore::TileKey> SupportReferenceKeys;
VoxelForgeCore::ShapeBuildRequest SupportRequest;
SupportRequest.workerLimit = 8;
SupportRequest.detail = VoxelForgeCore::DetailVariant::coherent;
SupportRequest.includeFarWorld = true;
std::int32_t SupportFineMin[3] = {MAX_int32, MAX_int32, MAX_int32};
std::int32_t SupportFineMax[3] = {MIN_int32, MIN_int32, MIN_int32};
std::int32_t SupportClipMin[3] = {MAX_int32, MAX_int32, MAX_int32};
std::int32_t SupportClipMax[3] = {MIN_int32, MIN_int32, MIN_int32};
for (const FVoxelTileKey& Key : RuntimeCollisionSupportKeys)
{
const std::int32_t Extent = 32 << Key.Level;
const std::int32_t Origin[3] = {
Key.Coord.X * Extent, Key.Coord.Y * Extent, Key.Coord.Z * Extent};
for (int32 Axis = 0; Axis < 3; ++Axis)
{
SupportClipMin[Axis] = FMath::Min(SupportClipMin[Axis], Origin[Axis]);
SupportClipMax[Axis] = FMath::Max(SupportClipMax[Axis], Origin[Axis] + Extent);
if (Key.Level < SupportReferenceWorld.configuration().fullResolutionLodCount)
{
SupportFineMin[Axis] = FMath::Min(SupportFineMin[Axis], Origin[Axis]);
SupportFineMax[Axis] = FMath::Max(SupportFineMax[Axis], Origin[Axis] + Extent);
}
}
SupportReferenceKeys.push_back({
{Key.Coord.X, Key.Coord.Y, Key.Coord.Z}, static_cast<std::int32_t>(Key.Level)});
}
if (SupportClipMin[0] == MAX_int32)
{
OutError = TEXT("Standalone collision-support tile bounds are invalid.");
return false;
}
if (SupportFineMin[0] == MAX_int32)
{
for (int32 Axis = 0; Axis < 3; ++Axis)
{
SupportFineMin[Axis] = SupportClipMin[Axis];
SupportFineMax[Axis] = SupportClipMax[Axis];
}
}
SupportRequest.minVoxel = {SupportFineMin[0], SupportFineMin[1], SupportFineMin[2]};
SupportRequest.maxVoxel = {SupportFineMax[0], SupportFineMax[1], SupportFineMax[2]};
SupportRequest.clipMinVoxel = {SupportClipMin[0], SupportClipMin[1], SupportClipMin[2]};
SupportRequest.clipMaxVoxel = {SupportClipMax[0], SupportClipMax[1], SupportClipMax[2]};
FStandaloneCoreAdapterScheduler SupportScheduler;
if (!SupportReferenceWorld.compile(SupportRequest, SupportScheduler, SupportError))
{
OutError = FString::Printf(TEXT("Standalone collision-support compile failed: %s"),
UTF8_TO_TCHAR(SupportError.c_str()));
return false;
}
SupportReferenceWorld.retainTileRegions(SupportReferenceKeys);
VoxelForgeCore::TileGenerationOptions SupportOptions;
SupportOptions.hashDensity = true;
for (const FVoxelTileKey& Key : RuntimeCollisionSupportKeys)
{
const VoxelForgeCore::TileKey SupportKey{
{Key.Coord.X, Key.Coord.Y, Key.Coord.Z}, static_cast<std::int32_t>(Key.Level)};
const std::optional<VoxelForgeCore::TileMesh> SupportMesh =
SupportReferenceWorld.generateTile(SupportKey, SupportOptions, SupportError);
const FVoxelForgeCoreTileHashRecord* Actual = Actor->VoxelForgeCoreTileHashes.Find(Key);
if (!SupportMesh || !SupportMesh->hasDensityHash || !SupportMesh->hasMeshHash
|| !SupportMesh->hasPayloadHash || !SupportMesh->hasTopologyHash
|| Actual == nullptr || !Actual->bHasHashes
|| Actual->Density != SupportMesh->densityHash
|| Actual->Mesh != SupportMesh->meshHash
|| Actual->Payload != SupportMesh->payloadHash
|| Actual->Topology != SupportMesh->topologyHash
|| Actual->Triangles != static_cast<int32>(SupportMesh->indices.size() / 3u)
|| Actual->bEmpty != SupportMesh->indices.empty())
{
OutError = FString::Printf(
TEXT("BuildHost collision-support tile (%d,%d,%d) does not match Standalone."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z);
return false;
}
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreIslandTest] collision_support_hashes=PASS standalone_tiles=%d"),
RuntimeCollisionSupportKeys.Num());
}
for (const FVoxelTileKey& Key : ExpectedDesiredSet)
{
const FVoxelForgeCoreTileHashRecord* Actual = Actor->VoxelForgeCoreTileHashes.Find(Key);
const FVoxelForgeCoreTileHashRecord* Expected = ExpectedHashes.Find(Key);
if (!ActualVisualSet.Contains(Key) || Actual == nullptr || Expected == nullptr
|| !Actual->bHasHashes || Actual->Density != Expected->Density
|| Actual->Mesh != Expected->Mesh || Actual->Payload != Expected->Payload
|| Actual->Topology != Expected->Topology || Actual->Triangles != Expected->Triangles
|| Actual->bEmpty != Expected->bEmpty)
{
OutError = FString::Printf(
TEXT("BuildHost island tile hash mismatch at (%d,%d,%d) LOD%d: "
"density=%016llx/%016llx mesh=%016llx/%016llx payload=%016llx/%016llx "
"topology=%016llx/%016llx triangles=%d/%d empty=%d/%d hashes=%d/%d."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z, Key.Level,
static_cast<unsigned long long>(Actual ? Actual->Density : 0ull),
static_cast<unsigned long long>(Expected ? Expected->Density : 0ull),
static_cast<unsigned long long>(Actual ? Actual->Mesh : 0ull),
static_cast<unsigned long long>(Expected ? Expected->Mesh : 0ull),
static_cast<unsigned long long>(Actual ? Actual->Payload : 0ull),
static_cast<unsigned long long>(Expected ? Expected->Payload : 0ull),
static_cast<unsigned long long>(Actual ? Actual->Topology : 0ull),
static_cast<unsigned long long>(Expected ? Expected->Topology : 0ull),
Actual ? Actual->Triangles : -1, Expected ? Expected->Triangles : -1,
Actual && Actual->bEmpty ? 1 : 0, Expected && Expected->bEmpty ? 1 : 0,
Actual && Actual->bHasHashes ? 1 : 0, Expected && Expected->bHasHashes ? 1 : 0);
return false;
}
}
for (const FVoxelTileKey& Key : Actor->CollisionDesiredTiles)
{
const FVoxelForgeCoreTileHashRecord* Expected = ExpectedHashes.Find(Key);
if (Expected == nullptr && Actor->CollisionOnlyTiles.Contains(Key))
{
const FVoxelForgeCoreTileHashRecord* SupportHash = Actor->VoxelForgeCoreTileHashes.Find(Key);
if (SupportHash == nullptr || !SupportHash->bHasHashes
|| (!Actor->CollisionReadyTiles.Contains(Key)
&& !Actor->CollisionNotRequiredTiles.Contains(Key)))
{
OutError = FString::Printf(
TEXT("BuildHost island collision-only support tile (%d,%d,%d) has no cooked collision."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z);
return false;
}
continue;
}
if (Expected == nullptr
|| (Expected->bEmpty && !Actor->CollisionNotRequiredTiles.Contains(Key))
|| (!Expected->bEmpty && !Actor->CollisionReadyTiles.Contains(Key)))
{
OutError = FString::Printf(TEXT("BuildHost island collision state differs from Standalone for (%d,%d,%d)."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z);
return false;
}
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreIslandTest] pass phase=initial standalone_visual_tiles=%d collision_support_extra=%d collision_desired=%d collision_ready=%d collision_empty=%d standalone_hashes=1 lod0=%d lod1=%d transitions=%d"),
ExpectedDesiredSet.Num(), RuntimeCollisionSupportTiles, Actor->CollisionDesiredTiles.Num(),
Actor->CollisionReadyTiles.Num(), Actor->CollisionNotRequiredTiles.Num(),
Lod0Tiles, Lod1Tiles, TransitionFaces);
const auto LocalPointFromSubvoxels = [CoreVoxelSizeCm](const std::int64_t Position[3])
{
return FVector(
static_cast<double>(Position[0]) * CoreVoxelSizeCm / 256.0,
static_cast<double>(Position[1]) * CoreVoxelSizeCm / 256.0,
static_cast<double>(Position[2]) * CoreVoxelSizeCm / 256.0);
};
const FVector IslandTopXY = LocalPointFromSubvoxels(
NativeIslandPlan.manifest.plan.islands.front().centerSubvoxels.data());
const auto& Connector = NativeIslandPlan.manifest.plan.clusterEdges.front();
const FVector SpanXY(
(static_cast<double>(Connector.startSubvoxels[0] + Connector.endSubvoxels[0]) * 0.5)
* CoreVoxelSizeCm / 256.0,
(static_cast<double>(Connector.startSubvoxels[1] + Connector.endSubvoxels[1]) * 0.5)
* CoreVoxelSizeCm / 256.0,
(static_cast<double>(Connector.startSubvoxels[2] + Connector.endSubvoxels[2]) * 0.5)
* CoreVoxelSizeCm / 256.0);
float IslandContentGroundCm = 0.0f;
float SpanContentGroundCm = 0.0f;
if (Actor->ContentManager == nullptr
|| !Actor->ContentManager->QueryNativeFloatingIslandAnchor(IslandStrateIndex,
FVector2D(IslandTopXY.X, IslandTopXY.Y), IslandContentGroundCm)
|| !Actor->ContentManager->QueryNativeFloatingIslandAnchor(IslandStrateIndex,
FVector2D(SpanXY.X, SpanXY.Y), SpanContentGroundCm))
{
OutError = TEXT("Core island content could not resolve the island-top and connector anchors.");
return false;
}
const auto CheckTerrainAnchor = [&](const TCHAR* Label, const FVector& XY,
float ContentGroundCm, float& OutTerrainGroundWorldZ, FVector& OutGroundWorld) -> bool
{
const FVector StartLocal(XY.X, XY.Y, ContentGroundCm + 400.0f);
const FVector StartWorld = Actor->GetActorTransform().TransformPosition(StartLocal);
float TerrainGroundWorldZ = 0.0f, TerrainCeilingWorldZ = 0.0f;
if (!Actor->GetVoxelSurfaceHeightAt(StartWorld, TerrainGroundWorldZ, TerrainCeilingWorldZ))
{
OutError = FString::Printf(TEXT("Core island %s height query found no ground from air."), Label);
return false;
}
const FVector GroundWorld = Actor->GetActorTransform().TransformPosition(
FVector(XY.X, XY.Y, ContentGroundCm));
OutTerrainGroundWorldZ = TerrainGroundWorldZ;
OutGroundWorld = FVector(GroundWorld.X, GroundWorld.Y, TerrainGroundWorldZ);
const double DeltaCm = FMath::Abs(TerrainGroundWorldZ - GroundWorld.Z);
if (DeltaCm > 25.0)
{
OutError = FString::Printf(TEXT("Core island %s terrain/content anchors differ by %.2f cm."),
Label, DeltaCm);
return false;
}
return true;
};
float IslandTerrainGroundWorldZ = 0.0f, SpanTerrainGroundWorldZ = 0.0f;
FVector IslandGroundWorld = FVector::ZeroVector, SpanGroundWorld = FVector::ZeroVector;
if (!CheckTerrainAnchor(TEXT("top"), IslandTopXY, IslandContentGroundCm,
IslandTerrainGroundWorldZ, IslandGroundWorld)
|| !CheckTerrainAnchor(TEXT("span"), SpanXY, SpanContentGroundCm,
SpanTerrainGroundWorldZ, SpanGroundWorld))
{
return false;
}
const int32 IslandStrate = Actor->GetStrateAtPosition(IslandGroundWorld);
const int32 SpanStrate = Actor->GetStrateAtPosition(SpanGroundWorld);
if (IslandStrate != IslandStrateIndex || SpanStrate != IslandStrateIndex
|| Actor->AtmosphereManager == nullptr)
{
OutError = FString::Printf(TEXT("Island top/span resolved to wrong strate: expected=%d island=%d span=%d atmosphere=%d."),
IslandStrateIndex, IslandStrate, SpanStrate, Actor->AtmosphereManager ? 1 : 0);
return false;
}
Actor->AtmosphereManager->UpdateForPlayer(
Actor->GetActorTransform().TransformPosition(CameraLocalCm));
const int32 AtmosphereStrate = Actor->AtmosphereManager->GetCurrentStrateIndex();
if (AtmosphereStrate != IslandStrateIndex)
{
OutError = FString::Printf(TEXT("Island route selected atmosphere strate %d instead of %d."),
AtmosphereStrate, IslandStrateIndex);
return false;
}
bool bContentHasWater = false;
float ContentWaterLocalZCm = -FLT_MAX;
if (!Actor->ContentManager->QueryStrateWaterAnchor(
IslandStrateIndex, bContentHasWater, ContentWaterLocalZCm))
{
OutError = TEXT("Core island content did not return the fixed strate water datum.");
return false;
}
float ManagerTopZ = 0.0f, ManagerBottomZ = 0.0f;
const float ManagerRouteZ = Actor->ToStrateManagerLocalZ(CameraLocalCm.Z);
if (!Actor->StrateManager->GetStrateUnrealZRange(
ManagerRouteZ, ManagerTopZ, ManagerBottomZ))
{
OutError = TEXT("Core island atmosphere check could not resolve corrected strate bounds.");
return false;
}
const float AtmosphereTopCm = Actor->FromStrateManagerLocalZ(ManagerTopZ);
const float AtmosphereBottomCm = Actor->FromStrateManagerLocalZ(ManagerBottomZ);
if (IslandContentGroundCm < AtmosphereBottomCm || IslandContentGroundCm > AtmosphereTopCm
|| (bContentHasWater && (ContentWaterLocalZCm < AtmosphereBottomCm
|| ContentWaterLocalZCm > AtmosphereTopCm)))
{
OutError = TEXT("Island ground or fixed water datum falls outside the resolved atmosphere strate.");
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreIslandTest] height_anchor_agreement=PASS island_ground_cm=%.3f island_terrain_world_z=%.3f span_ground_cm=%.3f span_terrain_world_z=%.3f corrected_strate=%d atmosphere_strate=%d water_enabled=%d water_local_cm=%.3f atmosphere_bounds_cm=(%.3f,%.3f)"),
IslandContentGroundCm, IslandTerrainGroundWorldZ, SpanContentGroundCm,
SpanTerrainGroundWorldZ, IslandStrate, AtmosphereStrate,
bContentHasWater ? 1 : 0, ContentWaterLocalZCm, AtmosphereBottomCm, AtmosphereTopCm);
const auto CheckCapsuleContact = [&](const TCHAR* Label, const FVector& GroundWorld) -> bool
{
const FVector Start = GroundWorld + FVector(0.0, 0.0, 200.0);
const FVector End = GroundWorld + FVector(0.0, 0.0, 60.0);
const FCollisionShape Capsule = FCollisionShape::MakeCapsule(34.8f, 90.1f);
FCollisionQueryParams QueryParams(
SCENE_QUERY_STAT(VoxelForgeCoreIslandCapsuleContact), false);
FHitResult Hit;
if (!TestWorld->SweepSingleByChannel(
Hit, Start, End, FQuat::Identity, ECC_Pawn, Capsule, QueryParams)
|| Hit.GetActor() != Actor)
{
OutError = FString::Printf(TEXT("BuildHost capsule did not contact the Core island %s."), Label);
return false;
}
const FVector LocalHit = Actor->GetActorTransform().InverseTransformPosition(Hit.ImpactPoint);
const double CoreTileExtentCm = static_cast<double>(CHUNK_SIZE) * Arguments.CoreVoxelSizeCm;
const FVoxelTileKey Tile(FIntVector(
FMath::FloorToInt(LocalHit.X / CoreTileExtentCm),
FMath::FloorToInt(LocalHit.Y / CoreTileExtentCm),
FMath::FloorToInt(LocalHit.Z / CoreTileExtentCm)), 0);
if (!Actor->CollisionReadyTiles.Contains(Tile)
|| FVector::Dist(Hit.ImpactPoint, GroundWorld) > 100.0)
{
OutError = FString::Printf(TEXT("Core island %s capsule hit is outside its cooked support tile."), Label);
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreIslandTest] capsule_contact=PASS surface=%s tile=(%d,%d,%d) hit_cm=(%.1f,%.1f,%.1f) collision_ready=1"),
Label, Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z,
Hit.ImpactPoint.X, Hit.ImpactPoint.Y, Hit.ImpactPoint.Z);
return true;
};
if (!CheckCapsuleContact(TEXT("island_top"), IslandGroundWorld)
|| !CheckCapsuleContact(TEXT("span"), SpanGroundWorld))
{
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreIslandTest] content_anchor=PASS source=native_island_field anchors=island_top,connector_span terrain_agreement=PASS"));
FVector CarveLocal(IslandTopXY.X, IslandTopXY.Y, IslandContentGroundCm - 40.0f);
const FVector CarveWorld = Actor->GetActorTransform().TransformPosition(CarveLocal);
float DensityBefore = 0.0f, OutsideBefore = 0.0f;
if (!Actor->QueryVoxelForgeCoreDensityAt(CarveWorld, DensityBefore)
|| !Actor->QueryVoxelForgeCoreDensityAt(CarveWorld + FVector(90, 0, 0), OutsideBefore)
|| DensityBefore >= 0.0f)
{
OutError = TEXT("Island Blueprint edit probe did not begin inside solid rock.");
return false;
}
Actor->CarveAtPosition(CarveWorld, 6.0f, 10.0f);
float CarvedDensity = 0.0f, OutsideAfterCarve = 0.0f;
if (!Actor->QueryVoxelForgeCoreDensityAt(CarveWorld, CarvedDensity)
|| !Actor->QueryVoxelForgeCoreDensityAt(CarveWorld + FVector(90, 0, 0), OutsideAfterCarve)
|| CarvedDensity <= DensityBefore || FMath::Abs(OutsideAfterCarve - OutsideBefore) > 1.0e-4f)
{
OutError = TEXT("Island Blueprint carve did not add air inside 75 cm or changed a point 90 cm away.");
return false;
}
Actor->FillAtPosition(CarveWorld, 6.0f, 10.0f);
float FilledDensity = 0.0f, OutsideAfterFill = 0.0f;
if (!Actor->QueryVoxelForgeCoreDensityAt(CarveWorld, FilledDensity)
|| !Actor->QueryVoxelForgeCoreDensityAt(CarveWorld + FVector(90, 0, 0), OutsideAfterFill)
|| FilledDensity >= CarvedDensity || FMath::Abs(OutsideAfterFill - OutsideBefore) > 1.0e-4f)
{
OutError = TEXT("Island Blueprint fill did not restore solid direction or changed a point 90 cm away.");
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreIslandTest] blueprint_edit=PASS carve_sign=air fill_sign=solid radius_legacy_voxels=6 radius_cm=%.3f strength_legacy=10 density_before=%.4f carved=%.4f filled=%.4f outside_90cm_delta=%.6f"),
6.0 * static_cast<double>(VOXEL_SIZE),
DensityBefore, CarvedDensity, FilledDensity,
FMath::Abs(OutsideAfterFill - OutsideBefore));
const FString CoreSeasonHash = Actor->GetCurrentSeasonContentHash();
const float LegacyDensityBeforeSwitch = Actor->Generator->GetDensityAt(
IslandTopXY.X / VOXEL_SIZE, IslandTopXY.Y / VOXEL_SIZE,
IslandContentGroundCm / VOXEL_SIZE);
Actor->bUseVoxelForgeCore = false;
float DisabledAnchorZ = 0.0f;
const bool bDisabledAnchorAvailable = Actor->ContentManager->QueryNativeFloatingIslandAnchor(
1, FVector2D(IslandTopXY.X, IslandTopXY.Y), DisabledAnchorZ);
const FString LegacySeasonHash = Actor->GetCurrentSeasonContentHash();
const float LegacyDensityAfterSwitch = Actor->Generator->GetDensityAt(
IslandTopXY.X / VOXEL_SIZE, IslandTopXY.Y / VOXEL_SIZE,
IslandContentGroundCm / VOXEL_SIZE);
if (bDisabledAnchorAvailable || Actor->bUseVoxelForgeCore
|| FMath::Abs(LegacyDensityAfterSwitch - LegacyDensityBeforeSwitch) > 1.0e-6f
|| CoreSeasonHash.IsEmpty() || CoreSeasonHash == LegacySeasonHash)
{
OutError = TEXT("Core-off island check changed legacy generation or retained Core manifest anchors/hash.");
return false;
}
Actor->bUseVoxelForgeCore = true;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreIslandTest] switch_off_legacy=PASS core_hash=%s off_hash=%s native_anchor_when_disabled=0 legacy_voxel_cm=%.1f legacy_density_before=%.6f after=%.6f"),
*CoreSeasonHash, *LegacySeasonHash, VOXEL_SIZE,
LegacyDensityBeforeSwitch, LegacyDensityAfterSwitch);
if (!RunCoreContentPlacementCostAudit(Actor,
FVector(IslandTopXY.X, IslandTopXY.Y, IslandContentGroundCm + 100.0f),
TEXT("FloatingIslands"), OutError))
{
return false;
}
UE_LOG(LogTemp, Display, TEXT("[VoxelForgeCoreIslandTest] pass complete=1"));
return true;
}
bool RunCoreContentPlacementCostAudit(AVoxelWorld* Actor, const FVector& CoreLocalProbeCm,
const TCHAR* Label, FString& OutError)
{
if (Actor == nullptr || Actor->ContentManager == nullptr)
{
OutError = TEXT("Content placement cost audit has no owner actor or content manager.");
return false;
}
constexpr int32 SampleCells = 64;
constexpr int32 SampleFrames = 32;
const float CoreVoxelSizeCm = Actor->CoreVoxelSizeCm;
const FTransform OwnerXf = Actor->GetActorTransform();
FString AssemblyJson, AssemblyHash;
if (Actor->GetVoxelForgeCoreWorldAssembly(AssemblyJson,AssemblyHash))
{
const FString ExportDir=FPaths::Combine(FPaths::ProjectSavedDir(),TEXT("CoreWorldValidation/Output"));
IFileManager::Get().MakeDirectory(*ExportDir,true);
FFileHelper::SaveStringToFile(AssemblyJson,
*FPaths::Combine(ExportDir,FString::Printf(TEXT("s2_assembly_%s.json"),Label)));
}
UVoxelContentManager::FDecorationPlacementCostAudit CoreAudit;
Actor->bUseVoxelForgeCore = true;
const FVector CorePlayerWorld = OwnerXf.TransformPosition(CoreLocalProbeCm);
if (!Actor->ContentManager->MeasurePlacementCostForValidation(
CorePlayerWorld, SampleCells, SampleFrames, CoreAudit, OutError))
{
return false;
}
UVoxelContentManager::FDecorationPlacementCostAudit LegacyAudit;
Actor->bUseVoxelForgeCore = false;
const float CoreToOwnerScale = VOXEL_SIZE / CoreVoxelSizeCm;
const FVector LegacyPlayerWorld = OwnerXf.TransformPosition(CoreLocalProbeCm * CoreToOwnerScale);
if (!Actor->ContentManager->MeasurePlacementCostForValidation(
LegacyPlayerWorld, SampleCells, SampleFrames, LegacyAudit, OutError))
{
Actor->bUseVoxelForgeCore = true;
return false;
}
Actor->bUseVoxelForgeCore = true;
if (CoreAudit.bSyntheticWorkload && CoreAudit.AnchorChecks > 0
&& (CoreAudit.MaximumAnchorErrorCm > 0.5 || CoreAudit.MaximumDensityResidual > 0.25))
{
OutError = FString::Printf(
TEXT("Core synthetic decoration anchors drifted: height_error_cm=%.6f density_residual=%.6f."),
CoreAudit.MaximumAnchorErrorCm, CoreAudit.MaximumDensityResidual);
return false;
}
if (LegacyAudit.bSyntheticWorkload && LegacyAudit.AnchorChecks > 0
&& LegacyAudit.MaximumDensityResidual > 0.25)
{
OutError = FString::Printf(
TEXT("Legacy synthetic decoration anchors drifted: density_residual=%.6f."),
LegacyAudit.MaximumDensityResidual);
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeContentCost] label=%s route=core voxel_cm=%.3f tile_cell_cm=%.3f authored_entries=%d synthetic=%d cells=%d columns_per_cell=%d density_queries_per_cell=%.2f surface_queries_per_cell=%.2f cell_median_ms=%.5f cell_p95_ms=%.5f update_median_ms=%.5f update_p95_ms=%.5f placements=%d anchor_checks=%d max_anchor_error_cm=%.6f max_density_residual=%.6f"),
Label, CoreVoxelSizeCm, CHUNK_SIZE * CoreVoxelSizeCm,
CoreAudit.NearEntries + CoreAudit.FarEntries, CoreAudit.bSyntheticWorkload ? 1 : 0,
CoreAudit.SampledCells, CoreAudit.ColumnsPerCell, CoreAudit.DensityQueriesPerCell,
CoreAudit.SurfaceQueriesPerCell, CoreAudit.CellMedianMilliseconds,
CoreAudit.CellP95Milliseconds, CoreAudit.UpdateMedianMilliseconds,
CoreAudit.UpdateP95Milliseconds, CoreAudit.Placements, CoreAudit.AnchorChecks,
CoreAudit.MaximumAnchorErrorCm, CoreAudit.MaximumDensityResidual);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeContentCostS2] label=%s authored_full_p95_ms=%.5f authored_candidate_p95_ms=%.5f authored_active_batch_full_ms=%.5f authored_active_batch_candidate_ms=%.5f populated_cells=%d populated_placements=%d populated_full_p95_ms=%.5f populated_candidate_p95_ms=%.5f exact_transform_comparisons=%d anchor_checks=%d max_density_residual=%.6f"),
Label,CoreAudit.FullScanCellP95Milliseconds,CoreAudit.CellP95Milliseconds,
CoreAudit.FullScanActiveBatchMilliseconds,CoreAudit.ActiveBatchMilliseconds,
CoreAudit.PopulatedCells,CoreAudit.PopulatedPlacements,
CoreAudit.FullScanPopulatedCellP95Milliseconds,CoreAudit.PopulatedCellP95Milliseconds,
CoreAudit.ExactPlacementComparisons,CoreAudit.AnchorChecks,CoreAudit.MaximumDensityResidual);
UE_LOG(LogTemp,Display,
TEXT("[VoxelForgeContentActiveS2] label=%s cells=%d placements=%d full_batch_ms=%.5f candidate_batch_ms=%.5f merge_ms=%.5f apply_ms=%.5f applied_instances=%d full_total_cpu_ms=%.5f candidate_total_cpu_ms=%.5f rhi=null"),
Label,CoreAudit.SampledCells,CoreAudit.PopulatedPlacements,
CoreAudit.FullScanPopulatedBatchMilliseconds,CoreAudit.PopulatedBatchMilliseconds,
CoreAudit.PopulatedMergeMilliseconds,CoreAudit.PopulatedApplyMilliseconds,CoreAudit.AppliedInstances,
CoreAudit.FullScanPopulatedBatchMilliseconds+CoreAudit.PopulatedMergeMilliseconds+CoreAudit.PopulatedApplyMilliseconds,
CoreAudit.PopulatedBatchMilliseconds+CoreAudit.PopulatedMergeMilliseconds+CoreAudit.PopulatedApplyMilliseconds);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeContentCost] label=%s route=legacy voxel_cm=%.3f tile_cell_cm=%.3f authored_entries=%d synthetic=%d cells=%d columns_per_cell=%d density_queries_per_cell=%.2f surface_queries_per_cell=%.2f cell_median_ms=%.5f cell_p95_ms=%.5f update_median_ms=%.5f update_p95_ms=%.5f placements=%d anchor_checks=%d max_anchor_error_cm=%.6f max_density_residual=%.6f"),
Label, VOXEL_SIZE, CHUNK_SIZE * VOXEL_SIZE,
LegacyAudit.NearEntries + LegacyAudit.FarEntries, LegacyAudit.bSyntheticWorkload ? 1 : 0,
LegacyAudit.SampledCells, LegacyAudit.ColumnsPerCell, LegacyAudit.DensityQueriesPerCell,
LegacyAudit.SurfaceQueriesPerCell, LegacyAudit.CellMedianMilliseconds,
LegacyAudit.CellP95Milliseconds, LegacyAudit.UpdateMedianMilliseconds,
LegacyAudit.UpdateP95Milliseconds, LegacyAudit.Placements, LegacyAudit.AnchorChecks,
LegacyAudit.MaximumAnchorErrorCm, LegacyAudit.MaximumDensityResidual);
return true;
}
bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FString& OutError)
{
if (Arguments.bCoreFloatingIslandTest)
{
return RunCoreFloatingIslandActorIntegrationTest(Arguments, OutError);
}
if (Arguments.bCorePlanOnlyTest)
{
return RunCorePlanOnlyTest(Arguments, OutError);
}
if (Arguments.bCoreFallbackTest)
{
return RunCoreFallbackActorTest(Arguments, OutError);
}
if (Arguments.bCoreOffTest)
{
return RunCoreOffActorTest(Arguments, OutError);
}
UVoxelSettings* AuthoredSettings = LoadObject<UVoxelSettings>(
nullptr, *Arguments.SettingsReference);
if (!AuthoredSettings)
{
OutError = FString::Printf(TEXT("Could not load Core actor test settings '%s'."),
*Arguments.SettingsReference);
return false;
}
float StagedWalkSpeedCmPerSecond = 0.0f;
float StagedSprintSpeedCmPerSecond = 0.0f;
if (Arguments.bCoreWorldStreamTraceOnly)
{
StagedWalkSpeedCmPerSecond = 600.0f;
StagedSprintSpeedCmPerSecond = 6000.0f;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgePawnSpeedFixture] source=trace_fixture_defaults walk_cm_s=%.3f sprint_cm_s=%.3f"),
StagedWalkSpeedCmPerSecond, StagedSprintSpeedCmPerSecond);
}
else if (!ReadStagedPlayerSpeeds(
StagedWalkSpeedCmPerSecond, StagedSprintSpeedCmPerSecond, OutError))
{
return false;
}
const int32 OwnerSeed = ResolveCoreTestWorldSeed(AuthoredSettings);
TStrongObjectPtr<UVoxelSettings> OwnerSurfaceSettings;
TStrongObjectPtr<UVoxelStrateDefinition> OwnerSurfaceDefinition;
TArray<TStrongObjectPtr<UVoxelStrateDefinition>> OwnerPlanDefinitions;
if (Arguments.bOwnerFourStratePlan)
{
UVoxelSettings* PlanSettings = DuplicateObject<UVoxelSettings>(
AuthoredSettings, GetTransientPackage(), TEXT("VoxelForgeOwnerFourStrateTraceSettings"));
if (PlanSettings == nullptr)
{
OutError = TEXT("Could not duplicate owner settings for the transient four-strate Core trace.");
return false;
}
PlanSettings->Seed = OwnerSeed;
if (!ApplyOwnerFourStratePlan(PlanSettings, OwnerPlanDefinitions, true, OutError))
{
return false;
}
OwnerSurfaceSettings = TStrongObjectPtr<UVoxelSettings>(PlanSettings);
}
else if (!CreateLoggedOwnerSurfaceSettingsCopy(AuthoredSettings, OwnerSeed,
OwnerSurfaceSettings, OwnerSurfaceDefinition, OutError,
Arguments.bCoreWorldStreamTraceOnly))
{
return false;
}
AuthoredSettings = OwnerSurfaceSettings.Get();
if (Arguments.bCoreHybridFixturesOnly)
{
UVoxelGenerator* FixtureGenerator = NewObject<UVoxelGenerator>(
GetTransientPackage(), NAME_None, RF_Transient);
if (FixtureGenerator == nullptr)
{
OutError = TEXT("Could not allocate the focused hybrid fixture generator.");
return false;
}
FixtureGenerator->InitializeSettings(AuthoredSettings);
if (!RunHybridPluginMesherFixtures(
FixtureGenerator, Arguments.bCoreHybridMesherV2, OutError)) return false;
UE_LOG(LogTemp, Display, TEXT("[WorldAssemblerHybridPluginFixture] fixture_only=PASS"));
return true;
}
if (!Arguments.CoreWorldAssemblyManifestPath.IsEmpty())
{
const FString ManifestPath = FPaths::ConvertRelativePathToFull(
Arguments.CoreWorldAssemblyManifestPath);
if (!IsUnderVoxelForgeSaved(ManifestPath))
{
OutError = TEXT("Core world assembly test input must be staged under the plugin Saved directory.");
return false;
}
FString ManifestJson;
if (!FFileHelper::LoadFileToString(ManifestJson, *ManifestPath))
{
OutError = FString::Printf(TEXT("Could not read staged Core world assembly '%s'."), *ManifestPath);
return false;
}
FString ImportReport;
if (!AuthoredSettings->SetWorldAssemblyManifestJson(ManifestJson, ImportReport))
{
OutError = FString::Printf(TEXT("Could not inject native SurfaceWorld plan: %s"), *ImportReport);
return false;
}
UE_LOG(LogTemp, Display, TEXT("[VoxelForgeCoreWorldTest] %s"), *ImportReport);
}
std::vector<FHybridFixtureContactProbe> HybridFixtureContacts;
FString HybridFixtureManifestHash;
if (Arguments.bCoreHybridFixturePlacement
&& !AddHybridFixturePlacement(AuthoredSettings, Arguments.OutDirectory,
HybridFixtureContacts, HybridFixtureManifestHash, OutError))
{
return false;
}
if (Arguments.bCoreHybridFixturePlacement)
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] hybrid_fixture_placement=1 boxes=9 contacts=%d manifest_hash=%s file=world_assembly_hybrid_fixtures.json"),
static_cast<int32>(HybridFixtureContacts.size()), *HybridFixtureManifestHash);
}
TStrongObjectPtr<UVoxelStrateManager> Manager(NewObject<UVoxelStrateManager>(
GetTransientPackage(), NAME_None, RF_Transient));
int32 ResolvedSeed = AuthoredSettings->Seed;
if (const UVoxelSeasonAsset* Season = AuthoredSettings->Season.LoadSynchronous())
{
ResolvedSeed = Season->GetSeasonSeed();
}
if (!Manager.IsValid() || !Manager->Initialize(AuthoredSettings, ResolvedSeed))
{
OutError = TEXT("Core actor test could not resolve the owner's strate manager.");
return false;
}
FString Json;
if (!VoxelForgeCoreAdapter::SerializeResolvedWorld(
AuthoredSettings, Manager.Get(), 3, 8, Arguments.CoreVoxelSizeCm, Json, OutError))
{
return false;
}
if (Arguments.bCoreWorldStreamTraceOnly)
{
const UWorld::InitializationValues WorldInitialization = UWorld::InitializationValues()
.AllowAudioPlayback(false)
.RequiresHitProxies(false)
.CreatePhysicsScene(true)
.CreateNavigation(false)
.CreateAISystem(false)
.ShouldSimulatePhysics(true)
.SetDefaultGameMode(AGameModeBase::StaticClass())
.SetTransactional(false);
UWorld* TestWorld = UWorld::CreateWorld(
EWorldType::Game, false, FName(TEXT("VoxelForgeCoreStreamTraceWorld")),
GetTransientPackage(), false, ERHIFeatureLevel::Num, &WorldInitialization);
if (TestWorld == nullptr)
{
OutError = TEXT("Could not create the transient BuildHost stream-trace world.");
return false;
}
UGameInstance* TestGameInstance = NewObject<UGameInstance>(GEngine);
FWorldContext& TestWorldContext = GEngine->CreateNewWorldContext(EWorldType::Game);
TestWorldContext.OwningGameInstance = TestGameInstance;
TestWorldContext.SetCurrentWorld(TestWorld);
TestWorld->SetGameInstance(TestGameInstance);
TestWorld->SetShouldTick(false);
TestGameInstance->Init();
TestWorld->AddToRoot();
struct FStreamTraceWorldCleanup
{
UWorld* World = nullptr;
uint64 PreviousFrameCounter = GFrameCounter;
~FStreamTraceWorldCleanup()
{
if (World != nullptr)
{
if (World->HasBegunPlay())
{
World->BeginTearingDown();
World->EndPlay(EEndPlayReason::Quit);
}
if (UGameInstance* GameInstance = World->GetGameInstance())
{
GameInstance->Shutdown();
}
if (GEngine != nullptr) GEngine->DestroyWorldContext(World);
World->DestroyWorld(false);
if (World->IsRooted()) World->RemoveFromRoot();
}
GFrameCounter = PreviousFrameCounter;
}
} Cleanup{TestWorld, GFrameCounter};
if (!TestWorld->SetGameMode(FURL()))
{
OutError = TEXT("Could not create the transient BuildHost GameMode for stream tracing.");
return false;
}
if (AGameModeBase* GameMode = TestWorld->GetAuthGameMode())
{
GameMode->DefaultPawnClass = nullptr;
}
UPlayer* TestPlayer = NewObject<UPlayer>(GEngine, NAME_None, RF_Transient);
APlayerController* PlayerController = TestWorld->SpawnActor<APlayerController>(
APlayerController::StaticClass(), FTransform::Identity, FActorSpawnParameters());
if (TestPlayer == nullptr || PlayerController == nullptr)
{
OutError = TEXT("Could not create the BuildHost stream-trace player controller.");
return false;
}
PlayerController->SetPlayer(TestPlayer);
TestWorld->AddController(PlayerController);
APawn* PlayerPawn = TestWorld->SpawnActor<APawn>(
APawn::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator);
if (PlayerPawn == nullptr)
{
OutError = TEXT("Could not create the BuildHost stream-trace pawn.");
return false;
}
FVector StartupTraceSpawnCm = FVector::ZeroVector;
FString StartupTraceSpawnText;
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StartupTraceSpawnCm="),
StartupTraceSpawnText, false))
{
TArray<FString> Components;
StartupTraceSpawnText.ParseIntoArray(Components, TEXT(","), true);
if (Components.Num() != 3
|| !LexTryParseString(StartupTraceSpawnCm.X, *Components[0])
|| !LexTryParseString(StartupTraceSpawnCm.Y, *Components[1])
|| !LexTryParseString(StartupTraceSpawnCm.Z, *Components[2])
|| !FMath::IsFinite(StartupTraceSpawnCm.X)
|| !FMath::IsFinite(StartupTraceSpawnCm.Y)
|| !FMath::IsFinite(StartupTraceSpawnCm.Z))
{
OutError = TEXT("voxel.StartupTraceSpawnCm must be finite X,Y,Z centimetres.");
return false;
}
}
USceneComponent* PawnRoot = NewObject<USceneComponent>(PlayerPawn, NAME_None, RF_Transient);
if (PawnRoot == nullptr)
{
OutError = TEXT("Could not create the BuildHost stream-trace pawn root.");
return false;
}
PlayerPawn->SetRootComponent(PawnRoot);
PawnRoot->RegisterComponent();
PlayerPawn->SetActorLocation(StartupTraceSpawnCm, false, nullptr, ETeleportType::TeleportPhysics);
PlayerController->Possess(PlayerPawn);
if (TestWorld->GetFirstPlayerController() != PlayerController
|| PlayerController->GetPawn() != PlayerPawn)
{
OutError = TEXT("BuildHost stream-trace controller did not retain its pawn.");
return false;
}
const FTransform ActorTransform = FTransform::Identity;
AVoxelWorld* Actor = TestWorld->SpawnActorDeferred<AVoxelWorld>(
AVoxelWorld::StaticClass(), ActorTransform, nullptr, nullptr,
ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
if (Actor == nullptr)
{
OutError = TEXT("Could not spawn the transient AVoxelWorld for stream tracing.");
return false;
}
Actor->Settings = AuthoredSettings;
Actor->bManageAtmosphere = false;
Actor->bUseVoxelForgeCore = true;
Actor->CoreVoxelSizeCm = Arguments.CoreVoxelSizeCm;
Actor->CoreMesherRecipe = Arguments.CoreHybridMesherVersion == 2
? EVoxelForgeCoreMesherRecipe::WorldAssemblerHybridV2
: (Arguments.CoreHybridMesherVersion == 1
? EVoxelForgeCoreMesherRecipe::WorldAssemblerHybridV1
: EVoxelForgeCoreMesherRecipe::MarchingCubesV1);
Actor->CoreClipRadius = 3;
Actor->CoreMaxClipLevel = 8;
// A timing trace must use the production tile path. Core density hashing disables the
// native heightfield band shortcut and measures a different workload; hash validation
// remains enabled in the dedicated parity tests below.
Actor->bCaptureVoxelForgeCoreHashesForValidation = false;
Actor->FinishSpawning(ActorTransform);
TestWorld->InitializeActorsForPlay(FURL());
TestWorld->BeginPlay();
if (!VoxelForgeStartupTrace::IsActive())
{
OutError = TEXT("Core stream trace mode requires -voxel.StartupTraceFile.");
return false;
}
Actor->SetActorTickEnabled(true);
VoxelForgeStartupTrace::RecordEvent(TEXT("trace_spawn_location"), FString::Printf(
TEXT("\"location_cm\":[%s,%s,%s]"),
*FString::SanitizeFloat(StartupTraceSpawnCm.X),
*FString::SanitizeFloat(StartupTraceSpawnCm.Y),
*FString::SanitizeFloat(StartupTraceSpawnCm.Z)));
auto DesiredTilesLoaded = [](const AVoxelWorld* World)
{
if (World == nullptr || World->DesiredSorted.IsEmpty()) return false;
for (const FVoxelTileKey& Key : World->DesiredSorted)
{
if (!World->LoadedTiles.Contains(Key)) return false;
}
return true;
};
auto CollisionSetSettled = [](const AVoxelWorld* World)
{
if (World == nullptr) return false;
for (const FVoxelTileKey& Key : World->CollisionDesiredTiles)
{
if (!World->CollisionReadyTiles.Contains(Key)
&& !World->CollisionNotRequiredTiles.Contains(Key)) return false;
}
return true;
};
const double ActorBeginSeconds = FPlatformTime::Seconds();
constexpr double WalkStartSeconds = 30.0;
constexpr double RunDurationSeconds = 45.0;
float WalkSpeedCmPerSecond = 600.0f;
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestMoveSpeedCmPerSecond="),
WalkSpeedCmPerSecond);
FString WalkDirectionText(TEXT("0,1,0"));
const FString DirectionKey(TEXT("voxel.TestMoveDirection="));
const FString CommandLineText(FCommandLine::Get());
const int32 DirectionKeyIndex = CommandLineText.Find(
DirectionKey, ESearchCase::IgnoreCase, ESearchDir::FromStart);
if (DirectionKeyIndex != INDEX_NONE)
{
WalkDirectionText = CommandLineText.Mid(DirectionKeyIndex + DirectionKey.Len());
const int32 DirectionEnd = WalkDirectionText.Find(
TEXT(" "), ESearchCase::CaseSensitive, ESearchDir::FromStart);
if (DirectionEnd != INDEX_NONE)
{
WalkDirectionText.LeftInline(DirectionEnd, EAllowShrinking::No);
}
WalkDirectionText.TrimStartAndEndInline();
WalkDirectionText.RemoveFromStart(TEXT("\""));
WalkDirectionText.RemoveFromEnd(TEXT("\""));
}
TArray<double> SpawnTickMilliseconds;
TArray<double> WalkTickMilliseconds;
TArray<TPair<double, double>> TickSamples;
bool bInitialSetReady = false;
double InitialSetReadySeconds = -1.0;
while (FPlatformTime::Seconds() - ActorBeginSeconds < RunDurationSeconds)
{
const double TickStartSeconds = FPlatformTime::Seconds();
TestWorld->Tick(LEVELTICK_All, 0.016f);
const double TickMilliseconds =
(FPlatformTime::Seconds() - TickStartSeconds) * 1000.0;
++GFrameCounter;
const double ElapsedSeconds = FPlatformTime::Seconds() - ActorBeginSeconds;
TickSamples.Emplace(ElapsedSeconds, TickMilliseconds);
if (!bInitialSetReady && Actor->IsVoxelForgeCoreReady()
&& Actor->bAllChunksLoaded && Actor->PendingTiles.IsEmpty()
&& Actor->PendingCollisionCooks.IsEmpty() && Actor->PendingUnload.IsEmpty()
&& Actor->ActiveTaskCount.load(std::memory_order_acquire) == 0
&& DesiredTilesLoaded(Actor) && CollisionSetSettled(Actor))
{
bInitialSetReady = true;
InitialSetReadySeconds = ElapsedSeconds;
VoxelForgeStartupTrace::RecordEvent(TEXT("stream_trace_milestone"), FString::Printf(
TEXT("\"name\":\"initial_set_ready\",\"elapsed_s\":%s,\"desired\":%d,\"loaded\":%d,\"collision_desired\":%d,\"collision_ready\":%d"),
*FString::SanitizeFloat(ElapsedSeconds), Actor->DesiredSorted.Num(),
Actor->LoadedTiles.Num(), Actor->CollisionDesiredTiles.Num(),
Actor->CollisionReadyTiles.Num()));
}
FPlatformProcess::Sleep(0.016f);
}
for (const TPair<double, double>& Sample : TickSamples)
{
if (bInitialSetReady && Sample.Key <= InitialSetReadySeconds)
{
SpawnTickMilliseconds.Add(Sample.Value);
}
else if (Sample.Key >= WalkStartSeconds)
{
WalkTickMilliseconds.Add(Sample.Value);
}
}
auto PercentileMilliseconds = [](TArray<double> Values, double Fraction)
{
if (Values.IsEmpty()) return 0.0;
Values.Sort();
const int32 Index = FMath::Clamp(
FMath::CeilToInt(Fraction * static_cast<double>(Values.Num() - 1)),
0, Values.Num() - 1);
return Values[Index];
};
const auto MaxMilliseconds = [](const TArray<double>& Values)
{
double Maximum = 0.0;
for (const double Value : Values) Maximum = FMath::Max(Maximum, Value);
return Maximum;
};
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldStreamTrace] initial_ready=%d initial_ready_s=%.6f walk_start_s=%.3f run_s=%.3f desired=%d loaded=%d "
"spawn_tick_ms_p95=%.3f spawn_tick_ms_max=%.3f walk_tick_ms_p95=%.3f walk_tick_ms_max=%.3f"),
bInitialSetReady ? 1 : 0, InitialSetReadySeconds, WalkStartSeconds,
RunDurationSeconds, Actor->DesiredSorted.Num(), Actor->LoadedTiles.Num(),
PercentileMilliseconds(SpawnTickMilliseconds, 0.95),
MaxMilliseconds(SpawnTickMilliseconds),
PercentileMilliseconds(WalkTickMilliseconds, 0.95),
MaxMilliseconds(WalkTickMilliseconds));
if (!bInitialSetReady)
{
OutError = FString::Printf(
TEXT("Actor-only Core stream trace did not reach initial readiness: ready=%d desired=%d loaded=%d pending=%d cooks=%d active=%d."),
Actor->IsVoxelForgeCoreReady() ? 1 : 0, Actor->DesiredSorted.Num(),
Actor->LoadedTiles.Num(), Actor->PendingTiles.Num(),
Actor->PendingCollisionCooks.Num(),
Actor->ActiveTaskCount.load(std::memory_order_acquire));
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldStreamTrace] pass complete=1 recipe=%s voxel_cm=%.3f walk_speed_cm_s=%.0f walk_direction=%s"),
Arguments.CoreHybridMesherVersion == 2 ? TEXT("WorldAssemblerHybridV2")
: (Arguments.CoreHybridMesherVersion == 1 ? TEXT("WorldAssemblerHybridV1")
: TEXT("MarchingCubesV1")),
Arguments.CoreVoxelSizeCm, WalkSpeedCmPerSecond, *WalkDirectionText);
return true;
}
// Build one Standalone reference before creating the actor world so the two full Core worlds
// never overlap in memory. The reference also precomputes the exact edit result and hashes.
// Construire la référence Standalone avant le monde acteur pour ne jamais garder deux mondes
// Core complets en mémoire simultanément ; elle calcule aussi les hachages après édition.
TMap<FVoxelTileKey, FVoxelForgeCoreTileHashRecord> ExpectedHashes;
TMap<FVoxelTileKey, FVoxelForgeCoreTileHashRecord> ExpectedPostEditHashes;
TMap<FVoxelTileKey, FVoxelForgeCoreTileHashRecord> ExpectedTravelHashes;
TMap<FVoxelTileKey, FVoxelForgeCoreTileHashRecord> ExpectedSurfaceTravelHashes;
std::optional<VoxelForgeCore::TileMesh> ExpectedHybridFixtureVisualMesh;
TSet<FVoxelTileKey> ExpectedInitialDesiredSet;
TSet<FVoxelTileKey> ExpectedTravelDesiredSet;
TSet<FVoxelTileKey> ExpectedSurfaceTravelDesiredSet;
TSet<FVoxelTileKey> ExpectedSurfaceCollisionSet;
int32 ExpectedSurfaceCollisionBodyCount = 0;
int32 ExpectedSurfaceCollisionNoBodyCount = 0;
TArray<FVoxelTileKey> ExpectedAffectedTiles;
FVector CarveLocalPositionCm = FVector::ZeroVector;
FVoxelTileKey CarveTargetTile;
constexpr float CarveBrushRadiusLegacyVoxels = 6.0f;
const double CoreVoxelSizeCm = Arguments.CoreVoxelSizeCm;
const double CarveBrushRadiusCoreVoxels =
CarveBrushRadiusLegacyVoxels * static_cast<double>(VOXEL_SIZE) / CoreVoxelSizeCm;
const int32 CollisionRadius = FMath::Clamp(AuthoredSettings->PawnCollisionRadiusChunks, 1, 4);
FIntVector ColdStartCollisionCenter = FIntVector::ZeroValue;
uint64 ExpectedEditDigest = 0;
std::array<std::uint64_t, 4> InitialAggregates{};
std::array<std::uint64_t, 4> TravelAggregates{};
std::array<std::uint64_t, 4> SurfaceTravelAggregates{};
std::vector<VoxelForgeCore::TileKey> MatchedTimingInitialDesired;
std::vector<VoxelForgeCore::TileKey> MatchedTimingSurfaceDesired;
std::vector<VoxelForgeCore::TileKey> MatchedTimingSurfaceMissingFine;
VoxelForgeCore::ShapeBuildRequest MatchedTimingInitialRequest;
double NativeSurfaceRegionCompileSeconds = 0.0;
double NativeSurfaceTileGenerationSeconds = 0.0;
std::vector<FAssemblerTileTimingSample> NativeSurfaceTileSamples;
std::vector<FAssemblerTileTimingSample> BridgeSurfaceTileSamples;
std::uint64_t NativeSurfaceTriangleCount = 0;
std::size_t ExpectedInitialTileCount = 0;
std::size_t ExpectedTravelTileCount = 0;
std::size_t TravelFineTilesCompiled = 0;
std::size_t TravelRegionsBuilt = 0;
std::int32_t TravelCoverageLocalTiles = 0;
std::int32_t TravelCoverageFaces = 0;
std::int32_t TravelLodTransitionFaces = 0;
std::int32_t TravelTilesWithSkirts = 0;
std::size_t SurfaceTravelFineTilesCompiled = 0;
std::size_t SurfaceTravelRegionsBuilt = 0;
std::int32_t SurfaceTravelCoverageLocalTiles = 0;
std::int32_t SurfaceTravelCoverageFaces = 0;
std::int32_t SurfaceTravelLodTransitionFaces = 0;
std::int32_t SurfaceTravelTilesWithSkirts = 0;
VoxelForgeCore::TileRegionCompileStats TravelCompileStats;
VoxelForgeCore::TileRegionCompileStats SurfaceTravelCompileStats;
const VoxelForgeCore::Vec3d TravelCameraVoxels{-156.75164794921875, 25.093801498413086,
-823.67364501953125};
VoxelForgeCore::Vec3d SurfaceCameraVoxels{};
int32 SurfaceStrateIndex = INDEX_NONE;
uint32 NativeSurfaceBiomeId = 0;
int32 NativeBiomeIdsSampled = 0;
int32 NativeBiomeSampleCount = 0;
bool bNativeBiomeFieldValidated = false;
{
FTCHARToUTF8 Utf8Json(*Json);
std::string CoreError;
std::optional<VoxelForgeCore::ResolvedWorldSnapshot> Snapshot =
VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
std::string_view(Utf8Json.Get(), static_cast<std::size_t>(Utf8Json.Length())),
CoreError);
if (!Snapshot)
{
OutError = FString::Printf(TEXT("Core rejected runtime adapter JSON: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
FString CheckedAssemblyJson;
FString ExpectedAssemblyHash;
FString AssemblyValidationError;
if (!AuthoredSettings->GetWorldAssemblyManifestJson(
CheckedAssemblyJson, ExpectedAssemblyHash, AssemblyValidationError))
{
OutError = FString::Printf(TEXT("Core actor test settings manifest failed validation: %s"),
*AssemblyValidationError);
return false;
}
int32 ExpectedSurfacePlanCount = 0;
int32 ExpectedIslandPlanCount = 0;
for (const FStrateSlot& Slot : Manager->GetLayout())
{
if (Slot.Definition == nullptr) continue;
const ECaveGeneratorType Type = Manager->GetGeneratorTypeForChunk(
FIntVector(0, 0, Slot.TopChunkZ));
ExpectedSurfacePlanCount += Type == ECaveGeneratorType::SurfaceWorld ? 1 : 0;
ExpectedIslandPlanCount += Type == ECaveGeneratorType::FloatingIslands ? 1 : 0;
if (SurfaceStrateIndex == INDEX_NONE && Type == ECaveGeneratorType::SurfaceWorld)
SurfaceStrateIndex = Slot.StrateIndex;
}
const VoxelForgeCore::WorldAssemblyManifest* Assembly = Snapshot->worldAssembly();
if (Assembly == nullptr
|| Assembly->plan.strates.size() != static_cast<std::size_t>(ExpectedSurfacePlanCount)
|| Assembly->floatingIslands.size() != static_cast<std::size_t>(ExpectedIslandPlanCount))
{
OutError = FString::Printf(
TEXT("Core runtime adapter planned SurfaceWorld/island counts %d/%d, expected %d/%d from the resolved owner layout."),
Assembly ? static_cast<int32>(Assembly->plan.strates.size()) : 0,
Assembly ? static_cast<int32>(Assembly->floatingIslands.size()) : 0,
ExpectedSurfacePlanCount, ExpectedIslandPlanCount);
return false;
}
if (!ExpectedAssemblyHash.IsEmpty())
{
const FString SnapshotHash = FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(Assembly->contentHash));
if (!SnapshotHash.Equals(ExpectedAssemblyHash, ESearchCase::IgnoreCase))
{
OutError = TEXT("Core runtime adapter did not preserve the settings world assembly manifest.");
return false;
}
}
else if (!ExpectedSurfacePlanCount && !ExpectedIslandPlanCount)
{
OutError = TEXT("The owner layout has no SurfaceWorld or FloatingIslands plan for this cross-system test.");
return false;
}
const FString SnapshotHash = FString::Printf(TEXT("%016llx"),
static_cast<unsigned long long>(Assembly->contentHash));
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] world_assembly_source=%s world_assembly_hash=%s surface_strates=%d island_strates=%d native=1"),
ExpectedAssemblyHash.IsEmpty() ? TEXT("generated_at_startup") : TEXT("settings_override"),
*SnapshotHash, static_cast<int32>(Assembly->plan.strates.size()),
static_cast<int32>(Assembly->floatingIslands.size()));
// Validate the native Core field's biome assignment, not the legacy decoration query.
// Valider les biomes du champ Core natif, pas la requete historique des decorations.
const VoxelForgeCore::WorldAssemblyManifest* NativeAssembly = Assembly;
const VoxelForgeCore::WorldAssemblyStratumPlanV1* NativeSurfacePlan = nullptr;
if (NativeAssembly != nullptr)
{
for (const VoxelForgeCore::WorldAssemblyStratumPlanV1& Plan : NativeAssembly->plan.strates)
{
if (Plan.strateIndex == SurfaceStrateIndex)
{
NativeSurfacePlan = &Plan;
break;
}
}
}
if (NativeAssembly == nullptr || NativeSurfacePlan == nullptr)
{
OutError = FString::Printf(TEXT("Core runtime snapshot has no native biome plan for owner SurfaceWorld strate %d."),
SurfaceStrateIndex);
return false;
}
const VoxelForgeCore::Heightfield2D NativeSurfaceField(
NativeAssembly->recipe, *NativeSurfacePlan);
if (!NativeSurfaceField.isValid())
{
OutError = TEXT("Core runtime snapshot's native SurfaceWorld heightfield is invalid.");
return false;
}
const int64 SubvoxelsPerVoxel = VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
if (NativeSurfacePlan->route.empty())
{
OutError = TEXT("Generated owner SurfaceWorld plan has no walkable route point.");
return false;
}
SurfaceCameraVoxels = VoxelForgeCore::Vec3d{
static_cast<double>(NativeSurfacePlan->route.front().positionSubvoxels[0]) / SubvoxelsPerVoxel,
static_cast<double>(NativeSurfacePlan->route.front().positionSubvoxels[1]) / SubvoxelsPerVoxel,
static_cast<double>(NativeSurfacePlan->route.front().positionSubvoxels[2]) / SubvoxelsPerVoxel};
const VoxelForgeCore::HeightfieldPointSample SurfaceBiomeSample = NativeSurfaceField.point(
static_cast<int64>(std::llround(SurfaceCameraVoxels.x * SubvoxelsPerVoxel)),
static_cast<int64>(std::llround(SurfaceCameraVoxels.y * SubvoxelsPerVoxel)), 0);
if (SurfaceBiomeSample.biomeId == 0)
{
OutError = TEXT("Native SurfaceWorld heightfield returned no biome at the route camera.");
return false;
}
TSet<uint32> NativeBiomeIds;
constexpr int32 BiomeProbeSide = 64;
constexpr double BiomeProbeSpanVoxels = 3200.0;
for (int32 Y = 0; Y < BiomeProbeSide; ++Y)
for (int32 X = 0; X < BiomeProbeSide; ++X)
{
const double XVoxel = (static_cast<double>(X) / (BiomeProbeSide - 1) - 0.5)
* BiomeProbeSpanVoxels;
const double YVoxel = (static_cast<double>(Y) / (BiomeProbeSide - 1) - 0.5)
* BiomeProbeSpanVoxels;
const VoxelForgeCore::HeightfieldPointSample Sample = NativeSurfaceField.point(
static_cast<int64>(std::llround(XVoxel * SubvoxelsPerVoxel)),
static_cast<int64>(std::llround(YVoxel * SubvoxelsPerVoxel)), 0);
if (Sample.biomeId != 0) NativeBiomeIds.Add(Sample.biomeId);
if (Sample.neighborBiomeId != 0 && Sample.biomeWeightQ16 > 0)
NativeBiomeIds.Add(Sample.neighborBiomeId);
++NativeBiomeSampleCount;
}
if (NativeBiomeIds.Num() < 2)
{
OutError = TEXT("Native SurfaceWorld heightfield did not assign at least two biomes across its review domain.");
return false;
}
NativeSurfaceBiomeId = SurfaceBiomeSample.biomeId;
NativeBiomeIdsSampled = NativeBiomeIds.Num();
bNativeBiomeFieldValidated = true;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] native_biome_field=1 surface_biome_id=%u sampled_biomes=%d samples=%d span_voxels=%.0f"),
NativeSurfaceBiomeId, NativeBiomeIdsSampled, NativeBiomeSampleCount,
BiomeProbeSpanVoxels);
VoxelForgeCore::World ReferenceWorld(std::move(*Snapshot));
// Match the actor's cold-start fallback camera when the transient world has no pawn.
// Aligner la reference sur la camera de repli de l'acteur sans pawn au demarrage.
const VoxelForgeCore::WorldConfiguration& ReferenceConfiguration =
ReferenceWorld.configuration();
const VoxelForgeCore::Vec3d InitialCameraVoxels = ReferenceConfiguration.viewerStartFound
? ReferenceConfiguration.viewerStartVoxels : VoxelForgeCore::Vec3d{};
const std::vector<VoxelForgeCore::TileKey> Desired =
ReferenceWorld.desiredTiles(InitialCameraVoxels);
if (Desired.empty())
{
OutError = TEXT("Standalone Core returned an empty reference clipmap at the cold-start camera.");
return false;
}
ExpectedInitialTileCount = Desired.size();
std::int32_t FineMin[3] = {MAX_int32, MAX_int32, MAX_int32};
std::int32_t FineMax[3] = {MIN_int32, MIN_int32, MIN_int32};
std::int32_t ClipMin[3] = {MAX_int32, MAX_int32, MAX_int32};
std::int32_t ClipMax[3] = {MIN_int32, MIN_int32, MIN_int32};
for (const VoxelForgeCore::TileKey& Key : Desired)
{
const std::int32_t Extent = 32 << Key.lod;
const std::int32_t Origin[3] = {
Key.coordinate.x * Extent, Key.coordinate.y * Extent, Key.coordinate.z * Extent};
for (int32 Axis = 0; Axis < 3; ++Axis)
{
ClipMin[Axis] = FMath::Min(ClipMin[Axis], Origin[Axis]);
ClipMax[Axis] = FMath::Max(ClipMax[Axis], Origin[Axis] + Extent);
if (Key.lod < ReferenceWorld.configuration().fullResolutionLodCount)
{
FineMin[Axis] = FMath::Min(FineMin[Axis], Origin[Axis]);
FineMax[Axis] = FMath::Max(FineMax[Axis], Origin[Axis] + Extent);
}
}
}
if (FineMin[0] == MAX_int32 || ClipMin[0] == MAX_int32)
{
OutError = TEXT("Standalone Core reference clipmap bounds are invalid.");
return false;
}
VoxelForgeCore::ShapeBuildRequest Request;
Request.minVoxel = {FineMin[0], FineMin[1], FineMin[2]};
Request.maxVoxel = {FineMax[0], FineMax[1], FineMax[2]};
Request.clipMinVoxel = {ClipMin[0], ClipMin[1], ClipMin[2]};
Request.clipMaxVoxel = {ClipMax[0], ClipMax[1], ClipMax[2]};
Request.workerLimit = 8;
Request.detail = VoxelForgeCore::DetailVariant::coherent;
Request.includeFarWorld = true;
MatchedTimingInitialDesired = Desired;
MatchedTimingInitialRequest = Request;
FStandaloneCoreAdapterScheduler Scheduler;
if (!ReferenceWorld.compile(Request, Scheduler, CoreError))
{
OutError = FString::Printf(TEXT("Standalone Core reference compile failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
// Match the runtime collision probe around the same cold-start viewer tile.
// Reproduire la sonde de collision autour de la meme tuile de depart a froid.
ColdStartCollisionCenter = FIntVector(
FMath::FloorToInt(InitialCameraVoxels.x / 32.0),
FMath::FloorToInt(InitialCameraVoxels.y / 32.0),
FMath::FloorToInt(InitialCameraVoxels.z / 32.0));
std::vector<VoxelForgeCore::TileKey> ColdStartCollisionTiles;
ColdStartCollisionTiles.reserve(static_cast<std::size_t>(2 * CollisionRadius + 1)
* static_cast<std::size_t>(2 * CollisionRadius + 1)
* static_cast<std::size_t>(2 * CollisionRadius + 1));
for (int32 Z = -CollisionRadius; Z <= CollisionRadius; ++Z)
for (int32 Y = -CollisionRadius; Y <= CollisionRadius; ++Y)
for (int32 X = -CollisionRadius; X <= CollisionRadius; ++X)
ColdStartCollisionTiles.push_back({
{ColdStartCollisionCenter.X + X,
ColdStartCollisionCenter.Y + Y,
ColdStartCollisionCenter.Z + Z}, 0});
if (Arguments.bCoreHybridCliffOnly)
{
// This is the sampled M1 type-4 cliff tile used by the round-2 standalone identity check.
// C'est la tuile de falaise native type 4 utilisee par le controle d'identite hors ligne.
const VoxelForgeCore::TileKey CliffKey{{-22, 13, -58}, 0};
const std::vector<VoxelForgeCore::TileKey> CliffTiles{CliffKey};
ReferenceWorld.retainTileRegions(CliffTiles);
if (!ReferenceWorld.compileTileRegion(CliffTiles, 16, Scheduler, CoreError))
{
OutError = FString::Printf(TEXT("Native SurfaceWorld cliff region compile failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
VoxelForgeCore::TileGenerationOptions McOptions;
McOptions.hashDensity = true;
McOptions.mesherRecipe = VoxelForgeCore::MesherRecipe::marchingCubesV1;
VoxelForgeCore::TileGenerationOptions HybridOptions = McOptions;
HybridOptions.mesherRecipe = Arguments.bCoreHybridMesherV2
? VoxelForgeCore::MesherRecipe::worldAssemblerHybridV2
: VoxelForgeCore::MesherRecipe::worldAssemblerHybridV1;
const auto McMesh = ReferenceWorld.generateTile(CliffKey, McOptions, CoreError);
const auto HybridMesh = ReferenceWorld.generateTile(CliffKey, HybridOptions, CoreError);
if (!McMesh || !HybridMesh || !McMesh->hasMeshHash || !HybridMesh->hasMeshHash)
{
OutError = FString::Printf(TEXT("Native SurfaceWorld cliff tile generation failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
bool bBitIdentical = McMesh->meshHash == HybridMesh->meshHash
&& McMesh->payloadHash == HybridMesh->payloadHash
&& McMesh->topologyHash == HybridMesh->topologyHash
&& McMesh->vertices.size() == HybridMesh->vertices.size()
&& McMesh->indices == HybridMesh->indices;
for (std::size_t I = 0; bBitIdentical && I < McMesh->vertices.size(); ++I)
{
const VoxelForgeCore::MeshVertex& A = McMesh->vertices[I];
const VoxelForgeCore::MeshVertex& B = HybridMesh->vertices[I];
bBitIdentical = A.position.x == B.position.x && A.position.y == B.position.y
&& A.position.z == B.position.z && A.normal.x == B.normal.x
&& A.normal.y == B.normal.y && A.normal.z == B.normal.z
&& A.uv.x == B.uv.x && A.uv.y == B.uv.y
&& A.color.r == B.color.r && A.color.g == B.color.g
&& A.color.b == B.color.b && A.color.a == B.color.a;
}
if (!bBitIdentical)
{
OutError = TEXT("The production hybrid changed the sampled native SurfaceWorld cliff tile.");
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[WorldAssemblerHybridNativeCliff] key=(-22,13,-58) lod=0 vertices=%llu triangles=%llu mesh_hash=0x%016llx bit_identical=1 type4_only=1"),
static_cast<unsigned long long>(McMesh->vertices.size()),
static_cast<unsigned long long>(McMesh->indices.size() / 3u),
static_cast<unsigned long long>(McMesh->meshHash));
return true;
}
if (!RunCoreSharedFeatureCostAudit(ReferenceWorld,Desired,TEXT("surface"),
Assembly,OutError)) return false;
if (FParse::Param(FCommandLine::Get(),TEXT("corefeatureauditonly")))
{
UE_LOG(LogTemp,Display,TEXT("[VoxelForgeSharedFeatureCost] pass complete=1 warmed_routes=1"));
return true;
}
VoxelForgeCore::TileGenerationOptions Options;
Options.hashDensity = true;
if (Arguments.CoreHybridMesherVersion == 2)
Options.mesherRecipe = VoxelForgeCore::MesherRecipe::worldAssemblerHybridV2;
else if (Arguments.CoreHybridMesherVersion == 1)
Options.mesherRecipe = VoxelForgeCore::MesherRecipe::worldAssemblerHybridV1;
else
Options.mesherRecipe = VoxelForgeCore::MesherRecipe::marchingCubesV1;
// Keep collision hashes outside ExpectedInitialDesiredSet; the collision cube is an
// additional runtime request even where it overlaps the visual clipmap.
// Garder les hachages de collision hors de l'ensemble visuel initial, meme s'ils se
// recoupent avec le clipmap.
std::vector<VoxelForgeCore::TileKey> RetainedReferenceTiles = Desired;
RetainedReferenceTiles.insert(RetainedReferenceTiles.end(),
ColdStartCollisionTiles.begin(), ColdStartCollisionTiles.end());
ReferenceWorld.retainTileRegions(RetainedReferenceTiles);
if (!ReferenceWorld.compileTileRegion(ColdStartCollisionTiles, 16, Scheduler, CoreError))
{
OutError = FString::Printf(TEXT("Standalone collision reference compile failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
std::size_t CollisionBodyTiles = 0;
for (const VoxelForgeCore::TileKey& Key : ColdStartCollisionTiles)
{
const std::optional<VoxelForgeCore::TileMesh> Mesh =
ReferenceWorld.generateTile(Key, Options, CoreError);
if (!Mesh)
{
OutError = FString::Printf(TEXT("Standalone collision tile generation failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
FVoxelForgeCoreTileHashRecord Record;
Record.bEmpty = Mesh->indices.empty();
Record.bHasHashes = Mesh->hasDensityHash && Mesh->hasMeshHash
&& Mesh->hasPayloadHash && Mesh->hasTopologyHash;
if (!Record.bEmpty) ++CollisionBodyTiles;
ExpectedHashes.Add(FVoxelTileKey(
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod), Record);
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] standalone_collision_reference=1 center=(%d,%d,%d) tiles=%d body=%llu no_body=%llu"),
ColdStartCollisionCenter.X, ColdStartCollisionCenter.Y, ColdStartCollisionCenter.Z,
static_cast<int32>(ColdStartCollisionTiles.size()),
static_cast<unsigned long long>(CollisionBodyTiles),
static_cast<unsigned long long>(ColdStartCollisionTiles.size() - CollisionBodyTiles));
std::vector<FStandaloneCoreAdapterTileHashes> AggregateRows;
AggregateRows.reserve(Desired.size());
bool bFoundCarveVertex = false;
for (const VoxelForgeCore::TileKey& Key : Desired)
{
const std::optional<VoxelForgeCore::TileMesh> Mesh =
ReferenceWorld.generateTile(Key, Options, CoreError);
if (!Mesh || !Mesh->hasDensityHash || !Mesh->hasMeshHash
|| !Mesh->hasPayloadHash || !Mesh->hasTopologyHash)
{
OutError = FString::Printf(TEXT("Standalone Core hash generation failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
const FVoxelTileKey RuntimeKey(
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
FVoxelForgeCoreTileHashRecord Record;
Record.Density = Mesh->densityHash;
Record.Mesh = Mesh->meshHash;
Record.Payload = Mesh->payloadHash;
Record.Topology = Mesh->topologyHash;
Record.Triangles = static_cast<int32>(Mesh->indices.size() / 3);
Record.bEmpty = Mesh->indices.empty();
Record.bHasHashes = true;
ExpectedHashes.Add(RuntimeKey, Record);
ExpectedInitialDesiredSet.Add(RuntimeKey);
AggregateRows.push_back({Key, {Record.Density, Record.Mesh, Record.Payload, Record.Topology}});
if (!bFoundCarveVertex && Key.lod == 0 && !Mesh->vertices.empty()
&& !Mesh->indices.empty())
{
const VoxelForgeCore::Vec3d Vertex = Mesh->vertices.front().position;
CarveLocalPositionCm = FVector(Vertex.x, Vertex.y, Vertex.z);
CarveTargetTile = RuntimeKey;
bFoundCarveVertex = true;
}
}
InitialAggregates = StandaloneCoreAggregateHashes(AggregateRows);
// Rebuild and hash the same full clipmap at W1 and W16; the main reference is W8.
// Recompiler et hacher le même clipmap à W1 et W16 ; la référence principale est W8.
auto BuildWorkerHash = [&](uint32 WorkerLimit,
std::array<std::uint64_t, 4>& OutHashes) -> bool
{
std::optional<VoxelForgeCore::ResolvedWorldSnapshot> WorkerSnapshot =
VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
std::string_view(Utf8Json.Get(), static_cast<std::size_t>(Utf8Json.Length())),
CoreError);
if (!WorkerSnapshot)
{
OutError = FString::Printf(TEXT("Core worker-hash snapshot failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
VoxelForgeCore::World WorkerWorld(std::move(*WorkerSnapshot));
VoxelForgeCore::ShapeBuildRequest WorkerRequest = Request;
WorkerRequest.workerLimit = WorkerLimit;
FStandaloneCoreAdapterScheduler WorkerScheduler;
if (!WorkerWorld.compile(WorkerRequest, WorkerScheduler, CoreError))
{
OutError = FString::Printf(TEXT("Core W%u compile failed: %s"), WorkerLimit,
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
WorkerWorld.retainTileRegions(Desired);
std::vector<FStandaloneCoreAdapterTileHashes> WorkerRows;
WorkerRows.reserve(Desired.size());
for (const VoxelForgeCore::TileKey& Key : Desired)
{
const std::optional<VoxelForgeCore::TileMesh> Mesh =
WorkerWorld.generateTile(Key, Options, CoreError);
if (!Mesh || !Mesh->hasDensityHash || !Mesh->hasMeshHash
|| !Mesh->hasPayloadHash || !Mesh->hasTopologyHash)
{
OutError = FString::Printf(TEXT("Core W%u tile hash failed: %s"), WorkerLimit,
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
WorkerRows.push_back({Key,
{Mesh->densityHash, Mesh->meshHash, Mesh->payloadHash, Mesh->topologyHash}});
}
OutHashes = StandaloneCoreAggregateHashes(WorkerRows);
return true;
};
std::array<std::uint64_t, 4> Worker1Aggregates{};
std::array<std::uint64_t, 4> Worker16Aggregates{};
if (!BuildWorkerHash(1, Worker1Aggregates)
|| !BuildWorkerHash(16, Worker16Aggregates))
{
return false;
}
if (Worker1Aggregates != InitialAggregates || Worker16Aggregates != InitialAggregates)
{
OutError = TEXT("Core full-clipmap hashes differ across worker limits 1, 8, and 16.");
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] worker_hash_matrix=1/8/16 stable=1 density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx"),
static_cast<unsigned long long>(InitialAggregates[0]),
static_cast<unsigned long long>(InitialAggregates[1]),
static_cast<unsigned long long>(InitialAggregates[2]),
static_cast<unsigned long long>(InitialAggregates[3]));
if (!Manager->GetLayout().IsEmpty() && Manager->GetLayout()[0].Definition != nullptr
&& Manager->GetGeneratorTypeForChunk(FIntVector(
0, 0, Manager->GetLayout()[0].TopChunkZ)) != ECaveGeneratorType::SurfaceWorld)
{
const FStrateSlot& TopSlot = Manager->GetLayout()[0];
const FIntVector MidChunk(0, 0, (TopSlot.TopChunkZ + TopSlot.BottomChunkZ) / 2);
const FStrateGenerationParams TopParams = Manager->GetGenerationParams(MidChunk);
const float TopZ = (static_cast<float>(TopSlot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
const float BottomZ = static_cast<float>(TopSlot.BottomChunkZ) * CHUNK_SIZE;
const UVoxelSeasonAsset* EffectiveSeason = AuthoredSettings->Season.LoadSynchronous();
const float OriginRadius = EffectiveSeason != nullptr
? EffectiveSeason->GetOriginSpineRadius() : AuthoredSettings->OriginSpineRadius;
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
VoxelPassageGeometry::BuildOriginLandingGeometry(
TopZ, BottomZ, TopParams.BoundarySealThickness,
OriginRadius);
if (Geometry.bValid)
{
const double RoomZ = 0.5 * (Geometry.FloorZ + Geometry.CeilingZ);
const float RoomDensity = ReferenceWorld.densityAt({0.0, 0.0, RoomZ});
const float FloorCenterBelow = ReferenceWorld.densityAt(
{0.0, 0.0, Geometry.FloorZ - 8.0});
const float FloorSideBelow = ReferenceWorld.densityAt(
{Geometry.HalfWidth * 0.5, 0.0, Geometry.FloorZ - 8.0});
const float FloorCenterAbove = ReferenceWorld.densityAt(
{0.0, 0.0, Geometry.FloorZ + 8.0});
const float FloorSideAbove = ReferenceWorld.densityAt(
{Geometry.HalfWidth * 0.5, 0.0, Geometry.FloorZ + 8.0});
const float ShaftContinuationDensity = ReferenceWorld.densityAt(
{0.0, 0.0, Geometry.FloorZ - 32.0});
if (!(RoomDensity > 0.0f) || !(FloorCenterBelow < 0.0f)
|| !(FloorSideBelow < 0.0f) || !(FloorCenterAbove > 0.0f)
|| !(FloorSideAbove > 0.0f) || !(ShaftContinuationDensity < 0.0f))
{
OutError = FString::Printf(
TEXT("Core origin landing geometry failed: room=%.3f floor_below=(%.3f,%.3f) floor_above=(%.3f,%.3f) shaft_below=%.3f."),
RoomDensity, FloorCenterBelow, FloorSideBelow,
FloorCenterAbove, FloorSideAbove, ShaftContinuationDensity);
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] origin_landing=PASS floor=%.3f room=%.3f floor_below=(%.3f,%.3f) floor_above=(%.3f,%.3f) shaft_below=%.3f"),
Geometry.FloorZ, RoomDensity, FloorCenterBelow, FloorSideBelow,
FloorCenterAbove, FloorSideAbove, ShaftContinuationDensity);
}
else
{
OutError = TEXT("Core origin landing test geometry is invalid for the top strate.");
return false;
}
}
else if (!Manager->GetLayout().IsEmpty()
&& Manager->GetLayout()[0].Definition != nullptr
&& Manager->GetGeneratorTypeForChunk(FIntVector(
0, 0, Manager->GetLayout()[0].TopChunkZ)) == ECaveGeneratorType::SurfaceWorld)
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] origin_landing=SKIP reason=surface_world_uses_native_walkable_route"));
}
if (!bFoundCarveVertex)
{
OutError = TEXT("Standalone Core reference has no level-0 surface vertex for the carve fixture.");
return false;
}
VoxelForgeCore::EditTransaction Transaction;
Transaction.sequence = 1;
VoxelForgeCore::EditBrush Brush;
Brush.shape = VoxelForgeCore::EditBrush::Shape::sphere;
Brush.operation = VoxelForgeCore::EditBrush::Operation::densityOffset;
Brush.a = {CarveLocalPositionCm.X / CoreVoxelSizeCm,
CarveLocalPositionCm.Y / CoreVoxelSizeCm,
CarveLocalPositionCm.Z / CoreVoxelSizeCm};
Brush.halfExtent = {5.0, 5.0, 5.0};
Brush.radius = CarveBrushRadiusCoreVoxels;
Brush.falloff = 2.0f;
const double ExpectedCoreBrushStrength = 10.0 * VOXEL_SIZE / CoreVoxelSizeCm;
Brush.strength = static_cast<float>(ExpectedCoreBrushStrength);
if (!FMath::IsNearlyEqual(CoreVoxelSizeCm, 25.0, 0.000001)
&& FMath::IsNearlyEqual(ExpectedCoreBrushStrength, 5.0, 0.000001))
{
OutError = TEXT("Non-default Core voxel size reused the fixed 25 cm carve strength.");
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] carve_scale_control=PASS legacy_fixed_strength=5.000 dynamic_strength=%.3f stale_default_control_detected=%d"),
ExpectedCoreBrushStrength,
FMath::IsNearlyEqual(ExpectedCoreBrushStrength, 5.0, 0.000001) ? 0 : 1);
Transaction.brushes.push_back(Brush);
const std::optional<VoxelForgeCore::EditResult> Edit =
ReferenceWorld.applyEditTransaction(Transaction, Desired, CoreError);
if (!Edit)
{
OutError = FString::Printf(TEXT("Standalone Core reference edit failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
ExpectedEditDigest = Edit->digest;
for (const std::vector<VoxelForgeCore::TileKey>& ByLod : Edit->affectedByLod)
{
for (const VoxelForgeCore::TileKey& Key : ByLod)
{
const FVoxelTileKey RuntimeKey(
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
ExpectedAffectedTiles.Add(RuntimeKey);
const std::optional<VoxelForgeCore::TileMesh> Mesh =
ReferenceWorld.generateTile(Key, Options, CoreError);
if (!Mesh || !Mesh->hasDensityHash || !Mesh->hasMeshHash
|| !Mesh->hasPayloadHash || !Mesh->hasTopologyHash)
{
OutError = FString::Printf(TEXT("Standalone Core edited tile hash failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
FVoxelForgeCoreTileHashRecord Record;
Record.Density = Mesh->densityHash;
Record.Mesh = Mesh->meshHash;
Record.Payload = Mesh->payloadHash;
Record.Topology = Mesh->topologyHash;
Record.Triangles = static_cast<int32>(Mesh->indices.size() / 3);
Record.bEmpty = Mesh->indices.empty();
Record.bHasHashes = true;
ExpectedPostEditHashes.Add(RuntimeKey, Record);
}
}
ExpectedAffectedTiles.Sort([](const FVoxelTileKey& A, const FVoxelTileKey& B)
{
if (A.Level != B.Level) return A.Level < B.Level;
if (A.Coord.X != B.Coord.X) return A.Coord.X < B.Coord.X;
if (A.Coord.Y != B.Coord.Y) return A.Coord.Y < B.Coord.Y;
return A.Coord.Z < B.Coord.Z;
});
if (!ExpectedPostEditHashes.Contains(CarveTargetTile)
|| ExpectedAffectedTiles.IsEmpty())
{
OutError = TEXT("Standalone Core edit fixture did not affect its carved level-0 tile.");
return false;
}
// The authored lower landing is in the second strate (the biome/surface strate). Compile
// its fine clipmap pages from the same Core reference before moving AVoxelWorld there.
// Le palier inférieur appartient à la strate surface/biomes; préparer la référence avant le déplacement.
const std::vector<VoxelForgeCore::TileKey> TravelDesired =
ReferenceWorld.desiredTiles(TravelCameraVoxels);
if (TravelDesired.empty())
{
OutError = TEXT("Standalone Core returned no desired tiles at the authored lower landing.");
return false;
}
FString CoverageError;
if (!AuditCoreClipmapCoverage(TravelDesired, TravelCameraVoxels, 128,
TravelCoverageLocalTiles, TravelCoverageFaces, TravelLodTransitionFaces,
CoverageError))
{
OutError = FString::Printf(TEXT("Standalone Core lower-landing coverage failed: %s"),
*CoverageError);
return false;
}
std::vector<VoxelForgeCore::TileKey> MissingFineTiles;
MissingFineTiles.reserve(TravelDesired.size());
for (const VoxelForgeCore::TileKey& Key : TravelDesired)
{
const FVoxelTileKey RuntimeKey(
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
ExpectedTravelDesiredSet.Add(RuntimeKey);
if (Key.lod < ReferenceWorld.configuration().fullResolutionLodCount)
{
MissingFineTiles.push_back(Key);
}
}
if (!ReferenceWorld.compileTileRegion(MissingFineTiles, 16, Scheduler, CoreError,
&TravelFineTilesCompiled, &TravelCompileStats))
{
OutError = FString::Printf(TEXT("Standalone Core lower-landing region compile failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
ReferenceWorld.retainTileRegions(TravelDesired);
TravelRegionsBuilt = TravelCompileStats.regionsBuilt;
VoxelForgeCore::TileGenerationOptions TravelOptions;
TravelOptions.hashDensity = true;
TravelOptions.mesherRecipe = Options.mesherRecipe;
std::vector<FStandaloneCoreAdapterTileHashes> TravelRows;
TravelRows.reserve(TravelDesired.size());
for (const VoxelForgeCore::TileKey& Key : TravelDesired)
{
const std::optional<VoxelForgeCore::TileMesh> Mesh =
ReferenceWorld.generateTile(Key, TravelOptions, CoreError);
if (!Mesh || !Mesh->hasDensityHash || !Mesh->hasMeshHash
|| !Mesh->hasPayloadHash || !Mesh->hasTopologyHash)
{
OutError = FString::Printf(TEXT("Standalone Core lower-landing tile generation failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
const FVoxelTileKey RuntimeKey(
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
FVoxelForgeCoreTileHashRecord Record;
Record.Density = Mesh->densityHash;
Record.Mesh = Mesh->meshHash;
Record.Payload = Mesh->payloadHash;
Record.Topology = Mesh->topologyHash;
Record.Triangles = static_cast<int32>(Mesh->indices.size() / 3);
Record.bEmpty = Mesh->indices.empty();
Record.bHasHashes = true;
ExpectedTravelHashes.Add(RuntimeKey, Record);
TravelRows.push_back({Key, {Record.Density, Record.Mesh, Record.Payload, Record.Topology}});
if (Mesh->groups.skirtTriangleCount > 0) ++TravelTilesWithSkirts;
}
TravelAggregates = StandaloneCoreAggregateHashes(TravelRows);
ExpectedTravelTileCount = TravelDesired.size();
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] standalone_travel_reference=1 location=(%.3f,%.3f,%.3f) tiles=%llu fine_compiled=%llu regions=%llu local_tiles=%d checked_faces=%d lod_transition_faces=%d skirt_tiles=%d density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx"),
TravelCameraVoxels.x, TravelCameraVoxels.y, TravelCameraVoxels.z,
static_cast<unsigned long long>(ExpectedTravelTileCount),
static_cast<unsigned long long>(TravelFineTilesCompiled),
static_cast<unsigned long long>(TravelRegionsBuilt),
TravelCoverageLocalTiles, TravelCoverageFaces, TravelLodTransitionFaces,
TravelTilesWithSkirts,
static_cast<unsigned long long>(TravelAggregates[0]),
static_cast<unsigned long long>(TravelAggregates[1]),
static_cast<unsigned long long>(TravelAggregates[2]),
static_cast<unsigned long long>(TravelAggregates[3]));
// Compile a second destination deeper in the biome strate, matching the owner's surface screenshot.
// Compiler une seconde destination dans la strate biome, à l'emplacement de la capture surface.
const std::vector<VoxelForgeCore::TileKey> SurfaceTravelDesired =
ReferenceWorld.desiredTiles(SurfaceCameraVoxels);
if (SurfaceTravelDesired.empty())
{
OutError = TEXT("Standalone Core returned no desired tiles at the surface-strate fixture.");
return false;
}
if (!AuditCoreClipmapCoverage(SurfaceTravelDesired, SurfaceCameraVoxels, 128,
SurfaceTravelCoverageLocalTiles, SurfaceTravelCoverageFaces,
SurfaceTravelLodTransitionFaces, CoverageError))
{
OutError = FString::Printf(TEXT("Standalone Core surface-strate coverage failed: %s"),
*CoverageError);
return false;
}
std::vector<VoxelForgeCore::TileKey> MissingSurfaceFineTiles;
MissingSurfaceFineTiles.reserve(SurfaceTravelDesired.size());
MatchedTimingSurfaceDesired = SurfaceTravelDesired;
for (const VoxelForgeCore::TileKey& Key : SurfaceTravelDesired)
{
const FVoxelTileKey RuntimeKey(
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
ExpectedSurfaceTravelDesiredSet.Add(RuntimeKey);
if (Key.lod < ReferenceWorld.configuration().fullResolutionLodCount)
MissingSurfaceFineTiles.push_back(Key);
}
MatchedTimingSurfaceMissingFine = MissingSurfaceFineTiles;
// Keep the retained reference clipmap bounded while compiling the second remote region.
// Garder la référence bornée avant de compiler la seconde région distante.
const double NativeSurfaceRegionCompileStart = FPlatformTime::Seconds();
ReferenceWorld.retainTileRegions(SurfaceTravelDesired);
if (!ReferenceWorld.compileTileRegion(MissingSurfaceFineTiles, 16, Scheduler, CoreError,
&SurfaceTravelFineTilesCompiled, &SurfaceTravelCompileStats))
{
OutError = FString::Printf(TEXT("Standalone Core surface-strate region compile failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
NativeSurfaceRegionCompileSeconds = FPlatformTime::Seconds()
- NativeSurfaceRegionCompileStart;
SurfaceTravelRegionsBuilt = SurfaceTravelCompileStats.regionsBuilt;
std::vector<FStandaloneCoreAdapterTileHashes> SurfaceTravelRows;
SurfaceTravelRows.reserve(SurfaceTravelDesired.size());
FAssemblerGeometryAudit GeometryAuditL0;
FAssemblerGeometryAudit GeometryAuditL1;
FAssemblerGeometryAudit GeometryAuditHorizon;
int32 GeometryAuditL0Distance = MAX_int32;
int32 GeometryAuditL1Distance = MAX_int32;
int32 GeometryAuditHorizonDistance = MAX_int32;
FAssemblerGeometryAudit BestSpanGeometryAuditL0;
double BestSpanRatioL0 = -std::numeric_limits<double>::infinity();
int32 GeometryAuditL0CandidateCount = 0;
int32 HorizonAuditLOD = 0;
for (const VoxelForgeCore::TileKey& Key : SurfaceTravelDesired)
{
HorizonAuditLOD = FMath::Max(HorizonAuditLOD, static_cast<int32>(Key.lod));
}
for (const VoxelForgeCore::TileKey& Key : SurfaceTravelDesired)
{
const double TileGenerationStart = FPlatformTime::Seconds();
const std::optional<VoxelForgeCore::TileMesh> Mesh =
ReferenceWorld.generateTile(Key, TravelOptions, CoreError);
if (!Mesh || !Mesh->hasDensityHash || !Mesh->hasMeshHash
|| !Mesh->hasPayloadHash || !Mesh->hasTopologyHash)
{
OutError = FString::Printf(TEXT("Standalone Core surface-strate tile generation failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
if (Arguments.bCoreHybridFixturePlacement && Key.lod == 0u
&& Key.coordinate.x == 1 && Key.coordinate.y == 5)
{
ExpectedHybridFixtureVisualMesh = *Mesh;
}
// Keep the terrain geometry audit on the sampled native field, not the opt-in box-fixture tile.
// Garder l'audit de geometrie sur le champ natif echantillonne, hors de la tuile des volumes de test.
const bool bInjectedHybridFixtureTile = Arguments.bCoreHybridFixturePlacement
&& Key.lod == 0u && Key.coordinate.x == 1 && Key.coordinate.y == 5;
if (!bInjectedHybridFixtureTile
&& (Key.lod <= 1u || static_cast<int32>(Key.lod) == HorizonAuditLOD)
&& Mesh->groups.groundTriangleCount > 0u)
{
const int32 LOD = static_cast<int32>(Key.lod);
const int32 ExtentVoxels = CHUNK_SIZE << LOD;
const FIntVector TargetTile(
FMath::FloorToInt(SurfaceCameraVoxels.x / ExtentVoxels),
FMath::FloorToInt(SurfaceCameraVoxels.y / ExtentVoxels),
FMath::FloorToInt(SurfaceCameraVoxels.z / ExtentVoxels));
const FIntVector TileCoord(
static_cast<int32>(Key.coordinate.x), static_cast<int32>(Key.coordinate.y),
static_cast<int32>(Key.coordinate.z));
const int32 Distance = FMath::Abs(TileCoord.X - TargetTile.X)
+ FMath::Abs(TileCoord.Y - TargetTile.Y) + FMath::Abs(TileCoord.Z - TargetTile.Z);
FAssemblerGeometryAudit Candidate;
FString GeometryError;
if (!AuditAssemblerCoreTile(ReferenceWorld, *Mesh, Candidate, GeometryError))
{
OutError = FString::Printf(TEXT("Native surface mesh geometry audit failed: %s"),
*GeometryError);
return false;
}
const double SpanRatioX = Candidate.SpanXcm / Candidate.ExpectedTileSpanCm;
const double SpanRatioY = Candidate.SpanYcm / Candidate.ExpectedTileSpanCm;
if (LOD == 0)
{
++GeometryAuditL0CandidateCount;
const double MinimumSpanRatio = FMath::Min(SpanRatioX, SpanRatioY);
if (MinimumSpanRatio > BestSpanRatioL0)
{
BestSpanRatioL0 = MinimumSpanRatio;
BestSpanGeometryAuditL0 = Candidate;
}
}
if (SpanRatioX >= 0.95 && SpanRatioY >= 0.95
&& SpanRatioX <= 1.05 && SpanRatioY <= 1.05)
{
if (LOD == 0 && Distance < GeometryAuditL0Distance)
{
GeometryAuditL0 = Candidate;
GeometryAuditL0Distance = Distance;
}
else if (LOD == 1 && Distance < GeometryAuditL1Distance)
{
GeometryAuditL1 = Candidate;
GeometryAuditL1Distance = Distance;
}
if (LOD == HorizonAuditLOD && Distance < GeometryAuditHorizonDistance)
{
GeometryAuditHorizon = Candidate;
GeometryAuditHorizonDistance = Distance;
}
}
}
const double TileGenerationSeconds = FPlatformTime::Seconds() - TileGenerationStart;
NativeSurfaceTileGenerationSeconds += TileGenerationSeconds;
NativeSurfaceTriangleCount += Mesh->indices.size() / 3;
const FVoxelTileKey RuntimeKey(
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
FVoxelForgeCoreTileHashRecord Record;
Record.Density = Mesh->densityHash;
Record.Mesh = Mesh->meshHash;
Record.Payload = Mesh->payloadHash;
Record.Topology = Mesh->topologyHash;
Record.Triangles = static_cast<int32>(Mesh->indices.size() / 3);
Record.bEmpty = Mesh->indices.empty();
Record.bHasHashes = true;
ExpectedSurfaceTravelHashes.Add(RuntimeKey, Record);
SurfaceTravelRows.push_back({Key,
{Record.Density, Record.Mesh, Record.Payload, Record.Topology}});
if (Mesh->groups.skirtTriangleCount > 0) ++SurfaceTravelTilesWithSkirts;
}
VoxelForgeCore::TileGenerationOptions NativeTimingOptions;
NativeTimingOptions.mesherRecipe = Options.mesherRecipe;
for (const VoxelForgeCore::TileKey& Key : SurfaceTravelDesired)
{
if (Key.lod > 1u)
{
continue;
}
const double TileGenerationStart = FPlatformTime::Seconds();
const std::optional<VoxelForgeCore::TileMesh> Mesh =
ReferenceWorld.generateTile(Key, NativeTimingOptions, CoreError);
if (!Mesh)
{
OutError = FString::Printf(TEXT("Native unhashed streaming sample failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
const double TileGenerationSeconds = FPlatformTime::Seconds() - TileGenerationStart;
NativeSurfaceTileSamples.push_back({
static_cast<int32>(Key.lod), ClassifyAssemblerTile(*Mesh), TileGenerationSeconds,
0.0, Mesh->diagnostics.classificationMilliseconds,
Mesh->diagnostics.columnComputeMilliseconds, Mesh->diagnostics.meshingMilliseconds,
Mesh->diagnostics.heightfieldBandLimited});
}
if (!GeometryAuditL0.bValid || !GeometryAuditL1.bValid || !GeometryAuditHorizon.bValid)
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeGeometryAudit] best_lod0_candidate count=%d min_span_ratio=%.6f tile=(%d,%d,%d) span_cm=(%.3f,%.3f) expected_cm=%.3f"),
GeometryAuditL0CandidateCount, BestSpanRatioL0,
BestSpanGeometryAuditL0.Tile.X, BestSpanGeometryAuditL0.Tile.Y,
BestSpanGeometryAuditL0.Tile.Z, BestSpanGeometryAuditL0.SpanXcm,
BestSpanGeometryAuditL0.SpanYcm, BestSpanGeometryAuditL0.ExpectedTileSpanCm);
OutError = FString::Printf(
TEXT("Native geometry audit missed full-span tiles (l0=%d l1=%d horizon_lod=%d horizon=%d)."),
GeometryAuditL0.bValid ? 1 : 0, GeometryAuditL1.bValid ? 1 : 0,
HorizonAuditLOD, GeometryAuditHorizon.bValid ? 1 : 0);
return false;
}
if (!FMath::IsNearlyEqual(Arguments.CoreVoxelSizeCm, 25.0f, 0.0001f))
{
const double StaleDefault25CmSpan = static_cast<double>(CHUNK_SIZE) * 25.0;
const bool bFixed25CmControlWouldFail =
!FMath::IsNearlyEqual(GeometryAuditL0.SpanXcm, StaleDefault25CmSpan, 0.1)
|| !FMath::IsNearlyEqual(GeometryAuditL0.SpanYcm, StaleDefault25CmSpan, 0.1);
if (!bFixed25CmControlWouldFail)
{
OutError = TEXT("The injected fixed-25-cm geometry negative control was not detected.");
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] geometry_scale_negative_control=DETECTED injected_fixed_voxel_cm=25 expected_tile_span_cm=%.1f actual_span_cm=(%.1f,%.1f)"),
StaleDefault25CmSpan, GeometryAuditL0.SpanXcm, GeometryAuditL0.SpanYcm);
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] geometry_scale_agreement=PASS legacy_default_cm=%.1f core_voxel_cm=%.1f "
"pre_fix_scale_ratio=%.4f lod0_expected_cm=%.1f span_cm=(%.1f,%.1f) ratio=(%.4f,%.4f) max_edge_cm=%.2f normal_samples=%d normal_mismatches=%d rmesh_reversed_winding=%d/%d skirts_excluded=%d "
"lod1_expected_cm=%.1f span_cm=(%.1f,%.1f) ratio=(%.4f,%.4f) max_edge_cm=%.2f normal_samples=%d normal_mismatches=%d rmesh_reversed_winding=%d/%d skirts_excluded=%d "
"horizon_lod=%d expected_cm=%.1f span_cm=(%.1f,%.1f) ratio=(%.4f,%.4f) max_edge_cm=%.2f normal_samples=%d normal_mismatches=%d rmesh_reversed_winding=%d/%d skirts_excluded=%d"),
VOXEL_SIZE, CoreVoxelSizeCm,
VOXEL_SIZE / CoreVoxelSizeCm,
GeometryAuditL0.ExpectedTileSpanCm, GeometryAuditL0.SpanXcm, GeometryAuditL0.SpanYcm,
GeometryAuditL0.SpanXcm / GeometryAuditL0.ExpectedTileSpanCm,
GeometryAuditL0.SpanYcm / GeometryAuditL0.ExpectedTileSpanCm,
GeometryAuditL0.MaxTriangleEdgeCm, GeometryAuditL0.NormalSamples,
GeometryAuditL0.NormalOrientationMismatches, GeometryAuditL0.TriangleWindingMismatches,
GeometryAuditL0.TriangleSamples, GeometryAuditL0.SkirtTrianglesExcluded,
GeometryAuditL1.ExpectedTileSpanCm, GeometryAuditL1.SpanXcm, GeometryAuditL1.SpanYcm,
GeometryAuditL1.SpanXcm / GeometryAuditL1.ExpectedTileSpanCm,
GeometryAuditL1.SpanYcm / GeometryAuditL1.ExpectedTileSpanCm,
GeometryAuditL1.MaxTriangleEdgeCm, GeometryAuditL1.NormalSamples,
GeometryAuditL1.NormalOrientationMismatches, GeometryAuditL1.TriangleWindingMismatches,
GeometryAuditL1.TriangleSamples, GeometryAuditL1.SkirtTrianglesExcluded,
HorizonAuditLOD, GeometryAuditHorizon.ExpectedTileSpanCm,
GeometryAuditHorizon.SpanXcm, GeometryAuditHorizon.SpanYcm,
GeometryAuditHorizon.SpanXcm / GeometryAuditHorizon.ExpectedTileSpanCm,
GeometryAuditHorizon.SpanYcm / GeometryAuditHorizon.ExpectedTileSpanCm,
GeometryAuditHorizon.MaxTriangleEdgeCm, GeometryAuditHorizon.NormalSamples,
GeometryAuditHorizon.NormalOrientationMismatches,
GeometryAuditHorizon.TriangleWindingMismatches, GeometryAuditHorizon.TriangleSamples,
GeometryAuditHorizon.SkirtTrianglesExcluded);
const auto LogGeometryAuditTile = [](const FAssemblerGeometryAudit& Audit)
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeGeometryAudit] lod=%d tile=(%d,%d,%d) max_edge_cm=%.3f edge_cells=%.3f triangle=%llu endpoints_cm=((%.2f,%.2f,%.2f),(%.2f,%.2f,%.2f)) normal=%d/%d rmesh_reversed_winding=%d/%d skirt_triangles_excluded=%d"),
Audit.LOD, Audit.Tile.X, Audit.Tile.Y, Audit.Tile.Z,
Audit.MaxTriangleEdgeCm, Audit.MaxTriangleEdgeCm / FMath::Max(Audit.CellWorldCm, 1.0),
static_cast<unsigned long long>(Audit.MaxEdgeTriangleIndex),
Audit.MaxEdgeStart.x, Audit.MaxEdgeStart.y, Audit.MaxEdgeStart.z,
Audit.MaxEdgeEnd.x, Audit.MaxEdgeEnd.y, Audit.MaxEdgeEnd.z,
Audit.NormalOrientationMismatches, Audit.NormalSamples,
Audit.TriangleWindingMismatches, Audit.TriangleSamples,
Audit.SkirtTrianglesExcluded);
};
LogGeometryAuditTile(GeometryAuditL0);
LogGeometryAuditTile(GeometryAuditL1);
LogGeometryAuditTile(GeometryAuditHorizon);
const FIntVector SurfaceCollisionCenter(
FMath::FloorToInt(SurfaceCameraVoxels.x / CHUNK_SIZE),
FMath::FloorToInt(SurfaceCameraVoxels.y / CHUNK_SIZE),
FMath::FloorToInt(SurfaceCameraVoxels.z / CHUNK_SIZE));
for (int32 Z = -CollisionRadius; Z <= CollisionRadius; ++Z)
for (int32 Y = -CollisionRadius; Y <= CollisionRadius; ++Y)
for (int32 X = -CollisionRadius; X <= CollisionRadius; ++X)
{
const FVoxelTileKey Key(SurfaceCollisionCenter + FIntVector(X, Y, Z), 0);
const FVoxelForgeCoreTileHashRecord* Record = ExpectedSurfaceTravelHashes.Find(Key);
if (Record == nullptr)
{
OutError = FString::Printf(
TEXT("Standalone native-surface reference omitted expected collision tile (%d,%d,%d)."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z);
return false;
}
ExpectedSurfaceCollisionSet.Add(Key);
if (Record->bEmpty) ++ExpectedSurfaceCollisionNoBodyCount;
else ++ExpectedSurfaceCollisionBodyCount;
}
SurfaceTravelAggregates = StandaloneCoreAggregateHashes(SurfaceTravelRows);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] standalone_surface_reference=1 location=(%.3f,%.3f,%.3f) tiles=%d fine_compiled=%llu regions=%llu local_tiles=%d checked_faces=%d lod_transition_faces=%d skirt_tiles=%d density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx"),
SurfaceCameraVoxels.x, SurfaceCameraVoxels.y, SurfaceCameraVoxels.z,
ExpectedSurfaceTravelDesiredSet.Num(),
static_cast<unsigned long long>(SurfaceTravelFineTilesCompiled),
static_cast<unsigned long long>(SurfaceTravelRegionsBuilt),
SurfaceTravelCoverageLocalTiles, SurfaceTravelCoverageFaces,
SurfaceTravelLodTransitionFaces, SurfaceTravelTilesWithSkirts,
static_cast<unsigned long long>(SurfaceTravelAggregates[0]),
static_cast<unsigned long long>(SurfaceTravelAggregates[1]),
static_cast<unsigned long long>(SurfaceTravelAggregates[2]),
static_cast<unsigned long long>(SurfaceTravelAggregates[3]));
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] standalone_reference=1 tiles=%llu affected=%d target=(%d,%d,%d) lod=%d"),
static_cast<unsigned long long>(ExpectedInitialTileCount),
ExpectedAffectedTiles.Num(), CarveTargetTile.Coord.X, CarveTargetTile.Coord.Y,
CarveTargetTile.Coord.Z, CarveTargetTile.Level);
}
// Compare native and compatibility bridge on the same remote surface keys in this process.
// Comparer le champ natif et le pont de compatibilite sur les memes cles de surface.
if (MatchedTimingInitialDesired.empty() || MatchedTimingSurfaceDesired.empty()
|| MatchedTimingSurfaceMissingFine.empty())
{
OutError = TEXT("Matched native/bridge timing did not retain its reference tile keys.");
return false;
}
FString BridgeJson = Json;
TSharedPtr<FJsonObject> BridgeRoot;
const TSharedRef<TJsonReader<>> BridgeReader = TJsonReaderFactory<>::Create(BridgeJson);
if (!FJsonSerializer::Deserialize(BridgeReader, BridgeRoot) || !BridgeRoot.IsValid())
{
OutError = TEXT("Matched legacy-bridge timing could not parse the Core snapshot JSON.");
return false;
}
BridgeRoot->RemoveField(TEXT("world_assembly"));
BridgeRoot->RemoveField(TEXT("world_assembly_content_hash"));
BridgeJson.Reset();
const TSharedRef<FExploreJsonWriter> BridgeWriter = TJsonWriterFactory<TCHAR,
TCondensedJsonPrintPolicy<TCHAR>>::Create(&BridgeJson);
if (!FJsonSerializer::Serialize(BridgeRoot.ToSharedRef(), BridgeWriter))
{
OutError = TEXT("Matched legacy-bridge timing could not serialize the bridge snapshot.");
return false;
}
BridgeWriter->Close();
FTCHARToUTF8 BridgeUtf8(*BridgeJson);
std::string BridgeError;
std::optional<VoxelForgeCore::ResolvedWorldSnapshot> BridgeSnapshot =
VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
std::string_view(BridgeUtf8.Get(), static_cast<std::size_t>(BridgeUtf8.Length())),
BridgeError);
if (!BridgeSnapshot || BridgeSnapshot->worldAssembly() != nullptr)
{
OutError = FString::Printf(
TEXT("Matched legacy-bridge snapshot failed validation or retained a native plan: %s"),
UTF8_TO_TCHAR(BridgeError.c_str()));
return false;
}
VoxelForgeCore::World BridgeWorld(std::move(*BridgeSnapshot));
FStandaloneCoreAdapterScheduler BridgeScheduler;
if (!BridgeWorld.compile(MatchedTimingInitialRequest, BridgeScheduler, BridgeError))
{
OutError = FString::Printf(TEXT("Matched legacy-bridge initial compile failed: %s"),
UTF8_TO_TCHAR(BridgeError.c_str()));
return false;
}
BridgeWorld.retainTileRegions(MatchedTimingInitialDesired);
const double BridgeRegionCompileStart = FPlatformTime::Seconds();
BridgeWorld.retainTileRegions(MatchedTimingSurfaceDesired);
VoxelForgeCore::TileRegionCompileStats BridgeRegionStats;
if (!BridgeWorld.compileTileRegion(MatchedTimingSurfaceMissingFine, 16, BridgeScheduler,
BridgeError, nullptr, &BridgeRegionStats))
{
OutError = FString::Printf(TEXT("Matched legacy-bridge surface compile failed: %s"),
UTF8_TO_TCHAR(BridgeError.c_str()));
return false;
}
const double BridgeSurfaceRegionCompileSeconds = FPlatformTime::Seconds()
- BridgeRegionCompileStart;
VoxelForgeCore::TileGenerationOptions BridgeTileOptions;
BridgeTileOptions.hashDensity = true;
double BridgeSurfaceTileGenerationSeconds = 0.0;
std::uint64_t BridgeSurfaceTriangleCount = 0;
std::size_t BridgeSurfaceTilesGenerated = 0;
for (const VoxelForgeCore::TileKey& Key : MatchedTimingSurfaceDesired)
{
const double TileGenerationStart = FPlatformTime::Seconds();
const std::optional<VoxelForgeCore::TileMesh> Mesh =
BridgeWorld.generateTile(Key, BridgeTileOptions, BridgeError);
if (!Mesh || !Mesh->hasDensityHash || !Mesh->hasMeshHash
|| !Mesh->hasPayloadHash || !Mesh->hasTopologyHash)
{
OutError = FString::Printf(TEXT("Matched legacy-bridge tile generation failed: %s"),
UTF8_TO_TCHAR(BridgeError.c_str()));
return false;
}
const double TileGenerationSeconds = FPlatformTime::Seconds() - TileGenerationStart;
BridgeSurfaceTileGenerationSeconds += TileGenerationSeconds;
BridgeSurfaceTriangleCount += Mesh->indices.size() / 3;
++BridgeSurfaceTilesGenerated;
}
if (BridgeSurfaceTilesGenerated != MatchedTimingSurfaceDesired.size())
{
OutError = TEXT("Matched legacy-bridge timing did not generate the complete surface key set.");
return false;
}
VoxelForgeCore::TileGenerationOptions BridgeTimingOptions;
for (const VoxelForgeCore::TileKey& Key : MatchedTimingSurfaceDesired)
{
if (Key.lod > 1u)
{
continue;
}
const double TileGenerationStart = FPlatformTime::Seconds();
const std::optional<VoxelForgeCore::TileMesh> Mesh =
BridgeWorld.generateTile(Key, BridgeTimingOptions, BridgeError);
if (!Mesh)
{
OutError = FString::Printf(TEXT("Legacy-bridge unhashed streaming sample failed: %s"),
UTF8_TO_TCHAR(BridgeError.c_str()));
return false;
}
const double TileGenerationSeconds = FPlatformTime::Seconds() - TileGenerationStart;
BridgeSurfaceTileSamples.push_back({
static_cast<int32>(Key.lod), ClassifyAssemblerTile(*Mesh), TileGenerationSeconds,
0.0, Mesh->diagnostics.classificationMilliseconds,
Mesh->diagnostics.columnComputeMilliseconds, Mesh->diagnostics.meshingMilliseconds,
Mesh->diagnostics.heightfieldBandLimited});
}
const auto AssignModelReadyTimes = [](std::vector<FAssemblerTileTimingSample>& Samples)
{
constexpr int32 WorkerSlots = 16;
std::array<double, WorkerSlots> WorkerReadySeconds{};
for (FAssemblerTileTimingSample& Sample : Samples)
{
int32 EarliestWorker = 0;
for (int32 Worker = 1; Worker < WorkerSlots; ++Worker)
{
if (WorkerReadySeconds[Worker] < WorkerReadySeconds[EarliestWorker])
{
EarliestWorker = Worker;
}
}
Sample.ModelReadySeconds = WorkerReadySeconds[EarliestWorker] + Sample.ServiceSeconds;
WorkerReadySeconds[EarliestWorker] = Sample.ModelReadySeconds;
}
};
const auto Percentile = [](std::vector<double> Values, double Fraction)
{
if (Values.empty()) return 0.0;
std::sort(Values.begin(), Values.end());
const std::size_t Index = static_cast<std::size_t>(FMath::Clamp(
FMath::CeilToInt(Fraction * static_cast<double>(Values.size() - 1u)),
0, static_cast<int32>(Values.size() - 1u)));
return Values[Index];
};
const auto EmitKindTiming = [&](const TCHAR* Backend,
std::vector<FAssemblerTileTimingSample>& Samples)
{
AssignModelReadyTimes(Samples);
for (int32 LOD = 0; LOD <= 1; ++LOD)
{
for (int32 KindIndex = 0; KindIndex < 4; ++KindIndex)
{
const EAssemblerTileKind Kind = static_cast<EAssemblerTileKind>(KindIndex);
std::vector<double> ServiceMs, ModelReadySeconds, ClassificationMs, ColumnMs, MeshingMs;
int32 HeightfieldBandedTiles = 0;
for (const FAssemblerTileTimingSample& Sample : Samples)
{
if (Sample.LOD != LOD || Sample.Kind != Kind) continue;
ServiceMs.push_back(Sample.ServiceSeconds * 1000.0);
ModelReadySeconds.push_back(Sample.ModelReadySeconds);
ClassificationMs.push_back(Sample.ClassificationMilliseconds);
ColumnMs.push_back(Sample.ColumnMilliseconds);
MeshingMs.push_back(Sample.MeshingMilliseconds);
if(Sample.bHeightfieldBandLimited)++HeightfieldBandedTiles;
}
const double ReadyP95 = Percentile(ModelReadySeconds, 0.95);
UE_LOG(LogTemp, Display,
TEXT("[WorldAssemblerPawnLag] sample=unhashed_runtime_fastpath backend=%s lod=%d kind=%s tiles=%d heightfield_banded=%d "
"service_ms[p50=%.3f p95=%.3f] worker_stage_ms_p95[classify=%.3f column=%.3f mesh=%.3f] "
"model_workers=16 model_ready_p95_s=%.6f walk_lag_p95_m=%.3f sprint_lag_p95_m=%.3f "
"worker_order=desired_distance"),
Backend, LOD, AssemblerTileKindName(Kind), static_cast<int32>(ServiceMs.size()),
HeightfieldBandedTiles,
Percentile(ServiceMs, 0.50), Percentile(ServiceMs, 0.95),
Percentile(ClassificationMs, 0.95), Percentile(ColumnMs, 0.95),
Percentile(MeshingMs, 0.95), ReadyP95,
ReadyP95 * static_cast<double>(StagedWalkSpeedCmPerSecond) / 100.0,
ReadyP95 * static_cast<double>(StagedSprintSpeedCmPerSecond) / 100.0);
}
}
};
EmitKindTiming(TEXT("core_native"), NativeSurfaceTileSamples);
EmitKindTiming(TEXT("legacy_surface_bridge"), BridgeSurfaceTileSamples);
const double NativeTotal = NativeSurfaceRegionCompileSeconds
+ NativeSurfaceTileGenerationSeconds;
const double BridgeTotal = BridgeSurfaceRegionCompileSeconds
+ BridgeSurfaceTileGenerationSeconds;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] matched_surface_tile_timing=1 native_backend=heightfield2d bridge_backend=legacy_surface_bridge same_keys=1 tiles=%llu hashes=on native_region_compile_s=%.6f bridge_region_compile_s=%.6f native_tile_generation_s=%.6f bridge_tile_generation_s=%.6f native_compile_plus_tiles_s=%.6f bridge_compile_plus_tiles_s=%.6f speedup=%.3fx native_triangles=%llu bridge_triangles=%llu"),
static_cast<unsigned long long>(MatchedTimingSurfaceDesired.size()),
NativeSurfaceRegionCompileSeconds, BridgeSurfaceRegionCompileSeconds,
NativeSurfaceTileGenerationSeconds, BridgeSurfaceTileGenerationSeconds,
NativeTotal, BridgeTotal, BridgeTotal / FMath::Max(NativeTotal, 1.0e-9),
static_cast<unsigned long long>(NativeSurfaceTriangleCount),
static_cast<unsigned long long>(BridgeSurfaceTriangleCount));
const UWorld::InitializationValues WorldInitialization = UWorld::InitializationValues()
.AllowAudioPlayback(false)
.RequiresHitProxies(false)
.CreatePhysicsScene(true)
.CreateNavigation(false)
.CreateAISystem(false)
.ShouldSimulatePhysics(true)
.SetDefaultGameMode(AGameModeBase::StaticClass())
.SetTransactional(false);
UWorld* TestWorld = UWorld::CreateWorld(
EWorldType::Game, false, FName(TEXT("VoxelForgeCoreActorValidationWorld")),
GetTransientPackage(), false, ERHIFeatureLevel::Num, &WorldInitialization);
if (TestWorld == nullptr)
{
OutError = TEXT("Could not create the transient BuildHost actor test world.");
return false;
}
UGameInstance* TestGameInstance = NewObject<UGameInstance>(GEngine);
FWorldContext& TestWorldContext = GEngine->CreateNewWorldContext(EWorldType::Game);
TestWorldContext.OwningGameInstance = TestGameInstance;
TestWorldContext.SetCurrentWorld(TestWorld);
TestWorld->SetGameInstance(TestGameInstance);
TestWorld->SetShouldTick(false);
TestGameInstance->Init();
TestWorld->AddToRoot();
struct FTransientWorldCleanup
{
UWorld* World = nullptr;
bool bHasEngineWorldContext = false;
uint64 PreviousFrameCounter = GFrameCounter;
~FTransientWorldCleanup()
{
if (World != nullptr)
{
if (World->HasBegunPlay())
{
World->BeginTearingDown();
World->EndPlay(EEndPlayReason::Quit);
}
if (UGameInstance* GameInstance = World->GetGameInstance())
{
GameInstance->Shutdown();
}
if (bHasEngineWorldContext && GEngine != nullptr)
{
GEngine->DestroyWorldContext(World);
}
World->DestroyWorld(false);
if (World->IsRooted()) World->RemoveFromRoot();
}
GFrameCounter = PreviousFrameCounter;
}
} Cleanup{TestWorld, true, GFrameCounter};
const FTransform ActorTransform = FTransform::Identity;
AVoxelWorld* Actor = TestWorld->SpawnActorDeferred<AVoxelWorld>(
AVoxelWorld::StaticClass(), ActorTransform, nullptr, nullptr,
ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
if (Actor == nullptr)
{
OutError = TEXT("Could not spawn the transient AVoxelWorld for Core validation.");
return false;
}
Actor->Settings = AuthoredSettings;
Actor->bManageAtmosphere = false;
Actor->bUseVoxelForgeCore = true;
Actor->CoreVoxelSizeCm = Arguments.CoreVoxelSizeCm;
Actor->CoreMesherRecipe = Arguments.CoreHybridMesherVersion == 2
? EVoxelForgeCoreMesherRecipe::WorldAssemblerHybridV2
: (Arguments.CoreHybridMesherVersion == 1
? EVoxelForgeCoreMesherRecipe::WorldAssemblerHybridV1
: EVoxelForgeCoreMesherRecipe::MarchingCubesV1);
Actor->CoreClipRadius = 3;
Actor->CoreMaxClipLevel = 8;
Actor->bCaptureVoxelForgeCoreHashesForValidation = true;
Actor->FinishSpawning(ActorTransform);
if (!TestWorld->SetGameMode(FURL()))
{
OutError = TEXT("Could not create the transient BuildHost GameMode for actor validation.");
return false;
}
TestWorld->InitializeActorsForPlay(FURL());
TestWorld->BeginPlay();
Actor->SetActorTickEnabled(true);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] world_initialized=1 recipe=%s world_begun=%d actor_initialized=%d actor_beginplay=%d"),
Arguments.CoreHybridMesherVersion == 2 ? TEXT("WorldAssemblerHybridV2")
: (Arguments.CoreHybridMesherVersion == 1 ? TEXT("WorldAssemblerHybridV1")
: TEXT("MarchingCubesV1")),
TestWorld->HasBegunPlay() ? 1 : 0, Actor->IsActorInitialized() ? 1 : 0,
Actor->HasActorBegunPlay() ? 1 : 0);
const float ActiveCoreVoxelSizeCm = Actor->CoreVoxelSizeCm;
const FString ActiveSeasonIdentity = Actor->GetCurrentSeasonContentHash();
const float AlternateCoreVoxelSizeCm = FMath::IsNearlyEqual(ActiveCoreVoxelSizeCm, 25.0f, 0.0001f)
? 20.0f : 25.0f;
Actor->CoreVoxelSizeCm = AlternateCoreVoxelSizeCm;
const FString AlternateSeasonIdentity = Actor->GetCurrentSeasonContentHash();
Actor->CoreVoxelSizeCm = ActiveCoreVoxelSizeCm;
if (ActiveSeasonIdentity.IsEmpty() || AlternateSeasonIdentity.IsEmpty()
|| ActiveSeasonIdentity == AlternateSeasonIdentity)
{
OutError = TEXT("Core voxel size did not distinguish the hashed season/world identity.");
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] voxel_size_identity=PASS active_cm=%.3f active_hash=%s alternate_cm=%.3f alternate_hash=%s distinct=1"),
ActiveCoreVoxelSizeCm, *ActiveSeasonIdentity, AlternateCoreVoxelSizeCm,
*AlternateSeasonIdentity);
if (Arguments.bCoreHybridMesher
&& !RunHybridPluginMesherFixtures(
Actor->Generator, Arguments.bCoreHybridMesherV2, OutError))
{
return false;
}
auto DesiredTilesLoaded = [](const AVoxelWorld* World)
{
if (World == nullptr || World->DesiredSorted.IsEmpty()) return false;
for (const FVoxelTileKey& Key : World->DesiredSorted)
{
if (!World->LoadedTiles.Contains(Key)) return false;
}
return true;
};
auto CollisionSetSettled = [](const AVoxelWorld* World)
{
if (World == nullptr) return false;
for (const FVoxelTileKey& Key : World->CollisionDesiredTiles)
{
if (!World->CollisionReadyTiles.Contains(Key)
&& !World->CollisionNotRequiredTiles.Contains(Key))
{
return false;
}
}
return true;
};
auto WaitForStable = [&](double TimeoutSeconds)
{
const double Deadline = FPlatformTime::Seconds() + TimeoutSeconds;
const double ProgressIntervalSeconds = 10.0;
double LastProgressSeconds = FPlatformTime::Seconds();
int32 TickCount = 0;
while (FPlatformTime::Seconds() < Deadline)
{
TestWorld->Tick(LEVELTICK_All, 0.016f);
// Match FTestWorldWrapper::TickTestWorld: each manually driven world tick must advance
// the global frame counter or registered actor ticks run only once in this harness.
++GFrameCounter;
++TickCount;
FPlatformProcess::Sleep(0.001f);
if (Actor->IsVoxelForgeCoreReady() && Actor->bAllChunksLoaded
&& Actor->PendingTiles.IsEmpty() && Actor->PendingCollisionCooks.IsEmpty()
&& Actor->PendingUnload.IsEmpty()
&& Actor->ActiveTaskCount.load(std::memory_order_acquire) == 0
&& DesiredTilesLoaded(Actor) && CollisionSetSettled(Actor))
{
return true;
}
const double NowSeconds = FPlatformTime::Seconds();
if (NowSeconds - LastProgressSeconds >= ProgressIntervalSeconds)
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] wait ticks=%d ready=%d desired=%d loaded=%d pending=%d cooks=%d active=%d"),
TickCount, Actor->IsVoxelForgeCoreReady() ? 1 : 0,
Actor->DesiredSorted.Num(), Actor->LoadedTiles.Num(), Actor->PendingTiles.Num(),
Actor->PendingCollisionCooks.Num(),
Actor->ActiveTaskCount.load(std::memory_order_acquire));
LastProgressSeconds = NowSeconds;
}
}
return false;
};
if (!WaitForStable(600.0))
{
OutError = FString::Printf(
TEXT("AVoxelWorld Core initial clipmap did not settle: ready=%d desired=%d loaded=%d pending=%d cooks=%d active=%d"),
Actor->IsVoxelForgeCoreReady() ? 1 : 0, Actor->DesiredSorted.Num(),
Actor->LoadedTiles.Num(), Actor->PendingTiles.Num(),
Actor->PendingCollisionCooks.Num(),
Actor->ActiveTaskCount.load(std::memory_order_acquire));
return false;
}
TSet<FVoxelTileKey> ActualInitialDesiredSet;
for (const FVoxelTileKey& Key : Actor->DesiredSorted) ActualInitialDesiredSet.Add(Key);
if (ActualInitialDesiredSet.Num() != ExpectedInitialDesiredSet.Num())
{
OutError = FString::Printf(TEXT("Core actor desired %d tiles; standalone reference desired %d."),
ActualInitialDesiredSet.Num(), ExpectedInitialDesiredSet.Num());
return false;
}
for (const FVoxelTileKey& Key : ExpectedInitialDesiredSet)
{
if (!ActualInitialDesiredSet.Contains(Key))
{
OutError = FString::Printf(TEXT("Core actor initial clipmap omitted tile (%d,%d,%d) lod=%d."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z, Key.Level);
return false;
}
const FVoxelForgeCoreTileHashRecord* Actual = Actor->VoxelForgeCoreTileHashes.Find(Key);
const FVoxelForgeCoreTileHashRecord* Expected = ExpectedHashes.Find(Key);
if (Actual == nullptr || Expected == nullptr || !Actual->bHasHashes
|| Actual->Density != Expected->Density || Actual->Mesh != Expected->Mesh
|| Actual->Payload != Expected->Payload || Actual->Topology != Expected->Topology
|| Actual->Triangles != Expected->Triangles || Actual->bEmpty != Expected->bEmpty)
{
OutError = FString::Printf(
TEXT("Core actor tile hash mismatch at (%d,%d,%d) lod=%d."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z, Key.Level);
return false;
}
}
TSet<FVoxelTileKey> ExpectedCollisionSet;
int32 ExpectedBodyCount = 0;
int32 ExpectedNoBodyCount = 0;
int32 ActualNoBodyCount = 0;
for (int32 Z = -CollisionRadius; Z <= CollisionRadius; ++Z)
for (int32 Y = -CollisionRadius; Y <= CollisionRadius; ++Y)
for (int32 X = -CollisionRadius; X <= CollisionRadius; ++X)
{
const FVoxelTileKey Key(ColdStartCollisionCenter + FIntVector(X, Y, Z), 0);
ExpectedCollisionSet.Add(Key);
const FVoxelForgeCoreTileHashRecord* Record = ExpectedHashes.Find(Key);
if (Record == nullptr)
{
OutError = FString::Printf(TEXT("Standalone reference omitted collision tile (%d,%d,%d)."),
X, Y, Z);
return false;
}
if (Record->bEmpty) ++ExpectedNoBodyCount;
else ++ExpectedBodyCount;
if (Actor->CollisionNotRequiredTiles.Contains(Key)) ++ActualNoBodyCount;
}
if (Actor->CollisionDesiredTiles.Num() != ExpectedCollisionSet.Num()
|| Actor->CollisionReadyTiles.Num() != ExpectedBodyCount
|| ActualNoBodyCount != ExpectedNoBodyCount)
{
OutError = FString::Printf(
TEXT("Core collision count mismatch: desired=%d/%d ready=%d/%d no_body=%d/%d."),
Actor->CollisionDesiredTiles.Num(), ExpectedCollisionSet.Num(),
Actor->CollisionReadyTiles.Num(), ExpectedBodyCount,
ActualNoBodyCount, ExpectedNoBodyCount);
return false;
}
for (const FVoxelTileKey& Key : ExpectedCollisionSet)
{
const FVoxelForgeCoreTileHashRecord& Record = ExpectedHashes.FindChecked(Key);
const bool bExpectedCollisionReady = !Record.bEmpty;
if (!Actor->CollisionDesiredTiles.Contains(Key)
|| (bExpectedCollisionReady && !Actor->CollisionReadyTiles.Contains(Key))
|| (!bExpectedCollisionReady && !Actor->CollisionNotRequiredTiles.Contains(Key)))
{
OutError = FString::Printf(TEXT("Core collision result differs for tile (%d,%d,%d)."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z);
return false;
}
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] pass phase=initial tiles=%llu collision_desired=%d collision_ready=%d collision_no_body=%d density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx"),
static_cast<unsigned long long>(ExpectedInitialTileCount), ExpectedCollisionSet.Num(),
Actor->CollisionReadyTiles.Num(), Actor->CollisionNotRequiredTiles.Num(),
static_cast<unsigned long long>(InitialAggregates[0]),
static_cast<unsigned long long>(InitialAggregates[1]),
static_cast<unsigned long long>(InitialAggregates[2]),
static_cast<unsigned long long>(InitialAggregates[3]));
if (Actor->ContentManager == nullptr)
{
OutError = TEXT("Core height agreement proof has no content manager.");
return false;
}
const FVector SurfaceLocalPosition(
SurfaceCameraVoxels.x * CoreVoxelSizeCm,
SurfaceCameraVoxels.y * CoreVoxelSizeCm,
SurfaceCameraVoxels.z * CoreVoxelSizeCm);
const FVector SurfaceWorldPosition = Actor->GetActorTransform().TransformPosition(SurfaceLocalPosition);
const int32 LegacyUnscaledStrateIndex = Manager->GetStrateIndex(SurfaceLocalPosition.Z);
const int32 ResolvedStrateIndex = Actor->GetStrateAtPosition(SurfaceWorldPosition);
float ContentGroundLocalZ = 0.0f;
bool bContentHasWater = false;
float ContentWaterLocalZ = -FLT_MAX;
if (ResolvedStrateIndex != SurfaceStrateIndex
|| !Actor->ContentManager->QueryNativeSurfaceAnchors(ResolvedStrateIndex,
FVector2D(SurfaceLocalPosition.X, SurfaceLocalPosition.Y),
ContentGroundLocalZ, bContentHasWater, ContentWaterLocalZ))
{
OutError = FString::Printf(
TEXT("Core height agreement failed: raw-strate=%d resolved-strate=%d content-anchor=missing."),
LegacyUnscaledStrateIndex, ResolvedStrateIndex);
return false;
}
const float ManagerLocalZ = Actor->ToStrateManagerLocalZ(SurfaceLocalPosition.Z);
float ManagerTopZ = 0.0f, ManagerBottomZ = 0.0f;
if (!Manager->GetStrateUnrealZRange(ManagerLocalZ, ManagerTopZ, ManagerBottomZ))
{
OutError = TEXT("Core height agreement failed: manager has no corrected strate bounds.");
return false;
}
const float AtmosphereTopLocalZ = Actor->FromStrateManagerLocalZ(ManagerTopZ);
const float AtmosphereBottomLocalZ = Actor->FromStrateManagerLocalZ(ManagerBottomZ);
const FVector GroundQueryLocal(
SurfaceLocalPosition.X, SurfaceLocalPosition.Y, ContentGroundLocalZ + CoreVoxelSizeCm);
const FVector GroundQueryWorld = Actor->GetActorTransform().TransformPosition(GroundQueryLocal);
float TerrainGroundWorldZ = 0.0f, TerrainCeilingWorldZ = 0.0f;
if (!Actor->GetVoxelSurfaceHeightAt(GroundQueryWorld, TerrainGroundWorldZ, TerrainCeilingWorldZ))
{
OutError = TEXT("Core height agreement failed: native terrain returned no ground anchor.");
return false;
}
const float ContentGroundWorldZ = Actor->GetActorTransform().TransformPosition(
FVector(SurfaceLocalPosition.X, SurfaceLocalPosition.Y, ContentGroundLocalZ)).Z;
const float GroundAgreementCm = FMath::Abs(TerrainGroundWorldZ - ContentGroundWorldZ);
if (GroundAgreementCm > 1.0f
|| ContentGroundLocalZ < AtmosphereBottomLocalZ
|| ContentGroundLocalZ > AtmosphereTopLocalZ
|| (bContentHasWater && (ContentWaterLocalZ < AtmosphereBottomLocalZ
|| ContentWaterLocalZ > AtmosphereTopLocalZ)))
{
OutError = FString::Printf(
TEXT("Core height agreement failed: terrain/content delta=%.3f cm ground=%.3f water=%.3f atmosphere=(%.3f,%.3f)."),
GroundAgreementCm, ContentGroundLocalZ, ContentWaterLocalZ,
AtmosphereBottomLocalZ, AtmosphereTopLocalZ);
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] height_agreement=PASS legacy_unscaled_strate=%d corrected_strate=%d terrain_ground_cm=%.3f content_ground_cm=%.3f water_enabled=%d water_cm=%.3f atmosphere_bounds_cm=(%.3f,%.3f) ground_delta_cm=%.3f"),
LegacyUnscaledStrateIndex, ResolvedStrateIndex, TerrainGroundWorldZ,
ContentGroundLocalZ, bContentHasWater ? 1 : 0, ContentWaterLocalZ,
AtmosphereBottomLocalZ, AtmosphereTopLocalZ, GroundAgreementCm);
if (!Actor->bUseVoxelForgeCore)
{
OutError = TEXT("BuildHost height agreement fixture unexpectedly has Core disabled.");
return false;
}
Actor->bUseVoxelForgeCore = false;
float DisabledGroundLocalZ = 0.0f;
bool bDisabledHasWater = false;
float DisabledWaterLocalZ = -FLT_MAX;
const bool bNativeAnchorEscapedSwitch = Actor->ContentManager->QueryNativeSurfaceAnchors(
ResolvedStrateIndex, FVector2D(SurfaceLocalPosition.X, SurfaceLocalPosition.Y),
DisabledGroundLocalZ, bDisabledHasWater, DisabledWaterLocalZ);
Actor->bUseVoxelForgeCore = true;
if (bNativeAnchorEscapedSwitch)
{
OutError = TEXT("BuildHost native content anchor remained available with Core disabled.");
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] switch_off_content_gate=PASS native_anchor_when_disabled=0 legacy_voxel_cm=%.1f"),
VOXEL_SIZE);
AActor* Anchor = TestWorld->SpawnActor<AActor>(AActor::StaticClass());
if (Anchor == nullptr)
{
OutError = TEXT("Could not spawn the Core streaming anchor fixture.");
return false;
}
USceneComponent* AnchorRoot = NewObject<USceneComponent>(Anchor, NAME_None, RF_Transient);
if (AnchorRoot == nullptr)
{
OutError = TEXT("Could not create the Core streaming anchor root component.");
return false;
}
Anchor->SetRootComponent(AnchorRoot);
AnchorRoot->RegisterComponent();
const double CoreTileExtentCm = static_cast<double>(CHUNK_SIZE) * Arguments.CoreVoxelSizeCm;
const FVector OldAnchorPosition(CoreTileExtentCm * 20.0, 0.0, 0.0);
const FVector NewAnchorPosition(CoreTileExtentCm * 21.0, 0.0, 0.0);
Anchor->SetActorLocation(OldAnchorPosition);
Actor->RegisterStreamingAnchor(
Anchor, EVoxelAnchorPolicy::CollisionOnly, 0, 0, 0);
if (!WaitForStable(180.0))
{
OutError = TEXT("Core CollisionOnly anchor did not finish its initial tile request.");
return false;
}
const FVoxelTileKey OldAnchorKey(FIntVector(20, 0, 0), 0);
const FVoxelTileKey NewAnchorKey(FIntVector(21, 0, 0), 0);
if (!Actor->DesiredSorted.Contains(OldAnchorKey)
|| !Actor->LoadedTiles.Contains(OldAnchorKey)
|| !Actor->CollisionDesiredTiles.Contains(OldAnchorKey))
{
OutError = TEXT("Core did not request and load the original anchor tile.");
return false;
}
Anchor->SetActorLocation(NewAnchorPosition);
if (!WaitForStable(180.0))
{
OutError = TEXT("Core did not settle after moving the streaming anchor by one tile.");
return false;
}
if (!Actor->DesiredSorted.Contains(NewAnchorKey)
|| !Actor->LoadedTiles.Contains(NewAnchorKey)
|| !Actor->CollisionDesiredTiles.Contains(NewAnchorKey)
|| Actor->DesiredSorted.Contains(OldAnchorKey)
|| Actor->LoadedTiles.Contains(OldAnchorKey)
|| Actor->CollisionDesiredTiles.Contains(OldAnchorKey))
{
OutError = TEXT("Core anchor move did not request the entering tile and release the leaving tile.");
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] pass phase=anchor old=(20,0,0) new=(21,0,0) requested=1 released=1"));
const FVector CarveWorldPosition = Actor->GetActorTransform().TransformPosition(CarveLocalPositionCm);
const FVector InsideBrushWorldPosition = CarveWorldPosition + FVector(60.0f, 0.0f, 0.0f);
const FVector OutsideBrushWorldPosition = CarveWorldPosition + FVector(80.0f, 0.0f, 0.0f);
float CarveDensityBefore = 0.0f, InsideDensityBefore = 0.0f, OutsideDensityBefore = 0.0f;
if (!Actor->QueryVoxelForgeCoreDensityAt(CarveWorldPosition, CarveDensityBefore)
|| !Actor->QueryVoxelForgeCoreDensityAt(InsideBrushWorldPosition, InsideDensityBefore)
|| !Actor->QueryVoxelForgeCoreDensityAt(OutsideBrushWorldPosition, OutsideDensityBefore))
{
OutError = TEXT("Core Blueprint carve fixture could not read its pre-edit density samples.");
return false;
}
Actor->CarveAtPosition(CarveWorldPosition, CarveBrushRadiusLegacyVoxels, 10.0f);
float CarveDensityAfter = 0.0f, InsideDensityAfter = 0.0f, OutsideDensityAfter = 0.0f;
if (!Actor->QueryVoxelForgeCoreDensityAt(CarveWorldPosition, CarveDensityAfter)
|| !Actor->QueryVoxelForgeCoreDensityAt(InsideBrushWorldPosition, InsideDensityAfter)
|| !Actor->QueryVoxelForgeCoreDensityAt(OutsideBrushWorldPosition, OutsideDensityAfter))
{
OutError = TEXT("Core Blueprint carve fixture could not read its post-edit density samples.");
return false;
}
const float CarveCenterDelta = CarveDensityAfter - CarveDensityBefore;
const float CarveInsideDelta = InsideDensityAfter - InsideDensityBefore;
const float CarveOutsideDelta = OutsideDensityAfter - OutsideDensityBefore;
const float ExpectedBrushCenterDelta = static_cast<float>(10.0 * VOXEL_SIZE / CoreVoxelSizeCm);
if (!FMath::IsNearlyEqual(CarveCenterDelta, ExpectedBrushCenterDelta, 0.01f)
|| !(CarveInsideDelta > 0.0f && CarveInsideDelta < CarveCenterDelta)
|| !FMath::IsNearlyZero(CarveOutsideDelta, 0.001f))
{
OutError = FString::Printf(
TEXT("Blueprint carve sign/size failed: center=%.4f expected=+%.4f inside60cm=%.4f outside80cm=%.4f; expected partial +, zero."),
CarveCenterDelta, ExpectedBrushCenterDelta, CarveInsideDelta, CarveOutsideDelta);
return false;
}
if (Actor->LastVoxelForgeCoreEditSequence != 1
|| Actor->LastVoxelForgeCoreEditDigest != ExpectedEditDigest
|| Actor->LastVoxelForgeCoreAffectedTiles != ExpectedAffectedTiles)
{
OutError = TEXT("Core actor carve transaction sequence, digest, or affected tile list differs from Standalone.");
return false;
}
if (!WaitForStable(240.0))
{
OutError = TEXT("Core actor carve remesh/collision work did not settle.");
return false;
}
for (const FVoxelTileKey& Key : ExpectedAffectedTiles)
{
const FVoxelForgeCoreTileHashRecord* Actual = Actor->VoxelForgeCoreTileHashes.Find(Key);
const FVoxelForgeCoreTileHashRecord* Expected = ExpectedPostEditHashes.Find(Key);
if (Actual == nullptr || Expected == nullptr || !Actual->bHasHashes
|| Actual->Density != Expected->Density || Actual->Mesh != Expected->Mesh
|| Actual->Payload != Expected->Payload || Actual->Topology != Expected->Topology
|| Actual->Triangles != Expected->Triangles || Actual->bEmpty != Expected->bEmpty)
{
OutError = FString::Printf(
TEXT("Core actor edited tile hash differs from Standalone at (%d,%d,%d) lod=%d."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z, Key.Level);
return false;
}
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] pass phase=edit sequence=%llu digest=0x%016llx affected=%d target=(%d,%d,%d) lod=%d hash=0x%016llx"),
static_cast<unsigned long long>(Actor->LastVoxelForgeCoreEditSequence),
static_cast<unsigned long long>(Actor->LastVoxelForgeCoreEditDigest),
ExpectedAffectedTiles.Num(), CarveTargetTile.Coord.X, CarveTargetTile.Coord.Y,
CarveTargetTile.Coord.Z, CarveTargetTile.Level,
static_cast<unsigned long long>(ExpectedPostEditHashes.FindChecked(CarveTargetTile).Mesh));
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] blueprint_carve=PASS radius_legacy=6 radius_cm=%.3f strength_legacy=10 center_delta=%.4f expected_center_delta=%.4f inside_60cm_delta=%.4f outside_80cm_delta=%.4f"),
6.0 * static_cast<double>(VOXEL_SIZE), CarveCenterDelta, ExpectedBrushCenterDelta,
CarveInsideDelta, CarveOutsideDelta);
Actor->UnregisterStreamingAnchor(Anchor);
Anchor->Destroy();
if (!WaitForStable(180.0))
{
OutError = TEXT("Core did not settle after removing the CollisionOnly anchor fixture.");
return false;
}
APlayerController* TravelController = TestWorld->SpawnActor<APlayerController>(
APlayerController::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator);
APawn* TravelPawn = TestWorld->SpawnActor<APawn>(
APawn::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator);
if (TravelController == nullptr || TravelPawn == nullptr)
{
OutError = TEXT("Could not create the BuildHost player/controller for the Core travel proof.");
return false;
}
USceneComponent* TravelPawnRoot = NewObject<USceneComponent>(TravelPawn, NAME_None, RF_Transient);
if (TravelPawnRoot == nullptr)
{
OutError = TEXT("Could not create a root component for the BuildHost travel pawn.");
return false;
}
TravelPawn->SetRootComponent(TravelPawnRoot);
TravelPawnRoot->RegisterComponent();
const FVector TravelWorldPosition(
TravelCameraVoxels.x * CoreVoxelSizeCm,
TravelCameraVoxels.y * CoreVoxelSizeCm,
TravelCameraVoxels.z * CoreVoxelSizeCm);
TravelPawn->SetActorLocation(TravelWorldPosition);
TravelController->Possess(TravelPawn);
if (TravelController->GetPawn() != TravelPawn)
{
OutError = TEXT("BuildHost player controller did not possess the travel pawn.");
return false;
}
if (!WaitForStable(300.0))
{
OutError = FString::Printf(
TEXT("Core did not page and settle around the traveled pawn: desired=%d loaded=%d pending=%d cooks=%d active=%d."),
Actor->DesiredSorted.Num(), Actor->LoadedTiles.Num(), Actor->PendingTiles.Num(),
Actor->PendingCollisionCooks.Num(),
Actor->ActiveTaskCount.load(std::memory_order_acquire));
return false;
}
TSet<FVoxelTileKey> ActualTravelDesiredSet;
std::vector<VoxelForgeCore::TileKey> ActorTravelDesired;
ActorTravelDesired.reserve(static_cast<std::size_t>(Actor->DesiredSorted.Num()));
for (const FVoxelTileKey& Key : Actor->DesiredSorted)
{
ActualTravelDesiredSet.Add(Key);
ActorTravelDesired.push_back({{Key.Coord.X, Key.Coord.Y, Key.Coord.Z}, Key.Level});
}
if (ActualTravelDesiredSet.Num() != ExpectedTravelDesiredSet.Num())
{
OutError = FString::Printf(
TEXT("Core actor desired %d tiles at the lower landing; standalone Core expects %d."),
ActualTravelDesiredSet.Num(), ExpectedTravelDesiredSet.Num());
return false;
}
for (const FVoxelTileKey& Key : ExpectedTravelDesiredSet)
{
if (!ActualTravelDesiredSet.Contains(Key) || !Actor->LoadedTiles.Contains(Key))
{
OutError = FString::Printf(
TEXT("Core actor omitted or failed to load lower-landing tile (%d,%d,%d) lod=%d."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z, Key.Level);
return false;
}
const FVoxelForgeCoreTileHashRecord* Actual = Actor->VoxelForgeCoreTileHashes.Find(Key);
const FVoxelForgeCoreTileHashRecord* Expected = ExpectedTravelHashes.Find(Key);
if (Actual == nullptr || Expected == nullptr || !Actual->bHasHashes
|| Actual->Density != Expected->Density || Actual->Mesh != Expected->Mesh
|| Actual->Payload != Expected->Payload || Actual->Topology != Expected->Topology
|| Actual->Triangles != Expected->Triangles || Actual->bEmpty != Expected->bEmpty)
{
OutError = FString::Printf(
TEXT("Core actor lower-landing tile differs from standalone Core at (%d,%d,%d) lod=%d."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z, Key.Level);
return false;
}
}
FString ActorCoverageError;
std::int32_t ActorCoverageLocalTiles = 0;
std::int32_t ActorCoverageFaces = 0;
std::int32_t ActorTransitionFaces = 0;
if (!AuditCoreClipmapCoverage(ActorTravelDesired,
{TravelCameraVoxels.x, TravelCameraVoxels.y, TravelCameraVoxels.z}, 128,
ActorCoverageLocalTiles, ActorCoverageFaces, ActorTransitionFaces,
ActorCoverageError))
{
OutError = FString::Printf(TEXT("AVoxelWorld lower-landing coverage failed: %s"),
*ActorCoverageError);
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] pass phase=surface_travel center=(%.3f,%.3f,%.3f) desired=%d loaded=%d local_tiles=%d checked_faces=%d lod_transition_faces=%d density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx"),
TravelCameraVoxels.x, TravelCameraVoxels.y, TravelCameraVoxels.z,
ActualTravelDesiredSet.Num(), Actor->LoadedTiles.Num(), ActorCoverageLocalTiles,
ActorCoverageFaces, ActorTransitionFaces,
static_cast<unsigned long long>(TravelAggregates[0]),
static_cast<unsigned long long>(TravelAggregates[1]),
static_cast<unsigned long long>(TravelAggregates[2]),
static_cast<unsigned long long>(TravelAggregates[3]));
const FVector SurfaceTravelWorldPosition(
SurfaceCameraVoxels.x * CoreVoxelSizeCm,
SurfaceCameraVoxels.y * CoreVoxelSizeCm,
SurfaceCameraVoxels.z * CoreVoxelSizeCm);
TravelPawn->SetActorLocation(SurfaceTravelWorldPosition);
auto WaitForSurfaceVisualTiles = [&]()
{
const double Deadline = FPlatformTime::Seconds() + 120.0;
while (FPlatformTime::Seconds() < Deadline)
{
TestWorld->Tick(LEVELTICK_All, 0.016f);
++GFrameCounter;
FPlatformProcess::Sleep(0.001f);
bool bVisualTilesReady = true;
for (const FVoxelTileKey& Key : ExpectedSurfaceTravelDesiredSet)
{
const FVoxelForgeCoreTileHashRecord* Expected = ExpectedSurfaceTravelHashes.Find(Key);
const FVoxelForgeCoreTileHashRecord* Actual = Actor->VoxelForgeCoreTileHashes.Find(Key);
if (Expected == nullptr || Actual == nullptr || !Actual->bHasHashes
|| (!Expected->bEmpty && !Actor->LoadedTiles.Contains(Key)))
{
bVisualTilesReady = false;
break;
}
}
if (bVisualTilesReady)
{
for (const FVoxelTileKey& Key : ExpectedSurfaceCollisionSet)
{
const FVoxelForgeCoreTileHashRecord* Expected = ExpectedSurfaceTravelHashes.Find(Key);
const bool bExpectedBody = Expected != nullptr && !Expected->bEmpty;
if (!Actor->CollisionDesiredTiles.Contains(Key)
|| (bExpectedBody && !Actor->CollisionReadyTiles.Contains(Key))
|| (!bExpectedBody && !Actor->CollisionNotRequiredTiles.Contains(Key)))
{
bVisualTilesReady = false;
break;
}
}
}
if (bVisualTilesReady) return true;
}
return false;
};
if (!WaitForSurfaceVisualTiles())
{
int32 HashedSurfaceTiles = 0;
int32 MissingVisibleSurfaceTiles = 0;
for (const FVoxelTileKey& Key : ExpectedSurfaceTravelDesiredSet)
{
if (Actor->VoxelForgeCoreTileHashes.Contains(Key)) ++HashedSurfaceTiles;
const FVoxelForgeCoreTileHashRecord* Expected = ExpectedSurfaceTravelHashes.Find(Key);
if (Expected != nullptr && !Expected->bEmpty && !Actor->LoadedTiles.Contains(Key))
++MissingVisibleSurfaceTiles;
}
OutError = FString::Printf(
TEXT("Core did not load the surface-strate visual set: expected=%d hashed=%d missing_visible=%d desired=%d loaded=%d pending=%d active=%d."),
ExpectedSurfaceTravelDesiredSet.Num(), HashedSurfaceTiles,
MissingVisibleSurfaceTiles, Actor->DesiredSorted.Num(), Actor->LoadedTiles.Num(),
Actor->PendingTiles.Num(), Actor->ActiveTaskCount.load(std::memory_order_acquire));
return false;
}
int32 ActualSurfaceCollisionReady = 0;
int32 ActualSurfaceCollisionNoBody = 0;
for (const FVoxelTileKey& Key : ExpectedSurfaceCollisionSet)
{
const FVoxelForgeCoreTileHashRecord& Expected = ExpectedSurfaceTravelHashes.FindChecked(Key);
if (!Actor->CollisionDesiredTiles.Contains(Key))
{
OutError = FString::Printf(TEXT("Native-surface collision tile (%d,%d,%d) was not requested."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z);
return false;
}
if (Expected.bEmpty)
{
if (!Actor->CollisionNotRequiredTiles.Contains(Key))
{
OutError = FString::Printf(TEXT("Native-surface empty tile (%d,%d,%d) has unexpected collision state."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z);
return false;
}
++ActualSurfaceCollisionNoBody;
}
else
{
if (!Actor->CollisionReadyTiles.Contains(Key))
{
OutError = FString::Printf(TEXT("Native-surface solid tile (%d,%d,%d) has no cooked collision body."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z);
return false;
}
++ActualSurfaceCollisionReady;
}
}
if (!HybridFixtureContacts.empty())
{
const FHybridFixtureContactProbe& FirstProbe = HybridFixtureContacts.front();
const FVoxelTileKey FixtureTileKey(FIntVector(
FMath::FloorToInt(FirstProbe.Expected.X / (CoreVoxelSizeCm * CHUNK_SIZE)),
FMath::FloorToInt(FirstProbe.Expected.Y / (CoreVoxelSizeCm * CHUNK_SIZE)),
FMath::FloorToInt(FirstProbe.Expected.Z / (CoreVoxelSizeCm * CHUNK_SIZE))), 0);
const FVoxelForgeCoreTileHashRecord* FixtureActorHash =
Actor->VoxelForgeCoreTileHashes.Find(FixtureTileKey);
const FVoxelForgeCoreTileHashRecord* FixtureExpectedHash =
ExpectedSurfaceTravelHashes.Find(FixtureTileKey);
if (!ExpectedHybridFixtureVisualMesh || FixtureExpectedHash == nullptr || FixtureActorHash == nullptr
|| !FixtureActorHash->bHasHashes
|| FixtureActorHash->Mesh != FixtureExpectedHash->Mesh
|| FixtureActorHash->Triangles != FixtureExpectedHash->Triangles)
{
OutError = FString::Printf(
TEXT("Hybrid fixture contact could not verify the actor render mesh against its standalone Core triangles: tile=(%d,%d,%d) visual_mesh=%d actor_hash=%d expected_hash=%d."),
FixtureTileKey.Coord.X, FixtureTileKey.Coord.Y, FixtureTileKey.Coord.Z,
ExpectedHybridFixtureVisualMesh ? 1 : 0, FixtureActorHash ? 1 : 0,
FixtureExpectedHash ? 1 : 0);
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeHybridVisualMesh] tile=(%d,%d,%d) actor_hash_match=1 mesh=0x%016llx triangles=%d source=standalone_core_render_triangles"),
FixtureTileKey.Coord.X, FixtureTileKey.Coord.Y, FixtureTileKey.Coord.Z,
static_cast<unsigned long long>(FixtureActorHash->Mesh), FixtureActorHash->Triangles);
FString HybridCollisionContactFailures;
for (const FHybridFixtureContactProbe& Probe : HybridFixtureContacts)
{
FHitResult PawnHit;
FHitResult PawnCapsuleHit;
FHitResult VisibilityHit;
FHitResult WorldStaticHit;
FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(VoxelForgeHybridFixtureContact), true);
FCollisionQueryParams CapsuleQueryParams(
SCENE_QUERY_STAT(VoxelForgeHybridFixtureCapsuleContact), false);
const bool bPawnHit = TestWorld->LineTraceSingleByChannel(
PawnHit, Probe.Start, Probe.End, ECC_Pawn, QueryParams);
const FCollisionShape PlayerCapsule = FCollisionShape::MakeCapsule(8.0f, 18.0f);
const bool bPawnCapsuleHit = TestWorld->SweepSingleByChannel(
PawnCapsuleHit, Probe.Start, Probe.End, FQuat::Identity,
ECC_Pawn, PlayerCapsule, CapsuleQueryParams);
const bool bVisibilityHit = TestWorld->LineTraceSingleByChannel(
VisibilityHit, Probe.Start, Probe.End, ECC_Visibility, QueryParams);
const bool bWorldStaticHit = TestWorld->LineTraceSingleByChannel(
WorldStaticHit, Probe.Start, Probe.End, ECC_WorldStatic, QueryParams);
URealtimeMeshComponent* FixtureComponent = nullptr;
const FVoxelTileKey ProbeTile(FIntVector(
FMath::FloorToInt(Probe.Expected.X / (CoreVoxelSizeCm * CHUNK_SIZE)),
FMath::FloorToInt(Probe.Expected.Y / (CoreVoxelSizeCm * CHUNK_SIZE)),
FMath::FloorToInt(Probe.Expected.Z / (CoreVoxelSizeCm * CHUNK_SIZE))), 0);
FixtureComponent = Actor->TileComponents.FindRef(ProbeTile);
FHitResult ObjectHit;
bool bObjectHit = false;
if (IsValid(FixtureComponent))
{
FCollisionObjectQueryParams ObjectQueryParams;
ObjectQueryParams.AddObjectTypesToQuery(FixtureComponent->GetCollisionObjectType());
bObjectHit = TestWorld->LineTraceSingleByObjectType(
ObjectHit, Probe.Start, Probe.End, ObjectQueryParams, QueryParams);
}
const bool bPawnLineContact = bPawnHit && PawnHit.GetActor() == Actor
&& PawnHit.GetComponent() == FixtureComponent;
const bool bPawnCapsuleContact = bPawnCapsuleHit
&& PawnCapsuleHit.GetActor() == Actor
&& PawnCapsuleHit.GetComponent() == FixtureComponent;
const FHitResult* ContactHit = bPawnLineContact ? &PawnHit
: (bPawnCapsuleContact ? &PawnCapsuleHit : nullptr);
const FVector ContactDeltaCm = ContactHit != nullptr
? ContactHit->ImpactPoint - Probe.Expected : FVector::ZeroVector;
const double ExpectedPointErrorCm = ContactHit != nullptr
? FVector::Dist(ContactHit->ImpactPoint, Probe.Expected)
: TNumericLimits<double>::Max();
float DensityAtContact = TNumericLimits<float>::Max();
const bool bCoreDensityAtContact = ContactHit != nullptr
&& Actor->QueryVoxelForgeCoreDensityAt(ContactHit->ImpactPoint, DensityAtContact);
FHitResult ComponentHit;
const bool bComponentHit = IsValid(FixtureComponent)
&& FixtureComponent->LineTraceComponent(
ComponentHit, Probe.Start, Probe.End, QueryParams);
FBox RenderBounds(EForceInit::ForceInit);
FBox CollisionBounds(EForceInit::ForceInit);
const bool bHaveRenderCollisionBounds = GetHybridFixtureRenderAndCollisionBounds(
FixtureComponent, RenderBounds, CollisionBounds);
constexpr double BoundsToleranceCm = 0.25;
const bool bRenderCollisionBoundsMatch = bHaveRenderCollisionBounds
&& FVector::Dist(RenderBounds.Min, CollisionBounds.Min) <= BoundsToleranceCm
&& FVector::Dist(RenderBounds.Max, CollisionBounds.Max) <= BoundsToleranceCm;
const double VisualContactErrorCm = ContactHit != nullptr && bComponentHit
? FVector::Dist(ContactHit->ImpactPoint, ComponentHit.ImpactPoint)
: TNumericLimits<double>::Max();
FVector VisualImpact = bComponentHit ? ComponentHit.ImpactPoint : FVector::ZeroVector;
const bool bVisualHit = ContactHit != nullptr && bComponentHit
&& bRenderCollisionBoundsMatch;
// Le trace direct du composant et le BodySetup cuisine doivent suivre la meme section RMC.
// The direct component trace and cooked BodySetup must agree with the same RMC render section.
const double AnalyticContactToleranceCm = Arguments.bCoreHybridMesherV2
? 1.0 : CoreVoxelSizeCm;
constexpr double ComponentTraceToleranceCm = 2.0;
if (ContactHit == nullptr || ExpectedPointErrorCm > AnalyticContactToleranceCm
|| !bVisualHit || VisualContactErrorCm > ComponentTraceToleranceCm)
{
const FTransform ComponentTransform = IsValid(FixtureComponent)
? FixtureComponent->GetComponentTransform() : FTransform::Identity;
const FVector TransformedStart = ComponentTransform.TransformPosition(Probe.Start);
const FVector TransformedEnd = ComponentTransform.TransformPosition(Probe.End);
const FVector TransformedExpected = ComponentTransform.TransformPosition(Probe.Expected);
FHitResult TransformedWorldHit;
const bool bTransformedWorldHit = IsValid(FixtureComponent)
&& TestWorld->LineTraceSingleByChannel(
TransformedWorldHit, TransformedStart, TransformedEnd, ECC_Pawn, QueryParams);
float DensityStart = TNumericLimits<float>::Max();
float DensityExpected = TNumericLimits<float>::Max();
float DensityEnd = TNumericLimits<float>::Max();
const bool bCoreDensityStart = Actor->QueryVoxelForgeCoreDensityAt(
Probe.Start, DensityStart);
const bool bCoreDensityExpected = Actor->QueryVoxelForgeCoreDensityAt(
Probe.Expected, DensityExpected);
const bool bCoreDensityEnd = Actor->QueryVoxelForgeCoreDensityAt(
Probe.End, DensityEnd);
const FVector ComponentLocation = IsValid(FixtureComponent)
? FixtureComponent->GetComponentLocation() : FVector::ZeroVector;
const FVector ComponentOrigin = IsValid(FixtureComponent)
? FixtureComponent->Bounds.Origin : FVector::ZeroVector;
const FVector ComponentExtent = IsValid(FixtureComponent)
? FixtureComponent->Bounds.BoxExtent : FVector::ZeroVector;
const URealtimeMesh* FixtureMesh = IsValid(FixtureComponent)
? FixtureComponent->GetRealtimeMesh() : nullptr;
const FBoxSphereBounds MeshLocalBounds = FixtureMesh != nullptr
? FixtureMesh->GetLocalBounds()
: FBoxSphereBounds(FSphere(FVector::ZeroVector, 0.0f));
const UBodySetup* BodySetup = IsValid(FixtureComponent)
? FixtureComponent->GetBodySetup() : nullptr;
FVector BodyMeshMinimum(TNumericLimits<double>::Max());
FVector BodyMeshMaximum(-TNumericLimits<double>::Max());
int32 BodyMeshCount = 0;
if (BodySetup != nullptr)
{
BodyMeshCount = BodySetup->TriMeshGeometries.Num();
for (const Chaos::FTriangleMeshImplicitObjectPtr& BodyMesh : BodySetup->TriMeshGeometries)
{
if (!BodyMesh.IsValid()) continue;
const Chaos::FAABB3& BodyBounds = BodyMesh->BoundingBox();
const FVector BodyMinimum(
BodyBounds.Min()[0], BodyBounds.Min()[1], BodyBounds.Min()[2]);
const FVector BodyMaximum(
BodyBounds.Max()[0], BodyBounds.Max()[1], BodyBounds.Max()[2]);
BodyMeshMinimum.X = FMath::Min(BodyMeshMinimum.X, BodyMinimum.X);
BodyMeshMinimum.Y = FMath::Min(BodyMeshMinimum.Y, BodyMinimum.Y);
BodyMeshMinimum.Z = FMath::Min(BodyMeshMinimum.Z, BodyMinimum.Z);
BodyMeshMaximum.X = FMath::Max(BodyMeshMaximum.X, BodyMaximum.X);
BodyMeshMaximum.Y = FMath::Max(BodyMeshMaximum.Y, BodyMaximum.Y);
BodyMeshMaximum.Z = FMath::Max(BodyMeshMaximum.Z, BodyMaximum.Z);
}
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeHybridCollisionDiagnostic] fixture=%s tile=(%d,%d,%d) "
"ready=%d not_required=%d component=%d body=%d physics_state=%d "
"collision_enabled=%d pawn_response=%d visibility_response=%d object_type=%d "
"pawn_hit=%d pawn_capsule_hit=%d capsule_actor_match=%d capsule_component_match=%d component_trace_hit=%d component_trace_error_cm=%.3f bounds_match=%d "
"visibility_hit=%d world_static_hit=%d object_hit=%d component_hit=%d "
"transformed_world_hit=%d "
"component_location=(%.2f,%.2f,%.2f) bounds_origin=(%.2f,%.2f,%.2f) "
"component_rotation=(%.2f,%.2f,%.2f) component_scale=(%.4f,%.4f,%.4f) "
"transformed_expected=(%.2f,%.2f,%.2f) "
"bounds_extent=(%.2f,%.2f,%.2f) mesh_local_origin=(%.2f,%.2f,%.2f) "
"mesh_local_extent=(%.2f,%.2f,%.2f) body_trace_flag=%d double_sided=%d "
"body_created=%d body_failed=%d body_tri_meshes=%d "
"body_min=(%.2f,%.2f,%.2f) body_max=(%.2f,%.2f,%.2f) "
"core_density_valid=%d/%d/%d "
"core_density_start=%.4f core_density_expected=%.4f core_density_end=%.4f"),
*Probe.Name, ProbeTile.Coord.X, ProbeTile.Coord.Y, ProbeTile.Coord.Z,
Actor->CollisionReadyTiles.Contains(ProbeTile) ? 1 : 0,
Actor->CollisionNotRequiredTiles.Contains(ProbeTile) ? 1 : 0,
IsValid(FixtureComponent) ? 1 : 0,
IsValid(FixtureComponent) && FixtureComponent->GetBodySetup() != nullptr ? 1 : 0,
IsValid(FixtureComponent) && FixtureComponent->IsPhysicsStateCreated() ? 1 : 0,
IsValid(FixtureComponent)
? static_cast<int32>(FixtureComponent->GetCollisionEnabled()) : -1,
IsValid(FixtureComponent)
? static_cast<int32>(FixtureComponent->GetCollisionResponseToChannel(ECC_Pawn)) : -1,
IsValid(FixtureComponent)
? static_cast<int32>(FixtureComponent->GetCollisionResponseToChannel(ECC_Visibility)) : -1,
IsValid(FixtureComponent)
? static_cast<int32>(FixtureComponent->GetCollisionObjectType()) : -1,
bPawnHit ? 1 : 0, bPawnCapsuleHit ? 1 : 0,
bPawnCapsuleHit && PawnCapsuleHit.GetActor() == Actor ? 1 : 0,
bPawnCapsuleHit && PawnCapsuleHit.GetComponent() == FixtureComponent ? 1 : 0,
bComponentHit ? 1 : 0, VisualContactErrorCm,
bRenderCollisionBoundsMatch ? 1 : 0,
bVisibilityHit ? 1 : 0,
bWorldStaticHit ? 1 : 0, bObjectHit ? 1 : 0,
bComponentHit ? 1 : 0, bTransformedWorldHit ? 1 : 0,
ComponentLocation.X, ComponentLocation.Y, ComponentLocation.Z,
ComponentOrigin.X, ComponentOrigin.Y, ComponentOrigin.Z,
ComponentTransform.Rotator().Pitch, ComponentTransform.Rotator().Yaw,
ComponentTransform.Rotator().Roll,
ComponentTransform.GetScale3D().X, ComponentTransform.GetScale3D().Y,
ComponentTransform.GetScale3D().Z,
TransformedExpected.X, TransformedExpected.Y, TransformedExpected.Z,
ComponentExtent.X, ComponentExtent.Y, ComponentExtent.Z,
MeshLocalBounds.Origin.X, MeshLocalBounds.Origin.Y, MeshLocalBounds.Origin.Z,
MeshLocalBounds.BoxExtent.X, MeshLocalBounds.BoxExtent.Y, MeshLocalBounds.BoxExtent.Z,
BodySetup != nullptr ? static_cast<int32>(BodySetup->CollisionTraceFlag) : -1,
BodySetup != nullptr && BodySetup->bDoubleSidedGeometry ? 1 : 0,
BodySetup != nullptr && BodySetup->bCreatedPhysicsMeshes ? 1 : 0,
BodySetup != nullptr && BodySetup->bFailedToCreatePhysicsMeshes ? 1 : 0,
BodyMeshCount,
BodyMeshCount > 0 ? BodyMeshMinimum.X : 0.0,
BodyMeshCount > 0 ? BodyMeshMinimum.Y : 0.0,
BodyMeshCount > 0 ? BodyMeshMinimum.Z : 0.0,
BodyMeshCount > 0 ? BodyMeshMaximum.X : 0.0,
BodyMeshCount > 0 ? BodyMeshMaximum.Y : 0.0,
BodyMeshCount > 0 ? BodyMeshMaximum.Z : 0.0,
bCoreDensityStart ? 1 : 0,
bCoreDensityExpected ? 1 : 0,
bCoreDensityEnd ? 1 : 0,
DensityStart, DensityExpected, DensityEnd);
HybridCollisionContactFailures += FString::Printf(
TEXT("Hybrid cooked Pawn collision contact failed for %s: pawn_hit=%d pawn_capsule_hit=%d capsule_actor_match=%d capsule_component_match=%d component_trace_hit=%d component_trace_error_cm=%.3f bounds_match=%d expected_point_error_cm=%.3f expected_delta_cm=(%.3f,%.3f,%.3f) density_at_contact=%.6f density_query=%d visibility_hit=%d world_static_hit=%d expected=(%.2f,%.2f,%.2f) pawn_impact=(%.2f,%.2f,%.2f) capsule_impact=(%.2f,%.2f,%.2f)."),
*Probe.Name, bPawnHit ? 1 : 0, bPawnCapsuleHit ? 1 : 0,
bPawnCapsuleHit && PawnCapsuleHit.GetActor() == Actor ? 1 : 0,
bPawnCapsuleHit && PawnCapsuleHit.GetComponent() == FixtureComponent ? 1 : 0,
bComponentHit ? 1 : 0, VisualContactErrorCm,
bRenderCollisionBoundsMatch ? 1 : 0, ExpectedPointErrorCm,
ContactDeltaCm.X, ContactDeltaCm.Y, ContactDeltaCm.Z,
DensityAtContact, bCoreDensityAtContact ? 1 : 0,
bVisibilityHit ? 1 : 0, bWorldStaticHit ? 1 : 0,
Probe.Expected.X, Probe.Expected.Y, Probe.Expected.Z,
bPawnHit ? PawnHit.ImpactPoint.X : 0.0,
bPawnHit ? PawnHit.ImpactPoint.Y : 0.0,
bPawnHit ? PawnHit.ImpactPoint.Z : 0.0,
bPawnCapsuleHit ? PawnCapsuleHit.ImpactPoint.X : 0.0,
bPawnCapsuleHit ? PawnCapsuleHit.ImpactPoint.Y : 0.0,
bPawnCapsuleHit ? PawnCapsuleHit.ImpactPoint.Z : 0.0);
HybridCollisionContactFailures += TEXT("\n");
continue;
}
const TCHAR* ContactMethod = bPawnLineContact ? TEXT("PawnLine") : TEXT("PawnCapsule");
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeHybridCollisionContact] fixture=%s method=%s channel=Pawn hit=1 actor_match=1 component_match=1 actor_core_hash_match=1 render_collision_bounds_match=1 component_trace_error_cm=%.3f expected_point_error_cm=%.3f expected_delta_cm=(%.3f,%.3f,%.3f) density_at_contact=%.6f density_query=%d expected=(%.2f,%.2f,%.2f) impact=(%.2f,%.2f,%.2f) component_impact=(%.2f,%.2f,%.2f)"),
*Probe.Name, ContactMethod, VisualContactErrorCm, ExpectedPointErrorCm,
ContactDeltaCm.X, ContactDeltaCm.Y, ContactDeltaCm.Z,
DensityAtContact, bCoreDensityAtContact ? 1 : 0,
Probe.Expected.X, Probe.Expected.Y, Probe.Expected.Z,
ContactHit->ImpactPoint.X, ContactHit->ImpactPoint.Y, ContactHit->ImpactPoint.Z,
VisualImpact.X, VisualImpact.Y, VisualImpact.Z);
}
if (!HybridCollisionContactFailures.IsEmpty())
{
OutError = HybridCollisionContactFailures;
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeHybridCollisionContact] pass=1 probes=%d cooked_ready_tiles=%d"),
static_cast<int32>(HybridFixtureContacts.size()), ActualSurfaceCollisionReady);
}
TSet<FVoxelTileKey> ActualSurfaceVisualDesiredSet;
std::vector<VoxelForgeCore::TileKey> ActorSurfaceTravelDesired;
ActorSurfaceTravelDesired.reserve(static_cast<std::size_t>(Actor->DesiredSorted.Num()));
for (const FVoxelTileKey& Key : Actor->DesiredSorted)
{
if (!Actor->CollisionOnlyTiles.Contains(Key))
{
ActualSurfaceVisualDesiredSet.Add(Key);
ActorSurfaceTravelDesired.push_back({{Key.Coord.X, Key.Coord.Y, Key.Coord.Z}, Key.Level});
}
}
if (ActualSurfaceVisualDesiredSet.Num() != ExpectedSurfaceTravelDesiredSet.Num())
{
OutError = FString::Printf(
TEXT("Core actor desired %d visual tiles at the surface-strate fixture; standalone Core expects %d."),
ActualSurfaceVisualDesiredSet.Num(), ExpectedSurfaceTravelDesiredSet.Num());
return false;
}
for (const FVoxelTileKey& Key : ExpectedSurfaceTravelDesiredSet)
{
if (!ActualSurfaceVisualDesiredSet.Contains(Key))
{
OutError = FString::Printf(
TEXT("Core actor omitted surface-strate visual tile (%d,%d,%d) lod=%d."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z, Key.Level);
return false;
}
const FVoxelForgeCoreTileHashRecord* Actual = Actor->VoxelForgeCoreTileHashes.Find(Key);
const FVoxelForgeCoreTileHashRecord* Expected = ExpectedSurfaceTravelHashes.Find(Key);
if (Actual == nullptr || Expected == nullptr || !Actual->bHasHashes
|| (!Expected->bEmpty && !Actor->LoadedTiles.Contains(Key)))
{
OutError = FString::Printf(
TEXT("Core actor surface-strate tile state is incomplete at (%d,%d,%d) lod=%d (actual=%d expected=%d hashed=%d loaded=%d)."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z, Key.Level,
Actual ? 1 : 0, Expected ? 1 : 0, Actual && Actual->bHasHashes ? 1 : 0,
Actor->LoadedTiles.Contains(Key) ? 1 : 0);
return false;
}
if (Actual->Density != Expected->Density || Actual->Mesh != Expected->Mesh
|| Actual->Payload != Expected->Payload || Actual->Topology != Expected->Topology
|| Actual->Triangles != Expected->Triangles || Actual->bEmpty != Expected->bEmpty)
{
OutError = FString::Printf(
TEXT("Core actor surface-strate hash mismatch at (%d,%d,%d) lod=%d; density=%016llx/%016llx mesh=%016llx/%016llx payload=%016llx/%016llx topology=%016llx/%016llx triangles=%d/%d empty=%d/%d."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z, Key.Level,
static_cast<unsigned long long>(Actual->Density),
static_cast<unsigned long long>(Expected->Density),
static_cast<unsigned long long>(Actual->Mesh),
static_cast<unsigned long long>(Expected->Mesh),
static_cast<unsigned long long>(Actual->Payload),
static_cast<unsigned long long>(Expected->Payload),
static_cast<unsigned long long>(Actual->Topology),
static_cast<unsigned long long>(Expected->Topology),
Actual->Triangles, Expected->Triangles, Actual->bEmpty ? 1 : 0, Expected->bEmpty ? 1 : 0);
return false;
}
}
FString SurfaceCoverageError;
std::int32_t ActorSurfaceLocalTiles = 0;
std::int32_t ActorSurfaceCheckedFaces = 0;
std::int32_t ActorSurfaceTransitionFaces = 0;
if (!AuditCoreClipmapCoverage(ActorSurfaceTravelDesired,
{SurfaceCameraVoxels.x, SurfaceCameraVoxels.y, SurfaceCameraVoxels.z}, 128,
ActorSurfaceLocalTiles, ActorSurfaceCheckedFaces, ActorSurfaceTransitionFaces,
SurfaceCoverageError))
{
OutError = FString::Printf(TEXT("AVoxelWorld surface-strate coverage failed: %s"),
*SurfaceCoverageError);
return false;
}
if (!bNativeBiomeFieldValidated || NativeBiomeIdsSampled < 2)
{
OutError = TEXT("Core native SurfaceWorld biome validation did not complete.");
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] pass phase=surface_strate_travel center=(%.3f,%.3f,%.3f) desired=%d loaded=%d fine_compiled=%llu regions=%llu local_tiles=%d checked_faces=%d lod_transition_faces=%d collision_desired=%d collision_ready=%d collision_no_body=%d native_surface_biome_id=%u native_biomes_sampled=%d density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx"),
SurfaceCameraVoxels.x, SurfaceCameraVoxels.y, SurfaceCameraVoxels.z,
ActualSurfaceVisualDesiredSet.Num(), Actor->LoadedTiles.Num(),
static_cast<unsigned long long>(SurfaceTravelFineTilesCompiled),
static_cast<unsigned long long>(SurfaceTravelRegionsBuilt), ActorSurfaceLocalTiles,
ActorSurfaceCheckedFaces, ActorSurfaceTransitionFaces, ExpectedSurfaceCollisionSet.Num(),
ActualSurfaceCollisionReady, ActualSurfaceCollisionNoBody,
NativeSurfaceBiomeId, NativeBiomeIdsSampled,
static_cast<unsigned long long>(SurfaceTravelAggregates[0]),
static_cast<unsigned long long>(SurfaceTravelAggregates[1]),
static_cast<unsigned long long>(SurfaceTravelAggregates[2]),
static_cast<unsigned long long>(SurfaceTravelAggregates[3]));
const FVector FillInsideWorldPosition = CarveWorldPosition + FVector(60.0f, 0.0f, 0.0f);
const FVector FillOutsideWorldPosition = CarveWorldPosition + FVector(80.0f, 0.0f, 0.0f);
float FillDensityBefore = 0.0f, FillInsideDensityBefore = 0.0f, FillOutsideDensityBefore = 0.0f;
if (!Actor->QueryVoxelForgeCoreDensityAt(CarveWorldPosition, FillDensityBefore)
|| !Actor->QueryVoxelForgeCoreDensityAt(FillInsideWorldPosition, FillInsideDensityBefore)
|| !Actor->QueryVoxelForgeCoreDensityAt(FillOutsideWorldPosition, FillOutsideDensityBefore))
{
OutError = TEXT("Core Blueprint fill fixture could not read its pre-edit size samples.");
return false;
}
Actor->FillAtPosition(CarveWorldPosition, 6.0f, 10.0f);
float FillDensityAfter = 0.0f, FillInsideDensityAfter = 0.0f, FillOutsideDensityAfter = 0.0f;
if (!Actor->QueryVoxelForgeCoreDensityAt(CarveWorldPosition, FillDensityAfter)
|| !Actor->QueryVoxelForgeCoreDensityAt(FillInsideWorldPosition, FillInsideDensityAfter)
|| !Actor->QueryVoxelForgeCoreDensityAt(FillOutsideWorldPosition, FillOutsideDensityAfter))
{
OutError = TEXT("Core Blueprint fill fixture could not read its post-edit size samples.");
return false;
}
const float FillCenterDelta = FillDensityAfter - FillDensityBefore;
const float FillInsideDelta = FillInsideDensityAfter - FillInsideDensityBefore;
const float FillOutsideDelta = FillOutsideDensityAfter - FillOutsideDensityBefore;
const float ExpectedFillCenterDelta = -ExpectedBrushCenterDelta;
if (!FMath::IsNearlyEqual(FillCenterDelta, ExpectedFillCenterDelta, 0.01f)
|| !(FillInsideDelta < 0.0f && FillInsideDelta > FillCenterDelta)
|| !FMath::IsNearlyZero(FillOutsideDelta, 0.001f))
{
OutError = FString::Printf(
TEXT("Blueprint fill sign/size failed: center=%.4f expected=%.4f inside60cm=%.4f outside80cm=%.4f; expected partial -, zero."),
FillCenterDelta, ExpectedFillCenterDelta, FillInsideDelta, FillOutsideDelta);
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] blueprint_fill=PASS radius_legacy=6 radius_cm=%.3f strength_legacy=10 center_delta=%.4f expected_center_delta=%.4f inside_60cm_delta=%.4f outside_80cm_delta=%.4f"),
6.0 * static_cast<double>(VOXEL_SIZE), FillCenterDelta, ExpectedFillCenterDelta,
FillInsideDelta, FillOutsideDelta);
if (!RunCoreContentPlacementCostAudit(Actor, SurfaceLocalPosition,
TEXT("SurfaceWorld"), OutError))
{
return false;
}
return true;
}
} // namespace
UVoxelForgeExploreCommandlet::UVoxelForgeExploreCommandlet()
{
IsClient = false;
IsServer = false;
IsEditor = true;
LogToConsole = true;
}
int32 RunExploreCase(const FString& Params, FString* OutJson)
{
const double MainStartSeconds = FPlatformTime::Seconds();
UE_LOG(LogTemp, Display, TEXT("[VoxelForgeExplore] invoked: %s"), FCommandLine::Get());
FExploreArguments Arguments;
FString Error;
if (!ParseArguments(Params, Arguments, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeExplore] %s"), *Error);
return 1;
}
// A commandlet can run several fresh manager lifetimes in one process. Release completed
// immutable proof caches from the preceding case before constructing this one.
VoxelDensityOps::TrimSharedRoomGraphCache();
// Every case starts from a disabled, empty diagnostic state. The world/cache objects are
// local to this invocation and their owner/lifetime keys prevent stale worker caches from
// serving the next case's generation.
VoxelDensityProfile::Reset();
VoxelDensityProfile::SetMode(
Arguments.IsDensityProfilingEnabled()
? ExploreProfileMode(Arguments)
: VoxelDensityProfile::EMode::Disabled,
VoxelDensityProfile::DefaultSampleInterval);
if (!IFileManager::Get().MakeDirectory(*Arguments.OutDirectory, true))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeExplore] Could not create output directory '%s'."),
*Arguments.OutDirectory);
return 1;
}
if (Arguments.bCoreWorldTest)
{
if (!RunCoreWorldActorIntegrationTest(Arguments, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeCoreWorldTest] failed: %s"), *Error);
return 1;
}
UE_LOG(LogTemp, Display, TEXT("[VoxelForgeCoreWorldTest] pass complete=1"));
return 0;
}
if (Arguments.bStandaloneWorldExport)
{
if (!RunStandaloneWorldExport(Arguments, Error, OutJson))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeExplore] %s"), *Error);
return 1;
}
return 0;
}
FExploreBudget Budget(
MainStartSeconds,
static_cast<double>(Arguments.BudgetMinutes) * 60.0);
const double SetupStartSeconds = FPlatformTime::Seconds();
FExploreWorld World;
if (!World.Build(Arguments, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeExplore] %s"), *Error);
return 1;
}
const double SetupSeconds = FPlatformTime::Seconds() - SetupStartSeconds;
FExploreStackSamplerSession StackSamplerSession;
if (Arguments.bSampleStacks
&& !StackSamplerSession.Start(
Arguments.SampleStacksIntervalUs, Arguments.OutDirectory))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeExplore] stack sampler was requested but could not be started."));
return 1;
}
FExploreRunOutput Output;
bool bRequestedModeFailed = false;
if (Arguments.bTunnelCoreProbe)
{
const double Start = FPlatformTime::Seconds();
const bool bProbeOk = !Budget.bTruncated
&& RunTunnelCoreProbe(Arguments, World, Output.TunnelCoreProbe, Budget);
if (!bProbeOk && !Budget.bTruncated)
{
bRequestedModeFailed = true;
}
if (bProbeOk && !Budget.bTruncated)
{
Budget.CompleteMode(TEXT("tunnelcoreprobe"));
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeExplore] tunnel core probe %.3fs (%s)"),
FPlatformTime::Seconds() - Start,
bProbeOk ? TEXT("ok")
: (Budget.bTruncated ? TEXT("truncated") : TEXT("error")));
}
if (Arguments.bDensitySlice)
{
const double Start = FPlatformTime::Seconds();
const bool bDensitySliceOk = !Budget.bTruncated
&& RunDensitySlice(Arguments, World, Output.DensitySlice, Budget);
if (!bDensitySliceOk && !Budget.bTruncated)
{
bRequestedModeFailed = true;
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeExplore] density slice failed: %s"),
*Output.DensitySlice.RefusalReason);
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeExplore] density slice %.3fs (%s)"),
FPlatformTime::Seconds() - Start,
bDensitySliceOk ? TEXT("ok")
: (Budget.bTruncated ? TEXT("truncated") : TEXT("error")));
}
if (Arguments.bOpBounds)
{
const double Start = FPlatformTime::Seconds();
const bool bOpBoundsOk = !Budget.bTruncated
&& RunOpBounds(Arguments, World, Budget, Error);
if (!bOpBoundsOk && !Budget.bTruncated)
{
bRequestedModeFailed = true;
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeExplore] opbounds failed: %s"), *Error);
}
if (bOpBoundsOk && !Budget.bTruncated)
{
Budget.CompleteMode(TEXT("opbounds"));
}
UE_LOG(LogTemp, Display, TEXT("[VoxelForgeExplore] opbounds %.3fs (%s)"),
FPlatformTime::Seconds() - Start,
bOpBoundsOk ? TEXT("ok") : (Budget.bTruncated ? TEXT("truncated") : TEXT("error")));
}
if (Arguments.bProfileLod)
{
const FIntVector ProbeOrigin(0, 0, World.TargetBottomWorldZ);
const int32 FullRes = World.Settings
? FMath::Max(1, World.Settings->FullResClipLevels)
: 2;
const int32 CoarseCells = World.Settings
? FMath::Clamp(World.Settings->CoarseTileCells, 4, CHUNK_SIZE)
: 16;
const int32 CutMin = World.Settings
? World.Settings->GetEffectiveStrateContentCutMinLevel()
: 9;
for (int32 Level = 0; Level <= 4; ++Level)
{
// Match AVoxelWorld::LoadTile exactly: the probe is a production tile at this LOD,
// including the configured coarse sample count. Using 1<<Level here would measure
// a denser mesh than the game generates at levels >= FullResClipLevels.
const int32 Extent = CHUNK_SIZE << Level;
const int32 Cells = Level < FullRes ? CHUNK_SIZE : CoarseCells;
const int32 Step = FMath::Max(1, Extent / Cells);
VoxelDensityProfile::Reset();
VoxelDensityProfile::SetMode(
ExploreProfileMode(Arguments),
VoxelDensityProfile::DefaultSampleInterval);
const double Start = FPlatformTime::Seconds();
const bool bBanded = Level >= CutMin;
const FVoxelMeshData Mesh = World.Mesher->GenerateMesh(
ProbeOrigin, Step, Cells, nullptr,
bBanded ? World.TargetBottomWorldZ : INT32_MIN,
bBanded ? World.TargetTopWorldZ - 1 : INT32_MAX);
const double Seconds = FPlatformTime::Seconds() - Start;
const VoxelDensityProfile::FSnapshot Profile = VoxelDensityProfile::Snapshot();
const int32 DensityIndex = static_cast<int32>(VoxelDensityProfile::EBucket::GetDensityAt);
const uint64 Calls = Profile.Calls[DensityIndex];
const uint64 Lookups = Profile.Counters[static_cast<int32>(VoxelDensityProfile::ECounter::TunnelCacheLookup)];
const uint64 Hits = Profile.Counters[static_cast<int32>(VoxelDensityProfile::ECounter::TunnelCacheHit)];
const uint64 SdfBuilds = Profile.Counters[static_cast<int32>(VoxelDensityProfile::ECounter::SdfCacheBuild)];
const double DensityUs = static_cast<double>(Profile.WallCycles[DensityIndex])
* FPlatformTime::GetSecondsPerCycle64() * 1.0e6;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeLODProfile] level=%d step=%d cells=%d banded=%d seconds=%.6f density_calls=%llu "
"density_us_per_call=%.3f sdf_builds=%llu cache_lookups=%llu cache_hits=%llu cache_misses=%llu "
"hit_rate=%.3f vertices=%d triangles=%d mesh_empty=%d"),
Level, Step, Cells, bBanded ? 1 : 0, Seconds,
static_cast<unsigned long long>(Calls),
Calls > 0 ? DensityUs / static_cast<double>(Calls) : 0.0,
static_cast<unsigned long long>(SdfBuilds),
static_cast<unsigned long long>(Lookups),
static_cast<unsigned long long>(Hits),
static_cast<unsigned long long>(Profile.Counters[static_cast<int32>(VoxelDensityProfile::ECounter::TunnelCacheMiss)]),
Lookups > 0 ? static_cast<double>(Hits) / static_cast<double>(Lookups) : 0.0,
Mesh.Vertices.Num(), Mesh.Triangles.Num() / 3,
Mesh.IsEmpty() ? 1 : 0);
}
if (Arguments.IsDensityProfilingEnabled())
{
VoxelDensityProfile::Reset();
VoxelDensityProfile::SetMode(
ExploreProfileMode(Arguments),
VoxelDensityProfile::DefaultSampleInterval);
}
else
{
VoxelDensityProfile::SetMode(VoxelDensityProfile::EMode::Disabled);
}
}
if (Budget.ShouldStop(TEXT("setup")))
{
UE_LOG(LogTemp, Warning,
TEXT("[VoxelForgeExplore] setup consumed the wall-clock budget; no mode was started."));
}
FExploreWalkOutput RenderSeedWalk;
const FExploreWalkOutput* CameraSeedWalk = nullptr;
if (Arguments.bWalk)
{
const double Start = FPlatformTime::Seconds();
const bool bWalkOk = RunWalk(Arguments, World, Output.Walk, Budget);
Output.WalkSeconds = FPlatformTime::Seconds() - Start;
if (!bWalkOk && !Budget.bTruncated)
{
bRequestedModeFailed = true;
}
UE_LOG(LogTemp, Display, TEXT("[VoxelForgeExplore] walk %.3fs (%s)"),
Output.WalkSeconds, *Output.Walk.Status);
CameraSeedWalk = &Output.Walk;
if (bWalkOk && !Budget.bTruncated)
{
Budget.CompleteMode(TEXT("walk"));
}
}
else if (Arguments.bRender)
{
if (Arguments.bRenderCameraOverride)
{
// Controlled diagnostic renders can supply a player-fit seed captured from a baseline
// run. This keeps the mesh region, camera pose, and raster target identical while a
// single generator writer is toggled. The density guard in RunRender still refuses a
// stale/inside-rock pose.
RenderSeedWalk.CameraSeedPoses.Add(Arguments.RenderCamera);
RenderSeedWalk.CameraSeedSources.Add(TEXT("render_camera_override"));
RenderSeedWalk.Status = TEXT("override");
Output.WalkSeconds = 0.0;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeExplore] render camera seed override (%.3f, %.3f, %.3f)"),
Arguments.RenderCamera.X, Arguments.RenderCamera.Y, Arguments.RenderCamera.Z);
}
else
{
// A render-only invocation still has to obey the inside-walkable-space contract. Run
// the same focused walk as a private seed pass; it is intentionally omitted from the
// render-only JSON so the selected mode remains render.
const double Start = FPlatformTime::Seconds();
const bool bWalkOk = RunWalk(Arguments, World, RenderSeedWalk, Budget);
Output.WalkSeconds = FPlatformTime::Seconds() - Start;
if (!bWalkOk && !Budget.bTruncated)
{
bRequestedModeFailed = true;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeExplore] render camera seed walk %.3fs (%s)"),
Output.WalkSeconds, *RenderSeedWalk.Status);
}
CameraSeedWalk = &RenderSeedWalk;
// Keep the private render seed capture available to the run-level shared-grid report;
// the walk object is still omitted from the public JSON when walk was not requested.
Output.Walk = MoveTemp(RenderSeedWalk);
CameraSeedWalk = &Output.Walk;
}
// A profile report gets a genuine unprofiled reference for the same canonical mesh region.
// The reference world is fresh, profiling is disabled for it, and its geometry hash is
// compared with the profiled world below. This is deliberately a diagnostic second pass;
// it cannot affect the profiled world's generation or its deterministic output.
if (Arguments.IsDensityProfilingEnabled()
&& (Arguments.bRender || Arguments.bExport)
&& (CameraSeedWalk != nullptr || Arguments.bExport)
&& !Budget.bTruncated)
{
Output.ProfilerComparison.Scope = TEXT("canonical_mesh_export");
VoxelDensityProfile::SetMode(VoxelDensityProfile::EMode::Disabled);
FExploreWorld ReferenceWorld;
FString ReferenceError;
if (ReferenceWorld.Build(Arguments, ReferenceError))
{
FExploreBudget ReferenceBudget(MainStartSeconds, Budget.LimitSeconds);
const double ReferenceStart = FPlatformTime::Seconds();
if (EnsureExploreMesh(
Arguments, ReferenceWorld, CameraSeedWalk, ReferenceBudget, ReferenceError)
&& ReferenceWorld.bExploreMeshComplete)
{
Output.ProfilerComparison.bAvailable = true;
Output.ProfilerComparison.UnprofiledSeconds = ReferenceWorld.ExploreMeshSeconds;
Output.ProfilerComparison.UnprofiledGeometryHash = ReferenceWorld.ExploreGeometryHash;
}
else
{
UE_LOG(LogTemp, Warning,
TEXT("[VoxelForgeProfiler] unprofiled reference failed after %.3fs: %s"),
FPlatformTime::Seconds() - ReferenceStart, *ReferenceError);
}
}
else
{
UE_LOG(LogTemp, Warning,
TEXT("[VoxelForgeProfiler] could not build unprofiled reference world: %s"),
*ReferenceError);
}
VoxelDensityProfile::Reset();
VoxelDensityProfile::SetMode(
ExploreProfileMode(Arguments),
VoxelDensityProfile::DefaultSampleInterval);
}
if (Arguments.bRender)
{
const double Start = FPlatformTime::Seconds();
bool bRenderOk = false;
if (!Budget.bTruncated && CameraSeedWalk != nullptr)
{
bRenderOk = RunRender(
Arguments, World, *CameraSeedWalk, Output.Render, Budget);
}
if (!bRenderOk && !Budget.bTruncated)
{
bRequestedModeFailed = true;
}
if (Budget.bTruncated && Output.Render.Status.IsEmpty())
{
Output.Render.Status = TEXT("truncated");
Output.Render.bTruncated = true;
Output.Render.RefusalReason = TEXT("The wall-clock budget stopped before rendering.");
}
if (bRenderOk && !Budget.bTruncated)
{
Budget.CompleteMode(TEXT("render"));
}
UE_LOG(LogTemp, Display, TEXT("[VoxelForgeExplore] render %.3fs (%s)"),
FPlatformTime::Seconds() - Start, *Output.Render.Status);
}
if (Arguments.bFailureFocusRender && Arguments.bRender && Arguments.bWalk
&& !Budget.bTruncated)
{
const double Start = FPlatformTime::Seconds();
if (!RunFailureBoundaryRender(Arguments, World, Output.Walk, Output.Render, Budget))
{
if (!Budget.bTruncated)
{
bRequestedModeFailed = true;
}
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeExplore] failure-focus render could not produce a boundary image."));
}
else
{
UE_LOG(LogTemp, Display, TEXT("[VoxelForgeExplore] failure-focus render %.3fs (ok)"),
FPlatformTime::Seconds() - Start);
}
}
if (Arguments.bExport)
{
const double Start = FPlatformTime::Seconds();
const bool bExportOk = !Budget.bTruncated
&& RunExport(Arguments, World, CameraSeedWalk, Output.Export, Budget);
if (!bExportOk && !Budget.bTruncated)
{
bRequestedModeFailed = true;
}
if (Budget.bTruncated && Output.Export.Status.IsEmpty())
{
Output.Export.Status = TEXT("truncated");
Output.Export.bTruncated = true;
Output.Export.RefusalReason = TEXT("The wall-clock budget stopped before export.");
}
if (bExportOk && !Budget.bTruncated)
{
Budget.CompleteMode(TEXT("export"));
}
UE_LOG(LogTemp, Display, TEXT("[VoxelForgeExplore] export %.3fs (%s)"),
FPlatformTime::Seconds() - Start, *Output.Export.Status);
}
// Stop before the report is assembled, while World still owns the generation state. This
// guarantees that the sampler's summary describes only this case and that all worker stacks
// have been released before the commandlet tears the world down.
StackSamplerSession.Stop();
// A phase can finish just inside its own check and cross the deadline while its artifact is
// being closed. Mark that state before serialising the report so the JSON never claims a full
// run after the budget has actually expired.
Budget.ShouldStop(TEXT("report"));
if (Budget.bTruncated)
{
if (Arguments.bWalk && Output.Walk.Status.IsEmpty())
{
Output.Walk.Status = TEXT("truncated");
Output.Walk.RefusalReason = TEXT("The wall-clock budget stopped before walk completion.");
}
UE_LOG(LogTemp, Warning,
TEXT("[VoxelForgeExplore] truncated during %s after %.3fs; completed modes=%s"),
*Budget.TruncatedDuring,
Budget.ElapsedSeconds(),
*FString::Join(Budget.CompletedModes, TEXT(",")));
}
Output.BudgetSeconds = Budget.LimitSeconds;
Output.ElapsedSeconds = FPlatformTime::Seconds() - MainStartSeconds;
Output.bTruncated = Budget.bTruncated;
Output.TruncatedDuring = Budget.TruncatedDuring;
Output.CompletedModes = Budget.CompletedModes;
Output.Profile = VoxelDensityProfile::Snapshot();
Output.bHasProfile = true;
if (Arguments.IsDensityProfilingEnabled())
{
if (Output.ProfilerComparison.bAvailable)
{
Output.ProfilerComparison.ProfiledSeconds = World.ExploreMeshSeconds;
Output.ProfilerComparison.ProfiledGeometryHash = World.ExploreGeometryHash;
Output.ProfilerComparison.DiscrepancySeconds =
Output.ProfilerComparison.ProfiledSeconds
- Output.ProfilerComparison.UnprofiledSeconds;
Output.ProfilerComparison.DiscrepancyPercent =
Output.ProfilerComparison.UnprofiledSeconds > 0.0
? Output.ProfilerComparison.DiscrepancySeconds
* 100.0 / Output.ProfilerComparison.UnprofiledSeconds
: 0.0;
Output.ProfilerComparison.Ratio =
Output.ProfilerComparison.UnprofiledSeconds > 0.0
? Output.ProfilerComparison.ProfiledSeconds
/ Output.ProfilerComparison.UnprofiledSeconds
: 0.0;
Output.ProfilerComparison.bGeometryEqual =
!Output.ProfilerComparison.ProfiledGeometryHash.IsEmpty()
&& Output.ProfilerComparison.ProfiledGeometryHash
== Output.ProfilerComparison.UnprofiledGeometryHash;
const bool bWithinTarget = FMath::Abs(Output.ProfilerComparison.DiscrepancyPercent) <= 20.0;
const bool bSelfCheckPassed = bWithinTarget
&& Output.ProfilerComparison.bGeometryEqual;
Output.ProfilerComparison.SelfCheck = bSelfCheckPassed ? TEXT("passed") : TEXT("FAILED");
if (bSelfCheckPassed)
{
UE_LOG(
LogTemp, Display,
TEXT("[VoxelForgeProfiler] profiled_total=%.6fs unprofiled_total=%.6fs "
"discrepancy=%.2f%% ratio=%.3fx geometry_equal=%s self_check=%s"),
Output.ProfilerComparison.ProfiledSeconds,
Output.ProfilerComparison.UnprofiledSeconds,
Output.ProfilerComparison.DiscrepancyPercent,
Output.ProfilerComparison.Ratio,
Output.ProfilerComparison.bGeometryEqual ? TEXT("yes") : TEXT("no"),
*Output.ProfilerComparison.SelfCheck);
}
else
{
UE_LOG(
LogTemp, Error,
TEXT("[VoxelForgeProfiler] profiled_total=%.6fs unprofiled_total=%.6fs "
"discrepancy=%.2f%% ratio=%.3fx geometry_equal=%s self_check=%s"),
Output.ProfilerComparison.ProfiledSeconds,
Output.ProfilerComparison.UnprofiledSeconds,
Output.ProfilerComparison.DiscrepancyPercent,
Output.ProfilerComparison.Ratio,
Output.ProfilerComparison.bGeometryEqual ? TEXT("yes") : TEXT("no"),
*Output.ProfilerComparison.SelfCheck);
}
if (!bSelfCheckPassed)
{
bRequestedModeFailed = true;
}
}
else
{
Output.ProfilerComparison.Scope = TEXT("canonical_mesh_export");
Output.ProfilerComparison.SelfCheck = TEXT("not_available");
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeProfiler] self-check could not obtain an unprofiled reference."));
bRequestedModeFailed = true;
}
}
// Build the payload twice before adding the assertion itself. This is deliberately a string
// comparison over a fixed-order writer, not a TMap/pretty-printer whose key order could drift.
const FString Payload = BuildExploreJson(Arguments, World, Output, false, 0);
const FString PayloadRepeat = BuildExploreJson(Arguments, World, Output, false, 0);
if (Payload.IsEmpty() || PayloadRepeat.IsEmpty())
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeExplore] JSON writer failed before report creation."));
return 1;
}
// Hash the exact UTF-8 representation that is written to disk. This keeps the reported
// digest independent of TCHAR width (and therefore meaningful when the same run is compared
// across editor platforms).
FTCHARToUTF8 PayloadUtf8(*Payload);
const uint32 PayloadCrc32 = FCrc::MemCrc32(PayloadUtf8.Get(), PayloadUtf8.Length());
const bool bPayloadRepeatEqual = Payload == PayloadRepeat;
FString Json = BuildExploreJson(
Arguments, World, Output, bPayloadRepeatEqual, PayloadCrc32);
const FString JsonRepeat = BuildExploreJson(
Arguments, World, Output, bPayloadRepeatEqual, PayloadCrc32);
if (Json.IsEmpty() || JsonRepeat.IsEmpty())
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeExplore] JSON writer failed during report creation."));
return 1;
}
const bool bFinalRepeatEqual = Json == JsonRepeat;
if (!bFinalRepeatEqual)
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeExplore] deterministic JSON assertion failed before write."));
bRequestedModeFailed = true;
Json = BuildExploreJson(Arguments, World, Output, false, PayloadCrc32);
}
const FString ReportPath = FPaths::Combine(Arguments.OutDirectory, TEXT("explore.json"));
if (!FFileHelper::SaveStringToFile(
Json,
*ReportPath,
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeExplore] Could not write '%s'."), *ReportPath);
return 1;
}
if (OutJson != nullptr)
{
*OutJson = Json;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeExplore] setup %.3fs, total %.3fs; report=%s; json_deterministic=%s"),
SetupSeconds,
FPlatformTime::Seconds() - MainStartSeconds,
*ReportPath,
(bPayloadRepeatEqual && bFinalRepeatEqual) ? TEXT("yes") : TEXT("no"));
if (Arguments.IsDensityProfilingEnabled())
{
const VoxelDensityProfile::FSnapshot Profile = VoxelDensityProfile::Snapshot();
const double CycleToMicroseconds = FPlatformTime::GetSecondsPerCycle64() * 1.0e6;
const uint64 DensityCalls = Profile.Calls[static_cast<int32>(VoxelDensityProfile::EBucket::GetDensityAt)];
const uint64 DensityCycles = Profile.WallCycles[static_cast<int32>(VoxelDensityProfile::EBucket::GetDensityAt)];
const double DensityTotalUs = static_cast<double>(DensityCycles) * CycleToMicroseconds;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeDensityProfile] total calls=%llu total_us=%.3f us_per_call=%.6f"),
static_cast<unsigned long long>(DensityCalls), DensityTotalUs,
DensityCalls > 0 ? DensityTotalUs / static_cast<double>(DensityCalls) : 0.0);
const uint64 CacheTotalStaticBytes = Profile.TunnelCacheStaticBytes
+ Profile.RoomGraphCacheStaticBytes;
const uint64 CacheTotalDynamicBytes = Profile.TunnelCacheDynamicBytes
+ Profile.RoomGraphCacheDynamicBytes;
const uint64 CacheTotalBytes = CacheTotalStaticBytes + CacheTotalDynamicBytes;
const uint64 CacheWorkers = FMath::Max(
Profile.TunnelCacheWorkers, Profile.RoomGraphCacheWorkers);
const uint64 CacheValidEntries = Profile.TunnelCacheValidEntries
+ Profile.RoomGraphCacheValidEntries;
const uint64 CacheEntryBytes = Profile.TunnelCacheEntryBytes
+ Profile.RoomGraphCacheEntryBytes;
const uint64 CacheCapacityEntries = Profile.TunnelCacheCapacityEntries
+ Profile.RoomGraphCacheCapacityEntries;
const uint64 CacheLargestEntryBytes = FMath::Max(
Profile.TunnelCacheLargestEntryBytes,
Profile.RoomGraphCacheLargestEntryBytes);
const uint64 CacheLargestWorkerBytes = FMath::Max(
Profile.TunnelCacheLargestWorkerBytes,
Profile.RoomGraphCacheLargestWorkerBytes);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCacheMemory] workers=%llu capacity_entries=%llu valid_entries=%llu "
"static_bytes=%llu dynamic_bytes=%llu total_bytes=%llu "
"avg_valid_entry_bytes=%.1f largest_entry_bytes=%llu "
"avg_worker_bytes=%.1f largest_worker_bytes=%llu "
"tunnel_avg_entry_bytes=%.1f tunnel_largest_entry_bytes=%llu "
"tunnel_largest_worker_valid=%llu "
"roomgraph_avg_entry_bytes=%.1f roomgraph_largest_entry_bytes=%llu "
"roomgraph_largest_worker_valid=%llu "
"tunnel_workers=%llu tunnel_capacity=%llu tunnel_valid=%llu "
"tunnel_static=%llu tunnel_dynamic=%llu roomgraph_workers=%llu "
"roomgraph_capacity=%llu roomgraph_valid=%llu roomgraph_static=%llu "
"roomgraph_dynamic=%llu"),
static_cast<unsigned long long>(CacheWorkers),
static_cast<unsigned long long>(CacheCapacityEntries),
static_cast<unsigned long long>(CacheValidEntries),
static_cast<unsigned long long>(CacheTotalStaticBytes),
static_cast<unsigned long long>(CacheTotalDynamicBytes),
static_cast<unsigned long long>(CacheTotalBytes),
CacheValidEntries > 0
? static_cast<double>(CacheEntryBytes) / static_cast<double>(CacheValidEntries)
: 0.0,
static_cast<unsigned long long>(CacheLargestEntryBytes),
CacheWorkers > 0
? static_cast<double>(CacheTotalBytes) / static_cast<double>(CacheWorkers)
: 0.0,
static_cast<unsigned long long>(CacheLargestWorkerBytes),
Profile.TunnelCacheValidEntries > 0
? static_cast<double>(Profile.TunnelCacheEntryBytes)
/ static_cast<double>(Profile.TunnelCacheValidEntries)
: 0.0,
static_cast<unsigned long long>(Profile.TunnelCacheLargestEntryBytes),
static_cast<unsigned long long>(Profile.TunnelCacheLargestWorkerValidEntries),
Profile.RoomGraphCacheValidEntries > 0
? static_cast<double>(Profile.RoomGraphCacheEntryBytes)
/ static_cast<double>(Profile.RoomGraphCacheValidEntries)
: 0.0,
static_cast<unsigned long long>(Profile.RoomGraphCacheLargestEntryBytes),
static_cast<unsigned long long>(Profile.RoomGraphCacheLargestWorkerValidEntries),
static_cast<unsigned long long>(Profile.TunnelCacheWorkers),
static_cast<unsigned long long>(Profile.TunnelCacheCapacityEntries),
static_cast<unsigned long long>(Profile.TunnelCacheValidEntries),
static_cast<unsigned long long>(Profile.TunnelCacheStaticBytes),
static_cast<unsigned long long>(Profile.TunnelCacheDynamicBytes),
static_cast<unsigned long long>(Profile.RoomGraphCacheWorkers),
static_cast<unsigned long long>(Profile.RoomGraphCacheCapacityEntries),
static_cast<unsigned long long>(Profile.RoomGraphCacheValidEntries),
static_cast<unsigned long long>(Profile.RoomGraphCacheStaticBytes),
static_cast<unsigned long long>(Profile.RoomGraphCacheDynamicBytes));
const VoxelDensityProfile::EBucket DensityPhases[] = {
VoxelDensityProfile::EBucket::DensityPrologue,
VoxelDensityProfile::EBucket::DensityCore,
VoxelDensityProfile::EBucket::DensityDisturbances,
VoxelDensityProfile::EBucket::DensityStructuralPosts,
VoxelDensityProfile::EBucket::DensityBoundarySeal,
VoxelDensityProfile::EBucket::DensityDiffLayer,
VoxelDensityProfile::EBucket::DensityTail,
};
uint64 DensityAttributionCycles = 0;
uint64 DensityAttributionCalls = 0;
for (const VoxelDensityProfile::EBucket Phase : DensityPhases)
{
const int32 PhaseIndex = static_cast<int32>(Phase);
DensityAttributionCycles += Profile.WallCycles[PhaseIndex];
DensityAttributionCalls += Profile.Calls[PhaseIndex];
}
const double DensityAttributionUs =
static_cast<double>(DensityAttributionCycles) * CycleToMicroseconds;
const double DensityUnattributedUs = DensityTotalUs - DensityAttributionUs;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeDensityProfile] attribution_sum_us=%.3f unattributed_us=%.3f "
"coverage=%.2f%% phase_calls=%llu"),
DensityAttributionUs, DensityUnattributedUs,
DensityTotalUs > 0.0 ? DensityAttributionUs * 100.0 / DensityTotalUs : 0.0,
static_cast<unsigned long long>(DensityAttributionCalls));
for (int32 Index = 0; Index < VoxelDensityProfile::CounterCount; ++Index)
{
const VoxelDensityProfile::ECounter Counter =
static_cast<VoxelDensityProfile::ECounter>(Index);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeDensityProfile] counter=%s value=%llu per_density_call=%.3f"),
VoxelDensityProfile::CounterName(Counter),
static_cast<unsigned long long>(Profile.Counters[Index]),
DensityCalls > 0
? static_cast<double>(Profile.Counters[Index])
/ static_cast<double>(DensityCalls)
: 0.0);
}
for (int32 Index = 0; Index < VoxelDensityProfile::BucketCount; ++Index)
{
const uint64 Calls = Profile.Calls[Index];
if (Calls == 0 || Index == static_cast<int32>(VoxelDensityProfile::EBucket::GetDensityAt))
{
continue;
}
const double TotalUs = static_cast<double>(Profile.WallCycles[Index]) * CycleToMicroseconds;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeDensityProfile] op=%s calls=%llu total_us=%.3f us_per_call=%.6f"),
VoxelDensityProfile::BucketName(static_cast<VoxelDensityProfile::EBucket>(Index)),
static_cast<unsigned long long>(Calls), TotalUs,
TotalUs / static_cast<double>(Calls));
}
const VoxelDensityProfile::EBucket MesherBuckets[] = {
VoxelDensityProfile::EBucket::MesherDensityGrid,
VoxelDensityProfile::EBucket::MesherCellClassification,
VoxelDensityProfile::EBucket::MesherGradientNormals,
VoxelDensityProfile::EBucket::MesherVertexInterpolation,
VoxelDensityProfile::EBucket::MesherStreamBuilding,
VoxelDensityProfile::EBucket::MesherOther,
};
uint64 MesherAttributionCycles = 0;
uint64 MesherAttributionCalls = 0;
for (const VoxelDensityProfile::EBucket Bucket : MesherBuckets)
{
const int32 BucketIndex = static_cast<int32>(Bucket);
MesherAttributionCycles += Profile.WallCycles[BucketIndex];
MesherAttributionCalls += Profile.Calls[BucketIndex];
}
const int32 MesherTotalIndex = static_cast<int32>(
VoxelDensityProfile::EBucket::MesherGenerateMesh);
const double MesherTotalUs = static_cast<double>(
Profile.WallCycles[MesherTotalIndex]) * CycleToMicroseconds;
const double MesherAttributionUs = static_cast<double>(
MesherAttributionCycles) * CycleToMicroseconds;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeMesherProfile] total_calls=%llu total_us=%.3f "
"attribution_sum_us=%.3f unattributed_us=%.3f coverage=%.2f%% "
"phase_calls=%llu"),
static_cast<unsigned long long>(Profile.Calls[MesherTotalIndex]),
MesherTotalUs, MesherAttributionUs, MesherTotalUs - MesherAttributionUs,
MesherTotalUs > 0.0 ? MesherAttributionUs * 100.0 / MesherTotalUs : 0.0,
static_cast<unsigned long long>(MesherAttributionCalls));
VoxelDensityProfile::SetMode(VoxelDensityProfile::EMode::Disabled);
}
VoxelDensityOps::ReportSharedRoomGraphCacheFootprint();
VoxelDensityOps::TrimSharedRoomGraphCache();
// Per-case isolation boundary. The UObject world dies with this function; worker-local
// caches are keyed by the fresh generator/manager lifetime and the diagnostics are reset before
// the next case. No case may inherit profiler counters or a live profiler mode.
VoxelDensityProfile::Reset();
VoxelDensityProfile::SetMode(VoxelDensityProfile::EMode::Disabled);
return bRequestedModeFailed ? 2 : 0;
}
namespace
{
FString SanitizeBatchCaseId(const FString& InId, int32 Index)
{
FString Result;
for (const TCHAR Character : InId)
{
const bool bAsciiAlphaNumeric =
(Character >= TEXT('a') && Character <= TEXT('z'))
|| (Character >= TEXT('A') && Character <= TEXT('Z'))
|| (Character >= TEXT('0') && Character <= TEXT('9'));
if (bAsciiAlphaNumeric || Character == TEXT('_') || Character == TEXT('-'))
{
Result.AppendChar(Character);
}
}
if (Result.IsEmpty())
{
Result = FString::Printf(TEXT("case_%03d"), Index);
}
return Result;
}
bool JsonNumber(const TSharedPtr<FJsonObject>& Object, const TCHAR* Key, double& OutValue)
{
if (!Object.IsValid() || !Object->HasField(Key))
{
return false;
}
OutValue = Object->GetNumberField(Key);
return FMath::IsFinite(OutValue);
}
bool JsonString(const TSharedPtr<FJsonObject>& Object, const TCHAR* Key, FString& OutValue)
{
if (!Object.IsValid() || !Object->HasField(Key))
{
return false;
}
OutValue = Object->GetStringField(Key);
return true;
}
bool JsonBool(const TSharedPtr<FJsonObject>& Object, const TCHAR* Key, bool& OutValue)
{
if (!Object.IsValid() || !Object->HasField(Key))
{
return false;
}
OutValue = Object->GetBoolField(Key);
return true;
}
bool BuildBatchCaseParams(
const TSharedPtr<FJsonObject>& Case,
const FString& CaseOutDirectory,
FString& OutParams,
FString& OutError)
{
if (!Case.IsValid())
{
OutError = TEXT("Each batch case must be a JSON object.");
return false;
}
double Number = 0.0;
int32 Seed = 0;
if (JsonNumber(Case, TEXT("seed"), Number))
{
Seed = FMath::RoundToInt(Number);
}
FString Archetype = TEXT("Maze");
JsonString(Case, TEXT("archetype"), Archetype);
int32 Slot = 4;
if (JsonNumber(Case, TEXT("slot"), Number))
{
Slot = FMath::RoundToInt(Number);
}
int32 ExportSize = DefaultExportSize;
if (JsonNumber(Case, TEXT("export_size"), Number))
{
ExportSize = FMath::RoundToInt(Number);
}
int32 ExportStep = DefaultExportStep;
if (JsonNumber(Case, TEXT("export_step"), Number)
|| JsonNumber(Case, TEXT("step"), Number))
{
ExportStep = FMath::RoundToInt(Number);
}
if (JsonNumber(Case, TEXT("lod"), Number))
{
const int32 Lod = FMath::RoundToInt(Number);
if (Lod < 0 || Lod > 3)
{
OutError = TEXT("case.lod must be in [0,3].");
return false;
}
ExportStep = 1 << Lod;
}
bool bReuseDensityGrid = true;
JsonBool(Case, TEXT("density_grid_reuse"), bReuseDensityGrid);
bool bBlockEarlyOut = false;
JsonBool(Case, TEXT("block_early_out"), bBlockEarlyOut);
int32 MeshMinBatchSize = 1;
if (JsonNumber(Case, TEXT("mesh_min_batch_size"), Number))
{
MeshMinBatchSize = FMath::Clamp(FMath::RoundToInt(Number), 1, 64);
}
FString Modes = TEXT("export");
JsonString(Case, TEXT("modes"), Modes);
bool bProfileDensity = false;
JsonBool(Case, TEXT("profile_density"), bProfileDensity);
bool bProfileDensityFull = false;
JsonBool(Case, TEXT("profile_density_full"), bProfileDensityFull);
bProfileDensity |= bProfileDensityFull;
bool bPerfAttribution = false;
JsonBool(Case, TEXT("perf_attribution"), bPerfAttribution);
bool bSampleStacks = false;
JsonBool(Case, TEXT("sample_stacks"), bSampleStacks);
int32 SampleStacksIntervalUs = static_cast<int32>(FVoxelStackSampler::DefaultIntervalUs);
if (JsonNumber(Case, TEXT("sample_stacks_interval_us"), Number))
{
SampleStacksIntervalUs = FMath::RoundToInt(Number);
bSampleStacks = true;
}
bool bProfileLod = false;
JsonBool(Case, TEXT("profile_lod"), bProfileLod);
bool bOpBounds = false;
JsonBool(Case, TEXT("op_bounds"), bOpBounds);
bool bFailureFocus = false;
JsonBool(Case, TEXT("failure_focus_render"), bFailureFocus);
bool bTunnelFloorEnabled = true;
const bool bHasTunnelFloorEnabled = JsonBool(
Case, TEXT("tunnel_floor_enabled"), bTunnelFloorEnabled);
bool bTunnelFloorTerracing = true;
const bool bHasTunnelFloorTerracing = JsonBool(
Case, TEXT("tunnel_floor_terracing"), bTunnelFloorTerracing);
OutParams = FString::Printf(
TEXT("-seed=%d -archetype=%s -slot=%d -modes=%s "
"-exportsize=%d -exportstep=%d -densitygridreuse=%d "
"-blockearlyout=%d -meshminbatch=%d "
"-failurefocus=%d -out=\"%s\""),
Seed, *Archetype, Slot, *Modes,
ExportSize, ExportStep, bReuseDensityGrid ? 1 : 0,
bBlockEarlyOut ? 1 : 0, MeshMinBatchSize,
bFailureFocus ? 1 : 0, *CaseOutDirectory);
if (bHasTunnelFloorEnabled)
{
OutParams += FString::Printf(
TEXT(" -tunnelfloor=%d"), bTunnelFloorEnabled ? 1 : 0);
}
if (bHasTunnelFloorTerracing)
{
OutParams += FString::Printf(
TEXT(" -tunnelfloorterracing=%d"), bTunnelFloorTerracing ? 1 : 0);
}
if (bProfileDensity)
{
OutParams += bProfileDensityFull
? TEXT(" -profiledensityfull")
: TEXT(" -profiledensity");
}
if (bPerfAttribution)
{
OutParams += TEXT(" -perfattribution");
}
if (bSampleStacks)
{
OutParams += FString::Printf(
TEXT(" -samplestacks=%d"), SampleStacksIntervalUs);
}
if (bProfileLod)
{
OutParams += TEXT(" -profilelod");
}
if (bOpBounds)
{
OutParams += TEXT(" -opbounds");
}
const TPair<const TCHAR*, const TCHAR*> IntegerFields[] = {
{ TEXT("render_width"), TEXT("renderwidth") },
{ TEXT("render_height"), TEXT("renderheight") },
{ TEXT("max_walk_cells"), TEXT("maxwalkcells") },
};
for (const auto& Field : IntegerFields)
{
if (JsonNumber(Case, Field.Key, Number))
{
OutParams += FString::Printf(TEXT(" -%s=%d"),
Field.Value, FMath::RoundToInt(Number));
}
}
const TPair<const TCHAR*, const TCHAR*> FloatFields[] = {
{ TEXT("render_step"), TEXT("renderstep") },
{ TEXT("render_max_distance"), TEXT("rendermaxdistance") },
{ TEXT("surface_roughness"), TEXT("surfaceroughness") },
{ TEXT("roughness_frequency"), TEXT("roughnessfrequency") },
{ TEXT("room_mouth_rise_strength"), TEXT("roommouthrise") },
{ TEXT("room_mouth_rise_blend_voxels"), TEXT("roommouthriseblend") },
{ TEXT("tunnel_floor_step_height"), TEXT("tunnelfloorstep") },
{ TEXT("tunnel_floor_max_ledge_height"), TEXT("tunnelfloormaxledge") },
{ TEXT("tunnel_floor_gentle_slope"), TEXT("tunnelfloorgentle") },
};
for (const auto& Field : FloatFields)
{
if (JsonNumber(Case, Field.Key, Number))
{
OutParams += FString::Printf(TEXT(" -%s=%.9g"), Field.Value, Number);
}
}
FString RoughnessType;
if (JsonString(Case, TEXT("roughness_noise_type"), RoughnessType))
{
OutParams += FString::Printf(TEXT(" -roughnesstype=%s"), *RoughnessType);
}
const TPair<const TCHAR*, const TCHAR*> IntegerFloorFields[] = {
{ TEXT("tunnel_floor_ledge_count"), TEXT("tunnelfloorledges") },
{ TEXT("tunnel_floor_max_ledges"), TEXT("tunnelfloormaxledges") },
};
for (const auto& Field : IntegerFloorFields)
{
if (JsonNumber(Case, Field.Key, Number))
{
OutParams += FString::Printf(TEXT(" -%s=%d"),
Field.Value, FMath::RoundToInt(Number));
}
}
return true;
}
int32 RunBatch(const FString& Params)
{
FString BatchPath;
FString OutText;
if (!FParse::Value(*Params, TEXT("batch="), BatchPath)
|| !FParse::Value(*Params, TEXT("out="), OutText))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeExplore] batch requires -batch=<json> and an absolute -out=<Saved path>."));
return 1;
}
if (FPaths::IsRelative(OutText))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeExplore] batch -out must be absolute and under '%s'."),
*VoxelForgeSavedRoot());
return 1;
}
const FString BatchOutDirectory = FPaths::ConvertRelativePathToFull(OutText);
if (!IsUnderVoxelForgeSaved(BatchOutDirectory))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeExplore] batch -out must be under '%s' (got '%s')."),
*VoxelForgeSavedRoot(), *BatchOutDirectory);
return 1;
}
if (!IFileManager::Get().MakeDirectory(*BatchOutDirectory, true))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeExplore] could not create batch output '%s'."),
*BatchOutDirectory);
return 1;
}
BatchPath = FPaths::ConvertRelativePathToFull(BatchPath);
FString BatchText;
if (!FFileHelper::LoadFileToString(BatchText, *BatchPath))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeExplore] could not read batch file '%s'."), *BatchPath);
return 1;
}
TSharedPtr<FJsonObject> Root;
const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(BatchText);
if (!FJsonSerializer::Deserialize(Reader, Root) || !Root.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeExplore] batch file is not valid JSON."));
return 1;
}
const TArray<TSharedPtr<FJsonValue>>* Cases = nullptr;
if (!Root->TryGetArrayField(TEXT("cases"), Cases) || Cases == nullptr || Cases->Num() == 0)
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeExplore] batch JSON needs a non-empty 'cases' array."));
return 1;
}
if (Cases->Num() > 256)
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeExplore] batch is capped at 256 cases."));
return 1;
}
double BatchBudgetMinutes = 29.0;
double Number = 0.0;
if (JsonNumber(Root, TEXT("budget_minutes"), Number))
{
BatchBudgetMinutes = Number;
}
if (FParse::Value(*Params, TEXT("batchbudget="), Number))
{
BatchBudgetMinutes = Number;
}
BatchBudgetMinutes = FMath::Clamp(BatchBudgetMinutes, 0.01, 30.0);
const double BatchStartSeconds = FPlatformTime::Seconds();
bool bTruncated = false;
int32 CompletedCases = 0;
int32 FailedCases = 0;
TSet<FString> UsedCaseIds;
TArray<TSharedPtr<FJsonValue>> CaseReports;
CaseReports.Reserve(Cases->Num());
for (int32 Index = 0; Index < Cases->Num(); ++Index)
{
const double RemainingSeconds = BatchBudgetMinutes * 60.0
- (FPlatformTime::Seconds() - BatchStartSeconds);
if (RemainingSeconds <= 0.25)
{
bTruncated = true;
break;
}
const TSharedPtr<FJsonObject> Case = (*Cases)[Index]->AsObject();
FString CaseId;
JsonString(Case, TEXT("id"), CaseId);
CaseId = SanitizeBatchCaseId(CaseId, Index);
const FString BaseCaseId = CaseId;
int32 Suffix = 1;
while (UsedCaseIds.Contains(CaseId))
{
CaseId = FString::Printf(TEXT("%s_%d"), *BaseCaseId, Suffix++);
}
UsedCaseIds.Add(CaseId);
FString CaseOutDirectory = FPaths::Combine(BatchOutDirectory, TEXT("cases"), CaseId);
FString CaseParams;
FString CaseError;
if (!BuildBatchCaseParams(Case, CaseOutDirectory, CaseParams, CaseError))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeExplore] batch case %d (%s): %s"),
Index, *CaseId, *CaseError);
TSharedPtr<FJsonObject> CaseReport = MakeShared<FJsonObject>();
CaseReport->SetStringField(TEXT("case_id"), CaseId);
CaseReport->SetNumberField(TEXT("case_index"), Index);
CaseReport->SetNumberField(TEXT("case_exit_code"), 1);
CaseReport->SetStringField(TEXT("status"), TEXT("invalid"));
CaseReport->SetStringField(TEXT("error"), CaseError);
CaseReports.Add(MakeShared<FJsonValueObject>(CaseReport));
++CompletedCases;
++FailedCases;
continue;
}
double CaseBudgetMinutes = FMath::Min(25.0, RemainingSeconds / 60.0);
if (JsonNumber(Case, TEXT("budget_minutes"), Number))
{
CaseBudgetMinutes = FMath::Min(CaseBudgetMinutes, Number);
}
CaseBudgetMinutes = FMath::Clamp(CaseBudgetMinutes, 0.01, 30.0);
CaseParams += FString::Printf(TEXT(" -budget=%.9g"), CaseBudgetMinutes);
FString CaseJson;
const int32 CaseResult = RunExploreCase(CaseParams, &CaseJson);
TSharedPtr<FJsonObject> CaseReport;
const TSharedRef<TJsonReader<>> CaseReader = TJsonReaderFactory<>::Create(CaseJson);
if (!FJsonSerializer::Deserialize(CaseReader, CaseReport) || !CaseReport.IsValid())
{
CaseReport = MakeShared<FJsonObject>();
CaseReport->SetStringField(TEXT("error"), TEXT("case did not produce a JSON report"));
}
CaseReport->SetStringField(TEXT("case_id"), CaseId);
CaseReport->SetNumberField(TEXT("case_index"), Index);
CaseReport->SetNumberField(TEXT("case_exit_code"), CaseResult);
CaseReports.Add(MakeShared<FJsonValueObject>(CaseReport));
++CompletedCases;
if (CaseResult != 0)
{
++FailedCases;
}
// RunExploreCase owns a complete transient UObject world. Release that case's strong
// roots before starting the next one and collect now; otherwise a batch retains every
// completed mesh/settings/manager graph until the commandlet exits, making peak RSS grow
// with the number of cases even though no report needs those objects afterward.
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
}
TSharedPtr<FJsonObject> BatchReport = MakeShared<FJsonObject>();
BatchReport->SetNumberField(TEXT("schema_version"), 2);
BatchReport->SetStringField(TEXT("tool"), TEXT("VoxelForgeExplore"));
BatchReport->SetBoolField(TEXT("batch"), true);
BatchReport->SetStringField(TEXT("input_file"), BatchPath);
BatchReport->SetStringField(TEXT("output_directory"), BatchOutDirectory);
BatchReport->SetNumberField(TEXT("budget_minutes"), BatchBudgetMinutes);
BatchReport->SetNumberField(TEXT("elapsed_seconds"), FPlatformTime::Seconds() - BatchStartSeconds);
BatchReport->SetBoolField(TEXT("truncated"), bTruncated);
BatchReport->SetNumberField(TEXT("completed_cases"), CompletedCases);
BatchReport->SetNumberField(TEXT("failed_cases"), FailedCases);
BatchReport->SetArrayField(TEXT("cases"), MoveTemp(CaseReports));
FString BatchJson;
const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&BatchJson);
if (!FJsonSerializer::Serialize(BatchReport.ToSharedRef(), Writer)
|| !FFileHelper::SaveStringToFile(
BatchJson,
*FPaths::Combine(BatchOutDirectory, TEXT("batch.json")),
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeExplore] could not write batch.json."));
return 1;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeExplore] batch completed=%d failed=%d truncated=%s report=%s"),
CompletedCases, FailedCases, bTruncated ? TEXT("yes") : TEXT("no"),
*FPaths::Combine(BatchOutDirectory, TEXT("batch.json")));
return FailedCases > 0 || bTruncated ? 2 : 0;
}
}
int32 UVoxelForgeExploreCommandlet::Main(const FString& Params)
{
if (FParse::Param(*Params, TEXT("coreassetupgradeaudit")))
{
// Load the untouched BuildHost copies and emit their in-memory conversions; never save a package.
// Charge les copies BuildHost intactes et affiche les conversions en memoire, sans sauver de package.
UVoxelSettings* SettingsAsset = LoadObject<UVoxelSettings>(nullptr,
TEXT("/Game/VoxelForge/DA_Settings.DA_Settings"));
if (SettingsAsset == nullptr)
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeAssetMigrationAudit] missing DA_Settings copy"));
return 2;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeAssetMigrationAudit] asset=%s old_gap_chunks=%d new_gap_m=%.6f layout_chunk_m=%.6f"),
*SettingsAsset->GetPathName(), SettingsAsset->InterStrateGapChunks,
SettingsAsset->InterStrateGapMeters, SettingsAsset->StrateLayoutChunkSizeMeters);
const TCHAR* StratePaths[] = {
TEXT("/Game/VoxelForge/DA_Strate1.DA_Strate1"),
TEXT("/Game/VoxelForge/DA_Strate2.DA_Strate2"),
TEXT("/Game/VoxelForge/DA_Strate3.DA_Strate3"),
TEXT("/Game/VoxelForge/DA_Strate4.DA_Strate4")
};
for (const TCHAR* Path : StratePaths)
{
UVoxelStrateDefinition* Definition = LoadObject<UVoxelStrateDefinition>(nullptr, Path);
if (Definition == nullptr)
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeAssetMigrationAudit] missing strate copy %s"), Path);
return 2;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeAssetMigrationAudit] asset=%s old_height_chunks=%d new_height_m=%.6f old_transition_chunks=%d new_transition_m=%.6f"),
*Definition->GetPathName(), Definition->StrateHeightInChunks,
Definition->StrateHeightMeters, Definition->TransitionBlendChunks,
Definition->TransitionWidthMeters);
}
return 0;
}
if (FParse::Param(*Params, TEXT("step2f")))
{
return RunStep2f(Params);
}
if (FParse::Param(*Params, TEXT("step3scale")))
{
return RunStep3ScaleBenchmark(Params);
}
if (FParse::Param(*Params, TEXT("step2e")))
{
return RunStep2e(Params);
}
if (FParse::Param(*Params, TEXT("step2d")))
{
return RunStep2d(Params);
}
const bool bRegionEvalSwitch = FParse::Param(*Params, TEXT("regioneval"));
int32 RegionEvalFlag = 0;
const bool bRegionEvalValue = FParse::Value(*Params, TEXT("regioneval="), RegionEvalFlag);
if (bRegionEvalSwitch || (bRegionEvalValue && RegionEvalFlag != 0))
{
return RunRegionEval(Params);
}
FString SymbolizeCsvPath;
if (FParse::Value(*Params, TEXT("symbolizestacks="), SymbolizeCsvPath))
{
FString SymbolizedOutputPath;
FString SymbolPath;
FParse::Value(*Params, TEXT("symbolizedout="), SymbolizedOutputPath);
FParse::Value(*Params, TEXT("symbolpath="), SymbolPath);
FString SummaryPath;
FString Error;
if (!VoxelForgeOfflineStackSymbolizer::Run(
SymbolizeCsvPath, SymbolizedOutputPath, SymbolPath, SummaryPath, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeExplore] offline stack symbolization failed: %s"),
*Error);
return 2;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeExplore] offline stack symbolization completed summary=%s"),
*SummaryPath);
return 0;
}
FString BatchPath;
if (FParse::Value(*Params, TEXT("batch="), BatchPath))
{
return RunBatch(Params);
}
return RunExploreCase(Params, nullptr);
}