Let a dialog opened over another dialog be closed

The "no research data" explainer is the only dialog in the game that opens
ON TOP of another one - the skill tree or the ship lab, from the button that
was just refused. IconButton._blocked_by_modal() refuses input to any button
sitting behind an open panel, and it had no notion of stacking order: it saw
the lab still visible, saw it was not an ancestor of the popup's close
button, and killed the touch. Every button in the explainer was dead,
including the only way out of it. Reported from the game.

The check now compares draw order. A panel that this button's own panel is
drawn on top of is behind it, not over it, and is ignored; a panel drawn
above it still blocks, so the lab's own buttons stay dead while the popup
covers them. Buttons belonging to no panel - the HUD - are unchanged and
still blocked by anything open.

The existing test missed this because it dismissed the popup by calling
close_menu() directly, which is not what a player can do. Case D presses the
button, and asserts the reverse too. Verified in both directions: with the
fix reverted it fails with "the explainer close button is blocked by the lab
behind it - the dialog is inescapable".

172/172.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
This commit is contained in:
Claude 2026-09-08 22:31:56 +02:00
commit 693919dadb
2 changed files with 62 additions and 2 deletions

View file

@ -113,6 +113,10 @@ func _process(delta: float) -> bool:
break
if game_state.research_point_cost(String(options[controller.lab_choice].id)) <= 0:
return _fail("no lab option costs research data, so this proves nothing")
# The lab panel is open when the player presses buy - that is what
# puts the explainer on top of another dialog, which is the whole
# point of case D below.
controller.lab_menu.open_menu()
controller._on_lab_buy()
if not popup.visible:
return _fail("the lab refused for want of data with no explanation")
@ -120,6 +124,30 @@ func _process(delta: float) -> bool:
return _fail("the lab still blames the queue: '%s'" % controller.lab_menu.status.text)
print("ship lab: refused and explained, not blamed on the queue")
# --- D. and the player can get back out of it ----------------
# The explainer is the only dialog that opens ON TOP of another
# one, and IconButton refuses input to any button sitting behind
# an open panel. That used to include this popup's own close
# button, so the dialog could not be dismissed at all. Case B
# above left the ship lab open underneath, which is the real
# situation - assert that rather than arranging it.
if not controller.lab_menu.visible:
return _fail("the lab is not open underneath, so this proves nothing")
var close: Control = popup.get_node("CloseButton")
if close._blocked_by_modal():
return _fail("the explainer close button is blocked by the lab behind it - the dialog is inescapable")
close.pressed.emit()
if popup.visible:
return _fail("pressing close left the explainer open")
# The reverse must still hold: the lab's own buttons stay dead
# while a dialog covers them.
popup.open_for_shortfall(10, "anything")
var lab_close: Control = controller.lab_menu.get_node("CloseButton")
if not lab_close._blocked_by_modal():
return _fail("the lab buttons are live underneath an open dialog")
popup.close_menu()
print("close button works over the lab, and the lab stays blocked underneath")
print("TEST_RESEARCH_POINTS_EXPLAINER: PASS")
quit(0)
return true