Resolve pending gameplay tickets

This commit is contained in:
Loïc Wisniewski 2026-09-02 13:43:56 +02:00
commit c2ebbfaa2d
28 changed files with 618 additions and 188 deletions

View file

@ -2,9 +2,9 @@ extends SceneTree
# Headless gameplay test: run with
# godot --headless -s res://tests/test_payments_use_fleet_hold.gd
# Every payment draws on the bank AND on what the ships are carrying. The
# player can already shuttle resources between the two by hand, so refusing a
# purchase that a couple of taps would have made affordable was ceremony.
# A payment draws on the bank and, only at the place being paid, what the
# ships are carrying. Fleet cargo is physical: a building across the map may
# not reach into it (#180).
#
# It was also a deadlock. A new game starts with an empty bank, ore is mined
# into the fleet hold, and the only way to bank it is to deliver it to a
@ -22,8 +22,10 @@ extends SceneTree
# are physically carrying
# F. end to end: an empty bank and ore in the hold actually gets a
# mothership built through BuildController
# G. no payment position, or a distant one, cannot drain the fleet hold
const HOLD_ORE: int = 40
const PAYMENT_AT := Vector2(120.0, 60.0)
var game_state: Node
var scene: Node
@ -37,6 +39,7 @@ func _initialize() -> void:
game_state.reset_progression()
game_state.buildings.clear()
game_state.extractors.clear()
game_state.fleet_position = PAYMENT_AT
var cost: int = int(game_state.building_costs(GameState.BUILDING_MOTHERSHIP)[GameState.RESOURCE_ORE])
@ -50,13 +53,16 @@ func _initialize() -> void:
if game_state.bank_inventory.amount(GameState.RESOURCE_ORE) >= cost:
_fail("the bank was not actually empty, so nothing below tests the deadlock")
return
if not game_state.can_afford(cost):
if game_state.can_afford(cost):
_fail("a location-free affordability check reached into the fleet hold")
return
if not game_state.can_afford(cost, PAYMENT_AT):
_fail("a fleet holding %d ore could not afford a %d purchase" % [HOLD_ORE, cost])
return
print("bank 0 + hold %d can afford a %d purchase" % [HOLD_ORE, cost])
# --- B. bank first, hold for the shortfall -------------------------------
if not game_state.spend(cost):
if not game_state.spend(cost, PAYMENT_AT):
_fail("spend refused a purchase it had just called affordable")
return
if game_state.bank_inventory.amount(GameState.RESOURCE_ORE) != 0 \
@ -68,7 +74,7 @@ func _initialize() -> void:
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 8)
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, 30)
if not game_state.spend(20):
if not game_state.spend(20, PAYMENT_AT):
_fail("a 20 purchase was refused with 8 banked and 30 held")
return
if game_state.bank_inventory.amount(GameState.RESOURCE_ORE) != 0 \
@ -82,7 +88,7 @@ func _initialize() -> void:
# --- C. no partial charge -----------------------------------------------
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 5)
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, 5)
if game_state.spend(50):
if game_state.spend(50, PAYMENT_AT):
_fail("a purchase costing more than everything owned was allowed")
return
if game_state.bank_inventory.amount(GameState.RESOURCE_ORE) != 5 \
@ -93,6 +99,22 @@ func _initialize() -> void:
return
print("a purchase nobody can afford takes nothing")
# --- G. cargo does not teleport to a remote purchase ------------------
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 0)
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, cost)
game_state.fleet_position = PAYMENT_AT + Vector2(9000.0, 0.0)
if game_state.spend(cost, PAYMENT_AT):
_fail("a remote purchase took resources from the fleet hold")
return
if game_state.fleet_resource(GameState.RESOURCE_ORE) != cost:
_fail("a refused remote payment still drained the hold")
return
if game_state.spend(cost):
_fail("a purchase with no location reached into the fleet hold")
return
game_state.fleet_position = PAYMENT_AT
print("distant and location-free payments leave fleet cargo untouched")
# The hold has a real capacity (one starting interceptor carries 20), and
# set_amount() is clamped by it - which silently gave the checks below
# less than they asked for and made an affordable price look unaffordable.
@ -125,12 +147,12 @@ func _check_multi_resource_atomicity() -> bool:
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, 10)
game_state.bank_inventory.set_amount(GameState.RESOURCE_GAS, 1)
game_state.fleet_inventory.set_amount(GameState.RESOURCE_GAS, 1)
if game_state.spend_resources({GameState.RESOURCE_ORE: 15, GameState.RESOURCE_GAS: 40}):
if game_state.spend_resources({GameState.RESOURCE_ORE: 15, GameState.RESOURCE_GAS: 40}, PAYMENT_AT):
return _fail("a two-resource price was charged with only one of them affordable")
if game_state.available_ore() != 20 or game_state.available_resource(GameState.RESOURCE_GAS) != 2:
return _fail("a refused two-resource purchase still took some ore or gas (ore %d, gas %d)" \
% [game_state.available_ore(), game_state.available_resource(GameState.RESOURCE_GAS)])
if not game_state.spend_resources({GameState.RESOURCE_ORE: 15, GameState.RESOURCE_GAS: 2}):
if not game_state.spend_resources({GameState.RESOURCE_ORE: 15, GameState.RESOURCE_GAS: 2}, PAYMENT_AT):
return _fail("an affordable two-resource price was refused")
if game_state.available_ore() != 5 or game_state.available_resource(GameState.RESOURCE_GAS) != 0:
return _fail("a two-resource purchase charged wrongly (ore %d left, gas %d left)" \
@ -147,9 +169,9 @@ func _check_skill_tree() -> bool:
game_state.research_points = 999
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 0)
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, cost)
if not game_state.can_unlock(id):
if not game_state.can_unlock(id, PAYMENT_AT):
return _fail("the skill tree refused '%s' (cost %d) with the whole price in the fleet hold" % [id, cost])
if not game_state.unlock(id):
if not game_state.unlock(id, PAYMENT_AT):
return _fail("unlocking '%s' failed despite can_unlock saying yes" % id)
if game_state.fleet_inventory.amount(GameState.RESOURCE_ORE) != 0:
return _fail("the skill unlock did not charge the hold: %d left of %d" \
@ -177,9 +199,9 @@ func _check_ship_lab() -> bool:
game_state.research_points = 999
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 0)
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, cost)
if not game_state.can_buy_upgrade(id):
if not game_state.can_buy_upgrade(id, PAYMENT_AT):
return _fail("the ship lab refused upgrade '%s' (cost %d) with the whole price in the hold" % [id, cost])
if not game_state.buy_upgrade(id):
if not game_state.buy_upgrade(id, PAYMENT_AT):
return _fail("buying '%s' failed despite can_buy_upgrade saying yes" % id)
if game_state.fleet_inventory.amount(GameState.RESOURCE_ORE) != 0:
return _fail("the upgrade did not charge the hold: %d left of %d" \
@ -206,7 +228,7 @@ func _check_vessel_research() -> bool:
for resource_type in costs:
game_state.bank_inventory.set_amount(String(resource_type), 0)
game_state.fleet_inventory.set_amount(String(resource_type), int(costs[resource_type]))
if not game_state.unlock_vessel(vessel):
if not game_state.unlock_vessel(vessel, PAYMENT_AT):
return _fail("researching '%s' was refused with its whole price (%s) in the fleet hold" % [vessel, str(costs)])
for resource_type in costs:
if game_state.available_resource(String(resource_type)) != 0: