using Godot;
///
/// The terrain DETAIL passes (terrain-water task 10) — pure numeric functions
/// (D-035; a named future C++ candidate, kept standalone):
///
/// PASS A — shelf micro-relief: a medium-frequency noise skin (±ShelfReliefAmp,
/// default 3 m) weighted by shelf-ness, so the compressed shelves get their
/// rolling texture back while risers and peaks stay untouched.
///
/// PASS B — shelf-edge variation: a per-column shift of the shelf/riser KNOT
/// BLOCK (K3/K4/K5) by a low-frequency noise field, so the boundary where a
/// shelf meets its riser wanders in and out instead of tracing a clean height
/// contour — organic notches, coves and peninsulas at the shelf edge.
///
/// Both are output-height only; the classify map never sees either of them.
///
/// NOTE — what this file deliberately does NOT contain: the task-10 draft's D8
/// flow routing / accumulation / drainage incision. It shipped, produced the
/// canonical grid artifact (thousands of straight, disconnected, pooling
/// scratches along the D8 neighbour directions) and was reverted whole. Rivers
/// and erosion are Phase C work — a hydraulic-erosion pass over FINAL terrain,
/// not a per-cell steepest-descent carve on a grid.
///
public static class TerrainDetailPass
{
// The TDTL body version is owned by the format (Core); the pass just stamps it.
public const ushort VERSION = IslaApocalypse.Core.BlueprintFormat.TDTL_VERSION;
// Pass A — micro-relief.
public const float RELIEF_AMP_DEFAULT_M = 3f; // config dial: ShelfReliefAmp (metres)
public const float RELIEF_FREQ_ISLANDS = 40f; // ~40 undulations per island width (~200 m features)
public const int RELIEF_SEED_OFFSET = 7409;
// Pass B — shelf-edge variation. The amplitude is stated in metres of INPUT
// height (raw × 251): it is how far, in raw-height terms, a shelf boundary
// contour is displaced — not an output elevation change. Lateral wander on
// the map is that displacement divided by the local raw gradient.
public const float EDGE_AMP_DEFAULT_M = 5f; // config dial: ShelfEdgeVariation (metres of input height)
public const float EDGE_FREQ_ISLANDS = 12f; // ~12 undulations per island width — a handful of notches
public const int EDGE_SEED_OFFSET = 7507; // per shelf perimeter, not hundreds of teeth
public const float EDGE_SAFETY_FRACTION = 0.5f; // shift ≤ half the smallest margin to a fixed knot
///
/// The largest per-column knot shift this preset can take while keeping the
/// knot set strictly ordered (K2 < K3+d, K5+d < K6) with margin to spare.
/// K1/K2/K6 never move, so the toe, the orange/red bands and the summit spike
/// are bit-identical whatever the warp does — which is what makes the
/// red-ceiling and peak-height guarantees exact rather than statistical.
///
public static float MaxEdgeShift(CurveKnots k)
{
return EDGE_SAFETY_FRACTION * Mathf.Min(k.K3 - k.K2, k.K6 - k.K5);
}
///
/// Shelf-ness weight from the RAW input height: 1 mid-shelf, feathering to 0
/// through the risers (feather extends 30 % of the band half-width past each
/// shelf edge). Covers both shelves. is the same
/// per-column warp the curve is evaluated with, so the micro-relief skin
/// follows the shelf wherever pass B has moved its boundary.
///
public static float ShelfWeight(float raw, CurveKnots k, float edgeShift)
{
return Mathf.Max(BandBump(raw, k.K3 + edgeShift, k.K4 + edgeShift),
BandBump(raw, k.K5 + edgeShift, k.K6));
}
private static float BandBump(float h, float lo, float hi)
{
float half = (hi - lo) * 0.5f;
float t = Mathf.Abs(h - (lo + half)) / half; // 0 centre, 1 at band edge
// full inside 60 % of the band, linear feather to zero at 130 %
return Mathf.Clamp(1f - (t - 0.6f) / 0.7f, 0f, 1f);
}
}