@indiegems/gem-web-sdk 16.3.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 +6 -0
- package/dist/gem-web-sdk.conformance.js +1 -1
- 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/docs/api/api-reference-function.md +1 -1
- package/docs/api/api-reference-game.md +43 -34
- package/docs/api/api-reference-server.md +1 -1
- package/docs/api/reference.json +9 -9
- package/docs/arcade-and-store.md +22 -17
- package/docs/arcade-economy.md +4 -4
- package/docs/brand-art.md +3 -1
- package/docs/changelog.md +6 -0
- package/docs/getting-started.md +1 -1
- package/docs/inventory-and-catalogs.md +4 -3
- package/docs/manifest.json +16 -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
|
|
@@ -221,7 +221,7 @@ Gem pack, a `grant` naming the currency and amount.
|
|
|
221
221
|
currency, already formatted. Never parse it, compare two of them, or compute a discount from it.
|
|
222
222
|
There is no numeric price.
|
|
223
223
|
- **Re-read offers when your game resumes.** What is on sale can change while the player is away.
|
|
224
|
-
- **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
|
|
225
225
|
the money is the Arcade's, and a game that draws something that looks like it confuses players
|
|
226
226
|
about which one is real.
|
|
227
227
|
- `unavailable` / `store_off` means the store is not offered here (a TV, an in-app browser that
|
|
@@ -234,12 +234,15 @@ Gem pack, a `grant` naming the currency and amount.
|
|
|
234
234
|
const { outcome, reason } = await gem.store.purchase(offer.skuId);
|
|
235
235
|
```
|
|
236
236
|
|
|
237
|
-
`purchase(skuId)` asks the Arcade to open
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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.
|
|
241
242
|
|
|
242
|
-
- **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`).
|
|
243
246
|
- **It has no timeout.** A payment takes as long as it takes, so the call waits for as long as the
|
|
244
247
|
Arcade is connected.
|
|
245
248
|
- `ok`: the player paid. **Read balances and inventory through `gem.me`**; the answer carries no
|
|
@@ -252,14 +255,16 @@ own celebration is visible.
|
|
|
252
255
|
const stop = gem.store.onSettled(({ skuId }) => refreshBalances());
|
|
253
256
|
```
|
|
254
257
|
|
|
255
|
-
- `declined` / `cancelled`: the player closed the
|
|
256
|
-
ask again unprompted**: the Arcade backs off after
|
|
257
|
-
`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.
|
|
258
263
|
- `unavailable` with `store_off`, `sign_in_required` or `not_found` (a SKU that is not in
|
|
259
264
|
`offers()` right now), or with a payment provider's own failure code: nothing was bought. The
|
|
260
265
|
first three show nothing.
|
|
261
266
|
- `unavailable` / `error`: **the outcome is unknown, and the player may have paid.** You get it
|
|
262
|
-
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
|
|
263
268
|
went through, and when the Arcade has no reliable answer. Never tell the player nothing was
|
|
264
269
|
bought. Re-read balances and inventory through `gem.me`, keep your `gem.store.onSettled`
|
|
265
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
|
@@ -296,7 +296,9 @@ decision of that date); the ruling's other rules are unchanged. In rules:
|
|
|
296
296
|
|
|
297
297
|
- **Keep no Gems counter.** Your game shows no Gems balance, running total or wallet of its own, on
|
|
298
298
|
any screen. The arcade's wallet is the only place Gems accrue, and the only place a total is shown
|
|
299
|
-
— 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)).
|
|
300
302
|
- **Draw no Gems flight or celebration of your own.** No particles flying to a corner, no counting-up
|
|
301
303
|
number, no burst of gems. `gem.cueReward` is the animation, and the arcade draws it.
|
|
302
304
|
- **Show no Gems amount at all when you are not connected to the arcade.** Without the arcade there is
|
package/docs/changelog.md
CHANGED
|
@@ -16,6 +16,12 @@ 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
|
+
|
|
19
25
|
## 16.3.0
|
|
20
26
|
|
|
21
27
|
Upgrading from 16.2.0. Impact: none.
|
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,8 +38,8 @@
|
|
|
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
43
|
},
|
|
44
44
|
{
|
|
45
45
|
"file": "branding.md",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
"file": "changelog.md",
|
|
51
|
-
"sha256": "
|
|
52
|
-
"updated": "2026-10-
|
|
51
|
+
"sha256": "e66efedcc9662bea610d3d4edc4e0125f3078e2b49059770f4553d57f37368b3",
|
|
52
|
+
"updated": "2026-10-29T23:50:25Z"
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
"file": "daily-challenges.md",
|
|
@@ -78,13 +78,13 @@
|
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
"file": "getting-started.md",
|
|
81
|
-
"sha256": "
|
|
82
|
-
"updated": "2026-10-
|
|
81
|
+
"sha256": "5f6b7c1e3763cb647379ccc96fc8eb4e5f22551f60cfe56bc6a957312f431fe3",
|
|
82
|
+
"updated": "2026-10-29T23:50:25Z"
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
85
|
"file": "inventory-and-catalogs.md",
|
|
86
|
-
"sha256": "
|
|
87
|
-
"updated": "2026-10-
|
|
86
|
+
"sha256": "032b911f515a7c81b0bcee6065300049651fef8b2108e7930e1d1354eada7a87",
|
|
87
|
+
"updated": "2026-10-29T23:35:13Z"
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
90
|
"file": "leaderboards.md",
|
|
@@ -138,8 +138,8 @@
|
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
"file": "server-bundles.md",
|
|
141
|
-
"sha256": "
|
|
142
|
-
"updated": "2026-10-
|
|
141
|
+
"sha256": "387d213d3770f21dccb32c94aac57b5af32bcac8bbe5ca9a6c3c2d8d51e5a2a2",
|
|
142
|
+
"updated": "2026-10-29T23:50:25Z"
|
|
143
143
|
},
|
|
144
144
|
{
|
|
145
145
|
"file": "serverless-functions-js.md",
|
|
@@ -163,8 +163,8 @@
|
|
|
163
163
|
},
|
|
164
164
|
{
|
|
165
165
|
"file": "what-a-game-must-do.md",
|
|
166
|
-
"sha256": "
|
|
167
|
-
"updated": "2026-10-
|
|
166
|
+
"sha256": "9d6f2121306bfee8b886d9555a644faa843d16f0c5200e257698336d747136b1",
|
|
167
|
+
"updated": "2026-10-29T21:40:17Z"
|
|
168
168
|
}
|
|
169
169
|
]
|
|
170
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.3.
|
|
84
|
-
curl -fsSL https://downloads.gemarcade.com/sdk/v16.3.
|
|
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
|
|
package/package.json
CHANGED
package/schema/bridge.v1.json
CHANGED
|
@@ -3771,8 +3771,8 @@
|
|
|
3771
3771
|
" arcade's store for one of the store's own SKUs -",
|
|
3772
3772
|
" gem.requestStorePurchase, gated on `store` -",
|
|
3773
3773
|
" but only from the player's press on a button of the game's; that",
|
|
3774
|
-
"
|
|
3775
|
-
" it by itself on a refusal here.",
|
|
3774
|
+
" checkout is the Arcade Store's, under its own rules, and nothing",
|
|
3775
|
+
" opens it by itself on a refusal here.",
|
|
3776
3776
|
" - MONEY IS TAKEN ONLY ON THE PAYMENT PROVIDER'S PAGE OR THE OPERATING",
|
|
3777
3777
|
" SYSTEM'S PURCHASE SHEET, which a player reaches through the parent's",
|
|
3778
3778
|
" own chrome. Nothing a frame sends carries a price or a card field,",
|
|
@@ -5342,7 +5342,9 @@
|
|
|
5342
5342
|
"rail's own formatted string, and a game shows it exactly as given,",
|
|
5343
5343
|
"never parsing it or computing a discount from it. No balance. And no",
|
|
5344
5344
|
"per-offer availability flag: one would reveal a recent purchase through",
|
|
5345
|
-
"its cooldown, so the parent
|
|
5345
|
+
"its cooldown, so the parent explains a cooldown in its own chrome",
|
|
5346
|
+
"instead, and a purchase of that offer is answered `declined` /",
|
|
5347
|
+
"`cancelled`.",
|
|
5346
5348
|
"",
|
|
5347
5349
|
"THE NAMES ARE THE PLATFORM'S, not a game developer's: the Arcade",
|
|
5348
5350
|
"Store's own catalog copy, which is why `name` and `description` sit",
|
|
@@ -5438,14 +5440,14 @@
|
|
|
5438
5440
|
"direction": "frameToParent",
|
|
5439
5441
|
"transport": "port",
|
|
5440
5442
|
"$comment": [
|
|
5441
|
-
"Ask the parent to open the Arcade Store's
|
|
5443
|
+
"Ask the parent to open the Arcade Store's checkout for ONE SKU it",
|
|
5442
5444
|
"listed in gem.storeOffersResult - real money, not Gems. Gated by the",
|
|
5443
5445
|
"`store` capability. Answered by `gem.storePurchaseResult`.",
|
|
5444
5446
|
"",
|
|
5445
5447
|
"THE GAME ASKS; THE PLATFORM SELLS. Nothing in this message is a price,",
|
|
5446
|
-
"a card field, a URL or a token. The parent
|
|
5447
|
-
"
|
|
5448
|
-
"
|
|
5448
|
+
"a card field, a URL or a token. The parent looks the SKU up in its own",
|
|
5449
|
+
"listing, takes money only on the payment provider's page or the",
|
|
5450
|
+
"operating system's purchase sheet, and tells the game the outcome.",
|
|
5449
5451
|
"",
|
|
5450
5452
|
"AN ACTING MESSAGE, under every rule gem.requestOpenApp states: a press",
|
|
5451
5453
|
"the parent can see (a missing `userActivation` API fails closed), one",
|
|
@@ -5456,17 +5458,26 @@
|
|
|
5456
5458
|
" would list to this player right now; anything else is `unavailable`",
|
|
5457
5459
|
" with nothing shown.",
|
|
5458
5460
|
"",
|
|
5459
|
-
"
|
|
5460
|
-
"
|
|
5461
|
-
"
|
|
5462
|
-
"
|
|
5463
|
-
"
|
|
5461
|
+
" STRAIGHT TO CHECKOUT, with no confirmation of the parent's own first,",
|
|
5462
|
+
" by owner decision: games are developed in-house and trusted, as for",
|
|
5463
|
+
" gem.requestOpenGame. The press in the game opens the",
|
|
5464
|
+
" payment provider's page on the web, or the operating system's",
|
|
5465
|
+
" purchase sheet in an app, over the paused game: hidden, not dimmed.",
|
|
5466
|
+
" That checkout is the confirmation - it has its own pay control, and",
|
|
5467
|
+
" no money moves without the player's press there.",
|
|
5468
|
+
"",
|
|
5469
|
+
" AN OFFER COOLING DOWN OPENS NOTHING. When the listed SKU cannot be",
|
|
5470
|
+
" bought again yet, the parent says when, in its own chrome, and",
|
|
5471
|
+
" answers `declined` / `cancelled` - the answer a closed checkout gets,",
|
|
5472
|
+
" so the cooldown, which would reveal a recent purchase, never crosses",
|
|
5473
|
+
" the bridge. It starts no back-off: the player turned nothing down.",
|
|
5464
5474
|
"",
|
|
5465
5475
|
" BACK OFF AFTER A DISMISSAL. `cancelled` is disclosed, so after the",
|
|
5466
|
-
" player
|
|
5476
|
+
" player closes a checkout a game started, further asks from that game",
|
|
5467
5477
|
" are `declined` / `busy`, unrendered, for an interval that grows with",
|
|
5468
5478
|
" each dismissal (the Arcade: 30 s, 2 min, 10 min, then the rest of the",
|
|
5469
|
-
" session).",
|
|
5479
|
+
" session). Only the player closing it starts the back-off: a failed",
|
|
5480
|
+
" payment, `error` and `ok` / `pending` do not.",
|
|
5470
5481
|
"",
|
|
5471
5482
|
" RESUME, THEN ANSWER. The result is sent after the game has been told",
|
|
5472
5483
|
" it is resumed, so the game's own celebration is visible.",
|
|
@@ -5508,9 +5519,10 @@
|
|
|
5508
5519
|
" `reason: pending`, the provider has not settled yet (or",
|
|
5509
5520
|
" the player left before it did): `gem.storePurchaseSettled`",
|
|
5510
5521
|
" follows when it does, and a game re-reads on resume too.",
|
|
5511
|
-
" declined - nothing was bought: the player
|
|
5512
|
-
" (`cancelled`)
|
|
5513
|
-
" or a back-off
|
|
5522
|
+
" declined - nothing was bought: the player closed the checkout,",
|
|
5523
|
+
" or the offer is cooling down (`cancelled`); or no",
|
|
5524
|
+
" press, another acting message open or a back-off",
|
|
5525
|
+
" running (`busy`).",
|
|
5514
5526
|
" unavailable - no sale the parent can vouch for. With `store_off`",
|
|
5515
5527
|
" (the store withheld), `sign_in_required` (a guest),",
|
|
5516
5528
|
" `not_found` (an unknown SKU) or the provider's own",
|