diff --git a/modules/worldgen-overworld-v2/src/root.zig b/modules/worldgen-overworld-v2/src/root.zig index 4606413b..0108f75d 100644 --- a/modules/worldgen-overworld-v2/src/root.zig +++ b/modules/worldgen-overworld-v2/src/root.zig @@ -481,6 +481,101 @@ test "overworld-v2 places ground vegetation" { try std.testing.expect(vegetation_blocks > 0); } +test "overworld-v2 tree bounds check is signed at high altitude" { + var gen = OverworldV2Generator.init(12345, std.testing.allocator); + + // At the skip threshold (CHUNK_SIZE_Y - 8 == 248) every column is skipped + // regardless of biome/density, so no tree block may be written. With grass + + // forest set on every column, this proves the guard isn't wrapping and letting + // placement (or out-of-bounds canopy writes) slip through. + { + var chunk = Chunk.init(0, 0); + var z: u32 = 0; + while (z < CHUNK_SIZE_Z) : (z += 1) { + var x: u32 = 0; + while (x < CHUNK_SIZE_X) : (x += 1) { + chunk.setSurfaceHeight(x, z, 248); + chunk.setBlock(x, 248, z, .grass); + chunk.setBiome(x, z, .forest); + } + } + gen.placeTrees(&chunk, null); + var tree_blocks: u32 = 0; + for (chunk.blocks) |b| if (trees.isTreeBlock(b)) { + tree_blocks += 1; + }; + try std.testing.expectEqual(@as(u32, 0), tree_blocks); + } + + // Just below the threshold (247), forest columns remain eligible and oak/birch + // (height 6) still fit below CHUNK_SIZE_Y, so placement must occur. + var placed: u32 = 0; + const tree_positions = [_][2]i32{ .{ 0, 0 }, .{ 1, 0 }, .{ 0, 1 }, .{ -3, 4 }, .{ 5, -2 } }; + for (tree_positions) |pos| { + var chunk = Chunk.init(pos[0], pos[1]); + var z: u32 = 0; + while (z < CHUNK_SIZE_Z) : (z += 1) { + var x: u32 = 0; + while (x < CHUNK_SIZE_X) : (x += 1) { + chunk.setSurfaceHeight(x, z, 247); + chunk.setBlock(x, 247, z, .grass); + chunk.setBiome(x, z, .forest); + } + } + gen.placeTrees(&chunk, null); + for (chunk.blocks) |b| if (trees.isTreeBlock(b)) { + placed += 1; + }; + } + try std.testing.expect(placed > 0); +} + +test "overworld-v2 vegetation bounds check is signed at high altitude" { + var gen = OverworldV2Generator.init(12345, std.testing.allocator); + + // At the skip threshold (CHUNK_SIZE_Y - 2 == 254) all columns are skipped. + { + var chunk = Chunk.init(0, 0); + var z: u32 = 0; + while (z < CHUNK_SIZE_Z) : (z += 1) { + var x: u32 = 0; + while (x < CHUNK_SIZE_X) : (x += 1) { + chunk.setSurfaceHeight(x, z, 254); + chunk.setBlock(x, 254, z, .grass); + chunk.setBiome(x, z, .plains); + } + } + gen.placeVegetation(&chunk, null); + var veg_blocks: u32 = 0; + for (chunk.blocks) |b| if (vegetation.isVegetationBlock(b)) { + veg_blocks += 1; + }; + try std.testing.expectEqual(@as(u32, 0), veg_blocks); + } + + // Just below the threshold (253), plains grass can still receive ground + // vegetation (tall_grass / flowers) one block above the surface. + var placed: u32 = 0; + const veg_positions = [_][2]i32{ .{ 0, 0 }, .{ 1, 0 }, .{ 0, 1 }, .{ -3, 4 }, .{ 5, -2 } }; + for (veg_positions) |pos| { + var chunk = Chunk.init(pos[0], pos[1]); + var z: u32 = 0; + while (z < CHUNK_SIZE_Z) : (z += 1) { + var x: u32 = 0; + while (x < CHUNK_SIZE_X) : (x += 1) { + chunk.setSurfaceHeight(x, z, 253); + chunk.setBlock(x, 253, z, .grass); + chunk.setBiome(x, z, .plains); + } + } + gen.placeVegetation(&chunk, null); + for (chunk.blocks) |b| if (vegetation.isVegetationBlock(b)) { + placed += 1; + }; + } + try std.testing.expect(placed > 0); +} + test "overworld-v2 generates representative LOD data" { var gen = OverworldV2Generator.init(12345, std.testing.allocator); var data = try LODSimplifiedData.init(std.testing.allocator, .lod3); diff --git a/modules/worldgen-overworld-v2/src/trees.zig b/modules/worldgen-overworld-v2/src/trees.zig index fe04088c..4df1ae98 100644 --- a/modules/worldgen-overworld-v2/src/trees.zig +++ b/modules/worldgen-overworld-v2/src/trees.zig @@ -32,7 +32,7 @@ pub fn placeTrees(self: anytype, chunk: *Chunk, stop_flag: ?*const bool) void { var local_x: u32 = 0; while (local_x < CHUNK_SIZE_X) : (local_x += 1) { const surface_y = chunk.getSurfaceHeight(local_x, local_z); - if (surface_y <= self.params.sea_level or surface_y >= CHUNK_SIZE_Y - 8) continue; + if (surface_y <= self.params.sea_level or surface_y >= @as(i32, CHUNK_SIZE_Y) - 8) continue; const surface = chunk.getBlock(local_x, @intCast(surface_y), local_z); if (surface != .grass and surface != .dirt and surface != .snow_block and surface != .sand) continue; diff --git a/modules/worldgen-overworld-v2/src/vegetation.zig b/modules/worldgen-overworld-v2/src/vegetation.zig index c8b18f19..7fc82452 100644 --- a/modules/worldgen-overworld-v2/src/vegetation.zig +++ b/modules/worldgen-overworld-v2/src/vegetation.zig @@ -28,7 +28,7 @@ pub fn placeVegetation(self: anytype, chunk: *Chunk, stop_flag: ?*const bool) vo var local_x: u32 = 0; while (local_x < CHUNK_SIZE_X) : (local_x += 1) { const surface_y = chunk.getSurfaceHeight(local_x, local_z); - if (surface_y <= 0 or surface_y >= CHUNK_SIZE_Y - 2) continue; + if (surface_y <= 0 or surface_y >= @as(i32, CHUNK_SIZE_Y) - 2) continue; const biome = chunk.biomes[local_x + local_z * CHUNK_SIZE_X]; const surface = chunk.getBlock(local_x, @intCast(surface_y), local_z);