feat(scale): size the world to a human body -- and it is NOT enough
Jahni: "can't you try and make the world to scale?" Every length parameter is now derived from the body and annotated with its metre value, so nobody has to do the arithmetic to notice something is absurd. CorridorRadius 4 -> 8 1 m -> 2 m radius (4 m bore) CellSize 40 -> 64 keeps a real wall between wider corridors TunnelMinRadius 3 -> 6 0.75 m -> 1.5 m ShaftMinRadius 5 -> 8 1.25 m -> 2 m IslandMinRadius 18 -> 24 4.5 m -> 6 m MouthRadius 6 -> 8 passage mouths 4 m plus passage reach, spiral, cascade, bridges, ridges, chasms The reasoning that motivated it still looks right: a 2 m circular bore for a 1.76 m player clears the head only along the exact centreline, so almost no cell has a floor with 7.04 voxels of clear air above it. A 4 m bore is genuinely walkable. ⚠️ BUT IT DID NOT FIX TRAVERSABILITY, and this time it is not the instrument. The player-fit law is measured at STEP 1 on a mouth-fitted window -- full resolution, both mouths inside the box -- and across the eight showcase archetypes it reports: 6 NotConnectedAtThisResolution (real negatives at full resolution) 2 OutOfWindow (unmeasurable, not passing) 1 StartCellNotPlayerFit (the arrival mouth itself is too small) 0 CONNECTED And every archetype reports hard-gate survivors=0, so the showcase is displaying the least-bad of an entirely failing set. ⛔ DO NOT READ player_fit_fraction / player_clearance FROM THE SHOWCASE LINE. Those come from the COARSE step-4 measurement in the same log line, where the 1.36-voxel player is sub-cell and fit is unmeasurable -- player_clearance reads a uniform "1.00 voxels" for all eight archetypes, which is one cell, not one voxel. Only restricted_law is at step 1 and only it is trustworthy. Fixing that mixed-resolution reporting is next. So scale was necessary and insufficient. My hypothesis was that bore diameter versus body height was the binding constraint; doubling it moved nothing measurable. Something else is preventing player-sized volume from existing at all -- worth finding before tuning any further. Codex applied these edits and then died mid-poll without validating them (third time a killed agent left a clean-looking unbuilt diff). Build and suite run by me: build clean, 30 succeeded / 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K8V9my1PL68pmQb2gutZZV
This commit is contained in:
1 parent
eb47efdec9
commit
fc43a86709
17 files changed
+1298
-349
No files matched your search
@@ -656,12 +656,12 @@ bool FVoxelForgeComposerCorpusFreeTest::RunTest(const FString& Parameters)
|
||||
const double MeasurementSeconds = FPlatformTime::Seconds() - MeasurementStartSeconds;
|
||||
|
||||
// Audit the same declared relations against the 12 current authored corpus vectors. Project
|
||||
// assets carry their own height; the eight C++ defaults use the fixture's 4-chunk height.
|
||||
// assets carry their own height; the eight C++ defaults use the fixture's 8-chunk height.
|
||||
int32 AuthoredConstraintViolations = 0;
|
||||
FString AuthoredViolationList;
|
||||
for (const FVoxelStrateCorpusEntry& Entry : Corpus.GetEntries())
|
||||
{
|
||||
float EntryHeight = 4.0f * (float)CHUNK_SIZE;
|
||||
float EntryHeight = 8.0f * (float)CHUNK_SIZE;
|
||||
if (!Entry.SourcePath.Contains(TEXT("/VoxelForge/ComposerDefaults/")))
|
||||
{
|
||||
UVoxelStrateDefinition* Definition = LoadObject<UVoxelStrateDefinition>(
|
||||
|
||||
@@ -49,7 +49,7 @@ bool FVoxelForgeComposerSeasonTest::RunTest(const FString& Parameters)
|
||||
Settings.Corpus = &Corpus;
|
||||
Settings.CandidateCount = 24;
|
||||
Settings.SelectedStrateCount = 6;
|
||||
Settings.StrateHeightInChunks = 4;
|
||||
Settings.StrateHeightInChunks = 8;
|
||||
Settings.InterStrateGapChunks = 0;
|
||||
Settings.OriginSpineRadius = 14.0f;
|
||||
Settings.WorldRadiusVoxels = 0.0f;
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace
|
||||
constexpr float ClearanceNormalizationVoxels = 64.0f;
|
||||
constexpr int32 FinePreviewSampleStep = 1;
|
||||
constexpr int32 FinePreviewRadiusVoxels = 64;
|
||||
constexpr int32 FinePreviewMaxCells = 2000000;
|
||||
constexpr int32 FinePreviewMaxCells = 4000000;
|
||||
|
||||
const ECaveGeneratorType GShowcaseArchetypes[] = {
|
||||
ECaveGeneratorType::CrystalChamber,
|
||||
@@ -125,23 +125,48 @@ namespace
|
||||
int32 EvaluatedCandidates = 0;
|
||||
TArray<FShowcaseSample> Survivors;
|
||||
TOptional<FShowcaseSample> Best;
|
||||
// Best coarse-measured candidate, retained even when a hard gate rejects it. This keeps
|
||||
// the scale audit useful when the player-fit law correctly finds no survivor.
|
||||
TOptional<FShowcaseSample> BestMeasured;
|
||||
int32 MeasuredCandidates = 0;
|
||||
FString LastFailure;
|
||||
};
|
||||
|
||||
bool VF_IsBetterShowcaseSample(const FShowcaseSample& Sample,
|
||||
const TOptional<FShowcaseSample>& Current)
|
||||
{
|
||||
return !Current.IsSet()
|
||||
|| Sample.SelectionScore > Current->SelectionScore
|
||||
|| (Sample.SelectionScore == Current->SelectionScore
|
||||
&& (Sample.Candidate.Seed < Current->Candidate.Seed
|
||||
|| (Sample.Candidate.Seed == Current->Candidate.Seed
|
||||
&& Sample.Candidate.Index < Current->Candidate.Index)));
|
||||
}
|
||||
|
||||
void VF_AddMetricSample(FShowcaseStats& Stats, const FShowcaseSample& Sample)
|
||||
{
|
||||
Stats.Survivors.Add(Sample);
|
||||
if (!Stats.Best.IsSet()
|
||||
|| Sample.SelectionScore > Stats.Best->SelectionScore
|
||||
|| (Sample.SelectionScore == Stats.Best->SelectionScore
|
||||
&& (Sample.Candidate.Seed < Stats.Best->Candidate.Seed
|
||||
|| (Sample.Candidate.Seed == Stats.Best->Candidate.Seed
|
||||
&& Sample.Candidate.Index < Stats.Best->Candidate.Index))))
|
||||
if (VF_IsBetterShowcaseSample(Sample, Stats.Best))
|
||||
{
|
||||
Stats.Best = Sample;
|
||||
}
|
||||
}
|
||||
|
||||
void VF_AddMeasuredSample(FShowcaseStats& Stats, const FShowcaseSample& Sample)
|
||||
{
|
||||
// NumSampled is the direct evidence that the coarse grid was built. Keep this diagnostic
|
||||
// path independent of the legacy gate's bool return and of any future metric-validity
|
||||
// policy changes: a rejected candidate can still be the most useful scale evidence.
|
||||
if (Sample.Candidate.bValid && Sample.Metrics.NumSampled > 0)
|
||||
{
|
||||
++Stats.MeasuredCandidates;
|
||||
if (VF_IsBetterShowcaseSample(Sample, Stats.BestMeasured))
|
||||
{
|
||||
Stats.BestMeasured = Sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VF_BuildShowcaseStack(
|
||||
const VoxelForgeTest::FTestWorld& World,
|
||||
const FVoxelStrateComposerCandidate& Candidate,
|
||||
@@ -569,6 +594,396 @@ namespace
|
||||
return Report;
|
||||
}
|
||||
|
||||
FString VF_FormatWorldScaleAudit()
|
||||
{
|
||||
constexpr float VoxelMetres = 0.25f;
|
||||
constexpr float StrateHeightVoxels = 8.0f * static_cast<float>(CHUNK_SIZE);
|
||||
FString Report = TEXT(
|
||||
"WORLD SCALE AUDIT (all distances are authored in voxels; METRES = voxels * 0.25; "
|
||||
"player radius=1.36 vox/0.34 m, height=7.04 vox/1.76 m; frequency rows show nominal "
|
||||
"period=1/frequency)\n"
|
||||
"archetype | name | voxels | METRES | body/level meaning\n");
|
||||
auto Add = [&Report](const TCHAR* Archetype, const TCHAR* Name,
|
||||
float Voxels, const TCHAR* Meaning)
|
||||
{
|
||||
Report += FString::Printf(
|
||||
TEXT("%s | %s | %.2f | %.2f | %s\n"), Archetype, Name,
|
||||
Voxels, Voxels * 0.25f, Meaning);
|
||||
};
|
||||
auto AddRange = [&Report](const TCHAR* Archetype, const TCHAR* Name,
|
||||
float MinVoxels, float MaxVoxels,
|
||||
const TCHAR* Meaning)
|
||||
{
|
||||
Report += FString::Printf(
|
||||
TEXT("%s | %s | %.2f..%.2f | %.2f..%.2f | %s\n"), Archetype, Name,
|
||||
MinVoxels, MaxVoxels, MinVoxels * 0.25f, MaxVoxels * 0.25f, Meaning);
|
||||
};
|
||||
const TCHAR* TunnelNames[] = { TEXT("TunnelNetwork"), TEXT("Underwater") };
|
||||
for (const TCHAR* Archetype : TunnelNames)
|
||||
{
|
||||
Add(Archetype, TEXT("StrateHeight"), StrateHeightVoxels,
|
||||
TEXT("8 chunks: a 64 m vertical envelope for tall rooms and passages"));
|
||||
Add(Archetype, TEXT("BoundarySealThickness"), 4.0f,
|
||||
TEXT("1 m solid geological boundary; not player space"));
|
||||
Add(Archetype, TEXT("WormNetworkRange"), 64.0f,
|
||||
TEXT("16 m side-passage reach around the room/tunnel skeleton"));
|
||||
Add(Archetype, TEXT("WormFrequency (nominal period)"), 1.0f / 0.015f,
|
||||
TEXT("16.7 m noise period: about two chunks for broad bends"));
|
||||
Add(Archetype, TEXT("RoomSpacing"), 128.0f,
|
||||
TEXT("32 m room-cell spacing; leaves a 6 m radial margin around a 20 m room"));
|
||||
AddRange(Archetype, TEXT("RoomRadius"), 16.0f, 40.0f,
|
||||
TEXT("8 m minimum room to 20 m large chamber diameter"));
|
||||
Add(Archetype, TEXT("OriginRoomRadius"), 48.0f,
|
||||
TEXT("24 m hub diameter: a three-chunk boss-space anchor"));
|
||||
AddRange(Archetype, TEXT("TunnelRadius"), 6.0f, 8.0f,
|
||||
TEXT("3-4 m bores: body height plus generous headroom and fight width"));
|
||||
Add(Archetype, TEXT("MaxTunnelLength"), 360.0f,
|
||||
TEXT("90 m: reaches the worst 32 m cell-jitter neighbour with margin"));
|
||||
Add(Archetype, TEXT("TunnelWarpStrength"), 24.0f,
|
||||
TEXT("6 m lateral bend, visibly organic but smaller than the 90 m reach"));
|
||||
Add(Archetype, TEXT("SDFBlendRadius"), 4.0f,
|
||||
TEXT("1 m junction rounding, below the 1.5 m minimum tunnel radius"));
|
||||
Add(Archetype, TEXT("CaveWarpStrength"), 16.0f,
|
||||
TEXT("4 m skeleton distortion"));
|
||||
Add(Archetype, TEXT("SurfaceRoughness"), 2.0f,
|
||||
TEXT("0.5 m wall texture; bounded reach is 0.94 m"));
|
||||
Add(Archetype, TEXT("FloorReliefStrength"), 4.0f,
|
||||
TEXT("1 m floor undulation, small relative to a 3 m bore"));
|
||||
Add(Archetype, TEXT("FloorReliefFrequency (nominal period)"), 1.0f / 0.015f,
|
||||
TEXT("16.7 m floor-undulation period: broad enough to walk as a slope"));
|
||||
Add(Archetype, TEXT("RoughnessFrequency (nominal period)"), 1.0f / 0.1f,
|
||||
TEXT("2.5 m wall-detail period: a few body lengths, not voxel-grain tunnel"));
|
||||
Add(Archetype, TEXT("CaveWarpFrequency (nominal period)"), 1.0f / 0.015f,
|
||||
TEXT("16.7 m skeleton-warp period: broad landform-scale bend"));
|
||||
Add(Archetype, TEXT("DomainWarpFrequency (nominal period)"), 1.0f / 0.03f,
|
||||
TEXT("8.3 m optional roughness-warp period"));
|
||||
Add(Archetype, TEXT("CliffSampleDist (transport)"), 2.0f,
|
||||
TEXT("0.5 m optional local slope sample distance"));
|
||||
Add(Archetype, TEXT("FloorBias"), 4.0f,
|
||||
TEXT("1 m floor stabilisation applied below room centre"));
|
||||
Add(Archetype, TEXT("DomainWarpStrength"), 0.0f,
|
||||
TEXT("0 m optional roughness-coordinate warp; disabled by default"));
|
||||
Add(Archetype, TEXT("OverhangFrequency (nominal period)"), 1.0f / 0.06f,
|
||||
TEXT("4.2 m optional shelf-shape period"));
|
||||
Add(Archetype, TEXT("ScallopFrequency (nominal period)"), 1.0f / 0.1f,
|
||||
TEXT("2.5 m optional erosion-bowl period"));
|
||||
Add(Archetype, TEXT("TerraceStepHeight (transport)"), 0.0f,
|
||||
TEXT("0 m optional step; disabled by default"));
|
||||
Add(Archetype, TEXT("LayerLineSpacing (transport)"), 0.0f,
|
||||
TEXT("0 m optional geological band period; disabled by default"));
|
||||
Add(Archetype, TEXT("LayerLineDepth (transport)"), 0.3f,
|
||||
TEXT("0.075 m optional groove depth; inactive while spacing is zero"));
|
||||
Add(Archetype, TEXT("RibbingSpacing (transport)"), 0.0f,
|
||||
TEXT("0 m optional rib period; disabled by default"));
|
||||
Add(Archetype, TEXT("RibbingDepth (transport)"), 0.4f,
|
||||
TEXT("0.1 m optional rib protrusion; inactive while spacing is zero"));
|
||||
Add(Archetype, TEXT("OverhangDepth (transport)"), 8.0f,
|
||||
TEXT("2 m optional shelf; terrain-op transport is reset when disabled"));
|
||||
AddRange(Archetype, TEXT("ArchRadius (transport)"), 6.0f, 12.0f,
|
||||
TEXT("1.5-3 m optional arch feature"));
|
||||
AddRange(Archetype, TEXT("ColumnRadius (transport)"), 4.0f, 8.0f,
|
||||
TEXT("1-2 m optional pillar radius"));
|
||||
AddRange(Archetype, TEXT("PitRadius (transport)"), 8.0f, 16.0f,
|
||||
TEXT("2-4 m optional vertical opening radius"));
|
||||
Add(Archetype, TEXT("PitDepth (transport)"), 32.0f,
|
||||
TEXT("8 m optional vertical drop"));
|
||||
AddRange(Archetype, TEXT("ChimneyRadius (transport)"), 4.0f, 8.0f,
|
||||
TEXT("1-2 m optional upward shaft radius"));
|
||||
Add(Archetype, TEXT("ChimneyHeight (transport)"), 32.0f,
|
||||
TEXT("8 m optional upward shaft height"));
|
||||
AddRange(Archetype, TEXT("DomeRadius (transport)"), 16.0f, 32.0f,
|
||||
TEXT("4-8 m optional ceiling dome radius"));
|
||||
Add(Archetype, TEXT("PinchLength (transport)"), 24.0f,
|
||||
TEXT("6 m optional bottleneck length; default operation is disabled"));
|
||||
}
|
||||
|
||||
for (const TCHAR* Archetype : { TEXT("FlatPlain"), TEXT("CrystalChamber") })
|
||||
{
|
||||
Add(Archetype, TEXT("StrateHeight"), StrateHeightVoxels,
|
||||
TEXT("8 chunks / 64 m: open vertical chamber envelope"));
|
||||
Add(Archetype, TEXT("Floor height"), StrateHeightVoxels * 0.25f,
|
||||
TEXT("16 m above the strate bottom"));
|
||||
Add(Archetype, TEXT("Ceiling height"), StrateHeightVoxels * 0.60f,
|
||||
TEXT("38.4 m above the bottom; 22.4 m clear span before roughness"));
|
||||
Add(Archetype, TEXT("FloorRoughness"), 4.0f,
|
||||
TEXT("1 m rolling floor displacement"));
|
||||
Add(Archetype, TEXT("CeilingRoughness"), 6.0f,
|
||||
TEXT("1.5 m downward formation reach"));
|
||||
Add(Archetype, TEXT("FloorRoughnessFrequency (nominal period)"), 1.0f / 0.04f,
|
||||
TEXT("6.25 m floor-detail period: broad rolling ground"));
|
||||
Add(Archetype, TEXT("CeilingRoughnessFrequency (nominal period)"), 1.0f / 0.04f,
|
||||
TEXT("6.25 m ceiling-formation period"));
|
||||
AddRange(Archetype, TEXT("ColumnRadius"), 8.0f, 16.0f,
|
||||
TEXT("4-8 m pillar diameters; large enough to read without erasing a fight lane"));
|
||||
Add(Archetype, TEXT("ColumnSpacing"), 96.0f,
|
||||
TEXT("24 m pillar-cell spacing"));
|
||||
Add(Archetype, TEXT("BoundarySealThickness"), 4.0f,
|
||||
TEXT("1 m solid boundary"));
|
||||
}
|
||||
|
||||
Add(TEXT("Common layout"), TEXT("OriginSpineRadius"), 14.0f,
|
||||
TEXT("3.5 m radius / 7 m diameter central descent spine"));
|
||||
Add(TEXT("Common layout"), TEXT("InterStrateGap"), 0.0f,
|
||||
TEXT("0 m bedrock gap by default; configured as whole 8 m chunks"));
|
||||
Add(TEXT("Common layout"), TEXT("WorldRadiusVoxels"), 0.0f,
|
||||
TEXT("0 m lateral bound: radial regions remain gated off"));
|
||||
|
||||
Add(TEXT("Maze"), TEXT("StrateHeight"), StrateHeightVoxels,
|
||||
TEXT("8 chunks / 64 m: supports several 16 m maze levels"));
|
||||
Add(TEXT("Maze"), TEXT("CellSize"), 64.0f,
|
||||
TEXT("16 m lattice cells, matching ordinary fight-space scale"));
|
||||
Add(TEXT("Maze"), TEXT("CorridorRadius"), 8.0f,
|
||||
TEXT("2 m radius / 4 m circular bore: upper fight-space target"));
|
||||
Add(TEXT("Maze"), TEXT("SurfaceRoughness"), 2.0f,
|
||||
TEXT("0.5 m texture; bounded reach is 0.94 m, below the 2 m radius"));
|
||||
Add(TEXT("Maze"), TEXT("BoundarySealThickness"), 4.0f,
|
||||
TEXT("1 m solid boundary"));
|
||||
|
||||
Add(TEXT("SurfaceWorld"), TEXT("StrateHeight"), StrateHeightVoxels,
|
||||
TEXT("8 chunks / 64 m: high sky-cap envelope"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("ElevationRange"), 80.0f,
|
||||
TEXT("20 m terrain relief: human-scale hills, not a miniature room"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("Base ground height"), StrateHeightVoxels * 0.25f,
|
||||
TEXT("16 m mean ground elevation above the strate bottom"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("HeightWarpStrength"), 48.0f,
|
||||
TEXT("12 m horizontal landform distortion"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("SurfaceRoughness"), 2.0f,
|
||||
TEXT("0.5 m ground detail; bounded reach is 0.94 m"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("ContinentFrequency (nominal period)"), 1.0f / 0.006f,
|
||||
TEXT("41.7 m broad landmass period"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("MountainFrequency (nominal period)"), 1.0f / 0.012f,
|
||||
TEXT("20.8 m ridge period"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("DetailFrequency (nominal period)"), 1.0f / 0.04f,
|
||||
TEXT("6.25 m fine terrain period"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("TerraceHeight"), 8.0f,
|
||||
TEXT("2 m optional step; operation remains disabled by strength 0"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("LayerLineSpacing"), 8.0f,
|
||||
TEXT("2 m optional geological band spacing"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("LayerLineDepth"), 0.0f,
|
||||
TEXT("0 m default band displacement: optional operation disabled"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("CliffSampleDist"), 2.0f,
|
||||
TEXT("0.5 m slope sample scale for optional cliff operation"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("OverhangReach"), 16.0f,
|
||||
TEXT("4 m optional shelf reach"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("OverhangHeight"), 24.0f,
|
||||
TEXT("6 m optional shelf zone"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("BeachWidth"), 12.0f,
|
||||
TEXT("3 m shore band"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("Sky cap height"), StrateHeightVoxels * 0.95f,
|
||||
TEXT("60.8 m above the strate bottom; leaves a high open sky"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("CeilingRoughness"), 6.0f,
|
||||
TEXT("1.5 m optional downward cap detail"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("CeilingUndulation"), 0.0f,
|
||||
TEXT("0 m broad cap swell; disabled by default"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("CeilingRidgeStrength"), 0.0f,
|
||||
TEXT("0 m hanging ridge height; disabled by default"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("CeilingWarpStrength"), 0.0f,
|
||||
TEXT("0 m cap-coordinate warp; disabled by default"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("BoundarySealThickness"), 4.0f,
|
||||
TEXT("1 m solid boundary"));
|
||||
|
||||
Add(TEXT("SurfaceWorld"), TEXT("HeightWarpFrequency (nominal period)"), 1.0f / 0.008f,
|
||||
TEXT("31.25 m landform-warp period"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("ReliefFrequency (nominal period)"), 1.0f / 0.0015f,
|
||||
TEXT("166.7 m macro-region period"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("OverhangFrequency (nominal period)"), 1.0f / 0.02f,
|
||||
TEXT("12.5 m optional shelf-shape period"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("CeilingRoughnessFrequency (nominal period)"), 1.0f / 0.04f,
|
||||
TEXT("6.25 m optional cap-detail period"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("CeilingUndulationFrequency (nominal period)"), 1.0f / 0.004f,
|
||||
TEXT("62.5 m optional cap-swell period"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("CeilingRidgeFrequency (nominal period)"), 1.0f / 0.02f,
|
||||
TEXT("12.5 m optional hanging-ridge period"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("CeilingWarpFrequency (nominal period)"), 1.0f / 0.01f,
|
||||
TEXT("25 m optional cap-warp period"));
|
||||
|
||||
Add(TEXT("VerticalShafts"), TEXT("StrateHeight"), StrateHeightVoxels,
|
||||
TEXT("8 chunks / 64 m: a full vertical traversal envelope"));
|
||||
Add(TEXT("VerticalShafts"), TEXT("ShaftSpacing"), 80.0f,
|
||||
TEXT("20 m shaft-cell spacing"));
|
||||
AddRange(TEXT("VerticalShafts"), TEXT("ShaftRadius"), 8.0f, 14.0f,
|
||||
TEXT("4-7 m shaft diameters"));
|
||||
Add(TEXT("VerticalShafts"), TEXT("ConnectorRadius"), 6.0f,
|
||||
TEXT("1.5 m radius / 3 m horizontal connector bore"));
|
||||
Add(TEXT("VerticalShafts"), TEXT("LedgeSpacing"), 32.0f,
|
||||
TEXT("8 m vertical landing interval"));
|
||||
Add(TEXT("VerticalShafts"), TEXT("LedgeDepth"), 4.0f,
|
||||
TEXT("1 m landing intrusion"));
|
||||
Add(TEXT("VerticalShafts"), TEXT("SurfaceRoughness"), 2.0f,
|
||||
TEXT("0.5 m wall texture; bounded reach is 0.94 m"));
|
||||
Add(TEXT("VerticalShafts"), TEXT("BoundarySealThickness"), 4.0f,
|
||||
TEXT("1 m solid boundary"));
|
||||
|
||||
Add(TEXT("FloatingIslands"), TEXT("StrateHeight"), StrateHeightVoxels,
|
||||
TEXT("8 chunks / 64 m: vertical sky volume"));
|
||||
Add(TEXT("FloatingIslands"), TEXT("IslandSpacing"), 112.0f,
|
||||
TEXT("28 m island-cell spacing; max 24 m diameter leaves a 2 m centre-to-edge gap"));
|
||||
AddRange(TEXT("FloatingIslands"), TEXT("IslandRadius"), 24.0f, 48.0f,
|
||||
TEXT("12-24 m landmass diameters: room to cathedral scale"));
|
||||
Add(TEXT("FloatingIslands"), TEXT("Underside half-depth"), 48.0f * 0.60f,
|
||||
TEXT("7.2 m below the centre for the largest island; top half is 2.4 m"));
|
||||
Add(TEXT("FloatingIslands"), TEXT("SurfaceRoughness"), 3.0f,
|
||||
TEXT("0.75 m island texture; bounded reach is 1.41 m"));
|
||||
Add(TEXT("FloatingIslands"), TEXT("SDFBlendRadius"), 6.0f,
|
||||
TEXT("1.5 m island edge blend"));
|
||||
Add(TEXT("FloatingIslands"), TEXT("BoundarySealThickness"), 4.0f,
|
||||
TEXT("1 m solid boundary"));
|
||||
|
||||
Add(TEXT("Common passage"), TEXT("MouthRadius"), 8.0f,
|
||||
TEXT("2 m radius / 4 m bore at each strate mouth"));
|
||||
Add(TEXT("Common passage"), TEXT("MidRadius"), 6.0f,
|
||||
TEXT("1.5 m radius / 3 m minimum bore between mouths"));
|
||||
AddRange(TEXT("Common passage"), TEXT("Reach"), 32.0f, 96.0f,
|
||||
TEXT("8-24 m reach into each strate"));
|
||||
AddRange(TEXT("Common passage"), TEXT("Distance from spine"), 64.0f, 192.0f,
|
||||
TEXT("16-48 m placement range"));
|
||||
Add(TEXT("Common passage"), TEXT("Wander"), 24.0f,
|
||||
TEXT("6 m sideways meander"));
|
||||
Add(TEXT("Common passage"), TEXT("VerticalWobble"), 0.0f,
|
||||
TEXT("0 m vertical wobble by default; descent remains controlled"));
|
||||
Add(TEXT("Common passage"), TEXT("SpiralRadius"), 24.0f,
|
||||
TEXT("6 m corkscrew radius"));
|
||||
Add(TEXT("Common passage"), TEXT("CascadeLedge"), 8.0f,
|
||||
TEXT("2 m ledge run per cascade step"));
|
||||
|
||||
Add(TEXT("Common disturbance"), TEXT("ChasmSpacing"), 192.0f,
|
||||
TEXT("48 m chasm-cell spacing"));
|
||||
Add(TEXT("Common disturbance"), TEXT("ChasmRadius"), 24.0f,
|
||||
TEXT("6 m radius / 12 m opening when enabled"));
|
||||
Add(TEXT("Common disturbance"), TEXT("BridgeSpacing"), 128.0f,
|
||||
TEXT("32 m bridge-cell spacing"));
|
||||
Add(TEXT("Common disturbance"), TEXT("BridgeRadius"), 8.0f,
|
||||
TEXT("2 m radius / 4 m bridge width"));
|
||||
Add(TEXT("Common disturbance"), TEXT("RidgeSpacing"), 128.0f,
|
||||
TEXT("32 m ridge-cell spacing"));
|
||||
Add(TEXT("Common disturbance"), TEXT("RidgeHeight"), 32.0f,
|
||||
TEXT("8 m optional blade height"));
|
||||
Add(TEXT("Common disturbance"), TEXT("RidgeThickness"), 8.0f,
|
||||
TEXT("2 m optional blade half-width"));
|
||||
Add(TEXT("Common disturbance"), TEXT("BoundarySealThickness"), 4.0f,
|
||||
TEXT("1 m solid boundary shared with the active archetype"));
|
||||
|
||||
Report += FString::Printf(
|
||||
TEXT("Audit anchors: 1 voxel=%.2f m; 1 chunk=8.00 m; 2.5 m walk corridor=10 vox; "
|
||||
"3-4 m fight corridor=12-16 vox; 8-15 m room=32-60 vox; 20 m cathedral=80 vox.\n"),
|
||||
VoxelMetres);
|
||||
return Report;
|
||||
}
|
||||
|
||||
FString VF_FormatDefaultRoughnessRatios()
|
||||
{
|
||||
const float TunnelReach = VF_RoughnessReach(2.0f);
|
||||
const float SlabFloorReach = VF_RoughnessReach(4.0f);
|
||||
const float SlabCeilingReach = VF_RoughnessReach(6.0f);
|
||||
const float MazeReach = VF_RoughnessReach(2.0f);
|
||||
const float ShaftReach = VF_RoughnessReach(2.0f);
|
||||
const float IslandReach = VF_RoughnessReach(3.0f);
|
||||
return FString::Printf(
|
||||
TEXT("Default roughness-to-feature ratios (reach = roughness * 1.25 * 1.5; "
|
||||
"all values are dimensionless): TunnelNetwork=%.3f reach/%.1f-voxel "
|
||||
"minimum tunnel radius (room mean %.3f); Underwater=same; FlatPlain="
|
||||
"%.3f floor reach/mean column radius and %.3f ceiling reach/mean column radius; "
|
||||
"CrystalChamber=same slab engine; Maze=%.3f reach/%.1f-voxel corridor radius; "
|
||||
"SurfaceWorld=%.3f reach/80-voxel terrain relief; VerticalShafts=%.3f shaft-min "
|
||||
"and %.3f connector; FloatingIslands=%.3f reach/min island radius "
|
||||
"(%.3f including 6-voxel blend).\n"),
|
||||
TunnelReach / 6.0f, 6.0f, TunnelReach / 28.0f,
|
||||
SlabFloorReach / 12.0f, SlabCeilingReach / 12.0f,
|
||||
MazeReach / 8.0f, 8.0f, TunnelReach / 80.0f,
|
||||
ShaftReach / 8.0f, ShaftReach / 6.0f,
|
||||
IslandReach / 24.0f, (IslandReach + 6.0f) / 24.0f);
|
||||
}
|
||||
|
||||
float VF_ShowcaseRoughnessFeatureRatio(
|
||||
const FVoxelStrateComposerCandidate& Candidate)
|
||||
{
|
||||
switch (Candidate.Archetype)
|
||||
{
|
||||
case ECaveGeneratorType::TunnelNetwork:
|
||||
case ECaveGeneratorType::Underwater:
|
||||
return VF_RoughnessReach(
|
||||
Candidate.ArchetypeParams.TunnelNetworkParams.SurfaceRoughness)
|
||||
/ FMath::Max(
|
||||
Candidate.ArchetypeParams.TunnelNetworkParams.TunnelMinRadius,
|
||||
KINDA_SMALL_NUMBER);
|
||||
case ECaveGeneratorType::FlatPlain:
|
||||
case ECaveGeneratorType::CrystalChamber:
|
||||
{
|
||||
const FSlabGenerationParams& P = Candidate.ArchetypeParams.SlabParams;
|
||||
const float ColumnRadius = 0.5f * (P.ColumnMinRadius + P.ColumnMaxRadius);
|
||||
return FMath::Max(
|
||||
VF_RoughnessReach(P.FloorRoughness),
|
||||
VF_RoughnessReach(P.CeilingRoughness))
|
||||
/ FMath::Max(ColumnRadius, KINDA_SMALL_NUMBER);
|
||||
}
|
||||
case ECaveGeneratorType::Maze:
|
||||
return VF_RoughnessReach(Candidate.ArchetypeParams.MazeParams.SurfaceRoughness)
|
||||
/ FMath::Max(Candidate.ArchetypeParams.MazeParams.CorridorRadius,
|
||||
KINDA_SMALL_NUMBER);
|
||||
case ECaveGeneratorType::SurfaceWorld:
|
||||
return VF_RoughnessReach(Candidate.ArchetypeParams.SurfaceParams.SurfaceRoughness)
|
||||
/ FMath::Max(Candidate.ArchetypeParams.SurfaceParams.ElevationRange,
|
||||
KINDA_SMALL_NUMBER);
|
||||
case ECaveGeneratorType::VerticalShafts:
|
||||
return VF_RoughnessReach(
|
||||
Candidate.ArchetypeParams.VerticalShaftParams.SurfaceRoughness)
|
||||
/ FMath::Max(
|
||||
Candidate.ArchetypeParams.VerticalShaftParams.ShaftMinRadius,
|
||||
KINDA_SMALL_NUMBER);
|
||||
case ECaveGeneratorType::FloatingIslands:
|
||||
{
|
||||
const FFloatingIslandParams& P = Candidate.ArchetypeParams.FloatingIslandParams;
|
||||
return (VF_RoughnessReach(P.SurfaceRoughness) + P.SDFBlendRadius)
|
||||
/ FMath::Max(P.IslandMinRadius, KINDA_SMALL_NUMBER);
|
||||
}
|
||||
default:
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
FString VF_FormatPlayerFitSummary(
|
||||
const TArray<FShowcaseSample>& Samples,
|
||||
const TCHAR* Label)
|
||||
{
|
||||
FString Report = FString::Printf(
|
||||
TEXT("%s (walkable floor area is the coarse 128 m x 128 m footprint proxy)\n"
|
||||
"archetype | PlayerFitFraction | TraversableComponentShare | "
|
||||
"player-fit arrival->departure | walkable floor area m2 | "
|
||||
"median vertical clearance m | roughness-to-feature ratio\n"), Label);
|
||||
for (const ECaveGeneratorType Archetype : GShowcaseArchetypes)
|
||||
{
|
||||
const FShowcaseSample* Found = Samples.FindByPredicate(
|
||||
[Archetype](const FShowcaseSample& Sample)
|
||||
{
|
||||
return Sample.Candidate.Archetype == Archetype;
|
||||
});
|
||||
if (Found == nullptr)
|
||||
{
|
||||
Report += FString::Printf(
|
||||
TEXT("%s | no measured sample | no measured sample | no measured sample | "
|
||||
"n/a | n/a | n/a\n"),
|
||||
VF_GetStrateArchetypeName(Archetype));
|
||||
continue;
|
||||
}
|
||||
const FVoxelStrateMetrics& M = Found->FineMetrics;
|
||||
const FString Law = M.bPlayerFitResolved
|
||||
? VF_ShowcaseConnectivityName(Found->PlayerFitLaw.Result)
|
||||
: TEXT("unresolved");
|
||||
const float FloorAreaM2 = Found->Metrics.WalkableFloorAreaFraction
|
||||
* 128.0f * 128.0f;
|
||||
Report += FString::Printf(
|
||||
TEXT("%s | %.6f | %.6f | %s | %.1f (%.6f footprint) | %.2f | %.3f\n"),
|
||||
VF_GetStrateArchetypeName(Archetype),
|
||||
M.bPlayerFitResolved ? M.PlayerFitFraction : 0.0f,
|
||||
M.bPlayerFitResolved ? M.TraversableComponentShare : 0.0f,
|
||||
*Law, FloorAreaM2,
|
||||
Found->Metrics.WalkableFloorAreaFraction,
|
||||
Found->Metrics.MedianVerticalClearance * 0.25f,
|
||||
VF_ShowcaseRoughnessFeatureRatio(Found->Candidate));
|
||||
}
|
||||
return Report;
|
||||
}
|
||||
|
||||
bool VF_RunMazeRoughnessExperiment(
|
||||
const VoxelForgeTest::FTestWorld& World,
|
||||
const FShowcaseSample& MazeSample,
|
||||
@@ -636,6 +1051,80 @@ namespace
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VF_RunMazeCorridorRadiusSweep(
|
||||
const VoxelForgeTest::FTestWorld& World,
|
||||
const FShowcaseSample& MazeSample,
|
||||
const FVoxelStrateMeasureSettings& BaseSettings,
|
||||
const FVoxelStrateFinePreviewSettings& FinePreviewSettings,
|
||||
FString& OutReport)
|
||||
{
|
||||
if (MazeSample.Candidate.Archetype != ECaveGeneratorType::Maze)
|
||||
{
|
||||
OutReport = TEXT("Part A Maze corridor-radius sweep unavailable: previous Maze roll was not found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
FVector ArrivalPoint = FVector::ZeroVector;
|
||||
FVector DeparturePoint = FVector::ZeroVector;
|
||||
if (!VF_GetShowcaseMouthPair(
|
||||
World, MazeSample.TargetStrateIndex, ArrivalPoint, DeparturePoint))
|
||||
{
|
||||
OutReport = TEXT("Part A Maze corridor-radius sweep unavailable: mouth pair was not found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 TopVoxelZ = 0;
|
||||
int32 BottomVoxelZ = 0;
|
||||
if (!World.GetSlotVoxelZRange(MazeSample.TargetStrateIndex, TopVoxelZ, BottomVoxelZ))
|
||||
{
|
||||
OutReport = TEXT("Part A Maze corridor-radius sweep unavailable: Z range was not found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const FVoxelStrateMeasureSettings FineSettings = VF_MakeFinePlayerFitSettings(
|
||||
BaseSettings, FinePreviewSettings, ArrivalPoint, DeparturePoint);
|
||||
const float RadiusSweep[] = { 4.0f, 6.0f, 8.0f, 10.0f, 12.0f };
|
||||
OutReport = TEXT(
|
||||
"Part A Maze corridor-radius sweep (same seed/index, fitted step-1 ROI; "
|
||||
"radius voxels -> bore metres):\n");
|
||||
for (const float CorridorRadius : RadiusSweep)
|
||||
{
|
||||
FVoxelStrateComposerCandidate Candidate = MazeSample.Candidate;
|
||||
Candidate.ArchetypeParams.MazeParams.CorridorRadius = CorridorRadius;
|
||||
|
||||
FVoxelOpStack Stack;
|
||||
FVoxelOpContext Context;
|
||||
FString BuildError;
|
||||
if (!VF_BuildShowcaseStack(
|
||||
World, Candidate, TopVoxelZ, BottomVoxelZ, Stack, Context, BuildError))
|
||||
{
|
||||
OutReport += FString::Printf(
|
||||
TEXT(" radius=%.0f bore=%.2f m: build failed (%s)\n"),
|
||||
CorridorRadius, CorridorRadius * 2.0f * VOXEL_SIZE / 100.0f, *BuildError);
|
||||
continue;
|
||||
}
|
||||
|
||||
Stack.PrepareChunk(Context);
|
||||
FShowcaseStackSampler Sampler(Stack);
|
||||
FVoxelStrateMetrics Metrics;
|
||||
const FVoxelConnectivityDiagnostics Law =
|
||||
VF_DiagnosePlayerFitConnectivityWithSampler(
|
||||
Sampler, BottomVoxelZ, TopVoxelZ + 1, Context.EdgeSealThickness,
|
||||
ArrivalPoint, DeparturePoint, FineSettings, &Metrics);
|
||||
OutReport += FString::Printf(
|
||||
TEXT(" radius=%.0f (%.2f m) bore=%.2f m: fit=%.6f traversable=%.6f "
|
||||
"fit_cells=%lld min_clearance=%.2f voxels (%.2f m) law=%s\n"),
|
||||
CorridorRadius, CorridorRadius * VOXEL_SIZE / 100.0f,
|
||||
CorridorRadius * 2.0f * VOXEL_SIZE / 100.0f,
|
||||
Metrics.PlayerFitFraction, Metrics.TraversableComponentShare,
|
||||
static_cast<long long>(Metrics.NumPlayerFitCells),
|
||||
Metrics.MinimumPlayerClearanceVoxels,
|
||||
Metrics.MinimumPlayerClearanceVoxels * VOXEL_SIZE / 100.0f,
|
||||
VF_ShowcaseConnectivityName(Law.Result));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct FIslandVerticalAudit
|
||||
{
|
||||
int32 NumIslands = 0;
|
||||
@@ -1073,7 +1562,9 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
|
||||
}
|
||||
|
||||
FTestWorld World;
|
||||
World.Build(BaseComposerSeed, 2, true);
|
||||
// The shared fixture defaults to a bounded 4-chunk volume for fast density tests. The
|
||||
// owner-facing scale audit must exercise the production/default 8-chunk envelope.
|
||||
World.Build(BaseComposerSeed, 2, true, 8);
|
||||
if (!World.IsValid())
|
||||
{
|
||||
AddError(World.WhyInvalid());
|
||||
@@ -1188,6 +1679,7 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
Stats[ArchetypeIndex].LastFailure = MoveTemp(FailureReason);
|
||||
}
|
||||
VF_AddMeasuredSample(Stats[ArchetypeIndex], Sample);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1248,6 +1740,7 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
Stats[ArchetypeIndex].LastFailure = MoveTemp(FailureReason);
|
||||
}
|
||||
VF_AddMeasuredSample(Stats[ArchetypeIndex], Sample);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1270,13 +1763,76 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
|
||||
++ArchetypeIndex)
|
||||
{
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("Showcase search detail %s: eligible=%d hard_gate_survivors=%d last_failure=%s"),
|
||||
TEXT("Showcase search detail %s: eligible=%d measured=%d hard_gate_survivors=%d last_failure=%s"),
|
||||
VF_GetStrateArchetypeName(GShowcaseArchetypes[ArchetypeIndex]),
|
||||
Stats[ArchetypeIndex].EvaluatedCandidates,
|
||||
Stats[ArchetypeIndex].MeasuredCandidates,
|
||||
Stats[ArchetypeIndex].Survivors.Num(),
|
||||
Stats[ArchetypeIndex].LastFailure.IsEmpty()
|
||||
Stats[ArchetypeIndex].LastFailure.IsEmpty()
|
||||
? TEXT("none") : *Stats[ArchetypeIndex].LastFailure));
|
||||
}
|
||||
TArray<FShowcaseSample> FreshSurvivorSamples;
|
||||
for (const FShowcaseStats& ArchetypeStats : Stats)
|
||||
{
|
||||
if (ArchetypeStats.Best.IsSet())
|
||||
{
|
||||
FreshSurvivorSamples.Add(ArchetypeStats.Best.GetValue());
|
||||
}
|
||||
}
|
||||
AddInfo(VF_FormatPlayerFitSummary(
|
||||
FreshSurvivorSamples, TEXT("AFTER defaults: fresh hard-gate survivors")));
|
||||
|
||||
TArray<FShowcaseSample> FreshDiagnosticSamples;
|
||||
FreshDiagnosticSamples.Reserve(UE_ARRAY_COUNT(GShowcaseArchetypes));
|
||||
for (const FShowcaseStats& ArchetypeStats : Stats)
|
||||
{
|
||||
if (!ArchetypeStats.BestMeasured.IsSet())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const FShowcaseSample Measured = ArchetypeStats.BestMeasured.GetValue();
|
||||
const FVoxelStrateComposerCandidate DiagnosticCandidate = Measured.Candidate;
|
||||
FShowcaseSample Diagnostic;
|
||||
FString DiagnosticFailure;
|
||||
// Re-run the selected coarse measurement with the player-fit pass enabled even when a
|
||||
// legacy gate fails. This is diagnostic only: it never enters Survivors or changes card
|
||||
// selection, but it gives every archetype an honest before/after scale row.
|
||||
VF_EvaluateShowcaseCandidate(
|
||||
World, DiagnosticCandidate, Measured.TargetStrateIndex, MeasureSettings,
|
||||
Diagnostic, &DiagnosticFailure, true);
|
||||
if (Diagnostic.Candidate.bValid && Diagnostic.Metrics.NumSampled > 0)
|
||||
{
|
||||
FreshDiagnosticSamples.Add(MoveTemp(Diagnostic));
|
||||
}
|
||||
}
|
||||
AddInfo(VF_FormatPlayerFitSummary(
|
||||
FreshDiagnosticSamples,
|
||||
TEXT("AFTER defaults: best measured candidate per archetype (diagnostic; hard gates separate)")));
|
||||
|
||||
TArray<FShowcaseSample> PreviewSelections;
|
||||
PreviewSelections.Reserve(UE_ARRAY_COUNT(GShowcaseArchetypes));
|
||||
bool bHaveAllPreviewSelections = true;
|
||||
for (int32 ArchetypeIndex = 0;
|
||||
ArchetypeIndex < UE_ARRAY_COUNT(GShowcaseArchetypes);
|
||||
++ArchetypeIndex)
|
||||
{
|
||||
if (Stats[ArchetypeIndex].Best.IsSet())
|
||||
{
|
||||
PreviewSelections.Add(Stats[ArchetypeIndex].Best.GetValue());
|
||||
continue;
|
||||
}
|
||||
const FShowcaseSample* Diagnostic = FreshDiagnosticSamples.FindByPredicate(
|
||||
[Archetype = GShowcaseArchetypes[ArchetypeIndex]](const FShowcaseSample& Sample)
|
||||
{
|
||||
return Sample.Candidate.Archetype == Archetype;
|
||||
});
|
||||
if (Diagnostic == nullptr)
|
||||
{
|
||||
bHaveAllPreviewSelections = false;
|
||||
break;
|
||||
}
|
||||
PreviewSelections.Add(*Diagnostic);
|
||||
}
|
||||
|
||||
struct FPreviousShowcaseSelection
|
||||
{
|
||||
@@ -1358,6 +1914,10 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("Previous eight showcase candidates rejected by the new player-fit gate: %d/8."),
|
||||
NumPreviousShowcaseRejected));
|
||||
AddInfo(VF_FormatPlayerFitSummary(
|
||||
PreviousSamples, TEXT("AFTER defaults: historical candidate re-measurement")));
|
||||
AddInfo(VF_FormatWorldScaleAudit());
|
||||
AddInfo(VF_FormatDefaultRoughnessRatios());
|
||||
AddInfo(VF_FormatRoughnessRadiusReport(PreviousSamples));
|
||||
FString MazeRoughnessReport;
|
||||
if (const FShowcaseSample* MazeSample = PreviousSamples.FindByPredicate(
|
||||
@@ -1368,6 +1928,10 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
VF_RunMazeRoughnessExperiment(
|
||||
World, *MazeSample, MeasureSettings, FinePreviewSettings, MazeRoughnessReport);
|
||||
FString MazeRadiusReport;
|
||||
VF_RunMazeCorridorRadiusSweep(
|
||||
World, *MazeSample, MeasureSettings, FinePreviewSettings, MazeRadiusReport);
|
||||
AddInfo(MazeRadiusReport);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1392,8 +1956,13 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
|
||||
}
|
||||
if (!bHaveAllArchetypes)
|
||||
{
|
||||
AddInfo(TEXT("Showcase cards are not written because the new hard gate found no complete "
|
||||
"eight-archetype survivor set; the diagnostic report above is the result."));
|
||||
AddInfo(TEXT("No complete eight-archetype hard-gate survivor set was found; the fresh "
|
||||
"preview cards below are rejected diagnostic candidates for scale review."));
|
||||
}
|
||||
if (!bHaveAllPreviewSelections)
|
||||
{
|
||||
AddInfo(TEXT("Fresh diagnostic previews are incomplete because at least one archetype had "
|
||||
"no measurable candidate."));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1429,8 +1998,9 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
|
||||
ArchetypeIndex < UE_ARRAY_COUNT(GShowcaseArchetypes);
|
||||
++ArchetypeIndex)
|
||||
{
|
||||
const FShowcaseSample& Selected = Stats[ArchetypeIndex].Best.GetValue();
|
||||
const FShowcaseSample& Selected = PreviewSelections[ArchetypeIndex];
|
||||
const FVoxelStrateComposerCandidate& Candidate = Selected.Candidate;
|
||||
const bool bDiagnosticCard = !bHaveAllArchetypes;
|
||||
|
||||
// The output filename namespace is local to this page. Keep it distinct even if a future
|
||||
// search selects equal candidate indices from two different composer seeds; the PIE fields
|
||||
@@ -1484,7 +2054,11 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
|
||||
ShowcaseDirectory, RenderIndex,
|
||||
VF_FormatNativeParameterRecipe(Candidate),
|
||||
SampleGrid, Metrics, MeasureSettings.HeadroomCells,
|
||||
Selected.SelectionScore, Selected.LawText, false, TEXT(""),
|
||||
Selected.SelectionScore, Selected.LawText, bDiagnosticCard,
|
||||
bDiagnosticCard
|
||||
? TEXT("Diagnostic only: no complete hard-gate survivor set; this card is retained "
|
||||
"to inspect the generated scale and exact player-fit law.")
|
||||
: TEXT(""),
|
||||
PreviewCandidate, PreviewError);
|
||||
TestTrue(FString::Printf(TEXT("selected %s coarse preview writes"),
|
||||
VF_GetStrateArchetypeName(Candidate.Archetype)),
|
||||
@@ -1516,6 +2090,11 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
|
||||
const FVoxelStrateMetrics FineMetrics = VF_MeasureStrateWithSampler(
|
||||
Sampler, Selected.BottomVoxelZ, Selected.TopVoxelZ + 1, Context.EdgeSealThickness,
|
||||
FineMeasureSettings, &FineGrid);
|
||||
FVoxelStrateMetrics FinePlayerMetrics;
|
||||
VF_DiagnosePlayerFitConnectivityWithSampler(
|
||||
Sampler, Selected.BottomVoxelZ, Selected.TopVoxelZ + 1,
|
||||
Context.EdgeSealThickness, FineArrivalPoint, FineDeparturePoint,
|
||||
FineMeasureSettings, &FinePlayerMetrics);
|
||||
const auto IsInsideFineXY = [&FineGrid](const FVector& Point)
|
||||
{
|
||||
return Point.X >= FineGrid.MinX && Point.X < FineGrid.MaxX
|
||||
@@ -1531,7 +2110,7 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
|
||||
!FineMetrics.bValid || FineGrid.CellCount <= FinePreviewMaxCells);
|
||||
TestTrue(FString::Printf(TEXT("selected %s fine player-fit metrics rerun identically"),
|
||||
VF_GetStrateArchetypeName(Candidate.Archetype)),
|
||||
VF_ShowcaseMetricsEqual(Selected.FineMetrics, FineMetrics));
|
||||
VF_ShowcaseMetricsEqual(Selected.FineMetrics, FinePlayerMetrics));
|
||||
const bool bFineWritten = VF_WriteStratePreviewFineCandidate(
|
||||
ShowcaseDirectory, RenderIndex, FineGrid,
|
||||
FineMeasureSettings.HeadroomCells,
|
||||
@@ -1546,17 +2125,17 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
|
||||
VF_GetStrateArchetypeName(Candidate.Archetype), *PreviewError));
|
||||
return false;
|
||||
}
|
||||
PreviewCandidate.bPlayerFitResolved = FineMetrics.bPlayerFitResolved;
|
||||
PreviewCandidate.PlayerFitRefusalReason = FineMetrics.PlayerFitRefusalReason;
|
||||
PreviewCandidate.NumPlayerFitCells = FineMetrics.NumPlayerFitCells;
|
||||
PreviewCandidate.PlayerFitFraction = FineMetrics.PlayerFitFraction;
|
||||
PreviewCandidate.NumTraversableComponents = FineMetrics.NumTraversableComponents;
|
||||
PreviewCandidate.bPlayerFitResolved = FinePlayerMetrics.bPlayerFitResolved;
|
||||
PreviewCandidate.PlayerFitRefusalReason = FinePlayerMetrics.PlayerFitRefusalReason;
|
||||
PreviewCandidate.NumPlayerFitCells = FinePlayerMetrics.NumPlayerFitCells;
|
||||
PreviewCandidate.PlayerFitFraction = FinePlayerMetrics.PlayerFitFraction;
|
||||
PreviewCandidate.NumTraversableComponents = FinePlayerMetrics.NumTraversableComponents;
|
||||
PreviewCandidate.LargestTraversableComponentCells =
|
||||
FineMetrics.LargestTraversableComponentCells;
|
||||
PreviewCandidate.TraversableComponentShare = FineMetrics.TraversableComponentShare;
|
||||
FinePlayerMetrics.LargestTraversableComponentCells;
|
||||
PreviewCandidate.TraversableComponentShare = FinePlayerMetrics.TraversableComponentShare;
|
||||
PreviewCandidate.MinimumPlayerClearanceVoxels =
|
||||
FineMetrics.MinimumPlayerClearanceVoxels;
|
||||
if (FineMetrics.bValid)
|
||||
FinePlayerMetrics.MinimumPlayerClearanceVoxels;
|
||||
if (FinePlayerMetrics.bValid)
|
||||
{
|
||||
TestTrue(FString::Printf(TEXT("selected %s fine preview has filled and contour views"),
|
||||
VF_GetStrateArchetypeName(Candidate.Archetype)),
|
||||
@@ -1581,17 +2160,28 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
|
||||
PreviewCandidate.ComposerSeed = Candidate.Seed;
|
||||
PreviewCandidate.ComposerTargetStrateIndex = Selected.TargetStrateIndex;
|
||||
PreviewCandidate.bComposerRollStructure = false;
|
||||
PreviewCandidate.SelectionReason = FString::Printf(
|
||||
TEXT("Hard gates passed: non-vacuous, largest air component >= %.2f, and "
|
||||
"player-fit arrival → departure connectivity (fine step=1; "
|
||||
"fit cells=%lld). Selected by legacy score "
|
||||
"floor_area + 0.25*clamp(clearance/%d,0,1) + 0.10*largest_surface = %.6f; "
|
||||
"floor area is the primary walking signal. Applied to existing target slot %d."),
|
||||
LargestComponentSurvivalThreshold,
|
||||
static_cast<long long>(FineMetrics.NumPlayerFitCells),
|
||||
static_cast<int32>(ClearanceNormalizationVoxels),
|
||||
Selected.SelectionScore,
|
||||
Selected.TargetStrateIndex);
|
||||
PreviewCandidate.SelectionReason = bDiagnosticCard
|
||||
? FString::Printf(
|
||||
TEXT("Diagnostic only: the complete hard-gate set was not found. Legacy law=%s; "
|
||||
"player-fit law=%s; selected by the same score "
|
||||
"floor_area + 0.25*clamp(clearance/%d,0,1) + 0.10*largest_surface = %.6f; "
|
||||
"applied to existing target slot %d."),
|
||||
VF_ShowcaseConnectivityName(Selected.LegacyLaw.Result),
|
||||
VF_ShowcaseConnectivityName(Selected.PlayerFitLaw.Result),
|
||||
static_cast<int32>(ClearanceNormalizationVoxels),
|
||||
Selected.SelectionScore,
|
||||
Selected.TargetStrateIndex)
|
||||
: FString::Printf(
|
||||
TEXT("Hard gates passed: non-vacuous, largest air component >= %.2f, and "
|
||||
"player-fit arrival → departure connectivity (fine step=1; "
|
||||
"fit cells=%lld). Selected by legacy score "
|
||||
"floor_area + 0.25*clamp(clearance/%d,0,1) + 0.10*largest_surface = %.6f; "
|
||||
"floor area is the primary walking signal. Applied to existing target slot %d."),
|
||||
LargestComponentSurvivalThreshold,
|
||||
static_cast<long long>(FineMetrics.NumPlayerFitCells),
|
||||
static_cast<int32>(ClearanceNormalizationVoxels),
|
||||
Selected.SelectionScore,
|
||||
Selected.TargetStrateIndex);
|
||||
PreviewCandidates.Add(MoveTemp(PreviewCandidate));
|
||||
|
||||
TestTrue(FString::Printf(TEXT("selected %s retains a valid exact coarse window"),
|
||||
@@ -1603,7 +2193,7 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
|
||||
ArchetypeIndex < UE_ARRAY_COUNT(GShowcaseArchetypes);
|
||||
++ArchetypeIndex)
|
||||
{
|
||||
const FShowcaseSample& Selected = Stats[ArchetypeIndex].Best.GetValue();
|
||||
const FShowcaseSample& Selected = PreviewSelections[ArchetypeIndex];
|
||||
Summaries[ArchetypeIndex].Window = PreviewCandidates[ArchetypeIndex].Window;
|
||||
Summaries[ArchetypeIndex].WindowSummary = VF_FormatShowcaseSummaryWindow(
|
||||
Stats[ArchetypeIndex]);
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace
|
||||
// retourne ce garde-fou : il EXIGE maintenant des colonnes.
|
||||
|
||||
/**
|
||||
* ⚠️ DENSIFIÉ après le premier run vert. Aux défauts (`RoomSpacing = 80`, `RoomDensity = 0.35`)
|
||||
* ⚠️ DENSIFIÉ après le premier run vert. Aux anciens défauts (`RoomSpacing = 80`, `RoomDensity = 0.35`)
|
||||
* le premier passage a rendu **65 échantillons en grotte sur 6000, soit 1,1 %** : bit-identique,
|
||||
* oui, mais en comparant surtout du roc plein à du roc plein, là où le carve et les vers ne
|
||||
* s'exécutent même pas. Le compteur avait été écrit exactement pour dire ça, et il l'a dit ;
|
||||
@@ -125,14 +125,14 @@ namespace
|
||||
*/
|
||||
void EnableTunnelFeatures(FStrateGenerationParams& P)
|
||||
{
|
||||
P.RoomSpacing = 42.0f; // 80 → 42 : des salles à portée de chaque chunk échantillonné
|
||||
P.RoomDensity = 0.85f; // 0.35 → 0.85
|
||||
P.RoomSpacing = 42.0f; // current production scale → 42: rooms reach sampled chunks
|
||||
P.RoomDensity = 0.85f; // production .35 → .85
|
||||
P.VerticalScale = 1.35f; // ≠ 1 ⇒ le Z « effectif » diverge du Z monde partout
|
||||
|
||||
// ── ÉTAPE B1 : rugosité de paroi (4b) ────────────────────────────────────────────────
|
||||
// Écrits EXPLICITEMENT, pas laissés au défaut : un test qui dépend d'un défaut se casse en
|
||||
// silence le jour où le défaut change. `SurfaceRoughness` était le seul de ces champs non nul
|
||||
// par défaut (5.0), et l'étape A le remettait à zéro — c'est ce zéro qui disparaît ici.
|
||||
// silence le jour où le défaut change. The production default is now 2.0, but this fixture
|
||||
// deliberately uses 5.0 so the full roughness branch is exercised.
|
||||
P.SurfaceRoughness = 5.0f;
|
||||
P.RoughnessFrequency = 0.1f;
|
||||
P.RoughnessNoiseType = EVoxelNoiseType::FBM; // les 4 types sont balayés au contrôle 1c
|
||||
@@ -450,7 +450,9 @@ bool FVoxelForgeOpStackTunnelTest::RunTest(const FString& Parameters)
|
||||
}
|
||||
|
||||
const int32 MidChunkZ = ((TopVoxelZ + BottomVoxelZ) / 2) / CHUNK_SIZE;
|
||||
FStrateGenerationParams P = World.StrateManager->GetGenerationParams(FIntVector(0, 0, MidChunkZ));
|
||||
const FStrateGenerationParams ProductionP =
|
||||
World.StrateManager->GetGenerationParams(FIntVector(0, 0, MidChunkZ));
|
||||
FStrateGenerationParams P = ProductionP;
|
||||
|
||||
if (P.StrateTopWorldZ - P.StrateBottomWorldZ <= 0.0f)
|
||||
{
|
||||
@@ -1115,8 +1117,8 @@ bool FVoxelForgeOpStackTunnelTest::RunTest(const FString& Parameters)
|
||||
float WorstViolation = 0.0f;
|
||||
TMap<FString, int32> SolidKillerCounts;
|
||||
int32 NumRoomKilled = 0, NumTilesAwayFromSpine = 0;
|
||||
// ±320 voxels = 4 x RoomSpacing. Hors de la boucle : la ligne de rapport en a besoin, et
|
||||
// une étendue d'échantillonnage qu'on ne peut pas citer est une étendue qu'on ne surveille pas.
|
||||
// Fixed ±320-voxel probe envelope. The report computes its ratio to the active
|
||||
// RoomSpacing; keeping the envelope fixed makes before/after tile counts comparable.
|
||||
const int32 SpanCells = 40;
|
||||
const int32 SpanVoxelsReported = SpanCells * 8; // Extent = Step * Cells = 1 * 8
|
||||
int32 TilesHitByRooms = 0, TilesHitByTunnels = 0, TilesHitByPits = 0, TilesHitByChimneys = 0;
|
||||
@@ -1133,9 +1135,9 @@ bool FVoxelForgeOpStackTunnelTest::RunTest(const FString& Parameters)
|
||||
// ⚠️ L'ÉTENDUE XY ÉTAIT ±32 VOXELS, ET C'EST CE QUI RENDAIT CE BLOC INEXPLOITABLE.
|
||||
// `RandRange(-4, 4) * 8` échantillonnait 40 tuiles dans un cube de ±32 voxels autour de
|
||||
// (0,0) — c'est-à-dire l'endroit le PLUS creusé du monde entier, et de loin :
|
||||
// • `RoomSpacing = 80`, donc ±32 ne couvre même pas la moitié d'UNE cellule de salle ;
|
||||
// • `OriginRoomRadius = 20` garantit une grosse salle exactement à (0,0), de rayon de
|
||||
// cull `max(20·1.5, 8) + 3·4 = 42` — qui avale la quasi-totalité de la fenêtre ;
|
||||
// • the old `RoomSpacing = 80` meant ±32 did not cover half of ONE room cell ;
|
||||
// • the old `OriginRoomRadius = 20` guaranteed a large room at (0,0), whose cull
|
||||
// radius `max(20·1.5, 8) + 3·4 = 42` swallowed almost the whole window ;
|
||||
// • la spine (0,0) descend précisément là.
|
||||
// La mesure « 4.9 salles sur 7.2 atteignent la boîte » ne décrivait donc pas la densité
|
||||
// de grottes du monde, elle décrivait le hub de la spine. Aucune conclusion sur la
|
||||
@@ -1146,9 +1148,8 @@ bool FVoxelForgeOpStackTunnelTest::RunTest(const FString& Parameters)
|
||||
// produirait un verdict FAUX échoue exactement comme avant. On corrige ce que la mesure
|
||||
// REGARDE, pas ce qu'elle exige.
|
||||
//
|
||||
// The XY extent was ±32 voxels around (0,0) -- with RoomSpacing = 80 and a guaranteed
|
||||
// OriginRoomRadius = 20 room at the origin, that samples the single most cave-dense spot
|
||||
// in the world and says nothing about deep rock. Widening changes what the measurement
|
||||
// The old XY extent was ±32 voxels around (0,0) -- inside the guaranteed origin room.
|
||||
// Widening changes what the measurement
|
||||
// LOOKS AT, not what it demands: every verdict is still brute-forced below.
|
||||
const FIntVector Origin(
|
||||
Rng.RandRange(-SpanCells, SpanCells) * Extent,
|
||||
@@ -1300,7 +1301,7 @@ bool FVoxelForgeOpStackTunnelTest::RunTest(const FString& Parameters)
|
||||
// A warning that fires every run and always means "this is fine" is noise that
|
||||
// trains the reader to ignore warnings. Zero proved is the only possible answer on
|
||||
// the dense fixture (info); on production defaults it would be a real regression
|
||||
// from 11 (warning).
|
||||
// from the previous measured count (warning).
|
||||
const FString ZeroMsg = FString::Printf(
|
||||
TEXT("[%s] No tile was proved, so the brute force verified nothing -- it has no ")
|
||||
TEXT("verdict to contradict. Do NOT re-derive the cause: read the two lines above, ")
|
||||
@@ -1326,15 +1327,14 @@ bool FVoxelForgeOpStackTunnelTest::RunTest(const FString& Parameters)
|
||||
// LES DEUX MONDES, ET POURQUOI IL EN FAUT DEUX
|
||||
//-------------------------------------------------------------------------
|
||||
// ⚠️ LA FIXTURE REND CE VERDICT STRUCTURELLEMENT IMPOSSIBLE, ET CE N'EST PAS UN DÉFAUT DE LA
|
||||
// FIXTURE. `EnableTunnelFeatures` densifie délibérément (`RoomSpacing` 80 → 42,
|
||||
// `RoomDensity` 0.35 → 0.85) parce qu'aux défauts le premier run n'avait que 1,1 % des
|
||||
// FIXTURE. `EnableTunnelFeatures` densifie délibérément (production spacing → 42,
|
||||
// `RoomDensity` 0.35 → 0.85) because the first run had only 1.1% of samples in open cave,
|
||||
// échantillons en grotte — l'équivalence comparait du roc plein à du roc plein. Cette
|
||||
// densification est ce qui rend le contrôle 1 SIGNIFIANT.
|
||||
//
|
||||
// Mais elle est exactement ANTAGONISTE de la prouvabilité, et l'arithmétique le dit sans
|
||||
// ambiguïté : le rayon de cull d'une salle vaut `max(R·1.5, R·HeightRatio) + 3·SDFBlendRadius`,
|
||||
// soit `1.5R + 12` ⇒ entre 27 et 57 pour `R ∈ [10, 30]`, moyenne ≈ 42 — c'est-à-dire
|
||||
// **exactement le pas du réseau**, à 85 % d'occupation. Des sphères de cull de rayon égal au pas
|
||||
// soit `1.5R + 12`; at the dense fixture's 85% occupancy those cull spheres overlap the lattice
|
||||
// du réseau recouvrent l'espace ~3,6 fois. **Aucune boîte de ce monde ne peut être hors de
|
||||
// toutes les sphères de cull.** Le « 6.3 salles sur 8.3 atteignent la boîte » mesuré est
|
||||
// exactement ça, et élargir l'échantillonneur n'y a rien changé (39 tuiles sur 40 étaient déjà
|
||||
@@ -1353,9 +1353,7 @@ bool FVoxelForgeOpStackTunnelTest::RunTest(const FString& Parameters)
|
||||
RunTileScan(Stack, P, Ctx, TEXT("dense fixture"), /*bZeroProvedIsExpected*/ true);
|
||||
|
||||
{
|
||||
FStrateGenerationParams SparseP = P;
|
||||
SparseP.RoomSpacing = 80.0f; // le défaut d'`UVoxelStrateDefinition`
|
||||
SparseP.RoomDensity = 0.35f; // idem — voir `EnableTunnelFeatures`
|
||||
FStrateGenerationParams SparseP = ProductionP;
|
||||
|
||||
FVoxelOpStack SparseStack;
|
||||
VoxelDensityOps::BuildTunnelNetworkStack(SparseStack, SparseP, World.Settings->Seed,
|
||||
|
||||
@@ -52,7 +52,10 @@ namespace
|
||||
case ECaveGeneratorType::Maze:
|
||||
return FMath::Max(Definition.MazeParams.CellSize, 1.0f);
|
||||
case ECaveGeneratorType::VerticalShafts:
|
||||
return FMath::Max(Definition.VerticalShaftParams.ShaftSpacing, 1.0f);
|
||||
// A 0.6 shaft density does not guarantee an occupied immediate cell. The production
|
||||
// query therefore permits a bounded two-cell local search and still returns the
|
||||
// selected site's exact axis.
|
||||
return FMath::Max(2.0f * Definition.VerticalShaftParams.ShaftSpacing, 1.0f);
|
||||
case ECaveGeneratorType::FloatingIslands:
|
||||
return FMath::Max(Definition.FloatingIslandParams.IslandSpacing, 1.0f);
|
||||
default:
|
||||
|
||||
@@ -2107,7 +2107,9 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
|
||||
FVoxelStrateMeasureSettings BaseSettings;
|
||||
BaseSettings.CenterXY = FVector2D::ZeroVector;
|
||||
BaseSettings.MaxCells = 8000000;
|
||||
// The centered control stays bounded after the 4 m vertical-volume fixture is retained;
|
||||
// 12 M also leaves room for the scale-aware 320-voxel control below.
|
||||
BaseSettings.MaxCells = 12000000;
|
||||
BaseSettings.HeadroomCells = 2;
|
||||
BaseSettings.InteriorMarginVoxels = -1;
|
||||
|
||||
@@ -2121,8 +2123,8 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
{4, 256},
|
||||
{4, 192},
|
||||
{2, 192},
|
||||
{4, 128},
|
||||
{1, 128},
|
||||
{4, 160},
|
||||
{1, 160},
|
||||
};
|
||||
|
||||
bool bAllChecksPassed = true;
|
||||
@@ -2458,7 +2460,7 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
}
|
||||
Summary += TEXT(
|
||||
"Controlled comparisons hold radius constant: (step 4, radius 192) vs (step 2, "
|
||||
"radius 192), and (step 4, radius 128) vs (step 1, radius 128).\n");
|
||||
"radius 192), and (step 4, radius 160) vs (step 1, radius 160).\n");
|
||||
Summary += FString::Printf(
|
||||
TEXT("VerticalShafts refinement sweep wall-clock: %.3f seconds.\n"),
|
||||
SweepSeconds);
|
||||
@@ -3236,10 +3238,17 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
// PART A/B — the production acceptance window follows the actual two mouths. The older
|
||||
// centered-window diagnostics above remain useful for the origin-spine invariant; they are
|
||||
// deliberately not reused here because a mouth-fitted box is not expected to contain (0,0).
|
||||
constexpr int32 FittedMaxCells = 20000000;
|
||||
// The tree's prescribed neighbour fallback reaches three shaft-grid cells. A mouth window
|
||||
// must therefore cover three spacings beyond the endpoints before it can be called a fitted
|
||||
// diagnostic box. This grows with the body-scale shaft spacing rather than preserving the
|
||||
// old 48-voxel window by habit.
|
||||
const FVerticalShaftParams& FittedShaftParams =
|
||||
Layout[VerticalShaftsIndex].Definition->VerticalShaftParams;
|
||||
const float FittedMargin = FMath::Max(
|
||||
48.0f, 3.0f * FMath::Max(FittedShaftParams.ShaftSpacing, 1.0f));
|
||||
const float DoubledFittedMargin = 2.0f * FittedMargin;
|
||||
constexpr int32 FittedMaxCells = 120000000;
|
||||
constexpr int32 BeforeChangeMaxCells = 12000000;
|
||||
constexpr float FittedMargin = 48.0f;
|
||||
constexpr float DoubledFittedMargin = 96.0f;
|
||||
|
||||
auto MakeFittedSettings = [&](int32 SampleStep,
|
||||
const FVector& A,
|
||||
@@ -3297,8 +3306,8 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
};
|
||||
|
||||
Summary += TEXT(
|
||||
"PART A — fitted VerticalShafts mouth window (AABB of the two XY mouths + 48 voxels; "
|
||||
"step 1/2; fitted MaxCells=20000000):\n");
|
||||
"PART A — fitted VerticalShafts mouth window (AABB of the two XY mouths + three shaft "
|
||||
"spacings; step 1/2; doubled-margin control; fitted MaxCells=120000000):\n");
|
||||
Summary += TEXT(
|
||||
" seed | step | default margin verdict | fitted dimensions/cells | doubled-margin check | "
|
||||
"interpretation | arrival component share | departure component share\n");
|
||||
@@ -3341,12 +3350,21 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("HARD FAILURE: fitted-window seed %d has a VerticalShafts mouth not on a "
|
||||
"shaft axis (arrival axis=%s, departure axis=%s)."),
|
||||
"shaft axis (arrival axis=%s, departure axis=%s; arrival=(%.3f,%.3f,%.3f); "
|
||||
"departure=(%.3f,%.3f,%.3f); spacing=%.3f; density=%.3f)."),
|
||||
Seed,
|
||||
IsVerticalShaftAxis(ShaftParams, Seed, ResolutionArrivalPoint)
|
||||
? TEXT("yes") : TEXT("no"),
|
||||
IsVerticalShaftAxis(ShaftParams, Seed, ResolutionDeparturePoint)
|
||||
? TEXT("yes") : TEXT("no")));
|
||||
? TEXT("yes") : TEXT("no"),
|
||||
ResolutionArrivalPoint.X,
|
||||
ResolutionArrivalPoint.Y,
|
||||
ResolutionArrivalPoint.Z,
|
||||
ResolutionDeparturePoint.X,
|
||||
ResolutionDeparturePoint.Y,
|
||||
ResolutionDeparturePoint.Z,
|
||||
ShaftParams.ShaftSpacing,
|
||||
ShaftParams.ShaftDensity));
|
||||
bPartAFittedChecksPassed = false;
|
||||
}
|
||||
|
||||
@@ -3491,13 +3509,13 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
}
|
||||
|
||||
Summary += TEXT(
|
||||
"PART B — fitted 16-seed audit (step 2, margin 48): strict-local-minimum shafts are the "
|
||||
"PART B — fitted 16-seed audit (step 2, margin=three shaft spacings): strict-local-minimum shafts are the "
|
||||
"pre-fix orphan candidates; post-fix parent paths are checked with the same fixed ±2/±3 "
|
||||
"resolver over a padded [-20,20] cell audit domain. Small-component threshold is one "
|
||||
"mean-radius shaft volume.\n");
|
||||
Summary += TEXT(
|
||||
" seed | fitted verdict | cells | components | small non-largest/total | strict minima/shafts "
|
||||
"| neighbour fallback | path orphans | axis mouths | doubled margin 96 verdict/cells | "
|
||||
"| neighbour fallback | path orphans | axis mouths | doubled fitted-margin verdict/cells | "
|
||||
"tree path XY bounds\n");
|
||||
|
||||
static constexpr int32 FittedSeedCases[] = {
|
||||
@@ -3859,7 +3877,7 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
SmallComponentThresholdMin == INT32_MAX ? 0 : SmallComponentThresholdMin,
|
||||
SmallComponentThresholdMax);
|
||||
Summary += FString::Printf(
|
||||
TEXT(" fitted acceptance arrival->departure: %s (%d/%d effective pass; direct margin48="
|
||||
TEXT(" fitted acceptance arrival->departure: %s (%d/%d effective pass; direct fitted margin="
|
||||
"%d/%d, %d fail; binding negatives=%d, trusted negatives=%d, measurement limits=%d); "
|
||||
"sweep wall-clock=%.3f seconds.\n"),
|
||||
SweepPasses == UE_ARRAY_COUNT(FittedSeedCases) && SweepFailures == 0
|
||||
|
||||
@@ -73,7 +73,8 @@ namespace VoxelForgeTest
|
||||
* dangerous — and `VoxelForge.Determinism.LargeSeedSurvives` deliberately passes big ones
|
||||
* (up to 2e9) to prove it stays that way.
|
||||
*/
|
||||
void Build(int32 InSeed = 1337, int32 InGapChunks = 2, bool bUseOperatorStack = false)
|
||||
void Build(int32 InSeed = 1337, int32 InGapChunks = 2, bool bUseOperatorStack = false,
|
||||
int32 InStrateHeightInChunks = 4)
|
||||
{
|
||||
Settings = TStrongObjectPtr<UVoxelSettings>(
|
||||
NewObject<UVoxelSettings>(GetTransientPackage(), NAME_None, RF_Transient));
|
||||
@@ -105,7 +106,11 @@ namespace VoxelForgeTest
|
||||
UVoxelStrateDefinition* Def = NewObject<UVoxelStrateDefinition>(
|
||||
GetTransientPackage(), NAME_None, RF_Transient);
|
||||
Def->GeneratorType = Archetypes[i];
|
||||
Def->StrateHeightInChunks = 4;
|
||||
// Keep the shared density fixture at the historical 4-chunk test volume unless
|
||||
// a caller explicitly asks for the production/default 8-chunk envelope. The
|
||||
// refinement and roll tests use the smaller synthetic volume as a bounded memory
|
||||
// control; the owner-facing showcase opts into 8 chunks below.
|
||||
Def->StrateHeightInChunks = FMath::Max(1, InStrateHeightInChunks);
|
||||
// L'OPT-IN de la pile d'opérateurs. Faux par défaut : les treize tests existants
|
||||
// doivent continuer à exercer le `switch`, qui reste le comportement de référence.
|
||||
// Seul le test de solidité de ClassifyTie côté pile le passe à vrai.
|
||||
|
||||
@@ -634,6 +634,13 @@ namespace
|
||||
// than a point on the smoothed ledge edge.
|
||||
float LandingFromBottom = FMath::Max(
|
||||
Params.BoundarySealThickness, Params.LedgeDepth) + FloorClearance;
|
||||
// The measurement API derives its default interior margin as two seal thicknesses.
|
||||
// Keep a source-level landing strictly inside that same measured window; otherwise a
|
||||
// lower-roughness default can produce a perfectly valid shaft landing just below the
|
||||
// diagnostic grid's lower edge and turn a real mouth query into OUT_OF_WINDOW.
|
||||
LandingFromBottom = FMath::Max(
|
||||
LandingFromBottom,
|
||||
2.0f * Params.BoundarySealThickness + 0.01f);
|
||||
if (Params.LedgeSpacing > 0.0f && Params.LedgeDepth > 0.0f)
|
||||
{
|
||||
const float SafeBand = Params.LedgeDepth + FloorClearance;
|
||||
|
||||
@@ -1119,34 +1119,34 @@ namespace
|
||||
if (FieldName == TEXT("WormThreshold")) return Set(0.01f, 0.50f);
|
||||
if (FieldName == TEXT("WormStrength")) return Set(1.0f, 64.0f);
|
||||
if (FieldName == TEXT("WormNetworkRange")) return Set(0.0f, H * 1.5f);
|
||||
if (FieldName == TEXT("RoomSpacing")) return Set(16.0f, H * 2.5f);
|
||||
if (FieldName == TEXT("RoomSpacing")) return Set(64.0f, FMath::Max(96.0f, H * 0.75f));
|
||||
if (FieldName == TEXT("RoomDensity")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("MinRoomRadius")) return Set(1.0f, HHalf);
|
||||
if (FieldName == TEXT("MaxRoomRadius")) return Set(1.0f, H);
|
||||
if (FieldName == TEXT("MinRoomRadius")) return Set(16.0f, FMath::Max(32.0f, HHalf));
|
||||
if (FieldName == TEXT("MaxRoomRadius")) return Set(16.0f, FMath::Max(48.0f, H * 0.75f));
|
||||
if (FieldName == TEXT("RoomHeightRatio")) return Set(0.10f, 1.0f);
|
||||
if (FieldName == TEXT("RoomShapeVariety")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("RoomFloorCutMin")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("RoomFloorCutMax")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("FloorReliefStrength")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("FloorReliefFrequency")) return Set(0.001f, 0.10f);
|
||||
if (FieldName == TEXT("OriginRoomRadius")) return Set(0.0f, H * 0.75f);
|
||||
if (FieldName == TEXT("TunnelMinRadius")) return Set(1.0f, HQuarter);
|
||||
if (FieldName == TEXT("TunnelMaxRadius")) return Set(1.0f, HHalf);
|
||||
if (FieldName == TEXT("OriginRoomRadius")) return Set(24.0f, FMath::Max(64.0f, H * 0.75f));
|
||||
if (FieldName == TEXT("TunnelMinRadius")) return Set(6.0f, FMath::Max(8.0f, HQuarter));
|
||||
if (FieldName == TEXT("TunnelMaxRadius")) return Set(8.0f, FMath::Max(16.0f, H * 0.25f));
|
||||
if (FieldName == TEXT("TunnelDensity")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("MaxTunnelLength")) return Set(1.0f, H * 4.0f);
|
||||
if (FieldName == TEXT("MaxTunnelLength")) return Set(64.0f, H * 4.0f);
|
||||
if (FieldName == TEXT("TunnelWarpStrength")) return Set(0.0f, H);
|
||||
if (FieldName == TEXT("TunnelHorizontalBias")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("TunnelEndpointZOffset")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("SDFBlendRadius")) return Set(0.0f, H * 0.15f);
|
||||
if (FieldName == TEXT("SDFBlendRadius")) return Set(2.0f, FMath::Max(4.0f, H * 0.15f));
|
||||
if (FieldName == TEXT("WaterLevelRelative")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("CaveWarpStrength")) return Set(0.0f, HHalf);
|
||||
if (FieldName == TEXT("CaveWarpFrequency")) return Set(0.001f, 0.10f);
|
||||
if (FieldName == TEXT("SurfaceRoughness")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("SurfaceRoughness")) return Set(0.5f, FMath::Max(4.0f, H * 0.04f));
|
||||
if (FieldName == TEXT("RoughnessFrequency")) return Set(0.001f, 0.20f);
|
||||
if (FieldName == TEXT("BoundarySealThickness")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("DomainWarpStrength")) return Set(0.0f, HHalf);
|
||||
if (FieldName == TEXT("DomainWarpFrequency")) return Set(0.001f, 0.20f);
|
||||
if (FieldName == TEXT("FloorBias")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("FloorBias")) return Set(0.0f, FMath::Max(4.0f, H * 0.10f));
|
||||
if (FieldName == TEXT("TerraceStepHeight")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("TerraceHardness")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("TerraceNoiseDisplacement")) return Set(0.0f, 1.0f);
|
||||
@@ -1161,22 +1161,22 @@ namespace
|
||||
if (FieldName == TEXT("ScallopStrength")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("ScallopFrequency")) return Set(0.001f, 0.20f);
|
||||
if (FieldName == TEXT("ArchDensity")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("ArchMinRadius")) return Set(1.0f, H * 0.20f);
|
||||
if (FieldName == TEXT("ArchMaxRadius")) return Set(1.0f, H * 0.20f);
|
||||
if (FieldName == TEXT("ArchMinRadius")) return Set(6.0f, FMath::Max(12.0f, H * 0.20f));
|
||||
if (FieldName == TEXT("ArchMaxRadius")) return Set(12.0f, FMath::Max(24.0f, H * 0.30f));
|
||||
if (FieldName == TEXT("ColumnDensity")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("ColumnMinRadius")) return Set(1.0f, H * 0.20f);
|
||||
if (FieldName == TEXT("ColumnMaxRadius")) return Set(1.0f, H * 0.40f);
|
||||
if (FieldName == TEXT("ColumnMinRadius")) return Set(4.0f, FMath::Max(8.0f, H * 0.20f));
|
||||
if (FieldName == TEXT("ColumnMaxRadius")) return Set(8.0f, FMath::Max(16.0f, H * 0.40f));
|
||||
if (FieldName == TEXT("PitDensity")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("PitMinRadius")) return Set(1.0f, H * 0.20f);
|
||||
if (FieldName == TEXT("PitMaxRadius")) return Set(1.0f, H * 0.40f);
|
||||
if (FieldName == TEXT("PitMinRadius")) return Set(8.0f, FMath::Max(16.0f, H * 0.20f));
|
||||
if (FieldName == TEXT("PitMaxRadius")) return Set(16.0f, FMath::Max(32.0f, H * 0.40f));
|
||||
if (FieldName == TEXT("PitDepth")) return Set(0.0f, HHalf);
|
||||
if (FieldName == TEXT("ChimneyDensity")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("ChimneyMinRadius")) return Set(1.0f, H * 0.20f);
|
||||
if (FieldName == TEXT("ChimneyMaxRadius")) return Set(1.0f, H * 0.40f);
|
||||
if (FieldName == TEXT("ChimneyMinRadius")) return Set(4.0f, FMath::Max(8.0f, H * 0.20f));
|
||||
if (FieldName == TEXT("ChimneyMaxRadius")) return Set(8.0f, FMath::Max(16.0f, H * 0.40f));
|
||||
if (FieldName == TEXT("ChimneyHeight")) return Set(0.0f, HHalf);
|
||||
if (FieldName == TEXT("DomeDensity")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("DomeMinRadius")) return Set(1.0f, H * 0.30f);
|
||||
if (FieldName == TEXT("DomeMaxRadius")) return Set(1.0f, H * 0.50f);
|
||||
if (FieldName == TEXT("DomeMinRadius")) return Set(16.0f, FMath::Max(32.0f, H * 0.30f));
|
||||
if (FieldName == TEXT("DomeMaxRadius")) return Set(32.0f, FMath::Max(48.0f, H * 0.50f));
|
||||
if (FieldName == TEXT("DomeHeightRatio")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("PinchDensity")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("PinchStrength")) return Set(0.0f, H);
|
||||
@@ -1190,58 +1190,58 @@ namespace
|
||||
case ECaveGeneratorType::CrystalChamber:
|
||||
if (FieldName == TEXT("FloorRelativeHeight")) return Set(0.0f, 0.95f);
|
||||
if (FieldName == TEXT("CeilingRelativeHeight")) return Set(0.05f, 1.0f);
|
||||
if (FieldName == TEXT("FloorRoughness")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("FloorRoughness")) return Set(0.5f, FMath::Max(4.0f, H * 0.04f));
|
||||
if (FieldName == TEXT("FloorRoughnessFrequency")) return Set(0.001f, 0.15f);
|
||||
if (FieldName == TEXT("CeilingRoughness")) return Set(0.0f, HHalf);
|
||||
if (FieldName == TEXT("CeilingRoughness")) return Set(1.0f, FMath::Max(16.0f, H * 0.12f));
|
||||
if (FieldName == TEXT("CeilingRoughnessFrequency")) return Set(0.001f, 0.15f);
|
||||
if (FieldName == TEXT("ColumnDensity")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("ColumnMinRadius")) return Set(1.0f, H * 0.20f);
|
||||
if (FieldName == TEXT("ColumnMaxRadius")) return Set(1.0f, H * 0.40f);
|
||||
if (FieldName == TEXT("ColumnSpacing")) return Set(10.0f, H * 1.5f);
|
||||
if (FieldName == TEXT("ColumnMinRadius")) return Set(8.0f, FMath::Max(12.0f, H * 0.20f));
|
||||
if (FieldName == TEXT("ColumnMaxRadius")) return Set(16.0f, FMath::Max(24.0f, H * 0.40f));
|
||||
if (FieldName == TEXT("ColumnSpacing")) return Set(64.0f, FMath::Max(96.0f, H * 0.75f));
|
||||
if (FieldName == TEXT("BoundarySealThickness")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("BaseDensity")) return Set(1.0f, 32.0f);
|
||||
return false;
|
||||
|
||||
case ECaveGeneratorType::Maze:
|
||||
if (FieldName == TEXT("CellSize")) return Set(8.0f, H * 2.0f);
|
||||
if (FieldName == TEXT("CorridorRadius")) return Set(1.0f, HQuarter);
|
||||
if (FieldName == TEXT("CellSize")) return Set(48.0f, FMath::Max(64.0f, FMath::Min(96.0f, H * 0.75f)));
|
||||
if (FieldName == TEXT("CorridorRadius")) return Set(6.0f, 8.0f);
|
||||
if (FieldName == TEXT("BranchProbability")) return Set(0.05f, 1.0f);
|
||||
if (FieldName == TEXT("Verticality")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("SurfaceRoughness")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("SurfaceRoughness")) return Set(0.5f, FMath::Max(3.0f, H * 0.02f));
|
||||
if (FieldName == TEXT("BoundarySealThickness")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("BaseDensity")) return Set(1.0f, 32.0f);
|
||||
return false;
|
||||
|
||||
case ECaveGeneratorType::SurfaceWorld:
|
||||
if (FieldName == TEXT("BaseGroundRelative")) return Set(0.05f, 0.90f);
|
||||
if (FieldName == TEXT("ElevationRange")) return Set(0.0f, H * 2.0f);
|
||||
if (FieldName == TEXT("ElevationRange")) return Set(24.0f, FMath::Max(80.0f, H * 0.50f));
|
||||
if (FieldName == TEXT("ContinentFrequency")) return Set(0.0005f, 0.10f);
|
||||
if (FieldName == TEXT("MountainStrength")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("MountainFrequency")) return Set(0.0005f, 0.10f);
|
||||
if (FieldName == TEXT("DetailFrequency")) return Set(0.0005f, 0.20f);
|
||||
if (FieldName == TEXT("SurfaceRoughness")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("HeightWarpStrength")) return Set(0.0f, H);
|
||||
if (FieldName == TEXT("SurfaceRoughness")) return Set(1.0f, FMath::Max(3.0f, H * 0.02f));
|
||||
if (FieldName == TEXT("HeightWarpStrength")) return Set(0.0f, FMath::Max(48.0f, H * 0.25f));
|
||||
if (FieldName == TEXT("HeightWarpFrequency")) return Set(0.0005f, 0.10f);
|
||||
if (FieldName == TEXT("ReliefFrequency")) return Set(0.0002f, 0.02f);
|
||||
if (FieldName == TEXT("ReliefStrength")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("ReliefContrast")) return Set(0.25f, 4.0f);
|
||||
if (FieldName == TEXT("TerraceStrength")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("TerraceHeight")) return Set(1.0f, HHalf);
|
||||
if (FieldName == TEXT("TerraceHeight")) return Set(2.0f, FMath::Max(8.0f, H * 0.08f));
|
||||
if (FieldName == TEXT("TerraceHardness")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("LayerLineDepth")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("LayerLineSpacing")) return Set(1.0f, H);
|
||||
if (FieldName == TEXT("LayerLineSpacing")) return Set(2.0f, FMath::Max(8.0f, H * 0.25f));
|
||||
if (FieldName == TEXT("CliffStrength")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("CliffSlopeThreshold")) return Set(0.05f, 2.0f);
|
||||
if (FieldName == TEXT("CliffSharpness")) return Set(0.0f, 4.0f);
|
||||
if (FieldName == TEXT("CliffSampleDist")) return Set(0.5f, HQuarter);
|
||||
if (FieldName == TEXT("OverhangStrength")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("OverhangReach")) return Set(0.0f, HHalf);
|
||||
if (FieldName == TEXT("OverhangHeight")) return Set(0.0f, HHalf);
|
||||
if (FieldName == TEXT("OverhangReach")) return Set(4.0f, FMath::Max(16.0f, H * 0.15f));
|
||||
if (FieldName == TEXT("OverhangHeight")) return Set(4.0f, FMath::Max(24.0f, H * 0.25f));
|
||||
if (FieldName == TEXT("OverhangFrequency")) return Set(0.0005f, 0.20f);
|
||||
if (FieldName == TEXT("OverhangZScale")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("OverhangSlopeThreshold")) return Set(0.05f, 2.0f);
|
||||
if (FieldName == TEXT("WaterLevelRelative")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("BeachWidth")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("BeachWidth")) return Set(4.0f, FMath::Max(12.0f, H * 0.10f));
|
||||
if (FieldName == TEXT("CeilingRelative")) return Set(0.30f, 1.0f);
|
||||
if (FieldName == TEXT("CeilingRoughness")) return Set(0.0f, HHalf);
|
||||
if (FieldName == TEXT("CeilingRoughnessFrequency")) return Set(0.0005f, 0.20f);
|
||||
@@ -1256,29 +1256,29 @@ namespace
|
||||
return false;
|
||||
|
||||
case ECaveGeneratorType::VerticalShafts:
|
||||
if (FieldName == TEXT("ShaftSpacing")) return Set(10.0f, H * 2.0f);
|
||||
if (FieldName == TEXT("ShaftSpacing")) return Set(64.0f, FMath::Max(80.0f, FMath::Min(112.0f, H * 0.75f)));
|
||||
if (FieldName == TEXT("ShaftDensity")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("ShaftMinRadius")) return Set(1.0f, H * 0.20f);
|
||||
if (FieldName == TEXT("ShaftMaxRadius")) return Set(1.0f, H * 0.40f);
|
||||
if (FieldName == TEXT("ShaftMinRadius")) return Set(8.0f, 16.0f);
|
||||
if (FieldName == TEXT("ShaftMaxRadius")) return Set(12.0f, 24.0f);
|
||||
if (FieldName == TEXT("CrossConnectChance")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("ConnectorRadius")) return Set(1.0f, H * 0.20f);
|
||||
if (FieldName == TEXT("LedgeSpacing")) return Set(0.0f, H);
|
||||
if (FieldName == TEXT("LedgeDepth")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("SurfaceRoughness")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("ConnectorRadius")) return Set(6.0f, 12.0f);
|
||||
if (FieldName == TEXT("LedgeSpacing")) return Set(8.0f, FMath::Max(32.0f, H * 0.25f));
|
||||
if (FieldName == TEXT("LedgeDepth")) return Set(2.0f, FMath::Max(4.0f, H * 0.08f));
|
||||
if (FieldName == TEXT("SurfaceRoughness")) return Set(0.5f, FMath::Max(3.0f, H * 0.02f));
|
||||
if (FieldName == TEXT("BoundarySealThickness")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("BaseDensity")) return Set(1.0f, 32.0f);
|
||||
return false;
|
||||
|
||||
case ECaveGeneratorType::FloatingIslands:
|
||||
if (FieldName == TEXT("IslandSpacing")) return Set(20.0f, H * 3.0f);
|
||||
if (FieldName == TEXT("IslandSpacing")) return Set(96.0f, FMath::Max(112.0f, FMath::Min(160.0f, H * 0.75f)));
|
||||
if (FieldName == TEXT("IslandDensity")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("IslandMinRadius")) return Set(2.0f, H * 0.30f);
|
||||
if (FieldName == TEXT("IslandMaxRadius")) return Set(2.0f, H * 0.75f);
|
||||
if (FieldName == TEXT("IslandMinRadius")) return Set(24.0f, FMath::Max(32.0f, H * 0.30f));
|
||||
if (FieldName == TEXT("IslandMaxRadius")) return Set(48.0f, FMath::Max(64.0f, H * 0.50f));
|
||||
if (FieldName == TEXT("ThicknessRatio")) return Set(0.10f, 1.5f);
|
||||
if (FieldName == TEXT("VerticalJitter")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("TopFlatten")) return Set(0.0f, 1.0f);
|
||||
if (FieldName == TEXT("SurfaceRoughness")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("SDFBlendRadius")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("SurfaceRoughness")) return Set(0.5f, FMath::Max(3.0f, H * 0.02f));
|
||||
if (FieldName == TEXT("SDFBlendRadius")) return Set(2.0f, FMath::Max(6.0f, H * 0.10f));
|
||||
if (FieldName == TEXT("BoundarySealThickness")) return Set(0.0f, HQuarter);
|
||||
if (FieldName == TEXT("BaseDensity")) return Set(1.0f, 32.0f);
|
||||
return false;
|
||||
@@ -1533,35 +1533,47 @@ namespace
|
||||
float StrateHeightInVoxels)
|
||||
{
|
||||
const float H = FMath::Max(StrateHeightInVoxels, static_cast<float>(CHUNK_SIZE));
|
||||
const float SpacingMin = FMath::Min(48.0f, H * 0.35f);
|
||||
const float SpacingMax = FMath::Max(SpacingMin + 8.0f, FMath::Min(160.0f, H * 0.94f));
|
||||
// Human-scale derivation: an 8 m room is the smallest room-like volume (radius 16),
|
||||
// a 20 m chamber is the large-room target (radius 40), and a 24 m hub is the three-
|
||||
// chunk boss-space target. Keep the room-cell margin independent of those radii.
|
||||
const float SpacingMin = FMath::Max(96.0f, H * 0.35f);
|
||||
const float SpacingMax = FMath::Max(SpacingMin + 32.0f, FMath::Min(160.0f, H * 0.94f));
|
||||
const float Coverage = VF_CorpusFreeU(Rng, 0.08f, 0.18f);
|
||||
|
||||
P.BoundarySealThickness = VF_CorpusFreeU(
|
||||
Rng, FMath::Min(3.0f, H * 0.02f), FMath::Min(8.0f, H * 0.08f));
|
||||
P.RoomSpacing = VF_CorpusFreeU(Rng, SpacingMin, SpacingMax);
|
||||
P.RoomDensity = VF_CorpusFreeU(Rng, 0.25f, 0.75f);
|
||||
P.RoomDensity = VF_CorpusFreeU(Rng, 0.20f, 0.45f);
|
||||
const float RadiusRatio = FMath::Sqrt(
|
||||
Coverage / (PI * FMath::Max(P.RoomDensity, 0.001f)));
|
||||
P.MaxRoomRadius = P.RoomSpacing * RadiusRatio;
|
||||
P.MinRoomRadius = P.MaxRoomRadius * VF_CorpusFreeU(Rng, 0.35f, 0.75f);
|
||||
P.RoomHeightRatio = VF_CorpusFreeU(Rng, 0.25f, 0.85f);
|
||||
P.OriginRoomRadius = P.MaxRoomRadius * VF_CorpusFreeU(Rng, 0.60f, 0.95f);
|
||||
P.MaxRoomRadius = FMath::Min(
|
||||
FMath::Max(40.0f, P.RoomSpacing * RadiusRatio),
|
||||
FMath::Min(P.RoomSpacing * 0.45f, H * 0.30f));
|
||||
P.MinRoomRadius = FMath::Max(
|
||||
16.0f, P.MaxRoomRadius * VF_CorpusFreeU(Rng, 0.45f, 0.70f));
|
||||
P.MinRoomRadius = FMath::Min(P.MinRoomRadius, P.MaxRoomRadius);
|
||||
P.RoomHeightRatio = VF_CorpusFreeU(Rng, 0.70f, 0.95f);
|
||||
const float MaxRoomVerticalRadius = FMath::Max(
|
||||
8.0f, H * 0.50f - P.BoundarySealThickness - 1.0f);
|
||||
P.OriginRoomRadius = FMath::Min(
|
||||
P.MaxRoomRadius * VF_CorpusFreeU(Rng, 0.90f, 1.15f),
|
||||
MaxRoomVerticalRadius / FMath::Max(P.RoomHeightRatio, 0.1f));
|
||||
P.OriginRoomRadius = FMath::Max(P.OriginRoomRadius, P.MinRoomRadius);
|
||||
|
||||
P.RoomFloorCutMin = VF_CorpusFreeU(Rng, 0.35f, 0.85f);
|
||||
P.RoomFloorCutMin = VF_CorpusFreeU(Rng, 0.55f, 0.75f);
|
||||
P.RoomFloorCutMax = P.RoomFloorCutMin + VF_CorpusFreeU(
|
||||
Rng, 0.0f, 1.0f - P.RoomFloorCutMin);
|
||||
Rng, 0.0f, FMath::Min(0.25f, 1.0f - P.RoomFloorCutMin));
|
||||
P.FloorReliefStrength = VF_CorpusFreeU(
|
||||
Rng, 0.0f,
|
||||
FMath::Min(12.0f, P.MaxRoomRadius * P.RoomHeightRatio * 0.25f));
|
||||
|
||||
const float TunnelMinCap = FMath::Max(1.5f, P.MinRoomRadius * 0.45f);
|
||||
const float TunnelMinCap = FMath::Max(6.0f, FMath::Min(8.0f, P.MinRoomRadius * 0.45f));
|
||||
P.TunnelMinRadius = FMath::Max(
|
||||
1.5f, P.MinRoomRadius * VF_CorpusFreeU(Rng, 0.25f, 0.45f));
|
||||
6.0f, P.MinRoomRadius * VF_CorpusFreeU(Rng, 0.25f, 0.40f));
|
||||
P.TunnelMinRadius = FMath::Min(P.TunnelMinRadius, TunnelMinCap);
|
||||
P.TunnelMaxRadius = FMath::Max(
|
||||
P.TunnelMinRadius,
|
||||
P.MinRoomRadius * VF_CorpusFreeU(Rng, 0.55f, 0.90f));
|
||||
FMath::Min(8.0f, P.MinRoomRadius * VF_CorpusFreeU(Rng, 0.50f, 0.75f)));
|
||||
|
||||
// Adjacent cells can differ in both X and Y. The placement jitter is [0.15, 0.85]
|
||||
// cell, so the worst local diagonal separation is 1.7 * S * sqrt(2), not the
|
||||
@@ -1570,17 +1582,17 @@ namespace
|
||||
const float VerticalReach = 2.0f * P.MaxRoomRadius * P.RoomHeightRatio;
|
||||
const float NeighborReach = FMath::Sqrt(
|
||||
HorizontalReach * HorizontalReach + VerticalReach * VerticalReach);
|
||||
P.MaxTunnelLength = NeighborReach * VF_CorpusFreeU(Rng, 1.0f, 1.40f);
|
||||
P.TunnelWarpStrength = P.MaxTunnelLength * VF_CorpusFreeU(Rng, 0.02f, 0.12f);
|
||||
P.WormStrength = P.BaseDensity * VF_CorpusFreeU(Rng, 1.10f, 1.80f);
|
||||
P.WormNetworkRange = P.RoomSpacing * VF_CorpusFreeU(Rng, 0.20f, 0.60f);
|
||||
P.CaveWarpStrength = P.RoomSpacing * VF_CorpusFreeU(Rng, 0.02f, 0.12f);
|
||||
P.MaxTunnelLength = NeighborReach * VF_CorpusFreeU(Rng, 1.05f, 1.30f);
|
||||
P.TunnelWarpStrength = P.MaxTunnelLength * VF_CorpusFreeU(Rng, 0.03f, 0.08f);
|
||||
P.WormStrength = P.BaseDensity * VF_CorpusFreeU(Rng, 1.35f, 1.80f);
|
||||
P.WormNetworkRange = P.RoomSpacing * VF_CorpusFreeU(Rng, 0.25f, 0.50f);
|
||||
P.CaveWarpStrength = P.RoomSpacing * VF_CorpusFreeU(Rng, 0.03f, 0.10f);
|
||||
|
||||
P.SDFBlendRadius = VF_CorpusFreeU(
|
||||
Rng, 0.5f, FMath::Max(0.5f, FMath::Min(4.0f, P.TunnelMinRadius * 0.35f)));
|
||||
Rng, 2.0f, FMath::Max(2.0f, FMath::Min(4.0f, P.TunnelMinRadius * 0.50f)));
|
||||
const float RoughnessMax = FMath::Max(
|
||||
0.0f, (P.TunnelMinRadius - P.SDFBlendRadius - 0.25f) / VOXEL_NOISE_SCALE);
|
||||
P.SurfaceRoughness = VF_CorpusFreeU(Rng, 0.0f, RoughnessMax * 0.70f);
|
||||
P.SurfaceRoughness = VF_CorpusFreeU(Rng, 0.5f, RoughnessMax * 0.60f);
|
||||
P.FloorBias = VF_CorpusFreeU(
|
||||
Rng, 0.0f, P.MaxRoomRadius * P.RoomHeightRatio * 0.25f);
|
||||
|
||||
@@ -1597,7 +1609,12 @@ namespace
|
||||
P.ArchMaxRadius = FMath::Max(
|
||||
P.ArchMinRadius,
|
||||
P.MinRoomRadius * VF_CorpusFreeU(Rng, 0.20f, 0.45f));
|
||||
P.DomeMaxRadius = P.MaxRoomRadius * VF_CorpusFreeU(Rng, 0.30f, 0.60f);
|
||||
// The public envelope has a 32-voxel minimum dome radius. Apply that envelope before
|
||||
// deriving the height ratio; otherwise a small sampled dome would be clamped larger
|
||||
// after this equation and could violate the room-height relation.
|
||||
P.DomeMaxRadius = FMath::Min(
|
||||
P.MaxRoomRadius,
|
||||
FMath::Max(32.0f, P.MaxRoomRadius * VF_CorpusFreeU(Rng, 0.30f, 0.60f)));
|
||||
P.DomeMinRadius = P.DomeMaxRadius * VF_CorpusFreeU(Rng, 0.35f, 0.75f);
|
||||
P.DomeHeightRatio = FMath::Min(
|
||||
1.0f,
|
||||
@@ -1615,23 +1632,27 @@ namespace
|
||||
const float H = FMath::Max(StrateHeightInVoxels, static_cast<float>(CHUNK_SIZE));
|
||||
P.BoundarySealThickness = VF_CorpusFreeU(
|
||||
Rng, FMath::Min(3.0f, H * 0.02f), FMath::Min(8.0f, H * 0.08f));
|
||||
P.FloorRoughness = VF_CorpusFreeU(Rng, 0.0f, H * 0.04f);
|
||||
P.CeilingRoughness = VF_CorpusFreeU(Rng, 0.0f, H * 0.08f);
|
||||
// A slab is an open fight/chamber volume, not a one-voxel shell. At the new
|
||||
// eight-chunk default this leaves roughly 20-50 m between the two surfaces after
|
||||
// reserving the 2.5-4 m feature envelopes below.
|
||||
P.FloorRoughness = VF_CorpusFreeU(Rng, 1.0f, FMath::Max(4.0f, H * 0.04f));
|
||||
P.CeilingRoughness = VF_CorpusFreeU(Rng, 2.0f, FMath::Max(8.0f, H * 0.06f));
|
||||
|
||||
const float FloorNoise = VOXEL_NOISE_SCALE * P.FloorRoughness;
|
||||
const float CeilingNoise = VOXEL_NOISE_SCALE * P.CeilingRoughness;
|
||||
const float FloorClearance = VF_CorpusFreeU(Rng, H * 0.08f, H * 0.16f);
|
||||
const float CeilingClearance = VF_CorpusFreeU(Rng, H * 0.08f, H * 0.16f);
|
||||
const float FloorClearance = VF_CorpusFreeU(Rng, H * 0.08f, H * 0.12f);
|
||||
const float CeilingClearance = VF_CorpusFreeU(Rng, H * 0.08f, H * 0.12f);
|
||||
P.FloorRelativeHeight = (P.BoundarySealThickness + FloorNoise + FloorClearance) / H;
|
||||
P.CeilingRelativeHeight = 1.0f
|
||||
- (P.BoundarySealThickness + CeilingNoise + CeilingClearance) / H;
|
||||
|
||||
P.ColumnSpacing = VF_CorpusFreeU(
|
||||
Rng, FMath::Max(24.0f, H * 0.30f), FMath::Max(32.0f, H * 0.75f));
|
||||
P.ColumnMinRadius = VF_CorpusFreeU(Rng, 1.0f, P.ColumnSpacing * 0.08f);
|
||||
Rng, FMath::Max(96.0f, H * 0.375f),
|
||||
FMath::Max(128.0f, FMath::Min(160.0f, H * 0.75f)));
|
||||
P.ColumnMinRadius = VF_CorpusFreeU(Rng, 8.0f, 12.0f);
|
||||
P.ColumnMaxRadius = FMath::Max(
|
||||
P.ColumnMinRadius,
|
||||
FMath::Min(P.ColumnSpacing * 0.40f, H * 0.38f));
|
||||
FMath::Min(P.ColumnSpacing * 0.40f, P.ColumnMinRadius + 8.0f));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1640,12 +1661,16 @@ namespace
|
||||
float StrateHeightInVoxels)
|
||||
{
|
||||
const float H = FMath::Max(StrateHeightInVoxels, static_cast<float>(CHUNK_SIZE));
|
||||
// Body-derived lattice: the fight corridor is 3-4 m wide (radius 6-8 voxels),
|
||||
// while the lattice cell is 12-16 m (48-64 voxels). Thus a corridor radius is
|
||||
// about one eighth of a cell, far below CellSize/2, leaving a real rock wall.
|
||||
P.CellSize = VF_CorpusFreeU(
|
||||
Rng, FMath::Max(24.0f, H * 0.25f), FMath::Min(96.0f, H * 0.75f));
|
||||
P.CorridorRadius = P.CellSize * VF_CorpusFreeU(Rng, 0.12f, 0.24f);
|
||||
Rng, 48.0f, FMath::Max(64.0f, FMath::Min(96.0f, H * 0.75f)));
|
||||
P.CorridorRadius = VF_CorpusFreeU(
|
||||
Rng, 6.0f, FMath::Min(8.0f, P.CellSize * 0.20f));
|
||||
const float RoughnessCap = FMath::Max(
|
||||
0.0f, (P.CorridorRadius + 2.0f) / (VOXEL_NOISE_SCALE * 1.25f));
|
||||
P.SurfaceRoughness = VF_CorpusFreeU(Rng, 0.0f, RoughnessCap * 0.70f);
|
||||
P.SurfaceRoughness = VF_CorpusFreeU(Rng, 0.5f, RoughnessCap * 0.60f);
|
||||
P.BoundarySealThickness = VF_CorpusFreeU(
|
||||
Rng, FMath::Min(3.0f, H * 0.02f), FMath::Min(8.0f, H * 0.08f));
|
||||
return true;
|
||||
@@ -1658,9 +1683,11 @@ namespace
|
||||
const float H = FMath::Max(StrateHeightInVoxels, static_cast<float>(CHUNK_SIZE));
|
||||
P.BoundarySealThickness = VF_CorpusFreeU(
|
||||
Rng, FMath::Min(3.0f, H * 0.02f), FMath::Min(8.0f, H * 0.08f));
|
||||
P.ElevationRange = VF_CorpusFreeU(Rng, H * 0.06f, H * 0.16f);
|
||||
// Outdoor relief uses 6-20 m as its authored human-scale band; the absolute
|
||||
// height is still tied to H so the sky cap has room above the terrain.
|
||||
P.ElevationRange = VF_CorpusFreeU(Rng, H * 0.10f, H * 0.25f);
|
||||
P.MountainStrength = VF_CorpusFreeU(Rng, 0.20f, 0.80f);
|
||||
P.SurfaceRoughness = VF_CorpusFreeU(Rng, 0.0f, H * 0.02f);
|
||||
P.SurfaceRoughness = VF_CorpusFreeU(Rng, 1.0f, FMath::Max(3.0f, H * 0.02f));
|
||||
|
||||
const float GroundLowerDeviation = P.ElevationRange * 0.50f
|
||||
+ P.SurfaceRoughness * VOXEL_NOISE_SCALE;
|
||||
@@ -1743,18 +1770,21 @@ namespace
|
||||
const float H = FMath::Max(StrateHeightInVoxels, static_cast<float>(CHUNK_SIZE));
|
||||
P.BoundarySealThickness = VF_CorpusFreeU(
|
||||
Rng, FMath::Min(3.0f, H * 0.02f), FMath::Min(8.0f, H * 0.08f));
|
||||
// A shaft is a 4-7 m diameter vertical room; connectors are 3-6 m bores.
|
||||
// The 20-28 m site spacing leaves a substantial rock web between shafts.
|
||||
P.ShaftSpacing = VF_CorpusFreeU(
|
||||
Rng, FMath::Max(32.0f, H * 0.30f),
|
||||
FMath::Max(32.0f, FMath::Min(112.0f, H * 0.90f)));
|
||||
P.SurfaceRoughness = VF_CorpusFreeU(Rng, 0.0f, H * 0.04f);
|
||||
Rng, 80.0f, FMath::Max(96.0f, FMath::Min(112.0f, H * 0.90f)));
|
||||
P.SurfaceRoughness = VF_CorpusFreeU(Rng, 1.0f, FMath::Max(3.0f, H * 0.02f));
|
||||
const float RoughnessReach = P.SurfaceRoughness * VOXEL_NOISE_SCALE * 1.5f;
|
||||
P.ShaftMinRadius = RoughnessReach + 0.5f + VF_CorpusFreeU(Rng, 1.0f, 4.0f);
|
||||
P.ShaftMinRadius = FMath::Max(
|
||||
8.0f, RoughnessReach + 0.5f + VF_CorpusFreeU(Rng, 1.0f, 3.0f));
|
||||
P.ShaftMaxRadius = FMath::Max(
|
||||
P.ShaftMinRadius + 0.5f,
|
||||
FMath::Min(P.ShaftSpacing * 0.40f, P.ShaftMinRadius + 16.0f));
|
||||
P.ConnectorRadius = RoughnessReach + 1.0f + VF_CorpusFreeU(Rng, 0.0f, 3.0f);
|
||||
P.ShaftMinRadius + 4.0f,
|
||||
FMath::Min(P.ShaftSpacing * 0.40f, P.ShaftMinRadius + 8.0f));
|
||||
P.ConnectorRadius = FMath::Max(
|
||||
6.0f, RoughnessReach + 1.0f + VF_CorpusFreeU(Rng, 0.0f, 2.0f));
|
||||
P.LedgeDepth = VF_CorpusFreeU(
|
||||
Rng, 0.5f, FMath::Max(0.5f, FMath::Min(4.0f, P.ShaftSpacing * 0.08f)));
|
||||
Rng, 2.0f, FMath::Max(4.0f, FMath::Min(6.0f, P.ShaftSpacing * 0.08f)));
|
||||
const float FloorClearance = FMath::Max(
|
||||
1.0f, P.SurfaceRoughness * VOXEL_NOISE_SCALE + 1.0f);
|
||||
P.LedgeSpacing = 2.0f * (P.LedgeDepth + FloorClearance)
|
||||
@@ -1770,21 +1800,23 @@ namespace
|
||||
const float H = FMath::Max(StrateHeightInVoxels, static_cast<float>(CHUNK_SIZE));
|
||||
P.BoundarySealThickness = VF_CorpusFreeU(
|
||||
Rng, FMath::Min(3.0f, H * 0.02f), FMath::Min(8.0f, H * 0.08f));
|
||||
// Island radii are deliberately larger than the roughness envelope: 12-24 m
|
||||
// diameters read as land masses, while 28-40 m site spacing prevents mush.
|
||||
P.IslandSpacing = VF_CorpusFreeU(
|
||||
Rng, FMath::Max(40.0f, H * 0.30f),
|
||||
FMath::Max(40.0f, FMath::Min(120.0f, H * 0.75f)));
|
||||
P.ThicknessRatio = VF_CorpusFreeU(Rng, 0.30f, 0.90f);
|
||||
Rng, 112.0f, FMath::Max(112.0f, FMath::Min(160.0f, H * 0.75f)));
|
||||
P.ThicknessRatio = VF_CorpusFreeU(Rng, 0.45f, 0.75f);
|
||||
const float MaxByHeight = (H * 0.50f - P.BoundarySealThickness)
|
||||
/ FMath::Max(P.ThicknessRatio, 0.25f);
|
||||
const float MaxRadius = FMath::Max(
|
||||
2.0f, FMath::Min(P.IslandSpacing * 0.45f, MaxByHeight * 0.85f));
|
||||
48.0f, FMath::Min(P.IslandSpacing * 0.45f, MaxByHeight * 0.85f));
|
||||
P.IslandMaxRadius = MaxRadius;
|
||||
P.IslandMinRadius = P.IslandMaxRadius * VF_CorpusFreeU(Rng, 0.45f, 0.80f);
|
||||
P.IslandMinRadius = FMath::Max(
|
||||
24.0f, P.IslandMaxRadius * VF_CorpusFreeU(Rng, 0.50f, 0.80f));
|
||||
P.SDFBlendRadius = VF_CorpusFreeU(
|
||||
Rng, 0.5f, FMath::Min(6.0f, P.IslandMinRadius * 0.20f));
|
||||
Rng, 2.0f, FMath::Min(6.0f, P.IslandMinRadius * 0.20f));
|
||||
const float RoughnessMax = FMath::Max(
|
||||
0.0f, (P.IslandMinRadius - P.SDFBlendRadius) / VOXEL_NOISE_SCALE);
|
||||
P.SurfaceRoughness = VF_CorpusFreeU(Rng, 0.0f, RoughnessMax * 0.70f);
|
||||
P.SurfaceRoughness = VF_CorpusFreeU(Rng, 0.5f, RoughnessMax * 0.60f);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1860,8 +1892,39 @@ namespace
|
||||
}
|
||||
if (bValid && bConstraintSampled)
|
||||
{
|
||||
bValid = VF_ApplyCorpusFreeConstraints(Archetype, Params, ScalarRng,
|
||||
StrateHeightInVoxels);
|
||||
// Dependent constraints are authored in terms of the final vector, while the
|
||||
// reflected envelope clamp below can move an independently sampled field (for
|
||||
// example SurfaceWorld elevation) after that derivation. Treat the fully clamped
|
||||
// vector as the candidate and deterministically resample until the declared
|
||||
// relations hold; never return a vector whose coupling was only true pre-clamp.
|
||||
constexpr int32 MaxConstraintAttempts = 64;
|
||||
FString ConstraintViolation;
|
||||
bool bConstraintValid = false;
|
||||
for (int32 Attempt = 0; Attempt < MaxConstraintAttempts; ++Attempt)
|
||||
{
|
||||
bConstraintValid = VF_ApplyCorpusFreeConstraints(
|
||||
Archetype, Params, ScalarRng, StrateHeightInVoxels);
|
||||
if (!bConstraintValid)
|
||||
{
|
||||
break;
|
||||
}
|
||||
VF_ClampCorpusFreeFields(Params, Archetype, StrateHeightInVoxels);
|
||||
VF_ResetExcludedFields(Params, Archetype);
|
||||
if (VF_ValidateStrateCorpusFreeConstraints(
|
||||
Archetype, Params, StrateHeightInVoxels, ConstraintViolation))
|
||||
{
|
||||
break;
|
||||
}
|
||||
bConstraintValid = false;
|
||||
}
|
||||
if (!bConstraintValid)
|
||||
{
|
||||
Result.FailureReason = ConstraintViolation.IsEmpty()
|
||||
? TEXT("Constraint sampler could not produce a clamped legal vector.")
|
||||
: FString::Printf(TEXT("Constraint sampler exhausted legal retries: %s"),
|
||||
*ConstraintViolation);
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
if (!bValid)
|
||||
{
|
||||
@@ -1869,10 +1932,12 @@ namespace
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Last safety net only: all dependent values above are derived before this envelope
|
||||
// clamp, and no corpus/default value is consulted.
|
||||
VF_ClampCorpusFreeFields(Params, Archetype, StrateHeightInVoxels);
|
||||
VF_ResetExcludedFields(Params, Archetype);
|
||||
if (!bConstraintSampled)
|
||||
{
|
||||
// Last safety net for the independent arm; no corpus/default value is consulted.
|
||||
VF_ClampCorpusFreeFields(Params, Archetype, StrateHeightInVoxels);
|
||||
VF_ResetExcludedFields(Params, Archetype);
|
||||
}
|
||||
Result.ArchetypeParams = Params;
|
||||
Result.Params = Params.TunnelNetworkParams;
|
||||
Result.bValid = true;
|
||||
|
||||
@@ -627,9 +627,11 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
return FMath::Max(Definition.MazeParams.CellSize, 1.0f);
|
||||
|
||||
case ECaveGeneratorType::VerticalShafts:
|
||||
// One shaft grid spacing is the natural nearest-site budget. A sparser layout may
|
||||
// legitimately decline instead of pulling the passage away from the spine.
|
||||
return FMath::Max(Definition.VerticalShaftParams.ShaftSpacing, 1.0f);
|
||||
// A density of 0.6 does not guarantee an occupied cell in the immediate grid
|
||||
// neighbourhood. Allow the nearest occupied shaft to be two grid cells away while
|
||||
// remaining a bounded local query; the landing query still returns that shaft's
|
||||
// exact axis, so this cannot create a non-topological mouth.
|
||||
return FMath::Max(2.0f * Definition.VerticalShaftParams.ShaftSpacing, 1.0f);
|
||||
|
||||
case ECaveGeneratorType::FloatingIslands:
|
||||
// One island grid spacing protects the intentional radial placement while still
|
||||
|
||||
@@ -57,7 +57,7 @@ struct VOXELFORGE_API FVoxelSeasonSelectionPolicy
|
||||
struct VOXELFORGE_API FVoxelSeasonFixedStrate
|
||||
{
|
||||
int32 Seed = 0;
|
||||
int32 HeightInChunks = 4;
|
||||
int32 HeightInChunks = 8;
|
||||
ECaveGeneratorType Archetype = ECaveGeneratorType::TunnelNetwork;
|
||||
FVoxelStrateArchetypeParams Params;
|
||||
FVoxelOpStackRecipe Recipe;
|
||||
@@ -85,7 +85,7 @@ struct VOXELFORGE_API FVoxelSeasonCompositionSettings
|
||||
|
||||
// Generated candidates use one stable height. Fixed authored slots may provide their own
|
||||
// height. World layout bounds are written into every selected parameter family below.
|
||||
int32 StrateHeightInChunks = 4;
|
||||
int32 StrateHeightInChunks = 8;
|
||||
int32 InterStrateGapChunks = 0;
|
||||
float OriginSpineRadius = 14.0f;
|
||||
float WorldRadiusVoxels = 0.0f;
|
||||
@@ -125,7 +125,9 @@ struct VOXELFORGE_API FVoxelSeasonCompositionSettings
|
||||
|
||||
PlayerFitMeasureSettings.SampleStep = 1;
|
||||
PlayerFitMeasureSettings.RadiusInVoxels = 64;
|
||||
PlayerFitMeasureSettings.MaxCells = 2000000;
|
||||
// A fitted mouth-to-mouth XY window across an 8-chunk (256-voxel) strate can approach
|
||||
// four million cells. Keep the cap finite and below the old multi-gigabyte retry cost.
|
||||
PlayerFitMeasureSettings.MaxCells = 4000000;
|
||||
PlayerFitMeasureSettings.MaxRouteRetries = 16;
|
||||
PlayerFitMeasureSettings.HeadroomCells = 2;
|
||||
PlayerFitMeasureSettings.InteriorMarginVoxels = -1;
|
||||
|
||||
@@ -66,11 +66,11 @@ public:
|
||||
|
||||
// How many chunks tall this strate is.
|
||||
// Each strate can have a different height!
|
||||
// Big open caverns = 6-8 chunks, tight tunnels = 3, vertical shaft = 12+
|
||||
// Surface/sky worlds want a high sky-cap ceiling, so they go much taller.
|
||||
// 1 chunk = 32 voxels x 25cm = 8 m, so 256 chunks ~= 2 km of vertical strate.
|
||||
// Human-scale defaults use 8 chunks = 64 m. This gives a 20 m+ vertical budget for
|
||||
// cathedral rooms, tall shafts, and the surface sky cap without changing the lateral gate.
|
||||
// 1 chunk = 32 voxels x 25cm = 8 m, so the maximum 256 chunks is ~= 2 km of vertical strate.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Dimensions", meta = (ClampMin = "1", ClampMax = "256"))
|
||||
int32 StrateHeightInChunks = 4;
|
||||
int32 StrateHeightInChunks = 8;
|
||||
|
||||
//=========================================================================
|
||||
// BOUNDARY TRANSITION
|
||||
|
||||
@@ -38,11 +38,11 @@ struct VOXELFORGE_API FVoxelStrateFinePreviewSettings
|
||||
{
|
||||
// The default is deliberately one voxel: the pass exists to expose detail that step 4 cannot.
|
||||
int32 SampleStep = 1;
|
||||
// radius=64 gives a 128x128 XY ROI and stays below the default two-million-cell cap for the
|
||||
// four-chunk fixture. The caller may re-centre it on the deterministic largest open space
|
||||
// radius=64 gives a 128x128 XY ROI and stays below the default four-million-cell cap for the
|
||||
// eight-chunk fixture. The caller may re-centre it on the deterministic largest open space
|
||||
// point found by the coarse air flood fill; it is not inherently the strate midpoint.
|
||||
int32 RadiusInVoxels = 64;
|
||||
int32 MaxCells = 2000000;
|
||||
int32 MaxCells = 4000000;
|
||||
FVector2D CenterXY = FVector2D::ZeroVector;
|
||||
int32 InteriorMarginVoxels = -1;
|
||||
|
||||
|
||||
@@ -446,10 +446,10 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
// the cave system instead of spraying disconnected noise pockets through the whole
|
||||
// strate (the far-field "confetti"). 0 = unlimited (legacy unmasked behaviour).
|
||||
// 16 → tight braiding right along rooms/tunnels
|
||||
// 24 → braids + short noodle shortcuts (good default)
|
||||
// 48+ → loose, wandering side-passages
|
||||
// 64 → 16 m braids + side passages (human-scale default)
|
||||
// 96+ → loose, wandering side-passages
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Worm Tunnels", meta = (ClampMin = "0.0"))
|
||||
float WormNetworkRange = 24.0f;
|
||||
float WormNetworkRange = 64.0f;
|
||||
|
||||
// ===== CAVE MORPHOLOGY (room-and-corridor) =====
|
||||
//
|
||||
@@ -465,12 +465,12 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
|
||||
// Distance between room grid cells (in voxels).
|
||||
// This controls room SPACING, not size. Larger = rooms further apart.
|
||||
// 40 → dense room network, rooms nearly touching
|
||||
// 80 → moderate density, corridors between rooms (good default)
|
||||
// 120 → sparse rooms, long corridors
|
||||
// 64 → dense room network
|
||||
// 128 → 32 m cell spacing (human-scale default)
|
||||
// 160 → sparse rooms, long corridors
|
||||
// 200+→ isolated chambers with long tunnel treks
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Rooms")
|
||||
float RoomSpacing = 80.0f;
|
||||
float RoomSpacing = 128.0f;
|
||||
|
||||
// Probability of a room existing in each grid cell (0-1).
|
||||
// Not every cell gets a room — this controls how many are filled.
|
||||
@@ -483,28 +483,29 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
|
||||
// Smallest possible room radius (in voxels).
|
||||
// Small rooms feel like alcoves or nooks.
|
||||
// 5-8 → small alcoves
|
||||
// 10-15→ natural small chambers (good default)
|
||||
// 20+ → even "small" rooms are spacious
|
||||
// 8-12 → alcoves and small rooms
|
||||
// 16 → 8 m diameter room (human-scale minimum)
|
||||
// 24+ → spacious small rooms
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Rooms")
|
||||
float MinRoomRadius = 10.0f;
|
||||
float MinRoomRadius = 16.0f;
|
||||
|
||||
// Largest possible room radius (in voxels).
|
||||
// Large rooms are cathedral chambers.
|
||||
// 15-20→ moderate rooms
|
||||
// 25-35→ large caverns (good default)
|
||||
// 50+ → massive cathedral spaces
|
||||
// 24-32→ moderate rooms
|
||||
// 40 → 20 m diameter chamber (large-room default)
|
||||
// 80+ → massive cathedral spaces
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Rooms")
|
||||
float MaxRoomRadius = 30.0f;
|
||||
float MaxRoomRadius = 40.0f;
|
||||
|
||||
// How vertically squished rooms are.
|
||||
// 1.0 = perfect sphere (tall as wide).
|
||||
// Lower = flatter, more horizontal chambers.
|
||||
// 0.2-0.3 → very flat caverns (strate-like, wide and low)
|
||||
// 0.4-0.5 → natural cave chambers (good default)
|
||||
// 0.4-0.5 → natural cave chambers
|
||||
// 0.85 → tall rooms (17 m diameter-height envelope at radius 40)
|
||||
// 0.7-1.0 → tall, cathedral-like rooms
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Rooms", meta = (ClampMin = "0.1", ClampMax = "1.0"))
|
||||
float RoomHeightRatio = 0.4f;
|
||||
float RoomHeightRatio = 0.85f;
|
||||
|
||||
// Per-room floor flatness range. Each room hash-rolls a value in [Min, Max].
|
||||
// FloorCutZ = RoomCenter.Z - RoomRadiusZ * roll
|
||||
@@ -521,11 +522,11 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
// with navigable flat floors.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Rooms",
|
||||
meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float RoomFloorCutMin = 1.0f;
|
||||
float RoomFloorCutMin = 0.70f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Rooms",
|
||||
meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float RoomFloorCutMax = 1.0f;
|
||||
float RoomFloorCutMax = 0.90f;
|
||||
|
||||
// Large-scale floor undulation — how many voxels the floor plane rises and falls.
|
||||
// This is NOT surface roughness (which adds rock texture). This shifts the entire
|
||||
@@ -535,13 +536,14 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
//
|
||||
// Only meaningful when RoomFloorCutMin < 1.0 (flat floor mode active).
|
||||
//
|
||||
// 0 → perfectly flat floor (default — use surface roughness for texture)
|
||||
// 0 → perfectly flat floor
|
||||
// 4 → 1 m variation (human-scale default; subtle beside a room)
|
||||
// 2-4 → subtle floor variation, barely perceptible gradient
|
||||
// 5-10 → clear hills and basins — interesting to walk across
|
||||
// 15+ → dramatic terrain — slopes and drops within the room
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Rooms",
|
||||
meta = (ClampMin = "0.0", EditCondition = "RoomFloorCutMin < 1.0"))
|
||||
float FloorReliefStrength = 0.0f;
|
||||
float FloorReliefStrength = 4.0f;
|
||||
|
||||
// How wide the floor hills are. Lower = broader, gentler undulations.
|
||||
// 0.005 → very broad (one hill spans the whole room)
|
||||
@@ -567,10 +569,11 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
// making it the natural hub of the strate's cave network.
|
||||
// 0 → disabled (no guaranteed origin room)
|
||||
// 15 → moderate hub room
|
||||
// 20 → large central chamber (good default)
|
||||
// 30+ → massive starting cavern
|
||||
// 32 → large central chamber
|
||||
// 48 → 24 m hub (three-chunk boss-space default)
|
||||
// 64+ → massive starting cavern
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Rooms")
|
||||
float OriginRoomRadius = 20.0f;
|
||||
float OriginRoomRadius = 48.0f;
|
||||
|
||||
// Maximum number of backbone tunnels that can force-connect to the origin room.
|
||||
// Without a limit, every room in the search area that picks origin as its nearest
|
||||
@@ -588,18 +591,18 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
// ===== TUNNELS (connecting corridors between rooms) =====
|
||||
|
||||
// Smallest tunnel radius (in voxels). Creates tight squeeze corridors.
|
||||
// 2-3 → claustrophobic crawlspaces
|
||||
// 3-4 → tight but passable (good default)
|
||||
// 5+ → even "narrow" tunnels are comfortable
|
||||
// 4-5 → marginal passage
|
||||
// 6 → 3 m bore (minimum comfortable round passage)
|
||||
// 8 → 4 m bore (human-scale wide passage)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Tunnels")
|
||||
float TunnelMinRadius = 3.0f;
|
||||
float TunnelMinRadius = 6.0f;
|
||||
|
||||
// Largest tunnel radius (in voxels). Creates wide passages.
|
||||
// 5-6 → comfortable walking tunnels
|
||||
// 7-8 → wide passages (good default)
|
||||
// 6 → 3 m bore (comfortable walking tunnel)
|
||||
// 8 → 4 m bore (fight-space passage)
|
||||
// 10+ → practically small rooms
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Tunnels")
|
||||
float TunnelMaxRadius = 7.0f;
|
||||
float TunnelMaxRadius = 8.0f;
|
||||
|
||||
// Probability that two nearby rooms are connected by a tunnel (0-1).
|
||||
// 0.2 → sparse connections, mostly dead-end rooms
|
||||
@@ -610,10 +613,11 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
|
||||
// Maximum tunnel length (in voxels). Rooms further apart won't connect.
|
||||
// 100 → only close neighbors
|
||||
// 200 → moderate reach (good default)
|
||||
// 200 → moderate reach
|
||||
// 360 → 90 m, reaches the worst jittered neighbour at 32 m cell spacing
|
||||
// 400+→ long-range connections possible
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Tunnels")
|
||||
float MaxTunnelLength = 200.0f;
|
||||
float MaxTunnelLength = 360.0f;
|
||||
|
||||
// How much tunnel paths curve (in voxels of sideways displacement).
|
||||
// Without this, tunnels are straight lines between rooms.
|
||||
@@ -622,10 +626,10 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
// Capped at 25% of tunnel length to prevent kinky short tunnels.
|
||||
// 0 → perfectly straight tunnels (artificial look)
|
||||
// 8-12→ gentle natural curves
|
||||
// 15 → clearly winding passages (good default)
|
||||
// 24 → 6 m lateral bend (human-scale default)
|
||||
// 25+ → very curvy, meandering corridors
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Tunnels")
|
||||
float TunnelWarpStrength = 15.0f;
|
||||
float TunnelWarpStrength = 24.0f;
|
||||
|
||||
// Preference for horizontal connections over vertical (0-1).
|
||||
// Real caves are mostly horizontal — vertical connections are rarer.
|
||||
@@ -652,17 +656,17 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
// Combined with terrain ops (cliffs, terraces), this creates multi-level rooms
|
||||
// where one tunnel exits at the upper ledge and another at the lower floor.
|
||||
// 0.0 → all tunnels connect at room center height
|
||||
// 0.5 → endpoints range from -50% to +50% of room height (good default)
|
||||
// 0.25 → endpoints use a restrained quarter-height offset (default)
|
||||
// 1.0 → full range — tunnels can enter near floor or ceiling
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Tunnels", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float TunnelEndpointZOffset = 0.5f;
|
||||
float TunnelEndpointZOffset = 0.25f;
|
||||
|
||||
// ===== SDF BLEND (junction smoothness) =====
|
||||
|
||||
// Smooth union blend radius. Controls how rounded the junctions
|
||||
// are where rooms meet tunnels (or rooms meet rooms).
|
||||
// 1-2 → sharp, angular junctions
|
||||
// 3-5 → natural rounded junctions (good default)
|
||||
// 4 → 1 m rounded junction (default)
|
||||
// 8+ → very blobby, organic blending
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Blend")
|
||||
float SDFBlendRadius = 4.0f;
|
||||
@@ -685,10 +689,10 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
// This controls the overall organic distortion of the cave skeleton.
|
||||
// 0 → disabled — pristine geometric rooms and straight tunnels
|
||||
// 4-6 → subtle natural variation (rooms slightly irregular)
|
||||
// 8-12→ clearly organic caves, tunnels visibly curve (good default)
|
||||
// 16 → 4 m skeleton distortion (human-scale default)
|
||||
// 16+ → heavily distorted, surreal cave shapes
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Cave Warp")
|
||||
float CaveWarpStrength = 8.0f;
|
||||
float CaveWarpStrength = 16.0f;
|
||||
|
||||
// Frequency of the warp noise. Lower = broader, smoother bends.
|
||||
// This controls the SCALE of the distortion.
|
||||
@@ -708,10 +712,11 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
// How strong the roughness is (in voxels of displacement).
|
||||
// 0 → perfectly smooth SDF shapes
|
||||
// 2-3 → subtle rocky texture
|
||||
// 4-6 → natural rocky cave surfaces (good default)
|
||||
// 2 → 0.5 m wall texture (human-scale default; bounded reach 0.94 m)
|
||||
// 4-6 → stronger natural rocky cave surfaces
|
||||
// 8+ → very rough, jagged rock
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Roughness")
|
||||
float SurfaceRoughness = 5.0f;
|
||||
float SurfaceRoughness = 2.0f;
|
||||
|
||||
// Frequency of the roughness noise. Higher = finer details.
|
||||
// 0.05 → large rocky features (boulders, ledges)
|
||||
@@ -758,13 +763,14 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
// Only applied below room center Z (fades to 0 at center, strongest at floor).
|
||||
// Does NOT affect the open space above room center or walls/ceiling.
|
||||
//
|
||||
// 0 → disabled — full roughness on all surfaces (default)
|
||||
// 0 → disabled — full roughness on all surfaces
|
||||
// 2-3 → subtle flattening, floor still has character
|
||||
// 4 → 1 m of floor bias (human-scale default)
|
||||
// 4-6 → noticeably flatter floor while keeping wall/ceiling rough
|
||||
// 8+ → near-flat floor (good for navigable caves)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Roughness",
|
||||
meta = (ClampMin = "0.0"))
|
||||
float FloorBias = 0.0f;
|
||||
float FloorBias = 4.0f;
|
||||
|
||||
// ===== TERRAIN OPERATIONS =====
|
||||
//
|
||||
@@ -837,8 +843,9 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
float OverhangStrength = 0.0f;
|
||||
|
||||
// How far overhangs protrude from walls (in voxels of density added).
|
||||
// 2-3 → small, 5-8 → natural, 10+ → dramatic
|
||||
float OverhangDepth = 5.0f;
|
||||
// 8 → 2 m optional shelf (human-scale default)
|
||||
// 16+ → dramatic
|
||||
float OverhangDepth = 8.0f;
|
||||
|
||||
// Frequency of the overhang noise. Z freq is auto 5x lower for horizontal features.
|
||||
// 0.03 → large rare, 0.06 → moderate, 0.1+ → many small
|
||||
@@ -919,8 +926,9 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
// 0.06 → moderate (good for cathedral strates)
|
||||
// 0.1 → frequent bridges
|
||||
float ArchDensity = 0.0f;
|
||||
float ArchMinRadius = 3.0f;
|
||||
float ArchMaxRadius = 6.0f;
|
||||
// Radius of an optional bridge tube: 1.5-3 m at the human-scale defaults.
|
||||
float ArchMinRadius = 6.0f;
|
||||
float ArchMaxRadius = 12.0f;
|
||||
|
||||
// ===== COLUMNS / PILLARS =====
|
||||
//
|
||||
@@ -939,8 +947,8 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
// 0.15 → moderate (good for cathedral strates)
|
||||
// 0.3 → dense forest of pillars
|
||||
float ColumnDensity = 0.0f;
|
||||
float ColumnMinRadius = 2.0f;
|
||||
float ColumnMaxRadius = 5.0f;
|
||||
float ColumnMinRadius = 4.0f;
|
||||
float ColumnMaxRadius = 8.0f;
|
||||
|
||||
// ===== PITS / VERTICAL SHAFTS =====
|
||||
//
|
||||
@@ -960,24 +968,25 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
// 0.08 → moderate (good for deep strates with vertical gameplay)
|
||||
// 0.15 → frequent pits (chaotic, maze-like)
|
||||
float PitDensity = 0.0f;
|
||||
float PitMinRadius = 4.0f;
|
||||
float PitMaxRadius = 10.0f;
|
||||
float PitDepth = 25.0f;
|
||||
float PitMinRadius = 8.0f;
|
||||
float PitMaxRadius = 16.0f;
|
||||
float PitDepth = 32.0f;
|
||||
|
||||
// ----- Chimney / Shaft (upward voids — inverse of pits) -----
|
||||
|
||||
// Chimneys: narrow vertical tubes piercing upward from cave ceilings.
|
||||
float ChimneyDensity = 0.0f;
|
||||
float ChimneyMinRadius = 2.0f;
|
||||
float ChimneyMaxRadius = 5.0f;
|
||||
float ChimneyHeight = 20.0f;
|
||||
float ChimneyMinRadius = 4.0f;
|
||||
float ChimneyMaxRadius = 8.0f;
|
||||
float ChimneyHeight = 32.0f;
|
||||
|
||||
// ----- Dome (hemispherical chamber ceilings) -----
|
||||
|
||||
// Domes: hemispherical chamber ceilings for cathedral-like feel.
|
||||
// Domes: hemispherical chamber ceilings for cathedral-like feel. The default 16-32 voxel
|
||||
// radius is a 4-8 m optional ceiling feature; the surrounding room remains the main volume.
|
||||
float DomeDensity = 0.0f;
|
||||
float DomeMinRadius = 8.0f;
|
||||
float DomeMaxRadius = 15.0f;
|
||||
float DomeMinRadius = 16.0f;
|
||||
float DomeMaxRadius = 32.0f;
|
||||
float DomeHeightRatio = 0.8f;
|
||||
|
||||
// ----- Pinch / Bottleneck (passage narrowing) -----
|
||||
@@ -985,7 +994,7 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
// Pinch: passage narrowing for bottlenecks/chokepoints.
|
||||
float PinchDensity = 0.0f;
|
||||
float PinchStrength = 5.0f;
|
||||
float PinchLength = 12.0f;
|
||||
float PinchLength = 24.0f;
|
||||
|
||||
// ----- Strate boundary sealing -----
|
||||
|
||||
@@ -1129,7 +1138,7 @@ struct VOXELFORGE_API FSlabGenerationParams
|
||||
// 0.0 = at the very bottom of the strate, 1.0 = at the very top.
|
||||
// Keep this well below CeilingRelativeHeight or you get no open space.
|
||||
// 0.1-0.2 → floor is near the bottom (lots of headroom)
|
||||
// 0.25 → floor at 25% height (good default)
|
||||
// 0.25 → floor at 16 m in a 64 m strate (human-scale default)
|
||||
// 0.4+ → very low ceiling; claustrophobic plains
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Slab|Shape",
|
||||
meta = (ClampMin = "0.0", ClampMax = "0.95"))
|
||||
@@ -1138,12 +1147,12 @@ struct VOXELFORGE_API FSlabGenerationParams
|
||||
// Where the ceiling surface sits, relative to this strate's height.
|
||||
// Must be above FloorRelativeHeight. The difference determines how tall
|
||||
// the open void is.
|
||||
// 0.6 → moderate ceiling height (tight plains)
|
||||
// 0.8 → tall open space (good default for FlatPlain)
|
||||
// 0.6 → ceiling at 38.4 m in a 64 m strate (22.4 m open span)
|
||||
// 0.8 → tall open space
|
||||
// 0.9+ → nearly the full strate height is open (cavernous plains)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Slab|Shape",
|
||||
meta = (ClampMin = "0.05", ClampMax = "1.0"))
|
||||
float CeilingRelativeHeight = 0.80f;
|
||||
float CeilingRelativeHeight = 0.60f;
|
||||
|
||||
// ===== FLOOR ROUGHNESS =====
|
||||
// Perlin-based displacement of the floor surface up/down.
|
||||
@@ -1152,7 +1161,8 @@ struct VOXELFORGE_API FSlabGenerationParams
|
||||
|
||||
// How many voxels the floor surface can shift up or down.
|
||||
// 0 → perfectly flat floor (artificial, but dramatic)
|
||||
// 3-5 → subtle rolling ground (good for FlatPlain)
|
||||
// 4 → 1 m rolling ground displacement (human-scale default)
|
||||
// 3-5 → subtle rolling ground
|
||||
// 8+ → significant hills, deep valleys
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Slab|Floor")
|
||||
float FloorRoughness = 4.0f;
|
||||
@@ -1172,8 +1182,8 @@ struct VOXELFORGE_API FSlabGenerationParams
|
||||
|
||||
// How far ceiling formations hang down into the void (in voxels).
|
||||
// 0 → flat ceiling (no formations)
|
||||
// 4-6 → subtle bumps (FlatPlain default)
|
||||
// 10-15 → significant formations (CrystalChamber default)
|
||||
// 6 → 1.5 m downward formations (FlatPlain/Crystal default)
|
||||
// 10-15 → significant formations
|
||||
// 20+ → dramatic columns reaching toward the floor
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Slab|Ceiling")
|
||||
float CeilingRoughness = 6.0f;
|
||||
@@ -1205,22 +1215,22 @@ struct VOXELFORGE_API FSlabGenerationParams
|
||||
|
||||
// Smallest column radius (in voxels).
|
||||
// 1-2 → slender needle-like pillars
|
||||
// 3-4 → narrow columns (good default)
|
||||
// 8 → 2 m radius / 4 m diameter (human-scale minimum)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Slab|Columns")
|
||||
float ColumnMinRadius = 2.0f;
|
||||
float ColumnMinRadius = 8.0f;
|
||||
|
||||
// Largest column radius (in voxels).
|
||||
// 6-8 → natural stone pillars (good default)
|
||||
// 12-15 → massive trunks
|
||||
// 8-16 → 2-4 m optional pillars
|
||||
// 24+ → massive trunks
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Slab|Columns")
|
||||
float ColumnMaxRadius = 7.0f;
|
||||
float ColumnMaxRadius = 16.0f;
|
||||
|
||||
// Hash grid cell size (higher = columns further apart).
|
||||
// 30 → dense grid, many candidate slots (most empty due to ColumnDensity)
|
||||
// 60 → moderate spacing (good default)
|
||||
// 96 → 24 m spacing (human-scale default)
|
||||
// 100+ → only occasional columns
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Slab|Columns")
|
||||
float ColumnSpacing = 60.0f;
|
||||
float ColumnSpacing = 96.0f;
|
||||
|
||||
// ===== BOUNDARY & DENSITY =====
|
||||
|
||||
@@ -1265,14 +1275,14 @@ struct VOXELFORGE_API FMazeGenerationParams
|
||||
GENERATED_BODY()
|
||||
|
||||
// Lattice cell size in voxels. Smaller = tighter, more claustrophobic maze.
|
||||
// 25-35 → very tight warren · 40 → classic maze (default) · 60+ → roomy
|
||||
// 32 → compact maze cell · 64 → 16 m fight-space cell (default) · 96+ → roomy
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Maze", meta = (ClampMin = "8.0"))
|
||||
float CellSize = 40.0f;
|
||||
float CellSize = 64.0f;
|
||||
|
||||
// Corridor tube radius in voxels. Keep well below CellSize/2 to leave walls.
|
||||
// 3 → crawlspace · 4 → walkable (default) · 6 → wide halls
|
||||
// 4-5 → marginal passage · 8 → 2 m radius / 4 m bore (default) · 10+ → broad hall
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Maze", meta = (ClampMin = "1.0"))
|
||||
float CorridorRadius = 4.0f;
|
||||
float CorridorRadius = 8.0f;
|
||||
|
||||
// Probability that a horizontal edge between adjacent cells is open (0-1).
|
||||
// Lower = more dead ends and a more maze-like feel; higher = more open/connected.
|
||||
@@ -1321,12 +1331,12 @@ struct VOXELFORGE_API FSurfaceGenerationParams
|
||||
// Mean ground height as a fraction of the strate height (0 = bottom, 1 = top).
|
||||
// The terrain heightfield varies around this baseline by ElevationRange.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Shape", meta = (ClampMin = "0.05", ClampMax = "0.9"))
|
||||
float BaseGroundRelative = 0.30f;
|
||||
float BaseGroundRelative = 0.25f;
|
||||
|
||||
// Total vertical amplitude of the terrain heightfield in voxels (peak-to-trough-ish).
|
||||
// 20 → gentle plains · 60 → hills + valleys (default) · 120+ → tall mountains
|
||||
// 20 → gentle plains · 80 → 20 m hills + valleys (default) · 120+ → tall mountains
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Shape", meta = (ClampMin = "0.0"))
|
||||
float ElevationRange = 60.0f;
|
||||
float ElevationRange = 80.0f;
|
||||
|
||||
// Low-frequency "continent" noise — broad landmasses and basins.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Shape")
|
||||
@@ -1345,9 +1355,10 @@ struct VOXELFORGE_API FSurfaceGenerationParams
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Shape")
|
||||
float DetailFrequency = 0.04f;
|
||||
|
||||
// Small-scale surface roughness in voxels (rocks, bumps).
|
||||
// Small-scale surface roughness in voxels (rocks, bumps). The default 2 voxels is 0.5 m;
|
||||
// its proved 0.94 m noise reach remains small beside the 20 m terrain relief.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Shape", meta = (ClampMin = "0.0"))
|
||||
float SurfaceRoughness = 3.0f;
|
||||
float SurfaceRoughness = 2.0f;
|
||||
|
||||
// ----- Macro relief & landforms -----
|
||||
|
||||
@@ -1355,7 +1366,7 @@ struct VOXELFORGE_API FSurfaceGenerationParams
|
||||
// sampling continents/mountains. Bends straight coastlines and ridgelines into winding,
|
||||
// organic landforms. 0 = no warp (axis-aligned blobby noise, the old look).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Macro", meta = (ClampMin = "0.0"))
|
||||
float HeightWarpStrength = 35.0f;
|
||||
float HeightWarpStrength = 48.0f;
|
||||
|
||||
// Frequency of the domain-warp noise. Lower = broader, sweeping bends.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Macro")
|
||||
@@ -1382,9 +1393,10 @@ struct VOXELFORGE_API FSurfaceGenerationParams
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Macro", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float TerraceStrength = 0.0f;
|
||||
|
||||
// Height of each terrace step in voxels (when TerraceStrength > 0).
|
||||
// Height of each terrace step in voxels (when TerraceStrength > 0). The 8-voxel default is
|
||||
// a 2 m optional landform step; TerraceStrength remains off by default.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Macro", meta = (ClampMin = "1.0"))
|
||||
float TerraceHeight = 12.0f;
|
||||
float TerraceHeight = 8.0f;
|
||||
|
||||
// Terrace edge sharpness (0-1). 0 = soft rounded steps; 1 = crisp flat mesas with near-
|
||||
// vertical risers. Only matters when TerraceStrength > 0. (F20 — the plateau tops flatten
|
||||
@@ -1405,9 +1417,9 @@ struct VOXELFORGE_API FSurfaceGenerationParams
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Ops", meta = (ClampMin = "0.0"))
|
||||
float LayerLineDepth = 0.0f;
|
||||
|
||||
// Vertical spacing between layer lines in voxels (band period).
|
||||
// Vertical spacing between layer lines in voxels (band period). The 8-voxel default is 2 m.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Ops", meta = (ClampMin = "1.0"))
|
||||
float LayerLineSpacing = 4.0f;
|
||||
float LayerLineSpacing = 8.0f;
|
||||
|
||||
// Cliff STEEPENING (0-1 master): where the surface is already STEEP (slope > threshold),
|
||||
// push the height away from the local mean so gentle slopes become sheer walls / canyon
|
||||
@@ -1448,13 +1460,13 @@ struct VOXELFORGE_API FSurfaceGenerationParams
|
||||
// at which the terrain gradient is measured (so a spot over the void can "see" the cliff). Bigger =
|
||||
// deeper overhangs reaching further out (and a bit more cost). ~8-20.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Ops", meta = (ClampMin = "0.0"))
|
||||
float OverhangReach = 12.0f;
|
||||
float OverhangReach = 16.0f;
|
||||
|
||||
// Vertical HEIGHT (voxels) of the overhang zone above the local ground — where the shelf sits above
|
||||
// the ground/void directly under it, AND the band ClassifyTile treats as ambiguous (so it never holes
|
||||
// a trivially-skipped tile). Larger = taller/higher shelves but more woken air tiles near cliffs.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Ops", meta = (ClampMin = "0.0"))
|
||||
float OverhangHeight = 20.0f;
|
||||
float OverhangHeight = 24.0f;
|
||||
|
||||
// Horizontal frequency of the shelf-shape noise (breaks the reach up so shelves are ragged, not a
|
||||
// uniform lip). Lower = broader, smoother shelves.
|
||||
@@ -1480,9 +1492,9 @@ struct VOXELFORGE_API FSurfaceGenerationParams
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Water", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float WaterLevelRelative = 0.27f;
|
||||
|
||||
// Width (voxels) of the flattened beach/shore band around the water line.
|
||||
// Width (voxels) of the flattened beach/shore band around the water line. The default is 3 m.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Water", meta = (ClampMin = "0.0"))
|
||||
float BeachWidth = 8.0f;
|
||||
float BeachWidth = 12.0f;
|
||||
|
||||
// ----- Sky cap & boundary -----
|
||||
|
||||
@@ -1491,7 +1503,7 @@ struct VOXELFORGE_API FSurfaceGenerationParams
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Sky", meta = (ClampMin = "0.3", ClampMax = "1.0"))
|
||||
float CeilingRelative = 0.95f;
|
||||
|
||||
// Downward bumpiness of the sky-cap ceiling (voxels). 0 = flat ceiling.
|
||||
// Downward bumpiness of the sky-cap ceiling (voxels). 0 = flat ceiling; 6 = 1.5 m detail.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Sky", meta = (ClampMin = "0.0"))
|
||||
float CeilingRoughness = 6.0f;
|
||||
|
||||
@@ -1564,39 +1576,41 @@ struct VOXELFORGE_API FVerticalShaftParams
|
||||
GENERATED_BODY()
|
||||
|
||||
// Hash-grid cell size for shaft XY placement (voxels). Larger = shafts further apart.
|
||||
// 80 voxels = 20 m, leaving room for a 4-7 m diameter shaft and a fight lane.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Shafts", meta = (ClampMin = "10.0"))
|
||||
float ShaftSpacing = 55.0f;
|
||||
float ShaftSpacing = 80.0f;
|
||||
|
||||
// Probability a grid cell contains a shaft (0-1).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Shafts", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float ShaftDensity = 0.6f;
|
||||
|
||||
// Shaft radius range (voxels).
|
||||
// Shaft radius range (voxels): 4-7 m diameter at the human-scale default.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Shafts", meta = (ClampMin = "1.0"))
|
||||
float ShaftMinRadius = 5.0f;
|
||||
float ShaftMinRadius = 8.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Shafts", meta = (ClampMin = "1.0"))
|
||||
float ShaftMaxRadius = 11.0f;
|
||||
float ShaftMaxRadius = 14.0f;
|
||||
|
||||
// Chance an adjacent pair of shafts is joined by a horizontal connector tunnel (0-1).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Shafts", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float CrossConnectChance = 0.35f;
|
||||
|
||||
// Radius of horizontal connector tunnels (voxels).
|
||||
// Radius of horizontal connector tunnels (voxels): 1.5 m radius / 3 m bore by default.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Shafts", meta = (ClampMin = "1.0"))
|
||||
float ConnectorRadius = 4.0f;
|
||||
float ConnectorRadius = 6.0f;
|
||||
|
||||
// Vertical spacing of ledges inside shafts (voxels). 0 = no ledges (sheer drops).
|
||||
// Vertical spacing of ledges inside shafts (voxels). 32 = 8 m landing interval;
|
||||
// 0 = no ledges (sheer drops).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Shafts", meta = (ClampMin = "0.0"))
|
||||
float LedgeSpacing = 24.0f;
|
||||
float LedgeSpacing = 32.0f;
|
||||
|
||||
// How far ledges intrude into the shaft (voxels of solid added).
|
||||
// How far ledges intrude into the shaft (voxels of solid added). 4 = 1 m by default.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Shafts", meta = (ClampMin = "0.0"))
|
||||
float LedgeDepth = 3.0f;
|
||||
float LedgeDepth = 4.0f;
|
||||
|
||||
// Small-scale wall roughness (voxels).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Shafts", meta = (ClampMin = "0.0"))
|
||||
float SurfaceRoughness = 3.0f;
|
||||
float SurfaceRoughness = 2.0f;
|
||||
|
||||
// Solid shell thickness at strate top/bottom (voxels).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Shafts", meta = (ClampMin = "0.0"))
|
||||
@@ -1626,25 +1640,26 @@ struct VOXELFORGE_API FFloatingIslandParams
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// Hash-grid cell size for island placement (voxels). Larger = islands further apart.
|
||||
// Hash-grid cell size for island placement (voxels). 112 = 28 m, while the largest default
|
||||
// island is 24 m across, leaving a 2 m centre-to-edge gap.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "20.0"))
|
||||
float IslandSpacing = 95.0f;
|
||||
float IslandSpacing = 112.0f;
|
||||
|
||||
// Probability a grid cell contains an island (0-1).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float IslandDensity = 0.5f;
|
||||
|
||||
// Island horizontal radius range (voxels).
|
||||
// Island horizontal radius range (voxels): 12-24 m landmass diameters by default.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "2.0"))
|
||||
float IslandMinRadius = 18.0f;
|
||||
float IslandMinRadius = 24.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "2.0"))
|
||||
float IslandMaxRadius = 42.0f;
|
||||
float IslandMaxRadius = 48.0f;
|
||||
|
||||
// Island vertical thickness as a fraction of its horizontal radius.
|
||||
// 0.4 → thin plates · 0.7 → chunky (default) · 1.0 → near-spherical
|
||||
// 0.4 → thin plates · 0.6 → substantial landmass (default) · 1.0 → near-spherical
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.1", ClampMax = "1.5"))
|
||||
float ThicknessRatio = 0.7f;
|
||||
float ThicknessRatio = 0.6f;
|
||||
|
||||
// How much islands scatter vertically within the void (0 = all mid-height, 1 = full spread).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
@@ -1656,11 +1671,11 @@ struct VOXELFORGE_API FFloatingIslandParams
|
||||
|
||||
// Surface roughness on island shells (voxels) — craggy undersides, bumpy tops.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0"))
|
||||
float SurfaceRoughness = 4.0f;
|
||||
float SurfaceRoughness = 3.0f;
|
||||
|
||||
// SmoothMin blend radius for merging overlapping islands (voxels).
|
||||
// SmoothMin blend radius for merging overlapping islands (voxels): 1.5 m by default.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0"))
|
||||
float SDFBlendRadius = 5.0f;
|
||||
float SDFBlendRadius = 6.0f;
|
||||
|
||||
// Solid shell thickness at strate top/bottom (voxels).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "0.0"))
|
||||
@@ -1696,27 +1711,30 @@ struct VOXELFORGE_API FStrateDisturbanceParams
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Disturbance|Chasms", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float ChasmDensity = 0.0f;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Disturbance|Chasms", meta = (ClampMin = "20.0"))
|
||||
float ChasmSpacing = 170.0f;
|
||||
// 192 voxels = 48 m between optional chasm sites.
|
||||
float ChasmSpacing = 192.0f;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Disturbance|Chasms", meta = (ClampMin = "1.0"))
|
||||
float ChasmRadius = 14.0f;
|
||||
float ChasmRadius = 24.0f;
|
||||
|
||||
// --- BRIDGES: horizontal solid spans across open space ---
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Disturbance|Bridges", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float BridgeDensity = 0.0f;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Disturbance|Bridges", meta = (ClampMin = "20.0"))
|
||||
float BridgeSpacing = 120.0f;
|
||||
// 128 voxels = 32 m between optional bridge sites.
|
||||
float BridgeSpacing = 128.0f;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Disturbance|Bridges", meta = (ClampMin = "1.0"))
|
||||
float BridgeRadius = 5.0f;
|
||||
float BridgeRadius = 8.0f;
|
||||
|
||||
// --- RIDGES: thin solid blades rising from the floor ---
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Disturbance|Ridges", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float RidgeDensity = 0.0f;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Disturbance|Ridges", meta = (ClampMin = "20.0"))
|
||||
float RidgeSpacing = 110.0f;
|
||||
// 128 voxels = 32 m between optional ridge sites.
|
||||
float RidgeSpacing = 128.0f;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Disturbance|Ridges", meta = (ClampMin = "0.0"))
|
||||
float RidgeHeight = 30.0f;
|
||||
float RidgeHeight = 32.0f;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Disturbance|Ridges", meta = (ClampMin = "1.0"))
|
||||
float RidgeThickness = 6.0f;
|
||||
float RidgeThickness = 8.0f;
|
||||
|
||||
// Rock solidity used to scale carve/fill strength. Match the strate's BaseDensity.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Disturbance")
|
||||
@@ -1768,33 +1786,36 @@ struct VOXELFORGE_API FStratePassageConfig
|
||||
|
||||
// ----- WIDTH (tapers along the length) -----
|
||||
// Radius at the two mouths (entry/exit) and at the middle. Equal = uniform tube;
|
||||
// The defaults are 2 m radius / 4 m bore at mouths and 1.5 m radius / 3 m bore in the middle.
|
||||
// Mouth > Mid = chambers at the ends with a squeeze between; Mid > Mouth = a bulge.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Width", meta = (ClampMin = "1.0"))
|
||||
float MouthRadius = 6.0f;
|
||||
float MouthRadius = 8.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Width", meta = (ClampMin = "1.0"))
|
||||
float MidRadius = 4.0f;
|
||||
float MidRadius = 6.0f;
|
||||
|
||||
// ----- LENGTH -----
|
||||
// How far the tunnel reaches INTO each strate (voxels). Auto-capped to the interior.
|
||||
// How far the tunnel reaches INTO each strate (voxels). Auto-capped to the interior;
|
||||
// the defaults span 8-24 m.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Length", meta = (ClampMin = "8.0"))
|
||||
float ReachMin = 40.0f;
|
||||
float ReachMin = 32.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Length", meta = (ClampMin = "8.0"))
|
||||
float ReachMax = 90.0f;
|
||||
float ReachMax = 96.0f;
|
||||
|
||||
// ----- PLACEMENT -----
|
||||
// Horizontal distance range from the (0,0) spine (voxels) where tunnels may appear.
|
||||
// Horizontal distance range from the (0,0) spine (voxels) where tunnels may appear;
|
||||
// the defaults are 16-48 m.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Placement", meta = (ClampMin = "0.0"))
|
||||
float DistanceMin = 60.0f;
|
||||
float DistanceMin = 64.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Placement", meta = (ClampMin = "0.0"))
|
||||
float DistanceMax = 200.0f;
|
||||
float DistanceMax = 192.0f;
|
||||
|
||||
// ----- SHAPE detail -----
|
||||
// Worm: max sideways excursion from the axis (voxels). 0 = straight even in Worm style.
|
||||
// Worm: max sideways excursion from the axis (voxels). 24 = 6 m; 0 = straight even in Worm style.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Shape", meta = (ClampMin = "0.0"))
|
||||
float Wander = 15.0f;
|
||||
float Wander = 24.0f;
|
||||
|
||||
// Path resolution (control points). Higher = smoother curves; lower = more faceted /
|
||||
// cheaper. The worm makes several bends, so keep this reasonably high for smoothness.
|
||||
@@ -1805,19 +1826,21 @@ struct VOXELFORGE_API FStratePassageConfig
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Shape", meta = (ClampMin = "0.0"))
|
||||
float VerticalWobble = 0.0f;
|
||||
|
||||
// Spiral style: helix radius (voxels) and number of full turns over the descent.
|
||||
// Spiral style: helix radius (voxels) and number of full turns over the descent. The default
|
||||
// radius is 6 m.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Spiral", meta = (ClampMin = "1.0"))
|
||||
float SpiralRadius = 16.0f;
|
||||
float SpiralRadius = 24.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Spiral", meta = (ClampMin = "0.25"))
|
||||
float SpiralTurns = 2.0f;
|
||||
|
||||
// Cascading style: number of ledge+drop steps, and how far each ledge runs (voxels).
|
||||
// Cascading style: number of ledge+drop steps, and how far each ledge runs (voxels). The
|
||||
// default ledge run is 2 m.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Cascade", meta = (ClampMin = "1", ClampMax = "16"))
|
||||
int32 CascadeSteps = 4;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Cascade", meta = (ClampMin = "0.0"))
|
||||
float CascadeLedge = 14.0f;
|
||||
float CascadeLedge = 8.0f;
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
|
||||
Reference in new issue
Block a user