@indiegems/gem-web-sdk 16.2.0 → 16.3.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 +14 -0
- package/README.md +2 -1
- package/dist/game.d.ts +50 -1
- package/dist/gem-web-sdk.conformance.js +2 -2
- package/dist/gem-web-sdk.esm.js +1 -1
- 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 +1 -1
- package/dist/sdk.d.ts +50 -1
- package/docs/api/api-reference-function.md +1 -1
- package/docs/api/api-reference-game.md +468 -34
- package/docs/api/api-reference-server.md +1 -1
- package/docs/api/reference.json +625 -9
- package/docs/arcade-and-store.md +28 -17
- package/docs/arcade-economy.md +4 -4
- package/docs/brand-art.md +17 -4
- package/docs/branding.md +160 -0
- package/docs/changelog.md +14 -0
- package/docs/getting-started.md +1 -1
- package/docs/inventory-and-catalogs.md +4 -3
- package/docs/manifest.json +21 -16
- package/docs/server-bundles.md +2 -2
- package/docs/what-a-game-must-do.md +75 -23
- package/package.json +1 -1
- package/schema/bridge.v1.json +29 -17
package/docs/arcade-and-store.md
CHANGED
|
@@ -21,8 +21,8 @@ async function onBuyPressed(offer) {
|
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
**Your game asks; the Arcade decides.** Nothing your game sends carries a URL, a price or a card
|
|
24
|
-
field. The Arcade builds every link, reads every price,
|
|
25
|
-
|
|
24
|
+
field. The Arcade builds every link, reads every price, opens every checkout and takes the payment
|
|
25
|
+
itself. You get an outcome.
|
|
26
26
|
|
|
27
27
|
- [1. Two surfaces, and which one is real money](#1-two-surfaces-and-which-one-is-real-money)
|
|
28
28
|
- [2. Nothing rejects](#2-nothing-rejects)
|
|
@@ -37,11 +37,11 @@ payment itself. You get an outcome.
|
|
|
37
37
|
|
|
38
38
|
## 1. Two surfaces, and which one is real money
|
|
39
39
|
|
|
40
|
-
| | **`gem.arcade`** | **`gem.store`**
|
|
41
|
-
| ------------ | ------------------------------------------------ |
|
|
42
|
-
| What it does | app-store listings, another game, similar games | the Arcade Store's own SKUs
|
|
43
|
-
| Money | none | **real money**, through the Arcade's
|
|
44
|
-
| Who draws it | the Arcade (a new tab, a QR code, the next game) | the
|
|
40
|
+
| | **`gem.arcade`** | **`gem.store`** |
|
|
41
|
+
| ------------ | ------------------------------------------------ | --------------------------------------------------- |
|
|
42
|
+
| What it does | app-store listings, another game, similar games | the Arcade Store's own SKUs |
|
|
43
|
+
| Money | none | **real money**, through the Arcade Store's checkout |
|
|
44
|
+
| Who draws it | the Arcade (a new tab, a QR code, the next game) | the checkout, over your paused and hidden game |
|
|
45
45
|
|
|
46
46
|
`gem.store` is not `gem.purchases`. **`gem.purchases` spends Gems or Coins on your game's own
|
|
47
47
|
items** ([`inventory-and-catalogs.md`](inventory-and-catalogs.md)). **`gem.store` sells the Arcade
|
|
@@ -194,6 +194,12 @@ talks to the Arcade: `gemctl serve` and the Project Editor too, when they are si
|
|
|
194
194
|
**Name each host exactly. Never allow `https://*.blob.core.windows.net`**: that wildcard is every
|
|
195
195
|
Azure customer's storage, not the Arcade's.
|
|
196
196
|
|
|
197
|
+
**The platform's branding** (the logo, the wordmark and the currency marks `gem.branding` hands
|
|
198
|
+
you) is served from `https://gemarcade.com`, in every environment. Allow it in `connect-src`, for
|
|
199
|
+
the branding manifest, and in each kind's own directive for the files you draw: `img-src` for
|
|
200
|
+
images, `media-src` for audio, `font-src` for fonts
|
|
201
|
+
([branding.md](branding.md#5-your-pages-content-security-policy)).
|
|
202
|
+
|
|
197
203
|
A non-production backend serves its art from hosts of its own. These hosts are infrastructure, and
|
|
198
204
|
they can change: art that will not load is the sign, and the browser's console names the host your
|
|
199
205
|
policy refused. Allow that host rather than a wildcard.
|
|
@@ -215,7 +221,7 @@ Gem pack, a `grant` naming the currency and amount.
|
|
|
215
221
|
currency, already formatted. Never parse it, compare two of them, or compute a discount from it.
|
|
216
222
|
There is no numeric price.
|
|
217
223
|
- **Re-read offers when your game resumes.** What is on sale can change while the player is away.
|
|
218
|
-
- **Do not imitate the Arcade's chrome.** Your offer list is yours to style; the
|
|
224
|
+
- **Do not imitate the Arcade's chrome.** Your offer list is yours to style; the checkout that takes
|
|
219
225
|
the money is the Arcade's, and a game that draws something that looks like it confuses players
|
|
220
226
|
about which one is real.
|
|
221
227
|
- `unavailable` / `store_off` means the store is not offered here (a TV, an in-app browser that
|
|
@@ -228,12 +234,15 @@ Gem pack, a `grant` naming the currency and amount.
|
|
|
228
234
|
const { outcome, reason } = await gem.store.purchase(offer.skuId);
|
|
229
235
|
```
|
|
230
236
|
|
|
231
|
-
`purchase(skuId)` asks the Arcade to open
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
237
|
+
`purchase(skuId)` asks the Arcade to open checkout for one SKU from `offers()`. The Arcade pauses
|
|
238
|
+
and hides your game and opens checkout **straight away**, with no confirmation screen of its own
|
|
239
|
+
first: the payment provider's page on the web, the app store's own purchase sheet in the Arcade's
|
|
240
|
+
apps. That checkout is where the player confirms and pays. Your game is resumed before the answer
|
|
241
|
+
arrives, so your own celebration is visible.
|
|
235
242
|
|
|
236
|
-
- **Call it from the press** ([§4](#4-call-the-acting-ones-from-a-press)).
|
|
243
|
+
- **Call it from the press** ([§4](#4-call-the-acting-ones-from-a-press)). The player's press on
|
|
244
|
+
your Buy button is what opens checkout, so make that button say plainly what it buys and for how
|
|
245
|
+
much (`priceDisplay`).
|
|
237
246
|
- **It has no timeout.** A payment takes as long as it takes, so the call waits for as long as the
|
|
238
247
|
Arcade is connected.
|
|
239
248
|
- `ok`: the player paid. **Read balances and inventory through `gem.me`**; the answer carries no
|
|
@@ -246,14 +255,16 @@ own celebration is visible.
|
|
|
246
255
|
const stop = gem.store.onSettled(({ skuId }) => refreshBalances());
|
|
247
256
|
```
|
|
248
257
|
|
|
249
|
-
- `declined` / `cancelled`: the player closed the
|
|
250
|
-
ask again unprompted**: the Arcade backs off after
|
|
251
|
-
`declined` / `busy` for a while.
|
|
258
|
+
- `declined` / `cancelled`: the player closed the checkout without paying. Return to your screen
|
|
259
|
+
quietly and **do not ask again unprompted**: the Arcade backs off after the player closes one, and
|
|
260
|
+
the next ask is `declined` / `busy` for a while, longer each time. An offer the player cannot buy
|
|
261
|
+
again yet also answers `declined` / `cancelled`: the Arcade tells the player when it can be
|
|
262
|
+
bought, opens nothing, and does not back off.
|
|
252
263
|
- `unavailable` with `store_off`, `sign_in_required` or `not_found` (a SKU that is not in
|
|
253
264
|
`offers()` right now), or with a payment provider's own failure code: nothing was bought. The
|
|
254
265
|
first three show nothing.
|
|
255
266
|
- `unavailable` / `error`: **the outcome is unknown, and the player may have paid.** You get it
|
|
256
|
-
when the bridge to the Arcade closes while
|
|
267
|
+
when the bridge to the Arcade closes while checkout is open, which can happen after the payment
|
|
257
268
|
went through, and when the Arcade has no reliable answer. Never tell the player nothing was
|
|
258
269
|
bought. Re-read balances and inventory through `gem.me`, keep your `gem.store.onSettled`
|
|
259
270
|
listener, and re-read again when your game resumes:
|
package/docs/arcade-economy.md
CHANGED
|
@@ -205,9 +205,9 @@ Nothing sells Coins or achievement points, alone or in a bundle. The arcade's ow
|
|
|
205
205
|
Gems, and the Gems a player bought there spend in your game exactly as the ones they earned do: your
|
|
206
206
|
game cannot tell the two apart and prices nothing differently for either. Your game never sells
|
|
207
207
|
either itself. It may put the arcade store's own Gem packs and Boosts in front of a player from a
|
|
208
|
-
button of its own, through `gem.store`, and the arcade prices them,
|
|
209
|
-
the money ([arcade-and-store.md](arcade-and-store.md)). The real-money product that changes
|
|
210
|
-
your game pays is a **Boost**, which a player always buys
|
|
208
|
+
button of its own, through `gem.store`, and the arcade prices them, opens its own checkout and
|
|
209
|
+
takes the money ([arcade-and-store.md](arcade-and-store.md)). The real-money product that changes
|
|
210
|
+
what your game pays is a **Boost**, which a player always buys in the arcade's own checkout:
|
|
211
211
|
|
|
212
212
|
- **It multiplies what Gems and Coins grants pay, and the ceilings with them.** A player on a 2×
|
|
213
213
|
Boost earns twice as much per grant, may earn twice as much per window and per day, and may hold
|
|
@@ -247,7 +247,7 @@ animates every payout into the right total. Gems and Coins follow the same rules
|
|
|
247
247
|
wallet top-up, or anything shaped like the arcade's purchase sheet: a game screen that looks like
|
|
248
248
|
platform payment chrome is an imitation of it, and players cannot tell the two apart. Your shop's
|
|
249
249
|
own Buy button is yours, and so is a "Get Gems" button that calls `gem.store.purchase` from the
|
|
250
|
-
player's press: the
|
|
250
|
+
player's press: the checkout it opens is the arcade store's own
|
|
251
251
|
([arcade-and-store.md](arcade-and-store.md)). When a purchase is refused for
|
|
252
252
|
`insufficient_funds`, say so in your shop's own words ("Not enough Coins") and stop there: a
|
|
253
253
|
refusal never opens the store by itself.
|
package/docs/brand-art.md
CHANGED
|
@@ -5,6 +5,13 @@ The SDK ships the platform's own art for its two currencies, **Gems** and **Coin
|
|
|
5
5
|
§1 before you pick a file: each currency has two kinds of art, and which one you use depends on what
|
|
6
6
|
the screen is for.
|
|
7
7
|
|
|
8
|
+
> **Draw the platform's current marks with `gem.branding`, and keep these files as the fallback.**
|
|
9
|
+
> `gem.branding.currencyMark('GEMS', { size: 20 })` and `gem.branding.trophy()` hand you the same
|
|
10
|
+
> renders, served by the platform, so a changed mark reaches your game with no SDK update and no
|
|
11
|
+
> republish ([branding.md](branding.md)). The files in this package are the marks as they were when
|
|
12
|
+
> this SDK version was released: draw them only when a lookup answers `unavailable`. Every size,
|
|
13
|
+
> role and rule on this page applies to both.
|
|
14
|
+
|
|
8
15
|
**Currency display belongs to the arcade.** The arcade draws the player's Gems and Coins totals in
|
|
9
16
|
its own chrome while your game runs, and it animates every payout of either into its total. Your game
|
|
10
17
|
draws Gems or Coins only in the three places §5 allows — an amount on offer or just paid, a reward
|
|
@@ -142,6 +149,9 @@ and the art softens as it is stretched. Below 64 px, use the icon (§2).
|
|
|
142
149
|
|
|
143
150
|
## 4. Loading and drawing the art
|
|
144
151
|
|
|
152
|
+
The platform serves every one of these files through `gem.branding` ([branding.md](branding.md)),
|
|
153
|
+
which is what to draw first. The copies below are the fallback for when it answers `unavailable`.
|
|
154
|
+
|
|
145
155
|
All of it is in the package you already install, under `brand/`, and published beside it on the CDN —
|
|
146
156
|
the same bytes either way, for the version you are on.
|
|
147
157
|
|
|
@@ -264,9 +274,10 @@ The CDN lets any origin read these files, so a canvas or a WebGL texture can use
|
|
|
264
274
|
path never changes once published, so a URL you ship keeps working, and `artifact-manifest.json` in
|
|
265
275
|
the same version folder lists each file's `sha384` hash under `brand`.
|
|
266
276
|
|
|
267
|
-
**Prefer
|
|
268
|
-
|
|
269
|
-
|
|
277
|
+
**Prefer `gem.branding` over a fixed URL here.** A URL in this folder is the art of the version you
|
|
278
|
+
pinned, forever; the platform's is the art as it is now. Either way, a load at play time can be slow
|
|
279
|
+
or fail, so lay the screen out so it reads correctly without the image, and keep a bundled copy for
|
|
280
|
+
`unavailable`.
|
|
270
281
|
|
|
271
282
|
### The platform currency's `icon_url`
|
|
272
283
|
|
|
@@ -285,7 +296,9 @@ decision of that date); the ruling's other rules are unchanged. In rules:
|
|
|
285
296
|
|
|
286
297
|
- **Keep no Gems counter.** Your game shows no Gems balance, running total or wallet of its own, on
|
|
287
298
|
any screen. The arcade's wallet is the only place Gems accrue, and the only place a total is shown
|
|
288
|
-
— in the Gem Arcade, the Gem menu, which the player opens from the Gem button
|
|
299
|
+
— in the Gem Arcade, the Gem menu, which the player opens from the Gem button, and beside the Gem
|
|
300
|
+
for a game that asks the arcade to draw it there (`gem.setWalletVisible(true)`,
|
|
301
|
+
[the Arcade's wallet](what-a-game-must-do.md#the-arcades-wallet-in-the-gem-menu-and-beside-the-gem-when-you-ask)).
|
|
289
302
|
- **Draw no Gems flight or celebration of your own.** No particles flying to a corner, no counting-up
|
|
290
303
|
number, no burst of gems. `gem.cueReward` is the animation, and the arcade draws it.
|
|
291
304
|
- **Show no Gems amount at all when you are not connected to the arcade.** Without the arcade there is
|
package/docs/branding.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# The platform's branding: the logo, the wordmark and the platform marks
|
|
2
|
+
|
|
3
|
+
Show the Indie Gems logo on your main menu, the Gem Arcade wordmark on a credits screen, or the Gems
|
|
4
|
+
mark beside a price, with the files **the platform serves now**. `gem.branding` reads them from the
|
|
5
|
+
platform's branding manifest, so when a mark changes, your game draws the new one on its next
|
|
6
|
+
session. You do not update the SDK or republish.
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
const logo = await gem.branding.logo();
|
|
10
|
+
if (logo.outcome === 'ok') {
|
|
11
|
+
menuLogo.src = logo.url;
|
|
12
|
+
} else {
|
|
13
|
+
menuLogo.hidden = true; // the platform's branding could not be read: draw no logo
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- [1. What you can ask for](#1-what-you-can-ask-for)
|
|
18
|
+
- [2. What a lookup answers](#2-what-a-lookup-answers)
|
|
19
|
+
- [3. Picking a file](#3-picking-a-file)
|
|
20
|
+
- [4. When the manifest cannot be read: the bundled copy](#4-when-the-manifest-cannot-be-read-the-bundled-copy)
|
|
21
|
+
- [5. Your page's Content-Security-Policy](#5-your-pages-content-security-policy)
|
|
22
|
+
- [6. Using the logo and the wordmark](#6-using-the-logo-and-the-wordmark)
|
|
23
|
+
|
|
24
|
+
## 1. What you can ask for
|
|
25
|
+
|
|
26
|
+
| Call | Asset id | What it is |
|
|
27
|
+
| --------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------- |
|
|
28
|
+
| `gem.branding.logo()` | `gem-logo` | The Indie Gems logo, the brand gem. Scalable (SVG). |
|
|
29
|
+
| `gem.branding.wordmark()` | `gem-wordmark` | The Gem Arcade wordmark, 336 and 672 px wide. |
|
|
30
|
+
| `gem.branding.currencyMark('GEMS')` | `gems-mark` | The Gems mark: the 20 and 32 CSS px icons at 1, 1.5, 2 and 3x, and hero renders 128 to 768 px wide. |
|
|
31
|
+
| `gem.branding.currencyMark('ARCADE_TICKETS')` | `ticket-mark` | The Coins mark, in the same sizes. |
|
|
32
|
+
| `gem.branding.trophy()` | `trophy-mark` | The trophy: 20, 28, 56 and 72 CSS px renders and hero renders 128 to 768 px wide. |
|
|
33
|
+
|
|
34
|
+
**Any asset, by id and kind:** `gem.branding.get(id, { kind })`. The named calls are `get` with the
|
|
35
|
+
id filled in. When the platform adds an asset (another image, a sound, a font), you reach it with
|
|
36
|
+
`get` from the SDK you already have:
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
// 'arcade-sting' is an example of an id the platform could add; today it answers not_found.
|
|
40
|
+
const sting = await gem.branding.get('arcade-sting', { kind: 'audio' });
|
|
41
|
+
if (sting.outcome === 'ok') new Audio(sting.url).play();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`gem.branding.list()` resolves every asset the platform serves today, each with its `id`, `kind`
|
|
45
|
+
and every file (`variants`). Compare `kind` rather than switching on every value: kinds other than
|
|
46
|
+
`image`, `audio` and `font` may appear.
|
|
47
|
+
|
|
48
|
+
The currency marks follow the rules in [brand-art.md](brand-art.md): **the icon** (`size: 20` or
|
|
49
|
+
`32`) beside an amount, **the hero** (no size, or a size of 64 CSS px or more) on a reward screen,
|
|
50
|
+
and never a balance of your own.
|
|
51
|
+
|
|
52
|
+
## 2. What a lookup answers
|
|
53
|
+
|
|
54
|
+
Every call resolves. None rejects, except for an argument that is not one: an empty id, a `size`
|
|
55
|
+
or `density` that is not a positive number, a `theme` other than `'light'` or `'dark'`, or a
|
|
56
|
+
currency other than `'GEMS'` or `'ARCADE_TICKETS'`.
|
|
57
|
+
|
|
58
|
+
| `outcome` | Means | Do |
|
|
59
|
+
| ------------- | ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
|
|
60
|
+
| `ok` | The platform has it. `url` is the file picked for you, `variant` describes it, and `asset` holds every file. | Draw `url`. |
|
|
61
|
+
| `not_found` | The manifest was read, and has no asset with that id and kind, or none in the theme you asked for. | Draw nothing for it. |
|
|
62
|
+
| `unavailable` | The manifest could not be read: the player is offline, the request failed, or your page's policy refused it (§5). | Draw your bundled copy (§4), or nothing. |
|
|
63
|
+
|
|
64
|
+
An id or a kind the SDK does not know is `not_found`, never an error, so a newer platform cannot
|
|
65
|
+
break an older game.
|
|
66
|
+
|
|
67
|
+
**Every URL is on the platform's own host**, absolute, and content-hashed: a changed mark is a new
|
|
68
|
+
URL. Use it as it is in an `<img src>`, a CSS `background-image` or a texture loader. Look it up
|
|
69
|
+
each session; do not store one in a save or your build.
|
|
70
|
+
|
|
71
|
+
The SDK reads the manifest once, on your first `gem.branding` call, and every later call shares
|
|
72
|
+
that read. A game that never calls it makes no request. If the read fails, calls answer
|
|
73
|
+
`unavailable` for a minute, then the next call tries again. Call `gem.branding.list()` during
|
|
74
|
+
start-up to have it ready before your menu draws.
|
|
75
|
+
|
|
76
|
+
## 3. Picking a file
|
|
77
|
+
|
|
78
|
+
Pass the box you draw in, in CSS px, as `size`. The SDK picks:
|
|
79
|
+
|
|
80
|
+
1. the file **made for that box**, at the nearest density at or above the screen's
|
|
81
|
+
`devicePixelRatio` (else the densest there is);
|
|
82
|
+
2. else a **scalable** file (the logo's SVG);
|
|
83
|
+
3. else the **narrowest** file at least `size × density` pixels wide, else the widest.
|
|
84
|
+
|
|
85
|
+
With no `size`, you get the scalable file, or else the widest. Pass `density` to override the
|
|
86
|
+
screen's ratio, for example `density: 3` for a canvas or a texture you scale down yourself. Pass
|
|
87
|
+
`theme: 'light'` or `'dark'` to prefer a file drawn for that theme; files for the other theme are
|
|
88
|
+
never picked, and a file rendered for your exact `size` still beats one drawn for your theme at
|
|
89
|
+
another size. Every file today serves both.
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
// A 20 CSS px Gems icon in an <img>, with every density for the browser to choose from.
|
|
93
|
+
const gems = await gem.branding.currencyMark('GEMS', { size: 20 });
|
|
94
|
+
if (gems.outcome === 'ok') {
|
|
95
|
+
const files = gems.asset.variants.filter((v) => v.size === 20);
|
|
96
|
+
icon.src = gems.url;
|
|
97
|
+
icon.srcset = files.map((v) => `${v.url} ${v.width}w`).join(', ');
|
|
98
|
+
icon.sizes = '20px';
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
// The wordmark on a canvas or a WebGL texture: the platform's host allows any origin to read it.
|
|
104
|
+
const wordmark = await gem.branding.wordmark({ size: 168, density: 3 });
|
|
105
|
+
if (wordmark.outcome === 'ok') {
|
|
106
|
+
const img = new Image();
|
|
107
|
+
img.crossOrigin = 'anonymous'; // before src, or the canvas is tainted
|
|
108
|
+
img.src = wordmark.url;
|
|
109
|
+
await img.decode();
|
|
110
|
+
ctx.drawImage(img, x, y, 168, 88);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 4. When the manifest cannot be read: the bundled copy
|
|
115
|
+
|
|
116
|
+
The SDK package still ships the Gems, Coins and trophy marks under `brand/`
|
|
117
|
+
([brand-art.md](brand-art.md) §4). **They are the fallback for `unavailable`**, not the art to draw
|
|
118
|
+
first: a file bundled in your build is the mark as it was when you built, and the platform's is the
|
|
119
|
+
mark as it is now.
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
import gemsIcon from '@indiegems/gem-web-sdk/brand/gem-icon-20-2x.png';
|
|
123
|
+
|
|
124
|
+
const gems = await gem.branding.currencyMark('GEMS', { size: 20 });
|
|
125
|
+
if (gems.outcome === 'ok') icon.src = gems.url;
|
|
126
|
+
else if (gems.outcome === 'unavailable') icon.src = gemsIcon; // the platform could not be reached
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
There is no bundled logo or wordmark. When `logo()` or `wordmark()` is `unavailable`, lay the screen
|
|
130
|
+
out so it reads correctly without it.
|
|
131
|
+
|
|
132
|
+
## 5. Your page's Content-Security-Policy
|
|
133
|
+
|
|
134
|
+
The platform does not restrict what your game fetches. If your own page sets a
|
|
135
|
+
Content-Security-Policy (a `<meta>` tag in your `index.html`), allow `https://gemarcade.com`,
|
|
136
|
+
where the manifest and every file it lists are served:
|
|
137
|
+
|
|
138
|
+
- in **`connect-src`**, for the manifest. Without it every lookup is `unavailable`, and the SDK says
|
|
139
|
+
so once on the console;
|
|
140
|
+
- in each kind's own directive for the files you draw: **`img-src`** for images, **`media-src`**
|
|
141
|
+
for audio, **`font-src`** for fonts.
|
|
142
|
+
|
|
143
|
+
[arcade-and-store.md](arcade-and-store.md#the-media-hosts) lists the hosts the Arcade's card art comes
|
|
144
|
+
from, beside this one.
|
|
145
|
+
|
|
146
|
+
## 6. Using the logo and the wordmark
|
|
147
|
+
|
|
148
|
+
- **Draw them as served:** never recoloured, stretched, rotated, cropped, outlined or given effects,
|
|
149
|
+
and never rebuilt from another font or file.
|
|
150
|
+
- **Keep clear space** around each, at least a quarter of its height, free of text, buttons and
|
|
151
|
+
other art.
|
|
152
|
+
- **Not smaller than the Arcade draws them:** the wordmark 60 CSS px wide, the gem logo 32 CSS px.
|
|
153
|
+
Below that the gem's facets blur together.
|
|
154
|
+
- **On a busy background,** put them on a plain panel rather than over art.
|
|
155
|
+
- **The wordmark is the product's name written as a logo.** Never set "Gem Arcade" in a text face
|
|
156
|
+
beside the gem in its place.
|
|
157
|
+
- **The currency marks stand for the platform's currencies only:** the Gems mark beside a Gems
|
|
158
|
+
amount, never for your own currency ([brand-art.md](brand-art.md) §5).
|
|
159
|
+
- **Never inside a panel dressed as the Arcade's own screens.** A player must always be able to tell
|
|
160
|
+
your game from the platform.
|
package/docs/changelog.md
CHANGED
|
@@ -16,6 +16,20 @@ 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
|
+
## 16.3.1
|
|
20
|
+
|
|
21
|
+
Upgrading from 16.3.0. Impact: none.
|
|
22
|
+
|
|
23
|
+
Nothing in this release asks anything of a game.
|
|
24
|
+
|
|
25
|
+
## 16.3.0
|
|
26
|
+
|
|
27
|
+
Upgrading from 16.2.0. Impact: none.
|
|
28
|
+
|
|
29
|
+
### New capability
|
|
30
|
+
|
|
31
|
+
- Games can now read the platform's branding manifest at run time through Gem.branding. A game can show the platform logo, the Arcade wordmark, the currency marks and the trophy, and a branding change on the platform reaches the game with no SDK update or republish. The manifest is read once on the first call and all callers share that read. A lookup by id and kind resolves to one of three outcomes: ok, with the URL, the chosen variant and the whole asset; not_found, when the manifest does not list that id or kind; or unavailable, when the manifest could not be read. In the unavailable case, draw your bundled brand copy instead. Only a bad argument rejects. A list call resolves every asset. You can pass the size you will draw and an optional theme, and the SDK picks the best-matching variant. Nothing is required of existing games.
|
|
32
|
+
|
|
19
33
|
## 16.2.0
|
|
20
34
|
|
|
21
35
|
Upgrading from 16.1.0. Impact: required.
|
package/docs/getting-started.md
CHANGED
|
@@ -828,7 +828,7 @@ inventory, never on a reason, and do not retry because of one.
|
|
|
828
828
|
that looks like the arcade's purchase sheet or wallet. A game screen shaped like platform payment
|
|
829
829
|
chrome is an imitation of it. Your Buy button is yours, and taking a player's money is the
|
|
830
830
|
arcade's alone: a "Get Gems" button of yours calls `gem.store.purchase` from the player's press,
|
|
831
|
-
and the arcade store's own
|
|
831
|
+
and the arcade store's own checkout does the rest ([arcade-and-store.md](arcade-and-store.md)). An
|
|
832
832
|
`insufficient_funds` refusal never opens it by itself.
|
|
833
833
|
|
|
834
834
|
### What Coins and Gems may buy: the spend scope
|
|
@@ -926,8 +926,9 @@ buyButton.disabled = !gems || gems.balance < price; // can the player afford it?
|
|
|
926
926
|
```
|
|
927
927
|
|
|
928
928
|
**Read it to decide, never to display.** Platform-currency display belongs to the arcade: its
|
|
929
|
-
wallet — the Gem menu, in the Gem Arcade
|
|
930
|
-
|
|
929
|
+
wallet — the Gem menu, in the Gem Arcade, and beside the Gem for a game that asks with
|
|
930
|
+
`gem.setWalletVisible(true)` ([the Arcade's wallet](what-a-game-must-do.md#the-arcades-wallet-in-the-gem-menu-and-beside-the-gem-when-you-ask))
|
|
931
|
+
— shows the player's Gems and Coins totals while your game runs, and your game keeps no
|
|
931
932
|
counter, balance or wallet total of either of its own ([brand-art.md](brand-art.md) §5). Use the
|
|
932
933
|
balance to enable a purchase or to say "not enough Gems" — not to draw a number.
|
|
933
934
|
|
package/docs/manifest.json
CHANGED
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
"file": "arcade-and-store.md",
|
|
21
|
-
"sha256": "
|
|
22
|
-
"updated": "2026-10-
|
|
21
|
+
"sha256": "e055b2b1ec540da9fae8f41c025de99b0faec29cef8fca35285ee34e23120941",
|
|
22
|
+
"updated": "2026-10-29T23:35:13Z"
|
|
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-10-
|
|
31
|
+
"sha256": "f0bdc98d765fa4794090fc035d2c32d9c125cbdae58ba89fb5e76fcf1fb9da94",
|
|
32
|
+
"updated": "2026-10-29T23:35:13Z"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"file": "async-opponents.md",
|
|
@@ -38,13 +38,18 @@
|
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
"file": "brand-art.md",
|
|
41
|
-
"sha256": "
|
|
42
|
-
"updated": "2026-10-
|
|
41
|
+
"sha256": "b7f8dda1afdb8d56cadee752e6b09fc62238372b68ebc8a746518e528f52e167",
|
|
42
|
+
"updated": "2026-10-29T21:40:17Z"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"file": "branding.md",
|
|
46
|
+
"sha256": "f22bd82245e6c4a74025884bfda54e35405e990661c84e5857d5e424e182e61d",
|
|
47
|
+
"updated": "2026-10-29T20:56:53Z"
|
|
43
48
|
},
|
|
44
49
|
{
|
|
45
50
|
"file": "changelog.md",
|
|
46
|
-
"sha256": "
|
|
47
|
-
"updated": "2026-10-
|
|
51
|
+
"sha256": "e66efedcc9662bea610d3d4edc4e0125f3078e2b49059770f4553d57f37368b3",
|
|
52
|
+
"updated": "2026-10-29T23:50:25Z"
|
|
48
53
|
},
|
|
49
54
|
{
|
|
50
55
|
"file": "daily-challenges.md",
|
|
@@ -73,13 +78,13 @@
|
|
|
73
78
|
},
|
|
74
79
|
{
|
|
75
80
|
"file": "getting-started.md",
|
|
76
|
-
"sha256": "
|
|
77
|
-
"updated": "2026-10-
|
|
81
|
+
"sha256": "5f6b7c1e3763cb647379ccc96fc8eb4e5f22551f60cfe56bc6a957312f431fe3",
|
|
82
|
+
"updated": "2026-10-29T23:50:25Z"
|
|
78
83
|
},
|
|
79
84
|
{
|
|
80
85
|
"file": "inventory-and-catalogs.md",
|
|
81
|
-
"sha256": "
|
|
82
|
-
"updated": "2026-10-
|
|
86
|
+
"sha256": "032b911f515a7c81b0bcee6065300049651fef8b2108e7930e1d1354eada7a87",
|
|
87
|
+
"updated": "2026-10-29T23:35:13Z"
|
|
83
88
|
},
|
|
84
89
|
{
|
|
85
90
|
"file": "leaderboards.md",
|
|
@@ -133,8 +138,8 @@
|
|
|
133
138
|
},
|
|
134
139
|
{
|
|
135
140
|
"file": "server-bundles.md",
|
|
136
|
-
"sha256": "
|
|
137
|
-
"updated": "2026-10-
|
|
141
|
+
"sha256": "387d213d3770f21dccb32c94aac57b5af32bcac8bbe5ca9a6c3c2d8d51e5a2a2",
|
|
142
|
+
"updated": "2026-10-29T23:50:25Z"
|
|
138
143
|
},
|
|
139
144
|
{
|
|
140
145
|
"file": "serverless-functions-js.md",
|
|
@@ -158,8 +163,8 @@
|
|
|
158
163
|
},
|
|
159
164
|
{
|
|
160
165
|
"file": "what-a-game-must-do.md",
|
|
161
|
-
"sha256": "
|
|
162
|
-
"updated": "2026-10-
|
|
166
|
+
"sha256": "9d6f2121306bfee8b886d9555a644faa843d16f0c5200e257698336d747136b1",
|
|
167
|
+
"updated": "2026-10-29T21:40:17Z"
|
|
163
168
|
}
|
|
164
169
|
]
|
|
165
170
|
}
|
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/v16.
|
|
84
|
-
curl -fsSL https://downloads.gemarcade.com/sdk/v16.
|
|
83
|
+
curl -fsSL -O https://downloads.gemarcade.com/sdk/v16.3.1/gem-web-sdk.tgz
|
|
84
|
+
curl -fsSL https://downloads.gemarcade.com/sdk/v16.3.1/gem-web-sdk.tgz.sri
|
|
85
85
|
openssl dgst -sha384 -binary gem-web-sdk.tgz | openssl base64 -A
|
|
86
86
|
```
|
|
87
87
|
|
|
@@ -783,11 +783,13 @@ game, a framed or full-window game on a desktop, a tablet or a TV in native full
|
|
|
783
783
|
opens [the Arcade's menu](#the-arcades-menu): Leave game, the player's Gems, Coins and
|
|
784
784
|
achievement score, mute, the player's Arcade-wide settings and the rest. Not your settings: those
|
|
785
785
|
are [your own button](#game-settings).
|
|
786
|
-
- **Nothing else.** The player's wallets (Gems, Coins, their achievement score) are
|
|
787
|
-
over
|
|
788
|
-
flies into the Gem button
|
|
789
|
-
|
|
790
|
-
|
|
786
|
+
- **Nothing else, unless you ask.** The player's wallets (Gems, Coins, their achievement score) are
|
|
787
|
+
**not** drawn over a game that says nothing: the player opens the Gem menu to see them, and every
|
|
788
|
+
platform-currency payout flies into the Gem button. A game that wants them on screen asks with
|
|
789
|
+
`gem.setWalletVisible(true)`, and the Arcade draws them beside the Gem
|
|
790
|
+
([the Arcade's wallet](#the-arcades-wallet-in-the-gem-menu-and-beside-the-gem-when-you-ask)).
|
|
791
|
+
When the player switches the frame-rate readout on, it sits beside the gem; the readout and any
|
|
792
|
+
wallets you asked for are reserved the same way as the Gem.
|
|
791
793
|
|
|
792
794
|
### The top of the screen
|
|
793
795
|
|
|
@@ -809,7 +811,10 @@ where "back" and "settings" are once, across every game:
|
|
|
809
811
|
SDK gives you as `--gem-reserved-tl-center-y` (below), never on the middle of its reserved area.
|
|
810
812
|
- **Your settings button is there on every screen.** It goes top-right even where no Arcade control
|
|
811
813
|
is: another embedder may keep one in that corner, so place it from the reserved areas below and it
|
|
812
|
-
moves aside for whatever is there.
|
|
814
|
+
moves aside for whatever is there. Inside the Gem Arcade you may leave it out, on every screen
|
|
815
|
+
at once, when the Arcade's menu reaches everything it did
|
|
816
|
+
([a game whose menu rows replace its button](#leaving-your-settings-button-out-inside-the-arcade));
|
|
817
|
+
Back then takes the end of the row.
|
|
813
818
|
|
|
814
819
|
The parts of your frame under the Gem and any other Arcade control are **reserved**: keep
|
|
815
820
|
essential UI and every touch target out of them. **Never hard-code their size or position.** They
|
|
@@ -1071,11 +1076,16 @@ This is the whole model, and every other page that mentions settings links here:
|
|
|
1071
1076
|
them ([the Arcade's Music and Sound switches](#the-arcades-music-and-sound-switches-onaudiochannels)).
|
|
1072
1077
|
- **Every game draws its own settings button, top-right ([the top of the
|
|
1073
1078
|
screen](#the-top-of-the-screen)), and its own settings screen.** The Arcade's menu never opens it.
|
|
1079
|
+
Inside the Gem Arcade the button may go when the Arcade's menu holds every setting and action of
|
|
1080
|
+
your screen ([below](#leaving-your-settings-button-out-inside-the-arcade)); the screen stays,
|
|
1081
|
+
for every other embedder.
|
|
1074
1082
|
- **A game should also declare its settings — all of them, up to eight — to the Arcade's menu**,
|
|
1075
1083
|
with `gem.settings.declare`: the Arcade's Settings screen lists every one, and the first
|
|
1076
1084
|
colour-help toggle fills the Color slot of its fixed quick row (the first music toggle fills Music
|
|
1077
1085
|
for a game that does not pass `onAudioChannels`). It is a second way
|
|
1078
|
-
to reach them
|
|
1086
|
+
to reach them — the only one only where a game
|
|
1087
|
+
[leaves its button out inside the Arcade](#leaving-your-settings-button-out-inside-the-arcade) —
|
|
1088
|
+
and never a second mute or vibration switch ([mirror your
|
|
1079
1089
|
settings into the Arcade's menu](#mirror-your-settings-into-the-arcades-menu-gemsettings)).
|
|
1080
1090
|
- **A setting starts from `recommended` until the player changes it in your game, and then the
|
|
1081
1091
|
player's choice in your game wins.** Save only what the player chose, never a recommended value.
|
|
@@ -1222,11 +1232,41 @@ player's Arcade-wide ones, in two places:
|
|
|
1222
1232
|
slot that nothing fills stays in its place, greyed and inert. Nothing else of yours reaches the
|
|
1223
1233
|
quick row.
|
|
1224
1234
|
|
|
1225
|
-
**It is a mirror
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
that dropped its button
|
|
1229
|
-
reach.
|
|
1235
|
+
**It is a mirror of your own screen: keep that screen**, with every setting you declare on it too.
|
|
1236
|
+
An embedder that draws no game settings shows none of this — your own screen is what remains there.
|
|
1237
|
+
Your own button stays too, except inside the Gem Arcade under the conditions in the next section:
|
|
1238
|
+
a game that dropped its button anywhere else, or while a setting was missing from the menu, would
|
|
1239
|
+
have settings nobody can reach.
|
|
1240
|
+
|
|
1241
|
+
#### Leaving your settings button out inside the Arcade
|
|
1242
|
+
|
|
1243
|
+
A game may leave its own settings button out inside the Gem Arcade (the owner, 2026-10-29), when
|
|
1244
|
+
the Arcade's menu reaches everything the button did:
|
|
1245
|
+
|
|
1246
|
+
- **Every toggle, slider and choice on your settings screen is declared** with
|
|
1247
|
+
`gem.settings.declare`, so the screen has eight of them at most.
|
|
1248
|
+
- **Your mute and vibration are the quick row's**, wired both ways as
|
|
1249
|
+
[Sound](#sound-your-own-mute-control-and-the-arcades) says.
|
|
1250
|
+
- **Every action on the screen is one of your own menu rows** — Replay the tutorial, How to play,
|
|
1251
|
+
Credits — with [`gem.setMenu({ items })`](#the-arcades-menu), so four at most.
|
|
1252
|
+
|
|
1253
|
+
Anything more (a ninth setting, a text field, a key-binding screen) keeps the button. **Hide it only
|
|
1254
|
+
while `gem.setMenu` returns `true`**: that is an Arcade that has connected and draws a menu. It
|
|
1255
|
+
returns `false` before the bridge is up, under an embedder with no menu and in a plain tab, and
|
|
1256
|
+
there the button stays, with a way out of your own where there is no menu. Ask again when the frame
|
|
1257
|
+
connects (`onStateChange` in `createGem`) and when the language changes, since your rows carry
|
|
1258
|
+
your words. Hide it on every screen alike, never on some, and let Back take the end of the top row.
|
|
1259
|
+
|
|
1260
|
+
```js
|
|
1261
|
+
let arcadeMenu = false;
|
|
1262
|
+
function offerMenu() {
|
|
1263
|
+
arcadeMenu = gem.setMenu({
|
|
1264
|
+
items: [{ id: 'replay-tutorial', label: t('Replay the tutorial'), onSelect: replayTutorial }],
|
|
1265
|
+
});
|
|
1266
|
+
document.documentElement.classList.toggle('arcade-menu', arcadeMenu); // CSS hides the button
|
|
1267
|
+
}
|
|
1268
|
+
offerMenu(); // and again from onStateChange, and on every language change
|
|
1269
|
+
```
|
|
1230
1270
|
|
|
1231
1271
|
```js
|
|
1232
1272
|
// Built from the same state your own settings screen shows: every setting on it.
|
|
@@ -1498,8 +1538,9 @@ gem.setMenu({
|
|
|
1498
1538
|
([p2p-and-parties.md](p2p-and-parties.md) §7.4) needs nothing more for this.
|
|
1499
1539
|
|
|
1500
1540
|
`gem.setMenu` returns `false` under an embedder with no menu, and outside any embedder. Your
|
|
1501
|
-
settings button is there
|
|
1502
|
-
|
|
1541
|
+
settings button is there then, even in a game that
|
|
1542
|
+
[leaves it out inside the Arcade](#leaving-your-settings-button-out-inside-the-arcade); what such a
|
|
1543
|
+
game also needs is a way out of its own, a control that calls `gem.exit()`.
|
|
1503
1544
|
|
|
1504
1545
|
Your own **back or home** button — the one that moves around inside your game — goes **top-right,
|
|
1505
1546
|
just left of your settings button** ([the top of the screen](#the-top-of-the-screen)). It never
|
|
@@ -1650,24 +1691,35 @@ its music out than cut it, say. The Arcade still shows its button for your game
|
|
|
1650
1691
|
so this is a promise: your `onPrefs` handler must go quiet on `muted: true` and come back on `false`,
|
|
1651
1692
|
and the release checklist checks that it does.
|
|
1652
1693
|
|
|
1653
|
-
## The Arcade's wallet: in the Gem menu,
|
|
1694
|
+
## The Arcade's wallet: in the Gem menu, and beside the Gem when you ask
|
|
1654
1695
|
|
|
1655
1696
|
The player's platform balances — Gems, Coins (`ARCADE_TICKETS`) and their achievement score —
|
|
1656
1697
|
belong to the Arcade. **Your game never draws one**, and never shows a pending state for one: the
|
|
1657
1698
|
Arcade takes every figure from the platform and shows them itself.
|
|
1658
1699
|
|
|
1659
|
-
**The Gem Arcade draws no wallet over
|
|
1660
|
-
|
|
1661
|
-
|
|
1700
|
+
**The Gem Arcade draws no wallet over a game that says nothing** (since 2026-10-11). The player
|
|
1701
|
+
opens the Gem menu — the Gem button, in whichever corner it sits — to see their Gems, Coins and
|
|
1702
|
+
achievement score, and every payout
|
|
1662
1703
|
[`gem.cueReward`](inventory-and-catalogs.md#showing-the-reward-land-in-the-arcades-wallet) announces
|
|
1663
1704
|
flies into the Gem button. A payout you do not cue flies too, from the middle of your frame, so cue
|
|
1664
1705
|
the ones the player earns on screen and the flight starts where they are looking.
|
|
1665
1706
|
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1707
|
+
**A game that wants them on screen asks** (the owner, 2026-10-29):
|
|
1708
|
+
|
|
1709
|
+
```js
|
|
1710
|
+
gem.setWalletVisible(true, { only: ['GEMS', 'ARCADE_TICKETS'] }); // add 'score' for the achievement score
|
|
1711
|
+
```
|
|
1712
|
+
|
|
1713
|
+
- **The Arcade draws them beside the Gem**, from the platform's own figures, and reserves them like
|
|
1714
|
+
the Gem: they arrive in `gem.viewport.reserved` and widen the Gem's corner in the
|
|
1715
|
+
`--gem-reserved-*` variables, so a top row laid out from those moves over by itself.
|
|
1716
|
+
- **`only` names the wallets.** Without it every wallet shows, the achievement score's chip
|
|
1717
|
+
included; a slug the platform does not serve names nothing.
|
|
1718
|
+
- **It is screen state, not a toggle**, held across a reconnect: ask once for every screen, or per
|
|
1719
|
+
screen, and `gem.setWalletVisible(false)` puts them away (during play, say). Nothing tells the
|
|
1720
|
+
game a balance changed, and nothing needs to: the figures are the Arcade's.
|
|
1721
|
+
- **An Arcade from before this change ignores the call**: nothing breaks, and the wallets stay in
|
|
1722
|
+
the Gem menu. Another embedder that draws wallets over games honours it the same way.
|
|
1671
1723
|
|
|
1672
1724
|
## No accidental text selection
|
|
1673
1725
|
|