四泳道并行全流水线(资产→开发→测试→玩家迭代闭环),双双 R1-R2 连续达标裁定可交付: - cipher《暗码》223 断言:摩尔斯解码嵌叙事/四结局矩阵/暴露度逐NIGHT验算/三层兜底;R2 三修(链首标记音/预警震/结局计数句+4句补录)4/4 全满分档 - teahouse《深夜茶馆》205 断言:泡茶时机窗/经济逐晚对账/多晚弧线;连续 2 轮 4/4 愿意玩 P0=0 - keeper《守塔人》257 断言:时空签名教学-兑现/插叙触发回返/trust×truth 档位/4结局;R2 F1/F2 坐实(震型豁免/首播跳句锁)4/4 - bridge《星环舰长》336 断言:巡检节奏逐毫秒/危机×系统态矩阵/仲裁7场景/忠诚差分/评级验算;R1 当场修F1-F4、R2 F5 菜单顺延保护 4/4 上行 公共:index.html 四卡片+profiles.js 四桶+check_all_assets 登记(预接线)、00-program 八款进度账收账、 gen_voice_dual 四游戏段、README/HANDOVER 阵容更新(11 款)、voice_text.json 四游戏键、mureka 台账(累计$1.26/$2.00) 验证:四游戏 playtest 223/205/257/336 全绿 + guide 122/122 + 全项目 15 游戏资产终检绿 遗留:keeper 三代人声滤波人耳试听(最高优先,ASR 对滤波句失明)、bridge 音频 32MB 超软指引 2MB(压缩候选)
111 lines
4.3 KiB
Python
111 lines
4.3 KiB
Python
"""web/cipher 封面:1080x1920,1940 密战主题——墨绿电波底、摩尔斯点划
|
||
电文、耳机轮廓微光与竖排标题《暗码》。可复现脚本(固定 seed,无外部网络依赖)。
|
||
|
||
用法:python3 web/cipher/gen/gen_cover.py
|
||
(或 .venv-tts/bin/python web/cipher/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(1940) # 固定 seed:可复现
|
||
img = Image.new("RGB", (W, H), (10, 16, 14))
|
||
d = ImageDraw.Draw(img)
|
||
|
||
# 背景竖向微渐变:军绿墨黑 → 电台暖绿(值机室的电子管夜光)
|
||
for y in range(H):
|
||
t = y / H
|
||
d.line([(0, y), (W, y)],
|
||
fill=(int(10 + 8 * t), int(16 + 18 * t), int(14 + 12 * t)))
|
||
|
||
# 示波器绿光波纹(密电的频率具象,模糊压暗做背景)
|
||
wave = Image.new("RGBA", (W, H), (0, 0, 0, 0))
|
||
wd = ImageDraw.Draw(wave)
|
||
for i in range(20):
|
||
cy = rng.randint(0, H)
|
||
amp = rng.randint(12, 60)
|
||
phase = rng.uniform(0, math.pi * 2)
|
||
freq = rng.uniform(0.004, 0.012)
|
||
pts = [(x, cy + amp * math.sin(freq * x + phase)
|
||
* math.exp(-((x - W / 2) / (W * 0.42)) ** 2))
|
||
for x in range(0, W, 8)]
|
||
wd.line(pts, fill=(96, 200, 140, rng.randint(10, 30)), width=3)
|
||
wave = wave.filter(ImageFilter.GaussianBlur(1.8))
|
||
img.paste(wave, (0, 0), wave)
|
||
d = ImageDraw.Draw(img)
|
||
|
||
# 中央:耳机轮廓 + 声波同心弧(「用耳朵当武器」的具象)
|
||
cx, cy = W // 2, 660
|
||
for i, (r, alpha) in enumerate([(310, 40), (236, 72), (168, 108), (110, 150)]):
|
||
ring = Image.new("RGBA", (W, H), (0, 0, 0, 0))
|
||
rd = ImageDraw.Draw(ring)
|
||
rd.arc([cx - r, cy - r, cx + r, cy + r], start=200, end=340,
|
||
fill=(140, 230, 170, alpha), width=9 - i * 2)
|
||
img.paste(ring, (0, 0), ring)
|
||
# 头梁 + 两侧耳罩
|
||
d.arc([cx - 190, cy - 210, cx + 190, cy + 170], start=180, end=360,
|
||
fill=(190, 235, 205, 210), width=26)
|
||
for sx in (-1, 1):
|
||
d.rounded_rectangle([cx + sx * 205 - 64, cy - 40, cx + sx * 205 + 64, cy + 190],
|
||
radius=64, fill=(24, 44, 34), outline=(170, 230, 190, 220), width=10)
|
||
# 电键(手键)剪影:底座 + 黄铜杆
|
||
ky = 1080
|
||
d.rounded_rectangle([cx - 150, ky + 60, cx + 150, ky + 110], radius=18, fill=(28, 40, 32))
|
||
d.line([cx - 30, ky + 62, cx + 96, ky - 46], fill=(212, 178, 106), width=22)
|
||
d.ellipse([cx + 82, ky - 72, cx + 132, ky - 22], fill=(232, 200, 130))
|
||
|
||
# 摩尔斯电文点划带:BAY ·− −··· / ·− / −·−− 的意象(不拼真码,做装饰带)
|
||
band_y = 1290
|
||
code = ([3, 1, 1, 1], [1, 3], [1, 3, 1, 3]) # −··· ·− −·−−(B A Y)
|
||
x = cx - 330
|
||
for letter in code:
|
||
for u in letter:
|
||
w = 34 if u == 3 else 14
|
||
d.rounded_rectangle([x, band_y - 9, x + w, band_y + 9], radius=8,
|
||
fill=(150, 235, 180, 220))
|
||
x += w + 22
|
||
x += 34
|
||
# 静电噪点
|
||
for _ in range(700):
|
||
x, y = rng.randint(0, W - 1), rng.randint(0, H - 1)
|
||
a = rng.randint(6, 22)
|
||
img.putpixel((x, y), (140, 220, 170))
|
||
|
||
img = img.filter(ImageFilter.GaussianBlur(0.4))
|
||
d = ImageDraw.Draw(img)
|
||
|
||
# 竖排标题《暗码》(右下留白区,避免与遮罩文字重叠)
|
||
f_title = load_font(236)
|
||
f_sub = load_font(44)
|
||
tx, ty = W - 330, 1420
|
||
for i, ch in enumerate("暗码"):
|
||
d.text((tx, ty + i * 260), ch, font=f_title, fill=(226, 242, 230),
|
||
stroke_width=6, stroke_fill=(10, 22, 16))
|
||
d.text((tx + 40, ty + 2 * 260 + 30), "1940 · 江城密电", font=f_sub,
|
||
fill=(150, 210, 175))
|
||
|
||
img.save(OUT)
|
||
print("saved", OUT)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|