feat: anchor first checkpoint for cold restart

This commit is contained in:
MaxTomahawk
2026-08-11 08:59:47 +02:00
parent 4e20f4585f
commit 4db97164bb
9 changed files with 305 additions and 19 deletions
+41
View File
@@ -388,6 +388,47 @@ end
local emitRestored
-- Persist the current verified checkpoint as the ordinary progress anchor only
-- when this playthrough has never had one. This is intentionally idempotent:
-- durable checkpoint tools can make a first session resumable without turning
-- every later checkpoint into a hidden normal SAVE. The live runtime must still
-- match the supplied checkpoint, and the ordinary save.write veto/lifecycle
-- remains authoritative through Game:writeSave().
function Checkpoint.ensureNormalSave(game, checkpoint, injectedFs)
local capability = Checkpoint.inspect(game)
if not capability.canCapture then
return false, capability.reason, capability.message
end
local validated, code, message = validate(game, checkpoint)
if not validated then return false, code, message end
local info, infoCode, infoMessage =
SaveData.selectedNormalSaveInfo(game.save, injectedFs)
if not info then return false, infoCode, infoMessage end
if info.exists then return true, "already_exists" end
local current, captureCode, captureMessage = Checkpoint.capture(game)
if not current then return false, captureCode, captureMessage end
if not equalData(current, validated) then
return false, "checkpoint_not_current",
"The active runtime changed after this checkpoint was captured."
end
if type(game.writeSave) ~= "function" then
return false, "save_unavailable",
"The active runtime cannot persist ordinary progress."
end
local ok, saved = pcall(game.writeSave, game)
if not ok or saved == false then
return false, "save_failed", "Could not create the first ordinary progress save."
end
local verified = SaveData.selectedNormalSaveInfo(game.save, injectedFs)
if type(verified) ~= "table" or not verified.exists then
return false, "save_verify_failed",
"The first ordinary progress save could not be verified."
end
return true
end
function Checkpoint.restore(game, checkpoint)
local capability = Checkpoint.inspect(game)
if not capability.canRestore then
+14 -4
View File
@@ -941,7 +941,17 @@ local function rememberPlaythroughId(save, opts, injectedFs)
if type(id) ~= "string" or id == "" then return opts, false end
local version = save.version or GameVersion.get()
local scope = playthroughScope(version, injectedFs)
opts = opts or SaveData.loadOptions(injectedFs)
local persisted = SaveData.loadOptions(injectedFs)
if opts then
-- Slot selection and opaque playthrough routing are engine-owned launcher
-- state. A live game may carry an options snapshot from before a legacy
-- save was promoted to slot1; writing that stale snapshot must not erase
-- the freshly persisted routing and strand tool storage on next boot.
opts.saveSlots = deepCopy(persisted.saveSlots)
opts.playthroughIds = deepCopy(persisted.playthroughIds)
else
opts = persisted
end
opts.playthroughIds = opts.playthroughIds or {}
opts.playthroughIds[version] = opts.playthroughIds[version] or {}
local changed = opts.playthroughIds[version][scope] ~= id
@@ -1021,18 +1031,18 @@ function SaveData.selectedNormalSaveInfo(save, injectedFs)
or readTable(fs, staged)
or readTable(fs, backup)
if type(normal) ~= "table" or normal.version ~= version then
return { savedAt = nil }
return { exists = false, savedAt = nil }
end
local normalId = normal.meta and normal.meta.playthroughId
if type(normalId) == "string" and normalId ~= "" and normalId ~= playthroughId then
return { savedAt = nil }
return { exists = false, savedAt = nil }
end
local savedAt = normal.meta and normal.meta.savedAt
if type(savedAt) ~= "number" or savedAt < 0 or savedAt ~= savedAt
or savedAt == math.huge or savedAt == -math.huge then
savedAt = nil
end
return { savedAt = savedAt }
return { exists = true, savedAt = savedAt }
end
-- ------- meta
+3
View File
@@ -683,6 +683,9 @@ function Loader:_api(mod)
resume = function(_, game, checkpoint)
return Checkpoint.resume(game, checkpoint)
end,
ensureNormalSave = function(_, game, checkpoint)
return Checkpoint.ensureNormalSave(game, checkpoint, loader.fs)
end,
},
options = {
define = function(_, schema)