using OpenTK.Mathematics; using VoxelEngine.Core; using VoxelEngine.World.Blocks; using VoxelEngine.World.Chunk; using VoxelEngine.World.WorldGen; using Xunit; namespace VoxelEngine.Tests.WorldGen; public class CaveCarverTests { private static readonly BlockRegistry Blocks = new(); private static readonly Configuration Cfg = new(); private static Chunk MakeStoneChunk() { Chunk c = new(); for (int x = 0; x < Chunk.Size; x++) for (int y = 0; y < Chunk.Size; y++) for (int z = 0; z < Chunk.Size; z++) c[x, y, z] = Blocks.Stone.Id; return c; } [Fact] public void Carve_IsNoOpOnAirChunk() { var carver = new CaveCarver(seed: 42, Blocks, Cfg); Chunk c = new(); // all-air by default carver.Carve(c, new Vector3i(0, 4, 0)); for (int x = 0; x < Chunk.Size; x++) for (int y = 0; y < Chunk.Size; y++) for (int z = 0; z < Chunk.Size; z++) Assert.Equal(Blocks.Air.Id, c[x, y, z]); } [Fact] public void NoiseCaves_OnlyConvertsSolidToAir_NeverWaterNeverOtherBlocks() { var carver = new CaveCarver(seed: 42, Blocks, Cfg); Chunk c = MakeStoneChunk(); // Plant some water and air voxels we expect untouched. c[1, 1, 1] = Blocks.Water.Id; c[2, 2, 2] = Blocks.Air.Id; carver.CarveNoiseCaves(c, new Vector3i(0, 4, 0)); Assert.Equal(Blocks.Water.Id, c[1, 1, 1]); // water survived Assert.Equal(Blocks.Air.Id, c[2, 2, 2]); // air survived // Spot-check that everywhere else is either Stone or Air (no other blocks). for (int x = 0; x < Chunk.Size; x++) for (int y = 0; y < Chunk.Size; y++) for (int z = 0; z < Chunk.Size; z++) { ushort id = c[x, y, z]; Assert.True( id == Blocks.Stone.Id || id == Blocks.Air.Id || id == Blocks.Water.Id, $"unexpected block id {id} at ({x},{y},{z})"); } } [Fact] public void NoiseCaves_IsDeterministic_ForSameSeedAndChunkPos() { var carver1 = new CaveCarver(seed: 1234, Blocks, Cfg); var carver2 = new CaveCarver(seed: 1234, Blocks, Cfg); Chunk a = MakeStoneChunk(); Chunk b = MakeStoneChunk(); carver1.CarveNoiseCaves(a, new Vector3i(2, 4, 3)); carver2.CarveNoiseCaves(b, new Vector3i(2, 4, 3)); for (int x = 0; x < Chunk.Size; x++) for (int y = 0; y < Chunk.Size; y++) for (int z = 0; z < Chunk.Size; z++) Assert.Equal(a[x, y, z], b[x, y, z]); } [Fact] public void NoiseCaves_RespectsBedrockFloor_DoesNotCarveBelowY1() { // ChunkPos.Y = 0 means worldY ranges 0..15. We require Y=0 stays solid. var carver = new CaveCarver(seed: 42, Blocks, Cfg); Chunk c = MakeStoneChunk(); carver.CarveNoiseCaves(c, new Vector3i(0, 0, 0)); for (int x = 0; x < Chunk.Size; x++) for (int z = 0; z < Chunk.Size; z++) Assert.Equal(Blocks.Stone.Id, c[x, 0, z]); // worldY = 0 is bedrock } [Fact] public void NoiseCaves_SeamContinuity_AdjacentChunksAgreeAtBoundary() { var carver = new CaveCarver(seed: 7, Blocks, Cfg); Chunk c1 = MakeStoneChunk(); Chunk c2 = MakeStoneChunk(); carver.CarveNoiseCaves(c1, new Vector3i(0, 4, 0)); carver.CarveNoiseCaves(c2, new Vector3i(1, 4, 0)); int airAtSeamC1 = 0, airAtSeamC2 = 0; for (int y = 1; y < Chunk.Size; y++) for (int z = 0; z < Chunk.Size; z++) { if (c1[15, y, z] == Blocks.Air.Id) airAtSeamC1++; if (c2[0, y, z] == Blocks.Air.Id) airAtSeamC2++; } // Sanity: noise threshold = -0.3 yields some carved cells in each. Assert.True(airAtSeamC1 > 0, "c1 seam column had no air — carve too sparse?"); Assert.True(airAtSeamC2 > 0, "c2 seam column had no air — carve too sparse?"); } [Fact] public void WormCaves_IsDeterministic_ForSameSeedAndChunkPos() { var c1 = new CaveCarver(seed: 999, Blocks, Cfg); var c2 = new CaveCarver(seed: 999, Blocks, Cfg); Chunk a = MakeStoneChunk(); Chunk b = MakeStoneChunk(); c1.CarveWormCaves(a, new Vector3i(0, 4, 0)); c2.CarveWormCaves(b, new Vector3i(0, 4, 0)); for (int x = 0; x < Chunk.Size; x++) for (int y = 0; y < Chunk.Size; y++) for (int z = 0; z < Chunk.Size; z++) Assert.Equal(a[x, y, z], b[x, y, z]); } [Fact] public void WormCaves_OnlyConvertsSolidToAir_NeverWater() { var carver = new CaveCarver(seed: 999, Blocks, Cfg); Chunk c = MakeStoneChunk(); // Set every voxel along a diagonal to water — must survive. for (int i = 0; i < Chunk.Size; i++) c[i, i, i] = Blocks.Water.Id; carver.CarveWormCaves(c, new Vector3i(0, 4, 0)); for (int i = 0; i < Chunk.Size; i++) Assert.Equal(Blocks.Water.Id, c[i, i, i]); } [Fact] public void WormCaves_RespectsBedrockFloor() { var carver = new CaveCarver(seed: 999, Blocks, Cfg); Chunk c = MakeStoneChunk(); carver.CarveWormCaves(c, new Vector3i(0, 0, 0)); // worldY = 0..15 for (int x = 0; x < Chunk.Size; x++) for (int z = 0; z < Chunk.Size; z++) Assert.Equal(Blocks.Stone.Id, c[x, 0, z]); // bedrock untouched } [Fact] public void WormCaves_SeamContinuity_VoxelAtSharedWorldCoord_IsSame() { var carver = new CaveCarver(seed: 999, Blocks, Cfg); Chunk c1 = MakeStoneChunk(); Chunk c2 = MakeStoneChunk(); carver.CarveWormCaves(c1, new Vector3i(0, 4, 0)); carver.CarveWormCaves(c2, new Vector3i(1, 4, 0)); int airC1Seam = 0, airC2Seam = 0; for (int y = 1; y < Chunk.Size; y++) for (int z = 0; z < Chunk.Size; z++) { if (c1[15, y, z] == Blocks.Air.Id) airC1Seam++; if (c2[0, y, z] == Blocks.Air.Id) airC2Seam++; } bool bothEmpty = airC1Seam == 0 && airC2Seam == 0; bool bothCarved = airC1Seam > 0 && airC2Seam > 0; Assert.True(bothEmpty || bothCarved, $"seam mismatch: c1={airC1Seam}, c2={airC2Seam}"); } [Fact] public void Carve_RespectsMinCaveY_NoCarvingAboveCeiling() { var cfg = new Configuration { MinCaveY = 20 }; var carver = new CaveCarver(seed: 42, Blocks, cfg); Chunk c = MakeStoneChunk(); // Chunk Y=2 → worldY 32..47, all above MinCaveY=20 → must stay solid. carver.Carve(c, new Vector3i(0, 2, 0)); for (int x = 0; x < Chunk.Size; x++) for (int y = 0; y < Chunk.Size; y++) for (int z = 0; z < Chunk.Size; z++) Assert.Equal(Blocks.Stone.Id, c[x, y, z]); } }