summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-29 13:46:49 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-29 13:46:49 +0800
commit0439e21a0582ab892c0e4bfa936c4d62ca2bcc61 (patch)
tree42cbf64e93c84f9644b2d001b12d4288b42f6a38 /scripts
parente959b1df06f29fe6542cc6cbe2f9005f620d5dc9 (diff)
downloadliving-village-0439e21a0582ab892c0e4bfa936c4d62ca2bcc61.tar.gz
p76: 底部条/槽位药丸化 + 对话/交互面板半透明描边与折行
DrawHotbar 去掉整条大色块与实心槽位,改半透明条+1px 描边+发丝线,槽位内键位小片与图形并排,选中用 accent 描边;新增 HudLayout.actionLegend(取真实 controlRows 的键位+动作名图例,纯展示不改绑定)。DrawDialogOverlay/DrawPanel 背景改半透明描边语言,禁用近纯黑底。新增 HudLayout.wrapText/wrapLines 按实测宽度折行,DrawPanel 弃用 Substring 截断。新增 P76HotbarPanelTests(4 例);LV_P43_SHOT 昼/夜 + LV_P44_SHOT 对话面板证据 + analyze-p76(四区无实心近黑块、亮像素充足,verdict=PASS)。Desktop 358/358、Kernel 120/120、digest 与 p70 一致。
Diffstat (limited to 'scripts')
-rw-r--r--scripts/analyze-p76.py152
1 files changed, 152 insertions, 0 deletions
diff --git a/scripts/analyze-p76.py b/scripts/analyze-p76.py
new file mode 100644
index 0000000..d67d4a0
--- /dev/null
+++ b/scripts/analyze-p76.py
@@ -0,0 +1,152 @@
+#!/usr/bin/env python3
+"""P76(底部条/对话面板精修)证据分析:真实 xvfb 昼/夜 HUD + 对话面板/任务面板打开态。
+
+判据(读真实实跑日志 + 区域像素统计;近黑阈值 24 对齐旧纯黑面板底色):
+ 1. 日志含 p43-shot day/night、p44-shot=p44-dialog-a、p67-shot=p67-target。
+ 2. 底部条区域(hotbar rect)与图例带:近黑占比 < 20%、无实心 24x24 近黑块、亮像素(键位/图标/文字)> 400。
+ 3. 对话面板 / 任务面板区域:近黑占比 < 20%、无实心 24x24 近黑块、亮文字像素 > 阈值。
+ 4. 昼/夜两帧、对话第 1/2 页两帧 pairwise diff > 0。
+ 5. 观感由 Hermes vision 侧复核,非本脚本判据。
+
+Usage: python3 scripts/analyze-p76.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
+
+
+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())
+ block = 0
+ if mask.shape[0] >= BLOCK and mask.shape[1] >= BLOCK:
+ win = sliding_window_view(mask.astype(np.int64), (BLOCK, BLOCK))
+ block = int(win.sum(axis=(2, 3)).max())
+ bright = int(((r[:, :, 0] >= 200) & (r[:, :, 1] >= 200) & (r[:, :, 2] >= 200)).sum())
+ return dark_frac, block, bright, (w, h)
+
+
+def main():
+ if len(sys.argv) != 3:
+ print(__doc__)
+ return 2
+ rec = Path(sys.argv[1])
+ out = Path(sys.argv[2])
+ failures = []
+ lines = [
+ "P76(底部条/槽位药丸化 + 对话/交互面板半透明描边)证据分析 — 真实 Desktop xvfb 1280x720",
+ "近黑阈值 24(旧纯黑面板底色 (14,22,30) 口径);实心块 = 24x24 全近黑。",
+ "",
+ ]
+
+ # --- 底部条:昼/夜 ---
+ hud = {m.group(1): (int(m.group(7)), int(m.group(8)), int(m.group(9)), int(m.group(10))) for m in P43_RE.finditer((rec / "log-P43.txt").read_text(encoding="utf-8", errors="replace") if (rec / "log-P43.txt").exists() else "")}
+ for name in ["p43-hud-day", "p43-hud-night"]:
+ path = rec / f"{name}.png"
+ if name not in hud or not path.exists():
+ failures.append(f"missing {name}")
+ continue
+ bar = hud[name]
+ df, block, bright, size = region_stats(path, bar)
+ # 图例带:条上方 30px
+ legend = (bar[0] - 5, bar[1] - 34, bar[2] + 10, 30)
+ ldf, lblock, lbright, _ = region_stats(path, legend)
+ lines.append(
+ f"{name}: hotbar={bar} size={size} dark_frac={df:.3f} maxblk={block} bright={bright} | legend dark_frac={ldf:.3f} maxblk={lblock} bright={lbright}"
+ )
+ if size != (1280, 720) and size[0] > 0:
+ pass
+ if df >= DARK_FRAC_MAX:
+ failures.append(f"{name} hotbar dark_frac {df:.3f} >= {DARK_FRAC_MAX}")
+ if block >= BLOCK * BLOCK:
+ failures.append(f"{name} hotbar has solid {BLOCK}x{BLOCK} near-black block")
+ if bright < 400:
+ failures.append(f"{name} hotbar bright {bright} < 400 (键位/图标/文字可能丢失)")
+ if ldf >= DARK_FRAC_MAX:
+ failures.append(f"{name} legend dark_frac {ldf:.3f} >= {DARK_FRAC_MAX}")
+ if lbright < 200:
+ failures.append(f"{name} legend bright {lbright} < 200 (键位+动作名可能不可读)")
+ lines.append("")
+
+ # --- 对话面板 ---
+ p44_log = (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_log)}
+ dpath = rec / "p44-dialog-a.png"
+ if "p44-dialog-a" in dialogs and dpath.exists():
+ rect = dialogs["p44-dialog-a"]
+ df, block, bright, size = region_stats(dpath, rect)
+ lines.append(f"p44-dialog-a: panel={rect} dark_frac={df:.3f} maxblk={block} bright={bright}")
+ if df >= DARK_FRAC_MAX:
+ failures.append(f"dialog panel dark_frac {df:.3f} >= {DARK_FRAC_MAX}")
+ if block >= BLOCK * BLOCK:
+ failures.append(f"dialog panel has solid {BLOCK}x{BLOCK} near-black block")
+ if bright < 300:
+ failures.append(f"dialog panel bright {bright} < 300")
+ else:
+ failures.append("missing p44-dialog-a panel")
+ lines.append("")
+
+ # --- 任务面板(交互面板换行渲染路径)---
+ tpath = rec / "p67-target.png"
+ if tpath.exists():
+ df, block, bright, size = region_stats(tpath, (16, 470, 380, 220))
+ lines.append(f"p67-target task-panel region=(16,470,380,220) 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(f"task panel has solid {BLOCK}x{BLOCK} near-black block")
+ else:
+ failures.append("missing p67-target.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-b")]:
+ 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(" hotbar/legend/dialog/task regions have no solid near-black block;")
+ lines.append(" key chips, icons and action-name legend remain bright; 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())