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条(全为设计内部冲突按承诺清单/归因真话律取优)
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user