guide层v1: 全局向导交互层落地(状态签名earcon/turn cue/静默看门狗/纠错) + 6游戏接线 + 8句向导语音 + 测试全绿

- engine/guide.js 运行时合成8个签名earcon(方法论L2硬参数),off档零定时器,插播queue不顶对白
- settings: guide三档(full/standard/off默认full) + 语速2.2视障档;audio: isVoicePlaying()/setRateBoostEnabled() 清两个已知问题
- 6游戏按schemes§2接线: storyline看门狗+无效手势纠错、simon免扣重听看门狗、escape turn cue+提前卡关提示、rhythm gd_answer/gd_miss+结算看门狗+快进豁免、space首撞指令提示+结算看门狗、rpg格挡纠错+60s催促+状态签名
- 大厅设置面板+向导文档(14-guide-layer.md)+00-global-ux §6;R5台词表补4条一致性
- 试玩转写含向导事件;6 playtest(guide=off断言零改动)+guide.test(34断言)+资产终检全绿
This commit is contained in:
SoundGame Dev
2026-08-30 22:46:05 +08:00
parent 7eb9e7f025
commit 79dffac01e
69 changed files with 2466 additions and 144 deletions
+90
View File
@@ -0,0 +1,90 @@
/**
* 《盲航》新手会话剧本:首局完整演练 D1-D4(第二颗故意超时,听当场重教)→
* 正式对局躲过保护期(20s/45s 的加速/护盾引入都照做)→ 保护期结束后故意漏一颗
* (触发归因句 + 向导「同向滑动就能拨开」指令)→ 再正常躲几颗收工。
*/
export function describe(game) {
const DIR = { left: '左边', right: '右边', up: '上面', down: '下面' };
switch (game.phase) {
case 'ask': return '问我直接出发还是重听演练:单击出发,上滑重听';
case 'drill': return game.drillOb
? `演练:${DIR[game.drillOb.dir]}有陨石在逼近,${DIR[game.drillOb.dir]}滑把它拨开`
: '演练旁白在播,先听';
case 'playing': {
const n = game.obstacles.size;
const protect = performance.now() < game.protectionUntil;
return n
? `对局中:${n} 颗陨石在逼近,听方位同向滑开${protect ? '(保护期,撞了也不掉血)' : ''}`
: `对局中:引擎声平稳,等下一颗${protect ? '(保护期)' : ''}`;
}
case 'feature': return game.featureKind === 'boost' ? '暂停了刷怪教新功能:单击试试加速' : '暂停了刷怪教新功能:双击展开护盾';
case 'menu': return '暂停菜单:单击切选项,双击确认,下滑恢复';
case 'over': return `坠毁了,存活数据播报中。单击再飞一次,下滑回大厅`;
default: return '开场,稍等';
}
}
export async function session(ctx, mock) {
const { game } = ctx;
// 演练 D1-D4:逼近音一响就同向拨开(第二颗故意不出手,听当场重教)。
//(刷怪链是自驱动的定时器链——禁止裸 waitIdle,等待一律挂条件)
for (let i = 0; i < 4; i++) {
await ctx.waitCond(() => game.phase === 'drill' && game.drillIdx === i && !!game.drillOb);
ctx.status(`演练 ${i + 1}/4:${{ left: '左', right: '右', up: '上', down: '下' }[game.drillOb.dir]}侧有陨石`);
if (i === 1) {
ctx.note('这颗犹豫了,没拨开——听当场重教');
await ctx.waitCond(() => game.phase === 'drill' && !game.drillOb); // 超时 → drill_retry
await ctx.waitCond(() => game.phase === 'drill' && game.drillIdx === i && !!game.drillOb);
ctx.note('重教听完,这回马上同向滑');
await ctx.gesture(game.drillOb.dir);
continue;
}
ctx.note('逼近音一响,同向滑开');
await ctx.gesture(game.drillOb.dir);
}
ctx.status('演练做完了,要出发了');
// 正式对局:保护期 60s + 结束后再躲一小段就收工(不再模拟,避免挂机撞毁)。
// 每颗陨石生成(呼啸声起)就立即同向拨开——比盯着 1.5s 判定窗口更稳。
//(刷怪链是自驱动的定时器链——禁止裸 waitIdle,等待一律挂条件)
await ctx.waitCond(() => game.phase === 'playing');
ctx.status('出发。引擎声起来了,保护期里先练手');
const dodged = new Set();
let victim = null; // 保护期结束后故意不躲的那颗
let eaten = false;
while ((game.phase === 'playing' || game.phase === 'feature')
&& performance.now() < game.protectionUntil + 8000) {
if (game.phase === 'feature') {
ctx.status('游戏停下来教新功能');
await ctx.waitIdle(); // 引入句听完(feature 态无刷怪链,可安全等待空闲)
ctx.note(game.featureKind === 'boost' ? '照它说的单击加速' : '照它说的双击开护盾');
await ctx.gesture(game.featureKind === 'boost' ? 'tap' : 'doubletap');
continue;
}
if (victim && !game.obstacles.has(victim)) {
ctx.note('被撞了。先听到方位归因,又听到「同向滑动就能拨开」');
victim = null;
continue;
}
// 只在判定窗口将开未开时出手(警报第一响 = 撞击前 1s,窗口内)
const target = [...game.obstacles].find((o) => !dodged.has(o)
&& o.impactAt - performance.now() < 1300);
if (target) {
if (!eaten && performance.now() >= game.protectionUntil) {
eaten = true;
victim = target;
dodged.add(target); // 标记 = 故意不躲
ctx.status('保护期结束了,这颗我故意没动');
continue;
}
dodged.add(target);
await ctx.gesture(target.dir);
continue;
}
await ctx.waitCond(() => (game.phase !== 'playing' && game.phase !== 'feature')
|| (victim && !game.obstacles.has(victim))
|| [...game.obstacles].some((o) => !dodged.has(o) && o.impactAt - performance.now() < 1300));
}
ctx.status('躲过保护期、挨过一次撞,心跳有点快,先到这');
}