summaryrefslogtreecommitdiff
path: root/docs/api-reference/world-save.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/api-reference/world-save.md')
-rw-r--r--docs/api-reference/world-save.md57
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` 返回,不抛出到调用方。