Provision transporters with hubs

This commit is contained in:
Loïc Wisniewski 2026-08-15 00:09:10 +02:00
commit d0a9f5b971
21 changed files with 309 additions and 588 deletions

View file

@ -1,144 +1,56 @@
extends SceneTree
# Headless gameplay test: run with
# godot --headless -s res://tests/test_hub_capacity.gd
# Forgejo #20 (part 7 of #12): a hub stations a limited number of
# transporters. More hubs, or research, raise it.
#
# Verifies:
# 1. capacity is per hub, so a second hub raises the total
# 2. ordering past the cap is REFUSED, not silently swallowed - the ticket
# is explicit that the player has to be told
# 3. a queued-but-unbuilt transporter still holds its berth, or ten could
# be ordered against two and only fail on arrival
# 4. researching Freight Yards raises capacity for hubs ALREADY BUILT, not
# just ones placed afterwards
# 5. the extra berth is immediately usable
var game_state: Node
var scene: Node
var field: Node
var crafter_id: int = 0
var stage: int = 0
var elapsed: float = 0.0
const CRAFTER_AT: Vector2 = Vector2(400.0, 0.0)
const HUB_A: Vector2 = Vector2(-500.0, 0.0)
const HUB_B: Vector2 = Vector2(-500.0, 600.0)
# A transport hub owns its logistics vessels directly. Building a hub
# provisions every researched slot immediately; Freight Yards provisions its
# extra slot on hubs that already exist.
func _initialize() -> void:
game_state = root.get_node("GameState")
game_state.buildings.clear()
game_state.extractors.clear()
game_state.transporters.clear()
game_state.rocks = 900
game_state.fleet_cargo = 0
game_state.stored_vessels.clear()
game_state.build_time_scale = 0.02
game_state.unlocked = ["shipyards", "logistics", "freight"]
scene = load("res://Main.tscn").instantiate()
root.add_child(scene)
field = scene.get_node("BuildingField")
var state: Node = root.get_node("GameState")
state.buildings.clear()
state.extractors.clear()
state.transporters.clear()
state.reset_progression()
state.rocks = 900
state.unlocked = ["shipyards", "logistics", "freight"]
var hub_a: int = state.add_building(state.BUILDING_TRANSPORT_HUB, Vector2(-500, 0))
var per_hub: int = state.hub_capacity()
if state.transporters.size() != per_hub or _at_hub(state, hub_a) != per_hub:
return _fail("first hub did not immediately provision %d transporters" % per_hub)
print("first hub provisioned %d transporters" % per_hub)
var hub_b: int = state.add_building(state.BUILDING_TRANSPORT_HUB, Vector2(-500, 600))
if state.transporters.size() != per_hub * 2 or _at_hub(state, hub_b) != per_hub:
return _fail("second hub did not bring its own transporters")
print("second hub raised logistics fleet to %d" % state.transporters.size())
var before: int = state.transporters.size()
if not state.unlock("freight_yards"):
return _fail("could not research Freight Yards")
var expected: int = state.hub_capacity() * state.hub_count()
if state.transporters.size() != expected or state.transporters.size() != before + 2:
return _fail("Freight Yards did not provision one transporter per existing hub")
if _at_hub(state, hub_a) != state.hub_capacity() \
or _at_hub(state, hub_b) != state.hub_capacity():
return _fail("researched transporters were assigned to the wrong hubs")
print("Freight Yards provisioned existing hubs: %d -> %d" % [before, expected])
if state.remove_buildings_of_type(state.BUILDING_TRANSPORT_HUB) != 2 \
or not state.transporters.is_empty():
return _fail("removing hubs left orphaned logistics transporters")
print("TEST_HUB_CAPACITY: PASS")
quit(0)
func _fill_berths(n: int) -> void:
# Queue and immediately fund n transporters.
for i in range(n):
if game_state.queue_order(crafter_id, GameState.PRODUCT_TRANSPORTER):
game_state.deposit_to_building(crafter_id, GameState.TRANSPORTER_COST)
func _at_hub(state: Node, hub_id: int) -> int:
var count: int = 0
for transporter in state.transporters:
if int(transporter.hub_id) == hub_id:
count += 1
return count
func _process(delta: float) -> bool:
elapsed += delta
match stage:
0:
scene.get_node("AsteroidField").set_process(false)
scene.get_node("EnemySpawner").set_process(false)
field.place(GameState.BUILDING_MOTHERSHIP, Vector2.ZERO)
field.place(GameState.BUILDING_SHIP_CRAFTER, CRAFTER_AT)
crafter_id = int(game_state.buildings[-1].id)
field.place(GameState.BUILDING_TRANSPORT_HUB, HUB_A)
elapsed = 0.0
stage = 1
return false
1:
if elapsed < 0.3:
return false
var per_hub: int = game_state.hub_capacity()
if game_state.transport_capacity() != per_hub:
print("TEST_HUB_CAPACITY: FAIL (one hub gives %d berths, per-hub capacity is %d)" % [
game_state.transport_capacity(), per_hub
])
quit(1)
return true
print("1 hub -> %d berths (%d per hub)" % [game_state.transport_capacity(), per_hub])
# --- 2 + 3. fill it, then try to exceed it ------------------
_fill_berths(per_hub)
if game_state.transporters_claimed() != per_hub:
print("TEST_HUB_CAPACITY: FAIL (claimed %d berths after ordering %d)" % [
game_state.transporters_claimed(), per_hub
])
quit(1)
return true
# Still only QUEUED at this point - the berth must already count.
if game_state.queue_order(crafter_id, GameState.PRODUCT_TRANSPORTER):
print("TEST_HUB_CAPACITY: FAIL (queued a transporter past the %d-berth cap)" % per_hub)
quit(1)
return true
print("cap enforced at %d, including orders not yet built" % per_hub)
# --- 1. a second hub raises the total -----------------------
field.place(GameState.BUILDING_TRANSPORT_HUB, HUB_B)
elapsed = 0.0
stage = 2
return false
2:
if elapsed < 0.3:
return false
if game_state.transport_capacity() != game_state.hub_capacity() * 2:
print("TEST_HUB_CAPACITY: FAIL (2 hubs give %d berths, expected %d)" % [
game_state.transport_capacity(), game_state.hub_capacity() * 2
])
quit(1)
return true
print("2 hubs -> %d berths" % game_state.transport_capacity())
if not game_state.can_station_transporter():
print("TEST_HUB_CAPACITY: FAIL (second hub did not free a berth)")
quit(1)
return true
# Fill the second hub too, so capacity is exhausted again.
_fill_berths(game_state.transport_capacity() - game_state.transporters_claimed())
if game_state.can_station_transporter():
print("TEST_HUB_CAPACITY: FAIL (still room after filling every berth)")
quit(1)
return true
# --- 4. research raises it for hubs ALREADY built -----------
var before: int = game_state.transport_capacity()
game_state.unlocked.append("freight_yards")
var after: int = game_state.transport_capacity()
print("researched Freight Yards: %d -> %d berths across %d existing hubs" % [
before, after, game_state.hub_count()
])
if after != before + game_state.hub_count():
print("TEST_HUB_CAPACITY: FAIL (research gave %d berths, expected +1 per existing hub)" % (after - before))
quit(1)
return true
# --- 5. and the new berth is usable straight away -----------
if not game_state.can_station_transporter():
print("TEST_HUB_CAPACITY: FAIL (research raised the number but left no usable berth)")
quit(1)
return true
if not game_state.queue_order(crafter_id, GameState.PRODUCT_TRANSPORTER):
print("TEST_HUB_CAPACITY: FAIL (could not order into the berth research just added)")
quit(1)
return true
print("ordered into the newly researched berth")
print("TEST_HUB_CAPACITY: PASS")
quit(0)
return true
return false
func _fail(message: String) -> void:
print("TEST_HUB_CAPACITY: FAIL (%s)" % message)
quit(1)