diff options
Diffstat (limited to 'docs/api-reference/world-save.md')
| -rw-r--r-- | docs/api-reference/world-save.md | 57 |
1 files changed, 57 insertions, 0 deletions
diff --git a/docs/api-reference/world-save.md b/docs/api-reference/world-save.md new file mode 100644 index 0000000..a8f474f --- /dev/null +++ b/docs/api-reference/world-save.md @@ -0,0 +1,57 @@ +# 存读档(WorldSave) + +源码:`src/LivingVillage.Kernel/WorldSave.fs`(`module WorldSave`)。 + +## 职责 + +把 `Sim.World`(可选附 `Occupation.State`)序列化为稳定的 `|` 分隔文本并解析回来, +兼容 v1/v2/v3 三种版式;不负责文件路径策略与 UI(由 Desktop 调用方决定)。 + +## 对外 API 概览 + +- `save world : string` = `saveWith None world`(无职业,产 **v2**,与历史格式逐字节一致)。 +- `saveWith occupation world : string`:`Some state` → **v3**(含职业段与可选尾段)。 +- `load text : Result<World, string>`、`loadFromFile path : Result<World, string>`。 +- `loadFromFileWith path : Result<World * Occupation.State option, string>`(读 v3 职业伴随数据)。 +- `saveToFile path world` / `saveToFileWith occupation path world`(UTF-8 无 BOM)。 +- 内部 token 读写:`writeDailyTask`(`task-today` 段)、`writeBackpack`(`backpack` 段)、 + `writeAnnal`(年鉴,含 `story`)。 + +## 单位与量纲 + +- 版式头:`LV_WORLD_SAVE_V1`(无宽高,读回默认 64×48)、`LV_WORLD_SAVE_V2`(带 `width height`)、 + `LV_WORLD_SAVE_V3`(带职业段)。文本以 `|` 分隔。 +- v3 职业段:`occupation.kind`(`farmer|fisher|peddler|scholar` 之一,未知 → `None`)、 + `occupation.task`(int)、`occupation.stage`(int)。 +- 尾段(annals 之后,按需出现):`task-today`(模板/状态/`OfferedTick`/`DueTick`/ + `npc-none|npc-some (+id)`/`tile-none|tile-some (+x,y)`)、`backpack`(`count` + 逐项 + `item quantity`)、`streak`(仅 `> 0` 时)。 +- 任务模板 token:`deliver-grain/help-work/till-soil/night-catch/sell-fish/market-inquiry/ + buy-goods/resell/observe-notes/reason-debate`;状态 token:`offered/active/done/failed`。 + +## 不变量 + +1. **v2 逐字节稳定**:`save world` 的输出在相同 `World` 下不变;`saveWith None world` 与 `save world` + 完全一致。 +2. **无职业 = 旧行为**:不带职业时 v2 序列化不产生任何 v3 专有段。 +3. **v1 向后兼容**:读到 `LV_WORLD_SAVE_V1` 时回退地图边界 64×48。 +4. **派生量不入档**:`RumorCount` / `RumorOldestDay` 不写;读档用 `rumorWorkingSetStats` 重建。 +5. **尾段可缺省**:旧 v3 无 `task-today`/`backpack`/`streak` 时,读回 `Today = None`、 + `Backpack` 用 `profileOf` 默认值、`Streak = 0`。 +6. **完整消费**:解析后 `reader.Remaining = 0`,否则视为 `trailing save data`。 + +## 失败分支 + +- 头未知 → `Error "unsupported save format"`。 +- token 非法/缺字段/多余尾数据 → `SaveParseError`,归一为 `Error`。 +- 数值越界/格式错误 → 捕获 `FormatException` / `OverflowException` / `ArgumentException`, + 返回 `Error "invalid save: <message>"`;`null` 文本 → `Error "save text is null"`。 +- 文件读取失败 → `Error "could not read save: <message>"`(捕获 `IOException`)。 +- 背包数量 `< 0` → 解析失败(`invalid backpack[i].quantity`)。 + +## 算法与性能取舍 + +- 手写 token 序列化(`ResizeArray<string>` 拼接),无反射/无 JSON 依赖;浮点用 + `CultureInfo.InvariantCulture` 格式以保证跨机一致。 +- 版本化采用「头 + 可选段」而非新格式文件:读端按头分派,v2 路径零额外开销。 +- 存档不是热路径;解析失败一律以 `Result` 返回,不抛出到调用方。 |
