spaceship/tests/test_gas_burner.gd
2026-09-03 00:12:08 +02:00

170 lines
7.4 KiB
GDScript

extends SceneTree
# Headless gameplay test: run with
# godot --headless -s res://tests/test_gas_burner.gd
# Forgejo #54: gas burner building + the energy resource, stored/moved
# exactly like ore and gas.
#
# Modeled on tests/test_extractor_income.gd's shape: pure GameState state,
# no scene loaded, poking buildings/inventories directly and waiting out
# real production/transporter timing with generous margins rather than
# fixed frame counts (SceneTree physics can run ahead of _process(), trap 5
# in the repo notes).
#
# Covers:
# A. energy is fully generic - in resource_types(), has its own
# ResourceDefinition/glyph/colour - with no per-resource code anywhere
# (ResourceBar/ResourceTransferList already iterate resource_types())
# B. a burner with local gas converts it to energy, into its OWN
# inventory, at the configured rate; with no gas it stalls and never
# touches the bank
# C. the building is gated behind its own research node
# D. a hub transporter both delivers gas to a burner and collects its
# energy, following the existing supply/pickup dispatch paths
var game_state: Node
var stage: int = 0
var elapsed: float = 0.0
var mothership_id: int = 0
var hub_id: int = 0
var burner_id: int = 0
var load_limit: int = 0
var energy_before_stall: int = 0
var conversion_rate: float = 0.0
var conversion_yield: int = 0
const MOTHERSHIP_AT := Vector2.ZERO
const HUB_AT := Vector2(150.0, 0.0)
const BURNER_AT := Vector2(400.0, 0.0)
func _initialize() -> void:
game_state = root.get_node("GameState")
game_state.rocks = 0
game_state.gas = 0
game_state.buildings.clear()
game_state.extractors.clear()
game_state.transporters.clear()
game_state.reset_progression()
mothership_id = game_state.add_building(GameState.BUILDING_MOTHERSHIP, MOTHERSHIP_AT)
burner_id = game_state.add_building(GameState.BUILDING_GAS_BURNER, BURNER_AT)
game_state.deposit_resource_to_building(burner_id, GameState.RESOURCE_GAS, 20)
var logistics: Resource = game_state.building_logistics_profile(game_state.find_building(burner_id))
conversion_rate = logistics.conversion_rate
conversion_yield = logistics.conversion_yield
if not is_equal_approx(conversion_rate, 0.2):
_fail("burner conversion rate is %.2f/s, expected the reduced 0.20/s" % conversion_rate)
func _process(delta: float) -> bool:
elapsed += delta
match stage:
0:
# --- A. energy is fully generic -------------------------------
if not game_state.resource_types().has(GameState.RESOURCE_ENERGY):
return _fail("energy is not in resource_types() - the HUD/transfer list would never show it")
var definition: Resource = game_state.resource_definition(GameState.RESOURCE_ENERGY)
if definition == null or definition.glyph_scene == null:
return _fail("energy has no ResourceDefinition or glyph_scene")
var energy_color: Color = game_state.resource_color(GameState.RESOURCE_ENERGY)
if energy_color == game_state.resource_color(GameState.RESOURCE_ORE) \
or energy_color == game_state.resource_color(GameState.RESOURCE_GAS):
return _fail("energy shares a colour with ore or gas")
print("energy is generic: in resource_types(), own definition, own colour %s" % energy_color)
# --- B. produces energy from local gas at the configured rate -
if elapsed < 5.5:
return false
var gas_left: int = game_state.building_resource(burner_id, GameState.RESOURCE_GAS)
var energy_made: int = game_state.building_resource(burner_id, GameState.RESOURCE_ENERGY)
var gas_burned: int = 20 - gas_left
if gas_burned < 1:
return _fail("burner burned no gas in 5.5s at rate %.2f/s" % conversion_rate)
if energy_made != gas_burned * conversion_yield:
return _fail("burner made %d energy for %d gas burned, expected %dx" % [
energy_made, gas_burned, conversion_yield
])
if game_state.rocks != 0 or game_state.gas != 0:
return _fail("conversion touched the bank (rocks=%d gas=%d) - it must only use local stock" % [
game_state.rocks, game_state.gas
])
print("burner converted %d gas -> %d energy locally, bank untouched" % [gas_burned, energy_made])
# --- stall with no gas -----------------------------------------
var burner_inventory: RefCounted = game_state.building_inventory(burner_id)
burner_inventory.withdraw(GameState.RESOURCE_GAS, burner_inventory.amount(GameState.RESOURCE_GAS))
energy_before_stall = game_state.building_resource(burner_id, GameState.RESOURCE_ENERGY)
elapsed = 0.0
stage = 1
return false
1:
if elapsed < 1.5:
return false
var energy_now: int = game_state.building_resource(burner_id, GameState.RESOURCE_ENERGY)
if energy_now != energy_before_stall:
return _fail("burner made energy with no local gas (energy %d -> %d) - it must stall" % [
energy_before_stall, energy_now
])
print("stalled with no gas: energy held at %d" % energy_now)
# --- C. gated behind research -----------------------------------
if game_state.is_building_unlocked(GameState.BUILDING_GAS_BURNER):
return _fail("gas_burner is buildable with no research at all")
var gate: String = game_state.research_for_building(GameState.BUILDING_GAS_BURNER)
if gate == "":
return _fail("no research node gates gas_burner")
game_state.unlocked = ["shipyards", "logistics", "freight", "power_systems"]
if not game_state.is_building_unlocked(GameState.BUILDING_GAS_BURNER):
return _fail("still locked after researching '%s'" % gate)
print("locked behind research '%s', unlockable" % gate)
# --- D. logistics: reset for a fresh delivery/pickup test -----
game_state.buildings.clear()
game_state.transporters.clear()
game_state.gas = 200
mothership_id = game_state.add_building(GameState.BUILDING_MOTHERSHIP, MOTHERSHIP_AT)
hub_id = game_state.add_building(GameState.BUILDING_TRANSPORT_HUB, HUB_AT)
burner_id = game_state.add_building(GameState.BUILDING_GAS_BURNER, BURNER_AT)
if game_state.transporters.size() != game_state.hub_capacity():
return _fail("hub did not provision its transporters")
load_limit = game_state.transporter_load_limit()
elapsed = 0.0
stage = 2
return false
2:
# --- a transporter delivers gas to the burner ------------------
if game_state.building_resource(burner_id, GameState.RESOURCE_GAS) > 0:
print("transporter delivered gas to the burner: %d" % [
game_state.building_resource(burner_id, GameState.RESOURCE_GAS)
])
# Force the burner's local energy up to a full load so pickup
# dispatch fires immediately, rather than waiting out the
# genuinely slow conversion rate for a second, separate check.
game_state.building_inventory(burner_id).set_amount(GameState.RESOURCE_ENERGY, load_limit)
elapsed = 0.0
stage = 3
return false
if elapsed > 10.0:
return _fail("no gas was ever delivered to the burner")
return false
3:
# --- a transporter collects the burner's energy to the bank ---
if game_state.banked_resource(GameState.RESOURCE_ENERGY) >= load_limit:
print("transporter collected %d energy to the bank" % game_state.banked_resource(GameState.RESOURCE_ENERGY))
print("TEST_GAS_BURNER: PASS")
quit(0)
return true
if elapsed > 10.0:
return _fail("burner's energy was never collected (bank=%d burner=%d)" % [
game_state.banked_resource(GameState.RESOURCE_ENERGY),
game_state.building_resource(burner_id, GameState.RESOURCE_ENERGY),
])
return false
return false
func _fail(message: String) -> bool:
print("TEST_GAS_BURNER: FAIL (%s)" % message)
quit(1)
return true