15 · 《不眠频率》(radio) — 深夜电台热线叙事 RPG 设计文档
给开发的实现要点摘要
- 叙事模式(声命线范式):节点链 onComplete 串行 + seqToken 打断作废 + pendingChoice 缓存;复制
web/storyline/ 结构起步,状态机用 6 宏状态(INTRO/RING/ONAIR/BRIDGE/ENDING + MENU 覆盖层)。
- 手势零豁免:全部走全局默认映射(单击=继续/接听/跳句、双击=重听、左右滑=抉择、上滑=状态、下滑=模态菜单 §5.2 约定、长按=引擎快进)。
- 台词依据 15-radio-voice-ids.md(132 句:双性别 103 + 平铺 NPC 29);NPC 人声电话滤波在资产侧 ffmpeg 带通(storyline n03_msg 先例),引擎零改动。
- 向导层:storyline 同款看门狗节奏 + RING 专用两级新句提示;
gd_ring1/gd_ring2 为本游戏新增 gd_* 资产。
- 章程任务 N1(research/new8/00-program.md):单局 15-25 分钟、9 抉择点、4 结局、分支局部化。
1. 概述
- 一句话:你是午夜档电台热线主播「今夜不眠」,直播间里一晚接进五个来电——你说什么话、接不接、转不转接,改变五个陌生人的夜晚,也改变你自己。
- 题材(声音即世界):全部叙事靠电话音质感(每位来电者一种滤波声)、电台垫乐、直播间环境音呈现——来电者永远不露面,只有声音;「声音即存在」与纯音频载体天然同构。
- 单局:一个完整夜班(一条抉择路径)15-22 分钟;五个来电共享主干,分歧点局部化在每通电话内部;9 个抉择点(单局实走 8-9 个),4 结局。
- 核心循环:接听 → 听 → 抉择(左=走心介入 / 右=克制程序)→ 结果与口碑结算 → 幕间喘息 → 下一通。跨来电积累「今晚状态」(听众口碑 + 各来电好感),收播前一个抉择决定你自己的心结是否解开。
2. 状态机
2.1 状态列表
| 状态 |
内容 |
BGM |
guide.state |
INTRO |
开场链:台标口播 + 一句规矩 + 今晚目标 |
bgm/onair |
explore |
RING |
热线接入:铃声循环,等待单击接听 |
bgm/onair(不切) |
无新签名(turn cue 承担) |
ONAIR |
通话叙事:对白链 + 抉择点 + 结果 |
bgm/onair |
explore;抉择点=choice |
BRIDGE |
幕间:口碑跨档播报(如有)+ 喘息垫乐,自动链 |
bgm/bridge |
settle |
ENDING |
结局链 + 尾声回响(epilogue),结算态 |
bgm/ending |
settle |
MENU |
模态覆盖层(任意叙事态下滑呼出,暂停一切链) |
bgm/menu |
menu |
2.2 转移图(文本)
- INTRO/RING/ONAIR/BRIDGE/ENDING 均为节点链驱动(storyline playNode 模式);
SIGNOFF 是 ONAIR 的最后一个节点组(◆p9),不单设状态。
- BRIDGE 自动链 ≤12s:口碑跨档则播 rep_*(播报真实档位,账目真实),随后直接进下一通 RING。
- MENU 打开时
guide.disarm() + 取消一切看门狗/自动链;关闭后重播当前节点(局面复述,storyline closeMenu 先例)。
2.3 每状态手势映射(6+2 全覆盖,零豁免)
| 手势 |
INTRO |
RING |
ONAIR |
BRIDGE |
ENDING |
MENU |
| 上滑 |
帮助+状态,随后重播当前段 |
状态播报(第几通+口碑档),留在 RING |
帮助+状态,随后重播当前节点 |
帮助+状态,自动链继续 |
帮助+状态(结局名+口碑) |
帮助(sys_help) |
| 下滑 |
模态菜单 |
模态菜单 |
模态菜单 |
模态菜单 |
模态菜单 |
关闭菜单 |
| 左滑 |
无效 → error 震+gd_invalid |
无效 → gd_invalid |
抉择=左(走心) |
无效 → gd_invalid |
无效 → gd_invalid |
无效 → error 震 |
| 右滑 |
无效 → gd_invalid |
无效 → gd_invalid |
抉择=右(克制);存档询问态=新夜班 |
无效 → gd_invalid |
无效 → gd_invalid |
无效 → error 震 |
| 单击 |
播放中=跳过本句;链末=进 RING |
接听 |
播放中=跳过本句(p* 不可跳);链末=继续 |
跳过本句(自动链) |
再来一局(新夜班) |
循环切换选项 |
| 双击 |
重听当前段 |
重听上一通话的收尾句 |
重听当前节点台词(含抉择提示) |
重听幕间链 |
重听结局链 |
确认执行(菜单约定覆盖重听) |
| 长按 |
引擎接管:旁白临时 1.7×(12 号 §2.2,叙事类不豁免) |
同左 |
同左 |
同左 |
同左 |
同左(快进菜单语音) |
| 长按松开 |
引擎接管:恢复 settings.speed |
同左 |
同左 |
同左 |
同左 |
同左 |
- 豁免登记:无。六个基础手势全部符合全局宪法默认语义(单击=确认/继续/接听,宪法 §5.1 原文即含「接听」);菜单走 §5.2 R3 模态约定(单击循环+双击确认),不引用 rpg 探索态豁免。
- 抉择 prompt(voice/p1..p9)播放中单击不可跳、左右滑缓存
pendingChoice 播完即生效(storyline 盲玩公平先例)。
- 无效手势零静默:所有「无效」格均有 error 震 + gd_invalid(onceKey 同局限次,F1.4 idle-only:语音在播时跳过语音只发震)。
3. 抉择树
3.1 全局骨架(来电共享主干,分歧局部化)
分支纪律:每通电话内部分支、通话末尾收敛到导播收尾句 → BRIDGE;跨来电只传三个数据(rep 数值、bond 好感、heardArgument 布尔),不生成组合节点。最坏路径与最好路径节点数相同。
3.2 分通结构(◆=抉择点,〔〕=条件差分)
3.3 分支局部化说明
- 跨来电状态只有三个标量:
rep(数值)、heardArgument(布尔)、bond(5 个 0/1)。没有任何「来电 i 的分支 × 来电 j 的分支」组合节点。
- 唯一的跨来电内容差分:C4 的 w1_say/w1_say2 两组(由 heardArgument 选播)与 C5 的 h1_echo 插播句(由 bond_zhou 选播)——都是节点内选播,不改树形。
- 树规模:132 句台词、约 60 个节点,任一路径实走 8-9 个抉择点(C3 左路/ C4 右路各少一个)。
3.4 四结局达成条件
| 结局 |
条件 |
主题 |
开环钩子 |
| e1 天亮之前 |
rep ≥ 5 且 p9=左(直面) |
城市有了心跳,话筒接住了 |
「明晚零点,接着不眠」 |
| e2 热线常亮 |
rep ≥ 5 且 p9=右(回避) |
满座夜班,留言机红灯未看 |
红灯「灭得很慢」 |
| e3 一个人的频道 |
rep < 5 且 p9=左(直面) |
夜班难,但晚安说出去了 |
「师父,收到了吗」 |
| e4 静音 |
rep < 5 且 p9=右(回避) |
谁也接不住谁的夜班 |
「明晚再说吧」 |
- 尾声回响(P2 回响感):epilogue 对 5 个来电者各播一句 bond ? 救到的后续 : 错过的后续(ep_zhou/deer/kid/wife/han_ok/no),玩家每晚的救与错过都被点名。
4. 数值设计
4.1 变量
| 变量 |
类型 |
初值 |
说明 |
rep(听众口碑) |
int |
0 |
抉择与结果积累,收播时结算档位 |
knot(心结) |
bool |
false |
仅由 p9=左 置 true |
bond_zhou/deer/kid/wife/han |
0/1 |
0 |
只影响尾声回响选播,不影响结局 |
heardArgument |
bool |
false |
p4=右 置 true,C4 消费 |
nights / endings{} |
int / map |
— |
重玩计数与结局收集(存档展示用) |
4.2 抉择计分表(左=走心 / 右=克制)
| 抉择 |
左(走心) |
右(克制) |
备注 |
| p1 哼歌/问路 |
+1 |
0 |
左路老周稳住(首个正反馈) |
| p2 陪等/转接 |
+2(p1=左)/ +1(p1=右) |
+1(p1=左)/ -1(p1=右,砸) |
唯一负分组合,触发归因句 |
| p3 劝上车/还自由 |
+2 |
+1 |
两个选项都是体面的(教学后建立信任) |
| p4 问她/哄睡 |
+1,heardArgument=true |
0 |
左=倾听,右=敷衍即错过线索 |
| p5 真话/善意谎言 |
+1 |
0 |
p5 仅 p4=右 可达 |
| p6 替孩子带话/劝回家 |
+2(heardArgument)/ +1(否则) |
+0(本通结束) |
|
| p7 留在直播/换垫乐 |
+2 |
+1 |
p7 仅 p6=左 可达 |
| p8 陪聊/服务区 |
+1(含危机拍) |
0 |
两路都活着(P4 零死亡) |
| p9 直面/回避 |
knot=解 |
knot=未解 |
不动 rep |
4.3 阈值与导向
- 收播结算:
rep ≥ 5 = 高口碑(e1/e2),rep < 5 = 低口碑(e3/e4)。
- 理论上限 12、全克制路径 0;「多数走心 + 容忍 1-2 次失误」落在 5-8 档——阈值 5 把「今晚是否立住」压在玩家自己的重量上,而非单点失误。
- 口碑跨档被动播报(BRIDGE):进入 ≥5 区间播 rep_high、跌回 1-4 播 rep_mid、≤0 播 rep_low——分档整句、账目真实(A4 资源账目律)。
- 归因真话律落地:每个负向结果节点(z1_bade、k1_lulle、k1_liee、e4 链)的导播收尾句必须指向玩家的实际选择(「地址没报清就转接」「你没听见她在怕什么」),禁止「运气不好」类假归因;成功路径不播任何补救教学(A5)。
5. 音频三层映射
5.1 BGM(状态→曲目,Mureka instrumental,4 首 ≈ $0.12)
| 状态 |
曲目 |
切换 |
Mureka prompt 草稿(英文,seamless loop) |
| INTRO/RING/ONAIR |
bgm/onair |
开局淡入 800ms,全夜班不切;语音 duck 引擎自理 |
"Midnight radio broadcast music bed, warm lo-fi neo-soul chords, soft vinyl crackle, gentle night-drive groove with brushed drums, intimate late-night host mood, no vocals, seamless loop, 60-90 seconds" |
| BRIDGE |
bgm/bridge |
crossfade 800ms 进;进下一通 RING 淡回 onair 600ms |
"Radio interlude pad, sparse felt piano notes over warm analog drone, distant rain mood, lonely but safe, no drums, no vocals, seamless loop, 60-90 seconds" |
| MENU |
bgm/menu |
crossfade 500ms 进;关菜单淡回原状态曲 800ms |
"Late-night radio studio menu pad, warm minor pad with faint tape hiss, static harmony, very sparse, no vocals, seamless loop, 60-90 seconds" + 共性后缀(10 号 §8) |
| ENDING |
bgm/ending |
p9 结果链播完后淡入 1500ms |
"Dawn sign-off theme, soft piano and warm strings, hopeful sunrise after a long night shift, gentle resolution, no vocals, seamless loop, 60-90 seconds" |
bgm=0 时全部静默(引擎约定);menu pad 与 onair 同调性优先。
5.2 SFX 清单(Stable Audio 3,one-shot 真峰值 ≤-3dBFS;amb 循环由游戏层 buffer.loop 实现,escape amb 先例)
| id |
用途 |
Stable Audio prompt(英文) |
sfx/onair_sting |
红灯亮起台标 sting(INTRO/重开) |
"Short broadcast on-air light sting, warm chime with vintage radio click, 1 second" |
sfx/phone_ring |
热线铃(RING 循环体,每遍 ≥4s 间隔) |
"Old studio telephone bell, two-strike bright metallic ring, clean" |
sfx/line_connect |
接听咔哒+线路接通音 |
"Telephone line connect click and short dial tone fade, subtle" |
sfx/line_drop |
挂断/转接忙音 |
"Telephone hang-up click with short busy tone, soft" |
sfx/amb_studio |
直播间底噪 loop(调音台嗡+空调) |
"Quiet radio studio room tone, console hum and soft air conditioning, seamless loop" |
sfx/amb_rain_city |
C1 老人侧:街道雨夜 loop |
"Heavy rain on city street at night, distant traffic hiss, seamless loop" |
sfx/amb_station |
C2 小鹿侧:深夜车站 loop |
"Empty train station hall at night, distant echo and rolling suitcase, seamless loop" |
sfx/amb_kid_room |
C3 糖糖侧:儿童房 loop |
"Quiet child bedroom at night, soft lamp buzz, muffled house silence, seamless loop" |
sfx/arg_muffled |
C3 线索音:闷住的争吵+摔门(信息承载,k1_b 时起) |
"Muffled argument behind a door, dull thud of door slam, indistinct voices, 4 seconds" |
sfx/amb_street_rain |
C4 陈姐侧:公车站雨 loop |
"Rain at an empty bus stop at night, distant wet road traffic, seamless loop" |
sfx/amb_cab |
C5 老韩侧:车内 loop(引擎+雨刷节奏) |
"Inside a moving truck cab at night, steady engine hum, rhythmic windshield wipers, rain outside, seamless loop" |
sfx/brake_screech |
C5 危机拍:急刹+护栏摩擦 |
"Sudden brake screech on wet asphalt, brief tire friction, 2 seconds" |
sfx/tape_static |
p9 直面:留言机电流声 |
"Old cassette tape static and tape hiss with a click stop, 3 seconds" |
sfx/ui_confirm |
菜单/接听确认(程序合成兜底) |
—(程序合成,同全局先例) |
sfx/ui_menu |
菜单呼出(程序合成兜底) |
—(程序合成) |
- C3/C4 的 amb 是信息层:arg_muffled 在 k1_b 起播 2-3 次,玩家听没听见由 p4 抉择兑现(声音即世界)。
- 预警窗纪律:h1_danger 前 2s 先起 brake 渐强(L2 前置预警),danger 签名与 brake 同窗。
5.3 Voice 划分(详见 15-radio-voice-ids.md)
| 组 |
声部 |
落位 |
数量 |
| 主播(你)HOST |
双性别(female=Serena / male=Uncle_Fu + HOST instruct 常量) |
`voice/female |
male/` |
| 导播/系统/向导 PROD |
双性别(female=Vivian / male=Dylan + PROD instruct 常量) |
`voice/female |
male/` |
| 来电者 NPC ×5 |
平铺单音色(老周=Uncle_Fu / 小鹿=Serena / 糖糖=Vivian / 陈姐=Vivian / 老韩=Eric),不做双性别 |
voice/ 根(平铺回退命中)+ 电话滤波转码 |
29 句 |
- NPC ≤5 个约束满足;玩家自己的声音跟随全局
settings.voice(男/女主播皆可代入),导播耳语是向导人设(L4 角色化系统声)。
- 合计 132 句(≤170 GPU 预算);音频文件 = 103×2 + 29 = 235 条(×2 格式 = 470 文件)。
6. 来电音质感设计(声音即世界的实现)
6.1 引擎能力边界(对照 web-engine-api.md)
| 能力 |
引擎现状 |
方案 |
| voice 运行时 EQ/滤波 |
❌ voice 链 = SoundTouch → gain → voiceBus,无滤波插入点 |
资产侧转码滤波(storyline n03_msg/n05_bcast 的 convert.sh 带通先例):ffmpeg highpass + lowpass + 压缩器 逐来电者定档 |
| voice 逐句声像/空间定位 |
❌ voice 单声道总线 |
不使用;空间感交给 sfx amb(sfx 资产内 pan,escape amb 先例) |
| sfx 循环 |
sfx 即发即弃,无 loop 选项 |
游戏层 buffer.loop=true 或 onEnded 重触发(escape amb_loop 先例) |
| 语音 duck 垫乐 |
✅ 内置(duck 20%,300ms) |
直接依赖 |
| 运行时混响/失真(「信号变差」差分) |
❌ |
降级:用 amb 响度强弱 + 台词停顿节奏表达,不做动态滤波 |
| 变速不变调 |
✅ SoundTouch(settings.speed + 长按 1.7×) |
直接依赖 |
6.2 每位来电者的声音签名(资产侧 ffmpeg 参数)
| 来电者 |
滤波档 |
质感设计 |
背景 amb(sfx 层) |
| 老周 |
highpass 500 + lowpass 2800 + 轻压缩 |
老旧手机,窄而颤抖 |
amb_rain_city |
| 小鹿 |
highpass 400 + lowpass 3400 |
车站手机,干净偏冷 |
amb_station |
| 糖糖 |
highpass 450 + lowpass 3000 |
家用座机,小而近 |
amb_kid_room + arg_muffled |
| 陈姐 |
highpass 420 + lowpass 3100 |
户外手机,风噪 |
amb_street_rain |
| 老韩 |
highpass 300 + lowpass 3400 |
车载蓝牙,宽而轰 |
amb_cab |
- 可读性底线(L2 反模式「写实但不可读」):滤波保持在语音可懂频带内,宁窄勿脏;QC 试听不可读的句子回退未滤波原声——叙事不破,只损失质感(显式降级方案,不阻塞管线)。
- 主播/导播人声不滤波(直播间级音质);「电话里的人」与「身边的人」靠这一层音质差立住,无需任何视觉。
7. 向导层接入
7.1 状态签名 earcon / turn cue
| 时机 |
向导动作 |
说明 |
| INTRO 进入 |
guide.state('explore') |
叙事态签名 |
| 每个抉择点 p* 起播 |
guide.state('choice') |
选项句本身即 turn cue(播完当刻 choiceArmed 即真,W2 契约零插播) |
| RING 铃响 |
guide.turn({haptic:true})(无语音) |
铃声+turn 签名=「轮到你接听」;首次铃前 b_goal 已口播规矩 |
| BRIDGE 进入 |
guide.state('settle') |
喘息/结算签名 |
| ENDING 进入 |
guide.state('settle') |
同上 |
| MENU 打开 |
guide.state('menu') |
菜单签名 |
| C5 危机拍 |
guide.state('danger') |
全局唯一 danger 使用点(刹车前 2s,与 brake_screech 同窗) |
| 无效手势 |
guide.error({voice:'gd_invalid', onceKey}) |
error 震 + full 档语音,idle-only(F1.4) |
- earcon 词表:只用全项目统一 8 音(menu/explore/battle/choice/settle/danger/turn/nudge),零新增;battle 本游戏不用。开场台词自然教 3 个最高频签名(铃=接听、短促提示音=轮到你选择、上滑/下滑口令);settle/menu 首遇即学;逐签名口播教学列 v2 候选(14 号 §4 同款)。
7.2 看门狗参数(秒;任何手势经 guide.disarm 复位;guide=off 全不挂)
| 等待点 |
full 档 |
standard 档 |
级别内容 |
礼仪 |
| RING 无接听 |
[10, 25] |
[10, 25](onEscalate 游戏层补播) |
① gd_ring1「热线在闪。单击,接听。」② gd_ring2「灯还亮着,听众在等。」 |
两级不同句(绝不逐字重播);两级用尽只剩铃声循环,永不惩罚 |
| ONAIR/INTRO/BRIDGE 叙事链末等待 |
[25, 50] |
[13, 30](同 storyline P2-3 先例) |
① gd_continue ② gd_again |
storyline F1.1 同款,两级后彻底静默 |
| 抉择点 p* 播完无操作 |
[25, 50] |
[25, 50] |
重播当前 p* 提示句本身(复述选项=合法帮助,13 号 §16 N3 先例) |
两级后静默;空点单击=重挂计时 |
| ENDING 结算等待 |
[25, 50] |
[13, 30] |
gd_continue / gd_again |
结算单击即重开,不阻拦 |
- 向导插播一律
queue:true,绝不顶掉在播对白;error/watchdog 类语音 idle-only(在播则跳过且过期丢弃,F1.4)。
- 单句语音看门狗(资产缺失兜底):30s 超时且
isVoicePlaying()===false 才自动续链,长句续约不掐(storyline N1 先例照抄)。
8. 存档设计
8.1 进度 data 结构(PUT /api/progress {game:'radio'})
- 写入时机:每通电话 RING 进入、每个抉择结算后、ENDING 进入、菜单「保存并回大厅」。
- 断点续玩:登录/本地读到存档 →
sys_continue_ask(单击=继续上次 / 右滑=新夜班);继续 = 恢复 phase+node 并重播当前节点链 = 一句话局面复述(L5:恢复成本游戏承担);重进 ≤5s 可玩。
- summary 字符串格式:进行中
第N通·<来电者> · 口碑±X · 结局K/4;已收播 已收播·<结局名> · 结局K/4(大厅卡片直接显示,人话)。
9. playtest 断言清单(登记 13 号文档 radio 节,格式同 §1)
关键路径(E1 全程,≤60 次手势):
- 启动遮罩点按 → INTRO 链(b_open 起,voice 层有播放记录)→ 链完自动进 RING(c1),turn 事件与铃声断言。
- RING 单击接听 → ONAIR(c1):p1 左 → p2 左 → z1_ok1 → BRIDGE(rep 断言 +1+2=3)→ RING(c2)。
- 依序走 p3 左 / p4 右 / p5 左 / p6 左 / p7 左 / p8 左(断言每步后 rep 与 §4.2 一致;p4 右后
heardArgument === true 且 C4 播 w1_say 组)。
- p9 左 → knot=true → ENDING(e1):结局链 + ep_lead + 5 条 ep_*_ok → 结算态单击重开回 INTRO。
边界断言:
- 四结局可达:剧本 B(路径同上但 p9 右)→ e2;剧本 C(p1右 p2左 p3右 p4右 p5右 p6右 p8右 p9左,rep=3)→ e3;剧本 D(p1右 p2右 p3右 p4左 p6右 p8右 p9右,rep=0)→ e4;各局 rep 终值与设计表一致。
- 双击重听:ONAIR/ENDING 播完态双击 → 当前节点链同 id 序列重播;重播期间单击=跳本句(nodeId 不变),播完回原等待态。
- 菜单返回不丢节点:ONAIR 中下滑开菜单 → 上滑听帮助 → 双击「回到直播」→ phase/node 不变、当前节点重播;菜单内单击循环顺序 = [回到直播, 重新开始今晚, 保存并回大厅];确认回大厅 → sys_menu_lobby 播出且 PUT/localStorage 有最新 data。
- 中断复述:杀进程重进(存档 node=k1_asko)→ sys_continue_ask → 单击 → 重播该节点链;上滑播 sys_help+prog_c*+rep_* 链(顺序断言)。
- 看门狗:RING 快进 10s → gd_ring1、25s → gd_ring2(断言第二级 id ≠ 第一级 id,绝不逐字重播;两级后仅铃声);ONAIR 链末 25s/50s → gd_continue/gd_again;p* 播完 25s/50s → 当前 p* 重播两次后静默;
settings.guide='off' 时全程零定时器零向导事件。
- pendingChoice:p* 播放中左右滑 → 播完立即生效(不丢、不重复)。
- p 不可跳*:p* 播放中单击 → 不跳句(PROMPT_RE 门)。
- idle-only:向导 gd_* 在对白在播时被跳过且不再补播(F1.4 契约);queue 句排在当前句后(在播句不被截断)。
- 归因真话:剧本 D 中 z1_bade 播出(含真实归因);剧本 A 全程无任何归因/补救句(成功不教学)。
- 资产缺失:屏蔽任意 voice 资产 → 30s 看门狗自动续链到结局,不卡死。
- 数字规范:prog_/rep_ 全部整句,转写审计无运行时拼接碎片。
- 坏音质降级:滤波资产缺失时原声可播(回退路径不断链)。
失败即不通过:任何等待 >5s 无声(静默死局);无效手势零反馈;结局计数与 §3.4 不符。
10. 难度 / 节奏曲线
| 段 |
内容 |
目标时长 |
设计目标 |
| 开场 |
台标 + 一句规矩 + 第一声铃 |
1.5-2 min |
首输入 <60s(链 ~30s 后铃响即接听);规矩一句讲完(P4) |
| C1 老周 |
教学通:2 个二选一,条条有出路 |
~3.5 min |
首个正反馈 ≤3 min(p1 左 → 老周声音稳住 + 导播确认);唯一 -1 组合不扣资源(学习期零惩罚) |
| C2 小鹿 |
单抉择重话术 |
~2.5 min |
节奏呼吸口;建立「两个选项都体面」的信任 |
| C3 糖糖 |
双抉择 + amb 线索埋设 |
~3.5 min |
环境音首次承载剧情(声音即世界);错过线索可被尾声点名 |
| C4 陈姐 |
最复杂:2 抉择 + 条件差分 |
~4 min |
情感高潮;选项句最长(p7 ≤40s 播完) |
| C5 老韩 |
高潮:危机拍(danger+急刹,前置 2s 预警) |
~4 min |
紧张峰值但零死亡、零倒计时(P4);h1_echo 兑现 C1 回响 |
| 签收+结局 |
p9 + 结局链 + 5 句回响 |
~3 min |
心结抉择 + epilogue 回响感(P2);开环钩子拉下一局 |
- 合计 ≈ 21-22 min(15-25 min 达标);每 30-60s 一个反馈颗粒(导播短句/结果句/口碑档),无沉默尴尬期(P3)。
- 难度曲线 = 信息复杂度(1 抉择 → 条件差分 → 双方对峙 → 生死时速),操作复杂度恒定(永远二选一)——P4「规则一遍听懂」由恒定操作面保证。
11. 自检清单
| # |
硬约束 |
落地 |
✓ |
| 1 |
FTUE 首输入 <60s |
INTRO 链 ~30s 后第一声铃即接听(§10) |
☐ |
| 2 |
FTUE 首个正反馈 ≤3min |
p1 左 outcome ~2.5min(z1_songo+z1_sdone) |
☐ |
| 3 |
学习期零惩罚 |
C1 全组合不扣资源;RING 不接听无超时惩罚 |
☐ |
| 4 |
失败三问归因 + 归因真话律 |
z1_bade/k1_liee/e4 链指向实际选择;§4.3 禁假归因 |
☐ |
| 5 |
手势宪法(6 手势默认语义,零豁免) |
§2.3 全覆盖;单击=确认/继续/接听、菜单走 §5.2 模态约定 |
☐ |
| 6 |
长按豁免显式登记 |
无豁免:长按=引擎快进(12 号叙事类不豁免),§2.3 登记在案 |
☐ |
| 7 |
向导层:状态签名/turn cue/看门狗 |
§7.1/§7.2;RING 两级不同句,绝不逐字重播 |
☐ |
| 8 |
插播一律 queue 不顶对白;纠错 idle-only 过期丢弃 |
§7.2(F1.4 契约) |
☐ |
| 9 |
提示永不逐字重复 |
gd_ring1≠gd_ring2;gd_continue→gd_again;抉择点复述 p* 为 13 号 §16 先例 |
☐ |
| 10 |
成功后不灌补救教学 |
归因句只挂负向结果(§9 断言) |
☐ |
| 11 |
单次播报 ≤3 选项 |
全部抉择恒 2 选项 |
☐ |
| 12 |
并发声流 ≤2 |
voice+amb 两流;无第二 voice 通道 |
☐ |
| 13 |
earcon ≤7 个、显式教一次、语义靠语音 |
只用统一 8 音中的 6 个(battle 不用),零新增;选项内容全语音 |
☐ |
| 14 |
台词一句一文件整句、≤40 字、数字分档整句、无运行时拼接 |
voice-ids 全表 ≤39 字;prog/rep 分档整句 |
☐ |
| 15 |
多音字规避(重新听)、同游戏同 instruct 常量、无语速词 |
voice-ids 头部注记与 instruct 常量表 |
☐ |
| 16 |
P1:可打断/重听、status key、菜单规整、不当白痴 |
skipVoice/doubletap/上滑状态链/模态菜单;成功零重复教学 |
☐ |
| 17 |
P2:头 3 分钟见效、结局回响 |
C1 即时改变对方情绪;ep_* 救/错过点名 |
☐ |
| 18 |
P3:单手免视、一句话复述局面、3min 反馈颗粒 |
全手势免视;重进重播当前节点;BRIDGE 口碑播报 |
☐ |
| 19 |
P4:规则一遍懂、高容错、无高压倒计时、NPC 语速感 |
一句规矩;零死亡零倒计时;NPC 句 ≤18 字+逗号停顿(不用 instruct 语速词,禁令遵守) |
☐ |
| 20 |
抉择树 4 结局可走通 |
§9 剧本 A-D 四条实路径 + rep 断言 |
☐ |
| 21 |
单局 15-25 min |
§10 曲线 ≈21-22 min |
☐ |
| 22 |
分支不爆炸 |
跨来电仅 3 标量;树 ~60 节点固定深度(§3.3) |
☐ |
| 23 |
台词 ≤170 句、NPC ≤5 |
132 句 / 5 个来电者(voice-ids 账目) |
☐ |
| 24 |
引擎边界诚实 |
§6.1 能力表:做不到的写明资产侧/降级方案 |
☐ |
实现核对(测试 agent 登记)
断言实现:web/radio/test/playtest.mjs,193 条,tools/node/bin/node web/radio/test/playtest.mjs
退出码 0 全绿。会话剧本:web/harness/sessions/radio.mjs(session 主线完整弧线 +
sessionStress 乱按压力),经 tools/node/bin/node web/harness/transcribe.mjs radio [--session sessionStress] 跑通(transcribe.mjs 已登记 radio/支持 --session 选剧本)。
回归:六游戏 playtest + guide.test 全绿不受影响。
§9 关键路径逐条
| # |
断言 |
状态 |
| 1 |
遮罩点按 → INTRO 链(b_open 起)→ 链完自动进 RING(c1),turn 事件与铃声 |
✅已断言(INTRO 四句逐句计数 + turn 事件 + phone_ring 循环) |
| 2 |
单击接听 → p1 左 → p2 左 → z1_ok1 → BRIDGE(rep=1+2=3)→ RING(c2) |
✅已断言 |
| 3 |
p3 左/p4 右/p5 左/p6 左/p7 左/p8 左逐点 rep 与 §4.2 一致;p4 右 heardArgument=true 且 C4 播 w1_say 组 |
✅已断言 |
| 4 |
p9 左 → knot=true → ENDING(e1) + ep_lead + 5 条 ep_*_ok → 结算单击重开回 INTRO |
✅已断言(含手势预算 55≤60) |
§9 边界断言逐条
| 断言 |
状态 |
| 四结局可达(剧本 B→e2 / C→e3 / D→e4,rep 终值与设计表一致) |
✅已断言(另补剧本 E:全左但 p4 左 → heardArgument=false → C4 播 w1_say2 组、rep=9,补齐 w1_say/w1_say2 双差分覆盖;剧本 C/D 额外断言带话差分不播与 ep_deer_no) |
| 双击重听:ONAIR 播完态/ENDING 播完态同 id 序列重播,重播期间单击=跳句 |
✅已断言 |
| 菜单返回不丢节点;单击循环顺序 [回到直播, 重新开始今晚, 保存并回大厅];确认回大厅播 sys_menu_lobby 且 localStorage 有最新 data |
✅已断言(含报名 queue 零截断顺序断言) |
| 中断复述:存档 node=k1_asko → sys_continue_ask → 单击恢复 → 重播节点链;上滑 sys_help→prog_c3→rep_mid 顺序 |
✅已断言(另补 ASK 右滑=新夜班) |
| 看门狗:RING 10s/25s 两级不同句、两级后仅铃声;ONAIR 链末 25s/50s gd_continue/gd_again;p* 25s/50s 复述两级后静默;ENDING 25s/50s;guide=off 全程零定时器零向导语音 |
✅已断言(full 档事件钩子计数,choice 签名 ×9) |
| pendingChoice:p* 播放中滑动感存、播完即生效(不丢不重复) |
✅已断言 |
| p* 不可跳(PROMPT_RE 门) |
✅已断言 |
| idle-only:对白在播时纠错跳过且不占 onceKey(F1.4);queue 句不顶在播句 |
✅已断言(skipped 事件 + 补播对照 + 菜单报名顺序) |
| 归因真话:剧本 D 播 z1_bade;剧本 A/E 全程零归因/补救句 |
✅已断言(含 k1_lulle/k1_liee 挂点) |
| 资产缺失:屏蔽 voice → 30s 看门狗续链到结局不卡死 |
✅已断言 |
| 数字规范:prog_/rep_/gd_ring* 分档整句字面量、无运行时拼接 |
✅已断言(静态审计) |
| 坏音质降级:滤波资产缺失回退原声 |
合并(回退是资产侧转码工序,代码无分支;NPC 性别目录→平铺回退由静态审计覆盖) |
| 失败即不通过(>5s 无声死局 / 无效手势零反馈 / 结局计数与 §3.4 不符) |
✅已断言(无效手势 error 震逐次计数;四结局矩阵即计数核对) |
本轮修复的实现 bug(web/radio/game.js)
- ONAIR 叙事链末不等待(规格 vs 实现最大偏差):
_onNodeSeqDone 无条件 advance(node.next),
节点表的 auto:true 标志(INTRO 链/危机拍/签收链/p9 结果链)从未被读取——与 §2.3「链末=继续」、
§7.2 ONAIR 链末看门狗行、§9「ONAIR 链末 25s/50s」及文件头注释均不符。修复:仅 node.auto 或
@x 过场转移自动衔接,其余 ONAIR 叙事节点链末等待单击并挂 gd_continue/gd_again 看门狗。
- longpress 误报无效:gesture.js 把 longpress/longpressend/tapcancel 派发进 onGesture,RING/
BRIDGE/ENDING/MENU/ASK 的 default 分支把它们当无效手势报 gd_invalid/error 震——§2.3 长按行是
「引擎接管快进」,全状态合法。修复:仅 left/right(ASK 另含 up/down/doubletap)报无效。
- 菜单单击循环的引擎队列竞态:
audio.play(item, {queue:true}) 在引擎 _finishVoice 中先跑
onComplete(游戏链播下一句)后 shift 队列,排队报名会把 sys_menu→sys_menu_resume 链播句顶掉。
修复:菜单报名改游戏侧 _menuQueue + _drainMenuQueue() 排水,当前报名播完接播、零截断。
- RING 双击首通不报无效:INTRO 链节点完成会写
lastCloser,首通双击重听了 b_goal 而未按开发
注记「首通前无上一通 → 无效手势零静默」报错。修复:callN <= 1 时报无效。
- ENDING 上滑缺口碑:§2.3 ENDING 上滑=「帮助+状态(结局名+口碑)」,原实现只播 sys_help+结局
主句。修复:链尾补
TIER_VOICES[tierOf(rep)]。
- stop() 后中断链复活:被 stopVoice 掐断的
_playSeq 链可经 30s 单句看门狗「资产缺失」路径续链,
已拆除实例会走到 enterRing 留下无人接听的永久铃声定时器。修复:stop() 先 seqToken++ 作废在途链。
- newNight 不清抉择武装:菜单重开/结算重开到新夜班首链起播前,choiceArmed/pendingChoice 残留,
窗口期手势会在旧节点上误抉择。修复:newNight() 显式清零。
- _ringOnce 双链防护:closeMenu(RING)/_resume 直接调 _ringOnce 不清旧计时器,可能双铃声链。
修复:_ringOnce 先 clearTimeout 旧 ringTimer。
规格歧义 / 实现取舍(登记不改设计)
- §4.2 表「p4 问她/哄睡」选项列序与 §3.2 树形(左=哄睡/右=问她)相反,实现按 §3.2 + p4 提示句原文
(game.js 已注记);断言按 §9 口径(p4 右 → heardArgument=true,+1)。
sys_saved(15b G0 组,触发=存档写入后)未接线:自动存档每局 ≥7 次,逐次播报会持续打断节目流;
菜单「保存并回大厅」已有 sys_menu_lobby 确认句。列遗留(如需可挂菜单「重新开始今晚」确认后)。
- INTRO 上滑=「帮助+状态」:开播前无通号/口碑可报,实现只播 sys_help 后重播当前段(RING 起状态链完整)。
- 存档 data 在 §8.1 字段外增加
call/ending/lastCloser(断点续玩与 RING 双击需要,超集兼容)。
- ASK 右滑新夜班的
nights 按新实例计数(不继承被拒绝存档的 nights),与结算重开 +1 口径不一致,
无游戏性影响,列遗留。
资产生产缺口(列遗留,本轮不修)
→ 已闭环(2026-09-02 晚):12 件 SA3 条目全部补产(onair_sting / phone_ring / line_connect /
line_drop / amb_rain_city / amb_station / amb_kid_room / arg_muffled / amb_street_rain / amb_cab /
brake_screech / tape_static;amb_studio 文档有、未接线,产出备用),check_all_assets radio 254+254
全过、playtest 221 断言全绿。详见 research/new8/batch1-assets-report.md §8。
- §5.2 SFX 15 件中 12 件 Stable Audio 3 条目未产出(
gen/gen_sfx.py 头注:HF token 未配置跳过):
onair_sting / phone_ring / line_connect / line_drop / amb_rain_city / amb_station / amb_kid_room /
arg_muffled / amb_street_rain / amb_cab / brake_screech / tape_static。缺失走引擎 console.error
静默降级(§6.1 降级方案),玩法与断言不受阻;playtest 静态审计锁定「缺失集合 ⊆ 该清单」,
资产补齐后断言自动收敛。已产出:ui_confirm / ui_menu(程序合成)+ 132 句 voice + 4 首 bgm
(ogg+m4a 双格式零缺失,见静态审计)。
R2 修复回归(round1 synthesis F1-F5 + V1/V2,2026-09-02)
证据源:research/new8/radio/round1/{synthesis,live-player}.md。断言全部落在
web/radio/test/playtest.mjs「R2 修复回归」块 + 主路径/看门狗/静态审计改断;
playtest 断言总数 R1=193 → R2=221(+28),全绿。
| 项 |
修复(文件 → 行为) |
回归断言(✅=已断言) |
| F1 (P1) |
game.js:onGesture 入口 guide.disarm() 拆掉等待态看门狗后,无效手势/引擎接管手势分支按态立即重挂——ONAIR/INTRO 链末(_inStoryWait() 与 _onNodeSeqDone 等待分支同口径)、ASK 空闲、RING 空闲(_rearmRingWatchdog)、ENDING 结算态;BRIDGE 本无 guide 看门狗(restTimer 为游戏层),不涉及 |
✅ 链末等待中发无效手势,gd_continue 25s 整准点、gd_again 50s 整准点(旧实现 34.2s 零事件);✅ RING 无效手势后 gd_ring1 仍 10s 准点;✅ ASK 无效手势后看门狗仍 25s 准点 |
| F2 (P4/P2) |
台词表两处同步改词(先 docs/design/15-radio-voice-ids.md 后 tools/gen_voice_dual.py,两表一致):rep_low「今晚的口碑,有点砸了。」→「今晚的口碑,还没立起来。」(零起步语义,与 rep_high「在听众里立住了」同尺度;rep=0 开播前不再读成「砸了」);重生成 voice/{male,female}/rep_low.{ogg,m4a}(m=1.92s / f=2.32s,--only rep_low → --convert → check_all_assets radio 242+242 全过:双格式配对、零 0 字节、全可解码、性别目录 id 一致) |
✅ 静态审计锁定 rep_low 字面量存在;live 转写复验归 R2 协议第 2 步(重新生成转写) |
| F3 (P2) |
gen/gen_sfx.py 增加 phone_ring 程序合成:440+480Hz 柔和双音一响 1.8s + 静默 3.4s = 5.2s 循环周期烘进文件(≥4s);无缝 loop 工艺 = 逐声道去直流、不做首尾淡化(save_loop)、解码回读校验真峰值 ≤-3dBFS(ogg -3.87 / m4a -3.84dBFS)+ 循环头/尾样本 ≤-60dBFS 边界静音;落 audio/sfx/phone_ring.{ogg,m4a}。game.js RING 接循环垫:引擎 sfx 层无 loop/stop → 游戏侧经 ctx 起循环 BufferSource 持句柄(_ringSrc,guide earcon 同款直用总线),接听/菜单/重开 _stopRing 即停;移除定时重触发(ringTimer/ringJitterMs/RING_INTERVAL_MS) |
✅ 进 RING 循环垫起播且挂起态虚拟时钟可空闲(零重触发定时器,可复现);✅ 单击接听循环垫即停不拖尾;✅ RING 无效手势不拆铃声垫;✅ 两级铃看门狗用尽后循环垫仍在(永不惩罚);✅ 静态审计 phone_ring 出待补清单(Stable Audio 待补 12→11 件) |
| F4 (P2) |
game.js:断点续玩恢复后的首句播完再武装跳句——_resume() 置 _resumeGuard,_playSeq 消费为 _skipLockId(恢复链首句),该句播完/链完/被新链顶掉即解锁;ONAIR 与 ENDING(live 陷阱场景)两态 tap 生效 |
✅ 恢复后首句(w1_say / e4_a)播完前单击不跳句;✅ 首句播完后跳句重新武装(w1_sayo / e4_b 被正常跳过,链照常走) |
| F5 (P2) |
game.js:链末句跳过保护——_isTailSentence():当前句为本节点末句且节点把控制权交出(链末等待单击 / @x 过场转移 / 直入抉择提示)时屏蔽「跳过本句」;自动链中段(b_open/b_rain、危机拍)与 w1_say 组前两句跳句自由保留。与 F1 同提交复核链末状态机:_inStoryWait 与 _onNodeSeqDone 口径一致 |
✅ 交接抉择提示前的末句(w1_saye)单击不可跳、完整播出后照常交接 p7;✅ @ending 过场前末句(d9_facee,live 被吃掉的点题句)单击不可跳、完整播出后自动进 ENDING(e1) |
| V1 |
复核结论:pendingChoice 生效时序无 bug(W2 契约实现正确),按裁定补显式断言关案 |
✅ p* 播放中连点不跳句、不清缓存、不提前生效(rep 未动);✅ 播中再滑覆盖缓存(最后一次为准);✅ 生效时序 = p1 播完 → ui_confirm 结算音 → 结果句;✅ 缓存恰好生效一次(rep=1、缓存清空、不丢不重复) |
| V2 |
复核结论:菜单逻辑无缺失(三项循环 + 回绕正确),按裁定补全项循环断言关案。注:菜单为 3 项(sys_menu + sys_menu_resume/restart/lobby,15-radio.md §实现核对「菜单单击循环顺序」口径)——任务书所写「4 项/sys_menu_save」对应菜单标题句 + 3 菜单项;「保存并回大厅」确认句即 sys_menu_lobby(无 sys_menu_save 资产) |
✅ 单击循环两个整周期逐项可达(resume→restart→lobby→resume→restart→lobby,第三项不再缺席)、6 次回绕模 3 无漂移;✅ 双击确认「保存并回大厅」→ sys_menu_lobby 播出 + leaving 置位 + 存档落盘 |
V3(arg_muffled/brake_screech 等 11 件 SA3 资产)维持「等待 HF token」,接线代码已备,不在本轮。
→ 已闭环(2026-09-02 晚):SA3 12 件补产落地(含 phone_ring 真铃声替换),接线复核零代码改动,
详见 research/new8/batch1-assets-report.md §8。
遗留(R2 收尾时需协同)
web/harness/voice_text.json 的 rep_low 期望文本仍是旧词「有点砸了」(harness 归属协议/
转写侧,本轮改动手清单之外):R2 协议第 2 步重新生成转写前需同步为新词「还没立起来」,
否则 live 复测该句会 MISMATCH。
- dungeon 款全量回归本轮 FAIL:另一 agent 并行改动
web/engine/{audio,gesture}.js 与
web/dungeon/** 进行中(失败点为 dungeon 网格/侧墙与 tut 旁白断言,与 radio 无关);
radio与其余 6 款全绿。
R3 修复回归(round2 synthesis G1-G6 打磨轮,2026-09-02)
证据源:research/new8/radio/round2/{synthesis,live-player,p1-blind,p3-office}.md。
断言全部落在 web/radio/test/playtest.mjs「R3 修复回归」块 + 主路径/中断复述两处顺序断言改写;
playtest 断言总数 R2=221 → R3=246(+25),全绿。未新增台词/资产(G1 选纯音频标记方案,
零 TTS 管线动作;voice_text.json / voice-ids 无改动)。
G5 显式规则登记(P1 要求,F4/F5 隐式行为升格)
规则语句:radio 的跳过公平契约 = 「首播必完整、复播可即掐」——
- 首播必完整(公平底线,玩家没听过的内容不许被抢跑):
- 抉择提示句 p*(voice/p1..p9)首播不可跳(PROMPT_RE 门),播中左右滑仅缓存
pendingChoice、句尾当刻才生效(W2 契约零插播;播中连滑取最后一次);
- 链末关键句(等待单击 /
@x 过场转移 / 直入抉择提示交接前的末句,如蛋糕句、点题句)
首播不可跳(F5 链末句保护);
- ASK 恢复首句例外:断点续玩恢复链本属局面复述(复播),但其首句仍受 F4 跳句锁
保护——首句播完才武装跳句。锁只锁这一句、播完即释放,不得升级为永久保护
(live 局 B + 会话 C 双向验证:恢复链第二句 0.2s 即可跳)。
- 复播可即掐(效率面,已完整交付过的内容不再强制重听):F4 锁只锁首播一句且播完即释放,
锁不随复播重新武装(关菜单局面复述 / 双击重听回放同一句时单击照常跳过);F5 只护交接前
末句本身,链中段句首播复播均可跳;复播期间播中滑动照常预缓存抉择、句尾即生效(V1 断言)。
注:p* 复播(双击重听/看门狗复述)仍不可跳、仍句尾生效——比 P1 建议的「复播即掐即生效」更保守;
R2 live 已将 4 处截断全数归因玩家 tap 且 P1/P3 均接受现状,按代码实际契约登记并断言。
G1-G6 逐条
| 项 |
选型 / 改法(web/radio/game.js) |
回归断言(✅=已断言) |
| G1 帮助链去重 |
选型:纯音频标记(不加新台词)。「只震+earcon+sys_help 现有句」方案被否——sys_help 为单文件 26 字整句,无「首句」子资产,重播即逐字重复,恰是待修问题。实现:_helpSeen(Set,实例级,不跨实例持久化,sys_skip 每实例一次同款口径)按等待点键控(ONAIR/INTRO 按 nodeId、BRIDGE 整段、ENDING 按结局、菜单整段);_helpSpokenOnce:同键第 1 次全量口播,第 2 次起 vibrate('beat') + nudge earcon(复用统一 8 音词表零新增;guide 无公开单发 earcon API,经 _playEarcon 直用,_startRing 直用引擎总线同款先例;guide=off 时只震)。状态句照播(复述状态=合法帮助,P1 先例);新等待点首滑仍全量口令,非全局静默 |
✅ 同一等待点第 2 次上滑不重播 sys_help(ONAIR/ENDING 两态);✅ 状态句照播(prog/rep 各 2 次);✅ beat 轻震标记;✅ nudge earcon;✅ 新等待点(z1_a)首滑仍全量口令 |
| G2 rep_low warning 震 |
R1 P3-3 遗留。原实现 enterBridge 跨档分支 vibrate('warning') 实际不可达(rep 唯一减分组合 p1右+p2右 从 0→-1 不跨档,lastAnnouncedTier 初值即 low)。修:_tierCue() 挂在全部 rep_low 播报位——RING/ONAIR/BRIDGE/ENDING 四处上滑复听,rep_low 整句出口同时手腕有感(BRIDGE 跨档分支保留不动) |
✅ RING 上滑复听 rep_low 附 warning 震(live 局 B t=198.5/497.5 证据位);✅ ONAIR 上滑同款;✅ ENDING 上滑同款 |
| G3 k1_a/k1_b male 回退 |
核查结论:回退正常,非静默,不修。 k1_a/k1_b 为糖糖 NPC 平铺组资产(设计即单音色 Vivian 童声、不做双性别,§5.3);male 设置下引擎候选链 voice/male/k1_a.{ogg,m4a}(404,符合预期)→ voice/k1_a.ogg(200)命中平铺版播出(web-engine-api.md voice 路径解析)。文档注记:live 局 B 观测的「同通电话声线跳变」实为主播声部(随 settings.voice 换声)与糖糖童声(恒定)在同通内交替,属「电话音质感」设计使然;不补双性别资产(违反 NPC ≤5 音色约束与 §5.3 设计) |
✅ male 先尝试性别目录 male/k1_a、k1_b(404 属设计);✅ 平铺版播出(voicePlays 各 1);✅ 零「资产缺失」console.error(真静默则修——实测无需修) |
| G4 空点确认轻震 |
R1 P3-1 / R2 P3-4 遗留。不可跳句(p* 首播 / F5 尾句 / F4 锁)上的单击「点了但没选上」,gesture 层确认震 [20] 游戏侧无法撤销(engine/gesture.js 不在本轮改动清单)——游戏层补一记最轻 beat [8] 形成可区分签名(20→8 连击 ≠ 真确认的单记 20),消解「像点上了」误导;落点 _onStoryGesture / _onEndingGesture 两处 tap 屏蔽分支 |
✅ F4 锁内单击 beat 震;✅ F5 尾句单击 beat 震(「p9 后空点」证据位);✅ p* 不可跳句单击 beat 震(R1 P3-1 证据位) |
| G5 显式规则 |
纯登记 + 断言,无代码改动(F4/F5/V1 现有实现即规则本体);规则语句见上节。新增断言锁「锁不随复播重新武装」(复播可即掐的反向面:保护不得扩大化) |
✅ ASK 恢复首句锁内单击不跳(例外);✅ 复播(关菜单局面复述)首句单击照常跳过;✅ 链末关键句首播不可跳(F5 引用);✅ p* 首播单击不跳(引用);(F4/F4b/F5/F5b/V1 既有断言为规则其余各半) |
| G6 状态链重排 |
P1 两次建议、纯排序改动 → 本轮做。_withHelp() 把帮助句接在状态链尾:「当前通号 → 口碑 → 帮助」。ONAIR/INTRO(有状态时 prog → rep → sys_help)、BRIDGE(下一通 prog → rep → sys_help)、ENDING(结局主句 → rep → sys_help);RING 本就状态在前且无帮助句(§2.3 RING 行),天然满足;MENU 链无通号/口碑状态句,G6 不适用(仅 G1 去重,帮助保持在前)。§2.3 表内「帮助+状态」措辞由本节登记更新(状态在前),正文不改 |
✅ ONAIR 顺序 prog_c1 → rep_low → sys_help;✅ BRIDGE 顺序 prog_c2 → rep_mid → sys_help(原断言改写);✅ ENDING 顺序 e4_a → rep_low → sys_help;✅ 中断复述 ONAIR 顺序 prog_c3 → rep_mid → sys_help(原断言改写) |
顺带补的 SA3 音效接线断言
主路径补 sfx/onair_sting(开场台标)/sfx/line_drop(幕间挂断)/sfx/tape_static(p9 直面)
接线计数断言(arg_muffled ×2 / brake_screech / phone_ring 已有);静态审计
STABLE_AUDIO_PENDING 注释更新为「SA3 12 件已全部补产,清单降级为守卫」。
观察项(按裁定不动代码)
- 复播晚点 +7.6s 一次性事件(live P3-3):一次性观测,未加断言盯防(避免为仪器怪癖加约束)。
- mid-prompt 生效掐选项句尾(live P3-6):设计备注维持,G5 规则已按现状登记。
- harness 丢 tap 仪器怪癖(live P3-5):记入 R3 协议(tap 后 listen 一次确认再重发),非游戏侧。
遗留
- 无新增遗留。G1 去重集合为实例级(重进游戏首滑仍是完整口令)——如 R3 复评希望跨实例
记忆(localStorage 持久化「已听过口令」),列 v2 候选,本轮不加(保持与 sys_skip 每实例
一次同口径,避免老玩家重进被静默)。