Files
xsl 4b39ae2e49 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(压缩候选)
2026-09-14 05:48:13 +08:00

93 lines
3.8 KiB
Python

"""深夜茶馆 cover.png 生成:1080x1920,深巷夜色底 + 灯笼暖光 + 茶烟 + 游戏名 + 玩法提示。"""
import math
import random
from PIL import Image, ImageDraw, ImageFilter, ImageChops
W, H = 1080, 1920
FONT = "/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc"
OUT = "/home/xsl/AutoVideo/SoundGame/web/teahouse/cover.png"
random.seed(20)
img = Image.new("RGB", (W, H))
px = img.load()
# 深巷夜色渐变:上 #0a0d16 -> 下 #1c130a(底部灯笼映暖)
top, bottom = (10, 13, 22), (28, 19, 10)
for y in range(H):
t = y / (H - 1)
for x in range(W):
px[x, y] = tuple(int(a + (b - a) * t) for a, b in zip(top, bottom))
d = ImageDraw.Draw(img)
# 星点(稀疏,都市夜空)
for _ in range(140):
x, y = random.randint(0, W - 1), random.randint(0, int(H * 0.4))
c = random.randint(90, 180)
d.point((x, y), fill=(c, c, min(255, c + 10)))
# 两侧屋檐剪影(巷子纵深)
d.polygon([(0, 0), (W * 0.30, 0), (W * 0.16, H * 0.34), (0, H * 0.42)], fill=(6, 7, 12))
d.polygon([(W, 0), (W * 0.70, 0), (W * 0.84, H * 0.34), (W, H * 0.42)], fill=(6, 7, 12))
# 巷子尽头茶馆门脸亮窗(暖光小方块)
d.rectangle([W * 0.42, H * 0.40, W * 0.58, H * 0.47], fill=(64, 42, 18))
d.rectangle([W * 0.44, H * 0.415, W * 0.56, H * 0.455], fill=(120, 78, 30))
# 主灯笼(中上,暖橙光晕两层)
cx, cy = W / 2, H * 0.30
glow = Image.new("RGB", (W, H), (0, 0, 0))
gg = ImageDraw.Draw(glow)
gg.ellipse([cx - W * 0.26, cy - W * 0.26, cx + W * 0.26, cy + W * 0.26], fill=(52, 30, 10))
glow = glow.filter(ImageFilter.GaussianBlur(60))
img = ImageChops.add(img, glow)
d = ImageDraw.Draw(img)
# 灯笼体:椭圆 + 骨架线
d.ellipse([cx - W * 0.115, cy - H * 0.052, cx + W * 0.115, cy + H * 0.052], fill=(196, 84, 32))
for k in range(-3, 4):
xk = cx + k * W * 0.028
d.arc([xk - W * 0.10, cy - H * 0.052, xk + W * 0.10, cy + H * 0.052],
start=300 if k < 0 else 240, end=420 if k > 0 else 60, fill=(140, 56, 20), width=3)
d.rectangle([cx - W * 0.03, cy - H * 0.062, cx + W * 0.03, cy - H * 0.050], fill=(120, 96, 40))
d.rectangle([cx - W * 0.03, cy + H * 0.050, cx + W * 0.03, cy + H * 0.062], fill=(120, 96, 40))
# 灯穗
d.line([cx, cy + H * 0.062, cx, cy + H * 0.095], fill=(160, 40, 30), width=6)
# 茶烟(灯笼上方,淡灰曲线)
smoke = Image.new("RGBA", (W, H), (0, 0, 0, 0))
sd = ImageDraw.Draw(smoke)
for i in range(3):
pts = []
for t in range(60):
u = t / 59
x = cx + math.sin(u * 6 + i * 2) * 34 * (1 - u * 0.4) + (i - 1) * 26
y = cy - H * 0.075 - u * H * 0.16
pts.append((x, y))
sd.line(pts, fill=(180, 180, 190, 46), width=10)
smoke = smoke.filter(ImageFilter.GaussianBlur(6))
img.paste(Image.alpha_composite(img.convert("RGBA"), smoke).convert("RGB"), (0, 0))
# 底部茶盏剪影 + 涟漪声纹环(声音主题)
d = ImageDraw.Draw(img)
bcx, bcy = W / 2, H * 0.74
d.ellipse([bcx - 130, bcy - 40, bcx + 130, bcy + 60], fill=(14, 10, 8))
d.ellipse([bcx - 108, bcy - 28, bcx + 108, bcy + 34], fill=(46, 30, 14))
for r, a in [(230, 60), (300, 40), (370, 24)]:
d.arc([bcx - r, bcy - r * 0.36, bcx + r, bcy + r * 0.36], start=200, end=340,
fill=(90, 66, 34), width=4)
# 文字
from PIL import ImageFont
def font(sz):
return ImageFont.truetype(FONT, sz)
d.text((W / 2, H * 0.56), "深夜茶馆", font=font(120), fill=(240, 226, 200), anchor="mm")
d.text((W / 2, H * 0.615), "茶是热的,夜是长的", font=font(40), fill=(200, 178, 140), anchor="mm")
d.text((W / 2, H * 0.885), "左右滑选茶 · 双击提壶出汤 · 上滑状态 · 下滑菜单",
font=font(34), fill=(170, 158, 138), anchor="mm")
d.text((W / 2, H * 0.925), "纯音频手势游戏 · 戴上耳机更佳",
font=font(30), fill=(130, 122, 108), anchor="mm")
img.save(OUT)
print("saved", OUT)