Files
gplan-2/tools/palette_check.py
T
DKChan 9c36c7a237 工程(工程清理批·用户令): 六件直改合批——①palette_check基线建档+双门②机检化: golden/palette_baseline两PNG(token92色∪素材快照1274色=并集1346色)+gen_palette_baseline.py建档刷新件+闸脚本05b色板闸(code域,quick/影响面SKIP)+头注基线纪律, 门①语义检查与48计数上限不承载(口径归art-bible owner,呈报); ②extract_checks补check_quiet静默通道: CALL_RE/DEF_RE扩双通道+collect_call按匹配长偏移+quiet列记channel:check_quiet, 自证三重(双计等式2155==2155/新旧对拍1719→2133=+414入账/往返--diff PASS零差异), test-conventions§5.2局限备案句随批换代; ③样张S1b四色墨×PANEL_DATA垫牌≥4.5:1色值对表断言(读数4.97–5.62与审计复算逐位一致, 样张31/31全绿; V10基线对拍图artwork环境件--save-baseline重建); ④main.gd死preload清理10条(登记8条复核多出2条, 7具名消费面已迁main_ui_build拆件自持, 逐条grep复核零代码使用), presentation 17套件全绿+冒烟15项0失败兜底; ⑤注释换代: ui_kit TEX_HERO_PORTRAIT「48px档」→原稿分辨率+显示档读PORTRAIT_DISPLAY_H, path_check头注删日期批注记; ⑥CAMP_HEADER_BAND退役评估: 4处消费面核过(关卡屏2/商店1/KeystoneBadge工厂1), 退役删除不成立, 处置=改名中性化归ui_kit令牌批顺带(零值变化); ⑦run_all_gates 02传参核查=d32ff342已完成漏删零改动; 待办清单7条目整条删除(状态机出口), pipeline_gate PASS(33条目/49总表行); 产出物=reports/工程改动报告-工程清理批-2026-11-02.md
2026-11-02 06:29:27 +08:00

