145 lines
8.8 KiB
Bash
145 lines
8.8 KiB
Bash
#!/usr/bin/env bash
|
||
# 渲染闸正主(迭代 19 卡① 自动化验收补齐批 2026-10-08 落码——T1/T2)——窗口化自动闸。
|
||
# 迭代 15③ 挂账「渲染五闸」机器补跑半随本闸落籍(shot_probe/golden_compare/board_shot 三件入闸;
|
||
# fx_probe 逻辑层归 probe_gate --full、ui_layout_probe 归其自身两档断言——归宿对账见卡①任务书 §一)。
|
||
# 骨架复用 probe_gate:①每段 timeout 守卫(挂死=FAIL 不静默)②段前预清+段后清本 game 目录孤儿
|
||
# Godot(按命令行精确匹配本 game 路径,不误杀其他 worktree 会话)③末尾残留清点=批末闸(必须 0)。
|
||
# 段:
|
||
# ① shot_probe 七屏窗口化截图(menu/settings/career/camp/level/battle/status——迭代 19 卡① T1 扩屏)
|
||
# → tools/shot_checker.py A-D 语义断言自动判定(A 屏级骨架全屏/B 文字对比度/C 明度三档带/
|
||
# D 文字禁压纹理;E 焦点预算=语义面仍归人闸不机检)。首跑存量 FAIL 按 S1 停机点全量呈报登记 06。
|
||
# ② 取证段(Q7:超时/零字节/文件缺失任一即 FAIL——「渲染真实发生」必须成立,不做像素判定):
|
||
# fx_shot_probe 三时刻抓帧(行到达/结算排播/轮末连帧)+ board_shot_probe L1-L3 棋盘抓帧。
|
||
# ③ 棋盘 golden 逐像素(T2):board 整屏帧按 board_meta.json 取样坐标裁剪棋盘区
|
||
# (tools/golden_board_crop.py——禁硬编码像素)→ tools/golden_compare.py 对
|
||
# game/tests/golden/ 三张基线(容差 provisional:差 ≤16/changed ≤2%,S2 停机点口径)。
|
||
# 素材缺位=SKIP 不判红(Q5 运行时口径;基线生成期素材缺位=L3 停机——基线禁锚回退态)。
|
||
# ④ 基准档 1280 单档(Q4)。1920 档注记:16:9 全屏下 stretch=canvas_items/aspect=keep 等比
|
||
# 1.35×+左右黑边各 96px,构图与基准档一致(迭代 9 分辨率案甲)——分辨率维度不重复建闸,
|
||
# ui_layout_probe 两档断言(T5)承担 1920 布局机检面。
|
||
# 过程产物纪律(Q2):一切截图 PNG 落 mktemp 临时区,闸毕即弃——任何过程 PNG 禁入 git;
|
||
# 例外=game/tests/golden/ 基线(测试资产入库,.gitignore 已核不挡)。
|
||
# 窗口化运行会短暂弹 Godot 窗口(本机可开窗口径,2026-10-07 用户令);禁改 --headless 跑本闸
|
||
# (headless 无渲染面,探针内 fail-fast 护栏会红——那是护栏生效不是闸红)。
|
||
# 用法: tools/render_gate.sh [game目录] 缺省 game 目录=脚本所在仓库的 game/(worktree 内自动指向该 worktree)
|
||
# 依赖: Git Bash(coreutils timeout/taskkill) + PowerShell + python(PIL);Godot 路径可用环境变量 GODOT_BIN 覆盖。
|
||
set -u
|
||
G="${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}"
|
||
REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
||
GAME="${1:-$REPO/game}"
|
||
GAME_WIN="$(cygpath -m "$GAME")" # Godot --path 需 Windows 形式
|
||
TMP="$(mktemp -d /tmp/rendergate_XXXXXX)"
|
||
trap 'rm -rf "$TMP"' EXIT
|
||
_rc=0
|
||
|
||
orphans() { # 列出命令行含本 game 路径的 Godot 进程 PID(console wrapper+真引擎都算)
|
||
printf "Get-CimInstance Win32_Process -Filter \"Name like 'Godot%%'\" | Where-Object { \$_.CommandLine -like '*%s*' } | ForEach-Object { \$_.ProcessId }\n" "$GAME_WIN" > "$TMP/k.ps1"
|
||
powershell -NoProfile -ExecutionPolicy Bypass -File "$(cygpath -w "$TMP/k.ps1")" 2>/dev/null | tr -d '\r'
|
||
}
|
||
kill_orphans() {
|
||
local p; for p in $(orphans); do taskkill //F //PID "$p" >/dev/null 2>&1; done
|
||
}
|
||
|
||
# 冷检出刷导入(.godot 不入 git,fresh worktree 首跑一次性付)
|
||
if [ ! -f "$GAME/.godot/uid_cache.bin" ]; then
|
||
echo "[render_gate] 冷检出:editor --quit 刷导入(一次性,可到 1-2 分钟)"
|
||
timeout 300 "$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"
|
||
timeout 180 "$G" --path "$GAME_WIN" res://smoke/shot_probe.tscn -- "$SHOT" >"$TMP/shot.log" 2>&1
|
||
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 [ "$st" -ne 0 ] || ! 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 全量呈报登记 06)"
|
||
fi
|
||
|
||
echo "======== fx_shot_probe 三时刻取证(Q7) ========"
|
||
FX="$TMP/fxshots"
|
||
timeout 180 "$G" --path "$GAME_WIN" res://smoke/fx_shot_probe.tscn -- "$FX" >"$TMP/fx.log" 2>&1
|
||
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
|
||
elif [ "$st" -ne 0 ]; then
|
||
echo "[render_gate] FAIL — fx_shot_probe 退出码=$st(取证失败=闸红)"; _rc=1
|
||
fi
|
||
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"
|
||
timeout 180 "$G" --path "$GAME_WIN" res://smoke/board_shot_probe.tscn -- "$BD" >"$TMP/bd.log" 2>&1
|
||
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
|
||
elif [ "$st" -ne 0 ]; then
|
||
echo "[render_gate] FAIL — board_shot_probe 退出码=$st(取证失败=闸红)"; _rc=1
|
||
fi
|
||
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 "$REPO/game/tests/golden" ]; then
|
||
echo "[render_gate] FAIL — game/tests/golden/ 基线目录缺失(基线在库=验收断言②)"; _rc=1
|
||
elif [ ! -s "$BD/board_meta.json" ]; then
|
||
echo "[render_gate] SKIP — board_meta.json 缺失(取证段已红),golden 对比不判"
|
||
else
|
||
# Q5 运行时素材缺位=SKIP:基线锚在素材齐盘态,缺盘态对比必误红——SKIP 不判红,注记在案
|
||
_asset_miss=0
|
||
for a in assets/fonts/ui_font_fusion_pixel_zh_hans.ttf \
|
||
assets/art/board/flag_seal.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] SKIP — 棋盘素材缺位: $a(Q5——基线禁锚回退态,运行时 SKIP)"; _asset_miss=1; break; }
|
||
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 "======== 残留检查(批末闸) ========"
|
||
left="$(orphans | wc -l | tr -d ' ')"
|
||
total="$(tasklist 2>/dev/null | grep -ci '^Godot' || true)"
|
||
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"
|