A dedicated term, not the edge noise scaled: pass 1's edge noise is positive-only, squircle- modulated, 2.5x the base frequency - the coastline's jitter; scaling it would roughen the whole rim and bias the coast inward. CoastalFragment adds, to the PRE-power falloff, amp * window(f) * noise(x + off, y + off) with its own deterministic field (seed offset 9109, coordinate offset 0.37 map widths - D-059), zero-mean, at 12 periods per map width (the lobe/neck scale, chosen by probe against 20 and 32: 20 climbs into smalls, 32 is fuzz), weighted by a smooth window on the falloff value itself, centred 0.66 (the coast's falloff for a median base noise, from the 08 diagnosis), half-width 0.18, EXACTLY ZERO beyond - so the interior, the massif and the deep sea are bit-identical by construction. Thin necks of barely-land flip first; nothing is detected, nothing is stamped. FragmentBitesOnly is exposed (probed: it erodes the coast inward - mainland -3/-8/-14 % at 0.06/0.15/0.30 - rather than detaching pieces; off by default). Batch: BatchRoot(9, "coastal_fragment") - 4 amplitudes (0.06, 0.15, 0.30, 0.50) x task 08's two seeds at 4096, stretch fixed at 2 (08's stretch_3 rung, whose dump is the bit-identical baseline, a8), sinker untouched, speck revert at < 4 cells, offshore/shelf off; lean render (regions overlay + relief + .f32), the hemisphere-split count/size table with largest-three and histograms. Oracle, all passing: a1 (the curve untouched), a8 x2 (frag OFF at stretch 2 == the 08 field, 16.8 M cells each), interior locked (r: every cell clear of the window bit-identical, 8/8), high-ground report (s, informational), centre is land, tag/coastline, classify == raw, ids and heights deterministic. The read: both hemispheres fragment; the north more readily (N 10 -> 28, 13 -> 40 islands across the ladder; S 4 -> 31, 14 -> 28) and the south's big stretch pieces are the zero-mean noise's other edge: on 999999937 the 83k-cell southern fragment is BRIDGED back onto the mainland at amp 0.50 (mainland +183k). The working range is amp 0.15-0.30; 0.50 is the bookend. Graduation held. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013EY3ZTF6NwzF8ukBHQXSK7
384 lines
20 KiB
C#
384 lines
20 KiB
C#
using IslaApocalypse.Core;
|
||
|
||
namespace IslaApocalypse.Tools
|
||
{
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
public enum CurveModeKind { Staircase, Continuous, LiftedWrong }
|
||
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
public sealed class TerrainGenConfig
|
||
{
|
||
// ---- world ----------------------------------------------------------
|
||
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
public int MapSize = 2048;
|
||
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
public int Seed = 0;
|
||
|
||
// ---- island shape (reference ConfigManager defaults, READ from source) ----
|
||
|
||
/// <summary>Falloff X axis ratio. Reference: <c>ConfigManager.LEGACY_AXIS_X = 1.15f</c>.</summary>
|
||
public float IslandAxisX = 1.15f;
|
||
|
||
/// <summary>Falloff Y axis ratio. Reference: <c>ConfigManager.LEGACY_AXIS_Y = 0.90f</c>.</summary>
|
||
public float IslandAxisY = 0.90f;
|
||
|
||
/// <summary>
|
||
/// Multiplier on the falloff term in the combine.
|
||
/// Reference: <c>[Export] public float FalloffStrength = 1.0f;</c> (MapGenerator ~:11),
|
||
/// and NOT overridden in MapPreview.tscn — so 1.0f is the value the island was tuned at.
|
||
/// </summary>
|
||
public float FalloffStrength = 1.0f;
|
||
|
||
/// <summary>
|
||
/// The flat sea level, in raw height units. Reference: <c>ConfigManager.SeaLevelValue = 0.15f</c>
|
||
/// with <c>SeaLevelModel = "flat"</c>, so <c>GetSeaLevel</c> 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.
|
||
/// </summary>
|
||
public float SeaLevel = 0.15f;
|
||
|
||
// ---- ABLATION TOGGLES — the pass-1 ladder ---------------------------
|
||
|
||
/// <summary>Rung 1: the base terrain noise, <c>(noise(x,y)+1)/2</c>. Off = flat zero.</summary>
|
||
public bool BaseNoise = true;
|
||
|
||
/// <summary>
|
||
/// Rung 2: the island falloff/mask — squircle + ellipse blend, and the <c>Pow(·, 2.5f)</c>.
|
||
/// ⚠ 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.
|
||
/// </summary>
|
||
public bool IslandFalloff = true;
|
||
|
||
/// <summary>Rung 3: coastline edge roughness, modulated by the squircle.</summary>
|
||
public bool EdgeNoise = true;
|
||
|
||
/// <summary>Rung 4: the southern sinker — extra sinking pressure in the bottom 25%.</summary>
|
||
public bool SouthernSinker = true;
|
||
|
||
/// <summary>Rung 5: the Trench — the map-anchored outer-band wall that guarantees an ocean border.</summary>
|
||
public bool Trench = true;
|
||
|
||
/// <summary>Rung 6: the mountain spine up the centre-X axis.</summary>
|
||
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.
|
||
|
||
/// <summary>
|
||
/// ⭐ Pass 2a rung 1: the height-redistribution curve. → <see cref="HeightCurve"/>.
|
||
/// Off = raw pass-1 height, unshaped (the control half of every A/B in this phase).
|
||
/// </summary>
|
||
public bool Curve = true;
|
||
|
||
/// <summary>
|
||
/// ⭐ WHICH curve (chat2/02). → <see cref="CurveModeKind"/>.
|
||
///
|
||
/// 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.
|
||
/// </summary>
|
||
public CurveModeKind CurveMode = CurveModeKind.Continuous;
|
||
|
||
// ---- the continuous climb's knobs — ALL act above the lowland ceiling only ----
|
||
|
||
/// <summary>
|
||
/// 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 <see cref="ContinuousCurve.Build"/>: 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.
|
||
/// </summary>
|
||
public float LowlandCeilingM = 30f;
|
||
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
public float ClimbFeather = 0.4f;
|
||
|
||
/// <summary>
|
||
/// ⚠ chat2/02's ANALYTIC summit knob — SUPERSEDED by <see cref="PeakSharpness"/>.
|
||
///
|
||
/// 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 <see cref="ClimbCalibration"/> is null.
|
||
/// </summary>
|
||
public float SummitDrama = 2.5f;
|
||
|
||
// ---- chat2/03: the CALIBRATED climb ---------------------------------
|
||
|
||
/// <summary>
|
||
/// ⭐ 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.
|
||
/// </summary>
|
||
public ClimbCalibration ClimbCalibration = null;
|
||
|
||
/// <summary>
|
||
/// ⭐ 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 <c>v ← v^(1/lift)</c> 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.
|
||
/// </summary>
|
||
public float MountainLift = 1.0f;
|
||
|
||
/// <summary>
|
||
/// ⭐ How pointy the summit is — and, unlike <see cref="SummitDrama"/>, <b>nothing else</b>.
|
||
///
|
||
/// 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.
|
||
/// </summary>
|
||
public float PeakSharpness = 1.0f;
|
||
|
||
/// <summary>
|
||
/// ⭐ Pass 2a rung 2: the shelf detail passes — micro-relief skin + shelf-edge knot warp.
|
||
/// ⚠ REQUIRES <see cref="Curve"/>: 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.
|
||
/// </summary>
|
||
public bool ShelfDetail = true;
|
||
|
||
/// <summary>
|
||
/// Micro-relief amplitude, in METRES of output height. Reference default: 3 m.
|
||
/// Converted through <see cref="WorldScale"/> at the call site — never a literal /251.
|
||
/// </summary>
|
||
public float ShelfReliefAmpM = TerrainDetailPass.ReliefAmpDefaultM;
|
||
|
||
/// <summary>
|
||
/// Shelf-edge warp amplitude, in METRES OF INPUT HEIGHT (not output elevation — see
|
||
/// <see cref="TerrainDetailPass"/>). Reference default: 12 m.
|
||
///
|
||
/// ⚠ CLAMPED, LOUDLY, to the knot set's safe bound (<c>TerrainDetailPass.MaxEdgeShift</c>).
|
||
/// Monotonicity is never a tuning question; an ignored dial is always reported.
|
||
/// </summary>
|
||
public float ShelfEdgeVariationM = TerrainDetailPass.EdgeAmpDefaultM;
|
||
|
||
/// <summary>
|
||
/// The input knot set — WHERE the land distribution is cut.
|
||
/// Default: <see cref="CurveKnots.V2Baseline"/>, re-measured on v2's own pass-1 output.
|
||
/// <see cref="CurveKnots.Reference"/> is available for a fidelity A/B against the prototype's.
|
||
/// </summary>
|
||
public CurveKnots Knots = CurveKnots.V2Baseline;
|
||
|
||
/// <summary>
|
||
/// The output anchors — WHAT HEIGHT each cut lands at. Default: the storm-ladder values,
|
||
/// reproducing the reference's constants bit-for-bit.
|
||
/// </summary>
|
||
public CurveAnchors Anchors = CurveAnchors.Default;
|
||
|
||
// ---- the crater seam — INERT THIS PHASE -----------------------------
|
||
|
||
/// <summary>
|
||
/// Crater radius in columns. ⚠ <b>0 = NO CRATER, which is this phase's state.</b> 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.
|
||
/// </summary>
|
||
public float CraterRadius = 0f;
|
||
|
||
/// <summary>Crater centre X, columns. Unused while <see cref="CraterRadius"/> is 0.</summary>
|
||
public float CraterCenterX = 0f;
|
||
|
||
/// <summary>Crater centre Y, columns. Unused while <see cref="CraterRadius"/> is 0.</summary>
|
||
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.
|
||
|
||
/// <summary>
|
||
/// The submarine coast shelf (<c>IslandFalloff.CoastShelf</c>). Below-sea only,
|
||
/// depth-preserving, held strictly below sea by <c>MathF.BitDecrement</c>. Invisible until
|
||
/// water renders; ported faithfully now, judged then.
|
||
/// </summary>
|
||
public bool CoastShelf = false;
|
||
|
||
// ⚠ Fully qualified: this class's own `IslandFalloff` ablation toggle shadows the static
|
||
// type of the same name inside field initializers.
|
||
|
||
/// <summary>Shelf strength, 0 = off → 1 = a flat lagoon. Reference 0.775.</summary>
|
||
public float ShelfStrength = IslaApocalypse.Tools.IslandFalloff.SHELF_STRENGTH;
|
||
|
||
/// <summary>Metres of depth over which the shelf relaxes. Reference 100.</summary>
|
||
public float ShelfScaleM = IslaApocalypse.Tools.IslandFalloff.SHELF_SCALE_M;
|
||
|
||
/// <summary>
|
||
/// ⭐ The offshore islet system — every dial in one object. <c>Mode = Off</c> by default
|
||
/// (see the note above). <see cref="OffshoreSettings.Faithful"/> is the reference verbatim;
|
||
/// <see cref="OffshoreSettings.Organic"/> is the reshape, tuned (chat2/06).
|
||
/// </summary>
|
||
public OffshoreSettings Offshore = new OffshoreSettings();
|
||
|
||
// ---- PASS 1c — region labeling + the speck revert (chat2/07) ----------
|
||
|
||
/// <summary>
|
||
/// ⭐ THE REGION-LABELING LAYER (<c>Core.RegionLabeling</c>, via <c>RegionPass</c>): 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).
|
||
/// </summary>
|
||
public bool RegionLabeling = true;
|
||
|
||
/// <summary>
|
||
/// ⭐ THE SPECK REVERT — lower every non-mainland land component smaller than
|
||
/// <see cref="MinLandComponentFrac"/> 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.
|
||
/// </summary>
|
||
public bool SpeckRevert = false;
|
||
|
||
/// <summary>The revert threshold, as a fraction of the map's AREA (scale-free). → <see cref="RegionPass.ThresholdMidFrac"/>.</summary>
|
||
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.
|
||
|
||
/// <summary>⭐ 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.</summary>
|
||
public float SouthStretch = 0f;
|
||
|
||
/// <summary>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.</summary>
|
||
public float SouthBandStartFrac = SouthernStretch.DefaultBandStartFrac;
|
||
|
||
/// <summary>The feather width across which the stretch ramps 0 → 1 (smoothstep), fraction of the map. A constant for a whole batch.</summary>
|
||
public float SouthBandFeatherFrac = SouthernStretch.DefaultBandFeatherFrac;
|
||
|
||
/// <summary>
|
||
/// 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; → <see cref="SouthernStretch"/>.
|
||
/// </summary>
|
||
public bool StretchSinker = SouthernStretch.DefaultStretchSinker;
|
||
|
||
// ---- PASS 1 — COASTAL FRAGMENTATION (chat2/09, exploration) ------------------
|
||
//
|
||
// A band-limited, zero-mean noise added to the PRE-power falloff only where the falloff sits in
|
||
// the coastal window (≈ the barely-land / barely-sea margin, around the whole perimeter). It
|
||
// self-targets thin necks: the cells closest to the sea threshold flip first, so lobes pinch off
|
||
// into islands while the interior — window weight exactly zero — is bit-identical by
|
||
// construction. Nothing is detected, nothing is stamped. → CoastalFragment.
|
||
|
||
/// <summary>⭐ THE SWEPT AXIS. 0 = off (bit-identical everywhere). Peak |Δfalloff| (pre-power) at the window's centre.</summary>
|
||
public float FragmentAmp = 0f;
|
||
|
||
/// <summary>The fragmentation noise's frequency, periods per map width — the neck/lobe scale. The secondary dial (fixed this round). → <see cref="CoastalFragment.DefaultFreqPerMapWidth"/>.</summary>
|
||
public float FragmentFreqPerMapWidth = CoastalFragment.DefaultFreqPerMapWidth;
|
||
|
||
/// <summary>The coastal window's centre and half-width in PRE-power falloff units. Weight 1 at the centre, smooth to 0 at ± half-width; exactly 0 beyond.</summary>
|
||
public float FragmentBandCentre = CoastalFragment.DefaultBandCentre;
|
||
public float FragmentBandHalfWidth = CoastalFragment.DefaultBandHalfWidth;
|
||
|
||
/// <summary>
|
||
/// false (default) ⇒ zero-mean noise: the margin is redrawn — bites AND builds (which can also
|
||
/// bridge an island back onto the mainland). true ⇒ bites only ((noise+1)/2 ≥ 0): land can only
|
||
/// recede, necks are cut, nothing is bridged, the coast net-recedes. → <see cref="CoastalFragment"/>.
|
||
/// </summary>
|
||
public bool FragmentBitesOnly = CoastalFragment.DefaultBitesOnly;
|
||
|
||
/// <summary>A short label for this variant, used in output filenames. E.g. "full", "base_only".</summary>
|
||
public string VariantLabel = "full";
|
||
|
||
/// <summary>The scale object every distance and frequency in the generator derives from.</summary>
|
||
public GenerationScale Scale => new GenerationScale(MapSize);
|
||
|
||
/// <summary>
|
||
/// ⚠ DEEP on <see cref="Anchors"/>. <c>MemberwiseClone</c> 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 (<see cref="CurveKnots"/>) does not need to be.
|
||
/// </summary>
|
||
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)}]";
|
||
}
|
||
}
|