Asteroids Destroyer Dev Log – 14 June 2026

Asteroids Destroyer โ€” Development Journal

Dev Log โ€” 14 June 2026

๐Ÿ“… Session 11
๐ŸŽฎ Godot 4.6
๐Ÿ”Š Full Audio Wiring
๐ŸŽต In-Game Music System
โš™๏ธ Settings Overhaul
๐Ÿš€

Session Overview

A dedicated audio session. All remaining “ready to wire” sound effects were wired in, a full in-game music system with 18 shuffled tracks was built, the Settings menu was overhauled with three independent volume sliders replacing the fullscreen toggle, and several bugs introduced during audio wiring were fixed.

  • Wired: All remaining SFX: mothership shot, asteroid hit, crystal pickup, power-up pickup, nuke explosion, ship purchase chime
  • New: Persistent menu music starting at the splash screen and playing through all menus
  • New: In-game background music: 18 tracks shuffled and played sequentially, stopping when gameplay starts and resuming on return to menus
  • New: Enemy unit and mothership ambient engine hum (looping, low volume)
  • New: Player death, shield activation, ship level-up, and rank promotion sounds
  • New: Settings menu volume system: Master Volume, Music Volume, SFX Volume sliders with individual bus control
  • Fixed: Three bugs: Array[int] type error, velocity variable shadowing, nuke physics flush error
๐Ÿ”Š

Sound Effects: Full Wiring Pass

All “Assets on Disk: Ready to Wire” sounds from the Session 10 audit were wired via the central GameManager.play_sfx(stream) helper. This dispatches a self-freeing AudioStreamPlayer on the GameManager node so sounds survive entity destruction.

Sound File Trigger Script
Enemy Shot Fired.wav Mothership fires a bullet EnemyMothership.gd ยท _shoot_at()
Asteroid Hit.mp3 Asteroid takes any hit (including final) Asteroid.gd ยท hit()
Crystal Pickup.mp3 Player collects a crystal Crystal.gd ยท _on_body_entered()
Power Up Pick UP.mp3 Any power-up collected PowerUp.gd ยท _apply()
Nuke Exp.mp3 Nuke detonates NukeExplosion.gd ยท _ready()
HoloTill Chime.wav Successful ship purchase CharacterShop.gd ยท _on_tile_pressed()
sci-fi-explosion.mp3 Enemy unit or mothership destroyed GameScene.gd ยท _on_enemy_destroyed()
Ship Level Up.wav Ship crosses X1โ†’X2 or X2โ†’X3 threshold GameScene.gd ยท _on_ship_leveled_up()
Level_up_upgrade…variation4.wav Player rank promotion HUD.gd ยท _on_rank_up()
Spaceship-large-underwater-explosion.mp3 Player dies GameScene.gd ยท _on_player_died()
spaceship Shield.mp3 Shield power-up activated Player.gd ยท activate_shield()

Ambient Enemy Hum: Per-Entity Looping

Enemy units and motherships now emit a continuous engine hum for the duration of their lifetime. An AudioStreamPlayer is created as a child node in _ready() so it is automatically freed when the entity is destroyed. The finished signal is connected back to play() to loop.

Sound Entity Volume
Alien Ship.wav EnemyUnit -18 dB
Mothership.mp3 EnemyMothership -15 dB
โœจ

Persistent Menu Music System

New Menu music previously started when MainMenu.tscn loaded, meaning it restarted every time the player navigated back to the main menu. It has been moved to GameManager so it starts immediately at the splash screen and continues uninterrupted through all menus.

Architecture

  • A persistent _menu_music: AudioStreamPlayer lives on GameManager (survives all scene changes)
  • play_menu_music() plays from the start; no-op if already playing (prevents restart when navigating between menus)
  • stop_menu_music() stops cleanly
  • _menu_music.finished connected to _menu_music.play for seamless looping

Scene Hooks

