docs(server): add ADR-0009 baseline-relative sparse persistence
This commit is contained in:
parent
0fedd1b30b
commit
6310c384e5
|
|
@ -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/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/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/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.
|
- [`adr/template.md`](adr/template.md): template for new decisions.
|
||||||
|
|
||||||
Subsystem notes:
|
Subsystem notes:
|
||||||
|
|
|
||||||
27
docs/adr/0009-baseline-relative-sparse-chunk-persistence.md
Normal file
27
docs/adr/0009-baseline-relative-sparse-chunk-persistence.md
Normal file
|
|
@ -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.
|
||||||
Loading…
Reference in a new issue