diff --git a/Core/Scripts/BlueprintWriter.cs b/Core/Scripts/BlueprintWriter.cs
index 4ed218d..ed2de85 100644
--- a/Core/Scripts/BlueprintWriter.cs
+++ b/Core/Scripts/BlueprintWriter.cs
@@ -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)
diff --git a/Core/Scripts/ConfigManager.cs b/Core/Scripts/ConfigManager.cs
index 6030555..4d13fa4 100644
--- a/Core/Scripts/ConfigManager.cs
+++ b/Core/Scripts/ConfigManager.cs
@@ -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}'.");
}
diff --git a/Core/Scripts/MapDataParser.cs b/Core/Scripts/MapDataParser.cs
index 00e03a6..aadc5df 100644
--- a/Core/Scripts/MapDataParser.cs
+++ b/Core/Scripts/MapDataParser.cs
@@ -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;
}
diff --git a/Tools/Scripts/HeightCurve.cs b/Tools/Scripts/HeightCurve.cs
index fd6f1ed..65916b9 100644
--- a/Tools/Scripts/HeightCurve.cs
+++ b/Tools/Scripts/HeightCurve.cs
@@ -1,146 +1,151 @@
using Godot;
///
-/// 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".
+///
+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;
+ }
+}
+
+///
+/// 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.
///
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);
}
- /// Shelf output span for a strength sample t ∈ [0,1].
public static float ShelfSpan(float strength01)
{
return Mathf.Lerp(SHELF_SPAN_MAX, SHELF_SPAN_MIN, Mathf.Clamp(strength01, 0f, 1f));
}
- /// Raw pre-curve height.
- /// Seed's raw pre-curve maximum (per-seed spike normalizer).
- /// This column's bench anchor (BENCH_BASE ± BENCH_AMP).
- /// This column's bench output span (ShelfSpan of the strength field).
- /// This column's plateau anchor (PLATEAU_BASE ± PLATEAU_AMP).
- /// This column's plateau output span.
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;
}
///
- /// 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.
///
- 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}).");
}
}
diff --git a/Tools/Scripts/MapGenerator.cs b/Tools/Scripts/MapGenerator.cs
index b311779..b7e3dc6 100644
--- a/Tools/Scripts/MapGenerator.cs
+++ b/Tools/Scripts/MapGenerator.cs
@@ -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
{
diff --git a/Tools/Scripts/RoundTripHarness.cs b/Tools/Scripts/RoundTripHarness.cs
index d912926..9c0a5cc 100644
--- a/Tools/Scripts/RoundTripHarness.cs
+++ b/Tools/Scripts/RoundTripHarness.cs
@@ -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; }