diff --git a/tests/testStructures.cpp b/tests/testStructures.cpp new file mode 100644 index 0000000..58e593f --- /dev/null +++ b/tests/testStructures.cpp @@ -0,0 +1,123 @@ +#include "testHarness.h" +#include "vf/Structures.h" +#include "vf/WorldGen.h" + +using namespace vf; + +namespace { + +bool sameEdits(const std::vector& a, const std::vector& b) { + if (a.size() != b.size()) return false; + for (std::size_t i = 0; i < a.size(); ++i) { + if (!(a[i].offset == b[i].offset) || a[i].block != b[i].block) return false; + } + return true; +} + +std::size_t countBlock(const World& world, Block target) { + std::size_t total = 0; + for (const auto& entry : world.allChunks()) + for (Block b : entry.second->raw()) + if (b == target) ++total; + return total; +} + +bool worldsEqual(const World& a, const World& b) { + if (a.chunkCount() != b.chunkCount()) return false; + for (const auto& entry : a.allChunks()) { + const Chunk* other = b.chunkAt(entry.first); + if (!other) return false; + if (entry.second->raw() != other->raw()) return false; + } + return true; +} + +} // namespace + +VF_TEST(structureBuildIsDeterministicPerSeed) { + std::vector oakA = buildStructure(StructureKind::OakTree, 12345u); + std::vector oakB = buildStructure(StructureKind::OakTree, 12345u); + CHECK(sameEdits(oakA, oakB)); // same seed, same tree + + std::vector oakC = buildStructure(StructureKind::OakTree, 999u); + CHECK(!sameEdits(oakA, oakC)); // different seed, different tree + CHECK(!oakA.empty()); +} + +VF_TEST(structureHutHasLanternFloorAndDoor) { + std::vector hut = buildStructure(StructureKind::Hut, 7u); + bool lantern = false, floorCentre = false, doorOpen = true; + for (const StructureEdit& e : hut) { + if (e.block == Block::Glowstone) lantern = true; + if (e.offset == Vec3i(0, 0, 0) && e.block == Block::Wood) floorCentre = true; + if (e.offset == Vec3i(0, 1, -2)) doorOpen = false; // doorway cell must stay empty + } + CHECK(lantern); + CHECK(floorCentre); + CHECK(doorOpen); +} + +VF_TEST(structureStampWritesBlocks) { + World world; + std::vector oak = buildStructure(StructureKind::OakTree, 42u); + stampStructure(world, {100, 40, 100}, oak, 128); + CHECK(world.blockAt(100, 40, 100) == Block::Wood); // trunk base + CHECK(countBlock(world, Block::Leaves) > 0); // canopy present +} + +VF_TEST(structureLeavesDoNotOverwriteSolids) { + World world; + world.setBlock(100, 41, 100, Block::Stone); // pre-existing solid where a leaf would go + std::vector oak = buildStructure(StructureKind::OakTree, 42u); + stampStructure(world, {100, 40, 100}, oak, 128); + CHECK(world.blockAt(100, 41, 100) == Block::Wood || world.blockAt(100, 41, 100) == Block::Stone); + // trunk (wood) may overwrite, but no leaf replaced the stone +} + +VF_TEST(worldGenWithStructuresIsDeterministic) { + WorldGenParams params; + params.structures = true; + WorldGen genA(params), genB(params); + World a, b; + for (int cx = 0; cx < 5; ++cx) + for (int cz = 0; cz < 5; ++cz) { + genA.generateColumn(a, cx, cz); + genB.generateColumn(b, cx, cz); + } + CHECK(worldsEqual(a, b)); // same seed -> identical structures and terrain +} + +VF_TEST(worldGenDifferentSeedDiffersWithStructures) { + WorldGenParams pa; + pa.seed = 1337u; + WorldGenParams pb; + pb.seed = 2024u; + WorldGen genA(pa), genB(pb); + World a, b; + for (int cx = 0; cx < 5; ++cx) + for (int cz = 0; cz < 5; ++cz) { + genA.generateColumn(a, cx, cz); + genB.generateColumn(b, cx, cz); + } + CHECK(!worldsEqual(a, b)); +} + +VF_TEST(worldGenStampsHutsWhenEnabled) { + WorldGenParams on; + on.structures = true; + WorldGen gen(on); + World world; + for (int cx = 0; cx < 16; ++cx) + for (int cz = 0; cz < 16; ++cz) + gen.generateColumn(world, cx, cz); + CHECK(countBlock(world, Block::Glowstone) > 0); // lantern proves a hut was stamped + + WorldGenParams off; + off.structures = false; + WorldGen bare(off); + World plain; + for (int cx = 0; cx < 16; ++cx) + for (int cz = 0; cz < 16; ++cz) + bare.generateColumn(plain, cx, cz); + CHECK_EQ(countBlock(plain, Block::Glowstone), static_cast(0)); +}