132 lines
5.0 KiB
GDScript
132 lines
5.0 KiB
GDScript
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||
# 演出渲染证据探针(渲染层测试解冻批 2026-10-01——fx_shot_probe:真实渲染设备抓战斗演出瞬间帧)。
|
||
# 必须**窗口化**运行(真渲染设备——headless 无渲染面):Godot_console.exe --path . res://smoke/fx_shot_probe.tscn -- <输出目录>
|
||
# 流程 = shot_probe 同款隔离面(存档/设置/元进度换注隔离档,禁碰真实档)+ 真实按钮路径开冒险进首战;
|
||
# 自动玩家挖格驱动行结算,在 行到达/弹道飞行/结算排播 三时刻抓帧——演出链「逻辑在(fx_probe PASS)×
|
||
# 渲染在(本探针 PNG)」双证的第二半边。PNG 落输出目录(artwork/qa-fx-*/,素材纪律不入 git)。
|
||
extends Node
|
||
|
||
var main
|
||
var out_dir := ""
|
||
var _problems: Array = []
|
||
|
||
|
||
func _ready() -> void:
|
||
var args := OS.get_cmdline_user_args()
|
||
out_dir = args[0] if args.size() >= 1 and args[0] != "" else "user://qa-fx"
|
||
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
|
||
for p0 in ["user://fx_shot_probe.json", "user://fx_shot_probe.json.tmp", "user://fx_shot_probe_settings.cfg"]:
|
||
if FileAccess.file_exists(p0):
|
||
DirAccess.remove_absolute(p0)
|
||
var iso_c = load("res://core/save_manager.gd").new("user://fx_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://fx_shot_probe_settings.cfg")
|
||
for i in 8:
|
||
await get_tree().process_frame # UI 装配完成等足帧(canvas_item 空引用窗口)
|
||
var lvbtn: Button = _find_button_by_sub(main._start_overlay, "开始冒险")
|
||
if lvbtn == null:
|
||
_fail("开始冒险按钮缺失")
|
||
return
|
||
lvbtn.pressed.emit()
|
||
# 等关卡画面出图(choices_presented 异步到达——最多 3s 轮询)
|
||
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("关卡画面未出现(lvbtn 路径)")
|
||
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
|
||
var st = main.settlement
|
||
if st == null:
|
||
_fail("首战未开(settlement 空)")
|
||
return
|
||
var rules = main.settlement.minesweeper
|
||
var board = rules.current_board()
|
||
# 自有驱动(只挖/走——测试驱动可用板面数据挑安全格;新手策略档的插旗拒绝会耗尽预算):挖到 3 格停
|
||
var dug := 0
|
||
var guard := 40
|
||
while dug < 3 and guard > 0:
|
||
guard -= 1
|
||
var target = null
|
||
for n in board.neighbors8(rules.hero_pos()):
|
||
if not rules.is_revealed(n) and not board.is_mine(n):
|
||
target = n
|
||
break
|
||
if target != null:
|
||
var out: Array = rules.settle_batch([{"action": "dig", "cell": target}])
|
||
if out.is_empty() or not bool(out[0].get("accepted", false)):
|
||
continue
|
||
dug += 1
|
||
await get_tree().create_timer(0.12).timeout
|
||
await _snap("fx_dig%d" % dug) # 行到达/弹道在飞窗口
|
||
await get_tree().create_timer(0.35).timeout
|
||
await _snap("fx_settle%d" % dug) # 结算排播窗口
|
||
continue
|
||
# 无相邻可挖 → 走向有未开安全格邻居的已揭开格(挖掘期移动免费)
|
||
var dest = null
|
||
for c in rules.revealed_cells():
|
||
if c == rules.hero_pos() or rules.compute_move_path(c).is_empty():
|
||
continue
|
||
for n in board.neighbors8(c):
|
||
if not rules.is_revealed(n) and not board.is_mine(n):
|
||
dest = c
|
||
break
|
||
if dest != null:
|
||
break
|
||
if dest == null:
|
||
break
|
||
rules.settle_batch([{"action": "move", "cell": dest}])
|
||
# 停手 → 轮末结算(敌方攻击弹道/受击闪白/结算排播在 0.2-1.6s 窗口流过)——连抓 6 帧
|
||
rules.settle_batch([{"action": "stop"}])
|
||
for i in 6:
|
||
await get_tree().create_timer(0.25).timeout
|
||
await _snap("fx_round%d" % (i + 1))
|
||
if dug == 0:
|
||
_fail("无安全格可挖(首板全雷不可信)")
|
||
return
|
||
await get_tree().create_timer(0.6).timeout
|
||
await _snap("fx_final")
|
||
if _problems.is_empty():
|
||
print("[fx_shot_probe] PASS — 演出帧 %d 张落 %s(挖格 %d)" % [dug * 2 + 1, out_dir, dug])
|
||
get_tree().quit(0)
|
||
else:
|
||
for p in _problems:
|
||
push_error("[fx_shot_probe] FAIL — " + p)
|
||
get_tree().quit(1)
|
||
|
||
|
||
func _snap(tag: String) -> void:
|
||
await RenderingServer.frame_post_draw
|
||
var img: Image = get_viewport().get_texture().get_image()
|
||
if img == null or img.is_empty():
|
||
_problems.append("抓帧空纹理: " + tag)
|
||
return
|
||
img.save_png("%s/%s.png" % [out_dir, tag])
|
||
|
||
|
||
func _find_button_by_sub(root: Node, needle: String) -> Button:
|
||
if root is Button and String(root.text).contains(needle):
|
||
return root
|
||
for c in root.get_children():
|
||
var r := _find_button_by_sub(c, needle)
|
||
if r != null:
|
||
return r
|
||
return null
|
||
|
||
|
||
func _fail(msg: String) -> void:
|
||
_problems.append(msg)
|
||
push_error("[fx_shot_probe] FAIL — " + msg)
|
||
get_tree().quit(1)
|