# VERTICAL SLICE - NOT FOR PRODUCTION # 探针共用面:树寻址助手 + 存档/设置/元进度隔离样板单点(T6 样板收口批 2026-10-02)。 # 收口范围:ui_probe / ui_click_probe / shot_probe / fx_shot_probe 四探针的 7 处树寻址样板 # 与 7 处隔离样板(各探针隔离文件名后缀 tag 保持不变)。 # 用法:探针侧 `const ProbeUiUtil := preload("res://smoke/probe_ui_util.gd")` 后静态调用 #(探针载体含 --script 场景两种,均无全局标识符——禁 class_name 裸引用)。 extends RefCounted const SaveManagerScript := preload("res://core/save_manager.gd") const MetaProgressionScript := preload("res://core/meta_progression.gd") const SettingsStoreScript := preload("res://presentation/settings_store.gd") # ── 树寻址(递归只读——按钮/标签句柄未存组合根成员的探针专用面) ────────────── # 匹配模式参数化(历史两种语义,禁混用禁静默改变): # mode="prefix" = text.begins_with(needle)(ui_probe / ui_click_probe 历史语义) # mode="contains" = text.contains(needle)(shot_probe / fx_shot_probe 历史语义) # 调用点替换时逐一核对原语义后选参。 static func find_button(root: Node, needle: String, mode: String = "prefix") -> Button: if root == null: return null if root is Button and _text_match(String(root.text), needle, mode): return root for child in root.get_children(): var found := find_button(child, needle, mode) if found != null: return found return null # CheckButton 同款寻址(ui_click_probe 历史语义 = contains——设置面旧入口退役断言用) static func find_check_button(root: Node, needle: String) -> CheckButton: if root == null: return null if root is CheckButton and String(root.text).contains(needle): return root for child in root.get_children(): var found := find_check_button(child, needle) if found != null: return found return null # Label 按文案包含寻址(shot_probe GP 徽章读数锚) static func find_label(root: Node, text_fragment: String) -> Label: if root == null: return null if root is Label and String(root.text).contains(text_fragment): return root for child in root.get_children(): var found := find_label(child, text_fragment) if found != null: return found return null # 控件实测几何(shot_probe checker 文字框取样坐标源——禁硬编码像素,分辨率档位自适应) static func label_rect(root: Node, text_fragment: String) -> Dictionary: if root == null: return {} if root is Label and String(root.text).contains(text_fragment): return {"x": root.global_position.x, "y": root.global_position.y, "w": root.size.x, "h": root.size.y} for child in root.get_children(): var r := label_rect(child, text_fragment) if not r.is_empty(): return r return {} # 按脚本名寻址(resource_path contains——shot_probe hero 面板锚) static func find_by_script(root: Node, script_name: String) -> Node: if root == null: return null var s: Script = root.get_script() if s != null and String(s.resource_path).contains(script_name): return root for child in root.get_children(): var r := find_by_script(child, script_name) if r != null: return r return null static func _text_match(text: String, needle: String, mode: String) -> bool: return text.begins_with(needle) if mode == "prefix" else text.contains(needle) # ── 存档/设置/元进度三面隔离(探针禁碰真实档 user://save.json——SaveManager 路径注入纪律) ── # tag = 隔离档名(不带扩展名,如 "smoke_input_probe_save"——各探针后缀保持不变);先删后建防陈旧档泄入。 # mark_first_done = 对齐真实档游玩史口径(模板抽签路径不变);with_settings = 连设置面换注 #(隔离 cfg 名 = _settings.cfg,与被收口探针原文件名逐一同名);refresh_resume = 组合根 # 「继续上局」按钮重刷;swap_save = 是否换注 main.save_manager(营地先行段只换 meta 不换 save_manager)。 # 返回隔离 SaveManager 实例(调用方常需后续直读余额/重读档)。 static func isolate_save(main, tag: String, mark_first_done: bool = false, with_settings: bool = false, refresh_resume: bool = false, swap_save: bool = true): 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): DirAccess.remove_absolute(p) var iso = SaveManagerScript.new(save_path) iso.load_from_disk() if mark_first_done: iso.mark_first_adventure_done() if swap_save: main.save_manager = iso main.meta = MetaProgressionScript.new(main.db, iso) if with_settings: main.settings_store = SettingsStoreScript.new("user://%s_settings.cfg" % tag) if refresh_resume: main._refresh_resume_button() return iso