Planet setups as authored resources #42

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

Part of #40. #40 requires enemy/point layouts to be hand-authored and chosen from a list, not procedural, and stored in the game as dedicated resources rather than in a save file. This ticket adds that data format, its registry, and the surface-side host. The editor that writes these files is a later ticket; here they are written by hand.

Scope

  1. PlanetSetupSpawn.gd - class_name PlanetSetupSpawn extends Resource, @export var enemy_id: String, @export var position: Vector2. (A sub-resource rather than a Dictionary, so ResourceSaver round-trips it cleanly for the editor later.)

  2. PlanetSetup.gd - class_name PlanetSetup extends Resource:

    • @export var setup_id: String
    • @export var display_name: String
    • @export var surface_profile_id: String = "rocky" - which surface profile this setup is authored for (PlanetSurfaceProfiles ids: "rocky", "gas").
    • @export var capture_points: PackedVector2Array - surface-local positions.
    • @export var enemy_spawns: Array[Resource] - PlanetSetupSpawn entries.
  3. PlanetSetups.gd - registry mirroring PlanetDefinitions.gd:

    • static func all() -> Array
    • static func setups_for_profile(profile_id: String) -> Array
    • static func setup_for_planet(planet_id: String, profile_id: String) -> Resource - deterministic pick: RandomNumberGenerator seeded with hash(planet_id), indexing into setups_for_profile. Same planet always gets the same setup, from anywhere, without storing anything - the pattern WorldGen.planet_data_at_chunk already uses.
    • Populate by scanning res://PlanetSetups/ with DirAccess at first use rather than a hardcoded preload dictionary, so adding a .tres file is the only step needed to add a setup. (In an exported build DirAccess over res:// sees exactly the files packed at export time, which is the desired behaviour.)
  4. Ship two starter setups committed under res://PlanetSetups/: rocky_default.tres (3 capture points, a couple of chaser spawns) and gas_default.tres (4 capture points). Positions should be a few hundred to ~1500 units from the origin, where the fleet lands.

  5. PlanetSetupHost.tscn + PlanetSetupHost.gd - a Node2D that takes a PlanetSetup and instantiates its contents. In this ticket it may spawn simple placeholder markers for capture points; the real CapturePoint scene arrives with the capture ticket and enemy spawning with the enemy-foundation ticket. Keep the seam (func apply_setup(setup: Resource) -> void) stable so those tickets only fill it in.

  6. PlanetSurfaceController._compose_surface() - after the existing profile.features_scene instantiation, resolve PlanetSetups.setup_for_planet(GameState.planet_id, profile.profile_id) and add a configured PlanetSetupHost under SurfaceFeatures.

Constraints

No planet-type string comparisons anywhere - the profile id selects the setup list, exactly as #31 established. PlanetSurfaceController.gd must stay free of "rocky"/"gas_giant" literals.

Acceptance criteria

  • setup_for_planet returns the same setup for the same planet_id across repeated calls and across fresh processes, and only ever returns setups whose surface_profile_id matches the one asked for.
  • Landing on a rocky planet and on a gas giant each produce a host carrying a setup of the matching profile.
  • Dropping a third .tres into res://PlanetSetups/ makes it appear in setups_for_profile with no other code change.
  • bash tests/run_tests.sh atmosphere still passes (the landing transition is what this hooks into).

Tests

