# Progress & saves

The SDK gives you account-bound cloud save with an automatic offline fallback. Use
the built-in `gamma.storage` adapter — it handles the boilerplate (ready-waiting,
fallback, and one-shot migration of an old `localStorage` save into the account).

## The adapter (recommended)

```js
await gamma.storage.init();                 // resolve host once
const state = (await gamma.storage.load())  // account-bound in-app, localStorage outside
  ?? {schemaVersion: 1};

// ... play ...

await gamma.storage.save(state);            // returns true on success
```

- **In-app:** reads/writes the player's account, synced across devices.
- **Outside the app:** transparently uses `localStorage`, so offline play works.
- **First in-app launch:** if there's a legacy `localStorage` save and no account
  save yet, it migrates the local save into the account once, then clears it.

Set the localStorage key (and other options) before the first call:

```js
gamma.configure({storageKey: 'mygame_save'});
```

## Always include a `schemaVersion`

Every persisted payload should carry an integer `schemaVersion` so future game
versions can migrate old saves forward without corrupting them.

```js
const CURRENT_SCHEMA = 3;

function migrate(state) {
  if (state.schemaVersion < 2) {
    state.coinsEarned = 0;            // field added in v2
    state.schemaVersion = 2;
  }
  if (state.schemaVersion < 3) {
    state.levels = state.stages ?? {}; // renamed in v3
    delete state.stages;
    state.schemaVersion = 3;
  }
  return state;
}

const raw = await gamma.storage.load();
const state = migrate(raw ?? {schemaVersion: CURRENT_SCHEMA});
```

## What to save (and what not to)

Save **derived, durable** state:

- ✅ Level number, per-level stars, unlock flags, cosmetic choices, counters
- ✅ Quest/mission progress, tutorial completion
- ✅ Settings the player explicitly changes (music on/off, control scheme)

Do **not** save **reconstructible, transient** state:

- ❌ Sprite positions, velocities, particle buffers, audio cursors
- ❌ Camera offsets, UI animation state
- ❌ Any cached coin balance — call [`getCoins`](./coins.md) when you need it

## Save cadence & limits

- **Debounce** routine saves (≈1000 ms trailing); save at meaningful milestones.
- **Force-save** on `visibilitychange → hidden` / `pagehide` — mobile can kill the
  page at any time.

```js
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') gamma.storage.save(state);
});
```

- Max encoded save size: **100 KB**. If you're close, you're probably persisting
  reconstructible state (see above) — compact into ids/bitfields.

## Low-level API

`gamma.storage` is built on `gamma.loadProgress()` / `gamma.saveProgress(data)`,
which talk to the account directly (no localStorage fallback). Prefer the adapter
unless you need full control. See the [API reference](../api-reference.md).
