using Godot; using System.Collections.Generic; using IslaApocalypse.Core; /// /// The blueprint round-trip oracle (terrain-water task 02). Runs headless in seconds, /// no generation, no road pass: /// /// 1. load a known-good v1 blueprint (default: the preserved reference copy of seed /// 1409879727) through the real parser, /// 2. write it back out as v2 through the real writer (to a harness-only name — the /// reference file is never overwritten), /// 3. load the v2 file through the real parser, /// 4. assert semantic equality: heights bitwise, biomes equal, towns equal in /// position/tier/count, each road tier point-for-point. /// /// Params and the per-town highway flag are EXCLUDED from equality — a v1 source /// cannot supply them; they are verified in the full-generation acceptance run. /// /// Run: Godot --headless --path res://Tools/Scenes/RoundTripHarness.tscn /// Env overrides: HARNESS_V1_PATH (source file), HARNESS_V2_PATH (output file). /// Exit code 0 = PASS, 1 = FAIL. Worth keeping permanently as a format regression test. /// public partial class RoundTripHarness : Node { public override void _Ready() { string v1Path = OS.GetEnvironment("HARNESS_V1_PATH"); if (string.IsNullOrEmpty(v1Path)) v1Path = ProjectSettings.GlobalizePath("user://reference_1409879727/MapData_Seed_1409879727.dat"); string v2Path = OS.GetEnvironment("HARNESS_V2_PATH"); if (string.IsNullOrEmpty(v2Path)) v2Path = ProjectSettings.GlobalizePath("user://Harness_RoundTrip_v2.dat"); GD.Print($"[Harness] v1 source: {v1Path}"); GD.Print($"[Harness] v2 output: {v2Path}"); bool pass = false; try { pass = RunRoundTrip(v1Path, v2Path); } catch (System.Exception e) { GD.PrintErr($"[Harness] EXCEPTION: {e}"); } GD.Print(pass ? "[Harness] RESULT: PASS" : "[Harness] RESULT: FAIL"); GetTree().Quit(pass ? 0 : 1); } private bool RunRoundTrip(string v1Path, string v2Path) { ulong t0 = Time.GetTicksMsec(); WorldBlueprint original = MapDataParser.LoadMapDataFromPath(v1Path); ulong t1 = Time.GetTicksMsec(); if (original == null) { GD.PrintErr("[Harness] v1 load failed."); return false; } GD.Print($"[Harness] v1 parse: {(t1 - t0) / 1000.0:F1}s (format v{original.FormatVersion})"); BlueprintWriter.WriteV2(v2Path, original); ulong t2 = Time.GetTicksMsec(); GD.Print($"[Harness] v2 write: {(t2 - t1) / 1000.0:F1}s"); WorldBlueprint reread = MapDataParser.LoadMapDataFromPath(v2Path); ulong t3 = Time.GetTicksMsec(); if (reread == null) { GD.PrintErr("[Harness] v2 load failed."); return false; } GD.Print($"[Harness] v2 parse: {(t3 - t2) / 1000.0:F1}s (format v{reread.FormatVersion})"); return Compare(original, reread); } private bool Compare(WorldBlueprint a, WorldBlueprint b) { bool ok = true; if (a.MapSize != b.MapSize) { GD.PrintErr($"[Harness] MapSize mismatch: {a.MapSize} vs {b.MapSize}"); return false; // nothing below is comparable } // Heights: bitwise-identical floats. long heightDiffs = 0; for (int x = 0; x < a.MapSize; x++) for (int y = 0; y < a.MapSize; y++) if (System.BitConverter.SingleToInt32Bits(a.HeightMap[x, y]) != System.BitConverter.SingleToInt32Bits(b.HeightMap[x, y])) heightDiffs++; if (heightDiffs > 0) { GD.PrintErr($"[Harness] {heightDiffs} height pixels differ bitwise."); ok = false; } // Biomes: equal values (i32 -> u8 narrowing is documented and lossless for 0..255). long biomeDiffs = 0; for (int x = 0; x < a.MapSize; x++) for (int y = 0; y < a.MapSize; y++) if (a.BiomeMap[x, y] != b.BiomeMap[x, y]) biomeDiffs++; if (biomeDiffs > 0) { GD.PrintErr($"[Harness] {biomeDiffs} biome pixels differ."); ok = false; } // Towns: identical position/tier/count (highway flag deliberately not compared). if (a.Towns.Count != b.Towns.Count) { GD.PrintErr($"[Harness] Town count mismatch: {a.Towns.Count} vs {b.Towns.Count}"); ok = false; } else { for (int i = 0; i < a.Towns.Count; i++) { if (a.Towns[i].Position != b.Towns[i].Position || a.Towns[i].Tier != b.Towns[i].Tier) { GD.PrintErr($"[Harness] Town {i} mismatch: " + $"{a.Towns[i].Position}/{a.Towns[i].Tier} vs {b.Towns[i].Position}/{b.Towns[i].Tier}"); ok = false; } } } ok &= CompareRoads("Highway", a.Highways, b.Highways); ok &= CompareRoads("Branch", a.BranchRoads, b.BranchRoads); ok &= CompareRoads("Rugged", a.RuggedRoads, b.RuggedRoads); ok &= CompareRoads("Trail", a.TrailRoads, b.TrailRoads); ok &= CompareWater(a, b); ok &= CompareTerrainCurve(a, b); ok &= CompareTerrainDetail(a, b); if (ok) GD.Print($"[Harness] Semantic equality holds: {a.MapSize}x{a.MapSize} grid, " + $"{a.Towns.Count} towns, roads {a.Highways.Count}/{a.BranchRoads.Count}/" + $"{a.RuggedRoads.Count}/{a.TrailRoads.Count} paths."); return ok; } // Water sections (WBID/WBTB/WSRF) are compared whenever the SOURCE carries them — // the regression test grows with the format. A source without water (legacy v1) // must round-trip to a file without water. private bool CompareWater(WorldBlueprint a, WorldBlueprint b) { if (a.WaterBodyIds == null && b.WaterBodyIds == null) { GD.Print("[Harness] Water sections: absent in source — nothing to compare (and none reappeared)."); return true; } if (a.WaterBodyIds == null || b.WaterBodyIds == null) { GD.PrintErr($"[Harness] Water sections presence mismatch: source {(a.WaterBodyIds != null ? "has" : "lacks")} them, reread {(b.WaterBodyIds != null ? "has" : "lacks")} them."); return false; } bool ok = true; long idDiffs = 0, surfDiffs = 0; int n = a.MapSize; for (int x = 0; x < n; x++) { for (int y = 0; y < n; y++) { if (a.WaterBodyIds[x, y] != b.WaterBodyIds[x, y]) idDiffs++; ushort sa = a.WaterSurfaceQ != null ? a.WaterSurfaceQ[x, y] : (ushort)0; ushort sb = b.WaterSurfaceQ != null ? b.WaterSurfaceQ[x, y] : (ushort)0; if (sa != sb) surfDiffs++; } } if (idDiffs > 0) { GD.PrintErr($"[Harness] {idDiffs} WBID pixels differ."); ok = false; } if (surfDiffs > 0) { GD.PrintErr($"[Harness] {surfDiffs} WSRF pixels differ."); ok = false; } if (a.WaterBodies.Count != b.WaterBodies.Count) { GD.PrintErr($"[Harness] Water body count mismatch: {a.WaterBodies.Count} vs {b.WaterBodies.Count}"); ok = false; } else { for (int i = 0; i < a.WaterBodies.Count; i++) { var wa = a.WaterBodies[i]; var wb = b.WaterBodies[i]; if (wa.Id != wb.Id || wa.Type != wb.Type || wa.Salinity != wb.Salinity || System.BitConverter.SingleToInt32Bits(wa.SurfaceLevel) != System.BitConverter.SingleToInt32Bits(wb.SurfaceLevel) || wa.PixelCount != wb.PixelCount || wa.Centroid != wb.Centroid) { GD.PrintErr($"[Harness] Water body {i} mismatch (id {wa.Id} vs {wb.Id})."); ok = false; } } } if (ok) GD.Print($"[Harness] Water sections equal: {a.WaterBodies.Count} bodies, WBID+WSRF grids identical."); return ok; } private bool CompareTerrainCurve(WorldBlueprint a, WorldBlueprint b) { if (a.TerrainCurve == null && b.TerrainCurve == null) { GD.Print("[Harness] TCRV: absent in source — nothing to compare (and none reappeared)."); return true; } if (a.TerrainCurve == null || b.TerrainCurve == null) { GD.PrintErr("[Harness] TCRV presence mismatch between source and reread."); return false; } var ca = a.TerrainCurve; var cb = b.TerrainCurve; 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.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.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; } GD.Print($"[Harness] TCRV equal (curve v{ca.Version})."); return true; } private bool CompareTerrainDetail(WorldBlueprint a, WorldBlueprint b) { if (a.TerrainDetail == null && b.TerrainDetail == null) { GD.Print("[Harness] TDTL: absent in source — nothing to compare (and none reappeared)."); return true; } if (a.TerrainDetail == null || b.TerrainDetail == null) { GD.PrintErr("[Harness] TDTL presence mismatch between source and reread."); return false; } var da = a.TerrainDetail; var db = b.TerrainDetail; float[] fa = { da.Version, da.ReliefAmpM, da.ReliefFreqIslands, da.IncK, da.IncP, da.IncCapM, da.SeaClampRaw, da.CraterExclFactor, da.ShelfIncWeight, da.ReliefSeedOffset }; float[] fb = { db.Version, db.ReliefAmpM, db.ReliefFreqIslands, db.IncK, db.IncP, db.IncCapM, db.SeaClampRaw, db.CraterExclFactor, db.ShelfIncWeight, db.ReliefSeedOffset }; for (int i = 0; i < fa.Length; i++) if (System.BitConverter.SingleToInt32Bits(fa[i]) != System.BitConverter.SingleToInt32Bits(fb[i])) { GD.PrintErr("[Harness] TDTL fields differ."); return false; } GD.Print($"[Harness] TDTL equal (detail v{da.Version})."); return true; } private bool CompareRoads(string tier, List a, List b) { if (a.Count != b.Count) { GD.PrintErr($"[Harness] {tier} path count mismatch: {a.Count} vs {b.Count}"); return false; } for (int i = 0; i < a.Count; i++) { if (a[i].Length != b[i].Length) { GD.PrintErr($"[Harness] {tier} path {i} length mismatch: {a[i].Length} vs {b[i].Length}"); return false; } for (int p = 0; p < a[i].Length; p++) { if (System.BitConverter.SingleToInt32Bits(a[i][p].X) != System.BitConverter.SingleToInt32Bits(b[i][p].X) || System.BitConverter.SingleToInt32Bits(a[i][p].Y) != System.BitConverter.SingleToInt32Bits(b[i][p].Y)) { GD.PrintErr($"[Harness] {tier} path {i} point {p} differs: {a[i][p]} vs {b[i][p]}"); return false; } } } return true; } }