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". # # The queue 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. ordering against an empty stock says it is queued AND how much more # ore the building needs # 2. 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 # 3. the message is the FIRST line, not buried under the standing state # 4. the panel is opened the way the GAME opens it - park the fleet on the # crafter and press the action button - and its buttons then work # # Point 4 exists because this test used to set craft_building_id by hand. # That skipped the one step that was actually broken: opening the panel # stopped recording WHICH crafter, so every handler bailed out at its `== 0` # guard while the panel still opened. Reported four times as "the buttons do # nothing", and invisible to every test that set the id itself. # # Storing ore used to be tested here too. It moved out of this panel entirely # in #24 - see test_resource_transfer.gd. var game_state: Node var scene: Node var controller: Node var crafter_id: int = 0 var fleet: Node 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() # Every capacity node, so the interceptor button stays available for the # real-tap checks below. A full fleet correctly DISABLES it, and a # disabled button not reacting to a tap is not the bug those checks are # looking for. game_state.unlocked = ["shipyards", "coordination", "wing", "squadron", "armada"] game_state.rocks = 100 game_state.fleet_cargo = 0 game_state.stored_vessels.clear() game_state.build_time_scale = 0.02 scene = load("res://Main.tscn").instantiate() root.add_child(scene) controller = scene.get_node("BuildController") fleet = scene.get_node("Fleet") func _tap(button: Control) -> void: for pressed in [true, false]: var ev: InputEventScreenTouch = InputEventScreenTouch.new() ev.pressed = pressed ev.position = button.size / 2.0 button._gui_input(ev) 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) for a in get_nodes_in_group("asteroids"): if is_instance_valid(a): a.queue_free() scene.get_node("BuildingField").place(GameState.BUILDING_SHIP_CRAFTER, Vector2(500.0, 0.0)) crafter_id = int(game_state.buildings[-1].id) # Park the fleet ON it and let the action button debounce in. for ship in fleet.ships: ship.position = Vector2(500.0, 0.0) fleet.move_to(Vector2(500.0, 0.0)) elapsed = 0.0 stage = 1 return false 1: if elapsed < 1.2: return false # --- 6. open it the way the game does ------------------------ if controller.current_building == null: print("TEST_CRAFTER_FEEDBACK: FAIL (fleet parked on the crafter never registered)") quit(1) return true # Transfer has its own shortcut beside Build, leaving Craft as the # single contextual action that fires directly. _tap(controller.action_button) if not controller.craft_menu.visible: print("TEST_CRAFTER_FEEDBACK: FAIL (the action button did not open the crafter panel)") quit(1) return true if controller.craft_building_id != crafter_id: print("craft_building_id=%d, crafter is %d" % [controller.craft_building_id, crafter_id]) print("TEST_CRAFTER_FEEDBACK: FAIL (opening the panel did not record which crafter - every button will bail out)") quit(1) return true print("panel opened from the building, bound to crafter %d" % controller.craft_building_id) # --- 1. 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) elapsed = 0.0 stage = 2 return false 2: # --- 2 + 3. it survives the refresh, and stays first -------- if elapsed < 0.5: return false controller._refresh_craft_status() var still: String = _first_line() if "queued" not in still.to_lower(): 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) # --- 4. a REAL tap, through the input path ------------------ # Everything above calls the handlers directly, which is how a # whole class of bug kept getting missed: the crafter logic was # fine and the BUTTON was inert, blocked by a modal guard. This # taps the control the way a touch does. var queued_before: int = game_state.pending_orders(crafter_id) _tap(scene.get_node("UI/CraftMenu/InterceptorButton")) if game_state.pending_orders(crafter_id) <= queued_before: print("orders %d -> %d after tapping Interceptor" % [ queued_before, game_state.pending_orders(crafter_id) ]) print("TEST_CRAFTER_FEEDBACK: FAIL (tapping the interceptor button did nothing)") quit(1) return true print("real tap on Interceptor queued another order") # And the same tap must still work after the build menu has been # opened and closed, which used to block every button forever. controller._on_build_pressed() controller._show_building_info(GameState.BUILDING_SHIP_CRAFTER) scene.get_node("UI/BuildMenu").visible = false var queued_mid: int = game_state.pending_orders(crafter_id) _tap(scene.get_node("UI/CraftMenu/InterceptorButton")) if game_state.pending_orders(crafter_id) <= queued_mid: print("TEST_CRAFTER_FEEDBACK: FAIL (craft button dead after the build menu was opened and closed)") quit(1) return true print("still works after opening and closing the build menu") print("TEST_CRAFTER_FEEDBACK: PASS") quit(0) return true return false