feat(server): resolve and persist the world spawn at creation #15

Merged
Serkyo merged 4 commits from feat/persisted-spawn into dev 2026-10-05 22:38:48 +00:00
Owner

What does this PR do?

I added a Spawn type to level.dat, a { dimension, block } pair held by WorldMetadata and read through WorldMetadata::spawn(). WorldMetadata::create now takes the spawn as an argument, so a record cannot exist without one. WORLD_FORMAT_VERSION moves from 5 to 6 and SaveError gains a Generator variant for a spawn generator that cannot be built.

On the server, load_or_create resolves the spawn when a world is first created. It binds the fresh block ID table, builds a VoxelGenerator from the config and seed, and scans the origin column through the new resolve_spawn_from_generator. The old resolve_spawn scan is now scan_for_spawn, which takes a block-lookup closure so the live ServerWorld path and the generator path share one implementation. Loading an existing world binds its table as before.

main reads the stored block back into an EntityPos with spawn_entity_pos, stores it in a WorldSpawn resource, and uses it for the player-less origin anchor and for every connecting player. The per-connection scan against ServerWorld is gone.

docs/save_format.md describes the spawn and the version 6 format.

Why is this change necessary?

The server rescanned the origin column of the live world on every connection and parked the streaming anchor at the origin, with a TODO for spawn selection and persistence. Retuning the worldgen config on a later launch could therefore move where new players appeared relative to an existing world. The spawn is now fixed in level.dat when the world is created and never recomputed.

Scope of Changes

server, shared, workspace

Testing

Automated, run on this branch:

  • cargo check -p server -p shared: passes
  • cargo test -p server -p shared: 63 passed in server, 286 passed in shared, 0 failed
  • cargo clippy --all-targets --all-features -- -D warnings: clean
  • cargo fmt --all -- --check: clean

New tests cover a created world recording its resolved spawn, a reopened world keeping that spawn instead of recomputing it, the generator-based scan landing on a standable surface across chunk boundaries, and a version 5 record being rejected.

Manual: I created a new world and confirmed the spawn was written to level.dat.

Additional Context

The level.dat format version moves from 5 to 6. A version 5 record is refused with UnsupportedVersion and there is no migrator, so existing dev worlds have to be recreated.

The spawn dimension is always syn:nyelenor, the only dimension that is generated today.

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 added a `Spawn` type to `level.dat`, a `{ dimension, block }` pair held by `WorldMetadata` and read through `WorldMetadata::spawn()`. `WorldMetadata::create` now takes the spawn as an argument, so a record cannot exist without one. `WORLD_FORMAT_VERSION` moves from 5 to 6 and `SaveError` gains a `Generator` variant for a spawn generator that cannot be built. On the server, `load_or_create` resolves the spawn when a world is first created. It binds the fresh block ID table, builds a `VoxelGenerator` from the config and seed, and scans the origin column through the new `resolve_spawn_from_generator`. The old `resolve_spawn` scan is now `scan_for_spawn`, which takes a block-lookup closure so the live `ServerWorld` path and the generator path share one implementation. Loading an existing world binds its table as before. `main` reads the stored block back into an `EntityPos` with `spawn_entity_pos`, stores it in a `WorldSpawn` resource, and uses it for the player-less origin anchor and for every connecting player. The per-connection scan against `ServerWorld` is gone. `docs/save_format.md` describes the spawn and the version 6 format. ### Why is this change necessary? The server rescanned the origin column of the live world on every connection and parked the streaming anchor at the origin, with a TODO for spawn selection and persistence. Retuning the worldgen config on a later launch could therefore move where new players appeared relative to an existing world. The spawn is now fixed in `level.dat` when the world is created and never recomputed. ### Scope of Changes server, shared, workspace ### Testing Automated, run on this branch: - `cargo check -p server -p shared`: passes - `cargo test -p server -p shared`: 63 passed in server, 286 passed in shared, 0 failed - `cargo clippy --all-targets --all-features -- -D warnings`: clean - `cargo fmt --all -- --check`: clean New tests cover a created world recording its resolved spawn, a reopened world keeping that spawn instead of recomputing it, the generator-based scan landing on a standable surface across chunk boundaries, and a version 5 record being rejected. Manual: I created a new world and confirmed the spawn was written to `level.dat`. ### Additional Context The `level.dat` format version moves from 5 to 6. A version 5 record is refused with `UnsupportedVersion` and there is no migrator, so existing dev worlds have to be recreated. The spawn dimension is always `syn:nyelenor`, the only dimension that is generated today. ### 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): document the persisted spawn
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 36m47s
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
74c4e146c2
Serkyo deleted branch feat/persisted-spawn 2026-10-05 22:38:48 +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!15
No description provided.