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>
2026-08-11 15:00:41 +02:00
|
|
|
extends Control
|
|
|
|
|
|
|
|
|
|
# The mothership's research tree, drawn in the game's own neon style rather
|
|
|
|
|
# than with Godot UI widgets, for the same reason everything else here is
|
|
|
|
|
# hand-drawn: a themed Button would look nothing like the rest of the game.
|
|
|
|
|
#
|
|
|
|
|
# Nodes are laid out from the normalised `pos` in GameState.SKILL_TREE, so
|
|
|
|
|
# the tree's shape is data next to the upgrades themselves instead of being
|
|
|
|
|
# duplicated here.
|
|
|
|
|
#
|
|
|
|
|
# Input arrives through BuildController._unhandled_input, NOT _gui_input -
|
|
|
|
|
# a Control that consumes touches through the GUI system swallows them
|
|
|
|
|
# before the menu can act on them, which is the bug that made the formation
|
|
|
|
|
# editor's drag silently do nothing. Everything in this menu must stay
|
|
|
|
|
# mouse_filter = IGNORE.
|
#93: drive the research menu with a mouse, and close the hole under it
Two separate faults, both reported on the same ticket.
The menu was inert under a mouse. Input reaches the tree as raw events
through BuildController._unhandled_input, because a Control that consumes
them through the GUI system swallows the drag, and the tree only ever read
touch events. That was invisible while the project set
emulate_touch_from_mouse and every click arrived as a synthetic touch; with
that setting gone, nothing selects and nothing scrolls, and there is no
other way to research anything on a desktop. The press and the pan are now
the behaviour, with touch and mouse as two ways in to them. Motion pans only
while the button is held - a hovering pointer must not drag the tree out
from under the reader - and the wheel scrolls, which is the first thing a
desktop player reaches for.
The panel also still had the buy button's hole in it. That button went away
in #83 and nothing moved down into the space, leaving ~180px of dead panel
below the readout while the tree above it stayed cramped. The readout moves
down 154px and the canvas takes the height: 1190px to 1344px.
The test drives clicks through BuildController rather than into the view,
since the missing piece was the routing - calling the view directly would
have passed against the bug as reported.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 23:29:34 +02:00
|
|
|
#
|
|
|
|
|
# #93: a mouse is a first-class way to drive this menu, not something
|
|
|
|
|
# emulation turns into touches. The project used to set
|
|
|
|
|
# emulate_touch_from_mouse, which made every click arrive here as an
|
|
|
|
|
# InputEventScreenTouch for free; with that off, a menu that only reads
|
|
|
|
|
# touch events is simply dead under a mouse - which is what the report was.
|
|
|
|
|
# So the press and the pan are the behaviour, and touch and mouse are two
|
|
|
|
|
# ways in to them.
|
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>
2026-08-11 15:00:41 +02:00
|
|
|
|
|
|
|
|
signal node_picked(id: String)
|
|
|
|
|
|
|
|
|
|
const LOCKED_COLOR: Color = Color(0.36, 0.39, 0.5)
|
|
|
|
|
const READY_COLOR: Color = Color(0.55, 0.6, 1.0)
|
|
|
|
|
const DONE_COLOR: Color = Color(0.45, 0.9, 0.55)
|
|
|
|
|
const SELECTED_RING: Color = Color(1.0, 0.85, 0.4)
|
|
|
|
|
# A locked node needs a dark core as well as a dim glow. NeonDraw tops every
|
|
|
|
|
# shape with a near-white core stroke, which at this size dominates the glow
|
|
|
|
|
# entirely - so a "dim" colour alone still came out the brightest thing on
|
|
|
|
|
# screen, making unreachable nodes look like the highlighted ones.
|
|
|
|
|
const LOCKED_CORE: Color = Color(0.17, 0.19, 0.26)
|
|
|
|
|
const LOCKED_PULSE: float = 0.5
|
|
|
|
|
|
|
|
|
|
@export var node_radius: float = 46.0
|
2026-08-13 16:25:41 +02:00
|
|
|
@export var row_spacing: float = 250.0
|
#93: drive the research menu with a mouse, and close the hole under it
Two separate faults, both reported on the same ticket.
The menu was inert under a mouse. Input reaches the tree as raw events
through BuildController._unhandled_input, because a Control that consumes
them through the GUI system swallows the drag, and the tree only ever read
touch events. That was invisible while the project set
emulate_touch_from_mouse and every click arrived as a synthetic touch; with
that setting gone, nothing selects and nothing scrolls, and there is no
other way to research anything on a desktop. The press and the pan are now
the behaviour, with touch and mouse as two ways in to them. Motion pans only
while the button is held - a hovering pointer must not drag the tree out
from under the reader - and the wheel scrolls, which is the first thing a
desktop player reaches for.
The panel also still had the buy button's hole in it. That button went away
in #83 and nothing moved down into the space, leaving ~180px of dead panel
below the readout while the tree above it stayed cramped. The readout moves
down 154px and the canvas takes the height: 1190px to 1344px.
The test drives clicks through BuildController rather than into the view,
since the missing piece was the routing - calling the view directly would
have passed against the bug as reported.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 23:29:34 +02:00
|
|
|
# One wheel notch: about a third of the default row spacing, so a notch is a
|
|
|
|
|
# clear step without skipping a whole depth of the tree.
|
|
|
|
|
const WHEEL_STEP: float = 90.0
|
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>
2026-08-11 15:00:41 +02:00
|
|
|
|
Implement #80, #81, #82 and #83
#80 - desktop combat. The mouse aims, the left button fires, and the number
keys pick the weapon: 1 the fleet's guns, 2 and up the abilities. The twin
sticks are built for thumbs; on a desktop they mean dragging a virtual stick
with a pointer that could simply BE the crosshair.
The pointer path hands its heading to FireStickController rather than driving
weapons itself, so exactly one thing in the scene aims a gun, and a held stick
wins - a finger on it is a deliberate act, a mouse resting somewhere is not.
Abilities gained fire_at(), which aims and fires in one call instead of
pretending to be a press-drag-release touch.
It is gated on the absence of a touchscreen, because Godot emulates a mouse
button from every touch and without that a tap on a phone would fire the guns.
The gate has an explicit override, and not for tidiness: headless reports a
touchscreen, so with the automatic answer alone the aiming could never be
exercised by a test and the only covered behaviour would be the guard.
#81 - the bottom-left control hint is gone, from both scenes and from
GameController.
#82 - a chaser is destroyed by the impact when it reaches a vessel. It used to
pass through the wreck untouched and line up another pass, which made a
ramming enemy strictly better than any other. Data-driven per enemy
(dies_on_contact), because a sniper that happens to drift into a ship has not
spent itself doing it - and routed through take_damage, so it dies exactly as
it dies to gunfire. The enemy is told about the impact BEFORE the ship
resolves its own damage, since resolving it can free the ship.
#83 - the research tree's buy button is gone; tapping a node twice buys it.
Deliberately "the same node twice" rather than a timed double-tap: no window
to miss on a slow phone, no accidental purchase while browsing, and the first
tap always does something visible, so the second is never a blind commitment.
The status line now says "Tap again to research" - with the button gone it is
the only thing distinguishing an affordable node from an unaffordable one.
test_enemy_collision asserted the exact behaviour #82 reversed, and now checks
the ram costs both. Suite 117/117.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 18:49:47 +02:00
|
|
|
# #83: a second tap on the node already selected buys it. The separate buy
|
|
|
|
|
# button is gone - it sat at the bottom of the panel, a long way from the node
|
|
|
|
|
# being chosen, and meant every purchase was look-down-tap-look-up. Tapping
|
|
|
|
|
# the thing you want is the shorter sentence.
|
|
|
|
|
#
|
|
|
|
|
# Deliberately "tap the SAME node twice" rather than a timed double-tap: no
|
|
|
|
|
# window to miss on a slow phone, no accidental purchase from a fast
|
|
|
|
|
# double-tap while browsing, and the first tap always does something visible
|
|
|
|
|
# (it selects, and the panel fills in with the cost and what it unlocks) so
|
|
|
|
|
# the second one is never a blind commitment.
|
|
|
|
|
signal node_confirmed(id: String)
|
|
|
|
|
|
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>
2026-08-11 15:00:41 +02:00
|
|
|
var selected_id: String = ""
|
2026-08-13 16:25:41 +02:00
|
|
|
var scroll_y: float = 0.0
|
|
|
|
|
var _dragging: bool = false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _content_height() -> float:
|
|
|
|
|
# The canvas deliberately exceeds the viewport. Four depths at this
|
|
|
|
|
# spacing leave clear breathing room around 92px-wide nodes and make each
|
|
|
|
|
# research branch read as a route rising away from Shipyards.
|
|
|
|
|
return maxf(size.y, GameState.max_skill_depth() * row_spacing + node_radius * 3.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _max_scroll() -> float:
|
|
|
|
|
return maxf(0.0, _content_height() - size.y)
|
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>
2026-08-11 15:00:41 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func _ready() -> void:
|
|
|
|
|
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
2026-08-13 16:25:41 +02:00
|
|
|
call_deferred("reset_scroll")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func reset_scroll() -> void:
|
|
|
|
|
# Start at Shipyards, the base of the tree, whenever the menu opens.
|
|
|
|
|
# The player then swipes up to travel along the branches.
|
|
|
|
|
scroll_y = _max_scroll()
|
|
|
|
|
queue_redraw()
|
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>
2026-08-11 15:00:41 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func node_screen_pos(node: Dictionary) -> Vector2:
|
2026-08-13 16:25:41 +02:00
|
|
|
# Every dependency depth gets its own widely-spaced horizontal row. The
|
|
|
|
|
# x lane is stable within a branch (set beside SKILL_TREE), so Logistics,
|
|
|
|
|
# Freight and Freight Yards rise vertically instead of zig-zagging across
|
|
|
|
|
# the display. scroll_y exposes the part of this tall canvas in view.
|
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>
2026-08-11 15:00:41 +02:00
|
|
|
var margin: float = node_radius * 1.4
|
|
|
|
|
return Vector2(
|
|
|
|
|
lerpf(margin, size.x - margin, float(node.x)),
|
2026-08-13 16:25:41 +02:00
|
|
|
_content_height() - margin - float(GameState.skill_depth(node.id)) * row_spacing - scroll_y
|
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>
2026-08-11 15:00:41 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _state_color(node: Dictionary) -> Color:
|
|
|
|
|
if GameState.is_unlocked(node.id):
|
|
|
|
|
return DONE_COLOR
|
|
|
|
|
if GameState.prerequisite_met(node.id):
|
|
|
|
|
return READY_COLOR
|
|
|
|
|
return LOCKED_COLOR
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Returns true when the press landed on a node, so the caller knows the
|
|
|
|
|
# touch was consumed.
|
|
|
|
|
func handle_touch(event: InputEventScreenTouch) -> bool:
|
#93: drive the research menu with a mouse, and close the hole under it
Two separate faults, both reported on the same ticket.
The menu was inert under a mouse. Input reaches the tree as raw events
through BuildController._unhandled_input, because a Control that consumes
them through the GUI system swallows the drag, and the tree only ever read
touch events. That was invisible while the project set
emulate_touch_from_mouse and every click arrived as a synthetic touch; with
that setting gone, nothing selects and nothing scrolls, and there is no
other way to research anything on a desktop. The press and the pan are now
the behaviour, with touch and mouse as two ways in to them. Motion pans only
while the button is held - a hovering pointer must not drag the tree out
from under the reader - and the wheel scrolls, which is the first thing a
desktop player reaches for.
The panel also still had the buy button's hole in it. That button went away
in #83 and nothing moved down into the space, leaving ~180px of dead panel
below the readout while the tree above it stayed cramped. The readout moves
down 154px and the canvas takes the height: 1190px to 1344px.
The test drives clicks through BuildController rather than into the view,
since the missing piece was the routing - calling the view directly would
have passed against the bug as reported.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 23:29:34 +02:00
|
|
|
return handle_press(event.pressed, event.position)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func handle_drag(event: InputEventScreenDrag) -> bool:
|
|
|
|
|
return handle_pan(event.relative)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# A left click is the same gesture as a tap. Other buttons are left alone so
|
|
|
|
|
# they stay available for whatever else wants them.
|
|
|
|
|
func handle_mouse_button(event: InputEventMouseButton) -> bool:
|
|
|
|
|
if event.button_index == MOUSE_BUTTON_WHEEL_UP and event.pressed:
|
|
|
|
|
return handle_pan(Vector2(0.0, WHEEL_STEP))
|
|
|
|
|
if event.button_index == MOUSE_BUTTON_WHEEL_DOWN and event.pressed:
|
|
|
|
|
return handle_pan(Vector2(0.0, -WHEEL_STEP))
|
|
|
|
|
if event.button_index != MOUSE_BUTTON_LEFT:
|
|
|
|
|
return false
|
|
|
|
|
return handle_press(event.pressed, event.position)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Only while the button is held. A touch drag cannot arrive without a finger
|
|
|
|
|
# on the glass, but the mouse moves across the menu the whole time it is
|
|
|
|
|
# open, and panning the tree under a hovering pointer would make the menu
|
|
|
|
|
# impossible to read.
|
|
|
|
|
func handle_mouse_motion(event: InputEventMouseMotion) -> bool:
|
|
|
|
|
if not _dragging:
|
|
|
|
|
return false
|
|
|
|
|
return handle_pan(event.relative)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func handle_press(pressed: bool, at: Vector2) -> bool:
|
2026-08-13 16:25:41 +02:00
|
|
|
if not visible:
|
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>
2026-08-11 15:00:41 +02:00
|
|
|
return false
|
#93: drive the research menu with a mouse, and close the hole under it
Two separate faults, both reported on the same ticket.
The menu was inert under a mouse. Input reaches the tree as raw events
through BuildController._unhandled_input, because a Control that consumes
them through the GUI system swallows the drag, and the tree only ever read
touch events. That was invisible while the project set
emulate_touch_from_mouse and every click arrived as a synthetic touch; with
that setting gone, nothing selects and nothing scrolls, and there is no
other way to research anything on a desktop. The press and the pan are now
the behaviour, with touch and mouse as two ways in to them. Motion pans only
while the button is held - a hovering pointer must not drag the tree out
from under the reader - and the wheel scrolls, which is the first thing a
desktop player reaches for.
The panel also still had the buy button's hole in it. That button went away
in #83 and nothing moved down into the space, leaving ~180px of dead panel
below the readout while the tree above it stayed cramped. The readout moves
down 154px and the canvas takes the height: 1190px to 1344px.
The test drives clicks through BuildController rather than into the view,
since the missing piece was the routing - calling the view directly would
have passed against the bug as reported.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 23:29:34 +02:00
|
|
|
if not pressed:
|
2026-08-13 16:25:41 +02:00
|
|
|
_dragging = false
|
|
|
|
|
return true
|
|
|
|
|
_dragging = true
|
#93: drive the research menu with a mouse, and close the hole under it
Two separate faults, both reported on the same ticket.
The menu was inert under a mouse. Input reaches the tree as raw events
through BuildController._unhandled_input, because a Control that consumes
them through the GUI system swallows the drag, and the tree only ever read
touch events. That was invisible while the project set
emulate_touch_from_mouse and every click arrived as a synthetic touch; with
that setting gone, nothing selects and nothing scrolls, and there is no
other way to research anything on a desktop. The press and the pan are now
the behaviour, with touch and mouse as two ways in to them. Motion pans only
while the button is held - a hovering pointer must not drag the tree out
from under the reader - and the wheel scrolls, which is the first thing a
desktop player reaches for.
The panel also still had the buy button's hole in it. That button went away
in #83 and nothing moved down into the space, leaving ~180px of dead panel
below the readout while the tree above it stayed cramped. The readout moves
down 154px and the canvas takes the height: 1190px to 1344px.
The test drives clicks through BuildController rather than into the view,
since the missing piece was the routing - calling the view directly would
have passed against the bug as reported.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 23:29:34 +02:00
|
|
|
var local: Vector2 = at - global_position
|
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>
2026-08-11 15:00:41 +02:00
|
|
|
for node in GameState.SKILL_TREE:
|
|
|
|
|
if local.distance_to(node_screen_pos(node)) <= node_radius * 1.25:
|
Implement #80, #81, #82 and #83
#80 - desktop combat. The mouse aims, the left button fires, and the number
keys pick the weapon: 1 the fleet's guns, 2 and up the abilities. The twin
sticks are built for thumbs; on a desktop they mean dragging a virtual stick
with a pointer that could simply BE the crosshair.
The pointer path hands its heading to FireStickController rather than driving
weapons itself, so exactly one thing in the scene aims a gun, and a held stick
wins - a finger on it is a deliberate act, a mouse resting somewhere is not.
Abilities gained fire_at(), which aims and fires in one call instead of
pretending to be a press-drag-release touch.
It is gated on the absence of a touchscreen, because Godot emulates a mouse
button from every touch and without that a tap on a phone would fire the guns.
The gate has an explicit override, and not for tidiness: headless reports a
touchscreen, so with the automatic answer alone the aiming could never be
exercised by a test and the only covered behaviour would be the guard.
#81 - the bottom-left control hint is gone, from both scenes and from
GameController.
#82 - a chaser is destroyed by the impact when it reaches a vessel. It used to
pass through the wreck untouched and line up another pass, which made a
ramming enemy strictly better than any other. Data-driven per enemy
(dies_on_contact), because a sniper that happens to drift into a ship has not
spent itself doing it - and routed through take_damage, so it dies exactly as
it dies to gunfire. The enemy is told about the impact BEFORE the ship
resolves its own damage, since resolving it can free the ship.
#83 - the research tree's buy button is gone; tapping a node twice buys it.
Deliberately "the same node twice" rather than a timed double-tap: no window
to miss on a slow phone, no accidental purchase while browsing, and the first
tap always does something visible, so the second is never a blind commitment.
The status line now says "Tap again to research" - with the button gone it is
the only thing distinguishing an affordable node from an unaffordable one.
test_enemy_collision asserted the exact behaviour #82 reversed, and now checks
the ram costs both. Suite 117/117.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 18:49:47 +02:00
|
|
|
var confirming: bool = selected_id == node.id
|
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>
2026-08-11 15:00:41 +02:00
|
|
|
selected_id = node.id
|
|
|
|
|
queue_redraw()
|
|
|
|
|
node_picked.emit(node.id)
|
Implement #80, #81, #82 and #83
#80 - desktop combat. The mouse aims, the left button fires, and the number
keys pick the weapon: 1 the fleet's guns, 2 and up the abilities. The twin
sticks are built for thumbs; on a desktop they mean dragging a virtual stick
with a pointer that could simply BE the crosshair.
The pointer path hands its heading to FireStickController rather than driving
weapons itself, so exactly one thing in the scene aims a gun, and a held stick
wins - a finger on it is a deliberate act, a mouse resting somewhere is not.
Abilities gained fire_at(), which aims and fires in one call instead of
pretending to be a press-drag-release touch.
It is gated on the absence of a touchscreen, because Godot emulates a mouse
button from every touch and without that a tap on a phone would fire the guns.
The gate has an explicit override, and not for tidiness: headless reports a
touchscreen, so with the automatic answer alone the aiming could never be
exercised by a test and the only covered behaviour would be the guard.
#81 - the bottom-left control hint is gone, from both scenes and from
GameController.
#82 - a chaser is destroyed by the impact when it reaches a vessel. It used to
pass through the wreck untouched and line up another pass, which made a
ramming enemy strictly better than any other. Data-driven per enemy
(dies_on_contact), because a sniper that happens to drift into a ship has not
spent itself doing it - and routed through take_damage, so it dies exactly as
it dies to gunfire. The enemy is told about the impact BEFORE the ship
resolves its own damage, since resolving it can free the ship.
#83 - the research tree's buy button is gone; tapping a node twice buys it.
Deliberately "the same node twice" rather than a timed double-tap: no window
to miss on a slow phone, no accidental purchase while browsing, and the first
tap always does something visible, so the second is never a blind commitment.
The status line now says "Tap again to research" - with the button gone it is
the only thing distinguishing an affordable node from an unaffordable one.
test_enemy_collision asserted the exact behaviour #82 reversed, and now checks
the ram costs both. Suite 117/117.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 18:49:47 +02:00
|
|
|
if confirming:
|
|
|
|
|
node_confirmed.emit(node.id)
|
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>
2026-08-11 15:00:41 +02:00
|
|
|
return true
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
#93: drive the research menu with a mouse, and close the hole under it
Two separate faults, both reported on the same ticket.
The menu was inert under a mouse. Input reaches the tree as raw events
through BuildController._unhandled_input, because a Control that consumes
them through the GUI system swallows the drag, and the tree only ever read
touch events. That was invisible while the project set
emulate_touch_from_mouse and every click arrived as a synthetic touch; with
that setting gone, nothing selects and nothing scrolls, and there is no
other way to research anything on a desktop. The press and the pan are now
the behaviour, with touch and mouse as two ways in to them. Motion pans only
while the button is held - a hovering pointer must not drag the tree out
from under the reader - and the wheel scrolls, which is the first thing a
desktop player reaches for.
The panel also still had the buy button's hole in it. That button went away
in #83 and nothing moved down into the space, leaving ~180px of dead panel
below the readout while the tree above it stayed cramped. The readout moves
down 154px and the canvas takes the height: 1190px to 1344px.
The test drives clicks through BuildController rather than into the view,
since the missing piece was the routing - calling the view directly would
have passed against the bug as reported.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 23:29:34 +02:00
|
|
|
func handle_pan(relative: Vector2) -> bool:
|
2026-08-13 16:25:41 +02:00
|
|
|
if not visible:
|
|
|
|
|
return false
|
|
|
|
|
# Dragging a finger upward reveals higher research; dragging downward
|
|
|
|
|
# returns to the root. Clamp hard so the canvas never leaves an empty gap.
|
#93: drive the research menu with a mouse, and close the hole under it
Two separate faults, both reported on the same ticket.
The menu was inert under a mouse. Input reaches the tree as raw events
through BuildController._unhandled_input, because a Control that consumes
them through the GUI system swallows the drag, and the tree only ever read
touch events. That was invisible while the project set
emulate_touch_from_mouse and every click arrived as a synthetic touch; with
that setting gone, nothing selects and nothing scrolls, and there is no
other way to research anything on a desktop. The press and the pan are now
the behaviour, with touch and mouse as two ways in to them. Motion pans only
while the button is held - a hovering pointer must not drag the tree out
from under the reader - and the wheel scrolls, which is the first thing a
desktop player reaches for.
The panel also still had the buy button's hole in it. That button went away
in #83 and nothing moved down into the space, leaving ~180px of dead panel
below the readout while the tree above it stayed cramped. The readout moves
down 154px and the canvas takes the height: 1190px to 1344px.
The test drives clicks through BuildController rather than into the view,
since the missing piece was the routing - calling the view directly would
have passed against the bug as reported.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 23:29:34 +02:00
|
|
|
scroll_y = clampf(scroll_y - relative.y, 0.0, _max_scroll())
|
2026-08-13 16:25:41 +02:00
|
|
|
queue_redraw()
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
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>
2026-08-11 15:00:41 +02:00
|
|
|
func _draw() -> void:
|
|
|
|
|
# Links first, so nodes sit on top of them.
|
|
|
|
|
for node in GameState.SKILL_TREE:
|
|
|
|
|
if node.requires == "":
|
|
|
|
|
continue
|
|
|
|
|
var parent: Dictionary = GameState.skill_node(node.requires)
|
|
|
|
|
if parent.is_empty():
|
|
|
|
|
continue
|
|
|
|
|
var from: Vector2 = node_screen_pos(parent)
|
|
|
|
|
var to: Vector2 = node_screen_pos(node)
|
|
|
|
|
# A link reads as "live" only once the step it leads to is actually
|
|
|
|
|
# reachable, so the eye follows the researched path outwards.
|
|
|
|
|
var lit: bool = GameState.is_unlocked(node.id)
|
|
|
|
|
var reachable: bool = GameState.prerequisite_met(node.id)
|
|
|
|
|
var link_color: Color = DONE_COLOR if lit else (READY_COLOR if reachable else LOCKED_COLOR)
|
|
|
|
|
var trim: Vector2 = (to - from).normalized() * node_radius
|
|
|
|
|
NeonDraw.draw_glowing_line(self, from + trim, to - trim, link_color,
|
|
|
|
|
1.0 if reachable else LOCKED_PULSE, 0.4,
|
|
|
|
|
NeonDraw.DEFAULT_CORE if reachable else LOCKED_CORE)
|
|
|
|
|
|
|
|
|
|
for node in GameState.SKILL_TREE:
|
|
|
|
|
var centre: Vector2 = node_screen_pos(node)
|
|
|
|
|
var colour: Color = _state_color(node)
|
|
|
|
|
var reach: bool = GameState.prerequisite_met(node.id)
|
|
|
|
|
var pulse: float = 1.0 if reach else LOCKED_PULSE
|
|
|
|
|
var core_stroke: Color = NeonDraw.DEFAULT_CORE if reach else LOCKED_CORE
|
|
|
|
|
var hexagon: PackedVector2Array = PackedVector2Array()
|
|
|
|
|
for i in range(6):
|
|
|
|
|
var a: float = (float(i) / 6.0) * TAU + PI / 6.0
|
|
|
|
|
hexagon.append(centre + Vector2(cos(a), sin(a)) * node_radius)
|
|
|
|
|
NeonDraw.draw_glowing_polygon(self, hexagon, colour, pulse, core_stroke)
|
|
|
|
|
|
|
|
|
|
# Researched nodes get a filled-in core; the rest stay hollow.
|
|
|
|
|
if GameState.is_unlocked(node.id):
|
|
|
|
|
var core: PackedVector2Array = PackedVector2Array()
|
|
|
|
|
for i in range(6):
|
|
|
|
|
var a: float = (float(i) / 6.0) * TAU + PI / 6.0
|
|
|
|
|
core.append(centre + Vector2(cos(a), sin(a)) * node_radius * 0.42)
|
|
|
|
|
NeonDraw.draw_glowing_polygon(self, core, colour, 1.0)
|
|
|
|
|
|
|
|
|
|
if node.id == selected_id:
|
|
|
|
|
var ring: PackedVector2Array = PackedVector2Array()
|
|
|
|
|
for i in range(6):
|
|
|
|
|
var a: float = (float(i) / 6.0) * TAU
|
|
|
|
|
ring.append(centre + Vector2(cos(a), sin(a)) * node_radius * 1.32)
|
|
|
|
|
NeonDraw.draw_glowing_polygon(self, ring, SELECTED_RING, 0.9)
|