feat/worldgen-versioning #3
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
scope/assets
scope/client
scope/networking
scope/renderer
scope/scripting
scope/server
scope/shared
scope/workspace
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Synvael/synvael!3
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/worldgen-versioning"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What does this PR do?
Replaces the single
worldgen_versionfield inlevel.datwith an append-only worldgen version history, and makes the server honour a chunk's stamped version on both load and write-back.shared::save::level:WorldMetadatanow carries aWorldgenHistory { current, versions }, where eachWorldgenVersionEntryrecords akerneldiscriminant (PERLIN_KERNEL, the only structural generator today) and theWorldGenConfigthat version generated at. A chunk's stored version is an index intoversions, so the parameters needed to reproduce any pinned chunk's baseline travel with the world rather than with the shipped asset directory. Accessors areworldgen_current(),worldgen_entry(version)andworldgen_versions().WORLD_FORMAT_VERSIONgoes to2.decode()still reads a version1record and migrates it throughWorldMetadataV1::migrate, backfillingworldgen_version + 1entries from the caller-supplied pre-history config. It takes alegacy_configargument for that reason.crates/server/src/main.rsbuilds aBTreeMap<u32, VoxelGenerator>registry from the loaded history, one generator per version, all seeded from the world seed.ServerWorldholds the registry behind anArcalongside the current generator.ResidentChunk { chunk, worldgen_version }.load_chunkregenerates a saved diff's baseline with the generator fordata.worldgen_version(), andreconcilestamps the eviction diff with the version the chunk actually holds instead of a hard-coded0. A stamp that names no registry entry falls back to the current generator with awarn!, since on-disk history is not proof against a hand-edited save.RegionFile::openandSaveActor::spawntakecurrent_version, so a freshly created region pins itsbase_worldgen_versionto the world's current version rather than0.assets/data/worldgen/default.jsonmoves toassets/data/worldgen/default/0.json. That file is now version 0's frozen input, read only whenlevel.datdoes not exist and a first history entry has to be minted.Why is this change necessary?
ADR-0009 stores modified chunks as a sparse diff against the deterministic worldgen baseline, and
ChunkDataalready carried aworldgen_versionto say which baseline a diff was pinned to. Nothing honoured it.load_chunkregenerated every baseline at the current generator andreconcilewrote every diff back stamped0, withTODOs in both places saying so.docs/save_format.mdrecords the same gap.That was harmless only while one version could ever exist. The moment terrain tuning changes, an old chunk's edits would be layered over terrain that never produced them, and its write-back would relabel the diff as belonging to the new baseline, so the original mapping is lost on disk with no way to recover it.
The history has to live in
level.datrather than inassets/, because a version's config must remain readable for the lifetime of any chunk pinned to it. Keeping the configs in the asset directory would mean a shipped-asset edit silently reinterprets every existing save, and it would break worlds carried between builds.Scope of Changes
server, shared, assets
Testing
cargo test -p shared -p server: 207 passing inshared, 47 inserver, no failures.cargo clippy --all-targets --all-features -- -D warningsandcargo fmt --all -- --checkare both clean.New coverage:
shared::tests::level::migrates_a_legacy_record_into_a_historybuilds a version1record on disk via a test-localencode_v1that mirrors the old framing, decodes it, and asserts the resulting history has one entry per legacy version withcurrentpreserved.levelround-trip tests were extended to cover the history payload, withsample_config()giving everyWorldGenConfigfield a distinct value so a field swapped during (de)serialization shows up.server::tests::main::shipped_worldgen_asset_matches_its_golden_digestpins an FNV-1a digest over a fixed chunk sample generated fromassets/data/worldgen/default/0.json. It catches an edit to the shipped tuning, which is a different failure from the generator-code digest already inshared::generator::tests. Re-pinning it is correct when version 0 is retuned before release, and is a defect signal otherwise.region_fileandworld_servertests were updated for the newcurrent_versionparameters, including a region opened at a non-zero current version.Not covered by automated tests: there is no second worldgen version to reach yet, so the cross-version load and write-back path is exercised only through hand-constructed stamps. Full verification waits on the first real version bump.
Additional Context
Backwards compatibility. A version 1
level.datloads and is migrated in place. The migration assumes every pre-history version was generated by the one shipped config, which holds because per-version configs did not exist before this branch. No released build writes version 1, so the legacy path exists for local worlds only and can be dropped later.Known limitation, tracked in code.
ChunkCacheis keyed by position alone, not by(position, version). Correct while a single version generates any given position's baseline. Once a second version is reachable, a baseline cached under one version's generation would be handed back for another. There is aNOTEat the eviction site inworld_server.rs; the fix belongs with the version-bump work that first makes a second version reachable.Registry keying. The registry is keyed by version alone. Dimension keying would need a second dimension, which does not exist in the codebase yet.
BTreeMapoverHashMapfor the determinism rule inAGENTS.md.Type changes worth a reviewer's eye.
WorldMetadatalosesCopyandEqbecause it now owns aVec.WorldGenConfiggainsPartialEqandVoxelGeneratorgainsClone, the latter somain.rscan handServerWorldthe current generator without a second construction.Scripting layer. No gameplay logic is involved, so nothing here needs a Lua binding. This is save-format and server-internal plumbing.
Related Issues
No response
Checklist
dev(or a feature branch offdev) and my PR targetsdev.///doc comments for Rust) where necessary.