islaApocalypse/Tools/Scripts/HeightCurve.cs
beezm 02577872f2 feat: curve v5 — corner fixes + COMPACT/BALANCED taste presets (terrain-water task 09)
The three corner fixes (both presets): riser endpoint slope floors
0.2 -> 0.1 (0.1u + 0.9*smoothstep — climbs decelerate into shelves and
accelerate out); summit-spike base floor 0.1 -> 0.05
(0.05u + 0.95*u^4 — no hard skirt under the peaks); SHELF_SPAN_MIN
2 m -> 6 m (pronounced shelves keep a gentle tilt).

Knots are preset-parameterized (CurveKnots, explicit parameter — pure,
D-035), calibrated from the same pooled batch-04 CDF:
COMPACT  P65/77/84/88/94/97 -> 65/12/7/4/6/3/3 land split
BALANCED P60/73/83/88/96/99 -> 60/13/10/5/8/3/1
Gate: "off"|"v5-compact"|"v5-balanced" (batch-only tri-state;
interim default v5-balanced pending the developer's verdict — the
winner becomes plain "v5" in a follow-up commit); v1-v4 retired
loudly. Assertion runs per preset (8 corners x per-seed spikeMax; the
lowered floors stay strictly positive). TCRV v5 extension (+9 B):
preset id + K5/K6 — the effective curve is unambiguous from the
record; old records parse (version-dispatched); harness compares.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-08 04:11:53 -04:00

