@indiegems/gem-web-sdk 9.1.0 → 9.1.2

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.
@@ -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.0/gem-web-sdk.tgz
84
- curl -fsSL https://downloads.gemarcade.com/sdk/v9.1.0/gem-web-sdk.tgz.sri
83
+ curl -fsSL -O https://downloads.gemarcade.com/sdk/v9.1.2/gem-web-sdk.tgz
84
+ curl -fsSL https://downloads.gemarcade.com/sdk/v9.1.2/gem-web-sdk.tgz.sri
85
85
  openssl dgst -sha384 -binary gem-web-sdk.tgz | openssl base64 -A
86
86
  ```
87
87
 
@@ -543,6 +543,12 @@ credit for it until you say so.
543
543
  - **Text entry the on-screen keyboard can serve.** Focusing a field summons the TV's OSK, so keep
544
544
  entry fields few and ordinary — and never gate first play behind one. A player made to type on a
545
545
  remote before they may play mostly does not.
546
+
547
+ The same fields have a rule that is not about televisions at all: **give every focusable text
548
+ field a font size of at least 16px**, on every device. Below that, iOS Safari zooms the page in
549
+ when the field takes focus and does not zoom back out, so the game is left cropped off the side of
550
+ the screen until the player pinches it back. A 14px lobby-code box is enough to do it.
551
+
546
552
  - **A TV performance budget.** TV-browser SoCs are weak next to any desktop: ship a player-settable
547
553
  render-scale option (render at DPR 1 and upscale), bound memory, assume a fixed frame — 16:9, or
548
554
  a centred 9:16 column when your store page makes you
@@ -589,6 +595,13 @@ there is no route by which the frame could request one. `createGem` forwards all
589
595
  nothing after the pause. So the pause edge is your last guaranteed moment. Write anything the
590
596
  player would mind losing there, and never hold it back for a terminate warning that may not come.
591
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
+
592
605
  - **`onWillTerminate`** — persist now. `graceMs` is a promise about how long the parent will wait.
593
606
  - **`onViewport`** — where your frame's own edges are under a cutout. **Most games need no
594
607
  handler**: the SDK has already written the same numbers to `--gem-safe-top` and friends on your
@@ -694,8 +707,8 @@ On a phone your game is full-bleed, and the arcade keeps one row of its own over
694
707
  [the Arcade's menu](#the-arcades-menu-your-settings-live-in-it): Return to the Arcade, your
695
708
  settings, mute and the rest.
696
709
  - **The player's wallets** (Gems, Tickets, their achievement score), in the **middle of the row**,
697
- [always there](#the-arcades-wallet-always-shown-never-drawn), during play as well as on your
698
- 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,
699
712
  and still taps the Arcade takes.
700
713
 
701
714
  **The top of the screen has one layout, in every game:**
@@ -1015,25 +1028,28 @@ its music out than cut it, say. The Arcade still shows its button for your game
1015
1028
  so this is a promise: your `onPrefs` handler must go quiet on `muted: true` and come back on `false`,
1016
1029
  and the release checklist checks that it does.
1017
1030
 
1018
- ## The Arcade's wallet: always shown, never drawn
1031
+ ## The Arcade's wallet: shown unless you hide it, never drawn
1019
1032
 
1020
1033
  The player's platform balances — Gems, Tickets (`ARCADE_TICKETS`) and their achievement score —
1021
1034
  belong to the Arcade. **Your game never draws one**, and never shows a pending state for one: the
1022
1035
  Arcade takes every figure from the platform and draws the wallets itself, in the middle of the row
1023
1036
  across the top of your frame on a phone, with its menu button at the right end.
1024
1037
 
1025
- **The Arcade shows every wallet the whole time, during play too** (since 2026-10-04). They are
1026
- 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
1027
1040
  [reserved rectangles](#the-arcades-own-controls-reserved-areas): lay every screen out below both
1028
- `--gem-reserved-tl-height` and `--gem-reserved-tr-height`, gameplay included. There is nothing for
1029
- 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.
1030
1050
 
1031
- ⚠️ **`gem.setWalletVisible` no longer hides anything in the Arcade.** It was how a game hid the
1032
- wallets for gameplay and showed them on its menus, and narrowed them to one currency on a store
1033
- screen. The Arcade still accepts the call — it returns `true`, and existing games need no change —
1034
- but the wallets stay up whatever it is told. **Do not design a screen around the top row being
1035
- clear during play**: it is not. Another embedder may still honour the call, so leaving it in is
1036
- 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.
1037
1053
 
1038
1054
  ## No accidental text selection
1039
1055
 
@@ -1125,15 +1141,15 @@ player (`gem.player`), room members, leaderboard rows, friends, profiles, showca
1125
1141
  async opponent you read through the raw client, once you pass the row through `mapIdentity`. One
1126
1142
  renderer serves all of them.
1127
1143
 
1128
- | Field | What it is | Type |
1129
- | --------------- | ------------------------------------------------------------------------------------------- | ------------ |
1130
- | `playerId` | The player's id. **Absent on an anonymous row** | `string` |
1131
- | `displayName` | The name the player chose | `PlayerText` |
1132
- | `discriminator` | The digits that tell two same-named players apart — `0001`, no `#` | `string` |
1133
- | `displayTag` | The precomposed `Name#0001` | `PlayerText` |
1134
- | `avatarIconId` | The portrait item's id | `string` |
1135
- | `avatarIconUrl` | The portrait's thumbnail | `string` |
1136
- | `badges` | Equipped badges, at most two, in the player's order, each `{ itemId, iconUrl }` | array |
1144
+ | Field | What it is | Type |
1145
+ | --------------- | ------------------------------------------------------------------------------- | ------------ |
1146
+ | `playerId` | The player's id. **Absent on an anonymous row** | `string` |
1147
+ | `displayName` | The name the player chose | `PlayerText` |
1148
+ | `discriminator` | The digits that tell two same-named players apart — `0001`, no `#` | `string` |
1149
+ | `displayTag` | The precomposed `Name#0001` | `PlayerText` |
1150
+ | `avatarIconId` | The portrait item's id | `string` |
1151
+ | `avatarIconUrl` | The portrait's thumbnail | `string` |
1152
+ | `badges` | Equipped badges, at most two, in the player's order, each `{ itemId, iconUrl }` | array |
1137
1153
 
1138
1154
  **A player chooses what they wear**: a portrait and up to two badges, each an icon they own — one
1139
1155
  an achievement in your game or any other may have awarded them ([achievements.md](achievements.md)
@@ -1189,7 +1205,7 @@ members. `gem.player.displayHandle` and `gem.player.avatarUrl` still work, and a
1189
1205
 
1190
1206
  ### The signed-in player's Boost: show it, never gate on it
1191
1207
 
1192
- `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.
1193
1209
  While it lasts the player earns Gems and Tickets faster (never achievement points), and the
1194
1210
  platform raises their earning limits to match. The platform does all of that on its own side when
1195
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.0",
3
+ "version": "9.1.2",
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",