#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 #include #include #include #include #include #include #include #include #include #include #include #include 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::infinity(); double MaxGroundZ = -std::numeric_limits::infinity(); const std::size_t GroundIndexCount = std::min( Mesh.indices.size(), static_cast(Mesh.groups.groundTriangleCount) * 3u); const std::size_t ProbeCount = std::min(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(Mesh.key.lod), 0, 20); const double ReliefThresholdCm = 4.0 * 25.0 * static_cast(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(nullptr, TEXT("/Game/FirstPerson/Blueprints/BP_FirstPersonCharacter.BP_FirstPersonCharacter_C")); const UClass* ControllerClass = LoadClass(nullptr, TEXT("/Game/FirstPerson/Blueprints/BP_FirstPersonPlayerController.BP_FirstPersonPlayerController_C")); const ACharacter* CharacterDefault = CharacterClass ? Cast(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(nullptr, TEXT("/Game/FirstPerson/Blueprints/BP_FirstPersonCharacter.BP_FirstPersonCharacter")); const UBlueprint* ControllerBlueprint = LoadObject(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(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(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( 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::infinity(); double MaxX = -std::numeric_limits::infinity(); double MinY = std::numeric_limits::infinity(); double MaxY = -std::numeric_limits::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(Mesh.key.lod); const VoxelForgeCore::WorldConfiguration& Configuration = World.configuration(); const double CoreVoxelSizeCm = static_cast(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(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(FMath::FloorLog2( static_cast(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(TriangleIndex)); return false; } if (Edge > OutAudit.MaxTriangleEdgeCm) { OutAudit.MaxTriangleEdgeCm = Edge; OutAudit.MaxEdgeTriangleIndex = static_cast(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(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(Mesh.key.coordinate.x), static_cast(Mesh.key.coordinate.y), static_cast(Mesh.key.coordinate.z)); OutAudit.ExpectedTileSpanCm = static_cast(ExtentVoxels) * CoreVoxelSizeCm; OutAudit.CellWorldCm = CellWorldCm; OutAudit.SpanXcm = MaxX - MinX; OutAudit.SpanYcm = MaxY - MinY; return true; } } namespace { using FExploreJsonWriter = TJsonWriter>; 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 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 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 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 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 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( FVoxelStackSampler::DefaultIntervalUs); if (FParse::Value(*Params, TEXT("samplestacks="), SampleStacksIntervalUs)) { OutArguments.bSampleStacks = true; OutArguments.SampleStacksIntervalUs = static_cast(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 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; } // The historical explorer contract needs two adjacent mouths around an interior slot. A // parity export can instead target the game's authored single-strate layout, which has no // such mouths and is valid for export-only mode. Build() still validates the slot against // the loaded settings layout. const int32 MinimumSlot = OutArguments.bGameRuntimeConfig ? 0 : 1; const int32 MaximumSlot = OutArguments.bGameRuntimeConfig ? 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 an interior slot in [1,%d] so arrival and departure mouths exist."), MaxSyntheticStrates - 2); } return false; } if (OutArguments.RenderWidth < 16 || OutArguments.RenderHeight < 16 || static_cast(OutArguments.RenderWidth) * static_cast(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.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 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; } }; struct FExploreWorld { TStrongObjectPtr Settings; TStrongObjectPtr Manager; TStrongObjectPtr DiffLayer; TStrongObjectPtr Generator; TStrongObjectPtr Mesher; TArray> 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 ExploreTileTriangleCounts; TArray 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 ExploreTriangles; TArray ExploreTriangleOrder; TArray 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( 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( nullptr, *Arguments.SettingsReference); if (AuthoredSettings == nullptr) { OutError = FString::Printf( TEXT("Could not load staged settings asset '%s'."), *Arguments.SettingsReference); return false; } Settings = TStrongObjectPtr( DuplicateObject(AuthoredSettings, GetTransientPackage())); if (!Settings.IsValid()) { OutError = TEXT("Could not duplicate the staged settings asset into the transient package."); return false; } } else { Settings = TStrongObjectPtr( NewObject(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 = 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 (bUseAuthoredStrate) { // The owner’s snapshot states that every slot uses DA_Strate3. Clone it per slot so // commandlet overrides never mutate a loaded package or leak between slots. Settings->StratePool.Reset(); Settings->FixedStrates.Reset(); } Definitions.Reserve(NumStrates); for (int32 Index = 0; Index < NumStrates; ++Index) { UVoxelStrateDefinition* Definition = bUseAuthoredStrate ? DuplicateObject( AuthoredDefinition, GetTransientPackage()) : NewObject( 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 SoftDefinition(Definition); Settings->FixedStrates.Add(Index, SoftDefinition); if (!bUseAuthoredStrate) { Settings->StratePool.Add(SoftDefinition); } Definitions.Add(TStrongObjectPtr(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( NewObject(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( NewObject(GetTransientPackage(), NAME_None, RF_Transient)); Generator = TStrongObjectPtr( NewObject(GetTransientPackage(), NAME_None, RF_Transient)); Generator->InitializeSettings(Settings.Get()); Generator->SetStrateManager(Manager.Get()); Generator->SetDiffLayer(DiffLayer.Get()); Mesher = TStrongObjectPtr( NewObject(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 Densities; TArray 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& 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(CenterZ)); Carve.Radius = 6.0f; Carve.Strength = -12.0f; Carve.Shape = EVoxelBrushShape::Sphere; const TArray CarvedChunks = World.DiffLayer->ApplyModification(Carve); FVoxelModification Fill; Fill.Center = FVector(-12.0f, 6.0f, static_cast(CenterZ - 10)); Fill.Radius = 4.0f; Fill.Strength = 12.0f; Fill.Shape = EVoxelBrushShape::Sphere; const TArray 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(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& 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 BuildRegionEvalSampleIndices( const FExploreWorld& World, FIntVector Origin, int32 Dimension, int32 RequestedRandomSamples, int32 Seed, int32& OutTargetedSamples) { const int64 Total = static_cast(Dimension) * Dimension * Dimension; const int32 RandomCount = FMath::Clamp(RequestedRandomSamples, 1, static_cast(Total)); TArray 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(Seed) * 48271ll) % Total; for (int32 Sample = 0; Sample < RandomCount; ++Sample) { Indices.Add((static_cast(Sample) * Multiplier + Offset) % Total); } TSet 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(CenterZ)), FVector(-12.0f, 6.0f, static_cast(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 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* 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 RegionEvalTimingJson(const FRegionEvalPass& Pass, bool bPrepared) { TSharedRef Json = MakeShared(); 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(Pass.Timing.SampleCount)); Json->SetNumberField(TEXT("evaluated_points"), static_cast(Pass.Timing.EvaluatedSamples)); Json->SetNumberField(TEXT("region_count"), static_cast(Pass.Timing.RegionCount)); Json->SetNumberField(TEXT("fallback_points"), static_cast(Pass.Timing.FallbackSamples)); Json->SetNumberField(TEXT("fallback_share"), Pass.Timing.FallbackShare); Json->SetNumberField(TEXT("general_path_entries"), static_cast(Pass.Timing.GeneralPathEntries)); Json->SetNumberField(TEXT("general_path_entries_per_evaluated_sample"), Pass.Timing.GeneralPathEntriesPerSample); Json->SetNumberField(TEXT("supported_block_setup_entries"), static_cast(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(Pass.Timing.SdfCacheBuilds)); Json->SetNumberField(TEXT("active_op_list_builds"), static_cast(Pass.Timing.ActiveOpListBuilds)); Json->SetNumberField(TEXT("broad_phase_index_builds"), static_cast(Pass.Timing.BroadPhaseIndexBuilds)); Json->SetNumberField(TEXT("sdf_spatial_index_builds"), static_cast(Pass.Timing.SdfSpatialIndexBuilds)); Json->SetNumberField(TEXT("sdf_3d_brick_index_builds"), static_cast(Pass.Timing.SdfBrickIndexBuilds)); Json->SetNumberField(TEXT("candidate_visits_total"), static_cast(Pass.Timing.TotalCandidateVisits)); Json->SetNumberField(TEXT("candidate_visits_per_sample"), Pass.Timing.CandidateVisitsPerSample); Json->SetNumberField(TEXT("candidate_evaluations_total"), static_cast(Pass.Timing.TotalCandidateEvaluations)); Json->SetNumberField(TEXT("candidate_evaluations_per_sample"), Pass.Timing.CandidateEvaluationsPerSample); Json->SetNumberField(TEXT("room_candidate_visits"), static_cast(Pass.Timing.RoomCandidateVisits)); Json->SetNumberField(TEXT("room_evaluations"), static_cast(Pass.Timing.RoomEvaluations)); Json->SetNumberField(TEXT("tunnel_candidate_visits"), static_cast(Pass.Timing.TunnelCandidateVisits)); Json->SetNumberField(TEXT("tunnel_evaluations"), static_cast(Pass.Timing.TunnelEvaluations)); Json->SetNumberField(TEXT("tunnel_core_candidate_visits"), static_cast(Pass.Timing.TunnelCoreCandidateVisits)); Json->SetNumberField(TEXT("tunnel_core_evaluations"), static_cast(Pass.Timing.TunnelCoreEvaluations)); Json->SetNumberField(TEXT("passage_candidate_visits"), static_cast(Pass.Timing.PassageCandidateVisits)); Json->SetNumberField(TEXT("passage_evaluations"), static_cast(Pass.Timing.PassageEvaluations)); TSharedRef Attribution = MakeShared(); 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(Pass.Timing.QuantizedHash))); Json->SetStringField(TEXT("raw_float_hash_fnv1a64"), FString::Printf(TEXT("%016llx"), static_cast(Pass.Timing.RawFloatHash))); Json->SetNumberField(TEXT("saturated_samples"), static_cast(Pass.Timing.SaturatedSamples)); Json->SetNumberField(TEXT("non_finite_samples"), static_cast(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(Pass.Visits.Room)); Json->SetNumberField(TEXT("probe_room_candidate_evaluations"), static_cast(Pass.Visits.RoomEvaluated)); Json->SetNumberField(TEXT("probe_tunnel_candidate_visits"), static_cast(Pass.Visits.Tunnel)); Json->SetNumberField(TEXT("probe_tunnel_candidate_evaluations"), static_cast(Pass.Visits.TunnelEvaluated)); Json->SetNumberField(TEXT("probe_tunnel_core_candidate_visits"), static_cast(Pass.Visits.TunnelCore)); Json->SetNumberField(TEXT("probe_tunnel_core_candidate_evaluations"), static_cast(Pass.Visits.TunnelCoreEvaluated)); Json->SetNumberField(TEXT("probe_passage_candidate_visits"), static_cast(Pass.Visits.Passage)); Json->SetNumberField(TEXT("probe_passage_candidate_evaluations"), static_cast(Pass.Visits.PassageEvaluated)); Json->SetNumberField(TEXT("probe_tunnel_cache_lookups"), static_cast(Pass.Visits.TunnelCacheLookups)); Json->SetNumberField(TEXT("probe_tunnel_cache_hits"), static_cast(Pass.Visits.TunnelCacheHits)); Json->SetNumberField(TEXT("probe_tunnel_cache_misses"), static_cast(Pass.Visits.TunnelCacheMisses)); Json->SetNumberField(TEXT("probe_sdf_cache_builds"), static_cast(Pass.Visits.SdfCacheBuilds)); Json->SetNumberField(TEXT("probe_candidate_counter_samples"), static_cast(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& A, const TArray& B, const TArray* 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(A[Index]) - static_cast(B[Index]))); if (!bFiniteA || !bFiniteB || bSaturatedA || bSaturatedB || QA != QB) { ++Result.Disagreements; if (Result.FirstIndex == INDEX_NONE) { Result.FirstIndex = LatticeIndices != nullptr ? (*LatticeIndices)[Index] : static_cast(Index); } } if ((QA < 0) != (QB < 0)) { ++Result.SignDisagreements; } } return Result; } TSharedRef RegionEvalComparisonJson(const FQuantizedComparison& Comparison) { TSharedRef Json = MakeShared(); Json->SetNumberField(TEXT("quantized_disagreements"), static_cast(Comparison.Disagreements)); Json->SetNumberField(TEXT("quantized_sign_disagreements"), static_cast(Comparison.SignDisagreements)); Json->SetNumberField(TEXT("max_abs_float_delta"), Comparison.MaxAbsoluteFloatDelta); Json->SetNumberField(TEXT("first_disagreement_linear_index"), static_cast(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(StackSummary.RegisteredThreadSamples), static_cast(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(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(VoxelForgeRegionEval::RingDimension) * VoxelForgeRegionEval::RingDimension * VoxelForgeRegionEval::RingDimension && WorkerHashPass.Timing.SaturatedSamples == 0 && WorkerHashPass.Timing.NonFiniteSamples == 0; TSharedRef ProbeReport = MakeShared(); 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> 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(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> SeedReports; TArray> OldTimingReports; TArray> PreparedTimingReports; TSharedPtr LiveFullRingDiagnostics; TSharedPtr PreparedFullRingDiagnostics; TArray OldFullDensities; TArray PreparedFullDensities; TArray 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(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(StackSummary.RegisteredThreadSamples), static_cast(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(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(RegionStackSummary.RegisteredThreadSamples), static_cast(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 SeedJson = MakeShared(); 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(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(LatticeIndex)], bSaturatedOld, bFiniteOld); const int16 QShrink = VoxelForgeRegionEval::QuantizeDensity( ShrunkPass.Densities[Sample], bSaturatedShrink, bFiniteShrink); ShrinkComparison.MaxAbsoluteFloatDelta = FMath::Max( ShrinkComparison.MaxAbsoluteFloatDelta, FMath::Abs(static_cast(OldFullDensities[static_cast(LatticeIndex)]) - static_cast(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& Value : PreparedTimingReports) { const TSharedPtr Json = Value->AsObject(); PreparedWallSum += Json->GetNumberField(TEXT("wall_seconds")); } const double PreparedMedianWall = PreparedWallSum / 2.0; double OldWallSum = 0.0; for (const TSharedPtr& Value : OldTimingReports) { OldWallSum += Value->AsObject()->GetNumberField(TEXT("wall_seconds")); } const double OldMedianWall = OldWallSum / 2.0; bool bSixWorkersObserved = true; for (const TSharedPtr& Value : OldTimingReports) { bSixWorkersObserved &= Value->AsObject()->GetNumberField(TEXT("observed_worker_threads")) >= VoxelForgeRegionEval::WorkerCount; } for (const TSharedPtr& Value : PreparedTimingReports) { bSixWorkersObserved &= Value->AsObject()->GetNumberField(TEXT("observed_worker_threads")) >= VoxelForgeRegionEval::WorkerCount; } const bool bSpeedPass = PreparedMedianWall <= 0.600 && bSixWorkersObserved; TSharedRef Report = MakeShared(); 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(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 Coverage = MakeShared(); 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 Shrink = MakeShared(); 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(ShrunkPass.Timing.SaturatedSamples)); Shrink->SetNumberField(TEXT("quantized_disagreements"), static_cast(ShrinkComparison.Disagreements)); Shrink->SetNumberField(TEXT("sign_disagreements"), static_cast(ShrinkComparison.SignDisagreements)); Shrink->SetNumberField(TEXT("max_abs_float_delta"), ShrinkComparison.MaxAbsoluteFloatDelta); Shrink->SetNumberField(TEXT("first_disagreement_linear_index"), static_cast(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> 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(FullComparison.Disagreements), bAllSeedComparisonsPass ? TEXT("pass") : TEXT("fail"), bFullDeterminismPass ? TEXT("pass") : TEXT("fail"), static_cast(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(Origin.X) / 8.0f), FMath::FloorToInt(static_cast(Origin.Y) / 8.0f), FMath::FloorToInt(static_cast(Origin.Z) / 8.0f)); const FIntVector LastBrick( FMath::FloorToInt(static_cast(Last.X) / 8.0f), FMath::FloorToInt(static_cast(Last.Y) / 8.0f), FMath::FloorToInt(static_cast(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(BrickOrigin.X + X), static_cast(BrickOrigin.Y + Y), static_cast(BrickOrigin.Z + Z), nullptr, 1.0f) : VoxelForgeRedefinedDensityField::Evaluate( Generator, static_cast(BrickOrigin.X + X), static_cast(BrickOrigin.Y + Y), static_cast(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(BrickOrigin.X - 1), static_cast(BrickOrigin.Y - 1), static_cast(BrickOrigin.Z - 1)); const FVector ProofMax( static_cast(BrickOrigin.X + Size.X), static_cast(BrickOrigin.Y + Size.Y), static_cast(BrickOrigin.Z + Size.Z)); const FVector ProofCentre = (ProofMin + ProofMax) * 0.5f; const FVector SamplePoint( static_cast(BrickOrigin.X + X), static_cast(BrickOrigin.Y + Y), static_cast(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(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(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(BrickOrigin.X + X), static_cast(BrickOrigin.Y + Y), static_cast(BrickOrigin.Z + Z), nullptr, 4.0f) : VoxelForgeRedefinedDensityField::Evaluate( Generator, static_cast(BrickOrigin.X + X), static_cast(BrickOrigin.Y + Y), static_cast(BrickOrigin.Z + Z), nullptr, 4.0f); if ((MaximumDetailDensity > 0.0f) != bDeliberatelyClaimedAir) { ++Result.DeliberateBoundDisagreements; } } } } return Result; } FStep2dSurfaceSpectrum ComputeStep2dSpectrum(const TArray& 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(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& OldDensity, const TArray& 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& 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& OldDensity, const TArray& NewDensity, int32 Dimension, TCHAR Plane, int32 FixedIndex, FString& OutError) { const int32 Width = Dimension * 2; const int32 Height = Dimension; TArray 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& Density, int32 Dimension) { FStep2eVarietyMetrics Result; const int64 Total = static_cast(Dimension) * Dimension * Dimension; int64 AirCount = 0; uint64 SurfaceEdgeCount = 0; uint64 HorizontalAdjacencies = 0; uint64 VerticalAdjacencies = 0; const int32 ColumnCount = Dimension * Dimension; TArray CurrentVerticalRun; CurrentVerticalRun.SetNumZeroed(ColumnCount); TArray 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(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(VOXEL_SIZE) / 100.0; Result.SurfaceAreaSquareMeters = static_cast(SurfaceEdgeCount) * VoxelMeters * VoxelMeters; const double VolumeCubicMeters = static_cast(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(VerticalAdjacencies) / AllAdjacencies : 0.0; uint64 ColumnsWithAir = 0; uint64 TotalLongestRunVoxels = 0; for (const int32 LongestRun : LongestVerticalRun) { if (LongestRun > 0) { ++ColumnsWithAir; TotalLongestRunVoxels += static_cast(LongestRun); Result.LongestVerticalAirRunVoxels = FMath::Max( Result.LongestVerticalAirRunVoxels, LongestRun); } } Result.VerticalAirColumnShare = ColumnCount > 0 ? static_cast(ColumnsWithAir) / ColumnCount : 0.0; Result.MeanLongestVerticalAirRunVoxels = ColumnsWithAir > 0 ? static_cast(TotalLongestRunVoxels) / ColumnsWithAir : 0.0; constexpr int32 VoxelsPerMeter = static_cast(100.0f / VOXEL_SIZE + 0.5f); constexpr int32 CoarseStep = VoxelsPerMeter; const int32 CoarseDimension = (Dimension - 1) / CoarseStep + 1; const int32 CoarseCount = CoarseDimension * CoarseDimension * CoarseDimension; TArray 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(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 ComponentSizes; for (int32 Index = 0; Index < CoarseCount; ++Index) { if (Parent[Index] != INDEX_NONE) { ++ComponentSizes.FindOrAdd(FindRoot(Index)); } } Result.ConnectedComponentsAtOneMeter = ComponentSizes.Num(); for (const TPair& 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 CrumbComponents; }; FStep2fFragmentMetrics ComputeStep2fFragmentMetrics( const TArray& 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 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 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(100.0f / VOXEL_SIZE + 0.5f); constexpr int32 VoxelsPerCubicMeter = VoxelsPerMeter * VoxelsPerMeter * VoxelsPerMeter; TMap 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 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(100.0f / VOXEL_SIZE + 0.5f); constexpr int32 HalfWindow = static_cast( 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 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(RingDimension - 1) * ZIndex / (HeightLayers - 1)); for (int32 YIndex = 0; YIndex < XYDimension; ++YIndex) for (int32 XIndex = 0; XIndex < XYDimension; ++XIndex) { const float WorldX = static_cast(RingCentre.X - HalfWindow + XIndex * SampleStep); const float WorldY = static_cast(RingCentre.Y - HalfWindow + YIndex * SampleStep); const uint8 bAir = VoxelForgeRedefinedDensityField::EvaluateParameterized( Generator, WorldX, WorldY, static_cast(Z)) > 0.0f ? 1 : 0; AirSamples[(ZIndex * XYDimension + YIndex) * XYDimension + XIndex] = bAir; AirCount += bAir; ++SampleCount; } } const double Mean = SampleCount > 0 ? static_cast(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(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(bAirA) - Mean) * (static_cast(bAirB) - Mean); ++Pairs; } } if (Pairs > 0) { BestCorrelation = FMath::Max( BestCorrelation, Covariance / (static_cast(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& 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(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(25.0f / VOXEL_SIZE + 0.5f); if (X + PhysicalLagVoxels < Dimension) { DifferenceEnergy += FMath::Square( static_cast(Detail[Index + PhysicalLagVoxels] - Detail[Index])); ++DifferenceCount; } if (Y + PhysicalLagVoxels < Dimension) { DifferenceEnergy += FMath::Square( static_cast(Detail[Index + PhysicalLagVoxels * Dimension] - Detail[Index])); ++DifferenceCount; } if (Z + PhysicalLagVoxels < Dimension) { DifferenceEnergy += FMath::Square( static_cast(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& Density, FIntVector RingOrigin, int32 Dimension) { constexpr int32 VoxelsPerMeter = static_cast(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> 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 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(static_cast(A)) << 32) | static_cast(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 Angles; Angles.Reserve(EdgeUses.Num()); int64 Over45 = 0; for (const TPair& 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(Dot))); Angles.Add(AngleDegrees); Over45 += AngleDegrees > 45.0f ? 1 : 0; } Result.ManifoldEdgeCount = Angles.Num(); if (!Angles.IsEmpty()) { Angles.Sort(); Result.ShareDihedralsOver45Degrees = static_cast(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 Step2eParametersJson( const VoxelForgeRedefinedDensityField::FGenerationParameters& P) { TSharedRef Json = MakeShared(); 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 Step2eChamberVolumeQuantilesJson( const VoxelForgeRedefinedDensityField::FGenerationParameters& P) { TSharedRef Json = MakeShared(); constexpr double Pi = 3.14159265358979323846; constexpr double VoxelMeters = static_cast(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(P.MinimumRoomRadiusFraction), static_cast(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 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 Result = MakeShared(); 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 Bounds = MakeShared(); 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 Step2dTimingJson(const VoxelForgeRegionEval::FTiming& Timing) { TSharedRef Json = MakeShared(); Json->SetNumberField(TEXT("workers_requested"), Timing.Workers); Json->SetNumberField(TEXT("workers_observed"), Timing.ObservedWorkerThreads); Json->SetNumberField(TEXT("lattice_samples"), static_cast(Timing.SampleCount)); Json->SetNumberField(TEXT("density_samples_evaluated"), static_cast(Timing.EvaluatedSamples)); Json->SetNumberField(TEXT("samples_skipped_by_brick_proof"), static_cast(Timing.SkippedSamples)); Json->SetNumberField(TEXT("bricks_tested"), static_cast(Timing.BrickCount)); Json->SetNumberField(TEXT("bricks_skipped"), static_cast(Timing.SkippedBricks)); Json->SetNumberField(TEXT("share_of_bricks_skipped"), Timing.BrickCount > 0 ? static_cast(Timing.SkippedBricks) / static_cast(Timing.BrickCount) : 0.0); Json->SetNumberField(TEXT("share_of_lattice_samples_skipped"), Timing.SampleCount > 0 ? static_cast(Timing.SkippedSamples) / static_cast(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(Timing.MacroBrickChecks)); Json->SetNumberField(TEXT("macro_center_evaluations"), static_cast(Timing.MacroCentreEvaluations)); Json->SetNumberField(TEXT("unsupported_brick_proofs"), static_cast(Timing.UnsupportedBrickProofs)); Json->SetNumberField(TEXT("chunk_or_strate_boundary_proofs"), static_cast(Timing.ChunkBoundaryBrickProofs)); Json->SetNumberField(TEXT("passage_bound_proofs"), static_cast(Timing.PassageBoundBrickProofs)); Json->SetNumberField(TEXT("mixed_macro_interval_proofs"), static_cast(Timing.MixedMacroBrickProofs)); Json->SetNumberField(TEXT("room_sdf_evaluations"), static_cast(Timing.RoomEvaluations)); Json->SetNumberField(TEXT("tunnel_sdf_evaluations"), static_cast(Timing.TunnelEvaluations)); Json->SetNumberField(TEXT("tunnel_core_evaluations"), static_cast(Timing.TunnelCoreEvaluations)); Json->SetNumberField(TEXT("passage_sdf_evaluations"), static_cast(Timing.PassageEvaluations)); Json->SetNumberField(TEXT("geometry_evaluations_per_lattice_sample"), Timing.CandidateEvaluationsPerSample); Json->SetNumberField(TEXT("quantized_hash"), static_cast(Timing.QuantizedHash)); Json->SetNumberField(TEXT("raw_float_hash"), static_cast(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 OldTrials; TArray NewTrials; TArray OldDensities; TArray NewDensities; TArray NewRepeatDensities; TArray NewSkippedDensities; TArray SmoothDensities; TArray 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(Dimension) * Dimension * Dimension; for (int64 Index = 0; Index < TotalLatticeSamples; Index += 2047) { const int32 X = static_cast(Index % Dimension); const int32 Y = static_cast((Index / Dimension) % Dimension); const int32 Z = static_cast(Index / (static_cast(Dimension) * Dimension)); const float RawDetailField = VoxelForgeRedefinedDensityField::Evaluate( *World.Generator, static_cast(Origin.X + X), static_cast(Origin.Y + Y), static_cast(Origin.Z + Z), nullptr, 1.0f); DetailBatchMaxAbsoluteError = FMath::Max( DetailBatchMaxAbsoluteError, static_cast(FMath::Abs(RawDetailField - NewDensities[static_cast(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 OldWallTrials; TArray 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(OldCounters.RoomEvaluations + OldCounters.TunnelEvaluations + OldCounters.TunnelCoreEvaluations + OldCounters.PassageEvaluations); const double OldGeometryPerSample = OldCounters.SampleCount > 0 ? OldGeometryEvaluations / static_cast(OldCounters.SampleCount) : 0.0; const double NewMacroPerSample = NewTrials.Last().SampleCount > 0 ? static_cast(NewTrials.Last().MacroCentreEvaluations + NewTrials.Last().EvaluatedSamples) / static_cast(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 Report = MakeShared(); 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(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 Window = MakeShared(); 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(NewTrials.Last().EvaluatedSamples) * 4.0 / static_cast(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(NewTrials.Last().SampleCount)); Report->SetNumberField(TEXT("new_speedup_vs_old"), NewMedianWall > 0.0 ? OldMedianWall / NewMedianWall : 0.0); TArray> OldRows; TArray> NewRows; for (const auto& Timing : OldTrials) { OldRows.Add(MakeShared(Step2dTimingJson(Timing))); } for (const auto& Timing : NewTrials) { NewRows.Add(MakeShared(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 Hashes = MakeShared(); Hashes->SetStringField(TEXT("six_worker_run_1_quantized"), FString::Printf( TEXT("%016llx"), static_cast(NewFullSixWorkerTiming.QuantizedHash))); Hashes->SetStringField(TEXT("six_worker_run_2_quantized"), FString::Printf( TEXT("%016llx"), static_cast(NewRepeatTiming.QuantizedHash))); Hashes->SetStringField(TEXT("one_worker_quantized"), FString::Printf( TEXT("%016llx"), static_cast(OneWorkerTiming.QuantizedHash))); Hashes->SetStringField(TEXT("six_worker_run_1_raw_float"), FString::Printf( TEXT("%016llx"), static_cast(NewFullSixWorkerTiming.RawFloatHash))); Hashes->SetStringField(TEXT("six_worker_run_2_raw_float"), FString::Printf( TEXT("%016llx"), static_cast(NewRepeatTiming.RawFloatHash))); Hashes->SetStringField(TEXT("one_worker_raw_float"), FString::Printf( TEXT("%016llx"), static_cast(OneWorkerTiming.RawFloatHash))); Hashes->SetBoolField(TEXT("same_across_two_runs_and_1_vs_6_workers"), bHashesAgree); Report->SetObjectField(TEXT("determinism"), Hashes); TSharedRef Detail = MakeShared(); Detail->SetNumberField(TEXT("old_surface_band_samples"), static_cast(OldSpectrum.SurfaceBandSamples)); Detail->SetNumberField(TEXT("new_surface_band_samples"), static_cast(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 Boundaries = MakeShared(); 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(BoundaryStats.BrickBoundarySamples)); Boundaries->SetNumberField(TEXT("chunk_boundary_samples_near_surface_xy"), static_cast(BoundaryStats.ChunkBoundarySamples)); Boundaries->SetNumberField(TEXT("macro_period_boundary_samples"), static_cast(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 Proofs = MakeShared(); Proofs->SetNumberField(TEXT("correctly_skipped_bricks_audited"), static_cast(BrickProofAudit.CorrectlySkippedBricksAudited)); Proofs->SetNumberField(TEXT("final_field_samples_checked_including_halo"), static_cast(BrickProofAudit.CorrectProofSamplesChecked)); Proofs->SetNumberField(TEXT("correct_proof_sign_violations"), static_cast(BrickProofAudit.CorrectProofViolations)); Proofs->SetNumberField(TEXT("deliberately_undersized_detail_bound_claims_checked"), static_cast(BrickProofAudit.DeliberatelyUndersizedBoundClaimsChecked)); Proofs->SetNumberField(TEXT("wrong_sign_samples_from_undersized_bound"), static_cast(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> Slices; for (const FString& Name : ImageNames) { TSharedRef Slice = MakeShared(); 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(Slice)); } Report->SetArrayField(TEXT("slices"), Slices); FString JsonText; const TSharedRef> 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(NewTrials.Last().SkippedSamples) / static_cast(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(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 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> ProfileRows; TArray> SliceRows; TArray 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 Times; TArray 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 Row = MakeShared(); 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> TrialValues; TrialValues.Add(MakeShared(Times[0])); TrialValues.Add(MakeShared(Times[1])); TrialValues.Add(MakeShared(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(LastTiming.SkippedBricks)); Row->SetNumberField(TEXT("skipped_sample_fraction"), LastTiming.SampleCount > 0 ? static_cast(LastTiming.SkippedSamples) / LastTiming.SampleCount : 0.0); Row->SetNumberField(TEXT("evaluated_samples"), static_cast(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(Row)); for (const FString& ImageName : ImageNames) { TSharedRef Slice = MakeShared(); 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(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 HardestFullDensity; TArray RepeatFullDensity; TArray OneWorkerDensity; TArray 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(Index % Dimension); const int32 Y = static_cast((Index / Dimension) % Dimension); const int32 Z = static_cast(Index / (static_cast(Dimension) * Dimension)); const float Direct = VoxelForgeRedefinedDensityField::EvaluateParameterized( *World.Generator, static_cast(Origin.X + X), static_cast(Origin.Y + Y), static_cast(Origin.Z + Z), nullptr, 1.0f); DetailParityMaxError = FMath::Max(DetailParityMaxError, static_cast(FMath::Abs(Direct - HardestFullDensity[static_cast(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 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 Report = MakeShared(); 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(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(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 Detail = MakeShared(); Detail->SetNumberField(TEXT("live_surface_band_samples"), static_cast(OldSpectrum.SurfaceBandSamples)); Detail->SetNumberField(TEXT("new_surface_band_samples"), static_cast(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 Boundary = MakeShared(); 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(DeliberateSeamSamplesMutated)); Boundary->SetBoolField(TEXT("deliberate_seam_failure_caught"), bDeliberateSeamFailureCaught); Report->SetObjectField(TEXT("boundary_artifact_check"), Boundary); TSharedRef Proofs = MakeShared(); Proofs->SetNumberField(TEXT("correctly_skipped_bricks_audited"), static_cast(ProofAudit.CorrectlySkippedBricksAudited)); Proofs->SetNumberField(TEXT("final_field_samples_checked_including_halo"), static_cast(ProofAudit.CorrectProofSamplesChecked)); Proofs->SetNumberField(TEXT("correct_proof_sign_violations"), static_cast(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(ProofAudit.DeliberatelyUndersizedBoundClaimsChecked)); Proofs->SetNumberField(TEXT("wrong_sign_samples_caught_by_undersized_bound"), static_cast(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 Determinism = MakeShared(); Determinism->SetStringField(TEXT("six_worker_run_1_quantized"), FString::Printf( TEXT("%016llx"), static_cast(HardestHash))); Determinism->SetStringField(TEXT("six_worker_run_2_quantized"), FString::Printf( TEXT("%016llx"), static_cast(RepeatHash))); Determinism->SetStringField(TEXT("one_worker_quantized"), FString::Printf( TEXT("%016llx"), static_cast(OneWorkerHash))); Determinism->SetStringField(TEXT("six_worker_run_1_raw_float"), FString::Printf( TEXT("%016llx"), static_cast(HardestFullTiming.RawFloatHash))); Determinism->SetStringField(TEXT("six_worker_run_2_raw_float"), FString::Printf( TEXT("%016llx"), static_cast(RepeatTiming.RawFloatHash))); Determinism->SetStringField(TEXT("one_worker_raw_float"), FString::Printf( TEXT("%016llx"), static_cast(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> 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> 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(Dimension) * Dimension * Dimension; TArray> TrialRows; TArray WallTimes; TArray 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 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(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 TrialJson = MakeShared(); 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(Timing.EvaluatedSamples)); TrialJson->SetNumberField(TEXT("skipped_samples"), static_cast(Timing.SkippedSamples)); TrialJson->SetNumberField(TEXT("bricks"), static_cast(Timing.BrickCount)); TrialJson->SetNumberField(TEXT("skipped_bricks"), static_cast(Timing.SkippedBricks)); TrialJson->SetStringField(TEXT("quantized_hash"), FString::Printf( TEXT("%016llx"), static_cast(Timing.QuantizedHash))); TrialJson->SetStringField(TEXT("raw_float_hash"), FString::Printf( TEXT("%016llx"), static_cast(Timing.RawFloatHash))); TrialRows.Add(MakeShared(TrialJson)); } WallTimes.Sort(); CpuTimes.Sort(); TSharedRef Cube = MakeShared(); Cube->SetNumberField(TEXT("dimension"), Dimension); Cube->SetNumberField(TEXT("sample_count"), static_cast(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(Dimension - 1) * VOXEL_SIZE / 100.0); Cube->SetNumberField(TEXT("density_output_mib"), static_cast(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(EvaluatedSamples)); Cube->SetNumberField(TEXT("skipped_samples"), static_cast(SkippedSamples)); Cube->SetNumberField(TEXT("bricks"), static_cast(BrickCount)); Cube->SetNumberField(TEXT("skipped_bricks"), static_cast(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(FirstQuantizedHash))); Cube->SetStringField(TEXT("raw_float_hash"), FString::Printf( TEXT("%016llx"), static_cast(FirstRawHash))); Cube->SetArrayField(TEXT("trials"), TrialRows); CubeRows.Add(MakeShared(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(ExpectedSamples), static_cast(Dimension - 1) * VOXEL_SIZE / 100.0, Workers, WallTimes[WallTimes.Num() / 2], WallTimes[0], WallTimes.Last(), static_cast(EvaluatedSamples), static_cast(SkippedSamples), bDeterministic ? 1 : 0, static_cast(FirstQuantizedHash)); } TSharedRef Report = MakeShared(); 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(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> 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(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> ProfileRows; TArray> 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& 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(100.0f / VOXEL_SIZE + 0.5f); const FIntVector DetailOrigin = Origin + FIntVector( (Dimension - DetailDimension) / 2, (Dimension - DetailDimension) / 2, (Dimension - DetailDimension) / 2); TArray 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(DetailOrigin.X + X), static_cast(DetailOrigin.Y + Y), static_cast(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( 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( 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 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> 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 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* 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& Layout = World.Manager->GetLayout(); constexpr int32 XYStep = static_cast(100.0f / VOXEL_SIZE + 0.5f); constexpr int32 ShellOffsets[] = { static_cast(25.0f / VOXEL_SIZE + 0.5f), static_cast(125.0f / VOXEL_SIZE + 0.5f), static_cast(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(X), static_cast(Y), static_cast(Bottom + Offset), nullptr, 1.0f); const float RoofDensity = VoxelForgeRedefinedDensityField::EvaluateParameterized( *World.Generator, static_cast(X), static_cast(Y), static_cast(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 Json = MakeShared(); 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(Shape.Fragments.AirCrumbsBelowOneCubicMeter)); Json->SetNumberField(TEXT("solid_crumbs_below_1m3"), static_cast(Shape.Fragments.SolidCrumbsBelowOneCubicMeter)); Json->SetNumberField(TEXT("air_crumbs_below_1m3_after_boundary_padding"), static_cast(Shape.Fragments.AirCrumbsAfterBoundaryPadding >= 0 ? Shape.Fragments.AirCrumbsAfterBoundaryPadding : Shape.Fragments.AirCrumbsBelowOneCubicMeter)); Json->SetNumberField(TEXT("solid_crumbs_below_1m3_after_boundary_padding"), static_cast(Shape.Fragments.SolidCrumbsAfterBoundaryPadding >= 0 ? Shape.Fragments.SolidCrumbsAfterBoundaryPadding : Shape.Fragments.SolidCrumbsBelowOneCubicMeter)); Json->SetNumberField(TEXT("air_crumb_voxel_samples"), static_cast(Shape.Fragments.AirCrumbVoxelSamples)); Json->SetNumberField(TEXT("solid_crumb_voxel_samples"), static_cast(Shape.Fragments.SolidCrumbVoxelSamples)); TArray> CrumbComponents; for (const FStep2fFragmentMetrics::FCrumbComponent& Crumb : Shape.Fragments.CrumbComponents) { TSharedRef CrumbJson = MakeShared(); 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(Crumb.Root.X), MakeShared(Crumb.Root.Y), MakeShared(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(Crumb.Min.X), MakeShared(Crumb.Min.Y), MakeShared(Crumb.Min.Z)}); CrumbJson->SetArrayField(TEXT("max_ring_voxel"), { MakeShared(Crumb.Max.X), MakeShared(Crumb.Max.Y), MakeShared(Crumb.Max.Z)}); CrumbComponents.Add(MakeShared(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(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(Shape.Structure.RoofSamples)); Json->SetNumberField(TEXT("roof_openings"), static_cast(Shape.Structure.RoofOpenings)); Json->SetNumberField(TEXT("floor_samples"), static_cast(Shape.Structure.FloorSamples)); Json->SetNumberField(TEXT("floor_openings"), static_cast(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(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* OutCrumbComponents = nullptr) { VoxelForgeRedefinedDensityField::SetLegacyStep2eAuditMode(bLegacyField); TArray 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 LegacyDensity; TArray 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 NewDensity; TArray 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 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 Row = MakeShared(); 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> 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(SampleVoxel.X); const float SampleY = static_cast(SampleVoxel.Y); const float SampleZ = static_cast(SampleVoxel.Z); TSharedRef CrumbJson = MakeShared(); 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(PaddedCrumbOrigin.X + Crumb.Root.X), MakeShared(PaddedCrumbOrigin.Y + Crumb.Root.Y), MakeShared(PaddedCrumbOrigin.Z + Crumb.Root.Z)}); CrumbJson->SetArrayField(TEXT("min_world_voxel"), { MakeShared(SampleVoxel.X), MakeShared(SampleVoxel.Y), MakeShared(SampleVoxel.Z)}); CrumbJson->SetArrayField(TEXT("max_world_voxel"), { MakeShared(PaddedCrumbOrigin.X + Crumb.Max.X), MakeShared(PaddedCrumbOrigin.Y + Crumb.Max.Y), MakeShared(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(CrumbJson)); } Row->SetArrayField(TEXT("step2f_padded_crumb_diagnostics"), PaddedCrumbDiagnostics); Row->SetObjectField(TEXT("step2e_parameters"), Step2eParametersJson(LegacyParameters)); Row->SetObjectField(TEXT("step2f_parameters"), Step2eParametersJson(NewParameters)); TArray> LegacyTrialValues; TArray> NewTrialValues; for (const double Time : LegacyTimes) { LegacyTrialValues.Add(MakeShared(Time)); } for (const double Time : NewTimes) { NewTrialValues.Add(MakeShared(Time)); } Row->SetArrayField(TEXT("step2e_six_worker_ring_trials_ms"), LegacyTrialValues); Row->SetArrayField(TEXT("step2f_six_worker_ring_trials_ms"), NewTrialValues); TSharedRef NewTimingBreakdown = MakeShared(); 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(NewLastTiming.EvaluatedSamples)); NewTimingBreakdown->SetNumberField(TEXT("skipped_samples"), static_cast(NewLastTiming.SkippedSamples)); NewTimingBreakdown->SetNumberField(TEXT("macro_centre_evaluations"), static_cast(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(Row)); for (const FString& ImageName : ImageNames) { TSharedRef Slice = MakeShared(); 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(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(NewShape.Fragments.AirCrumbsAfterBoundaryPadding), static_cast(NewShape.Fragments.SolidCrumbsAfterBoundaryPadding), NewShape.Mesh.ShareDihedralsOver45Degrees, NewShape.Detail.LatticeScaleEnergyRatio, NewShape.Grid.PitchCorrelationPeak, NewShape.Grid.PitchPeakExcess, NewShape.Detail.Rms, NewShape.Lod1P95SurfaceErrorVoxels, static_cast(NewShape.Structure.RoofOpenings), static_cast(NewShape.Structure.FloorOpenings)); } const int32 HardestProfile = VoxelForgeRedefinedDensityField::GetParameterSetCount() - 1; VoxelForgeRedefinedDensityField::SetLegacyStep2eAuditMode(false); VoxelForgeRedefinedDensityField::SetParameterSet(HardestProfile); TArray HardestFullDensity; TArray RepeatDensity; TArray 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(CenterZ) / CHUNK_SIZE) * CHUNK_SIZE + 4); const FIntVector BrickParitySize( VoxelForgeRedefinedDensityField::ParameterizedBrickSamples, VoxelForgeRedefinedDensityField::ParameterizedBrickSamples, 16); VoxelForgeRedefinedDensityField::FParameterizedBrickContext BrickParityContext; TArray BrickParityDetail; TArray BrickParityColumn; TArray 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(BrickParityOrigin.X + X), static_cast(BrickParityOrigin.Y + Y), static_cast(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 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 Gates = MakeShared(); const auto AddGate = [&Gates](const TCHAR* Name, const TCHAR* Threshold, bool bLegacyRejected, bool bNewPassed, const TSharedRef& LegacyNumbers, const TSharedRef& NewNumbers) { TSharedRef Gate = MakeShared(); 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 Step2eCrumbNumbers = MakeShared(); Step2eCrumbNumbers->SetNumberField(TEXT("crumbs_below_1m3_max"), WorstStep2eCrumbs); TSharedRef Step2fCrumbNumbers = MakeShared(); 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 Step2eFlakeNumbers = MakeShared(); Step2eFlakeNumbers->SetNumberField(TEXT("max_dihedral_share_over_45deg"), WorstStep2eDihedralShare); Step2eFlakeNumbers->SetNumberField(TEXT("max_dihedral_p95_deg"), WorstStep2eDihedralP95); TSharedRef Step2fFlakeNumbers = MakeShared(); 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 Step2eDetailNumbers = MakeShared(); Step2eDetailNumbers->SetNumberField(TEXT("max_lattice_scale_energy_ratio"), WorstStep2eDetailEnergy); Step2eDetailNumbers->SetNumberField(TEXT("min_detail_rms"), BestStep2eDetailRms); TSharedRef Step2fDetailNumbers = MakeShared(); 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 Step2eLodNumbers = MakeShared(); Step2eLodNumbers->SetNumberField(TEXT("max_l1_p95_surface_distance_voxels"), WorstStep2eLodDistance); TSharedRef Step2fLodNumbers = MakeShared(); 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 Step2eGridNumbers = MakeShared(); Step2eGridNumbers->SetNumberField(TEXT("max_pitch_autocorrelation"), WorstStep2ePitchCorrelation); Step2eGridNumbers->SetNumberField(TEXT("max_pitch_peak_excess"), WorstStep2ePitchExcess); TSharedRef Step2fGridNumbers = MakeShared(); 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 Step2eStructureNumbers = MakeShared(); Step2eStructureNumbers->SetNumberField(TEXT("minimum_solid_fraction"), LowestStep2eSolidFraction); Step2eStructureNumbers->SetNumberField(TEXT("max_roof_openings"), WorstStep2eRoofOpenings); Step2eStructureNumbers->SetNumberField(TEXT("max_floor_openings"), WorstStep2eFloorOpenings); TSharedRef Step2fStructureNumbers = MakeShared(); 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 Report = MakeShared(); 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(Origin.X), MakeShared(Origin.Y), MakeShared(Origin.Z)}); Report->SetNumberField(TEXT("ring_samples"), static_cast(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 Existing = MakeShared(); Existing->SetBoolField(TEXT("sound_brick_proofs"), bBoundAudit); Existing->SetNumberField(TEXT("correctly_skipped_bricks_audited"), static_cast(ProofAudit.CorrectlySkippedBricksAudited)); Existing->SetNumberField(TEXT("final_field_samples_checked"), static_cast(ProofAudit.CorrectProofSamplesChecked)); Existing->SetNumberField(TEXT("correct_proof_sign_violations"), static_cast(ProofAudit.CorrectProofViolations)); TSharedRef FirstProofViolation = MakeShared(); FirstProofViolation->SetBoolField(TEXT("present"), ProofAudit.CorrectProofViolations > 0); FirstProofViolation->SetArrayField(TEXT("brick_origin_voxels"), { MakeShared(ProofAudit.FirstViolationBrickOrigin.X), MakeShared(ProofAudit.FirstViolationBrickOrigin.Y), MakeShared(ProofAudit.FirstViolationBrickOrigin.Z)}); FirstProofViolation->SetArrayField(TEXT("brick_size"), { MakeShared(ProofAudit.FirstViolationBrickSize.X), MakeShared(ProofAudit.FirstViolationBrickSize.Y), MakeShared(ProofAudit.FirstViolationBrickSize.Z)}); FirstProofViolation->SetArrayField(TEXT("sample_voxels"), { MakeShared(ProofAudit.FirstViolationSample.X), MakeShared(ProofAudit.FirstViolationSample.Y), MakeShared(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(ProofAudit.FirstViolationPathMaxFrom.X), MakeShared(ProofAudit.FirstViolationPathMaxFrom.Y), MakeShared(ProofAudit.FirstViolationPathMaxFrom.Z)}); FirstProofViolation->SetArrayField(TEXT("path_max_to_voxels"), { MakeShared(ProofAudit.FirstViolationPathMaxTo.X), MakeShared(ProofAudit.FirstViolationPathMaxTo.Y), MakeShared(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(ProofAudit.DeliberatelyUndersizedBoundClaimsChecked)); Existing->SetNumberField(TEXT("wrong_sign_samples_caught_by_undersized_bound"), static_cast(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(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(BrickScalarMismatchCount)); Existing->SetStringField(TEXT("six_worker_hash"), FString::Printf( TEXT("%016llx"), static_cast(HardestHash))); Existing->SetStringField(TEXT("repeat_six_worker_hash"), FString::Printf( TEXT("%016llx"), static_cast(RepeatHash))); Existing->SetStringField(TEXT("one_worker_hash"), FString::Printf( TEXT("%016llx"), static_cast(OneWorkerHash))); Existing->SetStringField(TEXT("functional_test_filter"), TEXT("VoxelForge.ParameterizedGeneration.TraversalSeeds")); Report->SetObjectField(TEXT("existing_checks"), Existing); FString JsonText; const TSharedRef> 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 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 CameraSeedPoses; TArray 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 CoreCrossingsVoxels; TArray 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 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 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 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(Width) / static_cast(Height); OutBasis.TanHalfFov = FMath::Tan(FMath::DegreesToRadians(FieldOfViewDegrees * 0.5f)); return OutBasis.Aspect > 0.0f && OutBasis.TanHalfFov > 0.0f; } void PutPixel(TArray& 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& 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(Width - 1)); OutPixel.Y = FMath::RoundToInt((1.0f - (NdcY * 0.5f + 0.5f)) * static_cast(Height - 1)); return NdcX >= -1.0f && NdcX <= 1.0f && NdcY >= -1.0f && NdcY <= 1.0f; } void DrawScaleMarker( TArray& 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& Pixels, int32 Width, int32 Height, FString& OutError) { IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked( FName(TEXT("ImageWrapper"))); TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper( EImageFormat::PNG); if (!ImageWrapper.IsValid() || !ImageWrapper->SetRaw( Pixels.GetData(), static_cast(Pixels.Num()) * sizeof(FColor), Width, Height, ERGBFormat::BGRA, 8)) { OutError = TEXT("ImageWrapper could not accept the BGRA render buffer."); return false; } const TArray64& 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(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(MeshSize)) / static_cast(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( 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(PointsPerTile) * static_cast(PointsPerTile) * static_cast(PointsPerTile); const int32 SharedGridDim = Arguments.ExportSize / Arguments.ExportStep + 3; const int64 SharedGridSamples = static_cast(SharedGridDim) * static_cast(SharedGridDim) * static_cast(SharedGridDim); World.ExploreDensityGridTotalSamples = static_cast(World.ExploreMeshTileCount) * TileGridSamples; World.ExploreDensityGridUniqueSamples = SharedGridSamples; World.ExploreDensityGridDuplicateSamplesBefore = FMath::Max( 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 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 TileMeshes; TileMeshes.SetNum(World.ExploreMeshTileCount); TArray TileErrors; TileErrors.SetNum(World.ExploreMeshTileCount); TArray TileCompleted; TileCompleted.Init(0, World.ExploreMeshTileCount); TArray TileDurations; TileDurations.Init(0.0, World.ExploreMeshTileCount); TArray TileStartOffsets; TileStartOffsets.Init(-1.0, World.ExploreMeshTileCount); TArray TileEndOffsets; TileEndOffsets.Init(-1.0, World.ExploreMeshTileCount); TArray TileThreadIds; TileThreadIds.Init(0, World.ExploreMeshTileCount); std::atomic 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 CompletedTileDurations; TSet 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(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 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& 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(PixelX) + 0.5f) / static_cast(Arguments.RenderWidth)) - 1.0f; const float NormalizedY = 1.0f - (2.0f * (static_cast(PixelY) + 0.5f) / static_cast(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(FMath::Clamp(100.0f * Brightness, 0.0f, 255.0f)), static_cast(FMath::Clamp(128.0f * Brightness, 0.0f, 255.0f)), static_cast(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(Arguments.RenderWidth) * static_cast(Arguments.RenderHeight) * 3ll * static_cast(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, 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& 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& 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(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(ViewIndex), FVector::UpVector).GetSafeNormal(); const FVector Target = CameraPosition + LookDirection * 96.0f + FVector::UpVector * 8.0f; const double ViewStartSeconds = FPlatformTime::Seconds(); TArray 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(AdditionalViewCount) : 0.0; if (!OutOutput.bTruncated) { OutOutput.Status = TEXT("ok"); } return !OutOutput.bTruncated; } void DrawPointMarker( TArray& 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 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(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(EstimatedWorkingBytes), static_cast(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(World.TargetBottomWorldZ) || Point.Z > static_cast(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 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(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(X) + 0.5f) * OutOutput.SharedSampleGrid.SampleStep, OutOutput.SharedSampleGrid.MinY + (static_cast(Y) + 0.5f) * OutOutput.SharedSampleGrid.SampleStep, OutOutput.SharedSampleGrid.MinZ + (static_cast(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(X) + 0.5f) * OutOutput.SharedSampleGrid.SampleStep, OutOutput.SharedSampleGrid.MinY + (static_cast(Y) + 0.5f) * OutOutput.SharedSampleGrid.SampleStep, OutOutput.SharedSampleGrid.MinZ + (static_cast(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(CHUNK_SIZE) * static_cast(CHUNK_SIZE) * static_cast(CHUNK_SIZE); const int64 TileGridSamples = static_cast(CHUNK_SIZE + 3) * static_cast(CHUNK_SIZE + 3) * static_cast(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(sizeof(float)) + TileCells * 448ll; } bool WriteUtf8(FArchive& Archive, const FString& Text) { FTCHARToUTF8 Converted(*Text); if (Converted.Length() > 0) { Archive.Serialize(const_cast(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& SortedValues, float Quantile) { if (SortedValues.Num() == 0) { return 0.0f; } const float Position = FMath::Clamp(Quantile, 0.0f, 1.0f) * static_cast(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(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 EdgeLengthsMeters; TArray DihedralAnglesDegrees; TArray FaceNormalDivergencesDegrees; EdgeLengthsMeters.Reserve(NumTriangles * 3); DihedralAnglesDegrees.Reserve(NumTriangles * 2); FaceNormalDivergencesDegrees.Reserve(NumTriangles); TArray FaceNormals; FaceNormals.Init(FVector::ZeroVector, NumTriangles); TArray ValidFaceNormals; ValidFaceNormals.Init(0, NumTriangles); TMap 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& 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(Over20) / static_cast(DihedralAnglesDegrees.Num()); OutMetrics.DihedralFractionOver40Degrees = static_cast(Over40) / static_cast(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(Value.X) * Scale); Writer.WriteValue(TEXT("y"), static_cast(Value.Y) * Scale); Writer.WriteValue(TEXT("z"), static_cast(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(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(Metrics.EdgeLengthP50Meters)); Writer.WriteValue(TEXT("p99"), static_cast(Metrics.EdgeLengthP99Meters)); Writer.WriteValue(TEXT("max"), static_cast(Metrics.EdgeLengthMaxMeters)); Writer.WriteObjectEnd(); Writer.WriteObjectStart(TEXT("dihedral_angle_degrees")); Writer.WriteValue(TEXT("p50"), static_cast(Metrics.DihedralP50Degrees)); Writer.WriteValue(TEXT("p75"), static_cast(Metrics.DihedralP75Degrees)); Writer.WriteValue(TEXT("p90"), static_cast(Metrics.DihedralP90Degrees)); Writer.WriteValue(TEXT("p95"), static_cast(Metrics.DihedralP95Degrees)); Writer.WriteValue(TEXT("p99"), static_cast(Metrics.DihedralP99Degrees)); Writer.WriteValue(TEXT("fraction_over_20_degrees"), static_cast(Metrics.DihedralFractionOver20Degrees)); Writer.WriteValue(TEXT("fraction_over_40_degrees"), static_cast(Metrics.DihedralFractionOver40Degrees)); Writer.WriteObjectEnd(); Writer.WriteObjectStart(TEXT("face_normal_vs_mean_vertex_normal_degrees")); Writer.WriteValue(TEXT("p50"), static_cast(Metrics.FaceNormalVsMeanVertexNormalP50Degrees)); Writer.WriteValue(TEXT("p90"), static_cast(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(Arguments.TunnelFloorStep)); } Writer.WriteValue(TEXT("max_ledge_height_override"), Arguments.bTunnelFloorMaxLedgeOverride); if (Arguments.bTunnelFloorMaxLedgeOverride) { Writer.WriteValue(TEXT("max_ledge_height_voxels"), static_cast(Arguments.TunnelFloorMaxLedge)); } Writer.WriteValue(TEXT("gentle_slope_override"), Arguments.bTunnelFloorGentleOverride); if (Arguments.bTunnelFloorGentleOverride) { Writer.WriteValue(TEXT("gentle_slope"), static_cast(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 Writer = TJsonWriterFactory>::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(Arguments.WormStrength)); } Writer->WriteValue(TEXT("tunnel_ablation_mask"), static_cast(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(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(Arguments.RoomMouthRiseBlend)); } Writer->WriteValue(TEXT("surface_roughness_override"), Arguments.bSurfaceRoughnessOverride); if (Arguments.bSurfaceRoughnessOverride) { Writer->WriteValue(TEXT("surface_roughness"), static_cast(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(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(Export.RegionOrigin.X) * VOXEL_SIZE / 100.0); Writer->WriteValue(TEXT("y"), static_cast(Export.RegionOrigin.Y) * VOXEL_SIZE / 100.0); Writer->WriteValue(TEXT("z"), static_cast(Export.RegionOrigin.Z) * VOXEL_SIZE / 100.0); Writer->WriteObjectEnd(); Writer->WriteObjectStart(TEXT("max_exclusive")); Writer->WriteValue(TEXT("x"), static_cast(Export.RegionOrigin.X + Export.RegionSize) * VOXEL_SIZE / 100.0); Writer->WriteValue(TEXT("y"), static_cast(Export.RegionOrigin.Y + Export.RegionSize) * VOXEL_SIZE / 100.0); Writer->WriteValue(TEXT("z"), static_cast(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(FVoxelPlayerCapsuleConstants::RadiusCentimeters) / 100.0); Writer->WriteValue(TEXT("capsule_width_m"), static_cast(2.0f * FVoxelPlayerCapsuleConstants::RadiusCentimeters) / 100.0); Writer->WriteValue(TEXT("capsule_half_height_m"), static_cast(FVoxelPlayerCapsuleConstants::HalfHeightCentimeters) / 100.0); Writer->WriteValue(TEXT("capsule_height_m"), static_cast(2.0f * FVoxelPlayerCapsuleConstants::HalfHeightCentimeters) / 100.0); Writer->WriteValue(TEXT("max_step_height_m"), static_cast(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(OutOutput.EstimatedWorkingBytes), static_cast(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 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(World.ExploreMesh.Vertices.Num()) * sizeof(FVector) + static_cast(World.ExploreMesh.Normals.Num()) * sizeof(FVector) + static_cast(World.ExploreMesh.UVs.Num()) * sizeof(FVector2D) + static_cast(World.ExploreMesh.Colors.Num()) * sizeof(FColor) + static_cast(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(Arguments.ExportSize) / static_cast(Arguments.ExportSize) / static_cast(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::Max(); float MaxDensity = -TNumericLimits::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(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& 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(OutValidCount); OutMean = static_cast(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(AnalysisCount - 1) * StepVoxels; TArray> 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(FMath::Max( BestRunLength - 1, 1)); for (int32 Index = BestRunStart; Index <= BestRunEnd; ++Index) { const double NormalizedX = -1.0 + 2.0 * static_cast(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(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(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(static_cast(Samples[Index]) - Trend); } } } constexpr float MaxRibPeriodVoxels = 16.0f; const int32 MinRibFrequency = FMath::Max( 1, FMath::CeilToInt(StepVoxels * static_cast(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> 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(Window[Middle]) : 0.5 * (static_cast(Window[Middle - 1]) + static_cast(Window[Middle])); Residuals[AnalysisIndex] = static_cast( static_cast(Samples[Index]) - Median); } const double Residual = static_cast(Residuals[AnalysisIndex]); const double Angle = 2.0 * PI * static_cast(Frequency) * static_cast(AnalysisIndex) / static_cast(AnalysisCount); Real += Residual * FMath::Cos(static_cast(Angle)); Imaginary -= Residual * FMath::Sin(static_cast(Angle)); } const double Amplitude = 2.0 * FMath::Sqrt(Real * Real + Imaginary * Imaginary) / static_cast(AnalysisCount); if (Amplitude > BestAmplitude) { BestAmplitude = Amplitude; BestFrequency = Frequency; } } OutPeriodicAmplitudeVoxels = static_cast(BestAmplitude); OutPeriodicPeriodVoxels = BestFrequency > 0 ? StepVoxels * static_cast(AnalysisCount) / static_cast(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(World.TargetBottomWorldZ) / static_cast(CHUNK_SIZE)); const int32 TargetTopChunkZ = FMath::FloorToInt( (static_cast(World.TargetTopWorldZ) - KINDA_SMALL_NUMBER) / static_cast(CHUNK_SIZE)); const float TargetMinZ = static_cast(World.TargetBottomWorldZ); const float TargetMaxZ = static_cast(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(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(CandidateChunk.Z) + 0.5f) * static_cast(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* 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& Points = Tunnel.WorldControlPoints.Num() >= 2 && Tunnel.WorldControlRadii.Num() == Tunnel.WorldControlPoints.Num() ? Tunnel.WorldControlPoints : Tunnel.ControlPoints; const TArray& Radii = Tunnel.WorldControlRadii.Num() == Points.Num() ? Tunnel.WorldControlRadii : Tunnel.ControlRadii; if (Points.Num() < 2 || Radii.Num() != Points.Num()) { continue; } TArray 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(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(CHUNK_SIZE)), FMath::FloorToInt(Point.Y / static_cast(CHUNK_SIZE)), FMath::FloorToInt(Point.Z / static_cast(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(CHUNK_SIZE)), FMath::FloorToInt(RadialPoint.Y / static_cast(CHUNK_SIZE)), FMath::FloorToInt(RadialPoint.Z / static_cast(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& Points = BestTunnel->WorldControlPoints.Num() >= 2 && BestTunnel->WorldControlRadii.Num() == BestTunnel->WorldControlPoints.Num() ? BestTunnel->WorldControlPoints : BestTunnel->ControlPoints; TArray 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(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(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(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& 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(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(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::quiet_NaN(); Roots[AxisIndex][1] = std::numeric_limits::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& MouthRadii = Radii; const TArray& 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 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(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(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(Endpoint.Mouth.Z) - Endpoint.RadiusVoxels - 8.0f; for (int32 SampleIndex = 0; SampleIndex < LateralSamples; ++SampleIndex) { const float LateralOffset = -ActualHalfWidth + static_cast(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::quiet_NaN(); float FloorCrossing = std::numeric_limits::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(X), static_cast(Y), static_cast(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(Axis.CoreMeanCrossingVoxels)); Writer.WriteValue(TEXT("final_mean_crossing_voxels"), static_cast(Axis.FinalMeanCrossingVoxels)); Writer.WriteValue(TEXT("core_min_crossing_voxels"), static_cast(Axis.CoreMinCrossingVoxels)); Writer.WriteValue(TEXT("core_max_crossing_voxels"), static_cast(Axis.CoreMaxCrossingVoxels)); Writer.WriteValue(TEXT("final_min_crossing_voxels"), static_cast(Axis.FinalMinCrossingVoxels)); Writer.WriteValue(TEXT("final_max_crossing_voxels"), static_cast(Axis.FinalMaxCrossingVoxels)); Writer.WriteValue(TEXT("core_periodic_amplitude_voxels"), static_cast(Axis.CorePeriodicAmplitudeVoxels)); Writer.WriteValue(TEXT("core_periodic_amplitude_cm"), static_cast(Axis.CorePeriodicAmplitudeVoxels) * VOXEL_SIZE); Writer.WriteValue(TEXT("core_periodic_period_voxels"), static_cast(Axis.CorePeriodicPeriodVoxels)); Writer.WriteValue(TEXT("core_periodic_measured_samples"), Axis.CorePeriodicMeasuredSamples); Writer.WriteValue(TEXT("core_periodic_measured_length_voxels"), static_cast(Axis.CorePeriodicMeasuredLengthVoxels)); Writer.WriteValue(TEXT("final_periodic_amplitude_voxels"), static_cast(Axis.FinalPeriodicAmplitudeVoxels)); Writer.WriteValue(TEXT("final_periodic_amplitude_cm"), static_cast(Axis.FinalPeriodicAmplitudeVoxels) * VOXEL_SIZE); Writer.WriteValue(TEXT("final_periodic_period_voxels"), static_cast(Axis.FinalPeriodicPeriodVoxels)); Writer.WriteValue(TEXT("final_periodic_measured_samples"), Axis.FinalPeriodicMeasuredSamples); Writer.WriteValue(TEXT("final_periodic_measured_length_voxels"), static_cast(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(Axis.MeanCrossingVoxels)); Writer.WriteValue(TEXT("min_crossing_voxels"), static_cast(Axis.MinCrossingVoxels)); Writer.WriteValue(TEXT("max_crossing_voxels"), static_cast(Axis.MaxCrossingVoxels)); Writer.WriteValue(TEXT("periodic_amplitude_voxels"), static_cast(Axis.PeriodicAmplitudeVoxels)); Writer.WriteValue(TEXT("periodic_amplitude_cm"), static_cast(Axis.PeriodicAmplitudeVoxels) * VOXEL_SIZE); Writer.WriteValue(TEXT("periodic_period_voxels"), static_cast(Axis.PeriodicPeriodVoxels)); Writer.WriteValue(TEXT("periodic_measured_samples"), Axis.PeriodicMeasuredSamples); Writer.WriteValue(TEXT("periodic_measured_length_voxels"), static_cast(Axis.PeriodicMeasuredLengthVoxels)); Writer.WriteValue(TEXT("local_median_periodic_amplitude_voxels"), static_cast(Axis.LocalMedianPeriodicAmplitudeVoxels)); Writer.WriteValue(TEXT("local_median_periodic_amplitude_cm"), static_cast(Axis.LocalMedianPeriodicAmplitudeVoxels) * VOXEL_SIZE); Writer.WriteValue(TEXT("local_median_periodic_period_voxels"), static_cast(Axis.LocalMedianPeriodicPeriodVoxels)); Writer.WriteObjectEnd(); } void WritePlayerDimensions(FExploreJsonWriter& Writer, const TCHAR* Key) { Writer.WriteObjectStart(Key); Writer.WriteValue(TEXT("capsule_radius_m"), static_cast(FVoxelPlayerCapsuleConstants::RadiusCentimeters) / 100.0); Writer.WriteValue(TEXT("capsule_width_m"), static_cast(2.0f * FVoxelPlayerCapsuleConstants::RadiusCentimeters) / 100.0); Writer.WriteValue(TEXT("capsule_half_height_m"), static_cast(FVoxelPlayerCapsuleConstants::HalfHeightCentimeters) / 100.0); Writer.WriteValue(TEXT("capsule_height_m"), static_cast(2.0f * FVoxelPlayerCapsuleConstants::HalfHeightCentimeters) / 100.0); Writer.WriteValue(TEXT("max_step_height_m"), static_cast(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(FVoxelPlayerCapsuleConstants::WalkableFloorAngleDegrees)); Writer.WriteObjectEnd(); } FString BuildExploreJson( const FExploreArguments& Arguments, const FExploreWorld& World, const FExploreRunOutput& Output, bool bDeterminismAssertion, uint32 PayloadCrc32) { FString Json; TSharedRef Writer = TJsonWriterFactory>::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(Arguments.WormStrength)); } Writer->WriteValue(TEXT("tunnel_ablation_mask"), static_cast(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(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(Arguments.RoomMouthRiseBlend)); } Writer->WriteValue(TEXT("surface_roughness_override"), Arguments.bSurfaceRoughnessOverride); if (Arguments.bSurfaceRoughnessOverride) { Writer->WriteValue(TEXT("surface_roughness"), static_cast(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(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(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(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(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="), 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(Output.DensitySlice.MinDensity)); Writer->WriteValue(TEXT("max_density"), static_cast(Output.DensitySlice.MaxDensity)); Writer->WriteValue(TEXT("max_abs_density"), static_cast(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(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(Output.TunnelCoreProbe.StepVoxels)); Writer->WriteValue(TEXT("length_voxels"), static_cast(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(Output.TunnelCoreProbe.CoreCenterMin)); Writer->WriteValue(TEXT("core_center_max"), static_cast(Output.TunnelCoreProbe.CoreCenterMax)); Writer->WriteValue(TEXT("final_center_min"), static_cast(Output.TunnelCoreProbe.FinalCenterMin)); Writer->WriteValue(TEXT("final_center_max"), static_cast(Output.TunnelCoreProbe.FinalCenterMax)); Writer->WriteValue(TEXT("final_center_max_abs"), static_cast(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(Output.TunnelCoreProbe.Mouth.LateralStepVoxels)); Writer->WriteValue(TEXT("lateral_window_voxels"), static_cast(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(Output.TunnelCoreProbe.Mouth.MaxWallPeriodicAmplitudeVoxels)); Writer->WriteValue(TEXT("max_wall_periodic_amplitude_cm"), static_cast(Output.TunnelCoreProbe.Mouth.MaxWallPeriodicAmplitudeVoxels) * VOXEL_SIZE); Writer->WriteValue(TEXT("max_floor_periodic_amplitude_voxels"), static_cast(Output.TunnelCoreProbe.Mouth.MaxFloorPeriodicAmplitudeVoxels)); Writer->WriteValue(TEXT("max_floor_periodic_amplitude_cm"), static_cast(Output.TunnelCoreProbe.Mouth.MaxFloorPeriodicAmplitudeVoxels) * VOXEL_SIZE); Writer->WriteValue(TEXT("max_local_median_wall_periodic_amplitude_voxels"), static_cast(Output.TunnelCoreProbe.Mouth.MaxLocalMedianWallPeriodicAmplitudeVoxels)); Writer->WriteValue(TEXT("max_local_median_wall_periodic_amplitude_cm"), static_cast(Output.TunnelCoreProbe.Mouth.MaxLocalMedianWallPeriodicAmplitudeVoxels) * VOXEL_SIZE); Writer->WriteValue(TEXT("max_local_median_floor_periodic_amplitude_voxels"), static_cast(Output.TunnelCoreProbe.Mouth.MaxLocalMedianFloorPeriodicAmplitudeVoxels)); Writer->WriteValue(TEXT("max_local_median_floor_periodic_amplitude_cm"), static_cast(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(Endpoint.RoomHash)); WriteJsonVector(*Writer, TEXT("mouth_voxels"), Endpoint.Mouth, 1.0f); Writer->WriteValue(TEXT("radius_voxels"), static_cast(Endpoint.RadiusVoxels)); Writer->WriteValue(TEXT("tunnel_floor_z"), static_cast(Endpoint.TunnelFloorZ)); Writer->WriteValue(TEXT("room_floor_crossing_z"), static_cast(Endpoint.RoomFloorCrossingZ)); Writer->WriteValue(TEXT("floor_join_delta_voxels"), static_cast(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(Arguments.ExportSize) / static_cast(Arguments.ExportSize) / static_cast(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(Index)), static_cast(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(World.TargetBottomWorldZ) * VOXEL_SIZE / 100.0); Writer->WriteValue(TEXT("target_top_m_exclusive"), static_cast(World.TargetTopWorldZ) * VOXEL_SIZE / 100.0); Writer->WriteValue(TEXT("target_boundary_seal_thickness_voxels"), static_cast(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(Output.Render.FixedStepVoxels)); Writer->WriteValue(TEXT("fixed_step_m"), static_cast(Output.Render.FixedStepMeters)); Writer->WriteValue(TEXT("max_distance_voxels"), static_cast(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(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(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(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(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(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(Report.SampledMinX)); Writer->WriteValue(TEXT("sampled_max_x_exclusive"), static_cast(Report.SampledMaxX)); Writer->WriteValue(TEXT("sampled_min_y"), static_cast(Report.SampledMinY)); Writer->WriteValue(TEXT("sampled_max_y_exclusive"), static_cast(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(Report.DistanceTravelledMeters)); Writer->WriteValue(TEXT("straight_line_m"), static_cast(Report.StraightLineMeters)); Writer->WriteValue(TEXT("tortuosity"), static_cast(Report.Tortuosity)); Writer->WriteValue(TEXT("dead_ends_encountered"), Report.DeadEndsEncountered); Writer->WriteValue(TEXT("dead_ends_per_100m"), static_cast(Report.DeadEndsPer100m)); Writer->WriteValue(TEXT("capsule_radius_m"), static_cast(Report.CapsuleRadiusMeters)); Writer->WriteValue(TEXT("capsule_width_m"), static_cast(Report.CapsuleWidthMeters)); Writer->WriteValue(TEXT("capsule_height_m"), static_cast(Report.CapsuleHeightMeters)); Writer->WriteValue(TEXT("narrow_gap_threshold_m"), static_cast(Report.NarrowGapThresholdMeters)); Writer->WriteValue(TEXT("narrow_gap_traversals"), Report.NarrowGapTraversals); Writer->WriteValue(TEXT("narrow_gap_fraction"), static_cast(Report.NarrowGapFraction)); Writer->WriteValue(TEXT("narrow_gap_events_per_100m"), static_cast(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(Report.MouthComponentGapVoxels)); Writer->WriteValue(TEXT("mouth_component_gap_m"), static_cast(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(Report.AgentToTargetComponentGapVoxels)); Writer->WriteValue(TEXT("agent_to_target_component_gap_m"), static_cast(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(OriginReport.ReachableSetToOriginColumnVoxels)); Writer->WriteValue(TEXT("reachable_set_to_origin_column_m"), static_cast(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(OriginCheckReport.SampledMinX)); Writer->WriteValue(TEXT("sampled_max_x_exclusive"), static_cast(OriginCheckReport.SampledMaxX)); Writer->WriteValue(TEXT("sampled_min_y"), static_cast(OriginCheckReport.SampledMinY)); Writer->WriteValue(TEXT("sampled_max_y_exclusive"), static_cast(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(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(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(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(OriginCheckReport.ReachableSetToOriginColumnVoxels)); Writer->WriteValue(TEXT("reachable_set_to_origin_column_m"), static_cast(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(Output.Profile.SampleInterval)); Writer->WriteValue(TEXT("timer_pair_cycles"), static_cast(Output.Profile.TimerPairCycles)); Writer->WriteValue(TEXT("timer_pair_microseconds"), static_cast(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(Index)), static_cast(Output.Profile.Counters[Index])); } Writer->WriteObjectEnd(); const VoxelDensityProfile::FSnapshot::FClassifierStats& Classifier = Output.Profile.Classifier; Writer->WriteObjectStart(TEXT("classifier")); Writer->WriteValue(TEXT("calls"), static_cast(Classifier.Calls)); Writer->WriteValue(TEXT("sampled_calls"), static_cast(Classifier.SampledCalls)); Writer->WriteValue(TEXT("sample_fraction"), Classifier.Calls > 0 ? static_cast(Classifier.SampledCalls) / static_cast(Classifier.Calls) : 0.0); Writer->WriteValue(TEXT("refine_nodes"), static_cast(Classifier.RefineNodes)); Writer->WriteValue(TEXT("interval_stack_box_calls"), static_cast(Classifier.StackBoxCalls)); Writer->WriteValue(TEXT("whole_mixed_nodes"), static_cast(Classifier.WholeMixedNodes)); Writer->WriteValue(TEXT("whole_solid_nodes"), static_cast(Classifier.WholeSolidNodes)); Writer->WriteValue(TEXT("whole_air_nodes"), static_cast(Classifier.WholeAirNodes)); Writer->WriteValue(TEXT("needs_final_field_nodes"), static_cast(Classifier.NeedsFinalFieldNodes)); Writer->WriteValue(TEXT("split_nodes"), static_cast(Classifier.SplitNodes)); Writer->WriteValue(TEXT("max_refinement_depth"), static_cast(Classifier.MaxRefinementDepth)); Writer->WriteValue(TEXT("exact_core_samples"), static_cast(Classifier.ExactCoreSamples)); Writer->WriteValue(TEXT("exact_final_samples"), static_cast(Classifier.ExactFinalSamples)); Writer->WriteValue(TEXT("exact_core_cache_hits"), static_cast(Classifier.ExactCoreCacheHits)); Writer->WriteValue(TEXT("exact_final_cache_hits"), static_cast(Classifier.ExactFinalCacheHits)); Writer->WriteValue(TEXT("exact_core_leaves"), static_cast(Classifier.ExactCoreLeaves)); Writer->WriteValue(TEXT("exact_final_leaves"), static_cast(Classifier.ExactFinalLeaves)); Writer->WriteValue(TEXT("interval_stack_box_us"), static_cast(Classifier.StackBoxCycles) * CycleToMicroseconds); Writer->WriteValue(TEXT("exact_core_us"), static_cast(Classifier.ExactCoreCycles) * CycleToMicroseconds); Writer->WriteValue(TEXT("exact_final_us"), static_cast(Classifier.ExactFinalCycles) * CycleToMicroseconds); Writer->WriteValue(TEXT("room_tail_queries"), static_cast(Classifier.RoomTailQueries)); Writer->WriteValue(TEXT("room_tail_evaluated"), static_cast(Classifier.RoomTailEvaluated)); Writer->WriteValue(TEXT("room_tail_us"), static_cast(Classifier.RoomTailCycles) * CycleToMicroseconds); Writer->WriteValue(TEXT("room_propagate_us"), static_cast(Classifier.RoomPropagateCycles) * CycleToMicroseconds); Writer->WriteValue(TEXT("room_exact_primitive_us"), static_cast(Classifier.RoomExactPrimitiveCycles) * CycleToMicroseconds); Writer->WriteValue(TEXT("room_cache_window_us"), static_cast(Classifier.RoomCacheWindowCycles) * CycleToMicroseconds); Writer->WriteValue(TEXT("room_count"), static_cast(Classifier.RoomNumRooms)); Writer->WriteValue(TEXT("tunnel_count"), static_cast(Classifier.RoomNumTunnels)); Writer->WriteValue(TEXT("room_floor_join_count"), static_cast(Classifier.RoomNumRoomFloorJoins)); Writer->WriteValue(TEXT("pit_count"), static_cast(Classifier.RoomNumPits)); Writer->WriteValue(TEXT("chimney_count"), static_cast(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(Breakdown.SlotStorageBytes)); Writer->WriteValue(TEXT("op_stack_bytes"), static_cast(Breakdown.OpStackBytes)); Writer->WriteValue(TEXT("rooms_bytes"), static_cast(Breakdown.RoomsBytes)); Writer->WriteValue(TEXT("room_floor_joins_bytes"), static_cast(Breakdown.RoomFloorJoinsBytes)); Writer->WriteValue(TEXT("tunnels_bytes_including_control_points"), static_cast(Breakdown.TunnelsBytes)); Writer->WriteValue(TEXT("pits_bytes"), static_cast(Breakdown.PitsBytes)); Writer->WriteValue(TEXT("chimneys_bytes"), static_cast(Breakdown.ChimneysBytes)); Writer->WriteValue(TEXT("columns_bytes"), static_cast(Breakdown.ColumnsBytes)); Writer->WriteValue(TEXT("support_column_entries_bytes"), static_cast(Breakdown.SupportColumnEntriesBytes)); Writer->WriteValue(TEXT("support_columns_bytes"), static_cast(Breakdown.SupportColumnsBytes)); Writer->WriteValue(TEXT("support_column_intervals_bytes"), static_cast(Breakdown.SupportColumnIntervalsBytes)); Writer->WriteValue(TEXT("dynamic_bytes"), static_cast(Breakdown.DynamicBytes())); Writer->WriteValue(TEXT("total_bytes"), static_cast(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(Index))); Writer->WriteValue(TEXT("calls"), static_cast(Output.Profile.Calls[Index])); Writer->WriteValue(TEXT("samples"), static_cast(Output.Profile.Samples[Index])); Writer->WriteValue(TEXT("worker_count"), static_cast(Output.Profile.ActiveWorkers[Index])); Writer->WriteValue(TEXT("cpu_us"), static_cast(Output.Profile.Cycles[Index]) * CycleToMicroseconds); Writer->WriteValue(TEXT("wall_us"), static_cast(Output.Profile.WallCycles[Index]) * CycleToMicroseconds); Writer->WriteValue(TEXT("total_us"), static_cast(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(Phase)]; } const uint64 DensityParentCycles = Output.Profile.WallCycles[ static_cast(VoxelDensityProfile::EBucket::GetDensityAt)]; const int64 DensityRawGap = static_cast(DensityParentCycles) - static_cast(DensityPhaseCycles); Writer->WriteObjectStart(TEXT("density_ledger")); Writer->WriteValue(TEXT("parent_us"), static_cast(DensityParentCycles) * CycleToMicroseconds); Writer->WriteValue(TEXT("raw_phase_sum_us"), static_cast(DensityPhaseCycles) * CycleToMicroseconds); Writer->WriteValue(TEXT("unattributed_us"), static_cast(DensityRawGap) * CycleToMicroseconds); Writer->WriteValue(TEXT("ledger_sum_us"), static_cast(DensityParentCycles) * CycleToMicroseconds); Writer->WriteValue(TEXT("raw_phase_sum_matches_parent"), FMath::Abs(static_cast(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(Diagnostic.ActualDensityDeltaMin)); const double ActualFill = FMath::Max( 0.0, static_cast(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(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(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& StackSolidKillerCounts, const TArray& StackAirKillerCounts, int32 StackVerdictMismatches, const TArray& Aggregates, FString& OutError) { FString Json; TSharedRef Writer = TJsonWriterFactory>::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(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(Aggregate.Blocks)); Writer->WriteValue(TEXT("sample_points"), static_cast(Aggregate.Samples)); Writer->WriteValue(TEXT("forced_boxes"), static_cast(Aggregate.ForcedBoxes)); Writer->WriteValue(TEXT("forced_all_solid_boxes"), static_cast(Aggregate.ForcedSolidBoxes)); Writer->WriteValue(TEXT("forced_all_air_boxes"), static_cast(Aggregate.ForcedAirBoxes)); Writer->WriteValue(TEXT("forced_margin_supremum"), static_cast(Aggregate.MaxForcedMargin)); Writer->WriteObjectStart(TEXT("effect_counts")); Writer->WriteValue(TEXT("Identity"), static_cast(Aggregate.EffectCounts[0])); Writer->WriteValue(TEXT("CarveOnly"), static_cast(Aggregate.EffectCounts[1])); Writer->WriteValue(TEXT("FillOnly"), static_cast(Aggregate.EffectCounts[2])); Writer->WriteValue(TEXT("Both"), static_cast(Aggregate.EffectCounts[3])); Writer->WriteObjectEnd(); Writer->WriteValue(TEXT("carve_bound_known"), Aggregate.bCarveBoundKnown); Writer->WriteValue(TEXT("unknown_carve_bound_boxes"), static_cast(Aggregate.UnknownCarveBoundBoxes)); Writer->WriteValue(TEXT("carve_bound_supremum"), static_cast( Aggregate.bCarveBoundKnown ? Aggregate.MaxCarveSupremum : 0.0f)); Writer->WriteValue(TEXT("fill_bound_known"), Aggregate.bFillBoundKnown); Writer->WriteValue(TEXT("unknown_fill_bound_boxes"), static_cast(Aggregate.UnknownFillBoundBoxes)); Writer->WriteValue(TEXT("fill_bound_supremum"), static_cast( Aggregate.bFillBoundKnown ? Aggregate.MaxFillSupremum : 0.0f)); Writer->WriteValue(TEXT("actual_density_delta_min"), static_cast( Aggregate.bHasDensityActual ? Aggregate.ActualDensityDeltaMin : 0.0f)); Writer->WriteValue(TEXT("actual_density_delta_max"), static_cast( Aggregate.bHasDensityActual ? Aggregate.ActualDensityDeltaMax : 0.0f)); Writer->WriteValue(TEXT("actual_carve_supremum"), Aggregate.bHasDensityActual ? static_cast(FMath::Max(0.0f, -Aggregate.ActualDensityDeltaMin)) : 0.0); Writer->WriteValue(TEXT("actual_fill_supremum"), Aggregate.bHasDensityActual ? static_cast(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(Aggregate.CarveBoundViolations)); Writer->WriteValue(TEXT("fill_bound_violations"), static_cast(Aggregate.FillBoundViolations)); Writer->WriteValue(TEXT("sdf_bound_known"), Aggregate.bSdfBoundKnown); Writer->WriteValue(TEXT("sdf_bound_min"), static_cast( Aggregate.bHasSdfBound ? Aggregate.SdfBoundMin : 0.0f)); Writer->WriteValue(TEXT("sdf_bound_max"), static_cast( Aggregate.bHasSdfBound ? Aggregate.SdfBoundMax : 0.0f)); Writer->WriteValue(TEXT("actual_sdf_min"), static_cast( Aggregate.bHasSdfActual ? Aggregate.ActualSdfMin : 0.0f)); Writer->WriteValue(TEXT("actual_sdf_max"), static_cast( Aggregate.bHasSdfActual ? Aggregate.ActualSdfMax : 0.0f)); Writer->WriteValue(TEXT("sdf_bound_violations"), static_cast(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(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 StackSolidKillerCounts; TArray 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 Aggregates; TArray 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(Arguments.Seed) ^ (static_cast(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(BaseHash % 14u); const int32 BY = 1 + static_cast(VoxelHash::Mix(BaseHash ^ 0xA341316Cu) % 14u); const int32 BZ = 1 + static_cast(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(AllSolidBlocks + AllAirBlocks) / BlocksTested : 0.0, RandomBlocksTested, *FPaths::Combine(Arguments.OutDirectory, TEXT("op_bounds.json"))); return true; } TSharedPtr StandaloneSerializeStruct( const UStruct* Type, const void* Data, TSet& ActiveObjects, const UObject* Object = nullptr); TSharedPtr StandaloneSerializeProperty( FProperty* Property, const void* Data, TSet& ActiveObjects) { if (Property == nullptr || Data == nullptr) return MakeShared(); if (const FBoolProperty* Bool = CastField(Property)) { return MakeShared(Bool->GetPropertyValue(Data)); } if (const FEnumProperty* Enum = CastField(Property)) { return MakeShared(static_cast( Enum->GetUnderlyingProperty()->GetSignedIntPropertyValue(Data))); } if (const FNumericProperty* Numeric = CastField(Property)) { const double Value = Numeric->IsFloatingPoint() ? Numeric->GetFloatingPointPropertyValue(Data) : static_cast(Numeric->GetSignedIntPropertyValue(Data)); return MakeShared(Value); } if (const FStrProperty* String = CastField(Property)) { return MakeShared(String->GetPropertyValue(Data)); } if (const FNameProperty* Name = CastField(Property)) { return MakeShared(Name->GetPropertyValue(Data).ToString()); } if (const FTextProperty* Text = CastField(Property)) { return MakeShared(Text->GetPropertyValue(Data).ToString()); } if (const FSoftObjectProperty* Soft = CastField(Property)) { return MakeShared( Soft->GetPropertyValue(Data).ToSoftObjectPath().ToString()); } if (const FArrayProperty* Array = CastField(Property)) { TArray> 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(MoveTemp(Values)); } if (const FSetProperty* Set = CastField(Property)) { TArray> Values; TArray>> 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(MoveTemp(Values)); } if (const FMapProperty* Map = CastField(Property)) { TArray> Values; TArray>> OrderedValues; FScriptMapHelper Helper(Map, Data); for (int32 Index = 0; Index < Helper.GetMaxIndex(); ++Index) { if (!Helper.IsValidIndex(Index)) continue; TSharedRef Pair = MakeShared(); 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(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(MoveTemp(Values)); } if (const FStructProperty* Struct = CastField(Property)) { return StandaloneSerializeStruct(Struct->Struct, Data, ActiveObjects); } if (const FObjectPropertyBase* ObjectProperty = CastField(Property)) { const UObject* Object = ObjectProperty->GetObjectPropertyValue(Data); if (!Object) return MakeShared(); if (Object->GetClass()->GetName().StartsWith(TEXT("Voxel"), ESearchCase::CaseSensitive)) { return StandaloneSerializeStruct(Object->GetClass(), Object, ActiveObjects, Object); } return MakeShared(Object->GetPathName()); } return MakeShared(); } TSharedPtr StandaloneSerializeStruct( const UStruct* Type, const void* Data, TSet& ActiveObjects, const UObject* Object) { if (Type == nullptr || Data == nullptr) return MakeShared(); if (Object && ActiveObjects.Contains(Object)) { return MakeShared(Object->GetPathName()); } if (Object) ActiveObjects.Add(Object); TSharedRef Result = MakeShared(); for (TFieldIterator It(Type); It; ++It) { FProperty* Property = *It; Result->SetField(Property->GetName(), StandaloneSerializeProperty( Property, Property->ContainerPtrToValuePtr(const_cast(Data)), ActiveObjects)); } if (Object) ActiveObjects.Remove(Object); return MakeShared(Result); } template void StandaloneWriteJsonValue( TWriter& Writer, const TCHAR* Identifier, const TSharedPtr& 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& Element : Value->AsArray()) { StandaloneWriteJsonValue(Writer, nullptr, Element); } Writer.WriteArrayEnd(); } else if (Value->Type == EJson::Object) { Writer.WriteObjectStart(); TArray Keys; Value->AsObject()->Values.GetKeys(Keys); Keys.Sort(); for (const FString& Key : Keys) { const TSharedPtr* Field = Value->AsObject()->Values.Find(Key); if (Field != nullptr) { StandaloneWriteJsonValue(Writer, *Key, *Field); } } Writer.WriteObjectEnd(); } else { Writer.WriteNull(); } } TSharedRef StandaloneVectorJson(const FVector& Value) { TSharedRef Result = MakeShared(); Result->SetNumberField(TEXT("x"), Value.X); Result->SetNumberField(TEXT("y"), Value.Y); Result->SetNumberField(TEXT("z"), Value.Z); return Result; } TSharedRef StandaloneLandingJson(const FVoxelPassageLanding& Landing) { TSharedRef Result = MakeShared(); 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 StandalonePassageJson(const FVoxelPassage& Passage) { TSharedRef Result = MakeShared(); 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(Passage.PassageType)); auto WriteVectors = [](const TCHAR* Key, const TArray& Source, TSharedRef Out) { TArray> Values; Values.Reserve(Source.Num()); for (const FVector& Value : Source) { Values.Add(MakeShared(StandaloneVectorJson(Value))); } Out->SetArrayField(Key, MoveTemp(Values)); }; auto WriteFloats = [](const TCHAR* Key, const TArray& Source, TSharedRef Out) { TArray> Values; Values.Reserve(Source.Num()); for (float Value : Source) Values.Add(MakeShared(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(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(WorkerLimit, 1, std::min(16, std::max(1, Count))); const std::size_t Grain = std::max(1, BatchSize); std::atomic Next{0}; std::vector WorkerSeconds(WorkerCount, 0.0); std::vector 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( 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 Hashes{}; }; void StandaloneCoreHashWord(std::uint64_t& Hash, std::uint64_t Word, int32 ByteCount) { for (int32 Byte = 0; Byte < ByteCount; ++Byte) { Hash ^= static_cast(Word >> (Byte * 8)); Hash *= 1099511628211ull; } } std::array StandaloneCoreAggregateHashes( std::vector& 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 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(Row.Key.lod), 4); StandaloneCoreHashWord(Hash, static_cast(Row.Key.coordinate.x), 4); StandaloneCoreHashWord(Hash, static_cast(Row.Key.coordinate.y), 4); StandaloneCoreHashWord(Hash, static_cast(Row.Key.coordinate.z), 4); StandaloneCoreHashWord(Hash, Row.Hashes[HashIndex], 8); } } return Aggregates; } bool AuditCoreClipmapCoverage( const std::vector& 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 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(Key.coordinate.x) * Extent; Box.Min[1] = static_cast(Key.coordinate.y) * Extent; Box.Min[2] = static_cast(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(A), static_cast(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(Tile.Max[Axis]) >= FocusPoint[Axis] - FocusRadiusVoxels && static_cast(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(0, FMath::Min(Tile.Max[UAxis], Neighbour.Max[UAxis]) - FMath::Max(Tile.Min[UAxis], Neighbour.Min[UAxis])); const std::int64_t OverlapV = FMath::Max(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(CoveredArea), static_cast(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(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(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 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::max(); const std::int32_t IntMin = std::numeric_limits::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(WorkerLimit), UTF8_TO_TCHAR(CoreError.c_str())); return false; } VoxelForgeCore::TileGenerationOptions TileOptions; TileOptions.hashDensity = true; std::vector 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 Actual = StandaloneCoreAggregateHashes(Rows); const std::array Expected{ 0xdb2991c73215af4cull, 0x8236c5edd7fc8e4full, 0x4c48fa75a543d58eull, 0xbea22e76b34a4dfcull}; const VoxelForgeCore::ShapeBuildStats Stats = CoreWorld.buildStats(); const bool bPassageWalkabilityPassed = Stats.walkSamples == 5642 && Stats.walkFailures == 0; if (Rows.size() != 2226 || 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=5642/5642"), static_cast(WorkerLimit), static_cast(Rows.size()), static_cast(Actual[0]), static_cast(Actual[1]), static_cast(Actual[2]), static_cast(Actual[3]), static_cast(Stats.walkSamples - Stats.walkFailures), static_cast(Stats.walkSamples)); return false; } UE_LOG(LogTemp, Display, TEXT("[VoxelForgeCoreAdapter] pass schema=1 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=5642/5642"), static_cast(WorkerLimit), OwnerClipRadius, OwnerMaxLod, static_cast(Rows.size()), static_cast(Actual[0]), static_cast(Actual[1]), static_cast(Actual[2]), static_cast(Actual[3])); } return true; } bool RunStandaloneWorldExport(const FExploreArguments& Arguments, FString& OutError, FString* OutJson) { UVoxelSettings* AuthoredSettings = LoadObject( nullptr, *Arguments.SettingsReference); if (!AuthoredSettings) { OutError = FString::Printf(TEXT("Could not load owner settings asset '%s'."), *Arguments.SettingsReference); return false; } UVoxelStrateManager* Manager = NewObject( 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(AuthoredSettings, Seed)) { OutError = TEXT("Owner settings did not produce a valid resolved strate layout."); return false; } Seed = Manager->GetWorldSeed(); UVoxelGenerator* Generator = NewObject( GetTransientPackage(), NAME_None, RF_Transient); Generator->InitializeSettings(AuthoredSettings); Generator->SetStrateManager(Manager); TArray 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(Slot.TopChunkZ + 1) * CHUNK_SIZE; const float BottomZ = static_cast(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(SampleRandom.RandRange(-4096, 4096)); const float Y = static_cast(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(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(X), static_cast(Y), Z) >= 0.0f && Generator->GetDensityAt(static_cast(X), static_cast(Y), Z + 8.0f) >= 0.0f && Generator->GetDensityAt(static_cast(X), static_cast(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(Sample.Position.X), static_cast(Sample.Position.Y), static_cast(Sample.Position.Z)); } if (!bCameraFound) { OutError = TEXT("Could not locate open camera space in the owner's first strate."); return false; } FString Json; TSharedRef Writer = TJsonWriterFactory>::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"), AuthoredSettings->bOpenSurfaceEntry); Writer->WriteValue(TEXT("origin_spine_radius"), OriginSpineRadius); Writer->WriteValue(TEXT("world_radius_voxels"), WorldRadiusVoxels); Writer->WriteValue(TEXT("edge_seal_thickness"), AuthoredSettings->EdgeSealThickness); Writer->WriteValue(TEXT("total_strates"), Manager->GetNumStrates()); Writer->WriteValue(TEXT("inter_strate_gap_chunks"), AuthoredSettings->InterStrateGapChunks); Writer->WriteValue(TEXT("clip_radius"), AuthoredSettings->ClipRadius); Writer->WriteValue(TEXT("max_clip_level"), AuthoredSettings->MaxClipLevel); Writer->WriteValue(TEXT("full_res_clip_levels"), AuthoredSettings->FullResClipLevels); Writer->WriteValue(TEXT("coarse_tile_cells"), AuthoredSettings->CoarseTileCells); Writer->WriteValue(TEXT("render_distance_chunks"), AuthoredSettings->RenderDistanceChunks); Writer->WriteValue(TEXT("generate_skirts"), AuthoredSettings->bGenerateSkirts); Writer->WriteValue(TEXT("skirt_cells"), AuthoredSettings->SkirtCells); Writer->WriteValue(TEXT("lod_octave_drop"), AuthoredSettings->LODOctaveDrop); Writer->WriteValue(TEXT("strate_content_cut_min_level"), AuthoredSettings->GetEffectiveStrateContentCutMinLevel()); TSet ActiveObjects; StandaloneWriteJsonValue(*Writer, TEXT("settings_asset"), StandaloneSerializeStruct(AuthoredSettings->GetClass(), AuthoredSettings, ActiveObjects, AuthoredSettings)); 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(StandalonePassageJson(Passage))); } Writer->WriteArrayEnd(); Writer->WriteArrayStart(TEXT("density_samples")); for (const FStandaloneExportSample& Sample : Samples) { Writer->WriteObjectStart(); Writer->WriteValue(TEXT("x"), static_cast(Sample.Position.X)); Writer->WriteValue(TEXT("y"), static_cast(Sample.Position.Y)); Writer->WriteValue(TEXT("z"), static_cast(Sample.Position.Z)); Writer->WriteValue(TEXT("density"), static_cast(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(Camera.X)); Writer->WriteValue(TEXT("y"), static_cast(Camera.Y)); Writer->WriteValue(TEXT("z"), static_cast(Camera.Z)); Writer->WriteObjectEnd(); Writer->WriteObjectEnd(); Writer->Close(); 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 (!VerifyStandaloneCoreAdapter(Json, OutError)) { UE_LOG(LogTemp, Error, TEXT("[VoxelForgeCoreAdapter] %s"), *OutError); return false; } 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& 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 Manifest = VoxelForgeCore::WorldAssemblyManifest::FromJson( std::string_view(SourceUtf8.Get(), static_cast(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(Groups[Index].GroundX) * Scale, static_cast(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(MinX) * Scale, static_cast(MinY) * Scale, GroupGroundSubvoxels[GroupIndex] + static_cast(BottomOffset) * Scale}; Feature.maximumSubvoxels = { static_cast(MaxX) * Scale, static_cast(MaxY) * Scale, GroupGroundSubvoxels[GroupIndex] + static_cast(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(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(GroupGroundSubvoxels[0]) / Scale; const double DoorGround = static_cast(GroupGroundSubvoxels[1]) / Scale; const double RiserGround = static_cast(GroupGroundSubvoxels[2]) / Scale; const double LedgeGround = static_cast(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 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::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(EvaluateHybridAnalyticFixture(Fixture, Point, Phase)); } TArray 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(std::llround(Low * 256.0)); Box.MaximumSubvoxels[Axis] = static_cast(std::llround(High * 256.0)); } SharpFeatures.Add(Box); } } UVoxelMarchingCubesMesher* Mesher = NewObject( 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; using FHybridContourSet = std::set; 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(std::llround(A.Y * 100.0)); int64 AZ = static_cast(std::llround(A.Z * 100.0)); int64 BY = static_cast(std::llround(B.Y * 100.0)); int64 BZ = static_cast(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(Segment[0]) / 100.0; const double Z0 = static_cast(Segment[1]) / 100.0; const double Y1 = static_cast(Segment[2]) / 100.0; const double Z1 = static_cast(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(std::llround((Y0 + T0 * DY) * 100.0)); const int64 CZ0 = static_cast(std::llround((Z0 + T0 * DZ) * 100.0)); const int64 CY1 = static_cast(std::llround((Y0 + T1 * DY) * 100.0)); const int64 CZ1 = static_cast(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(Segment[0]) / 100.0; const double AZ = static_cast(Segment[1]) / 100.0; const double BY = static_cast(Segment[2]) / 100.0; const double BZ = static_cast(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::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(Segment[Endpoint * 2]) / 100.0; const double Z = static_cast(Segment[Endpoint * 2 + 1]) / 100.0; double Nearest = std::numeric_limits::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 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 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(McLeftBorder.size()), static_cast(McRightBorder.size()), static_cast(HybridLeftBorder.size()), static_cast(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(McLeftBorder.size()), static_cast(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::infinity(); double MaxY = -std::numeric_limits::infinity(); double MinZ = std::numeric_limits::infinity(); double MaxZ = -std::numeric_limits::infinity(); for (const FHybridContourSegment& Segment : Contour) { MinY = FMath::Min(MinY, static_cast(FMath::Min(Segment[0], Segment[2])) / 100.0); MaxY = FMath::Max(MaxY, static_cast(FMath::Max(Segment[0], Segment[2])) / 100.0); MinZ = FMath::Min(MinZ, static_cast(FMath::Min(Segment[1], Segment[3])) / 100.0); MaxZ = FMath::Max(MaxZ, static_cast(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(FineContour.size()), *ContourBoundsText(FineContour), static_cast(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& OutSettings, TArray>& 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( SourceSettings, GetTransientPackage(), NAME_None); if (SettingsCopy == nullptr) { OutError = TEXT("Could not duplicate owner settings for the transient Core test."); return false; } OutSettings = TStrongObjectPtr(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> DefinitionReferences; DefinitionReferences.Reserve(SourceManager->GetLayout().Num()); TMap 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( 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(DefinitionCopy)); DefinitionReferences.Add(TStrongObjectPtr(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(TargetSlot->TopChunkZ) + 1.0f) * CHUNK_SIZE; const float BottomVoxelZ = static_cast(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 VerificationManager(NewObject( 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(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& OutSettings, TStrongObjectPtr& OutSurfaceDefinition, FString& OutError) { 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( nullptr, TEXT("/Game/VoxelForge/DA_Strate1.DA_Strate1")); if (SurfaceDefinition == nullptr || SurfaceDefinition->GeneratorType != ECaveGeneratorType::SurfaceWorld || SurfaceDefinition->StrateHeightInChunks <= 0 || Source->TotalStrates != 1) { OutError = FString::Printf( TEXT("The staged owner assets do not describe the logged single SurfaceWorld (definition_loaded=%d type=%d height=%d settings_strates=%d)."), SurfaceDefinition != nullptr ? 1 : 0, SurfaceDefinition != nullptr ? static_cast(SurfaceDefinition->GeneratorType) : INDEX_NONE, SurfaceDefinition != nullptr ? SurfaceDefinition->StrateHeightInChunks : INDEX_NONE, Source->TotalStrates); return false; } UVoxelSettings* SettingsCopy = DuplicateObject( Source, GetTransientPackage(), NAME_None); if (SettingsCopy == nullptr) { OutError = TEXT("Could not duplicate owner settings for the logged SurfaceWorld layout."); return false; } OutSettings = TStrongObjectPtr(SettingsCopy); OutSurfaceDefinition = TStrongObjectPtr(SurfaceDefinition); SettingsCopy->Seed = Seed; SettingsCopy->TotalStrates = Source->TotalStrates; SettingsCopy->StratePool.Reset(); SettingsCopy->FixedStrates.Reset(); SettingsCopy->FixedStrates.Add(0, TSoftObjectPtr(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 seed=%d strata=1 bounds=[0,%d] height_chunks=%d gap=%d imported_override=cleared"), Seed, BottomChunkZ, SurfaceDefinition->StrateHeightInChunks, SettingsCopy->InterStrateGapChunks); return true; } bool RunCorePlanOnlyTest(const FExploreArguments& Arguments, FString& OutError) { UVoxelSettings* AuthoredSettings = LoadObject( 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 OwnerSurfaceSettings; TStrongObjectPtr OwnerSurfaceDefinition; if (!CreateLoggedOwnerSurfaceSettingsCopy(AuthoredSettings, AuthoredSeed, OwnerSurfaceSettings, OwnerSurfaceDefinition, OutError)) { return false; } TStrongObjectPtr SourceManager(NewObject( 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 SettingsCopy; TArray> 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( DuplicateObject(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 PlanManager(NewObject( 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 Snapshot = VoxelForgeCore::ResolvedWorldSnapshot::FromJson( std::string_view(Utf8.Get(), static_cast(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(ExpectedSurfaceCount) || Assembly->floatingIslands.size() != static_cast(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(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(Slot.Definition->GeneratorType) : INDEX_NONE, Slot.HeightInChunks); } return true; } bool RunCoreFallbackActorTest(const FExploreArguments& Arguments, FString& OutError) { UVoxelSettings* AuthoredSettings = LoadObject( 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 TestSettings; TStrongObjectPtr 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::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(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 OwnerSurfaceSettings; TStrongObjectPtr OwnerSurfaceDefinition; if (!CreateLoggedOwnerSurfaceSettingsCopy(Settings, Seed, OwnerSurfaceSettings, OwnerSurfaceDefinition, OutError)) { return false; } TStrongObjectPtr ReferenceManager(NewObject( 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( 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::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& 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(World.configuration().voxelSizeMicrons)/10000.0; using FXYKey=std::pair; struct FGroundVertices { std::map> 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::llround( Vertex.position.x/VoxelSizeCm*SubvoxelsPerVoxel)); const auto Y=static_cast(std::llround( Vertex.position.y/VoxelSizeCm*SubvoxelsPerVoxel)); if(X%GridPeriod!=0||Y%GridPeriod!=0)continue; const auto Z=static_cast(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(32)<(Key.coordinate.x)*Extent; const std::int64_t Y=static_cast(Key.coordinate.y)*Extent; const std::int64_t Z=static_cast(Key.coordinate.z)*Extent; return std::array{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 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 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(Ground)/SubvoxelsPerVoxel; if(GroundVoxelBounds7[5] ||GroundVoxelBounds8[5])continue; const auto Nearest=[Ground](const std::vector& Values) ->std::optional { if(Values.empty())return std::nullopt; return *std::min_element(Values.begin(),Values.end(),[Ground](auto A,auto B) {return std::abs(A-Ground)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::llround( static_cast(RouteXSub)/GridPeriod))*GridPeriod; const std::int64_t YSub=static_cast(std::llround( static_cast(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(std::floor(static_cast(X)/Extent)), static_cast(std::floor(static_cast(Y)/Extent)), static_cast(std::floor(static_cast(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(Ground)/SubvoxelsPerVoxel; if(GroundVoxelBounds7[5] ||GroundVoxelBounds8[5])continue; const auto Nearest=[Ground](const std::vector& Values) ->std::optional { if(Values.empty())return std::nullopt; return *std::min_element(Values.begin(),Values.end(),[Ground](auto A,auto B) {return std::abs(A-Ground)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(XSub)/SubvoxelsPerVoxel; const double Y=static_cast(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(1,(Samples.size()+127)/128); for(std::size_t Index=0;Index(Sample.ProfileGround)/SubvoxelsPerVoxel,NearVoxel))continue; const double Far7Cm=static_cast(Sample.Far7)/SubvoxelsPerVoxel*VoxelSizeCm; const double Far8Cm=static_cast(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(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(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; using FXYLod=std::array; 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(WalkStart.positionSubvoxels[0])/SubvoxelsPerVoxel, static_cast(WalkStart.positionSubvoxels[1])/SubvoxelsPerVoxel, static_cast(WalkStart.positionSubvoxels[2])/SubvoxelsPerVoxel}; const std::vector WalkKeys=World.desiredTiles(Camera); std::set Desired; std::set FarXYTiles; double ClipMaxZ=-std::numeric_limits::infinity(); for(const auto& Key:WalkKeys) { Desired.insert(IdFor(Key)); const double Extent=static_cast(32<(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 CapBands; std::uint64_t ProfileGridSamples=0, ProfileSamplesWithoutRoof=0; std::set FarXYTilesWithoutRoof; for(const FXYLod& XY:FarXYTiles) { const std::int32_t Lod=XY[2]; const std::int64_t Extent=static_cast(32)<(XY[0])*Extent; const std::int64_t OriginY=static_cast(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(Band),Lod}); if(FarCap%(Extent*SubvoxelsPerVoxel)==0 &&Band>MIN_int32) CapBands.insert({XY[0],XY[1],static_cast(Band-1),Lod}); } } std::uint64_t RequestedCapBands=0, EmittedCapBands=0, RequestedWithoutTriangles=0; std::uint64_t CulledCapBands=0; std::map> 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::set CapAgreementSeen; std::vector CapAgreementSamples; CapAgreementSamples.reserve(128); const double VoxelSizeCm=static_cast(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(Mesh->diagnostics.horizonCapTriangles)*3u; for(std::size_t Index=CapIndexStart;Indexindices.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::llround( Vertex.position.x/VoxelSizeCm*SubvoxelsPerVoxel)); const std::int64_t YSub=static_cast(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(XSub)/SubvoxelsPerVoxel; const double Y=static_cast(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(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(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(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 AzimuthDegrees={0.0,45.0,90.0,135.0,180.0,225.0,270.0,315.0}; constexpr std::array ElevationDegrees={15.0,30.0,45.0,60.0,75.0,90.0}; constexpr double RayStepVoxels=128.0; double ClipMinX=std::numeric_limits::infinity(); double ClipMinY=std::numeric_limits::infinity(); double ClipMaxX=-std::numeric_limits::infinity(); double ClipMaxY=-std::numeric_limits::infinity(); for(const FXYLod& XY:FarXYTiles) { const double Extent=static_cast(32<(XY[0])*Extent); ClipMinY=std::min(ClipMinY,static_cast(XY[1])*Extent); ClipMaxX=std::max(ClipMaxX,(static_cast(XY[0])+1.0)*Extent); ClipMaxY=std::max(ClipMaxY,(static_cast(XY[1])+1.0)*Extent); } enum class ECapRayTileResult : std::uint8_t { EmittedHit, CulledByClipmap, RequestedWithoutTriangles, EmittedWithoutHit }; struct FCapRayTileGeometry { std::vector> 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,6> RayResultsByElevation{}; std::vector RayHitDistancesMeters; RayHitDistancesMeters.reserve(Plan->route.size()*AzimuthDegrees.size()*ElevationDegrees.size()); std::map 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+2indices.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& 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(DistanceMaximumDistance+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(32< BoundsMin={static_cast(Id[0])*Extent, static_cast(Id[1])*Extent,static_cast(Id[2])*Extent}; const std::array 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(RayOriginBoundsMax[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||HitDistanceroute.size();++RouteIndex) { const auto& RoutePoint=Plan->route[RouteIndex]; const double StartX=static_cast(RoutePoint.positionSubvoxels[0])/SubvoxelsPerVoxel; const double StartY=static_cast(RoutePoint.positionSubvoxels[1])/SubvoxelsPerVoxel; const double StartZ=static_cast(RoutePoint.positionSubvoxels[2])/SubvoxelsPerVoxel; const auto StartColumn=Field.point(RoutePoint.positionSubvoxels[0], RoutePoint.positionSubvoxels[1],0); const double StartCap=static_cast(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::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::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::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::llround(X*SubvoxelsPerVoxel)), static_cast(std::llround(Y*SubvoxelsPerVoxel))); const double Cap=static_cast(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::llround(HitX*SubvoxelsPerVoxel)), static_cast(std::llround(HitY*SubvoxelsPerVoxel)),0); const std::int64_t HitCapSubvoxels=Field.farSurfaceCapHeightSubvoxels( static_cast(std::llround(HitX*SubvoxelsPerVoxel)), static_cast(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::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(32)<(std::floor(ProfileHitX)),Extent); const std::int64_t TileY=FloorDiv64(static_cast(std::floor(ProfileHitY)),Extent); const std::int64_t TileZ=FloorDiv64(ProfileHitCapSubvoxels,Extent*SubvoxelsPerVoxel); const FTileId Id{static_cast(TileX),static_cast(TileY), static_cast(TileZ),Lod}; if(Desired.find(Id)==Desired.end())continue; RootRequestedMask|=1u<(Lod); const FCapRayTileGeometry* Geometry=GetCapRayTileGeometry(Id); if(Geometry!=nullptr) { RootDownwardTriangles+=Geometry->Triangles.size(); if(!Geometry->Triangles.empty()) RootEmittedMask|=1u<(Lod); } if(Lod>=5) { FarRequestedMask|=1u<(Lod-5); if(Geometry!=nullptr&&!Geometry->Triangles.empty()) FarEmittedMask|=1u<(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(RouteIndex),Elevation,Azimuth, bProfileCrossing?1:0,bProfileRoofAtCrossing?1:0, ProfileHitDistance,EdgeDistance,ProfileHitX,ProfileHitY,ProfileHitZ, RootRequestedMask,RootEmittedMask, static_cast(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(Plan->route.size()), static_cast(WalkKeys.size()), static_cast(FarXYTiles.size()), static_cast(CapBands.size()), static_cast(RequestedCapBands), static_cast(EmittedCapBands), static_cast(RequestedWithoutTriangles), static_cast(CulledCapBands), static_cast(ProfileGridSamples), static_cast(ProfileSamplesWithoutRoof), static_cast(FarXYTilesWithoutRoof.size()), Camera.z,static_cast(WalkColumn.groundHeightSubvoxels)/SubvoxelsPerVoxel, static_cast(WalkFarCap)/SubvoxelsPerVoxel); const double CoveragePercent=RayCount>0?100.0*static_cast(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(RayCount),static_cast(RayHits),CoveragePercent, static_cast(RayMissedClipEdge), static_cast(RayMissedNoRoof), static_cast(RayMissedCulledByClipmap), static_cast(RayMissedRequestedWithoutTriangles), static_cast(RayMissedEmittedWithoutHit), static_cast(RayStartedAboveCap), static_cast(RayProfileCrossings), static_cast(RayProfileRoofCrossings), static_cast(RayProfileRoofWithoutMeshHit), static_cast(RayTileGeometryCache.size()), static_cast(RayTriangleCount),HitDistanceP50Meters, HitDistanceP95Meters,HitDistanceMaximumMeters); for(std::size_t Index=0;Index(AzimuthDegrees.size()*Plan->route.size()), static_cast(RayResultsByElevation[Index][0]), static_cast(RayResultsByElevation[Index][1]), static_cast(RayResultsByElevation[Index][2]), static_cast(RayResultsByElevation[Index][3]), static_cast(RayResultsByElevation[Index][4]), static_cast(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(RayCount),static_cast(RayHits), static_cast(RayMissedClipEdge), static_cast(RayMissedNoRoof), static_cast(RayMissedCulledByClipmap), static_cast(RayMissedRequestedWithoutTriangles), static_cast(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(Counts[0]), static_cast(Counts[1]), static_cast(Counts[2]), static_cast(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& 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 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& Values) { return Values.IsEmpty() ? 0.0 : *std::max_element(Values.GetData(),Values.GetData()+Values.Num()); }; const std::int32_t TileVoxelCount=static_cast( 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 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(TileVoxelCount)<(Key.coordinate.x)*TileExtent; const std::int64_t OriginY=static_cast(Key.coordinate.y)*TileExtent; const std::int64_t OriginZ=static_cast(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::min(Plan.bottomChunkZ,Plan.topChunkZ))*TileVoxelCount; const std::int64_t PlanMaximumZ=(static_cast( 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 Minimum={ (OriginX-TileStep)*Subvoxels,(OriginY-TileStep)*Subvoxels, (OriginZ-TileStep)*Subvoxels}; const std::array Maximum={ (OriginX+TileExtent+TileStep)*Subvoxels, (OriginY+TileExtent+TileStep)*Subvoxels, (OriginZ+TileExtent+TileStep)*Subvoxels}; std::vector Queries; Queries.push_back({Field.recipe(),Field.plan(),Field.buildFeatureTileList(Minimum,Maximum)}); const VoxelForgeCore::ScopedWorldAssemblyFeatureQueriesV1 QueryScope{ std::span(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 Exact(static_cast(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(Y)*(Cells+1)+X]={ Sample.groundHeightSubvoxels,Sample.capHeightSubvoxels}; } const auto& BaselineStats=VoxelForgeCore::Heightfield2D::threadColumnCacheStats(); BaselineColumnMs.Add(BaselineStats.columnComputeMilliseconds); BaselineColumns+=BaselineStats.columnMisses; std::vector 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 Coarse(static_cast(Axis.size())*Axis.size()); VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true); for(std::size_t Y=0;Y(Upper-Axis.begin()); const std::size_t Low=High==0?0:High-1; return std::pair{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(X-Axis[X0])/(Axis[X1]-Axis[X0]); const double TY=Axis[Y1]==Axis[Y0]?0.0 :static_cast(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(C00.Ground), static_cast(C10.Ground),TX),Lerp(static_cast(C01.Ground), static_cast(C11.Ground),TX),TY); const double Cap=Lerp(Lerp(static_cast(C00.Cap), static_cast(C10.Cap),TX),Lerp(static_cast(C01.Cap), static_cast(C11.Cap),TX),TY); const auto& Reference=Exact[static_cast(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(BaselineColumns), static_cast(CandidateColumns), BaselineAverageMs>0.0?100.0*(1.0-CandidateAverageMs/BaselineAverageMs):0.0, BaselineAverageMs,CandidateAverageMs, P95(GroundErrorsCm),MaxValue(GroundErrorsCm), static_cast(GroundOver2Cm),GroundErrorsCm.Num(), P95(CapErrorsCm),MaxValue(CapErrorsCm), static_cast(CapOver2Cm),CapErrorsCm.Num(), static_cast(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 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(TileVoxelCount)<(Key.coordinate.x)*TileExtent; const std::int64_t OriginY=static_cast(Key.coordinate.y)*TileExtent; const std::int64_t OriginZ=static_cast(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::min(Plan.bottomChunkZ,Plan.topChunkZ))*TileVoxelCount; const std::int64_t PlanMaximumZ=(static_cast( 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 Minimum={ (OriginX-TileStep)*Subvoxels,(OriginY-TileStep)*Subvoxels, (OriginZ-TileStep)*Subvoxels}; const std::array Maximum={ (OriginX+TileExtent+TileStep)*Subvoxels, (OriginY+TileExtent+TileStep)*Subvoxels, (OriginZ+TileExtent+TileStep)*Subvoxels}; std::vector Queries; Queries.push_back({Field.recipe(),Field.plan(),Field.buildFeatureTileList(Minimum,Maximum)}); const VoxelForgeCore::ScopedWorldAssemblyFeatureQueriesV1 QueryScope{ std::span(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 Exact(static_cast(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(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(Sample.groundHeightSubvoxels) -Exact[static_cast(Y)*(Cells+1)+X].Ground) *CoreVoxelSizeCm/Subvoxels); CapErrorsCm.Add(std::abs(static_cast(Sample.capHeightSubvoxels) -Exact[static_cast(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(Drop), static_cast(BaselineColumns), static_cast(CandidateColumns), BaselineAverageMs>0.0?100.0*(1.0-CandidateAverageMs/BaselineAverageMs):0.0, BaselineAverageMs,CandidateAverageMs, P95(GroundErrorsCm),MaxValue(GroundErrorsCm), static_cast(GroundOver2Cm),GroundErrorsCm.Num(), P95(CapErrorsCm),MaxValue(CapErrorsCm), static_cast(CapOver2Cm),CapErrorsCm.Num(), static_cast(FiniteFeatureTiles)); } VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true,0); } for (const int32 LOD : {0,1,2,3,4,5,6,7,8}) { TArray FullTimes,CandidateTimes,FullShellTimes,CandidateShellTimes,ListTimes; TArray UnknownBoxes,FieldCalls,UnclampedFieldCalls,ColumnMisses,ColumnConflictMisses,StratumShellTimes,ConnectorShellTimes; TArray DensitySamples,ClassificationTimes,ClassificationExclusiveTimes; TArray ColumnComputeTimes,MeshingTimes,HorizonTimes,RepairSkipped; TArray ColumnEvalExclusiveTimes,MarchingExclusiveTimes,GroundRefineExclusiveTimes; TArray CapRefineExclusiveTimes,MeshAssemblyTimes; TArray MarchingColumnTimes,GroundColumnTimes,CapColumnTimes; TArray ClassificationNsPerSample,ColumnNsPerSample,MarchingNsPerSample; TArray GroundNsPerSample,CapNsPerSample,AssemblyNsPerSample,ListNsPerSample; TArray MarchingColumnNsPerSample,GroundColumnNsPerSample,CapColumnNsPerSample; TArray McProfileTotalTimes,McProfileGridTimes,McProfileGridExclusiveTimes; TArray McProfileCellTimes,McProfileNormalTimes,McProfileInterpolationTimes; TArray McProfileStreamTimes,McProfileOtherTimes,McProfileResidualTimes; TArray McProfileGradientFallbackCalls,McProfileEdgeMidpointCalls,McProfileGradientMemoHits; TArray McProfileGridNsPerSample,McProfileGridExclusiveNsPerSample; TArray McProfileCellNsPerSample,McProfileNormalNsPerSample; TArray McProfileInterpolationNsPerSample,McProfileStreamNsPerSample; TArray McProfileOtherNsPerSample,McProfileResidualNsPerSample; TArray 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(Candidate->densitySampleCount); DensitySamples.Add(static_cast(Candidate->densitySampleCount)); UnknownBoxes.Add(static_cast(Diagnostics.unknownBoxes)); FieldCalls.Add(static_cast(Diagnostics.fieldCalls)); UnclampedFieldCalls.Add(static_cast(Diagnostics.unclampedFieldCalls)); ColumnMisses.Add(static_cast(Diagnostics.columnCacheMisses)); ColumnConflictMisses.Add(static_cast(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(Diagnostics.farStratumFieldCalls)); ConnectorFieldCalls.Add(static_cast(Diagnostics.farConnectorFieldCalls)); ShellTriangles.Add(static_cast(Diagnostics.farShellTrianglesAdded)); GroundTriangles.Add(static_cast(Diagnostics.horizonGroundTriangles)); GroundRemoved.Add(static_cast(Diagnostics.horizonGroundTrianglesRemoved)); CapTriangles.Add(static_cast(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( Mc.marchingCubesGradientFallbackDensityCalls)); McProfileEdgeMidpointCalls.Add(static_cast( Mc.marchingCubesEdgeMidpointDensityCalls)); McProfileGradientMemoHits.Add(static_cast( Mc.marchingCubesGradientMemoHits)); const double ProfileSamples=static_cast(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 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( 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 (!ReadStagedPlayerSpeeds( StagedWalkSpeedCmPerSecond, StagedSprintSpeedCmPerSecond, OutError)) { return false; } int32 EffectiveSeed = ResolveCoreTestWorldSeed(AuthoredSettings); if (const UVoxelSeasonAsset* AuthoredSeason = AuthoredSettings->Season.LoadSynchronous()) EffectiveSeed = AuthoredSeason->GetSeasonSeed(); TStrongObjectPtr OwnerSurfaceSettings; TStrongObjectPtr OwnerSurfaceDefinition; if (!CreateLoggedOwnerSurfaceSettingsCopy(AuthoredSettings, EffectiveSeed, OwnerSurfaceSettings, OwnerSurfaceDefinition, OutError)) { return false; } TStrongObjectPtr SourceManager(NewObject( 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 TestSettings; TArray> 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* 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 Manager(NewObject( 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 Snapshot = VoxelForgeCore::ResolvedWorldSnapshot::FromJson( std::string_view(Utf8Runtime.Get(), static_cast(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(StartRoute.positionSubvoxels[0]) / SubvoxelsPerCoreVoxel, static_cast(StartRoute.positionSubvoxels[1]) / SubvoxelsPerCoreVoxel, static_cast(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(NativeAssembly.contentHash), static_cast(NativeIslandPlan.manifest.recipe.contentHash), static_cast(NativeIslandPlan.manifest.plan.contentHash), EffectiveSeed, IslandStrateIndex, IslandSlot->TopChunkZ, IslandSlot->BottomChunkZ, static_cast(VOXEL_SIZE), CoreVoxelSizeCm, static_cast(NativeIslandPlan.manifest.plan.islands.size()), static_cast(NativeIslandPlan.manifest.plan.clusterEdges.size()), static_cast(RouteAudit.segmentsTested), static_cast(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 ExpectedHashes; TSet ExpectedDesiredSet; std::vector Desired; std::array ExpectedAggregates{}; std::map 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 AggregateRows; AggregateRows.reserve(Desired.size()); for (const VoxelForgeCore::TileKey& Key : Desired) { const std::optional 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(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(Key.coordinate.x) * (32ll << Key.lod) * 256ll, static_cast(Key.coordinate.y) * (32ll << Key.lod) * 256ll, static_cast(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( GetTransientPackage(), NAME_None, RF_Transient); UVoxelMarchingCubesMesher* LegacyMesher = NewObject( 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(CoreVoxelSizeCm)); const auto P95Milliseconds = [](TArray& Samples) { Samples.Sort(); const int32 Index = FMath::Clamp( FMath::CeilToInt(static_cast(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 CoreMilliseconds; TArray CoreClassificationMilliseconds; TArray CoreColumnComputeMilliseconds; TArray CoreMeshingMilliseconds; TArray 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 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(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(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(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(ExpectedAggregates[0]), static_cast(ExpectedAggregates[1]), static_cast(ExpectedAggregates[2]), static_cast(ExpectedAggregates[3])); } const auto BuildWorkerHashAggregate = [&](uint32 WorkerCount, std::array& OutHashes) -> bool { FTCHARToUTF8 WorkerJson(*RuntimeJson); std::string WorkerError; std::optional WorkerSnapshot = VoxelForgeCore::ResolvedWorldSnapshot::FromJson( std::string_view(WorkerJson.Get(), static_cast(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 Rows; Rows.reserve(Desired.size()); for (const VoxelForgeCore::TileKey& Key : Desired) { const std::optional 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 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(ExpectedAggregates[0]), static_cast(ExpectedAggregates[1]), static_cast(ExpectedAggregates[2]), static_cast(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(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(GEngine, NAME_None, RF_Transient); APlayerController* PlayerController = TestWorld->SpawnActor( 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::StaticClass(), CameraLocalCm, FRotator::ZeroRotator); if (PlayerPawn == nullptr) { OutError = TEXT("Could not spawn the BuildHost island route player."); return false; } USceneComponent* PawnRoot = NewObject(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(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::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& 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 ActualDesiredSet; TSet ActualVisualSet; TArray 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 SupportSnapshot = VoxelForgeCore::ResolvedWorldSnapshot::FromJson( std::string_view(SupportJson.Get(), static_cast(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 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(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(Key.Level)}; const std::optional 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(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(Actual ? Actual->Density : 0ull), static_cast(Expected ? Expected->Density : 0ull), static_cast(Actual ? Actual->Mesh : 0ull), static_cast(Expected ? Expected->Mesh : 0ull), static_cast(Actual ? Actual->Payload : 0ull), static_cast(Expected ? Expected->Payload : 0ull), static_cast(Actual ? Actual->Topology : 0ull), static_cast(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(Position[0]) * CoreVoxelSizeCm / 256.0, static_cast(Position[1]) * CoreVoxelSizeCm / 256.0, static_cast(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(Connector.startSubvoxels[0] + Connector.endSubvoxels[0]) * 0.5) * CoreVoxelSizeCm / 256.0, (static_cast(Connector.startSubvoxels[1] + Connector.endSubvoxels[1]) * 0.5) * CoreVoxelSizeCm / 256.0, (static_cast(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(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(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( 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 (!ReadStagedPlayerSpeeds( StagedWalkSpeedCmPerSecond, StagedSprintSpeedCmPerSecond, OutError)) { return false; } const int32 OwnerSeed = ResolveCoreTestWorldSeed(AuthoredSettings); TStrongObjectPtr OwnerSurfaceSettings; TStrongObjectPtr OwnerSurfaceDefinition; if (!CreateLoggedOwnerSurfaceSettingsCopy(AuthoredSettings, OwnerSeed, OwnerSurfaceSettings, OwnerSurfaceDefinition, OutError)) { return false; } AuthoredSettings = OwnerSurfaceSettings.Get(); if (Arguments.bCoreHybridFixturesOnly) { UVoxelGenerator* FixtureGenerator = NewObject( 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 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(HybridFixtureContacts.size()), *HybridFixtureManifestHash); } TStrongObjectPtr Manager(NewObject( 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(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(GEngine, NAME_None, RF_Transient); APlayerController* PlayerController = TestWorld->SpawnActor( 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::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator); if (PlayerPawn == nullptr) { OutError = TEXT("Could not create the BuildHost stream-trace pawn."); return false; } USceneComponent* PawnRoot = NewObject(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(FVector::ZeroVector, 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::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; Actor->bCaptureVoxelForgeCoreHashesForValidation = true; 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); 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 SpawnTickMilliseconds; TArray WalkTickMilliseconds; TArray> 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& Sample : TickSamples) { if (bInitialSetReady && Sample.Key <= InitialSetReadySeconds) { SpawnTickMilliseconds.Add(Sample.Value); } else if (Sample.Key >= WalkStartSeconds) { WalkTickMilliseconds.Add(Sample.Value); } } auto PercentileMilliseconds = [](TArray Values, double Fraction) { if (Values.IsEmpty()) return 0.0; Values.Sort(); const int32 Index = FMath::Clamp( FMath::CeilToInt(Fraction * static_cast(Values.Num() - 1)), 0, Values.Num() - 1); return Values[Index]; }; const auto MaxMilliseconds = [](const TArray& 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 ExpectedHashes; TMap ExpectedPostEditHashes; TMap ExpectedTravelHashes; TMap ExpectedSurfaceTravelHashes; std::optional ExpectedHybridFixtureVisualMesh; TSet ExpectedInitialDesiredSet; TSet ExpectedTravelDesiredSet; TSet ExpectedSurfaceTravelDesiredSet; TSet ExpectedSurfaceCollisionSet; int32 ExpectedSurfaceCollisionBodyCount = 0; int32 ExpectedSurfaceCollisionNoBodyCount = 0; TArray ExpectedAffectedTiles; FVector CarveLocalPositionCm = FVector::ZeroVector; FVoxelTileKey CarveTargetTile; constexpr float CarveBrushRadiusLegacyVoxels = 6.0f; const double CoreVoxelSizeCm = Arguments.CoreVoxelSizeCm; const double CarveBrushRadiusCoreVoxels = CarveBrushRadiusLegacyVoxels * static_cast(VOXEL_SIZE) / CoreVoxelSizeCm; const int32 CollisionRadius = FMath::Clamp(AuthoredSettings->PawnCollisionRadiusChunks, 1, 4); FIntVector ColdStartCollisionCenter = FIntVector::ZeroValue; uint64 ExpectedEditDigest = 0; std::array InitialAggregates{}; std::array TravelAggregates{}; std::array SurfaceTravelAggregates{}; std::vector MatchedTimingInitialDesired; std::vector MatchedTimingSurfaceDesired; std::vector MatchedTimingSurfaceMissingFine; VoxelForgeCore::ShapeBuildRequest MatchedTimingInitialRequest; double NativeSurfaceRegionCompileSeconds = 0.0; double NativeSurfaceTileGenerationSeconds = 0.0; std::vector NativeSurfaceTileSamples; std::vector 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 Snapshot = VoxelForgeCore::ResolvedWorldSnapshot::FromJson( std::string_view(Utf8Json.Get(), static_cast(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(ExpectedSurfacePlanCount) || Assembly->floatingIslands.size() != static_cast(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(Assembly->plan.strates.size()) : 0, Assembly ? static_cast(Assembly->floatingIslands.size()) : 0, ExpectedSurfacePlanCount, ExpectedIslandPlanCount); return false; } if (!ExpectedAssemblyHash.IsEmpty()) { const FString SnapshotHash = FString::Printf( TEXT("%016llx"), static_cast(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(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(Assembly->plan.strates.size()), static_cast(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(NativeSurfacePlan->route.front().positionSubvoxels[0]) / SubvoxelsPerVoxel, static_cast(NativeSurfacePlan->route.front().positionSubvoxels[1]) / SubvoxelsPerVoxel, static_cast(NativeSurfacePlan->route.front().positionSubvoxels[2]) / SubvoxelsPerVoxel}; const VoxelForgeCore::HeightfieldPointSample SurfaceBiomeSample = NativeSurfaceField.point( static_cast(std::llround(SurfaceCameraVoxels.x * SubvoxelsPerVoxel)), static_cast(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 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(X) / (BiomeProbeSide - 1) - 0.5) * BiomeProbeSpanVoxels; const double YVoxel = (static_cast(Y) / (BiomeProbeSide - 1) - 0.5) * BiomeProbeSpanVoxels; const VoxelForgeCore::HeightfieldPointSample Sample = NativeSurfaceField.point( static_cast(std::llround(XVoxel * SubvoxelsPerVoxel)), static_cast(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 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 ColdStartCollisionTiles; ColdStartCollisionTiles.reserve(static_cast(2 * CollisionRadius + 1) * static_cast(2 * CollisionRadius + 1) * static_cast(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 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(McMesh->vertices.size()), static_cast(McMesh->indices.size() / 3u), static_cast(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 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 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(ColdStartCollisionTiles.size()), static_cast(CollisionBodyTiles), static_cast(ColdStartCollisionTiles.size() - CollisionBodyTiles)); std::vector AggregateRows; AggregateRows.reserve(Desired.size()); bool bFoundCarveVertex = false; for (const VoxelForgeCore::TileKey& Key : Desired) { const std::optional 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(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& OutHashes) -> bool { std::optional WorkerSnapshot = VoxelForgeCore::ResolvedWorldSnapshot::FromJson( std::string_view(Utf8Json.Get(), static_cast(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 WorkerRows; WorkerRows.reserve(Desired.size()); for (const VoxelForgeCore::TileKey& Key : Desired) { const std::optional 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 Worker1Aggregates{}; std::array 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(InitialAggregates[0]), static_cast(InitialAggregates[1]), static_cast(InitialAggregates[2]), static_cast(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(TopSlot.TopChunkZ) + 1.0f) * CHUNK_SIZE; const float BottomZ = static_cast(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(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 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& 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 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(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 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 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 TravelRows; TravelRows.reserve(TravelDesired.size()); for (const VoxelForgeCore::TileKey& Key : TravelDesired) { const std::optional 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(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(ExpectedTravelTileCount), static_cast(TravelFineTilesCompiled), static_cast(TravelRegionsBuilt), TravelCoverageLocalTiles, TravelCoverageFaces, TravelLodTransitionFaces, TravelTilesWithSkirts, static_cast(TravelAggregates[0]), static_cast(TravelAggregates[1]), static_cast(TravelAggregates[2]), static_cast(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 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 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 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::infinity(); int32 GeometryAuditL0CandidateCount = 0; int32 HorizonAuditLOD = 0; for (const VoxelForgeCore::TileKey& Key : SurfaceTravelDesired) { HorizonAuditLOD = FMath::Max(HorizonAuditLOD, static_cast(Key.lod)); } for (const VoxelForgeCore::TileKey& Key : SurfaceTravelDesired) { const double TileGenerationStart = FPlatformTime::Seconds(); const std::optional 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(Key.lod) == HorizonAuditLOD) && Mesh->groups.groundTriangleCount > 0u) { const int32 LOD = static_cast(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(Key.coordinate.x), static_cast(Key.coordinate.y), static_cast(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(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 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(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(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(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(SurfaceTravelFineTilesCompiled), static_cast(SurfaceTravelRegionsBuilt), SurfaceTravelCoverageLocalTiles, SurfaceTravelCoverageFaces, SurfaceTravelLodTransitionFaces, SurfaceTravelTilesWithSkirts, static_cast(SurfaceTravelAggregates[0]), static_cast(SurfaceTravelAggregates[1]), static_cast(SurfaceTravelAggregates[2]), static_cast(SurfaceTravelAggregates[3])); UE_LOG(LogTemp, Display, TEXT("[VoxelForgeCoreWorldTest] standalone_reference=1 tiles=%llu affected=%d target=(%d,%d,%d) lod=%d"), static_cast(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 BridgeRoot; const TSharedRef> 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 BridgeWriter = TJsonWriterFactory>::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 BridgeSnapshot = VoxelForgeCore::ResolvedWorldSnapshot::FromJson( std::string_view(BridgeUtf8.Get(), static_cast(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 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 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(Key.lod), ClassifyAssemblerTile(*Mesh), TileGenerationSeconds, 0.0, Mesh->diagnostics.classificationMilliseconds, Mesh->diagnostics.columnComputeMilliseconds, Mesh->diagnostics.meshingMilliseconds, Mesh->diagnostics.heightfieldBandLimited}); } const auto AssignModelReadyTimes = [](std::vector& Samples) { constexpr int32 WorkerSlots = 16; std::array 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 Values, double Fraction) { if (Values.empty()) return 0.0; std::sort(Values.begin(), Values.end()); const std::size_t Index = static_cast(FMath::Clamp( FMath::CeilToInt(Fraction * static_cast(Values.size() - 1u)), 0, static_cast(Values.size() - 1u))); return Values[Index]; }; const auto EmitKindTiming = [&](const TCHAR* Backend, std::vector& Samples) { AssignModelReadyTimes(Samples); for (int32 LOD = 0; LOD <= 1; ++LOD) { for (int32 KindIndex = 0; KindIndex < 4; ++KindIndex) { const EAssemblerTileKind Kind = static_cast(KindIndex); std::vector 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(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(StagedWalkSpeedCmPerSecond) / 100.0, ReadyP95 * static_cast(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(MatchedTimingSurfaceDesired.size()), NativeSurfaceRegionCompileSeconds, BridgeSurfaceRegionCompileSeconds, NativeSurfaceTileGenerationSeconds, BridgeSurfaceTileGenerationSeconds, NativeTotal, BridgeTotal, BridgeTotal / FMath::Max(NativeTotal, 1.0e-9), static_cast(NativeSurfaceTriangleCount), static_cast(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(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::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 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 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(ExpectedInitialTileCount), ExpectedCollisionSet.Num(), Actor->CollisionReadyTiles.Num(), Actor->CollisionNotRequiredTiles.Num(), static_cast(InitialAggregates[0]), static_cast(InitialAggregates[1]), static_cast(InitialAggregates[2]), static_cast(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::StaticClass()); if (Anchor == nullptr) { OutError = TEXT("Could not spawn the Core streaming anchor fixture."); return false; } USceneComponent* AnchorRoot = NewObject(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(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(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(Actor->LastVoxelForgeCoreEditSequence), static_cast(Actor->LastVoxelForgeCoreEditDigest), ExpectedAffectedTiles.Num(), CarveTargetTile.Coord.X, CarveTargetTile.Coord.Y, CarveTargetTile.Coord.Z, CarveTargetTile.Level, static_cast(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(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::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator); APawn* TravelPawn = TestWorld->SpawnActor( 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(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 ActualTravelDesiredSet; std::vector ActorTravelDesired; ActorTravelDesired.reserve(static_cast(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(TravelAggregates[0]), static_cast(TravelAggregates[1]), static_cast(TravelAggregates[2]), static_cast(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(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::Max(); float DensityAtContact = TNumericLimits::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::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::Max(); float DensityExpected = TNumericLimits::Max(); float DensityEnd = TNumericLimits::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::Max()); FVector BodyMeshMaximum(-TNumericLimits::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(FixtureComponent->GetCollisionEnabled()) : -1, IsValid(FixtureComponent) ? static_cast(FixtureComponent->GetCollisionResponseToChannel(ECC_Pawn)) : -1, IsValid(FixtureComponent) ? static_cast(FixtureComponent->GetCollisionResponseToChannel(ECC_Visibility)) : -1, IsValid(FixtureComponent) ? static_cast(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(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(HybridFixtureContacts.size()), ActualSurfaceCollisionReady); } TSet ActualSurfaceVisualDesiredSet; std::vector ActorSurfaceTravelDesired; ActorSurfaceTravelDesired.reserve(static_cast(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(Actual->Density), static_cast(Expected->Density), static_cast(Actual->Mesh), static_cast(Expected->Mesh), static_cast(Actual->Payload), static_cast(Expected->Payload), static_cast(Actual->Topology), static_cast(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(SurfaceTravelFineTilesCompiled), static_cast(SurfaceTravelRegionsBuilt), ActorSurfaceLocalTiles, ActorSurfaceCheckedFaces, ActorSurfaceTransitionFaces, ExpectedSurfaceCollisionSet.Num(), ActualSurfaceCollisionReady, ActualSurfaceCollisionNoBody, NativeSurfaceBiomeId, NativeBiomeIdsSampled, static_cast(SurfaceTravelAggregates[0]), static_cast(SurfaceTravelAggregates[1]), static_cast(SurfaceTravelAggregates[2]), static_cast(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(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(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<= 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(VoxelDensityProfile::EBucket::GetDensityAt); const uint64 Calls = Profile.Calls[DensityIndex]; const uint64 Lookups = Profile.Counters[static_cast(VoxelDensityProfile::ECounter::TunnelCacheLookup)]; const uint64 Hits = Profile.Counters[static_cast(VoxelDensityProfile::ECounter::TunnelCacheHit)]; const uint64 SdfBuilds = Profile.Counters[static_cast(VoxelDensityProfile::ECounter::SdfCacheBuild)]; const double DensityUs = static_cast(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(Calls), Calls > 0 ? DensityUs / static_cast(Calls) : 0.0, static_cast(SdfBuilds), static_cast(Lookups), static_cast(Hits), static_cast(Profile.Counters[static_cast(VoxelDensityProfile::ECounter::TunnelCacheMiss)]), Lookups > 0 ? static_cast(Hits) / static_cast(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(VoxelDensityProfile::EBucket::GetDensityAt)]; const uint64 DensityCycles = Profile.WallCycles[static_cast(VoxelDensityProfile::EBucket::GetDensityAt)]; const double DensityTotalUs = static_cast(DensityCycles) * CycleToMicroseconds; UE_LOG(LogTemp, Display, TEXT("[VoxelForgeDensityProfile] total calls=%llu total_us=%.3f us_per_call=%.6f"), static_cast(DensityCalls), DensityTotalUs, DensityCalls > 0 ? DensityTotalUs / static_cast(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(CacheWorkers), static_cast(CacheCapacityEntries), static_cast(CacheValidEntries), static_cast(CacheTotalStaticBytes), static_cast(CacheTotalDynamicBytes), static_cast(CacheTotalBytes), CacheValidEntries > 0 ? static_cast(CacheEntryBytes) / static_cast(CacheValidEntries) : 0.0, static_cast(CacheLargestEntryBytes), CacheWorkers > 0 ? static_cast(CacheTotalBytes) / static_cast(CacheWorkers) : 0.0, static_cast(CacheLargestWorkerBytes), Profile.TunnelCacheValidEntries > 0 ? static_cast(Profile.TunnelCacheEntryBytes) / static_cast(Profile.TunnelCacheValidEntries) : 0.0, static_cast(Profile.TunnelCacheLargestEntryBytes), static_cast(Profile.TunnelCacheLargestWorkerValidEntries), Profile.RoomGraphCacheValidEntries > 0 ? static_cast(Profile.RoomGraphCacheEntryBytes) / static_cast(Profile.RoomGraphCacheValidEntries) : 0.0, static_cast(Profile.RoomGraphCacheLargestEntryBytes), static_cast(Profile.RoomGraphCacheLargestWorkerValidEntries), static_cast(Profile.TunnelCacheWorkers), static_cast(Profile.TunnelCacheCapacityEntries), static_cast(Profile.TunnelCacheValidEntries), static_cast(Profile.TunnelCacheStaticBytes), static_cast(Profile.TunnelCacheDynamicBytes), static_cast(Profile.RoomGraphCacheWorkers), static_cast(Profile.RoomGraphCacheCapacityEntries), static_cast(Profile.RoomGraphCacheValidEntries), static_cast(Profile.RoomGraphCacheStaticBytes), static_cast(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(Phase); DensityAttributionCycles += Profile.WallCycles[PhaseIndex]; DensityAttributionCalls += Profile.Calls[PhaseIndex]; } const double DensityAttributionUs = static_cast(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(DensityAttributionCalls)); for (int32 Index = 0; Index < VoxelDensityProfile::CounterCount; ++Index) { const VoxelDensityProfile::ECounter Counter = static_cast(Index); UE_LOG(LogTemp, Display, TEXT("[VoxelForgeDensityProfile] counter=%s value=%llu per_density_call=%.3f"), VoxelDensityProfile::CounterName(Counter), static_cast(Profile.Counters[Index]), DensityCalls > 0 ? static_cast(Profile.Counters[Index]) / static_cast(DensityCalls) : 0.0); } for (int32 Index = 0; Index < VoxelDensityProfile::BucketCount; ++Index) { const uint64 Calls = Profile.Calls[Index]; if (Calls == 0 || Index == static_cast(VoxelDensityProfile::EBucket::GetDensityAt)) { continue; } const double TotalUs = static_cast(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(Index)), static_cast(Calls), TotalUs, TotalUs / static_cast(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(Bucket); MesherAttributionCycles += Profile.WallCycles[BucketIndex]; MesherAttributionCalls += Profile.Calls[BucketIndex]; } const int32 MesherTotalIndex = static_cast( VoxelDensityProfile::EBucket::MesherGenerateMesh); const double MesherTotalUs = static_cast( Profile.WallCycles[MesherTotalIndex]) * CycleToMicroseconds; const double MesherAttributionUs = static_cast( 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(Profile.Calls[MesherTotalIndex]), MesherTotalUs, MesherAttributionUs, MesherTotalUs - MesherAttributionUs, MesherTotalUs > 0.0 ? MesherAttributionUs * 100.0 / MesherTotalUs : 0.0, static_cast(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& 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& Object, const TCHAR* Key, FString& OutValue) { if (!Object.IsValid() || !Object->HasField(Key)) { return false; } OutValue = Object->GetStringField(Key); return true; } bool JsonBool(const TSharedPtr& Object, const TCHAR* Key, bool& OutValue) { if (!Object.IsValid() || !Object->HasField(Key)) { return false; } OutValue = Object->GetBoolField(Key); return true; } bool BuildBatchCaseParams( const TSharedPtr& 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(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 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 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 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= and an absolute -out=.")); 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 Root; const TSharedRef> 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>* 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 UsedCaseIds; TArray> 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 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 CaseReport = MakeShared(); 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(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 CaseReport; const TSharedRef> CaseReader = TJsonReaderFactory<>::Create(CaseJson); if (!FJsonSerializer::Deserialize(CaseReader, CaseReport) || !CaseReport.IsValid()) { CaseReport = MakeShared(); 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(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 BatchReport = MakeShared(); 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> 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(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(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); }