Scene Action
GameManager._ready() Creates player, starts music; covers splash screen from first frame
GameScene._ready() Calls stop_menu_music() then play_game_music()
MainMenu._ready() Calls stop_game_music() then play_menu_music(), restarting menu music after returning from gameplay
Key design:play_menu_music() checks _menu_music.playing before acting. Navigating main menu โ†’ shop โ†’ level select โ†’ back never restarts the track mid-song. Only returning from gameplay (where it was stopped) triggers a restart.
โœจ

In-Game Music System (18 Tracks)

New 18 gameplay music tracks were added in a new res://Sounds/Game Music/ folder. The original files had long descriptive names; all were renamed Track 1.wav through Track 18.wav for clean resource paths.

Shuffled Playlist

The music system in GameManager shuffles all 18 track indices at the start of each gameplay session, plays them in shuffled order one at a time, then reshuffles and repeats when all tracks are exhausted. This avoids the same track playing twice in a row.

func play_game_music() -> void:
    # Lazy-initialise the player on first call
    _game_track_order.assign(range(GAME_MUSIC_PATHS.size()))
    _game_track_order.shuffle()
    _game_track_index = 0
    _play_game_track()

func _on_game_track_finished() -> void:
    _play_game_track()   # advance to next; reshuffles when list exhausted
Type fix:range() returns an untyped Array in Godot 4. Assigning it directly to Array[int] raises a runtime error. Fix: use Array[int].assign(range(n)) which accepts an untyped array and converts element-by-element.
โœจ

Settings Menu: Volume Slider Overhaul

NewChange The fullscreen toggle was removed. In its place, three independent volume sliders were added using existing neon UI assets from res://Game Assets/UI/Loading/.

Layout

Row Control Bus Affected
Music Toggle (on/off) Music bus mute
Music Volume HSlider 0โ€“100 Music bus volume
Sound Effects Toggle (on/off) SFX bus mute
SFX Volume HSlider 0โ€“100 SFX bus volume
Master Volume HSlider 0โ€“100 Both buses (multiplied)

Volume Maths

Each bus receives the product of the master and its individual level, converted to dB:

func apply_volume() -> void:
    var music_db := linear_to_db(master_volume * music_volume) if master_volume * music_volume > 0.0 else -80.0
    var sfx_db   := linear_to_db(master_volume * sfx_volume)   if master_volume * sfx_volume   > 0.0 else -80.0
    AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Music"), music_db)
    AudioServer.set_bus_volume_db(AudioServer.get_bus_index("SFX"),   sfx_db)

Setting any slider to 0 sends the bus to -80 dB (silence) rather than -inf, avoiding a Godot AudioServer edge case.

Slider Styling

Sliders are styled entirely in code via _style_slider(slider: HSlider) called for each of the three sliders in _ready():

  • Track:StyleBoxTexture using settings_volume.png (neon cyan outline)
  • Fill:StyleBoxTexture using settings_volume 2.png (solid teal)
  • Grabber knob: Programmatic 36ร—36 px cyan circle drawn via Image.create() and converted to ImageTexture at runtime, so no separate asset needed

Persistence

master_volume, music_volume, and sfx_volume are saved to user://save.cfg under the settings section. All default to 1.0 for first-run players.

๐Ÿ›

Bug Fixes

Array[int] Type Assignment Error

FixGameManager.play_game_music() raised a runtime error on first call:

-- Trying to assign an array of type "Array" to a variable of type "Array[int]"

range() returns an untyped Array in Godot 4. Direct assignment to a typed Array[int] variable fails. Fixed by replacing _game_track_order = range(n) with _game_track_order.assign(range(n)).

velocity Shadowing CharacterBody2D Property

FixPlayer.gd line 1098 declared var velocity: Vector2 inside the death effect loop, shadowing CharacterBody2D.velocity. Renamed to fly_vel in both the declaration and the timer lambda closure that references it.

Nuke Physics Flush Error

FixPowerUp.gd _fire_nuke() called get_tree().current_scene.add_child(missile) directly inside _on_body_entered(), a physics callback. Godot 2D physics forbids modifying the scene tree while queries are flushing:

-- Can't change this state while flushing queries. Use call_deferred().

