Files
SoundGame/web/harness/sessions/space.mjs
T
SoundGame Dev 79dffac01e 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断言)+资产终检全绿
2026-08-30 22:46:05 +08:00

91 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 《盲航》新手会话剧本:首局完整演练 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('躲过保护期、挨过一次撞,心跳有点快,先到这');
}