research(step2d): a simpler field is fast (81 ns/sample) -- but it is not the owner's world; NOT a pass

Editor-only, default off (voxel.ResearchRedefinedDensityField). The redefined field is a seeded,
periodic cross-corridor pattern per strate plus the authored passages, landings and a shaft tree,
with four value-noise detail bands at every near-surface 25 cm sample and sound 8^3 brick proofs.
259^3 ring, six workers: 308 ms median (467 ms without skips) vs 8.9 s for the live field.
Detail, boundary-artefact, determinism (1 vs 6 workers, two runs) and bound-audit checks pass.

Why it is not a pass: the slices show a mostly-solid field with a few straight corridors, where the
owner's world is mostly open space; rooms, the tunnel network, composer recipes and archetypes are
gone. Much of the speed comes from 60% of bricks being proven solid in that simpler world.
What it does show: detail at full resolution plus brick skipping costs ~80-130 ns/sample; the
cost of the live field is how rooms/tunnels/passages are represented, not the detail or the lattice.

The two walkability tests were redirected to the research field in the working tree; that change
is NOT committed (saved as scratchpad step2d-test-redirect.patch) so they keep testing the live world.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-09-30 09:06:25 +02:00
1 parent d6e021a80b
commit 78641da0bb
15 files changed
+3077 -6

No files matched your search

