## 组合根 Esc 流程承接件(产品码首个键盘输入入口——main._unhandled_input 同名薄委托落地): ## Esc 四态路由(契约 9 判据单点=ModalStack 登记册现开集合,禁手枚名单)+三出口动线。 ## 静态承接件零成员态(仿 main_overlay_flow 形态——main 侧留同名薄委托,探针 main.X 路径零改动)。 class_name MainEscFlow extends RefCounted ## 营地语境判据(Q1 甲——场景根 visible;子界面开态抑制由契约 9① 现开集合承载,不并入本谓词) static func is_camp_context(root: Variant) -> bool: return root.ui.modals.camp.scene != null and root.ui.modals.camp.scene.visible ## Esc 优先序(camp.md v2.3 定稿 2026-11-01 用户裁——有功能窗开着→先关最上层窗,覆盖卡⑤ 契约 9① ## 的 Q1 甲无反馈;设置开在浮窗之上仍无反馈=契约 3 保留): ## ①功能窗开着→close 最上层窗→①'设置开在浮窗之上→无反馈→②浮窗开→close(Q2 同键单义)→ ## ③营地语境→open→④非营地→无反馈 static func on_esc_input(root: Variant) -> void: var esc: Control = root.ui.modals.camp.esc_overlay var window := _topmost_camp_window(root) if window != null: root.ui.flow.close(window) # ① 先关最上层功能窗(v2.3 定稿) return if root.ui.modals.menu.settings_overlay.visible: return # ①' 设置开在浮窗之上 → 无反馈(浮窗不隔层关闭——契约 3) if esc.visible: root.ui.flow.close(esc) # ② 再按 Esc 关窗 return if is_camp_context(root): root.ui.flow.open(esc) # ③ 营地语境唤起 # ④ 非营地语境 → 无反馈 # 五功能窗现开查询(camp.md v2.3 五窗互斥单开——模态栈深恒 1,至多一窗在开;生涯页入窗集) static func _topmost_camp_window(root: Variant) -> Control: for w: Control in [root.ui.modals.camp.forge, root.ui.modals.camp.treasure, root.ui.modals.camp.tree, root.ui.modals.camp.codex, root.ui.modals.menu.career_overlay]: if w != null and w.visible: return w return null ## 返回主菜单动线(契约 4):首步 close 浮窗(缺此步浮窗残留主菜单上方)→营地与选档层同收+开始层恢复 static func on_return_to_menu(root: Variant) -> void: root.ui.flow.close(root.ui.modals.camp.esc_overlay) root.camp_pres().on_camp_closed() # 关层收闭单口(卡④ T3——营地层与选档层同收+开始层恢复) ## 设置出口(浮窗登记在设置层之下——open 只隐上方层,浮窗由树序遮挡非属性隐藏;关后重现=栈语义) static func on_settings(root: Variant) -> void: root.ui.flow.open(root.ui.modals.menu.settings_overlay) ## 退出出口(_quit_game 原样复用——settings_store.save_settings+quit,零语义副作用) static func on_quit(root: Variant) -> void: root.quit_game()