using System; namespace IslaApocalypse.Core { /// /// ⭐⭐ THE HEIGHT-REDISTRIBUTION CURVE — pass 2's first act, and the shape of the island's /// elevation profile. Ported from REFERENCE:Tools/Scripts/HeightCurve.cs (v5) at tag /// pre-rewrite-reference (ab78883). → D-050 ("port, don't re-derive"). /// /// ═══ WHAT IT IS FOR ═══ /// /// Raw fractal noise is Gaussian-ish: almost all land sits in a narrow mid-band and there is no /// coastal plain, no shelf, no distinguishable summit. The curve REDISTRIBUTES that distribution /// onto a designed elevation profile — a wide low plain, two shelves, risers between them, and a /// thin summit band under a hard cap. It changes WHERE heights land, never WHICH pixel is higher /// than which: the curve is strictly monotonic, so the terrain's topology is untouched. /// /// ═══ THE SEVEN BANDS (input knot → output anchor) ═══ /// /// band input output shape /// ─────────────── ─────────── ──────────────────────────── ────────────────────────── /// toe / orange [SEA, K1) Sea → OrangeCeil 0.3u + 0.7·u(2−u) ease-out /// red [K1, K2) Orange → RedCeil linear /// foothill riser [K2, k3) RedCeil → benchLo 0.1u + 0.9·smoothstep /// bench [k3, k4) benchLo → benchTop linear /// mid riser [k4, k5) benchTop → plateauLo 0.1u + 0.9·smoothstep /// plateau [k5, K6) plateauLo→ plateauTop linear /// summit spike [K6, sMax) plateauTop → PeakCap 0.05u + 0.95·u⁴ /// tail [sMax, ∞) PeakCap + (h−sMax)·TailSlope linear /// /// The riser and spike blends are the reference's "corner fixes" and are FROZEN: the 0.1 riser /// floor makes climbs decelerate into shelves and accelerate out of them (no machined edges), and /// the 0.05 spike floor lets the summit leave the plateau gently (no hard skirt under the peaks). /// /// ═══ ⚠⚠ THE LOAD-BEARING LINE ═══ /// /// if (h <= a.Sea) return h; /// /// THE CURVE IS IDENTITY AT AND BELOW SEA. Everything downstream rests on it: the waterline /// cannot move, the Trench's ocean-border guarantee survives, and — when water lands — the /// classify/render split agrees everywhere outside the crater, because a monotonic curve that /// fixes sea means Apply(raw) < sea exactly when raw < sea. Delete this line /// and the whole separability argument goes with it. /// /// ═══ ⚠ SEED-DEPENDENT BY CONSTRUCTION ═══ /// /// The summit spike maps [K6, hMaxSeed] onto the peak band, so the curve cannot be /// evaluated until pass 1 has scanned every pixel. That is why the pass-1/pass-2 boundary is a /// hard one and not an interleave. → , /// Tools/Pass1Result.HMaxSeed. /// /// ═══ PORT NOTES ═══ /// /// • Engine-free: the reference used Godot.Mathf only for arithmetic, so this lives in /// Core/ as a named C++-candidate seam (D-049, D-060). reproduces /// Mathf.Lerp's exact expression, so the port is bit-faithful and not merely equivalent. /// • Anchors are a parameter object () rather than consts, so the /// storm-ladder values are A/B-able from config. CurveAnchors.Default reproduces the /// reference's constants exactly. /// • The reference's two-preset machinery (COMPACT vs BALANCED) is NOT carried: COMPACT was /// retired by the task-09 verdict and exists only in that task's report. One knot set, named. /// • returns its confirmation line instead of printing it — Core /// has no GD.Print. The caller logs it. /// /// ⚠⚠ THE SHAPE IS FROZEN AT v5. This port adds no band, no anchor and no slope. Reshaping is a /// later, gated task; if you are here to steepen something, you are in the wrong file. /// public static class HeightCurve { /// Curve body version — the identity of the segment layout, not of the knots. public const ushort Version = 5; /// /// Mathf.Lerp, reproduced as the reference's engine wrote it: /// from + (to - from) * weight. ⚠ Written out rather than "simplified" because a /// different association of the same algebra is a different float32 result, and this port's /// fidelity claim is bit-level. /// private static float Lerp(float from, float to, float weight) => from + (to - from) * weight; /// /// The per-seed summit ceiling: the raw height the spike band's top maps to PeakCap. /// /// Max(hMaxSeed, K6 + SpikeMinSpan) — the floor guarantees a non-degenerate band on a /// seed whose map-wide maximum lands at or below K6, which would otherwise divide by zero. /// public static float EffectiveSpikeMax(float hMaxSeed, CurveKnots k, CurveAnchors a) => MathF.Max(hMaxSeed, k.K6 + a.SpikeMinSpan); /// /// Shelf band width from the per-column strength field. /// /// ⚠ THE LERP IS INVERTED, AND THAT IS THE REFERENCE'S INTENT: higher "strength" means a /// MORE PRONOUNCED shelf, which means a NARROWER input band mapped across the same output /// span — i.e. flatter ground. strength 0 → SpanMax, strength 1 → SpanMin. /// public static float ShelfSpan(float strength01, CurveAnchors a) => Lerp(a.ShelfSpanMax, a.ShelfSpanMin, Math.Clamp(strength01, 0f, 1f)); /// /// The curve, for ONE column. /// /// Raw pre-curve height. /// The map-wide raw maximum for this seed. → Pass1Result.HMaxSeed. /// This column's bench floor (base ± the anchor field). /// This column's bench output span. /// This column's plateau floor. /// This column's plateau output span. /// The input knot set. /// The output anchors. /// /// The shelf-edge warp (TerrainDetailPass pass B): slides the K3/K4/K5 BLOCK for this /// column. K1/K2/K6 never move, which is what keeps the red-ceiling floor and the peak cap /// EXACT under the warp rather than statistical. Zero when detail is off. /// public static float Apply(float h, float hMaxSeed, float benchLo, float benchSpan, float plateauLo, float plateauSpan, CurveKnots k, CurveAnchors a, float edgeShift) { // ⭐ IDENTITY AT AND BELOW SEA. See the type header — this line is the invariant. if (h <= a.Sea) return h; // The knot BLOCK slides rigidly: bench and mid-riser keep their exact widths (their // interiors are translated, not distorted); only the foothill riser and the plateau // stretch or compress to absorb the shift. float k3 = k.K3 + edgeShift, k4 = k.K4 + edgeShift, k5 = k.K5 + edgeShift; float u, s; if (h < k.K1) { u = (h - a.Sea) / (k.K1 - a.Sea); s = 0.3f * u + 0.7f * (u * (2f - u)); // frozen ease-out toe return a.Sea + s * (a.OrangeCeil - a.Sea); } if (h < k.K2) { u = (h - k.K1) / (k.K2 - k.K1); return a.OrangeCeil + u * (a.RedCeil - a.OrangeCeil); // frozen linear rise } if (h < k3) { u = (h - k.K2) / (k3 - k.K2); s = 0.1f * u + 0.9f * (u * u * (3f - 2f * u)); // foothill riser — corner fix 1 return a.RedCeil + s * (benchLo - a.RedCeil); } if (h < k4) { u = (h - k3) / (k4 - k3); return benchLo + u * benchSpan; // bench — corner fix 3 floors the span } float benchTop = benchLo + benchSpan; if (h < k5) { u = (h - k4) / (k5 - 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 - k5) / (k.K6 - k5); return plateauLo + u * plateauSpan; // plateau } float plateauTop = plateauLo + plateauSpan; float spikeMax = EffectiveSpikeMax(hMaxSeed, k, a); 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 * (a.PeakCap - plateauTop); } return a.PeakCap + (h - spikeMax) * a.TailSlope; // gentle tail, not a clip } /// /// Per-generation numeric strict-monotonicity proof of the EFFECTIVE curve — run once per /// seed, between the two passes, before any pixel is curved. /// /// ═══ WHY A NUMERIC SWEEP AND NOT AN ARGUMENT ═══ /// /// Monotonicity is structural in the algebra, but the curve as EVALUATED depends on three /// per-column fields and a per-column warp, and the corner fixes lowered the slope floors /// (risers 0.1, spike base 0.05) while the warp squeezes the foothill riser and the plateau. /// "It should be fine" is not the standard: the sweep proves every slope stays strictly /// positive at the extremes of BOTH, on this seed's actual spikeMax. /// /// 24 corners: 2 bench extremes × 2 plateau extremes × 2 span extremes × 3 edge shifts /// (−max, 0, +max). /// /// ⚠ THROWS AND REFUSES on violation, rather than warning. A non-monotonic curve inverts /// terrain — a peak becomes a pit — and that is not something to discover in a render. /// /// A one-line confirmation for the run log. Core cannot print; the caller does. public static string AssertMonotonic(float hMaxSeed, CurveKnots k, CurveAnchors a, float maxEdgeShift) { if (!k.IsStrictlyOrdered) throw new InvalidOperationException( $"[HeightCurve] KNOT ORDER VIOLATION: {k} is not strictly ascending. Refusing to generate."); if (maxEdgeShift < 0f || k.K2 + maxEdgeShift >= k.K3 || k.K5 + maxEdgeShift >= k.K6) throw new InvalidOperationException( $"[HeightCurve] EDGE-SHIFT BOUND VIOLATION: maxEdgeShift={maxEdgeShift} does not keep " + $"K2 < K3±d and K5±d < K6 (preset {k.Name}). Refusing to generate."); float[] benchLos = { a.BenchBase - a.BenchAmp, a.BenchBase + a.BenchAmp }; float[] plateauLos = { a.PlateauBase - a.PlateauAmp, a.PlateauBase + a.PlateauAmp }; float[] spans = { a.ShelfSpanMin, a.ShelfSpanMax }; float[] edgeShifts = maxEdgeShift > 0f ? new[] { -maxEdgeShift, 0f, maxEdgeShift } : new[] { 0f }; foreach (float bl in benchLos) foreach (float pl in plateauLos) foreach (float sp in spans) foreach (float es in edgeShifts) { float prevH = -7f; float prev = Apply(prevH, hMaxSeed, bl, sp, pl, sp, k, a, es); void Check(double hd) { float h = (float)hd; // Dedupe float32 samples: a fine double-precision step can land on the same // float twice, and "not greater" is not a violation when it is the same input. if (h <= prevH) return; float v = Apply(h, hMaxSeed, bl, sp, pl, sp, k, a, es); if (v <= prev) throw new InvalidOperationException( $"[HeightCurve] MONOTONICITY VIOLATION at h={h} (preset {k.Name}, " + $"hMaxSeed={hMaxSeed}, benchLo={bl}, plateauLo={pl}, span={sp}, edgeShift={es}): " + $"{v} <= {prev}. Refusing to generate."); prev = v; prevH = h; } double top = Math.Max(2.0, EffectiveSpikeMax(hMaxSeed, k, a) + 0.5); for (double hh = -7.0 + 0.01; hh < 0.10; hh += 0.01) Check(hh); // the below-sea identity run for (double hh = 0.10; hh <= top; hh += 0.0001) Check(hh); // every band, finely for (double hh = top + 0.05; hh <= top + 6.0; hh += 0.05) Check(hh); // the tail } return $"[HeightCurve] Monotonicity assertion passed (v{Version} preset '{k.Name}', " + $"8 modulation corners × edge shifts ±{maxEdgeShift:F6}, " + $"effective spikeMax {EffectiveSpikeMax(hMaxSeed, k, a):F6})."; } } }