Gas bombers consume gas from the fleet hold when firing #60

Closed
opened 2026-08-15 20:30:02 +00:00 by metaheavens · 0 comments
Owner

Gas bomb shots are currently free. Make each shot cost gas from the fleet hold (GameState.fleet_inventory), not from the bank - the fleet has to be carrying its own ammunition.

Scope

  1. const GAS_PER_SHOT: int on GasBombController (suggest 5).
  2. In GasBombController._fire(): before component.consume_shot(COOLDOWN), require GameState.fleet_resource(GameState.RESOURCE_GAS) >= GAS_PER_SHOT and withdraw it. If it cannot be paid, do not consume the bomber's cooldown, do not launch, and cancel the aim cleanly (_cancel_aim()).
  3. Extend the button's enabled rule, currently button.enabled = _next_ready_bomber() != null, to also require affordability - a control the player can press to no effect is exactly what IconButton's "greyed but still pressable, and explains itself" convention exists to avoid.
  4. Give the refusal a visible reason using the existing status-label idiom (see how BuildController explains an unaffordable build). No new modal.

Acceptance criteria

  • Firing drains exactly GAS_PER_SHOT from the fleet hold.
  • With insufficient fleet gas the button is disabled, pressing it explains why, and no projectile is created and no cooldown is consumed.
  • Gas sitting in the bank does not enable firing - only the fleet hold counts.
  • Existing gas bomber tests still pass, updated to stock the fleet hold first.

Tests

New tests/test_gas_bomber_ammo.gd (bash tests/run_tests.sh bomber), plus the existing test_gas_bomber.gd and test_gas_bomber_cooldowns.gd, which will need fleet gas added to their setup.

