feat(shared)!: resolve save paths per dimension #14

Merged
Serkyo merged 5 commits from feat/dimension-save-paths into dev 2026-10-02 18:09:10 +00:00
Owner

What does this PR do?

I gave dimensions a name and moved region files under a per-dimension directory.

  • shared::registry gains DimensionId, a newtype over ContentId so a block or item ID cannot stand in for a dimension, with DimensionId::nyelenor() returning syn:nyelenor. A private ContentId::syn constructor builds first-party IDs, and RegistryBuilder::with_air now uses it too.
  • shared::paths::region_dir takes a &DimensionId and resolves to saves/<world>/dim/<namespace>/<name>/region/. The ID splits into two path components because : is not a legal filename character on Windows.
  • shared::paths also resolves entities_dir beside a dimension's regions and the world-scope playerdata_dir, modstate_dir and worldstate_dir. Nothing writes to them yet.
  • The server and the inspect_save example pass DimensionId::nyelenor() to region_dir, and the server logs the dimension with the save location.
  • WORLD_FORMAT_VERSION goes from 4 to 5, so a world saved with the old flat region/ layout is refused at load.
  • docs/save_format.md describes the new layout and the version 5 bump.

Why is this change necessary?

Every region file sat directly under the world's region/ directory, so the save had no place for a second dimension's chunks. Keying the path by dimension now means adding one later is a new directory, with no change to the layout every existing world already uses.

Scope of Changes

server, shared, workspace

Testing

Automated:

  • cargo check -p shared -p server passed.
  • cargo test -p shared -p server passed, including the new tests in crates/shared/src/tests/paths.rs for the dimension and world-scope directories, nyelenor_matches_its_parsed_id, and rejects_a_version_4_record.
  • cargo clippy --all-targets --all-features -- -D warnings passed.
  • cargo fmt --all -- --check passed.

Manual:

I started the server and the client on a fresh world. The save came out as saves/default/level.dat beside dim/syn/nyelenor/region/, which held the eight region files around the origin (r.0.0.0 through r.-1.-1.-1). No region/ directory was written at the world root, and the level.dat header carries SYNW with format version 5.

Additional Context

This breaks the save format. WORLD_FORMAT_VERSION moves to 5 and a version 4 level.dat is refused with UnsupportedVersion. The version 4 payload is identical to version 5; only the region layout moved, so the header is the only thing marking the difference. There is no migrator, so existing worlds have to be recreated.

The worldgen registry in the server stays keyed by version alone. Every generator belongs to Nyelenor, so a dimension in the key would carry no information until a second dimension generates terrain.

None.

Checklist

  • I have branched from dev (or a feature branch off dev) and my PR targets dev.
  • I have kept my changes focused to a single concept.
  • I have added or updated documentation (/// doc comments for Rust) where necessary.
  • I have tested my changes and described any relevant automated or manual testing above.
### What does this PR do? I gave dimensions a name and moved region files under a per-dimension directory. - `shared::registry` gains `DimensionId`, a newtype over `ContentId` so a block or item ID cannot stand in for a dimension, with `DimensionId::nyelenor()` returning `syn:nyelenor`. A private `ContentId::syn` constructor builds first-party IDs, and `RegistryBuilder::with_air` now uses it too. - `shared::paths::region_dir` takes a `&DimensionId` and resolves to `saves/<world>/dim/<namespace>/<name>/region/`. The ID splits into two path components because `:` is not a legal filename character on Windows. - `shared::paths` also resolves `entities_dir` beside a dimension's regions and the world-scope `playerdata_dir`, `modstate_dir` and `worldstate_dir`. Nothing writes to them yet. - The server and the `inspect_save` example pass `DimensionId::nyelenor()` to `region_dir`, and the server logs the dimension with the save location. - `WORLD_FORMAT_VERSION` goes from 4 to 5, so a world saved with the old flat `region/` layout is refused at load. - `docs/save_format.md` describes the new layout and the version 5 bump. ### Why is this change necessary? Every region file sat directly under the world's `region/` directory, so the save had no place for a second dimension's chunks. Keying the path by dimension now means adding one later is a new directory, with no change to the layout every existing world already uses. ### Scope of Changes server, shared, workspace ### Testing Automated: - `cargo check -p shared -p server` passed. - `cargo test -p shared -p server` passed, including the new tests in `crates/shared/src/tests/paths.rs` for the dimension and world-scope directories, `nyelenor_matches_its_parsed_id`, and `rejects_a_version_4_record`. - `cargo clippy --all-targets --all-features -- -D warnings` passed. - `cargo fmt --all -- --check` passed. Manual: I started the server and the client on a fresh world. The save came out as `saves/default/level.dat` beside `dim/syn/nyelenor/region/`, which held the eight region files around the origin (`r.0.0.0` through `r.-1.-1.-1`). No `region/` directory was written at the world root, and the `level.dat` header carries `SYNW` with format version 5. ### Additional Context This breaks the save format. `WORLD_FORMAT_VERSION` moves to 5 and a version 4 `level.dat` is refused with `UnsupportedVersion`. The version 4 payload is identical to version 5; only the region layout moved, so the header is the only thing marking the difference. There is no migrator, so existing worlds have to be recreated. The worldgen registry in the server stays keyed by version alone. Every generator belongs to Nyelenor, so a dimension in the key would carry no information until a second dimension generates terrain. ### Related Issues None. ### Checklist - [x] I have branched from `dev` (or a feature branch off `dev`) and my PR targets `dev`. - [x] I have kept my changes focused to a single concept. - [x] I have added or updated documentation (`///` doc comments for Rust) where necessary. - [x] I have tested my changes and described any relevant automated or manual testing above.
docs(workspace): describe the per-dimension save layout
All checks were successful
Auto Labeler / label-scope (pull_request_target) Successful in 3s
CLA Signed All authors have signed the CLA.
CLA Check / cla-check (pull_request_target) Successful in 3s
CI / Rust Check, Lint & Test (pull_request) Successful in 36m54s
CI / Dependency Licenses & Advisories (pull_request) Successful in 33s
CI / Lua Lint & Format (pull_request) Successful in 8s
CI / Commit Message Lint (pull_request) Successful in 3s
CI / LFS Pointer Guard (pull_request) Successful in 6s
137f27b421
Serkyo deleted branch feat/dimension-save-paths 2026-10-02 18:09:11 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Synvael/synvael!14
No description provided.