Asteroids Destroyer Dev Log – 5 May 2026

Asteroids Destroyer โ€” Development Journal

Dev Log โ€” 5 May 2026

๐Ÿ“… Sessions 4โ€“7
๐ŸŽฎ Godot 4.6
๐Ÿš€ Ship Architecture Overhaul
โšก Performance
๐Ÿ› Scaling Fixes
๐Ÿš€

Day Overview: Four Sessions

A day of deep technical work across four sessions. The day started with adding mothership colour variants and performance optimisation, then pivoted to a major ship architecture overhaul that spanned three iterations and three sessions, ending with the correct dynamic per-variant scaling solution that accounts for the 8:1 range in ship texture sizes.

Session Focus Files
Session 4 15 mothership colour variants with dynamic scaling; 5 performance optimisations 5 files
Session 5 Ship architecture overhaul: 18 per-variant ship scenes, @tool editor preview, 2D scene path array 22 files
Session 6 Bug fix: ships 2ร— too large (missing ShipBody scale); bug fix: thrusters always visible 4 files
Session 7 Dynamic per-ship scaling: replaced fixed 0.5 scale with texture-driven calculation per variant 2 files
Session 4 ยท Mothership Variants & Performance
๐Ÿ›ธ

Mothership Colour Variants: 15 Variants, Random Spawn

14 new mothership sprites were added alongside the original, giving 15 total colour variants. Each time a mothership spawns it picks one at random, so no two waves look the same. The three source sizes (230ร—230, 370ร—385, 1024ร—1024) would render at wildly different sizes with a fixed scale, so the same dynamic scaling pattern used across the codebase was applied.

const DISPLAY_SIZE: float = 150.0  # target longest-side size in px

var tex: Texture2D = load(TEXTURE_PATHS[randi() % TEXTURE_PATHS.size()])
if tex:
    _sprite.texture = tex
    var longest: float = maxf(tex.get_width(), tex.get_height())
    var s: float = DISPLAY_SIZE / longest if longest > 0.0 else 1.0
    _sprite.scale = Vector2(s, s)

To adjust mothership size: change DISPLAY_SIZE in EnemyMothership.gd. To add variants: append the path to TEXTURE_PATHS.

โšก

Performance Optimisation: 5 Key Fixes

A full audit of the gameplay-hot scripts was run. Five issues identified and fixed, ranked by impact:

1. Cache GameManager Reference in Player.gd

_physics_process called get_node_or_null("/root/GameManager") on every frame (60+ times/sec). A _gm: Node instance var is now populated once in _ready() and reused everywhere. All four separate get_node* calls replaced.

2. Shared Texture Cache in EnemyBullet.gd

Every bullet called load(path) for all 6 animation frames in _ready(). Two static var arrays (_small_tex_cache and _big_tex_cache) are now shared across all instances, loaded only once on the first instantiation.

3. Preloaded Font in GameScene.gd

Both _show_wave_warning() and _show_sector_banner() called load(neuropol.ttf) each time they ran. Replaced with a class-level const _NEUROPOL: FontFile = preload(...), at zero runtime cost.

4. Cached Edge Spawn Points in GameScene.gd

_fixed_edge_point() allocated a typed Array[Vector2] with 8 entries on every call (36+ per wave). The 8 spawn points are now computed once in _ready() and stored in _edge_points. The function is now a single-line array index.

5. Crystal Double-Collect Guard

A _collected: bool = false flag was added. If two physics frames fire collision callbacks simultaneously (possible at high frame-rate peaks), the second call returns immediately before touching the GameManager. Signal disconnect remains as secondary safety.

Remaining Deferred Audit Items

Issue File Suggested Fix
Asteroid dodge loop every frame EnemyUnit.gd ยท _get_asteroid_dodge() Throttle to once per 0.15s using a timer, or use Area2D overlap detection
Enemy movement in _process EnemyUnit.gd, EnemyMothership.gd Move position logic to _physics_process for fixed-timestep accuracy
Session 5 ยท Ship Architecture Overhaul
๐Ÿ—๏ธ

Ship Architecture Overhaul: 18 Per-Variant Scenes + @tool Preview

The root problem: it was impossible to visually edit player ship collision shapes and gun barrel positions in the Godot editor because ship sprites load at runtime from GameManager, so the editor showed a blank Sprite2D. The session went through three iterations to arrive at a clean, warning-free solution.

Warning that drove iteration: “CollisionPolygon2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc.” That was received when collision nodes were placed inside the Node2D-rooted ship scenes.

Final Architecture: Two-Layer Split

