Explicit cloud upload, import and conflict resolution for named local saves.
Import blitz.savecloud As C gives your program these, with C. in front. Each name leads to its part of the reference.
UploadSlot:Int(store:Saves.SaveStore, slot:Str, expectedRevision:Str)ResolveSlot:Int(store:Saves.SaveStore, slot:Str, token:Str)0.1.0 source preview. Upload validated blitz.saves records through native
GameKit saved games on iOS 17+ or Play Games Saved Games on Android. Requires
Extended runtime 1116+, Import blitz.saves, and a configured native mobile
export. Importing the module performs no I/O and shows no sign-in interface.
Local play and saves work independently of cloud availability. macOS, Windows
and web report the cloud service as Unsupported. The cloud provider is explicit:
set services.cloudSaves, apple.cloudProvider: "gamekit", Game Center and the
provisioned iCloud containers for iOS; set the linked android.gamesProjectId
and enable Saved Games in Play Console for Android. Configure these in Mobile
App Setup and export normally. Provider accounts, provisioning and device/store
qualification are separate from local build tests.
| Function | Contract |
|---|---|
Savecloud.UploadSlot(store:Saves.SaveStore, slot:Str, expectedRevision:Str):Int | Export the selected complete local generation, including a tombstone, and submit SaveCloudSave. Empty revision creates a previously absent cloud slot; an existing slot requires the last explicitly observed revision. Returns a request ID or zero. Copy Saves.LastError() for local export failure; native rejection uses GameServiceLastError(). |
Savecloud.ResolveSlot(store:Saves.SaveStore, slot:Str, token:Str):Int | Submit the current local record as the explicitly chosen resolution of a conflict returned by LoadCloudSave or SaveCloudSave. A token is bound to one authenticated account and slot. It expires on account change or successful resolution. |
Both functions borrow the store for the call and copy the encoded record. They
never change local files or retain a runtime JSON handle. Events use the ordinary
PollGameServiceEvent, GameServiceEventData, and FreeGameServiceEvent APIs.
The caller owns event JSON and frees it with FreeJson.
The local adapter lives in blitz.saves, so inspecting or importing a downloaded
record does not require native cloud commands:
| Function/result | Contract |
|---|---|
Saves.ExportCloudRecord(store, slot):Str | Return {deleted,save} transport JSON or empty plus LastError. Validates the selected physical generation and checksum, including deletions. Never changes local progress. |
Saves.ReadCloudRecord(store, slot, recordPath):CloudSave | Read at most 1 MiB, validate the full cloud and local envelopes, identity, checksums and tombstone, then apply registered schema migrations to an isolated candidate. Returns caller-owned candidate or Null and LastError. Never imports it. |
Saves.ImportCloudRecord(store, slot, candidate):Int | Explicitly accept a candidate after the game's own domain validation. Creates a new local generation or tombstone. It never copies a remote local-generation counter and never swaps live game state. |
Saves.ReleaseCloudRecord(candidate):Int | Explicitly remove the downloaded file while preserving the candidate's in-memory payload. Call after successful conflict resolution or after dismissing an ordinary download, never while its conflict still needs resolving. |
CloudSave | Deleted():Int, borrowed Payload():Int, AccountID():Str (origin identity), Revision():Str, Title():Str, Description():Str, PlaySeconds():Double. Delete explicitly to free owned memory. A tombstone's payload is zero. |
ReadCloudRecord accepts only the private recordPath returned in an event;
applications should not accept arbitrary player-provided file paths. Keep up to
128 downloaded records; explicitly release completed downloads. Unresolved
conflict files are retained across failures. Account changes invalidate native
conflict tokens while retaining files for explicit local recovery. A successful
resolution removes its prior candidate files unless another conflict references
them. Candidate payloads already read into memory remain owned by the caller.
InitCloudSaves from an explicit player action. It uses the shared native
authentication provider and may show Game Center or Play Games sign-in.
Background account checks never prompt. Display the returned accountId / adoption decision.
Call AdoptCloudSaveAccount(accountId) only after the player explicitly chooses
this account for their local progress. This choice persists privately. A new
or switched account cannot upload until adopted.recordPath and revision; reading it does not import or overwrite anything.
Missing slots return exists:false. Upload with that revision or empty for a
new slot. Native adapters recheck the active account before writing.token and candidates, each with a local
recordPath, revision, writer identity and deletion flag. Display all
candidates and their validated game summaries. Never pick the greatest local
generation, newest clock, or first array element automatically.ResolveSlot to publish the resulting local record.
A further conflict is another explicit decision, not a retry with a winner.An accepted write returns state:"accepted" and confirmedSynced:false.
Provider SDK completion can mean a local write queued for background upload;
it is not evidence that another device has received the record. GameKit does not
provide a compare-and-swap operation: preflight revision checks catch observed
staleness, and a later concurrent write may create provider conflicts. Load again
before editing another device's version. Play Games uses manual conflict policy.
Offline errors preserve local state and candidates; retry from an explicit load.
The complete UTF-8 cloud envelope, including the nested local integrity record, metadata, tombstones, account and revisions, must fit 1,048,576 bytes. Android also queries the provider limit and uses the lower limit. Status 14 (TooLarge) leaves the local save intact. A locally valid 1 MiB payload can exceed this cloud budget after JSON escaping and metadata; there is no truncation or partial upload. Public events remain below 64 KiB: large record bytes travel through the private native bridge and are exposed through private downloaded files.
Cloud revisions are random UUIDs, separate from local Long generations. A stable
per-installation private writer UUID identifies the author. Origin accountId
is metadata, not a cross-device authorization token; the authenticated provider
scopes reads and account checks guard uploads. No timestamp resolves a conflict.
Open examples/cloud_counter.bb. It saves an offline counter and supports native
connect/adopt, upload, download, candidate inspection, explicit selection and
conflict resolution. Mobile controls work with touch and mouse. Disabled provider
controls explain the required configured export rather than simulating success.
Maintained checks: modern/tests/cloud_test.cpp covers the coordinator's bounds,
conflicts, accounts, callbacks and persistence; saves/tests/cloud_tests.bb checks
local integrity, explicit import, Unicode/Long data, tombstones and ownership.
Run the counter example with --verify for its automated offline persistence
and request-ownership checks without opening a window or connecting an account.
The official package contracts also check exact public interfaces and borrowed
store ownership.
The native adapters compile against the pinned SDKs. Real two-device/offline,
account-switch, uninstall/reinstall and provider-console qualification still need
configured physical devices and accounts.
Provider contracts: Apple saved games, Play Games Saved Games, SnapshotsClient.