Fixed by changing to add_child.call_deferred(missile). The missile is still instantiated and configured immediately; it is only added to the scene tree on the next frame.

๐Ÿ“

Files Changed

File Change Status
scripts/autoload/GameManager.gd Added play_sfx(), play/stop_menu_music(), play/stop_game_music(), 18-track shuffled playlist, master/music/sfx volume vars, apply_volume(), save/load all new settings Done
scripts/game/GameScene.gd Added stop_menu_music() + play_game_music() in _ready(); enemy explosion sound in _on_enemy_destroyed(); ship level-up sound in _on_ship_leveled_up(); player death sound in _on_player_died() Done
scripts/ui/HUD.gd Rank promotion sound in _on_rank_up() Done
scripts/menus/MainMenu.gd Removed local music player; calls stop_game_music() + play_menu_music() in _ready() Done
scripts/menus/Settings.gd Removed fullscreen toggle; added three styled HSlider controls (Master, Music, SFX); _style_slider() helper generates neon styling at runtime Done
scenes/menus/Settings.tscn Removed RowFullscreen; added RowMusicVolume and RowSFXVolume with HSlider nodes; spacing tuned to 50px uniform gap Done
scripts/entities/EnemyMothership.gd Enemy shot sound in _shoot_at(); ambient Mothership.mp3 loop in _ready() Done
scripts/entities/EnemyUnit.gd Ambient Alien Ship.wav loop in _ready() Done
scripts/entities/Asteroid.gd Asteroid Hit.mp3 in hit() Done
scripts/entities/Crystal.gd Crystal Pickup.mp3 in _on_body_entered() Done
scripts/entities/PowerUp.gd Power Up Pick UP.mp3 in _apply(); nuke add_child changed to call_deferred Done
scripts/entities/NukeExplosion.gd Nuke Exp.mp3 in _ready() Done
scripts/entities/Player.gd Shield sound in activate_shield(); velocity renamed to fly_vel to fix shadowing warning Done
scripts/menus/CharacterShop.gd HoloTill Chime.wav on successful ship purchase Done
Sounds/Game Music/ (folder) 18 tracks renamed from long descriptive names to Track 1.wav โ€“ Track 18.wav Done
Session 12 ยท Continued 14 June 2026
Achievements System, UI Consistency & Remaining SFX
๐Ÿ”Š

Remaining SFX Wired

Four new sound files identified in the Sounds folder were confirmed and wired in.

Sound File Trigger Script
error-pop.mp3 Failed ship purchase (insufficient coins) CharacterShop.gd ยท _on_tile_pressed() else branch
sci fi alarm.mp3 Next-wave red warning banner appears GameScene.gd ยท _show_wave_warning()
Level Complete.mp3 Player completes the level GameScene.gd ยท _level_complete()
achievement.mp3 Achievement unlocked popup fires GameManager ยท play_achievement() called by show_achievement()

The remaining audio files on disk (explosion-36210.mp3, futuristic-ship-ambience-494000.mp3, Powerup.mp3, PTModelSound_ID193943.mp3, Rank Up.wav, sci-fi-explosion-09-190268.mp3, science_fiction_spaceship...mp3, Spaceshuttle_Rocket_Steady_LV.mp3) were reviewed and confirmed as unused, with no game event to assign them to at this stage.

โœจ

Achievements Menu: UI Rebuild

Change The Achievements screen was rebuilt from scratch to match the Settings menu visual style. Consistency across all menus is a core UI goal; every screen should feel like it belongs to the same game.

What Changed

  • Removed the old StyleBoxFlat flat-blue panel and star parallax background
  • Added the same window_whole_alpha.png panel frame used in Settings
  • Same space background (background_01_parallax_01.png) and planet decorations (top-left and bottom-right) as Settings
  • “ACHIEVEMENTS” title in identical neuropol font, size 48, same colour as “SETTINGS”
  • Scrollable achievement list fills the frame content area
  • Achievement name font size increased 22โ†’28, description 16โ†’20, row height 72โ†’88px for readability

Navigation Consistency

