Files
SoundGame/tools/gen_voice_dual.py
T
xsl 963dfa1cc8 fix(escape): 暗室状态引导方案落地(25号策划文档 B1-B8 → F1-F9 实施)
策划轮:25-escape-map-flow.md as-built地图/玩家流程/信息账本+盲区清单(S1-S7)+方案B1-B8
实施(玩法零改动,只加状态引导层):
- F1 撞墙方位句「左边是墙」(wall_dir_*×4,同向10s限频)
- F2 目标句并入方位「铁柜,在右面的房间」(gd_*_dir×3)
- F3 无进展150s活跃计时器+gd_prog_1..3进度重述(只重述不泄题,专治活跃迷路玩家——修卡关计时从「无进展」退化为「无操作」的既定偏差)
- F4 双击兜底直通L2降级L1(不再白送答案)
- F5 空房首访描述(4空房可区分)
- F7 busy窗口手势缓存补判(原静默吞手势)
- F8 结算链单击守卫(time_band/again完整到达)
- F9 跳过引导补播目标句
- F6 空房方位特征句按25号裁定留档后续轮
新增语音10句×2性别(flock串行QC无离群,ASR逐字正确)
验证:escape playtest 235/235(+85新断言)+ guide 132/132 + 终检全绿 + 14游戏回归零回退
live复跑10.4分钟通关:零截断,S1-S7盲区逐条核销
档案 research/escape-state/round1-2;01号文档追加修订记录R1
2026-09-15 00:37:41 +08:00

