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:
parent
da8f6a81ea
commit
0e071877e2
6 changed files with 261 additions and 18 deletions
135
tests/test_build_menu_hides_locked.gd
Normal file
135
tests/test_build_menu_hides_locked.gd
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
extends SceneTree
|
||||
|
||||
# Headless gameplay test: run with
|
||||
# godot --headless -s res://tests/test_build_menu_hides_locked.gd
|
||||
# Forgejo #21: a structure the player has not researched should not be in the
|
||||
# build menu at all, rather than sitting there greyed out.
|
||||
#
|
||||
# The menu is a fixed grid in Main.tscn, so simply hiding entries would leave
|
||||
# holes where they used to be - it is repacked from whatever is researched.
|
||||
#
|
||||
# Verifies:
|
||||
# 1. with nothing researched only the mothership is offered (it is
|
||||
# deliberately never gated - it is where research happens)
|
||||
# 2. researching a structure adds it, and it is placed, not left at
|
||||
# whatever offsets it happened to have in the scene file
|
||||
# 3. no gaps: what is shown occupies consecutive grid slots
|
||||
# 4. an entry alone on its row is centred, not hanging in the left column
|
||||
# 5. researched-but-not-usable is still shown greyed (an extractor with no
|
||||
# planet under the fleet is "not now", not "not yet")
|
||||
|
||||
var game_state: Node
|
||||
var scene: Node
|
||||
var controller: Node
|
||||
var stage: int = 0
|
||||
var elapsed: float = 0.0
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
game_state = root.get_node("GameState")
|
||||
game_state.buildings.clear()
|
||||
game_state.extractors.clear()
|
||||
game_state.transporters.clear()
|
||||
game_state.reset_progression()
|
||||
game_state.rocks = 500
|
||||
scene = load("res://Main.tscn").instantiate()
|
||||
root.add_child(scene)
|
||||
controller = scene.get_node("BuildController")
|
||||
|
||||
|
||||
func _shown() -> Array:
|
||||
var out: Array = []
|
||||
for entry in controller.build_menu_entries:
|
||||
if entry[1].visible:
|
||||
out.append(entry)
|
||||
return out
|
||||
|
||||
|
||||
func _slots() -> Array:
|
||||
# (row, x) of every visible button, in menu order.
|
||||
var out: Array = []
|
||||
for entry in _shown():
|
||||
var b: Control = entry[1]
|
||||
out.append([b.offset_top, b.offset_left + (b.offset_right - b.offset_left) / 2.0])
|
||||
return out
|
||||
|
||||
|
||||
func _process(delta: float) -> bool:
|
||||
elapsed += delta
|
||||
match stage:
|
||||
0:
|
||||
if elapsed < 0.2:
|
||||
return false
|
||||
scene.get_node("AsteroidField").set_process(false)
|
||||
scene.get_node("EnemySpawner").set_process(false)
|
||||
|
||||
# --- 1. nothing researched ----------------------------------
|
||||
controller._refresh_build_status()
|
||||
var shown: Array = _shown()
|
||||
if shown.size() != 1 or shown[0][0] != GameState.BUILDING_MOTHERSHIP:
|
||||
var names: Array = []
|
||||
for e in shown:
|
||||
names.append(e[0])
|
||||
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (unresearched structures on offer: %s)" % [names])
|
||||
quit(1)
|
||||
return true
|
||||
# --- 4. and it is centred, being alone on its row -----------
|
||||
var only: Array = _slots()[0]
|
||||
if absf(only[1]) > 0.01:
|
||||
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (the only entry sits at x=%.1f instead of centred)" % only[1])
|
||||
quit(1)
|
||||
return true
|
||||
print("nothing researched -> only the mothership, centred")
|
||||
|
||||
# --- 2. research three more ---------------------------------
|
||||
game_state.unlocked = ["shipyards", "logistics", "coordination"]
|
||||
controller._refresh_build_status()
|
||||
stage = 1
|
||||
return false
|
||||
1:
|
||||
var shown2: Array = _shown()
|
||||
if shown2.size() != 4:
|
||||
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (researched 3, expected 4 entries, got %d)" % shown2.size())
|
||||
quit(1)
|
||||
return true
|
||||
for e in shown2:
|
||||
if e[0] == GameState.BUILDING_TRANSPORT_HUB:
|
||||
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (the unresearched transport hub is still listed)")
|
||||
quit(1)
|
||||
return true
|
||||
|
||||
# --- 3. consecutive slots, no holes -------------------------
|
||||
var slots: Array = _slots()
|
||||
var rows: Array = controller.MENU_ROW_TOPS
|
||||
for i in range(slots.size()):
|
||||
var want_row: float = rows[i / 2]
|
||||
if absf(slots[i][0] - want_row) > 0.01:
|
||||
print("slot %d at y=%.1f, expected row %d at y=%.1f" % [i, slots[i][0], i / 2, want_row])
|
||||
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (a gap was left where a hidden entry used to be)")
|
||||
quit(1)
|
||||
return true
|
||||
var want_x: float = -controller.MENU_COL_X if i % 2 == 0 else controller.MENU_COL_X
|
||||
if absf(slots[i][1] - want_x) > 0.01:
|
||||
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (entry %d at x=%.1f, expected %.1f)" % [i, slots[i][1], want_x])
|
||||
quit(1)
|
||||
return true
|
||||
print("4 entries packed into consecutive slots, no gaps")
|
||||
|
||||
# --- 5. researched but not usable is shown, greyed ----------
|
||||
var extractor_shown: bool = false
|
||||
for e in shown2:
|
||||
if e[0] == GameState.BUILDING_EXTRACTOR:
|
||||
extractor_shown = true
|
||||
if e[1].enabled:
|
||||
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (extractor offered as usable with no planet under the fleet)")
|
||||
quit(1)
|
||||
return true
|
||||
if not extractor_shown:
|
||||
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (researched extractor hidden - 'not now' is not 'not yet')")
|
||||
quit(1)
|
||||
return true
|
||||
print("researched extractor shown but greyed, with no planet in reach")
|
||||
print("TEST_BUILD_MENU_HIDES_LOCKED: PASS")
|
||||
quit(0)
|
||||
return true
|
||||
return false
|
||||
Loading…
Reference in a new issue