Repo notes (read before starting)

  • Godot 4.7.1, portrait 720x1280, touch UI. Project dir: /storage/emulated/0/GodotProjects/SpaceshipTest.
  • After editing any project file, run touch on it. Android FUSE does not bump mtime under ~/storage/shared, and Godot's import cache will silently keep serving the old version, making a correct fix look like it did nothing.
  • Headless tests: bash tests/run_tests.sh <substring>; full suite with no argument. Tests are SceneTree scripts printing TEST_NAME: PASS / TEST_NAME: FAIL (reason) and calling quit(0|1) - copy the shape of tests/test_capture_points.gd.
  • Real screenshots: bash tests/screenshot.sh <res://Scene.tscn> <out.png> <frames> 720x1280. Use few frames (~15); the software rasterizer is slow. --headless renders nothing at all, so it cannot validate visuals.
  • Drawing goes through NeonDraw / NeonPiece; do not introduce a different visual idiom.
  • The repo has no type switches (tickets #31-#35): behaviour comes from *Definition resources plus capability component scenes. Do not add if type == "..." branches.
  • There is no persistence layer (no FileAccess, no user://). GameState is an in-memory autoload. Do not invent a save system.
  • Commit this ticket separately and run the affected test groups plus the full suite before pushing.

Traps that have already cost hours on this codebase

  1. Never put a top-level const X = preload(...) in a test file that is itself the -s entry point. It evaluates during that file's own early compilation, which can run before autoloads are registered; any script it pulls in that references GameState inside a function body then fails to compile with Identifier not found: GameState. Use var X: Script plus load() inside _initialize() instead. See the header comment in tests/test_capture_points.gd.
  2. Assigning a plain [] to a typed Array[Resource] export from another script fails at runtime - the compiler only sees the target as Resource and cannot confirm the element type. Leave the declared default alone, or build the typed array inside the owning script.
  3. Fleet.spawn_ships(0, ...) does not empty the fleet - it falls back to one default interceptor by design. Clear fleet.ships explicitly if a test or preview needs genuinely zero ships.
  4. Setting ship.position without also setting ship.move_target lets Ship._physics_process steer it straight back toward the stale target within a few frames.
  5. Physics ticks can run ahead of a SceneTree test's own _process() clock under device load, even with --fixed-fps 60. Gate waits on the system's own state (a component's timer or flag), not on elapsed >= duration.
  6. A node that queue_free()s itself (projectiles, shells) must not be re-read inside a per-frame wait condition - cache the scalar you need before the wait.
  7. Preview scenes for screenshots are added to the tree from inside _screenshot_tool.gd's _initialize(), so their _ready() races the game scene's own _ready(). Do setup on the first _process() tick, and get_tree().paused = true once everything is placed if the scene must hold still.
Gas bomb shots are currently free. Make each shot cost gas **from the fleet hold** (`GameState.fleet_inventory`), not from the bank - the fleet has to be carrying its own ammunition. ### Scope 1. `const GAS_PER_SHOT: int` on `GasBombController` (suggest 5). 2. In `GasBombController._fire()`: before `component.consume_shot(COOLDOWN)`, require `GameState.fleet_resource(GameState.RESOURCE_GAS) >= GAS_PER_SHOT` and withdraw it. If it cannot be paid, do **not** consume the bomber's cooldown, do not launch, and cancel the aim cleanly (`_cancel_aim()`). 3. Extend the button's enabled rule, currently `button.enabled = _next_ready_bomber() != null`, to also require affordability - a control the player can press to no effect is exactly what `IconButton`'s "greyed but still pressable, and explains itself" convention exists to avoid. 4. Give the refusal a visible reason using the existing status-label idiom (see how `BuildController` explains an unaffordable build). No new modal. ### Acceptance criteria - Firing drains exactly `GAS_PER_SHOT` from the fleet hold. - With insufficient fleet gas the button is disabled, pressing it explains why, and no projectile is created and no cooldown is consumed. - Gas sitting in the **bank** does not enable firing - only the fleet hold counts. - Existing gas bomber tests still pass, updated to stock the fleet hold first. ### Tests New `tests/test_gas_bomber_ammo.gd` (`bash tests/run_tests.sh bomber`), plus the existing `test_gas_bomber.gd` and `test_gas_bomber_cooldowns.gd`, which will need fleet gas added to their setup. ### Repo notes (read before starting) - Godot 4.7.1, portrait 720x1280, touch UI. Project dir: `/storage/emulated/0/GodotProjects/SpaceshipTest`. - **After editing any project file, run `touch` on it.** Android FUSE does not bump mtime under `~/storage/shared`, and Godot's import cache will silently keep serving the old version, making a correct fix look like it did nothing. - Headless tests: `bash tests/run_tests.sh <substring>`; full suite with no argument. Tests are `SceneTree` scripts printing `TEST_NAME: PASS` / `TEST_NAME: FAIL (reason)` and calling `quit(0|1)` - copy the shape of `tests/test_capture_points.gd`. - Real screenshots: `bash tests/screenshot.sh <res://Scene.tscn> <out.png> <frames> 720x1280`. Use few frames (~15); the software rasterizer is slow. `--headless` renders nothing at all, so it cannot validate visuals. - Drawing goes through `NeonDraw` / `NeonPiece`; do not introduce a different visual idiom. - The repo has **no type switches** (tickets #31-#35): behaviour comes from `*Definition` resources plus capability component scenes. Do not add `if type == "..."` branches. - There is **no persistence layer** (no `FileAccess`, no `user://`). `GameState` is an in-memory autoload. Do not invent a save system. - Commit this ticket separately and run the affected test groups plus the full suite before pushing. ### Traps that have already cost hours on this codebase 1. **Never put a top-level `const X = preload(...)` in a test file that is itself the `-s` entry point.** It evaluates during that file's own early compilation, which can run before autoloads are registered; any script it pulls in that references `GameState` inside a function body then fails to compile with `Identifier not found: GameState`. Use `var X: Script` plus `load()` inside `_initialize()` instead. See the header comment in `tests/test_capture_points.gd`. 2. **Assigning a plain `[]` to a typed `Array[Resource]` export from another script fails at runtime** - the compiler only sees the target as `Resource` and cannot confirm the element type. Leave the declared default alone, or build the typed array inside the owning script. 3. **`Fleet.spawn_ships(0, ...)` does not empty the fleet** - it falls back to one default interceptor by design. Clear `fleet.ships` explicitly if a test or preview needs genuinely zero ships. 4. **Setting `ship.position` without also setting `ship.move_target`** lets `Ship._physics_process` steer it straight back toward the stale target within a few frames. 5. **Physics ticks can run ahead of a `SceneTree` test's own `_process()` clock** under device load, even with `--fixed-fps 60`. Gate waits on the system's own state (a component's timer or flag), not on `elapsed >= duration`. 6. **A node that `queue_free()`s itself** (projectiles, shells) must not be re-read inside a per-frame wait condition - cache the scalar you need before the wait. 7. **Preview scenes for screenshots** are added to the tree from inside `_screenshot_tool.gd`'s `_initialize()`, so their `_ready()` races the game scene's own `_ready()`. Do setup on the first `_process()` tick, and `get_tree().paused = true` once everything is placed if the scene must hold still.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
metaheavens/spaceship#60
No description provided.