Both Settings and Achievements previously used a centred CLOSE button at the bottom of the panel. This was replaced on both screens with a โ—€ BACK button in the top-left corner (offset 20, 20, identical to CharacterShop and LevelSelect). All four screens now share the same navigation pattern.

MainMenu: BtnAchievements Label Fix

Fix The hover label beneath the Achievements icon button was visually clipping the trailing “s” from “Achievements”, displaying “Achievement”. Root cause: the button is 200px wide and font size 22 left no margin for the 12-character word. Fixed by reducing the font size from 22 to 18, which gives the text comfortable room within the button bounds.

โœจ

Achievement Unlock Popup

New A global achievement popup system was added to GameManager. Calling show_achievement("Achievement Name") from anywhere in the game triggers a full animated notification without requiring any scene setup.

Behaviour

  • achievement.mp3 plays immediately on trigger
  • window_top.png bar slides up from off-screen bottom with a springy ease-out over 0.4 s
  • Text reads “ACHIEVEMENT UNLOCKED” for 1.5 s
  • Text swaps to the achievement name for a further 1.5 s
  • Bar slides back off-screen with ease-in over 0.35 s, then the CanvasLayer is freed

Architecture

  • Creates a temporary CanvasLayer at layer 50, renders above all game UI and menus
  • Popup Control is 1920ร—260px; window_top.png fills it proportionally
  • Label top offset 40px pushes text into the visual centre of the bar graphic
  • Font: neuropol 32px, colour Color(1.0, 0.95, 1.0) (soft white-pink)
  • All nodes self-clean via layer.queue_free at the end of the tween sequence

Debug

Press F6 during gameplay (debug builds only) to fire a test popup with the text “Asteroid Slayer”. Added alongside the existing F5 (Level Complete) shortcut in GameScene._unhandled_input().

## Usage โ€” call from anywhere:
GameManager.show_achievement("First Flight")
๐Ÿ“

Files Changed in Session 12

File Change Status
scripts/autoload/GameManager.gd Added play_achievement() and show_achievement(name), full animated popup with sound Done
scripts/game/GameScene.gd sci fi alarm.mp3 in _show_wave_warning(); Level Complete.mp3 in _level_complete(); F6 debug shortcut for achievement popup Done
scripts/menus/CharacterShop.gd error-pop.mp3 on failed purchase (insufficient coins) Done
scenes/menus/Achievements.tscn Full rebuild, matching Settings visual style; window_whole_alpha.png frame, space background, planet decorations, BtnBack top-left Done
scripts/menus/Achievements.gd Updated node paths for new scene structure; font sizes increased; row height increased Done
scenes/menus/Settings.tscn Replaced centred BtnClose with BtnBack in top-left (offset 20, 20) Done
scripts/menus/Settings.gd Updated btn_close node path to $BtnBack Done
scenes/menus/MainMenu.tscn BtnAchievements font size 22โ†’18 to fix “Achievement” label clipping Done
Session 13 ยท Continued 14 June 2026
Settings Controls Diagram & Bug Fixes
โœจ

Settings Controls Diagram

New A controls reference section was added to the Settings menu below the volume sliders, showing the keyboard layout for movement and firing.

Layout

The diagram is built entirely in code via _build_controls() called from _ready(), keeping the .tscn clean. A ControlsSection VBoxContainer node was added to Settings.tscn as the anchor point; its position was tuned in the editor to sit neatly beneath the sound rows.

Key(s) Action
W / A / S / D or โ†‘ โ† โ†“ โ†’ Move ship
Space Fire

Key Box Styling

Each key is rendered as a PanelContainer with a StyleBoxFlat: dark navy background (Color(0.02, 0.08, 0.20)), 2px cyan border, 6px corner radius, matching the existing UI colour scheme. The SPACE bar uses a wider minimum size (200px) to visually distinguish it as a bar key. The “or” connector between WASD and arrow clusters is rendered in a muted grey to reduce visual weight.

Why it’s invisible in the editor

The controls diagram is invisible in the Godot scene editor because _build_controls() runs in _ready(), which only executes at runtime. In the editor the ControlsSection node exists but is empty. This is the same behaviour as the slider styling; it is expected and not a bug.

