Files
gplan-2/tools/render_gate.sh
T

182 lines
11 KiB
Bash
Executable File
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.
#!/usr/bin/env bash
# 渲染闸正主——窗口化自动闸。
# 范围(渲染五闸的机器面:shot_probe/golden_compare/board_shot 三件入闸;
# fx_probe 逻辑层归 probe_gate --full;1920 档布局机检面待补)。
# 骨架复用 probe_gate:①每段 timeout 守卫(挂死=FAIL 不静默)②段前预清+段后清本 game 目录孤儿
# Godot(按命令行精确匹配本 game 路径,不误杀其他 worktree 会话)③末尾残留清点=批末闸(必须 0)。
# 段:
# ① shot_probe 七屏窗口化截图(menu/settings/career/camp/level/battle/status)
# → tools/shot_checker.py A-D 语义断言自动判定(A 屏级骨架全屏/B 文字对比度/C 明度三档带/
# D 文字禁压纹理;E 焦点预算=语义面仍归人闸不机检)。首跑存量 FAIL 按 S1 停机点全量呈报登记待办清单。
# ② 取证段(Q7:超时/零字节/文件缺失任一即 FAIL——「渲染真实发生」必须成立,不做像素判定):
# fx_shot_probe 三时刻抓帧(行到达/结算排播/轮末连帧)+ board_shot_probe L1-L3 棋盘抓帧。
# ③ 棋盘 golden 逐像素:board 整屏帧按 board_meta.json 取样坐标裁剪棋盘区
# (tools/golden_board_crop.py——禁硬编码像素)→ tools/golden_compare.py 对
# game/tests/golden/ 三张基线(容差 provisional:差 ≤16/changed ≤2%,S2 停机点口径)。
# 素材缺位=判红:缺盘时这一段根本不对比,若 SKIP 则闸末照样打
# 「全绿」exit 0,等于空跑判绿。基线生成期素材缺位=L3 停机,运行期同样无权出验收结论。
# ④ 基准档 1280 单档(Q4)。1920 档注记:16:9 全屏下 stretch=canvas_items/aspect=keep 等比
# 1.35×+左右黑边各 96px,构图与基准档一致(分辨率案甲)——分辨率维度不重复建闸。
# (1920 布局机检面现无承载,待补。)
# ⑤ 脚本错误扫描(三硬闸之二):shot/fx/board 三段 Godot 日志逐段过 tools/error_scan.py,
# 命中 SCRIPT ERROR/Parse Error 即红(标记与 run_smoke.py 同源);python 工具自身输出不扫。
# 此类错误不改退出码、不计失败数,且会中断所在函数的剩余断言——只能由外部扫日志。
# 新增日志段必须同步加扫描调用,漏加=该段静默错误漏网。
# 过程产物纪律(Q2):一切截图 PNG 落 mktemp 临时区,闸毕即弃——任何过程 PNG 禁入 git;
# 例外=game/tests/golden/ 基线(测试资产入库,.gitignore 已核不挡)。
# 窗口化运行会短暂弹 Godot 窗口(本机可开窗口径);禁改 --headless 跑本闸
# (headless 无渲染面,探针内 fail-fast 护栏会红——那是护栏生效不是闸红)。
# 用法: tools/render_gate.sh [game目录] 缺省 game 目录=脚本所在仓库的 game/(worktree 内自动指向该 worktree)
# 依赖: tools/gate_platform.sh(双平台封装)+ python(PIL);Windows 侧另用 taskkill/PowerShell,macOS/Linux 走共享件;Godot 路径可用环境变量 GODOT_BIN 覆盖。
set -u
G="${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}"
REPO="$(cd "$(dirname "$0")/.." && pwd)"
. "$REPO/tools/gate_platform.sh" # 跨平台件(平台探测/路径形式/带超时执行/孤儿清杀)
GAME="${1:-$REPO/game}"
GAME_WIN="$(gate_game_path "$GAME")" # Godot --path 接受形式(Windows 混合斜杠,其他平台原样)
GOLDEN="$REPO/game/tests/golden/$(gate_platform_id)" # 本平台基线目录(按平台各存,比较器同口径)
TMP="$(mktemp -d /tmp/rendergate_XXXXXX)"
trap 'rm -rf "$TMP"' EXIT
_rc=0
orphans() { # 列出命令行含本 game 路径的 Godot 进程 PID(实现单点在 gate_platform.sh,双平台)
gate_orphans "$GAME_WIN"
}
kill_orphans() {
gate_kill_orphans "$GAME_WIN"
}
scan_log() { # scan_log <段名> <日志>——脚本错误扫描;日志不可读(error_scan exit 2)同判红
local name="$1" log="$2"
if ! python "$REPO/tools/error_scan.py" "$log" >"$TMP/err_scan.txt" 2>&1; then
echo "[render_gate] FAIL — $name 脚本错误扫描未通过(命中或日志不可读):"
sed -n '1,6p' "$TMP/err_scan.txt" | sed 's/^/ /'
_rc=1
fi
}
# 冷检出刷导入(.godot 不入 git,fresh worktree 首跑一次性付)
if [ ! -f "$GAME/.godot/uid_cache.bin" ]; then
echo "[render_gate] 冷检出:editor --quit 刷导入(一次性,可到 1-2 分钟)"
gate_run_timeout 300 "$TMP/refresh.log" "$G" --headless --path "$GAME_WIN" --editor --quit >/dev/null 2>&1
kill_orphans
fi
pre="$(orphans | wc -l | tr -d ' ')"
if [ "$pre" -gt 0 ]; then echo "[render_gate] 预清本闸残留 Godot×$pre"; kill_orphans; fi
echo "======== shot_probe 七屏截图 → shot_checker A-D ========"
SHOT="$TMP/shots"
gate_run_timeout 360 "$TMP/shot.log" "$G" --path "$GAME_WIN" --quit-after 3000 res://probe/shot_probe.tscn -- "$SHOT"
st=$?; kill_orphans
grep -E "\[shot_probe\]" "$TMP/shot.log" | tail -12
if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then
echo "[render_gate] FAIL — shot_probe 超时挂死(exit=$st),孤儿已清"; _rc=1
elif ! grep -q "问题=0" "$TMP/shot.log"; then
echo "[render_gate] FAIL — shot_probe 未干净完成(exit=${st},缺失屏/空文件/路径断裂见上)"; _rc=1
fi
if [ "$_rc" -eq 0 ]; then
python tools/shot_checker.py "$SHOT" shot_menu.png shot_settings.png shot_career.png shot_camp.png shot_level.png shot_battle.png shot_status.png | sed 's/^/[render_gate] /'
_rc="${PIPESTATUS[0]}"
[ "$_rc" -ne 0 ] && echo "[render_gate] FAIL — shot_checker A-D 断言存在红(S1 停机点:首跑存量 FAIL 全量呈报登记待办清单)"
fi
scan_log "shot_probe" "$TMP/shot.log"
echo "======== fx_shot_probe 三时刻取证(Q7) ========"
FX="$TMP/fxshots"
gate_run_timeout 360 "$TMP/fx.log" "$G" --path "$GAME_WIN" --quit-after 3000 res://probe/fx_shot_probe.tscn -- "$FX"
st=$?; kill_orphans
grep -E "\[fx_shot_probe\]" "$TMP/fx.log" | tail -4
if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then
echo "[render_gate] FAIL — fx_shot_probe 超时挂死(exit=$st),孤儿已清"; _rc=1
fi
# fx 判定锚工件:取证双检(fx_evidence)+ 错误扫描(scan_log)已覆盖真红——macOS 下
# quit-after 与 quit(0) 竞速的 124 退出非真红(run21 实证),不单独判闸。
scan_log "fx_shot_probe" "$TMP/fx.log"
fx_evidence() { # 取证双检:模式文件存在且非零字节(零字节/缺失任一即 FAIL——Q7)
local pat="$1" min="$2" n=0 f
for f in "$FX"/$pat; do
[ -f "$f" ] || continue
[ -s "$f" ] || { echo "[render_gate] FAIL — 取证零字节: $f"; _rc=1; return; }
n=$((n+1))
done
if [ "$n" -lt "$min" ]; then echo "[render_gate] FAIL — 取证文件缺失: $pat 仅 $n/<$min"; _rc=1; fi
}
fx_evidence "fx_dig*.png" 1
fx_evidence "fx_settle*.png" 1
fx_evidence "fx_round*.png" 6
fx_evidence "fx_final.png" 1
echo "======== board_shot_probe L1-L3 取证(Q7) ========"
BD="$TMP/boardshots"
gate_run_timeout 360 "$TMP/bd.log" "$G" --path "$GAME_WIN" --quit-after 3000 res://probe/board_shot_probe.tscn -- "$BD"
st=$?; kill_orphans
grep -E "\[board_shot\]" "$TMP/bd.log" | tail -6
if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then
echo "[render_gate] FAIL — board_shot_probe 超时挂死(exit=$st),孤儿已清"; _rc=1
fi
# board 判定锚工件:board_meta.json 非零 + 错误扫描覆盖真红;124 竞速退出非真红(同 fx)。
scan_log "board_shot_probe" "$TMP/bd.log"
for b in board_L1_dungeon.png board_L2_underdark.png board_L3_deep_underdark.png board_meta.json; do
if [ ! -s "$BD/$b" ]; then echo "[render_gate] FAIL — 取证缺失/零字节: $b"; _rc=1; fi
done
echo "======== 棋盘 golden 逐像素(T2——裁剪→对比,容差 provisional) ========"
if [ ! -d "$GOLDEN" ]; then
echo "[render_gate] FAIL — 本平台($(gate_platform_id))基线目录缺失:${GOLDEN}(基线按平台各存、在库=验收断言②)"; _rc=1
elif [ ! -s "$BD/board_meta.json" ]; then
echo "[render_gate] SKIP — board_meta.json 缺失(取证段已红),golden 对比不判"
else
# 素材缺位=判红:缺盘态下这一段一个像素都不比,而闸末照样打印
# 「全绿」退出码 0——那是空跑判绿,比误红危险得多。基线锚在素材齐盘态(Q5),缺盘态本就无权出验收结论。
_asset_miss=0
for a in assets/fonts/ui_font_fusion_pixel_zh_hans.ttf \
assets/art/board/seal_mark.png \
assets/art/character/character_hero_idle.png \
assets/art/board/terrain_wall_n_dungeon.png assets/art/board/terrain_floor_dungeon_1.png \
assets/art/board/terrain_wall_n_underdark.png assets/art/board/terrain_floor_underdark_1.png \
assets/art/board/terrain_wall_n_deep_underdark.png assets/art/board/terrain_floor_deep_underdark_1.png; do
[ -f "$GAME/$a" ] || { echo "[render_gate] FAIL — 棋盘素材缺位: ${a}(Q5:基线锚素材齐盘态,缺盘不得出验收结论)"; _asset_miss=1; }
done
if [ "$_asset_miss" -eq 0 ]; then
CROP="$TMP/crop"
python tools/golden_board_crop.py "$BD/board_meta.json" "$BD" "$CROP" | sed 's/^/[render_gate] /'
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
echo "[render_gate] FAIL — 棋盘区裁剪失败"; _rc=1
else
python tools/golden_compare.py "$REPO/game/tests/golden" "$CROP" \
board_L1_dungeon.png board_L2_underdark.png board_L3_deep_underdark.png | sed 's/^/[render_gate] /'
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
echo "[render_gate] FAIL — golden 逐像素超容差(S2 停机点:字体/平台误红呈报后定容差)"; _rc=1
fi
fi
fi
fi
echo "======== 菜单 golden 逐像素(迭代 28 T4——启动态全屏基线)========"
if [ ! -s "$GOLDEN/menu_boot.png" ]; then
echo "[render_gate] FAIL — $GOLDEN/menu_boot.png 基线缺失(菜单 golden 未建——本平台跑 tools/golden_baseline_refresh.sh --menu)"; _rc=1
elif [ ! -s "$SHOT/shot_menu.png" ]; then
echo "[render_gate] SKIP — shot_menu.png 缺失(shot_probe 段已红),菜单 golden 不判"
elif [ ! -f "$GAME/assets/fonts/ui_font_fusion_pixel_zh_hans.ttf" ] || [ ! -f "$GAME/assets/art/ui/button_stone.png" ]; then
echo "[render_gate] FAIL — 菜单素材缺位(字体/按钮石纹):基线锚素材齐盘态,缺盘不得出验收结论(2026-10-17 由 SKIP 改判红)"; _rc=1
else
MENUG="$TMP/menugolden"
mkdir -p "$MENUG"
cp "$SHOT/shot_menu.png" "$MENUG/menu_boot.png"
python tools/golden_compare.py "$REPO/game/tests/golden" "$MENUG" menu_boot.png | sed 's/^/[render_gate] /'
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
echo "[render_gate] FAIL — 菜单 golden 超容差(S2 停机点口径:核对热区——按钮换墨/布局变更属预期面=显式重制基线+呈报;超预期面=L3)"; _rc=1
fi
fi
echo "======== 残留检查(批末闸) ========"
left="$(orphans | wc -l | tr -d ' ')"
total="$(gate_godot_count_all)"
echo "本闸残留 Godot=${left}(必须0) 全局 Godot 进程=${total}(其他会话的仅报数不算账)"
if [ "$left" -ne 0 ]; then echo "[render_gate] FAIL — 本闸残留未清零"; kill_orphans; _rc=1; fi
if [ "$_rc" -eq 0 ]; then echo "[render_gate] 全绿"; else echo "[render_gate] 存在 FAIL,见上"; fi
exit "$_rc"