feat(platform/P0): 无感用户系统 + PC 键盘双端一致(两 agent 并行 + 集成收口)

用户系统(B,游戏零改动):
- POST /api/device 首访 0 交互静默建档(自动玩家一/二…,per-IP 频控);断网降级纯本地照玩
- 大厅档案切换器(切换=flush 上云→快照回灌→倒灌→服务器回拉,profileId 分桶修同设备串档;aria menu 编号可听)
- claim 可选认领(命名+scrypt PIN);旧 users/sessions 单向迁移(PIN 明文即刻哈希,已真实数据验证);login 兼容+claim-merge 桶级 LWW;logout;/server/ 静态封禁
- PUT baseUpdatedAt 守卫(legacy 放行+警告);GET 无参全桶回拉(修 7/8 游戏跨设备只 PUT 不 GET)
- web/profiles.js 分桶纯函数 + api-smoke.mjs 7/7(建档/LWW/claim-merge/迁移/频控/封禁/分桶 H1-H4)

键盘双端(A,游戏代码零改动):
- gesture.js 键盘通道 +137 行:←↑↓→/WASD=滑动、空格=单击(走 300ms 双击窗与触摸共享虚拟指针)、回车=即时 doubletap、按住空格 ≥450ms=长按;repeat 抑制/表单守卫/方向键 preventDefault
- settings.input(auto/touch/key)+ gesture.inputMode();遮罩键盘解锁与文案自适应(按空格开始/轻触开始)
- 8 游戏遮罩改用自适应默认文案;大厅设置面板「操作方式」+页脚键位提示
- live control.mjs 键盘注入通道(CDP dispatchKeyEvent);engine-input.test 61 断言

回归:12 套件全绿(8 playtest + guide + engine-voice + engine-input + api-smoke 7/7);8001 服务器已重启加载新 API,/api/device 端到端验证通过

