#!/usr/bin/env python3 """P75(HUD 顶部条:去整宽深色带,改紧凑半透明药丸)证据分析:真实昼/夜同机位帧。 判据(读真实实跑日志 + 顶部条像素统计): 1. 日志含 `p43-shot=p43-hud-day` 与 `p43-shot=p43-hud-night`(同机位,12:00 / 23:30)。 2. 顶部条区域(y 90%。 3. 顶部条内无 >=24x24 近黑块。 4. 文字可读:顶部条内亮像素(RGB 均 >=200)数量 > 300(标签/数值/图标仍在)。 5. 昼/夜两帧 pixel diff > 0。 6. 观感是否精美由 Hermes vision 侧复核,非本脚本判据。 Usage: python3 scripts/analyze-p75.py Exit 0 pass / 1 fail / 2 usage. """ import re import sys from pathlib import Path import numpy as np from PIL import Image SHOT_RE = re.compile(r"^p43-shot=(p43-hud-(?:day|night))\S* tick=(\d+) calendar=(\S+)", re.MULTILINE) BAR_HEIGHT = 36 DARK_MAX = 40 DARK_FRAC_MAX = 0.10 BLOCK_SIDE = 24 BRIGHT_MIN = 200 BRIGHT_MIN_COUNT = 300 FRAMES = ["p43-hud-day", "p43-hud-night"] def max_block(mask, side): h, w = mask.shape if h < side or w < side: return 0 integral = np.zeros((h + 1, w + 1), dtype=np.int64) integral[1:, 1:] = mask.astype(np.int64).cumsum(axis=0).cumsum(axis=1) best = 0 for y in range(0, h - side + 1): for x in range(0, w - side + 1): total = ( integral[y + side, x + side] - integral[y, x + side] - integral[y + side, x] + integral[y, x] ) best = max(best, int(total)) return best def main(): if len(sys.argv) != 3: print(__doc__) return 2 rec = Path(sys.argv[1]) out = Path(sys.argv[2]) log = (rec / "log.txt").read_text(encoding="utf-8", errors="replace") failures = [] lines = [ "P75(HUD 顶部条去整宽深色带 → 紧凑半透明药丸)证据分析 — 真实 Desktop xvfb 1280x720", "钩子:LV_P43_SHOT=1(同机位昼 12:00 / 夜 23:30)", "判据:顶部条 y<36 近黑占比 vs 整宽深色带基线(100%)下降 >90%,文字亮像素仍在", "", ] shots = {m.group(1): m.group(0) for m in SHOT_RE.finditer(log)} for name in FRAMES: if name not in shots: failures.append(f"log missing p43-shot={name}") else: lines.append("log: " + shots[name]) lines.append("") baseline_px = 1280 * BAR_HEIGHT stats = {} for name in FRAMES: path = rec / f"{name}.png" if not path.exists(): failures.append(f"missing frame {path.name}") continue rgb = np.asarray(Image.open(path).convert("RGB"), dtype=np.int16) h, w = rgb.shape[0], rgb.shape[1] strip = rgb[0:BAR_HEIGHT, :, :] dark_mask = (strip[:, :, 0] <= DARK_MAX) & (strip[:, :, 1] <= DARK_MAX) & (strip[:, :, 2] <= DARK_MAX) dark_px = int(dark_mask.sum()) strip_px = strip.shape[0] * strip.shape[1] dark_frac = dark_px / strip_px drop = 1.0 - dark_frac block = max_block(dark_mask, BLOCK_SIDE) bright = int(((strip[:, :, 0] >= BRIGHT_MIN) & (strip[:, :, 1] >= BRIGHT_MIN) & (strip[:, :, 2] >= BRIGHT_MIN)).sum()) stats[name] = {"dark_frac": dark_frac, "bright": bright} lines.append( f"{name}.png: size={w}x{h} strip_dark_px={dark_px}/{strip_px} dark_frac={dark_frac:.3f} drop_vs_full_band={drop*100:.1f}% dark_block_{BLOCK_SIDE}={block} bright_px={bright}" ) if (w, h) != (1280, 720): failures.append(f"{path.name} size {w}x{h} != 1280x720") if dark_frac >= DARK_FRAC_MAX: failures.append(f"{name} strip dark_frac {dark_frac:.3f} >= {DARK_FRAC_MAX}") if drop <= 0.90: failures.append(f"{name} dark-area drop {drop*100:.1f}% <= 90%") if block >= BLOCK_SIDE * BLOCK_SIDE: failures.append(f"{name} has a solid {BLOCK_SIDE}x{BLOCK_SIDE} near-black block in strip") if bright < BRIGHT_MIN_COUNT: failures.append(f"{name} strip bright_px {bright} < {BRIGHT_MIN_COUNT} (text/icons may be unreadable)") lines.append("") have = [n for n in FRAMES if n in stats] if len(have) == 2: a = np.asarray(Image.open(rec / f"{have[0]}.png").convert("RGB"), dtype=np.int16) b = np.asarray(Image.open(rec / f"{have[1]}.png").convert("RGB"), dtype=np.int16) differing = int((np.abs(a - b).sum(axis=2) > 0).sum()) lines.append(f"diff {have[0]} vs {have[1]}: differing_pixels={differing}") if differing == 0: failures.append("day and night frames are identical") lines.append("") if failures: lines.append("verdict=FAIL") for f in failures: lines.append(" - " + f) else: lines.append("verdict=PASS") lines.append(f" baseline full-width dark band = {baseline_px} px (frac 1.000).") lines.append(" both strips are mostly scene/pill, no near-black block, and text/icons remain bright.") out.parent.mkdir(parents=True, exist_ok=True) out.write_text("\n".join(lines) + "\n", encoding="utf-8") print(f"wrote {out} verdict={'FAIL' if failures else 'PASS'}") return 1 if failures else 0 if __name__ == "__main__": sys.exit(main())