extends SceneTree # Headless gameplay test: run with # godot --headless -s res://tests/test_crafter_feedback.gd # Every press in the ship crafter must say what happened. Reported twice from # play-testing as "the buttons do nothing". # # There is no silent-failure path left to press: storing with an empty hold, # ordering something the crafter cannot pay for yet, and storing successfully # all report. The queue in particular is invisible by nature - an order that # cannot be afforded is accepted and waits (#16), which looks exactly like a # dead button unless it says so. # # Verifies: # 1. storing with an empty hold explains where ore comes from # 2. ordering against an empty stock says it is queued AND how much more # ore the building needs # 3. a successful store reports the amount # 4. the message survives the per-frame status refresh - it used to be # overwritten on the very next frame, which is what made a working # button look dead # 5. the message is the FIRST line, not buried under the standing state var game_state: Node var scene: Node var controller: Node var crafter_id: int = 0 var stage: int = 0 var elapsed: float = 0.0 func _initialize() -> void: game_state = root.get_node("GameState") game_state.buildings.clear() game_state.extractors.clear() game_state.transporters.clear() game_state.reset_progression() game_state.unlocked = ["shipyards", "coordination", "wing"] game_state.rocks = 100 game_state.fleet_cargo = 0 game_state.vessels_owed.clear() scene = load("res://Main.tscn").instantiate() root.add_child(scene) controller = scene.get_node("BuildController") func _first_line() -> String: return controller.craft_status.text.split("\n")[0] func _process(delta: float) -> bool: elapsed += delta match stage: 0: scene.get_node("AsteroidField").set_process(false) scene.get_node("EnemySpawner").set_process(false) scene.get_node("BuildingField").place(GameState.BUILDING_SHIP_CRAFTER, Vector2(500.0, 0.0)) crafter_id = int(game_state.buildings[-1].id) elapsed = 0.0 stage = 1 return false 1: if elapsed < 0.3: return false controller.craft_building_id = crafter_id controller.craft_menu.visible = true controller._refresh_craft_status() # --- 1. store with an empty hold ---------------------------- controller._on_store_ore() var empty_msg: String = _first_line() if "carrying nothing" not in empty_msg: print("got: %s" % empty_msg) print("TEST_CRAFTER_FEEDBACK: FAIL (storing with an empty hold said nothing useful)") quit(1) return true print("empty hold -> %s" % empty_msg) # --- 2. order against an empty stock ------------------------ controller._on_craft_interceptor() var queued_msg: String = _first_line() if "queued" not in queued_msg.to_lower() or "more ore" not in queued_msg: print("got: %s" % queued_msg) print("TEST_CRAFTER_FEEDBACK: FAIL (a queued order did not say it was queued and what it still needs)") quit(1) return true if game_state.pending_orders(crafter_id) != 1: print("TEST_CRAFTER_FEEDBACK: FAIL (the order was not actually queued)") quit(1) return true print("unaffordable order -> %s" % queued_msg) # --- 3. a successful store ---------------------------------- game_state.withdraw_to_fleet(30) controller._on_store_ore() var stored_msg: String = _first_line() if "Stored" not in stored_msg: print("got: %s" % stored_msg) print("TEST_CRAFTER_FEEDBACK: FAIL (a successful store did not report)") quit(1) return true print("successful store -> %s" % stored_msg) elapsed = 0.0 stage = 2 return false 2: # --- 4 + 5. it survives the refresh, and stays first -------- if elapsed < 0.5: return false controller._refresh_craft_status() var still: String = _first_line() if "Stored" not in still: print("first line half a second later: %s" % still) print("TEST_CRAFTER_FEEDBACK: FAIL (the message was overwritten by the per-frame refresh)") quit(1) return true print("half a second later, still first: %s" % still) print("TEST_CRAFTER_FEEDBACK: PASS") quit(0) return true return false