using System.Linq; using AcDream.Core.Audio; using Xunit; namespace AcDream.Core.Tests.Audio; /// /// Conformance tests for retail's AdminEnvirons code → interface sound /// table, read case-by-case out of the switch in /// CPlayerSystem::Handle_Admin__Environs @ 0x0055DE20 /// (0x0055E0C6..0x0055E2C7). /// public sealed class EnvironSoundCueMapTests { [Theory] // Every case in retail's switch, in address order. [InlineData(0x65u, SoundId.UI_Roar)] [InlineData(0x66u, SoundId.UI_Bell)] [InlineData(0x67u, SoundId.UI_Chant1)] [InlineData(0x68u, SoundId.UI_Chant2)] [InlineData(0x69u, SoundId.UI_DarkWhispers1)] [InlineData(0x6Au, SoundId.UI_DarkWhispers2)] [InlineData(0x6Bu, SoundId.UI_DarkLaugh)] [InlineData(0x6Cu, SoundId.UI_DarkWind)] [InlineData(0x6Du, SoundId.UI_DarkSpeech)] [InlineData(0x6Eu, SoundId.UI_Drums)] [InlineData(0x6Fu, SoundId.UI_GhostSpeak)] [InlineData(0x70u, SoundId.UI_Breathing)] [InlineData(0x71u, SoundId.UI_Howl)] [InlineData(0x72u, SoundId.UI_LostSouls)] [InlineData(0x75u, SoundId.UI_Squeal)] [InlineData(0x76u, SoundId.UI_Thunder1)] [InlineData(0x77u, SoundId.UI_Thunder2)] [InlineData(0x78u, SoundId.UI_Thunder3)] [InlineData(0x79u, SoundId.UI_Thunder4)] [InlineData(0x7Au, SoundId.UI_Thunder5)] [InlineData(0x7Bu, SoundId.UI_Thunder6)] public void EveryRetailCase_MapsToItsSound(uint code, SoundId expected) { Assert.True(EnvironSoundCueMap.TryGetSound(code, out SoundId sound)); Assert.Equal(expected, sound); } [Theory] // Retail's switch has NO case for these two, mid-run: they fall through and // play nothing. This is exactly why the port is a table and not arithmetic. [InlineData(0x73u)] [InlineData(0x74u)] public void GapCodes_HaveNoCase(uint code) { Assert.False(EnvironSoundCueMap.TryGetSound(code, out _)); } [Theory] // Below the run are the fog/lighting overrides (1..6, handled elsewhere); // 0x7C is one past the last case — the switch ends at 0x7B. [InlineData(0u)] [InlineData(1u)] [InlineData(6u)] [InlineData(0x64u)] [InlineData(0x7Cu)] [InlineData(0xFFu)] public void CodesOutsideTheRun_HaveNoCase(uint code) { Assert.False(EnvironSoundCueMap.TryGetSound(code, out _)); } [Fact] public void TheTableIsNotAConstantOffset() { // 0x65..0x72 sit 0x11 below their SoundType, but 0x75 -> UI_Squeal (0x84) // is 0x0F, because the two-code gap does not shift the sound run. An // offset port would mis-map everything from 0x75 up. Assert.True(EnvironSoundCueMap.TryGetSound(0x72u, out SoundId lostSouls)); Assert.True(EnvironSoundCueMap.TryGetSound(0x75u, out SoundId squeal)); Assert.Equal(0x11u, (uint)lostSouls - 0x72u); Assert.Equal(0x0Fu, (uint)squeal - 0x75u); } [Fact] public void CoversExactlyTheTwentyOneInterfaceStingers() { // The UI_* stinger run is UI_Roar (0x76) through UI_Thunder6 (0x8A) — // 21 sounds — and retail's switch has one case for each. Assert.Equal(21, EnvironSoundCueMap.Codes.Count); var sounds = EnvironSoundCueMap.Codes .Select(code => { EnvironSoundCueMap.TryGetSound(code, out SoundId sound); return (uint)sound; }) .OrderBy(value => value) .ToList(); Assert.Equal(Enumerable.Range(0x76, 21).Select(v => (uint)v), sounds); } }