@indiegems/gem-web-sdk 10.0.0 → 10.1.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.
- package/CHANGELOG.md +13 -0
- package/dist/game.d.ts +16 -1
- package/dist/gem-web-sdk.bridge.js +1 -1
- package/dist/gem-web-sdk.esm.js +1 -1
- package/dist/gem-web-sdk.function.js +2 -2
- package/dist/gem-web-sdk.game.js +2 -2
- package/dist/gem-web-sdk.min.js +2 -2
- package/dist/gem-web-sdk.sdk.js +2 -2
- package/dist/gem-web-sdk.server.js +2 -2
- package/dist/index.d.ts +14 -0
- package/dist/sdk.d.ts +16 -1
- package/docs/api/api-reference-game.md +116 -16
- package/docs/api/api-reference-server.md +1 -1
- package/docs/api/reference.json +122 -5
- package/docs/changelog.md +13 -0
- package/docs/getting-started.md +6 -6
- package/docs/manifest.json +10 -10
- package/docs/server-bundles.md +2 -2
- package/docs/vibration.md +8 -3
- package/docs/what-a-game-must-do.md +103 -25
- package/package.json +1 -1
- package/schema/bridge.v1.json +74 -5
package/docs/api/reference.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema_version": 2,
|
|
3
3
|
"source": {
|
|
4
4
|
"repo": "IndieGems/gem-web-sdk",
|
|
5
|
-
"version": "10.
|
|
5
|
+
"version": "10.1.0"
|
|
6
6
|
},
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -7630,7 +7630,7 @@
|
|
|
7630
7630
|
"group": "start",
|
|
7631
7631
|
"signature": "readonly onPrefs?: (prefs: GemPrefs) => void",
|
|
7632
7632
|
"documented": true,
|
|
7633
|
-
"summary": "The player's accessibility and locale preferences arrived, or changed.\n\n**A game that animates owes this a handler.** Reduced motion is the\nplayer's own account setting, and honouring it is on the platform's release\nchecklist.\n\n**`muted` is honoured for you** — the SDK silences your audio itself (see\n`manageAudio`). If your game has its own mute control, set it from `muted`\nhere so the two agree, and see [Gem.reportMuted](api-reference-game.md#gem-reportmuted) for the other\ndirection.",
|
|
7633
|
+
"summary": "The player's accessibility and locale preferences arrived, or changed.\n\n**A game that animates owes this a handler.** Reduced motion is the\nplayer's own account setting, and honouring it is on the platform's release\nchecklist.\n\n**`muted` is honoured for you** — the SDK silences your audio itself (see\n`manageAudio`). If your game has its own mute control, set it from `muted`\nhere so the two agree, and see [Gem.reportMuted](api-reference-game.md#gem-reportmuted) for the other\ndirection.\n\n**`recommended` changing is advice changing.** Apply it only to a setting\nthe player has not chosen in your game; never overwrite their own choice\nwith it.",
|
|
7634
7634
|
"deprecated": null,
|
|
7635
7635
|
"params": [],
|
|
7636
7636
|
"returns": null,
|
|
@@ -8008,7 +8008,7 @@
|
|
|
8008
8008
|
"group": "player",
|
|
8009
8009
|
"signature": "interface GemPrefs",
|
|
8010
8010
|
"documented": true,
|
|
8011
|
-
"summary": "The player's arcade-level preferences, which your game is expected to honour.\n\nReached as `gem.prefs`. These are the player's settings for the whole arcade,\nnot for your game — respecting them is part of what a game must do.\n\nThey can change mid-session, so read them when you need them rather than\ncaching at boot.",
|
|
8011
|
+
"summary": "The player's arcade-level preferences, which your game is expected to honour.\n\nReached as `gem.prefs`. These are the player's settings for the whole arcade,\nnot for your game — respecting them is part of what a game must do.\n\nThey can change mid-session, so read them when you need them rather than\ncaching at boot.\n\n`recommended` is the exception: the player's Arcade-wide defaults, which\nyour game may start its own settings from or ignore — never a reason to\noverwrite a choice the player made in your game.",
|
|
8012
8012
|
"deprecated": null,
|
|
8013
8013
|
"params": [],
|
|
8014
8014
|
"returns": null,
|
|
@@ -8086,6 +8086,123 @@
|
|
|
8086
8086
|
"defaultValue": null,
|
|
8087
8087
|
"guides": []
|
|
8088
8088
|
},
|
|
8089
|
+
{
|
|
8090
|
+
"id": "GemPrefs.recommended",
|
|
8091
|
+
"name": "recommended",
|
|
8092
|
+
"anchor": "gemprefs-recommended",
|
|
8093
|
+
"group": "player",
|
|
8094
|
+
"signature": "readonly recommended: GemRecommendedSettings | null",
|
|
8095
|
+
"documented": true,
|
|
8096
|
+
"summary": "The player's Arcade-wide defaults for this device — a volume, battery\nsaver, a keyboard layout, the gamepad's confirm button and vibration — or\n`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.",
|
|
8097
|
+
"deprecated": null,
|
|
8098
|
+
"params": [],
|
|
8099
|
+
"returns": null,
|
|
8100
|
+
"throws": null,
|
|
8101
|
+
"example": null,
|
|
8102
|
+
"defaultValue": null,
|
|
8103
|
+
"guides": []
|
|
8104
|
+
},
|
|
8105
|
+
{
|
|
8106
|
+
"id": "GemRecommendedSettings",
|
|
8107
|
+
"name": "GemRecommendedSettings",
|
|
8108
|
+
"anchor": "gemrecommendedsettings",
|
|
8109
|
+
"group": "player",
|
|
8110
|
+
"signature": "interface GemRecommendedSettings",
|
|
8111
|
+
"documented": true,
|
|
8112
|
+
"summary": "The player's Arcade-wide defaults for this device, as `gem.prefs.recommended`.\n\nThe player sets these once in the Arcade's Settings, and every game that\nreads them starts in the right place. They are advice: a game may ignore\nthem, and a choice the player made in your game always beats them.",
|
|
8113
|
+
"deprecated": null,
|
|
8114
|
+
"params": [],
|
|
8115
|
+
"returns": null,
|
|
8116
|
+
"throws": null,
|
|
8117
|
+
"example": null,
|
|
8118
|
+
"defaultValue": null,
|
|
8119
|
+
"guides": [
|
|
8120
|
+
{
|
|
8121
|
+
"slug": "what-a-game-must-do",
|
|
8122
|
+
"anchor": null
|
|
8123
|
+
}
|
|
8124
|
+
]
|
|
8125
|
+
},
|
|
8126
|
+
{
|
|
8127
|
+
"id": "GemRecommendedSettings.volume",
|
|
8128
|
+
"name": "volume",
|
|
8129
|
+
"anchor": "gemrecommendedsettings-volume",
|
|
8130
|
+
"group": "player",
|
|
8131
|
+
"signature": "readonly volume: number",
|
|
8132
|
+
"documented": true,
|
|
8133
|
+
"summary": "The volume the player wants, as a percentage from 0 to 100. A level, not a\nmute — `gem.prefs.muted` is the mute.",
|
|
8134
|
+
"deprecated": null,
|
|
8135
|
+
"params": [],
|
|
8136
|
+
"returns": null,
|
|
8137
|
+
"throws": null,
|
|
8138
|
+
"example": null,
|
|
8139
|
+
"defaultValue": null,
|
|
8140
|
+
"guides": []
|
|
8141
|
+
},
|
|
8142
|
+
{
|
|
8143
|
+
"id": "GemRecommendedSettings.batterySaver",
|
|
8144
|
+
"name": "batterySaver",
|
|
8145
|
+
"anchor": "gemrecommendedsettings-batterysaver",
|
|
8146
|
+
"group": "player",
|
|
8147
|
+
"signature": "readonly batterySaver: boolean",
|
|
8148
|
+
"documented": true,
|
|
8149
|
+
"summary": "The player asked games to save power on this device: a lower frame rate,\nfewer effects, a lower resolution.",
|
|
8150
|
+
"deprecated": null,
|
|
8151
|
+
"params": [],
|
|
8152
|
+
"returns": null,
|
|
8153
|
+
"throws": null,
|
|
8154
|
+
"example": null,
|
|
8155
|
+
"defaultValue": null,
|
|
8156
|
+
"guides": []
|
|
8157
|
+
},
|
|
8158
|
+
{
|
|
8159
|
+
"id": "GemRecommendedSettings.keyboardLayout",
|
|
8160
|
+
"name": "keyboardLayout",
|
|
8161
|
+
"anchor": "gemrecommendedsettings-keyboardlayout",
|
|
8162
|
+
"group": "player",
|
|
8163
|
+
"signature": "readonly keyboardLayout: \"wasd\" | \"arrows\"",
|
|
8164
|
+
"documented": true,
|
|
8165
|
+
"summary": "Which keys the player expects to move with: `'wasd'` or `'arrows'`.",
|
|
8166
|
+
"deprecated": null,
|
|
8167
|
+
"params": [],
|
|
8168
|
+
"returns": null,
|
|
8169
|
+
"throws": null,
|
|
8170
|
+
"example": null,
|
|
8171
|
+
"defaultValue": null,
|
|
8172
|
+
"guides": []
|
|
8173
|
+
},
|
|
8174
|
+
{
|
|
8175
|
+
"id": "GemRecommendedSettings.gamepadConfirm",
|
|
8176
|
+
"name": "gamepadConfirm",
|
|
8177
|
+
"anchor": "gemrecommendedsettings-gamepadconfirm",
|
|
8178
|
+
"group": "player",
|
|
8179
|
+
"signature": "readonly gamepadConfirm: \"south\" | \"east\"",
|
|
8180
|
+
"documented": true,
|
|
8181
|
+
"summary": "Which gamepad face button the player expects to confirm with, by position:\n`'south'` (the bottom button, A on an Xbox controller) or `'east'` (the\nright one, B on an Xbox controller). The other one goes back.",
|
|
8182
|
+
"deprecated": null,
|
|
8183
|
+
"params": [],
|
|
8184
|
+
"returns": null,
|
|
8185
|
+
"throws": null,
|
|
8186
|
+
"example": null,
|
|
8187
|
+
"defaultValue": null,
|
|
8188
|
+
"guides": []
|
|
8189
|
+
},
|
|
8190
|
+
{
|
|
8191
|
+
"id": "GemRecommendedSettings.vibration",
|
|
8192
|
+
"name": "vibration",
|
|
8193
|
+
"anchor": "gemrecommendedsettings-vibration",
|
|
8194
|
+
"group": "player",
|
|
8195
|
+
"signature": "readonly vibration: boolean",
|
|
8196
|
+
"documented": true,
|
|
8197
|
+
"summary": "Whether the player wants vibration and controller rumble.",
|
|
8198
|
+
"deprecated": null,
|
|
8199
|
+
"params": [],
|
|
8200
|
+
"returns": null,
|
|
8201
|
+
"throws": null,
|
|
8202
|
+
"example": null,
|
|
8203
|
+
"defaultValue": null,
|
|
8204
|
+
"guides": []
|
|
8205
|
+
},
|
|
8089
8206
|
{
|
|
8090
8207
|
"id": "GemCohort",
|
|
8091
8208
|
"name": "GemCohort",
|
|
@@ -8487,7 +8604,7 @@
|
|
|
8487
8604
|
"group": "frame",
|
|
8488
8605
|
"signature": "readonly onSettings?: () => void",
|
|
8489
8606
|
"documented": true,
|
|
8490
|
-
"summary": "Open your settings screen.
|
|
8607
|
+
"summary": "Open your settings screen. Another embedder may show a Settings entry that\ncalls it, but **the Gem Arcade no longer does**: draw a settings button of\nyour own, always (top-right is the recommendation). Passing this is still\naccepted and harmless.",
|
|
8491
8608
|
"deprecated": null,
|
|
8492
8609
|
"params": [],
|
|
8493
8610
|
"returns": null,
|
|
@@ -9271,7 +9388,7 @@
|
|
|
9271
9388
|
"group": "start",
|
|
9272
9389
|
"signature": "setMenu(menu: GameMenu): boolean",
|
|
9273
9390
|
"documented": true,
|
|
9274
|
-
"summary": "Put
|
|
9391
|
+
"summary": "Put up to four entries of your own in the arcade's menu. The arcade keeps\none menu button over your game: it is how a player leaves, mutes, goes\nfullscreen, reports a problem and changes their Arcade-wide settings.\n\n```js\ngem.setMenu({\n items: [{ id: 'how-to-play', label: t('How to play'), onSelect: () => help.open() }],\n});\n```\n\n**Your settings are not in it: draw your own settings button, always** —\ntop-right is the recommendation. `onSettings` is still accepted, and\nanother embedder may show an entry for it, but the Gem Arcade no longer\nshows a Settings entry, so a game whose settings open only from there has\nnone a player can reach.\n\n**It is screen state, not a toggle.** Each call is the whole answer and\nreplaces the last, so call it again when the entries change — a new\nlanguage, a screen with different options. `gem.setMenu({})` takes\neverything of yours back out. The arcade's own entries are not yours to\nchange, and Return to the Arcade is always among them.\n\n**The arcade pauses your game while its menu is open** (`onPause`), and\nresumes it before a handler of yours runs. A handler opens a screen; it\nshould not assume the game was paused by you.",
|
|
9275
9392
|
"deprecated": null,
|
|
9276
9393
|
"params": [
|
|
9277
9394
|
{
|
package/docs/changelog.md
CHANGED
|
@@ -16,6 +16,19 @@ 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.1.0
|
|
20
|
+
|
|
21
|
+
Upgrading from 10.0.0. Impact: required.
|
|
22
|
+
|
|
23
|
+
### Rebuild
|
|
24
|
+
|
|
25
|
+
- The host-side method for sending preferences now accepts an optional recommended object with the five default settings. Existing calls remain valid, so recompiling is enough. Pass the new field only if your test host should simulate the Arcade's recommended defaults.
|
|
26
|
+
|
|
27
|
+
### New capability
|
|
28
|
+
|
|
29
|
+
- GemPrefs now carries an optional recommended field holding the player's Arcade-wide defaults as advice: volume from 0 to 100, battery saver, keyboard layout (wasd or arrows), gamepad confirm button (south or east) and vibration. It is null when the Arcade does not send it. Changes arrive through the existing preferences callback, and the latest value is always available on the current preferences. Use these values as starting points when the player has no saved choice in your game, and never overwrite a choice the player made in your own settings. Muted, reduced motion and frame stats stay where they were.
|
|
30
|
+
- A new GemRecommendedSettings type describes the recommended defaults object. You can use it to type your own code that reads or stores the recommended values. The object is frozen, so copy values into your own settings rather than modifying it.
|
|
31
|
+
|
|
19
32
|
## 10.0.0
|
|
20
33
|
|
|
21
34
|
Upgrading from 9.1.4. Impact: required.
|
package/docs/getting-started.md
CHANGED
|
@@ -50,7 +50,7 @@ either way.
|
|
|
50
50
|
```jsonc
|
|
51
51
|
// package.json
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@indiegems/gem-web-sdk": "10.
|
|
53
|
+
"@indiegems/gem-web-sdk": "10.1.0"
|
|
54
54
|
}
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -175,11 +175,11 @@ gem.frame.progress(loaded, total); // while assets load
|
|
|
175
175
|
gem.frame.ready(); // once the player can actually play
|
|
176
176
|
```
|
|
177
177
|
|
|
178
|
-
And
|
|
179
|
-
|
|
180
|
-
([what a game must do](what-a-game-must-do.md#
|
|
181
|
-
the Arcade there is no
|
|
182
|
-
cannot leave is one they leave by closing the tab.
|
|
178
|
+
And draw a settings button of your own — top-right is the recommendation — whose values start from
|
|
179
|
+
the player's Arcade-wide defaults, `gem.prefs.recommended`, until the player chooses their own
|
|
180
|
+
([what a game must do](what-a-game-must-do.md#game-settings)). The Arcade's menu does not open
|
|
181
|
+
your settings. Outside the Arcade there is no Arcade menu at all, so also give players a way out — a
|
|
182
|
+
control that calls `gem.exit()`. A game a player cannot leave is one they leave by closing the tab.
|
|
183
183
|
|
|
184
184
|
## 6. Play it
|
|
185
185
|
|
package/docs/manifest.json
CHANGED
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"file": "changelog.md",
|
|
36
|
-
"sha256": "
|
|
37
|
-
"updated": "2026-10-
|
|
36
|
+
"sha256": "a194ff0323a071b622349d0b7de0f189d723472f2e6584594d752b21daf5f668",
|
|
37
|
+
"updated": "2026-10-10T20:52:09Z"
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
"file": "cross-promotion.md",
|
|
@@ -68,8 +68,8 @@
|
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
"file": "getting-started.md",
|
|
71
|
-
"sha256": "
|
|
72
|
-
"updated": "2026-10-
|
|
71
|
+
"sha256": "f7a67995ffcbddafaf7ed5c6c1ae8bc2c25dabe60c588e5b70dfb73218445a36",
|
|
72
|
+
"updated": "2026-10-10T20:52:09Z"
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
"file": "inventory-and-catalogs.md",
|
|
@@ -123,8 +123,8 @@
|
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"file": "server-bundles.md",
|
|
126
|
-
"sha256": "
|
|
127
|
-
"updated": "2026-10-
|
|
126
|
+
"sha256": "211384b7a68560979a43a57d25acc1e2897343958973b1c3f2a0d8fbeb0d7ffd",
|
|
127
|
+
"updated": "2026-10-10T20:52:09Z"
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
130
|
"file": "serverless-functions-js.md",
|
|
@@ -143,13 +143,13 @@
|
|
|
143
143
|
},
|
|
144
144
|
{
|
|
145
145
|
"file": "vibration.md",
|
|
146
|
-
"sha256": "
|
|
147
|
-
"updated": "2026-10-
|
|
146
|
+
"sha256": "587031f88acb64d8da0933ead76e50a88190e24a17effd875de0828f8c436393",
|
|
147
|
+
"updated": "2026-10-10T20:30:31Z"
|
|
148
148
|
},
|
|
149
149
|
{
|
|
150
150
|
"file": "what-a-game-must-do.md",
|
|
151
|
-
"sha256": "
|
|
152
|
-
"updated": "2026-10-
|
|
151
|
+
"sha256": "a339443ece37e5a5311a05ff946a081ac6e8557d29e2ab59b90832e4ad40fc5d",
|
|
152
|
+
"updated": "2026-10-10T20:42:00Z"
|
|
153
153
|
}
|
|
154
154
|
]
|
|
155
155
|
}
|
package/docs/server-bundles.md
CHANGED
|
@@ -80,8 +80,8 @@ To check the download yourself: the package npm serves is, byte for byte, the fi
|
|
|
80
80
|
also publishes on its download host, with the hash beside it:
|
|
81
81
|
|
|
82
82
|
```bash
|
|
83
|
-
curl -fsSL -O https://downloads.gemarcade.com/sdk/v10.
|
|
84
|
-
curl -fsSL https://downloads.gemarcade.com/sdk/v10.
|
|
83
|
+
curl -fsSL -O https://downloads.gemarcade.com/sdk/v10.1.0/gem-web-sdk.tgz
|
|
84
|
+
curl -fsSL https://downloads.gemarcade.com/sdk/v10.1.0/gem-web-sdk.tgz.sri
|
|
85
85
|
openssl dgst -sha384 -binary gem-web-sdk.tgz | openssl base64 -A
|
|
86
86
|
```
|
|
87
87
|
|
package/docs/vibration.md
CHANGED
|
@@ -23,7 +23,9 @@ Two rules come before everything else on this page. Both are easy to get wrong.
|
|
|
23
23
|
|
|
24
24
|
## The whole integration
|
|
25
25
|
|
|
26
|
-
Your game owns its vibration toggle
|
|
26
|
+
Your game owns its vibration toggle. The Arcade keeps only the player's Arcade-wide default,
|
|
27
|
+
`gem.prefs.recommended.vibration`, which your toggle starts from until the player flips it
|
|
28
|
+
([game settings](what-a-game-must-do.md#game-settings)). Keep the player's choice in
|
|
27
29
|
`gem.deviceSettings`, never in `localStorage`, which every publish of your game wipes.
|
|
28
30
|
|
|
29
31
|
```js
|
|
@@ -32,10 +34,13 @@ await gem.awaitBridge();
|
|
|
32
34
|
// Your settings, kept by the Arcade so they survive your next publish.
|
|
33
35
|
let settings = (await gem.deviceSettings.wait(1_500)) ?? {};
|
|
34
36
|
|
|
37
|
+
// The player's own choice, else the Arcade's recommendation, else on.
|
|
38
|
+
const vibrationOn = () => settings.vibration ?? gem.prefs?.recommended?.vibration ?? true;
|
|
39
|
+
|
|
35
40
|
// Build the row when the settings screen OPENS, never once at boot.
|
|
36
41
|
function openSettings() {
|
|
37
42
|
vibrationRow.hidden = !gem.vibration.available;
|
|
38
|
-
vibrationToggle.checked =
|
|
43
|
+
vibrationToggle.checked = vibrationOn();
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
vibrationToggle.onchange = () => {
|
|
@@ -45,7 +50,7 @@ vibrationToggle.onchange = () => {
|
|
|
45
50
|
|
|
46
51
|
// Check your own toggle before every call. Nothing else: no fallback.
|
|
47
52
|
function onHit() {
|
|
48
|
-
if (
|
|
53
|
+
if (vibrationOn()) gem.vibration.vibrate(30);
|
|
49
54
|
}
|
|
50
55
|
```
|
|
51
56
|
|
|
@@ -564,14 +564,15 @@ credit for it until you say so.
|
|
|
564
564
|
Five messages arrive that a game cannot ask for — the parent pushes them on its own schedule, and
|
|
565
565
|
there is no route by which the frame could request one. `createGem` forwards all five:
|
|
566
566
|
|
|
567
|
-
- **`onPrefs`** — `reducedMotion`, `muted`, `locale`, `showFrameStats`, sent
|
|
568
|
-
handshake and again on every change. Also **held**, as `gem.prefs`, because a component mounted
|
|
567
|
+
- **`onPrefs`** — `reducedMotion`, `muted`, `locale`, `showFrameStats` and `recommended`, sent
|
|
568
|
+
once after the handshake and again on every change. Also **held**, as `gem.prefs`, because a component mounted
|
|
569
569
|
after the push would otherwise have missed it and cannot ask again. Read it where you need it
|
|
570
570
|
rather than caching it — the locale changes mid-session. `muted` is the Arcade's mute: the SDK
|
|
571
571
|
silences your page itself, and your own mute control follows it — see
|
|
572
572
|
[sound](#sound-your-own-mute-control-and-the-arcades) below. `showFrameStats` you need not act
|
|
573
573
|
on: the SDK counts your frames itself — see [the Arcade's FPS overlay](#the-arcades-fps-overlay). See the language section for how to combine
|
|
574
|
-
`locale` with your own detection.
|
|
574
|
+
`locale` with your own detection. `recommended` is the player's Arcade-wide defaults, which your
|
|
575
|
+
own settings start from — see [game settings](#game-settings).
|
|
575
576
|
- **`onPause`** — **pause gameplay and release the cursor.** Cooperative; nothing enforces it.
|
|
576
577
|
|
|
577
578
|
```js
|
|
@@ -586,7 +587,7 @@ there is no route by which the frame could request one. `createGem` forwards all
|
|
|
586
587
|
the dialog sitting on top of your game.
|
|
587
588
|
|
|
588
589
|
**Save on pause.** When a player on a phone chooses Return to the Arcade in
|
|
589
|
-
[the Arcade's menu](#the-arcades-menu
|
|
590
|
+
[the Arcade's menu](#the-arcades-menu), or presses the device's Back, your
|
|
590
591
|
game is **parked**, not closed:
|
|
591
592
|
it receives `onPause(true)` and stays loaded while the player browses. They may come back and
|
|
592
593
|
press Continue — `onPause(false)`, and play resumes where it stopped — or the parked game may be
|
|
@@ -704,8 +705,8 @@ the `var()` above, or read `gem.viewport`. The release checklist looks for the b
|
|
|
704
705
|
On a phone your game is full-bleed, and the arcade keeps one row of its own over the top of it:
|
|
705
706
|
|
|
706
707
|
- **The menu button**, in the **top-right corner**, always there. It opens
|
|
707
|
-
[the Arcade's menu](#the-arcades-menu
|
|
708
|
-
settings
|
|
708
|
+
[the Arcade's menu](#the-arcades-menu): Return to the Arcade, mute, the player's Arcade-wide
|
|
709
|
+
settings and the rest. Not your settings: those are [your own button](#game-settings).
|
|
709
710
|
- **The player's wallets** (Gems, Tickets, their achievement score), in the **middle of the row**,
|
|
710
711
|
[there unless your game hides them](#the-arcades-wallet-shown-unless-you-hide-it-never-drawn),
|
|
711
712
|
during play as well as on your menus. They are translucent, so your game shows through them — but they are still the Arcade's,
|
|
@@ -723,8 +724,8 @@ Keep the left for Home / Back even on a screen that has neither today: a player
|
|
|
723
724
|
is once, across every game, and information that sits there on one screen teaches them the wrong
|
|
724
725
|
thing on the next.
|
|
725
726
|
|
|
726
|
-
**
|
|
727
|
-
|
|
727
|
+
**Your settings button goes top-right, clear of the Arcade's controls**: never under one, and below
|
|
728
|
+
any the Arcade keeps in that corner. [Game settings](#game-settings) shows how to place it.
|
|
728
729
|
|
|
729
730
|
**How much room the top-left has.** The menu button is about 48px across — the size of a Home or
|
|
730
731
|
Back button. With every wallet shown, the narrowest phones (360px wide) leave roughly 68px to the
|
|
@@ -903,22 +904,104 @@ and anything that must follow them, is a save or `gem.me.settings`:
|
|
|
903
904
|
draws the line in full. Never block your first frame on it either: apply defaults, then apply what
|
|
904
905
|
arrives.
|
|
905
906
|
|
|
906
|
-
##
|
|
907
|
+
## Game settings
|
|
907
908
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
909
|
+
**Your settings belong to your game: draw a settings button of your own, and the screen it opens.**
|
|
910
|
+
The Arcade does not put your settings in its menu any more — its menu holds the player's
|
|
911
|
+
_Arcade-wide_ settings instead — so a game with no button of its own has settings nobody can reach.
|
|
912
|
+
|
|
913
|
+
**Put the button in the top-right corner.** That is a recommendation, not a rule the Arcade
|
|
914
|
+
enforces, but a player who finds settings in the same corner in every game never has to look. Place
|
|
915
|
+
it with the reserved-area properties so it never sits under an Arcade control, whatever the Arcade
|
|
916
|
+
keeps in that corner on this device:
|
|
917
|
+
|
|
918
|
+
```css
|
|
919
|
+
#settings-button {
|
|
920
|
+
position: absolute;
|
|
921
|
+
top: max(
|
|
922
|
+
var(--gem-safe-top, env(safe-area-inset-top, 0px)),
|
|
923
|
+
var(--gem-reserved-tr-height, 0px)
|
|
924
|
+
);
|
|
925
|
+
right: var(--gem-safe-right, 0px);
|
|
926
|
+
}
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
### Start from the player's Arcade-wide defaults: `gem.prefs.recommended`
|
|
930
|
+
|
|
931
|
+
The player sets their defaults once, in the Arcade's Settings, separately for each device — a phone
|
|
932
|
+
and a television can say different things. They reach your game as `gem.prefs.recommended`, in the
|
|
933
|
+
same push as `muted` and `reducedMotion`, and again whenever the player changes one:
|
|
934
|
+
|
|
935
|
+
| Field | Values | What it suggests |
|
|
936
|
+
| ---------------- | --------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
937
|
+
| `volume` | `0` to `100` | Your master volume's starting level, as a percentage — divide by 100 for a gain. A level, never a mute. |
|
|
938
|
+
| `batterySaver` | `true` / `false` | Spend less power: a lower frame rate, fewer effects, a lower resolution. |
|
|
939
|
+
| `keyboardLayout` | `'wasd'` / `'arrows'` | Which keys move by default. |
|
|
940
|
+
| `gamepadConfirm` | `'south'` / `'east'` | Which face button confirms, by position: the bottom one (A on an Xbox controller) or the right one (B). The other one goes back. |
|
|
941
|
+
| `vibration` | `true` / `false` | Your vibration toggle's starting state ([if your game vibrates](#if-your-game-vibrates)). |
|
|
942
|
+
|
|
943
|
+
**They are recommendations, and the player's own choice in your game always wins:**
|
|
944
|
+
|
|
945
|
+
- **Start from `recommended` only when the player has no saved choice** for that setting. Decide it
|
|
946
|
+
setting by setting: a player who chose a volume and nothing else still gets the recommended
|
|
947
|
+
keyboard layout.
|
|
948
|
+
- **Never overwrite the player's own choice when the recommendation changes.** Keep what the player
|
|
949
|
+
chose apart from what you worked out, and save only what they chose: a recommended volume saved
|
|
950
|
+
as if it were theirs is one the next change to the recommendation can no longer reach. A "Reset
|
|
951
|
+
to defaults" in your settings clears what the player chose, so the recommendation applies again.
|
|
952
|
+
- **`null` means no advice**: an older Arcade, or an embedder that keeps no such settings. Use your
|
|
953
|
+
own defaults. Never hold your first frame for it.
|
|
954
|
+
- **You may ignore any of it.** A game with no keyboard support has nothing to do with
|
|
955
|
+
`keyboardLayout`.
|
|
956
|
+
|
|
957
|
+
```js
|
|
958
|
+
const DEFAULTS = { volume: 100, batterySaver: false, keyboardLayout: 'wasd', gamepadConfirm: 'south', vibration: true };
|
|
959
|
+
let chosen = {}; // ONLY what the player picked in your settings screen
|
|
960
|
+
|
|
961
|
+
// The player's choice, then the Arcade's recommendation, then your own default.
|
|
962
|
+
const setting = (key) => chosen[key] ?? gem.prefs?.recommended?.[key] ?? DEFAULTS[key];
|
|
963
|
+
|
|
964
|
+
const gem = createGem({
|
|
965
|
+
// The recommendation changed: apply it again. What the player chose does not move.
|
|
966
|
+
onPrefs: () => applySettings(setting),
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
chosen = (await gem.deviceSettings.wait(1_500)) ?? {};
|
|
970
|
+
applySettings(setting);
|
|
971
|
+
|
|
972
|
+
// The player moved YOUR slider: that one setting is now theirs.
|
|
973
|
+
volumeSlider.oninput = () => {
|
|
974
|
+
chosen = { ...chosen, v: 1, volume: volumeSlider.valueAsNumber };
|
|
975
|
+
gem.deviceSettings.put(chosen); // debounce it
|
|
976
|
+
applySettings(setting);
|
|
977
|
+
};
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
[Device settings](#device-settings-what-this-device-remembers-about-your-game) above is where the
|
|
981
|
+
player's choices are kept between sessions.
|
|
982
|
+
|
|
983
|
+
**Read `muted` and `reducedMotion` as before.** They are not recommendations and they did not move:
|
|
984
|
+
`muted` is the Arcade's mute, which the SDK applies for you
|
|
985
|
+
([sound](#sound-your-own-mute-control-and-the-arcades)), and `reducedMotion` is the player's
|
|
986
|
+
accessibility setting, which your game honours.
|
|
987
|
+
|
|
988
|
+
## The Arcade's menu
|
|
989
|
+
|
|
990
|
+
The Arcade keeps one menu button over every game, on every device. Its menu is how a player leaves
|
|
991
|
+
(**Return to the Arcade**), mutes, goes fullscreen, reports a problem and changes their Arcade-wide
|
|
992
|
+
settings — the ones that reach you as [`gem.prefs.recommended`](#game-settings). You can add up to
|
|
993
|
+
four entries of your own:
|
|
911
994
|
|
|
912
995
|
```js
|
|
913
|
-
|
|
914
|
-
onSettings: () => settingsScreen.open(),
|
|
996
|
+
gem.setMenu({
|
|
915
997
|
items: [{ id: 'how-to-play', label: t('How to play'), onSelect: () => help.open() }],
|
|
916
998
|
});
|
|
917
999
|
```
|
|
918
1000
|
|
|
919
|
-
-
|
|
920
|
-
|
|
921
|
-
|
|
1001
|
+
- **The Arcade no longer shows a "Game settings" row for `onSettings`.** Your settings open from
|
|
1002
|
+
[your own button](#game-settings). `gem.setMenu({ onSettings })` is still accepted, so a game that
|
|
1003
|
+
passes it keeps working, and another embedder may still show an entry for it — but in the Arcade
|
|
1004
|
+
it adds nothing.
|
|
922
1005
|
- **`items` are up to four entries of your own**, shown in a group below the Arcade's. `id` is your
|
|
923
1006
|
name for the entry (1 to 32 characters); `label` is what the player reads, **already in their
|
|
924
1007
|
language** (`gem.prefs.locale`) — the Arcade does not translate it, cuts it at 40 characters, and
|
|
@@ -934,14 +1017,9 @@ const inArcadeMenu = gem.setMenu({
|
|
|
934
1017
|
session, with `onWillTerminate` first. Either way your game heard `onPause(true)` when the menu
|
|
935
1018
|
opened: save there — it is the edge you are sure to get.
|
|
936
1019
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
```js
|
|
942
|
-
await gem.awaitBridge();
|
|
943
|
-
ownSettingsButton.hidden = gem.setMenu({ onSettings: () => settingsScreen.open() });
|
|
944
|
-
```
|
|
1020
|
+
`gem.setMenu` returns `false` under an embedder with no menu, and outside any embedder. Your
|
|
1021
|
+
settings button is there either way; what such a game also needs is a way out of its own, a control
|
|
1022
|
+
that calls `gem.exit()`.
|
|
945
1023
|
|
|
946
1024
|
Your own **back or home** button — the one that moves around inside your game — goes **top-left**,
|
|
947
1025
|
which the Arcade [leaves clear](#the-arcades-own-controls-reserved-areas). It never leaves the game:
|
package/package.json
CHANGED
package/schema/bridge.v1.json
CHANGED
|
@@ -763,6 +763,67 @@
|
|
|
763
763
|
"because removing it is a `v` bump and an embedder may legitimately send",
|
|
764
764
|
"one."
|
|
765
765
|
]
|
|
766
|
+
},
|
|
767
|
+
"recommended": {
|
|
768
|
+
"kind": "object",
|
|
769
|
+
"optional": true,
|
|
770
|
+
"$comment": [
|
|
771
|
+
"The player's ARCADE-WIDE DEFAULTS for this device, which the Arcade",
|
|
772
|
+
"keeps in its own Settings: a starting volume, battery saver, a keyboard",
|
|
773
|
+
"layout, the gamepad's confirm button, and vibration. RECOMMENDATIONS",
|
|
774
|
+
"ONLY. The game's own saved settings win: a game starts a setting from",
|
|
775
|
+
"here only while the player has made no choice of their own in that",
|
|
776
|
+
"game, and never overwrites one the player made because this changed.",
|
|
777
|
+
"A game may ignore it entirely.",
|
|
778
|
+
"",
|
|
779
|
+
"OPTIONAL because it is additive: a parent that predates it sends the",
|
|
780
|
+
"fields above without it, and the SDK reports `null`. All five fields",
|
|
781
|
+
"come together or the object is absent -- a parent that keeps only some",
|
|
782
|
+
"of them fills the rest from the defaults below rather than sending a",
|
|
783
|
+
"partial object.",
|
|
784
|
+
"",
|
|
785
|
+
"`muted`, `reducedMotion` and `showFrameStats` stay where they are, at",
|
|
786
|
+
"the top of this message, and keep their meaning: they are the Arcade's",
|
|
787
|
+
"own state the frame acts on, not a recommendation. The Arcade starts",
|
|
788
|
+
"them from the same per-device defaults.",
|
|
789
|
+
"",
|
|
790
|
+
"A DISPLAY PREFERENCE OF THE DEVICE, like `showFrameStats`: the Arcade",
|
|
791
|
+
"keeps one copy per device in the player's account (a guest's on the",
|
|
792
|
+
"device only), so a phone and a television recommend different things.",
|
|
793
|
+
"Sent in the first push after connecting and again on change.",
|
|
794
|
+
"",
|
|
795
|
+
"WIDENING AN ENUM HERE IS NOT FREE. The generated validator refuses the",
|
|
796
|
+
"WHOLE message on any field it rejects, so a value an older frame does",
|
|
797
|
+
"not know would cost it `muted` and `reducedMotion` as well. A new",
|
|
798
|
+
"keyboard layout or confirm button needs a capability the frame",
|
|
799
|
+
"advertises before a parent may send it."
|
|
800
|
+
],
|
|
801
|
+
"fields": {
|
|
802
|
+
"volume": {
|
|
803
|
+
"kind": "int",
|
|
804
|
+
"min": 0,
|
|
805
|
+
"max": 100,
|
|
806
|
+
"$comment": "Master volume as a percentage, 0..100. Default 100. A level, not a mute: `muted` above is the mute."
|
|
807
|
+
},
|
|
808
|
+
"batterySaver": {
|
|
809
|
+
"kind": "boolean",
|
|
810
|
+
"$comment": "The player asked games to spend less power: a lower frame rate, fewer effects, a lower resolution. Default false."
|
|
811
|
+
},
|
|
812
|
+
"keyboardLayout": {
|
|
813
|
+
"kind": "enum",
|
|
814
|
+
"values": ["wasd", "arrows"],
|
|
815
|
+
"$comment": "Which keys move by default: WASD or the arrow keys. Default `wasd`."
|
|
816
|
+
},
|
|
817
|
+
"gamepadConfirm": {
|
|
818
|
+
"kind": "enum",
|
|
819
|
+
"values": ["south", "east"],
|
|
820
|
+
"$comment": "Which face button confirms, by position: `south` (the bottom button, A on an Xbox pad) or `east` (the right button, B on an Xbox pad); the other one goes back. Default `south`."
|
|
821
|
+
},
|
|
822
|
+
"vibration": {
|
|
823
|
+
"kind": "boolean",
|
|
824
|
+
"$comment": "Whether the player wants vibration and controller rumble. Default true."
|
|
825
|
+
}
|
|
826
|
+
}
|
|
766
827
|
}
|
|
767
828
|
},
|
|
768
829
|
"rateLimit": { "perMinute": 60 }
|
|
@@ -2422,11 +2483,12 @@
|
|
|
2422
2483
|
"transport": "port",
|
|
2423
2484
|
"$comment": [
|
|
2424
2485
|
"What the game adds to the arcade's own menu — the one control the arcade",
|
|
2425
|
-
"keeps over a game, in
|
|
2426
|
-
"leaves, mutes
|
|
2486
|
+
"keeps over a game, in a corner `gem.viewport.reserved` names, which is how",
|
|
2487
|
+
"a player leaves, mutes and reports a problem.",
|
|
2427
2488
|
"",
|
|
2428
2489
|
" settings: true the game has a settings screen and will open it when",
|
|
2429
|
-
" told to;
|
|
2490
|
+
" told to; a parent MAY show a Settings entry for it",
|
|
2491
|
+
" (the Gem Arcade no longer does: see below)",
|
|
2430
2492
|
" items up to four entries of the game's own, in this order,",
|
|
2431
2493
|
" below the arcade's entries and visibly apart from them",
|
|
2432
2494
|
"",
|
|
@@ -2454,8 +2516,15 @@
|
|
|
2454
2516
|
"entry does nothing but send `gem.menuSelect`.",
|
|
2455
2517
|
"",
|
|
2456
2518
|
"Gated by `gameMenu`: sent only to a parent that advertised it. To any",
|
|
2457
|
-
"other, `gem.setMenu` returns false
|
|
2458
|
-
"
|
|
2519
|
+
"other, `gem.setMenu` returns false.",
|
|
2520
|
+
"",
|
|
2521
|
+
"THE GEM ARCADE NO LONGER SHOWS A SETTINGS ENTRY FOR `settings: true`.",
|
|
2522
|
+
"A game draws its own settings button (top-right is the recommendation)",
|
|
2523
|
+
"and the Arcade's menu holds the player's Arcade-wide settings instead,",
|
|
2524
|
+
"which reach the game as `gem.prefs.recommended`. The field STAYS: removing",
|
|
2525
|
+
"it is a `v` bump, every game that sends it keeps working, and another",
|
|
2526
|
+
"embedder may still show the entry. A parent that ignores it sends no",
|
|
2527
|
+
"`gem.menuSelect` for it."
|
|
2459
2528
|
],
|
|
2460
2529
|
"fields": {
|
|
2461
2530
|
"settings": { "kind": "boolean" },
|