using Godot; /// /// The height-redistribution curve, v3 — the TERRACED ASCENT (terrain-water task 07). /// Pure, static, monotonic piecewise map over raw blueprint heights (D-035: numbers /// in, numbers out; the per-seed spike maximum is an explicit PARAMETER). /// /// v3 (developer's task-06 ground-test verdict — floor and 420 m ceiling frozen, the /// ascent between them rebuilt): v2 spent ~87 % of the vertical budget in the last /// ~4 % of input, reading as flat-then-wall. v3 climbs in TERRACES — two benches, /// three risers — and relocates the white mountain-town plateau from 50 m to 220 m: /// /// h ≤ sea (0.15) identity — water and the below-sea world untouched /// sea → K1 toe, ease-out — the lowlands (orange coverage 59 %) /// K1 → K2 linear rise into the red band (storm anchors frozen) /// K2 → K3 FOOTHILL RISER — smoothstep climb to the 100 m bench /// K3 → K4 BENCH 1 — near-flat walkable foothill shelf (~2 m rise) /// K4 → K5 MID RISER — the big middle climb to the white plateau /// K5 → K6 WHITE PLATEAU — near-flat shelf at 220 m (mountain /// towns / snow band, where it was always intended) /// K6 → spikeMax SUMMIT SPIKE — per-seed normalized (hMaxSeed), stiff /// ease-in kept from v2: spires off the plateau to 420 m /// h > spikeMax linear tail (strict monotonicity, no clamp) /// /// Input knots recalibrated 2026-08-08 from the SAME pooled batch-04 flat-sea land /// CDF as v1/v2 (340,618,126 samples): P59/P72/P82/P87/P95/P98, giving pooled land /// fractions orange 59 / red 13 / foothill-riser 10 / bench 5 / mid-riser 8 / /// plateau 3 / spike 2 (exact by construction). Per-seed proportions vary — the /// wrinkle variety. /// /// Strictly monotonic (every segment's normalized slope bounded below by a positive /// constant); asserted numerically per generation against the EFFECTIVE per-seed /// curve once hMaxSeed is known. /// public static class HeightCurve { public const ushort VERSION = 3; // Input knots — v3 calibration (see class header). K1..K4 are serialized in // TCRV's four knot slots; K5/K6 are version constants documented in // BLUEPRINT_FORMAT.md (the TCRV version byte selects the anchor set). public const float K1 = 0.509179f; // P59 — orange coverage boundary public const float K2 = 0.604081f; // P72 — red top public const float K3 = 0.698485f; // P82 — foothill riser top public const float K4 = 0.767213f; // P87 — bench 1 top public const float K5 = 0.930304f; // P95 — mid riser top public const float K6 = 1.050720f; // P98 — plateau top / spike foot // Output anchors — storm ladder (frozen) + the terraces. public const float SEA = 0.15f; public const float ORANGE_CEIL = 0.206f; // 1000-yr storm ceiling (frozen) public const float RED_CEIL = 0.27f; // biblical ceiling (frozen) public const float FOOTHILL_BENCH = SEA + 100f / 251f; // ≈ 0.54841 (100 m above sea) public const float BENCH_STEP = 0.008f; // ~2 m rise across each bench public const float FOOTHILL_TOP = FOOTHILL_BENCH + BENCH_STEP; public const float PLATEAU = SEA + 220f / 251f; // ≈ 1.02649 (220 m — the white plateau) public const float PLATEAU_TOP = PLATEAU + BENCH_STEP; public const float PEAK_CAP = SEA + 420f / 251f; // ≈ 1.82869 (frozen from v2) public const float TAIL_SLOPE = 0.25f; // Degenerate/near-flat guard (unchanged from v2): spike domain floored at // K6 + SPIKE_MIN_SPAN so it stays positive and monotonic on any input. public const float SPIKE_MIN_SPAN = 0.01f; public static float EffectiveSpikeMax(float hMaxSeed) { return Mathf.Max(hMaxSeed, K6 + SPIKE_MIN_SPAN); } /// Raw pre-curve height. /// The seed's raw pre-curve maximum — per-seed spike normalizer. public static float Apply(float h, float hMaxSeed) { if (h <= SEA) return h; float u, s; if (h < K1) { u = (h - SEA) / (K1 - SEA); s = 0.3f * u + 0.7f * (u * (2f - u)); // ease-out toe, slope ≥ 0.3 return SEA + s * (ORANGE_CEIL - SEA); } if (h < K2) { u = (h - K1) / (K2 - K1); return ORANGE_CEIL + u * (RED_CEIL - ORANGE_CEIL); // linear rise } if (h < K3) { u = (h - K2) / (K3 - K2); s = 0.2f * u + 0.8f * (u * u * (3f - 2f * u)); // foothill riser, slope ≥ 0.2 return RED_CEIL + s * (FOOTHILL_BENCH - RED_CEIL); } if (h < K4) { u = (h - K3) / (K4 - K3); return FOOTHILL_BENCH + u * BENCH_STEP; // bench 1, near-flat } if (h < K5) { u = (h - K4) / (K5 - K4); s = 0.2f * u + 0.8f * (u * u * (3f - 2f * u)); // mid riser, slope ≥ 0.2 return FOOTHILL_TOP + s * (PLATEAU - FOOTHILL_TOP); } if (h < K6) { u = (h - K5) / (K6 - K5); return PLATEAU + u * BENCH_STEP; // white plateau, near-flat } float spikeMax = EffectiveSpikeMax(hMaxSeed); if (h < spikeMax) { u = (h - K6) / (spikeMax - K6); s = 0.1f * u + 0.9f * (u * u * u * u); // summit spike (v2 shape kept), slope ≥ 0.1 return PLATEAU_TOP + s * (PEAK_CAP - PLATEAU_TOP); } return PEAK_CAP + (h - spikeMax) * TAIL_SLOPE; } /// /// Numeric strict-monotonicity check of the EFFECTIVE per-seed curve — call once /// per generation after hMaxSeed is known. Loud throw, refuses to generate. /// public static void AssertMonotonic(float hMaxSeed) { float prevH = -7f; float prev = Apply(prevH, hMaxSeed); // Only strictly increasing float32 samples are compared (task-05 incident fix). void Check(double hd) { float h = (float)hd; if (h <= prevH) return; float v = Apply(h, hMaxSeed); if (v <= prev) throw new System.InvalidOperationException( $"[HeightCurve] MONOTONICITY VIOLATION at h={h} (hMaxSeed={hMaxSeed}): {v} <= {prev}. Refusing to generate."); prev = v; prevH = h; } double top = System.Math.Max(2.0, EffectiveSpikeMax(hMaxSeed) + 0.5); for (double h = -7.0 + 0.01; h < 0.10; h += 0.01) Check(h); for (double h = 0.10; h <= top; h += 0.0001) Check(h); for (double h = top + 0.05; h <= top + 6.0; h += 0.05) Check(h); GD.Print($"[HeightCurve] Monotonicity assertion passed (v{VERSION}, effective spikeMax {EffectiveSpikeMax(hMaxSeed):F6})."); } }