feat(playbook): 26 号 P0 推广至全部 15 款游戏——兼容输入管道 + 可闻度调优
真人首测反馈「交互还是很迷惑」:大厅 15 款里只有 3 款新交互,其余 12 款 仍是旧版(无声操作、连点连跳),整体体验割裂。本次: - engine/pipeline.js 新增 makeCompatPipeline(纯增量,游戏内部逻辑零改动): · 手势后 100ms 无任何音频调度 → invalid 回执(消灭无声操作,原则 4) · 语音中的手势 → 立即 ok 回执(§4.1 打断即收到) · 语音中 tap 400ms 防连点(§4.2,点两下不连跳两句) · longpressend/tapcancel 收尾噪声直通 - engine/audio.js 加 playCount 探针(回执启发式用) - 12 款游戏 index.html 接入兼容管道(rhythm 保留 immediateTap; 双击判定窗口不动——多款游戏双击是核心玩法/菜单确认,重映射会破坏) - 可闻度调优(真人首测:笔记本外放偏轻,26 号 §3「起步值,真人测试后再调」): 回执增益 ok 0.08→0.11 / invalid 0.10→0.13 / busy 0.04→0.06; 等待声 0.03→0.055 - 大厅:三款深度重构游戏(声命线/暗室/音序回响)置顶 + 「新交互」徽标, 卡片文案直接写玩法手势,测试不再误入旧交互游戏 测试:新增 compat-pipeline.test(9 断言);全部 15 款 playtest 回归通过 (共 3900+ 断言,0 失败);引擎 input/voice/guide 回归通过。
This commit is contained in:
@@ -113,6 +113,7 @@ export class WebAudioEngine {
|
||||
this._rateBoostEnabled = true; // 长按快进开关(setRateBoostEnabled per-game 控制)
|
||||
this._voiceFuseLog = []; // [{ id, t }] 保险丝调度流水(play 入口记录)
|
||||
this._voiceFuseErrored = new Set(); // 已报过 console.error 的 id(每 id 一次)
|
||||
this.playCount = 0; // play() 累计调用数(输入管道回执启发式探针,26 号 §4.2)
|
||||
|
||||
// 设置变更实时生效
|
||||
settings.subscribe((_, changed) => this._onSettingsChange(changed));
|
||||
@@ -239,6 +240,7 @@ export class WebAudioEngine {
|
||||
play(id, opts = {}) {
|
||||
const slash = id.indexOf('/');
|
||||
const layer = slash > 0 ? id.slice(0, slash) : '';
|
||||
this.playCount++; // 管道回执探针:本次手势是否带来任何音频(含资产缺失的尝试调度)
|
||||
if (!this.unlocked) {
|
||||
console.warn('[WebAudioEngine] 音频尚未解锁(需先 showStartOverlay/unlock),跳过:', id);
|
||||
return;
|
||||
|
||||
+2
-1
@@ -31,7 +31,8 @@ const BED = {
|
||||
f1: 220, f2: 330, // 两声「嘟—嘟」
|
||||
toneMs: 60, gapMs: 180, // 每声 60ms,两声间隔 180ms
|
||||
loopMs: 1400, slowMs: 2500,// 默认每 1.4s 重复;20s 后 2.5s
|
||||
gain: 0.03, // ≈ guide 提示音增益 0.12 的四分之一
|
||||
gain: 0.055, // ≈ guide 提示音增益 0.12 的一半(0.03 笔记本外放偏轻,
|
||||
// 真人首测调高;手机外放仍远轻于语音)
|
||||
slowAfterMs: 20000,
|
||||
fadeOutMs: 80, // 玩家一操作,等待声 80ms 内淡出
|
||||
};
|
||||
|
||||
@@ -122,3 +122,53 @@ export function makeInputPipeline({ floor, confusion, receipts, invalidHints, ga
|
||||
}
|
||||
|
||||
export default makeInputPipeline;
|
||||
|
||||
/**
|
||||
* 兼容管道 —— 未做深度改造的游戏(P0 层,26 号 §5「先把通用部分抽进引擎,推广到
|
||||
* 其他游戏」)。纯增量:游戏内部逻辑、手势语义、双击判定窗口一概不动,只在外围
|
||||
* 补三件事:
|
||||
*
|
||||
* 1. 消灭无声操作(原则 4):手势后 100ms 内游戏没有调度任何音频 → invalid 回执
|
||||
* (旧版「点了没反应」是真人测试的头号迷惑源)。
|
||||
* 2. 语音中的手势 → 立即 ok 回执(§4.1 打断即收到);tap 额外进 400ms 防连点窗口
|
||||
* (点两下不连跳两句,§4.2)。
|
||||
* 3. longpressend / tapcancel 收尾噪声直通,不吃回执。
|
||||
*
|
||||
* 游戏自带的内容音(脚步、音色、语音)即反馈,不叠回执(§4.2「已有明显内容音可
|
||||
* 省 ok」)。深度改造(发言权/等待点/接管)仍按 26-apply-* 设计单逐款推进。
|
||||
*
|
||||
* @param {{ audio: import('./audio.js').WebAudioEngine,
|
||||
* receipts: import('./receipts.js').Receipts,
|
||||
* game: { onGesture: Function } }} deps
|
||||
*/
|
||||
export function makeCompatPipeline({ audio, receipts, game }) {
|
||||
let lastSkipAt = -Infinity;
|
||||
|
||||
return (g, info) => {
|
||||
if (g === 'longpressend' || g === 'tapcancel') return game.onGesture(g, info);
|
||||
|
||||
const voicePlaying = audio.isVoicePlaying();
|
||||
|
||||
// 防连点:语音中 tap=跳过/继续(各游戏既有语义),400ms 内的第二下只给 busy
|
||||
if (g === 'tap' && voicePlaying && performance.now() - lastSkipAt < 400) {
|
||||
receipts.play('busy');
|
||||
return;
|
||||
}
|
||||
|
||||
const before = audio.playCount;
|
||||
const r = game.onGesture(g, info);
|
||||
|
||||
if (voicePlaying) {
|
||||
// 语音中的任何手势 = 打断/跳过语义:立即 ok(§4.1 打断即收到)
|
||||
receipts.play('ok');
|
||||
if (g === 'tap') lastSkipAt = performance.now();
|
||||
return r;
|
||||
}
|
||||
|
||||
// 100ms 内无任何音频调度 → invalid(消灭无声操作)
|
||||
setTimeout(() => {
|
||||
if (audio.playCount === before) receipts.play('invalid');
|
||||
}, 100);
|
||||
return r;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,12 +18,13 @@
|
||||
|
||||
/** 合成参数(26 号文档 §4.2 表:合成参数(起步值))。 */
|
||||
const SPECS = {
|
||||
// 三角波 1600Hz,35ms,起音 2ms,快速衰减,增益 0.08 —— 清脆的「嗒」
|
||||
ok: { wave: 'triangle', from: 1600, to: 1600, dur: 0.035, attack: 0.002, gain: 0.08 },
|
||||
// 正弦波 260Hz 滑到 200Hz,120ms,起音 10ms,增益 0.10 —— 低沉柔和的「咚」
|
||||
invalid: { wave: 'sine', from: 260, to: 200, dur: 0.12, attack: 0.01, gain: 0.10 },
|
||||
// 正弦波 520Hz,40ms,增益 0.04 —— 很轻的「嗯」
|
||||
busy: { wave: 'sine', from: 520, to: 520, dur: 0.04, attack: 0.005, gain: 0.04 },
|
||||
// 三角波 1600Hz,35ms,起音 2ms,快速衰减 —— 清脆的「嗒」
|
||||
// (增益 0.08→0.11:真人首测反馈笔记本外放偏轻,26 号 §3「参数是起步值,真人测试后再调」)
|
||||
ok: { wave: 'triangle', from: 1600, to: 1600, dur: 0.035, attack: 0.002, gain: 0.11 },
|
||||
// 正弦波 260Hz 滑到 200Hz,120ms,起音 10ms —— 低沉柔和的「咚」(0.10→0.13)
|
||||
invalid: { wave: 'sine', from: 260, to: 200, dur: 0.12, attack: 0.01, gain: 0.13 },
|
||||
// 正弦波 520Hz,40ms —— 很轻的「嗯」(0.04→0.06)
|
||||
busy: { wave: 'sine', from: 520, to: 520, dur: 0.04, attack: 0.005, gain: 0.06 },
|
||||
};
|
||||
|
||||
export class Receipts {
|
||||
|
||||
Reference in New Issue
Block a user