From 59f49ac8a2a61c4ce13e85f205d4f18247d77a6b Mon Sep 17 00:00:00 2001 From: beezm Date: Sat, 8 Aug 2026 21:23:28 -0400 Subject: [PATCH] fix: detail must yield to the crater carve (terrain-water task 10) Batch 10_detail_edge measured 532 new below-sea pixels on seed 1158286446 and 44 on 72563200 -- exported terrain under the sea scalar that the water grid (classify-driven, and correctly identical) calls dry. Located: every one of them strictly inside the crater's physical carve radius, 201-323 px out of 640. Mechanism, confirmed against the data: detail moves a column's PRE-carve height, and the carve is Lerp(curvedH, target, t). At t ~ 0.45-0.62 a pre-carve drop of up to 8.4 m (relief plus the band-shift the warp implies) still passes 0.02-3.53 m through, which is enough to push a column sitting 0-3.45 m above the sea inside the bowl under it. The crater carve is supposed to be the FINAL authority on its own terrain, so detail now yields to it: CraterDetailWeight is 0 inside 0.80 x CraterRadius (exactly the carve's own radius) and feathers to full by 1.05 x, scaling both the edge shift and the relief skin. Inside the carve, B is bit-identical to A by construction, so the count is zero rather than small. The reverted incision pass carried the same exclusion for the same reason -- the principle outlived the pass that motivated it. Re-running the 6 B_detail generations; A_off and the continuity run are untouched by a detail-only change. Co-Authored-By: Claude Opus 5 (1M context) --- Tools/Scripts/MapGenerator.cs | 15 ++++++++++----- Tools/Scripts/TerrainDetailPass.cs | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) 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