round2: 看门狗礼仪F1(不重播/单句升级/idle-skip) + 分档归因F2 + 交接count-in/answer F3 + 教学看表现F4-F5 + 警报去叠F6 + 回绕F7 + 开场瘦身F8 + 10句新语音 + 测试全绿(6playtest零改动+guide52断言+资产终检)

This commit is contained in:
SoundGame Dev
2026-08-31 00:26:25 +08:00
parent 296c996fb9
commit 902461f0f4
65 changed files with 1094 additions and 775 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+38 -18
View File
@@ -31,9 +31,10 @@
* 语音 id 权威清单:docs/design/04-space-voice-ids.md(52 句双性别);
* 旧 num 拼接件、n_ 前缀件、intro1-4/hit1/hit2/gameover/restart 全部弃用。
*
* 向导层(research/03-schemes/schemes.md §2):对局第 1 次受击(保护期外)追加
* 指令式 gd_hit_hint(归因句 hit_<dir> 之外的"该怎么拨");结算链换
* _speakGuarded(资产缺失不卡结算 BGM);长按快进对局期豁免
* 向导层(docs/design/14-guide-layer.md + round2 synthesis F2.2/F6):对局第 1 次
* 受击(保护期外)在归因链播完后补指令式 gd_hit_hint、第 2 次补 gd_last_life
* (idle-only:挂在语音空闲点);警报音效 600ms 去重、danger 震 ≥400ms 去抖;
* 结算链换 _speakGuarded(资产缺失不卡结算 BGM);对局期长按快进豁免
* (setRateBoostEnabled);状态签名 earcon 与状态 BGM 同点。
*/
import { Game } from '../engine/game.js';
@@ -120,6 +121,10 @@ export class SpaceGame extends Game {
this.shieldIntroDone = false;
this.featureKind = null;
this._menuIdx = 0;
// F6:警报去重 / danger 去抖时间戳(soft/loud 分档 + danger 全局)
this._alarmSoftAt = -Infinity;
this._alarmLoudAt = -Infinity;
this._dangerAt = -Infinity;
}
// ---------------- 生命周期 ----------------
@@ -449,22 +454,31 @@ export class SpaceGame extends Game {
this.obstacles.add(ob);
this.audio.play(APPROACH_SFX[dir]);
// 警报两档渐强,DANGER 震动同步叠加
// 警报两档渐强,DANGER 震动同步叠加。
// F6(round2):警报音效同 600ms 窗口去重(双陨石同时逼近只起一波警报,
// 按 soft/loud 各自去重,单颗的两段渐强不受影响);danger 震 ≥400ms 去抖。
this._after(impactAt - ALARM_LEAD_MS - now, () => {
if (this.obstacles.has(ob)) {
this.audio.play('sfx/alarm', { volume: 0.5 });
vibrate('danger');
}
if (this.obstacles.has(ob)) this._alarm(0.5);
});
this._after(impactAt - ALARM_LEAD_MS / 2 - now, () => {
if (this.obstacles.has(ob)) {
this.audio.play('sfx/alarm', { volume: 1 });
vibrate('danger');
}
if (this.obstacles.has(ob)) this._alarm(1);
});
this._after(impactAt - now, () => this._resolve(ob));
}
/** 一段警报:同档 600ms 内只起一波(F6);danger 震全局限 ≥400ms 间隔。 */
_alarm(volume) {
const now = performance.now();
const key = volume < 1 ? '_alarmSoftAt' : '_alarmLoudAt';
if (now - this[key] < 600) return;
this[key] = now;
if (now - this._dangerAt >= 400) {
this._dangerAt = now;
vibrate('danger');
}
this.audio.play('sfx/alarm', { volume });
}
/** 撞击点判定:加速增益 > 同向滑动 > 护盾 > 保护期训练护盾 > 撞击。 */
_resolve(ob) {
if (!this.obstacles.has(ob) || this.phase !== 'playing') return;
@@ -508,12 +522,18 @@ export class SpaceGame extends Game {
} else {
const ids = [`voice/hit_${ob.dir}`];
if (this.hits === 2) ids.push('voice/hit_last');
this._speak(ids);
// 向导层(schemes §2):对局第 1 次受击追加指令式提示(hit_<dir> 已归因方位,
// 这句补"该怎么拨";已震 hit 免重复 error 震;onceKey 同局一次)
if (this.hits === 1) {
this.guide.error({ voice: 'gd_hit_hint', onceKey: 'space_hit_hint', skipHaptic: true });
}
// 向导层(round2 F1.4/F2.2):受击提示挂在归因链播完后的空闲点——
// idle-only 语义下若插在链头会被判为「对白在播」整段跳过。
// 第 1 次受击补指令式 gd_hit_hint(hit_<dir> 已归因方位,这句补"该怎么拨",
// onceKey 同局一次);第 2 次受击播 gd_last_life(F2.2:生存经济透明,
// 与游戏层 hit_last「船体只剩最后一次」互补"撑住"的口径),已震 hit 免重复。
this._speak(ids, () => {
if (this.hits === 1) {
this.guide.error({ voice: 'gd_hit_hint', onceKey: 'space_hit_hint', skipHaptic: true });
} else if (this.hits === 2) {
this.guide.error({ voice: 'gd_last_life', skipHaptic: true });
}
});
}
}