islaApocalypse-v2/Tools/Scripts/OffshoreIslandsTool.cs
beezm 3b96e06c1a chat2/05: offshore islands — rejoin the stub faithfully, then reshape to a guaranteed hybrid
Stage 1 fills the IslandFalloff.cs stub from the reference, verbatim: the coast shelf
(below-sea only, depth-preserving, held strictly below sea by MathF.BitDecrement — the
clamp that makes "cannot move the waterline" exact rather than statistical) and the islet
layer (OffshoreBlob, CalibrateThreshold against the field's ACTUAL distribution, the
OffshoreZoneWeight moat + pre-Trench-falloff test). Both run as pass 1b — OffshorePass, a
second sweep over the finished pass-1 arrays with the same per-pixel arithmetic in the
same order — and HMaxSeed is retaken AFTER them, as the reference did. That closes
chat2/00 Drift §2. The value did not move on any of 15 runs; the order is now right by
construction and oracle (l) prints it every time.

Stage 2 is the reshape, OffshoreSettings.Hybrid(): a seeded floor of ≥2 N / ≥4 S islands
placed by a PCG32 off the world seed — min-separated, clear of ALL existing land by a gap
so each stamp is its own connected component by construction, fully inside the zone so
the moat and falloff protections gate the floor exactly as they gate the organic layer —
plus the noise layer on top, smaller (freq 16), lower (24 m pre-curve, which the
preserved toe squashes to ~5 m and keeps there across curve tweaks), flatter (core 0.25),
crisper (edge sharpness 2.5, stamp rim jittered so it is rigid without being a compass
disc), south-weighted (0.007 N / 0.012 S, blended across the midline), corners allowed
(trench mask 0.90→0.97). Every lifted cell is tagged with its hemisphere — NORTH is rows
[0, N/2), y runs south, read off the spine's southern fade and the southern sinker, not
invented — and carried through Pass1Result → Pass2Result for a consumer that does not
exist yet.

Two things the probes taught, both now in the code: the first organic densities produced
183 blobs of which 174 were noise debris (a six-config sweep fixed that), and the
reference's lerp-to-crest leaves shallow humps across the seabed wherever a blob fails to
surface (a guard reverts them outside any surviving island's skirt; specks by component
membership, bumps by location). The faithful control keeps both, because it is the
reference — its islets measure min 1 cell, median 28.

Oracle, all hard checks passing: floor met on every hybrid seed (N 2–4, S 9–16); zero
land bridges; every offshore-OFF land cell bit-identical with offshore ON; tag ↔ coastline
consistent in both fields; curve-off still bit-identical to Phase 1's dump and
continuous_restored to task 03's. Both gates default OFF, deliberately: flipping them is
the act that retires the Phase-1 regression dumps, and that belongs in a task that
re-baselines the oracles.

The faithful control on seed 8675309 produced one north island. That is the gap the
floor exists to close.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCWNaDZPfTiAy3meGNGgqt
2026-08-21 05:14:53 -04:00

