b451f83174
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>
102 lines
3.2 KiB
C#
102 lines
3.2 KiB
C#
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);
|
|
}
|
|
}
|