@indiegems/gem-web-sdk 10.3.0 → 10.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  # The browser surface — API reference
2
2
 
3
- Generated from `@indiegems/gem-web-sdk/sdk` v10.3.0, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
3
+ Generated from `@indiegems/gem-web-sdk/sdk` v10.4.0, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
4
4
 
5
5
  `@indiegems/gem-web-sdk/sdk` is the one import a game writes, wherever its code runs. `@indiegems/gem-web-sdk/game` exports exactly the symbols on this page and nothing else, for a build that wants only this half.
6
6
 
@@ -5193,7 +5193,8 @@ Whether the panel is on screen right now.
5193
5193
  interface RewardCue
5194
5194
  ```
5195
5195
 
5196
- A platform-currency reward on its way to the arcade's wallet — what
5196
+ A platform-currency reward on its way to the arcade — in the Gem Arcade, a
5197
+ flight into the Gem button at the top-left of your frame — what
5197
5198
  `gem.cueReward` takes.
5198
5199
 
5199
5200
  <a id="rewardcue-currencyslug"></a>
@@ -5205,8 +5206,8 @@ currencySlug: string
5205
5206
  ```
5206
5207
 
5207
5208
  The platform currency's slug, as `gem.platform.currencies.get()` lists it —
5208
- `GEMS` or `ARCADE_TICKETS`. It decides which total in the arcade's wallet
5209
- the payout flies into.
5209
+ `GEMS` or `ARCADE_TICKETS`. It decides what flies — gems or tickets — and
5210
+ which of the player's totals the payout moves.
5210
5211
 
5211
5212
  <a id="rewardcue-expected"></a>
5212
5213
 
@@ -5231,8 +5232,8 @@ origin?: { x: number; y: number }
5231
5232
  Where the flight starts, in CSS px of YOUR viewport: the centre of the
5232
5233
  currency's icon (or hero) your screen draws beside the reward — the gem
5233
5234
  for Gems, the ticket for Tickets. Measure that element's own box, not a
5234
- row or panel around it. Leave it out and the arcade animates at its wallet
5235
- alone.
5235
+ row or panel around it. Leave it out and the Gem Arcade flies the payout
5236
+ from the middle of your frame once the platform confirms it.
5236
5237
 
5237
5238
  <a id="rewardcue-origin-x"></a>
5238
5239
 
@@ -5500,9 +5501,9 @@ while connected, and only to an arcade that mirrors a game's mute.
5500
5501
  cueReward(cue: RewardCue): boolean
5501
5502
  ```
5502
5503
 
5503
- Tell the arcade a platform-currency reward is on its way, so its wallet can
5504
- show it landing — a pending state now and, once the platform confirms the
5505
- grant, the currency flying from `origin` into the wallet.
5504
+ Tell the arcade a platform-currency reward is on its way, so it can show it
5505
+ landing — in the Gem Arcade, the currency flying from `origin` into the Gem
5506
+ button, the total moving once the platform confirms the grant.
5506
5507
 
5507
5508
  Prefer `gem.cueReward`, which says what the arcade does with it. This one
5508
5509
  sends only while connected, and only to an arcade that draws a wallet.
@@ -7703,9 +7704,10 @@ readonly recommended: GemRecommendedSettings | null
7703
7704
  ```
7704
7705
 
7705
7706
  The player's Arcade-wide defaults for this device — a volume, battery
7706
- saver, a keyboard layout, the gamepad's confirm button, vibration, and the
7707
+ saver, a keyboard layout, the gamepad's confirm button, vibration, the
7707
7708
  camera and controls settings (look sensitivity, gamepad look sensitivity,
7708
- invert look, touch movement) — or `null` when the Arcade did not send them.
7709
+ invert look, touch movement) and a starting graphics quality — or `null`
7710
+ when the Arcade did not send them.
7709
7711
 
7710
7712
  **Recommendations only; the player's own choice in your game wins.** Start
7711
7713
  a setting from here while the player has made no choice of their own for
@@ -7837,6 +7839,36 @@ On a touch screen, the movement thumbstick the player wants: `'floating'`
7837
7839
  appears where the thumb lands, `'fixed'` stays in one place. `'floating'`
7838
7840
  when the Arcade did not say. Your game draws the stick.
7839
7841
 
7842
+ <a id="gemrecommendedsettings-graphicsquality"></a>
7843
+
7844
+ ## GemRecommendedSettings.graphicsQuality
7845
+
7846
+ ```ts
7847
+ readonly graphicsQuality: GemGraphicsQuality
7848
+ ```
7849
+
7850
+ The graphics quality to START at on this device: `'low'`, `'medium'` or
7851
+ `'high'`. The Arcade picks it per device — the tier it detected, or the
7852
+ player's own Graphics choice — so a game opens at a quality that suits the
7853
+ device instead of guessing. A starting point, not a cap: while
7854
+ `graphicsQualityAuto` is true your game may adapt from here as it runs,
7855
+ and `createQualityGovernor` from `@indiegems/gem-web-sdk/quality` does
7856
+ that for you. When the Arcade did not say, `'low'` with `batterySaver`
7857
+ on and `'medium'` otherwise.
7858
+
7859
+ <a id="gemrecommendedsettings-graphicsqualityauto"></a>
7860
+
7861
+ ## GemRecommendedSettings.graphicsQualityAuto
7862
+
7863
+ ```ts
7864
+ readonly graphicsQualityAuto: boolean
7865
+ ```
7866
+
7867
+ Whether the Arcade chose `graphicsQuality` for this device (`true`, the
7868
+ player left Graphics on Auto) or the player picked it by hand (`false`).
7869
+ While it is `false`, hold that quality: never step it down or up on your
7870
+ own. `true` when the Arcade did not say.
7871
+
7840
7872
  <a id="gemcohort"></a>
