islaApocalypse-v2/Tools/Scripts/OffshoreSettings.cs
beezm 3b96e06c1a chat2/05: offshore islands — rejoin the stub faithfully, then reshape to a guaranteed hybrid
Stage 1 fills the IslandFalloff.cs stub from the reference, verbatim: the coast shelf
(below-sea only, depth-preserving, held strictly below sea by MathF.BitDecrement — the
clamp that makes "cannot move the waterline" exact rather than statistical) and the islet
layer (OffshoreBlob, CalibrateThreshold against the field's ACTUAL distribution, the
OffshoreZoneWeight moat + pre-Trench-falloff test). Both run as pass 1b — OffshorePass, a
second sweep over the finished pass-1 arrays with the same per-pixel arithmetic in the
same order — and HMaxSeed is retaken AFTER them, as the reference did. That closes
chat2/00 Drift §2. The value did not move on any of 15 runs; the order is now right by
construction and oracle (l) prints it every time.

Stage 2 is the reshape, OffshoreSettings.Hybrid(): a seeded floor of ≥2 N / ≥4 S islands
placed by a PCG32 off the world seed — min-separated, clear of ALL existing land by a gap
so each stamp is its own connected component by construction, fully inside the zone so
the moat and falloff protections gate the floor exactly as they gate the organic layer —
plus the noise layer on top, smaller (freq 16), lower (24 m pre-curve, which the
preserved toe squashes to ~5 m and keeps there across curve tweaks), flatter (core 0.25),
crisper (edge sharpness 2.5, stamp rim jittered so it is rigid without being a compass
disc), south-weighted (0.007 N / 0.012 S, blended across the midline), corners allowed
(trench mask 0.90→0.97). Every lifted cell is tagged with its hemisphere — NORTH is rows
[0, N/2), y runs south, read off the spine's southern fade and the southern sinker, not
invented — and carried through Pass1Result → Pass2Result for a consumer that does not
exist yet.

Two things the probes taught, both now in the code: the first organic densities produced
183 blobs of which 174 were noise debris (a six-config sweep fixed that), and the
reference's lerp-to-crest leaves shallow humps across the seabed wherever a blob fails to
surface (a guard reverts them outside any surviving island's skirt; specks by component
membership, bumps by location). The faithful control keeps both, because it is the
reference — its islets measure min 1 cell, median 28.

Oracle, all hard checks passing: floor met on every hybrid seed (N 2–4, S 9–16); zero
land bridges; every offshore-OFF land cell bit-identical with offshore ON; tag ↔ coastline
consistent in both fields; curve-off still bit-identical to Phase 1's dump and
continuous_restored to task 03's. Both gates default OFF, deliberately: flipping them is
the act that retires the Phase-1 regression dumps, and that belongs in a task that
re-baselines the oracles.

The faithful control on seed 8675309 produced one north island. That is the gap the
floor exists to close.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCWNaDZPfTiAy3meGNGgqt
2026-08-21 05:14:53 -04:00

