@indiegems/gem-web-sdk 9.1.1 → 9.1.3

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.
@@ -18,8 +18,8 @@
18
18
  },
19
19
  {
20
20
  "file": "arcade-economy.md",
21
- "sha256": "7840941e761c1a460b6dc83ed4cec1ae8838161e0b37ec16cb184c66ccee1385",
22
- "updated": "2026-10-01T19:42:50Z"
21
+ "sha256": "9058c26bc11266f235dfa3c72c80a3fc962aa9f204ef491006e6d34545f5eeeb",
22
+ "updated": "2026-10-09T00:24:00Z"
23
23
  },
24
24
  {
25
25
  "file": "async-opponents.md",
@@ -33,8 +33,8 @@
33
33
  },
34
34
  {
35
35
  "file": "changelog.md",
36
- "sha256": "d40028f10bd3c9b068b4b4aa26396f0d81a628da9b048a01942d92395d1d26b4",
37
- "updated": "2026-10-08T21:35:00Z"
36
+ "sha256": "785ad9b32a4bf1c37db8583c98d9302456b3fb5d4c6309edc1bf89d6e2f31034",
37
+ "updated": "2026-10-09T21:32:29Z"
38
38
  },
39
39
  {
40
40
  "file": "cross-promotion.md",
@@ -68,13 +68,13 @@
68
68
  },
69
69
  {
70
70
  "file": "getting-started.md",
71
- "sha256": "0b447bcaf8f1e7ac69f98692e529f1371d19ae7dc1aea3763013dc01370fe734",
72
- "updated": "2026-10-08T21:35:00Z"
71
+ "sha256": "ebfb03fe43f9f5b499cc383881c396132481cf2d4d5fb045bb1bf31f3f37db62",
72
+ "updated": "2026-10-09T21:32:29Z"
73
73
  },
74
74
  {
75
75
  "file": "inventory-and-catalogs.md",
76
- "sha256": "23de4799808ec3745664eb8c8fd948891921d5077535e27155c7e4ed4b584fd8",
77
- "updated": "2026-10-01T19:42:50Z"
76
+ "sha256": "3e0f71db5b4f5d2d256289f90ffec3ed2887a70b1e72b02f55ec64815043d900",
77
+ "updated": "2026-10-09T00:13:03Z"
78
78
  },
79
79
  {
80
80
  "file": "leaderboards.md",
@@ -93,8 +93,8 @@
93
93
  },
94
94
  {
95
95
  "file": "platform-rules.md",
96
- "sha256": "269f2d75a717de583af5ca7e0b91655a2d1edcedb5745c6f2d6f8f0be4dcfbdc",
97
- "updated": "2026-09-27T19:51:18Z"
96
+ "sha256": "76fa088b27d27f7e5c936540a65ed5dc7c9f957334d0db650135173672ffba4d",
97
+ "updated": "2026-10-09T00:14:06Z"
98
98
  },
99
99
  {
100
100
  "file": "player-sign-in.md",
@@ -123,13 +123,13 @@
123
123
  },
124
124
  {
125
125
  "file": "server-bundles.md",
126
- "sha256": "f023ae20d8bc82ffc64238de0c12a126913a4d27084e70d35f8dcb835567e27e",
127
- "updated": "2026-10-08T21:35:00Z"
126
+ "sha256": "b1f6799b8b0abbbaeed801f50c50cd31faff37f91d2671e3aed8150997ca2415",
127
+ "updated": "2026-10-09T21:32:29Z"
128
128
  },
129
129
  {
130
130
  "file": "serverless-functions-js.md",
131
- "sha256": "8d5bda66dfebd93e80fe97b2f09c0651c26dc83b16827bdae7aa25f198f89e0b",
132
- "updated": "2026-10-03T00:26:43Z"
131
+ "sha256": "a8e66db4913654ba0ccca0e97cd40061c3854ce5b8900498d27c06c7fdf6ce2f",
132
+ "updated": "2026-10-09T20:20:17Z"
133
133
  },
134
134
  {
135
135
  "file": "serverless-functions.md",
@@ -148,8 +148,8 @@
148
148
  },
149
149
  {
150
150
  "file": "what-a-game-must-do.md",
151
- "sha256": "6dc36d737770abdbbdd32cee00b5fafe675513428376cd0166474b394d695f46",
152
- "updated": "2026-10-08T20:46:52Z"
151
+ "sha256": "bc02d4a864943da446d9411b338b75908d9e0c2e0bc8378bbd28bf3f0ccf7f2a",
152
+ "updated": "2026-10-09T00:24:00Z"
153
153
  }
