From ee9d6ad9c60393d3e7b9742879d8f2096caba3d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Wisniewski?= Date: Tue, 11 Aug 2026 23:41:00 +0200 Subject: [PATCH] Fix every button being permanently blocked after closing the build menu The real cause of "the crafter buttons do nothing", reported three times. IconButton._blocked_by_modal tested `menu.visible`, but hiding a parent leaves a child's own `visible` flag true - and the build menu's info popup is itself one of the modal panels. So opening any building's info popup and then closing the build menu with the X left an invisible popup still claiming to be open, and from that moment EVERY button in the game was blocked, with no way for a player to get out of it. It tests is_visible_in_tree() now, and closing the build menu also closes its popup rather than leaving it half-open. My earlier attempts kept missing it because the probes called the handlers directly - they exercised the crafter logic, which was fine all along, and never the input path in that state. The regression test reproduces the exact sequence: open the build menu, show a building's info, hide the menu, then check an unrelated button still fires. Co-Authored-By: Claude Opus 5 --- BuildController.gd | 10 +++++++++- IconButton.gd | 8 +++++++- tests/test_modal_blocks_input.gd | 24 ++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/BuildController.gd b/BuildController.gd index 1266c18..aba4f36 100644 --- a/BuildController.gd +++ b/BuildController.gd @@ -148,7 +148,7 @@ var pending_time: float = 0.0 func _ready() -> void: build_button.pressed.connect(_on_build_pressed) - build_close_button.pressed.connect(func(): build_menu.visible = false) + build_close_button.pressed.connect(_close_build_menu) build_mothership_button.pressed.connect(_on_place_mothership) build_crafter_button.pressed.connect(_on_place_crafter) build_command_button.pressed.connect(_on_place_command_center) @@ -394,6 +394,14 @@ func _on_place_command_center() -> void: _show_building_info(GameState.BUILDING_COMMAND_CENTER) +func _close_build_menu() -> void: + # The popup is a child panel of this menu; leaving it "open" while its + # parent is hidden is what blocked every button in the game. + info_popup.visible = false + pending_build_type = "" + build_menu.visible = false + + func _on_place_ship_lab() -> void: _show_building_info(GameState.BUILDING_SHIP_LAB) diff --git a/IconButton.gd b/IconButton.gd index 999e2fe..825cc9b 100644 --- a/IconButton.gd +++ b/IconButton.gd @@ -61,7 +61,13 @@ func _blocked_by_modal() -> bool: # there. So the panels stay transparent to input and the buttons check # instead. for menu in get_tree().get_nodes_in_group("modal_menus"): - if menu.visible and not menu.is_ancestor_of(self): + # is_visible_in_tree(), NOT visible. Hiding a parent leaves the + # child's own `visible` flag true, and the build menu's info popup is + # itself a modal panel - so closing the build menu with the X left an + # invisible popup still claiming to be open, and every button in the + # game was blocked from then on. That is not a state a player can + # get out of. + if menu.is_visible_in_tree() and not menu.is_ancestor_of(self): return true return false diff --git a/tests/test_modal_blocks_input.gd b/tests/test_modal_blocks_input.gd index 19c3e78..2aca0a9 100644 --- a/tests/test_modal_blocks_input.gd +++ b/tests/test_modal_blocks_input.gd @@ -132,6 +132,30 @@ func _process(delta: float) -> bool: quit(1) return true print("touch on the panel consumed, fleet target unchanged") + + # --- 5. a hidden parent must not leave a panel "open" ------- + # The build menu's info popup is itself a modal panel. Hiding the + # build menu leaves the popup's own `visible` flag true, so a + # guard testing `visible` rather than `is_visible_in_tree()` + # blocked EVERY button in the game from then on - with no way for + # a player to get out of it. Reported three times as "the buttons + # do nothing". + skill_menu.visible = false + var controller2: Node = scene.get_node("BuildController") + controller2._on_build_pressed() + controller2._show_building_info(GameState.BUILDING_SHIP_CRAFTER) + scene.get_node("UI/BuildMenu").visible = false + var info: Control = scene.get_node("UI/BuildMenu/InfoPopup") + print("popup after hiding its parent: visible=%s in_tree=%s" % [ + info.visible, info.is_visible_in_tree() + ]) + outside_fired = false + _tap(map_button) + if not outside_fired: + print("TEST_MODAL_BLOCKS_INPUT: FAIL (a panel hidden with its parent still blocks every button)") + quit(1) + return true + print("hidden-with-parent panel no longer blocks anything") print("TEST_MODAL_BLOCKS_INPUT: PASS") quit(0) return true