feat(platform/P1): 配对码跨设备 + 认领打磨 + 键盘语音双套资产(两 agent 并行 + 集成收口)
配对码与认领(B): - POST /api/pair/start|join:6 位码 10 分钟单次有效、仅存内存零泄露面、5 连错 per-IP 封锁 10 分钟;join=新设备档案按桶 LWW 并入老档案+token 改绑(复用 claim-merge);日志脱敏 - 大厅:生成配对码(speechSynthesis 逐位朗读+aria-live+重听)/从其他设备转入(one-time-code 输入框)/从本机移除/401 过期自愈引导/aria 补全/玩家名可听化 - api-smoke 12/12(配对全流程/频控/封锁/过期自愈/移除/digitsToCn)+ CDP 真浏览器 E2E 22/22 键盘语音双套资产(A,路线 a): - 164 变体句(8 游戏 grep 实测,超出调研预估 153:含轻触/点两下/按住变体)×2 性别=328 生成 656 文件 36.9 分钟;触屏主资产零字节改动(PCM 逐样本比对后还原) - 引擎 _candidates 三跳路由 key→gender→平铺(inputMode 跟随);缓存键 mode|gender|id;engine-input 新增 K9 路由断言(72 条) - check_all_assets 增 key 列(两性别一致且 ⊆ 主集)8 游戏全绿;voice_text.json 增 __key__ 表;live control.mjs key 会话对照键盘措辞验收(集成) - voice-ids 文档:8 游戏键盘变体小节由 KEY_LINES 程序化生成(与台词表逐字断言一致);补齐 storyline/escape/simon 三个历史缺失文档;design README 增 new8 索引 - mock 适配:playtest persona 加 maxTouchPoints=1 保持触屏链断言语义零改动 回归:12 套件全绿(8 playtest + guide 122 + engine-voice 19 + engine-input 72 + api-smoke 12/12) 同批入库:批次 2/3 已完稿设计文档(17-abyss/18-detective/19-cipher 全套,20-22 在途)
This commit is contained in:
@@ -14,7 +14,10 @@
|
||||
* 最近通道(keydown→key / touchstart→touch),初始按能力检测;
|
||||
* K7 immediateTap(rhythm):空格按下即 tap、长按补 tapcancel、双击改判——
|
||||
* 键盘路径与触摸路径行为逐条一致;
|
||||
* K8 detach 后键盘监听移除;孤儿 keyup 不产生事件。
|
||||
* K8 detach 后键盘监听移除;孤儿 keyup 不产生事件;
|
||||
* K9 voice 键盘变体路由(P1 路线 a):生效模式=key 时 audio.js 候选链前置
|
||||
* voice/<gender>/key/<id>(回退 gender → 平铺),touch 模式链与 P1 前一致;
|
||||
* 缓存键按输入模式分桶(key|<gender>|<id>)。
|
||||
*
|
||||
* 运行:tools/node/bin/node web/harness/engine-input.test.mjs
|
||||
*/
|
||||
@@ -245,6 +248,51 @@ assert(events.length === 0, 'detach 同时清掉挂起的定时器(无迟到 t
|
||||
keyUp('Space');
|
||||
assert(events.length === 0, '孤儿空格 keyup(无对应认领)无事件无异常');
|
||||
|
||||
// ---- K9 voice 键盘变体资产路由(P1 路线 a,engine/audio.js _candidates/_load)----
|
||||
console.log('\n=== K9 voice 键盘变体路由(key 候选前置 + 回退链) ===');
|
||||
{
|
||||
const detach9 = attachGestures(root, () => {}); // K8 已 detach:重新挂上才能用 keyDown 驱动 inputMode 跟随
|
||||
const { WebAudioEngine } = await import(path.join(WEB, 'engine', 'audio.js'));
|
||||
const engine = new WebAudioEngine('audio/');
|
||||
|
||||
settings.set({ input: 'auto' });
|
||||
keyDown('ArrowUp'); // 初始即 key(纯桌面),按键再确认一次
|
||||
assert(inputMode() === 'key', 'auto + 纯桌面 → 生效模式 key');
|
||||
let c = engine._candidates('voice/help');
|
||||
assert(c[0] === 'audio/voice/female/key/help.ogg', 'key 模式:候选链首跳 voice/<gender>/key/<id>.ogg');
|
||||
assert(c[4] === 'audio/voice/female/help.ogg', 'key 模式:第二跳回 voice/<gender>/<id>(主资产)');
|
||||
assert(c[8] === 'audio/voice/help.ogg', 'key 模式:第三跳平铺回退(触屏/角色句兜底)');
|
||||
assert(c.length === 12, 'key 模式:3 跳 × 4 扩展名 = 12 个候选');
|
||||
c = engine._candidates('voice/help.ogg');
|
||||
assert(c.length === 3 && c[0].endsWith('/key/help.ogg') && c[1].endsWith('/female/help.ogg'),
|
||||
'key 模式:带扩展名 id 同样三跳,key 在最前');
|
||||
|
||||
settings.set({ input: 'touch' });
|
||||
c = engine._candidates('voice/help');
|
||||
assert(!c.some((u) => u.includes('/key/')) && c[0] === 'audio/voice/female/help.ogg' && c.length === 8,
|
||||
'touch 模式:无 key 跳,候选链与 P1 前完全一致(gender → flat × 4 扩展)');
|
||||
|
||||
settings.set({ input: 'auto' });
|
||||
fireDoc('touchstart');
|
||||
c = engine._candidates('voice/help');
|
||||
assert(!c.some((u) => u.includes('/key/')), 'auto 运行时跟随:触摸后变体跳消失');
|
||||
keyDown('ArrowUp');
|
||||
c = engine._candidates('voice/help');
|
||||
assert(c[0] === 'audio/voice/female/key/help.ogg', 'auto 运行时跟随:键盘一按变体跳回来(语音随最近通道切措辞)');
|
||||
|
||||
// 缓存键按生效模式分桶:key/main 两套结果互不串缓存(与性别切换同语义)
|
||||
engine._cache.clear();
|
||||
await engine._load('voice/help'); // ctx 为 null → 直接 null,但仍写入缓存
|
||||
const keys = [...engine._cache.keys()];
|
||||
assert(keys.length === 1 && keys[0] === 'key|female|voice/help',
|
||||
'缓存键带输入模式:key 模式下为 key|<gender>|<id>');
|
||||
fireDoc('touchstart');
|
||||
await engine._load('voice/help');
|
||||
assert([...engine._cache.keys()].some((k) => k === 'main|female|voice/help'),
|
||||
'切回 touch 后同一 id 落独立 main|<gender>|<id> 缓存桶');
|
||||
detach9();
|
||||
}
|
||||
|
||||
console.log(`\n========================================`);
|
||||
console.log(`断言通过 ${passed} 条,失败 ${failures.length} 条`);
|
||||
if (failures.length) { failures.forEach((m) => console.log('FAILED: ' + m)); process.exit(1); }
|
||||
|
||||
Reference in New Issue
Block a user