diff --git a/Core/Game.cs b/Core/Game.cs index 5aeb658..0c0096d 100644 --- a/Core/Game.cs +++ b/Core/Game.cs @@ -55,7 +55,7 @@ public sealed class Game : GameWindow this._world = new World.World(this._registry, this._modStore); int seed = this._cfg.Seed ?? new Random().Next(); - this._generator = new WorldGenerator(seed, this._registry); + this._generator = new WorldGenerator(seed, this._registry, this._cfg); // Spawn at terrain surface above (0, 0). int surfaceY = this._generator.SurfaceHeightAt(0, 0); diff --git a/VoxelEngine.Tests/Streaming/StreamingIntegrationTests.cs b/VoxelEngine.Tests/Streaming/StreamingIntegrationTests.cs index 66935a1..a51e3ef 100644 --- a/VoxelEngine.Tests/Streaming/StreamingIntegrationTests.cs +++ b/VoxelEngine.Tests/Streaming/StreamingIntegrationTests.cs @@ -18,7 +18,7 @@ public class StreamingIntegrationTests var registry = new BlockRegistry(); var store = new InMemoryChunkModificationStore(); var world = new World.World(registry, store); - var gen = new WorldGenerator(seed: 1337, blocks: registry); + var gen = new WorldGenerator(seed: 1337, blocks: registry, cfg: new Configuration()); var mesher = new ChunkMesher(atlasWidthPixels: 128, atlasHeightPixels: 32); var executor = new SynchronousJobExecutor(); Func factory = m => new FakeRenderer(m); diff --git a/VoxelEngine.Tests/WorldGen/WorldGeneratorTests.cs b/VoxelEngine.Tests/WorldGen/WorldGeneratorTests.cs index d1aa186..817aaf2 100644 --- a/VoxelEngine.Tests/WorldGen/WorldGeneratorTests.cs +++ b/VoxelEngine.Tests/WorldGen/WorldGeneratorTests.cs @@ -1,4 +1,5 @@ using OpenTK.Mathematics; +using VoxelEngine.Core; using VoxelEngine.World.Blocks; using VoxelEngine.World.Chunk; using VoxelEngine.World.WorldGen; @@ -58,8 +59,8 @@ public class WorldGeneratorTests [Fact] public void Generate_IsDeterministic() { - var gen1 = new WorldGenerator(99, Blocks); - var gen2 = new WorldGenerator(99, Blocks); + var gen1 = new WorldGenerator(99, Blocks, new Configuration()); + var gen2 = new WorldGenerator(99, Blocks, new Configuration()); Chunk a = gen1.Generate(new Vector3i(0, 4, 0)); Chunk b = gen2.Generate(new Vector3i(0, 4, 0)); for (int x = 0; x < Chunk.Size; x++) @@ -67,4 +68,32 @@ public class WorldGeneratorTests for (int z = 0; z < Chunk.Size; z++) Assert.Equal(a[x, y, z], b[x, y, z]); } + + [Fact] + public void Generate_AppliesCaves_ProducesAirVoxelsBelowSurface() + { + var gen = new WorldGenerator(seed: 12345, Blocks, new Configuration()); + // A chunk well below typical surface height — guaranteed mostly stone before carving. + Chunk c = gen.Generate(new Vector3i(0, 1, 0)); // worldY = 16..31 + + int airCount = 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++) + if (c[x, y, z] == Blocks.Air.Id) airCount++; + + Assert.True(airCount > 0, + "Deep chunk had zero air voxels after generation — caves not running?"); + } + + [Fact] + public void Generate_PreservesBedrockFloor_Y0_IsStone() + { + var gen = new WorldGenerator(seed: 12345, Blocks, new Configuration()); + Chunk c = gen.Generate(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]); + } } diff --git a/World/WorldGen/WorldGenerator.cs b/World/WorldGen/WorldGenerator.cs index 13837c3..4e09a90 100644 --- a/World/WorldGen/WorldGenerator.cs +++ b/World/WorldGen/WorldGenerator.cs @@ -1,4 +1,5 @@ using OpenTK.Mathematics; +using VoxelEngine.Core; using VoxelEngine.World.Blocks; using VoxelChunk = VoxelEngine.World.Chunk.Chunk; @@ -12,8 +13,9 @@ public sealed class WorldGenerator private readonly BlockRegistry _blocks; private readonly BiomeMap _biomeMap; private readonly TerrainSampler _terrain; + private readonly CaveCarver _caves; - public WorldGenerator(int seed, BlockRegistry blocks) + public WorldGenerator(int seed, BlockRegistry blocks, Configuration cfg) { this._blocks = blocks; NoiseSource noise = new(seed); @@ -21,6 +23,7 @@ public sealed class WorldGenerator BiomeRegistry biomes = new(seed, blocks); this._biomeMap = new BiomeMap(noise, cells, biomes); this._terrain = new TerrainSampler(noise, this._biomeMap); + this._caves = new CaveCarver(seed, blocks, cfg); } public int SurfaceHeightAt(int worldX, int worldZ) => this._terrain.HeightAt(worldX, worldZ); @@ -46,10 +49,11 @@ public sealed class WorldGenerator chunk[x, y, z] = WorldGenerator.BlockAt(worldY, height, biome, this._blocks); } } + + this._caves.Carve(chunk, chunkPos); return chunk; } - // Pure column fill rule, extracted for testability. public static ushort BlockAt(int worldY, int surfaceHeight, IBiome biome, BlockRegistry blocks) { if (worldY > surfaceHeight)