fix(shared): correct SYNR framing order and widen worldgen version

This commit is contained in:
Serkyo 2026-07-09 14:59:18 +02:00
parent de6f7dd722
commit 8c8c70e990

View file

@ -44,19 +44,19 @@ pub struct RegionIndex {
/// Monotonic cache-coherence counter for derived LOD tiles; only ever increases.
region_tile_version: u32,
/// The worldgen version most chunks in the region are pinned to; the stamp table holds only exceptions.
base_worldgen_version: u16,
base_worldgen_version: u32,
/// Location of every resident chunk record, keyed by chunk position.
header_table: BTreeMap<ChunkPos, HeaderEntry>,
/// Reclaimable holes in the file, in no particular order.
free_list: Vec<FreeSpan>,
/// Worldgen-version exceptions: chunks pinned to a version other than `base_worldgen_version`.
stamps: BTreeMap<ChunkPos, u16>,
stamps: BTreeMap<ChunkPos, u32>,
}
impl RegionIndex {
/// Creates an empty index whose chunks default to `base_worldgen_version`.
#[must_use]
pub fn new(base_worldgen_version: u16) -> Self {
pub fn new(base_worldgen_version: u32) -> Self {
Self {
region_tile_version: 0,
base_worldgen_version,
@ -79,7 +79,7 @@ impl RegionIndex {
/// Returns the region's base worldgen version.
#[must_use]
pub fn base_worldgen_version(&self) -> u16 {
pub fn base_worldgen_version(&self) -> u32 {
self.base_worldgen_version
}
@ -106,7 +106,7 @@ impl RegionIndex {
/// Returns the pinned worldgen version for `pos`: its stamp exception, or the region base.
#[must_use]
pub fn worldgen_version(&self, pos: ChunkPos) -> u16 {
pub fn worldgen_version(&self, pos: ChunkPos) -> u32 {
self.stamps
.get(&pos)
.copied()
@ -114,7 +114,7 @@ impl RegionIndex {
}
/// Pins `pos` to `version`, recording it as a stamp exception only when it differs from the base.
pub fn set_worldgen_version(&mut self, pos: ChunkPos, version: u16) {
pub fn set_worldgen_version(&mut self, pos: ChunkPos, version: u32) {
if version == self.base_worldgen_version {
self.stamps.remove(&pos);
} else {
@ -138,8 +138,8 @@ impl RegionIndex {
let mut out = Vec::new();
out.extend_from_slice(&MAGIC);
out.extend_from_slice(&REGION_FORMAT_VERSION.to_le_bytes());
out.extend_from_slice(&self.region_tile_version.to_le_bytes());
out.extend_from_slice(&self.base_worldgen_version.to_le_bytes());
out.extend_from_slice(&self.region_tile_version.to_le_bytes());
out.extend_from_slice(&len_u32(self.header_table.len())?.to_le_bytes());
for (pos, entry) in &self.header_table {
@ -157,6 +157,8 @@ impl RegionIndex {
out.extend_from_slice(&span.length.to_le_bytes());
}
// The stamp value is widened to u32 to match ChunkData's u32 worldgen version and avoid
// truncation; the spec's u16 stamp field (Save format.md) is treated as an oversight.
out.extend_from_slice(&len_u32(self.stamps.len())?.to_le_bytes());
for (pos, version) in &self.stamps {
out.extend_from_slice(&pos.x.to_le_bytes());
@ -188,8 +190,8 @@ impl RegionIndex {
});
}
let base_worldgen_version = u32::from_le_bytes(reader.take_array()?);
let region_tile_version = u32::from_le_bytes(reader.take_array()?);
let base_worldgen_version = u16::from_le_bytes(reader.take_array()?);
let header_len = u32::from_le_bytes(reader.take_array()?);
let mut header_table = BTreeMap::new();
@ -216,7 +218,7 @@ impl RegionIndex {
let mut stamps = BTreeMap::new();
for _ in 0..stamp_len {
let pos = read_pos(&mut reader)?;
stamps.insert(pos, u16::from_le_bytes(reader.take_array()?));
stamps.insert(pos, u32::from_le_bytes(reader.take_array()?));
}
Ok(Self {
@ -282,6 +284,18 @@ mod tests {
Ok(())
}
#[test]
fn preserves_worldgen_versions_above_u16_max() -> Result<(), SaveError> {
// Regression guard: base and stamp worldgen versions are u32, so a value that would not
// fit a u16 must survive encode -> decode without truncation.
let mut index = RegionIndex::new(70_000);
index.set_worldgen_version(ChunkPos::new(0, 0, 0), 100_000);
let decoded = RegionIndex::decode(&index.encode()?)?;
assert_eq!(decoded.base_worldgen_version(), 70_000);
assert_eq!(decoded.worldgen_version(ChunkPos::new(0, 0, 0)), 100_000);
Ok(())
}
#[test]
fn stamp_equal_to_base_is_not_recorded() {
let mut index = RegionIndex::new(7);