using System; using System.Collections.Generic; using AcDream.Core.Audio; using DatReaderWriter.DBObjs; using DRWSoundEntry = DatReaderWriter.Types.SoundEntry; using DatReaderWriter.Types; using DRWSound = DatReaderWriter.Enums.Sound; using Xunit; namespace AcDream.Core.Tests.Audio; public sealed class SoundCookbookTests { // Deterministic Random for golden-value tests. private static Random Seed(int seed) => new Random(seed); [Fact] public void Roll_EmptyList_ReturnsNull() { Assert.Null(SoundCookbook.Roll(new List(), Seed(1))); } [Fact] public void Roll_SingleEntry_AlwaysReturnsIt() { var e = new DRWSoundEntry { Probability = 0.5f, Priority = 4f, Volume = 1f }; var entries = new List { e }; Assert.Same(e, SoundCookbook.Roll(entries, Seed(1))); Assert.Same(e, SoundCookbook.Roll(entries, Seed(999))); } [Fact] public void Roll_WeightedEntries_DistributionMatches() { // Three entries: 50%, 30%, 20%. Roll 10000 times and verify counts // are within 5% of expected. var a = new DRWSoundEntry { Probability = 0.5f }; var b = new DRWSoundEntry { Probability = 0.3f }; var c = new DRWSoundEntry { Probability = 0.2f }; var entries = new List { a, b, c }; var rng = new Random(42); int countA = 0, countB = 0, countC = 0, countNull = 0; for (int i = 0; i < 10000; i++) { var picked = SoundCookbook.Roll(entries, rng); if (ReferenceEquals(picked, a)) countA++; else if (ReferenceEquals(picked, b)) countB++; else if (ReferenceEquals(picked, c)) countC++; else countNull++; } Assert.InRange(countA, 4500, 5500); Assert.InRange(countB, 2500, 3500); Assert.InRange(countC, 1500, 2500); // Probabilities sum to 1.0 → no null rolls. Assert.True(countNull < 100); } [Fact] public void Roll_SilenceTail_ReturnsNullOccasionally() { // Two entries that only cover 60% of the probability mass — the // remaining 40% should roll as "silence" (null return). var a = new DRWSoundEntry { Probability = 0.3f }; var b = new DRWSoundEntry { Probability = 0.3f }; var entries = new List { a, b }; var rng = new Random(42); int nullCount = 0; for (int i = 0; i < 10000; i++) { if (SoundCookbook.Roll(entries, rng) is null) nullCount++; } Assert.InRange(nullCount, 3500, 4500); // ~40% ± margin } [Fact] public void Roll_WithSoundTable_LooksUpBySound() { var table = new SoundTable(); var footstep = new DRWSoundEntry { Probability = 1f, Volume = 0.7f }; table.Sounds[DRWSound.Footstep1] = new SoundData(); table.Sounds[DRWSound.Footstep1].Entries.Add(footstep); var picked = SoundCookbook.Roll(table, DRWSound.Footstep1, Seed(1)); Assert.Same(footstep, picked); } [Fact] public void Roll_WithSoundTable_MissingSound_ReturnsNull() { var table = new SoundTable(); // no entries at all Assert.Null(SoundCookbook.Roll(table, DRWSound.Attack1, Seed(1))); } }