7841
7873
 
7842
7874
  ## GemCohort
@@ -7998,10 +8030,11 @@ The unsafe margin on each edge of your frame.
7998
8030
  readonly reserved: readonly GemReservedArea[]
7999
8031
  ```
8000
8032
 
8001
- Rectangles of your frame the arcade's own controls cover — its menu button
8002
- in the top-right corner, and the wallets in the middle of the top row while they are shown (see
8003
- `setWalletVisible`). Keep essential UI and touch targets out of them. Empty
8004
- when the arcade covers nothing.
8033
+ Rectangles of your frame the arcade's own controls cover — in the Gem
8034
+ Arcade, its Gem button in the top-left corner, and the frame-rate readout
8035
+ beside it while the player shows it; an embedder that draws wallets over
8036
+ your game adds theirs (see `setWalletVisible`). Keep essential UI and touch
8037
+ targets out of them. Empty when the arcade covers nothing.
8005
8038
 
8006
8039
  A stylesheet reads the per-corner custom properties the SDK stamps from
8007
8040
  these instead — `--gem-reserved-tl-height` and friends.
@@ -8080,8 +8113,8 @@ While shown, show ONLY these: platform currency slugs as
8080
8113
  `gem.platform.currencies.get()` lists them (`GEMS`, `ARCADE_TICKETS`),
8081
8114
  matched exactly, and the literal `'score'` for the achievement score. Only
8082
8115
  for a screen about one currency, such as a ticket store; leave it out and
8083
- every wallet shows, which is always safe. The Gem Arcade ignores it and
8084
- shows every wallet while shown.
8116
+ every wallet shows, which is always safe. The Gem Arcade draws no wallet
8117
+ over a game, so it has nothing to narrow and ignores it.
8085
8118
 
8086
8119
  <a id="gamemenuitem"></a>
8087
8120
 
@@ -8929,6 +8962,20 @@ honouring them is part of the platform's release checklist.
8929
8962
  Read it at the moment you need it rather than caching it: the locale can
8930
8963
  change mid-session.
8931
8964
 
8965
+ <a id="gem-paused"></a>
8966
+
8967
+ ## Gem.paused
8968
+
8969
+ ```ts
8970
+ readonly paused: boolean
8971
+ ```
8972
+
8973
+ Whether the arcade has your game paused right now: `true` from an
8974
+ `onPause(true)` until the `onPause(false)` that follows it, `false` before
8975
+ any. The same pause `onPause` tells you about, readable at any moment —
8976
+ for code that was not around when the edge arrived, such as a frame-time
8977
+ monitor deciding whether a slow frame counts.
8978
+
8932
8979
  <a id="gem-cohorts"></a>
8933
8980
 
8934
8981
  ## Gem.cohorts
@@ -9084,10 +9131,13 @@ is ignored by the arcade, which keeps the image it is showing.
9084
9131
  cueReward(cue: RewardCue): boolean
9085
9132
  ```
9086
9133
 
9087
- Tell the arcade a platform-currency reward is on its way, so its **wallet
9088
- shows it landing**: a pending state right away and, once the platform
9089
- confirms the grant, the currency flying from `origin` into the wallet with
9090
- a bounce as the total moves.
9134
+ Tell the arcade a platform-currency reward is on its way, so it **shows it
9135
+ landing**: in the Gem Arcade the currency flies from `origin` into the Gem
9136
+ button at the top-left of your frame, which pops as each mark lands, and
9137
+ the player's total moves once the platform confirms the grant. The Gem
9138
+ Arcade draws no wallet over your game: the player sees their Gems and
9139
+ Tickets in the Gem menu. A payout you do not cue still flies, from the
9140
+ middle of your frame, so cue the ones the player earns on screen.
9091
9141
 
9092
9142
  Call it the moment your game decides to award — when your server has moved
9093
9143
  (or is about to move) the progression mapped onto a platform currency —
@@ -9135,28 +9185,23 @@ setWalletVisible(visible: boolean, options?: WalletVisibleOptions): boolean
9135
9185
  Show or hide the player's wallets — Gems, Tickets, the achievement score —
9136
9186
  that an embedder draws over your game.
9137
9187
 
9138
- **Shown unless you hide them.** Call nothing and the Gem Arcade shows
9139
- every wallet the whole time, during play too. A game that wants the top
9140
- row for gameplay hides them while it plays and shows them again on its
9141
- menus (the Gem Arcade honours this again since 2026-10-08, on a phone,
9142
- where the wallets sit over your game; where they sit beside the frame it
9143
- changes nothing).
9188
+ **The Gem Arcade draws no wallet over a game** (since 2026-10-11): the
9189
+ player sees their balances in the Gem menu, and payouts fly into the Gem
9190
+ button. So under the Gem Arcade this changes nothing on screen — it is
9191
+ accepted and harmless, and a game that already calls it keeps working.
9192
+ Call it for an embedder that does draw wallets over your game: hide them
9193
+ while you play, show them on your menus.
9144
9194
 
9145
9195
  ```js
9146
9196
  level.onStart = () => gem.setWalletVisible(false);
9147
9197
  menu.onOpen = () => gem.setWalletVisible(true);
9148
9198
  ```
9149
9199
 
9150
- The Gem Arcade does NOT act on `only`: a shown wallet there is every
9151
- wallet. Another embedder may honour it.
9152
-
9153
9200
  **It is screen state, not a toggle.** Each call is the whole answer and
