From cd89d53545895859e230feb466355cfd8715c15a Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 26 Aug 2026 21:47:53 +0200 Subject: [PATCH] test: add tests for biome selection from noise-driven sampling --- tests/testBiomeGen.cpp | 213 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 tests/testBiomeGen.cpp diff --git a/tests/testBiomeGen.cpp b/tests/testBiomeGen.cpp new file mode 100644 index 0000000..20dab07 --- /dev/null +++ b/tests/testBiomeGen.cpp @@ -0,0 +1,213 @@ +#include "testHarness.h" +#include "vf/BiomeGen.h" + +#include +#include +#include +#include + +using namespace vf; + +namespace { + +ClimateSample makeClimate(float temperature, float humidity, float continentalness, float erosion, float weirdness) { + ClimateSample climate; + climate.temperature = temperature; + climate.humidity = humidity; + climate.continentalness = continentalness; + climate.erosion = erosion; + climate.weirdness = weirdness; + return climate; +} + +} // namespace + +VF_TEST(biomeGenClimateMapsColdDryToTundra) { + BiomeGen gen; + CHECK(gen.biomeFromClimate(makeClimate(-0.6f, -0.2f, 0.3f, 0.0f, 0.5f)) == BiomeKind::Tundra); +} + +VF_TEST(biomeGenClimateMapsHotDryToDesert) { + BiomeGen gen; + CHECK(gen.biomeFromClimate(makeClimate(0.7f, -0.4f, 0.3f, 0.0f, 0.5f)) == BiomeKind::Desert); +} + +VF_TEST(biomeGenClimateMapsHotWetToJungle) { + BiomeGen gen; + CHECK(gen.biomeFromClimate(makeClimate(0.6f, 0.6f, 0.3f, 0.0f, 0.5f)) == BiomeKind::Jungle); +} + +VF_TEST(biomeGenClimateMapsHotMidHumidityToSavanna) { + BiomeGen gen; + CHECK(gen.biomeFromClimate(makeClimate(0.6f, 0.1f, 0.3f, 0.0f, 0.5f)) == BiomeKind::Savanna); +} + +VF_TEST(biomeGenClimateMapsMildWetToSwamp) { + BiomeGen gen; + CHECK(gen.biomeFromClimate(makeClimate(0.1f, 0.6f, 0.3f, 0.0f, 0.5f)) == BiomeKind::Swamp); +} + +VF_TEST(biomeGenClimateMapsMildDampToForest) { + BiomeGen gen; + CHECK(gen.biomeFromClimate(makeClimate(0.1f, 0.2f, 0.3f, 0.0f, 0.5f)) == BiomeKind::Forest); +} + +VF_TEST(biomeGenClimateMapsMildDryToPlains) { + BiomeGen gen; + CHECK(gen.biomeFromClimate(makeClimate(0.1f, -0.2f, 0.3f, 0.0f, 0.5f)) == BiomeKind::Plains); +} + +VF_TEST(biomeGenClimateMapsCoolMildToTaiga) { + BiomeGen gen; + CHECK(gen.biomeFromClimate(makeClimate(-0.2f, 0.0f, 0.3f, 0.0f, 0.5f)) == BiomeKind::Taiga); +} + +VF_TEST(biomeGenClimateMapsLowContinentalnessToOceanRegardlessOfOtherAxes) { + BiomeGen gen; + CHECK(gen.biomeFromClimate(makeClimate(0.8f, 0.8f, -0.6f, 0.9f, 0.5f)) == BiomeKind::Ocean); + CHECK(gen.biomeFromClimate(makeClimate(-0.8f, -0.8f, -0.6f, -0.9f, 0.5f)) == BiomeKind::Ocean); +} + +VF_TEST(biomeGenClimateMapsShallowContinentalnessToBeach) { + BiomeGen gen; + CHECK(gen.biomeFromClimate(makeClimate(0.2f, 0.2f, -0.25f, 0.0f, 0.5f)) == BiomeKind::Beach); +} + +VF_TEST(biomeGenClimateMapsHighErosionToMountains) { + BiomeGen gen; + CHECK(gen.biomeFromClimate(makeClimate(0.2f, 0.2f, 0.4f, 0.6f, 0.5f)) == BiomeKind::Mountains); +} + +VF_TEST(biomeGenProducesEveryCatalogueBiomeOverAWideScan) { + BiomeGen gen; + std::set seen; + for (int gx = -800; gx <= 800; gx += 11) + for (int gz = -800; gz <= 800; gz += 11) + seen.insert(static_cast(gen.biomeAt(gx, gz))); + CHECK(seen.size() >= 8); +} + +VF_TEST(biomeGenIsDeterministicForSameSeedAndColumn) { + BiomeGen first(BiomeGenParams{4242u, 0.003f, 0.006f, 0.01f, 0.045f, 26}); + BiomeGen second(BiomeGenParams{4242u, 0.003f, 0.006f, 0.01f, 0.045f, 26}); + for (int gx = -100; gx <= 100; gx += 7) { + for (int gz = -100; gz <= 100; gz += 7) { + ClimateSample a = first.climateAt(gx, gz); + ClimateSample b = second.climateAt(gx, gz); + CHECK_NEAR(a.temperature, b.temperature, 1e-6f); + CHECK_NEAR(a.humidity, b.humidity, 1e-6f); + CHECK(first.biomeAt(gx, gz) == second.biomeAt(gx, gz)); + } + } +} + +VF_TEST(biomeGenDifferentSeedsDivergeSomewhere) { + BiomeGen a(BiomeGenParams{1u, 0.003f, 0.006f, 0.01f, 0.045f, 26}); + BiomeGen b(BiomeGenParams{2u, 0.003f, 0.006f, 0.01f, 0.045f, 26}); + int differing = 0; + for (int gx = -400; gx <= 400; gx += 13) + for (int gz = -400; gz <= 400; gz += 13) + if (a.biomeAt(gx, gz) != b.biomeAt(gx, gz)) ++differing; + CHECK(differing > 0); +} + +VF_TEST(biomeGenRiverCorrelatesWithWeirdnessNearZero) { + BiomeGen gen; + int riverButFarFromZero = 0; + int nonRiverButNearZero = 0; + int riverSamples = 0; + for (int gx = -600; gx <= 600; gx += 5) { + for (int gz = -600; gz <= 600; gz += 5) { + ClimateSample climate = gen.climateAt(gx, gz); + bool river = gen.isRiver(gx, gz); + if (river) { + ++riverSamples; + if (std::fabs(climate.weirdness) >= gen.settings().riverWidth) ++riverButFarFromZero; + } else if (climate.continentalness >= -0.35f && std::fabs(climate.weirdness) < gen.settings().riverWidth) { + ++nonRiverButNearZero; + } + } + } + CHECK(riverSamples > 0); + CHECK_EQ(riverButFarFromZero, 0); + CHECK_EQ(nonRiverButNearZero, 0); +} + +VF_TEST(biomeGenSurfaceLayersMatchBiomeExpectation) { + BiomeGen gen; + CHECK(gen.surfaceFor(BiomeKind::Tundra).top == Block::Snow); + CHECK(gen.surfaceFor(BiomeKind::Desert).top == Block::Sand); + CHECK(gen.surfaceFor(BiomeKind::Ocean).top == Block::Sand); + CHECK(gen.surfaceFor(BiomeKind::Beach).top == Block::Sand); + CHECK(gen.surfaceFor(BiomeKind::Plains).top == Block::Grass); + CHECK(gen.surfaceFor(BiomeKind::Forest).top == Block::Grass); + CHECK(gen.surfaceFor(BiomeKind::Jungle).top == Block::Grass); + CHECK(gen.surfaceFor(BiomeKind::Mountains).top == Block::Stone); + CHECK(gen.surfaceFor(BiomeKind::Plains).filler == Block::Dirt); + CHECK(gen.surfaceFor(BiomeKind::Desert).filler == Block::Sandstone); +} + +VF_TEST(biomeGenSurfaceBlockAtPrefersRiverWaterOverLandTop) { + BiomeGen gen; + bool foundRiverWater = false; + for (int gx = -600; gx <= 600 && !foundRiverWater; gx += 3) { + for (int gz = -600; gz <= 600 && !foundRiverWater; gz += 3) { + if (gen.isRiver(gx, gz)) { + Block surface = gen.surfaceBlockAt(gx, gz, gen.settings().seaLevel); + CHECK(surface == Block::Water); + foundRiverWater = true; + } + } + } + CHECK(foundRiverWater); +} + +VF_TEST(biomeGenDecoratorDensityRanksJungleAboveDesertForTrees) { + BiomeGen gen; + CHECK(gen.decoratorsFor(BiomeKind::Jungle).treeDensity > gen.decoratorsFor(BiomeKind::Desert).treeDensity); + CHECK(gen.decoratorsFor(BiomeKind::Desert).caneDensity > 0.0f); + CHECK(gen.decoratorsFor(BiomeKind::Desert).treeDensity == 0.0f); + CHECK(gen.decoratorsFor(BiomeKind::Ocean).treeDensity == 0.0f); + CHECK(gen.decoratorsFor(BiomeKind::Ocean).bushDensity == 0.0f); +} + +VF_TEST(biomeGenBiomeBlendWeightsSumToOne) { + BiomeGen gen; + for (int gx = -50; gx <= 50; gx += 17) { + for (int gz = -50; gz <= 50; gz += 17) { + BiomeWeights weights = gen.biomeBlend(gx, gz, 2); + float total = 0.0f; + for (float w : weights.weight) total += w; + CHECK_NEAR(total, 1.0f, 1e-4f); + } + } +} + +VF_TEST(biomeGenBiomeBlendIsPureVoteOfSampledBiomes) { + BiomeGen gen; + int gx = 4000, gz = -3500; + BiomeWeights weights = gen.biomeBlend(gx, gz, 3); + int sampleCount = 0; + std::array(BiomeKind::Count)> counts{}; + for (int dz = -3; dz <= 3; ++dz) { + for (int dx = -3; dx <= 3; ++dx) { + BiomeKind biome = gen.biomeAt(gx + dx, gz + dz); + counts[static_cast(biome)] += 1; + ++sampleCount; + } + } + for (std::size_t i = 0; i < counts.size(); ++i) { + float expected = static_cast(counts[i]) / static_cast(sampleCount); + CHECK_NEAR(weights.weight[i], expected, 1e-5f); + } +} + +VF_TEST(biomeGenNameTableCoversEveryBiome) { + std::set names; + for (int i = 0; i < static_cast(BiomeKind::Count); ++i) { + std::string name = biomeKindName(static_cast(i)); + CHECK(!name.empty()); + names.insert(name); + } + CHECK_EQ(names.size(), static_cast(BiomeKind::Count)); +}