feat(new8/batch3-4): cipher/teahouse/keeper/bridge 全套交付——new8 八款全部可交付收官

四泳道并行全流水线(资产→开发→测试→玩家迭代闭环),双双 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(压缩候选)
This commit is contained in:
xsl
2026-09-14 05:48:13 +08:00
parent c8843233b0
commit 4b39ae2e49
2676 changed files with 62789 additions and 1282 deletions
+110
View File
@@ -0,0 +1,110 @@
"""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()