Ports the reference's v5 height curve and shelf-detail passes onto Phase 1's shape and re-calibrates them against this repo's actual pass-1 distribution. This is the BASELINE the reshape gets judged against, not the reshape. Core (engine-free, D-060): - WorldScale — THE vertical yardstick. One metres/raw number (251), replacing the prototype's three duplicate M_PER_UNIT constants and ~20 bare literals. The chunk-height coupling it had there is recorded as a DEFERRED vault decision, not inherited. RawFromMetres divides, matching the reference bit-for-bit. - HeightCurve — the 7 bands, the frozen corner-fix blends, the per-seed spike normalization, the 24-corner monotonicity sweep that throws and refuses. Identity at and below sea, which everything downstream rests on. - CurveKnots / CurveAnchors — input knots (measured percentiles) and output anchors (storm ladder) split apart and both made parameters, so the anchors are A/B-able without editing source. The reference's shipped knots are kept beside the measured ones as the fidelity yardstick. - TerrainDetailPass — micro-relief skin plus the shelf-edge KNOT warp (which slides K3/K4/K5, not height — that is what keeps monotonicity structural). The crater exclusion is ported and inert until the carve lands. Tools: - Shaping — pass 2a, producing the two height fields. classify is bit-for-bit the raw pass-1 field; render is curved and detailed. Aliased when the curve is off, as the reference did. Pass1Result is left immutable so the oracle can compare. - LandHistogram — the calibration engine AND the diagnostic. The reference shipped six knot literals and threw the measuring instrument away; this rebuilds it. - ShapingOracle + CurveBaselineTool — four automatic checks before anything is looked at, and the batch that runs them. Measured, not assumed: - Knots re-measured over a 6-seed / 12.8M-sample pool. They differ from the reference's by at most 5.6 m of world height, against a 44.7 m per-seed spread — the pass-1 port is faithful. - Oracle all pass, including pass 1 bit-identical to Phase 1's own .f32 dump. - Band shares land on 60/13/10/5/8/3/1 to 0.00 pp. - Knots hold across map size: the 8K delta (5.8 m) sits inside seed noise. The finding the histograms deliver: 83% of land ends below 100 m and 96% below 220 m, with the median column at 13 m. That is the share targets doing exactly what they say, not a bug — and it is the developer's call, which is why nothing here reshapes it and the palette was deliberately left mis-fitted rather than recalibrated to disguise it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DCWNaDZPfTiAy3meGNGgqt
120 lines
6.1 KiB
C#
120 lines
6.1 KiB
C#
namespace IslaApocalypse.Core
|
||
{
|
||
/// <summary>
|
||
/// 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
|
||
/// > <see cref="Reference"/> is kept beside <see cref="V2Baseline"/> 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.
|
||
/// </summary>
|
||
public sealed class CurveKnots
|
||
{
|
||
/// <summary>Preset id, carried into the blueprint's curve metadata when that lands.</summary>
|
||
public readonly byte PresetId;
|
||
|
||
/// <summary>Short name, for logs and batch folders.</summary>
|
||
public readonly string Name;
|
||
|
||
/// <summary>The six input knots, strictly ascending. K1..K6.</summary>
|
||
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 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.
|
||
/// </summary>
|
||
public static readonly double[] Percentiles = { 60.0, 73.0, 83.0, 88.0, 96.0, 99.0 };
|
||
|
||
/// <summary>The land-share target per output band, in percent, in band order.</summary>
|
||
public static readonly double[] BandShareTargets = { 60.0, 13.0, 10.0, 5.0, 8.0, 3.0, 1.0 };
|
||
|
||
/// <summary>Band names, in curve order. For tables and plot overlays.</summary>
|
||
public static readonly string[] BandNames =
|
||
{ "toe/orange", "red", "foothill riser", "bench", "mid riser", "plateau", "summit spike" };
|
||
|
||
/// <summary>
|
||
/// ⛔ THE REFERENCE'S SHIPPED KNOTS, kept verbatim as the fidelity yardstick — NOT for use.
|
||
///
|
||
/// Read from <c>REFERENCE:Tools/Scripts/HeightCurve.cs:57-58</c> at tag
|
||
/// <c>pre-rewrite-reference</c> (<c>ab78883</c>): 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.
|
||
/// </summary>
|
||
public static readonly CurveKnots Reference = new CurveKnots(2, "reference_balanced",
|
||
0.515899f, 0.612157f, 0.710472f, 0.784045f, 0.962922f, 1.119118f);
|
||
|
||
/// <summary>
|
||
/// ⭐ THE FAITHFUL v2 BASELINE — the same percentiles, re-measured on v2's own pass-1 output.
|
||
///
|
||
/// Measured by chat2/01 (<c>Tools/Scenes/CurveBaselineTool.tscn</c>) over a 6-seed pool at
|
||
/// MapSize 2048 — seeds 1063685222, 20260819, 777001, 424242, 90210, 31337 —
|
||
/// <b>12,854,486 land samples</b>, 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
|
||
/// <see cref="Reference"/> 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 <c>Tools/Scenes/CurveBaselineTool.tscn</c>; it prints this table.
|
||
/// </summary>
|
||
public static readonly CurveKnots V2Baseline = new CurveKnots(2, "v2_balanced",
|
||
0.529113f, 0.634439f, 0.732487f, 0.796957f, 0.960301f, 1.102473f);
|
||
|
||
/// <summary>Strictly ascending? The precondition every other guarantee rests on.</summary>
|
||
public bool IsStrictlyOrdered => K1 < K2 && K2 < K3 && K3 < K4 && K4 < K5 && K5 < K6;
|
||
|
||
/// <summary>Indexed access, K1..K6 as [0..5]. For tables and sweeps.</summary>
|
||
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})";
|
||
}
|
||
}
|