Skill tree, node-based building animation, bigger UI, and a 19x faster test suite

Gameplay and UI
- Research tree at the mothership gates which structures can be built and
  grants permanent fleet-capacity upgrades (GameState.SKILL_TREE,
  SkillTreeView). Node depth is derived from prerequisites, so the layout
  cannot drift out of step with the dependencies.
- Building info popup before committing to a build, with per-building cost
  and description.
- Ship crafter redrawn as a space factory with an assembly animation;
  mothership redrawn as a radial flagship; command centre as a plotting hub.
- Keyboard and mouse support (WASD, Q/E pivot, wheel zoom) for the web build.
- UI and font scaled up for phone use, via a custom Theme resource -
  gui/theme/default_font_size does not reach the default theme.
- Asteroids no longer spawn on top of the fleet's start position.
- Fleet formation origin derived from the layout's own offsets rather than
  the centroid, fixing the lurch when a lopsided formation started moving.
- Fleet.stop() brakes from the ship's TRUE motion (velocity +
  fleet_velocity). Ship zeroes its velocity when seated in its slot even
  while the anchor drags that slot forward, so braking from velocity alone
  stopped the fleet dead instead of coasting - and whether it did depended
  on the frame rate.

Animation architecture (issue #10)
- Buildings, projectiles and explosion VFX no longer animate by redrawing.
  Static geometry is rasterised once in _draw(); moving parts are NeonPiece
  children animated through node properties, which reuse the cached
  geometry. Every NeonDraw call expands into four antialiased polylines, so
  redrawing to move something re-tessellated all of it.

Test suite (issue #8): 1075s -> ~55s
- Runs from a copy on Termux's own filesystem. The project lives on
  Android's FUSE bridge, read through proot, where load("res://Main.tscn")
  alone cost 8.5s per test against ~2s natively - and nearly every test
  loads it.
- Runs 6 tests at a time, in a single proot-distro login, with output
  buffered per test and printed only on failure.
- --fixed-fps 60 pins delta instead of measuring the wall clock, so a test's
  result no longer depends on how busy the machine is. Without it,
  parallelism made two tests fail on 2 of 3 runs.
- A test passes only if it both exits 0 and prints ": PASS".
- 38 tests, green on three consecutive runs.

Bundles Carrois Gothic SC (Apache 2.0) with its licence text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Loïc Wisniewski 2026-08-11 15:00:41 +02:00
commit b641fa5891
48 changed files with 1985 additions and 303 deletions

203
Main.tscn
View file

@ -20,6 +20,7 @@
[ext_resource type="Script" path="res://HaulerField.gd" id="18"]
[ext_resource type="Script" path="res://NeonNumber.gd" id="19"]
[ext_resource type="Script" path="res://NeonPanel.gd" id="20"]
[ext_resource type="Script" path="res://SkillTreeView.gd" id="21"]
[sub_resource type="ShaderMaterial" id="1"]
shader = ExtResource("5")
@ -83,10 +84,10 @@ layer = 1
mouse_filter = 2
offset_left = 28
offset_top = 24
offset_right = 520
offset_bottom = 140
offset_right = 320
offset_bottom = 92
script = ExtResource("16")
digit_height = 100.0
digit_height = 60.0
[node name="PlanetNameLabel" type="Label" parent="UI"]
visible = false
@ -129,19 +130,6 @@ offset_bottom = -572
script = ExtResource("10")
icon = 2
[node name="BuildActionLabel" type="Label" parent="UI"]
visible = false
mouse_filter = 2
anchor_top = 1.0
anchor_bottom = 1.0
anchor_right = 1.0
offset_left = 24
offset_right = -24
offset_top = -566
offset_bottom = -440
text = ""
horizontal_alignment = 2
[node name="MapButton" type="Control" parent="UI"]
anchor_left = 1.0
anchor_right = 1.0
@ -201,19 +189,6 @@ offset_bottom = -24
script = ExtResource("10")
icon = 6
[node name="NavLabel" type="Label" parent="UI"]
mouse_filter = 2
anchor_left = 1.0
anchor_right = 1.0
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = -344
offset_right = -24
offset_top = -322
offset_bottom = -196
text = "Pivot"
horizontal_alignment = 1
[node name="WorldMap" type="Control" parent="UI"]
visible = false
anchor_right = 1.0
@ -287,8 +262,8 @@ anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -214
offset_right = -66
offset_top = -224
offset_bottom = -76
offset_top = -254
offset_bottom = -106
script = ExtResource("10")
icon = 8
glow_color = Color(0.45, 0.9, 0.55, 1)
@ -301,9 +276,9 @@ anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -290
offset_right = 10
offset_top = -70
offset_bottom = 60
theme_override_font_sizes/font_size = 44
offset_top = -96
offset_bottom = 4
theme_override_font_sizes/font_size = 36
autowrap_mode = 2
text = "Mothership"
horizontal_alignment = 1
@ -314,13 +289,13 @@ anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -220
offset_right = -60
offset_top = 70
offset_bottom = 150
offset_left = -204
offset_right = -64
offset_top = 14
offset_bottom = 78
script = ExtResource("19")
amount = 20
digit_height = 66.0
digit_height = 52.0
[node name="CrafterButton" type="Control" parent="UI/BuildMenu"]
anchor_left = 0.5
@ -329,8 +304,8 @@ anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 66
offset_right = 214
offset_top = -224
offset_bottom = -76
offset_top = -254
offset_bottom = -106
script = ExtResource("10")
icon = 9
glow_color = Color(1, 0.72, 0.25, 1)
@ -343,9 +318,9 @@ anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -10
offset_right = 290
offset_top = -70
offset_bottom = 60
theme_override_font_sizes/font_size = 44
offset_top = -96
offset_bottom = 4
theme_override_font_sizes/font_size = 36
autowrap_mode = 2
text = "Ship Crafter"
horizontal_alignment = 1
@ -356,13 +331,13 @@ anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 60
offset_right = 220
offset_top = 70
offset_bottom = 150
offset_left = 76
offset_right = 216
offset_top = 14
offset_bottom = 78
script = ExtResource("19")
amount = 12
digit_height = 66.0
digit_height = 52.0
[node name="CommandButton" type="Control" parent="UI/BuildMenu"]
anchor_left = 0.5
@ -371,8 +346,8 @@ anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -214
offset_right = -66
offset_top = 175
offset_bottom = 323
offset_top = 118
offset_bottom = 266
script = ExtResource("10")
icon = 10
glow_color = Color(0.55, 0.6, 1, 1)
@ -385,9 +360,9 @@ anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -290
offset_right = 10
offset_top = 333
offset_bottom = 463
theme_override_font_sizes/font_size = 44
offset_top = 276
offset_bottom = 376
theme_override_font_sizes/font_size = 36
autowrap_mode = 2
text = "Command Center"
horizontal_alignment = 1
@ -398,13 +373,13 @@ anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -220
offset_right = -60
offset_top = 473
offset_bottom = 553
offset_left = -204
offset_right = -64
offset_top = 386
offset_bottom = 450
script = ExtResource("19")
amount = 15
digit_height = 66.0
digit_height = 52.0
[node name="ExtractorButton" type="Control" parent="UI/BuildMenu"]
anchor_left = 0.5
@ -413,8 +388,8 @@ anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 66
offset_right = 214
offset_top = 175
offset_bottom = 323
offset_top = 118
offset_bottom = 266
script = ExtResource("10")
icon = 11
glow_color = Color(0, 0.62, 0.7, 1)
@ -427,9 +402,9 @@ anchor_top = 0.5
anchor_bottom = 0.5
offset_left = -10
offset_right = 290
offset_top = 333
offset_bottom = 463
theme_override_font_sizes/font_size = 44
offset_top = 276
offset_bottom = 376
theme_override_font_sizes/font_size = 36
autowrap_mode = 2
text = "Extractor"
horizontal_alignment = 1
@ -440,13 +415,13 @@ anchor_left = 0.5
anchor_right = 0.5
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 60
offset_right = 220
offset_top = 473
offset_bottom = 553
offset_left = 76
offset_right = 216
offset_top = 386
offset_bottom = 450
script = ExtResource("19")
amount = 25
digit_height = 66.0
digit_height = 52.0
[node name="CloseButton" type="Control" parent="UI/BuildMenu"]
anchor_left = 1.0
@ -696,3 +671,93 @@ anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0, 0, 0, 0)
[node name="SkillMenu" type="Control" parent="UI"]
visible = false
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
[node name="Backdrop" type="ColorRect" parent="UI/SkillMenu"]
mouse_filter = 2
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0.03, 0.03, 0.08, 0.94)
[node name="TitleLabel" type="Label" parent="UI/SkillMenu"]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24.0
offset_top = 70.0
offset_right = -24.0
offset_bottom = 196.0
text = "Research"
horizontal_alignment = 1
[node name="Tree" type="Control" parent="UI/SkillMenu"]
mouse_filter = 2
anchor_right = 1.0
offset_left = 24.0
offset_top = 210.0
offset_right = -24.0
offset_bottom = 760.0
script = ExtResource("21")
[node name="DetailLabel" type="Label" parent="UI/SkillMenu"]
mouse_filter = 2
anchor_right = 1.0
offset_left = 32.0
offset_top = 780.0
offset_right = -32.0
offset_bottom = 926.0
theme_override_font_sizes/font_size = 40
autowrap_mode = 2
text = ""
horizontal_alignment = 1
vertical_alignment = 1
[node name="CostNumber" type="Control" parent="UI/SkillMenu"]
mouse_filter = 2
anchor_left = 0.5
anchor_right = 0.5
offset_left = -64.0
offset_top = 936.0
offset_right = 76.0
offset_bottom = 1000.0
script = ExtResource("19")
amount = 0
digit_height = 52.0
[node name="StatusLabel" type="Label" parent="UI/SkillMenu"]
mouse_filter = 2
anchor_right = 1.0
offset_left = 32.0
offset_top = 1006.0
offset_right = -32.0
offset_bottom = 1070.0
theme_override_font_sizes/font_size = 36
autowrap_mode = 2
text = ""
horizontal_alignment = 1
[node name="UnlockButton" type="Control" parent="UI/SkillMenu"]
anchor_left = 0.5
anchor_right = 0.5
offset_left = -74.0
offset_top = 1090.0
offset_right = 74.0
offset_bottom = 1238.0
script = ExtResource("10")
icon = 5
glow_color = Color(0.45, 0.9, 0.55, 1)
[node name="CloseButton" type="Control" parent="UI/SkillMenu"]
anchor_left = 1.0
anchor_right = 1.0
offset_left = -172.0
offset_top = 24.0
offset_right = -24.0
offset_bottom = 172.0
script = ExtResource("10")
icon = 1
glow_color = Color(0.55, 0.6, 1, 1)