wip(step2e): parameterised chamber-graph model -- compiles, traversal test PASS; benchmark not run

Stopped by low memory (another app held 12.5 GB; processes failed with 0xC0000142).
Editor-only, default off: a seeded connected room graph (varied chambers on a spanning network,
optional loops, shafts, rock features, ledges, basin floors), eight parameter profiles, the
step-2d brick driver and detail bands, a new test VoxelForge.ParameterizedGeneration.TraversalSeeds
(PASS in step2e_functional_01, which also proves the tree compiles). Existing live tests untouched.
Not yet run: the eight-profile ring benchmark, variety metrics, slices, detail/artefact/determinism
checks.

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 11:45:45 +02:00
1 parent 78641da0bb
commit f8a46ecf8d
9 files changed
+1585 -51

No files matched your search

@@ -2552,7 +2552,8 @@ struct FStep2dBrickProofAudit
FStep2dBrickProofAudit AuditStep2dBrickProofs(
const UVoxelGenerator& Generator,
FIntVector Origin,
int32 Dimension)
int32 Dimension,
bool bParameterized = false)
{
FStep2dBrickProofAudit Result;
const FIntVector Last = Origin + FIntVector(Dimension - 1, Dimension - 1, Dimension - 1);
@@ -2580,8 +2581,12 @@ FStep2dBrickProofAudit AuditStep2dBrickProofs(
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 bSoundClaim = bParameterized
? VoxelForgeRedefinedDensityField::TryClassifyParameterizedBrick(
Generator, BrickOrigin, Size, bProvenAir, 1.0f)
: VoxelForgeRedefinedDensityField::TryClassifyBrick(
Generator, BrickOrigin, Size, bProvenAir, 1.0f);
if (bSoundClaim)
{
const bool bAuditThisSkip = (CorrectSkipOrdinal++ % 128u) == 0u;
if (bAuditThisSkip)
@@ -2591,11 +2596,15 @@ FStep2dBrickProofAudit AuditStep2dBrickProofs(
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);
const float Density = bParameterized
? VoxelForgeRedefinedDensityField::EvaluateParameterized(
Generator, static_cast<float>(BrickOrigin.X + X),
static_cast<float>(BrickOrigin.Y + Y),
static_cast<float>(BrickOrigin.Z + Z), nullptr, 1.0f)
: VoxelForgeRedefinedDensityField::Evaluate(
Generator, static_cast<float>(BrickOrigin.X + X),
static_cast<float>(BrickOrigin.Y + Y),
static_cast<float>(BrickOrigin.Z + Z), nullptr, 1.0f);
++Result.CorrectProofSamplesChecked;
if ((Density > 0.0f) != bProvenAir)
{
@@ -2609,21 +2618,27 @@ FStep2dBrickProofAudit AuditStep2dBrickProofs(
// 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))
const bool bUndersizedClaim = bParameterized
? VoxelForgeRedefinedDensityField::TryClassifyParameterizedBrick(
Generator, BrickOrigin, Size, bDeliberatelyClaimedAir, 0.0f)
: VoxelForgeRedefinedDensityField::TryClassifyBrick(
Generator, BrickOrigin, Size, bDeliberatelyClaimedAir, 0.0f);
if (bUndersizedClaim)
{
++Result.DeliberatelyUndersizedBoundClaimsChecked;
for (int32 Z = 0; Z < Size.Z; ++Z)
for (int32 Y = 0; Y < Size.Y; ++Y)
for (int32 X = 0; X < Size.X; ++X)
{
const float MaximumDetailDensity = VoxelForgeRedefinedDensityField::Evaluate(
Generator,
static_cast<float>(BrickOrigin.X + X),
static_cast<float>(BrickOrigin.Y + Y),
static_cast<float>(BrickOrigin.Z + Z),
nullptr,
4.0f);
const float MaximumDetailDensity = bParameterized
? VoxelForgeRedefinedDensityField::EvaluateParameterized(
Generator, static_cast<float>(BrickOrigin.X + X),
static_cast<float>(BrickOrigin.Y + Y),
static_cast<float>(BrickOrigin.Z + Z), nullptr, 4.0f)
: VoxelForgeRedefinedDensityField::Evaluate(
Generator, static_cast<float>(BrickOrigin.X + X),
static_cast<float>(BrickOrigin.Y + Y),
static_cast<float>(BrickOrigin.Z + Z), nullptr, 4.0f);
if ((MaximumDetailDensity > 0.0f) != bDeliberatelyClaimedAir)
{
++Result.DeliberateBoundDisagreements;
@@ -2847,6 +2862,200 @@ void SaveStep2dSideBySideSlice(
SavePng(Path, Pixels, Width, Height, OutError);
}
struct FStep2eVarietyMetrics
{
double OpenFraction = 0.0;
double SurfaceAreaSquareMeters = 0.0;
double SurfaceAreaPerVolume = 0.0;
double VerticalAdjacencyShare = 0.0;
double TunnelNeckShare = 0.0;
int32 ConnectedComponentsAtOneMeter = 0;
int64 LargestComponentCubicMeters = 0;
int64 SmallComponentsUpToEightCubicMeters = 0;
int64 MediumComponentsNineToSixtyFourCubicMeters = 0;
int64 LargeComponentsSixtyFiveToFiveHundredTwelveCubicMeters = 0;
int64 VeryLargeComponentsOverFiveHundredTwelveCubicMeters = 0;
};
FStep2eVarietyMetrics ComputeStep2eVarietyMetrics(
const TArray<float>& Density, int32 Dimension)
{
FStep2eVarietyMetrics Result;
const int64 Total = static_cast<int64>(Dimension) * Dimension * Dimension;
int64 AirCount = 0;
uint64 SurfaceEdgeCount = 0;
uint64 HorizontalAdjacencies = 0;
uint64 VerticalAdjacencies = 0;
for (int32 Z = 0; Z < Dimension; ++Z)
for (int32 Y = 0; Y < Dimension; ++Y)
for (int32 X = 0; X < Dimension; ++X)
{
const int32 Index = (Z * Dimension + Y) * Dimension + X;
const bool bAir = Density[Index] > 0.0f;
AirCount += bAir ? 1 : 0;
if (X + 1 < Dimension)
{
const bool bNextAir = Density[Index + 1] > 0.0f;
SurfaceEdgeCount += bAir != bNextAir ? 1u : 0u;
HorizontalAdjacencies += bAir && bNextAir ? 1u : 0u;
}
if (Y + 1 < Dimension)
{
const bool bNextAir = Density[Index + Dimension] > 0.0f;
SurfaceEdgeCount += bAir != bNextAir ? 1u : 0u;
HorizontalAdjacencies += bAir && bNextAir ? 1u : 0u;
}
if (Z + 1 < Dimension)
{
const bool bNextAir = Density[Index + Dimension * Dimension] > 0.0f;
SurfaceEdgeCount += bAir != bNextAir ? 1u : 0u;
VerticalAdjacencies += bAir && bNextAir ? 1u : 0u;
}
}
Result.OpenFraction = Total > 0 ? static_cast<double>(AirCount) / Total : 0.0;
// Each sign-changing lattice edge estimates one quarter-meter-squared patch. This is a
// consistent edge-count estimate, not a marching-cubes triangle integral.
Result.SurfaceAreaSquareMeters = static_cast<double>(SurfaceEdgeCount) * 0.25 * 0.25;
const double VolumeCubicMeters = static_cast<double>(Total) * 0.25 * 0.25 * 0.25;
Result.SurfaceAreaPerVolume = VolumeCubicMeters > 0.0
? Result.SurfaceAreaSquareMeters / VolumeCubicMeters : 0.0;
const uint64 AllAdjacencies = HorizontalAdjacencies + VerticalAdjacencies;
Result.VerticalAdjacencyShare = AllAdjacencies > 0
? static_cast<double>(VerticalAdjacencies) / AllAdjacencies : 0.0;
constexpr int32 CoarseStep = 4;
const int32 CoarseDimension = (Dimension - 1) / CoarseStep + 1;
const int32 CoarseCount = CoarseDimension * CoarseDimension * CoarseDimension;
TArray<int32> Parent;
Parent.Init(INDEX_NONE, CoarseCount);
const auto CoarseIndex = [CoarseDimension](int32 X, int32 Y, int32 Z)
{
return (Z * CoarseDimension + Y) * CoarseDimension + X;
};
int64 CoarseAirCount = 0;
int64 TunnelNeckCount = 0;
for (int32 Z = 0; Z < CoarseDimension; ++Z)
for (int32 Y = 0; Y < CoarseDimension; ++Y)
for (int32 X = 0; X < CoarseDimension; ++X)
{
const int32 SourceIndex = ((Z * CoarseStep) * Dimension + Y * CoarseStep)
* Dimension + X * CoarseStep;
if (Density[SourceIndex] <= 0.0f)
{
continue;
}
const int32 Index = CoarseIndex(X, Y, Z);
Parent[Index] = Index;
++CoarseAirCount;
int32 OpenNeighbors = 0;
const int32 Delta[6][3] = {
{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}};
for (const auto& D : Delta)
{
const int32 NX = X + D[0];
const int32 NY = Y + D[1];
const int32 NZ = Z + D[2];
if (NX < 0 || NY < 0 || NZ < 0
|| NX >= CoarseDimension || NY >= CoarseDimension || NZ >= CoarseDimension)
{
continue;
}
const int32 NSource = ((NZ * CoarseStep) * Dimension + NY * CoarseStep)
* Dimension + NX * CoarseStep;
OpenNeighbors += Density[NSource] > 0.0f ? 1 : 0;
}
TunnelNeckCount += OpenNeighbors <= 4 ? 1 : 0;
}
Result.TunnelNeckShare = CoarseAirCount > 0
? static_cast<double>(TunnelNeckCount) / CoarseAirCount : 0.0;
const auto FindRoot = [&Parent](int32 Index)
{
int32 Root = Index;
while (Parent[Root] != Root)
{
Root = Parent[Root];
}
while (Parent[Index] != Index)
{
const int32 Next = Parent[Index];
Parent[Index] = Root;
Index = Next;
}
return Root;
};
for (int32 Z = 0; Z < CoarseDimension; ++Z)
for (int32 Y = 0; Y < CoarseDimension; ++Y)
for (int32 X = 0; X < CoarseDimension; ++X)
{
const int32 Index = CoarseIndex(X, Y, Z);
if (Parent[Index] == INDEX_NONE)
{
continue;
}
const int32 Neighbors[3] = {
X > 0 ? CoarseIndex(X - 1, Y, Z) : INDEX_NONE,
Y > 0 ? CoarseIndex(X, Y - 1, Z) : INDEX_NONE,
Z > 0 ? CoarseIndex(X, Y, Z - 1) : INDEX_NONE};
for (const int32 Neighbor : Neighbors)
{
if (Neighbor == INDEX_NONE || Parent[Neighbor] == INDEX_NONE)
{
continue;
}
const int32 RootA = FindRoot(Index);
const int32 RootB = FindRoot(Neighbor);
if (RootA != RootB)
{
Parent[RootB] = RootA;
}
}
}
TMap<int32, int64> ComponentSizes;
for (int32 Index = 0; Index < CoarseCount; ++Index)
{
if (Parent[Index] != INDEX_NONE)
{
++ComponentSizes.FindOrAdd(FindRoot(Index));
}
}
Result.ConnectedComponentsAtOneMeter = ComponentSizes.Num();
for (const TPair<int32, int64>& Pair : ComponentSizes)
{
const int64 Size = Pair.Value;
Result.LargestComponentCubicMeters = FMath::Max(Result.LargestComponentCubicMeters, Size);
if (Size <= 8) ++Result.SmallComponentsUpToEightCubicMeters;
else if (Size <= 64) ++Result.MediumComponentsNineToSixtyFourCubicMeters;
else if (Size <= 512) ++Result.LargeComponentsSixtyFiveToFiveHundredTwelveCubicMeters;
else ++Result.VeryLargeComponentsOverFiveHundredTwelveCubicMeters;
}
return Result;
}
TSharedRef<FJsonObject> Step2eParametersJson(
const VoxelForgeRedefinedDensityField::FGenerationParameters& P)
{
TSharedRef<FJsonObject> Json = MakeShared<FJsonObject>();
Json->SetNumberField(TEXT("room_pitch_voxels"), P.RoomPitchVoxels);
Json->SetNumberField(TEXT("room_radius_fraction_min"), P.MinimumRoomRadiusFraction);
Json->SetNumberField(TEXT("room_radius_fraction_max"), P.MaximumRoomRadiusFraction);
Json->SetNumberField(TEXT("room_height_voxels"), P.RoomHeightVoxels);
Json->SetNumberField(TEXT("floor_relief_voxels"), P.FloorReliefVoxels);
Json->SetNumberField(TEXT("tunnel_radius_voxels"), P.TunnelRadiusVoxels);
Json->SetNumberField(TEXT("tunnel_half_height_voxels"), P.TunnelHalfHeightVoxels);
Json->SetNumberField(TEXT("loop_probability"), P.LoopProbability);
Json->SetNumberField(TEXT("vertical_shaft_probability"), P.VerticalShaftProbability);
Json->SetNumberField(TEXT("shaft_radius_voxels"), P.ShaftRadiusVoxels);
Json->SetNumberField(TEXT("pillar_probability"), P.PillarProbability);
Json->SetNumberField(TEXT("ledge_probability"), P.LedgeProbability);
Json->SetNumberField(TEXT("floating_mass_probability"), P.FloatingMassProbability);
Json->SetNumberField(TEXT("stalactite_stalagmite_probability"), P.SpireProbability);
Json->SetNumberField(TEXT("basin_probability"), P.BasinProbability);
Json->SetNumberField(TEXT("basin_depth_voxels"), P.BasinDepthVoxels);
Json->SetNumberField(TEXT("water_level_fraction"), P.WaterLevelFraction);
return Json;
}
TSharedRef<FJsonObject> Step2dTimingJson(const VoxelForgeRegionEval::FTiming& Timing)
{
TSharedRef<FJsonObject> Json = MakeShared<FJsonObject>();
@@ -3338,6 +3547,405 @@ int32 RunStep2d(const FString& Params)
return bPassed ? 0 : 2;
}
int32 RunStep2e(const FString& Params)
{
FExploreArguments Arguments;
FString Error;
if (!ParseArguments(Params, Arguments, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2e] argument error: %s"), *Error);
return 2;
}
int32 Seed = 1337;
FParse::Value(*Params, TEXT("seed="), Seed);
int32 Workers = VoxelForgeRegionEval::WorkerCount;
FParse::Value(*Params, TEXT("regionworkers="), Workers);
Workers = FMath::Clamp(Workers, 1, 32);
FString ResearchOut;
FParse::Value(*Params, TEXT("researchout="), ResearchOut);
if (ResearchOut.IsEmpty())
{
ResearchOut = FPaths::Combine(Arguments.OutDirectory, TEXT("Research/step2e"));
}
ResearchOut = FPaths::ConvertRelativePathToFull(ResearchOut);
IFileManager::Get().MakeDirectory(*ResearchOut, true);
IFileManager::Get().MakeDirectory(*Arguments.OutDirectory, true);
FExploreWorld World;
int32 RecipeIndex = INDEX_NONE;
uint32 RecipeHash = 0;
int32 EditChunkCount = 0;
const double PrepareStart = FPlatformTime::Seconds();
if (!PrepareRegionEvalWorld(World, Arguments, Seed,
RecipeIndex, RecipeHash, EditChunkCount, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2e] world preparation failed: %s"), *Error);
return 2;
}
const double WorldPrepareSeconds = FPlatformTime::Seconds() - PrepareStart;
if (World.DiffLayer != nullptr)
{
World.DiffLayer->Clear();
}
const int32 Dimension = VoxelForgeRegionEval::RingDimension;
const int64 TotalSamples = static_cast<int64>(Dimension) * Dimension * Dimension;
const int32 CenterZ = (World.TargetTopWorldZ + World.TargetBottomWorldZ) / 2;
const FIntVector Origin(-129, -129, CenterZ - 129);
VoxelForgeRedefinedDensityField::SetEnabled(false);
VoxelForgeRedefinedDensityField::SetParameterizedEnabled(false);
VoxelForgeRedefinedDensityField::SetParameterSet(-1);
VoxelForgeRedefinedDensityField::SetDetailScale(1.0f);
TArray<float> OldDensities;
VoxelForgeRegionEval::FTiming OldTiming;
if (!VoxelForgeRegionEval::Evaluate(*World.Generator, Origin, Dimension,
nullptr, true, false, 1.0f, &OldDensities, OldTiming, false, Workers, false))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2e] live comparison ring failed."));
return 2;
}
VoxelForgeRedefinedDensityField::SetParameterizedEnabled(true);
TArray<TSharedPtr<FJsonValue>> ProfileRows;
TArray<TSharedPtr<FJsonValue>> SliceRows;
TArray<double> ProfileMedianMs;
bool bProfilePasses = true;
VoxelForgeRedefinedDensityField::FGenerationParameters HardestParameters;
const TCHAR* HardestName = TEXT("HyperComplex");
for (int32 Profile = 0;
Profile < VoxelForgeRedefinedDensityField::GetParameterSetCount(); ++Profile)
{
const TCHAR* ProfileName = nullptr;
VoxelForgeRedefinedDensityField::FGenerationParameters ProfileParameters;
if (!VoxelForgeRedefinedDensityField::GetParameterSetInfo(
Profile, ProfileName, ProfileParameters))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2e] missing profile %d."), Profile);
return 2;
}
VoxelForgeRedefinedDensityField::SetParameterSet(Profile);
TArray<double> Times;
TArray<float> ProfileDensities;
VoxelForgeRegionEval::FTiming LastTiming;
for (int32 Trial = 0; Trial < 3; ++Trial)
{
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
ProfileDensities, LastTiming, true, true))
{
UE_LOG(LogTemp, Error,
TEXT("[VoxelForgeStep2e] profile %d six-worker ring trial %d failed."),
Profile, Trial + 1);
return 2;
}
Times.Add(LastTiming.WallSeconds * 1000.0);
}
Times.Sort();
const double MedianMs = Times[Times.Num() / 2];
ProfileMedianMs.Add(MedianMs);
bProfilePasses &= MedianMs <= 600.0;
const FStep2eVarietyMetrics Variety = ComputeStep2eVarietyMetrics(
ProfileDensities, Dimension);
TSharedRef<FJsonObject> Row = MakeShared<FJsonObject>();
Row->SetNumberField(TEXT("profile_index"), Profile);
Row->SetStringField(TEXT("name"), ProfileName);
Row->SetObjectField(TEXT("parameters"), Step2eParametersJson(ProfileParameters));
Row->SetNumberField(TEXT("six_worker_ring_median_ms"), MedianMs);
TArray<TSharedPtr<FJsonValue>> TrialValues;
TrialValues.Add(MakeShared<FJsonValueNumber>(Times[0]));
TrialValues.Add(MakeShared<FJsonValueNumber>(Times[1]));
TrialValues.Add(MakeShared<FJsonValueNumber>(Times[2]));
Row->SetArrayField(TEXT("six_worker_ring_trials_ms"), TrialValues);
Row->SetNumberField(TEXT("open_air_fraction"), Variety.OpenFraction);
Row->SetNumberField(TEXT("estimated_surface_area_m2"), Variety.SurfaceAreaSquareMeters);
Row->SetNumberField(TEXT("estimated_surface_area_per_volume_m_inv"),
Variety.SurfaceAreaPerVolume);
Row->SetNumberField(TEXT("vertical_air_adjacency_share"), Variety.VerticalAdjacencyShare);
Row->SetNumberField(TEXT("one_meter_tunnel_neck_share"), Variety.TunnelNeckShare);
Row->SetNumberField(TEXT("one_meter_connected_components"),
Variety.ConnectedComponentsAtOneMeter);
Row->SetNumberField(TEXT("largest_component_coarse_m3"), Variety.LargestComponentCubicMeters);
Row->SetNumberField(TEXT("components_1_to_8_coarse_m3"), Variety.SmallComponentsUpToEightCubicMeters);
Row->SetNumberField(TEXT("components_9_to_64_coarse_m3"), Variety.MediumComponentsNineToSixtyFourCubicMeters);
Row->SetNumberField(TEXT("components_65_to_512_coarse_m3"), Variety.LargeComponentsSixtyFiveToFiveHundredTwelveCubicMeters);
Row->SetNumberField(TEXT("components_over_512_coarse_m3"), Variety.VeryLargeComponentsOverFiveHundredTwelveCubicMeters);
Row->SetNumberField(TEXT("skipped_bricks"), static_cast<double>(LastTiming.SkippedBricks));
Row->SetNumberField(TEXT("skipped_sample_fraction"), LastTiming.SampleCount > 0
? static_cast<double>(LastTiming.SkippedSamples) / LastTiming.SampleCount : 0.0);
FString SafeName(ProfileName);
SafeName.ToLowerInline();
const FString SetName = FString::Printf(TEXT("set_%02d_%s"), Profile, *SafeName);
const FString SetDirectory = FPaths::Combine(ResearchOut, SetName);
IFileManager::Get().MakeDirectory(*SetDirectory, true);
const FString ImageNames[] = {
TEXT("slice_xy_center.png"), TEXT("slice_xz_y0.png"),
TEXT("slice_yz_x0.png"), TEXT("slice_xz_passage.png")};
FString SliceError;
SaveStep2dSideBySideSlice(FPaths::Combine(SetDirectory, ImageNames[0]),
OldDensities, ProfileDensities, Dimension, TEXT('h'), Dimension / 2, SliceError);
SaveStep2dSideBySideSlice(FPaths::Combine(SetDirectory, ImageNames[1]),
OldDensities, ProfileDensities, Dimension, TEXT('x'), Dimension / 2, SliceError);
SaveStep2dSideBySideSlice(FPaths::Combine(SetDirectory, ImageNames[2]),
OldDensities, ProfileDensities, Dimension, TEXT('y'), Dimension / 2, SliceError);
int32 PassageIndex = Dimension / 2;
if (World.Manager != nullptr && !World.Manager->GetPassages().IsEmpty())
{
PassageIndex = FMath::Clamp(FMath::RoundToInt(
World.Manager->GetPassages()[0].LowerLanding.StandingPoint.Y) - Origin.Y,
0, Dimension - 1);
}
SaveStep2dSideBySideSlice(FPaths::Combine(SetDirectory, ImageNames[3]),
OldDensities, ProfileDensities, Dimension, TEXT('x'), PassageIndex, SliceError);
if (!SliceError.IsEmpty())
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2e] slice write failed for %s: %s"),
ProfileName, *SliceError);
return 2;
}
Row->SetStringField(TEXT("slice_directory"), SetDirectory);
ProfileRows.Add(MakeShared<FJsonValueObject>(Row));
for (const FString& ImageName : ImageNames)
{
TSharedRef<FJsonObject> Slice = MakeShared<FJsonObject>();
Slice->SetStringField(TEXT("profile"), ProfileName);
Slice->SetStringField(TEXT("file"), FPaths::Combine(SetDirectory, ImageName));
Slice->SetStringField(TEXT("layout"),
TEXT("today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"));
SliceRows.Add(MakeShared<FJsonValueObject>(Slice));
}
if (Profile == VoxelForgeRedefinedDensityField::GetParameterSetCount() - 1)
{
HardestParameters = ProfileParameters;
HardestName = ProfileName;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeStep2e] profile=%d/%s ring_ms=%.3f open=%.3f area=%.1f m2 components=%d"),
Profile, ProfileName, MedianMs, Variety.OpenFraction,
Variety.SurfaceAreaSquareMeters, Variety.ConnectedComponentsAtOneMeter);
}
const int32 HardestProfile = VoxelForgeRedefinedDensityField::GetParameterSetCount() - 1;
VoxelForgeRedefinedDensityField::SetParameterSet(HardestProfile);
TArray<float> HardestFullDensity;
TArray<float> RepeatFullDensity;
TArray<float> OneWorkerDensity;
TArray<float> SmoothDensity;
VoxelForgeRegionEval::FTiming HardestFullTiming;
VoxelForgeRegionEval::FTiming RepeatTiming;
VoxelForgeRegionEval::FTiming OneWorkerTiming;
VoxelForgeRegionEval::FTiming SmoothTiming;
const bool bHardestFullOk = VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
HardestFullDensity, HardestFullTiming, false, true);
const bool bRepeatOk = bHardestFullOk && VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
RepeatFullDensity, RepeatTiming, false, true);
const bool bOneWorkerOk = bHardestFullOk && VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, 1, 1.0f,
OneWorkerDensity, OneWorkerTiming, false, true);
const bool bSmoothOk = bHardestFullOk && VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 0.0f,
SmoothDensity, SmoothTiming, false, true);
if (!bHardestFullOk || !bRepeatOk || !bOneWorkerOk || !bSmoothOk)
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2e] hardest-set control pass failed."));
return 2;
}
int64 DetailParitySamples = 0;
double DetailParityMaxError = 0.0;
for (int64 Index = 0; Index < TotalSamples; Index += 2047)
{
const int32 X = static_cast<int32>(Index % Dimension);
const int32 Y = static_cast<int32>((Index / Dimension) % Dimension);
const int32 Z = static_cast<int32>(Index
/ (static_cast<int64>(Dimension) * Dimension));
const float Direct = VoxelForgeRedefinedDensityField::EvaluateParameterized(
*World.Generator, static_cast<float>(Origin.X + X),
static_cast<float>(Origin.Y + Y), static_cast<float>(Origin.Z + Z), nullptr, 1.0f);
DetailParityMaxError = FMath::Max(DetailParityMaxError,
static_cast<double>(FMath::Abs(Direct - HardestFullDensity[static_cast<int32>(Index)])));
++DetailParitySamples;
}
const bool bDetailParity = DetailParityMaxError <= 1.0e-5;
const FStep2dSurfaceSpectrum OldSpectrum = ComputeStep2dSpectrum(OldDensities, Dimension);
const FStep2dSurfaceSpectrum NewSpectrum = ComputeStep2dSpectrum(HardestFullDensity, Dimension);
const FStep2dSurfaceSpectrum SmoothSpectrum = ComputeStep2dSpectrum(SmoothDensity, Dimension);
const FStep2dBoundaryStats BoundaryStats = ComputeStep2dBoundaryStats(
HardestFullDensity, Dimension, Origin,
FMath::RoundToInt(HardestParameters.RoomPitchVoxels));
const FStep2dBrickProofAudit ProofAudit = AuditStep2dBrickProofs(
*World.Generator, Origin, Dimension, true);
const uint64 HardestHash = VoxelForgeRegionEval::HashQuantized(HardestFullDensity);
const uint64 RepeatHash = VoxelForgeRegionEval::HashQuantized(RepeatFullDensity);
const uint64 OneWorkerHash = VoxelForgeRegionEval::HashQuantized(OneWorkerDensity);
const bool bDeterminism = HardestHash == RepeatHash && HardestHash == OneWorkerHash
&& HardestFullTiming.RawFloatHash == RepeatTiming.RawFloatHash
&& HardestFullTiming.RawFloatHash == OneWorkerTiming.RawFloatHash;
const bool bDetailAtLeastLive = NewSpectrum.RMSLag1 >= OldSpectrum.RMSLag1
&& NewSpectrum.RMSLag2 >= OldSpectrum.RMSLag2;
const bool bSmoothingControlCaught = SmoothSpectrum.RMSLag1 < NewSpectrum.RMSLag1 * 0.80
|| SmoothSpectrum.RMSLag2 < NewSpectrum.RMSLag2 * 0.80;
const bool bBoundaryPass = 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 bBoundAudit = ProofAudit.CorrectlySkippedBricksAudited > 0
&& ProofAudit.CorrectProofSamplesChecked > 0
&& ProofAudit.CorrectProofViolations == 0;
const bool bUndersizedBoundCaught = ProofAudit.DeliberateBoundDisagreements > 0;
double WorstMedianMs = 0.0;
for (const double MedianMs : ProfileMedianMs)
{
WorstMedianMs = FMath::Max(WorstMedianMs, MedianMs);
}
const bool bSpeedPass = Workers == 6 && bProfilePasses && WorstMedianMs <= 600.0;
const bool bPassed = bSpeedPass && bDeterminism && bDetailAtLeastLive
&& bSmoothingControlCaught && bDetailParity && bBoundaryPass
&& bBoundAudit && bUndersizedBoundCaught
&& HardestFullTiming.NonFiniteSamples == 0;
TSharedRef<FJsonObject> Report = MakeShared<FJsonObject>();
Report->SetStringField(TEXT("status"), bPassed ? TEXT("pass") : TEXT("fail"));
Report->SetBoolField(TEXT("pass"), bPassed);
Report->SetBoolField(TEXT("worst_profile_meets_600ms"), bSpeedPass);
Report->SetNumberField(TEXT("seed"), Seed);
Report->SetNumberField(TEXT("ring_dimension"), Dimension);
Report->SetNumberField(TEXT("ring_samples"), static_cast<double>(TotalSamples));
Report->SetNumberField(TEXT("workers_requested"), Workers);
Report->SetNumberField(TEXT("world_prepare_ms_excluded"), WorldPrepareSeconds * 1000.0);
Report->SetNumberField(TEXT("old_live_ring_ms"), OldTiming.WallSeconds * 1000.0);
Report->SetStringField(TEXT("old_live_ring_raw_float_hash"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(OldTiming.RawFloatHash)));
Report->SetStringField(TEXT("field_definition"),
TEXT("Seeded jittered XY chamber cells form a deterministic origin-rooted spanning tree; "
"selected loops, horizontal tunnels and full-strate shafts open the network. Each "
"chamber can add pillars, annular ledges, paired stalactites/stalagmites, floating "
"rock masses and a bowl floor with an explicit water level. Passage pads and anchor "
"tunnels join the authored inter-strate route to the generated network."));
Report->SetStringField(TEXT("parameter_space"),
TEXT("Eight named profiles can be assigned per strate from a seed hash (-1 mode), or "
"forced for measurement. Each profile rolls cell placement and feature choices from "
"the world seed and strate index; see parameters and documented ranges in the report."));
Report->SetStringField(TEXT("resolution_contract"),
TEXT("One exact field sample per 25 cm lattice point. The four existing 8/4/2/1-sample "
"detail bands are evaluated for every potentially surface-affecting point; no coarse "
"field is interpolated onto the lattice. 8-cubed skips require the final macro field "
"Lipschitz interval plus the full 5.25-unit detail supremum and marching-cubes halo."));
Report->SetStringField(TEXT("finer_lattice_scaling"),
TEXT("For 12.5 cm voxels, double room pitch, room height, tunnel/feature dimensions and "
"the broad-to-medium noise periods in sample units to preserve meter-scale shapes. "
"Keep the 1-sample detail band or add a finer octave. The lattice grows 8x for a fixed "
"world volume; surface bricks grow roughly with area while certified interior bricks "
"remain cheap. This scale has not been timed."));
Report->SetStringField(TEXT("variety_metric_method"),
TEXT("Open fraction uses every ring sample. Surface area estimates sign-changing lattice "
"edges times 0.25 m squared. Chamber-size bins, connected components and tunnel-neck "
"share use a 1 m downsampled six-neighbor occupancy grid; verticality is the share of "
"open 1-cell adjacencies that point vertically."));
Report->SetStringField(TEXT("water_geometry_limit"),
TEXT("Basin profiles generate the bowl floor and water-level parameter; this density field "
"does not render or simulate water."));
Report->SetNumberField(TEXT("worst_six_worker_ring_median_ms"), WorstMedianMs);
Report->SetNumberField(TEXT("target_aim_ms"), 350.0);
Report->SetStringField(TEXT("hardest_profile"), HardestName);
Report->SetArrayField(TEXT("profiles"), ProfileRows);
Report->SetArrayField(TEXT("slices"), SliceRows);
Report->SetStringField(TEXT("slice_directory"), ResearchOut);
TSharedRef<FJsonObject> Detail = MakeShared<FJsonObject>();
Detail->SetNumberField(TEXT("live_surface_band_samples"), static_cast<double>(OldSpectrum.SurfaceBandSamples));
Detail->SetNumberField(TEXT("new_surface_band_samples"), static_cast<double>(NewSpectrum.SurfaceBandSamples));
Detail->SetNumberField(TEXT("live_curvature_rms_lag1"), OldSpectrum.RMSLag1);
Detail->SetNumberField(TEXT("new_curvature_rms_lag1"), NewSpectrum.RMSLag1);
Detail->SetNumberField(TEXT("smooth_control_curvature_rms_lag1"), SmoothSpectrum.RMSLag1);
Detail->SetNumberField(TEXT("live_curvature_rms_lag2"), OldSpectrum.RMSLag2);
Detail->SetNumberField(TEXT("new_curvature_rms_lag2"), NewSpectrum.RMSLag2);
Detail->SetNumberField(TEXT("smooth_control_curvature_rms_lag2"), SmoothSpectrum.RMSLag2);
Detail->SetNumberField(TEXT("detail_raw_parity_samples"), DetailParitySamples);
Detail->SetNumberField(TEXT("detail_raw_parity_max_error"), DetailParityMaxError);
Detail->SetBoolField(TEXT("detail_at_least_live_spectrum"), bDetailAtLeastLive);
Detail->SetBoolField(TEXT("deliberate_zero_detail_smoothing_detected"), bSmoothingControlCaught);
Detail->SetBoolField(TEXT("direct_vs_brick_detail_parity"), bDetailParity);
Report->SetObjectField(TEXT("detail_check"), Detail);
TSharedRef<FJsonObject> Boundary = MakeShared<FJsonObject>();
Boundary->SetNumberField(TEXT("brick_boundary_gradient_jump_rms"), BoundaryStats.BrickJumpRms);
Boundary->SetNumberField(TEXT("interior_gradient_jump_rms"), BoundaryStats.InteriorJumpRms);
Boundary->SetNumberField(TEXT("chunk_boundary_gradient_jump_rms"), BoundaryStats.ChunkBoundaryJumpRms);
Boundary->SetNumberField(TEXT("feature_cell_boundary_gradient_jump_rms"), BoundaryStats.FeatureCellJumpRms);
Boundary->SetNumberField(TEXT("feature_cell_interior_gradient_jump_rms"), BoundaryStats.FeatureCellInteriorJumpRms);
Boundary->SetBoolField(TEXT("boundary_artifact_check_pass"), bBoundaryPass);
Report->SetObjectField(TEXT("boundary_artifact_check"), Boundary);
TSharedRef<FJsonObject> Proofs = MakeShared<FJsonObject>();
Proofs->SetNumberField(TEXT("correctly_skipped_bricks_audited"),
static_cast<double>(ProofAudit.CorrectlySkippedBricksAudited));
Proofs->SetNumberField(TEXT("final_field_samples_checked_including_halo"),
static_cast<double>(ProofAudit.CorrectProofSamplesChecked));
Proofs->SetNumberField(TEXT("correct_proof_sign_violations"),
static_cast<double>(ProofAudit.CorrectProofViolations));
Proofs->SetNumberField(TEXT("undersized_bound_claims_checked"),
static_cast<double>(ProofAudit.DeliberatelyUndersizedBoundClaimsChecked));
Proofs->SetNumberField(TEXT("wrong_sign_samples_caught_by_undersized_bound"),
static_cast<double>(ProofAudit.DeliberateBoundDisagreements));
Proofs->SetBoolField(TEXT("sound_supremum_audit_pass"), bBoundAudit);
Proofs->SetBoolField(TEXT("deliberate_undersized_bound_failure_caught"), bUndersizedBoundCaught);
Report->SetObjectField(TEXT("brick_bound_audit"), Proofs);
TSharedRef<FJsonObject> Determinism = MakeShared<FJsonObject>();
Determinism->SetStringField(TEXT("six_worker_run_1_quantized"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(HardestHash)));
Determinism->SetStringField(TEXT("six_worker_run_2_quantized"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(RepeatHash)));
Determinism->SetStringField(TEXT("one_worker_quantized"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(OneWorkerHash)));
Determinism->SetStringField(TEXT("six_worker_run_1_raw_float"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(HardestFullTiming.RawFloatHash)));
Determinism->SetStringField(TEXT("six_worker_run_2_raw_float"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(RepeatTiming.RawFloatHash)));
Determinism->SetStringField(TEXT("one_worker_raw_float"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(OneWorkerTiming.RawFloatHash)));
Determinism->SetBoolField(TEXT("same_across_two_runs_and_1_vs_6_workers"), bDeterminism);
Report->SetObjectField(TEXT("determinism"), Determinism);
Report->SetBoolField(TEXT("entrance_exit_walkability_test_added"), true);
Report->SetBoolField(TEXT("inter_strate_passage_open_space_test_added"), true);
Report->SetStringField(TEXT("functional_test_filter"),
TEXT("VoxelForge.ParameterizedGeneration.TraversalSeeds"));
FString JsonText;
const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&JsonText);
const FString ReportPath = FPaths::Combine(ResearchOut, TEXT("step2e_report.json"));
const FString LaunchReportPath = FPaths::Combine(Arguments.OutDirectory, TEXT("step2e_report.json"));
if (!FJsonSerializer::Serialize(Report, Writer)
|| !FFileHelper::SaveStringToFile(JsonText, *ReportPath,
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM)
|| !FFileHelper::SaveStringToFile(JsonText, *LaunchReportPath,
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2e] report write failed: %s"), *ReportPath);
return 2;
}
VoxelForgeRedefinedDensityField::SetParameterizedEnabled(false);
VoxelForgeRedefinedDensityField::SetDetailScale(1.0f);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeStep2e] pass=%s speed600=%s worst_ms=%.3f detail=%s boundary=%s "
"bound=%s deterministic=%s report=%s"),
bPassed ? TEXT("yes") : TEXT("no"), bSpeedPass ? TEXT("yes") : TEXT("no"),
WorstMedianMs, bDetailAtLeastLive ? TEXT("pass") : TEXT("fail"),
bBoundaryPass ? TEXT("pass") : TEXT("fail"), bBoundAudit ? TEXT("pass") : TEXT("fail"),
bDeterminism ? TEXT("pass") : TEXT("fail"), *ReportPath);
return bPassed ? 0 : 2;
}
class FGeneratorDensitySampler final : public IVoxelStrateDensitySampler
{
public:
@@ -10475,6 +11083,10 @@ int32 RunBatch(const FString& Params)
int32 UVoxelForgeExploreCommandlet::Main(const FString& Params)
{
if (FParse::Param(*Params, TEXT("step2e")))
{
return RunStep2e(Params);
}
if (FParse::Param(*Params, TEXT("step2d")))
{
return RunStep2d(Params);
@@ -157,7 +157,8 @@ namespace VoxelForgeRegionEval
float DetailScale,
TArray<float>& OutDensities,
FTiming& OutTiming,
bool bEnableBrickSkips)
bool bEnableBrickSkips,
bool bParameterized)
{
OutTiming = FTiming();
OutDensities.Reset();
@@ -250,6 +251,7 @@ namespace VoxelForgeRegionEval
const bool bHaveCpuStart = GetCurrentThreadCpuSeconds(CpuStart);
TArray<float> BrickDetail;
VoxelForgeRedefinedDensityField::FBrickEvaluationContext BrickContext;
VoxelForgeRedefinedDensityField::FParameterizedBrickContext ParameterizedBrickContext;
for (;;)
{
const int32 BatchBegin = NextBrickIndex.fetch_add(
@@ -268,10 +270,13 @@ namespace VoxelForgeRegionEval
VoxelForgeRedefinedDensityField::EBrickFallbackReason::UnsupportedState;
++WorkerMacroChecks[WorkerIndex];
const double ClassifyStart = FPlatformTime::Seconds();
const bool bCanSkip = bEnableBrickSkips
&& VoxelForgeRedefinedDensityField::TryClassifyBrick(
const bool bCanSkip = bEnableBrickSkips && (bParameterized
? VoxelForgeRedefinedDensityField::TryClassifyParameterizedBrick(
Generator, Brick.Origin, Brick.Size, bUniformAir, DetailScale,
&bEvaluatedMacroCentre, &FallbackReason);
&bEvaluatedMacroCentre, &FallbackReason)
: VoxelForgeRedefinedDensityField::TryClassifyBrick(
Generator, Brick.Origin, Brick.Size, bUniformAir, DetailScale,
&bEvaluatedMacroCentre, &FallbackReason));
WorkerClassify[WorkerIndex] += FPlatformTime::Seconds() - ClassifyStart;
WorkerMacroCentreEvaluations[WorkerIndex] += bEvaluatedMacroCentre ? 1u : 0u;
if (bEnableBrickSkips && !bCanSkip)
@@ -304,7 +309,12 @@ namespace VoxelForgeRegionEval
const double EvaluationStart = FPlatformTime::Seconds();
VoxelForgeRedefinedDensityField::BuildDetailBrick(
Generator, Brick.Origin, Brick.Size, DetailScale, BrickDetail,
&BrickContext);
bParameterized ? nullptr : &BrickContext);
if (bParameterized)
{
VoxelForgeRedefinedDensityField::BuildParameterizedBrickContext(
Generator, Brick.Origin, Brick.Size, ParameterizedBrickContext);
}
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)
@@ -315,11 +325,16 @@ namespace VoxelForgeRegionEval
const int64 LatticeIndex = LinearIndex(
Brick.Lattice.X + X, Brick.Lattice.Y + Y,
Brick.Lattice.Z + Z, Dimension);
OutDensities[static_cast<int32>(LatticeIndex)] =
VoxelForgeRedefinedDensityField::EvaluateWithDetailValue(
const float DetailValue = BrickDetail[
(Z * Brick.Size.Y + Y) * Brick.Size.X + X];
OutDensities[static_cast<int32>(LatticeIndex)] = bParameterized
? VoxelForgeRedefinedDensityField::EvaluateParameterizedWithDetailValue(
Generator, static_cast<float>(WorldX), static_cast<float>(WorldY),
static_cast<float>(WorldZ),
BrickDetail[(Z * Brick.Size.Y + Y) * Brick.Size.X + X],
static_cast<float>(WorldZ), DetailValue, nullptr, DetailScale,
&ParameterizedBrickContext)
: VoxelForgeRedefinedDensityField::EvaluateWithDetailValue(
Generator, static_cast<float>(WorldX), static_cast<float>(WorldY),
static_cast<float>(WorldZ), DetailValue,
nullptr, DetailScale, &BrickContext);
++WorkerEvaluated[WorkerIndex];
}
@@ -144,7 +144,8 @@ namespace VoxelForgeRegionEval
float DetailScale,
TArray<float>& OutDensities,
FTiming& OutTiming,
bool bEnableBrickSkips = true);
bool bEnableBrickSkips = true,
bool bParameterized = false);
/** Count exact room/tunnel/core/passage candidate visits with instrumentation enabled. */
FCandidateVisits CountCandidates(