test: cover seedFor and contextWith

This commit is contained in:
dualfroz committed 2026-09-11 21:52:54 +02:00
1 parent 9696853d5a
commit ea3d54436c
1 file changed
+158
+158
View File
@@ -0,0 +1,158 @@
#include <algorithm>
#include "testHarness.h"
#include "vf/Loot.h"
using namespace vf;
namespace {
// Consecutive small seeds share too much structure for a raw xorshift stream,
// so the sampling tests walk a scrambled sequence instead.
uint32_t seedFor(int index) {
uint32_t mixed = static_cast<uint32_t>(index) + 1u;
mixed ^= mixed >> 16;
mixed *= 0x7feb352du;
mixed ^= mixed >> 15;
mixed *= 0x846ca68bu;
mixed ^= mixed >> 16;
return mixed == 0u ? 1u : mixed;
}
LootContext contextWith(ItemId tool, uint32_t seed) {
LootContext context;
context.tool = tool;
context.seed = seed;
return context;
}
LootContext enchanted(ItemId tool, EnchantKind kind, int level, uint32_t seed) {
LootContext context = contextWith(tool, seed);
addEnchant(context.toolEnchants, kind, level);
return context;
}
LootContext killContext(bool byPlayer, uint32_t seed) {
LootContext context = contextWith(ItemId::IronSword, seed);
context.playerKill = byPlayer;
return context;
}
bool sameRoll(const LootRoll& left, const LootRoll& right) {
if (left.items.size() != right.items.size()) return false;
for (std::size_t i = 0; i < left.items.size(); ++i) {
if (left.items[i].item != right.items[i].item) return false;
if (left.items[i].count != right.items[i].count) return false;
if (left.items[i].durability != right.items[i].durability) return false;
}
return true;
}
LootEntry entryOf(ItemId item, uint32_t conditions) {
LootEntry entry;
entry.item = item;
entry.conditions = conditions;
return entry;
}
LootTable singlePool(const std::vector<LootEntry>& entries, int minRolls = 1, int maxRolls = 1) {
LootPool pool;
pool.entries = entries;
pool.minRolls = minRolls;
pool.maxRolls = maxRolls;
LootTable table;
table.pools.push_back(pool);
return table;
}
int maxOverSeeds(Block block, const EnchantSet& enchants, ItemId tool, ItemId wanted, int samples) {
int best = 0;
for (int i = 0; i < samples; ++i) {
LootContext context = contextWith(tool, seedFor(i));
context.toolEnchants = enchants;
best = std::max(best, rollBlockDrops(block, context).countOf(wanted));
}
return best;
}
} // namespace
VF_TEST(theSameSeedAlwaysReproducesTheSameRollAcrossEveryTableFamily) {
LootContext blockContext = enchanted(ItemId::DiamondPickaxe, EnchantKind::Fortune, 3, 0xabcdef01u);
CHECK(sameRoll(rollBlockDrops(Block::LapisOre, blockContext),
rollBlockDrops(Block::LapisOre, blockContext)));
LootContext mobContext = killContext(true, 0x1234567u);
addEnchant(mobContext.toolEnchants, EnchantKind::Looting, 2);
CHECK(sameRoll(rollMobDrops(LootTable::MobKind::Zombie, mobContext),
rollMobDrops(LootTable::MobKind::Zombie, mobContext)));
LootContext chestContext = contextWith(ItemId::None, 0x55aa33ccu);
LootRoll first = rollChestContents(LootTable::ChestKind::Dungeon, chestContext);
LootRoll second = rollChestContents(LootTable::ChestKind::Dungeon, chestContext);
CHECK(sameRoll(first, second));
CHECK(first.totalCount() > 0);
}
VF_TEST(differentSeedsPullApartTheContentsOfTheSameChest) {
LootRoll reference = rollChestContents(LootTable::ChestKind::Dungeon,
contextWith(ItemId::None, seedFor(0)));
int different = 0;
for (int i = 1; i < 64; ++i) {
LootRoll roll = rollChestContents(LootTable::ChestKind::Dungeon,
contextWith(ItemId::None, seedFor(i)));
if (!sameRoll(reference, roll)) ++different;
}
CHECK(different >= 55);
}
VF_TEST(stoneDropsCobblestoneUnlessTheSilkTouchBranchTakesOver) {
LootContext plain = contextWith(ItemId::StonePickaxe, 7u);
LootRoll broken = rollBlockDrops(Block::Stone, plain);
CHECK_EQ(broken.countOf(ItemId::Cobblestone), 1);
CHECK_EQ(broken.countOf(ItemId::Stone), 0);
LootContext silk = enchanted(ItemId::StonePickaxe, EnchantKind::SilkTouch, 1, 7u);
LootRoll harvested = rollBlockDrops(Block::Stone, silk);
CHECK_EQ(harvested.countOf(ItemId::Stone), 1);
CHECK_EQ(harvested.countOf(ItemId::Cobblestone), 0);
}
VF_TEST(oresDropTheirRawFormAndSilkTouchDropsTheOreBlockItself) {
const struct { Block block; ItemId raw; ItemId oreItem; } ores[] = {
{Block::CoalOre, ItemId::CoalLump, ItemId::CoalOre},
{Block::IronOre, ItemId::IronOre, ItemId::IronOre},
{Block::DiamondOre, ItemId::DiamondGem, ItemId::DiamondOre},
{Block::EmeraldOre, ItemId::EmeraldGem, ItemId::EmeraldOre},
};
for (const auto& ore : ores) {
LootContext plain = contextWith(ItemId::DiamondPickaxe, 31u);
CHECK_EQ(rollBlockDrops(ore.block, plain).countOf(ore.raw), 1);
LootContext silk = enchanted(ItemId::DiamondPickaxe, EnchantKind::SilkTouch, 1, 31u);
LootRoll harvested = rollBlockDrops(ore.block, silk);
CHECK_EQ(harvested.countOf(ore.oreItem), 1);
CHECK_EQ(harvested.totalCount(), 1);
}
}
VF_TEST(fortuneMultipliesOreDropsAndNeverDropsBelowTheBaseYield) {
EnchantSet bare;
EnchantSet fortune;
addEnchant(fortune, EnchantKind::Fortune, 3);
int worstPlain = 99;
int worstFortune = 99;
for (int i = 0; i < 400; ++i) {
LootContext plain = contextWith(ItemId::DiamondPickaxe, seedFor(i));
worstPlain = std::min(worstPlain, rollBlockDrops(Block::DiamondOre, plain)
.countOf(ItemId::DiamondGem));
LootContext lucky = contextWith(ItemId::DiamondPickaxe, seedFor(i));
lucky.toolEnchants = fortune;
worstFortune = std::min(worstFortune, rollBlockDrops(Block::DiamondOre, lucky)
.countOf(ItemId::DiamondGem));
}
CHECK_EQ(worstPlain, 1);
CHECK_EQ(worstFortune, 1);