diff --git a/tests/testNetherGen.cpp b/tests/testNetherGen.cpp new file mode 100644 index 0000000..f52b285 --- /dev/null +++ b/tests/testNetherGen.cpp @@ -0,0 +1,166 @@ +#include "testHarness.h" +#include "vf/NetherGen.h" + +#include +#include + +using namespace vf; + +namespace { + +std::map censusColumn(const World& world, int columnX, int columnZ, int ceilingY) { + std::map tally; + int ox = columnX * chunkSize; + int oz = columnZ * chunkSize; + for (int gx = ox; gx < ox + chunkSize; ++gx) + for (int gz = oz; gz < oz + chunkSize; ++gz) + for (int gy = 0; gy < ceilingY; ++gy) + ++tally[world.blockAt(gx, gy, gz)]; + return tally; +} + +int countOf(const std::map& tally, Block block) { + auto it = tally.find(block); + return it == tally.end() ? 0 : it->second; +} + +int findColumnWithBiome(const NetherGenerator& generator, NetherBiome biome, int& outColumnX, + int& outColumnZ) { + for (int columnX = -40; columnX <= 40; ++columnX) { + for (int columnZ = -40; columnZ <= 40; ++columnZ) { + int probeX = columnX * chunkSize + 8; + int probeZ = columnZ * chunkSize + 8; + if (generator.biomeAt(probeX, probeZ) != biome) continue; + outColumnX = columnX; + outColumnZ = columnZ; + return 1; + } + } + return 0; +} + +} // namespace + +VF_TEST(netherChunkGenerationRepeatsExactlyOnTheSameSeed) { + NetherGenerator generator; + Chunk first, second; + generator.generateChunk(first, {2, 3, -4}); + generator.generateChunk(second, {2, 3, -4}); + CHECK(first.raw() == second.raw()); + + Chunk elsewhere; + generator.generateChunk(elsewhere, {2, 3, 5}); + CHECK(first.raw() != elsewhere.raw()); + + NetherGenerator::Params other; + other.seed = 9001u; + NetherGenerator reseeded(other); + + World original, alternative; + generator.generateColumn(original, 2, -4); + reseeded.generateColumn(alternative, 2, -4); + + int differingChunks = 0; + for (const auto& entry : original.allChunks()) { + const Chunk* twin = alternative.chunkAt(entry.first); + if (!twin || twin->raw() != entry.second->raw()) ++differingChunks; + } + CHECK(differingChunks > 0); +} + +VF_TEST(netherColumnIsByteIdenticalAcrossTwoRuns) { + NetherGenerator generator; + World first, second; + generator.generateColumn(first, -3, 6); + generator.generateColumn(second, -3, 6); + + CHECK_EQ(first.chunkCount(), second.chunkCount()); + CHECK_EQ(first.chunkCount(), static_cast(generator.stackChunkCount())); + + int mismatches = 0; + for (const auto& entry : first.allChunks()) { + const Chunk* twin = second.chunkAt(entry.first); + if (!twin || twin->raw() != entry.second->raw()) ++mismatches; + } + CHECK_EQ(mismatches, 0); +} + +VF_TEST(netherGenerationStaysInsideTheColumnItWasAskedFor) { + NetherGenerator generator; + World world; + generator.generateColumn(world, 5, 7); + + int strays = 0; + for (const auto& entry : world.allChunks()) + if (entry.first.x != 5 || entry.first.z != 7) ++strays; + CHECK_EQ(strays, 0); +} + +VF_TEST(netherIsSealedByBedrockAtTheFloorAndTheRoof) { + NetherGenerator generator; + const NetherGenerator::Params& params = generator.settings(); + World world; + generator.generateColumn(world, 0, 0); + + int floorGaps = 0; + int roofGaps = 0; + for (int gx = 0; gx < chunkSize; ++gx) { + for (int gz = 0; gz < chunkSize; ++gz) { + if (world.blockAt(gx, params.floorY, gz) != Block::Bedrock) ++floorGaps; + if (world.blockAt(gx, params.ceilingY - 1, gz) != Block::Bedrock) ++roofGaps; + } + } + CHECK_EQ(floorGaps, 0); + CHECK_EQ(roofGaps, 0); + + // The bands just inside those layers are ragged, not uniformly bedrock. + int raggedFloor = 0; + for (int gx = 0; gx < chunkSize; ++gx) + for (int gz = 0; gz < chunkSize; ++gz) + if (generator.isBedrock(gx, params.floorY + 2, gz)) ++raggedFloor; + CHECK(raggedFloor > 0); + CHECK(raggedFloor < chunkSize * chunkSize); + CHECK(!generator.isBedrock(0, 60, 0)); +} + +VF_TEST(netherCavernSitsBetweenTheFloorAndTheRoofMass) { + NetherGenerator generator; + const NetherGenerator::Params& params = generator.settings(); + + int drownedColumns = 0; + int dryColumns = 0; + for (int gx = -400; gx <= 400; gx += 37) { + for (int gz = -400; gz <= 400; gz += 37) { + NetherColumn column = generator.columnAt(gx, gz); + CHECK(column.floorHeight > params.floorY); + CHECK(column.ceilingHeight < params.ceilingY - 1); + CHECK(column.cavernHeight() >= 9); + CHECK_EQ(column.lavaLevel, params.lavaLevel); + if (column.drowned) ++drownedColumns; + else ++dryColumns; + } + } + CHECK(drownedColumns > 0); + CHECK(dryColumns > 0); +} + +VF_TEST(netherLavaSeaLeavesNoAirBelowItsSurface) { + NetherGenerator generator; + const NetherGenerator::Params& params = generator.settings(); + + int airBelowLava = 0; + int lavaSamples = 0; + for (int gx = -120; gx <= 120; gx += 11) { + for (int gz = -120; gz <= 120; gz += 11) { + NetherColumn column = generator.columnAt(gx, gz); + for (int gy = params.floorY + 1; gy <= params.lavaLevel; ++gy) { + Block block = generator.blockAt(column, gx, gy, gz); + if (block == Block::Air) ++airBelowLava; + if (block == Block::Lava) ++lavaSamples; + } + } + } + CHECK_EQ(airBelowLava, 0); + CHECK(lavaSamples > 0); +} +