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