iter55-卡1:QA覆盖余量收口批: 46→45真缺口断言落地(违约词负面套件/结算与cap与换算率钉/配额满/rap端到端/布局/天赋树/状态行/L3变异/reward行)+p_perfect读表接线(行为零漂移)+豁免登记(ontology 154/controls 19)+探针扩面十一组真输入+登记件开发面过滤(DebugOverlay/调试角标/inter三根, seen开发面键0)+枚举器scan_screens基类分类(绘制域件与调试浮窗出圈, 868实体/未豁免未补测=0)+cell_mark_overlay渲染通道(采集态六类型注入全入帧, 棋盘三图基线换代用户过目, 渲染闸绿);卡修轮2用户裁两处不豁免(渲染通道+开发面出圈);独立审计U1修复闭合+增量轮通过

This commit is contained in:
DKChan committed 2026-11-07 19:56:13 +08:00
1 parent 095bb4c91b
commit c41ce9cfc5
25 files changed
+1502 -26

No files matched your search

+79 -1
View File
@@ -23,6 +23,73 @@ var _level_readings := {} # 关 → 双帧比对读数(实现报告读数表
var _cur_settlement = null # 当关 settlement(身份回调读取面——仿生产 run_scope.settlement 现值;
# 不用 Callable.bind 传参:bind 的 Callable 动态派发时对类型化参数做 Object 硬校验,
# 报「Cannot convert argument 2 from Object to Vector2i」——直接方法引用+成员现值无此坑)
var _cur_mark_counts := {} # 当关 cell_mark_overlay 绘制域逐类型格计数(注入后读数——V14 采集态核对)
func _probe_settlement_getter():
return _cur_settlement
# 绘制域缺型注入(确定性直构——每关固定选格,同种子同态可复现):
# 真视框=首个未开未封印雷格;预读徽章=身份查询命中的未开格(循环挑到命中,全空记零);
# 移除退色=remove_mines_in 真实写口移除一个未揭示雷格;点迹/高亮=英雄格 face 直设定格。
func _mark_domain_inject(view: Node, settlement: Object) -> void:
var ms = settlement.minesweeper
var board = ms.current_board()
var reveal_cell := Vector2i(-9, -9)
var preview_cell := Vector2i(-9, -9)
var remove_cell := Vector2i(-9, -9)
for y in board.height:
for x in board.width:
var c := Vector2i(x, y)
if ms.is_revealed(c) or ms.has_seal(c) or ms.is_removed(c):
continue
if board.is_mine(c):
if reveal_cell.x < 0:
reveal_cell = c
if remove_cell.x < 0:
remove_cell = c
elif preview_cell.x < 0 and _probe_identity_lookup(c) != null:
preview_cell = c
if remove_cell.x >= 0:
ms.remove_mines_in([remove_cell])
view.set_treasure_overlays([reveal_cell] if reveal_cell.x >= 0 else [],
[preview_cell] if preview_cell.x >= 0 else [])
view.refresh()
var hero: Vector2i = ms.hero_pos()
var cells: Dictionary = view.cells_probe()
if cells.has(hero):
(cells[hero] as Node2D).face.dot_visible = true
var nb: Vector2i = hero + Vector2i(1, 0)
if not cells.has(nb):
nb = hero + Vector2i(0, 1)
if cells.has(nb):
(cells[nb] as Node2D).face.hl_visible = true
_cur_mark_counts = {"injected": {"reveal": str(reveal_cell), "preview": str(preview_cell),
"remove": str(remove_cell), "dot": str(hero)}}
# 逐类型格计数读数(off 帧呈现态——face 现值直读,CellUnit 在册探针读口同面)
func _mark_domain_counts(view: Node, tag: String) -> Dictionary:
var counts := {"mutation_mark": 0, "treasure_mark": 0, "badge_glyph": 0,
"dot_visible": 0, "hl_visible": 0, "removed_dim": 0}
var cells: Dictionary = view.cells_probe()
for c in cells:
var f = (cells[c] as Node2D).face
if String(f.mutation_mark) != "":
counts["mutation_mark"] = int(counts["mutation_mark"]) + 1
if String(f.treasure_mark) != "":
counts["treasure_mark"] = int(counts["treasure_mark"]) + 1
if String(f.badge_glyph) != "":
counts["badge_glyph"] = int(counts["badge_glyph"]) + 1
if bool(f.dot_visible):
counts["dot_visible"] = int(counts["dot_visible"]) + 1
if bool(f.hl_visible):
counts["hl_visible"] = int(counts["hl_visible"]) + 1
if bool(f.removed_dim):
counts["removed_dim"] = int(counts["removed_dim"]) + 1
print("[board_shot] mark_domain %s: %s" % [tag, counts])
return counts
func _ready() -> void:
@@ -120,7 +187,17 @@ func _ready() -> void:
# 双帧比对空转、PASS 假绿)。现值经 _cur_settlement 成员读入
# (仿生产 run_scope.settlement;bind 传参有 Callable 类型校验坑,见成员注)。
view.set_identity_resolver(_probe_identity_lookup)
# 变异角标 resolver(生产同源——main_ui_build:262 set_variant_mark_resolver 接线同款):
# 裸视图不接 = mutation_mark 恒空 = L3 伤害型变异角标缺帧。rebuild 前接(paint 链读回调)。
var resolvers = load("res://presentation/board_resolvers.gd").new(db, null, _probe_settlement_getter)
view.set_variant_mark_resolver(resolvers.variant_mark_lookup)
view.rebuild(settlement.minesweeper.current_board(), settlement.minesweeper)
# ---- cell_mark_overlay 绘制域采集态注入(卡修轮 2 V14——确定性直构,缺型补进采集态)----
# 五类型注入全走真链/生产同源:真视框与预读徽章=set_treasure_overlays(生产呈现链);
# 移除退色=remove_mines_in(核心真实写口——移除集=未揭示无封印是雷,判定基线零回溯);
# 点迹/高亮=u.face 直设(CellUnit 探针读口同面的写侧,交互瞬态的定格化)。
_mark_domain_inject(view, settlement)
var mark_counts := _mark_domain_counts(view, tag)
# 进挖掘相位(生产态 = hud_presenter.set_dig_glow_enabled(not settling) 接线——裸视图默认
# _dig_glow=false,接触带与类型微光同源门控都不渲染;golden 基线即捕获于
# 该裸态。双帧取证要求生产默认态,故显式开——两帧均含静态接触带(motion_scale=0.5 停格),
@@ -342,7 +419,8 @@ func _check_dual_frames(tag: String, view, glow_cells: Dictionary, off_img: Imag
if first_outside == "":
first_outside = "%s 非生效格" % str(c2)
_level_readings[tag] = {"glow_cells": glow_cells.size(), "changed_total": changed_total,
"changed_outside": changed_outside, "band_px": band_px}
"changed_outside": changed_outside, "band_px": band_px,
"mark_domain": _cur_mark_counts}
print("[board_shot] %s 双帧读数 glow=%d changed=%d outside=%d band=%.0fpx"
% [tag, glow_cells.size(), changed_total, changed_outside, band_px])
if changed_outside != 0:
+37
View File
@@ -14,6 +14,11 @@
# 锚(具名节点用其名、带脚本节点用脚本文件名;@ 临时名段整体不入键),控件本体记 类#钮面文本,
# 同键控件按接线遍历序追加 #序位。键在确定构建序下可复现;UI 构建序变更会平移同键序位——
# 表现为键漂移而非静默丢失,属测量面可接受代价。
# 开发面边界(2026-11-07 用户裁——调试面不入玩家可达对账宇宙,登记层过滤、不产豁免条目):
# DebugOverlay 浮窗(class_name 声明,main_ui_build 装配注「玩家画面零常驻调试件」)+
# ui.screens.debug_badge 调试角标+ui.screens.inter_overlay 调试两层模式专用层
# (main.gd _on_battle_finished「仅调试模式接线」——正常冒险层间推进归 AdventureRunner)。
# 依据=test-conventions「调试注入两层」调试面发布态恒隐条款;过滤为纯减法(玩家可达面零影响)。
# 用法:探针 _ready 里 `_recorder = ProbeCoverageRecorder.new()` → `attach(main, "<探针名>")`;
# 收尾 `dump_js()`。探针共用面不入 S 系扫描面(code-conventions §4.13 分档)。
extends RefCounted
@@ -29,6 +34,8 @@ var _node_keys: Dictionary = {} # 接线时分配的节点键(instanc
var _key_seen: Dictionary = {} # 基键出现次数(同键控件按遍历序追加序位)
var _wired: Dictionary = {} # 已连 pressed 的按钮实例 id → true(防重连)
var _skipped_temp := 0 # 临时名命中笔数(不入 key,只计数)
var _dev_roots: Array[Node] = [] # 开发面根(DebugOverlay 浮窗/调试角标/inter 调试专用层)——整棵子树不入账
var _dev_skipped := 0 # 开发面接线跳过笔数(观测面——dump meta 携带)
# ---- 装载与接线 ----
@@ -37,10 +44,32 @@ func attach(main: Node, probe_name: String) -> void:
_main = main
_probe_name = probe_name
ProbeUiUtil.coverage_hook = self # 共用件真输入口统一过旗标(push_click/push_click_at/push_motion)
_resolve_dev_roots()
_wire_buttons()
_wire_camp_scene()
# 开发面根解析(三处成员引用/脚本身份单点,见头注边界声明)——解析仅 attach 时一次,
# 解析不到的根跳过(无独立计数;attach 后重建的开发面根会漏滤)——漏滤方向由 seen 集
# 「开发面键 0」核对兜住(开发面键再现=过滤失守),玩家可达面零误滤风险(早退只命中三根)。
func _resolve_dev_roots() -> void:
var overlay := _find_by_script(_main, "debug_overlay")
if overlay != null:
_dev_roots.append(overlay)
var ui: Object = _main.get("ui")
if ui != null:
var screens: Object = ui.get("screens")
if screens != null:
for prop in ["inter_overlay", "debug_badge"]:
var n: Node = screens.get(prop)
if n != null:
_dev_roots.append(n)
func _is_dev_root(n: Node) -> bool:
return _dev_roots.has(n)
# 懒重扫:每次真输入 push 前接线一遍新入树的按钮(开层重建的钮行在下次点击前接上);
# 已释放按钮的连接随节点释放失效,登记表按 instance_id 清扫。
func mark_real_input() -> void:
@@ -63,6 +92,9 @@ func _wire_buttons() -> void:
func _wire_into(node: Node) -> void:
if _is_dev_root(node): # 开发面整棵子树不入账(接线与命中双断——pressed 沿都不连)
_dev_skipped += 1
return
if node is BaseButton:
var id := node.get_instance_id()
if not _wired.has(id):
@@ -184,6 +216,9 @@ func enumerate_seen() -> Array[String]:
func _seen_into(node: Node, keys: Array[String]) -> void:
if _is_dev_root(node): # seen 枚举与登记同源同滤——开发面不入「存在但零覆盖」缺口面
_dev_skipped += 1
return
if node is BaseButton:
var id := node.get_instance_id()
var key := String(_node_keys.get(id, ""))
@@ -228,6 +263,8 @@ func dump_js() -> void:
"hits_total": total,
"seen_total": seen.size(),
"skipped_temp_names": _skipped_temp,
"dev_face_roots": _dev_roots.size(),
"dev_face_skipped": _dev_skipped,
},
"hits": _hits,
"seen": seen,
+18 -4
View File
@@ -198,16 +198,30 @@ static func window_panel(view: Control) -> PanelContainer:
# 判据:钮 rect 整体落在窗板内(上缘留 8px、下缘让开底栏 64px——返回营地钮带高约 56px)。
# 保险丝 60 次滚叶防异常内容死循环;窗板缺失或钮句柄失效即返回(调用方前置断言兜底)。
static func scroll_button_into_view(main, view: Control, btn: Control) -> void:
var panel := window_panel(view)
if panel == null or btn == null:
if btn == null:
return
# 可视区基准=按钮最近的 ScrollContainer 祖先(滚动裁剪的真实可视域);
# 无滚动祖先时回退窗壳面板(旧基准——按钮不在滚动容器内的窗体形态)。
# 修因(迭代 55):秘宝匣 _ws_scroll 336px 高于窗壳面板内部——以面板为基准时
# 裁剪区外按钮被误判「在视」,滚轮零滚动、真点落空(实证:购买 25/装配/升级 零命中)。
var panel: Control = null
var anc: Node = btn.get_parent()
while anc != null and anc is Control:
if anc is ScrollContainer:
panel = anc
break
anc = anc.get_parent()
if panel == null:
panel = window_panel(view)
if panel == null:
return
var guard := 60
while guard > 0:
var r := btn.get_global_rect()
var pr := panel.get_global_rect()
if r.position.y >= pr.position.y + 8.0 and r.end.y <= pr.end.y - 64.0:
if r.position.y >= pr.position.y + 8.0 and r.end.y <= pr.end.y - 8.0:
return
var down := r.end.y > pr.end.y - 64.0
var down := r.end.y > pr.end.y - 8.0
push_wheel(main, pr.get_center(), 1 if down else -1)
guard -= 1
await main.get_tree().process_frame
+335 -10
View File
@@ -42,6 +42,9 @@ var _locktest_cell := Vector2i(-9, -9)
var _locktest_frames := 0
var _stoptest_state := 0 # 停手钮定型化覆盖段状态机(0=待命 1=已点击待核效 2=完结)——每 run 一次
var _stoptest_frames := 0
var _tested_node_continue := false # 迭代 55 B5 扩面——节点继续钮真输入一次性段
var _tested_curio_refresh := false # 迭代 55 B5 扩面——三选一刷新钮真输入一次性段
var _tested_shop_buy := false # 迭代 55 B5 扩面——商店购买+刷新钮真输入一次性段
func _ready() -> void:
@@ -139,6 +142,47 @@ func _ready() -> void:
iso_fk.load_from_disk()
if String(iso_fk.meta_state.tree_state().fork_sides.get("fg_posture", "")) != "b":
_problems.append("Fork 切态未持久(reload 后激活态 != b)〔句柄子容器批 V1〕")
# ---- 树面高档钮扩面段(迭代 55 B5——购买0/20/40/60 档经调试注钥后真点+先手压制 A 侧回切)----
# 判据出处: design/gdd/systems/跨局循环/天赋树.md:198 (营地购买段——购买面真输入扩面组)
iso_c.meta_state._add_keystone(9999)
main.ui.modals.camp.tree.refresh_camp(main._camp_data())
await get_tree().process_frame
for target_id in ["forge_workshop", "att_socket2", "att_socket3"]:
var hi_btn: Button = null
for card in main.ui.modals.camp.tree._tree_buy_btns:
if String(card.node_id) == target_id:
hi_btn = card.btn
if hi_btn == null:
_problems.append("树面 %s 购买钮缺失(高档扩面驱动中断)〔QA余 V13〕" % target_id)
elif hi_btn.disabled:
_problems.append("树面 %s 购买钮置灰(注钥后仍不可达——前置链失守)〔QA余 V13〕" % target_id)
else:
var price_hi: int = int(main.db.crossrun.meta_tree.row_by_id(target_id).get("price", 0))
var bal_hi: int = iso_c.meta_state.keystone_balance()
await ProbeUiUtil.scroll_button_into_view(main, main.ui.modals.camp.tree, hi_btn)
ProbeUiUtil.push_click(main, hi_btn) # 真点高档购买(forge_workshop=购买 0 钥石档)
await get_tree().process_frame
main.ui.modals.camp.tree.refresh_camp(main._camp_data())
await get_tree().process_frame
if int(iso_c.meta_state.keystone_balance()) != bal_hi - price_hi:
_problems.append("树面 %s 购买未出账(%d→%d 应 −%d)〔QA余 V13〕" % [target_id, bal_hi, iso_c.meta_state.keystone_balance(), price_hi])
if int(iso_c.meta_state.tree_state().nodes.get(target_id, 0)) != 1:
_problems.append("树面 %s 购买落点失守(节点未点亮)〔QA余 V13〕" % target_id)
# 先手压制 A 侧回切(先手压制=fg_posture A 侧名——购买默认 A 后既有段切至 B,A 侧钮零真输入命中)
var side_a: Button = null
for fk in main.ui.modals.camp.tree._tree_fork_btns:
if String(fk.group) == "fg_posture" and String(fk.side) == "a":
side_a = fk.btn
if side_a == null:
_problems.append("Fork A 侧切换钮缺失(先手压制回切驱动中断)〔QA余 V13〕")
else:
await ProbeUiUtil.scroll_button_into_view(main, main.ui.modals.camp.tree, side_a)
ProbeUiUtil.push_click(main, side_a) # 真点先手压制(B→A 回切)
await get_tree().process_frame
var iso_fk2 = load("res://core/save_manager.gd").new("user://smoke_click_probe_camp_slot_1.json")
iso_fk2.load_from_disk()
if String(iso_fk2.meta_state.tree_state().fork_sides.get("fg_posture", "")) != "a":
_problems.append("先手压制回切未持久(reload 后激活态 != a)〔QA余 V13〕")
# ---- 营地灰字取色断言(迭代36 wc4——四子界面拆分后按视图分扫;卡④ T5 迁移):
# 图鉴锁文→codex 视图(锚随形态订正去『柜台』);秘宝行未解锁/同调形态/档位/刷新行→treasure 视图
# _workshop_holder 子树。font_color 逐处 == STONE_DEEP;树链段已闭树界面。
@@ -193,6 +237,116 @@ func _ready() -> void:
else:
ProbeUiUtil.push_click(main, treasure_back) # 真点返回(closed 同源钮)
await get_tree().process_frame
# ---- 秘宝架扩面段(迭代 55 B5——购买10/购买25/装配/升级30 真点;钥匙门先经树购 ks_workshop 点亮)----
# 判据出处: design/gdd/systems/跨局循环/秘宝.md:246 (秘宝匣柜台——购买/装配/升级真输入扩面组)
ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"dummy").get_center()) # 木桩→树界面
await get_tree().process_frame
var ks_btn: Button = null
for card in main.ui.modals.camp.tree._tree_buy_btns:
if String(card.node_id) == "ks_workshop":
ks_btn = card.btn
if ks_btn == null:
_problems.append("树面 ks_workshop 购买钮缺失(秘宝架扩面前置中断)〔QA余 V13〕")
else:
await ProbeUiUtil.scroll_button_into_view(main, main.ui.modals.camp.tree, ks_btn)
ProbeUiUtil.push_click(main, ks_btn) # 真点(free 行——钥匙门点亮)
await get_tree().process_frame
var ws_tree_back: Button = ProbeUiUtil.find_button(main.ui.modals.camp.tree, "返回营地")
if ws_tree_back != null:
ProbeUiUtil.push_click(main, ws_tree_back) # 真点返回营地
await get_tree().process_frame
ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"chest").get_center()) # 宝箱→秘宝匣(已点亮态)
await get_tree().process_frame
var tview = main.ui.modals.camp.treasure
iso_c.meta_state._add_keystone(9999)
tview.refresh_camp(main._camp_data())
await get_tree().process_frame
var buy10: Button = null
var buy25: Button = null
for c in tview.find_children("*", "Button", true, false):
var cb := c as Button
if cb.text.begins_with("购买 10") and not cb.disabled and buy10 == null:
buy10 = cb
if cb.text.begins_with("购买 25") and not cb.disabled and buy25 == null:
buy25 = cb
if buy10 == null:
_problems.append("秘宝架「购买 10」钮缺失或置灰(真输入可达性失守)〔QA余 V13〕")
else:
var bal10: int = iso_c.meta_state.keystone_balance()
await ProbeUiUtil.scroll_button_into_view(main, tview, buy10)
ProbeUiUtil.push_click(main, buy10) # 真点购买 10
await get_tree().process_frame
if int(iso_c.meta_state.keystone_balance()) != bal10 - 10:
_problems.append("秘宝购买 10 未出账(%d→%d 应 −10)〔QA余 V13〕" % [bal10, iso_c.meta_state.keystone_balance()])
if buy25 == null:
_problems.append("秘宝架「购买 25」钮缺失或置灰(真输入可达性失守)〔QA余 V13〕")
else:
var bal25: int = iso_c.meta_state.keystone_balance()
await ProbeUiUtil.scroll_button_into_view(main, tview, buy25)
ProbeUiUtil.push_click(main, buy25) # 真点购买 25
await get_tree().process_frame
if int(iso_c.meta_state.keystone_balance()) != bal25 - 25:
_problems.append("秘宝购买 25 未出账(%d→%d 应 −25)〔QA余 V13〕" % [bal25, iso_c.meta_state.keystone_balance()])
# 装配/升级段前重开视图(_refresh_attune_segment 的 unlocked 快照仅随 rebuild 刷新——
# 购后不重开则新解锁件的装配钮恒禁用;重开=全量重建,快照与解锁集对齐)
var ws_back_mid: Button = ProbeUiUtil.find_button(main.ui.modals.camp.treasure, "返回营地")
if ws_back_mid != null:
ProbeUiUtil.push_click(main, ws_back_mid)
await get_tree().process_frame
ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"chest").get_center())
await get_tree().process_frame
tview = main.ui.modals.camp.treasure
var attune_entry: Dictionary = {}
for a in tview._attune_btns:
if not (a["btn"] as Button).disabled:
attune_entry = a
break
if attune_entry.is_empty():
_problems.append("秘宝架「装配」钮缺失或全置灰(解锁后应可装)〔QA余 V13〕")
else:
await ProbeUiUtil.scroll_button_into_view(main, tview, attune_entry["btn"])
ProbeUiUtil.push_click(main, attune_entry["btn"]) # 真点装配(toggle_attunement 写口)
await get_tree().process_frame
if not (iso_c.meta_state.attune_state().equipped as Array).has(String(attune_entry["item_id"])):
_problems.append("秘宝装配落点失守(%s 未入装配集)〔QA余 V13〕" % String(attune_entry["item_id"]))
if tview._refresh_btn == null or tview._refresh_btn.disabled:
_problems.append("秘宝架「升级」钮缺失或置灰(Y 刷新升级行可达性失守)〔QA余 V13〕")
else:
var bal_y: int = iso_c.meta_state.keystone_balance()
var ylvl0: int = int(iso_c.meta_state.attune_state().refresh_level)
await ProbeUiUtil.scroll_button_into_view(main, tview, tview._refresh_btn)
ProbeUiUtil.push_click(main, tview._refresh_btn) # 真点升级(attune_refresh 同账写口)
await get_tree().process_frame
if int(iso_c.meta_state.attune_state().refresh_level) != ylvl0 + 1:
_problems.append("秘宝升级落点失守(refresh_level %d→%d)〔QA余 V13〕" % [ylvl0, int(iso_c.meta_state.attune_state().refresh_level)])
if int(iso_c.meta_state.keystone_balance()) >= bal_y:
_problems.append("秘宝升级未出账(%d→%d)〔QA余 V13〕" % [bal_y, iso_c.meta_state.keystone_balance()])
var ws_back2: Button = ProbeUiUtil.find_button(main.ui.modals.camp.treasure, "返回营地")
if ws_back2 != null:
ProbeUiUtil.push_click(main, ws_back2) # 真点返回(关闭后交还布告板段)
await get_tree().process_frame
# 树面购买 30 档(ks_refresh——前置 ks_workshop 已随秘宝段点亮;同账路由购 L2 扣 60)
ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"dummy").get_center())
await get_tree().process_frame
var ks30_btn: Button = null
for card in main.ui.modals.camp.tree._tree_buy_btns:
if String(card.node_id) == "ks_refresh":
ks30_btn = card.btn
if ks30_btn == null:
_problems.append("树面 ks_refresh 购买钮缺失(30 档扩面驱动中断)〔QA余 V13〕")
elif ks30_btn.disabled:
_problems.append("树面 ks_refresh 购买钮置灰(ks_workshop 点亮后仍不可达)〔QA余 V13〕")
else:
var bal30: int = iso_c.meta_state.keystone_balance()
await ProbeUiUtil.scroll_button_into_view(main, main.ui.modals.camp.tree, ks30_btn)
ProbeUiUtil.push_click(main, ks30_btn) # 真点购买 30(同账路由——L2 档扣 60)
await get_tree().process_frame
if int(iso_c.meta_state.keystone_balance()) != bal30 - 60:
_problems.append("树面 ks_refresh 购买未出账(%d→%d 应 −60)〔QA余 V13〕" % [bal30, iso_c.meta_state.keystone_balance()])
var tree_back3: Button = ProbeUiUtil.find_button(main.ui.modals.camp.tree, "返回营地")
if tree_back3 != null:
ProbeUiUtil.push_click(main, tree_back3)
await get_tree().process_frame
ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"board").get_center()) # 布告板→图鉴占位(锁文宿主)
await get_tree().process_frame
var codex_labels: Array = []
@@ -209,6 +363,38 @@ func _ready() -> void:
else:
ProbeUiUtil.push_click(main, codex_back) # 真点返回(closed 同源钮)
await get_tree().process_frame
# ---- 营地 Esc 浮窗设置动线(迭代 55 B5——Esc 真键唤起浮窗→设置钮真点开设置层)----
# 判据出处: design/ux/camp.md:45 (Esc 段组——三出口真输入扩面)
var esc_ev := InputEventKey.new()
esc_ev.keycode = KEY_ESCAPE
esc_ev.physical_keycode = KEY_ESCAPE
esc_ev.pressed = true
main.get_viewport().push_input(esc_ev, true) # 真键(营地根态——screen_flow 同款合成路径)
await get_tree().process_frame
if not main.ui.modals.camp.esc_overlay.visible:
_problems.append("营地 Esc 真键未唤起浮窗(扩面驱动中断)〔QA余 V13〕")
else:
var esc_settings: Button = main.ui.modals.camp.esc_overlay._settings_btn
if esc_settings == null:
_problems.append("Esc 浮窗「设置」钮缺失〔QA余 V13〕")
else:
ProbeUiUtil.push_click(main, esc_settings) # 真点设置(settings_requested 三出口之一)
await get_tree().process_frame
if not main.ui.modals.menu.settings_overlay.visible:
_problems.append("Esc 浮窗「设置」真点击未开设置层(落点判定失守)〔QA余 V13〕")
var esc_set_close: Button = ProbeUiUtil.find_button(main.ui.modals.menu.settings_overlay, "返回", "prefix")
if esc_set_close != null:
ProbeUiUtil.push_click(main, esc_set_close) # 真点返回收设置层
await get_tree().process_frame
if main.ui.modals.camp.esc_overlay.visible:
var esc_ev2 := InputEventKey.new()
esc_ev2.keycode = KEY_ESCAPE
esc_ev2.physical_keycode = KEY_ESCAPE
esc_ev2.pressed = true
main.get_viewport().push_input(esc_ev2, true) # 再按 Esc 关浮窗(同键单义切换)
await get_tree().process_frame
if main.ui.modals.camp.esc_overlay.visible:
_problems.append("再按 Esc 未关浮窗(扩面收口失守)〔QA余 V13〕")
main._on_camp_closed() # 关层收闭(营地与选档层同收+开始层恢复——卡④ T3)
await get_tree().process_frame
if main.ui.modals.camp.scene.visible or not main.ui.screens.start_overlay.visible:
@@ -341,7 +527,7 @@ func _ready() -> void:
if motion_btn == null:
_problems.append("设置面减动效开关缺失")
else:
motion_btn.button_pressed = false # off=快速(减动效开)
ProbeUiUtil.push_click(main, motion_btn) # 真点拨杆(迭代 55 B5 扩面——原 button_pressed 直改绕过真输入命中面;off=快速)
await get_tree().process_frame
if UiKit.motion_scale != 0.5:
_problems.append("减动效开未生效(motion_scale != 0.5)")
@@ -349,10 +535,24 @@ func _ready() -> void:
chk.load_settings()
if not chk.reduced_motion:
_problems.append("减动效开未持久化(设置独立存储失守——卡④ AC⑤)")
motion_btn.button_pressed = true # on=完整回常态
ProbeUiUtil.push_click(main, motion_btn) # 真点回完整(on=完整)
await get_tree().process_frame
if UiKit.motion_scale != 1.0:
_problems.append("减动效关未恢复(motion_scale != 1.0)")
# 全屏拨杆真输入段(迭代 55 B5 扩面——settings 两枚 switch_toggle 其二;store 态断言为准)
var fs_btn: Button = main.ui.modals.menu.settings_overlay._fs_btn
if fs_btn == null:
_problems.append("设置面全屏拨杆缺失〔QA余 V13〕")
else:
var fs0: bool = iso_s.fullscreen
ProbeUiUtil.push_click(main, fs_btn) # 真点全屏拨杆
await get_tree().process_frame
if bool(iso_s.fullscreen) == fs0:
_problems.append("全屏拨杆真点击未翻转 store 态(%s→%s)〔QA余 V13〕" % [str(fs0), str(iso_s.fullscreen)])
ProbeUiUtil.push_click(main, fs_btn) # 真点复原
await get_tree().process_frame
if bool(iso_s.fullscreen) != fs0:
_problems.append("全屏拨杆复原失守〔QA余 V13〕")
# 音量实时生效(卡④ AC①):滑杆 30% → master bus db 与 store 双读
var vol: HSlider = main.ui.modals.menu.settings_overlay._volume
vol.value = 30
@@ -412,7 +612,7 @@ func _ready() -> void:
_problems.append("生涯页关闭后未恢复开始层")
# 换回全新零档(营地购买态不泄入后续冒险段——确定性;档案残留下次段首清理兜底)
ProbeUiUtil.isolate_save(main, "smoke_click_probe_reset", true, false, true) # 零档换注(继续上局钮已随三入口化撤出——卡② mn8)
main.ui.modals.menu.debug_overlay.set_sliders(400, 200) # 高加成保 L1 可完成——L2 岔路选路段只能经冒险推进到达(单关直入口退役)
main.ui.modals.menu.debug_overlay.set_sliders(2000, 2000) # 高加成保 L1 可完成——L2 岔路选路段只能经冒险推进到达(单关直入口退役)
main.ui.modals.menu.debug_overlay.seed_edit.text = "42" # 种子钉死(种子框默认为空=非首局随机,行为走查确定性禁依赖默认分支)
var t1 := Time.get_ticks_msec()
main._start_adventure_from_menu()
@@ -451,7 +651,17 @@ func _ready() -> void:
action_budget -= 1
await get_tree().process_frame
if main.ui.modals.run.node_overlay.visible:
ProbeUiUtil.push_click(main, main.ui.modals.run.node_overlay._btn)
# 节点继续一次性落点断言(迭代 55 B5 扩面——休息停点真输入继续,落点=overlay 关闭回到战斗屏)
if not _tested_node_continue:
_tested_node_continue = true
if not main.ui.flow.locks_input():
_problems.append("节点信息浮窗打开时输入未锁定〔QA余 V13〕")
ProbeUiUtil.push_click(main, main.ui.modals.run.node_overlay._btn) # 真点「继续」
await get_tree().process_frame
if main.ui.modals.run.node_overlay.visible:
_problems.append("节点「继续」真点击后浮窗未关(落点判定失守)〔QA余 V13〕")
else:
ProbeUiUtil.push_click(main, main.ui.modals.run.node_overlay._btn)
_seals_placed.clear()
continue
if main.ui.modals.run.curio_overlay.visible:
@@ -475,6 +685,17 @@ func _ready() -> void:
_problems.append("奖励节点战报文案未换代(卡⑤ wr2——旧话残留或新话缺失)")
if not main.ui.flow.locks_input():
_problems.append("三选一停点打开时输入未锁定")
# 三选一刷新钮一次性真输入段(迭代 55 B5 扩面——重抽意图经 refresh_requested 信号走组合根
# on_refresh;落点=停点仍开且候选重投喂,零崩零关)
if not _tested_curio_refresh and main.ui.modals.run.curio_overlay._refresh_btn.visible \
and not main.ui.modals.run.curio_overlay._refresh_btn.disabled:
_tested_curio_refresh = true
var cards_before: int = (main.ui.modals.run.curio_overlay._bcards as Array).size()
ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._refresh_btn) # 真点刷新图标钮
await get_tree().process_frame
if not main.ui.modals.run.curio_overlay.visible \
or (main.ui.modals.run.curio_overlay._bcards as Array).size() != cards_before:
_problems.append("三选一刷新钮真点击后停点未保持重投喂形态(落点判定失守)〔QA余 V13〕")
if main.ui.modals.run.curio_overlay._continue_btn.visible:
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():
@@ -498,6 +719,39 @@ func _ready() -> void:
if shop_leave == null:
_problems.append("商店「离开商店」钮缺失(真输入化驱动中断)")
else:
# 商店购买+刷新一次性真输入段(迭代 55 B5 扩面——购买写口经 buy_requested 信号,
# 刷新经 refresh_requested→on_shop_refresh MC-1;测试通道注 GP 保可达,行为序列零依赖)
if not _tested_shop_buy:
_tested_shop_buy = true
var rnr_shop = main.run_scope.runner
if rnr_shop != null and int(rnr_shop.gp) < 500:
rnr_shop.gp = 500 # 测试通道:注 GP 使购买与付费刷新可达(探针行为序列不依赖本注记)
await get_tree().process_frame
var buy_btn: Button = null
for c in main.ui.modals.menu.shop_overlay.find_children("*", "Button", true, false):
var cb := c as Button
if cb.text.begins_with("购买") and not cb.disabled:
buy_btn = cb
break
if buy_btn == null:
_problems.append("商店「购买」钮缺失或全置灰(真输入可达性失守)〔QA余 V13〕")
else:
var gp_before: int = int(rnr_shop.gp)
ProbeUiUtil.push_click(main, buy_btn) # 真点购买
await get_tree().process_frame
if int(rnr_shop.gp) >= gp_before:
_problems.append("商店购买真点击未出账(GP %d→%d)〔QA余 V13〕" % [gp_before, int(rnr_shop.gp)])
var shop_refresh: Button = main.ui.modals.menu.shop_overlay._refresh_btn
if shop_refresh != null and not shop_refresh.disabled:
var gp_r: int = int(rnr_shop.gp)
ProbeUiUtil.push_click(main, shop_refresh) # 真点刷新(重抽货架)
await get_tree().process_frame
if not main.ui.modals.menu.shop_overlay.visible:
_problems.append("商店刷新真点击后覆盖层异常关闭(落点判定失守)〔QA余 V13〕")
if int(rnr_shop.gp) > gp_r:
_problems.append("商店刷新未出账反涨(GP %d→%d)" % [gp_r, int(rnr_shop.gp)])
else:
_problems.append("商店刷新钮缺失或置灰(MC-1 投喂可达性失守)〔QA余 V13〕")
ProbeUiUtil.push_click(main, shop_leave) # 真点离店(leave_pressed 同源钮)放行选路
_seals_placed.clear()
continue
@@ -534,13 +788,21 @@ func _ready() -> void:
_problems.append("地图屏加成帽未换代为「属性值提升」(wa2)")
# 选路定向(卡⑤ d4——wa4 转正的必达前提):候选含奖励节点优先选(真实
# press_candidate 命中路径零变化,只改索引策略);无奖励候选回退交替(岔路使用率观测面保留)
# 迭代 55 B5 扩面——节点继续段未测时休息候选最优先(node_overlay 仅休息停点开启)
var pick := _route_clicks % 2
if main.run_scope.runner != null:
for nd in main.run_scope.runner.nodes():
var nd_id: int = main.run_scope.runner.node_id_of(nd)
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)
break
if not _tested_node_continue:
for nd in main.run_scope.runner.nodes():
var nd_id_rest: int = main.run_scope.runner.node_id_of(nd)
if String(nd.get("type", "")) == "rest" and main.ui.modals.run.level_screen._cand_ids.has(nd_id_rest):
pick = main.ui.modals.run.level_screen._cand_ids.find(nd_id_rest)
break
if pick == _route_clicks % 2:
for nd in main.run_scope.runner.nodes():
var nd_id: int = main.run_scope.runner.node_id_of(nd)
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)
break
ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(pick).get_center())
_route_clicks += 1
_seals_placed.clear()
@@ -720,6 +982,54 @@ func _ready() -> void:
# 卡④大门 T5 接收,两头登记禁静默删除;续玩钮已退役,主菜单确认链已摘 mn6)。
# ① 战斗收口断点写盘断言保留(上方——冒险流直调中间态 mn10)。
# ---- 迭代 55 B5 扩面收尾段(地图放弃动线——主循环流不定达放弃,独立新局确定性驱动)----
# 判据出处: design/ux/level-map.md:11 (主循环选路段——放弃二击真输入组)
ProbeUiUtil.isolate_save(main, "smoke_click_probe_expansion", true, false, true)
main.ui.modals.menu.debug_overlay.set_sliders(2000, 2000) # 高加成上限(与主循环同口径——层速推进保放弃停点快达)
main.ui.modals.menu.debug_overlay.seed_edit.text = "44"
main._start_adventure_from_menu()
await get_tree().process_frame
await get_tree().process_frame
var ab_guard := 4000
var ab_done := false
while ab_guard > 0 and not ab_done:
ab_guard -= 1
await get_tree().process_frame
if main.ui.modals.run.end_overlay.is_open():
break # 败退先至——放弃动线未达(问题行呈报)
if main.ui.modals.run.curio_overlay.visible:
if main.ui.modals.run.curio_overlay._continue_btn.visible:
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():
ProbeUiUtil.push_click_at(main, (main.ui.modals.run.curio_overlay._bcards[0] as Control).get_global_rect().get_center())
await get_tree().process_frame
ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._confirm_btn)
else:
ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._continue_btn)
continue
if main.ui.modals.run.node_overlay.visible:
ProbeUiUtil.push_click(main, main.ui.modals.run.node_overlay._btn)
continue
if main.ui.modals.run.level_screen.visible:
var ab_btn: Button = ProbeUiUtil.find_button(main.ui.modals.run.level_screen, "放弃", "prefix")
if ab_btn != null:
ProbeUiUtil.push_click(main, ab_btn) # 真点「放弃」一击(3s 确认窗)
await get_tree().process_frame
ProbeUiUtil.push_click(main, ab_btn) # 真点二击确认(窗内)
for i in 10:
await get_tree().process_frame
if main.ui.modals.run.end_overlay.is_open():
print("[click_probe] 放弃动线落点=终局面(地图放弃真输入)")
else:
_problems.append("放弃二击后未达终局面(落点判定失守)〔QA余 V13〕")
ab_done = true
if not ab_done:
_problems.append("扩面收尾段放弃动线未达(guard 耗尽或败退先至)〔QA余 V13〕")
# 终局面返回开始菜单(大门段菜单前提归位——句柄沿断点段同源 _btn,文本查找零命中实证)
if main.ui.modals.run.end_overlay.is_open() and main.ui.modals.run.end_overlay._btn != null:
ProbeUiUtil.push_click(main, main.ui.modals.run.end_overlay._btn) # 真点「返回」(_btn=继续/返回共用句柄——断点段同款)
await get_tree().process_frame
# ---- 大门两分支断言段(卡④ T5 接收——卡② 移交落点;V3 断言面)----
# 判据出处: design/ux/camp.md:16 (大门段——点大门先出确认组)
# 驱动=营地场景根点选信号(真实路由)→组合根 _on_camp_object_pressed→确认弹窗。
@@ -906,7 +1216,15 @@ func _run_modal_probes_once() -> void:
", "|"))
if not legend_has_seal:
_problems.append("图例缺『封印』格态条目(格态图例基线)")
main.ui.modals.run.legend_overlay.close()
# 图例关闭走真输入关闭钮(迭代 55 B5 扩面——原 .close() 函数直调绕过关闭钮命中面;
# 本函数为主循环内同步调用(协程化会与主循环双分派竞态)——push_click 的 release
# 同帧触发关闭处理器,断言同帧读数,零 await)
var legend_close_btn: Button = ProbeUiUtil.find_button(main.ui.modals.run.legend_overlay, "关闭")
if legend_close_btn == null:
_problems.append("图例「关闭」钮缺失(真输入关闭路径驱动中断)〔QA余 V13〕")
main.ui.modals.run.legend_overlay.close()
else:
ProbeUiUtil.push_click(main, legend_close_btn) # 真点「关闭」收图例
if main.ui.modals.run.legend_overlay.is_open() or main.ui.flow.locks_input():
_problems.append("图例关闭后未恢复输入/可见性")
# ②/②b 明细回看模态无探针(M9/P-C7——冒险中回看归「生涯」,06 登记)
@@ -1001,6 +1319,13 @@ func _run_modal_probes_once() -> void:
_problems.append("状态槽 tooltip 未随移出隐藏")
break
# ④ 弃图刷新确认链(pattern 7 异位:确认 chips 与常规确认不同位 + 显式代价文案)
# 迭代 55 B5 扩面——确定性前置:RAP 不足时经 grant_reserve_ap 注足(测试通道——探针行为序列
# 不依赖本注记;零注记时本链只在停手转存积累 RAP≥8 的偶发轮触发=覆盖不稳定实证)
if not _tested_refresh_chain and main.run_scope.settlement != null \
and main.run_scope.settlement.action_economy.phase == "Active" \
and not main.run_scope.settlement.can_refresh() \
and int(main.run_scope.settlement.action_economy.rap) < 8:
main.run_scope.settlement.action_economy.grant_reserve_ap(8 - int(main.run_scope.settlement.action_economy.rap))
if not _tested_refresh_chain and main.ui.battle.board_zone.ap_bar.is_refresh_ready():
_tested_refresh_chain = true
var rap_before: int = int(main.run_scope.settlement.action_economy.rap)