424 lines
21 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Godot;
using IslaApocalypse.Core;
namespace IslaApocalypse.Tools
{
/// <summary>
/// ⭐ THE OFFSHORE-ISLANDS BATCH (chat2/05) — the faithful rejoin as the control, then the
/// reshape: small / low / flat / rigid, loose-guaranteed (≥2 N, ≥4 S), organic extras weighted
/// south, corners on. Judged on 2D maps, with the tag made visible.
///
/// ═══ THE VARIANTS ═══
///
/// faithful stage 1 — the reference's probabilistic islets, verbatim. Sparse, no corners,
/// no floor. THE CONTROL.
/// floor_only the reshape's seeded ≥2N/4S floor with the organic layer OFF — shows the
/// guaranteed minimum and that positions vary per seed.
/// hybrid ⭐ floor + organic extras, reshaped, corners on. THE DELIVERABLE. Six seeds.
/// dense hybrid with ×3 organic density — a bookend for "how many is too many".
///
/// The shelf is ON for every variant (it is stage 1's other half and is invisible on these
/// plates anyway); an offshore-OFF / shelf-OFF field is generated per seed for the oracle only.
///
/// ═══ THE CURVE IS THE TAGGED CURVE, UNCHANGED ═══
///
/// `continuous_restored` (tag terrain-curve-v1), calibrated on task 01's pool at the iteration
/// size WITH OFFSHORE OFF — islands are additive land on top of a curve that does not know they
/// exist. Their crest (2434 m pre-curve) lands inside the curve's preserved toe, which squashes
/// it to a few metres; because that toe is bit-preserved across curve tweaks, the islands' height
/// is stable however the upper climb is tuned later.
///
/// ═══ RUNNING IT ═══
///
/// xvfb-run -a Godot_v4.7.2-stable_mono_linux.x86_64 \
/// --path ~/celerNexus/islaApocalypse-v2 res://Tools/Scenes/OffshoreIslandsTool.tscn
///
/// ISLA_TASK / ISLA_BATCH / ISLA_SKIP_RAW
/// ISLA_MAPSIZE render size (default 4096 — islands need pixels to read)
/// ISLA_CALIB_SIZE curve calibration size (default 2048, task 01's)
/// ISLA_SEEDS_HYBRID the six hybrid seeds
/// ISLA_SEEDS_SMALL the three seeds for faithful / floor_only / dense
/// ISLA_PHASE1_SOURCE Phase-1 .f32 batch (default "02_pass1_port")
/// ISLA_T03_SOURCE task-03 .f32 batch (default "03_mountain_restore")
/// </summary>
public partial class OffshoreIslandsTool : Node
{
private static readonly int[] DefaultHybridSeeds = { 1063685222, 20260821, 8675309, 123456789, 271828182, 999999937 };
private static readonly int[] DefaultSmallSeeds = { 1063685222, 8675309, 999999937 };
/// <summary>⚠ Task 01's pool, verbatim — the curve's identity.</summary>
private static readonly int[] CalibrationSeeds = { 1063685222, 20260819, 777001, 424242, 90210, 31337 };
private const int DefaultMapSize = 4096;
private const int DefaultCalibSize = 2048;
public override void _Ready()
{
try { Run(); }
catch (Exception e)
{
GD.PrintErr("==================================================================");
GD.PrintErr($" REFUSED: {e.Message}");
GD.PrintErr("==================================================================");
GetTree().Quit(2);
}
}
private sealed class Row
{
public string Variant; public int Seed;
public int CountN, CountS; public long Lifted; public float HMaxBefore, HMaxAfter;
public bool Ok;
public string Centres; // the seeded floor's (x,y) list — the "positions vary per seed" evidence
}
private void Run()
{
ToolingPaths.Configure(OS.GetUserDataDir());
int task = EnvInt("ISLA_TASK", 5);
string descr = EnvStr("ISLA_BATCH", "offshore_islands");
int mapSize = EnvInt("ISLA_MAPSIZE", DefaultMapSize);
int calibSize = EnvInt("ISLA_CALIB_SIZE", DefaultCalibSize);
int[] hybridSeeds = EnvSeeds("ISLA_SEEDS_HYBRID", DefaultHybridSeeds);
int[] smallSeeds = EnvSeeds("ISLA_SEEDS_SMALL", DefaultSmallSeeds);
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "02_pass1_port");
string t03Source = EnvStr("ISLA_T03_SOURCE", "03_mountain_restore");
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
string only = EnvStr("ISLA_ONLY", null); // probe: comma list of variant labels to run
string batchRoot = ToolingPaths.BatchRoot(task, descr);
DirAccess.MakeDirRecursiveAbsolute(batchRoot);
DirAccess.MakeDirRecursiveAbsolute(ToolingPaths.BatchScratch(batchRoot));
var anchors = CurveAnchors.Default;
float sea = 0.15f;
int primary = hybridSeeds[0];
GD.Print("==================================================================");
GD.Print(" OFFSHORE ISLANDS (chat2/05) — faithful rejoin, then the reshape");
GD.Print("==================================================================");
GD.Print($"MapSize : {mapSize} curve calibrated at {calibSize} (offshore OFF)");
GD.Print($"hybrid : {string.Join(", ", hybridSeeds)}");
GD.Print($"others : {string.Join(", ", smallSeeds)}");
GD.Print($"hemisphere: NORTH = rows [0, {mapSize / 2}) SOUTH = rows [{mapSize / 2}, {mapSize}) (y runs south)");
GD.Print($"batch : {batchRoot}");
GD.Print("==================================================================");
// ═══ 0. THE CURVE — continuous_restored, calibrated with offshore off ═══
GD.Print($"\n--- 0. CURVE (task-01 pool at {calibSize}, offshore off) ---");
var (knots, calibration) = CalibrateCurve(calibSize, sea, anchors);
GD.Print($" {knots}");
GD.Print($" {calibration.Describe()}");
// ═══ 1. REGRESSIONS at the calibration size — the things that must not have moved ═══
GD.Print($"\n--- 1. REGRESSIONS at {calibSize}, seed {primary} ---");
var hard = new List<ShapingOracle.Check>();
{
var offCfg = BaseConfig(calibSize, primary, knots, anchors, calibration, "off");
Pass1Result p1 = Topography.Generate(offCfg);
var curveOff = offCfg.Clone(); curveOff.Curve = false;
Pass2Result pOff = Shaping.Shape(p1, curveOff);
string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{primary}_full", "height.f32");
hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF, offshore OFF == Phase-1 .f32 dump",
pOff.Height, HeightField.Load(p1Dump, calibSize), calibSize, p1Dump));
Pass2Result pRest = Shaping.Shape(p1, offCfg);
string t03Dump = Path.Combine(ToolingPaths.BatchesRoot, t03Source, $"{primary}_continuous_restored", "height.f32");
hard.Add(ShapingOracle.DumpRegression("a3", "continuous_restored, offshore OFF == task-03 .f32 dump (lowlands + curve untouched)",
pRest.Height, HeightField.Load(t03Dump, calibSize), calibSize, t03Dump));
// Shelf ON, islets OFF: land must be bit-identical (the shelf touches only sea).
var shelfCfg = offCfg.Clone(); shelfCfg.CoastShelf = true; shelfCfg.VariantLabel = "shelf_only";
Pass1Result p1Shelf = Topography.Generate(shelfCfg);
var j0 = ShapingOracle.MainlandUnmoved(p1, p1Shelf, sea);
j0.Name = "shelf alone: every land cell bit-identical (shelf is below-sea only)";
hard.Add(j0);
hard.Add(ShapingOracle.HMaxAfterOffshore(p1Shelf));
}
foreach (var c in hard) GD.Print(" " + c);
// ═══ 2. VARIANTS ═══
// ⚠ PROBE OVERRIDES on the reshape's organic knobs, so the hybrid can be swept in scratch
// without a rebuild. The defaults live in OffshoreSettings.Hybrid() — that is the
// preset of record; these move it only when set. Printed into the INDEX either way.
OffshoreSettings HybridTuned()
{
var h = OffshoreSettings.Hybrid();
h.FreqPerMapWidth = EnvFloat("ISLA_OFF_FREQ", h.FreqPerMapWidth);
h.DensityNorth = EnvFloat("ISLA_OFF_DENS_N", h.DensityNorth);
h.DensitySouth = EnvFloat("ISLA_OFF_DENS_S", h.DensitySouth);
h.CoreFraction = EnvFloat("ISLA_OFF_CORE", h.CoreFraction);
h.EdgeSharpness = EnvFloat("ISLA_OFF_SHARP", h.EdgeSharpness);
h.MinIslandAreaFrac = EnvFloat("ISLA_OFF_MINAREA", h.MinIslandAreaFrac);
h.CrestM = EnvFloat("ISLA_OFF_CREST", h.CrestM);
return h;
}
var variants = new List<(string label, int[] seeds, Action<TerrainGenConfig> mutate)>
{
("faithful", smallSeeds, c => { c.CoastShelf = true; c.Offshore = OffshoreSettings.Faithful(); }),
("floor_only", smallSeeds, c => { c.CoastShelf = true; c.Offshore = HybridTuned(); c.Offshore.Organic = false; }),
("hybrid", hybridSeeds, c => { c.CoastShelf = true; c.Offshore = HybridTuned(); }),
("dense", smallSeeds, c => { c.CoastShelf = true; c.Offshore = HybridTuned();
c.Offshore.DensityNorth *= 3f; c.Offshore.DensitySouth *= 3f; }),
};
if (!string.IsNullOrWhiteSpace(only))
{
var keep = new HashSet<string>(only.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
variants.RemoveAll(v => !keep.Contains(v.label));
GD.Print($" ⚠ ISLA_ONLY: running {string.Join(", ", keep)} only (a probe, not the batch of record)");
}
GD.Print($"\n--- 2. VARIANTS at {mapSize} ---");
var offFields = new Dictionary<int, Pass1Result>(); // per seed, shelf off + offshore off
var rows = new List<Row>();
var perVariantChecks = new List<ShapingOracle.Check>();
var knobs = new Dictionary<string, string>();
bool notesShown = false;
foreach (var (label, seeds, mutate) in variants)
{
foreach (int seed in seeds)
{
if (!offFields.TryGetValue(seed, out Pass1Result p1Off))
{
p1Off = Topography.Generate(BaseConfig(mapSize, seed, knots, anchors, calibration, "off"));
offFields[seed] = p1Off;
}
var cfg = BaseConfig(mapSize, seed, knots, anchors, calibration, label);
mutate(cfg);
knobs[label] = cfg.Offshore.Describe();
Pass1Result p1 = Topography.Generate(cfg);
Pass2Result p2 = Shaping.Shape(p1, cfg);
if (!notesShown || label == "hybrid" && seed == primary)
foreach (string n in p1.Notes) GD.Print(" " + n);
notesShown = true;
// ---- oracle, per field ----
var comps = OffshoreAnalysis.Components(p1.IsOffshoreIsland, p1.Height, sea, mapSize);
var (cn, cs) = OffshoreAnalysis.CountByHemisphere(comps);
var checks = new List<ShapingOracle.Check>();
if (cfg.Offshore.FloorNorth > 0 || cfg.Offshore.FloorSouth > 0)
checks.Add(ShapingOracle.OffshoreFloor(p1, cfg.Offshore.FloorNorth, cfg.Offshore.FloorSouth, sea, comps));
checks.Add(ShapingOracle.MoatIntact(p1, comps));
checks.Add(ShapingOracle.MainlandUnmoved(p1Off, p1, sea));
checks.Add(ShapingOracle.TagCoastlineConsistent(p2, sea));
checks.Add(ShapingOracle.HMaxAfterOffshore(p1));
checks.Add(ShapingOracle.ClassifyFidelity(p1, p2));
foreach (var c in checks) { c.Name += $" [{label} {seed}]"; perVariantChecks.Add(c); }
bool ok = checks.TrueForAll(c => c.Passed);
WriteVariant(batchRoot, p1, p2, sea, anchors, skipRaw, cn, cs);
var cl = new List<string>();
foreach (var (cx, cy, _) in p1.OffshoreCentres) cl.Add($"({cx},{cy})");
rows.Add(new Row
{
Variant = label, Seed = seed, CountN = cn, CountS = cs,
Lifted = p1.OffshoreLiftedCells, HMaxBefore = p1.HMaxSeedBeforeOffshore, HMaxAfter = p1.HMaxSeed, Ok = ok,
Centres = cl.Count == 0 ? "—" : string.Join(" ", cl),
});
GD.Print($" {label,-11} seed {seed,-11} islands N {cn,2} S {cs,2} lifted {p1.OffshoreLiftedCells,8:N0} " +
$"hMax {p1.HMaxSeedBeforeOffshore:F4}→{p1.HMaxSeed:F4} {(ok ? "ok" : " CHECK FAILED")} {p1.ElapsedMs} ms");
}
}
bool allOk = hard.TrueForAll(c => c.Passed) && perVariantChecks.TrueForAll(c => c.Passed);
GD.Print($"\n ORACLE: {(allOk ? "ALL HARD CHECKS PASS" : "*** FAILURES ***")}");
foreach (var c in perVariantChecks) if (!c.Passed) GD.PrintErr(" " + c);
WriteIndex(batchRoot, mapSize, calibSize, primary, hybridSeeds, smallSeeds, rows, knobs, hard, perVariantChecks, allOk);
GD.Print("\n==================================================================");
GD.Print($" DONE — {batchRoot}");
GD.Print($" ORACLE {(allOk ? "HARD CHECKS ALL PASS" : "*** FAILURES see the table ***")}");
GD.Print("==================================================================");
GetTree().Quit(allOk ? 0 : 3);
}
// ---- the curve, measured exactly as tasks 03/04 did --------------------
private static (CurveKnots, ClimbCalibration) CalibrateCurve(int calibSize, float sea, CurveAnchors anchors)
{
var rawPool = new LandHistogram(sea);
var pass1 = new Dictionary<int, Pass1Result>();
foreach (int s in CalibrationSeeds)
{
var p1 = Topography.Generate(new TerrainGenConfig { MapSize = calibSize, Seed = s }); // offshore OFF by default
pass1[s] = p1;
rawPool.Accumulate(p1.Height, calibSize);
}
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]));
float ceilingRaw = knots.K2;
var rawAbove = new LandHistogram(sea);
var outAbove = new LandHistogram(sea);
foreach (int s in CalibrationSeeds)
{
var scfg = new TerrainGenConfig
{
MapSize = calibSize, Seed = s, Curve = true, ShelfDetail = true,
CurveMode = CurveModeKind.Staircase, Knots = knots, Anchors = anchors, VariantLabel = "staircase",
};
Pass2Result st = Shaping.Shape(pass1[s], scfg);
rawAbove.AccumulateWhere(pass1[s].Height, pass1[s].Height, calibSize, ceilingRaw);
outAbove.AccumulateWhere(st.Height, pass1[s].Height, calibSize, ceilingRaw);
}
var pcts = ClimbCalibration.DefaultPercentiles;
var rawQ = new float[pcts.Length];
var outQ = new float[pcts.Length];
for (int i = 0; i < pcts.Length; i++) { rawQ[i] = rawAbove.Quantile(pcts[i]); outQ[i] = outAbove.Quantile(pcts[i]); }
var cal = ClimbCalibration.FromPercentiles(pcts, rawQ, outQ, ceilingRaw,
HeightCurve.EffectiveSpikeMax(pass1[CalibrationSeeds[0]].HMaxSeed, knots, anchors),
anchors.RedCeil, anchors.PeakCap, mountainLift: 1.0f, peakSharpness: 1.0f);
return (knots, cal);
}
private static TerrainGenConfig BaseConfig(int mapSize, int seed, CurveKnots k, CurveAnchors a,
ClimbCalibration cal, string label) => new TerrainGenConfig
{
MapSize = mapSize, Seed = seed, VariantLabel = label,
Curve = true, ShelfDetail = false, CurveMode = CurveModeKind.Continuous,
Knots = k, Anchors = a, ClimbCalibration = cal, LowlandCeilingM = 30f,
CoastShelf = false, Offshore = new OffshoreSettings(), // OFF unless the variant turns it on
};
// ---- output -----------------------------------------------------------
private static void WriteVariant(string batchRoot, Pass1Result p1, Pass2Result p2, float sea,
CurveAnchors anchors, bool skipRaw, int countN, int countS)
{
string dir = Path.Combine(batchRoot, $"{p2.Seed}_{p2.VariantLabel}");
DirAccess.MakeDirRecursiveAbsolute(dir);
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()} {p2.Seed}")
.SavePng(Path.Combine(dir, "relief.png"));
// ⭐ The tag overlay — the one artifact that shows the DATA this pass set.
TagOverlayRenderer.SavePng(p2.Height, p2.IsOffshoreIsland, p2.IslandHemisphere, p2.MapSize, sea,
p1.OffshoreCentres, countN, countS, Path.Combine(dir, "tags.png"));
}
private static void WriteIndex(string batchRoot, int mapSize, int calibSize, int primary,
int[] hybridSeeds, int[] smallSeeds, List<Row> rows, Dictionary<string, string> knobs,
List<ShapingOracle.Check> hard, List<ShapingOracle.Check> perVariant, bool allOk)
{
var sb = new StringBuilder();
sb.AppendLine("# Batch 05 — offshore islands: the faithful rejoin, then the reshape");
sb.AppendLine();
sb.AppendLine("Stage 1 rejoins the reference's coast shelf + islets verbatim (`faithful`, the control).");
sb.AppendLine("Stage 2 reshapes: **small / low / flat / rigid**, a **seeded floor of ≥2 N / ≥4 S** islands");
sb.AppendLine("(min-separated, positions varying per seed, never fixed zones) plus **organic extras weighted");
sb.AppendLine("south**, corners and the outer edge allowed. Every tagged island cell carries a hemisphere.");
sb.AppendLine();
sb.AppendLine("## ⭐ Open this first");
sb.AppendLine();
sb.AppendLine($"1. **`{primary}_hybrid/tags.png`** — the tag overlay: grey mainland, cyan = island N, orange = island S,");
sb.AppendLine(" rings = the seeded floor. Count, N/S split and tag correctness in one glance.");
sb.AppendLine($"2. **`{primary}_hybrid/relief.png`** beside **`{primary}_faithful/relief.png`** — the reshape vs the reference.");
sb.AppendLine($"3. Then the other hybrid seeds' `tags.png` — the floor holds everywhere; the extras vary.");
sb.AppendLine();
sb.AppendLine($"**Hemisphere convention (from the code, not invented):** y runs SOUTH. NORTH = rows `[0, {mapSize / 2})`,");
sb.AppendLine($"SOUTH = rows `[{mapSize / 2}, {mapSize})`. The spine fades southward; the southern sinker bites the bottom 25 %;");
sb.AppendLine("snow-town north, shipwreck south. Component hemisphere is by centroid; the tag per cell is by row.");
sb.AppendLine();
sb.AppendLine("## ⭐ The count table — the floor, and the spread above it");
sb.AppendLine();
sb.AppendLine("| Variant | Seed | N | S | total | lifted cells | HMaxSeed before → after | oracle | seeded floor centres (x,y) |");
sb.AppendLine("|---|---|---|---|---|---|---|---|---|");
foreach (var r in rows)
sb.AppendLine($"| `{r.Variant}` | `{r.Seed}` | **{r.CountN}** | **{r.CountS}** | {r.CountN + r.CountS} | {r.Lifted:N0} | " +
$"{r.HMaxBefore:F4} → {r.HMaxAfter:F4}{(r.HMaxBefore != r.HMaxAfter ? " " : "")} | {(r.Ok ? "pass" : "**FAIL**")} | {r.Centres} |");
sb.AppendLine();
sb.AppendLine("`floor_only` / `hybrid` / `dense` carry a floor of **≥2 N / ≥4 S**; `faithful` has no floor (the");
sb.AppendLine("reference's probabilistic layer) and is the control.");
sb.AppendLine();
sb.AppendLine("## The knobs per variant");
sb.AppendLine();
sb.AppendLine("| Variant | settings |");
sb.AppendLine("|---|---|");
foreach (var kv in knobs) sb.AppendLine($"| `{kv.Key}` | {kv.Value} |");
sb.AppendLine();
sb.AppendLine("The coast shelf is ON for every variant (strength 0.775, scale 100 m, BitDecrement-clamped). It is");
sb.AppendLine("invisible on these hypsometric plates — ported faithfully, judged when water renders.");
sb.AppendLine();
sb.AppendLine("## ⚠ The palette is PROVISIONAL");
sb.AppendLine();
sb.AppendLine("`ProvisionalEven`, flagged. Grayscale + `tags.png` are the honest instruments here.");
sb.AppendLine();
sb.AppendLine("## The oracle");
sb.AppendLine();
sb.AppendLine("Regressions at the calibration size:");
sb.AppendLine();
sb.AppendLine(ShapingOracle.ToMarkdownTable(hard));
sb.AppendLine("Per variant × seed (floor h · moat i · mainland unmoved j · tag/coastline k · HMaxSeed l · classify b):");
sb.AppendLine();
sb.AppendLine(ShapingOracle.ToMarkdownTable(perVariant));
sb.AppendLine($"**{(allOk ? "ALL HARD CHECKS PASS" : " FAILURES do not judge this batch")}**");
sb.AppendLine();
sb.AppendLine("## Disposability");
sb.AppendLine();
sb.AppendLine("| Artifact | Keep? |");
sb.AppendLine("|---|---|");
sb.AppendLine("| `tags.png`, `relief.png`, `INDEX.md` | **keep** |");
sb.AppendLine("| `grayscale.png` | ♻ regenerable from the `.f32` |");
sb.AppendLine("| `height.f32` | ♻ regenerable from seed + code — large, clear freely |");
sb.AppendLine("| `scratch/` | persistent by rule; never cleaned |");
sb.AppendLine();
sb.AppendLine($"MapSize {mapSize}, curve calibrated at {calibSize} with offshore off. {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<int>();
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;
}
}
}