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:
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);
|
||||
|
||||
Reference in new issue
Block a user