using IslaApocalypse.Core;
namespace IslaApocalypse.Tools
{
///
/// Which redistribution curve pass 2a applies (chat2/02). One seam, three occupants:
///
/// Staircase ⭐ the faithful v5 port (task 01) — toe/red/riser/bench/riser/plateau/spike.
/// THE CONTROL. Bit-identical to task 01's output, always present in a batch.
/// Continuous ⭐ the rev-3 redesign — the staircase's toe+red lowland PRESERVED bit-for-bit,
/// everything above it replaced by one smooth monotone climb to the 420 m cap.
/// → Core/ContinuousCurve.
/// LiftedWrong ⚠⚠ DELIBERATELY THE WRONG DIRECTION — an even linear remap of ALL land onto
/// [SEA, PEAK_CAP], which lifts the entire island off its shoreline and destroys
/// the low plain the developer likes. It exists as a CONTRAST BOOKEND so the
/// preserved-lowland variants can be judged against the mistake, and for no other
/// purpose. Do not ship it, do not tune it, do not "fix" it.
///
public enum CurveModeKind { Staircase, Continuous, LiftedWrong }
///
/// The generator's configuration, including the per-element ABLATION TOGGLES.
///
/// ═══ WHY EVERY PASS-1 ELEMENT IS GATED ═══
///
/// This is the standing A/B discipline: every shaping change ships behind a gate with the
/// previous behaviour surviving on the other side, so changes stay comparable as a pair rather
/// than as a memory of last week's render, and a rejected change costs a flipped default rather
/// than a reverted commit. → `Design - Tooling - Iteration and Batching.md`.
///
/// For THIS task the gates do a second job: they are the PORT-FIDELITY CHECK. Generating
/// base-noise-only, then adding one element at a time, shows each ported element doing what the
/// reference's did — rather than judging six simultaneous changes by their sum.
///
/// ⚠ Lives in Tools/, not Core/. It configures the generator specifically; Core carries the
/// world's data contracts and the scaling rule, not a tool's dials.
///
public sealed class TerrainGenConfig
{
// ---- world ----------------------------------------------------------
///
/// Map side in columns. A GENERATION PARAMETER — never baked in.
/// Iteration runs small (2048–4096); the shape is scale-invariant by construction, so the
/// island reads the same at any size and a full-size pass is confirmation, not iteration.
///
public int MapSize = 2048;
///
/// The noise seed. POSITIVE ONLY. Zero or negative means "pick one and print it" — a run
/// whose seed is not recorded is a run that cannot be reproduced, so the resolved seed is
/// always printed and always in the filename.
///
public int Seed = 0;
// ---- island shape (reference ConfigManager defaults, READ from source) ----
/// Falloff X axis ratio. Reference: ConfigManager.LEGACY_AXIS_X = 1.15f.
public float IslandAxisX = 1.15f;
/// Falloff Y axis ratio. Reference: ConfigManager.LEGACY_AXIS_Y = 0.90f.
public float IslandAxisY = 0.90f;
///
/// Multiplier on the falloff term in the combine.
/// Reference: [Export] public float FalloffStrength = 1.0f; (MapGenerator ~:11),
/// and NOT overridden in MapPreview.tscn — so 1.0f is the value the island was tuned at.
///
public float FalloffStrength = 1.0f;
///
/// The flat sea level, in raw height units. Reference: ConfigManager.SeaLevelValue = 0.15f
/// with SeaLevelModel = "flat", so GetSeaLevel ignores latitude entirely.
///
/// ⚠ PHASE 1 USES THIS AS A VISUALIZATION THRESHOLD ONLY — the boundary between the
/// bathymetric and hypsometric colour ramps. No water is modelled, no water bodies are
/// identified, nothing floods. That is Phase 2.
///
public float SeaLevel = 0.15f;
// ---- ABLATION TOGGLES — the pass-1 ladder ---------------------------
/// Rung 1: the base terrain noise, (noise(x,y)+1)/2. Off = flat zero.
public bool BaseNoise = true;
///
/// Rung 2: the island falloff/mask — squircle + ellipse blend, and the Pow(·, 2.5f).
/// ⚠ Off also disables rungs 3–5 in effect: edge noise, the sinker and the Trench are all
/// modifiers OF the falloff, so with no falloff there is nothing for them to modify. The
/// toggles stay independent so the ladder reads honestly; the report says so.
///
public bool IslandFalloff = true;
/// Rung 3: coastline edge roughness, modulated by the squircle.
public bool EdgeNoise = true;
/// Rung 4: the southern sinker — extra sinking pressure in the bottom 25%.
public bool SouthernSinker = true;
/// Rung 5: the Trench — the map-anchored outer-band wall that guarantees an ocean border.
public bool Trench = true;
/// Rung 6: the mountain spine up the centre-X axis.
public bool MountainSpine = true;
// ---- PASS 2a — the redistribution curve and shelf detail (Phase 2) ----
//
// ⚠ THE PRIMARY A/B OF THIS PHASE IS `Curve`. Off must reproduce Phase 1's pass-1 output
// BIT-IDENTICALLY — that is the regression oracle, not a figure of speech.
///
/// ⭐ Pass 2a rung 1: the height-redistribution curve. → .
/// Off = raw pass-1 height, unshaped (the control half of every A/B in this phase).
///
public bool Curve = true;
///
/// ⭐ WHICH curve (chat2/02). → .
///
/// Default CONTINUOUS per the rev-3 task — the exploration direction. Tools that exist to
/// reproduce the task-01 staircase (CurveBaselineTool) set Staircase EXPLICITLY, so the
/// default changing does not silently move a control batch.
///
public CurveModeKind CurveMode = CurveModeKind.Continuous;
// ---- the continuous climb's knobs — ALL act above the lowland ceiling only ----
///
/// How high the preserved lowland holds before the climb takes over, in METRES of output
/// height above sea. Default 30 = RED_CEIL, the flood line — the exact top of the
/// staircase's toe+red band, so nothing at all is re-mapped below it.
///
/// ⚠ Constrained to [30, 80] m by : below 30 would cut
/// the preserved band; at ~100 it could preserve a flat bench, the artifact this mode
/// exists to remove. Raising it extends the red band's gentle grade linearly before the
/// climb begins. THE PRIMARY VARIANT AXIS.
///
public float LowlandCeilingM = 30f;
///
/// The shape of the climb's departure from the lowland, 0..1: how long it hugs the red
/// band's exit slope before steepening. Replaces the old ambiguous "bow". Acts only above
/// the ceiling; CANNOT touch the low band.
///
public float ClimbFeather = 0.4f;
///
/// The summit's steepening, ≥ 1: the secant slope of the top 15 % of the climb, in units of
/// the climb's average grade. 1 = a ramp (refused); 2.5 = the default pointed peak; higher =
/// more dramatic. The peak reads pointy, never a needle-on-a-hump — there is no plateau
/// under it any more.
///
public float SummitDrama = 2.5f;
///
/// ⭐ Pass 2a rung 2: the shelf detail passes — micro-relief skin + shelf-edge knot warp.
/// ⚠ REQUIRES : the edge warp slides the CURVE's knots, so with no curve
/// there is nothing to warp. Requesting it with the curve off is a logged no-op, not an error.
///
public bool ShelfDetail = true;
///
/// Micro-relief amplitude, in METRES of output height. Reference default: 3 m.
/// Converted through at the call site — never a literal /251.
///
public float ShelfReliefAmpM = TerrainDetailPass.ReliefAmpDefaultM;
///
/// Shelf-edge warp amplitude, in METRES OF INPUT HEIGHT (not output elevation — see
/// ). Reference default: 12 m.
///
/// ⚠ CLAMPED, LOUDLY, to the knot set's safe bound (TerrainDetailPass.MaxEdgeShift).
/// Monotonicity is never a tuning question; an ignored dial is always reported.
///
public float ShelfEdgeVariationM = TerrainDetailPass.EdgeAmpDefaultM;
///
/// The input knot set — WHERE the land distribution is cut.
/// Default: , re-measured on v2's own pass-1 output.
/// is available for a fidelity A/B against the prototype's.
///
public CurveKnots Knots = CurveKnots.V2Baseline;
///
/// The output anchors — WHAT HEIGHT each cut lands at. Default: the storm-ladder values,
/// reproducing the reference's constants bit-for-bit.
///
public CurveAnchors Anchors = CurveAnchors.Default;
// ---- the crater seam — INERT THIS PHASE -----------------------------
///
/// Crater radius in columns. ⚠ 0 = NO CRATER, which is this phase's state. The detail
/// pass's crater exclusion is ported and wired, but with no crater it evaluates to "detail
/// everywhere" and the distance is never computed. It is exercised when the carve lands.
///
public float CraterRadius = 0f;
/// Crater centre X, columns. Unused while is 0.
public float CraterCenterX = 0f;
/// Crater centre Y, columns. Unused while is 0.
public float CraterCenterY = 0f;
/// A short label for this variant, used in output filenames. E.g. "full", "base_only".
public string VariantLabel = "full";
/// The scale object every distance and frequency in the generator derives from.
public GenerationScale Scale => new GenerationScale(MapSize);
///
/// ⚠ DEEP on . MemberwiseClone is shallow, so two configs cloned
/// from one parent would share a single mutable anchor object and an A/B that edited one
/// would silently move the other. The one reference type that is a DIAL gets copied; the one
/// that is immutable () does not need to be.
///
public TerrainGenConfig Clone()
{
var c = (TerrainGenConfig)MemberwiseClone();
c.Anchors = Anchors?.Clone();
return c;
}
public override string ToString() =>
$"MapSize={MapSize} Seed={Seed} axis={IslandAxisX:F2}x/{IslandAxisY:F2}y " +
$"falloffStrength={FalloffStrength:F2} sea={SeaLevel:F2} variant={VariantLabel} " +
$"[base={BaseNoise} falloff={IslandFalloff} edge={EdgeNoise} sinker={SouthernSinker} " +
$"trench={Trench} spine={MountainSpine}] " +
$"[curve={Curve} detail={ShelfDetail} relief={ShelfReliefAmpM:F1}m edge={ShelfEdgeVariationM:F1}m " +
$"knots={(Knots == null ? "-" : Knots.Name)}]";
}
}