Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified client/audio/sfx/explosion.ogg
Binary file not shown.
Binary file modified client/audio/sfx/pickup.ogg
Binary file not shown.
Binary file modified client/audio/sfx/powerup_airstrike.ogg
Binary file not shown.
Binary file modified client/audio/sfx/powerup_bounce.ogg
Binary file not shown.
Binary file modified client/audio/sfx/powerup_missile.ogg
Binary file not shown.
Binary file modified client/audio/sfx/powerup_pierce.ogg
Binary file not shown.
Binary file modified client/audio/sfx/powerup_rapid.ogg
Binary file not shown.
Binary file modified client/audio/sfx/powerup_repair.ogg
Binary file not shown.
Binary file modified client/audio/sfx/powerup_shield.ogg
Binary file not shown.
Binary file modified client/audio/sfx/powerup_speed.ogg
Binary file not shown.
Binary file modified client/audio/sfx/powerup_spread.ogg
Binary file not shown.
Binary file modified client/audio/sfx/wall_break.ogg
Binary file not shown.
24 changes: 17 additions & 7 deletions client/src/Presentation/Arena/SfxPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,21 @@ public partial class SfxPool : Node
private static readonly Dictionary<SfxKind, string> 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",
Expand Down Expand Up @@ -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);
}

/// <summary>Whether the kind resolved a loaded audio stream
/// (false = unmapped, or its file is missing / failed to decode).</summary>
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)
Expand Down
25 changes: 21 additions & 4 deletions client/tests/Presentation/SfxPoolTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using Godot;
using Chickensoft.GoDotTest;
using TankGame.Presentation;
Expand All @@ -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<SfxKind>())
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<SfxKind>())
_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);
}
}
24 changes: 12 additions & 12 deletions docs/credits/assets.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!" |
Expand Down
Loading