Fix the stop stutter, and hide unresearched buildings from the menu

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>
This commit is contained in:
Loïc Wisniewski 2026-08-11 21:38:17 +02:00
commit 0e071877e2
6 changed files with 261 additions and 18 deletions

View file

@ -28,6 +28,18 @@ extends Node2D
@onready var build_status: Label = get_node("../UI/BuildMenu/StatusLabel")
@onready var build_extractor_button: IconButton = get_node("../UI/BuildMenu/ExtractorButton")
@onready var build_hub_button: IconButton = get_node("../UI/BuildMenu/TransportHubButton")
@onready var build_menu_entries: Array = [
[GameState.BUILDING_MOTHERSHIP, build_mothership_button,
get_node("../UI/BuildMenu/MothershipLabel"), get_node("../UI/BuildMenu/MothershipCost")],
[GameState.BUILDING_SHIP_CRAFTER, build_crafter_button,
get_node("../UI/BuildMenu/CrafterLabel"), get_node("../UI/BuildMenu/CrafterCost")],
[GameState.BUILDING_COMMAND_CENTER, build_command_button,
get_node("../UI/BuildMenu/CommandLabel"), get_node("../UI/BuildMenu/CommandCost")],
[GameState.BUILDING_EXTRACTOR, build_extractor_button,
get_node("../UI/BuildMenu/ExtractorLabel"), get_node("../UI/BuildMenu/ExtractorCost")],
[GameState.BUILDING_TRANSPORT_HUB, build_hub_button,
get_node("../UI/BuildMenu/TransportHubLabel"), get_node("../UI/BuildMenu/TransportHubCost")],
]
@onready var planet_field: Node2D = get_node("../PlanetField")
@onready var info_popup: Control = get_node("../UI/BuildMenu/InfoPopup")
@onready var info_title: Label = get_node("../UI/BuildMenu/InfoPopup/Panel/TitleLabel")
@ -185,6 +197,7 @@ func _process(delta: float) -> void:
# a planet lights the option up without reopening it.
build_extractor_button.enabled = _planet_under_fleet() != null \
and GameState.is_building_unlocked(GameState.BUILDING_EXTRACTOR)
build_extractor_button.queue_redraw()
if craft_menu.visible:
# Live, so a delivery arriving while the menu is open visibly works
@ -266,14 +279,58 @@ func _planet_under_fleet() -> Node2D:
return planet_field.get_planet_fleet_is_on()
# Grid geometry, shared with the layout below. Rows of two, and the menu is
# rebuilt from whatever is currently researched (#21) rather than being a
# fixed arrangement with holes punched in it.
const MENU_ROW_TOPS: Array = [-300.0, -10.0, 280.0]
const MENU_COL_X: float = 140.0
const MENU_BUTTON_HALF: float = 64.0
const MENU_BUTTON_H: float = 128.0
const MENU_LABEL_HALF: float = 150.0
const MENU_LABEL_H: float = 66.0
const MENU_COST_HALF: float = 60.0
const MENU_COST_H: float = 52.0
func _place_entry(node: Control, cx: float, top: float, half: float, height: float) -> void:
node.offset_left = cx - half
node.offset_right = cx + half
node.offset_top = top
node.offset_bottom = top + height
func _layout_build_menu() -> void:
# Structures the player has not researched are hidden outright (#21), and
# what remains is repacked so there are no gaps where they used to be.
# This reverses the earlier decision to always list everything greyed
# out; an option you cannot explain is apparently worse than one you
# cannot see yet.
var shown: Array = []
for entry in build_menu_entries:
var visible: bool = GameState.is_building_unlocked(entry[0])
entry[1].visible = visible
entry[2].visible = visible
entry[3].visible = visible
if visible:
shown.append(entry)
for i in range(shown.size()):
var row: int = i / 2
# Centred when it is the only one on its row, rather than hanging in
# the left column looking like something is missing beside it.
var alone: bool = (i == shown.size() - 1) and (i % 2 == 0)
var cx: float = 0.0 if alone else (-MENU_COL_X if i % 2 == 0 else MENU_COL_X)
var top: float = MENU_ROW_TOPS[mini(row, MENU_ROW_TOPS.size() - 1)]
_place_entry(shown[i][1], cx, top, MENU_BUTTON_HALF, MENU_BUTTON_H)
_place_entry(shown[i][2], cx, top + 134.0, MENU_LABEL_HALF, MENU_LABEL_H)
_place_entry(shown[i][3], cx, top + 204.0, MENU_COST_HALF, MENU_COST_H)
func _refresh_build_status() -> void:
# Every option is always listed, greyed when it cannot be used - showing
# them only sometimes would leave the player wondering whether the game
# has them at all. Greyed buttons still emit `pressed`, so each one can
# explain itself in the info popup.
build_crafter_button.enabled = GameState.is_building_unlocked(GameState.BUILDING_SHIP_CRAFTER)
build_command_button.enabled = GameState.is_building_unlocked(GameState.BUILDING_COMMAND_CENTER)
build_hub_button.enabled = GameState.is_building_unlocked(GameState.BUILDING_TRANSPORT_HUB)
_layout_build_menu()
# Researched but not usable right here is still shown, greyed: an
# extractor needs a planet under the fleet, which is a "not now" rather
# than a "not yet".
build_extractor_button.enabled = _planet_under_fleet() != null \
and GameState.is_building_unlocked(GameState.BUILDING_EXTRACTOR)