feat(platform/P1): 配对码跨设备 + 认领打磨 + 键盘语音双套资产(两 agent 并行 + 集成收口)
配对码与认领(B): - POST /api/pair/start|join:6 位码 10 分钟单次有效、仅存内存零泄露面、5 连错 per-IP 封锁 10 分钟;join=新设备档案按桶 LWW 并入老档案+token 改绑(复用 claim-merge);日志脱敏 - 大厅:生成配对码(speechSynthesis 逐位朗读+aria-live+重听)/从其他设备转入(one-time-code 输入框)/从本机移除/401 过期自愈引导/aria 补全/玩家名可听化 - api-smoke 12/12(配对全流程/频控/封锁/过期自愈/移除/digitsToCn)+ CDP 真浏览器 E2E 22/22 键盘语音双套资产(A,路线 a): - 164 变体句(8 游戏 grep 实测,超出调研预估 153:含轻触/点两下/按住变体)×2 性别=328 生成 656 文件 36.9 分钟;触屏主资产零字节改动(PCM 逐样本比对后还原) - 引擎 _candidates 三跳路由 key→gender→平铺(inputMode 跟随);缓存键 mode|gender|id;engine-input 新增 K9 路由断言(72 条) - check_all_assets 增 key 列(两性别一致且 ⊆ 主集)8 游戏全绿;voice_text.json 增 __key__ 表;live control.mjs key 会话对照键盘措辞验收(集成) - voice-ids 文档:8 游戏键盘变体小节由 KEY_LINES 程序化生成(与台词表逐字断言一致);补齐 storyline/escape/simon 三个历史缺失文档;design README 增 new8 索引 - mock 适配:playtest persona 加 maxTouchPoints=1 保持触屏链断言语义零改动 回归:12 套件全绿(8 playtest + guide 122 + engine-voice 19 + engine-input 72 + api-smoke 12/12) 同批入库:批次 2/3 已完稿设计文档(17-abyss/18-detective/19-cipher 全套,20-22 在途)
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
# 01b · 《暗室》语音台词表(voice ids)
|
||||
|
||||
> 主台词表(80 句,双性别)的唯一事实源是 `tools/gen_voice_dual.py` 的
|
||||
> `ESCAPE_LINES`(R2 重设计全文见 [01-escape-redesign.md](01-escape-redesign.md);
|
||||
> 史源 app/src/escape/gen/gen_voice.py)。本文档登记 P1 键盘变体(输入平权
|
||||
> 路线 a)——变体只覆盖含操作词的句子,id 集合 ⊆ 主表,复用主表同 id 的
|
||||
> instruct/speaker。
|
||||
---
|
||||
|
||||
## 键盘变体(key,P1 输入平权路线 a,30 句)
|
||||
|
||||
「生效输入模式=键盘」(settings.input=key,或 auto 且键盘在跟/纯桌面初始判定,
|
||||
web/engine/gesture.js inputMode())时,引擎 voice 候选链前置加载
|
||||
`web/escape/audio/voice/<gender>/key/<id>.{ogg,m4a}`,变体缺失回退主资产——触屏措辞零改动。
|
||||
>
|
||||
> 措辞规范(02-input-parity.md §4.3 路线 a + P1 裁定):向左滑→按左方向键(四向同理,
|
||||
> WASD 不进台词);单击/轻触屏幕→按空格;双击→按回车;按住/长按→按住空格;
|
||||
> 复合短语(左右滑→左右方向键、滑动→方向键)按语义等价展开;只改操作词,其余
|
||||
> 内容一字不动,不新增「重」类多音字。本表与 `tools/gen_voice_dual.py` 的
|
||||
> `KEY_LINES` 逐字一致(两表一致铁律:改一处必改另一处)。
|
||||
> 资产:×2 性别 = 30×2 条、×2 格式 = 30×4 文件;管线 `gen_voice_dual.py --only-variants`
|
||||
> 生成 → `--convert` 落盘,终检 `tools/check_all_assets.py`(key 两性别一致且 ⊆ 主集)。
|
||||
|
||||
| id | 键盘台词全文(触屏原句见本表上方对应 id) |
|
||||
|---|---|
|
||||
| `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` | 按空格可跳过旁白。 |
|
||||
@@ -0,0 +1,52 @@
|
||||
# 02b · 《音序回响》语音台词表(voice ids)
|
||||
|
||||
> 主台词表(78 句,双性别)的唯一事实源是 `tools/gen_voice_dual.py` 的
|
||||
> `SIMON_LINES`(R2 重设计全文见 [02-simon-redesign.md](02-simon-redesign.md))。
|
||||
> 本文档登记 P1 键盘变体(输入平权路线 a)——变体只覆盖含操作词的句子,
|
||||
> id 集合 ⊆ 主表,复用主表同 id 的 instruct/speaker。
|
||||
---
|
||||
|
||||
## 键盘变体(key,P1 输入平权路线 a,28 句)
|
||||
|
||||
「生效输入模式=键盘」(settings.input=key,或 auto 且键盘在跟/纯桌面初始判定,
|
||||
web/engine/gesture.js inputMode())时,引擎 voice 候选链前置加载
|
||||
`web/simon/audio/voice/<gender>/key/<id>.{ogg,m4a}`,变体缺失回退主资产——触屏措辞零改动。
|
||||
>
|
||||
> 措辞规范(02-input-parity.md §4.3 路线 a + P1 裁定):向左滑→按左方向键(四向同理,
|
||||
> WASD 不进台词);单击/轻触屏幕→按空格;双击→按回车;按住/长按→按住空格;
|
||||
> 复合短语(左右滑→左右方向键、滑动→方向键)按语义等价展开;只改操作词,其余
|
||||
> 内容一字不动,不新增「重」类多音字。本表与 `tools/gen_voice_dual.py` 的
|
||||
> `KEY_LINES` 逐字一致(两表一致铁律:改一处必改另一处)。
|
||||
> 资产:×2 性别 = 28×2 条、×2 格式 = 28×4 文件;管线 `gen_voice_dual.py --only-variants`
|
||||
> 生成 → `--convert` 落盘,终检 `tools/check_all_assets.py`(key 两性别一致且 ⊆ 主集)。
|
||||
|
||||
| id | 键盘台词全文(触屏原句见本表上方对应 id) |
|
||||
|---|---|
|
||||
| `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` | 这一轮,按上方向键和回车都是作答。 |
|
||||
@@ -69,3 +69,30 @@
|
||||
---
|
||||
|
||||
**合计 31 句 × 2 性别 = 62 条**(其中 grade_*、restart 为重录覆盖)。无数字拼接:拍数(九十/一百零五/一百二十)与准确率档位全部写死在整句里。
|
||||
---
|
||||
|
||||
## 键盘变体(key,P1 输入平权路线 a,9 句)
|
||||
|
||||
「生效输入模式=键盘」(settings.input=key,或 auto 且键盘在跟/纯桌面初始判定,
|
||||
web/engine/gesture.js inputMode())时,引擎 voice 候选链前置加载
|
||||
`web/rhythm/audio/voice/<gender>/key/<id>.{ogg,m4a}`,变体缺失回退主资产——触屏措辞零改动。
|
||||
>
|
||||
> 措辞规范(02-input-parity.md §4.3 路线 a + P1 裁定):向左滑→按左方向键(四向同理,
|
||||
> WASD 不进台词);单击/轻触屏幕→按空格;双击→按回车;按住/长按→按住空格;
|
||||
> 复合短语(左右滑→左右方向键、滑动→方向键)按语义等价展开;只改操作词,其余
|
||||
> 内容一字不动,不新增「重」类多音字。本表与 `tools/gen_voice_dual.py` 的
|
||||
> `KEY_LINES` 逐字一致(两表一致铁律:改一处必改另一处)。
|
||||
> 资产:×2 性别 = 9×2 条、×2 格式 = 9×4 文件;管线 `gen_voice_dual.py --only-variants`
|
||||
> 生成 → `--convert` 落盘,终检 `tools/check_all_assets.py`(key 两性别一致且 ⊆ 主集)。
|
||||
|
||||
| id | 键盘台词全文(触屏原句见本表上方对应 id) |
|
||||
|---|---|
|
||||
| `tut_1` | 听,底鼓。在下一小节的同一个位置,按空格。 |
|
||||
| `tut_2` | 这次是左和右。跟着打,按左方向键,按右方向键。 |
|
||||
| `tut_3` | 最后一句,三个音,中间是按回车。 |
|
||||
| `select_hint` | 左右方向键选曲目,按空格开始,按下方向键回大厅。 |
|
||||
| `restart` | 按空格再来一遍,按右方向键下一首,按下方向键回大厅。 |
|
||||
| `menu_main` | 菜单。按空格切换选项,按回车确认。 |
|
||||
| `skip_hint` | 按空格可跳过旁白。 |
|
||||
| `gd_miss_early` | 抢拍了。等声音落下再按。 |
|
||||
| `gd_miss_wrong` | 方向不对。跟着音色按方向键。 |
|
||||
|
||||
@@ -103,3 +103,45 @@
|
||||
**合计 52 句 × 2 性别 = 104 条**(×2 格式 = 208 文件)。
|
||||
|
||||
旧 id 处置:R1 生成的 `num*/n_*/intro1-4/hit1/hit2/gameover/restart` 全部弃用(数字拼接与旧教学);`shield_ready`/`shield_block` 沿用 id 重录。平铺旧文件按 R1 约定在切换验证后统一清理。
|
||||
---
|
||||
|
||||
## 键盘变体(key,P1 输入平权路线 a,24 句)
|
||||
|
||||
「生效输入模式=键盘」(settings.input=key,或 auto 且键盘在跟/纯桌面初始判定,
|
||||
web/engine/gesture.js inputMode())时,引擎 voice 候选链前置加载
|
||||
`web/space/audio/voice/<gender>/key/<id>.{ogg,m4a}`,变体缺失回退主资产——触屏措辞零改动。
|
||||
>
|
||||
> 措辞规范(02-input-parity.md §4.3 路线 a + P1 裁定):向左滑→按左方向键(四向同理,
|
||||
> WASD 不进台词);单击/轻触屏幕→按空格;双击→按回车;按住/长按→按住空格;
|
||||
> 复合短语(左右滑→左右方向键、滑动→方向键)按语义等价展开;只改操作词,其余
|
||||
> 内容一字不动,不新增「重」类多音字。本表与 `tools/gen_voice_dual.py` 的
|
||||
> `KEY_LINES` 逐字一致(两表一致铁律:改一处必改另一处)。
|
||||
> 资产:×2 性别 = 24×2 条、×2 格式 = 24×4 文件;管线 `gen_voice_dual.py --only-variants`
|
||||
> 生成 → `--convert` 落盘,终检 `tools/check_all_assets.py`(key 两性别一致且 ⊆ 主集)。
|
||||
|
||||
| id | 键盘台词全文(触屏原句见本表上方对应 id) |
|
||||
|---|---|
|
||||
| `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` | 被撞了。听方位,按对应方向键就能拨开。 |
|
||||
|
||||
@@ -174,3 +174,37 @@
|
||||
---
|
||||
|
||||
**合计:旁白 104 句 × 2 性别 + NPC 16 句 = 224 条音频**(×2 格式 = 448 文件)。对话功能与语气大纲对照见 [05-rpg.md](05-rpg.md) §5.3,场景/战斗触发逻辑见 [05-rpg-chapter1.md](05-rpg-chapter1.md)。
|
||||
---
|
||||
|
||||
## 键盘变体(key,P1 输入平权路线 a,16 句)
|
||||
|
||||
「生效输入模式=键盘」(settings.input=key,或 auto 且键盘在跟/纯桌面初始判定,
|
||||
web/engine/gesture.js inputMode())时,引擎 voice 候选链前置加载
|
||||
`web/rpg/audio/voice/<gender>/key/<id>.{ogg,m4a}`,变体缺失回退主资产——触屏措辞零改动。
|
||||
>
|
||||
> 措辞规范(02-input-parity.md §4.3 路线 a + P1 裁定):向左滑→按左方向键(四向同理,
|
||||
> WASD 不进台词);单击/轻触屏幕→按空格;双击→按回车;按住/长按→按住空格;
|
||||
> 复合短语(左右滑→左右方向键、滑动→方向键)按语义等价展开;只改操作词,其余
|
||||
> 内容一字不动,不新增「重」类多音字。本表与 `tools/gen_voice_dual.py` 的
|
||||
> `KEY_LINES` 逐字一致(两表一致铁律:改一处必改另一处)。
|
||||
> 资产:×2 性别 = 16×2 条、×2 格式 = 16×4 文件;管线 `gen_voice_dual.py --only-variants`
|
||||
> 生成 → `--convert` 落盘,终检 `tools/check_all_assets.py`(key 两性别一致且 ⊆ 主集)。
|
||||
|
||||
| id | 键盘台词全文(触屏原句见本表上方对应 id) |
|
||||
|---|---|
|
||||
| `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` | 想好了吗?左右方向键选择,按空格出手。 |
|
||||
|
||||
@@ -758,3 +758,29 @@
|
||||
菜单暂停冻结、上滑 5 段状态链。
|
||||
|
||||
R1 起每轮的增量断言(修复回归)在各自设计文档的轮次小节追加,不在本文件重复。
|
||||
|
||||
## 21. 新游戏登记:《深海回音》(abyss)(new8 批次 2)
|
||||
|
||||
叙事款(radio 同骨架),断言清单与实现核对**住设计文档**(§20 先例):
|
||||
|
||||
- abyss:[17-abyss.md](17-abyss.md) §9(playtest 清单)。要点:四结局氧气剧本验算(lefts 公式 + 剧本 A-D 逐检查点
|
||||
oxy 档位句)、第四潜氧气门三形态(双载/单载/取消)、氧气账目真实(floor(oxy/3) 档位、补给恰一次、mid-dive
|
||||
floor 干涸链)、声呐双通道仲裁(echo/pipe_knock 与 voice 零重叠 + sonarGate 排队)、教学承诺兑现清单
|
||||
(金属=钢板/单击=脉冲/四潜/氧气门/扣三格/敲管/补给)、看门狗双轨(乱按 96s 必被抢救 + 有进展零打扰)、
|
||||
结算链 800ms 手势死窗(dungeon H1 内化)、键盘 _k 变体按 inputMode 选播、pendingChoice/p* 不可跳、
|
||||
中断复述、存档 schema。
|
||||
|
||||
## 22. 新游戏登记:《暗码》(cipher)(new8 批次 3)
|
||||
|
||||
叙事款(radio/abyss 同骨架 + 电文解码小局),断言清单与实现核对**住设计文档**(§21 先例):
|
||||
|
||||
- cipher:[19-cipher.md](19-cipher.md) §9(playtest 清单)。要点:**摩尔斯固定字符断言**(运行时 SYN 模块 `morse.js`
|
||||
事件钩子,BAY/TOP/HAND/SPY 逐元素 + 时值容差 ±5ms + 三档速度 u=80/110/160;**不随 settings.speed 变**);
|
||||
**三层兜底剧本各自走通**(纯耳朵剧本 A / 纯慢放剧本 F / 提示流剧本 E:T1 免费页 + 3 页逐值 book_2/1/0);
|
||||
**译错归因真话**(T1 dv1_wrong 句内字母与真词一致 + 无限重选零惩罚;正片判定出口零对错语音;ep_* 与
|
||||
t/picks flags 单源对应);**暴露度剧本验算**(剧本 A-D 逐 NIGHT 检查点档位句 = wrong+risk 公式,收网门=hot 档
|
||||
阈值 5);四结局矩阵(expose 档 × p9);morseGate 零重叠(摩尔斯与任一 voice 区间);密码本页数账目(逐值
|
||||
整句、页数 0 翻本=error 震+翻空本音零惩罚);看门狗双轨(纯浏览/重听不计进展,乱按 96s 必被抢救);
|
||||
结算链 800ms 手势死窗(判定出口/NIGHT/d9/e*);键盘 _k 变体 11 对按 inputMode 选播;pendingChoice/p* 不可跳/
|
||||
摩尔斯首播不可跳;中断复述(含 DECODE 态恢复链 prog_t→book 档→gd_tune2);存档 schema(summary 带
|
||||
暴露档名)。
|
||||
|
||||
@@ -228,3 +228,39 @@
|
||||
- 每个结局(e1..e4)专属主句+钩子各 2 句齐备,尾声救/错过双线齐备 ✓。
|
||||
- 归因真话律:负向收尾句(z1_bade/k1_lulle/k1_liee/ep_*_no/e4_b)全部指向玩家实际选择,无假归因句 ✓。
|
||||
- 向导 gd_* 6 句(gd_brief/gd_continue/gd_again/gd_invalid/gd_ring1/gd_ring2)符合 14 号 §6 台词规范(一句一义、无重复句式、单句 ≤15 字)✓。
|
||||
---
|
||||
|
||||
## 键盘变体(key,P1 输入平权路线 a,18 句)
|
||||
|
||||
「生效输入模式=键盘」(settings.input=key,或 auto 且键盘在跟/纯桌面初始判定,
|
||||
web/engine/gesture.js inputMode())时,引擎 voice 候选链前置加载
|
||||
`web/radio/audio/voice/<gender>/key/<id>.{ogg,m4a}`,变体缺失回退主资产——触屏措辞零改动。
|
||||
>
|
||||
> 措辞规范(02-input-parity.md §4.3 路线 a + P1 裁定):向左滑→按左方向键(四向同理,
|
||||
> WASD 不进台词);单击/轻触屏幕→按空格;双击→按回车;按住/长按→按住空格;
|
||||
> 复合短语(左右滑→左右方向键、滑动→方向键)按语义等价展开;只改操作词,其余
|
||||
> 内容一字不动,不新增「重」类多音字。本表与 `tools/gen_voice_dual.py` 的
|
||||
> `KEY_LINES` 逐字一致(两表一致铁律:改一处必改另一处)。
|
||||
> 资产:×2 性别 = 18×2 条、×2 格式 = 18×4 文件;管线 `gen_voice_dual.py --only-variants`
|
||||
> 生成 → `--convert` 落盘,终检 `tools/check_all_assets.py`(key 两性别一致且 ⊆ 主集)。
|
||||
|
||||
| id | 键盘台词全文(触屏原句见本表上方对应 id) |
|
||||
|---|---|
|
||||
| `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` | 留言机红灯在闪,是老主播三年前没播完的那句晚安。按左方向键,替他播出去。按右方向键,照常收播。 |
|
||||
|
||||
@@ -179,3 +179,41 @@
|
||||
通关链 = `settle_win_1 → settle_win_2 → meta_gold_X(同上)→ unlock 段 → settle_again`;
|
||||
上滑局面复述链 = `st_floor_N → st_hp_X → st_gold_X(band1 且金>0 时为 gold_some)→ st_face_X → st_threat_X`。
|
||||
主设计触发逻辑对照 [16-dungeon.md](16-dungeon.md) §4-§11;资产规模核对见其 §9。
|
||||
---
|
||||
|
||||
## 键盘变体(key,P1 输入平权路线 a,20 句)
|
||||
|
||||
「生效输入模式=键盘」(settings.input=key,或 auto 且键盘在跟/纯桌面初始判定,
|
||||
web/engine/gesture.js inputMode())时,引擎 voice 候选链前置加载
|
||||
`web/dungeon/audio/voice/<gender>/key/<id>.{ogg,m4a}`,变体缺失回退主资产——触屏措辞零改动。
|
||||
>
|
||||
> 措辞规范(02-input-parity.md §4.3 路线 a + P1 裁定):向左滑→按左方向键(四向同理,
|
||||
> WASD 不进台词);单击/轻触屏幕→按空格;双击→按回车;按住/长按→按住空格;
|
||||
> 复合短语(左右滑→左右方向键、滑动→方向键)按语义等价展开;只改操作词,其余
|
||||
> 内容一字不动,不新增「重」类多音字。本表与 `tools/gen_voice_dual.py` 的
|
||||
> `KEY_LINES` 逐字一致(两表一致铁律:改一处必改另一处)。
|
||||
> 资产:×2 性别 = 20×2 条、×2 格式 = 20×4 文件;管线 `gen_voice_dual.py --only-variants`
|
||||
> 生成 → `--convert` 落盘,终检 `tools/check_all_assets.py`(key 两性别一致且 ⊆ 主集)。
|
||||
|
||||
| id | 键盘台词全文(触屏原句见本表上方对应 id) |
|
||||
|---|---|
|
||||
| `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` | 按空格,再探一次。 |
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
# 17b · 《深海回音》(abyss) 语音台词表(voice ids,录音依据)
|
||||
|
||||
> **给音频总监的录制说明**
|
||||
> - **双性别组**(潜航员 DIVER + 调度/系统 DECK,126 句):落 `web/abyss/audio/voice/female|male/<id>.{ogg,m4a}`,两性别 id 集合完全一致(含全部 `_k` 变体)。
|
||||
> - **DIVER(潜航员=玩家)**:female = `Serena`,instruct 常量 `低沉冷静的年轻女声,深潜救援员,干练沉着,水下通讯的轻微气声`;male = `Uncle_Fu`,instruct 常量 `低沉冷静的男声,深潜救援员,干练沉着,水下通讯的轻微气声`。
|
||||
> - **DECK(母船调度/系统音/向导 gd_*)**:female = `Vivian`,instruct 常量 `清晰稳定的女声,母船潜水调度员,专业干脆,隔着耳机下达指令`;male = `Dylan`,instruct 常量 `清晰稳定的男声,母船潜水调度员,专业干脆,隔着耳机下达指令`。
|
||||
> - 区分度设计:玩家(头戴耳机的自语音)与调度(母船通讯声)共用 speaker 池但**不同 speaker 配对**——female 侧玩家 Serena / 调度 Vivian、male 侧玩家 Uncle_Fu / 调度 Dylan,同场景高频对话的两者永不同 speaker。
|
||||
> - **NPC 平铺组**(21 句):单音色、**不做双性别**,平铺落 `web/abyss/audio/voice/<id>.{ogg,m4a}`(引擎平铺回退命中),转码时按 §电台滤波 加带通。船员共 4 人(≤5 约束),同一角色 speaker+instruct 恒定,区分度风险逐个标注:
|
||||
> | 角色 | speaker | instruct 常量 | 区分度风险 |
|
||||
> |---|---|---|---|
|
||||
> | 小舟(23 岁声呐兵,女) | `Serena` | `年轻女声,二十三岁声呐兵,缺氧后发虚,强撑着干脆` | **中**:与 female 玩家同 speaker,靠年龄感 instruct + 电台滤波(420-3200Hz)区分,请试听确认 |
|
||||
> | 老白(50 岁轮机长,男) | `Uncle_Fu` | `沙哑爽朗的中年男声,五十岁轮机长,忍着伤痛开玩笑` | **中**:与 male 玩家同 speaker,靠沙哑 instruct + 滤波(350-3000Hz)区分,请试听确认 |
|
||||
> | 陈医官(45 岁船医,女) | `Vivian` | `平静沙哑的中年女声,四十五岁船医,专业镇定,语气温和` | **低**:与 female 调度同 speaker 不同 instruct + 滤波(400-3100Hz),且从不同场 |
|
||||
> | 高船长(58 岁船长,男) | `Eric` | `低沉苍老的男声,五十八岁船长,疲惫克制,有老舵手的威严` | **低**:male 侧唯一 Eric + 滤波(300-2800Hz) |
|
||||
> - **电台滤波(转码侧)**:NPC 全部走带通+轻压缩(参数见上表,详表见 17-abyss.md §5.3/§6.1);DIVER/DECK 不滤波(母船级音质)——「电台里的人」与「耳机里的人」靠音质差立住。试听不可读的句子回退未滤波原声(宁窄勿脏)。
|
||||
> - **称呼规则**:船员对玩家一律称「救援员/你」,**全表禁用**「姑娘/小哥/大哥/大姐/叔叔」类性别化称呼(玩家性别随用户设置变化)。
|
||||
> - **键盘变体(PC 键盘通道,P1 机制)**:带 `_k` 后缀的 id 为键盘措辞版(「按空格」「回车」「左箭头/右箭头/上箭头/下箭头」),与触屏版**成对生成、成对落盘**(`check_all_assets` 校验 `_k` 配对);运行时由 `gesture.inputMode()` 选择:`key` → `<id>_k`,`touch` → `<id>`,`auto` 跟随最近使用通道,`_k` 缺失回退无后缀版。变体共 **11 对**,全部集中在下方 §G0 操作提示句区与 §D1 首个抉择(dv1_teach / p1),以粗体标注。
|
||||
> - **数字规避**:潜次/氧气全部分档整句(prog_d1..d4、oxy_full/ok/low/crit、oxy_gain);句内数字(三千米/三格/四潜/十二小时/四小时/一小时/三页)写死在整句里,禁止运行时拼接。
|
||||
> - **禁语速词**:instruct 不含任何语速描述(引擎 SoundTouch 变速);NPC 句靠短句+逗号停顿实现「水下慢半拍」听感。
|
||||
> - 管线:`tools/gen_voice_dual.py` 照录(SPEAKERS 表需新增 abyss 行);多音字自查见文末注记。
|
||||
|
||||
## G0 组 · 系统/向导/状态(DECK 双性别 ×37)
|
||||
|
||||
### G0.a 操作提示句(含键盘变体,9 对 18 句——键盘玩家听这一区就够)
|
||||
|
||||
| id | 台词全文 | 触发 |
|
||||
|---|---|---|
|
||||
| `gd_brief` | 单击继续简报。要选择时,向左或向右滑。 | 开场链最前(full 档,F8 先例) |
|
||||
| **`gd_brief_k`** | 按空格继续简报。要选择时,用左右方向键。 | 同上(inputMode=key) |
|
||||
| `gd_continue` | 单击屏幕,继续。 | 叙事链末看门狗第 1 级(full 25s / standard 13s) |
|
||||
| **`gd_continue_k`** | 按空格,继续。 | 同上(key) |
|
||||
| `gd_again` | 还在吗?单击继续。 | 看门狗第 2 级(50s / 30s),新句不逐字重播 |
|
||||
| **`gd_again_k`** | 还在吗?按空格继续。 | 同上(key) |
|
||||
| `gd_ping1` | 声呐待命。单击,发脉冲。 | 搜索等待静默 10s(第 1 级) |
|
||||
| **`gd_ping1_k`** | 声呐待命。按空格,发脉冲。 | 同上(key) |
|
||||
| `gd_ping2` | 回波不会自己来。单击。 | 搜索等待静默 25s(第 2 级,**必须与第 1 级不同句**) |
|
||||
| **`gd_ping2_k`** | 回波不会自己来。按空格。 | 同上(key) |
|
||||
| `sys_help` | 单击继续,双击重新听。上滑听状态,下滑开菜单。选择时,左右滑。 | 上滑帮助 / 菜单内上滑 |
|
||||
| **`sys_help_k`** | 空格继续,回车重新听。上箭头听状态,下箭头开菜单。选择时,左右箭头。 | 同上(key) |
|
||||
| `sys_menu` | 菜单。单击切换选项,双击确认。 | 下滑呼出模态菜单 |
|
||||
| **`sys_menu_k`** | 菜单。空格切换选项,回车确认。 | 同上(key) |
|
||||
| `sys_skip` | 想跳过这句,单击就行。 | 开场每局一次(skip_hint 先例) |
|
||||
| **`sys_skip_k`** | 想跳过这句,按空格。 | 同上(key) |
|
||||
| `sys_continue_ask` | 检测到你的下潜记录。单击继续上次,右滑开始新救援。 | 有存档进游戏 |
|
||||
| **`sys_continue_ask_k`** | 检测到你的下潜记录。空格继续上次,右箭头开始新救援。 | 同上(key) |
|
||||
|
||||
### G0.b 状态/系统句(无手势词,无变体)
|
||||
|
||||
| id | 台词全文 | 触发 |
|
||||
|---|---|---|
|
||||
| `gd_invalid` | 这个手势,水里用不上。 | 无效手势(error 震,idle-only,同局限次) |
|
||||
| `gd_prog1` | 滑动在这里用不上。想想调度刚说的话。 | 无进展轨 60s(第 1 级,乱按抢救) |
|
||||
| `gd_prog2` | 别乱。停一下,想想下一步。 | 无进展轨 120s(第 2 级) |
|
||||
| `sys_menu_resume` | 回到水下。 | 菜单项 |
|
||||
| `sys_menu_restart` | 重新开始这次救援。 | 菜单项 |
|
||||
| `sys_menu_lobby` | 保存并回大厅。 | 菜单项(确认后跳转前播) |
|
||||
| `sys_settings` | 音色、语速、背景音乐,请回大厅修改。 | 菜单内设置提示 |
|
||||
| `sys_new_dive` | 新的一次下潜,开始了。 | 新救援开始 / 菜单重开 |
|
||||
| `prog_d1` | 现在是第一次下潜。 | 上滑状态链(按当前潜次选播) |
|
||||
| `prog_d2` | 现在是第二次下潜。 | 同上 |
|
||||
| `prog_d3` | 现在是第三次下潜。 | 同上 |
|
||||
| `prog_d4` | 现在是最后一次下潜。 | 同上 |
|
||||
| `oxy_full` | 气瓶还足,救得动。 | 氧气 ≥9(跨档时挂上浮结算链;上滑状态链) |
|
||||
| `oxy_ok` | 氧气还够两潜。 | 氧气 6-8(floor(oxy/3)=2,句义逐格为真) |
|
||||
| `oxy_low` | 氧气告急,只够最后一潜。 | 氧气 3-5;此后垫 sfx/breath_loop |
|
||||
| `oxy_crit` | 氧气见底,必须上浮。 | 氧气 0-2;各播报位附 warning 震 |
|
||||
| `oxy_gain` | 应急瓶接上了,多三格。 | D2 上浮后补给(全局唯一一次) |
|
||||
| `dry_a` | 气瓶用干了。立刻上浮。 | mid-dive 氧气 floor 至 0,干涸链首句 |
|
||||
| `dry_b` | 气花在了人身上。人,都上来了。 | 干涸链归因句(真话:气耗在走心选择上) |
|
||||
|
||||
## G1 组 · 开场(双性别 ×4)
|
||||
|
||||
| id | 声部 | 台词全文 | 触发 |
|
||||
|---|---|---|---|
|
||||
| `b_open` | 潜航员 | 风停了。我站在问海号甲板上,下面三千米,是海燕号。 | INTRO(配 sfx/amb_deck) |
|
||||
| `b_rule` | 调度 | 规矩就一条:气不够一潜,人就必须上浮。 | INTRO(一句规矩,P4;与取消门一致) |
|
||||
| `b_goal` | 调度 | 海燕号,五个人。你的双瓶,够下四潜。 | INTRO(氧气预算承诺) |
|
||||
| `b_go` | 调度 | 第一潜,找船。耳朵交给声呐。 | INTRO,随后自动下潜 |
|
||||
|
||||
## D1 组 · 第一潜·找船·小舟(教学潜)(双性别 13 + 小舟平铺 5 = 18)
|
||||
|
||||
| id | 声部 | 台词全文 | 触发 |
|
||||
|---|---|---|---|
|
||||
| `dv1_down` | 调度 | 下潜开始。水会越来越黑,声音会越来越多。 | 下潜链(垫 sfx/amb_deep) |
|
||||
| `dv1_teach` | 调度 | 到深度了。声呐静了。单击,发一枚脉冲。 | 搜索等待武装(教「单击=脉冲」,配 turn cue) |
|
||||
| **`dv1_teach_k`** | 调度 | 到深度了。声呐静了。按空格,发一枚脉冲。 | 同上(key) |
|
||||
| `dv1_metal` | 调度 | 金属回波,钢板。听见这个,就是海燕号的船身。 | 首枚回波解读(**教「金属回波=钢板」,配 echo_metal_l**) |
|
||||
| `dv1_link` | 调度 | 回波后面有杂音,是电台!接通了。 | 通讯接通(配 link_beep) |
|
||||
| `z1_a` | 小舟 | ……有人吗?声呐兵,小舟。我们还活着,全艇五个人。 | 通话主干(垫 sfx/static_burst) |
|
||||
| `z1_b` | 小舟 | 轮机长老白困在机舱,陈医官和小郑在病舱,船长在指挥舱。 | 通话主干 |
|
||||
| `z1_knock` | 小舟 | 等等,你听。管子里,有人在敲。 | 线索音引入(**配 sfx/pipe_knock 两遍**) |
|
||||
| `p1` | 调度 | 管路在响。向左滑,停下来听敲管的暗号。向右滑,先带小舟上浮。 | 抉择提示(播完即可选) |
|
||||
| **`p1_k`** | 调度 | 管路在响。左箭头,停下来听敲管的暗号。右箭头,先带小舟上浮。 | 同上(key;首个抉择的键位绑定) |
|
||||
| `d1_listen` | 潜航员 | 都别出声。管子那头,敲得很稳。 | p1 左(knowsPipe=true,-1 气) |
|
||||
| `d1_code` | 调度 | 是老白的暗号:腿被压住,先泄压,再撬。记住这个敲法。 | p1 左·收(情报句,D2 兑现) |
|
||||
| `d1_up` | 潜航员 | 小舟,抓住我的肩锁。我们上。 | p1 右 |
|
||||
| `z1_bye` | 小舟 | 好。上面……是不是天亮了? | p1 右·反应 |
|
||||
| `p2` | 调度 | 小舟想回去拿声呐日志。向左滑,等她。向右滑,直接上浮。 | 抉择提示 |
|
||||
| `d1_log` | 小舟 | 日志里有失事前,最后一段录音。给你,替我们收好。 | p2 左(hasLog=true,-1 气) |
|
||||
| `d1_nolog` | 潜航员 | 日志可以补,人不能等。走了。 | p2 右 |
|
||||
| `d1_surf` | 调度 | 小舟出水,第一潜收工。往返,扣三格气。 | 上浮结算(配 surf_splash+ascent_chime;教「每潜扣 3」) |
|
||||
|
||||
## D2 组 · 第二潜·机舱·老白(双性别 14 + 老白平铺 4 = 18)
|
||||
|
||||
| id | 声部 | 台词全文 | 触发 |
|
||||
|---|---|---|---|
|
||||
| `dv2_down` | 调度 | 第二潜,机舱。顺着敲管的声音走。 | 下潜链 |
|
||||
| `dv2_found` | 调度 | 碎石回波后面,敲管声密了。机舱到了。 | 搜索解读(配 echo_debris+pipe_knock) |
|
||||
| `bai_a` | 老白 | 哈哈,救援的?我这腿,跟冷却管缠上了。 | 通话主干 |
|
||||
| `bai_b` | 老白 | 艇上的药板烧了。病舱那两个,比我悬。 | 通话主干(埋 D3 险) |
|
||||
| `p3` | 调度 | 钢梁压着腿。向左滑,泄压后慢慢撬。向右滑,切割枪,快切。 | 抉择提示 |
|
||||
| `d2_pry` | 潜航员 | 老白,忍住。先泄压,再撬。 | p3 左 |
|
||||
| `d2_pry_ok` | 调度 | 暗号没白听。管子一松,钢梁抬起来了。 | p3 左·收(**条件 knowsPipe=true,-0 气**) |
|
||||
| `d2_pry_slow` | 调度 | 摸着找泄压阀,多费了一格气。腿,出来了。 | p3 左·收(条件 knowsPipe=false,-1 气) |
|
||||
| `d2_cut` | 潜航员 | 切割枪。老白,闭眼,憋气。 | p3 右 |
|
||||
| `d2_flash` | 调度 | 火花炸了一下,虚惊。老白咳着,腿抽出来了。 | p3 右·收(**危机拍:danger 签名+spark_flash 前置 2s**) |
|
||||
| `bai_out` | 老白 | 腿还在!我的腿还在!走,我给你带路。 | 两路汇合(正反馈) |
|
||||
| `p4` | 调度 | 老白要去关电池舱阀门。向左滑,陪他去。向右滑,直接上浮。 | 抉择提示 |
|
||||
| `d2_valve` | 潜航员 | 电池舱我陪你去。这片海不能脏。 | p4 左(valve=true,-2 气;配 valve_close) |
|
||||
| `d2_valve_e` | 调度 | 阀门关死了。两格气,换一片干净海。 | p4 左·收(账目真实:明说两格) |
|
||||
| `bai_thanks` | 老白 | 这片海养着一村人。这份情,我记一辈子。 | p4 左·反应 |
|
||||
| `d2_leave` | 潜航员 | 阀门交给母船。你先上,腿要紧。 | p4 右 |
|
||||
| `d2_surf` | 调度 | 老白出水。他一直扒着舱门,往水里看。 | 上浮结算 |
|
||||
| `d2_supply` | 调度 | 潜友阿凯捎来应急瓶。甲板上,粥给你温着。 | 补给剧情(随后 oxy_gain +3,全局唯一) |
|
||||
|
||||
## D3 组 · 第三潜·病舱·陈医官/小郑(双性别 12 + 陈医官平铺 4 = 16)
|
||||
|
||||
> 小郑为无台词角色(重伤固定于吊床,radio 老主播「带子里只有电流声」先例);其状态由调度句 d3_chen_e/d3_stay_lost 承载。
|
||||
|
||||
| id | 声部 | 台词全文 | 触发 |
|
||||
|---|---|---|---|
|
||||
| `dv3_down` | 调度 | 第三潜,病舱。海燕号在倾斜,别恋战。 | 下潜链 |
|
||||
| `dv3_groan` | 调度 | 回波发闷。船体在呻吟,它撑不久了。 | 搜索解读(**配 echo_groan,D4 险预告**) |
|
||||
| `chen_a` | 陈医官 | 你来了。小郑股骨断了,我给他固定好了。 | 通话主干 |
|
||||
| `chen_b` | 陈医官 | 救援钟一次带一个。我是医生,我最后走。 | 通话主干(抉择铺垫) |
|
||||
| `p5` | 调度 | 病舱一次只能走一个。向左滑,先带陈医官。向右滑,先带小郑。 | 抉择提示(无气差的双义抉择) |
|
||||
| `d3_chen` | 潜航员 | 陈医官,你的手在抖。这次,听我的。 | p5 左(陈医官上,stayer=zheng) |
|
||||
| `chen_up` | 陈医官 | ……好。小郑,等我,上面给你热粥。 | p5 左·反应 |
|
||||
| `d3_chen_e` | 调度 | 陈医官出水。小郑固定在吊床上,呼吸平稳。 | p5 左·收 |
|
||||
| `d3_zheng` | 潜航员 | 小郑先走。陈医官,下一潜,就是你的。 | p5 右(小郑上,stayer=chen) |
|
||||
| `d3_zheng_e` | 调度 | 小郑吊上去了。他一路,都没敢出声。 | p5 右·收 |
|
||||
| `chen_stay` | 陈医官 | 去吧。我守着灯,灯不灭,人就不散。 | p5 右·留守宣言 |
|
||||
| `p6` | 调度 | 留守的人要空气。向左滑,留下你的备用瓶。向右滑,约定下一潜来接。 | 抉择提示 |
|
||||
| `d3_spare` | 潜航员 | 备用瓶留下。灯,给我亮着。 | p6 左(spareGift=true,-2 气,可触发干涸链) |
|
||||
| `d3_spare_e` | 调度 | 备用瓶挂上了,能多撑十二小时。 | p6 左·收(账目:时间量而非气格) |
|
||||
| `d3_promise` | 潜航员 | 四小时内,我下来。等我。 | p6 右 |
|
||||
| `d3_surf` | 调度 | 病舱的人出水。海燕号的呻吟,隔着水都听得见。 | 上浮结算(随后走氧气门) |
|
||||
|
||||
## D4 组 · 第四潜·指挥舱·高船长(高潮)(双性别 18 + 高船长 7 + 陈医官 1 = 26)
|
||||
|
||||
| id | 声部 | 台词全文 | 触发 |
|
||||
|---|---|---|---|
|
||||
| `dv4_none` | 调度 | 气不够一潜了。第四潜,取消。 | **氧气门·取消形态**(oxy≤2;800ms 手势死窗) |
|
||||
| `gao_final` | 高船长 | 别下来。带他们回家,就算救完了。海燕号,通话结束。 | 取消链·船长的最后通讯(随后直达 ◆p9) |
|
||||
| `dv4_down` | 调度 | 最后一潜,指挥舱。海燕号,撑不住了。 | 下潜链(双载/单载共用) |
|
||||
| `dv4_slide` | 调度 | 它滑了!抓稳索缆,指挥舱里还有人! | 搜索危机拍(**danger 签名+landslide 前置 2s 渐强+danger 震**) |
|
||||
| `gao_a` | 高船长 | 救援员。我船上,还剩两个活人。 | 通话主干 |
|
||||
| `gao_b` | 高船长 | 都带走,我最后上。这是船长的规矩。 | 通话主干 |
|
||||
| `p7` | 调度 | 双人上浮。向左滑,逐站减压慢慢浮。向右滑,违规直浮,搏一把。 | 抉择提示(**双载形态**:oxy≥4) |
|
||||
| `p7_chen` | 调度 | 气只够带一个。向左滑,带船长。向右滑,带陈医官。 | 抉择提示(**单载形态·留守=陈医官**:oxy=3) |
|
||||
| `p7_zheng` | 调度 | 气只够带一个。向左滑,带船长。向右滑,带小郑。 | 抉择提示(单载形态·留守=小郑) |
|
||||
| `d4_slow` | 潜航员 | 逐站停,慢慢浮。规矩不能破。 | p7 左·双载(-2 气) |
|
||||
| `d4_slow_e` | 调度 | 减压站一个个过。气瓶在跳,人稳着。 | p7 左·双载·收 |
|
||||
| `d4_fast` | 潜航员 | 来不及了。直浮! | p7 右·双载 |
|
||||
| `d4_fast_e` | 调度 | 违规快浮。医疗舱的灯,先给你们开好。 | p7 右·双载·收(回响伏笔) |
|
||||
| `d4_pick_gao` | 潜航员 | 船长,抓住我。海燕号的事,上面再说。 | p7 左·单载(带船长) |
|
||||
| `d4_pick_chen` | 潜航员 | 陈医官,轮到你了。抓住,上浮。 | p7 右·单载·留守=陈医官 |
|
||||
| `d4_pick_zheng` | 潜航员 | 小郑,吊床连人一起。走了。 | p7 右·单载·留守=小郑 |
|
||||
| `chen_last` | 陈医官 | 去吧。灯我留着,人齐了就熄。 | 单载·带走船长时留守者=陈医官的留别 |
|
||||
| `d4_stay_lost` | 调度 | 另一位,留在了倾斜的船舱。航标灯,替所有人亮着。 | 单载·带走船长时留守者=小郑的留别(克制措辞) |
|
||||
| `gao_last` | 高船长 | 航标灯替我留着。就说,船长睡了。 | 单载·带走留守者时船长的留别(p8 跳过,直达上浮) |
|
||||
| `gao_c` | 高船长 | 三年前……没什么。走吧,别耽误。 | 心结引入(**条件 hasLog=false**) |
|
||||
| `gao_confess` | 高船长 | 日志你拿了?里面写着。是我下令,多采了一小时。 | 心结引入(**条件 hasLog=true**,替代 gao_c) |
|
||||
| `p8` | 调度 | 船长的话没说完。向左滑,把你听到的说出来。向右滑,什么都不问,拉他上浮。 | 抉择提示(船长在乘时可 达,30 字全表最长句) |
|
||||
| `d4_talk` | 潜航员 | 日志我听完了。海燕号为什么沉,我会带上去。走吧。 | p8 左(gaoPeace=true) |
|
||||
| `gao_peace` | 高船长 | ……好。这一次,听救援员的。 | p8 左·反应 |
|
||||
| `d4_silent` | 潜航员 | 纪律就是纪律。抓住,上浮。 | p8 右 |
|
||||
| `d4_surf` | 调度 | 最后一潜收工。问海号的甲板,从没这么亮过。 | 上浮结算 |
|
||||
|
||||
## F9 组 · 签收抉择(心结)(双性别 ×7)
|
||||
|
||||
| id | 声部 | 台词全文 | 触发 |
|
||||
|---|---|---|---|
|
||||
| `g9_in` | 调度 | 医疗舱躺满了。你的气瓶剩多少,你比我清楚。 | 收队签收引入(SURFACE(4)/取消链之后) |
|
||||
| `g9_knot` | 潜航员 | 三年前,也是这条沟。我按时上浮了,搭档没有。 | 心结自述(玩家背景) |
|
||||
| `p9` | 调度 | 海燕号的船钟,还挂在沉船上。向左滑,用最后的气,下去取。向右滑,进医疗舱。 | 抉择提示(左路需 oxy≥1) |
|
||||
| `g9_dry` | 调度 | 一格气都不剩了。能带的,你都带回来了。 | **p9 左路封锁句**(oxy<1:播出后自动走右,不摆假选项) |
|
||||
| `d9_bell` | 潜航员 | 最后一潜。不为救人,为个交代。 | p9 左(bell=true;配 bell_toll 后接 d9_bello) |
|
||||
| `d9_bello` | 调度 | 船钟出水。海燕号的声音,回家了。 | p9 左·收 |
|
||||
| `d9_rest` | 潜航员 | 交代,留给明天的人。我去医疗舱。 | p9 右 |
|
||||
|
||||
## EP 组 · 结局与尾声回响(双性别 22;结局 8 + 回响 14)
|
||||
|
||||
| id | 声部 | 台词全文 | 触发 |
|
||||
|---|---|---|---|
|
||||
| `e1_a` | 潜航员 | 五个人都在甲板上。船钟的第一声,是替这条沟敲的。 | 结局 e1(saved=5 且 p9=左)主句 |
|
||||
| `e1_b` | 潜航员 | 三年了,我终于敢往下看。明天,想再潜一次。 | e1 收(钩子) |
|
||||
| `e2_a` | 潜航员 | 五个人都救上来了。船钟,留在了沟底。 | 结局 e2(saved=5 且 p9=右)主句 |
|
||||
| `e2_b` | 潜航员 | 没人怪我。可半夜醒来,总听见钟在响。 | e2 收(钩子) |
|
||||
| `e3_a` | 潜航员 | 没能都带回来。可我到底下去了,敲响了那口钟。 | 结局 e3(saved<5 且 p9=左)主句 |
|
||||
| `e3_b` | 潜航员 | 钟声替他们,在沟底响了一声。这样,也好。 | e3 收(钩子) |
|
||||
| `e4_a` | 潜航员 | 我按规矩上浮了。规矩里,没写怎么难过。 | 结局 e4(saved<5 且 p9=右)主句 |
|
||||
| `e4_b` | 潜航员 | 沟底很安静。安静得,像三年前那天。 | e4 收(钩子;心结回环) |
|
||||
| `ep_lead` | 调度 | 收队了。调度台给你记一笔——他们,后来怎样了。 | 结局链后尾声引入 |
|
||||
| `ep_zhou_ok` | 调度 | 小舟:归队报告打了三页。她说,声音都记得。 | 尾声(bond_zhou=1:p1 左或 p2 左) |
|
||||
| `ep_zhou_no` | 调度 | 小舟:声呐日志没了。她凭记忆,重新描了一遍海图。 | 尾声(bond_zhou=0) |
|
||||
| `ep_bai_ok` | 调度 | 老白:腿保住了。阀门那夜之后,见人就夸你。 | 尾声(bond_bai=1:p3 左或 p4 左) |
|
||||
| `ep_bai_no` | 调度 | 老白:腿打了钢钉。他说,瘸着也值。 | 尾声(bond_bai=0) |
|
||||
| `ep_chen_ok` | 调度 | 陈医官:回了医院。诊室里,挂着一盏海底的灯。 | 尾声(陈医官生还) |
|
||||
| `ep_chen_lost` | 调度 | 陈医官:她守着灯,到最后一小时。灯,还亮着。 | 尾声(陈医官留守未归) |
|
||||
| `ep_zheng_ok` | 调度 | 小郑:拆了石膏,第一件事,去看海。 | 尾声(小郑生还) |
|
||||
| `ep_zheng_lost` | 调度 | 小郑:吊床随船,滑进了沟底。他没来得及看日出。 | 尾声(小郑留守未归) |
|
||||
| `ep_gao_peace` | 调度 | 高船长:退役申请自己写的。第一行写着:数据不如人。 | 尾声(船长生还且 p8 左) |
|
||||
| `ep_gao_up` | 调度 | 高船长:上了岸,一直没说话。听说,他在学敲管语。 | 尾声(船长生还且 p8 右) |
|
||||
| `ep_gao_lost` | 调度 | 高船长:随海燕号留在沟底。航标灯,还亮着。 | 尾声(船长未归) |
|
||||
| `ep_trench_bell` | 调度 | 沟底:船钟挂在问海号桅杆上。风一过,就响一声。 | 尾声(bell=true) |
|
||||
| `ep_trench_deep` | 调度 | 沟底:很深,很静。海燕号睡在里面,像睡着了。 | 尾声(bell=false) |
|
||||
|
||||
---
|
||||
|
||||
**合计:148 句**(GPU 预算 ≤170 ✓)
|
||||
- 双性别 127 句(DIVER 潜航员 30 + DECK 调度/系统 97,含 `_k` 键盘变体 11)×2 性别 = 254 文件;
|
||||
- NPC 平铺 21 句(小舟 5 / 老白 4 / 陈医官 5 / 高船长 7)= 21 文件;
|
||||
- 合计 275 条音频 ×2 格式(ogg+m4a)= **550 文件**。
|
||||
- 键盘变体 11 对:gd_brief / gd_continue / gd_again / gd_ping1 / gd_ping2 / sys_help / sys_menu / sys_skip / sys_continue_ask / dv1_teach / p1(成对生成,`check_all_assets` 校验 `_k` 配对与双性别集合一致)。
|
||||
- 每结局专属台词齐备:e1/e2/e3/e4 各 2 句 + 尾声回响 14 句(好感/存活/flag 选播)。
|
||||
- 每局实走抉择 7-9 个(p7 单载右路跳过 p8;D4 取消路径跳过 p7/p8),抉择提示 p1..p9 共 9 个。
|
||||
|
||||
## 多音字 / 发音风险自查注记
|
||||
|
||||
| 风险词 | 处理 |
|
||||
|---|---|
|
||||
| 重听 | 全表写「重新听」(sys_help / sys_help_k),无「重听」字样;「重写」规避为「日志可以补」(d1_nolog) |
|
||||
| 壳(ké/qiào) | 全表不用「壳」,写「钢板 / 船身」(dv1_metal) |
|
||||
| 脏(zāng/zàng) | 「这片海不能脏」(d2_valve)为 zāng 正确常用读法,保留,试听确认 |
|
||||
| 闷(mēn/mèn) | 「回波发闷」(dv3_groan)为 mèn 常用读法,保留,试听确认 |
|
||||
| 颤(chàn/zhàn) | 台词侧用「发抖」(d3_chen);instruct 描述不进语音,不受影响 |
|
||||
| 塞 / 巷 / 挣 / 血 / 露 / 降(jiàng 外义)/ 率 / 传(zhuàn 义) | 全表未使用(「撑住」「股骨断了」替代;下潜 qián 恒用) |
|
||||
| 教(jiāo/jiào) | 未使用;「船长」zhǎng、「数据」shùjù、「调度」diàodù 为固定词 |
|
||||
| 了 / 还 / 得 | 常规句式常见读法,逐句人工试听项(d3_zheng_e「都没敢出声」、d1_surf「扣三格气」等) |
|
||||
| 数字 | 句内数字全部写死(三千米 / 三格 / 四潜 / 两格 / 十二小时 / 四小时 / 一小时 / 三页 / 一盏),序号与档位用分档整句(prog_d1..d4、oxy_*) |
|
||||
|
||||
## 台词侧自查
|
||||
|
||||
- 单句 ≤40 字:全表最长 = p8 与 p9(各 30 字),次长 sys_help_k(28 字)、p6(27 字)✓;NPC 句平均 ≤20 字(P4 语速感靠短句+逗号,不用 instruct 语速词)✓。
|
||||
- 船员 ≤4 人、speaker+instruct 恒定、区分度风险逐个标注 ✓(小舟 Serena / 老白 Uncle_Fu / 陈医官 Vivian / 高船长 Eric)。
|
||||
- 称呼合规:船员对玩家只用「救援员 / 你」,性别化称呼零出现 ✓。
|
||||
- 每个结局(e1..e4)专属主句+钩子各 2 句齐备,尾声救/未救双线齐备 ✓。
|
||||
- 归因真话律:负向收尾句(dry_b / gao_final / chen_last / d4_stay_lost / gao_last / ep_*_lost / e4_b)全部指向玩家实际选择或真实处境,无「运气不好」类假归因、无惩罚性措辞 ✓。
|
||||
- 向导 gd_* 13 句(gd_brief/gd_continue/gd_again/gd_ping1/gd_ping2 各带 _k 变体,gd_invalid/gd_prog1/gd_prog2 无变体)符合 14 号 §6 台词规范(一句一义、无重复句式、单句 ≤18 字)✓。
|
||||
- 氧气档位句与数值一一对应(oxy_full≥9 / ok=6-8 / low=3-5 / crit=0-2,均按 floor(oxy/3) 可下潜次数措辞),账目真实 ✓。
|
||||
@@ -0,0 +1,490 @@
|
||||
# 17 · 《深海回音》(abyss) — 深海救援叙事 RPG 设计文档
|
||||
|
||||
> **给开发的实现要点摘要**
|
||||
> 1. 叙事模式(声命线范式,radio 同骨架):节点链 onComplete 串行 + seqToken 打断作废 + pendingChoice 缓存;复制 `web/radio/` 结构起步,状态机 6 宏状态(INTRO/DIVE/COMMS/SURFACE/ENDING + MENU 覆盖层)。
|
||||
> 2. 手势零豁免:全部走全局默认映射(单击=继续/**检查(搜索等待=发声呐脉冲,宪法 §5.1「检查」义,escape 先例)**/跳句、双击=重听、左右滑=抉择、上滑=状态、下滑=模态菜单、长按=引擎快进)。
|
||||
> 3. 台词依据 [17-abyss-voice-ids.md](17-abyss-voice-ids.md)(148 句:双性别 127 + 平铺 NPC 21);NPC 水声电台滤波在**资产侧** ffmpeg 带通(radio 先例),引擎零改动。
|
||||
> 4. 氧气=步进资源非实时倒计时:只在潜次边界与阈值跨越时播报(分档整句、账目真实);软失败=「被迫取消第四潜/单载」而非玩家死亡,结算链完整播出(含 800ms 手势死窗)。
|
||||
> 5. 声呐双通道:回波类信息音与语音**绝不重叠**(游戏层 sonarGate 空闲门,R1 规则 §6.2);并发声流 ≤2。
|
||||
> 6. 章程任务 N2(research/new8/00-program.md §2):单局 15-25 分钟、9 抉择点(实走 7-9)、4 结局、分支局部化。
|
||||
> 7. 断言清单住本文档 §9(radio §20 先例);13 号文档已登记指针(§21)。
|
||||
|
||||
## 1. 概述
|
||||
|
||||
- **一句话**:你是深潜救援员,声呐里是被困海沟的潜艇「海燕号」——氧气在倒数,每次下潜都是抉择:先救谁、花多少气、留不留、上不上去。
|
||||
- **题材(声音即世界)**:黑暗深海里视觉归零,**声呐回波=空间、电台人声=生命、氧气=代价**——「用耳朵救人」与纯音频载体天然同构。声呐脉冲/回波/管路敲击/船体呻吟是叙事本体,不是装饰。
|
||||
- **单局**:一次完整救援(一条抉择路径)20-22 分钟;四次下潜共享主干,分歧局部化在每潜内部;9 个抉择点(单局实走 7-9 个),4 结局。
|
||||
- **核心循环**:下潜(声呐搜索拍:单击发脉冲→回波→调度解读)→ 通讯(船员对话+抉择)→ 带人上浮 → 水面结算(氧气扣账+档位播报)→ 下一潜。跨潜积累氧气存量与船员生死/好感,最后一潜的氧气余量决定救援形态(双载/单载/取消),收队前 p9 决定你自己的心结。
|
||||
- **与 radio 的差异点**(骨架同、压力源不同):radio 零压力(口碑只涨不跌);abyss 引入**氧气稀缺**——但全部宽限化:无实时倒计时、无中途死亡、软失败只损失「机会」不损失「已有」、最优省气路径恰好全员生还(P4 友好默认)。
|
||||
|
||||
## 2. 状态机
|
||||
|
||||
### 2.1 状态列表
|
||||
|
||||
| 状态 | 内容 | BGM | guide.state |
|
||||
|---|---|---|---|
|
||||
| `INTRO` | 开场链:甲板口播 + 一句规矩 + 今晚目标 | `bgm/deck` | explore |
|
||||
| `DIVE` | 下潜过程:下潜链(auto)→ 搜索等待(单击=发脉冲,声呐主导)→ 回波+接通 | `bgm/dive` | explore;搜索等待由 turn cue 承担 |
|
||||
| `COMMS` | 通讯叙事:船员对白链 + 抉择点 + 结果 | `bgm/comms` | explore;抉择点=choice |
|
||||
| `SURFACE` | 上浮结算:出水确认 + 氧气扣账/档位 + 补给(D2 后),自动链 | `bgm/deck` | settle |
|
||||
| `ENDING` | 结局链 + 尾声回响(epilogue),结算态 | `bgm/ending` | settle |
|
||||
| `MENU` | 模态覆盖层(任意叙事态下滑呼出,暂停一切链) | `bgm/deck`(低响度淡入) | menu |
|
||||
|
||||
### 2.2 转移图(文本)
|
||||
|
||||
```
|
||||
┌──────────── 结算单击=新救援 ─────────────┐
|
||||
▼ │
|
||||
INTRO ──链完单击──> DIVE(1) ──脉冲找到──> COMMS(1) ──链完──> SURFACE(1) ─┤→ DIVE(2)
|
||||
│下潜链 auto;搜索等待=单击脉冲 │ …(D2/D3 同型)…
|
||||
└─────────────────────────────────────────┘
|
||||
SURFACE(3) ──氧气门──┬─ oxy≥4: DIVE(4 双载) ──链完──> SURFACE(4) ──链完──> 签收 ◆p9
|
||||
├─ oxy=3: DIVE(4 单载: p7_chen|p7_zheng) ──链完──> SURFACE(4) ─┘
|
||||
└─ oxy≤2: 终局取消链(dv4_none→gao_final) ─────────────────────┘
|
||||
▼
|
||||
d9 结果链 ──> ENDING(e1..e4 + ep 回响链)
|
||||
|
||||
任意叙事态(INTRO/DIVE/COMMS/SURFACE/ENDING):下滑 → MENU(模态)
|
||||
MENU:单击=循环切换选项 → 双击=确认执行 → 上滑=帮助 → 下滑/「回到水下」项=关闭回原状态(重播当前节点)
|
||||
```
|
||||
|
||||
- INTRO/DIVE/COMMS/SURFACE/ENDING 均为节点链驱动(radio playNode 模式);下潜链与上浮结算链 `auto:true`,COMMS 叙事节点链末等待单击(radio 实现核对 #1 同口径)。
|
||||
- SURFACE 自动链 ≤15s:扣账句(d*_surf)→ 补给句(仅 D2 后)→ 档位句(跨档才播)→ 直接进下一 DIVE;第四潜位置由**氧气门**分三形态(§4.3)。
|
||||
- 终局取消链与结局链入口处盖 `_settleGraceUntil = now+800ms` 手势死窗(dungeon H1 热修同款),窗内手势一律吞掉,结算链完整播出。
|
||||
- MENU 打开时 `guide.disarm()` + 取消一切看门狗/自动链;关闭后重播当前节点(局面复述,radio closeMenu 先例)。
|
||||
|
||||
### 2.3 每状态手势映射(6+2 全覆盖,零豁免)
|
||||
|
||||
| 手势 | INTRO | DIVE(下潜/搜索) | COMMS | SURFACE | ENDING | MENU |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 上滑 | 帮助+状态,随后重播当前段 | 帮助+状态(潜次+氧档),留在搜索等待 | 帮助+状态,随后重播当前节点 | 帮助+状态,自动链继续 | 帮助+状态(结局名+氧档) | 帮助(sys_help) |
|
||||
| 下滑 | 模态菜单 | 模态菜单 | 模态菜单 | 模态菜单 | 模态菜单 | 关闭菜单 |
|
||||
| 左滑 | 无效 → error 震+gd_invalid | 无效 → gd_invalid(喂无进展轨) | 抉择=左 | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → error 震 |
|
||||
| 右滑 | 无效 → gd_invalid | 无效 → gd_invalid(喂无进展轨) | 抉择=右;ASK 态=新救援 | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → error 震 |
|
||||
| 单击 | 播放中=跳过本句;链末=开始下潜 | 播放中=跳过本句;**搜索等待=发一枚声呐脉冲** | 播放中=跳过本句(p* 不可跳);链末=继续 | 跳过本句(自动链) | **再来一局**(新救援) | 循环切换选项 |
|
||||
| 双击 | 重听当前段 | **重听上一条回波+其解读句**(尚无回波=无效,首潜前零静默) | 重听当前节点台词(含抉择提示) | 重听结算链 | 重听结局链 | **确认执行**(菜单约定覆盖重听) |
|
||||
| 长按 | 引擎接管:旁白临时 1.7×(12 号 §2.2,叙事类不豁免) | 同左 | 同左 | 同左 | 同左 | 同左(快进菜单语音) |
|
||||
| 长按松开 | 引擎接管:恢复 settings.speed | 同左 | 同左 | 同左 | 同左 | 同左 |
|
||||
|
||||
- 豁免登记:**无**。六个基础手势全部符合全局宪法默认语义——单击在搜索等待=「检查」(宪法 §5.1 原文即含检查,escape 检查房间同款);菜单走 §5.2 R3 模态约定(单击循环+双击确认)。
|
||||
- ASK(存档询问态,radio 同款):单击=继续上次 / 右滑=新救援;上滑/下滑/左滑/双击=无效(error 震+gd_invalid)。
|
||||
- 抉择 prompt(voice/p1..p9)播放中单击不可跳(PROMPT_RE 门)、左右滑缓存 `pendingChoice` 播完即生效(radio W2 契约照抄)。
|
||||
- 继承 radio G5 跳过公平契约原文:**首播必完整(p* 首播不可跳 / 链末关键句保护 / ASK 恢复首句锁)、复播可即掐(锁不随复播重新武装、链中段句可跳、复播滑动照常预缓存)**;p* 复播(双击重听/看门狗复述)仍不可跳、句尾生效。
|
||||
- 无效手势零静默:所有「无效」格均有 error 震 + gd_invalid(onceKey 同局限次,F1.4 idle-only:语音在播时跳过语音只发震)。
|
||||
- 键盘通道:三通道并行,游戏代码零改动;操作提示句按 `inputMode()` 选 `_k` 变体资产(§7.3、voice-ids 集中分组)。
|
||||
|
||||
## 3. 抉择树
|
||||
|
||||
### 3.1 全局骨架(四潜共享主干,分歧局部化)
|
||||
|
||||
```
|
||||
INTRO: b_open → b_rule → b_goal → b_go ──> 下潜①
|
||||
【D1 找船·教学潜】救小舟 · 2 抉择(p1/p2)──> SURFACE(1) ──> 下潜②
|
||||
【D2 机舱·老白】危机拍#1 · 2 抉择(p3/p4)──> SURFACE(2·补给+3) ──> 下潜③
|
||||
【D3 病舱·生死抉择】回波预警 · 2 抉择(p5/p6)──> SURFACE(3) ──氧气门──> 下潜④ 或 终局取消链
|
||||
【D4 指挥舱·高潮】滑坡危机 · p7(双形态)+ p8 ──> SURFACE(4) ──> 签收 ◆p9(心结)──> ENDING
|
||||
```
|
||||
|
||||
分支纪律:**每潜内部分支、上浮时收敛**到结算句 → SURFACE;跨潜只传标量(oxy、saved{}、stayer、5 个 flag),不生成组合节点。最坏路径与最好路径节点数同型(剧本 A 与剧本 C 深度相同)。
|
||||
|
||||
### 3.2 分潜结构(◆=抉择点,〔〕=条件差分)
|
||||
|
||||
```
|
||||
【D1 找船·小舟】(教学潜)
|
||||
下潜链 dv1_down → dv1_teach →【搜索等待:单击脉冲】→ echo_metal_l + dv1_metal(教「金属回波=钢板」)
|
||||
→ dv1_link → COMMS: z1_a → z1_b → z1_knock(垫 pipe_knock)→ ◆p1
|
||||
├左 听暗号: d1_listen → pipe_knock → d1_code(knowsPipe=true,-1 气)─┐
|
||||
└右 直接上: d1_up → z1_bye ─────────────────────────────────────────┤
|
||||
▼
|
||||
小舟拉住你 → ◆p2
|
||||
├左 等日志: d1_log(hasLog=true,-1 气)─┐
|
||||
└右 不等: d1_nolog ────────────────────┴→ 上浮 → d1_surf(扣 3 格)→ 档位 → SURFACE(1)
|
||||
|
||||
【D2 机舱·老白】
|
||||
dv2_down →【搜索:脉冲 → echo_debris+pipe_knock → dv2_found】→ bai_a → bai_b → ◆p3
|
||||
├左 泄压慢撬: d2_pry →〔knowsPipe ? d2_pry_ok(-0): d2_pry_slow(-1)〕─┐
|
||||
└右 切割快切: d2_cut →【危机拍: danger 签名+spark_flash 前置 2s】→ d2_flash ─┤
|
||||
▼
|
||||
bai_out → ◆p4
|
||||
├左 陪关阀门: d2_valve → valve_close → d2_valve_e(valve=true,-2)→ bai_thanks ─┐
|
||||
└右 直接上浮: d2_leave ──────────────────────────────────────────────────────────┴→ d2_surf
|
||||
→ d2_supply + oxy_gain(应急瓶+3,全局唯一一次)→ 档位 → SURFACE(2)
|
||||
|
||||
【D3 病舱·陈医官/小郑(小郑无台词,吊床固定)】
|
||||
dv3_down →【搜索:脉冲 → echo_groan → dv3_groan(船体呻吟预警)】→ chen_a → chen_b → ◆p5
|
||||
├左 先带陈医官: d3_chen → chen_up → d3_chen_e(小郑留守)─┐
|
||||
└右 先带小郑: d3_zheng → d3_zheng_e → chen_stay(stayer=chen)┤
|
||||
▼
|
||||
◆p6
|
||||
├左 留备用瓶: d3_spare → d3_spare_e(spareGift=true,-2,floor 至 0 触发干涸链)─┐
|
||||
└右 约定来接: d3_promise ────────────────────────────────────────────────────────┴→ d3_surf → 档位 → SURFACE(3)
|
||||
|
||||
【SURFACE(3) 氧气门】oxy≥4 → D4 双载 | oxy=3 → D4 单载 | oxy≤2 → 终局取消链(dv4_none → gao_final,800ms 死窗)→ ◆p9
|
||||
|
||||
【D4 指挥舱·高船长】
|
||||
dv4_down →【搜索:脉冲 → echo_groan 渐强 → 危机拍: danger+landslide 前置 2s → dv4_slide】→ gao_a → gao_b
|
||||
双载形态 → ◆p7(快慢浮)
|
||||
├左 慢浮: d4_slow → d4_slow_e(-2)─┐
|
||||
└右 快浮: d4_fast → d4_fast_e ──────┤
|
||||
单载形态 → ◆p7_chen|p7_zheng(按 stayer 选提示,谁上)
|
||||
├左 带船长: d4_pick_gao →〔stayer=chen ? chen_last : d4_stay_lost〕─┐
|
||||
└右 带留守者: d4_pick_chen|d4_pick_zheng → gao_last(p8 跳过)─────┴→ 直达 d4_surf
|
||||
▼(船长在乘时)
|
||||
〔hasLog ? gao_confess : gao_c〕→ ◆p8
|
||||
├左 说出来: d4_talk → gao_peace(gaoPeace=true)─┐
|
||||
└右 不问: d4_silent ────────────────────────────┴→ d4_surf → 档位 → SURFACE(4)
|
||||
|
||||
【签收】g9_in → g9_knot → ◆p9
|
||||
├左 取船钟(需 oxy≥1): d9_bell → bell_toll → d9_bello(bell=true)─┐
|
||||
└右 进医疗舱: d9_rest ────────────────────────────────────────────────┤
|
||||
〔oxy<1 时左路封锁: g9_dry 播出后自动走右,不摆假选项〕 ▼
|
||||
ENDING: e*_a → e*_b(按 savedCount × p9 四选一)→ ep_lead → ep_*(按好感/存活/flag 选播)
|
||||
```
|
||||
|
||||
### 3.3 分支局部化说明
|
||||
|
||||
- 跨潜状态全部是标量:`oxy`(int)、`saved`(5 个 0/1)、`stayer`(null/chen/zheng)、flag 5 个(knowsPipe/hasLog/valve/spareGift/gaoPeace)+ p9 派生 bell。没有任何「潜 i 分支 × 潜 j 分支」组合节点。
|
||||
- 条件差分全部是**节点内选播**(radio w1_say/w1_say2 先例):d2_pry_ok/slow(knowsPipe)、gao_c/gao_confess(hasLog)、p7 双/单形态(氧气门)、p7_chen/p7_zheng 与留别句(stayer)、ep_* 选播。树形不变。
|
||||
- 树规模:148 句台词、约 70 个节点;任一路径实走 7-9 个抉择点(D4 取消路径少 p7/p8;单载右路少 p8)。
|
||||
|
||||
### 3.4 四结局达成条件
|
||||
|
||||
| 结局 | 条件 | 主题 | 开环钩子 |
|
||||
|---|---|---|---|
|
||||
| e1 海燕归钟 | saved=5 且 p9=左 | 全员生还+船钟出水,告别完整 | 「明天,想再潜一次」 |
|
||||
| e2 满载的黎明 | saved=5 且 p9=右 | 人都救回来了,钟留在沟底 | 「半夜总听见钟在响」 |
|
||||
| e3 最后一潜 | saved<5 且 p9=左 | 没能都救回,但下去敲响了钟 | 「钟声替他们响了一声」 |
|
||||
| e4 按规程上浮 | saved<5 且 p9=右 | 规程之内,难过之外 | 「安静得像三年前那天」 |
|
||||
|
||||
- 尾声回响(P2 回响感):epilogue 逐人点名——小舟/老白/陈医官/小郑/船长各按「好感/生死」双线播(voice-ids EP 组),沟底一句按 bell 二选一;玩家每一潜的救与错过都被点名(radio ep_* 同款)。
|
||||
|
||||
## 4. 数值设计
|
||||
|
||||
### 4.1 变量
|
||||
|
||||
| 变量 | 类型 | 初值 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `oxy`(氧气格) | int | 12 | 步进资源:只在事件点扣(下潜/抉择),无实时倒计时 |
|
||||
| `saved` | map | zhou:0 其余 0 | 小舟/老白/陈医官/小郑/高船长 各 0/1 |
|
||||
| `stayer` | str | null | p5 后留守病舱者('chen'/'zheng') |
|
||||
| `knowsPipe/hasLog/valve/spareGift/gaoPeace/bell` | bool | false | p1 左/p2 左/p4 左/p6 左/p8 左/p9 左 置位 |
|
||||
| `supply` | bool | false | D2 上浮后应急瓶已发(全局唯一) |
|
||||
| `endings{}` / `runs` | map/int | — | 结局收集与重玩计数(存档展示用) |
|
||||
|
||||
### 4.2 抉择计分表(左=走心·多花气 / 右=克制·省气)
|
||||
|
||||
| 抉择 | 左(走心) | 右(克制) | 备注 |
|
||||
|---|---|---|---|
|
||||
| p1 听暗号/直接上 | -1 气,knowsPipe=true | 0 | 左路获 D2 情报(下潜 EFF 的唯一免气左路) |
|
||||
| p2 等日志/不等 | -1 气,hasLog=true | 0 | hasLog 兑现 D4 船长坦白差分 |
|
||||
| p3 慢撬/快切 | knowsPipe?0:-1 气 | 0 | 右路挂危机拍#1(火花虚惊,两路都活着) |
|
||||
| p4 关阀门/直接走 | -2 气,valve=true | 0 | 左路兑现在回响(干净海) |
|
||||
| p5 先带医官/先带小郑 | 0(chen 上,stayer=zheng) | 0(zheng 上,stayer=chen) | 唯一无气差的双义抉择;stayer 决定 D4 单载差分 |
|
||||
| p6 留备用瓶/约定来接 | -2 气,spareGift=true(可触发干涸链) | 0 | 左路兑现在留守者存活回响 |
|
||||
| p7 慢浮/快浮(双载) | -2 气 | 0 | 右路兑现在 e1/e2 回响(医疗舱灯) |
|
||||
| p7 带船长/带留守者(单载) | 0 | 0 | 另一人随海燕号滑入沟底(措辞克制) |
|
||||
| p8 说出来/不问 | 0,gaoPeace=true | 0 | hasLog 时 gao_confess 差分前置 |
|
||||
| p9 取船钟/进医疗舱 | 需 oxy≥1,bell=true | 0 | oxy<1 时左路封锁(g9_dry,不摆假选项) |
|
||||
|
||||
### 4.3 氧气经济(单位/消耗/宽限/软失败)
|
||||
|
||||
| 项 | 规则 |
|
||||
|---|---|
|
||||
| 单位 | 「格」(内部 int)。初始 12(叙事=双瓶满气,够四次往返);D2 上浮后应急瓶 +3(`oxy_gain` 播报,全局唯一),总预算 15 |
|
||||
| 固定消耗 | 每次下潜开始扣 3(往返);D4 双载额外 +1(慢浮+减压成本按 p7 计) |
|
||||
| 抉择消耗 | 见 §4.2;**mid-dive 扣至负数一律 floor 到 0**,不中断当前救援(人已挂上吊索,照常带上) |
|
||||
| 阈值播报 | 分档整句(下表),**只在跨档时播**(挂上浮结算链尾);上滑 status 随时可查;oxy_crit 各播报位附 warning 震(radio G2 同款) |
|
||||
| 第四潜氧气门 | SURFACE(3) 结算时:oxy≥4 → 双载(带船长+留守者);oxy=3 → 单载(p7 谁上);oxy≤2 → **终局取消链**(第四潜取消,船长与留守者随海燕号滑入沟底——措辞克制、零描写) |
|
||||
| 宽限设计 | ① 学习期零惩罚:D1 两个左路合计 -2,恰好被 D2 后应急瓶 +3 兜底,教学潜永不亏局;② 到点不是死:软失败只损失「机会」(少带一人/少下一潜),已救上的人**永不失去**;③ 无实时倒计时、无 mid-dive 打断(P4 无高压);④ p9 左路是「氧气的礼物」——省下的气兑换终局抉择 |
|
||||
| 干涸链(软失败结算) | mid-dive floor 至 0 触发:dry_a →(当前救援照常完成)→ 上浮结算内 dry_b(归因真话:指向实际花掉最后两格的那个左选择)。链入口 800ms 手势死窗 |
|
||||
|
||||
**氧气档位表(账目真实:每格取值下句义皆为真)**
|
||||
|
||||
| 档 | oxy 区间 | 整句 | 真实性核验 |
|
||||
|---|---|---|---|
|
||||
| full | ≥9(floor(oxy/3)≥3) | 「气瓶还足,救得动。」 | ≥3 潜气量 ✓ |
|
||||
| ok | 6-8(floor=2) | 「氧气还够两潜。」 | 恰可下 2 潜 ✓ |
|
||||
| low | 3-5(floor=1) | 「氧气告急,只够最后一潜。」 | 恰可下 1 潜 ✓ |
|
||||
| crit | 0-2(floor=0) | 「氧气见底,必须上浮。」 | mid-dive=必须上浮 ✓ / 水面=一潜都下不去 ✓ |
|
||||
|
||||
- 档位以 `floor(oxy/3)`(可下潜次数)划界而非绝对半数——**句子说的就是判定用的**(radio rep_low 教训内化:措辞与数值对得上)。
|
||||
- 低氧混音签名:tier=low/crit 期间 DIVE/COMMS 垫 `breath_loop`(低响度呼吸声,SA3 循环);引擎无运行时滤波,BGM 闷化降级为该呼吸层(显式降级,不阻塞管线)。
|
||||
|
||||
### 4.4 船员好感/存活矩阵
|
||||
|
||||
| 船员 | 好感来源(bond) | 存活条件 | 回响句 |
|
||||
|---|---|---|---|
|
||||
| 小舟(声呐兵) | p1 左 或 p2 左 | **恒活**(D1 教学潜保底救上) | ep_zhou_ok / ep_zhou_no |
|
||||
| 老白(轮机长) | p3 左 或 p4 左 | **恒活**(D2 恒可成——预算数学保证,见 §9 验算) | ep_bai_ok / ep_bai_no |
|
||||
| 陈医官 | p5 左(当场救上);或留守时 p6 左 | p5 左即活;p5 右留守 → D4 双载或被选则活,否则失 | ep_chen_ok / ep_chen_lost |
|
||||
| 小郑(无台词角色) | —(吊床上的沉默存在,radio 老主播「带子里只有电流声」先例) | p5 右即活;p5 左留守 → D4 双载或被选则活,否则失 | ep_zheng_ok / ep_zheng_lost |
|
||||
| 高船长 | p8 左(gaoPeace) | D4 双载 或 单载选左则活;单载右/终局取消则失 | ep_gao_peace / ep_gao_up / ep_gao_lost |
|
||||
|
||||
- 好感只影响回响选播(情绪差分),**不影响结局判定**——结局由 savedCount × p9 决定,账目单一可验算。
|
||||
- 生死分支覆盖 3 人(陈医官/小郑/高船长),措辞全部克制(「滑进了沟底」「灯还亮着」),无临场描写;失败归因走干涸链/取消链的真话句。
|
||||
|
||||
### 4.5 氧气收支验算(设计期穷举,playtest 剧本 A-D 引用)
|
||||
|
||||
设 `lefts` = 前 3 潜左路气耗之和(p7/p9 不在前 3 潜):
|
||||
|
||||
- SURFACE(3) 时 oxy = 15 − 9(基础)− lefts = **6 − lefts** → 双载 ⟺ lefts≤2;单载 ⟺ lefts=3;取消 ⟺ lefts≥4。
|
||||
- 双载收队 oxy_end = 2 − lefts(p7 右)或 0 −(lefts−2)(p7 左,floor 0)→ p9 左可用 ⟺ lefts≤1。
|
||||
- 单载收队 oxy_end = 3 − lefts = 0(lefts=3)→ p9 左恒封锁 → 单载路径必 e4。
|
||||
- 取消收队 oxy_end = 6 − lefts ∈ {2,1,0}(lefts=4,5,≥6)→ lefts≤5 时 p9 左可用 → e3。
|
||||
- 前 3 潜恒可完成的预算证明:D1 最大支出 5(12→7);D2 最大支出 6(7→1)后应急瓶 +3(→4)≥3 ✓;D3 恒可下(最差 4→1,p6 左 floor 0 走干涸链)✓。**教学潜与机舱潜永不被氧气卡死**(P4)。
|
||||
|
||||
## 5. 音频三层映射
|
||||
|
||||
### 5.1 BGM(状态→曲目,Mureka instrumental,4 首 ≈ $0.12)
|
||||
|
||||
| 状态 | 曲目 | 切换 | Mureka prompt 草稿(英文,seamless loop) |
|
||||
|---|---|---|---|
|
||||
| INTRO/SURFACE/MENU | `bgm/deck` | 开局淡入 800ms;MENU 呼出低响度淡入 500ms | "Night sea surface from a rescue ship deck, sparse cello and low piano over gentle waves, distant rigging creak and engine hum, melancholic but purposeful, no vocals, seamless loop, 60-90 seconds" |
|
||||
| DIVE | `bgm/dive` | crossfade 800ms 进;接通 COMMS 淡至 comms 600ms | "Deep ocean descent ambience, dark ambient drone with a slow sonar-like pulse, immense water pressure, distant whale-like tones, subtle tension, no vocals, seamless loop, 60-90 seconds" |
|
||||
| COMMS | `bgm/comms` | crossfade 600ms;上浮淡回 deck | "Underwater radio contact scene, warm muted piano over a deep water drone, faint hydrophone static, intimate and quietly hopeful, no vocals, seamless loop, 60-90 seconds" |
|
||||
| ENDING | `bgm/ending` | p9 结果链播完后淡入 1500ms | "Dawn on the sea after a deep sea rescue, warm strings and soft piano, exhausted hope and quiet triumph, no vocals, seamless loop, 60-90 seconds" |
|
||||
|
||||
- `bgm=0` 时全部静默(引擎约定);menu 用 deck 同曲(省 1 首,海面场景同构);共性后缀(10 号 §8)照 radio 追加。
|
||||
|
||||
### 5.2 SFX 清单(SA3=Stable Audio 3 / SYN=程序合成;one-shot 真峰值 ≤-3dBFS;amb 循环由游戏层 `buffer.loop` 实现,radio phone_ring 先例)
|
||||
|
||||
| id | 用途 | 来源 | prompt(英文)/ 合成参数 |
|
||||
|---|---|---|---|
|
||||
| `sfx/sonar_ping` | 你发出的声呐脉冲(搜索单击) | SYN | 1.4→0.9kHz 正弦 chirp 220ms,指数衰减,-6dBFS(回波家族统一音头) |
|
||||
| `sfx/echo_metal_l` / `_r` | 钢板回波(左/右声像;「金属回波=海燕号」承诺音) | SA3 | "Sonar echo returning from a steel submarine hull, resonant metallic ping with slow decay, panned slightly left(→right), underwater, 2 seconds" |
|
||||
| `sfx/echo_debris` | 碎石回波(D2 机舱外) | SA3 | "Sonar echo returning from an underwater rubble and debris field, diffuse granular reverberation, 2 seconds" |
|
||||
| `sfx/echo_groan` | 船体呻吟(D3 预警/D4 滑坡前奏,可变增益复用) | SA3 | "Stressed submarine hull groaning deep underwater, long metallic moan under immense pressure, 4 seconds" |
|
||||
| `sfx/pipe_knock` | 管路敲击暗号(老白 SOS) | SYN | 三短三长三短固定节拍金属敲击(节拍精确可复现、断言可数) |
|
||||
| `sfx/link_beep` | 电台接通 | SYN | 双音上行 0.3s |
|
||||
| `sfx/static_burst` | 通讯静电/断续 | SA3 | "Shortwave radio static burst with faint ghost of a distorted voice, 2 seconds" |
|
||||
| `sfx/amb_deep` | 深潜底噪 loop | SA3 | "Deep ocean ambient loop, low pressure hum, distant current wash, occasional far creak, seamless loop" |
|
||||
| `sfx/amb_deck` | 甲板底噪 loop(风+浪+缆绳) | SA3 | "Night ship deck ambience, wind, gentle waves against hull, distant rigging creak and engine hum, seamless loop" |
|
||||
| `sfx/breath_loop` | 低氧呼吸 loop(仅 low/crit 档垫,混音签名) | SA3 | "Slow scuba regulator breathing loop, calm but slightly labored, underwater, seamless loop" |
|
||||
| `sfx/spark_flash` | D2 危机拍:切割火花+氢气虚惊 | SA3 | "Underwater cutting torch sparks with a brief muffled gas pop, 2 seconds" |
|
||||
| `sfx/valve_close` | 重阀关闭(p4 左) | SA3 | "Heavy submarine valve wheel shutting, metal rotation clunk and water seal, 2 seconds" |
|
||||
| `sfx/landslide` | D4 危机拍:海底滑坡轰鸣(前置 2s 渐强) | SA3 | "Underwater landslide rumble, rolling sediment and rocks, ominous low roar building, 4 seconds" |
|
||||
| `sfx/surf_splash` | 破水出水(每潜上浮) | SA3 | "Diver breaking through the sea surface, splash and one gasp, 2 seconds" |
|
||||
| `sfx/ascent_chime` | 出水确认音 | SYN | 上行三音琶音 0.4s |
|
||||
| `sfx/bell_toll` | 船钟(p9 左 payoff 音) | SA3 | "Old ship brass bell, single clean toll with warm resonance, 3 seconds" |
|
||||
| `sfx/ui_confirm` / `ui_menu` | 菜单/确认(程序合成兜底) | SYN | 同全局先例 |
|
||||
|
||||
- 预警窗纪律:D2 火花与 D4 滑坡均**前置 2s 起渐强**(L2),danger 签名与音效同窗(radio C5 刹车先例);D4 另叠 `danger` 震循环(每遍间隔 ≥400ms)。
|
||||
- 承诺兑现绑定:dv1_metal 教「金属回波=钢板」后,凡叙事提到船身/找到船的节点必播 echo_metal*;D2 找人必播 pipe_knock(§6.3 清单 + §9 断言)。
|
||||
|
||||
### 5.3 Voice 划分(详见 17-abyss-voice-ids.md)
|
||||
|
||||
| 组 | 声部 | 落位 | 数量 |
|
||||
|---|---|---|---|
|
||||
| 潜航员(你)DIVER | 双性别(female=Serena / male=Uncle_Fu + DIVER instruct 常量) | `voice/female\|male/` | 30 句 |
|
||||
| 调度/系统/向导 DECK | 双性别(female=Vivian / male=Dylan + DECK instruct 常量) | `voice/female\|male/` | 97 句 |
|
||||
| 船员 NPC ×4 | 平铺单音色(小舟=Serena / 老白=Uncle_Fu / 陈医官=Vivian / 高船长=Eric),**不做双性别** | `voice/` 根(平铺回退命中)+ 电台滤波转码 | 21 句 |
|
||||
|
||||
- 船员 ≤4 个单音色约束满足;玩家自己的声音跟随全局 `settings.voice`;调度是向导人设(L4 角色化系统声,radio 导播同型)。
|
||||
- 合计 148 句(≤170 GPU 预算 ✓,含 `_k` 键盘变体 11 对);音频文件 = 127×2 + 21 = 275 条(×2 格式 = 550 文件)。
|
||||
|
||||
## 6. 声呐双通道设计(本作核心差异层)
|
||||
|
||||
### 6.1 引擎能力边界(对照 web-engine-api.md)
|
||||
|
||||
| 能力 | 引擎现状 | 方案 |
|
||||
|---|---|---|
|
||||
| voice 运行时 EQ/滤波 | ❌(voice 链无滤波插入点) | **资产侧转码滤波**(radio §6.2 先例):ffmpeg `highpass+lowpass+压缩器` 逐船员定档(§5.3/voice-ids) |
|
||||
| sfx 循环 | ❌ 即发即弃 | 游戏层 `buffer.loop=true` 持句柄(amb_deep/amb_deck/breath_loop),切换档位时停(radio `_ringSrc` 先例) |
|
||||
| 语音 duck 垫乐 | ✅ 内置(duck 20%,300ms) | 直接依赖;BGM 不再手工让路 |
|
||||
| sfx vs voice 互让 | ❌ sfx 总线独立,无 duck | **游戏层 sonarGate 空闲门**(§6.2 R1):信息类回波只在 `!audio.isVoicePlaying()` 时播出,否则挂队列表、voice onComplete/句间轮询补发 |
|
||||
| sfx 声像 | ❌ 运行时无 pan API | 资产侧 pan(echo_metal_l/_r 两条,escape 先例);其余方向信息由调度语音明说(「左前方」) |
|
||||
| 变速不变调 | ✅ SoundTouch | 直接依赖(长按 1.7× / 2.2× 视障档) |
|
||||
|
||||
### 6.2 通道仲裁规则(并发声流 ≤2 约束)
|
||||
|
||||
- **通道定义**:声呐通道 = sfx 层的脉冲/回波/敲击/呻吟/环境 loop;通讯通道 = voice 层(调度+船员+你)。BGM 是第三层背景(voice 播时引擎自动 duck)。
|
||||
- **R1 回波不叠语音**:信息类回波(echo_*、pipe_knock、groan 渐强段)与任何 voice 播放区间**零重叠**——sonarGate 只在 voice idle 时放行,在播则排队到句间空隙(≥600ms);玩家主动脉冲只在搜索等待(voice 恒静)受理,按构造不重叠。
|
||||
- **R2 声呐主导段**(DIVE 搜索等待):前景=脉冲+回波余音+amb_deep,BGM=dive 曲低响度;voice 只出调度短句(解读句),且解读句固定在回波**结束后**接播(回波→静默一拍→解读,给耳朵留定位时间)。
|
||||
- **R3 常驻脉冲退位**:语音在播时,环境内的周期性声呐元素(母船远程脉冲垫)降 -6dB 或暂停(游戏层 gain 控制),不消失(保持声场存在感,A2 无静默死局)。
|
||||
- **R4 危机拍优先**:D2/D4 危机音(spark/landslide)为即发即弃 sfx,可叠在 amb 上;danger 签名同窗;当前 voice 句播完后危机链才插续(radio C5 同款;呼救类短句若必须抢时,走 sfx 层不占 voice)。
|
||||
- **R5 回波可重听**:搜索等待双击=重听「上一条回波+其解读句」(P1 关键线索可重听);COMMS 双击=重听当前节点(radio 同款)。
|
||||
- 声流账:任意时刻 ≤2 条前景流(voice 串行 1 + 声呐信息音 1),BGM/amb 为背景层——满足 ASA ≤2 注意流约束。
|
||||
|
||||
### 6.3 教学承诺清单(dungeon「铁器=楼梯」教训:每句承诺的机制必须真实存在且可感知)
|
||||
|
||||
| 教学句 | 承诺 | 兑现点(断言锁定) |
|
||||
|---|---|---|
|
||||
| dv1_metal「金属回波,钢板。听见这个,就是海燕号的船身」 | 金属回波=找到船 | D1 找到/D4 接近指挥舱均播 echo_metal*;全剧本任何「船身」节点零漏播 |
|
||||
| dv1_teach「单击,发一枚脉冲」(+gd_ping1) | 单击=脉冲 | 搜索等待单击恒出 sonar_ping(非跳句、非无效);无超时不惩罚 |
|
||||
| b_goal「五个人。你的双瓶,够下四潜」 | 5 人/4 潜 | 基础消耗恰 3 格×4=12(§4.5 验算);剧本 A 恰 4 潜走完 |
|
||||
| b_rule「气不够一潜,人就必须上浮」 | 氧气门 | 取消门恰在 oxy<3(§4.3/§4.5);剧本 C/D 实证 |
|
||||
| d1_surf「往返,扣三格气」 | 每潜扣 3 | 每次下潜开始扣 3(账目断言) |
|
||||
| d1_code「记住这个敲法」 | 敲管=老白的信号 | D2 搜索以 pipe_knock 引导找到机舱 |
|
||||
| oxy_gain「应急瓶接上了,多三格」 | +3 | 恰一次、恰 +3、恰在 D2 上浮后 |
|
||||
|
||||
## 7. 向导层接入
|
||||
|
||||
### 7.1 状态签名 earcon / turn cue
|
||||
|
||||
| 时机 | 向导动作 | 说明 |
|
||||
|---|---|---|
|
||||
| INTRO 进入 | `guide.state('explore')` | 叙事态签名 |
|
||||
| 搜索等待武装(下潜链末) | `guide.turn({haptic:true})`(无语音) | turn 签名+confirm 震=「轮到你发脉冲」;首次前 dv1_teach 已口播 |
|
||||
| 每个抉择点 p* 起播 | `guide.state('choice')` | 选项句本身即 turn cue(W2 契约零插播) |
|
||||
| SURFACE 进入 | `guide.state('settle')` | 结算签名 |
|
||||
| ENDING 进入 | `guide.state('settle')` | 同上 |
|
||||
| MENU 打开 | `guide.state('menu')` | 菜单签名 |
|
||||
| D2/D4 危机拍 | `guide.state('danger')` | 音效前置 2s 渐强同窗(全游戏仅两处) |
|
||||
| 无效手势 | `guide.error({voice:'gd_invalid', onceKey})` | error 震 + full 档语音,idle-only(F1.4) |
|
||||
|
||||
- earcon 词表:只用全项目统一 8 音(menu/explore/battle/choice/settle/danger/turn/nudge),**零新增**;battle 不用。
|
||||
|
||||
### 7.2 看门狗双轨(radio F1 + dungeon E0-1 教训:乱按也要被抢救)
|
||||
|
||||
**无操作轨**(guide watchdog,任何手势复位;两级不同句、用尽即停、永不惩罚):
|
||||
|
||||
| 等待点 | full 档 | standard 档 | 级别内容 |
|
||||
|---|---|---|---|
|
||||
| 搜索等待无脉冲 | [10, 25] | [10, 25](onEscalate 游戏层补播) | ① gd_ping1 ② gd_ping2(**必须不同句**);两级后仅 amb+余音 |
|
||||
| INTRO/COMMS/SURFACE 链末等待 | [25, 50] | [13, 30] | ① gd_continue ② gd_again(storyline F1.1 同款) |
|
||||
| 抉择点 p* 播完无操作 | [25, 50] | [25, 50] | 重播当前 p* 提示句(13 号 §16 先例;空点单击=重挂计时) |
|
||||
| ENDING 结算等待 | [25, 50] | [13, 30] | gd_continue / gd_again |
|
||||
|
||||
**无进展轨**(游戏层计时,dungeon E0-1 ②同型;仅挂搜索等待与 COMMS 链末两个可饿死点):
|
||||
|
||||
| 项 | 规则 |
|
||||
|---|---|
|
||||
| 进展定义 | 节点推进 / 发出脉冲 / 抉择结算。**无效手势(滑错、空点)不计进展**——无操作轨被它们复位、无进展轨不被它们复位,乱按 96s 也必被抢救 |
|
||||
| 阈值 | 60s → gd_prog1;120s → gd_prog2;用尽即停(每等待点一轮) |
|
||||
| 有真实进展 | 全程零 prog 播出(不误伤正常玩家,dungeon 断言同款) |
|
||||
| 双轨去重 | 两轨短句内容同级交叉登记;同一等待点先播过 gd_ping* 则无进展轨跳过同义级 |
|
||||
|
||||
- 向导插播一律 `queue:true`,绝不顶掉在播对白;error/watchdog 类语音 idle-only(在播则跳过且过期丢弃,F1.4)。
|
||||
- 单句语音看门狗(资产缺失兜底):30s 超时且 `isVoicePlaying()===false` 才自动续链,长句续约不掐(radio N1 先例照抄)。
|
||||
|
||||
### 7.3 键盘变体机制(PC 键盘玩家)
|
||||
|
||||
- 运行时按 `gesture.inputMode()`(settings.input auto/touch/key)选 `_k` 变体资产:key → `voice/<gender>/<id>_k.*`,touch → 无后缀版;**auto 跟随最近使用通道**(keydown 后即切 key)。`_k` 缺失回退无后缀版(资产侧保证成对,check_all_assets 校验)。
|
||||
- 变体清单(11 对,集中分组见 voice-ids §G0):gd_brief / gd_continue / gd_again / gd_ping1 / gd_ping2 / sys_help / sys_menu / sys_skip / sys_continue_ask / dv1_teach / p1。
|
||||
- 抉择提示 p2..p9 措辞统一「向左滑/向右滑」;键盘玩家由 p1_k 与 sys_help_k 完成首次绑定(左=←,右=→),后续提示语义不变。
|
||||
- 启动遮罩文案引擎自理(「按空格开始」/「轻触开始」),游戏零改动。
|
||||
|
||||
### 7.4 继承 radio 三轮验证机制(照抄不重设计)
|
||||
|
||||
| 机制 | 来源 | 落点 |
|
||||
|---|---|---|
|
||||
| 帮助链去重(同等待点第 2 次上滑只震+nudge) | radio G1 | `_helpSeen` 按等待点键控 |
|
||||
| 状态链顺序「状态在前、帮助在后」 | radio G6 | prog_dN → oxy 档 → sys_help |
|
||||
| 空点确认轻震(不可跳句上单击补 beat [8]) | radio G4 | p*/链末句/F4 锁各 tap 屏蔽分支 |
|
||||
| oxy_crit 播报位附 warning 震 | radio G2 | 全部 crit 出口(含上滑复听) |
|
||||
| 菜单报名游戏侧排队零截断 | radio 实现修复 #3 | `_menuQueue` 排水 |
|
||||
| stop() 先 `seqToken++` 作废在途链 | radio 实现修复 #6 | 同款 |
|
||||
| 新局清 choiceArmed/pendingChoice | radio 实现修复 #7 | newRun() |
|
||||
| 长按误报无效(longpress 全状态合法) | radio 实现修复 #2 | 仅 left/right(ASK 另含 up/down/doubletap)报无效 |
|
||||
|
||||
## 8. 存档设计
|
||||
|
||||
### 8.1 进度 data 结构(PUT /api/progress {game:'abyss'})
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": 1,
|
||||
"updatedAt": "2026-09-09T12:00:00Z",
|
||||
"summary": "第2潜·机舱 · 氧气6格 · 结局1/4",
|
||||
"phase": "COMMS",
|
||||
"node": "bai_b",
|
||||
"dive": 2,
|
||||
"oxy": 6,
|
||||
"supply": true,
|
||||
"saved": { "zhou": 1, "bai": 0, "chen": 0, "zheng": 0, "gao": 0 },
|
||||
"stayer": null,
|
||||
"flags": { "knowsPipe": true, "hasLog": false, "valve": false, "spareGift": false, "gaoPeace": false, "bell": false },
|
||||
"endings": { "e2": true },
|
||||
"runs": 3
|
||||
}
|
||||
```
|
||||
|
||||
- 写入时机:每潜 DIVE 进入、每个抉择结算后、SURFACE 进入、ENDING 进入、菜单「保存并回大厅」。
|
||||
- 断点续玩:读档 → sys_continue_ask(_k)(单击=继续上次 / 右滑=新救援);继续=恢复 phase+node+全部数值并**重播当前节点链**=一句话局面复述(L5);重进 ≤5s 可玩;恢复首句受 F4 跳句锁(radio G5 例外条款)。
|
||||
- summary 格式:进行中 `第N潜·<舱段> · 氧气K格 · 结局X/4`;收队 `救援收队·<结局名> · 结局X/4`(大厅卡片直接显示)。
|
||||
|
||||
## 9. playtest 断言清单(登记 13 号 §21 指针;`settings.set({guide:'off'})` 纯玩法验证,向导行为住 guide.test)
|
||||
|
||||
**关键路径**(剧本 A:全右+左p9 → e1,≤70 次手势):
|
||||
1. 遮罩点按 → INTRO 链(b_open 起,voice 层有播放记录)→ 链末单击 → DIVE(1) 下潜链 auto → 搜索等待武装(turn 事件 + amb_deep 循环起播)。
|
||||
2. 搜索单击 → sonar_ping → 回波静默一拍 → echo_metal_l → dv1_metal(承诺音)→ link_beep → COMMS(1):z1_a/b/knock → p1 右 → p2 右 → 上浮:surf_splash+ascent_chime → d1_surf → oxy=12-3=9 → oxy_full 播出(账目断言)。
|
||||
3. DIVE(2):脉冲 → echo_debris+pipe_knock → dv2_found → bai_a/b → p3 右(danger 签名前置 2s 与 spark_flash 同窗断言)→ p4 右 → d2_surf → d2_supply → oxy_gain(6→9,恰一次)。
|
||||
4. DIVE(3):脉冲 → echo_groan → dv3_groan → chen_a/b → p5 右 → p6 右 → d3_surf(oxy=6,ok 档)。
|
||||
5. 氧气门:6≥4 → DIVE(4) 双载:滑坡危机拍(landslide 前置 2s 渐强 + danger 签名 + danger 震)→ gao_a/b → p7 右 → gao_c → p8 右 → d4_surf → oxy=2(crit 档+warning 震)。
|
||||
6. 签收:g9_in → g9_knot → p9 左(oxy=2≥1 可用)→ d9_bell + bell_toll → ENDING(e1):e1_a/b + ep_lead + ep 链 → 结算单击 → 新救援回 INTRO。
|
||||
|
||||
**边界断言**:
|
||||
- **四结局可达(含氧气逐点验算)**:剧本 B(同 A 但 p9 右)→ e2;剧本 C(p1左 p2左 p4左,余右):D1 后 oxy=7(ok)/D2 后 2→supply→5(gain+low 档)/D3 后 2(crit)→ 氧气门走**终局取消链**(dv4_none→gao_final 完整播出)→ p9 左(2≥1)→ e3(saved=3);剧本 D(p1左 p4左,余右):D1 后 8(ok)/D2 后 3→supply→6(ok)/D3 后 3(low)→ **单载形态**(p7 按 stayer 选 p7_chen|p7_zheng)→ p7 左=带船长 → 留守者留别句(chen_last|d4_stay_lost)→ p8 右 → oxy_end=0 → p9 左**封锁**:g9_dry 播出后自动右 → e4(saved=4)。各局 oxy 逐检查点与 §4.5 公式一致。
|
||||
- **氧气账目真实**:每次扣减后播出的档位句与 floor(oxy/3) 桶一致;上滑 status 链顺序 prog_dN → oxy 档 → sys_help(G6);supply 全局恰一次;剧本 E(p2左 p3[无knows]左 p4左 p6左)mid-dive floor 至 0 → dry_a/dry_b 播出且当前救援者照常救上(人已挂上吊索不失去)。
|
||||
- **双通道仲裁**:全剧本转写断言——echo_*/pipe_knock/groan 信息音播出时刻与任一 voice 播放区间**零重叠**(sonarGate 排队语义:晚于在播句结束);搜索等待单击恒=脉冲(sonar_ping 计数=1,无跳句无 error);双击搜索等待=回波+解读句同序重播;语音在播时 amb_deep 持续、远程脉冲垫降 -6dB(R3)。
|
||||
- **教学承诺兑现**(§6.3 清单逐条):dv1_metal 后所有「船身」节点 echo_metal* 零漏播;D2 找到机舱前 pipe_knock 恒在;b_rule 与取消门一致(剧本 C 证明);b_goal 四潜=剧本 A 恰 4 潜;每潜起播扣 3 断言。
|
||||
- **看门狗双轨**:搜索等待快进 10s→gd_ping1、25s→gd_ping2(两级 id ≠,绝不逐字重播;两级后仅 amb);**乱按 96s**(每 2s 注入一个无效滑动):无操作轨零播出(被手势复位)+ 无进展轨 60s→gd_prog1、120s→gd_prog2(乱按饿不死);有真实进展(单击脉冲推进)全程零 prog 播出;COMMS 链末 25s/50s → gd_continue/gd_again;p* 播完 25s/50s 重播 p* 两次后静默;`guide=off` 全程零定时器零向导事件。
|
||||
- **键盘变体**:settings.input='key' 会话播出 gd_brief_k/sys_help_k/gd_ping1_k/dv1_teach_k/p1_k 且对应无后缀版零播出;'touch' 反之;'auto':keydown 后下一句用 _k。
|
||||
- **中断复述**:杀进程重进(存档 node=bai_b)→ sys_continue_ask → 单击 → 重播该节点链(恢复首句不可跳,第二句起可跳);右滑=新救援(runs 口径同结算重开)。
|
||||
- **结算链死窗(H1 内化)**:终局取消链/干涸链/e* 结局链开播后 800ms 内注入在途 tap(mock 补派 touchend+300ms 同款序列)→ 手势被吞、链完整有序播出、结算出口手势照常。
|
||||
- **pendingChoice**:p* 播放中左右滑 → 播完立即生效(不丢、不重复、连滑取最后一次)。
|
||||
- **p* 不可跳**:p* 播放中单击 → 不跳句(PROMPT_RE 门)+ beat 轻震(G4)。
|
||||
- **idle-only**:向导 gd_* 在对白在播时跳过且不占 onceKey(F1.4);queue 句排在当前句后零截断。
|
||||
- **菜单**:下滑开菜单 → 上滑帮助 → 双击「回到水下」→ phase/node 不变、当前节点重播;单击循环顺序 = [回到水下, 重新开始这次救援, 保存并回大厅];确认回大厅 → sys_menu_lobby 播出且 PUT/localStorage 有最新 data。
|
||||
- **资产缺失**:屏蔽任意 voice 资产 → 30s 看门狗自动续链到结局不卡死;NPC 滤波资产缺失回退原声可播。
|
||||
- **数字规范**:prog_*/oxy_*/gd_* 全部整句字面量,静态审计无运行时拼接碎片。
|
||||
- **低氧混音签名**:tier=low/crit 期间 breath_loop 在播、升档/收队即停。
|
||||
|
||||
**失败即不通过**:任何等待 >5s 无声(静默死局);无效手势零反馈;回波与语音重叠;结局计数与 §3.4 不符;氧气档位句与 oxy 值不符。
|
||||
|
||||
## 10. 节奏曲线(每次下潜目标时长)
|
||||
|
||||
| 段 | 内容 | 目标时长 | 设计目标 |
|
||||
|---|---|---|---|
|
||||
| 开场+下潜链 | 甲板简报+第一潜下水+搜索 | 1-1.5 min | 首输入 <60s(开场链 ~20s 链末单击,或 ~40s 搜索首脉冲);规矩一句讲完(P4) |
|
||||
| D1 找船 | 教学潜:脉冲/回波/接通/2 抉择 | 4-4.5 min | **首个正反馈 ≤3min**(~2.5min 金属回波+接通小舟=「看见它了」);两抉择条条有出路;左路气耗被补给兜底=学习期零惩罚 |
|
||||
| D2 机舱 | 老白:撬/切+阀门抉择 | 3.5-4 min | 首个危机拍(火花,前置 2s);补给+3 兑现宽限;暖拍(热粥,P4) |
|
||||
| D3 病舱 | 生死抉择+回波预警 | 3.5-4 min | 船体呻吟埋 D4 险;留守约定=段落边界(P3 中断友好:每潜一个完整存档颗粒) |
|
||||
| D4 指挥舱 | 滑坡高潮+氧气门三形态 | 4-4.5 min | 紧张峰值:danger+滑坡渐强+双人竞速;生死分支收敛 |
|
||||
| 终局+结局 | p9 心结+结局链+逐人回响 | 2.5-3 min | 氧气余量兑换终局抉择;ep 点名(P2 回响);开环钩子拉下一局 |
|
||||
|
||||
- 合计 ≈ 20-22 min(15-25 达标);每 30-60s 一个反馈颗粒(回波/解读短句/结果句/档位句),无沉默尴尬期(P3)。
|
||||
- 难度曲线 = 道德复杂度(教学二选一 → 情报投资 → 生死取舍 → 自己的心结),操作复杂度恒定(永远二选一+单击脉冲)——P4「规则一遍听懂」由恒定操作面保证。
|
||||
|
||||
## 11. 自检清单(策划交付自验;☐ = 待开发/测试轮填)
|
||||
|
||||
| # | 硬约束/教训 | 落地 | ✓ |
|
||||
|---|---|---|---|
|
||||
| 1 | FTUE 首输入 <60s | 开场链 ~20s 链末单击;~40s 搜索首脉冲(§10) | ☑ |
|
||||
| 2 | FTUE 首个正反馈 ≤3min | ~2.5min 金属回波+接通小舟(§10 D1) | ☑ |
|
||||
| 3 | 学习期零惩罚 | D1 左路 -2 被 D2 后 +3 兜底;搜索无超时惩罚;D1/D2 恒可完成(§4.5) | ☑ |
|
||||
| 4 | 失败三问归因+归因真话律 | 干涸链 dry_b 指向实际左选择;gao_final/d4 留别句克制不假安慰;§4.4 无假归因句 | ☑ |
|
||||
| 5 | 资源账目真实(radio rep_low 教训) | 氧档以 floor(oxy/3) 划界、每档句义逐格为真(§4.3 表);补给恰一次 +3;扣 3 恰每次下潜 | ☑ |
|
||||
| 6 | 手势宪法 6 手势默认语义零豁免 | §2.3 全覆盖;单击搜索=「检查」(宪法 §5.1+escape 先例);菜单 §5.2 模态约定 | ☑ |
|
||||
| 7 | 长按显式登记 | 无豁免:长按=引擎快进(12 号叙事类不豁免),§2.3 在案 | ☑ |
|
||||
| 8 | 向导:签名/turn cue/看门狗两级不同句 | §7.1/§7.2;gd_ping1≠gd_ping2;gd_continue→gd_again | ☑ |
|
||||
| 9 | 看门狗双轨(dungeon E0-1/radio F1 教训) | 无操作轨+无进展轨(§7.2);乱按 96s 必被抢救;有进展零打扰 | ☑ |
|
||||
| 10 | 插播 queue 不顶对白;纠错 idle-only 过期丢弃 | §7.2(F1.4 契约) | ☑ |
|
||||
| 11 | 死亡/失败结算链完整(dungeon H1 教训) | 终局取消链/干涸链/结局链 800ms 手势死窗;已救者永不失去(§2.2/§4.3) | ☑ |
|
||||
| 12 | 教学承诺兑现(dungeon「铁器=楼梯」教训) | §6.3 承诺清单 7 条逐条绑兑现点+断言 | ☑ |
|
||||
| 13 | 单次播报 ≤3 选项 | 全部抉择恒 2 选项;菜单 3 项循环 | ☑ |
|
||||
| 14 | 并发声流 ≤2 + 双通道仲裁 | voice 串行+声呐信息音互斥(R1-R5,§6.2);断言零重叠 | ☑ |
|
||||
| 15 | earcon ≤7、统一词表、语义靠语音 | 只用统一 8 音中 7 个(battle 不用),零新增 | ☑ |
|
||||
| 16 | 台词一句一文件 ≤40 字、数字分档整句、无运行时拼接 | voice-ids 全表 ≤30 字(最长 p8=30);prog/oxy 分档整句;句内数字写死 | ☑ |
|
||||
| 17 | 多音字规避/同 instruct 常量/无语速词 | voice-ids 头部+文末自查(重写→补、脏/晃/闷标注试听) | ☑ |
|
||||
| 18 | PC 键盘变体(提示句集中+标注) | 11 对 _k 变体集中分组(voice-ids §G0);inputMode() 运行时选择 | ☑ |
|
||||
| 19 | P1:可打断/重听(含回波)/status key/不当白痴 | 跳句+双击(回波可重听 R5)+上滑状态链+成功零补救教学 | ☑ |
|
||||
| 20 | P2:3 分钟见效/结局回响 | ~2.5min 首个改变世界的时刻;ep 逐人点名+bell 差分;e1 巧妙路径(p1左+p3左 省气取钟) | ☑ |
|
||||
| 21 | P3:3 分钟颗粒/中断复述/单手 | 一潜=3.5-4.5min 完整段落+边界自动存档;重进重播当前节点;全手势免视单手 | ☑ |
|
||||
| 22 | P4:一遍懂/容错/无高压倒计时/不骂人 | 一句规矩;无实时倒计时(步进资源+跨档才播);软失败零死亡;NPC 句 ≤24 字+逗号停顿 | ☑ |
|
||||
| 23 | 抉择树 4 结局可走通验算 | §4.5 公式+§9 剧本 A-D 逐 oxy 检查点(e1:lefts≤1+双载;e2:lefts≤2;e3:lefts=4-5+取消;e4:单载 lefts=3 或取消右) | ☑ |
|
||||
| 24 | 单局 15-25 min | §10 曲线 ≈20-22 min | ☑ |
|
||||
| 25 | 分支不爆炸(radio 纪律) | 跨潜仅标量(oxy/saved/stayer/6 flag);~70 节点固定深度(§3.3) | ☑ |
|
||||
| 26 | 台词 ≤170、船员 ≤4 单音色 | 148 句(双 127+NPC 21)/4 个船员音色(voice-ids 账目) | ☑ |
|
||||
| 27 | 引擎边界诚实 | §6.1 能力表:滤波/loop/声像/互让全写明资产侧或降级方案 | ☑ |
|
||||
| 28 | 存档带 summary、断点续玩 | §8:data+summary 格式、≥5 恢复、局面复述 | ☑ |
|
||||
|
||||
### 批 1 教训内化确认(5 条全落地)
|
||||
|
||||
| 教训 | 本作机制 | 断言 |
|
||||
|---|---|---|
|
||||
| 教学承诺必须兑现 | §6.3 七条承诺清单(金属=钢板/单击=脉冲/四潜/氧气门/扣三格/敲管/补给) | §9「教学承诺兑现」块 |
|
||||
| 看门狗双轨 | §7.2 无操作轨+无进展轨;无效手势不复位进展轨 | §9「看门狗双轨」块 |
|
||||
| 档位措辞真实 | §4.3 档位=floor(oxy/3) 可下潜次数,句义逐格为真 | §9「氧气账目真实」块 |
|
||||
| 失败结算链完整不被打断 | 800ms 手势死窗盖取消链/干涸链/结局链;seqToken 作废在途链 | §9「结算链死窗」块 |
|
||||
| PC 键盘操作提示 | 11 对 _k 变体+inputMode() 选择+集中分组标注 | §9「键盘变体」块 |
|
||||
@@ -0,0 +1,268 @@
|
||||
# 18b · 《雨夜专案》语音台词表(voice ids,录音依据)
|
||||
|
||||
> **给音频总监的录制说明**
|
||||
> - **旁白双性别组**(106 句):旁白只做系统/指引/教学/状态/现场勘验(探长自勘)/裁决宣读,**不念证词**。落 `web/detective/audio/voice/female|male/<id>.{ogg,m4a}`,两性别 id 集合完全一致。
|
||||
> - female = `Serena`,instruct 常量:`沉稳清晰的女声,专案组探长,冷静克制,像在雨夜读卷宗`
|
||||
> - male = `Uncle_Fu`,instruct 常量:`低沉沉稳的男声,专案组探长,冷静克制,像在雨夜读卷宗`
|
||||
> - 两常量全表统一(含教学/指证/结案/结算),禁止句级变体、禁语速词(引擎 SoundTouch 变速)。
|
||||
> - **NPC 平铺组**(52 句):证词=证人 NPC 平铺单音色、**不做双性别**,落 `web/detective/audio/voice/<id>.{ogg,m4a}`(引擎平铺回退命中)。NPC ≤5;同一角色 speaker+instruct 恒定;**不加电话滤波**(区别于 radio——本作区分度靠 speaker+instruct+游戏侧签名 earcon `sig_*` 前缀,见主设计 §8.2/§3.7)。区分度风险逐个标注:
|
||||
> | 角色 | speaker | instruct 常量 | 区分度风险 |
|
||||
> |---|---|---|---|
|
||||
> | 老葛(62 岁门卫) | `Eric` | `苍老沙哑的男声,守夜人,慢悠悠,带倦意` | 低 |
|
||||
> | 小顺(19 岁学徒) | `Dylan` | `年轻清亮的男声,学徒,怯生生的,慌张诚实` | 低 |
|
||||
> | 周先生(45 岁账房,pack A 真凶) | `Uncle_Fu` | `平板谨慎的男声,账房,字斟句酌,被追问时渐慌` | **中**:与 male 旁白同 speaker——sig_zhou 前缀+账房腔+证词链内零旁白插话,试听确认 |
|
||||
> | 温太太(50 岁房东) | `Vivian` | `快嘴泼辣的中年女声,房东太太,嗓门亮,热心肠` | 低 |
|
||||
> | 秦法医 | `Serena` | `冷静干练的女声,验尸官,临床腔,短句平直` | **中**:与 female 旁白同 speaker——sig_fore 前缀+报告固定语境,试听确认 |
|
||||
> - **声音演技核心**:周先生 z_react_1..4(记岔→支吾→拒答→想走)与 z_break_1..3(认罪三段)、pack B 的 b_shun_react/b_shun_break——情绪全部写在句内文本(省略号/破折号引导),instruct 恒定不变。
|
||||
> - **数字规避**:进度/疑点/冗余指证全部分档整句(st_asked 7 档 / st_doubt 5 档 / st_miss 3 档);**句序号一律不播**(指证选句用重播该句代替报「第几句」,交互侧落实拼接禁令);句内时刻数字写死在整句里(十点二十、九点四十)。
|
||||
> - **称呼中性**:旁白称玩家「你」,不出现性别化称呼(旁白性别随设置变化不影响证词内容)。
|
||||
> - **多音字自查**:全表无「血」「爪」「挨」「壳」;「重新听」统一写法(不用「重听」);已排雷:「当废铜卖了」→「卖给收废品的了」(当 dāng/dàng 歧义)。低风险 3 处标注:「记得真真的」「账算得比谁都精」的「得」(de,助词)、「微驼背」的「背」(bèi)、「疑点,一处」的「处」(chù)——试听异常再改写。
|
||||
> - **键盘变体(V 组)**:操作提示句在 `gesture.inputMode()==='key'` 时改播 `_k` 变体(touch 回基础句);变体与基础句 id 一一对应、集中登记见文末 V 组。
|
||||
> - 管线:`tools/gen_voice_dual.py` 照录(SPEAKERS 需新增 detective 行 + DETECTIVE_NPC 平铺表);生成后 `--convert` 落盘 + `tools/check_all_assets.py` 终检(双格式配对、双性别集合一致)。
|
||||
|
||||
## G0 组 · 系统/菜单/帮助(旁白 ×11)
|
||||
|
||||
| id | 台词全文 | 触发/语境 |
|
||||
|---|---|---|
|
||||
| `sys_menu` | 菜单。单击切换,双击确认。 | 下滑呼出模态菜单 |
|
||||
| `sys_menu_resume` | 回到证词板。 | 菜单项 |
|
||||
| `sys_menu_cf` | 去指证。 | 菜单项(长按捷径的冗余入口,P4) |
|
||||
| `sys_menu_save` | 保存并回大厅。 | 菜单项 |
|
||||
| `sys_menu_help` | 重新听帮助。 | 菜单项 |
|
||||
| `sys_menu_restart` | 重新查这桩案子。 | 菜单项 |
|
||||
| `sys_saved` | 进度已保存。 | 存档写入后 |
|
||||
| `settings_hint` | 音色、语速、背景音乐,请回大厅修改。 | 菜单内设置提示 |
|
||||
| `sys_help` | 左右滑换卷宗,单击听取,双击重新听。长按指证,上滑状态,下滑菜单。 | 菜单内上滑 / 帮助项 |
|
||||
| `sys_continue_ask` | 检测到未结的案子。单击继续,右滑换一桩新案。 | 有存档进游戏(TITLE) |
|
||||
| `sys_new_case` | 新案开卷。 | 新案开始 / 菜单重开 |
|
||||
|
||||
## G1 组 · 开场/教学(旁白 ×12)
|
||||
|
||||
| id | 台词全文 | 触发/语境 |
|
||||
|---|---|---|
|
||||
| `intro_1` | 雨下了一整夜。清晨,聚宝斋后院,古董商沈万川死了。 | 开场链 1(TITLE 上滑重听) |
|
||||
| `intro_2` | 你是专案组探长。这桩案子,归你。 | 开场链 2 |
|
||||
| `intro_3` | 卷宗六份:四位证人的证词,现场勘验,法医报告。 | 开场链 3 |
|
||||
| `tut_d0` | 先熟悉证词板。左右滑,换卷宗。 | D0 前 |
|
||||
| `tut_listen` | 单击,听取当前卷宗。 | D1 前 |
|
||||
| `tut_replay` | 双击,重新听这一句。 | D2 前 |
|
||||
| `tut_status` | 上滑,随时听进度:问了几份,疑点几处。 | D3 前 |
|
||||
| `tut_confront` | 按住屏幕,进入指证。去找两句对不上的证词。 | D4 前(豁免 E2 教学) |
|
||||
| `tut_cf_try` | 练习一次。选一份证词,单击确认;再选一份,任意一句。 | D5(判定被教学截获) |
|
||||
| `tut_cf_ok` | 指证就是这样。练习不算数,真矛盾要靠耳朵找。 | D5 完成(success 震) |
|
||||
| `tut_done` | 教学完了。开始查案。 | D6(进 BOARD) |
|
||||
| `tut_skip` | 已跳过教学。左右滑换卷宗,单击听取。 | 下滑跳过教学 |
|
||||
|
||||
## G2 组 · 状态/条目(旁白 ×20)
|
||||
|
||||
| id | 台词全文 | 触发/语境 |
|
||||
|---|---|---|
|
||||
| `st_asked_0` | 六份卷宗,一份都没听。 | 上滑状态链(已听条目数 0) |
|
||||
| `st_asked_1` | 已听一份,还有五份。 | 已听 1 |
|
||||
| `st_asked_2` | 已听两份,还有四份。 | 已听 2 |
|
||||
| `st_asked_3` | 已听三份,还有三份。 | 已听 3 |
|
||||
| `st_asked_4` | 已听四份,还有两份。 | 已听 4 |
|
||||
| `st_asked_5` | 已听五份,还有一份。 | 已听 5 |
|
||||
| `st_asked_6` | 六份都听完了。 | 已听 6 |
|
||||
| `st_doubt_0` | 疑点,还没有。 | 状态链第 2 段(疑点 0) |
|
||||
| `st_doubt_1` | 疑点,一处。 | 疑点 1 |
|
||||
| `st_doubt_2` | 疑点,两处。 | 疑点 2 |
|
||||
| `st_doubt_3` | 疑点,三处。 | 疑点 3 |
|
||||
| `st_doubt_4` | 疑点,四处,齐了。 | 疑点 4(齐时追加 hint_fin 一次) |
|
||||
| `item_w1` | 门卫老葛的证词。 | 条目切换(前缀 sig_ge) |
|
||||
| `item_w2` | 学徒小顺的证词。 | 条目切换(sig_shun) |
|
||||
| `item_w3` | 账房周先生的证词。 | 条目切换(sig_zhou) |
|
||||
| `item_w4` | 房东温太太的证词。 | 条目切换(sig_wen) |
|
||||
| `item_scene` | 现场勘验。 | 条目切换(sig_scene) |
|
||||
| `item_fore` | 法医报告。 | 条目切换(sig_fore) |
|
||||
| `item_case` | 结案陈词。 | 条目切换(无签名,turn earcon);单击进 FINISH |
|
||||
| `intro_w3` | 周先生,管了十年的账,见人三分笑。 | 周证词链首(旁白报名,防与 male 旁白混淆的强化锚点) |
|
||||
|
||||
## G3 组 · 现场勘验(旁白 ×6,探长自勘,sig_scene 前缀链播)
|
||||
|
||||
| id | 台词全文 | 触发/语境 |
|
||||
|---|---|---|
|
||||
| `sc_0` | 你打伞进了后院。雨水顺着瓦檐,淌成一道帘子。 | 现场链 1(无轴) |
|
||||
| `sc_1` | 柴房门口,沈万川面朝下倒着。后脑一处伤,现场没有凶器。 | struggle=none |
|
||||
| `sc_2` | 后门门闩完好,是死者自己开的门。生人叫不开这扇门。 | door=victim_opened |
|
||||
| `sc_3` | 泥地上只有进来的脚印,没有出去的。墙根的花盆,翻了一个。 | wall=climbed |
|
||||
| `sc_4` | 库房铜秤立在架上,秤砣不见了。 | scale=present(M4 对立端) |
|
||||
| `sc_5` | 柴房墙上,挂钥匙的铜钩,空着。 | 无轴(叙事佐证,不参与判定) |
|
||||
|
||||
## G4 组 · 指证(旁白 ×8)
|
||||
|
||||
| id | 台词全文 | 触发/语境 |
|
||||
|---|---|---|
|
||||
| `cf_start` | 指证矛盾。先选第一份证词。左右滑换,单击确认。 | 进 CONFROST(turn earcon 同点) |
|
||||
| `cf_pick_line` | 左右滑,一句一句听。 | 选句相位入口(首次) |
|
||||
| `cf_confirm` | 就这句?单击,定下来。 | 选句两段确认(重播该句后接播) |
|
||||
| `cf_second` | 再选与它矛盾的那份证词。 | 第一份定下(自动重播所选句一次) |
|
||||
| `cf_self` | 同一句证词,和自己比不出矛盾。 | 判定:同一句(真话) |
|
||||
| `cf_same` | 这两句说得一致,不矛盾。 | 判定:同轴同值(真话) |
|
||||
| `cf_diff` | 这两句,说的不是一件事。 | 判定:无共享轴(真话) |
|
||||
| `cf_hit` | 对不上!矛盾成立,记入疑点。 | 判定:命中(contra_hit 音先响) |
|
||||
|
||||
## G5 组 · 结案三问(旁白 ×18)
|
||||
|
||||
| id | 台词全文 | 触发/语境 |
|
||||
|---|---|---|
|
||||
| `fin_start` | 结案陈词。先指认真凶:四位证人,选一个。 | 进 FINISH(choice earcon) |
|
||||
| `fin_who_confirm` | 就指认他?单击,定下来。 | 真凶问两段确认(重播选项名后接播) |
|
||||
| `fin_clear` | 不在场证明成立。证据放人,回到证词板。 | 指认错(acc_NPC 句后接播;无限再机会) |
|
||||
| `fin_right` | 证据链闭合。周先生,你还有什么话说? | pack A 指认对 → 认罪链起(rain_swell 同点) |
|
||||
| `fin_right_b` | 证据链闭合。小顺,你还有什么话说? | pack B 指认对 |
|
||||
| `fin_ask_weapon` | 第二问:凶器是什么? | 凶器问入口 |
|
||||
| `wp_1` | 松木劈柴。 | 凶器选项(顺序由 seed 洗牌) |
|
||||
| `wp_2` | 铜秤砣。 | 凶器选项(正确答案,两包共用) |
|
||||
| `wp_3` | 铁门闩。 | 凶器选项 |
|
||||
| `wp_4` | 青瓷花盆。 | 凶器选项 |
|
||||
| `fin_wp_right` | 对。创口的铜屑,不见了的秤砣,都指向它。 | 凶器答对 |
|
||||
| `fin_wp_wrong` | 不对。铜屑不答应。记下了。 | 凶器答错(真话归因,不重答) |
|
||||
| `fin_ask_motive` | 第三问:动机是什么? | 动机问入口 |
|
||||
| `mv_1` | 监守自盗。 | 动机选项(正确答案,两包共用) |
|
||||
| `mv_2` | 寻仇泄愤。 | 动机选项 |
|
||||
| `mv_3` | 劫财入户。 | 动机选项 |
|
||||
| `fin_mv_right` | 对。库里短的东西,就是引线。 | 动机答对 |
|
||||
| `fin_mv_wrong` | 不对。账上短的东西,才是引线。记下了。 | 动机答错(真话归因) |
|
||||
|
||||
## G6 组 · 结算(旁白 ×9)
|
||||
|
||||
| id | 台词全文 | 触发/语境 |
|
||||
|---|---|---|
|
||||
| `settle_open` | 案件归档。雨,停了。 | 结算链首句(gavel 后) |
|
||||
| `rate_s` | 教科书结案。四处疑点,一击即中。 | S 档(条件见主设计 §7.2,措辞=账目) |
|
||||
| `rate_a` | 干净利落的结案。 | A 档 |
|
||||
| `rate_b` | 案子结了,卷宗上有疏漏。 | B 档 |
|
||||
| `rate_c` | 案子是结了,卷宗上补丁不少。 | C 档(保底/多错) |
|
||||
| `st_miss_0` | 指证,一次没错。 | 冗余指证 0 次 |
|
||||
| `st_miss_1` | 指证,错过一两回。 | 1-2 次 |
|
||||
| `st_miss_2` | 指证,错了不少。 | ≥3 次 |
|
||||
| `settle_hook` | 同一场雨里,还有别的案子。单击,接新案。 | 结算尾(链锁解除后单击生效;键盘态播 settle_hook_k) |
|
||||
|
||||
## G7 组 · 看门狗/卡关/纠错(旁白 ×12)
|
||||
|
||||
| id | 台词全文 | 触发/语境 |
|
||||
|---|---|---|
|
||||
| `gd_board` | 还在证词板。左右滑,换卷宗。 | BOARD 无操作轨第 1 级(10s,full,idle-only) |
|
||||
| `gd_cf` | 还在选证词。左右滑,单击确认。 | CONFRONT 无操作轨第 1 级(15s) |
|
||||
| `gd_fin` | 在想?左右滑选,单击定。 | FINISH 三问无操作轨第 1 级(20s) |
|
||||
| `gd_invalid` | 这个操作,这里用不上。 | 无效手势纠错(onceKey 同局 1 次) |
|
||||
| `hint_a` | 探长,还没听的卷宗,先过一遍耳朵。 | 无进展轨 L2(90s,有未听条目时) |
|
||||
| `hint_b` | 探长,证词里有对不上的地方,逐句比对。 | 无进展轨 L2(90s,条目听齐但疑点未齐) |
|
||||
| `hint_c1` | 试试指证:周先生说熄了灯,温太太说灯还亮着。 | 无进展轨 L3(150s,M1 保底,每句全局限 2 次) |
|
||||
| `hint_c2` | 试试指证:周先生说铜秤卖了,现场铜秤还在。 | L3(M4 保底) |
|
||||
| `b_hint_c1` | 试试指证:小顺说熄了灯,温太太说灯还亮着。 | pack B L3(M1b 保底) |
|
||||
| `b_hint_c2` | 试试指证:小顺说铜秤卖了,现场铜秤还在。 | pack B L3(M4b 保底) |
|
||||
| `hint_fin` | 疑点齐了。去结案陈词。 | 4 疑点齐(一次)/ 无进展轨 L3(疑点齐时) |
|
||||
| `recap_go` | 接着查。 | 中断复述链尾 |
|
||||
|
||||
## V 组 · 键盘变体(旁白 ×10;`gesture.inputMode()==='key'` 时替代同名基础句)
|
||||
|
||||
| id | 替代基础句 | 台词全文 |
|
||||
|---|---|---|
|
||||
| `sys_help_k` | sys_help | 方向键换卷宗,空格听取,回车重新听。按住空格指证,上键状态,下键菜单。 |
|
||||
| `tut_d0_k` | tut_d0 | 先熟悉证词板。左右方向键,换卷宗。 |
|
||||
| `tut_listen_k` | tut_listen | 空格,听取当前卷宗。 |
|
||||
| `tut_replay_k` | tut_replay | 回车,重新听这一句。 |
|
||||
| `tut_confront_k` | tut_confront | 按住空格,进入指证。 |
|
||||
| `gd_board_k` | gd_board | 还在证词板。左右方向键,换卷宗。 |
|
||||
| `gd_cf_k` | gd_cf | 还在选证词。左右方向键,空格确认。 |
|
||||
| `cf_pick_line_k` | cf_pick_line | 左右方向键,一句一句听。 |
|
||||
| `cf_confirm_k` | cf_confirm | 就这句?空格,定下来。 |
|
||||
| `settle_hook_k` | settle_hook | 同一场雨里,还有别的案子。按空格,接新案。 |
|
||||
|
||||
## N1 组 · 老葛证词(NPC Eric 平铺 ×6,sig_ge 前缀,链播句间 0.8s)
|
||||
|
||||
| id | 台词全文 | 线索标签 |
|
||||
|---|---|---|
|
||||
| `w1_1` | 昨儿夜里雨大,我守着街门,一步没敢挪窝。 | 无 |
|
||||
| `w1_2` | 九点四十,雨点子刚砸下来,小顺打着伞出的街门。 | rain=2140, dep_s=2140 |
|
||||
| `w1_3` | 十点一刻,一位先生收着黑伞进了后巷。个儿高,微驼背。 | visitor=male_tall_hunched |
|
||||
| `w1_4` | 九点五十,周账房出的街门,跟我说了声,回家。 | dep_z=2150 |
|
||||
| `w1_5` | 后半夜,温太太还来门房借过一回热水。 | 无 |
|
||||
| `w1_6` | 听说店里账上短了银子,沈老板撂了话,月底清账。 | motive=short |
|
||||
|
||||
## N2 组 · 小顺证词(NPC Dylan 平铺 ×6,sig_shun 前缀)
|
||||
|
||||
| id | 台词全文 | 线索标签 |
|
||||
|---|---|---|
|
||||
| `w2_1` | 夜里,师父打发我去城南,给刘掌柜送匣子。 | away_s=south |
|
||||
| `w2_2` | 雨太大,刘掌柜留我住的,今早才回店。 | away_s=south |
|
||||
| `w2_3` | 库房钥匙?师父嫌沉,平日就挂在柴房墙上的铜钩。 | key=hook |
|
||||
| `w2_4` | 师父的账,向来只信周先生一个人。 | 无 |
|
||||
| `w2_5` | 后门那闩,生人叫不开。熟人来了,师父才亲自去开。 | door=victim_opened |
|
||||
| `w2_6` | 周先生常念叨,账房的活,越到夜里越清静。 | 无 |
|
||||
|
||||
## N3 组 · 周先生证词(NPC Uncle_Fu 平铺 ×6,sig_zhou 前缀;pack A 真凶,4 句谎)
|
||||
|
||||
| id | 台词全文 | 线索标签 |
|
||||
|---|---|---|
|
||||
| `z_1` | 我七点进店上夜账。九点五十对完,锁了前门回家。 | dep_z=2150 |
|
||||
| `z_2` | 临走,我顺手熄了店堂的灯。那会儿,店里就剩老板。 | lamp=off(谎①,M1) |
|
||||
| `z_3` | 库房钥匙?沈老板贴身带着,从不离身。 | key=body(谎②,M2) |
|
||||
| `z_4` | 那伤,是松木劈柴砸的。铜秤?上个月卖给收废品的了。 | weapon=wood, scale=sold(谎③④,M3/M4) |
|
||||
| `z_5` | 我到家倒头就睡,夜里的事,一概不知。 | 无 |
|
||||
| `z_6` | 你们要是不信,去问我家婆娘。 | 无 |
|
||||
|
||||
## N4 组 · 温太太证词(NPC Vivian 平铺 ×6,sig_wen 前缀)
|
||||
|
||||
| id | 台词全文 | 线索标签 |
|
||||
|---|---|---|
|
||||
| `wen_1` | 雨是九点四十下的。我正收晾的衣裳,记得真真的。 | rain=2140 |
|
||||
| `wen_2` | 十点二十,我出门倒垃圾。聚宝斋店堂的灯,亮着呢。 | lamp=on_2220(M1 对立端) |
|
||||
| `wen_3` | 刚回屋,柴房后墙咯噔一响,有人爬墙!花盆翻了一个。 | wall=climbed |
|
||||
| `wen_4` | 吓得我蒙头半宿没睡着,天亮才敢吱声。 | 无 |
|
||||
| `wen_5` | 店里短银子的事,街坊都有耳闻。上礼拜他还摔了账本。 | motive=short |
|
||||
| `wen_6` | 周账房那人,见人三分笑,账算得比谁都精。 | 无 |
|
||||
|
||||
## N5 组 · 法医报告(NPC Serena 平铺 ×5,sig_fore 前缀)
|
||||
|
||||
| id | 台词全文 | 线索标签 |
|
||||
|---|---|---|
|
||||
| `fo_1` | 验尸报告,念给你听。 | 无 |
|
||||
| `fo_2` | 死亡时间,昨夜十点到十点半。 | death=2200_2230 |
|
||||
| `fo_3` | 后脑单次钝器伤,一击毙命。创口嵌有铜屑,凶器是铜器。 | weapon=bronze(M3 对立端) |
|
||||
| `fo_4` | 死者双手干净,没有搏斗痕迹。 | struggle=none |
|
||||
| `fo_5` | 鞋底是干的。当夜,死者没出过后门。 | 无 |
|
||||
|
||||
## N6 组 · 反应/被指认/认罪(NPC 平铺 ×11)
|
||||
|
||||
| id | 声部 | 台词全文 | 触发/语境 |
|
||||
|---|---|---|---|
|
||||
| `z_react_1` | 周先生 | 灯?……许是我记岔了。人上了岁数,记性不济。 | 真凶第 1 处矛盾坐实 |
|
||||
| `z_react_2` | 周先生 | 钥匙的事……他、他有时候,也贴身带着的…… | 第 2 处 |
|
||||
| `z_react_3` | 周先生 | 秤!秤的事,你们别再问了…… | 第 3 处 |
|
||||
| `z_react_4` | 周先生 | ……随你们怎么问。我要回去了。 | 第 4 处 |
|
||||
| `acc_ge` | 老葛 | 我守了一夜门!温太太来借热水,她能作证! | 错指老葛 |
|
||||
| `acc_shun` | 小顺 | 我宿在城南刘掌柜家,他全家都能作证! | 错指小顺(pack A) |
|
||||
| `acc_wen` | 温太太 | 我这把年纪翻墙?先问问那墙答不答应! | 错指温太太 |
|
||||
| `acc_zhou` | 周先生 | 我十点就到家了!我家婆娘能作证! | 错指周先生(pack B) |
|
||||
| `z_break_1` | 周先生 | 别念了……我说。库房短的三件货,是我抵了赌债。 | pack A 认罪 1 |
|
||||
| `z_break_2` | 周先生 | 他说月底清账,要报官。我求他,他不肯。 | 认罪 2 |
|
||||
| `z_break_3` | 周先生 | 我只想把钥匙放回去,把被盗做真。抡起秤砣的时候,他认出了我。 | 认罪 3 |
|
||||
|
||||
## N7 组 · 变体包 B fork(NPC 平铺 ×12;pack B 真凶=小顺)
|
||||
|
||||
| id | 声部 | 台词全文 | 线索标签/用途 |
|
||||
|---|---|---|---|
|
||||
| `b_zhou_1` | 周先生 | 我对完账就回家了,夜里雨大,路上没停。 | 无(替换 z_2 槽位) |
|
||||
| `b_zhou_2` | 周先生 | 钥匙?平时就挂在柴房铜钩上,十年了。 | key=hook(M2b 对立端) |
|
||||
| `b_zhou_3` | 周先生 | 柴房我常去,没什么不对劲的。 | 无 |
|
||||
| `b_shun_1` | 小顺 | 我走的时候,顺手熄了店堂的灯。 | lamp=off(谎①,M1b) |
|
||||
| `b_shun_2` | 小顺 | 钥匙,师父贴身带着,从不离身。 | key=body(谎②,M2b) |
|
||||
| `b_shun_3` | 小顺 | 那伤,八成是柴房劈柴砸的。铜秤早卖给收废品的了。 | weapon=wood, scale=sold(谎③④,M3b/M4b) |
|
||||
| `b_shun_4` | 小顺 | 库房的货,我一件都没碰过。 | 无(欲盖弥彰 filler) |
|
||||
| `b_shun_react_1` | 小顺 | 灯?我、我走的时候明明熄了…… | 第 1 处矛盾坐实 |
|
||||
| `b_shun_react_2` | 小顺 | 别问了……别问了…… | 第 2-4 处(复用) |
|
||||
| `b_shun_break_1` | 小顺 | 匣子是我送的,可我半路折回来了…… | 认罪 1 |
|
||||
| `b_shun_break_2` | 小顺 | 我把棉袄反穿,弓着背,葛大爷没认出我。 | 认罪 2(解释 w1_3 访客描述) |
|
||||
| `b_shun_break_3` | 小顺 | 库房的货,我拿过三件换了钱。他说要对账……秤砣下去的时候,他看着我。 | 认罪 3 |
|
||||
|
||||
---
|
||||
|
||||
**合计:158 句 ≤160 上限**(旁白 106 ×2 性别 = 212 条 + NPC 平铺 52 条 = **264 条音频** ×2 格式 = 528 文件)。
|
||||
分组账:G0 11 + G1 12 + G2 20 + G3 6 + G4 8 + G5 18 + G6 9 + G7 12 + V 10 = 106(旁白);N1-N5 29 + N6 11 + N7 12 = 52(NPC)。
|
||||
指证命中链 = `contra_hit(sfx)→ cf_hit → react_n`;miss 链 = `contra_miss(sfx)→ cf_same/cf_diff/cf_self`;
|
||||
认罪链(pack A)= `fin_right → z_break_1..3 → gavel → settle_open → rate_X → st_doubt_n → st_miss_档 → settle_hook`;
|
||||
状态链 = `st_asked_n → st_doubt_m`;中断复述链 = `st_asked_n →(未听 ≤3 时)item_* 名链 → st_doubt_m → recap_go`。
|
||||
矩阵验算与触发逻辑对照 [18-detective.md](18-detective.md) §3-§7;资产规模核对见其 §8。
|
||||
@@ -0,0 +1,600 @@
|
||||
# 18 · 《雨夜专案》完整设计(detective / Rainy Night Case)
|
||||
|
||||
> **给开发者的实现要点摘要**
|
||||
> 1. 本格推理 RPG:**核心动词=听证词找矛盾**。案卷 7 条目(4 证人证词 + 现场勘验 + 法医报告 + 结案陈词),全部音频呈现;证词=证人 NPC 平铺单音色(声音演技核心,5 个 speaker 池满配 + 专属签名 earcon 防混淆),旁白双性别只做系统/指引/现场自勘。每句证词预打**线索轴标签**,指证判定=两句是否「同轴异值」——判定与归因全部从数据单源推导,机制上不可能出现「台词说对得上、逻辑上对不上」的假话。
|
||||
> 2. 手势:证词板左右滑换条目、单击听取/停止、**双击=重听当前句**(证词按句切分,单句 ≤8s,链播句间 0.8s——≤40s 听觉负荷以「句粒度可停可重听」兑现)、上滑状态链(已问几份/疑点几处)、下滑菜单(无豁免)、**长按=进入指证**(仅限无声空闲时受理,与长按快进零冲突,豁免 E1/E2 登记 §2.6)。指证/结案菜单走「左右滑预览+单击两段确认」非模态范式,双击保持全局重听语义——**全游戏零手势偏离,仅 2 条已教学豁免**。
|
||||
> 3. 单局 15-25 分钟;结案=三问(真凶/凶器/动机),指认错误有不在场证明复述+真话归因+无限再机会,**无惩罚性失败,案子保底必结**(无进展轨看门狗三级提示最终直接给出可指证的矛盾对)。评级 S-C 账目全部真实(疑点数/冗余指证分档播报,播报值=运行值)。
|
||||
> 4. 随机性全走种子 PRNG(mulberry32,`opts.seed`/`opts.pack` 可注入):选案包(pack A 周先生真凶 / pack B 小顺真凶——同模板同证词骨架、fork 句替换)、洗牌只动 filler 句位与问题选项顺序、提示轮询顺序。**矩阵不变式:全案卷「同轴异值」语句对集合 ≡ 预设计矛盾表**(dev 断言 + playtest 断言双保险)。
|
||||
> 5. 台词表 [18-detective-voice-ids.md](18-detective-voice-ids.md):旁白 106 句 ×2 性别 + NPC 平铺 52 句 = **158 句 ≤160 上限**;操作提示句带键盘变体(`_k` 后缀,`gesture.inputMode()` 选播,V 组集中登记)。
|
||||
> 6. 规模对标 radio:单文件 game.js 预计 900-1100 行;bgm 3 + sfx 12 + voice 264 条(×2 格式 = 528 文件)。裁决仪式链不可打断(VERDICT 态 800ms 手势死窗 + 链锁,dungeon H1 同款)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
- **一句话**:你是专案组探长,一桩雨夜命案。翻听六份卷宗,比对证词,指出矛盾,锁定真凶。
|
||||
- **题材**:本格推理。核心命题「证词会撒谎,证据不会」——纯音频天然适合:证词本来就是"说"出来的信息,翻卷宗=翻语音段落,比对=重听+指证;视觉游戏需要的证词板 UI 在这里是一套**规整的听觉导航**(条目→句→指证对)。方法论 §4.1 无现成行(新模式探索),状态层重点自定为:**信息可回读**(每句可重听)+ **导航规整**(同一套左右滑/单击范式贯穿)+ **归因真话**(指证判定从数据推导)。
|
||||
- **为什么适合纯音频**:① 记忆与比对的工作记忆负载全在听觉通道——删 BGM 抢占(对白 duck 引擎自动);② 证人=声音演技(speaker+instruct 区分 + 签名 earcon),认人是听感本身;③ 本格逻辑链可完全用「谁说了什么」表述,无空间/动作信息需要降维。
|
||||
- **单局**:一周目(含教学)约 15-20 分钟;二周目(跳教学+变体案)约 13-15 分钟。结案必达(保底),评级 S-C 由过程质量决定。
|
||||
- **与现有 8 游戏的关系**:选项列表导航承接 rpg(左右滑+单击确认)、证词重听承接 simon/escape(重听=生命线)、状态档位链承接 dungeon(上滑 5 段链)、看门狗双轨承接 dungeon E0-1/E0-2(无操作轨+无进展轨);指证菜单是新交互增量(两级选择+两段确认),变体案包是内容增量。
|
||||
|
||||
## 2. 手势映射与豁免登记
|
||||
|
||||
### 2.1 证词板(BOARD,主循环)
|
||||
|
||||
BOARD 分**空闲**(无语音在播)与**播放**(条目链播中)两个子行为:
|
||||
|
||||
| 手势 | BOARD 空闲 | BOARD 播放 |
|
||||
|---|---|---|
|
||||
| 左滑 | 上一条目(循环):签名 earcon + 条目名 | 静默忽略(纯聆听保护,simon playback 先例) |
|
||||
| 右滑 | 下一条目(循环):签名 earcon + 条目名 | 静默忽略 |
|
||||
| 单击 | 播放当前条目(从首个未听句起,听过的整体重听) | **停止播报**,回空闲 |
|
||||
| 双击 | 重听当前条目最后一句 | **重听当前句**(≤8s,免费零消耗) |
|
||||
| 上滑 | 状态链(§2.5) | 状态链(打断证词;双击可重听当前句) |
|
||||
| 下滑 | 模态菜单(§2.4) | 模态菜单(打开即停播报) |
|
||||
| 长按 | **进入指证**(CONFRONT;仅空闲受理) | 引擎快进(旁白 1.7×,保持默认,不豁免) |
|
||||
| 长按松开 | — | 恢复常速 |
|
||||
|
||||
条目共 7 项:老葛证词 → 小顺证词 → 周先生证词 → 温太太证词 → 现场勘验 → 法医报告 → 结案陈词。每项播报=专属签名 earcon(0.3-0.5s,§8.2)+ 条目名整句;切到「结案陈词」+单击 → FINISH。
|
||||
|
||||
### 2.2 指证(CONFRONT)与结案(FINISH)——同一导航范式
|
||||
|
||||
两态统一为「列表预览 + 两段确认」非模态菜单(00 §5.2 非模态约定,rpg 探索同款),**双击全局语义=重听当前句,不偏离**:
|
||||
|
||||
| 手势 | CONFRONT(选证词对) | FINISH(三问) |
|
||||
|---|---|---|
|
||||
| 左/右滑 | 切换:选条目阶段=6 证词条目(同 2.1 循环);选句阶段=上一句/下一句(播该句全文) | 切换选项(播选项名) |
|
||||
| 单击 | ①选条目阶段=确认条目 → 进选句;②选句阶段=进入确认(重播该句+`cf_confirm`);③确认态=**定下这句**(第一份选定后自动进第二份,流程同前;第二份定下 → 判定) | 凶器/动机问:确认选项即答题;真凶问:进入确认(`fin_who_confirm` 重播+确认,二次单击定下=正式指认) |
|
||||
| 双击 | 重听当前预览句(宪法语义保留) | 重听当前问句/选项 |
|
||||
| 上滑 | 状态链(简版:疑点数) | 同左 |
|
||||
| 下滑 | 菜单(可「返回证词板」退出指证) | 菜单 |
|
||||
| 长按 | error 震(选择语境不作输入) | error 震 |
|
||||
|
||||
指证判定结果(§3.5)播出后自动回 BOARD 空闲。全程无倒计时、无尝试次数上限。
|
||||
|
||||
### 2.3 标题(TITLE)/ 结算(SETTLE)/ 裁决(VERDICT)
|
||||
|
||||
| 手势 | TITLE | VERDICT(认罪链播中) | SETTLE |
|
||||
|---|---|---|---|
|
||||
| 单击 | 继续(有档)/ 新案(无档) | **吞掉**(手势死窗+链锁,§7.4) | 接新案(pack 翻转+新 seed) |
|
||||
| 右滑 | 换一桩新案(重掷 seed/pack,meta 保留,run 清空) | 吞掉 | error |
|
||||
| 双击 | 重听 continue_ask | 吞掉 | 重听结算链 |
|
||||
| 上滑 | 重听案情简介(intro_1..3) | 吞掉 | 重听评级段 |
|
||||
| 下滑 | 菜单 | 吞掉 | 菜单 |
|
||||
| 左滑/长按 | error | 吞掉 | error |
|
||||
|
||||
### 2.4 模态菜单(遵守 00 文档 §5.2 全球约定,无豁免)
|
||||
|
||||
下滑呼出(同时停播报/停看门狗)→ 单击循环切换(每切播项名)→ 双击确认 → 上滑=帮助 → 下滑=关闭。选项 5 项:返回证词板 / 去指证(=长按捷径的菜单冗余入口,P4 容错)/ 保存并回大厅 / 重新听帮助 / 重新查这桩案子。本作无实时玩法,菜单=天然暂停。
|
||||
|
||||
### 2.5 每状态手势映射总表(6 手势 + 长按松开,全状态覆盖)
|
||||
|
||||
| 手势 | TITLE | DRILL | BOARD停 | BOARD播 | CONFRONT | FINISH | VERDICT | MENU | SETTLE |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| 单击 | 继续/新案 | 步骤作答 | 播放条目 | 停止 | 确认(两段) | 确认/答题 | 吞掉 | 循环切项 | 接新案 |
|
||||
| 双击 | 重听 | 重听教学句 | 重听末句 | 重听当前句 | 重听预览句 | 重听问句 | 吞掉 | 确认执行 | 重听结算链 |
|
||||
| 左滑 | error | 教学指定 | 上一条目 | 忽略 | 上一项/上一句 | 上一项 | 吞掉 | 上一项 | error |
|
||||
| 右滑 | 换新案 | 教学指定 | 下一条目 | 忽略 | 下一项/下一句 | 下一项 | 吞掉 | 下一项 | error |
|
||||
| 上滑 | 案情简介 | 状态(教过后) | 状态链 | 状态链 | 状态链(简) | 重听问句 | 吞掉 | 帮助 | 评级段 |
|
||||
| 下滑 | 菜单 | 跳过教学 | 菜单 | 菜单 | 菜单 | 菜单 | 吞掉 | 关闭 | 菜单 |
|
||||
| 长按 | error | 步骤作答 | 进入指证 | 快进(引擎) | error | error | 吞掉 | error | error |
|
||||
| 长按松开 | — | — | — | 恢复常速 | — | — | — | — | — |
|
||||
|
||||
无效手势一律 error 震;full 档 `gd_invalid`(onceKey 同局 1 次,之后仅 error 震);idle-only(纠错/看门狗句在播时跳过,引擎 guide 已实现)。
|
||||
|
||||
### 2.6 豁免登记(必须显式)
|
||||
|
||||
| # | 豁免条目 | 理由(先例) |
|
||||
|---|---|---|
|
||||
| E1 | BOARD 单击=播放/停止(非"确认") | "检查/翻阅"类非即时操作,300ms tap 延迟无碍(escape 单击=检查、dungeon 单击=脉冲同款);停止播报是纯聆听期的标准出口 |
|
||||
| E2 | BOARD 空闲长按=进入指证(核心动词快捷入口) | 绑定条件=无语音在播(`!audio.isVoicePlaying()`),与长按快进(仅在播时生效)**在时间上互斥、零冲突**,故不需 dungeon E3 式的对局期豁免;播放中长按仍走引擎快进。教学 D5 讲清(tut_confront),菜单另有「去指证」冗余入口 |
|
||||
|
||||
教学(DRILL)逐步讲清 E1/E2;无 immediateTap;模态菜单无豁免。
|
||||
|
||||
## 3. 核心动词:听证词找矛盾(信息设计)
|
||||
|
||||
### 3.1 信息结构总览
|
||||
|
||||
```
|
||||
案卷(7 条目)── 4 证人证词(各 6 句,NPC 平铺)
|
||||
├─ 现场勘验(6 句,旁白=探长自勘)
|
||||
├─ 法医报告(5 句,NPC 秦法医)
|
||||
└─ 结案陈词(FINISH 入口)
|
||||
每句 = 1 个音频文件 + 数据侧「线索轴:值」标签(0-2 个)
|
||||
矛盾 = 两句共享同一轴且值不同;全案卷该集合 ≡ 预设计矛盾表(4 条)
|
||||
```
|
||||
|
||||
- 证词链播:句间 0.8s 停顿;**句粒度重听**(双击)+ 句粒度停止(单击)——单段连续听觉负荷 ≤35s 且随时可停可回,满足「证词单段 ≤40s、可分段」要求。
|
||||
- 三套信息单源自洽(dungeon §3.2 同思路):证词=人的主张、现场/法医=物证主张、状态链=进度账目——进度账目(已问 X/疑点 n)从 `heard/doubts` 实时读出,播报值=运行值(账目真实)。
|
||||
|
||||
### 3.2 线索轴定义(pack A《雨夜·聚宝斋》)
|
||||
|
||||
案情:古董商沈万川死于自家后院柴房,后脑钝器伤,当夜暴雨。
|
||||
|
||||
| 轴 | 含义 | 取值 |
|
||||
|---|---|---|
|
||||
| rain | 雨何时开始 | 21:40 |
|
||||
| dep_s | 小顺离店时刻 | 21:40 |
|
||||
| away_s | 小顺当晚去处 | 城南 |
|
||||
| visitor | 后巷访客 | 22:15 高个微驼男性 |
|
||||
| dep_z | 周先生离店时刻 | 21:50 |
|
||||
| **lamp** | 店堂灯 | 熄 / 亮 |
|
||||
| **key** | 库房钥匙位置 | 挂柴房铜钩 / 贴身 |
|
||||
| **weapon** | 凶器材质 | 松木劈柴 / 铜器 |
|
||||
| **scale** | 铜秤下落 | 已卖 / 在库房 |
|
||||
| wall | 柴房后墙 | 有人翻爬 |
|
||||
| door | 后门开法 | 死者亲自为熟人开(规范值 victim_opened) |
|
||||
| death | 死亡时间 | 22:00-22:30 |
|
||||
| motive | 账上短银 | 月底要清账 |
|
||||
| struggle | 搏斗痕迹 | 无 |
|
||||
|
||||
(粗体 4 轴=矛盾承载轴;其余轴全部同值互证或单断言,见 3.4 验算。)
|
||||
|
||||
### 3.3 案卷正文(pack A;全部台词 ≤31 字,全文与台词表一致)
|
||||
|
||||
**老葛(门卫)**:w1_1「昨儿夜里雨大,我守着街门,一步没敢挪窝。」w1_2「九点四十,雨点子刚砸下来,小顺打着伞出的街门。」w1_3「十点一刻,一位先生收着黑伞进了后巷。个儿高,微驼背。」w1_4「九点五十,周账房出的街门,跟我说了声,回家。」w1_5「后半夜,温太太还来门房借过一回热水。」w1_6「听说店里账上短了银子,沈老板撂了话,月底清账。」
|
||||
|
||||
**小顺(学徒)**:w2_1「夜里,师父打发我去城南,给刘掌柜送匣子。」w2_2「雨太大,刘掌柜留我住的,今早才回店。」w2_3「库房钥匙?师父嫌沉,平日就挂在柴房墙上的铜钩。」w2_4「师父的账,向来只信周先生一个人。」w2_5「后门那闩,生人叫不开。熟人来了,师父才亲自去开。」w2_6「周先生常念叨,账房的活,越到夜里越清静。」
|
||||
|
||||
**周先生(账房,真凶)**:z_1「我七点进店上夜账。九点五十对完,锁了前门回家。」z_2「临走,我顺手熄了店堂的灯。那会儿,店里就剩老板。」z_3「库房钥匙?沈老板贴身带着,从不离身。」z_4「那伤,是松木劈柴砸的。铜秤?上个月卖给收废品的了。」z_5「我到家倒头就睡,夜里的事,一概不知。」z_6「你们要是不信,去问我家婆娘。」
|
||||
|
||||
**温太太(房东)**:wen_1「雨是九点四十下的。我正收晾的衣裳,记得真真的。」wen_2「十点二十,我出门倒垃圾。聚宝斋店堂的灯,亮着呢。」wen_3「刚回屋,柴房后墙咯噔一响,有人爬墙!花盆翻了一个。」wen_4「吓得我蒙头半宿没睡着,天亮才敢吱声。」wen_5「店里短银子的事,街坊都有耳闻。上礼拜他还摔了账本。」wen_6「周账房那人,见人三分笑,账算得比谁都精。」
|
||||
|
||||
**现场勘验(旁白)**:sc_0「你打伞进了后院。雨水顺着瓦檐,淌成一道帘子。」sc_1「柴房门口,沈万川面朝下倒着。后脑一处伤,现场没有凶器。」sc_2「后门门闩完好,是死者自己开的门。生人叫不开这扇门。」sc_3「泥地上只有进来的脚印,没有出去的。墙根的花盆,翻了一个。」sc_4「库房铜秤立在架上,秤砣不见了。」sc_5「柴房墙上,挂钥匙的铜钩,空着。」
|
||||
|
||||
**法医报告(秦法医)**:fo_1「验尸报告,念给你听。」fo_2「死亡时间,昨夜十点到十点半。」fo_3「后脑单次钝器伤,一击毙命。创口嵌有铜屑,凶器是铜器。」fo_4「死者双手干净,没有搏斗痕迹。」fo_5「鞋底是干的。当夜,死者没出过后门。」
|
||||
|
||||
(sc_5 铜钩空为叙事佐证,**不挂 key 轴**——「平日挂墙上」是习惯断言、「今晨钩空」是状态断言,二者不构成值冲突;数据侧 sc_5 clues={}。)
|
||||
|
||||
### 3.4 矛盾预设计表 + 矩阵验算(playtest 直证对象)
|
||||
|
||||
**矛盾表(pack A,4 条)**:
|
||||
|
||||
| # | 轴 | 语句 A(真凶侧) | 值 A | 语句 B | 值 B | hit 反应 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| M1 | lamp | z_2 周先生「顺手熄了店堂的灯」 | 熄 | wen_2 温太太「十点二十……灯,亮着呢」 | 亮 | z_react_1 |
|
||||
| M2 | key | z_3 周先生「贴身带着,从不离身」 | 贴身 | w2_3 小顺「平日就挂在柴房墙上的铜钩」 | 挂墙上 | z_react_2 |
|
||||
| M3 | weapon | z_4 周先生「是松木劈柴砸的」 | 木 | fo_3 法医「创口嵌有铜屑,凶器是铜器」 | 铜 | z_react_3 |
|
||||
| M4 | scale | z_4 周先生「铜秤上个月卖给收废品的了」 | 已卖 | sc_4 现场「铜秤立在架上,秤砣不见了」 | 在 | z_react_4 |
|
||||
|
||||
注:M3/M4 共用语句 z_4(一句两断言),指证时选 z_4 两次、分别对 fo_3 与 sc_4——设计内行为。指证对**无序**(任一端在前都判 hit)。
|
||||
|
||||
**矩阵验算(全轴枚举)**——「同轴异值对 ≡ {M1..M4}」:
|
||||
|
||||
| 轴 | 断言语句(值) | 结论 |
|
||||
|---|---|---|
|
||||
| rain | w1_2(21:40)/ wen_1(21:40) | 同值一致(互证) |
|
||||
| dep_s | w1_2(21:40) | 单断言 |
|
||||
| away_s | w2_1(城南)/ w2_2(城南) | 一致 |
|
||||
| visitor | w1_3 | 单断言 |
|
||||
| dep_z | z_1(21:50)/ w1_4(21:50) | 一致 |
|
||||
| lamp | z_2(熄)/ wen_2(亮) | **M1,唯一冲突对** |
|
||||
| key | z_3(贴身)/ w2_3(挂墙上) | **M2,唯一冲突对** |
|
||||
| weapon | z_4(木)/ fo_3(铜) | **M3,唯一冲突对** |
|
||||
| scale | z_4(已卖)/ sc_4(在架) | **M4,唯一冲突对** |
|
||||
| wall | wen_3(有人爬墙)/ sc_3(翻墙离开) | 一致 |
|
||||
| door | w2_5(victim_opened)/ sc_2(victim_opened) | 一致(习惯断言与状态断言取同一规范值) |
|
||||
| death | fo_2 | 单断言(wen_3 的 22:25 落在窗内,旁证) |
|
||||
| motive | w1_6 / wen_5 | 一致 |
|
||||
| struggle | sc_1(无)/ fo_4(无) | 一致 |
|
||||
| (无轴) | w1_1、w1_5、w2_4、w2_6、z_5、z_6、wen_4、wen_6、sc_0、sc_5、fo_1、fo_5 | 叙事/filler,不参与判定 |
|
||||
|
||||
验算结论:除 M1-M4 外不存在任何同轴异值对;一致对 6 组(cf_same 的真话来源)、无轴句 13 句(cf_diff 的真话来源)——三条归因档全部真实可达。
|
||||
|
||||
### 3.5 指证交互与判定算法
|
||||
|
||||
```
|
||||
judge(a, b):
|
||||
if a.id === b.id → cf_self(同一句,比自己)
|
||||
shared = axes(a) ∩ axes(b)
|
||||
if shared 为空 → cf_miss(钝音)+ cf_diff「说的不是一件事」
|
||||
else if 存在轴 v: a[v] ≠ b[v]:
|
||||
if {a,b} ∈ 矛盾表(无序) → contra_hit + cf_hit + react_n + doubts[m]=true(疑点+1)
|
||||
else → 数据错误(dev 断言禁止;playtest 失败项)
|
||||
else(共享轴全同值) → cf_miss + cf_same「说得一致,不矛盾」
|
||||
每次判定(无论中未中)→ cfMiss 判定计数、自动回 BOARD;无资源惩罚、无次数上限
|
||||
```
|
||||
|
||||
- hit 反应句按真凶**累计被坐实次数**取 z_react_1..4(递进:记岔了→支吾→拒绝→想走),声音演技核心段;pack B 用 b_shun_react_1..2(第 3/4 次复用 react_2)。
|
||||
- 疑点 4/4 齐时播一次 `hint_fin`(引导去结案);疑点数进状态链与存档,账目同源。
|
||||
|
||||
### 3.6 归因真话律(指证/指认双处)
|
||||
|
||||
| 场景 | 播报 | 真话依据 |
|
||||
|---|---|---|
|
||||
| 指证 miss·同轴同值 | cf_same | 轴数据同值(如 w1_2 vs wen_1 都说雨 21:40 起) |
|
||||
| 指证 miss·不同轴 | cf_diff | 无共享轴(绝大多数跨话题句对) |
|
||||
| 指证 miss·同一句 | cf_self | 选择器同一句 |
|
||||
| 指认错人 | 该证人 acc_NPC(自述不在场证明)+ fin_clear | 各无辜者的 alibi 在证词中已铺(w2_1/w2_2 小顺、w1_1/w1_5 老葛、wen_4+visitor 男性访客排除温太太)+ pack B 的 acc_zhou |
|
||||
| 凶器答错 | fin_wp_wrong(「铜屑不答应」) | fo_3 物证 |
|
||||
| 动机答错 | fin_mv_wrong | w1_6/wen_5 账短银 |
|
||||
|
||||
**禁止**:任何「差一点」「再想想」式无信息归因;播报值与数据值必须一致(playtest 失败项锁定)。
|
||||
|
||||
### 3.7 信息密度与记忆设计
|
||||
|
||||
- 单次播报 ≤3 选项:所有列表(条目/句/凶器/动机/嫌疑人)都是**逐项预览**(左右滑一次听一项),绝不通读清单;帮助句(sys_help)例外(dungeon help 先例)。
|
||||
- 证人区分三板斧:① 5 speaker 满配 + instruct 常量(§8.3);② 每证人**专属签名 earcon**(条目切换/指证选择/状态复述时前置,0.3-0.5s);③ 周先生(与男旁白同 speaker)与秦法医(与女旁白同 speaker)是仅有的两处重叠,靠签名+语境+instruct 兜底——重叠从不在相邻句出现(证词链内无旁白插话)。
|
||||
- P1 工作记忆:疑点无需记忆——状态链随时报数;已选第一份证词在选第二份时**自动重播一次**(选句确认后链上接播),不需要玩家脑内持有。
|
||||
|
||||
## 4. 状态机
|
||||
|
||||
### 4.1 状态列表
|
||||
|
||||
`TITLE / DRILL / BOARD / CONFRONT / FINISH / VERDICT / SETTLE` + `MENU`(挂起 overlay,记录返回态)。BOARD 含空闲/播放两子行为(非独立态)。FINISH 子相位:who → weapon → motive;CONFRONT 子相位:item1 → line1 → confirm → item2 → line2 → confirm → judged。
|
||||
|
||||
### 4.2 转移图
|
||||
|
||||
```
|
||||
start ──> TITLE ──单击(有档)──> BOARD(恢复存档+局面复述)
|
||||
│单击(无档) ├──────> DRILL(仅首次, meta.drillDone 控制) ──完成/下滑跳过──> BOARD
|
||||
│右滑=换新案(重掷 seed/pack) ──> BOARD(新案)
|
||||
v
|
||||
BOARD(证词板) ⇄ 条目链播(子行为)
|
||||
│ 长按(空闲) / 菜单「去指证」 │ 选中「结案陈词」条目+单击
|
||||
v v
|
||||
CONFRONT ──判定(hit/miss)──> BOARD FINISH(三问)
|
||||
│(经菜单「返回证词板」退出) ├─指认错→ acc 链 + fin_clear → BOARD
|
||||
│ └─指认对→ 凶器问→动机问→ VERDICT
|
||||
VERDICT(认罪链, 手势死窗+链锁) ──> SETTLE ──单击──> TITLE(新案: pack=1-pack)
|
||||
任意正交态 ──下滑──> MENU ──关闭/返回──> 原状态
|
||||
```
|
||||
|
||||
### 4.3 语音链推进契约
|
||||
|
||||
- 证词链=onComplete 串行(句间 0.8s 定时器);**兜底看门狗**:单句 8s 无 onComplete(资产缺失)→ 告警续链(「屏蔽任意 voice 资产流程不卡死」契约)。
|
||||
- 裁决链(VERDICT)与结算链(SETTLE)为**独占链**:链首停一切看门狗,链内不受理手势(§7.4)。
|
||||
- 教学链 `_speakGuarded` 串行(radio/dungeon 同款,杜绝同 tick 双调度抢占——D0-1 教训已固化引擎保险丝)。
|
||||
|
||||
## 5. 教学编排(DRILL,首局内嵌)
|
||||
|
||||
每步=一句短旁白→等待实操→反馈;做错重教同一步(新短句 ≤2 次)后自动演示通过;下滑=跳过教学(`tut_skip`),`meta.drillDone` 持久化,二周目不再进。教学寄生于真实案卷(D2 就听老葛真实证词,无假内容——教学承诺必须兑现)。
|
||||
|
||||
| 步 | 旁白 | 等待动作 | 通过条件/反馈 |
|
||||
|---|---|---|---|
|
||||
| D0 | intro_1→intro_2→intro_3→tut_d0 | 左滑或右滑任一 | 签名 earcon+条目名;confirm 震。**首输入 ≤60s 达成点 ≈t+20s** |
|
||||
| D1 | tut_listen | 单击 | 老葛证词链播(真实内容) |
|
||||
| D2 | tut_replay | 双击 | 当前句重播(免费) |
|
||||
| D3 | tut_status | 上滑 | 状态链(st_asked_1→st_doubt_0) |
|
||||
| D4 | tut_confront | 长按 | 进 CONFRONT + cf_start |
|
||||
| D5 | tut_cf_try | 选条目1(滑+单击)→选句(滑)→单击确认→选条目2→选句→单击确认 | **判定被教学截获**:任何选择组合都播 tut_cf_ok(练习不算数、不计 cfMiss、不动 doubts),回 BOARD。**首正反馈 ≤3min 达成点(success 震)** |
|
||||
| D6 | tut_done | — | 进 BOARD 自由调查;此后上滑/菜单随时可用 |
|
||||
|
||||
- D5 教学局不真判定的原因:教学时玩家只听过 1 份证词,真判定必 miss,会把「指证=挫败」印进第一印象;练习路径承诺「真矛盾要靠耳朵找」并在 2 份证词听齐后自然兑现(预期玩家首个真 hit 在 t+5-8min)。
|
||||
- FTUE 核对:遮罩→intro 三句(~13s)→D0 完成约 t+20s(<60s ✓);D5 完成 ≈t+2.5min(≤3min ✓);全程零惩罚(无资源、无倒计时)✓。
|
||||
|
||||
## 6. 案卷数据结构与随机性
|
||||
|
||||
### 6.1 JSON 草案(pack A 全量 + pack B fork;台词全文见台词表)
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": 1,
|
||||
"packs": [
|
||||
{
|
||||
"id": 0, "name": "雨夜·聚宝斋", "culprit": "w3", "culpritName": "周先生",
|
||||
"items": [
|
||||
{ "id": "w1", "kind": "witness", "name": "item_w1", "sig": "sig_ge",
|
||||
"intro": null, "lines": [
|
||||
{ "id": "w1_1", "shuffle": false, "clues": {} },
|
||||
{ "id": "w1_2", "shuffle": true, "clues": { "rain": "2140", "dep_s": "2140" } },
|
||||
{ "id": "w1_3", "shuffle": false, "clues": { "visitor": "male_tall_hunched" } },
|
||||
{ "id": "w1_4", "shuffle": true, "clues": { "dep_z": "2150" } },
|
||||
{ "id": "w1_5", "shuffle": true, "clues": {} },
|
||||
{ "id": "w1_6", "shuffle": false, "clues": { "motive": "short" } } ] },
|
||||
{ "id": "w2", "kind": "witness", "name": "item_w2", "sig": "sig_shun",
|
||||
"intro": null, "lines": [
|
||||
{ "id": "w2_1", "shuffle": false, "clues": { "away_s": "south" } },
|
||||
{ "id": "w2_2", "shuffle": true, "clues": { "away_s": "south" } },
|
||||
{ "id": "w2_3", "shuffle": false, "clues": { "key": "hook" } },
|
||||
{ "id": "w2_4", "shuffle": true, "clues": {} },
|
||||
{ "id": "w2_5", "shuffle": false, "clues": { "door": "victim_opened" } },
|
||||
{ "id": "w2_6", "shuffle": true, "clues": {} } ] },
|
||||
{ "id": "w3", "kind": "witness", "name": "item_w3", "sig": "sig_zhou",
|
||||
"intro": "intro_w3", "lines": [
|
||||
{ "id": "z_1", "shuffle": false, "clues": { "dep_z": "2150" } },
|
||||
{ "id": "z_2", "shuffle": false, "clues": { "lamp": "off" } },
|
||||
{ "id": "z_3", "shuffle": false, "clues": { "key": "body" } },
|
||||
{ "id": "z_4", "shuffle": false, "clues": { "weapon": "wood", "scale": "sold" } },
|
||||
{ "id": "z_5", "shuffle": true, "clues": {} },
|
||||
{ "id": "z_6", "shuffle": true, "clues": {} } ] },
|
||||
{ "id": "w4", "kind": "witness", "name": "item_w4", "sig": "sig_wen",
|
||||
"intro": null, "lines": [
|
||||
{ "id": "wen_1", "shuffle": false, "clues": { "rain": "2140" } },
|
||||
{ "id": "wen_2", "shuffle": false, "clues": { "lamp": "on_2220" } },
|
||||
{ "id": "wen_3", "shuffle": false, "clues": { "wall": "climbed" } },
|
||||
{ "id": "wen_4", "shuffle": true, "clues": {} },
|
||||
{ "id": "wen_5", "shuffle": false, "clues": { "motive": "short" } },
|
||||
{ "id": "wen_6", "shuffle": true, "clues": {} } ] },
|
||||
{ "id": "scene", "kind": "scene", "name": "item_scene", "sig": "sig_scene",
|
||||
"lines": [
|
||||
{ "id": "sc_0", "shuffle": false, "clues": {} },
|
||||
{ "id": "sc_1", "shuffle": false, "clues": { "struggle": "none" } },
|
||||
{ "id": "sc_2", "shuffle": false, "clues": { "door": "victim_opened" } },
|
||||
{ "id": "sc_3", "shuffle": false, "clues": { "wall": "climbed" } },
|
||||
{ "id": "sc_4", "shuffle": false, "clues": { "scale": "present" } },
|
||||
{ "id": "sc_5", "shuffle": false, "clues": {} } ] },
|
||||
{ "id": "fore", "kind": "forensic", "name": "item_fore", "sig": "sig_fore",
|
||||
"lines": [
|
||||
{ "id": "fo_1", "shuffle": false, "clues": {} },
|
||||
{ "id": "fo_2", "shuffle": false, "clues": { "death": "2200_2230" } },
|
||||
{ "id": "fo_3", "shuffle": false, "clues": { "weapon": "bronze" } },
|
||||
{ "id": "fo_4", "shuffle": false, "clues": { "struggle": "none" } },
|
||||
{ "id": "fo_5", "shuffle": false, "clues": {} } ] }
|
||||
],
|
||||
"contradictions": [
|
||||
{ "id": "m1", "a": "z_2", "b": "wen_2", "axis": "lamp", "hint": "hint_c1" },
|
||||
{ "id": "m2", "a": "z_3", "b": "w2_3", "axis": "key", "hint": null },
|
||||
{ "id": "m3", "a": "z_4", "b": "fo_3", "axis": "weapon", "hint": null },
|
||||
{ "id": "m4", "a": "z_4", "b": "sc_4", "axis": "scale", "hint": "hint_c2" }
|
||||
],
|
||||
"verdict": { "accuseLine": "fin_right",
|
||||
"break": ["z_break_1", "z_break_2", "z_break_3"] },
|
||||
"wrongAccuse": { "w1": "acc_ge", "w2": "acc_shun", "w4": "acc_wen" },
|
||||
"reacts": ["z_react_1", "z_react_2", "z_react_3", "z_react_4"],
|
||||
"questions": {
|
||||
"weapon": { "options": ["wp_1", "wp_2", "wp_3", "wp_4"], "answer": "wp_2" },
|
||||
"motive": { "options": ["mv_1", "mv_2", "mv_3"], "answer": "mv_1" } }
|
||||
},
|
||||
{
|
||||
"id": 1, "name": "雨夜·聚宝斋(变体)", "culprit": "w2", "culpritName": "小顺",
|
||||
"base": 0,
|
||||
"overrides": {
|
||||
"w2": { "lines": [
|
||||
{ "id": "w2_1", "shuffle": false, "clues": { "away_s": "south" } },
|
||||
{ "id": "w2_2", "shuffle": true, "clues": { "away_s": "south" } },
|
||||
{ "id": "b_shun_1", "shuffle": false, "clues": { "lamp": "off" } },
|
||||
{ "id": "b_shun_2", "shuffle": false, "clues": { "key": "body" } },
|
||||
{ "id": "b_shun_3", "shuffle": false, "clues": { "weapon": "wood", "scale": "sold" } },
|
||||
{ "id": "b_shun_4", "shuffle": true, "clues": {} } ] },
|
||||
"w3": { "lines": [
|
||||
{ "id": "z_1", "shuffle": false, "clues": { "dep_z": "2150" } },
|
||||
{ "id": "b_zhou_1", "shuffle": true, "clues": {} },
|
||||
{ "id": "b_zhou_2", "shuffle": false, "clues": { "key": "hook" } },
|
||||
{ "id": "b_zhou_3", "shuffle": true, "clues": {} },
|
||||
{ "id": "z_5", "shuffle": true, "clues": {} },
|
||||
{ "id": "z_6", "shuffle": true, "clues": {} } ] },
|
||||
"contradictions": [
|
||||
{ "id": "m1b", "a": "b_shun_1", "b": "wen_2", "axis": "lamp", "hint": "b_hint_c1" },
|
||||
{ "id": "m2b", "a": "b_shun_2", "b": "b_zhou_2", "axis": "key", "hint": null },
|
||||
{ "id": "m3b", "a": "b_shun_3", "b": "fo_3", "axis": "weapon", "hint": null },
|
||||
{ "id": "m4b", "a": "b_shun_3", "b": "sc_4", "axis": "scale", "hint": "b_hint_c2" } ],
|
||||
"verdict": { "accuseLine": "fin_right_b",
|
||||
"break": ["b_shun_break_1", "b_shun_break_2", "b_shun_break_3"] },
|
||||
"wrongAccuse": { "w1": "acc_ge", "w3": "acc_zhou", "w4": "acc_wen" },
|
||||
"reacts": ["b_shun_react_1", "b_shun_react_2", "b_shun_react_2", "b_shun_react_2"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 seed 用法
|
||||
|
||||
```
|
||||
rng = mulberry32(seed) // 与 dungeon 同族
|
||||
pack = opts.pack ?? (opts.seed != null ? seed % 2 : null)
|
||||
正式局:seed = Math.floor(Math.random()*1e9);pack 首局=0,结案后新案 pack = 1 - pack(强制换案)
|
||||
洗牌(生成期一次性,存入 run.layout):
|
||||
① 各证人内部 shuffle:true 的 filler 句在其槽位集合内置换(首句与全部矛盾承载句固定)
|
||||
② 凶器问 4 选项、动机问 3 选项:Fisher-Yates by rng
|
||||
③ 保底提示 hint 轮询顺序:permuted by rng
|
||||
复现断言口径:同 (seed, pack) 两次构造 → game._case 深相等(布局/选项序/提示序逐项相等,不写魔法值)
|
||||
```
|
||||
|
||||
### 6.3 变体生成规则(pack B 逻辑摘要)
|
||||
|
||||
- **同模板**:同一桩雨夜命案、同 4 证人位、同现场/法医/温太太/老葛证词(音频全复用)、同 4 轴矛盾结构(lamp/key/weapon/scale)。
|
||||
- **不同真凶**:pack B 真凶=学徒小顺。fork=替换两份证词的句集:小顺换成 4 句谎(b_shun_1..4,复用其真实离店叙事作伪 alibi),周先生换成 3 句中性真话(b_zhou_1..3,其中 b_zhou_2「钥匙挂铜钩」成为 M2b 的对立端)。
|
||||
- **矩阵自洽**:pack B 的验算与 pack A 同构——lamp 轴唯一冲突对 (b_shun_1, wen_2);key 轴唯一冲突对 (b_shun_2, b_zhou_2);weapon/scale 由 b_shun_3(一句两断言)对 fo_3/sc_4。老葛 w1_3 的访客描述(高个微驼)由 b_shun_break_2「棉袄反穿,弓着背」解释,不产生新轴冲突。
|
||||
- **动机/凶器答案跨包一致**:监守自盗(小顺拿库房货换钱,b_shun_break_3 交代)与铜秤砣——问题反馈句两包共用。
|
||||
- **内容成本**:fork 仅 12 句 NPC + 1 句旁白(fin_right_b)+ 2 句提示(b_hint_c1/2)。
|
||||
|
||||
### 6.4 矩阵不变式(dev/playtest 双保险)
|
||||
|
||||
启动时与测试中断言:`{(a,b,axis) : a,b 同轴且值不同} ≡ contradictions`。任何台词改稿若引入新的同轴异值对,此断言立即失败——防止「文案事故变成指证归因假话」。
|
||||
|
||||
## 7. 评级系统与裁决仪式
|
||||
|
||||
### 7.1 结案三问(FINISH)
|
||||
|
||||
1. **真凶**(4 选 1:老葛/小顺/周先生/温太太;两段确认)。错→该证人 acc_NPC 自述不在场证明 + fin_clear → 回 BOARD;**无限再机会**,wrongWho 计数。
|
||||
2. **凶器**(4 选 1:松木劈柴/铜秤砣/铁门闩/青瓷花盆;选项序由 seed 洗牌)。对=fin_wp_right,错=fin_wp_wrong(真话归因,不重答,记账)。
|
||||
3. **动机**(3 选 1:监守自盗/寻仇泄愤/劫财入户)。同上。
|
||||
|
||||
### 7.2 评级标准(S-C)
|
||||
|
||||
变量:`doubts`(疑点 0-4)、`weaponOk`、`motiveOk`、`wrongWho`(指认错次数)、`cfMiss`(冗余指证次数)、`hints`(保底提示已播数)。
|
||||
|
||||
| 档 | 条件(自上而下首个命中) | 播报 |
|
||||
|---|---|---|
|
||||
| S | doubts=4 ∧ weaponOk ∧ motiveOk ∧ wrongWho=0 ∧ cfMiss≤2 ∧ hints=0 | rate_s「教科书结案。四处疑点,一击即中。」 |
|
||||
| A | doubts≥3 ∧ weaponOk ∧ motiveOk ∧ wrongWho≤1 ∧ hints≤1 | rate_a「干净利落的结案。」 |
|
||||
| B | doubts≥2 ∧ wrongWho≤2 | rate_b「案子结了,卷宗上有疏漏。」 |
|
||||
| C | 其余(案子必然已结) | rate_c「案子是结了,卷宗上补丁不少。」 |
|
||||
|
||||
- 结案必达:无失败态;C 档覆盖「全靠保底提示/疑点<2/指认错 ≥3」。
|
||||
- 措辞-账目对账:S 的「一击即中」=wrongWho 0;「四处疑点」=doubts 4;C 的「补丁」=hints≥2 或 cfMiss≥3 或 wrongWho≥3——每句评级文案与触发条件一一对应(E1-2 档位措辞真实教训)。
|
||||
|
||||
### 7.3 结算账目播报(真实)
|
||||
|
||||
SETTLE 链:`settle_open → rate_X → st_doubt_n(疑点实数)→ st_miss_档(cfMiss: 0/1-2/≥3 → 三档真话)→ settle_hook(单击接新案,键盘态用 settle_hook_k)`。所有分档句按运行值选播,播报值=存储值(playtest 锁定)。
|
||||
|
||||
### 7.4 裁决仪式(VERDICT,结案宣判完整弧线)
|
||||
|
||||
指认正确后进入,**链不可打断**(dungeon H1 教训:结算链不许被在途手势截杀):
|
||||
|
||||
```
|
||||
fin_right(证据链闭合)→ sfx/rain_s swell 起
|
||||
→ z_break_1 → z_break_2 → z_break_3(认罪,声音演技高潮;bgm/verdict)
|
||||
→ sfx/gavel(法槌)→ settle_open → SETTLE(接 §7.3 链)
|
||||
```
|
||||
|
||||
- **手势死窗**:进 VERDICT 即盖 `VERDICT_GRACE_MS=800` 戳(同 dungeon SETTLE_GRACE:touchstart+450ms 长按判定+300ms 双击窗 ≈750ms,800 全盖);死窗内一切手势吞掉(ghost 手势不算输入、不碰看门狗)。
|
||||
- **链锁**:VERDICT→SETTLE 全链播完(settle_hook 落声)前,单击/右滑/长按一律吞掉;链完成后单击=接新案(settle_hook 承诺「单击,接新案」言行一致——simon round1-live 教训)。双击=重听结算链仅在链完成后受理。
|
||||
|
||||
## 8. 音频三层
|
||||
|
||||
### 8.1 BGM(3 首,Mureka instrumental,-20 LUFS,ogg+m4a,无缝循环;雨声底烤进曲目,省第三声流)
|
||||
|
||||
| id | 状态映射 | Mureka prompt(英文) |
|
||||
|---|---|---|
|
||||
| bgm/menu | TITLE / MENU / SETTLE(结算 volume 0.5) | "Rainy night noir jazz ballad, slow brushed drums, lazy saxophone, soft rain ambience, melancholy and calm, no vocals, seamless loop" |
|
||||
| bgm/board | DRILL / BOARD / CONFRONT / FINISH | "Moody jazz trio for a detective's office, muted piano walking, upright bass, soft rain on window, focused and mysterious, no vocals, seamless loop" |
|
||||
| bgm/verdict | VERDICT(认罪链起播,交叉淡入) | "Tense noir jazz climax, brooding low strings under sparse piano, rain intensifying then settling, dramatic restraint, no vocals, seamless loop" |
|
||||
|
||||
切换全走引擎 crossfade;对白 duck 20% 引擎自动。choice/danger 不换曲(状态区分由 guide earcon 承担,氛围连续性优先)。预算:3 曲 ×2 候选 = 6 任务 $0.18(项目上限 $2.00 内)。
|
||||
|
||||
### 8.2 SFX(12 个;SA3=Stable Audio 3,SYN=程序合成;双格式、真峰值 ≤-3dBFS)
|
||||
|
||||
| 类别 | id | 方式 | 说明 |
|
||||
|---|---|---|---|
|
||||
| 卷宗 | `paper_turn` | SA3 | 翻卷宗页(条目切换内容反馈,"single paper page turn on desk, dry, close") |
|
||||
| 证人签名 | `sig_ge` | SA3 | 搪瓷缸顿桌一下(老葛) |
|
||||
| | `sig_shun` | SYN | 布掸子拍柜台两下(小顺) |
|
||||
| | `sig_zhou` | SA3 | 算盘珠拨两响(周先生) |
|
||||
| | `sig_wen` | SYN | 门环叩两下(温太太) |
|
||||
| | `sig_fore` | SYN | 纸页翻动+钢笔划线(法医报告) |
|
||||
| | `sig_scene` | SA3 | 雨中皮鞋踩水两步+伞抖水(现场,0.5s) |
|
||||
| 指证 | `contra_hit` | SYN | 不和谐金属二度相击+盖章尾(矛盾证实音,全游戏最强确认音) |
|
||||
| | `contra_miss` | SYN | 钝木鱼一下(对不上——非 error 音色,不威吓) |
|
||||
| 裁决 | `gavel` | SA3 | 法槌一击("wooden gavel single strike, small room reverb") |
|
||||
| | `rain_swell` | SA3/SYN | 雨声渐强再落(认罪链垫,6s one-shot) |
|
||||
| 看门狗 | `turn_tick` | SYN | 880Hz 木鱼族轻响(无进展轨 L1 复用 dungeon 配方,自存一份) |
|
||||
|
||||
并发纪律:签名 earcon(≤0.5s)→ 条目名语音,串行不叠;contra_hit 先响、cf_hit 语音后接。声流恒 ≤2(BGM + voice)。
|
||||
|
||||
### 8.3 voice(158 句:旁白 106 ×2 性别 + NPC 平铺 52)
|
||||
|
||||
- **旁白**(系统/指引/教学/状态/现场自勘/裁决宣读):female=Serena / male=Uncle_Fu,instruct 常量全表统一(见台词表头);数字全分档整句(st_asked 7 档/st_doubt 5 档/st_miss 3 档),**句序号一律不播**(指证选句用重播该句代替报数——数字拼接禁令的交互侧落实)。
|
||||
- **NPC 平铺单音色**(证词=证人 NPC;不做双性别;落 `voice/` 根目录,引擎平铺回退命中):
|
||||
|
||||
| 角色 | speaker | instruct 常量 | 区分度风险 |
|
||||
|---|---|---|---|
|
||||
| 老葛(门卫 62) | Eric | 苍老沙哑的男声,守夜人,慢悠悠,带倦意 | 低:与两侧旁白均不同 speaker |
|
||||
| 小顺(学徒 19) | Dylan | 年轻清亮的男声,学徒,怯生生的,慌张诚实 | 低:同上 |
|
||||
| 周先生(账房,真凶 A) | Uncle_Fu | 平板谨慎的男声,账房,字斟句酌,被追问时渐慌 | **中**:与 male 旁白同 speaker——靠 sig_zhou 前缀+账房腔 instruct+证词链内零旁白插话;试听确认 |
|
||||
| 温太太(房东 50) | Vivian | 快嘴泼辣的中年女声,房东太太,嗓门亮,热心肠 | 低:与两侧旁白均不同 speaker |
|
||||
| 秦法医 | Serena | 冷静干练的女声,验尸官,临床腔,短句平直 | **中**:与 female 旁白同 speaker——靠 sig_fore 前缀+报告语境固定(只在整个案卷固定条目出现);试听确认 |
|
||||
|
||||
- **声音演技核心**:周先生 z_react_1..4(记岔了→支吾→拒答→想走)与 z_break_1..3(认罪三段)是本作表演高潮;instruct 恒定、情绪写在句内文本(标点与省略号引导),不靠句级 instruct 变体(音色漂移禁令)。pack B 对应 b_shun_react/b_shun_break。
|
||||
- 全表、触发时机、键盘变体(V 组)与多音字自查见 [18-detective-voice-ids.md](18-detective-voice-ids.md)。
|
||||
|
||||
## 9. 向导层接入(engine/guide.js,不新增 earcon 词表)
|
||||
|
||||
### 9.1 状态签名(进入状态时播)
|
||||
|
||||
| 状态 | guide.state() | 时点 |
|
||||
|---|---|---|
|
||||
| TITLE / MENU / SETTLE | `menu` | 进标题、开菜单、进结算 |
|
||||
| DRILL / BOARD | `explore` | 进教学、进证词板(含指证判定后返回) |
|
||||
| CONFRONT / FINISH | `choice` | 进指证、进结案三问 |
|
||||
| VERDICT | `settle` | 认罪链起 |
|
||||
| danger | 不使用 | 本作无生命/倒计时压力(登记) |
|
||||
|
||||
### 9.2 turn cue 与看门狗(双轨——批次 1 教训固化)
|
||||
|
||||
| 等待点 | 机制 | at | say(full 档;第 2 级一律纯 nudge) |
|
||||
|---|---|---|---|
|
||||
| CONFROST/FINISH 各选择相位入口 | guide.turn | — | turn earcon + confirm 震 |
|
||||
| BOARD 空闲 | watchdog 无操作轨 | [10s, 25s] | gd_board / null |
|
||||
| CONFROST 选句/确认 | watchdog 无操作轨 | [15s, 30s] | gd_cf / null |
|
||||
| FINISH 三问 | watchdog 无操作轨 | [20s, 40s] | gd_fin / null |
|
||||
| DRILL 每步 | watchdog 无进展轨(共享) | [30s/60s/90s] | earcon → 短句 → tut_skip 自动滚出(E0-2 先例) |
|
||||
|
||||
### 9.3 无进展轨(调查态卡关三级,**证词板乱按也要被抢救**)
|
||||
|
||||
- 进展事件=听完新条目 / 指证判定 / 结案相位推进(`_progressTrack()` 挂点:条目链 onComplete、judge()、FINISH 相位切换)。**只由进展重置**——乱按(疯狂换条目不听完、播放中狂点停起)永远攒不满进展,三级必达:
|
||||
- [45s] L1:turn_tick earcon(不占台词通道)→ [90s] L2:`hint_a`(还有未听条目时)/ `hint_b`(都听过但疑点未齐)→ [150s] L3:**保底给出可指证的矛盾对**(hint_c1/hint_c2 按未发现清单轮询,seed 定序);4 疑点齐 → `hint_fin`(去结案)。L3 后每 150s 重复(每句全局限 2 次,之后静默——不轰炸)。
|
||||
- 无操作轨(手势重置)与无进展轨并行取先到;两轨短句内容同级交叉登记防重复。纠错/提示 idle-only、过期丢弃(引擎已实现)。
|
||||
|
||||
## 10. 存档设计
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": 1, "game": "detective", "updatedAt": 0,
|
||||
"summary": "雨夜·聚宝斋 · 已问3/6 · 疑点2/4",
|
||||
"meta": { "drillDone": true, "casesClosed": 1, "bestGrade": "A", "lastPack": 0 },
|
||||
"run": {
|
||||
"seed": 42, "pack": 0, "layout": { "w1": [...], "wpOpts": [...], "hintOrder": [...] },
|
||||
"heard": ["w1", "scene", "fore"],
|
||||
"doubts": [false, true, false, false],
|
||||
"cfMiss": 1, "wrongWho": 0, "zHits": 1,
|
||||
"hintsGiven": ["hint_c1"], "weaponAns": null, "motiveAns": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- summary 生成规则:`{案名} · 已问{heard.length}/6 · 疑点{doubts 中 true 数}/4`(结案后 run=null,summary=`已结案{casesClosed}起 · 最佳{bestGrade}`)。
|
||||
- 保存时机:条目链播完 / 指证判定后 / 菜单「保存并回大厅」/ 结案入 meta。localStorage + PUT 服务器。
|
||||
- 读取:有 run → TITLE 播 sys_continue_ask,单击继续 → 恢复(layout 复用存档值,不重洗)→ **局面复述链**:`st_asked_n →(未听条目 ≤3 个时逐个播 item_* 名;>3 个只报计数)→ st_doubt_m → recap_go`,≤10s 回到可操作;右滑=换新案(seed/pack 重掷,meta 保留)。
|
||||
- 归因真话依据:评级各变量全部持久化(doubts/cfMiss/wrongWho/hintsGiven/answers),结算播报从同源读取。
|
||||
|
||||
## 11. playtest 断言清单(登记 13 号文档新增 §detective;mock 对齐 web/rpg/test/playtest.mjs;一律 guide:'off')
|
||||
|
||||
**关键路径**(seed=42, pack=0,≤200 手势):
|
||||
1. 启动→TITLE(无档)→单击→DRILL:D0..D6 逐步断言 voice 序与 drillStep 递进;D5 任意选择组合都播 tut_cf_ok 且 doubts/cfMiss 零变化(教学承诺兑现);下滑跳过路径 + meta.drillDone 二周目不再进。
|
||||
2. **seed 复现**:(42,0) 两次构造 → `_case` 深相等(条目/句序/filler 洗牌/选项序/提示序);(42,1) 与 (43,1) 布局不同。
|
||||
3. **矩阵不变式**:对 `_case` 全句对枚举轴交集——同轴异值对集合 ≡ contradictions(pack A 与 pack B 各验一遍)。
|
||||
4. 听取:6 条目链完整(句序=layout)、双击=当前句重播零消耗、单击停止、播完 st_asked 升档;播放中左右滑静默忽略(无 error 震)。
|
||||
5. **指证 4 矛盾逐条走通**:M1-M4 各自导航→两段确认→contra_hit+cf_hit+react_n(zHits 递进取句)→doubts 置位;M3/M4 共用 z_4(选 z_4 两次分别对 fo_3/sc_4);指证对无序(反向选也 hit)。
|
||||
6. **指证 miss 三档真话**:同轴同值对(w1_2 vs wen_1)→cf_same;无共享轴对(z_5 vs sc_1)→cf_diff;同句→cf_self;cfMiss 计数、无任何资源/次数惩罚。
|
||||
7. **指认全路径**:错指小顺/老葛/温 → acc_NPC+fin_clear+回 BOARD(无限次);对指周先生 → 凶器问(选项序=layout)→ 动机问 → VERDICT。
|
||||
8. **裁决死窗**:指认对瞬间同拍与 +300ms 各补一记在途 tap → 均被吞(仍 VERDICT);认罪链 4 句+gavel+settle 链完整有序;链后单击=接新案(pack 翻转、seed 重掷、meta 入账)。
|
||||
9. **评级账目**:注入 outcomes 矩阵(doubts∈{0..4}×weapon×motive×wrongWho×cfMiss×hints 代表组合 ≥10 组)→ 断言 grade 判定与 §7.2 表一致 + 结算链播报句(rate_X/st_doubt_n/st_miss_档)与运行值一致(假话即失败)。
|
||||
10. **看门狗双轨**:BOARD 10s/25s(gd_board→nudge);无操作 96s 乱按零进展 → 无进展轨 45s earcon/90s 短句/150s hint_c(给出未发现矛盾对且该对可走通指证);有真实进展零提示;4 疑点齐后 150s → hint_fin。
|
||||
11. **中断复述**:存档重进→sys_continue_ask→单击→复述链序(st_asked→未听 ≤3 时 item 名链→st_doubt→recap_go);上滑同链可重放。
|
||||
12. **二周目变体**:seed'/pack=1 → 矛盾表指向 b_shun_*/b_zhou_2、culprit=w2、错指周先生走 acc_zhou、认罪链 b_shun_break、hint 为 b_hint_c1/2;矩阵不变式复验。
|
||||
13. **键盘变体**:`settings.input='key'` 后播 sys_help_k/tut_d0_k 等 V 组(inputMode 联动);'touch' 回基础句。
|
||||
14. 菜单/帮助/存档:模态菜单循环-确认-关闭;「保存并回大厅」→PUT data.summary 命中生成规则;「去指证」= 长按同效。
|
||||
|
||||
**边界断言**:
|
||||
- 教学期零惩罚:D5 任意乱选不产生 cfMiss/doubts 变化;全程无倒计时(全 run 无 timeout 惩罚断言)。
|
||||
- 长按豁免:BOARD 播放中长按=引擎快进(不进指证);空闲长按→CONFRONT;CONFRONT/FINISH 长按=error 震。
|
||||
- 状态链随时可用(含播放中,打断后双击可重听当前句);播报值=heard/doubts 实值。
|
||||
- 指证第二份选择时自动重播第一份选定句(P1 记忆契约)。
|
||||
- 屏蔽任意 voice 资产:链 8s 兜底推进,流程到结案不死锁。
|
||||
- guide=off:全静、零定时器;资产齐套:voice 双性别 id 集合一致 + NPC 平铺存在 + sfx/bgm 双格式。
|
||||
- VERDICT/SETTLE 链内上滑/下滑/双击全部吞掉(链锁),链完成前无任何状态逃逸。
|
||||
|
||||
**失败即不通过**:归因假话(cf_same/cf_diff/评级句与数据实值不符);矩阵外冲突对存在;同 (seed,pack) 两次构造不一致;教学练习计入账目;裁决链被手势截杀或链句缺失;保底 hint 给出的矛盾对走不通指证。
|
||||
|
||||
## 12. 节奏曲线与单局核算
|
||||
|
||||
| 阶段 | 时长 | 内容 | 奖励点(≤60s 一个) |
|
||||
|---|---|---|---|
|
||||
| 开场链 | 0:20 | intro 三句 | — |
|
||||
| DRILL | 0:20-2:40 | D0-D6(老葛证词首听在 D1) | D5 完成 success 震(t+2.5min) |
|
||||
| 自由调查 | 2:40-9:00 | 5 条目听取+句级重听+状态核对 | 每条目听毕 st_asked 升档 |
|
||||
| 指证穿插 | 5:00-12:00 | 4 矛盾(两证词齐后即可开始,玩家自定节奏) | 每个 hit:contra_hit+真凶 react 递进 |
|
||||
| 结案三问 | 12:00-14:00 | 真凶/凶器/动机 | 指认对=fin_right(高潮起) |
|
||||
| 裁决+结算 | 14:00-15:30 | 认罪链+法槌+评级 | 评级播报+开环钩子 |
|
||||
|
||||
- 一周目 15-20 分钟(重听多的松玩家 ≤25);二周目(跳教学+已熟导航)13-15 分钟。15-30 分钟章程达标。
|
||||
- 悬念曲线:听证词(信息积累)→ 首个 hit(「证词会撒谎」兑现)→ react 递进(真凶渐慌)→ 三问(主动权全在玩家)→ 认罪(演技高潮)——每 30-60s 必有可感知进展(条目数/疑点数两个可见账本)。
|
||||
- 开环钩子:settle_hook「同一场雨里,还有别的案子」+ pack 强制翻转(二周目必遇变体真凶)。
|
||||
|
||||
## 13. 自检清单
|
||||
|
||||
| # | 硬约束/教训 | 自检 |
|
||||
|---|---|---|
|
||||
| 1 | FTUE 首输入 <60s | D0 完成约 t+20s ✓(§5) |
|
||||
| 2 | 首个正反馈 ≤3min | D5 教学完成 success 震 ≈t+2.5min;首个真 hit 预期 t+5-8min(本作类型内最近可达)✓ |
|
||||
| 3 | 学习期零惩罚 | 无资源无倒计时全域;D5 练习不算账 ✓ |
|
||||
| 4 | 失败三问归因+真话律 | 指证 miss 三档从轴数据推导;指认错=不在场证明复述;凶器/动机错=物证归因;评级句与账目一一对应 ✓(§3.6/§7,playtest 失败项锁定) |
|
||||
| 5 | 手势宪法三件套不豁免 | 上滑状态/下滑菜单/双击重听全状态保留;仅 E1/E2 登记且教学讲清 ✓(§2.6) |
|
||||
| 6 | 向导层 | 签名 4 态(choice 用、danger 登记不用);双轨看门狗(无操作+无进展,乱按必被抢救);保底 hint 给可走通矛盾对;idle-only/queue/onceKey ✓(§9) |
|
||||
| 7 | 教学承诺兑现 | tut 承诺的每个手势在正片同语义可用;settle_hook「单击接新案」言行一致(死窗+链锁后)✓ |
|
||||
| 8 | 结算链完整 | VERDICT 手势死窗 800ms+链锁,认罪→法槌→评级全链不可截(H1 教训)✓(§7.4) |
|
||||
| 9 | 档位措辞真实 | S-C 条件与文案一一映射;st_asked/st_doubt/st_miss 播报值=运行值 ✓(§7.2/7.3) |
|
||||
| 10 | 信息密度 | 所有列表逐项预览(≤3 选项/次);证词句粒度 ≤8s、链播 ≤35s 可停可重听(≤40s 听觉负荷)✓(§3.7) |
|
||||
| 11 | P4 容错 | 无惩罚性失败;错指无限再机会;保底必破案(hint_c 直接给对子);零新手势 ✓ |
|
||||
| 12 | P1 视障 | 句级免费重听;导航规整(条目→句→指证同一范式);状态链随时可查;账目真实;5 speaker+签名 earcon 防混淆(2 处重叠风险已登记缓解);2.2× 语速引擎既有 ✓ |
|
||||
| 13 | P3 通勤 | 中断复述(已问谁/待问谁 ≤3 名直报/疑点几处);单手免视;任意时刻菜单退出存档;3 分钟颗粒=一份证词完整听取+比对 ✓ |
|
||||
| 14 | P2 大学生 | react 递进/认罪演出钩子;pack 翻转二周目变体;评级 S 追求(全疑点+零冗余)✓ |
|
||||
| 15 | seed 可复现 | (seed,pack) 注入+布局洗牌全 PRNG+深相等断言;变体包数据驱动 ✓(§6.2) |
|
||||
| 16 | 矛盾矩阵验算 | 全轴枚举表:同轴异值对 ≡ M1-M4;一致对 6/单断言/无轴句清单 ✓(§3.4/§6.4 不变式) |
|
||||
| 17 | 指证全路径可走通 | hit(4 矛盾×双向)/miss 三档/同句/错指三人(A)/错指含周(B)/三问对错/保底 hint 对——全部有专属资产与断言 ✓(§11 路径 5-10、12) |
|
||||
| 18 | 台词规范 | 全句 ≤40 字(实测 ≤31);数字全分档整句;句序号不播(重播代替报数);「重新听」写法;同角色 speaker+instruct 恒定;总句数 158 ≤160 ✓(台词表) |
|
||||
| 19 | 键盘通道 | 操作提示句 V 组 10 变体集中登记,inputMode() 选播;空格/回车/方向键全手势可达(§5.5 平行通道,游戏层零改动)✓ |
|
||||
| 20 | 引擎能力边界 | 无实时节点(链播 onComplete+setTimeout);长按快进与长按指证时间互斥;资产缺失静默跳过+8s 兜底;30MB 预算内 ✓ |
|
||||
@@ -0,0 +1,258 @@
|
||||
# 19b · 《暗码》(cipher) 语音台词表(voice ids,录音依据)
|
||||
|
||||
> **给音频总监的录制说明**
|
||||
> - **双性别组**(报务员 OP + 北风电台 WIND,134 句):落 `web/cipher/audio/voice/female|male/<id>.{ogg,m4a}`,两性别 id 集合完全一致(含全部 `_k` 变体)。
|
||||
> - **OP(报务员=玩家)**:female = `Serena`,instruct 常量 `压低而冷静的年轻女声,报务员,值夜班,克制的紧张`;male = `Uncle_Fu`,instruct 常量 `压低而冷静的男声,报务员,值夜班,克制的紧张`。
|
||||
> - **WIND(北风电台=上线联络+系统音/向导 gd_*)**:female = `Vivian`,instruct 常量 `清晰平稳的女声,电台联络声,隔着电流,字字清楚`;male = `Dylan`,instruct 常量 `清晰平稳的男声,电台联络声,隔着电流,字字清楚`。
|
||||
> - 区分度设计:你(耳机旁的自语)与北风(电波里传来的控制声)共用 speaker 池但**不同 speaker 配对**——female 侧你 Serena / 北风 Vivian、male 侧你 Uncle_Fu / 北风 Dylan,同场景高频对话的两者永不同 speaker(radio 主播/导播先例)。
|
||||
> - **NPC 平铺组**(5 句):单音色、**不做双性别**,平铺落 `web/cipher/audio/voice/<id>.{ogg,m4a}`(引擎平铺回退命中)。有台词人物仅 2 人:北风(已并入 WIND 双性别组,向导人设 L4,radio 导播同型)+ 处长(明线上级,本表 NPC)。另有**无台词角色**「快手七」(对家报务手,存在=电波里的手法音,零台词零资产,abyss 小郑先例)。**不加滤波**(detective 先例:处长是当面训话,区分度靠 speaker+instruct+语境;「电波里的人」与「身边的人」靠 WIND/OP 音色对立立住):
|
||||
> | 角色 | speaker | instruct 常量 | 区分度风险 |
|
||||
> |---|---|---|---|
|
||||
> | 处长(明线上级,50 余岁) | `Eric` | `威严冷淡的中年男声,电讯处长,官腔,话里有话` | **低**:male 侧唯一 Eric,与两侧双性别组均不同 speaker |
|
||||
> - **称呼规则**:处长对玩家一律称「值机员/你」,北风称「你」,**全表禁用**「姑娘/小哥/先生/同志」类性别化称呼(玩家性别随设置变化)。
|
||||
> - **键盘变体(PC 键盘通道,P1 机制)**:带 `_k` 后缀的 id 为键盘措辞版(「按空格」「回车」「左箭头/右箭头/上箭头/下箭头」),与触屏版**成对生成、成对落盘**(`check_all_assets` 校验 `_k` 配对);运行时由 `gesture.inputMode()` 选择:`key` → `<id>_k`,`touch` → `<id>`,`auto` 跟随最近使用通道,`_k` 缺失回退无后缀版。变体共 **11 对**,全部集中在 §G0.a 操作提示句区与 §M1 首个候选导航/首个叙事抉择(dv1_teach2 / p2),以粗体标注。
|
||||
> - **数字规避**:电文序号/密码本页数/暴露档位全部分档整句(prog_t1..t4、book_3..0、exp_calm/low/mid/hot);句内数字(民国二十九年/零点/丑时/三时/两点五十九分/一刻钟)写死在整句里,禁止运行时拼接。
|
||||
> - **禁语速词**:instruct 不含任何语速描述(引擎 SoundTouch 变速);NPC 句靠短句+逗号停顿实现「官腔慢半拍」听感。
|
||||
> - **摩尔斯不在本表**:电文/候选/重听/慢放的滴答声为运行时程序合成(`web/cipher/morse.js`,19-cipher.md §2.1),零文件资产,勿排产。
|
||||
> - 管线:`tools/gen_voice_dual.py` 照录(SPEAKERS 表需新增 cipher 行 + CIPHER_NPC 平铺表);多音字自查见文末注记。
|
||||
|
||||
## G0 组 · 系统/向导/状态(WIND 双性别 ×39)
|
||||
|
||||
### G0.a 操作提示句(含键盘变体,9 对 18 句——键盘玩家听这一区就够)
|
||||
|
||||
| id | 台词全文 | 触发 |
|
||||
|---|---|---|
|
||||
| `gd_brief` | 单击继续值机。要选词时,向左或向右滑。 | 开场链最前(full 档,F8 先例) |
|
||||
| **`gd_brief_k`** | 按空格继续值机。要选词时,用左右方向键。 | 同上(inputMode=key) |
|
||||
| `gd_continue` | 单击屏幕,继续。 | 叙事链末看门狗第 1 级(full 25s / standard 13s) |
|
||||
| **`gd_continue_k`** | 按空格,继续。 | 同上(key) |
|
||||
| `gd_again` | 还在吗?单击继续。 | 看门狗第 2 级(50s / 30s),新句不逐字重播 |
|
||||
| **`gd_again_k`** | 还在吗?按空格继续。 | 同上(key) |
|
||||
| `gd_tune1` | 电文在等。左右滑,听候选词。 | DECODE 浏览静默 10s(第 1 级) |
|
||||
| **`gd_tune1_k`** | 电文在等。左右方向键,听候选词。 | 同上(key) |
|
||||
| `gd_tune2` | 双击,把电文再听一遍。再双击,会放慢。 | DECODE 浏览静默 25s(第 2 级,**必须与第 1 级不同句**;同时是慢放机制的常驻教学) |
|
||||
| **`gd_tune2_k`** | 按回车,把电文再听一遍。再按一次,会放慢。 | 同上(key) |
|
||||
| `sys_help` | 单击选定,双击重新听电文。上滑听状态,下滑开菜单。选择时,左右滑。 | 上滑帮助链尾 / 菜单内上滑 |
|
||||
| **`sys_help_k`** | 空格选定,回车重新听电文。上箭头听状态,下箭头开菜单。选择时,左右箭头。 | 同上(key) |
|
||||
| `sys_menu` | 菜单。单击切换选项,双击确认。 | 下滑呼出模态菜单 |
|
||||
| **`sys_menu_k`** | 菜单。空格切换选项,回车确认。 | 同上(key) |
|
||||
| `sys_skip` | 想跳过这句,单击就行。 | 开场每局一次(skip_hint 先例) |
|
||||
| **`sys_skip_k`** | 想跳过这句,按空格。 | 同上(key) |
|
||||
| `sys_continue_ask` | 检测到你的值机记录。单击继续上次,右滑开始新的夜班。 | 有存档进游戏 |
|
||||
| **`sys_continue_ask_k`** | 检测到你的值机记录。空格继续上次,右箭头开始新的夜班。 | 同上(key) |
|
||||
|
||||
### G0.b 状态/系统句(无手势词,无变体)
|
||||
|
||||
| id | 台词全文 | 触发 |
|
||||
|---|---|---|
|
||||
| `gd_invalid` | 这个手势,值机室里用不上。 | 无效手势(error 震,idle-only,同局限次) |
|
||||
| `gd_prog1` | 光切词没有用。双击,先听电文。 | 无进展轨 60s(第 1 级,乱按抢救) |
|
||||
| `gd_prog2` | 别急。停下来,把电文听完整。 | 无进展轨 120s(第 2 级) |
|
||||
| `sys_menu_resume` | 回到电波里。 | 菜单项 |
|
||||
| `sys_menu_book` | 翻密码本,换一个字母。 | 菜单项(长按捷径的冗余入口,P4) |
|
||||
| `sys_menu_restart` | 重新开始今晚。 | 菜单项 |
|
||||
| `sys_menu_lobby` | 保存并回大厅。 | 菜单项(确认后跳转前播) |
|
||||
| `sys_settings` | 音色、语速、背景音乐,请回大厅修改。 | 菜单内设置提示 |
|
||||
| `sys_new_night` | 新的夜班,开始了。 | 新夜班开始 / 菜单重开 |
|
||||
|
||||
### G0.c 分档状态句(整句账目,逐值为真)
|
||||
|
||||
| id | 台词全文 | 触发 |
|
||||
|---|---|---|
|
||||
| `prog_t1` | 现在是今晚第一封电文。 | 上滑状态链(按当前封选播) |
|
||||
| `prog_t2` | 现在是第二封,内部通报。 | 同上 |
|
||||
| `prog_t3` | 现在是第三封,交接电文。 | 同上 |
|
||||
| `prog_t4` | 第四封,最后一封。 | 同上 |
|
||||
| `book_3` | 密码本,还剩三页。 | 状态链(余页=3;开局) |
|
||||
| `book_2` | 密码本,还剩两页。 | 余页=2 |
|
||||
| `book_1` | 密码本,只剩最后一页。 | 余页=1 |
|
||||
| `book_0` | 密码本,一页都不剩了。 | 余页=0 |
|
||||
| `exp_calm` | 这一晚,还没人看你第二眼。 | 暴露度 0(跨档时挂夜结算链尾;状态链) |
|
||||
| `exp_low` | 处长端着茶,多看了你一眼。 | 暴露度 1-2 |
|
||||
| `exp_mid` | 走廊里的脚步声,慢了下来。 | 暴露度 3-4 |
|
||||
| `exp_hot` | 门外,好像有人换了岗。 | 暴露度 ≥5;各播报位附 warning 震 |
|
||||
|
||||
## G1 组 · 开场(双性别 3 + 处长平铺 1)
|
||||
|
||||
| id | 声部 | 台词全文 | 触发 |
|
||||
|---|---|---|---|
|
||||
| `b_open` | 你 | 民国二十九年,江城。电讯处的夜班,从零点的电波声开始。 | INTRO(配 sfx/amb_room) |
|
||||
| `b_rule` | 北风 | 北风问候你。规矩就一条:听不清,就再听一遍。 | INTRO(一句规矩,P4;重听/慢放承诺) |
|
||||
| `b_goal` | 北风 | 今晚频道里有四封密电。每一个字,都要过你的耳朵。 | INTRO(四封承诺;随后第一封起播) |
|
||||
| `b_chief` | 处长 | 四封电文,一封都不许漏。你的耳朵,是处里最值钱的东西。 | INTRO 收尾(处长夜令,明线口径;配 sfx/tea_cup) |
|
||||
|
||||
## M1 组 · 第一封·船期·教学(双性别 19,含 _k 2)
|
||||
|
||||
> 真词 **BAY=货船**;候选 DAY=客车 / RAY=巡逻艇;辨析位=第 1 字母。T1 译错**无限重选零惩罚**,恒收敛真词。
|
||||
|
||||
| id | 声部 | 台词全文 | 触发 |
|
||||
|---|---|---|---|
|
||||
| `rx1_in` | 北风 | 第一封。江上船队的调度,拍得很稳。 | RX 引入(配 sfx/tune_seek,随后摩尔斯首播 BAY) |
|
||||
| `dv1_teach` | 北风 | 电文进来了。先双击,把它重新听一遍。 | 首播后 DECODE 教学 ①(教「双击=重听」) |
|
||||
| `dv1_teach2` | 北风 | 左右滑,听密码本上的候选词。单击,选定一个。 | DECODE 教学 ②(教候选导航+两段确认首击) |
|
||||
| **`dv1_teach2_k`** | 北风 | 左右方向键,听密码本上的候选词。空格,选定一个。 | 同上(key;候选导航键位绑定) |
|
||||
| `dv1_ask` | 北风 | 密码本第一页。三个词,哪个是它? | 抉择提示(choiceArmed;进浏览自动预听候选 1) |
|
||||
| `cand_bay` | 北风 | 货船。 | 候选预听报名(候选摩尔斯 620Hz 对照档之后接播;全 DECODE 复用本封) |
|
||||
| `cand_day` | 北风 | 客车。 | 同上 |
|
||||
| `cand_ray` | 北风 | 巡逻艇。 | 同上 |
|
||||
| `dv1_right` | 北风 | 货船,译对了。药品,装在货船的底舱。 | T1 译对判定(success 震;首个正反馈 ≈t+2min) |
|
||||
| `dv1_wrong` | 北风 | 不对。听清楚:B,一长,三短。 | T1 译错即时归因(真话:真词首字母的摩尔斯写死在句里;无限重选) |
|
||||
| `hint_t1` | 北风 | 我教你一页:第一个字母,是 B。一长,三短。 | T1 翻密码本(**首次免费不扣页**,Prime→Teach;配 sfx/paper_page) |
|
||||
| `cf_confirm` | 北风 | 就这个词?再单击,定下来。 | 两段确认第 1 击(全 DECODE 共用;随后电文重播原速一遍) |
|
||||
| `p2` | 北风 | 药品船,丑时过江。向左滑,把船期报给北风。向右滑,压进值机日志。 | 叙事抉择提示(播完即可选) |
|
||||
| **`p2_k`** | 北风 | 药品船,丑时过江。左箭头,把船期报给北风。右箭头,压进值机日志。 | 同上(key;首个叙事抉择键位绑定) |
|
||||
| `r1a_send` | 你 | 北风,北风。货船,丑时过江,接住。 | p2 左(报船期;配 sfx/key_send) |
|
||||
| `r1a_e` | 北风 | 收到。江上有人等你。第一封,做得稳。 | p2 左·收(med_ok 预埋;expose+0) |
|
||||
| `r1b_hold` | 你 | 先压一压。这一船的事,得看清楚再说。 | p2 右(压日志;expose+1) |
|
||||
| `r1b_e` | 北风 | 日志下面压着的,是三箱药。船,不会等你。 | p2 右·收(诚实复盘,非惩罚) |
|
||||
| `n1` | 北风 | 零点半。挂钟走了半圈,雨没有停。 | NIGHT(1) 过渡句(随后跨档 exp_* 如有) |
|
||||
|
||||
## M2 组 · 第二封·名单(双性别 16 + 处长平铺 1)
|
||||
|
||||
> 真词 **TOP=黑名单**;候选 TAP=窃听令 / TIP=线人费;辨析位=第 2 字母。正片起**译错不揭穿**:rev_* 按玩家所选播「你的认知」;expose +1。
|
||||
|
||||
| id | 声部 | 台词全文 | 触发 |
|
||||
|---|---|---|---|
|
||||
| `rx2_in` | 北风 | 第二封。手法很急,是内部通报。 | RX 引入(摩尔斯首播 TOP) |
|
||||
| `dv2_ask` | 北风 | 密码本第二页。三个词,选一个。 | 抉择提示 |
|
||||
| `cand_top` | 北风 | 黑名单。 | 候选预听报名 |
|
||||
| `cand_tap` | 北风 | 窃听令。 | 同上 |
|
||||
| `cand_tip` | 北风 | 线人费。 | 同上 |
|
||||
| `rev_top` | 北风 | 黑名单。头一个代号,叫码头。 | 译 TOP·揭晓(你的认知:码头=你的交通员;驱动 p4_known) |
|
||||
| `rev_tap` | 北风 | 窃听令。今夜,有人要装耳朵。 | 译 TAP·揭晓(误读分支;expose+1) |
|
||||
| `rev_tip` | 北风 | 线人费。月底之前,有人要收钱。 | 译 TIP·揭晓(误读分支;expose+1) |
|
||||
| `p4_known` | 北风 | 名单上有码头。向左滑,暗号示警,让他连夜搬家。向右滑,按流程呈报处长。 | 抉择提示(**条件 t2=top**) |
|
||||
| `p4_blind` | 北风 | 一封通报。向左滑,压进抽屉,谁的耳朵也别装。向右滑,按流程呈报处长。 | 抉择提示(条件 t2≠top) |
|
||||
| `r2a_act` | 你 | 有的字,不能落进卷宗。 | p4 左·暗处理(known=示警码头 / blind=压下通报;expose+1) |
|
||||
| `r2a_e` | 北风 | 这一页,从今天起,不存在了。 | p4 左·收(ep_list_ok 预埋) |
|
||||
| `r2b_act` | 你 | 通报誊清,呈处长。 | p4 右·呈报(mateListed=true;expose+0) |
|
||||
| `r2b_e` | 北风 | 名单进了卷宗。码头,自己多保重。 | p4 右·收(ep_list_no 预埋) |
|
||||
| `m2_chief` | 处长 | 嗯,译得很全。去吧。 | p4 右·处长收报(配 sfx/pen_scratch;通用真话:你誊了什么它就收什么) |
|
||||
| `hint_t2` | 北风 | 翻到这页:第二个字母,是 O。三长。 | 翻密码本(扣 1 页;辨析位=O ⟹ 候选唯一) |
|
||||
| `n2` | 北风 | 一点整。处长的灯,还亮着。 | NIGHT(2) 过渡句 |
|
||||
|
||||
## M3 组 · 第三封·交接(双性别 16)
|
||||
|
||||
> 真词 **HAND=今夜交接**;候选 SAND=明晨交接 / LAND=地点变更;辨析位=第 1 字母(H 四短 vs S 三短,全作最狠听辨——慢放价值兑现段)。
|
||||
|
||||
| id | 声部 | 台词全文 | 触发 |
|
||||
|---|---|---|---|
|
||||
| `rx3_in` | 北风 | 第三封。很短,手法有点抖。 | RX 引入(摩尔斯首播 HAND) |
|
||||
| `dv3_ask` | 北风 | 密码本第三页。三个词,选一个。 | 抉择提示 |
|
||||
| `cand_hand` | 北风 | 今夜交接。 | 候选预听报名 |
|
||||
| `cand_sand` | 北风 | 明晨交接。 | 同上 |
|
||||
| `cand_land` | 北风 | 地点变更。 | 同上 |
|
||||
| `rev_hand` | 北风 | 今夜交接。丑时,老码头。 | 译 HAND·揭晓(你的行动按「今夜」展开) |
|
||||
| `rev_sand` | 北风 | 明晨交接。天亮再说。 | 译 SAND·揭晓(误读:你按「明晨」行动;expose+1) |
|
||||
| `rev_land` | 北风 | 地点变更。等下一封。 | 译 LAND·揭晓(误读:你按「等」行动;expose+1) |
|
||||
| `p6` | 北风 | 交接在你手里。向左滑,亲自去码头跑一趟。向右滑,留书暗号,托给下一班。 | 抉择提示(固定文本,known/blind 共用——行动时机由你的认知决定) |
|
||||
| `r3a_go` | 你 | 口令我背得出。今夜,我亲自去。 | p6 左·亲自去(expose+2;按你的认知时间出门) |
|
||||
| `r3a_e` | 北风 | 值班室空了一刻钟。值机日志,停了一行。 | p6 左·收(expose+2 的纸面依据;成败留尾声) |
|
||||
| `r3b_act` | 你 | 暗号夹进交接簿,让雨替我跑这一趟。 | p6 右·留书(expose+0) |
|
||||
| `r3b_e_ok` | 北风 | 交接赶上了。袋子,连夜上了船。 | p6 右·收(**条件 t3=hand**;ep_hand_ok 预埋) |
|
||||
| `r3b_e_no` | 北风 | 交接的袋子,在码头放到了天亮。 | p6 右·收(条件 t3≠hand——误译的戏剧性反讽:约定时间本身错了) |
|
||||
| `hint_t3` | 北风 | 翻到这页:第一个字母,是 H。四短。 | 翻密码本(扣 1 页) |
|
||||
| `n3` | 北风 | 一点三刻。雨小了,频道里很静。 | NIGHT(3) 过渡句(随后走氧气门式签收) |
|
||||
|
||||
## M4 组 · 第四封·处置令·高潮(双性别 15 + 处长平铺 3)
|
||||
|
||||
> 真词 **SPY=内线确认**;候选 SAY=约谈 / SLY=内部排查;辨析位=第 2 字母。rev_spy 挂全作唯一 danger 拍(step_halt 前置 2s);RELAY(4) 起 BGM 切 `bgm/closing`。
|
||||
|
||||
| id | 声部 | 台词全文 | 触发 |
|
||||
|---|---|---|---|
|
||||
| `rx4_in` | 北风 | 第四封。最后一个手法,你听过——快手七。 | RX 引入(无台词角色登场=手法音;摩尔斯首播 SPY) |
|
||||
| `dv4_ask` | 北风 | 密码本第四页。三个词,选一个。 | 抉择提示 |
|
||||
| `cand_spy` | 北风 | 内线确认。 | 候选预听报名 |
|
||||
| `cand_say` | 北风 | 约谈。 | 同上 |
|
||||
| `cand_sly` | 北风 | 内部排查。 | 同上 |
|
||||
| `rev_spy` | 北风 | 内线确认,今夜三时收网。代号,耳机。 | 译 SPY·揭晓(**danger 拍**:「耳机」=你自己的代号;expose+0 但驱动 p9_known) |
|
||||
| `rev_say` | 北风 | 明早九时,约谈相关人员。 | 译 SAY·揭晓(误读:危机被读成了例行公事;expose+1) |
|
||||
| `rev_sly` | 北风 | 即日起,排查站内三人。 | 译 SLY·揭晓(误读;expose+1) |
|
||||
| `m4_chief_a` | 处长 | 值机员,今晚的频道,很热闹啊。 | 处长试探 ①(p8 前铺垫;配 sfx/tea_cup) |
|
||||
| `m4_chief_b` | 处长 | 你的耳朵最好。听见什么,记得……要报。 | 处长试探 ②(施压;配 sfx/pen_scratch) |
|
||||
| `p8_known` | 北风 | 三点之前。向左滑,拍一封假电,说北风昨夜已撤。向右滑,手离开放键器,装傻。 | 抉择提示(**条件 t4=spy**;全表最长句 30 字) |
|
||||
| `p8_blind` | 北风 | 收台之前。向左滑,回一封例行电文,搅一搅水。向右滑,什么都不做。 | 抉择提示(条件 t4≠top;p9 将按 blind 版走) |
|
||||
| `r4a_feed` | 你 | 戴上手键。这一封,借快手七的手法。 | p8 左·反喂(**配 sfx/key_send**;expose+2,feed=true) |
|
||||
| `r4a_e` | 北风 | 假电出了门。对家的耳朵,听得出换人了吗。 | p8 左·收(悬而未决——真相在尾声) |
|
||||
| `r4b_act` | 你 | 手,离开放键器。我只是个值机的。 | p8 右·装傻(expose+0) |
|
||||
| `r4b_e` | 北风 | 有时,什么都不做,就是做了。 | p8 右·收 |
|
||||
| `hint_t4` | 北风 | 翻到这页:第二个字母,是 P。短,两长,短。 | 翻密码本(扣 1 页) |
|
||||
| `m4_chief_c` | 处长 | 行。去睡吧,明天还有明天的事。 | p8 右·处长收场(仅右路播出) |
|
||||
|
||||
## F9 组 · 签收抉择·最后一电(双性别 9)
|
||||
|
||||
| id | 声部 | 台词全文 | 触发 |
|
||||
|---|---|---|---|
|
||||
| `g9_in` | 北风 | 频道要收了。最后一件事,是你自己。 | 签收引入(NIGHT(3)/M4 之后) |
|
||||
| `g9_knot` | 你 | 三年前,我译错了一封电。那之后,我再没见过我的上线。 | 心结自述(玩家背景;与「译错也是剧情」主题闭环) |
|
||||
| `p9_known` | 北风 | 收网之前。向左滑,开机,发最后一电。向右滑,关机,装睡到天亮。 | 终局抉择提示(**条件 t4=spy**) |
|
||||
| `p9_blind` | 北风 | 北风在等答复。向左滑,开机,把今晚发出去。向右滑,例行收台。 | 终局抉择提示(条件 t4≠spy——你不知道那是什么电) |
|
||||
| `d9_send` | 你 | 北风,北风。处置令,今夜三时。撤。 | p9 左·发报(**配 sfx/key_send**;expose+2,结局判定用 p9 前值) |
|
||||
| `d9_send_e` | 北风 | 收到了。你的手法,我听了一整晚。快走。 | p9 左·低暴露收(**p9 前 expose≤4**;e1 入口) |
|
||||
| `d9_caught_e` | 北风 | 信号被咬住了。别回头,走水路。快。 | p9 左·高暴露收(**≥5**;danger 签名 + e3 入口) |
|
||||
| `d9_sleep` | 你 | 机关上了。雨声里,只剩挂钟。 | p9 右·关机 |
|
||||
| `d9_sleepe` | 北风 | 天亮之前,还有两个钟头。守住。 | p9 右·收 |
|
||||
|
||||
## EP 组 · 结局与尾声回响(双性别 17;结局 8 + 回响 9)
|
||||
|
||||
| id | 声部 | 台词全文 | 触发 |
|
||||
|---|---|---|---|
|
||||
| `e1_a` | 你 | 电波追不上我了。码头的灯,一盏一盏地灭。 | 结局 e1(≤4 且发报)主句(配 sfx/dawn_bell) |
|
||||
| `e1_b` | 你 | 新的密码本,在城外渡口等我。黎明,比想象中安静。 | e1 收(钩子) |
|
||||
| `e2_a` | 你 | 交了班,一切照旧。没人知道今晚频道里发生过什么。 | 结局 e2(≤4 且关机)主句 |
|
||||
| `e2_b` | 你 | 只是北风的呼叫,到天亮也没人接。下一次,我会接吗。 | e2 收(钩子) |
|
||||
| `e3_a` | 你 | 我把警报拍了三遍。门开的时候,挂钟正好敲三点。 | 结局 e3(≥5 且发报)主句(配 sfx/lock_click) |
|
||||
| `e3_b` | 你 | 审讯室的灯,也很亮。可码头那边,一个人都没少。 | e3 收(钩子;你的暴露换同伴撤尽) |
|
||||
| `e4_a` | 你 | 两点五十九分,脚步停在门口。我把最后一页密码,塞进了炉子。 | 结局 e4(≥5 且关机)主句(配 sfx/lock_click) |
|
||||
| `e4_b` | 你 | 耳机还挂着。里面有一封电文,永远译不完了。 | e4 收(钩子) |
|
||||
| `ep_lead` | 北风 | 天亮了。北风给你记了最后一笔账——今晚的事,都有了去向。 | 尾声引入 |
|
||||
| `ep_med_ok` | 北风 | 药品:货船丑时靠岸,三箱药,一箱不少。 | 尾声(t1=bay 且 p2=左) |
|
||||
| `ep_med_no` | 北风 | 药品:没人接住那三箱药。船,白白过了江。 | 尾声(其余——压下或译错,真话) |
|
||||
| `ep_list_ok` | 北风 | 名单:码头连夜搬了家。卷宗里,少了一个人。 | 尾声(p4=左) |
|
||||
| `ep_list_no` | 北风 | 名单:天亮时,多了一个红圈。 | 尾声(p4=右——呈报的真实代价) |
|
||||
| `ep_hand_ok` | 北风 | 交接:胶卷上了船,手很稳。 | 尾声(t3=hand) |
|
||||
| `ep_hand_no` | 北风 | 交接:码头的灯,白亮了一夜。 | 尾声(t3≠hand) |
|
||||
| `ep_spy_ok` | 北风 | 处置令:你译对了它。北风,撤在你前头。 | 尾声(t4=spy) |
|
||||
| `ep_spy_no` | 北风 | 处置令:差的那三个字母,差了一整条命。 | 尾声(t4≠spy——全作归因真话的收束句) |
|
||||
|
||||
---
|
||||
|
||||
**合计:139 句**(预算 ≤150 ✓)
|
||||
- 双性别 134 句(OP 你 20 + WIND 北风/系统 114,含 `_k` 键盘变体 11)×2 性别 = 268 文件;
|
||||
- NPC 平铺 5 句(处长)= 5 文件;
|
||||
- 合计 273 条音频 ×2 格式(ogg+m4a)= **546 文件**。
|
||||
- 键盘变体 11 对:gd_brief / gd_continue / gd_again / gd_tune1 / gd_tune2 / sys_help / sys_menu / sys_skip / sys_continue_ask / dv1_teach2 / p2(成对生成,`check_all_assets` 校验 `_k` 配对与双性别集合一致)。
|
||||
- 每结局专属台词齐备:e1/e2/e3/e4 各 2 句 + 尾声回响 9 句(译文/抉择 flag 选播)。
|
||||
- 每局实走 9 个抉择点(4 译词 p1/p3/p5/p7 恒 3 候选 + p2/p4/p6/p8/p9 恒 2 选项,无跳点路径)。
|
||||
- 摩尔斯滴答声零文件(运行时 `morse.js` 合成,19-cipher.md §2.1),本表不含。
|
||||
|
||||
## 多音字 / 发音风险自查注记
|
||||
|
||||
| 风险词 | 处理 |
|
||||
|---|---|
|
||||
| 重听 | 全表写「重新听」(sys_help / gd_tune2),无「重听」字样 |
|
||||
| 长(cháng/zhǎng) | 「一长三短」「三长」「两长」为 cháng 正确常用读法,保留,试听确认 |
|
||||
| 处(chǔ/chù) | 「处长」「处置令」为固定词,保留,试听确认 |
|
||||
| 卷(juǎn/juàn) | 「胶卷」juǎn 与「卷宗」juàn 两读并存(ep_hand_ok/r2a_act),逐句人工试听项 |
|
||||
| 塞(sāi/sè) | 「塞进了炉子」(e4_a)为 sāi 常用读法,保留,试听确认 |
|
||||
| 行(xíng/háng) | 「行。去睡吧」(m4_chief_c)为 xíng,试听确认 |
|
||||
| 着(zhe/zháo) | 「耳机还挂着」「守住」为常见读法,逐句试听项 |
|
||||
| 得(de/dé) | 「译得很全」「打得稳」等助词 de,逐句试听项 |
|
||||
| 教(jiāo/jiào) | 「我教你一页」为 jiāo 常用读法,保留 |
|
||||
| 答(dá/dā) | 「在等答复」为 dá,保留 |
|
||||
| 差(chà/chā/chāi) | 「差的那三个字母」「差了一整条命」为 chà,试听确认 |
|
||||
| 圈(quān/juàn) | 「多了一个红圈」为 quān,保留 |
|
||||
| 英文字母 | hint_t1..t4 / dv1_wrong 句内单字母(B/O/H/P)朗读为风险项,逐句试听;误读时改写为「字母 B」式带「字母」前缀重生成 |
|
||||
| 挣/血/露/巷/降/率/传(zhuàn 义) | 全表未使用(「暴露」不进台词,仅存档 summary) |
|
||||
| 了 / 还 / 中 | 常规句式常见读法,逐句人工试听项(「收住了吗」「挂钟走了半圈」等) |
|
||||
| 数字 | 句内数字全部汉字写死(民国二十九年/零点/零点半/一点整/一点三刻/丑时/三时/明早九时/三点/两点五十九分/一刻钟/两个钟头/三箱/三年前),序号与档位用分档整句(prog_t*/book_*/exp_*) |
|
||||
|
||||
## 台词侧自查
|
||||
|
||||
- 单句 ≤40 字:全表最长 = p8_known(30 字),次长 p6/p4_known(29 字)、sys_help(27 字)✓;NPC 处长句 ≤23 字(最长 b_chief,P4 语速感靠短句+逗号,不用 instruct 语速词)✓。
|
||||
- 有台词人物 2 人(北风=WIND 双性别组、处长=平铺),无台词角色快手七零资产 ✓;同一角色 speaker+instruct 恒定、区分度风险逐个标注 ✓。
|
||||
- 称呼合规:处长对玩家只用「值机员/你」,北风称「你」,性别化称呼零出现 ✓。
|
||||
- 每个结局(e1..e4)专属主句+钩子各 2 句齐备,尾声救/错过双线齐备(ep_* 由 flags 单源选播)✓。
|
||||
- 归因真话律:dv1_wrong 的字母/码与真词 BAY 一致;rev_* 只播玩家认知不冒认真相;ep_med_no/ep_hand_no/ep_spy_no 与 flags 一致;无「运气不好」类假归因、无惩罚性措辞 ✓。
|
||||
- 分档账目真实:book_3/2/1/0 与余页一一对应;exp_calm/low/mid/hot 与 0/1-2/3-4/≥5 一一对应;prog_t1..t4 分封整句 ✓。
|
||||
- 向导 gd_* 13 句(gd_brief/gd_continue/gd_again/gd_tune1/gd_tune2 各带 _k 变体,gd_invalid/gd_prog1/gd_prog2 无变体)符合 14 号 §6 台词规范(一句一义、无重复句式、单句 ≤18 字)✓。
|
||||
@@ -0,0 +1,552 @@
|
||||
# 19 · 《暗码》(cipher) — 1940 密战叙事 RPG 设计文档
|
||||
|
||||
> **给开发的实现要点摘要**
|
||||
> 1. 叙事模式(声命线范式,radio/abyss 同骨架):节点链 onComplete 串行 + seqToken 打断作废 + pendingChoice 缓存;复制 `web/radio/`(或 `web/abyss/`)结构起步,状态机 6 宏状态(INTRO/RX/DECODE/RELAY/NIGHT/ENDING + MENU 覆盖层)。
|
||||
> 2. 核心增量 = **电文解码小局嵌叙事**(DECODE 态):摩尔斯音由**运行时程序合成模块**(`morse.js`,guide earcon 同先例)直接排程 oscillator——**不走音频文件**(有损编码在 70-160ms 短音上有 ms 级抖动,毁时值精确);事件钩子供 playtest 固定字符断言。
|
||||
> 3. 手势:全局默认映射 + **唯一豁免 E1**(DECODE 空闲长按=翻密码本求提示,与引擎快进时间互斥,detective E2 同款);菜单走 00 号 §5.2 模态约定。摩尔斯三层兜底:正常译(原速+候选对照)/ 慢放(双击第 2 次 0.5×)/ 提示直译(密码本 3 页,逐值账目)。
|
||||
> 4. 暴露度 = 步进隐藏资源(radio 口碑同型):译错 +1/封(教学封 T1 恒 0)、危险抉择 +1/+2;分档整句、跨档才播、上滑可查;**档位措辞逐值为真**(book_1..0 / exp 四档均按实数措辞)。
|
||||
> 5. 译错归因真话:教学封 T1 译错**即时**归因(真词字母的摩尔斯写死在句里)+ 无限重选零惩罚;正片 T2-T4 译错**不揭穿**(进入剧情误读分支),真相在尾声回响兑现(ep_* 与 flags 单源对应,detective 信息单源思想)。
|
||||
> 6. 台词依据 [19-cipher-voice-ids.md](19-cipher-voice-ids.md)(139 句:双性别 134 + 平铺 NPC 5);键盘变体 11 对 `_k` 集中标注;结算链 800ms 手势死窗(abyss/地下城 H1 同款)。
|
||||
> 7. 章程任务 N3(research/new8/00-program.md §2):单局 15-25 分钟、9 抉择点(4 译词 3 选 + 5 叙事 2 选)、4 结局、分支局部化。断言清单住本文档 §9;13 号文档已登记指针(§22)。
|
||||
|
||||
## 1. 概述
|
||||
|
||||
- **一句话**:1940 年江城,你是电讯处的夜班报务员,也是「北风」安在敌台内部的一双耳朵——一晚四封密电过你的手,每个词怎么译、报不报、报给谁,都由你按下。
|
||||
- **题材(声音即世界)**:谍战的本体就是**听**——摩尔斯的滴答是情报本身、报务员的「手法」是 fingerprint、电台静电是世界底色。「用耳朵当武器」与纯音频载体天然同构:视觉谍战需要的文件/密写/表情,在这里全部是声音。
|
||||
- **单局**:一个完整夜班(一条抉择路径)19-22 分钟;四封电文共享主干(截获→解码→处置→夜结算),分歧局部化在每封内部;9 个抉择点(4 译词 + 5 叙事,全部恒在),4 结局。
|
||||
- **核心循环**:截获(调谐+电文摩尔斯首播)→ 解码小局(听音辨词:候选对照/慢放/提示三层兜底)→ 译文揭晓(**你的认知**,对错不揭穿)→ 处置抉择(报北风/压日志/呈处长/亲自交接/反喂假电)→ 夜结算(暴露度跨档播报)→ 下一封。终局 p9 用「发最后一电」把整晚收在你的电键上。
|
||||
- **与 radio/abyss 的差异点**(骨架同、压力源不同):radio 零压力、abyss 资源稀缺(氧气);cipher 的压力 = **隐藏的怀疑账本**(暴露度)+ **认知风险**(你永远不确定自己译对了没有——译错不是失败,是走另一条真实存在的剧情)。全部宽限化:无倒计时、无中途死亡、软失败只损失「情报效果」不损失玩家已有物。
|
||||
|
||||
## 2. 摩尔斯解码小局(信息设计,本作核心差异层)
|
||||
|
||||
### 2.1 电码呈现规格
|
||||
|
||||
**电码表**(标准摩尔斯;本作 4 封电文只用到 13 个字母,模块内置全 A-Z 表,断言只锁用到的):
|
||||
|
||||
| 字母 | 码 | 字母 | 码 | 字母 | 码 | 字母 | 码 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| A | ·− | H | ···· | O | −−− | T | − |
|
||||
| B | −··· | I | ·· | P | ·−−· | Y | −·−− |
|
||||
| D | −·· | L | ·−·· | R | ·−· | S | ··· |
|
||||
| N | −· | | | | | | |
|
||||
|
||||
**时值与音色(运行时合成,`web/cipher/morse.js`)**:
|
||||
|
||||
| 参数 | 值 | 说明 |
|
||||
|---|---|---|
|
||||
| 单位 u | 原速 80ms(dit=1u 音、dah=3u 音、字内隙 1u、字母隙 3u) | ≈15 WPM 等效;**u 是常量写死,不随 settings.speed**(见边界表) |
|
||||
| 慢放档 | u=160ms(0.5×),字母隙加宽至 6u | 双击第 2 次触发;三层兜底第 2 层 |
|
||||
| 候选对照档 | u=110ms,字母隙 5u | 密码本「念给你听」的质感 |
|
||||
| 音色 | 电文 750Hz / 候选 620Hz,正弦 + 5ms cos 边沿,gain 0.5 | **频率差 = 「电波里的」vs「密码本上的」**,防听感混淆 |
|
||||
| 首尾 | lead-in 400ms 静音,结尾 400ms 静音 | 排程器自然包含 |
|
||||
| 时长例 | BAY 原速 ≈2.6s / HAND ≈2.8s / TOP ≈2.8s;慢放 ≈2× | 单次听觉负荷远低于 40s 红线 |
|
||||
| 事件钩子 | `morse.play(word,{hz,unit})` 逐元素回调 `{type:'dit'|'dah',letter}` | playtest **固定字符断言**挂点(§9) |
|
||||
|
||||
- 为什么运行时合成而非 SA3/SYN 文件:①时值精确是玩法命根(H···· vs S··· 只差一个 80ms 的 dit,ogg/m4a 有损编码在短音上有数 ms 抖动与预卷,文件方案直接否决);②三档速度 × 双频率 × 13 字母的组合用文件不可维护;③零资产缺失风险。guide earcon(14 号裁定 3)运行时合成先例。
|
||||
|
||||
### 2.2 电文与候选设计表(信息单源:真词/候选/辨析位写死)
|
||||
|
||||
| 封 | 电文名 | 真词(义) | 候选(义) | 共享 | 辨析位 | 密码本提示揭示 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| T1 | 船期 | **BAY**(货船) | DAY(客车)/ RAY(巡逻艇) | _AY | 1:B(−···)/D(−··)/R(·−·) | 第 1 字母是 B,一长三短 |
|
||||
| T2 | 名单 | **TOP**(黑名单) | TAP(窃听令)/ TIP(线人费) | T_P | 2:O(−−−)/A(·−)/I(··) | 第 2 字母是 O,三长 |
|
||||
| T3 | 交接 | **HAND**(今夜交接) | SAND(明晨交接)/ LAND(地点变更) | _AND | 1:H(····)/S(···)/L(·−··) | 第 1 字母是 H,四短 |
|
||||
| T4 | 处置令 | **SPY**(内线确认) | SAY(约谈)/ SLY(内部排查) | S_Y | 2:P(·−−·)/A(·−)/L(·−··) | 第 2 字母是 P,短两长短 |
|
||||
|
||||
- **候选纪律**:三候选等字母数、共享其余字母、唯一辨析位;**提示揭示辨析位字母 ⟹ 候选唯一**(每行验算成立,playtest 断言「提示流可全对」)。
|
||||
- **难度曲线**:T1(B/D 差一个尾 dit,教学期有慢放+免费提示兜底)→ T2(中间字母 O/A/I 时值差异大,最易)→ T3(H/S/L:H 与 S 只差一个 80ms 尾 dit,全作最狠听辨)→ T4(P/L 需听完整字母,中高)。曲线说明:难度=听辨精度,操作面恒定(左右滑+单击+双击)。
|
||||
- 电文摩尔斯(固定字符断言对象):BAY=`−··· / ·− / −·−−`;TOP=`− / −−− / ·−−·`;HAND=`···· / ·− / −· / −··`;SPY=`··· / ·−−· / −·−−`。
|
||||
|
||||
### 2.3 解码交互流(DECODE 态内)
|
||||
|
||||
```
|
||||
RX 链:rx*_in(引入)→ sfx/tune_seek → 电文摩尔斯首播(原速 750Hz,auto,不可跳)
|
||||
→ guide.turn → dv*_ask(提示句,choiceArmed)→ 浏览等待
|
||||
浏览:左右滑 = 循环切换候选(每切自动预听:候选摩尔斯 620Hz 对照档 → 静默一拍 → cand_* 词义句)
|
||||
双击 = 重听电文(原速);【再双击】= 慢放 0.5×(两级内容不同:速度即差异)
|
||||
长按(语音空闲)= 翻密码本(§2.5)
|
||||
单击 = 进确认:cf_confirm + 电文重播原速一遍 → 再单击 = 定下(判定出口 800ms 死窗)
|
||||
确认态:左右滑 = 取消回浏览;双击 = 照常重听电文
|
||||
```
|
||||
|
||||
- **三层兜底**(成本递增的梯度,全部可独立走通,P3 嘈杂/P4 疲惫的双保险):
|
||||
|
||||
| 层 | 手段 | 成本 | 覆盖 |
|
||||
|---|---|---|---|
|
||||
| 1 正常译 | 原速电文 + 候选对照预听匹配 | 最快 | 安静环境/耳力好 |
|
||||
| 2 慢放 | 双击第 2 次 0.5×,**无限次免费** | 时间 | 通勤嘈杂听不清长短音(P3);无限兜底层 |
|
||||
| 3 提示直译 | 密码本 3 页,每页直译 1 封的辨析位字母 | 资源(页) | P4 保底:提示+候选对照 ⟹ 唯一解(§2.2 验算) |
|
||||
|
||||
- 教学期(T1):dv1_teach/dv1_teach2 先教「双击重听→再双击慢放」「左右滑候选、单击选定」;T1 判定有**即时反馈**(对=success 震+dv1_right;错=dv1_wrong 即时真话归因+无限重选,零惩罚)——正片 T2-T4 判定**无对错反馈**,直入剧情(译错是你不知道的分支)。教学承诺「练习有答案,正片靠耳朵」在 dv1_right 说破。
|
||||
|
||||
### 2.4 密码本机制(提示额度经济)
|
||||
|
||||
| 项 | 规则 |
|
||||
|---|---|
|
||||
| 页数 | 初始 3 页(叙事=北风给你的三页「电码补遗」);**全局不再补页** |
|
||||
| 兑换 | DECODE 空闲长按(E1 豁免)或菜单「翻密码本」项(P4 冗余入口);消耗 1 页 → 播 hint_*(「第 N 个字母,是 ○」+ 摩尔斯口述),留在浏览态 |
|
||||
| 教学免费 | T1 第 1 次翻页**不扣**(hint_t1:「我教你一页」)——Prime→Teach 承诺兑现 |
|
||||
| 账目真实 | 状态链随时可查:book_3「还剩三页」/ book_2「还剩两页」/ book_1「只剩最后一页」/ book_0「一页都不剩了」——**逐值整句,不拼接** |
|
||||
| 页数用尽 | 再翻 = error 震 + sfx/paper_page(翻空本声),无语音(零静默、不轰炸) |
|
||||
| 与结局关系 | 页数不进结局判定(只换准确度);纯提示流(T1 免费+3 页)可全对(§5.5 剧本 E 验算) |
|
||||
|
||||
### 2.5 译错归因真话(detective 信息单源思想)
|
||||
|
||||
| 场景 | 播报 | 真话依据 |
|
||||
|---|---|---|
|
||||
| T1 译错(教学) | dv1_wrong「不对。听清楚:B,一长,三短。」 | 真词字母的摩尔斯**写死在句里**,与 §2.2 电码表一致;无限重选零惩罚 |
|
||||
| 正片译错 | 不揭穿:rev_* 按你选的候选播「你的认知」(rev_top/tap/tip…) | 旁白只说你以为的事;真词唯一、由数据表单源决定 |
|
||||
| 后果分支 | 你的行动按认知展开(报错船期/留错时间/呈报错令);暴露度 +1 | 「译文进了纸面,与对家底档不符」——事件表单源(§5.2) |
|
||||
| 真相揭晓 | 尾声 ep_hand_no「码头的灯,白亮了一夜」/ ep_spy_no「差的那三个字母,差了一整条命」 | ep_* 由 flags(t3==hand 等)选播,**播报=存储**;禁止「运气不好」类假归因 |
|
||||
|
||||
## 3. 状态机
|
||||
|
||||
### 3.1 状态列表
|
||||
|
||||
| 状态 | 内容 | BGM | guide.state |
|
||||
|---|---|---|---|
|
||||
| `INTRO` | 开场链:夜班开场 + 一句规矩 + 今晚目标 + 处长夜令 | `bgm/radio_room` | explore |
|
||||
| `RX` | 截获:引入句 + 调谐 + 电文摩尔斯首播(auto 链) | `bgm/radio_room`(摩尔斯期间 −6dB) | explore;摩尔斯首播后由 turn cue 承担 |
|
||||
| `DECODE` | 解码小局:浏览/确认/重听/慢放/提示(§2.3) | 同上 | choice |
|
||||
| `RELAY` | 处置叙事:rev_* 揭晓链 + 后果 + 叙事抉择 p2/p4/p6/p8 | `bgm/radio_room`;RELAY(4) 起 crossfade 至 `bgm/closing` | explore;抉择点=choice |
|
||||
| `NIGHT` | 夜结算:n* 过渡句 + 暴露档位跨档播报(如有),auto ≤12s | `bgm/night` | settle |
|
||||
| `ENDING` | 结局链 + 尾声回响(epilogue),结算态 | `bgm/ending` | settle |
|
||||
| `MENU` | 模态覆盖层(任意叙事态下滑呼出,暂停一切链) | `bgm/radio_room` 低响度淡入 | menu |
|
||||
|
||||
- 附 `ASK`(存档询问态,radio 同款):单击=继续上次 / 右滑=新夜班;上滑/下滑/左滑/双击=无效(error 震+gd_invalid)。
|
||||
|
||||
### 3.2 转移图(文本)
|
||||
|
||||
```
|
||||
┌──────────── 结算单击=新夜班 ────────────────┐
|
||||
▼ │
|
||||
INTRO ──链完──> RX(1) ──首播完──> DECODE(1) ──两段确认+判定──> RELAY(1) ──链完──> NIGHT ─┤→ RX(2)
|
||||
(调谐+引入+摩尔斯首播 浏览/确认/重听/慢放/提示 揭晓+p*+结果链 │ …(T2/T3 同型)…
|
||||
auto,首播不可跳) (T1 判定有即时反馈) │
|
||||
└──────────────────────────────────────────────────────────────────────┘
|
||||
RELAY(4) ──链完──> 签收 g9_in → g9_knot → ◆p9 ──> d9 结果链 ──> ENDING(e1..e4 + ep 回响链)
|
||||
|
||||
任意叙事态(INTRO/RX/DECODE/RELAY/NIGHT/ENDING):下滑 → MENU(模态)
|
||||
MENU:单击=循环切换选项 → 双击=确认执行 → 上滑=帮助 → 下滑/「回到电波」项=关闭回原状态
|
||||
(RELAY 关闭后重播当前节点;DECODE 关闭后重挂浏览并自动预听当前候选——局面复述)
|
||||
```
|
||||
|
||||
- RX 首播链与 NIGHT 链 `auto:true`;DECODE 浏览为武装等待;RELAY 叙事节点链末等待单击(radio 实现核对 #1 同口径)。
|
||||
- 判定出口(DECODE 第 2 击确认)、NIGHT 链、d9 结果链、e* 结局链入口处盖 `_settleGraceUntil = now+800ms` 手势死窗(dungeon H1/abyss 同款),窗内手势一律吞掉——揭晓与结算链完整播出。
|
||||
- MENU 打开时 `guide.disarm()` + 取消一切看门狗/自动链 + 停摩尔斯排程;关闭后按原状态复述(L5 局面复述,radio closeMenu 先例)。
|
||||
|
||||
### 3.3 每状态手势映射(6+2 全覆盖)
|
||||
|
||||
| 手势 | INTRO/RX | DECODE 浏览 | DECODE 确认 | RELAY | NIGHT | ENDING | MENU |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 上滑 | 帮助+状态,随后重播当前段 | 状态链(prog_t→book 档→exp 档→sys_help,状态在前),留在浏览 | 同左 | 帮助+状态,随后重播当前节点 | 帮助+状态,自动链继续 | 帮助+状态(结局名+档) | 帮助(sys_help) |
|
||||
| 下滑 | 模态菜单 | 模态菜单 | 模态菜单 | 模态菜单 | 模态菜单 | 模态菜单 | 关闭菜单 |
|
||||
| 左滑 | 无效 → error 震+gd_invalid | 上一候选(循环,预听);**首播中=beat 轻震**(摩尔斯不可跳) | 取消确认,回浏览 | 抉择=左 | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → error 震 |
|
||||
| 右滑 | 无效 → gd_invalid | 下一候选(循环,预听) | 取消确认,回浏览 | 抉择=右 | 无效 → gd_invalid | 无效 → gd_invalid | 无效 → error 震 |
|
||||
| 单击 | 播放中=跳过本句;链末=继续 | 进确认:cf_confirm + 电文重播 | **定下判定**(800ms 死窗 → 揭晓链) | 播放中=跳过本句(p* 不可跳);链末=继续 | 跳过本句(自动链) | **再来一局**(新夜班) | 循环切换选项 |
|
||||
| 双击 | 重听当前段 | **重听电文(原速);第 2 次=慢放 0.5×**(任何其他手势重置计数) | 同左(确认态保留) | 重听当前节点台词(含抉择提示) | 重听夜结算链 | 重听结局链 | **确认执行**(菜单约定覆盖重听) |
|
||||
| 长按 | 引擎接管:旁白临时 1.7×(12 号 §2.2,叙事类不豁免) | **空闲=翻密码本(E1 豁免)**;语音在播=引擎快进 | 同左 | 引擎快进 | 引擎快进 | 引擎快进 | 引擎快进(菜单语音) |
|
||||
| 长按松开 | 引擎接管:恢复 settings.speed | 同左 | 同左 | 同左 | 同左 | 同左 | 同左 |
|
||||
|
||||
**豁免登记(必须显式,仅 1 条)**:
|
||||
|
||||
| # | 豁免 | 理由(先例) |
|
||||
|---|---|---|
|
||||
| E1 | DECODE 空闲长按 = 翻密码本求提示(核心资源动词快捷入口) | 绑定条件=无语音在播(`!audio.isVoicePlaying()`),与长按快进(仅在播时生效)**时间互斥、零冲突**(detective E2 同款);摩尔斯为 sfx 域不触发快进;菜单「翻密码本」提供冗余入口(P4 容错) |
|
||||
|
||||
- 其余全部符合宪法默认语义:单击=确认/继续(两段确认=确认的两击)、双击=重听(重听对象=电文)、左右滑=选择/移动、上滑=状态、下滑=菜单(00 号 §5.2 模态约定);菜单=单击循环+双击确认。
|
||||
- 继承 radio G5 跳过公平契约原文:**首播必完整**(摩尔斯首播不可跳、p* 不可跳、链末关键句保护、ASK 恢复首句锁)、**复播可即掐**(锁不随复播重新武装、链中段句可跳、复播滑动照常预缓存)。
|
||||
- 抉择 prompt(p2..p9)播放中单击不可跳(PROMPT_RE 门)+ beat 轻震(G4);左右滑缓存 `pendingChoice` 播完即生效(radio W2 契约)。
|
||||
- 无效手势零静默:所有「无效」格 error 震 + gd_invalid(onceKey 同局限次,F1.4 idle-only)。
|
||||
- **首播保护窗**:电文摩尔斯首播发生在 RX 链尾、DECODE 入口前(§3.2),期间一切玩法手势(左右滑/单击/双击)= beat 轻震(G4),不判无效、不惩罚——摩尔斯走 sfx 域本无跳句通道,「不可跳」由构造保证;首播完 400ms 内 guide.turn + dv*_ask 落地,浏览武装。
|
||||
|
||||
### 3.4 摩尔斯/语音双通道仲裁(abyss 声呐双通道 R1-R5 同型)
|
||||
|
||||
- **通道定义**:摩尔斯通道 = morse.js 的 osc 排程(电文+候选)+ sfx 层(静电/电键);语音通道 = voice 层(你+北风+处长)。BGM 第三层背景(voice 播时引擎 duck)。
|
||||
- **R1 摩尔斯不叠语音**:摩尔斯排程只在 `!audio.isVoicePlaying()` 时起振;在播则挂队列表、voice onComplete/句间轮询补发(abyss sonarGate 同款)。候选预听链 = 候选摩尔斯 → 静默一拍(≥500ms)→ cand_* 词义句(给耳朵留对照时间)。
|
||||
- **R2 电文主导段**(首播/重听/慢放):前景=摩尔斯,BGM dip;voice 只在摩尔斯**结束后**接播(dv*_ask 固定在首播后)。
|
||||
- **R3 BGM 退位**:摩尔斯排程期间游戏层把 BGM 降 −6dB(abyss 远程脉冲垫先例),不消失(A2 无静默死局)。
|
||||
- **R4 危机拍优先**:T4 揭晓 danger 拍(step_halt 前置 2s 渐强 + danger 签名 + danger 震);当前 voice 句播完后危机链才插续(radio C5/abyss D2 同款)。
|
||||
- **R5 摩尔斯可重听**:DECODE 双击=重听电文(P1 关键线索可重听;第 2 次=慢放是本作对 R5 的加强)。
|
||||
- 声流账:任意时刻 ≤2 条前景流(voice 串行 1 + 摩尔斯/信息音 1),BGM/amb 为背景层——ASA ≤2 约束 ✓。
|
||||
|
||||
**引擎能力边界(对照 docs/web-engine-api.md)**:
|
||||
|
||||
| 能力 | 引擎现状 | 方案 |
|
||||
|---|---|---|
|
||||
| 摩尔斯精确时值 | sfx 走文件(有损编码 ms 抖动) | **运行时 osc 模块**(§2.1;guide earcon 先例),三档速度一个参数 |
|
||||
| morse 与 voice 互让 | ❌ sfx 总线独立无 duck | 游戏层 morseGate 空闲门(R1) |
|
||||
| BGM 手动 dip | ❌ 引擎只对 voice duck | 游戏层 gain:摩尔斯期间 −6dB(R3) |
|
||||
| voice 运行时滤波 | ❌ | **不使用**(detective 先例:区分度靠 speaker+instruct+语境;处长=当面训话不需滤波) |
|
||||
| sfx 循环 | ❌ 即发即弃 | 游戏层 `buffer.loop=true`(amb_room/static_bed,radio `_ringSrc` 先例) |
|
||||
| 变速不变调 | ✅ SoundTouch(**仅 voice**) | **摩尔斯不受 settings.speed/长按快进影响**(u 恒定)——边界登记:P1 2.2× 档语速下电文仍原速,慢放兜底;列入 live 真人耳测项 |
|
||||
|
||||
## 4. 抉择树
|
||||
|
||||
### 4.1 全局骨架(四封电文共享主干,分歧局部化)
|
||||
|
||||
```
|
||||
INTRO: b_open → b_rule → b_goal → b_chief ──> RX(1)
|
||||
【T1 船期·教学】BAY 译词 ◆p1 + 报/压 ◆p2 ──> NIGHT(1) ──> RX(2)
|
||||
【T2 名单】TOP 译词 ◆p3 + 暗处理/呈报 ◆p4(known/blind 差分)──> NIGHT(2) ──> RX(3)
|
||||
【T3 交接】HAND 译词 ◆p5 + 亲自/留书 ◆p6 ──> NIGHT(3) ──> RX(4)
|
||||
【T4 处置令·高潮】SPY 译词 ◆p7 + 处长试探 ◆p8(反喂/装傻,known/blind 差分)──> 签收 ◆p9 ──> ENDING
|
||||
```
|
||||
|
||||
分支纪律:**每封电文内部分支、夜结算时收敛**;跨电文只传标量(expose、hints、4 个译文 flag、4 个抉择 flag),不生成组合节点。最坏路径与最好路径节点数同型。
|
||||
|
||||
### 4.2 分封结构(◆=抉择点,〔〕=条件差分)
|
||||
|
||||
```
|
||||
【T1 船期·教学】
|
||||
RX(1): rx1_in → tune_seek → 【摩尔斯首播 BAY,不可跳】→ dv1_teach → dv1_teach2 → dv1_ask → 浏览
|
||||
◆p1 译词(3 选 1,两段确认)
|
||||
├候选 BAY(对): dv1_right(success 震)──────────────────────┐
|
||||
└候选 DAY/RAY(错): dv1_wrong(即时真话归因)→ 回浏览无限重选 ┘(T1 恒收敛为 BAY)
|
||||
RELAY: rev 由 dv1_right 承担 → ◆p2
|
||||
├左 报北风: r1a_send → r1a_e(med_ok 预埋)──┐
|
||||
└右 压日志: r1b_hold → r1b_e(expose+1)────┴→ NIGHT(1): n1 → 〔跨档 exp_*〕→ RX(2)
|
||||
|
||||
【T2 名单】
|
||||
RX(2): rx2_in → 首播 TOP → dv2_ask → 浏览 → ◆p3 译词
|
||||
├TOP(对): rev_top「黑名单。头一个代号,叫码头」→ p4_known ──┐
|
||||
└TAP/TIP(错,不揭穿): rev_tap / rev_tip → p4_blind ─────────┤(expose 译错+1)
|
||||
◆p4: ├左 暗处理: r2a_act → r2a_e(expose+1;known=warn 码头 / blind=压下)┐
|
||||
└右 呈报: r2b_act → m2_chief → r2b_e(mateListed)────────────────┴→ NIGHT(2): n2 …
|
||||
|
||||
【T3 交接】
|
||||
RX(3): rx3_in → 首播 HAND → dv3_ask → 浏览 → ◆p5 译词
|
||||
├HAND(对): rev_hand → 〔p6 行动按「今夜」〕────────────┐
|
||||
└SAND/LAND(错): rev_sand / rev_land → 〔按误译时间行动〕┤(expose+1)
|
||||
◆p6: ├左 亲自去: r3a_go → r3a_e(expose+2)──────────────┤
|
||||
└右 留书暗号: r3b_act →〔t3==hand ? r3b_e_ok : r3b_e_no〕┴→ NIGHT(3): n3 …
|
||||
|
||||
【T4 处置令·高潮】
|
||||
RX(4): rx4_in → 首播 SPY → dv4_ask → 浏览 → ◆p7 译词
|
||||
├SPY(对): rev_spy(danger 拍:step_halt 前置 2s+签名+震)→ p8_known ──┐
|
||||
└SAY/SLY(错): rev_say / rev_sly → p8_blind ──────────────────────────┤(expose+1)
|
||||
处长试探: m4_chief_a → m4_chief_b → ◆p8
|
||||
├左 反喂假电: r4a_feed(配 key_send)→ r4a_e(expose+2,feed=true)──┐
|
||||
└右 装傻: r4b_act → m4_chief_c → r4b_e ────────────────────────────────┴→ 签收
|
||||
【签收】g9_in → g9_knot → ◆p9〔t4==spy ? p9_known : p9_blind〕
|
||||
├左 发最后一电: d9_send(key_send)→〔pre≤4 ? d9_send_e : d9_caught_e〕→ ENDING(e1|e3)
|
||||
└右 关机: d9_sleep → d9_sleepe → ENDING(e2|e4)
|
||||
|
||||
ENDING: e*_a → e*_b(按 expose 档 × p9 四选一)→ ep_lead → ep_med_* / ep_list_* / ep_hand_* / ep_spy_*(按 flags 选播)
|
||||
```
|
||||
|
||||
### 4.3 分支局部化说明
|
||||
|
||||
- 跨电文状态全部是标量:`expose`(int)、`hints`(int 0-3)、译文 flag `t1..t4`(每封所选候选 id;T1 恒真词)、抉择 flag `p2..p9`(左/右)。无任何「电文 i × 电文 j」组合节点。
|
||||
- 条件差分全部是**节点内选播**(radio w1_say/w1_say2 先例):p4/p8/p9 的 known/blind 双提示(由 t2/t4 决定)、r3b_e_ok/no(由 t3 决定)、d9_send_e/caught_e(由 expose 档决定)、ep_* 8 句(由 4 组 flag 决定)。树形不变。
|
||||
- 树规模:139 句台词、约 75 个节点;任一路径恒走 9 个抉择点(4 译词 + p2/p4/p6/p8/p9)——比 radio/abyss 更规整(无跳点路径),操作面完全恒定(P4)。
|
||||
|
||||
### 4.4 四结局达成条件
|
||||
|
||||
| 结局 | 条件 | 主题 | 开环钩子 |
|
||||
|---|---|---|---|
|
||||
| e1 最后一电 | p9 前 expose ≤4 且 p9=左 | 警报发出、北风撤尽,你随挑水的队伍出城 | 「新的密码本,在城外渡口等我」 |
|
||||
| e2 值完这个班 | p9 前 expose ≤4 且 p9=右 | 平安交班,一切照旧——但北风的呼叫没人接 | 「下一次呼叫,你接不接?」 |
|
||||
| e3 孤灯 | p9 前 expose ≥5 且 p9=左 | 发报被测向,你拍了三遍警报,坐等门开;码头撤空 | 「审讯室的灯,也很亮」 |
|
||||
| e4 长夜 | p9 前 expose ≥5 且 p9=右 | 两点五十九分,脚步停在门口;最后一页密码进了炉子 | 「耳机里还有一封译不完的电文」 |
|
||||
|
||||
- 处置令的烈度随暴露档缩放(fiction 自洽):低暴露=处长没有足够把握(约谈/排查);高暴露=今夜三时收网。
|
||||
- 尾声回响(P2):ep_lead 后逐封点名——药品(接住/白过江)、名单(少了一个人/多了红圈)、交接(上船/白亮一夜)、处置令(译对/差三个字母)——玩家每一封的译与错都被点名。
|
||||
|
||||
## 5. 数值设计
|
||||
|
||||
### 5.1 变量
|
||||
|
||||
| 变量 | 类型 | 初值 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `expose`(暴露度) | int | 0 | 步进隐藏资源:译错/危险抉择累积,只涨不跌;档位句播报(§5.3) |
|
||||
| `hints`(密码本余页) | int | 3 | 翻一页 −1;T1 首次免费(不扣);全局不补 |
|
||||
| `t1..t4`(译文) | str | null | 各封所选候选 id;T1 教学期恒收敛真词;t2/t3/t4 记玩家认知 |
|
||||
| `p2/p4/p6/p8/p9` | 'L'/'R' | null | 叙事抉择;派生 med_ok/mateListed/handOk/feed 等 flag |
|
||||
| `endings{}` / `runs` | map/int | — | 结局收集与重玩计数(存档展示用) |
|
||||
|
||||
### 5.2 暴露度累积事件表(单源,账目可验算)
|
||||
|
||||
| 事件 | 条件 | Δexpose | 叙事依据(真话) |
|
||||
|---|---|---|---|
|
||||
| T1 译错 | 教学期无限重选,恒收敛 | **0** | 学习期零惩罚(A5) |
|
||||
| T2/T3/T4 译错 | 选非真词(无论后续左右) | **+1/封** | 你的译文进了纸面(值机日志/呈报/交接条),与对家底档不符 |
|
||||
| p2 右·压日志 | T1 | +1 | 值机日志少一封,晨检对不上号 |
|
||||
| p4 左·暗处理 | T2 | +1 | 卷宗缺页/抽屉藏报,暗手的痕迹 |
|
||||
| p6 左·亲自去 | T3 | +2 | 值班室空岗一刻钟,日志停了一行 |
|
||||
| p8 左·反喂 | T4 | +2 | 拍假电的机器记录抹不掉(哪怕手法伪装了) |
|
||||
| p9 左·发报 | 终局 | +2(**结局判定用 p9 前值**) | 开机信号进了值机室的登记 |
|
||||
|
||||
- 上限:3(译错)+1+1+2+2 = 9(p9 前);下限 0。全对全保守 = 0(calm 档),符合「一晚干净值机」。
|
||||
- 每次事件**不播数字**(隐藏资源);危机感由档位句+氛围音承载;状态链随时可查档位(radio 口碑同型)。
|
||||
|
||||
### 5.3 暴露度档位表(措辞逐值为真)
|
||||
|
||||
| 档 | expose | 整句 | 真实性核验 |
|
||||
|---|---|---|---|
|
||||
| calm 平安 | 0 | 「这一晚,还没人看你第二眼。」 | 0 事件 ✓ |
|
||||
| low 疑云 | 1-2 | 「处长端着茶,多看了你一眼。」 | 1-2 个纸面破绽=被注意 ✓ |
|
||||
| mid 被盯 | 3-4 | 「走廊里的脚步声,慢了下来。」 | 3-4 个破绽=例行留意 ✓ |
|
||||
| hot 危局 | ≥5 | 「门外,好像有人换了岗。」 | ≥5=行动级怀疑(与 p9 收网门 ≥5 一致)✓ |
|
||||
|
||||
- 跨档才播(挂 NIGHT 链尾);上滑状态链随时可查;hot 档各播报位附 warning 震(abyss oxy_crit/G2 同款)。
|
||||
- **p9 收网门 = hot 档阈值 5**:档位句说的就是结局判定用的(radio rep_low/abyss floor(oxy/3) 教训内化——措辞与数值同一个源)。
|
||||
|
||||
### 5.4 密码本账(逐值整句)
|
||||
|
||||
| 余页 | 整句 |
|
||||
|---|---|
|
||||
| 3 | 「密码本,还剩三页。」 |
|
||||
| 2 | 「密码本,还剩两页。」 |
|
||||
| 1 | 「密码本,只剩最后一页。」 |
|
||||
| 0 | 「密码本,一页都不剩了。」 |
|
||||
|
||||
### 5.5 暴露度与四结局验算(设计期穷举,playtest 剧本引用)
|
||||
|
||||
设 `wrong` = T2/T3/T4 译错封数(0-3),`risk` = 危险抉择分(p2右1 + p4左1 + p6左2 + p8左2,0-6):
|
||||
|
||||
- **p9 前 expose = wrong + risk** ∈ [0, 9];结局门:≤4 → e1/e2(p9 左/右);≥5 → e3/e4。
|
||||
- **剧本 A(全对全右,p9 左)**:wrong=0,risk=0 → expose=0(calm)→ p9_known 左 → d9_send_e → **e1**;ep 全 ok(med_ok/list_no/hand_ok/spy_ok——p4 右呈报=名单进卷宗,ep_list_no 是呈报的真实代价)。
|
||||
- **剧本 B(同 A,p9 右)**:expose=0 → **e2**(d9_sleep/d9_sleepe)。
|
||||
- **剧本 C(全错全左,p9 左)**:wrong=3,risk=1+2+2=5 → expose=8(hot)→ p9_blind 左 → d9_caught_e(danger 拍)→ **e3**;逐检查点:NIGHT(1) 后 0(p2 左不加分)→ calm;NIGHT(2) 后 2(wrong T2=1 + p4 左=1)→ low;NIGHT(3) 后 4(+wrong T3 +p6 左 2)→ mid;签收前 8(+wrong T4 +p8 左 2)→ hot。跨档播报 3 次。
|
||||
- **剧本 D(同 C,p9 右)**:expose=8 → **e4**。
|
||||
- **剧本 E(提示流 P4)**:T1 免费提示(hints 3 不动)+ T2/T3/T4 各 1 页(3→2→1→0,book_2/1/0 逐值播出)→ 提示揭示辨析位 ⟹ 候选唯一 → 全对;全右 → expose=0 → e1/e2 任选。**纯提示流可通关 ✓**。
|
||||
- **剧本 F(慢放流 P3)**:T2-T4 每封双击×2 慢放后译对,零提示 → 全对;expose=0。**纯慢放可通关 ✓**。
|
||||
- 边界带验算:wrong=2 + risk=2(p2右+p4左)= 4 → 仍 e1/e2(宽容:两处失误不毁局,P4);wrong=1 + risk=4 = 5 → e3/e4(风险路径必热)。
|
||||
|
||||
## 6. 音频三层映射
|
||||
|
||||
### 6.1 BGM(状态→曲目,Mureka instrumental,4 首 ≈ $0.12;共性后缀 10 号 §8 照 radio 追加)
|
||||
|
||||
| 状态 | 曲目 | 切换 | Mureka prompt 草稿(英文,seamless loop) |
|
||||
|---|---|---|---|
|
||||
| INTRO/RX/DECODE/RELAY/MENU | `bgm/radio_room` | 开局淡入 800ms;MENU 低响度淡入 500ms;摩尔斯期间 −6dB(R3) | "1940s late night wireless station room, warm tube radio hum, distant muffled swing jazz through a wall, rain on window, clock tick, smoky and quiet, no vocals, seamless loop, 60-90 seconds" |
|
||||
| RELAY(4) 起(T4 高潮至签收) | `bgm/closing` | crossfade 800ms 进 | "Tense noir waltz for a spy's last night, muted brushed drums, low strings, subtle telegraph tick rhythm, restrained dread, no vocals, seamless loop, 60-90 seconds" |
|
||||
| NIGHT | `bgm/night` | crossfade 600ms 进;进下一 RX 淡回 600ms | "Quiet night interlude, sparse felt piano over warm vinyl static and soft rain, lonely but safe, no drums, no vocals, seamless loop, 60-90 seconds" |
|
||||
| ENDING | `bgm/ending` | d9 结果链播完后淡入 1500ms | "Dawn after a long night of rain, warm strings and soft piano, exhausted hope, quiet resolution, no vocals, seamless loop, 60-90 seconds" |
|
||||
|
||||
- `bgm=0` 时全部静默(引擎约定);menu 用同曲低响度(省 1 首,值班室同场景同构)。
|
||||
|
||||
### 6.2 SFX 清单(SA3=Stable Audio 3 / SYN=程序合成;one-shot 真峰值 ≤-3dBFS;amb 循环由游戏层 `buffer.loop` 实现)
|
||||
|
||||
| id | 用途 | 来源 | prompt(英文)/ 合成参数 |
|
||||
|---|---|---|---|
|
||||
| `sfx/tune_seek` | 调谐找频(每封 RX 起) | SA3 | "Hand turning a vintage radio tuning dial, static sweeps, faint morse ghost, 3 seconds" |
|
||||
| `sfx/static_bed` | 电台静电底噪 loop(RX/DECODE) | SA3 | "Shortwave radio static bed, soft crackle and hum, seamless loop" |
|
||||
| `sfx/amb_room` | 值班室 amb loop(挂钟+雨+暖炉) | SA3 | "1940s office night ambience, wall clock tick, rain on window, faint stove hum, seamless loop" |
|
||||
| `sfx/paper_page` | 密码本翻页(提示兑换/开局发本/页数用尽) | SA3 | "Thin paper page turn of a small codebook, dry, close" |
|
||||
| `sfx/key_send` | 你的电键拍发(p8 左反喂 / p9 左发报) | SA3 | "Vintage brass telegraph key sending morse, spring clatter and clicks, 4 seconds" |
|
||||
| `sfx/pen_scratch` | 处长记事笔(试探戏/呈报) | SA3 | "Fountain pen scratching on paper, short strokes, 2 seconds" |
|
||||
| `sfx/tea_cup` | 茶杯落桌(处长场标记音) | SA3 | "Tea cup set down on wooden desk, porcelain clink, soft" |
|
||||
| `sfx/step_halt` | 门外脚步停住(T4 danger 拍 / hot 档) | SA3 | "Footsteps in corridor stopping outside a door, heavy boots, tense, 4 seconds" |
|
||||
| `sfx/lock_click` | 门锁/枪机(e3/e4 链) | SA3 | "Door lock slowly turning, metal click, ominous, 2 seconds" |
|
||||
| `sfx/dawn_bell` | 黎明远钟(e1/e2 链) | SA3 | "Distant morning bell over light rain, calm after a long night, 4 seconds" |
|
||||
| `sfx/ui_confirm` / `ui_menu` | 菜单/确认(程序合成兜底) | SYN | 同全局先例 |
|
||||
| **(摩尔斯)** | 电文/候选/重听/慢放全部音 | **SYN 运行时模块** | §2.1 时值表;**零文件资产** |
|
||||
|
||||
- 预警窗纪律:T4 danger 拍 step_halt **前置 2s 渐强**(L2),danger 签名与音效同窗(radio 刹车/abyss 滑坡先例);e3 链首 danger 签名一次。
|
||||
- 承诺绑定:rx4_in 教「快手七的手法你听过」后,p9 左 d9_send 与 p8 左 r4a_feed 必播 key_send(§7.5 清单+断言)。
|
||||
|
||||
### 6.3 Voice 划分(详见 19-cipher-voice-ids.md)
|
||||
|
||||
| 组 | 声部 | 落位 | 数量 |
|
||||
|---|---|---|---|
|
||||
| 报务员(你)OP | 双性别(female=Serena / male=Uncle_Fu + OP instruct 常量) | `voice/female\|male/` | 20 句 |
|
||||
| 北风电台(上线联络+系统/向导)WIND | 双性别(female=Vivian / male=Dylan + WIND instruct 常量) | `voice/female\|male/` | 114 句 |
|
||||
| 处长 NPC | 平铺单音色(Eric),**不做双性别** | `voice/` 根(平铺回退命中) | 5 句 |
|
||||
|
||||
- 人物账:有台词人物 = 你 + 北风(上线联络人,向导人设 L4,radio 导播同型)+ 处长(明线上级)——**上级/联络人 ≤2 约束满足**;另有 1 个**无台词角色**「快手七」(对家报务手,存在=电波里的手法音,abyss 小郑先例)。
|
||||
- 合计 139 句(≤150 预算 ✓,含 `_k` 键盘变体 11 对);音频文件 = 134×2 + 5 = 273 条(×2 格式 = 546 文件)。
|
||||
|
||||
## 7. 向导层接入
|
||||
|
||||
### 7.1 状态签名 earcon / turn cue
|
||||
|
||||
| 时机 | 向导动作 | 说明 |
|
||||
|---|---|---|
|
||||
| INTRO 进入 | `guide.state('explore')` | 叙事态签名 |
|
||||
| DECODE 进入(首播后) | `guide.state('choice')` + `guide.turn({haptic:true})` | 摩尔斯首播完 → turn 签名+confirm 震=「轮到你选词」;dv*_ask 随后落地 |
|
||||
| 每个叙事抉择 p* 起播 | `guide.state('choice')` | 选项句本身即 turn cue(W2 契约零插播) |
|
||||
| NIGHT 进入 | `guide.state('settle')` | 夜结算签名 |
|
||||
| ENDING 进入 | `guide.state('settle')` | 同上 |
|
||||
| MENU 打开 | `guide.state('menu')` | 菜单签名 |
|
||||
| T4 揭晓 danger 拍 / e3 链首 | `guide.state('danger')` | 全游戏仅两处(step_halt 前置 2s 同窗 + danger 震) |
|
||||
| 无效手势 | `guide.error({voice:'gd_invalid', onceKey})` | error 震 + full 档语音,idle-only(F1.4) |
|
||||
|
||||
- earcon 词表:只用全项目统一 8 音中的 7 个(menu/explore/choice/settle/danger/turn/nudge),**零新增**;battle 不用。
|
||||
|
||||
### 7.2 看门狗双轨(radio F1 + dungeon E0-1 教训:乱按也要被抢救)
|
||||
|
||||
**无操作轨**(guide watchdog,任何手势复位;两级不同句、用尽即停、永不惩罚):
|
||||
|
||||
| 等待点 | full 档 | standard 档 | 级别内容 |
|
||||
|---|---|---|---|
|
||||
| DECODE 浏览无操作 | [10, 25] | [10, 25](onEscalate 游戏层补播) | ① gd_tune1「电文在等。左右滑,听候选词。」② gd_tune2「双击,把电文再听一遍。再双击,会放慢。」(**必须不同句**) |
|
||||
| INTRO/RELAY 链末等待 | [25, 50] | [13, 30] | ① gd_continue ② gd_again(storyline F1.1 同款) |
|
||||
| 抉择点 p* 播完无操作 | [25, 50] | [25, 50] | 重播当前 p* 提示句(13 号 §16 先例;空点单击=重挂计时) |
|
||||
| ENDING 结算等待 | [25, 50] | [13, 30] | gd_continue / gd_again |
|
||||
|
||||
**无进展轨**(游戏层计时,仅挂 DECODE 浏览一个可饿死点):
|
||||
|
||||
| 项 | 规则 |
|
||||
|---|---|
|
||||
| 进展定义 | 译词判定落定 / 叙事抉择结算 / 节点推进 / 翻密码本。**无效手势与纯浏览(切候选/重听)不计进展**——乱滑 96s 也必被抢救 |
|
||||
| 阈值 | 60s → gd_prog1「光切词没有用。双击,先听电文。」;120s → gd_prog2「别急。停下来,把电文听完整。」;用尽即停(每封一轮) |
|
||||
| 有真实进展 | 全程零 prog 播出(不误伤) |
|
||||
| 双轨去重 | 两轨短句内容同级交叉登记;同一等待点先播过 gd_tune* 则无进展轨跳过同义级 |
|
||||
|
||||
- 向导插播一律 `queue:true`,绝不顶掉在播对白;error/watchdog 类语音 idle-only(在播则跳过且过期丢弃,F1.4)。
|
||||
- 单句语音看门狗(资产缺失兜底):30s 超时且 `isVoicePlaying()===false` 才自动续链,长句续约不掐(radio N1 先例照抄)。
|
||||
|
||||
### 7.3 键盘变体机制(PC 键盘玩家)
|
||||
|
||||
- 运行时按 `gesture.inputMode()`(settings.input auto/touch/key)选 `_k` 变体资产:key → `voice/<gender>/<id>_k.*`,touch → 无后缀版;auto 跟随最近使用通道。`_k` 缺失回退无后缀版(check_all_assets 校验配对)。
|
||||
- 变体清单(11 对,集中分组见 voice-ids §G0.a + M1):gd_brief / gd_continue / gd_again / gd_tune1 / gd_tune2 / sys_help / sys_menu / sys_skip / sys_continue_ask / **dv1_teach2**(首个候选导航绑定)/ **p2**(首个叙事抉择绑定)。
|
||||
- 抉择提示 p4..p9 措辞统一「向左滑/向右滑」;键盘玩家由 p2_k 与 sys_help_k 完成首次绑定,后续语义不变。启动遮罩文案引擎自理,游戏零改动。
|
||||
|
||||
### 7.4 继承 radio/abyss 三轮验证机制(照抄不重设计)
|
||||
|
||||
| 机制 | 来源 | 落点 |
|
||||
|---|---|---|
|
||||
| 帮助链去重(同等待点第 2 次上滑只震+nudge) | radio G1 | `_helpSeen` 按等待点键控 |
|
||||
| 状态链顺序「状态在前、帮助在后」 | radio G6 | prog_t → book 档 → exp 档 → sys_help |
|
||||
| 空点确认轻震(不可跳上单击补 beat [8]) | radio G4 | p*/摩尔斯首播/链末句各 tap 屏蔽分支 |
|
||||
| exp_hot 播报位附 warning 震 | radio G2 / abyss | 全部 hot 出口(含上滑复听) |
|
||||
| 菜单报名游戏侧排队零截断 | radio 实现修复 #3 | `_menuQueue` 排水 |
|
||||
| stop() 先 `seqToken++` 作废在途链 + 停摩尔斯排程 | radio 实现修复 #6 | 同款(摩尔斯句柄一并 stop) |
|
||||
| 新局清 choiceArmed/pendingChoice/双击慢放计数 | radio 实现修复 #7 | newNight() |
|
||||
| 长按误报无效(longpress 全状态合法) | radio 实现修复 #2 | 仅 left/right(ASK 另含 up/down/doubletap)报无效 |
|
||||
|
||||
### 7.5 教学承诺清单(dungeon「铁器=楼梯」教训:每句承诺必须真实存在且可感知)
|
||||
|
||||
| 教学句 | 承诺 | 兑现点(断言锁定) |
|
||||
|---|---|---|
|
||||
| dv1_teach「先双击,把它重新听一遍」 | 双击=重听电文 | DECODE 全程恒可用(原速) |
|
||||
| gd_tune2/dv1_teach「再双击,会放慢」 | 第 2 次双击=慢放 0.5× | 慢放计数器任何封恒在;时值断言 u=160 |
|
||||
| dv1_teach2「左右滑,听候选词。单击,选定一个」 | 候选预览/两段确认 | 全部 DECODE 恒同语义(含确认态取消) |
|
||||
| hint_t1「我教你一页:第一个字母,是 B」 | T1 提示免费 + 提示给辨析位字母 | T1 翻页不扣;hint_* 与 §2.2 辨析位一致 |
|
||||
| b_rule「规矩就一条:听不清,就再听一遍」 | 重听/慢放无限免费 | 全局无重听惩罚;慢放不限次 |
|
||||
| b_goal「今晚频道里有四封密电」 | 恒 4 封 | 剧本 A 恰 4 封走完(断言) |
|
||||
| b_chief「四封电文,一封都不许漏」 | 同上(明线口径) | 同一计数 |
|
||||
| rx4_in「快手七的手法你听过」→r4a_feed「借快手七的手法」 | 提到发报必闻电键声 | p8 左/p9 左必播 key_send(零漏播断言) |
|
||||
| dv1_right「练习有答案」(正片无对错反馈) | 教学期即时反馈、正片不揭穿 | T1 判定出口有反馈句;T2-T4 判定出口零对错语音(断言) |
|
||||
|
||||
## 8. 存档设计
|
||||
|
||||
### 8.1 进度 data 结构(PUT /api/progress {game:'cipher'})
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": 1,
|
||||
"updatedAt": "2026-09-09T12:00:00Z",
|
||||
"summary": "第2封·名单 · 暴露:疑云 · 结局1/4",
|
||||
"phase": "DECODE",
|
||||
"node": "dv2_ask",
|
||||
"tel": 2,
|
||||
"expose": 2,
|
||||
"hints": 2,
|
||||
"t": { "t1": "bay", "t2": null, "t3": null, "t4": null },
|
||||
"picks": { "p2": "L", "p4": null, "p6": null, "p8": null, "p9": null },
|
||||
"endings": { "e2": true },
|
||||
"runs": 3
|
||||
}
|
||||
```
|
||||
|
||||
- 写入时机:每封 RX 进入、译词判定落定后、每个抉择结算后、NIGHT 进入、ENDING 进入、菜单「保存并回大厅」。
|
||||
- 断点续玩:读档 → sys_continue_ask(_k)(单击=继续上次 / 右滑=新夜班;两者 runs 均 +1,统一口径修 radio 遗留);继续 = 恢复 phase+node+全部数值并**局面复述**:RELAY 恢复=重播当前节点链;**DECODE 恢复=prog_t → book 档 → gd_tune2 → 重挂浏览(自动预听当前候选,电文可双击重听)**——一句话 ≤5s 回到可玩(L5);恢复首句受 F4 跳句锁(radio G5 例外条款)。
|
||||
- summary 格式:进行中 `第N封·<电文名> · 暴露:<档名:平安/疑云/被盯/危局> · 结局X/4`;已收台 `已收台·<结局名> · 结局X/4`(大厅卡片直接显示,人话)。
|
||||
|
||||
## 9. playtest 断言清单(登记 13 号 §22 指针;`settings.set({guide:'off'})` 纯玩法验证,向导行为住 guide.test)
|
||||
|
||||
**关键路径**(剧本 A:全对全右+p9 左 → e1,≤60 次手势):
|
||||
1. 遮罩点按 → INTRO 链(b_open 起,voice 层有播放记录)→ rx1_in → tune_seek → **摩尔斯固定字符断言**:事件序列 == BAY 编码(`dah,dit,dit,dit / dit,dah / dit,dah,dah,dah`),750Hz,u=80±5ms,BGM 同时段 −6dB(R3)→ dv1_teach → dv1_teach2 → dv1_ask → choiceArmed。
|
||||
2. DECODE 浏览:右滑 ×3 循环切换,每次预听 = 候选摩尔斯(620Hz,u=110,与 voice **零重叠**——morseGate 断言)→ cand_* 词义句;双击 → 电文原速重播;再双击 → 慢放(u=160,字母隙 6u,750Hz);单击 → cf_confirm + 电文重播 → 单击 → 判定出口 800ms 死窗内注入在途 tap 被吞。
|
||||
3. T1 译对:dv1_right(success 震)→ RELAY:p2 左(pendingChoice 播完生效)→ r1a_send/r1a_e → n1 → NIGHT(expose=0 calm 档,零跨档播报)→ RX(2)。
|
||||
4. T2:摩尔斯==TOP 断言;译对 → rev_top → **p4_known** 播出(t2=='top')→ 右 → r2b_act → m2_chief → r2b_e → n2。
|
||||
5. T3:摩尔斯==HAND;译对 → rev_hand → p6 右 → r3b_e_ok(t3=='hand')→ n3。
|
||||
6. T4:摩尔斯==SPY;译对 → rev_spy(danger 拍断言:step_halt 前置 2s 渐强 + danger 签名 + danger 震)→ m4_chief_a/b → **p8_known** → 右 → r4b_act → m4_chief_c → r4b_e。
|
||||
7. 签收:g9_in → g9_knot → **p9_known** 左 → d9_send + key_send → pre=0 ≤4 → d9_send_e → ENDING(e1):e1_a/b + ep_lead + ep_med_ok/ep_list_no/ep_hand_ok/ep_spy_ok → 结算单击 → 新夜班回 INTRO(runs+1)。
|
||||
|
||||
**边界断言**:
|
||||
- **四结局可达(含暴露度逐点验算)**:剧本 B(同 A,p9 右)→ e2;剧本 C(T2/T3/T4 全译错 + p4/p6/p8 全左):NIGHT(1) 后 expose=0(calm)/NIGHT(2) 后 2(low,跨档播 exp_low)/NIGHT(3) 后 4(mid)/签收前 8(hot,exp_hot+warning 震)→ p9_blind 左 → d9_caught_e → e3;剧本 D(同 C,p9 右)→ e4(lock_click 链)。各检查点档位句与 §5.3 区间一致。
|
||||
- **三层兜底各自可走通**:剧本 E(提示流)——T1 翻页免费(hints 仍 3),T2/T3/T4 各翻 1 页(book_2/1/0 逐值播出,逐页 −1 断言),按提示选定全对 → e1;第 4 封后页数 0 再长按 → error 震 + paper_page,无语音、零惩罚;剧本 F(慢放流)——每封双击×2 慢放后译对,零提示,全对(慢放不限次断言)。
|
||||
- **摩尔斯正确性**:四封电文事件序列逐元素 == §2.2 表(BAY/TOP/HAND/SPY);候选预听 == 所选候选编码;慢放时值 = 原速 2×(容差 ±5ms);**settings.speed=2.2 会话下 u 仍 80ms**(速度边界断言)。
|
||||
- **译错归因真话**:T1 错选 DAY/RAY → dv1_wrong 播出且句内字母/码与真词 B 一致 → 无限重选直至对(expose 零变化);剧本 C/D 的 ep_spy_no/ep_hand_no 与 t3/t4 flags 一致;正片判定出口零对错语音(教学承诺)。
|
||||
- **morseGate**:全剧本转写断言——摩尔斯起振时刻与任一 voice 播放区间零重叠(排队语义:晚于在播句结束);候选词义句在候选摩尔斯结束后 ≥500ms 接播。
|
||||
- **看门狗双轨**:DECODE 快进 10s→gd_tune1、25s→gd_tune2(两级 id ≠,绝不逐字重播;两级后仅静电底噪);**乱按 96s**(每 2s 注入一次切候选滑动+双击重听):无操作轨零播出(被手势复位)+ 无进展轨 60s→gd_prog1、120s→gd_prog2;有真实进展(译词落定推进)全程零 prog 播出;RELAY 链末 25s/50s → gd_continue/gd_again;p* 播完 25s/50s 重播 p* 两次后静默;`guide=off` 全程零定时器零向导事件。
|
||||
- **键盘变体**:settings.input='key' 会话播出 gd_brief_k/sys_help_k/gd_tune1_k/dv1_teach2_k/p2_k 且对应无后缀版零播出;'touch' 反之;'auto':keydown 后下一句用 _k。
|
||||
- **中断复述**:杀进程重进(存档 phase=DECODE, tel=3, hints=1)→ sys_continue_ask → 单击 → 恢复链 prog_t3 → book_1 → gd_tune2 → 浏览重挂 + 当前候选自动预听(电文双击可重听);恢复首句不可跳,第二句起可跳;右滑=新夜班(runs +1 口径同结算重开)。
|
||||
- **pendingChoice**:p* 播放中左右滑 → 播完立即生效(不丢、不重复、连滑取最后一次)。
|
||||
- **p* 不可跳**:p* 播放中单击 → 不跳句(PROMPT_RE 门)+ beat 轻震(G4)。
|
||||
- **idle-only**:向导 gd_* 在对白在播时跳过且不占 onceKey(F1.4);queue 句排在当前句后零截断。
|
||||
- **菜单**:下滑开菜单 → 上滑帮助 → 双击「回到电波」→ phase/node 不变、原状态复述;单击循环顺序 = [回到电波, 翻密码本, 重新开始今晚, 保存并回大厅];菜单「翻密码本」确认 = 长按同效(回 DECODE 执行提示流程);确认回大厅 → sys_menu_lobby 播出且 PUT/localStorage 有最新 data。
|
||||
- **资产缺失**:屏蔽任意 voice 资产 → 30s 看门狗自动续链到结局不卡死;摩尔斯为运行时合成,屏蔽 sfx/bgm 全部不影响电文可听(模块零依赖断言)。
|
||||
- **数字规范**:prog_*/book_*/exp_* 全部整句字面量,静态审计无运行时拼接碎片。
|
||||
|
||||
**失败即不通过**:任何等待 >5s 无声(静默死局);无效手势零反馈;摩尔斯字符序列/时值与 §2.2 表不符;摩尔斯与语音重叠;结局计数与 §4.4 不符;档位句与 expose 实值不符;归因假话(dv1_wrong 字母错、ep_* 与 flags 不符)。
|
||||
|
||||
## 10. 节奏曲线(每封电文目标时长)
|
||||
|
||||
| 段 | 内容 | 目标时长 | 设计目标 |
|
||||
|---|---|---|---|
|
||||
| 开场 | 夜班开场 + 一句规矩 + 处长夜令 | 1-1.5 min | 首输入 <60s(INTRO 链 ~25s 后第一封电文首播,浏览滑动即首输入 ~t+45s);规矩一句讲完(P4) |
|
||||
| T1 船期 | 教学封:两层重听教学 + 免费提示示范 + p2 | 3.5-4 min | **首个正反馈 ≤3min**(译对 dv1_right ≈ t+2min);译错即时归因+无限重选零惩罚 |
|
||||
| T2 名单 | known/blind 差分首现 | 3-3.5 min | 「你不知道自己译没译对」的第一次心跳;呼吸口(听辨最易) |
|
||||
| T3 交接 | 最狠听辨(H vs S 差一个 dit) | 3.5-4 min | 慢放价值兑现段(P3 嘈杂场景实测点);NIGHT(3) 档位句把压力可视化 |
|
||||
| T4 处置令 | 高潮:danger 拍 + 处长试探 + 反喂 | 4-4.5 min | 紧张峰值但无倒计时(P4);bgm/closing 交叉淡入 |
|
||||
| 签收+结局 | 心结 p9 + 发最后一电 + ep 点名 | 2.5-3 min | 整晚的「听」在你自己的电键上闭环(P2 回响);开环钩子拉下一局 |
|
||||
|
||||
- 合计 ≈ 19-22 min(15-25 达标);每 30-60s 一个反馈颗粒(候选预听/摩尔斯/揭晓句/结果句/档位句),无沉默尴尬期(P3)。
|
||||
- 难度曲线 = 听辨精度(教学 B/D → 最易 O/A/I → 最狠 H/S → 中高 P/L)+ 认知重量(练手 → 名单有同伴 → 时间就是命 → 处置令是你),操作复杂度恒定(左右滑+单击+双击,永远如此)——P4「规则一遍听懂」由恒定操作面保证。
|
||||
|
||||
## 11. 自检清单(策划交付自验;☐ = 待开发/测试轮填)
|
||||
|
||||
| # | 硬约束/教训 | 落地 | ✓ |
|
||||
|---|---|---|---|
|
||||
| 1 | FTUE 首输入 <60s | INTRO 链 ~25s 后第一封首播,~t+45s 首次滑动(§10) | ☑ |
|
||||
| 2 | FTUE 首个正反馈 ≤3min | 译对 dv1_right ≈ t+2min(§10 T1) | ☑ |
|
||||
| 3 | 学习期零惩罚 | T1 译错无限重选零资源;DECODE 无超时惩罚;密码本 T1 免费 | ☑ |
|
||||
| 4 | 失败三问归因+归因真话律 | dv1_wrong 字母真话;正片不揭穿+ep_* 单源对应;§2.5 无假归因 | ☑ |
|
||||
| 5 | 资源账目真实(radio rep_low 教训) | book_1..0 逐值整句;exp 四档措辞逐值为真;档位阈值=收网门(§5.3/5.4) | ☑ |
|
||||
| 6 | 手势宪法 6 手势默认语义,豁免显式登记 | §3.3 全覆盖;唯一豁免 E1(长按=密码本,时间互斥 detective E2 同款);菜单 §5.2 模态 | ☑ |
|
||||
| 7 | 长按显式登记 | E1 在案(空闲=提示/在播=引擎快进,互斥);其余长按=快进不豁免 | ☑ |
|
||||
| 8 | 向导:签名/turn cue/看门狗两级不同句 | §7.1/7.2;gd_tune1≠gd_tune2;gd_continue→gd_again | ☑ |
|
||||
| 9 | 看门狗双轨(dungeon E0-1/radio F1 教训) | 无操作轨+无进展轨;纯浏览不计进展,乱按 96s 必被抢救;有进展零打扰 | ☑ |
|
||||
| 10 | 插播 queue 不顶对白;纠错 idle-only 过期丢弃 | §7.2(F1.4 契约) | ☑ |
|
||||
| 11 | 死亡/失败结算链完整(dungeon H1 教训) | 判定出口/NIGHT/d9/e* 链 800ms 手势死窗;seqToken+停摩尔斯(§3.2/7.4) | ☑ |
|
||||
| 12 | 教学承诺兑现(dungeon「铁器=楼梯」教训) | §7.5 九条承诺清单逐条绑兑现点+断言 | ☑ |
|
||||
| 13 | 单次播报 ≤3 选项 | 译词恒 3 候选逐项预听(≤3 ✓);叙事抉择恒 2;菜单 4 项循环 | ☑ |
|
||||
| 14 | 并发声流 ≤2 + 双通道仲裁 | voice 串行+摩尔斯互斥(R1-R5,§3.4);断言零重叠 | ☑ |
|
||||
| 15 | earcon ≤7、统一词表、语义靠语音 | 只用统一 8 音中 7 个(battle 不用),零新增 | ☑ |
|
||||
| 16 | 台词一句一文件 ≤40 字、数字分档整句、无运行时拼接 | voice-ids 全表 ≤30 字(最长 p8_known);prog/book/exp 分档整句;句内数字汉字写死 | ☑ |
|
||||
| 17 | 多音字规避/同 instruct 常量/无语速词 | voice-ids 头部+文末自查(长/处/圈/卷/塞/着 试听项) | ☑ |
|
||||
| 18 | PC 键盘变体(提示句集中+标注) | 11 对 _k 集中分组(voice-ids §G0.a+M1);inputMode() 运行时选择 | ☑ |
|
||||
| 19 | P1:可打断/重听(含电文)/status key/不当白痴 | 跳句+双击(电文重听+慢放 R5)+上滑状态链+成功零补救教学 | ☑ |
|
||||
| 20 | P2:3 分钟见效/结局回响 | ~2min 首个「世界因你而动」(药船接应);ep 逐封点名+真词差分 | ☑ |
|
||||
| 21 | P3:3 分钟颗粒/中断复述/单手/嘈杂 | 一封=3-4min 完整段落+边界存档;重进一句话复述(含 DECODE 态);慢放无限=嘈杂兜底 | ☑ |
|
||||
| 22 | P4:一遍懂/容错/无高压倒计时/不骂人 | 一句规矩;无倒计时无死亡;译错也是剧情;操作面恒定;NPC 句 ≤23 字 | ☑ |
|
||||
| 23 | 抉择树 4 结局可走通验算 | §5.5 公式+剧本 A-D 逐检查点(e1/e2: wrong+risk≤4;e3/e4: ≥5) | ☑ |
|
||||
| 24 | 单局 15-25 min | §10 曲线 ≈19-22 min | ☑ |
|
||||
| 25 | 分支不爆炸(radio 纪律) | 跨封仅标量(expose/hints/t1..t4/picks);~75 节点固定深度(§4.3) | ☑ |
|
||||
| 26 | 台词 ≤150、上级/联络人 ≤2 | 139 句(双 134+NPC 5)/处长+北风两人(voice-ids 账目) | ☑ |
|
||||
| 27 | 引擎边界诚实 | §3.4 能力表:摩尔斯文件方案否决理由/滤波不使用/速度边界写明 | ☑ |
|
||||
| 28 | 存档带 summary、断点续玩 | §8:data+summary 格式、DECODE 态恢复、局面复述 | ☑ |
|
||||
|
||||
### 批 1 教训内化确认(5 条全落地)
|
||||
|
||||
| 教训 | 本作机制 | 断言 |
|
||||
|---|---|---|
|
||||
| 教学承诺必须兑现 | §7.5 九条承诺清单(重听/慢放/候选/单击/免费页/四封/电键声/教学反馈边界) | §9「摩尔斯正确性」「教学承诺」块 |
|
||||
| 看门狗双轨 | §7.2 无操作轨+无进展轨;纯浏览不复位进展轨 | §9「看门狗双轨」块 |
|
||||
| 档位措辞真实 | §5.3 exp 四档逐值为真且阈值=收网门;§5.4 book 逐值 | §9「档位句与实值一致」 |
|
||||
| 失败结算链完整不被打断 | 800ms 死窗盖判定出口/NIGHT/d9/e*;seqToken+停摩尔斯 | §9 关键路径 2 + 死窗断言 |
|
||||
| PC 键盘操作提示 | 11 对 _k+inputMode() 选择+集中分组标注 | §9「键盘变体」块 |
|
||||
|
||||
### 本作特有风险预防(摩尔斯听觉门槛)
|
||||
|
||||
| 风险 | 三层防线 | 验证 |
|
||||
|---|---|---|
|
||||
| P3 通勤嘈杂听不清长短音 | ①双击重听原速(无限)②再双击慢放 0.5×(无限、字母隙加倍)③密码本提示直译辨析位字母(3 页,T1 免费)——**三层均可独立走通** | §9 剧本 E/F + 三层兜底断言 |
|
||||
| P1 视障玩家音频编码接受度高,但语速档位边界 | 摩尔斯不受 settings.speed 影响(u 恒定)——2.2× 档语速下电文仍原速,慢放兜底;**边界登记 + live 真人耳测项**(转写层测不了时值听感,methodology §5 2026-09 教训:媒体播放类必须真人耳测+引擎级断言) | §9「摩尔斯正确性」速度边界断言 + live 轮清单 |
|
||||
| 摩尔斯时值精度 | 运行时 osc 合成(文件方案否决:有损编码 ms 抖动);事件钩子逐元素断言 | §9 固定字符断言 |
|
||||
| 「不知道译对没译错」的认知焦虑 | 教学期说破「练习有答案,正片靠耳朵」;档位句+ep 回响给最终真相;译错零惩罚只是分支 | §9「译错归因真话」 |
|
||||
@@ -15,6 +15,19 @@
|
||||
| [04-space-rebuild.md](04-space-rebuild.md) + [04-space-voice-ids.md](04-space-voice-ids.md) | 《盲航》重构:交互式演练、同向闪避、60 秒保护期、52 句台词表 | ⑨ |
|
||||
| [05-rpg.md](05-rpg.md) + [05-rpg-chapter1.md](05-rpg-chapter1.md) + [05-rpg-ch1-voice-ids.md](05-rpg-ch1-voice-ids.md) + [05-rpg-addendum.md](05-rpg-addendum.md) | 新 RPG《剑与声》:总设计 + 第一章施工稿(场景图/战斗状态机/数值/存档/资产 prompt)+ 120 句台词表 + 增补(战斗震动配合、无声前摇协议、层叠规则) | ⑩ |
|
||||
|
||||
## new8 系列设计文档(8 款新 RPG,research/new8/00-program.md)
|
||||
|
||||
| 文档 | 内容 |
|
||||
|---|---|
|
||||
| [15-radio.md](15-radio.md) + voice-ids | 《不眠频率》深夜电台热线叙事(批次 1 交付,4/4 人设满分档):9 抉择 4 结局、口碑经济、电话滤波资产侧带通 |
|
||||
| [16-dungeon.md](16-dungeon.md) + voice-ids | 《回声地窟》听声辨位 Roguelike(批次 1 交付,0/4→4/4 翻案):脉冲回声、seed 生成、双轨看门狗、元进度遗产 |
|
||||
| [17-abyss.md](17-abyss.md) + voice-ids | 《深海回音》深海救援叙事(批次 2):声呐双通道仲裁、氧气软失败经济(512 组合穷举验算)、148 句 |
|
||||
| [18-detective.md](18-detective.md) + voice-ids | 《雨夜专案》本格推理(批次 2):证词矛盾=数据单源推导、评级 S-C、二周目变体案、158 句 |
|
||||
| [19-cipher.md](19-cipher.md) + voice-ids | 《暗码》1940 密战叙事(批次 3):摩尔斯运行时合成解码、暴露度经济、三层兜底、139 句 |
|
||||
| 20~22 | 《深夜茶馆》《守塔人》《星环舰长》(批次 3/4,设计进行中,落地后补登记) |
|
||||
|
||||
各 voice-ids 文档的「键盘变体」小节由 KEY_LINES 程序化生成(tools/gen_voice_dual.py 单源),含 PC 键盘措辞(按空格/按回车/按方向键)。
|
||||
|
||||
## 审查档案(各轮落实度与裁定记录)
|
||||
|
||||
| 文档 | 内容 |
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# storyline-voice-ids · 《声命线》语音台词表(voice ids)
|
||||
|
||||
> 主台词表(41 句旁白/系统音,双性别)的唯一事实源是 `tools/gen_voice_dual.py`
|
||||
> 的 `STORYLINE_LINES`(角色台词为平铺单音色,沿用 `web/storyline/audio/voice/`
|
||||
> 原文件,不在双性别管线内;主表史源 app/src/storyline/gen/gen_voice.py)。
|
||||
> 本文档登记 P1 键盘变体(输入平权路线 a)——变体只覆盖含操作词的句子,
|
||||
> id 集合 ⊆ 主表,复用主表同 id 的 instruct/speaker。
|
||||
---
|
||||
|
||||
## 键盘变体(key,P1 输入平权路线 a,19 句)
|
||||
|
||||
「生效输入模式=键盘」(settings.input=key,或 auto 且键盘在跟/纯桌面初始判定,
|
||||
web/engine/gesture.js inputMode())时,引擎 voice 候选链前置加载
|
||||
`web/storyline/audio/voice/<gender>/key/<id>.{ogg,m4a}`,变体缺失回退主资产——触屏措辞零改动。
|
||||
>
|
||||
> 措辞规范(02-input-parity.md §4.3 路线 a + P1 裁定):向左滑→按左方向键(四向同理,
|
||||
> WASD 不进台词);单击/轻触屏幕→按空格;双击→按回车;按住/长按→按住空格;
|
||||
> 复合短语(左右滑→左右方向键、滑动→方向键)按语义等价展开;只改操作词,其余
|
||||
> 内容一字不动,不新增「重」类多音字。本表与 `tools/gen_voice_dual.py` 的
|
||||
> `KEY_LINES` 逐字一致(两表一致铁律:改一处必改另一处)。
|
||||
> 资产:×2 性别 = 19×2 条、×2 格式 = 19×4 文件;管线 `gen_voice_dual.py --only-variants`
|
||||
> 生成 → `--convert` 落盘,终检 `tools/check_all_assets.py`(key 两性别一致且 ⊆ 主集)。
|
||||
|
||||
| id | 键盘台词全文(触屏原句见本表上方对应 id) |
|
||||
|---|---|
|
||||
| `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` | 还在吗?按空格继续。 |
|
||||
@@ -5,7 +5,10 @@
|
||||
检查 8 个游戏 web/<game>/audio/:
|
||||
1. 每个 .ogg 都有同名 .m4a(反之亦然)
|
||||
2. voice/male 与 voice/female 的 id 集合一致(storyline/rpg 的平铺 NPC/角色句除外)
|
||||
3. 无 0 字节文件;ogg/m4a 均可被 ffprobe 解码且时长 > 0
|
||||
3. 键盘变体目录 voice/<gender>/key/(P1 路线 a):
|
||||
male/female 两性别 key 集合一致;key 集合 ⊆ 主(性别目录)集合——变体只
|
||||
覆盖含操作词的句子,其余 id 由引擎回退主资产
|
||||
4. 无 0 字节文件;ogg/m4a 均可被 ffprobe 解码且时长 > 0
|
||||
输出 Markdown 报告到 stdout(重定向进 docs/audio/final-asset-check.md)。
|
||||
"""
|
||||
import os
|
||||
@@ -27,8 +30,8 @@ def ids(d, ext):
|
||||
def main():
|
||||
problems = []
|
||||
print("# 全项目音频资产终检(R5 建立,批次 1 起含 radio/dungeon,2026-09-01)\n")
|
||||
print("| 游戏 | ogg | m4a | 0字节 | 解码失败 | voice male/female id 一致 |")
|
||||
print("|---|---|---|---|---|---|")
|
||||
print("| 游戏 | ogg | m4a | 0字节 | 解码失败 | voice male/female id 一致 | key male/female 一致且 ⊆ 主集 |")
|
||||
print("|---|---|---|---|---|---|---|")
|
||||
for g in GAMES:
|
||||
base = os.path.join(ROOT, g, "audio")
|
||||
oggs, m4as, zero, bad = [], [], [], []
|
||||
@@ -72,17 +75,30 @@ def main():
|
||||
problems.append(f"{g}: voice 缺 female/{i}")
|
||||
for i in sorted(vf - vm):
|
||||
problems.append(f"{g}: voice 缺 male/{i}")
|
||||
# 键盘变体目录(P1 路线 a):两性别 key 集合一致 + key ⊆ 主集合
|
||||
km = ids(os.path.join(base, "voice", "male", "key"), ".ogg")
|
||||
kf = ids(os.path.join(base, "voice", "female", "key"), ".ogg")
|
||||
if km == kf and km <= vm:
|
||||
kpair = f"✅ 各 {len(km)} ⊆ 主集" if km else "—(无变体目录)"
|
||||
else:
|
||||
kpair = f"❌ male={len(km)} female={len(kf)}"
|
||||
for i in sorted(km - kf):
|
||||
problems.append(f"{g}: key 变体缺 female/key/{i}")
|
||||
for i in sorted(kf - km):
|
||||
problems.append(f"{g}: key 变体缺 male/key/{i}")
|
||||
for i in sorted((km | kf) - (vm | vf)):
|
||||
problems.append(f"{g}: key 变体 {i} 不在主集合(key 必须 ⊆ 主集)")
|
||||
flat = ids(os.path.join(base, "voice"), ".ogg")
|
||||
flat_note = f"(平铺 {len(flat)} 个角色/NPC 句,豁免)" if g in FLAT_OK and flat else \
|
||||
(f"(⚠️ 非豁免游戏存在平铺 {len(flat)} 个)" if flat and g not in FLAT_OK else "")
|
||||
print(f"| {g} | {len(oggs)} | {len(m4as)} | {len(zero)} | {len(bad)} | {pair}{flat_note} |")
|
||||
print(f"| {g} | {len(oggs)} | {len(m4as)} | {len(zero)} | {len(bad)} | {pair}{flat_note} | {kpair} |")
|
||||
print()
|
||||
if problems:
|
||||
print(f"## 问题清单({len(problems)} 条)\n")
|
||||
for p in problems:
|
||||
print(f"- {p}")
|
||||
return 1
|
||||
print("## 结论\n\n全部通过:双格式配对完整、无 0 字节、全部可解码、voice 性别目录 id 集合一致(storyline/rpg/radio 平铺角色句按约定豁免)。")
|
||||
print("## 结论\n\n全部通过:双格式配对完整、无 0 字节、全部可解码、voice 性别目录 id 集合一致(storyline/rpg/radio 平铺角色句按约定豁免)、键盘变体目录两性别一致且 ⊆ 主集合。")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -31,6 +31,14 @@ for game, lines in gvd.GAMES.items():
|
||||
table[fid] = text
|
||||
out[game] = table
|
||||
|
||||
# 键盘变体(P1 路线 a):独立顶层表 "__key__",不与主表混放——现有消费者
|
||||
# (mock.mjs / transcribe.mjs / live/control.mjs 的 VOICE_TEXT[game][id] 查找)
|
||||
# 行为零改动;键盘措辞会话(input=key)按 VOICE_TEXT.__key__[game][id] 取期望文本。
|
||||
out["__key__"] = {
|
||||
game: {fid: text for fid, text in gvd.KEY_LINES[game]}
|
||||
for game in gvd.GAMES
|
||||
}
|
||||
|
||||
|
||||
def parse_legacy(py_path: Path) -> dict:
|
||||
"""AST 解析旧版 gen_voice.py:提取 ('id', ("Speaker", "instruct"), "text") 元组。
|
||||
@@ -57,5 +65,6 @@ for game, legacy in [("storyline", ROOT / "app/src/storyline/gen/gen_voice.py"),
|
||||
dest = ROOT / "web" / "harness" / "voice_text.json"
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
dest.write_text(json.dumps(out, ensure_ascii=False, indent=1), encoding="utf-8")
|
||||
total = sum(len(v) for v in out.values())
|
||||
print(f"OK {dest.relative_to(ROOT)} games={len(out)} lines={total}")
|
||||
total = sum(len(v) for k, v in out.items() if k != "__key__")
|
||||
key_total = sum(len(v) for v in out["__key__"].values())
|
||||
print(f"OK {dest.relative_to(ROOT)} games={len(out) - 1} lines={total} key_variants={key_total}")
|
||||
|
||||
+304
-38
@@ -9,6 +9,12 @@
|
||||
- 输出 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。
|
||||
@@ -897,6 +903,218 @@ DUNGEON_LINES = [
|
||||
|
||||
NPCS = {"rpg": RPG_NPC, "radio": RADIO_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": [
|
||||
("tut_1", "听,底鼓。在下一小节的同一个位置,按空格。"),
|
||||
("tut_2", "这次是左和右。跟着打,按左方向键,按右方向键。"),
|
||||
("tut_3", "最后一句,三个音,中间是按回车。"),
|
||||
("select_hint", "左右方向键选曲目,按空格开始,按下方向键回大厅。"),
|
||||
("restart", "按空格再来一遍,按右方向键下一首,按下方向键回大厅。"),
|
||||
("menu_main", "菜单。按空格切换选项,按回车确认。"),
|
||||
("skip_hint", "按空格可跳过旁白。"),
|
||||
("gd_miss_early", "抢拍了。等声音落下再按。"),
|
||||
("gd_miss_wrong", "方向不对。跟着音色按方向键。"),
|
||||
],
|
||||
"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", "按空格,再探一次。"),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
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": {}}
|
||||
@@ -970,8 +1188,12 @@ def split_text(text, limit=SPLIT_LIMIT):
|
||||
return [o for o in out if o.strip()]
|
||||
|
||||
|
||||
def staging_path(game, gender, fid):
|
||||
return os.path.join(STAGING, game, gender, fid + ".wav")
|
||||
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):
|
||||
@@ -991,34 +1213,21 @@ def valid_wav(path):
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 生成
|
||||
def generate(games, genders, only):
|
||||
def _generate_lines(model, game, gender, lines, variant=""):
|
||||
"""一组双性别台词(主表或 key 变体)生成到 staging <game>/<gender>[/<variant>]/。"""
|
||||
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:
|
||||
lines = GAMES[game]
|
||||
if only:
|
||||
lines = [x for x in lines if x[0] in only]
|
||||
for gender in genders:
|
||||
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)
|
||||
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)
|
||||
path = staging_path(game, gender, fid, variant)
|
||||
if valid_wav(path):
|
||||
continue
|
||||
spk = spk_map.get(fid, (speaker, speaker))[ins_idx]
|
||||
@@ -1026,8 +1235,8 @@ def generate(games, genders, only):
|
||||
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}] nothing to do", flush=True)
|
||||
continue
|
||||
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]
|
||||
@@ -1046,29 +1255,60 @@ def generate(games, genders, only):
|
||||
)
|
||||
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}] batch {bi + 1}/{len(queue)} ({kind}, n={len(batch)})", flush=True)
|
||||
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)
|
||||
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}/{fid}: 缺 chunk {len(got)}/{n_chunks}"
|
||||
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}/{fid}: peak {peak:.3f}"
|
||||
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}/{fid}: dur {dur:.2f}s"
|
||||
assert dur > 0.3, f"{game}/{gender}/{variant}/{fid}: dur {dur:.2f}s"
|
||||
sf.write(path, wav, sr)
|
||||
print(f"[ok] {game}/{gender}/{fid} {dur:.2f}s chunks={n_chunks}", flush=True)
|
||||
print(f"[ok] {game}/{gender}/{variant}/{fid} {dur:.2f}s chunks={n_chunks}", flush=True)
|
||||
|
||||
# NPC 单音色平铺(staging <game>/npc/,convert 落 flat voice/)
|
||||
npc_lines = NPCS.get(game)
|
||||
|
||||
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)
|
||||
@@ -1098,13 +1338,13 @@ def check(games):
|
||||
import numpy as np
|
||||
import soundfile as sf
|
||||
|
||||
def check_set(game, lines, variant=""):
|
||||
problems = []
|
||||
for game in games:
|
||||
print(f"=== {game} ===")
|
||||
for fid, _ins, _text in GAMES[game]:
|
||||
prefix = f"key/" if variant else ""
|
||||
for fid, _ins, _text in lines:
|
||||
durs = {}
|
||||
for gender in ("male", "female"):
|
||||
path = staging_path(game, gender, fid)
|
||||
path = staging_path(game, gender, fid, variant)
|
||||
if not os.path.exists(path):
|
||||
durs[gender] = None
|
||||
continue
|
||||
@@ -1112,16 +1352,26 @@ def check(games):
|
||||
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}/{fid}: 质量不达标 dur={durs[gender]:.2f} peak={peak:.3f}")
|
||||
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}/{fid}: 缺 {'male' if m is None else 'female'}")
|
||||
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}/{fid}: 时长离群 m={m:.2f}s f={f:.2f}s ratio={ratio:.1f}")
|
||||
print(f" {fid:24s} m={m:6.2f}s f={f:6.2f}s{flag}")
|
||||
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:")
|
||||
@@ -1151,6 +1401,20 @@ def convert(games):
|
||||
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 带通)
|
||||
for fid, _sp, _ins, _t in NPCS.get(game, []):
|
||||
src = os.path.join(STAGING, game, "npc", fid + ".wav")
|
||||
@@ -1175,6 +1439,8 @@ def main():
|
||||
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()
|
||||
@@ -1188,7 +1454,7 @@ def main():
|
||||
if args.convert:
|
||||
convert(games)
|
||||
return
|
||||
generate(games, genders, only)
|
||||
generate(games, genders, only, variants_only=args.only_variants)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -71,7 +71,8 @@ globalThis.clearTimeout = unschedule;
|
||||
globalThis.localStorage = localStorageMock;
|
||||
globalThis.fetch = (url, opts) => { fetchLog.push({ url, opts }); return Promise.resolve({ ok: true, json: async () => ({}) }); };
|
||||
Object.defineProperty(globalThis, 'navigator', {
|
||||
value: { vibrate: (p) => { vibrateLog.push({ t: vnow, p }); return true; } },
|
||||
// maxTouchPoints: 1 —— 手机 persona:inputMode() 判 touch,语音走触屏措辞主资产(键盘路由由 engine-input K9 专测)
|
||||
value: { maxTouchPoints: 1, vibrate: (p) => { vibrateLog.push({ t: vnow, p }); return true; } },
|
||||
configurable: true,
|
||||
});
|
||||
globalThis.window = {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user