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; }