using Godot;
using System;
///
/// The terrain DETAIL passes (terrain-water task 10) — pure numeric array machinery
/// (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 — drainage incision: D8 steepest-descent flow routing + accumulation
/// over the curved terrain; depth = K · accum^p · localSlope (capped), masked to
/// the risers (feathered ~30 % onto shelves, zero on the toe and above the
/// plateau top, zero near the crater), clamped so carved terrain never drops
/// below sea + 1 m. The channels double as the future river routes (Phase C).
///
/// Ordering (enforced by the caller): curve → micro-relief → incision → crater
/// carve. The classify map never sees any of it.
///
public static class TerrainDetailPass
{
public const ushort VERSION = 1;
// 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 — incision. K/p tuned against the depth targets (gullies 8–15 m,
// trunks ~25 m, cap 30 m); the tuning run's achieved distribution is in the
// task-10 report.
public const float INC_K = 0.78f;
public const float INC_P = 0.45f; // concave: many fingers, few deep trunks
public const float INC_CAP_M = 30f; // IncisionMax
public const float SEA_CLAMP = 0.15f + 1f / 251f; // carved height ≥ sea + 1 m
public const float SHELF_INC_WEIGHT = 0.3f; // shelves get washes, not gorges
public const float CRATER_EXCL_FACTOR = 1.2f; // zero incision inside this × CraterRadius
public const float CRATER_FEATHER_FACTOR = 1.4f; // ...feathering to full by this × CraterRadius
///
/// 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.
///
public static float ShelfWeight(float raw, CurveKnots k)
{
return Mathf.Max(BandBump(raw, k.K3, k.K4), BandBump(raw, k.K5, 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);
}
///
/// Incision mask from the RAW input height: 0 below the red-ceiling input (K2)
/// and above the plateau top (K6); 1 on the riser bands; SHELF_INC_WEIGHT on the
/// shelf bands; smooth feathers (15 % of the local band width) at every boundary.
///
public static float IncisionWeight(float raw, CurveKnots k)
{
if (raw <= k.K2 || raw >= k.K6) return 0f;
if (raw < k.K3) // foothill riser: feather in from K2, feather toward shelf weight at K3
return EdgeBlend(raw, k.K2, k.K3, 0f, 1f, SHELF_INC_WEIGHT);
if (raw < k.K4) // bench
return SHELF_INC_WEIGHT;
if (raw < k.K5) // mid riser
return EdgeBlend(raw, k.K4, k.K5, SHELF_INC_WEIGHT, 1f, SHELF_INC_WEIGHT);
// plateau band: shelf weight, feathering to zero at K6
float w = (k.K6 - raw) / ((k.K6 - k.K5) * 0.15f);
return Mathf.Min(SHELF_INC_WEIGHT, Mathf.Clamp(w, 0f, 1f) * SHELF_INC_WEIGHT);
}
private static float EdgeBlend(float h, float lo, float hi, float wIn, float wMid, float wOut)
{
float f = (hi - lo) * 0.15f;
if (h < lo + f) return Mathf.Lerp(wIn, wMid, (h - lo) / f);
if (h > hi - f) return Mathf.Lerp(wMid, wOut, (h - (hi - f)) / f);
return wMid;
}
///
/// D8 flow accumulation over a height field (row-major idx = x·n + y).
/// Steepest-descent routing (drop / distance, diagonals ÷√2), deterministic
/// tie-break (fixed neighbour order, first winner). Cells with no lower
/// neighbour are pits/outlets (no outflow). accum = upslope contributing cells
/// including self; steepestDrop = drop per pixel toward the chosen neighbour.
///
public static int[] FlowAccumulation(float[] h, int n, out float[] steepestDrop)
{
int total = n * n;
int[] downstream = new int[total];
steepestDrop = new float[total];
int[] dx = { 1, -1, 0, 0, 1, 1, -1, -1 };
int[] dy = { 0, 0, 1, -1, 1, -1, 1, -1 };
float[] invDist = { 1f, 1f, 1f, 1f, 0.7071068f, 0.7071068f, 0.7071068f, 0.7071068f };
for (int x = 0; x < n; x++)
{
for (int y = 0; y < n; y++)
{
int i = x * n + y;
float hc = h[i];
float best = 0f;
int bestIdx = -1;
for (int d = 0; d < 8; d++)
{
int nx = x + dx[d], ny = y + dy[d];
if (nx < 0 || nx >= n || ny < 0 || ny >= n) continue;
int ni = nx * n + ny;
float grade = (hc - h[ni]) * invDist[d];
if (grade > best)
{
best = grade;
bestIdx = ni;
}
}
downstream[i] = bestIdx;
steepestDrop[i] = best;
}
}
// Height-descending order: each cell pushes its accumulated count downstream.
float[] keys = (float[])h.Clone();
int[] order = new int[total];
for (int i = 0; i < total; i++) order[i] = i;
Array.Sort(keys, order); // ascending
int[] accum = new int[total];
for (int i = 0; i < total; i++) accum[i] = 1;
for (int i = total - 1; i >= 0; i--)
{
int c = order[i];
int d = downstream[c];
if (d >= 0) accum[d] += accum[c];
}
return accum;
}
}