Files
gplan-2/tools/render_gate.sh
T
DKChan 67342c690e feat(迭代28 T4): 渲染面内容断言三件(探针自检+墨色判据+启动态菜单 golden)
①shot_probe.gd 两条自检进 _problems:boot 态顶层可见层=start_overlay(内容层树遍历——屏壳容器不当候选,camp 壳透明实证);菜单标题/副标题 label is_visible_in_tree 且 rect 落屏内;隔离档 refresh_resume 开=菜单截图不依赖真实档断点态。②shot_checker.py 增 F 字在判据:菜单文字框与中位灰度差>60 像素占比带 [2%,55%](极性无关——亮字暗底同判;实测标题 13.07%/副标题 11.47% 定 provisional 阈值;对比度只判反差不判字在的缺口由此补)。③启动态菜单 golden:帧差前置验 0.0000%/max_diff=0(光柱静态贴片零动画);golden_baseline_refresh.sh 扩 --menu 模式(shot_probe 取材→game/tests/golden/menu_boot.png,字体+按钮石纹素材守卫);render_gate.sh 补菜单 golden 对比段(素材缺位 SKIP 同 Q5 口径)。闸=render_gate 全绿(探针问题=0+checker A-D/F 全过+棋盘 golden 0.00%×3+菜单 golden 0.00%)。注:本机 render_gate 需 gamedev venv Python 前置 PATH(闸机脚本调 python(PIL),本机默认 python 无 PIL——用户侧环境自带)
2026-10-15 07:28:38 +08:00

162 lines
9.9 KiB
Bash
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
# 渲染闸正主(迭代 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 "======== 菜单 golden 逐像素(迭代 28 T4——启动态全屏基线)========"
if [ ! -s "$REPO/game/tests/golden/menu_boot.png" ]; then
echo "[render_gate] FAIL — game/tests/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] SKIP — 菜单素材缺位(字体/按钮石纹——Q5 口径:基线禁锚回退态,运行时 SKIP)"
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="$(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"