# VERTICAL SLICE - NOT FOR PRODUCTION # 演播后排窗定点探针(规则=「伤害过程演完才切战斗胜利结算窗」——该行为的唯一关联测试): # 真实信号路径驱动一场战斗到击杀胜利(结算行在排播队列),断言两条: # ①胜利同帧:终局面未开 + 挂起态在案 + 队列非空(旧行为=同帧 flush+开窗,本断言即红——判别性); # ②排播放完:终局面开启(演播完边沿派发——_on_pump_ended)。 # 存档/设置/元进度三面隔离(isolate_save 现行事实源,meta 经 autoload 链同源),零真实档触碰。headless 可跑(不抓帧)。 extends Node const ProbeUiUtil := preload("res://probe/probe_ui_util.gd") # 隔离样板单点 var main var _problems: Array = [] func _ready() -> void: await get_tree().process_frame main = load("res://presentation/main.tscn").instantiate() add_child(main) await get_tree().process_frame # 存档/设置两面隔离(ProbeUiUtil.isolate_save 现行事实源,先删后建防陈旧档泄入)—— # 隔离面走 attach_path 原地重置 autoload 单例:组合根 save_manager/meta 是getter-only # 读视图,直接赋值无效,隔离必须落在单例上。 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 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: break if main.level_screen == null or not main.level_screen.visible: _fail("关卡画面未出现") return for i in 5: await get_tree().process_frame main.level_screen.press_candidate(0) for i in 20: await get_tree().process_frame if main.run_scope.settlement == null: _fail("战斗未启动") return # 直挖至击杀胜利(settle_batch 真实管线——step_row 信号→排播队列,与点击路径同链) var ms = main.run_scope.settlement.minesweeper var guard := 200 var t_ap_out := -1 # AP 用完/停手帧 var ap_clock := "" # 墙上时钟时间戳 var settle_clock := "" while guard > 0 and String(main.run_scope.settlement.battle_result) == "": guard -= 1 var plan = load("res://sim/auto_player.gd").choose_dig(ms) if plan.is_empty(): main.run_scope.settlement.settle_batch([{"action": "stop", "cell": Vector2i(-1, -1)}]) await get_tree().process_frame continue if plan.hero_cell != ms.hero_pos(): ms.settle_batch([{"action": "move", "cell": plan.hero_cell}]) ms.settle_batch([{"action": "dig", "cell": plan.dig_cell}]) if t_ap_out < 0 and main.run_scope.settlement.action_economy.phase != "Active": t_ap_out = Time.get_ticks_msec() ap_clock = _clock_ms() settle_clock = ap_clock # 触发结算与 AP 用完同帧(核心同步)——同一墙上时刻 await get_tree().process_frame if String(main.run_scope.settlement.battle_result) != "victory": _fail("战斗未胜利(result='%s')" % str(main.run_scope.settlement.battle_result)) return var t_settle := Time.get_ticks_msec() var _rows_at_victory: int = main._row_pacer.pending_rows().size() # 时间戳①:结算入队完成帧(AP 耗尽/停手→五行段同步跑完→行全在队) # 断言①胜利同帧:终局面未开 + 挂起在案 + 队列非空(判别性——旧行为同帧开窗即红) if main._end_overlay.is_open(): _problems.append("胜利同帧终局面已开(演播后排窗失效——同帧 flush 旧路径回潮)") if main._deferred_modal == null: _problems.append("胜利同帧挂起态缺失(_deferred_modal == null)") if main._row_pacer.pending_rows().is_empty(): _problems.append("胜利同帧排播队列已空(结算行未走排播链)") var _played_rows: int = _rows_at_victory # 断言②排播放完:终局面开启(20s 守卫——0.22s×行数 ×10 余量) var waited := 0.0 while not main._end_overlay.is_open() and waited < 20.0: await get_tree().process_frame waited += get_process_delta_time() var t_popup := Time.get_ticks_msec() var popup_clock := _clock_ms() # 弹窗出现:墙上时钟时间戳 print("[t17_defer] AP点用完: %s" % ap_clock) print("[t17_defer] 触发结算: %s(与 AP 用完同帧——核心同步,相差 0ms)" % settle_clock) print("[t17_defer] 弹窗出现: %s(距触发结算 %d ms = 逐行演播时长,行数 %d)" % [popup_clock, t_popup - t_ap_out, _played_rows]) if not main._end_overlay.is_open(): _problems.append("排播放完后终局面未开(%.1fs——演播完边沿派发失守)" % waited) if _problems.is_empty(): print("[t17_defer] PASS — 胜利同帧挂起+排播完开窗 两断言全过(waited=%.1fs)" % waited) get_tree().quit(0) else: for p in _problems: print("[t17_defer] FAIL — " + p) get_tree().quit(2) func _clock_ms() -> String: var unix := Time.get_unix_time_from_system() return "%s.%03d" % [Time.get_datetime_string_from_system(), int(fmod(unix, 1.0) * 1000.0) % 1000] func _fail(why: String) -> void: print("[t17_defer] FAIL — " + why) get_tree().quit(2) func _find_button_by_sub(root: Node, sub: String) -> Button: if root is Button and String(root.text).contains(sub): return root for c in root.get_children(): var r := _find_button_by_sub(c, sub) if r != null: return r return null