@indiegems/gem-web-sdk 23.0.6 → 23.1.1
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 +26 -0
- package/dist/conformance.d.ts +2 -1
- package/dist/game.d.ts +4 -0
- package/dist/gem-web-sdk.bridge.js +1 -1
- package/dist/gem-web-sdk.conformance.js +3 -3
- package/dist/gem-web-sdk.esm.js +3 -3
- package/dist/gem-web-sdk.function.js +1 -1
- 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 +3 -1
- package/dist/sdk.d.ts +4 -0
- package/docs/api/api-reference-function.md +1 -1
- package/docs/api/api-reference-game.md +73 -1
- package/docs/api/api-reference-server.md +1 -1
- package/docs/api/reference.json +49 -1
- package/docs/changelog.md +26 -0
- package/docs/getting-started.md +1 -1
- package/docs/manifest.json +10 -10
- package/docs/rewarded-ads.md +22 -3
- package/docs/server-bundles.md +2 -2
- package/docs/what-a-game-must-do.md +123 -16
- package/package.json +1 -1
- package/schema/bridge.v1.json +40 -0
- package/schema/capabilities.public.json +8 -0
package/docs/changelog.md
CHANGED
|
@@ -16,6 +16,32 @@ 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
|
+
## 23.1.1
|
|
20
|
+
|
|
21
|
+
Upgrading from 23.1.0. Impact: none.
|
|
22
|
+
|
|
23
|
+
Nothing in this release asks anything of a game.
|
|
24
|
+
|
|
25
|
+
## 23.1.0
|
|
26
|
+
|
|
27
|
+
*Written from the API facts alone; no summary was generated for this release.*
|
|
28
|
+
|
|
29
|
+
Upgrading from 23.0.6. Impact: required.
|
|
30
|
+
|
|
31
|
+
### Must change
|
|
32
|
+
|
|
33
|
+
- PARENT_CONFORMANCE_VERSION changed its signature.
|
|
34
|
+
- GemCapability changed its signature.
|
|
35
|
+
- GemHost.sendPrefs changed its signature.
|
|
36
|
+
- GemPrefs.recommended changed its signature.
|
|
37
|
+
|
|
38
|
+
### New capability
|
|
39
|
+
|
|
40
|
+
- ParentUnderTest.setTextScale was added.
|
|
41
|
+
- Gem.textScale was added.
|
|
42
|
+
- GemOptions.onTextScale was added.
|
|
43
|
+
- GemRecommendedSettings.textScale was added.
|
|
44
|
+
|
|
19
45
|
## 23.0.6
|
|
20
46
|
|
|
21
47
|
Upgrading from 23.0.5. Impact: none.
|
package/docs/getting-started.md
CHANGED
package/docs/manifest.json
CHANGED
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
"file": "changelog.md",
|
|
51
|
-
"sha256": "
|
|
52
|
-
"updated": "2026-11-
|
|
51
|
+
"sha256": "ffe343dbeb0820cb452ac8395245fa39ae3d09e1b2252eaa3730b566f215a879",
|
|
52
|
+
"updated": "2026-11-07T17:43:13Z"
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
"file": "daily-challenges.md",
|
|
@@ -78,8 +78,8 @@
|
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
"file": "getting-started.md",
|
|
81
|
-
"sha256": "
|
|
82
|
-
"updated": "2026-11-
|
|
81
|
+
"sha256": "28ab09ea55162912ff25ae8ccb6f6cdc35e494d65ebf5e2b0ee8952ea0265a81",
|
|
82
|
+
"updated": "2026-11-07T17:43:13Z"
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
85
|
"file": "graphics-route.md",
|
|
@@ -123,8 +123,8 @@
|
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"file": "rewarded-ads.md",
|
|
126
|
-
"sha256": "
|
|
127
|
-
"updated": "2026-11-
|
|
126
|
+
"sha256": "81b421d4bde3f71b40c7a5a173e6a2c114a315215a11491e488872ff9e1909dc",
|
|
127
|
+
"updated": "2026-11-07T16:40:01Z"
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
130
|
"file": "room-shapes.md",
|
|
@@ -143,8 +143,8 @@
|
|
|
143
143
|
},
|
|
144
144
|
{
|
|
145
145
|
"file": "server-bundles.md",
|
|
146
|
-
"sha256": "
|
|
147
|
-
"updated": "2026-11-
|
|
146
|
+
"sha256": "c6b46f9b4628385858f0834dc4f44b6e737adba5805f3faefda8d4db44878b44",
|
|
147
|
+
"updated": "2026-11-07T17:43:13Z"
|
|
148
148
|
},
|
|
149
149
|
{
|
|
150
150
|
"file": "serverless-functions-js.md",
|
|
@@ -168,8 +168,8 @@
|
|
|
168
168
|
},
|
|
169
169
|
{
|
|
170
170
|
"file": "what-a-game-must-do.md",
|
|
171
|
-
"sha256": "
|
|
172
|
-
"updated": "2026-11-
|
|
171
|
+
"sha256": "f70fe9fad67c6b386bddc53f1f876bb78f6a5e11c35f565bb93e925d65a99874",
|
|
172
|
+
"updated": "2026-11-06T22:48:07Z"
|
|
173
173
|
}
|
|
174
174
|
]
|
|
175
175
|
}
|
package/docs/rewarded-ads.md
CHANGED
|
@@ -110,7 +110,23 @@ rewarded_placements:
|
|
|
110
110
|
| `cooldown_seconds` | Seconds between two grants to one player, 0 to 86400. Defaults to 0. |
|
|
111
111
|
| `max_held` | No grant while the player already holds at least this many of the granted item. 1 to 100, and only on a placement whose grants are all items. |
|
|
112
112
|
| `without_ads` | **Optional:** `grant` or `nothing`, and `nothing` when you leave it out. What a press gets when no ad plays: where no ad serves your channel at all, and where one does but no ad was available for this press or it failed to play (§8). `grant` pays the reward with no ad, within your caps, so write it out only for a reward that is fine to give free. |
|
|
113
|
-
| `allowance_currency_slug` | Optional: one of your own currencies that each grant also spends 1 of. Use it when another path in your game must share the same daily cap. It must not be `client_writable
|
|
113
|
+
| `allowance_currency_slug` | Optional: one of your own currencies that each grant also spends 1 of. Use it when another path in your game must share the same daily cap. It must not be `client_writable` and must have `transfer_policy: none`. A grant is refused while it is empty (§3). ⚠️ For an allowance your game grants per use, put it in a bucket that never resets (`reset_interval: never`) for now, even though the upload warns `rewarded_placement_allowance_no_reset` (below). |
|
|
114
|
+
|
|
115
|
+
⚠️ **Until a gem-api fix ships, an allowance in a resetting bucket refuses a player's first
|
|
116
|
+
press.** A grant that creates the player's balance of the allowance currency is not stamped with
|
|
117
|
+
the bucket's current period, so the next order on that player's inventory resets it, and the
|
|
118
|
+
press's own check is that order. A bucket that resets to `reset_amount: 0` (`daily`, say) therefore
|
|
119
|
+
empties the first grant before it can be spent, and the press answers `no_ad` with nothing granted.
|
|
120
|
+
Meanwhile:
|
|
121
|
+
|
|
122
|
+
- **An allowance your game grants per use** (one per level chest, say) needs no refill. Put its
|
|
123
|
+
currency in a bucket with `reset_interval: never`. The upload's
|
|
124
|
+
`rewarded_placement_allowance_no_reset` warning is about refilling, which such an allowance does
|
|
125
|
+
not need, so keep the bucket that never resets when the upload warns.
|
|
126
|
+
- **For a daily cap on this placement alone**, use its own `max_per_day` (§6), which needs no
|
|
127
|
+
allowance.
|
|
128
|
+
|
|
129
|
+
This advice will change once the fix is out.
|
|
114
130
|
|
|
115
131
|
The placement only **selects** a reward your own config declared. Your game sends no reward, no
|
|
116
132
|
amount, no watch time and no item in the request — nothing from your frame is trusted for that — so
|
|
@@ -216,6 +232,7 @@ after a press:
|
|
|
216
232
|
| an ad source serves, but no ad was available for this press, or it failed to play | your `without_ads` decides: `grant` grants within your caps, with no ad; `nothing` grants nothing, and the Arcade shows "No ad available right now" |
|
|
217
233
|
| no ad source serves the channel (§8) | your `without_ads` decides, with no ad |
|
|
218
234
|
| a cap, a cooldown, `max_held`, the platform's spacing (§6), or something about the player | no |
|
|
235
|
+
| the placement's `allowance_currency_slug` (§2) is empty | no: the answer is `no_ad`, and on `dev` and `test` the console line says the allowance is spent (§10) |
|
|
219
236
|
|
|
220
237
|
The answer is `shown` whenever the Arcade put its ad stage up, however the ad then ended: watched
|
|
221
238
|
to the end, closed early, or no ad could play and `without_ads` decided. It is `no_ad` when no
|
|
@@ -312,8 +329,10 @@ never in a ranked mode — because the platform cannot see a run (§6).
|
|
|
312
329
|
**Within those rules, draw it only when `gem.ads.rewardedStatus` says it can pay** (SDK 8.1.0 and
|
|
313
330
|
later). The status reads the placement's caps as the platform holds them: `declared`, `grantable`,
|
|
314
331
|
`remainingToday`, `nextEligibleAt`, `heldLimitReached` and `resetsAt`. `grantable` is `false` when
|
|
315
|
-
a cap is spent, a cooldown runs, the player holds `max_held`, or rewarded is off on the channel
|
|
316
|
-
|
|
332
|
+
a cap is spent, a cooldown runs, the player holds `max_held`, or rewarded is off on the channel.
|
|
333
|
+
It does **not** read the placement's `allowance_currency_slug`: with the allowance empty,
|
|
334
|
+
`grantable` is still `true` and the press answers `no_ad`, so read the allowance currency's balance
|
|
335
|
+
yourself before you draw a placement that declares one (§2). Hide or grey the button out
|
|
317
336
|
otherwise, and say when it is back from `nextEligibleAt` or `resetsAt`. `grantable` is not a
|
|
318
337
|
promise: a press can still pay nothing for reasons the read never reports. The status reserves
|
|
319
338
|
nothing, and it never rejects:
|
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/v23.
|
|
84
|
-
curl -fsSL https://downloads.gemarcade.com/sdk/v23.
|
|
83
|
+
curl -fsSL -O https://downloads.gemarcade.com/sdk/v23.1.1/gem-web-sdk.tgz
|
|
84
|
+
curl -fsSL https://downloads.gemarcade.com/sdk/v23.1.1/gem-web-sdk.tgz.sri
|
|
85
85
|
openssl dgst -sha384 -binary gem-web-sdk.tgz | openssl base64 -A
|
|
86
86
|
```
|
|
87
87
|
|
|
@@ -1149,20 +1149,21 @@ The player sets their defaults once, in the Arcade's Settings, separately for ea
|
|
|
1149
1149
|
and a television can say different things. They reach your game as `gem.prefs.recommended`, in the
|
|
1150
1150
|
same push as `muted` and `reducedMotion`, and again whenever the player changes one:
|
|
1151
1151
|
|
|
1152
|
-
| Field | Values | What it suggests
|
|
1153
|
-
| ------------------------ | ------------------------------- |
|
|
1154
|
-
| `volume` | `0` to `100` | Your master volume's starting level, as a percentage — divide by 100 for a gain. A level, never a mute.
|
|
1155
|
-
| `batterySaver` | `true` / `false` | Spend less power: a lower frame rate, fewer effects, a lower resolution.
|
|
1156
|
-
| `keyboardLayout` | `'wasd'` / `'arrows'` | Which keys move by default.
|
|
1157
|
-
| `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.
|
|
1158
|
-
| `vibration` | `true` / `false` | Your vibration toggle's starting state ([if your game vibrates](#if-your-game-vibrates)).
|
|
1159
|
-
| `lookSensitivity` | `20` to `400` | Look speed for touch, mouse and pen, as a percentage of the base speeds: `100` is 1×.
|
|
1160
|
-
| `gamepadLookSensitivity` | `20` to `400` | Look speed for a gamepad stick, as a percentage: `100` is 1×.
|
|
1161
|
-
| `invertLookY` | `true` / `false` | Up/down look inverted: pushing up or dragging up looks down. Pitch only, never yaw.
|
|
1162
|
-
| `touchMovement` | `'floating'` / `'fixed'` | On a touch screen, a movement stick that appears where the thumb lands, or one fixed in place.
|
|
1163
|
-
| `graphicsQuality` | `'low'` / `'medium'` / `'high'` | The graphics quality to start at on this device.
|
|
1164
|
-
| `graphicsQualityAuto` | `true` / `false` | `true`: the Arcade picked that tier for this device, so you may adapt from it. `false`: the player picked it, so hold it.
|
|
1165
|
-
| `colorHelp` | `true` / `false` / `null` | Color help: start your colour-blind aids (patterns or symbols beside colours, a colour-blind palette) on, or off. `null`: no advice.
|
|
1152
|
+
| Field | Values | What it suggests |
|
|
1153
|
+
| ------------------------ | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1154
|
+
| `volume` | `0` to `100` | Your master volume's starting level, as a percentage — divide by 100 for a gain. A level, never a mute. |
|
|
1155
|
+
| `batterySaver` | `true` / `false` | Spend less power: a lower frame rate, fewer effects, a lower resolution. |
|
|
1156
|
+
| `keyboardLayout` | `'wasd'` / `'arrows'` | Which keys move by default. |
|
|
1157
|
+
| `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. |
|
|
1158
|
+
| `vibration` | `true` / `false` | Your vibration toggle's starting state ([if your game vibrates](#if-your-game-vibrates)). |
|
|
1159
|
+
| `lookSensitivity` | `20` to `400` | Look speed for touch, mouse and pen, as a percentage of the base speeds: `100` is 1×. |
|
|
1160
|
+
| `gamepadLookSensitivity` | `20` to `400` | Look speed for a gamepad stick, as a percentage: `100` is 1×. |
|
|
1161
|
+
| `invertLookY` | `true` / `false` | Up/down look inverted: pushing up or dragging up looks down. Pitch only, never yaw. |
|
|
1162
|
+
| `touchMovement` | `'floating'` / `'fixed'` | On a touch screen, a movement stick that appears where the thumb lands, or one fixed in place. |
|
|
1163
|
+
| `graphicsQuality` | `'low'` / `'medium'` / `'high'` | The graphics quality to start at on this device. |
|
|
1164
|
+
| `graphicsQualityAuto` | `true` / `false` | `true`: the Arcade picked that tier for this device, so you may adapt from it. `false`: the player picked it, so hold it. |
|
|
1165
|
+
| `colorHelp` | `true` / `false` / `null` | Color help: start your colour-blind aids (patterns or symbols beside colours, a colour-blind palette) on, or off. `null`: no advice. |
|
|
1166
|
+
| `textScale` | `100` to `200` / `null` | Text size, as a percent: scale your whole interface by it. Sent only to a game that passes `onTextScale`: see [text size](#the-arcades-text-size-size-in-rem-apply-ontextscale). |
|
|
1166
1167
|
|
|
1167
1168
|
`lookSensitivity` to `touchMovement` are the camera and controls settings: see
|
|
1168
1169
|
[camera and look controls](#camera-and-look-controls) for `gem.controls.look()`, which applies the
|
|
@@ -1242,6 +1243,96 @@ owns — starts from `recommended` the same way and is kept in `gem.me.settings`
|
|
|
1242
1243
|
([sound](#sound-your-own-mute-control-and-the-arcades)), and `reducedMotion` is the player's
|
|
1243
1244
|
accessibility setting, which your game honours.
|
|
1244
1245
|
|
|
1246
|
+
### The Arcade's Text size: size in `rem`, apply `onTextScale`
|
|
1247
|
+
|
|
1248
|
+
The player picks a **Text size** in the Arcade's Settings, per device: 100, 115, 130, 150, 180 or
|
|
1249
|
+
200%, or System, which follows the phone's own font size. The Arcade never enlarges your game's
|
|
1250
|
+
text for you — inside your frame, text renders at the size you chose — so a game that wants to
|
|
1251
|
+
honour it applies it, and applies it to **the whole interface**, not to text alone. Text that grows
|
|
1252
|
+
inside boxes that do not is exactly what breaks: labels overflow, overlap and clip.
|
|
1253
|
+
|
|
1254
|
+
**Pass `onTextScale` and set your root font size from it.** Passing the handler is what asks the
|
|
1255
|
+
Arcade for the value; a game without one is never sent it, and `gem.textScale` stays `null`:
|
|
1256
|
+
|
|
1257
|
+
```js
|
|
1258
|
+
const MAX = 150; // the largest size your layout supports: at least 130, ideally 150
|
|
1259
|
+
let chosen = null; // the size the player picked in YOUR settings, or null: "Arcade's size"
|
|
1260
|
+
|
|
1261
|
+
const applyTextSize = (p) => {
|
|
1262
|
+
document.documentElement.style.fontSize = (chosen ?? Math.min(p ?? 100, MAX)) + '%';
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1265
|
+
const gem = createGem({ allowedEmbedders, onTextScale: applyTextSize });
|
|
1266
|
+
```
|
|
1267
|
+
|
|
1268
|
+
It is called on the first push — with `null` when the Arcade gave no advice — and again whenever
|
|
1269
|
+
the value changes. The player's own choice in your game beats the Arcade's, the Arcade's is clamped
|
|
1270
|
+
to what your layout supports, and with neither you render at 100%. The SDK applies nothing itself.
|
|
1271
|
+
|
|
1272
|
+
**This works only if your interface is sized in `rem`:**
|
|
1273
|
+
|
|
1274
|
+
- **Every length in your DOM UI is `rem`, `em` or `%`, never `px`:** font sizes, padding, gaps,
|
|
1275
|
+
widths, heights, radii and borders. Then the root font size scales them all together.
|
|
1276
|
+
- **Leave the root alone.** The root font size is the browser's default (`100%`), set only by the
|
|
1277
|
+
handler above. A `font` or `font-size` rule on `html` or `:root` breaks every `rem`: put your base
|
|
1278
|
+
font on `body` instead.
|
|
1279
|
+
- **Media queries stay in `px`**, and so do values you measure at runtime (positions, safe-area
|
|
1280
|
+
insets). A `rem` in a media query does not follow your root anyway.
|
|
1281
|
+
- **Panels that hold text are capped to the screen:** `width: min(22rem, 100% - 2rem)`, so a panel
|
|
1282
|
+
that grows never runs off a phone.
|
|
1283
|
+
- **Boxes that hold text grow with it:** `min-height` rather than `height`, and wrapping (or a
|
|
1284
|
+
fit-text routine whose floor you check) rather than `white-space: nowrap` with
|
|
1285
|
+
`overflow: hidden` or an ellipsis on an action label.
|
|
1286
|
+
- **Lay out against measured chrome, not constants.** Code that reserves room for a HUD bar or a
|
|
1287
|
+
gear button measures it (`getBoundingClientRect`, a `ResizeObserver`) instead of hard-coding
|
|
1288
|
+
pixels.
|
|
1289
|
+
- **Canvas text counts too.** A game that draws interface text on a canvas scales it from the same
|
|
1290
|
+
value.
|
|
1291
|
+
- **`text-size-adjust` is not a fix.** It does not protect your layout from any text scaling. Keep
|
|
1292
|
+
`100%` only as cheap protection against iOS's own inflation.
|
|
1293
|
+
|
|
1294
|
+
A board or playfield sized from the viewport (a `1/390` unit, say) may keep that unit: it follows
|
|
1295
|
+
the screen, which is right for the playfield. Menus, the HUD, settings and dialogs use `rem`.
|
|
1296
|
+
|
|
1297
|
+
**Support at least 130%,** with 150% the target and 200% for a game that is mostly text. Check every
|
|
1298
|
+
screen at 100%, 130% and your maximum on a phone held upright (390×844), a phone held sideways
|
|
1299
|
+
(844×390) and a desktop (1280×800), in your longest language: nothing clipped, overlapping or off
|
|
1300
|
+
screen. A phone held sideways runs out of height first, so every screen must fit or scroll
|
|
1301
|
+
vertically at your maximum. Rendered text is at least **12 CSS px**, and at least **14 CSS px** for
|
|
1302
|
+
what the player must read to act (costs, instructions, buttons), at a 360 CSS px wide viewport.
|
|
1303
|
+
|
|
1304
|
+
**A text-size setting of your own** is kept per device, like the Arcade setting it overrides (in
|
|
1305
|
+
[device settings](#device-settings-what-this-device-remembers-about-your-game)): a size chosen on a
|
|
1306
|
+
phone should not follow the player to a television. Declare it as a `choice` with `icon: 'text'`
|
|
1307
|
+
whose **first option is the Arcade's size** — the `null` state, which a slider cannot express:
|
|
1308
|
+
|
|
1309
|
+
```js
|
|
1310
|
+
{
|
|
1311
|
+
id: 'text',
|
|
1312
|
+
label: t('Text size'),
|
|
1313
|
+
kind: 'choice',
|
|
1314
|
+
icon: 'text',
|
|
1315
|
+
value: chosen === null ? 'arcade' : String(chosen),
|
|
1316
|
+
options: [
|
|
1317
|
+
{ id: 'arcade', label: t("Arcade's size") },
|
|
1318
|
+
{ id: '100', label: '100%' },
|
|
1319
|
+
{ id: '130', label: '130%' },
|
|
1320
|
+
{ id: '150', label: '150%' },
|
|
1321
|
+
],
|
|
1322
|
+
}
|
|
1323
|
+
```
|
|
1324
|
+
|
|
1325
|
+
Your choice overrides the Arcade's **for your game only**; nothing you do changes the Arcade's
|
|
1326
|
+
setting.
|
|
1327
|
+
|
|
1328
|
+
**Two rules hold however your game uses it, because a text size hints at a player's eyesight:**
|
|
1329
|
+
|
|
1330
|
+
- **Never put a text size in analytics.** No event, dimension or measure carries `textScale`, your
|
|
1331
|
+
resolved size, or the value of a text-size change. Recording that the player opened your text
|
|
1332
|
+
settings, without the value, is fine.
|
|
1333
|
+
- **Save only the player's own choice.** Never store the Arcade's value as if the player had picked
|
|
1334
|
+
it, and a `null` choice stays `null`, so a later change to the Arcade setting still reaches them.
|
|
1335
|
+
|
|
1245
1336
|
### Mirror your settings into the Arcade's menu: `gem.settings`
|
|
1246
1337
|
|
|
1247
1338
|
Optional, and worth doing. The Arcade's Gem menu shows your game's own settings beside the
|
|
@@ -1304,8 +1395,24 @@ const declared = () => [
|
|
|
1304
1395
|
{ id: 'music', label: t('Music'), kind: 'toggle', value: music.on, icon: 'music' }, // the quick row's Music
|
|
1305
1396
|
{ id: 'sfx', label: t('Sound effects'), kind: 'toggle', value: sfx.on, icon: 'sfx' },
|
|
1306
1397
|
{ id: 'colors', label: t('Color help'), kind: 'toggle', value: colorHelp, icon: 'eye' }, // its Color
|
|
1307
|
-
{
|
|
1308
|
-
|
|
1398
|
+
{
|
|
1399
|
+
id: 'musicVolume',
|
|
1400
|
+
label: t('Music volume'),
|
|
1401
|
+
kind: 'slider',
|
|
1402
|
+
value: musicVolume,
|
|
1403
|
+
min: 0,
|
|
1404
|
+
max: 100,
|
|
1405
|
+
step: 5,
|
|
1406
|
+
},
|
|
1407
|
+
{
|
|
1408
|
+
id: 'speed',
|
|
1409
|
+
label: t('Game speed'),
|
|
1410
|
+
kind: 'slider',
|
|
1411
|
+
value: speed,
|
|
1412
|
+
min: 0.5,
|
|
1413
|
+
max: 2,
|
|
1414
|
+
step: 0.25,
|
|
1415
|
+
},
|
|
1309
1416
|
{
|
|
1310
1417
|
id: 'difficulty',
|
|
1311
1418
|
label: t('Difficulty'),
|
package/package.json
CHANGED
package/schema/bridge.v1.json
CHANGED
|
@@ -1007,6 +1007,10 @@
|
|
|
1007
1007
|
"2026-10-23): the SDK does NOT fill it. An older parent omits it and the",
|
|
1008
1008
|
"game reads `null`, \"no recommendation\", never false.",
|
|
1009
1009
|
"",
|
|
1010
|
+
"`textScale` IS OPTIONAL INSIDE IT AND NOT FILLED either (owner",
|
|
1011
|
+
"decision 2026-11-05), and a parent sends it only where the game",
|
|
1012
|
+
"advertised `textScale`; an absent one is no advice.",
|
|
1013
|
+
"",
|
|
1010
1014
|
"`muted`, `reducedMotion` and `showFrameStats` stay where they are, at",
|
|
1011
1015
|
"the top of this message, and keep their meaning: they are the Arcade's",
|
|
1012
1016
|
"own state the frame acts on, not a recommendation. The Arcade starts",
|
|
@@ -1127,6 +1131,42 @@
|
|
|
1127
1131
|
"reports `null` and the game keeps its own default. Unlike the",
|
|
1128
1132
|
"camera and controls fields there is no neutral value to invent."
|
|
1129
1133
|
]
|
|
1134
|
+
},
|
|
1135
|
+
"textScale": {
|
|
1136
|
+
"kind": "int",
|
|
1137
|
+
"min": 100,
|
|
1138
|
+
"max": 200,
|
|
1139
|
+
"optional": true,
|
|
1140
|
+
"$comment": [
|
|
1141
|
+
"The player's Arcade Text size for this device, as a percent (owner",
|
|
1142
|
+
"decision 2026-11-05): 100 is the size",
|
|
1143
|
+
"the game was designed at, 200 is twice that. A game sized in `rem`",
|
|
1144
|
+
"applies it by setting its ROOT font size, so its whole interface",
|
|
1145
|
+
"-- text, padding, boxes and icons -- scales together. The Arcade",
|
|
1146
|
+
"never applies it for a game: inside a game's frame, text renders at",
|
|
1147
|
+
"the size the game chose.",
|
|
1148
|
+
"",
|
|
1149
|
+
"A DEFAULT ONLY, like every field here: the player's own text size in",
|
|
1150
|
+
"the game wins, and a game clamps this to the largest size its layout",
|
|
1151
|
+
"supports. A game never saves it as the player's choice and never",
|
|
1152
|
+
"records it in analytics.",
|
|
1153
|
+
"",
|
|
1154
|
+
"BOUNDED 100..200 PERMANENTLY. Any integer in range is valid; a",
|
|
1155
|
+
"parent sends only the six sizes of the Arcade's own control (100,",
|
|
1156
|
+
"115, 130, 150, 180, 200 -- Android's font-scale ladder), its System",
|
|
1157
|
+
"choice resolved to one of them. Nothing below 100: games design to",
|
|
1158
|
+
"a 12 CSS px floor.",
|
|
1159
|
+
"",
|
|
1160
|
+
"SENT ONLY TO A FRAME THAT ADVERTISED `textScale`, which the SDK does",
|
|
1161
|
+
"when the game passes `onTextScale` to `createGem`. That limits which",
|
|
1162
|
+
"games receive the value; it is not a security control. A parent",
|
|
1163
|
+
"whose `recommended` would fail validation with this field in it",
|
|
1164
|
+
"sends `recommended` again without it, so a bad text size never costs",
|
|
1165
|
+
"a game its volume, Color help or graphics.",
|
|
1166
|
+
"",
|
|
1167
|
+
"ABSENT IS NO ADVICE. A parent built against an older SDK, or one",
|
|
1168
|
+
"that keeps no text size, omits it, and the SDK reports `null`."
|
|
1169
|
+
]
|
|
1130
1170
|
}
|
|
1131
1171
|
}
|
|
1132
1172
|
}
|