// // Golden conformance table for SoundId. See // docs/research/2026-07-29-enum-verification-campaign.md ยง8.2. // using System; using System.Linq; using AcDream.Core.Audio; using Xunit; namespace AcDream.Core.Tests.Audio; /// /// Pins every member of to its retail value. The table is /// transcribed from acclient.h:4569 enum SoundType and independently agrees /// with ACE Sound and DatReaderWriter Sound on every name and value. /// Retail's trailing NUM_SOUND_TYPES and FORCE_SoundType_32_BIT are a /// count and a width pin, not sounds, and are deliberately absent. /// public sealed class SoundIdConformanceTests { private static readonly (string Name, uint Value)[] Golden = [ ("Invalid", 0x00), ("Speak1", 0x01), ("Random", 0x02), ("Attack1", 0x03), ("Attack2", 0x04), ("Attack3", 0x05), ("SpecialAttack1", 0x06), ("SpecialAttack2", 0x07), ("SpecialAttack3", 0x08), ("Damage1", 0x09), ("Damage2", 0x0A), ("Damage3", 0x0B), ("Wound1", 0x0C), ("Wound2", 0x0D), ("Wound3", 0x0E), ("Death1", 0x0F), ("Death2", 0x10), ("Death3", 0x11), ("Grunt1", 0x12), ("Grunt2", 0x13), ("Grunt3", 0x14), ("Oh1", 0x15), ("Oh2", 0x16), ("Oh3", 0x17), ("Heave1", 0x18), ("Heave2", 0x19), ("Heave3", 0x1A), ("Knockdown1", 0x1B), ("Knockdown2", 0x1C), ("Knockdown3", 0x1D), ("Swoosh1", 0x1E), ("Swoosh2", 0x1F), ("Swoosh3", 0x20), ("Thump1", 0x21), ("Smash1", 0x22), ("Scratch1", 0x23), ("Spear", 0x24), ("Sling", 0x25), ("Dagger", 0x26), ("ArrowWhiz1", 0x27), ("ArrowWhiz2", 0x28), ("CrossbowPull", 0x29), ("CrossbowRelease", 0x2A), ("BowPull", 0x2B), ("BowRelease", 0x2C), ("ThrownWeaponRelease1", 0x2D), ("ArrowLand", 0x2E), ("Collision", 0x2F), ("HitFlesh1", 0x30), ("HitLeather1", 0x31), ("HitChain1", 0x32), ("HitPlate1", 0x33), ("HitMissile1", 0x34), ("HitMissile2", 0x35), ("HitMissile3", 0x36), ("Footstep1", 0x37), ("Footstep2", 0x38), ("Walk1", 0x39), ("Dance1", 0x3A), ("Dance2", 0x3B), ("Dance3", 0x3C), ("Hidden1", 0x3D), ("Hidden2", 0x3E), ("Hidden3", 0x3F), ("Eat1", 0x40), ("Drink1", 0x41), ("Open", 0x42), ("Close", 0x43), ("OpenSlam", 0x44), ("CloseSlam", 0x45), ("Ambient1", 0x46), ("Ambient2", 0x47), ("Ambient3", 0x48), ("Ambient4", 0x49), ("Ambient5", 0x4A), ("Ambient6", 0x4B), ("Ambient7", 0x4C), ("Ambient8", 0x4D), ("Waterfall", 0x4E), ("LogOut", 0x4F), ("LogIn", 0x50), ("LifestoneOn", 0x51), ("AttribUp", 0x52), ("AttribDown", 0x53), ("SkillUp", 0x54), ("SkillDown", 0x55), ("HealthUp", 0x56), ("HealthDown", 0x57), ("ShieldUp", 0x58), ("ShieldDown", 0x59), ("EnchantUp", 0x5A), ("EnchantDown", 0x5B), ("VisionUp", 0x5C), ("VisionDown", 0x5D), ("Fizzle", 0x5E), ("Launch", 0x5F), ("Explode", 0x60), ("TransUp", 0x61), ("TransDown", 0x62), ("BreatheFlaem", 0x63), ("BreatheAcid", 0x64), ("BreatheFrost", 0x65), ("BreatheLightning", 0x66), ("Create", 0x67), ("Destroy", 0x68), ("Lockpicking", 0x69), ("UI_EnterPortal", 0x6A), ("UI_ExitPortal", 0x6B), ("UI_GeneralQuery", 0x6C), ("UI_GeneralError", 0x6D), ("UI_TransientMessage", 0x6E), ("UI_IconPickUp", 0x6F), ("UI_IconSuccessfulDrop", 0x70), ("UI_IconInvalid_Drop", 0x71), ("UI_ButtonPress", 0x72), ("UI_GrabSlider", 0x73), ("UI_ReleaseSlider", 0x74), ("UI_NewTargetSelected", 0x75), ("UI_Roar", 0x76), ("UI_Bell", 0x77), ("UI_Chant1", 0x78), ("UI_Chant2", 0x79), ("UI_DarkWhispers1", 0x7A), ("UI_DarkWhispers2", 0x7B), ("UI_DarkLaugh", 0x7C), ("UI_DarkWind", 0x7D), ("UI_DarkSpeech", 0x7E), ("UI_Drums", 0x7F), ("UI_GhostSpeak", 0x80), ("UI_Breathing", 0x81), ("UI_Howl", 0x82), ("UI_LostSouls", 0x83), ("UI_Squeal", 0x84), ("UI_Thunder1", 0x85), ("UI_Thunder2", 0x86), ("UI_Thunder3", 0x87), ("UI_Thunder4", 0x88), ("UI_Thunder5", 0x89), ("UI_Thunder6", 0x8A), ("RaiseTrait", 0x8B), ("WieldObject", 0x8C), ("UnwieldObject", 0x8D), ("ReceiveItem", 0x8E), ("PickUpItem", 0x8F), ("DropItem", 0x90), ("ResistSpell", 0x91), ("PicklockFail", 0x92), ("LockSuccess", 0x93), ("OpenFailDueToLock", 0x94), ("TriggerActivated", 0x95), ("SpellExpire", 0x96), ("ItemManaDepleted", 0x97), ("TriggerActivated1", 0x98), ("TriggerActivated2", 0x99), ("TriggerActivated3", 0x9A), ("TriggerActivated4", 0x9B), ("TriggerActivated5", 0x9C), ("TriggerActivated6", 0x9D), ("TriggerActivated7", 0x9E), ("TriggerActivated8", 0x9F), ("TriggerActivated9", 0xA0), ("TriggerActivated10", 0xA1), ("TriggerActivated11", 0xA2), ("TriggerActivated12", 0xA3), ("TriggerActivated13", 0xA4), ("TriggerActivated14", 0xA5), ("TriggerActivated15", 0xA6), ("TriggerActivated16", 0xA7), ("TriggerActivated17", 0xA8), ("TriggerActivated18", 0xA9), ("TriggerActivated19", 0xAA), ("TriggerActivated20", 0xAB), ("TriggerActivated21", 0xAC), ("TriggerActivated22", 0xAD), ("TriggerActivated23", 0xAE), ("TriggerActivated24", 0xAF), ("TriggerActivated25", 0xB0), ("TriggerActivated26", 0xB1), ("TriggerActivated27", 0xB2), ("TriggerActivated28", 0xB3), ("TriggerActivated29", 0xB4), ("TriggerActivated30", 0xB5), ("TriggerActivated31", 0xB6), ("TriggerActivated32", 0xB7), ("TriggerActivated33", 0xB8), ("TriggerActivated34", 0xB9), ("TriggerActivated35", 0xBA), ("TriggerActivated36", 0xBB), ("TriggerActivated37", 0xBC), ("TriggerActivated38", 0xBD), ("TriggerActivated39", 0xBE), ("TriggerActivated40", 0xBF), ("TriggerActivated41", 0xC0), ("TriggerActivated42", 0xC1), ("TriggerActivated43", 0xC2), ("TriggerActivated44", 0xC3), ("TriggerActivated45", 0xC4), ("TriggerActivated46", 0xC5), ("TriggerActivated47", 0xC6), ("TriggerActivated48", 0xC7), ("TriggerActivated49", 0xC8), ("TriggerActivated50", 0xC9), ("HealthDownVoid", 0xCA), ("RegenDownVoid", 0xCB), ("SkillDownVoid", 0xCC), ]; [Fact] public void EveryRetailSoundHasItsExactValue() { foreach (var (name, value) in Golden) { Assert.True(Enum.IsDefined(typeof(SoundId), name), $"SoundId.{name} is missing"); Assert.Equal(value, (uint)Enum.Parse(name)); } } [Fact] public void DeclaresNothingRetailDoesNot() { var expected = Golden.Select(g => g.Name).OrderBy(n => n, StringComparer.Ordinal); var actual = Enum.GetNames().OrderBy(n => n, StringComparer.Ordinal); Assert.Equal(expected, actual); } /// Retail numbers the sounds densely from 0 to 0xCC, and /// NUM_SOUND_TYPES is 0xCD. A gap here means a member was dropped. [Fact] public void CoversTheDenseRetailRangeWithNoGaps() { Assert.Equal(205, Enum.GetNames().Length); var values = Enum.GetValues().Cast().OrderBy(v => v).ToArray(); Assert.Equal(Enumerable.Range(0, 205).Select(i => (uint)i), values); Assert.Equal(0xCCu, values[^1]); } /// The dat-side hook path resolves sounds through DatReaderWriter's /// own Sound enum. The two must not drift apart, or a SoundTable lookup /// named through would fetch a different slot. [Fact] public void AgreesWithTheDatReaderEnumUsedAtRuntime() { foreach (var (name, value) in Golden) { Assert.True(Enum.IsDefined(typeof(DatReaderWriter.Enums.Sound), name), $"DatReaderWriter.Enums.Sound.{name} is missing"); Assert.Equal(value, (uint)Enum.Parse(name)); } } }