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:
+3
-1
@@ -155,7 +155,9 @@ server {
|
||||
## 以后若要开放用户系统
|
||||
|
||||
不要把 `server.js` 绑到 `0.0.0.0`。至少做到:监听 `127.0.0.1`、Nginx 只反代
|
||||
`/api/`、加 `limit_req`、PIN 改哈希、token 设过期。在此之前保持静态-only。
|
||||
`/api/`、加 `limit_req`、数据目录定期备份。PIN 哈希(scrypt)与 token 过期/吊销
|
||||
已在 P0-B 无感档案系统落地(`web/server/README.md`),公网开放前剩余前置:
|
||||
限流、单实例约束、备份与隐私政策。在此之前保持静态-only(大厅自动降级为本地档案切换,仍可完整游玩)。
|
||||
|
||||
## 冒烟
|
||||
|
||||
|
||||
@@ -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)。本节只列与全局约定交叉的部分。
|
||||
|
||||
+62
-4
@@ -12,7 +12,7 @@ web/
|
||||
├── index.html # 游戏大厅(深色风格,5 个游戏入口 + 用户/设置区)
|
||||
├── serve.sh # 本地托管脚本(node web/server/server.js)
|
||||
├── engine/ # 共享引擎 —— 所有游戏复用,勿修改
|
||||
│ ├── gesture.js # 6 手势识别(触摸+鼠标,内置基础震动反馈)
|
||||
│ ├── gesture.js # 6 手势识别(触摸+鼠标+键盘三通道,内置基础震动反馈)
|
||||
│ ├── audio.js # Web Audio 三层音频引擎 + 启动遮罩(自动应用用户设置)
|
||||
│ ├── settings.js # 全局用户设置(voice/speed/bgm/guide,localStorage 持久化)
|
||||
│ ├── haptics.js # navigator.vibrate 震动封装(预定义震型 + iOS 视觉降级)
|
||||
@@ -122,8 +122,11 @@ new WebAudioEngine(basePath = 'audio/', opts?)
|
||||
|
||||
- `unlock(): Promise<void>` — 创建/恢复 AudioContext。已在用户手势中时可用;
|
||||
一般用 `showStartOverlay()` 代替。
|
||||
- `showStartOverlay({ cover, text = '轻触开始' }): Promise<void>` —
|
||||
启动遮罩,resolve 时音频已解锁。
|
||||
- `showStartOverlay({ cover, text }): Promise<void>` — 启动遮罩,resolve 时
|
||||
音频已解锁。解锁手势:触摸 / 鼠标 click / **键盘空格或回车 keydown**
|
||||
(keydown 同属 user activation,回调内同步发起 `resume()` 即可解锁——
|
||||
PC 键盘玩家无需伸手摸鼠标)。`text` 缺省按 `gesture.inputMode()` 自适应
|
||||
(key →「按空格开始」,touch →「轻触开始」);显式传 `text` 完全覆盖。
|
||||
- `play(id, opts?)` — 统一入口,按 id 前缀(`bgm/` `voice/` `sfx/`)分发:
|
||||
- 公共:`opts.volume`(0~1,乘层音量)
|
||||
- BGM:`opts.fadeMs`、`opts.loop = true`;同一首重复 `play` 不重启
|
||||
@@ -238,11 +241,16 @@ guide.setEventHook(fn); // 转写 harness 用:fn({type,name,vo
|
||||
```js
|
||||
import { settings } from '../engine/settings.js';
|
||||
|
||||
settings.get(); // -> { voice: 'male'|'female', speed: 1|1.35|1.7|2.2, bgm: 1|0.3|0, guide: 'full'|'standard'|'off' }
|
||||
settings.get(); // -> { voice, speed, bgm, guide, input }
|
||||
settings.set({ speed: 1.35 }); // 部分更新;非法键/值被忽略(console.warn)
|
||||
const off = settings.subscribe((snapshot, changed) => { ... }); // off() 退订
|
||||
```
|
||||
|
||||
- 五项设置:`voice`(`'male'|'female'`)、`speed`(`1|1.35|1.7|2.2`)、
|
||||
`bgm`(`1|0.3|0`)、`guide`(`'full'|'standard'|'off'`)、
|
||||
`input`(`'auto'|'touch'|'key'`,P0-A 新增——提示文案输入模式,
|
||||
`gesture.inputMode()` 读取;只影响提示,不关闭任何输入通道)。
|
||||
|
||||
- 非法值永远不会进入存储——读入时逐项校验,坏数据自动回退默认值。
|
||||
- 游戏侧一般**不需要**直接用本模块:audio.js 已自动应用三项设置。
|
||||
需要自定义响应(如设置页 UI)时才 `subscribe`。
|
||||
@@ -338,6 +346,56 @@ const detach = attachGestures(element, onGesture, config?)
|
||||
`onGesture` 签名因此扩为:`'up'|'down'|'left'|'right'|'tap'|'doubletap'|
|
||||
'longpress'|'longpressend'|'tapcancel'`。
|
||||
|
||||
### 键盘通道(PC 平行输入,P0-A)
|
||||
|
||||
`attachGestures` 内置第三条输入通道(与 touch/mouse 并列,**喂同一状态机**,
|
||||
游戏层收到的手势事件与震动反馈完全同构——游戏代码零改动)。设计依据与
|
||||
业界惯例见 `research/platform/02-input-parity.md`。
|
||||
|
||||
| 手势(触屏语义) | 键盘主键 | 别名 | 行为 |
|
||||
|---|---|---|---|
|
||||
| 左/右滑 | ← / → | A / D | keydown 即发,零延迟(无位移/速度判定) |
|
||||
| 上/下滑 | ↑ / ↓ | W / S | 同上 |
|
||||
| 单击 | 空格 | — | 走完整 tap 状态机:抬起判 tap,**保留 ~300ms 双击窗**(与触屏同感) |
|
||||
| 双击 | **回车** | 空格×2 | 回车**即时**触发(不等双击窗,确认类零延迟);空格连按两下走状态机同样判出 |
|
||||
| 长按 | 按住空格 ≥450ms | — | `longpress`,松开 `longpressend`(旁白快进 window 事件照发) |
|
||||
|
||||
行为要点(`web/harness/engine-input.test.mjs` 全量断言,61 条):
|
||||
|
||||
- **空格 = 虚拟指针**:keydown 落元素中心进 `handleDown`,keyup 收尾——
|
||||
tap/双击窗/长按定时器全部复用触摸路径;双击窗与触摸**共享**(混合设备
|
||||
"触屏点一下 + 空格点一下"也判 doubletap,语义 = 同一虚拟指针)。
|
||||
- **回车即时双击**:直接走 doubletap 分支(含 confirm 震动),同时作废
|
||||
等待中的单击(与触屏双击的收尾语义一致)。
|
||||
- **immediateTap 游戏(rhythm)零改动**:空格按下即发 tap(kick 演奏)、
|
||||
长按补 `tapcancel`、双击改判——键盘路径逐条对齐触摸路径。
|
||||
- **守卫**:`e.repeat` 抑制(按住 ↑ 只发一次 up;映射键 repeat 仍
|
||||
preventDefault 防连滚);映射键 keydown 一律 `preventDefault`(防方向键/
|
||||
空格滚页、回车激活焦点元素);焦点在 `INPUT/TEXTAREA/SELECT/
|
||||
contenteditable` 时放行不吞键(大厅打字正常);未映射键不拦截(Tab 等
|
||||
无障碍键放行);`tracking` 单飞行指针互斥(空格按住期间方向键/触摸互斥,
|
||||
与多指忽略语义一致)。
|
||||
- **无 Esc 映射**(裁定:下滑已覆盖菜单语义,Esc 留给浏览器/未来全屏)。
|
||||
- 监听挂 `window`(游戏页焦点在 body,冒泡必达),`detach()` 时移除;
|
||||
键盘监听在无 `window` 的 mock 环境(playtest/转写 harness)自动跳过,
|
||||
对既有触摸路径零影响。
|
||||
|
||||
**提示随输入模式(`settings.input` + `inputMode()`)**:
|
||||
|
||||
```js
|
||||
import { inputMode } from '../engine/gesture.js';
|
||||
inputMode(); // 'touch' | 'key' —— 当前提示应使用的输入模式
|
||||
```
|
||||
|
||||
- `settings.input`(`'auto'` 默认 / `'touch'` / `'key'`,settings.js VALID
|
||||
枚举):手动覆盖优先;`auto` = 跟随最近使用的通道(映射键 keydown →
|
||||
`key`,任意 touchstart → `touch`),会话内尚无输入时按能力检测
|
||||
(`maxTouchPoints > 0 && (pointer: coarse)` → 触屏)。
|
||||
- **只影响提示文案,不关闭任何输入通道**——touch/mouse/key 三通道永远
|
||||
并行在线(混合设备友好)。当前接入点:启动遮罩默认文字
|
||||
(`showStartOverlay` 不传 `text` 时按 `inputMode()` 选「轻触开始」/
|
||||
「按空格开始」);游戏内提示文案由各游戏 HTML 后续接入。
|
||||
|
||||
## engine/game.js — `Game` / `StateMachineGame`
|
||||
|
||||
```js
|
||||
|
||||
Reference in New Issue
Block a user