2861 lines
212 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.
# -*- coding: utf-8 -*-
"""双性别语音资产管线(R1):为 5 个 web 游戏生成 male/female 两版旁白 voice。
约定:
- 台词表从各游戏已有 gen 脚本原样提取(storyline/escape 的原始表在 app/src/<flavor>/gen/,
本脚本只做只读引用,不改 app/)。
- storyline 只生成旁白 + 系统音(NARR* 角色)的双性别版本;角色台词(林晚/猎狗/小满等)
不在本脚本范围内,沿用 web/storyline/audio/voice/ 平铺原文件。
- 输出 staging wav: tools/gen_voice_dual_wav/<game>/<gender>/<id>.wav
--convert 后落地: web/<game>/audio/voice/<gender>/<id>.{ogg,m4a}
(原有平铺 voice/*.ogg 保留不动,等引擎切换后由后续轮次清理。)
- 键盘变体(P1 路线 a,research/platform/02-input-parity.md §4.3):KEY_LINES 只登记
含操作词的句子(key 集合 ⊆ 主集合),复用主表同 id 的 instruct/speaker;
staging wav: tools/gen_voice_dual_wav/<game>/<gender>/key/<id>.wav
--convert 后落地: web/<game>/audio/voice/<gender>/key/<id>.{ogg,m4a}
(引擎 audio.js 在生效输入模式=键盘时前置该目录,触屏主资产零改动。)
生成建议:--only-variants 只跑变体(--check/--convert 对变体始终生效)。
质量控制:
- 每条 assert 时长 > 0.3s、峰值 > 0.05;统一峰值归一 0.89。
- 台词 > 40 字按句拆分生成再拼接(句间 0.12s 静音),避免长句吞字。
- 短句/数字类(文本 ≤ 2 字)同 instruct 一整批连续生成,减少 prosody 漂移。
- --check 对比同句两性时长,相差 3 倍以上判异常(删掉异常 wav 后重跑本脚本即可)。
用法(GPU 必须串行):
source env.d/tts.sh
flock /home/xsl/SoundGame/.gpu.lock .venv-tts/bin/python tools/gen_voice_dual.py --game simon --gender both
.venv-tts/bin/python tools/gen_voice_dual.py --game all --check # 无需 GPU
source env.d/android.sh && .venv-tts/bin/python tools/gen_voice_dual.py --game all --convert
"""
import argparse
import os
import re
import subprocess
import sys
os.environ.setdefault("HF_HOME", "/home/xsl/SoundGame/models/hf")
os.environ.setdefault("HF_ENDPOINT", "https://hf-mirror.com")
ROOT = "/home/xsl/SoundGame"
STAGING = os.path.join(ROOT, "tools", "gen_voice_dual_wav")
SPLIT_LIMIT = 40 # 超过 40 字拆句
GAP_SEC = 0.12 # 拼接句间静音
PEAK_NORM = 0.89
BATCH_SHORT = 16 # 数字/短句一批
BATCH_LONG = 6
# ---------------------------------------------------------------- 音色分配
# 游戏 × 性别 -> speaker。说明见 docs/audio/asset-manifest.md。
# Ryan(英语母语音色)经冒烟测试未采用:项目已有中文发音投诉,男声统一用
# 中文母语的 Uncle_Fu;测试样本在 tools/samples/ryan_zh_*.wav 供人工试听。
SPEAKERS = {
"storyline": {"male": "Uncle_Fu", "female": "Vivian"},
"escape": {"male": "Uncle_Fu", "female": "Serena"},
"simon": {"male": "Uncle_Fu", "female": "Serena"},
"rhythm": {"male": "Uncle_Fu", "female": "Vivian"},
"space": {"male": "Uncle_Fu", "female": "Serena"},
"rpg": {"male": "Uncle_Fu", "female": "Serena"},
# radio 默认 speaker 仅作兜底;双性别行按 VOICE_SPK["radio"] 逐句覆盖(HOST/PROD 双配对)
"radio": {"male": "Uncle_Fu", "female": "Serena"},
"dungeon": {"male": "Uncle_Fu", "female": "Serena"},
# abyss 默认=DECK 调度(多数行);DIVER 行按 VOICE_SPK["abyss"] 逐句覆盖(radio 同型)
"abyss": {"male": "Dylan", "female": "Vivian"},
"detective": {"male": "Uncle_Fu", "female": "Serena"},
# cipher 默认=WIND 北风/系统(多数行);OP 报务员行按 VOICE_SPK["cipher"] 逐句覆盖
"cipher": {"male": "Dylan", "female": "Vivian"},
# hunt 旁白双性别(23-hunt-voice-ids.md 头注);bot 喊话固定音色在 HUNT_NPC
"hunt": {"male": "Uncle_Fu", "female": "Serena"},
# teahouse 旁白双性别(20-teahouse-voice-ids.md 头注:male=Uncle_Fu/female=Serena)
"teahouse": {"male": "Uncle_Fu", "female": "Serena"},
# keeper 默认=SHORE 岸台/系统(多数行);KEEPER 守塔人行按 VOICE_SPK["keeper"] 逐句覆盖
"keeper": {"male": "Dylan", "female": "Vivian"},
# bridge 旁白双性别(22-bridge-voice-ids.md 头注:舰载系统音);NPC 恒定音色在 BRIDGE_NPC
"bridge": {"male": "Uncle_Fu", "female": "Serena"},
}
# instruct 对:(male, female)。None 表示两性别共用同一 instruct。
def _pair(m, f=None):
return (m, f if f is not None else m)
# ---------------------------------------------------------------- storyline
_SL_NARR = _pair("低沉醇厚的中年男声旁白,冷静、缓慢,带着末世废墟的苍凉感",
"冷静低沉的女声旁白,语速缓慢,带着末世废墟的苍凉感")
_SL_WARM = _pair("低沉醇厚的中年男声旁白,由紧张转为温暖,结尾带希望",
"女声旁白,由紧张转为温暖,结尾带希望")
_SL_COLD = _pair("低沉醇厚的中年男声旁白,冷漠、空洞,越说越轻",
"女声旁白,冷漠、空洞,越说越轻")
_SL_EPIC = _pair("低沉醇厚的中年男声旁白,悲壮,尾音下沉",
"女声旁白,悲壮,尾音下沉")
STORYLINE_LINES = [
# ---- 系统语音 ----
("help", _SL_NARR, "操作说明。单击屏幕:继续剧情。双击:重听刚才的台词。向上滑:听这段说明和当前进度。向下滑:呼出菜单,菜单里可以重新开始。当旁白说“请选择”的时候,向左滑或者向右滑,做出你的决定。选择无法撤销。"),
("status1", _SL_NARR, "当前进度:第一幕,城南地铁站附近。你的目标:活着离开这片废墟,前往北区的广播塔,寻找妹妹林晨的下落。"),
("status2", _SL_NARR, "当前进度:第二幕,酸雨笼罩的街区与断桥。你的目标:渡过江水,抵达广播塔。小心,猎狗的人,就在附近。"),
("status3", _SL_NARR, "当前进度:第三幕,广播塔下。你的目标:把警报发出去,找到林晨。这是最后一段路了。"),
("menu", _SL_NARR, "菜单。向左滑:重新开始整个故事。向右滑,或者单击:回到故事。向上滑:听操作说明。"),
# ---- 选择提示 ----
("p1", _SL_NARR, "请选择。向左滑:先检查背包里的收音机。向右滑:先摸黑寻找出口。"),
("p2", _SL_NARR, "请选择。向左滑:钻进旁边的通风管道。向右滑:躺进路边的尸体堆里装死。"),
("p3", _SL_NARR, "请选择。向左滑:循着哭声过去看看。向右滑:不去管它,直接去北区。"),
("p4", _SL_NARR, "请选择。向左滑:带上小满一起走。向右滑:留下食物,独自上路。"),
("p5", _SL_NARR, "请选择。向左滑:走桥下的污水涵洞。向右滑:爬桥上悬着的钢索。"),
("p6", _SL_NARR, "请选择。向左滑:交出坐标,保命要紧。向右滑:拒绝他,想办法周旋。"),
("p7", _SL_NARR, "请选择。向左滑:追上去,警告避难所。向右滑:自己逃命,越远越好。"),
("p8", _SL_NARR, "请选择。向左滑:用公开频道广播,让全城都听见。向右滑:用加密频段,只发给避难所。"),
# ---- 第一幕 ----
("n00_intro", _SL_NARR, "大停电后的第三十天,城市陷入彻底的黑暗。没有电,没有网络,只有收音机里偶尔闪过的沙沙声,像垂死者的呼吸。你是林晚,一名前急救员。在这个故事里,你用声音活下去。请记住:单击屏幕,继续剧情;双击,重听上一句;向上滑,听你的状态和帮助;向下滑,呼出菜单。当我说“请选择”的时候,向左滑,或者向右滑,做出你的决定。选择一旦做出,就无法回头。现在,单击屏幕,睁开眼睛。"),
("n01_wake", _SL_NARR, "后脑一阵钝痛,嘴里全是铁锈味。你在废弃的地铁站里醒来,四周黑得像沉在水底。背包还压在身下,手电筒却不知去向。远处滴水的声音,一下,一下,像某种倒计时。"),
("n04_exit", _SL_NARR, "你扶着潮湿的墙,一步一步往外摸。第五十步,脚尖踢到一个硬物——是一支手电,还剩半格电。光柱劈开黑暗的那一瞬,你看见墙上用红漆刷着一行大字:别去北区。"),
("n05_signal", _SL_NARR, "就在这时,背包侧袋里的收音机,沙沙声忽然变得清晰。一个平稳的女声,一遍又一遍地重复着:"),
("n06_whistle", _SL_NARR, "你刚走上街面,黑暗里忽然响起一声口哨。接着是第二声,第三声,从不同的方向,把你围在中间。有人的手电光,扫过你的脸。"),
("n08_vent", _SL_NARR, "你一矮身,钻进了通风管道。铁皮贴着脊背,窄得几乎无法呼吸。身后传来咒骂和铁棍敲击管道口的声音,越来越近,又渐渐远去。十分钟后,你从两条街外的井口爬出来,肺里像塞满了沙子。"),
("n09_dead", _SL_NARR, "你屏住呼吸,把自己埋进路边冰冷的尸体堆里。手电、罐头、半瓶水,被他们一样一样摸走。有人踢了你一脚,你纹丝不动。就在他们离开时,你听见一句不该听见的话——“北区那个陷阱,今晚就收网。”"),
# ---- 第二幕 ----
("n10_rain", _SL_NARR, "酸雨开始下了。街面上升起细密的嘶嘶声,像无数条小蛇在游。雨幕那头,隐约传来一阵哭声——细细的,是个孩子。"),
("n11_child", _SL_NARR, "哭声来自一间塌了一半的便利店。一个小男孩抱着膝盖坐在货架底下,身边躺着一个再也不会醒来的女人。他抬起头,脸上全是灰,小声说:“我叫小满。妈妈说,数到一千,她就会醒。我已经数到……数到好多个一千了。”"),
("n13_leave", _SL_NARR, "你把仅剩的一罐罐头放在他身边,转身走进雨里。身后,孩子重新开始数数:一……二……三……你告诉自己,别回头。千万别回头。"),
("n14_ignore", _SL_NARR, "哭声被你甩在身后,越来越远,最后混进雨声里,再也听不见了。你走得很快,快得像在逃跑。快一点,再快一点。你对自己说,只要到了北区,这一切就都值了。"),
("n15_bridge", _SL_NARR, "两个小时后,江水拦住了去路。跨江大桥断成两截,桥身斜插进黑沉沉的江面。要去广播塔,只剩两条路:桥下的污水涵洞,黑得看不见底;或者,桥上悬着的钢索,在风里荡来荡去。"),
("n16_culvert", _SL_NARR, "涵洞里,污水没到胸口,又冷又黏,带着腐烂的甜味。走到一半,水面下有什么东西,擦着你的腿滑了过去。你没有叫出声,只是走,一步,一步,直到看见洞口那点灰白的天光。"),
("n16b_culvert_c", _SL_NARR, "你让小满骑在肩上,自己蹚进没到胸口的污水里。又冷又黏,带着腐烂的甜味。走到一半,水面下有什么东西擦过你的腿,小满吓得一抖,你抬手按住他的膝盖:“数数。”他小声数起来,一,二,三……数到一百七,你们看见了洞口的天光。"),
("n17_cable", _SL_NARR, "钢索比想象中更滑。酸雨把它泡得发黏,你每挪一步,整根钢索就晃一下。江风从断桥的缺口灌进来,像有无数只手在水底下拽你的脚。你不往下看。你只知道,前面就是北岸。"),
("n17b_cable_c", _SL_NARR, "钢索比想象中更滑。小满趴在你背上,一声不吭,两条小胳膊勒得你生疼。走到一半,钢索猛地一沉,你听见身后钢丝崩断的脆响。“闭眼。”你说。然后你用尽全身力气,向前,向前,直到膝盖磕上北岸的石头。"),
# ---- 第三幕 ----
("n20_convoy", _SL_NARR, "猎狗的车队朝北区开走了,探照灯在雨幕里划出一道道口子。你蹲在原地,胃里一阵一阵地泛酸。晨晨在北区。而刚才,是你,亲手把狼,引到了她的门口。"),
("n22_fight", _SL_NARR, "你往后退了半步,忽然笑了:“坐标在我脑子里。杀了我,你什么都拿不到。”猎狗愣了半秒。就是这半秒,你抓起一把沙土扬在他脸上,转身就跑。子弹擦着肩膀打进墙里,火辣辣地疼。但你在跑,你在跑——活着的感觉,从来没有这么清楚过。"),
("n23_tower", _SL_NARR, "广播塔就立在头顶,红色的信号灯在雨里一闪一闪,像一颗还在跳动的心脏。塔底的中控台居然还能用。只要爬上去,你就能接入城市广播网,把警报发给北区的每一个人。问题是——用哪个频道。"),
# ---- 结局 ----
("e1_dawn", _SL_WARM, "加密频道那头沉默了三秒。然后,一个你等了三十天的声音响了起来:“姐?姐!是你吗?”天亮之前,避难所的救援队打开了大门。猎狗的人,在北区的高墙下溃散了。你抱着晨晨,听见广播塔里,重新响起了音乐。后来人们说,这座城市,是从一个女人的声音开始,重新活过来的。——好结局:黎明。感谢你,陪林晚走完了这条路。单击屏幕,可以回到故事开始的地方。"),
("e2_price", _SL_EPIC, "你赶在车队前面,撞进了避难所的大门。“关掉广播!关灯!猎狗来了——!”那一夜,北区守住了。探照灯熄灭的时候,晨晨从人群里扑出来抱住你,才发现你胳膊上的血,已经把半边袖子浸透了。坐标是你交出去的,命,是你抢回来的。你赢了,用一条胳膊的代价。——结局:代价。单击屏幕,重新开始。"),
("e3_alone", _SL_COLD, "你逃了。一路向南,没有回头。三天后,收音机里关于北区的广播,停了。又过了七天,你在一个废弃的加油站里,听见最后一个频段,也变成了沙沙声。你活下来了。只是从此以后的每一个夜里,你都会想起那声口哨,和你报出坐标时,自己发抖的声音。——坏结局:孤声。单击屏幕,重新开始。"),
("e4_static", _SL_EPIC, "公开频道,把所有人都引来了——幸存者,还有猎狗。塔顶的风很大,你看见山下火把连成的长龙,也看见避难所方向亮起的防御火光。猎狗的刀抵上来的时候,你对着麦克风笑了:“听见了吗,晨晨?姐姐的声音,全城都听见了。”信号中断了。但那晚之后,北区,再没有人投降。——结局:静默。单击屏幕,重新开始。"),
("skip_hint", _SL_NARR, "单击可跳过旁白。"),
# ---- 向导层(docs/design/14-guide-layer.md,schemes §1.5 定稿全文)----
("gd_continue", _SL_NARR, "轻触屏幕,继续。"),
("gd_invalid", _SL_NARR, "这个操作在这里用不上。"),
# ---- 向导层 round2(docs/design/14-guide-layer.md「round2 v1.1」,synthesis §3 定稿全文)----
("gd_brief", _SL_NARR, "单击继续剧情。请选择的时候,左右滑动。"),
("gd_again", _SL_NARR, "还在吗?单击继续。"),
]
# ---------------------------------------------------------------- escape
# R2 重设计(docs/design/01):全游戏同一 instruct 常量(00 文档 §3.2,不含语速词)。
_ESC = _pair("低沉平稳的男声,冷静、克制,带一点神秘感",
"沉稳平静的女声,冷静、克制,带一点神秘感")
ESCAPE_LINES = [
# ---- 开场引导 G0-G7 ----
("g0_intro", _ESC, "黑暗。你在废弃的地下设施里醒来,什么都想不起来。你必须找到出路。"),
("g1_move", _ESC, "先活动一下。朝任意方向滑一下,走一小步。"),
("g1_done", _ESC, "很好,你迈出了第一步。"),
("g2_wall", _ESC, "这个房间不大,四面都是墙。再滑两下,试试碰碰墙。"),
("g3_left", _ESC, "跟着我指的路走。向左滑。"),
("g4_down", _ESC, "对。再向下滑。"),
("g5_inspect", _ESC, "听到水声了吗?单击一下,仔细检查这个房间。"),
("g5_key", _ESC, "你摸到了一把钥匙!"),
("g6_doubletap", _ESC, "钥匙能开锁。双击,是把物品用出去的用法。现在,双击试试。"),
("g6_done", _ESC, "对,就是这样。接下来,靠你自己了。"),
("g7_goal", _ESC, "找到能用这把钥匙打开的东西。需要帮助时,在原地双击。"),
# ---- 引导纠错与跳过 ----
("guide_wrong_up", _ESC, "不是这个方向。向上滑。"),
("guide_wrong_down", _ESC, "不是这个方向。向下滑。"),
("guide_wrong_left", _ESC, "不是这个方向。向左滑。"),
("guide_wrong_right", _ESC, "不是这个方向。向右滑。"),
("guide_wrong_gesture", _ESC, "不是这个操作。照我说的做。"),
("guide_blunt_up", _ESC, "手指贴着屏幕,向上推一下。"),
("guide_blunt_down", _ESC, "手指贴着屏幕,向下推一下。"),
("guide_blunt_left", _ESC, "手指贴着屏幕,向左推一下。"),
("guide_blunt_right", _ESC, "手指贴着屏幕,向右推一下。"),
("guide_offpath", _ESC, "先跟我走。"),
("guide_skip", _ESC, "跳过引导,直接开始自由探索。"),
("reopen_prompt", _ESC, "上滑,重新听一遍引导。单击,直接开始。"),
# ---- 卡关三级提示 + 导航 ----
("stuck_ask", _ESC, "需要帮助吗?"),
("stuck_key_1", _ESC, "铁锈味最重的地方,值得仔细看看。"),
("stuck_key_2", _ESC, "去西南角的储物间,单击检查,找到钥匙。"),
("stuck_key_3", _ESC, "跟我说的方向走,去西南角的储物间。"),
("stuck_cabinet_1", _ESC, "钥匙就在你身上。它能打开某一把锁。"),
("stuck_cabinet_2", _ESC, "去东边的铁柜房,双击,用钥匙打开铁柜。"),
("stuck_cabinet_3", _ESC, "跟我说的方向走,去铁柜所在的房间。"),
("stuck_box_1", _ESC, "保险丝属于电箱。有电流声的地方,就有电箱。"),
("stuck_box_2", _ESC, "去西北角的电箱房,双击,把保险丝装进电箱。"),
("stuck_box_3", _ESC, "跟我说的方向走,去电箱所在的房间。"),
("stuck_gate_1", _ESC, "电已经通了。该去找那扇铁门了。"),
("stuck_gate_2", _ESC, "去东南角的大门,双击,打开它,离开这里。"),
("stuck_gate_3", _ESC, "跟我说的方向走,去大门所在的房间。"),
("nav_up", _ESC, "向上滑。"),
("nav_down", _ESC, "向下滑。"),
("nav_left", _ESC, "向左滑。"),
("nav_right", _ESC, "向右滑。"),
# ---- 房间描述 ----
("room_storage", _ESC, "一间储物间。货架上堆满杂物,角落里传来水滴声。"),
("room_cabinet", _ESC, "这个房间里,立着一个铁柜。"),
("room_power", _ESC, "这个房间的墙上,有一只电箱。"),
("room_gate", _ESC, "面前,是一扇沉重的铁门。"),
# ---- 用时播报 4 段整句 ----
("time_band1", _ESC, "你逃出来了,用时不到两分钟。了不起!"),
("time_band2", _ESC, "你逃出来了,用时不到五分钟。"),
("time_band3", _ESC, "你逃出来了,用时不到十分钟。"),
("time_band4", _ESC, "你逃出来了,用时超过十分钟。"),
# ---- 保留句(文本沿用,按新 instruct 重录) ----
("wall", _ESC, "那边是墙,换个方向。"),
("start_room", _ESC, "你站在设施中央的房间里。滑动移动,单击检查,双击使用物品。"),
("found_key", _ESC, "你蹲下身在储物架的角落里摸索,指尖碰到一个冰凉的金属物件——是一把钥匙!钥匙到手了。"),
("inspect_cabinet_locked", _ESC, "这个房间里立着一个铁柜,柜门上挂着一把锁。也许,有什么钥匙能打开它。"),
("unlock_cabinet", _ESC, "钥匙插进锁孔,缓缓转动。咔哒一声,柜门开了。柜子深处,躺着一枚保险丝。保险丝到手了。"),
("inspect_box_dead", _ESC, "墙上是一只电箱,箱盖敞开着,里面的保险丝座空空如也。没有电,这里的大门永远打不开。"),
("box_fixed", _ESC, "电箱运转正常,电流的嗡鸣声平稳而持续。"),
("fix_box", _ESC, "你把保险丝用力按进卡座。火花一闪,嗡鸣声响起——头顶的线路恢复供电了!"),
("inspect_gate", _ESC, "面前是一扇沉重的铁门。门边的电子锁一片漆黑,没有任何反应。它需要电力。"),
("inspect_gate_ready", _ESC, "电子锁的指示灯亮了,发出微弱的绿光。大门已经通电。双击,打开它,离开这里。"),
("escape", _ESC, "你扳动开关。电机轰鸣,沉重的铁门缓缓升起,刺眼的光线涌了进来。你眯起眼睛,向前迈出了第一步。自由了。你逃出来了。"),
("use_fail", _ESC, "这里没有用得上东西的地方。换个房间试试,或者先单击检查一下周围。"),
("inv_empty", _ESC, "你摸了摸身上,什么也没有。"),
("inv_key", _ESC, "你身上带着一把钥匙。"),
("inv_fuse", _ESC, "你身上带着一枚保险丝。"),
("inv_both", _ESC, "你身上带着一把钥匙和一枚保险丝。"),
("replay", _ESC, "黑暗再次合拢。你回到了中央的房间。这一次,你会更快。"),
("again", _ESC, "单击屏幕,再来一次。下滑,打开菜单。"),
("empty_1", _ESC, "房间里空空荡荡,只有你自己的回声。"),
("empty_2", _ESC, "你伸出手,摸到的只有冰冷粗糙的墙壁。"),
("empty_3", _ESC, "除了远处的水滴声,这里什么都没有。"),
# ---- 菜单与帮助 ----
("help_ops", _ESC, "滑动,在房间之间移动。单击,检查房间,或者重听房间的声音。双击,使用物品,或者查看背包。"),
("menu_open", _ESC, "菜单。"),
("menu_restart", _ESC, "重新开始。"),
("menu_home", _ESC, "回大厅。"),
("menu_back", _ESC, "返回游戏。"),
("skip_hint", _ESC, "单击可跳过旁白。"),
# ---- 向导层 round3(docs/design/14-guide-layer.md「round3 v1.2」,synthesis §3 定稿全文)----
("gd_next_fuse", _ESC, "保险丝到手。接下来,去找电箱。"),
# ---- 向导层 round4(docs/design/14-guide-layer.md「round4 v1.3」,synthesis §3 定稿全文)----
# H1:目标句后按房间图接播目标房间方位句(移动坐标系:up/down/left/right 滑动方向即方位)。
("gd_room_l", _ESC, "左面的房间。"),
("gd_room_r", _ESC, "右面的房间。"),
("gd_room_u", _ESC, "上面的房间。"),
("gd_room_d", _ESC, "下面的房间。"),
# ---- escape-state round2(docs/design/25-escape-map-flow.md §4.1 B1/B2/B3,台词与草案逐字一致)----
# B1(F1):撞墙方位句(陈述式非教学腔),wall 播完后接播,同向 10s 限频。
("wall_dir_l", _ESC, "左边是墙。"),
("wall_dir_r", _ESC, "右边是墙。"),
("wall_dir_u", _ESC, "上边是墙。"),
("wall_dir_d", _ESC, "下边是墙。"),
# B2(F2):目标名+方位一体句,替换三处 gd_room_* 接播(原句保留作异常路径兜底)。
("gd_cabinet_dir", _ESC, "铁柜,在右面的房间。"),
("gd_box_dir", _ESC, "电箱,在左面的房间。"),
("gd_gate_dir", _ESC, "大门,在右面的房间。"),
# B3(F3):无进展 150s 活跃计时器的进度重述(stage=cabinet/box/gate,key 阶段不走本通道)。
("gd_prog_1", _ESC, "钥匙已经到手。找到能用它的锁。"),
("gd_prog_2", _ESC, "铁柜已经打开。电还没有通。"),
("gd_prog_3", _ESC, "电已经通了。去找那扇大门。"),
]
# ---------------------------------------------------------------- simon
# R2 重设计(docs/design/02):全游戏同一 instruct 常量,轮次/成绩全部整句化。
_SIMON = _pair("平静、清晰,像一个耐心的游戏主持人")
_SIMON_ROUNDS = ["第一", "第二", "第三", "第四", "第五", "第六", "第七", "第八", "第九", "第十",
"第十一", "第十二", "第十三", "第十四", "第十五", "第十六", "第十七", "第十八", "第十九", "第二十"]
SIMON_LINES = [
# ---- 交互式教学 T0-T7 ----
("t0_welcome", _SIMON, "欢迎来到音序回响。先认识六种声音。"),
("t0_goal", _SIMON, "我会演奏一串声音,请你记住顺序,原样复现。"),
("t1_up", _SIMON, "向上滑,是这个声音。"),
("t2_down", _SIMON, "向下滑,是这个声音。"),
("t3_left", _SIMON, "向左滑,是这个声音。"),
("t4_right", _SIMON, "向右滑,是这个声音。"),
("t5_tap", _SIMON, "单击,是这个声音。"),
("t6_double", _SIMON, "双击,是这个声音。"),
("t_try", _SIMON, "你试试。"),
("t_ok", _SIMON, "对了。"),
("t_wrong_up", _SIMON, "不是这个,向上滑试试。"),
("t_wrong_down", _SIMON, "不是这个,向下滑试试。"),
("t_wrong_left", _SIMON, "不是这个,向左滑试试。"),
("t_wrong_right", _SIMON, "不是这个,向右滑试试。"),
("t_wrong_tap", _SIMON, "不是这个,单击试试。"),
("t_wrong_double", _SIMON, "不是这个,双击试试。"),
("t_blunt_up", _SIMON, "手指贴着屏幕,向上推一下。"),
("t_blunt_down", _SIMON, "手指贴着屏幕,向下推一下。"),
("t_blunt_left", _SIMON, "手指贴着屏幕,向左推一下。"),
("t_blunt_right", _SIMON, "手指贴着屏幕,向右推一下。"),
("t_blunt_tap", _SIMON, "用指尖,轻轻点一下屏幕。"),
("t_blunt_double", _SIMON, "用指尖,快速点两下屏幕。"),
("t7_ready", _SIMON, "六种声音都记住了。后面的序列,会越来越长。"),
("t7_retry", _SIMON, "答错可以消耗机会重听。向上滑,可以随时重听。"),
("t_skip", _SIMON, "跳过教学,直接开始。"),
# ---- 轮次播报(整句,round_01..round_20 + 兜底) ----
*[(f"round_{i + 1:02d}", _SIMON, f"{w}轮,听好了。") for i, w in enumerate(_SIMON_ROUNDS)],
("round_more", _SIMON, "新的一轮,听好了。"),
# ---- 成绩播报 6 段整句 ----
("score_band1", _SIMON, "一轮都没坚持下来。别灰心。"),
("score_band2", _SIMON, "你坚持了三轮以内。"),
("score_band3", _SIMON, "你坚持了五轮左右。"),
("score_band4", _SIMON, "你坚持了将近十轮。"),
("score_band5", _SIMON, "你坚持了十轮以上,很厉害。"),
("score_band6", _SIMON, "你坚持了十五轮以上,大师级!"),
# ---- 流程句 ----
("your_turn", _SIMON, "轮到你了。"),
("chances_out", _SIMON, "重听机会用完了,小心。"),
("retry_use", _SIMON, "错了。用一次机会,再听一遍。"),
("relisten", _SIMON, "再听一遍。"),
("replay", _SIMON, "单击屏幕,再来一局。"),
("rules_prompt", _SIMON, "上滑,重听规则。下滑,打开菜单。"),
("rules", _SIMON, "上行音,向上滑。下行音,向下滑。左边的音,向左滑。右边的音,向右滑。一声轻响,单击。两声轻响,双击。"),
("gameover", _SIMON, "游戏结束。"),
# ---- 反馈短句 ----
("correct", _SIMON, "答对了!"),
("wrong", _SIMON, "错了。"),
("chance_plus", _SIMON, "机会加一。"),
("new_record", _SIMON, "新纪录!"),
# ---- 菜单 ----
("menu_open", _SIMON, "菜单。"),
("menu_restart", _SIMON, "重新开始。"),
("menu_home", _SIMON, "回大厅。"),
("menu_back", _SIMON, "返回游戏。"),
("skip_hint", _SIMON, "单击可跳过旁白。"),
# ---- 向导层(docs/design/14-guide-layer.md,schemes §1.5 定稿全文)----
("gd_relisten", _SIMON, "需要的话,向上滑可以重新听一遍序列。"),
# ---- 向导层 round2(docs/design/14-guide-layer.md「round2 v1.1」,synthesis §3 定稿全文)----
# round3 G5 重录(14 号文档「round3 v1.2」):双击改回纯音色、重听改绑上滑,
# 机会经济句按裁定书新文案一字不改重录;教学链 t7_retry 同步手势口径。
("gd_economy", _SIMON, "记不住就向上滑,重新听一遍。免费机会有两次,用完要扣一次机会。"),
# ---- 向导层 round3(docs/design/14-guide-layer.md「round3 v1.2」,synthesis §3 定稿全文)----
("gd_clear", _SIMON, "漂亮,过了。"),
("gd_record", _SIMON, "破纪录了。"),
# ---- 向导层 round4(docs/design/14-guide-layer.md「round4 v1.3」,synthesis §3 定稿全文)----
# H2:轮次开场占用预告(序列播放后、轮到你提示前;重听键与答案音共用词表的根治形态)。
("gd_occ_up", _SIMON, "这一轮,向上滑是作答。"),
("gd_occ_dt", _SIMON, "这一轮,双击是作答。"),
("gd_occ_both", _SIMON, "这一轮,向上滑和双击都是作答。"),
# H3:使用时如实报余量(替换主动重听路径的旧「用完了」绝对句)。
("gd_chances_left1", _SIMON, "免费重听还剩一次。"),
("gd_chances_left0", _SIMON, "免费重听用完了。之后重听会花一次机会,别怕,慢慢来。"),
# ---- 状态查询(research/simon-polish round1,S1/S3/S4;input 阶段长按=问状态,
# 13 句全部整句预生成、数字分档,禁运行时拼接;不加 guide 前缀——三档 guide 全可用,
# 属玩家主动查询而非向导插播)----
("status_len_short", _SIMON, "这一轮很短。"),
("status_len_mid", _SIMON, "这一轮不长不短。"),
("status_len_long", _SIMON, "这一轮挺长。"),
("status_len_xlong", _SIMON, "这一轮很长,稳住。"),
("status_prog_start", _SIMON, "刚开头,一个还没对。"),
("status_prog_early", _SIMON, "刚答了个开头。"),
("status_prog_half", _SIMON, "答了一半左右。"),
("status_prog_late", _SIMON, "就剩最后几个了。"),
("status_free2", _SIMON, "免费重听还有两次。"),
("status_free1", _SIMON, "免费重听还有一次。"),
("status_chance2", _SIMON, "免费用完,机会还剩两次。"),
("status_chance1", _SIMON, "免费用完,机会还剩一次。"),
("status_out", _SIMON, "免费和机会都用完了,答错就结束。"),
]
# ---------------------------------------------------------------- rhythm
# 鼓手模式重构(docs/design/24-rhythm-voice-ids.md):41 句,全表同一 instruct
# 常量;向导层 9 句 gd_* 已在库沿用不重录(不在本表)。
_RHY = _pair("低沉醇厚的男声,像节奏游戏主持人,平稳有力",
"活力清晰的年轻女声,像节奏游戏主持人,情绪饱满但不夸张")
RHYTHM_LINES = [
# ---- W 组 · 开场与流程 ----
("welcome", _RHY, "欢迎来到节拍行者。今天你是乐队的鼓手,整面屏幕都是你的鼓。"),
("welcome_back", _RHY, "欢迎回来,鼓手。准备好了就开始。"),
("tut_done", _RHY, "很好,演出开始。"),
("tut_skip", _RHY, "跳过教学,直接开始。"),
("mid_song", _RHY, "过半了,保持。"),
# ---- Z 组 · 分区教学与区名 ----
("tut_map", _RHY, "先认识你的鼓。屏幕横着分三列,左边、中间、右边;竖着分两行,上面清亮,下面低沉。随便摸一摸,听到哪面鼓,我就告诉你它在哪。"),
("tut_probe", _RHY, "摸到三个不同的地方,我们就开始。"),
("tut_z_kick", _RHY, "下面正中间,是底鼓,你的家。先听四下数拍,然后跟着我敲。"),
("tut_z_sides", _RHY, "左下是军鼓,右下是低嗵。一左一右,跟着打。"),
("tut_z_patt", _RHY, "上面一排更清亮。左上踩镲,上中高嗵,右上吊镲。听这一句,三个音。"),
("tut_fill", _RHY, "最后有一段加花,跟着我连打。"),
("zone_tl", _RHY, "左上,踩镲。"),
("zone_tc", _RHY, "上中,高嗵。"),
("zone_tr", _RHY, "右上,吊镲。"),
("zone_bl", _RHY, "左下,军鼓。"),
("zone_bc", _RHY, "下中,底鼓。"),
("zone_br", _RHY, "右下,低嗵。"),
# ---- C 组 · 教学纠正 ----
("retry_early", _RHY, "早了,等鼓点再出手。"),
("retry_late", _RHY, "晚了,跟着鼓点走。"),
("retry_wrong", _RHY, "敲错鼓了,再听一遍。"),
("retry_listen", _RHY, "再听一遍,跟着来。"),
("retry_skip", _RHY, "没关系,我们先走下一句。"),
# ---- S 组 · 选曲与解锁 ----
("select_hint", _RHY, "左右滑选曲目,单击开始,下滑回大厅。"),
("song1_name", _RHY, "第一首,启程,九十拍。"),
("song2_name", _RHY, "第二首,追风,一百零五拍。"),
("song3_name", _RHY, "第三首,星爆,一百二十拍。"),
("song_locked", _RHY, "这首还没解锁。上一首拿到 B 级以上就能解锁。"),
("unlock_song2", _RHY, "新曲目解锁,追风!"),
("unlock_song3", _RHY, "新曲目解锁,星爆!"),
# ---- G 组 · 结算 ----
("grade_s", _RHY, "S 级!准确率超过九成五,完美演出。"),
("grade_a", _RHY, "A 级!准确率超过八成五,非常出色。"),
("grade_b", _RHY, "B 级,准确率超过七成。不错,还能更好。"),
("grade_c", _RHY, "C 级,准确率不到七成。别灰心,再来一遍。"),
("new_best_combo", _RHY, "新连击纪录!"),
("restart", _RHY, "单击再来一遍,右滑下一首,下滑回大厅。"),
# ---- M 组 · 菜单与规则 ----
("rules", _RHY, "规则:我敲一小节鼓,你在下一小节照着敲回来。鼓要选对,拍点也要对。"),
("settings_hint", _RHY, "音色、语速、背景音乐,请回大厅修改。"),
("menu_main", _RHY, "菜单。单击切换选项,双击确认。"),
("menu_resume", _RHY, "继续演奏。"),
("menu_lobby", _RHY, "回大厅。"),
("skip_hint", _RHY, "单击可跳过旁白。"),
]
# ---------------------------------------------------------------- space
# R3 备产(docs/design/04-space-voice-ids.md):52 句全量替换 R1 版,全表同一 instruct 常量。
_SPC = _pair("低沉醇厚的男声,像飞船领航员,沉稳有力",
"平静清晰的女声,像飞船领航员,沉稳可信")
SPACE_LINES = [
# ---- D 组 · 交互式演练 ----
("drill_welcome", _SPC, "欢迎登上盲航。你只有一双耳朵。"),
("drill_rule", _SPC, "陨石来了,会有声音告诉你方位。"),
("drill_left", _SPC, "听,左边有陨石。向左滑,把它拨开!"),
("drill_right", _SPC, "这次在右边。向右滑!"),
("drill_up", _SPC, "上面有东西,声音又尖又高。向上滑!"),
("drill_down", _SPC, "下面是低沉的轰隆。向下滑!"),
("drill_good", _SPC, "漂亮。"),
("drill_retry_left", _SPC, "那是左边来的,向左滑。"),
("drill_retry_right", _SPC, "那是右边来的,向右滑。"),
("drill_retry_up", _SPC, "声音又高又尖,是上面,向上滑。"),
("drill_retry_down", _SPC, "轰隆声在下面,向下滑。"),
("drill_moveon", _SPC, "没关系,跟着我,我们继续。"),
("drill_done", _SPC, "四个方向你都会了。引擎点火,出发!"),
("drill_skip", _SPC, "跳过演练,直接出发。"),
# ---- P 组 · 保护期 ----
("protect_left", _SPC, "训练护盾接住了。那颗在左边,听到左边的呼啸,向左滑。"),
("protect_right", _SPC, "训练护盾接住了。那颗在右边,听到右边的呼啸,向右滑。"),
("protect_up", _SPC, "训练护盾接住了。那颗在上面,听到高高的呼啸,向上滑。"),
("protect_down", _SPC, "训练护盾接住了。那颗在下面,听到低沉的轰隆,向下滑。"),
("protect_end", _SPC, "训练护盾关闭。接下来,靠你自己了。"),
# ---- F 组 · 单击/双击分批引入 ----
("feat_boost", _SPC, "试试单击,引擎会短促加速,直接冲过空隙。"),
("feat_boost_done", _SPC, "感觉到了吗?加速的时候,陨石撞不上你。"),
("feat_shield", _SPC, "双击展开护盾,能挡下一次撞击。"),
("feat_shield_done", _SPC, "护盾展开后会自动充能,好了我会告诉你。"),
("shield_ready", _SPC, "护盾充能完毕。"),
("shield_block", _SPC, "护盾挡下了撞击。"),
# ---- H 组 · 被撞归因 ----
("hit_left", _SPC, "左边撞上的。听到左边的呼啸,向左滑。"),
("hit_right", _SPC, "右边撞上的。听到右边的呼啸,向右滑。"),
("hit_up", _SPC, "上面撞上的。听到高高的呼啸,向上滑。"),
("hit_down", _SPC, "下面撞上的。听到低沉的轰隆,向下滑。"),
("hit_last", _SPC, "船体只剩最后一次了,小心!"),
# ---- M 组 · 里程碑 ----
("ms_10", _SPC, "十颗了,稳。"),
("ms_25", _SPC, "二十五颗,老练的飞行员。"),
("ms_50", _SPC, "五十颗,传奇。"),
("ms_100", _SPC, "一百颗!天空属于你。"),
# ---- R 组 · 结算 ----
("time_1", _SPC, "不到三十秒,再试一次。"),
("time_2", _SPC, "超过三十秒,有进步。"),
("time_3", _SPC, "超过一分钟,合格的飞行员。"),
("time_4", _SPC, "超过两分钟,王牌飞行员。"),
("dodge_1", _SPC, "只拨开几颗。"),
("dodge_2", _SPC, "拨开了十来颗。"),
("dodge_3", _SPC, "拨开了几十颗。"),
("dodge_4", _SPC, "拨开了一大群,漂亮。"),
("new_record", _SPC, "新纪录!"),
("restart_hint", _SPC, "单击再飞一次,下滑回大厅。"),
# ---- Y 组 · 菜单与系统 ----
("second_run_ask", _SPC, "上滑重新听演练,单击直接出发。"),
("menu_main", _SPC, "菜单。单击切换选项,双击确认。"),
("menu_resume", _SPC, "继续飞行。"),
("menu_lobby", _SPC, "回大厅。"),
("menu_help", _SPC, "重新听教学要点。"),
("help_recap", _SPC, "左边来向左滑,右边来向右滑,上面来向上滑,下面来向下滑。单击加速,双击护盾。"),
("settings_hint", _SPC, "音色、语速、背景音乐,请回大厅修改。"),
("paused", _SPC, "已暂停。"),
("skip_hint", _SPC, "单击可跳过旁白。"),
# ---- R5 新增(space polish round1/round2,research/space-polish/,04-space-voice-ids.md 追加节)----
("protect_soon", _SPC, "训练护盾还剩十秒。"),
("boost_not_ready", _SPC, "引擎还在冷却,稍等一下。"),
# ---- 向导层(docs/design/14-guide-layer.md,schemes §1.5 定稿全文)----
("gd_hit_hint", _SPC, "被撞了。听方位,同向滑动就能拨开。"),
# ---- 向导层 round2(docs/design/14-guide-layer.md「round2 v1.1」,synthesis §3 定稿全文)----
("gd_last_life", _SPC, "这是最后一次机会,撑住。"),
# ---- 向导层 round3(docs/design/14-guide-layer.md「round3 v1.2」,synthesis §3 定稿全文)----
# dir_* 为角色音风格超短呼喊,speaker 用本游戏旁白 pair(synthesis §3 表注)。
("dir_left", _SPC, "左!"),
("dir_right", _SPC, "右!"),
("dir_up", _SPC, "上!"),
("dir_down", _SPC, "下!"),
]
# ---------------------------------------------------------------- rpg
# R4 新游戏《剑与声》第一章(docs/design/05-rpg-ch1-voice-ids.md + 05-rpg-addendum.md §6.2)。
# 旁白全表同一 instruct 常量;NPC 单音色平铺见 RPG_NPC。
_RPG = _pair("低沉醇厚的男声,像说书人,平静有韵味",
"平静清晰的年轻女声,像在讲武侠故事")
RPG_LINES = [
# ---- A 组 · 序章 ----
("intro_1", _RPG, "你的眼睛看不见了。但你的耳朵,比谁都灵。"),
("intro_2", _RPG, "黑风寨掳走了你唯一的徒弟。追进迷雾林,把他们连根拔起。"),
("intro_3", _RPG, "先记住,怎么用耳朵走路。"),
# ---- S 组 · 系统/菜单/状态 ----
("sys_continue_ask", _RPG, "检测到你的旅程记录。单击继续,右滑开始新的旅程。"),
("sys_new_game", _RPG, "新的旅程,开始了。"),
("sys_saved", _RPG, "进度已保存。"),
("sys_rest", _RPG, "你在篝火边睡了一觉,状态全满。"),
("sys_drink", _RPG, "溪水清冽,你喝了个饱,伤势尽消。"),
("sys_menu", _RPG, "菜单。左右滑选择,单击确认。"),
("sys_menu_resume", _RPG, "继续旅程。"),
("sys_menu_save", _RPG, "保存并回大厅。"),
("sys_menu_help", _RPG, "重新听帮助。"),
("help_explore", _RPG, "左右滑切换选项,单击确认。双击重新听场景,上滑查看状态,下滑打开菜单。"),
("help_battle", _RPG, "战斗中,左右滑选招式,单击出手。听到敌人的前摇,双击格挡。"),
("settings_hint", _RPG, "音色、语速、背景音乐,请回大厅修改。"),
("st_hp_high", _RPG, "你状态很好。"),
("st_hp_mid", _RPG, "你受了伤。"),
("st_hp_low", _RPG, "你伤势很重,命悬一线。"),
("st_gold_poor", _RPG, "口袋空空。"),
("st_gold_some", _RPG, "有些积蓄。"),
("st_gold_rich", _RPG, "腰包鼓鼓。"),
("st_lv_low", _RPG, "你初出茅庐。"),
("st_lv_mid", _RPG, "你小有名气。"),
("st_lv_high", _RPG, "你已是高手。"),
("st_weapon_none", _RPG, "你赤手空拳。"),
("st_weapon_iron", _RPG, "你手持铁剑。"),
("st_weapon_steel", _RPG, "你手持青钢剑。"),
("st_weapon_wind", _RPG, "你手持听风剑。"),
("st_armor_none", _RPG, "你身无片甲。"),
("st_armor_leather", _RPG, "你身着皮甲。"),
("st_armor_iron", _RPG, "你身着铁鳞甲。"),
# ---- D 组 · 场景描述 ----
("desc_village", _RPG, "雾口村。篝火噼啪作响,村民低声议论着林子里的土匪。"),
("desc_village_re", _RPG, "你回到了雾口村。"),
("desc_path", _RPG, "林间小路。风穿过树叶,脚下的落叶沙沙作响。"),
("desc_path_re", _RPG, "你回到林间小路。"),
("desc_fork", _RPG, "岔路口。风声在左右两边,听起来不太一样。"),
("desc_fork_re", _RPG, "你回到岔路口。"),
("desc_chest", _RPG, "一个枯树洞。洞里似乎藏着什么。"),
("desc_chest_re", _RPG, "你回到枯树洞前。"),
("desc_stream", _RPG, "溪流。水声潺潺,是个歇脚的好地方。"),
("desc_stream_re", _RPG, "你回到溪流边。"),
("desc_hunter", _RPG, "猎人小屋。柴火正旺,还有磨刀的声音。"),
("desc_hunter_re", _RPG, "你回到猎人小屋。"),
("desc_deepwood", _RPG, "密林深处。虫鸣急促,远处有狼嚎,杀气很重。"),
("desc_deepwood_re", _RPG, "你回到密林深处。"),
("desc_gate", _RPG, "黑风寨门前。火把熊熊,人声嘈杂。"),
("desc_hall", _RPG, "聚义厅。火盆熊熊,大厅空旷,回声冰冷。"),
# ---- O 组 · 选项名 ----
("opt_go_path", _RPG, "前进,去林间小路。"),
("opt_go_fork", _RPG, "前进,去岔路口。"),
("opt_go_chest", _RPG, "往左,看看树洞。"),
("opt_go_stream", _RPG, "往右,去溪流。"),
("opt_go_hunter", _RPG, "前进,去猎人小屋。"),
("opt_go_deepwood", _RPG, "前进,进密林深处。"),
("opt_go_gate", _RPG, "前进,去寨门。"),
("opt_back", _RPG, "返回。"),
("opt_shop", _RPG, "去商店。"),
("opt_rest", _RPG, "休息一晚。"),
("opt_status", _RPG, "查看状态。"),
("opt_investigate", _RPG, "调查四周。"),
("opt_open_chest", _RPG, "打开树洞。"),
("opt_drink", _RPG, "饮水休息。"),
("opt_talk_hunter", _RPG, "和老猎人谈谈。"),
("opt_assault", _RPG, "强攻寨门。"),
("opt_sneak", _RPG, "绕行潜入。"),
("opt_leave", _RPG, "离开。"),
# ---- E 组 · 事件 ----
("evt_gold15", _RPG, "你在落叶下摸到十五枚金币。"),
("evt_nothing", _RPG, "这里什么都没有了。"),
("evt_chest", _RPG, "树洞里有两瓶金疮药,你收好了。"),
("evt_hunter_gift", _RPG, "老猎人塞给你一瓶金疮药,还说商店会给你算便宜些。"),
("evt_gate_open", _RPG, "寨门缓缓打开。"),
("evt_ch1_end", _RPG, "寨主倒了,但徒弟不在这里。一张字条,指向南方的腐沼。"),
# ---- B 组 · 战斗 ----
("btl_start_wolf", _RPG, "野狼拦路!"),
("btl_start_bandit", _RPG, "山贼围上来了!"),
("btl_start_spider", _RPG, "毒蛛从树上垂了下来!"),
("btl_start_elite", _RPG, "寨丁头目挡住了去路!"),
("btl_start_boss1", _RPG, "黑风寨主,就在你面前。"),
("cmd_attack", _RPG, "攻击。"),
("cmd_skill", _RPG, "破空斩。"),
("cmd_defend", _RPG, "防御。"),
("cmd_item", _RPG, "金疮药。"),
("btl_nomp", _RPG, "内劲不足。"),
("btl_noitem", _RPG, "没有金疮药了。"),
("btl_flee", _RPG, "你抽身退出了战斗。"),
("btl_noflee", _RPG, "避无可避!"),
("btl_half_bandit", _RPG, "山贼的攻势缓了下来。"),
("btl_enemy_die", _RPG, "敌人倒下了。"),
("btl_win", _RPG, "战斗胜利。"),
("btl_loot_small", _RPG, "小有收获。"),
("btl_loot_mid", _RPG, "收获不少。"),
("btl_loot_big", _RPG, "大获全胜,收获颇丰。"),
("btl_levelup", _RPG, "你的修为精进了。"),
("btl_lose", _RPG, "你倒下了,黑暗吞没了一切。"),
("btl_lose_hint", _RPG, "记住,听清前摇,双击格挡。"),
("tut_b1", _RPG, "它要扑了,听前摇!"),
("tut_b2", _RPG, "就是现在,双击格挡!"),
("tut_b3", _RPG, "挡住了!轮到你了。左右滑选攻击,单击出剑。"),
("tut_b4", _RPG, "没挡住也没关系,再来一次。"),
("tut_b5", _RPG, "好剑法!接下来,靠你自己了。"),
# ---- SH 组 · 商店 ----
("shop_open", _RPG, "商店。左右滑看看货,单击购买。"),
("shop_price_sword", _RPG, "铁剑,一百金。"),
("shop_price_armor", _RPG, "皮甲,八十金。"),
("shop_price_potion", _RPG, "金疮药,三十金。"),
("shop_owned", _RPG, "这件你已经有了。"),
("shop_poor", _RPG, "钱不够。"),
("shop_buy", _RPG, "买下了。"),
# ---- addendum §6.2 补录:敌方血量档位(旁白) ----
("foe_hp_high", _RPG, "敌人气焰正盛。"),
("foe_hp_mid", _RPG, "敌人露出疲态。"),
("foe_hp_low", _RPG, "敌人撑不住了。"),
("skip_hint", _RPG, "单击可跳过旁白。"),
# ---- R6 补录:首次战斗教学(用户反馈:首战必须讲清交互) ----
("tut_battle_1", _RPG, "第一场战斗来了。别慌,战斗是回合制:你先出手,敌人再反击。"),
("tut_battle_2", _RPG, "左右滑切换招式,攻击、破空斩、防御、金疮药。单击,出手。"),
("tut_battle_3", _RPG, "敌人出手前有一声前摇。听到前摇,马上双击,就能格挡。"),
("tut_battle_4", _RPG, "没把握就防御,伤害减半。金疮药可以回血。"),
("tut_battle_5", _RPG, "记住:听清前摇,双击格挡。开始吧!"),
# ---- R5 补录(11 文档新增台词,资产已先行落盘;staging 无 wav,convert 跳过)----
("tut_b0", _RPG, "战斗里,左右滑选招式,单击出手。听到这个声音,就是你的回合。先滑一滑,试试。"),
("tut_b6", _RPG, "来不及格挡,就选防御。左右滑,选防御,单击。"),
("btl_flee_fail", _RPG, "没甩掉!它追上来了。"),
("boss1_warn", _RPG, "里面就是黑风寨主。你现在回去练练再来,也来得及。"),
# ---- 向导层(docs/design/14-guide-layer.md,schemes §1.5 定稿全文)----
("gd_parry_miss", _RPG, "前摇声起的时候双击,才能格挡。"),
("gd_stall", _RPG, "想好了吗?左右滑动选择,单击出手。"),
# ---- round2 新增(docs/design/14-guide-layer.md「round2 v1.1」,synthesis §3 定稿全文)----
("tut_b2b", _RPG, "挡得漂亮。"),
("gd_wrap_first", _RPG, "回到第一个。"),
("gd_wrap_last", _RPG, "最后一个了。"),
]
# NPC 单音色平铺(id, speaker, instruct, 台词)→ web/rpg/audio/voice/<id>.*
# 角色 speaker+instruct 恒定(村长与寨主同 speaker 不同 instruct,从不在同场景出现)。
RPG_NPC = [
("npc_headman_1", "Uncle_Fu", "慈祥和缓的老者", "外乡人,林子里不太平。这五十金,拿着防身。"),
("npc_headman_2", "Uncle_Fu", "慈祥和缓的老者", "商店就在村里,缺什么就去看看。"),
("npc_headman_3", "Uncle_Fu", "慈祥和缓的老者", "听说你是用耳朵走路的剑客?保重啊。"),
("npc_headman_4", "Uncle_Fu", "慈祥和缓的老者", "救救那些被掳走的孩子吧。"),
("npc_hunter_1", "Eric", "沙哑粗犷,豪爽中带着暖意", "站住。……哦,是个用耳朵的。"),
("npc_hunter_2", "Eric", "沙哑粗犷,豪爽中带着暖意", "寨子里有我旧识。带上这个,别死在路上。"),
("npc_hunter_3", "Eric", "沙哑粗犷,豪爽中带着暖意", "黑风寨主的双刀很快,听清他出刀前的动静。"),
("npc_hunter_4", "Eric", "沙哑粗犷,豪爽中带着暖意", "去吧。让我听听,你的传说。"),
("npc_elite_1", "Dylan", "凶狠粗鲁,大声喝骂", "哪儿来的瞎子,敢闯黑风寨!"),
("npc_elite_half", "Dylan", "凶狠粗鲁,大声喝骂", "你……你到底是什么人!"),
("npc_elite_die", "Dylan", "凶狠粗鲁,大声喝骂", "寨主……会替我报仇……"),
("npc_boss1_1", "Uncle_Fu", "低沉阴狠,慢条斯理", "盲剑客?正好,省得挖你的眼睛。"),
("npc_boss1_2", "Uncle_Fu", "低沉阴狠,慢条斯理", "有两下子。可惜,你听不懂死期的声音。"),
("npc_boss1_die", "Uncle_Fu", "低沉阴狠,慢条斯理", "白无瞳……会找到你……"),
]
NPCS = {"rpg": RPG_NPC}
# ---------------------------------------------------------------- radio
# 《不眠频率》批次 1(docs/design/15-radio-voice-ids.md,132 句:双性别 103 + 平铺 NPC 29)。
# 台词全文由提取脚本逐字照抄 voice-ids 文档;双性别组分 HOST(主播)/ PROD(导播/系统)
# 两个 speaker 配对(同性别双音色,VOICE_SPK 覆盖);NPC 单音色平铺(RADIO_NPC)+ 通话带通(NPC_BANDPASS)。
_RADIO_HOST = _pair("低沉温和的中年男声,深夜电台主播,沉稳亲切,有磁性",
"温暖清晰的年轻女声,深夜电台主播,沉稳亲切,不甜腻")
_RADIO_PROD = _pair("贴近耳机的低语男声,年轻电台导播,冷静克制",
"贴近耳机的低语女声,年轻电台导播,冷静克制")
RADIO_HOST_LINES = [
("b_open", _RADIO_HOST, "午夜零点,红灯亮起。欢迎回到《今夜不眠》。"),
("b_rain", _RADIO_HOST, "雨下了一整晚。热线灯,马上就要亮了。"),
("z1_song", _RADIO_HOST, "大爷别急,这首歌我也会。咱们一句一句来。"),
("z1_ask", _RADIO_HOST, "大爷,路口有什么招牌?念一个给我听听。"),
("z1_stay", _RADIO_HOST, "哪儿也不去。我陪你,这首歌还长着呢。"),
("z1_pol", _RADIO_HOST, "大爷,我把您转给派出所,他们马上就到。"),
("l1_go", _RADIO_HOST, "你妈等的不是奖杯,是你进门喊的那声妈。"),
("l1_free", _RADIO_HOST, "退不退,都是你的自由。夜还长,慢慢想。"),
("k1_lull", _RADIO_HOST, "闭上眼睛。风把云朵盖在被子上,月亮替你值夜班。"),
("k1_ask", _RADIO_HOST, "睡不着的时候,心里是不是装着事?说给我听听。"),
("k1_truth", _RADIO_HOST, "糖糖,记住,大人的事,不是小孩的错。一个字都不是。"),
("k1_lie", _RADIO_HOST, "睡吧。明天一睁眼,什么都会好起来的。"),
("w1_say", _RADIO_HOST, "四十分钟前,有个孩子问我们:爸妈摔门,是不是她的错。"),
("w1_say2", _RADIO_HOST, "今天有位小朋友,还在等爸妈回家切蛋糕。"),
("w1_air", _RADIO_HOST, "陈姐,别挂。让他接电话——全城都在听。"),
("w1_priv", _RADIO_HOST, "这里只有我们俩了。想说就说,想哭就哭。"),
("w1_home", _RADIO_HOST, "吵架隔着电话吵不赢。回家吧,蛋糕别放到坏了。"),
("h1_talk", _RADIO_HOST, "说说她。开学第一天,她跟你说的头一句话,是什么?"),
("h1_stop", _RADIO_HOST, "听我的,前面三公里有服务区。学费急,命更急。"),
("d9_face", _RADIO_HOST, "师父,三年了。这句晚安,我替您播。各位,晚安。"),
("d9_avoid", _RADIO_HOST, "各位,今晚到这里。明晚零点,今夜不眠,不见不散。"),
("e1_a", _RADIO_HOST, "天亮了,热线灯还亮着。你们的晚安,是这座城市的心跳。"),
("e1_b", _RADIO_HOST, "这把话筒,我总算接得住了。明晚零点,接着不眠。"),
("e2_a", _RADIO_HOST, "今晚的热线烧得发烫。全城的失眠者,都被接住了。"),
("e2_b", _RADIO_HOST, "只是路过留言机的时候,你还是没敢看它。红灯灭得很慢。"),
("e3_a", _RADIO_HOST, "口碑掉了一夜。可那句迟到三年的晚安,今晚说出去了。"),
("e3_b", _RADIO_HOST, "一个人的频道,也是频道。师父,收到了吗?"),
("e4_a", _RADIO_HOST, "今夜不眠,静音收播。有的夜班,谁也接不住谁。"),
("e4_b", _RADIO_HOST, "留言机还在闪。你没看它,它也没怪你。明晚再说吧。"),
]
RADIO_PROD_LINES = [
("gd_brief", _RADIO_PROD, "单击继续节目。请选择的时候,向左或向右滑。"),
("gd_continue", _RADIO_PROD, "单击屏幕,继续。"),
("gd_again", _RADIO_PROD, "还在吗?单击继续。"),
("gd_invalid", _RADIO_PROD, "这个手势,直播间里用不上。"),
("gd_ring1", _RADIO_PROD, "热线在闪。单击,接听。"),
("gd_ring2", _RADIO_PROD, "灯还亮着,听众在等。单击,接听。"),
("sys_help", _RADIO_PROD, "单击继续,双击重新听。上滑听状态,下滑开菜单。请选择时,左右滑。"),
("sys_menu", _RADIO_PROD, "菜单。单击切换选项,双击确认。"),
("sys_menu_resume", _RADIO_PROD, "回到直播。"),
("sys_menu_restart", _RADIO_PROD, "重新开始今晚。"),
("sys_menu_lobby", _RADIO_PROD, "保存并回大厅。"),
("sys_settings", _RADIO_PROD, "音色、语速、背景音乐,请回大厅修改。"),
("sys_skip", _RADIO_PROD, "想跳过这句,单击就行。"),
("sys_continue_ask", _RADIO_PROD, "检测到你的夜班记录。单击继续上次,右滑开始新的夜晚。"),
("sys_new_night", _RADIO_PROD, "新的夜晚,开始了。"),
("sys_saved", _RADIO_PROD, "进度已保存。"),
("prog_c1", _RADIO_PROD, "现在是今晚第一通电话。"),
("prog_c2", _RADIO_PROD, "现在是今晚第二通电话。"),
("prog_c3", _RADIO_PROD, "现在是今晚第三通电话。"),
("prog_c4", _RADIO_PROD, "现在是今晚第四通电话。"),
("prog_c5", _RADIO_PROD, "现在是今晚第五通电话。"),
("rep_high", _RADIO_PROD, "今晚的口碑,在听众里立住了。"),
("rep_mid", _RADIO_PROD, "今晚的口碑,不好不坏。"),
("rep_low", _RADIO_PROD, "今晚的口碑,还没立起来。"),
("b_rule", _RADIO_PROD, "台里的规矩就一条:先接住人,再接电话。"),
("b_goal", _RADIO_PROD, "今晚五通电话。说什么话,由你决定。"),
("z1_ring_in", _RADIO_PROD, "一线来电。七十多岁,雨夜,走丢了。"),
("p1", _RADIO_PROD, "老人在发抖。向左滑,陪他哼那首老歌。向右滑,冷静问他路口的路牌。"),
("z1_sdone", _RADIO_PROD, "他稳住了。老歌,比路牌管用。"),
("z1_adone", _RADIO_PROD, "他慌了。先接住情绪,再走流程。"),
("p2", _RADIO_PROD, "雨声更密了。向左滑,留在原地陪他等巡逻队。向右滑,转接派出所。"),
("z1_staye", _RADIO_PROD, "十五分钟后,巡逻灯停在路口。全城听见老人笑了。"),
("z1_pole", _RADIO_PROD, "转接完成,地址报得清。干净利落。"),
("z1_bade", _RADIO_PROD, "地址没报清就转接,雨又大。这通砸了。先稳人,再办事。"),
("z1_ok1", _RADIO_PROD, "就这么接。记住这个手感。"),
("l1_in", _RADIO_PROD, "二线来电。姑娘,声音很轻,像怕吵到谁。"),
("p3", _RADIO_PROD, "向左滑,告诉她这趟车值得上。向右滑,把选择还给她。"),
("l1_goe", _RADIO_PROD, "二线收工。这句话,值一个头条。"),
("l1_freee", _RADIO_PROD, "她走了。没上车,也没退票。年轻人的夜,长了点。"),
("k1_in", _RADIO_PROD, "三线来电。听呼吸,是个小孩。"),
("p4", _RADIO_PROD, "向左滑,唱一段摇篮曲哄她睡。向右滑,轻轻问她怎么了。"),
("k1_lulle", _RADIO_PROD, "睡着了。可你没听见,她在怕什么。"),
("p5", _RADIO_PROD, "孩子的声音在抖。向左滑,告诉她这不是她的错。向右滑,答应她明天都会好。"),
("k1_truthe", _RADIO_PROD, "蛋糕。记下来——四线的来电,可能跟这个家有关。"),
("k1_liee", _RADIO_PROD, "你许了个兑不了的现。孩子睡了,谎留下了。"),
("w1_in", _RADIO_PROD, "四线来电。户外风声大,是位大姐。"),
("p6", _RADIO_PROD, "摔门声你听见了。向左滑,替孩子给她带句话。向右滑,劝她回家把话说开。"),
("w1_saye", _RADIO_PROD, "她攥着电话,很久没出声。风都停了。"),
("w1_say2e", _RADIO_PROD, "话递到了。就差最后一脚。"),
("p7", _RADIO_PROD, "她的呼吸平了。向左滑,留在直播里,让全城等这个门铃。向右滑,换垫乐,私下陪她说完。"),
("w1_aire", _RADIO_PROD, "电话那头,传来跑步声。今晚的高光时刻。"),
("w1_prive", _RADIO_PROD, "灯牌是你亲手关的,故事还是完整的。稳。"),
("w1_homee", _RADIO_PROD, "她往回走了。成不成,看他们自己的了。"),
("h1_in", _RADIO_PROD, "五线来电。引擎声没停过——他在开车!"),
("p8", _RADIO_PROD, "方向盘比话筒危险。向左滑,陪他聊聊闺女。向右滑,让他立刻进服务区。"),
("h1_danger", _RADIO_PROD, "车道偏了——听!"),
("h1_talke", _RADIO_PROD, "吓醒的,也算醒。这一趟,聊到家。"),
("h1_stope", _RADIO_PROD, "他下了高速。单子黄了,人齐了。"),
("h1_echo", _RADIO_PROD, "对了。头通电话那位大爷,就是这片被巡逻队找到的。这城市的夜,是接着的。"),
("g9_in", _RADIO_PROD, "五线收工。垫乐起——零点档,还剩最后一段。"),
("p9", _RADIO_PROD, "留言机红灯在闪,是老主播三年前没播完的那句晚安。向左滑,替他播出去。向右滑,照常收播。"),
("d9_facee", _RADIO_PROD, "带子里其实只有电流声。但空着的那一秒,你说满了。"),
("d9_avoide", _RADIO_PROD, "留言机的红灯,又多亮了一夜。"),
("ep_lead", _RADIO_PROD, "收播了。导播间替你记了一笔账——今晚的人都怎样了。"),
("ep_zhou_ok", _RADIO_PROD, "老周:孙子替他买了老花镜。回信说,谢谢那首歌。"),
("ep_zhou_no", _RADIO_PROD, "老周在便利店屋檐下坐到天亮。没出事,也没人听见他的歌。"),
("ep_deer_ok", _RADIO_PROD, "小鹿上车了。她妈后来打电话说,那声妈,比奖杯值钱。"),
("ep_deer_no", _RADIO_PROD, "小鹿退了票。检票口的灯,记得那趟没上成的车。"),
("ep_kid_ok", _RADIO_PROD, "糖糖家的门,这个月没再摔过。她说这是主播魔法。"),
("ep_kid_no", _RADIO_PROD, "糖糖后来又打来过一次。还是那句:楼下在吵。"),
("ep_wife_ok", _RADIO_PROD, "陈姐家切了蛋糕。那句「回来吧」,是我这晚听过最好的声音。"),
("ep_wife_no", _RADIO_PROD, "陈姐在公车站坐到了末班车。那通电话,成了没寄出的信。"),
("ep_han_ok", _RADIO_PROD, "老韩到家了。后视镜上,多了朵小红花。"),
("ep_han_no", _RADIO_PROD, "老韩在服务区睡了一觉。学费齐了,那首歌没放成。"),
]
RADIO_LINES = RADIO_HOST_LINES + RADIO_PROD_LINES
# NPC 单音色平铺(id, speaker, instruct, 台词)→ web/radio/audio/voice/<id>.*
#(转码时按 15-radio.md §6.2 逐来电者带通,见 NPC_BANDPASS;staging 保留未滤波原声)。
RADIO_NPC = [
("z1_a", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "主播……我,我找不到回家的路了。"),
("z1_b", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "雨太大了。我本来,只想买副老花镜。"),
("z1_songo", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "对,对!路口修车铺,天天放这首!"),
("z1_asko", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "招牌……雨糊住了,我眼花,看不清啊……"),
("z1_stayo", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "好,好。有人说话,我就不怕了。"),
("z1_polo", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "好,好。谢谢你啊,谢谢。"),
("z1_bad", "Uncle_Fu", "苍老沙哑的男声,七十五岁,疲惫害怕,带着雨夜的颤抖", "说不清……我真是,说不清了……"),
("l1_a", "Serena", "年轻女声,倔强,带着哭腔,疲惫", "主播,退票窗口十一点半就关了。我站这儿半小时了。"),
("l1_b", "Serena", "年轻女声,倔强,带着哭腔,疲惫", "票是回去看我妈的。可我试镜又黄了,两手空空……"),
("l1_goo", "Serena", "年轻女声,倔强,带着哭腔,疲惫", "呜……嗯。不退了,我现在就去检票。谢谢您。"),
("l1_freeo", "Serena", "年轻女声,倔强,带着哭腔,疲惫", "嗯……我再想想。窗口要关了,我先过去了。"),
("k1_a", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "喂……有人吗?我睡不着。"),
("k1_b", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "家里灯关了。可是楼下,一直有声音。"),
("k1_lullo", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "月亮也睡了……那我也睡。"),
("k1_asko", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "爸爸妈妈摔门了。是不是……我做错了什么呀。"),
("k1_trutho", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "嗯……那我睡了。他们明天,会一起给我切蛋糕吗?"),
("k1_lieo", "Vivian", "稚嫩童声,八岁小女孩,困倦,胆怯", "真的吗?拉钩。晚安……"),
("w1_a", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "主播,我就想找个人说说。我家那口子,今晚要出远门的活儿。"),
("w1_b", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "明天孩子生日!蛋糕都订好了,他人要今晚走!"),
("w1_sayo", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "什么?她还没睡?这么晚了,她还醒着?"),
("w1_say2o", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "蛋糕……是他订的。他嘴上不说,心里都记着。"),
("w1_airo", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "喂!回来吧!蛋糕要化了,孩子等着呢!"),
("w1_privq", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "呜……其实他订完蛋糕就跟我道歉了。是我,把门摔得太响。"),
("w1_homeo", "Vivian", "疲惫沙哑的中年女声,四十五岁,强撑着平静", "嗯……我这就往回走。谢谢你,听我瞎嚷嚷。"),
("h1_a", "Eric", "沙哑粗犷的中年男声,货车司机,困倦强撑", "主播,别挂。陪我说话,我的眼皮,沉得很。"),
("h1_b", "Eric", "沙哑粗犷的中年男声,货车司机,困倦强撑", "跑完这趟,闺女的学费就齐了。雨刷都快刮不动了。"),
("h1_talko", "Eric", "沙哑粗犷的中年男声,货车司机,困倦强撑", "她说,爸,我在学校给你赚了朵小红花。哈哈,哈哈……"),
("h1_dango", "Eric", "沙哑粗犷的中年男声,货车司机,困倦强撑", "嘶——好险!前车急刹,我擦着护栏过去了。彻底醒了!"),
("h1_stopo", "Eric", "沙哑粗犷的中年男声,货车司机,困倦强撑", "行,听你的。进了服务区,我给你们放首歌听。"),
]
# ---------------------------------------------------------------- dungeon
# 《回声地窟》批次 1(docs/design/16-dungeon-voice-ids.md,125 句 ×2 性别,无 NPC);
# R3 起 +2(E1-2 gold_some / 打磨 tut_flee_hold)= 127 句,st_best_2 措辞重录。
_DUN = _pair("低沉醇厚的男声,像探窟向导,沉稳有力",
"平静清晰的女声,像探窟向导,沉稳可信")
DUNGEON_LINES = [
("intro_1", _DUN, "你在黑暗中醒来。这里是回声地窟。"),
("intro_2", _DUN, "伸手不见五指,声音就是你的眼睛。"),
("sys_continue_ask", _DUN, "检测到探窟记录。单击继续,右滑重新开始。"),
("sys_new_run", _DUN, "新的探窟,开始了。"),
("tut_d0", _DUN, "先学会用耳朵看。单击,发出声波脉冲。"),
("tut_echo_come", _DUN, "听,回声回来了。"),
("tut_front_rule", _DUN, "回声回来越快,墙就越近。"),
("tut_side_rule", _DUN, "两侧的回声,发闷是墙,空旷是通路。"),
("tut_walk", _DUN, "按住屏幕不放,你就往前走。"),
("tut_turn", _DUN, "左滑右滑,可以转身。"),
("tut_replay", _DUN, "双击,再发一次脉冲,不要钱。"),
("tut_status", _DUN, "上滑,随时听自己的状态。"),
("tut_menu", _DUN, "下滑,打开菜单。"),
("tut_mob", _DUN, "前面有东西!放心,它伤不到你。"),
("tut_parry", _DUN, "听到它的动静,双击格挡。"),
("tut_parry_ok", _DUN, "挡住了!"),
("tut_parry_miss", _DUN, "没挡住也别怕,它伤不到你。"),
("tut_attack", _DUN, "现在单击,挥拳打它。"),
("tut_kill", _DUN, "打中了,就是这样。"),
("tut_flee_hold", _DUN, "想逃跑,按住别松手。"), # R3 打磨:战斗短长按取消逃跑时的区分提示(每场 1 次)
("tut_stairs", _DUN, "铁器的回声,就是下潜的楼梯。"),
("tut_stairs_on", _DUN, "楼梯就在脚下。单击,下潜。"),
("tut_done", _DUN, "教学完了。地窟很深,小心走。"),
("tut_skip", _DUN, "已跳过教学。单击脉冲,先听听四周。"),
("sys_saved", _DUN, "进度已保存。"),
("sys_menu", _DUN, "菜单。单击切换,双击确认。"),
("sys_menu_resume", _DUN, "返回探索。"),
("sys_menu_save", _DUN, "保存并回大厅。"),
("sys_menu_help", _DUN, "重新听帮助。"),
("sys_menu_restart", _DUN, "重新开始本周目。"),
("help_explore", _DUN, "单击脉冲,按住前进,左右滑转身。双击再听,上滑状态,下滑菜单。"),
("help_battle", _DUN, "敌人前摇声响时双击格挡,单击挥击。打不过就按住屏幕逃跑。"),
("settings_hint", _DUN, "音色、语速、背景音乐,请回大厅修改。"),
("st_floor_1", _DUN, "这是第一层。"),
("st_floor_2", _DUN, "这是第二层。"),
("st_floor_3", _DUN, "这是第三层。"),
("st_floor_4", _DUN, "这是第四层。"),
("st_floor_5", _DUN, "这是第五层。"),
("st_floor_6", _DUN, "这是第六层。"),
("st_floor_7", _DUN, "这是第七层。"),
("st_hp_1", _DUN, "你毫发无损。"),
("st_hp_2", _DUN, "你受了点伤。"),
("st_hp_3", _DUN, "你伤势不轻。"),
("st_hp_4", _DUN, "你命悬一线。"),
("st_gold_1", _DUN, "口袋空空。"),
("st_gold_2", _DUN, "有些积蓄。"),
("st_gold_3", _DUN, "收获可观。"),
("st_gold_4", _DUN, "腰包鼓鼓。"),
# R3 E1-2:金币档 1(0<金<50)新真话句——修「捡到金币→口袋空空/这趟没带回什么」
# 的档位谎报(P2/P4 共识);=0 仍播 st_gold_1/meta_gold_1 空手措辞。
# 上滑状态链与 SETTLE/TITLE(元进度)三处共用这一条(管线单条)。
("gold_some", _DUN, "带回来一点,不多。"),
("st_face_e", _DUN, "你面朝东。"),
("st_face_s", _DUN, "你面朝南。"),
("st_face_w", _DUN, "你面朝西。"),
("st_face_n", _DUN, "你面朝北。"),
("st_threat_none", _DUN, "附近很安静。"),
("st_threat_left", _DUN, "左边有脚步声。"),
("st_threat_right", _DUN, "右边有脚步声。"),
("st_threat_both", _DUN, "两边都有动静。"),
("st_best_1", _DUN, "你还没到过第三层。"),
("st_best_2", _DUN, "你最远到过中层。"), # R3 打磨措辞:加「最远」(live P3:防误读为"本局到过")
("st_best_3", _DUN, "你到过最深层。"),
("evt_coin_1", _DUN, "捡到几枚金币。"),
("evt_coin_2", _DUN, "捡到一小把金币。"),
("evt_coin_3", _DUN, "捡到一大袋金币。"),
("evt_potion", _DUN, "一瓶药水,收进腰带。"),
("evt_potion_full", _DUN, "腰带满了,药水留在原地。"),
("evt_weapon", _DUN, "捡到更快的短刃,出手更狠了。"),
("evt_armor", _DUN, "换上更硬的护甲,更抗打了。"),
("evt_stairs_lock", _DUN, "楼梯被大东西堵住了,先赢它。"),
("evt_descend", _DUN, "你顺着楼梯,往下潜。"),
("btl_bat", _DUN, "盲眼蝠扑了过来!"),
("btl_rat", _DUN, "窟鼠窜了出来!"),
("btl_mush", _DUN, "蘑菇怪拦住了你!"),
("btl_crab", _DUN, "窟主石甲蟹,就在你面前。"),
("btl_crawler", _DUN, "绕行者贴上来了,小心背后!"),
("btl_spider", _DUN, "洞蛛落地了!"),
("btl_leech", _DUN, "窟主腐沼蟒,挡住了楼梯。"),
("btl_guard", _DUN, "石像卫醒了!"),
("btl_king", _DUN, "窟中之王,就在深处。"),
("btl_parried", _DUN, "挡下了大半!"),
("btl_behind", _DUN, "它在你背后,转身再挡。"),
("btl_swish", _DUN, "挥空了,它不在你面前。"),
("btl_no_turn", _DUN, "它贴着你不放,不用转身。"),
("btl_kill", _DUN, "它倒下了。"),
("btl_loot_1", _DUN, "战利不多。"),
("btl_loot_2", _DUN, "小有收获。"),
("btl_loot_3", _DUN, "大有收获。"),
("btl_lvup", _DUN, "你的拳脚更利落了。"),
("btl_flee", _DUN, "你甩掉了它,被抓了一下。"),
("btl_flee_boss", _DUN, "退路塌了,跑不掉!"),
("btl_half_crab", _DUN, "石甲蟹的甲裂了!"),
("btl_half_leech", _DUN, "腐沼蟒暴怒了!"),
("btl_half_king", _DUN, "王怒了,脚步更重了!"),
("die_intro", _DUN, "你倒下了。"),
("die_cause_1", _DUN, "你被小怪拖垮了。"),
("die_cause_2", _DUN, "你没挡住那记重击。"),
("die_cause_3", _DUN, "你背后的偷袭,要了命。"),
("die_adv_1", _DUN, "下次,前摇声响时就双击。"),
("die_adv_2", _DUN, "打不过,就按住屏幕逃跑。"),
("die_adv_3", _DUN, "身后有动静,先转身。"),
("gd_pulse", _DUN, "单击脉冲,听听四周。"),
("gd_turn", _DUN, "轮到你了。单击挥击。"),
("gd_invalid", _DUN, "这个操作在这里用不上。"),
("stair_hint_a", _DUN, "找找铁器的回声,那是楼梯。"),
("stair_hint_b", _DUN, "楼梯有大东西守着,先赢它。"),
("stair_dir_e", _DUN, "楼梯在东边。"),
("stair_dir_s", _DUN, "楼梯在南边。"),
("stair_dir_w", _DUN, "楼梯在西边。"),
("stair_dir_n", _DUN, "楼梯在北边。"),
("nav_e", _DUN, "往东。"),
("nav_s", _DUN, "往南。"),
("nav_w", _DUN, "往西。"),
("nav_n", _DUN, "往北。"),
("nav_done", _DUN, "到了,就是这里。"),
("meta_gold_1", _DUN, "这趟没带回什么。"),
("meta_gold_2", _DUN, "这趟带回一些金币。"),
("meta_gold_3", _DUN, "这趟满载而归。"),
("settle_stronger", _DUN, "下个周目,你会更强。"),
("settle_win_1", _DUN, "你捧起了地窟之心,通关了!"),
("settle_win_2", _DUN, "黑暗的尽头,是光。"),
("settle_again", _DUN, "单击,再探一次。"),
("unlock_lead", _DUN, "攒够了新的遗产。"),
("unlock_potion", _DUN, "解锁行囊药,开局自带一瓶。"),
("unlock_vigor", _DUN, "解锁体魄,起始更强壮。"),
("unlock_veil", _DUN, "解锁护符,每层头一下减半。"),
("unlock_echo", _DUN, "解锁回声强化,侧向听得更远。"),
("unlock_blade", _DUN, "解锁武器,开局带着短刃。"),
("unlock_none", _DUN, "金币还不够,继续攒。"),
]
# ---------------------------------------------------------------- abyss
# 《深海回音》批次 2(docs/design/17-abyss-voice-ids.md,155 句:双性别 134 + 平铺 NPC 21;
# R2 修订 +7 句:dv4_dual / oxy_crit_deck / prog_saved1..5,sys_skip(_k) 改词)。
# 台词全文逐字照抄 voice-ids 文档;双性别组分 DIVER(潜航员=玩家)/ DECK(母船调度/系统/
# 向导 gd_*)两个 speaker 配对(同性别双音色,VOICE_SPK["abyss"] 覆盖,radio HOST/PROD 同型:
# 同场景高频对话的两者永不同 speaker);_k 键盘变体 11 对在 KEY_LINES["abyss"]。
# NPC 平铺 21 句(小舟/老白/陈医官/高船长),转码时按 17-abyss.md §5.3 水声电台带通
# (NPC_BANDPASS["abyss"]),DIVER/DECK 不滤波(母船级音质)。
_ABY_DIVER = _pair("低沉冷静的男声,深潜救援员,干练沉着,水下通讯的轻微气声",
"低沉冷静的年轻女声,深潜救援员,干练沉着,水下通讯的轻微气声")
_ABY_DECK = _pair("清晰稳定的男声,母船潜水调度员,专业干脆,隔着耳机下达指令",
"清晰稳定的女声,母船潜水调度员,专业干脆,隔着耳机下达指令")
ABYSS_DIVER_LINES = [
# ---- G1 开场 ----
("b_open", _ABY_DIVER, "风停了。我站在问海号甲板上,下面三千米,是海燕号。"),
# ---- D1 第一潜·找船·小舟 ----
("d1_listen", _ABY_DIVER, "都别出声。管子那头,敲得很稳。"),
("d1_up", _ABY_DIVER, "小舟,抓住我的肩锁。我们上。"),
("d1_nolog", _ABY_DIVER, "日志可以补,人不能等。走了。"),
# ---- D2 第二潜·机舱·老白 ----
("d2_pry", _ABY_DIVER, "老白,忍住。先泄压,再撬。"),
("d2_cut", _ABY_DIVER, "切割枪。老白,闭眼,憋气。"),
("d2_valve", _ABY_DIVER, "电池舱我陪你去。这片海不能脏。"),
("d2_leave", _ABY_DIVER, "阀门交给母船。你先上,腿要紧。"),
# ---- D3 第三潜·病舱·陈医官/小郑 ----
("d3_chen", _ABY_DIVER, "陈医官,你的手在抖。这次,听我的。"),
("d3_zheng", _ABY_DIVER, "小郑先走。陈医官,下一潜,就是你的。"),
("d3_spare", _ABY_DIVER, "备用瓶留下。灯,给我亮着。"),
("d3_promise", _ABY_DIVER, "四小时内,我下来。等我。"),
# ---- D4 第四潜·指挥舱·高船长 ----
("d4_slow", _ABY_DIVER, "逐站停,慢慢浮。规矩不能破。"),
("d4_fast", _ABY_DIVER, "来不及了。直浮!"),
("d4_pick_gao", _ABY_DIVER, "船长,抓住我。海燕号的事,上面再说。"),
("d4_pick_chen", _ABY_DIVER, "陈医官,轮到你了。抓住,上浮。"),
("d4_pick_zheng", _ABY_DIVER, "小郑,吊床连人一起。走了。"),
("d4_talk", _ABY_DIVER, "日志我听完了。海燕号为什么沉,我会带上去。走吧。"),
("d4_silent", _ABY_DIVER, "纪律就是纪律。抓住,上浮。"),
# ---- F9 签收抉择(心结) ----
("g9_knot", _ABY_DIVER, "三年前,也是这条沟。我按时上浮了,搭档没有。"),
("d9_bell", _ABY_DIVER, "最后一潜。不为救人,为个交代。"),
("d9_rest", _ABY_DIVER, "交代,留给明天的人。我去医疗舱。"),
# ---- EP 结局与尾声回响 ----
("e1_a", _ABY_DIVER, "五个人都在甲板上。船钟的第一声,是替这条沟敲的。"),
("e1_b", _ABY_DIVER, "三年了,我终于敢往下看。明天,想再潜一次。"),
("e2_a", _ABY_DIVER, "五个人都救上来了。船钟,留在了沟底。"),
("e2_b", _ABY_DIVER, "没人怪我。可半夜醒来,总听见钟在响。"),
("e3_a", _ABY_DIVER, "没能都带回来。可我到底下去了,敲响了那口钟。"),
("e3_b", _ABY_DIVER, "钟声替他们,在沟底响了一声。这样,也好。"),
("e4_a", _ABY_DIVER, "我按规矩上浮了。规矩里,没写怎么难过。"),
("e4_b", _ABY_DIVER, "沟底很安静。安静得,像三年前那天。"),
]
ABYSS_DECK_LINES = [
# ---- G0.a 操作提示句(触屏版;_k 变体在 KEY_LINES) ----
("gd_brief", _ABY_DECK, "单击继续简报。要选择时,向左或向右滑。"),
("gd_continue", _ABY_DECK, "单击屏幕,继续。"),
("gd_again", _ABY_DECK, "还在吗?单击继续。"),
("gd_ping1", _ABY_DECK, "声呐待命。单击,发脉冲。"),
("gd_ping2", _ABY_DECK, "回波不会自己来。单击。"),
("sys_help", _ABY_DECK, "单击继续,双击重新听。上滑听状态,下滑开菜单。选择时,左右滑。"),
("sys_menu", _ABY_DECK, "菜单。单击切换选项,双击确认。"),
("sys_skip", _ABY_DECK, "听过的句子再播时,单击就能跳过。"),
("sys_continue_ask", _ABY_DECK, "检测到你的下潜记录。单击继续上次,右滑开始新救援。"),
# ---- G0.b 状态/系统句 ----
("gd_invalid", _ABY_DECK, "这个手势,水里用不上。"),
("gd_prog1", _ABY_DECK, "滑动在这里用不上。想想调度刚说的话。"),
("gd_prog2", _ABY_DECK, "别乱。停一下,想想下一步。"),
("sys_menu_resume", _ABY_DECK, "回到水下。"),
("sys_menu_restart", _ABY_DECK, "重新开始这次救援。"),
("sys_menu_lobby", _ABY_DECK, "保存并回大厅。"),
("sys_settings", _ABY_DECK, "音色、语速、背景音乐,请回大厅修改。"),
("sys_new_dive", _ABY_DECK, "新的一次下潜,开始了。"),
("prog_d1", _ABY_DECK, "现在是第一次下潜。"),
("prog_d2", _ABY_DECK, "现在是第二次下潜。"),
("prog_d3", _ABY_DECK, "现在是第三次下潜。"),
("prog_d4", _ABY_DECK, "现在是最后一次下潜。"),
# R2-F10:上滑状态链·已救人数(saved=1..5 分档整句;saved=0 不播)
("prog_saved1", _ABY_DECK, "已经救上一个人。"),
("prog_saved2", _ABY_DECK, "已经救上两个人。"),
("prog_saved3", _ABY_DECK, "已经救上三个人。"),
("prog_saved4", _ABY_DECK, "已经救上四个人。"),
("prog_saved5", _ABY_DECK, "五个人都在甲板上了。"),
("oxy_full", _ABY_DECK, "气瓶还足,救得动。"),
("oxy_ok", _ABY_DECK, "氧气还够两潜。"),
("oxy_low", _ABY_DECK, "氧气告急,只够最后一潜。"),
("oxy_crit", _ABY_DECK, "氧气见底,必须上浮。"),
# R2-F5:crit 水面变体(SURFACE/ENDING;水下保留「必须上浮」)
("oxy_crit_deck", _ABY_DECK, "氧气见底,一潜都下不去了。"),
("oxy_gain", _ABY_DECK, "应急瓶接上了,多三格。"),
("dry_a", _ABY_DECK, "气瓶用干了。立刻上浮。"),
("dry_b", _ABY_DECK, "气花在了人身上。人,都上来了。"),
# ---- G1 开场 ----
("b_rule", _ABY_DECK, "规矩就一条:气不够一潜,人就必须上浮。"),
("b_goal", _ABY_DECK, "海燕号,五个人。你的双瓶,够下四潜。"),
("b_go", _ABY_DECK, "第一潜,找船。耳朵交给声呐。"),
# ---- D1 第一潜 ----
("dv1_down", _ABY_DECK, "下潜开始。水会越来越黑,声音会越来越多。"),
("dv1_teach", _ABY_DECK, "到深度了。声呐静了。单击,发一枚脉冲。"),
("dv1_metal", _ABY_DECK, "金属回波,钢板。听见这个,就是海燕号的船身。"),
("dv1_link", _ABY_DECK, "回波后面有杂音,是电台!接通了。"),
("p1", _ABY_DECK, "管路在响。向左滑,停下来听敲管的暗号。向右滑,先带小舟上浮。"),
("d1_code", _ABY_DECK, "是老白的暗号:腿被压住,先泄压,再撬。记住这个敲法。"),
("p2", _ABY_DECK, "小舟想回去拿声呐日志。向左滑,等她。向右滑,直接上浮。"),
("d1_surf", _ABY_DECK, "小舟出水,第一潜收工。往返,扣三格气。"),
# ---- D2 第二潜 ----
("dv2_down", _ABY_DECK, "第二潜,机舱。顺着敲管的声音走。"),
("dv2_found", _ABY_DECK, "碎石回波后面,敲管声密了。机舱到了。"),
("p3", _ABY_DECK, "钢梁压着腿。向左滑,泄压后慢慢撬。向右滑,切割枪,快切。"),
("d2_pry_ok", _ABY_DECK, "暗号没白听。管子一松,钢梁抬起来了。"),
("d2_pry_slow", _ABY_DECK, "摸着找泄压阀,多费了一格气。腿,出来了。"),
("d2_flash", _ABY_DECK, "火花炸了一下,虚惊。老白咳着,腿抽出来了。"),
("p4", _ABY_DECK, "老白要去关电池舱阀门。向左滑,陪他去。向右滑,直接上浮。"),
("d2_valve_e", _ABY_DECK, "阀门关死了。两格气,换一片干净海。"),
("d2_surf", _ABY_DECK, "老白出水。他一直扒着舱门,往水里看。"),
("d2_supply", _ABY_DECK, "潜友阿凯捎来应急瓶。甲板上,粥给你温着。"),
# ---- D3 第三潜 ----
("dv3_down", _ABY_DECK, "第三潜,病舱。海燕号在倾斜,别恋战。"),
("dv3_groan", _ABY_DECK, "回波发闷。船体在呻吟,它撑不久了。"),
("p5", _ABY_DECK, "病舱一次只能走一个。向左滑,先带陈医官。向右滑,先带小郑。"),
("d3_chen_e", _ABY_DECK, "陈医官出水。小郑固定在吊床上,呼吸平稳。"),
("d3_zheng_e", _ABY_DECK, "小郑吊上去了。他一路,都没敢出声。"),
("p6", _ABY_DECK, "留守的人要空气。向左滑,留下你的备用瓶。向右滑,约定下一潜来接。"),
("d3_spare_e", _ABY_DECK, "备用瓶挂上了,能多撑十二小时。"),
("d3_surf", _ABY_DECK, "病舱的人出水。海燕号的呻吟,隔着水都听得见。"),
# ---- D4 第四潜 ----
("dv4_none", _ABY_DECK, "气不够一潜了。第四潜,取消。"),
("dv4_down", _ABY_DECK, "最后一潜,指挥舱。海燕号,撑不住了。"),
# R2-F4:双载形态下潜链追加(3+1=4 格扣账口播化,不再无声扣款)
("dv4_dual", _ABY_DECK, "两个人下同一潜。这趟往返,扣四格气。"),
("dv4_slide", _ABY_DECK, "它滑了!抓稳索缆,指挥舱里还有人!"),
("p7", _ABY_DECK, "双人上浮。向左滑,逐站减压慢慢浮。向右滑,违规直浮,搏一把。"),
("p7_chen", _ABY_DECK, "气只够带一个。向左滑,带船长。向右滑,带陈医官。"),
("p7_zheng", _ABY_DECK, "气只够带一个。向左滑,带船长。向右滑,带小郑。"),
("d4_slow_e", _ABY_DECK, "减压站一个个过。气瓶在跳,人稳着。"),
("d4_fast_e", _ABY_DECK, "违规快浮。医疗舱的灯,先给你们开好。"),
("d4_stay_lost", _ABY_DECK, "另一位,留在了倾斜的船舱。航标灯,替所有人亮着。"),
("p8", _ABY_DECK, "船长的话没说完。向左滑,把你听到的说出来。向右滑,什么都不问,拉他上浮。"),
("d4_surf", _ABY_DECK, "最后一潜收工。问海号的甲板,从没这么亮过。"),
# ---- F9 签收抉择 ----
("g9_in", _ABY_DECK, "医疗舱躺满了。你的气瓶剩多少,你比我清楚。"),
("p9", _ABY_DECK, "海燕号的船钟,还挂在沉船上。向左滑,用最后的气,下去取。向右滑,进医疗舱。"),
("g9_dry", _ABY_DECK, "一格气都不剩了。能带的,你都带回来了。"),
("d9_bello", _ABY_DECK, "船钟出水。海燕号的声音,回家了。"),
# ---- EP 尾声回响 ----
("ep_lead", _ABY_DECK, "收队了。调度台给你记一笔——他们,后来怎样了。"),
("ep_zhou_ok", _ABY_DECK, "小舟:归队报告打了三页。她说,声音都记得。"),
("ep_zhou_no", _ABY_DECK, "小舟:声呐日志没了。她凭记忆,重新描了一遍海图。"),
("ep_bai_ok", _ABY_DECK, "老白:腿保住了。阀门那夜之后,见人就夸你。"),
("ep_bai_no", _ABY_DECK, "老白:腿打了钢钉。他说,瘸着也值。"),
("ep_chen_ok", _ABY_DECK, "陈医官:回了医院。诊室里,挂着一盏海底的灯。"),
("ep_chen_lost", _ABY_DECK, "陈医官:她守着灯,到最后一小时。灯,还亮着。"),
("ep_zheng_ok", _ABY_DECK, "小郑:拆了石膏,第一件事,去看海。"),
("ep_zheng_lost", _ABY_DECK, "小郑:吊床随船,滑进了沟底。他没来得及看日出。"),
("ep_gao_peace", _ABY_DECK, "高船长:退役申请自己写的。第一行写着:数据不如人。"),
("ep_gao_up", _ABY_DECK, "高船长:上了岸,一直没说话。听说,他在学敲管语。"),
("ep_gao_lost", _ABY_DECK, "高船长:随海燕号留在沟底。航标灯,还亮着。"),
("ep_trench_bell", _ABY_DECK, "沟底:船钟挂在问海号桅杆上。风一过,就响一声。"),
("ep_trench_deep", _ABY_DECK, "沟底:很深,很静。海燕号睡在里面,像睡着了。"),
]
ABYSS_LINES = ABYSS_DIVER_LINES + ABYSS_DECK_LINES
# NPC 单音色平铺(id, speaker, instruct, 台词)→ web/abyss/audio/voice/<id>.*
#(转码时按 17-abyss.md §5.3 逐船员水声电台带通,见 NPC_BANDPASS;staging 保留未滤波原声)。
ABYSS_NPC = [
# 小舟(23 岁声呐兵,女,Serena)
("z1_a", "Serena", "年轻女声,二十三岁声呐兵,缺氧后发虚,强撑着干脆", "……有人吗?声呐兵,小舟。我们还活着,全艇五个人。"),
("z1_b", "Serena", "年轻女声,二十三岁声呐兵,缺氧后发虚,强撑着干脆", "轮机长老白困在机舱,陈医官和小郑在病舱,船长在指挥舱。"),
("z1_knock", "Serena", "年轻女声,二十三岁声呐兵,缺氧后发虚,强撑着干脆", "等等,你听。管子里,有人在敲。"),
("z1_bye", "Serena", "年轻女声,二十三岁声呐兵,缺氧后发虚,强撑着干脆", "好。上面……是不是天亮了?"),
("d1_log", "Serena", "年轻女声,二十三岁声呐兵,缺氧后发虚,强撑着干脆", "日志里有失事前,最后一段录音。给你,替我们收好。"),
# 老白(50 岁轮机长,男,Uncle_Fu)
("bai_a", "Uncle_Fu", "沙哑爽朗的中年男声,五十岁轮机长,忍着伤痛开玩笑", "哈哈,救援的?我这腿,跟冷却管缠上了。"),
("bai_b", "Uncle_Fu", "沙哑爽朗的中年男声,五十岁轮机长,忍着伤痛开玩笑", "艇上的药板烧了。病舱那两个,比我悬。"),
("bai_out", "Uncle_Fu", "沙哑爽朗的中年男声,五十岁轮机长,忍着伤痛开玩笑", "腿还在!我的腿还在!走,我给你带路。"),
("bai_thanks", "Uncle_Fu", "沙哑爽朗的中年男声,五十岁轮机长,忍着伤痛开玩笑", "这片海养着一村人。这份情,我记一辈子。"),
# 陈医官(45 岁船医,女,Vivian)
("chen_a", "Vivian", "平静沙哑的中年女声,四十五岁船医,专业镇定,语气温和", "你来了。小郑股骨断了,我给他固定好了。"),
("chen_b", "Vivian", "平静沙哑的中年女声,四十五岁船医,专业镇定,语气温和", "救援钟一次带一个。我是医生,我最后走。"),
("chen_up", "Vivian", "平静沙哑的中年女声,四十五岁船医,专业镇定,语气温和", "……好。小郑,等我,上面给你热粥。"),
("chen_stay", "Vivian", "平静沙哑的中年女声,四十五岁船医,专业镇定,语气温和", "去吧。我守着灯,灯不灭,人就不散。"),
("chen_last", "Vivian", "平静沙哑的中年女声,四十五岁船医,专业镇定,语气温和", "去吧。灯我留着,人齐了就熄。"),
# 高船长(58 岁船长,男,Eric)
("gao_final", "Eric", "低沉苍老的男声,五十八岁船长,疲惫克制,有老舵手的威严", "别下来。带他们回家,就算救完了。海燕号,通话结束。"),
("gao_a", "Eric", "低沉苍老的男声,五十八岁船长,疲惫克制,有老舵手的威严", "救援员。我船上,还剩两个活人。"),
("gao_b", "Eric", "低沉苍老的男声,五十八岁船长,疲惫克制,有老舵手的威严", "都带走,我最后上。这是船长的规矩。"),
("gao_last", "Eric", "低沉苍老的男声,五十八岁船长,疲惫克制,有老舵手的威严", "航标灯替我留着。就说,船长睡了。"),
("gao_c", "Eric", "低沉苍老的男声,五十八岁船长,疲惫克制,有老舵手的威严", "三年前……没什么。走吧,别耽误。"),
("gao_confess", "Eric", "低沉苍老的男声,五十八岁船长,疲惫克制,有老舵手的威严", "日志你拿了?里面写着。是我下令,多采了一小时。"),
("gao_peace", "Eric", "低沉苍老的男声,五十八岁船长,疲惫克制,有老舵手的威严", "……好。这一次,听救援员的。"),
]
# ---------------------------------------------------------------- detective
# 《雨夜专案》批次 2(docs/design/18-detective-voice-ids.md,171 句:旁白基表 104 双性别 + V 组 14 + 平铺 NPC 53;
# R2 修订:改词 fin_who_confirm/tut_skip;新增 fin_pre、item_who_w1..w4、st_wrong、recap_cf、gd_board_cf、
# z_react_w(NPC)、V 组 +4 对——逐条理由见 18-detective.md 文末 R2 修订记录 / new8/detective/round1 F1-F11)。
# 台词全文逐字照抄 voice-ids 文档;旁白只做系统/指引/教学/状态/现场自勘(探长自勘)/裁决宣读,
# 不念证词(证词=证人 NPC 平铺,DETECTIVE_NPC);键盘变体 V 组 10 对在 KEY_LINES["detective"]。
# NPC 不加电话滤波(区别于 radio/abyss——区分度靠 speaker+instruct+游戏侧签名 earcon 前缀)。
_DET = _pair("低沉沉稳的男声,专案组探长,冷静克制,像在雨夜读卷宗",
"沉稳清晰的女声,专案组探长,冷静克制,像在雨夜读卷宗")
DETECTIVE_LINES = [
# ---- G0 系统/菜单/帮助 ----
("sys_menu", _DET, "菜单。单击切换,双击确认。"),
("sys_menu_resume", _DET, "回到证词板。"),
("sys_menu_cf", _DET, "去指证。"),
("sys_menu_save", _DET, "保存并回大厅。"),
("sys_menu_help", _DET, "重新听帮助。"),
("sys_menu_restart", _DET, "重新查这桩案子。"),
("sys_saved", _DET, "进度已保存。"),
("settings_hint", _DET, "音色、语速、背景音乐,请回大厅修改。"),
("sys_help", _DET, "左右滑换卷宗,单击听取,双击重新听。长按指证,上滑状态,下滑菜单。"),
("sys_continue_ask", _DET, "检测到未结的案子。单击继续,右滑换一桩新案。"),
("sys_new_case", _DET, "新案开卷。"),
# ---- G1 开场/教学 ----
("intro_1", _DET, "雨下了一整夜。清晨,聚宝斋后院,古董商沈万川死了。"),
("intro_2", _DET, "你是专案组探长。这桩案子,归你。"),
("intro_3", _DET, "卷宗六份:四位证人的证词,现场勘验,法医报告。"),
("tut_d0", _DET, "先熟悉证词板。左右滑,换卷宗。"),
("tut_listen", _DET, "单击,听取当前卷宗。"),
("tut_replay", _DET, "双击,重新听这一句。"),
("tut_status", _DET, "上滑,随时听进度:问了几份,疑点几处。"),
("tut_confront", _DET, "按住屏幕,进入指证。去找两句对不上的证词。"),
("tut_cf_try", _DET, "练习一次。选一份证词,单击确认;再选一份,任意一句。"),
("tut_cf_ok", _DET, "指证就是这样。练习不算数,真矛盾要靠耳朵找。"),
("tut_done", _DET, "教学完了。开始查案。"),
("tut_skip", _DET, "已跳过教学。左右滑换卷宗,单击听取;按住屏幕,指证矛盾。"),
# ---- G2 状态/条目 ----
("st_asked_0", _DET, "六份卷宗,一份都没听。"),
("st_asked_1", _DET, "已听一份,还有五份。"),
("st_asked_2", _DET, "已听两份,还有四份。"),
("st_asked_3", _DET, "已听三份,还有三份。"),
("st_asked_4", _DET, "已听四份,还有两份。"),
("st_asked_5", _DET, "已听五份,还有一份。"),
("st_asked_6", _DET, "六份都听完了。"),
("st_doubt_0", _DET, "疑点,还没有。"),
("st_doubt_1", _DET, "疑点,一处。"),
("st_doubt_2", _DET, "疑点,两处。"),
("st_doubt_3", _DET, "疑点,三处。"),
("st_doubt_4", _DET, "疑点,四处,齐了。"),
("item_w1", _DET, "门卫老葛的证词。"),
("item_w2", _DET, "学徒小顺的证词。"),
("item_w3", _DET, "账房周先生的证词。"),
("item_w4", _DET, "房东温太太的证词。"),
("item_scene", _DET, "现场勘验。"),
("item_fore", _DET, "法医报告。"),
("item_case", _DET, "结案陈词。"),
("intro_w3", _DET, "周先生,管了十年的账,见人三分笑。"),
# ---- G3 现场勘验(探长自勘) ----
("sc_0", _DET, "你打伞进了后院。雨水顺着瓦檐,淌成一道帘子。"),
("sc_1", _DET, "柴房门口,沈万川面朝下倒着。后脑一处伤,现场没有凶器。"),
("sc_2", _DET, "后门门闩完好,是死者自己开的门。生人叫不开这扇门。"),
("sc_3", _DET, "泥地上只有进来的脚印,没有出去的。墙根的花盆,翻了一个。"),
("sc_4", _DET, "库房铜秤立在架上,秤砣不见了。"),
("sc_5", _DET, "柴房墙上,挂钥匙的铜钩,空着。"),
# ---- G4 指证 ----
("cf_start", _DET, "指证矛盾。先选第一份证词。左右滑换,单击确认。"),
("cf_pick_line", _DET, "左右滑,一句一句听。"),
("cf_confirm", _DET, "就这句?单击,定下来。"),
("cf_second", _DET, "再选与它矛盾的那份证词。"),
("cf_self", _DET, "同一句证词,和自己比不出矛盾。"),
("cf_same", _DET, "这两句说得一致,不矛盾。"),
("cf_diff", _DET, "这两句,说的不是一件事。"),
("cf_hit", _DET, "对不上!矛盾成立,记入疑点。"),
# ---- G5 结案三问 ----
("fin_start", _DET, "结案陈词。先指认真凶:四位证人,选一个。"),
("fin_pre", _DET, "证据在手,就此结案?单击,定下来;滑走,继续查。"),
("item_who_w1", _DET, "门卫老葛。"),
("item_who_w2", _DET, "学徒小顺。"),
("item_who_w3", _DET, "账房周先生。"),
("item_who_w4", _DET, "房东温太太。"),
("fin_who_confirm", _DET, "就指认这位?单击,定下来。"),
("fin_clear", _DET, "不在场证明成立。证据放人,回到证词板。"),
("st_wrong", _DET, "错指一回,记档。"),
("fin_right", _DET, "证据链闭合。周先生,你还有什么话说?"),
("fin_right_b", _DET, "证据链闭合。小顺,你还有什么话说?"),
("fin_ask_weapon", _DET, "第二问:凶器是什么?"),
("wp_1", _DET, "松木劈柴。"),
("wp_2", _DET, "铜秤砣。"),
("wp_3", _DET, "铁门闩。"),
("wp_4", _DET, "青瓷花盆。"),
("fin_wp_right", _DET, "对。创口的铜屑,不见了的秤砣,都指向它。"),
("fin_wp_wrong", _DET, "不对。铜屑不答应。记下了。"),
("fin_ask_motive", _DET, "第三问:动机是什么?"),
("mv_1", _DET, "监守自盗。"),
("mv_2", _DET, "寻仇泄愤。"),
("mv_3", _DET, "劫财入户。"),
("fin_mv_right", _DET, "对。库里短的东西,就是引线。"),
("fin_mv_wrong", _DET, "不对。账上短的东西,才是引线。记下了。"),
# ---- G6 结算 ----
("settle_open", _DET, "案件归档。雨,停了。"),
("rate_s", _DET, "教科书结案。四处疑点,一击即中。"),
("rate_a", _DET, "干净利落的结案。"),
("rate_b", _DET, "案子结了,卷宗上有疏漏。"),
("rate_c", _DET, "案子是结了,卷宗上补丁不少。"),
("st_miss_0", _DET, "指证,一次没错。"),
("st_miss_1", _DET, "指证,错过一两回。"),
("st_miss_2", _DET, "指证,错了不少。"),
("settle_hook", _DET, "同一场雨里,还有别的案子。单击,接新案。"),
# ---- G7 看门狗/卡关/纠错 ----
("gd_board", _DET, "还在证词板。左右滑,换卷宗。"),
("gd_board_cf", _DET, "都听过了。按住屏幕,去指证。"),
("gd_cf", _DET, "还在选证词。左右滑,单击确认。"),
("gd_fin", _DET, "在想?左右滑选,单击定。"),
("gd_invalid", _DET, "这个操作,这里用不上。"),
("hint_a", _DET, "探长,还没听的卷宗,先过一遍耳朵。"),
("hint_b", _DET, "探长,证词里有对不上的地方,逐句比对。"),
("hint_c1", _DET, "试试指证:周先生说熄了灯,温太太说灯还亮着。"),
("hint_c2", _DET, "试试指证:周先生说铜秤卖了,现场铜秤还在。"),
("b_hint_c1", _DET, "试试指证:小顺说熄了灯,温太太说灯还亮着。"),
("b_hint_c2", _DET, "试试指证:小顺说铜秤卖了,现场铜秤还在。"),
("hint_fin", _DET, "疑点齐了。去结案陈词。"),
("recap_go", _DET, "接着查。"),
("recap_cf", _DET, "卷宗都在耳朵里了。挑一句你不信的,按住指证。"),
]
# NPC 单音色平铺(id, speaker, instruct, 台词)→ web/detective/audio/voice/<id>.*
# 证人 4 + 秦法医(≤5 约束);同一角色 speaker+instruct 恒定;不加滤波(18b 头注)。
DETECTIVE_NPC = [
# 老葛(62 岁门卫,Eric)
("w1_1", "Eric", "苍老沙哑的男声,守夜人,慢悠悠,带倦意", "昨儿夜里雨大,我守着街门,一步没敢挪窝。"),
("w1_2", "Eric", "苍老沙哑的男声,守夜人,慢悠悠,带倦意", "九点四十,雨点子刚砸下来,小顺打着伞出的街门。"),
("w1_3", "Eric", "苍老沙哑的男声,守夜人,慢悠悠,带倦意", "十点一刻,一位先生收着黑伞进了后巷。个儿高,微驼背。"),
("w1_4", "Eric", "苍老沙哑的男声,守夜人,慢悠悠,带倦意", "九点五十,周账房出的街门,跟我说了声,回家。"),
("w1_5", "Eric", "苍老沙哑的男声,守夜人,慢悠悠,带倦意", "后半夜,温太太还来门房借过一回热水。"),
("w1_6", "Eric", "苍老沙哑的男声,守夜人,慢悠悠,带倦意", "听说店里账上短了银子,沈老板撂了话,月底清账。"),
("acc_ge", "Eric", "苍老沙哑的男声,守夜人,慢悠悠,带倦意", "我守了一夜门!温太太来借热水,她能作证!"),
# 小顺(19 岁学徒,Dylan)
("w2_1", "Dylan", "年轻清亮的男声,学徒,怯生生的,慌张诚实", "夜里,师父打发我去城南,给刘掌柜送匣子。"),
("w2_2", "Dylan", "年轻清亮的男声,学徒,怯生生的,慌张诚实", "雨太大,刘掌柜留我住的,今早才回店。"),
("w2_3", "Dylan", "年轻清亮的男声,学徒,怯生生的,慌张诚实", "库房钥匙?师父嫌沉,平日就挂在柴房墙上的铜钩。"),
("w2_4", "Dylan", "年轻清亮的男声,学徒,怯生生的,慌张诚实", "师父的账,向来只信周先生一个人。"),
("w2_5", "Dylan", "年轻清亮的男声,学徒,怯生生的,慌张诚实", "后门那闩,生人叫不开。熟人来了,师父才亲自去开。"),
("w2_6", "Dylan", "年轻清亮的男声,学徒,怯生生的,慌张诚实", "周先生常念叨,账房的活,越到夜里越清静。"),
("acc_shun", "Dylan", "年轻清亮的男声,学徒,怯生生的,慌张诚实", "我宿在城南刘掌柜家,他全家都能作证!"),
("b_shun_1", "Dylan", "年轻清亮的男声,学徒,怯生生的,慌张诚实", "我走的时候,顺手熄了店堂的灯。"),
("b_shun_2", "Dylan", "年轻清亮的男声,学徒,怯生生的,慌张诚实", "钥匙,师父贴身带着,从不离身。"),
("b_shun_3", "Dylan", "年轻清亮的男声,学徒,怯生生的,慌张诚实", "那伤,八成是柴房劈柴砸的。铜秤早卖给收废品的了。"),
("b_shun_4", "Dylan", "年轻清亮的男声,学徒,怯生生的,慌张诚实", "库房的货,我一件都没碰过。"),
("b_shun_react_1", "Dylan", "年轻清亮的男声,学徒,怯生生的,慌张诚实", "灯?我、我走的时候明明熄了……"),
("b_shun_react_2", "Dylan", "年轻清亮的男声,学徒,怯生生的,慌张诚实", "别问了……别问了……"),
("b_shun_break_1", "Dylan", "年轻清亮的男声,学徒,怯生生的,慌张诚实", "匣子是我送的,可我半路折回来了……"),
("b_shun_break_2", "Dylan", "年轻清亮的男声,学徒,怯生生的,慌张诚实", "我把棉袄反穿,弓着背,葛大爷没认出我。"),
("b_shun_break_3", "Dylan", "年轻清亮的男声,学徒,怯生生的,慌张诚实", "库房的货,我拿过三件换了钱。他说要对账……秤砣下去的时候,他看着我。"),
# 周先生(45 岁账房,pack A 真凶,Uncle_Fu——与 male 旁白同 speaker,靠签名+instruct 区分)
("z_1", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "我七点进店上夜账。九点五十对完,锁了前门回家。"),
("z_2", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "临走,我顺手熄了店堂的灯。那会儿,店里就剩老板。"),
("z_3", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "库房钥匙?沈老板贴身带着,从不离身。"),
("z_4", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "那伤,是松木劈柴砸的。铜秤?上个月卖给收废品的了。"),
("z_5", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "我到家倒头就睡,夜里的事,一概不知。"),
("z_6", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "你们要是不信,去问我家婆娘。"),
("z_react_1", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "灯?……许是我记岔了。人上了岁数,记性不济。"),
("z_react_2", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "钥匙的事……他、他有时候,也贴身带着的……"),
("z_react_w", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "铜器?……我那是顺口猜的。凶器的事,我怎么会知道。"),
("z_react_3", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "秤!秤的事,你们别再问了……"),
("z_react_4", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "……随你们怎么问。我要回去了。"),
("acc_zhou", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "我十点就到家了!我家婆娘能作证!"),
("z_break_1", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "别念了……我说。库房短的三件货,是我抵了赌债。"),
("z_break_2", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "他说月底清账,要报官。我求他,他不肯。"),
("z_break_3", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "我只想把钥匙放回去,把被盗做真。抡起秤砣的时候,他认出了我。"),
("b_zhou_1", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "我对完账就回家了,夜里雨大,路上没停。"),
("b_zhou_2", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "钥匙?平时就挂在柴房铜钩上,十年了。"),
("b_zhou_3", "Uncle_Fu", "平板谨慎的男声,账房,字斟句酌,被追问时渐慌", "柴房我常去,没什么不对劲的。"),
# 温太太(50 岁房东,Vivian)
("wen_1", "Vivian", "快嘴泼辣的中年女声,房东太太,嗓门亮,热心肠", "雨是九点四十下的。我正收晾的衣裳,记得真真的。"),
("wen_2", "Vivian", "快嘴泼辣的中年女声,房东太太,嗓门亮,热心肠", "十点二十,我出门倒垃圾。聚宝斋店堂的灯,亮着呢。"),
("wen_3", "Vivian", "快嘴泼辣的中年女声,房东太太,嗓门亮,热心肠", "刚回屋,柴房后墙咯噔一响,有人爬墙!花盆翻了一个。"),
("wen_4", "Vivian", "快嘴泼辣的中年女声,房东太太,嗓门亮,热心肠", "吓得我蒙头半宿没睡着,天亮才敢吱声。"),
("wen_5", "Vivian", "快嘴泼辣的中年女声,房东太太,嗓门亮,热心肠", "店里短银子的事,街坊都有耳闻。上礼拜他还摔了账本。"),
("wen_6", "Vivian", "快嘴泼辣的中年女声,房东太太,嗓门亮,热心肠", "周账房那人,见人三分笑,账算得比谁都精。"),
("acc_wen", "Vivian", "快嘴泼辣的中年女声,房东太太,嗓门亮,热心肠", "我这把年纪翻墙?先问问那墙答不答应!"),
# 秦法医(Serena——与 female 旁白同 speaker,靠签名+报告语境区分)
("fo_1", "Serena", "冷静干练的女声,验尸官,临床腔,短句平直", "验尸报告,念给你听。"),
("fo_2", "Serena", "冷静干练的女声,验尸官,临床腔,短句平直", "死亡时间,昨夜十点到十点半。"),
("fo_3", "Serena", "冷静干练的女声,验尸官,临床腔,短句平直", "后脑单次钝器伤,一击毙命。创口嵌有铜屑,凶器是铜器。"),
("fo_4", "Serena", "冷静干练的女声,验尸官,临床腔,短句平直", "死者双手干净,没有搏斗痕迹。"),
("fo_5", "Serena", "冷静干练的女声,验尸官,临床腔,短句平直", "鞋底是干的。当夜,死者没出过后门。"),
]
# ---------------------------------------------------------------- hunt
# 《围猎》批次 3(docs/design/23-hunt-voice-ids.md):旁白/系统音 60 句 ×2 性别
# (全表同一 instruct 常量/性别,禁语速词;数字全分档整句无拼接;「重听」写「重新听」)
# + bot 喊话 24 条固定单音色平铺(HUNT_NPC)+ 键盘变体 V 组 11 对(KEY_LINES["hunt"])。
# 异例(23-hunt.md §15 裁决 7):喊话走 sfx 层带 pan,convert 落盘 audio/sfx/ 而非
# audio/voice/(生成管线、峰值归一 0.89 与 voice 一致;不滤波)。
# H 组头注「×9」为文档笔误,实表 8 句(end_win/end_lose/grade_×4/end_rank/end_again),
# 旁白合计 4+11+6+12+3+3+1+8+4+6+2 = 60 ✓。
_HUNT = _pair("低沉醇厚的男声,像守夜猎人的领队,沉稳有力",
"清亮沉稳的女声,像守夜猎人的领队,冷静中带着鼓舞")
HUNT_LINES = [
# ---- A 组 · 开场/房间 ----
("intro_1", _HUNT, "夜幕降临,野兽在山林里游荡。"),
("intro_2", _HUNT, "你们是守夜的猎人,营地就在身后。"),
("room_wait", _HUNT, "房间已开好,等同伴到齐。单击,立即开局。"),
("room_start", _HUNT, "狩猎开始,先听声音的方向。"),
# ---- B 组 · 教学 TEACH ----
("tut_rule", _HUNT, "野兽会从八个方向围上来。声音在哪边,就往哪边滑。"),
("tut_attack", _HUNT, "听,有狼在靠近。朝声音的方向,滑一下。"),
("tut_kill_ok", _HUNT, "好,杀掉了。就是这样。"),
("tut_callout", _HUNT, "单击一下,呼喊支援,队友会来帮你。"),
("tut_callout_ok", _HUNT, "听到没,同伴回应你了。"),
("tut_skill", _HUNT, "双击,旋风斩,清掉身边一圈。"),
("tut_skill_cd", _HUNT, "旋风斩有八秒冷却。冷却中双击,重新听威胁。"),
("tut_status", _HUNT, "上滑,听营地状态。下滑,打开菜单。"),
("tut_breach", _HUNT, "野兽冲进营地,营地就会受损。撑过五波就赢。"),
("tut_done", _HUNT, "教学完了。真正的围猎,现在开始。"),
("tut_skip", _HUNT, "已跳过教学,开打。"),
# ---- C 组 · 波次 ----
("wave_band1", _HUNT, "第一波来了,别慌。"),
("wave_band2", _HUNT, "第二波,比刚才多。"),
("wave_band3", _HUNT, "第三波,有大家伙混在里面。"),
("wave_band4", _HUNT, "第四波,更快,更多。"),
("wave_band5", _HUNT, "最后一波,全来了!"),
("wave_clear", _HUNT, "这一波,守住了。"),
# ---- D 组 · 状态链(耐久档 → 波次档 → 猎人数 → 击杀档) ----
("hp_band1", _HUNT, "营地固若金汤。"),
("hp_band2", _HUNT, "营地受了些损伤。"),
("hp_band3", _HUNT, "营地损毁过半。"),
("hp_band4", _HUNT, "营地快守不住了。"),
("st_wave_a", _HUNT, "还在头几波。"),
("st_wave_b", _HUNT, "打到中段了。"),
("st_wave_c", _HUNT, "最后一波了。"),
("team_3", _HUNT, "三名猎人在守。"),
("team_4", _HUNT, "四名猎人全在。"),
("kill_band0", _HUNT, "你还没开张。"),
("kill_band1", _HUNT, "你小有斩获。"),
("kill_band2", _HUNT, "你战果累累。"),
# ---- E 组 · 威胁重听(只报数量档,方位由声像承载) ----
("threat_none", _HUNT, "近处暂时没有野兽。"),
("threat_few", _HUNT, "有野兽贴近了,听方向出手。"),
("threat_many", _HUNT, "四周全是动静,快出手!"),
# ---- F 组 · 警报(可顶状态/播报类) ----
("breach", _HUNT, "有野兽冲进营地了!"),
("breach_heavy", _HUNT, "巨兽撞进营地,损伤很重!"),
("danger_low", _HUNT, "营地危险了,一只都不能再漏!"),
# ---- G 组 · 技能 ----
("skill_ready", _HUNT, "旋风斩准备好了。"),
# ---- H 组 · 结算 ----
("end_win", _HUNT, "天亮了,营地还在。"),
("end_lose", _HUNT, "营地守不住了,撤退!"),
("grade_s", _HUNT, "评级,S!完美的围猎。"),
("grade_a", _HUNT, "评级,A。漂亮。"),
("grade_b", _HUNT, "评级,B。守得还行。"),
("grade_c", _HUNT, "评级,C。险胜。"),
("end_rank", _HUNT, "成绩已记入猎手榜。"),
("end_again", _HUNT, "单击,再猎一局。"),
# ---- I 组 · 断线/重连 ----
("net_lost", _HUNT, "与营地断了联系,正在重连。"),
("net_back", _HUNT, "联系恢复了,接着打。"),
("net_to_bot", _HUNT, "有猎人离队,老猎手顶上。"),
("net_gone", _HUNT, "回不去那场围猎了,回大厅。"),
# ---- J 组 · 菜单/帮助 ----
("sys_menu", _HUNT, "菜单。单击切换,双击确认。"),
("sys_menu_resume", _HUNT, "回到猎场。"),
("sys_menu_help", _HUNT, "重新听帮助。"),
("sys_menu_home", _HUNT, "退出房间,回大厅。"),
("help_main", _HUNT, "朝声音的方向滑,就是出击。单击呼喊支援,双击旋风斩。上滑状态,下滑菜单。"),
("settings_hint", _HUNT, "音色、语速、背景音乐,请回大厅修改。"),
# ---- K 组 · 向导/纠错(idle-only) ----
("gd_invalid", _HUNT, "这个操作现在用不上。"),
("gd_idle", _HUNT, "听声音的方向,滑过去。"),
]
# bot 喊话固定单音色平铺(id, speaker, instruct, 台词)→ web/hunt/audio/sfx/<id>.*
# 角色 speaker+instruct 恒定(23-hunt-voice-ids.md N 组表):阿岩=Uncle_Fu /
# 小筝=Serena / 老周=Eric;座 3 真人呼喊无角色声,用 sfx/call_whistle(裁决 6)。
HUNT_NPC = [
# 阿岩(座 0,Uncle_Fu)
("npc_yan_greet", "Uncle_Fu", "憨厚壮实的中年男声,猎户,话少踏实", "我来了,这边有我。"),
("npc_yan_help_call", "Uncle_Fu", "憨厚壮实的中年男声,猎户,话少踏实", "我这侧吃紧,来个人搭把手!"),
("npc_yan_help_ack", "Uncle_Fu", "憨厚壮实的中年男声,猎户,话少踏实", "撑住,我来了。"),
("npc_yan_cover", "Uncle_Fu", "憨厚壮实的中年男声,猎户,话少踏实", "这边交给我。"),
("npc_yan_kill", "Uncle_Fu", "憨厚壮实的中年男声,猎户,话少踏实", "拿下!"),
("npc_yan_breach", "Uncle_Fu", "憨厚壮实的中年男声,猎户,话少踏实", "漏了一个,都小心。"),
("npc_yan_skill", "Uncle_Fu", "憨厚壮实的中年男声,猎户,话少踏实", "都闪开,看我的!"),
("npc_yan_wave", "Uncle_Fu", "憨厚壮实的中年男声,猎户,话少踏实", "新一波,稳住。"),
# 小筝(座 1,Serena)
("npc_zheng_greet", "Serena", "轻快爽利的年轻女声,猎手,干脆利落", "小筝到位,开工啦。"),
("npc_zheng_help_call", "Serena", "轻快爽利的年轻女声,猎手,干脆利落", "这边太多了,谁来帮一下!"),
("npc_zheng_help_ack", "Serena", "轻快爽利的年轻女声,猎手,干脆利落", "别慌,我来了!"),
("npc_zheng_cover", "Serena", "轻快爽利的年轻女声,猎手,干脆利落", "这边我罩着。"),
("npc_zheng_kill", "Serena", "轻快爽利的年轻女声,猎手,干脆利落", "搞定!"),
("npc_zheng_breach", "Serena", "轻快爽利的年轻女声,猎手,干脆利落", "哎呀,漏了一个!"),
("npc_zheng_skill", "Serena", "轻快爽利的年轻女声,猎手,干脆利落", "看我清场!"),
("npc_zheng_wave", "Serena", "轻快爽利的年轻女声,猎手,干脆利落", "又来一波,打起精神。"),
# 老周(座 2,Eric)
("npc_zhou_greet", "Eric", "沙哑沉稳的老年男声,老猎手,带点调侃", "老周我守着,放心。"),
("npc_zhou_help_call", "Eric", "沙哑沉稳的老年男声,老猎手,带点调侃", "我这边压不住了,来人。"),
("npc_zhou_help_ack", "Eric", "沙哑沉稳的老年男声,老猎手,带点调侃", "听见了,这就到。"),
("npc_zhou_cover", "Eric", "沙哑沉稳的老年男声,老猎手,带点调侃", "这边交给我吧。"),
("npc_zhou_kill", "Eric", "沙哑沉稳的老年男声,老猎手,带点调侃", "嗯,解决了。"),
("npc_zhou_breach", "Eric", "沙哑沉稳的老年男声,老猎手,带点调侃", "啧,溜进去一只。"),
("npc_zhou_skill", "Eric", "沙哑沉稳的老年男声,老猎手,带点调侃", "让让,老骨头活动活动。"),
("npc_zhou_wave", "Eric", "沙哑沉稳的老年男声,老猎手,带点调侃", "听,又来了。"),
]
# ---------------------------------------------------------------- cipher
# 《暗码》批次 3(docs/design/19-cipher-voice-ids.md,139 句:双性别 134 = OP 报务员 20
# + WIND 北风/系统 114(含 _k 键盘变体 11 对)+ 平铺 NPC 5(处长 Eric))。
# OP/WIND 两 speaker 配对(同性别双音色,VOICE_SPK["cipher"] 逐句覆盖,radio 同型:
# female 侧你 Serena / 北风 Vivian、male 侧你 Uncle_Fu / 北风 Dylan,同场景永不同 speaker)。
# 不加滤波(19b 头注:区分度靠 speaker+instruct+语境,detective 先例);摩尔斯滴答为
# 运行时程序合成(web/cipher/morse.js),零文件资产勿排产;数字全分档整句无拼接。
_CIPHER_OP = _pair("压低而冷静的男声,报务员,值夜班,克制的紧张",
"压低而冷静的年轻女声,报务员,值夜班,克制的紧张")
_CIPHER_WIND = _pair("清晰平稳的男声,电台联络声,隔着电流,字字清楚",
"清晰平稳的女声,电台联络声,隔着电流,字字清楚")
CIPHER_OP_LINES = [
("b_open", _CIPHER_OP, "民国二十九年,江城。电讯处的夜班,从零点的电波声开始。"),
("r1a_send", _CIPHER_OP, "北风,北风。货船,丑时过江,接住。"),
("r1b_hold", _CIPHER_OP, "先压一压。这一船的事,得看清楚再说。"),
("r2a_act", _CIPHER_OP, "有的字,不能落进卷宗。"),
("r2b_act", _CIPHER_OP, "通报誊清,呈处长。"),
("r3a_go", _CIPHER_OP, "口令我背得出。今夜,我亲自去。"),
("r3b_act", _CIPHER_OP, "暗号夹进交接簿,让雨替我跑这一趟。"),
("r4a_feed", _CIPHER_OP, "戴上手键。这一封,借快手七的手法。"),
("r4b_act", _CIPHER_OP, "手,离开放键器。我只是个值机的。"),
("g9_knot", _CIPHER_OP, "三年前,我译错了一封电。那之后,我再没见过我的上线。"),
("d9_send", _CIPHER_OP, "北风,北风。处置令,今夜三时。撤。"),
("d9_sleep", _CIPHER_OP, "机关上了。雨声里,只剩挂钟。"),
("e1_a", _CIPHER_OP, "电波追不上我了。码头的灯,一盏一盏地灭。"),
("e1_b", _CIPHER_OP, "新的密码本,在城外渡口等我。黎明,比想象中安静。"),
("e2_a", _CIPHER_OP, "交了班,一切照旧。没人知道今晚频道里发生过什么。"),
("e2_b", _CIPHER_OP, "只是北风的呼叫,到天亮也没人接。下一次,我会接吗。"),
("e3_a", _CIPHER_OP, "我把警报拍了三遍。门开的时候,挂钟正好敲三点。"),
("e3_b", _CIPHER_OP, "审讯室的灯,也很亮。可码头那边,一个人都没少。"),
("e4_a", _CIPHER_OP, "两点五十九分,脚步停在门口。我把最后一页密码,塞进了炉子。"),
("e4_b", _CIPHER_OP, "耳机还挂着。里面有一封电文,永远译不完了。"),
]
CIPHER_WIND_LINES = [
# ---- G0.a 操作提示句(_k 变体见 KEY_LINES["cipher"]) ----
("gd_brief", _CIPHER_WIND, "单击继续值机。要选词时,向左或向右滑。"),
("gd_continue", _CIPHER_WIND, "单击屏幕,继续。"),
("gd_again", _CIPHER_WIND, "还在吗?单击继续。"),
("gd_tune1", _CIPHER_WIND, "电文在等。左右滑,听候选词。"),
("gd_tune2", _CIPHER_WIND, "双击,把电文再听一遍。再双击,会放慢。"),
("sys_help", _CIPHER_WIND, "单击选定,双击重新听电文。上滑听状态,下滑开菜单。选择时,左右滑。"),
("sys_menu", _CIPHER_WIND, "菜单。单击切换选项,双击确认。"),
("sys_skip", _CIPHER_WIND, "想跳过这句,单击就行。"),
("sys_continue_ask", _CIPHER_WIND, "检测到你的值机记录。单击继续上次,右滑开始新的夜班。"),
# ---- G0.b 状态/系统句 ----
("gd_invalid", _CIPHER_WIND, "这个手势,值机室里用不上。"),
("gd_prog1", _CIPHER_WIND, "光切词没有用。双击,先听电文。"),
("gd_prog2", _CIPHER_WIND, "别急。停下来,把电文听完整。"),
("sys_menu_resume", _CIPHER_WIND, "回到电波里。"),
("sys_menu_book", _CIPHER_WIND, "翻密码本,换一个字母。"),
("sys_menu_restart", _CIPHER_WIND, "重新开始今晚。"),
("sys_menu_lobby", _CIPHER_WIND, "保存并回大厅。"),
("sys_settings", _CIPHER_WIND, "音色、语速、背景音乐,请回大厅修改。"),
("sys_new_night", _CIPHER_WIND, "新的夜班,开始了。"),
# ---- G0.c 分档状态句(整句账目,逐值为真) ----
("prog_t1", _CIPHER_WIND, "现在是今晚第一封电文。"),
("prog_t2", _CIPHER_WIND, "现在是第二封,内部通报。"),
("prog_t3", _CIPHER_WIND, "现在是第三封,交接电文。"),
("prog_t4", _CIPHER_WIND, "第四封,最后一封。"),
("book_3", _CIPHER_WIND, "密码本,还剩三页。"),
("book_2", _CIPHER_WIND, "密码本,还剩两页。"),
("book_1", _CIPHER_WIND, "密码本,只剩最后一页。"),
("book_0", _CIPHER_WIND, "密码本,一页都不剩了。"),
("exp_calm", _CIPHER_WIND, "这一晚,还没人看你第二眼。"),
("exp_low", _CIPHER_WIND, "处长端着茶,多看了你一眼。"),
("exp_mid", _CIPHER_WIND, "走廊里的脚步声,慢了下来。"),
("exp_hot", _CIPHER_WIND, "门外,好像有人换了岗。"),
# ---- G1 开场(b_rule/b_goal;b_open 在 OP 组,b_chief 在 NPC 组) ----
("b_rule", _CIPHER_WIND, "北风问候你。规矩就一条:听不清,就再听一遍。"),
("b_goal", _CIPHER_WIND, "今晚频道里有四封密电。每一个字,都要过你的耳朵。"),
# ---- M1 第一封·船期·教学 ----
("rx1_in", _CIPHER_WIND, "第一封。江上船队的调度,拍得很稳。"),
("dv1_teach", _CIPHER_WIND, "电文进来了。先双击,把它重新听一遍。"),
("dv1_teach2", _CIPHER_WIND, "左右滑,听密码本上的候选词。单击,选定一个。"),
("dv1_ask", _CIPHER_WIND, "密码本第一页。三个词,哪个是它?"),
("cand_bay", _CIPHER_WIND, "货船。"),
("cand_day", _CIPHER_WIND, "客车。"),
("cand_ray", _CIPHER_WIND, "巡逻艇。"),
("dv1_right", _CIPHER_WIND, "货船,译对了。药品,装在货船的底舱。"),
("dv1_wrong", _CIPHER_WIND, "不对。听清楚:B,一长,三短。"),
("hint_t1", _CIPHER_WIND, "我教你一页:第一个字母,是 B。一长,三短。"),
("cf_confirm", _CIPHER_WIND, "就这个词?再单击,定下来。"),
("p2", _CIPHER_WIND, "药品船,丑时过江。向左滑,把船期报给北风。向右滑,压进值机日志。"),
("r1a_e", _CIPHER_WIND, "收到。江上有人等你。第一封,做得稳。"),
("r1b_e", _CIPHER_WIND, "日志下面压着的,是三箱药。船,不会等你。"),
("n1", _CIPHER_WIND, "零点半。挂钟走了半圈,雨没有停。"),
# ---- M2 第二封·名单 ----
("rx2_in", _CIPHER_WIND, "第二封。手法很急,是内部通报。"),
("dv2_ask", _CIPHER_WIND, "密码本第二页。三个词,选一个。"),
("cand_top", _CIPHER_WIND, "黑名单。"),
("cand_tap", _CIPHER_WIND, "窃听令。"),
("cand_tip", _CIPHER_WIND, "线人费。"),
("rev_top", _CIPHER_WIND, "黑名单。头一个代号,叫码头。"),
("rev_tap", _CIPHER_WIND, "窃听令。今夜,有人要装耳朵。"),
("rev_tip", _CIPHER_WIND, "线人费。月底之前,有人要收钱。"),
("p4_known", _CIPHER_WIND, "名单上有码头。向左滑,暗号示警,让他连夜搬家。向右滑,按流程呈报处长。"),
("p4_blind", _CIPHER_WIND, "一封通报。向左滑,压进抽屉,谁的耳朵也别装。向右滑,按流程呈报处长。"),
("r2a_e", _CIPHER_WIND, "这一页,从今天起,不存在了。"),
("r2b_e", _CIPHER_WIND, "名单进了卷宗。码头,自己多保重。"),
("hint_t2", _CIPHER_WIND, "翻到这页:第二个字母,是 O。三长。"),
("n2", _CIPHER_WIND, "一点整。处长的灯,还亮着。"),
# ---- M3 第三封·交接 ----
("rx3_in", _CIPHER_WIND, "第三封。很短,手法有点抖。"),
("dv3_ask", _CIPHER_WIND, "密码本第三页。三个词,选一个。"),
("cand_hand", _CIPHER_WIND, "今夜交接。"),
("cand_sand", _CIPHER_WIND, "明晨交接。"),
("cand_land", _CIPHER_WIND, "地点变更。"),
("rev_hand", _CIPHER_WIND, "今夜交接。丑时,老码头。"),
("rev_sand", _CIPHER_WIND, "明晨交接。天亮再说。"),
("rev_land", _CIPHER_WIND, "地点变更。等下一封。"),
("p6", _CIPHER_WIND, "交接在你手里。向左滑,亲自去码头跑一趟。向右滑,留书暗号,托给下一班。"),
("r3a_e", _CIPHER_WIND, "值班室空了一刻钟。值机日志,停了一行。"),
("r3b_e_ok", _CIPHER_WIND, "交接赶上了。袋子,连夜上了船。"),
("r3b_e_no", _CIPHER_WIND, "交接的袋子,在码头放到了天亮。"),
("hint_t3", _CIPHER_WIND, "翻到这页:第一个字母,是 H。四短。"),
("n3", _CIPHER_WIND, "一点三刻。雨小了,频道里很静。"),
# ---- M4 第四封·处置令·高潮 ----
("rx4_in", _CIPHER_WIND, "第四封。最后一个手法,你听过——快手七。"),
("dv4_ask", _CIPHER_WIND, "密码本第四页。三个词,选一个。"),
("cand_spy", _CIPHER_WIND, "内线确认。"),
("cand_say", _CIPHER_WIND, "约谈。"),
("cand_sly", _CIPHER_WIND, "内部排查。"),
("rev_spy", _CIPHER_WIND, "内线确认,今夜三时收网。代号,耳机。"),
("rev_say", _CIPHER_WIND, "明早九时,约谈相关人员。"),
("rev_sly", _CIPHER_WIND, "即日起,排查站内三人。"),
("p8_known", _CIPHER_WIND, "三点之前。向左滑,拍一封假电,说北风昨夜已撤。向右滑,手离开放键器,装傻。"),
("p8_blind", _CIPHER_WIND, "收台之前。向左滑,回一封例行电文,搅一搅水。向右滑,什么都不做。"),
("r4a_e", _CIPHER_WIND, "假电出了门。对家的耳朵,听得出换人了吗。"),
("r4b_e", _CIPHER_WIND, "有时,什么都不做,就是做了。"),
("hint_t4", _CIPHER_WIND, "翻到这页:第二个字母,是 P。短,两长,短。"),
# ---- F9 签收(g9_knot/d9_send/d9_sleep 在 OP 组) ----
("g9_in", _CIPHER_WIND, "频道要收了。最后一件事,是你自己。"),
("p9_known", _CIPHER_WIND, "收网之前。向左滑,开机,发最后一电。向右滑,关机,装睡到天亮。"),
("p9_blind", _CIPHER_WIND, "北风在等答复。向左滑,开机,把今晚发出去。向右滑,例行收台。"),
("d9_send_e", _CIPHER_WIND, "收到了。你的手法,我听了一整晚。快走。"),
("d9_caught_e", _CIPHER_WIND, "信号被咬住了。别回头,走水路。快。"),
("d9_sleepe", _CIPHER_WIND, "天亮之前,还有两个钟头。守住。"),
# ---- EP 结局与尾声回响(e*_a/e*_b 在 OP 组) ----
("ep_lead", _CIPHER_WIND, "天亮了。北风给你记了最后一笔账——今晚的事,都有了去向。"),
("ep_med_ok", _CIPHER_WIND, "药品:货船丑时靠岸,三箱药,一箱不少。"),
("ep_med_no", _CIPHER_WIND, "药品:没人接住那三箱药。船,白白过了江。"),
("ep_list_ok", _CIPHER_WIND, "名单:码头连夜搬了家。卷宗里,少了一个人。"),
("ep_list_no", _CIPHER_WIND, "名单:天亮时,多了一个红圈。"),
("ep_hand_ok", _CIPHER_WIND, "交接:胶卷上了船,手很稳。"),
("ep_hand_no", _CIPHER_WIND, "交接:码头的灯,白亮了一夜。"),
("ep_spy_ok", _CIPHER_WIND, "处置令:你译对了它。北风,撤在你前头。"),
("ep_spy_no", _CIPHER_WIND, "处置令:差的那三个字母,差了一整条命。"),
# ---- EP.b 结局计数(R2 F3:数字规避分档整句;结算链尾播报,存档 endings 账同源) ----
("end_c1", _CIPHER_WIND, "这是你在电波里挣下的第一个结局。"),
("end_c2", _CIPHER_WIND, "这是你在电波里挣下的第二个结局。"),
("end_c3", _CIPHER_WIND, "这是你在电波里挣下的第三个结局。"),
("end_c4", _CIPHER_WIND, "四个结局,都收进你的密码本了。"),
]
CIPHER_LINES = CIPHER_OP_LINES + CIPHER_WIND_LINES
# 处长 NPC 单音色平铺(id, speaker, instruct, 台词)→ web/cipher/audio/voice/<id>.*
# 有台词人物 2 人(北风=WIND 双性别组、处长=平铺 Eric);不加滤波(19b 头注,detective 先例)。
CIPHER_NPC = [
("b_chief", "Eric", "威严冷淡的中年男声,电讯处长,官腔,话里有话", "四封电文,一封都不许漏。你的耳朵,是处里最值钱的东西。"),
("m2_chief", "Eric", "威严冷淡的中年男声,电讯处长,官腔,话里有话", "嗯,译得很全。去吧。"),
("m4_chief_a", "Eric", "威严冷淡的中年男声,电讯处长,官腔,话里有话", "值机员,今晚的频道,很热闹啊。"),
("m4_chief_b", "Eric", "威严冷淡的中年男声,电讯处长,官腔,话里有话", "你的耳朵最好。听见什么,记得……要报。"),
("m4_chief_c", "Eric", "威严冷淡的中年男声,电讯处长,官腔,话里有话", "行。去睡吧,明天还有明天的事。"),
]
# ---------------------------------------------------------------- bridge
# 《星环舰长》批次 4(docs/design/22-bridge-voice-ids.md,154 句:旁白 132 基表 ×2 性别
# + V 组 10 对(KEY_LINES["bridge"],base id 落 key/ 子目录)+ 平铺 NPC 12(BRIDGE_NPC)。
# 旁白=舰载系统音,全表同一 instruct 常量/性别,禁句级变体、禁语速词;数字全分档整句。
# NPC 与旁白零 speaker 重叠(大副 Dylan / 工程师 Eric / 医师 Vivian),不加滤波(22b 头注)。
_BRIDGE = _pair("低沉沉稳的男声,舰载系统音,冷静克制",
"清晰沉稳的女声,舰载系统音,冷静克制,句读干净")
BRIDGE_LINES = [
# ---- G0 组 · 系统/菜单/帮助 ----
("sys_menu", _BRIDGE, "菜单。单击切换,双击确认。"),
("sys_menu_resume", _BRIDGE, "回到舰桥。"),
("sys_menu_save", _BRIDGE, "保存并回大厅。"),
("sys_menu_help", _BRIDGE, "重新听帮助。"),
("sys_menu_restart", _BRIDGE, "放弃本次巡逻。"),
("sys_saved", _BRIDGE, "进度已保存。"),
("settings_hint", _BRIDGE, "音色、语速、背景音乐,请回大厅修改。"),
("sys_help", _BRIDGE, "巡航中,上滑巡检全舰;危机中,左右滑听选项,单击下令。下滑菜单,双击重新听。"),
("sys_continue_ask", _BRIDGE, "检测到未完成的巡逻。单击继续,右滑进星港。"),
("sys_new_patrol", _BRIDGE, "新巡逻,星环号启航。"),
# ---- G1 组 · 开场/教学 ----
("intro_1", _BRIDGE, "你是巡逻舰星环号的舰长。在这条航线上,耳朵就是雷达。"),
("intro_2", _BRIDGE, "全舰四个系统:引擎、护盾、扫描、武器,各守一方。"),
("intro_3", _BRIDGE, "引擎在舰尾低鸣,护盾在舰首嗡响。"),
("intro_4", _BRIDGE, "扫描在左舷扫掠,武器在右舷充能。"),
("intro_5", _BRIDGE, "危机从哪边来,哪边先遭殃。"),
("tut_d0", _BRIDGE, "先试巡检。向上滑,听全舰状态。"),
("tut_all_ok", _BRIDGE, "四个音都稳,全舰正常。"),
("tut_crisis", _BRIDGE, "有情况。听签名音,辨认类型。"),
("tut_decide", _BRIDGE, "左右滑听选项,单击下令。"),
("tut_aid", _BRIDGE, "驰援靠扫描。我们的扫描,稳得很。"),
("tut_after", _BRIDGE, "就是这样:听声,辨型,下令。"),
("tut_loyal", _BRIDGE, "船员的心分三档:离心、平常、信赖。上滑巡检听得到。"),
("tut_done", _BRIDGE, "教学完了。舰长,航线交给你。"),
("tut_skip", _BRIDGE, "已跳过教学。上滑巡检,危机时左右滑听选项。"),
# ---- G2 组 · 巡检/状态 ----
("sys_engine_dmg", _BRIDGE, "引擎,受损。"),
("sys_engine_off", _BRIDGE, "引擎,离线。"),
("sys_shield_dmg", _BRIDGE, "护盾,受损。"),
("sys_shield_off", _BRIDGE, "护盾,离线。"),
("sys_scan_dmg", _BRIDGE, "扫描,受损。"),
("sys_scan_off", _BRIDGE, "扫描,离线。"),
("sys_weapon_dmg", _BRIDGE, "武器,受损。"),
("sys_weapon_off", _BRIDGE, "武器,离线。"),
("st_hull_1", _BRIDGE, "舰体,稳固。"),
("st_hull_2", _BRIDGE, "舰体,带伤。"),
("st_hull_3", _BRIDGE, "舰体,危急。"),
("st_seg_1", _BRIDGE, "第一巡航段。"),
("st_seg_2", _BRIDGE, "第二巡航段。"),
("st_seg_3", _BRIDGE, "第三巡航段。"),
("st_seg_4", _BRIDGE, "第四巡航段,末段。"),
("st_rp_1", _BRIDGE, "资源紧张。"),
("st_rp_2", _BRIDGE, "资源够用。"),
("st_rp_3", _BRIDGE, "资源充足。"),
("st_xo_1", _BRIDGE, "大副,离心。"),
("st_xo_2", _BRIDGE, "大副,平常。"),
("st_xo_3", _BRIDGE, "大副,信赖。"),
("st_eng_1", _BRIDGE, "工程师,离心。"),
("st_eng_2", _BRIDGE, "工程师,平常。"),
("st_eng_3", _BRIDGE, "工程师,信赖。"),
("st_doc_1", _BRIDGE, "医师,离心。"),
("st_doc_2", _BRIDGE, "医师,平常。"),
("st_doc_3", _BRIDGE, "医师,信赖。"),
("recap_go", _BRIDGE, "继续航行。"),
("evt_dock", _BRIDGE, "本段结束,靠港整备。"),
("evt_resume", _BRIDGE, "启航,下一段巡航。"),
# ---- G3 组 · 危机 ----
("evt_meteor", _BRIDGE, "碎石呼啸,陨石群。"),
("evt_pirate", _BRIDGE, "敌船引擎,海盗。"),
("evt_distress", _BRIDGE, "断续三短,求救信号。"),
("evt_radiation", _BRIDGE, "计数急响,辐射云。"),
("dir_port", _BRIDGE, "方位,左舷。"),
("dir_star", _BRIDGE, "方位,右舷。"),
("dir_bow", _BRIDGE, "方位,舰首。"),
("dir_stern", _BRIDGE, "方位,舰尾。"),
("cr_decide", _BRIDGE, "听选项,下令。"),
("opt_evade", _BRIDGE, "规避机动,靠引擎。"),
("opt_fight", _BRIDGE, "迎战,靠武器。"),
("opt_breach", _BRIDGE, "硬闯,靠护盾。"),
("opt_aid", _BRIDGE, "驰援,靠扫描。"),
("opt_ignore", _BRIDGE, "按兵不动,不理会。"),
("cr_timeout", _BRIDGE, "没来得及下令。"),
("res_clean", _BRIDGE, "干净利落,有惊无险。"),
("res_hurt", _BRIDGE, "舰体受创。"),
("res_bad", _BRIDGE, "损失惨重。"),
("res_mine", _BRIDGE, "轰开碎石,采到矿材,资源加一。"),
("res_plunder", _BRIDGE, "海盗击退,缴获物资,资源加二。"),
("res_reward", _BRIDGE, "救援成功,对方致谢,资源加二。"),
("res_ambush", _BRIDGE, "遭了伏击,舰体受创。"),
("res_rad", _BRIDGE, "全船受照。"),
("trap_hint", _BRIDGE, "求救码后面,有引擎声,多留心。"),
# ---- G4 组 · 忠诚/支援 ----
("loy_aid", _BRIDGE, "医师与大副,记下了这次驰援。"),
("loy_ignore", _BRIDGE, "求救没了下文,医师很失望。"),
("loy_fight_win", _BRIDGE, "大副打得尽兴,士气高涨。"),
("loy_evade_pirate", _BRIDGE, "大副觉得,这一躲有些窝囊。"),
("loy_breach", _BRIDGE, "工程师心疼过载的护盾。"),
("loy_rad", _BRIDGE, "医师在处理伤员。"),
("sup_xo", _BRIDGE, "大副坐上炮位,火力压制!"),
("sup_eng", _BRIDGE, "工程师出手,系统复航了。"),
("sup_doc", _BRIDGE, "医师带人抢修,舰体补回一点。"),
("sup_none", _BRIDGE, "这个岗位,眼下没人顶上。"),
# ---- G5 组 · 靠港整备 ----
("dock_open", _BRIDGE, "靠港整备。左右滑看项目,单击执行。"),
("rep_engine", _BRIDGE, "修理引擎,升一级,两资源。"),
("rep_shield", _BRIDGE, "修理护盾,升一级,两资源。"),
("rep_scan", _BRIDGE, "修理扫描,升一级,两资源。"),
("rep_weapon", _BRIDGE, "修理武器,升一级,两资源。"),
("rep_ok", _BRIDGE, "修理完成。"),
("rep_poor", _BRIDGE, "资源不够。"),
("dock_go", _BRIDGE, "启航,继续巡逻。"),
("dock_empty", _BRIDGE, "全舰状态良好,无需整备。"),
# ---- G6 组 · 结算/死亡 ----
("die_intro", _BRIDGE, "舰体解体在即,全员撤离。"),
("die_meteor", _BRIDGE, "陨石撕开了船身。"),
("die_pirate", _BRIDGE, "海盗的炮火击穿了舰体。"),
("die_rad", _BRIDGE, "辐射熔穿了舱壁。"),
("die_adv_to", _BRIDGE, "早下令,勤靠港,别硬扛。"),
("die_adv_sys", _BRIDGE, "系统离线时,别把宝押在它身上。"),
("rep_open", _BRIDGE, "巡逻归来,全舰记功。剩余资源已折算功勋。"),
("rate_s", _BRIDGE, "教科书巡逻,功勋卓著。"),
("rate_a", _BRIDGE, "出色完成巡逻。"),
("rate_b", _BRIDGE, "巡逻结束,有惊无险。"),
("rate_c", _BRIDGE, "巡逻结束,遍体鳞伤。"),
("rate_d", _BRIDGE, "巡逻失败,弃舰撤离。"),
("merit_1", _BRIDGE, "功勋,寥寥。"),
("merit_2", _BRIDGE, "功勋,可观。"),
("merit_3", _BRIDGE, "功勋,丰厚。"),
("settle_hook", _BRIDGE, "星港期待你的下一次巡逻。单击,返航。"),
# ---- G7 组 · 看门狗/纠错 ----
("gd_cruise", _BRIDGE, "还在巡航。上滑,巡检全舰。"),
("gd_decide", _BRIDGE, "还在等命令。左右滑,听选项。"),
("gd_dock", _BRIDGE, "还在整备。左右滑,单击执行。"),
("gd_invalid", _BRIDGE, "这个操作,这里用不上。"),
("hint_dock3", _BRIDGE, "整备完毕,就启航。"),
# ---- G8 组 · 星港 ----
("port_open", _BRIDGE, "星港整备,功勋兑换。左右滑看,单击定。"),
("upg_spare", _BRIDGE, "备用零件,出发自带一批物资。功勋二十。"),
("upg_harvest", _BRIDGE, "采集协议,每段多一份资源。功勋三十。"),
("upg_armor1", _BRIDGE, "加装装甲,舰体上限加一。功勋四十。"),
("upg_vet", _BRIDGE, "老兵船组,开局全员忠诚加一。功勋五十。"),
("upg_armor2", _BRIDGE, "重型装甲,舰体上限再加一。功勋六十。"),
("upg_aegis", _BRIDGE, "应急护盾,每段首次受创减一。功勋七十。"),
("upg_done", _BRIDGE, "这项已改装到位。"),
("upg_buy", _BRIDGE, "改装完成。"),
("port_poor", _BRIDGE, "功勋不够。"),
("port_back", _BRIDGE, "返回。"),
# ---- GR 组 · 航线选择 ----
("route_ask", _BRIDGE, "选择巡航航线。"),
("opt_route_safe", _BRIDGE, "稳妥航线,采集平平,太平。"),
("opt_route_rich", _BRIDGE, "矿富航线,采得多,磨损也多。"),
]
# 船员 NPC 单音色平铺(id, speaker, instruct, 台词)→ web/bridge/audio/voice/<id>.*
# 与旁白零 speaker 重叠(22b 头注);不加滤波;同一角色 speaker+instruct 恒定。
BRIDGE_NPC = [
# 大副(XO 34,Dylan)
("xo_fight", "Dylan", "干脆利落的年轻男声,星舰大副,令出必行,敬重舰长", "舰长,交给我,炮位就绪!"),
("xo_win", "Dylan", "干脆利落的年轻男声,星舰大副,令出必行,敬重舰长", "打得漂亮,这条航线够他们传一阵子。"),
("xo_uneasy", "Dylan", "干脆利落的年轻男声,星舰大副,令出必行,敬重舰长", "又是一路躲。舰长,弟兄们想打。"),
("xo_aid", "Dylan", "干脆利落的年轻男声,星舰大副,令出必行,敬重舰长", "驰援是应该的,星环号不是过路人。"),
# 工程师(ENG 52,Eric)
("eng_wear", "Eric", "沙哑厚重的中年男声,轮机工程师,惜船如命,慢条斯理", "老伙计累出毛病了,让我来。"),
("eng_fix", "Eric", "沙哑厚重的中年男声,轮机工程师,惜船如命,慢条斯理", "修好了,再听它稳稳地转。"),
("eng_sup", "Eric", "沙哑厚重的中年男声,轮机工程师,惜船如命,慢条斯理", "交给我,三分钟,给它救活。"),
("eng_gripe", "Eric", "沙哑厚重的中年男声,轮机工程师,惜船如命,慢条斯理", "护盾不是这么用的,舰长……"),
# 医师(DOC 38,Vivian)
("doc_aid", "Vivian", "温和坚定的女声,舰载医师,轻声但不容置疑", "幸好停了船,他们撑不了多久了。"),
("doc_ignore", "Vivian", "温和坚定的女声,舰载医师,轻声但不容置疑", "……我没有什么要说的。"),
("doc_care", "Vivian", "温和坚定的女声,舰载医师,轻声但不容置疑", "有伤员的地方,就有我。"),
("doc_trap", "Vivian", "温和坚定的女声,舰载医师,轻声但不容置疑", "有人拿求救当诱饵,小心。"),
]
# ---------------------------------------------------------------- teahouse
# 《深夜茶馆》批次 3(docs/design/20-teahouse-voice-ids.md,163 句:旁白 98 ×2 性别
# + NPC 平铺 65;键盘变体 V 组 10 对在 KEY_LINES["teahouse"])。
# 台词全文逐字照抄 voice-ids 文档;旁白只做系统/教学/状态/账目/氛围客转述(不念客人
# 台词);数字全分档整句无拼接;不加滤波(detective 先例:区分度靠 sig_* 前缀+
# speaker+instruct 三层冗余)。
_TEA = _pair("低沉温润的男声,深夜茶馆掌柜,从容安稳,像守着一炉将开的水",
"温润平和的女声,深夜茶馆掌柜,从容安稳,像守着一炉将开的水")
TEAHOUSE_LINES = [
# ---- G0 系统/菜单/帮助(10) ----
("sys_menu", _TEA, "菜单。单击切换,双击确认。"),
("sys_menu_resume", _TEA, "回到堂前。"),
("sys_menu_upg", _TEA, "打烊置办。"),
("sys_menu_save", _TEA, "保存并回大厅。"),
("sys_menu_help", _TEA, "重新听帮助。"),
("sys_saved", _TEA, "进度已保存。"),
("settings_hint", _TEA, "音色、语速、背景音乐,请回大厅修改。"),
("sys_help", _TEA, "左右滑选茶,单击定下。听水声,双击提壶;滴答密,双击出汤。上滑状态,下滑菜单。"),
("sys_continue_ask", _TEA, "检测到没打烊的茶馆。单击继续,右滑两次重开新店。"),
("sys_reset_ask", _TEA, "重开新店?口碑和存款都从头来。再右滑一次,确认。"),
# ---- G1 开场/教学(17) ----
("intro_1", _TEA, "巷子最深处,有一盏亮到后半夜的灯。"),
("intro_2", _TEA, "你接手了这家茶馆。今晚,是它易主后的头一晚。"),
("intro_3", _TEA, "泡茶三步:选茶、候汤、出汤。手上的事,耳朵说了算。"),
("tut_d0", _TEA, "先看茶柜。左右滑,一罐一罐听。"),
("tut_tea", _TEA, "单击,选定这罐茶。"),
("tut_water", _TEA, "灶上坐水。泡泡细密是将沸,咕嘟连片是滚透——听到细密,双击提壶。"),
("tut_steep", _TEA, "注水,闷泡。滴答由稀到密,密起来就是将好——双击,出汤。"),
("tut_serve", _TEA, "茶好了。单击,端出去。"),
("tut_status", _TEA, "上滑,听今晚的局面:第几位客人,口碑如何;客人要的茶,上滑灶上随时再听。"),
("tut_done", _TEA, "都会了。往后每一晚,都这么泡。"),
("tut_skip", _TEA, "已跳过教学。左右滑选茶,单击选定,双击动手。"),
("water_early", _TEA, "急了,水还没醒透。这一步,偏了。"),
("water_ok", _TEA, "正是时候。"),
("water_late", _TEA, "水滚老了。这一步,偏了。"),
("steep_early", _TEA, "出急了,茶还睡着。这一步,偏了。"),
("steep_ok", _TEA, "出汤,正好。"),
("steep_late", _TEA, "闷过了,汤要发苦。这一步,偏了。"),
# ---- G2 状态/茶名(18) ----
("st_guest_1", _TEA, "今晚第一位客人。"),
("st_guest_2", _TEA, "今晚第二位客人。"),
("st_guest_3", _TEA, "今晚第三位客人。"),
("st_guest_4", _TEA, "今晚第四位客人。"),
("st_guest_5", _TEA, "今晚第五位客人。"),
("st_rep_1", _TEA, "口碑还没立起来,街坊不知道巷子里有这家茶馆。"),
("st_rep_2", _TEA, "有了回头客,口碑立住脚了。"),
("st_rep_3", _TEA, "巷子口都在传你的茶,小有名气。"),
("st_rep_4", _TEA, "一盏难求,茶客口耳相传。"),
("st_purse_1", _TEA, "钱箱见底,不到十文。"),
("st_purse_2", _TEA, "钱箱周转得开,不愁明日开销。"),
("st_purse_3", _TEA, "手头宽裕,二十五文往上。"),
("tea_longjing", _TEA, "龙井。绿茶性鲜:水将沸就提壶,出汤要快。"),
("tea_moli", _TEA, "茉莉花茶。香靠滚水激:水滚透再冲,出汤要快。"),
("tea_tieguanyin", _TEA, "铁观音。乌龙要滚水才醒,闷到滴答密了再出汤。"),
("tea_dianhong", _TEA, "滇红。红茶性暖,滚水冲,经得住闷,越闷越醇。"),
("tea_puer", _TEA, "普洱。老茶经泡:滚水才泡得开,多闷一会儿更醇。"),
("tea_yinzhen", _TEA, "白毫银针。嫩芽金贵:水将沸就提,闷到滴答密。"),
# ---- G3 泡茶衔接(5) ----
("brew_start", _TEA, "回到灶前。"),
("res_good", _TEA, "这一盏,泡得地道。"),
("res_plain", _TEA, "这一盏,泡得中平。"),
("res_wrong", _TEA, "这一盏,泡岔了——不是客人今晚要的那口。"),
("serve_link", _TEA, "你把茶端了过去。"),
# ---- G4 反应过场/氛围客人(9) ----
("bridge_good", _TEA, "客人把这盏茶喝得很慢,像是就着茶,把心事也喝顺了。"),
("bridge_plain", _TEA, "茶喝完了,客人点点头,起身。"),
("bridge_wrong", _TEA, "那盏茶凉在桌上,客人走的时候,没回头。"),
("amb_1", _TEA, "车铃一响,晚班的邮差探进头来。老规矩:茉莉花茶,要滚烫的。"),
("amb_2", _TEA, "门帘一挑,绣坊的阿姐收了工,想喝一盏清火的龙井。"),
("amb_3", _TEA, "巷口的醉汉扶着门框,含含糊糊,只嚷嚷要最浓的滇红。"),
("amb_react_good", _TEA, "他喝得很慢,末了多搁下两文。"),
("amb_react_plain", _TEA, "他喝完,点点头,搁下茶钱。"),
("amb_react_wrong", _TEA, "他抿了一口,搁下半盏,晃出去了。"),
# ---- G5 打烊/经济/置办(16) ----
("close_open", _TEA, "客人走尽了。收壶,打烊。"),
("close_coin_1", _TEA, "今晚进账,不到十五文。"),
("close_coin_2", _TEA, "今晚进账,十五到二十四文。"),
("close_coin_3", _TEA, "今晚进账,二十五文往上。"),
("rent_due", _TEA, "今天是房钱的日子,这一回,二十文。"),
("rent_ok", _TEA, "房钱交清,秦婶没多话。"),
("rent_short", _TEA, "房钱不够,差的那几文先记在账上。秦婶撂下一句:下回一起算。"),
("debt_clear", _TEA, "旧账清了,秦婶把那一页翻了过去。"),
("st_debt_1", _TEA, "账上,还欠着秦婶的钱。"),
("settle_night", _TEA, "熄了灯,这一晚就过去了。单击,睡下。"),
("upg_open", _TEA, "要添置点什么?左右滑看,单击买定。"),
("upg_cabinet", _TEA, "茶柜扩容,十五文。往后普洱、白毫银针,都上柜。"),
("upg_teaware", _TEA, "添一副新茶具,十二文。手上更准,火候窗更宽。"),
("upg_sign", _TEA, "立一块招牌,二十文。要口碑立得住——十二分往上才挂。"),
("upg_none", _TEA, "眼下的钱和账,还置办不起。改日再说。"),
("upg_lock", _TEA, "招牌还挂不得:口碑得十二分往上,欠账也得清。"),
# ---- G6 看门狗/纠错/复述(6) ----
("gd_serve", _TEA, "客人还在堂前。上滑,再听一遍他要的茶。"),
("gd_brew", _TEA, "还在灶上,听声动手,双击。"),
("gd_close", _TEA, "账报完了。单击,熄灯睡下。"),
("gd_invalid", _TEA, "这个操作,这里用不上。"),
("hint_water", _TEA, "水听两遍:泡泡细密是将沸,咕嘟连片是滚透。"),
("recap_go", _TEA, "接着张罗。"),
# ---- G7 晚数/章节(7) ----
("night_1", _TEA, "头一晚。灯亮了,水坐上,茶馆开门。"),
("night_2", _TEA, "第二晚。老客们,已经认得这条巷子。"),
("night_3", _TEA, "第三晚。今天,是收房钱的日子。"),
("night_4", _TEA, "第四晚。熟客进门,都不用问位子了。"),
("night_5", _TEA, "第五晚。这一章的夜,快要听全了。"),
("night_more", _TEA, "又一夜。灯还亮着,茶还热着。"),
("chapter_end", _TEA, "第一章完。这条巷子的夜,你都听过了。茶馆的故事,还长。"),
]
# NPC 单音色平铺(id, speaker, instruct, 台词)→ web/teahouse/audio/voice/<id>.*
# 5 位弧线客人(20-teahouse-voice-ids.md 头注区分度表):speaker+instruct 恒定,不滤波。
_ZHOU = ("Eric", "沙哑洪亮的男声,修车铺老师傅,大嗓门,直爽热乎")
_SU = ("Vivian", "轻柔平稳的女声,夜班护士,温和疲惫,吐字干净")
_SHI = ("Dylan", "年轻清亮的男声,高三学生,紧张,语速快,有点怯")
_BAI = ("Uncle_Fu", "苍老缓慢的男声,独居老先生,慢条斯理,带茶韵")
_HE = ("Serena", "低哑爽利的女声,夜班出租车司机,利落,带笑带倦")
TEAHOUSE_NPC = [
# 老周(13)
("zhou_enter", *_ZHOU, "小掌柜!老位置,还给我留着呢吧?"),
("zhou_o1", *_ZHOU, "修了一天车,嗓子眼里全是机油味。就你那龙井,给我去去火。"),
("zhou_r1_good", *_ZHOU, "嚯!就是这个味儿!多谢啦,小掌柜。"),
("zhou_r1_plain", *_ZHOU, "嗯……还行。今天手底下,差了点意思。"),
("zhou_r1_wrong", *_ZHOU, "这是我要的龙井吗?……算了算了,凑合喝。"),
("zhou_o2", *_ZHOU, "今天冷。来盏顶饱的、浓的,扛饿又扛冻。"),
("zhou_r2_good", *_ZHOU, "对喽,暖到脚后跟了!跟你说——我儿子今天来电话了。"),
("zhou_r2_plain", *_ZHOU, "茶是茶,水是水,差点火候。儿子的事,改天再说吧。"),
("zhou_r2_wrong", *_ZHOU, "……今晚这盏不对。儿子的事,不说也罢。"),
("zhou_o3", *_ZHOU, "今晚就想要头一回那盏。一样的,再来一份。"),
("zhou_e_a", *_ZHOU, "明天铺子歇一天,儿子回来帮我盘货。这盏茶,记你头上了!"),
("zhou_e_b", *_ZHOU, "电话里跟儿子拌了两句嘴。没事——茶还热着,日子还长。"),
("zhou_e_c", *_ZHOU, "铺子我想关了,儿子叫我过去帮工。茶馆的灯,替我多亮会儿。"),
# 苏晚(13)
("su_enter", *_SU, "请问……还营业吗?我刚下夜班。"),
("su_o1", *_SU, "胃里发凉,人空荡荡的。想要一盏暖的,别太冲,喝完好睡。"),
("su_r1_good", *_SU, "好暖……像被窝里塞了个汤婆子。谢谢你。"),
("su_r1_plain", *_SU, "嗯,是热乎的。今晚,凑合能睡。"),
("su_r1_wrong", *_SU, "这盏……有点寒。没关系,是我没说清。"),
("su_o2", *_SU, "今晚换个口味,想要香的。可我还得眯一会儿才接班。"),
("su_r2_good", *_SU, "真香啊……今夜走了位老人,家属拉着我的手道谢。这盏茶,敬他们。"),
("su_r2_plain", *_SU, "茶还行。今夜病房平静,平静得累人。"),
("su_r2_wrong", *_SU, "香气没出来……像我今晚的运气。算了。"),
("su_o3", *_SU, "最后一晚了,明天起不用值夜班。还是头一回那种,暖的。"),
("su_e_a", *_SU, "我调去白天班啦,还能常来。给你带我们科的喜糖。"),
("su_e_b", *_SU, "还在犹豫,夜班再值一季。好在这盏茶,够我撑到天亮。"),
("su_e_c", *_SU, "我递了辞呈,往后去哪还没想好。但今晚茶馆的灯,我记住了。"),
# 陈拾(13)
("shi_enter", *_SHI, "老板……还有位子吗?我背单词,背得头疼。"),
("shi_o1", *_SHI, "想喝点香的提提神,但别太浓——喝完回家还得睡,明天早读。"),
("shi_r1_good", *_SHI, "好香!脑子清醒了。这是我攒的早饭钱……值了。"),
("shi_r1_plain", *_SHI, "还行吧。单词它还是不进脑子。"),
("shi_r1_wrong", *_SHI, "有点苦……没事,我泡的方便面更苦。"),
("shi_o2", *_SHI, "模拟考砸了,心里烧得慌。想喝口最清火的。"),
("shi_r2_good", *_SHI, "凉丝丝的,火气下去一半。老板,你说我还能考上吗?……算了,当我没问。"),
("shi_r2_plain", *_SHI, "茶一般,我也一般,挺配的。"),
("shi_r2_wrong", *_SHI, "不是这个味儿……回家还得接着刷题。"),
("shi_o3", *_SHI, "考完了!今晚想喝口金贵的,犒劳自己。"),
("shi_e_a", *_SHI, "成绩出来了,比模拟考高了四十多分!这盏茶,算我请全茶馆的!"),
("shi_e_b", *_SHI, "考完了,心里反倒空荡荡的。去哪读书,听爸妈安排吧。"),
("shi_e_c", *_SHI, "考砸了,茶也不对味……但至少,这儿的灯一直亮着。"),
# 白老先生(13)
("bai_enter", *_BAI, "小掌柜,给我的紫砂壶,留个座。"),
("bai_o1", *_BAI, "晚饭积了食。要一盏刮油的、经得住泡的,我喝得慢。"),
("bai_r1_good", *_BAI, "好茶,有回甘。像我家老婆子腌的萝卜干,越嚼越有味。"),
("bai_r1_plain", *_BAI, "嗯,茶汤尚可。人老了,舌头也钝了。"),
("bai_r1_wrong", *_BAI, "这不是我要的那口。不怪你——是我话没说全。"),
("bai_o2", *_BAI, "昨儿那盏不错。今晚想喝口陈的,越陈越好。"),
("bai_r2_good", *_BAI, "陈味出来了。四十年前,我跟老婆子初见,就是一盏普洱。"),
("bai_r2_plain", *_BAI, "茶汤淡了些。故事嘛,等茶好了再说。"),
("bai_r2_wrong", *_BAI, "……不是我惦记的那口。罢了,回家睡了。"),
("bai_o3", *_BAI, "就馋那口陈味。老伙计,看你的手艺了。"),
("bai_e_a", *_BAI, "明儿我把棋盘搬来,你这儿灯亮,我天天来摆谱——就当,她还在看着。"),
("bai_e_b", *_BAI, "回去还是那间空屋子。不过睡前有盏茶垫着,梦能沉一点。"),
("bai_e_c", *_BAI, "下个月,我搬去养老院了。这把紫砂壶,留给茶馆吧。"),
# 何雨眠(13)
("he_enter", *_HE, "师傅,门口能停车不?——哦,我是说,喝茶。"),
("he_o1", *_HE, "跑了一宿,眼睛发涩,脑子发木。要一盏清口的,不要浓的。"),
("he_r1_good", *_HE, "清亮,像拿凉水洗了把脸。今晚最后一单,稳了。"),
("he_r1_plain", *_HE, "一般吧,能开就行。"),
("he_r1_wrong", *_HE, "不是这口……算了,我还得赶路。"),
("he_o2", *_HE, "今晚不打算睡了,还要跑单。要最浓的,越浓越好。"),
("he_r2_good", *_HE, "得劲儿!闺女的照片就贴在方向盘上——再跑俩月,学费就齐了。"),
("he_r2_plain", *_HE, "不够劲,今晚里程表怕是要难看。"),
("he_r2_wrong", *_HE, "淡了。算了,我赶时间。"),
("he_o3", *_HE, "最后一趟车收工。想要一盏柔的,收收心。"),
("he_e_a", *_HE, "学费攒齐了,明天去看闺女。老师说她考了年级前十——这盏,我慢慢喝。"),
("he_e_b", *_HE, "钱还差一点,再跑一个月。茶馆这盏灯,是我每晚的路标。"),
("he_e_c", *_HE, "我把夜班退了。闺女在电话里说:妈,钱够用。这盏茶,我喝得最慢。"),
]
# ---------------------------------------------------------------- keeper
# 《守塔人》批次 4(docs/design/21-keeper-voice-ids.md,148 句:双性别 121 = KEEPER 守塔人 42
# + SHORE 岸台/系统 79,含 `_k` 键盘变体 8 对 + 平铺 NPC 27 = 阿澈 19 / 父亲 3 / 祖父 5)。
# 台词全文逐字照抄 voice-ids 文档;双性别组分 KEEPER(玩家)/ SHORE(岸台值班员=向导人设)
# 两个 speaker 配对(同性别双音色,VOICE_SPK["keeper"] 逐句覆盖,radio HOST/PROD 同型:
# female 侧守塔人 Serena / 岸台 Vivian、male 侧守塔人 Uncle_Fu / 岸台 Dylan,同场景永不同 speaker);
# `_k` 变体 8 对在 KEY_LINES["keeper"](措辞「方向键/空格/回车」,abyss §G0.a 同型)。
# NPC 平铺 27 句,转码时按 21b 头注 §滤波表 带通(NPC_BANDPASS["keeper"]):
# 阿澈=电台 350-3200 / 父亲=唱片 300-2800 / 祖父=唱片 250-2600(+轻压缩,宁窄勿脏);
# 锚句(rev_g1/g4、rev_f1/f5、rev_c1/c5)是 KEEPER 双性别行、不滤波(「现在的你」在回忆里旁白)。
_KEEPER = _pair("沉静克制的男声,孤岛守塔人,低而稳,藏着心事",
"沉静克制的年轻女声,孤岛守塔人,低而稳,藏着心事")
_SHORE = _pair("清晰平稳的男声,海岸电台值班员,隔着无线电的温和干练",
"清晰平稳的女声,海岸电台值班员,隔着无线电的温和干练")
KEEPER_K_LINES = [
# ---- G1 开场(2) ----
("b_open", _KEEPER, "白角灯塔,风暴夜。我是今晚的守塔人。"),
("b_rain", _KEEPER, "浪拍到第七级台阶。雾角两长一短——塔在,我也在。"),
# ---- C1 幕一(8) ----
("c1_hold", _KEEPER, "别慌。我在白角,哪儿也不去。慢慢说。"),
("c1_ask", _KEEPER, "顺发号,报你的方位,报进水的速度。"),
("c1_open", _KEEPER, "父亲的钥匙,就在抽屉里。灯座第三级,开了。"),
("rev_teach", _KEEPER, "听到唱针落下、风声退后,是回忆。风声回来,就是现在。"),
("rev_g1", _KEEPER, "唱针落下。四十年前,也是这盏灯下。"),
("rev_g4", _KEEPER, "风声回来了。爷爷,我听见了。"),
("c1_diary", _KEEPER, "暗格里是祖父的日志。缺了最后一页。"),
("c1_focus", _KEEPER, "先守住这盏灯。旧账,天亮再算。"),
# ---- C2 幕二(9) ----
("c2_song", _KEEPER, "跟我念:浪头过,船头抬。浪头退,船头落。"),
("rev_f1", _KEEPER, "唱针又落下了。十二年前,也是这样的风。"),
("rev_f5", _KEEPER, "风声回来了。那扇门,留到今天。"),
("c2_key", _KEEPER, "父亲桌上那支手电,尾盖里卷着一页纸。"),
("c2_shore", _KEEPER, "岸台,记下他的船位。请渔政天亮前到。"),
("c4_true", _KEEPER, "我父亲也是守塔人。十二年前风暴夜,他出海救人,没有回来。"),
("c4_pass", _KEEPER, "家里人?都在大陆。这行当,习惯了。"),
("c5_patch", _KEEPER, "拿堵漏垫顶住裂缝。人守在电台边,我一直在。"),
("c5_suit", _KEEPER, "穿上救生衣,带上信号灯,站到甲板上去。"),
# ---- C3 幕三(11) ----
("c3_page", _KEEPER, "手电的尾盖旋开了。那页纸,在灯下摊平。"),
("rev_c1", _KEEPER, "唱针落下。缺的那一页,自己开了口。"),
("rev_c5", _KEEPER, "风声回来了。原来那道家规,是这么来的。"),
("c6_shut", _KEEPER, "合上。守塔人的事,守塔人自己扛。"),
("c7_go", _KEEPER, "机械上满发条,灯能自己转到天亮。等着我。"),
("c7_go2", _KEEPER, "发条……我只上了半卷。快去,快回。"),
("c7_hold", _KEEPER, "顺发号,听我的。灯照着的那条道,跟我念,一步一步来。"),
("c8_song", _KEEPER, "他爷爷传下的渔歌……在哪?再唱一遍!"),
("c8_math", _KEEPER, "坐标向东两百米。灯光扫过去,看见救生衣了!"),
("c8_old", _KEEPER, "走老航道。多绕半里,绕开所有的暗礁。"),
("c8_new", _KEEPER, "数三个浪头,借着风的角度,钻新缝!"),
# ---- D9 签收(4) ----
("g9_knot", _KEEPER, "十二年了。这一晚,我总算替父亲,站完了他的班。"),
("d9_tell_a", _KEEPER, "四十年前守塔的,是我爷爷。他离灯去救自己的儿子。灯灭那刻,回澜号……对不住。"),
("d9_tell_b", _KEEPER, "我只知道,那晚守塔的是我爷爷,灯灭过一下。剩下的,我也不敢说全。"),
("d9_keep", _KEEPER, "那晚风太大,灯出了点故障。别再问了,孩子,回家吧。"),
# ---- EP 结局主句+钩子(8) ----
("e1_a", _KEEPER, "天亮了。他在门厅睡熟,那页纸攥在手里。灯,亮到了最后一刻。"),
("e1_b", _KEEPER, "回澜号的账,翻开了第一页。这座塔,等了四十年。"),
("e2_a", _KEEPER, "他走的时候,天刚亮。我送到码头,什么也没说。"),
("e2_b", _KEEPER, "缺页放回灯底。有些东西,再压一代人。灯,还转着。"),
("e3_a", _KEEPER, "我把知道的,都说了。他望着海,半信半疑地点头。"),
("e3_b", _KEEPER, "碎片也是真话。剩下的,让海自己讲吧。"),
("e4_a", _KEEPER, "天亮,各自归位。他回他的港,我守我的灯。"),
("e4_b", _KEEPER, "隔着一道海,两座塔,谁也没再提那晚。"),
]
KEEPER_S_LINES = [
# ---- G0.a 操作提示句(7,_k 变体在 KEY_LINES) ----
("gd_brief", _SHORE, "单击,继续值守。要选择时,向左或向右滑。"),
("gd_continue", _SHORE, "单击屏幕,继续。"),
("gd_again", _SHORE, "还在吗?单击继续。"),
("sys_help", _SHORE, "单击继续,双击重新听。上滑听状态,下滑开菜单。选择时,左右滑。"),
("sys_menu", _SHORE, "菜单。单击切换选项,双击确认。"),
("sys_skip", _SHORE, "想跳过这句,单击就行。"),
("sys_continue_ask", _SHORE, "检测到你的值守记录。单击继续上次,右滑开始新的风暴夜。"),
# ---- G0.b 状态/系统句(20) ----
("gd_invalid", _SHORE, "这个手势,塔里用不上。"),
("gd_prog1", _SHORE, "滑动这里用不上。想想岸台刚说的话。"),
("gd_prog2", _SHORE, "别乱。停一停,再听一遍。"),
("sys_menu_resume", _SHORE, "回到值守。"),
("sys_menu_restart", _SHORE, "重新开始今夜。"),
("sys_menu_lobby", _SHORE, "保存并回大厅。"),
("sys_settings", _SHORE, "音色、语速、背景音乐,请回大厅修改。"),
("sys_new_night", _SHORE, "新的风暴夜,开始了。"),
("prog_m1", _SHORE, "风暴起来了。今晚第一程。"),
("prog_m2", _SHORE, "风暴到顶。第二程。"),
("prog_m3", _SHORE, "风在退。最后一程。"),
("prog_mem_g", _SHORE, "这是回忆:祖父的年代,四十年前。"),
("prog_mem_f", _SHORE, "这是回忆:父亲的最后一晚,十二年前。"),
("tr_deep", _SHORE, "他把命,交给了你的声音。"),
("tr_half", _SHORE, "他信一半,疑一半。"),
("tr_thin", _SHORE, "今晚,他只信自己的手。"),
("tru_zero", _SHORE, "旧账压在灯底,你一页没翻。"),
("tru_one", _SHORE, "灯下的旧账,你翻开了一角。"),
("tru_two", _SHORE, "祖父和父亲的事,你都翻到了。"),
("tru_full", _SHORE, "三代人的账,今夜见底了。"),
# ---- G1 开场(2) ----
("b_rule", _SHORE, "岸台的规矩就一条:风暴夜,灯不能灭。"),
("b_goal", _SHORE, "今夜两艘货轮过境。守住灯,也守住频道。"),
# ---- C1 幕一(8,含 p1) ----
("c1_tune", _SHORE, "频道里有动静。很轻,像隔着整片海。"),
("p1", _SHORE, "他的声音在抖。向左滑,先接住这个人。向右滑,先要他的船位。"),
("c1_holde", _SHORE, "他喘匀了。先接人,再办事。"),
("c1_aske", _SHORE, "他更慌了。人没接住,数字也乱。"),
("p2", _SHORE, "他提到四十年前。向左滑,去翻灯座下的暗格。向右滑,先专心守灯。"),
("c1_focuse", _SHORE, "灯转得稳。可那句「灭过一下」,留在了你耳朵里。"),
("lull_1", _SHORE, "岸台通报:风加到十级。第一程,过去了。"),
# ---- C2 幕二(7) ----
("c2_back", _SHORE, "频道又响了。顺发号的引擎,时断时续。"),
("p3", _SHORE, "他快撑不住了。向左滑,用守塔人的口诀带他稳船。向右滑,把船位报给岸台。"),
("c2_shoreo", _SHORE, "船位落在图上了。渔政的船,最快也要天亮。"),
("p4", _SHORE, "他问起你的人。向左滑,告诉他真话。向右滑,轻轻带过。"),
("c2_crack", _SHORE, "听!他那边,船板裂了!"),
("p5", _SHORE, "船在漏。向左滑,让他回舱堵漏,守住电台。向右滑,让他穿救生衣,上甲板。"),
("lull_2", _SHORE, "风眼凌晨过境。第二程,到顶了。"),
# ---- C3 幕三(12) ----
("c3_eye", _SHORE, "风眼过了。最后的浪墙,就跟在后面。"),
("m6_seal", _SHORE, "那些旧东西,你今晚没碰过。缺页的事,你一无所知。"),
("p6", _SHORE, "祖父的最后一页。向左滑,今晚就读完它。向右滑,合上,塞回灯底。"),
("c6_reade", _SHORE, "四十年,两代人,一条规矩。今晚,轮到你选了。"),
("p7", _SHORE, "浪墙压过来了。向左滑,给灯上满发条,驾接驳艇去接他。向右滑,守着灯,用声音引他过礁。"),
("c3_wall", _SHORE, "浪墙起来了!就是现在!"),
("p8_boat", _SHORE, "海上没有灯。向左滑,循着他哼的渔歌找他。向右滑,按推算的坐标,直线搜。"),
("p8_reef", _SHORE, "礁缝两道口。向左滑,走老航道,稳但绕远。向右滑,钻新缝,近但冒险。"),
("c9_return", _SHORE, "接驳艇回来了。两个人,一身水,一盏灯。"),
("c3_dark", _SHORE, "你离塔的时候,发条走到了头。灯,灭过一刻钟。过境的货轮,平安。"),
("c9_land", _SHORE, "顺发号过了礁缝,搁上背风湾的滩。人,站上了岸。"),
("c9_found", _SHORE, "他在半路改了主意。天亮时,渔队找到了他。活着,湿透了。"),
# ---- D9 签收(5) ----
("g9_in", _SHORE, "风,一层一层退下去。天边,有点灰了。"),
("p9a", _SHORE, "他问起四十年前的灯。向左滑,把缺页的事,连根说给他。向右滑,就说,那晚风太大。"),
("p9b", _SHORE, "他问起四十年前的灯。你知道的,只有碎片。向左滑,把碎片给他。向右滑,说风太大。"),
("d9_telle", _SHORE, "他听完,沉默了很久。天,亮了。"),
("d9_keepe", _SHORE, "这个话题,沉回了海里。天,亮了。"),
# ---- EP 尾声回响(11) ----
("ep_lead", _SHORE, "风停了。岸台给你记一笔——这一晚,后来怎样了。"),
("ep_amo_home", _SHORE, "阿澈:那晚之后改跑日班。他说,夜里的海,有守塔人听着。"),
("ep_amo_solo", _SHORE, "阿澈:自己把船开上了背风湾。船主骂了船,没骂人。"),
("ep_amo_dawn", _SHORE, "阿澈:抱着冷藏柜漂到天亮。打那以后,出海带两台电台。"),
("ep_lamp_full", _SHORE, "灯塔记录簿:你离塔三刻,灯自己转着。父亲的发条,说中了。"),
("ep_lamp_dark", _SHORE, "灯塔记录簿:凌晨两点,灯灭一刻钟。那一行,你写得格外用力。"),
("ep_lamp_hold", _SHORE, "灯塔记录簿:转灯整夜未停。你一步,也没离开过它。"),
("ep_log_told", _SHORE, "灯下的旧账,今夜见了光。"),
("ep_log_kept", _SHORE, "灯下的旧账,又压回了黑暗里。"),
("ep_dad", _SHORE, "值班表上,你替十二年前那个班次签了字。老陈,收工了。"),
("ep_shore", _SHORE, "岸台晨报:风暴过境,航道平安。白角灯,正常。"),
]
KEEPER_LINES = KEEPER_K_LINES + KEEPER_S_LINES
# NPC 单音色平铺(id, speaker, instruct, 台词)→ web/keeper/audio/voice/<id>.*
#(转码时按 21b 头注滤波表带通,见 NPC_BANDPASS["keeper"];staging 保留未滤波原声)。
_AMO = "年轻男声,二十二岁渔民,海风养出的嗓子,风暴里喊话,慌而不乱"
_DAD = "温和低沉的男声,四十岁守塔人,隔着老唱片,疲惫而坚定"
_GRAND = "苍老缓慢的男声,七十岁老守塔人,隔着更老的唱片,字字压秤"
KEEPER_NPC = [
# 阿澈(22 岁渔民,现在线·电台,Dylan,19 句)
("amo_a", "Dylan", _AMO, "……有人吗……顺发号……船进水了……"),
("amo_b", "Dylan", _AMO, "白角塔!是你吗?求你,回个话!"),
("c1_holdo", "Dylan", _AMO, "……好,好。你在。我听见了。"),
("c1_asko", "Dylan", _AMO, "方位……仪表进水了,我,我看不清……"),
("amo_c", "Dylan", _AMO, "我爷爷,四十年前也是在这片海没的。他说,白角的灯灭过一下。"),
("amo_d", "Dylan", _AMO, "塔……听得到吗?水,到膝盖了。"),
("c2_songo", "Dylan", _AMO, "浪头过,船头抬……真的稳了些!这口诀,谁教你的?"),
("amo_e", "Dylan", _AMO, "守塔人……塔里就你一个人?家里人不拦着你干这行?"),
("c4_trueo", "Dylan", _AMO, "……对不起。难怪今晚,你一直都在。"),
("c4_passo", "Dylan", _AMO, "哦……你也是一个人守夜。我们俩,隔着一道海。"),
("amo_f", "Dylan", _AMO, "裂了!水进得更快了。塔,告诉我怎么办!"),
("c5_patcho", "Dylan", _AMO, "顶住了!水慢下来了。我不走,守着电台。"),
("c5_suito", "Dylan", _AMO, "穿好了。要是船散了,我就抱着它漂。"),
("c8_songo", "Dylan", _AMO, "海风那个吹呀——塔!是你吗?我在这儿!"),
("c8_matho", "Dylan", _AMO, "塔!你的灯扫到我啦!我在这边!"),
("c8_oldo", "Dylan", _AMO, "老航道……我爷爷念叨过这条道!稳了!"),
("c8_newo", "Dylan", _AMO, "一个,两个,三——过去了!塔,我过来了!"),
("amo_g", "Dylan", _AMO, "……我爷爷,就在那七个人里。难怪他到死,都念着白角的灯。"),
("amo_h", "Dylan", _AMO, "风太大……嗯。那晚的风,一定很大。"),
# 父亲(回忆·十二年前,Uncle_Fu,3 句)
("rev_f2", "Uncle_Fu", _DAD, "白角塔,我是老陈。接驳艇上两个人,都活着。"),
("rev_f3", "Uncle_Fu", _DAD, "守塔人听见了求救,就不能装没听见。等我回来。"),
("rev_f4", "Uncle_Fu", _DAD, "塔上的机械上满发条,能自己转一夜。给我留着门。"),
# 祖父(回忆·四十年前,Eric,5 句)
("rev_g2", "Eric", _GRAND, "手稳住。灯罩擦到第三遍,才算干净。"),
("rev_g3", "Eric", _GRAND, "记住,灯下压着的东西,比灯重。守塔人,替它守一辈子。"),
("rev_c2", "Eric", _GRAND, "我写下这些,等一个敢看的人。那晚,我的儿子,偷了接驳艇。"),
("rev_c3", "Eric", _GRAND, "我去追他。灯,离了人。半个时辰,回澜号,七个人。"),
("rev_c4", "Eric", _GRAND, "儿子回来了。这页我撕下来,压进灯底。守塔人,不能离灯。"),
]
NPCS = {"rpg": RPG_NPC, "radio": RADIO_NPC, "abyss": ABYSS_NPC, "detective": DETECTIVE_NPC,
"hunt": HUNT_NPC, "cipher": CIPHER_NPC, "bridge": BRIDGE_NPC, "teahouse": TEAHOUSE_NPC,
"keeper": KEEPER_NPC}
# ---------------------------------------------------------------- 键盘变体(P1 路线 a)
# 「生效输入模式=键盘」的玩家听按键措辞:向左滑→按左方向键(四向同理,WASD 不进
# 台词);单击/轻触屏幕→按空格;双击→按回车;按住/长按→按住空格。只改操作词,
# 其余内容一字不动(复合短语「左右滑」→「左右方向键」等按语义等价展开;确需
# 补一两个连接字才成句处已在 docs/design/*-voice-ids.md「键盘变体」小节逐句登记)。
# 主表为叙事误用(非操作指令)的句子不做变体:storyline n16/n16b(「滑了过去」
# 「按住他的膝盖」)、n17/n17b(「更滑」)。
# 双表一致铁律:本表与 docs/design/*-voice-ids.md「键盘变体」小节必须逐字一致。
KEY_LINES = {
"storyline": [
("help", "操作说明。按空格:继续剧情。按回车:重听刚才的台词。按上方向键:听这段说明和当前进度。按下方向键:呼出菜单,菜单里可以重新开始。当旁白说“请选择”的时候,按左方向键或者按右方向键,做出你的决定。选择无法撤销。"),
("menu", "菜单。按左方向键:重新开始整个故事。按右方向键,或者按空格:回到故事。按上方向键:听操作说明。"),
("p1", "请选择。按左方向键:先检查背包里的收音机。按右方向键:先摸黑寻找出口。"),
("p2", "请选择。按左方向键:钻进旁边的通风管道。按右方向键:躺进路边的尸体堆里装死。"),
("p3", "请选择。按左方向键:循着哭声过去看看。按右方向键:不去管它,直接去北区。"),
("p4", "请选择。按左方向键:带上小满一起走。按右方向键:留下食物,独自上路。"),
("p5", "请选择。按左方向键:走桥下的污水涵洞。按右方向键:爬桥上悬着的钢索。"),
("p6", "请选择。按左方向键:交出坐标,保命要紧。按右方向键:拒绝他,想办法周旋。"),
("p7", "请选择。按左方向键:追上去,警告避难所。按右方向键:自己逃命,越远越好。"),
("p8", "请选择。按左方向键:用公开频道广播,让全城都听见。按右方向键:用加密频段,只发给避难所。"),
("n00_intro", "大停电后的第三十天,城市陷入彻底的黑暗。没有电,没有网络,只有收音机里偶尔闪过的沙沙声,像垂死者的呼吸。你是林晚,一名前急救员。在这个故事里,你用声音活下去。请记住:按空格,继续剧情;按回车,重听上一句;按上方向键,听你的状态和帮助;按下方向键,呼出菜单。当我说“请选择”的时候,按左方向键,或者按右方向键,做出你的决定。选择一旦做出,就无法回头。现在,按空格,睁开眼睛。"),
("e1_dawn", "加密频道那头沉默了三秒。然后,一个你等了三十天的声音响了起来:“姐?姐!是你吗?”天亮之前,避难所的救援队打开了大门。猎狗的人,在北区的高墙下溃散了。你抱着晨晨,听见广播塔里,重新响起了音乐。后来人们说,这座城市,是从一个女人的声音开始,重新活过来的。——好结局:黎明。感谢你,陪林晚走完了这条路。按空格,可以回到故事开始的地方。"),
("e2_price", "你赶在车队前面,撞进了避难所的大门。“关掉广播!关灯!猎狗来了——!”那一夜,北区守住了。探照灯熄灭的时候,晨晨从人群里扑出来抱住你,才发现你胳膊上的血,已经把半边袖子浸透了。坐标是你交出去的,命,是你抢回来的。你赢了,用一条胳膊的代价。——结局:代价。按空格,重新开始。"),
("e3_alone", "你逃了。一路向南,没有回头。三天后,收音机里关于北区的广播,停了。又过了七天,你在一个废弃的加油站里,听见最后一个频段,也变成了沙沙声。你活下来了。只是从此以后的每一个夜里,你都会想起那声口哨,和你报出坐标时,自己发抖的声音。——坏结局:孤声。按空格,重新开始。"),
("e4_static", "公开频道,把所有人都引来了——幸存者,还有猎狗。塔顶的风很大,你看见山下火把连成的长龙,也看见避难所方向亮起的防御火光。猎狗的刀抵上来的时候,你对着麦克风笑了:“听见了吗,晨晨?姐姐的声音,全城都听见了。”信号中断了。但那晚之后,北区,再没有人投降。——结局:静默。按空格,重新开始。"),
("skip_hint", "按空格可跳过旁白。"),
("gd_continue", "按空格,继续。"),
("gd_brief", "按空格继续剧情。请选择的时候,按左右方向键。"),
("gd_again", "还在吗?按空格继续。"),
],
"escape": [
("g1_move", "先活动一下。按任意方向键,走一小步。"),
("g2_wall", "这个房间不大,四面都是墙。再按两下方向键,试试碰碰墙。"),
("g3_left", "跟着我指的路走。按左方向键。"),
("g4_down", "对。再按下方向键。"),
("g5_inspect", "听到水声了吗?按一下空格,仔细检查这个房间。"),
("g6_doubletap", "钥匙能开锁。按回车,是把物品用出去的用法。现在,按回车试试。"),
("g7_goal", "找到能用这把钥匙打开的东西。需要帮助时,在原地按回车。"),
("guide_wrong_up", "不是这个方向。按上方向键。"),
("guide_wrong_down", "不是这个方向。按下方向键。"),
("guide_wrong_left", "不是这个方向。按左方向键。"),
("guide_wrong_right", "不是这个方向。按右方向键。"),
("guide_blunt_up", "按一下上方向键。"),
("guide_blunt_down", "按一下下方向键。"),
("guide_blunt_left", "按一下左方向键。"),
("guide_blunt_right", "按一下右方向键。"),
("reopen_prompt", "按上方向键,重新听一遍引导。按空格,直接开始。"),
("stuck_key_2", "去西南角的储物间,按空格检查,找到钥匙。"),
("stuck_cabinet_2", "去东边的铁柜房,按回车,用钥匙打开铁柜。"),
("stuck_box_2", "去西北角的电箱房,按回车,把保险丝装进电箱。"),
("stuck_gate_2", "去东南角的大门,按回车,打开它,离开这里。"),
("nav_up", "按上方向键。"),
("nav_down", "按下方向键。"),
("nav_left", "按左方向键。"),
("nav_right", "按右方向键。"),
("start_room", "你站在设施中央的房间里。方向键移动,按空格检查,按回车使用物品。"),
("inspect_gate_ready", "电子锁的指示灯亮了,发出微弱的绿光。大门已经通电。按回车,打开它,离开这里。"),
("use_fail", "这里没有用得上东西的地方。换个房间试试,或者先按空格检查一下周围。"),
("again", "按空格,再来一次。按下方向键,打开菜单。"),
("help_ops", "方向键,在房间之间移动。按空格,检查房间,或者重听房间的声音。按回车,使用物品,或者查看背包。"),
("skip_hint", "按空格可跳过旁白。"),
],
"simon": [
("t1_up", "按上方向键,是这个声音。"),
("t2_down", "按下方向键,是这个声音。"),
("t3_left", "按左方向键,是这个声音。"),
("t4_right", "按右方向键,是这个声音。"),
("t5_tap", "按空格,是这个声音。"),
("t6_double", "按回车,是这个声音。"),
("t_wrong_up", "不是这个,按上方向键试试。"),
("t_wrong_down", "不是这个,按下方向键试试。"),
("t_wrong_left", "不是这个,按左方向键试试。"),
("t_wrong_right", "不是这个,按右方向键试试。"),
("t_wrong_tap", "不是这个,按空格试试。"),
("t_wrong_double", "不是这个,按回车试试。"),
("t_blunt_up", "按一下上方向键。"),
("t_blunt_down", "按一下下方向键。"),
("t_blunt_left", "按一下左方向键。"),
("t_blunt_right", "按一下右方向键。"),
("t_blunt_tap", "轻轻按一下空格。"),
("t_blunt_double", "按一下回车。"),
("t7_retry", "答错可以消耗机会重听。按上方向键,可以随时重听。"),
("replay", "按空格,再来一局。"),
("rules_prompt", "按上方向键,重听规则。按下方向键,打开菜单。"),
("rules", "上行音,按上方向键。下行音,按下方向键。左边的音,按左方向键。右边的音,按右方向键。一声轻响,按空格。两声轻响,按回车。"),
("skip_hint", "按空格可跳过旁白。"),
("gd_relisten", "需要的话,按上方向键可以重新听一遍序列。"),
("gd_economy", "记不住就按上方向键,重新听一遍。免费机会有两次,用完要扣一次机会。"),
("gd_occ_up", "这一轮,按上方向键是作答。"),
("gd_occ_dt", "这一轮,按回车是作答。"),
("gd_occ_both", "这一轮,按上方向键和回车都是作答。"),
],
# rhythm:8 对(24-rhythm-voice-ids.md 键盘变体表,措辞含数字键/方向键/空格/回车)。
"rhythm": [
("tut_map", "先认识你的鼓。屏幕横着分三列,左边、中间、右边;竖着分两行,上面清亮,下面低沉。按数字一键到六键,听六面鼓。"),
("tut_probe", "按出三个不同的键,我们就开始。"),
("tut_z_kick", "数字五,是底鼓,你的家。先听四下数拍,然后跟着我敲。"),
("tut_z_sides", "数字四,军鼓。数字六,低嗵。一左一右,跟着打。"),
("tut_z_patt", "上面一排更清亮。数字一踩镲,数字二高嗵,数字三吊镲。听这一句,三个音。"),
("select_hint", "左右方向键选曲目,按空格开始,按下方向键回大厅。"),
("restart", "按空格再来一遍,按右方向键下一首,按下方向键回大厅。"),
("menu_main", "菜单。按空格切换选项,按回车确认。"),
],
"space": [
("drill_left", "听,左边有陨石。按左方向键,把它拨开!"),
("drill_right", "这次在右边。按右方向键!"),
("drill_up", "上面有东西,声音又尖又高。按上方向键!"),
("drill_down", "下面是低沉的轰隆。按下方向键!"),
("drill_retry_left", "那是左边来的,按左方向键。"),
("drill_retry_right", "那是右边来的,按右方向键。"),
("drill_retry_up", "声音又高又尖,是上面,按上方向键。"),
("drill_retry_down", "轰隆声在下面,按下方向键。"),
("protect_left", "训练护盾接住了。那颗在左边,听到左边的呼啸,按左方向键。"),
("protect_right", "训练护盾接住了。那颗在右边,听到右边的呼啸,按右方向键。"),
("protect_up", "训练护盾接住了。那颗在上面,听到高高的呼啸,按上方向键。"),
("protect_down", "训练护盾接住了。那颗在下面,听到低沉的轰隆,按下方向键。"),
("feat_boost", "试试按空格,引擎会短促加速,直接冲过空隙。"),
("feat_shield", "按回车展开护盾,能挡下一次撞击。"),
("hit_left", "左边撞上的。听到左边的呼啸,按左方向键。"),
("hit_right", "右边撞上的。听到右边的呼啸,按右方向键。"),
("hit_up", "上面撞上的。听到高高的呼啸,按上方向键。"),
("hit_down", "下面撞上的。听到低沉的轰隆,按下方向键。"),
("restart_hint", "按空格再飞一次,按下方向键回大厅。"),
("second_run_ask", "按上方向键重新听演练,按空格直接出发。"),
("menu_main", "菜单。按空格切换选项,按回车确认。"),
("help_recap", "左边来按左方向键,右边来按右方向键,上面来按上方向键,下面来按下方向键。按空格加速,按回车护盾。"),
("skip_hint", "按空格可跳过旁白。"),
("gd_hit_hint", "被撞了。听方位,按对应方向键就能拨开。"),
],
"rpg": [
("sys_continue_ask", "检测到你的旅程记录。按空格继续,按右方向键开始新的旅程。"),
("sys_menu", "菜单。左右方向键选择,按空格确认。"),
("help_explore", "左右方向键切换选项,按空格确认。按回车重新听场景,按上方向键查看状态,按下方向键打开菜单。"),
("help_battle", "战斗中,左右方向键选招式,按空格出手。听到敌人的前摇,按回车格挡。"),
("btl_lose_hint", "记住,听清前摇,按回车格挡。"),
("tut_b2", "就是现在,按回车格挡!"),
("tut_b3", "挡住了!轮到你了。左右方向键选攻击,按空格出剑。"),
("shop_open", "商店。左右方向键看看货,按空格购买。"),
("skip_hint", "按空格可跳过旁白。"),
("tut_battle_2", "左右方向键切换招式,攻击、破空斩、防御、金疮药。按空格,出手。"),
("tut_battle_3", "敌人出手前有一声前摇。听到前摇,马上按回车,就能格挡。"),
("tut_battle_5", "记住:听清前摇,按回车格挡。开始吧!"),
("tut_b0", "战斗里,左右方向键选招式,按空格出手。听到这个声音,就是你的回合。先按一按方向键,试试。"),
("tut_b6", "来不及格挡,就选防御。左右方向键,选防御,按空格。"),
("gd_parry_miss", "前摇声起的时候按回车,才能格挡。"),
("gd_stall", "想好了吗?左右方向键选择,按空格出手。"),
],
"radio": [
("gd_brief", "按空格继续节目。请选择的时候,按左或右方向键。"),
("gd_continue", "按空格,继续。"),
("gd_again", "还在吗?按空格继续。"),
("gd_ring1", "热线在闪。按空格,接听。"),
("gd_ring2", "灯还亮着,听众在等。按空格,接听。"),
("sys_help", "按空格继续,按回车重新听。按上方向键听状态,按下方向键开菜单。请选择时,左右方向键。"),
("sys_menu", "菜单。按空格切换选项,按回车确认。"),
("sys_skip", "想跳过这句,按空格就行。"),
("sys_continue_ask", "检测到你的夜班记录。按空格继续上次,按右方向键开始新的夜晚。"),
("p1", "老人在发抖。按左方向键,陪他哼那首老歌。按右方向键,冷静问他路口的路牌。"),
("p2", "雨声更密了。按左方向键,留在原地陪他等巡逻队。按右方向键,转接派出所。"),
("p3", "按左方向键,告诉她这趟车值得上。按右方向键,把选择还给她。"),
("p4", "按左方向键,唱一段摇篮曲哄她睡。按右方向键,轻轻问她怎么了。"),
("p5", "孩子的声音在抖。按左方向键,告诉她这不是她的错。按右方向键,答应她明天都会好。"),
("p6", "摔门声你听见了。按左方向键,替孩子给她带句话。按右方向键,劝她回家把话说开。"),
("p7", "她的呼吸平了。按左方向键,留在直播里,让全城等这个门铃。按右方向键,换垫乐,私下陪她说完。"),
("p8", "方向盘比话筒危险。按左方向键,陪他聊聊闺女。按右方向键,让他立刻进服务区。"),
("p9", "留言机红灯在闪,是老主播三年前没播完的那句晚安。按左方向键,替他播出去。按右方向键,照常收播。"),
],
"dungeon": [
("sys_continue_ask", "检测到探窟记录。按空格继续,按右方向键重新开始。"),
("tut_d0", "先学会用耳朵看。按空格,发出声波脉冲。"),
("tut_walk", "按住空格不放,你就往前走。"),
("tut_turn", "按左右方向键,可以转身。"),
("tut_replay", "按回车,再发一次脉冲,不要钱。"),
("tut_status", "按上方向键,随时听自己的状态。"),
("tut_menu", "按下方向键,打开菜单。"),
("tut_parry", "听到它的动静,按回车格挡。"),
("tut_attack", "现在按空格,挥拳打它。"),
("tut_flee_hold", "想逃跑,按住空格别松手。"),
("tut_stairs_on", "楼梯就在脚下。按空格,下潜。"),
("tut_skip", "已跳过教学。按空格发脉冲,先听听四周。"),
("sys_menu", "菜单。按空格切换,按回车确认。"),
("help_explore", "按空格发脉冲,按住空格前进,左右方向键转身。按回车再听,上方向键查状态,下方向键开菜单。"),
("help_battle", "敌人前摇声响时按回车格挡,按空格挥击。打不过就按住空格逃跑。"),
("die_adv_1", "下次,前摇声响时就按回车。"),
("die_adv_2", "打不过,就按住空格逃跑。"),
("gd_pulse", "按空格发脉冲,听听四周。"),
("gd_turn", "轮到你了。按空格挥击。"),
("settle_again", "按空格,再探一次。"),
],
# abyss:11 对(17-abyss-voice-ids.md §G0.a 9 对 + §D1 dv1_teach/p1;措辞「左箭头/右箭头」
# 「上箭头/下箭头」为文档原文,与 dungeon 的「左方向键」系两文档各自定稿措辞,不改写)。
"abyss": [
("gd_brief", "按空格继续简报。要选择时,用左右方向键。"),
("gd_continue", "按空格,继续。"),
("gd_again", "还在吗?按空格继续。"),
("gd_ping1", "声呐待命。按空格,发脉冲。"),
("gd_ping2", "回波不会自己来。按空格。"),
("sys_help", "空格继续,回车重新听。上箭头听状态,下箭头开菜单。选择时,左右箭头。"),
("sys_menu", "菜单。空格切换选项,回车确认。"),
("sys_skip", "听过的句子再播时,按空格跳过。"),
("sys_continue_ask", "检测到你的下潜记录。空格继续上次,右箭头开始新救援。"),
("dv1_teach", "到深度了。声呐静了。按空格,发一枚脉冲。"),
("p1", "管路在响。左箭头,停下来听敲管的暗号。右箭头,先带小舟上浮。"),
],
# detective:V 组 14 对(18-detective-voice-ids.md 文末;_k 逻辑 id 落 key/ 子目录;R2 +4 对)。
"detective": [
("sys_help", "方向键换卷宗,空格听取,回车重新听。按住空格指证,上键状态,下键菜单。"),
("tut_d0", "先熟悉证词板。左右方向键,换卷宗。"),
("tut_listen", "空格,听取当前卷宗。"),
("tut_replay", "回车,重新听这一句。"),
("tut_confront", "按住空格,进入指证。"),
("gd_board", "还在证词板。左右方向键,换卷宗。"),
("gd_cf", "还在选证词。左右方向键,空格确认。"),
("cf_pick_line", "左右方向键,一句一句听。"),
("cf_confirm", "就这句?空格,定下来。"),
("settle_hook", "同一场雨里,还有别的案子。按空格,接新案。"),
("fin_pre", "证据在手,就此结案?空格,定下来;方向键,继续查。"),
("fin_who_confirm", "就指认这位?空格,定下来。"),
("recap_cf", "卷宗都在耳朵里了。挑一句你不信的,按住空格指证。"),
("gd_board_cf", "都听过了。按住空格,去指证。"),
],
# hunt:V 组 11 对(23-hunt-voice-ids.md 文末;措辞「方向键/空格/回车」同 dungeon 规范)。
"hunt": [
("room_wait", "房间已开好,等同伴到齐。按空格,立即开局。"),
("tut_rule", "野兽会从八个方向围上来。声音在哪边,就按哪边方向键。"),
("tut_attack", "听,有狼在靠近。朝声音的方向,按方向键。"),
("tut_callout", "按空格,呼喊支援,队友会来帮你。"),
("tut_skill", "按回车,旋风斩,清掉身边一圈。"),
("tut_skill_cd", "旋风斩有八秒冷却。冷却中按回车,重新听威胁。"),
("tut_status", "按上方向键,听营地状态。按下方向键,打开菜单。"),
("sys_menu", "菜单。按空格切换,按回车确认。"),
("help_main", "朝声音的方向按方向键,就是出击。按空格呼喊支援,按回车旋风斩。上方向键状态,下方向键菜单。"),
("end_again", "按空格,再猎一局。"),
("gd_idle", "听声音的方向,按方向键。"),
],
# cipher:11 对(19-cipher-voice-ids.md §G0.a 9 对 + §M1 dv1_teach2/p2;措辞
# 「左右方向键/上箭头/下箭头/左箭头/右箭头」为文档原文,与 hunt「方向键」系各文档定稿措辞)。
"cipher": [
("gd_brief", "按空格继续值机。要选词时,用左右方向键。"),
("gd_continue", "按空格,继续。"),
("gd_again", "还在吗?按空格继续。"),
("gd_tune1", "电文在等。左右方向键,听候选词。"),
("gd_tune2", "按回车,把电文再听一遍。再按一次,会放慢。"),
("sys_help", "空格选定,回车重新听电文。上箭头听状态,下箭头开菜单。选择时,左右箭头。"),
("sys_menu", "菜单。空格切换选项,回车确认。"),
("sys_skip", "想跳过这句,按空格。"),
("sys_continue_ask", "检测到你的值机记录。空格继续上次,右箭头开始新的夜班。"),
("dv1_teach2", "左右方向键,听密码本上的候选词。空格,选定一个。"),
("p2", "药品船,丑时过江。左箭头,把船期报给北风。右箭头,压进值机日志。"),
],
# bridge:V 组 10 对(22-bridge-voice-ids.md §V 组;措辞「方向键/空格/回车」同 hunt 规范)。
"bridge": [
("sys_help", "巡航中,上键巡检全舰;危机中,方向键听选项,空格下令。下键菜单,回车重新听。"),
("tut_d0", "先试巡检。按上键,听全舰状态。"),
("tut_decide", "方向键听选项,空格下令。"),
("cr_decide", "听选项,空格下令。"),
("dock_open", "靠港整备。方向键看项目,空格执行。"),
("gd_cruise", "还在巡航。上键,巡检全舰。"),
("gd_decide", "还在等命令。方向键,听选项。"),
("gd_dock", "还在整备。方向键看,空格执行。"),
("sys_continue_ask", "检测到未完成的巡逻。空格继续,右方向键进星港。"),
("settle_hook", "星港期待你的下一次巡逻。按空格,返航。"),
],
# teahouse:V 组 10 对(20-teahouse-voice-ids.md §V 组;措辞「方向键/空格/回车」同 hunt 规范)。
"teahouse": [
("sys_help", "方向键选茶,空格定下。听水声,回车提壶;滴答密,回车出汤。上键状态,下键菜单。"),
("tut_d0", "先看茶柜。左右方向键,一罐一罐听。"),
("tut_tea", "空格,选定这罐茶。"),
("tut_water", "灶上坐水。泡泡细密是将沸,咕嘟连片是滚透——听到细密,回车提壶。"),
("tut_steep", "注水,闷泡。滴答由稀到密,密起来就是将好——回车,出汤。"),
("tut_serve", "茶好了。空格,端出去。"),
("gd_serve", "客人还在堂前。上键,再听一遍他要的茶。"),
("gd_brew", "还在灶上,听声动手,回车。"),
("settle_night", "熄了灯,这一晚就过去了。按空格,睡下。"),
("upg_open", "要添置点什么?左右方向键看,空格买定。"),
],
# keeper:8 对(21-keeper-voice-ids.md §G0.a 7 对 + 首个抉择 p1;措辞「方向键/空格/
# 回车/左右箭头」为 21b 文档原文,与 abyss §G0.a 同型,逐字照录不改写)。
"keeper": [
("gd_brief", "按空格,继续值守。要选择时,用左右方向键。"),
("gd_continue", "按空格,继续。"),
("gd_again", "还在吗?按空格继续。"),
("sys_help", "空格继续,回车重新听。上箭头听状态,下箭头开菜单。选择时,左右箭头。"),
("sys_menu", "菜单。空格切换选项,回车确认。"),
("sys_skip", "想跳过这句,按空格。"),
("sys_continue_ask", "检测到你的值守记录。空格继续上次,右箭头开始新的风暴夜。"),
("p1", "他的声音在抖。左箭头,先接住这个人。右箭头,先要他的船位。"),
],
}
def key_variants(game):
"""key 变体表 [(fid, instruct_pair, text), ...]。
约束(启动即校验,防两表漂移):
- key id 必须存在于该游戏主台词表(key 集合 ⊆ 主集合),复用其 instruct
与 VOICE_SPK speaker(radio HOST/PROD 配对自动生效);
- key 表内 id 不得重复。
"""
ins_by_id = {fid: ins for fid, ins, _t in GAMES[game]}
seen = set()
out = []
for fid, text in KEY_LINES[game]:
if fid not in ins_by_id:
raise SystemExit(f"key 变体 {game}/{fid} 不在主台词表中(key 集合必须 ⊆ 主集合)")
if fid in seen:
raise SystemExit(f"key 变体重复 id:{game}/{fid}")
seen.add(fid)
out.append((fid, ins_by_id[fid], text))
return out
# radio 双性别行的逐句 speaker 覆盖(HOST=主播 / PROD=导播,同性别两个音色,
# 见 15-radio-voice-ids.md 录制说明:同场景高频对话的两者永不同 speaker)。
VOICE_SPK = {"radio": {}}
VOICE_SPK["radio"].update({fid: ("Uncle_Fu", "Serena") for fid, _i, _t in RADIO_HOST_LINES})
VOICE_SPK["radio"].update({fid: ("Dylan", "Vivian") for fid, _i, _t in RADIO_PROD_LINES})
# abyss 双性别行的逐句 speaker 覆盖(DIVER=潜航员玩家 / DECK=母船调度,17b 录制说明:
# female 侧玩家 Serena/调度 Vivian、male 侧玩家 Uncle_Fu/调度 Dylan,同场景永不同 speaker)。
VOICE_SPK["abyss"] = {}
VOICE_SPK["abyss"].update({fid: ("Uncle_Fu", "Serena") for fid, _i, _t in ABYSS_DIVER_LINES})
VOICE_SPK["abyss"].update({fid: ("Dylan", "Vivian") for fid, _i, _t in ABYSS_DECK_LINES})
# cipher 双性别行的逐句 speaker 覆盖(OP=报务员玩家 / WIND=北风电台+系统音,19b 录制说明:
# female 侧你 Serena / 北风 Vivian、male 侧你 Uncle_Fu / 北风 Dylan,同场景永不同 speaker)。
VOICE_SPK["cipher"] = {}
VOICE_SPK["cipher"].update({fid: ("Uncle_Fu", "Serena") for fid, _i, _t in CIPHER_OP_LINES})
VOICE_SPK["cipher"].update({fid: ("Dylan", "Vivian") for fid, _i, _t in CIPHER_WIND_LINES})
# keeper 双性别行的逐句 speaker 覆盖(KEEPER=守塔人玩家 / SHORE=岸台值班员+系统音,
# 21b 录制说明:female 侧守塔人 Serena/岸台 Vivian、male 侧守塔人 Uncle_Fu/岸台 Dylan,
# 同场景高频对话的两者永不同 speaker,radio HOST/PROD / abyss DIVER/DECK 同型)。
VOICE_SPK["keeper"] = {}
VOICE_SPK["keeper"].update({fid: ("Uncle_Fu", "Serena") for fid, _i, _t in KEEPER_K_LINES})
VOICE_SPK["keeper"].update({fid: ("Dylan", "Vivian") for fid, _i, _t in KEEPER_S_LINES})
# radio 来电者电话滤波(15-radio.md §6.2,全部 + 轻压缩):按 NPC id 前缀分档,
# convert 转码时应用;staging wav 保留未滤波原声以便重滤/回退(宁窄勿脏)。
NPC_BANDPASS = {
"radio": [
("z1_", "highpass=f=500,lowpass=f=2800"), # 老周:老旧手机,窄而颤抖
("l1_", "highpass=f=400,lowpass=f=3400"), # 小鹿:车站手机,干净偏冷
("k1_", "highpass=f=450,lowpass=f=3000"), # 糖糖:家用座机,小而近
("w1_", "highpass=f=420,lowpass=f=3100"), # 陈姐:户外手机,风噪
("h1_", "highpass=f=300,lowpass=f=3400"), # 老韩:车载蓝牙,宽而轰
],
# abyss 船员水声电台滤波(17-abyss.md §5.3/17b 头注,全部 + 轻压缩):
# 「电台里的人」与「耳机里的人」靠音质差立住;d1_log 是小舟(z1_ 前缀外单独登记)。
"abyss": [
("z1_", "highpass=f=420,lowpass=f=3200"), # 小舟:年轻女声,窄带
("d1_log", "highpass=f=420,lowpass=f=3200"),
("bai_", "highpass=f=350,lowpass=f=3000"), # 老白:沙哑中年男,中窄带
("chen_", "highpass=f=400,lowpass=f=3100"), # 陈医官:平静女声
("gao_", "highpass=f=300,lowpass=f=2800"), # 高船长:苍老男声,最低最窄
],
# keeper NPC 滤波(21-keeper-voice-ids.md 头注 §滤波表,全部 + 轻压缩):阿澈=电台
# 350-3200;父亲=唱片 300-2800;祖父=唱片 250-2600(三代人频带逐人递减,最老者最窄最「远」)。
# 前缀只命中 NPC 平铺表内的 id(锚句 rev_f1/f5、rev_g1/g4、rev_c1/c5 是 KEEPER 双性别行不滤波)。
"keeper": [
("amo_", "highpass=f=350,lowpass=f=3200"), # 阿澈:年轻渔民电台嗓
("rev_f", "highpass=f=300,lowpass=f=2800"), # 父亲:中年男声,老唱片
("rev_g", "highpass=f=250,lowpass=f=2600"), # 祖父(插叙1):苍老男声,更老唱片
("rev_c", "highpass=f=250,lowpass=f=2600"), # 祖父(插叙3):同档
],
}
_NPC_ACOMP = "acompressor=threshold=-20dB:ratio=3:attack=10:release=150" # 轻压缩(全档)
def npc_af(game, fid):
"""NPC id -> ffmpeg -af 字符串(无滤波档返回 None)。"""
for prefix, af in NPC_BANDPASS.get(game, []):
if fid.startswith(prefix):
return af + "," + _NPC_ACOMP
return None
GAMES = {
"storyline": STORYLINE_LINES,
"escape": ESCAPE_LINES,
"simon": SIMON_LINES,
"rhythm": RHYTHM_LINES,
"space": SPACE_LINES,
"rpg": RPG_LINES,
"radio": RADIO_LINES,
"dungeon": DUNGEON_LINES,
"abyss": ABYSS_LINES,
"detective": DETECTIVE_LINES,
"hunt": HUNT_LINES,
"cipher": CIPHER_LINES,
"bridge": BRIDGE_LINES,
"teahouse": TEAHOUSE_LINES,
"keeper": KEEPER_LINES,
}
# ---------------------------------------------------------------- 工具函数
def split_text(text, limit=SPLIT_LIMIT):
"""按句末标点拆句并合并到 <=limit 字;超长单句再按逗号拆。"""
if len(text) <= limit:
return [text]
sents = [s for s in re.split(r"(?<=[。!?;…])", text) if s]
chunks, cur = [], ""
for s in sents:
if cur and len(cur) + len(s) > limit:
chunks.append(cur)
cur = s
else:
cur += s
if cur:
chunks.append(cur)
out = []
for c in chunks:
if len(c) <= limit:
out.append(c)
else: # 单句仍超限,按逗号硬拆
parts = [p for p in re.split(r"(?<=[,、:])", c) if p]
buf = ""
for p in parts:
if buf and len(buf) + len(p) > limit:
out.append(buf)
buf = p
else:
buf += p
if buf:
out.append(buf)
return [o for o in out if o.strip()]
def staging_path(game, gender, fid, variant=""):
"""staging wav 路径;variant='key' 时落 <gender>/key/ 子目录。"""
parts = [STAGING, game, gender]
if variant:
parts.append(variant)
return os.path.join(*parts, fid + ".wav")
def valid_wav(path):
import numpy as np
import soundfile as sf
if not os.path.exists(path):
return False
try:
data, sr = sf.read(path)
except Exception:
return False
if len(data) == 0:
return False
dur = len(data) / sr
peak = float(np.max(np.abs(data)))
return dur > 0.3 and peak > 0.05
# ---------------------------------------------------------------- 生成
def _generate_lines(model, game, gender, lines, variant=""):
"""一组双性别台词(主表或 key 变体)生成到 staging <game>/<gender>[/<variant>]/。"""
import numpy as np
import soundfile as sf
speaker = SPEAKERS[game][gender]
ins_idx = 0 if gender == "male" else 1
spk_map = VOICE_SPK.get(game, {})
outdir = os.path.join(STAGING, game, gender, variant) if variant else os.path.join(STAGING, game, gender)
os.makedirs(outdir, exist_ok=True)
# 展开成 (fid, chunk_idx, n_chunks, text, instruct, speaker),跳过已完成
items = []
for fid, ins_pair, text in lines:
path = staging_path(game, gender, fid, variant)
if valid_wav(path):
continue
spk = spk_map.get(fid, (speaker, speaker))[ins_idx]
chunks = split_text(text)
for ci, ctext in enumerate(chunks):
items.append((fid, ci, len(chunks), ctext, ins_pair[ins_idx], spk))
if not items:
print(f"[{game}/{gender}/{variant or '-'}] nothing to do", flush=True)
return
# 短句(数字类)优先、同 instruct 聚合成整批,减少 prosody 漂移
shorts = [it for it in items if len(it[3]) <= 2]
longs = [it for it in items if len(it[3]) > 2]
queue = ([("short", shorts[i:i + BATCH_SHORT]) for i in range(0, len(shorts), BATCH_SHORT)]
+ [("long", longs[i:i + BATCH_LONG]) for i in range(0, len(longs), BATCH_LONG)])
results = {} # fid -> {chunk_idx: wav}
for bi, (kind, batch) in enumerate(queue):
wavs, sr = model.generate_custom_voice(
text=[b[3] for b in batch],
language=["Chinese"] * len(batch),
speaker=[b[5] for b in batch],
instruct=[b[4] for b in batch],
max_new_tokens=2048,
)
for (fid, ci, _n, _t, _i, _s), wav in zip(batch, wavs):
results.setdefault(fid, {})[ci] = np.asarray(wav, dtype=np.float32)
print(f"[{game}/{gender}/{variant or '-'}] batch {bi + 1}/{len(queue)} ({kind}, n={len(batch)})", flush=True)
gap = np.zeros(int(GAP_SEC * sr), dtype=np.float32)
for fid, ins_pair, text in lines:
path = staging_path(game, gender, fid, variant)
if valid_wav(path):
continue
got = results.get(fid, {})
n_chunks = len(split_text(text))
assert len(got) == n_chunks, f"{game}/{gender}/{variant}/{fid}: 缺 chunk {len(got)}/{n_chunks}"
wav = got[0]
for ci in range(1, n_chunks):
wav = np.concatenate([wav, gap, got[ci]])
peak = float(np.max(np.abs(wav)))
assert peak > 0.05, f"{game}/{gender}/{variant}/{fid}: peak {peak:.3f}"
wav = wav / peak * PEAK_NORM
dur = len(wav) / sr
assert dur > 0.3, f"{game}/{gender}/{variant}/{fid}: dur {dur:.2f}s"
sf.write(path, wav, sr)
print(f"[ok] {game}/{gender}/{variant}/{fid} {dur:.2f}s chunks={n_chunks}", flush=True)
def generate(games, genders, only, variants_only=False):
import numpy as np
import soundfile as sf
import torch
from qwen_tts import Qwen3TTSModel
model = Qwen3TTSModel.from_pretrained(
"Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice",
device_map="cuda:0",
dtype=torch.bfloat16,
)
print("model loaded", flush=True)
for game in games:
if not variants_only:
lines = GAMES[game]
if only:
lines = [x for x in lines if x[0] in only]
for gender in genders:
_generate_lines(model, game, gender, lines)
# 键盘变体(key 集合 ⊆ 主集合,复用主表 instruct/speaker)
kvars = key_variants(game)
if only:
kvars = [x for x in kvars if x[0] in only]
for gender in genders:
_generate_lines(model, game, gender, kvars, variant="key")
# NPC 单音色平铺(staging <game>/npc/,convert 落 flat voice/)。
# --only-variants 时跳过:R5 起部分 NPC 资产已先行落盘而 staging 无 wav
# (见 RPG_LINES R5 注释),变体跑批不应顺手重录已验收 NPC 台词——
# 否则 --convert 会用新 take 覆盖 web/ 下已验收资产。
npc_lines = None if variants_only else NPCS.get(game)
if npc_lines:
outdir = os.path.join(STAGING, game, "npc")
os.makedirs(outdir, exist_ok=True)
todo = [x for x in npc_lines if not valid_wav(os.path.join(outdir, x[0] + ".wav"))]
for i in range(0, len(todo), BATCH_LONG):
batch = todo[i:i + BATCH_LONG]
wavs, sr = model.generate_custom_voice(
text=[b[3] for b in batch],
language=["Chinese"] * len(batch),
speaker=[b[1] for b in batch],
instruct=[b[2] for b in batch],
max_new_tokens=2048,
)
for (fid, _sp, _ins, _t), wav in zip(batch, wavs):
wav = np.asarray(wav, dtype=np.float32)
peak = float(np.max(np.abs(wav)))
assert peak > 0.05, f"{game}/npc/{fid}: peak {peak:.3f}"
wav = wav / peak * PEAK_NORM
dur = len(wav) / sr
assert dur > 0.3, f"{game}/npc/{fid}: dur {dur:.2f}s"
sf.write(os.path.join(outdir, fid + ".wav"), wav, sr)
print(f"[ok] {game}/npc/{fid} {dur:.2f}s", flush=True)
# ---------------------------------------------------------------- 检查
def check(games):
import numpy as np
import soundfile as sf
def check_set(game, lines, variant=""):
problems = []
prefix = f"key/" if variant else ""
for fid, _ins, _text in lines:
durs = {}
for gender in ("male", "female"):
path = staging_path(game, gender, fid, variant)
if not os.path.exists(path):
durs[gender] = None
continue
data, sr = sf.read(path)
peak = float(np.max(np.abs(data)))
durs[gender] = len(data) / sr
if durs[gender] <= 0.3 or peak <= 0.05:
problems.append(f"{game}/{gender}/{prefix}{fid}: 质量不达标 dur={durs[gender]:.2f} peak={peak:.3f}")
m, f = durs["male"], durs["female"]
if m is None or f is None:
problems.append(f"{game}/{prefix}{fid}: 缺 {'male' if m is None else 'female'}")
continue
ratio = max(m, f) / min(m, f)
flag = " <-- OUTLIER" if ratio > 3 else ""
if ratio > 3:
problems.append(f"{game}/{prefix}{fid}: 时长离群 m={m:.2f}s f={f:.2f}s ratio={ratio:.1f}")
print(f" {prefix}{fid:24s} m={m:6.2f}s f={f:6.2f}s{flag}")
return problems
problems = []
for game in games:
print(f"=== {game} ===")
problems += check_set(game, GAMES[game])
kvars = key_variants(game)
if kvars:
print(f" --- key 变体 {len(kvars)} 句 ---")
problems += check_set(game, kvars, variant="key")
print()
if problems:
print("PROBLEMS:")
for p in problems:
print(" ", p)
return 1
print("ALL OK")
return 0
# ---------------------------------------------------------------- 转码
def convert(games):
# 只按台词表转换(不扫目录),staging 里残留的旧 id wav 不会落盘
total = 0
for game in games:
for gender in ("male", "female"):
dst_dir = os.path.join(ROOT, "web", game, "audio", "voice", gender)
os.makedirs(dst_dir, exist_ok=True)
for fid, _ins, _text in GAMES[game]:
src = staging_path(game, gender, fid)
if not os.path.exists(src):
print(f"[convert] WARN missing {game}/{gender}/{fid}.wav")
continue
base = os.path.join(dst_dir, fid)
subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", src,
"-ar", "44100", "-c:a", "libvorbis", "-q:a", "3", base + ".ogg"], check=True)
subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", src,
"-ar", "44100", "-c:a", "aac", "-b:a", "128k", base + ".m4a"], check=True)
total += 1
# 键盘变体:落 voice/<gender>/key/ 子目录(引擎 key 模式候选链前置)
kdst = os.path.join(dst_dir, "key")
for fid, _ins, _text in key_variants(game):
src = staging_path(game, gender, fid, "key")
if not os.path.exists(src):
print(f"[convert] WARN missing {game}/{gender}/key/{fid}.wav")
continue
os.makedirs(kdst, exist_ok=True)
base = os.path.join(kdst, fid)
subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", src,
"-ar", "44100", "-c:a", "libvorbis", "-q:a", "3", base + ".ogg"], check=True)
subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", src,
"-ar", "44100", "-c:a", "aac", "-b:a", "128k", base + ".m4a"], check=True)
total += 1
# NPC 平铺(staging <game>/npc/ → audio/voice/<id>.*;radio 按 NPC_BANDPASS 带通)。
# 异例:hunt 喊话走 sfx 层带 pan(23-hunt.md §8 R2 / §15 裁决 7),落 audio/sfx/。
npc_layer = "sfx" if game == "hunt" else "voice"
for fid, _sp, _ins, _t in NPCS.get(game, []):
src = os.path.join(STAGING, game, "npc", fid + ".wav")
if not os.path.exists(src):
print(f"[convert] WARN missing {game}/npc/{fid}.wav")
continue
base = os.path.join(ROOT, "web", game, "audio", npc_layer, fid)
af = npc_af(game, fid)
af_args = ["-af", af] if af else []
subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", src, *af_args,
"-ar", "44100", "-c:a", "libvorbis", "-q:a", "3", base + ".ogg"], check=True)
subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", src, *af_args,
"-ar", "44100", "-c:a", "aac", "-b:a", "128k", base + ".m4a"], check=True)
total += 1
print(f"[convert] {game} done")
print(f"converted {total} files (ogg+m4a each)")
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--game", required=True,
choices=list(GAMES.keys()) + ["all"])
ap.add_argument("--gender", default="both", choices=["male", "female", "both"])
ap.add_argument("--only", default="", help="逗号分隔的 id 列表,只生成这些")
ap.add_argument("--only-variants", action="store_true",
help="只生成键盘变体(key)台词,跳过主表(--check/--convert 对变体始终生效)")
ap.add_argument("--check", action="store_true")
ap.add_argument("--convert", action="store_true")
args = ap.parse_args()
games = list(GAMES.keys()) if args.game == "all" else [args.game]
genders = ["male", "female"] if args.gender == "both" else [args.gender]
only = set(x for x in args.only.split(",") if x)
if args.check:
sys.exit(check(games))
if args.convert:
convert(games)
return
generate(games, genders, only, variants_only=args.only_variants)
if __name__ == "__main__":
main()