diff --git a/docs/README.md b/docs/README.md index cfcf2d3..1b94a13 100644 --- a/docs/README.md +++ b/docs/README.md @@ -38,6 +38,7 @@ Each subsystem note should name the design topic it implements (by title, e.g. " - [`adr/0006-base-game-on-modding-api.md`](adr/0006-base-game-on-modding-api.md): base game built on the modding API. - [`adr/0007-declarative-content-via-modding-api.md`](adr/0007-declarative-content-via-modding-api.md): declarative content loads through the modding API. - [`adr/0008-split-coordinate-entity-positions.md`](adr/0008-split-coordinate-entity-positions.md): split-coordinate entity positions. + - [`adr/0009-baseline-relative-sparse-chunk-persistence.md`](adr/0009-baseline-relative-sparse-chunk-persistence.md): baseline-relative sparse chunk persistence. - [`adr/template.md`](adr/template.md): template for new decisions. Subsystem notes: diff --git a/docs/adr/0009-baseline-relative-sparse-chunk-persistence.md b/docs/adr/0009-baseline-relative-sparse-chunk-persistence.md new file mode 100644 index 0000000..324ed27 --- /dev/null +++ b/docs/adr/0009-baseline-relative-sparse-chunk-persistence.md @@ -0,0 +1,27 @@ +# 0009. Baseline-relative sparse chunk persistence + +- **Status:** Accepted +- **Date:** 2026-07-10 + +## Context + +Worldgen is seed-deterministic ([ADR-0003](0003-seed-deterministic-worldgen.md)): any unmodified chunk is reproducible bit-for-bit from `(seed, chunk_coord, worldgen_version)`. The world is procedurally generated, unbounded in Y, and viewed at large horizontal distance in both single-player and multiplayer, so the set of chunks a session *visits* is effectively unbounded. + +Persisting the full voxel contents of every visited chunk, the naive model, makes save size scale with the volume *explored* rather than the volume *changed*. In a half-scale voxel grid ([ADR-0002](0002-half-scale-voxel-grid.md)), where a unit volume holds roughly eight times the voxels of a 1 m grid, that cost is compounded. The overwhelming majority of visited chunks are never modified, so storing them at all duplicates data the generator can reproduce on demand. + +The decision that is hard to reverse is the *on-disk representation* of a chunk: the `SYNC` record format and the `ChunkData` type are both shaped by it, and changing the representation later requires a save-format migration. + +## Decision + +A chunk is persisted only when its contents diverge from its deterministic baseline. + +- **Representation.** Both on disk (the `SYNC` record) and in memory (`ChunkData`), a modified chunk is stored as a sparse `local_index → BlockId` edit map layered over the regenerated baseline, together with the `worldgen_version` the baseline is pinned to. An unmodified chunk stores no voxel data and is omitted from its region file entirely. +- **Load.** A load resolves the baseline by regenerating it from the seed, then applies the stored diff when a record exists (a hit). A miss means the chunk was never modified, so the regenerated baseline *is* the chunk. +- **Version pinning.** Each persisted chunk records the `worldgen_version` its baseline was generated under, so a later generator update does not silently shift the baseline beneath an already-modified chunk. A region pins a `base_worldgen_version` and stores only per-chunk exceptions. + +## Consequences + +- Save size scales with the volume *modified*, not the volume explored. A session that walks across untouched terrain writes nothing. +- Revisiting an unmodified chunk re-runs worldgen instead of reading it back. This CPU cost is mitigated by an LRU cache of regenerated baselines, which is a pure performance layer and does not affect authority or determinism. +- Worldgen determinism is promoted from a worldgen-local property to a hard invariant of the persistence layer: if the generator ceased to be reproducible, every unmodified chunk and every stored diff's baseline would be corrupted. Determinism regressions are therefore guarded aggressively by tests. +- A per-chunk `worldgen_version` stamp is mandatory metadata, and the load and write-back paths must both honour it once more than one worldgen version exists. Until then a single version is assumed, tracked as follow-on work.