Files
SoundGame/web/abyss/gen/gen_cover.py
T
xsl 31372615b2 feat(new8/batch2): 《深海回音》abyss +《雨夜专案》detective 全套交付(多agent流水线:策划→资产→开发→测试→真资产复跑)
- 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条(全为设计内部冲突按承诺清单/归因真话律取优)
2026-09-10 01:40:31 +08:00

107 lines
4.0 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.
"""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()