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; /// /// ⚠ chat2/02's ANALYTIC summit knob — SUPERSEDED by . /// /// It steepened the peak by pulling the summit ONSET DOWN, which lowered the whole mid-massif /// with it: at 4.5 the p99 land height collapsed from 199 m to 121 m. A bad trade, and the /// bug chat2/03 §3 exists to fix. It survives ONLY so the 02 curve stays reproducible as a /// contrast variant; it is read only when is null. /// public float SummitDrama = 2.5f; // ---- chat2/03: the CALIBRATED climb --------------------------------- /// /// ⭐ The measured climb calibration. Non-null ⇒ the climb reproduces the staircase's /// above-ceiling elevation distribution as a smooth slope. Null ⇒ chat2/02's analytic /// feather/drama curve (kept only as the "before" contrast). /// /// ⚠ Not a value knob — it is MEASURED, per calibration pool, by the batch tool. Two configs /// may share one instance safely: it is immutable. /// public ClimbCalibration ClimbCalibration = null; /// /// ⭐ How big the mountain is, relative to the staircase's. /// /// 1.0 reproduce the staircase's mountain (the default — the least-surprising baseline) /// >1 lift the mid-massif higher: more land at 150–300 m /// <1 a smaller mountain, toward chat2/02's bottom-heavy climb /// /// Applied as v ← v^(1/lift) on the calibrated control points: monotone, and it fixes /// both endpoints, so it moves the massif without touching the lowland handover or the cap. /// ⚠ It scales the CLIMB only. It cannot move a lowland cell — oracle (d) proves that. /// public float MountainLift = 1.0f; /// /// ⭐ How pointy the summit is — and, unlike , nothing else. /// /// It reshapes only the span above the last measured percentile, leaving that percentile's /// height fixed. Raising it therefore cannot reduce the land below the onset: peak sharpness /// and mountain mass are independent knobs. → chat2/03 §3. /// /// 1.0 = a straight run to the cap; higher defers the rise so the final approach steepens. /// public float PeakSharpness = 1.0f; /// /// ⭐ 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; // ---- PASS 1b — the coast shelf + offshore islets (chat2/05) ---------- // // ⚠⚠ BOTH DEFAULT OFF, DELIBERATELY — and that is a decision to revisit, not an oversight. // // Every oracle in this phase holds pass 1 against Phase 1's `.f32` dumps (curve-off == // `02_pass1_port`), and the curve tools hold it against task 01/03's. The shelf changes every // below-sea cell and the islets ADD LAND, so the moment either defaults ON, every one of // those regression anchors goes stale at once. The batch tools that want them turn them on // explicitly. FLIPPING THESE DEFAULTS IS THE ACT THAT RETIRES THE PHASE-1 REGRESSION DUMPS — // do it deliberately, in a task that re-baselines the oracles, not as a side effect here. /// /// The submarine coast shelf (IslandFalloff.CoastShelf). Below-sea only, /// depth-preserving, held strictly below sea by MathF.BitDecrement. Invisible until /// water renders; ported faithfully now, judged then. /// public bool CoastShelf = false; // ⚠ Fully qualified: this class's own `IslandFalloff` ablation toggle shadows the static // type of the same name inside field initializers. /// Shelf strength, 0 = off → 1 = a flat lagoon. Reference 0.775. public float ShelfStrength = IslaApocalypse.Tools.IslandFalloff.SHELF_STRENGTH; /// Metres of depth over which the shelf relaxes. Reference 100. public float ShelfScaleM = IslaApocalypse.Tools.IslandFalloff.SHELF_SCALE_M; /// /// ⭐ The offshore islet system — every dial in one object. Mode = Off by default /// (see the note above). is the reference verbatim; /// is the reshape, tuned (chat2/06). /// public OffshoreSettings Offshore = new OffshoreSettings(); // ---- PASS 1c — region labeling + the speck revert (chat2/07) ---------- /// /// ⭐ THE REGION-LABELING LAYER (Core.RegionLabeling, via RegionPass): 8-connected /// land components on the classify field, mainland = the centre component, the island tag BY /// CONSTRUCTION. Pure analysis — it changes no height — so it is ON by default without touching /// any regression anchor. Off ⇒ no tag, no region table (the tag arrays are null). /// public bool RegionLabeling = true; /// /// ⭐ THE SPECK REVERT — lower every non-mainland land component smaller than /// of the map to seabed. Origin-blind; lower-only and /// component-only, asserted; mainland never a candidate. /// /// ⚠ DEFAULT OFF IN THE BARE CONFIG, for exactly the reason the shelf and the islets are: the /// raw field has small natural nubs, so with this ON the calibration pool's land histogram, the /// curve knots and every Phase-1 / task-03 / task-04 regression dump would move at once. /// The region batch turns it on explicitly (its preset is ON); flipping the bare default is /// the act that re-baselines the oracles — own task, not a side effect. /// public bool SpeckRevert = false; /// The revert threshold, as a fraction of the map's AREA (scale-free). → . public float MinLandComponentFrac = RegionPass.ThresholdMidFrac; // ---- PASS 1 — THE SOUTHERN STRETCH (chat2/08, exploration) ---------------- // // ⚠ THE ONE DELIBERATE RELAXATION OF SEA IDENTITY — and only below a FIXED latitude band. // Inside the band (feathered, keyed off a fixed y, never distance-from-coast) the falloff's // southward distance is compressed: y' = yB + (y − yB) / (1 + stretch · ramp). The mask // geometry is stretched south; the base noise, edge noise and latitude field are NOT — so // the extended mass keeps the elevation/relief of the rows it came from (preserve height // as the mass extends), and where the stretched thin edge thins below sea it fragments // organically. Cells north of the band take the UNTOUCHED code path, so the classify field // there is bit-identical by construction (asserted). Nothing is stamped. /// ⭐ THE SWEPT AXIS. 0 = off (bit-identical to the unstretched field everywhere). Stretch factor inside the band: 1 ⇒ the southward distance is halved, 3 ⇒ quartered. public float SouthStretch = 0f; /// The band's FIXED latitude line, fraction of the map (y runs south). Sea identity is hard above it. A constant for a whole batch. public float SouthBandStartFrac = SouthernStretch.DefaultBandStartFrac; /// The feather width across which the stretch ramps 0 → 1 (smoothstep), fraction of the map. A constant for a whole batch. public float SouthBandFeatherFrac = SouthernStretch.DefaultBandFeatherFrac; /// /// Does the SOUTHERN SINKER ride the stretched distance (true — it is part of the southern /// geometry and is pushed out with it, i.e. held back inside the band) or the real y (false — /// it keeps pulling the extended mass down where it always did)? The chat2/08 diagnostic /// measured both; → . /// public bool StretchSinker = SouthernStretch.DefaultStretchSinker; /// 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(); c.Offshore = Offshore?.Clone(); // same reason: a mutable dial object, deep-copied 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)}]"; } }