Saves#
A game can keep its saves in slots the Hub knows about. The Hub lists each
game's slots and lets the player delete them. It never opens, reads or edits what's
inside a save. Like achievements, saves live in the player's browser, in the
same shared localStorage (why this works).
Slots#
A slot is one save, with a stable slot id: a-z, 0-9 and -, at most 40
characters, for example main, slot-1 or autosave. A game may have any number
of slots.
Only progress belongs in a slot. Settings such as volume, quality or language are not saves: keep them out of slots, so that deleting a save never resets them.
With the SDK:
uniplay.saves.write('slot-1', state, { label: 'Mansion 3', summary: '$182,000 stolen · 4 crew', playtime: 4210 });
const state = uniplay.saves.read('slot-1'); // the data, or null
uniplay.saves.list(); // every slot, newest first
uniplay.saves.remove('slot-1');
Storage: localStorage#
| Key | Value |
|---|---|
uniplay:saves:<game id> |
The index: one entry per slot, read by the Hub. |
uniplay:saves:<game id>:<slot id> |
The save data: any JSON value, in a format only the game knows. |
The index:
{
"v": 1,
"slots": {
"slot-1": {
"label": "Mansion 3",
"summary": "$182,000 stolen · 4 crew",
"playtime": 4210,
"updatedAt": 1790000000000
}
}
}
| Field | Required | Meaning |
|---|---|---|
updatedAt |
yes | Last write, in milliseconds since epoch. The Hub lists the newest slot first. |
label |
no | Name to show, at most 40 characters. Defaults to the slot id. |
summary |
no | One line about where the player is, at most 80 characters. |
playtime |
no | Seconds played in this save. |
- To write a slot, write the data key first, then re-read the index, set the slot's entry and write the index. If the index can't be written (storage full), put the previous data back, so there's never data the Hub can't see.
- A slot exists only when it has both an index entry and a data key. Readers skip an entry whose data key is missing; data without an entry isn't listed.
- Readers must ignore unknown fields, malformed entries and invalid slot ids, and treat a missing or unparsable index as "no saves". The data is the game's business: if its format changes, version it inside the data.
- Storage can be unavailable or full. Games must keep working and simply not save, and may tell the player.
Each game writes only its own keys. The Hub only deletes (deleting), and restores slots from the player's cloud saves.
Deleting from the Hub#
To delete a slot, the Hub removes the data key, then re-reads the index and removes the slot's entry. It never touches achievements: unlocks stay when their save is gone. With cloud saves on, the slot is deleted from the cloud too, and the Hub may also write slots: it restores them in the same order a game writes them (data key first, then the index entry), and never while that game runs.
A game reads its slots when it starts, so a deleted slot simply isn't there next time. A game open in another tab during a delete may write that slot again; that's accepted for now.
Playthroughs#
Players can start a new game without losing the one they have. Start a new game in Game details puts every slot of the game aside in one bundle, and the game finds no saves the next time it starts. Switch brings a put-aside playthrough back: its slots are written again exactly as the game left them (data key first, then the index entry), after the current ones are put aside in turn. Achievements, time played and settings never move.
The Hub does this only while the game isn't running, so a game needs nothing new: it already reads its slots when it starts, and treats no saves as a new game. Put-aside playthroughs live under keys the Hub owns (storage keys) and sync to the cloud like slots. A game that has its own save menu can keep several slots as before; the Hub puts all of them aside together.
Moving existing saves#
Games that saved under their own keys before adopting slots move them on start: if
the old key exists and the slot is empty, copy the value into the slot (as JSON
data), write the index entry, then delete the old key. The SDK does this with
saves.adopt(oldKey, slot). Settings keys stay where they are.
