Commit graph spaceship/PlanetSurface.tscn
Author SHA1 Message Date
Loïc Wisniewski
42535c7320 Fix UI overlaps: SpeedFader vs status labels, packed ship lab layout (#67, #71)
SpeedFader was missing anchor_top/anchor_bottom = 1.0 in both Main.tscn
and PlanetSurface.tscn, so it rendered TOP-anchored at a fixed y while
its siblings (LaserStatus, GasBombStatus) are bottom-anchored - on a
720x1280 viewport both landed in the same screen region, directly on
top of each other. Properly bottom-anchored SpeedFader now, positioned
above both status labels instead.

Separately, the ship lab's 7 vessel-name labels (#57-#59 grew the
roster from 4 to 7 without widening their row) were laid out 120px wide
on only a 90px pitch - a fixed 30px overlap between adjacent labels
regardless of text length, worse for genuinely long names like
"Interceptor"/"Transporter". Narrowed each label to fit its own lane
with real gaps, dropped the font size, and enabled word-wrap; shifted
the upgrade-card rows below down to give the now-taller label row room.
2026-08-17 19:47:09 +02:00
Loïc Wisniewski
facbd83dd2 Laser shooters consume energy from the fleet hold when firing (#61)
Mirrors #60's gas bomber ammunition exactly: ENERGY_PER_SHOT (8) drawn
from GameState.fleet_inventory before consuming the firing ship's
cooldown, the button's enabled rule requires affordability too, and an
unaffordable-but-otherwise-ready press still explains itself through a
new LaserStatus label rather than silently doing nothing.

Added two small generic GameState helpers this needed:
deposit_fleet_resource()/deposit_bank_resource(), counterparts to
#60's withdraw_fleet_resource() for resources (energy) with no
dedicated fleet_cargo/fleet_gas_cargo-style property. Along the way,
confirmed deposit_resource_to_building() on a "bank" building deposits
into that building's own per-record inventory, NOT bank_inventory
itself - only building_resource()/building_inventory() special-case
the "bank" capability to redirect there.

test_laser_vessel.gd (#58) updated to stock fleet energy first, since
shots are no longer free - same fix #60 required for the gas bomber's
own pre-existing tests. Also fixed test_laser_vessel.gd's own instance
of the bare-ClassName.CONST script-corruption trap (documented in
#60's commit): var skill: LaserController and a bare
LaserController.BEAM_HALF_WIDTH reference, mirrored into a local const
instead.
2026-08-16 19:11:31 +02:00
Loïc Wisniewski
17d0cf7e66 Gas bombers consume gas from the fleet hold when firing (#60)
Shots cost GAS_PER_SHOT (5) from GameState.fleet_inventory, not the
bank - the fleet has to be carrying its own ammunition. The button's
enabled rule now also requires affordability, and a press on a ready-
but-unaffordable bomber still consumes the touch to explain itself via
a new status label (GasBombStatus, IconButton's own "greyed but still
pressable" convention) rather than either silently doing nothing or
consuming the bomber's cooldown for a shot that never fires.

Also documents a real, reproducible GDScript trap hit while writing
tests/test_gas_bomber_ammo.gd: a bare `ClassName.SOME_CONST` reference
anywhere in a -s entry test script can corrupt method resolution for
the WHOLE script, making an unrelated, earlier `some_node._process()`
call fail with "Nonexistent function '_process' in base 'Nil'" despite
the node being valid and the method existing (confirmed via
has_method()). Root cause unconfirmed; fixed by mirroring the constant
's value into a local const instead of referencing the class directly,
in all three gas-bomber tests.

tests/test_gas_bomber.gd and test_gas_bomber_cooldowns.gd updated to
stock fleet gas first, since shots are no longer free.
2026-08-16 18:54:44 +02:00
Loïc Wisniewski
116e0f97d3 Add space-time distorder vessel and fleet speed fader (#59)
GameState.fleet_speed_multiplier (1.0-3.0) is applied at the point of
use - Fleet's cruise anchor and Ship's own max_speed/acceleration -
rather than baked into stored stats, so a stat upgrade researched
mid-boost can't corrupt the base value and the fader takes effect
immediately with no re-apply step.

Ship.arrive_radius is scaled by the same multiplier: at higher speeds
each physics tick covers a proportionally larger step, and once that
step outgrows a fixed arrive_radius the ship can overshoot its
snap-to-target zone every tick while chasing a target that's now also
receding faster - visible as the fleet stretching apart under boost.
Scaling it preserves the same "snaps onto its slot" tightness at any
boost level that 1.0x already has.

SpeedFader.gd is a self-contained vertical drag control (TouchJoystick's
raw-touch idiom, not _gui_input) that writes straight to the autoload
rather than reporting a value for something else to apply. It also
resets the multiplier to 1.0 the instant fleet.has_capability("distorder")
goes false, so a fleet can never be left permanently boosted by a vessel
it no longer has.

VesselDistorderComponent carries no per-shot behaviour (energy drain
while boosted is #62) - its presence is what matters. Registered in
VesselDefinitions, wired into the crafter/hangar/ship-lab menus per
#57's note (5/5/6 -> 6/6/7 columns).
2026-08-16 17:59:04 +02:00
Loïc Wisniewski
ed46cd77e6 Add laser ship vessel and its skill shot (#58)
A manually aimed instant beam, modelled on the gas bomber's press-drag-
release aiming (GasBombController/VesselGasBomberComponent) but firing
the moment the touch releases - no projectile travel time. Damage,
beam_range and cooldown are resolved per ship from base_stats/upgrades
(VesselLaserComponent.apply_stats(), the same pattern
VesselWeaponComponent uses) rather than hardcoded in the controller, so
the new damage/range/cooldown ship-lab upgrades actually reach them.
Cooldown lives on the component (per ship), not the controller (per
fleet) - two laser ships give two shots, the exact bug already fixed
for bombers in eb79efd.

LaserController.gd is reused unmodified by both Main.tscn and
PlanetSurface.tscn (relative ../Fleet, ../Camera2D, ../UI/LaserButton
paths, matching GasBombController), so it works identically in space
and on a landed surface. Wired into GameController's touch/drag
first-refusal chain alongside the bomb controller.

The crafter, hangar and ship-lab menus were already generic over
VesselDefinitions.fleet()/.all() - only the per-vessel Main.tscn nodes
are hand-placed, not the logic - so this repacks those rows to
5/5/6 columns and extends BuildController's onready arrays. No new
signal handlers were needed. Also fixes test_vessel_composition.gd's
hardcoded fleet-catalog size again (4 -> 5) now that laser is a fifth
fleet-eligible vessel.

Note for later tickets: a brand-new class_name script (LaserController)
resolves fine as a static type when its own file is the godot -s entry
point, but the identical type hint fails to parse ("Could not find
type") when the script holding it is only reached indirectly - e.g.
Main.tscn loaded from inside a _screenshot_tool.gd preview. Untyped
Node + duck-typed calls sidesteps it; GameController.gd's
laser_controller field documents this in place.
2026-08-16 17:29:53 +02:00
Loïc Wisniewski
0582181f26 Generalize the resource HUD readouts (#49)
Replaces ResourceCounter/GasCounter/CargoCounter/GasCargoCounter - one
hardcoded node plus one script per resource, times two scenes - with a
single ResourceReadout.gd that builds one banked + one fleet-hold row
per GameState.resource_types() entry at _ready(). A new resource added
to ResourceDefinitions gets a HUD row with no scene edit and no new
script, matching how ResourceBar and ResourceTransferList already
generalize over resources.

Preserves the existing visual layout (BANKED left, FLEET HOLD right),
the ore-on-bright-surface contrast (via a bright_background flag
replacing PlanetSurface's per-node color overrides), and the amber
fleet-in-transit color convention. Reuses the already-generic
ResourceGlyph for icons instead of NeonNumber's hardcoded ore hexagon.

Uncovered and fixed a second bug along the way: test_cargo_readout.gd
looked up its NeonNumber nodes immediately after add_child() in
_initialize(), but ResourceReadout builds its rows in _ready(), which
for a -s entry-point script does not fire synchronously during that
add_child() - it's deferred to the first frame. Moved the lookup to
after the existing elapsed wait in _process(), which is also what
tests/test_resource_readout.gd's setup does from the start.

Repo notes for the next agent, confirmed empirically this session:
- A -s entry-point script can only reach an autoload's CONSTANTS via
  the bare identifier (GameState.RESOURCE_ORE works everywhere). Calling
  any of its METHODS that way (GameState.resource_types()) fails to
  compile with "Identifier not found: GameState", even from
  _initialize(). Route method calls through the actual instance
  (root.get_node("GameState")) instead, as the rest of the suite already
  does.
- `git status --short | awk '{print $2}' | xargs touch` recreates
  deleted files as empty stubs, since touch on a missing path creates
  it. Filter out D-status paths before touching, or touch only the
  paths you know still exist.

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 08:30:10 +02:00
Loïc Wisniewski
c4e2ee7178 Add capture points and the planet conquest loop (#43)
CapturePoint tracks neutral/blocked/capturing/captured state by polling
proximity each frame (a ship in capture_radius, no enemy within
enemy_clear_radius), draining gradually rather than resetting when
interrupted, and calling GameState.mark_point_captured() on completion.
PlanetSetupHost now instantiates the real CapturePoint scene per
authored point (restoring already-captured state immediately on
re-landing) and spawns each setup's authored enemies, replacing the
placeholder markers from #42. CaptureLabel is a small self-polling HUD
(same convention as ResourceCounter) showing "CAPTURED n/m" or
"CONQUERED" with a one-shot fade on completion, themed per surface
profile alongside the existing bank/fleet captions.

test_planet_atmosphere_transition.gd's "no enemies on a landed
surface" assumption no longer holds now that setups can place guard
enemies by design - narrowed the check to what it actually needs to
prove: no space-side EnemySpawner/AsteroidField node leaks across the
scene swap.

Part of the #40 conquest-mechanic breakdown.
2026-08-15 21:22:10 +02:00
Loïc Wisniewski
1d9afcbc2e Add gas bomber skill-shot vessel 2026-08-15 04:45:15 +02:00
Loïc Wisniewski
2f68515f33 Compose planet surfaces from profiles 2026-08-14 16:05:32 +02:00
Loïc Wisniewski
8231e165ff Refactor multi-resource UI and transfers 2026-08-14 11:54:31 +02:00
Loïc Wisniewski
e9acca569e Queue lab research from local stock 2026-08-13 22:04:21 +02:00
Loïc Wisniewski
b641fa5891 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
Loïc Wisniewski
85814ce88c 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