1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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))
|