Files
gplan-2/tools/render_gate.sh
T
DKChan 126e583fed 工程(基线合一批·golden单基线跨平台+补锚14张+审核关卡立规): 分平台基线机制拆除(2026-11-08用户裁:跨平台渲染必须一致,禁加基线消化差异)——windows 6件基线提升到golden/根,删windows/mac两平台目录与根目录遗留(level_map.png+边车,零引用核实); 闸件去平台化(render_gate.sh GOLDEN直拼/golden_compare.py删_PLAT与plat_missing/golden_baseline_refresh.sh落点改根+--screens换代入口); 拆自愈采集(ci.yml步+tools/ci/golden_selfheal.sh——CI自动落基线=既当裁判又当选手,测量数据已采完退役); 补锚14张(用户裁:发现少了就补,备案清零)——shot_probe扩九态(营地五窗真点热点开窗/图例真点钮/战末结算与三选一直调喂真实形态文案/调试浮窗直调)+battle段呼吸相位冻结(motion_scale0.5+1.2s动画完结锚定,board_shot同款)+热点键→基线语义名映射(dummy→tree/board→codex); 渲染闸屏态浮层golden段(golden_compare 14面)+shot_checker 16屏清单; 基线审核关卡立规(test-conventions §8.2:逐张审核确认后才入库生效+审核记录落档+非卡片批基线操作同关卡;process-conventions五·实现行:未审核的基线禁合回;req-card/iter技能特殊标注——豁免面直改); 实测依据: mac真渲染vs windows基线四指标(棋盘0.025~0.033%/menu 0.674% changed,RGB零色偏,块平均≈0)全在容差带内——数据归档reports/基线合一批-反哺点-2026-11-08.md; 读数: shot_probe直跑16图问题=0+渲染闸直跑exit=0(20面全PASS,新候选changed=0.00%)+bash -n×3+py_compile+文档域闸绿; 基线审核记录: 20张全部DKChan 2026-11-08确认(README审核记录节)
2026-11-08 23:19:47 +08:00

235 lines
14 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 逻辑层归场景探针闸;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" # 单基线跨平台(2026-11-08 用户裁:跨平台渲染必须一致,差异由容差带吸收)
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 ! python3 "$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
# shot 判定锚工件:问题=0 签名在位=完成(124 为 macOS 退出段挂死回收/quit-after 竞速,非真红——run21/26 实证)
if ! grep -q "问题=0" "$TMP/shot.log"; then
echo "[render_gate] FAIL — shot_probe 未干净完成(exit=${st},缺失屏/空文件/路径断裂见上)"; _rc=1
fi
if [ "$_rc" -eq 0 ]; then
python3 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 shot_camp_forge.png shot_camp_chest.png shot_camp_tree.png shot_camp_codex.png shot_camp_notebook.png shot_battle_end.png shot_curio_pick.png shot_legend.png shot_debug.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
# 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
# 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 — 基线目录缺失:${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"
python3 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
python3 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"
python3 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 "======== 屏态/浮层 golden 逐像素(golden 补锚批 2026-11-08——七屏锚+营地五窗+四浮层)========"
if [ ! -s "$SHOT/shot_camp.png" ]; then
echo "[render_gate] SKIP — shot_camp.png 缺失(shot_probe 段已红),屏态/浮层 golden 不判"
else
SG="$TMP/sgolden"
mkdir -p "$SG"
cp "$SHOT/shot_camp.png" "$SG/camp_screen.png"
cp "$SHOT/shot_settings.png" "$SG/settings_screen.png"
cp "$SHOT/shot_battle.png" "$SG/battle_screen.png"
cp "$SHOT/shot_career.png" "$SG/career_screen.png"
cp "$SHOT/shot_status.png" "$SG/status_panel.png"
cp "$SHOT/shot_camp_forge.png" "$SG/camp_forge.png"
cp "$SHOT/shot_camp_chest.png" "$SG/camp_chest.png"
cp "$SHOT/shot_camp_tree.png" "$SG/camp_tree.png"
cp "$SHOT/shot_camp_codex.png" "$SG/camp_codex.png"
cp "$SHOT/shot_camp_notebook.png" "$SG/camp_notebook.png"
cp "$SHOT/shot_battle_end.png" "$SG/battle_end.png"
cp "$SHOT/shot_curio_pick.png" "$SG/curio_pick.png"
cp "$SHOT/shot_legend.png" "$SG/legend.png"
cp "$SHOT/shot_debug.png" "$SG/debug_overlay.png"
python3 tools/golden_compare.py "$REPO/game/tests/golden" "$SG" \
camp_screen.png settings_screen.png battle_screen.png career_screen.png status_panel.png \
camp_forge.png camp_chest.png camp_tree.png camp_codex.png camp_notebook.png \
battle_end.png curio_pick.png legend.png debug_overlay.png | sed 's/^/[render_gate] /'
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
echo "[render_gate] FAIL — 屏态/浮层 golden 超容差(S2 停机点口径:核对热区——预期面=显式重制基线+呈报审核;超预期面=L3)"; _rc=1
fi
fi
echo "======== 关卡视口 golden 逐像素(卷轴视口批——两 Shot 面:入口停点+Boss 列锚定)========"
LM="$TMP/lmapshots"
gate_run_timeout 360 "$TMP/lm.log" "$G" --path "$GAME_WIN" --quit-after 3000 res://probe/level_map_shot_probe.tscn -- "$LM"
st=$?; kill_orphans
grep -E "\[level_map_shot_probe\]" "$TMP/lm.log" | tail -3
# 判定锚工件(同 fx/board——quit-after 竞速 124 非真红):PASS 签名 + 双图非零。
if ! grep -q "PASS — 两 Shot 落盘" "$TMP/lm.log"; then
echo "[render_gate] FAIL — level_map_shot_probe 未干净完成(exit=${st})"; _rc=1
fi
for b in lmap_entry.png lmap_boss.png; do
if [ ! -s "$LM/$b" ]; then echo "[render_gate] FAIL — 取证缺失/零字节: $b"; _rc=1; fi
done
scan_log "level_map_shot_probe" "$TMP/lm.log"
if [ "$_rc" -eq 0 ]; then
for b in lmap_entry.png lmap_boss.png; do
if [ ! -s "$GOLDEN/$b" ]; then
# 两 Shot 面(2026-11-05 用户裁)缺基线=未冻结态记 SKIP 不判红(先锚后盯),已锚照常逐像素严判
echo "[render_gate] SKIP — $GOLDEN/$b 基线缺失(未冻结——跑 tools/golden_baseline_refresh.sh --lmap)"
else
LMG="$TMP/lmgolden"
mkdir -p "$LMG"
cp "$LM/$b" "$LMG/$b"
python3 tools/golden_compare.py "$REPO/game/tests/golden" "$LMG" "$b" | sed 's/^/[render_gate] /'
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
echo "[render_gate] FAIL — 关卡视口 golden 超容差(S2 停机点口径:视口呈现换代=显式重制基线+呈报;超预期面=L3)"; _rc=1
fi
fi
done
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"