- radio:帮助链去重(同等待点二次=beat震+earcon,全量口令38→20)/rep_low警告震/空点轻震/状态链重排(状态在前)/「首播必完整、复播可即掐」显式规则登记/G3核查为设计使然转断言;221→246断言 - dungeon:看门狗双轨制(无操作轨+无进展轨,30/60/90s三级升级到导航接管)/跳教学90s自动滚出(2403s→95s内)/踩楼梯到达即报贴身铁器/band1新词「带回来一点,不多」全管线重录/逃跑长按900ms+提示句;1610→1642断言 - R3 验证(进行中已入库部分):全量回归 9/9 全绿;R3 转写×4;P2/P3/P4 终审×6份(radio 满分档首评/三审,dungeon 4/4.5/3-4 全票愿意玩,无P0);两处独立定址同一残余=漫游型迷路刷不响无进展轨(R4 第一条) - 8套件+guide全绿;Mureka 累计 $0.87/$2.00
540 lines
50 KiB
Markdown
540 lines
50 KiB
Markdown
# 15 · 《不眠频率》(radio) — 深夜电台热线叙事 RPG 设计文档
|
||
|
||
> **给开发的实现要点摘要**
|
||
> 1. 叙事模式(声命线范式):节点链 onComplete 串行 + seqToken 打断作废 + pendingChoice 缓存;复制 `web/storyline/` 结构起步,状态机用 6 宏状态(INTRO/RING/ONAIR/BRIDGE/ENDING + MENU 覆盖层)。
|
||
> 2. 手势零豁免:全部走全局默认映射(单击=继续/接听/跳句、双击=重听、左右滑=抉择、上滑=状态、下滑=模态菜单 §5.2 约定、长按=引擎快进)。
|
||
> 3. 台词依据 [15-radio-voice-ids.md](15-radio-voice-ids.md)(132 句:双性别 103 + 平铺 NPC 29);NPC 人声电话滤波在**资产侧** ffmpeg 带通(storyline n03_msg 先例),引擎零改动。
|
||
> 4. 向导层:storyline 同款看门狗节奏 + RING 专用两级新句提示;`gd_ring1/gd_ring2` 为本游戏新增 gd_* 资产。
|
||
> 5. 章程任务 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(c1) ──单击接听──> ONAIR(c1) ──链完──> BRIDGE ──┤→ RING(c2)
|
||
│铃声循环/看门狗两级,无超时惩罚 │ …(c3,c4 同型)…
|
||
└────────────────────────────────────────────────┘
|
||
ONAIR(c5) ──链完──> BRIDGE ──链完──> SIGNOFF
|
||
│
|
||
└──> ◆p9 抉择 ──> d9 结果链 ──> ENDING(e1..e4 + ep 回响链)
|
||
|
||
任意叙事态(INTRO/RING/ONAIR/BRIDGE/ENDING):下滑 → MENU(模态)
|
||
MENU:单击=循环切换选项 → 双击=确认执行 → 上滑=帮助 → 下滑/「回到直播」项=关闭回原状态(重播当前节点)
|
||
```
|
||
|
||
- 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 全局骨架(来电共享主干,分歧局部化)
|
||
|
||
```
|
||
INTRO: b_open → b_rain → b_rule → b_goal ──> 铃①
|
||
【C1 老周·走丢的老人】教学通 · 2 抉择 ──> BRIDGE ──> 铃②
|
||
【C2 小鹿·想退票的年轻人】单抉择 · 呼吸口 ──> BRIDGE ──> 铃③
|
||
【C3 糖糖·失眠的孩子】2 抉择 · 埋 heardArgument ──> BRIDGE ──> 铃④
|
||
【C4 陈姐·争吵的夫妻】2 抉择 · 消费 heardArgument ──> BRIDGE ──> 铃⑤
|
||
【C5 老韩·雨夜司机】高潮 · 1 抉择 + 危机脚本拍 ──> 签收 ◆p9(心结)──> ENDING
|
||
```
|
||
|
||
分支纪律:**每通电话内部分支、通话末尾收敛**到导播收尾句 → BRIDGE;跨来电只传三个数据(rep 数值、bond 好感、heardArgument 布尔),不生成组合节点。最坏路径与最好路径节点数相同。
|
||
|
||
### 3.2 分通结构(◆=抉择点,〔〕=条件差分)
|
||
|
||
```
|
||
【C1 老周】
|
||
铃① → z1_ring_in → z1_a → z1_b → ◆p1
|
||
├左 哼老歌: z1_song → z1_songo → z1_sdone ─┐
|
||
└右 问路牌: z1_ask → z1_asko → z1_adone ───┤
|
||
▼
|
||
◆p2
|
||
├左 陪他等: z1_stay → z1_stayo → z1_staye ─┐
|
||
└右 转接: z1_pol → z1_polo → z1_pole ────┤ (仅 p1=右 时 p2=右 改走 z1_bad → z1_bade 归因)
|
||
▼
|
||
z1_ok1(首通确认,全局一次)→ BRIDGE
|
||
|
||
【C2 小鹿】
|
||
铃② → l1_in → l1_a → l1_b → ◆p3
|
||
├左 劝上车: l1_go → l1_goo → l1_goe ──┐
|
||
└右 还自由: l1_free → l1_freeo → l1_freee ─┴→ BRIDGE
|
||
|
||
【C3 糖糖】
|
||
铃③ → k1_in → k1_a → k1_b(垫 arg_muffled 争吵声)→ ◆p4
|
||
├左 哄睡: k1_lull → k1_lullo → k1_lulle ──────────────┐(错过线索,本通结束)
|
||
└右 问她: k1_ask → k1_asko → ◆p5 │
|
||
├左 真话: k1_truth → k1_trutho → k1_truthe ────────┤(heardArgument=true)
|
||
└右 谎言: k1_lie → k1_lieo → k1_liee ──────────────┴→ BRIDGE
|
||
|
||
【C4 陈姐】
|
||
铃④ → w1_in → w1_a → w1_b → ◆p6
|
||
├左 替孩子带话:〔heardArgument ? w1_say 组 : w1_say2 组〕→ ◆p7
|
||
│ ├左 留在直播: w1_air → w1_airo → w1_aire ─┐
|
||
│ └右 换垫乐私聊: w1_priv → w1_privq → w1_prive ─┤
|
||
└右 劝她回家: w1_home → w1_homeo → w1_homee ──────────┴→ BRIDGE(右路本通结束,无 p7)
|
||
|
||
【C5 老韩】高潮
|
||
铃⑤ → h1_in → h1_a → h1_b → ◆p8
|
||
├左 陪聊闺女: h1_talk → h1_talko → 【危机拍: danger 签名+brake_screech(前置2s)】
|
||
│ → h1_danger → h1_dango → h1_talke ─┐
|
||
└右 服务区: h1_stop → h1_stopo → h1_stope ────────┤
|
||
〔bond_zhou=true 时插播 h1_echo 回响句〕 ▼
|
||
BRIDGE → g9_in → ◆p9
|
||
├左 直面(播出老主播的晚安): d9_face → d9_facee → knot=解
|
||
└右 回避(照常收播): d9_avoid → d9_avoide
|
||
▼
|
||
ENDING: e*_a → e*_b(按 rep×knot 四选一)→ ep_lead → ep_<caller>×5(按 bond 选播)
|
||
```
|
||
|
||
### 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/` | 21 句 |
|
||
| 导播/系统/向导 PROD | 双性别(female=Vivian / male=Dylan + PROD instruct 常量) | `voice/female|male/` | 82 句 |
|
||
| 来电者 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'})
|
||
|
||
```json
|
||
{
|
||
"schema": 1,
|
||
"updatedAt": "2026-09-01T12:00:00Z",
|
||
"summary": "第3通·失眠的孩子 · 口碑+2 · 结局1/4",
|
||
"phase": "ONAIR",
|
||
"node": "k1_asko",
|
||
"rep": 2,
|
||
"knot": false,
|
||
"heardArgument": true,
|
||
"bond": { "zhou": 1, "deer": 1, "kid": 0, "wife": 0, "han": 0 },
|
||
"endings": { "e2": true },
|
||
"nights": 3
|
||
}
|
||
```
|
||
|
||
- 写入时机:每通电话 RING 进入、每个抉择结算后、ENDING 进入、菜单「保存并回大厅」。
|
||
- 断点续玩:登录/本地读到存档 → `sys_continue_ask`(单击=继续上次 / 右滑=新夜班);继续 = 恢复 phase+node 并**重播当前节点链** = 一句话局面复述(L5:恢复成本游戏承担);重进 ≤5s 可玩。
|
||
- summary 字符串格式:进行中 `第N通·<来电者> · 口碑±X · 结局K/4`;已收播 `已收播·<结局名> · 结局K/4`(大厅卡片直接显示,人话)。
|
||
|
||
## 9. playtest 断言清单(登记 13 号文档 radio 节,格式同 §1)
|
||
|
||
**关键路径**(E1 全程,≤60 次手势):
|
||
1. 启动遮罩点按 → INTRO 链(b_open 起,voice 层有播放记录)→ 链完自动进 RING(c1),turn 事件与铃声断言。
|
||
2. RING 单击接听 → ONAIR(c1):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。
|
||
|
||
**边界断言**:
|
||
- **四结局可达**:剧本 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)
|
||
|
||
1. **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 看门狗。
|
||
2. **longpress 误报无效**:gesture.js 把 longpress/longpressend/tapcancel 派发进 onGesture,RING/
|
||
BRIDGE/ENDING/MENU/ASK 的 default 分支把它们当无效手势报 gd_invalid/error 震——§2.3 长按行是
|
||
「引擎接管快进」,全状态合法。修复:仅 left/right(ASK 另含 up/down/doubletap)报无效。
|
||
3. **菜单单击循环的引擎队列竞态**:`audio.play(item, {queue:true})` 在引擎 `_finishVoice` 中先跑
|
||
onComplete(游戏链播下一句)后 shift 队列,排队报名会把 sys_menu→sys_menu_resume 链播句顶掉。
|
||
修复:菜单报名改游戏侧 `_menuQueue` + `_drainMenuQueue()` 排水,当前报名播完接播、零截断。
|
||
4. **RING 双击首通不报无效**:INTRO 链节点完成会写 `lastCloser`,首通双击重听了 b_goal 而未按开发
|
||
注记「首通前无上一通 → 无效手势零静默」报错。修复:`callN <= 1` 时报无效。
|
||
5. **ENDING 上滑缺口碑**:§2.3 ENDING 上滑=「帮助+状态(结局名+口碑)」,原实现只播 sys_help+结局
|
||
主句。修复:链尾补 `TIER_VOICES[tierOf(rep)]`。
|
||
6. **stop() 后中断链复活**:被 stopVoice 掐断的 `_playSeq` 链可经 30s 单句看门狗「资产缺失」路径续链,
|
||
已拆除实例会走到 enterRing 留下无人接听的永久铃声定时器。修复:stop() 先 `seqToken++` 作废在途链。
|
||
7. **newNight 不清抉择武装**:菜单重开/结算重开到新夜班首链起播前,choiceArmed/pendingChoice 残留,
|
||
窗口期手势会在旧节点上误抉择。修复:newNight() 显式清零。
|
||
8. **_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 的跳过公平契约 = **「首播必完整、复播可即掐」**——
|
||
|
||
- **首播必完整**(公平底线,玩家没听过的内容不许被抢跑):
|
||
1. 抉择提示句 p*(voice/p1..p9)首播不可跳(PROMPT_RE 门),播中左右滑仅缓存
|
||
`pendingChoice`、句尾当刻才生效(W2 契约零插播;播中连滑取最后一次);
|
||
2. 链末关键句(等待单击 / `@x` 过场转移 / 直入抉择提示交接前的末句,如蛋糕句、点题句)
|
||
首播不可跳(F5 链末句保护);
|
||
3. **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 每实例
|
||
一次同口径,避免老玩家重进被静默)。
|