islaApocalypse-v2/Tools/Scripts/TerrainGenConfig.cs
beezm 9af5c73b04 Phase 1: port the crown-jewel noise and pass-1, and render the island
Ports MapGenerator.GenerateTopography's pass-1 loop (reference ~:553-619 at tag
pre-rewrite-reference / ab78883) into Tools/. Working code and its tuned constants
carried over verbatim, read from source rather than re-derived from a design
summary (D-050). Six elements, in the reference's execution order — the order is
load-bearing: the sinker lands before the Pow (superlinear), the Trench after it
(a raw additive wall), preTrenchFalloff captured between them.

The fractal config is now PINNED. The reference set only NoiseType/Seed/Frequency,
so the island's entire fractal character was Godot 4.7.1 constructor defaults that
nothing recorded. Measured on 4.7.2 (NoiseDefaultsProbe): Fbm / 5 / 0.5 / 2.0 / 0.0
— identical to the 4.7.1 values, so pinning reproduces the look with no delta. The
terrain no longer depends on an engine default, and the probe makes a future drift
visible instead of silent.

Two deliberate departures from the reference's literals, both flagged in-code:
  - the latitude noise offset is expressed in map widths, not the reference's raw
    +1000 px, so a resize no longer samples a different slice of the noise field
    (chat1/00 §4.2). Pinned to 1000/8192 so the 8K realization is unchanged.
  - the `if (temperature < 0.65f)` spine gate is dropped as the provable no-op it
    is — southernFade already reaches exactly 0 at 0.65. Bit-identical.

The reference's "temperature" is ported as a latitude field and deliberately NOT
named temperature: it is stage-1-local input to terrain geometry, and climate is
stage 3 (D-049 §2, D-056). The ±0.1 wobble is kept — it is what stops the spine's
southern terminus being a ruler-straight line.

Deferred to Phase 2 with the seam open: coast shelf and offshore islets (below-sea,
judged once water renders). Pass1Result exposes PreTrenchFalloff at their exact
consumption point, plus HMaxSeed for the seed-dependent curve.

Render is a direct Godot.Image, not the SubViewport capture path — that machinery
exists to composite vector overlays and is why --headless hangs; Phase 1 has none.
Hypsometric above 0.15, bathymetric below, fixed ramp anchors so seeds and ablation
rungs are comparable. No biome words anywhere.

Verified: land fraction 51.5% identical at 1024, 2048 and 10240 — the scaling
discipline holds across a 10x resize. Full-size 10240 pass: 36.3 s.

No curve, erosion, rivers, water, crater, biomes, roads, or mesher.
2026-08-19 21:27:20 -04:00

104 lines
4.7 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 IslaApocalypse.Core;
namespace IslaApocalypse.Tools
{
/// <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 (20484096); 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 35 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;
/// <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);
public TerrainGenConfig Clone() => (TerrainGenConfig)MemberwiseClone();
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}]";
}
}