@indiegems/gem-web-sdk 16.3.0 → 17.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 +33 -0
- package/dist/conformance.d.ts +1 -1
- package/dist/game.d.ts +17 -2
- 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 +2 -2
- 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/index.d.ts +27 -8
- package/dist/sdk.d.ts +17 -2
- package/docs/api/api-reference-function.md +1 -1
- package/docs/api/api-reference-game.md +97 -36
- package/docs/api/api-reference-server.md +1 -1
- package/docs/api/reference.json +49 -10
- package/docs/arcade-and-store.md +91 -26
- package/docs/arcade-economy.md +7 -6
- package/docs/brand-art.md +3 -1
- package/docs/changelog.md +33 -0
- package/docs/getting-started.md +1 -1
- package/docs/inventory-and-catalogs.md +6 -4
- 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 +137 -17
- package/schema/capabilities.public.json +7 -0
package/docs/arcade-economy.md
CHANGED
|
@@ -204,10 +204,10 @@ player got.
|
|
|
204
204
|
Nothing sells Coins or achievement points, alone or in a bundle. The arcade's own store sells
|
|
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
|
-
either itself. It may put the arcade store's own Gem packs and
|
|
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
|
|
207
|
+
either itself. It may put the arcade store's own Gem packs, Boosts and passes in front of a player
|
|
208
|
+
from a button of its own, through `gem.store`, and the arcade prices them, opens its own checkout
|
|
209
|
+
and 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
|
|
@@ -246,8 +246,9 @@ animates every payout into the right total. Gems and Coins follow the same rules
|
|
|
246
246
|
- **No top-up or payment screen of your own.** Never draw a "buy Coins" offer, a checkout, a
|
|
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
|
-
own Buy button is yours, and so is a "Get Gems" button that calls `gem.store.purchase
|
|
250
|
-
player's press: the
|
|
249
|
+
own Buy button is yours, and so is a "Get Gems" button that calls `gem.store.purchase`, or a "Get
|
|
250
|
+
VIP" button that calls `gem.store.showOffer`, from the player's press: the checkout each one
|
|
251
|
+
leads to is the arcade store's own
|
|
251
252
|
([arcade-and-store.md](arcade-and-store.md)). When a purchase is refused for
|
|
252
253
|
`insufficient_funds`, say so in your shop's own words ("Not enough Coins") and stop there: a
|
|
253
254
|
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,39 @@ 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
|
+
## 17.0.0
|
|
20
|
+
|
|
21
|
+
*Written from the API facts alone; no summary was generated for this release.*
|
|
22
|
+
|
|
23
|
+
Upgrading from 16.3.1. Impact: required.
|
|
24
|
+
|
|
25
|
+
### Must change
|
|
26
|
+
|
|
27
|
+
- PARENT_CONFORMANCE_VERSION changed its signature.
|
|
28
|
+
- FRAME_TO_PARENT changed its signature.
|
|
29
|
+
- FrameMessage changed its signature.
|
|
30
|
+
- GemCapability changed its signature.
|
|
31
|
+
- PARENT_TO_FRAME changed its signature.
|
|
32
|
+
- ParentMessage changed its signature.
|
|
33
|
+
|
|
34
|
+
### New capability
|
|
35
|
+
|
|
36
|
+
- StoreApi.showOffer was added.
|
|
37
|
+
- StoreApi.showOfferAvailable was added.
|
|
38
|
+
- GemHost.answerStoreOfferSheet was added.
|
|
39
|
+
- GemRequestStoreOfferSheet was added.
|
|
40
|
+
- GemStoreOfferSheetResult was added.
|
|
41
|
+
- HostCallbacks.onRequestStoreOfferSheet was added.
|
|
42
|
+
- HostOptions.onRequestStoreOfferSheet was added.
|
|
43
|
+
- validateGemRequestStoreOfferSheet was added.
|
|
44
|
+
- validateGemStoreOfferSheetResult was added.
|
|
45
|
+
|
|
46
|
+
## 16.3.1
|
|
47
|
+
|
|
48
|
+
Upgrading from 16.3.0. Impact: none.
|
|
49
|
+
|
|
50
|
+
Nothing in this release asks anything of a game.
|
|
51
|
+
|
|
19
52
|
## 16.3.0
|
|
20
53
|
|
|
21
54
|
Upgrading from 16.2.0. Impact: none.
|
package/docs/getting-started.md
CHANGED
|
@@ -827,8 +827,9 @@ inventory, never on a reason, and do not retry because of one.
|
|
|
827
827
|
⚠️ **Never draw a top-up or payment screen of your own** — no checkout, no wallet top-up, nothing
|
|
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
|
-
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
|
|
830
|
+
arcade's alone: a "Get Gems" button of yours calls `gem.store.purchase` from the player's press, a
|
|
831
|
+
"Get VIP" button calls `gem.store.showOffer`, and the arcade store's own checkout does the rest
|
|
832
|
+
([arcade-and-store.md](arcade-and-store.md)). An
|
|
832
833
|
`insufficient_funds` refusal never opens it by itself.
|
|
833
834
|
|
|
834
835
|
### What Coins and Gems may buy: the spend scope
|
|
@@ -926,8 +927,9 @@ buyButton.disabled = !gems || gems.balance < price; // can the player afford it?
|
|
|
926
927
|
```
|
|
927
928
|
|
|
928
929
|
**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
|
-
|
|
930
|
+
wallet — the Gem menu, in the Gem Arcade, and beside the Gem for a game that asks with
|
|
931
|
+
`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))
|
|
932
|
+
— shows the player's Gems and Coins totals while your game runs, and your game keeps no
|
|
931
933
|
counter, balance or wallet total of either of its own ([brand-art.md](brand-art.md) §5). Use the
|
|
932
934
|
balance to enable a purchase or to say "not enough Gems" — not to draw a number.
|
|
933
935
|
|
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": "7320826529435d290159c8b0707f69819124607d1b38160dbc85f63b385fdea9",
|
|
22
|
+
"updated": "2026-10-30T13:36:37Z"
|
|
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": "b72474b4694842a1fda030ab9df79987d2906f7a8c922e8630622d747a4b2c3c",
|
|
32
|
+
"updated": "2026-10-30T13:36:37Z"
|
|
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": "b795d5ed73558a4d355196971fb4bd9689ca15d38fd01d9a67b4cf6c0e8ff25d",
|
|
52
|
+
"updated": "2026-10-30T14:09:44Z"
|
|
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": "a69a8be43e655a71717c1d15ff3e9136103c17eebd9074c2f3dab4fad07d2689",
|
|
82
|
+
"updated": "2026-10-30T14:09:44Z"
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
85
|
"file": "inventory-and-catalogs.md",
|
|
86
|
-
"sha256": "
|
|
87
|
-
"updated": "2026-10-
|
|
86
|
+
"sha256": "9c898355af92ab7e17035bad964dce57b26e64624b18e83149b88ae10b96fd9c",
|
|
87
|
+
"updated": "2026-10-30T13:36:37Z"
|
|
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": "d00bc2ea3896c2e262619bf87b2892dd954c9ef87d37dfb5881bb6ef7439401a",
|
|
142
|
+
"updated": "2026-10-30T14:09:44Z"
|
|
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/
|
|
84
|
-
curl -fsSL https://downloads.gemarcade.com/sdk/
|
|
83
|
+
curl -fsSL -O https://downloads.gemarcade.com/sdk/v17.0.0/gem-web-sdk.tgz
|
|
84
|
+
curl -fsSL https://downloads.gemarcade.com/sdk/v17.0.0/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",
|
|
@@ -5569,6 +5581,114 @@
|
|
|
5569
5581
|
}
|
|
5570
5582
|
},
|
|
5571
5583
|
"rateLimit": { "perMinute": 30 }
|
|
5584
|
+
},
|
|
5585
|
+
{
|
|
5586
|
+
"type": "gem.requestStoreOfferSheet",
|
|
5587
|
+
"direction": "frameToParent",
|
|
5588
|
+
"transport": "port",
|
|
5589
|
+
"$comment": [
|
|
5590
|
+
"Ask the parent to SHOW one SKU it listed in gem.storeOffersResult, in",
|
|
5591
|
+
"the Arcade's own one-offer sheet over the paused game - real money, not",
|
|
5592
|
+
"Gems. Gated by the `storeOfferSheet` capability. Answered by",
|
|
5593
|
+
"`gem.storeOfferSheetResult`.",
|
|
5594
|
+
"",
|
|
5595
|
+
"THE ARCADE DRAWS THE OFFER; THE GAME ONLY POINTS AT IT. gem.requestStorePurchase",
|
|
5596
|
+
"is for an offer the game drew itself from gem.storeOffersResult, and goes",
|
|
5597
|
+
"straight to checkout. This is for an offer whose pitch the bridge cannot",
|
|
5598
|
+
"carry: its perks, a sale, what a holder already has. All of that stays",
|
|
5599
|
+
"with the parent, so none of it - least of all whether this player holds",
|
|
5600
|
+
"a pass - crosses to the game. The sheet's Buy opens the same checkout",
|
|
5601
|
+
"gem.requestStorePurchase opens.",
|
|
5602
|
+
"",
|
|
5603
|
+
"EVERY RULE OF gem.requestStorePurchase HOLDS, and it shares them rather",
|
|
5604
|
+
"than having its own: an acting message (a press the parent can see, one",
|
|
5605
|
+
"at a time across all of them, both checked before `skuId` is resolved,",
|
|
5606
|
+
"the answer bound to this frame session); only a SKU the parent would",
|
|
5607
|
+
"list now; an offer cooling down opens nothing and answers `declined` /",
|
|
5608
|
+
"`cancelled`; the SAME back-off, so a game cannot escape one by switching",
|
|
5609
|
+
"message; resume, then answer; evidence, not accounting.",
|
|
5610
|
+
"",
|
|
5611
|
+
"THE SHEET IS NOT A SECOND CONFIRMATION, and it does not change",
|
|
5612
|
+
"gem.requestStorePurchase's straight-to-checkout rule. It is the pitch;",
|
|
5613
|
+
"the checkout is still the confirmation, with its own pay control. The",
|
|
5614
|
+
"player leaving the sheet without pressing Buy is a dismissal, exactly",
|
|
5615
|
+
"as closing a checkout is, and starts the back-off.",
|
|
5616
|
+
"",
|
|
5617
|
+
"A PARENT THAT ADVERTISES `storeOfferSheet` ADVERTISES `store`: the",
|
|
5618
|
+
"game's SKU comes from gem.storeOffersResult, and a purchase that answers",
|
|
5619
|
+
"`ok` / `pending` here is followed by gem.storePurchaseSettled.",
|
|
5620
|
+
"",
|
|
5621
|
+
"Every field is untrusted text: match `skuId` against the offers you",
|
|
5622
|
+
"read, never interpolate it into a path, a query or a log line."
|
|
5623
|
+
],
|
|
5624
|
+
"fields": {
|
|
5625
|
+
"requestId": {
|
|
5626
|
+
"kind": "string",
|
|
5627
|
+
"maxLength": 64,
|
|
5628
|
+
"$comment": "Opaque and frame-chosen. Echo-only, and never a key on the money path: the parent derives its own request identity."
|
|
5629
|
+
},
|
|
5630
|
+
"skuId": {
|
|
5631
|
+
"kind": "string",
|
|
5632
|
+
"maxLength": 64,
|
|
5633
|
+
"$comment": "One `skuId` from gem.storeOffersResult. Frame-chosen: matched by the parent against the listing it would show now."
|
|
5634
|
+
}
|
|
5635
|
+
},
|
|
5636
|
+
"rateLimit": {
|
|
5637
|
+
"perMinute": 12,
|
|
5638
|
+
"$comment": "gem.requestStorePurchase's limit and its argument: it bounds a BUGGY game, not a hostile one. What bounds the player's experience is the activation check, the one-at-a-time rule and the back-off."
|
|
5639
|
+
}
|
|
5640
|
+
},
|
|
5641
|
+
{
|
|
5642
|
+
"type": "gem.storeOfferSheetResult",
|
|
5643
|
+
"direction": "parentToFrame",
|
|
5644
|
+
"transport": "port",
|
|
5645
|
+
"$comment": [
|
|
5646
|
+
"The answer to one gem.requestStoreOfferSheet, carrying exactly what",
|
|
5647
|
+
"gem.storePurchaseResult carries and meaning the same. Gated by the",
|
|
5648
|
+
"`storeOfferSheet` capability.",
|
|
5649
|
+
"",
|
|
5650
|
+
" ok - the player paid. With no reason, fulfilment has",
|
|
5651
|
+
" settled; with `reason: pending`,",
|
|
5652
|
+
" gem.storePurchaseSettled follows when it does.",
|
|
5653
|
+
" declined - nothing was bought: the player left the sheet or closed",
|
|
5654
|
+
" the checkout, or the offer is cooling down (`cancelled`);",
|
|
5655
|
+
" or no press, another acting message open or a back-off",
|
|
5656
|
+
" running (`busy`).",
|
|
5657
|
+
" unavailable - no sale the parent can vouch for. With `store_off`,",
|
|
5658
|
+
" `sign_in_required`, `not_found` or the provider's own",
|
|
5659
|
+
" failure code, nothing was bought. With `error`, the",
|
|
5660
|
+
" OUTCOME IS UNKNOWN and the player may have paid. The SDK",
|
|
5661
|
+
" resolves the same `unavailable` / `error` when the port",
|
|
5662
|
+
" closes under an open sheet.",
|
|
5663
|
+
"",
|
|
5664
|
+
"`cancelled` DOES NOT SAY WHICH. The game is not told whether the player",
|
|
5665
|
+
"left the sheet or the checkout, and nothing here says what the sheet",
|
|
5666
|
+
"showed.",
|
|
5667
|
+
"",
|
|
5668
|
+
"NO TIMEOUT ON THE GAME'S SIDE while the port is alive: the parent always",
|
|
5669
|
+
"answers, and a player may read a sheet for as long as they like.",
|
|
5670
|
+
"NEVER AUTO-RETRIED. Only a fresh press may ask again."
|
|
5671
|
+
],
|
|
5672
|
+
"fields": {
|
|
5673
|
+
"requestId": {
|
|
5674
|
+
"kind": "string",
|
|
5675
|
+
"maxLength": 64,
|
|
5676
|
+
"$comment": "Echoes the gem.requestStoreOfferSheet that asked. Echo-only."
|
|
5677
|
+
},
|
|
5678
|
+
"outcome": {
|
|
5679
|
+
"kind": "enum",
|
|
5680
|
+
"values": ["ok", "declined", "unavailable"],
|
|
5681
|
+
"$comment": "Closed at three, as gem.purchaseResult's."
|
|
5682
|
+
},
|
|
5683
|
+
"reason": {
|
|
5684
|
+
"kind": "string",
|
|
5685
|
+
"maxLength": 64,
|
|
5686
|
+
"format": "code",
|
|
5687
|
+
"optional": true,
|
|
5688
|
+
"$comment": "An open code, on any outcome: `pending` with `ok`; `cancelled` or `busy` with `declined`; `store_off`, `sign_in_required`, `not_found`, `error` or the payment provider's own failure code with `unavailable`. Normalized to the code grammar before it is sent, as gem.storePurchaseResult's. A receiver must not fail on a code it does not know."
|
|
5689
|
+
}
|
|
5690
|
+
},
|
|
5691
|
+
"rateLimit": { "perMinute": 30 }
|
|
5572
5692
|
}
|
|
5573
5693
|
],
|
|
5574
5694
|
|