// story.js — 消息数据 v2(VoxCPM2 语音 + 多轮对话) // // 选择方向的情感含义(全局一致): // 单击(tap) → 简单回应 // 双击 → 不回复 / 跳过 // 右滑(right) → 温暖 / 接受 / 开放 // 左滑(left) → 冷淡 / 拒绝 / 疏远 // 上滑(up) → 好奇 / 主动 / 追问 // 下滑(down) → 沉默 / 退缩 // // 每个 choice 结构: // linxia: 林夏回应的音频路径(null/缺省 = 沉默) // npcReact: NPC反应音频数组(缺省 = 无反应) // isSilence: 沉默选项标记 // state: 剧情状态变更 // profile: 画像分数变更 // followUp: 可选的下一轮对话 { choices: { ... } } const A = '../audio/mp3'; const T = `${A}/tts`; // VoxCPM2 生成的所有语音 const MUS = `${A}/music`; const SFX = `${A}/sfx`; const AMB = `${A}/ambience`; // ── 快捷函数 ── const npc = f => `${T}/${f}`; // NPC Round 1 台词 const lx = f => `${T}/${f}`; // 林夏回应 const rx = f => `${T}/${f}`; // NPC 反应 const MESSAGES = [ // ══════════════════════════════════════════ // 01 · 妈妈 · 电话 // ══════════════════════════════════════════ { id: 1, type: 'call', sender: 'mom', senderName: '妈妈', ringtone: `${MUS}/lv11_ring_mom_v1.mp3`, audio: [npc('lv11_2103_mom_call_01_wav_v1.mp3')], autoReply: lx('v2_msg01_linxia_auto_wav_v1.mp3'), stateOnAnswer: { MOM_LINK: true }, profileOnMiss: { avoidance: 2 }, profileOnAnswer: { engagement: 1 }, }, // ══════════════════════════════════════════ // 02 · 小美 · 语音 ×3(教学关) // ══════════════════════════════════════════ { id: 2, type: 'wechat_voice', sender: 'xiaomei', senderName: '小美', audio: [ npc('lv11_2115_xiaomei_voice_01_wav_v1.mp3'), npc('lv11_2115_xiaomei_voice_02_wav_v1.mp3'), npc('lv11_2115_xiaomei_voice_03_wav_v1.mp3'), ], choices: { right: { linxia: lx('v2_msg02_linxia_warm_wav_v1.mp3'), npcReact: [rx('v2_msg02_react_warm_wav_v1.mp3')], profile: { engagement: 2 }, }, left: { linxia: lx('v2_msg02_linxia_cold_wav_v1.mp3'), npcReact: [rx('v2_msg02_react_cold_wav_v1.mp3')], profile: { avoidance: 1 }, }, up: { linxia: lx('v2_msg02_linxia_curious_wav_v1.mp3'), npcReact: [rx('v2_msg02_react_curious_wav_v1.mp3')], profile: { engagement: 1 }, followUp: { choices: { right: { linxia: lx('v2_msg02_r3_linxia_curious_wav_v1.mp3'), npcReact: [rx('v2_msg02_r3_react_wav_v1.mp3')], profile: { nostalgia: 1 }, }, }, }, }, down: { isSilence: true, npcReact: [rx('v2_msg02_react_silent_wav_v1.mp3')], profile: { avoidance: 2 }, }, tap: { linxia: lx('v2_msg02_linxia_tap_wav_v1.mp3'), npcReact: [rx('v2_msg02_react_tap_wav_v1.mp3')], }, }, }, // ══════════════════════════════════════════ // 03 · 外卖小哥 · 电话 // ══════════════════════════════════════════ { id: 3, type: 'call', sender: 'delivery', senderName: '外卖小哥', ringtone: `${MUS}/lv11_ring_delivery_v1.mp3`, audio: [npc('lv11_2120_delivery_call_01_wav_v1.mp3')], autoReply: lx('v2_msg03_linxia_auto_wav_v1.mp3'), profileOnMiss: { avoidance: 1 }, profileOnAnswer: { engagement: 1 }, }, // ══════════════════════════════════════════ // 04 · 阿哲 · 语音(前男友要东西) // ══════════════════════════════════════════ { id: 4, type: 'wechat_voice', sender: 'azhe', senderName: '阿哲', audio: [npc('lv11_2130_azhe_voice_01_wav_v1.mp3')], choices: { right: { linxia: lx('v2_msg04_linxia_warm_wav_v1.mp3'), npcReact: [rx('v2_msg04_react_warm_wav_v1.mp3')], profile: { engagement: 2 }, followUp: { // 阿哲问"你呢?"后 choices: { right: { linxia: lx('v2_msg04_r3w_linxia_warm_wav_v1.mp3'), npcReact: [rx('v2_msg04_r3w_react_warm_wav_v1.mp3')], }, left: { linxia: lx('v2_msg04_r3w_linxia_cold_wav_v1.mp3'), npcReact: [rx('v2_msg04_r3w_react_cold_wav_v1.mp3')], }, down: { isSilence: true, npcReact: [rx('v2_msg04_r3w_react_silent_wav_v1.mp3')], }, }, }, }, left: { linxia: lx('v2_msg04_linxia_cold_wav_v1.mp3'), npcReact: [rx('v2_msg04_react_cold_wav_v1.mp3')], state: { HE_BACK: false }, profile: { avoidance: 1 }, }, up: { linxia: lx('v2_msg04_linxia_curious_wav_v1.mp3'), npcReact: [rx('v2_msg04_react_curious_wav_v1.mp3')], profile: { engagement: 2 }, followUp: { // 阿哲说"不太方便"后 choices: { up: { linxia: lx('v2_msg04_r3c_linxia_curious_wav_v1.mp3'), npcReact: [rx('v2_msg04_r3c_react_curious_wav_v1.mp3')], state: { HE_BACK: false }, }, right: { linxia: lx('v2_msg04_r3c_linxia_warm_wav_v1.mp3'), }, }, }, }, down: { isSilence: true, npcReact: [rx('v2_msg04_react_silent_wav_v1.mp3')], state: { HE_BACK: null }, profile: { avoidance: 2 }, }, tap: { linxia: lx('v2_msg04_linxia_tap_wav_v1.mp3'), npcReact: [rx('v2_msg04_react_tap_wav_v1.mp3')], state: { HE_BACK: false }, profile: { engagement: 1 }, }, }, }, // ══════════════════════════════════════════ // 05 · HR王姐 · 语音 // ══════════════════════════════════════════ { id: 5, type: 'wechat_voice', sender: 'hr', senderName: 'HR王姐', audio: [npc('lv11_2145_hr_voice_01_wav_v1.mp3')], choices: { right: { linxia: lx('v2_msg05_linxia_warm_wav_v1.mp3'), npcReact: [rx('v2_msg05_react_warm_wav_v1.mp3')], profile: { engagement: 1 }, }, left: { linxia: lx('v2_msg05_linxia_cold_wav_v1.mp3'), }, up: { linxia: lx('v2_msg05_linxia_curious_wav_v1.mp3'), npcReact: [rx('v2_msg05_react_curious_wav_v1.mp3')], profile: { engagement: 2 }, }, down: { isSilence: true, profile: { avoidance: 1 }, }, tap: { linxia: lx('v2_msg05_linxia_tap_wav_v1.mp3'), }, }, }, // ══════════════════════════════════════════ // 06 · 安安 · 语音(酒吧邀约) // ══════════════════════════════════════════ { id: 6, type: 'wechat_voice', sender: 'anan', senderName: '安安', audio: [npc('lv11_2200_anan_voice_01_wav_v1.mp3')], choices: { right: { linxia: lx('v2_msg06_linxia_warm_wav_v1.mp3'), npcReact: [rx('v2_msg06_react_warm_wav_v1.mp3')], profile: { engagement: 2 }, }, left: { linxia: lx('v2_msg06_linxia_cold_wav_v1.mp3'), npcReact: [rx('v2_msg06_react_cold_wav_v1.mp3')], }, up: { linxia: lx('v2_msg06_linxia_curious_wav_v1.mp3'), npcReact: [rx('v2_msg06_react_curious_wav_v1.mp3')], profile: { engagement: 1 }, }, down: { isSilence: true, npcReact: [rx('v2_msg06_react_silent_wav_v1.mp3')], profile: { avoidance: 1 }, }, tap: { linxia: lx('v2_msg06_linxia_tap_wav_v1.mp3'), npcReact: [rx('v2_msg06_react_tap_wav_v1.mp3')], }, }, }, // ══════════════════════════════════════════ // 07 · 寝室群 · 5条语音 // ══════════════════════════════════════════ { id: 7, type: 'wechat_voice', sender: 'dorm', senderName: '寝室群', audio: [ npc('lv11_2215_dorm_a_voice_01_wav_v1.mp3'), npc('lv11_2215_dorm_b_voice_01_wav_v1.mp3'), npc('lv11_2215_dorm_c_voice_01_wav_v1.mp3'), npc('lv11_2215_dorm_a_voice_02_wav_v1.mp3'), npc('lv11_2215_dorm_b_voice_02_wav_v1.mp3'), ], choices: { right: { linxia: lx('v2_msg07_linxia_warm_wav_v1.mp3'), npcReact: [rx('v2_msg07_react_warm_wav_v1.mp3')], state: { GROUP_REPLY: true }, profile: { engagement: 2 }, followUp: { // 室友A问"你最近怎么样"后 choices: { right: { linxia: lx('v2_msg07_r3_linxia_warm_wav_v1.mp3'), profile: { engagement: 1 }, }, left: { linxia: lx('v2_msg07_r3_linxia_cold_wav_v1.mp3'), npcReact: [rx('v2_msg07_r3_react_cold_wav_v1.mp3')], }, }, }, }, left: { linxia: lx('v2_msg07_linxia_cold_wav_v1.mp3'), profile: { avoidance: 1 }, }, up: { linxia: lx('v2_msg07_linxia_curious_wav_v1.mp3'), npcReact: [rx('v2_msg07_react_curious_wav_v1.mp3')], state: { GROUP_REPLY: true }, profile: { engagement: 2 }, }, down: { isSilence: true, profile: { avoidance: 2 }, }, tap: { linxia: lx('v2_linxia_generic_hmm_wav_v1.mp3'), npcReact: [rx('v2_msg07_react_tap_wav_v1.mp3')], state: { GROUP_REPLY: true }, }, }, }, // ══════════════════════════════════════════ // 08 · 未知号码 · 电话(悬疑钩子) // ══════════════════════════════════════════ { id: 8, type: 'call', sender: 'unknown', senderName: '未知号码', ringtone: `${MUS}/lv11_ring_unknown_v1.mp3`, audio: [`${SFX}/sfx_breathing_unknown_v1.mp3`], autoHangup: true, sfxAfter: `${SFX}/sfx_heartbeat_fast_v1.mp3`, profileOnMiss: { avoidance: 1 }, profileOnAnswer: { nostalgia: 1 }, }, // ══════════════════════════════════════════ // 09 · 小美(醉)· 4段语音 // ══════════════════════════════════════════ { id: 9, type: 'wechat_voice', sender: 'xiaomei', senderName: '小美', audio: [ npc('lv11_2245_xiaomei_voice_01_seg1_wav_v1.mp3'), npc('lv11_2245_xiaomei_voice_01_seg2_wav_v1.mp3'), npc('lv11_2245_xiaomei_voice_01_seg3_wav_v1.mp3'), npc('lv11_2245_xiaomei_voice_01_seg4_wav_v1.mp3'), ], bgm: `${MUS}/lv11_bgm_m2_suspense_v1.mp3`, choices: { right: { linxia: lx('v2_msg09_linxia_warm_wav_v1.mp3'), npcReact: [rx('v2_msg09_react_warm_wav_v1.mp3')], profile: { engagement: 2 }, }, left: { linxia: lx('v2_msg09_linxia_cold_wav_v1.mp3'), profile: { avoidance: 1 }, }, up: { linxia: lx('v2_msg09_linxia_curious_wav_v1.mp3'), npcReact: [rx('v2_msg09_react_curious_wav_v1.mp3')], profile: { engagement: 2 }, followUp: { // 小美说"怕你恨我"后 choices: { right: { linxia: lx('v2_msg09_r3_linxia_warm_wav_v1.mp3'), npcReact: [rx('v2_msg09_r3_react_warm_wav_v1.mp3')], }, left: { linxia: lx('v2_msg09_r3_linxia_cold_wav_v1.mp3'), npcReact: [rx('v2_msg09_r3_react_cold_wav_v1.mp3')], }, down: { isSilence: true, npcReact: [rx('v2_msg09_r3_react_silent_q_wav_v1.mp3')], }, }, }, }, down: { isSilence: true, profile: { avoidance: 2, nostalgia: 1 }, }, tap: { linxia: lx('v2_msg09_linxia_tap_wav_v1.mp3'), npcReact: [rx('v2_msg09_react_tap_wav_v1.mp3')], profile: { nostalgia: 1 }, }, }, }, // ══════════════════════════════════════════ // 10 · 妈妈 · 文字消息(多层嵌套对话) // ══════════════════════════════════════════ { id: 10, type: 'wechat_text', sender: 'mom', senderName: '妈妈', text: '睡了吗?', choices: { right: { linxia: lx('v2_msg10_linxia_warm_wav_v1.mp3'), npcReact: [rx('v2_msg10_react_warm_wav_v1.mp3')], state: { MOM_LINK: true }, profile: { engagement: 2 }, followUp: { // 妈妈问"今天怎么样"后 choices: { right: { linxia: lx('v2_msg10_rw_linxia_warm_wav_v1.mp3'), npcReact: [rx('v2_msg10_rw_react_warm_wav_v1.mp3')], }, up: { linxia: lx('v2_msg10_rw_linxia_curious_wav_v1.mp3'), npcReact: [rx('v2_msg10_rw_react_curious_wav_v1.mp3')], }, }, }, }, left: { linxia: lx('v2_msg10_linxia_cold_wav_v1.mp3'), npcReact: [rx('v2_msg10_react_cold_wav_v1.mp3')], state: { MOM_LINK: true }, profile: { avoidance: 1 }, }, up: { linxia: lx('v2_msg10_linxia_curious_wav_v1.mp3'), npcReact: [rx('v2_msg10_call_mom_wav_v1.mp3')], state: { MOM_LINK: true }, profile: { engagement: 3 }, followUp: { // 妈妈打来电话说"怎么了夏夏?"后 choices: { right: { linxia: lx('v2_msg10_call_linxia_warm_wav_v1.mp3'), npcReact: [rx('v2_msg10_call_react_warm_wav_v1.mp3')], }, up: { linxia: lx('v2_msg10_call_linxia_curious_wav_v1.mp3'), npcReact: [rx('v2_msg10_call_react_curious_wav_v1.mp3')], }, down: { isSilence: true, npcReact: [rx('v2_msg10_call_react_silent_wav_v1.mp3')], }, }, }, }, down: { isSilence: true, npcReact: [rx('v2_msg10_react_silent_wav_v1.mp3')], profile: { avoidance: 2 }, }, tap: { linxia: lx('v2_msg10_linxia_tap_wav_v1.mp3'), npcReact: [rx('v2_msg10_react_tap_wav_v1.mp3')], state: { MOM_LINK: true }, }, }, }, // ══════════════════════════════════════════ // 11 · 阿哲 · 电话(关键分支:来不来) // ══════════════════════════════════════════ { id: 11, type: 'call', sender: 'azhe', senderName: '阿哲', ringtone: `${MUS}/lv11_ring_azhe_v1.mp3`, audio: [npc('lv11_2315_azhe_call_01_wav_v1.mp3')], profileOnMiss: { avoidance: 2 }, profileOnAnswer: { engagement: 1 }, choices: { right: { linxia: lx('v2_msg11_linxia_warm_wav_v1.mp3'), npcReact: [rx('v2_msg11_react_warm_wav_v1.mp3')], state: { HE_BACK: true }, }, left: { linxia: lx('v2_msg11_linxia_cold_wav_v1.mp3'), npcReact: [rx('v2_msg11_react_cold_wav_v1.mp3')], state: { HE_BACK: false }, }, up: { linxia: lx('v2_msg11_linxia_curious_wav_v1.mp3'), npcReact: [rx('v2_msg11_react_curious_wav_v1.mp3')], followUp: { choices: { right: { linxia: lx('v2_msg11_linxia_warm_wav_v1.mp3'), npcReact: [rx('v2_msg11_react_warm_wav_v1.mp3')], state: { HE_BACK: true }, }, left: { linxia: lx('v2_msg11_linxia_cold_wav_v1.mp3'), npcReact: [rx('v2_msg11_react_cold_wav_v1.mp3')], state: { HE_BACK: false }, }, }, }, }, down: { isSilence: true, state: { HE_BACK: false }, }, tap: { linxia: lx('v2_msg11_linxia_tap_wav_v1.mp3'), npcReact: [rx('v2_msg11_react_tap_wav_v1.mp3')], state: { HE_BACK: false }, }, }, }, // ══════════════════════════════════════════ // 12 · 周南 · 语音(老同学) // ══════════════════════════════════════════ { id: 12, type: 'wechat_voice', sender: 'zhounan', senderName: '周南', audio: [npc('lv11_2330_zhounan_voice_01_wav_v1.mp3')], choices: { right: { linxia: lx('v2_msg12_linxia_warm_wav_v1.mp3'), npcReact: [rx('v2_msg12_react_warm_wav_v1.mp3')], state: { ZHOUNAN_DEPTH: 1 }, profile: { engagement: 2, nostalgia: 1 }, }, left: { linxia: lx('v2_msg12_linxia_cold_wav_v1.mp3'), npcReact: [rx('v2_msg12_react_cold_wav_v1.mp3')], state: { ZHOUNAN_DEPTH: 1 }, profile: { nostalgia: 1 }, }, up: { linxia: lx('v2_msg12_linxia_curious_wav_v1.mp3'), npcReact: [rx('v2_msg12_react_curious_wav_v1.mp3')], state: { ZHOUNAN_DEPTH: 1 }, profile: { nostalgia: 2 }, }, down: { isSilence: true, npcReact: [rx('v2_msg12_react_silent_wav_v1.mp3')], profile: { nostalgia: 1 }, }, tap: { linxia: lx('v2_msg12_linxia_tap_wav_v1.mp3'), npcReact: [rx('v2_msg12_react_tap_wav_v1.mp3')], state: { ZHOUNAN_DEPTH: 1 }, }, }, }, // ══════════════════════════════════════════ // 13 · 周南 · 4条长语音(他的十年) // ══════════════════════════════════════════ { id: 13, type: 'wechat_voice', sender: 'zhounan', senderName: '周南', audio: [ npc('lv11_2345_zhounan_voice_01_wav_v1.mp3'), npc('lv11_2345_zhounan_voice_02_wav_v1.mp3'), npc('lv11_2345_zhounan_voice_03_wav_v1.mp3'), npc('lv11_2345_zhounan_voice_04_wav_v1.mp3'), ], bgm: `${MUS}/lv11_bgm_m3_zhounan_v1.mp3`, choices: { right: { linxia: lx('v2_msg13_linxia_warm_wav_v1.mp3'), npcReact: [rx('v2_msg13_react_warm_wav_v1.mp3')], state: { ZHOUNAN_DEPTH_ADD: 1 }, profile: { engagement: 2 }, followUp: { // 周南问"你在北京过得好吗"后 choices: { right: { linxia: lx('v2_msg13_r3_linxia_warm_wav_v1.mp3'), npcReact: [rx('v2_msg13_r3_react_warm_wav_v1.mp3')], state: { ZHOUNAN_SHARE: true }, }, left: { linxia: lx('v2_msg13_r3_linxia_cold_wav_v1.mp3'), npcReact: [rx('v2_msg13_r3_react_cold_wav_v1.mp3')], }, up: { linxia: lx('v2_msg13_r3_linxia_curious_wav_v1.mp3'), npcReact: [rx('v2_msg13_r3_react_curious_wav_v1.mp3')], }, }, }, }, left: { linxia: lx('v2_msg13_linxia_cold_wav_v1.mp3'), npcReact: [rx('v2_msg13_react_cold_wav_v1.mp3')], state: { ZHOUNAN_DEPTH_ADD: 1 }, profile: { nostalgia: 1 }, }, up: { linxia: lx('v2_msg13_linxia_curious_wav_v1.mp3'), npcReact: [rx('v2_msg13_react_curious_wav_v1.mp3')], state: { ZHOUNAN_DEPTH_ADD: 1, ZHOUNAN_SHARE: true }, profile: { nostalgia: 2, engagement: 1 }, }, down: { isSilence: true, npcReact: [rx('v2_msg13_react_silent_wav_v1.mp3')], profile: { avoidance: 1 }, }, tap: { linxia: lx('v2_msg13_linxia_tap_wav_v1.mp3'), npcReact: [rx('v2_msg13_react_tap_wav_v1.mp3')], state: { ZHOUNAN_DEPTH_ADD: 1 }, }, }, }, // ══════════════════════════════════════════ // 14 · 妈妈 · 第二次电话 // ══════════════════════════════════════════ { id: 14, type: 'call', sender: 'mom', senderName: '妈妈', ringtone: `${MUS}/lv11_ring_mom_v1.mp3`, audio: [npc('lv11_0015_mom_call_01_wav_v1.mp3')], autoReply: lx('v2_msg14_linxia_auto_wav_v1.mp3'), autoReact: { condition: 'MOM_LINK', true: rx('v2_msg14_react_linked_wav_v1.mp3'), false: rx('v2_msg14_react_unlinked_wav_v1.mp3'), }, autoReactPreamble: rx('v2_msg14_mom_ask_wav_v1.mp3'), stateOnAnswer: { MOM_LINK: true }, profileOnMiss: { avoidance: 2 }, profileOnAnswer: { engagement: 2 }, }, // ══════════════════════════════════════════ // 15 · 时光胶囊 · 系统通知 // ══════════════════════════════════════════ { id: 15, type: 'system_notification', sender: 'self', senderName: '时光胶囊', systemAudio: npc('lv11_sys_memo_01_wav_v1.mp3'), audioByProfile: { avoidance: npc('lv11_0030_self3y_voice_02_wav_v1.mp3'), engagement: npc('lv11_0030_self3y_voice_01_wav_v1.mp3'), nostalgia: npc('lv11_0030_self3y_voice_03_wav_v1.mp3'), }, stateOnPlay: { SELF_RECORD: true }, profileOnPlay: { nostalgia: 2 }, profileOnSkip: { avoidance: 2 }, }, // ══════════════════════════════════════════ // 16 · 妈妈 · 长语音 ×8(结局前奏) // ══════════════════════════════════════════ { id: 16, type: 'wechat_voice', sender: 'mom', senderName: '妈妈', audio: [ npc('lv11_0200_mom_voice_01_seg1_wav_v1.mp3'), npc('lv11_0200_mom_voice_01_seg2_wav_v1.mp3'), npc('lv11_0200_mom_voice_01_seg3_wav_v1.mp3'), npc('lv11_0200_mom_voice_01_seg4_wav_v1.mp3'), npc('lv11_0200_mom_voice_01_seg5_wav_v1.mp3'), npc('lv11_0200_mom_voice_01_seg6_wav_v1.mp3'), npc('lv11_0200_mom_voice_01_seg7_wav_v1.mp3'), npc('lv11_0200_mom_voice_01_seg8_wav_v1.mp3'), ], bgm: `${MUS}/lv11_bgm_m4_ending_v1.mp3`, isEnding: true, requiresMomLink: true, }, ]; // 角色信息 const SENDERS = { mom: { name: '妈妈' }, azhe: { name: '阿哲' }, xiaomei: { name: '小美' }, delivery: { name: '外卖小哥' }, hr: { name: 'HR王姐' }, anan: { name: '安安' }, dorm: { name: '寝室群' }, unknown: { name: '未知号码' }, zhounan: { name: '周南' }, self: { name: '时光胶囊' }, }; const AMBIENCE_DEFAULT = `${AMB}/amb_apartment_night_01_v1.mp3`;