@@ -34,6 +34,7 @@
#include "VoxelGenerator.h"
#include "VoxelGeometryHash.h"
#include "VoxelMarchingCubesMesher.h"
#include "VoxelRedefinedDensityField.h"
#include "VoxelNoise.h"
#include "VoxelPassageGeometry.h"
#include "VoxelSettings.h"
@@ -1509,6 +1510,9 @@ struct FRegionEvalStackSamplerSession
}
};
bool SavePng(const FString& FilePath, const TArray<FColor>& Pixels,
int32 Width, int32 Height, FString& OutError);
int32 FindRoomGraphRecipeIndex(int32 Seed)
{
for (int32 Index = 0; Index < 64; ++Index)
@@ -2511,6 +2515,829 @@ int32 RunRegionEval(const FString& Params)
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;
uint64 DeliberatelyUndersizedBoundClaimsChecked = 0;
uint64 DeliberateBoundDisagreements = 0;
};
FStep2dBrickProofAudit AuditStep2dBrickProofs(
const UVoxelGenerator& Generator,
FIntVector Origin,
int32 Dimension)
{
FStep2dBrickProofAudit Result;
const FIntVector Last = Origin + FIntVector(Dimension - 1, Dimension - 1, Dimension - 1);
const FIntVector FirstBrick(
FMath::FloorToInt(static_cast<float>(Origin.X) / 8.0f),
FMath::FloorToInt(static_cast<float>(Origin.Y) / 8.0f),
FMath::FloorToInt(static_cast<float>(Origin.Z) / 8.0f));
const FIntVector LastBrick(
FMath::FloorToInt(static_cast<float>(Last.X) / 8.0f),
FMath::FloorToInt(static_cast<float>(Last.Y) / 8.0f),
FMath::FloorToInt(static_cast<float>(Last.Z) / 8.0f));
uint64 CorrectSkipOrdinal = 0;
for (int32 BZ = FirstBrick.Z; BZ <= LastBrick.Z; ++BZ)
for (int32 BY = FirstBrick.Y; BY <= LastBrick.Y; ++BY)
for (int32 BX = FirstBrick.X; BX <= LastBrick.X; ++BX)
{
const FIntVector BrickWorldMin(BX * 8, BY * 8, BZ * 8);
const FIntVector BrickOrigin(
FMath::Max(Origin.X, BrickWorldMin.X),
FMath::Max(Origin.Y, BrickWorldMin.Y),
FMath::Max(Origin.Z, BrickWorldMin.Z));
const FIntVector End(
FMath::Min(BrickWorldMin.X + 8, Origin.X + Dimension),
FMath::Min(BrickWorldMin.Y + 8, Origin.Y + Dimension),
FMath::Min(BrickWorldMin.Z + 8, Origin.Z + Dimension));
const FIntVector Size = End - BrickOrigin;
bool bProvenAir = false;
if (VoxelForgeRedefinedDensityField::TryClassifyBrick(
Generator, BrickOrigin, Size, bProvenAir, 1.0f))
{
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 = VoxelForgeRedefinedDensityField::Evaluate(
Generator,
static_cast<float>(BrickOrigin.X + X),
static_cast<float>(BrickOrigin.Y + Y),
static_cast<float>(BrickOrigin.Z + Z), nullptr, 1.0f);
++Result.CorrectProofSamplesChecked;
if ((Density > 0.0f) != bProvenAir)
{
++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;
if (VoxelForgeRedefinedDensityField::TryClassifyBrick(
Generator, BrickOrigin, Size, bDeliberatelyClaimedAir, 0.0f))
{
++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 = VoxelForgeRedefinedDensityField::Evaluate(
Generator,
static_cast<float>(BrickOrigin.X + X),
static_cast<float>(BrickOrigin.Y + Y),
static_cast<float>(BrickOrigin.Z + Z),
nullptr,
4.0f);
if ((MaximumDetailDensity > 0.0f) != bDeliberatelyClaimedAir)
{
++Result.DeliberateBoundDisagreements;
}
}
}
}
return Result;
}
FStep2dSurfaceSpectrum ComputeStep2dSpectrum(const TArray<float>& Density, int32 Dimension)
{
FStep2dSurfaceSpectrum Result;
double SumSquares[3] = {0.0, 0.0, 0.0};
uint64 Counts[3] = {0, 0, 0};
const int32 Lags[] = {1, 2, 4};
for (int32 Z = 4; Z < Dimension - 4; ++Z)
for (int32 Y = 4; Y < Dimension - 4; ++Y)
for (int32 X = 4; X < Dimension - 4; ++X)
{
const int32 CenterIndex = (Z * Dimension + Y) * Dimension + X;
const float Center = Density[CenterIndex];
if (!FMath::IsFinite(Center) || FMath::Abs(Center) > 12.0f)
{
continue;
}
++Result.SurfaceBandSamples;
for (int32 LagIndex = 0; LagIndex < UE_ARRAY_COUNT(Lags); ++LagIndex)
{
const int32 Lag = Lags[LagIndex];
const float Laplacian =
Density[CenterIndex - Lag] + Density[CenterIndex + Lag]
+ Density[CenterIndex - Lag * Dimension]
+ Density[CenterIndex + Lag * Dimension]
+ Density[CenterIndex - Lag * Dimension * Dimension]
+ Density[CenterIndex + Lag * Dimension * Dimension]
- 6.0f * Center;
SumSquares[LagIndex] += static_cast<double>(Laplacian) * Laplacian;
++Counts[LagIndex];
}
}
Result.RMSLag1 = Counts[0] > 0 ? FMath::Sqrt(SumSquares[0] / Counts[0]) : 0.0;
Result.RMSLag2 = Counts[1] > 0 ? FMath::Sqrt(SumSquares[1] / Counts[1]) : 0.0;
Result.RMSLag4 = Counts[2] > 0 ? FMath::Sqrt(SumSquares[2] / Counts[2]) : 0.0;
return Result;
}
FIntVector SelectStep2dMeshOrigin(
const TArray<float>& OldDensity,
const TArray<float>& NewDensity,
FIntVector RingOrigin,
int32 Dimension,
bool& bOutContainsBothIsoBands)
{
bOutContainsBothIsoBands = false;
float BestCombinedBand = FLT_MAX;
FIntVector BestSample(Dimension / 2, Dimension / 2, Dimension / 2);
for (int32 Z = 32; Z < Dimension - 32; ++Z)
for (int32 Y = 32; Y < Dimension - 32; ++Y)
for (int32 X = 32; X < Dimension - 32; ++X)
{
const int32 Index = (Z * Dimension + Y) * Dimension + X;
const float OldAbs = FMath::Abs(OldDensity[Index]);
const float NewAbs = FMath::Abs(NewDensity[Index]);
if (OldAbs <= 8.0f && NewAbs <= 8.0f)
{
const float Combined = OldAbs + NewAbs;
if (Combined < BestCombinedBand)
{
BestCombinedBand = Combined;
BestSample = FIntVector(X, Y, Z);
bOutContainsBothIsoBands = true;
}
}
}
if (!bOutContainsBothIsoBands)
{
float BestNewBand = FLT_MAX;
for (int32 Z = 32; Z < Dimension - 32; ++Z)
for (int32 Y = 32; Y < Dimension - 32; ++Y)
for (int32 X = 32; X < Dimension - 32; ++X)
{
const int32 Index = (Z * Dimension + Y) * Dimension + X;
const float NewAbs = FMath::Abs(NewDensity[Index]);
if (NewAbs < BestNewBand)
{
BestNewBand = NewAbs;
BestSample = FIntVector(X, Y, Z);
}
}
}
return RingOrigin + BestSample - FIntVector(32, 32, 32);
}
FStep2dBoundaryStats ComputeStep2dBoundaryStats(
const TArray<float>& Density, int32 Dimension, FIntVector Origin, int32 FeatureCellSpacing)
{
FStep2dBoundaryStats Result;
double BrickSquares = 0.0;
double InteriorSquares = 0.0;
double ChunkSquares = 0.0;
double FeatureSquares = 0.0;
double FeatureInteriorSquares = 0.0;
const int32 CellSpacing = FMath::Max(FeatureCellSpacing, 1);
for (int32 Z = 1; Z < Dimension - 1; ++Z)
for (int32 Y = 1; Y < Dimension - 1; ++Y)
for (int32 X = 1; X < Dimension - 1; ++X)
{
const int32 CenterIndex = (Z * Dimension + Y) * Dimension + X;
const int32 Stride[3] = {1, Dimension, Dimension * Dimension};
const int32 World[3] = {Origin.X + X, Origin.Y + Y, Origin.Z + Z};
for (int32 Axis = 0; Axis < 3; ++Axis)
{
const double LeftGradient = Density[CenterIndex] - Density[CenterIndex - Stride[Axis]];
const double RightGradient = Density[CenterIndex + Stride[Axis]] - Density[CenterIndex];
const double Jump = FMath::Abs(RightGradient - LeftGradient);
const bool bBrickBoundary = (World[Axis] % 8) == 0;
const bool bChunkBoundary = (World[Axis] % CHUNK_SIZE) == 0 && Axis < 2;
const bool bFeatureBoundary = ((World[Axis] + CellSpacing / 2) % CellSpacing) == 0;
if (FMath::Abs(Density[CenterIndex]) <= 12.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;
}
FColor Step2dDensityColor(float Density)
{
if (FMath::Abs(Density) < 1.0f)
{
return FColor(56, 184, 206, 255);
}
return Density >= 0.0f
? FColor(226, 220, 202, 255)
: FColor(22, 30, 45, 255);
}
void SaveStep2dSideBySideSlice(
const FString& Path,
const TArray<float>& OldDensity,
const TArray<float>& NewDensity,
int32 Dimension,
TCHAR Plane,
int32 FixedIndex,
FString& OutError)
{
const int32 Width = Dimension * 2;
const int32 Height = Dimension;
TArray<FColor> Pixels;
Pixels.SetNumUninitialized(Width * Height);
for (int32 Row = 0; Row < Height; ++Row)
for (int32 Column = 0; Column < Dimension; ++Column)
{
int32 OldIndex = 0;
int32 NewIndex = 0;
if (Plane == TEXT('h'))
{
const int32 X = Column;
const int32 Y = Dimension - 1 - Row;
const int32 Z = FixedIndex;
OldIndex = (Z * Dimension + Y) * Dimension + X;
}
else if (Plane == TEXT('x'))
{
const int32 X = Column;
const int32 Y = FixedIndex;
const int32 Z = Dimension - 1 - Row;
OldIndex = (Z * Dimension + Y) * Dimension + X;
}
else
{
const int32 X = FixedIndex;
const int32 Y = Column;
const int32 Z = Dimension - 1 - Row;
OldIndex = (Z * Dimension + Y) * Dimension + X;
}
NewIndex = OldIndex;
Pixels[Row * Width + Column] = Step2dDensityColor(OldDensity[OldIndex]);
Pixels[Row * Width + Dimension + Column] = Step2dDensityColor(NewDensity[NewIndex]);
}
SavePng(Path, Pixels, Width, Height, OutError);
}
TSharedRef<FJsonObject> Step2dTimingJson(const VoxelForgeRegionEval::FTiming& Timing)
{
TSharedRef<FJsonObject> Json = MakeShared<FJsonObject>();
Json->SetNumberField(TEXT("workers_requested"), Timing.Workers);
Json->SetNumberField(TEXT("workers_observed"), Timing.ObservedWorkerThreads);
Json->SetNumberField(TEXT("lattice_samples"), static_cast<double>(Timing.SampleCount));
Json->SetNumberField(TEXT("density_samples_evaluated"), static_cast<double>(Timing.EvaluatedSamples));
Json->SetNumberField(TEXT("samples_skipped_by_brick_proof"), static_cast<double>(Timing.SkippedSamples));
Json->SetNumberField(TEXT("bricks_tested"), static_cast<double>(Timing.BrickCount));
Json->SetNumberField(TEXT("bricks_skipped"), static_cast<double>(Timing.SkippedBricks));
Json->SetNumberField(TEXT("share_of_bricks_skipped"), Timing.BrickCount > 0
? static_cast<double>(Timing.SkippedBricks) / static_cast<double>(Timing.BrickCount) : 0.0);
Json->SetNumberField(TEXT("share_of_lattice_samples_skipped"), Timing.SampleCount > 0
? static_cast<double>(Timing.SkippedSamples) / static_cast<double>(Timing.SampleCount) : 0.0);
Json->SetNumberField(TEXT("wall_ms"), Timing.WallSeconds * 1000.0);
Json->SetNumberField(TEXT("worker_cpu_seconds"), Timing.WorkerCpuSeconds);
Json->SetNumberField(TEXT("worker_cpu_ns_per_lattice_sample"), Timing.NanosecondsPerSample);
Json->SetNumberField(TEXT("brick_classification_worker_seconds"), Timing.BrickClassificationSeconds);
Json->SetNumberField(TEXT("density_evaluation_worker_seconds"), Timing.DensityEvaluationSeconds);
Json->SetNumberField(TEXT("uniform_fill_worker_seconds"), Timing.UniformFillSeconds);
Json->SetNumberField(TEXT("longest_worker_wall_ms"), Timing.LongestWorkerWallSeconds * 1000.0);
Json->SetNumberField(TEXT("parallel_overhead_wall_ms"), FMath::Max(
0.0, Timing.WallSeconds - Timing.LongestWorkerWallSeconds) * 1000.0);
Json->SetNumberField(TEXT("brick_classification_calls"), static_cast<double>(Timing.MacroBrickChecks));
Json->SetNumberField(TEXT("macro_center_evaluations"), static_cast<double>(Timing.MacroCentreEvaluations));
Json->SetNumberField(TEXT("unsupported_brick_proofs"), static_cast<double>(Timing.UnsupportedBrickProofs));
Json->SetNumberField(TEXT("chunk_or_strate_boundary_proofs"),
static_cast<double>(Timing.ChunkBoundaryBrickProofs));
Json->SetNumberField(TEXT("passage_bound_proofs"), static_cast<double>(Timing.PassageBoundBrickProofs));
Json->SetNumberField(TEXT("mixed_macro_interval_proofs"), static_cast<double>(Timing.MixedMacroBrickProofs));
Json->SetNumberField(TEXT("room_sdf_evaluations"), static_cast<double>(Timing.RoomEvaluations));
Json->SetNumberField(TEXT("tunnel_sdf_evaluations"), static_cast<double>(Timing.TunnelEvaluations));
Json->SetNumberField(TEXT("tunnel_core_evaluations"), static_cast<double>(Timing.TunnelCoreEvaluations));
Json->SetNumberField(TEXT("passage_sdf_evaluations"), static_cast<double>(Timing.PassageEvaluations));
Json->SetNumberField(TEXT("geometry_evaluations_per_lattice_sample"), Timing.CandidateEvaluationsPerSample);
Json->SetNumberField(TEXT("quantized_hash"), static_cast<double>(Timing.QuantizedHash));
Json->SetNumberField(TEXT("raw_float_hash"), static_cast<double>(Timing.RawFloatHash));
return Json;
}
int32 RunStep2d(const FString& Params)
{
FExploreArguments Arguments;
FString Error;
if (!ParseArguments(Params, Arguments, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] argument error: %s"), *Error);
return 2;
}
int32 Seed = 1337;
FParse::Value(*Params, TEXT("seed="), Seed);
int32 Workers = VoxelForgeRegionEval::WorkerCount;
FParse::Value(*Params, TEXT("regionworkers="), Workers);
Workers = FMath::Clamp(Workers, 1, 32);
FString ResearchOut;
FParse::Value(*Params, TEXT("researchout="), ResearchOut);
if (ResearchOut.IsEmpty())
{
ResearchOut = FPaths::Combine(Arguments.OutDirectory, TEXT("Research/step2d"));
}
ResearchOut = FPaths::ConvertRelativePathToFull(ResearchOut);
IFileManager::Get().MakeDirectory(*ResearchOut, true);
IFileManager::Get().MakeDirectory(*Arguments.OutDirectory, true);
FExploreWorld World;
int32 RecipeIndex = INDEX_NONE;
uint32 RecipeHash = 0;
int32 EditChunkCount = 0;
const double PrepareStartSeconds = FPlatformTime::Seconds();
if (!PrepareRegionEvalWorld(World, Arguments, Seed,
RecipeIndex, RecipeHash, EditChunkCount, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] world preparation failed: %s"), *Error);
return 2;
}
const double WorldPrepareSeconds = FPlatformTime::Seconds() - PrepareStartSeconds;
if (World.DiffLayer != nullptr)
{
World.DiffLayer->Clear();
}
const int32 Dimension = VoxelForgeRegionEval::RingDimension;
const int32 CenterZ = (World.TargetTopWorldZ + World.TargetBottomWorldZ) / 2;
const FIntVector Origin(-129, -129, CenterZ - 129);
TArray<VoxelForgeRegionEval::FTiming> OldTrials;
TArray<VoxelForgeRegionEval::FTiming> NewTrials;
TArray<float> OldDensities;
TArray<float> NewDensities;
TArray<float> NewRepeatDensities;
TArray<float> NewSkippedDensities;
TArray<float> SmoothDensities;
TArray<float> SingleWorkerDensities;
VoxelForgeRedefinedDensityField::SetEnabled(false);
VoxelForgeRedefinedDensityField::SetDetailScale(1.0f);
for (int32 Trial = 0; Trial < 3; ++Trial)
{
VoxelForgeRegionEval::FTiming Timing;
if (!VoxelForgeRegionEval::Evaluate(
*World.Generator, Origin, Dimension, nullptr, true, false, 1.0f,
&OldDensities, Timing, false, Workers, false))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] old field timing pass %d failed."), Trial + 1);
return 2;
}
OldTrials.Add(Timing);
}
// This fourth old-field pass carries counters; its wall time is deliberately excluded from
// the performance row so instrumentation does not contaminate the measured baseline.
VoxelForgeRegionEval::FTiming OldCounters;
if (!VoxelForgeRegionEval::Evaluate(
*World.Generator, Origin, Dimension, nullptr, true, false, 1.0f,
nullptr, OldCounters, false, Workers, true))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] old field counter pass failed."));
return 2;
}
OldCounters.WallSeconds = OldTrials.Last().WallSeconds;
OldCounters.WorkerCpuSeconds = OldTrials.Last().WorkerCpuSeconds;
OldCounters.NanosecondsPerSample = OldTrials.Last().NanosecondsPerSample;
for (int32 Trial = 0; Trial < 3; ++Trial)
{
VoxelForgeRegionEval::FTiming Timing;
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
NewSkippedDensities, Timing, true))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] redefined 6-worker pass %d failed."), Trial + 1);
return 2;
}
NewTrials.Add(Timing);
}
VoxelForgeRegionEval::FTiming OneWorkerTiming;
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, 1, 1.0f,
SingleWorkerDensities, OneWorkerTiming, false))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] 1-worker determinism pass failed."));
return 2;
}
VoxelForgeRegionEval::FTiming NewFullSixWorkerTiming;
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
NewDensities, NewFullSixWorkerTiming, false))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] full-detail six-worker evidence pass failed."));
return 2;
}
VoxelForgeRegionEval::FTiming NewRepeatTiming;
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
NewRepeatDensities, NewRepeatTiming, false))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] repeated full-detail hash pass failed."));
return 2;
}
VoxelForgeRegionEval::FTiming SmoothTiming;
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 0.0f,
SmoothDensities, SmoothTiming, false))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] deliberate smoothing control failed."));
return 2;
}
int64 DetailBatchParitySamples = 0;
double DetailBatchMaxAbsoluteError = 0.0;
const int64 TotalLatticeSamples = static_cast<int64>(Dimension) * Dimension * Dimension;
for (int64 Index = 0; Index < TotalLatticeSamples; Index += 2047)
{
const int32 X = static_cast<int32>(Index % Dimension);
const int32 Y = static_cast<int32>((Index / Dimension) % Dimension);
const int32 Z = static_cast<int32>(Index / (static_cast<int64>(Dimension) * Dimension));
const float RawDetailField = VoxelForgeRedefinedDensityField::Evaluate(
*World.Generator, static_cast<float>(Origin.X + X),
static_cast<float>(Origin.Y + Y), static_cast<float>(Origin.Z + Z), nullptr, 1.0f);
DetailBatchMaxAbsoluteError = FMath::Max(
DetailBatchMaxAbsoluteError,
static_cast<double>(FMath::Abs(RawDetailField - NewDensities[static_cast<int32>(Index)])));
++DetailBatchParitySamples;
}
const bool bDetailBatchMatchesRaw = DetailBatchMaxAbsoluteError <= 1.0e-5;
const FStep2dSurfaceSpectrum OldSpectrum = ComputeStep2dSpectrum(OldDensities, Dimension);
const FStep2dSurfaceSpectrum NewSpectrum = ComputeStep2dSpectrum(NewDensities, Dimension);
const FStep2dSurfaceSpectrum SmoothSpectrum = ComputeStep2dSpectrum(SmoothDensities, Dimension);
const FStep2dBrickProofAudit BrickProofAudit = AuditStep2dBrickProofs(
*World.Generator, Origin, Dimension);
const int32 FeatureCellSpacing = World.Definitions.IsValidIndex(Arguments.Slot)
&& World.Definitions[Arguments.Slot].IsValid()
&& World.Definitions[Arguments.Slot]->GeneratorType == ECaveGeneratorType::Maze
? 96 : 128;
const FStep2dBoundaryStats BoundaryStats = ComputeStep2dBoundaryStats(
NewDensities, Dimension, Origin, FeatureCellSpacing);
const bool bHashesAgree = OldDensities.Num() == Dimension * Dimension * Dimension
&& VoxelForgeRegionEval::HashQuantized(NewDensities)
== VoxelForgeRegionEval::HashQuantized(SingleWorkerDensities)
&& VoxelForgeRegionEval::HashQuantized(NewDensities)
== VoxelForgeRegionEval::HashQuantized(NewRepeatDensities)
&& NewFullSixWorkerTiming.RawFloatHash == OneWorkerTiming.RawFloatHash
&& NewFullSixWorkerTiming.RawFloatHash == NewRepeatTiming.RawFloatHash;
VoxelForgeRedefinedDensityField::SetEnabled(false);
VoxelForgeRedefinedDensityField::SetDetailScale(1.0f);
bool bMeshContainsBothIsoBands = false;
const FIntVector MeshOrigin = SelectStep2dMeshOrigin(
OldDensities, NewDensities, Origin, Dimension, bMeshContainsBothIsoBands);
const FVoxelMeshData OldMesh = World.Mesher->GenerateMesh(MeshOrigin, 1, 64);
const int32 OldTriangleCount = FMath::Max(0, OldMesh.Triangles.Num() / 3 - OldMesh.NumSkirtTriangles);
VoxelForgeRedefinedDensityField::SetEnabled(true);
const FVoxelMeshData NewMesh = World.Mesher->GenerateMesh(MeshOrigin, 1, 64);
const int32 NewTriangleCount = FMath::Max(0, NewMesh.Triangles.Num() / 3 - NewMesh.NumSkirtTriangles);
VoxelForgeRedefinedDensityField::SetDetailScale(0.0f);
const FVoxelMeshData SmoothMesh = World.Mesher->GenerateMesh(MeshOrigin, 1, 64);
const int32 SmoothTriangleCount = FMath::Max(0, SmoothMesh.Triangles.Num() / 3 - SmoothMesh.NumSkirtTriangles);
VoxelForgeRedefinedDensityField::SetDetailScale(1.0f);
VoxelForgeRedefinedDensityField::SetEnabled(false);
const FString ImageNames[] = {
TEXT("slice_xy_center.png"), TEXT("slice_xz_y0.png"),
TEXT("slice_yz_x0.png"), TEXT("slice_xz_passage.png")};
FString SliceError;
SaveStep2dSideBySideSlice(
FPaths::Combine(ResearchOut, ImageNames[0]), OldDensities, NewDensities,
Dimension, TEXT('h'), Dimension / 2, SliceError);
SaveStep2dSideBySideSlice(
FPaths::Combine(ResearchOut, ImageNames[1]), OldDensities, NewDensities,
Dimension, TEXT('x'), Dimension / 2, SliceError);
SaveStep2dSideBySideSlice(
FPaths::Combine(ResearchOut, ImageNames[2]), OldDensities, NewDensities,
Dimension, TEXT('y'), Dimension / 2, SliceError);
int32 PassageIndex = Dimension / 2;
if (World.Manager != nullptr && !World.Manager->GetPassages().IsEmpty())
{
const FVoxelPassage& Passage = World.Manager->GetPassages()[0];
PassageIndex = FMath::Clamp(FMath::RoundToInt(Passage.LowerLanding.StandingPoint.Y)
- Origin.Y, 0, Dimension - 1);
}
SaveStep2dSideBySideSlice(
FPaths::Combine(ResearchOut, ImageNames[3]), OldDensities, NewDensities,
Dimension, TEXT('x'), PassageIndex, SliceError);
if (!SliceError.IsEmpty())
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] slice image write failed: %s"), *SliceError);
return 2;
}
TArray<double> OldWallTrials;
TArray<double> NewWallTrials;
for (const auto& Timing : OldTrials) OldWallTrials.Add(Timing.WallSeconds);
for (const auto& Timing : NewTrials) NewWallTrials.Add(Timing.WallSeconds);
OldWallTrials.Sort();
NewWallTrials.Sort();
const double OldMedianWall = OldWallTrials[OldWallTrials.Num() / 2];
const double NewMedianWall = NewWallTrials[NewWallTrials.Num() / 2];
const double OldGeometryEvaluations = static_cast<double>(OldCounters.RoomEvaluations
+ OldCounters.TunnelEvaluations + OldCounters.TunnelCoreEvaluations
+ OldCounters.PassageEvaluations);
const double OldGeometryPerSample = OldCounters.SampleCount > 0
? OldGeometryEvaluations / static_cast<double>(OldCounters.SampleCount) : 0.0;
const double NewMacroPerSample = NewTrials.Last().SampleCount > 0
? static_cast<double>(NewTrials.Last().MacroCentreEvaluations
+ NewTrials.Last().EvaluatedSamples)
/ static_cast<double>(NewTrials.Last().SampleCount) : 0.0;
const bool bDetailAtLeastOld = NewSpectrum.RMSLag1 >= OldSpectrum.RMSLag1
&& NewSpectrum.RMSLag2 >= OldSpectrum.RMSLag2;
const bool bSmoothControlDetected = SmoothSpectrum.RMSLag1 < NewSpectrum.RMSLag1 * 0.80
|| SmoothSpectrum.RMSLag2 < NewSpectrum.RMSLag2 * 0.80;
const bool bBoundaryStatsSound = BoundaryStats.BrickBoundarySamples > 0
&& BoundaryStats.InteriorSamples > 0
&& BoundaryStats.ChunkBoundarySamples > 0
&& BoundaryStats.FeatureCellBoundarySamples > 0
&& BoundaryStats.BrickJumpRms <= BoundaryStats.InteriorJumpRms * 2.0 + 0.25
&& BoundaryStats.ChunkBoundaryJumpRms <= BoundaryStats.InteriorJumpRms * 2.0 + 0.25
&& BoundaryStats.FeatureCellJumpRms
<= BoundaryStats.FeatureCellInteriorJumpRms * 2.0 + 0.25;
const bool bCorrectProofsSound = BrickProofAudit.CorrectlySkippedBricksAudited > 0
&& BrickProofAudit.CorrectProofSamplesChecked > 0
&& BrickProofAudit.CorrectProofViolations == 0;
const bool bUndersizedBoundCaught = BrickProofAudit.DeliberateBoundDisagreements > 0;
const bool bSmoothWouldFallBelowOldSpectrum = SmoothSpectrum.RMSLag1 < OldSpectrum.RMSLag1
|| SmoothSpectrum.RMSLag2 < OldSpectrum.RMSLag2;
const bool bSpeedPass = NewMedianWall <= 0.600;
const bool bPassed = bSpeedPass && bHashesAgree && bDetailAtLeastOld
&& bSmoothControlDetected
&& bBoundaryStatsSound && bCorrectProofsSound
&& bUndersizedBoundCaught
&& bDetailBatchMatchesRaw
&& NewTrials.Last().NonFiniteSamples == 0;
TSharedRef<FJsonObject> Report = MakeShared<FJsonObject>();
Report->SetStringField(TEXT("status"), bPassed ? TEXT("pass") : TEXT("fail"));
Report->SetBoolField(TEXT("pass"), bPassed);
Report->SetBoolField(TEXT("speed_pass_600ms"), bSpeedPass);
Report->SetNumberField(TEXT("seed"), Seed);
Report->SetNumberField(TEXT("ring_dimension"), Dimension);
Report->SetNumberField(TEXT("ring_samples"), static_cast<double>(Dimension) * Dimension * Dimension);
Report->SetNumberField(TEXT("workers_requested"), Workers);
Report->SetNumberField(TEXT("world_prepare_ms_excluded"), WorldPrepareSeconds * 1000.0);
Report->SetNumberField(TEXT("world_prepare_recipe_index"), RecipeIndex);
Report->SetNumberField(TEXT("world_prepare_recipe_hash"), RecipeHash);
Report->SetNumberField(TEXT("cleared_player_edit_chunks"), EditChunkCount);
TSharedRef<FJsonObject> Window = MakeShared<FJsonObject>();
Window->SetNumberField(TEXT("origin_x_voxels"), Origin.X);
Window->SetNumberField(TEXT("origin_y_voxels"), Origin.Y);
Window->SetNumberField(TEXT("origin_z_voxels"), Origin.Z);
Window->SetNumberField(TEXT("samples_per_axis"), Dimension);
Window->SetNumberField(TEXT("sample_stride_voxels"), 1);
Window->SetNumberField(TEXT("sample_spacing_cm"), 25.0);
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 25 cm. No coarse density result is interpolated "
"onto it: every potentially surface-affecting point evaluates all four detail bands. "
"Brick skips require a final-field sign proof including the full noise supremum, a "
"floating-point safety margin, and the one-sample marching-cubes halo."));
Report->SetStringField(TEXT("finer_lattice_scaling"),
TEXT("At 12.5 cm, scale macro lengths and the broad/medium noise cell sizes in lattice "
"units by 2 to preserve their world-space shapes; keep or add a 1-2 sample fine octave "
"to retain and extend detail. With fixed 8^3 sample bricks, samples grow 8x but "
"surface-intersecting bricks grow approximately with area (about 4x); the rest are "
"sign-proven and require only density writes. Full-resolution detail remains per "
"potentially surface-affecting point."));
Report->SetNumberField(TEXT("old_macro_geometry_evaluations_per_point"), OldGeometryPerSample);
Report->SetNumberField(TEXT("new_macro_evaluations_per_point_including_brick_proofs"), NewMacroPerSample);
Report->SetNumberField(TEXT("new_noise_value_octaves_per_evaluated_point"), 4.0);
Report->SetNumberField(TEXT("new_noise_octave_evaluations_per_full_ring_point"),
static_cast<double>(NewTrials.Last().EvaluatedSamples) * 4.0
/ static_cast<double>(NewTrials.Last().SampleCount));
Report->SetNumberField(TEXT("old_reported_noise_share_percent_from_step2c"), 2.6);
Report->SetNumberField(TEXT("old_six_worker_median_ms"), OldMedianWall * 1000.0);
Report->SetNumberField(TEXT("new_six_worker_median_ms"), NewMedianWall * 1000.0);
const auto WorkerRemainderSeconds = [](const VoxelForgeRegionEval::FTiming& Timing)
{
return FMath::Max(0.0, Timing.WorkerCpuSeconds
- Timing.BrickClassificationSeconds - Timing.DensityEvaluationSeconds
- Timing.UniformFillSeconds);
};
Report->SetNumberField(TEXT("new_other_worker_cpu_seconds"),
WorkerRemainderSeconds(NewTrials.Last()));
Report->SetNumberField(TEXT("new_other_worker_cpu_ns_per_point"),
WorkerRemainderSeconds(NewTrials.Last()) * 1.0e9
/ static_cast<double>(NewTrials.Last().SampleCount));
Report->SetNumberField(TEXT("new_speedup_vs_old"), NewMedianWall > 0.0
? OldMedianWall / NewMedianWall : 0.0);
TArray<TSharedPtr<FJsonValue>> OldRows;
TArray<TSharedPtr<FJsonValue>> NewRows;
for (const auto& Timing : OldTrials)
{
OldRows.Add(MakeShared<FJsonValueObject>(Step2dTimingJson(Timing)));
}
for (const auto& Timing : NewTrials)
{
NewRows.Add(MakeShared<FJsonValueObject>(Step2dTimingJson(Timing)));
}
Report->SetArrayField(TEXT("old_field_trials"), OldRows);
Report->SetArrayField(TEXT("new_field_trials"), NewRows);
Report->SetObjectField(TEXT("old_full_ring_counters"), Step2dTimingJson(OldCounters));
Report->SetObjectField(TEXT("new_one_worker_determinism_pass"), Step2dTimingJson(OneWorkerTiming));
Report->SetObjectField(TEXT("new_full_detail_six_worker_evidence_pass"),
Step2dTimingJson(NewFullSixWorkerTiming));
Report->SetObjectField(TEXT("new_repeated_full_detail_six_worker_hash_pass"),
Step2dTimingJson(NewRepeatTiming));
Report->SetObjectField(TEXT("smooth_control_full_detail_pass"), Step2dTimingJson(SmoothTiming));
TSharedRef<FJsonObject> Hashes = MakeShared<FJsonObject>();
Hashes->SetStringField(TEXT("six_worker_run_1_quantized"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(NewFullSixWorkerTiming.QuantizedHash)));
Hashes->SetStringField(TEXT("six_worker_run_2_quantized"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(NewRepeatTiming.QuantizedHash)));
Hashes->SetStringField(TEXT("one_worker_quantized"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(OneWorkerTiming.QuantizedHash)));
Hashes->SetStringField(TEXT("six_worker_run_1_raw_float"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(NewFullSixWorkerTiming.RawFloatHash)));
Hashes->SetStringField(TEXT("six_worker_run_2_raw_float"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(NewRepeatTiming.RawFloatHash)));
Hashes->SetStringField(TEXT("one_worker_raw_float"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(OneWorkerTiming.RawFloatHash)));
Hashes->SetBoolField(TEXT("same_across_two_runs_and_1_vs_6_workers"), bHashesAgree);
Report->SetObjectField(TEXT("determinism"), Hashes);
TSharedRef<FJsonObject> Detail = MakeShared<FJsonObject>();
Detail->SetNumberField(TEXT("old_surface_band_samples"), static_cast<double>(OldSpectrum.SurfaceBandSamples));
Detail->SetNumberField(TEXT("new_surface_band_samples"), static_cast<double>(NewSpectrum.SurfaceBandSamples));
Detail->SetNumberField(TEXT("old_curvature_rms_lag1"), OldSpectrum.RMSLag1);
Detail->SetNumberField(TEXT("new_curvature_rms_lag1"), NewSpectrum.RMSLag1);
Detail->SetNumberField(TEXT("smoothed_control_curvature_rms_lag1"), SmoothSpectrum.RMSLag1);
Detail->SetNumberField(TEXT("old_curvature_rms_lag2"), OldSpectrum.RMSLag2);
Detail->SetNumberField(TEXT("new_curvature_rms_lag2"), NewSpectrum.RMSLag2);
Detail->SetNumberField(TEXT("smoothed_control_curvature_rms_lag2"), SmoothSpectrum.RMSLag2);
Detail->SetNumberField(TEXT("new_curvature_rms_lag4"), NewSpectrum.RMSLag4);
Detail->SetNumberField(TEXT("smoothed_control_curvature_rms_lag4"), SmoothSpectrum.RMSLag4);
Detail->SetNumberField(TEXT("old_surface_triangles_64cubed"), OldTriangleCount);
Detail->SetNumberField(TEXT("new_surface_triangles_64cubed"), NewTriangleCount);
Detail->SetNumberField(TEXT("smoothed_control_surface_triangles_64cubed"), SmoothTriangleCount);
Detail->SetNumberField(TEXT("mesh_region_origin_x_voxels"), MeshOrigin.X);
Detail->SetNumberField(TEXT("mesh_region_origin_y_voxels"), MeshOrigin.Y);
Detail->SetNumberField(TEXT("mesh_region_origin_z_voxels"), MeshOrigin.Z);
Detail->SetBoolField(TEXT("mesh_region_contains_old_and_new_iso_bands"),
bMeshContainsBothIsoBands);
Detail->SetBoolField(TEXT("new_detail_at_least_old_spectrum"), bDetailAtLeastOld);
Detail->SetNumberField(TEXT("detail_batch_raw_parity_samples"), DetailBatchParitySamples);
Detail->SetNumberField(TEXT("detail_batch_max_absolute_error"), DetailBatchMaxAbsoluteError);
Detail->SetBoolField(TEXT("detail_batch_matches_direct_field"), bDetailBatchMatchesRaw);
Detail->SetBoolField(TEXT("deliberate_smoothing_control_detected"), bSmoothControlDetected);
Detail->SetBoolField(TEXT("smoothed_control_below_old_spectrum"),
bSmoothWouldFallBelowOldSpectrum);
Report->SetObjectField(TEXT("detail_preservation"), Detail);
TSharedRef<FJsonObject> Boundaries = MakeShared<FJsonObject>();
Boundaries->SetNumberField(TEXT("brick_size_voxels"), 8);
Boundaries->SetNumberField(TEXT("feature_cell_spacing_voxels"), FeatureCellSpacing);
Boundaries->SetNumberField(TEXT("chunk_boundary_gradient_jump_rms_xy_near_surface"),
BoundaryStats.ChunkBoundaryJumpRms);
Boundaries->SetNumberField(TEXT("chunk_boundary_gradient_jump_max_xy_near_surface"),
BoundaryStats.ChunkBoundaryJumpMax);
Boundaries->SetNumberField(TEXT("brick_boundary_gradient_jump_rms"), BoundaryStats.BrickJumpRms);
Boundaries->SetNumberField(TEXT("interior_gradient_jump_rms"), BoundaryStats.InteriorJumpRms);
Boundaries->SetNumberField(TEXT("feature_cell_boundary_gradient_jump_rms"), BoundaryStats.FeatureCellJumpRms);
Boundaries->SetNumberField(TEXT("feature_cell_interior_gradient_jump_rms"),
BoundaryStats.FeatureCellInteriorJumpRms);
Boundaries->SetNumberField(TEXT("brick_boundary_samples_near_surface"),
static_cast<double>(BoundaryStats.BrickBoundarySamples));
Boundaries->SetNumberField(TEXT("chunk_boundary_samples_near_surface_xy"),
static_cast<double>(BoundaryStats.ChunkBoundarySamples));
Boundaries->SetNumberField(TEXT("macro_period_boundary_samples"),
static_cast<double>(BoundaryStats.FeatureCellBoundarySamples));
Boundaries->SetNumberField(TEXT("brick_boundary_gradient_jump_max"), BoundaryStats.BrickJumpMax);
Boundaries->SetNumberField(TEXT("feature_cell_boundary_gradient_jump_max"), BoundaryStats.FeatureCellJumpMax);
Boundaries->SetBoolField(TEXT("boundary_check_pass"), bBoundaryStatsSound);
Report->SetObjectField(TEXT("boundary_artifact_check"), Boundaries);
TSharedRef<FJsonObject> Proofs = MakeShared<FJsonObject>();
Proofs->SetNumberField(TEXT("correctly_skipped_bricks_audited"),
static_cast<double>(BrickProofAudit.CorrectlySkippedBricksAudited));
Proofs->SetNumberField(TEXT("final_field_samples_checked_including_halo"),
static_cast<double>(BrickProofAudit.CorrectProofSamplesChecked));
Proofs->SetNumberField(TEXT("correct_proof_sign_violations"),
static_cast<double>(BrickProofAudit.CorrectProofViolations));
Proofs->SetNumberField(TEXT("deliberately_undersized_detail_bound_claims_checked"),
static_cast<double>(BrickProofAudit.DeliberatelyUndersizedBoundClaimsChecked));
Proofs->SetNumberField(TEXT("wrong_sign_samples_from_undersized_bound"),
static_cast<double>(BrickProofAudit.DeliberateBoundDisagreements));
Proofs->SetNumberField(TEXT("deliberate_control_classifier_detail_scale"), 0.0);
Proofs->SetNumberField(TEXT("deliberate_control_actual_detail_scale"), 4.0);
Proofs->SetBoolField(TEXT("sound_bound_audit_pass"), bCorrectProofsSound);
Proofs->SetBoolField(TEXT("deliberate_undersized_bound_control_caught"), bUndersizedBoundCaught);
Report->SetObjectField(TEXT("brick_bound_audit"), Proofs);
TArray<TSharedPtr<FJsonValue>> Slices;
for (const FString& Name : ImageNames)
{
TSharedRef<FJsonObject> Slice = MakeShared<FJsonObject>();
Slice->SetStringField(TEXT("file"), FPaths::Combine(ResearchOut, Name));
Slice->SetStringField(TEXT("layout"), TEXT("old field on left; redefined field on right; light=air, dark=solid, cyan=near zero"));
Slices.Add(MakeShared<FJsonValueObject>(Slice));
}
Report->SetArrayField(TEXT("slices"), Slices);
FString JsonText;
const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&JsonText);
const FString ReportPath = FPaths::Combine(ResearchOut, TEXT("step2d_report.json"));
if (!FJsonSerializer::Serialize(Report, Writer)
|| !FFileHelper::SaveStringToFile(
JsonText, *ReportPath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM)
|| !FFileHelper::SaveStringToFile(
JsonText, *FPaths::Combine(Arguments.OutDirectory, TEXT("step2d_report.json")),
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] could not write report '%s'."), *ReportPath);
return 2;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeStep2d] pass=%s speed600=%s old_ms=%.3f new_ms=%.3f skip=%.1f%% "
"roughness(old/new/smooth)=%.4f/%.4f/%.4f hashes=%s report=%s"),
bPassed ? TEXT("yes") : TEXT("no"), bSpeedPass ? TEXT("yes") : TEXT("no"),
OldMedianWall * 1000.0, NewMedianWall * 1000.0,
NewTrials.Last().SampleCount > 0
? 100.0 * static_cast<double>(NewTrials.Last().SkippedSamples)
/ static_cast<double>(NewTrials.Last().SampleCount) : 0.0,
OldSpectrum.RMSLag1, NewSpectrum.RMSLag1, SmoothSpectrum.RMSLag1,
bHashesAgree ? TEXT("same") : TEXT("DIFFERENT"), *ReportPath);
return bPassed ? 0 : 2;
}
class FGeneratorDensitySampler final : public IVoxelStrateDensitySampler
{
public:
@@ -9648,6 +10475,10 @@ int32 RunBatch(const FString& Params)
int32 UVoxelForgeExploreCommandlet::Main(const FString& 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);
@@ -5,9 +5,12 @@
#include "VoxelCaveMorphology.h"
#include "VoxelDensityProfile.h"
#include "VoxelGenerator.h"
#include "VoxelRedefinedDensityField.h"
#include "VoxelStackSampler.h"
#include "HAL/PlatformTLS.h"
#include <atomic>
#if PLATFORM_WINDOWS
#include "Windows/AllowWindowsPlatformTypes.h"
THIRD_PARTY_INCLUDES_START
@@ -92,6 +95,14 @@ namespace VoxelForgeRegionEval
int32 LatticeZ = 0;
TArray<FRegionChunkSample> RequestedSamples;
};
struct FResearchBrick
{
FIntVector Origin = FIntVector::ZeroValue;
FIntVector Size = FIntVector::ZeroValue;
FIntVector Lattice = FIntVector::ZeroValue;
int32 SampleCount = 0;
};
}
int64 LinearIndex(int32 X, int32 Y, int32 Z, int32 Dimension)
@@ -138,6 +149,245 @@ namespace VoxelForgeRegionEval
return Hash;
}
bool EvaluateRedefined(
const UVoxelGenerator& Generator,
FIntVector OriginVoxels,
int32 Dimension,
int32 RequestedWorkerCount,
float DetailScale,
TArray<float>& OutDensities,
FTiming& OutTiming,
bool bEnableBrickSkips)
{
OutTiming = FTiming();
OutDensities.Reset();
if (Dimension <= 0 || Dimension > 1024 || !FMath::IsFinite(DetailScale))
{
return false;
}
const int32 NumWorkers = FMath::Clamp(RequestedWorkerCount, 1, 32);
const int64 TotalSamples = LatticeCount(Dimension);
if (TotalSamples > MAX_int32)
{
return false;
}
const double StartSeconds = FPlatformTime::Seconds();
OutTiming.Workers = NumWorkers;
OutTiming.SampleCount = TotalSamples;
OutDensities.SetNumUninitialized(static_cast<int32>(TotalSamples));
TArray<FResearchBrick> Bricks;
const FIntVector Last = OriginVoxels
+ FIntVector(Dimension - 1, Dimension - 1, Dimension - 1);
const FIntVector FirstBrick(
FMath::FloorToInt(static_cast<float>(OriginVoxels.X) / 8.0f),
FMath::FloorToInt(static_cast<float>(OriginVoxels.Y) / 8.0f),
FMath::FloorToInt(static_cast<float>(OriginVoxels.Z) / 8.0f));
const FIntVector LastBrick(
FMath::FloorToInt(static_cast<float>(Last.X) / 8.0f),
FMath::FloorToInt(static_cast<float>(Last.Y) / 8.0f),
FMath::FloorToInt(static_cast<float>(Last.Z) / 8.0f));
Bricks.Reserve((LastBrick.X - FirstBrick.X + 1)
* (LastBrick.Y - FirstBrick.Y + 1) * (LastBrick.Z - FirstBrick.Z + 1));
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);
FResearchBrick& Brick = Bricks.AddDefaulted_GetRef();
Brick.Origin = FIntVector(
FMath::Max(OriginVoxels.X, BrickWorldMin.X),
FMath::Max(OriginVoxels.Y, BrickWorldMin.Y),
FMath::Max(OriginVoxels.Z, BrickWorldMin.Z));
const FIntVector BrickEnd = BrickWorldMin + FIntVector(8, 8, 8);
const FIntVector RingEnd = OriginVoxels
+ FIntVector(Dimension, Dimension, Dimension);
const FIntVector End(
FMath::Min(BrickEnd.X, RingEnd.X),
FMath::Min(BrickEnd.Y, RingEnd.Y),
FMath::Min(BrickEnd.Z, RingEnd.Z));
Brick.Size = End - Brick.Origin;
Brick.Lattice = Brick.Origin - OriginVoxels;
Brick.SampleCount = Brick.Size.X * Brick.Size.Y * Brick.Size.Z;
}
TArray<double> WorkerWall;
TArray<double> WorkerCpu;
TArray<double> WorkerClassify;
TArray<double> WorkerEvaluate;
TArray<double> WorkerFill;
TArray<uint32> WorkerThreadIds;
TArray<uint64> WorkerEvaluated;
TArray<uint64> WorkerSkippedBricks;
TArray<uint64> WorkerSkippedSamples;
TArray<uint64> WorkerMacroChecks;
TArray<uint64> WorkerMacroCentreEvaluations;
TArray<uint64> WorkerFallbackReasons;
WorkerWall.SetNumZeroed(NumWorkers);
WorkerCpu.SetNumZeroed(NumWorkers);
WorkerClassify.SetNumZeroed(NumWorkers);
WorkerEvaluate.SetNumZeroed(NumWorkers);
WorkerFill.SetNumZeroed(NumWorkers);
WorkerThreadIds.SetNumZeroed(NumWorkers);
WorkerEvaluated.SetNumZeroed(NumWorkers);
WorkerSkippedBricks.SetNumZeroed(NumWorkers);
WorkerSkippedSamples.SetNumZeroed(NumWorkers);
WorkerMacroChecks.SetNumZeroed(NumWorkers);
WorkerMacroCentreEvaluations.SetNumZeroed(NumWorkers);
WorkerFallbackReasons.SetNumZeroed(NumWorkers * 5);
// Surface bricks carry more work than uniform interior bricks. Equal brick counts do
// not balance that work, so claim small contiguous batches dynamically. Each brick
// writes a disjoint output range and still evaluates the same exact field.
std::atomic<int32> NextBrickIndex{0};
constexpr int32 BrickBatchSize = 16;
ParallelFor(NumWorkers, [&](int32 WorkerIndex)
{
WorkerThreadIds[WorkerIndex] = FPlatformTLS::GetCurrentThreadId();
const double WorkerStart = FPlatformTime::Seconds();
double CpuStart = 0.0;
const bool bHaveCpuStart = GetCurrentThreadCpuSeconds(CpuStart);
TArray<float> BrickDetail;
VoxelForgeRedefinedDensityField::FBrickEvaluationContext BrickContext;
for (;;)
{
const int32 BatchBegin = NextBrickIndex.fetch_add(
BrickBatchSize, std::memory_order_relaxed);
if (BatchBegin >= Bricks.Num())
{
break;
}
const int32 BatchEnd = FMath::Min(BatchBegin + BrickBatchSize, Bricks.Num());
for (int32 BrickIndex = BatchBegin; BrickIndex < BatchEnd; ++BrickIndex)
{
const FResearchBrick& Brick = Bricks[BrickIndex];
bool bUniformAir = false;
bool bEvaluatedMacroCentre = false;
VoxelForgeRedefinedDensityField::EBrickFallbackReason FallbackReason =
VoxelForgeRedefinedDensityField::EBrickFallbackReason::UnsupportedState;
++WorkerMacroChecks[WorkerIndex];
const double ClassifyStart = FPlatformTime::Seconds();
const bool bCanSkip = bEnableBrickSkips
&& VoxelForgeRedefinedDensityField::TryClassifyBrick(
Generator, Brick.Origin, Brick.Size, bUniformAir, DetailScale,
&bEvaluatedMacroCentre, &FallbackReason);
WorkerClassify[WorkerIndex] += FPlatformTime::Seconds() - ClassifyStart;
WorkerMacroCentreEvaluations[WorkerIndex] += bEvaluatedMacroCentre ? 1u : 0u;
if (bEnableBrickSkips && !bCanSkip)
{
const int32 ReasonIndex = static_cast<uint8>(FallbackReason);
if (ReasonIndex < 5)
{
++WorkerFallbackReasons[WorkerIndex * 5 + ReasonIndex];
}
}
if (bCanSkip)
{
++WorkerSkippedBricks[WorkerIndex];
WorkerSkippedSamples[WorkerIndex] += static_cast<uint64>(Brick.SampleCount);
const float UniformDensity = bUniformAir ? 64.0f : -64.0f;
const double FillStart = FPlatformTime::Seconds();
for (int32 Z = 0; Z < Brick.Size.Z; ++Z)
for (int32 Y = 0; Y < Brick.Size.Y; ++Y)
for (int32 X = 0; X < Brick.Size.X; ++X)
{
const int64 LatticeIndex = LinearIndex(
Brick.Lattice.X + X, Brick.Lattice.Y + Y,
Brick.Lattice.Z + Z, Dimension);
OutDensities[static_cast<int32>(LatticeIndex)] = UniformDensity;
}
WorkerFill[WorkerIndex] += FPlatformTime::Seconds() - FillStart;
continue;
}
const double EvaluationStart = FPlatformTime::Seconds();
VoxelForgeRedefinedDensityField::BuildDetailBrick(
Generator, Brick.Origin, Brick.Size, DetailScale, BrickDetail,
&BrickContext);
for (int32 Z = 0; Z < Brick.Size.Z; ++Z)
for (int32 Y = 0; Y < Brick.Size.Y; ++Y)
for (int32 X = 0; X < Brick.Size.X; ++X)
{
const int32 WorldX = Brick.Origin.X + X;
const int32 WorldY = Brick.Origin.Y + Y;
const int32 WorldZ = Brick.Origin.Z + Z;
const int64 LatticeIndex = LinearIndex(
Brick.Lattice.X + X, Brick.Lattice.Y + Y,
Brick.Lattice.Z + Z, Dimension);
OutDensities[static_cast<int32>(LatticeIndex)] =
VoxelForgeRedefinedDensityField::EvaluateWithDetailValue(
Generator, static_cast<float>(WorldX), static_cast<float>(WorldY),
static_cast<float>(WorldZ),
BrickDetail[(Z * Brick.Size.Y + Y) * Brick.Size.X + X],
nullptr, DetailScale, &BrickContext);
++WorkerEvaluated[WorkerIndex];
}
WorkerEvaluate[WorkerIndex] += FPlatformTime::Seconds() - EvaluationStart;
}
}
WorkerWall[WorkerIndex] = FPlatformTime::Seconds() - WorkerStart;
double CpuEnd = 0.0;
if (bHaveCpuStart && GetCurrentThreadCpuSeconds(CpuEnd))
{
WorkerCpu[WorkerIndex] = FMath::Max(0.0, CpuEnd - CpuStart);
}
else
{
WorkerCpu[WorkerIndex] = WorkerWall[WorkerIndex];
}
}, EParallelForFlags::None);
OutTiming.WallSeconds = FPlatformTime::Seconds() - StartSeconds;
OutTiming.bRegionProgram = true;
OutTiming.BrickCount = static_cast<uint64>(Bricks.Num());
OutTiming.ObservedWorkerThreads = 0;
for (int32 Worker = 0; Worker < NumWorkers; ++Worker)
{
OutTiming.WorkerCpuSeconds += WorkerCpu[Worker];
OutTiming.BrickClassificationSeconds += WorkerClassify[Worker];
OutTiming.DensityEvaluationSeconds += WorkerEvaluate[Worker];
OutTiming.UniformFillSeconds += WorkerFill[Worker];
OutTiming.LongestWorkerWallSeconds = FMath::Max(
OutTiming.LongestWorkerWallSeconds, WorkerWall[Worker]);
OutTiming.EvaluatedSamples += static_cast<int64>(WorkerEvaluated[Worker]);
OutTiming.SkippedBricks += WorkerSkippedBricks[Worker];
OutTiming.SkippedSamples += WorkerSkippedSamples[Worker];
OutTiming.MacroBrickChecks += WorkerMacroChecks[Worker];
OutTiming.MacroCentreEvaluations += WorkerMacroCentreEvaluations[Worker];
OutTiming.UnsupportedBrickProofs += WorkerFallbackReasons[Worker * 5
+ static_cast<uint8>(VoxelForgeRedefinedDensityField::EBrickFallbackReason::UnsupportedState)];
OutTiming.ChunkBoundaryBrickProofs += WorkerFallbackReasons[Worker * 5
+ static_cast<uint8>(VoxelForgeRedefinedDensityField::EBrickFallbackReason::ChunkOrStrateBoundary)];
OutTiming.PassageBoundBrickProofs += WorkerFallbackReasons[Worker * 5
+ static_cast<uint8>(VoxelForgeRedefinedDensityField::EBrickFallbackReason::PassageBounds)];
OutTiming.MixedMacroBrickProofs += WorkerFallbackReasons[Worker * 5
+ static_cast<uint8>(VoxelForgeRedefinedDensityField::EBrickFallbackReason::MixedMacroInterval)];
bool bPreviouslyObserved = false;
for (int32 Previous = 0; Previous < Worker; ++Previous)
{
bPreviouslyObserved |= WorkerThreadIds[Previous] == WorkerThreadIds[Worker];
}
OutTiming.ObservedWorkerThreads += bPreviouslyObserved ? 0 : 1;
}
OutTiming.NanosecondsPerSample = TotalSamples > 0
? OutTiming.WorkerCpuSeconds * 1.0e9 / static_cast<double>(TotalSamples)
: 0.0;
OutTiming.CandidateEvaluationsPerSample = TotalSamples > 0
? static_cast<double>(OutTiming.EvaluatedSamples
+ static_cast<int64>(OutTiming.MacroCentreEvaluations))
/ static_cast<double>(TotalSamples)
: 0.0;
for (const float Density : OutDensities)
{
OutTiming.NonFiniteSamples += FMath::IsFinite(Density) ? 0u : 1u;
}
OutTiming.QuantizedHash = HashQuantized(OutDensities);
OutTiming.RawFloatHash = HashRawFloats(OutDensities);
return OutTiming.EvaluatedSamples + static_cast<int64>(OutTiming.SkippedSamples)
== TotalSamples && OutTiming.NonFiniteSamples == 0;
}
bool Evaluate(
const UVoxelGenerator& Generator,
FIntVector OriginVoxels,
@@ -26,6 +26,9 @@ namespace VoxelForgeRegionEval
double GeneralPathEntriesPerSample = 0.0;
double WallSeconds = 0.0;
double WorkerCpuSeconds = 0.0;
double BrickClassificationSeconds = 0.0;
double DensityEvaluationSeconds = 0.0;
double UniformFillSeconds = 0.0;
double NanosecondsPerSample = 0.0;
bool bWorkerCpuFromOsThreadTimes = false;
bool bRegionProgram = false;
@@ -34,6 +37,16 @@ namespace VoxelForgeRegionEval
uint64 RawFloatHash = 0;
uint64 SaturatedSamples = 0;
uint64 NonFiniteSamples = 0;
uint64 BrickCount = 0;
uint64 SkippedBricks = 0;
uint64 SkippedSamples = 0;
uint64 MacroBrickChecks = 0;
uint64 MacroCentreEvaluations = 0;
uint64 UnsupportedBrickProofs = 0;
uint64 ChunkBoundaryBrickProofs = 0;
uint64 PassageBoundBrickProofs = 0;
uint64 MixedMacroBrickProofs = 0;
double LongestWorkerWallSeconds = 0.0;
uint64 SdfCacheBuilds = 0;
uint64 ActiveOpListBuilds = 0;
uint64 BroadPhaseIndexBuilds = 0;
@@ -122,6 +135,17 @@ namespace VoxelForgeRegionEval
int32 RequestedWorkerCount = WorkerCount,
bool bCollectWholeRunCounters = false);
/** Full-resolution step 2d field pass with sound world-aligned 8^3 brick proofs. */
bool EvaluateRedefined(
const UVoxelGenerator& Generator,
FIntVector OriginVoxels,
int32 Dimension,
int32 RequestedWorkerCount,
float DetailScale,
TArray<float>& OutDensities,
FTiming& OutTiming,
bool bEnableBrickSkips = true);
/** Count exact room/tunnel/core/passage candidate visits with instrumentation enabled. */
FCandidateVisits CountCandidates(
const UVoxelGenerator& Generator,