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