Files
gplan-2/game/smoke/fx_shot_probe.gd
T
DKChan 32fdf957fd refactor(代码术语): 卡① flag→seal 全族换代+game/ 禁用词清扫(迭代 31 卡①)
标识符面:flag_*→seal_ 族(含 Godot 信号 flag_judged→seal_judged 与连接/回调同名、
FACTS 两键 flags_count→seals_count/dug_under_flag→dug_under_seal 全链、
.tres 键名与 schema .gd 同批同步);显式豁免零改动=size_flags_*(Godot 布局属性)+_fatal_flag。
中文面:金币→GP(金币袋→GP 袋)/插旗→封印/雷格→危险格/惩罚格→危险格/旗→封印·封印印记族,
game/ 内 *.gd/.tres/.md/.json 四类禁用词与 flag 双双零命中(V1/V4)。
守卫字面量不动(被检查的禁字不是使用):smoke_all V21 零「雷/旗」检查面、
ui_font_asset_test 字形集攻/雷/旗/封 保持原断言逻辑。
代拍板:板面单字槽 glyph「旗」→「封」(禁字纪律+单字位宽,消费面四处同名随动)。
素材换代(2026-10-18 用户裁案甲):assets/art/board/flag_seal.png→seal_mark.png(素材不入 git,
盘上改名+导入重刷),代码两处 res:// 字符串+COL_FLAG/CURSOR_FLAG 常量同步,
闸件四处随批(run_all_gates.sh/render_gate.sh/golden_baseline_refresh.sh/path_check.py)。
anchor.json 键名手改,重锚与改前改后 diff 随本卡收口(同裁案甲)。

基线读数(改前,主区 ed2dfaf6):冒烟 1151 检查 0 失败/单元 34 套全绿/探针 --click
存量红仅 screen_flow_probe(user:// 断点污染在册已知项)。
改后同批读数(本 worktree):冒烟 1151 检查 0 失败/探针 --click 同一存量红、其余全绿。
2026-10-19 02:52:38 +08:00

126 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://smoke/fx_shot_probe.tscn -- <输出目录>
# 流程 = shot_probe 同款隔离面(存档/设置/元进度换注隔离档,禁碰真实档)+ 真实按钮路径开冒险进首战;
# 自动玩家挖格驱动行结算,在 行到达/弹道飞行/结算排播 三时刻抓帧——演出链「逻辑在(fx_probe PASS)×
# 渲染在(本探针 PNG)」双证的第二半边。PNG 落输出目录(artwork/qa-fx-*/,素材纪律不入 git)。
extends Node
const ProbeUiUtil := preload("res://smoke/probe_ui_util.gd") # T6 收口:树寻址/隔离样板单点
# ── 演出采样窗口口径(拍板点:headless/窗口化帧率不定,帧时刻以墙钟近似锚定;改窗口 = 改抓帧
# 时刻观测面,非断言语义。窗口值与原字面量逐一同值——T6 收口批仅提具名。) ──
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, false)
# 隔离档即时应用(迭代 19 卡① T1——真实档 fullscreen 态会污染抓帧尺寸〔shot_probe 同款修正〕)
main.settings_store.apply()
for i in 8:
await get_tree().process_frame # UI 装配完成等足帧(canvas_item 空引用窗口)
var lvbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "开始冒险", "contains")
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.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)