@indiegems/gem-web-sdk 13.4.2 → 14.0.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 +19 -0
- package/dist/game.d.ts +66 -1
- package/dist/gem-web-sdk.bridge.js +2 -2
- package/dist/gem-web-sdk.esm.js +3 -3
- package/dist/gem-web-sdk.function.js +3 -3
- package/dist/gem-web-sdk.game.js +3 -3
- package/dist/gem-web-sdk.min.js +3 -3
- package/dist/gem-web-sdk.sdk.js +3 -3
- package/dist/gem-web-sdk.server.js +2 -2
- package/dist/index.d.ts +38 -4
- package/dist/sdk.d.ts +66 -1
- package/docs/api/api-reference-function.md +1 -1
- package/docs/api/api-reference-game.md +530 -12
- package/docs/api/api-reference-server.md +1 -1
- package/docs/api/reference.json +683 -2
- package/docs/changelog.md +19 -0
- package/docs/getting-started.md +9 -15
- package/docs/manifest.json +8 -8
- package/docs/server-bundles.md +2 -2
- package/docs/what-a-game-must-do.md +122 -27
- package/package.json +1 -1
- package/schema/bridge.v1.json +201 -2
- package/schema/capabilities.public.json +7 -0
package/docs/changelog.md
CHANGED
|
@@ -16,6 +16,25 @@ 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
|
+
## 14.0.0
|
|
20
|
+
|
|
21
|
+
Upgrading from 13.4.3. Impact: required.
|
|
22
|
+
|
|
23
|
+
### Rebuild
|
|
24
|
+
|
|
25
|
+
- The message name lists, the frame and parent message unions, and the capability union each gained new members for game settings (the gameSettings capability among them). Recompiling is enough for most games. If your code switches exhaustively over message kinds or capability names, add handling for the new members.
|
|
26
|
+
|
|
27
|
+
### New capability
|
|
28
|
+
|
|
29
|
+
- A game can now mirror up to eight of its own settings into the Arcade's in-game menu using Gem.settings. A setting is a toggle, a slider or a choice. The Arcade shows up to two game toggles in the quick row next to its own sound and vibration controls, and lists the game's settings at the top of its settings screen. The game receives a change whenever the player alters one of them, but only for a setting it declared and only with a value of that setting's kind. Slider values are clamped and stepped. Keep drawing your own settings button and screen, because the declaration only mirrors a few settings. Do not declare the Arcade's own settings such as master volume, mute, vibration, graphics or controls.
|
|
30
|
+
- A game that talks to the Arcade through the frame object directly can now declare its settings with GemFrame.declareSettings and receive player changes through the onSettingChange option or callback. The declaration is held until the connection is ready and re-sent after each handshake. Updates are paced to about two per second.
|
|
31
|
+
|
|
32
|
+
## 13.4.3
|
|
33
|
+
|
|
34
|
+
Upgrading from 13.4.2. Impact: none.
|
|
35
|
+
|
|
36
|
+
Nothing in this release asks anything of a game.
|
|
37
|
+
|
|
19
38
|
## 13.4.2
|
|
20
39
|
|
|
21
40
|
Upgrading from 13.4.1. Impact: none.
|
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": "
|
|
53
|
+
"@indiegems/gem-web-sdk": "14.0.0"
|
|
54
54
|
}
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -149,19 +149,12 @@ reports. A `clientVersion` option still exists and is deprecated; see
|
|
|
149
149
|
[what a game must do](what-a-game-must-do.md#2-your-version-number-which-you-no-longer-declare)
|
|
150
150
|
for when an older arcade still needs it.
|
|
151
151
|
|
|
152
|
-
**
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
Players can attach a screenshot to a bug report, and without that flag the browser may clear
|
|
159
|
-
your drawing buffer before the platform can read it. The SDK detects that and refuses rather
|
|
160
|
-
than sending your team **a fully black image with no error attached** — but the cost is that
|
|
161
|
-
your reports arrive with no picture. Two-dimensional canvas games need nothing here.
|
|
162
|
-
[The full section](what-a-game-must-do.md#bug-reports-players-send-you-and-the-one-webgl-flag-that-decides-them)
|
|
163
|
-
covers overriding the capture and attaching your own debug state, which is the part actually
|
|
164
|
-
worth spending effort on.
|
|
152
|
+
**Screenshots need nothing from you**, in 2D, WebGL or WebGPU, and that includes
|
|
153
|
+
`preserveDrawingBuffer`, which you should leave off. Players can attach a screenshot to a bug
|
|
154
|
+
report, and the SDK reads your canvas right after a frame you draw, so call `createGem` before you
|
|
155
|
+
create your renderer and draw inside `requestAnimationFrame`, as every engine does.
|
|
156
|
+
[The full section](what-a-game-must-do.md#bug-reports-players-send-you) covers overriding the
|
|
157
|
+
capture and attaching your own debug state, which is the part actually worth spending effort on.
|
|
165
158
|
|
|
166
159
|
**There is no offline mode.** A game must be framed by a page that speaks the bridge: opened
|
|
167
160
|
at the top level, `createGem` throws `GemUnframedError`, which says how to run it. To run framed
|
|
@@ -185,7 +178,8 @@ And draw a settings button of your own — top-right, with your Back button just
|
|
|
185
178
|
([the top of the screen](what-a-game-must-do.md#the-top-of-the-screen)) — whose values start from
|
|
186
179
|
the player's Arcade-wide defaults, `gem.prefs.recommended`, until the player chooses their own
|
|
187
180
|
([what a game must do](what-a-game-must-do.md#game-settings)). The Arcade's menu does not open
|
|
188
|
-
your settings
|
|
181
|
+
your settings screen; you may also mirror a few of your settings into it with
|
|
182
|
+
`gem.settings.declare`, but never instead of your own button. Outside the Arcade there is no Arcade menu at all, so also give players a way out — a
|
|
189
183
|
control that calls `gem.exit()`. A game a player cannot leave is one they leave by closing the tab.
|
|
190
184
|
|
|
191
185
|
## 6. Play it
|
package/docs/manifest.json
CHANGED
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
"file": "changelog.md",
|
|
41
|
-
"sha256": "
|
|
42
|
-
"updated": "2026-10-
|
|
41
|
+
"sha256": "3ccaf70d80a6164736920b4df6f817d600b2a80287d4a14783f72fbb470863f6",
|
|
42
|
+
"updated": "2026-10-21T13:51:48Z"
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
"file": "daily-challenges.md",
|
|
@@ -68,8 +68,8 @@
|
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
"file": "getting-started.md",
|
|
71
|
-
"sha256": "
|
|
72
|
-
"updated": "2026-10-
|
|
71
|
+
"sha256": "dbda9199712f4112bfe316b7af6e63a11fcce6842e778672459e1532947cf21f",
|
|
72
|
+
"updated": "2026-10-21T13:51:48Z"
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
"file": "inventory-and-catalogs.md",
|
|
@@ -128,8 +128,8 @@
|
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
130
|
"file": "server-bundles.md",
|
|
131
|
-
"sha256": "
|
|
132
|
-
"updated": "2026-10-
|
|
131
|
+
"sha256": "2312a25bb11ce29f50b97b0737c708b31f6e0290f99fe2520179a9dbac67fb8c",
|
|
132
|
+
"updated": "2026-10-21T13:51:48Z"
|
|
133
133
|
},
|
|
134
134
|
{
|
|
135
135
|
"file": "serverless-functions-js.md",
|
|
@@ -153,8 +153,8 @@
|
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
"file": "what-a-game-must-do.md",
|
|
156
|
-
"sha256": "
|
|
157
|
-
"updated": "2026-10-
|
|
156
|
+
"sha256": "3a9c0e307b78cfb8b959c7b47a1ad0349699be944903a24834ee785c3823b402",
|
|
157
|
+
"updated": "2026-10-21T13:31:36Z"
|
|
158
158
|
}
|
|
159
159
|
]
|
|
160
160
|
}
|
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/
|
|
84
|
-
curl -fsSL https://downloads.gemarcade.com/sdk/
|
|
83
|
+
curl -fsSL -O https://downloads.gemarcade.com/sdk/v14.0.0/gem-web-sdk.tgz
|
|
84
|
+
curl -fsSL https://downloads.gemarcade.com/sdk/v14.0.0/gem-web-sdk.tgz.sri
|
|
85
85
|
openssl dgst -sha384 -binary gem-web-sdk.tgz | openssl base64 -A
|
|
86
86
|
```
|
|
87
87
|
|
|
@@ -1016,10 +1016,13 @@ This is the whole model, and every other page that mentions settings links here:
|
|
|
1016
1016
|
|
|
1017
1017
|
- **The Arcade's settings are the player's Arcade-wide defaults for this device.** Your game reads
|
|
1018
1018
|
them from `gem.prefs.recommended` (and `muted` and `reducedMotion` from `gem.prefs`) and applies
|
|
1019
|
-
them again on `onPrefs`. The Arcade never asks your game anything about them
|
|
1020
|
-
anything your game says.
|
|
1019
|
+
them again on `onPrefs`. The Arcade never asks your game anything about them.
|
|
1021
1020
|
- **Every game draws its own settings button, top-right ([the top of the
|
|
1022
1021
|
screen](#the-top-of-the-screen)), and its own settings screen.** The Arcade's menu never opens it.
|
|
1022
|
+
- **A game may also mirror up to eight of its own settings into the Arcade's menu**, with
|
|
1023
|
+
`gem.settings.declare` — a second way to reach a few of them, never the only one, and never a
|
|
1024
|
+
setting the Arcade already has ([mirror a few settings into the Arcade's
|
|
1025
|
+
menu](#mirror-a-few-settings-into-the-arcades-menu-gemsettings)).
|
|
1023
1026
|
- **A setting starts from `recommended` until the player changes it in your game, and then the
|
|
1024
1027
|
player's choice in your game wins.** Save only what the player chose, never a recommended value.
|
|
1025
1028
|
- **Where a choice is kept.** Device configuration — volume, graphics quality, look sensitivity, a
|
|
@@ -1135,6 +1138,90 @@ owns — starts from `recommended` the same way and is kept in `gem.me.settings`
|
|
|
1135
1138
|
([sound](#sound-your-own-mute-control-and-the-arcades)), and `reducedMotion` is the player's
|
|
1136
1139
|
accessibility setting, which your game honours.
|
|
1137
1140
|
|
|
1141
|
+
### Mirror a few settings into the Arcade's menu: `gem.settings`
|
|
1142
|
+
|
|
1143
|
+
Optional. The Arcade's Gem menu can show a few of your game's own settings beside the player's
|
|
1144
|
+
Arcade-wide ones: in its Settings screen, in a group named for your game above the player's
|
|
1145
|
+
defaults for all games, and — for up to two toggles — in the menu's quick row, next to the
|
|
1146
|
+
Arcade's own Sound and Vibration buttons. A player who opens the menu mid-game can turn your music
|
|
1147
|
+
off without hunting for your settings screen.
|
|
1148
|
+
|
|
1149
|
+
**It is a mirror, never a replacement: keep your own settings button and screen**, with every
|
|
1150
|
+
setting you declare on it too. An embedder that draws no game settings shows none of this, and the
|
|
1151
|
+
Arcade may leave an entry out when it is short of room — your own screen is what remains. A game
|
|
1152
|
+
that dropped its button because the Arcade's menu had its settings would have settings nobody can
|
|
1153
|
+
reach.
|
|
1154
|
+
|
|
1155
|
+
```js
|
|
1156
|
+
// Built from the same state your own settings screen shows.
|
|
1157
|
+
const declared = () => [
|
|
1158
|
+
{ id: 'music', label: t('Music'), kind: 'toggle', value: music.on, quick: true, icon: 'music' },
|
|
1159
|
+
{ id: 'sfx', label: t('Sound effects'), kind: 'toggle', value: sfx.on, quick: true, icon: 'sfx' },
|
|
1160
|
+
{ id: 'colors', label: t('Color help'), kind: 'toggle', value: colorHelp, icon: 'eye' },
|
|
1161
|
+
{ id: 'speed', label: t('Game speed'), kind: 'slider', value: speed, min: 0.5, max: 2, step: 0.25 },
|
|
1162
|
+
{
|
|
1163
|
+
id: 'difficulty',
|
|
1164
|
+
label: t('Difficulty'),
|
|
1165
|
+
kind: 'choice',
|
|
1166
|
+
value: difficulty,
|
|
1167
|
+
options: [
|
|
1168
|
+
{ id: 'easy', label: t('Easy') },
|
|
1169
|
+
{ id: 'normal', label: t('Normal') },
|
|
1170
|
+
{ id: 'hard', label: t('Hard') },
|
|
1171
|
+
],
|
|
1172
|
+
},
|
|
1173
|
+
];
|
|
1174
|
+
|
|
1175
|
+
gem.settings.declare(declared());
|
|
1176
|
+
|
|
1177
|
+
// The player changed one in the Arcade's menu. Your game is usually paused: apply it anyway.
|
|
1178
|
+
gem.settings.onChange(({ id, value }) => {
|
|
1179
|
+
if (id === 'music') setMusic(value === true);
|
|
1180
|
+
if (id === 'sfx') setSfx(value === true);
|
|
1181
|
+
if (id === 'colors') setColorHelp(value === true);
|
|
1182
|
+
if (id === 'speed') setSpeed(value);
|
|
1183
|
+
if (id === 'difficulty') setDifficulty(value);
|
|
1184
|
+
keepChoice(id, value); // where your own screen keeps the same choice
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
// Your own screen changed one: declare again, so the menu never shows a stale value.
|
|
1188
|
+
musicToggle.onchange = () => {
|
|
1189
|
+
setMusic(musicToggle.checked);
|
|
1190
|
+
keepChoice('music', musicToggle.checked);
|
|
1191
|
+
gem.settings.declare(declared());
|
|
1192
|
+
};
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
- **Three kinds, up to eight settings, shown in your order.** A `toggle`'s `value` is `true` or
|
|
1196
|
+
`false`; a `slider`'s is a number from `min` to `max` on `step` (defaults `0`, `100`, `1`); a
|
|
1197
|
+
`choice` has two to four `options` and its `value` is one of their ids. `id` is your own name
|
|
1198
|
+
for the setting, 1 to 40 of `A-Z a-z 0-9 _ -`, unique. An entry that breaks a rule is left out
|
|
1199
|
+
with a warning on the console, and nothing throws.
|
|
1200
|
+
- **Mark at most two toggles `quick`** — the ones a player reaches for mid-game: Music, Sound
|
|
1201
|
+
effects, Color help. A third that asks is shown only in the list. `icon` is optional: `music`,
|
|
1202
|
+
`sfx`, `voice`, `eye`, `text`, `hint`, `speed`, `bright`, `controls` or `star`.
|
|
1203
|
+
- **Labels are already in the player's language** (`gem.prefs.locale`): the Arcade does not
|
|
1204
|
+
translate them, and cuts them at 40 characters. Declare again when the language changes.
|
|
1205
|
+
- **Never declare a setting the Arcade already has.** Master volume and mute, vibration, graphics
|
|
1206
|
+
quality and the controls defaults (look sensitivity, invert, touch stick, keyboard layout,
|
|
1207
|
+
confirm button) are in the Arcade's own settings and reach you as `gem.prefs` and
|
|
1208
|
+
`gem.prefs.recommended`. A "Vibration", "Volume" or "Mute" of yours beside them is a second
|
|
1209
|
+
switch for the same thing, and the player cannot tell which one wins. Your own music and
|
|
1210
|
+
sound-effect levels are yours to declare.
|
|
1211
|
+
- **It is screen state, not a toggle.** Each call is the whole list and replaces the last;
|
|
1212
|
+
`gem.settings.declare([])` takes them all back out. **Declare again whenever your own screen
|
|
1213
|
+
changes a value**, so the menu never shows a stale one. Before the Arcade connects the list is
|
|
1214
|
+
held and sent when it does, and again after any reconnect; a burst — a slider being dragged — is
|
|
1215
|
+
sent at most twice a second, always ending on the last value.
|
|
1216
|
+
- **Apply a change at once, even while paused.** The Arcade's menu holds your game paused
|
|
1217
|
+
(`onPause(true)`) while it is open, and a player who turns Music off expects the music to stop
|
|
1218
|
+
while the menu is still open. The change is the player's own choice, exactly as if they had made
|
|
1219
|
+
it on your screen: keep it where you keep that setting (device settings or `gem.me.settings`,
|
|
1220
|
+
[above](#game-settings)). The SDK has already written it into your declaration, so you do not
|
|
1221
|
+
need to declare again for it.
|
|
1222
|
+
- **`declare` returns `false`** before the Arcade connects (the list is held) and under an
|
|
1223
|
+
embedder that draws no game settings. Do nothing differently: your own screen has them all.
|
|
1224
|
+
|
|
1138
1225
|
## Camera and look controls
|
|
1139
1226
|
|
|
1140
1227
|
If your game turns a camera — a drag on a phone, a mouse under pointer lock, a gamepad's right
|
|
@@ -1289,8 +1376,10 @@ gem.setMenu({
|
|
|
1289
1376
|
});
|
|
1290
1377
|
```
|
|
1291
1378
|
|
|
1292
|
-
- **Your settings
|
|
1293
|
-
Arcade's menu never opens
|
|
1379
|
+
- **Your settings screen is not in it.** It opens from [your own button](#game-settings), and the
|
|
1380
|
+
Arcade's menu never opens it. A few of your settings can also appear in the Arcade's menu, with
|
|
1381
|
+
[`gem.settings`](#mirror-a-few-settings-into-the-arcades-menu-gemsettings) — a mirror of your
|
|
1382
|
+
screen, never a replacement for it.
|
|
1294
1383
|
- **`items` are up to four entries of your own**, shown in a group below the Arcade's. `id` is your
|
|
1295
1384
|
name for the entry (1 to 32 characters); `label` is what the player reads, **already in their
|
|
1296
1385
|
language** (`gem.prefs.locale`) — the Arcade does not translate it, cuts it at 40 characters, and
|
|
@@ -1951,34 +2040,40 @@ advice to a client you do not trust, so whatever it decides it must also carry o
|
|
|
1951
2040
|
spending currency or granting an item is a recipe, which the platform already runs where a client
|
|
1952
2041
|
cannot change it. [`serverless-functions.md`](serverless-functions.md) is the page.
|
|
1953
2042
|
|
|
1954
|
-
## Bug reports players send you
|
|
2043
|
+
## Bug reports players send you
|
|
1955
2044
|
|
|
1956
2045
|
The arcade has a **Feedback** control. A player taps it, the platform asks your game for a picture
|
|
1957
2046
|
of itself, the player draws on it and writes what happened, and it arrives on your game's feedback
|
|
1958
2047
|
list. You get this for nothing — there is no option to set.
|
|
1959
2048
|
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
`
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
2049
|
+
### WebGL and WebGPU games: leave `preserveDrawingBuffer` off
|
|
2050
|
+
|
|
2051
|
+
**You do not need `preserveDrawingBuffer: true` for screenshots, and should not set it.** With it
|
|
2052
|
+
on, the browser copies the whole drawing buffer every frame instead of swapping it (measured at
|
|
2053
|
+
about 0.4 ms of GPU time a frame on a 2021 Android flagship) to keep pixels readable that the SDK
|
|
2054
|
+
only needs once, when a player files a report.
|
|
2055
|
+
|
|
2056
|
+
A WebGL canvas without the flag, and every WebGPU canvas, can be read only right after a frame is
|
|
2057
|
+
drawn: read it later and you get **a fully black image and no error**. So the SDK reads it there.
|
|
2058
|
+
It watches `requestAnimationFrame` and, when a capture is asked for, reads your canvas right after
|
|
2059
|
+
your next frame. When your game has stopped asking for frames, because nothing on screen is moving,
|
|
2060
|
+
it uses the picture it kept from the last frame you drew before you stopped. Every read is checked,
|
|
2061
|
+
and a blank one is never sent.
|
|
2062
|
+
|
|
2063
|
+
Two things keep that working, and an ordinary game does both already:
|
|
2064
|
+
|
|
2065
|
+
- **Call `createGem` before you create your renderer.** The SDK records each context as it is made,
|
|
2066
|
+
so that it never has to ask a canvas for one. Asking would create a context on a canvas you had
|
|
2067
|
+
not started yet, and take it from you.
|
|
2068
|
+
- **Draw inside `requestAnimationFrame`** (three.js's `setAnimationLoop`, Phaser, Babylon.js and
|
|
2069
|
+
PixiJS all do), **from a loop that stops by not asking for the next frame.** A frame drawn from
|
|
2070
|
+
an input handler or a timer has no moment the SDK can read it in, and a loop stopped with
|
|
2071
|
+
`cancelAnimationFrame` from outside a frame leaves no last frame to keep.
|
|
2072
|
+
|
|
2073
|
+
When neither read produced a picture, the report says so in its diagnostics: `unsupported` for
|
|
2074
|
+
WebGL (`preserveDrawingBuffer: true` would fix it, at the cost above), `unreadable` for WebGPU (only
|
|
2075
|
+
`onScreenshot` can). 2D canvas games need nothing. `OffscreenCanvas` games need `onScreenshot`,
|
|
2076
|
+
below.
|
|
1982
2077
|
|
|
1983
2078
|
### When the default capture is not right
|
|
1984
2079
|
|
package/package.json
CHANGED
package/schema/bridge.v1.json
CHANGED
|
@@ -2155,8 +2155,8 @@
|
|
|
2155
2155
|
"$comment": [
|
|
2156
2156
|
"`not_wired` -- the capability was advertised but nothing implements it.",
|
|
2157
2157
|
"`no_source` -- the default strategy found no visible canvas, or one of zero area.",
|
|
2158
|
-
"`unsupported` -- the WebGL context has `preserveDrawingBuffer: false
|
|
2159
|
-
"`unreadable` -- this page cannot read that canvas
|
|
2158
|
+
"`unsupported` -- the WebGL context has `preserveDrawingBuffer: false` and no read produced a picture (no frame was drawn while the capture waited, and none was kept when the loop last stopped), or the context was lost. THE ONE THE FLAG FIXES, and it is kept narrow on purpose: it used to also cover the case below, so a developer whose canvas simply cannot be read was told to set a flag that would change nothing.",
|
|
2159
|
+
"`unreadable` -- this page cannot read that canvas: control was transferred to an OffscreenCanvas, or a WebGPU canvas produced no picture in the frame or at rest. No flag fixes it; the game must supply `onScreenshot`.",
|
|
2160
2160
|
"`tainted` -- the canvas origin-clean flag is false. THE ONE A DEVELOPER CAN ACT ON (add `crossorigin` to the offending image), so it must stay distinguishable from `failed`.",
|
|
2161
2161
|
"`too_large` -- the source exceeds the requested caps and cannot be downscaled.",
|
|
2162
2162
|
"`busy` -- a capture is already in flight. Single-flight is enforced by the frame.",
|
|
@@ -3076,6 +3076,205 @@
|
|
|
3076
3076
|
"rateLimit": { "perMinute": 12 }
|
|
3077
3077
|
},
|
|
3078
3078
|
|
|
3079
|
+
{
|
|
3080
|
+
"type": "gem.settingsDeclare",
|
|
3081
|
+
"direction": "frameToParent",
|
|
3082
|
+
"transport": "port",
|
|
3083
|
+
"$comment": [
|
|
3084
|
+
"A few of the game's OWN settings, mirrored into the arcade's menu - in",
|
|
3085
|
+
"the Gem Arcade, a group headed with the game's name in its Settings",
|
|
3086
|
+
"screen (\"only this game\"), above the player's Arcade-wide defaults,",
|
|
3087
|
+
"and up to two toggles in the menu's quick row beside the arcade's own",
|
|
3088
|
+
"Sound and Vibration (owner decision 2026-10-21).",
|
|
3089
|
+
"",
|
|
3090
|
+
"A MIRROR, NEVER A REPLACEMENT. A game that declares settings still draws",
|
|
3091
|
+
"its own settings button and the screen it opens, and every one of these",
|
|
3092
|
+
"settings is on that screen too. That is the answer to why the earlier",
|
|
3093
|
+
"game-to-arcade settings channels (`gem.controlsDeclare`, `gem.menu`'s",
|
|
3094
|
+
"`settings` flag) were withdrawn: games hid their own button, and their",
|
|
3095
|
+
"settings became unreachable wherever the arcade showed nothing. A parent may",
|
|
3096
|
+
"draw none of this - any embedder that did not advertise `gameSettings`,",
|
|
3097
|
+
"a parent short of room - and the game's own screen is what remains.",
|
|
3098
|
+
"",
|
|
3099
|
+
" settings 0..8 entries, in the order the game wants them shown;",
|
|
3100
|
+
" empty means the game shows none",
|
|
3101
|
+
"",
|
|
3102
|
+
"Each entry is one of three kinds:",
|
|
3103
|
+
"",
|
|
3104
|
+
" toggle `value` a boolean",
|
|
3105
|
+
" slider `value` a number in [min, max], on `step` from `min`;",
|
|
3106
|
+
" `min`, `max` and `step` default to 0, 100 and 1",
|
|
3107
|
+
" choice `value` one of `options`' ids; 2..4 options",
|
|
3108
|
+
"",
|
|
3109
|
+
"`id` is the game's own name for the setting, 1..40 characters of",
|
|
3110
|
+
"A-Z a-z 0-9 _ -, and is what comes back in `gem.settingChange`;",
|
|
3111
|
+
"unique within one message, and the parent keeps the first of a",
|
|
3112
|
+
"repeated id. `label` (and each option's) is what the player reads,",
|
|
3113
|
+
"1..40 characters, ALREADY IN THE PLAYER'S LANGUAGE (`gem.prefs.locale`):",
|
|
3114
|
+
"the parent does not translate it. A parent drops an entry whose id,",
|
|
3115
|
+
"kind, value or options break these rules rather than guessing; the SDK",
|
|
3116
|
+
"never sends one.",
|
|
3117
|
+
"",
|
|
3118
|
+
"`quick` asks for a toggle in the menu's quick row. A parent honours it",
|
|
3119
|
+
"on the first two toggles that ask, in order, and ignores it on any other",
|
|
3120
|
+
"kind. `icon` is a CODE, not an enum: one of music, sfx, voice, eye,",
|
|
3121
|
+
"text, hint, speed, bright, controls, star - and a parent draws its",
|
|
3122
|
+
"generic glyph for one it does not know, so the vocabulary can grow",
|
|
3123
|
+
"without an older parent refusing the message. The SDK drops a code it",
|
|
3124
|
+
"does not know before sending.",
|
|
3125
|
+
"",
|
|
3126
|
+
"NOT THE ARCADE'S OWN SETTINGS. Master volume and mute, vibration,",
|
|
3127
|
+
"graphics and the controls defaults are the arcade's, reaching the game",
|
|
3128
|
+
"as `gem.prefs` and `gem.prefs.recommended`, and a game declares none of",
|
|
3129
|
+
"them.",
|
|
3130
|
+
"",
|
|
3131
|
+
"A LABEL IS UNTRUSTED TEXT IN THE PARENT'S CHROME. A parent renders it as",
|
|
3132
|
+
"text and never as markup, in the game's own group and never among its",
|
|
3133
|
+
"own entries, truncated to fit, and may decline to show an entry at all.",
|
|
3134
|
+
"",
|
|
3135
|
+
"SCREEN STATE, NOT A TOGGLE, like `gem.menu`. Each message is the whole",
|
|
3136
|
+
"answer and the latest one wins; nothing accumulates. The SDK holds it",
|
|
3137
|
+
"from before the bridge is up and re-sends it after every handshake; a",
|
|
3138
|
+
"game declares again whenever its own screen changes a value, and the",
|
|
3139
|
+
"SDK writes a `gem.settingChange` into the declaration it holds, so a",
|
|
3140
|
+
"reconnect never shows a stale value. A declaration is per game",
|
|
3141
|
+
"session: the parent forgets it when the game is left.",
|
|
3142
|
+
"",
|
|
3143
|
+
"A PRESENTATION REQUEST ONLY, and not an authorization control. It",
|
|
3144
|
+
"carries no player data and changes nothing but what the parent draws;",
|
|
3145
|
+
"a change the player makes there reaches the game as",
|
|
3146
|
+
"`gem.settingChange` and does nothing else.",
|
|
3147
|
+
"",
|
|
3148
|
+
"Gated by `gameSettings`: sent only to a parent that advertised it. To",
|
|
3149
|
+
"any other, `gem.settings.declare` returns false."
|
|
3150
|
+
],
|
|
3151
|
+
"fields": {
|
|
3152
|
+
"settings": {
|
|
3153
|
+
"kind": "array",
|
|
3154
|
+
"maxItems": 8,
|
|
3155
|
+
"items": {
|
|
3156
|
+
"kind": "object",
|
|
3157
|
+
"fields": {
|
|
3158
|
+
"id": {
|
|
3159
|
+
"kind": "string",
|
|
3160
|
+
"maxLength": 40,
|
|
3161
|
+
"$comment": "The game's own name for the setting, 1..40 characters of A-Z a-z 0-9 _ -, echoed in `gem.settingChange`."
|
|
3162
|
+
},
|
|
3163
|
+
"label": {
|
|
3164
|
+
"kind": "string",
|
|
3165
|
+
"maxLength": 40,
|
|
3166
|
+
"$comment": "What the player reads, 1..40 characters, already translated. Untrusted text: rendered as text only."
|
|
3167
|
+
},
|
|
3168
|
+
"kind": {
|
|
3169
|
+
"kind": "enum",
|
|
3170
|
+
"values": ["toggle", "slider", "choice"]
|
|
3171
|
+
},
|
|
3172
|
+
"value": {
|
|
3173
|
+
"kind": "scalar",
|
|
3174
|
+
"maxLength": 40,
|
|
3175
|
+
"min": -1000000,
|
|
3176
|
+
"max": 1000000,
|
|
3177
|
+
"$comment": "The setting's current value: a boolean for a toggle, a number in [min, max] for a slider, one of `options`' ids for a choice."
|
|
3178
|
+
},
|
|
3179
|
+
"quick": {
|
|
3180
|
+
"kind": "boolean",
|
|
3181
|
+
"optional": true,
|
|
3182
|
+
"$comment": "Toggles only: ask to appear in the menu's quick row. A parent honours the first two that ask."
|
|
3183
|
+
},
|
|
3184
|
+
"icon": {
|
|
3185
|
+
"kind": "string",
|
|
3186
|
+
"maxLength": 16,
|
|
3187
|
+
"format": "code",
|
|
3188
|
+
"optional": true,
|
|
3189
|
+
"$comment": "A code: music, sfx, voice, eye, text, hint, speed, bright, controls or star. Unknown means the parent's generic glyph."
|
|
3190
|
+
},
|
|
3191
|
+
"min": {
|
|
3192
|
+
"kind": "number",
|
|
3193
|
+
"min": -1000000,
|
|
3194
|
+
"max": 1000000,
|
|
3195
|
+
"optional": true,
|
|
3196
|
+
"$comment": "Sliders only: the lowest value, 0 when absent. Below `max`."
|
|
3197
|
+
},
|
|
3198
|
+
"max": {
|
|
3199
|
+
"kind": "number",
|
|
3200
|
+
"min": -1000000,
|
|
3201
|
+
"max": 1000000,
|
|
3202
|
+
"optional": true,
|
|
3203
|
+
"$comment": "Sliders only: the highest value, 100 when absent. Above `min`."
|
|
3204
|
+
},
|
|
3205
|
+
"step": {
|
|
3206
|
+
"kind": "number",
|
|
3207
|
+
"min": 0,
|
|
3208
|
+
"max": 1000000,
|
|
3209
|
+
"optional": true,
|
|
3210
|
+
"$comment": "Sliders only: the increment from `min`, 1 when absent. Greater than 0."
|
|
3211
|
+
},
|
|
3212
|
+
"options": {
|
|
3213
|
+
"kind": "array",
|
|
3214
|
+
"maxItems": 4,
|
|
3215
|
+
"optional": true,
|
|
3216
|
+
"$comment": "Choices only: 2..4 options, ids unique, each with the same id and label rules as a setting.",
|
|
3217
|
+
"items": {
|
|
3218
|
+
"kind": "object",
|
|
3219
|
+
"fields": {
|
|
3220
|
+
"id": { "kind": "string", "maxLength": 40 },
|
|
3221
|
+
"label": { "kind": "string", "maxLength": 40 }
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
},
|
|
3229
|
+
"rateLimit": { "perMinute": 120 }
|
|
3230
|
+
},
|
|
3231
|
+
|
|
3232
|
+
{
|
|
3233
|
+
"type": "gem.settingChange",
|
|
3234
|
+
"direction": "parentToFrame",
|
|
3235
|
+
"transport": "port",
|
|
3236
|
+
"$comment": [
|
|
3237
|
+
"The player changed one of the game's declared settings in the arcade's",
|
|
3238
|
+
"own chrome: the setting with this `id`, now at `value`.",
|
|
3239
|
+
"",
|
|
3240
|
+
"Sent only for an id the frame's LATEST `gem.settingsDeclare` holds,",
|
|
3241
|
+
"with a value of that setting's kind - a boolean for a toggle, a slider's",
|
|
3242
|
+
"number clamped into [min, max] and onto its step, one of a choice's",
|
|
3243
|
+
"option ids - and only to a frame that advertised `gameSettings`. A",
|
|
3244
|
+
"parent coalesces a drag: it sends a slider's value when the player",
|
|
3245
|
+
"lets go, or at most a few times a second while they hold it.",
|
|
3246
|
+
"",
|
|
3247
|
+
"THE GAME IS USUALLY PAUSED when this arrives - the menu holds the",
|
|
3248
|
+
"`gem.pause` it opened with - and applies the change anyway, so the",
|
|
3249
|
+
"player hears the music stop while the menu is still open. A frame",
|
|
3250
|
+
"that hears an id it no longer declares ignores it: the menu may have",
|
|
3251
|
+
"been open across a change.",
|
|
3252
|
+
"",
|
|
3253
|
+
"The SDK writes the new value into the declaration it holds before the",
|
|
3254
|
+
"game's handlers run, so the next handshake re-sends what the player",
|
|
3255
|
+
"chose rather than what the game last said.",
|
|
3256
|
+
"",
|
|
3257
|
+
"A PRESENTATION EVENT, NOT AN AUTHORIZATION CONTROL. It tells the game",
|
|
3258
|
+
"what the player picked in a mirror of the game's own screen, and",
|
|
3259
|
+
"nothing else; the game keeps the choice where it keeps any other."
|
|
3260
|
+
],
|
|
3261
|
+
"fields": {
|
|
3262
|
+
"id": {
|
|
3263
|
+
"kind": "string",
|
|
3264
|
+
"maxLength": 40,
|
|
3265
|
+
"$comment": "The setting's id, from the latest `gem.settingsDeclare`."
|
|
3266
|
+
},
|
|
3267
|
+
"value": {
|
|
3268
|
+
"kind": "scalar",
|
|
3269
|
+
"maxLength": 40,
|
|
3270
|
+
"min": -1000000,
|
|
3271
|
+
"max": 1000000,
|
|
3272
|
+
"$comment": "The new value, of the setting's kind."
|
|
3273
|
+
}
|
|
3274
|
+
},
|
|
3275
|
+
"rateLimit": { "perMinute": 120 }
|
|
3276
|
+
},
|
|
3277
|
+
|
|
3079
3278
|
{
|
|
3080
3279
|
"type": "gem.requestShare",
|
|
3081
3280
|
"direction": "frameToParent",
|