using Godot;
///
/// The island-falloff shaping functions (terrain-water task 11) — pure numeric
/// functions of their inputs (D-035; a named future C++ candidate, kept standalone).
///
/// SMOOTH CREST — the mountain spine's ridge axis is the line x = centre, and
/// `1 - |x - cx|` peaks there with a slope discontinuity. Measured on the shipped
/// terrain, that crease is the single largest slope step anywhere on the map once
/// the by-design Trench walls are excluded (top 4 of 5999 columns). SmoothAbs
/// rounds the crest without moving its height.
///
/// COAST SHELF — the height curve is identity at and below sea level, so it never
/// touched the SUBMARINE slope. Measured: land rises from the shoreline at
/// 0.038 m/px while the seabed drops at 0.258 m/px — the shoreline is a shelf on
/// the land side and a ramp on the sea side. CoastShelf compresses shallow depth
/// so the shallows extend much further out, leaving deep water and the Trench
/// essentially untouched.
///
/// OFFSHORE BLOB — sparse discoverable islets, seeded from an ocean noise layer.
///
/// Every one of these is monotone in the sign of (sea - height): none of them can
/// turn water into land or land into water on its own. The coast shelf therefore
/// leaves the biome and water classification bit-identical, which is why it is
/// separable from elongation in the batch.
///
public static class IslandFalloff
{
// ---- centre-line crest ------------------------------------------------
// Rounding radius in normalized spine-width units (1.0 = MapSize/2 * IslandAxisX
// ~ 4710 px at 8K with the default axis). 0.03 ~ 141 px: it cuts the crest's
// 1-px kink by 99.3% (-7.64e-04 -> -5.41e-06 raw) while filling at most 3.9 m,
// decaying under 0.5 m by ~1100 px from the axis.
public const float CREST_EPSILON = 0.03f;
///
/// A C¹ stand-in for |d|: exactly 0 with zero slope at d = 0, and converging to
/// |d| within e²/(2|d|) away from it. Replaces the V-shaped crest of the spine
/// with a rounded one WITHOUT lowering it — SmoothAbs(0) is 0, so the peak keeps
/// its full height.
///
public static float SmoothAbs(float d, float epsilon)
{
float a = Mathf.Abs(d);
return a * a / Mathf.Sqrt(a * a + epsilon * epsilon);
}
// ---- coast shelf ------------------------------------------------------
// depth' = depth * (1 - STRENGTH * exp(-depth / SCALE_M)).
// At the shoreline the seabed starts at (1 - STRENGTH) of its former gradient and
// recovers smoothly, so the shallows widen and the deep ocean keeps its shape.
// C^inf everywhere, and strictly positive for positive depth — it cannot move the
// waterline by even one pixel.
public const float SHELF_STRENGTH = 0.775f; // 0 = off, ->1 = a flat lagoon
public const float SHELF_SCALE_M = 100f; // metres of depth over which it relaxes
/// Remaps a positive depth in metres. Returns the new depth in metres.
public static float CoastShelf(float depthMetres)
{
if (depthMetres <= 0f) return depthMetres;
return depthMetres * (1f - SHELF_STRENGTH * Mathf.Exp(-depthMetres / SHELF_SCALE_M));
}
// ---- offshore islands -------------------------------------------------
// Islets are placed by lerping the seabed TOWARD a target height, not by adding to
// it, so they can surface at any ambient depth instead of only where the seafloor
// happens to be shallow.
public const float OFFSHORE_FREQ_ISLANDS = 14f; // ~585 px blobs at 8K — few and sizeable,
// not a scatter of 50 px debris
public const int OFFSHORE_SEED_OFFSET = 7607;
public const float OFFSHORE_ISLAND_H_M = 34f; // target crest, metres above sea (pre-curve)
public const float OFFSHORE_CORE = 0.45f; // fraction of a blob's excess that saturates
// The moat that keeps islets off the mainland. The raise is EXACTLY zero wherever
// the ambient water is shallower than this, so the ring of water between the
// mainland shore and any islet cannot be bridged: a continuous path from shore to
// islet must cross this depth contour, and every pixel on it is untouched water.
public const float OFFSHORE_MIN_DEPTH_M = 14f;
public const float OFFSHORE_DEPTH_FEATHER_M = 10f;
// ...the margin that keeps them out of the Trench ramp (which starts at 0.90)...
public const float OFFSHORE_TRENCH_INNER = 0.78f;
public const float OFFSHORE_TRENCH_OUTER = 0.86f;
// ...and the test for "actually offshore". Depth alone is not enough: a deep LAKE
// or the carved crater bay is also below sea level, and islets have no business in
// either. The pre-Trench falloff is the honest discriminator — the mainland coast
// sits near f = 0.66 (where f^2.5 ~ rawBase - sea), and inland water is far below
// that whatever the axis ratios are, because elongation moves WHERE a given f
// occurs, not the f at which land ends.
public const float OFFSHORE_MIN_FALLOFF = 0.72f;
public const float OFFSHORE_FALLOFF_FEATHER = 0.06f;
///
/// Blob weight in [0,1] for one ocean column. comes
/// from CalibrateThreshold, NOT from the density directly — Simplex output is
/// concentrated well inside [-1,1] (in practice it rarely passes ±0.87), so
/// treating density as a fraction of the theoretical range produces a threshold
/// almost nothing clears. That bug shipped in the first task-11 build and raised
/// 171 pixels on the whole map, none of them above sea.
///
public static float OffshoreBlob(float noise01, float threshold)
{
if (noise01 <= threshold) return 0f;
float core = Mathf.Max((1f - threshold) * OFFSHORE_CORE, 1e-4f);
float k = Mathf.Clamp((noise01 - threshold) / core, 0f, 1f);
return k * k * (3f - 2f * k);
}
///
/// The noise value that of
/// exceed. Sorts a copy, so the caller's array is left alone.
///
public static float CalibrateThreshold(float[] samples, float density)
{
if (samples.Length == 0 || density <= 0f) return 1f;
float[] s = (float[])samples.Clone();
System.Array.Sort(s);
int idx = (int)((1f - Mathf.Clamp(density, 0f, 1f)) * (s.Length - 1));
return s[Mathf.Clamp(idx, 0, s.Length - 1)];
}
///
/// How much of the blob is allowed here: zero in shallow water near the mainland
/// (the moat), zero anywhere that is not genuinely outside the island body, zero
/// in and near the Trench ramp, full in the open ocean between.
///
public static float OffshoreZoneWeight(float ambientDepthMetres, float preTrenchFalloff,
float distX01, float distY01)
{
if (ambientDepthMetres < OFFSHORE_MIN_DEPTH_M) return 0f;
if (preTrenchFalloff < OFFSHORE_MIN_FALLOFF) return 0f;
float w = Mathf.Clamp((ambientDepthMetres - OFFSHORE_MIN_DEPTH_M) / OFFSHORE_DEPTH_FEATHER_M, 0f, 1f);
w *= Mathf.Clamp((preTrenchFalloff - OFFSHORE_MIN_FALLOFF) / OFFSHORE_FALLOFF_FEATHER, 0f, 1f);
float d = Mathf.Max(distX01, distY01);
if (d >= OFFSHORE_TRENCH_OUTER) return 0f;
if (d > OFFSHORE_TRENCH_INNER)
w *= 1f - (d - OFFSHORE_TRENCH_INNER) / (OFFSHORE_TRENCH_OUTER - OFFSHORE_TRENCH_INNER);
return w;
}
}