Addresses Tyre's 9 architecture items from the sprint-36 client review. - decisions/architecture.md (Tyre #1): D-192 now says "deprecate; removal tracked in #868" instead of "remove". The branch does not remove the version field or guard — that belongs in the coordinated server+client PR. The decision text now matches the code on this branch. - meta_stack.gd (#2): handle_escape() on a screen with closable_by_escape=false now consumes the event unconditionally. Was returning whatever on_escape() returned, which default-returned false and leaked ESC into main.gd's implant/settings chain — opening the settings dialog behind the loading screen. - debug_console.gd (#4): drop the direct KEY_ESCAPE branch in _unhandled_input. ESC now falls through to main.gd → MetaStack, which finds the console on top of the stack and closes it via the normal path. Other keys are still consumed so movement/action can't leak. - main.gd (#6, #10): extract the ESC priority chain into _handle_menu_key() so "MetaStack → implant → settings" is a named thing. Add a comment near connect_to_sim explaining that GameState.bookmark_catalog survives the Option A scene transition via the autoload. - main_menu.gd (#7): header comment documenting the double LoadingScreen lifecycle — safe today because main_menu.tscn and main.tscn never co-exist, noted for future promotion to autoload if that changes. - meta_screen.gd (#8): apply captures_input symmetrically in open()/ close() — was set in open() only, so a screen changing the flag between open+close kept the opened value forever. - meta_screen.gd (#9): on_escape() docstring clarifies the tri-state (consume-and-hold / consume-and-close / ignore) — and that closable_by_escape=false is the screen-wide way to say "consume-and-hold". - bug_report_dialog.gd (#11): capture_cancelled now emits from on_close() (covers any close path — ESC, MetaStack pop, programmatic close) rather than only on_escape(). A new _completed flag distinguishes completion from cancel so the two signals stay mutually exclusive.
79 lines
2.2 KiB
GDScript
79 lines
2.2 KiB
GDScript
extends Node
|
|
## Autoload coordinator for overlay MetaScreens (#618, #680).
|
|
## Scene-root screens (main_menu, character_creation) do NOT push onto this stack.
|
|
## Only overlay screens (settings, debug_console, bug_report, loading_screen) push.
|
|
##
|
|
## Autoload parse-order: MetaScreen is a class_name type — referenced here only
|
|
## inside method bodies called at runtime, never at the top level or in _ready().
|
|
|
|
signal meta_active_changed(active: bool)
|
|
|
|
var _stack: Array = [] # Array[MetaScreen] — untyped per parse-order rule
|
|
|
|
|
|
func push(screen) -> void: # screen: MetaScreen
|
|
if screen in _stack:
|
|
return
|
|
_stack.append(screen)
|
|
screen.closed.connect(_on_screen_closed.bind(screen), CONNECT_ONE_SHOT)
|
|
if _stack.size() == 1:
|
|
meta_active_changed.emit(true)
|
|
if screen.pauses_sim:
|
|
_request_pause(true)
|
|
|
|
|
|
func pop() -> void:
|
|
if _stack.is_empty():
|
|
return
|
|
_stack[-1].close()
|
|
|
|
|
|
func top(): # returns MetaScreen or null
|
|
return _stack[-1] if not _stack.is_empty() else null
|
|
|
|
|
|
func is_active() -> bool:
|
|
return not _stack.is_empty()
|
|
|
|
|
|
## Handle ESC key. Call from main.gd before HudGroups ESC handling.
|
|
## Returns true if the event was consumed (callers must return after).
|
|
##
|
|
## A screen on the stack always consumes the event. `closable_by_escape = false`
|
|
## means "I refuse to close on ESC" — not "pass the event through to the
|
|
## implant/gameplay layer." Otherwise an un-escapable screen (e.g. LoadingScreen)
|
|
## would leak ESC to main.gd and open the settings dialog behind it.
|
|
func handle_escape() -> bool:
|
|
var t = top()
|
|
if t == null:
|
|
return false
|
|
if not t.closable_by_escape:
|
|
t.on_escape()
|
|
return true
|
|
if t.on_escape():
|
|
return true
|
|
t.close()
|
|
return true
|
|
|
|
|
|
func _on_screen_closed(screen) -> void: # screen: MetaScreen
|
|
_stack.erase(screen)
|
|
if screen.pauses_sim and not _any_pausing():
|
|
_request_pause(false)
|
|
if _stack.is_empty():
|
|
meta_active_changed.emit(false)
|
|
|
|
|
|
func _any_pausing() -> bool:
|
|
for s in _stack:
|
|
if s.pauses_sim:
|
|
return true
|
|
return false
|
|
|
|
|
|
func _request_pause(pause: bool) -> void:
|
|
if SimBridge.state != SimBridge.ConnectionState.CONNECTED:
|
|
return
|
|
var action = InputMapper.Action.PAUSE if pause else InputMapper.Action.UNPAUSE
|
|
SimBridge.send_input({"action": action, "timestamp_msec": Time.get_ticks_msec()})
|