using System; using System.Collections.Generic; using IslaApocalypse.Core; namespace IslaApocalypse.Tools { /// /// ⭐⭐ THE ORACLE — the automatic correctness checks that let the developer's eye judge ONLY /// relief. /// /// ═══ WHY THIS EXISTS AT ALL ═══ /// /// The prototype's single most valuable terrain lesson was not about terrain: /// /// > *"Five rounds of taste-iteration were safe BECAUSE correctness was not being judged by eye. /// > Where a future phase has a subjective gate, ask first what the automatic invariant is."* /// > — `Design - Tooling - Iteration and Batching.md`, "build the oracle before the /// > taste-iteration, not after". /// /// The curve is a subjective gate. So before a single render is looked at, four things are /// proven mechanically: /// /// (a) REGRESSION curve OFF is bit-identical to Phase 1's pass-1 output. /// ⇒ the port disturbed nothing upstream. /// (b) CLASSIFY FIDELITY the classify field is bit-identical to the raw pre-curve field, /// curve on or off. ⇒ the oracle field is actually an oracle. /// (c) MONOTONICITY the effective per-seed curve is strictly increasing everywhere. /// ⇒ no peak has become a pit. /// (d) BAND SHARES realized land shares match the 60/13/10/5/8/3/1 targets. /// ⇒ the calibration did what it claimed. /// /// ⚠ BIT-IDENTICAL MEANS BIT-IDENTICAL. These compare IEEE-754 bit patterns, not values within /// an epsilon. "Close enough" is how a drift becomes a fact — and the whole point of the `.f32` /// dump is that two generators agree or their dumps differ. /// /// ⚠ Any failure fails the TASK, loudly. Nothing here papers over a mismatch: a check that /// reports "mostly passed" is a check that has stopped working. /// public static class ShapingOracle { /// One check's verdict. carries the evidence either way. public sealed class Check { public string Id; // "a", "b", "c", "d" public string Name; public bool Passed; public string Detail; public override string ToString() => $"[{(Passed ? "PASS" : "FAIL")}] ({Id}) {Name} — {Detail}"; } /// /// Compare two float fields for BIT equality. Returns the number of differing cells and the /// first difference found, so a failure is actionable rather than just red. /// public static (long differing, string firstDiff) CompareBitwise(float[,] a, float[,] b, int mapSize) { long differing = 0; string first = null; for (int x = 0; x < mapSize; x++) { for (int y = 0; y < mapSize; y++) { int ba = BitConverter.SingleToInt32Bits(a[x, y]); int bb = BitConverter.SingleToInt32Bits(b[x, y]); if (ba == bb) continue; differing++; first ??= $"first at [{x},{y}]: {a[x, y]:G9} (0x{ba:X8}) vs {b[x, y]:G9} (0x{bb:X8})"; } } return (differing, first); } /// /// (a) REGRESSION — with the curve off, shaping must return the pass-1 field untouched. /// /// ⚠ This is the WEAKER, always-available half of check (a): it proves pass 2 is a no-op when /// gated off. The stronger half — that pass 1 ITSELF still matches Phase 1 byte-for-byte — is /// , which needs a Phase-1 `.f32` on disk. /// public static Check RegressionCurveOff(Pass1Result p1, Pass2Result offResult) { var c = new Check { Id = "a", Name = "regression: curve OFF == pass-1 output" }; if (offResult.CurveOn) { c.Passed = false; c.Detail = "the result handed in was generated with the curve ON — wrong variant."; return c; } var (differing, firstDiff) = CompareBitwise(p1.Height, offResult.Height, p1.MapSize); bool aliased = offResult.FieldsAreAliased; c.Passed = differing == 0; c.Detail = c.Passed ? $"bit-identical over {(long)p1.MapSize * p1.MapSize:N0} cells" + (aliased ? " (and the fields alias one array, as the reference did)" : "") : $"{differing:N0} cells differ — {firstDiff}"; return c; } /// /// (a′) REGRESSION against a Phase-1 `.f32` dump — the cross-run half. /// /// ⚠ A MISSING DUMP IS NOT A PASS. It is reported as INCONCLUSIVE and the caller says so; a /// check that silently succeeds when its input is absent is worse than no check, because it /// buys confidence that was never earned. /// public static Check RegressionAgainstDump(float[,] current, float[,] phase1Dump, int mapSize, string dumpPath) { var c = new Check { Id = "a′", Name = "regression: pass-1 == Phase-1 .f32 dump" }; if (phase1Dump == null) { c.Passed = false; c.Detail = $"INCONCLUSIVE — no readable Phase-1 dump at {dumpPath} for this seed/size. " + "Not counted as a pass; set ISLA_PHASE1_SOURCE to a batch that has one."; return c; } var (differing, firstDiff) = CompareBitwise(phase1Dump, current, mapSize); c.Passed = differing == 0; c.Detail = c.Passed ? $"bit-identical to {dumpPath} over {(long)mapSize * mapSize:N0} cells" : $"{differing:N0} cells differ from {dumpPath} — {firstDiff}"; return c; } /// /// (b) CLASSIFY FIDELITY — the classify field is the raw pre-curve field, bit-for-bit, with /// the curve on or off. /// /// This is the invariant every later phase's oracle rests on: biomes and water will classify /// from this field, so if it has drifted by even one ulp the "md5-identical across shaping /// changes" guarantee is gone before it is ever used. /// public static Check ClassifyFidelity(Pass1Result p1, Pass2Result p2) { var c = new Check { Id = "b", Name = "classify field == raw pass-1 field" }; var (differing, firstDiff) = CompareBitwise(p1.Height, p2.HeightClassify, p1.MapSize); c.Passed = differing == 0; c.Detail = c.Passed ? $"bit-identical over {(long)p1.MapSize * p1.MapSize:N0} cells (curve {(p2.CurveOn ? "ON" : "OFF")})" : $"{differing:N0} cells differ — {firstDiff}"; return c; } /// /// (c) MONOTONICITY — recorded rather than re-run. /// /// HeightCurve.AssertMonotonic THROWS on violation and is called inside /// , so reaching this code at all means the sweep passed. The /// check exists so the oracle table states it explicitly instead of leaving the strongest /// guarantee implicit in the absence of a crash. /// public static Check Monotonicity(Pass2Result p2) { var c = new Check { Id = "c", Name = "curve strictly monotonic (24-corner sweep)" }; if (!p2.CurveOn) { c.Passed = true; c.Detail = "curve off — nothing to prove (identity is trivially monotonic)."; return c; } string note = p2.Notes.Find(n => n.Contains("Monotonicity assertion passed")); c.Passed = note != null; c.Detail = note ?? "no monotonicity confirmation recorded — AssertMonotonic did not run."; return c; } /// /// (d) BAND SHARES — the realized land shares against the M3 targets. /// /// ⚠ MEASURED ON THE RAW (INPUT) DISTRIBUTION, because that is where the knots cut. The /// shares are exact by construction IF the quantile machinery is right — so this check is /// really a proof that measured what it claimed, which is the one /// thing the reference could never verify about its own knots. /// /// /// Allowed absolute deviation per band, in percentage points. The knots come from the SAME /// histogram, so agreement is limited only by in-bin interpolation — tenths of a point, not /// whole ones. /// public static Check BandShares(LandHistogram raw, CurveKnots k, double tolerancePercentagePoints) { var c = new Check { Id = "d", Name = "realized land band shares == 60/13/10/5/8/3/1 targets" }; double[] realized = RealizedShares(raw, k); double worst = 0.0; int worstBand = -1; for (int i = 0; i < realized.Length; i++) { double d = Math.Abs(realized[i] - CurveKnots.BandShareTargets[i]); if (d > worst) { worst = d; worstBand = i; } } c.Passed = worst <= tolerancePercentagePoints; c.Detail = $"worst band '{CurveKnots.BandNames[worstBand]}' off by {worst:F3} pp " + $"(tolerance {tolerancePercentagePoints:F2} pp); realized " + string.Join("/", Array.ConvertAll(realized, v => v.ToString("F2"))); return c; } /// /// The fraction of land, in percent, falling in each of the curve's seven INPUT bands. /// Band edges are sea, K1..K6, +∞. /// public static double[] RealizedShares(LandHistogram raw, CurveKnots k) { float[] edges = { raw.SeaLevel, k.K1, k.K2, k.K3, k.K4, k.K5, k.K6 }; var shares = new double[7]; for (int i = 0; i < 6; i++) shares[i] = raw.FractionBetween(edges[i], edges[i + 1]) * 100.0; shares[6] = Math.Max(0.0, (1.0 - raw.FractionBelow(k.K6)) * 100.0); return shares; } /// Render the whole oracle as a markdown table for the INDEX and the report. public static string ToMarkdownTable(IEnumerable checks) { var sb = new System.Text.StringBuilder(); sb.AppendLine("| | Check | Result | Evidence |"); sb.AppendLine("|---|---|---|---|"); foreach (Check c in checks) sb.AppendLine($"| `{c.Id}` | {c.Name} | **{(c.Passed ? "PASS" : "FAIL")}** | {c.Detail} |"); return sb.ToString(); } } }