using System; using System.Collections.Generic; using System.IO; using System.Text; using Godot; using IslaApocalypse.Core; namespace IslaApocalypse.Tools { /// /// ⭐ THE SOUTHERN-STRETCH EXPLORATION BATCH (chat2/08) — map the fragmentation knob space: a /// peninsula → few-big-pieces → gravel ladder, 5 stretch levels × 2 seeds, with the region layer /// as the instrument. NOT a converged setting. /// /// ═══ TWO MODES ═══ /// /// ISLA_DIAG_ONLY=1 the DIAGNOSTIC (numbers, no plates): the three southern forces along a /// south-running profile (falloff blend / edge noise / sinker, each alone), /// the reach table for the candidate seeds, and a stretch sweep under both /// sinker modes — written to scratch/southern_diagnosis.md. Run first; it /// sets the ladder. /// (default) the BATCH: 5 levels × 2 seeds at ISLA_MAPSIZE, lean render per field /// (labeled-regions overlay + relief + .f32), the hemisphere-split count/size /// table, the asymmetric oracle. /// /// Every field: pass 1 + the stretch, region labeling ON, offshore OFF, shelf OFF, speck revert OFF /// — the pure fragmentation signal (the instrument counts "all islands" and "islands ≥ the 07 /// mid threshold" side by side). The curve is the tagged curve, unchanged. /// /// ═══ RUNNING IT ═══ /// /// xvfb-run -a Godot_v4.7.2-stable_mono_linux.x86_64 \ /// --path ~/celerNexus/islaApocalypse-v2 res://Tools/Scenes/SouthernStretchTool.tscn /// /// ISLA_TASK / ISLA_BATCH / ISLA_SKIP_RAW / ISLA_OUTPUT_DIR /// ISLA_MAPSIZE plate size (default 4096) /// ISLA_CALIB_SIZE curve calibration + diagnostic size (default 2048) /// ISLA_SEEDS the two batch seeds (default: auto — the two 07 seeds with the most southern mass) /// ISLA_CANDIDATE_SEEDS the pool the auto-pick reads (default: the 07 table seeds) /// ISLA_STRETCH_LEVELS the 5 stretch values (default: the diagnostic-chosen ladder below) /// ISLA_BAND_START / ISLA_BAND_FEATHER the fixed band (fractions of the map; constants for the batch) /// ISLA_STRETCH_SINKER 1 = the sinker rides the stretched distance (default), 0 = real y /// ISLA_DIAG_ONLY=1 diagnostic only /// public partial class SouthernStretchTool : Node { private static readonly int[] DefaultCandidateSeeds = { 1063685222, 20260821, 8675309, 123456789, 271828182, 999999937, 90210, 424242, }; /// ⚠ Task 01's pool, verbatim — the curve's identity. private static readonly int[] CalibrationSeeds = { 1063685222, 20260819, 777001, 424242, 90210, 31337 }; /// /// ⭐ THE LADDER — chosen by the diagnostic (chat2/08 report §1), not linear: the stretch /// bites unevenly, so the steps are spaced where the southern count/size actually moves. /// private static readonly float[] DefaultLadder = { 0.5f, 1.0f, 2.0f, 3.0f, 5.0f }; /// The diagnostic's sweep (both sinker modes). private static readonly float[] DiagSweep = { 0.25f, 0.5f, 1f, 1.5f, 2f, 3f, 5f, 8f, 16f }; 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(e.StackTrace); GD.PrintErr("=================================================================="); GetTree().Quit(2); } } private sealed class Row { public int Level; public float Stretch; public int Seed; public double ReachFrac, MedianCoastFrac; public long MainlandCells, MainlandSouthOfBand; public int SouthAll, SouthBig, NorthAll, NorthBig; public long SMin, SMed, SMax, NMax; public double SMean; public int[] SHist; public bool Ok; public ulong Ms; } 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", 8); string descr = EnvStr("ISLA_BATCH", "southern_stretch_explore"); int mapSize = EnvInt("ISLA_MAPSIZE", DefaultMapSize); int calibSize = EnvInt("ISLA_CALIB_SIZE", DefaultCalibSize); int[] candidates = EnvSeeds("ISLA_CANDIDATE_SEEDS", DefaultCandidateSeeds); int[] seedsEnv = EnvSeeds("ISLA_SEEDS", null); float[] ladder = EnvFloats("ISLA_STRETCH_LEVELS", DefaultLadder); float bandStart = EnvFloat("ISLA_BAND_START", SouthernStretch.DefaultBandStartFrac); float bandFeather = EnvFloat("ISLA_BAND_FEATHER", SouthernStretch.DefaultBandFeatherFrac); bool stretchSinker = EnvStr("ISLA_STRETCH_SINKER", SouthernStretch.DefaultStretchSinker ? "1" : "0") == "1"; bool diagOnly = EnvStr("ISLA_DIAG_ONLY", "0") == "1"; bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1"; string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "chat1/02_pass1_port"); string batchRoot = ToolingPaths.BatchRoot(task, descr); DirAccess.MakeDirRecursiveAbsolute(batchRoot); string scratch = ToolingPaths.BatchScratch(batchRoot); DirAccess.MakeDirRecursiveAbsolute(scratch); var anchors = CurveAnchors.Default; float sea = 0.15f; long bigCells4k = Cells(RegionPass.ThresholdMidFrac, mapSize); GD.Print("=================================================================="); GD.Print(" SOUTHERN STRETCH (chat2/08) — EXPLORATION: map the fragmentation knob space"); GD.Print("=================================================================="); GD.Print($"MapSize : {mapSize} (plates) calibration + diagnostic at {calibSize}"); GD.Print($"band : start {bandStart:F3} of the map (row {(int)(bandStart * mapSize)} at {mapSize}), feather {bandFeather:F3} — FIXED for the batch"); GD.Print($"sinker : {(stretchSinker ? "rides the stretched distance (held back inside the band)" : "real y (keeps pulling the extended mass down)")}"); GD.Print($"ladder : {string.Join(", ", ladder)}"); GD.Print($"fields : pass 1 + stretch · labeling ON · offshore OFF · shelf OFF · speck revert OFF (\"big\" island = ≥ {bigCells4k:N0} cells at {mapSize}, the 07 mid threshold)"); GD.Print($"batch : {batchRoot}{(diagOnly ? " ⚠ ISLA_DIAG_ONLY — the diagnostic, no plates" : "")}"); GD.Print("=================================================================="); // ═══ 0. THE CURVE ═══ GD.Print($"\n--- 0. CURVE (task-01 pool at {calibSize}, offshore off) ---"); var (knots, calibration) = CalibrateCurve(calibSize, sea, anchors); GD.Print($" {knots}"); TerrainGenConfig Cfg(int size, int seed, string label, float stretch, bool sinkerStretched, bool sinkerOn = true, bool edgeOn = true) { // ⭐ rivers/01 — FAMILY-OFF PINNED, not defaulted. This tool is chat-2 shaping DEVELOPMENT: // it was authored and judged before the shape family existed, and its regression checks // hold pass 1 against the FAMILY-OFF `02_pass1_port` dump. The re-baseline flipped the // bare defaults family-ON, so without this pin every config here would silently acquire // stretch + fragmentation and every anchor check would fail for a configuration reason. // → TerrainGenConfig.WithFamilyOff(). var c = new TerrainGenConfig { MapSize = size, Seed = seed, VariantLabel = label, Curve = true, ShelfDetail = false, CurveMode = CurveModeKind.Continuous, Knots = knots, Anchors = anchors, ClimbCalibration = calibration, LowlandCeilingM = 30f, RegionLabeling = true, SouthernSinker = sinkerOn, EdgeNoise = edgeOn, }.WithFamilyOff(); // …then the swept axis, AFTER the pin. Coastal fragmentation stays OFF here: chat2/08 // predates it, and this tool's ladder measures the stretch ALONE. c.SouthStretch = stretch; c.SouthBandStartFrac = bandStart; c.SouthBandFeatherFrac = bandFeather; c.StretchSinker = sinkerStretched; return c; } // ═══ 1. THE SEEDS — southern mass, measured ═══ GD.Print($"\n--- 1. SOUTHERN REACH of the candidate seeds at {calibSize} (offshore off, stretch off) ---"); var reachRows = new List<(int seed, double reach, double median, long southCells, long mainland, int southIslands)>(); int bandRowC = (int)(bandStart * calibSize); foreach (int s in candidates) { var p = Topography.Generate(Cfg(calibSize, s, "reach", 0f, stretchSinker)); var (reach, median, southCells) = Reach(p.Regions, calibSize, bandRowC); var (n, so) = RegionLabeling.IslandsByHemisphere(p.Regions); reachRows.Add((s, reach, median, southCells, p.Regions.Mainland.SizeCells, so)); GD.Print($" seed {s,-11} southernmost mainland row {reach:F3} of map, median coast {median:F3}, mainland cells south of band {southCells,9:N0} ({100.0 * southCells / p.Regions.Mainland.SizeCells:F1} % of mainland), natural S islands {so}"); } int[] seeds = seedsEnv; if (seeds == null) { reachRows.Sort((a, b) => b.southCells.CompareTo(a.southCells)); seeds = new[] { reachRows[0].seed, reachRows[1].seed }; } GD.Print($" → batch seeds: {seeds[0]}, {seeds[1]}{(seedsEnv == null ? " (auto: the two with the most mainland south of the band)" : " (ISLA_SEEDS)")}"); // ═══ 2. THE DIAGNOSTIC ═══ var diag = new StringBuilder(); diag.AppendLine("# The southern diagnosis — chat2/08 (measured by SouthernStretchTool, ISLA_DIAG_ONLY)"); diag.AppendLine(); diag.AppendLine($"Size {calibSize}. Band start {bandStart:F3} (row {bandRowC}), feather {bandFeather:F3}. y runs south; fractions are y / MapSize."); diag.AppendLine(); diag.AppendLine("## 1. Southern reach of the candidate seeds (offshore off, stretch off)"); diag.AppendLine(); diag.AppendLine("| seed | southernmost mainland row | median coast row (per column, central 60 %) | mainland cells south of band | % of mainland | natural S islands |"); diag.AppendLine("|---|---|---|---|---|---|"); foreach (var r in reachRows) diag.AppendLine($"| `{r.seed}` | {r.reach:F3} | {r.median:F3} | {r.southCells:N0} | {100.0 * r.southCells / r.mainland:F1} % | {r.southIslands} |"); diag.AppendLine(); diag.AppendLine($"**Batch seeds:** `{seeds[0]}`, `{seeds[1]}`."); diag.AppendLine(); if (diagOnly) { GD.Print($"\n--- 2. THE THREE FORCES along a south-running profile (central 20 % of x, averaged) ---"); foreach (int s in seeds) { var full = Topography.Generate(Cfg(calibSize, s, "full", 0f, stretchSinker)); var noSink = Topography.Generate(Cfg(calibSize, s, "nosink", 0f, stretchSinker, sinkerOn: false)); var bare = Topography.Generate(Cfg(calibSize, s, "bare", 0f, stretchSinker, sinkerOn: false, edgeOn: false)); diag.AppendLine($"## 2. The three forces — seed `{s}` (central 20 % of x averaged; falloff terms are PRE-power)"); diag.AppendLine(); diag.AppendLine("| y / N | blend (ellipse+squircle) | edge noise | sinker | total pre-trench | total^2.5 | mean height | land fraction of row |"); diag.AppendLine("|---|---|---|---|---|---|---|---|"); int x0 = (int)(calibSize * 0.40), x1 = (int)(calibSize * 0.60); for (int yi = 50; yi <= 100; yi += 2) { int y = Math.Min(calibSize - 1, yi * calibSize / 100); double sb = 0, sn = 0, sf = 0, sh = 0; long land = 0; int cnt = 0; for (int x = x0; x < x1; x++) { sb += bare.PreTrenchFalloff[x, y]; sn += noSink.PreTrenchFalloff[x, y]; sf += full.PreTrenchFalloff[x, y]; sh += full.Height[x, y]; if (full.Height[x, y] >= sea) land++; cnt++; } double blend = sb / cnt, edge = sn / cnt - blend, sink = sf / cnt - sn / cnt, total = sf / cnt; diag.AppendLine($"| {y / (double)calibSize:F2} | {blend:F3} | {edge:F3} | {sink:F3} | {total:F3} | {Math.Pow(Math.Max(0, total), 2.5):F3} | {sh / cnt:F3} | {land / (double)cnt:P0} |"); GD.Print($" seed {s} y {y / (double)calibSize:F2}: blend {blend:F3} edge {edge:F3} sinker {sink:F3} total {total:F3} pow {Math.Pow(Math.Max(0, total), 2.5):F3} height {sh / cnt:F3} land {land / (double)cnt:P0}"); } diag.AppendLine(); } GD.Print($"\n--- 3. THE STRETCH SWEEP (both sinker modes) ---"); diag.AppendLine("## 3. The stretch sweep — reach, southern islands (all / ≥ mid threshold), sizes, and the northern control"); diag.AppendLine(); long bigC = Cells(RegionPass.ThresholdMidFrac, calibSize); diag.AppendLine($"\"big\" = ≥ {bigC} cells at {calibSize} (the 07 `threshold_mid`). Reach = southernmost mainland row / N; median coast over the central 60 % of columns."); diag.AppendLine(); foreach (bool mode in new[] { false, true }) { diag.AppendLine($"### Sinker on {(mode ? "the STRETCHED distance (held back with the geometry)" : "the REAL y (unchanged)")}"); diag.AppendLine(); diag.AppendLine("| seed | stretch | reach | median coast | mainland cells south of band | S islands all / big | S size med / max | S largest island | N islands (control) |"); diag.AppendLine("|---|---|---|---|---|---|---|---|---|"); foreach (int s in seeds) { var sweep = new List { 0f }; sweep.AddRange(DiagSweep); foreach (float st in sweep) { var p = Topography.Generate(Cfg(calibSize, s, $"sweep_{st}", st, mode)); var (reach, median, southCells) = Reach(p.Regions, calibSize, bandRowC); var stats = Hemi(p.Regions, bigC); diag.AppendLine($"| `{s}` | {st:G3} | {reach:F3} | {median:F3} | {southCells:N0} | {stats.southAll} / {stats.southBig} | {stats.sMed} / {stats.sMax} | {stats.sMax} | {stats.northAll} |"); GD.Print($" sinker {(mode ? "stretched" : "real ")} seed {s,-11} stretch {st,5:G3} reach {reach:F3} median {median:F3} southCells {southCells,8:N0} S {stats.southAll,3}/{stats.southBig,3} med {stats.sMed,6} max {stats.sMax,7} N {stats.northAll}"); } } diag.AppendLine(); } WriteText(Path.Combine(scratch, "southern_diagnosis.md"), diag.ToString()); GD.Print($"\n diagnosis written: {Path.Combine(scratch, "southern_diagnosis.md")}"); GD.Print(" ISLA_DIAG_ONLY — done; no plates."); GetTree().Quit(0); return; } // ═══ 3. REGRESSIONS — north bit-identical to terrain-curve-v1; stretch-off bit-identical everywhere ═══ GD.Print($"\n--- 3. REGRESSIONS at {calibSize}, seed {seeds[0]} ---"); var hard = new List(); int plate = seeds[0]; float maxStretch = ladder[ladder.Length - 1]; { var offCfg = Cfg(calibSize, plate, "off", 0f, stretchSinker); Pass1Result p1 = Topography.Generate(offCfg); var curveOff = offCfg.Clone(); curveOff.Curve = false; // ⭐ a1 KEPT at rivers/01 — the family-off pass-1 guard (config pinned family-off). ⚠ loud. string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{plate}_full", "height.f32"); hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF, stretch OFF == Phase-1 .f32 dump", Shaping.Shape(p1, curveOff).Height, ShapingOracle.LoadAnchor("a1", "ISLA_PHASE1_SOURCE", p1Dump, calibSize), calibSize, p1Dump)); // ⭐ a3c KEPT and RE-POINTED — the north-locked invariant is the load-bearing claim of the // southern stretch (→ D-065) and it does NOT need an external anchor: the unstretched field // is generated right here. Re-pointing it off `03_mountain_restore` is what let that dump // retire without losing the guarantee. var topCfg = Cfg(calibSize, plate, "top", maxStretch, stretchSinker); Pass2Result pTop = Shaping.Shape(Topography.Generate(topCfg), topCfg); Pass2Result pUnstretched = Shaping.Shape(p1, offCfg); hard.Add(ShapingOracle.NorthLocked("a3c", $"stretch {maxStretch:G3} ON: north of the band bit-identical to the SAME-RUN unstretched field; changes only in/below the band", pTop.Height, pUnstretched.Height, calibSize, bandRowC)); foreach (var c in hard) GD.Print(" " + c); // ⚑ RETIRED at rivers/01 — a3 (`03_mountain_restore`) and a4b (`04_seed_gallery`). // a3 was a curve-development intermediate, subsumed by the terrain-shape-v1 acceptance. // a4b asserted the north-lock against the PRE-FAMILY 8192 gallery — but the north-lock is // now proven scale-free against a same-run field (a3c above), so the external anchor bought // nothing except an 8192 generation on every run. // The dump is NOT deleted (file-safety; regenerable, and the record of what was judged); // its `INDEX.md` is marked superseded. → XX_Human/output/rivers/01_*.report.md §A4. } // ═══ 4. THE LADDER — 5 levels × 2 seeds ═══ GD.Print($"\n--- 4. THE LADDER at {mapSize} ---"); int bandRow = (int)(bandStart * mapSize); var rows = new List(); var perField = new List(); var baselineRows = new Dictionary(); foreach (int seed in seeds) { var c0 = Cfg(mapSize, seed, "stretch_0", 0f, stretchSinker); Pass1Result p0 = Topography.Generate(c0); Pass2Result q0 = Shaping.Shape(p0, c0); baselineRows[seed] = MakeRow(0, 0f, seed, p0, mapSize, bandRow, bigCells4k, true, p0.ElapsedMs); var r0 = baselineRows[seed]; GD.Print($" seed {seed} baseline (stretch 0): reach {r0.ReachFrac:F3}, S islands {r0.SouthAll} / big {r0.SouthBig}, N islands {r0.NorthAll}"); for (int li = 0; li < ladder.Length; li++) { float st = ladder[li]; string label = $"stretch_{li + 1}"; var cfg = Cfg(mapSize, seed, label, st, stretchSinker); Pass1Result p1 = Topography.Generate(cfg); Pass2Result p2 = Shaping.Shape(p1, cfg); var checks = new List { ShapingOracle.NorthLocked("p", "north of the band bit-locked (classify) vs stretch 0", p1.Height, p0.Height, mapSize, bandRow), ShapingOracle.NorthLocked("p2", "north of the band bit-locked (render) vs stretch 0", p2.Height, q0.Height, mapSize, bandRow), ShapingOracle.NorthIslandsInvariant("northern islands invariant vs stretch 0", p0.Regions, p1.Regions), ShapingOracle.CentreIsLand(p1), ShapingOracle.TagCoastlineConsistent(p2, sea), ShapingOracle.ClassifyFidelity(p1, p2), }; foreach (var c in checks) { c.Name += $" [{label} = {st:G3}, {seed}]"; perField.Add(c); } bool ok = checks.TrueForAll(c => c.Passed); var row = MakeRow(li + 1, st, seed, p1, mapSize, bandRow, bigCells4k, ok, p1.ElapsedMs); rows.Add(row); WriteField(batchRoot, p1, p2, sea, anchors, skipRaw, st); GD.Print($" {label,-10} {st,5:G3} seed {seed,-11} reach {row.ReachFrac:F3} (median coast {row.MedianCoastFrac:F3}) mainland S-of-band {row.MainlandSouthOfBand,9:N0} " + $"S islands {row.SouthAll,3} / big {row.SouthBig,3} S size med {row.SMed,6} max {row.SMax,7} N islands {row.NorthAll,3} {(ok ? "ok" : "⚠ CHECK FAILED")} {p1.ElapsedMs} ms"); } } // determinism: the middle level on the first seed, twice { float st = ladder[ladder.Length / 2]; var cA = Cfg(mapSize, plate, "det", st, stretchSinker); var cB = Cfg(mapSize, plate, "det", st, stretchSinker); var a = Topography.Generate(cA); var b = Topography.Generate(cB); var det = ShapingOracle.LabelsDeterministic(a, b); det.Name += $" [stretch {st:G3}, {plate}]"; var bits = ShapingOracle.NorthLocked("o2", $"two generations bit-identical everywhere [stretch {st:G3}, {plate}]", a.Height, b.Height, mapSize, mapSize); perField.Add(det); perField.Add(bits); GD.Print(" " + det); GD.Print(" " + bits); } bool allOk = hard.TrueForAll(c => c.Passed) && perField.TrueForAll(c => c.Passed); GD.Print($"\n ORACLE: {(allOk ? "ALL HARD CHECKS PASS" : "*** FAILURES ***")}"); foreach (var c in perField) if (!c.Passed) GD.PrintErr(" " + c); WriteTable(batchRoot, mapSize, ladder, seeds, rows, baselineRows, bigCells4k, bandStart, bandFeather, stretchSinker); WriteIndex(batchRoot, mapSize, calibSize, ladder, seeds, rows, baselineRows, bigCells4k, bandStart, bandFeather, stretchSinker, hard, perField, allOk, diag.ToString()); 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 instrument -------------------------------------------------- private static long Cells(float frac, int size) => Math.Max(1L, (long)Math.Round(frac * (double)size * size)); /// Southernmost mainland row / N, the median per-column coast row (central 60 % of columns) / N, and mainland cells at/below the band row. private static (double reach, double median, long southCells) Reach(RegionLabels l, int n, int bandRow) { int main = l.MainlandId; int maxY = -1; long south = 0; var coast = new List(); int x0 = (int)(n * 0.2), x1 = (int)(n * 0.8); for (int x = 0; x < n; x++) { int colMax = -1; for (int y = 0; y < n; y++) { if (l.Id[x * n + y] != main) continue; if (y > colMax) colMax = y; if (y >= bandRow) south++; } if (colMax > maxY) maxY = colMax; if (x >= x0 && x < x1 && colMax >= 0) coast.Add(colMax); } coast.Sort(); double median = coast.Count == 0 ? 0 : coast[coast.Count / 2] / (double)n; return (maxY / (double)n, median, south); } private static (int southAll, int southBig, int northAll, int northBig, long sMin, long sMed, double sMean, long sMax, long nMax, int[] sHist) Hemi(RegionLabels l, long big) { var south = new List(); int northAll = 0, northBig = 0; long nMax = 0; foreach (var r in l.Regions) { if (r.IsMainland) continue; if (r.Hemisphere == RegionLabeling.HemiSouth) south.Add(r.SizeCells); else { northAll++; if (r.SizeCells >= big) northBig++; nMax = Math.Max(nMax, r.SizeCells); } } south.Sort(); var hist = new int[RegionLabeling.HistogramEdges.Length + 1]; int sBig = 0; double sum = 0; foreach (long s in south) { hist[RegionLabeling.HistogramBin(s)]++; if (s >= big) sBig++; sum += s; } return (south.Count, sBig, northAll, northBig, south.Count == 0 ? 0 : south[0], south.Count == 0 ? 0 : south[south.Count / 2], south.Count == 0 ? 0 : sum / south.Count, south.Count == 0 ? 0 : south[south.Count - 1], nMax, hist); } private static Row MakeRow(int level, float st, int seed, Pass1Result p1, int mapSize, int bandRow, long big, bool ok, ulong ms) { var (reach, median, southCells) = Reach(p1.Regions, mapSize, bandRow); var h = Hemi(p1.Regions, big); return new Row { Level = level, Stretch = st, Seed = seed, ReachFrac = reach, MedianCoastFrac = median, MainlandCells = p1.Regions.Mainland.SizeCells, MainlandSouthOfBand = southCells, SouthAll = h.southAll, SouthBig = h.southBig, NorthAll = h.northAll, NorthBig = h.northBig, SMin = h.sMin, SMed = h.sMed, SMean = h.sMean, SMax = h.sMax, NMax = h.nMax, SHist = h.sHist, Ok = ok, Ms = ms, }; } // ---- the curve -------------------------------------------------------- private static (CurveKnots, ClimbCalibration) CalibrateCurve(int calibSize, float sea, CurveAnchors anchors) { var rawPool = new LandHistogram(sea); var pass1 = new Dictionary(); foreach (int s in CalibrationSeeds) { var p1 = Topography.Generate(TerrainGenConfig.CalibrationPool(calibSize, s)); // family-off PINNED (rivers/01), not defaulted 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) { // ⭐ rivers/01: family-off PINNED, like the pool it shapes. (The family acts in pass 1 and // `Shaping.Shape` never reads it, so this is inert today — pinned anyway so "the whole // calibration is family-off" is a total claim rather than a field-by-field one.) var scfg = new TerrainGenConfig { MapSize = calibSize, Seed = s, Curve = true, ShelfDetail = true, CurveMode = CurveModeKind.Staircase, Knots = knots, Anchors = anchors, VariantLabel = "staircase", }.WithFamilyOff(); 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); } // ---- output ----------------------------------------------------------- private static void WriteField(string batchRoot, Pass1Result p1, Pass2Result p2, float sea, CurveAnchors anchors, bool skipRaw, float stretch) { string dir = Path.Combine(batchRoot, $"{p2.Seed}_{p2.VariantLabel}"); DirAccess.MakeDirRecursiveAbsolute(dir); 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()} ({stretch:G3}) {p2.Seed}") .SavePng(Path.Combine(dir, "relief.png")); RegionOverlayRenderer.SavePng(p1.Regions, null, p1.MapSize, 0, 0, Path.Combine(dir, "regions.png")); } private static string HistRow(int[] h) { if (h == null) return "—"; var sb = new StringBuilder(); for (int i = 0; i < h.Length; i++) { if (i > 0) sb.Append(" · "); sb.Append(h[i]); } return sb.ToString(); } private static string TableMarkdown(float[] ladder, int[] seeds, List rows, Dictionary baseline, long big) { var sb = new StringBuilder(); var histHead = new StringBuilder(); for (int i = 0; i <= RegionLabeling.HistogramEdges.Length; i++) { if (i > 0) histHead.Append(" · "); histHead.Append(RegionLabeling.HistogramLabel(i)); } sb.AppendLine($"| Level | stretch | Seed | reach (southernmost mainland row / N) | median coast / N | mainland cells south of band | **SOUTH islands: all / ≥ {big:N0} cells** | **S size min / med / mean / max** | S histogram ({histHead}) | **NORTH islands (control)** | N largest | oracle |"); sb.AppendLine("|---|---|---|---|---|---|---|---|---|---|---|---|"); foreach (int seed in seeds) { var b = baseline[seed]; sb.AppendLine($"| *baseline* | 0 | `{seed}` | {b.ReachFrac:F3} | {b.MedianCoastFrac:F3} | {b.MainlandSouthOfBand:N0} | **{b.SouthAll} / {b.SouthBig}** | **{b.SMin} / {b.SMed} / {b.SMean:F0} / {b.SMax}** | {HistRow(b.SHist)} | **{b.NorthAll}** | {b.NMax} | — |"); foreach (var r in rows) { if (r.Seed != seed) continue; sb.AppendLine($"| `stretch_{r.Level}` | {r.Stretch:G3} | `{seed}` | {r.ReachFrac:F3} | {r.MedianCoastFrac:F3} | {r.MainlandSouthOfBand:N0} | **{r.SouthAll} / {r.SouthBig}** | **{r.SMin} / {r.SMed} / {r.SMean:F0} / {r.SMax}** | {HistRow(r.SHist)} | **{r.NorthAll}**{(r.NorthAll != b.NorthAll ? " ⚠ MOVED" : "")} | {r.NMax} | {(r.Ok ? "pass" : "**FAIL**")} |"); } } return sb.ToString(); } private static void WriteTable(string batchRoot, int mapSize, float[] ladder, int[] seeds, List rows, Dictionary baseline, long big, float bandStart, float bandFeather, bool stretchSinker) { var sb = new StringBuilder(); sb.AppendLine($"# The hemisphere-split count/size table — {ladder.Length} stretch levels × {seeds.Length} seeds at {mapSize}"); sb.AppendLine(); sb.AppendLine($"Band start {bandStart:F3} (row {(int)(bandStart * mapSize)}), feather {bandFeather:F3} — FIXED. Sinker {(stretchSinker ? "rides the stretched distance" : "on the real y")}."); sb.AppendLine("Islands = non-mainland 8-connected land components of the classify field (region layer); hemisphere by centroid. Offshore OFF, speck revert OFF."); sb.AppendLine("SOUTH = the fragmentation signal; NORTH = the should-stay-flat control (flagged if it moves)."); sb.AppendLine(); sb.Append(TableMarkdown(ladder, seeds, rows, baseline, big)); WriteText(Path.Combine(batchRoot, "count_size_table.md"), sb.ToString()); var csv = new StringBuilder(); csv.AppendLine("level,stretch,seed,reach,median_coast,mainland_cells,mainland_south_of_band,south_all,south_big,s_min,s_median,s_mean,s_max,s_hist,north_all,north_big,n_max,oracle,ms"); var ic = System.Globalization.CultureInfo.InvariantCulture; foreach (int seed in seeds) { var all = new List { baseline[seed] }; all.AddRange(rows.FindAll(r => r.Seed == seed)); foreach (var r in all) csv.AppendLine(string.Join(",", r.Level, r.Stretch.ToString("G5", ic), r.Seed, r.ReachFrac.ToString("F4", ic), r.MedianCoastFrac.ToString("F4", ic), r.MainlandCells, r.MainlandSouthOfBand, r.SouthAll, r.SouthBig, r.SMin, r.SMed, r.SMean.ToString("F1", ic), r.SMax, "\"" + HistRow(r.SHist) + "\"", r.NorthAll, r.NorthBig, r.NMax, r.Ok ? "pass" : "FAIL", r.Ms)); } WriteText(Path.Combine(batchRoot, "count_size_table.csv"), csv.ToString()); } private static void WriteIndex(string batchRoot, int mapSize, int calibSize, float[] ladder, int[] seeds, List rows, Dictionary baseline, long big, float bandStart, float bandFeather, bool stretchSinker, List hard, List perField, bool allOk, string diagSummary) { int midLevel = ladder.Length / 2 + 1; var sb = new StringBuilder(); sb.AppendLine("# Batch 08 — southern stretch, EXPLORATION: the fragmentation knob space"); sb.AppendLine(); sb.AppendLine("**A ladder, not a setting.** Inside a FIXED feathered latitude band the falloff's southward distance is compressed"); sb.AppendLine("(`y' = yB + (y − yB) / (1 + stretch · ramp)`), so the mainland reaches further south with the elevation of the rows it came"); sb.AppendLine("from, and where the stretched thin edge thins below sea it fragments organically. North of the band the classify field is"); sb.AppendLine("bit-locked in both directions (asserted). Nothing is stamped. The region layer is the instrument: SOUTH island count/size"); sb.AppendLine("is the fragmentation signal, NORTH is the should-stay-flat control."); sb.AppendLine(); sb.AppendLine("## ⭐ Open this first"); sb.AppendLine(); sb.AppendLine($"1. **`{seeds[0]}_stretch_{midLevel}/regions.png`** — the middle of the ladder on the first seed: grey mainland, each island its own colour."); sb.AppendLine($"2. Walk the ladder on that seed: `{seeds[0]}_stretch_1/` … `_stretch_{ladder.Length}/` (`regions.png` beside `relief.png`)."); sb.AppendLine($"3. Then the same five on `{seeds[1]}` — what repeats is the knob; what does not is the seed."); sb.AppendLine("4. Then the table: southern count/size down the rows, the northern control beside it."); sb.AppendLine(); sb.AppendLine("## The fixed frame and the axis"); sb.AppendLine(); sb.AppendLine($"- **Band (constant for the batch):** start `{bandStart:F3}` of the map (row {(int)(bandStart * mapSize)} at {mapSize}), feather `{bandFeather:F3}` (smoothstep). Sea identity is hard above it; ramped across; extended below."); sb.AppendLine($"- **Sinker:** {(stretchSinker ? "rides the stretched distance (pushed out with the geometry — held back inside the band)" : "on the real y (keeps pulling the extended mass down where it always did)")}."); sb.AppendLine($"- **The axis — stretch strength:** {string.Join(" · ", Array.ConvertAll(ladder, v => v.ToString("G3")))} (levels 1–{ladder.Length}); baseline 0 measured for the control."); sb.AppendLine($"- Every field: pass 1 + stretch, region labeling ON, offshore OFF, shelf OFF, speck revert OFF. \"big\" island = ≥ {big:N0} cells at {mapSize} (the 07 `threshold_mid`)."); sb.AppendLine(); sb.AppendLine($"## ⭐ The hemisphere-split count/size table — {ladder.Length} levels × {seeds.Length} seeds at {mapSize}"); sb.AppendLine(); sb.Append(TableMarkdown(ladder, seeds, rows, baseline, big)); sb.AppendLine(); sb.AppendLine("Also as plain data: `count_size_table.md` / `.csv`."); sb.AppendLine(); sb.AppendLine("## The diagnosis (summary — full tables in `scratch/southern_diagnosis.md`)"); sb.AppendLine(); sb.Append(diagSummary); sb.AppendLine(); sb.AppendLine("## ⚠ The palette is PROVISIONAL"); sb.AppendLine(); sb.AppendLine("`ProvisionalEven`, flagged. The individually-coloured scheme is only the `regions.png` overlay."); sb.AppendLine(); sb.AppendLine("## The oracle (asymmetric)"); sb.AppendLine(); sb.AppendLine("Regressions (stretch OFF bit-identical everywhere; stretch ON at the ladder's top bit-identical NORTH OF THE BAND to the `terrain-curve-v1` dumps):"); sb.AppendLine(); sb.AppendLine(ShapingOracle.ToMarkdownTable(hard)); sb.AppendLine("Per field (north bit-locked classify p / render p2 · northern islands invariant q · centre-is-land m · tag/coastline k · classify b · determinism o):"); sb.AppendLine(); sb.AppendLine(ShapingOracle.ToMarkdownTable(perField)); 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("| `regions.png`, `relief.png`, `INDEX.md`, `count_size_table.md` / `.csv`, `scratch/southern_diagnosis.md` | **keep** |"); sb.AppendLine("| `height.f32` | ♻ regenerable from seed + code — large, clear freely |"); sb.AppendLine("| `scratch/` | persistent by rule; never cleaned |"); sb.AppendLine(); sb.AppendLine($"Plates at {mapSize}, curve calibrated at {calibSize} with offshore off. {WorldScale.Describe()}."); WriteText(Path.Combine(batchRoot, "INDEX.md"), sb.ToString()); } private static void WriteText(string path, string text) { using var f = Godot.FileAccess.Open(path, Godot.FileAccess.ModeFlags.Write); if (f == null) { GD.PrintErr($"could not write {path}"); return; } f.StoreString(text); } // ---- 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 float[] EnvFloats(string k, float[] fallback) { string v = EnvStr(k, null); if (v == null) return fallback; var outp = new List(); foreach (string part in v.Split(',', StringSplitOptions.RemoveEmptyEntries)) if (float.TryParse(part.Trim(), System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float f)) outp.Add(f); return outp.Count > 0 ? outp.ToArray() : 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; } } }