Buy fleet berths with Fleet Offices

#219: fleet capacity is a built, supplied thing rather than a researched
one. The eight fleet-size doctrines are gone; capacity is one berth free
plus one per Fleet Office standing. An office charges every 25s for the
vessel in its berth, priced off that vessel's own build cost so an expensive
hull is expensive to keep, and drawn from its own store the way a gas burner
draws gas. An office that cannot pay goes dark, its vessel is scuttled, and
the player is told through the same queue quest lines use - which reaches
them on a planet surface too.

An empty berth is idle rather than starved: an office is only failing while
there is something in it to fail, and holding it disabled would leave a
berth that could never be crewed and so could never be paid for again.

Ships have no hit points and a shield absorbs a hit wholesale, so the lost
vessel is scuttled rather than damaged - otherwise a shielded hull would
survive its own eviction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
This commit is contained in:
Loïc Wisniewski 2026-09-05 17:09:27 +02:00
commit 1f5fecd371
60 changed files with 695 additions and 243 deletions

View file

@ -131,19 +131,34 @@ func _process(delta: float) -> bool:
print("TEST_SKILL_TREE: FAIL (freight branch does not rise vertically)")
quit(1)
return true
var before_scroll: float = controller.skill_menu.tree.scroll_y
var tree_view: Control = controller.skill_menu.tree
var before_scroll: float = tree_view.scroll_y
var drag: InputEventScreenDrag = InputEventScreenDrag.new()
# Start at the root at the bottom; pull the canvas down to reveal
# the branches rising above it.
drag.relative = Vector2(0.0, 180.0)
controller.skill_menu.tree.handle_drag(drag)
if controller.skill_menu.tree.scroll_y >= before_scroll:
print("TEST_SKILL_TREE: FAIL (tree did not scroll upward)")
quit(1)
return true
tree_view.handle_drag(drag)
# #219 removed the eight-deep doctrine lane, which was what made
# the canvas taller than the view. A tree that FITS has nothing to
# scroll, so what is checked depends on which it is - and the
# clamp is worth pinning either way, since a canvas that drifts
# off its own top edge is the bug this gesture used to expose.
if before_scroll > 0.0:
if tree_view.scroll_y >= before_scroll:
print("TEST_SKILL_TREE: FAIL (tree did not scroll upward)")
quit(1)
return true
print("tree scrolls: %.0f -> %.0f" % [before_scroll, tree_view.scroll_y])
else:
if tree_view.scroll_y != 0.0:
print("TEST_SKILL_TREE: FAIL (a tree that fits scrolled to %.1f)" \
% tree_view.scroll_y)
quit(1)
return true
print("tree fits the view; the pan clamps at the top")
# --- prerequisites --------------------------------------------
if game_state.can_unlock("wing"):
if game_state.can_unlock("power_systems"):
print("TEST_SKILL_TREE: FAIL (deep node unlockable with no prerequisite)")
quit(1)
return true
@ -172,16 +187,21 @@ func _process(delta: float) -> bool:
scene.get_node("UI/BuildMenu").visible = false
# --- fleet capacity -------------------------------------------
# #219 took capacity out of the tree entirely: a berth is a Fleet
# Office standing somewhere. What this checks now is that the
# tree does NOT move the cap, and that the structure does.
var base_cap: int = game_state.max_fleet_size()
game_state.research_points = 2
game_state.unlock("coordination")
var wing_ok: bool = game_state.unlock("wing")
if game_state.max_fleet_size() != base_cap:
print("TEST_SKILL_TREE: FAIL (research still moves the fleet cap)")
quit(1)
return true
game_state.add_building(GameState.BUILDING_FLEET_OFFICE, Vector2(2400.0, 0.0))
var raised_cap: int = game_state.max_fleet_size()
print("fleet cap %d -> %d after Wing Doctrine (unlocked=%s)" % [
base_cap, raised_cap, wing_ok
])
if not (wing_ok and raised_cap == base_cap + 1):
print("TEST_SKILL_TREE: FAIL (capacity upgrade did not raise the cap)")
print("fleet cap %d -> %d after a Fleet Office" % [base_cap, raised_cap])
if raised_cap != base_cap + 1:
print("TEST_SKILL_TREE: FAIL (a Fleet Office did not raise the cap)")
quit(1)
return true