Commit graph

166 commits

Author SHA1 Message Date
Loïc Wisniewski
def224667d Add artillery enemy with telegraphed AOE (#46)
EnemyArtilleryComponent is a stationary siege piece (slow drift toward
the fleet, deliberately no pursuit component) that fires at the fleet
centroid on a long cooldown via a standalone ArtilleryShell -
telegraph ring (~1.8s, growing radius plus a filling countdown arc) ->
slow visible travel -> AOE explosion via Ship.take_damage() on every
player_ships node inside, same all-or-nothing shield absorption as
every other damage source. The shell is independent of the enemy that
fired it (added to the tree root, like EnemyProjectile/Projectile) so
the shooter dying mid-flight doesn't cancel an already-telegraphed
shot, and the impact point is fixed at fire time - never re-aimed at
where the fleet moves to afterward.

Part of the #40 conquest-mechanic breakdown.
2026-08-15 21:23:43 +02:00
Loïc Wisniewski
fc2b5ca74a Add standoff sniper enemy (#45)
EnemyStandoffComponent holds a preferred distance from the nearest
player ship instead of ramming (advance beyond it, retreat inside
retreat_range_factor of it, drift laterally in the comfortable band
between) - never paired with a pursuit component. EnemyRangedWeaponComponent
mirrors VesselWeaponComponent for an enemy: fires an EnemyProjectile
aimed at the target's CURRENT position rather than led, so a moving
ship can dodge it (projectile_speed 260 vs the interceptor's 460
max_speed). Registered in EnemyDefinitions so it's placeable from a
planet setup by enemy_id.

test_sniper_enemy.gd went through a few iterations to get its own
timing right: physics ticks can run ahead of a SceneTree test's own
_process() clock under this device's load, so the cooldown-violation
check reads the component's own fire_timer directly (a rising value
means it refired) instead of comparing projectile counts against a
wall-clock wait, and manually repositioned ships need their
move_target re-pinned or Ship's own steering silently drives them back
to the stale target.

Part of the #40 conquest-mechanic breakdown.
2026-08-15 21:23:01 +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
65fe19aeb6 Backfill missing script UID sidecar files
Unrelated pre-existing gap: 11 scripts never got a committed .uid,
exposed by running a full --headless --import pass while working on
#40's sub-tickets. Godot generates these deterministically from the
script path, so this is a no-op for behavior.
2026-08-15 14:44:02 +02:00
Loïc Wisniewski
a1fb239a50 Add a main menu scene (#47)
MainMenu.tscn is now project.godot's entry point: Play / Map Editor
(disabled, explains itself - #48 wires it up) / Quit (hidden in web
builds), styled like the rest of the game's menus and reusing the
starfield shader for its background.

Play calls GameState.reset_new_game(), so a second playthrough in the
same process starts fresh instead of inheriting whatever the previous
run left in the autoload - existing tests still load Main.tscn
directly and are unaffected.

Part of the #40 conquest-mechanic breakdown.
2026-08-15 14:43:46 +02:00
Loïc Wisniewski
f14ee656ea Add enemy foundation: definitions, components, ranged damage (#44)
Enemy.gd now resolves stats/hull/color from EnemyDefinition resources
and composes behaviour from capability components (EnemyPursuitComponent
holds today's chase-the-nearest-ship logic), the same shape Ship.gd
already uses for vessels. Fleet lookups go through a "player_fleet"
group instead of a hardcoded ../Fleet sibling path, so an enemy parented
anywhere (e.g. under a planet surface's SurfaceFeatures) still finds it.

Also closes a real gap: enemies previously had no way to damage the
fleet at range at all (Ship only reacted to physical contact,
Projectile.gd was player-only). Ship.take_damage() is now a proper
entry point - contact damage routes through it unchanged - and
EnemyProjectile lets an enemy component fire at player_ships. Both
ranged enemy archetypes in #45/#46 build on this.

Part of the #40 conquest-mechanic breakdown.
2026-08-15 14:43:29 +02:00
Loïc Wisniewski
5326852807 Add planet setups as authored resources (#42)
PlanetSetup/PlanetSetupSpawn define a curated capture-point/enemy
layout for a surface profile; PlanetSetups scans res://PlanetSetups/
at first use (not a hardcoded preload table) so dropping in a new
.tres is the only step needed to add one, and resolves the same
setup for the same planet deterministically, same pattern as
WorldGen.planet_data_at_chunk. PlanetSurfaceController composes a
PlanetSetupHost onto the landed surface alongside the existing
resource-provider feature scene.

Ships two starter setups (rocky_default, gas_default) with
placeholder capture-point markers; #43 replaces the markers with the
real CapturePoint scene through the same apply_setup() entry point.

Part of the #40 conquest-mechanic breakdown.
2026-08-15 14:41:37 +02:00
Loïc Wisniewski
a1ad6cc844 Add planet conquest state and gate extractor placement (#41)
Planets now need every capture point held before an extractor can go
up there: GameState tracks per-planet capture progress keyed by the
same stable planet_id as extractors, and BuildController's four
extractor placement checks (menu enable, status text, info popup,
confirm) all re-check conquest, not just proximity/duplication.

Part of the #40 conquest-mechanic breakdown.
2026-08-15 14:41:19 +02:00
Loïc Wisniewski
7da4531f25 Fill bomber hull during cooldown 2026-08-15 10:41:24 +02:00
Loïc Wisniewski
f5c60f8805 Prioritize queued logistics deliveries 2026-08-15 10:41:18 +02:00
Loïc Wisniewski
eb79efdafe Track gas bomb cooldowns per bomber 2026-08-15 10:12:33 +02:00
Loïc Wisniewski
1d9afcbc2e Add gas bomber skill-shot vessel 2026-08-15 04:45:15 +02:00
Loïc Wisniewski
d0a9f5b971 Provision transporters with hubs 2026-08-15 00:09:10 +02:00
Loïc Wisniewski
663648d949 Refine context actions and preserve fleet landings 2026-08-14 23:15:12 +02:00
Loïc Wisniewski
18a7d44db4 Make shield vessels unarmed hexagons 2026-08-14 22:46:30 +02:00
Loïc Wisniewski
3fc04f816a Supply lab research with transporters 2026-08-14 22:46:25 +02:00
Loïc Wisniewski
20ba0011fa Slow logistics vessel travel 2026-08-14 22:30:19 +02:00
Loïc Wisniewski
db0e6a0ec2 Add manual extractor resource transfers 2026-08-14 22:21:01 +02:00
Loïc Wisniewski
e2d4297ee5 Key extractors by stable planet identity 2026-08-14 22:16:40 +02:00
Loïc Wisniewski
14081ad230 Compose vessels from definitions and capabilities 2026-08-14 22:12:57 +02:00
Loïc Wisniewski
0504b24d8d Compose buildings from definitions and capabilities 2026-08-14 21:44:28 +02:00
Loïc Wisniewski
1721edd123 Store resources in generic inventories 2026-08-14 20:32:58 +02:00
Loïc Wisniewski
02643c76c4 Drive planets from definitions 2026-08-14 16:13:24 +02:00
Loïc Wisniewski
2f68515f33 Compose planet surfaces from profiles 2026-08-14 16:05:32 +02:00
Loïc Wisniewski
bcbf1b6515 Scale transfer UI for phones 2026-08-14 15:45:08 +02:00
Loïc Wisniewski
98fd654e3f Enlarge transfer amount controls 2026-08-14 14:01:14 +02:00
Loïc Wisniewski
e4df9bc41a Add transfer amounts and active gas vents 2026-08-14 13:44:10 +02:00
Loïc Wisniewski
31ebfa905e Guarantee a nearby gas giant 2026-08-14 12:07:50 +02:00
Loïc Wisniewski
8231e165ff Refactor multi-resource UI and transfers 2026-08-14 11:54:31 +02:00
Loïc Wisniewski
af5e08e5a1 Use gas glyph in HUD counter 2026-08-13 23:28:57 +02:00
Loïc Wisniewski
ac5b5415f5 Correct gas HUD and transfer rows 2026-08-13 23:25:46 +02:00
Loïc Wisniewski
7d05042f51 Redesign resource transfer interface 2026-08-13 23:02:09 +02:00
Loïc Wisniewski
cccdc85c9e Show gas icons in resource displays 2026-08-13 22:33:12 +02:00
Loïc Wisniewski
4c82155cf3 Support gas cargo transfers 2026-08-13 22:17:24 +02:00
Loïc Wisniewski
e9acca569e Queue lab research from local stock 2026-08-13 22:04:21 +02:00
Loïc Wisniewski
798deb48a1 Animate ship crafter deliveries 2026-08-13 21:39:05 +02:00
Loïc Wisniewski
fc68c4e8cf Add gas giant resource system 2026-08-13 21:07:35 +02:00
Loïc Wisniewski
8f7b58f2a8 Highlight selected ship lab cards 2026-08-13 20:56:24 +02:00
Loïc Wisniewski
d20a62a26f Expand contextual actions horizontally 2026-08-13 20:54:22 +02:00
Loïc Wisniewski
d79c756d5b Collapse contextual building actions 2026-08-13 20:49:15 +02:00
Loïc Wisniewski
1c9ac62e8c Differentiate ship lab upgrade icons 2026-08-13 20:13:32 +02:00
Loïc Wisniewski
8219893cda Improve ship lab upgrade selection 2026-08-13 16:45:08 +02:00
Loïc Wisniewski
5755568346 Route extractor ore through transporters 2026-08-13 16:38:41 +02:00
Loïc Wisniewski
13492a52e2 Make research tree vertically scrollable 2026-08-13 16:25:41 +02:00
Loïc Wisniewski
06081c41b0 Improve ship crafter and hangar controls 2026-08-12 23:08:43 +02:00
Loïc Wisniewski
82552464d2 Add timed craft queue and hangar 2026-08-12 22:47:46 +02:00
Loïc Wisniewski
b6427b080d Remove the one-way ore API superseded by the transfer menu
The loose end from #24. withdraw_to_fleet, unload_into_building and
deliver_cargo were the old one-way moves behind "Load ore" and "Store
ore here"; every one of them is a special case of transfer_to_building /
transfer_to_fleet, and none had a caller left outside tests. The
cargo_delivered signal had no listeners at all.

Not just tidying: unload_into_building routed through
deposit_to_building, which does NOT special-case the mothership - it
writes b.stock, while building_holdings() returns `rocks` for a
mothership and ignores stock entirely. So unload_into_building(
mothership_id) would have parked ore in a field nothing reads, with no
error. Nothing called it that way, but it is a plausibly-named function
sitting there to be reached for later. transfer_to_building handles it.

CARGO_LOAD_BATCH stays - the transfer menu's batch buttons use it; only
its role as withdraw_to_fleet's default argument is gone.

The #24 note expected folding these in to be costly for
test_cargo_readout, which places no buildings. It is not: the transfer
primitives work off GameState records rather than spawned nodes, so a
single add_building() in setup is enough and it stays a display test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 13:07:52 +02:00
Loïc Wisniewski
fe1c721d64 Restore the crafter binding lost in an over-wide edit
This is the actual cause of "the build a ship menu buttons do nothing",
reported four times. Opening the crafter panel stopped recording WHICH
crafter: when the auto-unload was replaced with an explicit store button,
the replaced block reached one line further up than intended and took
`craft_building_id = current_building.building_id` with it. Every handler
in the panel then bailed out at its `if craft_building_id == 0` guard,
while the panel itself still opened and Close still worked - because
Close is the only button that does not need the binding.

The silent `return` on that guard is what let it pass unnoticed, so it
now says so instead.

The test was complicit: it set craft_building_id by hand, which skipped
the exact step that was broken. It now parks the fleet on the crafter,
waits for the action button to debounce in, and presses it - the way the
game does. Verified against the broken code: it fails with
"opening the panel did not record which crafter".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 23:55:12 +02:00
Loïc Wisniewski
7a8f521941 Test the store button through a real tap, not the handler
The bug reported three times was in the input path, and every test I had
called the handlers directly - so they all passed while the button was
inert. test_crafter_feedback now taps the control the way a touch does,
including after the build menu has been opened and closed, which is the
sequence that used to block every button in the game.

Verified against the old guard: the direct-call assertions still pass,
and the real tap fails with "store button dead after the build menu was
opened and closed".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 23:44:29 +02:00
Loïc Wisniewski
ee9d6ad9c6 Fix every button being permanently blocked after closing the build menu
The real cause of "the crafter buttons do nothing", reported three
times. IconButton._blocked_by_modal tested `menu.visible`, but hiding a
parent leaves a child's own `visible` flag true - and the build menu's
info popup is itself one of the modal panels. So opening any building's
info popup and then closing the build menu with the X left an invisible
popup still claiming to be open, and from that moment EVERY button in
the game was blocked, with no way for a player to get out of it.

It tests is_visible_in_tree() now, and closing the build menu also
closes its popup rather than leaving it half-open.

My earlier attempts kept missing it because the probes called the
handlers directly - they exercised the crafter logic, which was fine all
along, and never the input path in that state. The regression test
reproduces the exact sequence: open the build menu, show a building's
info, hide the menu, then check an unrelated button still fires.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 23:41:00 +02:00