Layer 1: 18 Ship Scenes (pure visuals, no warnings): Located in scenes/entities/ships/. Each is a Node2D with: Sprite2D (texture baked in), ThrusterCenter/Left/Right (visible=false), GunBarrel/GunBarrelLeft/GunBarrelRight Marker2Ds.

Series Scenes
CX16, DKO, WO84, CP2, PTM X1, X2, X3 (3 scenes each = 15 total)
Viper, BGS-1, BGS-2 Standalone (1 scene each = 3 total)

Layer 2: Player.tscn (physics shell): Contains only ThrustAudio, ShipBody placeholder Node2D (ship scene instanced at runtime), and 18 Col_* CollisionPolygon2D nodes as direct CharacterBody2D children.

Player.gd: Key Changes

  • @tool editor preview:@export_range(-1,7) var editor_ship_series and @export_range(0,2) var editor_ship_level. Changing either slider shows the correct ship sprite in the editor without affecting runtime.
  • SHIP_SCENE_PATHS: Changed from flat Array[String] to 2D array indexed [series][level].
  • _apply_ship_setup(): Loads SHIP_SCENE_PATHS[series][level] directly. Old get_ship_texture() call removed; the correct sprite is baked into each variant scene.
  • Ship node refs: Changed from @onready to plain vars populated after instantiation, with null guards throughout.

Editor Workflows

Gun barrels and thrusters: Open the relevant ship scene (e.g. Ship_CX16_X2.tscn). Sprite is already visible. Drag marker nodes to the correct positions. Save.

Collision polygons: Open Player.tscn. Set editor_ship_series and editor_ship_level in Inspector and the sprite appears live. Select the matching Col_* node and drag vertices. Set editor_ship_series back to -1 when done.

Sessions 6 & 7 ยท Scaling Bugs Fixed
๐Ÿ›

Ship Scaling: Two Bugs Found and Fixed

Bug 1 (Session 6): Ships Rendering 2ร— Too Large

The original Player.tscn had scale = Vector2(0.5, 0.5) on its Sprite2D. New ship scenes defaulted to Vector2(1,1), so ships appeared at twice the intended size in-game.

Session 6 Fix: Applied scale = Vector2(0.5, 0.5) once to the ShipBodyNode2D in Player.tscn, which fixes all 18 scenes simultaneously. The @tool preview sprite also inherits the scale so collision polygon editing shows the ship at the correct in-game size.

Bug 2 (Session 6): Thrusters Always Visible

Some ship scenes had thruster nodes temporarily made visible during editor positioning then saved without re-hiding. Godot 4 never writes visible = true (it’s the default), so visible = false was missing from those files.

Fixed by adding visible = false to: Ship_CX16_X1.tscn (ThrusterCenter), Ship_CX16_X2.tscn (ThrusterLeft, ThrusterRight), Ship_WO84_X3.tscn (all three thrusters).

Rule: If a thruster is made visible in the editor to check its position, always untick Visible in Inspector before saving. Thruster visibility is controlled entirely at runtime by Player.gd, so they must be hidden by default in the scene file.

Bug 3 (Session 7): Fixed Scale Fails for Large-Texture Ships

The Session 6 fix (scale = 0.5 on ShipBody) works for CX16, DKO, and WO84 (all 160ร—160 px textures), but CP2 is 609px, PTM X2 is 1178px, Viper is 957px, BGS ships are 1057px, so at 0.5 scale these would render 4โ€“7ร— too large.

Ship Longest Axis Old Scale (0.5) Rendered Size New Scale
CX16 / DKO 160 px 0.500 80 px โœ“ 0.500
CP2 609 px 0.500 305 px โœ— 0.131
PTM X2 1178 px 0.500 589 px โœ— 0.068
Viper 957 px 0.500 479 px โœ— 0.084
BGS-1 / BGS-2 1057 px 0.500 529 px โœ— 0.076
โœ…

Dynamic Scale from Texture Dimensions

The fixed scale was replaced with a runtime calculation: each ship is scaled so its longest axis always renders at exactly 80 px on screen, regardless of original texture dimensions. Same pattern used in Crystal.gd and EnemyMothership.gd.

const SHIP_DISPLAY_SIZE: float = 80.0

# Inside _apply_ship_setup(), after caching sprite ref:
if sprite and sprite.texture:
    var longest: float = maxf(float(sprite.texture.get_width()),
                              float(sprite.texture.get_height()))
    if longest > 0.0:
        var s: float = SHIP_DISPLAY_SIZE / longest
        _ship_body.scale = Vector2(s, s)

The same calculation is applied in _editor_update_preview() so the @tool viewport preview also scales correctly, so collision polygon vertices sit on the ship at the exact size they will appear in-game.

