diff --git a/tests/testHeaderCoexistence.cpp b/tests/testHeaderCoexistence.cpp index ac98c00..47bd998 100644 --- a/tests/testHeaderCoexistence.cpp +++ b/tests/testHeaderCoexistence.cpp @@ -152,3 +152,118 @@ VF_TEST(everyPublicHeaderIsListedInTheCoexistenceGuard) { } CHECK(onDisk.size() > 60u); + std::set included; + std::ifstream source(root / "tests" / "testHeaderCoexistence.cpp"); + CHECK(source.good()); + const std::string prefix = "#include \"vf/"; + std::string line; + while (std::getline(source, line)) { + if (line.rfind(prefix, 0) != 0) continue; + std::size_t end = line.find('"', prefix.size()); + if (end == std::string::npos) continue; + included.insert(line.substr(prefix.size(), end - prefix.size())); + } + + for (const std::string& header : onDisk) { + // A header on disk but missing from the include list above. + CHECK(included.count(header) == 1u); + if (included.count(header) != 1u) { + std::printf(" unguarded header: core/include/vf/%s\n", header.c_str()); + } + } + for (const std::string& header : included) { + // An include line left behind after a header was deleted or renamed. + CHECK(onDisk.count(header) == 1u); + } + CHECK_EQ(included.size(), onDisk.size()); +} + +// The two names that actually collided, now proven to coexist: an enum in +// Survival.h and a struct in Damage.h, both reachable from one scope. +VF_TEST(survivalAndDamageHeadersDeclareDistinctSourceTypes) { + DamageSource legacy = DamageSource::Fall; + CHECK(legacy == DamageSource::Fall); + CHECK(legacy != DamageSource::Lava); + + HurtSource blow; + blow.type = DamageType::Fall; + blow.byPlayer = true; + blow.attackerId = 7; + CHECK(blow.type == DamageType::Fall); + CHECK(blow.byPlayer); + CHECK_EQ(blow.attackerId, 7); + CHECK_EQ(blow.ownerId, -1); + + // Distinct types: the sizes below are unrelated, and the point is only + // that both names resolve in the same translation unit. + CHECK(sizeof(DamageSource) >= 1u); + CHECK(sizeof(HurtSource) > sizeof(DamageSource)); +} + +// Every block in the palette must map to a tile inside the atlas, on every +// face. A block added without a faceTile case silently falls through to the +// stone tile, which is the kind of thing nobody notices until the atlas is +// resized. Both facts are checked here because faceTile and tileCount live in +// the one header every module shares. +VF_TEST(everyPaletteBlockMapsToATileInsideTheAtlas) { + const Face faces[6] = {Face::PosX, Face::NegX, Face::PosY, + Face::NegY, Face::PosZ, Face::NegZ}; + int highestTile = -1; + for (int i = 1; i < static_cast(Block::Count); ++i) { + Block block = static_cast(i); + for (Face face : faces) { + int tile = faceTile(block, face); + CHECK(tile >= 0); + CHECK(tile < tileCount); + if (tile > highestTile) highestTile = tile; + } + } + // The atlas must not be padded with layers nothing references. + CHECK_EQ(highestTile, tileCount - 1); +} + +// Opacity and solidity are independent flags, but light emission is only +// meaningful within the 0..15 range the light engine packs into four bits. +VF_TEST(blockLightEmissionStaysInsideTheFourBitLightRange) { + for (int i = 0; i < static_cast(Block::Count); ++i) { + Block block = static_cast(i); + int emission = blockLightEmission(block); + CHECK(emission >= 0); + CHECK(emission <= 15); + if (isAir(block)) CHECK_EQ(emission, 0); + } + CHECK_EQ(blockLightEmission(Block::Glowstone), 15); + CHECK_EQ(blockLightEmission(Block::EndPortal), 15); + CHECK_EQ(blockLightEmission(Block::NetherPortal), 11); + CHECK_EQ(blockLightEmission(Block::Stone), 0); +} + +// A transparent block must never be opaque, or the mesher would both skip the +// neighbouring face and draw the block in the transparent pass. +VF_TEST(transparentBlocksAreNeverAlsoOpaque) { + for (int i = 0; i < static_cast(Block::Count); ++i) { + Block block = static_cast(i); + if (isTransparent(block)) CHECK(!isOpaque(block)); + if (isOpaque(block)) CHECK(occludesLight(block)); + } + CHECK(isTransparent(Block::Glass)); + CHECK(!isOpaque(Block::Glass)); + CHECK(isOpaque(Block::Stone)); + CHECK(occludesLight(Block::Stone)); +} + +// faceVisible is the single rule the mesher trusts. It has to stay coherent +// with isAir and isOpaque for the whole palette, not just for the handful of +// blocks that existed when it was written. +VF_TEST(faceVisibilityAgreesWithAirAndOpacityAcrossThePalette) { + for (int i = 0; i < static_cast(Block::Count); ++i) { + Block self = static_cast(i); + CHECK(!faceVisible(self, Block::Stone)); + if (isAir(self)) { + CHECK(!faceVisible(self, Block::Air)); + continue; + } + CHECK(faceVisible(self, Block::Air)); + CHECK(!faceVisible(self, self)); + } +}