summaryrefslogtreecommitdiff
path: root/scripts/analyze-p77.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/analyze-p77.py')
-rw-r--r--scripts/analyze-p77.py174
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())