๐Ÿ›

Settings BtnMainMenu Not Working

Fix The Main Menu button in the Settings screen had no effect when pressed. The BtnMainMenu node existed in Settings.tscn but was never referenced or connected in Settings.gd.

Fixed by adding the @onready reference and connecting it to a new _go_main_menu() function:

func _go_main_menu() -> void:
    _gm.play_ui_click()
    queue_free()
    get_tree().change_scene_to_file("res://scenes/menus/MainMenu.tscn")

The Settings overlay is freed before the scene change so it does not linger in memory.

๐Ÿ“‹

Planned Game Over Screen Redesign

The current Game Over panel lives inside HUD.tscn‘s CanvasLayer alongside the score widget, health bar, and pause button. Because they share the same layer these HUD elements render through the game over panel making the screen look cluttered.

Proposed Fix

Extract Game Over into its own dedicated scene (scenes/menus/GameOver.tscn) on a higher CanvasLayer, the same approach as how Settings overlays the game. The scene will use the same full-screen background style as all other menus (space background, planet decorations, window_whole_alpha.png frame). When game over fires:

  • HUD hides its live elements (LeftWidget, PauseButton)
  • GameOver.tscn is instantiated at a higher layer, rendering cleanly on top
  • Retry / Main Menu / Ships buttons sit inside the panel frame

This completely eliminates the see-through issue and brings the Game Over screen into visual consistency with all other menus. Implementation pending confirmation.

๐Ÿ“

Files Changed in Session 13

File Change Status
scenes/menus/Settings.tscn Added ControlsSection VBoxContainer node; position tuned in editor to sit below sound rows Done
scripts/menus/Settings.gd Added _build_controls(), _make_key(), _make_action_label() for programmatic keyboard diagram; wired BtnMainMenu to new _go_main_menu() Done
Session 14 ยท Continued 14 June 2026
Release Review & First-Run Tutorial
๐Ÿ“‹

Release Readiness Review

A comprehensive release readiness review was conducted with the goal of shipping on Steam and recovering the ยฃ100 listing fee. All dev logs (Sessions 1โ€“13) were audited to answer open questions and surface gaps. Key findings below.

Platform Decision

Steam first. Mobile (App Store / Google Play) only if Steam sales cover the additional listing fee. This defers AdMob, IAP, GDPR, and mobile joystick work entirely.

Ship Mechanics Confirmed

  • Viper: Fast nimble fighter: rotation 300, thrust 3000, max speed 750, friction 0.85, zero drift. Snappy turns, no slide.
  • BGS-1 / BGS-2: Slow capital ships: rotation 50, thrust 600, max speed 260, friction 0.90. Always-firing 3 front guns plus 6 side guns per side unlocked progressively by ship level. Side guns fire at 90ยฐ to heading. 13-blast death sequence.
  • Gap: BGS standalone level-up benefit not implemented. Viper level-up benefit not designed.

Level Design

99 levels are algorithmically scaled, not hand-authored. Wave composition (asteroid count, spawn interval) increases with level number. Enemy units enter at level 4; mothership at level 21; dual mothership at level 41. Mid-game (levels 30โ€“70) has not been validated for feel, and that is the biggest unknown.

Known Blockers Before Shipping

  • DEBUG_SHIP_SCALE must be set to false
  • DEBUG_AddCoins button must be removed from HUD
  • F6 achievement popup debug shortcut is gated behind OS.is_debug_build(), safe to leave
  • Gun barrel positions need visual verification per ship variant
  • Collision polygons: 18 ship variants still need hull geometry
  • Full clean end-to-end playthrough must happen before submission
  • Game Over screen see-through issue must be fixed
  • Steam store assets: capsule image, header, minimum 5 screenshots, trailer, description, tags: none exist yet
  • Privacy policy required by Steam
โœจ

First-Run Tutorial Overlay

New A one-time tutorial overlay was added to GameScene. It appears automatically the first time a new player starts Level 1 and is never shown again after dismissal. Tested and confirmed working.

