Files
TheriapolisV3/Theriapolis.Tests/Worldgen/LinearFeatureTests.cs
T

102 lines
3.2 KiB
C#
Raw Normal View History

using Theriapolis.Core;
using Theriapolis.Core.World;
using Theriapolis.Core.World.Generation;
using Xunit;
namespace Theriapolis.Tests.Worldgen;
/// <summary>
/// Linear feature exclusion (Addendum A §2): no parallel river+rail or rail+road
/// on the same non-settlement tile. Road+river is allowed — it represents a
/// bridge crossing (see ValidationPassStage.CheckLinearExclusion).
/// </summary>
public sealed class LinearFeatureTests : IClassFixture<WorldCache>
{
private readonly WorldCache _cache;
public LinearFeatureTests(WorldCache cache) => _cache = cache;
[Theory]
[InlineData(0xCAFEBABEUL)]
[InlineData(0x11223344UL)]
[InlineData(0xDEADBEEFUL)]
[InlineData(0xFEEDFACEUL)]
[InlineData(0xABCDEF00UL)]
[InlineData(0x00112233UL)]
[InlineData(0x99AABBCCUL)]
[InlineData(0x12345678UL)]
[InlineData(0x87654321UL)]
[InlineData(0x0DEADC0DUL)]
public void NoParallelLinearFeatures_OnNonSettlementTiles(ulong seed)
{
var ctx = _cache.Get(seed);
var world = ctx.World;
int W = C.WORLD_WIDTH_TILES;
int H = C.WORLD_HEIGHT_TILES;
int violations = 0;
for (int y = 0; y < H; y++)
for (int x = 0; x < W; x++)
{
ref var tile = ref world.TileAt(x, y);
if ((tile.Features & FeatureFlags.IsSettlement) != 0) continue;
bool hasRiver = (tile.Features & FeatureFlags.HasRiver) != 0;
bool hasRail = (tile.Features & FeatureFlags.HasRail) != 0;
bool hasRoad = (tile.Features & FeatureFlags.HasRoad) != 0;
// River + Rail parallel
if (hasRiver && hasRail &&
tile.RiverFlowDir != Theriapolis.Core.Util.Dir.None &&
tile.RailDir != Theriapolis.Core.Util.Dir.None &&
Theriapolis.Core.Util.Dir.IsParallel(tile.RiverFlowDir, tile.RailDir))
violations++;
// Rail + Road (always a violation outside settlements)
if (hasRail && hasRoad)
violations++;
}
Assert.Equal(0, violations);
}
[Fact]
public void RoadNetwork_HasAtLeastOneRoad()
{
var ctx = _cache.Get(0xCAFEBABEUL);
Assert.NotEmpty(ctx.World.Roads);
}
[Fact]
public void RailNetwork_HasAtLeastOneRailLine()
{
if (!C.ENABLE_RAIL) return; // rail subsystem disabled; nothing to assert
var ctx = _cache.Get(0xCAFEBABEUL);
Assert.NotEmpty(ctx.World.Rails);
}
[Fact]
public void Roads_AreInWorldPixelSpace()
{
var ctx = _cache.Get(0xCAFEBABEUL);
float maxCoord = C.WORLD_WIDTH_TILES * C.WORLD_TILE_PIXELS;
foreach (var road in ctx.World.Roads)
foreach (var pt in road.Points)
{
Assert.InRange(pt.X, 0f, maxCoord);
Assert.InRange(pt.Y, 0f, maxCoord);
}
}
[Fact]
public void ValidationPassHash_RecordsZeroViolations()
{
var ctx = _cache.Get(0xCAFEBABEUL);
if (!ctx.World.StageHashes.TryGetValue("ValidationPass", out ulong vhash))
return; // stage didn't record hash — skip
int violations = (int)(vhash / 1000);
Assert.Equal(0, violations);
}
}