using Theriapolis.Core; using Theriapolis.Core.World; using Theriapolis.Core.World.Generation; using Theriapolis.Core.World.Polylines; using Xunit; namespace Theriapolis.Tests.Worldgen; /// /// Hydrology correctness: rivers must be generated, endpoints must reach water. /// public sealed class HydrologyTests : IClassFixture { private const ulong TestSeed = 0xCAFEBABEUL; // HydrologyGen is stage 10 → 0-based index 9 (fast-path for hydrology-only tests). private const int HydrologyStageIndex = 9; private readonly WorldCache _cache; public HydrologyTests(WorldCache cache) => _cache = cache; [Fact] public void Pipeline_GeneratesAtLeastOneRiver() { var ctx = _cache.GetThrough(TestSeed, HydrologyStageIndex); Assert.NotEmpty(ctx.World.Rivers); } [Fact] public void AllRivers_HaveAtLeastTwoPoints() { var ctx = _cache.GetThrough(TestSeed, HydrologyStageIndex); foreach (var river in ctx.World.Rivers) Assert.True(river.Points.Count >= 2, $"River {river.Id} has fewer than 2 points"); } [Fact] public void RiverPolylines_AreInWorldPixelSpace() { var ctx = _cache.GetThrough(TestSeed, HydrologyStageIndex); float maxCoord = C.WORLD_WIDTH_TILES * C.WORLD_TILE_PIXELS; foreach (var river in ctx.World.Rivers) foreach (var pt in river.Points) { Assert.True(pt.X >= 0 && pt.X <= maxCoord, $"River {river.Id} point X={pt.X} out of world-pixel range"); Assert.True(pt.Y >= 0 && pt.Y <= maxCoord, $"River {river.Id} point Y={pt.Y} out of world-pixel range"); } } [Theory] [InlineData(0xCAFEBABEUL)] [InlineData(0x12345678UL)] [InlineData(0xDEADBEEFUL)] public void RiverEndpoints_DrainToWaterOrWorldEdge(ulong seed) { var ctx = _cache.GetThrough(seed, HydrologyStageIndex); var world = ctx.World; int W = C.WORLD_WIDTH_TILES; int H = C.WORLD_HEIGHT_TILES; int nonDrainingCount = 0; foreach (var river in world.Rivers) { if (river.Points.Count < 2) continue; var last = river.Points[^1]; int lx = Math.Clamp((int)(last.X / C.WORLD_TILE_PIXELS), 0, W - 1); int ly = Math.Clamp((int)(last.Y / C.WORLD_TILE_PIXELS), 0, H - 1); var biome = world.Tiles[lx, ly].Biome; bool atWater = biome == BiomeId.Ocean || biome == BiomeId.Wetland || (world.Tiles[lx, ly].Features & FeatureFlags.HasRiver) != 0; bool atEdge = lx == 0 || ly == 0 || lx == W - 1 || ly == H - 1; if (!atWater && !atEdge) nonDrainingCount++; } // Allow up to 10% non-draining rivers (noise from complex terrain) double failRate = world.Rivers.Count > 0 ? (double)nonDrainingCount / world.Rivers.Count : 0.0; Assert.True(failRate <= 0.10, $"Seed {seed:X}: {nonDrainingCount}/{world.Rivers.Count} rivers do not drain to water ({failRate:P0})"); } [Fact] public void EncounterDensityMap_IsPopulated() { var ctx = _cache.Get(TestSeed); Assert.NotNull(ctx.World.EncounterDensity); float sum = 0; int W = C.WORLD_WIDTH_TILES; int H = C.WORLD_HEIGHT_TILES; for (int y = 0; y < H; y++) for (int x = 0; x < W; x++) sum += ctx.World.EncounterDensity![x, y]; Assert.True(sum > 0, "EncounterDensity map is all zeros"); } }