tests/test_planet_setups.gd - determinism, profile filtering, registry pickup, and host instantiation on landing. bash tests/run_tests.sh 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_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. #40 requires enemy/point layouts to be **hand-authored and chosen from a list**, not procedural, and stored in the game as dedicated resources rather than in a save file. This ticket adds that data format, its registry, and the surface-side host. The editor that writes these files is a later ticket; here they are written by hand. ### Scope 1. `PlanetSetupSpawn.gd` - `class_name PlanetSetupSpawn extends Resource`, `@export var enemy_id: String`, `@export var position: Vector2`. (A sub-resource rather than a `Dictionary`, so `ResourceSaver` round-trips it cleanly for the editor later.) 2. `PlanetSetup.gd` - `class_name PlanetSetup extends Resource`: - `@export var setup_id: String` - `@export var display_name: String` - `@export var surface_profile_id: String = "rocky"` - which surface profile this setup is authored for (`PlanetSurfaceProfiles` ids: `"rocky"`, `"gas"`). - `@export var capture_points: PackedVector2Array` - surface-local positions. - `@export var enemy_spawns: Array[Resource]` - `PlanetSetupSpawn` entries. 3. `PlanetSetups.gd` - registry mirroring `PlanetDefinitions.gd`: - `static func all() -> Array` - `static func setups_for_profile(profile_id: String) -> Array` - `static func setup_for_planet(planet_id: String, profile_id: String) -> Resource` - deterministic pick: `RandomNumberGenerator` seeded with `hash(planet_id)`, indexing into `setups_for_profile`. Same planet always gets the same setup, from anywhere, without storing anything - the pattern `WorldGen.planet_data_at_chunk` already uses. - Populate by scanning `res://PlanetSetups/` with `DirAccess` at first use rather than a hardcoded `preload` dictionary, so adding a `.tres` file is the only step needed to add a setup. (In an exported build `DirAccess` over `res://` sees exactly the files packed at export time, which is the desired behaviour.) 4. Ship two starter setups committed under `res://PlanetSetups/`: `rocky_default.tres` (3 capture points, a couple of chaser spawns) and `gas_default.tres` (4 capture points). Positions should be a few hundred to ~1500 units from the origin, where the fleet lands. 5. `PlanetSetupHost.tscn` + `PlanetSetupHost.gd` - a `Node2D` that takes a `PlanetSetup` and instantiates its contents. In this ticket it may spawn simple placeholder markers for capture points; the real `CapturePoint` scene arrives with the capture ticket and enemy spawning with the enemy-foundation ticket. Keep the seam (`func apply_setup(setup: Resource) -> void`) stable so those tickets only fill it in. 6. `PlanetSurfaceController._compose_surface()` - after the existing `profile.features_scene` instantiation, resolve `PlanetSetups.setup_for_planet(GameState.planet_id, profile.profile_id)` and add a configured `PlanetSetupHost` under `SurfaceFeatures`. ### Constraints No planet-type string comparisons anywhere - the profile id selects the setup list, exactly as #31 established. `PlanetSurfaceController.gd` must stay free of `"rocky"`/`"gas_giant"` literals. ### Acceptance criteria - `setup_for_planet` returns the same setup for the same `planet_id` across repeated calls and across fresh processes, and only ever returns setups whose `surface_profile_id` matches the one asked for. - Landing on a rocky planet and on a gas giant each produce a host carrying a setup of the matching profile. - Dropping a third `.tres` into `res://PlanetSetups/` makes it appear in `setups_for_profile` with no other code change. - `bash tests/run_tests.sh atmosphere` still passes (the landing transition is what this hooks into). ### Tests `tests/test_planet_setups.gd` - determinism, profile filtering, registry pickup, and host instantiation on landing. `bash tests/run_tests.sh 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_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 5326852.

  • PlanetSetup/PlanetSetupSpawn resources, PlanetSetups registry that scans res://PlanetSetups/ at first use (not a hardcoded preload table) so a new .tres needs no code change, and resolves the same setup deterministically per planet_id - same pattern as WorldGen.planet_data_at_chunk.
  • PlanetSurfaceController composes a PlanetSetupHost onto the landed surface, alongside the existing resource-provider feature scene, with no planet-type string checks.
  • Two starter setups shipped: rocky_default.tres, gas_default.tres.

New test tests/test_planet_setups.gd covers determinism, profile filtering, registry pickup (drops a scratch .tres, rescans, confirms it's found and rescans clean), and landing composition on both profiles. Verified visually at 720x1280 - capture-point markers render as clean cyan rings against both surfaces.

Markers are placeholders; #43 replaces them with the real CapturePoint scene through the same apply_setup() entry point, which is why that method exists as a stable seam already.

Implemented and pushed as `5326852`. - `PlanetSetup`/`PlanetSetupSpawn` resources, `PlanetSetups` registry that scans `res://PlanetSetups/` at first use (not a hardcoded preload table) so a new `.tres` needs no code change, and resolves the same setup deterministically per `planet_id` - same pattern as `WorldGen.planet_data_at_chunk`. - `PlanetSurfaceController` composes a `PlanetSetupHost` onto the landed surface, alongside the existing resource-provider feature scene, with no planet-type string checks. - Two starter setups shipped: `rocky_default.tres`, `gas_default.tres`. New test `tests/test_planet_setups.gd` covers determinism, profile filtering, registry pickup (drops a scratch `.tres`, rescans, confirms it's found and rescans clean), and landing composition on both profiles. Verified visually at 720x1280 - capture-point markers render as clean cyan rings against both surfaces. Markers are placeholders; #43 replaces them with the real `CapturePoint` scene through the same `apply_setup()` entry point, which is why that method exists as a stable seam already.
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#42
No description provided.