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
|
|
|
|
|
|
2026-08-14 16:13:24 +02:00
|
|
|
const PLANET_DEFINITIONS = preload("res://PlanetDefinitions.gd")
|
2026-08-14 21:44:28 +02:00
|
|
|
const BUILDING_DEFINITIONS = preload("res://BuildingDefinitions.gd")
|
2026-08-14 16:13:24 +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
|
|
|
const WORLD_TO_LOCAL: float = 0.04
|
|
|
|
|
|
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
|
|
|
# A structure built on a planet's surface (see GameState.extractors) is not
|
|
|
|
|
# itself a map marker - it is a small badge on the existing planet marker,
|
|
|
|
|
# in a colour distinct from both the planets and the space buildings so it
|
|
|
|
|
# reads as "something is mined here" rather than another building type.
|
|
|
|
|
const EXTRACTOR_BADGE_COLOR: Color = Color(1.0, 0.85, 0.3)
|
|
|
|
|
const EXTRACTOR_BADGE_OFFSET: Vector2 = Vector2(8.0, -8.0)
|
|
|
|
|
const EXTRACTOR_BADGE_RADIUS: float = 5.0
|
|
|
|
|
|
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
|
|
|
@onready var tracker: Node2D = get_node("../../../ExplorationTracker")
|
|
|
|
|
@onready var fleet: Node2D = get_node("../../../Fleet")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _process(_delta: float) -> void:
|
|
|
|
|
if is_visible_in_tree():
|
|
|
|
|
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
|
|
|
# Local-space marker data for every player-placed building (GameState.
|
|
|
|
|
# buildings), one entry per building with its colour already resolved. Kept
|
|
|
|
|
# as a separate, callable step (rather than inlined in _draw()) so a
|
|
|
|
|
# headless test - which has no GPU and cannot check drawn pixels - can
|
|
|
|
|
# verify a built building actually produces a marker.
|
|
|
|
|
func building_markers() -> Array:
|
|
|
|
|
var markers: Array = []
|
|
|
|
|
for b in GameState.buildings:
|
2026-08-14 21:44:28 +02:00
|
|
|
var definition: Resource = BUILDING_DEFINITIONS.get_definition(String(b.type))
|
|
|
|
|
if definition == null:
|
|
|
|
|
continue
|
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
|
|
|
markers.append({
|
|
|
|
|
"position": b.position * WORLD_TO_LOCAL,
|
|
|
|
|
"type": b.type,
|
2026-08-14 21:44:28 +02:00
|
|
|
"color": definition.map_color,
|
|
|
|
|
"sides": definition.map_sides,
|
|
|
|
|
"radius": definition.map_radius,
|
|
|
|
|
"phase": definition.map_phase,
|
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
|
|
|
})
|
|
|
|
|
return markers
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Whether the planet in this (explored) chunk has an extractor on it, per
|
|
|
|
|
# GameState.has_extractor_at(). A separate predicate for the same
|
|
|
|
|
# GPU-less-testability reason as building_markers().
|
|
|
|
|
func chunk_has_extractor_badge(chunk: Vector2i) -> bool:
|
|
|
|
|
if not WorldGen.has_planet_at_chunk(chunk):
|
|
|
|
|
return false
|
2026-08-14 22:16:40 +02:00
|
|
|
return GameState.has_extractor_at(WorldGen.planet_data_at_chunk(chunk).planet_id)
|
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 _regular_polygon(center: Vector2, radius: float, sides: int, phase: float = 0.0) -> PackedVector2Array:
|
|
|
|
|
var pts: PackedVector2Array = PackedVector2Array()
|
|
|
|
|
for i in range(sides):
|
|
|
|
|
var a: float = (float(i) / float(sides)) * TAU + phase
|
|
|
|
|
pts.append(center + Vector2(cos(a), sin(a)) * radius)
|
|
|
|
|
return pts
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Filled shape plus a thin white outline - the same construction the fleet
|
|
|
|
|
# marker below already uses, just factored out since buildings now need it
|
|
|
|
|
# three times over (once per type) and the extractor badge needs it too.
|
|
|
|
|
func _draw_marker_polygon(pts: PackedVector2Array, color: Color) -> void:
|
|
|
|
|
draw_colored_polygon(pts, color)
|
|
|
|
|
var closed: PackedVector2Array = pts.duplicate()
|
|
|
|
|
closed.append(pts[0])
|
|
|
|
|
draw_polyline(closed, Color(1, 1, 1), 1.5, 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
|
|
|
func _draw() -> void:
|
|
|
|
|
var chunk_size_local: float = WorldGen.CHUNK_SIZE * WORLD_TO_LOCAL
|
|
|
|
|
|
|
|
|
|
for chunk in tracker.get_explored_chunks():
|
|
|
|
|
var origin_local: Vector2 = WorldGen.chunk_origin(chunk) * WORLD_TO_LOCAL
|
|
|
|
|
var biome: int = WorldGen.biome_at_chunk(chunk)
|
|
|
|
|
var color: Color = WorldGen.biome_color(biome)
|
|
|
|
|
var rect: Rect2 = Rect2(origin_local, Vector2(chunk_size_local, chunk_size_local))
|
|
|
|
|
draw_rect(rect, color, true)
|
|
|
|
|
draw_rect(rect, Color(0.0, 0.0, 0.0, 0.4), false, 1.5)
|
|
|
|
|
|
|
|
|
|
if WorldGen.has_planet_at_chunk(chunk):
|
|
|
|
|
# Same deterministic per-chunk data PlanetField spawns the
|
|
|
|
|
# actual Planet from - the marker stays put on the map even
|
|
|
|
|
# for a chunk whose Planet node isn't currently loaded (out of
|
|
|
|
|
# spawn_radius_chunks of the fleet).
|
|
|
|
|
var data: Dictionary = WorldGen.planet_data_at_chunk(chunk)
|
|
|
|
|
var planet_local: Vector2 = origin_local + data.local_pos * WORLD_TO_LOCAL
|
2026-08-14 16:13:24 +02:00
|
|
|
var definition: Resource = PLANET_DEFINITIONS.get_definition(String(data.definition_id))
|
|
|
|
|
draw_circle(planet_local, 5.0, definition.map_color)
|
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
|
|
|
draw_arc(planet_local, 5.0, 0.0, TAU, 16, Color(1.0, 1.0, 1.0, 0.9), 1.0, true)
|
|
|
|
|
|
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 chunk_has_extractor_badge(chunk):
|
|
|
|
|
var badge_pts: PackedVector2Array = _regular_polygon(
|
|
|
|
|
planet_local + EXTRACTOR_BADGE_OFFSET, EXTRACTOR_BADGE_RADIUS, 4, PI / 4.0)
|
|
|
|
|
_draw_marker_polygon(badge_pts, EXTRACTOR_BADGE_COLOR)
|
|
|
|
|
|
|
|
|
|
# Buildings are placed freely in space rather than tied to a chunk, so
|
|
|
|
|
# they get their own pass rather than living inside the chunk loop above.
|
|
|
|
|
for marker in building_markers():
|
2026-08-14 21:44:28 +02:00
|
|
|
_draw_marker_polygon(_regular_polygon(marker.position, marker.radius, marker.sides, marker.phase), marker.color)
|
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
|
|
|
var fleet_local: Vector2 = fleet.get_centroid() * WORLD_TO_LOCAL
|
|
|
|
|
var marker_points: PackedVector2Array = PackedVector2Array([
|
|
|
|
|
fleet_local + Vector2(0, -10),
|
|
|
|
|
fleet_local + Vector2(7, 8),
|
|
|
|
|
fleet_local + Vector2(-7, 8),
|
|
|
|
|
])
|
|
|
|
|
draw_colored_polygon(marker_points, Color(0.3, 0.9, 1.0))
|
|
|
|
|
draw_polyline(PackedVector2Array([marker_points[0], marker_points[1], marker_points[2], marker_points[0]]), Color(1, 1, 1), 1.5, true)
|