9154
- replaces the last. While hidden, the arcade still pops a wallet up for a
9155
- moment when something happens to it — a reward you cued, a spend, a
9156
- landing — and your state never cancels that. Shown wallets cover the
9157
- middle of your frame's top row, each arriving as a rectangle in
9158
- `gem.viewport.reserved`; hiding them gives that space back. A wallet the
9159
- arcade pops up for a moment floats over your game and moves nothing.
9201
+ replaces the last. Where an embedder draws wallets, shown ones arrive as
9202
+ rectangles in `gem.viewport.reserved` and hiding them gives that space
9203
+ back; the Gem Arcade sends none for wallets. `only` narrows what shows,
9204
+ where an embedder honours it; the Gem Arcade does not.
9160
9205
 
9161
9206
  **Never draw a platform balance yourself**, and never show a pending state
9162
9207
  for one: the arcade takes every figure from the platform. If this returns
@@ -11692,6 +11737,17 @@ readonly code: "unknown_currency" | "not_purchasable" | "no_price_in_currency" |
11692
11737
  Which check refused: `unknown_currency`, `not_purchasable`,
11693
11738
  `no_price_in_currency` or `quantity_invalid`.
11694
11739
 
11740
+ <a id="gemgraphicsquality"></a>
11741
+
11742
+ ## GemGraphicsQuality
11743
+
11744
+ ```ts
11745
+ type GemGraphicsQuality = "low" | "medium" | "high"
11746
+ ```
11747
+
11748
+ A graphics quality tier: `'low'`, `'medium'` or `'high'`. What
11749
+ `gem.prefs.recommended.graphicsQuality` holds.
11750
+
11695
11751
  <a id="servertokengrant"></a>
11696
11752
 
11697
11753
  ## ServerTokenGrant
@@ -1,6 +1,6 @@
1
1
  # The server surface — API reference
2
2
 
3
- Generated from `@indiegems/gem-web-sdk/sdk` v10.3.0, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
3
+ Generated from `@indiegems/gem-web-sdk/sdk` v10.4.0, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
4
4
 
5
5
  `@indiegems/gem-web-sdk/sdk` is the one import a game writes, wherever its code runs. `@indiegems/gem-web-sdk/server` exports exactly the symbols on this page and nothing else, for a build that wants only this half.
6
6
 
@@ -2,7 +2,7 @@
2
2
  "schema_version": 2,
3
3
  "source": {
4
4
  "repo": "IndieGems/gem-web-sdk",
5
- "version": "10.3.0"
5
+ "version": "10.4.0"
6
6
  },
