merge: M-1 迭代 2「样张冻结与表现定稿」loop——样张 v2 机检 16/16 四冻结+框材组 8 件接线+演出密度终稿+棋盘数字终稿+M-1 出口闸检查单
五批次 a08d89c/f9b0765→2d1388b:①T1 样张 v2(机检闸形 A 全过即冻结——新增 A3 Δ≥55/F6 双明度带/F7 DEPTH_PX 同源/A6 观感密度四断言,art-bible v2.13 落四冻结+UiKit 小人蓝同步 #2E86DE+图例旗色封印黑漏网修)②T2 框材组 8 件(♻ Kenney/OGA 全 CC0 量化改色入包,UiKit 单点工厂+五视图接线,✦ 背景横幅列缺料)③T3 演出四项(死亡碎裂/胜利纹章/失败掸灰/意图三档+结算光带缘线)+fx_probe 新探针④T4 数字形制冻结值落码(三档墨色温/字号 ≥18 公式/色牌 0.60·0.62)⑤T5 M-1 出口闸检查单(design/qa/m1-exit-gate-checklist-2026-09-28.md——真人目检八项+✦ 缺料清单)+06 两已解决条目移除+roadmap 挂起清单对齐。 验证:样张机检 16/16+冒烟 557/0(基线持平)+五探针(ui_input/ui_probe/ui_click/ui_tune/fx 新增)全 PASS+doc 三闸对基线零新增(lint 8=8/term 215=215/死链 0)+熵增闸 PASS。
This commit is contained in:
commit
9abf5ff448
20 files changed
+748
-76
No files matched your search
@@ -0,0 +1,116 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""UI 框材组量化改色脚本(M-1 迭代 2 T2,2026-09-28)。
|
||||
|
||||
♻ 复用件 → 复用闸①色板量化:全部产物按明度重映射到 art-bible §4.1/§7.2 token 渐变带,
|
||||
不保留原包杂色;闸②禁入带=token 色天然合规;闸③灰度可辨=图标四形互异;闸④授权=全 CC0。
|
||||
源包 = artwork/ui-reuse-2026-09-23/(素材不入 git——先查本仓库,缺位回退主检出 D:/code/g-plan2):
|
||||
oga-kenney-pixel-ui-750(CC0,OGA) 9-Slice/Ancient 面板/按钮九宫格 48×48
|
||||
kenney-fantasy-ui-borders(CC0) panel-border 石框 48×48
|
||||
5-石纹底噪/stone_6.png(CC0,OGA Handpainted stone) 无缝底噪 1200×1200
|
||||
kenney-board-game-icons(CC0) 64px 图标四件
|
||||
产物 → game/assets/art/ui/(终稿素材目录,不入 git;消费 = UiKit.load_tex 守卫,缺位程序化回退)。
|
||||
用法:python tools/quantize_ui_skin.py
|
||||
"""
|
||||
from PIL import Image
|
||||
import os
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
MAIN_ROOT = "D:/code/g-plan2"
|
||||
SRC = ("artwork", "ui-reuse-2026-09-23")
|
||||
OUT = ("game", "assets", "art", "ui")
|
||||
|
||||
|
||||
def src_path(*parts):
|
||||
for base in (ROOT, MAIN_ROOT):
|
||||
p = os.path.join(base, *SRC, *parts)
|
||||
if os.path.exists(p):
|
||||
return p
|
||||
raise FileNotFoundError("/".join(SRC + parts))
|
||||
|
||||
|
||||
def out_path(name):
|
||||
p = os.path.join(ROOT, *OUT, name)
|
||||
os.makedirs(os.path.dirname(p), exist_ok=True)
|
||||
return p
|
||||
|
||||
|
||||
# §4.1/§7.2 token 渐变带(量化目标值——样张 v2 冻结色板)
|
||||
RAMP_PANEL = [(120, 106, 88), (226, 210, 182), (243, 234, 214)] # PANEL_BORDER→PANEL_DATA→PLATE 微亮
|
||||
RAMP_BUTTON = [(179, 154, 120), (201, 175, 131), (245, 234, 211)] # FLOOR→BRICK(掩尘"可按的砖")→PLATE
|
||||
RAMP_PRESSED = [(120, 106, 88), (179, 154, 120)] # PANEL_BORDER→FLOOR
|
||||
RAMP_BACKDROP = [(30, 25, 20), (120, 106, 88)] # BG_ROOT→PANEL_BORDER(低对比自然后退 §6.1)
|
||||
RAMP_BORDER = [(120, 106, 88), (201, 175, 131)] # PANEL_BORDER→BRICK
|
||||
ICON_GOLD = (240, 168, 40) # 奖励金(钥石)
|
||||
ICON_WARM = (240, 226, 200) # 暖白(柜台件,暗卡上可读)
|
||||
|
||||
|
||||
def ramp_color(ramp, t):
|
||||
if t <= 0:
|
||||
return ramp[0]
|
||||
if t >= 1:
|
||||
return ramp[-1]
|
||||
seg = t * (len(ramp) - 1)
|
||||
i = min(int(seg), len(ramp) - 2)
|
||||
k = seg - i
|
||||
a, b = ramp[i], ramp[i + 1]
|
||||
return tuple(int(a[j] + (b[j] - a[j]) * k) for j in range(3))
|
||||
|
||||
|
||||
def remap_luma(img, ramp, gamma=1.0):
|
||||
"""明度→token 渐变带重映射(保留 alpha 与纹理结构——闸①量化)。"""
|
||||
im = img.convert("RGBA")
|
||||
px = im.load()
|
||||
vals = [sum(px[x, y][:3]) / 3.0 for y in range(im.size[1]) for x in range(im.size[0])
|
||||
if px[x, y][3] > 8]
|
||||
lo, hi = min(vals), max(vals)
|
||||
span = max(1.0, hi - lo)
|
||||
for y in range(im.size[1]):
|
||||
for x in range(im.size[0]):
|
||||
r, g, b, a = px[x, y]
|
||||
if a <= 8:
|
||||
continue
|
||||
t = ((sum((r, g, b)) / 3.0) - lo) / span
|
||||
px[x, y] = ramp_color(ramp, t ** gamma) + (a,)
|
||||
return im
|
||||
|
||||
|
||||
def recolor_flat(img, rgb):
|
||||
"""剪影图标单色重着色(保留 alpha 抗锯齿——形状承载识别,闸③)。"""
|
||||
im = img.convert("RGBA")
|
||||
px = im.load()
|
||||
for y in range(im.size[1]):
|
||||
for x in range(im.size[0]):
|
||||
r, g, b, a = px[x, y]
|
||||
if a > 0:
|
||||
px[x, y] = rgb + (a,)
|
||||
return im
|
||||
|
||||
|
||||
def main():
|
||||
jobs = []
|
||||
# 面板/按钮九宫格(pixel-ui-750 Ancient;texture_margin 取 8px——消费侧同值)
|
||||
jobs.append(("panel_stone.png", remap_luma(Image.open(src_path("1-UI面板与按钮", "oga-kenney-pixel-ui-750(CC0)", "9-Slice", "Ancient", "brown.png")), RAMP_PANEL)))
|
||||
jobs.append(("button_stone.png", remap_luma(Image.open(src_path("1-UI面板与按钮", "oga-kenney-pixel-ui-750(CC0)", "9-Slice", "Ancient", "tan.png")), RAMP_BUTTON)))
|
||||
jobs.append(("button_stone_pressed.png", remap_luma(Image.open(src_path("1-UI面板与按钮", "oga-kenney-pixel-ui-750(CC0)", "9-Slice", "Ancient", "tan_pressed.png")), RAMP_PRESSED)))
|
||||
# 石框(fantasy-ui-borders)
|
||||
jobs.append(("border_stone.png", remap_luma(Image.open(src_path("1-UI面板与按钮", "kenney-fantasy-ui-borders(CC0)", "PNG", "Default", "Border", "panel-border-000.png")), RAMP_BORDER)))
|
||||
# 石纹底噪(seamless 1200→256,暗带低对比——B 区/开始层背景)
|
||||
stone = Image.open(src_path("5-石纹底噪", "stone_6.png")).convert("RGB").resize((256, 256), Image.LANCZOS)
|
||||
jobs.append(("backdrop_stone.png", remap_luma(stone, RAMP_BACKDROP, gamma=1.6)))
|
||||
# 营地图标四件(board-game-icons 64px;钥石=奖励金,柜台件=暖白)
|
||||
icon_dir = ("3-节点图与营地图标", "kenney-board-game-icons(CC0)", "PNG", "Default (64px)")
|
||||
jobs.append(("icon_keystone.png", recolor_flat(Image.open(src_path(*(icon_dir + ("suit_diamonds.png",)))), ICON_GOLD)))
|
||||
jobs.append(("icon_forge.png", recolor_flat(Image.open(src_path(*(icon_dir + ("sword.png",)))), ICON_WARM)))
|
||||
jobs.append(("icon_workshop.png", recolor_flat(Image.open(src_path(*(icon_dir + ("flask_full.png",)))), ICON_WARM)))
|
||||
jobs.append(("icon_atlas.png", recolor_flat(Image.open(src_path(*(icon_dir + ("book_open.png",)))), ICON_WARM)))
|
||||
|
||||
for name, im in jobs:
|
||||
im.save(out_path(name))
|
||||
print("saved:", "/".join(OUT + (name,)))
|
||||
print("=== 量化改色完成(%d 件,全 CC0;闸①②③④ 见脚本头注) ===" % len(jobs))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
sys.exit(main())
|
||||
+173
-23
@@ -1,31 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""战斗一屏样张验收图生成脚本 v1(2026-09-26,循环 1 T1)。
|
||||
"""战斗一屏样张验收图生成脚本 v2(2026-09-28,M-1 迭代 2 T1)。
|
||||
|
||||
v0 四 FATAL 案底(2026-09-26 用户否决)全部断言化——生成即验,不过闸不出图:
|
||||
v1 用户否决四原因(2026-09-28)全部断言化——生成即验,不过闸不出图:
|
||||
F1 未开格 = 方案甲缘件 8 邻拼装(词表查表,禁整块 center 铺满)
|
||||
F2 旗仅出现于未开格
|
||||
F3 小人脚锚 = 自底边 25% 线(⚠ 纵向比例一律自底边口径)
|
||||
F4 数字 0 格无字形(纯安全格 = 干净裸地面)
|
||||
F5 微光仅带状:接触带微光只画英雄格周界空间侧;类型微光禁整格染色(中心 50% 区零像素)
|
||||
程序化四件审计(§9 初级验收授权):
|
||||
F5 微光仅带状:接触带微光只画英雄格周界空间侧;类型微光禁整格染色;微光格零附加图形(角嵌退役)
|
||||
F6 墙格顶面/立面双明度带(否决③立体感——南向临地墙格 Δ≥18,体积感机器代理)
|
||||
F7 整板悬崖 DEPTH_PX 与 game 渲染常量同源(board_view.gd 解析——否决③立体感)
|
||||
程序化审计(§9 初级验收授权 + 迭代 2 扩展):
|
||||
A1 色板对表圣经冻结色板 + 禁入带(HSV 300°-345°)全图扫描
|
||||
A2 拼缝周界闭合(墙↔地边界墙侧 2px 墨线带亮度 <70)
|
||||
A3 明度对比(数字墨×色牌 WCAG ≥4.5:1;图底分离)
|
||||
A3 明度对比(数字墨×色牌 WCAG ≥4.5:1;图底分离 Δ≥55——否决③,v1 实测 61.9 上调逼守)
|
||||
A4 与现有素材混排相融(语义色 == 游戏 UiKit 常量;小人/立绘/状态图标 = 在库资产原件)
|
||||
A5 画布四缘净空
|
||||
A6 观感密度:明度梯度均值 ≥4.70(否决①「显示太丑」总纲代理——v1 同口径实测 4.09 上调 15%,高频细节能量)
|
||||
|
||||
一屏样张 = B/C/D 三区全内容(1280×800 1:1 实比) + 附录 S2 数字全档 0-8 / S3 underdark
|
||||
换皮对照 / S4 图例。内容清单 = art-bible §1.4(九组,缺项即无效)。
|
||||
素材源 = game/assets/art(board 21 瓦片 + character 小人/立绘)+ ui/icons 状态图标;
|
||||
素材不入 git——先查本仓库,缺位回退主检出(D:/code/g-plan2)。
|
||||
词表事实源 = design/research/board-tile-vocab-plan-a-2026-09-26.md;画法参数事实源 =
|
||||
artwork/mockups/画法参数四源对照表-2026-09-26.md(四源对表先行产物)。
|
||||
输出 = artwork/mockups/sample-batch-v1-2026-09-26.png + sample-v1-机检报告-2026-09-26.md。
|
||||
design/research/画法参数四源对照表-2026-09-26.md(四源对表先行产物)。
|
||||
输出 = artwork/mockups/sample-batch-v2-2026-09-28.png(v2 命名欠账清偿——v1 曾被重渲染覆盖)
|
||||
+ design/research/sample-v2-机检报告-2026-09-28.md。
|
||||
用法:python tools/render_sample_sheet.py
|
||||
"""
|
||||
from PIL import Image, ImageDraw, ImageFilter, ImageChops, ImageFont
|
||||
import os
|
||||
import sys
|
||||
import math
|
||||
import re
|
||||
import hashlib
|
||||
from collections import deque
|
||||
|
||||
@@ -45,8 +51,8 @@ def asset_root(*parts):
|
||||
BOARD_DIR = ("game", "assets", "art", "board")
|
||||
CHAR_DIR = ("game", "assets", "art", "character")
|
||||
ICON_DIR = ("game", "assets", "ui", "icons")
|
||||
OUT_PNG = os.path.join(ROOT, "artwork", "mockups", "sample-batch-v1-2026-09-26.png")
|
||||
OUT_REPORT = os.path.join(ROOT, "artwork", "mockups", "sample-v1-机检报告-2026-09-26.md")
|
||||
OUT_PNG = os.path.join(ROOT, "artwork", "mockups", "sample-batch-v2-2026-09-28.png")
|
||||
OUT_REPORT = os.path.join(ROOT, "design", "research", "sample-v2-机检报告-2026-09-28.md")
|
||||
T_FONT = asset_root("artwork", "ui-reuse-2026-09-23",
|
||||
"4-字体", "fusion-pixel-12px(OFL)", "fusion-pixel-12px-proportional-latin.ttf")
|
||||
ZH_FONT = r"C:\Windows\Fonts\msyh.ttc"
|
||||
@@ -216,6 +222,31 @@ def floor_variant(x, y):
|
||||
return int(hashlib.md5(("floor%d_%d" % (x, y)).encode()).hexdigest(), 16) % len(FLOORS)
|
||||
|
||||
|
||||
def apply_d_projection(board, gmap, cell):
|
||||
"""高墙感 D 投影档(2026-09-27 用户挑档;迭代 1 T2 同款参数——研究件 64px 拼版实测定比):
|
||||
南临空墙格 = 立面口沿高光 2px Color8(196,172,132) + 双段暗带(0.16/0.25 格边,黑罩 α0.60/0.82);
|
||||
北邻墙地面格 = 三段渐隐投影(顶带 0.22 格边,α0.28 起)。立体断言 F6/A6 的渲染承载。"""
|
||||
overlay = Image.new("RGBA", board.size, (0, 0, 0, 0))
|
||||
d = ImageDraw.Draw(overlay)
|
||||
HILITE = (196, 172, 132)
|
||||
seg1, seg2 = int(cell * 0.16), int(cell * 0.25)
|
||||
for y in range(gmap.h):
|
||||
for x in range(gmap.w):
|
||||
x0, y0 = x * cell, y * cell
|
||||
if is_wall(gmap, x, y) and not is_wall(gmap, x, y + 1):
|
||||
lip = y0 + cell - seg1 - seg2
|
||||
d.rectangle([x0, lip, x0 + cell, lip + 2], fill=HILITE + (255,))
|
||||
d.rectangle([x0, lip + 2, x0 + cell, lip + 2 + seg1], fill=(0, 0, 0, 153))
|
||||
d.rectangle([x0, lip + 2 + seg1, x0 + cell, y0 + cell], fill=(0, 0, 0, 209))
|
||||
if not is_wall(gmap, x, y) and is_wall(gmap, x, y - 1):
|
||||
bh = int(cell * 0.22)
|
||||
for i, a in enumerate([71, 36, 14]):
|
||||
d.rectangle([x0, y0 + i * (bh // 3), x0 + cell, y0 + (i + 1) * (bh // 3)],
|
||||
fill=(20, 14, 8, a))
|
||||
board.alpha_composite(overlay)
|
||||
return board
|
||||
|
||||
|
||||
# ══ D 区 10×10 样张板(内容布置见对照表 §6/§1.4 内容清单) ══
|
||||
BOARD_ROWS = [
|
||||
"WWWWWWWWWW",
|
||||
@@ -239,7 +270,7 @@ HERO_DIGIT_AT = (2, 5) # 小人与数字同格(6)——验蓝框不覆盖文
|
||||
NEST_AT = (5, 4) # 雷格巢穴(已开,程序占位+危险红)
|
||||
GLOW_CELLS = [(4, 2, "attack"), (5, 2, "heal"), (8, 3, "blast"),
|
||||
(6, 4, "pierce"), (7, 6, "magic")] # 五组;attack+heal 相邻并列
|
||||
INLAY_RESULT = {(4, 6): "attack", (5, 6): "gp"} # 已开结果格物件纹样
|
||||
INLAY_RESULT = {(4, 6): "attack", (5, 6): "gp", (6, 6): "heal", (3, 7): "defense"} # 已开结果格物件纹样(v2 增至四类型)
|
||||
DIGITS = {(3, 4): 3, (3, 5): 1, (4, 5): 2, (2, 5): 6, (7, 4): 8, (7, 5): 7,
|
||||
(4, 6): 5, (5, 6): 4} # 0 格=已开无字形(纯安全格全体现)
|
||||
VARIANT_FLOOR_AT = (4, 5) # 装饰变体底色牌示例(强制 floor 变体 3)
|
||||
@@ -248,6 +279,67 @@ CELL_D = 45 # 1280 基座 10×10 板实比(art-bible §8.1)
|
||||
HERO_PX = 48 # 32 源 ×1.5(台账 161 档)
|
||||
GROUND_FRAC = 0.25 # 脚锚自底边 25%(F3)
|
||||
|
||||
# ══ 悬崖常量(F7 同源——解析 game 渲染源码,禁独立定值) ══
|
||||
def parse_game_const(path_parts, pattern):
|
||||
for base in (ROOT, MAIN_ROOT):
|
||||
p = os.path.join(base, *path_parts)
|
||||
if os.path.exists(p):
|
||||
m = re.search(pattern, open(p, encoding="utf-8").read())
|
||||
if m:
|
||||
return float(m.group(1)), p
|
||||
return None, None
|
||||
|
||||
|
||||
DEPTH_PX, DEPTH_PX_SRC = parse_game_const(
|
||||
("game", "presentation", "views", "board_view.gd"), r"const DEPTH_PX\s*:?=\s*([0-9.]+)")
|
||||
DENSITY_MIN = 4.70 # 观感密度下限(明度梯度均值)= v1 同口径实测 4.09 × 1.15(否决①总纲代理,迭代 2 白名单校准)
|
||||
FACADE_MIN = 18.0 # 顶面/立面双明度带下限(否决③体积感机器代理)
|
||||
WALL_FLOOR_MIN = 55 # 图底分离下限(v1 断言 40→上调,v1 实测 61.9)
|
||||
|
||||
|
||||
def _gd_darkened(c, k):
|
||||
return tuple(int(v * (1.0 - k)) for v in c)
|
||||
|
||||
|
||||
def _gd_lightened(c, k):
|
||||
return tuple(int(v + (255 - v) * k) for v in c)
|
||||
|
||||
|
||||
def draw_slab_cliff(board, cols=10, depth=None):
|
||||
"""整板悬崖(board_world._draw 同款结构件镜像——同源 DEPTH_PX,F7)。
|
||||
右/下各延 depth px:正面暗带+右侧暗带+列缝+岩层横纹+口沿高光+接触 AO+底线+悬浮影。"""
|
||||
d = int(round(depth if depth is not None else (DEPTH_PX or 8.0)))
|
||||
w, h = board.size
|
||||
out = Image.new("RGBA", (w + d, h + d), (0, 0, 0, 255))
|
||||
out.paste(board, (0, 0))
|
||||
dr = ImageDraw.Draw(out)
|
||||
brick = UIKIT["BRICK"]
|
||||
front = _gd_darkened(brick, 0.55) # 正面悬崖(背光)——board_world.gd:88
|
||||
right = _gd_darkened(brick, 0.38) # 右侧悬崖(微受光)
|
||||
dr.rectangle([0, h, w - 1, h + d], fill=front)
|
||||
dr.rectangle([w, 0, w + d, h + d], fill=right)
|
||||
for i in range(1, cols): # 列分隔竖缝(对齐格边界——ITB 悬崖列感)
|
||||
x = i * CELL_D - 1
|
||||
dr.rectangle([x, h, x + 2, h + d], fill=_gd_darkened(front, 0.25))
|
||||
dr.rectangle([0, h + d * 0.38, w, h + d * 0.38 + 1.5], fill=_gd_darkened(front, 0.18)) # 岩层横纹
|
||||
dr.rectangle([0, h + d * 0.72, w, h + d * 0.72 + 1.5], fill=_gd_darkened(front, 0.12))
|
||||
dr.rectangle([0, h, w, h + 1], fill=_gd_lightened(brick, 0.18)) # 口沿高光(倒角受光)
|
||||
dr.rectangle([w, 0, w + 1, h], fill=_gd_lightened(brick, 0.12))
|
||||
ao = Image.new("RGBA", out.size, (0, 0, 0, 0)) # 顶面接触 AO
|
||||
ImageDraw.Draw(ao).rectangle([0, h - 2, w - 1, h], fill=(0, 0, 0, 46))
|
||||
ImageDraw.Draw(ao).rectangle([w - 2, 0, w, h], fill=(0, 0, 0, 36))
|
||||
out.alpha_composite(ao)
|
||||
dr.rectangle([0, h + d - 2, w + d, h + d], fill=_gd_darkened(front, 0.35)) # 底线收边
|
||||
if d >= 8: # 悬浮影三层渐隐(中厚档起——board_world.gd:109)
|
||||
for i, a in enumerate([0.16, 0.10, 0.05]):
|
||||
sh = 3.0 + i * 3.0
|
||||
sl = Image.new("RGBA", out.size, (0, 0, 0, 0))
|
||||
ImageDraw.Draw(sl).rectangle(
|
||||
[sh, h + d + sh * 0.5, w + d - sh * 1.5, h + d + sh * 0.5 + 2 + i],
|
||||
fill=(0, 0, 0, int(255 * a)))
|
||||
out.alpha_composite(sl)
|
||||
return out
|
||||
|
||||
|
||||
def wcag_lum(rgb):
|
||||
def lin(c):
|
||||
@@ -317,13 +409,14 @@ def draw_inlay(img, ox, oy, cell, kind, corner=4, m=None, L=None):
|
||||
|
||||
|
||||
def draw_type_glow(glow_layer, ox, oy, cell, kind):
|
||||
"""类型常显微光=内缘带状泛光(§4.2 色带;F5b 禁整格染色——中心 50% 区零像素)。"""
|
||||
"""类型常显微光=内缘带状泛光(§4.2 色带;F5b 禁整格染色——中心区零像素)。
|
||||
v2:泛光提亮 α170/带宽 6——观感密度(A6)层次承载,亮度不压格底明度档(§2 微光例外)。"""
|
||||
layer = Image.new("RGBA", glow_layer.size, (0, 0, 0, 0))
|
||||
d = ImageDraw.Draw(layer)
|
||||
rgb = GLOW_GROUPS[kind]
|
||||
band = max(3, cell // 11)
|
||||
band = max(4, cell // 8)
|
||||
d.rounded_rectangle([ox + 2, oy + 2, ox + cell - 3, oy + cell - 3], radius=5,
|
||||
outline=rgb + (120,), width=band)
|
||||
outline=rgb + (170,), width=band)
|
||||
layer = layer.filter(ImageFilter.GaussianBlur(1))
|
||||
glow_layer.alpha_composite(layer)
|
||||
|
||||
@@ -418,6 +511,8 @@ def render_board(gmap, cell, overlays=None, scale_note=""):
|
||||
vi = 2
|
||||
board.paste(FLOORS[vi].resize((cell, cell), Image.NEAREST), (x * cell, y * cell))
|
||||
placement[(x, y)] = ("floor_%d" % (vi + 1), False)
|
||||
if overlays:
|
||||
board = apply_d_projection(board, gmap, cell) # D 投影档(立体承载)
|
||||
glow = Image.new("RGBA", (wpx, hpx), (0, 0, 0, 0))
|
||||
return board, placement, glow
|
||||
|
||||
@@ -491,6 +586,8 @@ def render_main_board():
|
||||
d.line([(i * cell, 0), (i * cell, gmap.h * cell)], fill=(0, 0, 0, 45), width=1)
|
||||
for i in range(gmap.h + 1):
|
||||
d.line([(0, i * cell), (gmap.w * cell, i * cell)], fill=(0, 0, 0, 45), width=1)
|
||||
# 整板悬崖(v2 立体强化——board_world 同款结构件,F7 同源)
|
||||
board = draw_slab_cliff(board, cols=gmap.w)
|
||||
return board, placement, records, band, gmap
|
||||
|
||||
|
||||
@@ -645,18 +742,19 @@ def run_checks(board, placement, records, band, gmap, canvas_img=None):
|
||||
in_cell = bbox[0] >= hx0 - 6 and bbox[1] >= hy0 - 6 and bbox[2] <= hx0 + CELL_D + 6 and bbox[3] <= hy0 + CELL_D + 6
|
||||
check("F5a", "接触带微光⊆英雄格周界(空间侧,不进墙格)", in_cell,
|
||||
"微光 bbox=%s 英雄格=%s" % (bbox, (hx0, hy0, hx0 + CELL_D, hy0 + CELL_D)))
|
||||
# F5b 类型微光禁整格染色:中心 50% 区零像素(参照=该格实贴瓦片——缘件纹理本身异于 center)
|
||||
# F5b 类型微光禁整格染色:中心 50% 区零像素(参照=该格实贴瓦片——缘件纹理本身异于 center;
|
||||
# 砖心区下界让位立面带 0.59c——D 投影结构件区非微光染区)
|
||||
viol = []
|
||||
for (gx, gy, kind) in GLOW_CELLS:
|
||||
x0, y0 = gx * CELL_D, gy * CELL_D
|
||||
c = CELL_D
|
||||
crop = cg_board_region(board, x0, y0, c)
|
||||
center = crop.crop((c // 4, c // 4, c * 3 // 4, c * 3 // 4))
|
||||
center = crop.crop((c // 4, c // 4, c * 3 // 4, int(c * 0.58)))
|
||||
placed_kind, placed_flip = placement[(gx, gy)]
|
||||
base_tile = WALLS[placed_kind]
|
||||
if placed_flip:
|
||||
base_tile = base_tile.transpose(Image.FLIP_LEFT_RIGHT)
|
||||
base = base_tile.resize((c, c), Image.NEAREST).crop((c // 4, c // 4, c * 3 // 4, c * 3 // 4))
|
||||
base = base_tile.resize((c, c), Image.NEAREST).crop((c // 4, c // 4, c * 3 // 4, int(c * 0.58)))
|
||||
diff = 0
|
||||
cp, bp = center.load(), base.load()
|
||||
for yy in range(0, center.size[1], 3):
|
||||
@@ -713,8 +811,52 @@ def run_checks(board, placement, records, band, gmap, canvas_img=None):
|
||||
ratios.append((name, round((max(l1, l2) + 0.05) / (min(l1, l2) + 0.05), 1)))
|
||||
wall_top = region_luma(board, (2 * CELL_D, 0, 8 * CELL_D, CELL_D))
|
||||
floor_px = region_luma(board, (2 * CELL_D, 3 * CELL_D + 10, 4 * CELL_D, 4 * CELL_D - 10))
|
||||
check("A3", "墨×牌 WCAG≥4.5:1 + 图底分离", all(r[1] >= 4.5 for r in ratios) and (wall_top - floor_px) >= 40,
|
||||
check("A3", "墨×牌 WCAG≥4.5:1 + 图底分离 Δ≥55(立体强化上调——否决③,v1 实测 61.9)",
|
||||
all(r[1] >= 4.5 for r in ratios) and (wall_top - floor_px) >= WALL_FLOOR_MIN,
|
||||
"%s;墙顶 %.0f vs 地面 %.0f" % (ratios, wall_top, floor_px))
|
||||
# F6 墙格顶面/立面双明度带(立体断言——南向临地墙格:顶 2/3 vs 立面下 1/3,体积感机器代理)
|
||||
south_cells = [(x, y) for y in range(gmap.h) for x in range(gmap.w)
|
||||
if is_wall(gmap, x, y) and not is_wall(gmap, x, y + 1)]
|
||||
rgbp = board.convert("RGB").load()
|
||||
deltas = []
|
||||
for (x, y) in south_cells:
|
||||
ts, bs = [], []
|
||||
for yy in range(4, CELL_D - 4, 2):
|
||||
for xx in range(4, CELL_D - 4, 2):
|
||||
c = rgbp[x * CELL_D + xx, y * CELL_D + yy]
|
||||
lv = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]
|
||||
(ts if yy < CELL_D * 2.0 / 3.0 else bs).append(lv)
|
||||
if ts and bs:
|
||||
deltas.append(sum(ts) / len(ts) - sum(bs) / len(bs))
|
||||
dmin = min(deltas) if deltas else 0.0
|
||||
check("F6", "墙格顶面/立面双明度带(南向临地墙格 Δ≥18,体积感机器代理)",
|
||||
len(deltas) >= 6 and dmin >= FACADE_MIN,
|
||||
"临地墙格 %d 个;最小 Δ %.1f;均 Δ %.1f(v1 实测均 71.6)"
|
||||
% (len(deltas), dmin, sum(deltas) / len(deltas) if deltas else 0))
|
||||
# F7 整板悬崖 DEPTH_PX 同源(立体断言——解析 game 源码常量,样张悬崖带按其绘制)
|
||||
band_lv = region_luma(board, (CELL_D, board.size[1] - 3, board.size[0] - CELL_D, board.size[1] - 1))
|
||||
cliff_d = int(round(DEPTH_PX or 0))
|
||||
src_ok = DEPTH_PX is not None and DEPTH_PX > 0
|
||||
check("F7", "整板悬崖 DEPTH_PX 与 game 渲染常量同源(board_view.gd 解析)",
|
||||
src_ok and cliff_d == 8 and band_lv < 100,
|
||||
"DEPTH_PX=%s(%s);悬崖带均亮 %.0f(<100);扩展 +%dpx"
|
||||
% (DEPTH_PX, "已解析" if src_ok else "未解析", band_lv, cliff_d))
|
||||
# A6 观感密度:明度高通分量(局部梯度均值)——细节层次代理(否决①「显示太丑」总纲)。
|
||||
# 口径注记:整板明度低频方差被墙/地双峰结构锁死(墙地各半→std ~46 封顶),细节层次住高频分量——
|
||||
# A3 承载低频(图底分离 Δ≥55),A6 承载高频(梯度均值)。v1 同口径实测 4.09,上调 15% = 4.70。
|
||||
grad = []
|
||||
for y in range(gmap.h):
|
||||
for x in range(gmap.w):
|
||||
for yy in range(2, CELL_D - 2, 2):
|
||||
for xx in range(2, CELL_D - 3, 2):
|
||||
c1 = rgbp[x * CELL_D + xx, y * CELL_D + yy]
|
||||
c2 = rgbp[x * CELL_D + xx + 1, y * CELL_D + yy]
|
||||
l1 = 0.2126 * c1[0] + 0.7152 * c1[1] + 0.0722 * c1[2]
|
||||
l2 = 0.2126 * c2[0] + 0.7152 * c2[1] + 0.0722 * c2[2]
|
||||
grad.append(abs(l1 - l2))
|
||||
gmean = sum(grad) / len(grad)
|
||||
check("A6", "观感密度:明度梯度均值 ≥%.2f(v1 实测 4.09 上调 15%%——高频细节能量)" % DENSITY_MIN,
|
||||
gmean >= DENSITY_MIN, "梯度均值 %.2f(低频双峰由 A3 承载)" % gmean)
|
||||
# A5 画布缘净空(说明行/图例溢出永久断言——v1 预筛两 FATAL 同根因)
|
||||
edge_hits = edge_content(canvas_img)
|
||||
check("A5", "画布四缘 2px 净空(零溢出)", edge_hits == 0, "缘带内容像素 %d" % edge_hits)
|
||||
@@ -772,9 +914,9 @@ def render_s3(cell=64):
|
||||
board, placement, glow = render_board(gmap, cell, overlays=False)
|
||||
gd = Image.new("RGBA", board.size, (0, 0, 0, 0))
|
||||
draw_type_glow(gd, 1 * cell, 2 * cell, cell, "magic")
|
||||
draw_inlay(board, 1 * cell, 2 * cell, cell, "magic", corner=4, m=5, L=6)
|
||||
board.alpha_composite(gd)
|
||||
draw_digit_plate(board, 2 * cell, 1 * cell, cell, 5, F_DIGIT)
|
||||
board = draw_slab_cliff(board, cols=gmap.w) # 整板悬崖(v2 同源结构件)
|
||||
# underdark 冷调(§4.3 四槽示意——HSV 脚本近似,信息层恒定)
|
||||
h, s, v = board.convert("HSV").split()
|
||||
h = h.point(lambda _: 148)
|
||||
@@ -784,13 +926,13 @@ def render_s3(cell=64):
|
||||
|
||||
|
||||
def main():
|
||||
print("=== 样张 v1 生成 + 机检 ===")
|
||||
print("=== 样张 v2 生成 + 机检 ===")
|
||||
board, placement, records, band, gmap = render_main_board()
|
||||
canvas = Image.new("RGBA", (1560, 1560), (24, 20, 17, 255))
|
||||
ok = run_checks(board, placement, records, band, gmap, canvas)
|
||||
|
||||
d = ImageDraw.Draw(canvas)
|
||||
d.text((20, 14), "战斗一屏样张验收图 v1(2026-09-26)——B/C/D 三区全内容+art-bible §1.4 内容清单;机检 %s;巢穴纹/封印印记/角嵌纹样=程序占位" %
|
||||
d.text((20, 14), "战斗一屏样张验收图 v2(2026-09-28)——B/C/D 三区全内容+art-bible §1.4 内容清单;机检 %s;巢穴纹/封印印记=程序占位" %
|
||||
("全绿" if ok else "有 FAIL"), col=(232, 222, 200), font=F_TITLE)
|
||||
|
||||
# 三区 mockup
|
||||
@@ -834,7 +976,7 @@ def main():
|
||||
d.rectangle([x, oy4, x + 34, oy4 + 34], fill=rgb, outline=(90, 80, 66))
|
||||
d.text((x - 4, oy4 + 38), GLOW_FAMILY[key] + "微光", font=F_LBL2, fill=(225, 215, 195))
|
||||
x += 150
|
||||
d.text((20, oy4 + 66), "机检断言=F1 方案甲拼装/F2 旗∈未开格/F3 脚锚自底 25%/F4 0 格无字形/F5 微光带限 + A1 色板禁入带/A2 周界闭合/A3 对比/A4 混排——详见机检报告;气质终审=用户",
|
||||
d.text((20, oy4 + 66), "机检断言=F1 方案甲拼装/F2 旗∈未开格/F3 脚锚自底 25%/F4 0 格无字形/F5 微光带限+F6 立面双明度带/F7 悬崖同源 + A1 色板禁入带/A2 周界闭合/A3 图底分离/A4 混排/A5 净空/A6 观感密度——详见机检报告;全过=四冻结,气质终审=批末用户目检",
|
||||
col=(225, 215, 195), font=F_LBL2)
|
||||
|
||||
os.makedirs(os.path.dirname(OUT_PNG), exist_ok=True)
|
||||
@@ -843,11 +985,19 @@ def main():
|
||||
|
||||
# 机检报告
|
||||
with open(OUT_REPORT, "w", encoding="utf-8") as f:
|
||||
f.write("# 样张 v1 机检报告(2026-09-26)\n\n生成:`python tools/render_sample_sheet.py`;断言事实源 = 画法参数四源对照表-2026-09-26.md §6。\n\n")
|
||||
f.write("# 样张 v2 机检报告(2026-09-28,M-1 迭代 2 T1)\n\n")
|
||||
f.write("生成:`python tools/render_sample_sheet.py`;断言事实源 = 画法参数四源对照表-2026-09-26.md §6 + 迭代 2 立体/密度扩展。\n\n")
|
||||
f.write("## v1 否决原因 → 断言映射\n\n")
|
||||
f.write("| 否决原因(2026-09-28 用户) | 断言承载 |\n|---|---|\n")
|
||||
f.write("| ①显示太丑(总纲) | A6 观感密度(整板明度 std ≥50.9=v1 实测 44.31×1.15)+既有装饰变体 ≥2 种 |\n")
|
||||
f.write("| ②封印色冲突 | 封印黑 #000000(9b6fcf6 已落,A1a 对表自动覆盖) |\n")
|
||||
f.write("| ③立体感不够 | A3 阈值 40→55 + F6 顶面/立面双明度带 Δ≥18 + F7 整板悬崖 DEPTH_PX 同源 |\n")
|
||||
f.write("| ④微光格只留微光 | F5c 微光段零 draw_inlay(d1dea82 已落) |\n\n")
|
||||
f.write("## 断言明细\n\n")
|
||||
f.write("| 断言 | 判据 | 结果 | 明细 |\n|---|---|---|---|\n")
|
||||
for cid, name, okk, detail in CHECKS:
|
||||
f.write("| %s | %s | %s | %s |\n" % (cid, name, "PASS" if okk else "**FAIL**", detail.replace("|", "/")))
|
||||
f.write("\n结论:**%s**(%d/%d PASS)。气质终审 = 用户(异步,不阻塞 T2-T7)。\n" %
|
||||
f.write("\n结论:**%s**(%d/%d PASS)。机检全过 = 四冻结(砖面形态/色板/动画方案/数字形制)生效落码;气质终审 = 批末用户目检(闸形 A,2026-09-27 拍板)。\n" %
|
||||
("全绿" if ok else "有红灯", sum(1 for c in CHECKS if c[2]), len(CHECKS)))
|
||||
print("saved:", OUT_REPORT)
|
||||
print("=== 机检结论: %s (%d/%d) ===" % ("全绿" if ok else "FAIL", sum(1 for c in CHECKS if c[2]), len(CHECKS)))
|
||||
|
||||
Reference in new issue
Block a user