diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-28 19:29:27 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-28 19:29:27 +0800 |
| commit | e28b3c102f2a0b387a4df84f053306d71e5e41ba (patch) | |
| tree | 0b3b13b10bba4283dde3aba60cc0ec42495d6b82 /scripts | |
| parent | d671335d687496f393dbad63950707cabbf24e5b (diff) | |
| download | living-village-e28b3c102f2a0b387a4df84f053306d71e5e41ba.tar.gz | |
p44: 对话框/文字弹层 — 完整背景框+中文断行换行+分页翻页+Esc关闭+输入隔离(原创32x64点阵头像)
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/analyze-p44-dialog.py | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/scripts/analyze-p44-dialog.py b/scripts/analyze-p44-dialog.py new file mode 100644 index 0000000..c9405e0 --- /dev/null +++ b/scripts/analyze-p44-dialog.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +"""P44 dialog-overlay analysis. + +Reads the `LV_P44_SHOT=1` frames + shot log and writes +docs/evidence/p44-analysis.txt with measured numbers: + + 1. overlay rect / page / wrapped line count / avatar flag for the open frames; + 2. the accent top border really exists inside the overlay rect (frame a/b); + 3. the overlay rect is gone after Esc (frame c): accent border pixels ~0 and the + region differs from frame a, and the world prompt is restored. + +Usage: + python3 scripts/analyze-p44-dialog.py <record-dir> <shot-log> <out-txt> + +Exit 0 when checks pass, 1 when they fail, 2 on usage/read error. +""" + +import re +import sys +from pathlib import Path + +import numpy as np +from PIL import Image + +WIDTH = 1280 +HEIGHT = 720 +ACCENT_DAY = np.array([212, 168, 96]) +OPEN_RE = re.compile(r"p44-shot=(\S+) panel=(\S+) rect=(\d+),(\d+),(\d+),(\d+) page=(\d+)/(\d+) lines=(\d+) avatar=(\w+)") +GONE_RE = re.compile(r"p44-shot=(\S+) panel=(\S+) overlay=gone prompt=(.+)") + + +def load(record_dir, name): + return np.asarray(Image.open(record_dir / f"{name}.png").convert("RGB"), dtype=int) + + +def accent_border_px(img, rect): + x, y, w, _ = rect + strip = img[y:y + 3, x:x + w] + return int((np.abs(strip - ACCENT_DAY).max(axis=2) <= 16).sum()) + + +def text_bands(img, rect, show_avatar): + x, y, w, h = rect + text_x = x + 16 + (32 + 12 if show_avatar else 0) + # body rows only (below the header), inside the frame. + body = img[y + 30:y + h - 20, text_x:x + w - 16] + bright = (body.max(axis=2) >= 180).any(axis=1) + bands = 0 + previous = False + for row in bright: + if row and not previous: + bands += 1 + previous = row + return bands + + +def main(argv): + if len(argv) != 4: + sys.stderr.write(__doc__) + return 2 + record_dir = Path(argv[1]) + log_text = Path(argv[2]).read_text(encoding="utf-8") + opens = [] + gone = [] + for line in log_text.splitlines(): + m = OPEN_RE.search(line) + if m: + opens.append( + { + "name": m.group(1), + "panel": m.group(2), + "rect": tuple(int(m.group(i)) for i in range(3, 7)), + "page": f"{m.group(7)}/{m.group(8)}", + "lines": int(m.group(9)), + "avatar": m.group(10) == "true", + } + ) + g = GONE_RE.search(line) + if g: + gone.append({"name": g.group(1), "panel": g.group(2), "prompt": g.group(3)}) + if not opens or not gone: + sys.stderr.write("need open frames and one overlay=gone frame\n") + return 1 + + failures = [] + lines = ["P44 dialog-overlay analysis (LV_P44_SHOT=1)", ""] + reference_rect = None + reference_img = None + for shot in opens: + img = load(record_dir, shot["name"]) + reference_rect = shot["rect"] if reference_rect is None else reference_rect + reference_img = img if reference_img is None else reference_img + x, y, w, h = shot["rect"] + if not (x >= 0 and y >= 0 and x + w <= WIDTH and y + h <= HEIGHT): + failures.append(f"{shot['name']} rect out of viewport") + border = accent_border_px(img, shot["rect"]) + bands = text_bands(img, shot["rect"], shot["avatar"]) + lines.append(f"[{shot['name']}] panel={shot['panel']} rect=({x},{y},{w}x{h}) page={shot['page']} " + f"log_lines={shot['lines']} avatar={shot['avatar']}") + lines.append(f" rendered_text_bands={bands} accent_border_px={border}") + if border < 500: + failures.append(f"{shot['name']} overlay accent border missing ({border})") + if bands < 1: + failures.append(f"{shot['name']} no wrapped text rendered") + if shot["lines"] > 4: + failures.append(f"{shot['name']} page has more than linesPerPage lines") + + for shot in gone: + img = load(record_dir, shot["name"]) + border = accent_border_px(img, reference_rect) + x, y, w, h = reference_rect + delta = float(np.abs(img[y:y + h, x:x + w].astype(float) - reference_img[y:y + h, x:x + w].astype(float)).mean()) + lines.append(f"[{shot['name']}] panel={shot['panel']} prompt={shot['prompt']!r} " + f"(rect from {opens[0]['name']})") + lines.append(f" accent_border_px={border} region_mean_abs_diff_vs_open={delta:.1f}") + if border > 50: + failures.append(f"{shot['name']} overlay rect still present after close (border={border})") + if delta < 5.0: + failures.append(f"{shot['name']} region unchanged vs open, overlay may persist") + if "E " not in shot["prompt"]: + failures.append(f"{shot['name']} world prompt not restored") + + lines.append("") + lines.append("verdict = " + ("PASS" if not failures else "FAIL")) + out_txt = Path(argv[3]) + out_txt.parent.mkdir(parents=True, exist_ok=True) + out_txt.write_text("\n".join(lines) + "\n", encoding="utf-8") + sys.stdout.write("\n".join(lines) + "\n") + if failures: + for f in failures: + sys.stderr.write(f"FAIL {f}\n") + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv)) |
