Saving player progress

Project autosave protects your source and assets. Game saves protect the player's progress: levels unlocked, inventory, settings, high scores.

All three machines provide 256 bytes, accessible as 64 unsigned 32-bit slots:

lua
-- saveid: yourstudio.yourgame.v1
cartdata()

function _init()
  unlocked = max(1, dget(0))
  best = dget(1)
end

function finish_level(level, score)
  unlocked = max(unlocked, level+1)
  best = max(best, score)
  dset(0, unlocked)
  dset(1, best)
end

Call cartdata() before accessing slots. Slot indices are 0–63. Values are integers 0–4294967295; pack flags into bits or smaller values into bytes when needed. This is a defined unsigned-integer API, not PICO-8 fixed-point-number compatibility. peek/poke also access the first 256 bytes of the user region. Do not write into the following score/seed words as part of a save.

The host loads progress before the first line of cart code. Write changed progress at checkpoints; it is saved automatically from successful frames. Do not unconditionally zero your slots in _init, because that erases the restored data. Keep game-specific state in Lua variables while playing and write only what you need to reconstruct progress next time. Lua tables, functions, coroutines, screen/audio state and the instruction pointer are not serialized for you.

Use a unique saveid comment (1–64 letters/digits/underscore/dot/hyphen) so updates retain progress. The ID is scoped to the machine tier and website. Without it, each exact cart payload has its own save. Keep the ID unchanged for compatible updates; use a new version suffix when the data layout changes. Remixes should choose a new ID unless sharing progress is intentional.

In the player, open game progress to export a JSON backup, import one, erase saved progress, continue the saved game, or start a ranked run. Import validates identity, format, version and all 256 bytes before replacing data. An import restarts the game, so its usual _init runs on the imported values. Standalone HTML includes equivalent backup/import/erase controls.

Browser storage can be unavailable, full or cleared by the user. Save failures are visible; export a backup if progress matters. A detected competing tab stops automatic writes so it does not silently overwrite newer data. Close extra tabs or export both versions and choose which to import. Saves are local; there is no account or cloud-save service. Browsers may treat local file: exports differently; host the HTML on a stable HTTPS origin for predictable storage and use backups to move between origins/devices.

Competition starts clean. Ranked runs, daily challenges, jams and replay playback ignore personal saves and leave them untouched. A run restored from nonzero save data cannot be submitted through the replay UI. Use start ranked run to compete, then continue saved game to return to progress. Headless CLI/MCP executions and editor previews also start with zero save data; testing a cart in the editor cannot overwrite the player's save.

This follows the small, game-authored save model used by PICO-8 and TIC-80, with Omibit's existing 256-byte map and explicit clean-start replay policy.

Save schema upgrades

Reserve a slot for your game's save schema. Keep the same saveid for compatible updates, inspect the schema in _init, and migrate older data before writing the new schema marker. For example, reserve slot 63; a value of zero means an older or fresh save. Initialize only newly introduced slots and set slot 63 to 1 after the migration completes. Never reset existing level/inventory slots unconditionally. Test an exported old save against the new cart before publishing it. Use a new ID for intentionally incompatible formats, and explain how players can keep an old export. The JSON wrapper version is separate from your game's schema.

Explicit IDs share storage within a website and machine tier. They are not ownership credentials: another cart can choose the same ID. Use a distinctive studio/game ID, choose a new ID for remixes, and retain backups. On browsers with Web Locks, save writes and explicit import/reset operations are serialized across tabs before checking for conflicts. Older contexts without Web Locks retain compare-before-write detection, which is not an atomic cross-tab transaction. Avoid simultaneous play of one saved game and keep backups. Progress is coalesced over roughly 250 ms, so abrupt termination can lose the most recent changes even when earlier checkpoints were saved.