round4: H1 escape目标带实时方位 + H2 simon轮次占用预告(根治手势两义) + H3 重听账目真实化(诊断=初始发放1次vs承诺2次) + H4 input看门狗5s + H5 归因真话(gd_miss_none) + 20语音 + guide120断言全绿
This commit is contained in:
+57
-18
@@ -26,6 +26,14 @@
|
||||
* 答题输入的 00 §5.3 豁免同款)。非输入阶段行为不变(gameover/菜单上滑仍是规则/
|
||||
* 帮助)。同一手势在 input 阶段不再有第二种含义(P1 round2 P0 根治)。
|
||||
*
|
||||
* round4(docs/design/14-guide-layer.md「round4 v1.3」,synthesis H2~H4):
|
||||
* - H2 占用手势预告:每轮序列播放完成后、「轮到你」提示前,序列含 up/doubletap
|
||||
* 则播 gd_occ_up/dt/both(游戏反馈层全档位,插在轮次链中);被占用轮次 input
|
||||
* 看门狗只发 nudge earcon 不播重听建议句(say 动态置空)。
|
||||
* - H3 重听账目真实:主动重听使用时如实报余量(2→1 播 gd_chances_left1、
|
||||
* 1→0 播 gd_chances_left0,替换该路径旧「用完了」绝对句,全档位)。
|
||||
* - H4 input 看门狗首级 10s→5s(升级级 25s 与次数不变;非 input 阶段不动)。
|
||||
*
|
||||
* 手势 → 音色映射:
|
||||
* up 上行琶音 / down 下行低音 / left 左声道弹拨 / right 右声道弹拨 /
|
||||
* tap 底鼓 / doubletap 双连响
|
||||
@@ -66,10 +74,11 @@ const TEACH_BLUNT = {
|
||||
const BEST_KEY = 'soundgame.simon.best';
|
||||
const AUTH_KEY = 'soundgame.auth'; // 大厅登录写入(见 web/index.html)
|
||||
|
||||
// 向导层(14 号文档 + round2 synthesis F1.2):input 阶段静默看门狗——10s 轻提醒
|
||||
// +gd_relisten(不改变重听扣机会规则);25s 升级取消自动重播序列(P1:泄题且与
|
||||
// 扣机会规则矛盾)→ 改为 confirm 震 + turn earcon(纯标记,无语音)
|
||||
const GUIDE_INPUT_WATCHDOG_AT = [10000, 25000];
|
||||
// 向导层(14 号文档 + round2 synthesis F1.2 + round4 H4):input 阶段静默看门狗
|
||||
// ——首级 5s 轻提醒 +gd_relisten(H4 round4:10s→5s,P4「10 秒干等」;升级级
|
||||
// 不变、次数不变);25s 升级取消自动重播序列(P1:泄题且与扣机会规则矛盾)→
|
||||
// 改为 confirm 震 + turn earcon(纯标记,无语音)
|
||||
const GUIDE_INPUT_WATCHDOG_AT = [5000, 25000];
|
||||
|
||||
export class SimonGame extends Game {
|
||||
constructor(audio, root) {
|
||||
@@ -321,13 +330,15 @@ export class SimonGame extends Game {
|
||||
if (s !== this._session || this.phase !== 'playback') return;
|
||||
vibrate('confirm'); // "轮到你了"
|
||||
this.guide.turn({}); // turn earcon 每轮泛化(第 1 轮另有 your_turn 语音,下行)
|
||||
if (this.round === 1) {
|
||||
this.audio.play('voice/your_turn', {
|
||||
onComplete: () => {
|
||||
if (s === this._session && this.phase === 'playback') this._toInput();
|
||||
},
|
||||
});
|
||||
this._timer(() => { if (this.phase === 'playback') this._toInput(); }, 2500); // 兜底
|
||||
// H2(round4):序列播放完成后、「轮到你」提示前,按本轮序列播占用预告
|
||||
//(gd_occ_up/dt/both,游戏反馈层全档位,插在轮次链中)——重听键与答案音
|
||||
// 共用词表的结构性冲突,换手势无解,改为轮次开场讲清判据(P1#1+P2)。
|
||||
const ids = this._occupancyVoices();
|
||||
if (this.round === 1) ids.push('voice/your_turn');
|
||||
if (ids.length) {
|
||||
this._speakGuarded(ids, () => {
|
||||
if (s === this._session && this.phase === 'playback') this._toInput();
|
||||
}, 2500 * ids.length); // 语音缺失兜底:每句 2.5s
|
||||
} else {
|
||||
this._toInput();
|
||||
}
|
||||
@@ -337,17 +348,33 @@ export class SimonGame extends Game {
|
||||
step();
|
||||
}
|
||||
|
||||
/**
|
||||
* H2(round4):本轮占用预告句。序列含 up → gd_occ_up、含 doubletap →
|
||||
* gd_occ_dt、两者皆有 → gd_occ_both;都不含则不播(空链直达 input)。
|
||||
*/
|
||||
_occupancyVoices() {
|
||||
const hasUp = this.sequence.includes('up');
|
||||
const hasDt = this.sequence.includes('doubletap');
|
||||
if (hasUp && hasDt) return ['voice/gd_occ_both'];
|
||||
if (hasUp) return ['voice/gd_occ_up'];
|
||||
if (hasDt) return ['voice/gd_occ_dt'];
|
||||
return [];
|
||||
}
|
||||
|
||||
_toInput() {
|
||||
this.phase = 'input';
|
||||
this.inputIdx = 0;
|
||||
// 向导层静默看门狗(round2 F1.2 礼仪重构):input 阶段无 BGM 无超时,挂机
|
||||
// 永久静默。10s → nudge + gd_relisten 提示(不改变重听扣机会规则);25s 升级
|
||||
// **取消免扣机会自动重播序列**(P1:等答案自动重念,双击重听的经济就矛盾了,
|
||||
// 还顺手拆了记忆难度)→ 只发 confirm 震 + turn earcon(纯标记,无语音)。
|
||||
// 向导层静默看门狗(round2 F1.2 礼仪重构 + round4 H4/H2):input 阶段无 BGM
|
||||
// 无超时,挂机永久静默。5s → nudge + gd_relisten 提示(H4:首级 10s→5s,
|
||||
// P4「10 秒干等」;升级级 25s 与级数不变);25s 升级**取消免扣机会自动重播
|
||||
// 序列**(P1:等答案自动重念,双击重听的经济就矛盾了,还顺手拆了记忆难度)
|
||||
// → 只发 confirm 震 + turn earcon(纯标记,无语音)。
|
||||
// H2:本轮 up 已被占用为作答时,重听建议句不再建议上滑(实现从简:被占用
|
||||
// 轮次看门狗只发 nudge earcon 不播重听建议句)。
|
||||
this.guide.watchdog({
|
||||
arm: true,
|
||||
at: GUIDE_INPUT_WATCHDOG_AT,
|
||||
say: ['gd_relisten', null],
|
||||
say: [this.sequence.includes('up') ? null : 'gd_relisten', null],
|
||||
onEscalate: (n) => { if (n === 2) this.guide.turn({ haptic: true }); },
|
||||
cond: () => this.phase === 'input',
|
||||
});
|
||||
@@ -371,7 +398,18 @@ export class SimonGame extends Game {
|
||||
if (this.inputIdx >= this.sequence.length) this._roundClear();
|
||||
}
|
||||
|
||||
/** 主动重听:消耗一次机会,重播当前序列(进度清零重输,序列不变)。 */
|
||||
/**
|
||||
* 主动重听:消耗一次机会,重播当前序列(进度清零重输,序列不变)。
|
||||
* H3(round4):使用时如实报余量——免费重听第 1 次用掉(余 1)播
|
||||
* gd_chances_left1「免费重听还剩一次。」、第 2 次用掉(余 0)播
|
||||
* gd_chances_left0「免费重听用完了。之后重听会花一次机会,别怕,慢慢来。」
|
||||
* (游戏档全级别,替换本路径旧「用完了」绝对句;P1:教学说两次、用一次即报
|
||||
* 用完了——账目不可知)。账目诊断:教学/演练/看门狗均无隐性扣减(watchdog
|
||||
* 提示不消耗机会,教学期不触 chances),缺口在「免费两次」中的第二次要打完
|
||||
* 第 4 轮才发放(每 4 轮 +1、上限 2)——「用完了」因此只在真实余量为 0 时出现。
|
||||
* 答错自动消耗路径(retry_use + chances_out)与 0 次时再求重听的拒绝路径
|
||||
* (chances_out)是「答错可以消耗机会重听」的机会账,语义如实、维持不变。
|
||||
*/
|
||||
_relisten() {
|
||||
if (this.chances <= 0) {
|
||||
vibrate('error');
|
||||
@@ -381,7 +419,8 @@ export class SimonGame extends Game {
|
||||
this.chances--;
|
||||
vibrate('pickup');
|
||||
const ids = ['voice/relisten'];
|
||||
if (this.chances === 0) ids.push('voice/chances_out'); // 机会归 0 提示
|
||||
if (this.chances === 1) ids.push('voice/gd_chances_left1'); // 余 1:还剩一次
|
||||
else if (this.chances === 0) ids.push('voice/gd_chances_left0'); // 余 0:用完了
|
||||
const s = this._session;
|
||||
this.phase = 'roundend'; // 重听播报期间暂停接收输入
|
||||
this._speakGuarded(ids, () => { if (s === this._session) this._playSequence(); });
|
||||
|
||||
Reference in New Issue
Block a user