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
/// floor. THE CONTROL.
///
Faithful,
///
/// ⭐ chat2/05 stage 2 — the loose-guaranteed hybrid: a seeded floor of ≥N north / ≥S south
/// islands (min-separated, positions varying per seed, never fixed zones) PLUS the organic
/// layer on top, reshaped small / low / flat / rigid, south-weighted, corners allowed.
///
Hybrid,
}
///
/// ⭐ 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 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 hybrid 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 — and OffshorePass applies
/// them to the SEEDED stamps as well, so a guaranteed island cannot be guaranteed into the wrong
/// place.
///
public sealed class OffshoreSettings
{
public OffshoreMode Mode = OffshoreMode.Off;
// ---- the organic (noise) layer ----------------------------------------
/// Run the noise layer at all. Off ⇒ only the seeded floor (the `floor_only` contrast).
public bool Organic = true;
/// 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;
///
/// Organic density — the fraction of the noise field's ACTUAL sampled distribution that
/// clears the threshold — per hemisphere. Equal ⇒ the reference's single dial. South >
/// north ⇒ the developer's "weighted south". Reference 0.02 / 0.02.
///
public float DensityNorth = 0.02f;
public float DensitySouth = 0.02f;
///
/// 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 DEBRIS 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. 0 ⇒ off (the reference had no such
/// rule and its "~585 px blobs" comment was about the NOISE wavelength, not the caps).
/// Stated as a fraction so the rule is scale-free: 3e-5 is ~126 cells at 2048, ~2,000 cells
/// (a ~50 m islet) at 8192.
///
public float MinIslandAreaFrac = 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;
// ---- the seeded floor (Hybrid only) -----------------------------------
/// ⭐ The guaranteed minimum island count per hemisphere. 0 / 0 ⇒ no floor (Faithful).
public int FloorNorth = 0;
public int FloorSouth = 0;
/// Stamp radius, fraction of the map. Small and low is the target.
public float StampRadiusFrac = 0.012f;
/// Fraction of the stamp radius that is flat top. Higher ⇒ flatter, crisper shore.
public float StampCoreFrac = 0.60f;
///
/// Radial jitter on the stamp's rim, as a fraction of the radius, from a fine noise field:
/// the outline wanders ±this much around the circle. 0 ⇒ a compass-drawn disc (the first
/// probe plate: rigid, but visibly geometric). 0.25 keeps the crisp shore and the flat top
/// while the coastline stops looking stamped.
///
public float StampEdgeJitter = 0f;
/// Minimum centre-to-centre separation between seeded islands, fraction of the map.
public float SeparationFrac = 0.08f;
///
/// Minimum clear water between a seeded stamp's rim and ANY existing land (mainland or
/// organic), fraction of the map. This is what makes every seeded island its own connected
/// component by construction — and therefore what makes the floor COUNT hold, not just the
/// number of stamps.
///
public float LandGapFrac = 0.010f;
/// The placement RNG's seed offset. A SEED offset; positions vary per world seed.
public int PlacementSeedOffset = 7703;
/// Rejection-sampling budget per hemisphere before the pass REFUSES (loudly) rather than under-delivers.
public int MaxPlacementAttempts = 40000;
// ---- presets ----------------------------------------------------------
/// The reference, verbatim. No floor, single density, original footprint/crest/mask.
public static OffshoreSettings Faithful() => new OffshoreSettings
{
Mode = OffshoreMode.Faithful,
Organic = true,
FreqPerMapWidth = IslandFalloff.OFFSHORE_FREQ_ISLANDS,
SeedOffset = IslandFalloff.OFFSHORE_SEED_OFFSET,
DensityNorth = 0.02f, DensitySouth = 0.02f, // ConfigManager.OffshoreIslandDensity
CrestM = IslandFalloff.OFFSHORE_ISLAND_H_M,
CoreFraction = IslandFalloff.OFFSHORE_CORE,
EdgeSharpness = 1f,
TrenchInner = IslandFalloff.OFFSHORE_TRENCH_INNER,
TrenchOuter = IslandFalloff.OFFSHORE_TRENCH_OUTER,
FloorNorth = 0, FloorSouth = 0,
};
///
/// ⭐ The reshape: small / low / flatter / more rigid, loose-guaranteed (≥2 N, ≥4 S), organic
/// extras weighted south, corners and the outer edge allowed.
///
public static OffshoreSettings Hybrid() => new OffshoreSettings
{
Mode = OffshoreMode.Hybrid,
Organic = true,
// ⚠ Chosen by SWEEP, not by feel (chat2/05 scratch): at freq 24 / 0.010 / 0.025 the field
// produced 183 organic blobs of which 174 were debris. freq 16 with these densities gives
// the floor plus ~+1–2 N / +6–8 S genuine extras of a few hundred cells each at 2048.
FreqPerMapWidth = 16f, // a little smaller than the reference's 14
DensityNorth = 0.007f, DensitySouth = 0.012f, // weighted south
CrestM = 24f, // lower than the reference's 34 (pre-curve)
CoreFraction = 0.25f, // flatter than 0.45
EdgeSharpness = 2.5f, // crisper shore than the faithful smoothstep
MinIslandAreaFrac = 3e-5f, // no noise debris
TrenchInner = 0.90f, TrenchOuter = 0.97f, // corners + outer edge allowed; centre stays on-map
FloorNorth = 2, FloorSouth = 4,
StampRadiusFrac = 0.012f, StampCoreFrac = 0.60f, StampEdgeJitter = 0.25f,
SeparationFrac = 0.08f, LandGapFrac = 0.010f,
};
public OffshoreSettings Clone() => (OffshoreSettings)MemberwiseClone();
public string Describe()
{
if (Mode == OffshoreMode.Off) return "offshore OFF";
var sb = new StringBuilder();
sb.Append($"{Mode}: organic {(Organic ? "on" : "OFF")} freq {FreqPerMapWidth:F0}/map density N {DensityNorth:F3} S {DensitySouth:F3} · ");
sb.Append($"crest {CrestM:F0} m core {CoreFraction:F2} sharp {EdgeSharpness:F1} minArea {MinIslandAreaFrac:G2} · ");
sb.Append($"moat {MinDepthM:F0}+{DepthFeatherM:F0} m falloff {MinFalloff:F2}+{FalloffFeather:F2} trench {TrenchInner:F2}→{TrenchOuter:F2}");
if (FloorNorth > 0 || FloorSouth > 0)
sb.Append($" · floor N{FloorNorth} S{FloorSouth} r {StampRadiusFrac:F3} core {StampCoreFrac:F2} sep {SeparationFrac:F2} gap {LandGapFrac:F3}");
return sb.ToString();
}
}
}