Files
jev-match-game/README.md
T

105 lines
6.2 KiB
Markdown
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.
# 苍梧疑冢 · 互动剧情测评(MVP)
陌生人交友匹配 app 的冷启动载体:单人互动叙事("影游")。MVP 范围 = 影游第一幕(3 章)
+ 人格画像报告 + 勋章分享卡,**不含匹配和聊天**。
产品共识见 `CONTEXT.md`,架构决策见 `docs/adr/0001`~`0004`,剧本母本见
`docs/story-framework.md`(第 4 节是 Act 1 全部选择点地图)。
## 怎么跑
```bash
pnpm install
pnpm dev # 浏览器开发(无 API key 时自动降级 Mock,见下)
pnpm build # 类型检查 + 产物构建
pnpm verify # 无头校验:选择点地图对照母本 + 引擎三章流程 + 重玩语义(Mock)
pnpm verify:live # 真 key 冒烟:C1-01 抽取 + C1-02 情绪签名 + 7 维 score(读 .env)
pnpm lint # oxlint
pnpm tauri dev # Tauri 桌面壳(Rust 工具链需先 source ~/.cargo/env)
```
## Jev(TypeSafe AI System One)配置
复制 `.env.example` 为 `.env`,填入 key:
```
VITE_TYPESAFE_API_KEY=<your key>
```
- 有 key:前端直连 `POST https://api.typesafe.ai/v1/systemone`(`HttpJevClient`)。
- 无 key:自动降级 `MockJevClient`(确定性伪随机返分,console 有提示),开发联调不受影响。
- `.env` 已在 `.gitignore` 中,绝不能入库;`scripts/verify-live.ts` 手动解析 `.env`
(不引 dotenv),任何输出都不打印 key。
- 请求/响应 schema 已按真接口 curl 实测对齐(2026-10-03):Question 文本字段为
`instructions`;choice 的 `criteria` 是 `{选项: 描述}` map;score 的 `criteria`
为 2-10 级有序数组(索引 0 起),答案是概率加权浮点值 + `legend` + `probabilities`。
## 项目结构
```
content/chapters/ Act 1 三章剧情数据(JSON,选择点 ID/维度标签对母本第 4 节)
src/engine/ 数据驱动剧情引擎(纯 TS,与 UI 解耦)
types.ts 章节 schema:dialogue / choice / narration / cutscene / identity / chapterEnd
engine.ts StoryEngine 状态机:节点推进、分支、情绪签名→场景变体、
身份抽取(extractIdentity)与确认(confirmIdentity)、重玩
eventStore.ts EventStore 接口(append-only,只有 append/list)+ localStorage 实现
src/services/jev.ts JevClient 接口 + HttpJevClient / MockJevClient
src/ui/ 最简影游界面(对话框/选项/VoiceInput 语音输入/身份登记/场景变体渐变)
src/ui/report/ 画像报告(Jev 7 维 score)、勋章映射、canvas 分享卡
scripts/verify.ts 内容完整性与流程校验(Mock 回归)
scripts/verify-live.ts 真 key 冒烟
src-tauri/ Tauri 2 壳(iOS/Android/桌面共用)
```
## 架构要点
- **append-only 事件存储(ADR-0004)**:`EventStore` 类型层面只有 `append`/`list`,
没有 update/delete 路径。重玩 = 新 sessionId 的新轨迹,旧事件原样保留。
- **三通道边界(ADR-0003)**:主线全部预写在 JSON 里;Jev 在讲述点只做
"情绪签名 → 预写场景变体" 的选择题(通道 1);C3-02 问心幻境通过
`variantFrom: "C1-02"` 复用第一章的记忆解析。
- **语音输入**:`VoiceInput` 组件优先用 Web Speech API(`zh-CN`,continuous +
interimResults,实时转写),不支持的环境(如 Tauri WKWebView)优雅降级为文本框。
只保留转写文本、不保存任何音频,天然符合 ADR-0004"特征提取后即焚"。
- **C1-01 语音化身份登记**:馆长邀请自由介绍(称呼/年龄/城市/职业/学历),
Jev 一次调用并行抽取:gender(choice)、age(score 分档)、occupation(choice)、
education(choice)、city(noul 判断是否提及 + 城市 choice 兜底);
抽不出的字段留空,进**确认页**由用户修改补填,确认后 append `profile` 事件
(身份字段是匹配硬过滤字段,用户有权直接改,不进行为信号流)。
- **画像报告**:Act 1 结束后把全部行为轨迹(不含 profile 字段)汇总为 state,
一次 Jev 调用并行问 7 个维度的 score 问题(10 级 criteria),API 返回的
0-9 浮点分数 +1 映射为 1-10 级展示;勋章取置信度最高的维度。
## 已知缺口
- **CORS**:浏览器直连 `api.typesafe.ai` 可能有跨域限制,正式环境需加后端代理转发。
- **Web Speech API 覆盖**:Chrome/Edge 可用,Tauri WKWebView 与 Firefox 不支持
(自动降级文本框);原生端的正式 ASR 管线未接。
- **Tauri SQLite 未实现**:`EventStore` 目前只有 localStorage 实现;原生端应用
tauri-plugin-sql 实现同一接口(表只建 INSERT/SELECT,不建 UPDATE/DELETE 路径),
预留位置见 `src/engine/eventStore.ts` 末尾注释。
- **Tauri 移动端(iOS/Android)推送与构建未验证**:目前只验证了 Rust 壳
`cargo check` 与 `tauri info`,未跑 `tauri android/ios build`。
- **追问补全未接**:讲述点 schema 里有 `followUp` 策略占位(Jev 缺失维度检测 +
NPC 口吻追问,至多 1-2 句),MVP 未实现。
- **美术全部占位**:CSS 色块 + 渐变,无图片/视频素材;cutscene 节点是占位渲染。
- **自适应支线路由未实现**:story-framework 第 6 节的低置信度维度插支线属于后续章节工作。
## 后端代理
`server/` 是独立子包(Node 22 + Hono,自己的 lockfile,与前端互不干扰):
```bash
pnpm --dir server install
cp server/.env.example server/.env # 填 TYPESAFE_API_KEY(key 只在服务端)
pnpm --dir server dev # 默认 http://localhost:8787
pnpm --dir server smoke # 冒烟:代理调真实 Jev + /api/asr 自测
```
- `POST /api/jev` 透传 systemone(状态码原样返回),解决浏览器直连的 CORS 问题;
前端在根 `.env` 设 `VITE_JEV_PROXY_URL=http://localhost:8787` 即切代理模式,
此时前端不需要 `VITE_TYPESAFE_API_KEY`,未配置时回退直连(见 `src/services/jev.ts`)。
- `POST /api/asr` 为移动端 ASR 预留:provider 可插拔(当前 Mock 占位),音频遵守
ADR-0004"特征提取后即焚"不落盘,返回 `{ transcript, prosody }`。
- 详见 `server/README.md`。