Files
gplan-2/game/probe/fx_shot_probe.gd
T

122 lines
5.2 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
# 演出渲染证据探针(真实渲染设备抓战斗演出瞬间帧)。
# 必须**窗口化**运行(真渲染设备——headless 无渲染面):Godot_console.exe --path . res://probe/fx_shot_probe.tscn -- <输出目录>
# 流程 = shot_probe 同款隔离面(存档/设置/元进度换注隔离档,禁碰真实档)+ 真实按钮路径开冒险进首战;
# 自动玩家挖格驱动行结算,在 行到达/弹道飞行/结算排播 三时刻抓帧——演出链「逻辑在(fx_probe PASS)×
# 渲染在(本探针 PNG)」双证的第二半边。PNG 落输出目录(artwork/qa-fx-*/,素材纪律不入 git)。
extends Node
const ProbeUiUtil := preload("res://probe/probe_ui_util.gd") # T6 收口:树寻址/隔离样板单点
# ── 演出采样窗口口径(headless/窗口化帧率不定,帧时刻以墙钟近似锚定;改窗口 = 改抓帧
# 时刻观测面,非断言语义。窗口值与字面量同值——仅提为具名常量。) ──
const T_DIG_FLIGHT := 0.12 # 挖格行到达/弹道在飞窗口(settle_batch 回执后停留)
const T_SETTLE := 0.35 # 结算排播窗口(行到达后二连抓)
const T_ROUND_TICK := 0.25 # 停手轮末(敌方攻击弹道/受击闪白 0.2-1.6s 流过)连抓 6 帧的采样步距
const T_FINAL := 0.6 # 收尾帧前静置
var main: Node
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
ProbeUiUtil.isolate_save(main, "fx_shot_probe", false, true, true) # 末参=swap_save(卡②签名换代——原 refresh_resume 槽位语义退役)
# 隔离档即时应用(真实档 fullscreen 态会污染抓帧尺寸〔shot_probe 同款口径〕)
main.settings_store.apply()
for i in 8:
await get_tree().process_frame # UI 装配完成等足帧(canvas_item 空引用窗口)
main._start_adventure_from_menu() # 函数直调中间态(卡② mn10——开始冒险钮已撤)
# 等关卡画面出图(choices_presented 异步到达——最多 3s 轮询)
for i in 60:
await get_tree().process_frame
if main.ui.modals.run.level_screen != null and main.ui.modals.run.level_screen.visible:
break
if main.ui.modals.run.level_screen == null or not main.ui.modals.run.level_screen.visible:
_fail("关卡画面未出现(函数直调中间态路径)")
return
for i in 5:
await get_tree().process_frame
main.ui.modals.run.level_screen.press_candidate(0)
for i in 20:
await get_tree().process_frame
var st = main.run_scope.settlement
if st == null:
_fail("首战未开(settlement 空)")
return
var rules = main.run_scope.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(T_DIG_FLIGHT).timeout
await _snap("fx_dig%d" % dug) # 行到达/弹道在飞窗口
await get_tree().create_timer(T_SETTLE).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(T_ROUND_TICK).timeout
await _snap("fx_round%d" % (i + 1))
if dug == 0:
_fail("无安全格可挖(首板全雷不可信)")
return
await get_tree().create_timer(T_FINAL).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 _fail(msg: String) -> void:
_problems.append(msg)
push_error("[fx_shot_probe] FAIL — " + msg)
get_tree().quit(1)