using System; using System.Collections.Generic; using System.IO; using System.Text; using Godot; using IslaApocalypse.Core; namespace IslaApocalypse.Tools { /// /// ⭐ THE REGION-LABELING BATCH (chat2/07) — the general region layer on the current terrain, the /// island tag fixed by construction, and the tunable speck revert swept. /// /// ═══ WHAT IT PRODUCES — a fixed budget: 4 plates + the count/size table ═══ /// /// PLATES (4 fields, each grayscale + .f32 + relief + the LABELED-REGIONS overlay + the tag overlay): /// {plate}_threshold_low / _mid / _high three revert thresholds on ONE seed — the developer /// dials "where too-small-to-keep sits" by eye. /// {second}_threshold_mid the preset on a second seed — the table seed with the /// most NATURAL islands (offshore off), auto-picked or /// ISLA_SECOND_SEED — labeling + threshold are not /// seed-specific; the big organic masses tag correctly. /// /// THE COUNT/SIZE TABLE (data): per seed, natural islands (offshore off), pre-revert islands, and /// post-revert islands at each threshold, with size min / median / mean / max and a log-spaced /// size histogram — the instrument the later southern-stretch step tunes against. /// /// Every "current terrain" field = shelf ON + the chat2/06 organic preset (`density_mid`) + region /// labeling ON; the revert is the variable. 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/RegionLabelingTool.tscn /// /// ISLA_TASK / ISLA_BATCH / ISLA_SKIP_RAW / ISLA_OUTPUT_DIR /// ISLA_MAPSIZE plate + table size (default 4096) /// ISLA_TABLE_SIZE count-table size (default = ISLA_MAPSIZE; a probe may drop it) /// ISLA_CALIB_SIZE curve calibration size (default 2048, task 01's) /// ISLA_TABLE_SEEDS the table seeds (default 8 below) /// ISLA_PLATE_SEED the three-threshold seed (default 1063685222) /// ISLA_SECOND_SEED the second plate seed (default 0 = auto: most natural islands) /// ISLA_THR_LOW / ISLA_THR_MID / ISLA_THR_HIGH thresholds, fraction of map area (probe overrides) /// ISLA_TABLE_ONLY=1 probe: table only (no regressions, no plates) /// ISLA_PHASE1_SOURCE the Phase-1 regression dump's batch (default "chat1/02_pass1_port") /// public partial class RegionLabelingTool : Node { private static readonly int[] DefaultTableSeeds = { 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 }; 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 Level { public string Label; public float Frac; } private sealed class Row { public string Level; public int Seed; public long ThresholdCells; public int Natural, NaturalN, NaturalS; // offshore OFF, revert OFF public int Pre, PreN, PreS; // offshore ON, revert OFF public int Post, PostN, PostS; // offshore ON, revert at this level public int RevertedComps; public long RevertedCells; public long PreMin, PreMed, PreMax, PostMin, PostMed, PostMax; public double PreMean, PostMean; public int[] PreHist, PostHist; public long MainlandCells; 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", 7); string descr = EnvStr("ISLA_BATCH", "region_labeling"); int mapSize = EnvInt("ISLA_MAPSIZE", DefaultMapSize); int tableSize = EnvInt("ISLA_TABLE_SIZE", mapSize); int calibSize = EnvInt("ISLA_CALIB_SIZE", DefaultCalibSize); int[] tableSeeds = EnvSeeds("ISLA_TABLE_SEEDS", DefaultTableSeeds); int plateSeed = EnvInt("ISLA_PLATE_SEED", 1063685222); int secondEnv = EnvInt("ISLA_SECOND_SEED", 0); string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "chat1/02_pass1_port"); bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1"; bool tableOnly = EnvStr("ISLA_TABLE_ONLY", "0") == "1"; var levels = new List { new() { Label = "threshold_low", Frac = EnvFloat("ISLA_THR_LOW", RegionPass.ThresholdLowFrac) }, new() { Label = "threshold_mid", Frac = EnvFloat("ISLA_THR_MID", RegionPass.ThresholdMidFrac) }, new() { Label = "threshold_high", Frac = EnvFloat("ISLA_THR_HIGH", RegionPass.ThresholdHighFrac) }, }; Level mid = levels[1]; string batchRoot = ToolingPaths.BatchRoot(task, descr); DirAccess.MakeDirRecursiveAbsolute(batchRoot); DirAccess.MakeDirRecursiveAbsolute(ToolingPaths.BatchScratch(batchRoot)); var anchors = CurveAnchors.Default; float sea = 0.15f; GD.Print("=================================================================="); GD.Print(" REGION LABELING (chat2/07) — label all land, fix the tag, tunable speck revert"); GD.Print("=================================================================="); GD.Print($"MapSize : {mapSize} (plates) table at {tableSize} curve calibrated at {calibSize} (offshore OFF)"); GD.Print($"table : {string.Join(", ", tableSeeds)}"); GD.Print($"plate seed: {plateSeed} second seed: {(secondEnv > 0 ? secondEnv.ToString() : "auto (most natural islands)")}"); foreach (var l in levels) GD.Print($" {l.Label,-15} {l.Frac:G3} of map area = {Cells(l.Frac, mapSize):N0} cells at {mapSize} ({Cells(l.Frac, tableSize):N0} at {tableSize})"); GD.Print($"terrain : shelf ON + offshore {OffshoreSettings.Organic().Describe()}"); GD.Print($"contract : classify field · land 8-connected · mainland = centre component · id/size/centroid/hemisphere(centroid)/isMainland"); GD.Print($"batch : {batchRoot}{(tableOnly ? " ⚠ ISLA_TABLE_ONLY — a probe, not the batch of record" : "")}"); 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}"); GD.Print($" {calibration.Describe()}"); TerrainGenConfig Cfg(int size, int seed, string label, bool offshoreOn, bool revertOn, float frac) { // ⭐ 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 = BaseConfig(size, seed, knots, anchors, calibration, label).WithFamilyOff(); // …then this tool's own axes, AFTER the pin (the pin would otherwise clear them). if (offshoreOn) { c.CoastShelf = true; c.Offshore = OffshoreSettings.Organic(); } c.RegionLabeling = true; c.SpeckRevert = revertOn; c.MinLandComponentFrac = frac; return c; } // ═══ 1. REGRESSIONS ═══ var hard = new List(); if (!tableOnly) { GD.Print($"\n--- 1. REGRESSIONS at {calibSize}, seed {plateSeed} ---"); var offCfg = Cfg(calibSize, plateSeed, "off", offshoreOn: false, revertOn: false, mid.Frac); Pass1Result p1 = Topography.Generate(offCfg); var curveOff = offCfg.Clone(); curveOff.Curve = false; Pass2Result pOff = Shaping.Shape(p1, curveOff); // ⭐ a1 KEPT at rivers/01 — the family-off pass-1 guard (config pinned family-off). ⚠ loud. string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{plateSeed}_full", "height.f32"); hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF, offshore OFF, revert OFF (labeling on) == Phase-1 .f32 dump", pOff.Height, ShapingOracle.LoadAnchor("a1", "ISLA_PHASE1_SOURCE", p1Dump, calibSize), calibSize, p1Dump)); // ⚑ RETIRED at rivers/01 — a3 and a3r, both against `03_mountain_restore`. // A curve-development intermediate, subsumed by the `terrain-shape-v1` acceptance (a10). // a3r was informational only, and its subject — how many natural specks the revert takes — // is now reported by the region ledger every run, with the revert ON by default. // 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. 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.CentreIsLand(p1)); foreach (var c in hard) GD.Print(" " + c); // ⚑ RETIRED at rivers/01 — a4 (`04_seed_gallery`) and a6 (`06_offshore_organic_tune`). // a4 held the PRE-FAMILY committed curve; the locked shape is family-ON, and a10 is the // 8192 acceptance now. a6 held the ORGANIC ISLET layer — the DROPPED mechanism (→ D-063): // islands are organic-only, made by the stretch + fragmentation and identified here, never // placed. An oracle pinning islet output is an oracle defending a design that was reversed. // 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. } // ═══ 2. DETERMINISM ═══ GD.Print($"\n--- 2. DETERMINISM at {tableSize}, seed {plateSeed}, {mid.Label} ---"); var perFieldChecks = new List(); { var cA = Cfg(tableSize, plateSeed, mid.Label, true, true, mid.Frac); var cB = Cfg(tableSize, plateSeed, mid.Label, true, true, mid.Frac); var det = ShapingOracle.LabelsDeterministic(Topography.Generate(cA), Topography.Generate(cB)); det.Name += $" [{plateSeed}]"; perFieldChecks.Add(det); GD.Print(" " + det); } // ═══ 3. THE COUNT/SIZE TABLE ═══ GD.Print($"\n--- 3. COUNT/SIZE TABLE at {tableSize} ---"); var rows = new List(); var naturalCount = new Dictionary(); bool notesShown = false; foreach (int seed in tableSeeds) { // natural: offshore OFF, revert OFF Pass1Result pNat = Topography.Generate(Cfg(tableSize, seed, "natural", false, false, mid.Frac)); var (natN, natS) = RegionLabeling.IslandsByHemisphere(pNat.Regions); naturalCount[seed] = pNat.Regions.IslandCount; // pre: offshore ON, revert OFF var cPre = Cfg(tableSize, seed, "pre", true, false, mid.Frac); Pass1Result pPre = Topography.Generate(cPre); { var cj = ShapingOracle.MainlandUnmoved(pNat, pPre, sea); cj.Name += $" [offshore on vs off, {seed}]"; perFieldChecks.Add(cj); var cm = ShapingOracle.CentreIsLand(pPre); cm.Name += $" [pre {seed}]"; perFieldChecks.Add(cm); } if (!notesShown) { foreach (string n in pPre.Notes) GD.Print(" " + n); } GD.Print($" seed {seed,-11} natural islands {pNat.Regions.IslandCount,3} (N {natN} / S {natS}) pre-revert {pPre.Regions.IslandCount,3} (N {pPre.RegionLedger.PostNorth} / S {pPre.RegionLedger.PostSouth}) mainland {pPre.Regions.Mainland.SizeCells:N0} cells"); foreach (var lv in levels) { var cfg = Cfg(tableSize, seed, lv.Label, true, true, lv.Frac); Pass1Result p1 = Topography.Generate(cfg); Pass2Result p2 = Shaping.Shape(p1, cfg); if (!notesShown) { foreach (string n in p1.Notes) if (n.StartsWith("[Regions]")) GD.Print(" " + n); notesShown = true; } var led = p1.RegionLedger; long thr = led.ThresholdCells; var comps = OffshoreAnalysis.Components(p1.IsIsland, p1.Height, sea, tableSize); var checks = new List { ShapingOracle.CentreIsLand(p1), ShapingOracle.RevertGuards(pPre, p1, sea, thr), ShapingOracle.MoatIntact(p1, comps), ShapingOracle.TagCoastlineConsistent(p2, sea), ShapingOracle.HMaxAfterOffshore(p1), ShapingOracle.ClassifyFidelity(p1, p2), }; foreach (var c in checks) { c.Name += $" [{lv.Label} {seed}]"; perFieldChecks.Add(c); } bool ok = checks.TrueForAll(c => c.Passed); var row = new Row { Level = lv.Label, Seed = seed, ThresholdCells = thr, Natural = pNat.Regions.IslandCount, NaturalN = natN, NaturalS = natS, Pre = led.PreIslands, PreN = led.PreNorth, PreS = led.PreSouth, Post = led.PostIslands, PostN = led.PostNorth, PostS = led.PostSouth, RevertedComps = led.RevertedComponents, RevertedCells = led.RevertedCells, PreMin = led.PreMin, PreMed = led.PreMedian, PreMean = led.PreMean, PreMax = led.PreMax, PostMin = led.PostMin, PostMed = led.PostMedian, PostMean = led.PostMean, PostMax = led.PostMax, PreHist = led.PreHistogram, PostHist = led.PostHistogram, MainlandCells = p1.Regions.Mainland.SizeCells, Ok = ok, Ms = p1.ElapsedMs, }; rows.Add(row); GD.Print($" {lv.Label,-15} seed {seed,-11} thr {thr,5} pre {row.Pre,3} → post {row.Post,3} (N {row.PostN,2} / S {row.PostS,2}) reverted {row.RevertedComps,3} comps / {row.RevertedCells,7:N0} cells " + $"post size min {row.PostMin,5} med {row.PostMed,5} max {row.PostMax,6} {(ok ? "ok" : "⚠ CHECK FAILED")} {p1.ElapsedMs} ms"); } } // ═══ 4. THE PLATES ═══ int secondSeed = secondEnv > 0 ? secondEnv : PickSecondSeed(naturalCount, tableSeeds, plateSeed); GD.Print($"\n second seed: {secondSeed}{(secondEnv > 0 ? " (ISLA_SECOND_SEED)" : $" (auto: most natural islands among the table seeds — {naturalCount.GetValueOrDefault(secondSeed)})")}"); var plateRows = new List(); if (!tableOnly) { GD.Print($"\n--- 4. PLATES at {mapSize} ---"); var plates = new List<(int seed, Level lv)> { (plateSeed, levels[0]), (plateSeed, levels[1]), (plateSeed, levels[2]), (secondSeed, mid) }; foreach (var (seed, lv) in plates) { var cfg = Cfg(mapSize, seed, lv.Label, true, true, lv.Frac); Pass1Result p1 = Topography.Generate(cfg); Pass2Result p2 = Shaping.Shape(p1, cfg); var led = p1.RegionLedger; var cm = ShapingOracle.CentreIsLand(p1); cm.Name += $" [plate {lv.Label} {seed}]"; var ck = ShapingOracle.TagCoastlineConsistent(p2, sea); ck.Name += $" [plate {lv.Label} {seed}]"; perFieldChecks.Add(cm); perFieldChecks.Add(ck); WritePlate(batchRoot, p1, p2, sea, anchors, skipRaw); plateRows.Add(new Row { Level = lv.Label, Seed = seed, ThresholdCells = led.ThresholdCells, Pre = led.PreIslands, PreN = led.PreNorth, PreS = led.PreSouth, Post = led.PostIslands, PostN = led.PostNorth, PostS = led.PostSouth, RevertedComps = led.RevertedComponents, RevertedCells = led.RevertedCells, PostMin = led.PostMin, PostMed = led.PostMedian, PostMean = led.PostMean, PostMax = led.PostMax, MainlandCells = p1.Regions.Mainland.SizeCells, Ok = cm.Passed && ck.Passed, Ms = p1.ElapsedMs, }); GD.Print($" plate {seed}_{lv.Label}: pre {led.PreIslands} → post {led.PostIslands} (N {led.PostNorth} / S {led.PostSouth}), reverted {led.RevertedComponents} comps {(cm.Passed && ck.Passed ? "ok" : "⚠ CHECK FAILED")} {p1.ElapsedMs} ms"); } } bool allOk = hard.TrueForAll(c => c.Passed) && perFieldChecks.TrueForAll(c => c.Passed); GD.Print($"\n ORACLE: {(allOk ? "ALL HARD CHECKS PASS" : "*** FAILURES ***")}"); foreach (var c in perFieldChecks) if (!c.Passed) GD.PrintErr(" " + c); WriteTable(batchRoot, tableSize, levels, rows); WriteIndex(batchRoot, mapSize, tableSize, calibSize, plateSeed, secondSeed, tableSeeds, levels, rows, plateRows, hard, perFieldChecks, allOk, tableOnly); 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); } private static long Cells(float frac, int size) => Math.Max(1L, (long)Math.Round(frac * (double)size * size)); private static int PickSecondSeed(Dictionary natural, int[] seeds, int plateSeed) { int best = 0, bestN = -1; foreach (int s in seeds) { if (s == plateSeed) continue; int n = natural.GetValueOrDefault(s); if (n > bestN) { best = s; bestN = n; } } return best == 0 ? plateSeed : best; } // ---- the curve, measured exactly as tasks 03–06 did -------------------- 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); } 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 WritePlate(string batchRoot, Pass1Result p1, Pass2Result p2, float sea, CurveAnchors anchors, bool skipRaw) { 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 labeled-regions overlay — the point of this task. var led = p1.RegionLedger; RegionOverlayRenderer.SavePng(p1.Regions, led.RevertOn ? p1.RegionsPre : null, p1.MapSize, led.RevertedComponents, led.ThresholdCells, Path.Combine(dir, "regions.png")); // The hemisphere tag overlay (chat2/05's), now showing the tag by construction. var (n, s) = RegionLabeling.IslandsByHemisphere(p1.Regions); TagOverlayRenderer.SavePng(p2.Height, p2.IsIsland, p2.IslandHemisphere, p2.MapSize, sea, n, s, Path.Combine(dir, "tags.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(List levels, List rows, int tableSize) { 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 | Seed | threshold (cells) | natural islands (offshore off) N / S | pre-revert islands N / S | **post-revert islands N / S** | reverted comps / cells | pre size min / med / mean / max | **post size min / med / mean / max** | post histogram ({histHead}) | mainland cells | oracle |"); sb.AppendLine("|---|---|---|---|---|---|---|---|---|---|---|---|"); foreach (var lv in levels) foreach (var r in rows) { if (r.Level != lv.Label) continue; sb.AppendLine($"| `{r.Level}` | `{r.Seed}` | {r.ThresholdCells:N0} | {r.Natural} ({r.NaturalN} / {r.NaturalS}) | {r.Pre} ({r.PreN} / {r.PreS}) | **{r.Post} ({r.PostN} / {r.PostS})** | {r.RevertedComps} / {r.RevertedCells:N0} | " + $"{r.PreMin} / {r.PreMed} / {r.PreMean:F0} / {r.PreMax} | **{r.PostMin} / {r.PostMed} / {r.PostMean:F0} / {r.PostMax}** | {HistRow(r.PostHist)} | {r.MainlandCells:N0} | {(r.Ok ? "pass" : "**FAIL**")} |"); } sb.AppendLine(); sb.AppendLine("**Per level (over the seeds):**"); sb.AppendLine(); sb.AppendLine("| Level | threshold | post islands min / mean / max | post N min / mean / max | post S min / mean / max | reverted comps (total) | reverted cells (total) | post median island (median over seeds) | smallest surviving island |"); sb.AppendLine("|---|---|---|---|---|---|---|---|---|"); foreach (var lv in levels) { int cnt = 0, minP = int.MaxValue, maxP = 0, minN = int.MaxValue, maxN = 0, minS = int.MaxValue, maxS = 0; double sumP = 0, sumN = 0, sumS = 0; long revC = 0, revCells = 0, smallest = long.MaxValue; var meds = new List(); long thr = 0; foreach (var r in rows) { if (r.Level != lv.Label) continue; cnt++; thr = r.ThresholdCells; minP = Math.Min(minP, r.Post); maxP = Math.Max(maxP, r.Post); sumP += r.Post; minN = Math.Min(minN, r.PostN); maxN = Math.Max(maxN, r.PostN); sumN += r.PostN; minS = Math.Min(minS, r.PostS); maxS = Math.Max(maxS, r.PostS); sumS += r.PostS; revC += r.RevertedComps; revCells += r.RevertedCells; meds.Add(r.PostMed); if (r.Post > 0) smallest = Math.Min(smallest, r.PostMin); } if (cnt == 0) continue; meds.Sort(); sb.AppendLine($"| `{lv.Label}` | {lv.Frac:G3} = {thr:N0} cells | {minP} / {sumP / cnt:F1} / {maxP} | {minN} / {sumN / cnt:F1} / {maxN} | {minS} / {sumS / cnt:F1} / {maxS} | {revC} | {revCells:N0} | {meds[meds.Count / 2]} | {(smallest == long.MaxValue ? 0 : smallest)} |"); } return sb.ToString(); } private static void WriteTable(string batchRoot, int tableSize, List levels, List rows) { var sb = new StringBuilder(); sb.AppendLine($"# The count/size table — {rows.Count / Math.Max(1, levels.Count)} seeds × {levels.Count} revert thresholds at {tableSize}"); sb.AppendLine(); sb.AppendLine("Islands = non-mainland 8-connected land components of the CLASSIFY field (mainland = the centre component)."); sb.AppendLine("*natural* = offshore off, revert off; *pre-revert* = offshore `density_mid` on, revert off; *post-revert* = the same with"); sb.AppendLine("the speck revert on at the level's threshold. Sizes in cells. Histogram bins are cells, log-spaced."); sb.AppendLine(); sb.Append(TableMarkdown(levels, rows, tableSize)); WriteText(Path.Combine(batchRoot, "count_size_table.md"), sb.ToString()); var csv = new StringBuilder(); csv.AppendLine("level,seed,threshold_cells,natural,natural_n,natural_s,pre,pre_n,pre_s,post,post_n,post_s,reverted_comps,reverted_cells,pre_min,pre_median,pre_mean,pre_max,post_min,post_median,post_mean,post_max,post_hist,mainland_cells,oracle,ms"); var ic = System.Globalization.CultureInfo.InvariantCulture; foreach (var r in rows) csv.AppendLine(string.Join(",", r.Level, r.Seed, r.ThresholdCells, r.Natural, r.NaturalN, r.NaturalS, r.Pre, r.PreN, r.PreS, r.Post, r.PostN, r.PostS, r.RevertedComps, r.RevertedCells, r.PreMin, r.PreMed, r.PreMean.ToString("F1", ic), r.PreMax, r.PostMin, r.PostMed, r.PostMean.ToString("F1", ic), r.PostMax, "\"" + HistRow(r.PostHist) + "\"", r.MainlandCells, 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 tableSize, int calibSize, int plateSeed, int secondSeed, int[] tableSeeds, List levels, List rows, List plateRows, List hard, List perField, bool allOk, bool tableOnly) { var sb = new StringBuilder(); sb.AppendLine("# Batch 07 — region labeling: label all land, fix the tag, tunable speck revert"); sb.AppendLine(); sb.AppendLine("The **region-labeling layer** (`Core.RegionLabeling`) flood-fills the CLASSIFY field's land into 8-connected"); sb.AppendLine("components, names the **centre component** the mainland, and exposes id / size / centroid / hemisphere (by"); sb.AppendLine("centroid) / isMainland. The **island tag is now a consequence of labeling** — every non-mainland component,"); sb.AppendLine("natural detached masses included. The **speck revert** (origin-blind, lower-only, component-only, mainland never)"); sb.AppendLine("lowers sub-threshold islands to their ring's seabed; the threshold is the dial swept here."); sb.AppendLine(); if (tableOnly) sb.AppendLine("> ⚠ **ISLA_TABLE_ONLY** — a probe run: table only, no regressions, no plates. Not the batch of record.\n"); sb.AppendLine("## ⭐ Open this first"); sb.AppendLine(); sb.AppendLine($"1. **`{plateSeed}_threshold_mid/regions.png`** — the labeled-regions overlay: grey = mainland (the centre component),"); sb.AppendLine(" every island its own colour, dark red = where a reverted speck was. Then `relief.png` for the clean ocean."); sb.AppendLine($"2. **`{plateSeed}_threshold_low/`** and **`{plateSeed}_threshold_high/`** beside it — same seed, lower / higher cutoff."); sb.AppendLine($"3. **`{secondSeed}_threshold_mid/regions.png`** + `tags.png` — the second seed (most natural islands): the big organic"); sb.AppendLine(" detached masses are labeled and TAGGED (cyan / orange), which task 06's overlay left grey."); sb.AppendLine("4. Then the count/size table — the instrument for the later southern-stretch step."); sb.AppendLine(); sb.AppendLine("**The contract (verbatim):** field = classify (raw, uncurved) · land 8-connected (the complement of water's 4) ·"); sb.AppendLine("component = maximal 8-connected set of land cells (classify ≥ sea) · mainland = the component containing the map"); sb.AppendLine("centre (not merely the largest; the crater is NOT central) · per component: id, sizeCells, centroid, hemisphere"); sb.AppendLine($"(by centroid), isMainland. NORTH = rows `[0, {mapSize / 2})`, SOUTH = rows `[{mapSize / 2}, {mapSize})`; y runs south."); sb.AppendLine(); sb.AppendLine("## The four plates"); sb.AppendLine(); sb.AppendLine("| Plate | threshold (cells) | pre-revert islands N / S | **post-revert islands N / S** | reverted comps / cells | post size min / med / mean / max | mainland cells | oracle |"); sb.AppendLine("|---|---|---|---|---|---|---|---|"); foreach (var r in plateRows) sb.AppendLine($"| `{r.Seed}_{r.Level}/` | {r.ThresholdCells:N0} | {r.Pre} ({r.PreN} / {r.PreS}) | **{r.Post} ({r.PostN} / {r.PostS})** | {r.RevertedComps} / {r.RevertedCells:N0} | {r.PostMin} / {r.PostMed} / {r.PostMean:F0} / {r.PostMax} | {r.MainlandCells:N0} | {(r.Ok ? "pass" : "**FAIL**")} |"); if (plateRows.Count == 0) sb.AppendLine("| *(no plates — probe run)* | | | | | | | |"); sb.AppendLine(); sb.AppendLine($"## ⭐ The count/size table — {tableSeeds.Length} seeds × 3 thresholds at {tableSize}"); sb.AppendLine(); sb.Append(TableMarkdown(levels, rows, tableSize)); sb.AppendLine(); sb.AppendLine("Also as plain data: `count_size_table.md` / `.csv`."); sb.AppendLine(); sb.AppendLine("## The levels"); sb.AppendLine(); sb.AppendLine("| Level | `MinLandComponentFrac` | cells at the plate size | cells at 8192 |"); sb.AppendLine("|---|---|---|---|"); foreach (var lv in levels) sb.AppendLine($"| `{lv.Label}`{(lv.Label == "threshold_mid" ? " ⭐ config default" : "")} | {lv.Frac:G3} | {Cells(lv.Frac, mapSize):N0} | {Cells(lv.Frac, 8192):N0} |"); sb.AppendLine(); sb.AppendLine($"Every field: coast shelf ON + offshore `{OffshoreSettings.Organic().Describe()}` + region labeling ON. The revert is the variable."); sb.AppendLine("The revert is **origin-blind**: it removes small natural nubs as well as offshore-pass dots (fewer / bigger, intended). An offshore"); sb.AppendLine("island it removes leaves its submerged skirt (not this component — component-only) as a shoal."); 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"); sb.AppendLine(); sb.AppendLine("Regressions (offshore OFF + revert OFF must be bit-identical to Phase 1, task 03 and the `terrain-curve-v1` gallery dump; labeling ON + revert OFF bit-identical to the task-06 dump):"); sb.AppendLine(); sb.AppendLine(hard.Count == 0 ? "*(skipped — probe run)*\n" : ShapingOracle.ToMarkdownTable(hard)); sb.AppendLine("Per field (centre-is-land m · revert guards n · determinism o · moat i · mainland unmoved j · tag/coastline k · HMaxSeed l · classify b):"); 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`, `tags.png`, `relief.png`, `INDEX.md`, `count_size_table.md` / `.csv` | **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($"Plates at {mapSize}, table at {tableSize}, 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 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; } } }