Behaviour

  • Shows only on current_level == 1 and only when tutorial_seen == false, never shown in endless mode
  • Blocks wave start until the player actively dismisses it (any key or mouse button)
  • On dismiss: sets tutorial_seen = true, saves to disk, calls _start_wave()
  • After dismissal the flag persists in the save file, so the overlay never appears again for that player

Visual Design

The overlay uses the same key-box visual language as the Settings controls diagram (dark navy background, 2px cyan border, 6px corner radius) so the style is consistent across both places a player sees the controls.

  • Dark semi-transparent backdrop (Color(0, 0, 0, 0.72)) covers the full screen and blocks mouse input passing through
  • Centred card panel with dark navy background and cyan border
  • “HOW TO PLAY” title in neuropol 42px
  • WASD cluster + “or” + arrow cluster โ†’ “Move”
  • Wide SPACE bar โ†’ “Fire”
  • Wide ESC bar โ†’ “Pause”
  • “PRESS ANY KEY TO BEGIN” blinking on a looping tween (opacity 1.0 โ†’ 0.2 โ†’ 1.0, 0.55 s each leg)

Architecture

  • tutorial_seen: bool added to GameManager persistent data, saved and loaded under settings/tutorial_seen in user://save.cfg
  • _tutorial_layer: CanvasLayer var on GameScene, non-null while overlay is active
  • Overlay lives at layer 30, above the game and below the achievement popup (layer 50)
  • _show_tutorial_overlay() builds all nodes programmatically using two helpers: _tut_make_key() and _tut_make_action_label()
  • _dismiss_tutorial() frees the layer, sets the flag, saves, then calls _start_wave()
  • _unhandled_input() catches any key/mouse press while _tutorial_layer != null, calls _dismiss_tutorial(), and returns, so pause/debug input is unaffected after dismissal
Existing saves: The tutorial_seen key is absent from all existing save files. load_game() uses cfg.get_value("settings", "tutorial_seen", false), so all existing players will see the tutorial once on their next session, exactly the intended behaviour.
๐Ÿ“

Files Changed in Session 14

File Change Status
scripts/autoload/GameManager.gd Added tutorial_seen: bool = false; saved and loaded under settings/tutorial_seen Done
scripts/game/GameScene.gd Added _tutorial_layer var; conditional tutorial check in _ready(); _show_tutorial_overlay(), _tut_make_key(), _tut_make_action_label(), _dismiss_tutorial(); tutorial dismiss handling in _unhandled_input() Done
Session 15 ยท Continued 14 June 2026
Game Over Screen Redesign
โœจ

Game Over Screen as a Dedicated Scene

NewChange The Game Over panel was extracted from HUD.tscn into its own dedicated scene, scenes/menus/GameOver.tscn. This completely resolves the HUD see-through issue where the score widget, health bar, and pause button were visible behind the panel.

Root Cause of the Old Problem

The old GameOverPanel and GameOverOverlay were siblings of the HUD score/health elements inside the same CanvasLayer. Because they shared a layer, the semi-transparent overlay could never fully hide the other elements; they all rendered at the same depth.

Solution

The Game Over screen is now instantiated as its own CanvasLayer at layer 15, above the HUD layer, from GameScene._on_player_died(). The full-screen space background (background_01_parallax_01.png) is fully opaque, so no HUD elements are visible underneath. No overlay hack required.

Visual Design

Matches all other menus: identical background, planet decorations, and window_whole_alpha.png panel frame:

  • Full-screen background_01_parallax_01.png, same as Settings and Achievements
  • Semi-transparent Dimmer ColorRect added by user in editor for depth
  • Planet decorations top-left and bottom-right
  • window_whole_alpha.png frame centred (351โ†’1597, 16โ†’1041, identical to Settings)
  • “GAME OVER” title, Score label, Best label inside the frame
  • Continue button (hidden by default, shown only if the player has โ‰ฅ 100 coins on desktop)
  • Retry button
  • Main Menu icon button, top-right
  • Settings icon button, bottom-right (added in the editor)
  • Ships icon button, bottom-left

