Capture points and the planet conquest loop #43

Closed
opened 2026-08-15 11:16:19 +00:00 by metaheavens · 1 comment
Owner

Part of #40. Depends on the conquest-state ticket and the planet-setups ticket.

Several points on the surface must be held for some time with no enemies around; once all are captured the planet is conquered and an extractor becomes buildable.

Scope

  1. CapturePoint.tscn + CapturePoint.gd (Area2D):

    • @export var point_index: int (its index within the setup - this is what GameState keys on)
    • @export var capture_radius: float = 220.0
    • @export var capture_time: float = 8.0
    • @export var enemy_clear_radius: float = 700.0
    • @export var drain_rate: float = 0.5 (fraction of fill rate)
    • States: neutral / blocked / capturing / captured.
    • Progress rises only while at least one node in group player_ships is within capture_radius and no live node in group enemies is within enemy_clear_radius. Otherwise it drains at drain_rate - a hard reset is too punishing to read on a phone.
    • On reaching 1.0: captured, permanent for the run, calls GameState.mark_point_captured(GameState.planet_id, point_index).
    • Drawing: outer ring plus a filling progress arc, in the NeonDraw idiom - GasBombController._draw_ring is the reference for ring rendering, and VesselGasBomberComponent's hull fill is the reference for "fills up as it progresses". Distinct colours per state so blocked (enemies nearby) is obvious without text.
  2. PlanetSetupHost (from the setups ticket) instantiates one CapturePoint per setup.capture_points entry with its index, calls GameState.register_capture_points(planet_id, count), and marks already-captured indices as captured immediately so re-landing on a partly taken planet restores progress.

  3. HUD on PlanetSurface.tscn's UI CanvasLayer, near PlanetNameLabel and styled the same: "CAPTURED 2/3", or "CONQUERED" once done. It must update live as points complete.

  4. On the final point, a brief on-screen confirmation (a label fade is enough - no new modal).

Out of scope

New enemy types (the enemy tickets), the world map showing conquered planets.

Acceptance criteria

  • A ship parked inside a point with no enemies present captures it in approximately capture_time seconds, and GameState.captured_point_count reflects it.
  • With a node in group enemies inside enemy_clear_radius, progress does not increase.
  • Moving out of capture_radius drains progress gradually; it does not drop to zero instantly, and an already-captured point never reverts.
  • Capturing every point makes GameState.is_planet_conquered(planet_id) true, and back in space the extractor option over that planet is enabled.
  • Exiting to space and landing again keeps captured points captured and the HUD count correct.

Tests

tests/test_capture_points.gd (bash tests/run_tests.sh capture), plus a 720x1280 screenshot of res://PlanetSurface.tscn confirming the ring, its progress arc and the HUD count read clearly at phone size.

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_gas_bomber_cooldowns.gd.
  • Real screenshots: bash tests/screenshot.sh <res://Scene.tscn> <out.png> <frames> 720x1280. Use few frames (~10-20); 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 deliberately has no type switches left (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:// anywhere outside this ticket's explicit scope). GameState is an in-memory autoload. Do not invent a save system.
  • Commit each ticket separately and run the full suite before pushing.
