#149, #153, #154: dialogs arrive, sit on top, and give the keyboard back
#149 - a line is typed out at about three and a half words a second, with a
scan sweep running down the panel and faint rules across it while it
decodes. The frame carries the charge while it works and settles when the
line lands; the caret is a solid write-head during the decode and only
starts blinking once it is waiting on the reader.
Two implementation notes worth keeping. The full text is set and then
REVEALED with visible_characters rather than the string being grown - a
growing string re-wraps on nearly every frame, so words jump between rows as
they are typed. And a press on a line still arriving FINISHES it instead of
dismissing it: without that, a player clicking at their usual pace skips the
second line having never seen a word of it, which is the exact thing #127
exists to prevent.
#153 - "after a dialog popup the input system is lost on keypress". Nothing
was lost; it was never delivered. A dialog stops the tree (#128), a paused
node receives no input, so a key RELEASED while the line was up never
reached the controller - keys_down kept it and the fleet flew on in that
direction once the game restarted.
NOTIFICATION_UNPAUSED arrives whatever a node's process mode is, which makes
it the one moment the question can be asked, and the physical keyboard is
the authority on what is actually held. That fixes anything that stops the
tree, not only dialogs, and the test checks it that way.
#154 - the banner was the ninth child of the UI layer and every panel came
after it, so all twelve of them drew over the top. It is the last child now,
in both scenes. Input order was never the problem and does not change: the
UI layer already sees unhandled input before the controllers do.
2026-08-29 14:39:07 +02:00
|
|
|
extends SceneTree
|
|
|
|
|
|
|
|
|
|
# Headless test: run with
|
|
|
|
|
# godot --headless -s res://tests/test_input_after_dialog.gd
|
|
|
|
|
# Forgejo #153: "after a dialog popup the godot input system is lost on
|
|
|
|
|
# keypress. If I had a key held it keeps moving in the key direction."
|
|
|
|
|
#
|
|
|
|
|
# Nothing was lost. A dialog stops the tree (#128) and a paused node receives
|
|
|
|
|
# no input, so a key RELEASED while the line was up never reached the
|
|
|
|
|
# controller - keys_down kept it, and the fleet flew on in that direction
|
|
|
|
|
# once the game restarted. The keyboard is the authority on what is held;
|
|
|
|
|
# keys_down is only a record of what we were told, and it went stale.
|
|
|
|
|
#
|
|
|
|
|
# Covers:
|
|
|
|
|
# A. holding a key steers, as it always did
|
|
|
|
|
# B. a release that arrives normally is honoured
|
|
|
|
|
# C. the bug: a key held when a dialog opens, released while it is up -
|
|
|
|
|
# the release never arrives, and the fleet must still stop
|
|
|
|
|
# D. and it is the pause LIFTING that does it, not the dismissal itself,
|
|
|
|
|
# so anything else that stops the tree is covered too
|
|
|
|
|
#
|
|
|
|
|
# Forgejo #154 rides along here, being the same scene: a dialog has to be
|
|
|
|
|
# drawn over every panel, and it was drawn under the skill tree.
|
|
|
|
|
|
|
|
|
|
const KEY_UP: Key = KEY_W
|
|
|
|
|
|
|
|
|
|
var game_state: Node
|
|
|
|
|
var scene: Node
|
|
|
|
|
var controller: Node
|
|
|
|
|
var narration: Control
|
|
|
|
|
var stage: int = 0
|
|
|
|
|
var elapsed: float = 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _initialize() -> void:
|
|
|
|
|
# GameState is an autoload: a bare GameState.<instance member> does not
|
|
|
|
|
# compile in a SceneTree script, so the node is fetched by name instead.
|
|
|
|
|
game_state = root.get_node("GameState")
|
|
|
|
|
game_state.reset_new_game()
|
|
|
|
|
game_state.intro_active = false
|
|
|
|
|
game_state.reset_quests()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _fail(message: String) -> bool:
|
|
|
|
|
print("TEST_INPUT_AFTER_DIALOG: FAIL (%s)" % message)
|
|
|
|
|
quit(1)
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _key(keycode: Key, pressed: bool) -> void:
|
|
|
|
|
var event: InputEventKey = InputEventKey.new()
|
|
|
|
|
event.keycode = keycode
|
|
|
|
|
event.pressed = pressed
|
|
|
|
|
controller._handle_key(event)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _process(delta: float) -> bool:
|
|
|
|
|
elapsed += delta
|
|
|
|
|
match stage:
|
|
|
|
|
0:
|
2026-09-05 17:09:27 +02:00
|
|
|
# #219: fleet capacity is built now - one berth free, one per Fleet
|
|
|
|
|
# Office. This test flies more than one vessel, so it needs the offices
|
|
|
|
|
# standing before the fleet spawns into them.
|
|
|
|
|
load("res://tests/_berths.gd").grant(root.get_node("GameState"), 6)
|
#149, #153, #154: dialogs arrive, sit on top, and give the keyboard back
#149 - a line is typed out at about three and a half words a second, with a
scan sweep running down the panel and faint rules across it while it
decodes. The frame carries the charge while it works and settles when the
line lands; the caret is a solid write-head during the decode and only
starts blinking once it is waiting on the reader.
Two implementation notes worth keeping. The full text is set and then
REVEALED with visible_characters rather than the string being grown - a
growing string re-wraps on nearly every frame, so words jump between rows as
they are typed. And a press on a line still arriving FINISHES it instead of
dismissing it: without that, a player clicking at their usual pace skips the
second line having never seen a word of it, which is the exact thing #127
exists to prevent.
#153 - "after a dialog popup the input system is lost on keypress". Nothing
was lost; it was never delivered. A dialog stops the tree (#128), a paused
node receives no input, so a key RELEASED while the line was up never
reached the controller - keys_down kept it and the fleet flew on in that
direction once the game restarted.
NOTIFICATION_UNPAUSED arrives whatever a node's process mode is, which makes
it the one moment the question can be asked, and the physical keyboard is
the authority on what is actually held. That fixes anything that stops the
tree, not only dialogs, and the test checks it that way.
#154 - the banner was the ninth child of the UI layer and every panel came
after it, so all twelve of them drew over the top. It is the last child now,
in both scenes. Input order was never the problem and does not change: the
UI layer already sees unhandled input before the controllers do.
2026-08-29 14:39:07 +02:00
|
|
|
scene = load("res://world/Main.tscn").instantiate()
|
|
|
|
|
root.add_child(scene)
|
|
|
|
|
current_scene = scene
|
|
|
|
|
controller = scene
|
|
|
|
|
narration = scene.get_node("UI/NarrationBanner")
|
|
|
|
|
elapsed = 0.0
|
|
|
|
|
stage = 1
|
|
|
|
|
return false
|
|
|
|
|
1:
|
|
|
|
|
if elapsed < 0.3:
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
# --- A. holding a key steers ----------------------------
|
|
|
|
|
_key(KEY_UP, true)
|
|
|
|
|
if controller.keyboard_direction() == Vector2.ZERO:
|
|
|
|
|
return _fail("holding a key steers nowhere")
|
|
|
|
|
print("held key steers %s" % controller.keyboard_direction())
|
|
|
|
|
|
|
|
|
|
# --- B. and a release that arrives is honoured ----------
|
|
|
|
|
_key(KEY_UP, false)
|
|
|
|
|
if controller.keyboard_direction() != Vector2.ZERO:
|
|
|
|
|
return _fail("a delivered release did not stop the steering")
|
|
|
|
|
print("a delivered release stops it")
|
|
|
|
|
|
|
|
|
|
# --- C. the bug ------------------------------------------
|
|
|
|
|
# Held, then a dialog opens. The player lets go while reading;
|
|
|
|
|
# that release is never delivered, because this node is paused.
|
|
|
|
|
_key(KEY_UP, true)
|
|
|
|
|
narration.say("Let go while you read this.", "TEST")
|
|
|
|
|
if not paused:
|
|
|
|
|
return _fail("the dialog did not stop the game, so this proves nothing")
|
|
|
|
|
if controller.keyboard_direction() == Vector2.ZERO:
|
|
|
|
|
return _fail("the key was already forgotten before the dialog closed")
|
|
|
|
|
# ...the release happens here, and reaches nobody.
|
|
|
|
|
narration.finish_typing()
|
|
|
|
|
if not narration.dismiss():
|
|
|
|
|
return _fail("the dialog would not close")
|
|
|
|
|
if paused:
|
|
|
|
|
return _fail("closing the dialog left the game stopped")
|
|
|
|
|
# NOTIFICATION_UNPAUSED is delivered as part of lifting the
|
|
|
|
|
# pause, so by now the stale key must be gone.
|
|
|
|
|
if controller.keyboard_direction() != Vector2.ZERO:
|
|
|
|
|
return _fail("the fleet is still steering %s after a release that was never delivered" \
|
|
|
|
|
% controller.keyboard_direction())
|
|
|
|
|
if controller.keys_down.has(KEY_UP):
|
|
|
|
|
return _fail("the key is still recorded as held")
|
|
|
|
|
print("a release swallowed by the pause no longer strands the fleet")
|
|
|
|
|
|
|
|
|
|
# --- D. by the pause lifting, not by the dismissal -------
|
|
|
|
|
_key(KEY_UP, true)
|
|
|
|
|
paused = true
|
|
|
|
|
paused = false
|
|
|
|
|
if controller.keyboard_direction() != Vector2.ZERO:
|
|
|
|
|
return _fail("stopping and restarting the tree left a stale key held")
|
|
|
|
|
print("any pause, not just a dialog, reconciles what is held")
|
|
|
|
|
|
|
|
|
|
# --- #154. a dialog draws over every panel ---------------
|
|
|
|
|
var ui: Node = scene.get_node("UI")
|
|
|
|
|
var banner_at: int = narration.get_index()
|
|
|
|
|
var panels: Array = []
|
|
|
|
|
for menu in get_nodes_in_group("modal_menus"):
|
|
|
|
|
if ui.is_ancestor_of(menu):
|
|
|
|
|
panels.append(menu)
|
|
|
|
|
if panels.is_empty():
|
|
|
|
|
return _fail("no modal panels found to be drawn over")
|
|
|
|
|
for menu in panels:
|
2026-09-05 17:09:27 +02:00
|
|
|
# A panel nested inside another panel - the build menu's own
|
|
|
|
|
# info popup - indexes against ITS parent, not the UI layer,
|
|
|
|
|
# so comparing that number against the banner's is comparing
|
|
|
|
|
# two different sibling lists. What actually has to sit under
|
|
|
|
|
# the banner is the UI-level ancestor it is drawn inside.
|
|
|
|
|
var top_level: Node = menu
|
|
|
|
|
while top_level.get_parent() != ui:
|
|
|
|
|
top_level = top_level.get_parent()
|
|
|
|
|
if top_level.get_index() > banner_at:
|
|
|
|
|
return _fail("%s is drawn inside %s, a later child than the dialog" \
|
|
|
|
|
% [menu.name, top_level.name])
|
#149, #153, #154: dialogs arrive, sit on top, and give the keyboard back
#149 - a line is typed out at about three and a half words a second, with a
scan sweep running down the panel and faint rules across it while it
decodes. The frame carries the charge while it works and settles when the
line lands; the caret is a solid write-head during the decode and only
starts blinking once it is waiting on the reader.
Two implementation notes worth keeping. The full text is set and then
REVEALED with visible_characters rather than the string being grown - a
growing string re-wraps on nearly every frame, so words jump between rows as
they are typed. And a press on a line still arriving FINISHES it instead of
dismissing it: without that, a player clicking at their usual pace skips the
second line having never seen a word of it, which is the exact thing #127
exists to prevent.
#153 - "after a dialog popup the input system is lost on keypress". Nothing
was lost; it was never delivered. A dialog stops the tree (#128), a paused
node receives no input, so a key RELEASED while the line was up never
reached the controller - keys_down kept it and the fleet flew on in that
direction once the game restarted.
NOTIFICATION_UNPAUSED arrives whatever a node's process mode is, which makes
it the one moment the question can be asked, and the physical keyboard is
the authority on what is actually held. That fixes anything that stops the
tree, not only dialogs, and the test checks it that way.
#154 - the banner was the ninth child of the UI layer and every panel came
after it, so all twelve of them drew over the top. It is the last child now,
in both scenes. Input order was never the problem and does not change: the
UI layer already sees unhandled input before the controllers do.
2026-08-29 14:39:07 +02:00
|
|
|
if banner_at != ui.get_child_count() - 1:
|
|
|
|
|
return _fail("the dialog is child %d of %d - something still draws over it" \
|
|
|
|
|
% [banner_at, ui.get_child_count()])
|
|
|
|
|
print("the dialog is the last child of the UI layer, over all %d panels" % panels.size())
|
|
|
|
|
|
|
|
|
|
print("TEST_INPUT_AFTER_DIALOG: PASS")
|
|
|
|
|
quit(0)
|
|
|
|
|
return true
|
|
|
|
|
return false
|