diff --git a/SVSim.Database/Models/Config/PackRateConfig.cs b/SVSim.Database/Models/Config/PackRateConfig.cs
index d959c14..a17a116 100644
--- a/SVSim.Database/Models/Config/PackRateConfig.cs
+++ b/SVSim.Database/Models/Config/PackRateConfig.cs
@@ -6,6 +6,7 @@ namespace SVSim.Database.Models.Config;
/// , not in the initialiser — see PerSlot docstring.
///
[ConfigSection("PackRates")]
+[Obsolete("PackRateConfig is no longer consulted by PackOpenService — per-pack rates come from PackDrawTable. Retire once v1 stabilizes.")]
public class PackRateConfig
{
///
diff --git a/SVSim.UnitTests/Services/PackOpenServiceTests.cs b/SVSim.UnitTests/Services/PackOpenServiceTests.cs
index f54d3da..ac05d36 100644
--- a/SVSim.UnitTests/Services/PackOpenServiceTests.cs
+++ b/SVSim.UnitTests/Services/PackOpenServiceTests.cs
@@ -139,6 +139,62 @@ public class PackOpenServiceTests
Assert.That(bonus.CardId, Is.AnyOf(300L, 301L));
}
+ [Test]
+ [Category("Slow")]
+ public void Draw_observed_tier_rates_track_seed_within_half_a_percent()
+ {
+ // Synthetic Classic pack: Bronze=76.5/Silver=16/Gold=6/Legendary=1.5 in general slots;
+ // slot 8 is Silver=92.5/Gold=6/Legendary=1.5 (no Bronze).
+ var table = new PackDrawTable
+ {
+ Config = new PackDrawConfigEntry { Id = 10000, AnimationRatePct = 0 },
+ SlotRates = new[]
+ {
+ new PackDrawSlotRateEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Bronze, RatePct = 76.5 },
+ new PackDrawSlotRateEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Silver, RatePct = 16.0 },
+ new PackDrawSlotRateEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Gold, RatePct = 6.0 },
+ new PackDrawSlotRateEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Legendary, RatePct = 1.5 },
+ new PackDrawSlotRateEntry { PackId = 10000, Slot = DrawSlot.Eighth, Tier = DrawTier.Silver, RatePct = 92.5 },
+ new PackDrawSlotRateEntry { PackId = 10000, Slot = DrawSlot.Eighth, Tier = DrawTier.Gold, RatePct = 6.0 },
+ new PackDrawSlotRateEntry { PackId = 10000, Slot = DrawSlot.Eighth, Tier = DrawTier.Legendary, RatePct = 1.5 },
+ },
+ CardWeights = new[]
+ {
+ new PackDrawCardWeightEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Bronze, CardId = 1, RatePct = 76.5 },
+ new PackDrawCardWeightEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Silver, CardId = 2, RatePct = 16.0 },
+ new PackDrawCardWeightEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Gold, CardId = 3, RatePct = 6.0 },
+ new PackDrawCardWeightEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Legendary, CardId = 4, RatePct = 1.5 },
+ },
+ };
+ var svc = new PackOpenService();
+ var rng = new SystemRandom(42);
+ var pack = new PackConfigEntry { Id = 10000 };
+ int totalSlots = 200_000;
+ int bronze = 0, silver = 0, gold = 0, legendary = 0;
+
+ // 25_000 packs * 7 general slots = 175_000 general-slot observations.
+ for (int i = 0; i < totalSlots / 8; i++)
+ {
+ var r = svc.Draw(table, pack, 1, Array.Empty(), Array.Empty(), new NoFoil(), rng);
+ for (int s = 0; s < 7; s++)
+ {
+ switch (r.Cards[s].Rarity)
+ {
+ case Rarity.Bronze: bronze++; break;
+ case Rarity.Silver: silver++; break;
+ case Rarity.Gold: gold++; break;
+ case Rarity.Legendary: legendary++; break;
+ }
+ }
+ }
+
+ double n = bronze + silver + gold + legendary;
+ Assert.That(100 * bronze / n, Is.EqualTo(76.5).Within(0.5));
+ Assert.That(100 * silver / n, Is.EqualTo(16.0).Within(0.5));
+ Assert.That(100 * gold / n, Is.EqualTo(6.0).Within(0.5));
+ Assert.That(100 * legendary / n, Is.EqualTo(1.5).Within(0.5));
+ }
+
[Test]
public void Draw_does_not_emit_bonus_for_packNumber_less_than_10()
{