@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.
@@ -1,6 +1,6 @@
1
1
  # The browser surface — API reference
2
2
 
3
- Generated from `@indiegems/gem-web-sdk/sdk` v16.3.0, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
3
+ Generated from `@indiegems/gem-web-sdk/sdk` v17.0.0, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
4
4
 
5
5
  `@indiegems/gem-web-sdk/sdk` is the one import a game writes, wherever its code runs. `@indiegems/gem-web-sdk/game` exports exactly the symbols on this page and nothing else, for a build that wants only this half.
6
6
 
@@ -4590,7 +4590,7 @@ interface StoreApi
4590
4590
  ```
4591
4591
 
4592
4592
  **Real money.** The Arcade Store's own Gem packs, Boosts, passes and bundles,
4593
- bought with the player's money through the arcade's own purchase sheet.
4593
+ bought with the player's money through the Arcade Store's own checkout.
4594
4594
  `gem.store` is one of these. To spend Gems or Coins on your game's own items,
4595
4595
  use `gem.purchases`.
4596
4596
 
@@ -4605,6 +4605,19 @@ readonly available: boolean
4605
4605
  Whether the Arcade Store answers here, synchronously. Draw your own "Get
4606
4606
  Gems" button only when it is true.
4607
4607
 
4608
+ <a id="storeapi-showofferavailable"></a>
4609
+
4610
+ ## StoreApi.showOfferAvailable
4611
+
4612
+ ```ts
4613
+ readonly showOfferAvailable: boolean
4614
+ ```
4615
+
4616
+ Whether [StoreApi.showOffer](#storeapi-showoffer) answers here, synchronously. An older
4617
+ Arcade can sell through `purchase` and still not draw this sheet, so read
4618
+ this, not [StoreApi.available](#storeapi-available), before you draw a button that calls
4619
+ `showOffer`. `false` until the bridge is up.
4620
+
4608
4621
  <a id="storeapi-offers"></a>
4609
4622
 
4610
4623
  ## StoreApi.offers
@@ -4628,20 +4641,24 @@ is withheld. Re-read on resume: what is on sale can change.
4628
4641
  purchase(skuId: string): Promise<ArcadeOutcome>
4629
4642
  ```
4630
4643
 