154
154
  ]
155
155
  }
@@ -328,9 +328,9 @@ longer one, are in [`server-bundles.md`](server-bundles.md#how-long-your-server-
328
328
 
329
329
  Decided by the platform on **2026-09-26**, and a contract rather than an implementation fact.
330
330
 
331
- The **Boost** is the only thing the Arcade sells for real money. While a player has one, they earn
332
- **Gems and Tickets** faster — every grant is multiplied — and their earning limits and holdings cap
333
- are raised by the same tier. It **never** touches achievement points. The platform applies all of
331
+ The **Boost** is something the Arcade sells for real money, in its own store. While a player has
332
+ one, they earn **Gems and Tickets** faster — every grant is multiplied — and their earning limits
333
+ and holdings cap are raised by the same tier. It **never** touches achievement points. The platform applies all of
334
334
  it on its own side, when it pays: a game does nothing to make a boost work, and cannot make one
335
335
  work harder.
336
336
 
@@ -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/v9.1.1/gem-web-sdk.tgz
84
- curl -fsSL https://downloads.gemarcade.com/sdk/v9.1.1/gem-web-sdk.tgz.sri
83
+ curl -fsSL -O https://downloads.gemarcade.com/sdk/v9.1.3/gem-web-sdk.tgz
84
+ curl -fsSL https://downloads.gemarcade.com/sdk/v9.1.3/gem-web-sdk.tgz.sri
85
85
  openssl dgst -sha384 -binary gem-web-sdk.tgz | openssl base64 -A
86
86
  ```
87
87
 
@@ -12,6 +12,32 @@ what it may change, its limits, channels, and calling it from your game holds he
12
12
  page covers only what is different: how you write one, how the SDK behaves inside it, and how
13
13
  publishing checks it.
14
14
 
15
+ **JavaScript suits light work; heavy computation belongs in a component.** The platform's engine
16
+ runs JavaScript without a JIT, so the same work takes many times longer than in a WebAssembly
17
+ component, and the gap is smallest on a call that does little and widest on sustained computation.
18
+ Checking a claim, looking a record up or making a small state update finishes well inside a call's
19
+ limits in either language. Replaying a run to verify it, or ranking a large board, is where the gap
20
+ decides whether the call finishes: the CPU limit is the same for both, so JavaScript runs out of it
21
+ on work a component finishes many times over, and the call fails with `function_failed` and
22
+ `cpu_budget_exceeded`. A game has one function per channel, so the heaviest thing it does decides
23
+ the language. Measured with each running as the platform runs it, the same algorithm in JavaScript
24
+ and in Rust:
25
+
26
+ | The work | The component was faster by |
27
+ | --------------------------------------------------------------------------------- | --------------------------------- |
28
+ | A function that only returns its input, which measures the engine's cost per call | about 20 times |
29
+ | A loop of 20,000 integer steps | about 20 times |
30
+ | Ranking a daily leaderboard of 10,000 players | about 60 times |
31
+ | Numeric benchmarks: n-body, spectral-norm, fannkuch-redux and binary-trees | from about 30 to nearly 200 times |
32
+ | The numeric benchmarks and the leaderboard, on average | about 80 times |
33
+
34
+ **Your game's speed in a browser is no guide.** The same JavaScript under V8, the engine in Chrome,
35
+ measured with Node.js on the numeric benchmarks and the leaderboard, ran about 100 times faster than
36
+ on the platform's engine once its JIT had warmed up, which is a little faster than the component,
37
+ and about 7 times faster on its first call. With its JIT switched off it was still about 5 times
38
+ faster. Before you rely on a heavy check, invoke it on `dev` with input as large as a real
39
+ player's.
40
+
15
41
  ## 1. The contract
16
42
 
17
43
  The same as a component's: bytes in, bytes out, one fresh instance per call, nothing kept between
@@ -595,6 +595,13 @@ there is no route by which the frame could request one. `createGem` forwards all
595
595
  nothing after the pause. So the pause edge is your last guaranteed moment. Write anything the
596
596
  player would mind losing there, and never hold it back for a terminate warning that may not come.
597
597
 
598
+ **Re-read on resume.** Your game is told that it was paused and resumed, never why, and never
599
+ what the player did in the meantime. Their Gems and Tickets can change while you are paused, so
600
+ if your game keeps anything worked out from a balance, such as a Buy button it greyed out or a
601
+ "not enough Gems" line, read `gem.platform.currencies.get()` again on every `onPause(false)`
602
+ rather than trusting what it read before the pause
603
+ ([inventory-and-catalogs.md](inventory-and-catalogs.md#reading-the-players-balance)).
604
+
598
605
  - **`onWillTerminate`** — persist now. `graceMs` is a promise about how long the parent will wait.
599
606
  - **`onViewport`** — where your frame's own edges are under a cutout. **Most games need no
600
607
  handler**: the SDK has already written the same numbers to `--gem-safe-top` and friends on your
@@ -700,8 +707,8 @@ On a phone your game is full-bleed, and the arcade keeps one row of its own over
700
707
  [the Arcade's menu](#the-arcades-menu-your-settings-live-in-it): Return to the Arcade, your
701
708
  settings, mute and the rest.
702
709
  - **The player's wallets** (Gems, Tickets, their achievement score), in the **middle of the row**,
703
- [always there](#the-arcades-wallet-always-shown-never-drawn), during play as well as on your
704
- menus. They are translucent, so your game shows through them — but they are still the Arcade's,
710
+ [there unless your game hides them](#the-arcades-wallet-shown-unless-you-hide-it-never-drawn),
711
+ during play as well as on your menus. They are translucent, so your game shows through them — but they are still the Arcade's,
705
712
  and still taps the Arcade takes.
706
713
 
707
714
  **The top of the screen has one layout, in every game:**
@@ -1021,25 +1028,28 @@ its music out than cut it, say. The Arcade still shows its button for your game
1021
1028
  so this is a promise: your `onPrefs` handler must go quiet on `muted: true` and come back on `false`,
1022
1029
  and the release checklist checks that it does.
1023
1030
 
1024
- ## The Arcade's wallet: always shown, never drawn
1031
+ ## The Arcade's wallet: shown unless you hide it, never drawn
1025
1032
 
1026
1033
  The player's platform balances — Gems, Tickets (`ARCADE_TICKETS`) and their achievement score —
1027
1034
  belong to the Arcade. **Your game never draws one**, and never shows a pending state for one: the
1028
1035
  Arcade takes every figure from the platform and draws the wallets itself, in the middle of the row
1029
1036
  across the top of your frame on a phone, with its menu button at the right end.
1030
1037
 
1031
- **The Arcade shows every wallet the whole time, during play too** (since 2026-10-04). They are
1032
- small, translucent chips in the middle of the top row, and they are
1038
+ **By default the Arcade shows every wallet the whole time, during play too.** They are small,
1039
+ translucent chips in the middle of the top row, and while shown they are
1033
1040
  [reserved rectangles](#the-arcades-own-controls-reserved-areas): lay every screen out below both
1034
- `--gem-reserved-tl-height` and `--gem-reserved-tr-height`, gameplay included. There is nothing for
1035
- your game to call.
1041
+ `--gem-reserved-tl-height` and `--gem-reserved-tr-height`. A game that calls nothing keeps them.
1042
+
1043
+ **A game may hide them for gameplay** (honoured again since 2026-10-08): call
1044
+ `gem.setWalletVisible(false)` when play starts and `gem.setWalletVisible(true)` when a menu opens.
1045
+ While hidden, their rectangles leave `gem.viewport.reserved` and the top row is the game's; the
1046
+ Arcade still pops a wallet up for a moment when something happens to it (a reward you cued, a
1047
+ spend, a landing), floating over the game without moving anything. This applies on a phone, where
1048
+ the wallets sit over your game; where the Arcade docks them beside the frame it changes nothing.
1049
+ Read the `--gem-reserved-*` properties rather than assuming either state.
1036
1050
 
1037
- ⚠️ **`gem.setWalletVisible` no longer hides anything in the Arcade.** It was how a game hid the
1038
- wallets for gameplay and showed them on its menus, and narrowed them to one currency on a store
1039
- screen. The Arcade still accepts the call — it returns `true`, and existing games need no change —
1040
- but the wallets stay up whatever it is told. **Do not design a screen around the top row being
1041
- clear during play**: it is not. Another embedder may still honour the call, so leaving it in is
1042
- harmless; new code does not need it.
1051
+ ⚠️ **The Arcade ignores the `only` option**: a shown wallet is every wallet. Do not build a store
1052
+ screen that relies on narrowing them to one currency.
1043
1053
 
1044
1054
  ## No accidental text selection
1045
1055
 
@@ -1195,7 +1205,7 @@ members. `gem.player.displayHandle` and `gem.player.avatarUrl` still work, and a
1195
1205
 
1196
1206
  ### The signed-in player's Boost: show it, never gate on it
1197
1207
 
1198
- `gem.player` also carries **`boost`** — the player's Boost, the Arcade's one real-money product.
1208
+ `gem.player` also carries **`boost`** — the player's Boost, which the Arcade sells for real money.
1199
1209
  While it lasts the player earns Gems and Tickets faster (never achievement points), and the
1200
1210
  platform raises their earning limits to match. The platform does all of that on its own side when
1201
1211
  it pays; your game does nothing to make a boost work.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indiegems/gem-web-sdk",
3
- "version": "9.1.1",
3
+ "version": "9.1.3",
4
4
  "description": "postMessage bridge contract between gem-web and browser-playable games",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -616,7 +616,7 @@
616
616
  "optional": true,
617
617
  "$comment": [
618
618
  "The player's Boost, FOR DISPLAY ONLY — a platform rule, not advice. A",
619
- "Boost is the Arcade's one real-money product: it",
619
+ "Boost is something the Arcade sells for real money: it",
620
620
  "multiplies what the player EARNS in platform currencies (Gems and",
621
621
  "Tickets) and raises their earning limits. It never touches achievement",
622
622
  "points.",
@@ -2813,11 +2813,8 @@
2813
2813
  "the following is not offering a degraded store, it is offering a game a",
2814
2814
  "button that charges a player:",
2815
2815
  "",
2816
- " PAYABLE, EARNED CURRENCIES ONLY. `currencySlug` is frame-chosen and this",
2817
- " schema does not constrain which currency it names. The argument that let",
2818
- " this ship without a top-up flow binds a currency a player EARNS: short of",
2819
- " Gems or Tickets the answer is to go and play, and neither is sold for",
2820
- " money. It does not bind a currency a player BUYS. So resolve the slug",
2816
+ " PAYABLE CURRENCIES ONLY. `currencySlug` is frame-chosen and this schema",
2817
+ " does not constrain which currency it names. So resolve the slug",
2821
2818
  " against the platform's own currency records - an active currency the",
2822
2819
  " platform marks payable in games - and refuse the rest, `declined` with",
2823
2820
  " `unknown_currency` or `currency_not_in_game_payable`. Resolve it EXACTLY,",
@@ -2828,6 +2825,28 @@
2828
2825
  " the frame does choose which wallet pays; the prompt names that currency",
2829
2826
  " and its price from platform records, which keeps the choice the player's.",
2830
2827
  "",
2828
+ " A PAYABLE CURRENCY MAY BE ONE THE PLAYER BOUGHT. This shipped on the",
2829
+ " argument that a payable currency is only ever EARNED: short of Gems or",
2830
+ " Tickets the answer was to go and play, and neither was sold for money.",
2831
+ " That is no longer true of Gems. The arcade's own store sells them, and",
2832
+ " bought Gems stay payable in games (owner decision, 2026-10-06). So",
2833
+ " where a balance came from protects",
2834
+ " nobody, and no parent may lean on it. What stands between a game and a",
2835
+ " balance a player paid for is the rest of this list, and these four are",
2836
+ " the ones the old argument stood in for:",
2837
+ "",
2838
+ " - THE CONFIRMATION IS THE PARENT'S, and it costs a gesture. The sheet is",
2839
+ " drawn in the parent's own chrome from platform records, and only on",
2840
+ " user activation (below).",
2841
+ " - A DISMISSAL BACKS OFF, and the confirm control is quiet after paint",
2842
+ " (both below; the arcade holds the quiet period for 400 ms).",
2843
+ " - A SHORTFALL IS NEVER A SALE. `insufficient_funds` gets no top-up, no",
2844
+ " upsell and no way into a store, from the parent or from the game. A",
2845
+ " game cannot open the arcade's store, and no message here asks for it.",
2846
+ " - MONEY IS TAKEN ONLY ON THE PAYMENT PROVIDER'S PAGE, which a player",
2847
+ " reaches through the parent's own chrome. Nothing a frame sends starts",
2848
+ " a payment or carries a card field.",
2849
+ "",
2831
2850
  " THE FRAME CHOOSES THE AMOUNT. Not the unit price - but `targetId` x",
2832
2851
  " `currencySlug` x `quantity` is nevertheless the total, up to ten times the",
2833
2852
  " most expensive item in a catalog the game itself publishes. Render unit",