diff --git a/tests/testTilePalette.cpp b/tests/testTilePalette.cpp new file mode 100644 index 0000000..4988085 --- /dev/null +++ b/tests/testTilePalette.cpp @@ -0,0 +1,146 @@ +#include +#include +#include + +#include "testHarness.h" +#include "vf/Block.h" +#include "vf/TilePalette.h" + +using namespace vf; + +namespace { + +const Face allFaces[6] = {Face::PosX, Face::NegX, Face::PosY, + Face::NegY, Face::PosZ, Face::NegZ}; + +bool sameLook(const TileSpec& a, const TileSpec& b) { + return a.base == b.base && a.accent == b.accent && a.pattern == b.pattern; +} + +// A palette short of a row is exactly what these tests are here to catch, so +// they must report it rather than walk off the end of the table proving it. +int rowsToWalk(const TilePalette& palette) { + return palette.size() < tileCount ? palette.size() : tileCount; +} + +} // namespace + +// The whole point of the palette: 157 tiles, 157 entries. The old generator had +// a switch covering fifteen of them and one grey default for the other one +// hundred and forty two, and nothing could tell you that from the outside. +VF_TEST(everyAtlasTileHasItsOwnPaletteEntry) { + TilePalette palette; + CHECK_EQ(palette.size(), tileCount); + + for (int tile = 0; tile < rowsToWalk(palette); ++tile) { + const TileSpec& spec = palette.specFor(tile); + bool named = spec.name != nullptr && std::strlen(spec.name) > 0; + CHECK(named); + if (!named) std::printf(" unnamed tile: %d\n", tile); + CHECK(spec.noise >= 0.0f); + CHECK(spec.noise <= 0.30f); + CHECK(spec.scale >= 0.0f); + CHECK(spec.scale <= 1.0f); + CHECK(spec.density >= 0.0f); + CHECK(spec.density <= 1.0f); + CHECK(spec.pattern != TilePattern::Count); + CHECK(spec.material != TileMaterial::Count); + } +} + +// Two tiles that agree on base colour, accent colour and pattern render the +// same picture, which means one of them is a copy nobody meant to ship. +VF_TEST(noTwoTilesShareTheSameBaseAccentAndPattern) { + TilePalette palette; + int duplicates = 0; + for (int a = 0; a < palette.size(); ++a) { + for (int b = a + 1; b < palette.size(); ++b) { + if (!sameLook(palette.specFor(a), palette.specFor(b))) continue; + ++duplicates; + if (duplicates <= 8) { + std::printf(" duplicate tiles: %d (%s) and %d (%s)\n", a, palette.specFor(a).name, + b, palette.specFor(b).name); + } + } + } + CHECK_EQ(duplicates, 0); +} + +// The palette is indexed by whatever faceTile hands out, so the two have to +// agree over the entire block list, not over the blocks that existed when the +// palette was written. +VF_TEST(everyBlockFaceLandsOnATileThatHasASpec) { + TilePalette palette; + for (int id = 1; id < static_cast(Block::Count); ++id) { + Block block = static_cast(id); + for (Face face : allFaces) { + int tile = faceTile(block, face); + CHECK(tile >= 0); + CHECK(tile < palette.size()); + if (tile < 0 || tile >= palette.size()) { + std::printf(" block %d has no spec for tile %d\n", id, tile); + continue; + } + CHECK(std::strlen(palette.specFor(tile).name) > 0); + } + } +} + +// Regression on the look of the world that already exists. These fifteen +// numbers are copied out of the generator that render/Atlas.cpp used to carry; +// if the palette drifts from them, old terrain changes colour. +VF_TEST(theFifteenOriginalTilesKeepTheirExactColours) { + TilePalette palette; + const TileRgb historical[15] = { + {86, 154, 66}, // 0 grass top + {112, 92, 58}, // 1 grass side, dirt under the green lip + {119, 88, 58}, // 2 dirt + {129, 129, 132}, // 3 stone + {214, 199, 143}, // 4 sand + {104, 78, 46}, // 5 wood bark + {160, 128, 78}, // 6 wood rings + {58, 122, 52}, // 7 leaves + {54, 96, 168}, // 8 water + {236, 240, 245}, // 9 snow + {129, 129, 132}, // 10 coal ore, stone base + {129, 129, 132}, // 11 iron ore, stone base + {129, 129, 132}, // 12 gold ore, stone base + {122, 120, 120}, // 13 gravel + {242, 220, 132}, // 14 glowstone + }; + CHECK(palette.size() >= 15); + for (int tile = 0; tile < rowsToWalk(palette) && tile < 15; ++tile) { + bool kept = palette.specFor(tile).base == historical[tile]; + CHECK(kept); + if (!kept) { + const TileRgb& now = palette.specFor(tile).base; + std::printf(" tile %d drifted to %u,%u,%u\n", tile, now.r, now.g, now.b); + } + } + + // The accents the old code drew on top: the green lip on the grass side and + // the three ore vein colours. + TileRgb grassGreen{86, 154, 66}; + TileRgb coal{38, 38, 42}; + TileRgb iron{196, 156, 120}; + TileRgb gold{238, 214, 96}; + CHECK(palette.specFor(1).accent == grassGreen); + CHECK(palette.specFor(10).accent == coal); + CHECK(palette.specFor(11).accent == iron); + CHECK(palette.specFor(12).accent == gold); +} + +// The grey the old generator returned for every unhandled tile. Nothing may +// still be sitting on it, and the TileSpec default is that same grey so an +// entry that was declared and never filled in is caught here too. +VF_TEST(noTileIsLeftOnTheOldDefaultGrey) { + TilePalette palette; + TileSpec untouched; + for (int tile = 0; tile < palette.size(); ++tile) { + const TileSpec& spec = palette.specFor(tile); + bool blank = spec.base == untouched.base && spec.accent == untouched.accent; + CHECK(!blank); + if (blank) std::printf(" tile %d is still default grey\n", tile); + } +} +