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.
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>
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>
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>
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>
Forgejo #5. A new building gated behind a "Vessel Science" research
node. Its own screen upgrades each vessel type - weapon, movement,
shield and freight - and unlocks the shield vessel, which the ship
crafter can then build.
Vessel research is deliberately NOT more nodes in the mothership tree:
that tree gates what may be built at all and is a strategic spine, while
these are repeatable tuning on vessels already owned. Levels cost
base * (level + 1), so the third point in something is not the price of
the first. Upgrades are read live rather than baked in at build time, so
researching one improves the ships already flying.
The shield is personal, not a bubble: it absorbs a hit that would
destroy the vessel - ships have no health, contact kills - and is then
down until it cycles. Its ring is a NeonPiece child toggled with
`visible` per #10, not a redraw of the hull.
The build menu now holds six entries across three rows, which the #21
repacking already handles.
Enemies still have no weapon, so "blocks shots" has nothing to block
yet; that is filed separately rather than widened into this ticket.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Groundwork for #5. vessels_owed carries the VESSEL TYPE rather than a
bare count, so a shield vessel finishing while the player is on a planet
comes back as a shield vessel. Fleet.add_ship takes a type, and vessels
already flying are re-statted when an upgrade is researched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The crafter buttons doing nothing was not the crafter: Ship.gd was left
mid-edit and failed to parse (undeclared SHIELD_COLOR/SHIELD_RADIUS, and
a spawn() that only exists on the concrete VFX subclass). A script that
does not compile takes Main.tscn's whole node tree with it, so every
button in the game was dead. The project directory is the one being
played from, so an unfinished edit there is a broken build - finish or
revert before stopping.
With that fixed, the real gap underneath: an order the crafter cannot
pay for is ACCEPTED and queued (#16), which is indistinguishable from a
dead button. Ordering now reports either "building now" or "queued - N
more ore must be delivered here", and the transient message is rendered
LAST so it is the first line read rather than buried under the standing
fleet-capacity note.
test_crafter_feedback covers all three presses and asserts the message
survives the per-frame refresh, which is the specific way this looked
broken before.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Forgejo #13: releasing the stick paused, then moved again before
settling. stop() aims each ship at a braking point ahead of it, but the
ship's own velocity was ~0 - the anchor had been carrying it - so it
started from REST, accelerated into its own stopping distance, and
halted a second time. Measured: 0 -> 45 -> 90 -> 135 -> 180 -> brake.
stop() now hands the fleet's motion to the ship, so the release is one
continuous decelerating coast (222 -> 0) to the identical endpoint.
test_joystick_control now asserts the speed one frame after release is a
real fraction of cruise, and never rises afterwards.
Forgejo #21: unresearched structures are no longer listed greyed out.
They are hidden, and the menu repacks around what is left - a fixed grid
with entries hidden would otherwise leave holes where they used to be.
An entry alone on its row is centred rather than hanging in the left
column. Researched-but-not-usable is still shown greyed: an extractor
with no planet under the fleet is "not now", not "not yet".
This reverses a documented decision (the old comment argued that always
listing everything stops the player wondering whether the game has it at
all), so test_skill_tree's matching assertion was updated from
greyed-but-listed to hidden. The gate itself is unchanged and still
refuses a build forced past the menu.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three play-testing reports.
1. Buttons behind an open panel were still clickable. There are two
input paths and both leaked. GUI clicks: the research and formation
panels cannot block with mouse_filter = STOP, because their contents
are driven from raw _unhandled_input and a Control consuming touches
through the GUI system swallows them first - so the panels stay
transparent and IconButton now checks whether it is behind one, via a
"modal_menus" group. Raw input: a touch landing on the research panel
but missing a node was left unhandled, so it fell through to
GameController - which sits on the ROOT node and sees whatever its
children ignore - and moved the fleet or pinch-zoomed the world.
2. "Store ore here" looked dead. It was working: the confirmation was
overwritten one frame later by the per-frame status refresh added in
#16, so it flashed for a single frame. Craft-panel messages now
outlive a refresh (3s), and the button stays ENABLED with an empty
hold so pressing it can say why - a greyed button that cannot explain
itself is the same dead-button problem.
3. The mothership's description could overlap the button below it. The
description block was positioned from the TOP while the buttons were
anchored to the BOTTOM, so the gap between them was whatever the
screen height left over - overlapping at 1280 and separating at 960.
It is one bottom-anchored stack now, checked at both.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Forgejo #20, part 7 of #12 and the last of it. A hub stations
BASE_HUB_CAPACITY (2) transporters. Total berths are hubs x per-hub
capacity, so building another hub or researching "Freight Yards" both
raise it - which is the choice #12 asks the player to make.
Capacity follows the same shape as max_fleet_size() (sum a bonus over
`unlocked`) rather than being a second mechanism doing the same job, so
research applies to hubs already built, not only to ones placed
afterwards.
A queued-but-unbuilt transporter holds its berth. Without that a player
could order ten against two berths and only discover the problem when
they refused to appear. Ordering past the cap is refused outright, and
the two refusals - no hub at all, versus every berth taken - say which,
because "nothing happened" is exactly the bug reported against the
craft button.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Forgejo #19, part 6 of #12. Transporters are built at the ship crafter,
fly themselves to the nearest hub and wait there. When a building is
short of what its queue needs, the nearest idle transporter runs
hub -> mothership -> building -> hub. Ore leaves the bank when it is
loaded and lands in the building's stock on arrival, so what is drawn
and what happens are the same event - the extractor hauler's rule.
Production orders are now TYPED (interceptor / transporter) rather than
a bare count, since the crafter builds two different things. One at a
time is dispatched per building: without that, every idle transporter
chases the same shortfall and they all arrive carrying ore nobody needs.
Three fixes from play-testing feedback:
- Storing ore into a building is now a deliberate button press, not a
side effect of opening the menu.
- The mothership's load-ore controls were positioned from the TOP of the
screen, so on any viewport shorter than ~1300px the label fell off the
bottom. They are anchored to the bottom edge now, and the label had no
explicit font size so it inherited the theme's 78px and wrapped off
screen. Checked at 720x960 and 720x1280.
- The craft button looked live and did nothing. It was not the transport
work: BASE_FLEET_SIZE is 5 and a new game starts with exactly 5 ships,
so the fleet is at its cap from the first frame and every interceptor
order was refused. The button is now visibly disabled and says why.
The underlying balance (crafter useless until Wing Doctrine) is left
alone - that is a design call, not a bug to quietly change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>