b451f83174
Captures the pre-Godot-port state of the codebase. This is the rollback anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md). All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
167 lines
8.7 KiB
C#
167 lines
8.7 KiB
C#
using Theriapolis.Core;
|
|
using Theriapolis.Core.World;
|
|
using Theriapolis.Core.World.Generation;
|
|
|
|
namespace Theriapolis.Tools.Commands;
|
|
|
|
/// <summary>
|
|
/// settlement-report --seed <n> [--data-dir <dir>]
|
|
///
|
|
/// Runs the full pipeline and prints a human-readable settlement report:
|
|
/// narrative anchors, tier breakdown, economy distribution, and PoI list.
|
|
/// </summary>
|
|
public static class SettlementReport
|
|
{
|
|
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];
|
|
if (raw.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
|
seed = Convert.ToUInt64(raw[2..], 16);
|
|
else
|
|
seed = ulong.Parse(raw);
|
|
}
|
|
break;
|
|
case "--data-dir":
|
|
if (i + 1 < args.Length) dataDir = args[++i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
Console.WriteLine($"[settlement-report] seed=0x{seed:X} data-dir={dataDir}");
|
|
|
|
if (!Directory.Exists(dataDir))
|
|
{
|
|
Console.Error.WriteLine($"Data directory not found: {dataDir}");
|
|
return 1;
|
|
}
|
|
|
|
var ctx = new WorldGenContext(seed, dataDir)
|
|
{
|
|
ProgressCallback = (name, _) => Console.Write($"\r Running {name,-28} "),
|
|
Log = _ => { }, // suppress detailed logs
|
|
};
|
|
|
|
WorldGenerator.RunAll(ctx);
|
|
Console.WriteLine("\r ");
|
|
|
|
var world = ctx.World;
|
|
var ss = world.Settlements;
|
|
|
|
if (ss.Count == 0)
|
|
{
|
|
Console.WriteLine("No settlements generated.");
|
|
return 0;
|
|
}
|
|
|
|
// ── Narrative anchors ─────────────────────────────────────────────────
|
|
Console.WriteLine("═══════════════════════════════════════════════════════");
|
|
Console.WriteLine(" NARRATIVE ANCHORS");
|
|
Console.WriteLine("═══════════════════════════════════════════════════════");
|
|
foreach (var s in ss.Where(s => s.Anchor.HasValue).OrderBy(s => s.Anchor))
|
|
{
|
|
Console.WriteLine($" [{s.Anchor}]");
|
|
Console.WriteLine($" Name : {s.Name}");
|
|
Console.WriteLine($" Tier : {s.Tier}");
|
|
Console.WriteLine($" Position : ({s.TileX}, {s.TileY})");
|
|
Console.WriteLine($" Economy : {s.Economy}");
|
|
Console.WriteLine($" Wealth : {s.WealthLevel:F3}");
|
|
Console.WriteLine($" Pop : ~{s.Population}");
|
|
Console.WriteLine($" River : {s.IsOnRiver}");
|
|
Console.WriteLine($" Rail : {s.HasRailStation}");
|
|
Console.WriteLine();
|
|
}
|
|
|
|
// ── Tier breakdown ────────────────────────────────────────────────────
|
|
Console.WriteLine("═══════════════════════════════════════════════════════");
|
|
Console.WriteLine(" SETTLEMENTS BY TIER");
|
|
Console.WriteLine("═══════════════════════════════════════════════════════");
|
|
for (int tier = 1; tier <= 4; tier++)
|
|
{
|
|
var ts = ss.Where(s => s.Tier == tier && !s.IsPoi).ToList();
|
|
if (ts.Count == 0) continue;
|
|
Console.WriteLine($" Tier {tier} ({ts.Count}):");
|
|
foreach (var s in ts.OrderBy(s => s.Name))
|
|
{
|
|
string anchor = s.Anchor.HasValue ? $" [{s.Anchor}]" : "";
|
|
Console.WriteLine($" ({s.TileX,4},{s.TileY,4}) {s.Name,-24}{anchor}");
|
|
Console.WriteLine($" Economy={s.Economy,-14} Gov={s.Governance,-16} Wealth={s.WealthLevel:F2}");
|
|
}
|
|
}
|
|
Console.WriteLine();
|
|
|
|
// ── Economy distribution ──────────────────────────────────────────────
|
|
Console.WriteLine("═══════════════════════════════════════════════════════");
|
|
Console.WriteLine(" ECONOMY DISTRIBUTION (Tier 1-4)");
|
|
Console.WriteLine("═══════════════════════════════════════════════════════");
|
|
var econGroups = ss.Where(s => !s.IsPoi)
|
|
.GroupBy(s => s.Economy)
|
|
.OrderByDescending(g => g.Count());
|
|
foreach (var g in econGroups)
|
|
Console.WriteLine($" {g.Key,-18}: {g.Count(),3}");
|
|
Console.WriteLine();
|
|
|
|
// ── PoI list ──────────────────────────────────────────────────────────
|
|
var pois = ss.Where(s => s.IsPoi).ToList();
|
|
if (pois.Count > 0)
|
|
{
|
|
Console.WriteLine("═══════════════════════════════════════════════════════");
|
|
Console.WriteLine($" POINTS OF INTEREST ({pois.Count})");
|
|
Console.WriteLine("═══════════════════════════════════════════════════════");
|
|
foreach (var p in pois.OrderBy(p => p.PoiType).ThenBy(p => p.TileX))
|
|
Console.WriteLine($" ({p.TileX,4},{p.TileY,4}) {p.Name,-24} [{p.PoiType}]");
|
|
Console.WriteLine();
|
|
}
|
|
|
|
// ── Linear features ────────────────────────────────────────────────────
|
|
Console.WriteLine("═══════════════════════════════════════════════════════");
|
|
Console.WriteLine(" LINEAR FEATURES");
|
|
Console.WriteLine("═══════════════════════════════════════════════════════");
|
|
Console.WriteLine($" Rivers : {world.Rivers.Count}");
|
|
Console.WriteLine($" Roads : {world.Roads.Count}");
|
|
Console.WriteLine($" Rails : {world.Rails.Count}");
|
|
|
|
// ── Validation summary ─────────────────────────────────────────────────
|
|
if (world.StageHashes.TryGetValue("ValidationPass", out ulong vhash))
|
|
{
|
|
int violations = (int)(vhash / 1000);
|
|
int warnings = (int)(vhash % 1000);
|
|
Console.WriteLine();
|
|
Console.WriteLine("═══════════════════════════════════════════════════════");
|
|
Console.WriteLine(" VALIDATION");
|
|
Console.WriteLine("═══════════════════════════════════════════════════════");
|
|
Console.WriteLine($" Violations : {violations}");
|
|
Console.WriteLine($" Warnings : {warnings}");
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|