diff --git a/client/scripts/autoloads/session_manager.gd b/client/scripts/autoloads/session_manager.gd index cba63a5d9..d480ae0ce 100644 --- a/client/scripts/autoloads/session_manager.gd +++ b/client/scripts/autoloads/session_manager.gd @@ -33,9 +33,11 @@ func new_game() -> String: GameState.current_game_id = game_id # #175: Generate world_seed for deterministic simulation (D-010, D-029). - # Combines two randi() calls (u32 each) into full u64 entropy range. - # Without this, upper 32 bits are always zero — halving the seed space. - GameState.world_seed = (rng.randi() << 32) | rng.randi() + # Combines two randi() calls (u32 each) into 63-bit entropy range. + # Mask bit 31 of the upper word before shifting to prevent signed overflow: + # GDScript int is i64 — if bit 63 is set, MessagePack encodes as negative, + # and Rust rmp_serde rejects negative values when deserializing as u64. + GameState.world_seed = ((rng.randi() & 0x7FFFFFFF) << 32) | rng.randi() # Persist world_seed to save directory so resume_game() can restore it. # Without this, loaded sessions would send seed=0, breaking D-010 determinism. @@ -134,12 +136,14 @@ func _write_seed_file(save_path: String, seed: int) -> void: ## Read world_seed from save directory. Returns 0 if file missing (legacy saves). +## Masks the sign bit on read: save files written before the signed-overflow fix +## may contain negative i64 values that Rust rmp_serde rejects as u64. func _read_seed_file(save_path: String) -> int: var file := FileAccess.open(save_path + "world_seed", FileAccess.READ) if file == null: push_warning("SessionManager: no seed file in %s — using seed=0 (legacy save)" % save_path) return 0 - return file.get_64() + return file.get_64() & 0x7FFFFFFFFFFFFFFF func _find_newest_save(dir_path: String) -> String: