export: byte-stable bundles

A bundle is full of HashMaps — rooms, scripts, sprites, every prop's msgs —
and Rust randomizes their iteration per process, so exporting an unchanged
game twice produced two different files. These are committed build outputs;
a diff should mean the game changed.

Serializing through serde_json::Value fixes it at every depth in one hop,
because Value is BTreeMap-backed by default. Three consecutive exports are
now byte-identical, and the golden replay is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-27 18:27:04 +10:00
parent caf4d5e558
commit 8f98380261

View File

@ -77,7 +77,17 @@ fn main() {
if let Some(path) = get("--export-bundle") {
let bundle = gs.world.to_bundle();
let j = serde_json::to_string(&bundle).expect("bundle serializes");
// Through Value first, so the output is byte-stable.
//
// A bundle is full of HashMaps — rooms, scripts, sprites, every
// prop's msgs — and Rust randomizes their iteration per process, so
// exporting the same unchanged game twice produced two different
// files. These are committed build outputs; a diff should mean the
// game changed. serde_json's Value is BTreeMap-backed by default, so
// this one hop sorts every key at every depth.
let j = serde_json::to_value(&bundle)
.and_then(|v| serde_json::to_string(&v))
.expect("bundle serializes");
std::fs::write(&path, &j).expect("write bundle");
println!("bundle exported: {} ({} rooms, {} scripts, {} sprites, {} bytes)",
path, bundle.rooms.len(), bundle.scripts.len(), bundle.sprites.len(), j.len());