From 240f1a1769c6c67208cf2ee3c8dff69ecb99a53c Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 2 Nov 2026 07:30:33 +0800 Subject: [PATCH] =?UTF-8?q?iter49-=E5=8D=A12=EF=BC=9A=E6=A8=A1=E6=80=81?= =?UTF-8?q?=E5=8F=A5=E6=9F=84=E5=88=86=E5=9F=9F=E6=89=B9(=E5=8F=A5?= =?UTF-8?q?=E6=9F=84=E5=86=8C=E4=B8=89=E5=9F=9F=E5=8C=96):UiModals=2014?= =?UTF-8?q?=E2=86=923=E7=BA=AF=E5=AD=97=E6=AE=B5=E5=AE=B9=E5=99=A8+run/men?= =?UTF-8?q?u=E4=B8=A4=E6=96=B0=E5=AD=90=E5=AE=B9=E5=99=A8+ModalFlow?= =?UTF-8?q?=E5=8D=87=E6=A0=BC=E6=B3=A8=E5=86=8C=E9=9D=A2=E7=9B=B4=E9=A9=BB?= =?UTF-8?q?+=E5=85=A8=E4=BB=93=E5=AF=BB=E5=9D=80=E6=8D=A2=E4=BB=A3+V1?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E6=96=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/presentation/main.gd | 42 ++-- game/presentation/main_adventure_flow.gd | 58 ++--- game/presentation/main_debug_flow.gd | 20 +- game/presentation/main_esc_flow.gd | 14 +- game/presentation/main_overlay_flow.gd | 60 ++--- game/presentation/main_ui_build.gd | 124 ++++----- game/presentation/ui_camp_modals.gd | 4 +- game/presentation/ui_menu_modals.gd | 11 + game/presentation/ui_menu_modals.gd.uid | 1 + game/presentation/ui_modals.gd | 34 +-- game/presentation/ui_registry.gd | 46 ++-- game/presentation/ui_run_modals.gd | 11 + game/presentation/ui_run_modals.gd.uid | 1 + game/probe/fx_shot_probe.gd | 6 +- game/probe/layout_probe.gd | 4 +- game/probe/probe_ui_util.gd | 2 +- game/probe/screen_flow_probe.gd | 38 +-- game/probe/shot_probe.gd | 40 +-- game/probe/t17_defer_probe.gd | 14 +- game/probe/ui_click_probe.gd | 236 +++++++++--------- game/probe/ui_input_probe.gd | 26 +- game/probe/ui_probe.gd | 24 +- game/probe/ui_struct_probe.gd | 129 ++++++---- game/probe/ui_tune_probe.gd | 70 +++--- game/tests/smoke_boot.gd | 4 +- game/tests/system_test.gd | 42 ++-- .../unit/presentation/pacing_queue_test.gd | 2 +- .../presentation/theme_projection_test.gd | 2 +- 28 files changed, 552 insertions(+), 513 deletions(-) create mode 100644 game/presentation/ui_menu_modals.gd create mode 100644 game/presentation/ui_menu_modals.gd.uid create mode 100644 game/presentation/ui_run_modals.gd create mode 100644 game/presentation/ui_run_modals.gd.uid diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 09caad6f..fa3d5bd2 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -1,7 +1,7 @@ ## VERTICAL SLICE - NOT FOR PRODUCTION ## 组合根:唯一的模式流转与 core↔view 接线处(垂直切片验证:线性第一关承载元进度循环)。 ## db.hero 运行期只读,加成经 runner.set_hero_override / debug_session 生效值入战斗。 -## 禁链式穿透 settlement.*(相位读取只走 presentation_snapshot);模态开合只走 ui.modals.flow(注册面编排口)。 +## 禁链式穿透 settlement.*(相位读取只走 presentation_snapshot);模态开合只走 ui.flow(注册面编排口)。 ## 界面句柄与层清单在 ui 注册面(ui_registry.gd)——屏壳/战区/模态三域句柄与 LAYER_CONTRACT 单点宿主。 ## 局态全在 run_scope.gd 节点,禁在屏/视图侧持有局态引用;autoload 节点路径常量单点在 AUTOLOAD_PATH_*。 extends Control @@ -104,7 +104,7 @@ func _ready() -> void: func _build_ui() -> void: MainUiBuild.build(self) - ui.modals.debug_overlay.diagnose_requested.connect(_on_diagnose_pressed) # 诊断转储入口 + ui.modals.menu.debug_overlay.diagnose_requested.connect(_on_diagnose_pressed) # 诊断转储入口 func _fatal_screen(msg: String) -> void: @@ -126,11 +126,11 @@ func _start_adventure_from_menu() -> void: #(确认才删、取消只关窗——裁决 3)。"gate"/"gate_new"=卡④ 大门两分支消费方(有/无断点), # "campfire_leave"=篝火二次确认(卡④ T4 接入——分派机制扩展第三/第四消费方)。 func _on_confirm_accepted() -> void: - ui.modals.flow.close(ui.modals.confirm_dialog) # 模态栈单口 + ui.flow.close(ui.modals.menu.confirm_dialog) # 模态栈单口 match _confirm_consumer: "delete_slot": save_manager.slots.delete_slot(_confirm_consumer_slot) - ui.modals.save_select_overlay.open_save_select(HudPresenter.save_select_data(save_manager)) # 就地刷新(该槽回空态) + ui.modals.menu.save_select_overlay.open_save_select(HudPresenter.save_select_data(save_manager)) # 就地刷新(该槽回空态) "gate": _camp_pres.on_camp_closed() # 冒险发起后营地场景屏隐藏(选档层同收——卡④ T4 关层收闭) _resume_from_menu() # 继续上一段冒险(现役续玩编排同源——卡④ 契约 4) @@ -144,7 +144,7 @@ func _on_confirm_accepted() -> void: # 次确认分派(三钮形态中钮——大门「开始新冒险」出口;卡④ T4) func _on_confirm_secondary() -> void: - ui.modals.flow.close(ui.modals.confirm_dialog) # 模态栈单口 + ui.flow.close(ui.modals.menu.confirm_dialog) # 模态栈单口 match _confirm_consumer: "gate": _camp_pres.on_camp_closed() @@ -153,7 +153,7 @@ func _on_confirm_secondary() -> void: func _on_confirm_declined() -> void: - ui.modals.flow.close(ui.modals.confirm_dialog) # 只关窗(取消无状态变更——裁决 3:取消不删/卡④:退回营地);关侧恢复归 ModalFlow + ui.flow.close(ui.modals.menu.confirm_dialog) # 只关窗(取消无状态变更——裁决 3:取消不删/卡④:退回营地);关侧恢复归 ModalFlow _confirm_consumer = "" @@ -169,16 +169,16 @@ func _resume_from_menu() -> void: if not save_manager.has_breakpoint(): return var bp: Dictionary[Variant, Variant] = save_manager.breakpoint_snapshot() - ui.modals.flow.close(ui.modals.debug_overlay) # 开冒险路径显式关浮窗(续玩同口径) + ui.flow.close(ui.modals.menu.debug_overlay) # 开冒险路径显式关浮窗(续玩同口径) ui.screens.start_overlay.visible = false _begin_session(true) _wire_adventure() run_scope.level_active = true _sync_status_entry() # 续玩恢复点亮状态面板入口角标 - ui.modals.level_screen.set_abandon_available(true) + ui.modals.run.level_screen.set_abandon_available(true) ui.battle.log_view.clear() logger.start_session(int(bp.get("master_seed", 0))) - run_scope.adventure.set_hero_override(ui.modals.debug_overlay.atk_pct(), ui.modals.debug_overlay.hp_pct()) + run_scope.adventure.set_hero_override(ui.modals.menu.debug_overlay.atk_pct(), ui.modals.menu.debug_overlay.hp_pct()) # 同调三键随续玩下传(build_adventure_params 统一构造,与开新局同源同口径) if not run_scope.adventure.resume_adventure(bp, build_adventure_params(save_manager, meta, {"treasure_unlocked": _treasure_ui.unlocked_set()})): @@ -214,16 +214,16 @@ static func build_adventure_params(save_manager_ref: SaveManagerCore, meta_ref: # 冒险模式(三关连探——AdventureRunner 编排;转发信号签名与 runner 一致,接线照原样对接; # 信号连接只在组合根) func _start_adventure() -> void: - ui.modals.flow.close(ui.modals.debug_overlay) # 开冒险路径显式关浮窗(调试浮窗默认开态下开局不带窗;对局中经角标再开) + ui.flow.close(ui.modals.menu.debug_overlay) # 开冒险路径显式关浮窗(调试浮窗默认开态下开局不带窗;对局中经角标再开) _begin_session(true) _wire_adventure() run_scope.level_active = true _sync_status_entry() # 进对局点亮状态面板入口角标 - ui.modals.level_screen.set_abandon_available(true) # 放弃入口挂选路停点(天然非战斗期) + ui.modals.run.level_screen.set_abandon_available(true) # 放弃入口挂选路停点(天然非战斗期) ui.battle.log_view.clear() var seed_val := _seed_value() logger.start_session(seed_val) - run_scope.adventure.set_hero_override(ui.modals.debug_overlay.atk_pct(), ui.modals.debug_overlay.hp_pct()) + run_scope.adventure.set_hero_override(ui.modals.menu.debug_overlay.atk_pct(), ui.modals.menu.debug_overlay.hp_pct()) # 永久强化注入:冒险开始参数面——meta 现算 override 显式传参(全零 = 白板) # 开局参数面(同调三键同源构造——确定性承载=固定种子+参数档,首局教学图由种子域三分判定给 42) if not run_scope.adventure.start_adventure(seed_val, build_adventure_params(save_manager, meta, @@ -287,16 +287,16 @@ func _on_battle_finished(payload: Dictionary) -> void: _refresh_presentations() return "level_end": - ui.modals.end_overlay.open_victory(Narrator.battle_end_text("victory", true), + ui.modals.run.end_overlay.open_victory(Narrator.battle_end_text("victory", true), "返回", ui.battle.board_zone.board_view.get_global_rect(), _end_victory_opts(payload)) "standalone_end": if result == "victory": - ui.modals.end_overlay.open_victory(Narrator.battle_end_text("victory", false), + ui.modals.run.end_overlay.open_victory(Narrator.battle_end_text("victory", false), "回到遭遇选择", ui.battle.board_zone.board_view.get_global_rect(), _end_victory_opts(payload)) else: - ui.modals.end_overlay.open_defeat(Narrator.battle_end_text("defeat", false), + ui.modals.run.end_overlay.open_defeat(Narrator.battle_end_text("defeat", false), "返回", ui.battle.board_zone.board_view.get_global_rect()) - ui.modals.flow.open_after_playback(ui.modals.end_overlay) + ui.flow.open_after_playback(ui.modals.run.end_overlay) _refresh_presentations() @@ -562,7 +562,7 @@ func _on_wallet_changed(event: String, gp: int, extra: Dictionary)-> void: func _on_board_action(cell: Vector2i, kind: String) -> void: # 玩家动作 → 核心写调用的唯一通道(模态/无战斗拒绝在此收口) - if ui.modals.flow.locks_input(): + if ui.flow.locks_input(): return # 结算演播期棋盘冻结(v3 口径——不可点/不可移/不可挖): # 锁面=排播队列活跃(纯队列判据;排播泵尾迹窗口不可作演播中标志——见 row_pacer._process 注)。 @@ -605,7 +605,7 @@ func _on_board_action(cell: Vector2i, kind: String) -> void: func _on_stop_pressed() -> void: - if ui.modals.flow.locks_input(): + if ui.flow.locks_input(): return # stop 经批次提交(排雷玩法 D1×6①——不直调 行动经济) if run_scope.settlement == null: @@ -622,7 +622,7 @@ func _on_stop_pressed() -> void: func _on_refresh_confirmed() -> void: # 弃图刷新(挖掘期 Active 当场换图继续挖——D4;RAP 按门槛扣费 · 旧板封印作废,每轮上限读表) - if ui.modals.flow.locks_input(): + if ui.flow.locks_input(): return var ok: bool = false if run_scope.level_active and run_scope.runner != null: @@ -764,8 +764,8 @@ func _seed_value() -> int: # 重放模板 0 即在此种子下标定〕;置位 true=教学局已收束的非首局走 ③ 随机); # ③非首局空/非法框=引擎全局熵 randi()(Godot 4 启动自播种子,headless 同;不走 rng_service # 封闭流、禁 randomize()/自建 RNG——组合根建种子属引擎熵豁免面,不进玩法 RNG 消费) - if ui.modals.debug_overlay.seed_edit.text.is_valid_int(): - return int(ui.modals.debug_overlay.seed_edit.text) + if ui.modals.menu.debug_overlay.seed_edit.text.is_valid_int(): + return int(ui.modals.menu.debug_overlay.seed_edit.text) if save_manager.meta_state.career_state().first_adventure_done: return randi() # rng-seed-exempt(非首局空框引擎熵——组合根建种子不进玩法 RNG 消费,static_scan S1/S2 标记豁免) return 42 diff --git a/game/presentation/main_adventure_flow.gd b/game/presentation/main_adventure_flow.gd index f52a4557..8eedfa9f 100644 --- a/game/presentation/main_adventure_flow.gd +++ b/game/presentation/main_adventure_flow.gd @@ -30,7 +30,7 @@ static func _on_adventure_level_started(root: Variant, level_index: int, display # 开场行只显示 atk/max_hp 两值,注记指向不存在的显示轴反成失真),白板局不带 var ov: Dictionary[Variant, Variant] = root.meta.hero_override() var boosted: bool = int(ov.get("atk", 0)) > 0 or int(ov.get("max_hp", 0)) > 0 \ - or root.ui.modals.debug_overlay.atk_pct() > 0 or root.ui.modals.debug_overlay.hp_pct() > 0 \ + or root.ui.modals.menu.debug_overlay.atk_pct() > 0 or root.ui.modals.menu.debug_overlay.hp_pct() > 0 \ or root.run_scope.runner.journal().bonus_total("atk") > 0 \ or root.run_scope.runner.journal().bonus_total("max_hp") > 0 root.ui.battle.log_view.append_bb(root.Narrator.level_open_line(display_name, int(root.run_scope.adventure.master_seed), @@ -70,21 +70,21 @@ static func _on_runner_node(root: Variant, node: Dictionary, info: Dictionary) - root.ui.battle.log_view.append_bb(root.Narrator.reward_line()) elif t == "rest": var healed: int = int(info.get("healed", 0)) - root.ui.modals.flow.open(root.ui.modals.node_overlay) - root.ui.modals.node_overlay.show_rest(healed, int(info.get("hp", 0)), int(info.get("max_hp", 0))) + root.ui.flow.open(root.ui.modals.run.node_overlay) + root.ui.modals.run.node_overlay.show_rest(healed, int(info.get("hp", 0)), int(info.get("max_hp", 0))) root.ui.battle.log_view.append_bb(root.Narrator.rest_line(healed, int(info.get("hp", 0)), int(info.get("max_hp", 0)))) elif t == "shop": # 商店房兑付:到达即开店——商品/货架快照与余额经 runner 只读投影喂入; # 购买/离开归玩家意图(buy_requested/leave_pressed 接线),零自动推进。 # pool 面 = 共享池货架三选一同构卡;legacy 面 = 固定三件(off 对照承载) - root.ui.modals.flow.open(root.ui.modals.shop_overlay) + root.ui.flow.open(root.ui.modals.menu.shop_overlay) if root.run_scope.runner.shop.is_shop_pool(): - root.ui.modals.shop_overlay.open_shelf(root.run_scope.runner.shop.shop_shelf(), int(root.run_scope.runner.gp)) - root.ui.modals.shop_overlay.set_visit_info(int(root.run_scope.runner.shop.shop_bp_visit_buys()), float(root.db.run.shop.price_a)) # 爬价规则行 - root.ui.modals.shop_overlay.set_refresh_display(root.run_scope.runner.shop.shop_refresh_free_left(), root.run_scope.runner.shop.shop_refresh_cost(), int(root.run_scope.runner.gp) >= root.run_scope.runner.shop.shop_refresh_cost()) # MC-1 + root.ui.modals.menu.shop_overlay.open_shelf(root.run_scope.runner.shop.shop_shelf(), int(root.run_scope.runner.gp)) + root.ui.modals.menu.shop_overlay.set_visit_info(int(root.run_scope.runner.shop.shop_bp_visit_buys()), float(root.db.run.shop.price_a)) # 爬价规则行 + root.ui.modals.menu.shop_overlay.set_refresh_display(root.run_scope.runner.shop.shop_refresh_free_left(), root.run_scope.runner.shop.shop_refresh_cost(), int(root.run_scope.runner.gp) >= root.run_scope.runner.shop.shop_refresh_cost()) # MC-1 else: - root.ui.modals.shop_overlay.open_shop(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp)) - root.ui.modals.shop_overlay.set_visit_info(-1, 1.0) # legacy 面无爬价行(off 对照承载) + root.ui.modals.menu.shop_overlay.open_shop(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp)) + root.ui.modals.menu.shop_overlay.set_visit_info(-1, 1.0) # legacy 面无爬价行(off 对照承载) root.ui.battle.log_view.append_bb(root.Narrator.shop_line(int(root.run_scope.runner.gp))) root.logger.log_node(node, info, root.run_scope.runner.node_index) root._refresh_presentations() @@ -110,20 +110,20 @@ static func _on_adventure_finished(root: Variant, summary: Dictionary) -> void: if not detail.is_empty(): text += "\n" + root.Narrator.keystone_settle_text(detail, root._bonus.item_name_lookup()) if result == "completed": - root.ui.modals.end_overlay.open_victory(text, "返回冒险", root.ui.battle.board_zone.board_view.get_global_rect(), + root.ui.modals.run.end_overlay.open_victory(text, "返回冒险", root.ui.battle.board_zone.board_view.get_global_rect(), {"gp_summary": "本局 GP 入账 · 合计 %d" % int(summary.get("gp_final", 0))}) # GP 行(取数=summary gp_final 清账前快照);钥石明细已在 text(keystone_settle_text)——三行账 else: - root.ui.modals.end_overlay.open_defeat(text, "返回", root.ui.battle.board_zone.board_view.get_global_rect()) + root.ui.modals.run.end_overlay.open_defeat(text, "返回", root.ui.battle.board_zone.board_view.get_global_rect()) root.logger.log_level_end(summary) # 试玩时间线:冒险终局(摘要随载荷) if root.run_scope.runner != null: root.run_scope.runner.restore_hero_base() - root.ui.modals.flow.open_after_playback(root.ui.modals.end_overlay) + root.ui.flow.open_after_playback(root.ui.modals.run.end_overlay) root._refresh_presentations() static func _on_battle_end_continue(root: Variant) -> void: # 终局出口:返回开始界面(冒险摘要留档试玩日志;钥石在收束时入存档) - root.ui.modals.flow.close(root.ui.modals.end_overlay) - root.ui.modals.end_overlay.close() + root.ui.flow.close(root.ui.modals.run.end_overlay) + root.ui.modals.run.end_overlay.close() # 三选一停点挂起分支:战斗总结结算(Boss 胜利钥石增量行)确认后进三选一面 if root._bonus.resume_if_pending(): # 三选一停点在途恢复入口(curio_interact_controller) return @@ -131,9 +131,9 @@ static func _on_battle_end_continue(root: Variant) -> void: if root.run_scope.settlement_stop_timer.is_armed(): root.run_scope.settlement_stop_timer.disarm() _enter_level_screen(root) - root.ui.modals.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held()))) - root.ui.modals.level_screen.open_choices(root.run_scope.pending_candidates, root.run_scope.runner) - root.ui.modals.level_screen.apply_status(level_map_status_payload(root.run_scope.runner)) + root.ui.modals.run.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held()))) + root.ui.modals.run.level_screen.open_choices(root.run_scope.pending_candidates, root.run_scope.runner) + root.ui.modals.run.level_screen.apply_status(level_map_status_payload(root.run_scope.runner)) root.run_scope.pending_candidates = [] root._refresh_presentations() return @@ -141,7 +141,7 @@ static func _on_battle_end_continue(root: Variant) -> void: # 局态封印/账本/模态挂起全复位(局态字段清单见 run_scope.gd) root._debug.reset() root._begin_session(false) - root.ui.modals.debug_overlay.reset_sliders() + root.ui.modals.menu.debug_overlay.reset_sliders() root.ui.screens.start_overlay.visible = true root._refresh_presentations() @@ -158,7 +158,7 @@ static func _on_inter_next_pressed(root: Variant) -> void: static func _on_node_continue(root: Variant) -> void: - root.ui.modals.flow.close(root.ui.modals.node_overlay) + root.ui.flow.close(root.ui.modals.run.node_overlay) if root.run_scope.runner != null: # 确认 ≠ 推进——兑付确认后经 choices_presented 弹出选路口 root.run_scope.runner.continue_after_node() @@ -176,24 +176,24 @@ static func _on_choices_presented(root: Variant, candidates: Array) -> void: and String(root.run_scope.runner.current_node().get("type", "")) == "battle" and not candidates.is_empty(): root.run_scope.settlement_stop_timer.arm() root.run_scope.pending_candidates = candidates.duplicate() - root.ui.modals.end_overlay.open_victory(root.Narrator.battle_end_text("victory", false), "下一节点", + root.ui.modals.run.end_overlay.open_victory(root.Narrator.battle_end_text("victory", false), "下一节点", root.ui.battle.board_zone.board_view.get_global_rect(), {"gp_summary": "战利品 GP +%d · 合计 %d" % [root.run_scope.last_gain_amount, int(root.run_scope.runner.gp)]}) - root.ui.modals.flow.open_after_playback(root.ui.modals.end_overlay) + root.ui.flow.open_after_playback(root.ui.modals.run.end_overlay) root._refresh_presentations() return _enter_level_screen(root) - root.ui.modals.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held()))) - root.ui.modals.level_screen.open_choices(candidates, root.run_scope.runner) - root.ui.modals.level_screen.apply_status(level_map_status_payload(root.run_scope.runner)) + root.ui.modals.run.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held()))) + root.ui.modals.run.level_screen.open_choices(candidates, root.run_scope.runner) + root.ui.modals.run.level_screen.apply_status(level_map_status_payload(root.run_scope.runner)) root._refresh_presentations() static func _on_route_chosen(root: Variant, node_id: int) -> void: # 视图已按节点类型定去留——战斗/Boss 出屏(visible=false,此处收栈 # 记录一致性+回战斗屏);奖励/恢复/商店本屏保持激活(兑付覆盖层由节点事件域内弹出,模态栈 # 打开低层时自动隐藏本屏、关闭自动恢复) - if not root.ui.modals.level_screen.visible: - root.ui.modals.flow.close(root.ui.modals.level_screen) + if not root.ui.modals.run.level_screen.visible: + root.ui.flow.close(root.ui.modals.run.level_screen) _exit_level_screen(root) if root.run_scope.runner != null and not root.run_scope.runner.pick_next_node(node_id): push_error("[组合根] 选路被拒(编号 %d 不在候选)" % node_id) @@ -203,14 +203,14 @@ static func _on_route_chosen(root: Variant, node_id: int) -> void: static func _enter_level_screen(root: Variant) -> void: root.run_scope.level_screen_active = true root.ui.screens.battle_root.visible = false - root.ui.modals.flow.open(root.ui.modals.level_screen) + root.ui.flow.open(root.ui.modals.run.level_screen) root._sync_status_entry() # 屏级互斥切换同步(_open_modal 单口不识屏级状态) static func _exit_level_screen(root: Variant) -> void: root.run_scope.level_screen_active = false root.ui.screens.battle_root.visible = true - if root.ui.modals.level_screen.visible: - root.ui.modals.flow.close(root.ui.modals.level_screen) + if root.ui.modals.run.level_screen.visible: + root.ui.flow.close(root.ui.modals.run.level_screen) root._sync_status_entry() # 同上 diff --git a/game/presentation/main_debug_flow.gd b/game/presentation/main_debug_flow.gd index 949b96ed..4dce4189 100644 --- a/game/presentation/main_debug_flow.gd +++ b/game/presentation/main_debug_flow.gd @@ -21,17 +21,17 @@ static func _show_debug_overlay(root: Variant) -> void: # 门禁:对局中可开;本函数仍是唯一开窗入口,门禁逻辑收函数内: # 棋盘输入锁/状态角标判据排除浮窗, # 遮挡语义=ModalStack 登记制(他模态开时暂隐、关后自动恢复),此处零场景限制 - if root.ui.modals.debug_overlay == null: + if root.ui.modals.menu.debug_overlay == null: return - root.ui.modals.flow.open(root.ui.modals.debug_overlay) + root.ui.flow.open(root.ui.modals.menu.debug_overlay) _sync_debug_overlay(root) # 调试浮窗角标点击(两屏同款处理器——与状态角标同形制守卫;仍收口 _show_debug_overlay 单口) static func _on_debug_badge_pressed(root: Variant) -> void: - var map_on: bool = root.ui.modals.level_screen != null and root.ui.modals.level_screen.visible - if (map_on and root.ui.modals.flow.badge_greyed_map()) or (not map_on and root.ui.modals.flow.badge_greyed_battle()): + var map_on: bool = root.ui.modals.run.level_screen != null and root.ui.modals.run.level_screen.visible + if (map_on and root.ui.flow.badge_greyed_map()) or (not map_on and root.ui.flow.badge_greyed_battle()): return _show_debug_overlay(root) @@ -40,10 +40,10 @@ static func _on_debug_badge_pressed(root: Variant) -> void: # 浮窗上下文同步(当局数据刷新+对局中按钮禁用单点):调用面=开窗口与模态开合 # 单口(凡影响开态/对局态的路径必经其一——与 _sync_status_entry 同款纪律) static func _sync_debug_overlay(root: Variant) -> void: - if root.ui.modals.debug_overlay == null: + if root.ui.modals.menu.debug_overlay == null: return - root.ui.modals.debug_overlay.set_context_locked(root.run_scope.settlement != null or root.run_scope.is_adventure) # 对局中批量模拟/两层调试入口禁用(长程异步模拟与活跃结算并存态不开放) - root.ui.modals.debug_overlay.update_run_data(_debug_run_data(root)) # 当局数据只读区块喂入(开窗+模态开合信号驱动) + root.ui.modals.menu.debug_overlay.set_context_locked(root.run_scope.settlement != null or root.run_scope.is_adventure) # 对局中批量模拟/两层调试入口禁用(长程异步模拟与活跃结算并存态不开放) + root.ui.modals.menu.debug_overlay.update_run_data(_debug_run_data(root)) # 当局数据只读区块喂入(开窗+模态开合信号驱动) @@ -63,7 +63,7 @@ static func _debug_run_data(root: Variant) -> Dictionary: static func _close_debug_overlay(root: Variant) -> void: - root.ui.modals.flow.close(root.ui.modals.debug_overlay) # 模态栈单口——开始层全程未隐藏,无需恢复 + root.ui.flow.close(root.ui.modals.menu.debug_overlay) # 模态栈单口——开始层全程未隐藏,无需恢复 static func _on_tune_apply(root: Variant, atk_pct: int, hp_pct: int) -> void: # 加成无持久态可"落"——各战斗开局经 board_resolvers.debug_hero_override/runner 现算生效(应用=关闭浮窗) @@ -83,8 +83,8 @@ static func _start_debug_levels(root: Variant) -> void: # settlement/编排态=状态机破坏;模拟在跑时 reset_sliders 会作废测量读数(d5 置灰之外的信号绕过闸) if root.run_scope.is_adventure or root.run_scope.settlement != null or root._tune_sim.is_running(): return - root.ui.modals.debug_overlay.reset_sliders() - root.ui.modals.flow.close(root.ui.modals.debug_overlay) # 浮窗内入口发起——随开随收(对局期不可再开:门禁在 _show_debug_overlay) + root.ui.modals.menu.debug_overlay.reset_sliders() + root.ui.flow.close(root.ui.modals.menu.debug_overlay) # 浮窗内入口发起——随开随收(对局期不可再开:门禁在 _show_debug_overlay) root.ui.screens.start_overlay.visible = false root.ui.battle.log_view.clear() root.ui.battle.log_view.append_bb(root.Narrator.debug_open_line(root._seed_value(), 1, true)) diff --git a/game/presentation/main_esc_flow.gd b/game/presentation/main_esc_flow.gd index 9ec94068..40cfc2a1 100644 --- a/game/presentation/main_esc_flow.gd +++ b/game/presentation/main_esc_flow.gd @@ -18,22 +18,22 @@ 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.modals.flow.close(window) # ① 先关最上层功能窗(v2.3 定稿) + root.ui.flow.close(window) # ① 先关最上层功能窗(v2.3 定稿) return - if root.ui.modals.settings_overlay.visible: + if root.ui.modals.menu.settings_overlay.visible: return # ①' 设置开在浮窗之上 → 无反馈(浮窗不隔层关闭——契约 3) if esc.visible: - root.ui.modals.flow.close(esc) # ② 再按 Esc 关窗 + root.ui.flow.close(esc) # ② 再按 Esc 关窗 return if is_camp_context(root): - root.ui.modals.flow.open(esc) # ③ 营地语境唤起 + 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.career_overlay]: + root.ui.modals.camp.codex, root.ui.modals.menu.career_overlay]: if w != null and w.visible: return w return null @@ -42,13 +42,13 @@ static func _topmost_camp_window(root: Variant) -> Control: ## 返回主菜单动线(契约 4):首步 close 浮窗(缺此步浮窗残留主菜单上方)→营地与选档层同收+开始层恢复 static func _on_return_to_menu(root: Variant) -> void: - root.ui.modals.flow.close(root.ui.modals.camp.esc_overlay) + 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.modals.flow.open(root.ui.modals.settings_overlay) + root.ui.flow.open(root.ui.modals.menu.settings_overlay) ## 退出出口(_quit_game 原样复用——settings_store.save_settings+quit,零语义副作用) diff --git a/game/presentation/main_overlay_flow.gd b/game/presentation/main_overlay_flow.gd index 7df4e973..4a9e4e37 100644 --- a/game/presentation/main_overlay_flow.gd +++ b/game/presentation/main_overlay_flow.gd @@ -1,15 +1,15 @@ ## VERTICAL SLICE - NOT FOR PRODUCTION ## 组合根覆盖层流程承接件:设置/状态面板/生涯页/选档界面/图例/钱包事件的开合与数据投喂。 ## 本件是 main.gd 同名函数的逐字搬运(成员访问改 root 前缀)——main 侧留同名薄委托,探针 main.X 路径零改动。 -## 全部开合走组合根模态编排口(root.ui.modals.flow.open/close);本件零成员态。 +## 全部开合走组合根模态编排口(root.ui.flow.open/close);本件零成员态。 extends RefCounted static func _open_settings(root: Variant) -> void: root.ui.screens.start_overlay.visible = false - root.ui.modals.flow.open(root.ui.modals.settings_overlay) # 模态栈单口 + root.ui.flow.open(root.ui.modals.menu.settings_overlay) # 模态栈单口 static func _on_settings_closed(root: Variant) -> void: - root.ui.modals.flow.close(root.ui.modals.settings_overlay) # 模态栈单口 + root.ui.flow.close(root.ui.modals.menu.settings_overlay) # 模态栈单口 # 语境分支(卡⑤ 契约 3——谓词=camp_esc_overlay.visible,谁开的设置谁收): # 营地浮窗路径关设置回浮窗不回主菜单(栈语义:浮窗登记在下、由树序遮挡非属性隐藏); # 菜单路径保留开始层恢复硬副作用。 @@ -29,21 +29,21 @@ static func _status_data(root: Variant) -> Dictionary: return HudPresenter.status_data(root.db, root.meta, root.run_scope.runner, root._treasure_ui) static func _open_status_panel(root: Variant) -> void: - root.ui.modals.flow.open(root.ui.modals.status_panel) # 模态栈单口(开层前排空排播队列——既有层同款行为,目检确认项) - root.ui.modals.status_panel.open_status(_status_data(root)) + root.ui.flow.open(root.ui.modals.run.status_panel) # 模态栈单口(开层前排空排播队列——既有层同款行为,目检确认项) + root.ui.modals.run.status_panel.open_status(_status_data(root)) static func _on_status_closed(root: Variant) -> void: - root.ui.modals.flow.close(root.ui.modals.status_panel) # 模态栈单口 + root.ui.flow.close(root.ui.modals.run.status_panel) # 模态栈单口 static func _open_career(root: Variant) -> void: root.ui.screens.start_overlay.visible = false - root.ui.modals.flow.open(root.ui.modals.career_overlay) # 模态栈单口 - root.ui.modals.career_overlay.open_career(HudPresenter.career_data(root.db, root.save_manager, root.meta)) + root.ui.flow.open(root.ui.modals.menu.career_overlay) # 模态栈单口 + root.ui.modals.menu.career_overlay.open_career(HudPresenter.career_data(root.db, root.save_manager, root.meta)) static func _on_career_closed(root: Variant) -> void: - root.ui.modals.flow.close(root.ui.modals.career_overlay) # 模态栈单口 + root.ui.flow.close(root.ui.modals.menu.career_overlay) # 模态栈单口 if not root.ui.modals.camp.scene.visible: # 营地语境关生涯不回开始层(笔记本入口——卡④ T3;栈已复现场景根) root.ui.screens.start_overlay.visible = true @@ -70,17 +70,17 @@ static func _on_camp_object_pressed(root: Variant, key: StringName) -> void: _open_gate_confirm(root) &"campfire": root._confirm_consumer = "campfire_leave" - root.ui.modals.confirm_dialog.open("确定离开营地回到主菜单?", "离开", "留下") + root.ui.modals.menu.confirm_dialog.open("确定离开营地回到主菜单?", "离开", "留下") static func _open_gate_confirm(root: Variant) -> void: if root.save_manager.has_breakpoint(): root._confirm_consumer = "gate" - root.ui.modals.confirm_dialog.open_three("有未完成的冒险——继续上一段冒险,还是开始新冒险?", + root.ui.modals.menu.confirm_dialog.open_three("有未完成的冒险——继续上一段冒险,还是开始新冒险?", "继续上一段冒险", "开始新冒险", "取消") return root._confirm_consumer = "gate_new" - root.ui.modals.confirm_dialog.open("是否开始新冒险?", "是", "否") + root.ui.modals.menu.confirm_dialog.open("是否开始新冒险?", "是", "否") # 生涯页快照投影 —— 承接体见 HudPresenter.career_data(开层调用点直接改引) @@ -93,11 +93,11 @@ static func _open_gate_confirm(root: Variant) -> void: # 该钮的唯一落点(卡③ 批内探针函数直调=中间态,实现报告「已知中间态」登记)。 static func _open_save_select(root: Variant) -> void: root.ui.screens.start_overlay.visible = false - root.ui.modals.flow.open(root.ui.modals.save_select_overlay) # 模态栈单口 - root.ui.modals.save_select_overlay.open_save_select(HudPresenter.save_select_data(root.save_manager)) + root.ui.flow.open(root.ui.modals.menu.save_select_overlay) # 模态栈单口 + root.ui.modals.menu.save_select_overlay.open_save_select(HudPresenter.save_select_data(root.save_manager)) static func _on_save_select_closed(root: Variant) -> void: - root.ui.modals.flow.close(root.ui.modals.save_select_overlay) # 模态栈单口 + root.ui.flow.close(root.ui.modals.menu.save_select_overlay) # 模态栈单口 root.ui.screens.start_overlay.visible = true @@ -125,18 +125,18 @@ static func _on_save_select_load(root: Variant, slot: int) -> void: static func _on_save_select_delete(root: Variant, slot: int) -> void: root._confirm_consumer = "delete_slot" root._confirm_consumer_slot = slot - root.ui.modals.flow.open(root.ui.modals.confirm_dialog) # 模态栈单口(显示编排/挂起兜底/遮挡记账——与其余覆盖层同口) - root.ui.modals.confirm_dialog.open("删除存档位 %d?删除后该档不可恢复。" % slot, "删除", "取消") + root.ui.flow.open(root.ui.modals.menu.confirm_dialog) # 模态栈单口(显示编排/挂起兜底/遮挡记账——与其余覆盖层同口) + root.ui.modals.menu.confirm_dialog.open("删除存档位 %d?删除后该档不可恢复。" % slot, "删除", "取消") # 入口角标点击(两屏同款处理器):P1 守卫兜底——其他模态开着即拒开(常态防线=角标置灰, # 探针 pressed.emit 绕过 disabled 时由此兜住;地图屏自身的登记开态不计——has_open_excluding) static func _on_status_badge_pressed(root: Variant) -> void: - var map_on: bool = root.ui.modals.level_screen != null and root.ui.modals.level_screen.visible + var map_on: bool = root.ui.modals.run.level_screen != null and root.ui.modals.run.level_screen.visible # P1 守卫兜底——其他模态开着即拒开(常态防线=角标置灰,探针 pressed.emit 绕过 disabled 时由此兜住; # 地图屏自身的登记开态不计;判据排除调试浮窗:对局中浮窗开着时状态角标仍可点) - if (map_on and root.ui.modals.flow.badge_greyed_map()) or (not map_on and root.ui.modals.flow.badge_greyed_battle()): + if (map_on and root.ui.flow.badge_greyed_map()) or (not map_on and root.ui.flow.badge_greyed_battle()): return _open_status_panel(root) @@ -146,14 +146,14 @@ static func _on_status_badge_pressed(root: Variant) -> void: # 地图屏角标=随屏;置灰=登记册上除地图屏自身外任一层开着(防「看得见点不着」态)。 # 调用面=模态开合单口与屏级互斥口(凡影响开态/屏态的路径必经其一)。 static func _sync_status_entry(root: Variant) -> void: - if root.ui.modals.status_panel == null or root.ui.modals.flow == null or root.ui.battle.board_zone == null or root.ui.modals.level_screen == null: + if root.ui.modals.run.status_panel == null or root.ui.flow == null or root.ui.battle.board_zone == null or root.ui.modals.run.level_screen == null: return - var map_on: bool = root.ui.modals.level_screen.visible + var map_on: bool = root.ui.modals.run.level_screen.visible root.ui.battle.board_zone.status_badge.visible = root.run_scope.level_active and not map_on - root.ui.modals.level_screen.status_badge.visible = map_on + root.ui.modals.run.level_screen.status_badge.visible = map_on # 置灰判据为排除式(调试浮窗开态不计入:对局中浮窗开着时角标仍可点) - root.ui.battle.board_zone.status_badge.disabled = root.ui.modals.flow.badge_greyed_battle() # 互斥域谓词 - root.ui.modals.level_screen.status_badge.disabled = root.ui.modals.flow.badge_greyed_map() + root.ui.battle.board_zone.status_badge.disabled = root.ui.flow.badge_greyed_battle() # 互斥域谓词 + root.ui.modals.run.level_screen.status_badge.disabled = root.ui.flow.badge_greyed_map() # 调试浮层角标(全局浮层任何 UI 不遮挡——可见性不随场景变,只抬 offset # 避让:地图屏底栏 85px 常驻,角标抬至底栏上方 16px;其余场景右下角 16px 贴底) root.ui.screens.debug_badge.offset_bottom = -101.0 if map_on else -16.0 @@ -171,19 +171,19 @@ static func _on_wallet_changed(root: Variant, event: String, gp: int, extra: Dic root.logger.log_wallet(event, gp, extra) if String(event) == "gain": root.run_scope.last_gain_amount = int(extra.get("amount", 0)) # GP 增量取数(每战恰一笔 gain) - if root.ui.modals.shop_overlay.visible and root.run_scope.runner != null: + if root.ui.modals.menu.shop_overlay.visible and root.run_scope.runner != null: if root.run_scope.runner.shop.is_shop_pool(): - root.ui.modals.shop_overlay.refresh_shelf_state(int(root.run_scope.runner.gp)) # pool 面余额联动(货架内容仅买后重抽变) + root.ui.modals.menu.shop_overlay.refresh_shelf_state(int(root.run_scope.runner.gp)) # pool 面余额联动(货架内容仅买后重抽变) else: - root.ui.modals.shop_overlay.refresh_state(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp)) + root.ui.modals.menu.shop_overlay.refresh_state(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp)) root._refresh_presentations() static func _on_legend_toggled(root: Variant) -> void: - if root.ui.modals.legend_overlay.is_open(): - root.ui.modals.flow.close(root.ui.modals.legend_overlay) + if root.ui.modals.run.legend_overlay.is_open(): + root.ui.flow.close(root.ui.modals.run.legend_overlay) else: - root.ui.modals.flow.open(root.ui.modals.legend_overlay) + root.ui.flow.open(root.ui.modals.run.legend_overlay) diff --git a/game/presentation/main_ui_build.gd b/game/presentation/main_ui_build.gd index 09164e42..fb628fff 100644 --- a/game/presentation/main_ui_build.gd +++ b/game/presentation/main_ui_build.gd @@ -46,7 +46,7 @@ static func _build_screens(root: Variant) -> void: root.ui.battle.info_panel = root.ui.screens.adventure_screen.info_panel root.ui.battle.board_zone = root.ui.screens.adventure_screen.board_zone root.ui.battle.settlement_fx = root.ui.screens.adventure_screen.settlement_fx - root.ui.modals.level_screen = root.ui.screens.adventure_screen.level_screen + root.ui.modals.run.level_screen = root.ui.screens.adventure_screen.level_screen static func _build_battle_wiring(root: Variant) -> void: # 结算行排播泵(本体在 row_pacer.gd;本类注入视图句柄与呈现刷新回调,零信号连接) @@ -57,7 +57,7 @@ static func _build_battle_wiring(root: Variant) -> void: root.ui.screens.row_pacer.settlement_fx = root.ui.battle.settlement_fx root.ui.screens.row_pacer.board_view = root.ui.battle.board_zone.board_view root.ui.screens.row_pacer.refresh_request = root._refresh_presentations - root.ui.screens.row_pacer.pump_ended = root.ui.modals.flow.pump_ended # 演播完边沿统一派发(挂起 modal 优先,其次锁期点击重放) + root.ui.screens.row_pacer.pump_ended = root.ui.flow.pump_ended # 演播完边沿统一派发(挂起 modal 优先,其次锁期点击重放) root.add_child(root.ui.screens.row_pacer) # 秘宝两步交互(本体在 treasure_interact_controller.gd;settlement/runner/grant 随关卡/战斗更替, # 经现值回调注入——仿 identity_resolver 只读回调先例;grant 每战新建编排器,现值取件防悬挂) @@ -86,10 +86,10 @@ static func _build_overlays(root: Variant) -> void: # < 商店 < 营地场景根 < 四子界面 < 设置 < 状态面板(只读) < 生涯页(只读) < 确认弹窗 < 调试浮窗; # 关卡画面住冒险屏壳(树序在模态之下)——登记序仍夹在节点层与三选一层之间,遮挡语义零改动) var overlays: Dictionary[Variant, Variant] = UiAssembler.build_overlays(root, root.db) - root.ui.modals.legend_overlay = overlays.legend_overlay - root.ui.modals.node_overlay = overlays.node_overlay - root.ui.modals.end_overlay = overlays.end_overlay - root.ui.modals.shop_overlay = overlays.shop_overlay + root.ui.modals.run.legend_overlay = overlays.legend_overlay + root.ui.modals.run.node_overlay = overlays.node_overlay + root.ui.modals.run.end_overlay = overlays.end_overlay + root.ui.modals.menu.shop_overlay = overlays.shop_overlay root.ui.screens.camp_screen = CampScreenScene.instantiate() root.add_child(root.ui.screens.camp_screen) # 营地场景根装配(布局表经 ConfigDB 单点注入——视图禁自载表路径;CampOverlay 随四柜台拆解退役) @@ -120,58 +120,58 @@ static func _build_overlays(root: Variant) -> void: var hud_band := CampHudBand.new() root.ui.modals.camp.scene.set_hud_band(hud_band) # 屏壳/子界面插位由 LAYER_CONTRACT 槽统一落位——禁散落 move_child - root.ui.modals.legend_overlay.closed.connect(root.ui.modals.flow.close.bind(root.ui.modals.legend_overlay)) - root.ui.modals.node_overlay.continue_pressed.connect(root._on_node_continue) - root.ui.modals.level_screen.route_chosen.connect(root._on_route_chosen) - root.ui.modals.level_screen.status_badge.pressed.connect(root._on_status_badge_pressed) # 状态面板入口(地图屏顶栏角标) - root.ui.modals.level_screen.abandon_requested.connect(root._on_abandon_requested) # 放弃入口 - root.ui.modals.end_overlay.continue_pressed.connect(root._on_battle_end_continue) + root.ui.modals.run.legend_overlay.closed.connect(root.ui.flow.close.bind(root.ui.modals.run.legend_overlay)) + root.ui.modals.run.node_overlay.continue_pressed.connect(root._on_node_continue) + root.ui.modals.run.level_screen.route_chosen.connect(root._on_route_chosen) + root.ui.modals.run.level_screen.status_badge.pressed.connect(root._on_status_badge_pressed) # 状态面板入口(地图屏顶栏角标) + root.ui.modals.run.level_screen.abandon_requested.connect(root._on_abandon_requested) # 放弃入口 + root.ui.modals.run.end_overlay.continue_pressed.connect(root._on_battle_end_continue) static func _build_aux_panels(root: Variant) -> void: - root.ui.modals.settings_overlay = SettingsOverlay.new(root.settings_store) # 最小设置面(音量/全屏/减动效) - root.add_child(root.ui.modals.settings_overlay) - root.ui.modals.settings_overlay.closed.connect(root._on_settings_closed) + root.ui.modals.menu.settings_overlay = SettingsOverlay.new(root.settings_store) # 最小设置面(音量/全屏/减动效) + root.add_child(root.ui.modals.menu.settings_overlay) + root.ui.modals.menu.settings_overlay.closed.connect(root._on_settings_closed) # 局内状态面板·只读(登记设置层同档带 dim 遮罩,确认弹窗之前; # 数据=组合根开层实时读 MetaProgression 访问器喂入,本层零写通路) - root.ui.modals.status_panel = StatusPanel.new() - root.add_child(root.ui.modals.status_panel) - root.ui.modals.status_panel.closed.connect(root._on_status_closed) + root.ui.modals.run.status_panel = StatusPanel.new() + root.add_child(root.ui.modals.run.status_panel) + root.ui.modals.run.status_panel.closed.connect(root._on_status_closed) # 生涯页·只读全屏页(登记序=营地同档,设置层旁、确认弹窗之前; # 树序同步=addChild 同位(登记序=树序=z 序纪律);数据=组合根开层实时读 SaveManager 计数 # 访问器+MetaProgression 访问器喂入,本层零写通路零跳转;入口=开始层「营地与系统」组生涯钮) - root.ui.modals.career_overlay = CareerOverlay.new() - root.add_child(root.ui.modals.career_overlay) - root.ui.modals.career_overlay.closed.connect(root._on_career_closed) + root.ui.modals.menu.career_overlay = CareerOverlay.new() + root.add_child(root.ui.modals.menu.career_overlay) + root.ui.modals.menu.career_overlay.closed.connect(root._on_career_closed) # 选档界面(营地交互重做卡③):登记序=生涯同档(树序同步=addChild 同位);三动作只发意图信号 #(激活/创建/删除+确认分派归组合根——视图零数据层依赖);入口=「进入存档」钮(卡② mn3 接线)。 - root.ui.modals.save_select_overlay = SaveSelectOverlay.new() - root.add_child(root.ui.modals.save_select_overlay) - root.ui.modals.save_select_overlay.closed.connect(root._on_save_select_closed) - root.ui.modals.save_select_overlay.new_slot_requested.connect(root._on_save_select_new) - root.ui.modals.save_select_overlay.load_slot_requested.connect(root._on_save_select_load) - root.ui.modals.save_select_overlay.delete_slot_requested.connect(root._on_save_select_delete) + root.ui.modals.menu.save_select_overlay = SaveSelectOverlay.new() + root.add_child(root.ui.modals.menu.save_select_overlay) + root.ui.modals.menu.save_select_overlay.closed.connect(root._on_save_select_closed) + root.ui.modals.menu.save_select_overlay.new_slot_requested.connect(root._on_save_select_new) + root.ui.modals.menu.save_select_overlay.load_slot_requested.connect(root._on_save_select_load) + root.ui.modals.menu.save_select_overlay.delete_slot_requested.connect(root._on_save_select_delete) static func _build_curio(root: Variant) -> void: # 提升三选一覆盖层(z 序 = 节点层之上、终局层之下;确认后同拍推进; # 插位由 LAYER_CONTRACT curio 槽统一落位——禁散落 move_child) - root.ui.modals.curio_overlay = CurioPickOverlay.new() - root.add_child(root.ui.modals.curio_overlay) + root.ui.modals.run.curio_overlay = CurioPickOverlay.new() + root.add_child(root.ui.modals.run.curio_overlay) # 提升三选一与替换子交互(本体在 curio_interact_controller.gd;本类只接线与回调注入, # 模态栈/演播后排窗/呈现刷新/fatal 经回调注入,adventure/runner 现值回调) root._bonus = CurioInteractController.new(CurioFlowContext.new(root.db, - {"curio_overlay": root.ui.modals.curio_overlay, "end_overlay": root.ui.modals.end_overlay, + {"curio_overlay": root.ui.modals.run.curio_overlay, "end_overlay": root.ui.modals.run.end_overlay, "board_view": root.ui.battle.board_zone.board_view, "toast": root.ui.battle.board_zone.toast}, {"meta_get": func() -> Variant: return root.meta, "save_get": func() -> Variant: return root.save_manager, "adventure_get": func() -> Variant: return root.run_scope.adventure, "runner_get": func() -> Variant: return root.run_scope.runner, "adventure_mode_get": func() -> Variant: return root.run_scope.is_adventure}, - {"open_modal": root.ui.modals.flow.open, "open_modal_after_playback": root.ui.modals.flow.open_after_playback, - "close_modal": root.ui.modals.flow.close, "refresh_presentations": root._refresh_presentations, + {"open_modal": root.ui.flow.open, "open_modal_after_playback": root.ui.flow.open_after_playback, + "close_modal": root.ui.flow.close, "refresh_presentations": root._refresh_presentations, "fatal_screen": root._fatal_screen, "gp_summary": _gp_summary_cb(root)})) # 构造参数收拢 context(u20 三束) - root.ui.modals.curio_overlay.pick_confirmed.connect(root._bonus.on_pick_confirmed) - root.ui.modals.curio_overlay.continue_pressed.connect(root._bonus.on_continue) - root.ui.modals.curio_overlay.refresh_requested.connect(root._bonus.on_refresh) + root.ui.modals.run.curio_overlay.pick_confirmed.connect(root._bonus.on_pick_confirmed) + root.ui.modals.run.curio_overlay.continue_pressed.connect(root._bonus.on_continue) + root.ui.modals.run.curio_overlay.refresh_requested.connect(root._bonus.on_refresh) static func _build_menu_wiring(root: Variant) -> void: # 菜单屏接线(句柄经屏壳暴露;三入口化——进入存档/设置/退出游戏,卡② mn3: @@ -192,17 +192,17 @@ static func _build_menu_wiring(root: Variant) -> void: root._camp_pres = CampShopPresenter.new(CampFlowContext.new(root.db, {"camp_scene": root.ui.modals.camp.scene, "forge_view": root.ui.modals.camp.forge, "treasure_view": root.ui.modals.camp.treasure, "tree_view": root.ui.modals.camp.tree, - "codex_view": root.ui.modals.camp.codex, "shop_overlay": root.ui.modals.shop_overlay, + "codex_view": root.ui.modals.camp.codex, "shop_overlay": root.ui.modals.menu.shop_overlay, "toast": root.ui.battle.board_zone.toast, "start_overlay": root.ui.screens.start_overlay, - "save_select_overlay": root.ui.modals.save_select_overlay, "logger": root.logger, + "save_select_overlay": root.ui.modals.menu.save_select_overlay, "logger": root.logger, "refresh_bus": camp_bus}, {"meta_get": func() -> Variant: return root.meta, "save_get": func() -> Variant: return root.save_manager, "runner_get": func() -> Variant: return root.run_scope.runner}, - {"open_modal": root.ui.modals.flow.open, "close_modal": root.ui.modals.flow.close, + {"open_modal": root.ui.flow.open, "close_modal": root.ui.flow.close, "refresh_presentations": root._refresh_presentations})) # 构造参数收拢 context(u21 三束) - root.ui.modals.shop_overlay.buy_requested.connect(root._camp_pres.on_shop_buy) - root.ui.modals.shop_overlay.refresh_requested.connect(root._camp_pres.on_shop_refresh) # MC-1 - root.ui.modals.shop_overlay.leave_pressed.connect(root._camp_pres.on_shop_leave) + root.ui.modals.menu.shop_overlay.buy_requested.connect(root._camp_pres.on_shop_buy) + root.ui.modals.menu.shop_overlay.refresh_requested.connect(root._camp_pres.on_shop_refresh) # MC-1 + root.ui.modals.menu.shop_overlay.leave_pressed.connect(root._camp_pres.on_shop_leave) # 营地信号接线(CampOverlay 退役——四子界面意图信号+场景点选组合根路由+closed 经 bind 携视图) root.ui.modals.camp.forge.forge_buy_requested.connect(root._camp_pres.on_camp_forge_buy) root.ui.modals.camp.forge.forge_unlock_requested.connect(root._camp_pres.on_camp_forge_unlock) @@ -229,13 +229,13 @@ static func _build_debug_and_resolvers(root: Variant) -> void: #(纪律「登记序 = 树序 = z 序」——confirm_dialog.open 禁运行时 move_child 自提抬层; # 弹窗只在开始菜单域弹出,绘制序恒最上; # 诊断转储入口在调试浮窗) - root.ui.modals.confirm_dialog = ConfirmDialog.new() - root.add_child(root.ui.modals.confirm_dialog) + root.ui.modals.menu.confirm_dialog = ConfirmDialog.new() + root.add_child(root.ui.modals.menu.confirm_dialog) # 消费方编排(卡③ 契约 3/sl8——禁双常驻静态接线):open 前设 _confirm_consumer, # confirmed/cancelled 后按该状态量分派;分派面可容纳后续消费方(卡④ 到位直接接入不返工) - root.ui.modals.confirm_dialog.confirmed.connect(root._on_confirm_accepted) - root.ui.modals.confirm_dialog.secondary_confirmed.connect(root._on_confirm_secondary) # 三钮次确认(卡④ 大门「开始新冒险」出口) - root.ui.modals.confirm_dialog.cancelled.connect(root._on_confirm_declined) + root.ui.modals.menu.confirm_dialog.confirmed.connect(root._on_confirm_accepted) + root.ui.modals.menu.confirm_dialog.secondary_confirmed.connect(root._on_confirm_secondary) # 三钮次确认(卡④ 大门「开始新冒险」出口) + root.ui.modals.menu.confirm_dialog.cancelled.connect(root._on_confirm_declined) # Esc 浮窗三出口接线(卡⑤——意图信号经薄委托归 MainEscFlow 动线;输入入口=main._unhandled_input) root.ui.modals.camp.esc_overlay.return_requested.connect(root._on_esc_return) root.ui.modals.camp.esc_overlay.settings_requested.connect(root._on_esc_settings) @@ -246,15 +246,15 @@ static func _build_debug_and_resolvers(root: Variant) -> void: # 诊断转储入口在本浮窗,玩家画面零常驻调试件)。 # 树序在开始层与确认弹窗之后 = 浮于菜单之上的 Dialog(不隐藏开始层——与确认弹窗同款形态); # 开窗唯一入口 = _show_debug_overlay(C6 单口——角标/探针/启动路径全收口) - root.ui.modals.debug_overlay = DebugOverlay.new(root.db, int(root.db.combat.hero.atk), int(root.db.combat.hero.max_hp)) - root.add_child(root.ui.modals.debug_overlay) - root.ui.modals.debug_overlay.closed.connect(root.ui.modals.flow.close.bind(root.ui.modals.debug_overlay)) - root.ui.modals.debug_overlay.apply_requested.connect(root._on_tune_apply) - root.ui.modals.debug_overlay.simulate_requested.connect(root._on_tune_simulate) - root.ui.modals.debug_overlay.debug_levels_requested.connect(root._start_debug_levels) + root.ui.modals.menu.debug_overlay = DebugOverlay.new(root.db, int(root.db.combat.hero.atk), int(root.db.combat.hero.max_hp)) + root.add_child(root.ui.modals.menu.debug_overlay) + root.ui.modals.menu.debug_overlay.closed.connect(root.ui.flow.close.bind(root.ui.modals.menu.debug_overlay)) + root.ui.modals.menu.debug_overlay.apply_requested.connect(root._on_tune_apply) + root.ui.modals.menu.debug_overlay.simulate_requested.connect(root._on_tune_simulate) + root.ui.modals.menu.debug_overlay.debug_levels_requested.connect(root._start_debug_levels) # 棋盘解析回调族(有态绑定件——board_resolvers.gd 承接;组合根只接线;局部持有不占类成员位) - var resolvers = BoardResolversScript.new(root.db, root.ui.modals.debug_overlay, func() -> Variant: return root.run_scope.settlement) + var resolvers = BoardResolversScript.new(root.db, root.ui.modals.menu.debug_overlay, func() -> Variant: return root.run_scope.settlement) # 奖励徽章数据源:地图战斗资源 身份解析只读回调(已开格身份 icon——挖掘期 Active 相位内有效) root.ui.battle.board_zone.board_view.set_identity_resolver(resolvers.identity_lookup) # 数字遮蔽数据源:地形变异 hide_number 解析只读回调(信息型呈现——board_view 渲染面读取) @@ -271,21 +271,21 @@ static func _build_modal_flow(root: Variant) -> void: # _apply_layer_contract 统一落位(关卡画面宿住冒险屏壳、营地屏壳夹商店与设置层之间、 # 三选一对位终局层之下等特例均在合同数据内表达,禁散落 move_child) # 模态编排注入(ModalFlow 纯逻辑类:依赖仅 ModalStack+查询 Callable——实例随 ui.modals 容器构造建立) - root.ui.modals.flow.start_visible_get = func() -> bool: return root.ui.screens.start_overlay.visible - root.ui.modals.flow.inter_visible_get = func() -> bool: return root.ui.screens.inter_overlay.visible - root.ui.modals.flow.settlement_get = func() -> Variant: return root.run_scope.settlement - root.ui.modals.flow.rows_empty_get = func() -> bool: return root.ui.screens.row_pacer.pending_rows().is_empty() - root.ui.modals.flow.flush_rows = root.ui.screens.row_pacer.flush - root.ui.modals.flow.on_open_sync = func() -> void: + root.ui.flow.start_visible_get = func() -> bool: return root.ui.screens.start_overlay.visible + root.ui.flow.inter_visible_get = func() -> bool: return root.ui.screens.inter_overlay.visible + root.ui.flow.settlement_get = func() -> Variant: return root.run_scope.settlement + root.ui.flow.rows_empty_get = func() -> bool: return root.ui.screens.row_pacer.pending_rows().is_empty() + root.ui.flow.flush_rows = root.ui.screens.row_pacer.flush + root.ui.flow.on_open_sync = func() -> void: root._sync_status_entry() # 状态面板入口角标可见性/置灰随开态同步 root._sync_debug_overlay() # 浮窗当局数据/按钮态随开态同步 - root.ui.modals.flow.on_close_sync = func() -> void: + root.ui.flow.on_close_sync = func() -> void: root._sync_status_entry() # 关闭恢复角标可点态(无残留——同源口径) root._sync_debug_overlay() # 他层关闭复活浮窗时同步刷新(关后自动恢复语义配套) - root.ui.modals.flow.replay_click = root._replay_locked_click + root.ui.flow.replay_click = root._replay_locked_click # 视觉层合同装配(唯一事实源=LAYER_CONTRACT:树序+登记序同源派生——改序只改合同表) root.ui.apply_layer_contract(root) - root._tune_sim = TuneSimController.new(root.db, root.ui.modals.debug_overlay, int(root.db.combat.hero.max_hp)) + root._tune_sim = TuneSimController.new(root.db, root.ui.modals.menu.debug_overlay, int(root.db.combat.hero.max_hp)) static func fatal_screen(root: Variant, msg: String) -> void: var l := UiKit.label(msg,UiKit.FS_SUB, true, UiKit.RED_ALERT) diff --git a/game/presentation/ui_camp_modals.gd b/game/presentation/ui_camp_modals.gd index 69cbd2a3..22db9832 100644 --- a/game/presentation/ui_camp_modals.gd +++ b/game/presentation/ui_camp_modals.gd @@ -1,6 +1,6 @@ ## 营地交互域模态句柄子容器:持营地域全部模态句柄(终态六枚=营地场景根+锻造/秘宝/天赋树/图鉴四子界面+Esc 浮窗)。 -## RefCounted 不挂树;随 UiModals._init 构造(与 flow 同拍)。Esc 浮窗枚随卡⑤落——禁空占位凑结构。 -## 新营地句柄禁平铺回 ui_modals:营地域句柄寻址一律 ui.modals.camp.X 二层(模态句柄册分域纪律——全量分家另立批)。 +## RefCounted 不挂树;随 UiModals._init 构造(flow 同拍升格由 UiRegistry._init 直驻)。Esc 浮窗枚随卡⑤落——禁空占位凑结构。 +## 新营地句柄禁平铺回 ui_modals:营地域句柄寻址一律 ui.modals.camp.X 二层(模态句柄册分域纪律——册级全量分家已落地)。 class_name UiCampModals extends RefCounted diff --git a/game/presentation/ui_menu_modals.gd b/game/presentation/ui_menu_modals.gd new file mode 100644 index 00000000..5f78a77d --- /dev/null +++ b/game/presentation/ui_menu_modals.gd @@ -0,0 +1,11 @@ +## 菜单与跨语境域六句柄唯一容器——菜单层与多语境共用模态的句柄册(纯字段 RefCounted)。 +## 分域纪律册级化:任何模态句柄禁平铺回 ui_modals(营地先例条款推广)——寻址一律 ui.modals.menu.X 二段式。 +class_name UiMenuModals +extends RefCounted + +var shop_overlay: ShopOverlay # 商店(冒险商店节点+营地商店跨语境) +var settings_overlay: SettingsOverlay # 设置(开始层+营地 Esc 双路径跨语境) +var career_overlay: CareerOverlay # 生涯记录(开始层笔记本+营地语境跨语境) +var save_select_overlay: SaveSelectOverlay # 选档(开始层+营地大门流跨语境) +var confirm_dialog: ConfirmDialog # 确认框(全语境) +var debug_overlay: DebugOverlay # 调试浮窗(全语境,non_locking) diff --git a/game/presentation/ui_menu_modals.gd.uid b/game/presentation/ui_menu_modals.gd.uid new file mode 100644 index 00000000..01cba150 --- /dev/null +++ b/game/presentation/ui_menu_modals.gd.uid @@ -0,0 +1 @@ +uid://byakojaxcljbw diff --git a/game/presentation/ui_modals.gd b/game/presentation/ui_modals.gd index e068d317..5cd1a3b3 100644 --- a/game/presentation/ui_modals.gd +++ b/game/presentation/ui_modals.gd @@ -1,33 +1,17 @@ -## 模态层域句柄册:十二个平铺模态句柄+营地交互域子容器(camp)+模态栈编排(ModalFlow——登记/开合/输入锁谓词)的注册面域容器。 -## RefCounted 不挂树;模态登记唯一口=register_layer(anchors 撑满宿主+排入 ModalStack)。 -## flow 随容器构造即建(装配早期注入点经 root.ui.modals.flow.* 取 Callable——晚于 flow 建立时点)。 +## 模态层域句柄册:三子容器(run=局内冒险域六句柄/menu=菜单与跨语境域六句柄/camp=营地域六句柄) +## 的纯字段容器——与 UiScreens/UiBattle 同型。 +## 分域纪律全册化:任何模态句柄禁平铺回本册(营地先例条款推广)——子容器成员寻址一律 ui.modals.<域>.X 二段式。 +## RefCounted 不挂树;模态登记唯一口=register_layer 已迁驻注册面 ui_registry(登记职责随编排域—— +## ModalFlow 升格由 UiRegistry._init 直驻,本册无 flow 成员)。 class_name UiModals extends RefCounted -var flow: ModalFlow -var legend_overlay: LegendOverlay -var node_overlay: NodeOverlay -var level_screen: LevelMapScreen -var curio_overlay: CurioPickOverlay -var end_overlay: BattleEndOverlay -var shop_overlay: ShopOverlay +var run: UiRunModals +var menu: UiMenuModals var camp: UiCampModals -var settings_overlay: SettingsOverlay -var status_panel: StatusPanel -var career_overlay: CareerOverlay -var save_select_overlay: SaveSelectOverlay -var confirm_dialog: ConfirmDialog -var debug_overlay: DebugOverlay func _init() -> void: - flow = ModalFlow.new() # 查询/钩子 Callable 由 _build_modal_flow 注入(注入时点不变——注入值面向已在座实例) + run = UiRunModals.new() + menu = UiMenuModals.new() camp = UiCampModals.new() # 营地域句柄子容器(分域纪律——营地域句柄禁平铺回本册) - - -func register_layer(m: Control, tags: Array = []) -> void: - # 撑满宿主(Control 默认锚 = 0×0——全屏变暗层与居中布局整体失效;route 选路层预览截图实证后 - # 统一收口:六个模态同病,探针经信号直发绕过真实点击从未暴露。anchors_and_offsets 而非 anchors - # ——后者会把控件重置回最小内容尺寸,ui_assembler 头注同款纪律) - m.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) - flow.register(m, tags) # 排除组随层登记(non_locking=调试浮窗/screen_state=关卡地图屏) diff --git a/game/presentation/ui_registry.gd b/game/presentation/ui_registry.gd index ad4db46b..f8b9ad8c 100644 --- a/game/presentation/ui_registry.gd +++ b/game/presentation/ui_registry.gd @@ -1,6 +1,7 @@ ## 界面注册面:呈现层界面元素清单的唯一事实源——哪些层、什么序、地址在哪。 -## 持有三域容器(screens/battle/modals)、LAYER_CONTRACT 合同表、node(key) 合同寻址、 -## apply_layer_contract(root) 唯一落位口(全仓唯一 move_child 点)。 +## 持有三域容器(screens/battle/modals)+模态编排件 ModalFlow 直驻(自 UiModals 升格——构造时点 +## 与今等价:注册面构造点先于装配)、模态登记唯一口 register_layer(随登记职责迁驻)、 +## LAYER_CONTRACT 合同表、node(key) 合同寻址、apply_layer_contract(root) 唯一落位口(全仓唯一 move_child 点)。 ## RefCounted 不挂树:零信号连接、零会话期引用、零核心写调用——组合根单点装配不变。 class_name UiRegistry extends RefCounted @@ -42,12 +43,23 @@ const LAYER_CONTRACT: Array[Dictionary] = [ var screens: UiScreens var battle: UiBattle var modals: UiModals +var flow: ModalFlow # 模态编排件直驻注册面(升格自 UiModals——查询/钩子 Callable 由 _build_modal_flow 注入,注入时点不变) func _init() -> void: screens = UiScreens.new() battle = UiBattle.new() modals = UiModals.new() + flow = ModalFlow.new() + + +# 模态登记唯一口(自 ui_modals.gd 逐字迁驻——登记职责随编排域归注册面单点) +func register_layer(m: Control, tags: Array = []) -> void: + # 撑满宿主(Control 默认锚 = 0×0——全屏变暗层与居中布局整体失效;route 选路层预览截图实证后 + # 统一收口:六个模态同病,探针经信号直发绕过真实点击从未暴露。anchors_and_offsets 而非 anchors + # ——后者会把控件重置回最小内容尺寸,ui_assembler 头注同款纪律) + m.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + flow.register(m, tags) # 排除组随层登记(non_locking=调试浮窗/screen_state=关卡地图屏) # 合同层寻址(key→节点;纯查表不含层序语义——层序唯一事实源=LAYER_CONTRACT) @@ -56,12 +68,12 @@ func node(key: String) -> Node: "adventure_screen": return screens.adventure_screen "menu_screen": return screens.menu_screen "row_pacer": return screens.row_pacer - "legend_overlay": return modals.legend_overlay - "node_overlay": return modals.node_overlay - "level_screen": return modals.level_screen - "curio_overlay": return modals.curio_overlay - "end_overlay": return modals.end_overlay - "shop_overlay": return modals.shop_overlay + "legend_overlay": return modals.run.legend_overlay + "node_overlay": return modals.run.node_overlay + "level_screen": return modals.run.level_screen + "curio_overlay": return modals.run.curio_overlay + "end_overlay": return modals.run.end_overlay + "shop_overlay": return modals.menu.shop_overlay "camp_screen": return screens.camp_screen "camp_scene": return modals.camp.scene "forge_view": return modals.camp.forge @@ -69,13 +81,13 @@ func node(key: String) -> Node: "tree_view": return modals.camp.tree "codex_view": return modals.camp.codex "camp_esc_overlay": return modals.camp.esc_overlay - "settings_overlay": return modals.settings_overlay - "status_panel": return modals.status_panel - "career_overlay": return modals.career_overlay - "save_select_overlay": return modals.save_select_overlay + "settings_overlay": return modals.menu.settings_overlay + "status_panel": return modals.run.status_panel + "career_overlay": return modals.menu.career_overlay + "save_select_overlay": return modals.menu.save_select_overlay "inter_overlay": return screens.inter_overlay - "confirm_dialog": return modals.confirm_dialog - "debug_overlay": return modals.debug_overlay + "confirm_dialog": return modals.menu.confirm_dialog + "debug_overlay": return modals.menu.debug_overlay "debug_badge": return screens.debug_badge return null @@ -94,7 +106,7 @@ func apply_layer_contract(root: Control) -> void: root.move_child(shell, slot) slot += 1 if member != null: - modals.register_layer(member as Control, tags) + register_layer(member as Control, tags) else: push_error("[合同装配] 层缺失: %s" % String(entry["key"])) continue @@ -103,9 +115,9 @@ func apply_layer_contract(root: Control) -> void: push_error("[合同装配] 层缺失: %s" % String(entry["key"])) continue if entry.has("host"): - modals.register_layer(layer as Control, tags) + register_layer(layer as Control, tags) continue root.move_child(layer, slot) slot += 1 if String(entry["kind"]) == "modal": - modals.register_layer(layer as Control, tags) + register_layer(layer as Control, tags) diff --git a/game/presentation/ui_run_modals.gd b/game/presentation/ui_run_modals.gd new file mode 100644 index 00000000..23cd57a4 --- /dev/null +++ b/game/presentation/ui_run_modals.gd @@ -0,0 +1,11 @@ +## 局内冒险域六句柄唯一容器——冒险会话内开合的模态呈现面句柄册(纯字段 RefCounted)。 +## 分域纪律册级化:任何模态句柄禁平铺回 ui_modals(营地先例条款推广)——寻址一律 ui.modals.run.X 二段式。 +class_name UiRunModals +extends RefCounted + +var legend_overlay: LegendOverlay # 战斗中图例(棋盘角标触发) +var node_overlay: NodeOverlay # 节点结算窗 +var level_screen: LevelMapScreen # 关卡地图屏(screen_state 全屏态) +var curio_overlay: CurioPickOverlay # 奖励三选一 +var end_overlay: BattleEndOverlay # 终局面板 +var status_panel: StatusPanel # 状态面板(地图/棋盘两角标入口) diff --git a/game/presentation/ui_run_modals.gd.uid b/game/presentation/ui_run_modals.gd.uid new file mode 100644 index 00000000..5e0f9065 --- /dev/null +++ b/game/presentation/ui_run_modals.gd.uid @@ -0,0 +1 @@ +uid://ssh8gogewptf diff --git a/game/probe/fx_shot_probe.gd b/game/probe/fx_shot_probe.gd index eed8663e..14075ee3 100644 --- a/game/probe/fx_shot_probe.gd +++ b/game/probe/fx_shot_probe.gd @@ -37,14 +37,14 @@ func _ready() -> void: # 等关卡画面出图(choices_presented 异步到达——最多 3s 轮询) for i in 60: await get_tree().process_frame - if main.ui.modals.level_screen != null and main.ui.modals.level_screen.visible: + if main.ui.modals.run.level_screen != null and main.ui.modals.run.level_screen.visible: break - if main.ui.modals.level_screen == null or not main.ui.modals.level_screen.visible: + if main.ui.modals.run.level_screen == null or not main.ui.modals.run.level_screen.visible: _fail("关卡画面未出现(函数直调中间态路径)") return for i in 5: await get_tree().process_frame - main.ui.modals.level_screen.press_candidate(0) + main.ui.modals.run.level_screen.press_candidate(0) for i in 20: await get_tree().process_frame var st = main.run_scope.settlement diff --git a/game/probe/layout_probe.gd b/game/probe/layout_probe.gd index 0346ae24..cdeebcdc 100644 --- a/game/probe/layout_probe.gd +++ b/game/probe/layout_probe.gd @@ -24,12 +24,12 @@ func _ready() -> void: add_child(main) await get_tree().process_frame ProbeUiUtil.isolate_save(main, "smoke_layout_probe", true, false, true) - main.ui.modals.debug_overlay.seed_edit.text = "42" # 种子钉死(确定性禁依赖默认随机分支) + main.ui.modals.menu.debug_overlay.seed_edit.text = "42" # 种子钉死(确定性禁依赖默认随机分支) main._start_adventure_from_menu() for i in range(8): await get_tree().process_frame # 进战斗屏——三区(B 战斗画面/C 角色信息/D 扫雷图)同屏常驻,菜单屏不构成断言面 - main.ui.modals.level_screen.press_candidate(0) + main.ui.modals.run.level_screen.press_candidate(0) for i in range(20): await get_tree().process_frame for vp in VIEWPORTS: diff --git a/game/probe/probe_ui_util.gd b/game/probe/probe_ui_util.gd index 27b614b9..f885dfb2 100644 --- a/game/probe/probe_ui_util.gd +++ b/game/probe/probe_ui_util.gd @@ -130,7 +130,7 @@ static func open_camp_via_savesel(main, tag: String) -> bool: if FileAccess.file_exists(sp): DirAccess.remove_absolute(sp) main._open_save_select() - var new_btn: Button = main.ui.modals.save_select_overlay._new_btns.get(1) + var new_btn: Button = main.ui.modals.menu.save_select_overlay._new_btns.get(1) if new_btn == null: return false new_btn.pressed.emit() # 真实按钮信号路径 → _on_save_select_new → create_slot+open_camp diff --git a/game/probe/screen_flow_probe.gd b/game/probe/screen_flow_probe.gd index b2232b59..ef87e290 100644 --- a/game/probe/screen_flow_probe.gd +++ b/game/probe/screen_flow_probe.gd @@ -54,7 +54,7 @@ func _screen_seals() -> Dictionary: "菜单": main.ui.screens.start_overlay != null and main.ui.screens.start_overlay.visible, "营地": main.ui.modals.camp.scene != null and main.ui.modals.camp.scene.visible, "冒险": main.run_scope != null and main.run_scope.is_adventure, - "终局": main.ui.modals.end_overlay != null and main.ui.modals.end_overlay.visible, + "终局": main.ui.modals.run.end_overlay != null and main.ui.modals.run.end_overlay.visible, } @@ -102,7 +102,7 @@ func _frames(n: int = 2) -> void: func _step_boot_menu() -> void: _expect_exclusive("①boot", "菜单") _expect_adventure_seal("①boot", false) - if main.ui.modals.debug_overlay != null and main.ui.modals.debug_overlay.visible: + if main.ui.modals.menu.debug_overlay != null and main.ui.modals.menu.debug_overlay.visible: _problems.append("①boot:调试浮窗 boot 自动开(菜单优先失守)") @@ -123,9 +123,9 @@ func _step_camp_back_to_menu() -> void: return main.ui.modals.camp.scene.object_pressed.emit(&"campfire") await _frames() - if not main.ui.modals.confirm_dialog.visible: + if not main.ui.modals.menu.confirm_dialog.visible: _problems.append("③篝火:二次确认弹窗未现(camp.md v2.2 必须先确认)〔营地图批 V7〕") - main.ui.modals.confirm_dialog.confirmed.emit() # 二次确认离场(confirmed 先于关层——卡④ T4) + main.ui.modals.menu.confirm_dialog.confirmed.emit() # 二次确认离场(confirmed 先于关层——卡④ T4) await _frames() _expect_exclusive("③营地→菜单(回退)", "菜单") @@ -143,15 +143,15 @@ func _step_save_select_open_back() -> void: esb.pressed.emit() # 真实按钮信号路径 → _open_save_select(卡② mn3 点击链——卡③ 直调中间态就此闭环) await get_tree().process_frame var seals := _screen_seals() - if not main.ui.modals.save_select_overlay.visible or not seals_check_single(seals): - _problems.append("[savesel] sl10 开层后屏级互斥失守(选档=%s)" % str(main.ui.modals.save_select_overlay.visible)) - var back_btn: Button = ProbeUiUtil.find_button(main.ui.modals.save_select_overlay, "返回", "prefix") + if not main.ui.modals.menu.save_select_overlay.visible or not seals_check_single(seals): + _problems.append("[savesel] sl10 开层后屏级互斥失守(选档=%s)" % str(main.ui.modals.menu.save_select_overlay.visible)) + var back_btn: Button = ProbeUiUtil.find_button(main.ui.modals.menu.save_select_overlay, "返回", "prefix") if back_btn == null: _problems.append("[savesel] sl10 返回钮缺失(页壳顶条)") else: back_btn.pressed.emit() await get_tree().process_frame - if main.ui.modals.save_select_overlay.visible or not main.ui.screens.start_overlay.visible: + if main.ui.modals.menu.save_select_overlay.visible or not main.ui.screens.start_overlay.visible: _problems.append("[savesel] sl10 返回后选档未隐藏或开始层未恢复(关闭链失守)") # ESC 语境:再开选档→合成 ESC 键→层不关、流程零扰动(ESC 不属选档语境——零实现断言) main._open_save_select() @@ -162,7 +162,7 @@ func _step_save_select_open_back() -> void: ev.pressed = true main.get_viewport().push_input(ev, true) await get_tree().process_frame - if not main.ui.modals.save_select_overlay.visible: + if not main.ui.modals.menu.save_select_overlay.visible: _problems.append("[savesel] sl10 ESC 关闭了选档层(选档语境 ESC 应不响应——卡⑤ 范围外)") main._on_save_select_closed() await get_tree().process_frame @@ -174,7 +174,7 @@ func seals_check_single(f: Dictionary) -> bool: for k in f: if bool(f[k]): on += 1 - if main.ui.modals.save_select_overlay.visible: + if main.ui.modals.menu.save_select_overlay.visible: on += 1 # 选档自身计入(调用点已开层) return on == 1 @@ -187,25 +187,25 @@ func _step_menu_to_adventure() -> void: _expect_adventure_seal("④菜单→冒险", true) if main.run_scope != null and main.run_scope.adventure == null: _problems.append("④菜单→冒险:冒险实例未建立(入口停点未达)") - if main.ui.modals.level_screen != null and not main.ui.modals.level_screen.visible: + if main.ui.modals.run.level_screen != null and not main.ui.modals.run.level_screen.visible: _problems.append("④菜单→冒险:入口停点未落在关卡地图屏(level_screen 不可见)") # ---- ⑤ 冒险内屏切换:关卡地图屏 ⇄ 战斗屏(屏内两态,屏级仍独占)---- func _step_level_to_battle_and_back() -> void: - if not main.ui.modals.level_screen.visible: + if not main.ui.modals.run.level_screen.visible: _problems.append("⑤冒险内切换:前置不在关卡地图屏") return main._exit_level_screen() await _frames() - if main.ui.modals.level_screen.visible: + if main.ui.modals.run.level_screen.visible: _problems.append("⑤冒险内切换:退出关卡屏后 level_screen 仍可见") if main.ui.screens.battle_root != null and not main.ui.screens.battle_root.visible: _problems.append("⑤冒险内切换:战斗屏未接手(battle_root 不可见)") _expect_exclusive("⑤冒险内切换(关卡→战斗)", "冒险") main._enter_level_screen() await _frames() - if not main.ui.modals.level_screen.visible: + if not main.ui.modals.run.level_screen.visible: _problems.append("⑤冒险内切换:回到关卡地图屏失败") _expect_exclusive("⑤冒险内切换(战斗→关卡)", "冒险") @@ -218,16 +218,16 @@ func _step_abandon_to_end() -> void: main._on_abandon_requested() await _frames(4) _expect_exclusive("⑥放弃→终局", "终局") - if main.ui.modals.end_overlay != null and not main.ui.modals.end_overlay.visible: + if main.ui.modals.run.end_overlay != null and not main.ui.modals.run.end_overlay.visible: _problems.append("⑥放弃→终局:终局覆盖层未开") # ---- ⑦ 终局 → 菜单(终局出口返回开始界面)---- func _step_end_back_to_menu() -> void: - if main.ui.modals.end_overlay == null or not main.ui.modals.end_overlay.visible: + if main.ui.modals.run.end_overlay == null or not main.ui.modals.run.end_overlay.visible: _problems.append("⑦终局→菜单:前置不在终局") return - main.ui.modals.end_overlay.continue_pressed.emit() + main.ui.modals.run.end_overlay.continue_pressed.emit() await _frames(3) _expect_exclusive("⑦终局→菜单", "菜单") if main.run_scope != null and main.run_scope.is_adventure: @@ -273,7 +273,7 @@ func _step_camp_esc_and_hud() -> void: await _frames() main._on_esc_settings() await _frames() - if not main.ui.modals.settings_overlay.visible: + if not main.ui.modals.menu.settings_overlay.visible: _problems.append("[esc] 浮窗设置出口断裂〔ESC批 V4〕") _push_esc() await _frames() @@ -286,7 +286,7 @@ func _step_camp_esc_and_hud() -> void: # 返回主菜单动线(契约 4——浮窗自收+营地隐+选档收+菜单显) main.ui.modals.camp.esc_overlay.return_requested.emit() await _frames() - if main.ui.modals.camp.esc_overlay.visible or main.ui.modals.camp.scene.visible or main.ui.modals.save_select_overlay.visible or not main.ui.screens.start_overlay.visible: + if main.ui.modals.camp.esc_overlay.visible or main.ui.modals.camp.scene.visible or main.ui.modals.menu.save_select_overlay.visible or not main.ui.screens.start_overlay.visible: _problems.append("[esc] 返回主菜单动线失守(浮窗自收/营地隐/选档收/菜单显)〔ESC批 V3〕〔ESC批 B3〕") # 非营地语境(boot 菜单)Esc→无响应(契约 9④ 回归面) _push_esc() diff --git a/game/probe/shot_probe.gd b/game/probe/shot_probe.gd index 90184d93..d7e92577 100644 --- a/game/probe/shot_probe.gd +++ b/game/probe/shot_probe.gd @@ -52,9 +52,9 @@ func _ready() -> void: var top_rx := -1 var top_ry := -1 for c: Node in [main.ui.screens.start_overlay, main.ui.screens.battle_root, main.ui.screens.inter_overlay, - main.ui.modals.legend_overlay, main.ui.modals.node_overlay, main.ui.modals.level_screen, main.ui.modals.curio_overlay, - main.ui.modals.end_overlay, main.ui.modals.shop_overlay, main.ui.modals.camp.scene, main.ui.modals.settings_overlay, - main.ui.modals.status_panel, main.ui.modals.career_overlay, main.ui.modals.confirm_dialog, main.ui.modals.debug_overlay]: + main.ui.modals.run.legend_overlay, main.ui.modals.run.node_overlay, main.ui.modals.run.level_screen, main.ui.modals.run.curio_overlay, + main.ui.modals.run.end_overlay, main.ui.modals.menu.shop_overlay, main.ui.modals.camp.scene, main.ui.modals.menu.settings_overlay, + main.ui.modals.run.status_panel, main.ui.modals.menu.career_overlay, main.ui.modals.menu.confirm_dialog, main.ui.modals.menu.debug_overlay]: if c == null or not (c is CanvasItem) or not (c as CanvasItem).visible: continue var rx: int = c.get_parent().get_index() @@ -89,12 +89,12 @@ func _ready() -> void: setbtn.pressed.emit() for i in 3: await get_tree().process_frame - if not main.ui.modals.settings_overlay.visible: + if not main.ui.modals.menu.settings_overlay.visible: _problems.append("设置面未打开(settingsbtn 路径)") else: - _meta["settings_title"] = ProbeUiUtil.label_rect(main.ui.modals.settings_overlay, "设置") + _meta["settings_title"] = ProbeUiUtil.label_rect(main.ui.modals.menu.settings_overlay, "设置") await _snap("settings") - var set_close: Button = ProbeUiUtil.find_button(main.ui.modals.settings_overlay, "返回", "prefix") + var set_close: Button = ProbeUiUtil.find_button(main.ui.modals.menu.settings_overlay, "返回", "prefix") if set_close == null: _problems.append("设置面返回钮缺失") else: @@ -105,19 +105,19 @@ func _ready() -> void: main._open_career() for i in 3: await get_tree().process_frame - if not main.ui.modals.career_overlay.visible or main.ui.screens.start_overlay.visible: + if not main.ui.modals.menu.career_overlay.visible or main.ui.screens.start_overlay.visible: _problems.append("生涯页未打开或开始层未隐藏(函数直调中间态路径)") else: - _meta["career_title"] = ProbeUiUtil.label_rect(main.ui.modals.career_overlay, "生 涯") + _meta["career_title"] = ProbeUiUtil.label_rect(main.ui.modals.menu.career_overlay, "生 涯") await _snap("career") - var career_close: Button = ProbeUiUtil.find_button(main.ui.modals.career_overlay, "返回", "prefix") + var career_close: Button = ProbeUiUtil.find_button(main.ui.modals.menu.career_overlay, "返回", "prefix") if career_close == null: _problems.append("生涯页返回钮缺失") else: career_close.pressed.emit() for i in 3: await get_tree().process_frame - if main.ui.modals.career_overlay.visible or not main.ui.screens.start_overlay.visible: + if main.ui.modals.menu.career_overlay.visible or not main.ui.screens.start_overlay.visible: _problems.append("生涯页关闭后页未隐藏或开始层未恢复") # ② 营地场景图(进档开层新路径——卡② mn9;截图=可点击场景图形态——卡④ 契约 9 V1 验收载体) if not ProbeUiUtil.open_camp_via_savesel(main, "smoke_shot_probe_camp"): @@ -127,7 +127,7 @@ func _ready() -> void: await _snap("camp") main.ui.modals.camp.scene.object_pressed.emit(&"campfire") # 收闭改接篝火动线(营地根态无关闭钮——卡④ T5) await get_tree().process_frame - main.ui.modals.confirm_dialog.confirmed.emit() # 二次确认离场(关层收闭——选档层随收) + main.ui.modals.menu.confirm_dialog.confirmed.emit() # 二次确认离场(关层收闭——选档层随收) await get_tree().process_frame # ③ 关卡画面(开始冒险→停点出图——入口停点语义;函数直调中间态 mn10) main._start_adventure_from_menu() @@ -135,16 +135,16 @@ func _ready() -> void: await get_tree().process_frame await _snap("level") # GP 常驻徽章断言(读数=runner.gp 现值,刷新=写路径回执后重查〔open_choices〕) - var gp_label: Label = ProbeUiUtil.find_label(main.ui.modals.level_screen, "◈ ") + var gp_label: Label = ProbeUiUtil.find_label(main.ui.modals.run.level_screen, "◈ ") if gp_label == null or not gp_label.is_visible_in_tree(): _problems.append("GP 徽章不可见(关卡画面顶栏)") - elif main.ui.modals.level_screen.get("_runner") != null: - var expect := "◈ %d" % int(main.ui.modals.level_screen.get("_runner").gp) + elif main.ui.modals.run.level_screen.get("_runner") != null: + var expect := "◈ %d" % int(main.ui.modals.run.level_screen.get("_runner").gp) if gp_label.text != expect: _problems.append("GP 徽章读数不符: %s != %s" % [gp_label.text, expect]) # ④ 战斗(节点一——真实选路提交路径 press_candidate,ui_click_probe :199 同款) - if main.ui.modals.level_screen != null and main.ui.modals.level_screen.visible: - main.ui.modals.level_screen.press_candidate(0) + if main.ui.modals.run.level_screen != null and main.ui.modals.run.level_screen.visible: + main.ui.modals.run.level_screen.press_candidate(0) for i in 10: await get_tree().process_frame await _snap("battle") @@ -167,18 +167,18 @@ func _ready() -> void: pb.pressed.emit() for i in 3: await get_tree().process_frame - if not main.ui.modals.status_panel.visible: + if not main.ui.modals.run.status_panel.visible: _problems.append("状态面板未打开(角标路径)") else: - _meta["status_title"] = ProbeUiUtil.label_rect(main.ui.modals.status_panel, "状态") + _meta["status_title"] = ProbeUiUtil.label_rect(main.ui.modals.run.status_panel, "状态") await _snap("status") - var status_close: Button = ProbeUiUtil.find_button(main.ui.modals.status_panel, "继续", "prefix") + var status_close: Button = ProbeUiUtil.find_button(main.ui.modals.run.status_panel, "继续", "prefix") if status_close == null: _problems.append("状态面板继续钮缺失") else: status_close.pressed.emit() await get_tree().process_frame - if main.ui.modals.status_panel.visible: + if main.ui.modals.run.status_panel.visible: _problems.append("状态面板关闭后未隐藏(继续钮路径)") else: _problems.append("关卡画面未出现(函数直调中间态路径)") diff --git a/game/probe/t17_defer_probe.gd b/game/probe/t17_defer_probe.gd index 53c1b4b7..086adf3c 100644 --- a/game/probe/t17_defer_probe.gd +++ b/game/probe/t17_defer_probe.gd @@ -29,14 +29,14 @@ func _ready() -> void: main._start_adventure_from_menu() # 函数直调中间态(卡② mn10——开始冒险钮已撤) for i in 60: await get_tree().process_frame - if main.ui.modals.level_screen != null and main.ui.modals.level_screen.visible: + if main.ui.modals.run.level_screen != null and main.ui.modals.run.level_screen.visible: break - if main.ui.modals.level_screen == null or not main.ui.modals.level_screen.visible: + if main.ui.modals.run.level_screen == null or not main.ui.modals.run.level_screen.visible: _fail("关卡画面未出现") return for i in 5: await get_tree().process_frame - main.ui.modals.level_screen.press_candidate(0) + main.ui.modals.run.level_screen.press_candidate(0) for i in 20: await get_tree().process_frame if main.run_scope.settlement == null: @@ -69,16 +69,16 @@ func _ready() -> void: var t_settle := Time.get_ticks_msec() var _rows_at_victory: int = main.ui.screens.row_pacer.pending_rows().size() # 时间戳①:结算入队完成帧(AP 耗尽/停手→五行段同步跑完→行全在队) # 断言①胜利同帧:终局面未开 + 挂起在案 + 队列非空(判别性——旧行为同帧开窗即红) - if main.ui.modals.end_overlay.is_open(): + if main.ui.modals.run.end_overlay.is_open(): _problems.append("胜利同帧终局面已开(演播后排窗失效——同帧 flush 旧路径回潮)") - if main.ui.modals.flow._deferred == null: + if main.ui.flow._deferred == null: _problems.append("胜利同帧挂起态缺失(_deferred_modal == null)") if main.ui.screens.row_pacer.pending_rows().is_empty(): _problems.append("胜利同帧排播队列已空(结算行未走排播链)") var _played_rows: int = _rows_at_victory # 断言②排播放完:终局面开启(20s 守卫——0.22s×行数 ×10 余量) var waited := 0.0 - while not main.ui.modals.end_overlay.is_open() and waited < 20.0: + while not main.ui.modals.run.end_overlay.is_open() and waited < 20.0: await get_tree().process_frame waited += get_process_delta_time() var t_popup := Time.get_ticks_msec() @@ -86,7 +86,7 @@ func _ready() -> void: print("[t17_defer] AP点用完: %s" % ap_clock) print("[t17_defer] 触发结算: %s(与 AP 用完同帧——核心同步,相差 0ms)" % settle_clock) print("[t17_defer] 弹窗出现: %s(距触发结算 %d ms = 逐行演播时长,行数 %d)" % [popup_clock, t_popup - t_ap_out, _played_rows]) - if not main.ui.modals.end_overlay.is_open(): + if not main.ui.modals.run.end_overlay.is_open(): _problems.append("排播放完后终局面未开(%.1fs——演播完边沿派发失守)" % waited) if _problems.is_empty(): print("[t17_defer] PASS — 胜利同帧挂起+排播完开窗 两断言全过(waited=%.1fs)" % waited) diff --git a/game/probe/ui_click_probe.gd b/game/probe/ui_click_probe.gd index 9871f175..637f418a 100644 --- a/game/probe/ui_click_probe.gd +++ b/game/probe/ui_click_probe.gd @@ -51,7 +51,7 @@ func _ready() -> void: var t_ui_ready := Time.get_ticks_msec() - t0 # 调试浮窗 boot 不开(菜单优先——入口=UI 最顶层全局浮层角标, # 任何 UI 不遮挡;浮层角标遮挡判据见设置面段) - if main.ui.modals.debug_overlay.visible: + if main.ui.modals.menu.debug_overlay.visible: _problems.append("调试浮窗 boot 自动开(菜单优先失守)") # ---- 营地购买合成点击路径(选档进档开层→购买→关闭全走真实按钮信号路径; # 隔离存档换注 main.meta 保确定性(禁碰真实档 user://save.json);陈旧档段首清理)---- @@ -204,11 +204,11 @@ func _ready() -> void: f_c2.close() # 槽2 有档异值(进档链夹具);槽1/3 空 main._open_save_select() await get_tree().process_frame - if not main.ui.modals.save_select_overlay.visible: + if not main.ui.modals.menu.save_select_overlay.visible: _problems.append("[savesel] 选档层未打开(click 段前置)") # sl12 动线序(新档链同拍采样):逐帧可见性次序——首帧选档隐藏时营地层必须已可见、开始层保持隐藏 #(禁先关后开——_close_modal 会恢复开始层=回闪一帧) - var new_btn: Button = main.ui.modals.save_select_overlay._new_btns.get(1) + var new_btn: Button = main.ui.modals.menu.save_select_overlay._new_btns.get(1) if new_btn == null: _problems.append("[savesel] 槽1 新档钮缺失(sl5/sl12 驱动中断)") else: @@ -216,7 +216,7 @@ func _ready() -> void: new_btn.pressed.emit() # 真实按钮信号路径 → _on_save_select_new → create_slot+open_camp for fi in 3: await get_tree().process_frame - samples.append([main.ui.modals.save_select_overlay.visible, main.ui.modals.camp.scene.visible, main.ui.screens.start_overlay.visible]) + samples.append([main.ui.modals.menu.save_select_overlay.visible, main.ui.modals.camp.scene.visible, main.ui.screens.start_overlay.visible]) if samples[0][0]: _problems.append("[savesel] sl12 新档后选档层仍可见(落点开层未发生)") elif not samples[0][1]: @@ -225,8 +225,8 @@ func _ready() -> void: _problems.append("[savesel] sl12 动线序违约:开始层在落点帧恢复(回闪一帧)") if main.save_manager.slots.active_slot() != 1 or not FileAccess.file_exists(csel_tpl % 1): _problems.append("[savesel] sl5 新档链失守(active=%d 应=1 且槽1 白板应落盘)" % main.save_manager.slots.active_slot()) - var occluded: Array = main.ui.modals.flow.stack._occluded.get(main.ui.modals.camp.scene, []) - if not occluded.has(main.ui.modals.save_select_overlay): + var occluded: Array = main.ui.flow.stack._occluded.get(main.ui.modals.camp.scene, []) + if not occluded.has(main.ui.modals.menu.save_select_overlay): _problems.append("[savesel] sl12 选档层被关闭而非栈记账挂起(先关后开形态——遮挡挂起中间态钉死)") if int((JSON.parse_string(FileAccess.get_file_as_string(csel_tpl % 1)) as Dictionary).get("meta", {}).get("keystone_balance", -1)) != 0: _problems.append("[savesel] sl5 新档非白板(槽1 落盘应全零)") @@ -235,7 +235,7 @@ func _ready() -> void: # sl6 进档链:槽2 进档 → 激活=2(camp 数据随激活槽) main._open_save_select() await get_tree().process_frame - var load_btn: Button = main.ui.modals.save_select_overlay._load_btns.get(2) + var load_btn: Button = main.ui.modals.menu.save_select_overlay._load_btns.get(2) if load_btn == null: _problems.append("[savesel] 槽2 进档钮缺失(sl6 驱动中断——夹具应有档)") else: @@ -243,51 +243,51 @@ func _ready() -> void: await get_tree().process_frame if main.save_manager.slots.active_slot() != 2: _problems.append("[savesel] sl6 进档链失守(active=%d 应=2——激活该槽)" % main.save_manager.slots.active_slot()) - if not main.ui.modals.camp.scene.visible or main.ui.modals.save_select_overlay.visible: + if not main.ui.modals.camp.scene.visible or main.ui.modals.menu.save_select_overlay.visible: _problems.append("[savesel] sl6 落点未开营地层或选档未隐藏") main._on_camp_closed() await get_tree().process_frame # sl7 删档二次确认双路径:一击只开窗(文件仍在)→取消只关窗(文件仍在)→再开窗确认才删(槽回空) main._open_save_select() await get_tree().process_frame - var del_btn: Button = main.ui.modals.save_select_overlay._delete_btns.get(2) + var del_btn: Button = main.ui.modals.menu.save_select_overlay._delete_btns.get(2) if del_btn == null: _problems.append("[savesel] 槽2 删档钮缺失(sl7 驱动中断)") else: del_btn.pressed.emit() await get_tree().process_frame - if not main.ui.modals.confirm_dialog.is_open(): + if not main.ui.modals.menu.confirm_dialog.is_open(): _problems.append("[savesel] sl7 一击未只开确认窗(直删形态)") elif not FileAccess.file_exists(csel_tpl % 2): _problems.append("[savesel] sl7 确认前文件已被删(一次点击即删违契)") else: - if not main.ui.modals.flow.stack._occluded.has(main.ui.modals.confirm_dialog): + if not main.ui.flow.stack._occluded.has(main.ui.modals.menu.confirm_dialog): _problems.append("[savesel] sl7 确认窗未经模态栈开(直开旁路——wr2 单口失守)") - elif not main.ui.modals.flow.locks_input(): + elif not main.ui.flow.locks_input(): _problems.append("[savesel] sl7 确认窗开时输入锁未生效(锁域失守——wr2)") - main.ui.modals.confirm_dialog.cancelled.emit() # 取消路径(真实信号——cancelled 钮同源) + main.ui.modals.menu.confirm_dialog.cancelled.emit() # 取消路径(真实信号——cancelled 钮同源) await get_tree().process_frame - if main.ui.modals.confirm_dialog.is_open() or not FileAccess.file_exists(csel_tpl % 2): + if main.ui.modals.menu.confirm_dialog.is_open() or not FileAccess.file_exists(csel_tpl % 2): _problems.append("[savesel] sl7 取消路径失守(应只关窗、文件仍在)") elif main._confirm_consumer != "": _problems.append("[savesel] sl8 取消后消费方状态量未清(分派面残留)") - if main.ui.modals.flow.stack._occluded.has(main.ui.modals.confirm_dialog): + if main.ui.flow.stack._occluded.has(main.ui.modals.menu.confirm_dialog): _problems.append("[savesel] sl7 取消后确认窗遮挡账未清(关侧未走模态栈——wr2)") del_btn.pressed.emit() # 再开窗走确认路径 await get_tree().process_frame - main.ui.modals.confirm_dialog.confirmed.emit() # 确认路径(真实信号——confirmed 钮同源) + main.ui.modals.menu.confirm_dialog.confirmed.emit() # 确认路径(真实信号——confirmed 钮同源) await get_tree().process_frame - if FileAccess.file_exists(csel_tpl % 2) or main.ui.modals.confirm_dialog.is_open(): + if FileAccess.file_exists(csel_tpl % 2) or main.ui.modals.menu.confirm_dialog.is_open(): _problems.append("[savesel] sl7 确认路径失守(应删档+关窗)") elif main.run_scope.is_adventure or main.ui.screens.start_overlay.visible: _problems.append("[savesel] sl8 删档确认误触发开局(双常驻接线形态——分派单口失守)") - elif bool(main.ui.modals.save_select_overlay._occupied_flags.get(2, true)): + elif bool(main.ui.modals.menu.save_select_overlay._occupied_flags.get(2, true)): _problems.append("[savesel] sl7 删后选档面未就地刷新(槽2 应回空态)") elif main._confirm_consumer != "": _problems.append("[savesel] sl8 确认后消费方状态量未清(分派面残留)") main._on_save_select_closed() await get_tree().process_frame - if main.ui.modals.save_select_overlay.visible or not main.ui.screens.start_overlay.visible: + if main.ui.modals.menu.save_select_overlay.visible or not main.ui.screens.start_overlay.visible: _problems.append("[savesel] 关闭后层未隐藏或开始层未恢复(模态栈单口失守)") # ---- 设置面段(隔离 store 换注禁碰真实 user://settings.cfg;真实按钮信号路径)---- @@ -297,19 +297,19 @@ func _ready() -> void: var iso_s = load("res://presentation/settings_store.gd").new("user://smoke_click_probe_settings.cfg") iso_s.load_settings() main.settings_store = iso_s - main.ui.modals.settings_overlay.retarget(iso_s) + main.ui.modals.menu.settings_overlay.retarget(iso_s) var settings_btn: Button = ProbeUiUtil.find_button(main.ui.screens.start_overlay, "设置", "prefix") if settings_btn == null: _problems.append("设置入口按钮缺失(开始层 settingsbtn)") else: settings_btn.pressed.emit() # 真实按钮信号路径 → _open_settings await get_tree().process_frame - if not main.ui.modals.settings_overlay.visible or main.ui.screens.start_overlay.visible: + if not main.ui.modals.menu.settings_overlay.visible or main.ui.screens.start_overlay.visible: _problems.append("设置面未打开(点击路径)") else: # 减动效转正双态(卡④ AC③)——M4 适配:拨杆开关(_motion_btn)语义翻转 on=完整/off=快速, # 状态字随行 _motion_st;查找由文字改成员引用(拨杆无文字) - var motion_btn: Button = main.ui.modals.settings_overlay._motion_btn + var motion_btn: Button = main.ui.modals.menu.settings_overlay._motion_btn if motion_btn == null: _problems.append("设置面减动效开关缺失") else: @@ -326,7 +326,7 @@ func _ready() -> void: if UiKit.motion_scale != 1.0: _problems.append("减动效关未恢复(motion_scale != 1.0)") # 音量实时生效(卡④ AC①):滑杆 30% → master bus db 与 store 双读 - var vol: HSlider = main.ui.modals.settings_overlay._volume + var vol: HSlider = main.ui.modals.menu.settings_overlay._volume vol.value = 30 await get_tree().process_frame var master_idx := AudioServer.get_bus_index("Master") @@ -339,22 +339,22 @@ func _ready() -> void: # 回归语义随调试浮层发布态退役,申报见迭代 38 实现报告 §8.2) if main.ui.screens.debug_badge == null or main.ui.screens.debug_badge.visible: _problems.append("设置面开中调试角标可见(发布态恒隐失守)") - var settings_close: Button = ProbeUiUtil.find_button(main.ui.modals.settings_overlay, "返回", "prefix") + var settings_close: Button = ProbeUiUtil.find_button(main.ui.modals.menu.settings_overlay, "返回", "prefix") if settings_close == null: _problems.append("设置面返回按钮缺失") else: settings_close.pressed.emit() await get_tree().process_frame - if main.ui.modals.settings_overlay.visible or not main.ui.screens.start_overlay.visible: + if main.ui.modals.menu.settings_overlay.visible or not main.ui.screens.start_overlay.visible: _problems.append("设置面关闭后未恢复开始层") # 旧入口零残留断言(卡④ AC③——测试调整板不含减动效开关;14.5 起载体=调试浮窗) - var legacy_motion = ProbeUiUtil.find_check_button(main.ui.modals.debug_overlay, "减动效") + var legacy_motion = ProbeUiUtil.find_check_button(main.ui.modals.menu.debug_overlay, "减动效") if legacy_motion != null: _problems.append("调整板减动效旧入口未退役(设置面唯一入口失守——卡④ AC③)") # D1 断言(二轮):退出游戏只在一级菜单——开始菜单有、设置面无 if ProbeUiUtil.find_button(main.ui.screens.start_overlay, "退出游戏", "prefix") == null: _problems.append("开始菜单缺退出游戏按钮(二轮 D1)") - if ProbeUiUtil.find_button(main.ui.modals.settings_overlay, "退出游戏", "prefix") != null: + if ProbeUiUtil.find_button(main.ui.modals.menu.settings_overlay, "退出游戏", "prefix") != null: _problems.append("设置面仍含退出游戏按钮(二轮 D1——只在一级菜单出现)") # ---- 生涯页段(入口点击链+关闭回返,真实按钮信号路径;本页只读零写, # 值断言归冒烟投影段与 ui_struct 结构族,此处只验开合路径与零跳转)---- @@ -362,29 +362,29 @@ func _ready() -> void: if true: main._open_career() await get_tree().process_frame - if not main.ui.modals.career_overlay.visible or main.ui.screens.start_overlay.visible: + if not main.ui.modals.menu.career_overlay.visible or main.ui.screens.start_overlay.visible: _problems.append("生涯页未打开(点击路径)") else: # 零跳转契约(提纲 P4 甲):页内除「返回」外零导航钮 for nav in ["营地", "商店", "设置", "退出", "冒险"]: - if ProbeUiUtil.find_button(main.ui.modals.career_overlay, nav, "contains") != null: + if ProbeUiUtil.find_button(main.ui.modals.menu.career_overlay, nav, "contains") != null: _problems.append("生涯页内出现导航入口「%s」(零跳转违约——生涯纯只读)" % nav) # 局内入口断言(提纲 P4 甲):生涯入口只在开始层——战斗屏/地图屏零生涯角标 if main.ui.battle.board_zone.find_child("career_badge", true, false) != null \ - or main.ui.modals.level_screen.find_child("career_badge", true, false) != null: + or main.ui.modals.run.level_screen.find_child("career_badge", true, false) != null: _problems.append("局内屏出现生涯入口件(局内不放生涯入口违约)") - var career_close: Button = ProbeUiUtil.find_button(main.ui.modals.career_overlay, "返回", "prefix") + var career_close: Button = ProbeUiUtil.find_button(main.ui.modals.menu.career_overlay, "返回", "prefix") if career_close == null: _problems.append("生涯页返回按钮缺失") else: career_close.pressed.emit() await get_tree().process_frame - if main.ui.modals.career_overlay.visible or not main.ui.screens.start_overlay.visible: + if main.ui.modals.menu.career_overlay.visible or not main.ui.screens.start_overlay.visible: _problems.append("生涯页关闭后未恢复开始层") # 换回全新零档(营地购买态不泄入后续冒险段——确定性;档案残留下次段首清理兜底) ProbeUiUtil.isolate_save(main, "smoke_click_probe_reset", true, false, true) # 零档换注(继续上局钮已随三入口化撤出——卡② mn8) - main.ui.modals.debug_overlay.set_sliders(400, 200) # 高加成保 L1 可完成——L2 岔路选路段只能经冒险推进到达(单关直入口退役) - main.ui.modals.debug_overlay.seed_edit.text = "42" # 种子钉死(种子框默认为空=非首局随机,行为走查确定性禁依赖默认分支) + main.ui.modals.menu.debug_overlay.set_sliders(400, 200) # 高加成保 L1 可完成——L2 岔路选路段只能经冒险推进到达(单关直入口退役) + main.ui.modals.menu.debug_overlay.seed_edit.text = "42" # 种子钉死(种子框默认为空=非首局随机,行为走查确定性禁依赖默认分支) var t1 := Time.get_ticks_msec() main._start_adventure_from_menu() await get_tree().process_frame @@ -396,11 +396,11 @@ func _ready() -> void: _problems.append("开场行回归失守:局外加成(pct>0)局的开场行应带「(含面板加成)」(wc3)") # 关卡开始 = 入口选路停点——press_candidate 图上命中路径选入口候选进首战 #(首格可点 AC 口径随之含入口选路一步:菜单点击 → 停点呈现 → pick → 棋盘安装) - if main.ui.modals.level_screen.visible: + if main.ui.modals.run.level_screen.visible: # 独立性断言:独立屏激活期战斗屏宿主整体隐藏 if main.ui.screens.battle_root.visible: _problems.append("独立屏激活期战斗屏宿主未隐藏(零透出契约失守)") - main.ui.modals.level_screen.press_candidate(0) + main.ui.modals.run.level_screen.press_candidate(0) await get_tree().process_frame await get_tree().process_frame var t_entry := Time.get_ticks_msec() - t1 @@ -421,21 +421,21 @@ func _ready() -> void: while not _done and action_budget > 0: action_budget -= 1 await get_tree().process_frame - if main.ui.modals.node_overlay.visible: - main.ui.modals.node_overlay.press_continue() + if main.ui.modals.run.node_overlay.visible: + main.ui.modals.run.node_overlay.press_continue() _seals_placed.clear() continue - if main.ui.modals.curio_overlay.visible: + if main.ui.modals.run.curio_overlay.visible: # 三选一停点(CurioPickOverlay——奖励节点/关卡完成兑付共用面,须与硬锁态适配, # 未适配则走到即卡死)。两态分派:result 态(兑付反馈——点继续推进)/ # 待选态(点首件+确认)。真实信号路径全线。〔无替换子态——装配不可变契约。〕 # 三选一停点输入锁定断言(停点态断言面:待选/兑付两态共用此层)。 - if not _tested_curio_head and main.run_scope.adventure != null and not main.run_scope.adventure.stop()._node_pending.is_empty() and String(main.ui.modals.curio_overlay._header).contains("关卡奖励点"): + if not _tested_curio_head and main.run_scope.adventure != null and not main.run_scope.adventure.stop()._node_pending.is_empty() and String(main.ui.modals.run.curio_overlay._header).contains("关卡奖励点"): # 停点态判据 = _node_curio_pending(奖励节点三选一停点住此——原条件查 # _curio_pending〔关卡完成三选一态〕与奖励头部互斥、恒不可命中=拦截能力未证病根, # 卡⑤ d4 转正随修;头部校验串本体零触碰) _tested_curio_head = true - var ch := String(main.ui.modals.curio_overlay._header) + var ch := String(main.ui.modals.run.curio_overlay._header) if not ch.contains("冒险牌库") or not ch.contains("属性值提升与补给") or ch.contains("同权重") or ch.contains("同上限") or ch.contains("提升池"): _problems.append("三选一头部旧话残留或新话缺失(wa4——%s)" % ch.replace(" ", "|")) @@ -444,45 +444,45 @@ func _ready() -> void: var logtext: String = main.ui.battle.log_view.parsed_text() if not logtext.contains("关卡奖励点(三选一——选毕推进)") or logtext.contains("内容待定"): _problems.append("奖励节点战报文案未换代(卡⑤ wr2——旧话残留或新话缺失)") - if not main.ui.modals.flow.locks_input(): + if not main.ui.flow.locks_input(): _problems.append("三选一停点打开时输入未锁定") - if main.ui.modals.curio_overlay._continue_btn.visible: - main.ui.modals.curio_overlay.press_continue() + if main.ui.modals.run.curio_overlay._continue_btn.visible: + main.ui.modals.run.curio_overlay.press_continue() elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._node_pending.is_empty(): - main.ui.modals.curio_overlay.press_card(0) # 奖励节点待选态:点首件+确认(真实按钮信号路径——与关卡完成停点同形) - main.ui.modals.curio_overlay.press_confirm() + main.ui.modals.run.curio_overlay.press_card(0) # 奖励节点待选态:点首件+确认(真实按钮信号路径——与关卡完成停点同形) + main.ui.modals.run.curio_overlay.press_confirm() elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._pending.is_empty(): - main.ui.modals.curio_overlay.press_card(0) - main.ui.modals.curio_overlay.press_confirm() + main.ui.modals.run.curio_overlay.press_card(0) + main.ui.modals.run.curio_overlay.press_confirm() else: - main.ui.modals.curio_overlay.press_continue() # 停点已消费的残面——继续关屏 + main.ui.modals.run.curio_overlay.press_continue() # 停点已消费的残面——继续关屏 _seals_placed.clear() continue - if main.ui.modals.shop_overlay.visible: + if main.ui.modals.menu.shop_overlay.visible: # 商店房覆盖层(摆放约束:每终点前一位恰一店,真实路径恒必经一店):零购离开(真实 leave 信号路径)放行选路。 # 分派链序:商店层=选路页上的模态覆盖(停点呈现为 # level_screen+shop 双 visible)——必须先于 level_screen 分派,否则下层候选点击 # 空转烧尽预算(实证:lv2 商店停点双 visible 循环 12000 帧 done=false)。 - main.ui.modals.shop_overlay.leave_pressed.emit() + main.ui.modals.menu.shop_overlay.leave_pressed.emit() _seals_placed.clear() continue - if main.ui.modals.level_screen.visible: + if main.ui.modals.run.level_screen.visible: # 节点图选路口(零自动推进停点)——图上命中路径交替选候选(岔路使用行为;布局决策 2 改造) # 地图屏角标一次性 case:选路前经角标开/关状态面板(层在地图屏之上开合, # 关闭后地图屏选路态零扰动——P2 甲层级语义) - if not _tested_map_status and not main.ui.modals.status_panel.visible: + if not _tested_map_status and not main.ui.modals.run.status_panel.visible: _tested_map_status = true - var mpb: Button = main.ui.modals.level_screen.status_badge + var mpb: Button = main.ui.modals.run.level_screen.status_badge if mpb == null or not mpb.visible or mpb.disabled: _problems.append("地图屏状态角标缺失/未点亮/被置灰(P1 丁)") else: mpb.pressed.emit() - if not main.ui.modals.status_panel.visible or not main.ui.modals.level_screen.visible: + if not main.ui.modals.run.status_panel.visible or not main.ui.modals.run.level_screen.visible: _problems.append("地图屏角标开状态面板失败或地图屏被误藏(P2 甲层级失守)") - var rbtn2: Button = ProbeUiUtil.find_button(main.ui.modals.status_panel, "继续", "prefix") + var rbtn2: Button = ProbeUiUtil.find_button(main.ui.modals.run.status_panel, "继续", "prefix") if rbtn2 != null: rbtn2.pressed.emit() - if main.ui.modals.status_panel.visible or not main.ui.modals.level_screen.visible: + if main.ui.modals.run.status_panel.visible or not main.ui.modals.run.level_screen.visible: _problems.append("状态面板关闭后地图屏态失守(恢复语义破)") # 地图屏加成帽换代断言(迭代36 卡A wa2——「特性」→「属性值提升」;帽 Label 常在树; # 独立旗标——_tested_map_status 在同拍前置块已置位,禁复用其门控) @@ -490,7 +490,7 @@ func _ready() -> void: _tested_map_cap = true var cap_found := false var ls_labels: Array = [] - _collect_labels_by_text(main.ui.modals.level_screen, ls_labels) + _collect_labels_by_text(main.ui.modals.run.level_screen, ls_labels) for l in ls_labels: if l.text == "属性值提升": cap_found = true @@ -502,16 +502,16 @@ func _ready() -> void: if main.run_scope.runner != null: for nd in main.run_scope.runner.nodes(): var nd_id: int = main.run_scope.runner.node_id_of(nd) - if String(nd.get("type", "")) == "reward" and main.ui.modals.level_screen._cand_ids.has(nd_id): - pick = main.ui.modals.level_screen._cand_ids.find(nd_id) + if String(nd.get("type", "")) == "reward" and main.ui.modals.run.level_screen._cand_ids.has(nd_id): + pick = main.ui.modals.run.level_screen._cand_ids.find(nd_id) break - main.ui.modals.level_screen.press_candidate(pick) + main.ui.modals.run.level_screen.press_candidate(pick) _route_clicks += 1 _seals_placed.clear() continue if main.ui.screens.inter_overlay.visible: # 大奖励屏(冒险级停点——非末关的关卡完成恰一次):首见确认推进 L2(真实按钮信号路径);再见 = L2 已关卡完成,收口 - if not main.ui.modals.flow.locks_input(): + if not main.ui.flow.locks_input(): _problems.append("大奖励屏打开时输入未锁定") _inter_confirms += 1 if _inter_confirms == 1: @@ -530,23 +530,23 @@ func _ready() -> void: break _seals_placed.clear() continue - if main.ui.modals.end_overlay.is_open() and main.run_scope.settlement_stop_timer.is_armed(): + if main.ui.modals.run.end_overlay.is_open() and main.run_scope.settlement_stop_timer.is_armed(): # 战斗胜利总结结算框(真实信号路径——每场普通胜利到达):断言已拍板面后点「下一节点」 - if not main.ui.modals.flow.locks_input(): + if not main.ui.flow.locks_input(): _problems.append("胜利结算框打开时输入未锁定") - var gp_line: String = "%s %s" % [main.ui.modals.end_overlay._gp_name.text, main.ui.modals.end_overlay._gp_val.text] # 两列账行(审计差距④)——名/值列拼回整串断言 + var gp_line: String = "%s %s" % [main.ui.modals.run.end_overlay._gp_name.text, main.ui.modals.run.end_overlay._gp_val.text] # 两列账行(审计差距④)——名/值列拼回整串断言 if not gp_line.contains("GP +%d" % main.run_scope.last_gain_amount) or not gp_line.contains("合计 %d" % int(main.run_scope.runner.gp)): _problems.append("胜利结算框 GP 增量/总量与钱包实态不符(%s|实态 +%d/%d)" % [gp_line, main.run_scope.last_gain_amount, int(main.run_scope.runner.gp)]) - if main.ui.modals.end_overlay.verdict_text() == "": + if main.ui.modals.run.end_overlay.verdict_text() == "": _problems.append("胜利结算框判定行缺失") _settlement_confirms += 1 - main.ui.modals.end_overlay._btn.pressed.emit() # 「下一节点」真实信号路径 + main.ui.modals.run.end_overlay._btn.pressed.emit() # 「下一节点」真实信号路径 await get_tree().process_frame await get_tree().process_frame - if main.run_scope.settlement_stop_timer.is_armed() or not main.ui.modals.level_screen.visible: + if main.run_scope.settlement_stop_timer.is_armed() or not main.ui.modals.run.level_screen.visible: _problems.append("胜利结算框「下一节点」未进选路页(卡⑥ AC②)") continue - if main.ui.modals.end_overlay.is_open(): + if main.ui.modals.run.end_overlay.is_open(): # 冒险终局(失败/放弃/打穿)——无单关直入口,见终局即收口(模态断言照旧) # 三选一链适配:_adventure_mode 仍真且非选路挂起=中途结算面(Boss 胜利 # 总结「继续」后挂三选一→真终局未到)——真实信号路径点继续推进。 @@ -555,13 +555,13 @@ func _ready() -> void: # (_adventure_mode)在真终局仍 true——会把真终局当中途面点掉返回键(实证)。 var _adv_running: bool = main.run_scope.adventure != null and String(main.run_scope.adventure.state) == "running" if main.run_scope.is_adventure and _adv_running: - main.ui.modals.end_overlay._btn.pressed.emit() + main.ui.modals.run.end_overlay._btn.pressed.emit() continue - if not main.ui.modals.flow.locks_input(): + if not main.ui.flow.locks_input(): _problems.append("终局 overlay 打开时输入未锁定") _done = true print("[click_probe] 冒险终局:%s · 选路口点击 %d 次" % [ - main.ui.modals.end_overlay.verdict_text().replace("\n", " "), _route_clicks]) + main.ui.modals.run.end_overlay.verdict_text().replace("\n", " "), _route_clicks]) break if main.run_scope.settlement == null or main.run_scope.runner == null: continue @@ -603,36 +603,36 @@ func _ready() -> void: # ---- 预算耗尽收口(T17 硬锁批适配):选件变强后 probe 可能活过预算不再战败——断点段前提 # (冒险终局面在案)须主动满足。收口驱动:任何停点层逐个推进(复用主循环分支语义)至 # level_screen 选路停点→真实放弃二击→终局面。战斗中则继续 autoplay 打完。 - if not _done and not main.ui.modals.end_overlay.is_open(): + if not _done and not main.ui.modals.run.end_overlay.is_open(): var close_guard := 8000 var dbg_frame := 0 # T17 演播后排窗:每场胜利 +~400 帧演播等待(旧同帧 flush 零等待)——守卫按多场战斗放大 while close_guard > 0: close_guard -= 1 await get_tree().process_frame - if main.ui.modals.end_overlay.is_open(): + if main.ui.modals.run.end_overlay.is_open(): # 中途结算面(state=running)=点继续推进;真终局(settling)=断点段前提满足,收口。 if main.run_scope.is_adventure and main.run_scope.adventure != null and String(main.run_scope.adventure.state) == "running": - main.ui.modals.end_overlay._btn.pressed.emit() + main.ui.modals.run.end_overlay._btn.pressed.emit() else: break - elif main.ui.modals.curio_overlay.visible: - if main.ui.modals.curio_overlay._continue_btn.visible: - main.ui.modals.curio_overlay.press_continue() + elif main.ui.modals.run.curio_overlay.visible: + if main.ui.modals.run.curio_overlay._continue_btn.visible: + main.ui.modals.run.curio_overlay.press_continue() elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._pending.is_empty(): - main.ui.modals.curio_overlay.press_card(0) - main.ui.modals.curio_overlay.press_confirm() + main.ui.modals.run.curio_overlay.press_card(0) + main.ui.modals.run.curio_overlay.press_confirm() else: - main.ui.modals.curio_overlay.press_continue() - elif main.ui.modals.node_overlay.visible: - main.ui.modals.node_overlay.press_continue() - elif main.ui.modals.shop_overlay.visible: - main.ui.modals.shop_overlay.leave_pressed.emit() + main.ui.modals.run.curio_overlay.press_continue() + elif main.ui.modals.run.node_overlay.visible: + main.ui.modals.run.node_overlay.press_continue() + elif main.ui.modals.menu.shop_overlay.visible: + main.ui.modals.menu.shop_overlay.leave_pressed.emit() elif main.ui.screens.inter_overlay.visible: var next_btn2: Button = ProbeUiUtil.find_button(main.ui.screens.inter_overlay, "进入下一层", "prefix") if next_btn2 != null: next_btn2.pressed.emit() - elif main.ui.modals.level_screen.visible: - var abandon_btn: Button = ProbeUiUtil.find_button(main.ui.modals.level_screen, "放弃", "prefix") + elif main.ui.modals.run.level_screen.visible: + var abandon_btn: Button = ProbeUiUtil.find_button(main.ui.modals.run.level_screen, "放弃", "prefix") if abandon_btn != null: _closed_via_abandon = true abandon_btn.pressed.emit() @@ -660,12 +660,12 @@ func _ready() -> void: str(((bpf as Dictionary).get("bonus_deck", []) as Array).size()), str((bpf as Dictionary).get("bonus_pending"))]) _problems.append("断点段:战败结算后断点未清(T3 三结局清断点失守)") - if not main.ui.modals.end_overlay.is_open(): + if not main.ui.modals.run.end_overlay.is_open(): _problems.append("断点段:战败后未出冒险终局面") - if main.ui.modals.end_overlay._btn == null: + if main.ui.modals.run.end_overlay._btn == null: _problems.append("断点段:终局面返回按钮缺失") else: - main.ui.modals.end_overlay._btn.pressed.emit() # 真实按钮信号路径(_btn=继续/返回共用句柄) + main.ui.modals.run.end_overlay._btn.pressed.emit() # 真实按钮信号路径(_btn=继续/返回共用句柄) await get_tree().process_frame if not main.ui.screens.start_overlay.visible: # 豁免:放弃路径收口时终局「返回」后仍挂三选一/关卡推进残留态(停点链), @@ -687,19 +687,19 @@ func _ready() -> void: var bal_gate: int = main._camp_data()["balance"] main.ui.modals.camp.scene.object_pressed.emit(&"gate") await get_tree().process_frame - if not main.ui.modals.confirm_dialog.is_open(): + if not main.ui.modals.menu.confirm_dialog.is_open(): _problems.append("大门无断点:确认弹窗未开(点大门应先出确认不直接进冒险)〔营地图批 V3〕") if main.run_scope.is_adventure: _problems.append("大门无断点:未确认即进冒险〔营地图批 V3〕") - if main.ui.modals.confirm_dialog._secondary_btn.visible: + if main.ui.modals.menu.confirm_dialog._secondary_btn.visible: _problems.append("大门无断点:次钮应隐藏(两出口形态)〔营地图批 V3〕") - main.ui.modals.confirm_dialog.cancelled.emit() + main.ui.modals.menu.confirm_dialog.cancelled.emit() await get_tree().process_frame if not main.ui.modals.camp.scene.visible or main.run_scope.is_adventure or main._camp_data()["balance"] != bal_gate: _problems.append("大门取消:未无变更退回营地(写口零调用/营地根 visible 失守)〔营地图批 V3〕") main.ui.modals.camp.scene.object_pressed.emit(&"gate") await get_tree().process_frame - main.ui.modals.confirm_dialog.confirmed.emit() # 「是」→开始新冒险 + main.ui.modals.menu.confirm_dialog.confirmed.emit() # 「是」→开始新冒险 await get_tree().process_frame if not main.run_scope.is_adventure or main.ui.modals.camp.scene.visible: _problems.append("大门确认:冒险未发起或营地场景屏未隐藏〔营地图批 V3〕〔营地图批 B1〕") @@ -714,9 +714,9 @@ func _ready() -> void: await get_tree().process_frame main.ui.modals.camp.scene.object_pressed.emit(&"gate") await get_tree().process_frame - if not main.ui.modals.confirm_dialog.is_open() or not main.ui.modals.confirm_dialog._secondary_btn.visible: + if not main.ui.modals.menu.confirm_dialog.is_open() or not main.ui.modals.menu.confirm_dialog._secondary_btn.visible: _problems.append("大门有断点:三出口形态失守(次钮应现形)〔营地图批 V3〕") - main.ui.modals.confirm_dialog.secondary_confirmed.emit() # 「开始新冒险」出口 + main.ui.modals.menu.confirm_dialog.secondary_confirmed.emit() # 「开始新冒险」出口 await get_tree().process_frame if not main.run_scope.is_adventure or main.ui.modals.camp.scene.visible: _problems.append("大门有断点次确认:冒险未发起或营地未隐藏〔营地图批 V3〕") @@ -736,8 +736,8 @@ func _ready() -> void: if FileAccess.file_exists(p_ol): DirAccess.remove_absolute(p_ol) ProbeUiUtil.isolate_save(main, "smoke_click_probe_openline", true, false, true) - main.ui.modals.debug_overlay.reset_sliders() # ① 白板反向前提(_start_adventure 只关窗不清值——残值跨段存活防坑) - main.ui.modals.debug_overlay.seed_edit.text = "42" + main.ui.modals.menu.debug_overlay.reset_sliders() # ① 白板反向前提(_start_adventure 只关窗不清值——残值跨段存活防坑) + main.ui.modals.menu.debug_overlay.seed_edit.text = "42" main._start_adventure_from_menu() var ol_l1_wait := 0 while ol_l1_wait < 24 and String(main.ui.battle.log_view._log.get_parsed_text()).count("· 种子 ") < 1: @@ -822,14 +822,14 @@ func _run_modal_probes_once() -> void: if not _tested_legend: _tested_legend = true main.ui.battle.board_zone.ap_bar._btn_legend.pressed.emit() - if not main.ui.modals.legend_overlay.is_open(): + if not main.ui.modals.run.legend_overlay.is_open(): _problems.append("图例打开失败") - if main.ui.modals.end_overlay.is_open() or main.ui.screens.start_overlay.visible: + if main.ui.modals.run.end_overlay.is_open() or main.ui.screens.start_overlay.visible: _problems.append("图例打开时上方遮挡层未隐藏(缺陷⑤复发)") - if not main.ui.modals.flow.locks_input(): + if not main.ui.flow.locks_input(): _problems.append("图例打开时棋盘输入未锁定") var legend_has_seal := false - for e in main.ui.modals.legend_overlay.ENTRIES: + for e in main.ui.modals.run.legend_overlay.ENTRIES: if String(e[1]) == "封印": legend_has_seal = true # 图例封印行换代断言(迭代36 卡A wa5——真实 AP 条图例钮开层路径) @@ -838,13 +838,13 @@ func _run_modal_probes_once() -> void: ", "|")) if not legend_has_seal: _problems.append("图例缺『封印』格态条目(格态图例基线)") - main.ui.modals.legend_overlay.close() - if main.ui.modals.legend_overlay.is_open() or main.ui.modals.flow.locks_input(): + main.ui.modals.run.legend_overlay.close() + if main.ui.modals.run.legend_overlay.is_open() or main.ui.flow.locks_input(): _problems.append("图例关闭后未恢复输入/可见性") # ②/②b 明细回看模态无探针(M9/P-C7——冒险中回看归「生涯」,06 登记) # ⑤ 状态面板(P1 丁角标真实信号路径 + P2 甲模态语义 + P1 守卫 + R3 锁恢复): # 角标打开 → 只读层可见+输入锁定+余额徽非空 → 「继续」关闭恢复;图例开着 → 角标置灰且点击被守卫兜住 - if not _tested_status and not main.ui.modals.legend_overlay.is_open() and not main.ui.modals.status_panel.visible: + if not _tested_status and not main.ui.modals.run.legend_overlay.is_open() and not main.ui.modals.run.status_panel.visible: _tested_status = true var pbtn: Button = main.ui.battle.board_zone.status_badge if pbtn == null or not pbtn.visible or pbtn.disabled: @@ -852,17 +852,17 @@ func _run_modal_probes_once() -> void: % [str(pbtn != null and pbtn.visible), str(pbtn != null and pbtn.disabled)]) else: pbtn.pressed.emit() # 真实按钮信号路径 → 组合根 _on_status_badge_pressed - if not main.ui.modals.status_panel.visible: + if not main.ui.modals.run.status_panel.visible: _problems.append("状态面板未随角标打开(组合根接线失守)") - if not main.ui.modals.flow.locks_input(): + if not main.ui.flow.locks_input(): _problems.append("状态面板打开时输入未锁定(R3)") - var bal_txt: String = main.ui.modals.status_panel._balance_badge.value_label.text + var bal_txt: String = main.ui.modals.run.status_panel._balance_badge.value_label.text if not bal_txt.begins_with("钥石 "): _problems.append("状态面板余额徽投影缺失(%s)" % bal_txt) # ---- 本局信息面板换代断言(迭代36 卡③——真实入口三断言)---- # ① 面板子树零旧段文案(跨关概览退役零残留) var sv_labels: Array = [] - _collect_labels_by_text(main.ui.modals.status_panel, sv_labels) + _collect_labels_by_text(main.ui.modals.run.status_panel, sv_labels) for l in sv_labels: if String(l.text).contains("道具工坊") or String(l.text).contains("天赋树"): _problems.append("状态面板子树残留旧段文案「%s」(sv6①——零旧段失守)" % String(l.text)) @@ -874,8 +874,8 @@ func _run_modal_probes_once() -> void: var attr_expect := "生命 %d/%d · 攻击 %d · 护甲 AC %d · GP %d" % [ int(rnr.hero_hp), rnr.effective_max_hp(), rnr.effective_atk(), rnr.effective_ac(), int(rnr.gp)] - if String(main.ui.modals.status_panel._attrs_label.text) != attr_expect: - _problems.append("状态面板属性行投影失真(sv6②——期望 %s 实测 %s)" % [attr_expect, main.ui.modals.status_panel._attrs_label.text]) + if String(main.ui.modals.run.status_panel._attrs_label.text) != attr_expect: + _problems.append("状态面板属性行投影失真(sv6②——期望 %s 实测 %s)" % [attr_expect, main.ui.modals.run.status_panel._attrs_label.text]) # ③ _status_data() 直调对拍(sv7——键集恰八键+curios/feats 独立 oracle) var sd: Dictionary = main._status_data() var keys := ["balance", "hero_hp", "max_hp", "atk", "ac", "gp", "curios", "feats"] @@ -899,22 +899,22 @@ func _run_modal_probes_once() -> void: var feats_expect: Array = load("res://presentation/hud_presenter.gd").feat_holdings_lines(main.db, rnr.feats_held()) if str((sd as Dictionary).get("feats", [])) != str(feats_expect): _problems.append("_status_data().feats 对拍 feat_holdings_lines 失守(sv7)") - var resume_btn: Button = ProbeUiUtil.find_button(main.ui.modals.status_panel, "继续", "prefix") + var resume_btn: Button = ProbeUiUtil.find_button(main.ui.modals.run.status_panel, "继续", "prefix") if resume_btn == null: _problems.append("状态面板关闭钮缺失(「继续」)") else: resume_btn.pressed.emit() - if main.ui.modals.status_panel.visible or main.ui.modals.flow.locks_input(): + if main.ui.modals.run.status_panel.visible or main.ui.flow.locks_input(): _problems.append("状态面板关闭后未恢复(visible=%s locked=%s——R3 残留)" - % [str(main.ui.modals.status_panel.visible), str(main.ui.modals.flow.locks_input())]) + % [str(main.ui.modals.run.status_panel.visible), str(main.ui.flow.locks_input())]) # P1 守卫:图例开着 → 两处入口角标置灰;置灰态点击被处理器兜住(探针 emit 绕过 disabled) main.ui.battle.board_zone.ap_bar._btn_legend.pressed.emit() if not main.ui.battle.board_zone.status_badge.disabled: _problems.append("图例开着时战斗屏状态角标未置灰(P1 守卫失守)") main.ui.battle.board_zone.status_badge.pressed.emit() - if main.ui.modals.status_panel.visible: + if main.ui.modals.run.status_panel.visible: _problems.append("置灰态仍可开状态面板(守卫兜底失守)") - main.ui.modals.legend_overlay.close() + main.ui.modals.run.legend_overlay.close() # ③ tooltip(状态效果槽悬停——来源+语义+剩余):有槽可见时触发 if not _tested_tooltip: var row = main.ui.battle.stage.hero_standee.slot_row # 状态 tooltip 载体(B 区站桩) @@ -1045,7 +1045,7 @@ func _locktest_tick() -> void: if settlement == null or settlement.minesweeper.current_board() == null: return if _locktest_state == 0: - if main.ui.screens.row_pacer.pending_rows().is_empty() or main.ui.modals.flow.locks_input(): + if main.ui.screens.row_pacer.pending_rows().is_empty() or main.ui.flow.locks_input(): return if String(settlement.battle_result) != "" or String(settlement.action_economy.phase) != "Active": return @@ -1067,7 +1067,7 @@ func _locktest_tick() -> void: return _locktest_state = 1 elif _locktest_state == 1: - if main.ui.modals.curio_overlay.visible or main.ui.screens.inter_overlay.visible or main.ui.modals.end_overlay.is_open() \ + if main.ui.modals.run.curio_overlay.visible or main.ui.screens.inter_overlay.visible or main.ui.modals.run.end_overlay.is_open() \ or String(settlement.battle_result) != "": _locktest_state = 2 # 前提消失守卫:终结演播竞态——静默弃测 return diff --git a/game/probe/ui_input_probe.gd b/game/probe/ui_input_probe.gd index 9e2285b0..0909c65c 100644 --- a/game/probe/ui_input_probe.gd +++ b/game/probe/ui_input_probe.gd @@ -31,15 +31,15 @@ func _ready() -> void: # 同 ui_click_probe 隔离纪律。首次冒险布尔位置位=对齐真实档游玩史口径,模板抽签路径不变; # isolate_save 先删后建——上次运行的残留断点会触发互斥确认早退) ProbeUiUtil.isolate_save(main, "smoke_input_probe_save", true, false, true) - main.ui.modals.debug_overlay.seed_edit.text = "42" # 种子钉死(种子框默认为空=非首局随机,探针确定性禁依赖默认分支) + main.ui.modals.menu.debug_overlay.seed_edit.text = "42" # 种子钉死(种子框默认为空=非首局随机,探针确定性禁依赖默认分支) main._start_adventure_from_menu() await get_tree().process_frame # ---- ⑫ 图上命中真实输入(回归闸):入口停点候选节点中心 # push_input 真实鼠标路径 motion+click → 悬停预承诺置位 → route_chosen 到达组合根。 # 由来=真机 bug:深层节点(L1 Boss 内容 x=802)在固定画布命中区外,press_candidate 直调 # 命中函数探针全绿、真鼠标点不中——本 case 封住输入管线,⑬封住几何拟合。 - if main.ui.modals.level_screen.visible: - var ro = main.ui.modals.level_screen + if main.ui.modals.run.level_screen.visible: + var ro = main.ui.modals.run.level_screen var cands: Array = main.run_scope.runner.next_candidate_ids() # ---- ⑱ 信息迷雾呈现接线(回归闸):真机 display_probe 读实际绘制记录(雾下节点类型未画、 # Boss 描边画了、候选类型画了)+点击雾下非候选节点零提交(雾不扩大可点域——候选集即合法集)---- @@ -90,9 +90,9 @@ func _ready() -> void: await get_tree().process_frame await get_tree().process_frame ro.route_chosen.disconnect(on_route) - if not submitted.is_empty() or not main.ui.modals.level_screen.visible or not main.run_scope.runner.awaiting_choice(): + if not submitted.is_empty() or not main.ui.modals.run.level_screen.visible or not main.run_scope.runner.awaiting_choice(): _pre_fail("case⑱ 雾下节点点击产生提交(提交=%s visible=%s 选路态=%s——雾不扩大可点域)〔fv9〕" - % [str(submitted), str(main.ui.modals.level_screen.visible), str(main.run_scope.runner.awaiting_choice())]) + % [str(submitted), str(main.ui.modals.run.level_screen.visible), str(main.run_scope.runner.awaiting_choice())]) return var nds: Array = main.run_scope.runner.nodes() var tgt := {} @@ -116,13 +116,13 @@ func _ready() -> void: main.get_viewport().push_input(evc, true) await get_tree().process_frame await get_tree().process_frame - if main.ui.modals.level_screen.visible or main.run_scope.runner.awaiting_choice(): - _pre_fail("图上真实点击未提交选路(overlay 可见=%s 选路态=%s)" % [str(main.ui.modals.level_screen.visible), str(main.run_scope.runner.awaiting_choice())]) + if main.ui.modals.run.level_screen.visible or main.run_scope.runner.awaiting_choice(): + _pre_fail("图上真实点击未提交选路(overlay 可见=%s 选路态=%s)" % [str(main.ui.modals.run.level_screen.visible), str(main.run_scope.runner.awaiting_choice())]) for i in range(3): await get_tree().process_frame # ---- ⑬ 画布随图拟合(同 bug 的几何面回归):复刻分层公式独立推导最深节点内容 x, # 断言画布 min 尺寸覆盖之(open_choices 每 stop 拟合——L1 单链 Boss x=70+6*122=802 曾越界) - var ro2 = main.ui.modals.level_screen + var ro2 = main.ui.modals.run.level_screen var nds2: Array = main.run_scope.runner.nodes() var layer2 := {} var inbound2 := {} @@ -522,24 +522,24 @@ func _ready() -> void: _push_click(bpos, MOUSE_BUTTON_LEFT) _push_release(bpos, MOUSE_BUTTON_LEFT) # Button pressed 信号走 release 沿——按压+释放成对 await get_tree().process_frame - if not main.ui.modals.status_panel.visible or not main.ui.modals.flow.locks_input(): + if not main.ui.modals.run.status_panel.visible or not main.ui.flow.locks_input(): _problems.append("case⑯ 角标真点击未开状态面板(visible=%s locked=%s——入口可达性失守)" - % [str(main.ui.modals.status_panel.visible), str(main.ui.modals.flow.locks_input())]) + % [str(main.ui.modals.run.status_panel.visible), str(main.ui.flow.locks_input())]) else: _push_click(_pos(bv, status_dig), MOUSE_BUTTON_LEFT) await get_tree().process_frame await get_tree().process_frame if _has_action(status_dig, "dig") or ms.is_revealed(status_dig): _problems.append("case⑯ 状态面板开启时挖掘穿透(R3 输入锁破——目标=%s)" % status_dig) - var resume_btn: Button = ProbeUiUtil.find_button(main.ui.modals.status_panel, "继续", "prefix") + var resume_btn: Button = ProbeUiUtil.find_button(main.ui.modals.run.status_panel, "继续", "prefix") if resume_btn == null: _problems.append("case⑯ 状态面板关闭钮缺失") else: resume_btn.pressed.emit() await get_tree().process_frame - if main.ui.modals.status_panel.visible or main.ui.modals.flow.locks_input(): + if main.ui.modals.run.status_panel.visible or main.ui.flow.locks_input(): _problems.append("case⑯ 关闭后未恢复(visible=%s locked=%s——R3 残留)" - % [str(main.ui.modals.status_panel.visible), str(main.ui.modals.flow.locks_input())]) + % [str(main.ui.modals.run.status_panel.visible), str(main.ui.flow.locks_input())]) _push_click(_pos(bv, status_dig), MOUSE_BUTTON_LEFT) await get_tree().process_frame await get_tree().process_frame diff --git a/game/probe/ui_probe.gd b/game/probe/ui_probe.gd index 640d3a81..2e6295ca 100644 --- a/game/probe/ui_probe.gd +++ b/game/probe/ui_probe.gd @@ -16,7 +16,7 @@ func _ready() -> void: await get_tree().process_frame var problems: Array = [] # ---- 放弃入口(台账 119):默认隐藏(非冒险模式无冒险可放弃——开始层/调试两层)---- - if main.ui.modals.level_screen.abandon_btn == null or main.ui.modals.level_screen.abandon_btn.visible: + if main.ui.modals.run.level_screen.abandon_btn == null or main.ui.modals.run.level_screen.abandon_btn.visible: print("[ui_probe] FAIL — 放弃按钮默认应隐藏(非冒险模式关)") get_tree().quit(1) return @@ -65,14 +65,14 @@ func _ready() -> void: problems.append("营地关闭后未恢复开始层(开合往返失守)〔句柄子容器批 V1〕") # 换回全新零档(营地段购买态不泄入后续冒险段——确定性;先删后建防陈旧档泄入) ProbeUiUtil.isolate_save(main, "smoke_ui_probe_camp", true, false, true) - main.ui.modals.debug_overlay.seed_edit.text = "42" # 种子钉死(种子框默认为空=非首局随机;本探针 start_single 第五路同吃此框,确定性禁依赖默认分支) + main.ui.modals.menu.debug_overlay.seed_edit.text = "42" # 种子钉死(种子框默认为空=非首局随机;本探针 start_single 第五路同吃此框,确定性禁依赖默认分支) main._start_adventure_from_menu() await get_tree().process_frame await get_tree().process_frame # ---- 入口选路停点(关卡开始即出节点图——level_started 后、首战前弹选路层; # press_candidate 图上命中路径 pick 入口单候选进首战)---- # ---- 独立性断言(独立屏激活期战斗屏宿主整体隐藏,零透出)---- - if not main.ui.modals.level_screen.visible: + if not main.ui.modals.run.level_screen.visible: problems.append("关卡开始未弹入口选路停点(level_screen 应可见)") elif main.ui.screens.battle_root.visible: problems.append("独立屏激活期战斗屏宿主未隐藏(零透出契约失守)") @@ -80,16 +80,16 @@ func _ready() -> void: or main.run_scope.runner.next_candidate_ids().size() != 1: problems.append("入口停点状态失守(选路态 + 入口单候选)") else: - main.ui.modals.level_screen.press_candidate(0) + main.ui.modals.run.level_screen.press_candidate(0) await get_tree().process_frame await get_tree().process_frame - if not main.ui.screens.battle_root.visible or main.ui.modals.level_screen.visible: + if not main.ui.screens.battle_root.visible or main.ui.modals.run.level_screen.visible: problems.append("选路提交战斗节点后未切回战斗屏(屏级互斥恢复失守)") # ---- 放弃入口·冒险模式可见 + 战斗中按下被拒(真实按钮路径——入口挂选路停点,战斗中拒绝为防御分支)---- - if main.ui.modals.level_screen.abandon_btn == null or not main.ui.modals.level_screen.abandon_btn.visible: + if main.ui.modals.run.level_screen.abandon_btn == null or not main.ui.modals.run.level_screen.abandon_btn.visible: problems.append("冒险模式放弃按钮未开放(选路停点入口在案)") else: - main.ui.modals.level_screen.abandon_btn.pressed.emit() + main.ui.modals.run.level_screen.abandon_btn.pressed.emit() await get_tree().process_frame if main.run_scope.adventure == null or String(main.run_scope.adventure.state) != "running": problems.append("首战中放弃未被拒(战斗域单事务在飞——state 应保持 running)") @@ -233,12 +233,12 @@ func _ready() -> void: main._on_adventure_finished({"result": "completed", "cleared": true, "levels_cleared": 3, "stopped_at": 0, "keystone_total": 30, "master_seed": 42, "gp_final": 57}) main.meta.save = meta_saved - if not main.ui.modals.end_overlay.is_open(): + if not main.ui.modals.run.end_overlay.is_open(): problems.append("冒险终局面板未开(合成摘要路径)") - elif not main.ui.modals.end_overlay._gp_row.visible or not main.ui.modals.end_overlay._gp_val.text.contains("57"): - problems.append("终局面板 GP 行缺失或取数不对(%s)" % main.ui.modals.end_overlay._gp_val.text) - main.ui.modals.end_overlay.close() - main.ui.modals.flow.close(main.ui.modals.end_overlay) + elif not main.ui.modals.run.end_overlay._gp_row.visible or not main.ui.modals.run.end_overlay._gp_val.text.contains("57"): + problems.append("终局面板 GP 行缺失或取数不对(%s)" % main.ui.modals.run.end_overlay._gp_val.text) + main.ui.modals.run.end_overlay.close() + main.ui.flow.close(main.ui.modals.run.end_overlay) if problems.is_empty(): print("[ui_probe] PASS — cells=%d board=%dx%d hp='%s' enemies=%d ap_dots=%d" % [ main.ui.battle.board_zone.board_view.cells_probe().size(), main.ui.battle.board_zone.board_view.board().width, main.ui.battle.board_zone.board_view.board().height, diff --git a/game/probe/ui_struct_probe.gd b/game/probe/ui_struct_probe.gd index aa2e73e4..0a77e69c 100644 --- a/game/probe/ui_struct_probe.gd +++ b/game/probe/ui_struct_probe.gd @@ -34,7 +34,7 @@ func _ready() -> void: main._open_settings() _settle() - await _audit("settings", [main.ui.modals.settings_overlay]) + await _audit("settings", [main.ui.modals.menu.settings_overlay]) main._on_settings_closed() _settle() @@ -94,22 +94,22 @@ func _ready() -> void: _settle() main.ui.modals.camp.scene.object_pressed.emit(&"notebook") _settle() - if not main.ui.modals.career_overlay.visible or main.ui.screens.start_overlay.visible: + if not main.ui.modals.menu.career_overlay.visible or main.ui.screens.start_overlay.visible: problems.append("[career] 生涯页未打开或开始层未隐藏(笔记本改挂路径断裂)〔营地图批 V5〕") - await _audit("career", [main.ui.modals.career_overlay]) + await _audit("career", [main.ui.modals.menu.career_overlay]) _career_readonly_contract(main) _career_zero_motion_contract(main) _career_nojump_contract(main) main._on_career_closed() _settle() - if main.ui.modals.career_overlay.visible or main.ui.screens.start_overlay.visible: + if main.ui.modals.menu.career_overlay.visible or main.ui.screens.start_overlay.visible: problems.append("[career] 关闭后页未隐藏或开始层回弹(营地语境分支失守)〔营地图批 V5〕") # ---- Esc 浮窗结构段(卡⑤——三钮在位且仅三+零动效契约+登记序在册+debug_badge 不波及)---- main._camp_pres.open_camp() _settle() main.ui.modals.camp.scene.object_pressed.emit(&"gate") _settle() - main.ui.modals.confirm_dialog.cancelled.emit() # 关大门弹窗(清场——浮窗段前提) + main.ui.modals.menu.confirm_dialog.cancelled.emit() # 关大门弹窗(清场——浮窗段前提) _settle() var push_esc := func() -> void: var ev := InputEventKey.new() @@ -175,37 +175,37 @@ func _ready() -> void: problems.append("[savesel] 开层前激活态非零(sl9 重启链——boot/注入面后应无激活)") main._open_save_select() _settle() - if not main.ui.modals.save_select_overlay.visible or main.ui.screens.start_overlay.visible: + if not main.ui.modals.menu.save_select_overlay.visible or main.ui.screens.start_overlay.visible: problems.append("[savesel] 选档层未打开或开始层未隐藏(开层函数断裂)") - await _audit("savesel", [main.ui.modals.save_select_overlay]) + await _audit("savesel", [main.ui.modals.menu.save_select_overlay]) _save_select_contract(main, ssel_tpl) main._on_save_select_closed() _settle() - if main.ui.modals.save_select_overlay.visible or not main.ui.screens.start_overlay.visible: + if main.ui.modals.menu.save_select_overlay.visible or not main.ui.screens.start_overlay.visible: problems.append("[savesel] 关闭后层未隐藏或开始层未恢复(模态栈单口失守)") - main.ui.modals.confirm_dialog.open("结构探针采样", "确认") + main.ui.modals.menu.confirm_dialog.open("结构探针采样", "确认") _settle() - await _audit("confirm", [main.ui.modals.confirm_dialog]) - main.ui.modals.confirm_dialog.close() + await _audit("confirm", [main.ui.modals.menu.confirm_dialog]) + main.ui.modals.menu.confirm_dialog.close() _settle() main._show_debug_overlay() _settle() - await _audit("debug", [main.ui.modals.debug_overlay]) + await _audit("debug", [main.ui.modals.menu.debug_overlay]) main._close_debug_overlay() _settle() # ---- 冒险簇(真流推进:开冒险→地图选路态→选战斗节点→战斗 HUD)---- ProbeUiUtil.isolate_save(main, "smoke_ui_struct_probe_adv", true, false, true) - main.ui.modals.debug_overlay.seed_edit.text = "42" # 种子钉死(种子框默认为空=非首局随机,结构采样确定性禁依赖默认分支) + main.ui.modals.menu.debug_overlay.seed_edit.text = "42" # 种子钉死(种子框默认为空=非首局随机,结构采样确定性禁依赖默认分支) main._start_adventure_from_menu() _settle(4) - await _audit("level_map", [main.ui.modals.level_screen]) + await _audit("level_map", [main.ui.modals.run.level_screen]) var battle_id := _pick_battle_node(main) if battle_id >= 0: - main.ui.modals.level_screen.route_chosen.emit(battle_id) + main.ui.modals.run.level_screen.route_chosen.emit(battle_id) _settle(6) _audit("battle_hud", [main.ui.battle.board_zone, main.ui.battle.info_panel, main.ui.battle.stage]) _standeefit_contract(main) @@ -215,12 +215,12 @@ func _ready() -> void: # 一律红,须先入册审查);白名单={视图自身 _emit_closed, 组合根 _on_status_closed}。 main._open_status_panel() _settle() - await _audit("status", [main.ui.modals.status_panel]) + await _audit("status", [main.ui.modals.run.status_panel]) _status_readonly_contract(main) _status_zero_motion_contract(main) main._on_status_closed() _settle() - if main.ui.modals.status_panel.visible: + if main.ui.modals.run.status_panel.visible: problems.append("[status] 关闭后层未隐藏(模态栈单口失守)") # ---- 图例层(旧模态、首过结构族):ap_bar legend_toggled @@ -228,61 +228,61 @@ func _ready() -> void: # closed 信号真路径关(close()→组合根 _on_modal_closed)。 main.ui.battle.board_zone.legend_toggled.emit() _settle() - if not main.ui.modals.legend_overlay.visible: + if not main.ui.modals.run.legend_overlay.visible: problems.append("[legend] 图例层未打开(legend_toggled 路径中断)") - await _audit("legend", [main.ui.modals.legend_overlay]) - main.ui.modals.legend_overlay.close() + await _audit("legend", [main.ui.modals.run.legend_overlay]) + main.ui.modals.run.legend_overlay.close() _settle() - if main.ui.modals.legend_overlay.visible: + if main.ui.modals.run.legend_overlay.visible: problems.append("[legend] 关闭后层未隐藏(模态栈单口失守)") else: - problems.append("[battle_hud] 候选中无战斗节点(驱动中断——候选=%s)" % str(main.ui.modals.level_screen._cand_ids)) + problems.append("[battle_hud] 候选中无战斗节点(驱动中断——候选=%s)" % str(main.ui.modals.run.level_screen._cand_ids)) # ---- 兑付屏(真实 open 面 + 合成最小数据——结构断言只看矩形/宽度/可见性)---- - main.ui.modals.end_overlay.open_victory("结构探针·胜利演算", "返回", Rect2(200, 200, 400, 300), {"gp_summary": "GP 0"}) + main.ui.modals.run.end_overlay.open_victory("结构探针·胜利演算", "返回", Rect2(200, 200, 400, 300), {"gp_summary": "GP 0"}) _settle() - await _audit("battle_end", [main.ui.modals.end_overlay]) - main.ui.modals.end_overlay.close() + await _audit("battle_end", [main.ui.modals.run.end_overlay]) + main.ui.modals.run.end_overlay.close() _settle() # ---- 节点结算层(旧模态、首过结构族):rest 兑付真路径开 #(组合根 _open_modal 登记册+show_rest 合成喂入)→结构断言族→continue_pressed 信号真路径关 #(press_continue→组合根 _on_node_continue→_close_modal)。 - main.ui.modals.flow.open(main.ui.modals.node_overlay) - main.ui.modals.node_overlay.show_rest(3, 15, 20) + main.ui.flow.open(main.ui.modals.run.node_overlay) + main.ui.modals.run.node_overlay.show_rest(3, 15, 20) _settle() - if not main.ui.modals.node_overlay.visible: + if not main.ui.modals.run.node_overlay.visible: problems.append("[node] 节点结算层未打开(rest 兑付路径中断)") - await _audit("node_overlay", [main.ui.modals.node_overlay]) - main.ui.modals.node_overlay.press_continue() + await _audit("node_overlay", [main.ui.modals.run.node_overlay]) + main.ui.modals.run.node_overlay.press_continue() _settle() - if main.ui.modals.node_overlay.visible: + if main.ui.modals.run.node_overlay.visible: problems.append("[node] 关闭后层未隐藏(continue_pressed 路径中断)") - main.ui.modals.curio_overlay.open_pick("结构探针·三选一", [ + main.ui.modals.run.curio_overlay.open_pick("结构探针·三选一", [ {"id": "probe_heal", "text": "回血\n恢复 3 点"}, {"id": "probe_magic", "text": "爆裂轰击\n下一击 +5"}, {"id": "probe_pierce", "text": "穿甲\n无视护甲"}, ]) _settle() - await _audit("curio_pick", [main.ui.modals.curio_overlay]) - main.ui.modals.curio_overlay.visible = false + await _audit("curio_pick", [main.ui.modals.run.curio_overlay]) + main.ui.modals.run.curio_overlay.visible = false _settle() - main.ui.modals.shop_overlay.open_shop([ + main.ui.modals.menu.shop_overlay.open_shop([ {"product_id": "probe_p1", "display_name": "探针商品甲", "attr": "atk", "flat_value": 1, "price": 10, "purchased": 0}, {"product_id": "probe_p2", "display_name": "探针商品乙", "attr": "hp", "flat_value": 2, "price": 20, "purchased": 0}, ], 100) _settle() - await _audit("shop", [main.ui.modals.shop_overlay]) + await _audit("shop", [main.ui.modals.menu.shop_overlay]) # ---- 术语传导:货架卡与三选一卡片的玩家面文案(物品名=专长获取、分类=专长凭证) - main.ui.modals.shop_overlay.open_shelf([ + main.ui.modals.menu.shop_overlay.open_shelf([ {"kind_id": "feat_ticket", "display_name": "专长获取", "value": 0, "price": 5}, {"kind_id": "heal", "display_name": "应急回复", "value": 6, "price": 5}, ], 20) _settle() - var ticket_row: Dictionary = main.ui.modals.shop_overlay._shelf_rows[0] + var ticket_row: Dictionary = main.ui.modals.menu.shop_overlay._shelf_rows[0] if String(main.db.run.shop.pool_row("feat_ticket").get("display_name", "")) != "专长获取": problems.append("[shop_term] shop.tres 专长凭证件名未换代(现=%s)" % String(main.db.run.shop.pool_row("feat_ticket").get("display_name", ""))) if ticket_row.card.name_l.text != "专长获取": @@ -293,7 +293,7 @@ func _ready() -> void: problems.append("[shop_term] 专长凭证件描述未用规范流程句(现=%s)" % ticket_row.card.desc_l.text) if ticket_row.card.foot_l.text != "专长凭证": problems.append("[shop_term] 专长凭证件分类标注未用「专长凭证」(现=%s)" % ticket_row.card.foot_l.text) - var heal_row: Dictionary = main.ui.modals.shop_overlay._shelf_rows[1] + var heal_row: Dictionary = main.ui.modals.menu.shop_overlay._shelf_rows[1] if heal_row.card.desc_l.text != "回复 +6(本冒险)": problems.append("[shop_term] 回复件描述漏了 heal 词表(现=%s)" % heal_row.card.desc_l.text) if heal_row.card.foot_l.text != "回复": @@ -305,7 +305,7 @@ func _ready() -> void: problems.append("[curio_term] 专长凭证三选一卡未用物品名/规范流程句(现=%s)" % ticket_text) if ticket_text.contains("门票") or ticket_text.contains("T5") or ticket_text.contains("持有集") or ticket_text.contains("奇物三选一"): problems.append("[curio_term] 专长凭证三选一卡残留退役词/内部任务号(现=%s)" % ticket_text) - main.ui.modals.shop_overlay.visible = false + main.ui.modals.menu.shop_overlay.visible = false _settle() # ---- ⑦resume 专项:续玩停点恢复(main.gd:717 报错回归闸)---- @@ -316,11 +316,11 @@ func _ready() -> void: main.run_scope.runner = null # 全新启动等价态(真机=菜单直进续玩,无前置冒险——runner 从未赋值;探针此前推流会残留旧 runner) main._resume_from_menu() _settle(8) - if not main.ui.modals.end_overlay.visible: + if not main.ui.modals.run.end_overlay.visible: problems.append("[resume] 续玩恢复后胜利总结面未开(三选一停点分支中断)") else: var found := {"ok": false} - _find_label_text(main.ui.modals.end_overlay, "合计 24", found) + _find_label_text(main.ui.modals.run.end_overlay, "合计 24", found) if not found.ok: problems.append("[resume] 胜利总结面 GP 行未见快照合计「合计 24」(runner 缺位守卫缺位——717 回归)") var s: String = main._gp_summary_text({"gp_dropped": 0}) @@ -362,7 +362,7 @@ func _find_label_text(node: Node, needle: String, found: Dictionary) -> void: func _pick_battle_node(main: Node) -> int: # 入口/兑付停点走 level_screen.open_choices 直传(不写 _pending_candidates)—— # 候选从视图态取:_cand_ids × runner.nodes() 的 type(编号解析=node_id_of 单一出口) - var ls: Node = main.ui.modals.level_screen + var ls: Node = main.ui.modals.run.level_screen if ls._map._runner == null: return -1 for nd in ls._map._runner.nodes(): @@ -388,7 +388,7 @@ const _PAUSE_READONLY_SIGNALS: Array[String] = [ func _status_readonly_contract(main: Node) -> void: - var overlay: Control = main.ui.modals.status_panel + var overlay: Control = main.ui.modals.run.status_panel var stack: Array = [overlay] var conn_count := 0 while not stack.is_empty(): @@ -414,7 +414,7 @@ func _status_readonly_contract(main: Node) -> void: # ⑨status 零动效契约(R5/P4 甲——零动效直开直关路径存在断言): # 子树零 AnimationPlayer + 视图源码零 tween/动效包装调用(源码 token 扫描=ui_contrast 静态段同手法) func _status_zero_motion_contract(main: Node) -> void: - var overlay: Control = main.ui.modals.status_panel + var overlay: Control = main.ui.modals.run.status_panel var stack: Array = [overlay] while not stack.is_empty(): var n: Node = stack.pop_back() @@ -434,7 +434,7 @@ func _status_zero_motion_contract(main: Node) -> void: # 清单外零连接。白名单={视图自身 _emit_closed〔返回钮〕, 组合根 _on_career_closed};页内任何 # 购买/导航/写入方向的新连接一律红,须先入册审查。 func _career_readonly_contract(main: Node) -> void: - var overlay: Control = main.ui.modals.career_overlay + var overlay: Control = main.ui.modals.menu.career_overlay var stack: Array = [overlay] var conn_count := 0 while not stack.is_empty(): @@ -459,7 +459,7 @@ func _career_readonly_contract(main: Node) -> void: # ⑪career 零动效契约(状态面板 ⑨ 同形制——P4 甲直开直关) func _career_zero_motion_contract(main: Node) -> void: - var overlay: Control = main.ui.modals.career_overlay + var overlay: Control = main.ui.modals.menu.career_overlay var stack: Array = [overlay] while not stack.is_empty(): var n: Node = stack.pop_back() @@ -480,7 +480,7 @@ func _career_zero_motion_contract(main: Node) -> void: # _career_registry_contract 的断言(career=状态面板后/确认弹窗前、树序同步)由本全量合同覆盖) # ⑭选档界面契约(卡③ sl1/sl2/sl3/sl4/sl9——呈现面全部来自喂入数据,视图零自判) func _save_select_contract(main: Node, tpl: String) -> void: - var sso: Node = main.ui.modals.save_select_overlay + var sso: Node = main.ui.modals.menu.save_select_overlay # sl1 三槽呈现:槽卡计数==3+零滚动容器(禁滚动长列表形态——契约 1) var rows: VBoxContainer = sso._rows_box var live_rows := 0 @@ -559,7 +559,7 @@ func _collect_labels(node: Node, out: Array[String]) -> void: func _modal_registry_contract(main: Node) -> void: - var list: Array = main.ui.modals.flow.stack._list + var list: Array = main.ui.flow.stack._list # ① 登记册序==合同模态序(逐位点名——合同是登记序唯一事实源) var contract_modals: Array = [] for entry: Dictionary in main.ui.LAYER_CONTRACT: @@ -592,23 +592,42 @@ func _modal_registry_contract(main: Node) -> void: prev.name, node.name, node.get_index(), prev.get_index()]) prev = node # ③ 宿住层特例:level_screen 宿主=冒险屏壳且在全直接子模态之下(遮挡语义归模态栈 screen_state) - var lv: Node = main.ui.modals.level_screen + var lv: Node = main.ui.modals.run.level_screen if lv.get_parent() != main.ui.screens.adventure_screen: problems.append("[registry] level_screen 宿主应=冒险屏壳(实测 %s)〔装配生命批 V1〕" % lv.get_parent().name) - elif lv.get_parent().get_index() >= main.ui.modals.legend_overlay.get_index(): + elif lv.get_parent().get_index() >= main.ui.modals.run.legend_overlay.get_index(): problems.append("[registry] 冒险屏壳应在全模态之下(宿主 idx=%d legend idx=%d)〔装配生命批 V1〕" % [ - lv.get_parent().get_index(), main.ui.modals.legend_overlay.get_index()]) + lv.get_parent().get_index(), main.ui.modals.run.legend_overlay.get_index()]) # ④ 营地同槽特例:camp_screen 屏壳树序夹商店层与设置层之间(成员 camp_scene 登记位代言) var cs: Node = main.ui.screens.camp_screen - if cs.get_index() <= main.ui.modals.shop_overlay.get_index() or cs.get_index() >= main.ui.modals.settings_overlay.get_index(): + if cs.get_index() <= main.ui.modals.menu.shop_overlay.get_index() or cs.get_index() >= main.ui.modals.menu.settings_overlay.get_index(): problems.append("[registry] camp_screen 屏壳树序应夹商店与设置层之间(idx=%d shop=%d settings=%d)〔装配生命批 V1〕" % [ - cs.get_index(), main.ui.modals.shop_overlay.get_index(), main.ui.modals.settings_overlay.get_index()]) + cs.get_index(), main.ui.modals.menu.shop_overlay.get_index(), main.ui.modals.menu.settings_overlay.get_index()]) + # ⑤ 分域结构(句柄分域批 T1——两新子容器在座/平铺面清零/编排口独立) + # V1a 分域在座:run/menu 各六句柄经新二段式寻址逐件非 null(12 检) + var run_names: Array[String] = ["legend_overlay", "node_overlay", "level_screen", "curio_overlay", "end_overlay", "status_panel"] + var menu_names: Array[String] = ["shop_overlay", "settings_overlay", "career_overlay", "save_select_overlay", "confirm_dialog", "debug_overlay"] + for n in run_names: + if main.ui.modals.run.get(n) == null: + problems.append("[registry] run 域句柄缺失: %s〔句柄分域批 V1〕" % n) + for n in menu_names: + if main.ui.modals.menu.get(n) == null: + problems.append("[registry] menu 域句柄缺失: %s〔句柄分域批 V1〕" % n) + # V1b 平铺面清零:12 旧平铺名经 get() 逐件为 null(旧寻址成员不存在——纯搬家零别名) + for n in run_names + menu_names: + if main.ui.modals.get(n) != null: + problems.append("[registry] 平铺残留: modals.%s 应已迁域(get 非 null)〔句柄分域批 V1〕" % n) + # V1c 编排口独立:ModalFlow 直驻注册面(ui.flow 非 null)且 modals 无 flow 成员 + if main.ui.flow == null: + problems.append("[registry] ui.flow 直驻缺失〔句柄分域批 V1〕") + if main.ui.modals.get("flow") != null: + problems.append("[registry] modals.flow 残留(应升格独立持有)〔句柄分域批 V1〕") # ⑬career 零跳转契约(提纲 P4 甲——页内零「前往他处」入口:除「返回」外任何导航钮=红) func _career_nojump_contract(main: Node) -> void: for nav in ["营地", "商店", "设置", "冒险", "继续上局", "退出", "前往"]: - if ProbeUiUtil.find_button(main.ui.modals.career_overlay, nav, "contains") != null: + if ProbeUiUtil.find_button(main.ui.modals.menu.career_overlay, nav, "contains") != null: problems.append("[career][nojump] 页内出现导航入口「%s」(零跳转违约——生涯纯只读)" % nav) diff --git a/game/probe/ui_tune_probe.gd b/game/probe/ui_tune_probe.gd index dab76e35..c3eb8d13 100644 --- a/game/probe/ui_tune_probe.gd +++ b/game/probe/ui_tune_probe.gd @@ -45,33 +45,33 @@ func _ready() -> void: # 悬浮于菜单之上——开始层不隐藏;树序新基准=登记册末位(确认弹窗之后=绘制序最上)) main.ui.screens.debug_badge.pressed.emit() await get_tree().process_frame - if not main.ui.modals.debug_overlay.visible: + if not main.ui.modals.menu.debug_overlay.visible: problems.append("调试浮层角标点击后浮窗未开(全局浮层入口失守)") if not main.ui.screens.start_overlay.visible: problems.append("开始覆盖层被隐藏——浮窗应悬浮于菜单之上(14.5 形态失守)") - if main.ui.modals.debug_overlay.get_index() < main.ui.modals.confirm_dialog.get_index(): + if main.ui.modals.menu.debug_overlay.get_index() < main.ui.modals.menu.confirm_dialog.get_index(): problems.append("调试浮窗树序不在确认弹窗之后——登记册末位失守") # ② 滑条 → 生效预览联动 - main.ui.modals.debug_overlay.set_sliders(100, 50) - if str(boosted_atk) not in main.ui.modals.debug_overlay.effect_text() or str(boosted_hp) not in main.ui.modals.debug_overlay.effect_text(): - problems.append("生效预览未联动(%s)" % main.ui.modals.debug_overlay.effect_text()) + main.ui.modals.menu.debug_overlay.set_sliders(100, 50) + if str(boosted_atk) not in main.ui.modals.menu.debug_overlay.effect_text() or str(boosted_hp) not in main.ui.modals.menu.debug_overlay.effect_text(): + problems.append("生效预览未联动(%s)" % main.ui.modals.menu.debug_overlay.effect_text()) # ③ 应用(真实信号路径:apply_requested → 组合根)——E4 后加成零表写,生效断言移至 ⑧ 战斗快照 - main.ui.modals.debug_overlay.press_apply_for_probe() + main.ui.modals.menu.debug_overlay.press_apply_for_probe() # ④ 重置(重开浮窗 → 重置按钮真实路径;面板百分比归零——下一场战斗生效值回到基线) main._show_debug_overlay() - main.ui.modals.debug_overlay.press_reset_for_probe() + main.ui.modals.menu.debug_overlay.press_reset_for_probe() # ⑤ 批量模拟全链路(N=10——走 level_autoplay 与 headless 扫描同口径;表基线由组合根复位; # 菜单态上下文未锁定=按钮可点,is_running 语义=模拟在跑〔18卡⑦ 与对局禁用态拆分〕) - main.ui.modals.debug_overlay.set_sliders(100, 0) - main.ui.modals.debug_overlay.set_sim_count(10) - main.ui.modals.debug_overlay.press_simulate_for_probe() + main.ui.modals.menu.debug_overlay.set_sliders(100, 0) + main.ui.modals.menu.debug_overlay.set_sim_count(10) + main.ui.modals.menu.debug_overlay.press_simulate_for_probe() var guard := SIM_GUARD_FRAMES - while main.ui.modals.debug_overlay.is_running() and guard > 0: + while main.ui.modals.menu.debug_overlay.is_running() and guard > 0: guard -= 1 await get_tree().process_frame - if main.ui.modals.debug_overlay.is_running(): + if main.ui.modals.menu.debug_overlay.is_running(): problems.append("模拟未在预算帧内完成(疑似挂死)") - var out: String = main.ui.modals.debug_overlay.output_text() + var out: String = main.ui.modals.menu.debug_overlay.output_text() if not out.contains("关卡完成率"): problems.append("模拟输出缺关卡完成率") # 口径换代 V7:胜局统计行措辞钉子(atk+100% 加成档 10 局必有胜局——hp_left_win 非空路径恒出) @@ -83,7 +83,7 @@ func _ready() -> void: problems.append("模拟输出 N 标注错误(%s)" % out.substr(0, 40)) # ⑥ 关闭 → 浮窗收起、开始层全程在屏 main._close_debug_overlay() - if main.ui.modals.debug_overlay.visible or not main.ui.screens.start_overlay.visible: + if main.ui.modals.menu.debug_overlay.visible or not main.ui.screens.start_overlay.visible: problems.append("关闭后层级状态错误") # ⑦ hero 表运行期不可变(E4 收口回归断言:任何代码 reintroduce 表写在此被抓获——原"表值残留"断言语义升级) if int(main.db.combat.hero.atk) != base_atk or int(main.db.combat.hero.max_hp) != base_hp: @@ -93,20 +93,20 @@ func _ready() -> void: # 存档隔离(停点断点写盘随 main.save_manager;探针禁碰真实档,同族探针隔离纪律; # isolate_save 先删后建——上次运行的残留断点会触发互斥确认早退) ProbeUiUtil.isolate_save(main, "smoke_tune_probe_save", true, false, true) # 末参=swap_save(卡②签名换代——断点写盘经 autoload 落隔离档) - main.ui.modals.debug_overlay.set_sliders(100, 50) - main.ui.modals.debug_overlay.press_apply_for_probe() - main.ui.modals.debug_overlay.seed_edit.text = "42" # 种子钉死(18卡⑦审计🔴——空框=非首局随机,显式优先经真实路径断言 master_seed) + main.ui.modals.menu.debug_overlay.set_sliders(100, 50) + main.ui.modals.menu.debug_overlay.press_apply_for_probe() + main.ui.modals.menu.debug_overlay.seed_edit.text = "42" # 种子钉死(18卡⑦审计🔴——空框=非首局随机,显式优先经真实路径断言 master_seed) main._start_adventure_from_menu() # 完整冒险路径(唯一试玩入口;_start_single 按语义清零滑条 = 恒基线,不承载加成断言) await get_tree().process_frame await get_tree().process_frame # ⑧b 开冒险路径显式关浮窗(18卡⑦ T2 防护——调试会话默认开态下开局不带窗,对局中经角标再开) - if main.ui.modals.debug_overlay.visible: + if main.ui.modals.menu.debug_overlay.visible: problems.append("开冒险路径未显式关浮窗(T2 防护失守)") if int(main.run_scope.adventure.master_seed) != 42: problems.append("显式种子未达冒险(master_seed=%s ≠ 42——真实路径显式优先失守)" % str(main.run_scope.adventure.master_seed)) # 关卡开始 = 入口选路停点——真实按钮路径选入口候选进首战再读呈现快照 - if main.ui.modals.level_screen.visible: - main.ui.modals.level_screen.press_candidate(0) + if main.ui.modals.run.level_screen.visible: + main.ui.modals.run.level_screen.press_candidate(0) await get_tree().process_frame await get_tree().process_frame var hero: Dictionary = main.run_scope.settlement.presentation_snapshot().hero @@ -121,18 +121,18 @@ func _ready() -> void: problems.append("对局中调试浮层角标非恒隐(发布态语义失守——卡⑦ 删写死行批)") else: main._show_debug_overlay() # 对局中开浮窗回归经直调承载(:61 既有直调先例——发布态角标恒隐,按钮信号路径退役) - if not main.ui.modals.debug_overlay.visible: + if not main.ui.modals.menu.debug_overlay.visible: problems.append("对局中角标开浮窗失败(P2 甲对局中可达失守)") - if main.ui.modals.flow.locks_input(): + if main.ui.flow.locks_input(): problems.append("对局中开浮窗后棋盘输入锁死(T2 防护失守——S1+ 停机点)") if main.ui.battle.board_zone.status_badge.disabled: problems.append("对局中开浮窗后状态角标置灰(T2 防护失守——S1+ 停机点)") - if main.ui.modals.debug_overlay.is_running(): + if main.ui.modals.menu.debug_overlay.is_running(): problems.append("对局中上下文锁定误判为模拟在跑(is_running 语义互扰)") - if not main.ui.modals.debug_overlay._sim_btn.disabled or not main.ui.modals.debug_overlay._dbg_btn.disabled: + if not main.ui.modals.menu.debug_overlay._sim_btn.disabled or not main.ui.modals.menu.debug_overlay._dbg_btn.disabled: problems.append("对局中批量模拟/两层调试按钮未禁用(T2 并存态不开放失守)") # 当局数据六项值对(T3——数值与只读访问器逐项一致;种子=adventure.master_seed 现值非 _seed_value 重算) - var rd: Dictionary = main.ui.modals.debug_overlay.run_data_probe() + var rd: Dictionary = main.ui.modals.menu.debug_overlay.run_data_probe() var r = main.run_scope.adventure.runner() if String(rd.get("seed", "")) != "42": problems.append("当局数据种子 ≠ master_seed 现值(%s)" % str(rd.get("seed"))) @@ -146,27 +146,27 @@ func _ready() -> void: problems.append("当局数据 GP 失对(%s ≠ %s)" % [str(rd.get("gp")), str(r.gp)]) if int(rd.get("keystone", -1)) != main.run_scope.adventure.journal().keystone_gained(): problems.append("当局数据钥石失对(%s ≠ %s)" % [str(rd.get("keystone")), str(main.run_scope.adventure.journal().keystone_gained())]) - var close_btn: Button = ProbeUiUtil.find_button(main.ui.modals.debug_overlay, "关闭", "prefix") + var close_btn: Button = ProbeUiUtil.find_button(main.ui.modals.menu.debug_overlay, "关闭", "prefix") if close_btn == null: problems.append("浮窗关闭钮缺失(真实收窗路径中断)") else: close_btn.pressed.emit() - if main.ui.modals.debug_overlay.visible or main.ui.modals.flow.locks_input(): + if main.ui.modals.menu.debug_overlay.visible or main.ui.flow.locks_input(): problems.append("对局中关闭浮窗后态失守(visible=%s locked=%s)" - % [str(main.ui.modals.debug_overlay.visible), str(main.ui.modals.flow.locks_input())]) + % [str(main.ui.modals.menu.debug_overlay.visible), str(main.ui.flow.locks_input())]) # ⑨b 只读巡检(17卡③形制)+零动效契约(17卡③ R5——零 AnimationPlayer+源码零 tween) _overlay_readonly_contract(main, problems) _overlay_zero_motion_contract(main, problems) # ⑩ 种子三分单点断言(18卡⑦ C1——判定只落 _seed_value() 单点;对局态不影响本判定面): # 显式合法整数全场景优先(含首局)→ 首局(存档布尔 false)恒 42 → 非首局空框引擎熵连抽不全等 42 - main.ui.modals.debug_overlay.seed_edit.text = "777" + main.ui.modals.menu.debug_overlay.seed_edit.text = "777" if main._seed_value() != 777: problems.append("显式种子输入未优先(_seed_value != 777——C1 ①失守)") # SaveManager 是 autoload 单例——换注走原地 attach(首局态挂载+恢复隔离档); # --script 模式无裸 autoload 标识符——经 main 节点路径解析 var sm = main.get_node("/root/SaveManager") sm.slots.attach_path("user://smoke_tune_probe_seed.json") # 全新空档——first_adventure_done=false(首局态;新档缺省面) - main.ui.modals.debug_overlay.seed_edit.text = "" + main.ui.modals.menu.debug_overlay.seed_edit.text = "" if main._seed_value() != 42: problems.append("首局空框种子 != 42(P5 甲失守——教学态可复现破约)") sm.slots.attach_path("user://smoke_tune_probe_save.json") # 恢复隔离档(非首局态——isolate_save 已建档置位) @@ -181,7 +181,7 @@ func _ready() -> void: all42 = false if all42: problems.append("非首局空框连抽 4 次全等 42(随机化失守——C1 ③)") - main.ui.modals.debug_overlay.seed_edit.text = "42" # 钉回显式值(现场整洁) + main.ui.modals.menu.debug_overlay.seed_edit.text = "42" # 钉回显式值(现场整洁) for f0 in ["user://smoke_tune_probe_seed.json", "user://smoke_tune_probe_seed.json.tmp"]: if FileAccess.file_exists(f0): DirAccess.remove_absolute(f0) @@ -196,11 +196,11 @@ func _ready() -> void: # 只读巡检(18卡⑦ T3——17卡③「白名单外零连接」形制沿用):浮窗子树全控件 × 游戏语义信号名清单 -# 全连接巡检——白名单={视图自身八方法 + 注册面模态编排口 ui.modals.flow.close(closed 外发消费—— +# 全连接巡检——白名单={视图自身八方法 + 注册面模态编排口 ui.flow.close(closed 外发消费—— # 注册面批把该连接目标从组合根转发壳改为 flow 真方法,机检白名单随批登记同一连接)}; # 当局数据区块=纯标签零连接在此被一并约束(新增任何连接=红须入册审查)。 func _overlay_readonly_contract(main: Node, problems: Array) -> void: - var overlay: Control = main.ui.modals.debug_overlay + var overlay: Control = main.ui.modals.menu.debug_overlay var stack: Array = [overlay] var conn_count := 0 while not stack.is_empty(): @@ -211,7 +211,7 @@ func _overlay_readonly_contract(main: Node, problems: Array) -> void: var cb: Callable = conn["callable"] var host: Object = cb.get_object() var m := String(cb.get_method()) - var flow_host: Object = main.ui.modals.flow # 注册面模态编排口(closed 外发消费白名单宿主) + var flow_host: Object = main.ui.flow # 注册面模态编排口(closed 外发消费白名单宿主) var ok := (host == overlay and m in _OVERLAY_VIEW_METHODS) \ or (host == flow_host and m == "close") \ or (host is VScrollBar or host is HScrollBar) # 引擎滚动机构内部 drag 接线(RichTextLabel 自动滚动条——17卡③布局生命线同类豁免,零游戏写通路) @@ -226,7 +226,7 @@ func _overlay_readonly_contract(main: Node, problems: Array) -> void: # 零动效契约(17卡③ R5 形制——子树零 AnimationPlayer + 视图源码零 tween/动效包装调用) func _overlay_zero_motion_contract(main: Node, problems: Array) -> void: - var overlay: Control = main.ui.modals.debug_overlay + var overlay: Control = main.ui.modals.menu.debug_overlay var stack: Array = [overlay] while not stack.is_empty(): var n: Node = stack.pop_back() diff --git a/game/tests/smoke_boot.gd b/game/tests/smoke_boot.gd index 172017f8..25f1960a 100644 --- a/game/tests/smoke_boot.gd +++ b/game/tests/smoke_boot.gd @@ -71,5 +71,5 @@ func _check_main_scene(main: Node) -> void: check(start_overlay != null and start_overlay.visible, "启动态菜单可见(boot 后玩家见菜单)") check(main.get("settings_store") != null, "系统设置存储已装配(settings_store)") check(main.get("ui").screens.debug_badge != null, "调试入口角标已装配(debug_badge)") - check(main.get("ui").modals.legend_overlay != null, "图例覆盖层已装配(legend_overlay)") - check(main.get("ui").modals.status_panel != null, "局内状态面板已装配(status_panel)") + check(main.get("ui").modals.run.legend_overlay != null, "图例覆盖层已装配(legend_overlay)") + check(main.get("ui").modals.run.status_panel != null, "局内状态面板已装配(status_panel)") diff --git a/game/tests/system_test.gd b/game/tests/system_test.gd index 439c7455..6adb2c54 100644 --- a/game/tests/system_test.gd +++ b/game/tests/system_test.gd @@ -105,7 +105,7 @@ func _smoke_scene_split() -> void: chain_prev = node check(chain_ok, "V1c 合同树序链逐对保序(运行期树序==LAYER_CONTRACT 序;违例:%s)〔装配生命批 V1〕〔句柄子容器批 V2〕" % chain_detail) # ③ 探针挂载假设句柄在座(main.X 一级路径——探针迁移面收口证明) - check(main.ui.screens.start_overlay == main.ui.screens.menu_screen.start_overlay and main.ui.modals.camp.scene == main.ui.screens.camp_screen.camp_scene and main.ui.modals.level_screen == main.ui.screens.adventure_screen.level_screen and main.ui.screens.battle_root == main.ui.screens.adventure_screen.battle_root and main.ui.battle.board_zone.board_view == main.ui.screens.adventure_screen.board_view, + check(main.ui.screens.start_overlay == main.ui.screens.menu_screen.start_overlay and main.ui.modals.camp.scene == main.ui.screens.camp_screen.camp_scene and main.ui.modals.run.level_screen == main.ui.screens.adventure_screen.level_screen and main.ui.screens.battle_root == main.ui.screens.adventure_screen.battle_root and main.ui.battle.board_zone.board_view == main.ui.screens.adventure_screen.board_view, "V2 组合根句柄指向屏壳成员(探针挂载假设 main.X 零迁移)〔句柄子容器批 V1〕") check(main.run_scope != null and not main.run_scope.is_adventure, "V2 闲时会话自 _ready 即在(永非 null;迭代 27 T3——gp 影子账退役无账本成员)") @@ -117,9 +117,9 @@ func _smoke_scene_split() -> void: # camp 壳 boot 态 vis=true 但成员恒隐,裸树遍历自顶向下会先命中透明壳) func _topmost_visible_layer(main: Node) -> Node: var candidates: Array[Node] = [main.ui.screens.start_overlay, main.ui.screens.battle_root, main.ui.screens.inter_overlay, - main.ui.modals.legend_overlay, main.ui.modals.node_overlay, main.ui.modals.level_screen, main.ui.modals.curio_overlay, - main.ui.modals.end_overlay, main.ui.modals.shop_overlay, main.ui.modals.camp.scene, main.ui.modals.settings_overlay, - main.ui.modals.status_panel, main.ui.modals.career_overlay, main.ui.modals.confirm_dialog, main.ui.modals.debug_overlay] + main.ui.modals.run.legend_overlay, main.ui.modals.run.node_overlay, main.ui.modals.run.level_screen, main.ui.modals.run.curio_overlay, + main.ui.modals.run.end_overlay, main.ui.modals.menu.shop_overlay, main.ui.modals.camp.scene, main.ui.modals.menu.settings_overlay, + main.ui.modals.run.status_panel, main.ui.modals.menu.career_overlay, main.ui.modals.menu.confirm_dialog, main.ui.modals.menu.debug_overlay] var best: Node = null var best_x := -1 var best_y := -1 @@ -199,7 +199,7 @@ func _smoke_boot_chain() -> void: "V2e run_scope 非空且为闲时会话(is_adventure=false+is_inside_tree)〔装配生命批 V2〕") # ⑥ 调试浮窗 boot 不开+菜单角标在(菜单优先——boot 不自动开浮窗, # 入口=开始屏右下角「调试」角标+对局中两屏角标) - check(not main.ui.modals.debug_overlay.visible, + check(not main.ui.modals.menu.debug_overlay.visible, "V2f 调试浮窗 boot 不自动开(菜单优先)〔2026-10-16 用户令〕") check(main.ui.screens.debug_badge != null and not main.ui.screens.debug_badge.visible, "V2f 调试浮层角标发布态恒隐(2026-10-25 删写死行批)") @@ -223,7 +223,7 @@ func _smoke_run_lifecycle() -> void: sm.slots.attach_path("user://smoke_run_lifecycle.json") sm.meta_state.mark_first_adventure_done() # ---- 第一局:开局→注脏→放弃→终局出口→会话收束 ---- - main.ui.modals.debug_overlay.seed_edit.text = "42" + main.ui.modals.menu.debug_overlay.seed_edit.text = "42" main._start_adventure_from_menu() await process_frame check(main.run_scope != null and main.run_scope.is_adventure and main.run_scope.adventure != null, @@ -243,7 +243,7 @@ func _smoke_run_lifecycle() -> void: break await process_frame check(finished.done, "V3 冒险终局到达(abandon→adventure_finished 同步链)") - check(main.ui.modals.end_overlay.is_open(), "V3 终局面板弹起") + check(main.ui.modals.run.end_overlay.is_open(), "V3 终局面板弹起") main._on_battle_end_continue() # 终局出口(回菜单+会话收束——手工置空清单已消亡) await process_frame check(not main.run_scope.is_adventure and main.run_scope.adventure == null and main.run_scope.runner == null, @@ -253,7 +253,7 @@ func _smoke_run_lifecycle() -> void: check(main.ui.screens.start_overlay.visible, "V3 回开始菜单(终局出口路径)") # ---- 第二局:放弃终局无断点残留→直进开局→全新会话 ---- check(not sm.has_breakpoint(), "V3 放弃终局清断点(无残留断点)") - main.ui.modals.debug_overlay.seed_edit.text = "43" + main.ui.modals.menu.debug_overlay.seed_edit.text = "43" main._start_adventure_from_menu() await process_frame check(main.run_scope.is_adventure and main.run_scope.adventure != null and main.run_scope.hints_shown.is_empty(), @@ -288,7 +288,7 @@ func _smoke_sim_guards() -> void: "模拟运行态下调试两层入口守卫早退(开始层不收起)〔wd6〕") main._tune_sim._running = false # wd7:真实冒险关间态(is_adventure=true、settlement==null)→ 模拟入口守卫早退 - main.ui.modals.debug_overlay.seed_edit.text = "42" + main.ui.modals.menu.debug_overlay.seed_edit.text = "42" main._start_adventure_from_menu() await process_frame check(main.run_scope.is_adventure and main.run_scope.settlement == null, @@ -313,7 +313,7 @@ func _face_snapshot(main: Node) -> Dictionary: var tu = main._treasure_ui var bo = main._bonus var cp = main._camp_pres - var mf = main.ui.modals.flow + var mf = main.ui.flow var rp = main.ui.screens.row_pacer return { "treasure_settlement": tu._settlement_get.call(), @@ -381,10 +381,10 @@ func _stage_faces(main: Node, tag: String, adv: String, run: String, set: String if bool(snap["mf_rows_empty"]) != bool(extra[k]): bad += " modal_flow.rows_empty_get 应=%s" % str(extra[k]) "level_vis": - if main.ui.modals.level_screen.visible != bool(extra[k]): + if main.ui.modals.run.level_screen.visible != bool(extra[k]): bad += " level_screen.visible 应=%s" % str(extra[k]) "end_open": - if main.ui.modals.end_overlay.is_open() != bool(extra[k]): + if main.ui.modals.run.end_overlay.is_open() != bool(extra[k]): bad += " _end_overlay.is_open 应=%s" % str(extra[k]) "start_vis": if main.ui.screens.start_overlay.visible != bool(extra[k]): @@ -419,7 +419,7 @@ func _smoke_lifecycle_matrix() -> void: if FileAccess.file_exists(p): DirAccess.remove_absolute(p) sm.slots.attach_path("user://smoke_lifecycle.json") - main.ui.modals.debug_overlay.seed_edit.text = "42" + main.ui.modals.menu.debug_overlay.seed_edit.text = "42" # 阶段①闲时 await _stage_faces(main, "V3a 闲时", "null", "null", "null", {"mf_start": true, "mf_inter": false, "mf_rows_empty": true, "start_vis": true}) @@ -430,7 +430,7 @@ func _smoke_lifecycle_matrix() -> void: await _stage_faces(main, "V3b 开局停点", "alive", "alive", "null", {"mf_start": false, "level_vis": true}) # 阶段③地图选路→战斗中(真实选路提交 press_candidate) - main.ui.modals.level_screen.press_candidate(0) + main.ui.modals.run.level_screen.press_candidate(0) for i in range(10): await process_frame await _stage_faces(main, "V3c 战斗中", "alive", "alive", "alive", {"mf_start": false}) @@ -467,7 +467,7 @@ func _smoke_lifecycle_matrix() -> void: await process_frame main.ui.screens.row_pacer.flush() for i in range(12): - if main.ui.modals.end_overlay.is_open(): + if main.ui.modals.run.end_overlay.is_open(): break await process_frame # 分支甲:战斗胜利停点(结算后)→确认进选路→放弃→终局面 @@ -484,11 +484,11 @@ func _smoke_lifecycle_matrix() -> void: await process_frame main.ui.screens.row_pacer.flush() for i in range(8): - if main.ui.modals.end_overlay.is_open(): + if main.ui.modals.run.end_overlay.is_open(): break await process_frame # 阶段⑤终局面(败北/放弃皆达) - check(finished.done and main.ui.modals.end_overlay.is_open(), + check(finished.done and main.ui.modals.run.end_overlay.is_open(), "V3e 终局面到达(adventure_finished+终局面板开)〔装配生命批 V3〕") await _stage_faces(main, "V3e 终局面", "alive", "alive", "null", {"mf_start": false, "end_open": true}) # 阶段⑥回菜单收束(旧局消亡+新会话即驻+无真空窗) @@ -515,7 +515,7 @@ func _smoke_lifecycle_matrix() -> void: main._open_save_select() for i in range(2): await process_frame - var ssel_new: Button = main.ui.modals.save_select_overlay._new_btns.get(1) + var ssel_new: Button = main.ui.modals.menu.save_select_overlay._new_btns.get(1) if ssel_new == null: check(false, "V3g 营地入口缺失(选档层新档钮驱动中断)〔装配生命批 V3〕") continue @@ -551,7 +551,7 @@ func _smoke_lifecycle_matrix() -> void: DirAccess.remove_absolute(slot_p) # 阶段⑧两连局(第二局全新会话——旧局对象不复用、脏态零残留) var first_adv = main.run_scope.adventure - main.ui.modals.debug_overlay.seed_edit.text = "43" + main.ui.modals.menu.debug_overlay.seed_edit.text = "43" main._start_adventure_from_menu() for i in range(6): await process_frame @@ -560,12 +560,12 @@ func _smoke_lifecycle_matrix() -> void: await _stage_faces(main, "V3h 两连局·开局停点", "alive", "alive", "null", {"mf_start": false, "level_vis": true}) check(main.run_scope.adventure.abandon(), "V3h 放弃受理(第二局)〔装配生命批 V3〕") for i in range(12): - if main.ui.modals.end_overlay.is_open(): + if main.ui.modals.run.end_overlay.is_open(): break await process_frame main.ui.screens.row_pacer.flush() for i in range(8): - if main.ui.modals.end_overlay.is_open(): + if main.ui.modals.run.end_overlay.is_open(): break await process_frame await _stage_faces(main, "V3h 两连局·终局面", "alive", "alive", "null", {"mf_start": false, "end_open": true}) diff --git a/game/tests/unit/presentation/pacing_queue_test.gd b/game/tests/unit/presentation/pacing_queue_test.gd index 2da70b5f..fdf2a6bc 100644 --- a/game/tests/unit/presentation/pacing_queue_test.gd +++ b/game/tests/unit/presentation/pacing_queue_test.gd @@ -58,7 +58,7 @@ func _init() -> void: main._start_adventure_from_menu() await process_frame # 入口停点选路进首战(真实停点链) - if not main.ui.modals.level_screen.visible: + if not main.ui.modals.run.level_screen.visible: _problems.append("入口停点未呈现(前置失守)") _finish() return diff --git a/game/tests/unit/presentation/theme_projection_test.gd b/game/tests/unit/presentation/theme_projection_test.gd index 03d3812a..71dacd2b 100644 --- a/game/tests/unit/presentation/theme_projection_test.gd +++ b/game/tests/unit/presentation/theme_projection_test.gd @@ -716,7 +716,7 @@ func _smoke_diag_button_retired() -> void: Kit.check("V6 主区无 build_diag_button 调用残留(旧实现同批清理)", not main_src.contains("build_diag_button")) Kit.check("V6 转储入口连接随迁(组合根经注册面接浮窗 diagnose_requested 信号)〔注册面 V2〕", - main_src.contains("ui.modals.debug_overlay.diagnose_requested.connect(_on_diagnose_pressed)")) + main_src.contains("ui.modals.menu.debug_overlay.diagnose_requested.connect(_on_diagnose_pressed)")) Kit.check("V6 转储编排承接函数在位(dump_diagnosis 卡死取证保留)", main_src.contains("func _on_diagnose_pressed()")) var asm_src: String = FileAccess.get_file_as_string("res://presentation/ui_assembler.gd")