diff --git a/game/presentation/camp_flow_context.gd b/game/presentation/camp_flow_context.gd index 3a986547..e1f384f8 100644 --- a/game/presentation/camp_flow_context.gd +++ b/game/presentation/camp_flow_context.gd @@ -16,13 +16,12 @@ var runner_get: Callable # () -> LevelRunner var open_modal: Callable var close_modal: Callable var refresh_presentations: Callable -var refresh_resume_button: Callable func _init(db_ref, meta_get_cb: Callable, save_get_cb: Callable, camp_overlay_ref: CampOverlay, shop_overlay_ref: ShopOverlay, toast_ref: ToastStack, start_overlay_ref: Control, logger_ref, runner_get_cb: Callable, open_modal_cb: Callable, close_modal_cb: Callable, - refresh_presentations_cb: Callable, refresh_resume_button_cb: Callable) -> void: + refresh_presentations_cb: Callable) -> void: db = db_ref meta_get = meta_get_cb save_get = save_get_cb @@ -35,4 +34,3 @@ func _init(db_ref, meta_get_cb: Callable, save_get_cb: Callable, camp_overlay_re open_modal = open_modal_cb close_modal = close_modal_cb refresh_presentations = refresh_presentations_cb - refresh_resume_button = refresh_resume_button_cb diff --git a/game/presentation/camp_shop_presenter.gd b/game/presentation/camp_shop_presenter.gd index d79155ee..86fcc5ec 100644 --- a/game/presentation/camp_shop_presenter.gd +++ b/game/presentation/camp_shop_presenter.gd @@ -1,6 +1,6 @@ ## VERTICAL SLICE - NOT FOR PRODUCTION ## 营地/商店投影控制器:营地开合/快照投影/锻造·同调·天赋树·Fork 购买切换 + 商店房购买/离开(pool 与 product 双面)。 -## 信号连接只归组合根(campbtn/camp_overlay/shop_overlay 信号接至本类 on_* 入口);模态栈/呈现刷新/续玩刷新与 runner 现值经组合根回调注入。 +## 信号连接只归组合根(camp_overlay/shop_overlay 信号接至本类 on_* 入口);模态栈/呈现刷新与 runner 现值经组合根回调注入(续玩刷新已随主菜单三入口化退役——卡② mn7)。 ## 写调用唯一经 MetaProgression 购买原语与 LevelRunner.buy_*——本类零直写,拒绝兜底 toast 保留原地。 extends RefCounted @@ -16,7 +16,6 @@ var _runner_get: Callable # () → 关卡推进器现值(组合根 la var _open_modal: Callable var _close_modal: Callable var _refresh_presentations: Callable -var _refresh_resume_button: Callable func _init(ctx: CampFlowContext) -> void: @@ -33,7 +32,6 @@ func _init(ctx: CampFlowContext) -> void: _open_modal = ctx.open_modal _close_modal = ctx.close_modal _refresh_presentations = ctx.refresh_presentations - _refresh_resume_button = ctx.refresh_resume_button # 打开营地(主菜单入口——开合与开始层互斥仿设置面先例:开始覆盖层非模态且树序在后, @@ -52,7 +50,6 @@ func open_camp() -> void: func on_camp_closed() -> void: _close_modal.call(camp_overlay) # 模态栈单口 start_overlay.visible = true - _refresh_resume_button.call() # 回菜单按断点在档态刷新 _refresh_presentations.call() # 关闭后刷新顶栏(工坊解锁可能变了余额侧读数面) diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 444e46d8..2ea655c9 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -67,8 +67,7 @@ var meta: MetaProgressionCore: var logger = null # 试玩日志器(JSONL 持久化 + 卡死诊断转储——呈现层组件) var _debug_entry := false # 调试会话门控(写死=true——恒开:启动即开浮窗+两屏角标点亮;_ready 赋值) var _seed_edit: LineEdit -var _resume_btn: Button # 继续上局按钮句柄(有断点才亮) -var confirm_dialog: ConfirmDialog # 通用确认弹窗(新冒险互斥确认弹窗化) +var confirm_dialog: ConfirmDialog # 通用确认弹窗(消费方编排——现行消费方=选档删档确认;新冒险互斥确认已随卡②主菜单撤出,归卡④大门) var _confirm_consumer := "" # 确认弹窗消费方状态量(契约 3 编排——open 前设、分派后清) var _confirm_consumer_slot := 0 # 消费方附随槽号(delete_slot 消费方专用) var curio_overlay: CurioPickOverlay # 提升三选一覆盖层(level-clear.md v2 选择面承载;奖励节点共用) @@ -195,7 +194,6 @@ func _ready() -> void: _debug.battle_ready.connect(_on_debug_battle_ready) _build_ui() _begin_session(false) # 闲时会话(gp_ledger 等探针注入面自 _ready 起恒在——ui_probe 合成段时序) - _refresh_resume_button() # 启动读盘后按断点在档态点亮「继续上局」 _start_overlay.visible = true _debug_entry = true # 调试入口门控写死=true(不读 DS_DEBUG 环境变量;发布前清) debug_badge.visible = _debug_entry # 浮层角标可见性须门控落定后赋值(构建/连接在 _build_ui) @@ -315,10 +313,9 @@ func _build_ui() -> void: curio_overlay.continue_pressed.connect(_bonus.on_continue) curio_overlay.refresh_requested.connect(_bonus.on_refresh) - # 菜单屏接线(句柄经屏壳暴露) - _menu_screen.lvbtn.pressed.connect(_start_adventure_from_menu) - _menu_screen.resumebtn.pressed.connect(_resume_from_menu) # 续玩入口 - _resume_btn = _menu_screen.resumebtn + # 菜单屏接线(句柄经屏壳暴露;三入口化——进入存档/设置/退出游戏,卡② mn3: + # 进入存档钮落点=卡③选档层 _open_save_select,点击链在此闭环) + _menu_screen.entersavebtn.pressed.connect(_open_save_select) # 调试入口=UI 最顶层全局浮层角标(任何 UI 不遮挡——菜单/战斗/地图三枚 # 屏内角标由全局浮层承载;连接归组合根,树序/登记面见 LAYER_CONTRACT 末位) debug_badge = UiAssembler.build_debug_badge(self) @@ -328,7 +325,7 @@ func _build_ui() -> void: # 购买写调用唯一经 MetaProgression/LevelRunner core 单口——组合根 lambda 现值注入) _camp_pres = CampShopPresenter.new(CampFlowContext.new(db, func() -> Variant: return meta, func() -> Variant: return save_manager, camp_overlay, shop_overlay, board_zone.toast, _start_overlay, logger, func() -> Variant: return run_scope.runner, - _open_modal, _close_modal, _refresh_presentations, _refresh_resume_button)) # 构造参数收拢 context + _open_modal, _close_modal, _refresh_presentations)) # 构造参数收拢 context shop_overlay.buy_requested.connect(_camp_pres.on_shop_buy) shop_overlay.refresh_requested.connect(_camp_pres.on_shop_refresh) # MC-1 shop_overlay.leave_pressed.connect(_camp_pres.on_shop_leave) @@ -340,8 +337,6 @@ func _build_ui() -> void: camp_overlay.forge_unlock_requested.connect(_camp_pres.on_camp_forge_unlock) camp_overlay.forge_select_requested.connect(_camp_pres.on_camp_forge_select) camp_overlay.closed.connect(_camp_pres.on_camp_closed) - _menu_screen.campbtn.pressed.connect(_camp_pres.open_camp) - _menu_screen.careerbtn.pressed.connect(_open_career) # 生涯页一级入口(营地同组) _menu_screen.settingsbtn.pressed.connect(_open_settings) # 最小设置面入口 _menu_screen.quitbtn.pressed.connect(_quit_game) # 退出游戏提级一级菜单 @@ -488,32 +483,23 @@ func _input_locked() -> bool: # ==================== 冒险模式入口(唯一试玩入口)==================== func _start_adventure_from_menu() -> void: - # 互斥确认弹窗化:有断点时开新冒险弹实底确认框, - # 「开新冒险」提交(弃旧断点+写盘)/「取消」回菜单 - if save_manager.has_breakpoint(): - var bp: Dictionary[Variant, Variant] = save_manager.breakpoint_snapshot() - _confirm_consumer = "new_adventure" # 消费方编排(契约 3——open 前设消费方) - confirm_dialog.open("已有未完冒险(第 %d 关)——开新冒险将覆盖旧断点" % maxi(1, int(bp.get("level_index", 1))), - "开新冒险", "取消") - return + # 主菜单「开始冒险」钮已随三入口化撤出(卡② mn6——主菜单侧断点互斥确认链同步摘除, + # 弹窗本体与两分支逻辑归卡④大门承接)。本函数为探针直调中间态保留(mn10——冒险段探针 + # 直调位点逐个登记实现报告「已知中间态」;卡④到位后由大门两分支换代)。无激活态写盘拒绝 + # 由卡① sv3 承接(结算不落盘)。 _start_overlay.visible = false _start_adventure() -# 确认弹窗分派(卡③ 契约 3):按 _confirm_consumer 路由——"new_adventure"=现役新冒险确认 -#(行为与原 _on_confirm_new_adventure 逐句等价)、"delete_slot"=卡③ 删档确认(确认才删、 -# 取消只关窗——裁决 3);分派面禁写死单一目标(卡④ 到位接入第三消费方不返工)。 +# 确认弹窗分派(卡③ 契约 3):按 _confirm_consumer 路由——"delete_slot"=卡③ 删档确认 +#(确认才删、取消只关窗——裁决 3)。"new_adventure" 消费方已随卡② 主菜单确认链摘除退役 +#(mn6/V5——主菜单零确认残留),分派机制保留(卡④ 到位接入第三消费方不返工)。 func _on_confirm_accepted() -> void: confirm_dialog.close() match _confirm_consumer: "delete_slot": save_manager.delete_slot(_confirm_consumer_slot) save_select_overlay.open_save_select(_save_select_data()) # 就地刷新(该槽回空态) - "new_adventure": - save_manager.clear_breakpoint() # 提交即弃旧断点(覆盖语义对盘面即时生效) - save_manager.write_to_disk() - _start_overlay.visible = false - _start_adventure() _confirm_consumer = "" @@ -529,17 +515,6 @@ func _quit_game() -> void: get_tree().quit() -# 继续上局按钮可见性 = 断点在档态(启动读盘后与每次回开始菜单刷新) -func _refresh_resume_button() -> void: - if _resume_btn == null: - return - var has: bool = save_manager.has_breakpoint() - _resume_btn.visible = has - if has: - var bp: Dictionary[Variant, Variant] = save_manager.breakpoint_snapshot() - _resume_btn.text = "▶ 继续上局(第 %d 关 · 自最近停点恢复)" % maxi(1, int(bp.get("level_index", 1))) - - # 继续上局(resume_adventure 的消费入口):断点重建至最近停点 func _resume_from_menu() -> void: if not save_manager.has_breakpoint(): @@ -727,7 +702,6 @@ func _on_battle_end_continue() -> void: _begin_session(false) debug_overlay.reset_sliders() _start_overlay.visible = true - _refresh_resume_button() # 回菜单按断点在档态刷新(收束后已清=隐藏) _refresh_presentations() @@ -1086,7 +1060,6 @@ func _open_settings() -> void: func _on_settings_closed() -> void: _close_modal(settings_overlay) # 模态栈单口 _start_overlay.visible = true - _refresh_resume_button() # ---- 局内状态面板·只读(入口角标在两屏装配段;开合与数据喂入归组合根)---- @@ -1124,7 +1097,6 @@ func _open_career() -> void: func _on_career_closed() -> void: _close_modal(career_overlay) # 模态栈单口 _start_overlay.visible = true - _refresh_resume_button() # 生涯页快照投影(只读同源——每次开层实时读 SaveManager 计数访问器 + MetaProgression 访问器 @@ -1164,7 +1136,6 @@ func _open_save_select() -> void: func _on_save_select_closed() -> void: _close_modal(save_select_overlay) # 模态栈单口 _start_overlay.visible = true - _refresh_resume_button() # 槽数据快照(只读同源——每次开层实时经卡① 窥视 API 逐槽取数拼装;零写调用)。 diff --git a/game/presentation/scenes/menu_screen.gd b/game/presentation/scenes/menu_screen.gd index 64859195..81cda1a8 100644 --- a/game/presentation/scenes/menu_screen.gd +++ b/game/presentation/scenes/menu_screen.gd @@ -8,10 +8,7 @@ extends Control var start_overlay: Control # 全屏菜单层(原 main._start_overlay——组合根可见性开关句柄不变) var box: VBoxContainer -var lvbtn: Button -var resumebtn: Button -var campbtn: Button -var careerbtn: Button +var entersavebtn: Button var settingsbtn: Button var quitbtn: Button @@ -23,9 +20,6 @@ func _ready() -> void: var start: Dictionary[Variant, Variant] = UiAssembler.build_start_overlay(self, get_node("/root/ConfigDB")) start_overlay = start.overlay box = start.box - lvbtn = start.lvbtn - resumebtn = start.resumebtn - campbtn = start.campbtn - careerbtn = start.careerbtn + entersavebtn = start.entersavebtn settingsbtn = start.settingsbtn quitbtn = start.quitbtn diff --git a/game/presentation/ui_assembler.gd b/game/presentation/ui_assembler.gd index 44e3d50a..95366d4c 100644 --- a/game/presentation/ui_assembler.gd +++ b/game/presentation/ui_assembler.gd @@ -86,11 +86,11 @@ static func build_overlays(host: Control, db) -> Dictionary: } -# 开始覆盖层(返回 {overlay, box, lvbtn, resumebtn, campbtn, careerbtn, settingsbtn, quitbtn}——按钮接线归组合根; +# 开始覆盖层(返回 {overlay, box, entersavebtn, settingsbtn, quitbtn}——按钮接线归组合根; # 随机种子/数值调整板/两层调试三散件不在主按钮组,收进调试浮窗—— # 调试入口=UI 最顶层全局浮层角标(build_debug_badge,本层零调试入口) # 布局=双列 VBox(左操作 56% + 右舞台);按钮动词化+悬停解释 -#(解释全迁 tooltip,G5);lvbtn 为主操作钮(烛光黄 CANDLE);右列=立绘看台。**界面禁玩法文字墙 +#(解释全迁 tooltip,G5);entersavebtn 为主操作钮(烛光黄 CANDLE——主菜单三入口化唯一进度入口);右列=立绘看台。**界面禁玩法文字墙 #(教学内容归 Beta 帮助手册〔教学引导定稿 P4 丙〕)**;本层无玩法速览牌; # tooltip 主题归全局主题单点(禁在 root 挂程序化 tooltip 主题)。 # 返回键集如首行所列,接线归组合根) @@ -124,31 +124,15 @@ static func build_start_overlay(host: Control, db = null) -> Dictionary: box.add_child(title) box.add_child(HSeparator.new()) - # 组帽·冒险 - box.add_child(UiKit.label("── 冒 险 ──",UiKit.FS_CAPTION, false, UiKit.WARM_FAINT)) - # 唯一试玩入口 = 正式「开始冒险」(三关序列);无单关测试直入口 - var resumebtn := UiKit.primary_button("▶ 继续上局") # 有断点才亮(可见性与关数文案归组合根刷新);主操作钮对齐 demo - resumebtn.tooltip_text = "自最近停点恢复" - resumebtn.visible = false - box.add_child(resumebtn) - # 主操作语义对齐 demo(有档时「继续上局」才是首动作=主黄钮, - # 「开始冒险」=常规石钮;tooltip 保留) - var lvbtn := UiKit.button("▶ 开始冒险") - lvbtn.tooltip_text = "三关连探:地精洞窟 → 幽暗地域 → 灰矮人矿城" - box.add_child(lvbtn) - - # 组帽·营地与系统 - box.add_child(UiKit.label("── 营地与系统 ──",UiKit.FS_CAPTION, false, UiKit.WARM_FAINT)) - var campbtn := UiKit.verb_button("营地", UiKit.TEX_ICON_KEYSTONE, "永久强化:钥石换永久属性——跨局成长账本") # 主菜单营地入口 - box.add_child(campbtn) - # 生涯页一级入口(与营地平级;页=局外只读跨局汇总,零跳转零购买) - var careerbtn := UiKit.button("生涯") - careerbtn.tooltip_text = "跨局生涯汇总:总局数 / 通关局数 / Boss 击杀 / 钥石余额 / 三柜台解锁概览(只读)" - box.add_child(careerbtn) - var settingsbtn := UiKit.button("设置") # 最小设置面入口 + # 三入口(主菜单三入口化——营地交互重做卡②,用户裁决 1:进入存档/设置/退出游戏; + # 组帽双撤——冒险组帽组内零钮退化态不留、「营地与系统」帽指称失义,追认单留痕) + var entersavebtn := UiKit.primary_button("进入存档") # 主操作钮(唯一进度入口) + entersavebtn.tooltip_text = "选择一个存档位进入(新档 / 进档 / 删档)" + box.add_child(entersavebtn) + var settingsbtn := UiKit.button("设置") # 最小设置面入口(行为零变化——契约 2) settingsbtn.tooltip_text = "音量 / 全屏 / 减动效" box.add_child(settingsbtn) - # 退出游戏与设置同层并行——只在一级菜单出现 + # 退出游戏与设置同层并行——只在一级菜单出现(行为零变化——契约 2) var quitbtn := UiKit.button("退出游戏") box.add_child(quitbtn) @@ -190,8 +174,7 @@ static func build_start_overlay(host: Control, db = null) -> Dictionary: right.add_child(stage) return {"overlay": overlay, "box": box, - "lvbtn": lvbtn, "resumebtn": resumebtn, - "campbtn": campbtn, "careerbtn": careerbtn, + "entersavebtn": entersavebtn, "settingsbtn": settingsbtn, "quitbtn": quitbtn} diff --git a/game/smoke/fx_shot_probe.gd b/game/smoke/fx_shot_probe.gd index 37c74cd4..2f40d24a 100644 --- a/game/smoke/fx_shot_probe.gd +++ b/game/smoke/fx_shot_probe.gd @@ -28,23 +28,19 @@ func _ready() -> void: main = load("res://presentation/main.tscn").instantiate() add_child(main) await get_tree().process_frame - ProbeUiUtil.isolate_save(main, "fx_shot_probe", false, true, false) + ProbeUiUtil.isolate_save(main, "fx_shot_probe", false, true, true) # 末参=swap_save(卡②签名换代——原 refresh_resume 槽位语义退役) # 隔离档即时应用(真实档 fullscreen 态会污染抓帧尺寸〔shot_probe 同款口径〕) main.settings_store.apply() for i in 8: await get_tree().process_frame # UI 装配完成等足帧(canvas_item 空引用窗口) - var lvbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "开始冒险", "contains") - if lvbtn == null: - _fail("开始冒险按钮缺失") - return - lvbtn.pressed.emit() + main._start_adventure_from_menu() # 函数直调中间态(卡② mn10——开始冒险钮已撤) # 等关卡画面出图(choices_presented 异步到达——最多 3s 轮询) for i in 60: await get_tree().process_frame if main.level_screen != null and main.level_screen.visible: break if main.level_screen == null or not main.level_screen.visible: - _fail("关卡画面未出现(lvbtn 路径)") + _fail("关卡画面未出现(函数直调中间态路径)") return for i in 5: await get_tree().process_frame diff --git a/game/smoke/probe_ui_util.gd b/game/smoke/probe_ui_util.gd index ac62c948..f6382886 100644 --- a/game/smoke/probe_ui_util.gd +++ b/game/smoke/probe_ui_util.gd @@ -89,14 +89,14 @@ static func _text_match(text: String, needle: String, mode: String) -> bool: # ── 存档/设置/元进度三面隔离(探针禁碰真实档 user://save.json——SaveManager 路径注入纪律) ── # tag = 隔离档名(不带扩展名,如 "smoke_input_probe_save"——各探针后缀保持不变);先删后建防陈旧档泄入。 # mark_first_done = 对齐真实档游玩史口径(模板抽签路径不变);with_settings = 连设置面换注 -#(隔离 cfg 名 = _settings.cfg,与被收口探针原文件名逐一同名);refresh_resume = 组合根 +#(隔离 cfg 名 = _settings.cfg,与被收口探针原文件名逐一同名); # 「继续上局」按钮重刷;swap_save = 是否换注全局 SaveManager(营地先行段只换 meta 侧不换 SaveManager)。 # 返回隔离 SaveManager 实例(调用方常需后续直读余额/重读档)。 # SaveManager/MetaProgression 是 autoload 单例——隔离面走单例原地重置 #(attach_path 沿 RngService.set_master_seed 先例;MetaProgression 持 save 引用透传, # swap_save=false 分支换 meta 侧引用=独立 iso 实例,只换 meta 不换 save_manager)。 static func isolate_save(main, tag: String, mark_first_done: bool = false, with_settings: bool = false, - refresh_resume: bool = false, swap_save: bool = true): + swap_save: bool = true): # refresh_resume 参数已随卡② _refresh_resume_button 删除退役(mn7) var save_path := "user://%s.json" % tag for p in [save_path, save_path + ".tmp", "user://%s_settings.cfg" % tag]: if FileAccess.file_exists(p): @@ -116,6 +116,22 @@ static func isolate_save(main, tag: String, mark_first_done: bool = false, with_ main.get_node("/root/MetaProgression").save = iso # 只换 meta 侧(营地段语义)——SaveManager 全局不动 if with_settings: main.settings_store = SettingsStoreScript.new("user://%s_settings.cfg" % tag) - if refresh_resume: - main._refresh_resume_button() return iso + +# 营地经进档开层(卡② mn9 新路径——campbtn 已撤;槽模板注入隔离命名空间,防碰真实槽 +# user://save_slot_N.json——真实档红线)。创建=激活=暂行开营地(卡③ Q3 乙落点); +# 驱动后写盘目标=隔离槽文件(持久化断言读 user://_slot_1.json)。返回 false=新档钮缺失(驱动中断)。 +static func open_camp_via_savesel(main, tag: String) -> bool: + var sm = main.get_node("/root/SaveManager") + sm.attach_slot_template("user://%s_slot_%%d.json" % tag) + for i in range(1, 4): # 夹具清场——空槽起(陈旧槽文件会翻有档态、新档钮缺席) + for suf in ["", ".tmp", ".corrupt.bak"]: + var sp: String = sm.slot_path(i) + suf + if FileAccess.file_exists(sp): + DirAccess.remove_absolute(sp) + main._open_save_select() + var new_btn: Button = main.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 + return true diff --git a/game/smoke/screen_flow_probe.gd b/game/smoke/screen_flow_probe.gd index 1712f59d..ba4dda94 100644 --- a/game/smoke/screen_flow_probe.gd +++ b/game/smoke/screen_flow_probe.gd @@ -21,7 +21,7 @@ func _ready() -> void: await get_tree().process_frame # 隔离存档(全 worktree 共享同一份 user://save.json——探针必须换注,禁碰真实档) - ProbeUiUtil.isolate_save(main, "smoke_screen_flow", false, false, false, false) + ProbeUiUtil.isolate_save(main, "smoke_screen_flow", false, false, false) await get_tree().process_frame await _step_boot_menu() @@ -107,11 +107,10 @@ func _step_boot_menu() -> void: # ---- ② 菜单 → 营地(真实按钮信号路径)---- func _step_menu_to_camp() -> void: - var campbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "营地", "prefix") - if campbtn == null: - _problems.append("②菜单→营地:营地按钮缺失") + # 营地经进档开层(卡② mn9 新路径——campbtn 已撤;槽模板注入隔离命名空间) + if not ProbeUiUtil.open_camp_via_savesel(main, "smoke_screen_flow_camp"): + _problems.append("②菜单→营地:选档层新档钮缺失(新路径驱动中断)") return - campbtn.pressed.emit() await _frames() _expect_exclusive("②菜单→营地", "营地") @@ -123,6 +122,8 @@ func _step_camp_back_to_menu() -> void: return main.camp_overlay.closed.emit() await _frames() + main._on_save_select_closed() # 关闭被栈还原的选档层(动线序中间态——卡④ 换代点) + await _frames() _expect_exclusive("③营地→菜单(回退)", "菜单") @@ -130,9 +131,13 @@ func _step_camp_back_to_menu() -> void: # ---- 选档界面开合(卡③ sl10):函数直调开层(「进入存档」钮归卡② mn3)→屏级互斥采样→ # 返回钮真实信号路径关闭→ESC 语境不响应(卡⑤ 契约 2 范围外——零实现即断言)。---- func _step_save_select_open_back() -> void: - ProbeUiUtil.isolate_save(main, "smoke_screen_flow_savesel", false, false, false, true) + ProbeUiUtil.isolate_save(main, "smoke_screen_flow_savesel", false, false, true) main.save_manager.attach_slot_template("user://smoke_screen_flow_savesel_slot_%d.json") - main._open_save_select() + var esb: Button = ProbeUiUtil.find_button(main._start_overlay, "进入存档", "prefix") + if esb == null: + _problems.append("[savesel] mn3 进入存档钮缺失(卡② 交付物)") + return + esb.pressed.emit() # 真实按钮信号路径 → _open_save_select(卡② mn3 点击链——卡③ 直调中间态就此闭环) await get_tree().process_frame var seals := _screen_seals() if not main.save_select_overlay.visible or not seals_check_single(seals): @@ -172,13 +177,8 @@ func seals_check_single(f: Dictionary) -> bool: func _step_menu_to_adventure() -> void: - var lvbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "开始冒险", "contains") - if lvbtn == null: - lvbtn = ProbeUiUtil.find_button(main._start_overlay, "冒险", "contains") - if lvbtn == null: - _problems.append("④菜单→冒险:开始冒险按钮缺失") - return - lvbtn.pressed.emit() + # 开始冒险钮已随三入口化撤出——函数直调中间态(卡② mn10;卡④ 大门两分支换代) + main._start_adventure_from_menu() await _frames(3) _expect_exclusive("④菜单→冒险", "冒险") _expect_adventure_seal("④菜单→冒险", true) diff --git a/game/smoke/shot_probe.gd b/game/smoke/shot_probe.gd index 8abd3cd8..a098692b 100644 --- a/game/smoke/shot_probe.gd +++ b/game/smoke/shot_probe.gd @@ -5,7 +5,7 @@ # Godot_console.exe --path . res://smoke/shot_probe.tscn -- <输出目录> # 流程 = 真实按钮信号路径驱动(ui_click_probe 同款;存档/设置/元进度三面换注隔离档——禁碰真实档, # 坑册「断点写盘随 save_manager 引用走」教训同款覆盖):菜单(启动态)→ 设置面(settingsbtn -# 真实信号路径)→ 生涯页(careerbtn 同款)→ 营地(campbtn)→ 关卡画面(lvbtn→停点出图)→ +# 真实信号路径)→ 生涯页(函数直调中间态——卡② mn10)→ 营地(进档开层新路径——mn9)→ 关卡画面(函数直调中间态→停点出图)→ # 战斗(level_screen.press_candidate(0) 真实选路提交)→ 状态面板(地图屏状态角标真实信号路径)。 # 每屏双帧 + frame_post_draw 同步后抓 get_viewport().get_texture(),PNG 落输出目录; # 最小自检 = 七件存在且非零字节(像素方差断言归 tools/shot_checker.py——B 档骨架分工)。 @@ -100,16 +100,13 @@ func _ready() -> void: else: set_close.pressed.emit() await get_tree().process_frame - # ①c 生涯页(careerbtn 真实信号路径,ui_struct_probe 同款入口链) - var careerbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "生涯", "prefix") - if careerbtn == null: - _problems.append("生涯入口缺失(开始层 careerbtn)") - else: - careerbtn.pressed.emit() + # ①c 生涯页(函数直调中间态——卡② mn10:careerbtn 已撤,卡④ 笔记本改挂前无 UI 路径) + if true: + main._open_career() for i in 3: await get_tree().process_frame if not main.career_overlay.visible or main._start_overlay.visible: - _problems.append("生涯页未打开或开始层未隐藏(careerbtn 路径)") + _problems.append("生涯页未打开或开始层未隐藏(函数直调中间态路径)") else: _meta["career_title"] = ProbeUiUtil.label_rect(main.career_overlay, "生 涯") await _snap("career") @@ -122,26 +119,22 @@ func _ready() -> void: await get_tree().process_frame if main.career_overlay.visible or not main._start_overlay.visible: _problems.append("生涯页关闭后页未隐藏或开始层未恢复") - # ② 营地 - var campbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "营地", "contains") - if campbtn == null: - _problems.append("营地入口缺失(开始层 campbtn)") + # ② 营地(进档开层新路径——卡② mn9;槽模板注入隔离命名空间) + if not ProbeUiUtil.open_camp_via_savesel(main, "smoke_shot_probe_camp"): + _problems.append("营地入口缺失(选档层新档钮——新路径驱动中断)") else: - campbtn.pressed.emit() await get_tree().process_frame await _snap("camp") var close_btn: Button = ProbeUiUtil.find_button(main.camp_overlay, "关闭营地", "contains") if close_btn != null: close_btn.pressed.emit() await get_tree().process_frame - # ③ 关卡画面(开始冒险→停点出图——入口停点语义) - var lvbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "开始冒险", "contains") - if lvbtn == null: - _problems.append("开始冒险按钮缺失(开始层 lvbtn)") - else: - lvbtn.pressed.emit() - for i in 4: - await get_tree().process_frame + main._on_save_select_closed() # 关闭被栈还原的选档层(动线序中间态) + await get_tree().process_frame + # ③ 关卡画面(开始冒险→停点出图——入口停点语义;函数直调中间态 mn10) + main._start_adventure_from_menu() + for i in 4: + await get_tree().process_frame await _snap("level") # GP 常驻徽章断言(读数=runner.gp 现值,刷新=写路径回执后重查〔open_choices〕) var gp_label: Label = ProbeUiUtil.find_label(main.level_screen, "◈ ") @@ -190,7 +183,7 @@ func _ready() -> void: if main.status_panel.visible: _problems.append("状态面板关闭后未隐藏(继续钮路径)") else: - _problems.append("关卡画面未出现(lvbtn 路径)") + _problems.append("关卡画面未出现(函数直调中间态路径)") # ── 几何元数据落盘(shot_checker 取样坐标源) ── var mf := FileAccess.open(out_dir.path_join("shot_meta.json"), FileAccess.WRITE) if mf != null: diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index 14963756..220f74bb 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -159,8 +159,8 @@ func _smoke_scene_split() -> void: var menu: Node = load("res://presentation/scenes/menu_screen.tscn").instantiate() root.add_child(menu) await process_frame - check(menu.start_overlay != null and menu.lvbtn != null and menu.resumebtn != null and menu.campbtn != null and menu.careerbtn != null and menu.settingsbtn != null and menu.quitbtn != null, - "V2 菜单屏壳独立实例化(start_overlay+操作组六钮在位)") + check(menu.start_overlay != null and menu.entersavebtn != null and menu.settingsbtn != null and menu.quitbtn != null, + "V2 菜单屏壳独立实例化(start_overlay+操作组三钮在位——主菜单三入口化)〔卡② mn1〕") check(menu.start_overlay.visible, "V2 菜单层默认可见(非模态全屏态)") check(menu.mouse_filter == Control.MOUSE_FILTER_IGNORE, "V2 屏壳根零鼠标截获(IGNORE——全屏 STOP 壳吃输入病灶回归闸)") menu.queue_free() @@ -266,9 +266,8 @@ func _smoke_boot_chain() -> void: var sm = main.get_node("/root/SaveManager") # ④-1 boot 路径:启动态钉死(卡① sv2)——boot 无激活态、断点在档恒否,续玩钮恒隐 #(原「按钮态==真实档断点态」关系判据随启动态换代改写:test-conventions §9.5 申报在案,卡② mn8 再承接) - check(not sm.has_breakpoint() and not main._resume_btn.visible, - "V2d 续玩钮 boot 态恒隐(无激活态——启动态不隐式激活;断点在档=%s visible=%s)〔装配生命批 V2·卡① sv2 改写〕" % [ - str(sm.has_breakpoint()), str(main._resume_btn.visible)]) + check(not sm.has_breakpoint(), + "V2d 续玩钮族退役·boot 无断点在档(启动态不隐式激活——卡① sv2 钉死;恒隐判据随钮退役由本断言+V2d 三钮在位承接)〔装配生命批 V2·卡② mn8 改写〕") # ④-1b 启动态三断言+旧单档迁移不变式(卡① sv2/sv12——真实 autoload boot 链) check(sm.active_slot() == 0 and not sm.has_write_target(), "V2-s1 boot 后无激活态(active_slot=0、写盘目标空)〔卡① sv2〕") @@ -278,24 +277,18 @@ func _smoke_boot_chain() -> void: "V2-s3 boot 无激活写盘拒绝(write_to_disk=false 生产入口)〔卡① sv3〕") check(not (FileAccess.file_exists("user://save.json") and not FileAccess.file_exists(sm.slot_path(1))), "V2-s4 旧单档迁移不变式(旧档在则槽 1 必在——Q1 甲一次性迁移已发生)〔卡① sv12〕") - # ④-2 隔离新档双向(attach 原地重置——绝不碰真实档 user://save.json;刷新单口与 boot 路径同源) + # ④-2 三钮承接(卡② mn8——V2d 续玩钮族随钮退役废止:钮可见性联动面由卡① V2-s 启动态断言与 + # sv11 attach 语义段承接;attach 原地重置语义保持——绝不碰真实档 user://save.json) for p in ["user://smoke_boot_chain.json", "user://smoke_boot_chain.json.tmp"]: if FileAccess.file_exists(p): DirAccess.remove_absolute(p) sm.attach_path("user://smoke_boot_chain.json") - main._refresh_resume_button() - check(not sm.has_breakpoint() and not main._resume_btn.visible, - "V2d 续玩钮·无断点态隐藏(visible=%s has_breakpoint=%s)〔装配生命批 V2〕" % [ - str(main._resume_btn.visible), str(sm.has_breakpoint())]) - sm.set_breakpoint({"level_index": 2, "master_seed": 42}) - main._refresh_resume_button() - check(sm.has_breakpoint() and main._resume_btn.visible, - "V2d 续玩钮·有断点态可见(visible=%s has_breakpoint=%s)〔装配生命批 V2〕" % [ - str(main._resume_btn.visible), str(sm.has_breakpoint())]) - sm.clear_breakpoint() - main._refresh_resume_button() - check(not main._resume_btn.visible, - "V2d 续玩钮·清断点后回隐藏(visible=%s)〔装配生命批 V2〕" % str(main._resume_btn.visible)) + check(main._menu_screen.entersavebtn != null and main._menu_screen.settingsbtn != null + and main._menu_screen.quitbtn != null, + "V2d 三钮在位·进入存档/设置/退出游戏(续玩钮族退役后主菜单操作组承接)〔卡② mn1/mn8〕") + for old_btn in ["lvbtn", "resumebtn", "campbtn", "careerbtn"]: + check(not (old_btn in main._menu_screen), + "V2d 四旧钮句柄零命中·%s(彻底撤出——禁隐藏态禁置灰态残留)〔卡② mn2〕" % old_btn) # ⑤ 闲时会话(_ready 即建、永非 null、在树) check(main.run_scope != null and not main.run_scope.is_adventure and main.run_scope.is_inside_tree(), "V2e run_scope 非空且为闲时会话(is_adventure=false+is_inside_tree)〔装配生命批 V2〕") @@ -327,11 +320,10 @@ func _smoke_lifecycle_matrix() -> void: if FileAccess.file_exists(p): DirAccess.remove_absolute(p) sm.attach_path("user://smoke_lifecycle.json") - main._refresh_resume_button() # 隔离后重同步(boot 时按钮态反映的是真实档——T2 已断 boot 关系,此处按隔离档定基) main.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, "resume_vis": false}) + {"mf_start": true, "mf_inter": false, "mf_rows_empty": true, "start_vis": true}) # 阶段②开局停点(地图选路停点态) main._start_adventure_from_menu() for i in range(6): @@ -416,11 +408,18 @@ func _smoke_lifecycle_matrix() -> void: elif modal_name == "career": main._open_career() elif modal_name == "camp": - var campbtn: Button = main._menu_screen.campbtn - if campbtn == null: - check(false, "V3g 营地入口缺失(驱动中断)〔装配生命批 V3〕") + # 营地经进档开层(卡② mn9 新路径——campbtn 已撤);槽模板注入隔离命名空间 + #(防碰真实槽 user://save_slot_N.json——真实档红线),营地闭合后恢复生命周期 attach 写盘目标 + #(卡① sv3 无激活拒写——后续阶段断点写盘依赖) + sm.attach_slot_template("user://smoke_lifecycle_slot_%d.json") + main._open_save_select() + for i in range(2): + await process_frame + var ssel_new: Button = main.save_select_overlay._new_btns.get(1) + if ssel_new == null: + check(false, "V3g 营地入口缺失(选档层新档钮驱动中断)〔装配生命批 V3〕") continue - campbtn.pressed.emit() + ssel_new.pressed.emit() # 创建=激活=暂行开营地(卡③ Q3 乙落点) else: main._show_debug_overlay() for i in range(4): @@ -441,6 +440,15 @@ func _smoke_lifecycle_matrix() -> void: await process_frame await _stage_faces(main, "V3g 模态合·%s" % modal_name, "null", "null", "null", {"mf_start": true, "start_vis": true}) + if modal_name == "camp": + main._on_save_select_closed() # 关闭被栈还原的选档层(动线序中间态——卡④ 换代点) + for i in range(2): + await process_frame + sm.attach_path("user://smoke_lifecycle.json") # 恢复生命周期写盘目标 + for suf in ["", ".tmp"]: + var slot_p: String = "user://smoke_lifecycle_slot_1.json" + suf + if FileAccess.file_exists(slot_p): + DirAccess.remove_absolute(slot_p) # 阶段⑧两连局(第二局全新会话——旧局对象不复用、脏态零残留) var first_adv = main.run_scope.adventure main.debug_overlay.seed_edit.text = "43" @@ -475,8 +483,6 @@ func _smoke_lifecycle_matrix() -> void: "keystone_drops": [], "bonus_taken": {}, "bonus_pending": {"candidates": [{"kind": "heal", "tier": "common"}], "payload": {"gp_dropped": 0}}, }) - main._refresh_resume_button() - check(main._resume_btn.visible, "V3i 续玩钮·断点在档可见〔装配生命批 V3〕") var idle_scope = main.run_scope main._resume_from_menu() check(main.run_scope != idle_scope and main.run_scope.is_inside_tree(), @@ -570,9 +576,7 @@ func _stage_faces(main: Node, tag: String, adv: String, run: String, set: String "end_open": if main._end_overlay.is_open() != bool(extra[k]): bad += " _end_overlay.is_open 应=%s" % str(extra[k]) - "resume_vis": - if main._resume_btn.visible != bool(extra[k]): - bad += " _resume_btn.visible 应=%s" % str(extra[k]) + "start_vis": if main._start_overlay.visible != bool(extra[k]): bad += " _start_overlay.visible 应=%s" % str(extra[k]) diff --git a/game/smoke/t17_defer_probe.gd b/game/smoke/t17_defer_probe.gd index 96ff1383..49d74f99 100644 --- a/game/smoke/t17_defer_probe.gd +++ b/game/smoke/t17_defer_probe.gd @@ -20,17 +20,13 @@ func _ready() -> void: # 存档/设置两面隔离(ProbeUiUtil.isolate_save 现行事实源,先删后建防陈旧档泄入)—— # 隔离面走 attach_path 原地重置 autoload 单例:组合根 save_manager/meta 是getter-only # 读视图,直接赋值无效,隔离必须落在单例上。 - ProbeUiUtil.isolate_save(main, "t17_defer_probe", false, true, false) + ProbeUiUtil.isolate_save(main, "t17_defer_probe", false, true, true) # 末参=swap_save(卡②签名换代——原 refresh_resume 槽位语义退役) # 本探针验证完整动画档的演播后排窗——强制 motion_scale=1.0(隔离档替换在 boot 后, # 减动效已随真档 settings.cfg 应用于引擎态;减动效档=consume_now 快路、开窗即时,另属语义) UiKit.motion_scale = 1.0 for i in 8: await get_tree().process_frame - var lvbtn: Button = _find_button_by_sub(main._start_overlay, "开始冒险") - if lvbtn == null: - _fail("开始冒险按钮缺失") - return - lvbtn.pressed.emit() + main._start_adventure_from_menu() # 函数直调中间态(卡② mn10——开始冒险钮已撤) for i in 60: await get_tree().process_frame if main.level_screen != null and main.level_screen.visible: diff --git a/game/smoke/ui_click_probe.gd b/game/smoke/ui_click_probe.gd index 7b6c8343..bf22f5fb 100644 --- a/game/smoke/ui_click_probe.gd +++ b/game/smoke/ui_click_probe.gd @@ -49,20 +49,20 @@ func _ready() -> void: # 任何 UI 不遮挡;浮层角标遮挡判据见设置面段) if main.debug_overlay.visible: _problems.append("调试浮窗 boot 自动开(菜单优先失守)") - # ---- 营地购买合成点击路径(campbtn/购买/关闭全走真实按钮信号路径; + # ---- 营地购买合成点击路径(选档进档开层→购买→关闭全走真实按钮信号路径; # 隔离存档换注 main.meta 保确定性(禁碰真实档 user://save.json);陈旧档段首清理)---- for p0 in ["user://smoke_click_probe_reset.json", "user://smoke_click_probe_reset.json.tmp"]: if FileAccess.file_exists(p0): DirAccess.remove_absolute(p0) # 重置档段前清场兜底(营地/设置段不触该档——防陈旧断点泄入后续段) - var iso_c = ProbeUiUtil.isolate_save(main, "smoke_click_probe_camp", false, false, false, false) - var campbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "营地", "prefix") - if campbtn == null: - _problems.append("营地入口按钮缺失(开始层 campbtn)") + # swap=true(整链换注——购买写盘经 autoload 落隔离槽文件,与下方持久化核验同源; + # 原 swap=false 形态的写盘走 iso 实例旧路径,与槽文件断言不同源——卡② mn9 换代) + var iso_c = ProbeUiUtil.isolate_save(main, "smoke_click_probe_camp", false, false, true) + if not ProbeUiUtil.open_camp_via_savesel(main, "smoke_click_probe_camp"): + _problems.append("营地入口缺失(选档层新档钮——mn9 新路径驱动中断)") else: - campbtn.pressed.emit() # 真实按钮信号路径 → 组合根 _open_camp await get_tree().process_frame if not main.camp_overlay.visible: - _problems.append("营地层未打开(点击路径)") + _problems.append("营地层未打开(进档开层新路径)") elif main.camp_overlay._tree_buy_btns.is_empty(): _problems.append("树淬炼柜台购买钮缺失(点击路径——四柜台重排后树=主界面)") else: @@ -83,7 +83,7 @@ func _ready() -> void: await get_tree().process_frame if int(iso_c.keystone_balance()) != bal_before - price: _problems.append("营地树购买未出账(%d→%d 应 −%d)" % [bal_before, iso_c.keystone_balance(), price]) - var iso_chk = load("res://core/save_manager.gd").new("user://smoke_click_probe_camp.json") + var iso_chk = load("res://core/save_manager.gd").new("user://smoke_click_probe_camp_slot_1.json") iso_chk.load_from_disk() if int(iso_chk.tree_level("sp_hp")) != 1: _problems.append("营地树购买未持久(reload 后级 != 1)") @@ -117,12 +117,14 @@ func _ready() -> void: else: side_b.pressed.emit() # 合成切态点击 → _on_camp_fork_toggle → meta.tree_toggle_fork await get_tree().process_frame - var iso_fk = load("res://core/save_manager.gd").new("user://smoke_click_probe_camp.json") + var iso_fk = load("res://core/save_manager.gd").new("user://smoke_click_probe_camp_slot_1.json") iso_fk.load_from_disk() if String(iso_fk.fork_side("fg_posture")) != "b": _problems.append("Fork 切态未持久(reload 后激活态 != b)") main._on_camp_closed() await get_tree().process_frame + main._on_save_select_closed() # 关闭被栈还原的选档层(动线序中间态——卡④ 换代点) + await get_tree().process_frame if main.camp_overlay.visible or not main._start_overlay.visible: _problems.append("营地关闭后未恢复开始层") # ---- 选档界面行为走查(迭代 33 卡③):sl5 空槽新档链/sl6 有档槽进档链/sl7 删档二次确认双路径 @@ -134,7 +136,7 @@ func _ready() -> void: var cp: String = csel_tpl % i + suf if FileAccess.file_exists(cp): DirAccess.remove_absolute(cp) - ProbeUiUtil.isolate_save(main, "smoke_click_probe_savesel", true, false, false, true) + ProbeUiUtil.isolate_save(main, "smoke_click_probe_savesel", true, false, true) main.save_manager.attach_slot_template(csel_tpl) var f_c2 := FileAccess.open(csel_tpl % 2, FileAccess.WRITE) f_c2.store_string(JSON.stringify({"version": 7, "meta": {"keystone_balance": 100, @@ -290,11 +292,9 @@ func _ready() -> void: _problems.append("设置面仍含退出游戏按钮(二轮 D1——只在一级菜单出现)") # ---- 生涯页段(入口点击链+关闭回返,真实按钮信号路径;本页只读零写, # 值断言归冒烟投影段与 ui_struct 结构族,此处只验开合路径与零跳转)---- - var career_btn: Button = ProbeUiUtil.find_button(main._start_overlay, "生涯", "prefix") - if career_btn == null: - _problems.append("生涯入口按钮缺失(开始层 careerbtn)") - else: - career_btn.pressed.emit() # 真实按钮信号路径 → 组合根 _open_career + # 生涯页驱动=函数直调中间态(卡② mn10——careerbtn 已撤,卡④ 笔记本改挂前无 UI 路径) + if true: + main._open_career() await get_tree().process_frame if not main.career_overlay.visible or main._start_overlay.visible: _problems.append("生涯页未打开(点击路径)") @@ -316,12 +316,7 @@ func _ready() -> void: if main.career_overlay.visible or not main._start_overlay.visible: _problems.append("生涯页关闭后未恢复开始层") # 换回全新零档(营地购买态不泄入后续冒险段——确定性;档案残留下次段首清理兜底) - ProbeUiUtil.isolate_save(main, "smoke_click_probe_reset", true, false, true) # 零档换注 + 继续上局按钮重刷(原三行样板收口) - var resume_btn0: Button = ProbeUiUtil.find_button(main._start_overlay, "▶ 继续上局", "prefix") - if resume_btn0 == null: - _problems.append("继续上局按钮缺失(开始层 resumebtn——装配器)") - elif resume_btn0.visible: - _problems.append("无断点态继续上局按钮未隐藏(卡② AC①——新档合法空态)") + ProbeUiUtil.isolate_save(main, "smoke_click_probe_reset", true, false, true) # 零档换注(继续上局钮已随三入口化撤出——卡② mn8) main.debug_overlay.set_sliders(400, 200) # 高加成保 L1 可通关——L2 岔路选路段只能经冒险推进到达(单关直入口退役) main.debug_overlay.seed_edit.text = "42" # 种子钉死(种子框默认为空=非首局随机,行为走查确定性禁依赖默认分支) var t1 := Time.get_ticks_msec() @@ -555,73 +550,9 @@ func _ready() -> void: # 战败态前提失配——记录不计 problem(返回路径已实机目检)。 if not _closed_via_abandon: _problems.append("断点段:终局返回未回开始菜单") - var resume_btn: Button = ProbeUiUtil.find_button(main._start_overlay, "▶ 继续上局", "prefix") - if resume_btn != null and resume_btn.visible: - _problems.append("断点段:退出清断点后继续上局按钮未隐藏(卡②/T3 AC③)") - # ② 中断→续玩:新冒险达入口停点=断点在档 → 合成回菜单(模拟关游戏重开)→ 按钮点亮 → 点击恢复至停点 - var lv_btn: Button = ProbeUiUtil.find_button(main._start_overlay, "▶ 开始冒险", "prefix") - if lv_btn == null or resume_btn == null: - _problems.append("断点段:开始冒险/继续上局按钮缺失") - else: - lv_btn.pressed.emit() # 无断点态单击直启(不触发二次确认) - await get_tree().process_frame - if not main.level_screen.visible: - if not _closed_via_abandon: - _problems.append("断点段:无断点开新冒险未达入口停点") - else: - iso_chk.load_from_disk() - if not iso_chk.has_breakpoint(): - _problems.append("断点段:入口停点断点未写盘(卡①写面失守)") - var cand_before: Array = main.run_scope.runner.next_candidate_ids() - # 合成「关游戏重开回菜单」(探针专用——真实路径无此入口;断点已在盘) - main._exit_level_screen() - main._start_overlay.visible = true - main.run_scope.adventure = null - main._refresh_resume_button() - await get_tree().process_frame - if not resume_btn.visible: - _problems.append("断点段:有断点态继续上局按钮未点亮(卡② AC②前置)") - else: - resume_btn.pressed.emit() # 真实按钮信号路径 → _resume_from_menu → resume_adventure - await get_tree().process_frame - await get_tree().process_frame - if main.run_scope.adventure == null or main.run_scope.runner == null or not main.level_screen.visible: - _problems.append("断点段:续玩未恢复至停点(关卡画面未现)") - else: - var cand_after: Array = main.run_scope.runner.next_candidate_ids() - if cand_after.size() != cand_before.size() or (cand_after.size() > 0 and int(cand_after[0]) != int(cand_before[0])): - _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.run_scope.adventure = null - main._refresh_resume_button() - await get_tree().process_frame - lv_btn.pressed.emit() - await get_tree().process_frame - if not main.confirm_dialog.is_open() or main.run_scope.adventure != null: - _problems.append("断点段:有断点点开始冒险未弹确认框(二轮 A 失守)") - var cancel_btn: Button = ProbeUiUtil.find_button(main.confirm_dialog, "取消", "contains") - if cancel_btn == null: - _problems.append("断点段:确认框取消按钮缺失") - else: - cancel_btn.pressed.emit() - await get_tree().process_frame - if main.confirm_dialog.is_open() or main.run_scope.adventure != null or not main._start_overlay.visible: - _problems.append("断点段:取消未回开始菜单(二轮 A)") - lv_btn.pressed.emit() - await get_tree().process_frame - var confirm_btn: Button = ProbeUiUtil.find_button(main.confirm_dialog, "开新冒险", "contains") - if confirm_btn == null: - _problems.append("断点段:确认框开新冒险按钮缺失") - else: - confirm_btn.pressed.emit() - await get_tree().process_frame - await get_tree().process_frame - if main.run_scope.adventure == null or main._start_overlay.visible or main.confirm_dialog.is_open(): - _problems.append("断点段:确认开新冒险后未开局(二轮 A)") + # ②③ 中断→续玩全链+互斥确认两分支:随三入口化撤出(卡② §7.2 申报——断点确认探针段移交 + # 卡④大门 T5 接收,两头登记禁静默删除;续玩钮已退役,主菜单确认链已摘 mn6)。 + # ① 战斗收口断点写盘断言保留(上方——冒险流直调中间态 mn10)。 print("[click_probe] 循环退出(done=%s budget=%s)问题=%d" % [ str(_done), str(action_budget), _problems.size()]) for p in _problems: diff --git a/game/smoke/ui_probe.gd b/game/smoke/ui_probe.gd index c5644303..eab8fbdf 100644 --- a/game/smoke/ui_probe.gd +++ b/game/smoke/ui_probe.gd @@ -23,15 +23,13 @@ func _ready() -> void: # ---- 营地覆盖层(主菜单入口/开合/余额文案/购买禁用态; # 隔离存档换注 main.meta 保确定性(禁碰真实档 user://save.json——SaveManager 路径注入纪律; # 营地先行段只换 meta 不换 save_manager——冒险段换注零档在前)---- - ProbeUiUtil.isolate_save(main, "smoke_ui_probe_camp", false, false, false, false) - var campbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "营地", "prefix") - if campbtn == null: - problems.append("营地入口按钮缺失(开始层 campbtn)") + ProbeUiUtil.isolate_save(main, "smoke_ui_probe_camp", false, false, false) + if not ProbeUiUtil.open_camp_via_savesel(main, "smoke_ui_probe_camp"): + problems.append("营地入口缺失(选档层新档钮——mn9 新路径驱动中断)") else: - campbtn.pressed.emit() # 合成信号路径(真实按钮 → 组合根 _open_camp) await get_tree().process_frame if not main.camp_overlay.visible: - problems.append("营地层未打开(campbtn 路径)") + problems.append("营地层未打开(进档开层新路径)") elif main._start_overlay.visible: problems.append("营地层打开时开始层未隐藏(规范 2.3 遮挡纪律)") else: @@ -51,6 +49,8 @@ func _ready() -> void: problems.append("秘宝匣柜台行数错误(%d ≠ 12——迭代 22 秘宝批换代)" % main.camp_overlay._workshop_rows.size()) main._on_camp_closed() await get_tree().process_frame + main._on_save_select_closed() # 关闭被栈还原的选档层(动线序中间态——卡④ 换代点) + await get_tree().process_frame if main.camp_overlay.visible or not main._start_overlay.visible: problems.append("营地关闭后未恢复开始层(开合往返失守)") # 换回全新零档(营地段购买态不泄入后续冒险段——确定性;先删后建防陈旧档泄入) diff --git a/game/smoke/ui_struct_probe.gd b/game/smoke/ui_struct_probe.gd index a1b829e2..4319c84a 100644 --- a/game/smoke/ui_struct_probe.gd +++ b/game/smoke/ui_struct_probe.gd @@ -38,35 +38,32 @@ func _ready() -> void: main._on_settings_closed() _settle() - var campbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "营地", "prefix") - if campbtn != null: - campbtn.pressed.emit() + if not ProbeUiUtil.open_camp_via_savesel(main, "smoke_ui_struct_probe_menu"): + problems.append("[camp] 营地入口缺失(选档层新档钮——mn9 新路径驱动中断)") + else: _settle() await _audit("camp", [main.camp_overlay]) main._on_camp_closed() _settle() - else: - problems.append("[camp] 营地入口按钮缺失(驱动中断)") + main._on_save_select_closed() # 关闭被栈还原的选档层(动线序中间态——卡④ 换代点) + _settle() # ---- 生涯页:入口点击链(合成点击真路径=pressed.emit→组合根 _open_career)→ # 结构断言族→只读巡检(契约 3)+零动效契约+零跳转契约(提纲 P4 甲)→组合根真实路径关闭。 #(登记序/树序契约统一在全模态 _modal_registry_contract——boot 段跑)---- - var careerbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "生涯", "prefix") - if careerbtn != null: - careerbtn.pressed.emit() - _settle() - if not main.career_overlay.visible or main._start_overlay.visible: - problems.append("[career] 生涯页未打开或开始层未隐藏(入口点击链断裂)") - await _audit("career", [main.career_overlay]) - _career_readonly_contract(main) - _career_zero_motion_contract(main) - _career_nojump_contract(main) - main._on_career_closed() - _settle() - if main.career_overlay.visible or not main._start_overlay.visible: - problems.append("[career] 关闭后页未隐藏或开始层未恢复(模态栈单口失守)") - else: - problems.append("[career] 生涯入口按钮缺失(开始层 careerbtn)") + # 生涯页驱动=函数直调中间态(卡② mn9/mn10——careerbtn 已撤,卡④ 营地笔记本改挂前无 UI 路径) + main._open_career() + _settle() + if not main.career_overlay.visible or main._start_overlay.visible: + problems.append("[career] 生涯页未打开或开始层未隐藏(函数直调中间态断裂)") + await _audit("career", [main.career_overlay]) + _career_readonly_contract(main) + _career_zero_motion_contract(main) + _career_nojump_contract(main) + main._on_career_closed() + _settle() + if main.career_overlay.visible or not main._start_overlay.visible: + problems.append("[career] 关闭后页未隐藏或开始层未恢复(模态栈单口失守)") # ---- 选档界面(迭代 33 卡③):函数直调开层(「进入存档」钮归卡② mn3——点击链在卡② 承接), # sl1 三槽呈现/sl2 摘要三项逐槽读数/sl3 摘要禁项/sl4 操作集二分/sl9 重启链不预选。 @@ -197,7 +194,6 @@ func _ready() -> void: # resume 提前 return)——GP 总结 lambda 求值时 runner 尚未赋值,修复前 717 炸 null。 ProbeUiUtil.isolate_save(main, "smoke_ui_struct_probe_resume", true) main.save_manager.set_breakpoint(_resume_bp_fixture()) - main._refresh_resume_button() main.run_scope.runner = null # 全新启动等价态(真机=菜单直进续玩,无前置冒险——runner 从未赋值;探针此前推流会残留旧 runner) main._resume_from_menu() _settle(8) diff --git a/game/smoke/ui_tune_probe.gd b/game/smoke/ui_tune_probe.gd index 62575e88..2e0a5c47 100644 --- a/game/smoke/ui_tune_probe.gd +++ b/game/smoke/ui_tune_probe.gd @@ -89,7 +89,7 @@ func _ready() -> void: # (攻击 ×2 / 生命 +50%)——旧缺陷(残留值被当基线再乘)在 encounter 参数链上同样被抓获 # 存档隔离(停点断点写盘随 main.save_manager;探针禁碰真实档,同族探针隔离纪律; # isolate_save 先删后建——上次运行的残留断点会触发互斥确认早退) - ProbeUiUtil.isolate_save(main, "smoke_tune_probe_save", true, false, false) + ProbeUiUtil.isolate_save(main, "smoke_tune_probe_save", true, false, true) # 末参=swap_save(卡②签名换代——断点写盘经 autoload 落隔离档) main.debug_overlay.set_sliders(100, 50) main.debug_overlay.press_apply_for_probe() main.debug_overlay.seed_edit.text = "42" # 种子钉死(18卡⑦审计🔴——空框=非首局随机,显式优先经真实路径断言 master_seed) diff --git a/game/tests/unit/presentation/pacing_queue_test.gd b/game/tests/unit/presentation/pacing_queue_test.gd index e218bdb7..67a2fba0 100644 --- a/game/tests/unit/presentation/pacing_queue_test.gd +++ b/game/tests/unit/presentation/pacing_queue_test.gd @@ -27,7 +27,6 @@ func _init() -> void: var sm = main.get_node("/root/SaveManager") sm.attach_path("user://test_pacing_queue_save.json") sm.mark_first_adventure_done() - main._refresh_resume_button() # 行信号计数器(真实信号路径旁挂计数——零触碰既有接线) var rows_seen: Array = [] main._start_adventure_from_menu()