diff --git a/tests/testTilePalette.cpp b/tests/testTilePalette.cpp index 4988085..b2f4dea 100644 --- a/tests/testTilePalette.cpp +++ b/tests/testTilePalette.cpp @@ -144,3 +144,130 @@ VF_TEST(noTileIsLeftOnTheOldDefaultGrey) { } } +// The table is filled by pushing rows in order, so a row inserted or dropped in +// the middle shifts everything after it. Names pinned to the indices Block.h +// documents catch that shift immediately. +VF_TEST(paletteEntriesSitAtTheTileIndexTheyName) { + TilePalette palette; + struct Anchor { + int tile; + const char* name; + }; + const Anchor anchors[] = { + {0, "grassTop"}, {3, "stone"}, {14, "glowstone"}, + {15, "cobblestone"}, {26, "lapisOre"}, {27, "craftingTableTop"}, + {49, "tntFuseBand"}, {50, "ladder"}, {61, "noteBlock"}, + {62, "farmlandTop"}, {79, "packedIce"}, {80, "brewingStand"}, + {88, "cobweb"}, {89, "netherrack"}, {98, "blackstone"}, + {99, "endStone"}, {105, "chorusFlower"},{106, "glass"}, + {134, "hayBaleSide"}, {135, "clay"}, {156, "moss"}, + }; + for (const Anchor& anchor : anchors) { + CHECK(anchor.tile < palette.size()); + if (anchor.tile >= palette.size()) { + std::printf(" tile %d is missing entirely\n", anchor.tile); + continue; + } + bool matched = std::strcmp(palette.specFor(anchor.tile).name, anchor.name) == 0; + CHECK(matched); + if (!matched) { + std::printf(" tile %d is %s, expected %s\n", anchor.tile, + palette.specFor(anchor.tile).name, anchor.name); + } + } +} + +// Cutout is what earns a tile a shape in the alpha channel. Glass, both +// portals, the web, the plants, the ladder and the grates need one; a solid +// rock must never get one or the mesher would show daylight through a wall. +VF_TEST(cutoutIsSetExactlyForTheTilesThatCarryAShape) { + TilePalette palette; + const int shaped[] = {33, 34, 35, 38, 50, 51, 52, 53, 54, 55, 63, 64, 65, 70, 71, + 72, 76, 77, 80, 88, 94, 100, 104, 105, 106, 113, 117, 153}; + for (int tile : shaped) { + CHECK(tile < palette.size()); + if (tile >= palette.size()) continue; + bool cut = palette.specFor(tile).cutout; + CHECK(cut); + if (!cut) std::printf(" tile %d (%s) lost its cutout\n", tile, palette.specFor(tile).name); + } + + const int solid[] = {0, 2, 3, 4, 7, 8, 13, 15, 16, 20, 89, 107, 108, 137, 156}; + for (int tile : solid) { + CHECK(tile < palette.size()); + if (tile >= palette.size()) continue; + bool cut = palette.specFor(tile).cutout; + CHECK(!cut); + if (cut) std::printf(" tile %d (%s) became a cutout\n", tile, palette.specFor(tile).name); + } +} + +// A taxonomy nothing uses is decoration. Every material and every pattern the +// enums declare has to be carried by at least one real tile. +VF_TEST(everyMaterialAndEveryPatternIsCarriedByATile) { + TilePalette palette; + int materialUses[static_cast(TileMaterial::Count)] = {}; + int patternUses[static_cast(TilePattern::Count)] = {}; + for (const TileSpec& spec : palette.specs()) { + ++materialUses[static_cast(spec.material)]; + ++patternUses[static_cast(spec.pattern)]; + } + for (int i = 0; i < static_cast(TileMaterial::Count); ++i) { + CHECK(materialUses[i] > 0); + if (materialUses[i] == 0) std::printf(" material %d is unused\n", i); + } + for (int i = 0; i < static_cast(TilePattern::Count); ++i) { + CHECK(patternUses[i] > 0); + if (patternUses[i] == 0) std::printf(" pattern %d is unused\n", i); + } +} + +// Ore in stone is stone with a vein in it. The host colour has to stay the +// stone colour, and the veins have to be told apart from each other, because +// the base colour alone cannot distinguish coal from gold. +VF_TEST(stoneBorneOresKeepTheHostRockAndDifferByVein) { + TilePalette palette; + TileRgb stone = palette.specFor(3).base; + const int stoneOres[] = {10, 11, 12, 23, 24, 25, 26, 145}; + std::vector veins; + for (int tile : stoneOres) { + CHECK(tile < palette.size()); + if (tile >= palette.size()) continue; + const TileSpec& spec = palette.specFor(tile); + CHECK(spec.pattern == TilePattern::OreSpeckle); + CHECK(spec.base == stone); + CHECK(spec.accent != stone); + for (const TileRgb& seen : veins) CHECK(seen != spec.accent); + veins.push_back(spec.accent); + } + CHECK_EQ(veins.size(), sizeof(stoneOres) / sizeof(stoneOres[0])); + + CHECK(palette.size() > 92); + if (palette.size() <= 92) return; + // Nether quartz sits in netherrack, not stone, so it must not share the + // overworld host colour. + CHECK(palette.specFor(92).pattern == TilePattern::OreSpeckle); + CHECK(palette.specFor(92).base != stone); +} + +// Two palettes built independently must agree row for row: the table is data, +// not something assembled from whatever ran before it. +VF_TEST(twoPalettesBuiltSeparatelyAgreeRowForRow) { + TilePalette first; + TilePalette second; + CHECK_EQ(first.size(), second.size()); + for (int tile = 0; tile < first.size(); ++tile) { + const TileSpec& a = first.specFor(tile); + const TileSpec& b = second.specFor(tile); + CHECK(a.base == b.base); + CHECK(a.accent == b.accent); + CHECK(a.pattern == b.pattern); + CHECK(a.material == b.material); + CHECK(a.cutout == b.cutout); + CHECK(a.vertical == b.vertical); + CHECK(a.flipped == b.flipped); + CHECK_NEAR(a.noise, b.noise, 0.0f); + CHECK_NEAR(a.scale, b.scale, 0.0f); + CHECK_NEAR(a.density, b.density, 0.0f); + } +}