P1 待排:153 句×双性 TTS 键盘语音变体 / 6 位配对码跨设备 / 认领 UI 打磨(research/platform/00-synthesis.md §3)
This commit is contained in:
xsl
2026-09-09 23:39:23 +08:00
parent 3704cd0792
commit ede522dda1
22 changed files with 2370 additions and 275 deletions
+44 -2
View File
@@ -1,10 +1,10 @@
# 00 · 全局交互规范(Global UX)
> **给开发者的实现要点摘要**
> 1. 设置项(voice / speed / bgm / guide)只在大厅页用 `<select>` 可视化 UI 修改,存 `localStorage["soundgame.settings"]`,由 `engine/settings.js` 管理;游戏启动时只读生效,游戏内不提供修改入口(菜单里只播报当前值)。**R1 已落地,本文 §1/§2/§4 已按实现修订(R2 修订版);R7 增补 guide 设置与向导层(§6,见 14 号文档)。**
> 1. 设置项(voice / speed / bgm / guide / input)只在大厅页用 `<select>` 可视化 UI 修改,存 `localStorage["soundgame.settings"]`,由 `engine/settings.js` 管理;游戏启动时只读生效,游戏内不提供修改入口(菜单里只播报当前值)。**R1 已落地,本文 §1/§2/§4 已按实现修订(R2 修订版);R7 增补 guide 设置与向导层(§6,见 14 号文档);P0-A 增补 input 设置(§1.1,见 §5.5)。**
> 2. 所有提示音反馈改为震动:`engine/haptics.js`(**已实现**,震型名小写:`confirm/error/success/warning/swipe/heartbeat`);iOS Safari 不支持时**静默降级,绝不回退到提示音**。
> 3. 旁白全部整句预生成:禁止数字/碎片拼接(音色漂移根因),每款游戏旁白固定同一 `speaker` + 同一 `instruct` 常量。
> 4. 手势全局约定:上滑=帮助/状态、下滑=菜单、单击=确认/继续、双击=重听、左右滑=选择/移动;以滑动为核心玩法的游戏可偏离(见 §5 豁免表),但必须在开场交互教学中讲清。
> 4. 手势全局约定:上滑=帮助/状态、下滑=菜单、单击=确认/继续、双击=重听、左右滑=选择/移动;以滑动为核心玩法的游戏可偏离(见 §5 豁免表),但必须在开场交互教学中讲清。**PC 键盘平行通道(§5.5,P0-A 已实现)不改变这些语义——同一手势宪法、同一状态机,仅多一条输入通路。**
> 5. 用户系统:大厅页登录,进度经 `web/server/server.js` REST API 存服务器(**已实现**),本地 localStorage 做离线缓存。
---
@@ -19,6 +19,7 @@
| `speed` | `1.0`(默认)/ `1.35` / `1.7` / `2.2` | **只作用于 voice 层**的 `playbackRate`(创建 voice source 时实时读取,**从下一句开始生效**,正在播的句子不变速)。BGM、sfx 速率不变。TTS 一律按 1.0 语速生成;`instruct` 里禁止出现语速描述词。**2.2 档为视障档**(R7 增补:资深读屏用户听速 250+ WPM,见 research/01-literature/03)。 |
| `bgm` | `1.0`(默认)/ `0.3` / `0` | BGM 层独立增益总线系数。`0` = 不加载、不播放任何 `bgm/` 资产(`_playBgm` 短路),ducking 逻辑保持原样。改设置即时生效(已实现 subscribe 机制)。 |
| `guide` | `"full"`(默认)/ `"standard"` / `"off"` | **向导帮助等级**(R7 增补,engine/guide.js 实时读取,见 §6 与 14 号文档):`full`=签名 earcon+向导语音+震动(新手全托管);`standard`=只留 earcon+震动(无向导语音,尊重专家);`off`=全静且**不挂任何看门狗定时器**(老手/回归测试)。 |
| `input` | `"auto"`(默认)/ `"touch"` / `"key"` | **提示文案输入模式**(P0-A 增补,`gesture.inputMode()` 读取):`auto`=跟随最近使用的通道(键盘 keydown→key,任意 touchstart→touch,会话内无输入时按能力检测);`touch`/`key`=手动覆盖。**只影响提示文案**(如启动遮罩「轻触开始」/「按空格开始」),**不关闭任何输入通道**——三通道永远并行(混合设备)。 |
存储:localStorage key = `soundgame.settings`,值形如:
@@ -174,6 +175,47 @@ PUT /api/progress { game, data } (Bearer) -> 200 { ok, game }
- 单击有 ~300ms 引擎级延迟:即时判定(格挡、节奏打击)一律绑滑动或双击,单击只用于非即时操作(确认、检查、菜单)。新设计继续遵守。**唯一例外**:rhythm 开启引擎 `immediateTap`(按下即发 tap、双击补发改判),因它的单击就是核心演奏输入;其他游戏不得开(每次按下都先发 tap,会污染滑动/长按语义)。
- 结算画面统一:单击=再来一局,下滑=菜单(回大厅)。教学可跳过:任何游戏的引导阶段,下滑=跳过引导(首次强制完整引导的标志位存进度里)。
### 5.5 键盘平行通道(P0-A 已实现:engine/gesture.js 内置通道)
> 调研与业界依据:[research/platform/02-input-parity.md](../../research/platform/02-input-parity.md)(audiogames.net 社区惯例、A Blind Legend / Blind Drive 双端先例)。
PC 键盘是触屏手势的**平行输入通道**,不是另一套手势宪法:键盘事件在
`attachGestures` 内部喂入与 touch/mouse **同一状态机**,游戏层收到的
7 种手势事件(up/down/left/right/tap/doubletap/longpress/longpressend
+ immediateTap 的 tapcancel)与内置震动**完全同构**——游戏代码零改动,
§5.1-§5.4 的全部手势语义(含各游戏豁免表)原封不动。
**键位表(裁定方案,`e.code`):**
| 手势(§5.1 语义不变) | 键盘主键 | 别名 | 行为差异 |
|---|---|---|---|
| 左滑 / 右滑 | ← / → | A / D | keydown 即发(无位移/速度判定,零延迟) |
| 上滑 / 下滑 | ↑ / ↓ | W / S | 同上 |
| 单击 | 空格 | — | 走完整 tap 状态机:**保留 ~300ms 双击窗**(与触屏单击同感) |
| 双击 | **回车** | 空格×2 | 回车**即时**触发(不等双击窗);空格两连也判双击(状态机路径) |
| 长按 / 长按结束 | 按住空格 ≥450ms / 松开 | — | 与触摸长按完全一致(含旁白快进 window 事件) |
设计裁定(P0-A,均已实现并回归):
1. **空格也走 300ms 双击窗**(与触摸共享窗口,混合设备"触屏 tap + 空格
tap"判 doubletap = 同一虚拟指针语义);**回车是唯一的即时双击键**——
确认类操作零延迟,同时保留空格×2 兼容触屏肌肉记忆。
2. **不做 Esc 映射**:下滑已覆盖菜单语义(§5.1/§5.2),Esc 留给浏览器
自身/未来全屏退出,不进入手势域。
3. **守卫**:`e.repeat` 抑制(按住只发一次);映射键 preventDefault
(防滚页/焦点激活);表单控件焦点(input/textarea/select/
contenteditable)放行;未映射键(Tab 等)不拦截;tracking 单飞行
指针互斥(与多指忽略一致)。
4. **提示随输入模式**(settings.input,见 §1.1):三通道永远并行在线,
模式只切提示文案(遮罩「轻触开始」/「按空格开始」),供后续游戏内
提示与语音变体(input-parity 路线 a)接入。
5. 遮罩键盘解锁:空格/回车 keydown 即可解锁音频(audio.js
showStartOverlay),PC 玩家全程无需鼠标。
6. 测试:`web/harness/engine-input.test.mjs`(61 断言);live 管线
`POST /gesture {"keyboard":true,"key":"ArrowLeft"|"Space"|"Enter"}`
或 `{"type":"tap","keyboard":true}`(CDP Input.dispatchKeyEvent,
会话剧本加 `keyboard:true` 即可键盘复跑)。
## 6. 向导层(R7 增补,已实现:engine/guide.js)
> 设计裁定与台词表全录:[14-guide-layer.md](14-guide-layer.md)。本节只列与全局约定交叉的部分。