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 Node2D
|
|
|
|
|
|
|
|
|
|
# A landable world-object - unlike regular Asteroid.gd, planets are
|
|
|
|
|
# stationary ("Big Stable Asteroid" - stable means it doesn't drift or
|
|
|
|
|
# destroy ships on touch). Whether the fleet counts as "landed" is decided
|
|
|
|
|
# by PlanetField comparing the fleet's CENTROID distance to landing_radius,
|
|
|
|
|
# not per-ship Area2D collision - a single stable number, immune to the
|
|
|
|
|
# constant minor jostling of individual ships holding formation, which
|
|
|
|
|
# used to make the "enter atmosphere" prompt flicker on/off as ships
|
|
|
|
|
# drifted in and out of the boundary independently of each other even
|
|
|
|
|
# while the fleet as a whole was clearly sitting on the planet.
|
|
|
|
|
|
|
|
|
|
@export var planet_name: String = ""
|
2026-08-13 21:07:35 +02:00
|
|
|
@export var planet_type: String = "rocky"
|
|
|
|
|
@export var resource_type: String = "ore"
|
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
|
|
|
@export var landing_radius: float = 140.0
|
|
|
|
|
# Set by whoever spawns this planet (PlanetField, seeded from the chunk's
|
|
|
|
|
# own coordinates) so the outline shape is stable across respawns of the
|
|
|
|
|
# same chunk, instead of reading as a "different" planet every time.
|
|
|
|
|
@export var shape_seed: int = 0
|
|
|
|
|
|
|
|
|
|
var shape_points: PackedVector2Array = PackedVector2Array()
|
|
|
|
|
|
Show buildings on the world map, and extractors on their planets
Forgejo #11: buildings the player has placed now appear on the world
map. They are placed freely in space rather than tied to a chunk, so
they get their own pass in WorldMapContent._draw() rather than riding
along with the chunk markers. Each type has its own shape and colour,
mirroring Building.gd's constants so a marker reads as the same
building you built.
Forgejo #9: a planet with an extractor built on it now shows it, in
both places you look at a planet - an amber derrick on its outline in
space, and a matching badge on its world-map marker. The extractor
lives on PlanetSurface.tscn, a scene you are not looking at from
space, so an already-mined planet was otherwise indistinguishable
from an untouched one until you landed on it.
The positioning steps are factored out (building_markers(),
chunk_has_extractor_badge(), extractor_derrick_tip()) so headless
tests can assert on them - --headless has no GPU and draws no pixels,
so there is nothing to check in _draw() itself. Both features were
also rendered for real via tests/screenshot.sh and looked at.
extractor_derrick_tip() exists because the first version of the
"derrick is not buried inside the planet" check measured the furthest
of all four derrick points, which passes a derrick pointing straight
into the ground: the base corners sit a half-width off the rim vertex
and clear the outline on their own. Mutation-testing caught it.
screenshot.sh now clears a stale /tmp/.X99-lock. A run killed
mid-screenshot leaves it behind and Xvfb then refuses :99, which
resurfaces later as Godot's misleading "X11 Display is not available".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 15:29:56 +02:00
|
|
|
# An extractor is built down on this planet's SURFACE, in a different scene
|
|
|
|
|
# the player is no longer looking at, so from space nothing distinguished a
|
|
|
|
|
# planet already being mined from an untouched one - you had to land to find
|
|
|
|
|
# out. This is a derrick planted on the rim: the same amber the world map
|
|
|
|
|
# uses for its extractor badge, so the two read as the same fact.
|
|
|
|
|
const EXTRACTOR_MARKER_COLOR: Color = Color(1.0, 0.85, 0.3)
|
|
|
|
|
const DERRICK_HEIGHT: float = 44.0
|
|
|
|
|
const DERRICK_HALF_WIDTH: float = 16.0
|
|
|
|
|
|
2026-08-11 15:43:43 +02:00
|
|
|
# Kept in step with GameState.extractor_added rather than polled per frame.
|
|
|
|
|
# An extractor is built from SPACE (BuildController._planet_under_fleet(),
|
|
|
|
|
# with the fleet parked on the planet), so this node is already in the tree
|
|
|
|
|
# with its _ready() long since run when the answer changes - resolving it
|
|
|
|
|
# only at _ready() left the planet looking untouched until it happened to
|
|
|
|
|
# despawn and respawn.
|
Show buildings on the world map, and extractors on their planets
Forgejo #11: buildings the player has placed now appear on the world
map. They are placed freely in space rather than tied to a chunk, so
they get their own pass in WorldMapContent._draw() rather than riding
along with the chunk markers. Each type has its own shape and colour,
mirroring Building.gd's constants so a marker reads as the same
building you built.
Forgejo #9: a planet with an extractor built on it now shows it, in
both places you look at a planet - an amber derrick on its outline in
space, and a matching badge on its world-map marker. The extractor
lives on PlanetSurface.tscn, a scene you are not looking at from
space, so an already-mined planet was otherwise indistinguishable
from an untouched one until you landed on it.
The positioning steps are factored out (building_markers(),
chunk_has_extractor_badge(), extractor_derrick_tip()) so headless
tests can assert on them - --headless has no GPU and draws no pixels,
so there is nothing to check in _draw() itself. Both features were
also rendered for real via tests/screenshot.sh and looked at.
extractor_derrick_tip() exists because the first version of the
"derrick is not buried inside the planet" check measured the furthest
of all four derrick points, which passes a derrick pointing straight
into the ground: the base corners sit a half-width off the rim vertex
and clear the outline on their own. Mutation-testing caught it.
screenshot.sh now clears a stale /tmp/.X99-lock. A run killed
mid-screenshot leaves it behind and Xvfb then refuses :99, which
resurfaces later as Godot's misleading "X11 Display is not available".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 15:29:56 +02:00
|
|
|
var shows_extractor: bool = false
|
2026-08-13 21:07:35 +02:00
|
|
|
var gas_phase: float = 0.0
|
Show buildings on the world map, and extractors on their planets
Forgejo #11: buildings the player has placed now appear on the world
map. They are placed freely in space rather than tied to a chunk, so
they get their own pass in WorldMapContent._draw() rather than riding
along with the chunk markers. Each type has its own shape and colour,
mirroring Building.gd's constants so a marker reads as the same
building you built.
Forgejo #9: a planet with an extractor built on it now shows it, in
both places you look at a planet - an amber derrick on its outline in
space, and a matching badge on its world-map marker. The extractor
lives on PlanetSurface.tscn, a scene you are not looking at from
space, so an already-mined planet was otherwise indistinguishable
from an untouched one until you landed on it.
The positioning steps are factored out (building_markers(),
chunk_has_extractor_badge(), extractor_derrick_tip()) so headless
tests can assert on them - --headless has no GPU and draws no pixels,
so there is nothing to check in _draw() itself. Both features were
also rendered for real via tests/screenshot.sh and looked at.
extractor_derrick_tip() exists because the first version of the
"derrick is not buried inside the planet" check measured the furthest
of all four derrick points, which passes a derrick pointing straight
into the ground: the base corners sit a half-width off the rim vertex
and clear the outline on their own. Mutation-testing caught it.
screenshot.sh now clears a stale /tmp/.X99-lock. A run killed
mid-screenshot leaves it behind and Xvfb then refuses :99, which
resurfaces later as Godot's misleading "X11 Display is not available".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 15:29:56 +02:00
|
|
|
|
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 _ready() -> void:
|
|
|
|
|
add_to_group("planets")
|
|
|
|
|
z_index = -1
|
|
|
|
|
|
|
|
|
|
var rng: RandomNumberGenerator = RandomNumberGenerator.new()
|
|
|
|
|
rng.seed = shape_seed
|
|
|
|
|
var point_count: int = 10
|
|
|
|
|
for i in range(point_count):
|
|
|
|
|
var angle: float = (float(i) / point_count) * TAU
|
|
|
|
|
var radius: float = rng.randf_range(70.0, 100.0)
|
|
|
|
|
shape_points.append(Vector2(cos(angle), sin(angle)) * radius)
|
Show buildings on the world map, and extractors on their planets
Forgejo #11: buildings the player has placed now appear on the world
map. They are placed freely in space rather than tied to a chunk, so
they get their own pass in WorldMapContent._draw() rather than riding
along with the chunk markers. Each type has its own shape and colour,
mirroring Building.gd's constants so a marker reads as the same
building you built.
Forgejo #9: a planet with an extractor built on it now shows it, in
both places you look at a planet - an amber derrick on its outline in
space, and a matching badge on its world-map marker. The extractor
lives on PlanetSurface.tscn, a scene you are not looking at from
space, so an already-mined planet was otherwise indistinguishable
from an untouched one until you landed on it.
The positioning steps are factored out (building_markers(),
chunk_has_extractor_badge(), extractor_derrick_tip()) so headless
tests can assert on them - --headless has no GPU and draws no pixels,
so there is nothing to check in _draw() itself. Both features were
also rendered for real via tests/screenshot.sh and looked at.
extractor_derrick_tip() exists because the first version of the
"derrick is not buried inside the planet" check measured the furthest
of all four derrick points, which passes a derrick pointing straight
into the ground: the base corners sit a half-width off the rim vertex
and clear the outline on their own. Mutation-testing caught it.
screenshot.sh now clears a stale /tmp/.X99-lock. A run killed
mid-screenshot leaves it behind and Xvfb then refuses :99, which
resurfaces later as Godot's misleading "X11 Display is not available".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 15:29:56 +02:00
|
|
|
shows_extractor = GameState.has_extractor_at(planet_name)
|
2026-08-11 15:43:43 +02:00
|
|
|
GameState.extractor_added.connect(_on_extractor_added)
|
|
|
|
|
queue_redraw()
|
|
|
|
|
|
|
|
|
|
|
2026-08-13 21:07:35 +02:00
|
|
|
func _process(delta: float) -> void:
|
|
|
|
|
if planet_type == "gas_giant":
|
|
|
|
|
gas_phase += delta
|
|
|
|
|
queue_redraw()
|
|
|
|
|
|
|
|
|
|
|
2026-08-11 15:43:43 +02:00
|
|
|
func _on_extractor_added(built_on: String) -> void:
|
|
|
|
|
# Only the planet it was actually built on, and only a redraw when the
|
|
|
|
|
# answer really changed - every other planet in range gets this too.
|
|
|
|
|
if built_on != planet_name or shows_extractor:
|
|
|
|
|
return
|
|
|
|
|
shows_extractor = true
|
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
|
|
|
queue_redraw()
|
|
|
|
|
|
|
|
|
|
|
Show buildings on the world map, and extractors on their planets
Forgejo #11: buildings the player has placed now appear on the world
map. They are placed freely in space rather than tied to a chunk, so
they get their own pass in WorldMapContent._draw() rather than riding
along with the chunk markers. Each type has its own shape and colour,
mirroring Building.gd's constants so a marker reads as the same
building you built.
Forgejo #9: a planet with an extractor built on it now shows it, in
both places you look at a planet - an amber derrick on its outline in
space, and a matching badge on its world-map marker. The extractor
lives on PlanetSurface.tscn, a scene you are not looking at from
space, so an already-mined planet was otherwise indistinguishable
from an untouched one until you landed on it.
The positioning steps are factored out (building_markers(),
chunk_has_extractor_badge(), extractor_derrick_tip()) so headless
tests can assert on them - --headless has no GPU and draws no pixels,
so there is nothing to check in _draw() itself. Both features were
also rendered for real via tests/screenshot.sh and looked at.
extractor_derrick_tip() exists because the first version of the
"derrick is not buried inside the planet" check measured the furthest
of all four derrick points, which passes a derrick pointing straight
into the ground: the base corners sit a half-width off the rim vertex
and clear the outline on their own. Mutation-testing caught it.
screenshot.sh now clears a stale /tmp/.X99-lock. A run killed
mid-screenshot leaves it behind and Xvfb then refuses :99, which
resurfaces later as Godot's misleading "X11 Display is not available".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 15:29:56 +02:00
|
|
|
func extractor_derrick_anchor() -> Vector2:
|
|
|
|
|
# The rim vertex the derrick stands on: the shape's own topmost point,
|
|
|
|
|
# rather than a fixed radius, so it sits ON the outline whatever lumpy
|
|
|
|
|
# shape the seed drew instead of floating off it or sinking inside it.
|
|
|
|
|
var base: Vector2 = Vector2(0.0, -100.0)
|
|
|
|
|
for p in shape_points:
|
|
|
|
|
if p.y < base.y:
|
|
|
|
|
base = p
|
|
|
|
|
return base
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func extractor_derrick_tip() -> Vector2:
|
|
|
|
|
# Exposed separately from the polygon because "does the derrick clear the
|
|
|
|
|
# planet's outline" is a question about the TIP alone. The base corners
|
|
|
|
|
# sit a half-width off to either side, so they are sqrt(|base|^2 + w^2)
|
|
|
|
|
# from the centre and can clear the rim on their own even when the tower
|
|
|
|
|
# points straight into the ground.
|
|
|
|
|
var base: Vector2 = extractor_derrick_anchor()
|
|
|
|
|
var out: Vector2 = base.normalized()
|
|
|
|
|
if out == Vector2.ZERO:
|
|
|
|
|
out = Vector2.UP
|
|
|
|
|
return base + out * DERRICK_HEIGHT
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func extractor_derrick_points() -> PackedVector2Array:
|
|
|
|
|
var base: Vector2 = extractor_derrick_anchor()
|
|
|
|
|
var out: Vector2 = base.normalized()
|
|
|
|
|
if out == Vector2.ZERO:
|
|
|
|
|
out = Vector2.UP
|
|
|
|
|
var side: Vector2 = Vector2(-out.y, out.x)
|
|
|
|
|
var tip: Vector2 = extractor_derrick_tip()
|
|
|
|
|
return PackedVector2Array([
|
|
|
|
|
base - side * DERRICK_HALF_WIDTH,
|
|
|
|
|
tip - side * (DERRICK_HALF_WIDTH * 0.35),
|
|
|
|
|
tip + side * (DERRICK_HALF_WIDTH * 0.35),
|
|
|
|
|
base + side * DERRICK_HALF_WIDTH,
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
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 _draw() -> void:
|
2026-08-13 21:07:35 +02:00
|
|
|
if planet_type == "gas_giant":
|
|
|
|
|
_draw_gas_giant()
|
|
|
|
|
if shows_extractor:
|
|
|
|
|
NeonDraw.draw_glowing_polygon(self, extractor_derrick_points(), EXTRACTOR_MARKER_COLOR, 1.0)
|
|
|
|
|
return
|
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
|
|
|
NeonDraw.draw_glowing_polygon(self, shape_points, Color(0.6, 0.55, 0.5), 1.0)
|
Show buildings on the world map, and extractors on their planets
Forgejo #11: buildings the player has placed now appear on the world
map. They are placed freely in space rather than tied to a chunk, so
they get their own pass in WorldMapContent._draw() rather than riding
along with the chunk markers. Each type has its own shape and colour,
mirroring Building.gd's constants so a marker reads as the same
building you built.
Forgejo #9: a planet with an extractor built on it now shows it, in
both places you look at a planet - an amber derrick on its outline in
space, and a matching badge on its world-map marker. The extractor
lives on PlanetSurface.tscn, a scene you are not looking at from
space, so an already-mined planet was otherwise indistinguishable
from an untouched one until you landed on it.
The positioning steps are factored out (building_markers(),
chunk_has_extractor_badge(), extractor_derrick_tip()) so headless
tests can assert on them - --headless has no GPU and draws no pixels,
so there is nothing to check in _draw() itself. Both features were
also rendered for real via tests/screenshot.sh and looked at.
extractor_derrick_tip() exists because the first version of the
"derrick is not buried inside the planet" check measured the furthest
of all four derrick points, which passes a derrick pointing straight
into the ground: the base corners sit a half-width off the rim vertex
and clear the outline on their own. Mutation-testing caught it.
screenshot.sh now clears a stale /tmp/.X99-lock. A run killed
mid-screenshot leaves it behind and Xvfb then refuses :99, which
resurfaces later as Godot's misleading "X11 Display is not available".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 15:29:56 +02:00
|
|
|
if shows_extractor:
|
|
|
|
|
NeonDraw.draw_glowing_polygon(self, extractor_derrick_points(), EXTRACTOR_MARKER_COLOR, 1.0)
|
2026-08-13 21:07:35 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func _draw_gas_giant() -> void:
|
|
|
|
|
# A round, animated giant with drifting latitudinal bands. It is visibly
|
|
|
|
|
# unlike the rocky outline and keeps moving even while the fleet is idle.
|
|
|
|
|
var rim := PackedVector2Array()
|
|
|
|
|
for i in range(32):
|
|
|
|
|
var a := TAU * float(i) / 32.0
|
|
|
|
|
rim.append(Vector2(cos(a) * 118.0, sin(a) * 118.0))
|
|
|
|
|
NeonDraw.draw_glowing_polygon(self, rim, Color(0.4, 0.78, 1.0), 0.9)
|
|
|
|
|
for row in [-62.0, -28.0, 8.0, 43.0, 73.0]:
|
|
|
|
|
var band := PackedVector2Array()
|
|
|
|
|
var half_width: float = sqrt(maxf(0.0, 118.0 * 118.0 - row * row))
|
|
|
|
|
for i in range(15):
|
|
|
|
|
var x := lerpf(-half_width, half_width, float(i) / 14.0)
|
|
|
|
|
band.append(Vector2(x, row + sin(x * 0.045 + gas_phase * 1.6 + row) * 7.0))
|
|
|
|
|
NeonDraw.draw_glowing_path(self, band, Color(0.85, 0.52, 1.0), 0.75, 0.65)
|