Files
gplan-2/game/smoke/shot_probe.gd
T

175 lines
7.5 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# VERTICAL SLICE - NOT FOR PRODUCTION
# 整屏截图探针(迭代 9 卡⑰a 2026-09-30——呈现层测试分级 B 档基建:四屏视口截图程序化产出)。
# 必须**窗口化**运行(真渲染设备——headless 无渲染面,get_image 得到空纹理):
# Godot_console.exe --path . res://smoke/shot_probe.tscn -- <输出目录>
# 流程 = 真实按钮信号路径驱动(ui_click_probe 同款;存档/设置/元进度三面换注隔离档——禁碰真实档,
# 坑册「断点写盘随 save_manager 引用走」教训同款覆盖):菜单(启动态)→ 营地(campbtn)→
# 关卡画面(lvbtn→停点出图)→ 战斗(level_screen.press_candidate(0) 真实选路提交)。
# 每屏双帧 + frame_post_draw 同步后抓 get_viewport().get_texture(),PNG 落输出目录;
# 最小自检 = 四件存在且非零字节(像素方差断言归 tools/shot_checker.py——B 档骨架分工)。
extends Node
var main
var out_dir := ""
var _problems: Array = []
var _meta := {} # 控件实测几何(全局 rect——shot_checker 文字框/明度带取样坐标源,禁硬编码像素)
func _ready() -> void:
var args := OS.get_cmdline_user_args()
out_dir = args[0] if args.size() >= 1 and args[0] != "" else "user://qa-screens"
DirAccess.make_dir_recursive_absolute(out_dir)
await get_tree().process_frame
main = load("res://presentation/main.tscn").instantiate()
add_child(main)
await get_tree().process_frame
# ── 存档/元进度/设置三面隔离(ui_click_probe 同款——真档零污染) ──
for p0 in ["user://smoke_shot_probe.json", "user://smoke_shot_probe.json.tmp",
"user://smoke_shot_probe_settings.cfg"]:
if FileAccess.file_exists(p0):
DirAccess.remove_absolute(p0)
var iso_c = load("res://core/save_manager.gd").new("user://smoke_shot_probe.json")
iso_c.load_from_disk()
main.save_manager = iso_c
main.meta = load("res://core/meta_progression.gd").new(main.db, iso_c)
main.settings_store = load("res://presentation/settings_store.gd").new("user://smoke_shot_probe_settings.cfg")
await get_tree().process_frame
# ① 菜单(启动态)
_meta["menu_sub"] = _find_label_rect(main._start_overlay, "改造批")
_meta["menu_help"] = _find_label_rect(main._start_overlay, "玩法:")
_meta["menu_title"] = _find_label_rect(main._start_overlay, "Dungeon Sweeper")
await _snap("menu")
# ② 营地
var campbtn: Button = _find_button_by_prefix(main._start_overlay, "营地")
if campbtn == null:
_problems.append("营地入口缺失(开始层 campbtn)")
else:
campbtn.pressed.emit()
await get_tree().process_frame
await _snap("camp")
var close_btn: Button = _find_button_by_prefix(main.camp_overlay, "关闭营地")
if close_btn != null:
close_btn.pressed.emit()
await get_tree().process_frame
# ③ 关卡画面(开始冒险→停点出图——冒险开始即出图,停点前移批语义)
var lvbtn: Button = _find_button_by_prefix(main._start_overlay, "开始冒险")
if lvbtn == null:
_problems.append("开始冒险按钮缺失(开始层 lvbtn)")
else:
lvbtn.pressed.emit()
for i in 4:
await get_tree().process_frame
await _snap("level")
# GP 常驻徽章断言(迭代 9 卡⑪核验——读数=runner.gp 现值,刷新=写路径回执后重查〔open_choices〕)
var gp_label: Label = _find_label_by_text(main.level_screen, "◈ ")
if gp_label == null or not gp_label.is_visible_in_tree():
_problems.append("GP 徽章不可见(关卡画面顶栏)")
elif main.level_screen.get("_runner") != null:
var expect := "◈ %d" % int(main.level_screen.get("_runner").gp)
if gp_label.text != expect:
_problems.append("GP 徽章读数不符: %s != %s" % [gp_label.text, expect])
# ④ 战斗(节点一——真实选路提交路径 press_candidate,ui_click_probe :199 同款)
if main.level_screen != null and main.level_screen.visible:
main.level_screen.press_candidate(0)
for i in 10:
await get_tree().process_frame
await _snap("battle")
# 战斗屏文字框/明度带锚点(全局 rect——checker 取样坐标源)
_meta["battle_log_hint"] = _find_label_rect(main.stage, "结算明细")
_meta["hero_panel"] = _node_rect(_find_by_script(main, "hero_info_panel_view"))
_meta["stage_view"] = _node_rect(main.stage)
_meta["board_zone"] = _node_rect(main.board_zone)
# draw calls 真机测量(迭代 9 卡⑦——基线 270→目标 <200;窗口化渲染帧读数,
# 取值时机=战斗截图同帧后一帧——呈现终态满负荷)
await get_tree().process_frame
var dc := int(Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME))
_meta["battle_draw_calls"] = dc
print("[shot_probe] battle draw_calls=%d(基线 270/目标 <200)" % dc)
else:
_problems.append("关卡画面未出现(lvbtn 路径)")
# ── 几何元数据落盘(shot_checker 取样坐标源) ──
var mf := FileAccess.open(out_dir.path_join("shot_meta.json"), FileAccess.WRITE)
if mf != null:
mf.store_string(JSON.stringify(_meta))
mf.close()
# ── 最小自检:四件存在且非零字节 ──
for s in ["menu", "camp", "level", "battle"]:
var p := out_dir.path_join("shot_%s.png" % s)
if not FileAccess.file_exists(p):
_problems.append("截图缺失: %s" % p)
else:
var fa := FileAccess.open(p, FileAccess.READ)
if fa == null or fa.get_length() < 1000:
_problems.append("截图疑似空文件: %s" % p)
print("[shot_probe] 完成 out=%s 问题=%d %s" % [out_dir, _problems.size(), str(_problems)])
get_tree().quit(1 if _problems.size() > 0 else 0)
func _snap(key: String) -> void:
await RenderingServer.frame_post_draw
await RenderingServer.frame_post_draw
var img: Image = get_viewport().get_texture().get_image()
if img == null:
_problems.append("视口纹理为空: %s" % key)
return
img.save_png(out_dir.path_join("shot_%s.png" % key))
print("[shot_probe] saved %s (%dx%d)" % [key, img.get_width(), img.get_height()])
func _find_button_by_prefix(root: Node, prefix: String) -> Button:
if root == null:
return null
if root is Button and String(root.text).contains(prefix):
return root
for c in root.get_children():
var r := _find_button_by_prefix(c, prefix)
if r != null:
return r
return null
# 控件实测几何采集(checker 断言坐标源——禁硬编码像素,分辨率档位自适应)
func _find_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 c in root.get_children():
var r := _find_label_rect(c, text_fragment)
if not r.is_empty():
return r
return {}
func _find_label_by_text(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 c in root.get_children():
var r := _find_label_by_text(c, text_fragment)
if r != null:
return r
return null
func _node_rect(n: Node) -> Dictionary:
if n == null or not (n is CanvasItem):
return {}
return {"x": n.global_position.x, "y": n.global_position.y,
"w": n.size.x, "h": n.size.y}
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 c in root.get_children():
var r := _find_by_script(c, script_name)
if r != null:
return r
return null