Save(进度与存档)
Save 保存与恢复剧本播放进度,并登记每个扩展单元对旗标命名空间的归属。它把「谁写了哪些旗标」和「进度本身」分开记录,卸载单元不会删除用户进度。
顶部信息表
| 项 | 内容 |
|---|---|
| 契约版本 | save@1 |
| 当前状态 | 已完成 |
| 源码目录 | src/core/save |
| 页面定位 | 面向小插件作者与外壳集成者,说明槽位、后端、旗标归属与孤儿数据的边界 |
职责
负责:
- 手动槽位的保存、读取、删除与摘要列表;
- 存档对象的编码与解码校验;
- 旗标命名空间的归属登记与孤儿状态;
- 已读键集合的查询与合批写入;
- 在 Stage 在场时保存、恢复舞台快照。
不负责:
- 剧本 VM 的状态定义与续播,见 ScriptPlay;
- 舞台、音频的实际表现,见 Stage 与 Audio;
- 云存档、自动存档、孤儿 GC 与自动删除;这些属于后续阶段;
- 外壳的存档界面本身;内置外壳只消费本插件的服务。
对外接口
名称与 policy 以 src/core/save/contract.ts 为准。
| 名称 | 类别 | policy | 说明 |
|---|---|---|---|
save:slots | 服务 | trusted | 槽位列表、保存、读取、删除 |
save:codec | 服务 | trusted | 存档对象与 JSON 文本互转 |
save:orphans | 服务 | closed | 只读归属记录,不开放登记或清理 |
progress:stats | 服务 | trusted | 已读键查询与记录 |
save:resolve-field | 机制插槽 | trusted | 保存时解析单个旗标值 |
save:on-save | 通知 | — | 保存成功后广播槽位摘要 |
save:on-load | 通知 | — | 读档后广播结果 |
save:on-orphan-detected | 通知 | — | 单元卸载后广播受影响的归属记录 |
save:on-gc-completed | 通知 | — | 只声明;GC 在后续阶段实现 |
save:backend@1 | 实现位(single) | trusted | 默认 indexeddb,卸载后回落 |
save:codec-impl@1 | 实现位(single) | trusted | 默认 json,校验字段与格式版本 |
save:orphan-store@1 | 实现位(single) | closed | 默认 indexeddb,只读 list() |
槽位方法
list():返回槽位摘要数组。save(slot):保存当前 VM。load(slot):恢复 VM,返回{ slot, missingUnits }。remove(slot):显式删除指定槽位。
槽位编号是非空字符串。摘要含 slot、savedAt、position、units;savedAt 为 UTC 毫秒。
编解码方法
encode(data):返回 JSON 文本。decode(text):校验并返回存档对象。
存档对象含 format: 1、槽位编号、保存时间、播放位置、旗标、随机数状态、已走过的分支、单元稳定 ID 与版本。
visited 必须是不稀疏的字符串数组,最多 1,000,000 条,每项 UTF-8 字节数不超过 1 KiB。超出数量、单项过长、元素不是字符串或数组存在空洞的存档直接拒绝。
关键语义
保存与读档
- 保存要求编解码往返不改变状态;往返结果不一致即拒绝保存。
- 读档先等待旧 VM 停止,再恢复状态;外壳随后经
script:flow.resume()续播。 - 缺少单元时仍恢复全部旗标与状态,并返回缺失单元提示;继续播放仍须有相应剧本与指令实现。
- 播放位置可带
chapter。旧存档缺省时只继续当前图;未知的已记录章节应明确提示,不猜测章节归属。 - 默认后端把槽位与归属写入同一事务;事务失败不广播保存成功。
- 外部后端不能与本地归属库共享事务,因此先登记不可清理的记录,再调用外部写入;写入失败时保留保守记录并报告失败,不把失败当成已成功保存。
旗标归属
- 包通过可选的
flagNamespaces声明持有的旗标命名空间;同一命名空间只允许一个已装载单元声明,core与sui保留。 - 新旗标写入须有已装载的所有者;归属按
closed登记。 - 归属记录含
schema: 1、稳定 ID、版本、数据块编号、UTF-8 字节数、首次写入时间、最后认领时间与可清理性。 - 数据块按后端、槽位、命名空间区分;同一稳定身份更新版本不另建数据块,首次写入时间保留。
- 所有者的可清理性声明不一致时告警。
- 缺失单元的旧存档旗标保持原值,可再次保存;没有所有者时不能写入新的值。
- 卸载只把归属标为孤儿,不删除任何进度;装载不刷新认领时间,实际读档或写入才刷新。
- 归属无法读取或损坏时拒绝相关操作,保留数据。
机制插槽与通知
save:resolve-field在保存时接收{ name, value }并返回旗标值。- 它不能更改框架确定的归属或用户进度的
closed属性;不合法的返回值拒绝保存。 save:on-save广播槽位摘要;save:on-load广播读档结果;save:on-orphan-detected广播受影响的归属记录。save:on-gc-completed只有声明,当前不会发出。
读档与舞台快照
- Stage 在场时,存档可带可选字段
stage,内容为stage:compose.snapshot()的结果;形状不对即视为存档损坏。 - 读档在恢复 VM 之后依快照恢复舞台:先撤下在场的立绘,再依次调用
stage:bg.set、stage:cast.apply、stage:camera.apply,ms一律为 0。 - 没有 Stage 或旧存档不带
stage时不恢复舞台。 - 舞台恢复失败只在控制台告警,不影响读档本身;整屏黑幕不在快照里,读档不改变它。
已读统计
progress:stats提供isRead(key)与markRead(key),键为<剧本图 ID>#<节点 ID>#<指令位置>。- 集合存在同一个 IndexedDB 库的
progress表,清除存档不清它。 - 启动时整份载入内存供同步查询;记下的键合批写盘;载入或写盘失败只告警。
- 没有 IndexedDB 的环境只记在内存里。
当前实现范围
- 已完成:手动槽位保存、读取、删除与摘要;JSON 编解码;IndexedDB 后端与归属同事务;旗标归属登记、孤儿标记与写入限制;已读集合;舞台快照的保存与恢复;内置外壳的 20 个手动槽、覆盖二次确认与缺失单元提示。
- 未实现:孤儿 GC 与
save:on-gc-completed的实际发出,属于 Phase 4;自动存档与快速槽在界面上置灰,未提供;云同步、自动删除与周目继承未实现。 - 按设计,卸载单元只标记孤儿,不删除进度,因此「没有自动清理」不是缺陷而是当前边界。
相关链接
- 插槽与实现位:
save:backend@1等 single 实现位的回落规则。 - policy 与写边:
save:orphans为何是closed。 - 装载管线与 guard:单元卸载通知与归属状态变化的关系。
- 孤儿标记与清理:当前只登记、不清理的整体设计。
- ScriptPlay:VM 状态、旗标与续播入口。