diff options
Diffstat (limited to 'scripts/analyze-p77.py')
| -rw-r--r-- | scripts/analyze-p77.py | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/scripts/analyze-p77.py b/scripts/analyze-p77.py new file mode 100644 index 0000000..5778b43 --- /dev/null +++ b/scripts/analyze-p77.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +"""P77(日历卡/浮动提示药丸化 + 面板与 hotbar 不遮挡)证据分析。 + +判据(读真实实跑日志 + 区域像素统计;近黑阈值 24 对齐旧纯黑底色): + 1. 日志含 p43-shot day/night(日历 rect + hotbar rect)、p44-shot 对话 rect、p67 任务面板。 + 2. 日历卡、交互面板、提示带:近黑占比 < 20%、无实心 24x24 近黑块、亮文字像素充足。 + 3. 几何不遮挡:面板/对话弹层底边 <= hotbar 顶(p67 面板用同一 panelRect 公式复核)。 + 4. 昼/夜、对话两态帧 pairwise diff > 0。 + 5. 观感由 Hermes vision 侧复核,非本脚本判据。 + +Usage: python3 scripts/analyze-p77.py <record-dir> <out-txt> +Exit 0 pass / 1 fail / 2 usage. +""" + +import re +import sys +from pathlib import Path + +import numpy as np +from PIL import Image +from numpy.lib.stride_tricks import sliding_window_view + +P43_RE = re.compile( + r"^p43-shot=(p43-hud-(?:day|night))\S* tick=(\d+) calendar=(\d+),(\d+),(\d+),(\d+) hotbar=(\d+),(\d+),(\d+),(\d+)", + re.MULTILINE, +) +P44_RE = re.compile(r"^p44-shot=(p44-dialog-[a-z]) panel=dialogue rect=(\d+),(\d+),(\d+),(\d+)", re.MULTILINE) +THR = 24 +BLOCK = 24 +DARK_FRAC_MAX = 0.20 +BAR_HEIGHT = 36 +PANEL_LINE_HEIGHT = 18 +PANEL_MAX_WIDTH = 380 +PANEL_TOP_EXTRA = 26 + + +def region_stats(path, rect): + x, y, w, h = rect + rgb = np.asarray(Image.open(path).convert("RGB"), dtype=np.int16) + r = rgb[y : y + h, x : x + w] + mask = (r[:, :, 0] <= THR) & (r[:, :, 1] <= THR) & (r[:, :, 2] <= THR) + dark_frac = float(mask.mean()) if mask.size else 1.0 + block = 0 + if mask.shape[0] >= BLOCK and mask.shape[1] >= BLOCK: + block = int(sliding_window_view(mask.astype(np.int64), (BLOCK, BLOCK)).sum(axis=(2, 3)).max()) + bright = int(((r[:, :, 0] >= 200) & (r[:, :, 1] >= 200) & (r[:, :, 2] >= 200)).sum()) + return dark_frac, block, bright + + +def panel_rect(viewport_w, viewport_h, line_count): + width = min(PANEL_MAX_WIDTH, max(1, viewport_w - 32)) + height = max(1, line_count) * PANEL_LINE_HEIGHT + PANEL_TOP_EXTRA + # hotbarBarRect (mirrors HudLayout): 10*40 + 9*6 wide, bottom-anchored. + hotbar_w = 10 * 40 + 9 * 6 + hotbar_x = (viewport_w - hotbar_w) // 2 - 8 + hotbar_y = viewport_h - 40 - 12 - 8 + max_bottom = hotbar_y - 8 + y = max(BAR_HEIGHT + 12, min(viewport_h - height - 16, max(0, max_bottom - height))) + return (16, y, width, height), (hotbar_x, hotbar_y, hotbar_w, 40 + 16) + + +def main(): + if len(sys.argv) != 3: + print(__doc__) + return 2 + rec = Path(sys.argv[1]) + out = Path(sys.argv[2]) + failures = [] + lines = [ + "P77(日历卡/浮动提示药丸化 + 面板与 hotbar 错开)证据分析 — 真实 Desktop xvfb 1280x720", + "近黑阈值 24;实心块 = 24x24 全近黑。", + "", + ] + + p43 = (rec / "log-P43.txt").read_text(encoding="utf-8", errors="replace") if (rec / "log-P43.txt").exists() else "" + frames = { + m.group(1): { + "tick": int(m.group(2)), + "calendar": (int(m.group(3)), int(m.group(4)), int(m.group(5)), int(m.group(6))), + "hotbar": (int(m.group(7)), int(m.group(8)), int(m.group(9)), int(m.group(10))), + } + for m in P43_RE.finditer(p43) + } + for name in ["p43-hud-day", "p43-hud-night"]: + path = rec / f"{name}.png" + if name not in frames or not path.exists(): + failures.append(f"missing {name}") + continue + cal = frames[name]["calendar"] + df, block, bright = region_stats(path, cal) + lines.append(f"{name}: calendar={cal} dark_frac={df:.3f} maxblk={block} bright={bright}") + if df >= DARK_FRAC_MAX: + failures.append(f"{name} calendar dark_frac {df:.3f} >= {DARK_FRAC_MAX}") + if block >= BLOCK * BLOCK: + failures.append(f"{name} calendar has solid near-black block") + if bright < 100: + failures.append(f"{name} calendar bright {bright} < 100 (文字不可读?)") + lines.append("") + + hotbar = frames.get("p43-hud-day", {}).get("hotbar") + # 几何不遮挡:面板与对话弹层底边须 <= hotbar 顶。 + prect, hbrect = panel_rect(1280, 720, 5) + panel_bottom = prect[1] + prect[3] + hb_top = hbrect[1] + lines.append(f"panelRect(1280,720,5)={prect} bottom={panel_bottom} vs hotbar_top={hb_top}") + if panel_bottom > hb_top: + failures.append(f"panel bottom {panel_bottom} overlaps hotbar top {hb_top}") + p67 = rec / "p67-target.png" + if p67.exists(): + df, block, bright = region_stats(p67, prect) + lines.append(f"p67-target panel region={prect} dark_frac={df:.3f} maxblk={block} bright={bright}") + if df >= DARK_FRAC_MAX: + failures.append(f"task panel dark_frac {df:.3f} >= {DARK_FRAC_MAX}") + if block >= BLOCK * BLOCK: + failures.append("task panel has solid near-black block") + if bright < 100: + failures.append(f"task panel bright {bright} < 100") + else: + failures.append("missing p67-target.png") + lines.append("") + + p44 = (rec / "log-P44.txt").read_text(encoding="utf-8", errors="replace") if (rec / "log-P44.txt").exists() else "" + dialogs = {m.group(1): (int(m.group(2)), int(m.group(3)), int(m.group(4)), int(m.group(5))) for m in P44_RE.finditer(p44)} + if "p44-dialog-a" in dialogs: + drect = dialogs["p44-dialog-a"] + d_bottom = drect[1] + drect[3] + lines.append(f"p44-dialog-a rect={drect} bottom={d_bottom} vs hotbar_top={hb_top}") + if hb_top is not None and d_bottom > hb_top: + failures.append(f"dialog bottom {d_bottom} overlaps hotbar top {hb_top}") + else: + failures.append("missing p44-dialog-a rect") + prompt_path = rec / "p44-dialog-c.png" + if prompt_path.exists(): + df, block, bright = region_stats(prompt_path, (0, 480, 1280, 240)) + lines.append(f"p44-dialog-c prompt band (0,480,1280,240) dark_frac={df:.3f} maxblk={block} bright={bright}") + if df >= DARK_FRAC_MAX: + failures.append(f"prompt band dark_frac {df:.3f} >= {DARK_FRAC_MAX}") + if block >= BLOCK * BLOCK: + failures.append("prompt band has solid near-black block") + if bright < 500: + failures.append(f"prompt band bright {bright} < 500") + else: + failures.append("missing p44-dialog-c.png") + lines.append("") + + def diff(a, b): + ia = np.asarray(Image.open(rec / f"{a}.png").convert("RGB"), dtype=np.int16) + ib = np.asarray(Image.open(rec / f"{b}.png").convert("RGB"), dtype=np.int16) + return int((np.abs(ia - ib).sum(axis=2) > 0).sum()) + + for a, b in [("p43-hud-day", "p43-hud-night"), ("p44-dialog-a", "p44-dialog-c")]: + if (rec / f"{a}.png").exists() and (rec / f"{b}.png").exists(): + d = diff(a, b) + lines.append(f"diff {a} vs {b}: differing_pixels={d}") + if d == 0: + failures.append(f"{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(" calendar/panel/prompt regions have no solid near-black block;") + lines.append(" panel and dialog bottoms clear the hotbar top; frames differ pairwise.") + 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()) |
