feat: curve v5 — corner fixes + COMPACT/BALANCED taste presets (terrain-water task 09)
The three corner fixes (both presets): riser endpoint slope floors 0.2 -> 0.1 (0.1u + 0.9*smoothstep — climbs decelerate into shelves and accelerate out); summit-spike base floor 0.1 -> 0.05 (0.05u + 0.95*u^4 — no hard skirt under the peaks); SHELF_SPAN_MIN 2 m -> 6 m (pronounced shelves keep a gentle tilt). Knots are preset-parameterized (CurveKnots, explicit parameter — pure, D-035), calibrated from the same pooled batch-04 CDF: COMPACT P65/77/84/88/94/97 -> 65/12/7/4/6/3/3 land split BALANCED P60/73/83/88/96/99 -> 60/13/10/5/8/3/1 Gate: "off"|"v5-compact"|"v5-balanced" (batch-only tri-state; interim default v5-balanced pending the developer's verdict — the winner becomes plain "v5" in a follow-up commit); v1-v4 retired loudly. Assertion runs per preset (8 corners x per-seed spikeMax; the lowered floors stay strictly positive). TCRV v5 extension (+9 B): preset id + K5/K6 — the effective curve is unambiguous from the record; old records parse (version-dispatched); harness compares. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
68f7bca8e0
commit
02577872f2
6 changed files with 137 additions and 101 deletions
|
|
@ -142,6 +142,13 @@ namespace IslaApocalypse.Core
|
|||
writer.Write(c.BenchSeedOffset); writer.Write(c.PlateauSeedOffset);
|
||||
writer.Write(c.StrengthSeedOffset);
|
||||
}
|
||||
|
||||
// v5+ extension: knot preset id + K5/K6.
|
||||
if (c.Version >= 5)
|
||||
{
|
||||
writer.Write(c.PresetId);
|
||||
writer.Write(c.K5); writer.Write(c.K6);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteWaterBodyIds(BinaryWriter writer, WorldBlueprint bp)
|
||||
|
|
|
|||
|
|
@ -26,12 +26,13 @@ namespace IslaApocalypse.Core // Change this if your namespace is different
|
|||
public static string SeaLevelModel = "flat";
|
||||
public static float SeaLevelValue = 0.15f;
|
||||
|
||||
// Height-redistribution curve (tasks 05–08, graduation M-7): "v4" applies the
|
||||
// terraced-ascent curve with spatially modulated shelves and the per-seed peak
|
||||
// spike (HeightCurve.cs); "off" is the raw legacy profile. "v1"–"v3" were
|
||||
// retired by successor recalibrations (old blueprints are regenerable). Biome
|
||||
// classification is curve-invariant by construction either way. Default: v4.
|
||||
public static string TerrainCurve = "v4";
|
||||
// Height-redistribution curve (tasks 05–09, graduation M-7): the task-09 taste
|
||||
// batch runs a tri-state — "v5-compact" (maximum lowland) vs "v5-balanced"
|
||||
// (task-07/08 lineage), both with the corner fixes; "off" is the raw legacy
|
||||
// profile. The loser retires after the developer's gate and the winner becomes
|
||||
// plain "v5". "v1"–"v4" retired. INTERIM default pending the verdict:
|
||||
// v5-balanced.
|
||||
public static string TerrainCurve = "v5-balanced";
|
||||
|
||||
public static void LoadConfig()
|
||||
{
|
||||
|
|
@ -106,10 +107,10 @@ namespace IslaApocalypse.Core // Change this if your namespace is different
|
|||
if (data.ContainsKey("TerrainCurve"))
|
||||
{
|
||||
string curve = (string)data["TerrainCurve"];
|
||||
if (curve == "off" || curve == "v4")
|
||||
if (curve == "off" || curve == "v5-compact" || curve == "v5-balanced")
|
||||
TerrainCurve = curve;
|
||||
else if (curve == "v1" || curve == "v2" || curve == "v3")
|
||||
GD.PrintErr($"[ConfigManager] TerrainCurve '{curve}' was retired by a later recalibration (v4, task 08). Keeping '{TerrainCurve}' — use \"v4\" or \"off\".");
|
||||
else if (curve == "v1" || curve == "v2" || curve == "v3" || curve == "v4")
|
||||
GD.PrintErr($"[ConfigManager] TerrainCurve '{curve}' was retired by a later recalibration (v5, task 09). Keeping '{TerrainCurve}' — use \"v5-compact\", \"v5-balanced\" or \"off\".");
|
||||
else
|
||||
GD.PrintErr($"[ConfigManager] Unknown TerrainCurve '{curve}'. Keeping '{TerrainCurve}'.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,11 @@ namespace IslaApocalypse.Core
|
|||
public float BenchAmp, PlateauAmp, ShelfSpanMin, ShelfSpanMax;
|
||||
public float ElevFreqIslands, StrengthFreqIslands;
|
||||
public int BenchSeedOffset, PlateauSeedOffset, StrengthSeedOffset;
|
||||
|
||||
// v5 extension — knot preset (1 = compact, 2 = balanced) and the two knots the
|
||||
// four base slots cannot carry; with these the effective curve is unambiguous.
|
||||
public byte PresetId;
|
||||
public float K5, K6;
|
||||
}
|
||||
|
||||
public class WorldBlueprint
|
||||
|
|
@ -416,6 +421,11 @@ namespace IslaApocalypse.Core
|
|||
c.BenchSeedOffset = reader.ReadInt32(); c.PlateauSeedOffset = reader.ReadInt32();
|
||||
c.StrengthSeedOffset = reader.ReadInt32();
|
||||
}
|
||||
if (c.Version >= 5)
|
||||
{
|
||||
c.PresetId = reader.ReadByte();
|
||||
c.K5 = reader.ReadSingle(); c.K6 = reader.ReadSingle();
|
||||
}
|
||||
blueprint.TerrainCurve = c;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,146 +1,151 @@
|
|||
using Godot;
|
||||
|
||||
/// <summary>
|
||||
/// The height-redistribution curve, v4 — SPATIALLY MODULATED SHELVES (terrain-water
|
||||
/// task 08). Pure, static, monotonic piecewise map; every per-column input is an
|
||||
/// explicit PARAMETER (D-035): the seed spike max and the four modulated shelf
|
||||
/// values. The generator samples the modulation fields; this class never touches
|
||||
/// noise.
|
||||
/// One preset's input knots for the v5 curve. Two presets exist for the task-09
|
||||
/// taste batch — COMPACT (maximum lowland, cordillera-from-plains) and BALANCED
|
||||
/// (the task-07/08 lineage, gradual highland approach). The loser retires after
|
||||
/// the developer's gate; the winner becomes plain "v5".
|
||||
/// </summary>
|
||||
public sealed class CurveKnots
|
||||
{
|
||||
public readonly byte PresetId;
|
||||
public readonly string Name;
|
||||
public readonly float K1, K2, K3, K4, K5, K6;
|
||||
|
||||
public CurveKnots(byte id, string name, float k1, float k2, float k3, float k4, float k5, float k6)
|
||||
{
|
||||
PresetId = id; Name = name;
|
||||
K1 = k1; K2 = k2; K3 = k3; K4 = k4; K5 = k5; K6 = k6;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The height-redistribution curve, v5 (terrain-water task 09) — v4's spatially
|
||||
/// modulated shelves plus the THREE CORNER FIXES, with PRESET-PARAMETERIZED knots.
|
||||
/// Pure, static, monotonic; every per-column input and the knot set are explicit
|
||||
/// PARAMETERS (D-035).
|
||||
///
|
||||
/// v4 (developer's task-07 gate finding: the terraces work, but uniform anchors put
|
||||
/// a flat ring at exactly 100 m and exactly 220 m on every mountain — the bathtub
|
||||
/// rings): the bench and plateau OUTPUT anchors become smooth spatial fields —
|
||||
/// benchLo = 100 m ± 12 m and plateauLo = 220 m ± 20 m via two decorrelated
|
||||
/// very-low-frequency noise fields — and a third field modulates SHELF STRENGTH,
|
||||
/// blending each shelf between "pronounced flat" (output span ~2 m) and "barely a
|
||||
/// hint" (~25 m of gentle slope), so not every flank at shelf height develops the
|
||||
/// full terrace. Shelves stay locally flat; the two magic altitudes stop existing.
|
||||
/// The corner fixes (both presets — the naturalness work):
|
||||
/// 1. Riser endpoint slope floor 0.2 → 0.1 (blend 0.1u + 0.9·smoothstep): climbs
|
||||
/// decelerate into shelves and accelerate out of them — no machined edges.
|
||||
/// 2. Summit-spike base floor 0.1 → 0.05 (blend 0.05u + 0.95·u⁴): the spike
|
||||
/// leaves the plateau gently — no hard skirt under the peaks.
|
||||
/// 3. SHELF_SPAN_MIN 2 m → 6 m: pronounced shelves keep a gentle tilt — flat to
|
||||
/// build on, never snooker-table flat.
|
||||
///
|
||||
/// Structure otherwise v3's, unchanged: identity ≤ sea, frozen toe/rise (storm
|
||||
/// ladder), foothill riser → bench → mid riser → plateau → per-seed-normalized
|
||||
/// stiff spike (u⁴, 420 m cap) → tail. Input knots are v3's calibration.
|
||||
/// Presets (input land-fraction targets; knots calibrated 2026-08-08 from the same
|
||||
/// pooled batch-04 flat-sea land CDF as tasks 05/07, 340,618,126 samples; achieved
|
||||
/// fractions exact by construction):
|
||||
/// COMPACT — 65/12/7/4/6/3/3 (orange/red/foothill-riser/bench/mid-riser/plateau/spike)
|
||||
/// BALANCED — 60/13/10/5/8/3/1
|
||||
///
|
||||
/// ORDERING SAFETY BY CONSTRUCTION (asserted): with the amplitudes below, at every
|
||||
/// column: red ceiling 0.27 < benchLo−… (bench min 0.5006), bench top max 0.6962 <
|
||||
/// plateauLo min 0.9468, plateau top max 1.2062 < peak cap 1.8287. The numeric
|
||||
/// assertion additionally sweeps all 8 modulation-extreme corners per generation.
|
||||
/// Unchanged from v4: storm-ladder anchors, bench 100±12 m, plateau 220±20 m,
|
||||
/// strength modulation (span max 25 m), 420 m cap, per-seed spike normalization,
|
||||
/// modulation fields/seed offsets, the classify-map invariant.
|
||||
/// </summary>
|
||||
public static class HeightCurve
|
||||
{
|
||||
public const ushort VERSION = 4;
|
||||
public const ushort VERSION = 5;
|
||||
|
||||
// Input knots — v3 calibration (pooled batch-04 land CDF, P59/72/82/87/95/98).
|
||||
public const float K1 = 0.509179f;
|
||||
public const float K2 = 0.604081f;
|
||||
public const float K3 = 0.698485f;
|
||||
public const float K4 = 0.767213f;
|
||||
public const float K5 = 0.930304f;
|
||||
public const float K6 = 1.050720f;
|
||||
public static readonly CurveKnots COMPACT = new CurveKnots(1, "compact",
|
||||
0.550982f, 0.646528f, 0.723145f, 0.784045f, 0.903025f, 1.003079f); // P65/77/84/88/94/97
|
||||
|
||||
public static readonly CurveKnots BALANCED = new CurveKnots(2, "balanced",
|
||||
0.515899f, 0.612157f, 0.710472f, 0.784045f, 0.962922f, 1.119118f); // P60/73/83/88/96/99
|
||||
|
||||
// Fixed output anchors — storm ladder + ceiling (frozen).
|
||||
public const float SEA = 0.15f;
|
||||
public const float ORANGE_CEIL = 0.206f;
|
||||
public const float RED_CEIL = 0.27f;
|
||||
public const float PEAK_CAP = SEA + 420f / 251f; // ≈ 1.82869
|
||||
public const float PEAK_CAP = SEA + 420f / 251f;
|
||||
public const float TAIL_SLOPE = 0.25f;
|
||||
public const float SPIKE_MIN_SPAN = 0.01f;
|
||||
|
||||
// Modulated shelf anchors: base ± amplitude (raw units; 251 m per unit).
|
||||
public const float BENCH_BASE = SEA + 100f / 251f; // ≈ 0.54841 (100 m)
|
||||
public const float BENCH_AMP = 12f / 251f; // ± 12 m
|
||||
public const float PLATEAU_BASE = SEA + 220f / 251f; // ≈ 1.02649 (220 m)
|
||||
public const float PLATEAU_AMP = 20f / 251f; // ± 20 m
|
||||
// Modulated shelf anchors (unchanged from v4).
|
||||
public const float BENCH_BASE = SEA + 100f / 251f;
|
||||
public const float BENCH_AMP = 12f / 251f;
|
||||
public const float PLATEAU_BASE = SEA + 220f / 251f;
|
||||
public const float PLATEAU_AMP = 20f / 251f;
|
||||
|
||||
// Shelf strength: output span of each shelf segment, blended by the strength
|
||||
// field. Strong (t=1) → SPAN_MIN (~2 m, pronounced flat). Weak (t=0) →
|
||||
// SPAN_MAX (~25 m, barely a hint of a shelf).
|
||||
public const float SHELF_SPAN_MIN = 0.008f;
|
||||
// Corner fix 3: pronounced shelves keep ~6 m of tilt across the shelf band.
|
||||
public const float SHELF_SPAN_MIN = 6f / 251f; // ≈ 0.0239 (v4: 0.008 ≈ 2 m)
|
||||
public const float SHELF_SPAN_MAX = 0.10f;
|
||||
|
||||
// Modulation-field derivation (generator-side, recorded in TCRV): field seed =
|
||||
// RESOLVED WorldSeed + offset; frequency = (periods per island width) / MapSize.
|
||||
// Modulation-field derivation (unchanged from v4).
|
||||
public const int BENCH_SEED_OFFSET = 7101;
|
||||
public const int PLATEAU_SEED_OFFSET = 7207;
|
||||
public const int STRENGTH_SEED_OFFSET = 7303;
|
||||
public const float ELEV_FREQ_ISLANDS = 3.0f; // ~3 undulations across the island
|
||||
public const float STRENGTH_FREQ_ISLANDS = 5.0f; // finer patchiness for shelf strength
|
||||
public const float ELEV_FREQ_ISLANDS = 3.0f;
|
||||
public const float STRENGTH_FREQ_ISLANDS = 5.0f;
|
||||
|
||||
public static float EffectiveSpikeMax(float hMaxSeed)
|
||||
public static float EffectiveSpikeMax(float hMaxSeed, CurveKnots k)
|
||||
{
|
||||
return Mathf.Max(hMaxSeed, K6 + SPIKE_MIN_SPAN);
|
||||
return Mathf.Max(hMaxSeed, k.K6 + SPIKE_MIN_SPAN);
|
||||
}
|
||||
|
||||
/// <summary>Shelf output span for a strength sample t ∈ [0,1].</summary>
|
||||
public static float ShelfSpan(float strength01)
|
||||
{
|
||||
return Mathf.Lerp(SHELF_SPAN_MAX, SHELF_SPAN_MIN, Mathf.Clamp(strength01, 0f, 1f));
|
||||
}
|
||||
|
||||
/// <param name="h">Raw pre-curve height.</param>
|
||||
/// <param name="hMaxSeed">Seed's raw pre-curve maximum (per-seed spike normalizer).</param>
|
||||
/// <param name="benchLo">This column's bench anchor (BENCH_BASE ± BENCH_AMP).</param>
|
||||
/// <param name="benchSpan">This column's bench output span (ShelfSpan of the strength field).</param>
|
||||
/// <param name="plateauLo">This column's plateau anchor (PLATEAU_BASE ± PLATEAU_AMP).</param>
|
||||
/// <param name="plateauSpan">This column's plateau output span.</param>
|
||||
public static float Apply(float h, float hMaxSeed,
|
||||
float benchLo, float benchSpan, float plateauLo, float plateauSpan)
|
||||
float benchLo, float benchSpan, float plateauLo, float plateauSpan, CurveKnots k)
|
||||
{
|
||||
if (h <= SEA) return h;
|
||||
|
||||
float u, s;
|
||||
if (h < K1)
|
||||
if (h < k.K1)
|
||||
{
|
||||
u = (h - SEA) / (K1 - SEA);
|
||||
s = 0.3f * u + 0.7f * (u * (2f - u)); // frozen ease-out toe
|
||||
u = (h - SEA) / (k.K1 - SEA);
|
||||
s = 0.3f * u + 0.7f * (u * (2f - u)); // frozen ease-out toe
|
||||
return SEA + s * (ORANGE_CEIL - SEA);
|
||||
}
|
||||
if (h < K2)
|
||||
if (h < k.K2)
|
||||
{
|
||||
u = (h - K1) / (K2 - K1);
|
||||
return ORANGE_CEIL + u * (RED_CEIL - ORANGE_CEIL); // frozen linear rise
|
||||
u = (h - k.K1) / (k.K2 - k.K1);
|
||||
return ORANGE_CEIL + u * (RED_CEIL - ORANGE_CEIL); // frozen linear rise
|
||||
}
|
||||
if (h < K3)
|
||||
if (h < k.K3)
|
||||
{
|
||||
u = (h - K2) / (K3 - K2);
|
||||
s = 0.2f * u + 0.8f * (u * u * (3f - 2f * u)); // foothill riser
|
||||
u = (h - k.K2) / (k.K3 - k.K2);
|
||||
s = 0.1f * u + 0.9f * (u * u * (3f - 2f * u)); // foothill riser — corner fix 1
|
||||
return RED_CEIL + s * (benchLo - RED_CEIL);
|
||||
}
|
||||
if (h < K4)
|
||||
if (h < k.K4)
|
||||
{
|
||||
u = (h - K3) / (K4 - K3);
|
||||
return benchLo + u * benchSpan; // bench — modulated
|
||||
u = (h - k.K3) / (k.K4 - k.K3);
|
||||
return benchLo + u * benchSpan; // bench (min span 6 m — fix 3)
|
||||
}
|
||||
float benchTop = benchLo + benchSpan;
|
||||
if (h < K5)
|
||||
if (h < k.K5)
|
||||
{
|
||||
u = (h - K4) / (K5 - K4);
|
||||
s = 0.2f * u + 0.8f * (u * u * (3f - 2f * u)); // mid riser
|
||||
u = (h - k.K4) / (k.K5 - k.K4);
|
||||
s = 0.1f * u + 0.9f * (u * u * (3f - 2f * u)); // mid riser — corner fix 1
|
||||
return benchTop + s * (plateauLo - benchTop);
|
||||
}
|
||||
if (h < K6)
|
||||
if (h < k.K6)
|
||||
{
|
||||
u = (h - K5) / (K6 - K5);
|
||||
return plateauLo + u * plateauSpan; // plateau — modulated
|
||||
u = (h - k.K5) / (k.K6 - k.K5);
|
||||
return plateauLo + u * plateauSpan; // plateau
|
||||
}
|
||||
float plateauTop = plateauLo + plateauSpan;
|
||||
float spikeMax = EffectiveSpikeMax(hMaxSeed);
|
||||
float spikeMax = EffectiveSpikeMax(hMaxSeed, k);
|
||||
if (h < spikeMax)
|
||||
{
|
||||
u = (h - K6) / (spikeMax - K6);
|
||||
s = 0.1f * u + 0.9f * (u * u * u * u); // summit spike (v2/v3 shape)
|
||||
u = (h - k.K6) / (spikeMax - k.K6);
|
||||
s = 0.05f * u + 0.95f * (u * u * u * u); // summit spike — corner fix 2
|
||||
return plateauTop + s * (PEAK_CAP - plateauTop);
|
||||
}
|
||||
return PEAK_CAP + (h - spikeMax) * TAIL_SLOPE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-generation numeric strict-monotonicity check of the EFFECTIVE curve:
|
||||
/// sweeps the full domain at every one of the 8 modulation-extreme corners
|
||||
/// (bench anchor ±, plateau anchor ±, strength min/max) with the per-seed
|
||||
/// spikeMax — the adversarial corner set for the ordering constraints. Loud
|
||||
/// throw, refuses to generate.
|
||||
/// Per-generation numeric strict-monotonicity check of the EFFECTIVE curve for
|
||||
/// the selected preset: all 8 modulation-extreme corners × per-seed spikeMax.
|
||||
/// The corner fixes lower the slope floors (risers 0.1, spike base 0.05) — the
|
||||
/// sweep proves they stay strictly positive everywhere. Loud throw on failure.
|
||||
/// </summary>
|
||||
public static void AssertMonotonic(float hMaxSeed)
|
||||
public static void AssertMonotonic(float hMaxSeed, CurveKnots k)
|
||||
{
|
||||
float[] benchLos = { BENCH_BASE - BENCH_AMP, BENCH_BASE + BENCH_AMP };
|
||||
float[] plateauLos = { PLATEAU_BASE - PLATEAU_AMP, PLATEAU_BASE + PLATEAU_AMP };
|
||||
|
|
@ -153,27 +158,27 @@ public static class HeightCurve
|
|||
foreach (float sp in spans)
|
||||
{
|
||||
float prevH = -7f;
|
||||
float prev = Apply(prevH, hMaxSeed, bl, sp, pl, sp);
|
||||
float prev = Apply(prevH, hMaxSeed, bl, sp, pl, sp, k);
|
||||
|
||||
void Check(double hd)
|
||||
{
|
||||
float h = (float)hd;
|
||||
if (h <= prevH) return; // dedupe float32 samples (task-05 fix)
|
||||
float v = Apply(h, hMaxSeed, bl, sp, pl, sp);
|
||||
float v = Apply(h, hMaxSeed, bl, sp, pl, sp, k);
|
||||
if (v <= prev)
|
||||
throw new System.InvalidOperationException(
|
||||
$"[HeightCurve] MONOTONICITY VIOLATION at h={h} (hMaxSeed={hMaxSeed}, benchLo={bl}, plateauLo={pl}, span={sp}): {v} <= {prev}. Refusing to generate.");
|
||||
$"[HeightCurve] MONOTONICITY VIOLATION at h={h} (preset {k.Name}, hMaxSeed={hMaxSeed}, benchLo={bl}, plateauLo={pl}, span={sp}): {v} <= {prev}. Refusing to generate.");
|
||||
prev = v;
|
||||
prevH = h;
|
||||
}
|
||||
|
||||
double top = System.Math.Max(2.0, EffectiveSpikeMax(hMaxSeed) + 0.5);
|
||||
double top = System.Math.Max(2.0, EffectiveSpikeMax(hMaxSeed, k) + 0.5);
|
||||
for (double hh = -7.0 + 0.01; hh < 0.10; hh += 0.01) Check(hh);
|
||||
for (double hh = 0.10; hh <= top; hh += 0.0001) Check(hh);
|
||||
for (double hh = top + 0.05; hh <= top + 6.0; hh += 0.05) Check(hh);
|
||||
}
|
||||
}
|
||||
}
|
||||
GD.Print($"[HeightCurve] Monotonicity assertion passed (v{VERSION}, 8 modulation corners, effective spikeMax {EffectiveSpikeMax(hMaxSeed):F6}).");
|
||||
GD.Print($"[HeightCurve] Monotonicity assertion passed (v{VERSION} preset '{k.Name}', 8 modulation corners, effective spikeMax {EffectiveSpikeMax(hMaxSeed, k):F6}).");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ public partial class MapGenerator : TextureRect
|
|||
private FastNoiseLite _plateauNoise;
|
||||
private FastNoiseLite _strengthNoise;
|
||||
|
||||
// v5: the selected knot preset (task-09 taste batch: COMPACT or BALANCED);
|
||||
// null = curve off.
|
||||
private CurveKnots _curveKnots;
|
||||
|
||||
// The seed's raw pre-curve height maximum (post noise/falloff/Trench/spine,
|
||||
// pre-carve) — the v2 curve's per-seed spike normalizer. Computed in
|
||||
// GenerateTopography pass 1; recorded in TCRV (effective, guard applied).
|
||||
|
|
@ -84,7 +88,10 @@ public partial class MapGenerator : TextureRect
|
|||
this.CustomMinimumSize = new Vector2(MapSize, MapSize);
|
||||
|
||||
_heightMap = new float[MapSize, MapSize];
|
||||
_curveOn = ConfigManager.TerrainCurve == "v4";
|
||||
_curveKnots = ConfigManager.TerrainCurve == "v5-compact" ? HeightCurve.COMPACT
|
||||
: ConfigManager.TerrainCurve == "v5-balanced" ? HeightCurve.BALANCED
|
||||
: null;
|
||||
_curveOn = _curveKnots != null;
|
||||
// (The monotonicity assertion now runs inside GenerateTopography, against the
|
||||
// effective per-seed curve, once hMaxSeed is known.)
|
||||
_heightMapClassify = _curveOn ? new float[MapSize, MapSize] : _heightMap;
|
||||
|
|
@ -276,11 +283,14 @@ public partial class MapGenerator : TextureRect
|
|||
// the bench slots carry the two BASE anchors; the v4 extension record carries
|
||||
// the modulation parameters (amplitudes, spans, frequencies, seed offsets) —
|
||||
// the blueprint stays self-describing.
|
||||
// TCRV v5: knot slots carry the PRESET's K1..K4; the extension carries the
|
||||
// modulation params plus preset id and K5/K6 — the effective curve is
|
||||
// unambiguous from the record alone.
|
||||
TerrainCurve = _curveOn ? new TerrainCurveInfo
|
||||
{
|
||||
Version = HeightCurve.VERSION,
|
||||
T1 = HeightCurve.K1, T2 = HeightCurve.K2, T3 = HeightCurve.K3, T4 = HeightCurve.K4,
|
||||
SpikeMax = HeightCurve.EffectiveSpikeMax(_hMaxSeed), // per-seed
|
||||
T1 = _curveKnots.K1, T2 = _curveKnots.K2, T3 = _curveKnots.K3, T4 = _curveKnots.K4,
|
||||
SpikeMax = HeightCurve.EffectiveSpikeMax(_hMaxSeed, _curveKnots), // per-seed
|
||||
Sea = HeightCurve.SEA, OrangeCeil = HeightCurve.ORANGE_CEIL, RedCeil = HeightCurve.RED_CEIL,
|
||||
PlateauLo = HeightCurve.BENCH_BASE, PlateauHi = HeightCurve.PLATEAU_BASE,
|
||||
PeakCap = HeightCurve.PEAK_CAP, TailSlope = HeightCurve.TAIL_SLOPE,
|
||||
|
|
@ -288,7 +298,8 @@ public partial class MapGenerator : TextureRect
|
|||
ShelfSpanMin = HeightCurve.SHELF_SPAN_MIN, ShelfSpanMax = HeightCurve.SHELF_SPAN_MAX,
|
||||
ElevFreqIslands = HeightCurve.ELEV_FREQ_ISLANDS, StrengthFreqIslands = HeightCurve.STRENGTH_FREQ_ISLANDS,
|
||||
BenchSeedOffset = HeightCurve.BENCH_SEED_OFFSET, PlateauSeedOffset = HeightCurve.PLATEAU_SEED_OFFSET,
|
||||
StrengthSeedOffset = HeightCurve.STRENGTH_SEED_OFFSET
|
||||
StrengthSeedOffset = HeightCurve.STRENGTH_SEED_OFFSET,
|
||||
PresetId = _curveKnots.PresetId, K5 = _curveKnots.K5, K6 = _curveKnots.K6
|
||||
} : null,
|
||||
FormatVersion = 2,
|
||||
Params = new BlueprintParams
|
||||
|
|
@ -445,7 +456,7 @@ public partial class MapGenerator : TextureRect
|
|||
// The v2 curve is SEED-DEPENDENT: its spike maps [t4, hMaxSeed] onto the peak
|
||||
// band, so the monotonicity assertion must run against the EFFECTIVE per-seed
|
||||
// curve — after hMaxSeed is known, before any pixel is curved.
|
||||
if (_curveOn) HeightCurve.AssertMonotonic(_hMaxSeed);
|
||||
if (_curveOn) HeightCurve.AssertMonotonic(_hMaxSeed, _curveKnots);
|
||||
|
||||
// --- PASS 2: curve (task 05/06) + crater carve ---
|
||||
// Curve applied AFTER noise + falloff + Trench, BEFORE the crater carve, so
|
||||
|
|
@ -469,7 +480,7 @@ public partial class MapGenerator : TextureRect
|
|||
float benchLo = HeightCurve.BENCH_BASE + _benchNoise.GetNoise2D(x, y) * HeightCurve.BENCH_AMP;
|
||||
float plateauLo = HeightCurve.PLATEAU_BASE + _plateauNoise.GetNoise2D(x, y) * HeightCurve.PLATEAU_AMP;
|
||||
float shelfSpan = HeightCurve.ShelfSpan((_strengthNoise.GetNoise2D(x, y) + 1f) * 0.5f);
|
||||
curvedH = HeightCurve.Apply(raw, _hMaxSeed, benchLo, shelfSpan, plateauLo, shelfSpan);
|
||||
curvedH = HeightCurve.Apply(raw, _hMaxSeed, benchLo, shelfSpan, plateauLo, shelfSpan, _curveKnots);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -202,10 +202,12 @@ public partial class RoundTripHarness : Node
|
|||
bool same = ca.Version == cb.Version;
|
||||
float[] fa = { ca.T1, ca.T2, ca.T3, ca.T4, ca.SpikeMax, ca.Sea, ca.OrangeCeil, ca.RedCeil, ca.PlateauLo, ca.PlateauHi, ca.PeakCap, ca.TailSlope,
|
||||
ca.BenchAmp, ca.PlateauAmp, ca.ShelfSpanMin, ca.ShelfSpanMax, ca.ElevFreqIslands, ca.StrengthFreqIslands,
|
||||
ca.BenchSeedOffset, ca.PlateauSeedOffset, ca.StrengthSeedOffset };
|
||||
ca.BenchSeedOffset, ca.PlateauSeedOffset, ca.StrengthSeedOffset,
|
||||
ca.PresetId, ca.K5, ca.K6 };
|
||||
float[] fb = { cb.T1, cb.T2, cb.T3, cb.T4, cb.SpikeMax, cb.Sea, cb.OrangeCeil, cb.RedCeil, cb.PlateauLo, cb.PlateauHi, cb.PeakCap, cb.TailSlope,
|
||||
cb.BenchAmp, cb.PlateauAmp, cb.ShelfSpanMin, cb.ShelfSpanMax, cb.ElevFreqIslands, cb.StrengthFreqIslands,
|
||||
cb.BenchSeedOffset, cb.PlateauSeedOffset, cb.StrengthSeedOffset };
|
||||
cb.BenchSeedOffset, cb.PlateauSeedOffset, cb.StrengthSeedOffset,
|
||||
cb.PresetId, cb.K5, cb.K6 };
|
||||
for (int i = 0; i < fa.Length; i++)
|
||||
if (System.BitConverter.SingleToInt32Bits(fa[i]) != System.BitConverter.SingleToInt32Bits(fb[i])) same = false;
|
||||
if (!same) { GD.PrintErr("[Harness] TCRV fields differ."); return false; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue