Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
extends SceneTree
|
|
|
|
|
|
|
|
|
|
# Headless gameplay test: run with
|
|
|
|
|
# godot --headless -s res://tests/test_planet_atmosphere_transition.gd
|
|
|
|
|
# Verifies the full enter/exit atmosphere flow now that it's a genuine
|
|
|
|
|
# scene swap (Main.tscn <-> PlanetSurface.tscn) rather than an in-place
|
|
|
|
|
# overlay: standing on a planet shows the prompt, pressing it fades out
|
|
|
|
|
# and swaps to PlanetSurface.tscn with the SAME surviving ship count,
|
2026-08-15 21:22:10 +02:00
|
|
|
# where no asteroid ever exists and no space-side EnemySpawner/AsteroidField
|
|
|
|
|
# node carries over (structurally absent - PlanetSurface.tscn has no such
|
|
|
|
|
# nodes at all), and the fleet is still fully flyable. Landed enemies MAY
|
|
|
|
|
# still exist post-#43 (PlanetSetupHost places authored ones to guard
|
|
|
|
|
# capture points), which is a different thing from the space-side spawner
|
|
|
|
|
# leaking across the swap - see the structural check below. Exiting fades
|
|
|
|
|
# back to Main.tscn with the fleet restored at the world position it left
|
|
|
|
|
# from.
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
|
|
|
|
|
var main_scene: Node
|
|
|
|
|
var fleet: Node
|
|
|
|
|
var planet_field: Node
|
|
|
|
|
var landing_controller: Node
|
|
|
|
|
var asteroid_field_ref: Node
|
|
|
|
|
var planet_name_label: Label
|
|
|
|
|
var enter_button: IconButton
|
|
|
|
|
var elapsed: float = 0.0
|
|
|
|
|
var stage: int = 0
|
|
|
|
|
var target_chunk: Vector2i
|
|
|
|
|
var position_before_leaving: Vector2
|
|
|
|
|
var cached_fade_duration: float = 0.4
|
|
|
|
|
var returned_ship_count: int = -1
|
2026-08-14 23:15:12 +02:00
|
|
|
var returned_vessel_types: PackedStringArray = PackedStringArray()
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
var game_state: Node
|
2026-08-14 23:15:12 +02:00
|
|
|
var expected_vessel_types: PackedStringArray = PackedStringArray([
|
|
|
|
|
GameState.VESSEL_INTERCEPTOR, GameState.VESSEL_SHIELD,
|
2026-08-15 00:09:10 +02:00
|
|
|
GameState.VESSEL_INTERCEPTOR, GameState.VESSEL_SHIELD,
|
2026-08-14 23:15:12 +02:00
|
|
|
GameState.VESSEL_INTERCEPTOR,
|
|
|
|
|
])
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func _initialize() -> void:
|
|
|
|
|
var found: bool = false
|
|
|
|
|
for x in range(-15, 15):
|
|
|
|
|
for y in range(-15, 15):
|
|
|
|
|
var chunk: Vector2i = Vector2i(x, y)
|
|
|
|
|
if WorldGen.has_planet_at_chunk(chunk):
|
|
|
|
|
target_chunk = chunk
|
|
|
|
|
found = true
|
|
|
|
|
break
|
|
|
|
|
if found:
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if not found:
|
|
|
|
|
print("FAIL: could not find any chunk with a planet to test with")
|
|
|
|
|
quit(1)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
game_state = root.get_node("GameState")
|
2026-09-05 17:09:27 +02:00
|
|
|
# #219: fleet capacity is built now - one berth free, one per Fleet
|
|
|
|
|
# Office. This test flies more than one vessel, so it needs the offices
|
|
|
|
|
# standing before the fleet spawns into them.
|
|
|
|
|
load("res://tests/_berths.gd").grant(root.get_node("GameState"), 6)
|
#84: move 215 root files into a folder hierarchy
Everything lived in one flat directory, so a diff gave no clue what area it
touched and finding the file you wanted meant knowing its exact name. Twelve
folders now, grouped by what a thing IS rather than what type it is - a
vessel's definition, its component scenes and its upgrade resources sit
together, because that is the set you read when you change one of them.
buildings core controllers enemies logistics planets
resources shaders surface ui vessels vfx world
332 files moved and every res:// reference rewritten - 306 files touched,
including the autoload and main-scene paths in project.godot. Nothing renamed:
this is purely where files sit, so the diff is a pile of pure moves and no
behaviour is buried in it. ui_theme.tres, icon.svg and web_shell.html stay at
the root, where Godot and the export preset expect them.
Verified three ways, because a suite alone would not have caught all of it:
the full 117 pass, the web export builds clean, and the game renders.
The export is why that matters. The FIRST export after the move failed with
"Attempt to open script 'res://GameState.gd'" - the old path - while
project.godot on disk plainly said res://core/GameState.gd. That is this
project's documented Android FUSE mtime trap: Godot served a cached copy of a
file whose content had changed. A `touch` fixed it. Nothing in the test suite
would have found that, because the suite runs from a copy on a native
filesystem - the one place the bug cannot happen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 19:44:14 +02:00
|
|
|
var packed: PackedScene = load("res://world/Main.tscn")
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
main_scene = packed.instantiate()
|
|
|
|
|
root.add_child(main_scene)
|
|
|
|
|
# change_scene_to_file() operates on current_scene - the real game
|
|
|
|
|
# gets this set automatically via project.godot's run/main_scene on
|
|
|
|
|
# boot, but a hand-built headless test harness has to do it itself.
|
|
|
|
|
current_scene = main_scene
|
|
|
|
|
|
|
|
|
|
fleet = main_scene.get_node("Fleet")
|
Rework how a game starts: dev mode, a bare opening, and fleet growth
Adds a Dev Mode entry to the main menu and takes the play-testing
conveniences out of a normal game, which turn out to be the same change
from two directions.
DEV MODE. Banks 2000 of every resource, grants 500 research points, and
plants one of each planet type in the chunks around the spawn. Built ON TOP
of reset_new_game() rather than beside it, so a dev game is a normal game
plus a stock rather than a second definition of "fresh game" to keep in
sync. Offered only when running from Godot - OS.has_feature("editor"), the
tag an export clears - as is the map editor. Hidden AND guarded: pressing
either anyway is refused, because visibility is presentation and the rule is
the rule.
The menu now lays its stack out in code. Hiding two of four entries with
scene-authored offsets would leave holes in a shipped build - the same
failure the HUD columns had - so it measures the entries that actually exist
and centres on them.
A NORMAL GAME NOW HANDS OVER NOTHING. No 500 banked ore (removed from both
reset_new_game and the autoload's own default, so the two cannot disagree),
and no gas giant guaranteed in the origin chunk. Both were play-testing
conveniences that had become part of the real world's shape: every player
got the same landmark in the same place. Play-testing wanted all four planet
types anyway, and a normal game wanted none of them - different needs, now
different mechanisms.
WorldGen cannot read GameState back (an autoload is not resolvable as a
global identifier that early, the hazard PlanetSetups.gd already works
around), so GameState.dev_mode's setter PUSHES the flag down. World
generation stays static and dependency-free, GameState stays the single
source of truth, and dev planting is applied after every rng draw so a dev
world and a normal one differ only in those four chunks.
FLEET SIZE. BASE_FLEET_SIZE was doing two jobs - how many slots exist AND
how many ships you start with - which forced a new game to open with a full
fleet, leaving the first ship crafter nothing to do. Split into
BASE_FLEET_SIZE (3 slots) and STARTING_SHIP_COUNT (1 vessel), so the opening
has two empty slots to fill. A third copy of the old value turned up in
PlanetAtmosphereController, hardcoded as spawn_ships(5) and silently
degraded into "however many slots exist" once max_fleet_size() clamped it.
The doctrine branch now runs 3 -> 10 slots. Wing (30 -> 15) and Squadron
(70 -> 35, 2 -> 1 points) are cut hardest: they are no longer a mid-game
luxury but the ordinary way out of the opening, and pricing them as a luxury
made the early game feel stuck. Armada follows at 90/2. Two new tiers,
Battlegroup and Grand Fleet, grant +2 each - seven single-vessel nodes in
one lane would be a long monotonous chain for a branch that only ever does
one thing. DEFAULT_FORMATION grows to 10 slots, because max_fleet_size()
clamps to its length and a doctrine past that would charge real resources
for nothing; test_fleet_growth.gd asserts that invariant directly.
TEST FALLOUT, all of it from the deliberate change rather than a defect:
- Tests that silently inherited the old 5-ship default now ask for the fleet
they need. Several had to crew it from their first frame rather than
_initialize(), where the engine's deferred tree-entry finalization undoes
it - the same hazard those tests already documented for set_process(false).
- test_planet_landing_proximity and test_return_position_matches_planet take
the FIRST chunk holding a planet and land there. The guaranteed gas giant
had been masking that this can now be a black hole, which destroys ships on
contact. They exclude it, and clear the asteroids the field already placed.
- test_pivot_controls measured rotation with angle_difference, which wraps at
+/-pi, so a hold that turned a full circle read as no rotation. Latent
flakiness that only ever showed under load; it now reads the controller's
unwrapped pivot_hold_amount.
- test_research_points was failing on ore, not points: can_unlock() checks
the ore price too, and a fresh game banks nothing now.
- test_gas_system's "a reachable gas source near the spawn" premise moved to
dev mode, which is where that guarantee now lives.
Full suite green at 97/97.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 12:10:14 +02:00
|
|
|
# A new game now flies STARTING_SHIP_COUNT (1) vessel into BASE_FLEET_SIZE
|
|
|
|
|
# (3) slots. This test needs a crewed fleet, so it asks for one outright
|
|
|
|
|
# rather than inheriting whatever a fresh game happens to open with -
|
|
|
|
|
# which is exactly what changed underneath it.
|
|
|
|
|
fleet.spawn_ships(5, Vector2.ZERO)
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
planet_field = main_scene.get_node("PlanetField")
|
|
|
|
|
landing_controller = main_scene.get_node("LandingController")
|
|
|
|
|
asteroid_field_ref = main_scene.get_node("AsteroidField")
|
|
|
|
|
planet_name_label = main_scene.get_node("UI/PlanetNameLabel")
|
2026-08-14 23:15:12 +02:00
|
|
|
enter_button = main_scene.get_node("UI/BuildActionButton")
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func _process(delta: float) -> bool:
|
|
|
|
|
elapsed += delta
|
|
|
|
|
match stage:
|
|
|
|
|
0:
|
|
|
|
|
# target_chunk is guaranteed ASTEROID_FIELD biome (a
|
|
|
|
|
# prerequisite for having a planet), so regular drifting
|
|
|
|
|
# asteroids/enemies could also spawn nearby and interfere with
|
|
|
|
|
# a ship during this focused scenario - isolate it, same as
|
|
|
|
|
# test_enemy_combat.gd does for its own scenario.
|
|
|
|
|
#
|
|
|
|
|
# This has to happen here, not in _initialize(): a node's
|
|
|
|
|
# set_process(false) call made before it has fully finished
|
|
|
|
|
# entering the tree doesn't reliably stick - the engine's own
|
|
|
|
|
# (deferred) tree-entry finalization, which runs strictly
|
|
|
|
|
# after _ready(), can silently reset it back to enabled. Here,
|
|
|
|
|
# at the point _ready() is already confirmed complete
|
|
|
|
|
# (fleet.ships is reliably populated by now), it's safe.
|
|
|
|
|
asteroid_field_ref.set_process(false)
|
|
|
|
|
main_scene.get_node("EnemySpawner").set_process(false)
|
|
|
|
|
# Disabling the spawners stops NEW asteroids, but any already
|
|
|
|
|
# placed keep drifting under their own _process and can still
|
|
|
|
|
# collide with a ship mid-test. Clear the existing ones too.
|
|
|
|
|
for a in get_nodes_in_group("asteroids"):
|
|
|
|
|
a.queue_free()
|
2026-08-14 23:15:12 +02:00
|
|
|
# A mixed fleet proves the handoff preserves composition rather
|
|
|
|
|
# than merely reconstructing the same number of interceptors.
|
|
|
|
|
fleet.spawn_ships(expected_vessel_types.size(), fleet.get_centroid(),
|
|
|
|
|
expected_vessel_types)
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
|
|
|
|
|
# Get the fleet into the target chunk's vicinity first so
|
|
|
|
|
# PlanetField's spawn radius covers it - the planet's own exact
|
|
|
|
|
# position within the chunk is randomized, so this is only
|
|
|
|
|
# enough to trigger spawning, not yet close enough to land on.
|
|
|
|
|
var vicinity_pos: Vector2 = WorldGen.chunk_origin(target_chunk) + Vector2.ONE * WorldGen.CHUNK_SIZE * 0.5
|
|
|
|
|
for ship in fleet.ships:
|
|
|
|
|
ship.position = vicinity_pos
|
|
|
|
|
fleet.move_to(vicinity_pos)
|
|
|
|
|
elapsed = 0.0
|
|
|
|
|
stage = 1
|
|
|
|
|
return false
|
|
|
|
|
1:
|
|
|
|
|
if elapsed < planet_field.check_interval + 0.1:
|
|
|
|
|
return false
|
|
|
|
|
if not planet_field.populated_chunks.has(target_chunk):
|
|
|
|
|
print("TEST_PLANET_ATMOSPHERE_TRANSITION: FAIL (planet never spawned)")
|
|
|
|
|
quit(1)
|
|
|
|
|
return true
|
|
|
|
|
var planet: Node2D = planet_field.populated_chunks[target_chunk][0]
|
|
|
|
|
for ship in fleet.ships:
|
|
|
|
|
ship.position = planet.position
|
|
|
|
|
fleet.move_to(planet.position)
|
|
|
|
|
elapsed = 0.0
|
|
|
|
|
stage = 2
|
|
|
|
|
return false
|
|
|
|
|
2:
|
|
|
|
|
if elapsed < 0.5:
|
|
|
|
|
return false
|
|
|
|
|
var prompt_ok: bool = enter_button.visible and planet_name_label.visible and planet_name_label.text != ""
|
|
|
|
|
print("prompt_ok=%s ship_count=%d name_text=%s" % [prompt_ok, fleet.ships.size(), planet_name_label.text])
|
|
|
|
|
if not prompt_ok:
|
|
|
|
|
print("TEST_PLANET_ATMOSPHERE_TRANSITION: FAIL (prompt never appeared)")
|
|
|
|
|
quit(1)
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
# Let the fleet finish settling onto the planet before recording
|
|
|
|
|
# where it left from. Capturing mid-settle made this test
|
|
|
|
|
# intermittently fail later: the baseline was a still-moving
|
|
|
|
|
# centroid, while the return always places the fleet exactly on
|
|
|
|
|
# the planet, so the two could differ by more than the tolerance.
|
|
|
|
|
for ship in fleet.ships:
|
|
|
|
|
if ship.position.distance_to(ship.move_target) > ship.arrive_radius:
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
position_before_leaving = fleet.get_centroid()
|
|
|
|
|
cached_fade_duration = landing_controller.fade_duration
|
|
|
|
|
_press(enter_button)
|
|
|
|
|
elapsed = 0.0
|
|
|
|
|
stage = 3
|
|
|
|
|
return false
|
|
|
|
|
3:
|
|
|
|
|
# Fade-out + change_scene_to_file + fade-in. landing_controller
|
|
|
|
|
# gets freed once the scene swap completes, so the wait
|
|
|
|
|
# duration must be cached beforehand, not read live each frame.
|
|
|
|
|
if elapsed < cached_fade_duration + 0.3:
|
|
|
|
|
return false
|
|
|
|
|
var on_surface: bool = current_scene != null and current_scene.name == "PlanetSurface"
|
|
|
|
|
print("on_surface=%s current_scene=%s" % [on_surface, current_scene])
|
|
|
|
|
if not on_surface:
|
|
|
|
|
print("TEST_PLANET_ATMOSPHERE_TRANSITION: FAIL (did not swap to PlanetSurface)")
|
|
|
|
|
quit(1)
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
var surface_fleet: Node = current_scene.get_node("Fleet")
|
|
|
|
|
var ship_count_ok: bool = surface_fleet.ships.size() == 5
|
2026-08-14 23:15:12 +02:00
|
|
|
var composition_ok: bool = surface_fleet.vessel_types() == expected_vessel_types
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
var no_asteroids: bool = get_nodes_in_group("asteroids").is_empty()
|
2026-08-15 21:22:10 +02:00
|
|
|
# Landed enemies from an authored PlanetSetup are expected
|
|
|
|
|
# post-#43 (they guard capture points) - what this test actually
|
|
|
|
|
# owes is that the SPACE-SIDE spawner nodes never carry across,
|
|
|
|
|
# checked structurally rather than by assuming zero enemies.
|
|
|
|
|
var no_space_spawners: bool = current_scene.get_node_or_null("EnemySpawner") == null \
|
|
|
|
|
and current_scene.get_node_or_null("AsteroidField") == null
|
|
|
|
|
print("ship_count_ok=%s (count=%d) no_asteroids=%s no_space_spawners=%s" % [
|
|
|
|
|
ship_count_ok, surface_fleet.ships.size(), no_asteroids, no_space_spawners
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
])
|
2026-08-15 21:22:10 +02:00
|
|
|
if not (ship_count_ok and composition_ok and no_asteroids and no_space_spawners):
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
print("TEST_PLANET_ATMOSPHERE_TRANSITION: FAIL (surface scene contents wrong)")
|
|
|
|
|
quit(1)
|
|
|
|
|
return true
|
|
|
|
|
|
Implement #85, #86, #87 and #88
#85 - on a computer the sticks and the pivot pair are dead weight: WASD
steers, the mouse aims, Q/E pivot. All four go together the first time a
keyboard or a mouse is used, and only where there is no touchscreen. A touch
device with a keyboard attached keeps everything, because a finger is still a
way to play it.
#86 - the default gun shoots faster (500 -> 850) and further (320 -> 620).
The interesting half is that a bolt now STOPS where the gun's range ends.
Projectile lifetime is derived from fire_range at launch rather than set
independently, so the distance a shot covers and the distance the aim guide
draws are one number. They were two, and they disagreed: the guide promised
320 while a bolt actually flew speed * lifetime = 750, so shots landed on
things well past the line saying where they would stop.
#87 - the ship that fires throws sparks, so a shot is visibly attached to the
vessel it came from; with a whole fleet shooting there was nothing tying the
two together, which started mattering once the player had to aim. Enemy deaths
gained a shockwave ring and roughly double the debris - a kill used to read as
a small red puff that was easy to miss in a fight.
#88 - everything moves 1.5x faster: fleet cruise, every vessel's max_speed and
acceleration, and every enemy's. Scaling the enemies too keeps it a change of
TEMPO rather than a difficulty cut, and scaling acceleration alongside speed
keeps the handling from #77's tuning intact.
Three tests carried assertions that were really about one balance pass:
test_enemy_mothership pinned a literal 35.0 and now compares against the
chaser, which is what "never reaches ramming speed" actually means;
test_planet_atmosphere_transition flew far enough at the new speed to reach
the landed garrison, and now clears it, since that test is about the handoff
and not about a fight it never meant to pick.
Also repaired real data loss, caught only because test_gas_burner failed:
Godot had silently re-serialised GasBurnerBuildingDefinition.tres and dropped
collected_outputs, capabilities, map_radius and map_phase, and had recreated a
stale copy of it at the pre-#84 root path. Without collected_outputs no
transporter ever collects a burner's energy. Both files restored from git.
It had also resurrected pre-#84 SNAPSHOTS of Main.tscn, Planet.tscn and
lunar_surface.gdshader at their old root paths - the root Main.tscn still
carried 27 references to the flat layout. They are dead weight the engine
never loads, since project.godot and every preload point into the folders, but
they are exactly the kind of thing that wastes an hour later when someone
edits the wrong copy. Deleted, and the root is now down to ui_theme.tres,
icon.svg, web_shell.html and the two config files.
Suite 119/119.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 02:19:14 +02:00
|
|
|
# Clear the landed garrison before flying anywhere. The authored
|
|
|
|
|
# enemies are legitimately there (checked above), but this test is
|
|
|
|
|
# about the TRANSITION - it hands the fleet down, flies it, and
|
|
|
|
|
# hands it back, then asserts every vessel survived the round
|
|
|
|
|
# trip. A chaser destroying one on the way is a correct outcome of
|
|
|
|
|
# a fight this test never meant to pick, and #82 made that outcome
|
|
|
|
|
# more likely by making contact lethal to both. It surfaced when
|
|
|
|
|
# #88 raised every speed by 1.5 and the same 2.5s flight started
|
|
|
|
|
# covering enough ground to reach one.
|
|
|
|
|
for enemy in get_nodes_in_group("enemies"):
|
|
|
|
|
if is_instance_valid(enemy):
|
|
|
|
|
enemy.queue_free()
|
|
|
|
|
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
# Confirm the fleet is genuinely flyable on the surface.
|
|
|
|
|
var start_pos: Vector2 = surface_fleet.get_centroid()
|
|
|
|
|
surface_fleet.move_to(start_pos + Vector2(400, 0))
|
|
|
|
|
elapsed = 0.0
|
|
|
|
|
stage = 4
|
|
|
|
|
return false
|
|
|
|
|
4:
|
|
|
|
|
if elapsed < 2.5:
|
|
|
|
|
return false
|
|
|
|
|
var surface_fleet: Node = current_scene.get_node("Fleet")
|
|
|
|
|
var moved_ok: bool = surface_fleet.get_centroid().x > 300.0
|
|
|
|
|
print("moved_ok=%s centroid=%s" % [moved_ok, surface_fleet.get_centroid()])
|
|
|
|
|
if not moved_ok:
|
|
|
|
|
print("TEST_PLANET_ATMOSPHERE_TRANSITION: FAIL (fleet could not move on the surface)")
|
|
|
|
|
quit(1)
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
var exit_button: IconButton = current_scene.get_node("UI/ExitButton")
|
|
|
|
|
var surface_controller: Node = current_scene.get_node("SurfaceController")
|
|
|
|
|
cached_fade_duration = surface_controller.fade_duration
|
|
|
|
|
_press(exit_button)
|
|
|
|
|
elapsed = 0.0
|
|
|
|
|
stage = 5
|
|
|
|
|
return false
|
|
|
|
|
5:
|
|
|
|
|
# Poll for the swap rather than waiting a fixed duration, so the
|
|
|
|
|
# ship count can be captured on the very FIRST frame back. The
|
|
|
|
|
# fresh Main.tscn populates asteroids during its own _ready(),
|
|
|
|
|
# and they can destroy a ship within a few frames - waiting out
|
|
|
|
|
# the fade first meant sometimes measuring a fleet that had
|
|
|
|
|
# already lost one to a collision, which says nothing about the
|
|
|
|
|
# transition being tested here.
|
|
|
|
|
var back_in_space: bool = current_scene != null and current_scene.name == "Main"
|
|
|
|
|
if not back_in_space:
|
|
|
|
|
if elapsed > cached_fade_duration + 3.0:
|
|
|
|
|
print("TEST_PLANET_ATMOSPHERE_TRANSITION: FAIL (did not return to Main)")
|
|
|
|
|
quit(1)
|
|
|
|
|
return true
|
|
|
|
|
return false
|
|
|
|
|
if returned_ship_count == -1:
|
|
|
|
|
print("back_in_space=%s current_scene=%s" % [back_in_space, current_scene])
|
|
|
|
|
|
|
|
|
|
# Returning builds a BRAND NEW Main.tscn, so the spawners
|
|
|
|
|
# silenced back in stage 0 are live again in this fresh scene.
|
|
|
|
|
# Silence them on the first frame back, and record the ship count
|
|
|
|
|
# right then: asteroids populate immediately on scene load and
|
|
|
|
|
# could destroy a ship during the settle wait below, which is a
|
|
|
|
|
# gameplay collision rather than anything about the transition
|
|
|
|
|
# this test is checking.
|
|
|
|
|
var space_fleet: Node = current_scene.get_node("Fleet")
|
|
|
|
|
if returned_ship_count == -1:
|
|
|
|
|
current_scene.get_node("AsteroidField").set_process(false)
|
|
|
|
|
current_scene.get_node("EnemySpawner").set_process(false)
|
|
|
|
|
for a in get_nodes_in_group("asteroids"):
|
|
|
|
|
a.queue_free()
|
|
|
|
|
returned_ship_count = space_fleet.ships.size()
|
2026-08-14 23:15:12 +02:00
|
|
|
returned_vessel_types = space_fleet.vessel_types()
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
|
|
|
|
|
# Ships respawn in a wedge and then steer into formation, so the
|
|
|
|
|
# centroid only means anything once they have actually stopped.
|
|
|
|
|
# Sampling after a fixed delay made this test intermittently fail
|
|
|
|
|
# by measuring a fleet still on its way in.
|
|
|
|
|
for ship in space_fleet.ships:
|
|
|
|
|
if ship.position.distance_to(ship.move_target) > ship.arrive_radius:
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
# Assert on the HANDOFF, not on how many ships survive the next
|
|
|
|
|
# few frames. Asteroids populate during the fresh scene's own
|
|
|
|
|
# _ready(), so one can spawn overlapping the returning fleet and
|
|
|
|
|
# destroy a ship on the first physics frame - before any test
|
|
|
|
|
# code can run. That is a gameplay collision, not a transition
|
|
|
|
|
# fault, and made this test fail roughly 1 run in 12. What the
|
|
|
|
|
# transition owes us is that the surviving count was carried
|
|
|
|
|
# across correctly and a fleet came back.
|
|
|
|
|
var handoff_ok: bool = game_state.surviving_ship_count == 5
|
|
|
|
|
var ship_count_ok: bool = handoff_ok and returned_ship_count >= 1
|
2026-08-14 23:15:12 +02:00
|
|
|
var composition_ok: bool = returned_vessel_types == expected_vessel_types
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
var position_ok: bool = space_fleet.get_centroid().distance_to(position_before_leaving) < 50.0
|
|
|
|
|
print("handoff=%d returned_ships=%d ship_count_ok=%s position_ok=%s centroid=%s expected=%s" % [
|
|
|
|
|
game_state.surviving_ship_count, returned_ship_count,
|
|
|
|
|
ship_count_ok, position_ok, space_fleet.get_centroid(), position_before_leaving
|
|
|
|
|
])
|
2026-08-14 23:15:12 +02:00
|
|
|
if ship_count_ok and composition_ok and position_ok:
|
Initial commit: SpaceshipTest, a top-down space RTS in Godot 4.7.1
A fleet of neon-outlined ships explores a procedurally generated, seeded
infinite world: tap/joystick steering, pinch zoom, formation pivoting,
planet landings, an ore economy with extractors and haulers, and player
built structures (mothership, ship crafter, command centre).
All art is hand-drawn with draw_* calls rather than textures, and the
backgrounds are camera-relative shaders so they stay precise at large
world coordinates.
Includes a 35-test headless suite (tests/run_tests.sh) plus screenshot
tooling that renders real frames through Xvfb and Mesa softpipe, since
--headless has no GPU.
Excluded from version control: .godot/ (import cache, and it holds
export credentials), builds/, android/build/ and packaged APKs - all
regenerable, and ~325 MB combined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 18:41:58 +02:00
|
|
|
print("TEST_PLANET_ATMOSPHERE_TRANSITION: PASS")
|
|
|
|
|
quit(0)
|
|
|
|
|
else:
|
|
|
|
|
print("TEST_PLANET_ATMOSPHERE_TRANSITION: FAIL (return state wrong)")
|
|
|
|
|
quit(1)
|
|
|
|
|
return true
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _press(button: IconButton) -> void:
|
|
|
|
|
var down: InputEventScreenTouch = InputEventScreenTouch.new()
|
|
|
|
|
down.pressed = true
|
|
|
|
|
button._gui_input(down)
|
|
|
|
|
var up: InputEventScreenTouch = InputEventScreenTouch.new()
|
|
|
|
|
up.pressed = false
|
|
|
|
|
button._gui_input(up)
|