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
|
|
|
class_name ShipExplosionVFX
|
|
|
|
|
extends LightBurstVFX
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _init() -> void:
|
#130, #135: a vessel's death takes a moment, and leaves an interceptor
#130 asked for the hull to break away from the fleet, drift a few metres,
and go up further off with something more explosive than a light animation.
The wreck is a separate node rather than the Ship staying alive to animate,
and that is the load-bearing part. A dying Ship still in the tree is still
an Area2D in the player_ships group with live weapon components - it would
keep firing, keep being fired at, keep counting as a target, and keep
colliding on the way out. So the ship's own lifecycle is untouched: it
leaves the fleet, emits destroyed, and is freed on exactly the frame it
always was. What is left behind is scenery, and the test pins that.
The explosion at the end was also the quieter of the two the game has.
Killing an enemy was made deliberately loud by #87 while losing one of your
own kept the base class's twelve-particle default - the reward louder than
the loss. It is now the bigger of the two.
#135 is the same scene from the other end: the prologue's convoy flies its
escorts on the corners of the box, which is the exposed part of it, and the
debris belt can take all four. With no interceptor left, the only hulls the
vortex could spare were holds, and the player flew out of the prologue in a
carrier. Two guards now: one interceptor is put back into the convoy at the
top of the vortex, where a vessel appearing among ten being thrown around a
black hole is not something anyone will catch; and the handover checks the
TYPES it hands over rather than only the count - one surviving carrier is
also a fleet of one, and it is not the fleet a new game starts with.
2026-08-28 15:04:26 +02:00
|
|
|
# #130: "a rather more explosive explosion than just a light animation".
|
|
|
|
|
# This used to take the base class's defaults - 12 particles, half a
|
|
|
|
|
# second, no shockwave - which made losing one of your own vessels a
|
|
|
|
|
# quieter event on screen than killing an enemy, whose explosion #87
|
|
|
|
|
# had already been told to be the loudest thing in the frame. Now it is
|
|
|
|
|
# the louder of the two, which is the right way round: the enemy's is a
|
|
|
|
|
# reward, this one is a loss.
|
#155, #152: shots reach further and sooner, and the other explosions catch up
#155 - the projectile flew at 850, which took 1.1 seconds to cross the gun's
own range: long enough that a shot read as leaving the ship rather than as
hitting something. 1400 now, and the interceptor's range goes 950 to 1200,
so it crosses in 0.86s and comfortably outranges the 620 the standoff
enemies hold at. A projectile's lifetime is derived from its range and
speed, so faster does not mean falling short - the test pins that
arithmetic rather than trusting it.
#152 asked for VFX on three events, and the finding is worth recording: all
three already existed and were already wired. What had happened is that #130
gave a lost VESSEL thirty particles and a shockwave, which left everything
else in the game visibly smaller than it - an enemy kill, the thing the
player is being rewarded for, and a broken rock, the thing they do more than
anything else, were both still on or near the base class's twelve-particle
default with no shockwave at all.
So this is about PROPORTION rather than about adding anything, and
proportion is what the test checks: a lost vessel outweighs a kill,
a kill outweighs a rock, and nothing sits on the base defaults. Numbers get
rebalanced; an ordering that says a rock outweighs a battleship is a bug.
Particles now run 38 > 32 > 24 against a default of 12.
2026-08-29 14:39:35 +02:00
|
|
|
#
|
|
|
|
|
# #152 lifted the enemy's and the asteroid's to match; this goes up with
|
|
|
|
|
# them, because the ORDER is the point rather than the numbers. Losing a
|
|
|
|
|
# vessel must stay the bigger event than killing something.
|
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
|
|
|
color = Color(0.25, 0.85, 1.0)
|
#155, #152: shots reach further and sooner, and the other explosions catch up
#155 - the projectile flew at 850, which took 1.1 seconds to cross the gun's
own range: long enough that a shot read as leaving the ship rather than as
hitting something. 1400 now, and the interceptor's range goes 950 to 1200,
so it crosses in 0.86s and comfortably outranges the 620 the standoff
enemies hold at. A projectile's lifetime is derived from its range and
speed, so faster does not mean falling short - the test pins that
arithmetic rather than trusting it.
#152 asked for VFX on three events, and the finding is worth recording: all
three already existed and were already wired. What had happened is that #130
gave a lost VESSEL thirty particles and a shockwave, which left everything
else in the game visibly smaller than it - an enemy kill, the thing the
player is being rewarded for, and a broken rock, the thing they do more than
anything else, were both still on or near the base class's twelve-particle
default with no shockwave at all.
So this is about PROPORTION rather than about adding anything, and
proportion is what the test checks: a lost vessel outweighs a kill,
a kill outweighs a rock, and nothing sits on the base defaults. Numbers get
rebalanced; an ordering that says a rock outweighs a battleship is a bug.
Particles now run 38 > 32 > 24 against a default of 12.
2026-08-29 14:39:35 +02:00
|
|
|
particle_count = 38
|
|
|
|
|
lifetime = 0.95
|
|
|
|
|
min_speed = 190.0
|
|
|
|
|
max_speed = 640.0
|
|
|
|
|
shockwave_radius = 150.0
|
#130, #135: a vessel's death takes a moment, and leaves an interceptor
#130 asked for the hull to break away from the fleet, drift a few metres,
and go up further off with something more explosive than a light animation.
The wreck is a separate node rather than the Ship staying alive to animate,
and that is the load-bearing part. A dying Ship still in the tree is still
an Area2D in the player_ships group with live weapon components - it would
keep firing, keep being fired at, keep counting as a target, and keep
colliding on the way out. So the ship's own lifecycle is untouched: it
leaves the fleet, emits destroyed, and is freed on exactly the frame it
always was. What is left behind is scenery, and the test pins that.
The explosion at the end was also the quieter of the two the game has.
Killing an enemy was made deliberately loud by #87 while losing one of your
own kept the base class's twelve-particle default - the reward louder than
the loss. It is now the bigger of the two.
#135 is the same scene from the other end: the prologue's convoy flies its
escorts on the corners of the box, which is the exposed part of it, and the
debris belt can take all four. With no interceptor left, the only hulls the
vortex could spare were holds, and the player flew out of the prologue in a
carrier. Two guards now: one interceptor is put back into the convoy at the
top of the vortex, where a vessel appearing among ten being thrown around a
black hole is not something anyone will catch; and the handover checks the
TYPES it hands over rather than only the count - one surviving carrier is
also a fleet of one, and it is not the fleet a new game starts with.
2026-08-28 15:04:26 +02:00
|
|
|
shockwave_color = Color(0.6, 0.95, 1.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
|
|
|
|
|
|
|
|
|
|
|
|
|
static func spawn(parent: Node, world_position: Vector2) -> void:
|
|
|
|
|
var vfx: ShipExplosionVFX = ShipExplosionVFX.new()
|
|
|
|
|
vfx.position = world_position
|
|
|
|
|
parent.add_child(vfx)
|