Planet conquest state and extractor placement gate #41

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

Part of #40. Data layer and gate only - the capture gameplay itself is a separate ticket.

Planets must be conquered before an extractor can be built on them. Until then only manual gathering is possible, which is already the behaviour on the surface (RockField/Rock), so nothing has to be added for that half.

Scope

  1. GameState.gd, next to the existing extractors array (~line 945, same keying by planet_id):

    • var planet_capture: Dictionary = {} mapping planet_id -> {"captured": Array[bool], "conquered": bool}
    • signal planet_conquered(planet_id: String)
    • func register_capture_points(planet_key: String, total: int) -> void - creates the entry on first landing, and is idempotent: re-landing on a planet must NOT wipe already-captured points.
    • func capture_state(planet_key: String) -> Dictionary
    • func is_point_captured(planet_key: String, index: int) -> bool
    • func mark_point_captured(planet_key: String, index: int) -> void - sets conquered and emits planet_conquered once every entry is true.
    • func is_planet_conquered(planet_key: String) -> bool
    • func captured_point_count(planet_key: String) -> int
      Planet keys are the stable planet_id ("chunk:x:y", from WorldGen.planet_data_at_chunk) - never the display name, which is not unique (see #37).
  2. BuildController.gd - four existing extractor gates, all keyed off _planet_under_fleet():

    • _refresh_build_option_states() (~line 557): a placement_mode == "planet" option is enabled only if the planet is also conquered.
    • _refresh_build_status() (~line 547): over an unconquered planet, say "Over %s - capture it before building an extractor".
    • the info popup (~line 596): add a branch before the has_extractor_at one - "%s is not captured yet - take all its capture points.", can_build = false.
    • _confirm_build() (~line 639): re-check conquest alongside the existing has_extractor_at re-check (the fleet keeps flying while the popup is open) and bail with a build_status.text message, spending nothing.

Out of scope

Capture points, enemies, surface HUD, showing conquest on the world map.

Acceptance criteria

  • A fresh GameState reports every planet unconquered.
  • Over an unconquered planet: the extractor option is disabled, the popup explains why, and _confirm_build() places no extractor and spends no ore.
  • After register_capture_points(id, 3) and three mark_point_captured calls, is_planet_conquered(id) is true and planet_conquered fired exactly once; the same build flow then places the extractor and charges its cost.
  • register_capture_points called a second time with the same total preserves captured points.
  • Existing extractor tests pass by marking their planet conquered in setup - not by weakening the gate.

Tests

New tests/test_planet_conquest_gate.gd. Run bash tests/run_tests.sh conquest, then bash tests/run_tests.sh extractor and bash tests/run_tests.sh build for regressions.

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. **Data layer and gate only** - the capture gameplay itself is a separate ticket. Planets must be conquered before an extractor can be built on them. Until then only manual gathering is possible, which is already the behaviour on the surface (`RockField`/`Rock`), so nothing has to be added for that half. ### Scope 1. `GameState.gd`, next to the existing `extractors` array (~line 945, same keying by `planet_id`): - `var planet_capture: Dictionary = {}` mapping `planet_id -> {"captured": Array[bool], "conquered": bool}` - `signal planet_conquered(planet_id: String)` - `func register_capture_points(planet_key: String, total: int) -> void` - creates the entry on first landing, and is idempotent: re-landing on a planet must NOT wipe already-captured points. - `func capture_state(planet_key: String) -> Dictionary` - `func is_point_captured(planet_key: String, index: int) -> bool` - `func mark_point_captured(planet_key: String, index: int) -> void` - sets `conquered` and emits `planet_conquered` once every entry is true. - `func is_planet_conquered(planet_key: String) -> bool` - `func captured_point_count(planet_key: String) -> int` Planet keys are the stable `planet_id` (`"chunk:x:y"`, from `WorldGen.planet_data_at_chunk`) - never the display name, which is not unique (see #37). 2. `BuildController.gd` - four existing extractor gates, all keyed off `_planet_under_fleet()`: - `_refresh_build_option_states()` (~line 557): a `placement_mode == "planet"` option is enabled only if the planet is also conquered. - `_refresh_build_status()` (~line 547): over an unconquered planet, say `"Over %s - capture it before building an extractor"`. - the info popup (~line 596): add a branch before the `has_extractor_at` one - `"%s is not captured yet - take all its capture points."`, `can_build = false`. - `_confirm_build()` (~line 639): re-check conquest alongside the existing `has_extractor_at` re-check (the fleet keeps flying while the popup is open) and bail with a `build_status.text` message, spending nothing. ### Out of scope Capture points, enemies, surface HUD, showing conquest on the world map. ### Acceptance criteria - A fresh `GameState` reports every planet unconquered. - Over an unconquered planet: the extractor option is disabled, the popup explains why, and `_confirm_build()` places no extractor and spends no ore. - After `register_capture_points(id, 3)` and three `mark_point_captured` calls, `is_planet_conquered(id)` is true and `planet_conquered` fired exactly once; the same build flow then places the extractor and charges its cost. - `register_capture_points` called a second time with the same total preserves captured points. - Existing extractor tests pass by marking their planet conquered in setup - not by weakening the gate. ### Tests New `tests/test_planet_conquest_gate.gd`. Run `bash tests/run_tests.sh conquest`, then `bash tests/run_tests.sh extractor` and `bash tests/run_tests.sh build` for regressions. ### 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 a1ad6cc.

  • GameState.planet_capture tracks per-planet capture progress, keyed by the same stable planet_id as extractors; register_capture_points is idempotent so re-landing never wipes progress.
  • BuildController's four extractor placement checks (menu enable state, status text, info popup, confirm) all re-check conquest, not just proximity/duplication.

New test tests/test_planet_conquest_gate.gd covers the unconquered-blocks-build and conquered-allows-build paths plus the idempotent-registration/single-signal-fire cases. Extractor and build regressions (test_extractor_building, test_build_*) still pass - the existing extractor test now pre-conquers its target planet in setup rather than the gate being weakened.

No capture UI/enemies yet - that's #43.

Implemented and pushed as `a1ad6cc`. - `GameState.planet_capture` tracks per-planet capture progress, keyed by the same stable `planet_id` as `extractors`; `register_capture_points` is idempotent so re-landing never wipes progress. - `BuildController`'s four extractor placement checks (menu enable state, status text, info popup, confirm) all re-check conquest, not just proximity/duplication. New test `tests/test_planet_conquest_gate.gd` covers the unconquered-blocks-build and conquered-allows-build paths plus the idempotent-registration/single-signal-fire cases. Extractor and build regressions (`test_extractor_building`, `test_build_*`) still pass - the existing extractor test now pre-conquers its target planet in setup rather than the gate being weakened. No capture UI/enemies yet - that's #43.
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#41
No description provided.