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