using Theriapolis.Core.World.Generation; namespace Theriapolis.Tools.Commands; /// /// worldgen-hash --seed <n> [--data-dir <dir>] /// /// Runs the full worldgen pipeline and prints the FNV-1a hashes of every /// output channel (elevation, moisture, temperature, biomes, settlements, /// polylines) plus the per-stage hash dictionary. Used as a determinism /// oracle for the Godot port — the Godot host prints the same hashes, /// they must match byte-for-byte. /// public static class WorldgenHash { public static int Run(string[] args) { ulong seed = 12345; string dataDir = ResolveDataDir(); for (int i = 0; i < args.Length; i++) { switch (args[i].ToLowerInvariant()) { case "--seed": if (i + 1 < args.Length) { string raw = args[++i]; seed = raw.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToUInt64(raw[2..], 16) : ulong.Parse(raw); } break; case "--data-dir": if (i + 1 < args.Length) dataDir = args[++i]; break; } } if (!Directory.Exists(dataDir)) { Console.Error.WriteLine($"Data directory not found: {dataDir}"); return 1; } Console.WriteLine($"[worldgen-hash] seed=0x{seed:X} data-dir={dataDir}"); var ctx = new WorldGenContext(seed, dataDir); WorldGenerator.RunAll(ctx); var w = ctx.World; Console.WriteLine($"[worldgen-hash] === FNV hashes ==="); Console.WriteLine($"[worldgen-hash] elevation = 0x{w.HashElevation():X16}"); Console.WriteLine($"[worldgen-hash] moisture = 0x{w.HashMoisture():X16}"); Console.WriteLine($"[worldgen-hash] temperature = 0x{w.HashTemperature():X16}"); Console.WriteLine($"[worldgen-hash] biomes = 0x{w.HashBiomes():X16}"); Console.WriteLine($"[worldgen-hash] settlements = 0x{w.HashSettlements():X16}"); Console.WriteLine($"[worldgen-hash] polylines = 0x{w.HashPolylines():X16}"); Console.WriteLine($"[worldgen-hash] === Per-stage hashes ({w.StageHashes.Count}) ==="); foreach (var kv in w.StageHashes.OrderBy(k => k.Key, StringComparer.Ordinal)) Console.WriteLine($"[worldgen-hash] {kv.Key,-32} = 0x{kv.Value:X16}"); return 0; } private static string ResolveDataDir() { string local = Path.Combine(AppContext.BaseDirectory, "Data"); if (Directory.Exists(local)) return local; string? dir = AppContext.BaseDirectory.TrimEnd( Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); for (int i = 0; i < 6; i++) { if (dir is null) break; string candidate = Path.Combine(dir, "Content", "Data"); if (Directory.Exists(candidate)) return candidate; dir = Path.GetDirectoryName(dir); } return local; } }