extends SceneTree # Headless gameplay test: run with # godot --headless -s res://tests/test_distorder_energy.gd # Forgejo #62: sustaining a boost above the fleet's default speed drains # energy from the fleet hold, continuously, proportional to how far above # 1.0x the multiplier is. # # Most of this is pure GameState math (GameState._advance_distorder_drain, # called directly the same way other tests call _advance_lab_research/ # _advance_production) - no Main.tscn/Fleet needed for it, which is itself # proof the drain is scene-independent (it lives in the autoload's own # _process(), unlike a component's tick() which only runs while its ship's # scene is loaded) and so "continues correctly across a landing and # return" without a scene-swap simulation to re-prove that. # # Covers: # A. sustained boost drains energy at the configured rate; half boost # drains proportionally less than full boost # B. a small multiplier still drains a nonzero amount over time - no # fractional rounding loss # D. at exactly 1.0x, energy is never consumed # C. hitting zero energy forces the multiplier back to 1.0, and the # SpeedFader visibly follows it down with the reason shown var game_state: Node var main: Node var fleet: Node var fader: Node var stage: int = 0 var elapsed: float = 0.0 func _initialize() -> void: game_state = root.get_node("GameState") game_state.reset_progression() game_state.fleet_speed_multiplier = 1.0 game_state.deposit_fleet_resource(GameState.RESOURCE_ENERGY, 1000) func _process(delta: float) -> bool: elapsed += delta match stage: 0: # --- D. nothing drains at exactly 1.0x ---------------------------- var before: int = game_state.fleet_resource(GameState.RESOURCE_ENERGY) for i in range(60): game_state._advance_distorder_drain(1.0) if game_state.fleet_resource(GameState.RESOURCE_ENERGY) != before: return _fail_now("energy drained at 1.0x: %d -> %d" \ % [before, game_state.fleet_resource(GameState.RESOURCE_ENERGY)]) print("no drain at 1.0x, even over 60 simulated seconds") # --- A. full boost drains at the configured rate ------------------ game_state.fleet_speed_multiplier = GameState.MAX_FLEET_SPEED_MULTIPLIER var full_before: int = game_state.fleet_resource(GameState.RESOURCE_ENERGY) for i in range(10): game_state._advance_distorder_drain(1.0) var full_drained: int = full_before - game_state.fleet_resource(GameState.RESOURCE_ENERGY) if full_drained != 20: return _fail_now("full boost drained %d over 10s, expected 20 (ENERGY_DRAIN_PER_SECOND=2.0)" \ % full_drained) print("full boost drained %d over 10s, matches ENERGY_DRAIN_PER_SECOND" % full_drained) # --- A continued: half boost drains proportionally less ----------- game_state.fleet_speed_multiplier = 2.0 var half_before: int = game_state.fleet_resource(GameState.RESOURCE_ENERGY) for i in range(10): game_state._advance_distorder_drain(1.0) var half_drained: int = half_before - game_state.fleet_resource(GameState.RESOURCE_ENERGY) if half_drained <= 0 or half_drained >= full_drained: return _fail_now("half boost drained %d over 10s, expected between 0 and full's %d" \ % [half_drained, full_drained]) print("half boost drained %d over 10s - less than full's %d, still nonzero" \ % [half_drained, full_drained]) # --- B. a small multiplier still drains a nonzero amount ---------- game_state.fleet_speed_multiplier = 1.05 var small_before: int = game_state.fleet_resource(GameState.RESOURCE_ENERGY) for i in range(600): game_state._advance_distorder_drain(0.1) var small_drained: int = small_before - game_state.fleet_resource(GameState.RESOURCE_ENERGY) if small_drained <= 0: return _fail_now("a small (1.05x) sustained boost drained nothing over 60s - fractional rounding loss") print("small boost (1.05x) still drained %d over 60s (no rounding loss)" % small_drained) # --- C. running out resets the multiplier and raises the notice --- game_state.withdraw_fleet_resource(GameState.RESOURCE_ENERGY, game_state.fleet_resource(GameState.RESOURCE_ENERGY)) game_state.deposit_fleet_resource(GameState.RESOURCE_ENERGY, 5) game_state.fleet_speed_multiplier = GameState.MAX_FLEET_SPEED_MULTIPLIER game_state.boost_depleted_notice = false for i in range(10): game_state._advance_distorder_drain(1.0) if game_state.fleet_resource(GameState.RESOURCE_ENERGY) != 0: return _fail_now("fleet energy did not reach exactly 0, got %d" \ % game_state.fleet_resource(GameState.RESOURCE_ENERGY)) if not is_equal_approx(game_state.fleet_speed_multiplier, 1.0): return _fail_now("multiplier was not forced back to 1.0 after running out: %f" \ % game_state.fleet_speed_multiplier) if not game_state.boost_depleted_notice: return _fail_now("no depleted notice was raised when energy ran out") print("running out of energy dropped the multiplier to 1.0 and raised the notice") stage = 1 elapsed = 0.0 return false 1: # --- C continued: the SpeedFader visibly follows it down ---------- if elapsed < 0.2: return false main = load("res://world/Main.tscn").instantiate() root.add_child(main) fleet = main.get_node("Fleet") fader = main.get_node("UI/SpeedFader") fleet.spawn_ships(1, Vector2.ZERO, PackedStringArray([GameState.VESSEL_DISTORDER])) fader._process(0.0) if not fader.visible: return _fail_now("fader hidden with a distorder ship in the fleet") game_state.deposit_fleet_resource(GameState.RESOURCE_ENERGY, 4) game_state.set_fleet_speed_multiplier(GameState.MAX_FLEET_SPEED_MULTIPLIER) fader._process(0.0) if fader.knob_t < 0.9: return _fail_now("fader knob did not reflect the boosted multiplier before draining: %f" \ % fader.knob_t) for i in range(10): game_state._advance_distorder_drain(1.0) if not is_equal_approx(game_state.fleet_speed_multiplier, 1.0): return _fail_now("multiplier was not forced back to 1.0 after running out (with a live fleet)") fader._process(0.0) if fader.knob_t > 0.05: return _fail_now("fader knob did not snap down after the boost was depleted: %f" % fader.knob_t) if fader.label.text != "Out of energy!": return _fail_now("fader did not show the depleted reason, text was '%s'" % fader.label.text) if game_state.boost_depleted_notice: return _fail_now("boost_depleted_notice was not consumed by the fader") print("fader visibly snapped its knob down and explained the depletion") print("TEST_DISTORDER_ENERGY: PASS") quit(0) return true return false func _fail_now(message: String) -> bool: print("TEST_DISTORDER_ENERGY: FAIL (%s)" % message) quit(1) return true