summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/analyze-p74.py132
1 files changed, 132 insertions, 0 deletions
diff --git a/scripts/analyze-p74.py b/scripts/analyze-p74.py
new file mode 100644
index 0000000..4569791
--- /dev/null
+++ b/scripts/analyze-p74.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python3
+"""P74(启动开场过渡:水墨式渐显)证据分析:真实 xvfb 录启动 0-3 秒序列抽帧。
+
+判据(读真实实跑日志 + 逐帧像素统计):
+ 1. 日志含 5 行 `p74-shot=p74-splash-<frame> inkStrength=<v>`,帧号递增 {8,30,60,100,140}。
+ 2. inkStrength 随帧非增,且在 dissolveFrames(96) 后为 0(墨幕确实在消散)。
+ 3. 抽帧 PNG:1280x720、非空白(std>=6)、无 24x24 纯黑块(标题/屋脊的纯白属预期,仅报告不判失败)。
+ 4. 动画在变:任意两帧 pixel diff > 0(且首末帧 diff 显著)。
+ 5. 画面「是否好看/水墨味」的 vision 复核由 Hermes 侧完成,非本脚本判据。
+
+Usage: python3 scripts/analyze-p74.py <record-dir> <out-txt>
+Exit 0 pass / 1 fail / 2 usage.
+"""
+
+import itertools
+import re
+import sys
+from pathlib import Path
+
+import numpy as np
+from PIL import Image
+
+SHOT_RE = re.compile(r"^p74-shot=p74-splash-(\d+) inkStrength=([\d.]+)$", re.MULTILINE)
+EXPECTED = [8, 30, 60, 100, 140]
+DARK_SIDE = 24
+DARK_MAX = 8
+WHITE_SIDE = 64
+WHITE_MIN = 250
+
+
+def max_block(rgba, side, predicate):
+ h, w = rgba.shape[0], rgba.shape[1]
+ if h < side or w < side:
+ return 0
+ mask = predicate(rgba).astype(np.int64)
+ integral = np.zeros((h + 1, w + 1), dtype=np.int64)
+ integral[1:, 1:] = mask.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 = [
+ "P74(启动开场过渡:水墨式渐显)证据分析 — 真实 Desktop xvfb 1280x720",
+ "钩子:LV_P74_SHOT=1(60Hz 固定步进,splash 帧表 8/30/60/100/140 各一帧)",
+ "路径:splashFrame → InkReveal.inkStrength/dabs(预乘墨滴)→ 露出天色/水面/屋脊/标题",
+ "",
+ ]
+
+ shots = [(int(m.group(1)), float(m.group(2))) for m in SHOT_RE.finditer(log)]
+ got = [f for f, _ in shots]
+ lines.append("log shots: " + ", ".join(f"{f}(ink={v:.3f})" for f, v in shots))
+ if got != EXPECTED:
+ failures.append(f"log frames {got} != expected {EXPECTED}")
+ strengths = [v for _, v in shots]
+ if strengths != sorted(strengths, reverse=True):
+ failures.append("inkStrength is not non-increasing across frames")
+ if not all(v == 0.0 for f, v in shots if f >= 96):
+ failures.append("inkStrength is not 0 after dissolveFrames (96)")
+ lines.append("")
+
+ stats = {}
+ for frame in EXPECTED:
+ path = rec / f"p74-splash-{frame:03d}.png"
+ if not path.exists():
+ failures.append(f"missing frame {path.name}")
+ continue
+ rgba = np.asarray(Image.open(path).convert("RGBA"), dtype=np.uint8)
+ rgb = rgba[:, :, :3].astype(np.float64)
+ h, w = rgb.shape[0], rgb.shape[1]
+ brightness = float(rgb.mean())
+ std = float(rgb.std())
+ dark = max_block(
+ rgba, DARK_SIDE,
+ lambda a: (a[:, :, 0] <= DARK_MAX) & (a[:, :, 1] <= DARK_MAX) & (a[:, :, 2] <= DARK_MAX) & (a[:, :, 3] >= 250),
+ )
+ white = max_block(
+ rgba, WHITE_SIDE,
+ lambda a: (a[:, :, 0] >= WHITE_MIN) & (a[:, :, 1] >= WHITE_MIN) & (a[:, :, 2] >= WHITE_MIN) & (a[:, :, 3] >= 250),
+ )
+ stats[frame] = {"brightness": brightness, "std": std}
+ lines.append(f"p74-splash-{frame:03d}.png: size={w}x{h} brightness={brightness:.1f} std={std:.1f} dark_block={dark} white_block={white}")
+ if (w, h) != (1280, 720):
+ failures.append(f"{path.name} size {w}x{h} != 1280x720")
+ if std < 6.0:
+ failures.append(f"{path.name} looks blank (std={std:.1f})")
+ if dark > 0:
+ failures.append(f"{path.name} has {DARK_SIDE}x{DARK_SIDE} pure-black block")
+ lines.append("")
+
+ have = [f for f in EXPECTED if f in stats]
+ for a, b in itertools.combinations(have, 2):
+ ia = np.asarray(Image.open(rec / f"p74-splash-{a:03d}.png").convert("RGB"), dtype=np.int16)
+ ib = np.asarray(Image.open(rec / f"p74-splash-{b:03d}.png").convert("RGB"), dtype=np.int16)
+ differing = int((np.abs(ia - ib).sum(axis=2) > 0).sum())
+ lines.append(f"diff frame {a:03d} vs {b:03d}: differing_pixels={differing}")
+ if differing == 0:
+ failures.append(f"frame {a} identical to {b}")
+
+ lines.append("")
+ if failures:
+ lines.append("verdict=FAIL")
+ for f in failures:
+ lines.append(" - " + f)
+ else:
+ lines.append("verdict=PASS")
+ lines.append(" ink strength decays to 0 across the captured frames; frames render 1280x720,")
+ lines.append(" no big black blocks, and every pair differs (the opening transition animates).")
+ 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())