using Theriapolis.Core; using Theriapolis.Core.World; using Theriapolis.Core.World.Generation; using Xunit; namespace Theriapolis.Tests.Worldgen; /// /// Settlement placement correctness: narrative anchors, tier counts, distances, /// reachability, and no overlaps. /// public sealed class SettlementTests : IClassFixture { 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()) 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}"); } }