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";
}
}