@indiegems/gem-web-sdk 23.2.0 → 24.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 +48 -0
- package/dist/conformance.d.ts +5 -2
- package/dist/function.d.ts +22 -1
- package/dist/game.d.ts +1590 -1466
- package/dist/gem-web-sdk.bridge.js +2 -2
- 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 +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 +8 -1
- package/dist/sdk.d.ts +1600 -1464
- package/dist/server.d.ts +44 -1
- package/docs/analytics.md +40 -0
- package/docs/api/api-reference-function.md +196 -45
- package/docs/api/api-reference-game.md +778 -33
- package/docs/api/api-reference-server.md +477 -3
- package/docs/api/reference.json +1833 -102
- package/docs/arcade-and-store.md +166 -61
- package/docs/arcade-economy.md +184 -184
- package/docs/changelog.md +48 -0
- package/docs/deprecation-policy.md +2 -0
- package/docs/getting-started.md +1 -1
- package/docs/inventory-and-catalogs.md +247 -76
- package/docs/manifest.json +24 -24
- package/docs/platform-rules.md +36 -27
- package/docs/player-sign-in.md +60 -3
- package/docs/rewarded-ads.md +32 -1
- package/docs/server-bundles.md +9 -7
- package/docs/what-a-game-must-do.md +7 -4
- package/package.json +1 -1
- package/schema/bridge.v1.json +97 -7
package/docs/manifest.json
CHANGED
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"file": "analytics.md",
|
|
16
|
-
"sha256": "
|
|
17
|
-
"updated": "2026-11-
|
|
16
|
+
"sha256": "3f81025a763f96d0ca17f57377042a3e3890797983265cdb1721902b700aa3a6",
|
|
17
|
+
"updated": "2026-11-09T18:54:26Z"
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
"file": "arcade-and-store.md",
|
|
21
|
-
"sha256": "
|
|
22
|
-
"updated": "2026-11-
|
|
21
|
+
"sha256": "73e77f3bd0124f69d53b42714fb24c7d128dd1be97e203fc3a353a8eb4e3c05a",
|
|
22
|
+
"updated": "2026-11-11T01:56:34Z"
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
"file": "arcade-avatars.md",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"file": "arcade-economy.md",
|
|
31
|
-
"sha256": "
|
|
32
|
-
"updated": "2026-11-
|
|
31
|
+
"sha256": "6fadbb07d8a6ab44cb231b12a6ba89ce65a46bf42841bff932d3813aa4e0931a",
|
|
32
|
+
"updated": "2026-11-11T01:56:34Z"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"file": "async-opponents.md",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
"file": "changelog.md",
|
|
51
|
-
"sha256": "
|
|
52
|
-
"updated": "2026-11-
|
|
51
|
+
"sha256": "f58b3ff7bc37b6da56ac6cc57a72baf32d5e8be83be29be1ec2420971076cee9",
|
|
52
|
+
"updated": "2026-11-11T03:16:15Z"
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
"file": "daily-challenges.md",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
"file": "deprecation-policy.md",
|
|
66
|
-
"sha256": "
|
|
67
|
-
"updated": "2026-
|
|
66
|
+
"sha256": "4622fbf889603436d753013296e9aa8f9edd460ebdae91e4e8594698ea5a0500",
|
|
67
|
+
"updated": "2026-11-10T21:45:26Z"
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
"file": "friends.md",
|
|
@@ -78,8 +78,8 @@
|
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
"file": "getting-started.md",
|
|
81
|
-
"sha256": "
|
|
82
|
-
"updated": "2026-11-
|
|
81
|
+
"sha256": "18a2affd0d2420cd543aabeb3f5d2ef366ef654685e303ef1c9ee2e0ed24eeca",
|
|
82
|
+
"updated": "2026-11-11T03:16:15Z"
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
85
|
"file": "graphics-route.md",
|
|
@@ -88,8 +88,8 @@
|
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
90
|
"file": "inventory-and-catalogs.md",
|
|
91
|
-
"sha256": "
|
|
92
|
-
"updated": "2026-11-
|
|
91
|
+
"sha256": "22998fb3ff26d4f9f29d2a73ce6db0ccf42ca2b635f171023a2e2214bbea4b1a",
|
|
92
|
+
"updated": "2026-11-11T01:56:34Z"
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"file": "leaderboards.md",
|
|
@@ -113,18 +113,18 @@
|
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"file": "platform-rules.md",
|
|
116
|
-
"sha256": "
|
|
117
|
-
"updated": "2026-
|
|
116
|
+
"sha256": "e9cb3cf1945002d182d917b0f3f7cfdf26f41de9ec9cd34a47c487cb4c615152",
|
|
117
|
+
"updated": "2026-11-11T01:56:34Z"
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
120
|
"file": "player-sign-in.md",
|
|
121
|
-
"sha256": "
|
|
122
|
-
"updated": "2026-11-
|
|
121
|
+
"sha256": "ebdaa0a35815c72148bd72f529b5ecee264c2eeb703e73fdfc277066be63fab4",
|
|
122
|
+
"updated": "2026-11-11T01:30:40Z"
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"file": "rewarded-ads.md",
|
|
126
|
-
"sha256": "
|
|
127
|
-
"updated": "2026-11-
|
|
126
|
+
"sha256": "505a6addbac54b9a154ca0b1afd727d319ba35f5f2a9b24d9835ac2a9104ded8",
|
|
127
|
+
"updated": "2026-11-11T01:56:34Z"
|
|
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": "acc4cd517d6c330640fae1affb2ba15a74c671ae72bdc11d13589ba7b66d5432",
|
|
147
|
+
"updated": "2026-11-11T03:16:15Z"
|
|
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": "c5400f43a1218de0d2838d4332dce4c05afe21ff04e352d2e201ef5bb4268bed",
|
|
172
|
+
"updated": "2026-11-09T18:39:56Z"
|
|
173
173
|
}
|
|
174
174
|
]
|
|
175
175
|
}
|
package/docs/platform-rules.md
CHANGED
|
@@ -326,41 +326,50 @@ longer one, are in [`server-bundles.md`](server-bundles.md#how-long-your-server-
|
|
|
326
326
|
|
|
327
327
|
## 8. A player's Boost is for display, never a gate
|
|
328
328
|
|
|
329
|
-
Decided by the platform on **2026-09-26**,
|
|
329
|
+
Decided by the platform on **2026-09-26**, restated for VIP on **2026-11-10**, and a contract
|
|
330
|
+
rather than an implementation fact.
|
|
330
331
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
and holdings cap are raised by the same tier. It **never** touches achievement points. The platform applies all of
|
|
334
|
-
it on its own side, when it pays: a game does nothing to make a boost work, and cannot make one
|
|
335
|
-
work harder.
|
|
332
|
+
**Gate on the pass, never on the boost.** What a player paid for is unlocked by the pass, and the
|
|
333
|
+
multiplier it brings is only ever shown.
|
|
336
334
|
|
|
337
|
-
A
|
|
338
|
-
|
|
335
|
+
A player's earnings multiplier comes from **VIP**, a pass that pays +50% Coins, or from a **Boost**
|
|
336
|
+
bought before the Boost was retired. The platform applies it on its own side, when it pays: every
|
|
337
|
+
Coins grant is multiplied, and the earning limits with it. It **never** touches achievement points.
|
|
338
|
+
A game does nothing to make it work, and cannot make it work harder.
|
|
339
|
+
|
|
340
|
+
A game **may show** it. `gem.player.boost` carries each currency's multiplier and the whole days
|
|
341
|
+
left, so a reward screen can say "+50% Coins"; see
|
|
339
342
|
[what a game must do](what-a-game-must-do.md#the-signed-in-players-boost-show-it-never-gate-on-it).
|
|
340
|
-
|
|
341
|
-
|
|
343
|
+
To show what a payout will be, use `gem.gameConfig.preview`, never your own arithmetic on the
|
|
344
|
+
multiplier. A dedicated server can read the same multiplier for a player in its match
|
|
345
|
+
(`gem.match.playerBoost`), and the same rule holds there.
|
|
342
346
|
|
|
343
|
-
A game **must not gate** anything on
|
|
347
|
+
A game **must not gate** anything on the multiplier:
|
|
344
348
|
|
|
345
349
|
- **No content or power behind it.** No level, item, character, mode, stat, difficulty setting or
|
|
346
|
-
extra attempt may depend on whether the player
|
|
347
|
-
the same game.
|
|
348
|
-
- **No prices that depend on it**, and no discount or surcharge keyed on
|
|
349
|
-
- **No matchmaking on it.** Never exclude, separate or wait on a player because of
|
|
350
|
-
- **No nagging.** No "you're not boosted" loops, interstitials or countdown pressure.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
350
|
+
extra attempt may depend on whether, or by how much, the player earns faster. A player at 1x and a
|
|
351
|
+
player at 1.5x play the same game.
|
|
352
|
+
- **No prices that depend on it**, and no discount or surcharge keyed on it.
|
|
353
|
+
- **No matchmaking on it.** Never exclude, separate or wait on a player because of it.
|
|
354
|
+
- **No nagging.** No "you're not boosted" loops, interstitials or countdown pressure.
|
|
355
|
+
|
|
356
|
+
**What VIP unlocks goes behind the pass.** A VIP room, a VIP roll or a VIP track is a
|
|
357
|
+
`requires: [{ kind: 'holds_pass', pass: 'vip' }]` on the item, recipe, bonus or track, which the
|
|
358
|
+
platform checks when it runs; or a decision your function or server makes from its pass read
|
|
359
|
+
(`gem.me.passes` in a function, `gem.match.playerPasses` on a server). The browser's
|
|
360
|
+
`gem.me.passes` draws the door; it never opens it. Offer VIP only before a reward, by the rules in
|
|
361
|
+
[inventory-and-catalogs.md](inventory-and-catalogs.md#offering-vip-before-a-reward-the-upsell).
|
|
362
|
+
|
|
363
|
+
Three reasons not to gate on the multiplier, any one of which would be enough. It is **advisory**:
|
|
364
|
+
the browser value is pushed on connect and on change, so it can lag, and the platform's own figure
|
|
365
|
+
is what pays. It is **forgeable** inside your page, so a gate on it is a gate anyone can open. And it
|
|
366
|
+
**is not the purchase**: a player can hold an old Boost without VIP, so a gate on the multiplier
|
|
367
|
+
opens for the wrong player and stays shut for none.
|
|
358
368
|
|
|
359
369
|
It carries **a day count, never an instant — and capped at 7**, so `7` means "7 or more" — and
|
|
360
|
-
nothing about the tier, the price or whether the player paid at all
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
paid. Show `7` as "7+ days", never as an end date.
|
|
370
|
+
nothing about the tier, the price or whether the player paid at all. A second-precision expiry
|
|
371
|
+
would be a near-unique value per player that two games could join on, and an exact day count would
|
|
372
|
+
jump by a purchase's term the moment the player paid. Show `7` as "7+ days", never as an end date.
|
|
364
373
|
|
|
365
374
|
## Where the rest of it is
|
|
366
375
|
|
package/docs/player-sign-in.md
CHANGED
|
@@ -17,8 +17,65 @@ signInButton.onclick = async () => {
|
|
|
17
17
|
};
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
That is the whole surface. Your game never draws a sign-in form and never handles a
|
|
21
|
-
could not be trusted with either, and it does not need to be.
|
|
20
|
+
That is almost the whole surface. Your game never draws a sign-in form and never handles a
|
|
21
|
+
password; it could not be trusted with either, and it does not need to be.
|
|
22
|
+
|
|
23
|
+
## Before you draw the button
|
|
24
|
+
|
|
25
|
+
Not every player can be shown a sign-in. Before you draw "Sign in to claim", read
|
|
26
|
+
`gem.account.signInAvailable`, and listen for it to change:
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
const drawUpsell = (available) => {
|
|
30
|
+
signInButton.hidden = !available;
|
|
31
|
+
};
|
|
32
|
+
drawUpsell(gem.account.signInAvailable);
|
|
33
|
+
gem.account.onSignInAvailableChange(drawUpsell);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- **`true`**: pressing the button will normally put the Arcade's sign-in up for this player. It can
|
|
37
|
+
still answer `declined` at a busy moment, such as while the Arcade has a sheet or a menu open.
|
|
38
|
+
- **`false`**: pressing it would show nothing. Don't draw the offer. Either the player is already
|
|
39
|
+
signed in (an ask answers `signed_in` at once), or nothing can be shown here (it answers
|
|
40
|
+
`unavailable`). It is also `false` until the player is known, and again once the game is
|
|
41
|
+
disconnected.
|
|
42
|
+
|
|
43
|
+
**In current Gem Arcade app builds (v3.33.174 and later), every player is already signed in.** The
|
|
44
|
+
app signs each player in to a full account: a device account, Play Games or Game Center, or another
|
|
45
|
+
sign-in. So `signInAvailable` is `false` there, and an ask answers `signed_in` at once, with nothing
|
|
46
|
+
shown. Don't draw a sign-in offer in the app. Older app builds run an older Arcade, where the next
|
|
47
|
+
paragraph applies: `signInAvailable` reads `true` there.
|
|
48
|
+
|
|
49
|
+
On an Arcade older than this signal, `signInAvailable` reads `true` for every player, a full
|
|
50
|
+
account included. That is how a sign-in ask behaved before the signal existed. So a game that reads
|
|
51
|
+
it keeps its button there, and a player who is already signed in and presses it gets `signed_in`
|
|
52
|
+
at once, with nothing shown. Keep handling every answer quietly (section 3).
|
|
53
|
+
|
|
54
|
+
`onSignInAvailableChange` runs only when the answer changes: when the player arrives, when a guest
|
|
55
|
+
signs up in place, and when the game is disconnected. It returns a function that stops it.
|
|
56
|
+
|
|
57
|
+
### Gating a sign-up gift
|
|
58
|
+
|
|
59
|
+
If your game gives a gift for signing up, offer it only while **`gem.player.isGuest`** is `true`:
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
const gem = createGem({
|
|
63
|
+
// Runs for each pushed player, again when a guest signs up in place.
|
|
64
|
+
onPlayer: (player) => (signUpGift.hidden = !player.isGuest),
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`isGuest` is `true` only when the Arcade says the player is a guest it can sign up here. It is
|
|
69
|
+
`false` for a full account, where the Arcade offers no sign-up, and on an older Arcade that does not
|
|
70
|
+
say, so a gift gated on it never reaches a player who already has an account. On such an older
|
|
71
|
+
Arcade no gift shows at all: the safe way to be wrong.
|
|
72
|
+
|
|
73
|
+
Use it rather than `signInAvailable`, which reads `true` for every player on an older Arcade (it
|
|
74
|
+
keeps a sign-in button working there), and rather than `gem.store.offers()` answering
|
|
75
|
+
`sign_in_required`, which no longer means a guest: a current Arcade answers a guest's `offers()`
|
|
76
|
+
with `ok`, because a guest can buy, and signs them up first. Pressing the ask answers `signed_in`
|
|
77
|
+
for a player who was signed in all along too, so that answer is no proof of a sign-up either; what
|
|
78
|
+
the gift pays is granted by your function or server, which reads the account itself.
|
|
22
79
|
|
|
23
80
|
## 1. Save before you ask
|
|
24
81
|
|
|
@@ -56,7 +113,7 @@ and nothing has changed; hide the upsell.
|
|
|
56
113
|
|---|---|---|
|
|
57
114
|
| `signed_in` | the player is signed in | re-read, then hide the upsell |
|
|
58
115
|
| `declined` | the player closed the prompt, or the Arcade chose not to show one | return to the game silently |
|
|
59
|
-
| `unavailable` | the Arcade does not offer this here, or never answered | return to the game silently |
|
|
116
|
+
| `unavailable` | the Arcade does not offer this here, or never answered | return to the game silently; `signInAvailable` was `false` for most of these |
|
|
60
117
|
|
|
61
118
|
`requestSignIn` always resolves when it resolves at all. There is no error branch and no
|
|
62
119
|
`try`/`catch` to write.
|
package/docs/rewarded-ads.md
CHANGED
|
@@ -213,7 +213,7 @@ the player then did, so **branch on your inventory, never on the outcome**, as f
|
|
|
213
213
|
The upload refuses it, and `ad: false` with no `platform_prices` too, since then nothing unlocks
|
|
214
214
|
the placement.
|
|
215
215
|
- **Each currency's spend scope applies,** as it does to an item's price
|
|
216
|
-
([`inventory-and-catalogs.md`](inventory-and-catalogs.md#what-coins-
|
|
216
|
+
([`inventory-and-catalogs.md`](inventory-and-catalogs.md#what-coins-may-buy-the-spend-scope)):
|
|
217
217
|
where Coins may price cosmetics only, a Coin price on a placement that grants anything else is
|
|
218
218
|
refused. Gems may price any placement. `gemctl platform config` lists which currencies your
|
|
219
219
|
game may price in, and each one's spend scope: author against it, never against a list you
|
|
@@ -226,6 +226,37 @@ for a placement yet.** Until then your prices are not offered, the placement unl
|
|
|
226
226
|
alone, `ad: false` included, and `rewardedStatus` carries no `options` (§5). Nothing in your game
|
|
227
227
|
needs to change when it goes live: draw the button from `rewardedStatus` and it follows.
|
|
228
228
|
|
|
229
|
+
### A placement or the store?
|
|
230
|
+
|
|
231
|
+
Now that a placement can take Coins and Gems, pick by what the player is buying:
|
|
232
|
+
|
|
233
|
+
- **A single-reward button** (a hint, an extra life, a skip) is a **rewarded placement**, with
|
|
234
|
+
`platform_prices` for Coins and Gems. Whether it offers the ad, Coins, Gems or any mix is then a
|
|
235
|
+
config switch (`ad` and `platform_prices`), not code: the button and its `requestAd` stay the
|
|
236
|
+
same, and the Arcade shows the player the choices.
|
|
237
|
+
- **A real shop** (several items, quantities, locked items) is **store items and recipes** with
|
|
238
|
+
`platform_prices`
|
|
239
|
+
([`inventory-and-catalogs.md`](inventory-and-catalogs.md#16-selling-for-a-platform-currency)).
|
|
240
|
+
A placement grants one fixed reward per press: it has no list of items, no quantity, and no
|
|
241
|
+
prerequisite to lock anything behind.
|
|
242
|
+
|
|
243
|
+
A hint button the player can unlock with an ad or 2 Gems:
|
|
244
|
+
|
|
245
|
+
```yaml
|
|
246
|
+
rewarded_placements:
|
|
247
|
+
- placement: hint # exactly the string your game passes
|
|
248
|
+
grants:
|
|
249
|
+
- { type: item_grant, item_definition_slug: hint_token, quantity: 1 }
|
|
250
|
+
max_per_day: 5 # limits the ad only
|
|
251
|
+
platform_prices:
|
|
252
|
+
- { currency: GEMS, price: 2 }
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Add `{ currency: ARCADE_TICKETS, price: 20 }` beside it for Coins where Coins' spend scope lets
|
|
256
|
+
them price a hint (above), and `ad: false` to drop the ad. Do not also sell `hint_token` in your
|
|
257
|
+
shop: the reward then counts as sold for a platform currency (§1), the upload warns, and outside
|
|
258
|
+
test ads its ad is only served where the platform verifies completion with the ad provider.
|
|
259
|
+
|
|
229
260
|
## 3. Ask from the press, then re-read
|
|
230
261
|
|
|
231
262
|
```js
|
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/v24.0.0/gem-web-sdk.tgz
|
|
84
|
+
curl -fsSL https://downloads.gemarcade.com/sdk/v24.0.0/gem-web-sdk.tgz.sri
|
|
85
85
|
openssl dgst -sha384 -binary gem-web-sdk.tgz | openssl base64 -A
|
|
86
86
|
```
|
|
87
87
|
|
|
@@ -829,11 +829,13 @@ The short version: the browser half is what a player's **session** can do, and t
|
|
|
829
829
|
what a **match** can do. `gem.rooms.join()` has no meaning to a process that exists because a match
|
|
830
830
|
was already allocated.
|
|
831
831
|
|
|
832
|
-
**`gem.me.passes` is browser and function only
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
832
|
+
**`gem.me.passes` is browser and function only, and `gem.match.playerPasses(playerId)` is the
|
|
833
|
+
server's.** Both routes behind a player's passes and Boost in your match require the allocation
|
|
834
|
+
nonce, which never enters your bundle, so the platform half of the runtime makes the read for you,
|
|
835
|
+
as it does `gem.match.end()`: `gem.match.playerPasses(playerId)` for what to gate on, and
|
|
836
|
+
`gem.match.playerBoost(playerId)` for display only. Each throws on a runtime image that predates it
|
|
837
|
+
and on a listen host. [arcade-and-store.md](arcade-and-store.md#on-a-dedicated-server) has the
|
|
838
|
+
details.
|
|
837
839
|
|
|
838
840
|
**Rooms are the case where those two answers differ**, so they are worth stating separately rather
|
|
839
841
|
than filed under "browser-only". `gem.rooms` — join, leave, ready up, quick-join — is a player
|
|
@@ -2404,10 +2404,13 @@ Not on the list above either. When a player reaches for something only an accoun
|
|
|
2404
2404
|
`gem.account.requestSignIn` asks the Arcade to sign them in, in its own chrome. Your game draws no
|
|
2405
2405
|
form and passes nothing.
|
|
2406
2406
|
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2407
|
+
Draw the offer only while `gem.account.signInAvailable` is `true`: it is `false` for a player
|
|
2408
|
+
already signed in (every player in a current Gem Arcade app build, v3.33.174 and later, is), and
|
|
2409
|
+
wherever nothing can be shown. Two
|
|
2410
|
+
things before you reach for it: **save first**, because the Arcade may sign the player in by taking
|
|
2411
|
+
the whole page away, and your game then starts again rather than hearing an answer; and on `signed_in`, **re-read what you loaded**, because the player
|
|
2412
|
+
underneath your game may not be the one who was there a moment ago.
|
|
2413
|
+
[`player-sign-in.md`](player-sign-in.md) is the page.
|
|
2411
2414
|
|
|
2412
2415
|
## A Share button, if your game has something worth sending a friend
|
|
2413
2416
|
|
package/package.json
CHANGED
package/schema/bridge.v1.json
CHANGED
|
@@ -840,6 +840,47 @@
|
|
|
840
840
|
]
|
|
841
841
|
}
|
|
842
842
|
}
|
|
843
|
+
},
|
|
844
|
+
"signInAvailable": {
|
|
845
|
+
"kind": "boolean",
|
|
846
|
+
"optional": true,
|
|
847
|
+
"$comment": [
|
|
848
|
+
"Whether a `gem.requestSignIn` from a player's press would put the",
|
|
849
|
+
"Arcade's sign-in prompt up for THIS player HERE.",
|
|
850
|
+
"The SDK reads it as `gem.account.signInAvailable`, so a game hides a",
|
|
851
|
+
"\"Sign in to claim\" offer that could show nothing.",
|
|
852
|
+
"",
|
|
853
|
+
"`true`: a browser guest on a surface that offers sign-up. An ask can",
|
|
854
|
+
"still answer `declined` at a busy moment (a sheet, a menu, a parked",
|
|
855
|
+
"game, a second ask); those holds are momentary and not part of this",
|
|
856
|
+
"field. `false`: an ask shows nothing — it answers `signed_in` at once",
|
|
857
|
+
"(a full account, which every player in the Gem Arcade app is) or",
|
|
858
|
+
"`unavailable` (a garden that offers no sign-up, a surface with no",
|
|
859
|
+
"sign-in). A parent",
|
|
860
|
+
"that knows sends it on EVERY `gem.player`, `false` included, and",
|
|
861
|
+
"re-sends the message when it changes (a guest who signs up in place).",
|
|
862
|
+
"",
|
|
863
|
+
"ABSENT means an older parent, and a frame reads it as `true`, which is",
|
|
864
|
+
"what `gem.requestSignIn` did there before this field: a game that adopts",
|
|
865
|
+
"it keeps its button on an old host. So a parent must never omit it to",
|
|
866
|
+
"mean `false`.",
|
|
867
|
+
"",
|
|
868
|
+
"A BOOLEAN, NEVER AN ENUM. A new enum value is breaking here: an N-1",
|
|
869
|
+
"frame's validator would refuse the whole message and cost the game its",
|
|
870
|
+
"player's name.",
|
|
871
|
+
"",
|
|
872
|
+
"Platform-assigned, so it sits outside `untrusted`. Ungated like the rest",
|
|
873
|
+
"of `gem.player`; it means something only beside the `signIn`",
|
|
874
|
+
"capability, without which the SDK reads `false` whatever it says.",
|
|
875
|
+
"",
|
|
876
|
+
"DISCLOSURE. `true` tells a game \"this is a browser guest\": the fact",
|
|
877
|
+
"`gem.requestSignIn` already discloses, since a",
|
|
878
|
+
"full account answers `signed_in` at once and a guest's answer follows a",
|
|
879
|
+
"person. `false` reveals less: it does not tell a signed-in player from",
|
|
880
|
+
"a surface with no sign-in. And `gem.player` is withheld until the launch is",
|
|
881
|
+
"confirmed, so an unconfirmed launch learns nothing. It carries no",
|
|
882
|
+
"sanction, session or age band."
|
|
883
|
+
]
|
|
843
884
|
}
|
|
844
885
|
},
|
|
845
886
|
"rateLimit": { "perMinute": 12 }
|
|
@@ -6002,7 +6043,7 @@
|
|
|
6002
6043
|
"transport": "port",
|
|
6003
6044
|
"$comment": [
|
|
6004
6045
|
"Ask what the Arcade Store sells to this player, here, for real money:",
|
|
6005
|
-
"
|
|
6046
|
+
"Coin packs, Boosts, passes and bundles. Gated by the `store` capability.",
|
|
6006
6047
|
"Answered by `gem.storeOffersResult`.",
|
|
6007
6048
|
"",
|
|
6008
6049
|
"WHY THE PARENT ANSWERS IT. The price a player pays depends on the",
|
|
@@ -6016,9 +6057,13 @@
|
|
|
6016
6057
|
"",
|
|
6017
6058
|
"A READ, NOT AN ACT: no activation, no mutex. Per-session state comes",
|
|
6018
6059
|
"back as a reason: `store_off` where the store is withheld (an age or",
|
|
6019
|
-
"region policy, a TV, an in-app browser that takes no payment)
|
|
6020
|
-
"
|
|
6021
|
-
"
|
|
6060
|
+
"region policy, a TV, an in-app browser that takes no payment).",
|
|
6061
|
+
"",
|
|
6062
|
+
"A GUEST IS ANSWERED `ok`, with what the store would sell them:",
|
|
6063
|
+
"buying takes a guest through sign-up first, in the parent's own",
|
|
6064
|
+
"chrome, and back to the offer. An",
|
|
6065
|
+
"older parent answered a guest `sign_in_required`, and a frame must",
|
|
6066
|
+
"still read that as nothing for sale."
|
|
6022
6067
|
],
|
|
6023
6068
|
"fields": {
|
|
6024
6069
|
"requestId": {
|
|
@@ -6049,6 +6094,23 @@
|
|
|
6049
6094
|
"instead, and a purchase of that offer is answered `declined` /",
|
|
6050
6095
|
"`cancelled`.",
|
|
6051
6096
|
"",
|
|
6097
|
+
"WHAT A PASS SAYS, AND WHAT IT MUST NOT. A pass offer names its",
|
|
6098
|
+
"offering slug in `pass` and the earning",
|
|
6099
|
+
"multipliers it carries in `earnMultipliers`, so a game can find VIP by",
|
|
6100
|
+
"slug and preview what it would add without naming a SKU or a number.",
|
|
6101
|
+
"A parent lists a pass only where it would open that pass's sheet: an",
|
|
6102
|
+
"offering with `visible_to_games`, to a player who has not declined",
|
|
6103
|
+
"its sheet twice today. For a KNOWN MINOR it leaves the pass out",
|
|
6104
|
+
"exactly as if it were not on sale - the same `ok`, the same other",
|
|
6105
|
+
"offers, no reason - so a game can never tell a minor from a pass that",
|
|
6106
|
+
"is not sold here. A holder still sees it: the sheet adds time.",
|
|
6107
|
+
"",
|
|
6108
|
+
"COIN PACKS ARE `gem_pack`. The kind enum is closed (a fifth value",
|
|
6109
|
+
"would be refused whole by an N-1 frame), so a pack of any platform",
|
|
6110
|
+
"currency crosses as `gem_pack` with `grant` naming the currency, and",
|
|
6111
|
+
"the SDK reports a pack whose `grant.currency` is not GEMS as",
|
|
6112
|
+
"`coin_pack`.",
|
|
6113
|
+
"",
|
|
6052
6114
|
"THE NAMES ARE THE PLATFORM'S, not a game developer's: the Arcade",
|
|
6053
6115
|
"Store's own catalog copy, which is why `name` and `description` sit",
|
|
6054
6116
|
"outside an `untrusted` object.",
|
|
@@ -6090,7 +6152,7 @@
|
|
|
6090
6152
|
"kind": {
|
|
6091
6153
|
"kind": "enum",
|
|
6092
6154
|
"values": ["gem_pack", "boost", "pass", "bundle"],
|
|
6093
|
-
"$comment": "What sort of thing it is. A new sort of SKU is left out of this list by the parent, never sent as a fifth value: an N-1 frame would refuse the whole message."
|
|
6155
|
+
"$comment": "What sort of thing it is. A new sort of SKU is left out of this list by the parent, never sent as a fifth value: an N-1 frame would refuse the whole message. `gem_pack` is a pack of ANY platform currency, Coins included: `grant` says which."
|
|
6094
6156
|
},
|
|
6095
6157
|
"name": {
|
|
6096
6158
|
"kind": "string",
|
|
@@ -6117,12 +6179,12 @@
|
|
|
6117
6179
|
"grant": {
|
|
6118
6180
|
"kind": "object",
|
|
6119
6181
|
"optional": true,
|
|
6120
|
-
"$comment": "For a
|
|
6182
|
+
"$comment": "For a currency pack (`gem_pack`): the currency and amount it grants. Absent for every other kind.",
|
|
6121
6183
|
"fields": {
|
|
6122
6184
|
"currency": {
|
|
6123
6185
|
"kind": "string",
|
|
6124
6186
|
"maxLength": 32,
|
|
6125
|
-
"$comment": "The platform currency's exact slug, e.g.
|
|
6187
|
+
"$comment": "The platform currency's exact slug, e.g. ARCADE_TICKETS for Coins."
|
|
6126
6188
|
},
|
|
6127
6189
|
"amount": {
|
|
6128
6190
|
"kind": "int",
|
|
@@ -6131,6 +6193,34 @@
|
|
|
6131
6193
|
"$comment": "How much of it."
|
|
6132
6194
|
}
|
|
6133
6195
|
}
|
|
6196
|
+
},
|
|
6197
|
+
"pass": {
|
|
6198
|
+
"kind": "string",
|
|
6199
|
+
"maxLength": 64,
|
|
6200
|
+
"optional": true,
|
|
6201
|
+
"$comment": "For a pass, or a bundle carrying one: the pass's offering slug, e.g. `vip` - the slug `gem.me.passes` and a `holds_pass` requirement name. Absent for everything else, and from an older parent. Additive: no `v` bump."
|
|
6202
|
+
},
|
|
6203
|
+
"earnMultipliers": {
|
|
6204
|
+
"kind": "array",
|
|
6205
|
+
"maxItems": 8,
|
|
6206
|
+
"optional": true,
|
|
6207
|
+
"$comment": "For an offer that makes the player earn a platform currency faster while held - a pass with the `earn_boost` feature, or a Boost: one entry per currency, as `gem.player`'s `boost.multipliers` carry them. Absent or empty when it boosts nothing. The platform's multiplier, never a game's: the preview reads it to say what holding the pass would add.",
|
|
6208
|
+
"items": {
|
|
6209
|
+
"kind": "object",
|
|
6210
|
+
"fields": {
|
|
6211
|
+
"currencySlug": {
|
|
6212
|
+
"kind": "string",
|
|
6213
|
+
"maxLength": 32,
|
|
6214
|
+
"$comment": "The currency's slug, e.g. ARCADE_TICKETS."
|
|
6215
|
+
},
|
|
6216
|
+
"multiplierBp": {
|
|
6217
|
+
"kind": "int",
|
|
6218
|
+
"min": 10000,
|
|
6219
|
+
"max": 1000000,
|
|
6220
|
+
"$comment": "Basis points: 10000 is 1x, 15000 is 1.5x. gem-api clamps what it applies to 10x."
|
|
6221
|
+
}
|
|
6222
|
+
}
|
|
6223
|
+
}
|
|
6134
6224
|
}
|
|
6135
6225
|
}
|
|
6136
6226
|
}
|