using System; using System.Collections.Generic; using System.IO; using System.Text; using Godot; using IslaApocalypse.Core; namespace IslaApocalypse.Tools { /// /// ⭐ THE OFFSHORE-ISLANDS BATCH — chat2/06: ORGANIC-ONLY, TUNED FOR COVERAGE, SOUTH-WEIGHTED, /// NO FORCED COUNT. (chat2/05's version of this tool — faithful / floor_only / hybrid / dense — /// is in git history at 3b96e06; the forced floor it batched was reverted out on look.) /// /// ⚠ Since chat2/07 the island tag is set BY THE REGION LAYER (every non-mainland land component, /// natural islets included), so this tool's counts now include natural islands; the chat2/06 batch /// of record (offshore-pass islands only) was produced at e8571b2. /// /// ═══ WHAT IT PRODUCES — a fixed budget: 4 plates + a count table + a diagnosis ═══ /// /// PLATES (4, at ISLA_MAPSIZE, grayscale + .f32 + relief + tags overlay): /// {plate}_density_low / _mid / _high three densities on ONE seed — the developer picks the /// look by eye (more islands vs slop), apples to apples. /// {bulge}_density_mid the preset on the southern-bulge seed — the table seed /// whose south was SPARSEST at density_mid (auto-picked, /// or ISLA_BULGE_SEED) — the south fix is not seed-specific. /// /// THE COUNT TABLE (data, not plates): per seed, north / south island counts across ~12 seeds for /// each of the 3 density levels, with min / mean / max per hemisphere per level, the guard /// ledger and the island sizes. This is how "a few south / a couple north, consistently" is /// READ — as statistics of the tuning, never as a floor. /// /// THE DIAGNOSIS (data): per hemisphere over the calibration seed pool — valid-zone area, which /// gate binds, noise peaks clearing the threshold — measured BEFORE any knob moved (§2 of the /// task). → . /// /// ═══ 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. Oracle a1 / a3 / a4 prove offshore-off is bit-identical to Phase 1, task 03 and the /// tag's own 04 gallery dump. /// /// ═══ 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_OUTPUT_DIR /// ISLA_MAPSIZE plate + table size (default 4096 — islands need pixels to read) /// 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) — also the diagnosis size /// ISLA_TABLE_SEEDS the count-table seeds (default 12 below) /// ISLA_PLATE_SEED the three-density seed (default 1063685222) /// ISLA_BULGE_SEED the south-bulge seed (default 0 = auto: sparsest south at density_mid) /// ISLA_DENS_LOW / ISLA_DENS_MID / ISLA_DENS_HIGH / ISLA_SOUTH_WEIGHT the tuning (probe overrides) /// ISLA_OFF_MINAREA / ISLA_OFF_MINSEP / ISLA_OFF_MAXAREA the guards (probe overrides) /// ISLA_OFF_FREQ / ISLA_OFF_CORE / ISLA_OFF_SHARP / ISLA_OFF_CREST the shape (probe overrides) /// ISLA_TABLE_ONLY=1 probe: diagnosis + count table only (no regressions, no plates) /// ISLA_PHASE1_SOURCE the Phase-1 regression dump's batch (default "chat1/02_pass1_port") /// public partial class OffshoreIslandsTool : Node { /// /// The count-table pool: chat2/05's six hybrid seeds + task 01's calibration pool (minus the /// shared anchor) + one more. Twelve draws; the anchor first. /// private static readonly int[] DefaultTableSeeds = { 1063685222, 20260821, 8675309, 123456789, 271828182, 999999937, 20260819, 777001, 424242, 90210, 31337, 55555, }; /// ⚠ Task 01's pool, verbatim — the curve's identity. Also the diagnosis pool. private static readonly int[] CalibrationSeeds = { 1063685222, 20260819, 777001, 424242, 90210, 31337 }; private const int DefaultMapSize = 4096; private const int DefaultCalibSize = 2048; /// The consistency targets the table is read against: "a couple north, a few south". private const int TargetNorth = 2, TargetSouth = 3; 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 string Level; public int Seed; public int CountN, CountS, PreN, PreS, SpecksN, SpecksS, ClustersN, ClustersS, BlobsN, BlobsS; public long Lifted, SizeMin, SizeMed, SizeMax; public double SizeMean; public float HMaxBefore, HMaxAfter; public bool Ok; public ulong Ms; } private sealed class Level { public string Label; public OffshoreSettings Settings; } 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", 6); string descr = EnvStr("ISLA_BATCH", "offshore_organic_tune"); 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 bulgeSeedEnv = EnvInt("ISLA_BULGE_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"; string batchRoot = ToolingPaths.BatchRoot(task, descr); DirAccess.MakeDirRecursiveAbsolute(batchRoot); DirAccess.MakeDirRecursiveAbsolute(ToolingPaths.BatchScratch(batchRoot)); var anchors = CurveAnchors.Default; float sea = 0.15f; // ═══ THE THREE DENSITY LEVELS — the preset of record is `mid` ═══ OffshoreSettings LevelSettings(float density) { var o = OffshoreSettings.Organic(); o.Density = density; o.SouthWeight = EnvFloat("ISLA_SOUTH_WEIGHT", o.SouthWeight); o.MinIslandAreaFrac = EnvFloat("ISLA_OFF_MINAREA", o.MinIslandAreaFrac); o.MinSeparationFrac = EnvFloat("ISLA_OFF_MINSEP", o.MinSeparationFrac); o.MaxIslandAreaFrac = EnvFloat("ISLA_OFF_MAXAREA", o.MaxIslandAreaFrac); o.FreqPerMapWidth = EnvFloat("ISLA_OFF_FREQ", o.FreqPerMapWidth); o.CoreFraction = EnvFloat("ISLA_OFF_CORE", o.CoreFraction); o.EdgeSharpness = EnvFloat("ISLA_OFF_SHARP", o.EdgeSharpness); o.CrestM = EnvFloat("ISLA_OFF_CREST", o.CrestM); return o; } var levels = new List { new() { Label = "density_low", Settings = LevelSettings(EnvFloat("ISLA_DENS_LOW", OffshoreSettings.OrganicDensityLow)) }, new() { Label = "density_mid", Settings = LevelSettings(EnvFloat("ISLA_DENS_MID", OffshoreSettings.OrganicDensityMid)) }, new() { Label = "density_high", Settings = LevelSettings(EnvFloat("ISLA_DENS_HIGH", OffshoreSettings.OrganicDensityHigh)) }, }; Level mid = levels[1]; GD.Print("=================================================================="); GD.Print(" OFFSHORE ISLANDS (chat2/06) — organic-only, tuned for coverage, south-weighted, no forced count"); GD.Print("=================================================================="); GD.Print($"MapSize : {mapSize} (plates) table at {tableSize} curve calibrated at {calibSize} (offshore OFF) diagnosis at {calibSize}"); GD.Print($"table : {string.Join(", ", tableSeeds)}"); GD.Print($"plate seed: {plateSeed} bulge seed: {(bulgeSeedEnv > 0 ? bulgeSeedEnv.ToString() : "auto (sparsest south at density_mid)")}"); foreach (var l in levels) GD.Print($" {l.Label,-13} {l.Settings.Describe()}"); GD.Print($"hemisphere: NORTH = rows [0, {mapSize / 2}) SOUTH = rows [{mapSize / 2}, {mapSize}) (y runs south)"); GD.Print($"batch : {batchRoot}{(tableOnly ? " ⚠ ISLA_TABLE_ONLY — a probe, not the batch of record" : "")}"); 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 — the things that must not have moved ═══ var hard = new List(); if (!tableOnly) { GD.Print($"\n--- 1. REGRESSIONS at {calibSize}, seed {plateSeed} ---"); var offCfg = BaseConfig(calibSize, plateSeed, knots, anchors, calibration, "off"); 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 == Phase-1 .f32 dump", pOff.Height, ShapingOracle.LoadAnchor("a1", "ISLA_PHASE1_SOURCE", p1Dump, calibSize), calibSize, p1Dump)); // ⚑ RETIRED at rivers/01 — a3, `continuous_restored` == `03_mountain_restore`. // A curve-development intermediate: it proved the chat2/03 climb restoration against // chat2/02. Both are upstream of the locked shape, and `terrain-shape-v1` (a10) now // asserts the whole chain end-to-end — subsuming it. // 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. // 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); // ⭐ a4 — offshore OFF at the 04 gallery's size == the terrain-curve-v1 tag's OWN output. // The literal "offshore-off is bit-identical to terrain-curve-v1", at full size. // ⚑ RETIRED at rivers/01 — a4, offshore-OFF at 8192 == `04_seed_gallery` (`terrain-curve-v1`). // The PRE-FAMILY committed curve. The locked shape is family-ON, so this dump is a // baseline the generator is deliberately no longer on; `a10` replaced it as the 8192 // acceptance. (It also cost an 8192 generation on every run of this tool.) // 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. THE DIAGNOSIS — measure the south before touching a knob ═══ GD.Print($"\n--- 2. DIAGNOSIS (calibration pool at {calibSize}, shelf on, {mid.Label} thresholds) ---"); var diag = new List(); foreach (int s in CalibrationSeeds) { var dCfg = BaseConfig(calibSize, s, knots, anchors, calibration, "shelf_only"); dCfg.CoastShelf = true; Pass1Result pShelf = Topography.Generate(dCfg); var rep = OffshoreDiagnosis.Run(pShelf.Height, pShelf.PreTrenchFalloff, calibSize, s, sea, mid.Settings, dCfg.Scale); diag.Add(rep); GD.Print($" seed {s,-11} N: zone {rep.North.Zone,9:N0} ({rep.North.ZoneShareOfSea,6:P1}) peaks in zone {rep.North.PeaksInZone,3} > thr {rep.North.PeaksInZoneOverThr,3} " + $"S: zone {rep.South.Zone,9:N0} ({rep.South.ZoneShareOfSea,6:P1}) peaks in zone {rep.South.PeaksInZone,3} > thr {rep.South.PeaksInZoneOverThr,3} " + $"sole-blocked N d/f/t {rep.North.SoleDepth:N0}/{rep.North.SoleFalloff:N0}/{rep.North.SoleTrench:N0} S {rep.South.SoleDepth:N0}/{rep.South.SoleFalloff:N0}/{rep.South.SoleTrench:N0}"); } var (poolN, poolS) = OffshoreDiagnosis.Pool(diag); string interpretation = OffshoreDiagnosis.Interpret(poolN, poolS); GD.Print(" " + interpretation); // ═══ 3. THE COUNT TABLE — ~12 seeds × 3 levels ═══ GD.Print($"\n--- 3. COUNT TABLE at {tableSize} ---"); var rows = new List(); var perFieldChecks = new List(); bool notesShown = false; foreach (int seed in tableSeeds) { Pass1Result p1Off = Topography.Generate(BaseConfig(tableSize, seed, knots, anchors, calibration, "off")); foreach (var lv in levels) { var cfg = BaseConfig(tableSize, seed, knots, anchors, calibration, lv.Label); cfg.CoastShelf = true; cfg.Offshore = lv.Settings.Clone(); Pass1Result p1 = Topography.Generate(cfg); Pass2Result p2 = Shaping.Shape(p1, cfg); if (!notesShown) { foreach (string n in p1.Notes) GD.Print(" " + n); notesShown = true; } var comps = OffshoreAnalysis.Components(p1.IsIsland, p1.Height, sea, tableSize); var (cn, cs) = OffshoreAnalysis.CountByHemisphere(comps); var (szMin, szMed, szMean, szMax, _) = OffshoreAnalysis.SizeSummary(comps, 0); var checks = new List { ShapingOracle.MoatIntact(p1, comps), ShapingOracle.MainlandUnmoved(p1Off, p1, sea), 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, CountN = cn, CountS = cs, Lifted = p1.OffshoreLiftedCells, HMaxBefore = p1.HMaxSeedBeforeOffshore, HMaxAfter = p1.HMaxSeed, SizeMin = szMin, SizeMed = szMed, SizeMean = szMean, SizeMax = szMax, Ok = ok, Ms = p1.ElapsedMs, }; ReadGuardLedger(p1, row); rows.Add(row); GD.Print($" {lv.Label,-13} seed {seed,-11} N {cn,2} S {cs,2} (pre-guard N {row.PreN,2} S {row.PreS,2}; specks {row.SpecksN + row.SpecksS,2} clusters {row.ClustersN + row.ClustersS,2} blobs {row.BlobsN + row.BlobsS,2}) " + $"size med {szMed,5} max {szMax,6} {(ok ? "ok" : "⚠ CHECK FAILED")} {p1.ElapsedMs} ms"); } } var stats = LevelStats(levels, rows); GD.Print("\n per level (min / mean / max):"); foreach (var st in stats) GD.Print($" {st.Label,-13} N {st.MinN} / {st.MeanN:F1} / {st.MaxN} S {st.MinS} / {st.MeanS:F1} / {st.MaxS} " + $"seeds with N≥{TargetNorth} & S≥{TargetSouth}: {st.MeetBoth}/{st.Seeds} S≥N: {st.SouthAtLeastNorth}/{st.Seeds} " + $"guards: specks {st.Specks} clusters {st.Clusters} blobs {st.Blobs}"); // ═══ 4. THE PLATES — exactly four ═══ int bulgeSeed = bulgeSeedEnv > 0 ? bulgeSeedEnv : PickBulgeSeed(rows, mid.Label, plateSeed); GD.Print($"\n southern-bulge seed: {bulgeSeed}{(bulgeSeedEnv > 0 ? " (ISLA_BULGE_SEED)" : " (auto: sparsest south at density_mid among the table seeds)")}"); var plates = new List<(int seed, Level level)> { (plateSeed, levels[0]), (plateSeed, levels[1]), (plateSeed, levels[2]), (bulgeSeed, mid) }; var plateRows = new List(); if (!tableOnly) { GD.Print($"\n--- 4. PLATES at {mapSize} ---"); foreach (var (seed, lv) in plates) { var cfg = BaseConfig(mapSize, seed, knots, anchors, calibration, lv.Label); cfg.CoastShelf = true; cfg.Offshore = lv.Settings.Clone(); Pass1Result p1 = Topography.Generate(cfg); Pass2Result p2 = Shaping.Shape(p1, cfg); var comps = OffshoreAnalysis.Components(p1.IsIsland, p1.Height, sea, mapSize); var (cn, cs) = OffshoreAnalysis.CountByHemisphere(comps); var moat = ShapingOracle.MoatIntact(p1, comps); moat.Name += $" [plate {lv.Label} {seed}]"; var tag = ShapingOracle.TagCoastlineConsistent(p2, sea); tag.Name += $" [plate {lv.Label} {seed}]"; perFieldChecks.Add(moat); perFieldChecks.Add(tag); WritePlate(batchRoot, p1, p2, sea, anchors, skipRaw, cn, cs); var row = new Row { Level = lv.Label, Seed = seed, CountN = cn, CountS = cs, Lifted = p1.OffshoreLiftedCells, Ok = moat.Passed && tag.Passed }; ReadGuardLedger(p1, row); plateRows.Add(row); GD.Print($" plate {seed}_{lv.Label}: N {cn} S {cs} lifted {p1.OffshoreLiftedCells:N0} {(row.Ok ? "ok" : "⚠ CHECK FAILED")} {p1.ElapsedMs} ms"); foreach (string n in p1.Notes) if (n.Contains("guards") || n.Contains("islands:")) GD.Print(" " + n); } } 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); // ═══ 5. THE DATA FILES + INDEX ═══ WriteCountTable(batchRoot, tableSize, levels, rows, stats); WriteDiagnosis(batchRoot, calibSize, mid, diag, poolN, poolS, interpretation); WriteIndex(batchRoot, mapSize, tableSize, calibSize, plateSeed, bulgeSeed, tableSeeds, levels, rows, stats, plateRows, diag, poolN, poolS, interpretation, 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); } // ---- the table's statistics ------------------------------------------- private sealed class LevelStat { public string Label; public int Seeds; public int MinN, MaxN, MinS, MaxS; public double MeanN, MeanS; public int MeetBoth, SouthAtLeastNorth, Specks, Clusters, Blobs; public long SizeMed, SizeMax; } private static List LevelStats(List levels, List rows) { var outp = new List(); foreach (var lv in levels) { var st = new LevelStat { Label = lv.Label, MinN = int.MaxValue, MinS = int.MaxValue }; double sumN = 0, sumS = 0; var meds = new List(); foreach (var r in rows) { if (r.Level != lv.Label) continue; st.Seeds++; st.MinN = Math.Min(st.MinN, r.CountN); st.MaxN = Math.Max(st.MaxN, r.CountN); sumN += r.CountN; st.MinS = Math.Min(st.MinS, r.CountS); st.MaxS = Math.Max(st.MaxS, r.CountS); sumS += r.CountS; if (r.CountN >= TargetNorth && r.CountS >= TargetSouth) st.MeetBoth++; if (r.CountS >= r.CountN) st.SouthAtLeastNorth++; st.Specks += r.SpecksN + r.SpecksS; st.Clusters += r.ClustersN + r.ClustersS; st.Blobs += r.BlobsN + r.BlobsS; meds.Add(r.SizeMed); st.SizeMax = Math.Max(st.SizeMax, r.SizeMax); } if (st.Seeds == 0) { st.MinN = st.MinS = 0; } else { st.MeanN = sumN / st.Seeds; st.MeanS = sumS / st.Seeds; meds.Sort(); st.SizeMed = meds[meds.Count / 2]; } outp.Add(st); } return outp; } /// The table seed whose SOUTH count at the preset level is lowest (ties → lower total, then first in the pool), excluding the plate seed. private static int PickBulgeSeed(List rows, string midLabel, int plateSeed) { int best = 0, bestS = int.MaxValue, bestTotal = int.MaxValue; foreach (var r in rows) { if (r.Level != midLabel || r.Seed == plateSeed) continue; int total = r.CountN + r.CountS; if (r.CountS < bestS || (r.CountS == bestS && total < bestTotal)) { best = r.Seed; bestS = r.CountS; bestTotal = total; } } return best == 0 ? plateSeed : best; } /// Copy the pass's guard ledger into a table row (the pass reports it; the tool does not recompute it). private static void ReadGuardLedger(Pass1Result p1, Row row) { var l = p1.OffshoreLedger; if (l == null) return; row.PreN = l.PreGuardNorth; row.PreS = l.PreGuardSouth; row.SpecksN = l.SpecksNorth; row.SpecksS = l.SpecksSouth; row.ClustersN = l.ClustersNorth; row.ClustersS = l.ClustersSouth; row.BlobsN = l.BlobsNorth; row.BlobsS = l.BlobsSouth; } // ---- the curve, measured exactly as tasks 03/04/05 did ---------------- private static (CurveKnots, ClimbCalibration, Dictionary) 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, pass1); } /// /// ⭐ rivers/01 — FAMILY-OFF PINNED, not defaulted. This tool is chat-2 shaping DEVELOPMENT /// (chat2/05–06): authored and judged before the shape family existed, and its regressions hold /// pass 1 against the FAMILY-OFF `02_pass1_port` dump. The re-baseline flipped the bare defaults /// family-ON, so without every config here would /// silently acquire stretch + fragmentation and the anchor checks would fail for a configuration /// reason, not a regression. The variants re-enable offshore explicitly, after the pin. /// 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, }.WithFamilyOff(); // ⭐ shelf / islets / speck / stretch / frag / erosion all OFF — pinned // ---- output ----------------------------------------------------------- private static void WritePlate(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.IsIsland, p2.IslandHemisphere, p2.MapSize, sea, countN, countS, Path.Combine(dir, "tags.png")); } private static string CountTableMarkdown(List levels, List rows, List stats) { var sb = new StringBuilder(); sb.AppendLine("| Level | Seed | **N** | **S** | total | pre-guard N / S | specks | clusters | blobs | size cells min / median / mean / max | lifted cells | HMaxSeed before → after | oracle | ms |"); 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.CountN}** | **{r.CountS}** | {r.CountN + r.CountS} | {r.PreN} / {r.PreS} | " + $"{r.SpecksN + r.SpecksS} | {r.ClustersN + r.ClustersS} | {r.BlobsN + r.BlobsS} | " + $"{r.SizeMin} / {r.SizeMed} / {r.SizeMean:F0} / {r.SizeMax} | {r.Lifted:N0} | " + $"{r.HMaxBefore:F4} → {r.HMaxAfter:F4}{(r.HMaxBefore != r.HMaxAfter ? " ⚠" : "")} | {(r.Ok ? "pass" : "**FAIL**")} | {r.Ms} |"); } sb.AppendLine(); sb.AppendLine("**Per level — the consistency read (min / mean / max over the seeds):**"); sb.AppendLine(); sb.AppendLine($"| Level | density N / S | seeds | **N min / mean / max** | **S min / mean / max** | seeds with N ≥ {TargetNorth} & S ≥ {TargetSouth} | seeds with S ≥ N | specks / clusters / blobs reverted | median island (cells) | largest island (cells) |"); sb.AppendLine("|---|---|---|---|---|---|---|---|---|---|"); foreach (var st in stats) { var lv = levels.Find(l => l.Label == st.Label); sb.AppendLine($"| `{st.Label}` | {lv.Settings.Density:F4} / {lv.Settings.DensitySouth:F4} | {st.Seeds} | **{st.MinN} / {st.MeanN:F1} / {st.MaxN}** | **{st.MinS} / {st.MeanS:F1} / {st.MaxS}** | " + $"**{st.MeetBoth} / {st.Seeds}** | {st.SouthAtLeastNorth} / {st.Seeds} | {st.Specks} / {st.Clusters} / {st.Blobs} | {st.SizeMed} | {st.SizeMax} |"); } return sb.ToString(); } private static void WriteCountTable(string batchRoot, int tableSize, List levels, List rows, List stats) { var sb = new StringBuilder(); sb.AppendLine($"# The count table — {rows.Count / Math.Max(1, levels.Count)} seeds × {levels.Count} density levels at {tableSize}"); sb.AppendLine(); sb.AppendLine("Tagged 8-connected island components per hemisphere (centroid), after the guards. **No count is forced**;"); sb.AppendLine("this is the statistical outcome of the tuning. NORTH = rows [0, N/2), SOUTH = rows [N/2, N); y runs south."); sb.AppendLine(); sb.Append(CountTableMarkdown(levels, rows, stats)); WriteText(Path.Combine(batchRoot, "count_table.md"), sb.ToString()); var csv = new StringBuilder(); csv.AppendLine("level,seed,density_n,density_s,north,south,total,preguard_n,preguard_s,specks,clusters,blobs,size_min,size_median,size_mean,size_max,lifted_cells,hmax_before,hmax_after,oracle,ms"); foreach (var r in rows) { var lv = levels.Find(l => l.Label == r.Level); csv.AppendLine(string.Join(",", r.Level, r.Seed, lv.Settings.Density.ToString("F5", System.Globalization.CultureInfo.InvariantCulture), lv.Settings.DensitySouth.ToString("F5", System.Globalization.CultureInfo.InvariantCulture), r.CountN, r.CountS, r.CountN + r.CountS, r.PreN, r.PreS, r.SpecksN + r.SpecksS, r.ClustersN + r.ClustersS, r.BlobsN + r.BlobsS, r.SizeMin, r.SizeMed, r.SizeMean.ToString("F1", System.Globalization.CultureInfo.InvariantCulture), r.SizeMax, r.Lifted, r.HMaxBefore.ToString("G9", System.Globalization.CultureInfo.InvariantCulture), r.HMaxAfter.ToString("G9", System.Globalization.CultureInfo.InvariantCulture), r.Ok ? "pass" : "FAIL", r.Ms)); } WriteText(Path.Combine(batchRoot, "count_table.csv"), csv.ToString()); } private static string DiagnosisMarkdown(int calibSize, Level mid, List diag, HemisphereDiagnosis poolN, HemisphereDiagnosis poolS, string interpretation) { var sb = new StringBuilder(); sb.AppendLine($"Measured on the calibration pool at {calibSize} (shelf on, offshore off — the field the islet layer sees), with the"); sb.AppendLine($"`{mid.Label}` thresholds (density N {mid.Settings.Density:F4} / S {mid.Settings.DensitySouth:F4}). *Zone* = cells with zone weight > 0"); sb.AppendLine("(pass the falloff test + moat + outer bound); *peaks* = strict 8-neighbour local maxima of the islet noise field on sea cells;"); sb.AppendLine("*sole blocker* = sea cells that fail exactly one gate (loosen that gate and they join the zone); *lost > thr* = over-threshold"); sb.AppendLine("peaks outside the zone, with the gate(s) that excluded them."); sb.AppendLine(); sb.AppendLine(OffshoreDiagnosis.TableHeader()); foreach (var r in diag) { sb.AppendLine(OffshoreDiagnosis.TableRow(r, r.North)); sb.AppendLine(OffshoreDiagnosis.TableRow(r, r.South)); } var poolRep = new OffshoreDiagnosis.Report { Seed = 0 }; sb.AppendLine(OffshoreDiagnosis.TableRow(poolRep, poolN).Replace("| `0` |", "| **pool** |")); sb.AppendLine(OffshoreDiagnosis.TableRow(poolRep, poolS).Replace("| `0` |", "| **pool** |")); sb.AppendLine(); sb.AppendLine($"**Reading:** {interpretation}"); return sb.ToString(); } private static void WriteDiagnosis(string batchRoot, int calibSize, Level mid, List diag, HemisphereDiagnosis poolN, HemisphereDiagnosis poolS, string interpretation) { var sb = new StringBuilder(); sb.AppendLine("# The south diagnosis — zone area, gates and peaks per hemisphere"); sb.AppendLine(); sb.Append(DiagnosisMarkdown(calibSize, mid, diag, poolN, poolS, interpretation)); WriteText(Path.Combine(batchRoot, "diagnosis.md"), sb.ToString()); } private static void WriteIndex(string batchRoot, int mapSize, int tableSize, int calibSize, int plateSeed, int bulgeSeed, int[] tableSeeds, List levels, List rows, List stats, List plateRows, List diag, HemisphereDiagnosis poolN, HemisphereDiagnosis poolS, string interpretation, List hard, List perField, bool allOk, bool tableOnly) { var mid = levels[1]; var sb = new StringBuilder(); sb.AppendLine("# Batch 06 — offshore islands: organic-only, tuned for coverage, south-weighted, no forced count"); sb.AppendLine(); sb.AppendLine("The chat2/05 forced floor (seeded stamps, guaranteed ≥2 N / ≥4 S) is **reverted out** — it looked stamped."); sb.AppendLine("The **organic noise-field layer is the only island mechanism**; this batch tunes its **density** (the main"); sb.AppendLine("knob) and a **south weight** so it yields *a few south / a couple north* **consistently across seeds, as a"); sb.AppendLine("statistical outcome** — never a hard-coded count. Guards against slop: specks, clusters and blobs are"); sb.AppendLine("reverted whole; every surviving island is a noise outline, small, low, crisp."); sb.AppendLine(); if (tableOnly) sb.AppendLine("> ⚠ **ISLA_TABLE_ONLY** — a probe run: diagnosis + count table only, no regressions, no plates. Not the batch of record.\n"); sb.AppendLine("## ⭐ Open this first"); sb.AppendLine(); sb.AppendLine($"1. **`{plateSeed}_density_mid/tags.png`** — the preset of record: grey mainland, cyan = island N, orange = island S."); sb.AppendLine(" No rings any more — nothing is seeded. Then its `relief.png` for the shape."); sb.AppendLine($"2. **`{plateSeed}_density_low/`** and **`{plateSeed}_density_high/`** beside it — the same seed, less and more density;"); sb.AppendLine(" pick the look by eye (more islands vs slop)."); sb.AppendLine($"3. **`{bulgeSeed}_density_mid/tags.png`** — the preset on the southern-bulge seed (the table seed whose south was"); sb.AppendLine(" sparsest at `density_mid`): the south tuning is not seed-specific."); sb.AppendLine("4. Then the count table below — *does it consistently give a few south / a couple north?*"); 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})`. Component hemisphere is by centroid; the tag per cell is by row."); sb.AppendLine(); sb.AppendLine("## The four plates"); sb.AppendLine(); sb.AppendLine("| Plate | N | S | total | pre-guard N / S | specks / clusters / blobs | lifted cells | oracle |"); sb.AppendLine("|---|---|---|---|---|---|---|---|"); foreach (var r in plateRows) sb.AppendLine($"| `{r.Seed}_{r.Level}/` | **{r.CountN}** | **{r.CountS}** | {r.CountN + r.CountS} | {r.PreN} / {r.PreS} | {r.SpecksN + r.SpecksS} / {r.ClustersN + r.ClustersS} / {r.BlobsN + r.BlobsS} | {r.Lifted:N0} | {(r.Ok ? "pass" : "**FAIL**")} |"); if (plateRows.Count == 0) sb.AppendLine("| *(no plates — probe run)* | | | | | | | |"); sb.AppendLine(); sb.AppendLine($"## ⭐ The count table — {tableSeeds.Length} seeds × 3 levels at {tableSize} (the consistency evidence)"); sb.AppendLine(); sb.Append(CountTableMarkdown(levels, rows, stats)); sb.AppendLine(); sb.AppendLine("Also as plain data: `count_table.md` / `count_table.csv`."); sb.AppendLine(); sb.AppendLine("## The levels — density and south weight"); sb.AppendLine(); sb.AppendLine("| Level | settings |"); sb.AppendLine("|---|---|"); foreach (var lv in levels) sb.AppendLine($"| `{lv.Label}`{(lv == mid ? " ⭐ preset of record" : "")} | {lv.Settings.Describe()} |"); sb.AppendLine(); sb.AppendLine("The coast shelf is ON for every field (strength 0.775, scale 100 m, BitDecrement-clamped); invisible on these"); sb.AppendLine("hypsometric plates — ported faithfully, judged when water renders. Moat / falloff test / outer bound unchanged from chat2/05."); sb.AppendLine(); sb.AppendLine("## The south diagnosis — measured before tuning"); sb.AppendLine(); sb.Append(DiagnosisMarkdown(calibSize, mid, diag, poolN, poolS, interpretation)); sb.AppendLine(); sb.AppendLine("Also as `diagnosis.md`."); 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 (offshore OFF must be bit-identical to Phase 1, task 03 and the `terrain-curve-v1` gallery dump):"); sb.AppendLine(); sb.AppendLine(hard.Count == 0 ? "*(skipped — probe run)*\n" : ShapingOracle.ToMarkdownTable(hard)); sb.AppendLine("Per field (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("| `tags.png`, `relief.png`, `INDEX.md`, `count_table.md` / `.csv`, `diagnosis.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($"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; } } }