Architecture

# GameScene._on_player_died()
func _on_player_died() -> void:
    is_game_over = true
    _gm.play_sfx(preload("res://Sounds/Spaceship-large-underwater-explosion.mp3"))
    await get_tree().create_timer(1.0).timeout
    var go: CanvasLayer = load("res://scenes/menus/GameOver.tscn").instantiate()
    add_child(go)
    go.setup(_gm.session_score, _gm.best_score, _gm.coins)
    get_tree().paused = true

GameOver.gd has process_mode = PROCESS_MODE_ALWAYS so buttons remain interactive while the scene tree is paused. All button handlers unset get_tree().paused before any scene change.

HUD Cleanup

All game over code was removed from HUD.tscn and HUD.gd: GameOverPanel, GameOverOverlay, all their child nodes, the five StyleBox sub-resources, all @onready refs, show_game_over(), and the four button handler functions. HUD is now purely a gameplay overlay: score, health, rank badge, pause button, mobile controls.

๐Ÿ“

Files Changed in Session 15

File Change Status
scenes/menus/GameOver.tscn New scene: space background, dimmer, planet decorations, window_whole_alpha.png frame, GAME OVER title, Score/Best labels, Continue/Retry/MainMenu/Settings/Ships buttons. Layout adjusted by user in editor. Done
scripts/menus/GameOver.gd New script: setup(score, best, coins), handlers for all five buttons including Settings overlay and Ships navigation Done
scenes/ui/HUD.tscn Removed GameOverOverlay, GameOverPanel and all children; removed all related StyleBox sub-resources and ext_resource references Done
scripts/ui/HUD.gd Removed all game_over_* node refs, show_game_over(), four button handlers, CONTINUE_COST const; HUD is now gameplay-only Done
scripts/game/GameScene.gd _on_player_died() now instantiates GameOver.tscn, calls setup(), sets get_tree().paused = true Done
Session 16 ยท Continued 14 June 2026
Achievement Unlock Logic
โœจ

Achievement System: Full Implementation

New All 12 achievements are now fully wired. The popup and sound already existed; this session added the unlock logic, lifetime stat tracking, persistence, and live display in the Achievements screen.

Design Decisions Confirmed

  • Coin Hoarder triggers on lifetime coins earned (not current balance), so spending does not reset progress
  • Asteroid Slayer counts all asteroid destructions including split fragments (every _on_asteroid_destroyed event)
  • Endless Pilot: 10 waves in a single consecutive endless run; counter resets on death/scene reload

New Data in GameManager

Four lifetime counters added to persistent data, saved under a new [stats] section in user://save.cfg:

Variable Drives Incremented In
total_asteroids_destroyed Asteroid Slayer (โ‰ฅ 100) GameScene._on_asteroid_destroyed()
total_crystals_collected Crystal Collector (โ‰ฅ 50) Crystal._on_body_entered()
total_nukes_used Nuke ’em (โ‰ฅ 10) GameManager.use_nuke()
total_coins_earned Coin Hoarder (โ‰ฅ 5,000) GameManager.add_coins()

A fifth new field, unlocked_achievements: Dictionary, is saved under [achievements]. It is keyed by achievement name (string) with value true. Existing save files default to an empty dictionary, so all achievements start locked for current players.

Central Unlock Method

func unlock_achievement(achievement_name: String) -> void:
    if unlocked_achievements.get(achievement_name, false):
        return   # already unlocked โ€” never fire twice
    unlocked_achievements[achievement_name] = true
    save_game()
    show_achievement(achievement_name)

The one-time guard means it is safe to call on every score add, every coin add, every wave; it only acts the first time the condition is met.

Trigger Map

