Synvael/docs/adr/0009-baseline-relative-sparse-chunk-persistence.md
Serkyo cae434d227
Some checks are pending
CI / Rust Check & Lint (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Lua Lint & Format (push) Waiting to run
CI / LFS Pointer Guard (push) Waiting to run
docs(workspace): rewrite the subsystem notes and ADRs
2026-08-06 22:52:57 +02:00

3.6 KiB

0009. Baseline-relative sparse chunk persistence

  • Status: Accepted
  • Date: 2026-07-10

Context

Because our worldgen is strictly seed-deterministic (see ADR-0003), any completely unmodified chunk is reproducible bit-for-bit from just (seed, chunk_coord, worldgen_version). Our world is procedurally generated, entirely unbounded on the Y axis, and viewed at massive horizontal distances in both single-player and multiplayer. Because of this, the total set of chunks a session simply visits is effectively unbounded.

If we persisted the full voxel contents of every single visited chunk (the naive model), the save file size would scale directly with the volume explored rather than the volume actually changed. Since we use a half-scale voxel grid (ADR-0002) where a unit volume holds roughly eight times the voxels of a standard 1-meter grid, that storage cost would compound aggressively. The overwhelming majority of visited chunks are never modified by the player, so storing them on disk just duplicates data that our generator can reproduce perfectly on demand.

The part of this decision that is hardest to reverse is the on-disk representation of a chunk. Both the SYNC record format and the ChunkData type are strictly shaped by it, and changing this representation later will require a heavy save-format migration.

Decision

We only persist a chunk when its contents actually diverge from its deterministic baseline.

  • Representation: Both on disk (in the SYNC record) and in memory (as ChunkData), a modified chunk is stored purely as a sparse local_index → BlockId edit map layered directly over the regenerated baseline, alongside the worldgen_version that the baseline is pinned to. If a chunk is totally unmodified, it stores absolutely no voxel data and is omitted from its region file entirely.
  • Load: When we load a chunk, we resolve the baseline by regenerating it from the seed, and then we just apply the stored diff on top if a record exists. If there is no record (a miss), it means the chunk was never modified, so the freshly regenerated baseline is the chunk.
  • Version pinning: Each persisted chunk explicitly records the worldgen_version its baseline was generated under. This guarantees that a future generator update won't silently shift the baseline beneath an already-modified chunk and corrupt the edits. A region file pins a base_worldgen_version globally and only stores per-chunk exceptions to save space.

Consequences

  • Save file size explicitly scales with the volume modified, not the volume explored. If a player walks across untouched terrain for miles, it writes absolutely nothing to disk.
  • When you revisit an unmodified chunk, it inherently re-runs worldgen instead of reading anything back from disk. We mitigate this CPU cost heavily using an LRU cache of regenerated baselines. This cache is purely a performance layer and doesn't affect authority or determinism at all.
  • Worldgen determinism is aggressively promoted from a mere worldgen-local property to a rock-solid, load-bearing invariant of the entire persistence layer. If the generator ever ceased to be reproducible, every unmodified chunk and every stored diff's baseline would be instantly corrupted. Because of this, determinism regressions are guarded aggressively by tests.
  • A per-chunk worldgen_version stamp is mandatory metadata. The load and write-back paths must strictly honor it once we introduce more than one worldgen version. (Right now we assume a single version, and honoring it fully is tracked as follow-on work).