/** * 《声命线》新手会话剧本:像真人一样等语音播完再动、偶尔走神(触发看门狗)、 * 在非抉择节点试一次无效的左滑(触发向导纠错)、菜单进去逛一圈再出来、 * 抉择点一律选左,一路打到结局。 */ export function describe(game) { if (game.mode === 'MENU') return '菜单里:左滑重开、右滑或单击返回剧情、上滑听帮助'; if (game.awaitingRestart) return '结局播完了。单击屏幕,可以回到故事开始的地方'; if (game.choiceArmed) return `「请选择」念完了(节点 ${game.nodeId}):左滑或右滑,做出你的决定`; if (game.voicePlaying) return '旁白正在播。单击跳过本句,双击重听整个节点'; return `剧情等着你(节点 ${game.nodeId}):单击继续,双击重听`; } export async function session(ctx, mock) { const { game } = ctx; let invalidTried = false; let menuDone = false; // 开场:n00 全听完,愣一会儿——向导看门狗先说话 await ctx.waitIdle(); ctx.status('开场旁白播完了,屏幕黑着,有点不知所措'); ctx.silence(8, '新手没反应过来'); await ctx.waitVirtual(8600); // 触发看门狗:nudge + gd_continue ctx.status('听到一声轻响和「轻触屏幕,继续」,试着点一下'); await ctx.gesture('tap'); for (let i = 0; i < 60 && !game.awaitingRestart && mock.now < 20 * 60 * 1000; i++) { await ctx.waitIdle(); // 等当前语音链播完(真人不会抢话) if (game.awaitingRestart) break; // 中途进一次菜单,逛一圈再出来 if (!menuDone && game.nodeId === 'n04') { menuDone = true; ctx.note('想看看还有什么可按的,下滑呼出菜单'); await ctx.gesture('down'); await ctx.waitIdle(); ctx.status('菜单里'); await ctx.gesture('up'); // 听帮助 + 进度 await ctx.waitIdle(); await ctx.gesture('doubletap'); // 重听菜单项 await ctx.waitIdle(); ctx.note('听懂了,先回去继续剧情'); await ctx.gesture('tap'); // 关菜单(回剧情并重播当前节点) continue; } if (game.choiceArmed) { ctx.status('抉择点,提示音念完了'); if (mock.now % 2 < 1) await ctx.waitVirtual(3000); // 偶尔犹豫一下 ctx.note('选了左边'); await ctx.gesture('left'); // 抉择点一律选左 continue; } // W1:叙事节点播完,静默等待 ctx.status('剧情停下来了,在等我'); if (!invalidTried) { invalidTried = true; ctx.note('试着左滑了一下——这里用不上,向导纠正了我'); await ctx.gesture('left'); await ctx.waitIdle(); } else if (i % 4 === 2) { ctx.silence(8, '走神想剧情去了'); await ctx.waitVirtual(8600); // 偶尔挂机,触发看门狗轻提醒 ctx.status('又听到那声轻响,想起该点屏幕了'); } await ctx.gesture('tap'); } await ctx.waitIdle(); ctx.status('结局'); ctx.note('听完结局,单击重开一次看看别的走向'); await ctx.gesture('tap'); // 结算态单击重开 await ctx.waitIdle(); ctx.status('回到开头,会话结束'); }