namespace IslaApocalypse.Core { /// /// The redistribution curve's fixed OUTPUT anchors — the elevations the bands are mapped ONTO. /// /// ═══ INPUT KNOTS vs OUTPUT ANCHORS — the distinction the whole curve rests on ═══ /// /// — WHERE the land distribution is cut. Percentiles. Measured. /// — WHAT HEIGHT each cut lands at. Storm-ladder. Chosen. /// /// Re-measuring the knots moves how much land is in each band. Moving the anchors moves how HIGH /// each band sits. They are independent, and conflating them is how a "recalibration" turns into /// an unnoticed reshape. /// /// ═══ ⚠ WHY THIS IS A PARAMETER OBJECT AND NOT A WALL OF CONSTANTS ═══ /// /// The reference held these as const fields on HeightCurve and passed only the /// per-column modulated values (benchLo, benchSpan, …) as arguments. That made the /// storm-ladder anchors unreachable from config: A/B-ing the 420 m cap meant editing and /// rebuilding. /// /// Here every anchor is an explicit parameter, in the spirit of D-035 ("every per-column input /// and the knot set are explicit PARAMETERS"). reproduces the reference's /// constants bit-for-bit, so this is an exposure, not a change. /// /// ⚠⚠ THE BAND COUNT AND THE SEGMENT SHAPES ARE NOT EXPOSED, DELIBERATELY. Adding a knot, /// steepening a segment or reallocating the shares is the RESHAPE — a later task with its own /// gate. This type exposes the existing seven-band curve's dials and nothing more. /// /// Every metre-denominated anchor is derived through — /// the single yardstick — never a literal /251f. /// public sealed class CurveAnchors { // ---- the frozen band ceilings (raw height units) --------------------- /// /// Sea level in raw units. ⚠ ALSO THE CURVE'S IDENTITY THRESHOLD: at and below this the /// curve returns its input untouched, which is what keeps the waterline, the Trench /// guarantee and (later) every water body invariant under the curve. Reference: 0.15f. /// public float Sea = 0.15f; /// Top of the toe/orange band. Reference: 0.206f. public float OrangeCeil = 0.206f; /// Top of the red band — the floor the foothill riser climbs from. Reference: 0.27f. public float RedCeil = 0.27f; // ---- the modulated shelf anchors ------------------------------------- /// Bench centre, raw. Reference: SEA + 100 m. public float BenchBase = 0.15f + WorldScale.RawFromMetres(100f); /// Bench modulation amplitude, raw. Reference: ±12 m. public float BenchAmp = WorldScale.RawFromMetres(12f); /// Plateau centre, raw. Reference: SEA + 220 m. public float PlateauBase = 0.15f + WorldScale.RawFromMetres(220f); /// Plateau modulation amplitude, raw. Reference: ±20 m. public float PlateauAmp = WorldScale.RawFromMetres(20f); /// /// Narrowest a shelf band may be, raw. Reference: 6 m (v4 was 2 m — "corner fix 3": /// a pronounced shelf keeps a gentle tilt, flat to build on but never snooker-table flat). /// public float ShelfSpanMin = WorldScale.RawFromMetres(6f); /// Widest a shelf band may be, raw. Reference: 0.10f (≈ 25 m). public float ShelfSpanMax = 0.10f; // ---- the ceiling and its tail --------------------------------------- /// /// The peak cap, raw. Reference: SEA + 420 m. The summit spike maps /// [K6, spikeMax] onto [plateauTop, PeakCap], so this is the island's /// nominal ceiling — exact, not statistical, because K6 never moves under the edge warp. /// public float PeakCap = 0.15f + WorldScale.RawFromMetres(420f); /// /// Slope above spikeMax. Reference: 0.25f. ⚠ A gentle TAIL, not a hard clip — a seed /// whose max exceeds the spike range still rises, just slowly. /// public float TailSlope = 0.25f; /// /// Minimum spike span, raw. Reference: 0.01f. Guarantees a non-degenerate summit band on a /// seed whose map-wide max lands at or below K6. /// public float SpikeMinSpan = 0.01f; // ---- the modulation fields' identity --------------------------------- // // ⚠ THESE ARE SEED OFFSETS, NOT COORDINATE OFFSETS. The reference decorrelated its curve // modulation fields by seeding each one at `resolvedSeed + offset` and sampling all of them // at the bare (x, y) — there is no `GetNoise2D(x + 1000, …)` anywhere in this path. So the // raw-pixel-offset hazard GenerationScale warns about does NOT apply here, and there is // nothing to normalize. (Verified against every MakeModulationNoise call site; recorded // because the absence of a bug is only reassuring if someone checked.) /// Bench-anchor field seed offset. Reference: 7101. public int BenchSeedOffset = 7101; /// Plateau-anchor field seed offset. Reference: 7207. public int PlateauSeedOffset = 7207; /// Shelf-strength field seed offset. Reference: 7303. public int StrengthSeedOffset = 7303; /// /// Anchor-field frequency, in periods per MAP WIDTH. Reference: 3.0f — a very low frequency, /// so the bench and plateau elevations drift across the island rather than flickering. /// ⚠ Already scale-safe by construction: stated per map width, not per pixel. /// public float ElevFreqPerMapWidth = 3.0f; /// Shelf-strength field frequency, periods per map width. Reference: 5.0f. public float StrengthFreqPerMapWidth = 5.0f; /// /// The reference's shipped anchors, reproduced bit-for-bit. Every metre value goes through /// , which divides — matching the reference's /// 420f / 251f exactly rather than approximating it with a reciprocal multiply. /// public static CurveAnchors Default => new CurveAnchors(); public CurveAnchors Clone() => (CurveAnchors)MemberwiseClone(); /// /// The anchors as the storm ladder states them — metres above sea. For a run header, where /// raw units mean nothing to a reader. /// public string DescribeMetres() => $"sea {Sea:F3} raw · orange {WorldScale.MetresFromRaw(OrangeCeil - Sea):F0} m · " + $"red {WorldScale.MetresFromRaw(RedCeil - Sea):F0} m · " + $"bench {WorldScale.MetresFromRaw(BenchBase - Sea):F0}±{WorldScale.MetresFromRaw(BenchAmp):F0} m · " + $"plateau {WorldScale.MetresFromRaw(PlateauBase - Sea):F0}±{WorldScale.MetresFromRaw(PlateauAmp):F0} m · " + $"cap {WorldScale.MetresFromRaw(PeakCap - Sea):F0} m"; } }