You are integrating the Gamma Games SDK (`@swiftware/gamma-sdk`) into an existing
HTML5 game. Follow this specification exactly. Do not invent methods or behavior
that is not stated here.

# What the SDK is

`@swiftware/gamma-sdk` is a small, typed, promise-first wrapper around a bridge
(`window.GammaSDK`) that the Gamma Games app injects into the game's WebView at
runtime. It exposes the player's coins, account-bound cloud save, public profile,
and a canonical coin value. There is no API key. The host supplies the user
identity — the game never sends it.

CRITICAL RULE: Coins only work INSIDE the Gamma Games app. Outside the app the SDK
is inert by default (coin calls return `{ok:false, error:'no_transport'}`,
`getCoins()` returns 0). The game MUST still run outside the app using its own
offline behavior. `gamma.storage` always works (it falls back to localStorage).

# Install

If the project uses a bundler:
    npm install @swiftware/gamma-sdk
    import { gamma } from '@swiftware/gamma-sdk';

If the project is plain HTML/JS with no bundler, add the drop-in script (exposes a
global `gamma`):
    <script src="https://cdn.jsdelivr.net/npm/@swiftware/gamma-sdk/dist/gamma-sdk.min.js"></script>

# The integration contract — do exactly this

1. On boot, call `await gamma.ready()` once before using any SDK feature.
2. Gate every coin feature on `gamma.isInApp`. When false, use the game's existing
   offline behavior (do not call spend/earn expecting success).
3. Route player-progress storage through `gamma.storage` (load on boot, save at
   milestones). Keep any existing localStorage logic as the offline fallback — the
   adapter already handles that.
4. Replace any in-game currency that represents real Gamma Coins with
   `gamma.spendCoins` / `gamma.earnCoins`. Only grant the reward INSIDE the result
   when `ok === true`. Leave purely game-internal resources alone.
5. Include an integer `schemaVersion` field in every saved object.
6. Do NOT enable the dev mock in production builds.

# API (all promise-first)

    gamma.configure(config)            // call BEFORE ready(); see config below
    await gamma.ready()                // resolves when settled; idempotent
    gamma.isInApp                       // boolean — true only inside the app

    await gamma.getCoins()             // number (0 outside the app)
    await gamma.spendCoins(amount, reason)
        // amount: int 1..10000; reason: /^[a-z0-9_]+$/, <=64 chars
        // -> { ok, newBalance?, spent?, balance?, error? }
    await gamma.earnCoins(amount, reason)
        // amount: int 1..100; server caps 2000 coins/user/hour
        // -> { ok, newBalance?, earned?, error? }

    await gamma.getCoinValue()
        // -> { usdPerCoin, currencyCode, displayName, symbol, iconUrl }
    await gamma.coinsToUsd(amount)     // number
    await gamma.formatCoins(amount)    // string, e.g. "50 GC"

    await gamma.getPlayer()            // { id, displayName, avatar, frame, isSubscribed } | null

    await gamma.storage.init()         // resolve host once; returns isInApp
    await gamma.storage.load()         // ProgressData | null
    await gamma.storage.save(state)    // boolean
    // low-level (prefer storage): gamma.loadProgress(), gamma.saveProgress(data)

GammaConfig:
    {
      readyTimeoutMs?: number,         // default 1500
      storageKey?: string,             // default 'gamma_save'
      mock?: boolean | { coins?, player?, coinValue? }  // OFF by default; dev only
    }

# Error codes (in `error`)

insufficient_coins | invalid_amount | invalid_reason | data_too_large |
serialization_error | rate_limited | user_not_found | network_error |
internal_error | timeout | unknown_type | no_transport

# Canonical example

    import { gamma } from '@swiftware/gamma-sdk';

    async function boot() {
      await gamma.ready();
      await gamma.storage.init();
      const state = (await gamma.storage.load()) ?? { schemaVersion: 1 };
      applyState(state);

      if (gamma.isInApp) {
        const player = await gamma.getPlayer();
        if (player) greet(player.displayName);
      }
      startGame();
    }

    async function buyExtraLife() {
      if (!gamma.isInApp) { return offerOfflineContinue(); }
      const res = await gamma.spendCoins(50, 'extra_life');
      if (res.ok) grantExtraLife();
      else showError(res.error);   // never show the raw code to the player
    }

    function onProgressChanged(state) {
      gamma.storage.save(state);   // debounce in real code; force-save on visibilitychange->hidden
    }

# Guardrails — do NOT

- Grant a reward before `spendCoins` resolves with `ok === true`.
- Read coins without checking `gamma.isInApp` (outside the app they're inert).
- Cache the coin balance for longer than a single user action — call getCoins again.
- Send user_id / game_id / tokens from the game — the host supplies identity.
- Remove the offline/localStorage fallback — it is load-bearing.
- Invent methods that are not in this spec (no getFriends, showAd, leaderboards…).
- Ship with `mock` enabled.

# Your task

Locate where the game (a) boots, (b) reads/writes progress in localStorage, and
(c) mutates any currency that maps to real Gamma Coins. Apply the contract above to
those sites only, keeping the offline path intact. Then summarize the changes you
made and list any spots where you were unsure whether an in-game resource maps to
real Gamma Coins.
