using Theriapolis.Core; using Theriapolis.Core.Data; using Theriapolis.Core.Loot; using Xunit; namespace Theriapolis.Tests.Dungeons; /// /// Phase 7 M2 — determinism tests for the dungeon loot generator. Same /// (table, containerSeed) → byte-identical item drops, regardless of /// process / clock / PRNG warm-up. /// public sealed class LootGeneratorTests { private readonly ContentResolver _content = new(new ContentLoader(TestHelpers.DataDirectory)); [Fact] public void RollContainer_SameSeed_ProducesIdenticalDrops() { const ulong seed = 0xABCDEF; var a = LootGenerator.RollContainer("loot_dungeon_imperium_t2", seed, _content.LootTables, _content.Items); var b = LootGenerator.RollContainer("loot_dungeon_imperium_t2", seed, _content.LootTables, _content.Items); Assert.Equal(a.Length, b.Length); for (int i = 0; i < a.Length; i++) { Assert.Equal(a[i].Def.Id, b[i].Def.Id); Assert.Equal(a[i].Qty, b[i].Qty); } } [Fact] public void RollContainer_DifferentSeeds_DivergeAcrossManyRolls() { // Across 100 (seed, slotIdx) pairs, the *aggregate* drop count // should differ between two different base seeds. (A single pair // could collide; the population can't, with overwhelming probability.) int aTotal = 0, bTotal = 0; for (int i = 0; i < 100; i++) { ulong seedA = 0x10000UL ^ (ulong)i; ulong seedB = 0x20000UL ^ (ulong)i; aTotal += LootGenerator.RollContainer("loot_dungeon_imperium_t2", seedA, _content.LootTables, _content.Items).Length; bTotal += LootGenerator.RollContainer("loot_dungeon_imperium_t2", seedB, _content.LootTables, _content.Items).Length; } Assert.NotEqual(aTotal, bTotal); } [Fact] public void RollContainer_HonoursDungeonLayoutSeedConvention() { ulong dungeonLayoutSeed = 0xD06E07AUL ^ 7UL; // simulated — same shape as DungeonGenerator var a = LootGenerator.RollContainer( "loot_dungeon_imperium_t1", dungeonLayoutSeed, slotIdx: 0, _content.LootTables, _content.Items); var b = LootGenerator.RollContainer( "loot_dungeon_imperium_t1", dungeonLayoutSeed ^ C.RNG_DUNGEON_LOOT ^ 0UL, _content.LootTables, _content.Items); // Both forms should produce identical results — the convenience // overload XORs the same RNG_DUNGEON_LOOT + slotIdx the explicit // overload's caller would. Assert.Equal(a.Length, b.Length); for (int i = 0; i < a.Length; i++) { Assert.Equal(a[i].Def.Id, b[i].Def.Id); Assert.Equal(a[i].Qty, b[i].Qty); } } [Fact] public void RollContainer_UnknownTable_ReturnsEmpty() { var drops = LootGenerator.RollContainer("nonexistent_table", 1, _content.LootTables, _content.Items); Assert.Empty(drops); } }