Ports the reference's v5 height curve and shelf-detail passes onto Phase 1's shape and re-calibrates them against this repo's actual pass-1 distribution. This is the BASELINE the reshape gets judged against, not the reshape. Core (engine-free, D-060): - WorldScale — THE vertical yardstick. One metres/raw number (251), replacing the prototype's three duplicate M_PER_UNIT constants and ~20 bare literals. The chunk-height coupling it had there is recorded as a DEFERRED vault decision, not inherited. RawFromMetres divides, matching the reference bit-for-bit. - HeightCurve — the 7 bands, the frozen corner-fix blends, the per-seed spike normalization, the 24-corner monotonicity sweep that throws and refuses. Identity at and below sea, which everything downstream rests on. - CurveKnots / CurveAnchors — input knots (measured percentiles) and output anchors (storm ladder) split apart and both made parameters, so the anchors are A/B-able without editing source. The reference's shipped knots are kept beside the measured ones as the fidelity yardstick. - TerrainDetailPass — micro-relief skin plus the shelf-edge KNOT warp (which slides K3/K4/K5, not height — that is what keeps monotonicity structural). The crater exclusion is ported and inert until the carve lands. Tools: - Shaping — pass 2a, producing the two height fields. classify is bit-for-bit the raw pass-1 field; render is curved and detailed. Aliased when the curve is off, as the reference did. Pass1Result is left immutable so the oracle can compare. - LandHistogram — the calibration engine AND the diagnostic. The reference shipped six knot literals and threw the measuring instrument away; this rebuilds it. - ShapingOracle + CurveBaselineTool — four automatic checks before anything is looked at, and the batch that runs them. Measured, not assumed: - Knots re-measured over a 6-seed / 12.8M-sample pool. They differ from the reference's by at most 5.6 m of world height, against a 44.7 m per-seed spread — the pass-1 port is faithful. - Oracle all pass, including pass 1 bit-identical to Phase 1's own .f32 dump. - Band shares land on 60/13/10/5/8/3/1 to 0.00 pp. - Knots hold across map size: the 8K delta (5.8 m) sits inside seed noise. The finding the histograms deliver: 83% of land ends below 100 m and 96% below 220 m, with the median column at 13 m. That is the share targets doing exactly what they say, not a bug — and it is the developer's call, which is why nothing here reshapes it and the palette was deliberately left mis-fitted rather than recalibrated to disguise it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DCWNaDZPfTiAy3meGNGgqt
142 lines
7.4 KiB
C#
142 lines
7.4 KiB
C#
using Godot;
|
|
using IslaApocalypse.Core;
|
|
|
|
namespace IslaApocalypse.Tools
|
|
{
|
|
/// <summary>
|
|
/// ⭐⭐ THE CROWN JEWEL — the terrain noise configuration, ported and PINNED.
|
|
/// → D-050 ("port, don't re-derive"), `Design - Rewrite - Extraction Manifest.md`.
|
|
///
|
|
/// ═══ WHAT THE REFERENCE ACTUALLY SET, AND WHAT IT DID NOT ═══
|
|
///
|
|
/// The reference (MapGenerator._Ready ~:141-144) set exactly THREE properties:
|
|
///
|
|
/// _noise = new FastNoiseLite();
|
|
/// _noise.Seed = ConfigManager.WorldSeed == 0 ? (int)GD.Randi() : ConfigManager.WorldSeed;
|
|
/// _noise.NoiseType = FastNoiseLite.NoiseTypeEnum.Simplex;
|
|
/// _noise.Frequency = 0.004f / scaleFactor;
|
|
///
|
|
/// That is all four lines. There is no fifth. Fractal type, octaves, gain, lacunarity and
|
|
/// weighted-strength were NEVER SET anywhere in that codebase — so the island's entire fractal
|
|
/// character was Godot 4.7.1's `FastNoiseLite` CONSTRUCTOR DEFAULTS, and nothing recorded what
|
|
/// they were. (chat1/00 §2.1, §4.1.)
|
|
///
|
|
/// ═══ ⚠⚠ WHY EVERY VALUE IS PINNED EXPLICITLY BELOW ═══
|
|
///
|
|
/// Because a default is not a decision, and this project no longer rides one. The terrain must
|
|
/// not change when the engine version changes, and it must not change when this code is ported
|
|
/// off Godot's FastNoiseLite to a C++ or hand-rolled implementation — which D-049 explicitly
|
|
/// anticipates ("C++-ready seams... port path-by-path when settled and hot"). An unpinned
|
|
/// default would move the island silently, and nothing in the source would say what moved.
|
|
///
|
|
/// Measured on Godot 4.7.2.stable.mono (Tools/Scenes/NoiseDefaultsProbe.tscn):
|
|
/// FractalType Fbm · Octaves 5 · Gain 0.5 · Lacunarity 2.0 · WeightedStrength 0.0
|
|
/// These MATCH the Godot 4 documented defaults the reference rode on 4.7.1 — so pinning them
|
|
/// reproduces the reference look exactly, with no delta to reconcile.
|
|
///
|
|
/// ⚠ Note NoiseType is the one place the reference did NOT ride the default: the constructor
|
|
/// default is SimplexSmooth, and the reference explicitly chose Simplex. Different noise.
|
|
///
|
|
/// Run NoiseDefaultsProbe after any engine upgrade. It cannot change the terrain — this file
|
|
/// pins it — but it makes a drifting default visible instead of silent.
|
|
/// </summary>
|
|
public static class TerrainNoise
|
|
{
|
|
// ---- the pinned fractal character (was 4.7.1 constructor defaults) ----
|
|
public const FastNoiseLite.NoiseTypeEnum PinnedNoiseType = FastNoiseLite.NoiseTypeEnum.Simplex;
|
|
public const FastNoiseLite.FractalTypeEnum PinnedFractalType = FastNoiseLite.FractalTypeEnum.Fbm;
|
|
public const int PinnedOctaves = 5;
|
|
public const float PinnedGain = 0.5f;
|
|
public const float PinnedLacunarity = 2.0f;
|
|
public const float PinnedWeightedStrength = 0.0f;
|
|
|
|
/// <summary>
|
|
/// The base frequency, stated at the 1024 baseline. The reference wrote
|
|
/// <c>0.004f / scaleFactor</c>; here the division is <see cref="GenerationScale.NoiseFrequency"/>'s
|
|
/// job, so the literal never reaches a call site. → `Design - Tooling - Scaling Discipline.md`.
|
|
/// </summary>
|
|
public const float BaselineFrequency = 0.004f;
|
|
|
|
/// <summary>
|
|
/// Build the one terrain noise field. ⚠ ONE INSTANCE, deliberately.
|
|
///
|
|
/// The reference sampled the latitude wobble, the coastline edge roughness and the base
|
|
/// height from the SAME `_noise` object — three correlated slices of one field, at three
|
|
/// different coordinate transforms. That correlation is part of the look, so the port keeps
|
|
/// one instance rather than "tidying" it into three seeded fields.
|
|
/// </summary>
|
|
public static FastNoiseLite Create(int seed, GenerationScale scale)
|
|
{
|
|
var noise = new FastNoiseLite();
|
|
|
|
// --- ported verbatim from the reference ---
|
|
noise.Seed = seed;
|
|
noise.NoiseType = PinnedNoiseType;
|
|
noise.Frequency = scale.NoiseFrequency(BaselineFrequency); // = 0.004f / scaleFactor
|
|
|
|
// --- PINNED: the reference left these to the engine. We do not. ---
|
|
noise.FractalType = PinnedFractalType;
|
|
noise.FractalOctaves = PinnedOctaves;
|
|
noise.FractalGain = PinnedGain;
|
|
noise.FractalLacunarity = PinnedLacunarity;
|
|
noise.FractalWeightedStrength = PinnedWeightedStrength;
|
|
|
|
return noise;
|
|
}
|
|
|
|
/// <summary>
|
|
/// A MODULATION field — the curve's bench/plateau/strength anchors and the detail pass's
|
|
/// relief/edge fields. Ported from the reference's <c>MapGenerator.MakeModulationNoise</c>
|
|
/// (~:1041-1048).
|
|
///
|
|
/// ═══ ⚠ DECORRELATION IS BY SEED, NOT BY COORDINATE OFFSET ═══
|
|
///
|
|
/// The reference offset these fields from each other with <c>Seed = resolvedSeed + offset</c>
|
|
/// (7101 / 7207 / 7303 / 7409 / 7507 / 7607 / 9271) and then sampled every one of them at the
|
|
/// bare <c>(x, y)</c>. There is no <c>GetNoise2D(x + 1000, …)</c> anywhere in this path.
|
|
///
|
|
/// So the raw-pixel-offset hazard <see cref="GenerationScale"/> warns about — the one that
|
|
/// bit the latitude wobble in pass 1 — <b>does not apply here, and there was nothing to
|
|
/// normalize on the port.</b> Recorded explicitly because "we checked and it was fine" is
|
|
/// only worth anything if someone wrote down that they checked. (chat2/01.)
|
|
///
|
|
/// ⚠ FREQUENCY IS STATED PER MAP WIDTH, not at the 1024 baseline — the reference wrote
|
|
/// <c>periodsPerIsland / MapSize</c>, which is already size-independent. →
|
|
/// <see cref="GenerationScale.NoiseFrequencyPerMapWidth"/>.
|
|
///
|
|
/// ⚠ THE FRACTAL PROPERTIES ARE PINNED HERE TOO. The reference left them to the engine on
|
|
/// these fields exactly as it did on the base noise, so the same argument applies: a default
|
|
/// is not a decision, and an engine upgrade must not move the island. The pinned values are
|
|
/// the ones measured on 4.7.2 and match what 4.7.1 supplied, so pinning reproduces the
|
|
/// reference with no delta.
|
|
/// </summary>
|
|
/// <param name="seed">The run's resolved seed — the offset is added here, not by the caller.</param>
|
|
/// <param name="seedOffset">The field's decorrelation offset (e.g. <c>CurveAnchors.BenchSeedOffset</c>).</param>
|
|
/// <param name="periodsPerMapWidth">How many undulations across the island.</param>
|
|
public static FastNoiseLite CreateModulation(int seed, int seedOffset, float periodsPerMapWidth,
|
|
GenerationScale scale)
|
|
{
|
|
var noise = new FastNoiseLite();
|
|
|
|
// --- ported verbatim from the reference ---
|
|
noise.Seed = seed + seedOffset; // deterministic from the resolved seed
|
|
noise.NoiseType = PinnedNoiseType;
|
|
noise.Frequency = scale.NoiseFrequencyPerMapWidth(periodsPerMapWidth);
|
|
|
|
// --- PINNED: the reference left these to the engine here too. We do not. ---
|
|
noise.FractalType = PinnedFractalType;
|
|
noise.FractalOctaves = PinnedOctaves;
|
|
noise.FractalGain = PinnedGain;
|
|
noise.FractalLacunarity = PinnedLacunarity;
|
|
noise.FractalWeightedStrength = PinnedWeightedStrength;
|
|
|
|
return noise;
|
|
}
|
|
|
|
/// <summary>The pinned configuration as one line, for a run header.</summary>
|
|
public static string Describe(int seed, GenerationScale scale) =>
|
|
$"Simplex · Fbm · octaves {PinnedOctaves} · gain {PinnedGain} · lacunarity {PinnedLacunarity} · " +
|
|
$"weighted {PinnedWeightedStrength} · freq {scale.NoiseFrequency(BaselineFrequency):G6} " +
|
|
$"(= {BaselineFrequency} / {scale.ScaleFactor:F3}) · seed {seed}";
|
|
}
|
|
}
|