Part of #40. Depends on the conquest-state ticket and the planet-setups ticket. Several points on the surface must be held for some time with no enemies around; once all are captured the planet is conquered and an extractor becomes buildable. ### Scope 1. `CapturePoint.tscn` + `CapturePoint.gd` (`Area2D`): - `@export var point_index: int` (its index within the setup - this is what `GameState` keys on) - `@export var capture_radius: float = 220.0` - `@export var capture_time: float = 8.0` - `@export var enemy_clear_radius: float = 700.0` - `@export var drain_rate: float = 0.5` (fraction of fill rate) - States: `neutral` / `blocked` / `capturing` / `captured`. - Progress rises only while at least one node in group `player_ships` is within `capture_radius` **and** no live node in group `enemies` is within `enemy_clear_radius`. Otherwise it drains at `drain_rate` - a hard reset is too punishing to read on a phone. - On reaching 1.0: `captured`, permanent for the run, calls `GameState.mark_point_captured(GameState.planet_id, point_index)`. - Drawing: outer ring plus a filling progress arc, in the `NeonDraw` idiom - `GasBombController._draw_ring` is the reference for ring rendering, and `VesselGasBomberComponent`'s hull fill is the reference for "fills up as it progresses". Distinct colours per state so `blocked` (enemies nearby) is obvious without text. 2. `PlanetSetupHost` (from the setups ticket) instantiates one `CapturePoint` per `setup.capture_points` entry with its index, calls `GameState.register_capture_points(planet_id, count)`, and marks already-captured indices as `captured` immediately so re-landing on a partly taken planet restores progress. 3. HUD on `PlanetSurface.tscn`'s `UI` `CanvasLayer`, near `PlanetNameLabel` and styled the same: `"CAPTURED 2/3"`, or `"CONQUERED"` once done. It must update live as points complete. 4. On the final point, a brief on-screen confirmation (a label fade is enough - no new modal). ### Out of scope New enemy types (the enemy tickets), the world map showing conquered planets. ### Acceptance criteria - A ship parked inside a point with no enemies present captures it in approximately `capture_time` seconds, and `GameState.captured_point_count` reflects it. - With a node in group `enemies` inside `enemy_clear_radius`, progress does not increase. - Moving out of `capture_radius` drains progress gradually; it does not drop to zero instantly, and an already-captured point never reverts. - Capturing every point makes `GameState.is_planet_conquered(planet_id)` true, and back in space the extractor option over that planet is enabled. - Exiting to space and landing again keeps captured points captured and the HUD count correct. ### Tests `tests/test_capture_points.gd` (`bash tests/run_tests.sh capture`), plus a 720x1280 screenshot of `res://PlanetSurface.tscn` confirming the ring, its progress arc and the HUD count read clearly at phone size. ### 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_gas_bomber_cooldowns.gd`. - Real screenshots: `bash tests/screenshot.sh <res://Scene.tscn> <out.png> <frames> 720x1280`. Use few frames (~10-20); 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 deliberately has **no type switches** left (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://` anywhere outside this ticket's explicit scope). `GameState` is an in-memory autoload. Do not invent a save system. - Commit each ticket separately and run the full suite before pushing.
Author
Owner

Implemented and pushed as c4e2ee7.

  • CapturePoint tracks neutral/blocked/capturing/captured by polling proximity each frame (a ship in capture_radius, no enemy within enemy_clear_radius), draining gradually rather than resetting on interruption, and calling GameState.mark_point_captured() on completion.
  • PlanetSetupHost now instantiates the real CapturePoint scene per authored point - restoring already-captured state immediately on re-landing - and spawns each setup's authored enemies, replacing #42's placeholder markers through the same apply_setup() seam.
  • CaptureLabel is a small self-polling HUD (same convention as ResourceCounter) showing "CAPTURED n/m" or "CONQUERED" with a one-shot fade, themed per surface profile.

New test tests/test_capture_points.gd covers restoration-on-relanding, fill-to-capture timing, enemy-blocking without erasing progress, gradual (not instant) drain, and the GameState flags BuildController's extractor gate already reads. Verified visually at 720x1280 - distinct rings per state (neutral/blocked/capturing/captured), each with the capture-radius ring plus a filling progress beacon.

Caught one real regression along the way: test_planet_atmosphere_transition.gd assumed a landed surface never has enemies, which authored guard enemies now legitimately violate. Narrowed that check to what it actually needs to prove - no space-side EnemySpawner/AsteroidField node leaks across the scene swap - rather than "zero enemies ever."

#45/#46 build the enemy types these points can now be guarded by.

Implemented and pushed as `c4e2ee7`. - `CapturePoint` tracks neutral/blocked/capturing/captured by polling proximity each frame (a ship in `capture_radius`, no enemy within `enemy_clear_radius`), draining gradually rather than resetting on interruption, and calling `GameState.mark_point_captured()` on completion. - `PlanetSetupHost` now instantiates the real `CapturePoint` scene per authored point - restoring already-captured state immediately on re-landing - and spawns each setup's authored enemies, replacing #42's placeholder markers through the same `apply_setup()` seam. - `CaptureLabel` is a small self-polling HUD (same convention as `ResourceCounter`) showing "CAPTURED n/m" or "CONQUERED" with a one-shot fade, themed per surface profile. New test `tests/test_capture_points.gd` covers restoration-on-relanding, fill-to-capture timing, enemy-blocking without erasing progress, gradual (not instant) drain, and the GameState flags BuildController's extractor gate already reads. Verified visually at 720x1280 - distinct rings per state (neutral/blocked/capturing/captured), each with the capture-radius ring plus a filling progress beacon. Caught one real regression along the way: `test_planet_atmosphere_transition.gd` assumed a landed surface never has enemies, which authored guard enemies now legitimately violate. Narrowed that check to what it actually needs to prove - no space-side `EnemySpawner`/`AsteroidField` node leaks across the scene swap - rather than "zero enemies ever." #45/#46 build the enemy types these points can now be guarded by.
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#43
No description provided.