namespace SVSim.Database.Models.Config; /// /// Tunables for pack-opening RNG. Property initialisers reproduce the original Shadowverse /// Classic main-slot rates exactly. Collection-shaped defaults (slot-8 PerSlot entry) live in /// , 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 { /// /// Per-card-slot probability of upgrading any drawn card to its foil/animated twin. /// Applied AFTER rarity selection — independent of rarity, slot position, and pack category. /// Default 0.08 (8%). Cards without a foil twin in master data keep the non-foil silently. /// public double AnimatedRate { get; set; } = 0.08; /// /// Global default rarity weights, used for any slot that has no entry in /// . Defaults match SV Classic main-slot. Weights sum to 0.9994; /// the 0.06% slack absorbs into Bronze via the PickRarity catch-all band. /// public SlotRarityWeights Default { get; set; } = new() { Bronze = 0.6744, Silver = 0.25, Gold = 0.06, Legendary = 0.015, }; /// /// Per-slot overrides keyed by 1-based slot index (stored as a list for json compatibility — /// Dictionary<string,T> of complex owned types is not supported). Look up by /// . A missing slot falls back to . /// Each entry is a FULL OVERRIDE, not a delta — if you change , existing /// PerSlot entries do NOT auto-recompute. /// /// MUST default to empty. The original EF Core 8 OwnsMany+ToJson path APPENDED /// jsonb rows onto whatever collection the parent's parameterless ctor produced — a non-empty /// initialiser here meant every config load doubled-up and the original seed silently won the /// FirstOrDefault lookup in PackOpenService.ResolveWeights. The EF path is gone /// now (config goes through IGameConfigService + STJ), but the rule stays: collection /// defaults live in , not in property initialisers. /// /// public List PerSlot { get; set; } = []; /// /// Canonical SV Classic shipped defaults — what an operator gets if neither the DB nor /// appsettings.json supplies a PackRates section. Source of truth for the fresh-install seeder /// and the IGameConfigService inline-default tier. /// public static PackRateConfig ShippedDefaults() => new() { PerSlot = { new SlotRarityWeights { Slot = "8", Bronze = 0, Silver = 0.7692, Gold = 0.1846, Legendary = 0.0462 }, }, }; }