@indiegems/gem-web-sdk 23.2.0 → 24.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.
@@ -2,7 +2,7 @@
2
2
  "schema_version": 2,
3
3
  "source": {
4
4
  "repo": "IndieGems/gem-web-sdk",
5
- "version": "23.2.0"
5
+ "version": "24.0.0"
6
6
  },
7
7
  "entries": [
8
8
  {
@@ -77,6 +77,10 @@
77
77
  "key": "store",
78
78
  "title": "The Arcade Store, for real money"
79
79
  },
80
+ {
81
+ "key": "payout-preview",
82
+ "title": "Showing a platform payout: preview, upsells and receipts"
83
+ },
80
84
  {
81
85
  "key": "branding",
82
86
  "title": "The platform branding: logo, wordmark, currency marks"
@@ -3929,7 +3933,7 @@
3929
3933
  "group": "account",
3930
3934
  "signature": "type SignInOutcomeKind = \"signed_in\" | \"declined\" | \"unavailable\"",
3931
3935
  "documented": true,
3932
- "summary": "The complete vocabulary of answers. Three values, and there will not be a\nfourth.\n\n- `signed_in` — the player is signed in, and [Gem.identity](api-reference-game.md#gem-identity),\n [Gem.player](api-reference-game.md#gem-player) and [Gem.me](api-reference-game.md#gem-me) already answer for their account.\n Re-read anything you loaded before asking. Also the answer for a player who\n was signed in all along.\n- `declined` — the player closed the prompt, or the arcade chose not to show\n one. Return to the game silently.\n- `unavailable` — the arcade could not offer sign-in, or never answered.\n\nNo reason is given for a `declined`, and one will never be added: why a\nsign-in did not happen is a fact about the player's account, and the arcade\nis the one that explains those.",
3936
+ "summary": "The complete vocabulary of answers. Three values, and there will not be a\nfourth.\n\n- `signed_in` — the player is signed in, and [Gem.identity](api-reference-game.md#gem-identity),\n [Gem.player](api-reference-game.md#gem-player) and [Gem.me](api-reference-game.md#gem-me) already answer for their account.\n Re-read anything you loaded before asking. Also the answer for a player who\n was signed in all along.\n- `declined` — the player closed the prompt, or the arcade chose not to show\n one. Return to the game silently.\n- `unavailable` — the arcade could not offer sign-in, or never answered.\n [AccountApi.signInAvailable](api-reference-game.md#accountapi-signinavailable) says so before you draw the button.\n\nNo reason is given for a `declined`, and one will never be added: why a\nsign-in did not happen is a fact about the player's account, and the arcade\nis the one that explains those.",
3933
3937
  "deprecated": null,
3934
3938
  "params": [],
3935
3939
  "returns": null,
@@ -3986,6 +3990,50 @@
3986
3990
  "defaultValue": null,
3987
3991
  "guides": []
3988
3992
  },
3993
+ {
3994
+ "id": "AccountApi.signInAvailable",
3995
+ "name": "signInAvailable",
3996
+ "anchor": "accountapi-signinavailable",
3997
+ "group": "account",
3998
+ "signature": "readonly signInAvailable: boolean",
3999
+ "documented": true,
4000
+ "summary": "Whether [AccountApi.requestSignIn](api-reference-game.md#accountapi-requestsignin) can show this player a sign-in\nhere. Read it before you draw a \"Sign in to claim\" button, and hide the\noffer when it is `false`.\n\n- `true` — a guest the arcade can offer sign-up to. An ask may still\n answer `declined` at a busy moment.\n- `false` — an ask would show nothing. The player is already signed in\n (it answers `signed_in` at once; every player in a current Gem Arcade\n app build, v3.33.174 and later, is), or there is no Arcade sign-in here\n (it answers `unavailable`). Also `false` until the player is known.\n\nOn an arcade older than this signal it reads `true` for every player, a\nfull account included, as before it existed. So gate a sign-up gift on it\nand on the guest test you use today; see docs/player-sign-in.md.\n[AccountApi.onSignInAvailableChange](api-reference-game.md#accountapi-onsigninavailablechange) tells you when it changes.",
4001
+ "deprecated": null,
4002
+ "params": [],
4003
+ "returns": null,
4004
+ "throws": null,
4005
+ "example": "```ts\nconst draw = (available: boolean) => {\n signInButton.hidden = !available;\n};\ndraw(gem.account.signInAvailable);\ngem.account.onSignInAvailableChange(draw);\n```",
4006
+ "defaultValue": null,
4007
+ "guides": [
4008
+ {
4009
+ "slug": "player-sign-in",
4010
+ "anchor": null
4011
+ }
4012
+ ]
4013
+ },
4014
+ {
4015
+ "id": "AccountApi.onSignInAvailableChange",
4016
+ "name": "onSignInAvailableChange",
4017
+ "anchor": "accountapi-onsigninavailablechange",
4018
+ "group": "account",
4019
+ "signature": "onSignInAvailableChange(listener: (available: boolean) => void): () => void",
4020
+ "documented": true,
4021
+ "summary": "Runs `listener` whenever [AccountApi.signInAvailable](api-reference-game.md#accountapi-signinavailable) changes: when\nthe player arrives, when the arcade changes its answer (a guest who signs\nup), and when the connection closes. Not on a re-sent player with the same\nanswer.",
4022
+ "deprecated": null,
4023
+ "params": [
4024
+ {
4025
+ "name": "listener",
4026
+ "type": "(available: boolean) => void",
4027
+ "optional": false,
4028
+ "summary": null
4029
+ }
4030
+ ],
4031
+ "returns": "A function that stops the listener.",
4032
+ "throws": null,
4033
+ "example": null,
4034
+ "defaultValue": null,
4035
+ "guides": []
4036
+ },
3989
4037
  {
3990
4038
  "id": "AccountApi.requestSignIn",
3991
4039
  "name": "requestSignIn",
@@ -4735,7 +4783,7 @@
4735
4783
  "group": "analytics",
4736
4784
  "signature": "experiment(key: string, count: number): Promise<number>",
4737
4785
  "documented": true,
4738
- "summary": "Which arm of one of your experiments this player gets — the same answer on\nevery call, every refresh and every device. Use it wherever you would have\npicked an arm with `Math.random()`, which deals a player a new arm on each\nvisit and makes the test unreadable.\n\nPass a COUNT and get an index, `0` to `count - 1`; pass a LIST of strings\nand get one of them. A string listed twice is twice as likely.\n\n```js\nconst tutorial = await gem.analytics.experiment('tutorial_length', ['short', 'long']);\nconst layout = await gem.analytics.experiment('shop_layout', 3); // 0, 1 or 2\n```\n\nThe selection is a hash of the key and the signed-in player's id — FNV-1a\n32-bit over `\"<key>:<playerId>\"`, then MurmurHash3's 32-bit finalizer, mod\nthe arm count; see\n[experimentSelection](api-reference-game.md#experimentselection), which computes the same answer for an id you\nhold. Hashing the key in is what keeps your experiments independent: the\nplayers in one experiment's first arm are not the same players in every\nother experiment's first arm.\n\n⚠️ **Keep the key and the arms fixed for the life of the experiment.** A new\ncount or list re-deals every player. To start over on purpose, use a new\nkey.\n\n⚠️ **NOTHING IS RECORDED FOR YOU.** The selection is not sent anywhere, and\nthe SDK does not add it to your events. To analyse an experiment, declare a\ndimension for it on the events you want to break down, with the arms as its\nvalues, named after the experiment, and pass the selection there yourself:\n`gem.analytics.track('tutorial_finished', { tutorial_length: tutorial })`.\nNever the platform's `variant` field, which belongs to store-page\nexperiments.\n\nWAITS FOR THE PLAYER. The id arrives with the first play token, so a call\nduring boot waits for it, and rejects as [Gem.awaitIdentity](api-reference-game.md#gem-awaitidentity) does\nwhen none arrives — outside the Arcade, the Gem Editor or the dev host.\nThere is no random fallback: catch the rejection and play your control arm.\nA guest is selected by their guest id, which is stable for as long as their\nguest account lasts; once they sign in to an account they are that\naccount's player, and its selection applies.\n\nIn a serverless function it selects for the player who invoked it, with\nno wait, and rejects when a game server invoked it.\n\nWorks with analytics turned off: it reads no setting and sends nothing.",
4786
+ "summary": "Which arm of one of your experiments this player gets — the same answer on\nevery call, every refresh and every device. Use it wherever you would have\npicked an arm with `Math.random()`, which deals a player a new arm on each\nvisit and makes the test unreadable.\n\nPass a COUNT and get an index, `0` to `count - 1`; pass a LIST of strings\nand get one of them. A string listed twice is twice as likely.\n\n```js\nconst tutorial = await gem.analytics.experiment('tutorial_length', ['short', 'long']);\nconst layout = await gem.analytics.experiment('shop_layout', 3); // 0, 1 or 2\n```\n\nThe selection is a hash of the key and the signed-in player's id — FNV-1a\n32-bit over `\"<key>:<playerId>\"`, then MurmurHash3's 32-bit finalizer, mod\nthe arm count; see\n[experimentSelection](api-reference-game.md#experimentselection), which computes the same answer for an id you\nhold. Hashing the key in is what keeps your experiments independent: the\nplayers in one experiment's first arm are not the same players in every\nother experiment's first arm.\n\n⚠️ **Keep the key and the arms fixed for the life of the experiment.** A new\ncount or list re-deals every player. To start over on purpose, use a new\nkey.\n\n⚠️ **NOTHING IS RECORDED FOR YOU.** The selection is not sent anywhere, and\nthe SDK does not add it to your events. To analyse an experiment, declare a\ndimension for it on the events you want to break down, with the arms as its\nvalues, named after the experiment, and pass the selection there yourself:\n`gem.analytics.track('tutorial_finished', { tutorial_length: tutorial })`.\nNever the platform's `variant` field, which belongs to store-page\nexperiments. For the platform's results to show the test, also declare it\nunder `analytics_tests` in your game config (see docs/analytics.md).\n\nWAITS FOR THE PLAYER. The id arrives with the first play token, so a call\nduring boot waits for it, and rejects as [Gem.awaitIdentity](api-reference-game.md#gem-awaitidentity) does\nwhen none arrives — outside the Arcade, the Gem Editor or the dev host.\nThere is no random fallback: catch the rejection and play your control arm.\nA guest is selected by their guest id, which is stable for as long as their\nguest account lasts; once they sign in to an account they are that\naccount's player, and its selection applies.\n\nIn a serverless function it selects for the player who invoked it, with\nno wait, and rejects when a game server invoked it.\n\nWorks with analytics turned off: it reads no setting and sends nothing.",
4739
4787
  "deprecated": null,
4740
4788
  "params": [
4741
4789
  {
@@ -5545,9 +5593,9 @@
5545
5593
  "name": "kind",
5546
5594
  "anchor": "arcadeoffer-kind",
5547
5595
  "group": "store",
5548
- "signature": "readonly kind: \"gem_pack\" | \"boost\" | \"pass\" | \"bundle\"",
5596
+ "signature": "readonly kind: ArcadeOfferKind",
5549
5597
  "documented": true,
5550
- "summary": "What sort of thing it is.",
5598
+ "summary": "What sort of thing it is: `coin_pack`, `boost`, `pass` or `bundle` — or\n`gem_pack`, which is deprecated: Gems are being retired, and a pack of\nCoins is `coin_pack`. Keep a `default` arm. See\n[the deprecation policy](../deprecation-policy.md).",
5551
5599
  "deprecated": null,
5552
5600
  "params": [],
5553
5601
  "returns": null,
@@ -5627,7 +5675,7 @@
5627
5675
  "group": "store",
5628
5676
  "signature": "readonly grant?: { currency: string; amount: number }",
5629
5677
  "documented": true,
5630
- "summary": "For a Gem pack: the platform currency and the amount it grants.",
5678
+ "summary": "For a Coin pack: the platform currency and the amount it grants.",
5631
5679
  "deprecated": null,
5632
5680
  "params": [],
5633
5681
  "returns": null,
@@ -5643,7 +5691,7 @@
5643
5691
  "group": "store",
5644
5692
  "signature": "readonly currency: string",
5645
5693
  "documented": true,
5646
- "summary": "The platform currency's exact slug, e.g. `GEMS`.",
5694
+ "summary": "The platform currency's exact slug, e.g. `ARCADE_TICKETS` for Coins.",
5647
5695
  "deprecated": null,
5648
5696
  "params": [],
5649
5697
  "returns": null,
@@ -5668,6 +5716,166 @@
5668
5716
  "defaultValue": null,
5669
5717
  "guides": []
5670
5718
  },
5719
+ {
5720
+ "id": "ArcadeOffer.pass",
5721
+ "name": "pass",
5722
+ "anchor": "arcadeoffer-pass",
5723
+ "group": "store",
5724
+ "signature": "readonly pass?: string",
5725
+ "documented": true,
5726
+ "summary": "For a pass, or a bundle carrying one: the pass's slug, such as `vip` — the\nslug `gem.me.passes` reports and [StoreApi.showPass](api-reference-game.md#storeapi-showpass) takes. Absent\nfor everything else, and on an older Arcade.",
5727
+ "deprecated": null,
5728
+ "params": [],
5729
+ "returns": null,
5730
+ "throws": null,
5731
+ "example": null,
5732
+ "defaultValue": null,
5733
+ "guides": []
5734
+ },
5735
+ {
5736
+ "id": "ArcadeOffer.earnMultipliers",
5737
+ "name": "earnMultipliers",
5738
+ "anchor": "arcadeoffer-earnmultipliers",
5739
+ "group": "store",
5740
+ "signature": "readonly earnMultipliers: readonly ArcadeOfferEarnMultiplier[]",
5741
+ "documented": true,
5742
+ "summary": "For an offer that makes the player earn a platform currency faster while\nthey hold it: one entry per currency. Empty when it boosts nothing.\n\nThe platform's own figure, for `gem.gameConfig.preview` to work out what\nholding it would add. **Never multiply a reward by it yourself**: the\npreview rounds the way the platform pays.",
5743
+ "deprecated": null,
5744
+ "params": [],
5745
+ "returns": null,
5746
+ "throws": null,
5747
+ "example": null,
5748
+ "defaultValue": null,
5749
+ "guides": []
5750
+ },
5751
+ {
5752
+ "id": "ArcadeOfferKind",
5753
+ "name": "ArcadeOfferKind",
5754
+ "anchor": "arcadeofferkind",
5755
+ "group": "store",
5756
+ "signature": "type ArcadeOfferKind = \"coin_pack\" | \"gem_pack\" | \"boost\" | \"pass\" | \"bundle\"",
5757
+ "documented": true,
5758
+ "summary": "What sort of SKU an [ArcadeOffer](api-reference-game.md#arcadeoffer) is. `gem_pack` is deprecated: Gems\nare being retired, and a pack of Coins is `coin_pack`.",
5759
+ "deprecated": null,
5760
+ "params": [],
5761
+ "returns": null,
5762
+ "throws": null,
5763
+ "example": null,
5764
+ "defaultValue": null,
5765
+ "guides": []
5766
+ },
5767
+ {
5768
+ "id": "ArcadeOfferEarnMultiplier",
5769
+ "name": "ArcadeOfferEarnMultiplier",
5770
+ "anchor": "arcadeofferearnmultiplier",
5771
+ "group": "store",
5772
+ "signature": "interface ArcadeOfferEarnMultiplier",
5773
+ "documented": true,
5774
+ "summary": "One currency an [ArcadeOffer](api-reference-game.md#arcadeoffer) boosts while it is held.",
5775
+ "deprecated": null,
5776
+ "params": [],
5777
+ "returns": null,
5778
+ "throws": null,
5779
+ "example": null,
5780
+ "defaultValue": null,
5781
+ "guides": []
5782
+ },
5783
+ {
5784
+ "id": "ArcadeOfferEarnMultiplier.currencySlug",
5785
+ "name": "currencySlug",
5786
+ "anchor": "arcadeofferearnmultiplier-currencyslug",
5787
+ "group": "store",
5788
+ "signature": "readonly currencySlug: string",
5789
+ "documented": true,
5790
+ "summary": "The currency's slug, e.g. `ARCADE_TICKETS`.",
5791
+ "deprecated": null,
5792
+ "params": [],
5793
+ "returns": null,
5794
+ "throws": null,
5795
+ "example": null,
5796
+ "defaultValue": null,
5797
+ "guides": []
5798
+ },
5799
+ {
5800
+ "id": "ArcadeOfferEarnMultiplier.multiplierBp",
5801
+ "name": "multiplierBp",
5802
+ "anchor": "arcadeofferearnmultiplier-multiplierbp",
5803
+ "group": "store",
5804
+ "signature": "readonly multiplierBp: number",
5805
+ "documented": true,
5806
+ "summary": "Basis points: `10000` is 1x, `15000` is 1.5x. An integer, so the preview\nrounds exactly as the platform does.",
5807
+ "deprecated": null,
5808
+ "params": [],
5809
+ "returns": null,
5810
+ "throws": null,
5811
+ "example": null,
5812
+ "defaultValue": null,
5813
+ "guides": []
5814
+ },
5815
+ {
5816
+ "id": "ShowPassOutcome",
5817
+ "name": "ShowPassOutcome",
5818
+ "anchor": "showpassoutcome",
5819
+ "group": "store",
5820
+ "signature": "interface ShowPassOutcome",
5821
+ "documented": true,
5822
+ "summary": "What [StoreApi.showPass](api-reference-game.md#storeapi-showpass) resolves with: an [ArcadeOutcome](api-reference-game.md#arcadeoutcome), plus\nwhether a fresh read shows the pass held.",
5823
+ "deprecated": null,
5824
+ "params": [],
5825
+ "returns": null,
5826
+ "throws": null,
5827
+ "example": null,
5828
+ "defaultValue": null,
5829
+ "guides": []
5830
+ },
5831
+ {
5832
+ "id": "ShowPassOutcome.outcome",
5833
+ "name": "outcome",
5834
+ "anchor": "showpassoutcome-outcome",
5835
+ "group": "store",
5836
+ "signature": "readonly outcome: ArcadeOutcomeKind",
5837
+ "documented": true,
5838
+ "summary": "What happened: `ok`, `declined` or `unavailable`. Branch on this.",
5839
+ "deprecated": null,
5840
+ "params": [],
5841
+ "returns": null,
5842
+ "throws": null,
5843
+ "example": null,
5844
+ "defaultValue": null,
5845
+ "guides": []
5846
+ },
5847
+ {
5848
+ "id": "ShowPassOutcome.reason",
5849
+ "name": "reason",
5850
+ "anchor": "showpassoutcome-reason",
5851
+ "group": "store",
5852
+ "signature": "readonly reason?: string",
5853
+ "documented": true,
5854
+ "summary": "Why, as a machine code — `busy`, `cancelled`, `not_found`, `pending`,\n`store_off`, `sign_in_required` and more. OPEN-ENDED: the arcade may send a\ncode this SDK has never heard of, so keep a `default` arm and never show a\ncode to a player. Absent when there is nothing to add.\n\nThree are the SDK's own, answered without asking the arcade:\n`unsupported` (the arcade here does not offer this), `invalid_argument`\n(the SDK refused what you passed) and `busy` (another ask of the same kind\nis still open). `error` means no reliable answer arrived — the ask never\nleft, the wait ran out, the bridge closed or the arcade had none. For\n`gem.store.purchase` that makes the outcome UNKNOWN, not a refusal: the\nplayer may have paid.",
5855
+ "deprecated": null,
5856
+ "params": [],
5857
+ "returns": null,
5858
+ "throws": null,
5859
+ "example": null,
5860
+ "defaultValue": null,
5861
+ "guides": []
5862
+ },
5863
+ {
5864
+ "id": "ShowPassOutcome.held",
5865
+ "name": "held",
5866
+ "anchor": "showpassoutcome-held",
5867
+ "group": "store",
5868
+ "signature": "readonly held: boolean",
5869
+ "documented": true,
5870
+ "summary": "Whether a fresh read of `gem.me.passes` showed the pass held once the\nsheet closed. `true` exactly when `outcome` is `ok` with no `reason`: a\nrecipe the game runs next gets the pass's bonus.",
5871
+ "deprecated": null,
5872
+ "params": [],
5873
+ "returns": null,
5874
+ "throws": null,
5875
+ "example": null,
5876
+ "defaultValue": null,
5877
+ "guides": []
5878
+ },
5671
5879
  {
5672
5880
  "id": "StoreOffersOutcome",
5673
5881
  "name": "StoreOffersOutcome",
@@ -5771,7 +5979,7 @@
5771
5979
  "group": "store",
5772
5980
  "signature": "interface StoreApi",
5773
5981
  "documented": true,
5774
- "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`.",
5982
+ "summary": "**Real money.** The Arcade Store's own Coin 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 Coins on your game's own items, use\n`gem.purchases`. Only the Arcade sells Coins: a game never sells currency\nfor money.",
5775
5983
  "deprecated": null,
5776
5984
  "params": [],
5777
5985
  "returns": null,
@@ -5787,7 +5995,7 @@
5787
5995
  "group": "store",
5788
5996
  "signature": "readonly available: boolean",
5789
5997
  "documented": true,
5790
- "summary": "Whether the Arcade Store answers here, synchronously. Draw your own \"Get\nGems\" button only when it is true.",
5998
+ "summary": "Whether the Arcade Store answers here, synchronously. Draw your own \"Get\nCoins\" button only when it is true.",
5791
5999
  "deprecated": null,
5792
6000
  "params": [],
5793
6001
  "returns": null,
@@ -5819,7 +6027,7 @@
5819
6027
  "group": "store",
5820
6028
  "signature": "offers(): Promise<StoreOffersOutcome>",
5821
6029
  "documented": true,
5822
- "summary": "What the store sells this player here: names, art and the rail's own\nprices. `unavailable` / `sign_in_required` for a guest (offer\n`gem.account.requestSignIn`), `unavailable` / `store_off` where the store\nis withheld. Re-read on resume: what is on sale can change.",
6030
+ "summary": "What the store sells this player here: names, art and the rail's own\nprices. A guest is answered too: buying takes them through sign-up first,\nin the Arcade's own screens. `unavailable` / `store_off` where the store is\nwithheld. An older Arcade answers a guest `unavailable` /\n`sign_in_required`. Re-read on resume: what is on sale can change.",
5823
6031
  "deprecated": null,
5824
6032
  "params": [],
5825
6033
  "returns": "Always resolves; never rejects.",
@@ -5858,7 +6066,7 @@
5858
6066
  "group": "store",
5859
6067
  "signature": "showOffer(skuId: string): Promise<ArcadeOutcome>",
5860
6068
  "documented": true,
5861
- "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). Like `purchase`, it needs no press.\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. Do not ask again\nunprompted; the player's next press opens it again. `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.",
6069
+ "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). Like `purchase`, it needs no press.\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\nCoin pack yourself and use `purchase` instead. For a pass, call\n[StoreApi.showPass](api-reference-game.md#storeapi-showpass) with its slug. 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. Do not ask again\nunprompted; the player's next press opens it again. `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.",
5862
6070
  "deprecated": null,
5863
6071
  "params": [
5864
6072
  {
@@ -5874,6 +6082,52 @@
5874
6082
  "defaultValue": null,
5875
6083
  "guides": []
5876
6084
  },
6085
+ {
6086
+ "id": "StoreApi.showPass",
6087
+ "name": "showPass",
6088
+ "anchor": "storeapi-showpass",
6089
+ "group": "store",
6090
+ "signature": "showPass(slug: string): Promise<ShowPassOutcome>",
6091
+ "documented": true,
6092
+ "summary": "Show a pass by its slug in the Arcade's own sheet, over your paused game.\nPass it `upsell.pass` straight from `gem.gameConfig.preview`, never a\nliteral. Sugar over [StoreApi.offers](api-reference-game.md#storeapi-offers) and [StoreApi.showOffer](api-reference-game.md#storeapi-showoffer):\nit finds the offer whose `pass` is `slug` and shows that. A guest is taken\nthrough sign-up first, then back to the sheet.\n\n- `ok`, `held: true` — **a fresh read of `gem.me.passes` shows the pass\n held.** Run the recipe or claim now, and the pass's bonus applies.\n- `ok` / `pending`, `held: false` — the player paid, and the pass has not\n arrived yet (Ask to Buy, a slow payment). Run the recipe now, without\n the bonus: the platform holds nothing back for later.\n- `declined` — they closed the sheet. Run the recipe as it is, and don't\n ask again at this reward moment.\n- `unavailable` / `not_found` — the pass is not on sale to this player\n here, or this Arcade is too old to say which offer is a pass, and\n nothing opened. `unavailable` / `unsupported` — this Arcade draws no\n sheet. Run the recipe as it is.\n- `unavailable` / `error` — **the outcome is unknown, and the player may\n have paid.** Run the recipe; `gem.me.passes.on('changed')` hears the\n pass if it arrives.\n\nNO TIMEOUT on the sheet, as with `showOffer`. After a paid `ok` it reads\n`gem.me.passes` for about ten seconds before it settles for `pending`.",
6093
+ "deprecated": null,
6094
+ "params": [
6095
+ {
6096
+ "name": "slug",
6097
+ "type": "string",
6098
+ "optional": false,
6099
+ "summary": null
6100
+ }
6101
+ ],
6102
+ "returns": "Always resolves; never rejects.",
6103
+ "throws": null,
6104
+ "example": null,
6105
+ "defaultValue": null,
6106
+ "guides": []
6107
+ },
6108
+ {
6109
+ "id": "StoreApi.passOnSale",
6110
+ "name": "passOnSale",
6111
+ "anchor": "storeapi-passonsale",
6112
+ "group": "store",
6113
+ "signature": "passOnSale(slug: string): Promise<boolean>",
6114
+ "documented": true,
6115
+ "summary": "Whether the pass `slug` is on sale to this player here, so\n[StoreApi.showPass](api-reference-game.md#storeapi-showpass) would open it. For a screen that sells a pass\noutside a reward moment, such as a \"VIP perks\" panel; at a reward moment,\n`gem.gameConfig.preview`'s `upsells` already says so. `false` where the\nstore is off, for a player the platform won't sell it to, after the\nplayer declined its sheet twice today, and on an older Arcade. A player\nwho holds the pass may still read `true`: the sheet adds time.",
6116
+ "deprecated": null,
6117
+ "params": [
6118
+ {
6119
+ "name": "slug",
6120
+ "type": "string",
6121
+ "optional": false,
6122
+ "summary": null
6123
+ }
6124
+ ],
6125
+ "returns": "Always resolves; never rejects.",
6126
+ "throws": null,
6127
+ "example": null,
6128
+ "defaultValue": null,
6129
+ "guides": []
6130
+ },
5877
6131
  {
5878
6132
  "id": "StoreApi.onSettled",
5879
6133
  "name": "onSettled",
@@ -7814,6 +8068,374 @@
7814
8068
  "defaultValue": null,
7815
8069
  "guides": []
7816
8070
  },
8071
+ {
8072
+ "id": "PreviewTarget",
8073
+ "name": "PreviewTarget",
8074
+ "anchor": "previewtarget",
8075
+ "group": "payout-preview",
8076
+ "signature": "type PreviewTarget = { recipe: string } | { progression: string; add?: number }",
8077
+ "documented": true,
8078
+ "summary": "What to preview: a recipe, or a progression with the delta the game is about\nto apply. A config id, never an amount and never a pass.\n\n- `{ recipe: 'open_chest' }` — the recipe's grants, its roll, its bonuses,\n and the platform payouts of any progression it adds to.\n- `{ progression: 'run_complete' }` — the progression's `on_complete`\n payouts, \"if it completes\".\n- `{ progression: 'puzzles_cleared', add: 3 }` — its `on_add` payouts for\n that delta, and its `on_complete` payouts if it completes.\n\nIds and slugs are both accepted, as everywhere in `gem.gameConfig`.",
8079
+ "deprecated": null,
8080
+ "params": [],
8081
+ "returns": null,
8082
+ "throws": null,
8083
+ "example": null,
8084
+ "defaultValue": null,
8085
+ "guides": []
8086
+ },
8087
+ {
8088
+ "id": "PreviewGrant",
8089
+ "name": "PreviewGrant",
8090
+ "anchor": "previewgrant",
8091
+ "group": "payout-preview",
8092
+ "signature": "type PreviewGrant = { kind: \"item\"; itemId: string; quantity: number } | { kind: \"currency\"; currencyId: string; amount: number } | { kind: \"roll\"; lootTableId: string; count: number }",
8093
+ "documented": true,
8094
+ "summary": "One thing a claim grants in your game. A roll is a count of rolls from a loot\ntable, never what they land on: never show a locked roll's outcome.",
8095
+ "deprecated": null,
8096
+ "params": [],
8097
+ "returns": null,
8098
+ "throws": null,
8099
+ "example": null,
8100
+ "defaultValue": null,
8101
+ "guides": []
8102
+ },
8103
+ {
8104
+ "id": "UnmetRequirement",
8105
+ "name": "UnmetRequirement",
8106
+ "anchor": "unmetrequirement",
8107
+ "group": "payout-preview",
8108
+ "signature": "interface UnmetRequirement",
8109
+ "documented": true,
8110
+ "summary": "One requirement a bonus has that the player does not meet: where it is, and its kind.",
8111
+ "deprecated": null,
8112
+ "params": [],
8113
+ "returns": null,
8114
+ "throws": null,
8115
+ "example": null,
8116
+ "defaultValue": null,
8117
+ "guides": []
8118
+ },
8119
+ {
8120
+ "id": "UnmetRequirement.index",
8121
+ "name": "index",
8122
+ "anchor": "unmetrequirement-index",
8123
+ "group": "payout-preview",
8124
+ "signature": "readonly index: number",
8125
+ "documented": true,
8126
+ "summary": "Its position in the bonus's `requires`.",
8127
+ "deprecated": null,
8128
+ "params": [],
8129
+ "returns": null,
8130
+ "throws": null,
8131
+ "example": null,
8132
+ "defaultValue": null,
8133
+ "guides": []
8134
+ },
8135
+ {
8136
+ "id": "UnmetRequirement.kind",
8137
+ "name": "kind",
8138
+ "anchor": "unmetrequirement-kind",
8139
+ "group": "payout-preview",
8140
+ "signature": "readonly kind: string",
8141
+ "documented": true,
8142
+ "summary": "Its kind, such as `holds_pass`. Never the pass or the item it names.",
8143
+ "deprecated": null,
8144
+ "params": [],
8145
+ "returns": null,
8146
+ "throws": null,
8147
+ "example": null,
8148
+ "defaultValue": null,
8149
+ "guides": []
8150
+ },
8151
+ {
8152
+ "id": "BonusPreview",
8153
+ "name": "BonusPreview",
8154
+ "anchor": "bonuspreview",
8155
+ "group": "payout-preview",
8156
+ "signature": "interface BonusPreview",
8157
+ "documented": true,
8158
+ "summary": "One `bonuses` entry, and whether this player gets it.",
8159
+ "deprecated": null,
8160
+ "params": [],
8161
+ "returns": null,
8162
+ "throws": null,
8163
+ "example": null,
8164
+ "defaultValue": null,
8165
+ "guides": []
8166
+ },
8167
+ {
8168
+ "id": "BonusPreview.id",
8169
+ "name": "id",
8170
+ "anchor": "bonuspreview-id",
8171
+ "group": "payout-preview",
8172
+ "signature": "readonly id: string",
8173
+ "documented": true,
8174
+ "summary": "The bonus's id, as you configured it.",
8175
+ "deprecated": null,
8176
+ "params": [],
8177
+ "returns": null,
8178
+ "throws": null,
8179
+ "example": null,
8180
+ "defaultValue": null,
8181
+ "guides": []
8182
+ },
8183
+ {
8184
+ "id": "BonusPreview.grants",
8185
+ "name": "grants",
8186
+ "anchor": "bonuspreview-grants",
8187
+ "group": "payout-preview",
8188
+ "signature": "readonly grants: readonly PreviewGrant[]",
8189
+ "documented": true,
8190
+ "summary": "What it grants.",
8191
+ "deprecated": null,
8192
+ "params": [],
8193
+ "returns": null,
8194
+ "throws": null,
8195
+ "example": null,
8196
+ "defaultValue": null,
8197
+ "guides": []
8198
+ },
8199
+ {
8200
+ "id": "BonusPreview.eligible",
8201
+ "name": "eligible",
8202
+ "anchor": "bonuspreview-eligible",
8203
+ "group": "payout-preview",
8204
+ "signature": "readonly eligible: boolean | \"unknown\"",
8205
+ "documented": true,
8206
+ "summary": "`true` — every requirement holds now. `false` — at least one does not\n(see `unmet`). `'unknown'` — one the browser cannot see, such as owning a\nplatform item; never a guess. Advisory: the run decides.",
8207
+ "deprecated": null,
8208
+ "params": [],
8209
+ "returns": null,
8210
+ "throws": null,
8211
+ "example": null,
8212
+ "defaultValue": null,
8213
+ "guides": []
8214
+ },
8215
+ {
8216
+ "id": "BonusPreview.unmet",
8217
+ "name": "unmet",
8218
+ "anchor": "bonuspreview-unmet",
8219
+ "group": "payout-preview",
8220
+ "signature": "readonly unmet: readonly UnmetRequirement[]",
8221
+ "documented": true,
8222
+ "summary": "The requirements that do not hold. Empty unless `eligible` is `false`.",
8223
+ "deprecated": null,
8224
+ "params": [],
8225
+ "returns": null,
8226
+ "throws": null,
8227
+ "example": null,
8228
+ "defaultValue": null,
8229
+ "guides": []
8230
+ },
8231
+ {
8232
+ "id": "PassUpsell",
8233
+ "name": "PassUpsell",
8234
+ "anchor": "passupsell",
8235
+ "group": "payout-preview",
8236
+ "signature": "interface PassUpsell",
8237
+ "documented": true,
8238
+ "summary": "What holding one pass would add to a claim.",
8239
+ "deprecated": null,
8240
+ "params": [],
8241
+ "returns": null,
8242
+ "throws": null,
8243
+ "example": null,
8244
+ "defaultValue": null,
8245
+ "guides": []
8246
+ },
8247
+ {
8248
+ "id": "PassUpsell.pass",
8249
+ "name": "pass",
8250
+ "anchor": "passupsell-pass",
8251
+ "group": "payout-preview",
8252
+ "signature": "readonly pass: string",
8253
+ "documented": true,
8254
+ "summary": "The pass's slug: what `gem.store.showPass` takes.",
8255
+ "deprecated": null,
8256
+ "params": [],
8257
+ "returns": null,
8258
+ "throws": null,
8259
+ "example": null,
8260
+ "defaultValue": null,
8261
+ "guides": []
8262
+ },
8263
+ {
8264
+ "id": "PassUpsell.name",
8265
+ "name": "name",
8266
+ "anchor": "passupsell-name",
8267
+ "group": "payout-preview",
8268
+ "signature": "readonly name: string",
8269
+ "documented": true,
8270
+ "summary": "The store's own name for it, localized. Show this, never your own word for it.",
8271
+ "deprecated": null,
8272
+ "params": [],
8273
+ "returns": null,
8274
+ "throws": null,
8275
+ "example": null,
8276
+ "defaultValue": null,
8277
+ "guides": []
8278
+ },
8279
+ {
8280
+ "id": "PassUpsell.offer",
8281
+ "name": "offer",
8282
+ "anchor": "passupsell-offer",
8283
+ "group": "payout-preview",
8284
+ "signature": "readonly offer: ArcadeOffer",
8285
+ "documented": true,
8286
+ "summary": "The pass's offer, as `gem.store.offers` lists it.",
8287
+ "deprecated": null,
8288
+ "params": [],
8289
+ "returns": null,
8290
+ "throws": null,
8291
+ "example": null,
8292
+ "defaultValue": null,
8293
+ "guides": []
8294
+ },
8295
+ {
8296
+ "id": "PassUpsell.platformGrants",
8297
+ "name": "platformGrants",
8298
+ "anchor": "passupsell-platformgrants",
8299
+ "group": "payout-preview",
8300
+ "signature": "readonly platformGrants: readonly { currencySlug?: string; currencyId?: string; total: number; extra: number; ifComplete?: true }[]",
8301
+ "documented": true,
8302
+ "summary": "Each payout this pass would raise: the new `total`, and the `extra` over now.",
8303
+ "deprecated": null,
8304
+ "params": [],
8305
+ "returns": null,
8306
+ "throws": null,
8307
+ "example": null,
8308
+ "defaultValue": null,
8309
+ "guides": []
8310
+ },
8311
+ {
8312
+ "id": "PassUpsell.bonuses",
8313
+ "name": "bonuses",
8314
+ "anchor": "passupsell-bonuses",
8315
+ "group": "payout-preview",
8316
+ "signature": "readonly bonuses: readonly { id: string; grants: readonly PreviewGrant[] }[]",
8317
+ "documented": true,
8318
+ "summary": "The bonuses this pass would make eligible.",
8319
+ "deprecated": null,
8320
+ "params": [],
8321
+ "returns": null,
8322
+ "throws": null,
8323
+ "example": null,
8324
+ "defaultValue": null,
8325
+ "guides": []
8326
+ },
8327
+ {
8328
+ "id": "ConfigPreview",
8329
+ "name": "ConfigPreview",
8330
+ "anchor": "configpreview",
8331
+ "group": "payout-preview",
8332
+ "signature": "interface ConfigPreview",
8333
+ "documented": true,
8334
+ "summary": "What [PreviewTarget](api-reference-game.md#previewtarget) would give this player now, and what each pass would add.",
8335
+ "deprecated": null,
8336
+ "params": [],
8337
+ "returns": null,
8338
+ "throws": null,
8339
+ "example": null,
8340
+ "defaultValue": null,
8341
+ "guides": []
8342
+ },
8343
+ {
8344
+ "id": "ConfigPreview.grants",
8345
+ "name": "grants",
8346
+ "anchor": "configpreview-grants",
8347
+ "group": "payout-preview",
8348
+ "signature": "readonly grants: readonly PreviewGrant[]",
8349
+ "documented": true,
8350
+ "summary": "Your game's own grants: items, game currencies, rolls.",
8351
+ "deprecated": null,
8352
+ "params": [],
8353
+ "returns": null,
8354
+ "throws": null,
8355
+ "example": null,
8356
+ "defaultValue": null,
8357
+ "guides": []
8358
+ },
8359
+ {
8360
+ "id": "ConfigPreview.platformGrants",
8361
+ "name": "platformGrants",
8362
+ "anchor": "configpreview-platformgrants",
8363
+ "group": "payout-preview",
8364
+ "signature": "readonly platformGrants: readonly PlatformGrantPreview[]",
8365
+ "documented": true,
8366
+ "summary": "One per platform payout that would fire.",
8367
+ "deprecated": null,
8368
+ "params": [],
8369
+ "returns": null,
8370
+ "throws": null,
8371
+ "example": null,
8372
+ "defaultValue": null,
8373
+ "guides": []
8374
+ },
8375
+ {
8376
+ "id": "ConfigPreview.bonuses",
8377
+ "name": "bonuses",
8378
+ "anchor": "configpreview-bonuses",
8379
+ "group": "payout-preview",
8380
+ "signature": "readonly bonuses: readonly BonusPreview[]",
8381
+ "documented": true,
8382
+ "summary": "One per `bonuses` entry, in config order.",
8383
+ "deprecated": null,
8384
+ "params": [],
8385
+ "returns": null,
8386
+ "throws": null,
8387
+ "example": null,
8388
+ "defaultValue": null,
8389
+ "guides": []
8390
+ },
8391
+ {
8392
+ "id": "ConfigPreview.upsells",
8393
+ "name": "upsells",
8394
+ "anchor": "configpreview-upsells",
8395
+ "group": "payout-preview",
8396
+ "signature": "readonly upsells: readonly PassUpsell[]",
8397
+ "documented": true,
8398
+ "summary": "One per pass on sale here, not held, that would change this answer. Empty\nmeans show no upsell: no pass would add anything, or none is on sale to\nthis player (a minor, a store that is off, a pass declined twice today, or\nalready held).",
8399
+ "deprecated": null,
8400
+ "params": [],
8401
+ "returns": null,
8402
+ "throws": null,
8403
+ "example": null,
8404
+ "defaultValue": null,
8405
+ "guides": []
8406
+ },
8407
+ {
8408
+ "id": "PreviewError",
8409
+ "name": "PreviewError",
8410
+ "anchor": "previewerror",
8411
+ "group": "failures",
8412
+ "signature": "class PreviewError",
8413
+ "documented": true,
8414
+ "summary": "Why `gem.gameConfig.preview` could not work out an answer.",
8415
+ "deprecated": null,
8416
+ "params": [],
8417
+ "returns": null,
8418
+ "throws": null,
8419
+ "example": null,
8420
+ "defaultValue": null,
8421
+ "guides": []
8422
+ },
8423
+ {
8424
+ "id": "PreviewError.code",
8425
+ "name": "code",
8426
+ "anchor": "previewerror-code",
8427
+ "group": "failures",
8428
+ "signature": "readonly code: \"unsupported\" | \"not_found\" | \"invalid_argument\"",
8429
+ "documented": true,
8430
+ "summary": "`not_found` — the config has no recipe or progression by that id or\nslug; `invalid_argument` — the target is not `{ recipe }` or\n`{ progression, add? }` with a positive whole `add`; `unsupported` —\nnot in a browser game (a function or a server runs the recipe and reads\nthe receipt).",
8431
+ "deprecated": null,
8432
+ "params": [],
8433
+ "returns": null,
8434
+ "throws": null,
8435
+ "example": null,
8436
+ "defaultValue": null,
8437
+ "guides": []
8438
+ },
7817
8439
  {
7818
8440
  "id": "GemLookSpeed",
7819
8441
  "name": "GemLookSpeed",
@@ -8452,13 +9074,13 @@
8452
9074
  "guides": []
8453
9075
  },
8454
9076
  {
8455
- "id": "GamePass",
8456
- "name": "GamePass",
8457
- "anchor": "gamepass",
8458
- "group": "saves",
8459
- "signature": "interface GamePass",
9077
+ "id": "PlatformGrantSource",
9078
+ "name": "PlatformGrantSource",
9079
+ "anchor": "platformgrantsource",
9080
+ "group": "payout-preview",
9081
+ "signature": "type PlatformGrantSource = { source: \"pass\"; pass: string; amount: number } | { source: \"boost\"; amount: number }",
8460
9082
  "documented": true,
8461
- "summary": "One pass games may see, and whether this player holds it. Listed whether they\nhold it or not, and this is everything a game learns about it.",
9083
+ "summary": "Where the part of a payout above its base comes from.",
8462
9084
  "deprecated": null,
8463
9085
  "params": [],
8464
9086
  "returns": null,
@@ -8468,13 +9090,13 @@
8468
9090
  "guides": []
8469
9091
  },
8470
9092
  {
8471
- "id": "GamePass.pass",
8472
- "name": "pass",
8473
- "anchor": "gamepass-pass",
8474
- "group": "saves",
8475
- "signature": "readonly pass: string",
9093
+ "id": "PlatformGrantPreview",
9094
+ "name": "PlatformGrantPreview",
9095
+ "anchor": "platformgrantpreview",
9096
+ "group": "payout-preview",
9097
+ "signature": "interface PlatformGrantPreview",
8476
9098
  "documented": true,
8477
- "summary": "The pass's offering slug, such as `vip`. Stable and not localized: key on this.",
9099
+ "summary": "One platform payout — Coins, today — as the preview expects it or a receipt\nreports it, in one shape, so \"you will earn\" and \"you earned\" render with one\ncomponent.",
8478
9100
  "deprecated": null,
8479
9101
  "params": [],
8480
9102
  "returns": null,
@@ -8484,13 +9106,13 @@
8484
9106
  "guides": []
8485
9107
  },
8486
9108
  {
8487
- "id": "GamePass.held",
8488
- "name": "held",
8489
- "anchor": "gamepass-held",
8490
- "group": "saves",
8491
- "signature": "readonly held: boolean",
9109
+ "id": "PlatformGrantPreview.currencySlug",
9110
+ "name": "currencySlug",
9111
+ "anchor": "platformgrantpreview-currencyslug",
9112
+ "group": "payout-preview",
9113
+ "signature": "readonly currencySlug?: string",
8492
9114
  "documented": true,
8493
- "summary": "Whether the player holds it right now.",
9115
+ "summary": "The currency's slug, e.g. `ARCADE_TICKETS`. Absent when unknown.",
8494
9116
  "deprecated": null,
8495
9117
  "params": [],
8496
9118
  "returns": null,
@@ -8500,13 +9122,13 @@
8500
9122
  "guides": []
8501
9123
  },
8502
9124
  {
8503
- "id": "GamePass.endsAt",
8504
- "name": "endsAt",
8505
- "anchor": "gamepass-endsat",
8506
- "group": "saves",
8507
- "signature": "readonly endsAt: string | null",
9125
+ "id": "PlatformGrantPreview.currencyId",
9126
+ "name": "currencyId",
9127
+ "anchor": "platformgrantpreview-currencyid",
9128
+ "group": "payout-preview",
9129
+ "signature": "readonly currencyId?: string",
8508
9130
  "documented": true,
8509
- "summary": "When the player stops holding it, RFC 3339 in UTC, grace and any queued\nperiod included. `null` when it is not held, and when it is held with no\nend, so read [GamePass.held](api-reference-game.md#gamepass-held) first: `null` alone never means ended.",
9131
+ "summary": "The currency's id, when it is known.",
8510
9132
  "deprecated": null,
8511
9133
  "params": [],
8512
9134
  "returns": null,
@@ -8515,6 +9137,109 @@
8515
9137
  "defaultValue": null,
8516
9138
  "guides": []
8517
9139
  },
9140
+ {
9141
+ "id": "PlatformGrantPreview.base",
9142
+ "name": "base",
9143
+ "anchor": "platformgrantpreview-base",
9144
+ "group": "payout-preview",
9145
+ "signature": "readonly base: number",
9146
+ "documented": true,
9147
+ "summary": "What a player with no pass and no Boost gets.",
9148
+ "deprecated": null,
9149
+ "params": [],
9150
+ "returns": null,
9151
+ "throws": null,
9152
+ "example": null,
9153
+ "defaultValue": null,
9154
+ "guides": []
9155
+ },
9156
+ {
9157
+ "id": "PlatformGrantPreview.total",
9158
+ "name": "total",
9159
+ "anchor": "platformgrantpreview-total",
9160
+ "group": "payout-preview",
9161
+ "signature": "readonly total: number",
9162
+ "documented": true,
9163
+ "summary": "What this player gets: `base` plus `breakdown`.",
9164
+ "deprecated": null,
9165
+ "params": [],
9166
+ "returns": null,
9167
+ "throws": null,
9168
+ "example": null,
9169
+ "defaultValue": null,
9170
+ "guides": []
9171
+ },
9172
+ {
9173
+ "id": "PlatformGrantPreview.breakdown",
9174
+ "name": "breakdown",
9175
+ "anchor": "platformgrantpreview-breakdown",
9176
+ "group": "payout-preview",
9177
+ "signature": "readonly breakdown: readonly PlatformGrantSource[]",
9178
+ "documented": true,
9179
+ "summary": "The part above `base`, and what it is from. Empty when `total` is `base`.",
9180
+ "deprecated": null,
9181
+ "params": [],
9182
+ "returns": null,
9183
+ "throws": null,
9184
+ "example": null,
9185
+ "defaultValue": null,
9186
+ "guides": []
9187
+ },
9188
+ {
9189
+ "id": "PlatformGrantPreview.ifComplete",
9190
+ "name": "ifComplete",
9191
+ "anchor": "platformgrantpreview-ifcomplete",
9192
+ "group": "payout-preview",
9193
+ "signature": "readonly ifComplete?: true",
9194
+ "documented": true,
9195
+ "summary": "`true` for an `on_complete` payout in a preview: it pays only if this\nclaim completes the progression. Absent otherwise, and on a receipt.",
9196
+ "deprecated": null,
9197
+ "params": [],
9198
+ "returns": null,
9199
+ "throws": null,
9200
+ "example": null,
9201
+ "defaultValue": null,
9202
+ "guides": []
9203
+ },
9204
+ {
9205
+ "id": "EarningsApi",
9206
+ "name": "EarningsApi",
9207
+ "anchor": "earningsapi",
9208
+ "group": "payout-preview",
9209
+ "signature": "interface EarningsApi",
9210
+ "documented": true,
9211
+ "summary": "`gem.earnings`: what a payout was, in the shape `gem.gameConfig.preview`\nsays it would be.",
9212
+ "deprecated": null,
9213
+ "params": [],
9214
+ "returns": null,
9215
+ "throws": null,
9216
+ "example": null,
9217
+ "defaultValue": null,
9218
+ "guides": []
9219
+ },
9220
+ {
9221
+ "id": "EarningsApi.fromReceipt",
9222
+ "name": "fromReceipt",
9223
+ "anchor": "earningsapi-fromreceipt",
9224
+ "group": "payout-preview",
9225
+ "signature": "fromReceipt(change: unknown): Promise<PlatformGrantPreview | null>",
9226
+ "documented": true,
9227
+ "summary": "Turn one `platform_grant` change from an order's receipt into a\n[PlatformGrantPreview](api-reference-game.md#platformgrantpreview): `base` is what a player with no pass and no\nBoost would have got under the same limits, `total` what this player got,\nand `breakdown` the pass's or the Boost's part, even when a limit trimmed\nthe payout. Draw \"you earned\" with the component that drew \"you will\nearn\". Resolves `null` for any other kind of change.\n\n**This and `preview` are the only way to show a platform payout.** Never\nmultiply by `gem.player.boost`, and never copy a config amount into your\ngame.",
9228
+ "deprecated": null,
9229
+ "params": [
9230
+ {
9231
+ "name": "change",
9232
+ "type": "unknown",
9233
+ "optional": false,
9234
+ "summary": null
9235
+ }
9236
+ ],
9237
+ "returns": "Always resolves; never rejects.",
9238
+ "throws": null,
9239
+ "example": null,
9240
+ "defaultValue": null,
9241
+ "guides": []
9242
+ },
8518
9243
  {
8519
9244
  "id": "PassesEvent",
8520
9245
  "name": "PassesEvent",
@@ -11989,6 +12714,22 @@
11989
12714
  }
11990
12715
  ]
11991
12716
  },
12717
+ {
12718
+ "id": "GemPlayer.isGuest",
12719
+ "name": "isGuest",
12720
+ "anchor": "gemplayer-isguest",
12721
+ "group": "player",
12722
+ "signature": "readonly isGuest: boolean",
12723
+ "documented": true,
12724
+ "summary": "Whether the Arcade says this player is a **guest it can sign up here**.\n\n`true` only when the Arcade said so. `false` for a player with a full\naccount, on a surface that offers no sign-up, and when the Arcade did not\nsay — an older Arcade — so `false` is never proof of an account. That\nmakes it the test for a sign-up gift: offer one only while this is\n`true`, and the gift can never reach a player who already has an account.\n\nIt changes when a guest signs up in place: `gem.player` is pushed again,\nand `onPlayer` hears it.\n\n```js\nconst gem = createGem({ onPlayer: (player) => (signUpGift.hidden = !player.isGuest) });\n```\n\nFor drawing only, like everything on `gem.player`: what a sign-up earns\nis granted by your function or server, which reads the account itself.",
12725
+ "deprecated": null,
12726
+ "params": [],
12727
+ "returns": null,
12728
+ "throws": null,
12729
+ "example": null,
12730
+ "defaultValue": null,
12731
+ "guides": []
12732
+ },
11992
12733
  {
11993
12734
  "id": "GemPlayer.playerId",
11994
12735
  "name": "playerId",
@@ -13371,6 +14112,22 @@
13371
14112
  "defaultValue": null,
13372
14113
  "guides": []
13373
14114
  },
14115
+ {
14116
+ "id": "Gem.earnings",
14117
+ "name": "earnings",
14118
+ "anchor": "gem-earnings",
14119
+ "group": "start",
14120
+ "signature": "readonly earnings: EarningsApi",
14121
+ "documented": true,
14122
+ "summary": "What a platform payout was: `gem.earnings.fromReceipt(change)` turns a\nreceipt's `platform_grant` change into the shape `gem.gameConfig.preview`\ngives, so \"you will earn\" and \"you earned\" draw with one component.",
14123
+ "deprecated": null,
14124
+ "params": [],
14125
+ "returns": null,
14126
+ "throws": null,
14127
+ "example": null,
14128
+ "defaultValue": null,
14129
+ "guides": []
14130
+ },
13374
14131
  {
13375
14132
  "id": "Gem.purchases",
13376
14133
  "name": "purchases",
@@ -16616,6 +17373,86 @@
16616
17373
  "defaultValue": null,
16617
17374
  "guides": []
16618
17375
  },
17376
+ {
17377
+ "id": "GamePass",
17378
+ "name": "GamePass",
17379
+ "anchor": "gamepass",
17380
+ "group": "saves",
17381
+ "signature": "interface GamePass",
17382
+ "documented": true,
17383
+ "summary": "One pass games may see, and whether this player holds it. Listed whether they\nhold it or not, and this is everything a game learns about it.",
17384
+ "deprecated": null,
17385
+ "params": [],
17386
+ "returns": null,
17387
+ "throws": null,
17388
+ "example": null,
17389
+ "defaultValue": null,
17390
+ "guides": []
17391
+ },
17392
+ {
17393
+ "id": "GamePass.pass",
17394
+ "name": "pass",
17395
+ "anchor": "gamepass-pass",
17396
+ "group": "saves",
17397
+ "signature": "readonly pass: string",
17398
+ "documented": true,
17399
+ "summary": "The pass's offering slug, such as `vip`. Stable and not localized: key on this.",
17400
+ "deprecated": null,
17401
+ "params": [],
17402
+ "returns": null,
17403
+ "throws": null,
17404
+ "example": null,
17405
+ "defaultValue": null,
17406
+ "guides": []
17407
+ },
17408
+ {
17409
+ "id": "GamePass.held",
17410
+ "name": "held",
17411
+ "anchor": "gamepass-held",
17412
+ "group": "saves",
17413
+ "signature": "readonly held: boolean",
17414
+ "documented": true,
17415
+ "summary": "Whether the player holds it right now.",
17416
+ "deprecated": null,
17417
+ "params": [],
17418
+ "returns": null,
17419
+ "throws": null,
17420
+ "example": null,
17421
+ "defaultValue": null,
17422
+ "guides": []
17423
+ },
17424
+ {
17425
+ "id": "GamePass.daysLeft",
17426
+ "name": "daysLeft",
17427
+ "anchor": "gamepass-daysleft",
17428
+ "group": "saves",
17429
+ "signature": "readonly daysLeft: number",
17430
+ "documented": true,
17431
+ "summary": "Whole days the player keeps it, rounded up so a pass with any time left\nreads at least `1`, and **capped at 7**: `7` means \"7 or more\", and is\nalso what a pass held with no end reads. `0` when it is not held. Show\n`7` as \"7+ days left\", never as an exact end.",
17432
+ "deprecated": null,
17433
+ "params": [],
17434
+ "returns": null,
17435
+ "throws": null,
17436
+ "example": null,
17437
+ "defaultValue": null,
17438
+ "guides": []
17439
+ },
17440
+ {
17441
+ "id": "GamePass.endsAt",
17442
+ "name": "endsAt",
17443
+ "anchor": "gamepass-endsat",
17444
+ "group": "saves",
17445
+ "signature": "readonly endsAt: string | null",
17446
+ "documented": true,
17447
+ "summary": "When the player stops holding it, RFC 3339 in UTC, grace and any queued\nperiod included. `null` when it is not held, and when it is held with no\nend, so read [GamePass.held](api-reference-game.md#gamepass-held) first: `null` alone never means ended.",
17448
+ "deprecated": "Use [GamePass.daysLeft](api-reference-game.md#gamepass-daysleft). An exact instant is a value two\ngames could match to link one player's identities, so the platform will\nstop sending it; it reads `null` then. See\n[the deprecation policy](../deprecation-policy.md).",
17449
+ "params": [],
17450
+ "returns": null,
17451
+ "throws": null,
17452
+ "example": null,
17453
+ "defaultValue": null,
17454
+ "guides": []
17455
+ },
16619
17456
  {
16620
17457
  "id": "PlatformCurrencyAccess",
16621
17458
  "name": "PlatformCurrencyAccess",
@@ -17971,6 +18808,52 @@
17971
18808
  "defaultValue": null,
17972
18809
  "guides": []
17973
18810
  },
18811
+ {
18812
+ "id": "GemServerHost.passes",
18813
+ "name": "passes",
18814
+ "anchor": "gemserverhost-passes",
18815
+ "group": "match",
18816
+ "signature": "passes(playerId: string): Promise<GemServerHostRead>",
18817
+ "documented": true,
18818
+ "summary": "Ask the platform half which game-visible passes a player in this match\nholds: the match pass read, made with the allocation's own proof.\n\nMAY BE ABSENT on older platform images, exactly as `endMatch` may, and a\nlisten host never has it. `gem.match.playerPasses(playerId)` is this call;\nyou rarely call it yourself.\n\nResolves with what the platform answered — see [GemServerHostRead](api-reference-game.md#gemserverhostread)\n— and rejects only when the platform half could not be asked at all.",
18819
+ "deprecated": null,
18820
+ "params": [
18821
+ {
18822
+ "name": "playerId",
18823
+ "type": "string",
18824
+ "optional": false,
18825
+ "summary": null
18826
+ }
18827
+ ],
18828
+ "returns": null,
18829
+ "throws": null,
18830
+ "example": null,
18831
+ "defaultValue": null,
18832
+ "guides": []
18833
+ },
18834
+ {
18835
+ "id": "GemServerHost.boost",
18836
+ "name": "boost",
18837
+ "anchor": "gemserverhost-boost",
18838
+ "group": "match",
18839
+ "signature": "boost(playerId: string): Promise<GemServerHostRead>",
18840
+ "documented": true,
18841
+ "summary": "Ask the platform half for a player's Boost, for display: the match boost\nread, made with the allocation's own proof.\n\nMAY BE ABSENT on older platform images and on a listen host.\n`gem.match.playerBoost(playerId)` is this call; you rarely call it\nyourself. Resolves and rejects as [GemServerHost.passes](api-reference-game.md#gemserverhost-passes) does.",
18842
+ "deprecated": null,
18843
+ "params": [
18844
+ {
18845
+ "name": "playerId",
18846
+ "type": "string",
18847
+ "optional": false,
18848
+ "summary": null
18849
+ }
18850
+ ],
18851
+ "returns": null,
18852
+ "throws": null,
18853
+ "example": null,
18854
+ "defaultValue": null,
18855
+ "guides": []
18856
+ },
17974
18857
  {
17975
18858
  "id": "GemServerHost.log",
17976
18859
  "name": "log",
@@ -18017,6 +18900,54 @@
18017
18900
  "defaultValue": null,
18018
18901
  "guides": []
18019
18902
  },
18903
+ {
18904
+ "id": "GemServerHostRead",
18905
+ "name": "GemServerHostRead",
18906
+ "anchor": "gemserverhostread",
18907
+ "group": "match",
18908
+ "signature": "interface GemServerHostRead",
18909
+ "documented": true,
18910
+ "summary": "What the platform half answered to a player read it made for this match:\nthe HTTP status and the decoded JSON body, untouched.\n\nThe runtime adds only the allocation's proof, which never enters the bundle\nprocess; the SDK reads the body and turns a refusal into a `GemApiError`, so\na runtime never has to know what the route's answer means.",
18911
+ "deprecated": null,
18912
+ "params": [],
18913
+ "returns": null,
18914
+ "throws": null,
18915
+ "example": null,
18916
+ "defaultValue": null,
18917
+ "guides": []
18918
+ },
18919
+ {
18920
+ "id": "GemServerHostRead.status",
18921
+ "name": "status",
18922
+ "anchor": "gemserverhostread-status",
18923
+ "group": "match",
18924
+ "signature": "readonly status: number",
18925
+ "documented": true,
18926
+ "summary": "The HTTP status the platform answered with.",
18927
+ "deprecated": null,
18928
+ "params": [],
18929
+ "returns": null,
18930
+ "throws": null,
18931
+ "example": null,
18932
+ "defaultValue": null,
18933
+ "guides": []
18934
+ },
18935
+ {
18936
+ "id": "GemServerHostRead.body",
18937
+ "name": "body",
18938
+ "anchor": "gemserverhostread-body",
18939
+ "group": "match",
18940
+ "signature": "readonly body: unknown",
18941
+ "documented": true,
18942
+ "summary": "The decoded JSON body, or `null` when there was none.",
18943
+ "deprecated": null,
18944
+ "params": [],
18945
+ "returns": null,
18946
+ "throws": null,
18947
+ "example": null,
18948
+ "defaultValue": null,
18949
+ "guides": []
18950
+ },
18020
18951
  {
18021
18952
  "id": "GemServerBundle",
18022
18953
  "name": "GemServerBundle",
@@ -27005,6 +27936,173 @@
27005
27936
  "defaultValue": null,
27006
27937
  "guides": []
27007
27938
  },
27939
+ {
27940
+ "id": "PlatformGrantSource",
27941
+ "name": "PlatformGrantSource",
27942
+ "anchor": "platformgrantsource",
27943
+ "group": "match",
27944
+ "signature": "type PlatformGrantSource = { source: \"pass\"; pass: string; amount: number } | { source: \"boost\"; amount: number }",
27945
+ "documented": true,
27946
+ "summary": "Where the part of a payout above its base comes from.",
27947
+ "deprecated": null,
27948
+ "params": [],
27949
+ "returns": null,
27950
+ "throws": null,
27951
+ "example": null,
27952
+ "defaultValue": null,
27953
+ "guides": []
27954
+ },
27955
+ {
27956
+ "id": "PlatformGrantPreview",
27957
+ "name": "PlatformGrantPreview",
27958
+ "anchor": "platformgrantpreview",
27959
+ "group": "match",
27960
+ "signature": "interface PlatformGrantPreview",
27961
+ "documented": true,
27962
+ "summary": "One platform payout — Coins, today — as the preview expects it or a receipt\nreports it, in one shape, so \"you will earn\" and \"you earned\" render with one\ncomponent.",
27963
+ "deprecated": null,
27964
+ "params": [],
27965
+ "returns": null,
27966
+ "throws": null,
27967
+ "example": null,
27968
+ "defaultValue": null,
27969
+ "guides": []
27970
+ },
27971
+ {
27972
+ "id": "PlatformGrantPreview.currencySlug",
27973
+ "name": "currencySlug",
27974
+ "anchor": "platformgrantpreview-currencyslug",
27975
+ "group": "match",
27976
+ "signature": "readonly currencySlug?: string",
27977
+ "documented": true,
27978
+ "summary": "The currency's slug, e.g. `ARCADE_TICKETS`. Absent when unknown.",
27979
+ "deprecated": null,
27980
+ "params": [],
27981
+ "returns": null,
27982
+ "throws": null,
27983
+ "example": null,
27984
+ "defaultValue": null,
27985
+ "guides": []
27986
+ },
27987
+ {
27988
+ "id": "PlatformGrantPreview.currencyId",
27989
+ "name": "currencyId",
27990
+ "anchor": "platformgrantpreview-currencyid",
27991
+ "group": "match",
27992
+ "signature": "readonly currencyId?: string",
27993
+ "documented": true,
27994
+ "summary": "The currency's id, when it is known.",
27995
+ "deprecated": null,
27996
+ "params": [],
27997
+ "returns": null,
27998
+ "throws": null,
27999
+ "example": null,
28000
+ "defaultValue": null,
28001
+ "guides": []
28002
+ },
28003
+ {
28004
+ "id": "PlatformGrantPreview.base",
28005
+ "name": "base",
28006
+ "anchor": "platformgrantpreview-base",
28007
+ "group": "match",
28008
+ "signature": "readonly base: number",
28009
+ "documented": true,
28010
+ "summary": "What a player with no pass and no Boost gets.",
28011
+ "deprecated": null,
28012
+ "params": [],
28013
+ "returns": null,
28014
+ "throws": null,
28015
+ "example": null,
28016
+ "defaultValue": null,
28017
+ "guides": []
28018
+ },
28019
+ {
28020
+ "id": "PlatformGrantPreview.total",
28021
+ "name": "total",
28022
+ "anchor": "platformgrantpreview-total",
28023
+ "group": "match",
28024
+ "signature": "readonly total: number",
28025
+ "documented": true,
28026
+ "summary": "What this player gets: `base` plus `breakdown`.",
28027
+ "deprecated": null,
28028
+ "params": [],
28029
+ "returns": null,
28030
+ "throws": null,
28031
+ "example": null,
28032
+ "defaultValue": null,
28033
+ "guides": []
28034
+ },
28035
+ {
28036
+ "id": "PlatformGrantPreview.breakdown",
28037
+ "name": "breakdown",
28038
+ "anchor": "platformgrantpreview-breakdown",
28039
+ "group": "match",
28040
+ "signature": "readonly breakdown: readonly PlatformGrantSource[]",
28041
+ "documented": true,
28042
+ "summary": "The part above `base`, and what it is from. Empty when `total` is `base`.",
28043
+ "deprecated": null,
28044
+ "params": [],
28045
+ "returns": null,
28046
+ "throws": null,
28047
+ "example": null,
28048
+ "defaultValue": null,
28049
+ "guides": []
28050
+ },
28051
+ {
28052
+ "id": "PlatformGrantPreview.ifComplete",
28053
+ "name": "ifComplete",
28054
+ "anchor": "platformgrantpreview-ifcomplete",
28055
+ "group": "match",
28056
+ "signature": "readonly ifComplete?: true",
28057
+ "documented": true,
28058
+ "summary": "`true` for an `on_complete` payout in a preview: it pays only if this\nclaim completes the progression. Absent otherwise, and on a receipt.",
28059
+ "deprecated": null,
28060
+ "params": [],
28061
+ "returns": null,
28062
+ "throws": null,
28063
+ "example": null,
28064
+ "defaultValue": null,
28065
+ "guides": []
28066
+ },
28067
+ {
28068
+ "id": "EarningsApi",
28069
+ "name": "EarningsApi",
28070
+ "anchor": "earningsapi",
28071
+ "group": "match",
28072
+ "signature": "interface EarningsApi",
28073
+ "documented": true,
28074
+ "summary": "`gem.earnings`: what a payout was, in the shape `gem.gameConfig.preview`\nsays it would be.",
28075
+ "deprecated": null,
28076
+ "params": [],
28077
+ "returns": null,
28078
+ "throws": null,
28079
+ "example": null,
28080
+ "defaultValue": null,
28081
+ "guides": []
28082
+ },
28083
+ {
28084
+ "id": "EarningsApi.fromReceipt",
28085
+ "name": "fromReceipt",
28086
+ "anchor": "earningsapi-fromreceipt",
28087
+ "group": "match",
28088
+ "signature": "fromReceipt(change: unknown): Promise<PlatformGrantPreview | null>",
28089
+ "documented": true,
28090
+ "summary": "Turn one `platform_grant` change from an order's receipt into a\n[PlatformGrantPreview](api-reference-server.md#platformgrantpreview): `base` is what a player with no pass and no\nBoost would have got under the same limits, `total` what this player got,\nand `breakdown` the pass's or the Boost's part, even when a limit trimmed\nthe payout. Draw \"you earned\" with the component that drew \"you will\nearn\". Resolves `null` for any other kind of change.\n\n**This and `preview` are the only way to show a platform payout.** Never\nmultiply by `gem.player.boost`, and never copy a config amount into your\ngame.",
28091
+ "deprecated": null,
28092
+ "params": [
28093
+ {
28094
+ "name": "change",
28095
+ "type": "unknown",
28096
+ "optional": false,
28097
+ "summary": null
28098
+ }
28099
+ ],
28100
+ "returns": "Always resolves; never rejects.",
28101
+ "throws": null,
28102
+ "example": null,
28103
+ "defaultValue": null,
28104
+ "guides": []
28105
+ },
27008
28106
  {
27009
28107
  "id": "FunctionInvokeOptions",
27010
28108
  "name": "FunctionInvokeOptions",
@@ -27354,6 +28452,86 @@
27354
28452
  "defaultValue": null,
27355
28453
  "guides": []
27356
28454
  },
28455
+ {
28456
+ "id": "GamePass",
28457
+ "name": "GamePass",
28458
+ "anchor": "gamepass",
28459
+ "group": "match",
28460
+ "signature": "interface GamePass",
28461
+ "documented": true,
28462
+ "summary": "One pass games may see, and whether this player holds it. Listed whether they\nhold it or not, and this is everything a game learns about it.",
28463
+ "deprecated": null,
28464
+ "params": [],
28465
+ "returns": null,
28466
+ "throws": null,
28467
+ "example": null,
28468
+ "defaultValue": null,
28469
+ "guides": []
28470
+ },
28471
+ {
28472
+ "id": "GamePass.pass",
28473
+ "name": "pass",
28474
+ "anchor": "gamepass-pass",
28475
+ "group": "match",
28476
+ "signature": "readonly pass: string",
28477
+ "documented": true,
28478
+ "summary": "The pass's offering slug, such as `vip`. Stable and not localized: key on this.",
28479
+ "deprecated": null,
28480
+ "params": [],
28481
+ "returns": null,
28482
+ "throws": null,
28483
+ "example": null,
28484
+ "defaultValue": null,
28485
+ "guides": []
28486
+ },
28487
+ {
28488
+ "id": "GamePass.held",
28489
+ "name": "held",
28490
+ "anchor": "gamepass-held",
28491
+ "group": "match",
28492
+ "signature": "readonly held: boolean",
28493
+ "documented": true,
28494
+ "summary": "Whether the player holds it right now.",
28495
+ "deprecated": null,
28496
+ "params": [],
28497
+ "returns": null,
28498
+ "throws": null,
28499
+ "example": null,
28500
+ "defaultValue": null,
28501
+ "guides": []
28502
+ },
28503
+ {
28504
+ "id": "GamePass.daysLeft",
28505
+ "name": "daysLeft",
28506
+ "anchor": "gamepass-daysleft",
28507
+ "group": "match",
28508
+ "signature": "readonly daysLeft: number",
28509
+ "documented": true,
28510
+ "summary": "Whole days the player keeps it, rounded up so a pass with any time left\nreads at least `1`, and **capped at 7**: `7` means \"7 or more\", and is\nalso what a pass held with no end reads. `0` when it is not held. Show\n`7` as \"7+ days left\", never as an exact end.",
28511
+ "deprecated": null,
28512
+ "params": [],
28513
+ "returns": null,
28514
+ "throws": null,
28515
+ "example": null,
28516
+ "defaultValue": null,
28517
+ "guides": []
28518
+ },
28519
+ {
28520
+ "id": "GamePass.endsAt",
28521
+ "name": "endsAt",
28522
+ "anchor": "gamepass-endsat",
28523
+ "group": "match",
28524
+ "signature": "readonly endsAt: string | null",
28525
+ "documented": true,
28526
+ "summary": "When the player stops holding it, RFC 3339 in UTC, grace and any queued\nperiod included. `null` when it is not held, and when it is held with no\nend, so read [GamePass.held](api-reference-server.md#gamepass-held) first: `null` alone never means ended.",
28527
+ "deprecated": "Use [GamePass.daysLeft](api-reference-server.md#gamepass-daysleft). An exact instant is a value two\ngames could match to link one player's identities, so the platform will\nstop sending it; it reads `null` then. See\n[the deprecation policy](../deprecation-policy.md).",
28528
+ "params": [],
28529
+ "returns": null,
28530
+ "throws": null,
28531
+ "example": null,
28532
+ "defaultValue": null,
28533
+ "guides": []
28534
+ },
27357
28535
  {
27358
28536
  "id": "Clock",
27359
28537
  "name": "Clock",
@@ -27938,6 +29116,22 @@
27938
29116
  "defaultValue": null,
27939
29117
  "guides": []
27940
29118
  },
29119
+ {
29120
+ "id": "GemServerHostRead",
29121
+ "name": "GemServerHostRead",
29122
+ "anchor": "gemserverhostread",
29123
+ "group": "host",
29124
+ "signature": "interface GemServerHostRead",
29125
+ "documented": true,
29126
+ "summary": "What the platform half answered to a player read it made for this match:\nthe HTTP status and the decoded JSON body, untouched.\n\nThe runtime adds only the allocation's proof, which never enters the bundle\nprocess; the SDK reads the body and turns a refusal into a `GemApiError`, so\na runtime never has to know what the route's answer means.",
29127
+ "deprecated": null,
29128
+ "params": [],
29129
+ "returns": null,
29130
+ "throws": null,
29131
+ "example": null,
29132
+ "defaultValue": null,
29133
+ "guides": []
29134
+ },
27941
29135
  {
27942
29136
  "id": "SendOptions",
27943
29137
  "name": "SendOptions",
@@ -28253,6 +29447,118 @@
28253
29447
  "defaultValue": null,
28254
29448
  "guides": []
28255
29449
  },
29450
+ {
29451
+ "id": "MatchBoostMultiplier",
29452
+ "name": "MatchBoostMultiplier",
29453
+ "anchor": "matchboostmultiplier",
29454
+ "group": "match",
29455
+ "signature": "interface MatchBoostMultiplier",
29456
+ "documented": true,
29457
+ "summary": "One platform currency's Boost, as a dedicated server reads it.",
29458
+ "deprecated": null,
29459
+ "params": [],
29460
+ "returns": null,
29461
+ "throws": null,
29462
+ "example": null,
29463
+ "defaultValue": null,
29464
+ "guides": []
29465
+ },
29466
+ {
29467
+ "id": "MatchBoostMultiplier.currencySlug",
29468
+ "name": "currencySlug",
29469
+ "anchor": "matchboostmultiplier-currencyslug",
29470
+ "group": "match",
29471
+ "signature": "readonly currencySlug: string",
29472
+ "documented": true,
29473
+ "summary": "The currency's slug, e.g. `GEMS` or `ARCADE_TICKETS`.",
29474
+ "deprecated": null,
29475
+ "params": [],
29476
+ "returns": null,
29477
+ "throws": null,
29478
+ "example": null,
29479
+ "defaultValue": null,
29480
+ "guides": []
29481
+ },
29482
+ {
29483
+ "id": "MatchBoostMultiplier.multiplier",
29484
+ "name": "multiplier",
29485
+ "anchor": "matchboostmultiplier-multiplier",
29486
+ "group": "match",
29487
+ "signature": "readonly multiplier: number",
29488
+ "documented": true,
29489
+ "summary": "How much faster the player earns it right now: `2` is 2x, `1` is not boosted.",
29490
+ "deprecated": null,
29491
+ "params": [],
29492
+ "returns": null,
29493
+ "throws": null,
29494
+ "example": null,
29495
+ "defaultValue": null,
29496
+ "guides": []
29497
+ },
29498
+ {
29499
+ "id": "MatchPlayerBoost",
29500
+ "name": "MatchPlayerBoost",
29501
+ "anchor": "matchplayerboost",
29502
+ "group": "match",
29503
+ "signature": "interface MatchPlayerBoost",
29504
+ "documented": true,
29505
+ "summary": "A player's Boost, as a dedicated server reads it — **for display only**,\nexactly as `gem.player.boost` is in the browser.\n\n⚠️ **Show it; never gate on it.** Nothing a player can do, reach or win may\ndepend on whether they are boosted. To gate on something a player paid for,\ngate on a pass: `gem.match.playerPasses`.",
29506
+ "deprecated": null,
29507
+ "params": [],
29508
+ "returns": null,
29509
+ "throws": null,
29510
+ "example": null,
29511
+ "defaultValue": null,
29512
+ "guides": []
29513
+ },
29514
+ {
29515
+ "id": "MatchPlayerBoost.active",
29516
+ "name": "active",
29517
+ "anchor": "matchplayerboost-active",
29518
+ "group": "match",
29519
+ "signature": "readonly active: boolean",
29520
+ "documented": true,
29521
+ "summary": "Whether any currency earns faster than 1x right now.",
29522
+ "deprecated": null,
29523
+ "params": [],
29524
+ "returns": null,
29525
+ "throws": null,
29526
+ "example": null,
29527
+ "defaultValue": null,
29528
+ "guides": []
29529
+ },
29530
+ {
29531
+ "id": "MatchPlayerBoost.multipliers",
29532
+ "name": "multipliers",
29533
+ "anchor": "matchplayerboost-multipliers",
29534
+ "group": "match",
29535
+ "signature": "readonly multipliers: readonly MatchBoostMultiplier[]",
29536
+ "documented": true,
29537
+ "summary": "One entry per boostable platform currency, boosted or not.",
29538
+ "deprecated": null,
29539
+ "params": [],
29540
+ "returns": null,
29541
+ "throws": null,
29542
+ "example": null,
29543
+ "defaultValue": null,
29544
+ "guides": []
29545
+ },
29546
+ {
29547
+ "id": "MatchPlayerBoost.expiresInDays",
29548
+ "name": "expiresInDays",
29549
+ "anchor": "matchplayerboost-expiresindays",
29550
+ "group": "match",
29551
+ "signature": "readonly expiresInDays?: number",
29552
+ "documented": true,
29553
+ "summary": "Whole days until the soonest boosted currency drops back to 1x, rounded up\nand **capped at 7** (`7` means \"7 or more\"). Absent when nothing is boosted.",
29554
+ "deprecated": null,
29555
+ "params": [],
29556
+ "returns": null,
29557
+ "throws": null,
29558
+ "example": null,
29559
+ "defaultValue": null,
29560
+ "guides": []
29561
+ },
28256
29562
  {
28257
29563
  "id": "PlayerText",
28258
29564
  "name": "PlayerText",
@@ -28761,7 +30067,7 @@
28761
30067
  "name": "match",
28762
30068
  "anchor": "gemserver-match",
28763
30069
  "group": "api",
28764
- "signature": "readonly match: { end: null; setBackfill: null; reportPlayers: null }",
30070
+ "signature": "readonly match: { end: null; setBackfill: null; reportPlayers: null; playerPasses: null; playerBoost: null }",
28765
30071
  "documented": true,
28766
30072
  "summary": "Ending the match.\n\n`end()` is HOW A SERVER-AUTHORITATIVE GAME FINISHES. It tells the platform\nthe match is over; the platform then tells the runtime, and your server\nwinds down through its ordinary shutdown. You do not stop the process\nyourself.\n\nIt always means THIS match — the id is already known, so there is nothing to\npass and no way to end someone else's.\n\nThe platform half makes the request, not your process: it holds the proof\nthat this server is the one allocated to the match, which the platform\nrequires. On a platform image too old to offer that, `end()` throws rather\nthan sending a request without the proof.",
28767
30073
  "deprecated": null,
@@ -28853,6 +30159,80 @@
28853
30159
  "defaultValue": null,
28854
30160
  "guides": []
28855
30161
  },
30162
+ {
30163
+ "id": "GemServer.match.playerPasses",
30164
+ "name": "playerPasses",
30165
+ "anchor": "gemserver-match-playerpasses",
30166
+ "group": "api",
30167
+ "signature": "playerPasses(playerId: string, signal?: AbortSignal): Promise<readonly GamePass[]>",
30168
+ "documented": true,
30169
+ "summary": "Which game-visible passes a player in this match holds, in the shape\n`gem.me.passes.list()` gives in the browser: one entry per pass the\nplatform marked visible to games, held or not, with `held` and\n`daysLeft`.\n\n**This is what to gate on** — a VIP door, a VIP-only mode. `held` is\nexactly what a `requires: holds_pass` requirement on the same slug\ndecides. Read it when the player joins and again when you need it; it is\nnot pushed.",
30170
+ "deprecated": null,
30171
+ "params": [
30172
+ {
30173
+ "name": "playerId",
30174
+ "type": "string",
30175
+ "optional": false,
30176
+ "summary": "A player in THIS match. Anyone else is refused\n `not_found` (`player_not_in_match`) and nothing about them is read."
30177
+ },
30178
+ {
30179
+ "name": "signal",
30180
+ "type": "AbortSignal",
30181
+ "optional": true,
30182
+ "summary": "Not used — the platform half bounds its own request."
30183
+ }
30184
+ ],
30185
+ "returns": null,
30186
+ "throws": "When the platform image running this server predates the hook, or\n on a listen host (a player's browser hosting a P2P match), which holds no\n allocation proof. A `GemApiError` when the platform refuses, including\n `forbidden` (`client_permission_denied`) until the platform grants game\n servers the pass read.",
30187
+ "example": "```js\nconst vip = (await gem.match.playerPasses(playerId)).find((p) => p.pass === 'vip');\nif (vip?.held) openVipLounge(playerId);\n```",
30188
+ "defaultValue": null,
30189
+ "guides": []
30190
+ },
30191
+ {
30192
+ "id": "GemServer.match.playerBoost",
30193
+ "name": "playerBoost",
30194
+ "anchor": "gemserver-match-playerboost",
30195
+ "group": "api",
30196
+ "signature": "playerBoost(playerId: string, signal?: AbortSignal): Promise<MatchPlayerBoost>",
30197
+ "documented": true,
30198
+ "summary": "A player's Boost, **for display only** — \"2x Coins\" on a results screen.\nNever gate content, difficulty, matchmaking or power on it; gate on a pass\nwith `playerPasses`.",
30199
+ "deprecated": null,
30200
+ "params": [
30201
+ {
30202
+ "name": "playerId",
30203
+ "type": "string",
30204
+ "optional": false,
30205
+ "summary": "A player in THIS match."
30206
+ },
30207
+ {
30208
+ "name": "signal",
30209
+ "type": "AbortSignal",
30210
+ "optional": true,
30211
+ "summary": "Not used — the platform half bounds its own request."
30212
+ }
30213
+ ],
30214
+ "returns": null,
30215
+ "throws": "As `playerPasses` does.",
30216
+ "example": null,
30217
+ "defaultValue": null,
30218
+ "guides": []
30219
+ },
30220
+ {
30221
+ "id": "GemServer.earnings",
30222
+ "name": "earnings",
30223
+ "anchor": "gemserver-earnings",
30224
+ "group": "api",
30225
+ "signature": "readonly earnings: EarningsApi",
30226
+ "documented": true,
30227
+ "summary": "What a platform payout was: `earnings.fromReceipt(change)` turns a\n`platform_grant` change from a receipt into the shape the browser's payout\npreview gives — `base`, `total`, and the pass's or the Boost's part — to\nsend to the player whose screen draws it.\n\nA server has no read of the platform's currencies, so the answer carries\n`currencyId` and no `currencySlug`; the browser's `gem.earnings` fills the\nslug in.",
30228
+ "deprecated": null,
30229
+ "params": [],
30230
+ "returns": null,
30231
+ "throws": null,
30232
+ "example": null,
30233
+ "defaultValue": null,
30234
+ "guides": []
30235
+ },
28856
30236
  {
28857
30237
  "id": "GemServer.analytics",
28858
30238
  "name": "analytics",
@@ -29582,6 +30962,52 @@
29582
30962
  "defaultValue": null,
29583
30963
  "guides": []
29584
30964
  },
30965
+ {
30966
+ "id": "GemServerHost.passes",
30967
+ "name": "passes",
30968
+ "anchor": "gemserverhost-passes",
30969
+ "group": "host",
30970
+ "signature": "passes(playerId: string): Promise<GemServerHostRead>",
30971
+ "documented": true,
30972
+ "summary": "Ask the platform half which game-visible passes a player in this match\nholds: the match pass read, made with the allocation's own proof.\n\nMAY BE ABSENT on older platform images, exactly as `endMatch` may, and a\nlisten host never has it. `gem.match.playerPasses(playerId)` is this call;\nyou rarely call it yourself.\n\nResolves with what the platform answered — see [GemServerHostRead](api-reference-server.md#gemserverhostread)\n— and rejects only when the platform half could not be asked at all.",
30973
+ "deprecated": null,
30974
+ "params": [
30975
+ {
30976
+ "name": "playerId",
30977
+ "type": "string",
30978
+ "optional": false,
30979
+ "summary": null
30980
+ }
30981
+ ],
30982
+ "returns": null,
30983
+ "throws": null,
30984
+ "example": null,
30985
+ "defaultValue": null,
30986
+ "guides": []
30987
+ },
30988
+ {
30989
+ "id": "GemServerHost.boost",
30990
+ "name": "boost",
30991
+ "anchor": "gemserverhost-boost",
30992
+ "group": "host",
30993
+ "signature": "boost(playerId: string): Promise<GemServerHostRead>",
30994
+ "documented": true,
30995
+ "summary": "Ask the platform half for a player's Boost, for display: the match boost\nread, made with the allocation's own proof.\n\nMAY BE ABSENT on older platform images and on a listen host.\n`gem.match.playerBoost(playerId)` is this call; you rarely call it\nyourself. Resolves and rejects as [GemServerHost.passes](api-reference-server.md#gemserverhost-passes) does.",
30996
+ "deprecated": null,
30997
+ "params": [
30998
+ {
30999
+ "name": "playerId",
31000
+ "type": "string",
31001
+ "optional": false,
31002
+ "summary": null
31003
+ }
31004
+ ],
31005
+ "returns": null,
31006
+ "throws": null,
31007
+ "example": null,
31008
+ "defaultValue": null,
31009
+ "guides": []
31010
+ },
29585
31011
  {
29586
31012
  "id": "GemServerHost.log",
29587
31013
  "name": "log",
@@ -29628,6 +31054,38 @@
29628
31054
  "defaultValue": null,
29629
31055
  "guides": []
29630
31056
  },
31057
+ {
31058
+ "id": "GemServerHostRead.status",
31059
+ "name": "status",
31060
+ "anchor": "gemserverhostread-status",
31061
+ "group": "host",
31062
+ "signature": "readonly status: number",
31063
+ "documented": true,
31064
+ "summary": "The HTTP status the platform answered with.",
31065
+ "deprecated": null,
31066
+ "params": [],
31067
+ "returns": null,
31068
+ "throws": null,
31069
+ "example": null,
31070
+ "defaultValue": null,
31071
+ "guides": []
31072
+ },
31073
+ {
31074
+ "id": "GemServerHostRead.body",
31075
+ "name": "body",
31076
+ "anchor": "gemserverhostread-body",
31077
+ "group": "host",
31078
+ "signature": "readonly body: unknown",
31079
+ "documented": true,
31080
+ "summary": "The decoded JSON body, or `null` when there was none.",
31081
+ "deprecated": null,
31082
+ "params": [],
31083
+ "returns": null,
31084
+ "throws": null,
31085
+ "example": null,
31086
+ "defaultValue": null,
31087
+ "guides": []
31088
+ },
29631
31089
  {
29632
31090
  "id": "HostTokenSourceOptions.clock",
29633
31091
  "name": "clock",
@@ -30161,7 +31619,7 @@
30161
31619
  "name": "match",
30162
31620
  "anchor": "gemmatchserver-match",
30163
31621
  "group": "start",
30164
- "signature": "readonly match: { end: null; setBackfill: null; reportPlayers: null }",
31622
+ "signature": "readonly match: { end: null; setBackfill: null; reportPlayers: null; playerPasses: null; playerBoost: null }",
30165
31623
  "documented": true,
30166
31624
  "summary": "Ending the match.\n\n`end()` is HOW A SERVER-AUTHORITATIVE GAME FINISHES. It tells the platform\nthe match is over; the platform then tells the runtime, and your server\nwinds down through its ordinary shutdown. You do not stop the process\nyourself.\n\nIt always means THIS match — the id is already known, so there is nothing to\npass and no way to end someone else's.\n\nThe platform half makes the request, not your process: it holds the proof\nthat this server is the one allocated to the match, which the platform\nrequires. On a platform image too old to offer that, `end()` throws rather\nthan sending a request without the proof.",
30167
31625
  "deprecated": null,
@@ -30253,6 +31711,80 @@
30253
31711
  "defaultValue": null,
30254
31712
  "guides": []
30255
31713
  },
31714
+ {
31715
+ "id": "GemMatchServer.match.playerPasses",
31716
+ "name": "playerPasses",
31717
+ "anchor": "gemmatchserver-match-playerpasses",
31718
+ "group": "start",
31719
+ "signature": "playerPasses(playerId: string, signal?: AbortSignal): Promise<readonly GamePass[]>",
31720
+ "documented": true,
31721
+ "summary": "Which game-visible passes a player in this match holds, in the shape\n`gem.me.passes.list()` gives in the browser: one entry per pass the\nplatform marked visible to games, held or not, with `held` and\n`daysLeft`.\n\n**This is what to gate on** — a VIP door, a VIP-only mode. `held` is\nexactly what a `requires: holds_pass` requirement on the same slug\ndecides. Read it when the player joins and again when you need it; it is\nnot pushed.",
31722
+ "deprecated": null,
31723
+ "params": [
31724
+ {
31725
+ "name": "playerId",
31726
+ "type": "string",
31727
+ "optional": false,
31728
+ "summary": "A player in THIS match. Anyone else is refused\n `not_found` (`player_not_in_match`) and nothing about them is read."
31729
+ },
31730
+ {
31731
+ "name": "signal",
31732
+ "type": "AbortSignal",
31733
+ "optional": true,
31734
+ "summary": "Not used — the platform half bounds its own request."
31735
+ }
31736
+ ],
31737
+ "returns": null,
31738
+ "throws": "When the platform image running this server predates the hook, or\n on a listen host (a player's browser hosting a P2P match), which holds no\n allocation proof. A `GemApiError` when the platform refuses, including\n `forbidden` (`client_permission_denied`) until the platform grants game\n servers the pass read.",
31739
+ "example": "```js\nconst vip = (await gem.match.playerPasses(playerId)).find((p) => p.pass === 'vip');\nif (vip?.held) openVipLounge(playerId);\n```",
31740
+ "defaultValue": null,
31741
+ "guides": []
31742
+ },
31743
+ {
31744
+ "id": "GemMatchServer.match.playerBoost",
31745
+ "name": "playerBoost",
31746
+ "anchor": "gemmatchserver-match-playerboost",
31747
+ "group": "start",
31748
+ "signature": "playerBoost(playerId: string, signal?: AbortSignal): Promise<MatchPlayerBoost>",
31749
+ "documented": true,
31750
+ "summary": "A player's Boost, **for display only** — \"2x Coins\" on a results screen.\nNever gate content, difficulty, matchmaking or power on it; gate on a pass\nwith `playerPasses`.",
31751
+ "deprecated": null,
31752
+ "params": [
31753
+ {
31754
+ "name": "playerId",
31755
+ "type": "string",
31756
+ "optional": false,
31757
+ "summary": "A player in THIS match."
31758
+ },
31759
+ {
31760
+ "name": "signal",
31761
+ "type": "AbortSignal",
31762
+ "optional": true,
31763
+ "summary": "Not used — the platform half bounds its own request."
31764
+ }
31765
+ ],
31766
+ "returns": null,
31767
+ "throws": "As `playerPasses` does.",
31768
+ "example": null,
31769
+ "defaultValue": null,
31770
+ "guides": []
31771
+ },
31772
+ {
31773
+ "id": "GemMatchServer.earnings",
31774
+ "name": "earnings",
31775
+ "anchor": "gemmatchserver-earnings",
31776
+ "group": "start",
31777
+ "signature": "readonly earnings: EarningsApi",
31778
+ "documented": true,
31779
+ "summary": "What a platform payout was: `earnings.fromReceipt(change)` turns a\n`platform_grant` change from a receipt into the shape the browser's payout\npreview gives — `base`, `total`, and the pass's or the Boost's part — to\nsend to the player whose screen draws it.\n\nA server has no read of the platform's currencies, so the answer carries\n`currencyId` and no `currencySlug`; the browser's `gem.earnings` fills the\nslug in.",
31780
+ "deprecated": null,
31781
+ "params": [],
31782
+ "returns": null,
31783
+ "throws": null,
31784
+ "example": null,
31785
+ "defaultValue": null,
31786
+ "guides": []
31787
+ },
30256
31788
  {
30257
31789
  "id": "GemMatchServer.analytics",
30258
31790
  "name": "analytics",
@@ -31018,7 +32550,7 @@
31018
32550
  "group": "analytics",
31019
32551
  "signature": "experiment(key: string, count: number): Promise<number>",
31020
32552
  "documented": true,
31021
- "summary": "Which arm of one of your experiments this player gets — the same answer on\nevery call, every refresh and every device. Use it wherever you would have\npicked an arm with `Math.random()`, which deals a player a new arm on each\nvisit and makes the test unreadable.\n\nPass a COUNT and get an index, `0` to `count - 1`; pass a LIST of strings\nand get one of them. A string listed twice is twice as likely.\n\n```js\nconst tutorial = await gem.analytics.experiment('tutorial_length', ['short', 'long']);\nconst layout = await gem.analytics.experiment('shop_layout', 3); // 0, 1 or 2\n```\n\nThe selection is a hash of the key and the signed-in player's id — FNV-1a\n32-bit over `\"<key>:<playerId>\"`, then MurmurHash3's 32-bit finalizer, mod\nthe arm count; see\n`experimentSelection`, which computes the same answer for an id you\nhold. Hashing the key in is what keeps your experiments independent: the\nplayers in one experiment's first arm are not the same players in every\nother experiment's first arm.\n\n⚠️ **Keep the key and the arms fixed for the life of the experiment.** A new\ncount or list re-deals every player. To start over on purpose, use a new\nkey.\n\n⚠️ **NOTHING IS RECORDED FOR YOU.** The selection is not sent anywhere, and\nthe SDK does not add it to your events. To analyse an experiment, declare a\ndimension for it on the events you want to break down, with the arms as its\nvalues, named after the experiment, and pass the selection there yourself:\n`gem.analytics.track('tutorial_finished', { tutorial_length: tutorial })`.\nNever the platform's `variant` field, which belongs to store-page\nexperiments.\n\nWAITS FOR THE PLAYER. The id arrives with the first play token, so a call\nduring boot waits for it, and rejects as `Gem.awaitIdentity` does\nwhen none arrives — outside the Arcade, the Gem Editor or the dev host.\nThere is no random fallback: catch the rejection and play your control arm.\nA guest is selected by their guest id, which is stable for as long as their\nguest account lasts; once they sign in to an account they are that\naccount's player, and its selection applies.\n\nIn a serverless function it selects for the player who invoked it, with\nno wait, and rejects when a game server invoked it.\n\nWorks with analytics turned off: it reads no setting and sends nothing.",
32553
+ "summary": "Which arm of one of your experiments this player gets — the same answer on\nevery call, every refresh and every device. Use it wherever you would have\npicked an arm with `Math.random()`, which deals a player a new arm on each\nvisit and makes the test unreadable.\n\nPass a COUNT and get an index, `0` to `count - 1`; pass a LIST of strings\nand get one of them. A string listed twice is twice as likely.\n\n```js\nconst tutorial = await gem.analytics.experiment('tutorial_length', ['short', 'long']);\nconst layout = await gem.analytics.experiment('shop_layout', 3); // 0, 1 or 2\n```\n\nThe selection is a hash of the key and the signed-in player's id — FNV-1a\n32-bit over `\"<key>:<playerId>\"`, then MurmurHash3's 32-bit finalizer, mod\nthe arm count; see\n`experimentSelection`, which computes the same answer for an id you\nhold. Hashing the key in is what keeps your experiments independent: the\nplayers in one experiment's first arm are not the same players in every\nother experiment's first arm.\n\n⚠️ **Keep the key and the arms fixed for the life of the experiment.** A new\ncount or list re-deals every player. To start over on purpose, use a new\nkey.\n\n⚠️ **NOTHING IS RECORDED FOR YOU.** The selection is not sent anywhere, and\nthe SDK does not add it to your events. To analyse an experiment, declare a\ndimension for it on the events you want to break down, with the arms as its\nvalues, named after the experiment, and pass the selection there yourself:\n`gem.analytics.track('tutorial_finished', { tutorial_length: tutorial })`.\nNever the platform's `variant` field, which belongs to store-page\nexperiments. For the platform's results to show the test, also declare it\nunder `analytics_tests` in your game config (see docs/analytics.md).\n\nWAITS FOR THE PLAYER. The id arrives with the first play token, so a call\nduring boot waits for it, and rejects as `Gem.awaitIdentity` does\nwhen none arrives — outside the Arcade, the Gem Editor or the dev host.\nThere is no random fallback: catch the rejection and play your control arm.\nA guest is selected by their guest id, which is stable for as long as their\nguest account lasts; once they sign in to an account they are that\naccount's player, and its selection applies.\n\nIn a serverless function it selects for the player who invoked it, with\nno wait, and rejects when a game server invoked it.\n\nWorks with analytics turned off: it reads no setting and sends nothing.",
31022
32554
  "deprecated": null,
31023
32555
  "params": [
31024
32556
  {
@@ -32291,6 +33823,173 @@
32291
33823
  "defaultValue": null,
32292
33824
  "guides": []
32293
33825
  },
33826
+ {
33827
+ "id": "PlatformGrantSource",
33828
+ "name": "PlatformGrantSource",
33829
+ "anchor": "platformgrantsource",
33830
+ "group": "player",
33831
+ "signature": "type PlatformGrantSource = { source: \"pass\"; pass: string; amount: number } | { source: \"boost\"; amount: number }",
33832
+ "documented": true,
33833
+ "summary": "Where the part of a payout above its base comes from.",
33834
+ "deprecated": null,
33835
+ "params": [],
33836
+ "returns": null,
33837
+ "throws": null,
33838
+ "example": null,
33839
+ "defaultValue": null,
33840
+ "guides": []
33841
+ },
33842
+ {
33843
+ "id": "PlatformGrantPreview",
33844
+ "name": "PlatformGrantPreview",
33845
+ "anchor": "platformgrantpreview",
33846
+ "group": "player",
33847
+ "signature": "interface PlatformGrantPreview",
33848
+ "documented": true,
33849
+ "summary": "One platform payout — Coins, today — as the preview expects it or a receipt\nreports it, in one shape, so \"you will earn\" and \"you earned\" render with one\ncomponent.",
33850
+ "deprecated": null,
33851
+ "params": [],
33852
+ "returns": null,
33853
+ "throws": null,
33854
+ "example": null,
33855
+ "defaultValue": null,
33856
+ "guides": []
33857
+ },
33858
+ {
33859
+ "id": "PlatformGrantPreview.currencySlug",
33860
+ "name": "currencySlug",
33861
+ "anchor": "platformgrantpreview-currencyslug",
33862
+ "group": "player",
33863
+ "signature": "readonly currencySlug?: string",
33864
+ "documented": true,
33865
+ "summary": "The currency's slug, e.g. `ARCADE_TICKETS`. Absent when unknown.",
33866
+ "deprecated": null,
33867
+ "params": [],
33868
+ "returns": null,
33869
+ "throws": null,
33870
+ "example": null,
33871
+ "defaultValue": null,
33872
+ "guides": []
33873
+ },
33874
+ {
33875
+ "id": "PlatformGrantPreview.currencyId",
33876
+ "name": "currencyId",
33877
+ "anchor": "platformgrantpreview-currencyid",
33878
+ "group": "player",
33879
+ "signature": "readonly currencyId?: string",
33880
+ "documented": true,
33881
+ "summary": "The currency's id, when it is known.",
33882
+ "deprecated": null,
33883
+ "params": [],
33884
+ "returns": null,
33885
+ "throws": null,
33886
+ "example": null,
33887
+ "defaultValue": null,
33888
+ "guides": []
33889
+ },
33890
+ {
33891
+ "id": "PlatformGrantPreview.base",
33892
+ "name": "base",
33893
+ "anchor": "platformgrantpreview-base",
33894
+ "group": "player",
33895
+ "signature": "readonly base: number",
33896
+ "documented": true,
33897
+ "summary": "What a player with no pass and no Boost gets.",
33898
+ "deprecated": null,
33899
+ "params": [],
33900
+ "returns": null,
33901
+ "throws": null,
33902
+ "example": null,
33903
+ "defaultValue": null,
33904
+ "guides": []
33905
+ },
33906
+ {
33907
+ "id": "PlatformGrantPreview.total",
33908
+ "name": "total",
33909
+ "anchor": "platformgrantpreview-total",
33910
+ "group": "player",
33911
+ "signature": "readonly total: number",
33912
+ "documented": true,
33913
+ "summary": "What this player gets: `base` plus `breakdown`.",
33914
+ "deprecated": null,
33915
+ "params": [],
33916
+ "returns": null,
33917
+ "throws": null,
33918
+ "example": null,
33919
+ "defaultValue": null,
33920
+ "guides": []
33921
+ },
33922
+ {
33923
+ "id": "PlatformGrantPreview.breakdown",
33924
+ "name": "breakdown",
33925
+ "anchor": "platformgrantpreview-breakdown",
33926
+ "group": "player",
33927
+ "signature": "readonly breakdown: readonly PlatformGrantSource[]",
33928
+ "documented": true,
33929
+ "summary": "The part above `base`, and what it is from. Empty when `total` is `base`.",
33930
+ "deprecated": null,
33931
+ "params": [],
33932
+ "returns": null,
33933
+ "throws": null,
33934
+ "example": null,
33935
+ "defaultValue": null,
33936
+ "guides": []
33937
+ },
33938
+ {
33939
+ "id": "PlatformGrantPreview.ifComplete",
33940
+ "name": "ifComplete",
33941
+ "anchor": "platformgrantpreview-ifcomplete",
33942
+ "group": "player",
33943
+ "signature": "readonly ifComplete?: true",
33944
+ "documented": true,
33945
+ "summary": "`true` for an `on_complete` payout in a preview: it pays only if this\nclaim completes the progression. Absent otherwise, and on a receipt.",
33946
+ "deprecated": null,
33947
+ "params": [],
33948
+ "returns": null,
33949
+ "throws": null,
33950
+ "example": null,
33951
+ "defaultValue": null,
33952
+ "guides": []
33953
+ },
33954
+ {
33955
+ "id": "EarningsApi",
33956
+ "name": "EarningsApi",
33957
+ "anchor": "earningsapi",
33958
+ "group": "player",
33959
+ "signature": "interface EarningsApi",
33960
+ "documented": true,
33961
+ "summary": "`gem.earnings`: what a payout was, in the shape `gem.gameConfig.preview`\nsays it would be.",
33962
+ "deprecated": null,
33963
+ "params": [],
33964
+ "returns": null,
33965
+ "throws": null,
33966
+ "example": null,
33967
+ "defaultValue": null,
33968
+ "guides": []
33969
+ },
33970
+ {
33971
+ "id": "EarningsApi.fromReceipt",
33972
+ "name": "fromReceipt",
33973
+ "anchor": "earningsapi-fromreceipt",
33974
+ "group": "player",
33975
+ "signature": "fromReceipt(change: unknown): Promise<PlatformGrantPreview | null>",
33976
+ "documented": true,
33977
+ "summary": "Turn one `platform_grant` change from an order's receipt into a\n[PlatformGrantPreview](api-reference-function.md#platformgrantpreview): `base` is what a player with no pass and no\nBoost would have got under the same limits, `total` what this player got,\nand `breakdown` the pass's or the Boost's part, even when a limit trimmed\nthe payout. Draw \"you earned\" with the component that drew \"you will\nearn\". Resolves `null` for any other kind of change.\n\n**This and `preview` are the only way to show a platform payout.** Never\nmultiply by `gem.player.boost`, and never copy a config amount into your\ngame.",
33978
+ "deprecated": null,
33979
+ "params": [
33980
+ {
33981
+ "name": "change",
33982
+ "type": "unknown",
33983
+ "optional": false,
33984
+ "summary": null
33985
+ }
33986
+ ],
33987
+ "returns": "Always resolves; never rejects.",
33988
+ "throws": null,
33989
+ "example": null,
33990
+ "defaultValue": null,
33991
+ "guides": []
33992
+ },
32294
33993
  {
32295
33994
  "id": "CurrencyBalance",
32296
33995
  "name": "CurrencyBalance",
@@ -34331,70 +36030,6 @@
34331
36030
  "defaultValue": null,
34332
36031
  "guides": []
34333
36032
  },
34334
- {
34335
- "id": "GamePass",
34336
- "name": "GamePass",
34337
- "anchor": "gamepass",
34338
- "group": "player",
34339
- "signature": "interface GamePass",
34340
- "documented": true,
34341
- "summary": "One pass games may see, and whether this player holds it. Listed whether they\nhold it or not, and this is everything a game learns about it.",
34342
- "deprecated": null,
34343
- "params": [],
34344
- "returns": null,
34345
- "throws": null,
34346
- "example": null,
34347
- "defaultValue": null,
34348
- "guides": []
34349
- },
34350
- {
34351
- "id": "GamePass.pass",
34352
- "name": "pass",
34353
- "anchor": "gamepass-pass",
34354
- "group": "player",
34355
- "signature": "readonly pass: string",
34356
- "documented": true,
34357
- "summary": "The pass's offering slug, such as `vip`. Stable and not localized: key on this.",
34358
- "deprecated": null,
34359
- "params": [],
34360
- "returns": null,
34361
- "throws": null,
34362
- "example": null,
34363
- "defaultValue": null,
34364
- "guides": []
34365
- },
34366
- {
34367
- "id": "GamePass.held",
34368
- "name": "held",
34369
- "anchor": "gamepass-held",
34370
- "group": "player",
34371
- "signature": "readonly held: boolean",
34372
- "documented": true,
34373
- "summary": "Whether the player holds it right now.",
34374
- "deprecated": null,
34375
- "params": [],
34376
- "returns": null,
34377
- "throws": null,
34378
- "example": null,
34379
- "defaultValue": null,
34380
- "guides": []
34381
- },
34382
- {
34383
- "id": "GamePass.endsAt",
34384
- "name": "endsAt",
34385
- "anchor": "gamepass-endsat",
34386
- "group": "player",
34387
- "signature": "readonly endsAt: string | null",
34388
- "documented": true,
34389
- "summary": "When the player stops holding it, RFC 3339 in UTC, grace and any queued\nperiod included. `null` when it is not held, and when it is held with no\nend, so read [GamePass.held](api-reference-function.md#gamepass-held) first: `null` alone never means ended.",
34390
- "deprecated": null,
34391
- "params": [],
34392
- "returns": null,
34393
- "throws": null,
34394
- "example": null,
34395
- "defaultValue": null,
34396
- "guides": []
34397
- },
34398
36033
  {
34399
36034
  "id": "PassesEvent",
34400
36035
  "name": "PassesEvent",
@@ -38559,6 +40194,86 @@
38559
40194
  "defaultValue": null,
38560
40195
  "guides": []
38561
40196
  },
40197
+ {
40198
+ "id": "GamePass",
40199
+ "name": "GamePass",
40200
+ "anchor": "gamepass",
40201
+ "group": "player",
40202
+ "signature": "interface GamePass",
40203
+ "documented": true,
40204
+ "summary": "One pass games may see, and whether this player holds it. Listed whether they\nhold it or not, and this is everything a game learns about it.",
40205
+ "deprecated": null,
40206
+ "params": [],
40207
+ "returns": null,
40208
+ "throws": null,
40209
+ "example": null,
40210
+ "defaultValue": null,
40211
+ "guides": []
40212
+ },
40213
+ {
40214
+ "id": "GamePass.pass",
40215
+ "name": "pass",
40216
+ "anchor": "gamepass-pass",
40217
+ "group": "player",
40218
+ "signature": "readonly pass: string",
40219
+ "documented": true,
40220
+ "summary": "The pass's offering slug, such as `vip`. Stable and not localized: key on this.",
40221
+ "deprecated": null,
40222
+ "params": [],
40223
+ "returns": null,
40224
+ "throws": null,
40225
+ "example": null,
40226
+ "defaultValue": null,
40227
+ "guides": []
40228
+ },
40229
+ {
40230
+ "id": "GamePass.held",
40231
+ "name": "held",
40232
+ "anchor": "gamepass-held",
40233
+ "group": "player",
40234
+ "signature": "readonly held: boolean",
40235
+ "documented": true,
40236
+ "summary": "Whether the player holds it right now.",
40237
+ "deprecated": null,
40238
+ "params": [],
40239
+ "returns": null,
40240
+ "throws": null,
40241
+ "example": null,
40242
+ "defaultValue": null,
40243
+ "guides": []
40244
+ },
40245
+ {
40246
+ "id": "GamePass.daysLeft",
40247
+ "name": "daysLeft",
40248
+ "anchor": "gamepass-daysleft",
40249
+ "group": "player",
40250
+ "signature": "readonly daysLeft: number",
40251
+ "documented": true,
40252
+ "summary": "Whole days the player keeps it, rounded up so a pass with any time left\nreads at least `1`, and **capped at 7**: `7` means \"7 or more\", and is\nalso what a pass held with no end reads. `0` when it is not held. Show\n`7` as \"7+ days left\", never as an exact end.",
40253
+ "deprecated": null,
40254
+ "params": [],
40255
+ "returns": null,
40256
+ "throws": null,
40257
+ "example": null,
40258
+ "defaultValue": null,
40259
+ "guides": []
40260
+ },
40261
+ {
40262
+ "id": "GamePass.endsAt",
40263
+ "name": "endsAt",
40264
+ "anchor": "gamepass-endsat",
40265
+ "group": "player",
40266
+ "signature": "readonly endsAt: string | null",
40267
+ "documented": true,
40268
+ "summary": "When the player stops holding it, RFC 3339 in UTC, grace and any queued\nperiod included. `null` when it is not held, and when it is held with no\nend, so read [GamePass.held](api-reference-function.md#gamepass-held) first: `null` alone never means ended.",
40269
+ "deprecated": "Use [GamePass.daysLeft](api-reference-function.md#gamepass-daysleft). An exact instant is a value two\ngames could match to link one player's identities, so the platform will\nstop sending it; it reads `null` then. See\n[the deprecation policy](../deprecation-policy.md).",
40270
+ "params": [],
40271
+ "returns": null,
40272
+ "throws": null,
40273
+ "example": null,
40274
+ "defaultValue": null,
40275
+ "guides": []
40276
+ },
38562
40277
  {
38563
40278
  "id": "PlatformCurrencyAccess",
38564
40279
  "name": "PlatformCurrencyAccess",
@@ -40596,6 +42311,22 @@
40596
42311
  "defaultValue": null,
40597
42312
  "guides": []
40598
42313
  },
42314
+ {
42315
+ "id": "GemFunction.earnings",
42316
+ "name": "earnings",
42317
+ "anchor": "gemfunction-earnings",
42318
+ "group": "start",
42319
+ "signature": "readonly earnings: EarningsApi",
42320
+ "documented": true,
42321
+ "summary": "What a platform payout was: `earnings.fromReceipt(change)` turns a\n`platform_grant` change from a receipt into the shape the browser's payout\npreview gives — `base`, `total`, and the pass's or the Boost's part — to\nreturn to the game that draws it.",
42322
+ "deprecated": null,
42323
+ "params": [],
42324
+ "returns": null,
42325
+ "throws": null,
42326
+ "example": null,
42327
+ "defaultValue": null,
42328
+ "guides": []
42329
+ },
40599
42330
  {
40600
42331
  "id": "GemFunction.profiles",
40601
42332
  "name": "profiles",