From a59e52fcbcb0b17db53831bc3f2b4119acf09cfb Mon Sep 17 00:00:00 2001 From: beezm Date: Sat, 22 Aug 2026 10:24:56 -0400 Subject: [PATCH] =?UTF-8?q?chat2/10:=20frag=5F4=20seed=20gallery=20?= =?UTF-8?q?=E2=80=94=20render-only,=20the=2009=20setting=20frozen=20across?= =?UTF-8?q?=202=20anchors=20+=206=20fresh=20seeds=20at=208192?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FragGalleryTool pins every frag_4 value explicitly (amp 0.5, freq 12, window 0.66 +- 0.18, zero-mean, stretch 2 with band 0.70/0.05 and the sinker stretched, speck revert 2.5e-7, offshore and shelf off, labeling on) and proves it is the 09 setting: at 4096 the frozen field is bit-identical to the 09 frag_4 dump on both anchors (a9, 16.8 M cells each), the frag-off field to the 08 stretch_3 dump (a8), the interior locked against it (r), ids deterministic (o), the curve untouched (a1). No generation code changed. Eight seeds at 8192 - the anchors 1063685222 and 999999937 plus 20260822, 31415926, 27182818, 16180339, 14142135, 17320508 (constants chosen before any render) - each with grayscale, .f32, relief and the labeled-regions overlay, and the hemisphere-split count/size table with a stated numeric read rule (too solid < 12 big islands across both hemispheres; shredded if big < 25 % of all; else good spread). Seven read good spread; 14142135 trips the ratio rule (17 big of 69) and is the most solid mainland of the eight (99.3 % of land). Graduation held. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_013EY3ZTF6NwzF8ukBHQXSK7 --- Tools/README.md | 1 + Tools/Scenes/FragGalleryTool.tscn | 6 + Tools/Scripts/FragGalleryTool.cs | 443 +++++++++++++++++++++++++++ Tools/Scripts/FragGalleryTool.cs.uid | 1 + 4 files changed, 451 insertions(+) create mode 100644 Tools/Scenes/FragGalleryTool.tscn create mode 100644 Tools/Scripts/FragGalleryTool.cs create mode 100644 Tools/Scripts/FragGalleryTool.cs.uid diff --git a/Tools/README.md b/Tools/README.md index 99b4236..9de7678 100644 --- a/Tools/README.md +++ b/Tools/README.md @@ -43,6 +43,7 @@ constants, carried over verbatim — not re-derived from a design summary** (→ | `Scripts/SouthernStretchTool.cs` + `Scenes/SouthernStretchTool.tscn` | The chat2/08 batch — the diagnostic (`ISLA_DIAG_ONLY`) and the 5-level × 2-seed fragmentation ladder with the hemisphere-split instrument | | `Scripts/CoastalFragment.cs` | ⭐ **Coastal fragmentation** (chat2/09, exploration) — a perimeter-wide, band-limited, zero-mean noise on the pre-power falloff inside the coastal window (≈ 0.66 ± 0.18); thin necks flip first; interior bit-identical by construction | | `Scripts/CoastalFragmentTool.cs` + `Scenes/CoastalFragmentTool.tscn` | The chat2/09 batch — the frequency × amplitude probe (`ISLA_PROBE`) and the 4-amplitude × 2-seed ladder at a fixed stretch | +| `Scripts/FragGalleryTool.cs` + `Scenes/FragGalleryTool.tscn` | The chat2/10 gallery — render-only: 09's `frag_4` frozen across 2 anchors + 6 fresh seeds at 8192, with the count/size table | | `Scripts/OffshoreIslandsTool.cs` + `Scenes/OffshoreIslandsTool.tscn` | The offshore batch — chat2/06: 4 plates + the count table + the diagnosis (the chat2/05 version is at `3b96e06`) | | `Scripts/Pass1Result.cs` | The height field **and the Phase-2 seams** | | `Scripts/TerrainGenConfig.cs` | Config + the per-element ablation toggles | diff --git a/Tools/Scenes/FragGalleryTool.tscn b/Tools/Scenes/FragGalleryTool.tscn new file mode 100644 index 0000000..967de7e --- /dev/null +++ b/Tools/Scenes/FragGalleryTool.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://cfraggal10isla"] + +[ext_resource type="Script" path="res://Tools/Scripts/FragGalleryTool.cs" id="1_fgt"] + +[node name="FragGalleryTool" type="Node"] +script = ExtResource("1_fgt") diff --git a/Tools/Scripts/FragGalleryTool.cs b/Tools/Scripts/FragGalleryTool.cs new file mode 100644 index 0000000..143d6ce --- /dev/null +++ b/Tools/Scripts/FragGalleryTool.cs @@ -0,0 +1,443 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Godot; +using IslaApocalypse.Core; + +namespace IslaApocalypse.Tools +{ + /// + /// ⭐ THE FRAG-4 SEED GALLERY (chat2/10) — RENDER-ONLY. Does the chat2/09 `frag_4` look generalize? + /// Every setting is FROZEN at frag_4 (pinned explicitly here, not left to a default), 2 anchor seeds + + /// 6 fresh seeds, at showpiece size, each with grayscale + .f32 + relief + the labeled-regions overlay, + /// and the hemisphere-split count/size table as the instrument. No knob, no ladder, no logic change. + /// + /// ═══ RUNNING IT ═══ + /// + /// xvfb-run -a Godot_v4.7.2-stable_mono_linux.x86_64 \ + /// --path ~/celerNexus/islaApocalypse-v2 res://Tools/Scenes/FragGalleryTool.tscn + /// + /// ISLA_TASK / ISLA_BATCH / ISLA_SKIP_RAW / ISLA_OUTPUT_DIR + /// ISLA_MAPSIZE gallery size (default 8192) + /// ISLA_CALIB_SIZE curve calibration size (default 2048) + /// ISLA_SEEDS the gallery seeds (default: the 2 anchors + 6 fresh below) + /// ISLA_SKIP_ANCHOR_CHECK=1 skip the 4096 bit-identity check against the 09 frag_4 dumps + /// + public partial class FragGalleryTool : Node + { + /// The two seeds frag_4 was judged on (chat2/09). + private static readonly int[] AnchorSeeds = { 1063685222, 999999937 }; + + /// Six fresh seeds, chosen BEFORE any render — constants, not picks: the date and five famous digit strings. + private static readonly int[] FreshSeeds = { 20260822, 31415926, 27182818, 16180339, 14142135, 17320508 }; + + /// ⚠ Task 01's pool, verbatim — the curve's identity. + private static readonly int[] CalibrationSeeds = { 1063685222, 20260819, 777001, 424242, 90210, 31337 }; + + // ═══ THE FROZEN frag_4 SETTING — every value pinned explicitly (chat2/09 batch, level 4) ═══ + private const float FrozenFragmentAmp = 0.5f; + private const float FrozenFragmentFreq = 12f; + private const float FrozenBandCentre = 0.66f; + private const float FrozenBandHalfWidth = 0.18f; + private const bool FrozenBitesOnly = false; + private const float FrozenStretch = 2f; + private const float FrozenBandStart = 0.70f; + private const float FrozenBandFeather = 0.05f; + private const bool FrozenStretchSinker = true; + private const float FrozenSpeckFrac = 2.5e-7f; // 09's low speck revert (≈ 4 cells at 4096, ≈ 17 at 8192) + + private const int DefaultMapSize = 8192; + private const int DefaultCalibSize = 2048; + private const int AnchorCheckSize = 4096; // the 09 batch's size + + 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 HemiStats + { + public int All, Big; public long Min, Med, Max; public double Mean; public int[] Hist; public long[] Largest = Array.Empty(); + } + + private sealed class Row + { + public int Seed; public bool Anchor; + public HemiStats N, S; public long MainlandCells; public double MainlandFrac; public int SpecksReverted; + public string Read; public bool Ok; public ulong Ms; + } + + private void Run() + { + ToolingPaths.Configure(OS.GetUserDataDir()); + + int task = EnvInt("ISLA_TASK", 10); + string descr = EnvStr("ISLA_BATCH", "frag4_seed_gallery"); + int mapSize = EnvInt("ISLA_MAPSIZE", DefaultMapSize); + int calibSize = EnvInt("ISLA_CALIB_SIZE", DefaultCalibSize); + int[] seedsEnv = EnvSeeds("ISLA_SEEDS", null); + bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1"; + bool skipAnchor = EnvStr("ISLA_SKIP_ANCHOR_CHECK", "0") == "1"; + string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "02_pass1_port"); + string t08Source = EnvStr("ISLA_T08_SOURCE", "08_southern_stretch_explore"); + string t09Source = EnvStr("ISLA_T09_SOURCE", "09_coastal_fragment"); + + var seeds = new List(AnchorSeeds); if (seedsEnv == null) seeds.AddRange(FreshSeeds); else { seeds.Clear(); seeds.AddRange(seedsEnv); } + var anchorSet = new HashSet(AnchorSeeds); + + string batchRoot = ToolingPaths.BatchRoot(task, descr); + DirAccess.MakeDirRecursiveAbsolute(batchRoot); + DirAccess.MakeDirRecursiveAbsolute(ToolingPaths.BatchScratch(batchRoot)); + + var anchors = CurveAnchors.Default; + float sea = 0.15f; + long big = Cells(RegionPass.ThresholdMidFrac, mapSize); + long speckCells = Cells(FrozenSpeckFrac, mapSize); + + GD.Print("=================================================================="); + GD.Print(" FRAG-4 SEED GALLERY (chat2/10) — render-only: does frag_4 generalize?"); + GD.Print("=================================================================="); + GD.Print($"MapSize : {mapSize} curve calibrated at {calibSize} (offshore off)"); + GD.Print($"seeds : anchors {string.Join(", ", AnchorSeeds)} · fresh {string.Join(", ", seeds.FindAll(s => !anchorSet.Contains(s)))}"); + GD.Print($"FROZEN : FragmentAmp {FrozenFragmentAmp} · freq {FrozenFragmentFreq}/map · window {FrozenBandCentre} ± {FrozenBandHalfWidth} · bitesOnly {FrozenBitesOnly} · " + + $"stretch {FrozenStretch} (band {FrozenBandStart}/{FrozenBandFeather}, sinker stretched {FrozenStretchSinker}) · speck revert < {speckCells} cells ({FrozenSpeckFrac:G2}) · offshore OFF · shelf OFF · labeling ON"); + GD.Print($"\"big\" : ≥ {big:N0} cells at {mapSize} (the 07 mid threshold)"); + GD.Print($"batch : {batchRoot}"); + GD.Print("=================================================================="); + + GD.Print($"\n--- 0. CURVE (task-01 pool at {calibSize}, offshore off) ---"); + var (knots, calibration) = CalibrateCurve(calibSize, sea, anchors); + GD.Print($" {knots}"); + + TerrainGenConfig Frozen(int size, int seed, string label, bool frag = true, bool revert = true, bool stretch = true) => new TerrainGenConfig + { + MapSize = size, Seed = seed, VariantLabel = label, + Curve = true, ShelfDetail = false, CurveMode = CurveModeKind.Continuous, + Knots = knots, Anchors = anchors, ClimbCalibration = calibration, LowlandCeilingM = 30f, + CoastShelf = false, Offshore = new OffshoreSettings(), + RegionLabeling = true, SpeckRevert = revert, MinLandComponentFrac = FrozenSpeckFrac, + SouthStretch = stretch ? FrozenStretch : 0f, SouthBandStartFrac = FrozenBandStart, SouthBandFeatherFrac = FrozenBandFeather, StretchSinker = FrozenStretchSinker, + FragmentAmp = frag ? FrozenFragmentAmp : 0f, FragmentFreqPerMapWidth = FrozenFragmentFreq, + FragmentBandCentre = FrozenBandCentre, FragmentBandHalfWidth = FrozenBandHalfWidth, FragmentBitesOnly = FrozenBitesOnly, + }; + + // ═══ 1. THE ORACLE — no code change, same setting ═══ + GD.Print($"\n--- 1. ORACLE: the setting is the 09 frag_4 setting, and nothing upstream moved ---"); + var hard = new List(); + { + var offCfg = Frozen(calibSize, AnchorSeeds[0], "off", frag: false, revert: false, stretch: false); + Pass1Result p1 = Topography.Generate(offCfg); + var curveOff = offCfg.Clone(); curveOff.Curve = false; + string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{AnchorSeeds[0]}_full", "height.f32"); + hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF, stretch OFF, frag OFF == Phase-1 .f32 dump (the curve is untouched)", Shaping.Shape(p1, curveOff).Height, HeightField.Load(p1Dump, calibSize), calibSize, p1Dump)); + + if (!skipAnchor) + { + foreach (int seed in AnchorSeeds) + { + // ⭐ a9 — the frozen setting at the 09 batch's size reproduces the 09 frag_4 field bit for bit. + string t09Dump = Path.Combine(ToolingPaths.BatchesRoot, t09Source, $"{seed}_frag_4", "height.f32"); + var c9 = Frozen(AnchorCheckSize, seed, "frag_4"); + Pass1Result q9p = Topography.Generate(c9); + Pass2Result q9 = Shaping.Shape(q9p, c9); + hard.Add(ShapingOracle.DumpRegression("a9", $"frozen frag_4 at {AnchorCheckSize} == task-09 frag_4 dump [{seed}] (no code change, same setting)", q9.Height, HeightField.Load(t09Dump, AnchorCheckSize), AnchorCheckSize, t09Dump)); + + // a8 — the stretch-2, frag-off baseline still equals the 08 field, and the interior is still locked against it. + string t08Dump = Path.Combine(ToolingPaths.BatchesRoot, t08Source, $"{seed}_stretch_3", "height.f32"); + var c8 = Frozen(AnchorCheckSize, seed, "t08", frag: false, revert: false); + Pass1Result q8p = Topography.Generate(c8); + hard.Add(ShapingOracle.DumpRegression("a8", $"frag OFF, stretch 2 at {AnchorCheckSize} == task-08 stretch_3 dump [{seed}]", Shaping.Shape(q8p, c8).Height, HeightField.Load(t08Dump, AnchorCheckSize), AnchorCheckSize, t08Dump)); + var r = ShapingOracle.InteriorLocked(q8p, q9p, FrozenBandCentre, FrozenBandHalfWidth); r.Name += $" [{seed}, {AnchorCheckSize}]"; hard.Add(r); + } + // determinism at the check size + var a = Topography.Generate(Frozen(AnchorCheckSize, AnchorSeeds[0], "det")); + var b = Topography.Generate(Frozen(AnchorCheckSize, AnchorSeeds[0], "det")); + var det = ShapingOracle.LabelsDeterministic(a, b); det.Name += $" [{AnchorSeeds[0]}, {AnchorCheckSize}]"; hard.Add(det); + } + foreach (var c in hard) GD.Print(" " + c); + } + + // ═══ 2. THE GALLERY ═══ + GD.Print($"\n--- 2. THE GALLERY at {mapSize} ---"); + var rows = new List(); + var perSeed = new List(); + foreach (int seed in seeds) + { + var cfg = Frozen(mapSize, seed, "frag_4"); + Pass1Result p1 = Topography.Generate(cfg); + Pass2Result p2 = Shaping.Shape(p1, cfg); + var checks = new List + { + ShapingOracle.CentreIsLand(p1), + ShapingOracle.TagCoastlineConsistent(p2, sea), + ShapingOracle.ClassifyFidelity(p1, p2), + }; + foreach (var c in checks) { c.Name += $" [{seed}]"; perSeed.Add(c); } + bool ok = checks.TrueForAll(c => c.Passed); + + var (n, s) = Stats(p1.Regions, big); + long landCells = p1.Regions.LandCells; + var row = new Row + { + Seed = seed, Anchor = anchorSet.Contains(seed), N = n, S = s, + MainlandCells = p1.Regions.Mainland.SizeCells, MainlandFrac = landCells == 0 ? 0 : p1.Regions.Mainland.SizeCells / (double)landCells, + SpecksReverted = p1.RegionLedger?.RevertedComponents ?? 0, Ok = ok, Ms = p1.ElapsedMs, + }; + row.Read = AutoRead(row); + rows.Add(row); + WriteSeed(batchRoot, p1, p2, sea, anchors, skipRaw); + GD.Print($" seed {seed,-11}{(row.Anchor ? " ⭐" : " ")} N {n.All,3}/{n.Big,3} med {n.Med,6} largest {Largest(n.Largest)} S {s.All,3}/{s.Big,3} med {s.Med,6} largest {Largest(s.Largest)} mainland {row.MainlandCells:N0} ({row.MainlandFrac:P1} of land) → {row.Read} {(ok ? "ok" : "⚠ CHECK FAILED")} {p1.ElapsedMs} ms"); + } + + bool allOk = hard.TrueForAll(c => c.Passed) && perSeed.TrueForAll(c => c.Passed); + GD.Print($"\n ORACLE: {(allOk ? "ALL HARD CHECKS PASS" : "*** FAILURES ***")}"); + foreach (var c in perSeed) if (!c.Passed) GD.PrintErr(" " + c); + + WriteTable(batchRoot, mapSize, rows, big, speckCells); + WriteIndex(batchRoot, mapSize, calibSize, rows, big, speckCells, hard, perSeed, allOk); + + GD.Print("\n=================================================================="); + GD.Print($" DONE — {batchRoot}"); + GD.Print($" ORACLE {(allOk ? "HARD CHECKS ALL PASS" : "*** FAILURES — see the table ***")}"); + GD.Print("=================================================================="); + GetTree().Quit(allOk ? 0 : 3); + } + + // ---- the instrument -------------------------------------------------- + + private static long Cells(float frac, int size) => Math.Max(1L, (long)Math.Round(frac * (double)size * size)); + + private static (HemiStats north, HemiStats south) Stats(RegionLabels l, long big) + { + var n = new List(); var s = new List(); + foreach (var r in l.Regions) + { + if (r.IsMainland) continue; + if (r.Hemisphere == RegionLabeling.HemiSouth) s.Add(r.SizeCells); else n.Add(r.SizeCells); + } + return (Make(n, big), Make(s, big)); + } + + private static HemiStats Make(List sizes, long big) + { + sizes.Sort(); + var h = new HemiStats { All = sizes.Count, Hist = new int[RegionLabeling.HistogramEdges.Length + 1] }; + if (sizes.Count == 0) return h; + double sum = 0; + foreach (long v in sizes) { h.Hist[RegionLabeling.HistogramBin(v)]++; if (v >= big) h.Big++; sum += v; } + h.Min = sizes[0]; h.Med = sizes[sizes.Count / 2]; h.Max = sizes[^1]; h.Mean = sum / sizes.Count; + int k = Math.Min(3, sizes.Count); h.Largest = new long[k]; + for (int i = 0; i < k; i++) h.Largest[i] = sizes[sizes.Count - 1 - i]; + return h; + } + + /// + /// The one-word NUMERIC read — a stated rule, not a judgement: "too solid" if fewer than 12 big + /// (≥ the 07 mid threshold) islands detached across both hemispheres (the 09 anchors had 23–27); + /// "shredded" if fewer than a quarter of the islands are big (all specks, no bigs); else "good + /// spread". The eye's read is in the report. + /// + private static string AutoRead(Row r) + { + int all = r.N.All + r.S.All, bigs = r.N.Big + r.S.Big; + if (bigs < 12) return "too solid"; + if (all > 0 && bigs < all * 0.25) return "shredded"; + return "good spread"; + } + + // ---- 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(new TerrainGenConfig { MapSize = calibSize, Seed = s }); + pass1[s] = p1; + rawPool.Accumulate(p1.Height, calibSize); + } + var knots = new CurveKnots(2, "v2_balanced", + rawPool.Quantile(CurveKnots.Percentiles[0]), rawPool.Quantile(CurveKnots.Percentiles[1]), + rawPool.Quantile(CurveKnots.Percentiles[2]), rawPool.Quantile(CurveKnots.Percentiles[3]), + rawPool.Quantile(CurveKnots.Percentiles[4]), rawPool.Quantile(CurveKnots.Percentiles[5])); + float ceilingRaw = knots.K2; + var rawAbove = new LandHistogram(sea); + var outAbove = new LandHistogram(sea); + foreach (int s in CalibrationSeeds) + { + var scfg = new TerrainGenConfig + { + MapSize = calibSize, Seed = s, Curve = true, ShelfDetail = true, + CurveMode = CurveModeKind.Staircase, Knots = knots, Anchors = anchors, VariantLabel = "staircase", + }; + Pass2Result st = Shaping.Shape(pass1[s], scfg); + rawAbove.AccumulateWhere(pass1[s].Height, pass1[s].Height, calibSize, ceilingRaw); + outAbove.AccumulateWhere(st.Height, pass1[s].Height, calibSize, ceilingRaw); + } + var pcts = ClimbCalibration.DefaultPercentiles; + var rawQ = new float[pcts.Length]; var outQ = new float[pcts.Length]; + for (int i = 0; i < pcts.Length; i++) { rawQ[i] = rawAbove.Quantile(pcts[i]); outQ[i] = outAbove.Quantile(pcts[i]); } + var cal = ClimbCalibration.FromPercentiles(pcts, rawQ, outQ, ceilingRaw, + HeightCurve.EffectiveSpikeMax(pass1[CalibrationSeeds[0]].HMaxSeed, knots, anchors), + anchors.RedCeil, anchors.PeakCap, mountainLift: 1.0f, peakSharpness: 1.0f); + return (knots, cal); + } + + // ---- output ----------------------------------------------------------- + + private static void WriteSeed(string batchRoot, Pass1Result p1, Pass2Result p2, float sea, CurveAnchors anchors, bool skipRaw) + { + string dir = Path.Combine(batchRoot, $"{p2.Seed}"); + 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, $"FRAG_4 {p2.Seed}").SavePng(Path.Combine(dir, "relief.png")); + var led = p1.RegionLedger; + RegionOverlayRenderer.SavePng(p1.Regions, led != null && led.RevertOn ? p1.RegionsPre : null, p1.MapSize, + led?.RevertedComponents ?? 0, led?.ThresholdCells ?? 0, Path.Combine(dir, "regions.png")); + } + + private static string HistRow(int[] h) + { + 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 Largest(long[] l) => l.Length == 0 ? "—" : string.Join(" / ", Array.ConvertAll(l, v => v.ToString("N0"))); + + private static string TableMarkdown(List rows, 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($"| Seed | read | **N islands all / ≥ {big:N0}** | N median / largest three | N histogram ({histHead}) | **S islands all / ≥ {big:N0}** | S median / largest three | S histogram | mainland cells (% of land) | specks reverted | oracle |"); + sb.AppendLine("|---|---|---|---|---|---|---|---|---|---|---|"); + foreach (var r in rows) + sb.AppendLine($"| `{r.Seed}`{(r.Anchor ? " ⭐ anchor" : "")} | **{r.Read}** | **{r.N.All} / {r.N.Big}** | {r.N.Med} / {Largest(r.N.Largest)} | {HistRow(r.N.Hist)} | **{r.S.All} / {r.S.Big}** | {r.S.Med} / {Largest(r.S.Largest)} | {HistRow(r.S.Hist)} | {r.MainlandCells:N0} ({r.MainlandFrac:P1}) | {r.SpecksReverted} | {(r.Ok ? "pass" : "**FAIL**")} |"); + return sb.ToString(); + } + + private static void WriteTable(string batchRoot, int mapSize, List rows, long big, long speckCells) + { + var sb = new StringBuilder(); + sb.AppendLine($"# The count/size table — frag_4 frozen, {rows.Count} seeds at {mapSize}"); + sb.AppendLine(); + sb.AppendLine($"Islands = non-mainland 8-connected land components of the classify field; hemisphere by centroid; \"big\" = ≥ {big:N0} cells; speck revert < {speckCells} cells."); + sb.AppendLine("Read rule (numeric, stated): **too solid** if < 12 big islands across both hemispheres · **shredded** if big < 25 % of all · else **good spread**. The eye's read is in the report."); + sb.AppendLine(); + sb.Append(TableMarkdown(rows, big)); + WriteText(Path.Combine(batchRoot, "count_size_table.md"), sb.ToString()); + + var csv = new StringBuilder(); + csv.AppendLine("seed,anchor,read,n_all,n_big,n_med,n_mean,n_max,n_largest,n_hist,s_all,s_big,s_med,s_mean,s_max,s_largest,s_hist,mainland_cells,mainland_frac,specks_reverted,oracle,ms"); + var ic = System.Globalization.CultureInfo.InvariantCulture; + foreach (var r in rows) + csv.AppendLine(string.Join(",", r.Seed, r.Anchor ? 1 : 0, r.Read, + r.N.All, r.N.Big, r.N.Med, r.N.Mean.ToString("F1", ic), r.N.Max, "\"" + Largest(r.N.Largest) + "\"", "\"" + HistRow(r.N.Hist) + "\"", + r.S.All, r.S.Big, r.S.Med, r.S.Mean.ToString("F1", ic), r.S.Max, "\"" + Largest(r.S.Largest) + "\"", "\"" + HistRow(r.S.Hist) + "\"", + r.MainlandCells, r.MainlandFrac.ToString("F4", ic), r.SpecksReverted, 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, List rows, long big, long speckCells, + List hard, List perSeed, bool allOk) + { + var sb = new StringBuilder(); + sb.AppendLine($"# Batch 10 — frag_4 seed gallery: does the look generalize? (render-only, {mapSize})"); + sb.AppendLine(); + sb.AppendLine("**A contact sheet, not a tune.** Every plate is the SAME setting — chat2/09's `frag_4`, frozen — across the two"); + sb.AppendLine("seeds it was judged on (⭐ anchors) and six fresh seeds chosen before any render. The question: does a coherent"); + sb.AppendLine("mainland with medium/large lobes detaching all around generalize, or do some seeds come out too solid or shredded?"); + sb.AppendLine(); + sb.AppendLine("## The frozen setting"); + sb.AppendLine(); + sb.AppendLine($"`FragmentAmp {FrozenFragmentAmp}` · `FragmentFreqPerMapWidth {FrozenFragmentFreq}` · window `{FrozenBandCentre} ± {FrozenBandHalfWidth}` · bites-only `{FrozenBitesOnly}` · " + + $"`SouthStretch {FrozenStretch}` (band `{FrozenBandStart}` / feather `{FrozenBandFeather}`, sinker stretched `{FrozenStretchSinker}`) · speck revert < {speckCells} cells (`{FrozenSpeckFrac:G2}` of the map) · " + + "offshore OFF · shelf OFF · region labeling ON · the tagged curve (calibrated on task 01's pool at " + calibSize + "). Pinned explicitly in `FragGalleryTool` — nothing is left to a default."); + sb.AppendLine(); + sb.AppendLine("## ⭐ The contact sheet"); + sb.AppendLine(); + sb.AppendLine("Open each seed's `regions.png` (the key view — detached pieces in colour) beside its `relief.png`."); + sb.AppendLine(); + sb.AppendLine("| Seed | read | overlay | relief | N islands all / big | S islands all / big | largest N / S piece |"); + sb.AppendLine("|---|---|---|---|---|---|---|"); + foreach (var r in rows) + sb.AppendLine($"| `{r.Seed}`{(r.Anchor ? " ⭐ anchor" : "")} | **{r.Read}** | [`{r.Seed}/regions.png`]({r.Seed}/regions.png) | [`{r.Seed}/relief.png`]({r.Seed}/relief.png) | {r.N.All} / {r.N.Big} | {r.S.All} / {r.S.Big} | {r.N.Max:N0} / {r.S.Max:N0} |"); + sb.AppendLine(); + sb.AppendLine($"## ⭐ The count/size table — hemisphere-split, with the size distribution"); + sb.AppendLine(); + sb.Append(TableMarkdown(rows, big)); + sb.AppendLine(); + sb.AppendLine("Read rule (numeric, stated): **too solid** if < 12 big islands across both hemispheres (the anchors had 23–27 at 4096) · **shredded** if big < 25 % of all · else **good spread**. The eye's read is in the report. Also as `count_size_table.md` / `.csv`."); + sb.AppendLine(); + sb.AppendLine("## ⚠ The palette is PROVISIONAL"); + sb.AppendLine(); + sb.AppendLine("`ProvisionalEven`, flagged. The grayscale is the honest instrument; the overlay is the region layer's data."); + sb.AppendLine(); + sb.AppendLine("## The oracle (render-only: the setting is the 09 setting, nothing upstream moved)"); + sb.AppendLine(); + sb.AppendLine(ShapingOracle.ToMarkdownTable(hard)); + sb.AppendLine("Per seed (centre-is-land m · tag/coastline k · classify b):"); + sb.AppendLine(); + sb.AppendLine(ShapingOracle.ToMarkdownTable(perSeed)); + 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` | **keep** |"); + sb.AppendLine("| `grayscale.png` | ♻ regenerable from the `.f32` |"); + sb.AppendLine("| `height.f32` | ♻ regenerable from seed + the frozen setting — large (256 MB each), clear freely |"); + sb.AppendLine("| `scratch/` | persistent by rule; never cleaned |"); + sb.AppendLine(); + sb.AppendLine($"Gallery 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 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; + } + } +} diff --git a/Tools/Scripts/FragGalleryTool.cs.uid b/Tools/Scripts/FragGalleryTool.cs.uid new file mode 100644 index 0000000..145e27e --- /dev/null +++ b/Tools/Scripts/FragGalleryTool.cs.uid @@ -0,0 +1 @@ +uid://b484d8yc32y3c