using System.Text;
namespace IslaApocalypse.Tools
{
/// Which offshore-islet system pass 1 runs. → .
public enum OffshoreMode
{
/// No islets. The shelf is gated separately (TerrainGenConfig.CoastShelf).
Off,
///
/// ⭐ chat2/05 stage 1 — the reference's probabilistic layer, verbatim: one noise field, one
/// calibrated threshold, the faithful blob, the faithful zone mask. Sparse, no corners, no
/// guards. THE CONTROL.
///
Faithful,
///
/// ⭐ chat2/06 — THE ONE ISLAND MECHANISM: the organic noise-field layer, reshaped small /
/// low / flat / crisp (chat2/05 stage 2's shape, unchanged), tuned for coverage by DENSITY
/// and a SOUTH WEIGHT, with the speck / separation / blob guards. No seeded floor, no
/// stamps, no count guarantee — every island is a noise outline, and the per-hemisphere
/// counts are a statistical outcome of the tuning (the chat2/05 forced floor was tried and
/// reverted on look; it is in git history, one checkout away).
///
Organic,
}
///
/// ⭐ EVERY OFFSHORE-ISLET DIAL, IN ONE OBJECT — config-gated and isolated, per the developer's
/// standing modularity concern. Nothing above hardcodes an island
/// specific; the whole system is this object + IslandFalloff + OffshorePass.
///
/// ═══ TWO PRESETS, AND WHY BOTH EXIST ═══
///
/// the reference's constants, verbatim. The control in every batch.
/// the reshape + the chat2/06 coverage tuning — the deliverable.
///
/// The reshape does not EDIT the faithful constants; it sets different values on the same
/// dials. So Faithful() stays bit-reproducible however far the organic layer is tuned.
///
/// ═══ THE TWO PROTECTIONS THAT ARE NOT DIALS ═══
///
/// (the moat) and (the "actually offshore" test)
/// are exposed here because every knob is, but they are the MAIN-ISLAND AND LAKE PROTECTIONS and
/// the presets do not move them. They are what makes "no island can bridge to shore" and "no
/// island in a lake or the crater bay" true by construction.
///
/// ═══ ⚠ WHAT IS DELIBERATELY NOT HERE (chat2/06) ═══
///
/// No floor, no stamp radius/core/jitter, no separation-of-seeded-centres, no placement RNG —
/// nothing that guarantees a count or places an island from a centre. The forced mechanism
/// (chat2/05 Hybrid()) was reverted out whole; if a hard count ever comes back it is a
/// design decision, not a knob that was left lying around.
///
public sealed class OffshoreSettings
{
public OffshoreMode Mode = OffshoreMode.Off;
// ---- the organic (noise) layer ----------------------------------------
/// Islet noise frequency, periods per map width. Higher ⇒ SMALLER blobs. Reference 14.
public float FreqPerMapWidth = IslandFalloff.OFFSHORE_FREQ_ISLANDS;
/// Islet noise seed offset. Reference 7607.
public int SeedOffset = IslandFalloff.OFFSHORE_SEED_OFFSET;
///
/// ⭐ THE MAIN KNOB — organic density: the fraction of the noise field's ACTUAL sampled
/// distribution that clears the calibrated threshold. More ⇒ more of the field becomes
/// island. This is the NORTH density; the south is this × .
/// Reference 0.02.
///
public float Density = 0.02f;
///
/// ⭐ THE SOUTH WEIGHT — a per-hemisphere density bias. South density =
/// × this. 1 ⇒ one dial (the reference); > 1 ⇒ "weighted south",
/// the developer's preference. It is applied to the DENSITY (i.e. the calibration quantile),
/// not to the blob shape, so a south island looks exactly like a north island — there are
/// simply more of them.
///
public float SouthWeight = 1f;
/// The effective south density.
public float DensitySouth => Density * SouthWeight;
///
/// Half-width of the smooth threshold blend across the hemisphere midline, as a fraction of
/// the map. ⚠ Without it a north/south density difference would cut any organic island that
/// straddles the midline along a dead-straight line. 0 ⇒ hard step (never wanted).
///
public float HemisphereBlendHalfWidth = 0.05f;
/// Islet crest, metres above sea, PRE-CURVE. Reference 34. The curve's toe squashes it lower.
public float CrestM = IslandFalloff.OFFSHORE_ISLAND_H_M;
/// Fraction of a blob's excess over threshold that saturates. LOWER ⇒ flatter. Reference 0.45.
public float CoreFraction = IslandFalloff.OFFSHORE_CORE;
/// Crest-to-sea edge sharpening exponent. 1 ⇒ the faithful smoothstep. Higher ⇒ crisper shore.
public float EdgeSharpness = 1f;
// ---- the slop guards (Organic only; all scale-free fractions of the map) --------------
//
// More density must not buy speck-debris or merged blobs. Three rules, each a revert BY
// COMPONENT MEMBERSHIP (the island goes back to seabed whole, and is untagged) — none of
// them places, shapes or counts anything. 0 ⇒ that rule is off. The faithful preset has none
// (the reference had no such rules).
///
/// ⚠ THE SPECK GUARD. An organic island smaller than this fraction of the map's AREA is
/// noise debris — a local maximum that barely cleared the threshold and surfaced a cap of a
/// few cells — and is reverted to seabed and untagged. 3e-5 is ~126 cells at 2048, ~500 at
/// 4096, ~2,000 cells (a ~50 m islet) at 8192.
///
public float MinIslandAreaFrac = 0f;
///
/// ⚠ THE SEPARATION GUARD — "enough separation that they read as distinct islands". Two
/// surviving islands whose nearest shores are closer than this fraction of the map WIDTH
/// read as one cluster; the SMALLER is reverted. Greedy by size, so the largest island in a
/// cluster always stays. Chebyshev distance between boundary cells.
///
public float MinSeparationFrac = 0f;
///
/// ⚠ THE BLOB GUARD. An organic island LARGER than this fraction of the map's area is a
/// superlevel region that merged several maxima into one sprawling landmass — the "blob"
/// the developer does not want — and is reverted whole. Set well above the natural size at
/// the preset density so it is a safety net, not a sculptor; how often it bites is reported.
///
public float MaxIslandAreaFrac = 0f;
// ---- the zone mask: protections + the Trench bound --------------------
/// ⭐ THE MOAT. Reference 14 m. The presets do not move it.
public float MinDepthM = IslandFalloff.OFFSHORE_MIN_DEPTH_M;
public float DepthFeatherM = IslandFalloff.OFFSHORE_DEPTH_FEATHER_M;
/// ⭐ THE "ACTUALLY OFFSHORE" TEST on the pre-Trench falloff. Reference 0.72. The presets do not move it.
public float MinFalloff = IslandFalloff.OFFSHORE_MIN_FALLOFF;
public float FalloffFeather = IslandFalloff.OFFSHORE_FALLOFF_FEATHER;
///
/// The outer bound, as a fraction of the half-span (map-anchored, like the Trench). Zone
/// fades from INNER to zero at OUTER. Reference 0.78 / 0.86 keeps islets well off the
/// Trench ramp (which starts at 0.90) and out of the corners. The reshape pushes both OUT so
/// islands populate the corners and may sit over the outer edge — the developer accepts
/// islands clipped by the map edge. OUTER < 1 keeps every island's centre on the playable
/// map.
///
public float TrenchInner = IslandFalloff.OFFSHORE_TRENCH_INNER;
public float TrenchOuter = IslandFalloff.OFFSHORE_TRENCH_OUTER;
// ---- presets ----------------------------------------------------------
/// The reference, verbatim. Single density, no guards, original footprint/crest/mask.
public static OffshoreSettings Faithful() => new OffshoreSettings
{
Mode = OffshoreMode.Faithful,
FreqPerMapWidth = IslandFalloff.OFFSHORE_FREQ_ISLANDS,
SeedOffset = IslandFalloff.OFFSHORE_SEED_OFFSET,
Density = 0.02f, SouthWeight = 1f, // ConfigManager.OffshoreIslandDensity
CrestM = IslandFalloff.OFFSHORE_ISLAND_H_M,
CoreFraction = IslandFalloff.OFFSHORE_CORE,
EdgeSharpness = 1f,
TrenchInner = IslandFalloff.OFFSHORE_TRENCH_INNER,
TrenchOuter = IslandFalloff.OFFSHORE_TRENCH_OUTER,
};
///
/// ⭐ THE DELIVERABLE (chat2/06) — the organic layer, reshaped (chat2/05 stage 2's shape:
/// small / low / flat / crisp, corners on) and tuned for coverage: density up from 05's
/// 0.007, south-weighted, guarded against specks / clusters / blobs.
///
/// ⚠ TUNED BY MEASUREMENT, not by feel (chat2/06 batch — the count table over 12 seeds ×
/// 3 levels and the per-hemisphere diagnosis). This preset is the batch's `density_mid`.
///
public static OffshoreSettings Organic() => new OffshoreSettings
{
Mode = OffshoreMode.Organic,
FreqPerMapWidth = 16f, // a little smaller than the reference's 14 (chat2/05)
Density = OrganicDensityMid,
SouthWeight = OrganicSouthWeight,
CrestM = 24f, // lower than the reference's 34 (pre-curve) — lands in the curve's preserved toe
CoreFraction = 0.25f, // flatter than 0.45
EdgeSharpness = 2.5f, // crisper shore than the faithful smoothstep
MinIslandAreaFrac = OrganicMinIslandAreaFrac,
MinSeparationFrac = OrganicMinSeparationFrac,
MaxIslandAreaFrac = OrganicMaxIslandAreaFrac,
TrenchInner = 0.90f, TrenchOuter = 0.97f, // corners + outer edge allowed; centre stays on-map
};
///
/// The three density levels of the chat2/06 batch, in one place so the tool and the preset
/// cannot disagree. Mid is the preset of record.
///
public const float OrganicDensityLow = 0.016f; // the edge: every seed of the 12 still clears N ≥ 2 / S ≥ 3, but N's minimum IS 2
public const float OrganicDensityMid = 0.022f; // ⭐ the preset: N min 3 / mean 6.3, S min ~15 / mean ~20 over 12 seeds at 4096
public const float OrganicDensityHigh = 0.030f; // the "how many is too many" bookend
public const float OrganicSouthWeight = 1.25f; // preference, not a fix — the diagnosis found the south UN-suppressed (see the report)
public const float OrganicMinIslandAreaFrac = 3e-5f;
public const float OrganicMinSeparationFrac = 0.008f;
public const float OrganicMaxIslandAreaFrac = 6e-4f;
public OffshoreSettings Clone() => (OffshoreSettings)MemberwiseClone();
public string Describe()
{
if (Mode == OffshoreMode.Off) return "offshore OFF";
var sb = new StringBuilder();
sb.Append($"{Mode}: freq {FreqPerMapWidth:F0}/map density {Density:F4} × south {SouthWeight:F2} (= S {DensitySouth:F4}) · ");
sb.Append($"crest {CrestM:F0} m core {CoreFraction:F2} sharp {EdgeSharpness:F1} · ");
sb.Append($"guards minArea {MinIslandAreaFrac:G2} minSep {MinSeparationFrac:G2} maxArea {MaxIslandAreaFrac:G2} · ");
sb.Append($"moat {MinDepthM:F0}+{DepthFeatherM:F0} m falloff {MinFalloff:F2}+{FalloffFeather:F2} trench {TrenchInner:F2}→{TrenchOuter:F2}");
return sb.ToString();
}
}
}