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