summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-21 21:54:01 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-21 21:54:01 +0800
commit87a00a036219f0f1b9c3eaccd7fcd9d782fe6011 (patch)
treef39ea3a04739705e84bce6558b1230da55495f98 /scripts
parentb5ffb35fc0824a297ff88297390310acb6c45335 (diff)
downloadliving-village-87a00a036219f0f1b9c3eaccd7fcd9d782fe6011.tar.gz
test(desktop): 预乘/光晕/水面/站位回归与夜间白块校验脚本
- premultiply 定点:核心 (255,226,168,34)->(34,30,22,34),(252,196,128,34)->(34,26,17,34), 通道恒 <= alpha、<250 无白 - lanternGlowLayers 径向/预乘/无白、水面 per-tile 三相、角色脚底对齐断言 - scripts/check-no-white-blocks.py:夜晚帧不得有 >=64x64、每通道>=250、alpha>=250 的块 (P13 旧帧 42/69 命中,P14 修复后 0 命中)
Diffstat (limited to 'scripts')
-rw-r--r--scripts/check-no-white-blocks.py93
1 files changed, 93 insertions, 0 deletions
diff --git a/scripts/check-no-white-blocks.py b/scripts/check-no-white-blocks.py
new file mode 100644
index 0000000..609c832
--- /dev/null
+++ b/scripts/check-no-white-blocks.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+"""Fail if any recorded frame contains a near-white opaque block.
+
+This is the image-level regression guard for the P13/P14 lantern blow-out: a
+premultiplied-alpha mistake painted ~75x75 pure white (255,255,255) squares
+where the night lantern glow should fall off softly.
+
+A "white block" is any window of at least MIN_SIDE x MIN_SIDE pixels where every
+pixel is fully opaque (alpha >= 250) and every colour channel is >= 250.
+
+Usage:
+ python3 scripts/check-no-white-blocks.py <png-or-dir> [more...]
+
+Exit code 0 when clean, 1 when a block is found, 2 on usage/read error.
+"""
+
+import os
+import sys
+
+import numpy as np
+from PIL import Image
+
+MIN_SIDE = 64
+CHANNEL_MIN = 250
+ALPHA_MIN = 250
+
+
+def first_white_block(path, min_side=MIN_SIDE):
+ image = Image.open(path)
+ rgba = np.asarray(image.convert("RGBA"), dtype=np.uint8)
+ if rgba.shape[0] < min_side or rgba.shape[1] < min_side:
+ return None
+ white_opaque = (
+ (rgba[:, :, 0] >= CHANNEL_MIN)
+ & (rgba[:, :, 1] >= CHANNEL_MIN)
+ & (rgba[:, :, 2] >= CHANNEL_MIN)
+ & (rgba[:, :, 3] >= ALPHA_MIN)
+ ).astype(np.int64)
+ # Summed-area table so any k x k window is one lookup.
+ integral = np.zeros((white_opaque.shape[0] + 1, white_opaque.shape[1] + 1), dtype=np.int64)
+ integral[1:, 1:] = white_opaque.cumsum(0).cumsum(1)
+ rows, cols = white_opaque.shape
+ for y in range(0, rows - min_side + 1):
+ for x in range(0, cols - min_side + 1):
+ total = (
+ integral[y + min_side, x + min_side]
+ - integral[y, x + min_side]
+ - integral[y + min_side, x]
+ + integral[y, x]
+ )
+ if total == min_side * min_side:
+ return (x, y, min_side, min_side)
+ return None
+
+
+def iter_pngs(targets):
+ for target in targets:
+ if os.path.isdir(target):
+ for root, _, files in os.walk(target):
+ for name in sorted(files):
+ if name.lower().endswith(".png"):
+ yield os.path.join(root, name)
+ else:
+ yield target
+
+
+def main(argv):
+ if len(argv) < 2:
+ sys.stderr.write(__doc__)
+ return 2
+ checked = 0
+ failures = []
+ for path in iter_pngs(argv[1:]):
+ try:
+ block = first_white_block(path)
+ except Exception as error: # noqa: BLE001 - report and continue
+ sys.stderr.write(f"unreadable: {path}: {error}\n")
+ return 2
+ checked += 1
+ if block is not None:
+ x, y, side, _ = block
+ failures.append(f"{path}: white block {side}x{side} at ({x},{y})")
+ for line in failures:
+ print(f"FAIL {line}")
+ if failures:
+ print(f"checked={checked} failures={len(failures)}")
+ return 1
+ print(f"checked={checked} failures=0 OK")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main(sys.argv))