diff --git a/Tools/Scripts/MapGenerator.cs b/Tools/Scripts/MapGenerator.cs index 9ae2d6e..5f1d3ba 100644 --- a/Tools/Scripts/MapGenerator.cs +++ b/Tools/Scripts/MapGenerator.cs @@ -519,6 +519,7 @@ public partial class MapGenerator : TextureRect float raw = _heightMap[x, y]; float classifyH = raw; float curvedH; + float distToCrater = new Vector2(x, y).DistanceTo(_impactCenter); if (_curveOn) { // v4: per-column shelf modulation — anchors and strength from the @@ -528,14 +529,20 @@ public partial class MapGenerator : TextureRect float plateauLo = HeightCurve.PLATEAU_BASE + _plateauNoise.GetNoise2D(x, y) * HeightCurve.PLATEAU_AMP; float shelfSpan = HeightCurve.ShelfSpan((_strengthNoise.GetNoise2D(x, y) + 1f) * 0.5f); - float edgeShift = _detailOn ? _edgeNoise.GetNoise2D(x, y) * _edgeAmpRaw : 0f; + // Detail yields to the crater: zero inside the carve, feathered just + // outside it, so the carve stays the final authority on its terrain. + float wCrater = _detailOn + ? TerrainDetailPass.CraterDetailWeight(distToCrater, _impactRadius) + : 0f; + + float edgeShift = _detailOn ? _edgeNoise.GetNoise2D(x, y) * _edgeAmpRaw * wCrater : 0f; curvedH = HeightCurve.Apply(raw, _hMaxSeed, benchLo, shelfSpan, plateauLo, shelfSpan, _curveKnots, edgeShift); - if (_detailOn) + if (_detailOn && wCrater > 0f) { float wShelf = TerrainDetailPass.ShelfWeight(raw, _curveKnots, edgeShift); if (wShelf > 0f) - curvedH += _reliefNoise.GetNoise2D(x, y) * reliefAmpRaw * wShelf; + curvedH += _reliefNoise.GetNoise2D(x, y) * reliefAmpRaw * wShelf * wCrater; } } else @@ -546,8 +553,6 @@ public partial class MapGenerator : TextureRect // --- 5. CARVE THE CRATER (The Flooded Bay & Landbridge Fix!) --- // Both maps are carved from LOCALS and written once, so the curve-off // aliasing (classify and height are the same array) cannot double-carve. - float distToCrater = new Vector2(x, y).DistanceTo(_impactCenter); - // We only carve the physical hole at 80% of the radius to guarantee a landbridge! if (distToCrater < physicalCraterRadius) { diff --git a/Tools/Scripts/TerrainDetailPass.cs b/Tools/Scripts/TerrainDetailPass.cs index 3687db3..44b1985 100644 --- a/Tools/Scripts/TerrainDetailPass.cs +++ b/Tools/Scripts/TerrainDetailPass.cs @@ -52,6 +52,30 @@ public static class TerrainDetailPass // ordering follows from it. public const float EDGE_SAFETY_FRACTION = 2f / 3f; + // The crater carve is the final authority on its own terrain. Detail is masked + // out inside the physical carve radius (0.80 × CraterRadius — exactly where the + // carve applies) and feathers to full by 1.05 ×. Without this, detail moves a + // column's PRE-carve height, the carve's Lerp passes a fraction of that through, + // and columns sitting a metre or two above the sea inside the bowl get pushed + // under it — 532 px on seed 1158286446 in the first batch, terrain below the sea + // scalar that the (classify-driven, and correctly unchanged) water grid calls dry. + public const float CRATER_DETAIL_EXCL_FACTOR = 0.80f; + public const float CRATER_DETAIL_FEATHER_FACTOR = 1.05f; + + /// + /// Detail weight from distance to the impact centre: 0 inside the carve, 1 well + /// outside it, linear between. is the configured + /// CraterRadius (the carve itself uses 0.80 × of it). + /// + public static float CraterDetailWeight(float distToCrater, float craterRadius) + { + float excl = craterRadius * CRATER_DETAIL_EXCL_FACTOR; + if (distToCrater <= excl) return 0f; + float feather = craterRadius * CRATER_DETAIL_FEATHER_FACTOR; + if (distToCrater >= feather) return 1f; + return (distToCrater - excl) / (feather - excl); + } + /// /// The largest per-column knot shift this preset allows: bounded by the band /// squeeze above, which also keeps the knot set strictly ordered