Draw capture points behind fleet vessels #50

Closed
opened 2026-08-15 20:29:57 +00:00 by metaheavens · 1 comment
Owner

Capture point rings currently draw over the fleet, so a ship parked inside one is partly swallowed by the ring fill - exactly where the player most needs to see their own vessels.

Why it happens

CapturePoint (added under PlanetSetupHost, inside SurfaceFeatures) leaves z_index at its default 0, the same layer Ship draws on. The codebase already has a settled convention for world objects that must sit behind the fleet, all of them setting z_index = -1 in _ready():

  • Planet.gd:66, Rock.gd:31, Building.gd:19, Hauler.gd:14, Transporter.gd:14, VesselDelivery.gd:11

Scope

  1. In CapturePoint._ready(), set z_index = -1, with a one-line comment naming the convention (same shape as the comment in Rock.gd).
  2. Sanity-check the rest of the landed-surface stack while you are there: rocks and gas vents sit at -1, enemies and ships at 0. A capture point must never occlude a ship or an enemy, since "is an enemy inside my capture radius" is the core read of the mechanic.

Out of scope

Any change to capture behaviour, colours, or radii.

Acceptance criteria

  • A fleet parked inside a capture point ring renders every ship fully on top of it, verified in a 720x1280 screenshot.
  • An enemy standing in the ring is also fully visible.
  • bash tests/run_tests.sh capture passes.

Tests

Extend tests/test_capture_points.gd with a z_index < 0 assertion (no new test file needed) and re-render tests/_capture_points_preview.tscn, adding a ship inside one of the rings so the layering is actually visible in the shot.

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.
Capture point rings currently draw over the fleet, so a ship parked inside one is partly swallowed by the ring fill - exactly where the player most needs to see their own vessels. ### Why it happens `CapturePoint` (added under `PlanetSetupHost`, inside `SurfaceFeatures`) leaves `z_index` at its default `0`, the same layer `Ship` draws on. The codebase already has a settled convention for world objects that must sit behind the fleet, all of them setting `z_index = -1` in `_ready()`: - `Planet.gd:66`, `Rock.gd:31`, `Building.gd:19`, `Hauler.gd:14`, `Transporter.gd:14`, `VesselDelivery.gd:11` ### Scope 1. In `CapturePoint._ready()`, set `z_index = -1`, with a one-line comment naming the convention (same shape as the comment in `Rock.gd`). 2. Sanity-check the rest of the landed-surface stack while you are there: rocks and gas vents sit at -1, enemies and ships at 0. A capture point must never occlude a ship *or* an enemy, since "is an enemy inside my capture radius" is the core read of the mechanic. ### Out of scope Any change to capture behaviour, colours, or radii. ### Acceptance criteria - A fleet parked inside a capture point ring renders every ship fully on top of it, verified in a 720x1280 screenshot. - An enemy standing in the ring is also fully visible. - `bash tests/run_tests.sh capture` passes. ### Tests Extend `tests/test_capture_points.gd` with a `z_index < 0` assertion (no new test file needed) and re-render `tests/_capture_points_preview.tscn`, adding a ship inside one of the rings so the layering is actually visible in the shot. ### 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.
Author
Owner

Implemented and pushed as 5b2dd77.

CapturePoint.gd left z_index at its default 0, the same layer Ship draws on, so a ship parked inside a ring was partly swallowed by the ring fill. Set z_index = -1 in _ready(), matching the convention every other world object already follows.

Verified visually: rendered a ship directly on top of a capture point ring and confirmed it draws fully in front. Asserted z_index < 0 in tests/test_capture_points.gd.

Implemented and pushed as `5b2dd77`. `CapturePoint.gd` left `z_index` at its default `0`, the same layer `Ship` draws on, so a ship parked inside a ring was partly swallowed by the ring fill. Set `z_index = -1` in `_ready()`, matching the convention every other world object already follows. Verified visually: rendered a ship directly on top of a capture point ring and confirmed it draws fully in front. Asserted `z_index < 0` in `tests/test_capture_points.gd`.
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#50
No description provided.