184 lines
7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Godot;
/// <summary>
/// One preset's input knots for the v5 curve. Two presets exist for the task-09
/// taste batch — COMPACT (maximum lowland, cordillera-from-plains) and BALANCED
/// (the task-07/08 lineage, gradual highland approach). The loser retires after
/// the developer's gate; the winner becomes plain "v5".
/// </summary>
public sealed class CurveKnots
{
public readonly byte PresetId;
public readonly string Name;
public readonly float K1, K2, K3, K4, K5, K6;
public CurveKnots(byte id, string name, float k1, float k2, float k3, float k4, float k5, float k6)
{
PresetId = id; Name = name;
K1 = k1; K2 = k2; K3 = k3; K4 = k4; K5 = k5; K6 = k6;
}
}
/// <summary>
/// The height-redistribution curve, v5 (terrain-water task 09) — v4's spatially
/// modulated shelves plus the THREE CORNER FIXES, with PRESET-PARAMETERIZED knots.
/// Pure, static, monotonic; every per-column input and the knot set are explicit
/// PARAMETERS (D-035).
///
/// The corner fixes (both presets — the naturalness work):
/// 1. Riser endpoint slope floor 0.2 → 0.1 (blend 0.1u + 0.9·smoothstep): climbs
/// decelerate into shelves and accelerate out of them — no machined edges.
/// 2. Summit-spike base floor 0.1 → 0.05 (blend 0.05u + 0.95·u⁴): the spike
/// leaves the plateau gently — no hard skirt under the peaks.
/// 3. SHELF_SPAN_MIN 2 m → 6 m: pronounced shelves keep a gentle tilt — flat to
/// build on, never snooker-table flat.
///
/// Presets (input land-fraction targets; knots calibrated 2026-08-08 from the same
/// pooled batch-04 flat-sea land CDF as tasks 05/07, 340,618,126 samples; achieved
/// fractions exact by construction):
/// COMPACT — 65/12/7/4/6/3/3 (orange/red/foothill-riser/bench/mid-riser/plateau/spike)
/// BALANCED — 60/13/10/5/8/3/1
///
/// Unchanged from v4: storm-ladder anchors, bench 100±12 m, plateau 220±20 m,
/// strength modulation (span max 25 m), 420 m cap, per-seed spike normalization,
/// modulation fields/seed offsets, the classify-map invariant.
/// </summary>
public static class HeightCurve
{
public const ushort VERSION = 5;
public static readonly CurveKnots COMPACT = new CurveKnots(1, "compact",
0.550982f, 0.646528f, 0.723145f, 0.784045f, 0.903025f, 1.003079f); // P65/77/84/88/94/97
public static readonly CurveKnots BALANCED = new CurveKnots(2, "balanced",
0.515899f, 0.612157f, 0.710472f, 0.784045f, 0.962922f, 1.119118f); // P60/73/83/88/96/99
// Fixed output anchors — storm ladder + ceiling (frozen).
public const float SEA = 0.15f;
public const float ORANGE_CEIL = 0.206f;
public const float RED_CEIL = 0.27f;
public const float PEAK_CAP = SEA + 420f / 251f;
public const float TAIL_SLOPE = 0.25f;
public const float SPIKE_MIN_SPAN = 0.01f;
// Modulated shelf anchors (unchanged from v4).
public const float BENCH_BASE = SEA + 100f / 251f;
public const float BENCH_AMP = 12f / 251f;
public const float PLATEAU_BASE = SEA + 220f / 251f;
public const float PLATEAU_AMP = 20f / 251f;
// Corner fix 3: pronounced shelves keep ~6 m of tilt across the shelf band.
public const float SHELF_SPAN_MIN = 6f / 251f; // ≈ 0.0239 (v4: 0.008 ≈ 2 m)
public const float SHELF_SPAN_MAX = 0.10f;
// Modulation-field derivation (unchanged from v4).
public const int BENCH_SEED_OFFSET = 7101;
public const int PLATEAU_SEED_OFFSET = 7207;
public const int STRENGTH_SEED_OFFSET = 7303;
public const float ELEV_FREQ_ISLANDS = 3.0f;
public const float STRENGTH_FREQ_ISLANDS = 5.0f;
public static float EffectiveSpikeMax(float hMaxSeed, CurveKnots k)
{
return Mathf.Max(hMaxSeed, k.K6 + SPIKE_MIN_SPAN);
}
public static float ShelfSpan(float strength01)
{
return Mathf.Lerp(SHELF_SPAN_MAX, SHELF_SPAN_MIN, Mathf.Clamp(strength01, 0f, 1f));
}
public static float Apply(float h, float hMaxSeed,
float benchLo, float benchSpan, float plateauLo, float plateauSpan, CurveKnots k)
{
if (h <= SEA) return h;
float u, s;
if (h < k.K1)
{
u = (h - SEA) / (k.K1 - SEA);
s = 0.3f * u + 0.7f * (u * (2f - u)); // frozen ease-out toe
return SEA + s * (ORANGE_CEIL - SEA);
}
if (h < k.K2)
{
u = (h - k.K1) / (k.K2 - k.K1);
return ORANGE_CEIL + u * (RED_CEIL - ORANGE_CEIL); // frozen linear rise
}
if (h < k.K3)
{
u = (h - k.K2) / (k.K3 - k.K2);
s = 0.1f * u + 0.9f * (u * u * (3f - 2f * u)); // foothill riser — corner fix 1
return RED_CEIL + s * (benchLo - RED_CEIL);
}
if (h < k.K4)
{
u = (h - k.K3) / (k.K4 - k.K3);
return benchLo + u * benchSpan; // bench (min span 6 m — fix 3)
}
float benchTop = benchLo + benchSpan;
if (h < k.K5)
{
u = (h - k.K4) / (k.K5 - k.K4);
s = 0.1f * u + 0.9f * (u * u * (3f - 2f * u)); // mid riser — corner fix 1
return benchTop + s * (plateauLo - benchTop);
}
if (h < k.K6)
{
u = (h - k.K5) / (k.K6 - k.K5);
return plateauLo + u * plateauSpan; // plateau
}
float plateauTop = plateauLo + plateauSpan;
float spikeMax = EffectiveSpikeMax(hMaxSeed, k);
if (h < spikeMax)
{
u = (h - k.K6) / (spikeMax - k.K6);
s = 0.05f * u + 0.95f * (u * u * u * u); // summit spike — corner fix 2
return plateauTop + s * (PEAK_CAP - plateauTop);
}
return PEAK_CAP + (h - spikeMax) * TAIL_SLOPE;
}
/// <summary>
/// Per-generation numeric strict-monotonicity check of the EFFECTIVE curve for
/// the selected preset: all 8 modulation-extreme corners × per-seed spikeMax.
/// The corner fixes lower the slope floors (risers 0.1, spike base 0.05) — the
/// sweep proves they stay strictly positive everywhere. Loud throw on failure.
/// </summary>
public static void AssertMonotonic(float hMaxSeed, CurveKnots k)
{
float[] benchLos = { BENCH_BASE - BENCH_AMP, BENCH_BASE + BENCH_AMP };
float[] plateauLos = { PLATEAU_BASE - PLATEAU_AMP, PLATEAU_BASE + PLATEAU_AMP };
float[] spans = { SHELF_SPAN_MIN, SHELF_SPAN_MAX };
foreach (float bl in benchLos)
{
foreach (float pl in plateauLos)
{
foreach (float sp in spans)
{
float prevH = -7f;
float prev = Apply(prevH, hMaxSeed, bl, sp, pl, sp, k);
void Check(double hd)
{
float h = (float)hd;
if (h <= prevH) return; // dedupe float32 samples (task-05 fix)
float v = Apply(h, hMaxSeed, bl, sp, pl, sp, k);
if (v <= prev)
throw new System.InvalidOperationException(
$"[HeightCurve] MONOTONICITY VIOLATION at h={h} (preset {k.Name}, hMaxSeed={hMaxSeed}, benchLo={bl}, plateauLo={pl}, span={sp}): {v} <= {prev}. Refusing to generate.");
prev = v;
prevH = h;
}
double top = System.Math.Max(2.0, EffectiveSpikeMax(hMaxSeed, k) + 0.5);
for (double hh = -7.0 + 0.01; hh < 0.10; hh += 0.01) Check(hh);
for (double hh = 0.10; hh <= top; hh += 0.0001) Check(hh);
for (double hh = top + 0.05; hh <= top + 6.0; hh += 0.05) Check(hh);
}
}
}
GD.Print($"[HeightCurve] Monotonicity assertion passed (v{VERSION} preset '{k.Name}', 8 modulation corners, effective spikeMax {EffectiveSpikeMax(hMaxSeed, k):F6}).");
}
}