7
7
  "entries": [
8
8
  {
@@ -6152,7 +6152,7 @@
6152
6152
  "group": "platform",
6153
6153
  "signature": "interface RewardCue",
6154
6154
  "documented": true,
6155
- "summary": "A platform-currency reward on its way to the arcade's wallet — what\n`gem.cueReward` takes.",
6155
+ "summary": "A platform-currency reward on its way to the arcade — in the Gem Arcade, a\nflight into the Gem button at the top-left of your frame — what\n`gem.cueReward` takes.",
6156
6156
  "deprecated": null,
6157
6157
  "params": [],
6158
6158
  "returns": null,
@@ -6168,7 +6168,7 @@
6168
6168
  "group": "platform",
6169
6169
  "signature": "currencySlug: string",
6170
6170
  "documented": true,
6171
- "summary": "The platform currency's slug, as `gem.platform.currencies.get()` lists it —\n`GEMS` or `ARCADE_TICKETS`. It decides which total in the arcade's wallet\nthe payout flies into.",
6171
+ "summary": "The platform currency's slug, as `gem.platform.currencies.get()` lists it —\n`GEMS` or `ARCADE_TICKETS`. It decides what flies — gems or tickets — and\nwhich of the player's totals the payout moves.",
6172
6172
  "deprecated": null,
6173
6173
  "params": [],
6174
6174
  "returns": null,
@@ -6200,7 +6200,7 @@
6200
6200
  "group": "platform",
6201
6201
  "signature": "origin?: { x: number; y: number }",
6202
6202
  "documented": true,
6203
- "summary": "Where the flight starts, in CSS px of YOUR viewport: the centre of the\ncurrency's icon (or hero) your screen draws beside the reward — the gem\nfor Gems, the ticket for Tickets. Measure that element's own box, not a\nrow or panel around it. Leave it out and the arcade animates at its wallet\nalone.",
6203
+ "summary": "Where the flight starts, in CSS px of YOUR viewport: the centre of the\ncurrency's icon (or hero) your screen draws beside the reward — the gem\nfor Gems, the ticket for Tickets. Measure that element's own box, not a\nrow or panel around it. Leave it out and the Gem Arcade flies the payout\nfrom the middle of your frame once the platform confirms it.",
6204
6204
  "deprecated": null,
6205
6205
  "params": [],
6206
6206
  "returns": null,
@@ -6484,7 +6484,7 @@
6484
6484
  "group": "frame",
6485
6485
  "signature": "cueReward(cue: RewardCue): boolean",
6486
6486
  "documented": true,
6487
- "summary": "Tell the arcade a platform-currency reward is on its way, so its wallet can\nshow it landing — a pending state now and, once the platform confirms the\ngrant, the currency flying from `origin` into the wallet.\n\nPrefer `gem.cueReward`, which says what the arcade does with it. This one\nsends only while connected, and only to an arcade that draws a wallet.",
6487
+ "summary": "Tell the arcade a platform-currency reward is on its way, so it can show it\nlanding — in the Gem Arcade, the currency flying from `origin` into the Gem\nbutton, the total moving once the platform confirms the grant.\n\nPrefer `gem.cueReward`, which says what the arcade does with it. This one\nsends only while connected, and only to an arcade that draws a wallet.",
6488
6488
  "deprecated": null,
6489
6489
  "params": [
6490
6490
  {
@@ -8702,7 +8702,7 @@
8702
8702
  "group": "player",
8703
8703
  "signature": "readonly recommended: GemRecommendedSettings | null",
8704
8704
  "documented": true,
8705
- "summary": "The player's Arcade-wide defaults for this device — a volume, battery\nsaver, a keyboard layout, the gamepad's confirm button, vibration, and the\ncamera and controls settings (look sensitivity, gamepad look sensitivity,\ninvert look, touch movement) — or `null` when the Arcade did not send them.\n\n**Recommendations only; the player's own choice in your game wins.** Start\na setting from here while the player has made no choice of their own for\nit, and never overwrite one they made because this changed:\n\n```js\nconst saved = gem.deviceSettings.current ?? {};\nconst volume = saved.volume ?? gem.prefs?.recommended?.volume ?? 100;\n```\n\nUnlike `muted` and `reducedMotion`, nothing here is applied for you, and\nnothing obliges you to use it. See the guide's \"Game settings\" section.",
8705
+ "summary": "The player's Arcade-wide defaults for this device — a volume, battery\nsaver, a keyboard layout, the gamepad's confirm button, vibration, the\ncamera and controls settings (look sensitivity, gamepad look sensitivity,\ninvert look, touch movement) and a starting graphics quality — or `null`\nwhen the Arcade did not send them.\n\n**Recommendations only; the player's own choice in your game wins.** Start\na setting from here while the player has made no choice of their own for\nit, and never overwrite one they made because this changed:\n\n```js\nconst saved = gem.deviceSettings.current ?? {};\nconst volume = saved.volume ?? gem.prefs?.recommended?.volume ?? 100;\n```\n\nUnlike `muted` and `reducedMotion`, nothing here is applied for you, and\nnothing obliges you to use it. See the guide's \"Game settings\" section.",
8706
8706
  "deprecated": null,
8707
8707
  "params": [],
8708
8708
  "returns": null,
@@ -8876,6 +8876,38 @@
8876
8876
  "defaultValue": null,
8877
8877
  "guides": []
8878
8878
  },
8879
+ {
8880
+ "id": "GemRecommendedSettings.graphicsQuality",
8881
+ "name": "graphicsQuality",
8882
+ "anchor": "gemrecommendedsettings-graphicsquality",
8883
+ "group": "player",
8884
+ "signature": "readonly graphicsQuality: GemGraphicsQuality",
8885
+ "documented": true,
8886
+ "summary": "The graphics quality to START at on this device: `'low'`, `'medium'` or\n`'high'`. The Arcade picks it per device — the tier it detected, or the\nplayer's own Graphics choice — so a game opens at a quality that suits the\ndevice instead of guessing. A starting point, not a cap: while\n`graphicsQualityAuto` is true your game may adapt from here as it runs,\nand `createQualityGovernor` from `@indiegems/gem-web-sdk/quality` does\nthat for you. When the Arcade did not say, `'low'` with `batterySaver`\non and `'medium'` otherwise.",
8887
+ "deprecated": null,
8888
+ "params": [],
8889
+ "returns": null,
8890
+ "throws": null,
8891
+ "example": null,
8892
+ "defaultValue": null,
8893
+ "guides": []
8894
+ },
8895
+ {
8896
+ "id": "GemRecommendedSettings.graphicsQualityAuto",
8897
+ "name": "graphicsQualityAuto",
8898
+ "anchor": "gemrecommendedsettings-graphicsqualityauto",
8899
+ "group": "player",
8900
+ "signature": "readonly graphicsQualityAuto: boolean",
8901
+ "documented": true,
8902
+ "summary": "Whether the Arcade chose `graphicsQuality` for this device (`true`, the\nplayer left Graphics on Auto) or the player picked it by hand (`false`).\nWhile it is `false`, hold that quality: never step it down or up on your\nown. `true` when the Arcade did not say.",
8903
+ "deprecated": null,
8904
+ "params": [],
8905
+ "returns": null,
8906
+ "throws": null,
8907
+ "example": null,
8908
+ "defaultValue": null,
8909
+ "guides": []
8910
+ },
8879
8911
  {
8880
8912
  "id": "GemCohort",
8881
8913
  "name": "GemCohort",
@@ -9069,7 +9101,7 @@
9069
9101
  "group": "player",
9070
9102
  "signature": "readonly reserved: readonly GemReservedArea[]",
9071
9103
  "documented": true,
9072
- "summary": "Rectangles of your frame the arcade's own controls cover — its menu button\nin the top-right corner, and the wallets in the middle of the top row while they are shown (see\n`setWalletVisible`). Keep essential UI and touch targets out of them. Empty\nwhen the arcade covers nothing.\n\nA stylesheet reads the per-corner custom properties the SDK stamps from\nthese instead — `--gem-reserved-tl-height` and friends.",
9104
+ "summary": "Rectangles of your frame the arcade's own controls cover — in the Gem\nArcade, its Gem button in the top-left corner, and the frame-rate readout\nbeside it while the player shows it; an embedder that draws wallets over\nyour game adds theirs (see `setWalletVisible`). Keep essential UI and touch\ntargets out of them. Empty when the arcade covers nothing.\n\nA stylesheet reads the per-corner custom properties the SDK stamps from\nthese instead — `--gem-reserved-tl-height` and friends.",
9073
9105
  "deprecated": null,
9074
9106
  "params": [],
9075
9107
  "returns": null,
@@ -9181,7 +9213,7 @@
9181
9213
  "group": "platform",
9182
9214
  "signature": "readonly only?: readonly string[]",
9183
9215
  "documented": true,
9184
- "summary": "While shown, show ONLY these: platform currency slugs as\n`gem.platform.currencies.get()` lists them (`GEMS`, `ARCADE_TICKETS`),\nmatched exactly, and the literal `'score'` for the achievement score. Only\nfor a screen about one currency, such as a ticket store; leave it out and\nevery wallet shows, which is always safe. The Gem Arcade ignores it and\nshows every wallet while shown.",
9216
+ "summary": "While shown, show ONLY these: platform currency slugs as\n`gem.platform.currencies.get()` lists them (`GEMS`, `ARCADE_TICKETS`),\nmatched exactly, and the literal `'score'` for the achievement score. Only\nfor a screen about one currency, such as a ticket store; leave it out and\nevery wallet shows, which is always safe. The Gem Arcade draws no wallet\nover a game, so it has nothing to narrow and ignores it.",
9185
9217
  "deprecated": null,
9186
9218
  "params": [],
9187
9219
  "returns": null,
@@ -9949,6 +9981,22 @@
9949
9981
  "defaultValue": null,
9950
9982
  "guides": []
9951
9983
  },
9984
+ {
9985
+ "id": "Gem.paused",
9986
+ "name": "paused",
9987
+ "anchor": "gem-paused",
9988
+ "group": "start",
9989
+ "signature": "readonly paused: boolean",
9990
+ "documented": true,
9991
+ "summary": "Whether the arcade has your game paused right now: `true` from an\n`onPause(true)` until the `onPause(false)` that follows it, `false` before\nany. The same pause `onPause` tells you about, readable at any moment —\nfor code that was not around when the edge arrived, such as a frame-time\nmonitor deciding whether a slow frame counts.",
9992
+ "deprecated": null,
9993
+ "params": [],
9994
+ "returns": null,
9995
+ "throws": null,
9996
+ "example": null,
9997
+ "defaultValue": null,
9998
+ "guides": []
9999
+ },
9952
10000
  {
9953
10001
  "id": "Gem.cohorts",
9954
10002
  "name": "cohorts",
@@ -10057,7 +10105,7 @@
10057
10105
  "group": "start",
10058
10106
  "signature": "cueReward(cue: RewardCue): boolean",
10059
10107
  "documented": true,
10060
- "summary": "Tell the arcade a platform-currency reward is on its way, so its **wallet\nshows it landing**: a pending state right away and, once the platform\nconfirms the grant, the currency flying from `origin` into the wallet with\na bounce as the total moves.\n\nCall it the moment your game decides to award — when your server has moved\n(or is about to move) the progression mapped onto a platform currency —\nand pass the centre of that currency's icon (or hero) on your screen, so\nthe flight starts from the mark the player is looking at:\n\n```js\nconst { x, y, width, height } = rewardGem.getBoundingClientRect(); // the gem's own box\ngem.cueReward({\n currencySlug: 'GEMS', // or 'ARCADE_TICKETS', from the ticket\n expected: 15,\n origin: { x: x + width / 2, y: y + height / 2 },\n});\n```\n\n**This is the platform-currency animation, and the only one.** The display\nof every platform currency — Gems and Tickets alike — belongs to the\narcade: a game draws no flight or celebration of its own for one and keeps\nno counter of one. **If this returns `false`, do nothing** — there is no\nfallback celebration.\n\n**The arcade never takes your word for the amount.** What lands, and the\nplayer's new total, come from the platform itself; `expected` is drawn as\nprovisional, and a cue the platform never confirms fades on its own — a\ngrant clamped to zero, say, or one the receipt reports `suppressed`. So\nthe cue can only ever make the wallet move for a reward that really\nlanded — which it does on every channel, dev and test included.",
10108
+ "summary": "Tell the arcade a platform-currency reward is on its way, so it **shows it\nlanding**: in the Gem Arcade the currency flies from `origin` into the Gem\nbutton at the top-left of your frame, which pops as each mark lands, and\nthe player's total moves once the platform confirms the grant. The Gem\nArcade draws no wallet over your game: the player sees their Gems and\nTickets in the Gem menu. A payout you do not cue still flies, from the\nmiddle of your frame, so cue the ones the player earns on screen.\n\nCall it the moment your game decides to award — when your server has moved\n(or is about to move) the progression mapped onto a platform currency —\nand pass the centre of that currency's icon (or hero) on your screen, so\nthe flight starts from the mark the player is looking at:\n\n```js\nconst { x, y, width, height } = rewardGem.getBoundingClientRect(); // the gem's own box\ngem.cueReward({\n currencySlug: 'GEMS', // or 'ARCADE_TICKETS', from the ticket\n expected: 15,\n origin: { x: x + width / 2, y: y + height / 2 },\n});\n```\n\n**This is the platform-currency animation, and the only one.** The display\nof every platform currency — Gems and Tickets alike — belongs to the\narcade: a game draws no flight or celebration of its own for one and keeps\nno counter of one. **If this returns `false`, do nothing** — there is no\nfallback celebration.\n\n**The arcade never takes your word for the amount.** What lands, and the\nplayer's new total, come from the platform itself; `expected` is drawn as\nprovisional, and a cue the platform never confirms fades on its own — a\ngrant clamped to zero, say, or one the receipt reports `suppressed`. So\nthe cue can only ever make the wallet move for a reward that really\nlanded — which it does on every channel, dev and test included.",
10061
10109
  "deprecated": null,
10062
10110
  "params": [
10063
10111
  {
@@ -10080,7 +10128,7 @@
10080
10128
  "group": "start",
10081
10129
  "signature": "setWalletVisible(visible: boolean, options?: WalletVisibleOptions): boolean",
10082
10130
  "documented": true,
10083
- "summary": "Show or hide the player's wallets — Gems, Tickets, the achievement score —\nthat an embedder draws over your game.\n\n**Shown unless you hide them.** Call nothing and the Gem Arcade shows\nevery wallet the whole time, during play too. A game that wants the top\nrow for gameplay hides them while it plays and shows them again on its\nmenus (the Gem Arcade honours this again since 2026-10-08, on a phone,\nwhere the wallets sit over your game; where they sit beside the frame it\nchanges nothing).\n\n```js\nlevel.onStart = () => gem.setWalletVisible(false);\nmenu.onOpen = () => gem.setWalletVisible(true);\n```\n\nThe Gem Arcade does NOT act on `only`: a shown wallet there is every\nwallet. Another embedder may honour it.\n\n**It is screen state, not a toggle.** Each call is the whole answer and\nreplaces the last. While hidden, the arcade still pops a wallet up for a\nmoment when something happens to it — a reward you cued, a spend, a\nlanding — and your state never cancels that. Shown wallets cover the\nmiddle of your frame's top row, each arriving as a rectangle in\n`gem.viewport.reserved`; hiding them gives that space back. A wallet the\narcade pops up for a moment floats over your game and moves nothing.\n\n**Never draw a platform balance yourself**, and never show a pending state\nfor one: the arcade takes every figure from the platform. If this returns\n`false`, do nothing in its place.",
10131
+ "summary": "Show or hide the player's wallets — Gems, Tickets, the achievement score —\nthat an embedder draws over your game.\n\n**The Gem Arcade draws no wallet over a game** (since 2026-10-11): the\nplayer sees their balances in the Gem menu, and payouts fly into the Gem\nbutton. So under the Gem Arcade this changes nothing on screen — it is\naccepted and harmless, and a game that already calls it keeps working.\nCall it for an embedder that does draw wallets over your game: hide them\nwhile you play, show them on your menus.\n\n```js\nlevel.onStart = () => gem.setWalletVisible(false);\nmenu.onOpen = () => gem.setWalletVisible(true);\n```\n\n**It is screen state, not a toggle.** Each call is the whole answer and\nreplaces the last. Where an embedder draws wallets, shown ones arrive as\nrectangles in `gem.viewport.reserved` and hiding them gives that space\nback; the Gem Arcade sends none for wallets. `only` narrows what shows,\nwhere an embedder honours it; the Gem Arcade does not.\n\n**Never draw a platform balance yourself**, and never show a pending state\nfor one: the arcade takes every figure from the platform. If this returns\n`false`, do nothing in its place.",
10084
10132
  "deprecated": null,
10085
10133
  "params": [
10086
10134
  {
@@ -12778,6 +12826,22 @@
12778
12826
  "defaultValue": null,
12779
12827
  "guides": []
12780
12828
  },
12829
+ {
12830
+ "id": "GemGraphicsQuality",
12831
+ "name": "GemGraphicsQuality",
12832
+ "anchor": "gemgraphicsquality",
12833
+ "group": "player",
12834
+ "signature": "type GemGraphicsQuality = \"low\" | \"medium\" | \"high\"",
12835
+ "documented": true,
12836
+ "summary": "A graphics quality tier: `'low'`, `'medium'` or `'high'`. What\n`gem.prefs.recommended.graphicsQuality` holds.",
12837
+ "deprecated": null,
12838
+ "params": [],
12839
+ "returns": null,
12840
+ "throws": null,
12841
+ "example": null,
12842
+ "defaultValue": null,
12843
+ "guides": []
12844
+ },
12781
12845
  {
12782
12846
  "id": "ServerTokenGrant",
12783
12847
  "name": "ServerTokenGrant",
package/docs/brand-art.md CHANGED
@@ -75,7 +75,7 @@ These are the Arcade's own icon renders — the files its wallet draws — with
75
75
  for byte, apart from stripping the colour-space and resolution tags every browser already assumes. They are the gem
76
76
  of the arcade's Gems flight — a 3D model rendered in Blender, a round cut with the icon's six facet
77
77
  colours — at rest and without its motion blur, so the gem a player sees in your price is the gem
78
- that flies into their wallet. Each size has its own recipe, in CSS px, so it carries the same visual
78
+ that flies into the Gem button when they are paid. Each size has its own recipe, in CSS px, so it carries the same visual
79
79
  weight on every screen:
80
80
 
81
81
  | Size | Outline | Girdle line | Facet lines | Sparkle |
@@ -282,7 +282,8 @@ too, with the ticket in place of the gem. Amended 2026-09-30 for the earn allowa
282
282
  decision of that date); the ruling's other rules are unchanged. In rules:
283
283
 
284
284
  - **Keep no Gems counter.** Your game shows no Gems balance, running total or wallet of its own, on
285
- any screen. The arcade's wallet is the only place Gems accrue, and the only place a total is shown.
285
+ any screen. The arcade's wallet is the only place Gems accrue, and the only place a total is shown
286
+ — in the Gem Arcade, the Gem menu, which the player opens from the Gem button at the top-left.
286
287
  - **Draw no Gems flight or celebration of your own.** No particles flying to a corner, no counting-up
287
288
  number, no burst of gems. `gem.cueReward` is the animation, and the arcade draws it.
288
289
  - **Show no Gems amount at all when you are not connected to the arcade.** Without the arcade there is
@@ -306,8 +307,9 @@ decision of that date); the ruling's other rules are unchanged. In rules:
306
307
  with the canvas's `getBoundingClientRect()`.
307
308
 
308
309
  A Tickets payout is cued the same way, from the ticket, with `currencySlug: 'ARCADE_TICKETS'`. The
309
- arcade's wallet shows Gems and Tickets side by side and flies each payout into the total of the
310
- currency the cue names, so the slug is what decides where your ticket lands.
310
+ slug decides what flies — gems or tickets — and which total the platform moves. In the Gem Arcade
311
+ every payout flies into the Gem button at the top-left of your frame; no wallet is drawn over
312
+ your game.
311
313
  - **If `cueReward` returns `false`, do nothing.** There is no fallback celebration. `false` means the
312
314
  arcade will not show the payout here — the bridge is not up, or the embedder has no wallet — and
313
315
  your game does not replace it with one of its own. Your reward screen still says what was earned;
package/docs/changelog.md CHANGED
@@ -16,6 +16,26 @@ Its entries are grouped under up to five headings:
16
16
  - **Rebuild**: a type changed. Recompile against the new version; code that compiles today keeps compiling unless the entry says otherwise.
17
17
  - **New capability**: something a game can now do. Nothing changes unless you use it.
18
18
 
19
+ ## 10.4.0
20
+
21
+ Upgrading from 10.3.1. Impact: required.
22
+
23
+ ### Rebuild
24
+
25
+ - The recommended preferences type gained optional graphics quality and automatic fields, and the host method that sends preferences accepts the same two fields. Existing code keeps working; recompile your game against the new types. Host or test-harness code that sends preferences can now also pass the graphics quality tier and the automatic flag.
26
+
27
+ ### New capability
28
+
29
+ - The recommended settings now carry a graphics quality tier and an automatic flag. The Arcade picks a starting tier per device (low, medium or high) and a game can start from it. An unknown tier is read as medium, a missing tier follows the battery saver setting (low when it is on, otherwise medium), and a missing automatic flag is read as true, so both values are always present in the game. When the automatic flag is false the player picked the tier by hand, and the game should keep it rather than adapt it.
30
+ - The new paused value holds the latest state of the pause signal. Code that starts after the pause signal has already arrived, such as a frame-time monitor created mid-game, can read it to learn whether the game is currently paused.
31
+ - A new optional quality entry point lets a game adapt its graphics level to frame time. It starts at the Arcade's tier mapped onto the game's own levels. It steps down one level after about two seconds of slow frames and up one level after about ten seconds of comfortably fast frames, never going above one level over its start. It ignores paused and hidden time, holds steady when the player chose the tier by hand, and restarts when a new recommendation arrives. It can run on animation frames or be fed frame times manually. A game that does not import it pays nothing in size.
32
+
33
+ ## 10.3.1
34
+
35
+ Upgrading from 10.3.0. Impact: none.
36
+
37
+ Nothing in this release asks anything of a game.
38
+
19
39
  ## 10.3.0
20
40
 
21
41
  Upgrading from 10.2.0. Impact: required.
@@ -320,11 +320,11 @@ Four things to know before you ship one:
320
320
  in" is not in general when the allowance refills. Each date's allowance is spent once, whatever
321
321
  the player does to their time zone.
322
322
 
323
- **When a completion pays a platform currency, show it where it happened.** The arcade draws the
324
- player's Gems and Tickets in its own chrome, and without a cue a payout lands there silently, in a
325
- corner the player is not looking at. At the moment your game shows the challenge complete, call
323
+ **When a completion pays a platform currency, show it where it happened.** The arcade shows the
324
+ player's Gems and Tickets in its own chrome, and without a cue a payout flies from the middle of
325
+ your frame, not from the payout the player is looking at. At the moment your game shows the challenge complete, call
326
326
  `gem.cueReward` with the currency that paid (`GEMS` or `ARCADE_TICKETS`) and the centre of its icon
327
- beside the payout on your completion screen, and the payout flies from there into the wallet:
327
+ beside the payout on your completion screen, and the payout flies from there into the Gem button:
328
328
  [inventory-and-catalogs.md](inventory-and-catalogs.md#showing-the-reward-land-in-the-arcades-wallet).
329
329
  **If it returns `false`, do nothing** — no celebration of your own, no counter, no flight;
330
330
  platform-currency display belongs to the arcade, and a game not connected to it shows no
@@ -50,7 +50,7 @@ either way.
50
50
  ```jsonc
51
51
  // package.json
52
52
  "dependencies": {
53
- "@indiegems/gem-web-sdk": "10.3.0"
53
+ "@indiegems/gem-web-sdk": "10.4.0"
54
54
  }
55
55
  ```
56
56
 
@@ -589,14 +589,15 @@ the source progression from a function the player calls, on a progression that o
589
589
  ### Showing the reward land in the arcade's wallet
590
590
 
591
591
  **Platform-currency display belongs to the arcade** (the creator's ruling, 2026-09-24) — Gems and
592
- Tickets alike. The arcade draws the player's platform currencies in its own chrome while your game
593
- runs, and moves a total when the platform confirms a grant. Your game keeps no counter or balance
592
+ Tickets alike. The arcade shows the player's platform currencies in its own chrome — in the Gem
593
+ Arcade, the Gem menu the player opens from the Gem button at the top-left of your frame; no wallet
594
+ is drawn over your game — and moves a total when the platform confirms a grant. Your game keeps no counter or balance
594
595
  of a platform currency of its own, draws no flight or celebration of its own, and shows no
595
596
  platform-currency amount at all when it is not connected to the arcade.
596
597
 
597
598
  What your game does is ask for the payout to start on **your** screen, from the currency's icon (or
598
- hero) beside the reward, so the currency flies from there into the wallet and the total bounces up
599
- as it lands. A Gems payout is cued from the gem:
599
+ hero) beside the reward, so the currency flies from there into the arcade — in the Gem Arcade,
600
+ into the Gem button, which pops as each mark lands. A Gems payout is cued from the gem:
600
601
 
601
602
  ```js
602
603
  const { x, y, width, height } = rewardGem.getBoundingClientRect(); // the gem element's own box
@@ -609,8 +610,10 @@ gem.cueReward({
609
610
 
610
611
  `origin` is the **centre of the gem on screen** — measure the gem element itself, not the row or
611
612
  panel around it. A Tickets payout is the same call with `currencySlug: 'ARCADE_TICKETS'`, from the
612
- ticket: the slug decides which total the payout flies into. **If `cueReward` returns `false`, do
613
- nothing**: there is no fallback celebration.
613
+ ticket: the slug decides what flies and which total moves. **A payout you do not cue still flies**
614
+ in the Gem Arcade, from the middle of your frame, once the platform confirms it — cue it so the
615
+ flight starts where the player is looking. **If `cueReward` returns `false`, do nothing**: there is
616
+ no fallback celebration.
614
617
  `false` means the arcade will not show it here — the bridge is not up, or the embedder has no wallet
615
618
  — and your game does not draw one of its own in its place.
616
619
 
@@ -898,7 +901,8 @@ buyButton.disabled = !gems || gems.balance < price; // can the player afford it?
898
901
  ```
899
902
 
900
903
  **Read it to decide, never to display.** Platform-currency display belongs to the arcade: its
901
- wallet shows the player's Gems and Tickets totals while your game runs, and your game keeps no
904
+ wallet — the Gem menu, in the Gem Arcade — shows the player's Gems and Tickets totals while your
905
+ game runs, and your game keeps no
902
906
  counter, balance or wallet total of either of its own ([brand-art.md](brand-art.md) §5). Use the
903
907
  balance to enable a purchase or to say "not enough Gems" — not to draw a number.
904
908
 
@@ -28,13 +28,13 @@
28
28
  },
29
29
  {
30
30
  "file": "brand-art.md",
31
- "sha256": "cf54939a0fcee1f5a6e11b9de52b5ed498ec8c373683e92aa5bc6654b12eb0ab",
32
- "updated": "2026-10-01T03:49:20Z"
31
+ "sha256": "87267dca53e5506b1aee77ac9819aef8025c8e0de2780e65a2ea3b7236a9c0ab",
32
+ "updated": "2026-10-11T18:48:12Z"
33
33
  },
34
34
  {
35
35
  "file": "changelog.md",
36
- "sha256": "daae6b71cecc9761bc1e3ed5603c5d8f366e0533ce4ecc6d6c57f546efe1a292",
37
- "updated": "2026-10-11T18:22:44Z"
36
+ "sha256": "fd69a4c08141e0646d695adedce0d313de2295cfd6bc656433ce33dd02ec731a",
37
+ "updated": "2026-10-11T19:38:28Z"
38
38
  },
39
39
  {
40
40
  "file": "cross-promotion.md",
@@ -43,8 +43,8 @@
43
43
  },
44
44
  {
45
45
  "file": "daily-challenges.md",
46
- "sha256": "f4842b2fe01c8780020326f4722ab69dc01c1f40e1e6455cf2fb27752094383d",
47
- "updated": "2026-10-10T14:37:50Z"
46
+ "sha256": "0423168355c22724e705def15ca19a5651b7f0da3598ec5aabcc69c32e0bb401",
47
+ "updated": "2026-10-11T18:48:12Z"
48
48
  },
49
49
  {
50
50
  "file": "dedicated-server-flow.md",
@@ -68,13 +68,13 @@
68
68
  },
69
69
  {
70
70
  "file": "getting-started.md",
71
- "sha256": "fd566ed7d7f0b1b2b9bea823de5455d2ee9a66072e7303df1bc5c12c1acddcbe",
72
- "updated": "2026-10-11T18:22:44Z"
71
+ "sha256": "f2c13478823888b777058bbdc4de41216dd8b48d7e7efab3a31334f980d647ec",
72
+ "updated": "2026-10-11T19:38:28Z"
73
73
  },
74
74
  {
75
75
  "file": "inventory-and-catalogs.md",
76
- "sha256": "3e0f71db5b4f5d2d256289f90ffec3ed2887a70b1e72b02f55ec64815043d900",
77
- "updated": "2026-10-09T00:13:03Z"
76
+ "sha256": "fecd1299ef19bc50ea17abe96bbe49f62dc366a9fc0f354c843a42b2d5a2f9e2",
77
+ "updated": "2026-10-11T18:48:12Z"
78
78
  },
79
79
  {
80
80
  "file": "leaderboards.md",
@@ -123,8 +123,8 @@
123
123
  },
124
124
  {
125
125
  "file": "server-bundles.md",
126
- "sha256": "c6935f10ba645e8a9d3fd48c10f313324bfdbca6fcbd273dfade6579dcfd32e8",
127
- "updated": "2026-10-11T18:22:44Z"
126
+ "sha256": "04bb5fb5f631a52c7962728a74602ce7eec6ee6a1a3efa6197542ce5aa4cce95",
127
+ "updated": "2026-10-11T19:38:28Z"
128
128
  },
129
129
  {
130
130
  "file": "serverless-functions-js.md",
@@ -148,8 +148,8 @@
148
148
  },
149
149
  {
150
150
  "file": "what-a-game-must-do.md",
151
- "sha256": "44fa07b8f379181cfac9a89c8fa672946d0e4aaeefe1fd3386cc3565abcb3c95",
152
- "updated": "2026-10-11T17:59:34Z"
151
+ "sha256": "ce2abba4b32ee1c0dd69eabaaac1f33e2534ac5b58e9a45c4f8a7b3bd3e11f27",
152
+ "updated": "2026-10-11T19:25:48Z"
153
153
  }
154
154
  ]
155
155
  }