166 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""B2 色板检查器(圣经 §8.3①挂点实现)。
⚠⚠ 口径纪律(**勿改回**)——两处极易写错:
1. **48 色上限不是「每张 PNG 的色数」**。§4.1 的「全作色板硬上限 48 色」指
**设计色板 token**(UI 语义色/墨色/材质色那套),不是像素画的独立色数。
像素画单张另有**32 色冻结**口径,由 standee_asset_check 管(像素域)。
本器口径:单张色数**只报读数不判红**;48 只在 --tokens 模式下校验色板表。
2. **近色 ≥8/255 是「新色入板双门」,不是存量两两互查**。§4.1 原文:
「**新色**入板须同时过两道门……②与既有任一色的 RGB 三通道差 ≥8/255」。
它管的是**新增色 vs 既有色板**。对存量色两两互查会把 AI 生成的明暗渐变
全部打成近色(实测 drider 22 组 / 巨蜘蛛 82 组)⇒ 噪声淹没真问题。
本器口径:近色**只对新增色集**判(新增色 vs 基线色板)。
另:必须排除全透明像素 (0,0,0,0)(同standee_asset_check 的坑)。
基线(双门②「新增色 vs 既有色板」的既有色板机检承载物):
目录 = game/tests/golden/palette_baseline/,两张条带 PNG(1×N,一色一像素列):
ui_tokens.png = ui_kit.gd 全量 Color8 token 快照(token 板现役值)
art_snapshot.png = 现役素材全量唯一色快照(像素板+各屏环境子板)
两张并集=「既有色板」。缺一张即漏判——基线刷新必须重生成全套。
门①语义检查属设计面人工核对(§4.2 色相语义判域),不在本器执法面。
用法:
# 主用法(闸机 05b 色板闸承载):扫目录,新增色相对 --baseline 判定
python tools/palette_check.py <目录> --baseline <基线目录>
# 无基线:只出读数(单张色数/ 近色对计数),不判红
python tools/palette_check.py <目录>
# 基线建档与刷新:重扫现役素材∪token 重生成基线条带(素材批收尾动作,随批提交)
python tools/gen_palette_baseline.py
退出码: 0 通过 / 1 有红灯(仅新增色近色不过门②时)
"""
import os
import sys
from PIL import Image
# §4.1 硬上限(**设计色板 token 口径**,非像素画单张色数)
TOKEN_MAX = 48
NEAR_TOL = 8 # §4.1 新色双门:与既有任一色三通道差 ≥8/255
# 像素画单张色数(像素域32 色冻结)——由 standee_asset_check 执法,
# 本器只作读数提示,避免两处口径打架
PIXEL_COLOR_SOFT_MAX = 32
def palette_of(path):
"""返回 (不透明像素唯一色集合, 像素数)。排除全透明。"""
im = Image.open(path).convert("RGBA")
px = im.load()
w, h = im.size
cols = set()
n = 0
for y in range(h):
for x in range(w):
c = px[x, y]
if c[3] == 0:
continue
cols.add(c[:3])
n += 1
return cols, n
def nearest_to(col, pool, tol):
"""返回 col 在 pool 中最近的色及其差值(差 < tol 算近色)。"""
best, bd = None, 999
for c in pool:
d = max(abs(col[k] - c[k]) for k in range(3))
if d < bd:
best, bd = c, d
return best, bd
def hexs(c):
return "#%02X%02X%02X" % c
def scan(d, baseline_cols, report_only):
files = []
for dirpath, _, names in os.walk(d):
for fn in sorted(names):
if fn.lower().endswith(".png"):
files.append(os.path.join(dirpath, fn))
files.sort()
print("=== %s(%d 张 PNG)===" % (d, len(files)))
n_fail = 0
all_new = set()
for p in files:
cols, npx = palette_of(p)
rel = os.path.relpath(p, d)
notes = []
msgs = []
over = len(cols) > PIXEL_COLOR_SOFT_MAX
notes.append("色%d%s" % (len(cols), "(>32 像素域冻结)" if over else ""))
new = set()
if baseline_cols is not None:
new = cols - baseline_cols
if new:
# §4.1 新色双门②:新增色须与既有任一色差 ≥8/255
bad = []
for c in sorted(new):
near, d3 = nearest_to(c, baseline_cols, 999)
if d3 < NEAR_TOL:
bad.append((c, near, d3))
all_new |= new
notes.append("新增%d" % len(new))
for c, near, d3 in bad[:5]:
msgs.append("新增色 %s 与既有 %s 三通道差 %d < %d"
"(§4.1 新色双门②,须走换色合并)"
% (hexs(c), hexs(near), d3, NEAR_TOL))
if len(bad) > 5:
msgs.append("…… 另有 %d 个新增色同样不过门" % (len(bad) - 5))
else:
notes.append("新增0")
line = "FAIL " if msgs else ("WARN " if (report_only and over) else "PASS ")
print("%s %-46s %s" % (line, rel, " ".join(notes)))
for m in msgs:
print(" └─ %s" % m)
n_fail += 1 if msgs else 0
print("小计:%d 张,%d 张有红灯\n" % (len(files), n_fail))
return n_fail, len(files), all_new
def main():
args = sys.argv[1:]
baseline = args[args.index("--baseline") + 1] if "--baseline" in args else None
dirs = [a for a in args if not a.startswith("--")
and os.path.isdir(a) and a != baseline]
if not dirs:
print(__doc__)
sys.exit(2)
baseline_cols = None
if baseline:
baseline_cols = set()
for dirpath, _, names in os.walk(baseline):
for fn in names:
if fn.lower().endswith(".png"):
cols, _ = palette_of(os.path.join(dirpath, fn))
baseline_cols |= cols
print("基线色板:%s,唯一色 %d" % (baseline, len(baseline_cols)))
total_fail = total_files = 0
total_new = set()
report_only = baseline_cols is None
for d in dirs:
f, n, new = scan(d, baseline_cols, report_only)
total_fail += f
total_files += n
total_new |= new
print("色板机检完成:%d 张 PNG,%d 张有红灯,累计新增色 %d"
% (total_files, total_fail, len(total_new)))
if report_only:
print("提示:未给 --baseline ⇒ 本次为只读模式(不判红)。"
"『色数>32』标 WARN 属像素域提示,最终执法在 standee_asset_check。")
sys.exit(1 if total_fail else 0)
if __name__ == "__main__":
main()