Achievement Condition Trigger Location
First Flight completed_level == 1 GameScene._level_complete()
Asteroid Slayer total_asteroids_destroyed โ‰ฅ 100 GameScene._on_asteroid_destroyed()
Crystal Collector total_crystals_collected โ‰ฅ 50 Crystal._on_body_entered()
Coin Hoarder total_coins_earned โ‰ฅ 5,000 GameManager.add_coins()
Promoted! current_rank โ‰ฅ 4 (Officer 1) GameManager._check_rank_up()
Ship Upgrade new_level โ‰ฅ 1 (X2) GameScene._on_ship_leveled_up()
Max Firepower new_level โ‰ฅ 2 (X3) GameScene._on_ship_leveled_up()
Fleet Commander all owned_ships == true GameManager.buy_ship()
Sharpshooter player_health == 3 on level complete GameScene._level_complete()
Nuke ’em total_nukes_used โ‰ฅ 10 GameManager.use_nuke()
Endless Pilot _endless_waves_completed โ‰ฅ 10 GameScene._next_wave_or_complete()
High Scorer session_score โ‰ฅ 100,000 GameManager.add_score()

Achievements Screen: Live Data

Achievements.gd was updated to read unlock state from GameManager.unlocked_achievements at build time. The PLACEHOLDER_ACHIEVEMENTS const (which hardcoded "unlocked": false for all entries) was replaced with ACHIEVEMENTS, names and descriptions only. Each row now checks _gm.unlocked_achievements.get(name, false) to determine its star colour and label colour. Locked achievements show a hollow grey star (โ˜†) and white-blue name; unlocked achievements show a filled gold star (โ˜…) and gold name.

Testing deferred to next session. F6 debug shortcut remains available in debug builds to manually trigger the popup. To test a specific achievement, temporarily set its counter to one below the threshold in _ready() and trigger the relevant action.
๐Ÿ“

Files Changed in Session 16

File Change Status
scripts/autoload/GameManager.gd Added 4 lifetime stat counters + unlocked_achievements dict; unlock_achievement() method; checks wired into add_coins, use_nuke, _check_rank_up, buy_ship, add_score; all new fields saved/loaded Done
scripts/game/GameScene.gd Asteroid counter + Asteroid Slayer in _on_asteroid_destroyed; First Flight + Sharpshooter in _level_complete; Ship Upgrade + Max Firepower in _on_ship_leveled_up; Endless Pilot in _next_wave_or_complete Done
scripts/entities/Crystal.gd Crystal counter increment + Crystal Collector check in _on_body_entered Done
scripts/menus/Achievements.gd Replaced PLACEHOLDER_ACHIEVEMENTS with ACHIEVEMENTS (no unlocked field); added _gm ref; _build_list() reads live unlock state from _gm.unlocked_achievements Done
๐Ÿ“‹

Open Items

Item Notes Status
Insufficient funds sound Wired: error-pop.mp3 plays on failed shop purchase Done
First-run tutorial overlay Implemented and tested: HOW TO PLAY card blocks wave start, dismissed by any key, one-time flag saved to disk Done
Game Over screen redesign Extracted into GameOver.tscn at layer 15 with full menu-style background; HUD see-through issue eliminated Done
Achievement system All 12 achievements wired with live tracking and persistence; testing deferred to next session Testing
science_fiction_spaceship_or_rocket_fly_by_fast_001.mp3 Confirmed unused, no game event assigned Unused
PTModelSound_ID193943.mp3 Confirmed unused, purpose unknown Unused
Rank Up.wav Replaced by Firefly level-up sound, now unused Unused
Shield scale per ship Per-ship tuning in editor Tuning
Collision polygons 18 polygons need geometry authored Pending
BGS rocket mechanic / Viper level-up benefit Gameplay feature, not yet designed Pending
Gun barrel positions Visual verification needed per ship variant Pending
Full end-to-end playthrough Must happen before Steam submission; no clean run on record Before ship
Steam store assets Capsule image, header, min 5 screenshots, trailer, description, tags: none exist Before ship
Privacy policy Required by Steam Before ship
Mobile joystick wiring Deferred: Steam first Deferred
Coin shop / IAP / AdMob / GDPR / leaderboards Deferred: Steam first Deferred
DEBUG_SHIP_SCALE = false Must be set before shipping Before ship
Remove DEBUG_AddCoins button Must be removed before shipping Before ship