CX16 collision polygons are unaffected. Dynamic calculation produces scale 0.5 for 160ร—160 px textures, identical to the old fixed value. All CX16 polygon vertices tuned in earlier sessions remain correct.

Ship Tuning Status After All Sessions

Ship Scene Gun Barrels Thrusters Collision Polygon
Ship_CX16_X1.tscn Done Done Done
Ship_CX16_X2.tscn Done Done Done
Ship_CX16_X3.tscn Pending Pending Done
Ship_WO84_X3.tscn Done Done Pending
Ship_DKO / WO84 X1–X2 / CP2 / PTM Pending Pending Pending
Ship_Viper / BGS1 / BGS2 Pending Pending Pending
๐Ÿ“‹

All Files Changed on 5 May 2026

File Change Session
scripts/entities/EnemyMothership.gd CHANGE TEXTURE_PATH โ†’ TEXTURE_PATHS (15 entries); DISPLAY_SIZE constant; dynamic scale from texture dimensions S4
scripts/entities/Player.gd OPT _gm cached; 4ร— get_node* removed; CHANGE @tool; editor preview exports; SHIP_SCENE_PATHS 2D array; _apply_ship_setup() loads exact variant; SHIP_DISPLAY_SIZE const; dynamic scale block; editor preview scaling S4โ€“S7
scripts/entities/EnemyBullet.gd OPT Static _small_tex_cache / _big_tex_cache; textures loaded once per class S4
scripts/game/GameScene.gd OPT _NEUROPOL preloaded; _edge_points cached; _fixed_edge_point() simplified; FIX revive_player() โ†’ player.sprite.visible instead of get_node(“Sprite2D”) S4โ€“S5
scripts/entities/Crystal.gd OPT _collected bool guard prevents double-award S4
scenes/entities/Player.tscn CHANGE Stripped to physics shell; 18 Col_* collision nodes; ShipBody placeholder; removed fixed scale S5โ€“S7
scenes/entities/ships/Ship_CX16_X1/X2/X3.tscn NEW CX16: 3 variant scenes (X1 thruster fix applied) S5โ€“S6
scenes/entities/ships/Ship_DKO_X1/X2/X3.tscn NEW DKO: 3 variant scenes S5
scenes/entities/ships/Ship_WO84_X1/X2/X3.tscn NEW WO84: 3 variant scenes (X3 thruster fix applied) S5โ€“S6
scenes/entities/ships/Ship_CP2_X1/X2/X3.tscn NEW CP2: 3 variant scenes S5
scenes/entities/ships/Ship_PTM_X1/X2/X3.tscn NEW PTM: 3 variant scenes S5
scenes/entities/ships/Ship_Viper.tscn NEW Viper standalone S5
scenes/entities/ships/Ship_BGS1.tscn NEW BGS-1 standalone S5
scenes/entities/ships/Ship_BGS2.tscn NEW BGS-2 standalone S5
Old 8 shared-series ship scenes REMOVED Deleted; replaced by 18 per-variant scenes S5
๐Ÿ“‹

Open Items

Item Priority Notes
Add all 12 sound effects High SOUND NEEDED comments mark every trigger point in the codebase
Tune player ship collision polygons (ร—18) High Open Player.tscn, use Editor Preview sliders, edit Col_* vertices
Tune GunBarrelLeft / GunBarrelRight per variant High Open each ship scene, drag markers to match wing gun art
Enemy movement โ†’ _physics_process Medium EnemyUnit + EnemyMothership position logic is in _process (frame-rate dependent)
Throttle asteroid dodge loop Medium EnemyUnit._get_asteroid_dodge() scans all asteroids every frame; add a 0.15s throttle or Area2D detection
Wire real achievement data Medium 12 slots are placeholder (unlocked = false). Connect to GameManager progress.
Standalone ship level-up (rockets) Medium Viper, BGS-1, BGS-2 gain no level-up benefit
Level Complete visual polish Medium SHIP_DISPLAY_CONFIG offsets/scales need tuning per ship
Tune asteroid collision polygons (ร—12) Medium A1 traced. A3โ€“A13 use default octagon.
Mobile joystick wiring Medium MobileControls visible=false; input not connected
Audio buses Medium Create Master โ†’ Music and SFX buses once sounds are added
Wave warning text Low Still says “Asteroids Incoming” on enemy-only waves
Planet positions BG_02โ€“BG_13 Low Editor adjustment needed per background scene
Coin shop / IAP Low Not started
AdMob, Vungle, AppLovin Low SDK integration pending
GDPR consent Low Required before store submission
Google Play Games leaderboards Low Not started
Android / iOS export signing Low Export templates not configured