class_name ModalFlow extends RefCounted # 模态编排纯逻辑类(迭代 27 T4——审计 P1-5/P2-7:组合根可测化抽取 + 模态互斥域内聚)。 # 依赖仅 ModalStack + 查询 Callable(审计处方原话)——零节点构建、零信号连接、零核心触碰 #(同构:连接归组合根,本类只收注入)。可直接 new() 单测(V6)。 # 职责三件: # ① 登记与开合编排(遮挡记账单口 / T17 演播后排窗挂起——原 main._open_modal 系逻辑随迁) # ② 棋盘输入锁谓词(互斥域随层登记——原 main._input_locked 排除式拼装消亡) # ③ 角标置灰谓词(状态/调试两屏同款守卫判据) # 排除语义随层登记(ModalStack.register tags):debug_overlay=["non_locking"]、 # level_screen=["screen_state"],其余层零标签——原调用点排除式拼装面消亡(P2-7 修法)。 const ModalStackScript := preload("res://presentation/modal_stack.gd") # 非全局类(main 同款 preload 引用) var stack = ModalStackScript.new() # ---- 组合根注入面(查询 Callable 组——依赖仅 ModalStack + 查询 Callable 纪律载体) ---- var start_visible_get: Callable # () -> bool——开始层可见(非模态全屏态例外一) var inter_visible_get: Callable # () -> bool——过场层可见(例外二) var settlement_get: Callable # () -> Variant——当前战斗编排器(null=战斗缺位例外三) var rows_empty_get: Callable # () -> bool——排播队列空(演播后排窗判据) var flush_rows: Callable # 开层前排空排播(卡⑦——明细带恒完整) var on_open_sync: Callable # 开侧同步钩子(状态角标+浮窗当局数据) var on_close_sync: Callable # 关侧同步钩子 var replay_click: Callable # 演播完边沿无挂起时的锁期点击重放(main._replay_locked_click) var _deferred: Control = null # 演播后开窗挂起(T17——排播放完再开) func register(m: Control, tags: Array = []) -> void: stack.register(m, tags) func open(m: Control) -> void: # 挂起兜底(T17 演播后排窗):其他路径(如回看)在播期排空队列会使演播完边沿不再触发—— # 此处先结挂起 modal 防永不兑现(m==挂起对象时跳过=本调用自身)。 if _deferred != null and m != _deferred: var dm: Control = _deferred _deferred = null open(dm) # 原语义:先结挂起后继续开本层(不 return——两拍都走) flush_rows.call() # 卡⑦——任何覆盖层开启前排空排播队列(明细带恒完整,回看/停点面不缺行) stack.open(m) on_open_sync.call() func close(m: Control) -> void: stack.close(m) on_close_sync.call() # 演播后排窗(T17 用户令「伤害过程演完才切结算窗口」):排播队列在播=挂起,演播完边沿 # (pump_ended 派发)再开——旧路径前置 flush 会把击杀轮的伤害/死亡演出同帧倒掉。 # 队列空=立即开(行为同旧路径)。 func open_after_playback(m: Control) -> void: if rows_empty_get.call(): open(m) return _deferred = m m.visible = false # 挂起含视觉(open_* 已置 visible=true——不压掉则窗在排播期照会上屏,延迟形同虚设) # 演播完边沿派发(row_pacer.pump_ended 注入口):先开挂起 modal(胜利窗接管呈现), # 后锁期点击重放——modal 开启后输入锁吞掉重放点击(战斗已收场,重放无意义)。 func pump_ended() -> void: if _deferred != null: var m: Control = _deferred _deferred = null open(m) return replay_click.call() # 棋盘输入锁谓词(原 main._input_locked 随迁):模态互斥域(non_locking 层开态不计——迭代 18 # 卡⑦ T2 防线)+ 三例外(开始层/过场层非模态全屏态 + 战斗缺位棋盘输入无载体)。 func locks_input() -> bool: return stack.has_open_excluding_tags([ModalStackScript.TAG_NON_LOCKING]) \ or bool(start_visible_get.call()) or bool(inter_visible_get.call()) or settlement_get.call() == null # 角标置灰谓词·战斗屏形态(状态/调试两角标同判据):登记册上 non_locking 层外的任一层开着。 # (地图屏此态恒关——屏级互斥,与旧排除式 [debug] 逐位同值) func badge_greyed_battle() -> bool: return stack.has_open_excluding_tags([ModalStackScript.TAG_NON_LOCKING]) # 角标置灰谓词·地图屏形态(地图屏自身 screen_state 不计 + 调试浮窗 non_locking 不计—— # 与旧排除式 [level_screen, debug] 逐位同值)。 func badge_greyed_map() -> bool: return stack.has_open_excluding_tags([ModalStackScript.TAG_NON_LOCKING, ModalStackScript.TAG_SCREEN_STATE])