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:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+38
-2
@@ -22,6 +22,7 @@
|
||||
* 按需解码 + AudioBuffer 缓存,见 docs/web-engine-api.md)。
|
||||
*/
|
||||
import { Game } from '../engine/game.js';
|
||||
import { Guide } from '../engine/guide.js';
|
||||
|
||||
/** 剧情节点。voices 顺序播放;prompt 非空表示这是一个选择节点。 */
|
||||
function node(voices, opts = {}) {
|
||||
@@ -81,9 +82,13 @@ const VOICE_WATCHDOG_MS = 30000;
|
||||
/** 抉择提示音 id(voice/p1~p8):播放中单击不可跳过(12 号文档 §1.2)。 */
|
||||
const PROMPT_RE = /^voice\/p\d/;
|
||||
|
||||
// 向导层(schemes.md §2):W1 静默看门狗阈值——8s 轻提醒+gd_continue,20s 升级重播帮助
|
||||
const GUIDE_WATCHDOG_AT = [8000, 20000];
|
||||
|
||||
export class StorylineGame extends Game {
|
||||
constructor(audio, root) {
|
||||
super(audio, root);
|
||||
this.guide = new Guide(audio); // 全局向导层(guide='off' 时全部静默 no-op)
|
||||
this.mode = MODE.STORY;
|
||||
this.nodeId = 'n00';
|
||||
this.voicePlaying = false;
|
||||
@@ -99,12 +104,17 @@ export class StorylineGame extends Game {
|
||||
this.companion = false; // 带上了小满
|
||||
this.knowTrap = false; // 装死时听到北区陷阱的情报
|
||||
this.betrayed = false; // 向猎狗交出过坐标
|
||||
|
||||
// 向导层:升级帮助播过后不再自动重挂看门狗(L4"之后停止,不轰炸"),
|
||||
// 玩家下一次手势即复位
|
||||
this._guideHelped = false;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Game
|
||||
|
||||
start() {
|
||||
this.audio.play('bgm/dark_drone');
|
||||
this.guide.state('explore'); // 状态签名:剧情/探索(与状态 BGM 同点,10 号文档)
|
||||
// 预解码全部短音效(等价 Android 的 SoundPool 音量 0 预热)
|
||||
this.audio.prewarm(ALL_SFX);
|
||||
this.playNode('n00');
|
||||
@@ -112,6 +122,8 @@ export class StorylineGame extends Game {
|
||||
|
||||
onGesture(g) {
|
||||
console.log(`[StorylineGame] onGesture: ${g} mode=${this.mode} node=${this.nodeId} armed=${this.choiceArmed} playing=${this.voicePlaying}`);
|
||||
this.guide.disarm(); // 任何手势复位静默看门狗(guide=off 时 no-op)
|
||||
this._guideHelped = false; // 玩家有动作,恢复看门狗自动挂载
|
||||
if (this.mode === MODE.MENU) this.onMenuGesture(g);
|
||||
else this.onStoryGesture(g);
|
||||
}
|
||||
@@ -134,7 +146,12 @@ export class StorylineGame extends Game {
|
||||
break;
|
||||
case 'left':
|
||||
case 'right': {
|
||||
if (node.prompt == null) return;
|
||||
if (node.prompt == null) {
|
||||
// 无效手势零反馈是全 6 款最盲点(清点 §1.4):向导纠错——error 震 +
|
||||
// full 档 gd_invalid(onceKey 同局只播一次;off 档整段 no-op)
|
||||
this.guide.error({ voice: 'gd_invalid', onceKey: 'storyline_invalid' });
|
||||
return;
|
||||
}
|
||||
if (this.choiceArmed) this.choose(g);
|
||||
else if (this.voicePlaying) this.pendingChoice = g; // 提示音播放中,缓存抉择
|
||||
break;
|
||||
@@ -220,10 +237,26 @@ export class StorylineGame extends Game {
|
||||
this.pendingChoice = null;
|
||||
this.choose(g);
|
||||
}
|
||||
// 抉择提示播完即可选(W2 契约):此处不可插任何语音,也不挂看门狗
|
||||
} else if (node.ending) {
|
||||
this.awaitingRestart = true;
|
||||
}
|
||||
// 否则等待单击继续
|
||||
// W1/W3 静默等待看门狗(向导层,schemes §2):8s nudge+gd_continue;
|
||||
// 20s 升级 = 复用 playHelpAndResume(帮助+进度+重播当前节点),升级后不再
|
||||
// 自动重挂(防循环轰炸),玩家下一次手势恢复。抉择态与语音播放中不挂。
|
||||
if (node.prompt == null && !this._guideHelped) {
|
||||
this.guide.watchdog({
|
||||
arm: true,
|
||||
at: GUIDE_WATCHDOG_AT,
|
||||
say: ['gd_continue', null],
|
||||
onEscalate: (n) => {
|
||||
if (n !== 2 || this.mode !== MODE.STORY) return;
|
||||
this._guideHelped = true;
|
||||
this.playHelpAndResume();
|
||||
},
|
||||
cond: () => this.mode === MODE.STORY && !this.voicePlaying,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** 顺序播放一组对白,全部自然播完后回调。被打断时回调作废(seqToken 机制)。 */
|
||||
@@ -302,6 +335,7 @@ export class StorylineGame extends Game {
|
||||
this.audio.stopVoice();
|
||||
this.audio.play('sfx/ui_menu');
|
||||
this.audio.play('bgm/menu', { fadeMs: 500 }); // 状态 BGM:菜单 pad(10 号文档 §2)
|
||||
this.guide.state('menu');
|
||||
this.playSeq(['voice/menu'], () => { this.voicePlaying = false; });
|
||||
}
|
||||
|
||||
@@ -313,6 +347,7 @@ export class StorylineGame extends Game {
|
||||
this.mode = MODE.STORY;
|
||||
this.audio.play('sfx/ui_confirm');
|
||||
this.audio.play('bgm/dark_drone', { fadeMs: 800 }); // 关菜单淡回主题 drone
|
||||
this.guide.state('explore');
|
||||
this.playNode(this.nodeId); // 回到故事,重播当前节点
|
||||
}
|
||||
|
||||
@@ -325,6 +360,7 @@ export class StorylineGame extends Game {
|
||||
this.mode = MODE.STORY;
|
||||
this.audio.play('sfx/ui_confirm');
|
||||
this.audio.play('bgm/dark_drone'); // 从菜单重开时切回主题(结局重开为同首不重启)
|
||||
this.guide.state('explore');
|
||||
this.playNode('n00');
|
||||
}
|
||||
|
||||
|
||||
@@ -165,6 +165,9 @@ const { WebAudioEngine } = await import('../../engine/audio.js');
|
||||
const { settings } = await import('../../engine/settings.js');
|
||||
const { StorylineGame } = await import('../game.js');
|
||||
|
||||
// 向导层关闭:回归测试只验证纯游戏逻辑(guide='full' 的行为由 harness/guide.test.mjs 覆盖)
|
||||
settings.set({ guide: 'off', voice: 'female', speed: 1.0, bgm: 1.0 });
|
||||
|
||||
let gestureCount = 0;
|
||||
function makeRig() {
|
||||
const audio = new WebAudioEngine('audio/');
|
||||
|
||||
Reference in New Issue
Block a user