4631
- Ask the arcade to open its store's purchase sheet for one SKU from
4632
- [StoreApi.offers](#storeapi-offers). **Call it from the player's press handler** — the
4644
+ Ask the arcade to open checkout for one SKU from [StoreApi.offers](#storeapi-offers):
4645
+ the payment provider's page on the web, the app store's purchase sheet in
4646
+ the Arcade's apps, straight away, with no confirmation screen of the
4647
+ arcade's own first. **Call it from the player's press handler** — the
4633
4648
  arcade refuses an ask no person pressed for, `declined` / `busy`.
4634
4649
 
4635
4650
  - `ok` — the player paid. Re-read balances and inventory through `gem.me`;
4636
4651
  nothing here carries an amount. With `reason: 'pending'`, the provider
4637
4652
  has not settled yet: [StoreApi.onSettled](#storeapi-onsettled) hears when it does.
4638
- - `declined` — nothing was bought: `cancelled` (the player said no) or
4639
- `busy`. Return to your screen silently, and do not ask again unprompted.
4653
+ - `declined` — nothing was bought: `cancelled` (the player closed the
4654
+ checkout, or the offer cannot be bought again yet) or `busy` (after a
4655
+ closed checkout the arcade refuses the next ask for a while). Return to
4656
+ your screen silently, and do not ask again unprompted.
4640
4657
  - `unavailable` with `store_off`, `sign_in_required`, `not_found`,
4641
4658
  `unsupported`, `invalid_argument` or a payment provider's failure code —
4642
4659
  nothing was bought.
4643
4660
  - `unavailable` / `error` — **the outcome is unknown, and the player may
4644
- have paid.** It is what you get when the bridge closes while the sheet
4661
+ have paid.** It is what you get when the bridge closes while checkout
4645
4662
  is open, or the arcade has no reliable answer. Never tell the player
4646
4663
  nothing was bought: re-read balances and inventory through `gem.me`,
4647
4664
  and keep listening with [StoreApi.onSettled](#storeapi-onsettled).
@@ -4655,6 +4672,43 @@ payment takes as long as it takes.
4655
4672
 
4656
4673
  **Returns.** Always resolves; never rejects.
4657
4674
 
4675
+ <a id="storeapi-showoffer"></a>
4676
+
4677
+ ## StoreApi.showOffer
4678
+
4679
+ ```ts
4680
+ showOffer(skuId: string): Promise<ArcadeOutcome>
4681
+ ```
4682
+
4683
+ Ask the arcade to show one SKU from [StoreApi.offers](#storeapi-offers) in its own
4684
+ sheet, over your paused and hidden game: its art, what the player gets, a
4685
+ sale if one is on, and, for a pass the player already holds, how long it
4686
+ has left. The sheet's Buy opens the same checkout as
4687
+ [StoreApi.purchase](#storeapi-purchase). **Call it from the player's press handler.**
4688
+
4689
+ Use it for an offer you would have to hardcode a pitch for: VIP and other
4690
+ passes, and bundles. Your button only needs to say what it opens. Draw a
4691
+ Gem pack yourself and use `purchase` instead. Check
4692
+ [StoreApi.showOfferAvailable](#storeapi-showofferavailable) first, and that the SKU is in `offers`.
4693
+
4694
+ It answers exactly as `purchase` does, with one addition: `declined` /
4695
+ `cancelled` also covers the player leaving the sheet without pressing Buy,
4696
+ and you are not told which of the two they closed. That starts the same
4697
+ back-off, so do not ask again unprompted. `unavailable` / `unsupported`
4698
+ means this Arcade does not draw the sheet, and nothing was sent.
4699
+ `unavailable` / `error` means **the outcome is unknown**: re-read balances
4700
+ and inventory through `gem.me`, and keep listening with
4701
+ [StoreApi.onSettled](#storeapi-onsettled).
4702
+
4703
+ NO TIMEOUT: it waits for as long as the arcade is connected, because a
4704
+ player may read a sheet and pay for as long as they like.
4705
+
4706
+ | Parameter | Type |
4707
+ | --- | --- |
4708
+ | `skuId` | `string` |
4709
+
4710
+ **Returns.** Always resolves; never rejects.
4711
+
4658
4712
  <a id="storeapi-onsettled"></a>
4659
4713
 
4660
4714
  ## StoreApi.onSettled
@@ -10478,9 +10532,9 @@ readonly reserved: readonly GemReservedArea[]
10478
10532
  ```
10479
10533
 
10480
10534
  Rectangles of your frame the arcade's own controls cover — in the Gem
10481
- Arcade, its Gem button in whichever corner it sits, and the frame-rate readout
10482
- beside it while the player shows it; an embedder that draws wallets over
10483
- your game adds theirs (see `setWalletVisible`). Keep essential UI and touch
10535
+ Arcade, its Gem button in whichever corner it sits, the frame-rate readout
10536
+ beside it while the player shows it, and the wallets beside it while your
10537
+ game asks for them (see `setWalletVisible`). Keep essential UI and touch
10484
10538
  targets out of them. Empty when the arcade covers nothing.
10485
10539
 
10486
10540
  Each is a keep-out area, grown out to the frame edges it sits nearest — not
@@ -10629,10 +10683,10 @@ readonly only?: readonly string[]
10629
10683
 
10630
10684
  While shown, show ONLY these: platform currency slugs as
10631
10685
  `gem.platform.currencies.get()` lists them (`GEMS`, `ARCADE_TICKETS`),
10632
- matched exactly, and the literal `'score'` for the achievement score. Only
10633
- for a screen about one currency, such as a Coins store; leave it out and
10634
- every wallet shows, which is always safe. The Gem Arcade draws no wallet
10635
- over a game, so it has nothing to narrow and ignores it.
10686
+ matched exactly, and the literal `'score'` for the achievement score. Leave
10687
+ it out and every wallet shows, the achievement score's included. The Gem
10688
+ Arcade honours it: `{ only: ['GEMS', 'ARCADE_TICKETS'] }` shows those two
10689
+ and no achievement score; a slug the platform does not serve names nothing.
10636
10690
 
10637
10691
  <a id="gamemenuitem"></a>
10638
10692
 
@@ -10688,7 +10742,8 @@ interface GameMenu
10688
10742
  ```
10689
10743
 
10690
10744
  What your game adds to the arcade's menu — see `gem.setMenu`. Your settings
10691
- are not part of it: they open from your own settings button.
10745
+ screen is not part of it: it opens from your own settings button. An action
10746
+ on that screen (Replay the tutorial, How to play) may also be an entry here.
10692
10747
 
10693
10748
  <a id="gamemenu-items"></a>
10694
10749
 
@@ -11211,8 +11266,10 @@ sheet. To spend Gems or Coins on your game's own items, use
11211
11266
  [Gem.purchases](#gem-purchases).
11212
11267
 
11213
11268
  List what the store sells here with `offers()`, show each `priceDisplay`
11214
- exactly as given, and call `purchase(skuId)` from the player's press. After
11215
- `ok`, re-read balances through [Gem.me](#gem-me). Nothing rejects.
11269
+ exactly as given, and call `purchase(skuId)` from the player's press. For a
11270
+ pass, a bundle or anything else whose pitch you would have to hardcode, call
11271
+ `showOffer(skuId)` instead, and the arcade draws the offer. After `ok`,
11272
+ re-read balances through [Gem.me](#gem-me). Nothing rejects.
11216
11273
 
11217
11274
  **Guides.** [arcade-and-store](../arcade-and-store.md)
11218
11275
 
@@ -11796,9 +11853,10 @@ Tell the arcade a platform-currency reward is on its way, so it **shows it
11796
11853
  landing**: in the Gem Arcade the currency flies from `origin` into the Gem
11797
11854
  button, wherever on your frame it sits, which pops as each mark lands, and
11798
11855
  the player's total moves once the platform confirms the grant. The Gem
11799
- Arcade draws no wallet over your game: the player sees their Gems and
11800
- Coins in the Gem menu. A payout you do not cue still flies, from the
11801
- middle of your frame, so cue the ones the player earns on screen.
11856
+ Arcade draws no wallet over your game unless you ask (`setWalletVisible`):
11857
+ the player sees their Gems and Coins in the Gem menu. A payout you do not
11858
+ cue still flies, from the middle of your frame, so cue the ones the player
11859
+ earns on screen.
11802
11860
 
11803
11861
  Call it the moment your game decides to award — when your server has moved
11804
11862
  (or is about to move) the progression mapped onto a platform currency —
@@ -11843,26 +11901,25 @@ landed — which it does on every channel, dev and test included.
11843
11901
  setWalletVisible(visible: boolean, options?: WalletVisibleOptions): boolean
11844
11902
  ```
11845
11903
 
11846
- Show or hide the player's wallets — Gems, Coins, the achievement score —
11847
- that an embedder draws over your game.
11904
+ Ask the arcade to show, or stop showing, the player's wallets — Gems,
11905
+ Coins, the achievement score — over your game.
11848
11906
 
11849
- **The Gem Arcade draws no wallet over a game** (since 2026-10-11): the
11850
- player sees their balances in the Gem menu, and payouts fly into the Gem
11851
- button. So under the Gem Arcade this changes nothing on screen — it is
11852
- accepted and harmless, and a game that already calls it keeps working.
11853
- Call it for an embedder that does draw wallets over your game: hide them
11854
- while you play, show them on your menus.
11907
+ **The Gem Arcade draws them on request** (the owner, 2026-10-29): a game
11908
+ that says nothing gets no wallet over it, as since 2026-10-11, and the
11909
+ player sees their balances in the Gem menu; a game that asks gets them
11910
+ drawn beside the Gem, from the platform's own figures. An Arcade from
11911
+ before that ignores the call, harmlessly.
11855
11912
 
11856
11913
  ```js
11857
- level.onStart = () => gem.setWalletVisible(false);
11858
- menu.onOpen = () => gem.setWalletVisible(true);
11914
+ gem.setWalletVisible(true, { only: ['GEMS', 'ARCADE_TICKETS'] }); // every screen
11915
+ level.onStart = () => gem.setWalletVisible(false); // or put them away during play
11859
11916
  ```
11860
11917
 
11861
11918
  **It is screen state, not a toggle.** Each call is the whole answer and
11862
- replaces the last. Where an embedder draws wallets, shown ones arrive as
11863
- rectangles in `gem.viewport.reserved` and hiding them gives that space
11864
- back; the Gem Arcade sends none for wallets. `only` narrows what shows,
11865
- where an embedder honours it; the Gem Arcade does not.
11919
+ replaces the last. Shown wallets arrive as rectangles in
11920
+ `gem.viewport.reserved` (and widen the Gem's corner in the
11921
+ `--gem-reserved-*` variables), and hiding them gives that space back.
11922
+ `only` narrows what shows.
11866
11923
 
11867
11924
  **Never draw a platform balance yourself**, and never show a pending state
11868
11925
  for one: the arcade takes every figure from the platform. If this returns
@@ -11895,10 +11952,14 @@ gem.setMenu({
11895
11952
  });
11896
11953
  ```
11897
11954
 
11898
- **Your settings are not in it: draw your own settings button, always** —
11955
+ **Your settings screen is not in it: draw your own settings button** —
11899
11956
  top-right, with your Back button just to its left, clear of
11900
11957
  `gem.viewport.reserved`. The Arcade's menu never opens your settings
11901
- screen.
11958
+ screen. Inside the Gem Arcade you may leave the button out while this
11959
+ returns `true`, when every setting on your screen is declared with
11960
+ `gem.settings` and every action on it is one of these entries; it returns
11961
+ `false` before the bridge is up and under an embedder with no menu, and the
11962
+ button stays there.
11902
11963
 
11903
11964
  **It is screen state, not a toggle.** Each call is the whole answer and
11904
11965
  replaces the last, so call it again when the entries change — a new
@@ -1,6 +1,6 @@
1
1
  # The server surface — API reference
2
2
 
3
- Generated from `@indiegems/gem-web-sdk/sdk` v16.3.0, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
3
+ Generated from `@indiegems/gem-web-sdk/sdk` v17.0.0, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
4
4
 
5
5
  `@indiegems/gem-web-sdk/sdk` is the one import a game writes, wherever its code runs. `@indiegems/gem-web-sdk/server` exports exactly the symbols on this page and nothing else, for a build that wants only this half.
6
6
 
@@ -2,7 +2,7 @@
2
2
  "schema_version": 2,
3
3
  "source": {
4
4
  "repo": "IndieGems/gem-web-sdk",
5
- "version": "16.3.0"
5
+ "version": "17.0.0"
6
6
  },
7
7
  "entries": [
8
8
  {
@@ -5512,7 +5512,7 @@
5512
5512
  "group": "store",
5513
5513
  "signature": "interface StoreApi",
5514
5514
  "documented": true,
5515
- "summary": "**Real money.** The Arcade Store's own Gem packs, Boosts, passes and bundles,\nbought with the player's money through the arcade's own purchase sheet.\n`gem.store` is one of these. To spend Gems or Coins on your game's own items,\nuse `gem.purchases`.",
5515
+ "summary": "**Real money.** The Arcade Store's own Gem packs, Boosts, passes and bundles,\nbought with the player's money through the Arcade Store's own checkout.\n`gem.store` is one of these. To spend Gems or Coins on your game's own items,\nuse `gem.purchases`.",
5516
5516
  "deprecated": null,
5517
5517
  "params": [],
5518
5518
  "returns": null,
@@ -5537,6 +5537,22 @@
5537
5537
  "defaultValue": null,
5538
5538
  "guides": []
5539
5539
  },
5540
+ {
5541
+ "id": "StoreApi.showOfferAvailable",
5542
+ "name": "showOfferAvailable",
5543
+ "anchor": "storeapi-showofferavailable",
5544
+ "group": "store",
5545
+ "signature": "readonly showOfferAvailable: boolean",
5546
+ "documented": true,
5547
+ "summary": "Whether [StoreApi.showOffer](api-reference-game.md#storeapi-showoffer) answers here, synchronously. An older\nArcade can sell through `purchase` and still not draw this sheet, so read\nthis, not [StoreApi.available](api-reference-game.md#storeapi-available), before you draw a button that calls\n`showOffer`. `false` until the bridge is up.",
5548
+ "deprecated": null,
5549
+ "params": [],
5550
+ "returns": null,
5551
+ "throws": null,
5552
+ "example": null,
5553
+ "defaultValue": null,
5554
+ "guides": []
5555
+ },
5540
5556
  {
5541
5557
  "id": "StoreApi.offers",
5542
5558
  "name": "offers",
@@ -5560,7 +5576,30 @@
5560
5576
  "group": "store",
5561
5577
  "signature": "purchase(skuId: string): Promise<ArcadeOutcome>",
5562
5578
  "documented": true,
5563
- "summary": "Ask the arcade to open its store's purchase sheet for one SKU from\n[StoreApi.offers](api-reference-game.md#storeapi-offers). **Call it from the player's press handler** — the\narcade refuses an ask no person pressed for, `declined` / `busy`.\n\n- `ok` — the player paid. Re-read balances and inventory through `gem.me`;\n nothing here carries an amount. With `reason: 'pending'`, the provider\n has not settled yet: [StoreApi.onSettled](api-reference-game.md#storeapi-onsettled) hears when it does.\n- `declined` — nothing was bought: `cancelled` (the player said no) or\n `busy`. Return to your screen silently, and do not ask again unprompted.\n- `unavailable` with `store_off`, `sign_in_required`, `not_found`,\n `unsupported`, `invalid_argument` or a payment provider's failure code —\n nothing was bought.\n- `unavailable` / `error` — **the outcome is unknown, and the player may\n have paid.** It is what you get when the bridge closes while the sheet\n is open, or the arcade has no reliable answer. Never tell the player\n nothing was bought: re-read balances and inventory through `gem.me`,\n and keep listening with [StoreApi.onSettled](api-reference-game.md#storeapi-onsettled).\n\nNO TIMEOUT: it waits for as long as the arcade is connected, because a\npayment takes as long as it takes.",
5579
+ "summary": "Ask the arcade to open checkout for one SKU from [StoreApi.offers](api-reference-game.md#storeapi-offers):\nthe payment provider's page on the web, the app store's purchase sheet in\nthe Arcade's apps, straight away, with no confirmation screen of the\narcade's own first. **Call it from the player's press handler** — the\narcade refuses an ask no person pressed for, `declined` / `busy`.\n\n- `ok` — the player paid. Re-read balances and inventory through `gem.me`;\n nothing here carries an amount. With `reason: 'pending'`, the provider\n has not settled yet: [StoreApi.onSettled](api-reference-game.md#storeapi-onsettled) hears when it does.\n- `declined` — nothing was bought: `cancelled` (the player closed the\n checkout, or the offer cannot be bought again yet) or `busy` (after a\n closed checkout the arcade refuses the next ask for a while). Return to\n your screen silently, and do not ask again unprompted.\n- `unavailable` with `store_off`, `sign_in_required`, `not_found`,\n `unsupported`, `invalid_argument` or a payment provider's failure code —\n nothing was bought.\n- `unavailable` / `error` — **the outcome is unknown, and the player may\n have paid.** It is what you get when the bridge closes while checkout\n is open, or the arcade has no reliable answer. Never tell the player\n nothing was bought: re-read balances and inventory through `gem.me`,\n and keep listening with [StoreApi.onSettled](api-reference-game.md#storeapi-onsettled).\n\nNO TIMEOUT: it waits for as long as the arcade is connected, because a\npayment takes as long as it takes.",
5580
+ "deprecated": null,
5581
+ "params": [
5582
+ {
5583
+ "name": "skuId",
5584
+ "type": "string",
5585
+ "optional": false,
5586
+ "summary": null
5587
+ }
5588
+ ],
5589
+ "returns": "Always resolves; never rejects.",
5590
+ "throws": null,
5591
+ "example": null,
5592
+ "defaultValue": null,
5593
+ "guides": []
5594
+ },
5595
+ {
5596
+ "id": "StoreApi.showOffer",
5597
+ "name": "showOffer",
5598
+ "anchor": "storeapi-showoffer",
5599
+ "group": "store",
5600
+ "signature": "showOffer(skuId: string): Promise<ArcadeOutcome>",
5601
+ "documented": true,
5602
+ "summary": "Ask the arcade to show one SKU from [StoreApi.offers](api-reference-game.md#storeapi-offers) in its own\nsheet, over your paused and hidden game: its art, what the player gets, a\nsale if one is on, and, for a pass the player already holds, how long it\nhas left. The sheet's Buy opens the same checkout as\n[StoreApi.purchase](api-reference-game.md#storeapi-purchase). **Call it from the player's press handler.**\n\nUse it for an offer you would have to hardcode a pitch for: VIP and other\npasses, and bundles. Your button only needs to say what it opens. Draw a\nGem pack yourself and use `purchase` instead. Check\n[StoreApi.showOfferAvailable](api-reference-game.md#storeapi-showofferavailable) first, and that the SKU is in `offers`.\n\nIt answers exactly as `purchase` does, with one addition: `declined` /\n`cancelled` also covers the player leaving the sheet without pressing Buy,\nand you are not told which of the two they closed. That starts the same\nback-off, so do not ask again unprompted. `unavailable` / `unsupported`\nmeans this Arcade does not draw the sheet, and nothing was sent.\n`unavailable` / `error` means **the outcome is unknown**: re-read balances\nand inventory through `gem.me`, and keep listening with\n[StoreApi.onSettled](api-reference-game.md#storeapi-onsettled).\n\nNO TIMEOUT: it waits for as long as the arcade is connected, because a\nplayer may read a sheet and pay for as long as they like.",
5564
5603
  "deprecated": null,
5565
5604
  "params": [
5566
5605
  {
@@ -12027,7 +12066,7 @@
12027
12066
  "group": "player",
12028
12067
  "signature": "readonly reserved: readonly GemReservedArea[]",
12029
12068
  "documented": true,
12030
- "summary": "Rectangles of your frame the arcade's own controls cover — in the Gem\nArcade, its Gem button in whichever corner it sits, and the frame-rate readout\nbeside it while the player shows it; an embedder that draws wallets over\nyour game adds theirs (see `setWalletVisible`). Keep essential UI and touch\ntargets out of them. Empty when the arcade covers nothing.\n\nEach is a keep-out area, grown out to the frame edges it sits nearest — not\nthe control's own box, and not something to line anything up with. Where\nthe arcade says, `control` on each is the control itself.\n\nA stylesheet reads the per-corner custom properties the SDK stamps from\nthese instead — `--gem-reserved-tl-height` and friends, and\n`--gem-reserved-tl-center-y` to line a top bar up with the Gem.",
12069
+ "summary": "Rectangles of your frame the arcade's own controls cover — in the Gem\nArcade, its Gem button in whichever corner it sits, the frame-rate readout\nbeside it while the player shows it, and the wallets beside it while your\ngame asks for them (see `setWalletVisible`). Keep essential UI and touch\ntargets out of them. Empty when the arcade covers nothing.\n\nEach is a keep-out area, grown out to the frame edges it sits nearest — not\nthe control's own box, and not something to line anything up with. Where\nthe arcade says, `control` on each is the control itself.\n\nA stylesheet reads the per-corner custom properties the SDK stamps from\nthese instead — `--gem-reserved-tl-height` and friends, and\n`--gem-reserved-tl-center-y` to line a top bar up with the Gem.",
12031
12070
  "deprecated": null,
12032
12071
  "params": [],
12033
12072
  "returns": null,
@@ -12235,7 +12274,7 @@
12235
12274
  "group": "platform",
12236
12275
  "signature": "readonly only?: readonly string[]",
12237
12276
  "documented": true,
12238
- "summary": "While shown, show ONLY these: platform currency slugs as\n`gem.platform.currencies.get()` lists them (`GEMS`, `ARCADE_TICKETS`),\nmatched exactly, and the literal `'score'` for the achievement score. Only\nfor a screen about one currency, such as a Coins store; leave it out and\nevery wallet shows, which is always safe. The Gem Arcade draws no wallet\nover a game, so it has nothing to narrow and ignores it.",
12277
+ "summary": "While shown, show ONLY these: platform currency slugs as\n`gem.platform.currencies.get()` lists them (`GEMS`, `ARCADE_TICKETS`),\nmatched exactly, and the literal `'score'` for the achievement score. Leave\nit out and every wallet shows, the achievement score's included. The Gem\nArcade honours it: `{ only: ['GEMS', 'ARCADE_TICKETS'] }` shows those two\nand no achievement score; a slug the platform does not serve names nothing.",
12239
12278
  "deprecated": null,
12240
12279
  "params": [],
12241
12280
  "returns": null,
@@ -12315,7 +12354,7 @@
12315
12354
  "group": "frame",
12316
12355
  "signature": "interface GameMenu",
12317
12356
  "documented": true,
12318
- "summary": "What your game adds to the arcade's menu — see `gem.setMenu`. Your settings\nare not part of it: they open from your own settings button.",
12357
+ "summary": "What your game adds to the arcade's menu — see `gem.setMenu`. Your settings\nscreen is not part of it: it opens from your own settings button. An action\non that screen (Replay the tutorial, How to play) may also be an entry here.",
12319
12358
  "deprecated": null,
12320
12359
  "params": [],
12321
12360
  "returns": null,
@@ -12790,7 +12829,7 @@
12790
12829
  "group": "start",
12791
12830
  "signature": "readonly store: StoreApi",
12792
12831
  "documented": true,
12793
- "summary": "**Real money.** The Arcade Store's own Gem packs, Boosts, passes and\nbundles, sold for the player's money through the arcade's own purchase\nsheet. To spend Gems or Coins on your game's own items, use\n[Gem.purchases](api-reference-game.md#gem-purchases).\n\nList what the store sells here with `offers()`, show each `priceDisplay`\nexactly as given, and call `purchase(skuId)` from the player's press. After\n`ok`, re-read balances through [Gem.me](api-reference-game.md#gem-me). Nothing rejects.",
12832
+ "summary": "**Real money.** The Arcade Store's own Gem packs, Boosts, passes and\nbundles, sold for the player's money through the arcade's own purchase\nsheet. To spend Gems or Coins on your game's own items, use\n[Gem.purchases](api-reference-game.md#gem-purchases).\n\nList what the store sells here with `offers()`, show each `priceDisplay`\nexactly as given, and call `purchase(skuId)` from the player's press. For a\npass, a bundle or anything else whose pitch you would have to hardcode, call\n`showOffer(skuId)` instead, and the arcade draws the offer. After `ok`,\nre-read balances through [Gem.me](api-reference-game.md#gem-me). Nothing rejects.",
12794
12833
  "deprecated": null,
12795
12834
  "params": [],
12796
12835
  "returns": null,
@@ -13234,7 +13273,7 @@
13234
13273
  "group": "start",
13235
13274
  "signature": "cueReward(cue: RewardCue): boolean",
13236
13275
  "documented": true,
13237
- "summary": "Tell the arcade a platform-currency reward is on its way, so it **shows it\nlanding**: in the Gem Arcade the currency flies from `origin` into the Gem\nbutton, wherever on your frame it sits, which pops as each mark lands, and\nthe player's total moves once the platform confirms the grant. The Gem\nArcade draws no wallet over your game: the player sees their Gems and\nCoins in the Gem menu. A payout you do not cue still flies, from the\nmiddle of your frame, so cue the ones the player earns on screen.\n\nCall it the moment your game decides to award — when your server has moved\n(or is about to move) the progression mapped onto a platform currency —\nand pass the centre of that currency's icon (or hero) on your screen, so\nthe flight starts from the mark the player is looking at:\n\n```js\nconst { x, y, width, height } = rewardGem.getBoundingClientRect(); // the gem's own box\ngem.cueReward({\n currencySlug: 'GEMS', // or 'ARCADE_TICKETS', from the coin\n expected: 15,\n origin: { x: x + width / 2, y: y + height / 2 },\n});\n```\n\n**This is the platform-currency animation, and the only one.** The display\nof every platform currency — Gems and Coins alike — belongs to the\narcade: a game draws no flight or celebration of its own for one and keeps\nno counter of one. **If this returns `false`, do nothing** — there is no\nfallback celebration.\n\n**The arcade never takes your word for the amount.** What lands, and the\nplayer's new total, come from the platform itself; `expected` is drawn as\nprovisional, and a cue the platform never confirms fades on its own — a\ngrant clamped to zero, say, or one the receipt reports `suppressed`. So\nthe cue can only ever make the wallet move for a reward that really\nlanded — which it does on every channel, dev and test included.",
13276
+ "summary": "Tell the arcade a platform-currency reward is on its way, so it **shows it\nlanding**: in the Gem Arcade the currency flies from `origin` into the Gem\nbutton, wherever on your frame it sits, which pops as each mark lands, and\nthe player's total moves once the platform confirms the grant. The Gem\nArcade draws no wallet over your game unless you ask (`setWalletVisible`):\nthe player sees their Gems and Coins in the Gem menu. A payout you do not\ncue still flies, from the middle of your frame, so cue the ones the player\nearns on screen.\n\nCall it the moment your game decides to award — when your server has moved\n(or is about to move) the progression mapped onto a platform currency —\nand pass the centre of that currency's icon (or hero) on your screen, so\nthe flight starts from the mark the player is looking at:\n\n```js\nconst { x, y, width, height } = rewardGem.getBoundingClientRect(); // the gem's own box\ngem.cueReward({\n currencySlug: 'GEMS', // or 'ARCADE_TICKETS', from the coin\n expected: 15,\n origin: { x: x + width / 2, y: y + height / 2 },\n});\n```\n\n**This is the platform-currency animation, and the only one.** The display\nof every platform currency — Gems and Coins alike — belongs to the\narcade: a game draws no flight or celebration of its own for one and keeps\nno counter of one. **If this returns `false`, do nothing** — there is no\nfallback celebration.\n\n**The arcade never takes your word for the amount.** What lands, and the\nplayer's new total, come from the platform itself; `expected` is drawn as\nprovisional, and a cue the platform never confirms fades on its own — a\ngrant clamped to zero, say, or one the receipt reports `suppressed`. So\nthe cue can only ever make the wallet move for a reward that really\nlanded — which it does on every channel, dev and test included.",
13238
13277
  "deprecated": null,
13239
13278
  "params": [
13240
13279
  {
@@ -13257,7 +13296,7 @@
13257
13296
  "group": "start",
13258
13297
  "signature": "setWalletVisible(visible: boolean, options?: WalletVisibleOptions): boolean",
13259
13298
  "documented": true,
13260
- "summary": "Show or hide the player's wallets — Gems, Coins, the achievement score —\nthat an embedder draws over your game.\n\n**The Gem Arcade draws no wallet over a game** (since 2026-10-11): the\nplayer sees their balances in the Gem menu, and payouts fly into the Gem\nbutton. So under the Gem Arcade this changes nothing on screen — it is\naccepted and harmless, and a game that already calls it keeps working.\nCall it for an embedder that does draw wallets over your game: hide them\nwhile you play, show them on your menus.\n\n```js\nlevel.onStart = () => gem.setWalletVisible(false);\nmenu.onOpen = () => gem.setWalletVisible(true);\n```\n\n**It is screen state, not a toggle.** Each call is the whole answer and\nreplaces the last. Where an embedder draws wallets, shown ones arrive as\nrectangles in `gem.viewport.reserved` and hiding them gives that space\nback; the Gem Arcade sends none for wallets. `only` narrows what shows,\nwhere an embedder honours it; the Gem Arcade does not.\n\n**Never draw a platform balance yourself**, and never show a pending state\nfor one: the arcade takes every figure from the platform. If this returns\n`false`, do nothing in its place.",
13299
+ "summary": "Ask the arcade to show, or stop showing, the player's wallets — Gems,\nCoins, the achievement score — over your game.\n\n**The Gem Arcade draws them on request** (the owner, 2026-10-29): a game\nthat says nothing gets no wallet over it, as since 2026-10-11, and the\nplayer sees their balances in the Gem menu; a game that asks gets them\ndrawn beside the Gem, from the platform's own figures. An Arcade from\nbefore that ignores the call, harmlessly.\n\n```js\ngem.setWalletVisible(true, { only: ['GEMS', 'ARCADE_TICKETS'] }); // every screen\nlevel.onStart = () => gem.setWalletVisible(false); // or put them away during play\n```\n\n**It is screen state, not a toggle.** Each call is the whole answer and\nreplaces the last. Shown wallets arrive as rectangles in\n`gem.viewport.reserved` (and widen the Gem's corner in the\n`--gem-reserved-*` variables), and hiding them gives that space back.\n`only` narrows what shows.\n\n**Never draw a platform balance yourself**, and never show a pending state\nfor one: the arcade takes every figure from the platform. If this returns\n`false`, do nothing in its place.",
13261
13300
  "deprecated": null,
13262
13301
  "params": [
13263
13302
  {
@@ -13286,7 +13325,7 @@
13286
13325
  "group": "start",
13287
13326
  "signature": "setMenu(menu: GameMenu): boolean",
13288
13327
  "documented": true,
13289
- "summary": "Put up to four entries of your own in the arcade's menu. The arcade keeps\none menu button over your game: it is how a player leaves, mutes, goes\nfullscreen, reports a problem and changes their Arcade-wide settings.\n\n```js\ngem.setMenu({\n items: [{ id: 'how-to-play', label: t('How to play'), onSelect: () => help.open() }],\n});\n```\n\n**Your settings are not in it: draw your own settings button, always** —\ntop-right, with your Back button just to its left, clear of\n`gem.viewport.reserved`. The Arcade's menu never opens your settings\nscreen.\n\n**It is screen state, not a toggle.** Each call is the whole answer and\nreplaces the last, so call it again when the entries change — a new\nlanguage, a screen with different options. `gem.setMenu({})` takes\neverything of yours back out. The arcade's own entries are not yours to\nchange, and Return to the Arcade is always among them.\n\n**The arcade pauses your game while its menu is open** (`onPause`), and\nresumes it before a handler of yours runs. A handler opens a screen; it\nshould not assume the game was paused by you.",
13328
+ "summary": "Put up to four entries of your own in the arcade's menu. The arcade keeps\none menu button over your game: it is how a player leaves, mutes, goes\nfullscreen, reports a problem and changes their Arcade-wide settings.\n\n```js\ngem.setMenu({\n items: [{ id: 'how-to-play', label: t('How to play'), onSelect: () => help.open() }],\n});\n```\n\n**Your settings screen is not in it: draw your own settings button** —\ntop-right, with your Back button just to its left, clear of\n`gem.viewport.reserved`. The Arcade's menu never opens your settings\nscreen. Inside the Gem Arcade you may leave the button out while this\nreturns `true`, when every setting on your screen is declared with\n`gem.settings` and every action on it is one of these entries; it returns\n`false` before the bridge is up and under an embedder with no menu, and the\nbutton stays there.\n\n**It is screen state, not a toggle.** Each call is the whole answer and\nreplaces the last, so call it again when the entries change — a new\nlanguage, a screen with different options. `gem.setMenu({})` takes\neverything of yours back out. The arcade's own entries are not yours to\nchange, and Return to the Arcade is always among them.\n\n**The arcade pauses your game while its menu is open** (`onPause`), and\nresumes it before a handler of yours runs. A handler opens a screen; it\nshould not assume the game was paused by you.",
13290
13329
  "deprecated": null,
13291
13330
  "params": [
13292
13331
  {
@@ -18,11 +18,17 @@ async function onBuyPressed(offer) {
18
18
  // Read through gem.me; nothing here carries an amount. `error` is "outcome unknown" (§9).
19
19
  if (outcome === 'ok' || reason === 'error') await refreshBalances();
20
20
  }
21
+
22
+ // From the press on your own "Get VIP" button: the Arcade draws the offer.
23
+ async function onVipPressed() {
24
+ const { outcome, reason } = await gem.store.showOffer(VIP_SKU);
25
+ if (outcome === 'ok' || reason === 'error') await refreshBalances();
26
+ }
21
27
  ```
22
28
 
23
29
  **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, draws every purchase sheet and takes the
25
- payment itself. You get an outcome.
30
+ field. The Arcade builds every link, reads every price, opens every checkout and takes the payment
31
+ itself. You get an outcome.
26
32
 
27
33
  - [1. Two surfaces, and which one is real money](#1-two-surfaces-and-which-one-is-real-money)
28
34
  - [2. Nothing rejects](#2-nothing-rejects)
@@ -33,15 +39,16 @@ payment itself. You get an outcome.
33
39
  - [7. Similar games, as cards](#7-similar-games-as-cards)
34
40
  - [8. The Arcade Store's offers](#8-the-arcade-stores-offers)
35
41
  - [9. Buying from the Arcade Store](#9-buying-from-the-arcade-store)
36
- - [10. Outcomes and reasons](#10-outcomes-and-reasons)
42
+ - [10. Letting the Arcade show one offer](#10-letting-the-arcade-show-one-offer)
43
+ - [11. Outcomes and reasons](#11-outcomes-and-reasons)
37
44
 
38
45
  ## 1. Two surfaces, and which one is real money
39
46
 
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 own sheet |
44
- | Who draws it | the Arcade (a new tab, a QR code, the next game) | the Arcade Store's sheet, over your paused game |
47
+ | | **`gem.arcade`** | **`gem.store`** |
48
+ | ------------ | ------------------------------------------------ | --------------------------------------------------- |
49
+ | What it does | app-store listings, another game, similar games | the Arcade Store's own SKUs |
50
+ | Money | none | **real money**, through the Arcade Store's checkout |
51
+ | Who draws it | the Arcade (a new tab, a QR code, the next game) | your card or the Arcade's sheet, then the checkout |
45
52
 
46
53
  `gem.store` is not `gem.purchases`. **`gem.purchases` spends Gems or Coins on your game's own
47
54
  items** ([`inventory-and-catalogs.md`](inventory-and-catalogs.md)). **`gem.store` sells the Arcade
@@ -60,7 +67,7 @@ three and never grows:
60
67
 
61
68
  `reason` is a short code that says why. The set is open: a new Arcade build may send a reason your
62
69
  game has never seen, so branch on `outcome` and treat `reason` as a hint. The codes in use today are
63
- in [§10](#10-outcomes-and-reasons).
70
+ in [§11](#11-outcomes-and-reasons).
64
71
 
65
72
  ## 3. What is available here
66
73
 
@@ -71,17 +78,19 @@ An Arcade that does not offer a feature answers every call for it at once with `
71
78
  if (gem.arcade.available.openGame) showMoreGamesSection();
72
79
  if (gem.arcade.available.appLinks) showGetTheAppButton();
73
80
  if (gem.store.available) showGetGemsButton();
81
+ if (gem.store.showOfferAvailable) showGetVipButton();
74
82
  ```
75
83
 
76
- `gem.arcade.available` is `{ appLinks, openGame }`, and `gem.store.available` is one boolean. All of
77
- them are synchronous and `false` until the bridge is up. A feature can be off for reasons you
84
+ `gem.arcade.available` is `{ appLinks, openGame }`, and `gem.store.available` and
85
+ `gem.store.showOfferAvailable` are one boolean each. All of them are synchronous and `false` until
86
+ the bridge is up. A feature can be off for reasons you
78
87
  cannot see from inside the frame: a TV, an in-app browser that cannot take payment, an app build
79
88
  where it is withheld. Do not guess; read it.
80
89
 
81
90
  ## 4. Call the acting ones from a press
82
91
 
83
- Three calls put something in front of the player: `gem.arcade.openApp`, `gem.arcade.openGame` and
84
- `gem.store.purchase`. **Call each one from the player's own press handler**, in the same turn as the
92
+ Four calls put something in front of the player: `gem.arcade.openApp`, `gem.arcade.openGame`,
93
+ `gem.store.purchase` and `gem.store.showOffer`. **Call each one from the player's own press handler**, in the same turn as the
85
94
  press. The Arcade checks that a person pressed, and refuses an ask nobody pressed for with
86
95
  `declined` / `busy`, showing nothing.
87
96
 
@@ -221,7 +230,7 @@ Gem pack, a `grant` naming the currency and amount.
221
230
  currency, already formatted. Never parse it, compare two of them, or compute a discount from it.
222
231
  There is no numeric price.
223
232
  - **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 sheet that takes
233
+ - **Do not imitate the Arcade's chrome.** Your offer list is yours to style; the checkout that takes
225
234
  the money is the Arcade's, and a game that draws something that looks like it confuses players
226
235
  about which one is real.
227
236
  - `unavailable` / `store_off` means the store is not offered here (a TV, an in-app browser that
@@ -234,12 +243,15 @@ Gem pack, a `grant` naming the currency and amount.
234
243
  const { outcome, reason } = await gem.store.purchase(offer.skuId);
235
244
  ```
236
245
 
237
- `purchase(skuId)` asks the Arcade to open its store's sheet for one SKU from `offers()`. The Arcade
238
- pauses your game, shows the offer with its own price, and takes the payment on the provider's page
239
- or the operating system's purchase sheet. Your game is resumed before the answer arrives, so your
240
- own celebration is visible.
246
+ `purchase(skuId)` asks the Arcade to open checkout for one SKU from `offers()`. The Arcade pauses
247
+ and hides your game and opens checkout **straight away**, with no confirmation screen of its own
248
+ first: the payment provider's page on the web, the app store's own purchase sheet in the Arcade's
249
+ apps. That checkout is where the player confirms and pays. Your game is resumed before the answer
250
+ arrives, so your own celebration is visible.
241
251
 
242
- - **Call it from the press** ([§4](#4-call-the-acting-ones-from-a-press)).
252
+ - **Call it from the press** ([§4](#4-call-the-acting-ones-from-a-press)). The player's press on
253
+ your Buy button is what opens checkout, so make that button say plainly what it buys and for how
254
+ much (`priceDisplay`).
243
255
  - **It has no timeout.** A payment takes as long as it takes, so the call waits for as long as the
244
256
  Arcade is connected.
245
257
  - `ok`: the player paid. **Read balances and inventory through `gem.me`**; the answer carries no
@@ -252,14 +264,16 @@ own celebration is visible.
252
264
  const stop = gem.store.onSettled(({ skuId }) => refreshBalances());
253
265
  ```
254
266
 
255
- - `declined` / `cancelled`: the player closed the sheet. Return to your screen quietly and **do not
256
- ask again unprompted**: the Arcade backs off after a dismissal, and the next ask is
257
- `declined` / `busy` for a while.
267
+ - `declined` / `cancelled`: the player closed the checkout without paying. Return to your screen
268
+ quietly and **do not ask again unprompted**: the Arcade backs off after the player closes one, and
269
+ the next ask is `declined` / `busy` for a while, longer each time. An offer the player cannot buy
270
+ again yet also answers `declined` / `cancelled`: the Arcade tells the player when it can be
271
+ bought, opens nothing, and does not back off.
258
272
  - `unavailable` with `store_off`, `sign_in_required` or `not_found` (a SKU that is not in
259
273
  `offers()` right now), or with a payment provider's own failure code: nothing was bought. The
260
274
  first three show nothing.
261
275
  - `unavailable` / `error`: **the outcome is unknown, and the player may have paid.** You get it
262
- when the bridge to the Arcade closes while the sheet is open, which can happen after the payment
276
+ when the bridge to the Arcade closes while checkout is open, which can happen after the payment
263
277
  went through, and when the Arcade has no reliable answer. Never tell the player nothing was
264
278
  bought. Re-read balances and inventory through `gem.me`, keep your `gem.store.onSettled`
265
279
  listener, and re-read again when your game resumes:
@@ -268,7 +282,57 @@ own celebration is visible.
268
282
  if (outcome === 'unavailable' && reason === 'error') await refreshBalances(); // not "nothing bought"
269
283
  ```
270
284
 
271
- ## 10. Outcomes and reasons
285
+ ## 10. Letting the Arcade show one offer
286
+
287
+ ```js
288
+ // Decide on the screen you draw the button on.
289
+ const { offers } = await gem.store.offers();
290
+ const vip = offers.find((offer) => offer.skuId === VIP_SKU);
291
+ if (gem.store.showOfferAvailable && vip) showGetVipButton();
292
+ else hideGetVipButton();
293
+
294
+ // From the press on that button.
295
+ const { outcome, reason } = await gem.store.showOffer(VIP_SKU);
296
+ if (outcome === 'ok' || reason === 'error') await refreshBalances();
297
+ ```
298
+
299
+ `showOffer(skuId)` asks the Arcade to draw one SKU from `offers()` in its own sheet, over your paused
300
+ and hidden game. The Arcade draws the art, what the player gets, a sale if one is running, and, for a
301
+ pass the player already holds, how long it has left. The sheet's Buy opens the same checkout
302
+ `purchase` opens.
303
+
304
+ Pick by who can say what the offer is:
305
+
306
+ | | **You draw it: `offers()` + `purchase()`** | **The Arcade draws it: `showOffer()`** |
307
+ | --------------- | --------------------------------------------------------- | ------------------------------------------------------------ |
308
+ | Use it for | Gem packs: a name, an amount and a price say it all | VIP and other passes, and bundles |
309
+ | Your button | says what it buys and shows `priceDisplay` | says what it opens, such as "Get VIP" |
310
+ | The press opens | the checkout, straight away | the Arcade's sheet, then the checkout if the player presses Buy |
311
+ | Perks and sales | not available to you | always current, in the player's language |
312
+ | A holder sees | the same Buy button | what they already have |
313
+
314
+ - **Never write an offer's perks into your game.** They change without a new build of your game, and
315
+ yours would go stale. Your button only needs the offer's name.
316
+ - **Do not show a price on the button.** The sheet shows the right one for this player, which a
317
+ sale or a pass can change, and `priceDisplay` does not say either of those. If you want a price on
318
+ screen, draw the offer yourself and use `purchase`.
319
+ - **You cannot tell whether the player holds a pass.** The Arcade keeps that to itself, and the
320
+ sheet tells the player. So the button stays up for a holder, and that is fine: the sheet is what
321
+ they see, and they choose from there.
322
+ - **Hide the button when the SKU is not in `offers()`.** A SKU that is not on sale here answers
323
+ `unavailable` / `not_found`, and the player would have pressed a button that does nothing.
324
+ - **Read `showOfferAvailable`, not `available`.** An older Arcade can sell through `purchase` and
325
+ still not draw this sheet. It answers `unavailable` / `unsupported` and sends nothing. Hide the
326
+ button there.
327
+
328
+ Every rule of `purchase` holds ([§9](#9-buying-from-the-arcade-store)), and the answers are the
329
+ same, with one addition. `declined` / `cancelled` also covers the player leaving the sheet without
330
+ pressing Buy, and you are not told which of the two they closed. That starts the same back-off as a
331
+ closed checkout, shared with `purchase`: the next ask of either kind is `declined` / `busy` for a
332
+ while. The press shows nothing during a back-off, so give the player something else to do on that
333
+ screen.
334
+
335
+ ## 11. Outcomes and reasons
272
336
 
273
337
  | Call | `ok` | `declined` | `unavailable` |
274
338
  | ---------------- | ------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------- |
@@ -278,6 +342,7 @@ own celebration is visible.
278
342
  | `similarGames` | cards, possibly none | | `unsupported`, `error` |
279
343
  | `store.offers` | offers, possibly none | | `sign_in_required`, `store_off`, `unsupported`, `error` |
280
344
  | `store.purchase` | paid, or `pending` | `cancelled`, `busy` | `sign_in_required`, `store_off`, `not_found`, the provider's reason, `unsupported`, `error` (outcome unknown) |
345
+ | `store.showOffer` | paid, or `pending` | `cancelled`, `busy` | `sign_in_required`, `store_off`, `not_found`, the provider's reason, `unsupported`, `error` (outcome unknown) |
281
346
 
282
347
  Every reason is a short code of lower-case letters and underscores. The Arcade turns a payment
283
348
  provider's own code into that shape before it reaches you.
@@ -291,5 +356,5 @@ Four reasons come from the SDK itself rather than the Arcade:
291
356
  - `error`: no reliable answer arrived. The ask could not be sent, a read or an open waited out its
292
357
  timeout (30 seconds for a read, two minutes for `openApp` and `openGame`), the bridge closed
293
358
  before the answer came, or the Arcade said it had no reliable answer. For a read, try again
294
- later. For `store.purchase` it means **the outcome is unknown**, never "nothing was bought"
295
- ([§9](#9-buying-from-the-arcade-store)).
359
+ later. For `store.purchase` and `store.showOffer` it means **the outcome is unknown**, never
360
+ "nothing was bought" ([§9](#9-buying-from-the-arcade-store)).