feat(playbook): 26 号交互手册全量落地——剩余 12 款深度重构 + 语音全量重制

storyline/escape/simon 首批之后的推广收官:15 款全部按 26 号模式运行
(发言权/回执/三档提示/迷茫接管/手势阶梯/断点复述),旧「双击重听/格挡/
菜单确认」等反模式口径全线废除,开场零规则念白(首句即动手)。

游戏层(12 款):rpg(格挡=前摇窗口内再单击)/ rhythm / space / radio /
dungeon / abyss / detective / cipher / teahouse / keeper / bridge /
hunt(联机切片):
- 每款 game.js 接引擎六模块 + makeInputPipeline(doubleTapWindow:0);
  四向玩法类 swipeCore + 长按帮助;双击是玩法的(space 护盾 / hunt
  旋风斩)按手势阶梯阶段 3 分档恢复双击窗口(doubletapCore)
- 每款 playtest 重写:玩法断言 + §6.2 乱点机器人(无声操作 / 被动
  原句重复 / 打断失败 = 0);15 款 3900+ 断言全绿
- 12 份 docs/design/26-apply-*.md 施工单 + tools/voice_lines_26/
  台词清单 + voice-ids 文档同步 + 转写会话剧本按新口径重写

引擎层(web/engine/,6 处修复):pipeline 增 doubletapCore;novice
teachLine 带 voice/ 前缀(此前教学句被静默丢弃);confusion.record
前置到新手门(锁定手势的乱点也计信号);InvalidHints 播完恢复解释前
状态(根治孤儿 BUSY);WaitPoint takeover(2) 台账去重;台账只在
speak 真正播出时登记。遗留引擎债登记 roadmap §3.1。

语音资产:台词表合并(新增 ~250 系统句 / 改 ~50 旧口径 / 删 ~110
废止句;合并器 tools/merge_voice_lines_26.py + 引用校验器
tools/validate_voice_refs.py),GPU 重录 1536 wav → 4204 ogg/m4a
落地,check_all_assets + validate_voice_refs 双绿;键盘变体同步扩表。

大厅/文档:15 款统一新交互口径(「新交互」徽标退役);README /
HANDOVER / rpg README 旧口径修正;roadmap P0-P3 置完成。
This commit is contained in:
xsl
2026-10-02 12:33:20 +08:00
parent 9e4d67aa84
commit ee5d4d2544
6992 changed files with 30127 additions and 20451 deletions
+13 -5
View File
@@ -46,37 +46,45 @@ const REAL_GESTURES = new Set(['tap', 'doubletap', 'up', 'down', 'left', 'right'
* 游戏的帮助键,§4.3 裁定);false 时 longpress → invalid
* @param {boolean} [deps.opts.swipeCore=false] 四向滑动是本游戏核心玩法(§4.3 裁定):
* up/down 不按阶梯锁(它们是移动,不是帮助/菜单);阶梯只管 doubletap
* @param {boolean} [deps.opts.doubletapCore=false] 双击是本游戏核心玩法输入(如护盾/
* 旋风斩,§4.3 阶段 4「游戏专属高级手势」):doubletap 不折算成 tap,直接
* 放行给游戏(此类游戏 attachGestures 保留双击判定窗口)
*/
export function makeInputPipeline({ floor, confusion, receipts, invalidHints, game, ladder, opts = {} }) {
const lad = ladder || new GestureLadder();
const longpressCore = (opts && opts.longpressCore) === true;
const swipeCore = (opts && opts.swipeCore) === true;
const doubletapCore = (opts && opts.doubletapCore) === true;
let lastTapActedAt = -Infinity;
return (g, info) => {
if (g === 'longpressend' || g === 'tapcancel') return; // 手势收尾/撤销噪声,不吃回执
// ---- 1. 新手手势门(§4.3)----
if (g === 'doubletap' && !lad.has('doubletap')) g = 'tap'; // 双击按一次单击处理
// ---- 1. 迷茫检测(§4.5):所有真实手势先喂信号(含被锁手势——乱点就是乱点;
// 2026-10 修复:此前锁定门在 record 之前 return,锁定手势的乱点不触发接管)----
if (REAL_GESTURES.has(g)) confusion.record(g);
// ---- 2. 新手手势门(§4.3)----
if (g === 'doubletap' && !doubletapCore && !lad.has('doubletap')) g = 'tap'; // 双击按一次单击处理
if (!swipeCore && g === 'up' && !lad.has('up')) {
receipts.play('invalid');
invalidHints.explainOnce('locked:up', game.invalidHint ? game.invalidHint('locked', 'up') : null);
confusion.recordInvalid();
return;
}
if (!swipeCore && g === 'down' && !lad.has('down')) {
receipts.play('invalid');
invalidHints.explainOnce('locked:down', game.invalidHint ? game.invalidHint('locked', 'down') : null);
confusion.recordInvalid();
return;
}
if (g === 'longpress' && !longpressCore) {
receipts.play('invalid');
invalidHints.explainOnce('locked:longpress', game.invalidHint ? game.invalidHint('locked', 'longpress') : null);
confusion.recordInvalid();
return;
}
// ---- 2. 迷茫检测(§4.5)----
if (REAL_GESTURES.has(g)) confusion.record(g);
// ---- 3. 防连点(§4.2):tap 刚执行过「继续/跳过」后的 400ms 内,再来的 tap
// 只给 busy 回执(无论它将被解释成跳句还是继续——都不执行)----
if (g === 'tap' && floor.state !== 'ACTION' && performance.now() - lastTapActedAt < TAP_GUARD_MS) {