Refactor audio module to use built-in music and sound effect identifiers

- Introduced BuiltInMusicModule and BuiltInSfxModule to replace RetailMusicModule and RetailSfxModule.
- Updated RetailAssetRegistry and SharewareAssetRegistry to utilize the new built-in modules.
- Removed deprecated MusicKey and SfxKey classes, replacing them with Music and SoundEffect enums for better clarity and maintainability.
- Adjusted music and sound effect resolution methods to align with the new structure.
- Updated audio playback methods in WolfAudio and FlutterAudioAdapter to accept the new Music and SoundEffect types.
- Refactored tests to accommodate changes in audio event handling and ensure compatibility with the new identifiers.

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-20 17:07:25 +01:00
parent 8cca66e966
commit 10417d26ba
42 changed files with 499 additions and 622 deletions
@@ -9,7 +9,7 @@ import 'package:wolf_3d_dart/src/registry/modules/sfx_module.dart';
/// Access version-specific asset IDs through the five typed modules:
///
/// ```dart
/// data.registry.sfx.resolve(SfxKey.pistolFire)
/// data.registry.sfx.resolve(SoundEffect.pistolFire)
/// data.registry.music.musicForLevel(episode, level)
/// data.registry.entities.resolve(EntityKey.guard)
/// data.registry.hud.faceForHealth(player.health)
@@ -0,0 +1,27 @@
import 'package:wolf_3d_dart/src/data_types/game_version.dart';
import 'package:wolf_3d_dart/src/registry/keys/music.dart';
import 'package:wolf_3d_dart/src/registry/modules/music_module.dart';
/// Built-in music router backed directly by [Music] metadata.
class BuiltInMusicModule extends MusicModule {
const BuiltInMusicModule(this.version);
final GameVersion version;
@override
MusicRoute get menuMusic =>
MusicRoute(Music.menuTheme.trackIndexFor(version)!);
@override
MusicRoute musicForLevel(int episodeIndex, int levelIndex) {
return MusicRoute(
Music.trackIndexForLevel(version, episodeIndex, levelIndex)!,
);
}
@override
MusicRoute? resolve(Music key) {
final index = key.trackIndexFor(version);
return index != null ? MusicRoute(index) : null;
}
}
@@ -0,0 +1,13 @@
import 'package:wolf_3d_dart/src/data_types/game_version.dart';
import 'package:wolf_3d_dart/src/registry/keys/sound_effect.dart';
import 'package:wolf_3d_dart/src/registry/modules/sfx_module.dart';
/// Built-in sound-slot resolver backed directly by [SoundEffect] metadata.
class BuiltInSfxModule extends SfxModule {
const BuiltInSfxModule(this.version);
final GameVersion version;
@override
SoundAssetRef? resolve(SoundEffect key) => SoundAssetRef(key.idFor(version));
}
@@ -1,20 +1,21 @@
import 'package:wolf_3d_dart/src/data_types/game_version.dart';
import 'package:wolf_3d_dart/src/registry/asset_registry.dart';
import 'package:wolf_3d_dart/src/registry/built_in/built_in_music_module.dart';
import 'package:wolf_3d_dart/src/registry/built_in/built_in_sfx_module.dart';
import 'package:wolf_3d_dart/src/registry/built_in/retail_entity_module.dart';
import 'package:wolf_3d_dart/src/registry/built_in/retail_hud_module.dart';
import 'package:wolf_3d_dart/src/registry/built_in/retail_menu_module.dart';
import 'package:wolf_3d_dart/src/registry/built_in/retail_music_module.dart';
import 'package:wolf_3d_dart/src/registry/built_in/retail_sfx_module.dart';
/// The canonical [AssetRegistry] for all retail Wolf3D releases.
///
/// Composes the five retail built-in modules and preserves the original
/// id Software asset layout exactly. All other registries and tests that
/// id Software asset layout exactly. All other registries and tests that
/// need a stable reference should start from this factory.
class RetailAssetRegistry extends AssetRegistry {
RetailAssetRegistry()
: super(
sfx: const RetailSfxModule(),
music: const RetailMusicModule(),
sfx: const BuiltInSfxModule(GameVersion.retail),
music: const BuiltInMusicModule(GameVersion.retail),
entities: const RetailEntityModule(),
hud: const RetailHudModule(),
menu: const RetailMenuPicModule(),
@@ -1,62 +0,0 @@
import 'package:wolf_3d_dart/src/registry/keys/music_key.dart';
import 'package:wolf_3d_dart/src/registry/modules/music_module.dart';
/// Built-in music module for all retail Wolf3D releases (v1.0, v1.1, v1.4).
///
/// Encodes the original id Software level-to-track routing table and sets
/// track index 1 as the menu music, exactly matching the original engine.
class RetailMusicModule extends MusicModule {
const RetailMusicModule();
// Original WL_INTER.C music table — 60 entries (6 episodes × 10 levels).
static const List<int> _levelMap = [
2, 3, 4, 5, 2, 3, 4, 5, 6, 7, // Episode 1
8, 9, 10, 11, 8, 9, 11, 10, 6, 12, // Episode 2
13, 14, 15, 16, 13, 14, 15, 16, 17, 18, // Episode 3
2, 3, 4, 5, 2, 3, 4, 5, 6, 7, // Episode 4
8, 9, 10, 11, 8, 9, 11, 10, 6, 12, // Episode 5
13, 14, 15, 16, 13, 14, 15, 16, 17, 19, // Episode 6
];
// Named MusicKey → track index for keyed lookups.
static final Map<MusicKey, int> _named = {
MusicKey.menuTheme: 1,
MusicKey.level01: 2,
MusicKey.level02: 3,
MusicKey.level03: 4,
MusicKey.level04: 5,
MusicKey.level05: 6,
MusicKey.level06: 7,
MusicKey.level07: 8,
MusicKey.level08: 9,
MusicKey.level09: 10,
MusicKey.level10: 11,
MusicKey.level11: 12,
MusicKey.level12: 13,
MusicKey.level13: 14,
MusicKey.level14: 15,
MusicKey.level15: 16,
MusicKey.level16: 17,
MusicKey.level17: 18,
MusicKey.level18: 19,
};
@override
MusicRoute get menuMusic => const MusicRoute(1);
@override
MusicRoute musicForLevel(int episodeIndex, int levelIndex) {
final flat = episodeIndex * 10 + levelIndex;
if (flat >= 0 && flat < _levelMap.length) {
return MusicRoute(_levelMap[flat]);
}
// Wrap for custom episode counts beyond the built-in 6.
return MusicRoute(_levelMap[flat % _levelMap.length]);
}
@override
MusicRoute? resolve(MusicKey key) {
final index = _named[key];
return index != null ? MusicRoute(index) : null;
}
}
@@ -1,67 +0,0 @@
import 'package:wolf_3d_dart/src/data_types/sound.dart';
import 'package:wolf_3d_dart/src/registry/keys/sfx_key.dart';
import 'package:wolf_3d_dart/src/registry/modules/sfx_module.dart';
/// Built-in SFX module for all retail Wolf3D releases (v1.0, v1.1, v1.4).
///
/// Maps every [SfxKey] to the corresponding numeric slot from [WolfSound],
/// preserving the original id Software audio index layout exactly.
class RetailSfxModule extends SfxModule {
const RetailSfxModule();
static final Map<SfxKey, int> _slots = {
// --- Doors & Environment ---
SfxKey.openDoor: WolfSound.openDoor,
SfxKey.closeDoor: WolfSound.closeDoor,
SfxKey.pushWall: WolfSound.pushWall,
// --- Weapons ---
SfxKey.knifeAttack: WolfSound.knifeAttack,
SfxKey.pistolFire: WolfSound.pistolFire,
SfxKey.machineGunFire: WolfSound.machineGunFire,
SfxKey.chainGunFire: WolfSound.gatlingFire,
SfxKey.enemyFire: WolfSound.naziFire,
// --- Pickups ---
SfxKey.getMachineGun: WolfSound.getMachineGun,
SfxKey.getAmmo: WolfSound.getAmmo,
SfxKey.getChainGun: WolfSound.getGatling,
SfxKey.healthSmall: WolfSound.healthSmall,
SfxKey.healthLarge: WolfSound.healthLarge,
SfxKey.treasure1: WolfSound.treasure1,
SfxKey.treasure2: WolfSound.treasure2,
SfxKey.treasure3: WolfSound.treasure3,
SfxKey.treasure4: WolfSound.treasure4,
SfxKey.extraLife: WolfSound.extraLife,
// --- Standard Enemies ---
SfxKey.guardHalt: WolfSound.guardHalt,
SfxKey.dogBark: WolfSound.dogBark,
SfxKey.dogDeath: WolfSound.dogDeath,
SfxKey.dogAttack: WolfSound.dogAttack,
SfxKey.deathScream1: WolfSound.deathScream1,
SfxKey.deathScream2: WolfSound.deathScream2,
SfxKey.deathScream3: WolfSound.deathScream3,
SfxKey.ssAlert: WolfSound.ssSchutzstaffel,
SfxKey.ssDeath: WolfSound.ssMeinGott,
// --- Bosses ---
SfxKey.bossActive: WolfSound.bossActive,
SfxKey.hansGrosseDeath: WolfSound.mutti,
SfxKey.schabbs: WolfSound.schabbsHas,
SfxKey.schabbsDeath: WolfSound.eva,
SfxKey.hitlerGreeting: WolfSound.gutenTag,
SfxKey.hitlerDeath: WolfSound.scheist,
SfxKey.mechaSteps: WolfSound.mechSteps,
SfxKey.ottoAlert: WolfSound.spion,
SfxKey.gretelDeath: WolfSound.neinSoVass,
// --- UI & Progression ---
SfxKey.levelComplete: WolfSound.levelDone,
SfxKey.endBonus1: WolfSound.endBonus1,
SfxKey.endBonus2: WolfSound.endBonus2,
SfxKey.noBonus: WolfSound.noBonus,
SfxKey.percent100: WolfSound.percent100,
};
@override
SoundAssetRef? resolve(SfxKey key) {
final slot = _slots[key];
return slot != null ? SoundAssetRef(slot) : null;
}
}
@@ -1,14 +1,15 @@
import 'package:wolf_3d_dart/src/data_types/game_version.dart';
import 'package:wolf_3d_dart/src/registry/asset_registry.dart';
import 'package:wolf_3d_dart/src/registry/built_in/retail_sfx_module.dart';
import 'package:wolf_3d_dart/src/registry/built_in/built_in_music_module.dart';
import 'package:wolf_3d_dart/src/registry/built_in/built_in_sfx_module.dart';
import 'package:wolf_3d_dart/src/registry/built_in/shareware_entity_module.dart';
import 'package:wolf_3d_dart/src/registry/built_in/shareware_hud_module.dart';
import 'package:wolf_3d_dart/src/registry/built_in/shareware_menu_module.dart';
import 'package:wolf_3d_dart/src/registry/built_in/shareware_music_module.dart';
/// The [AssetRegistry] for the Wolfenstein 3D v1.4 Shareware release.
///
/// - SFX slots are identical to retail (same AUDIOT layout).
/// - Music routing uses the 10-level shareware table.
/// - SFX slots are resolved through version-aware [SoundEffect.idFor].
/// - Music routing uses [Music.levelFor] for the 10-level shareware table.
/// - Entity definitions are limited to the three shareware enemies.
/// - HUD indices are shareware-aware and offset from retail layout.
/// - Menu picture indices are resolved via runtime heuristic offset; call
@@ -17,8 +18,8 @@ import 'package:wolf_3d_dart/src/registry/built_in/shareware_music_module.dart';
class SharewareAssetRegistry extends AssetRegistry {
SharewareAssetRegistry({bool strictOriginalShareware = false})
: super(
sfx: const RetailSfxModule(),
music: const SharewareMusicModule(),
sfx: const BuiltInSfxModule(GameVersion.shareware),
music: const BuiltInMusicModule(GameVersion.shareware),
entities: const SharewareEntityModule(),
hud: SharewareHudModule(
useOriginalWl1Map: strictOriginalShareware,
@@ -1,49 +0,0 @@
import 'package:wolf_3d_dart/src/registry/keys/music_key.dart';
import 'package:wolf_3d_dart/src/registry/modules/music_module.dart';
/// Built-in music module for the Wolfenstein 3D v1.4 Shareware release.
///
/// Uses the original id Software shareware level-to-track routing table
/// (10 levels, 1 episode) as opposed to the 60-entry retail table.
class SharewareMusicModule extends MusicModule {
const SharewareMusicModule();
// Original WL_INTER.C shareware music table (Episode 1 only).
static const List<int> _levelMap = [
2,
3,
4,
5,
2,
3,
4,
5,
6,
7,
];
static final Map<MusicKey, int> _named = {
MusicKey.menuTheme: 1,
MusicKey.level01: 2,
MusicKey.level02: 3,
MusicKey.level03: 4,
MusicKey.level04: 5,
MusicKey.level05: 6,
MusicKey.level06: 7,
};
@override
MusicRoute get menuMusic => const MusicRoute(1);
@override
MusicRoute musicForLevel(int episodeIndex, int levelIndex) {
final flat = levelIndex % _levelMap.length;
return MusicRoute(_levelMap[flat]);
}
@override
MusicRoute? resolve(MusicKey key) {
final index = _named[key];
return index != null ? MusicRoute(index) : null;
}
}
@@ -0,0 +1,162 @@
import 'package:wolf_3d_dart/src/data_types/game_version.dart';
/// Canonical music identifiers used by built-in registries.
enum Music {
// --- Menus & UI ---
menuTheme(retailTrackIndex: 1, sharewareTrackIndex: 1),
// --- Gameplay ---
// Generic level-slot keys used when routing by episode+floor index.
level01(retailTrackIndex: 2, sharewareTrackIndex: 2),
level02(retailTrackIndex: 3, sharewareTrackIndex: 3),
level03(retailTrackIndex: 4, sharewareTrackIndex: 4),
level04(retailTrackIndex: 5, sharewareTrackIndex: 5),
level05(retailTrackIndex: 6, sharewareTrackIndex: 6),
level06(retailTrackIndex: 7, sharewareTrackIndex: 7),
level07(retailTrackIndex: 8),
level08(retailTrackIndex: 9),
level09(retailTrackIndex: 10),
level10(retailTrackIndex: 11),
level11(retailTrackIndex: 12),
level12(retailTrackIndex: 13),
level13(retailTrackIndex: 14),
level14(retailTrackIndex: 15),
level15(retailTrackIndex: 16),
level16(retailTrackIndex: 17),
level17(retailTrackIndex: 18),
level18(retailTrackIndex: 19),
level19(),
level20(),
;
const Music({this.retailTrackIndex, this.sharewareTrackIndex});
final int? retailTrackIndex;
final int? sharewareTrackIndex;
int? trackIndexFor(GameVersion version) {
switch (version) {
case GameVersion.shareware:
return sharewareTrackIndex;
case GameVersion.retail:
case GameVersion.spearOfDestiny:
case GameVersion.spearOfDestinyDemo:
return retailTrackIndex;
}
}
static Music levelFor(GameVersion version, int episodeIndex, int levelIndex) {
final route = switch (version) {
GameVersion.shareware => _sharewareLevelRoute,
GameVersion.retail ||
GameVersion.spearOfDestiny ||
GameVersion.spearOfDestinyDemo => _retailLevelRoute,
};
final flatIndex = switch (version) {
GameVersion.shareware => levelIndex,
GameVersion.retail ||
GameVersion.spearOfDestiny ||
GameVersion.spearOfDestinyDemo => episodeIndex * 10 + levelIndex,
};
final normalizedIndex = flatIndex >= 0
? flatIndex % route.length
: (flatIndex % route.length + route.length) % route.length;
return route[normalizedIndex];
}
static int? trackIndexForLevel(
GameVersion version,
int episodeIndex,
int levelIndex,
) {
return levelFor(version, episodeIndex, levelIndex).trackIndexFor(version);
}
static Music? fromTrackIndex(GameVersion version, int trackIndex) {
for (final music in values) {
if (music.trackIndexFor(version) == trackIndex) {
return music;
}
}
return null;
}
static const List<Music> _sharewareLevelRoute = [
level01,
level02,
level03,
level04,
level01,
level02,
level03,
level04,
level05,
level06,
];
static const List<Music> _retailLevelRoute = [
level01,
level02,
level03,
level04,
level01,
level02,
level03,
level04,
level05,
level06,
level07,
level08,
level09,
level10,
level07,
level08,
level10,
level09,
level05,
level11,
level12,
level13,
level14,
level15,
level12,
level13,
level14,
level15,
level16,
level17,
level01,
level02,
level03,
level04,
level01,
level02,
level03,
level04,
level05,
level06,
level07,
level08,
level09,
level10,
level07,
level08,
level10,
level09,
level05,
level11,
level12,
level13,
level14,
level15,
level12,
level13,
level14,
level15,
level16,
level18,
];
}
@@ -1,51 +0,0 @@
/// Extensible typed key for music tracks.
///
/// Built-in Wolf3D music contexts are exposed as named static constants.
/// Custom modules can define new keys with `const MusicKey('myTrack')`
/// without modifying this class.
///
/// Example:
/// ```dart
/// registry.music.resolve(MusicKey.menuTheme)
/// ```
final class MusicKey {
const MusicKey(this._id);
final String _id;
// --- Menus & UI ---
static const menuTheme = MusicKey('menuTheme');
// --- Gameplay ---
// Generic level-slot keys used when routing by episode+floor index.
// The MusicModule maps these to actual IMF track indices.
static const level01 = MusicKey('level01');
static const level02 = MusicKey('level02');
static const level03 = MusicKey('level03');
static const level04 = MusicKey('level04');
static const level05 = MusicKey('level05');
static const level06 = MusicKey('level06');
static const level07 = MusicKey('level07');
static const level08 = MusicKey('level08');
static const level09 = MusicKey('level09');
static const level10 = MusicKey('level10');
static const level11 = MusicKey('level11');
static const level12 = MusicKey('level12');
static const level13 = MusicKey('level13');
static const level14 = MusicKey('level14');
static const level15 = MusicKey('level15');
static const level16 = MusicKey('level16');
static const level17 = MusicKey('level17');
static const level18 = MusicKey('level18');
static const level19 = MusicKey('level19');
static const level20 = MusicKey('level20');
@override
bool operator ==(Object other) => other is MusicKey && other._id == _id;
@override
int get hashCode => _id.hashCode;
@override
String toString() => 'MusicKey($_id)';
}
@@ -1,77 +0,0 @@
/// Extensible typed key for sound effects.
///
/// Built-in Wolf3D sound effects are exposed as named static constants.
/// Custom modules can define new keys with `const SfxKey('myCustomId')`
/// without modifying this class.
///
/// Example:
/// ```dart
/// registry.sfx.resolve(SfxKey.pistolFire)
/// ```
final class SfxKey {
const SfxKey(this._id);
final String _id;
// --- Doors & Environment ---
static const openDoor = SfxKey('openDoor');
static const closeDoor = SfxKey('closeDoor');
static const pushWall = SfxKey('pushWall');
// --- Weapons ---
static const knifeAttack = SfxKey('knifeAttack');
static const pistolFire = SfxKey('pistolFire');
static const machineGunFire = SfxKey('machineGunFire');
static const chainGunFire = SfxKey('chainGunFire');
static const enemyFire = SfxKey('enemyFire');
// --- Pickups ---
static const getMachineGun = SfxKey('getMachineGun');
static const getAmmo = SfxKey('getAmmo');
static const getChainGun = SfxKey('getChainGun');
static const healthSmall = SfxKey('healthSmall');
static const healthLarge = SfxKey('healthLarge');
static const treasure1 = SfxKey('treasure1');
static const treasure2 = SfxKey('treasure2');
static const treasure3 = SfxKey('treasure3');
static const treasure4 = SfxKey('treasure4');
static const extraLife = SfxKey('extraLife');
// --- Standard Enemies ---
static const guardHalt = SfxKey('guardHalt');
static const dogBark = SfxKey('dogBark');
static const dogDeath = SfxKey('dogDeath');
static const dogAttack = SfxKey('dogAttack');
static const deathScream1 = SfxKey('deathScream1');
static const deathScream2 = SfxKey('deathScream2');
static const deathScream3 = SfxKey('deathScream3');
static const ssAlert = SfxKey('ssAlert');
static const ssDeath = SfxKey('ssDeath');
// --- Bosses ---
static const bossActive = SfxKey('bossActive');
static const hansGrosseDeath = SfxKey('hansGrosseDeath');
static const schabbs = SfxKey('schabbs');
static const schabbsDeath = SfxKey('schabbsDeath');
static const hitlerGreeting = SfxKey('hitlerGreeting');
static const hitlerDeath = SfxKey('hitlerDeath');
static const mechaSteps = SfxKey('mechaSteps');
static const ottoAlert = SfxKey('ottoAlert');
static const gretelDeath = SfxKey('gretelDeath');
// --- UI & Progression ---
static const levelComplete = SfxKey('levelComplete');
static const endBonus1 = SfxKey('endBonus1');
static const endBonus2 = SfxKey('endBonus2');
static const noBonus = SfxKey('noBonus');
static const percent100 = SfxKey('percent100');
@override
bool operator ==(Object other) => other is SfxKey && other._id == _id;
@override
int get hashCode => _id.hashCode;
@override
String toString() => 'SfxKey($_id)';
}
@@ -0,0 +1,75 @@
import 'package:wolf_3d_dart/src/data_types/game_version.dart';
/// Canonical sound-effect identifiers used by built-in registries.
enum SoundEffect {
// --- Doors & Environment ---
openDoor(retailId: 8),
closeDoor(retailId: 9),
pushWall(retailId: 46),
// --- Weapons ---
knifeAttack(retailId: 23),
pistolFire(retailId: 24),
machineGunFire(retailId: 26),
chainGunFire(retailId: 32),
enemyFire(retailId: 58),
// --- Pickups ---
getMachineGun(retailId: 30),
getAmmo(retailId: 31),
getChainGun(retailId: 38),
healthSmall(retailId: 33),
healthLarge(retailId: 34),
treasure1(retailId: 35),
treasure2(retailId: 36),
treasure3(retailId: 37),
treasure4(retailId: 45),
extraLife(retailId: 44),
// --- Standard Enemies ---
guardHalt(retailId: 21),
dogBark(retailId: 41),
dogDeath(retailId: 62),
dogAttack(retailId: 68),
deathScream1(retailId: 29),
deathScream2(retailId: 22),
deathScream3(retailId: 25),
ssAlert(retailId: 51),
ssDeath(retailId: 63),
// --- Bosses ---
bossActive(retailId: 49),
hansGrosseDeath(retailId: 50),
schabbs(retailId: 64),
schabbsDeath(retailId: 54),
hitlerGreeting(retailId: 55),
hitlerDeath(retailId: 57),
mechaSteps(retailId: 70),
ottoAlert(retailId: 66),
gretelDeath(retailId: 67),
// --- UI & Progression ---
levelComplete(retailId: 40),
endBonus1(retailId: 42),
endBonus2(retailId: 43),
noBonus(retailId: 47),
percent100(retailId: 48),
;
const SoundEffect({required this.retailId, int? sharewareId})
: sharewareId = sharewareId ?? retailId;
final int retailId;
final int sharewareId;
int idFor(GameVersion version) {
switch (version) {
case GameVersion.shareware:
return sharewareId;
case GameVersion.retail:
case GameVersion.spearOfDestiny:
case GameVersion.spearOfDestinyDemo:
return retailId;
}
}
}
@@ -1,4 +1,4 @@
import 'package:wolf_3d_dart/src/registry/keys/music_key.dart';
import 'package:wolf_3d_dart/src/registry/keys/music.dart';
/// The resolved reference for a music track: a numeric index into
/// [WolfensteinData.music].
@@ -19,10 +19,10 @@ class MusicRoute {
abstract class MusicModule {
const MusicModule();
/// Resolves a named [MusicKey] to a [MusicRoute].
/// Resolves a named [Music] to a [MusicRoute].
///
/// Returns `null` if the key is not supported by this module.
MusicRoute? resolve(MusicKey key);
MusicRoute? resolve(Music key);
/// Resolves the level music track for a given [episodeIndex] and
/// zero-based [levelIndex].
@@ -1,4 +1,4 @@
import 'package:wolf_3d_dart/src/registry/keys/sfx_key.dart';
import 'package:wolf_3d_dart/src/registry/keys/sound_effect.dart';
/// The resolved reference for a sound effect: a numeric slot index into
/// [WolfensteinData.sounds].
@@ -11,7 +11,7 @@ class SoundAssetRef {
String toString() => 'SoundAssetRef($slotIndex)';
}
/// Owns the mapping from symbolic [SfxKey] identifiers to numeric sound slots.
/// Owns the mapping from symbolic [SoundEffect] identifiers to numeric sound slots.
///
/// Implement this class to provide a custom sound layout for a modded or
/// alternate game version. Pass the implementation inside a custom
@@ -22,5 +22,5 @@ abstract class SfxModule {
/// Resolves [key] to a [SoundAssetRef] containing the numeric slot index.
///
/// Returns `null` if the key is not supported by this module.
SoundAssetRef? resolve(SfxKey key);
SoundAssetRef? resolve(SoundEffect key);
}