Download at the next join when the controller did not store an upload

The controller gave an upload it did not store the same empty reply as
one it stored, and the instance confirmed either to the save. An instance
restarting after a crash uploads the players it had online, and when
another instance had since uploaded the same generation the save took its
own copy for the stored one: the next join kept it, and the leave after
that stored it over the newer data.

The reply now says whether the upload was stored. One that was not marks
the save's copy outdated, so it is not uploaded again and the next join
downloads.

Fixes #1218

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JyNnVzRW4jPWaoWTxCyhM7
This commit is contained in:
ILLISISandClaude Fable 5.2 committed 2026-11-14 13:20:31 +11:00
1 parent 635b10a3dc
commit b6bc118297
7 files changed
+84 -6

No files matched your search

+27 -1
View File
@@ -384,12 +384,14 @@ function player_sync.check_players_in_cutscene()
end
-- Whether a join should take the stored data: there is some, and this save hasn't synced the player in that epoch at
-- that generation or newer with every component synced here now. A player without a record here has nothing to keep.
-- that generation or newer with every component synced here now, or the controller did not store its last upload
-- (reject_upload). A player without a record here has nothing to keep.
local function download_needed(player_record, acquire_response, script_data)
return acquire_response.has_data
and (
not player_record
or not player_record.sync
or player_record.outdated
or (player_record.epoch or 0) ~= (acquire_response.epoch or 0)
or acquire_response.generation > player_record.generation
or serialize.newly_synced(player_record.components, script_data.components)
@@ -543,6 +545,8 @@ function player_sync.sync_player(acquire_response)
start_sync(player_record, script_data.components)
player_record.dirty = true
player_record.sync_in_progress = false
-- Nothing stored to download: the copy here is all there is
player_record.outdated = nil
-- Deleted data keeps its generation and epoch so the next upload is accepted as the new baseline
player_record.epoch = epoch
-- A copy from another epoch has nothing to do with the stored generation
@@ -573,6 +577,7 @@ function player_sync.finish_download(player, finished_record)
player_record.dirty = true
player_record.sync_in_progress = false
player_record.outdated = nil
start_sync(player_record, finished_record.components)
player_record.generation = finished_record.generation
player_record.epoch = finished_record.epoch
@@ -724,6 +729,27 @@ function player_sync.confirm_upload(player_name, generation)
player_record.dirty = false
end
-- Invoked by the instance code when the controller did not store the uploaded data: it holds another copy that is
-- newer, locked by another instance or from another epoch. This save's copy lost, even at the same generation, so it
-- is not uploaded again and the next join downloads. A retry of an upload the controller already stored ends up here
-- too, and then downloads what this save sent.
function player_sync.reject_upload(player_name, generation)
local player = game.get_player(player_name)
if not player or player.connected then
return
end
local script_data = get_script_data()
local player_record = script_data.players[player.name]
if not player_record or player_record.generation ~= generation then
return
end
log("Upload of " .. player_name .. " was not stored, the next join downloads")
script_data.active_uploads[player_name] = nil
player_record.dirty = false
player_record.outdated = true
end
function player_sync.initiate_inventory_download(player, player_record, generation, epoch)
player.print("Initiating inventory download...")
local record = {