Stop the saved world from masking generator changes
chunkAt prefers the store over the generator, and nothing invalidated a stored chunk when the generator changed. Combined with four region files committed to the repo, the chunks around spawn were frozen output from an older generator: worldgen fixes looked like they did nothing in exactly the area you land in when you join, and a fresh clone inherited that world. Chunks now carry a RegionIOGeneratorVersion stamp, written on save and checked on load; a mismatch returns ErrChunkNotFound so the caller regenerates. Chunks written before the stamp existed have no tag, decode as 0, and are invalidated the same way. Bump the constant in any commit that changes generator output. This is deliberately per-chunk and deliberately quiet. The world metadata file already guards the seed, where a mismatch means two incompatible terrains and refusing to open is right. A generator change is routine by comparison and should just regenerate. world/region/*.mca and chat.md are untracked (left on disk) and /world/ is gitignored, superseding the narrower /world/regionio-world.json rule, along with /.refjava/ and root-level session transcripts. CLAUDE.md covers the parts of working here that README does not: the no-dependencies rule, the version-stamp rule and why forgetting it looks like a failed fix, where the vanilla ground truth lives and how to query the jar directly with unzip and javap, the precedent for dumping runtime constants with a throwaway Java program, and an honest list of which layers are bit-exact versus approximated.
This commit is contained in:
parent
0a2845fa76
commit
90e9380ae7
9 changed files with 224 additions and 606 deletions
|
|
@ -495,3 +495,56 @@ func TestConcurrentAutosavePreservesLatestEdit(t *testing.T) {
|
|||
t.Fatalf("persisted final block = %d, want %d", got, StateBedrock)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGeneratorVersionStampRejectsStaleChunks covers the guard that keeps a
|
||||
// saved world from masking generator changes.
|
||||
//
|
||||
// chunkAt prefers the store over the generator, so without this check a chunk
|
||||
// saved by an older build is served forever and every later worldgen fix looks
|
||||
// like it did nothing in the already-explored area around spawn. Chunks written
|
||||
// before the stamp existed carry no tag and must be rejected the same way.
|
||||
func TestGeneratorVersionStampRejectsStaleChunks(t *testing.T) {
|
||||
toRoot := func(t *testing.T, c *nbt.Compound) *nbt.Compound {
|
||||
t.Helper()
|
||||
_, tag, err := nbt.UnmarshalNamed(nbt.MarshalNamed("", c))
|
||||
if err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
root, ok := tag.(*nbt.Compound)
|
||||
if !ok {
|
||||
t.Fatal("root is not a compound")
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
t.Run("current version loads", func(t *testing.T) {
|
||||
root := toRoot(t, chunkToNBT(NewChunk(0, 0, BiomePlains)))
|
||||
if _, err := nbtToChunk(root, 0, 0, 0, 0); err != nil {
|
||||
t.Fatalf("nbtToChunk on a freshly written chunk: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("older version is rejected", func(t *testing.T) {
|
||||
c := chunkToNBT(NewChunk(0, 0, BiomePlains))
|
||||
c.Set(generatorVersionTag, nbt.Int(generatorVersion-1))
|
||||
if _, err := nbtToChunk(toRoot(t, c), 0, 0, 0, 0); err != ErrChunkNotFound {
|
||||
t.Errorf("err = %v, want ErrChunkNotFound so the chunk regenerates", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("missing stamp is rejected", func(t *testing.T) {
|
||||
// What every chunk written before this guard existed looks like.
|
||||
c := chunkToNBT(NewChunk(0, 0, BiomePlains))
|
||||
stripped := nbt.NewCompound()
|
||||
for _, name := range c.Keys() {
|
||||
if name == generatorVersionTag {
|
||||
continue
|
||||
}
|
||||
v, _ := c.Get(name)
|
||||
stripped.Set(name, v)
|
||||
}
|
||||
if _, err := nbtToChunk(toRoot(t, stripped), 0, 0, 0, 0); err != ErrChunkNotFound {
|
||||
t.Errorf("err = %v, want ErrChunkNotFound so the chunk regenerates", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue