diff --git a/client/audio/sfx/explosion.ogg b/client/audio/sfx/explosion.ogg index 9303c6a..980340f 100644 Binary files a/client/audio/sfx/explosion.ogg and b/client/audio/sfx/explosion.ogg differ diff --git a/client/audio/sfx/pickup.ogg b/client/audio/sfx/pickup.ogg index 9faad40..9d94121 100644 Binary files a/client/audio/sfx/pickup.ogg and b/client/audio/sfx/pickup.ogg differ diff --git a/client/audio/sfx/powerup_airstrike.ogg b/client/audio/sfx/powerup_airstrike.ogg index 9b903b4..f281655 100644 Binary files a/client/audio/sfx/powerup_airstrike.ogg and b/client/audio/sfx/powerup_airstrike.ogg differ diff --git a/client/audio/sfx/powerup_bounce.ogg b/client/audio/sfx/powerup_bounce.ogg index 739a356..d5e7fdf 100644 Binary files a/client/audio/sfx/powerup_bounce.ogg and b/client/audio/sfx/powerup_bounce.ogg differ diff --git a/client/audio/sfx/powerup_missile.ogg b/client/audio/sfx/powerup_missile.ogg index 23a7322..634b7ca 100644 Binary files a/client/audio/sfx/powerup_missile.ogg and b/client/audio/sfx/powerup_missile.ogg differ diff --git a/client/audio/sfx/powerup_pierce.ogg b/client/audio/sfx/powerup_pierce.ogg index 878383c..05e6480 100644 Binary files a/client/audio/sfx/powerup_pierce.ogg and b/client/audio/sfx/powerup_pierce.ogg differ diff --git a/client/audio/sfx/powerup_rapid.ogg b/client/audio/sfx/powerup_rapid.ogg index b7c26b7..f071b87 100644 Binary files a/client/audio/sfx/powerup_rapid.ogg and b/client/audio/sfx/powerup_rapid.ogg differ diff --git a/client/audio/sfx/powerup_repair.ogg b/client/audio/sfx/powerup_repair.ogg index 50b5dc6..1bcfa99 100644 Binary files a/client/audio/sfx/powerup_repair.ogg and b/client/audio/sfx/powerup_repair.ogg differ diff --git a/client/audio/sfx/powerup_shield.ogg b/client/audio/sfx/powerup_shield.ogg index 7a9355f..292cacd 100644 Binary files a/client/audio/sfx/powerup_shield.ogg and b/client/audio/sfx/powerup_shield.ogg differ diff --git a/client/audio/sfx/powerup_speed.ogg b/client/audio/sfx/powerup_speed.ogg index d2547ae..ad835d3 100644 Binary files a/client/audio/sfx/powerup_speed.ogg and b/client/audio/sfx/powerup_speed.ogg differ diff --git a/client/audio/sfx/powerup_spread.ogg b/client/audio/sfx/powerup_spread.ogg index 984ba8a..bd0a27f 100644 Binary files a/client/audio/sfx/powerup_spread.ogg and b/client/audio/sfx/powerup_spread.ogg differ diff --git a/client/audio/sfx/wall_break.ogg b/client/audio/sfx/wall_break.ogg index 50aeb00..518bb66 100644 Binary files a/client/audio/sfx/wall_break.ogg and b/client/audio/sfx/wall_break.ogg differ diff --git a/client/src/Presentation/Arena/SfxPool.cs b/client/src/Presentation/Arena/SfxPool.cs index 428023a..c877d96 100644 --- a/client/src/Presentation/Arena/SfxPool.cs +++ b/client/src/Presentation/Arena/SfxPool.cs @@ -26,14 +26,21 @@ public partial class SfxPool : Node private static readonly Dictionary SfxFiles = new() { [SfxKind.Fire] = "fire.ogg", - // placeholder: barrier-break SFX removed pending a better fit - [SfxKind.WallBreak] = "", + [SfxKind.Explosion] = "explosion.ogg", + [SfxKind.WallBreak] = "wall_break.ogg", + [SfxKind.Pickup] = "pickup.ogg", [SfxKind.Victory] = "victory.ogg", [SfxKind.UiClick] = "ui_click.ogg", [SfxKind.UiHover] = "ui_hover.ogg", - // Tank-death (Explosion) and powerup-pickup (Pickup + every PowerupX) are intentionally - // silent placeholders: no entry here means the play methods no-op. Restore these mappings - // once the owner supplies the real audio assets. + [SfxKind.PowerupSpeed] = "powerup_speed.ogg", + [SfxKind.PowerupRapidFire] = "powerup_rapid.ogg", + [SfxKind.PowerupBouncing] = "powerup_bounce.ogg", + [SfxKind.PowerupSpread] = "powerup_spread.ogg", + [SfxKind.PowerupPiercing] = "powerup_pierce.ogg", + [SfxKind.PowerupRepair] = "powerup_repair.ogg", + [SfxKind.PowerupShield] = "powerup_shield.ogg", + [SfxKind.PowerupMissile] = "powerup_missile.ogg", + [SfxKind.PowerupAirstrike] = "powerup_airstrike.ogg", [SfxKind.KillEnemy] = "kill_enemy.ogg", [SfxKind.StreakDouble] = "streak_double.ogg", [SfxKind.StreakTriple] = "streak_triple.ogg", @@ -77,10 +84,13 @@ public override void _Ready() AddChild(_voicePlayer); foreach (var (kind, file) in SfxFiles) - // An empty path is a deliberate silent placeholder: leave the stream null so PlayAt no-ops. - _streams[kind] = string.IsNullOrEmpty(file) ? null : LoadOgg(SfxDir + file); + _streams[kind] = LoadOgg(SfxDir + file); } + /// Whether the kind resolved a loaded audio stream + /// (false = unmapped, or its file is missing / failed to decode). + public bool HasStream(SfxKind kind) => _streams.TryGetValue(kind, out var s) && s is not null; + // Load an OGG file via FileAccess + LoadFromBuffer — bypasses the Godot import/resource // system entirely, so the file works immediately after a git pull without an editor import. private static AudioStream? LoadOgg(string resPath) diff --git a/client/tests/Presentation/SfxPoolTests.cs b/client/tests/Presentation/SfxPoolTests.cs index a62e0fc..95c1d15 100644 --- a/client/tests/Presentation/SfxPoolTests.cs +++ b/client/tests/Presentation/SfxPoolTests.cs @@ -1,3 +1,4 @@ +using System; using Godot; using Chickensoft.GoDotTest; using TankGame.Presentation; @@ -20,11 +21,27 @@ public void Setup() [Cleanup] public void Cleanup() => _pool.QueueFree(); - // WallBreak is mapped to "" (barrier-break SFX removed): playing it must be a silent no-op — - // no stream, no crash. The test passes iff PlayAt returns without throwing. + // Every kind maps to a real OGG: a null stream here means a missing or undecodable file + // (e.g. a WAV smuggled in under an .ogg name — LoadFromBuffer would return null). [Test] - public void PlayAt_WithASilentPlaceholderKind_DoesNotThrow() + public void EveryKind_ResolvesALoadedStream() { - _pool.PlayAt(SfxKind.WallBreak, Vector3.Zero); + foreach (var kind in Enum.GetValues()) + if (!_pool.HasStream(kind)) + throw new Exception($"SfxKind.{kind} must resolve a loaded audio stream."); + } + + [Test] + public void PlayAt_WithEveryKind_DoesNotThrow() + { + foreach (var kind in Enum.GetValues()) + _pool.PlayAt(kind, Vector3.Zero); + } + + // The pool must stay robust to a kind with no mapping: PlayAt no-ops instead of throwing. + [Test] + public void PlayAt_WithAnUnmappedKind_DoesNotThrow() + { + _pool.PlayAt((SfxKind)999, Vector3.Zero); } } diff --git a/docs/credits/assets.md b/docs/credits/assets.md index 9c7f702..3819b9d 100644 --- a/docs/credits/assets.md +++ b/docs/credits/assets.md @@ -29,21 +29,21 @@ update the affected row (Kenney license ≈ CC0, no attribution required). | Asset | Model / Tool | License | Prompt | |-------|-------------|---------|--------| | `client/audio/sfx/fire.ogg` (3.0 s) | **Stable Audio Open 1.0** via the `assetfactory` MCP `generate_sound`; auto-QC'd (CLAP-ranked best of 3, trimmed, peak-normalised); FLAC→OGG via ffmpeg | **Royalty-free / commercial use permitted** — Stable Audio Open's licence does **not** restrict the generated audio; free for any use including commercial | "a cartoonish tank cannon firing a shell, a punchy boomy pop with a quick airy whoosh, playful arcade style" (regenerated 2026-06-18, cartoonier per owner ask) | -| `client/audio/sfx/explosion.ogg` (2.0 s) | Same pipeline | Same | "a big punchy cartoon explosion, deep booming blast with a bright crack and scattering debris, impactful arcade style" (regenerated 2026-06-18) | -| `client/audio/sfx/wall_break.ogg` (2.0 s) | Same pipeline | Same | "brick wall crumbling and breaking apart, heavy rubble crashing to ground, destruction impact" | -| `client/audio/sfx/pickup.ogg` (2.0 s) | Same pipeline | Same | "item pickup collected, short positive electronic beep, power-up sound, retro arcade" | +| `client/audio/sfx/explosion.ogg` (1.5 s) | Same pipeline; post: trim + fade-out + 25 Hz highpass + peak-matched to `fire.ogg` (ffmpeg) | Same | "a big punchy cartoon explosion, a deep booming blast with a bright crack, playful arcade style" (regenerated 2026-07-17, SFX overhaul — all 12 non-voice clips below share this date + post chain) | +| `client/audio/sfx/wall_break.ogg` (1.5 s) | Same pipeline + post | Same | "a brick wall and wooden crate crumbling apart, rubble chunks tumbling to the ground, short destruction impact" | +| `client/audio/sfx/pickup.ogg` (1.0 s) | Same pipeline + post | Same | "a quick cheerful marimba plink, a soft rounded two-note pickup pop, warm and playful" | | `client/audio/sfx/victory.ogg` (3.6 s) | Same pipeline | Same | "triumphant victory fanfare, short brass sting, military bugle call, winner announcement" | | `client/audio/sfx/ui_click.ogg` (0.2 s) | Same pipeline | Same | "soft menu button click, brief UI tap, interface interaction sound, subtle click" | | `client/audio/sfx/ui_hover.ogg` (0.2 s) | Derived from `ui_click.ogg` via ffmpeg (`-filter:a volume=-10dB`) — a quieter click used for mouse-hover feedback | Same | (derived) | -| `client/audio/sfx/powerup_speed.ogg` (2.0 s) | Same pipeline | Same | "a revving car engine accelerating, a quick turbo boost rev-up whoosh, energetic arcade style" (Speed Boost) | -| `client/audio/sfx/powerup_rapid.ogg` (2.0 s) | Same pipeline | Same | "a fast machine gun rattle, rapid mechanical clatter of gunfire, arcade style" (Rapid Fire) | -| `client/audio/sfx/powerup_bounce.ogg` (2.0 s) | Same pipeline | Same | "a metallic ricochet ping, a bouncing bullet zing ricocheting off steel, cartoon style" (Bouncing Ammo) | -| `client/audio/sfx/powerup_spread.ogg` (1.5 s) | Same pipeline | Same | "a deep punchy shotgun blast, a low booming buckshot shot with a pump, arcade style" (Spread Shot) | -| `client/audio/sfx/powerup_pierce.ogg` (2.0 s) | Same pipeline | Same | "a sharp armor-piercing zing, a high-velocity metallic whistle punching through steel plate, cartoon style" (Piercing Ammo) | -| `client/audio/sfx/powerup_repair.ogg` (2.0 s) | Same pipeline | Same | "a heavy wrench ratcheting and a solid metal clunk, tank armor being repaired, warm low mechanical clanks" (Repair) | -| `client/audio/sfx/powerup_shield.ogg` (3.0 s) | Same pipeline | Same | "an energy shield powering up, a humming force field activation tone, sci-fi protective bubble" (Shield) | -| `client/audio/sfx/powerup_missile.ogg` (2.0 s) | Same pipeline | Same | "a rocket missile launching with a powerful whoosh and ignition roar, arcade style" (Missile) | -| `client/audio/sfx/powerup_airstrike.ogg` (3.5 s) | Same pipeline | Same | "a fighter jet roaring overhead with a fast whoosh followed by an air-raid warning siren, incoming airstrike" (Airstrike / Telephone) | +| `client/audio/sfx/powerup_speed.ogg` (1.5 s) | Same pipeline + post | Same | "a cartoon car engine revving up fast, a quick turbo boost rev, energetic and playful" (Speed Boost) | +| `client/audio/sfx/powerup_rapid.ogg` (1.5 s) | Same pipeline + post | Same | "a fast machine gun rattle, a short burst of rapid mechanical gunfire clatter, arcade style" (Rapid Fire) | +| `client/audio/sfx/powerup_bounce.ogg` (1.2 s) | Same pipeline + post (extra: −4 dB treble shelf @ 8 kHz) | Same | "a metallic ricochet ping, a bullet zinging off a steel plate, cartoon style" (Bouncing Ammo) | +| `client/audio/sfx/powerup_spread.ogg` (1.5 s) | Same pipeline + post | Same | "a single loud shotgun blast, a deep booming gunshot" (Spread Shot) | +| `client/audio/sfx/powerup_pierce.ogg` (1.4 s) | Same pipeline + post | Same | "a sharp armor piercing zing, a fast metallic whoosh punching through a steel plate, cartoon style" (Piercing Ammo) | +| `client/audio/sfx/powerup_repair.ogg` (1.5 s) | Same pipeline + post | Same | "a warm restorative glockenspiel ding with a quick wrench ratchet click, gentle and positive repair sound" (Repair) | +| `client/audio/sfx/powerup_shield.ogg` (1.5 s) | Same pipeline + post | Same | "an energy shield powering on, a rising electric hum swelling into a steady force field tone, sci-fi bubble" (Shield) | +| `client/audio/sfx/powerup_missile.ogg` (1.5 s) | Same pipeline + post | Same | "a rocket missile launching with a fast whoosh and ignition burst, arcade style" (Missile) | +| `client/audio/sfx/powerup_airstrike.ogg` (2.0 s) | Same pipeline + post | Same | "an air raid siren wailing as a military jet plane flies past overhead with a loud roaring whoosh" (Airstrike / Telephone) | | `client/audio/sfx/kill_enemy.ogg` (~1 s) | **Kokoro TTS** voice `bm_george` (British male) via Reader `scripts/synth.py`; WAV→OGG via ffmpeg | **Kokoro-82M is Apache-2.0**; the synthesized speech is free for any use including commercial | spoken: "Enemy destroyed!" — the local player's kill announcer | | `client/audio/sfx/streak_double.ogg` (~1 s) | Same (Kokoro `bm_george`) | Same | spoken: "Double kill!" | | `client/audio/sfx/streak_triple.ogg` (~1 s) | Same (Kokoro `bm_george`) | Same | spoken: "Triple kill!" |