using AcDream.Core.Terrain; using Xunit; namespace AcDream.Core.Tests.Terrain; public class TerrainBlendingTests { // Asheron's Call terrain texture type values (from TerrainTextureType enum) private const int Grass = 1; private const int Dirt = 4; [Fact] public void GetPalCode_AllGrassNoRoads_ProducesHandComputedValue() { // sizeBits = 1 << 28 = 0x10000000 // terrainBits= (1<<15)|(1<<10)|(1<<5)|1 = 0x8421 (t1..t4 = Grass=1) // roadBits = 0 // total = 0x10008421 uint actual = TerrainBlending.GetPalCode(0, 0, 0, 0, Grass, Grass, Grass, Grass); Assert.Equal(0x10008421u, actual); } [Fact] public void GetPalCode_AllZeros_ProducesOnlySizeBit() { uint actual = TerrainBlending.GetPalCode(0, 0, 0, 0, 0, 0, 0, 0); Assert.Equal(0x10000000u, actual); } [Fact] public void GetPalCode_IsDeterministic() { uint a = TerrainBlending.GetPalCode(1, 0, 0, 1, Grass, Dirt, Grass, Dirt); uint b = TerrainBlending.GetPalCode(1, 0, 0, 1, Grass, Dirt, Grass, Dirt); Assert.Equal(a, b); } [Fact] public void GetPalCode_DifferentTerrainCornersProduceDifferentCodes() { uint allGrass = TerrainBlending.GetPalCode(0, 0, 0, 0, Grass, Grass, Grass, Grass); uint grassDirtMix = TerrainBlending.GetPalCode(0, 0, 0, 0, Grass, Dirt, Grass, Dirt); Assert.NotEqual(allGrass, grassDirtMix); } [Fact] public void GetPalCode_RoadFlagAffectsOutput() { uint noRoad = TerrainBlending.GetPalCode(0, 0, 0, 0, Grass, Grass, Grass, Grass); uint withRoad = TerrainBlending.GetPalCode(1, 0, 0, 0, Grass, Grass, Grass, Grass); Assert.NotEqual(noRoad, withRoad); // r1 occupies bits 26-27, so a road-only delta changes only those bits Assert.Equal(1u << 26, noRoad ^ withRoad); } [Fact] public void GetPalCode_SizeBitAlwaysSet() { uint code = TerrainBlending.GetPalCode(3, 3, 3, 3, Dirt, Dirt, Dirt, Dirt); Assert.Equal(1u << 28, code & (1u << 28)); } [Fact] public void GetPalCode_TerrainBitsOccupyLowFifteen() { // With all roads zero, the top 15 bits (above bit 15) should only contain // the sizeBits marker. Terrain fields occupy bits 0-19: t1=15..19, t2=10..14, // t3=5..9, t4=0..4. With t values up to 31 (5 bits), the terrain region // stays within bits 0-19. uint code = TerrainBlending.GetPalCode(0, 0, 0, 0, 31, 31, 31, 31); // Nothing between bits 20-27 should be set. uint forbiddenRegion = code & 0x0FF00000u; Assert.Equal(0u, forbiddenRegion); } [Fact] public void CalculateSplitDirection_OriginCell_IsSWtoNE() { // Hand-computed: // seedA = 0, seedB = 0 // magicA = 1813693831, magicB = 0 // uintRes = 1813693831 - 0 - 1369149221 = 444544610 // norm = 444544610 * (1/2^32) ≈ 0.1035 < 0.5 → SWtoNE var actual = TerrainBlending.CalculateSplitDirection(0, 0, 0, 0); Assert.Equal(CellSplitDirection.SWtoNE, actual); } [Fact] public void CalculateSplitDirection_IsDeterministic() { var a = TerrainBlending.CalculateSplitDirection(0xA9, 3, 0xB4, 5); var b = TerrainBlending.CalculateSplitDirection(0xA9, 3, 0xB4, 5); Assert.Equal(a, b); } [Fact] public void CalculateSplitDirection_DifferentCellsCanDiffer() { // We can't assert the exact value for every cell without instrumenting // WorldBuilder, but across a full landblock's 64 cells the hash is // designed to produce a mix of both directions — if we see only one, // the hash has collapsed. int swCount = 0, seCount = 0; for (uint cx = 0; cx < 8; cx++) for (uint cy = 0; cy < 8; cy++) { var dir = TerrainBlending.CalculateSplitDirection(0xA9, cx, 0xB4, cy); if (dir == CellSplitDirection.SWtoNE) swCount++; else seCount++; } Assert.True(swCount > 0, "Expected at least one SWtoNE cell in a landblock"); Assert.True(seCount > 0, "Expected at least one SEtoNW cell in a landblock"); } }