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 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 < 2.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 < 2: return _fail("burner only burned %d gas in 2.5s at rate %.1f/s" % [gas_burned, 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