# 交互式电影游戏引擎 — Roadmap

## 技术栈

- **框架**: Vue 3 (Composition API + `<script setup>`)
- **构建**: Vite
- **状态管理**: Pinia
- **可视化编辑器**: Vue Flow
- **存储**: IndexedDB (Dexie.js)
- **BGM**: Web Audio API
- **语言**: TypeScript
- **视频**: 原生 `<video>` + A/B 双缓冲

## 实现路线

### P18 视频加载失败恢复（待实现）

目标：视频加载失败时显示错误画面 + 重试/跳过按钮，不再静默黑屏。

- [ ] `engine/core/VideoManager.ts` — `play`/`switchTo` 增加错误回调 + 超时检测（5 秒）+ 重试（最多 3 次，指数退避 1s/2s/4s）
- [ ] `src/components/VideoErrorOverlay.vue` — 错误画面：图标 + 提示 + [重试] [跳过] 按钮
- [ ] `src/stores/gameStore.ts` — `videoError` 状态
- [ ] `src/App.vue` — 整合 VideoErrorOverlay
- [ ] 验证：断网播放 → 错误画面 → 重试恢复 → 跳过下一场景

### P24 多画质视频 — 本地 + CDN 流双模式 ✅ 已完成 2026-06-10

目标：桌面版用本地 `videoUrl`，Web 版用 CDN `streamingUrl`（HLS 流）。
Web 版不打包视频文件，用户手动选择超清/高清/标清，系统提示各画质所需网速。

**设计决策：**

| 决策 | 做法 |
|------|------|
| **环境检测** | Electron `preload.js` 注入 `__ELECTRON__` → `VideoManager` 判断走本地还是 CDN |
| **Web 画质** | 用户从设置面板手动选择（超清/高清/标清），非带宽自适应。localStorage 持久化 |
| **Web 打包** | `pack:html` 跳过 `videos/` 目录，音频/图片/字幕保留 |
| **HLS 兼容** | Safari 原生播放 `.m3u8`；Chrome/Edge 按需动态 `import('hls.js')`（~100KB） |

**场景数据设计：**

```json
{
  "id": "intro",
  "videoUrl": "/videos/intro.mp4",
  "streamingUrl": {
    "超清 (1080P)": "https://cdn.example.com/hls/intro/1080p.m3u8",
    "高清 (720P)": "https://cdn.example.com/hls/intro/720p.m3u8",
    "标清 (480P)": "https://cdn.example.com/hls/intro/480p.m3u8"
  }
}
```

**设置面板画质选项：**

| 选项 | 网速提示 |
|------|---------|
| 超清 (1080P) | 需要 2.5 Mbps |
| 高清 (720P) | 需要 2 Mbps |
| 标清 (480P) | 需要 0.8 Mbps |

**实现清单：**

- [x] `engine/types.ts` — `SceneNode.streamingUrl?: Record<string, string>`
- [x] `engine/core/VideoManager.ts` — `resolveVideoUrl(scene, quality)` + `streamingQuality` 属性
- [x] `engine/core/Engine.ts` — `goToScene` 用 `resolveVideoUrl` 替代直接 `scene.videoUrl`
- [x] `electron/preload.js` — `contextBridge.exposeInMainWorld('__ELECTRON__', true)`
- [x] `electron/main.js` — `webPreferences.preload` 加载 preload.js
- [x] `src/stores/gameStore.ts` — `preferredQuality` + localStorage 持久化
- [x] `src/components/AccessibilitySettings.vue` — Web 模式新增画质下拉（附网速提示）
- [x] `src/App.vue` — watch `preferredQuality` → sync 到 `engine.videoManager.streamingQuality`
- [x] `scripts/pack-html.cjs` — 跳过 `videos/` 目录
- [x] 验证：TypeScript + Vite build 通过
- [ ] 验证：Electron `window.__ELECTRON__` = true，使用本地 `videoUrl`
- [ ] 验证：浏览器 `window.__ELECTRON__` = undefined，设置面板显示画质下拉
- [ ] 验证：`pack:html` 产物不包含 `videos/` 目录

### P25 条件路由 — nextScene 支持条件数组 ✅ 已完成 2026-06-12

目标：`nextScene` 从单一场景 ID 扩展为条件路由数组。第一个满足条件的场景自动跳转，
否则 fallback 到末尾无条件的默认场景。不局限于 QTE，所有场景均可使用。

**场景数据设计：**

```json
{
  "id": "combat_router",
  "nextScene": [
    { "conditions": [{ "variable": "enemy_hp", "op": "<=", "value": 0 }], "targetScene": "victory" },
    { "conditions": [{ "variable": "player_hp", "op": "<=", "value": 0 }], "targetScene": "defeat" },
    { "targetScene": "combat" }
  ]
}
```

**引擎行为：**

```
onVideoEnd(scene)
  ├── nextScene 是 string？→ 现存逻辑不变
  ├── nextScene 是 Choice[]？
  │     → 遍历数组，第一个满足 conditions 的 → 跳转到它的 targetScene
  │     → 都不满足 → endGame()
  └── 无 nextScene → 现有逻辑不变
```

**使用场景：**

```
QTE 成功 → effects: enemy_hp -= 25
  → successScene = "combat_router"
    ├── enemy_hp <= 0 → victory 场景
    ├── player_hp <= 0 → defeat 场景
    └── 否则 → 回到 QTE 场景（循环）
```

**实现清单：**

- [x] `engine/types.ts` — `SceneNode.nextScene` 类型改为 `string | Choice[]`
- [x] `engine/core/Engine.ts` — `onVideoEnd` 中加数组判断，遍历 conditions 跳转
- [x] `engine/core/SceneManager.ts` — `getCandidateTargetIds` 支持数组 nextScene
- [x] `src/components/StoryGallery.vue` — BFS 遍历 + `buildPlayerTree` 支持数组 nextScene
- [x] `public/scenes/demo.json` — 新增 `combat_router` 条件路由示例
- [x] 验证：TypeScript + Vite build 通过

## 已完成

P0~P23 全部实现（除 P18）。详见 [CHANGELOG.md](CHANGELOG.md)。

## 相关文档

| 文档 | 说明 |
|------|------|
| [docs/SCENE_JSON_SPEC.md](docs/SCENE_JSON_SPEC.md) | 场景 JSON 完整字段参考手册 |
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | 关键架构决策记录 |
| [PRODUCTION.md](PRODUCTION.md) | 生产级交付检查清单 |
| [FUTURE.md](FUTURE.md) | 远期功能扩展笔记 |
| [CHANGELOG.md](CHANGELOG.md) | 功能更新日志 |
