#!/usr/bin/env python3 r"""shot_checker.py — 整屏截图 B 档断言骨架(迭代 9 卡⑰a 2026-09-30)。 用法: python tools/shot_checker.py <截图目录> [shot_menu.png shot_camp.png shot_level.png shot_battle.png] 断言(v1 骨架——像素级可判层): ① 四屏 PNG 存在且尺寸 = 1280×800(工程逻辑分辨率档); ② 每屏灰度标准差 ≥ 8(非空白/非纯色——渲染真实发生的最低证据); ③ 每屏唯一色数 ≥ 64(内容丰富度下限——占位纯色屏会落在个位数)。 T8 整屏样张闸四断言(全局文字 ≥4.5:1/明度三档带/焦点预算/文字禁压纹理)在本骨架上扩。 退出码: 0 = 全过 / 1 = 有失败。 """ import sys from pathlib import Path from PIL import Image EXPECTED = (1280, 800) def main() -> int: if len(sys.argv) < 2: print("用法: python tools/shot_checker.py <截图目录> [屏清单...]") return 1 d = Path(sys.argv[1]) shots = sys.argv[2:] or ["shot_menu.png", "shot_camp.png", "shot_level.png", "shot_battle.png"] failures = [] for name in shots: p = d / name if not p.exists(): failures.append("%s: 文件缺失" % name) continue img = Image.open(p).convert("RGB") if img.size != EXPECTED: failures.append("%s: 尺寸 %s != %s" % (name, img.size, EXPECTED)) continue gray = img.convert("L") hist = gray.histogram() total = sum(hist) mean = sum(i * c for i, c in enumerate(hist)) / total var = sum(c * (i - mean) ** 2 for i, c in enumerate(hist)) / total std = var ** 0.5 colors = len({p2 for p2 in img.getdata()}) if std < 8.0: failures.append("%s: 灰度 std %.1f < 8(疑似空白/纯色)" % (name, std)) if colors < 64: failures.append("%s: 唯一色数 %d < 64(疑似占位屏)" % (name, colors)) print("[shot_checker] %s — %s std=%.1f colors=%d %s" % ( name, "x".join(map(str, img.size)), std, colors, "OK" if std >= 8 and colors >= 64 else "FAIL")) if failures: print("[shot_checker] FAIL——") for f in failures: print(" - %s" % f) return 1 print("[shot_checker] PASS(%d 屏骨架断言全过)" % len(shots)) return 0 if __name__ == "__main__": sys.exit(main())