using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Godot;
using IslaApocalypse.Core;
namespace IslaApocalypse.Tools
{
///
/// ⭐ THE MOUNTAIN-RESTORE BATCH (chat2/03) — put the mountain back, as a smooth slope.
///
/// ═══ THE STORY THIS BATCH TELLS, IN THREE HISTOGRAMS ═══
///
/// staircase the mass is there, but parked in two spikes (bench 100 m, plateau 220 m)
/// continuous_02 the spikes are gone — and so is the mass. It fell to 30–90 m.
/// continuous_restored ⭐ the same mass as the staircase, spread as one smooth grade.
///
/// That contrast is the point, so the three are named to sort adjacent.
///
/// ═══ HOW THE RESTORATION IS MEASURED ═══
///
/// The climb's control points are no longer invented from shape knobs. They are MEASURED off the
/// staircase itself, on the same 6-seed pool tasks 01/02 use:
///
/// for p in {10,30,50,70,85,95} of ABOVE-CEILING land:
/// u_p ← that percentile of the RAW height (normalized into the climb's span)
/// v_p ← that percentile of the OUTPUT height (staircase, normalized)
///
/// PCHIP through those points reproduces the staircase's elevation envelope; the flat bench and
/// plateau interiors become grade because ClimbCalibration.MinNormalizedSecant floors
/// every segment. → .
///
/// ⚠ The two quantile sets are paired by percentile across the SAME cell population, which is
/// exact only if the staircase were strictly monotone per column. It is monotone in raw, but the
/// per-column bench/plateau modulation (±12 / ±20 m) blurs the pairing by about that much. That
/// is well inside the envelope being targeted, and calibrating on the MEASURED output (rather
/// than a nominal unmodulated curve) is what makes oracle (g)'s land-above-100 m figure the thing
/// actually being aimed at.
///
/// ═══ RUNNING IT ═══
///
/// xvfb-run -a Godot_v4.7.2-stable_mono_linux.x86_64 \
/// --path ~/celerNexus/islaApocalypse-v2 res://Tools/Scenes/MountainRestoreTool.tscn
///
/// ISLA_TASK / ISLA_BATCH / ISLA_MAPSIZE / ISLA_SEEDS / ISLA_SHOWPIECE_SIZE / ISLA_SHOWPIECE
/// ISLA_PHASE1_SOURCE (default "chat1/02_pass1_port")
/// ISLA_SKIP_RAW
/// ISLA_LIFT_BIG probe: the `continuous_bigger` lift (default 1.35)
/// ISLA_SHARP probe: the `continuous_sharper_peak` knob (default 2.5)
///
public partial class MountainRestoreTool : Node
{
private static readonly int[] DefaultSeeds = { 1063685222, 777001 };
/// ⚠ Task 01's pool, verbatim — the knots' identity, and with it the staircase control's.
private static readonly int[] CalibrationSeeds = { 1063685222, 20260819, 777001, 424242, 90210, 31337 };
private const int DefaultMapSize = 2048;
private const int DefaultShowpieceSize = 8192;
/// Oracle (g)'s PASS/NOTE threshold, percentage points of land above 100 m. Reported either way.
private const double MountainTolerancePp = 2.0;
public override void _Ready()
{
try { Run(); }
catch (Exception e)
{
GD.PrintErr("==================================================================");
GD.PrintErr($" REFUSED: {e.Message}");
GD.PrintErr("==================================================================");
GetTree().Quit(2);
}
}
private void Run()
{
ToolingPaths.Configure(OS.GetUserDataDir());
// ⭐ rivers/01: batches are namespaced by chat. The default is this tool's AUTHORING chat,
// so re-running it reproduces its own batch in place; ISLA_CHAT redirects a run to another
// chat's namespace — which is what keeps an acceptance run from overwriting its own anchor.
ToolingPaths.ConfigureChat(EnvStr(ToolingPaths.ChatVar, "chat2"));
int task = EnvInt("ISLA_TASK", 3);
string descr = EnvStr("ISLA_BATCH", "mountain_restore");
int mapSize = EnvInt("ISLA_MAPSIZE", DefaultMapSize);
int[] seeds = EnvSeeds("ISLA_SEEDS", DefaultSeeds);
int showSize = EnvInt("ISLA_SHOWPIECE_SIZE", DefaultShowpieceSize);
bool showpiece = EnvStr("ISLA_SHOWPIECE", "1") == "1";
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "chat1/02_pass1_port");
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
float liftBig = EnvFloat("ISLA_LIFT_BIG", 1.35f);
float sharpKnob = EnvFloat("ISLA_SHARP", 2.5f);
string batchRoot = ToolingPaths.BatchRoot(task, descr);
DirAccess.MakeDirRecursiveAbsolute(batchRoot);
DirAccess.MakeDirRecursiveAbsolute(ToolingPaths.BatchScratch(batchRoot));
var anchors = CurveAnchors.Default;
float sea = 0.15f;
int primary = seeds[0];
GD.Print("==================================================================");
GD.Print(" MOUNTAIN RESTORE (chat2/03) — the staircase's mountain,");
GD.Print(" de-terraced. Calibrated, not invented.");
GD.Print("==================================================================");
GD.Print($"MapSize : {mapSize} showpiece {(showpiece ? showSize.ToString() : "off")}");
GD.Print($"yardstick : {WorldScale.Describe()}");
GD.Print($"seeds : {string.Join(", ", seeds)} (calibration pool: {string.Join(", ", CalibrationSeeds)})");
GD.Print($"batch : {batchRoot}");
GD.Print("==================================================================");
// ═══ 0. KNOTS — task 01's pool, re-measured for bit-identity ═══
GD.Print("\n--- 0. KNOTS ---");
var rawPool = new LandHistogram(sea);
var pass1 = new Dictionary();
foreach (int seed in CalibrationSeeds)
{
var p1 = Topography.Generate(TerrainGenConfig.CalibrationPool(mapSize, seed));
pass1[seed] = p1;
rawPool.Accumulate(p1.Height, mapSize);
}
var knots = new CurveKnots(2, "v2_balanced",
rawPool.Quantile(CurveKnots.Percentiles[0]), rawPool.Quantile(CurveKnots.Percentiles[1]),
rawPool.Quantile(CurveKnots.Percentiles[2]), rawPool.Quantile(CurveKnots.Percentiles[3]),
rawPool.Quantile(CurveKnots.Percentiles[4]), rawPool.Quantile(CurveKnots.Percentiles[5]));
GD.Print($" {rawPool}");
GD.Print($" {knots}");
// ═══ 1. CALIBRATE — measure the staircase's above-ceiling elevation distribution ═══
//
// ⚠ The ceiling is the DEFAULT 30 m handover, which is exactly (K2, RED_CEIL). So
// "above-ceiling" is simply "raw > K2" — no derived float, and the same population the
// climb will later be responsible for.
GD.Print("\n--- 1. CALIBRATION (staircase above-ceiling distribution) ---");
float ceilingRaw = knots.K2;
var rawAbove = new LandHistogram(sea);
var outAbove = new LandHistogram(sea);
var stairPool = new Dictionary();
foreach (int seed in CalibrationSeeds)
{
var scfg = MakeConfig(mapSize, seed, knots, anchors, "staircase");
scfg.CurveMode = CurveModeKind.Staircase;
scfg.ShelfDetail = true;
Pass2Result st = Shaping.Shape(pass1[seed], scfg);
stairPool[seed] = st;
rawAbove.AccumulateWhere(pass1[seed].Height, pass1[seed].Height, mapSize, ceilingRaw);
outAbove.AccumulateWhere(st.Height, pass1[seed].Height, mapSize, ceilingRaw);
}
double shareAbove = 100.0 * rawAbove.TotalLand / rawPool.TotalLand;
GD.Print($" above-ceiling land: {rawAbove.TotalLand:N0} cells = {shareAbove:F1}% of all land");
var pcts = ClimbCalibration.DefaultPercentiles;
var rawQ = new float[pcts.Length];
var outQ = new float[pcts.Length];
GD.Print(" percentile → raw → staircase output");
for (int i = 0; i < pcts.Length; i++)
{
rawQ[i] = rawAbove.Quantile(pcts[i]);
outQ[i] = outAbove.Quantile(pcts[i]);
GD.Print($" P{pcts[i],-4:F0} raw {rawQ[i]:F4} → {WorldScale.MetresFromRaw(outQ[i] - sea),6:F1} m");
}
ClimbCalibration Calib(float lift, float sharp) => ClimbCalibration.FromPercentiles(
pcts, rawQ, outQ, ceilingRaw, HeightCurve.EffectiveSpikeMax(pass1[primary].HMaxSeed, knots, anchors),
anchors.RedCeil, anchors.PeakCap, lift, sharp);
// ⚠ ONE calibration object per knob pair, shared across seeds. spikeMax differs slightly
// per seed, but the calibration is NORMALIZED (u, v in [0,1]) — BuildCalibrated
// denormalizes against each seed's own spikeMax. So the shape is shared; the extent is
// per-seed, exactly as the per-seed peak normalization requires.
var calRestored = Calib(1.0f, 1.0f);
var calBigger = Calib(liftBig, 1.0f);
var calSharper = Calib(1.0f, sharpKnob);
GD.Print($" restored: {calRestored.Describe()}");
GD.Print($" bigger : {calBigger.Describe()}");
GD.Print($" sharper : {calSharper.Describe()}");
// ═══ 2. VARIANTS ═══
var variants = new List<(string label, Action mutate)>
{
("staircase", c => { c.CurveMode = CurveModeKind.Staircase; c.ShelfDetail = true; }),
("continuous_02default", c => { c.CurveMode = CurveModeKind.Continuous;
c.ClimbCalibration = null; // the analytic 02 curve
c.ClimbFeather = 0.4f; c.SummitDrama = 2.5f; }),
("continuous_restored", c => { c.CurveMode = CurveModeKind.Continuous; c.ClimbCalibration = calRestored; }),
("continuous_bigger", c => { c.CurveMode = CurveModeKind.Continuous; c.ClimbCalibration = calBigger; }),
("continuous_sharper_peak", c => { c.CurveMode = CurveModeKind.Continuous; c.ClimbCalibration = calSharper; }),
};
GD.Print("\n--- 2. VARIANTS ---");
var results = new Dictionary<(int, string), Pass2Result>();
var offs = new Dictionary();
var rows = new List();
bool notesPrinted = false;
foreach (int seed in seeds)
{
var offCfg = MakeConfig(mapSize, seed, knots, anchors, "curve_off");
offCfg.Curve = false;
offs[seed] = Shaping.Shape(pass1[seed], offCfg);
foreach (var (label, mutate) in variants)
{
var cfg = MakeConfig(mapSize, seed, knots, anchors, label);
mutate(cfg);
Pass2Result p2 = Shaping.Shape(pass1[seed], cfg);
results[(seed, label)] = p2;
if (!notesPrinted) foreach (string nt in p2.Notes) GD.Print(" " + nt);
rows.Add(WriteVariant(batchRoot, p2, sea, anchors, skipRaw));
}
notesPrinted = true;
}
// ═══ 3. ORACLE ═══
GD.Print("\n--- 3. ORACLE ---");
var hard = new List();
var soft = new List();
// ⭐ a1 KEPT at rivers/01 — the family-off pass-1 guard (config pinned family-off).
// ⚠ A missing dump now THROWS instead of skipping silently.
string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{primary}_full", "height.f32");
hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF == Phase-1 .f32 dump",
offs[primary].Height, ShapingOracle.LoadAnchor("a1", "ISLA_PHASE1_SOURCE", p1Dump, mapSize), mapSize, p1Dump));
// ⚑ RETIRED at rivers/01 — a2, the staircase == `01_curve_baseline` control.
// Superseded by the continuous grade (→ D-062) — see CurveContinuousTool for the full note.
// The dump is NOT deleted (file-safety; it is regenerable and it is the record of what
// was judged); its `INDEX.md` is marked superseded. The check is gone so nothing can
// pass against a superseded baseline. → XX_Human/output/rivers/01_*.report.md §A4.
long bFail = 0;
foreach (int seed in seeds)
foreach (var (label, _) in variants)
if (!ShapingOracle.ClassifyFidelity(pass1[seed], results[(seed, label)]).Passed) bFail++;
hard.Add(new ShapingOracle.Check
{
Id = "b", Name = "classify == raw, all seeds × all variants",
Passed = bFail == 0,
Detail = bFail == 0 ? $"bit-identical on {seeds.Length} seeds × {variants.Count} variants"
: $"{bFail} (seed, variant) pairs drifted",
});
bool cOk = results[(primary, "continuous_restored")].Notes
.Exists(n => n.Contains("strict-increase sample passed"));
hard.Add(new ShapingOracle.Check
{
Id = "c", Name = "monotone — Fritsch–Carlson + per-seed sampled",
Passed = cOk,
Detail = cOk ? "confirmed on the calibrated climb (throws and refuses on violation)"
: "no strict-increase confirmation recorded",
});
string[] continuous = { "continuous_02default", "continuous_restored", "continuous_bigger", "continuous_sharper_peak" };
foreach (int seed in seeds)
foreach (string label in continuous)
{
var d = ShapingOracle.LowlandsPreserved(pass1[seed], results[(seed, "staircase")], results[(seed, label)]);
d.Name += $" [seed {seed}]";
hard.Add(d);
var e = ShapingOracle.UpperClimbProfile(results[(seed, label)]);
e.Name += $" [seed {seed}]";
soft.Add(e);
}
foreach (int seed in seeds)
foreach (var (label, _) in variants)
{
var f = ShapingOracle.SeaIdentity(offs[seed], results[(seed, label)], sea);
f.Name += $" [seed {seed}]";
hard.Add(f);
}
// (g) the restoration, measured — reported for every variant, gated for none.
var mountain = new List();
foreach (int seed in seeds)
foreach (string label in continuous)
{
var g = ShapingOracle.MountainRestored(results[(seed, label)], results[(seed, "staircase")],
sea, MountainTolerancePp);
g.Name += $" [seed {seed}]";
mountain.Add(g);
}
foreach (var c in hard) GD.Print(" " + c);
foreach (var c in soft) { if (c.Passed) GD.Print(" " + c); else GD.PrintErr(" ⚠ SLOPE: " + c); }
GD.Print(" --- (g) mountain, reported not gated ---");
foreach (var c in mountain) GD.Print(" " + c);
bool hardOk = hard.TrueForAll(c => c.Passed);
GD.Print($" ORACLE: {(hardOk ? "ALL HARD CHECKS PASS" : "*** HARD FAILURES ***")}");
// ═══ 4. HISTOGRAMS — the three-way contrast, adjacent by filename ═══
GD.Print("\n--- 4. HISTOGRAMS ---");
foreach (int seed in seeds)
{
var rawSeed = new LandHistogram(sea);
rawSeed.Accumulate(pass1[seed].Height, mapSize);
int order = 1;
foreach (var (label, _) in variants)
DrawShaped(results[(seed, label)], anchors, seed, mapSize, batchRoot, order++, sea);
}
// ═══ 5. SHOWPIECE ═══
string showNote = "skipped (ISLA_SHOWPIECE=0)";
if (showpiece)
{
GD.Print($"\n--- 5. SHOWPIECE at {showSize} (continuous_restored, seed {primary}) ---");
var cfg = MakeConfig(showSize, primary, knots, anchors, "continuous_restored_showpiece");
cfg.CurveMode = CurveModeKind.Continuous;
cfg.ClimbCalibration = calRestored;
Pass1Result p1 = Topography.Generate(cfg);
Pass2Result big = Shaping.Shape(p1, cfg);
foreach (string nt in big.Notes) GD.Print(" " + nt);
var cb = ShapingOracle.ClassifyFidelity(p1, big);
var offBigCfg = MakeConfig(showSize, primary, knots, anchors, "off"); offBigCfg.Curve = false;
var cf = ShapingOracle.SeaIdentity(Shaping.Shape(p1, offBigCfg), big, sea);
GD.Print($" {cb}");
GD.Print($" {cf}");
if (!cb.Passed || !cf.Passed) hardOk = false;
var (b100, b220) = ShapingOracle.LandAbove(big, sea);
GD.Print($" land >100 m {b100:F2}% >220 m {b220:F2}% (at {showSize})");
rows.Add(WriteVariant(batchRoot, big, sea, anchors, skipRaw));
showNote = $"seed {primary} at {showSize}; >100 m {b100:F2}%, >220 m {b220:F2}%";
}
WriteIndex(batchRoot, mapSize, showSize, seeds, primary, anchors, results, calRestored,
calBigger, calSharper, pcts, rawQ, outQ, hard, soft, mountain, rows, hardOk, showNote, sea);
GD.Print("\n==================================================================");
GD.Print($" DONE — {batchRoot}");
GD.Print($" ORACLE {(hardOk ? "HARD CHECKS ALL PASS" : "*** HARD FAILURES ***")}");
GD.Print("==================================================================");
GetTree().Quit(hardOk ? 0 : 3);
}
///
/// ⭐ rivers/01 — FAMILY-OFF PINNED, not defaulted. This is chat-2 CURVE development: authored
/// and judged before the shape family existed, on the family-off distribution the knots are
/// percentiles of. The re-baseline flipped the bare defaults family-ON, so the pin is what
/// keeps this tool measuring the thing it was written to measure.
/// → .
///
private static TerrainGenConfig MakeConfig(int mapSize, int seed, CurveKnots k, CurveAnchors a, string label)
=> new TerrainGenConfig
{
MapSize = mapSize, Seed = seed, VariantLabel = label,
Curve = true, ShelfDetail = false, Knots = k, Anchors = a,
LowlandCeilingM = 30f,
}.WithFamilyOff();
// ---- output ---------------------------------------------------------
private static string WriteVariant(string batchRoot, Pass2Result p2, float sea,
CurveAnchors anchors, bool skipRaw)
{
string dir = Path.Combine(batchRoot, $"{p2.Seed}_{p2.VariantLabel}");
DirAccess.MakeDirRecursiveAbsolute(dir);
var (gMin, gMax) = GrayscaleRenderer.SavePng(p2.Height, p2.MapSize, Path.Combine(dir, "grayscale.png"));
if (!skipRaw) HeightField.Save(p2.Height, p2.MapSize, Path.Combine(dir, "height.f32"));
var look = new LookConfig
{
Name = "hillshade_even", Palette = ReliefPalette.Kind.ProvisionalEven,
ZExaggeration = 18f, LightAzimuth = 315f, LightAltitude = 45f,
HillshadeStrength = 0.30f, SeaLevel = sea,
};
Image map = ReliefRenderer.Render(p2.Height, p2.MapSize, look);
LegendRenderer.WithLegend(map, look.Palette, sea, anchors.PeakCap, p2.VariantLabel.ToUpperInvariant())
.SavePng(Path.Combine(dir, "relief.png"));
var (a100, a220) = ShapingOracle.LandAbove(p2, sea);
GD.Print($" {p2.VariantLabel,-30} seed {p2.Seed,-11} h[{p2.HMin,7:F3} .. {p2.HMax,6:F3}] " +
$" >100m {a100,5:F2}% >220m {a220,5:F2}% {p2.ElapsedMs,5} ms");
return $"| `{p2.Seed}_{p2.VariantLabel}` | {p2.Seed} | {p2.VariantLabel} | {p2.HMin:F3} | {p2.HMax:F3} | " +
$"{a100:F2}% | {a220:F2}% | {gMin:F3}..{gMax:F3} | {p2.ElapsedMs} ms |";
}
private static void DrawShaped(Pass2Result p2, CurveAnchors a, int seed, int mapSize,
string batchRoot, int order, float sea)
{
var shaped = new LandHistogram(sea);
shaped.Accumulate(p2.Height, mapSize);
float top = MathF.Ceiling(shaped.MaxLand * 20f) / 20f;
var display = shaped.Rebin((top - shaped.SeaLevel) / 360f);
var (a100, a220) = ShapingOracle.LandAbove(p2, sea);
var o = new HistogramRenderer.Options
{
Title = $"{p2.VariantLabel.ToUpperInvariant()} - SEED {seed}",
Subtitle = $"LAND ABOVE 100M {a100:F2} PCT - ABOVE 220M {a220:F2} PCT",
XAxisLabel = "RAW HEIGHT (POST-CURVE)",
XTop = top,
Footer = $"{shaped.TotalLand} LAND COLUMNS AT MAPSIZE {mapSize}",
};
// The two heights the restoration is measured at, on every plate, so the three-way
// contrast can be read off the same reference lines.
o.Markers.Add(new HistogramRenderer.Marker { Value = a.Sea + WorldScale.RawFromMetres(100f), Label = "100M" });
o.Markers.Add(new HistogramRenderer.Marker { Value = a.Sea + WorldScale.RawFromMetres(220f), Label = "220M" });
o.Markers.Add(new HistogramRenderer.Marker { Value = a.PeakCap, Label = "CAP 420M", Strong = false });
if (p2.Continuous != null)
o.Markers.Add(new HistogramRenderer.Marker { Value = p2.Continuous.CeilingOut, Label = "LOWLAND", Strong = false });
string file = $"hist_{seed}_{order}_{p2.VariantLabel}.png";
HistogramRenderer.SavePng(display, o, Path.Combine(batchRoot, file));
GD.Print($" {file}");
}
private static void WriteIndex(string batchRoot, int mapSize, int showSize, int[] seeds, int primary,
CurveAnchors a, Dictionary<(int, string), Pass2Result> results,
ClimbCalibration calRestored, ClimbCalibration calBigger, ClimbCalibration calSharper,
double[] pcts, float[] rawQ, float[] outQ,
List hard, List soft, List mountain,
List rows, bool hardOk, string showNote, float sea)
{
var sb = new StringBuilder();
sb.AppendLine("# Batch 03 — restore the mountain, as a smooth slope");
sb.AppendLine();
sb.AppendLine("The continuous climb's control points are now **measured off the staircase** instead of");
sb.AppendLine("invented from shape knobs. Same mountain mass, zero terraces. The lowlands are still");
sb.AppendLine("preserved bit-for-bit (oracle d).");
sb.AppendLine();
sb.AppendLine("## ⭐ Open this first");
sb.AppendLine();
sb.AppendLine($"1. **`{primary}_continuous_restored_showpiece/relief.png`** — the centerpiece ({showNote}).");
sb.AppendLine("2. **The three-way histogram contrast**, adjacent by filename:");
sb.AppendLine($" - `hist_{primary}_1_staircase.png` — the mass, parked in two spikes");
sb.AppendLine($" - `hist_{primary}_2_continuous_02default.png` — spikes gone, **and so is the mass**");
sb.AppendLine($" - `hist_{primary}_3_continuous_restored.png` — ⭐ **the mass back, spread smooth**");
sb.AppendLine(" Every plate carries the same 100 m / 220 m reference lines.");
sb.AppendLine();
sb.AppendLine("## The restoration, measured");
sb.AppendLine();
sb.AppendLine("| Variant | land >100 m | land >220 m |");
sb.AppendLine("|---|---|---|");
foreach (string label in new[] { "staircase", "continuous_02default", "continuous_restored", "continuous_bigger", "continuous_sharper_peak" })
{
var (x100, x220) = ShapingOracle.LandAbove(results[(primary, label)], sea);
string star = label == "continuous_restored" ? " ⭐" : label == "staircase" ? " *(target)*" : "";
sb.AppendLine($"| `{label}`{star} | {x100:F2} % | {x220:F2} % |");
}
sb.AppendLine();
sb.AppendLine($"*(seed {primary} at {mapSize}; per-seed rows in Results below.)*");
sb.AppendLine();
sb.AppendLine("## The calibration");
sb.AppendLine();
sb.AppendLine("Measured on the 6-seed pool, above-ceiling land only:");
sb.AppendLine();
sb.AppendLine("| percentile | raw | staircase output |");
sb.AppendLine("|---|---|---|");
for (int i = 0; i < pcts.Length; i++)
sb.AppendLine($"| P{pcts[i]:F0} | {rawQ[i]:F4} | **{WorldScale.MetresFromRaw(outQ[i] - sea):F0} m** |");
sb.AppendLine();
sb.AppendLine("| Variant | knobs | control points (u,v) |");
sb.AppendLine("|---|---|---|");
sb.AppendLine($"| `continuous_restored` | {calRestored.Describe().Split('·')[0].Trim()} | `{calRestored.Describe().Split('·')[1].Trim()}` |");
sb.AppendLine($"| `continuous_bigger` | {calBigger.Describe().Split('·')[0].Trim()} | `{calBigger.Describe().Split('·')[1].Trim()}` |");
sb.AppendLine($"| `continuous_sharper_peak` | {calSharper.Describe().Split('·')[0].Trim()} | `{calSharper.Describe().Split('·')[1].Trim()}` |");
sb.AppendLine();
sb.AppendLine($"`floored` counts segments the no-bench floor had to lift — i.e. where the staircase was flat.");
sb.AppendLine();
sb.AppendLine("## ⚠ The palette is PROVISIONAL");
sb.AppendLine();
sb.AppendLine("`ProvisionalEven` — the CostaRica colours re-spaced evenly SEA → 420 m. Final calibration");
sb.AppendLine("waits for the chosen profile. **Grayscale + the histograms are the honest instruments.**");
sb.AppendLine();
sb.AppendLine("## The oracle");
sb.AppendLine();
sb.AppendLine(ShapingOracle.ToMarkdownTable(hard));
sb.AppendLine($"**{(hardOk ? "ALL HARD CHECKS PASS" : "⚠⚠ HARD FAILURES — do not judge this batch")}**");
sb.AppendLine();
sb.AppendLine("Soft — upper climb slope profile (e):");
sb.AppendLine();
sb.AppendLine(ShapingOracle.ToMarkdownTable(soft));
sb.AppendLine("(g) mountain restored — **reported, not gated** (it is a taste target the developer tunes):");
sb.AppendLine();
sb.AppendLine(ShapingOracle.ToMarkdownTable(mountain));
sb.AppendLine("## Disposability");
sb.AppendLine();
sb.AppendLine("| Artifact | Keep? |");
sb.AppendLine("|---|---|");
sb.AppendLine("| `relief.png`, `hist_*.png`, `INDEX.md` | **keep** |");
sb.AppendLine("| `grayscale.png` | ♻ regenerable from the `.f32` |");
sb.AppendLine("| `height.f32` | ♻ regenerable from seed + code (the byte-level oracle) |");
sb.AppendLine("| `scratch/` | persistent by rule; never cleaned |");
sb.AppendLine();
sb.AppendLine("## Results");
sb.AppendLine();
sb.AppendLine("| Folder | Seed | Variant | h min | h max | >100 m | >220 m | grayscale range | time |");
sb.AppendLine("|---|---|---|---|---|---|---|---|---|");
foreach (string row in rows) sb.AppendLine(row);
sb.AppendLine();
sb.AppendLine($"MapSize {mapSize}, showpiece {showSize}, seeds {string.Join(", ", seeds)}. {WorldScale.Describe()}.");
string index = Path.Combine(batchRoot, "INDEX.md");
using var f = Godot.FileAccess.Open(index, Godot.FileAccess.ModeFlags.Write);
if (f == null) { GD.PrintErr($"could not write {index}"); return; }
f.StoreString(sb.ToString());
}
// ---- env helpers ----------------------------------------------------
private static string EnvStr(string k, string fallback)
{
string v = System.Environment.GetEnvironmentVariable(k);
return string.IsNullOrWhiteSpace(v) ? fallback : v;
}
private static int EnvInt(string k, int fallback)
=> int.TryParse(EnvStr(k, null) ?? "", out int v) ? v : fallback;
private static float EnvFloat(string k, float fallback)
=> float.TryParse(EnvStr(k, null) ?? "", System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture, out float v) ? v : fallback;
private static int[] EnvSeeds(string k, int[] fallback)
{
string v = EnvStr(k, null);
if (v == null) return fallback;
var outp = new List();
foreach (string part in v.Split(',', StringSplitOptions.RemoveEmptyEntries))
if (int.TryParse(part.Trim(), out int s) && s > 0) outp.Add(s);
return outp.Count > 0 ? outp.ToArray() : fallback;
}
}
}