- abyss:game.js 242 断言全绿(剧本A-F六路径/氧气门三形态/sonarGate仲裁/结算死窗/双轨看门狗/radio 8项修复继承);资产 148句+11key变体(电台滤波带内能量47-62%→69-80%) - detective:game.js 265 断言真资产下全绿(矩阵不变式独立重算/指证全路径/评级12组注入/VERDICT死窗/二周目变体);真资产复跑回炉3处实现修复(无进展轨墙钟化/菜单暂停记账/停止播报真停声——mock时长掩盖的正确性bug) - 资产:TTS 539条×2格式、Mureka 7首 $0.21(累计$1.08/$2.00)、SYN12+SA3 19件(QC抓出1例AAC过冲并修复)、封面×2、check_all_assets 10游戏全绿(abyss 298/298、detective 279/279) - 平台接线:大厅双卡片、BUCKETS 增 abyss/detective 桶(H4 断言更新 10 游戏)、voice_text.json 10游戏991句 - 回归:14 套件全绿(10 playtest+guide+engine-voice+engine-input+api-smoke 12/12) - 设计偏差登记:abyss 7条/detective 9条(全为设计内部冲突按承诺清单/归因真话律取优)
107 lines
4.0 KiB
Python
107 lines
4.0 KiB
Python
"""web/abyss 封面:1080x1920,深潜救援主题——深海近黑蓝底、声呐同心圆、
|
||
单点脉冲光与竖排标题《深海回音》。可复现脚本(固定 seed,无外部网络依赖)。
|
||
|
||
用法(PIL 探测顺序:系统 python3 → .venv-tts → 装 pillow 后任一):
|
||
python3 web/abyss/gen/gen_cover.py
|
||
"""
|
||
import math
|
||
import os
|
||
import random
|
||
|
||
from PIL import Image, ImageDraw, ImageFilter, ImageFont
|
||
|
||
W, H = 1080, 1920
|
||
FONT_CANDIDATES = [
|
||
"/home/xsl/SoundGame/web/rhythm/gen/NotoSansSC-Bold.ttf",
|
||
"/home/xsl/SoundGame/app/src/reflex/gen/NotoSansSC-Bold.ttf",
|
||
]
|
||
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "cover.png")
|
||
|
||
|
||
def load_font(size):
|
||
for p in FONT_CANDIDATES:
|
||
if os.path.exists(p):
|
||
return ImageFont.truetype(p, size)
|
||
raise SystemExit("未找到中文字体,请放一份 NotoSansSC-Bold.ttf 到 web/rhythm/gen/")
|
||
|
||
|
||
def main():
|
||
rng = random.Random(922) # 固定 seed:可复现
|
||
img = Image.new("RGB", (W, H), (3, 8, 16))
|
||
d = ImageDraw.Draw(img)
|
||
|
||
# 背景竖向渐变:海面微光 -> 沟底近黑(下潜方向)
|
||
for y in range(H):
|
||
t = y / H
|
||
d.line([(0, y), (W, y)],
|
||
fill=(int(16 - 13 * t), int(34 - 26 * t), int(56 - 40 * t)))
|
||
|
||
# 远景悬浮微粒(深海水雪 marine snow)
|
||
dot = Image.new("RGBA", (W, H), (0, 0, 0, 0))
|
||
dd = ImageDraw.Draw(dot)
|
||
for _ in range(150):
|
||
x, y = rng.randint(0, W - 1), rng.randint(0, H - 1)
|
||
r = rng.choice([1, 1, 2])
|
||
dd.ellipse([x - r, y - r, x + r, y + r],
|
||
fill=(180, 210, 230, rng.randint(14, 46)))
|
||
img.paste(dot, (0, 0), dot)
|
||
d = ImageDraw.Draw(img)
|
||
|
||
# 中央:声呐同心圆(你发出的脉冲),青色环从光点向外扩散
|
||
cx, cy = W // 2, 600
|
||
for i, (r, alpha, wd) in enumerate([(348, 30, 3), (272, 48, 4), (196, 70, 5), (124, 96, 6)]):
|
||
d.ellipse([cx - r, cy - r, cx + r, cy + r],
|
||
outline=(56, 189, 248, alpha) if i % 2 == 0 else (125, 211, 252, alpha),
|
||
width=wd)
|
||
glow = Image.new("RGBA", (W, H), (0, 0, 0, 0))
|
||
gd = ImageDraw.Draw(glow)
|
||
gd.ellipse([cx - 44, cy - 44, cx + 44, cy + 44], fill=(186, 230, 253, 200))
|
||
gd.ellipse([cx - 88, cy - 88, cx + 88, cy + 88], fill=(56, 189, 248, 60))
|
||
glow = glow.filter(ImageFilter.GaussianBlur(10))
|
||
img.paste(glow, (0, 0), glow)
|
||
d = ImageDraw.Draw(img)
|
||
d.ellipse([cx - 20, cy - 20, cx + 20, cy + 20], fill=(240, 249, 255))
|
||
|
||
# 一记微弱回波:右下偏移的暗环(「深海回音」具象)
|
||
rx, ry = cx + 250, cy + 170
|
||
for r, alpha in [(150, 40), (104, 60), (64, 84)]:
|
||
ring = Image.new("RGBA", (W, H), (0, 0, 0, 0))
|
||
rd = ImageDraw.Draw(ring)
|
||
rd.ellipse([rx - r, ry - r, rx + r, ry + r],
|
||
outline=(94, 234, 212, alpha), width=3)
|
||
ring = ring.filter(ImageFilter.GaussianBlur(1.2))
|
||
img.paste(ring, (0, 0), ring)
|
||
d = ImageDraw.Draw(img)
|
||
|
||
# 标题(居中)与副题
|
||
f_title = load_font(172)
|
||
f_sub = load_font(54)
|
||
f_small = load_font(38)
|
||
|
||
def ctext(y, s, font, fill, spacing=8):
|
||
bbox = d.textbbox((0, 0), s, font=font)
|
||
w = bbox[2] - bbox[0]
|
||
x = (W - w) / 2
|
||
for ch in s:
|
||
d.text((x, y), ch, font=font, fill=fill)
|
||
bb = d.textbbox((0, 0), ch, font=font)
|
||
x += (bb[2] - bb[0]) + spacing
|
||
|
||
ctext(1020, "深海回音", f_title, (224, 242, 254), spacing=16)
|
||
ctext(1272, "声呐里,是被困潜艇的五个人", f_sub, (110, 134, 166))
|
||
|
||
# 手势图例(极简两行)
|
||
hints = ["单击 发声呐脉冲", "左右滑 抉择", "上滑 氧气与进度", "下滑 菜单", "双击 重听"]
|
||
for i, s in enumerate(hints):
|
||
col, row = i % 3, i // 3
|
||
d.text((110 + col * 300, 1420 + row * 76), s, font=f_small, fill=(92, 112, 142))
|
||
|
||
ctext(1700, "氧气在倒数。每次下潜,都是抉择。", f_small, (76, 94, 122))
|
||
|
||
img.save(OUT)
|
||
print(OUT, img.size)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|