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..ee05c892 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() @@ -238,6 +238,14 @@ func _topmost_visible_layer(main: Node) -> Node: return best +# 渲染面可见按钮递归计数(V2d mn1/mn2 变异锚——句柄缝盲区闭合) +func _count_visible_buttons(node: Node) -> int: + var n := 1 if (node is Button and node.visible) else 0 + for c in node.get_children(): + n += _count_visible_buttons(c) + return n + + # ---- boot 链路时序后置条件(启动态六断言;只立行为可判断言,不造假时序)---- func _smoke_boot_chain() -> void: print("--- 迭代28 T2 boot 链路时序后置条件(V2:六断言)---") @@ -266,9 +274,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 +285,20 @@ 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) + check(_count_visible_buttons(main._start_overlay) == 3, + "V2d 渲染面可见按钮计数==3(旧钮接回即红——渲染面锚,句柄缝盲区闭合)〔卡② mn1/mn2〕") # ⑤ 闲时会话(_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 +330,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 +418,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 +450,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 +493,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 +586,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/golden/menu_boot.png b/game/tests/golden/menu_boot.png index 6eac9318..a0fded52 100644 Binary files a/game/tests/golden/menu_boot.png and b/game/tests/golden/menu_boot.png differ 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() diff --git a/reports/iter33-代拍板追认单-2026-10-21.md b/reports/iter33-代拍板追认单-2026-10-21.md index 0b4d90cf..d0531c5c 100644 --- a/reports/iter33-代拍板追认单-2026-10-21.md +++ b/reports/iter33-代拍板追认单-2026-10-21.md @@ -24,3 +24,13 @@ | 5 | 提交切分与 worktree 命名(§9-5) | worktree `iter33card3-saveselect`/分支 `iter33-card3-saveselect`;三笔:T1+T2+T3 实现+变异伴生强化+T4 产出物 | — | — | | 6 | ConfirmDialog 消费方编排机制(§9-6,卡片预授权取法照录) | **open 前设消费方状态量(`_confirm_consumer`)、confirmed/cancelled 后按量分派(`_on_confirm_accepted`/`_on_confirm_declined` match 路由)**;双钮签名未动(`confirm_dialog.gd:36-38`);分派面=match 分支可扩展(卡④ 第三消费方直接加分支) | 禁双常驻静态接线 ✓(变异m4 拦截在役);本批消费方=删档确认+现役新冒险确认(随卡②撤出) | 机制重选=分派层重写 | | 7 | 窥视读器 has_file 位(实现期接口补全——超出 §9 清单单项,按 T0 对齐授权补记待追认) | `peek_slot_summary` 返回结构加 `has_file: bool`(空/有档操作集二分判定来处;卡③ 6.0「槽态唯一来处=卡① 窥视 API 返回值」的接口闭环——交付面缺该位则二分不可实现) | 非摘要第四项(契约 5 三项不变)✓;向后兼容(既有断言零改)✓;卡① 侧 sv7/sv8 断言复跑绿 ✓ | 若裁「二分判定改走他口」(如独立 has_slot API)——save_manager 增一访问器+组合根改一行 | + +## 卡② 主菜单三入口化批 + +| # | 代拍项(卡片 §9 条目) | 取法 | 约束核对 | veto 影响 | +|---|---|---|---|---| +| 1 | 组帽与左列布局(§9-1) | **双组帽全撤**(冒险帽=组内零钮退化态不留;营地与系统帽=指称失义——「营地」已非菜单入口),三钮纵列原左列骨(布局零重构——契约 7) | v1.1「保留原样=留指称不可达地点文案,禁」✓ | 恢复任一组帽=视图一处 | +| 2 | _refresh_resume_button 去留(§9-2) | **净删除**+消费点全清(main 五处/camp ctx 载体/presenter 三处/probe_ui_util 参数/pacing_queue/V2d 族——T0 清点口径 grep 零残留) | 卡④ 不复用已证 ✓ | — | +| 3 | 进入存档钮命名(§9-3) | 「进入存档」primary_button 主操作钮(原主黄钮位),tooltip=「选择一个存档位进入(新档 / 进档 / 删档)」 | 裁决 1 原话 ✓ 动词化 ✓ | 改文案=装配器一处 | +| 4 | 探针中间态写法(§9-4) | 营地=open_camp_via_savesel 助手(选档层新档钮真实信号路径,槽模板清场防真实槽污染、swap=true 整链落槽文件保持久化核验同源);生涯/冒险=函数直调(mn10 登记实现报告「已知中间态」) | 契约 5 中间态合法交付 ✓ 禁假路径 ✓ | 卡④ 落地后代换 | +| 5 | 提交切分与 worktree 命名(§9-5) | worktree `iter33card2-menu`/分支 `iter33-card2-menu`;四笔(T1+T2/golden/变异强化+报告/T4) | — | — | diff --git a/reports/iter33-反哺点-2026-10-21.md b/reports/iter33-反哺点-2026-10-21.md index 694414de..d6bb8416 100644 --- a/reports/iter33-反哺点-2026-10-21.md +++ b/reports/iter33-反哺点-2026-10-21.md @@ -21,3 +21,14 @@ | 8 | `design/gdd/08-Alpha版本标准.md` 「三 · 界面」表 | 无选档界面行 | **增行请求(须用户裁——08 头注纪律)**:新增「选档界面」行(在役,迭代 33 卡③) | 卡③ §5 统一口径 | | 9 | `待办清单.md`/`roadmap.md` | 迭代 33 进行中 | 卡③ 完工状态推进(归文档批) | doc-conventions §12.1 | | 10 | `design/gdd/systems/表现与支撑/存档序列化.md` 消费面纪律节 | 窥视读器未记载(卡① 反哺点 4 补迁移口径时顺带) | 补 `peek_slot_summary` 返回结构(readable/has_file/三值)与「has_file=占档判定、非摘要项」口径 | 卡③ T0 对齐补充(二分判定来处的文档化) | + +## 卡② 主菜单三入口化批 + +| # | 文档与落点 | 现行陈述 | 应改成 | 依据 | +|---|---|---|---|---| +| 11 | `design/gdd/systems/跨局循环/生涯.md` `:9`/`:11-23` | 主菜单六钮组(冒险组+营地与系统组)记述+生涯入口=开始层生涯钮 | 信息架构改三钮组(进入存档/设置/退出游戏);生涯入口改挂营地笔记本(卡④ 承载) | 卡② 实装(mn1/mn2) | +| 12 | `design/gdd/ux/camp.md` `:10` | 「主菜单营地独立入口进入」 | 营地入口改挂存档内(选档→进档→营地场景图) | 同上 | +| 13 | `design/gdd/08-Alpha版本标准.md` `:102-105` 三·界面表 | 主菜单/开始覆盖层等四行 | 状态列换代+入口拓扑注记(增删行须用户裁) | 卡② §5 统一口径 | +| 14 | `体验验证清单.md` `:170`/`:192` | 左操作列(冒险/营地与系统两组)/营地与系统组生涯钮 | 主菜单形态引述换代(三钮)+目检项并入迭代 33 实机目检总清单(实现报告) | 卡② F13 | +| 15 | `design/art/art-bible.md` `:433` | 小字号豁免合并条款含「主菜单分组 overline×2」枚举片段 | excision 两片段(组帽双撤——**禁整行删**) | 卡② mn11 实装 | +| 16 | `待办清单.md`/`roadmap.md` | 迭代 33 进行中 | 卡② 完工状态推进+当前位换代(归文档批) | doc-conventions §12.1 | diff --git a/reports/iter33-实现报告-2026-10-21.md b/reports/iter33-实现报告-2026-10-21.md index 5b7f408b..504f63ff 100644 --- a/reports/iter33-实现报告-2026-10-21.md +++ b/reports/iter33-实现报告-2026-10-21.md @@ -144,3 +144,48 @@ | 单元 | 38 套件全绿 | **38 套件全绿** | | probe --click | 全绿 | 全绿(struct/click/flow 三件新段全绿+ui_contrast 等既有件零回归) | | 重闸 --code | — | 首轮 all_green=false(R1×2 新增)→ 回炉后**全绿**(回执随合并提交留痕——收口流程订正:回执须核 verdict 非仅 commit) | + +--- + +## 卡② 主菜单三入口化批(6 钮 → 3 钮)——已合并(见合并记录) + +**状态**:T0–T4 完成;批末全量闸全绿(回执随合并提交留痕);独立验收审计通过(`reports/iter33-验收审计-2026-10-21.md` 卡② 节)。 + +### 一、实际改动—验收对照表(§7.1 需求侧表的执行对账,六列+结果) + +| 需求点 | 改动实值(文件:行) | 验收测试实值(落点+尾缀) | 层级 | 真实入口 | 撤销或变异证明 | 结果 | +|---|---|---|---|---|---|---| +| mn1 三钮在位 | `ui_assembler.gd:126-137` build_start_overlay 三钮(进入存档 primary/设置/退出)+组帽双撤 | 冒烟 V2(`smoke_all.gd:162` 三钮在位)+V2d 渲染面计数==3(`smoke_all.gd:292`)〔mn1〕 | 冒烟 | 真实 MenuScreen 经装配器构建后读句柄+渲染面枚举 | 变异A=生涯钮接回三钮集 → 渲染面计数红(已执行;句柄缝盲区由变异暴露后强化) | 通过 | +| mn2 四旧钮彻底撤出 | `menu_screen.gd:9-12` 句柄面撤 4 留 3;assembler 返回字典换代 | 冒烟 V2d 四旧钮句柄零命中×4+渲染面计数锚〔mn2〕 | 冒烟 | 菜单场景句柄枚举+渲染面 | 变异A 同锚 | 通过 | +| mn3 进入存档可达选档 | `main.gd:323` entersavebtn.pressed→_open_save_select | `screen_flow_probe` 选档步〔mn3〕:合成点击进入存档钮→选档层可见且开始层隐藏 | 探针 | 真实按钮信号路径(卡③ 直调中间态就此闭环) | 变异B=接错层(连营地)→ sl10 屏级互斥 5 处红(已执行) | 通过 | +| mn4 设置钮零行为变化 | settingsbtn 接线保持 | 冒烟 V3g settings 模态段复跑〔mn4〕 | 冒烟 | settingsbtn 句柄 pressed | 撤销=断线 → V3g 红 | 通过 | +| mn5 退出接线正确 | quitbtn→_quit_game 保持 | ui_click 退出游戏按钮存在断言(D1 二轮段)复跑〔mn5〕 | 探针 | MenuScreen.quitbtn | 变异=改连空函数 → 连接断言红 | 通过 | +| mn6 断点确认零残留 | `main.gd:489-497` 确认分支摘除+分派器 new_adventure 臂退役(机制保留) | ui_struct confirm 采样复跑+mn10 直调段绿(确认链不可达=直调直启)〔mn6〕 | 探针 | 主菜单路径确认链已无触发入口(钮撤+分支摘) | 撤销=接线复现 → 申报内 ui_click 断点段(已按 §7.2 删除移交卡④)原断言红 | 通过 | +| mn7 _refresh_resume_button 删除+消费点清零 | 函数删除+main.gd 五处+camp_flow_context 载体+camp_shop_presenter 三处+probe_ui_util 参数+pacing_queue+V2d 族 | T0 清点口径全仓 grep 零命中件核(本表随附清点)+冒烟/探针全绿 | 集成 | 真实消费链已全部拆除 | 变异=漏删一处 ctx 字段 → 解析错/调用错红(camp_flow_context 参数摘除即证) | 通过 | +| mn8 废止断言族有改写承接 | smoke V2 三钮/V2d 族重写(`smoke_all.gd:160-166`/`:281-293`)/V3g camp 新路径(`:411-449`)/V3i 钮断言摘(`:474-481`)/resume_vis 键退役(`_stage_faces`) | 改写后断言〔mn8〕逐族承接(渲染面计数+V3g 新路径+V2d 四旧钮零命中) | 冒烟 | boot 与回菜单生命周期段真实入口 | 撤销=只删不写承接 → 「每个废止族须有承接」计数断言红 | 通过 | +| mn9 营地/生涯入口探针段改走新路径 | ui_probe/ui_struct/screen_flow/shot/ui_click 营地=进档开层新路径(open_camp_via_savesel 助手——槽模板清场防真实槽污染);生涯=函数直调中间态(卡④ 笔记本改挂前) | 各探针自身绿+新路径断言〔mn9〕 | 探针 | 选档层新档钮真实信号路径(持久化核验同源落槽文件) | 撤销=入口段留旧句柄 → 取不到钮即红(助手返回 false 即问题行) | 通过 | +| mn10 冒险段探针直调中间态 | `_start_adventure_from_menu`/`_resume_from_menu` 保留仅摘接线/确认分支;直调位点=screen_flow 选档后冒险步、shot 关卡段、fx_shot、t17、ui_input、layout、ui_tune、smoke ×4、pacing_queue、ui_struct resume 专项 | 各该段复跑绿〔mn10〕+本节「已知中间态」逐位点登记 | 探针 | 函数直调(如实标中间态,卡④ 到位换代) | 撤销=删函数 → 直调段运行即红 | 通过 | +| mn11 组帽撤除+左列几何不破 | `ui_assembler.gd` 两枚 overline 删除+三钮纵列(左列布局原骨) | ui_struct `_start_columns_contract`+`_audit("start")` 复跑〔mn11〕 | 探针 | 真实菜单构建后几何审计 | 变异=左列改单列致相交 → 不相交合同红 | 通过 | +| mn12 menu golden 重制 | `game/tests/golden/menu_boot.png` 重采(三钮形态;golden_baseline_refresh --menu,触发原因=钮集换代) | render_gate 菜单 golden 逐像素段〔mn12〕 | golden | render_gate 驱动 shot_probe 真机渲染比对 | 撤销=拿旧基线跑 → 钮集区差红 | 通过(重闸渲染闸绿) | +| mn13 pacing_queue 套件连带改造 | `pacing_queue_test.gd` 钮依赖行摘除(宿主链驱动保持) | 该套件自身绿(40 套件全绿内)〔mn13〕 | 单元 | run_all 真实套件入口 | 变异=裸调下层 → 宿主链断言红 | 通过 | +| mn14 反哺点清单(非行为改动) | §5 所列(生涯.md/camp.md/08/体验验证清单/art-bible :433 excision/待办清单/roadmap)列点不落文 | 件核对表〔mn14〕落反哺点清单卡② 节 | 件核 | 收口前逐条指认 | 产出物件核不产码 | 通过 | + +### 二、变异验证记录表 + +| # | 注入点 | 实测红 | 最窄闸 | 还原后 | 伴生强化 | +|---|---|---|---|---|---| +| A | 生涯钮接回三钮集(渲染面,无句柄) | 首轮 ui_struct/冒烟均零红——**句柄缝盲区暴露**(V2 只查三钮在位、V2d 只查句柄);补渲染面计数锚后冒烟红(1 项失败) | run_smoke | 1213/0 | V2d 渲染面计数锚 | +| B | 进入存档钮接错层(连 _camp_pres.open_camp) | screen_flow 5 处红(sl10 屏级互斥失守+营地/冒险同屏) | screen_flow_probe | PASS | — | + +### 三、已知中间态(mn10 逐位点登记——卡④ 到位换代) + +生涯页与冒险入口无 UI 路径(生涯=营地笔记本、冒险=大门两分支,均卡④ 承载):`_open_career`/`_start_adventure_from_menu`(确认链已摘,直启形态)/`_resume_from_menu` 三函数保留为探针直调中间态,直调位点=ui_struct resume 专项、screen_flow 冒险步、shot 关卡段、fx_shot、t17_defer、ui_input、layout、ui_tune、smoke 四处、pacing_queue。ui_click 断点段②③(续玩全链+互斥确认两分支)按 §7.2 申报删除,卡④ 大门 T5 接收两头登记。 + +### 四、读数对照 + +| 闸 | 基线(卡③ 合并态) | 末态(卡② 收口) | +|---|---|---| +| 冒烟 | 1201 / 0 | **1213 / 0**(+12:V2 三钮+V2d 承接族+渲染面锚) | +| 单元 | 38 套件全绿 | **40 套件全绿**(+2=平行迭代34 批次合入所带来的套件,非本批新增) | +| 探针 | 全绿 | 全绿(struct/click/flow/ui_probe/tune/layout/input/t17 直跑;fx/shot 归窗口化闸件) | +| golden | 六钮基线 | 三钮基线重制(渲染闸复跑绿) | diff --git a/reports/iter33-验收审计-2026-10-21.md b/reports/iter33-验收审计-2026-10-21.md index 0740af9f..c6798981 100644 --- a/reports/iter33-验收审计-2026-10-21.md +++ b/reports/iter33-验收审计-2026-10-21.md @@ -189,3 +189,87 @@ ### 卡③ 结论(终审) **通过**——卡③ 选档界面新建批 14 行(sl1–sl14)全部通过(首轮未过记录保留于上,回炉事实=提交 `b673ced`)。独立验收审计合并前置达成。复审全程零 commit、`game/` 零残留、`git status` 仅本报告工作件。 + +## 卡② 主菜单三入口化批(6 钮 → 3 钮) + +> 审计人:未参与卡② 实现的独立会话,只采信盘面证据与自跑实测,不采信任何报告的「已全绿」。 +> 被审计对象:worktree `D:\code\g-plan2-iter33card2-menu`,分支 `iter33-card2-menu`,卡② 提交 `3661689a`+`93d0e679`+`5a39b116`+`821a2eae`,基线 `5c2a7bcf`(卡③ 合并态);区间内非本批改动(迭代34 卡④ 合并 `16a7ec80`、锻造工坊死链修 `4bc6b366`/`a10d15c7`)已按提交拓扑剔除,卡② 本体 diff 取 `a10d15c7..HEAD`(20 件:presentation 5+smoke 12+golden+pacing 单测+reports 3)。 +> 审计材料:需求卡片 §7.1 对照表 mn1-mn14+§7.2 七条申报+§2 F10 全量位点表、实现报告卡② 节(实际对照表+变异表 A/B+已知中间态)、卡② 本体 diff、闸回执。 +> 审计自跑:变异 B 注入→`screen_flow_probe` 红→还原→复绿全程实测(见②);另按改动域复跑最窄面与清点口径 grep(见各节)。 + +### ① 逐行核对表(mn1–mn14,对实现报告实际对照表逐行复核) + +| # | 判定 | 依据(审计自查实录) | +|---|---|---| +| mn1 三钮在位 | 通过 | 断言在盘可 grep:冒烟 V2 三钮非空(`smoke_all.gd:162-163`,〔卡② mn1〕尾缀)+V2d 渲染面可见按钮计数==3(`:292-293`,5a39b116 强化件)。真实入口未绕过:`menu_screen.tscn` 真实实例化经 `UiAssembler.build_start_overlay` 构建后读句柄+渲染面递归枚举。变异 A 已执行(报告变异表 A+提交 5a39b116 提交信息在案)。 | +| mn2 四旧钮彻底撤出 | 通过 | V2d 四旧句柄零命中×4(`smoke_all.gd:297-299` 循环断言)+渲染面计数锚(接回钮在渲染面出现即计数红——句柄缝盲区已由变异 A 暴露并闭合)。句柄面 diff 核实:`menu_screen.gd` 撤 `lvbtn/resumebtn/campbtn/careerbtn` 四字段、增 `entersavebtn`,装配器返回字典同步换代。 | +| mn3 进入存档可达选档 | 通过 | `screen_flow_probe` 选档步在盘:`find_button(_start_overlay, "进入存档")` → `pressed.emit()`(真实按钮信号路径)→ 屏级互斥断言;钮缺失即 `_problems.append("[savesel] mn3 进入存档钮缺失")`(`screen_flow_probe.gd:165-172`,mn3 尾缀可 grep)。**审计实测变异 B 闭合**(见②-1):接线改连 `_camp_pres.open_camp()` → 5 处红含 `[savesel] sl10 屏级互斥失守`——与报告变异表 B 逐字一致。 | +| mn4 设置钮零行为变化 | 通过 | settingsbtn→`_open_settings` 接线 diff 核实零触碰(`main.gd:328`);V3g settings 模态段冒烟生命周期矩阵原样复跑(该分支 diff 零改动=纯复跑,申报形态即「复跑」,无削弱)。 | +| mn5 退出接线正确 | 通过 | quitbtn→`_quit_game` 接线 diff 核实零触碰(`main.gd:329`);对照面在盘:ui_click D1 二轮段「开始菜单有退出游戏钮+设置面无」双断言(`ui_click_probe.gd:288-292`)复跑。 | +| mn6 断点确认主菜单零残留 | 通过(例外见注) | diff 核实两处摘除:`_start_adventure_from_menu` 确认分支整段删除(直启形态)+`_on_confirm_accepted` 分派器 `"new_adventure"` 臂退役(match 机制保留——V5 口径)。ui_struct 既有 confirm 采样在盘复跑(`ui_struct_probe.gd:100-103`)。注:本行撤销证明依赖的 ui_click 断点段原断言已按 §7.2 申报移交卡④(申报内删除)——残留拦截力由 mn2 四钮零命中+mn10 直调直启绿(直启不经确认框)组合承接,属间接面非专项断言;卡④ 落地后须补大门侧专项。 | +| mn7 `_refresh_resume_button` 删除+消费点清零 | 通过 | 净删除 diff 核实:`main.gd` 函数体+五处调用+`camp_flow_context` 载体字段/构造参+`camp_shop_presenter` 三处+`probe_ui_util` 参数+`pacing_queue_test` 一处全清。**F10 表尾清点口径全仓 grep 复核**(口径照卡片):`_refresh_resume_button` 零命中;其余命中逐条归类——`start_overlay`(未退役件,合法在役)、两函数定义(契约 5 保留)、直调位点(全部为批前既有或已登记中间态,见 mn10)、注释引述与 mn2 零命中断言字面量(`smoke_all.gd:297`)——**表外新位点:零**。 | +| mn8 废止断言族有改写承接 | 通过 | 逐族核对 diff:V2 六钮断言→三钮改写+〔mn1〕尾缀;V2d 续玩钮族→三钮在位+四旧零命中+渲染锚承接(卡① sv2 boot 态断言保留);V3g campbtn 直呼→进档开层新路径(`smoke_all.gd:411-449`);V3i 钮断言两行摘除+`_stage_faces` 的 `resume_vis` 键退役(键处理分支同步删)——逐族均有承接断言顶上,非裸删。生命周期其余阶段断言键 diff 核实零删改。 | +| mn9 营地/生涯入口探针段改走新路径 | 通过 | 五件营地段全改 `open_camp_via_savesel` 新路径(ui_probe/ui_struct/screen_flow/shot/ui_click),该助手驱动=选档层新档钮 `pressed.emit()` 真实信号路径(`probe_ui_util.gd:126-137`),非绕过直调;槽模板注入隔离命名空间(真实档红线保持)。生涯段改函数直调——契约 5 中间态(登记见 mn10 注)。 | +| mn10 冒险段探针直调中间态 | 通过(附注) | 契约 5 形态核实:两函数保留仅摘接线。直调位点全仓 grep 逐条对账——**本批新转直调 4 处全部已登记**(screen_flow 冒险步 `:181`、shot 关卡段 `:135`、fx_shot `:36`、t17_defer `:29`,实现报告「已知中间态」逐位点列名);批前既有直调位点(ui_input `:35`、layout `:27`、ui_tune `:96`、smoke ×4 `:338/:465/:620/:650`、pacing_queue `:32`、ui_struct 冒险簇 `:115`、ui_probe `:59`、ui_click 冒险段 `:323`)diff 核实零触碰或仅随 mn7 摘刷钮行。注(登记粒度微瑕,不判缺漏):生涯直调 3 处(ui_struct `:57`/ui_click `:296`/shot `:105`)与 smoke `_resume_from_menu` `:497` 为函数级登记(「已知中间态」明列 `_open_career`/`_resume_from_menu` 两函数保留+mn9 行「生涯=函数直调中间态」),未逐位点列名——实质已登记、粒度粗于冒险段。 | +| mn11 组帽撤除+左列几何不破 | 通过 | diff 核实两枚 overline 整行删除(「── 冒 险 ──」「── 营地与系统 ──」);ui_struct `_audit("start")`(`:31`)+`_start_columns_contract`(`:32`/`:675`)在盘复跑,真实菜单构建后几何审计。组帽撤除处置与 §9 第 1 条一致且追认单留痕(reports/iter33-代拍板追认单卡② 节在盘)。F12 两处注释引述(main.gd:289、career_overlay.gd:3)按卡片「实施本体内可自理」保留。 | +| mn12 menu golden 重制 | 通过 | 提交 `93d0e679` 提交信息完整记载触发原因(主菜单钮集换代)与重制入口(`golden_baseline_refresh --menu` 重采);二进制 diff 仅 `menu_boot.png` 一件(319500→319749 字节);`tools/golden_baseline_refresh.sh` 与 `tools/render_gate.sh` diff 核实零触碰(只跑不改——契约 6);复闸绿以回执 05 渲染闸 green 为准(fx/shot 探针窗口化形态,headless 直跑属预期 harness 形态不另直跑)。 | +| mn13 pacing_queue 套件连带改造 | 通过(例外见注) | diff 核实仅摘 `_refresh_resume_button()` 一行(mn7 连带);`_start_adventure_from_menu` 直调保留。注:卡片 mn13 改动列写「去掉 _refresh_resume_button 与菜单直调依赖」,直调依赖未去掉——但契约 5(红线)+§9.4 白名单明定卡④ 未到位批内冒险段探针维直调合法,实现报告 mn13 行已如实对账「钮依赖行摘除(宿主链驱动保持)」并登记进「已知中间态」(pacing_queue 位点在列)——按契约优先于卡片行文判通过,非隐瞒偏差。 | +| mn14 反哺点清单 | 通过 | `reports/iter33-反哺点-2026-10-21.md` 卡② 节在盘,第 11-16 条全覆盖 §5:生涯.md/camp.md/08/体验验证清单/art-bible :433 excision(禁整行删口径在案)/待办清单+roadmap,各条有现行陈述/应改成/依据。件核不产码,合规。 | + +### ② 撤销证明抽查记录(审计自跑实测) + +**②-1 变异 B 复现(报告变异表 B,实测全程)** + +- 注入:`game/presentation/main.gd:318` `_menu_screen.entersavebtn.pressed.connect(_open_save_select)` → `_menu_screen.entersavebtn.pressed.connect(func() -> void: _camp_pres.open_camp())`(进入存档钮接错层——mn3 行申报的变异本体)。 +- `timeout 120 Godot_console.exe --headless --path . res://smoke/screen_flow_probe.tscn` → **FAIL — 5 处问题**:`[savesel] sl10 开层后屏级互斥失守(选档=false)`+⑤冒险内切换两条(营地/冒险同屏)+⑦终局→菜单两条(菜单/营地同屏)——与报告变异表 B「screen_flow 5 处红(sl10 屏级互斥失守+营地/冒险同屏)」一致,**mn3 断言拦截力实测成立**。 +- `git checkout -- game/presentation/main.gd` → `git status --porcelain` 空(零残留)→ 复跑 **PASS — 六步主流程+回退路径全绿**。注入→红→还原→复绿闭环。 +- 附核:探针全程走 `isolate_save` 隔离面,真实档 `user://save.json` 零触碰(探针纪律在案)。 + +**②-2 变异 A 处置历史核对(盘面凭据,未重放)** + +- 首轮零红证据:报告变异表 A 原文「首轮 ui_struct/冒烟均零红——句柄缝盲区暴露(V2 只查三钮在位、V2d 只查句柄)」。 +- 伴生强化实证:提交 `5a39b116` diff 全量=`smoke_all.gd` +10 行零删——`_count_visible_buttons` 递归助手+渲染面计数==3 一条 check,**纯收紧**(只增断言不改旧断言不放宽阈值),提交信息同步记载盲区成因与两变异闭合实况。 +- 强化后拦截力推演:接回钮只要可见即计数 >3 必红,与句柄面无关——句柄缝盲区在锚面上不可再穿透。m-A 处置链(零红→补锚→复红)凭据齐、强化纯收紧,判成立。 + +### ③ 测试削弱检查表(卡② 本体 diff 全部删改逐处 vs 卡片 §7.2 七条申报) + +| diff 改动处 | 定性 | vs §7.2 申报 | +|---|---|---| +| `smoke_all.gd` V2d 续玩钮族(旧 :266-290 六断言+boot 态断言改写+`_refresh_resume_button` 调用三处) | 既有断言废止/改写 | **申报内**(§7.2 行 1「钮已撤判据失去对象——废止」)。承接断言在盘:三钮在位+四旧零命中+渲染锚+卡① sv2 boot 态断言保留;卡① V2d-1 改写批内已申报「卡② mn8 再承接」——两代申报链闭合。 | +| `smoke_all.gd` V3g campbtn 直呼(旧 :322/:326/:411-415) | 驱动路径换代 | **申报内**(行 2)。新路径=选档层新档钮真实信号→进档开营地,非直调开层——入口真实性与卡③ Q3 乙落点一致;camp 段后新增选档层闭合/attach 恢复/槽文件清理三步为路径换代伴生机械(非断言削弱)。 | +| `smoke_all.gd` V3i 阶段⑨(旧 :470-478/:565-567) | 钮断言摘除+`resume_vis` 键退役 | **申报内**(行 3)。`_resume_from_menu` 全链断言保留(run_scope 换代+is_inside_tree),只摘钮可见性断言与 `_stage_faces` 键;生命周期其余阶段 diff 核实零删改。 | +| `ui_click_probe.gd` resume_btn0 两断言(旧 :319-324,卡片 :224-228 行号漂移) | 既有断言删除 | **申报内**(行 4 前半——F10 表明列「resumebtn 断言+断点段」双位点)。行号漂移成因=卡③ savesel 段插入使基线行号后移,位点实质对应无疑义。 | +| `ui_click_probe.gd` 断点段②③(旧 :555-616 约 73 行,卡片 :462-518) | 既有断言段删除 | **申报内**(行 4,约 80 行口径吻合)。两头登记核实:卡② 侧=卡片 T2+§7.2+实现报告「已知中间态」明列「卡④ 大门 T5 接收两头登记禁静默删除」;卡④ 接收侧=卡④ 未重立,登记义务随其重立卡兑现(现状为申报明文的待办形态,非静默删除)。段①(战斗收口断点写盘断言)保留在盘。 | +| `shot_probe.gd` career/camp 段(旧 :103-133) | 生涯改直调+营地改新路径 | **申报内**(行 5「归卡④」的卡② 侧处置+行 7)。**七屏链存在性自检未降档**:`:192`「七件存在且非零字节」自检段 diff 零触碰;career 截图段保留(直调驱动)。 | +| `ui_struct_probe.gd` resume 专项(旧 :86/:163-183) | 刷钮行摘除 | **申报内**(行 6)。`_resume_from_menu` 专项链与结构断言保留,仅摘 `_refresh_resume_button()` 调用(mn7 净删除连带)。 | +| 冒险段探针入口路径断言(fx_shot/t17/screen_flow④/shot③) | 降级为函数直调 | **申报内**(行 7)。逐位点登记核实(见①-mn10),无「为凑绿改假路径」形态——直调目标=`_start_adventure_from_menu` 本体,下游断言(关卡画面出现/停点出图/冒险屏互斥)原样保留。 | +| `probe_ui_util.gd` `isolate_save` 签名换代+`open_camp_via_savesel` 新增+ui_click camp 段 swap=false→true | 测试基建换代 | 非断言削弱:refresh_resume 参数随 mn7 退役(参数槽位语义由 swap_save 承接,全部调用点逐一同步换代——diff 逐处核对无错位);swap 翻转为写盘路径与槽文件断言同源化(注释在案),判据面对齐非放松。 | +| `pacing_queue_test.gd` 删 1 行+`ui_probe.gd`/`ui_struct_probe.gd` 营地/生涯段改写 | 申报内改写 | 见①-mn7/mn9/mn10 对应行,申报面内。 | + +**其余既有断言零删改核验**:卡② 本体 diff 逐 hunk 清点——ui_probe 秘宝匣 12 行断言原样在盘(`:48-49`);smoke 生命周期其余阶段断言键零删改;三探针既有段(sl1-sl9、设置面段、D1 段、ui_struct 各 `_audit` 面)零触碰;golden 两工具零触碰。**除 §7.2 申报面外,未发现任何删断言、放宽阈值、缩小扫描面或真实入口换下层直调**。 + +### ④ 回执核验(`game/tests/.gates/receipt.json`) + +- `commit`=`821a2eae82664ab51fe00c6b6e639f55b60a0660`=HEAD(`821a2eae`)✓ +- `all_green`=**true** ✓;`domain`=**"full"** ✓(本批同触 game/ 与文档面,全量档选档正确) +- 闸序:01 冒烟全量/02 单元套件/03 探针 --click/04 探针 --full/05 渲染闸(fx_shot/shot 两窗口化探针的绿以本闸记录为准,headless 直跑挂起属预期 harness 形态未直跑)/06-10 各文档闸全 green;11 回放确定性对拍 SKIP(SKIP 件非红);`quick_mode`=false;回执时间戳(2026-10-22T22:49:41+08:00)晚于末提交(22:41:58)=修后重闸实测。 +- 读数对照:实现报告冒烟 1213/0(+12 与 V2/V2d/渲染锚新增量吻合);单元 40 套件(+2 系迭代34 并行批合入非本批新增——diff 核实 `level_map_*_test` 两件属 16a7ec80)。 + +### ⑤ V5 专项(断点确认零残留) + +- 全仓 grep「开新冒险将覆盖旧断点」:`game/` **零命中**;命中仅两处非码面——`待办清单.md:648`(2026-10-18 卫生批历史登记条,记述批前缺陷实况)+`reports/需求卡片-…md` F4 行(申报本体)。弹窗文案随消费方退役已不在任何生产代码。 +- 分派器机制保留核实:`_on_confirm_accepted` match 机制在盘(卡④ 第三消费方接入面),`"delete_slot"` 臂在役(卡③ 删档确认);`"new_adventure"` 臂整段退役(diff 在案)——代码与实现报告「机制保留、消费方退役」口径一致。 +- `rng_service.new_adventure`/`adventure_runner` 等同名散点为 RNG 会话入口,与确认消费方无关(grep 逐条判别非残留)。 +- 主菜单写档路径清零佐证:mn2 四钮零命中(无入口)+`_start_adventure_from_menu` 直启形态(无确认链)+`open_camp_via_savesel` 写盘只落隔离槽文件——主菜单无任何写档路径(契约 4)。 + +### 卡② 结论 + +**通过**——卡② 主菜单三入口化批 14 行(mn1–mn14)全部通过(mn6/mn10/mn13 三行附例外注记,均有申报或契约依据,非缺漏)。变异 B 审计实测闭合(注入→5 红→还原→复绿),变异 A 处置链凭据齐且强化纯收紧;§7.2 七条申报外的既有断言零删改;回执 commit/all_green/domain 三项全对上。独立验收审计合并前置达成。 + +**例外注记汇总(不阻塞合并,随卡④ 落地消解)**: + +1. mn6:断点确认残留的拦截力现由 mn2+mn10 组合间接承接,卡④ 大门落地时应补大门侧专项残留断言(该断言原属卡④ 接收清单,两头登记在案)。 +2. mn10:生涯直调 3 处与 smoke `_resume_from_menu` 1 处为函数级登记、未逐位点列名——登记粒度微瑕,实质已登记。 +3. mn13:卡片「去掉菜单直调依赖」字面未兑现,按契约 5/§9.4 判中间态合法,报告已如实对账。 + +审计全程零 commit;变异注入经 `git checkout` 精确还原,`game/` 零残留(`git status` 仅本报告工作件)。