diff --git a/Assets/Scripts/SaveSystem/README.md b/Assets/Scripts/SaveSystem/README.md index 52a2a5257..c2b5c790f 100644 --- a/Assets/Scripts/SaveSystem/README.md +++ b/Assets/Scripts/SaveSystem/README.md @@ -1,28 +1,34 @@ # SaveSystem 存档快照层 -> P1 实现:纯数据快照 + 逐项还原。设计文档见 [`Docs/存档系统设计方案.md`](../../../Docs/存档系统设计方案.md)(**§4.1 / D6** 描述读档编排终态)。 +> 当前代码包含 P1 快照层、P2 槽位/落盘基础版、P3 可存点判定基础版、P4 读档编排基础版。设计文档见 [`Docs/存档系统设计方案.md`](../../../Docs/存档系统设计方案.md)(**§4.1 / D6** 描述读档编排模型)。 ## 职责边界 | 模块 | 位置 | 做什么 | | --- | --- | --- | | Yarn 变量 | `Game Loop/YarnVariableStorage.cs` | 运行时 `$` / `$global_` 读写 | -| **本目录** | `SaveSystem/` | 快照结构、捕获/还原、落盘、流程编排 | +| **本目录** | `SaveSystem/` | 快照结构、捕获/还原、槽位落盘、流程编排 | | 深度维修(旧) | `DeepRepairDataRegistry.cs` | FixSystem 等 IData,P5 前临时保留 | -| 槽位 / 截图 | P2 待做 | 接管 `SnapshotPersistence` 与目录布局 | +| 槽位 / 截图 | `SlotManager` / `SlotThumbnailCapture` | P2 基础版:slot_0 自动档、slot_1..8 手动档、sidecar、缩略图、原子写 | **不要**再往 `YarnVariableStorage` 上堆存档逻辑。 ## 对外入口 ```csharp -// 存盘(<>、调试) -SaveRestoreOrchestrator.SaveToTestPath(); +// 自动存档(节点进入事件触发) +yield return SaveRestoreOrchestrator.AutoSaveRoutine(nodeName); -// 读盘 +// 手动存档:复制最近自动档 +SaveRestoreOrchestrator.CreateManualSlot(slotIndex); + +// 槽位读档 +yield return SaveRestoreOrchestrator.RestoreFromSlot(slotIndex); + +// 文件读档(旧档 / 调试) yield return SaveRestoreOrchestrator.RestoreFromFile(path); -// 仅内存快照(P2 手动档固化等) +// 仅内存快照 var snap = SnapshotService.Capture(); yield return SnapshotService.Restore(snap); @@ -30,7 +36,9 @@ yield return SnapshotService.Restore(snap); YarnVariableStorage.Instance.SetValue("$foo", 1f); ``` -P1 测试落盘路径:`persistentDataPath/AllOurBrokenParts/snapshot_test/latest_snapshot.json` +正式槽位路径:`persistentDataPath/AllOurBrokenParts/saves/slot_x/{snapshot.json, meta.json, thumbnail.png}` + +P1 测试落盘路径:`persistentDataPath/AllOurBrokenParts/snapshot_test/latest_snapshot.json`(首次访问槽位系统时会尝试迁移到 `slot_0`) ## 目录结构 @@ -180,11 +188,12 @@ Fix 场景各 State 的 `Enter()` 通常包含相机过渡、Timeline 播放、F - 深度维修子系统详细状态(BlockPuzzle grid、Cutting 进度等)→ P5 `deepRepair` - Fix 场景读档的 Fade 时序 → P4 编排层 -## 相关阶段(未在本目录完整实现) +## 相关阶段 | 阶段 | 内容 | | --- | --- | -| P2 | 槽位、原子写、截图 sidecar → 扩展 `SnapshotPersistence` | -| P3 | 可存点判定、写盘门控 | -| P4 | 按 §4.1 重写 `SnapshotRestore`(Phase + Barrier);refactor Provider 契约;`SaveRestoreOrchestrator` 接入淡入淡出 | +| 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、继续游戏、新游戏覆盖自动档、游戏中读档确认等玩家流程 | diff --git a/Docs/存档系统设计方案.md b/Docs/存档系统设计方案.md index 337a91a49..61adb5f44 100644 --- a/Docs/存档系统设计方案.md +++ b/Docs/存档系统设计方案.md @@ -1,84 +1,45 @@ -# 存档系统设计方案 - - +# 存档系统设计方案 > 本文档为**实现层**设计文档,承接 [存档系统需求](存档系统需求.md)。 - > 记录拆分方案、各部分实现思路,以及已确定的关键决策。需求未定项见文末 TODO。 - - ## 〇. 文档状态 - - - 需求来源:`Docs/存档系统需求.md` -- 当前阶段:**P1 快照层已实现**;原 `StorageSystem` 已重命名为 `YarnVariableStorage` 并完成职责拆分;FixStateMachine 接口与 DTO 已接入。 -- 最近更新:2026-06-11(Fix 场景快照接口层落地,P1 DTO 设计告一段落;深度维修待 P5)。 - - +- 当前阶段:**P1 快照层、P2 槽位/落盘基础版、P3 可存点判定基础版、P4 读档编排基础版已落地**。原 `StorageSystem` 已重命名为 `YarnVariableStorage` 并完成职责拆分;FixStateMachine 接口与 DTO 已接入;自动档 / 手动档槽位、sidecar、缩略图、原子写、latest_slot 与 P1 测试档迁移已接入;Yarn `onNodeStart` 已触发自动存档判定与写盘;读档已切换为 Provider sync/async 契约 + Phase/Barrier 编排。 +- 最近更新:2026-06-18(P4 基础版落地:Provider 契约重构、读档自动存档抑制、Timeline Addressable 可等待恢复、验证窗口读档入口;正式 UI、P5 深度维修、P6 玩家流程仍未闭环)。 --- - - ## 一. 前提:旧实现的三档归类 - - 需求§一明确:新方案**不沿用**旧实现的**恢复锚点、存档文件组织**等不可靠部分;旧系统中**仍可靠的底层能力**在后续实现阶段**再评估复用**。据此分三档: - - | 档别 | 含义 | 内容 | - | --- | --- | --- | - | **A. 废弃 / 重做** | 被点名的不可靠部分,不沿用旧做法 | 恢复锚点;存档文件组织;依附其上的「无条件写盘触发」 | - | **B. 候选,待评估复用** | 旧系统中仍可靠的底层能力,到对应阶段再决定是否复用 | Yarn 变量存取能力;承接深度维修状态的数据容器(`IData`/`DataContainer`) | - | **C. 全新构建** | 旧系统无对应实现 | 快照模型、槽位/落盘、可存点判定、复原编排、存读档 UI、横切项 | - - > B 类只是「候选」,不作为既定地基;是否复用、复用多少,在 P1/P5 设计时单独决策。 - - --- - - ## 二. 关键决策记录 - - | 编号 | 决策 | 取值 | 影响 | - | --- | --- | --- | --- | - | D1 | 表现类复原方式 | **逐项还原** | P1 快照内容、P4 复原方式 | - | D1.1 | Timeline 等时间序列项的复原粒度 | 复原到**结尾(或开头)状态**即可,**不**按时间轴逐帧复原 | P1/P4 | - | D2 | 自动档策略 | **单覆盖**(始终保留一份,新档覆盖旧档) | P2 | - | D3 | 槽位界面是否截图 | **需要截图** | P0/P2 | - | D4 | 「继续游戏」指向 | **最近保存的存档**(自动 / 手动中时间最新的一份) | P6 | - | D5 | 新游戏与既有档关系 | **覆盖自动存档,不影响其他(手动)存档** | P6 | - | D6 | 读档还原编排 | **Phase + Barrier**:少数异步边界 `yield`,其余同步批量还原 | P4 编排、`ISnapshotProvider` 契约 | - - ### 决策展开 - - - **D1 逐项还原**:表现类(角色动画、环境、Timeline、UI 遮罩等)在读档时由快照中记录的状态逐项恢复,而非「重进节点重演」。 - **D1.1 时间序列粒度**:Timeline 等只需复原到终态或初态,不记录播放进度。 @@ -87,399 +48,229 @@ - **D2~D5**:见上表;P2/P6 实现时使用。 - - --- - - ## 三. 两个被点名不可靠点的全新设计(A 档) - - ### 1. 恢复锚点(全新) - - - **锚点定义**:YarnProject 标识 + 节点名 + 进入该节点那一刻的宏观阶段。 -- **落点时机**:P3 可存点判定通过后(当前 P1:`SnapshotCapture` 在进入节点写档时捕获 `GetCurrentNodeContext()`)。 +- **落点时机**:P3 可存点判定通过后(当前代码由 `DialogController.OnNodeStart` 传入触发节点名,`SnapshotCapture` 以该节点作为 anchor)。 - **瞬跳节点(规范层,待脚本排查 + 实现补强)**:凡进入后不向玩家停留、仅作路由/阶段切换的节点,均不得作为存档边界。维修流程中 **`center` 明确属于瞬跳**;`start` / `init` / `event` / `end` 等 tag 已在 `SavePointEvaluator` 黑名单。部分 **`content` 瞬跳节点**(如 `UF检查状态`)须 Yarn 侧标 `no_save`,详见 [Yarn 维修节点类型规范 §8](Yarn维修节点类型规范.md#8-瞬跳节点与存档边界)。另:若判定通过后在 settle 帧内 Yarn 已连跳,锚点 nodeName 可能漂移——属实现层已知问题,与瞬跳规范一并处理。 - **重入方式**:读档后 `SnapshotRestore.RestoreAnchor` → `StartDialogue(节点名)`。 - - ### 2. 存档文件组织(全新) - - -- 槽位制、sidecar、原子写、Steam 目录——**P2 实现**;P1 仅用 `SnapshotPersistence` 写固定测试路径。 - - +- 槽位制、sidecar、原子写、截图缩略图——**P2 基础实现已落地**,统一由 `SlotManager` / `SlotDirectory` 写入 `persistentDataPath/AllOurBrokenParts/saves/slot_x/`。Steam 云存档目前仅保留 `CloudSaveManager` / `ICloudSaveBackend` 扩展点,尚未接真实平台后端。 --- - - ## 四. 代码架构:职责拆分(已实现) - - 旧 `StorageSystem`(现 `YarnVariableStorage`)曾同时承担 Yarn 变量、深度维修容器、快照 I/O、读档编排,现已拆成以下类。**禁止再往 `YarnVariableStorage` 上堆存档逻辑。** - - ``` - ┌─────────────────────┐ - │ YarnVariableStorage │ Yarn 运行时变量(VariableStorageBehaviour) - └──────────┬──────────┘ - │ GetAllVariables / SetAllVariables - ┌──────────▼──────────┐ - │ SnapshotService │ Capture() / Restore() — 纯内存快照 - └──────────┬──────────┘ - ┌───────────────────┼───────────────────┐ - │ │ │ - ┌──────────▼─────────┐ ┌───────▼────────┐ ┌───────▼──────────────────┐ - │ SnapshotPersistence │ │ ISnapshotProvider × N │ DeepRepairDataRegistry │ - │ 文件读写(P2 接管) │ │ scene/macro/env/… │ 深度维修 IData(P5) │ - └──────────┬─────────┘ └──────────────────────┘ └────────────────────────┘ - │ - ┌──────────▼─────────────────┐ - │ SaveRestoreOrchestrator │ 存读档流程:UI 反馈、落盘、还原、旧档兼容 - └────────────────────────────┘ - ``` - - | 类 | 路径 | 职责 | - | --- | --- | --- | - | `YarnVariableStorage` | `Game Loop/YarnVariableStorage.cs` | 仅 Yarn `$` / `$global_` 变量读写 | - | `SnapshotService` | `SaveSystem/SnapshotService.cs` | `Capture()` / `Restore()`,不涉及文件 | - -| `SnapshotPersistence` | `SaveSystem/SnapshotPersistence.cs` | JSON 读写;P1 测试路径;旧档格式检测 | - -| `SaveRestoreOrchestrator` | `SaveSystem/SaveRestoreOrchestrator.cs` | `SaveToTestPath()` / `RestoreFromFile()`;保存 UI;legacy 分支 | - +| `SnapshotPersistence` | `SaveSystem/SnapshotPersistence.cs` | JSON 序列化 / 反序列化;P1 测试路径兼容;旧档格式检测 | +| `SlotManager` / `SlotDirectory` | `SaveSystem/SlotManager.cs` / `SaveSystem/SlotFileSystem.cs` | P2 槽位业务、sidecar、缩略图、latest_slot、原子写、P1 测试档迁移 | +| `SaveRestoreOrchestrator` | `SaveSystem/SaveRestoreOrchestrator.cs` | `AutoSaveRoutine()` / `CreateManualSlot()` / `RestoreFromSlot()` / `RestoreFromFile()`;保存 UI;legacy 分支 | | `DeepRepairDataRegistry` | `SaveSystem/DeepRepairDataRegistry.cs` | `RegisterData` / `LoadByJson`(FixSystem 等 P5 前临时) | - | `SnapshotCapture` / `SnapshotRestore` | `SaveSystem/` | 快照组装与逐项 provider 还原 | - | `SnapshotSerializer` | `SaveSystem/` | schemaVersion、稳定 SaveId key | - | `SnapshotRegistry` + `Providers/*` | `SaveSystem/` | 6 个表现层 Provider(env / actor / audio / timeline / fix / screen) | - - ### 调用约定 - - | 场景 | 入口 | - | --- | --- | - -| `<>` | `SaveRestoreOrchestrator.SaveToTestPath()` | - -| 读档 | `SaveRestoreOrchestrator.RestoreFromFile(path)` | - -| 仅捕获内存快照(P2 手动档固化) | `SnapshotService.Capture()` | - +| Yarn `onNodeStart` 自动存档 | `SavePointEvaluator.CanAutoSave()` → `SaveRestoreOrchestrator.AutoSaveRoutine(nodeName)` | +| 手动存档 | `SaveRestoreOrchestrator.CreateManualSlot(slotIndex)`(复制最近自动档) | +| 槽位读档 | `SaveRestoreOrchestrator.RestoreFromSlot(slotIndex)` | +| 文件读档(旧档 / 调试) | `SaveRestoreOrchestrator.RestoreFromFile(path)` | +| 仅捕获内存快照 | `SnapshotService.Capture()` | | 深度维修注册 | `DeepRepairDataRegistry.RegisterData(...)` | - | Yarn 变量 | `YarnVariableStorage.Instance.SetValue / TryGetValue` | - - ### P1 测试落盘路径 - - `persistentDataPath/AllOurBrokenParts/snapshot_test/latest_snapshot.json`(`ConstRef.SnapshotTestPath`) - - ### 4.1 读档还原编排与 Provider 契约(重要) - - > **勿将 P1 代码形态当作终态架构。** 当前 `ISnapshotProvider.Restore` 统一返回 `IEnumerator`,且 `SnapshotRestore` 对每个 Provider 做 `yield return`,这是受旧 `IData.Load()` 影响的**临时 scaffolding**,容易误导后续 P4/P5 规划。本节描述终态模型;P4 实施前应据此 refactor 编排层与 Provider 契约。 - - #### 终态模型:Phase + Barrier,而非协程链 - - 读档还原由 `SnapshotRestore` / `SaveRestoreOrchestrator`(P4 扩展)按**阶段**编排,阶段之间用 **Barrier(必须等完再继续)** 分隔: - - ``` - Phase 0 同步预备 - └─ Yarn 变量 SetAllVariables - │ - Phase 1 Barrier(异步) - └─ 场景加载(Addressables LoadSceneAsync) - │ - Phase 2 同步批量还原(同帧连续调用,不逐步 yield) - └─ macro / env / actor / audio / timeline / fix / screen … - │ - Phase 2′ 可选 Barrier(异步,按需) - └─ Timeline Addressable 加载、其他须上报的 async 还原 - │ - Phase 3 Barrier(异步) - └─ RestoreAnchor(Stop + StartDialogue) - │ - Phase 4 Barrier(P4:淡入淡出等演出时序) - └─ 黑屏 / Loading / FadeIn … - ``` - - - **Barrier**:该步完成前不进入下一阶段(如场景未加载完不还原 actor;Yarn 未 LoadDialog 不 StartDialogue)。 - - **同步批量**:Phase 2 内各子系统在主线程上连续调用即可,**不需要** Provider 之间逐步 `yield return`;这与 D1「逐项还原」不矛盾——「逐项」指各子系统各自写回状态,不是指每步之间必须挂起协程。 - - **并行**:Unity 主线程上不做多线程并行;此处「不必串行 yield」指**不必为 sync 还原逐步挂协程**,而非 CPU 多线程并行。 - - #### 哪些步骤属于 Barrier(异步边界) - - | 步骤 | 是否 Barrier | 说明 | - | --- | --- | --- | - | Yarn 变量 | 否(Phase 0 sync) | `SetAllVariables` | - | 场景加载 | **是** | 唯一 P1 中 Provider 层真正需要等待的 async | - | macro / env / actor / audio / fix / screen | 否(Phase 2 sync) | 当前实现均为同步写回;fix 的 `EnterImmediate` 默认 `yield break` | - | timeline(本地终态) | 否(Phase 2 sync) | `RestoreAtEndLocal` 等 | - | timeline(Addressable) | **可选 Barrier** | 若要求进节点前 Timeline 终态就绪,须显式等待;**禁止** fire-and-forget(见下) | - | RestoreAnchor | **是** | `StartDialogue` 为 Yarn 异步 API | - | P4 淡入淡出 | **是** | 演出时序 | - | P5 深度维修 | **部分 Barrier** | 如 `FixSystemData.Load()` → `SwitchState` 须等待;不宜硬套「与 scene 同款的单一 Provider 协程」 | - - #### `RestoreOrder` 的语义(终态) - - - 表示**同 Phase 内的建议顺序或软依赖**(如 scene 必须先于 env/actor),**不是**「每步之间必须 `yield return`」。 - - 硬依赖应通过 **Phase 划分 + Barrier** 表达,而不是无限细化 RestoreOrder 数字。 - - scene 加载(Phase 1 Barrier)必须在 Phase 2 之前完成;设置章节 SO(Phase 1.5)必须在 RestoreAnchor 之前完成;env / actor / audio / timeline / fix / screen 之间目前无硬依赖,Phase 2 内一批执行即可。 - - #### Provider 契约:终态 vs P1 临时形态 - - | | P1 临时形态(当前代码) | 终态(P4 前 refactor 目标) | - | --- | --- | --- | - | 同步还原 | `IEnumerator Restore` + `yield break` | `void Restore(object dto)` | - | 异步还原 | 同上(仅 scene 真正 yield) | `IAsyncSnapshotRestore.RestoreAsync(object dto)` 或等价显式接口 | - | Manager 层 | 部分 `IEnumerator RestoreSnapshot` 仅 `yield break` | 默认 `void RestoreSnapshot`;仅真有 async 处保留 `IEnumerator` | - | 编排层 | `foreach` 逐步 `yield return provider.Restore` | Phase 编排 + 仅对 Barrier 步骤 `yield` | - - 新增可存子系统时:**默认实现 sync `Restore`**;仅当存在必须等待的加载/状态切换时,才实现 async 接口并向编排层**上报**可等待句柄,不得在 Provider 内私自 `StartCoroutine` 而不纳入 Barrier。 - - #### 已知偏离(tech debt,非推荐 pattern) - - - `DirectorHandler.RestoreSnapshotEntry` 在 Addressable 路径下内部 `StartCoroutine(RestoreAtEndFromAddressable)`,Provider 已返回,编排层无法感知完成——与 D6 冲突。P4 编排时应改为**可等待**的还原路径,或纳入 Phase 2′ Barrier。 - - 各 Manager 的 `RestoreSnapshot` 声明为 `IEnumerator` 但内部仅 `yield break`——属 `IData.Load()` 惯性,终态应收回到 `void`。 - - #### 对后续阶段的影响 - - - **P2 / P3**:可不改动 `SnapshotRestore`;但新增代码**不应**再复制「全 Provider 协程链」模式。 - - **P4**:在 `SaveRestoreOrchestrator` / `SnapshotRestore` 上按本节 Phase 模型重写编排;顺带 refactor Provider 契约。P4 是「加 fade UI」+「修正编排模型」,而非在现有 foreach 链首尾叠 UI。 - - **P5**:深度维修还原更接近旧 `DataContainer` + `LoadIndex` + 部分 `IData.Load()` async,**不应**假设「再注册一个 Provider、继续逐步 yield」即可;是否独立 Phase、哪些模块 Barrier,在 P5 单独定案。 - - --- - - ## 五. 拆分方案(分层 × 分阶段) - - ``` - P0 契约定义 ── 已完成 - │ - P1 快照层 ── 已实现(含原 StorageSystem 职责拆分与重命名) - │ - ┌────┴───────────────┐ - -P2 槽位/落盘层 P3 可存点判定层 - +P2 槽位/落盘层基础版 P3 可存点判定基础版 +已落地:slot_0/slot_1..8、sidecar、缩略图、原子写、latest_slot、onNodeStart 门控 └────┬───────────────┘ - │ - -P4 复原编排层(按 §4.1 Phase+Barrier 重写 SnapshotRestore,并扩展淡入淡出) - +P4 复原编排层基础版(Provider 契约 + Phase/Barrier + 抑制自动存档)── 已落地 │ - ┌────┴────┐ - P5 深度维修阶段存档 P6 存/读档 UI & 流程 - │ - P7 横切 - ``` - - ### P1 快照层(已实现) - - - `SaveSnapshot` 纯数据结构 + `ISnapshotProvider` 逐项还原(D1/D1.1)。 - - Provider:`scene` / `macro` / `env` / `actor` / `audio` / `timeline` / `fix` / `screen`。 - - Yarn 变量经 `YarnVariableStorage` 纳入快照;`$data.*` 死路径已移除。 - - `DeepRepair` 段可空占位(`DeepRepairSavePolicy`),P5 再定。 - - 深度维修旧 `IData` 仍经 `DeepRepairDataRegistry` 运行,**尚未**纳入新快照。 - - **注意**:P1 中 `Restore` 统一为协程、逐步 `yield return` 属临时实现,终态见 **§4.1 / D6**;后续阶段勿在此基础上堆逻辑。 +### P2 槽位/落盘层(基础版已落地) +- `SlotIndex`:`slot_0` 为自动档,`slot_1..slot_8` 为手动档。 +- `SlotDirectory` / `SlotAtomicWriter`:槽位目录、`snapshot.json` / `meta.json` / `thumbnail.png`、`latest_slot.json` 与原子写。 +- `SlotManager`:自动档覆盖写入、手动档复制、删除槽位、读取快照 / meta / 缩略图、UI view model、P1 测试档迁移。 +- `SlotThumbnailCapture`:当前使用 `Camera.main` 渲染 640×360 PNG;失败时返回 null,不阻断存档。 +- `CloudSaveManager` / `ICloudSaveBackend`:仅保留云存档扩展点,未接 Steam 后端与冲突处理。 +- 手动档 = 拷贝最近自动快照,符合需求约定。 -### P2 槽位/落盘层(待做) +仍待补齐: +- 玩家 UI 尚未接入 `SlotManager.GetSlotViewModels()`;旧 `SavesPanel` / `SaveFileUIController` 仍按 `StreamingAssets/SaveFiles/*.json` 或旧文件列表工作。 +- 「继续游戏」尚未统一改为 `SlotManager.GetLatestSlotIndex()` → `GameManager.StartWithSlot()`;`ChapterController` 仍按旧扁平 JSON 文件查找。 +- 缩略图规格暂按 640×360 实装,是否满足正式 UI 仍需 P6 验证。 +### P3 可存点判定层(基础版已落地) -- 接管 `SnapshotPersistence`:槽位抽象、原子写、sidecar、截图(D2/D3)。 +- `DialogController.OnNodeStart` 更新当前 node/tags 后调用 `SavePointEvaluator.CanAutoSave()`,通过后执行 `SaveRestoreOrchestrator.AutoSaveRoutine(nodeName)`。 +- `SavePointEvaluator` 当前规则:`hub` / `linear` / `content` 默认允许;`start` / `init` / `function` / `detour` / `center` / `performance` / `event` / `end` 默认禁止;`no_save` 覆盖一切并禁止。 +- `SaveRestoreOrchestrator.IsRestoring` 与 `GameManager.state.isInPause` 会阻止自动保存,避免读档中覆盖自动档。 +- `CreateManualSlot` 复用 `CanManualSave`:当前可自动存档且 `slot_0` 存在时,才能复制到手动档。 -- 手动档 = 拷贝最近自动快照。 +仍待补齐: +- 维修 Yarn 的瞬跳 `content` 节点需要继续排查并补 `no_save`,优先处理验证工具中暴露的误存节点。 +- “无活跃 Yarn 节点默认允许存档”与“深度维修过程中不可手动存档”的需求边界需要再确认;若部分 FixSystemNew 交互状态可存,应显式列清楚。 +- 目前验证工具主要覆盖存盘行为;复杂节点 tags 边界和读档后落点尚未形成自动化验证。 +### P4~P7 -### P3~P7 - - - -- P4:按 **§4.1** 将编排从「Provider 协程链」改为 Phase + Barrier;refactor `ISnapshotProvider` 为 sync 默认 + 显式 async;在 `SaveRestoreOrchestrator` 接入淡入淡出。不再回到 `YarnVariableStorage`。 - +- P4:基础版已落地。`ISnapshotProvider` 已拆为捕获契约 + `ISyncSnapshotProvider` / `IAsyncSnapshotProvider`;`SnapshotRestore` 已改为 Phase + Barrier;`SaveRestoreOrchestrator` 已接入读档黑屏、自动存档抑制与 restore 日志;Timeline Addressable 恢复已改为可等待路径。 - P5:深度维修还原单独定案,不默认套用 P1 Provider 协程模式(见 §4.1)。 - -- P6 / P7:见需求文档与上文决策表。 - - +- P6:接入正式存 / 读档 UI、继续游戏、新游戏覆盖自动档、游戏中读档确认等玩家流程。 +- P7:Steam 云存档真实后端、版本兼容策略、全局 / 跨周目数据边界等横切项。 --- - - ## 六. 推荐落地顺序 - - -**P0 → P1(完成)→ P2 → P3 → P4 →(P5/P6)→ P7** - - - +**P0 → P1(完成)→ P2/P3 基础版(已落地,待补边界与 UI 接入)→ P4 基础版(完成,待 Play Mode 验证)→(P5/P6)→ P7** --- - - ## 七. 仍待确定(TODO) - - - - 手动档数量上限、覆盖 / 删除 / 重命名规则。 - - 游戏进行中读档是否允许、是否二次确认。 - -- 自动存档是否需要可见反馈(当前 Orchestrator 保留旧 loading UI)。 - +- 自动存档是否需要正式可见反馈(当前 `SaveRestoreOrchestrator` 使用 `InfoPanel.ShowSaveLoading()` / `HideSaveLoading()`,是否作为最终 UX 待定)。 - 各深度维修是否支持阶段存档的逐个清单(P5)。 - - **维修 Yarn 瞬跳 `content` 节点**:按 [Yarn 维修节点类型规范 §8.4](Yarn维修节点类型规范.md#84-维修脚本中的常见瞬跳-content待排查) 全项目补 `no_save`(Peipei `UF检查状态` 等优先)。 - - 全局 / 跨周目数据与单局存档的边界划分。 - -- 截图的具体规格(P2)。 - +- 缩略图最终规格与 UI 展示方式(当前 P2 基础版为 640×360 PNG)。