namespace IslaApocalypse.Core { /// /// The six INPUT knots of the redistribution curve — thresholds on the RAW pre-curve height that /// cut the land distribution into the curve's seven bands. /// /// ═══ ⚠⚠ THESE ARE A CALIBRATION ARTEFACT, NOT A DESIGN CONSTANT ═══ /// /// They are literal floats in source, but they were never CHOSEN as numbers. Each is a PERCENTILE /// of the measured land-height distribution, baked down to a literal: /// /// K1..K6 = P60 / P73 / P83 / P88 / P96 / P99 of the land CDF /// /// which is what makes the band SHARES — 60/13/10/5/8/3/1 % of land — exact by construction. /// The shares are the design decision; the knots are whatever percentiles land on THIS /// generator's distribution. /// /// > ### ⚠ A KNOT SET IS ONLY VALID FOR THE DISTRIBUTION IT WAS MEASURED ON. /// > Copying knots across a change to pass 1 silently reallocates the bands. That is why /// > is kept beside rather than replaced by it: /// > the DELTA between them is the port-fidelity check. /// /// Monotonicity does not depend on the values: the curve is monotonic for ANY strictly ordered /// knot set, and the shelf-edge warp's bound keeps the set ordered by construction. So a /// recalibration cannot break the curve — it can only move where the bands sit. /// public sealed class CurveKnots { /// Preset id, carried into the blueprint's curve metadata when that lands. public readonly byte PresetId; /// Short name, for logs and batch folders. public readonly string Name; /// The six input knots, strictly ascending. K1..K6. 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; } /// /// The quantiles the knots ARE, in percent. The band shares follow by differencing: /// 60 / 13 / 10 / 5 / 8 / 3 / 1. /// /// ⚠ THE SHARE TARGETS ARE THE M3 VALUES AND ARE HELD FIXED BY THIS PORT. Reallocating them /// is the reshape, and the reshape is a later task. /// public static readonly double[] Percentiles = { 60.0, 73.0, 83.0, 88.0, 96.0, 99.0 }; /// The land-share target per output band, in percent, in band order. public static readonly double[] BandShareTargets = { 60.0, 13.0, 10.0, 5.0, 8.0, 3.0, 1.0 }; /// Band names, in curve order. For tables and plot overlays. public static readonly string[] BandNames = { "toe/orange", "red", "foothill riser", "bench", "mid riser", "plateau", "summit spike" }; /// /// ⛔ THE REFERENCE'S SHIPPED KNOTS, kept verbatim as the fidelity yardstick — NOT for use. /// /// Read from REFERENCE:Tools/Scripts/HeightCurve.cs:57-58 at tag /// pre-rewrite-reference (ab78883): preset BALANCED (id 2), the task-09 taste /// gate's winner. Calibrated 2026-08-08 from the pooled batch-04 flat-sea land CDF, /// 340,618,126 samples. COMPACT (id 1) retired with that verdict. /// /// ⚠ The CDF that produced these does not exist in the reference repo — no sampler, no /// histogram, no percentile helper survives at the tag. Only the six outputs were committed, /// which is precisely why v2 had to rebuild the measuring instrument rather than copy them. /// public static readonly CurveKnots Reference = new CurveKnots(2, "reference_balanced", 0.515899f, 0.612157f, 0.710472f, 0.784045f, 0.962922f, 1.119118f); /// /// ⭐ THE FAITHFUL v2 BASELINE — the same percentiles, re-measured on v2's own pass-1 output. /// /// Measured by chat2/01 (Tools/Scenes/CurveBaselineTool.tscn) over a 6-seed pool at /// MapSize 2048 — seeds 1063685222, 20260819, 777001, 424242, 90210, 31337 — /// 12,854,486 land samples, fine-histogram quantiles at 1e-4 raw resolution with /// in-bin linear interpolation. Land range [0.1500 .. 1.4146] raw, zero overflow. /// /// ⚠ These are DELIBERATELY not the reference literals. The delta against /// is the port's fidelity evidence, and it is SMALL — the knots agree /// to within +5.6 / −4.2 metres of world height across all six: /// /// K1 P60 0.529113 (ref 0.515899, +3.32 m) /// K2 P73 0.634439 (ref 0.612157, +5.59 m) /// K3 P83 0.732487 (ref 0.710472, +5.53 m) /// K4 P88 0.796957 (ref 0.784045, +3.24 m) /// K5 P96 0.960301 (ref 0.962922, −0.66 m) /// K6 P99 1.102473 (ref 1.119118, −4.18 m) /// /// v2's land distribution is very slightly FATTER in the middle and SHORTER in the tail than /// the reference's — consistent with a faithful pass-1 port measured on six seeds rather than /// the reference's own pooled batch, not with a divergence. → `output/chat2/01_*.report.md`. /// /// ⚠ Quoted to six decimals, which is float32's honest precision; the stored values differ /// from the raw measurement by <1e-7 raw (2.5e-5 m). The batch tool always re-measures for /// its own run, so this constant is the default for OTHER callers, never the batch's input. /// /// Re-measure by running Tools/Scenes/CurveBaselineTool.tscn; it prints this table. /// public static readonly CurveKnots V2Baseline = new CurveKnots(2, "v2_balanced", 0.529113f, 0.634439f, 0.732487f, 0.796957f, 0.960301f, 1.102473f); /// Strictly ascending? The precondition every other guarantee rests on. public bool IsStrictlyOrdered => K1 < K2 && K2 < K3 && K3 < K4 && K4 < K5 && K5 < K6; /// Indexed access, K1..K6 as [0..5]. For tables and sweeps. public float this[int i] => i switch { 0 => K1, 1 => K2, 2 => K3, 3 => K4, 4 => K5, 5 => K6, _ => throw new System.IndexOutOfRangeException($"A curve has six knots; asked for {i}.") }; public override string ToString() => $"{Name}(K1={K1:F6} K2={K2:F6} K3={K3:F6} K4={K4:F6} K5={K5:F6} K6={K6:F6})"; } }