feat(new8/R3): 双游戏收口修复轮 + 验证档案(radio G1-G6 / dungeon E0-1..E1-2)
- radio:帮助链去重(同等待点二次=beat震+earcon,全量口令38→20)/rep_low警告震/空点轻震/状态链重排(状态在前)/「首播必完整、复播可即掐」显式规则登记/G3核查为设计使然转断言;221→246断言 - dungeon:看门狗双轨制(无操作轨+无进展轨,30/60/90s三级升级到导航接管)/跳教学90s自动滚出(2403s→95s内)/踩楼梯到达即报贴身铁器/band1新词「带回来一点,不多」全管线重录/逃跑长按900ms+提示句;1610→1642断言 - R3 验证(进行中已入库部分):全量回归 9/9 全绿;R3 转写×4;P2/P3/P4 终审×6份(radio 满分档首评/三审,dungeon 4/4.5/3-4 全票愿意玩,无P0);两处独立定址同一残余=漫游型迷路刷不响无进展轨(R4 第一条) - 8套件+guide全绿;Mureka 累计 $0.87/$2.00
This commit is contained in:
+96
-15
@@ -34,6 +34,16 @@
|
||||
* - F4 断点续玩恢复后的首句播完才武装跳句(_resumeGuard → _skipLockId);
|
||||
* - F5 链末句跳过保护(等待/过场/抉择提示前的末句单击不可跳)。
|
||||
*
|
||||
* R3 打磨(research/new8/radio/round2/synthesis.md G1-G6):
|
||||
* - G1 帮助链去重:同一等待点第 2 次上滑起 sys_help 口令换「纯音频标记」
|
||||
* (beat 轻震 + nudge earcon,不加新台词方案),状态句照播;
|
||||
* - G2 rep_low 整句播报处(幕间跨档 + 各态上滑复听)一律附 warning 震;
|
||||
* - G3 k1_a/k1_b(糖糖 NPC 平铺组)male 设置下经引擎「性别目录→平铺」回退链
|
||||
* 播出,非静默——确认 + 断言 + 文档注记(设计即单音色,不补双性别资产);
|
||||
* - G4 不可跳句(p* 首播 / F5 尾句 / F4 锁)上的空点单击补 beat 轻震标记;
|
||||
* - G5「首播必完整、复播可即掐」升为显式规则(15-radio.md R3 节)+ 断言;
|
||||
* - G6 上滑状态链重排:「当前通号 → 口碑 → 帮助」(状态在前、帮助垫后)。
|
||||
*
|
||||
* 确定性:铃声为无缝循环垫(无定时器重触发),测试与转写可复现。
|
||||
*/
|
||||
import { Game } from '../engine/game.js';
|
||||
@@ -281,6 +291,7 @@ export class RadioGame extends Game {
|
||||
this.sysSkipShown = false; // sys_skip 开场每实例一次(storyline skip_hint 先例)
|
||||
this.saved = null; // ASK 态暂存的存档
|
||||
this._menuQueue = []; // 菜单项报名队列(游戏侧排队,不截断当前报名)
|
||||
this._helpSeen = new Set(); // G1:已全量播过 sys_help 口令的等待点(实例级,第 2 次起纯音频标记)
|
||||
|
||||
// F4 断点续玩跳句锁:_resume 置位,恢复链首句起播时落为该句 id,播完即清
|
||||
//(首句播完才武装跳句——ASK「单击=确认」与「单击=跳句」的语义陷阱,R2 F4)
|
||||
@@ -391,10 +402,13 @@ export class RadioGame extends Game {
|
||||
});
|
||||
break;
|
||||
case 'up': {
|
||||
// 状态播报(第几通 + 口碑档),留在 RING
|
||||
// 状态播报(第几通 + 口碑档),留在 RING——本就「状态在前」且无帮助句
|
||||
//(§2.3 RING 行),G6 重排天然满足;G2:rep_low 附 warning 震
|
||||
this.seqToken++;
|
||||
this.audio.stopVoice();
|
||||
const ids = [PROG_VOICES[Math.max(0, this.callN - 1)], TIER_VOICES[tierOf(this.rep)]];
|
||||
const tier = TIER_VOICES[tierOf(this.rep)];
|
||||
this._tierCue(tier);
|
||||
const ids = [PROG_VOICES[Math.max(0, this.callN - 1)], tier];
|
||||
this._playSeq(ids, () => {
|
||||
this.voicePlaying = false;
|
||||
if (this.phase === 'RING') this._armRingWatchdog();
|
||||
@@ -441,6 +455,11 @@ export class RadioGame extends Game {
|
||||
if (!this.promptPlaying && !this._isTailSentence()
|
||||
&& this.currentVoiceId !== this._skipLockId) {
|
||||
this.audio.skipVoice();
|
||||
} else {
|
||||
// G4(R1 P3-1 / R2 P3-4 遗留):不可跳句上的单击「点了但没选上」,
|
||||
// gesture 层确认震 [20] 游戏侧无法撤销——补一记最轻 beat [8] 形成
|
||||
// 可区分签名(20→8 连击 ≠ 真确认的单记 20),消解「像点上了」误导
|
||||
vibrate('beat');
|
||||
}
|
||||
} else if (node.prompt != null && this.choiceArmed) {
|
||||
this.armChoiceHint(); // 抉择等待态空点 = 重新挂载兜底(25s 计时重置)
|
||||
@@ -519,12 +538,17 @@ export class RadioGame extends Game {
|
||||
this._bridgeChain(this._bridgeAnnounce ? [this._bridgeAnnounce] : []);
|
||||
break;
|
||||
case 'up': {
|
||||
// 帮助 + 状态,自动链继续(垫乐喘息不被打断——链尾照常进下一通)
|
||||
// G6:状态(下一通 + 口碑档)在前、帮助垫后,自动链继续(垫乐喘息不被
|
||||
// 打断——链尾照常进下一通);G2:rep_low 附 warning 震;
|
||||
// G1:同一等待点第 2 次起口令换纯音频标记
|
||||
this.seqToken++;
|
||||
this.audio.stopVoice();
|
||||
const ids = ['voice/sys_help'];
|
||||
const ids = [];
|
||||
if (this.callN < CALLS.length) ids.push(PROG_VOICES[this.callN]); // 下一通
|
||||
ids.push(TIER_VOICES[tierOf(this.rep)]);
|
||||
const tier = TIER_VOICES[tierOf(this.rep)];
|
||||
this._tierCue(tier);
|
||||
ids.push(tier);
|
||||
this._withHelp(ids, 'B');
|
||||
this._bridgeChain(ids);
|
||||
break;
|
||||
}
|
||||
@@ -546,8 +570,12 @@ export class RadioGame extends Game {
|
||||
switch (g) {
|
||||
case 'tap':
|
||||
if (this.awaitingRestart) this.newNight();
|
||||
else if (this.voicePlaying && this.currentVoiceId !== this._skipLockId) {
|
||||
this.audio.skipVoice(); // F4:续玩恢复的首句播完才武装跳句
|
||||
else if (this.voicePlaying) {
|
||||
if (this.currentVoiceId !== this._skipLockId) {
|
||||
this.audio.skipVoice(); // F4:续玩恢复的首句播完才武装跳句
|
||||
} else {
|
||||
vibrate('beat'); // G4:续玩锁首句上的空点单击同款轻震标记
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'doubletap':
|
||||
@@ -558,12 +586,16 @@ export class RadioGame extends Game {
|
||||
this._playEndingChain();
|
||||
break;
|
||||
case 'up': {
|
||||
// 帮助 + 状态(结局主句=结局名播报 + 口碑档整句,§2.3「结局名+口碑」;
|
||||
// storyline P3-2 先例),播完回结算态
|
||||
// 状态(结局主句=结局名播报 + 口碑档整句,§2.3「结局名+口碑」;
|
||||
// storyline P3-2 先例)在前 + 帮助垫后(G6),G2:rep_low 附 warning 震,
|
||||
// G1:同一等待点第 2 次起口令换纯音频标记;播完回结算态
|
||||
this.seqToken++;
|
||||
this.audio.stopVoice();
|
||||
const def = ENDINGS[this.endingId] || ENDINGS.e1;
|
||||
this._playSeq(['voice/sys_help', def.voices[0], TIER_VOICES[tierOf(this.rep)]], () => {
|
||||
const tier = TIER_VOICES[tierOf(this.rep)];
|
||||
this._tierCue(tier);
|
||||
const ids = this._withHelp([def.voices[0], tier], `E:${this.endingId}`);
|
||||
this._playSeq(ids, () => {
|
||||
this.voicePlaying = false;
|
||||
if (this.phase === 'ENDING') {
|
||||
this.awaitingRestart = true;
|
||||
@@ -605,11 +637,15 @@ export class RadioGame extends Game {
|
||||
this._execMenuItem(MENU_ITEMS[this.menuIdx].id); // 双击确认执行
|
||||
break;
|
||||
case 'up': {
|
||||
// 帮助 + 菜单内设置提示 + 复述当前项(清空报名队列:帮助链顶掉一切)
|
||||
// 帮助 + 菜单内设置提示 + 复述当前项(清空报名队列:帮助链顶掉一切)。
|
||||
// MENU 链无通号/口碑状态句,G6 重排不适用(帮助保持在前);G1 适用:
|
||||
// 同一菜单等待点第 2 次起口令换纯音频标记,设置提示与当前项照播
|
||||
this._menuQueue = [];
|
||||
this.seqToken++;
|
||||
this.audio.stopVoice();
|
||||
this._playSeq(['voice/sys_help', 'voice/sys_settings', MENU_ITEMS[this.menuIdx].voice], () => {
|
||||
const ids = this._withHelp([], 'M');
|
||||
ids.push('voice/sys_settings', MENU_ITEMS[this.menuIdx].voice);
|
||||
this._playSeq(ids, () => {
|
||||
this.voicePlaying = false;
|
||||
});
|
||||
break;
|
||||
@@ -1113,15 +1149,60 @@ export class RadioGame extends Game {
|
||||
|
||||
// ---------------------------------------------------------------- 帮助 / 复述
|
||||
|
||||
/** 上滑:帮助 + 状态(第几通 + 口碑档整句),随后重播当前节点(局面复述)。 */
|
||||
/**
|
||||
* G2(R1 P3-3 遗留):rep_low 整句播报附 warning 震——「还没立起来」出口的
|
||||
* 同时手腕有感。原实现只在 BRIDGE 跨档播报时震,各态上滑复听不震,
|
||||
* TIER_VOICES 注释与行为不符(live 局 A t=99.6-102.0 RING 上滑复现)。
|
||||
*/
|
||||
_tierCue(tierId) {
|
||||
if (tierId === TIER_VOICES.low) vibrate('warning');
|
||||
}
|
||||
|
||||
/**
|
||||
* G1 帮助链去重(R3,压力局 sys_help 口令 38 次逐字重播,违反「提示永不逐字
|
||||
* 重复」公理 §11 #9)。不加新台词方案:同一等待点第 1 次全量口播 sys_help;
|
||||
* 第 2 次起改「纯音频标记」——beat 轻震 + nudge earcon(复用统一 8 音词表,
|
||||
* 零新增),状态句照播(复述状态=合法帮助,P1 先例)。等待点键:ONAIR/INTRO
|
||||
* 按节点、BRIDGE/菜单整段、ENDING 按结局;集合实例级(不跨实例持久化,
|
||||
* sys_skip 每实例一次同款口径——重进游戏首滑仍是完整口令)。
|
||||
* @returns {boolean} true = 本次应全量口播 sys_help
|
||||
*/
|
||||
_helpSpokenOnce(waitKey) {
|
||||
if (this._helpSeen.has(waitKey)) {
|
||||
vibrate('beat'); // 轻震标记(gesture 层确认震之外的 game 层「口令省略」签名)
|
||||
if (this.guide.level() !== 'off') {
|
||||
// earcon 直用 guide 合成器(复用 nudge「轻提醒」音,零新增词表;
|
||||
// guide 无公开单发 earcon API,_startRing 直用引擎总线同款先例)
|
||||
this.guide._playEarcon('nudge');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
this._helpSeen.add(waitKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** G6:状态在前、帮助垫后——把帮助句接在状态链尾(「当前通号 → 口碑 → 帮助」)。 */
|
||||
_withHelp(ids, waitKey) {
|
||||
if (this._helpSpokenOnce(waitKey)) ids.push('voice/sys_help');
|
||||
return ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上滑:状态(第几通 + 口碑档整句)在前 + 帮助垫后(G6 重排,round2 G6/
|
||||
* P1 建议「状态放最前」),随后重播当前节点(局面复述)。
|
||||
* INTRO 开播前无通号/口碑可报,仅帮助(或标记)后重播当前段(实现取舍登记)。
|
||||
*/
|
||||
_playHelpAndResume() {
|
||||
this.seqToken++;
|
||||
this.audio.stopVoice();
|
||||
const ids = ['voice/sys_help'];
|
||||
const ids = [];
|
||||
if (this.phase === 'ONAIR' && this.callN >= 1) {
|
||||
ids.push(PROG_VOICES[this.callN - 1]);
|
||||
ids.push(TIER_VOICES[tierOf(this.rep)]);
|
||||
const tier = TIER_VOICES[tierOf(this.rep)];
|
||||
this._tierCue(tier); // G2
|
||||
ids.push(tier);
|
||||
}
|
||||
this._withHelp(ids, `S:${this.nodeId}`); // G1:同一等待点第 2 次起纯音频标记
|
||||
this._playSeq(ids, () => {
|
||||
this.voicePlaying = false;
|
||||
if (this.phase === 'ONAIR' || this.phase === 'INTRO') this.playNode(this.nodeId);
|
||||
|
||||
Reference in New Issue
Block a user