diff --git a/research/platform/00-synthesis.md b/research/platform/00-synthesis.md new file mode 100644 index 00000000..3b2f8fc0 --- /dev/null +++ b/research/platform/00-synthesis.md @@ -0,0 +1,51 @@ +# 平台能力调研汇总 — 无感用户系统 + PC/手机双端一致 + +> 三路并行调研的汇总与实施方案建议(2026-09-08)。分报告:[01-identity](01-identity.md) 身份方案 | [02-input-parity](02-input-parity.md) 键盘与双端 | [03-data-sync](03-data-sync.md) 数据架构。 + +## 1. 用户需求 → 方案映射 + +| 需求(用户原话) | 方案 | +|---|---| +| 支持多玩家访问,不想影响体验 | 自动匿名档案:首访 0 输入 0 点击,服务器静默建档发 token | +| 不让用户注册 | 注册降级为可选「认领」(给档案起名 + 设 PIN),永不弹窗 | +| 能区分用户、保留状态 | 设备 token 即档案;localStorage 按 profileId 分桶(修掉现状同设备串档 bug);进度按游戏分桶 LWW 同步 | +| PC 键盘操作 | 方向键/WASD=滑动、空格=单击、回车=双击(即时)、按住空格=长按;游戏代码零改动 | +| PC 画面提示 | 输入模式自动检测 + 手动覆盖;HTML 文案随模式切换 | + +## 2. 推荐架构(三报告收敛) + +``` +首访浏览器 ──POST /api/device──> 匿名档案 {profileId, token}(0 交互) + 本地:localStorage 按 profileId 分桶(进度主存 + 异步上传) + 同设备多人:大厅「切换玩家」档案切换器(倒灌/回灌 localStorage,游戏零改动) + 跨设备:旧设备出示 6 位配对码(语音逐位播报)→ 新设备输入 → 合并 + 认领(可选):给档案命名 + 设 PIN(scrypt 哈希);旧数据按桶 LWW 合并 +键盘:gesture.js 键盘通道喂现有状态机(~120 行)+ 启动遮罩键盘解锁 + settings.input +语音:双套资产 voice//key/(153 句 ×2 性别,TTS 306 生成/37 分钟音频) +``` + +**关键否决/发现**: +- passkey 否决为主方案:WebAuthn 在局域网 HTTP 私有 IP 下不可用(W3C secure context) +- 存量 bug 三连:① 7/8 游戏跨设备只 PUT 不 GET(换设备进度不回拉);② localStorage 单桶(同设备换人登录会串档污染);③ 大厅零 aria、玩家名不可朗读 +- 语音路线 c(不动语音只改文字)不能作终态:AppleVis 社区明确批评过无键盘等价命令的音频游戏 + +## 3. 分期实施 + +### P0 最小可用(约 3-4 人日,无 TTS) +1. gesture.js 键盘通道 + 遮罩解锁 + settings.input(自动检测+手动覆盖)+ 大厅/遮罩文案适配 +2. `/api/device` 匿名档案 + localStorage 分桶 + PUT baseUpdatedAt 守卫 + GET 回拉(修存量三 bug) +3. PIN scrypt 哈希(十行级) +4. 大厅档案切换器(自动命名「玩家一」,earcon 签名 + 编号音可听化) +5. playtest/guide 回归 + engine-voice + live 键盘通道 + +### P1 完整版(+1 GPU 晚 + 1-2 人日) +6. 153 句键盘措辞 ×2 性别 TTS 双套资产 + 引擎 input 资产路由 +7. 6 位配对码跨设备(含逐位播报的「数字单音」拼接豁免裁定) +8. 可选认领(命名+PIN+旧档案 LWW 合并) + +### 公网前(03 报告 §6 的 8 条,另计) + +## 4. 待产品拍板 +- D1 首访命名:全静默(自动「玩家一」,事后可改名)vs 首次轻量起名 +- D2 找回底线:仅配对码(旧设备必须在手)vs 配对码 + 名/PIN 兜底(丢设备可救) +- D3 语音路线:双套资产(推荐,增量灰度、不动已验收资产)vs 通用措辞重录(一套通吃但全量重录)vs 先文字后补 diff --git a/research/platform/01-identity.md b/research/platform/01-identity.md new file mode 100644 index 00000000..f6200d1d --- /dev/null +++ b/research/platform/01-identity.md @@ -0,0 +1,260 @@ +# 平台调研 01 · 无感多用户身份系统 + +> 课题:多个玩家(家庭 WiFi 手机/PC + 未来公网 game.xiangsilian.com)可区分、可保留各自进度与设置,且**绝不能强迫注册**。 +> 调研日期:2026-09-09。方法:本仓库代码审计 + 业界模式检索(来源链接见 §3.6)。 + +--- + +## 0. 结论速览 + +**主推:方案 B「自动匿名档案 + 设备档案切换器 + 配对码跨设备」**——首次访问 0 输入 0 点击即自动建档(服务器静默发 token,玩家无感知),同设备多玩家用 Netflix 式档案切换,跨设备/找回用「旧设备出示 6 位配对码 → 新设备输入」;纯静态公网形态自动降级为方案 A(纯本地档案)。PIN 降级为**可选的"认领"升级项**(防兄弟恶作剧),passkey 留给公网化之后再评估(LAN HTTP 下技术上不可用,见 §3.3)。 + +三个待产品拍板的决策点见文末 §6。 + +--- + +## 1. 现状审计 + +### 1.1 服务器(web/server/server.js,310 行) + +| 项 | 现状 | 评估 | +|---|---|---| +| 注册/登录 | `POST /api/register {username, pin}` / `POST /api/login`,PIN 4~6 位数字 | **PIN 明文存 users.json**;用户名可中文(`USERNAME_RE`) | +| Token | 登录返回 24 字节随机 hex,内存 + sessions.json | **永不过期、无登出端点**——客户端"退出"只是删 localStorage,token 在服务器侧永久有效 | +| 进度 API | `GET/PUT /api/progress`,Bearer token,`data` 自由格式但须带 `summary`(大厅卡片显示) | 接口形状健康,可复用 | +| 并发 | 按文件串行 Promise 队列,tmp+rename 原子写 | 健康,可复用 | +| 防护 | 无速率限制、无 IP 封禁、HTTP 明文 | HANDOVER §2.2 已自我标注"玩具级" | + +数据模型(web/server/data/ 实测): + +``` +users.json { "uuu": "0858" } // username -> pin 明文 +sessions.json { "<48位hex>": "uuu" } // token -> username,无过期时间 +uuu.json { "_settings": {voice, speed, bgm, guide} } // 一用户一文件 {: data} +``` + +### 1.2 客户端(web/index.html + 各游戏) + +- **登录态**:localStorage `soundgame.auth` = `{username, token}`,大厅与 7 个游戏各自直读此 key。 +- **游客态今天就能玩**:不登录 → 全部 8 款游戏可玩,进度写各游戏自己的 localStorage key,设置写 `soundgame.settings`。服务器同步仅在登录时进行(PUT 静默跳过)。**即"本地优先 + 可选账号"架构今天已经成立,只是"账号"这一半摩擦大(要起名+想 PIN+输两遍)且公网不可用。** +- **公网形态**(docs/deploy.md):Nginx 纯静态、**不跑任何 API**;大厅检测 `https://*.xiangsilian.com` 即隐藏整个用户面板(index.html `PUBLIC_STATIC`)→ 公网访客全部是孤立的 localStorage 游客。deploy.md 已写明公网开放 API 的前置(127.0.0.1 绑定、只反代 /api/、limit_req、PIN 哈希、token 过期)。 + +localStorage key 全量清单(多档案方案的现实约束,见 §4): + +| key | 归属 | 说明 | +|---|---|---| +| `soundgame.auth` | 大厅+7游戏 | 登录 token | +| `soundgame.settings` | engine/settings.js | 四项全局设置 | +| `soundgame.progress.rpg` / `.dungeon` / `.radio` | 各游戏 | 存档(命名不统一) | +| `soundgame.rhythm.progress` | rhythm | 存档 | +| `soundgame.simon.best` / `soundgame.space.best` / `soundgame.escape.stats` | 各游戏 | 最高分/统计 | +| `soundgame.rpg.tutIntroSeen` / `soundgame.escape.guideDone` / `soundgame.space.drillDone` | 各游戏 | 一次性教学标记 | +| (storyline 无本地 key) | — | 无本地持久化 | + +### 1.3 审计发现的关键缺口(新方案必须解决) + +1. **跨设备恢复只做了 1/8**:只有 radio 在"本地无存档 + 已登录"时 GET 服务器存档(radio/game.js:325-342);rpg/dungeon/rhythm/escape/simon/space 只 PUT 不 GET——**已登录用户换设备,7 款游戏进度照样丢**(服务器上有副本却不拉回)。 +2. **设置是"设备级"而非"玩家级"**:一台手机一个 `soundgame.settings`;家里视障玩家 2.2x 语速与普通玩家 1.0x 共用一部手机时互相覆盖(虽然登录后会拉 `_settings`,但游客态无档案可分)。 +3. **大厅无读屏适配**:index.html **零 aria 属性**;登录表单是 JS 动态生成、仅 placeholder 无 `