Files
aibis-dream/Assets/Scripts/SaveSystem/README.md
T

200 lines
10 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.
# SaveSystem 存档快照层
> 当前代码包含 P1 快照层、P2 槽位/落盘基础版、P3 可存点判定基础版、P4 读档编排基础版。设计文档见 [`Docs/存档系统设计方案.md`](../../../Docs/存档系统设计方案.md)**§4.1 / D6** 描述读档编排模型)。
## 职责边界
| 模块 | 位置 | 做什么 |
| --- | --- | --- |
| Yarn 变量 | `Game Loop/YarnVariableStorage.cs` | 运行时 `$` / `$global_` 读写 |
| **本目录** | `SaveSystem/` | 快照结构、捕获/还原、槽位落盘、流程编排 |
| 深度维修(旧) | `DeepRepairDataRegistry.cs` | FixSystem 等 IDataP5 前临时保留 |
| 槽位 / 截图 | `SlotManager` / `SlotThumbnailCapture` | P2 基础版:slot_0 自动档、slot_1..8 手动档、sidecar、缩略图、原子写 |
**不要**再往 `YarnVariableStorage` 上堆存档逻辑。
## 对外入口
```csharp
// 自动存档(节点进入事件触发)
yield return SaveRestoreOrchestrator.AutoSaveRoutine(nodeName);
// 手动存档:复制最近自动档
SaveRestoreOrchestrator.CreateManualSlot(slotIndex);
// 槽位读档
yield return SaveRestoreOrchestrator.RestoreFromSlot(slotIndex);
// 文件读档(旧档 / 调试)
yield return SaveRestoreOrchestrator.RestoreFromFile(path);
// 仅内存快照
var snap = SnapshotService.Capture();
yield return SnapshotService.Restore(snap);
// Yarn 变量(与存档无关)
YarnVariableStorage.Instance.SetValue("$foo", 1f);
```
正式槽位路径:`persistentDataPath/AllOurBrokenParts/saves/slot_x/{snapshot.json, meta.json, thumbnail.png}`
P1 测试落盘路径:`persistentDataPath/AllOurBrokenParts/snapshot_test/latest_snapshot.json`(首次访问槽位系统时会尝试迁移到 `slot_0`
## 目录结构
```
SaveSystem/
├── README.md ← 本文件
├── ISnapshotProvider.cs 契约:Capture / RestoreP1 临时形态,见下)
├── SaveSnapshot.cs 快照根对象 + 各 section DTO + SaveSnapshotSummary
├── SnapshotRegistry.cs provider 稳定 id、注册表、启动注册
├── SnapshotCapture.cs 组装 SaveSnapshotscene / anchor 由框架直管)
├── SnapshotRestore.cs 写回运行时;含 section JObject → 强类型转换
├── SnapshotService.cs Capture / Restore 对外 API
├── SnapshotPersistence.cs 文件读写、JSON 序列化、旧档检测
├── SaveRestoreOrchestrator.cs UI + 落盘 + 读盘编排
├── DeepRepairSnapshot.cs 深度维修占位(P5)
├── DeepRepairDataRegistry.cs 旧 IData 容器(P5 前)
├── ScreenSnapshotHelper.cs screen section 实现细节
├── SlotTypes.cs P2:槽位常量、sidecar、UI 视图模型
├── SlotFileSystem.cs P2:槽位路径 + 原子写
├── SlotManager.cs P2:槽位业务核心
├── SlotThumbnailCapture.cs P2:截图 helper
├── CloudSave/ P2:云存档适配器接口与协调器
│ ├── ICloudSaveBackend.cs
│ └── CloudSaveManager.cs
└── Providers/ 表现层子系统薄适配层
├── EnvironmentSnapshotProvider.cs order 20
├── ActorSnapshotProvider.cs order 40
├── AudioSnapshotProvider.cs order 50
├── TimelineSnapshotProvider.cs order 60
├── FixSnapshotProvider.cs order 65
└── ScreenSnapshotProvider.cs order 70
```
## 存盘 / 读盘流程
```
存盘:
SaveRestoreOrchestrator
→ SnapshotService.Capture()
→ SnapshotCapture(场景 + 锚点 + Yarn 变量 + 各 Provider
→ SnapshotPersistence.Save()
读盘:
SaveRestoreOrchestrator
→ SnapshotPersistence.Load() (或 legacy 分支)
→ SnapshotService.Restore()
→ SnapshotRestore(见「读档还原编排」)
```
## 读档还原编排(终态 vs P1 代码)
> **勿将当前代码当作终态。** P1 中 `ISnapshotProvider.Restore` 统一返回 `IEnumerator``SnapshotRestore` 对每个 Provider 做 `yield return`——这是受旧 `IData.Load()` 影响的**临时 scaffolding**。终态见设计文档 **§4.1 / D6**P4 实施前应 refactor。
### 终态:Phase + Barrier
读档分阶段推进,仅在**必须等待**的边界上 `yield`
```
Phase 0 Yarn 变量(sync
Phase 1 场景加载(BarrierLoadSceneAsync)← 框架直管,不走 Provider
Phase 1.5 设置章节 SOsync)← 框架直管,从 anchor.sceneSoName 读取
Phase 2 env / actor / audio / timeline / fix / screenProvider 按 RestoreOrder
Phase 2 可选 Barrier(如 Timeline Addressable 须显式等待)
Phase 3 加载对话工程 + RestoreAnchorBarrierStartDialogue)← 框架直管
Phase 4 P4:淡入淡出等演出时序
```
- **核心层**`scene``anchor``yarnVariables`)由 `SnapshotRestore` 框架直管,不通过 Provider 注册。
- **表现层**`sections` 内各子系统)通过 `ISnapshotProvider` 扩展;`RestoreOrder` 表示同 Phase 内的建议顺序或软依赖。
- **逐项还原(D1)**指各子系统各自写回状态,**不是** Provider 之间逐步 `yield return`
### P1 临时形态 vs P4 目标
| | P1(当前) | P4 目标 |
| --- | --- | --- |
| 核心层还原 | 硬编码在 `SnapshotRestore` | 保持框架直管 |
| Provider 还原 | 全部 `IEnumerator Restore` | 默认 `void Restore`;仅需 async 加载的实现显式协程 |
| 编排 | `foreach` 逐步 `yield return` | Phase 编排,仅 Barrier 步骤 `yield` |
| Manager | 部分 `IEnumerator RestoreSnapshot``yield break` | 默认 `void`;真有 async 才保留协程 |
### 已知偏离(tech debt
- `DirectorHandler` 在 Addressable Timeline 路径下内部 `StartCoroutine`,Provider 已返回,编排层无法感知——P4 应改为可等待路径。
- P2/P3 新增代码**不要**再复制「全 Provider 协程链」模式。
## 快照 JSON 结构(schemaVersion = 1
| 字段 | 含义 |
| --- | --- |
| `scene` | 当前场景名(Addressable key),读档时最先加载 |
| `anchor` | 恢复锚点:`sceneSoName` + `yarnProjectId` + `nodeName`;最后阶段加载对话并重进节点 |
| `yarnVariables` | floats / strings / bools |
| `sections` | key 为 `SnapshotProviderIds`,值为各 DTO**表现层状态(env / actor / audio / timeline / fix / screen** |
| `deepRepair` | **P5 深度维修**BlockPuzzle / Cutting / AnalysisMode 等)。P1 阶段已预留 `DeepRepairSnapshotDto`,但详细子系统状态暂不捕获;待存档系统主干稳定后再补充并测试 |
> 宏观信息(场景、章节、节点)直接位于 `SaveSnapshot` 根对象,不再冗余到 `sections`;槽位 / 「继续游戏」UI 需要展示时,从 `scene` / `anchor` 直接读取。
## 新增一个可存子系统(表现层)
表现层子系统通过 Provider 注册到 `sections`
1.`SaveSnapshot.cs` 增加 DTO 类。
2.`SnapshotRegistry.cs``SnapshotProviderIds` 区域增加稳定 id,并加入 `RequiredForCapture`(若 P1 必须存)。
3. 在对应 Manager 实现 `CaptureSnapshot` / `RestoreSnapshot`**默认 sync `void`**;仅确有 async 加载/状态切换时用 `IEnumerator` 并向编排层上报)。
4. 新建 `Providers/XxxSnapshotProvider.cs` 实现 `ISnapshotProvider`P4 后:sync 默认 + 显式 async 接口)。
5.`SnapshotRegistry.EnsureInitialized``Register`
6.`SnapshotRestore.CoerceSectionDto` 增加 JObject 转换分支。
**禁止**在 Provider 内私自 `StartCoroutine` 而不纳入编排 Barrier。
> **例外**`scene`(场景加载)与 `anchor`(章节+节点重进)属于**核心叙事坐标**,由 `SnapshotCapture` / `SnapshotRestore` 框架直管,不走 Provider 注册。新增「核心层」字段需修改 `SaveSnapshot` 根对象与对应编排方法。
## Fix 场景快照(P1 接口层完成)
Fix 场景(维修场景)的快照存储在 `sections["fix"]`,由 `FixSnapshotProvider`order 65)管理。
### 为什么 FixStateMachine 放在 sections 而不是 deepRepair
FixStateMachine 不仅管理深度维修(Eye、Memory、EmoWave 等),还管理 **Clinic(诊所)****BodyModule(插线维修)** 等非深度维修状态。把它放在 `sections` 中作为表现层子系统之一,可以避免概念错位。
### 双入口设计:Enter vs EnterImmediate
Fix 场景各 State 的 `Enter()` 通常包含相机过渡、Timeline 播放、Fade 等动画。读档时需要跳过这些动画直接到达终态,因此为 `IFixState` 增加了 `EnterImmediate()` 入口:
- 正常游戏流程 → `Enter()`(完整动画)
- 读档恢复 → `SwitchStateImmediate()``EnterImmediate()`(跳过动画)
读档时 `SwitchStateImmediate` **不调用前一个状态的 Exit()**,因为读档本质是覆盖当前状态,不需要清理。
### P1 已落地的接口
- `FixSnapshotDto`state、args、moduleState、eyeColorState、isSystemOn、currentRepairSystemType
- `IFixState` 接口 + `EnterImmediate()` 默认实现
- `FixStateMachine.SwitchStateImmediate()`
- `FixSystemCenter.CaptureSnapshot()` / `RestoreSnapshot()`
- `FixSnapshotProvider` 注册到 `SnapshotRegistry.EnsureInitialized`
### 尚未实现(P4/P5
- 各 State 的 `EnterImmediate()` 具体逻辑(当前默认 `yield break`
- 深度维修子系统详细状态(BlockPuzzle grid、Cutting 进度等)→ P5 `deepRepair`
- Fix 场景读档的 Fade 时序 → P4 编排层
## 相关阶段
| 阶段 | 内容 |
| --- | --- |
| P2 | 基础版已落地:槽位、原子写、meta sidecar、缩略图、latest_slot、P1 测试档迁移;正式 UI 接入仍属 P6 |
| P3 | 基础版已落地:`onNodeStart` 判定、tag 白/黑名单、`no_save`、读档/暂停门控;瞬跳 content 排查与无活跃 Yarn 边界仍待补 |
| P4 | 基础版已落地:Provider sync/async 契约、Phase + Barrier 编排、读档自动存档抑制、Timeline Addressable 可等待恢复、验证窗口读档入口 |
| P5 | `deepRepair` 段、维修模块清单;还原模型单独定案,不默认套用 P1 Provider 协程链 |
| P6 | 正式存 / 读档 UI、继续游戏、新游戏覆盖自动档、游戏中读档确认等玩家流程 |