Initial commit: Theriapolis baseline at port/godot branch point
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>
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user