iter54-卡1:QA harness 缺陷与覆盖遗漏收口批(A2 终局结算框 has_meta 容错+B1 停手真输入探针定型段与 journey 停手行为+B2 视图方法口 41 处全清转真输入+死接缝 11 方法清理+LevelMapScreen.candidate_rect 读口)——对照表 R1-R9 全绿(fx_probe/screen_flow_probe 零报错行/ui_click_probe 问题0 覆盖生长/单元套+系统测试+82 套件 all green/双模式 grep 零命中/journey 停手键 6 次),批末 --code 六闸全绿【验收通过】无新增红

This commit is contained in:
DKChan committed 2026-11-07 03:05:11 +08:00
1 parent a2e500a4af
commit 96123978c1
19 files changed
+279 -120

No files matched your search

-10
View File
@@ -189,20 +189,10 @@ func is_refresh_ready() -> bool:
return not _btn_refresh.disabled return not _btn_refresh.disabled
# 探针接缝(合成输入走真实翻译出口口径):刷新按钮点击 → 确认 chips 全链路
func press_refresh_for_probe() -> void:
_on_refresh_pressed()
func confirm_visible() -> bool: func confirm_visible() -> bool:
return _confirm_row.visible return _confirm_row.visible
func press_confirm_for_probe() -> void:
if _arm_timer.is_armed():
_disarm_and_fire()
func refresh_remaining_text() -> String: func refresh_remaining_text() -> String:
return _refresh_sub.text return _refresh_sub.text
@@ -191,10 +191,11 @@ func close() -> void:
st.seal.visible = false st.seal.visible = false
st.seal.modulate = Color.WHITE st.seal.modulate = Color.WHITE
st.cele.visible = false st.cele.visible = false
var tw: Tween = get_meta(&"celebrate_tw", null) # 循环句柄随节点存(跨 open/close,close 必杀) if has_meta(&"celebrate_tw"): # 循环句柄随节点存(跨 open/close:有则必杀、无则无事——缺 meta 跳过)
if tw != null and tw.is_valid(): var tw: Tween = get_meta(&"celebrate_tw")
tw.kill() if tw.is_valid():
remove_meta(&"celebrate_tw") tw.kill()
remove_meta(&"celebrate_tw")
_clear_crest_bricks() _clear_crest_bricks()
@@ -328,9 +329,10 @@ func _spawn_dust_mote(i: int, base: Vector2) -> void:
func _play_celebrate() -> void: func _play_celebrate() -> void:
if UiKit.motion_scale < 1.0: if UiKit.motion_scale < 1.0:
return return
var prev: Tween = get_meta(&"celebrate_tw", null) if has_meta(&"celebrate_tw"): # 有句柄才取并 kill(缺 meta 静默跳过——败走后首次胜利同款容错)
if prev != null and prev.is_valid(): var prev: Tween = get_meta(&"celebrate_tw")
prev.kill() if prev.is_valid():
prev.kill()
var tw := st.cele.create_tween() var tw := st.cele.create_tween()
set_meta(&"celebrate_tw", tw) set_meta(&"celebrate_tw", tw)
tw.set_loops() tw.set_loops()
@@ -153,26 +153,6 @@ func _on_confirm_pressed() -> void:
pick_confirmed.emit(_selected) pick_confirmed.emit(_selected)
# 探针/测试复用:以真实按钮路径触发点选、确认与继续(树序遮挡纪律——断言走真实信号路径)
func press_card(index: int) -> void:
# M7:卡载体 Button→bcard(gui_input 点选)——真实路径=同一 _select_card 处理器,探针语义不变
if index >= 0 and index < _card_meta.size():
_select_card(String(_card_meta[index].get("id", "")))
func press_confirm() -> void:
_confirm_btn.pressed.emit()
func press_continue() -> void:
_continue_btn.pressed.emit()
# 探针/测试复用:以真实按钮路径触发刷新意图(信号外发——受理归组合根)
func press_refresh() -> void:
_refresh_btn.pressed.emit()
# 兑付反馈态(curio_resolved 到达——结果行追加在头部之后;唯一出口 = 继续) # 兑付反馈态(curio_resolved 到达——结果行追加在头部之后;唯一出口 = 继续)
func show_result(text: String) -> void: func show_result(text: String) -> void:
_label.text = _header + "\n" + text _label.text = _header + "\n" + text
-14
View File
@@ -123,20 +123,6 @@ func output_text() -> String:
return st.sim_out.get_parsed_text() return st.sim_out.get_parsed_text()
# ---- 探针接缝(真实翻译出口——按钮 pressed 信号路径,非直调组合根)----
func press_apply_for_probe() -> void:
_apply_btn_pressed()
func press_reset_for_probe() -> void:
_reset_btn_pressed()
func press_simulate_for_probe() -> void:
_sim_btn.pressed.emit()
# ---- 内部(信号 → 外发)---- # ---- 内部(信号 → 外发)----
func _apply_btn_pressed() -> void: func _apply_btn_pressed() -> void:
+9 -7
View File
@@ -465,18 +465,20 @@ func _submit_choice(nid: int) -> void:
route_chosen.emit(nid) route_chosen.emit(nid)
# 探针复用:按候选序号走图上命中同路径(候选节点中心坐标 → 命中检测 → 提交——布局决策 2 探针面改造) # 探针面命中矩形读口:候选节点的图上命中矩形(全局坐标——真输入 push_click_at 落点)。
func press_candidate(index: int) -> void: # 命中面与 _candidate_at 同源(节点方块+外扩容差);index 越界或候选未就绪返回空 Rect2(调用方跳过)。
func candidate_rect(index: int) -> Rect2:
if index < 0 or index >= _cand_ids.size() or _map.runner == null: if index < 0 or index >= _cand_ids.size() or _map.runner == null:
return return Rect2()
var nodes: Array[Variant] = _map.runner.nodes() var nodes: Array[Variant] = _map.runner.nodes()
var node := _node_by_id(int(_cand_ids[index]), nodes) var node := _node_by_id(int(_cand_ids[index]), nodes)
if node.is_empty(): if node.is_empty():
return return Rect2()
var pos: Vector2 = _layout(nodes, _map.canvas)[node] var pos: Vector2 = _layout(nodes, _map.canvas)[node]
var nid := _candidate_at(pos) # 节点中心必命中自身(命中面含节点方块) var half := NODE_HALF + Vector2(HIT_PAD, HIT_PAD)
if nid >= 0: var local := Rect2(pos - half, half * 2.0)
_submit_choice(nid) var xform := _map.canvas.get_global_transform()
return Rect2(xform * local.position, local.size)
# ---- 视口锚点(布局决策 5:当前节点居中;入口停点尚无当前节点=锚入口组包围盒居中) ---- # ---- 视口锚点(布局决策 5:当前节点居中;入口停点尚无当前节点=锚入口组包围盒居中) ----
-4
View File
@@ -29,10 +29,6 @@ func show_rest(healed: int, hp_after: int, max_hp: int) -> void:
_btn.text = "休息完毕,继续" _btn.text = "休息完毕,继续"
visible = true visible = true
# 探针复用:以真实按钮路径触发继续
func press_continue() -> void:
_btn.pressed.emit()
func _emit_continue_pressed() -> void: func _emit_continue_pressed() -> void:
continue_pressed.emit() continue_pressed.emit()
+1 -1
View File
@@ -45,7 +45,7 @@ func _ready() -> void:
return return
for i in 5: for i in 5:
await get_tree().process_frame await get_tree().process_frame
main.ui.modals.run.level_screen.press_candidate(0) ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
for i in 20: for i in 20:
await get_tree().process_frame await get_tree().process_frame
var st = main.run_scope.settlement var st = main.run_scope.settlement
+1 -1
View File
@@ -30,7 +30,7 @@ func _ready() -> void:
for i in range(8): for i in range(8):
await get_tree().process_frame await get_tree().process_frame
# 进战斗屏——三区(B 战斗画面/C 角色信息/D 扫雷图)同屏常驻,菜单屏不构成断言面 # 进战斗屏——三区(B 战斗画面/C 角色信息/D 扫雷图)同屏常驻,菜单屏不构成断言面
main.ui.modals.run.level_screen.press_candidate(0) ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
for i in range(20): for i in range(20):
await get_tree().process_frame await get_tree().process_frame
for vp in VIEWPORTS: for vp in VIEWPORTS:
+3 -3
View File
@@ -6,7 +6,7 @@
# 流程 = 真实按钮信号路径驱动(ui_click_probe 同款;存档/设置/元进度三面换注隔离档——禁碰真实档, # 流程 = 真实按钮信号路径驱动(ui_click_probe 同款;存档/设置/元进度三面换注隔离档——禁碰真实档,
# 坑册「断点写盘随 save_manager 引用走」教训同款覆盖):菜单(启动态)→ 设置面(settingsbtn # 坑册「断点写盘随 save_manager 引用走」教训同款覆盖):菜单(启动态)→ 设置面(settingsbtn
# 真实信号路径)→ 生涯页(函数直调中间态——卡② mn10)→ 营地(进档开层新路径——mn9)→ 关卡画面(函数直调中间态→停点出图)→ # 真实信号路径)→ 生涯页(函数直调中间态——卡② mn10)→ 营地(进档开层新路径——mn9)→ 关卡画面(函数直调中间态→停点出图)→
# 战斗(level_screen.press_candidate(0) 真实选路提交)→ 状态面板(地图屏状态角标真实信号路径)。 # 战斗(level_screen candidate_rect 命中矩形真点选路提交)→ 状态面板(地图屏状态角标真实信号路径)。
# 每屏双帧 + frame_post_draw 同步后抓 get_viewport().get_texture(),PNG 落输出目录; # 每屏双帧 + frame_post_draw 同步后抓 get_viewport().get_texture(),PNG 落输出目录;
# 最小自检 = 七件存在且非零字节(像素方差断言归 tools/shot_checker.py——B 档骨架分工)。 # 最小自检 = 七件存在且非零字节(像素方差断言归 tools/shot_checker.py——B 档骨架分工)。
# 商店房不纳屏(真实路径口径):真实路径必经 L1 打穿→L2 商店节点 # 商店房不纳屏(真实路径口径):真实路径必经 L1 打穿→L2 商店节点
@@ -149,10 +149,10 @@ func _ready() -> void:
var expect := "◈ %d" % int(main.ui.modals.run.level_screen.get("_runner").gp) var expect := "◈ %d" % int(main.ui.modals.run.level_screen.get("_runner").gp)
if gp_label.text != expect: if gp_label.text != expect:
_problems.append("GP 徽章读数不符: %s != %s" % [gp_label.text, expect]) _problems.append("GP 徽章读数不符: %s != %s" % [gp_label.text, expect])
# ④ 战斗(节点一——真实选路提交路径 press_candidate,ui_click_probe :199 同款) # ④ 战斗(节点一——candidate_rect 命中矩形真点选路提交,ui_click_probe 同款)
# oracle: design/gdd/ux/battle.md:122 B 区构建:站桩/明细带/轮次指示(战斗出图与几何锚组) # oracle: design/gdd/ux/battle.md:122 B 区构建:站桩/明细带/轮次指示(战斗出图与几何锚组)
if main.ui.modals.run.level_screen != null and main.ui.modals.run.level_screen.visible: if main.ui.modals.run.level_screen != null and main.ui.modals.run.level_screen.visible:
main.ui.modals.run.level_screen.press_candidate(0) ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
for i in 10: for i in 10:
await get_tree().process_frame await get_tree().process_frame
await _snap("battle") await _snap("battle")
+1 -1
View File
@@ -37,7 +37,7 @@ func _ready() -> void:
return return
for i in 5: for i in 5:
await get_tree().process_frame await get_tree().process_frame
main.ui.modals.run.level_screen.press_candidate(0) ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
for i in 20: for i in 20:
await get_tree().process_frame await get_tree().process_frame
if main.run_scope.settlement == null: if main.run_scope.settlement == null:
+78 -18
View File
@@ -40,6 +40,8 @@ var _route_clicks := 0 # 选路覆盖层点击计数(岔路使用
var _locktest_state := 0 # T1 D1 锁期/重放断言状态机(0=待注入 1=等重放 2=完结)——每 run 一次 var _locktest_state := 0 # T1 D1 锁期/重放断言状态机(0=待注入 1=等重放 2=完结)——每 run 一次
var _locktest_cell := Vector2i(-9, -9) var _locktest_cell := Vector2i(-9, -9)
var _locktest_frames := 0 var _locktest_frames := 0
var _stoptest_state := 0 # 停手钮定型化覆盖段状态机(0=待命 1=已点击待核效 2=完结)——每 run 一次
var _stoptest_frames := 0
func _ready() -> void: func _ready() -> void:
@@ -421,13 +423,13 @@ func _ready() -> void:
var wc3_txt: String = main.ui.battle.log_view._log.get_parsed_text() var wc3_txt: String = main.ui.battle.log_view._log.get_parsed_text()
if not wc3_txt.contains("种子 42") or not wc3_txt.contains("(含面板加成)"): if not wc3_txt.contains("种子 42") or not wc3_txt.contains("(含面板加成)"):
_problems.append("开场行回归失守:局外加成(pct>0)局的开场行应带「(含面板加成)」(wc3)") _problems.append("开场行回归失守:局外加成(pct>0)局的开场行应带「(含面板加成)」(wc3)")
# 关卡开始 = 入口选路停点——press_candidate 图上命中路径选入口候选进首战 # 关卡开始 = 入口选路停点——candidate_rect 命中矩形中心真点选入口候选进首战
#(首格可点 AC 口径随之含入口选路一步:菜单点击 → 停点呈现 → pick → 棋盘安装) #(首格可点 AC 口径随之含入口选路一步:菜单点击 → 停点呈现 → pick → 棋盘安装)
if main.ui.modals.run.level_screen.visible: if main.ui.modals.run.level_screen.visible:
# 独立性断言:独立屏激活期战斗屏宿主整体隐藏 # 独立性断言:独立屏激活期战斗屏宿主整体隐藏
if main.ui.screens.battle_root.visible: if main.ui.screens.battle_root.visible:
_problems.append("独立屏激活期战斗屏宿主未隐藏(零透出契约失守)") _problems.append("独立屏激活期战斗屏宿主未隐藏(零透出契约失守)")
main.ui.modals.run.level_screen.press_candidate(0) ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
await get_tree().process_frame await get_tree().process_frame
await get_tree().process_frame await get_tree().process_frame
var t_entry := Time.get_ticks_msec() - t1 var t_entry := Time.get_ticks_msec() - t1
@@ -449,7 +451,7 @@ func _ready() -> void:
action_budget -= 1 action_budget -= 1
await get_tree().process_frame await get_tree().process_frame
if main.ui.modals.run.node_overlay.visible: if main.ui.modals.run.node_overlay.visible:
main.ui.modals.run.node_overlay.press_continue() ProbeUiUtil.push_click(main, main.ui.modals.run.node_overlay._btn)
_seals_placed.clear() _seals_placed.clear()
continue continue
if main.ui.modals.run.curio_overlay.visible: if main.ui.modals.run.curio_overlay.visible:
@@ -474,15 +476,17 @@ func _ready() -> void:
if not main.ui.flow.locks_input(): if not main.ui.flow.locks_input():
_problems.append("三选一停点打开时输入未锁定") _problems.append("三选一停点打开时输入未锁定")
if main.ui.modals.run.curio_overlay._continue_btn.visible: if main.ui.modals.run.curio_overlay._continue_btn.visible:
main.ui.modals.run.curio_overlay.press_continue() ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._continue_btn)
elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._node_pending.is_empty(): elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._node_pending.is_empty():
main.ui.modals.run.curio_overlay.press_card(0) # 奖励节点待选态:点首件+确认(真实按钮信号路径——与关卡完成停点同形) ProbeUiUtil.push_click_at(main, (main.ui.modals.run.curio_overlay._bcards[0] as Control).get_global_rect().get_center()) # 奖励节点待选态:真点首卡+确认(gui_input 点选——与关卡完成停点同形)
main.ui.modals.run.curio_overlay.press_confirm() await get_tree().process_frame # 选卡当帧确认钮才现形、容器重排在途——排版帧后矩形成形再真点
ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._confirm_btn)
elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._pending.is_empty(): elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._pending.is_empty():
main.ui.modals.run.curio_overlay.press_card(0) ProbeUiUtil.push_click_at(main, (main.ui.modals.run.curio_overlay._bcards[0] as Control).get_global_rect().get_center())
main.ui.modals.run.curio_overlay.press_confirm() await get_tree().process_frame # 同上:确认钮排版帧
ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._confirm_btn)
else: else:
main.ui.modals.run.curio_overlay.press_continue() # 停点已消费的残面——继续关屏 ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._continue_btn) # 停点已消费的残面——继续关屏
_seals_placed.clear() _seals_placed.clear()
continue continue
if main.ui.modals.menu.shop_overlay.visible: if main.ui.modals.menu.shop_overlay.visible:
@@ -537,7 +541,7 @@ func _ready() -> void:
if String(nd.get("type", "")) == "reward" and main.ui.modals.run.level_screen._cand_ids.has(nd_id): if String(nd.get("type", "")) == "reward" and main.ui.modals.run.level_screen._cand_ids.has(nd_id):
pick = main.ui.modals.run.level_screen._cand_ids.find(nd_id) pick = main.ui.modals.run.level_screen._cand_ids.find(nd_id)
break break
main.ui.modals.run.level_screen.press_candidate(pick) ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(pick).get_center())
_route_clicks += 1 _route_clicks += 1
_seals_placed.clear() _seals_placed.clear()
continue continue
@@ -626,11 +630,13 @@ func _ready() -> void:
_ap_spent_this_round = 0 _ap_spent_this_round = 0
_last_state = st _last_state = st
_locktest_tick() _locktest_tick()
_stoptest_tick()
if main.run_scope.settlement.battle_result == "" and main.run_scope.settlement.action_economy.phase == "Active": if main.run_scope.settlement.battle_result == "" and main.run_scope.settlement.action_economy.phase == "Active":
# T1 D1——锁测在飞期(state 1)冻结探针动作面:排空帧 autoplay 的免费 move 会抢在 # T1 D1——锁测在飞期(state 1)冻结探针动作面:排空帧 autoplay 的免费 move 会抢在
# 帧末 deferred 重放之前改变英雄位(seal 拒收 not_adjacent 实证)——「注入→重放」 # 帧末 deferred 重放之前改变英雄位(seal 拒收 not_adjacent 实证)——「注入→重放」
# 窗口内局面零变化正是 v3.1 被测语义(演播期冻结),动作面恢复待断言收口。 # 窗口内局面零变化正是 v3.1 被测语义(演播期冻结),动作面恢复待断言收口。
if _locktest_state != 1: # 停手定型化段同款冻结:核效窗口内零挖掘提交,排除 AP/盘面漂移对收轮判据的混淆。
if _locktest_state != 1 and _stoptest_state != 1:
_run_modal_probes_once() _run_modal_probes_once()
_play_one_action() _play_one_action()
# ---- 预算耗尽收口(T17 硬锁批适配):选件变强后 probe 可能活过预算不再战败——断点段前提 # ---- 预算耗尽收口(T17 硬锁批适配):选件变强后 probe 可能活过预算不再战败——断点段前提
@@ -651,14 +657,15 @@ func _ready() -> void:
break break
elif main.ui.modals.run.curio_overlay.visible: elif main.ui.modals.run.curio_overlay.visible:
if main.ui.modals.run.curio_overlay._continue_btn.visible: if main.ui.modals.run.curio_overlay._continue_btn.visible:
main.ui.modals.run.curio_overlay.press_continue() ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._continue_btn)
elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._pending.is_empty(): elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._pending.is_empty():
main.ui.modals.run.curio_overlay.press_card(0) ProbeUiUtil.push_click_at(main, (main.ui.modals.run.curio_overlay._bcards[0] as Control).get_global_rect().get_center())
main.ui.modals.run.curio_overlay.press_confirm() await get_tree().process_frame # 选卡当帧确认钮才现形、容器重排在途——排版帧后矩形成形再真点
ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._confirm_btn)
else: else:
main.ui.modals.run.curio_overlay.press_continue() ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._continue_btn)
elif main.ui.modals.run.node_overlay.visible: elif main.ui.modals.run.node_overlay.visible:
main.ui.modals.run.node_overlay.press_continue() ProbeUiUtil.push_click(main, main.ui.modals.run.node_overlay._btn)
elif main.ui.modals.menu.shop_overlay.visible: elif main.ui.modals.menu.shop_overlay.visible:
var shop_leave2: Button = ProbeUiUtil.find_button(main.ui.modals.menu.shop_overlay, "离开商店") var shop_leave2: Button = ProbeUiUtil.find_button(main.ui.modals.menu.shop_overlay, "离开商店")
if shop_leave2 != null: if shop_leave2 != null:
@@ -978,10 +985,13 @@ func _run_modal_probes_once() -> void:
_tested_refresh_chain = true _tested_refresh_chain = true
var rap_before: int = int(main.run_scope.settlement.action_economy.rap) var rap_before: int = int(main.run_scope.settlement.action_economy.rap)
var board_before = main.run_scope.settlement.minesweeper.current_board() var board_before = main.run_scope.settlement.minesweeper.current_board()
main.ui.battle.board_zone.ap_bar.press_refresh_for_probe() ProbeUiUtil.push_click(main, main.ui.battle.board_zone.ap_bar._btn_refresh) # 真点弃图刷新(disabled 守卫=真实拒绝路径)
if not main.ui.battle.board_zone.ap_bar.confirm_visible(): if not main.ui.battle.board_zone.ap_bar.confirm_visible():
_problems.append("弃图刷新确认 chips 未出现(异位确认缺失)") _problems.append("弃图刷新确认 chips 未出现(异位确认缺失)")
main.ui.battle.board_zone.ap_bar.press_confirm_for_probe() for c in main.ui.battle.board_zone.ap_bar._confirm_row.get_children():
if c is Button:
ProbeUiUtil.push_click(main, c) # 真点确认 chips 真钮(异位确认——_confirm_row 内 Button 子件)
break
if int(main.run_scope.settlement.action_economy.rap) != rap_before - 8: if int(main.run_scope.settlement.action_economy.rap) != rap_before - 8:
_problems.append("弃图刷新未扣 RAP8(%d→%d)" % [rap_before, int(main.run_scope.settlement.action_economy.rap)]) _problems.append("弃图刷新未扣 RAP8(%d→%d)" % [rap_before, int(main.run_scope.settlement.action_economy.rap)])
if main.run_scope.settlement.action_economy.phase != "Active": if main.run_scope.settlement.action_economy.phase != "Active":
@@ -1155,6 +1165,56 @@ func _locktest_pick_cell(minesweeper) -> Vector2i:
return Vector2i(-1, -1) return Vector2i(-1, -1)
# ---- 停手钮定型化覆盖段(B1——每 run 一次:使能窗内真点停手钮,钉命中登记与收轮落效) ----
# 触发条件=战局挖掘期(相位 Active+停手钮使能+盘面有挖可走+本 run 未测过);触发帧冻结
# 自动玩家其余随机行为,仅提交停手(驱动与 _click_stop 同型——AP 条真钮 push_input)。
# 断言两钉:①覆盖登记件停手键在场(零命中=驱动空转假绿,即红);②相位离开 Active(停手
# 收轮)。前提消失守卫:点击后战局收场(终结演播竞态)→ 静默弃测不计 problem(正常局不触发)。
func _stoptest_tick() -> void:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:167 (停手=玩家主动收轮——判定上报通道)
if _stoptest_state == 2:
return
var settlement = main.run_scope.settlement
if settlement == null or settlement.minesweeper == null or settlement.minesweeper.current_board() == null:
return
if _stoptest_state == 0:
if String(settlement.battle_result) != "" or main.ui.modals.run.end_overlay.is_open():
return # 战局已收场——本 run 无停手窗(静置)
if not main.ui.screens.row_pacer.pending_rows().is_empty():
return # 演播期不出手:点击会进锁期缓存被吞(同 _locktest_tick 前提)
var ae = settlement.action_economy
if String(ae.phase) != "Active" or int(ae.ap) < 1:
return
var stop_btn: Button = main.ui.battle.board_zone.ap_bar._btn_stop
if stop_btn == null or stop_btn.disabled:
return # 停手钮未使能——下帧重试
if AutoPlayer.choose_dig(settlement.minesweeper).is_empty():
return # 盘面已无可挖——不算挖掘期触发窗(防清图混淆)
_stoptest_state = 1
_stoptest_frames = 0
_click_stop()
elif _stoptest_state == 1:
_stoptest_frames += 1
if _stoptest_frames > 120:
_problems.append("停手定型化段:点击后 120 帧相位仍 Active(收轮落效失守)〔QA收口 V2〕")
_stoptest_state = 2
return
if String(settlement.battle_result) != "" or main.ui.modals.run.end_overlay.is_open():
_stoptest_state = 2 # 前提消失守卫:终结演播竞态——静默弃测
return
if String(settlement.action_economy.phase) != "Active":
_stoptest_assert_hit()
_stoptest_state = 2
func _stoptest_assert_hit() -> void:
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:167 (停手钮真输入命中登记——判定上报通道)
var stop_btn: Button = main.ui.battle.board_zone.ap_bar._btn_stop
var stop_key := String(_recorder._node_keys.get(stop_btn.get_instance_id(), ""))
if stop_key == "" or not _recorder._hits.has(stop_key):
_problems.append("停手定型化段:停手钮真点零命中登记(驱动空转假绿)〔QA收口 V2〕")
func _report_stall(st: Dictionary) -> void: func _report_stall(st: Dictionary) -> void:
# oracle: design/engineering/test-conventions.md:30 (判定上报通道——验证函数组) # oracle: design/engineering/test-conventions.md:30 (判定上报通道——验证函数组)
var why := "" var why := ""
+2 -2
View File
@@ -75,7 +75,7 @@ func _ready() -> void:
await get_tree().process_frame await get_tree().process_frame
await get_tree().process_frame await get_tree().process_frame
# ---- 入口选路停点(关卡开始即出节点图——level_started 后、首战前弹选路层; # ---- 入口选路停点(关卡开始即出节点图——level_started 后、首战前弹选路层;
# press_candidate 图上命中路径 pick 入口单候选进首战)---- # candidate_rect 命中矩形真点 pick 入口单候选进首战)----
# oracle: design/gdd/ux/level-map.md:11 入口停点候选数=入口个数(入口单候选断言) # oracle: design/gdd/ux/level-map.md:11 入口停点候选数=入口个数(入口单候选断言)
# ---- 独立性断言(独立屏激活期战斗屏宿主整体隐藏,零透出)---- # ---- 独立性断言(独立屏激活期战斗屏宿主整体隐藏,零透出)----
if not main.ui.modals.run.level_screen.visible: if not main.ui.modals.run.level_screen.visible:
@@ -86,7 +86,7 @@ func _ready() -> void:
or main.run_scope.runner.next_candidate_ids().size() != 1: or main.run_scope.runner.next_candidate_ids().size() != 1:
problems.append("入口停点状态失守(选路态 + 入口单候选)") problems.append("入口停点状态失守(选路态 + 入口单候选)")
else: else:
main.ui.modals.run.level_screen.press_candidate(0) ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
await get_tree().process_frame await get_tree().process_frame
await get_tree().process_frame await get_tree().process_frame
if not main.ui.screens.battle_root.visible or main.ui.modals.run.level_screen.visible: if not main.ui.screens.battle_root.visible or main.ui.modals.run.level_screen.visible:
+1 -1
View File
@@ -272,7 +272,7 @@ func _ready() -> void:
if not main.ui.modals.run.node_overlay.visible: if not main.ui.modals.run.node_overlay.visible:
problems.append("[node] 节点结算层未打开(rest 兑付路径中断)") problems.append("[node] 节点结算层未打开(rest 兑付路径中断)")
await _audit("node_overlay", [main.ui.modals.run.node_overlay]) await _audit("node_overlay", [main.ui.modals.run.node_overlay])
main.ui.modals.run.node_overlay.press_continue() ProbeUiUtil.push_click(main, main.ui.modals.run.node_overlay._btn)
_settle() _settle()
if main.ui.modals.run.node_overlay.visible: if main.ui.modals.run.node_overlay.visible:
problems.append("[node] 关闭后层未隐藏(continue_pressed 路径中断)") problems.append("[node] 关闭后层未隐藏(continue_pressed 路径中断)")
+42 -6
View File
@@ -58,16 +58,47 @@ func _ready() -> void:
problems.append("生效预览未联动(%s)" % main.ui.modals.menu.debug_overlay.effect_text()) problems.append("生效预览未联动(%s)" % main.ui.modals.menu.debug_overlay.effect_text())
# ③ 应用(真实信号路径:apply_requested → 组合根)——E4 后加成零表写,生效断言移至 ⑧ 战斗快照 # ③ 应用(真实信号路径:apply_requested → 组合根)——E4 后加成零表写,生效断言移至 ⑧ 战斗快照
# oracle: design/engineering/test-conventions.md:49 调试面加成链路:滑条联动/应用/重置、hero 表运行期只读(②③④⑦组) # oracle: design/engineering/test-conventions.md:49 调试面加成链路:滑条联动/应用/重置、hero 表运行期只读(②③④⑦组)
main.ui.modals.menu.debug_overlay.press_apply_for_probe() var apply_btn: Button = ProbeUiUtil.find_button(main.ui.modals.menu.debug_overlay, "应用并关闭")
if apply_btn == null:
problems.append("调试浮窗「应用并关闭」钮缺失")
else:
ProbeUiUtil.push_click(main, apply_btn) # 真点应用(组合根应用=关浮窗——落点断言由 ④ 重开承载)
# ④ 重置(重开浮窗 → 重置按钮真实路径;面板百分比归零——下一场战斗生效值回到基线) # ④ 重置(重开浮窗 → 重置按钮真实路径;面板百分比归零——下一场战斗生效值回到基线)
main._show_debug_overlay() main._show_debug_overlay()
main.ui.modals.menu.debug_overlay.press_reset_for_probe() var reset_btn: Button = ProbeUiUtil.find_button(main.ui.modals.menu.debug_overlay, "重置为 0")
if reset_btn == null:
problems.append("调试浮窗「重置为 0」钮缺失")
else:
ProbeUiUtil.push_click(main, reset_btn)
# ⑤ 批量模拟全链路(N=10——走 level_autoplay 与 headless 扫描同口径;表基线由组合根复位; # ⑤ 批量模拟全链路(N=10——走 level_autoplay 与 headless 扫描同口径;表基线由组合根复位;
# 菜单态上下文未锁定=按钮可点,is_running 语义=模拟在跑〔18卡⑦ 与对局禁用态拆分〕) # 菜单态上下文未锁定=按钮可点,is_running 语义=模拟在跑〔18卡⑦ 与对局禁用态拆分〕)
# oracle: design/engineering/test-conventions.md:49 调试面批量模拟链路:N=10 输出关卡完成率/剩余 HP/败北节点(⑤组) # oracle: design/engineering/test-conventions.md:49 调试面批量模拟链路:N=10 输出关卡完成率/剩余 HP/败北节点(⑤组)
main.ui.modals.menu.debug_overlay.set_sliders(100, 0) main.ui.modals.menu.debug_overlay.set_sliders(100, 0)
main.ui.modals.menu.debug_overlay.set_sim_count(10) main.ui.modals.menu.debug_overlay.set_sim_count(10)
main.ui.modals.menu.debug_overlay.press_simulate_for_probe() # 折叠区控件真输入通道(test-conventions §2——先滚轮滚入可视区再点):sim 钮在滚动列表 boot
# 滚位下跨出视口底边(rect y=639 对 scroll 底 641、钮高 40——hit 面被裁),真点必落空;
# 滚轮下探滚入可视区后真点(实证一档入视口、同帧 is_running 翻真)
var sim_btn: Button = main.ui.modals.menu.debug_overlay._sim_btn
var sim_scroll: ScrollContainer = null
var sim_stack: Array = [main.ui.modals.menu.debug_overlay]
while not sim_stack.is_empty():
var sn: Node = sim_stack.pop_back()
if sn is ScrollContainer:
sim_scroll = sn
break
for sch in sn.get_children():
sim_stack.append(sch)
if sim_scroll == null:
problems.append("调试浮窗滚动列表缺失(sim 钮滚入通道无载体)")
else:
for i in 12:
if sim_scroll.get_global_rect().encloses(sim_btn.get_global_rect()):
break
ProbeUiUtil.push_wheel(main, sim_scroll.get_global_rect().get_center(), 1)
await get_tree().process_frame
if not sim_scroll.get_global_rect().encloses(sim_btn.get_global_rect()):
problems.append("sim 钮滚轮滚入可视区失败(12 档未入——折叠区真输入通道断)")
ProbeUiUtil.push_click(main, sim_btn) # 真点「运行模拟」(菜单态未锁可点——disabled 守卫=真实拒绝路径)
var guard := SIM_GUARD_FRAMES var guard := SIM_GUARD_FRAMES
while main.ui.modals.menu.debug_overlay.is_running() and guard > 0: while main.ui.modals.menu.debug_overlay.is_running() and guard > 0:
guard -= 1 guard -= 1
@@ -97,7 +128,12 @@ func _ready() -> void:
# isolate_save 先删后建——上次运行的残留断点会触发互斥确认早退) # isolate_save 先删后建——上次运行的残留断点会触发互斥确认早退)
ProbeUiUtil.isolate_save(main, "smoke_tune_probe_save", true, false, true) # 末参=swap_save(卡②签名换代——断点写盘经 autoload 落隔离档) ProbeUiUtil.isolate_save(main, "smoke_tune_probe_save", true, false, true) # 末参=swap_save(卡②签名换代——断点写盘经 autoload 落隔离档)
main.ui.modals.menu.debug_overlay.set_sliders(100, 50) main.ui.modals.menu.debug_overlay.set_sliders(100, 50)
main.ui.modals.menu.debug_overlay.press_apply_for_probe() main._show_debug_overlay() # ⑥ 关闭后重开——⑧ 应用真点(应用=组合根关窗,⑧b 显式关浮窗断言照旧承载)
var apply_btn2: Button = ProbeUiUtil.find_button(main.ui.modals.menu.debug_overlay, "应用并关闭")
if apply_btn2 == null:
problems.append("调试浮窗「应用并关闭」钮缺失(⑧)")
else:
ProbeUiUtil.push_click(main, apply_btn2)
main.ui.modals.menu.debug_overlay.seed_edit.text = "42" # 种子钉死(18卡⑦审计🔴——空框=非首局随机,显式优先经真实路径断言 master_seed) main.ui.modals.menu.debug_overlay.seed_edit.text = "42" # 种子钉死(18卡⑦审计🔴——空框=非首局随机,显式优先经真实路径断言 master_seed)
main._start_adventure_from_menu() # 完整冒险路径(唯一试玩入口;_start_single 按语义清零滑条 = 恒基线,不承载加成断言) main._start_adventure_from_menu() # 完整冒险路径(唯一试玩入口;_start_single 按语义清零滑条 = 恒基线,不承载加成断言)
await get_tree().process_frame await get_tree().process_frame
@@ -107,9 +143,9 @@ func _ready() -> void:
problems.append("开冒险路径未显式关浮窗(T2 防护失守)") problems.append("开冒险路径未显式关浮窗(T2 防护失守)")
if int(main.run_scope.adventure.master_seed) != 42: if int(main.run_scope.adventure.master_seed) != 42:
problems.append("显式种子未达冒险(master_seed=%s ≠ 42——真实路径显式优先失守)" % str(main.run_scope.adventure.master_seed)) problems.append("显式种子未达冒险(master_seed=%s ≠ 42——真实路径显式优先失守)" % str(main.run_scope.adventure.master_seed))
# 关卡开始 = 入口选路停点——真实按钮路径选入口候选进首战再读呈现快照 # 关卡开始 = 入口选路停点——candidate_rect 命中矩形真点选入口候选进首战再读呈现快照
if main.ui.modals.run.level_screen.visible: if main.ui.modals.run.level_screen.visible:
main.ui.modals.run.level_screen.press_candidate(0) ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
await get_tree().process_frame await get_tree().process_frame
await get_tree().process_frame await get_tree().process_frame
var hero: Dictionary = main.run_scope.settlement.presentation_snapshot().hero var hero: Dictionary = main.run_scope.settlement.presentation_snapshot().hero
+42 -16
View File
@@ -10,9 +10,9 @@
# 真实输入-场景热点/真实输入-键盘/方法-地图命中口/方法-棋盘口)。原归因制度残骸=本结构内字段。 # 真实输入-场景热点/真实输入-键盘/方法-地图命中口/方法-棋盘口)。原归因制度残骸=本结构内字段。
# 误报收敛:误报指纹(类+段+归因摘要)落 .gates/coverage/journey_misfire_ledger.json——同一指纹 # 误报收敛:误报指纹(类+段+归因摘要)落 .gates/coverage/journey_misfire_ledger.json——同一指纹
# 第 2 次起自动登记静默(计数照记、不再进上报清单);连 3 批零真缺陷判据收紧归用户复核轮。 # 第 2 次起自动登记静默(计数照记、不再进上报清单);连 3 批零真缺陷判据收紧归用户复核轮。
# 驱动纪律:按钮/场景物件全走真输入(push_input 管线,同探针判据——ProbeUiUtil 单点通道)。 # 驱动纪律:按钮/场景物件/地图候选全走真输入(push_input 管线,同探针判据——ProbeUiUtil 单点通道;
# 两处方法口如实标注通道:地图候选=press_candidate(自绘布局命中路径,探针族既定入口); # 地图候选=LevelMapScreen.candidate_rect 命中矩形中心 push_click_at——方法口已清零)。
# 棋盘格=click_cell(BoardView 公共点击口,ui_input_probe 先例——棋盘管线面由该探针承载)。 # 棋盘格=click_cell(BoardView 公共点击口,ui_input_probe 先例——棋盘管线面由该探针承载)=余下唯一方法口。
# 停点分发的视图内部读数(_continue_btn/_node_pending 等)沿用探针族直读受控例外(iter53 反哺点 9 同款在册)。 # 停点分发的视图内部读数(_continue_btn/_node_pending 等)沿用探针族直读受控例外(iter53 反哺点 9 同款在册)。
# 运行:Godot_console.exe --headless --path . res://sim/journey_player.tscn # 运行:Godot_console.exe --headless --path . res://sim/journey_player.tscn
extends Node extends Node
@@ -41,6 +41,10 @@ var _map_status_done := false # 地图屏状态角标一次性
var _legend_done := false # 图例开合一次性(每战斗面一次) var _legend_done := false # 图例开合一次性(每战斗面一次)
var _status_done := false # 战斗屏状态面板一次性 var _status_done := false # 战斗屏状态面板一次性
var _refresh_done := false # 刷新确认链一次性 var _refresh_done := false # 刷新确认链一次性
var _stop_done := false # 停手行为一次性(每战至多一次——挖掘期确定性触发)
var _stop_settlement: Variant = null # 停手基线战局(换战局重置一次性)
var _stop_round := -1 # 停手轮次基线(轮换重置已花计数)
var _digs_this_round := 0 # 本轮已提交挖掘数(停手条件——AP 已花 ≥1)
func _ready() -> void: func _ready() -> void:
@@ -376,11 +380,11 @@ func _phase_adventure() -> void:
_record_screens() _record_screens()
var st := _screen_state() var st := _screen_state()
if bool(st["节点"]): if bool(st["节点"]):
_last_action = "节点层「继续」(方法-视图口)" _last_action = "真点节点层「继续」钮"
main.ui.modals.run.node_overlay.press_continue() ProbeUiUtil.push_click(main, main.ui.modals.run.node_overlay._btn)
continue continue
if bool(st["三选一"]): if bool(st["三选一"]):
_drive_curio() await _drive_curio()
continue continue
if bool(st["商店"]): if bool(st["商店"]):
var lv: Button = ProbeUiUtil.find_button(main.ui.modals.menu.shop_overlay, "离开商店") var lv: Button = ProbeUiUtil.find_button(main.ui.modals.menu.shop_overlay, "离开商店")
@@ -437,30 +441,42 @@ func _drive_map_once() -> void:
var resumed: Button = ProbeUiUtil.find_button(main.ui.modals.run.status_panel, "继续", "prefix") var resumed: Button = ProbeUiUtil.find_button(main.ui.modals.run.status_panel, "继续", "prefix")
if resumed != null and main.ui.modals.run.status_panel.visible: if resumed != null and main.ui.modals.run.status_panel.visible:
ProbeUiUtil.push_click(main, resumed) ProbeUiUtil.push_click(main, resumed)
_last_action = "地图候选命中(方法-地图命中口)" _last_action = "地图候选命中(真实输入-命中矩形)"
main.ui.modals.run.level_screen.press_candidate(0) ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
func _drive_curio() -> void: func _drive_curio() -> void:
# 三选一停点:待选态点首件+确认;兑付/已消费态点继续(方法-视图口——curio 停点既定驱动面) # 三选一停点:待选态真点首卡+确认(gui_input 点选——确认钮现形当帧容器重排在途,排版帧后再点);
# 兑付/已消费态真点继续
var co: Control = main.ui.modals.run.curio_overlay var co: Control = main.ui.modals.run.curio_overlay
_last_action = "三选一停点分发(方法-视图口)" _last_action = "三选一停点分发(真输入)"
if co._continue_btn.visible: if co._continue_btn.visible:
co.press_continue() ProbeUiUtil.push_click(main, co._continue_btn)
elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._node_pending.is_empty(): elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._node_pending.is_empty():
co.press_card(0) ProbeUiUtil.push_click_at(main, (co._bcards[0] as Control).get_global_rect().get_center())
co.press_confirm() await _frames(2)
ProbeUiUtil.push_click(main, co._confirm_btn)
elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._pending.is_empty(): elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._pending.is_empty():
co.press_card(0) ProbeUiUtil.push_click_at(main, (co._bcards[0] as Control).get_global_rect().get_center())
co.press_confirm() await _frames(2)
ProbeUiUtil.push_click(main, co._confirm_btn)
else: else:
co.press_continue() ProbeUiUtil.push_click(main, co._continue_btn)
func _drive_battle_once() -> void: func _drive_battle_once() -> void:
# 战斗单步:挂死检测+功能钮真点(图例/状态/刷新确认链/停手)+棋盘挖格(方法-棋盘口) # 战斗单步:挂死检测+功能钮真点(图例/状态/刷新确认链/停手)+棋盘挖格(方法-棋盘口)
var settlement = main.run_scope.settlement var settlement = main.run_scope.settlement
var diag: Dictionary = settlement.diagnostic_snapshot() var diag: Dictionary = settlement.diagnostic_snapshot()
if settlement != _stop_settlement: # 换战局——停手一次性与轮内计数重置
_stop_settlement = settlement
_stop_done = false
_stop_round = -1
_digs_this_round = 0
var round_now := int(diag.round)
if round_now != _stop_round:
_stop_round = round_now
_digs_this_round = 0
var st := { var st := {
"round": diag.round, "phase": diag.action_economy.phase, "ap": diag.action_economy.ap, "round": diag.round, "phase": diag.action_economy.phase, "ap": diag.action_economy.ap,
"revealed": diag.minesweeper_revealed, "revealed": diag.minesweeper_revealed,
@@ -514,6 +530,15 @@ func _drive_battle_once() -> void:
# 棋盘动作(方法-棋盘口——BoardView 公共点击口,ui_input_probe 先例;C 的按钮面全走真输入) # 棋盘动作(方法-棋盘口——BoardView 公共点击口,ui_input_probe 先例;C 的按钮面全走真输入)
var minesweeper = settlement.minesweeper var minesweeper = settlement.minesweeper
var plan: Dictionary = AutoPlayer.choose_dig(minesweeper) var plan: Dictionary = AutoPlayer.choose_dig(minesweeper)
# 停手行为(每战至多一次——挖掘期确定性触发,无随机占比;触发时点微调在代拍板白名单):
# 相位 Active+停手钮使能+AP 已花 ≥1+盘面有挖可走 → 真点停手钮收轮(真输入行为之一)
if not _stop_done and String(diag.action_economy.phase) == "Active" \
and not ap_bar._btn_stop.disabled and _digs_this_round >= 1 and not plan.is_empty():
_stop_done = true
_last_action = "真点「停手」钮(挖掘期定型化触发)"
ProbeUiUtil.push_click(main, ap_bar._btn_stop)
await _frames(2)
return
if plan.is_empty(): if plan.is_empty():
plan = AutoPlayer.gamble_pick(minesweeper) plan = AutoPlayer.gamble_pick(minesweeper)
if plan.is_empty(): if plan.is_empty():
@@ -525,6 +550,7 @@ func _drive_battle_once() -> void:
if plan.hero_cell != minesweeper.hero_pos(): if plan.hero_cell != minesweeper.hero_pos():
main.ui.battle.board_zone.board_view.click_cell(plan.hero_cell, MOUSE_BUTTON_LEFT) main.ui.battle.board_zone.board_view.click_cell(plan.hero_cell, MOUSE_BUTTON_LEFT)
main.ui.battle.board_zone.board_view.click_cell(plan.dig_cell, MOUSE_BUTTON_LEFT) main.ui.battle.board_zone.board_view.click_cell(plan.dig_cell, MOUSE_BUTTON_LEFT)
_digs_this_round += 1
await _frames(1) await _frames(1)
+3 -2
View File
@@ -9,6 +9,7 @@
extends SceneTree extends SceneTree
const AutoPlayer := preload("res://sim/auto_player.gd") # 自动玩家策略单一实现(lifecycle 活战斗驱动;smoke 库件迁移期按现路径 preload) const AutoPlayer := preload("res://sim/auto_player.gd") # 自动玩家策略单一实现(lifecycle 活战斗驱动;smoke 库件迁移期按现路径 preload)
const ProbeUiUtil := preload("res://probe/probe_ui_util.gd") # 真输入合成单点(tests 面复用共用件——禁复制第二份合成输入件)
var _failures: int = 0 var _failures: int = 0
var _checks: int = 0 var _checks: int = 0
@@ -436,8 +437,8 @@ func _smoke_lifecycle_matrix() -> void:
await process_frame await process_frame
await _stage_faces(main, "V3b 开局停点", "alive", "alive", "null", await _stage_faces(main, "V3b 开局停点", "alive", "alive", "null",
{"mf_start": false, "level_vis": true}) {"mf_start": false, "level_vis": true})
# 阶段③地图选路→战斗中(真实选路提交 press_candidate) # 阶段③地图选路→战斗中(真实选路提交——candidate_rect 命中矩形真点)
main.ui.modals.run.level_screen.press_candidate(0) ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
for i in range(10): for i in range(10):
await process_frame await process_frame
await _stage_faces(main, "V3c 战斗中", "alive", "alive", "alive", {"mf_start": false}) await _stage_faces(main, "V3c 战斗中", "alive", "alive", "alive", {"mf_start": false})
@@ -6,6 +6,7 @@ extends SceneTree
# 运行:godot --headless --path game --script res://tests/unit/presentation/reward_overlay_test.gd(expect 0) # 运行:godot --headless --path game --script res://tests/unit/presentation/reward_overlay_test.gd(expect 0)
# 断言走 test_kit 计数(Kit.check)+ 失败即停早退协议(顺序状态机——坏态下后继断言无效)。 # 断言走 test_kit 计数(Kit.check)+ 失败即停早退协议(顺序状态机——坏态下后继断言无效)。
const Kit := preload("res://tests/unit/test_kit.gd") const Kit := preload("res://tests/unit/test_kit.gd")
const ProbeUiUtil := preload("res://probe/probe_ui_util.gd") # 真输入合成单点(tests 面复用共用件——禁复制第二份合成输入件)
# 断言 + 失败即停(原直写 print FAIL + quit(1) + return 样板单点化——早退语义逐位保持) # 断言 + 失败即停(原直写 print FAIL + quit(1) + return 样板单点化——早退语义逐位保持)
func _req(desc: String, ok: bool) -> bool: func _req(desc: String, ok: bool) -> bool:
@@ -60,13 +61,15 @@ func _run() -> void:
if not _req("付费档余额可付:免费位 0 但钮点亮(对齐商店——GP 档可刷)", if not _req("付费档余额可付:免费位 0 但钮点亮(对齐商店——GP 档可刷)",
ov._refresh_btn.visible and not ov._refresh_btn.disabled): ov._refresh_btn.visible and not ov._refresh_btn.disabled):
return return
ov.press_refresh() await process_frame # 刷新钮现形当帧容器重排在途——排版帧后矩形成形再真点
if not _req("press_refresh 未外发 refresh_requested", refreshed.size() == 1): ProbeUiUtil.push_click(root, ov._refresh_btn) # 真点刷新钮(disabled 守卫=真实拒绝路径)
if not _req("真点刷新未外发 refresh_requested", refreshed.size() == 1):
return return
# ② 点选→高亮→确认外发(真实按钮路径);确认前改选以最后点选为准 # ② 点选→高亮→确认外发(真输入管线:bcard gui_input 点选——与 press_card 同一 _select_card 处理器);确认前改选以最后点选为准
ov.press_card(0) ProbeUiUtil.push_click_at(root, (ov._bcards[0] as Control).get_global_rect().get_center())
ov.press_card(1) ProbeUiUtil.push_click_at(root, (ov._bcards[1] as Control).get_global_rect().get_center())
ov.press_confirm() await process_frame # 确认钮现形当帧容器重排在途——排版帧后矩形成形再真点
ProbeUiUtil.push_click(root, ov._confirm_btn)
if not _req("点选确认未外发 pick_confirmed(gp)(%s)" % str(picked), if not _req("点选确认未外发 pick_confirmed(gp)(%s)" % str(picked),
ov._confirm_btn.visible and picked.size() == 1 and String(picked[0]) == "gp"): ov._confirm_btn.visible and picked.size() == 1 and String(picked[0]) == "gp"):
return return
@@ -76,7 +79,8 @@ func _run() -> void:
if not _req("结果态应点亮继续且收三卡与刷新钮", if not _req("结果态应点亮继续且收三卡与刷新钮",
ov._continue_btn.visible and not ov._choice_box.visible and not ov._refresh_btn.visible): ov._continue_btn.visible and not ov._choice_box.visible and not ov._refresh_btn.visible):
return return
ov.press_continue() await process_frame # 继续钮现形当帧容器重排在途——排版帧后矩形成形再真点
ProbeUiUtil.push_click(root, ov._continue_btn)
if not _req("继续未外发 continue_pressed", continued.size() == 1): if not _req("继续未外发 continue_pressed", continued.size() == 1):
return return
ov.close_pick() ov.close_pick()
+11
View File
@@ -0,0 +1,11 @@
# 迭代 54 反哺点清单(2026-11-07)
> 本清单逐卡增量落笔;用户在会话中裁决后由文档批写入 `design/` 并把该条清掉(AGENTS 红线:实现批禁写 `design/`,迭代实现报告与反哺点归 `reports/`)。
## 卡 1|QA harness 缺陷与覆盖遗漏收口批——申报:零条
卡 1 §6 申报「无关联文档」——A2 为容错修复、B1/B2 为测试驱动面,零设计语义变化。核过面:`design/gdd/systems/排雷循环/排雷玩法.md`(停手语义句 :162/:167 与实装一致)、`design/engineering/test-conventions.md`(真输入管线条文 §2 与实装一致)——两处无需改句。
## 卡 2|营地交互修复批——(随卡 2 推进补齐)
## 卡 3|精英专属池——(随卡 3 推进补齐)
+65
View File
@@ -0,0 +1,65 @@
# 迭代 54 实现报告(2026-11-07)
> 三卡:QA harness 缺陷与覆盖遗漏收口批 → 营地交互修复批 → 精英专属池。本报告随各卡末卡增量落笔(卡 1 QA 收口段先行,卡 2/卡 3 段随后补齐),总收口前为完整版。
> 独立验收审计入口预约:`reports/iter54-验收审计-2026-11-07.md`(未参与实现的只读会话/子代理执行,未过不合回)。审计深度:卡 1=中档(对照表逐行全量、最窄复跑省略以远端 CI 现查代证);卡 2=中档;卡 3=高档(全量)。
## 卡 1|QA harness 缺陷与覆盖遗漏收口批——状态:已完成
### 各任务卡状态
| 任务卡 | 状态 | 摘要 |
|---|---|---|
| T1 A2 关闭容错修复 | 完成 | battle_end_overlay.gd close(:194) 与 _play_celebrate(:331) 两处 `get_meta(&"celebrate_tw", null)` 改 `has_meta` 前置判断,语义不变(有则必杀、无则无事)〔提交 57f3cc75〕 |
| T2 B1 探针定型化停手段 | 完成 | ui_click_probe 新增 `_stoptest_tick` 状态机(镜像 `_locktest_tick` 形态)+`_stoptest_assert_hit`,挖掘期触发窗真点停手钮,断言①覆盖登记停手键在场②相位离开 Active〔提交 816aaae5〕 |
| T3 B1 journey 停手+S4c 8 处转换 | 完成 | journey_player 新增停手行为(每战至多一次,挖掘期确定性触发)+8 处真输入转换+头注方法口句换代〔提交 2f5c734e〕 |
| T4 B2 ui_click_probe 16 处+candidate_rect | 完成 | LevelMapScreen 删 press_candidate 增 candidate_rect(index)->Rect2(命中面与 _candidate_at 同源,全局坐标,越界空 Rect2);ui_click_probe 候选 2+节点继续 2+三选一 10+弃图刷新链 2 全转真输入,既有落点断言逐条原样〔提交 afcfbf37〕 |
| T5 B2 其余 7 探针 11 处 | 完成 | 候选 6+停点 1+调试浮窗 4;shot_probe 头注同步〔提交 22903043〕 |
| T6 B2 非探针层 6 处+死接缝清理 | 完成 | system_test 1 处+reward_overlay_test 5 处真输入;死接缝 11 方法删除(ap_bar_view 2+curio_pick_overlay 4+node_overlay 1+debug_overlay 3,level_map_screen 1 随 T4)〔提交 710df6ce〕 |
| T7 产出物落位 | 完成 | 本报告+反哺点清单+批末选档跑闸(读数见下)+审计入口预约 |
### 实际对照表(R1–R9,补「结果」格)
| 行 | 验收测试 | 结果 |
|---|---|---|
| R1 终局结算框关闭动线零引擎报错 | fx_probe+screen_flow_probe 直跑 grep「SCRIPT ERROR」「celebrate_tw」 | 绿——两探针各 exit=0、SCRIPT ERROR 0、celebrate_tw 0(提交 57f3cc75 读数) |
| R2 停手钮真输入命中与落效 | ui_click_probe 直跑(_stoptest_tick 断言①②) | 绿——exit=0、问题=0、覆盖落盘停手键命中 1 次(确定性触发实证;提交 816aaae5 读数) |
| R3 journey 停手+方法口清零 | journey 全线跑批 | 绿(读数判据满足)——fatal 0、上报结构不变(2 条=已知缺陷 A1 双指纹,改前基线同源)、覆盖登记停手键 6 次+停点键在场(结算面继续 6/确认 3)、屏到达 16 类含战斗/终局;exit=1 为 A1 基线态(营地交互修复批承接中,本卡不做动作——卡 1 §5 范围边界) |
| R4 停点链真输入化(ui_click_probe 16 处) | ui_click_probe 直跑+既有落点断言+登记键集不回退 | 绿——exit=0、问题=0、覆盖 43 命中/30 控件较改前 39/28 不回退、新增停点链键(curio 确认选择 2/继续 2)、冒险终局在案选路口点击 9 次 |
| R5 到达型候选+停点(7 探针) | 7 探针逐件孤立直跑 | 绿——headless 五件(ui_struct/layout/ui_probe/t17_defer/ui_tune)exit=0+SCRIPT ERROR 0;窗口化两件(shot 七屏 PNG 齐、fx_shot PASS 演出帧 7 张挖格 3)exit=0 |
| R6 调试浮窗面(4 处) | ui_tune_probe 直跑 | 绿——PASS 全链路(含模拟 N=10 输出与种子三分断言) |
| R7 系统测试宿主候选真输入化 | 系统测试直跑 | 绿——69 项检查 0 失败、三闸过(V3c 阶段真实选路提交在跑批中执行) |
| R8 三选一覆盖层单元真输入化 | 单元与集成套件直跑 | 绿——82 套件 all green、exit=0、0 FAIL(reward_verify 断言在册) |
| R9 死接缝同批清理(5 文件 11 方法) | 全仓 .gd 双模式 grep | 绿——定义清 `func press_(candidate\|continue\|card\|confirm\|refresh\|apply\|reset\simulate)` 与调用清 `.press_(…)` 双零命中 |
### 卡 1 批末选档跑闸读数(T7)
`bash tools/run_all_gates.sh --code`(游戏码批选档)=**【验收通过】闸况与基线一致,无新增红**。回执六闸全绿:冒烟测试(smoke_boot.log)=绿/单元与集成测试(unit.log)=绿/系统测试(system_test.log)=绿/场景探针(probe.log)=绿/渲染闸(render.log)=绿/静态扫描(static_scan.log)=绿。改前改后对拍:改前基线=master 40a3ac12 远端 CI run 99 绿;改后=本跑批无新增红(闸况与基线一致)。
### 实机目检总清单(卡 1)
| 目检项 | 登记处 | 义务口径 |
|---|---|---|
| 覆盖面板控件下钻停手键与停点链键在场 | 本表(数据面读数:probe_hits_ui_click.js 停手 1 次、停点链键在案) | 真人检测只登记不阻塞(AGENTS 红线)——登记即完成义务 |
### 遗留读数义务(卡 1)
- 无 MC/verifier 义务(本卡零数值零随机面)。
- 远端 CI 现查读数(合回后):审计与追绿按 process-conventions §1 第 3 条按需现查。
### 实现决定记录(卡 1,超白名单事项备案)
1. **reward_overlay_test.gd 五处行号就地校正**:卡面申报 :60/:64/:65/:66/:76,实查为 :63/:67/:68/:69/:79(漂移 +3,内容与描述全一致)——依卡内 T1 开工核对条款就地校正,未呈报(漂移远小于一屏)。
2. **ui_click_probe 停手段与 system_test 行号就地校正**:停手触发/驱动 :1041-1045/:1063-1065 → 实查 :1051-1055/:1074-1077;system_test :433 → :440。同上依据。
3. **排版帧插入**:三选一选卡→确认之间、reward_overlay_test 刷新/确认/继续点击前、journey _drive_curio 卡→确认之间插入 1–2 帧等待——确认钮/继续钮现形当帧容器重排在途,矩形未成形真点必落空。属驱动机制细节(白名单「使能窗等待时长、触发条件的具体写法」同族)。
4. **ui_tune ⑤ sim 真点的折叠区滚轮通道**:初跑发现 sim 钮真点落空——根因=滚动列表 boot 滚位下钮矩形跨出 ScrollContainer 视口底边(hit 面被裁;emit 路径历史性掩盖)。按 test-conventions §2「折叠区控件先以真输入滚轮滚入可视区再点」落滚轮通道(一档滚入实证后定装),并配 12 档上限+滚入失败 problem 上报(正面效力自检)。⑧ 应用前置重开浮窗(同款机制先例=④ 重开浮窗)。属真输入通道的实现形态(卡片白名单「具体写法」族),随提交留痕。
5. **候选/bcard 覆盖登记键的结构性缺口**:覆盖登记件自动登记通道=BaseButton pressed+营地自绘物件两源;地图画布与 BCard 非 BaseButton、无 pressed 信号,真输入命中不产键(登记件零改动——卡 1 §13 申报)。候选/弃图确认键的对照表括注预期键与登记件能力不符:效力由既有落点断言承载(R4 判据绑定面=既有落点断言逐条+键集不回退,两者均成立)。弃图确认链在 ui_click_probe 受 is_refresh_ready(RAP≥8)门槛四种子未触发——改前同门槛不可达,链面真输入效力由 journey 既有真点链承载。此为观测面局限申报,非缺陷。
6. **T2 断言①的登记件读取形态**:`_stoptest_assert_hit` 经 `_recorder._node_keys`/`_recorder._hits` 直读登记件命中表(登记件为探针共用面、卡 1 §13 申报零改动——不新增公开读口,探针面读私有沿现役惯例:ap_bar._btn_stop 同型先例)。
7. **卡 2 T4.4「等用户发令再走收口」与迭代单合并语义的冲突处置**:卡 2 写卡时为独立批(编入迭代 54 前),其末卡收口语义已被编排段换代为迭代级总收口(iter 技能第 5 步:一次合并、五步登记)。本批按迭代级收口执行,不逐卡等发令;此冲突为编排换代遗留,随本报告呈报。
## 卡 2|营地交互修复批——状态:进行中
(随卡 2 任务卡推进补齐。)
## 卡 3|精英专属池——状态:待开工
(随卡 3 任务卡推进补齐。)