diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 764448b4..a5a28dbd 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -39,9 +39,8 @@ var _adventure_mode := false # 冒险模式标记(inter 层确认分派 var logger = null # 试玩日志器(JSONL 持久化 + 卡死诊断转储——呈现层组件) var _level_active := false var _seed_edit: LineEdit -var _lvbtn: Button # 开始冒险按钮句柄(迭代 8 卡③——有断点时互斥二次确认文案换面) var _resume_btn: Button # 继续上局按钮句柄(迭代 8 卡②——有断点才亮) -var _lv_start_armed := false # 新冒险二次确认态(首击置位、二击提交、3s 超时回落——仿放弃确认) +var confirm_dialog: ConfirmDialog # 通用确认弹窗(二轮修改批 A——新冒险互斥确认弹窗化) var _settlement_stop_pending := false # 卡⑥ 战斗胜利总结结算框挂起态(确认「下一节点」后进选路页) var _pending_candidates: Array = [] # 结算框挂起期的选路候选暂存(choices_presented 载荷) var _last_gain_amount := 0 # 最近一笔钱包入账(卡⑥ GP 增量取数面——wallet_changed gain 载荷) @@ -185,18 +184,21 @@ func _build_ui() -> void: tune_panel.closed.connect(_close_tune_panel) tune_panel.apply_requested.connect(_on_tune_apply) tune_panel.simulate_requested.connect(_on_tune_simulate) - settings_overlay = SettingsOverlay.new(settings_store) # 迭代 8 卡④——最小设置面(音量/全屏/减动效/退出) + settings_overlay = SettingsOverlay.new(settings_store) # 迭代 8 卡④——最小设置面(音量/全屏/减动效) add_child(settings_overlay) settings_overlay.closed.connect(_on_settings_closed) + confirm_dialog = ConfirmDialog.new() # 二轮修改批 A——新冒险互斥确认弹窗 + add_child(confirm_dialog) + confirm_dialog.confirmed.connect(_on_confirm_new_adventure) + confirm_dialog.cancelled.connect(_on_confirm_cancelled) _modals = ModalStack.new() - for m: Control in [legend_overlay, review_overlay, node_overlay, level_screen, _end_overlay, shop_overlay, camp_overlay, tune_panel, settings_overlay]: + for m: Control in [legend_overlay, review_overlay, node_overlay, level_screen, _end_overlay, shop_overlay, camp_overlay, tune_panel, settings_overlay, confirm_dialog]: _register_modal(m) _tune_sim = TuneSimController.new(db, tune_panel, int(db.hero.max_hp)) var start: Dictionary = UiAssembler.build_start_overlay(self, db) _start_overlay = start.overlay _seed_edit = start.seed_edit - _lvbtn = start.lvbtn _resume_btn = start.resumebtn start.lvbtn.pressed.connect(_start_adventure_from_menu) start.resumebtn.pressed.connect(_resume_from_menu) # 迭代 8 卡②——续玩入口 @@ -205,6 +207,7 @@ func _build_ui() -> void: start.dbgbtn.visible = OS.get_environment("DS_DEBUG") == "1" # 迭代 8 卡④——调试入口默认隐藏门控(DS_DEBUG=1 点亮) start.campbtn.pressed.connect(_open_camp) start.settingsbtn.pressed.connect(_open_settings) # 迭代 8 卡④——最小设置面入口 + start.quitbtn.pressed.connect(_quit_game) # 二轮修改批 D1——退出游戏提级一级菜单 var inter: Dictionary = UiAssembler.build_inter_overlay(self) _inter_overlay = inter.overlay @@ -250,35 +253,44 @@ func _input_locked() -> bool: # 模态/非战斗期棋盘输入锁(各 overlay 自身可见即锁定) return legend_overlay.is_open() or review_overlay.is_open() or _end_overlay.is_open() \ or node_overlay.visible or level_screen.visible or shop_overlay.visible or tune_panel.visible \ - or _start_overlay.visible or _inter_overlay.visible or settlement == null + or _start_overlay.visible or _inter_overlay.visible or confirm_dialog.visible or settlement == null # ==================== 冒险模式入口(唯一试玩入口——单关测试直入口退役 2026-09-23 用户拍板)==================== func _start_adventure_from_menu() -> void: - # 互斥二次确认(迭代 8 卡②③):有断点时开新冒险须两击——首击转确认态(文案换面+3s 超时回落, - # 仿放弃确认形态),二击才提交;提交即弃旧断点(clear+写盘——「开新局」覆盖语义对盘面即时生效) - if save_manager != null and save_manager.has_breakpoint() and not _lv_start_armed: - _lv_start_armed = true - if _lvbtn != null: - _lvbtn.text = "已有未完冒险——再点一次开新冒险(旧断点将被覆盖)" - get_tree().create_timer(3.0).timeout.connect(_reset_lv_confirm) + # 互斥确认弹窗化(二轮修改批 A——用户目检裁决「弹窗提示」):有断点时开新冒险弹实底确认框, + # 「开新冒险」提交(弃旧断点+写盘)/「取消」回菜单 + if save_manager != null and save_manager.has_breakpoint(): + var bp: Dictionary = save_manager.breakpoint_snapshot() + confirm_dialog.open("已有未完冒险(第 %d 关)——开新冒险将覆盖旧断点" % maxi(1, int(bp.get("level_index", 1))), + "开新冒险", "取消") return - if save_manager != null: - save_manager.clear_breakpoint() - save_manager.write_to_disk() - _lv_start_armed = false _start_overlay.visible = false _start_adventure() -func _reset_lv_confirm() -> void: - _lv_start_armed = false - if _lvbtn != null: - _lvbtn.text = "▶ 开始冒险(三关连探:地精洞窟 → 幽暗地域 → 灰矮人矿城)" +func _on_confirm_new_adventure() -> void: + confirm_dialog.close() + if save_manager != null: + save_manager.clear_breakpoint() # 提交即弃旧断点(覆盖语义对盘面即时生效) + save_manager.write_to_disk() + _start_overlay.visible = false + _start_adventure() -# 继续上局按钮可见性 = 断点在档态(迭代 8 卡②——启动读盘后与每次回开始菜单刷新;确认态随面复位) +func _on_confirm_cancelled() -> void: + confirm_dialog.close() # 回开始菜单(无状态变更) + + +# 退出游戏(二轮修改批 D1——一级菜单入口;设置即时落盘后退出) +func _quit_game() -> void: + if settings_store != null: + settings_store.save_settings() + get_tree().quit() + + +# 继续上局按钮可见性 = 断点在档态(迭代 8 卡②——启动读盘后与每次回开始菜单刷新) func _refresh_resume_button() -> void: if _resume_btn == null or save_manager == null: return @@ -287,7 +299,6 @@ func _refresh_resume_button() -> void: if has: var bp: Dictionary = save_manager.breakpoint_snapshot() _resume_btn.text = "▶ 继续上局(第 %d 关 · 自最近停点恢复)" % maxi(1, int(bp.get("level_index", 1))) - _reset_lv_confirm() # 继续上局(迭代 8 卡②——卡① resume_adventure 的消费入口):断点重建至最近停点 diff --git a/game/presentation/ui_assembler.gd b/game/presentation/ui_assembler.gd index 1c6d9b00..7b078c95 100644 --- a/game/presentation/ui_assembler.gd +++ b/game/presentation/ui_assembler.gd @@ -142,8 +142,11 @@ static func build_start_overlay(host: Control, db = null) -> Dictionary: box.add_child(dbgbtn) var campbtn := UiKit.button("营地 · 永久强化(钥石换永久属性——跨局成长账本)") # 元进度实现批——主菜单营地入口(双入口框架的主菜单一侧) box.add_child(campbtn) - var settingsbtn := UiKit.button("设置(音量 / 全屏 / 减动效 / 退出游戏)") # 迭代 8 卡④——最小设置面入口 + var settingsbtn := UiKit.button("设置(音量 / 全屏 / 减动效)") # 迭代 8 卡④——最小设置面入口 box.add_child(settingsbtn) + # 二轮修改批 D1(2026-09-29 用户目检裁决):退出游戏与设置同层并行——只在一级菜单出现 + var quitbtn := UiKit.button("退出游戏") + box.add_child(quitbtn) # 玩法帮助文案(表值驱动——2026-09-16 AP 压速批遗留硬编码 8 随手修正;护甲等级掷点批同步"减伤"→"护甲强化") var help_text := "玩法:每轮 %d AP——挖开(1AP)/插旗(1AP)/移动(免费)/连锁(免费)。\n每个非封锁格都藏一种格面资源(增伤/护甲强化/回血/爆裂/削弱)。开安全格、标中雷 = 正向兑付;\n开雷格、标错安全格 = 负向兑付——全部在轮末结算逐行兑现;吃掷点的基础攻击按护甲等级判定,无 miss、最低保底 1 点。\n停手:剩余 AP 转存 RAP;弃图刷新:RAP−%d 当场换图继续挖(每轮剩 %d 次)。\n敌人全灭胜、主角倒下负。悬停可看三态光标与可挖域微光。" % [ @@ -154,7 +157,7 @@ static func build_start_overlay(host: Control, db = null) -> Dictionary: return {"overlay": overlay, "box": box, "seed_edit": seed_edit, "lvbtn": lvbtn, "resumebtn": resumebtn, "tunebtn": tunebtn, "dbgbtn": dbgbtn, - "campbtn": campbtn, "settingsbtn": settingsbtn} + "campbtn": campbtn, "settingsbtn": settingsbtn, "quitbtn": quitbtn} # 调试两层模式的层间过渡覆盖层(历史入口专用)。返回 {overlay, label, next_btn}——接线归组合根 diff --git a/game/presentation/views/battle_end_overlay.gd b/game/presentation/views/battle_end_overlay.gd index 884c6b58..d66f6e2e 100644 --- a/game/presentation/views/battle_end_overlay.gd +++ b/game/presentation/views/battle_end_overlay.gd @@ -23,6 +23,9 @@ var _board_rect := Rect2() func _init() -> void: + # 二轮修改批 B(2026-09-29 用户目检裁决):半透明浮层文字不可读 → 实底石板皮面板形态 + #(与营地/设置/图例同族)。半透明氛围 dim 保留(胜利暖纱/失败暮蓝),信息与按钮全部上板; + # 金蜡封 = 面板头部静态徽记(原全屏坠落演出退役——落点被面板遮挡)。 _dim = ColorRect.new() _dim.color = Color(COL_WARM, 0.0) _dim.set_anchors_preset(Control.PRESET_FULL_RECT) @@ -30,33 +33,41 @@ func _init() -> void: var center := CenterContainer.new() center.set_anchors_preset(Control.PRESET_FULL_RECT) _dim.add_child(center) + var panel := PanelContainer.new() + panel.add_theme_stylebox_override("panel", UiKit.panel_skinned(Vector4(16, 14, 16, 14))) + center.add_child(panel) var box := VBoxContainer.new() + box.custom_minimum_size = Vector2(520, 0) box.add_theme_constant_override("separation", 12) - center.add_child(box) - _verdict = UiKit.label("", 26, true) - _verdict.add_theme_color_override("font_color", COL_WARM) + panel.add_child(box) + var header := HBoxContainer.new() + header.add_theme_constant_override("separation", 12) + header.alignment = BoxContainer.ALIGNMENT_CENTER + box.add_child(header) + _seal = Panel.new() + _seal.mouse_filter = Control.MOUSE_FILTER_IGNORE + var sb := UiKit.panel_flat(COL_GOLD, UiKit.GOLD_DEEP, 200, 4) + _seal.add_theme_stylebox_override("panel", sb) + _seal.custom_minimum_size = Vector2(34, 34) + _seal.size_flags_vertical = Control.SIZE_SHRINK_CENTER + header.add_child(_seal) + _verdict = UiKit.label("", 24, true) + _verdict.add_theme_color_override("font_color", UiKit.INK_TITLE) _verdict.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - box.add_child(_verdict) + _verdict.size_flags_vertical = Control.SIZE_SHRINK_CENTER + header.add_child(_verdict) _gp_line = UiKit.label("", 16, true) - _gp_line.add_theme_color_override("font_color", UiKit.GOLD) + _gp_line.add_theme_color_override("font_color", UiKit.INK_LABEL) _gp_line.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _gp_line.visible = false box.add_child(_gp_line) + box.add_child(HSeparator.new()) _review_btn = UiKit.button("明细回看") _review_btn.pressed.connect(_emit_review_pressed) box.add_child(_review_btn) _btn = UiKit.button("继续") _btn.pressed.connect(_emit_continue_pressed) box.add_child(_btn) - # 金蜡封占位(胜利):圆形色块落于扫雷图区中央——位置由 set_board_rect 注入 - _seal = Panel.new() - _seal.mouse_filter = Control.MOUSE_FILTER_IGNORE - var sb := UiKit.panel_flat(COL_GOLD, UiKit.GOLD_DEEP, 200, 4) - _seal.add_theme_stylebox_override("panel", sb) - _seal.size = Vector2(72, 72) - _seal.visible = false - _seal.top_level = true - add_child(_seal) visible = false @@ -66,14 +77,13 @@ func _init() -> void: func open_victory(verdict_line: String, continue_label: String, board_rect: Rect2, gp_summary: String = "") -> void: _dim.color = Color(COL_WARM, 0.28) _verdict.text = verdict_line - _verdict.add_theme_color_override("font_color", UiKit.INK_ON_GOLD) + _seal.visible = true # 胜利徽记(面板头部静态) _gp_line.text = gp_summary _gp_line.visible = gp_summary != "" _btn.text = continue_label _board_rect = board_rect visible = true - _play_seal_fall() - _play_crest_assemble() # 碎砖拼纹章(§2.4——样张 v2 冻结批 T3 终稿) + _play_crest_assemble() # 碎砖拼纹章(§2.4——样张 v2 冻结批 T3 终稿;绕面板屏心环收) _play_flag_recall(0) # 胜利不放旗收回 @@ -81,11 +91,10 @@ func open_defeat(verdict_line: String, continue_label: String, board_rect: Rect2 # 明度不变的暮蓝色相氛围(全屏唯一暮蓝档——§2.5 最静一拍) _dim.color = Color(COL_DUSK, 0.45) _verdict.text = verdict_line - _verdict.add_theme_color_override("font_color", COL_WARM) _gp_line.visible = false # GP 总量+增量归胜利总结结算(OQ14)——失败侧不呈现 _board_rect = board_rect visible = true - _seal.visible = false + _seal.visible = false # 失败无徽记(暮蓝氛围) _play_dust_sadle() # 坐砖掸灰(§2.5——终稿) _play_flag_recall(8) @@ -104,16 +113,8 @@ func verdict_text() -> String: return _verdict.text -# 金蜡封自上落下弹跳定格于棋盘中央(§2.4 克制不狂欢——占位级) -func _play_seal_fall() -> void: - var target := _board_rect.get_center() - _seal.size * 0.5 - if _board_rect.size == Vector2.ZERO: - target = get_viewport_rect().size * 0.5 - _seal.size * 0.5 - _seal.position = Vector2(target.x, -100.0) - _seal.visible = true - var tw := _seal.create_tween() - tw.tween_property(_seal, "position:y", target.y - 26.0, UiKit.motion(0.42)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN) - tw.tween_property(_seal, "position:y", target.y, UiKit.motion(0.22)).set_trans(Tween.TRANS_BOUNCE).set_ease(Tween.EASE_OUT) +func _fx_center() -> Vector2: + return get_viewport_rect().size * 0.5 # 旗化光点收回(失败——插过的旗逐面化光点收回占位,§2.5) @@ -147,7 +148,7 @@ var _fx_flag_dots := 0 func _play_crest_assemble() -> void: _clear_crest_bricks() _fx_crest_shards = 10 - var c := _board_rect.get_center() if _board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5 + var c := _fx_center() for i in 10: var brick := ColorRect.new() brick.color = UiKit.BRICK if i % 2 == 0 else UiKit.FLOOR # 碎砖=砖面族色(掩尘/裸土——图底同源) @@ -167,7 +168,7 @@ func _play_crest_assemble() -> void: # 坐砖掸灰(失败 §2.5——暖灰尘絮自棋盘中带缓缓飘落,最静一拍的运动承载) func _play_dust_sadle() -> void: _fx_dust_motes = 8 - var base := _board_rect.get_center() if _board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5 + var base := _fx_center() for i in 8: var mote := ColorRect.new() mote.color = Color(UiKit.WARM_FAINT, 0.85) diff --git a/game/presentation/views/battle_log_view.gd b/game/presentation/views/battle_log_view.gd index f83f5e09..a76184e2 100644 --- a/game/presentation/views/battle_log_view.gd +++ b/game/presentation/views/battle_log_view.gd @@ -192,7 +192,9 @@ func _format_row(row: Dictionary, agg_count := -1) -> String: roll_txt += " · ×%d%%" % int(row.tier) roll_txt += "〕" var kill := " ⚔击杀!" if row.get("killed", false) else "" - var body := "%s -%d%s%s%s%s" % [name_, v, roll_txt, tail, kill, suffix] + # 二轮修改批 C(2026-09-29 用户目检——「每次结算标出的伤害数字不明显」): + # 伤害值放大加色独立于行文(朱红大一号——数字即信息主角,非同字号色温微差) + var body := "%s [color=#d9402a][size=17]-%d[/size][/color]%s%s%s%s" % [name_, v, roll_txt, tail, kill, suffix] var body_col := "#e07050" if row.get("killed", false) else ("#9a8a72" if v == 0 else "#e0a050") if bool(row.get("crit", false)): return prefix + _gold_frame(body) # nat 最大面暴击 = 金框高亮行 diff --git a/game/presentation/views/confirm_dialog.gd b/game/presentation/views/confirm_dialog.gd new file mode 100644 index 00000000..06ad1dc8 --- /dev/null +++ b/game/presentation/views/confirm_dialog.gd @@ -0,0 +1,71 @@ +# VERTICAL SLICE - NOT FOR PRODUCTION +# Date: 2026-09-29(迭代 8 二轮修改批 A——新冒险互斥确认弹窗化,用户目检裁决: +# 按钮文字换面 → 实底确认弹窗「开新冒险 + 取消」) +# 通用确认弹窗(实底石板皮面板,与营地/设置/图例同族视觉):open(text, confirm_label, cancel_label) +# 双钮外发 confirmed/cancelled——语义装配归调用方,本类零业务触碰(纯视图边界规范 1.3)。 +class_name ConfirmDialog +extends Control + +signal confirmed +signal cancelled + +var _text: Label +var _confirm_btn: Button +var _cancel_btn: Button + + +func _init() -> void: + var dim := ColorRect.new() + dim.color = Color(UiKit.DIM, 0.55) + dim.set_anchors_preset(Control.PRESET_FULL_RECT) + add_child(dim) + var center := CenterContainer.new() + center.set_anchors_preset(Control.PRESET_FULL_RECT) + dim.add_child(center) + var panel := PanelContainer.new() + panel.add_theme_stylebox_override("panel", UiKit.panel_skinned(Vector4(14, 12, 14, 12))) + center.add_child(panel) + var box := VBoxContainer.new() + box.custom_minimum_size = Vector2(460, 0) + box.add_theme_constant_override("separation", 12) + panel.add_child(box) + _text = UiKit.label("", 16, false) + _text.add_theme_color_override("font_color", UiKit.INK_TITLE) + _text.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + _text.custom_minimum_size = Vector2(430, 0) + box.add_child(_text) + box.add_child(HSeparator.new()) + var btn_row := HBoxContainer.new() + btn_row.add_theme_constant_override("separation", 12) + btn_row.alignment = BoxContainer.ALIGNMENT_CENTER + box.add_child(btn_row) + _cancel_btn = UiKit.button("取消") + _cancel_btn.pressed.connect(_emit_cancelled) + btn_row.add_child(_cancel_btn) + _confirm_btn = UiKit.button("确认") + _confirm_btn.pressed.connect(_emit_confirmed) + btn_row.add_child(_confirm_btn) + visible = false + + +func open(text: String, confirm_label: String, cancel_label: String = "取消") -> void: + _text.text = text + _confirm_btn.text = confirm_label + _cancel_btn.text = cancel_label + visible = true + + +func close() -> void: + visible = false + + +func is_open() -> bool: + return visible + + +func _emit_confirmed() -> void: + confirmed.emit() + + +func _emit_cancelled() -> void: + cancelled.emit() diff --git a/game/presentation/views/confirm_dialog.gd.uid b/game/presentation/views/confirm_dialog.gd.uid new file mode 100644 index 00000000..72fb0746 --- /dev/null +++ b/game/presentation/views/confirm_dialog.gd.uid @@ -0,0 +1 @@ +uid://dhe557khvoq3y diff --git a/game/presentation/views/settings_overlay.gd b/game/presentation/views/settings_overlay.gd index 6301eb59..c42d08f9 100644 --- a/game/presentation/views/settings_overlay.gd +++ b/game/presentation/views/settings_overlay.gd @@ -1,6 +1,6 @@ # VERTICAL SLICE - NOT FOR PRODUCTION # Date: 2026-09-29(迭代 8 卡④ 最小设置面) -# 设置覆盖层(纯视图边界规范 1.3 同款):音量滑杆/全屏/减动效三件+退出游戏+返回。 +# 设置覆盖层(纯视图边界规范 1.3 同款):音量滑杆/全屏/减动效三件+返回(退出游戏已提级开始菜单)。 # 生效与持久化全经 SettingsStore 单点(实时应用+即改即存);本类零核心触碰。 # 减动效转正承载(测试调整板旧开关退役——设置面唯一入口,卡④ AC③)。 # 探针接缝:retarget(store) = 隔离存档换注口(同 camp 换注先例);控件引用公有只读供断言。 @@ -12,8 +12,8 @@ signal closed var _store var _volume: HSlider var _volume_val: Label -var _fs_btn: CheckButton -var _motion_btn: CheckButton +var _fs_btn: Button +var _motion_btn: Button func _init(store) -> void: @@ -65,22 +65,21 @@ func _build() -> void: _volume_val.custom_minimum_size = Vector2(60, 0) vol_row.add_child(_volume_val) - # 全屏(art-bible 1920 档缩放策略冲突条目挂起注记——现行视口恒等拉伸,全屏切换即窗口模式切换) - _fs_btn = CheckButton.new() - _fs_btn.text = "全屏" + # 开关形态(二轮修改批 D2 2026-09-29 用户目检——原生 CheckButton 暗底不显眼, + # 换 UiKit 按钮形态 toggle:文字「全屏:开/关」随态换面) + _fs_btn = UiKit.button("全屏:关") + _fs_btn.toggle_mode = true _fs_btn.toggled.connect(_on_fullscreen_toggled) box.add_child(_fs_btn) # 减动效(battle.md §Transitions 承诺转正——时长减半,结算逐行兑现=信息载体不整体跳过) - _motion_btn = CheckButton.new() - _motion_btn.text = "减动效(动画时长减半——运动敏感/演示用)" + _motion_btn = UiKit.button("减动效:关(动画时长减半——运动敏感/演示用)") + _motion_btn.toggle_mode = true _motion_btn.toggled.connect(_on_motion_toggled) box.add_child(_motion_btn) box.add_child(HSeparator.new()) - var quit_btn := UiKit.button("退出游戏") - quit_btn.pressed.connect(_on_quit_pressed) - box.add_child(quit_btn) + # 退出游戏已提级开始菜单(D1——设置面不再承载,用户裁决「退出只在一级菜单出现」) var close_btn := UiKit.button("返回") close_btn.pressed.connect(_emit_closed) box.add_child(close_btn) @@ -90,7 +89,9 @@ func _sync() -> void: _volume.set_value_no_signal(roundf(_store.volume_linear * 100.0)) _volume_val.text = "%d%%" % roundi(_store.volume_linear * 100.0) _fs_btn.set_pressed_no_signal(_store.fullscreen) + _fs_btn.text = "全屏:%s" % ("开" if _store.fullscreen else "关") _motion_btn.set_pressed_no_signal(_store.reduced_motion) + _motion_btn.text = "减动效:%s(动画时长减半——运动敏感/演示用)" % ("开" if _store.reduced_motion else "关") func _on_volume_changed(v: float) -> void: @@ -112,10 +113,5 @@ func _on_motion_toggled(on: bool) -> void: _store.save_settings() -func _on_quit_pressed() -> void: - _store.save_settings() - get_tree().quit() - - func _emit_closed() -> void: closed.emit() diff --git a/game/smoke/ui_click_probe.gd b/game/smoke/ui_click_probe.gd index 075c0c50..44f6fd69 100644 --- a/game/smoke/ui_click_probe.gd +++ b/game/smoke/ui_click_probe.gd @@ -100,7 +100,7 @@ func _ready() -> void: _problems.append("设置面未打开(点击路径)") else: # 减动效转正双态(卡④ AC③):开=0.5 引擎态即时生效+落盘持久;关=1.0 回常态 - var motion_btn: CheckButton = _find_checkbutton_by_text(main.settings_overlay, "减动效") + var motion_btn: Button = _find_button_by_text(main.settings_overlay, "减动效") # 二轮 D2——开关已换 UiKit toggle 按钮形态 if motion_btn == null: _problems.append("设置面减动效开关缺失") else: @@ -142,6 +142,11 @@ func _ready() -> void: var legacy_motion = _find_checkbutton_by_text(main.tune_panel, "减动效") if legacy_motion != null: _problems.append("调整板减动效旧入口未退役(设置面唯一入口失守——卡④ AC③)") + # D1 断言(二轮):退出游戏只在一级菜单——开始菜单有、设置面无 + if _find_button_by_prefix(main._start_overlay, "退出游戏") == null: + _problems.append("开始菜单缺退出游戏按钮(二轮 D1)") + if _find_button_by_prefix(main.settings_overlay, "退出游戏") != null: + _problems.append("设置面仍含退出游戏按钮(二轮 D1——只在一级菜单出现)") # 换回全新零档(营地购买态不泄入后续冒险段——确定性;档案残留下次段首清理兜底) for p0 in ["user://smoke_click_probe_reset.json", "user://smoke_click_probe_reset.json.tmp"]: if FileAccess.file_exists(p0): @@ -321,7 +326,7 @@ func _ready() -> void: _problems.append("断点段:续玩候选与中断前不一致(停点重建失准)") if not iso_chk.load_from_disk() or not iso_chk.has_breakpoint(): _problems.append("断点段:续玩误清断点(只在三结局清)") - # ③ 互斥二次确认(卡②③):断点在档时开新冒险首击=确认态、二击才提交 + # ③ 互斥确认弹窗(二轮修改批 A):断点在档点开始冒险 → 弹确认框;取消=回菜单;开新冒险=提交 main._exit_level_screen() main._start_overlay.visible = true main.adventure = null @@ -329,13 +334,27 @@ func _ready() -> void: await get_tree().process_frame lv_btn.pressed.emit() await get_tree().process_frame - if not main._start_overlay.visible or main.adventure != null: - _problems.append("断点段:有断点首击即开新冒险(互斥二次确认失守卡②③)") + if not main.confirm_dialog.is_open() or main.adventure != null: + _problems.append("断点段:有断点点开始冒险未弹确认框(二轮 A 失守)") + var cancel_btn: Button = _find_button_by_text(main.confirm_dialog, "取消") + if cancel_btn == null: + _problems.append("断点段:确认框取消按钮缺失") + else: + cancel_btn.pressed.emit() + await get_tree().process_frame + if main.confirm_dialog.is_open() or main.adventure != null or not main._start_overlay.visible: + _problems.append("断点段:取消未回开始菜单(二轮 A)") lv_btn.pressed.emit() await get_tree().process_frame - await get_tree().process_frame - if main.adventure == null or main._start_overlay.visible: - _problems.append("断点段:二次确认后未开新冒险") + var confirm_btn: Button = _find_button_by_text(main.confirm_dialog, "开新冒险") + if confirm_btn == null: + _problems.append("断点段:确认框开新冒险按钮缺失") + else: + confirm_btn.pressed.emit() + await get_tree().process_frame + await get_tree().process_frame + if main.adventure == null or main._start_overlay.visible or main.confirm_dialog.is_open(): + _problems.append("断点段:确认开新冒险后未开局(二轮 A)") print("[click_probe] 循环退出(done=%s budget=%s)问题=%d" % [ str(_done), str(action_budget), _problems.size()]) for p in _problems: @@ -526,6 +545,16 @@ func _find_button_by_prefix(root: Node, prefix: String) -> Button: return null +func _find_button_by_text(root: Node, needle: String) -> Button: + if root is Button and String(root.text).contains(needle): + return root + for child in root.get_children(): + var found := _find_button_by_text(child, needle) + if found != null: + return found + return null + + func _find_checkbutton_by_text(root: Node, needle: String) -> CheckButton: if root is CheckButton and String(root.text).contains(needle): return root diff --git a/game/tests/unit/presentation/pacing_queue_test.gd.uid b/game/tests/unit/presentation/pacing_queue_test.gd.uid new file mode 100644 index 00000000..6b0380fb --- /dev/null +++ b/game/tests/unit/presentation/pacing_queue_test.gd.uid @@ -0,0 +1 @@ +uid://fjdvajc5ek85