Files
SoundGame/web/engine/novice.js
T
xsl ee5d4d2544 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 置完成。
2026-10-02 12:33:20 +08:00

99 lines
3.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* novice.js — 新手手势模式与解锁阶梯(26 号文档 §4.3,P0 第 1 项 + P3 第 11 项)。
*
* 新手手势表(全部游戏共用,原则 6):
* | 手势 | 唯一含义 | 新手期(阶段 0) |
* |------------|------------------------|---------------------------------|
* | tap | 继续 / 确定 / 听一下 | 开放 |
* | left/right | 选择 / 移动 | 开放 |
* | up | 我在哪、我该干嘛 | 阶段 1 解锁(完成第一个小目标) |
* | down | 菜单 | 阶段 2 解锁(完成第一局) |
* | doubletap | 重听 | 阶段 3 解锁(累计 3 局);未解锁按单击处理 |
* | longpress | 帮助(四向玩法类游戏) | 仅「滑动是玩法」的游戏开放(§4.3 裁定) |
*
* 解锁进度跨局持久(localStorage),全局共享(手势表全项目一致,学会一次就算会)。
* 游戏在对应时机调 onGoal() / onGameDone(),返回新解锁的手势(null=无新解锁),
* 随后按原则 11 教学:先说一句(teachLine),马上用一次。
*/
const STORAGE_KEY = 'soundgame.gestures';
/** 解锁教学句 id(每游戏目录各存一份同文案资产,§4.3 阶梯表)。 */
// 带 voice/ 层前缀,可直接进 floor.speak(2026-10 修复:此前返回裸 id 被 audio.play 按未知层丢弃)
const TEACH_IDS = {
up: 'voice/sys_unlock_up', // 「想知道自己在哪,就向上滑。」
down: 'voice/sys_unlock_down', // 「向下滑,可以打开菜单。」
doubletap: 'voice/sys_unlock_dt', // 「没听清,就双击,再听一遍。」
};
const UNLOCK_AT = { 1: 'up', 2: 'down', 3: 'doubletap' };
export class GestureLadder {
constructor(key = STORAGE_KEY) {
this.key = key;
}
_load() {
try {
const raw = localStorage.getItem(this.key);
if (!raw) return { stage: 0, games: 0 };
const o = JSON.parse(raw);
return {
stage: Math.max(0, Math.min(3, Number(o.stage) || 0)),
games: Math.max(0, Number(o.games) || 0),
};
} catch (_) {
return { stage: 0, games: 0 };
}
}
_save(o) {
try { localStorage.setItem(this.key, JSON.stringify(o)); } catch (_) { /* 隐私模式 */ }
}
/** 当前阶段 0..3。 */
stage() { return this._load().stage; }
/** 手势是否已解锁(tap/left/right 永远开放;longpress 由游戏按玩法裁定)。 */
has(g) {
if (g === 'tap' || g === 'left' || g === 'right') return true;
if (g === 'up') return this.stage() >= 1;
if (g === 'down') return this.stage() >= 2;
if (g === 'doubletap') return this.stage() >= 3;
return false; // longpress 等:走游戏自己的核心玩法裁定
}
/** 完成第一个小目标 → 阶段 1。返回新解锁手势或 null。 */
onGoal() { return this._unlock(1); }
/** 完成一局 → games+1;第 1 局解锁阶段 2,第 3 局解锁阶段 3。 */
onGameDone() {
const o = this._load();
o.games += 1;
this._save(o);
let got = null;
if (o.games >= 1) got = this._unlock(2) || got;
if (o.games >= 3) got = this._unlock(3) || got;
return got;
}
_unlock(stage) {
const o = this._load();
if (o.stage >= stage) return null;
o.stage = stage;
this._save(o);
return UNLOCK_AT[stage];
}
/** 老玩家在大厅一键全部解锁。 */
unlockAll() {
const o = this._load();
o.stage = 3;
this._save(o);
}
/** 解锁教学句 voice id。 */
teachLine(g) { return TEACH_IDS[g] || null; }
}
export default GestureLadder;