diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-28 19:03:22 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-28 19:03:22 +0800 |
| commit | 588560deda6b701a62653dff19079dc0a21fa944 (patch) | |
| tree | 3d734aaf9ad546ec52a42af0a2c2635d2d26d670 /scripts | |
| parent | 5c67e85688ff1e6150b03501adae1a5d5e67bbdc (diff) | |
| download | living-village-588560deda6b701a62653dff19079dc0a21fa944.tar.gz | |
p43: HUD 星露谷化切片 — 右上日历钟卡/体力条形图/底部10格hotbar(1-0选中+高亮+输入隔离)
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/analyze-p43-hud.py | 130 | ||||
| -rw-r--r-- | scripts/make-p43-hud-closeup.py | 49 |
2 files changed, 179 insertions, 0 deletions
diff --git a/scripts/analyze-p43-hud.py b/scripts/analyze-p43-hud.py new file mode 100644 index 0000000..ad0a859 --- /dev/null +++ b/scripts/analyze-p43-hud.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +"""P43 HUD analysis: prove the new calendar card + energy bar + hotbar are real. + +Reads the `LV_P43_SHOT=1` frames and the shot log (which prints the layout rects +computed by HudLayout in the running game), then writes +docs/evidence/p43-analysis.txt with the measured numbers: + + 1. calendar card / hotbar / slot0 rectangles exactly as the game laid them out, + all inside the 1280x720 viewport and non-overlapping; + 2. per-slot hotbar brightness (10 distinct cells) and the selected-slot delta; + 3. calendar-card and hotbar opacity is semi-transparent (never an opaque block). + +Usage: + python3 scripts/analyze-p43-hud.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 +SHOT_RE = re.compile( + r"p43-shot=(\S+) tick=(\d+) calendar=([\d,]+) hotbar=([\d,]+) slot0=([\d,]+) " + r"selected=(\d+) energy=([\d.]+) day=(\d+) season=(\S+) period=(\S+) clock=(\S+)" +) + + +def rect(text): + x, y, w, h = (int(v) for v in text.split(",")) + return (x, y, w, h) + + +def inside(name, r): + x, y, w, h = r + return x >= 0 and y >= 0 and x + w <= WIDTH and y + h <= HEIGHT + + +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") + shots = [] + for line in log_text.splitlines(): + m = SHOT_RE.search(line) + if m: + shots.append( + { + "name": m.group(1), + "tick": int(m.group(2)), + "calendar": rect(m.group(3)), + "hotbar": rect(m.group(4)), + "slot0": rect(m.group(5)), + "selected": int(m.group(6)), + "energy": float(m.group(7)), + "day": int(m.group(8)), + "season": m.group(9), + "period": m.group(10), + "clock": m.group(11), + } + ) + if not shots: + sys.stderr.write("no p43-shot lines in log\n") + return 1 + + lines = ["P43 HUD analysis (LV_P43_SHOT=1)", ""] + failures = [] + for shot in shots: + img = np.asarray(Image.open(record_dir / f"{shot['name']}.png").convert("RGBA"), dtype=np.float64) + lines.append(f"[{shot['name']}] tick={shot['tick']} day={shot['day']} season={shot['season']} " + f"period={shot['period']} clock={shot['clock']} energy={shot['energy']:.0f} selected={shot['selected']}") + cx, cy, cw, ch = shot["calendar"] + bx, by, bw, bh = shot["hotbar"] + sx, sy, sw, sh = shot["slot0"] + lines.append(f" calendar_rect = ({cx},{cy},{cw}x{ch}) hotbar_rect = ({bx},{by},{bw}x{bh}) slot0_rect = ({sx},{sy},{sw}x{sh})") + for name in ("calendar", "hotbar", "slot0"): + if not inside(name, shot[name]): + failures.append(f"{shot['name']} {name} out of viewport {shot[name]}") + # calendar must not overlap the top strip (y >= 39) and must sit left of hotbar band. + if cy < 39: + failures.append(f"{shot['name']} calendar overlaps top bar") + + # 1) per-slot brightness across the 10 slots (distinct cells, selected delta). + slot_means = [] + for index in range(10): + x = bx + 8 + index * (sw + 6) + y = by + 8 + cell = img[y:y + sh, x:x + sw, :3].mean() + slot_means.append(cell) + sel = shot["selected"] + others = [v for i, v in enumerate(slot_means) if i != sel] + lines.append(" hotbar_slot_mean_luma = " + ", ".join(f"{v:.1f}" for v in slot_means)) + lines.append(f" selected_slot={sel} selected_luma={slot_means[sel]:.1f} others_mean={np.mean(others):.1f} " + f"delta={slot_means[sel] - np.mean(others):+.1f}") + + # 2) opacity: the calendar card and hotbar backdrops must be semi-transparent. + for name, r in (("calendar", shot["calendar"]), ("hotbar", shot["hotbar"])): + x, y, w, h = r + alpha = img[y:y + h, x:x + w, 3] + region = img[y:y + h, x:x + w, :3] + opaque_white = int(((alpha >= 250) & (region.min(axis=2) >= 250)).sum()) + lines.append(f" {name}: alpha_min={int(alpha.min())} alpha_max={int(alpha.max())} " + f"mean_luma={region.mean():.1f} opaque_white_px={opaque_white}") + if opaque_white > 0: + failures.append(f"{shot['name']} {name} contains opaque white pixels") + if region.mean() < 8.0: + failures.append(f"{shot['name']} {name} too dark (possible big black block)") + 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)) diff --git a/scripts/make-p43-hud-closeup.py b/scripts/make-p43-hud-closeup.py new file mode 100644 index 0000000..adcabca --- /dev/null +++ b/scripts/make-p43-hud-closeup.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""P43 evidence: NEAREST 2x close-up sheet of the two new HUD cards. + +Crops the top-right calendar/clock card and the bottom 10-slot hotbar from the +`LV_P43_SHOT=1` day frame and stacks them at exactly 2x with NEAREST so pixel +edges stay sharp. The sheet is a pure function of the input PNG (fixed crop +boxes, fixed gap), no synthetic pixels. + +Usage: + python3 scripts/make-p43-hud-closeup.py <record-dir> <out-dir> +""" + +import sys +from pathlib import Path + +from PIL import Image + +SOURCE = "p43-hud-day.png" +OUT_NAME = "p43-hud-closeup-2x.png" +# (left, top, right, bottom) in the 1280x720 frame; rects from the p43-shot log. +CALENDAR_BOX = (1124, 44, 1272, 102) +HOTBAR_BOX = (398, 655, 882, 719) +GAP = 12 + + +def main(argv): + if len(argv) != 3: + sys.stderr.write(__doc__) + return 2 + record_dir = Path(argv[1]) + out_dir = Path(argv[2]) + out_dir.mkdir(parents=True, exist_ok=True) + source = Image.open(record_dir / SOURCE).convert("RGBA") + crops = [source.crop(CALENDAR_BOX), source.crop(HOTBAR_BOX)] + doubled = [c.resize((c.width * 2, c.height * 2), Image.NEAREST) for c in crops] + width = max(img.width for img in doubled) + height = sum(img.height for img in doubled) + GAP * (len(doubled) - 1) + sheet = Image.new("RGBA", (width, height), (0, 0, 0, 0)) + y = 0 + for img in doubled: + sheet.paste(img, (0, y)) + y += img.height + GAP + sheet.save(out_dir / OUT_NAME) + print(f"ok {OUT_NAME} {sheet.width}x{sheet.height} from {SOURCE} boxes={[CALENDAR_BOX, HOTBAR_BOX]}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv)) |
