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,172 @@
|
||||
using Theriapolis.Core;
|
||||
using Theriapolis.Core.World;
|
||||
using Theriapolis.Core.World.Generation;
|
||||
using Xunit;
|
||||
|
||||
namespace Theriapolis.Tests.Worldgen;
|
||||
|
||||
/// <summary>
|
||||
/// Settlement placement correctness: narrative anchors, tier counts, distances,
|
||||
/// reachability, and no overlaps.
|
||||
/// </summary>
|
||||
public sealed class SettlementTests : IClassFixture<WorldCache>
|
||||
{
|
||||
private const ulong TestSeed = 0xCAFEBABEUL;
|
||||
private readonly WorldCache _cache;
|
||||
|
||||
public SettlementTests(WorldCache cache) => _cache = cache;
|
||||
|
||||
// ── Narrative anchors ─────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void SanctumFidelis_IsPresent()
|
||||
{
|
||||
var ctx = _cache.Get(TestSeed);
|
||||
var capital = ctx.World.Settlements
|
||||
.FirstOrDefault(s => s.Anchor == NarrativeAnchor.SanctumFidelis);
|
||||
Assert.NotNull(capital);
|
||||
Assert.Equal(1, capital!.Tier);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllNarrativeAnchors_ArePlaced()
|
||||
{
|
||||
var ctx = _cache.Get(TestSeed);
|
||||
var anchors = ctx.World.Settlements
|
||||
.Where(s => s.Anchor.HasValue)
|
||||
.Select(s => s.Anchor!.Value)
|
||||
.ToHashSet();
|
||||
|
||||
foreach (NarrativeAnchor anchor in Enum.GetValues<NarrativeAnchor>())
|
||||
Assert.Contains(anchor, anchors);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0xCAFEBABEUL)]
|
||||
[InlineData(0x11111111UL)]
|
||||
[InlineData(0x99887766UL)]
|
||||
[InlineData(0xABCDEF01UL)]
|
||||
[InlineData(0x00000042UL)]
|
||||
public void NarrativeAnchors_PlacedAcrossMultipleSeeds(ulong seed)
|
||||
{
|
||||
var ctx = _cache.Get(seed);
|
||||
var capital = ctx.World.Settlements
|
||||
.FirstOrDefault(s => s.Anchor == NarrativeAnchor.SanctumFidelis);
|
||||
Assert.NotNull(capital);
|
||||
}
|
||||
|
||||
// ── Tier counts ───────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void TierCounts_MeetMinimums()
|
||||
{
|
||||
var ctx = _cache.Get(TestSeed);
|
||||
var ss = ctx.World.Settlements.Where(s => !s.IsPoi).ToList();
|
||||
|
||||
int tier1 = ss.Count(s => s.Tier == 1);
|
||||
int tier2 = ss.Count(s => s.Tier == 2);
|
||||
int tier3 = ss.Count(s => s.Tier == 3);
|
||||
int tier4 = ss.Count(s => s.Tier == 4);
|
||||
|
||||
Assert.Equal(1, tier1); // exactly one capital
|
||||
Assert.InRange(tier2, C.SETTLE_TIER2_MIN, C.SETTLE_TIER2_MAX);
|
||||
Assert.InRange(tier3, C.SETTLE_TIER3_MIN, C.SETTLE_TIER3_MAX);
|
||||
Assert.True(tier4 >= C.SETTLE_TIER4_MIN,
|
||||
$"Only {tier4} tier-4 settlements, need at least {C.SETTLE_TIER4_MIN}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PoICount_MeetsMinimum()
|
||||
{
|
||||
var ctx = _cache.Get(TestSeed);
|
||||
int pois = ctx.World.Settlements.Count(s => s.IsPoi);
|
||||
Assert.True(pois >= C.SETTLE_TIER5_MIN,
|
||||
$"Only {pois} PoIs, need at least {C.SETTLE_TIER5_MIN}");
|
||||
}
|
||||
|
||||
// ── No overlapping settlements ─────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void NoSettlements_ShareTheSameTile()
|
||||
{
|
||||
var ctx = _cache.Get(TestSeed);
|
||||
var positions = new HashSet<(int, int)>();
|
||||
foreach (var s in ctx.World.Settlements)
|
||||
{
|
||||
bool added = positions.Add((s.TileX, s.TileY));
|
||||
Assert.True(added,
|
||||
$"Two settlements share tile ({s.TileX},{s.TileY})");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Minimum separation ─────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Tier1And2Settlements_MeetMinimumDistance()
|
||||
{
|
||||
var ctx = _cache.Get(TestSeed);
|
||||
var high = ctx.World.Settlements.Where(s => s.Tier <= 2 && !s.IsPoi).ToList();
|
||||
int minSq = C.SETTLE_MIN_DIST_TIER2 * C.SETTLE_MIN_DIST_TIER2;
|
||||
|
||||
for (int i = 0; i < high.Count; i++)
|
||||
for (int j = i + 1; j < high.Count; j++)
|
||||
{
|
||||
int dx = high[i].TileX - high[j].TileX;
|
||||
int dy = high[i].TileY - high[j].TileY;
|
||||
Assert.True(dx * dx + dy * dy >= minSq,
|
||||
$"{high[i].Name} and {high[j].Name} are too close ({Math.Sqrt(dx*dx+dy*dy):F0} tiles, min {C.SETTLE_MIN_DIST_TIER2})");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Settlement attributes ─────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void AllSettlements_HaveNames()
|
||||
{
|
||||
var ctx = _cache.Get(TestSeed);
|
||||
foreach (var s in ctx.World.Settlements.Where(s => !s.IsPoi))
|
||||
Assert.False(string.IsNullOrWhiteSpace(s.Name),
|
||||
$"Settlement at ({s.TileX},{s.TileY}) Tier {s.Tier} has no name");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllSettlements_HaveValidTileCoordinates()
|
||||
{
|
||||
var ctx = _cache.Get(TestSeed);
|
||||
int W = C.WORLD_WIDTH_TILES;
|
||||
int H = C.WORLD_HEIGHT_TILES;
|
||||
foreach (var s in ctx.World.Settlements)
|
||||
{
|
||||
Assert.InRange(s.TileX, 0, W - 1);
|
||||
Assert.InRange(s.TileY, 0, H - 1);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoSettlement_PlacedOnOcean()
|
||||
{
|
||||
var ctx = _cache.Get(TestSeed);
|
||||
foreach (var s in ctx.World.Settlements)
|
||||
{
|
||||
var biome = ctx.World.Tiles[s.TileX, s.TileY].Biome;
|
||||
Assert.NotEqual(BiomeId.Ocean, biome);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Faction influence ─────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void FactionInfluence_IsComputed()
|
||||
{
|
||||
var ctx = _cache.Get(TestSeed);
|
||||
Assert.NotNull(ctx.World.FactionInfluence);
|
||||
|
||||
// Capital area should have high Enforcer influence
|
||||
var capital = ctx.World.Settlements
|
||||
.First(s => s.Anchor == NarrativeAnchor.SanctumFidelis);
|
||||
float enforcer = ctx.World.FactionInfluence!
|
||||
.Get((int)FactionId.CovenantEnforcers, capital.TileX, capital.TileY);
|
||||
Assert.True(enforcer > 0.5f,
|
||||
$"Enforcer influence at capital is only {enforcer:F3}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user