# 引擎开发约定与参考

本文档记录交互电影游戏引擎的开发规范、架构约定和工作流程，供后续开发对话中引用。

## 工作流程

| 规则 | 说明 |
|------|------|
| **先讨论再执行** | 功能实现前必须先讨论方案，确认后再写代码 |
| **先更新 ROADMAP 再实现** | 新功能先写入 ROADMAP 的 P 条目，附实现清单，再逐项完成 |
| **不自动提交** | 代码写完不要 `git commit` / `git push`，等用户检查通过后再操作 |
| **验证方式** | `npx vue-tsc --noEmit` + `npx vite build` 通过视为基本验证 |
| **生成测试数据** | 新功能要生成配套的示例视频 / 音频 / JSON 数据 |

## 架构原则

| 原则 | 说明 |
|------|------|
| **引擎与 UI 分离** | `engine/` 下纯 TS 类，不 import Vue。UI 层通过 composables 桥接 |
| **A/B 双缓冲** | 两个 `<video>` 元素轮换，一个播放时另一个预加载候选场景 |
| **JSON 驱动** | 所有剧情数据放在 JSON 中，编辑器本质是 JSON 的可视化读写工具 |
| **IndexedDB 存档** | 比 localStorage 容量大，可存储截屏缩略图。多槽位支持 |
| **故事图与玩家树** | 创作端 JSON 是有向图，展示端 BFS 投影为树（汇聚节点复制展示） |

## 目录结构

```
moviegame/
├── engine/                     # 框架无关的核心引擎（纯 TS）
│   ├── core/
│   │   ├── Engine.ts           # 主循环，驱动各子系统
│   │   ├── SceneManager.ts     # 剧情节点图遍历、章节管理
│   │   ├── VideoManager.ts     # A/B 双缓冲视频播放 + 流媒体质量选择
│   │   └── StateManager.ts     # 全局状态、变量条件求值、效果执行
│   ├── systems/
│   │   ├── ChoiceSystem.ts     # 限时选择 + 倒计时
│   │   ├── QTESystem.ts        # QTE 触发、键盘监听、超时判定
│   │   ├── AudioSystem.ts      # Web Audio API BGM + Ducking
│   │   ├── AchievementSystem.ts # 纯变量成就检测 + 解锁
│   │   └── SaveSystem.ts       # IndexedDB 多表持久化
│   └── types.ts                # 全部类型定义
├── src/
│   ├── components/             # Vue 组件——玩家端 UI + 编辑器 UI
│   ├── composables/            # 引擎 ↔ UI 桥接
│   ├── stores/                 # Pinia 全局状态
│   └── locales/                # UI 文本翻译（静态 import，构建时打包）
├── editor/                     # Vue Flow 可视化编辑器（独立入口）
├── electron/                   # Electron 桌面应用打包
├── public/
│   └── demo/                   # 示例素材——按场景分目录，每场景含视频、字幕、缩略图
│       └── locales/            # 故事文本翻译（动态 fetch 加载，制作者维护）
├── docs/
│   ├── SCENE_JSON_SPEC.md      # 场景 JSON 完整字段参考
│   └── ARCHITECTURE.md         # 关键架构决策记录
├── scripts/                    # 构建与打包脚本
├── ROADMAP.md                  # 待实现功能清单
└── CHANGELOG.md                # 功能更新日志
```

## 代码约定

| 规则 | 示例 |
|------|------|
| 不要写注释 | 代码自解释，不添加多余的中英文注释 |
| Engine 事件驱动 | `this.emit('sceneChange', scene)` → composable 响应 |
| composable 桥接 | `useGameEngine.ts` 是 Engine 和 Vue store 的中间层 |
| i18n 双文件分层 | `src/locales/` 存 UI 文本，`public/demo/locales/` 存故事文本。`useI18n.t()` 先查故事消息，fallback UI 消息 |
| 选择翻译在 composable 中 | `choiceRequest` 事件触发时 composable 调用 `t(textKey)` 翻译后存入 store |
| 引擎不感知 i18n | `Choice.textKey` 和 `Hotspot.labelKey` 是数据层字段，翻译完全在 Vue 层完成 |
| demo 素材按场景分目录 | `public/demo/<scene_id>/<file>` |
| demo 的 assetBase | `"assetBase": "demo/"` — 所有资源路径以此为前缀 |

## 菜单系统

| 菜单 | 设计 |
|------|------|
| **主菜单 (MainMenu.vue)** | 竖排单列，开始游戏最大、继续次之、底部小字装饰行（故事进度 · 成就 · 设置） |
| **暂停菜单 (PauseMenu.vue)** | ESC 弹出的全屏暂停，包含继续 / 存档 / 设置 / 返回主菜单 |
| **设置面板 (AccessibilitySettings.vue)** | 语言 + 画质(仅 Web) + 字幕 + QTE 辅助 + 防误触 + 可暂停 |
| **游戏内顶栏** | 精简：跳过(条件显示) · 倍速(条件显示) · 全屏 · ≡菜单 |

## 视频播放模式

| 模式 | 检测方式 | 使用的 URL 字段 | 切换方式 |
|------|---------|---------------|---------|
| **Electron 桌面** | `window.__ELECTRON__ === true` | `videoUrl`（本地 MP4） | 不切换，单文件 |
| **Web 浏览器** | `__ELECTRON__` undefined | `streamingUrl[quality]`（CDN HLS） | 设置面板手动选择超清/高清/标清 |
| **画质切换** | 仅在视频播放中切换（`video.ended === false`），结束后不切换等待下一场景 |

关键方法：

- `VideoManager.resolveVideoUrl(scene, quality)` — 环境检测 + URL 选择
- `VideoManager.switchQuality(src, seekTime)` — 实时切换画质
- `SceneManager.getCandidateSceneIds()` — 返回候选场景 ID，Engine 用 `resolveVideoUrl` 解析为 URL
- 预加载也走 `resolveVideoUrl`，Web 模式禁止预加载本地 MP4

## 场景 JSON 约定

| 规则 | 说明 |
|------|------|
| 单文件包含全部场景 | 不拆分章节为独立 JSON 文件 |
| 章节用 `startScene` 标记 | 无 `endScene`，BFS 遍历可达场景作为章节范围 |
| 影像场景标记 | `"type": "image"`, `videoUrl` 为空 |
| QTE 配置 | `effects.success` / `effects.fail` 中附加变量修改，供成就检测 |
| 结局归属 | 通过 BFS 自动推导 `ending.sceneId` 属于哪个章节，不显式声明 `chapterId` |

## 成就系统

- 纯变量检测，在 `StateManager.apply` 末尾 `onAfterApply` 单一检查点触发
- 事件型成就改写为变量型：QTE 成功 → effects 中 `set: qte_succeeded, value: 1`
- 解锁时有 toast 弹出、持久化写入 IndexedDB `achievements` 表

## 流媒体 / HLS 约定

- 每场景三档 HLS，目录结构：`public/demo/<scene>/<quality>p/index.m3u8` + `seg_000.ts`
- `demo.json` 中每场景配 `streamingUrl: { "超清 (1080P)": "...", "高清 (720P)": "...", "标清 (480P)": "..." }`
- 生成命令：`ffmpeg -i source.mp4 -c:v libx264 -b:v <bitrate>k -c:a aac -b:a 128k -hls_time 2 -hls_segment_filename <dir>/seg_%03d.ts <dir>/index.m3u8`
- `pack-html.cjs` 跳过 `videos/` 目录（Web 版使用 CDN 流媒体）
- `pack-mac` / `pack-win` 保留完整视频文件（桌面版使用本地 MP4）