210 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Text;
namespace IslaApocalypse.Tools
{
/// <summary>Which offshore-islet system pass 1 runs. → <see cref="OffshoreSettings"/>.</summary>
public enum OffshoreMode
{
/// <summary>No islets. The shelf is gated separately (<c>TerrainGenConfig.CoastShelf</c>).</summary>
Off,
/// <summary>
/// ⭐ 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.
/// </summary>
Faithful,
/// <summary>
/// ⭐ 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.
/// </summary>
Hybrid,
}
/// <summary>
/// ⭐ EVERY OFFSHORE-ISLET DIAL, IN ONE OBJECT — config-gated and isolated, per the developer's
/// standing modularity concern. Nothing above <see cref="OffshorePass"/> hardcodes an island
/// specific; the whole system is this object + <c>IslandFalloff</c> + <c>OffshorePass</c>.
///
/// ═══ TWO PRESETS, AND WHY BOTH EXIST ═══
///
/// <see cref="Faithful"/> the reference's constants, verbatim. The control in every batch.
/// <see cref="Hybrid"/> the reshape — the deliverable.
///
/// The reshape does not EDIT the faithful constants; it sets different values on the same
/// dials. So <c>Faithful()</c> stays bit-reproducible however far the hybrid is tuned.
///
/// ═══ THE TWO PROTECTIONS THAT ARE NOT DIALS ═══
///
/// <see cref="MinDepthM"/> (the moat) and <see cref="MinFalloff"/> (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 <c>OffshorePass</c> applies
/// them to the SEEDED stamps as well, so a guaranteed island cannot be guaranteed into the wrong
/// place.
/// </summary>
public sealed class OffshoreSettings
{
public OffshoreMode Mode = OffshoreMode.Off;
// ---- the organic (noise) layer ----------------------------------------
/// <summary>Run the noise layer at all. Off ⇒ only the seeded floor (the `floor_only` contrast).</summary>
public bool Organic = true;
/// <summary>Islet noise frequency, periods per map width. Higher ⇒ SMALLER blobs. Reference 14.</summary>
public float FreqPerMapWidth = IslandFalloff.OFFSHORE_FREQ_ISLANDS;
/// <summary>Islet noise seed offset. Reference 7607.</summary>
public int SeedOffset = IslandFalloff.OFFSHORE_SEED_OFFSET;
/// <summary>
/// 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 &gt;
/// north ⇒ the developer's "weighted south". Reference 0.02 / 0.02.
/// </summary>
public float DensityNorth = 0.02f;
public float DensitySouth = 0.02f;
/// <summary>
/// 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).
/// </summary>
public float HemisphereBlendHalfWidth = 0.05f;
/// <summary>Islet crest, metres above sea, PRE-CURVE. Reference 34. The curve's toe squashes it lower.</summary>
public float CrestM = IslandFalloff.OFFSHORE_ISLAND_H_M;
/// <summary>Fraction of a blob's excess over threshold that saturates. LOWER ⇒ flatter. Reference 0.45.</summary>
public float CoreFraction = IslandFalloff.OFFSHORE_CORE;
/// <summary>Crest-to-sea edge sharpening exponent. 1 ⇒ the faithful smoothstep. Higher ⇒ crisper shore.</summary>
public float EdgeSharpness = 1f;
/// <summary>
/// ⚠ 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.
/// </summary>
public float MinIslandAreaFrac = 0f;
// ---- the zone mask: protections + the Trench bound --------------------
/// <summary>⭐ THE MOAT. Reference 14 m. The presets do not move it.</summary>
public float MinDepthM = IslandFalloff.OFFSHORE_MIN_DEPTH_M;
public float DepthFeatherM = IslandFalloff.OFFSHORE_DEPTH_FEATHER_M;
/// <summary>⭐ THE "ACTUALLY OFFSHORE" TEST on the pre-Trench falloff. Reference 0.72. The presets do not move it.</summary>
public float MinFalloff = IslandFalloff.OFFSHORE_MIN_FALLOFF;
public float FalloffFeather = IslandFalloff.OFFSHORE_FALLOFF_FEATHER;
/// <summary>
/// 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 &lt; 1 keeps every island's centre on the playable
/// map.
/// </summary>
public float TrenchInner = IslandFalloff.OFFSHORE_TRENCH_INNER;
public float TrenchOuter = IslandFalloff.OFFSHORE_TRENCH_OUTER;
// ---- the seeded floor (Hybrid only) -----------------------------------
/// <summary>⭐ The guaranteed minimum island count per hemisphere. 0 / 0 ⇒ no floor (Faithful).</summary>
public int FloorNorth = 0;
public int FloorSouth = 0;
/// <summary>Stamp radius, fraction of the map. Small and low is the target.</summary>
public float StampRadiusFrac = 0.012f;
/// <summary>Fraction of the stamp radius that is flat top. Higher ⇒ flatter, crisper shore.</summary>
public float StampCoreFrac = 0.60f;
/// <summary>
/// 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.
/// </summary>
public float StampEdgeJitter = 0f;
/// <summary>Minimum centre-to-centre separation between seeded islands, fraction of the map.</summary>
public float SeparationFrac = 0.08f;
/// <summary>
/// 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.
/// </summary>
public float LandGapFrac = 0.010f;
/// <summary>The placement RNG's seed offset. A SEED offset; positions vary per world seed.</summary>
public int PlacementSeedOffset = 7703;
/// <summary>Rejection-sampling budget per hemisphere before the pass REFUSES (loudly) rather than under-delivers.</summary>
public int MaxPlacementAttempts = 40000;
// ---- presets ----------------------------------------------------------
/// <summary>The reference, verbatim. No floor, single density, original footprint/crest/mask.</summary>
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,
};
/// <summary>
/// ⭐ The reshape: small / low / flatter / more rigid, loose-guaranteed (≥2 N, ≥4 S), organic
/// extras weighted south, corners and the outer edge allowed.
/// </summary>
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 ~+12 N / +68 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();
}
}
}