@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.
@@ -1,6 +1,6 @@
1
1
  # The browser surface — API reference
2
2
 
3
- Generated from `@indiegems/gem-web-sdk/sdk` v23.2.0, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
3
+ Generated from `@indiegems/gem-web-sdk/sdk` v24.0.0, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
4
4
 
5
5
  `@indiegems/gem-web-sdk/sdk` is the one import a game writes, wherever its code runs. `@indiegems/gem-web-sdk/game` exports exactly the symbols on this page and nothing else, for a build that wants only this half.
6
6
 
@@ -3123,6 +3123,7 @@ fourth.
3123
3123
  - `declined` — the player closed the prompt, or the arcade chose not to show
3124
3124
  one. Return to the game silently.
3125
3125
  - `unavailable` — the arcade could not offer sign-in, or never answered.
3126
+ [AccountApi.signInAvailable](#accountapi-signinavailable) says so before you draw the button.
3126
3127
 
3127
3128
  No reason is given for a `declined`, and one will never be added: why a
3128
3129
  sign-in did not happen is a fact about the player's account, and the arcade
@@ -3162,6 +3163,59 @@ interface AccountApi
3162
3163
 
3163
3164
  The account surface. `gem.account` is one of these.
3164
3165
 
3166
+ <a id="accountapi-signinavailable"></a>
3167
+
3168
+ ## AccountApi.signInAvailable
3169
+
3170
+ ```ts
3171
+ readonly signInAvailable: boolean
3172
+ ```
3173
+
3174
+ Whether [AccountApi.requestSignIn](#accountapi-requestsignin) can show this player a sign-in
3175
+ here. Read it before you draw a "Sign in to claim" button, and hide the
3176
+ offer when it is `false`.
3177
+
3178
+ - `true` — a guest the arcade can offer sign-up to. An ask may still
3179
+ answer `declined` at a busy moment.
3180
+ - `false` — an ask would show nothing. The player is already signed in
3181
+ (it answers `signed_in` at once; every player in a current Gem Arcade
3182
+ app build, v3.33.174 and later, is), or there is no Arcade sign-in here
3183
+ (it answers `unavailable`). Also `false` until the player is known.
3184
+
3185
+ On an arcade older than this signal it reads `true` for every player, a
3186
+ full account included, as before it existed. So gate a sign-up gift on it
3187
+ and on the guest test you use today; see docs/player-sign-in.md.
3188
+ [AccountApi.onSignInAvailableChange](#accountapi-onsigninavailablechange) tells you when it changes.
3189
+
3190
+ ```ts
3191
+ const draw = (available: boolean) => {
3192
+ signInButton.hidden = !available;
3193
+ };
3194
+ draw(gem.account.signInAvailable);
3195
+ gem.account.onSignInAvailableChange(draw);
3196
+ ```
3197
+
3198
+ **Guides.** [player-sign-in](../player-sign-in.md)
3199
+
3200
+ <a id="accountapi-onsigninavailablechange"></a>
3201
+
3202
+ ## AccountApi.onSignInAvailableChange
3203
+
3204
+ ```ts
3205
+ onSignInAvailableChange(listener: (available: boolean) => void): () => void
3206
+ ```
3207
+
3208
+ Runs `listener` whenever [AccountApi.signInAvailable](#accountapi-signinavailable) changes: when
3209
+ the player arrives, when the arcade changes its answer (a guest who signs
3210
+ up), and when the connection closes. Not on a re-sent player with the same
3211
+ answer.
3212
+
3213
+ | Parameter | Type |
3214
+ | --- | --- |
3215
+ | `listener` | `(available: boolean) => void` |
3216
+
3217
+ **Returns.** A function that stops the listener.
3218
+
3165
3219
  <a id="accountapi-requestsignin"></a>
3166
3220
 
3167
3221
  ## AccountApi.requestSignIn
@@ -3998,7 +4052,8 @@ dimension for it on the events you want to break down, with the arms as its
3998
4052
  values, named after the experiment, and pass the selection there yourself:
3999
4053
  `gem.analytics.track('tutorial_finished', { tutorial_length: tutorial })`.
4000
4054
  Never the platform's `variant` field, which belongs to store-page
4001
- experiments.
4055
+ experiments. For the platform's results to show the test, also declare it
4056
+ under `analytics_tests` in your game config (see docs/analytics.md).
4002
4057
 
4003
4058
  WAITS FOR THE PLAYER. The id arrives with the first play token, so a call
4004
4059
  during boot waits for it, and rejects as [Gem.awaitIdentity](#gem-awaitidentity) does
@@ -4683,10 +4738,13 @@ The SKU's id: what [StoreApi.purchase](#storeapi-purchase) takes.
4683
4738
  ## ArcadeOffer.kind
4684
4739
 
4685
4740
  ```ts
4686
- readonly kind: "gem_pack" | "boost" | "pass" | "bundle"
4741
+ readonly kind: ArcadeOfferKind
4687
4742
  ```
4688
4743
 
4689
- What sort of thing it is.
4744
+ What sort of thing it is: `coin_pack`, `boost`, `pass` or `bundle` — or
4745
+ `gem_pack`, which is deprecated: Gems are being retired, and a pack of
4746
+ Coins is `coin_pack`. Keep a `default` arm. See
4747
+ [the deprecation policy](../deprecation-policy.md).
4690
4748
 
4691
4749
  <a id="arcadeoffer-name"></a>
4692
4750
 
@@ -4736,7 +4794,7 @@ The price, formatted by the payment rail. Show it as given.
4736
4794
  readonly grant?: { currency: string; amount: number }
4737
4795
  ```
4738
4796
 
4739
- For a Gem pack: the platform currency and the amount it grants.
4797
+ For a Coin pack: the platform currency and the amount it grants.
4740
4798
 
4741
4799
  <a id="arcadeoffer-grant-currency"></a>
4742
4800
 
@@ -4746,7 +4804,7 @@ For a Gem pack: the platform currency and the amount it grants.
4746
4804
  readonly currency: string
4747
4805
  ```
4748
4806
 
4749
- The platform currency's exact slug, e.g. `GEMS`.
4807
+ The platform currency's exact slug, e.g. `ARCADE_TICKETS` for Coins.
4750
4808
 
4751
4809
  <a id="arcadeoffer-grant-amount"></a>
4752
4810
 
@@ -4758,6 +4816,129 @@ readonly amount: number
4758
4816
 
4759
4817
  How much of it the pack grants.
4760
4818
 
4819
+ <a id="arcadeoffer-pass"></a>
4820
+
4821
+ ## ArcadeOffer.pass
4822
+
4823
+ ```ts
4824
+ readonly pass?: string
4825
+ ```
4826
+
4827
+ For a pass, or a bundle carrying one: the pass's slug, such as `vip` — the
4828
+ slug `gem.me.passes` reports and [StoreApi.showPass](#storeapi-showpass) takes. Absent
4829
+ for everything else, and on an older Arcade.
4830
+
4831
+ <a id="arcadeoffer-earnmultipliers"></a>
4832
+
4833
+ ## ArcadeOffer.earnMultipliers
4834
+
4835
+ ```ts
4836
+ readonly earnMultipliers: readonly ArcadeOfferEarnMultiplier[]
4837
+ ```
4838
+
4839
+ For an offer that makes the player earn a platform currency faster while
4840
+ they hold it: one entry per currency. Empty when it boosts nothing.
4841
+
4842
+ The platform's own figure, for `gem.gameConfig.preview` to work out what
4843
+ holding it would add. **Never multiply a reward by it yourself**: the
4844
+ preview rounds the way the platform pays.
4845
+
4846
+ <a id="arcadeofferkind"></a>
4847
+
4848
+ ## ArcadeOfferKind
4849
+
4850
+ ```ts
4851
+ type ArcadeOfferKind = "coin_pack" | "gem_pack" | "boost" | "pass" | "bundle"
4852
+ ```
4853
+
4854
+ What sort of SKU an [ArcadeOffer](#arcadeoffer) is. `gem_pack` is deprecated: Gems
4855
+ are being retired, and a pack of Coins is `coin_pack`.
4856
+
4857
+ <a id="arcadeofferearnmultiplier"></a>
4858
+
4859
+ ## ArcadeOfferEarnMultiplier
4860
+
4861
+ ```ts
4862
+ interface ArcadeOfferEarnMultiplier
4863
+ ```
4864
+
4865
+ One currency an [ArcadeOffer](#arcadeoffer) boosts while it is held.
4866
+
4867
+ <a id="arcadeofferearnmultiplier-currencyslug"></a>
4868
+
4869
+ ## ArcadeOfferEarnMultiplier.currencySlug
4870
+
4871
+ ```ts
4872
+ readonly currencySlug: string
4873
+ ```
4874
+
4875
+ The currency's slug, e.g. `ARCADE_TICKETS`.
4876
+
4877
+ <a id="arcadeofferearnmultiplier-multiplierbp"></a>
4878
+
4879
+ ## ArcadeOfferEarnMultiplier.multiplierBp
4880
+
4881
+ ```ts
4882
+ readonly multiplierBp: number
4883
+ ```
4884
+
4885
+ Basis points: `10000` is 1x, `15000` is 1.5x. An integer, so the preview
4886
+ rounds exactly as the platform does.
4887
+
4888
+ <a id="showpassoutcome"></a>
4889
+
4890
+ ## ShowPassOutcome
4891
+
4892
+ ```ts
4893
+ interface ShowPassOutcome
4894
+ ```
4895
+
4896
+ What [StoreApi.showPass](#storeapi-showpass) resolves with: an [ArcadeOutcome](#arcadeoutcome), plus
4897
+ whether a fresh read shows the pass held.
4898
+
4899
+ <a id="showpassoutcome-outcome"></a>
4900
+
4901
+ ## ShowPassOutcome.outcome
4902
+
4903
+ ```ts
4904
+ readonly outcome: ArcadeOutcomeKind
4905
+ ```
4906
+
4907
+ What happened: `ok`, `declined` or `unavailable`. Branch on this.
4908
+
4909
+ <a id="showpassoutcome-reason"></a>
4910
+
4911
+ ## ShowPassOutcome.reason
4912
+
4913
+ ```ts
4914
+ readonly reason?: string
4915
+ ```
4916
+
4917
+ Why, as a machine code — `busy`, `cancelled`, `not_found`, `pending`,
4918
+ `store_off`, `sign_in_required` and more. OPEN-ENDED: the arcade may send a
4919
+ code this SDK has never heard of, so keep a `default` arm and never show a
4920
+ code to a player. Absent when there is nothing to add.
4921
+
4922
+ Three are the SDK's own, answered without asking the arcade:
4923
+ `unsupported` (the arcade here does not offer this), `invalid_argument`
4924
+ (the SDK refused what you passed) and `busy` (another ask of the same kind
4925
+ is still open). `error` means no reliable answer arrived — the ask never
4926
+ left, the wait ran out, the bridge closed or the arcade had none. For
4927
+ `gem.store.purchase` that makes the outcome UNKNOWN, not a refusal: the
4928
+ player may have paid.
4929
+
4930
+ <a id="showpassoutcome-held"></a>
4931
+
4932
+ ## ShowPassOutcome.held
4933
+
4934
+ ```ts
4935
+ readonly held: boolean
4936
+ ```
4937
+
4938
+ Whether a fresh read of `gem.me.passes` showed the pass held once the
4939
+ sheet closed. `true` exactly when `outcome` is `ok` with no `reason`: a
4940
+ recipe the game runs next gets the pass's bonus.
4941
+
4761
4942
  <a id="storeoffersoutcome"></a>
4762
4943
 
4763
4944
  ## StoreOffersOutcome
@@ -4837,10 +5018,11 @@ The SKU it was for. Re-read balances and inventory through `gem.me`.
4837
5018
  interface StoreApi
4838
5019
  ```
4839
5020
 
4840
- **Real money.** The Arcade Store's own Gem packs, Boosts, passes and bundles,
5021
+ **Real money.** The Arcade Store's own Coin packs, Boosts, passes and bundles,
4841
5022
  bought with the player's money through the Arcade Store's own checkout.
4842
- `gem.store` is one of these. To spend Gems or Coins on your game's own items,
4843
- use `gem.purchases`.
5023
+ `gem.store` is one of these. To spend Coins on your game's own items, use
5024
+ `gem.purchases`. Only the Arcade sells Coins: a game never sells currency
5025
+ for money.
4844
5026
 
4845
5027
  <a id="storeapi-available"></a>
4846
5028
 
@@ -4851,7 +5033,7 @@ readonly available: boolean
4851
5033
  ```
4852
5034
 
4853
5035
  Whether the Arcade Store answers here, synchronously. Draw your own "Get
4854
- Gems" button only when it is true.
5036
+ Coins" button only when it is true.
4855
5037
 
4856
5038
  <a id="storeapi-showofferavailable"></a>
4857
5039
 
@@ -4875,9 +5057,10 @@ offers(): Promise<StoreOffersOutcome>
4875
5057
  ```
4876
5058
 
4877
5059
  What the store sells this player here: names, art and the rail's own
4878
- prices. `unavailable` / `sign_in_required` for a guest (offer
4879
- `gem.account.requestSignIn`), `unavailable` / `store_off` where the store
4880
- is withheld. Re-read on resume: what is on sale can change.
5060
+ prices. A guest is answered too: buying takes them through sign-up first,
5061
+ in the Arcade's own screens. `unavailable` / `store_off` where the store is
5062
+ withheld. An older Arcade answers a guest `unavailable` /
5063
+ `sign_in_required`. Re-read on resume: what is on sale can change.
4881
5064
 
4882
5065
  **Returns.** Always resolves; never rejects.
4883
5066
 
@@ -4937,7 +5120,8 @@ has left. The sheet's Buy opens the same checkout as
4937
5120
 
4938
5121
  Use it for an offer you would have to hardcode a pitch for: VIP and other
4939
5122
  passes, and bundles. Your button only needs to say what it opens. Draw a
4940
- Gem pack yourself and use `purchase` instead. Check
5123
+ Coin pack yourself and use `purchase` instead. For a pass, call
5124
+ [StoreApi.showPass](#storeapi-showpass) with its slug. Check
4941
5125
  [StoreApi.showOfferAvailable](#storeapi-showofferavailable) first, and that the SKU is in `offers`.
4942
5126
 
4943
5127
  It answers exactly as `purchase` does, with one addition: `declined` /
@@ -4958,6 +5142,66 @@ player may read a sheet and pay for as long as they like.
4958
5142
 
4959
5143
  **Returns.** Always resolves; never rejects.
4960
5144
 
5145
+ <a id="storeapi-showpass"></a>
5146
+
5147
+ ## StoreApi.showPass
5148
+
5149
+ ```ts
5150
+ showPass(slug: string): Promise<ShowPassOutcome>
5151
+ ```
5152
+
5153
+ Show a pass by its slug in the Arcade's own sheet, over your paused game.
5154
+ Pass it `upsell.pass` straight from `gem.gameConfig.preview`, never a
5155
+ literal. Sugar over [StoreApi.offers](#storeapi-offers) and [StoreApi.showOffer](#storeapi-showoffer):
5156
+ it finds the offer whose `pass` is `slug` and shows that. A guest is taken
5157
+ through sign-up first, then back to the sheet.
5158
+
5159
+ - `ok`, `held: true` — **a fresh read of `gem.me.passes` shows the pass
5160
+ held.** Run the recipe or claim now, and the pass's bonus applies.
5161
+ - `ok` / `pending`, `held: false` — the player paid, and the pass has not
5162
+ arrived yet (Ask to Buy, a slow payment). Run the recipe now, without
5163
+ the bonus: the platform holds nothing back for later.
5164
+ - `declined` — they closed the sheet. Run the recipe as it is, and don't
5165
+ ask again at this reward moment.
5166
+ - `unavailable` / `not_found` — the pass is not on sale to this player
5167
+ here, or this Arcade is too old to say which offer is a pass, and
5168
+ nothing opened. `unavailable` / `unsupported` — this Arcade draws no
5169
+ sheet. Run the recipe as it is.
5170
+ - `unavailable` / `error` — **the outcome is unknown, and the player may
5171
+ have paid.** Run the recipe; `gem.me.passes.on('changed')` hears the
5172
+ pass if it arrives.
5173
+
5174
+ NO TIMEOUT on the sheet, as with `showOffer`. After a paid `ok` it reads
5175
+ `gem.me.passes` for about ten seconds before it settles for `pending`.
5176
+
5177
+ | Parameter | Type |
5178
+ | --- | --- |
5179
+ | `slug` | `string` |
5180
+
5181
+ **Returns.** Always resolves; never rejects.
5182
+
5183
+ <a id="storeapi-passonsale"></a>
5184
+
5185
+ ## StoreApi.passOnSale
5186
+
5187
+ ```ts
5188
+ passOnSale(slug: string): Promise<boolean>
5189
+ ```
5190
+
5191
+ Whether the pass `slug` is on sale to this player here, so
5192
+ [StoreApi.showPass](#storeapi-showpass) would open it. For a screen that sells a pass
5193
+ outside a reward moment, such as a "VIP perks" panel; at a reward moment,
5194
+ `gem.gameConfig.preview`'s `upsells` already says so. `false` where the
5195
+ store is off, for a player the platform won't sell it to, after the
5196
+ player declined its sheet twice today, and on an older Arcade. A player
5197
+ who holds the pass may still read `true`: the sheet adds time.
5198
+
5199
+ | Parameter | Type |
5200
+ | --- | --- |
5201
+ | `slug` | `string` |
5202
+
5203
+ **Returns.** Always resolves; never rejects.
5204
+
4961
5205
  <a id="storeapi-onsettled"></a>
4962
5206
 
4963
5207
  ## StoreApi.onSettled
@@ -6647,6 +6891,256 @@ only when your studio corrects a running period; clients may hold the old
6647
6891
  content for up to an hour afterwards, and the hash is how a verifier tells
6648
6892
  them apart.
6649
6893
 
6894
+ <a id="previewtarget"></a>
6895
+
6896
+ ## PreviewTarget
6897
+
6898
+ ```ts
6899
+ type PreviewTarget = { recipe: string } | { progression: string; add?: number }
6900
+ ```
6901
+
6902
+ What to preview: a recipe, or a progression with the delta the game is about
6903
+ to apply. A config id, never an amount and never a pass.
6904
+
6905
+ - `{ recipe: 'open_chest' }` — the recipe's grants, its roll, its bonuses,
6906
+ and the platform payouts of any progression it adds to.
6907
+ - `{ progression: 'run_complete' }` — the progression's `on_complete`
6908
+ payouts, "if it completes".
6909
+ - `{ progression: 'puzzles_cleared', add: 3 }` — its `on_add` payouts for
6910
+ that delta, and its `on_complete` payouts if it completes.
6911
+
6912
+ Ids and slugs are both accepted, as everywhere in `gem.gameConfig`.
6913
+
6914
+ <a id="previewgrant"></a>
6915
+
6916
+ ## PreviewGrant
6917
+
6918
+ ```ts
6919
+ type PreviewGrant = { kind: "item"; itemId: string; quantity: number } | { kind: "currency"; currencyId: string; amount: number } | { kind: "roll"; lootTableId: string; count: number }
6920
+ ```
6921
+
6922
+ One thing a claim grants in your game. A roll is a count of rolls from a loot
6923
+ table, never what they land on: never show a locked roll's outcome.
6924
+
6925
+ <a id="unmetrequirement"></a>
6926
+
6927
+ ## UnmetRequirement
6928
+
6929
+ ```ts
6930
+ interface UnmetRequirement
6931
+ ```
6932
+
6933
+ One requirement a bonus has that the player does not meet: where it is, and its kind.
6934
+
6935
+ <a id="unmetrequirement-index"></a>
6936
+
6937
+ ## UnmetRequirement.index
6938
+
6939
+ ```ts
6940
+ readonly index: number
6941
+ ```
6942
+
6943
+ Its position in the bonus's `requires`.
6944
+
6945
+ <a id="unmetrequirement-kind"></a>
6946
+
6947
+ ## UnmetRequirement.kind
6948
+
6949
+ ```ts
6950
+ readonly kind: string
6951
+ ```
6952
+
6953
+ Its kind, such as `holds_pass`. Never the pass or the item it names.
6954
+
6955
+ <a id="bonuspreview"></a>
6956
+
6957
+ ## BonusPreview
6958
+
6959
+ ```ts
6960
+ interface BonusPreview
6961
+ ```
6962
+
6963
+ One `bonuses` entry, and whether this player gets it.
6964
+
6965
+ <a id="bonuspreview-id"></a>
6966
+
6967
+ ## BonusPreview.id
6968
+
6969
+ ```ts
6970
+ readonly id: string
6971
+ ```
6972
+
6973
+ The bonus's id, as you configured it.
6974
+
6975
+ <a id="bonuspreview-grants"></a>
6976
+
6977
+ ## BonusPreview.grants
6978
+
6979
+ ```ts
6980
+ readonly grants: readonly PreviewGrant[]
6981
+ ```
6982
+
6983
+ What it grants.
6984
+
6985
+ <a id="bonuspreview-eligible"></a>
6986
+
6987
+ ## BonusPreview.eligible
6988
+
6989
+ ```ts
6990
+ readonly eligible: boolean | "unknown"
6991
+ ```
6992
+
6993
+ `true` — every requirement holds now. `false` — at least one does not
6994
+ (see `unmet`). `'unknown'` — one the browser cannot see, such as owning a
6995
+ platform item; never a guess. Advisory: the run decides.
6996
+
6997
+ <a id="bonuspreview-unmet"></a>
6998
+
6999
+ ## BonusPreview.unmet
7000
+
7001
+ ```ts
7002
+ readonly unmet: readonly UnmetRequirement[]
7003
+ ```
7004
+
7005
+ The requirements that do not hold. Empty unless `eligible` is `false`.
7006
+
7007
+ <a id="passupsell"></a>
7008
+
7009
+ ## PassUpsell
7010
+
7011
+ ```ts
7012
+ interface PassUpsell
7013
+ ```
7014
+
7015
+ What holding one pass would add to a claim.
7016
+
7017
+ <a id="passupsell-pass"></a>
7018
+
7019
+ ## PassUpsell.pass
7020
+
7021
+ ```ts
7022
+ readonly pass: string
7023
+ ```
7024
+
7025
+ The pass's slug: what `gem.store.showPass` takes.
7026
+
7027
+ <a id="passupsell-name"></a>
7028
+
7029
+ ## PassUpsell.name
7030
+
7031
+ ```ts
7032
+ readonly name: string
7033
+ ```
7034
+
7035
+ The store's own name for it, localized. Show this, never your own word for it.
7036
+
7037
+ <a id="passupsell-offer"></a>
7038
+
7039
+ ## PassUpsell.offer
7040
+
7041
+ ```ts
7042
+ readonly offer: ArcadeOffer
7043
+ ```
7044
+
7045
+ The pass's offer, as `gem.store.offers` lists it.
7046
+
7047
+ <a id="passupsell-platformgrants"></a>
7048
+
7049
+ ## PassUpsell.platformGrants
7050
+
7051
+ ```ts
7052
+ readonly platformGrants: readonly { currencySlug?: string; currencyId?: string; total: number; extra: number; ifComplete?: true }[]
7053
+ ```
7054
+
7055
+ Each payout this pass would raise: the new `total`, and the `extra` over now.
7056
+
7057
+ <a id="passupsell-bonuses"></a>
7058
+
7059
+ ## PassUpsell.bonuses
7060
+
7061
+ ```ts
7062
+ readonly bonuses: readonly { id: string; grants: readonly PreviewGrant[] }[]
7063
+ ```
7064
+
7065
+ The bonuses this pass would make eligible.
7066
+
7067
+ <a id="configpreview"></a>
7068
+
7069
+ ## ConfigPreview
7070
+
7071
+ ```ts
7072
+ interface ConfigPreview
7073
+ ```
7074
+
7075
+ What [PreviewTarget](#previewtarget) would give this player now, and what each pass would add.
7076
+
7077
+ <a id="configpreview-grants"></a>
7078
+
7079
+ ## ConfigPreview.grants
7080
+
7081
+ ```ts
7082
+ readonly grants: readonly PreviewGrant[]
7083
+ ```
7084
+
7085
+ Your game's own grants: items, game currencies, rolls.
7086
+
7087
+ <a id="configpreview-platformgrants"></a>
7088
+
7089
+ ## ConfigPreview.platformGrants
7090
+
7091
+ ```ts
7092
+ readonly platformGrants: readonly PlatformGrantPreview[]
7093
+ ```
7094
+
7095
+ One per platform payout that would fire.
7096
+
7097
+ <a id="configpreview-bonuses"></a>
7098
+
7099
+ ## ConfigPreview.bonuses
7100
+
7101
+ ```ts
7102
+ readonly bonuses: readonly BonusPreview[]
7103
+ ```
7104
+
7105
+ One per `bonuses` entry, in config order.
7106
+
7107
+ <a id="configpreview-upsells"></a>
7108
+
7109
+ ## ConfigPreview.upsells
7110
+
7111
+ ```ts
7112
+ readonly upsells: readonly PassUpsell[]
7113
+ ```
7114
+
7115
+ One per pass on sale here, not held, that would change this answer. Empty
7116
+ means show no upsell: no pass would add anything, or none is on sale to
7117
+ this player (a minor, a store that is off, a pass declined twice today, or
7118
+ already held).
7119
+
7120
+ <a id="previewerror"></a>
7121
+
7122
+ ## PreviewError
7123
+
7124
+ ```ts
7125
+ class PreviewError
7126
+ ```
7127
+
7128
+ Why `gem.gameConfig.preview` could not work out an answer.
7129
+
7130
+ <a id="previewerror-code"></a>
7131
+
7132
+ ## PreviewError.code
7133
+
7134
+ ```ts
7135
+ readonly code: "unsupported" | "not_found" | "invalid_argument"
7136
+ ```
7137
+
7138
+ `not_found` — the config has no recipe or progression by that id or
7139
+ slug; `invalid_argument` — the target is not `{ recipe }` or
7140
+ `{ progression, add? }` with a positive whole `add`; `unsupported` —
7141
+ not in a browser game (a function or a server runs the recipe and reads
7142
+ the receipt).
7143
+
6650
7144
  <a id="gemlookspeed"></a>
6651
7145
 
6652
7146
  ## GemLookSpeed
@@ -7137,48 +7631,124 @@ readonly isOpen: boolean
7137
7631
 
7138
7632
  Whether the panel is on screen right now.
7139
7633
 
7140
- <a id="gamepass"></a>
7634
+ <a id="platformgrantsource"></a>
7141
7635
 
7142
- ## GamePass
7636
+ ## PlatformGrantSource
7143
7637
 
7144
7638
  ```ts
7145
- interface GamePass
7639
+ type PlatformGrantSource = { source: "pass"; pass: string; amount: number } | { source: "boost"; amount: number }
7146
7640
  ```
7147
7641
 
7148
- One pass games may see, and whether this player holds it. Listed whether they
7149
- hold it or not, and this is everything a game learns about it.
7642
+ Where the part of a payout above its base comes from.
7150
7643
 
7151
- <a id="gamepass-pass"></a>
7644
+ <a id="platformgrantpreview"></a>
7152
7645
 
7153
- ## GamePass.pass
7646
+ ## PlatformGrantPreview
7154
7647
 
7155
7648
  ```ts
7156
- readonly pass: string
7649
+ interface PlatformGrantPreview
7157
7650
  ```
7158
7651
 
7159
- The pass's offering slug, such as `vip`. Stable and not localized: key on this.
7652
+ One platform payout — Coins, today — as the preview expects it or a receipt
7653
+ reports it, in one shape, so "you will earn" and "you earned" render with one
7654
+ component.
7160
7655
 
7161
- <a id="gamepass-held"></a>
7656
+ <a id="platformgrantpreview-currencyslug"></a>
7162
7657
 
7163
- ## GamePass.held
7658
+ ## PlatformGrantPreview.currencySlug
7164
7659
 
7165
7660
  ```ts
7166
- readonly held: boolean
7661
+ readonly currencySlug?: string
7167
7662
  ```
7168
7663
 
7169
- Whether the player holds it right now.
7664
+ The currency's slug, e.g. `ARCADE_TICKETS`. Absent when unknown.
7170
7665
 
7171
- <a id="gamepass-endsat"></a>
7666
+ <a id="platformgrantpreview-currencyid"></a>
7172
7667
 
7173
- ## GamePass.endsAt
7668
+ ## PlatformGrantPreview.currencyId
7174
7669
 
7175
7670
  ```ts
7176
- readonly endsAt: string | null
7671
+ readonly currencyId?: string
7177
7672
  ```
7178
7673
 
7179
- When the player stops holding it, RFC 3339 in UTC, grace and any queued
7180
- period included. `null` when it is not held, and when it is held with no
7181
- end, so read [GamePass.held](#gamepass-held) first: `null` alone never means ended.
7674
+ The currency's id, when it is known.
7675
+
7676
+ <a id="platformgrantpreview-base"></a>
7677
+
7678
+ ## PlatformGrantPreview.base
7679
+
7680
+ ```ts
7681
+ readonly base: number
7682
+ ```
7683
+
7684
+ What a player with no pass and no Boost gets.
7685
+
7686
+ <a id="platformgrantpreview-total"></a>
7687
+
7688
+ ## PlatformGrantPreview.total
7689
+
7690
+ ```ts
7691
+ readonly total: number
7692
+ ```
7693
+
7694
+ What this player gets: `base` plus `breakdown`.
7695
+
7696
+ <a id="platformgrantpreview-breakdown"></a>
7697
+
7698
+ ## PlatformGrantPreview.breakdown
7699
+
7700
+ ```ts
7701
+ readonly breakdown: readonly PlatformGrantSource[]
7702
+ ```
7703
+
7704
+ The part above `base`, and what it is from. Empty when `total` is `base`.
7705
+
7706
+ <a id="platformgrantpreview-ifcomplete"></a>
7707
+
7708
+ ## PlatformGrantPreview.ifComplete
7709
+
7710
+ ```ts
7711
+ readonly ifComplete?: true
7712
+ ```
7713
+
7714
+ `true` for an `on_complete` payout in a preview: it pays only if this
7715
+ claim completes the progression. Absent otherwise, and on a receipt.
7716
+
7717
+ <a id="earningsapi"></a>
7718
+
7719
+ ## EarningsApi
7720
+
7721
+ ```ts
7722
+ interface EarningsApi
7723
+ ```
7724
+
7725
+ `gem.earnings`: what a payout was, in the shape `gem.gameConfig.preview`
7726
+ says it would be.
7727
+
7728
+ <a id="earningsapi-fromreceipt"></a>
7729
+
7730
+ ## EarningsApi.fromReceipt
7731
+
7732
+ ```ts
7733
+ fromReceipt(change: unknown): Promise<PlatformGrantPreview | null>
7734
+ ```
7735
+
7736
+ Turn one `platform_grant` change from an order's receipt into a
7737
+ [PlatformGrantPreview](#platformgrantpreview): `base` is what a player with no pass and no
7738
+ Boost would have got under the same limits, `total` what this player got,
7739
+ and `breakdown` the pass's or the Boost's part, even when a limit trimmed
7740
+ the payout. Draw "you earned" with the component that drew "you will
7741
+ earn". Resolves `null` for any other kind of change.
7742
+
7743
+ **This and `preview` are the only way to show a platform payout.** Never
7744
+ multiply by `gem.player.boost`, and never copy a config amount into your
7745
+ game.
7746
+
7747
+ | Parameter | Type |
7748
+ | --- | --- |
7749
+ | `change` | `unknown` |
7750
+
7751
+ **Returns.** Always resolves; never rejects.
7182
7752
 
7183
7753
  <a id="passesevent"></a>
7184
7754
 
@@ -10581,6 +11151,32 @@ when it pays. See [PlayerBoost](#playerboost).
10581
11151
 
10582
11152
  **Guides.** [platform-rules](../platform-rules.md)
10583
11153
 
11154
+ <a id="gemplayer-isguest"></a>
11155
+
11156
+ ## GemPlayer.isGuest
11157
+
11158
+ ```ts
11159
+ readonly isGuest: boolean
11160
+ ```
11161
+
11162
+ Whether the Arcade says this player is a **guest it can sign up here**.
11163
+
11164
+ `true` only when the Arcade said so. `false` for a player with a full
11165
+ account, on a surface that offers no sign-up, and when the Arcade did not
11166
+ say — an older Arcade — so `false` is never proof of an account. That
11167
+ makes it the test for a sign-up gift: offer one only while this is
11168
+ `true`, and the gift can never reach a player who already has an account.
11169
+
11170
+ It changes when a guest signs up in place: `gem.player` is pushed again,
11171
+ and `onPlayer` hears it.
11172
+
11173
+ ```js
11174
+ const gem = createGem({ onPlayer: (player) => (signUpGift.hidden = !player.isGuest) });
11175
+ ```
11176
+
11177
+ For drawing only, like everything on `gem.player`: what a sign-up earns
11178
+ is granted by your function or server, which reads the account itself.
11179
+
10584
11180
  <a id="gemplayer-playerid"></a>
10585
11181
 
10586
11182
  ## GemPlayer.playerId
@@ -11774,6 +12370,18 @@ Every other surface returns ids; this is what turns them into something a
11774
12370
  player can read. Call `load()` once during start-up, then look up
11775
12371
  synchronously wherever you render.
11776
12372
 
12373
+ <a id="gem-earnings"></a>
12374
+
12375
+ ## Gem.earnings
12376
+
12377
+ ```ts
12378
+ readonly earnings: EarningsApi
12379
+ ```
12380
+
12381
+ What a platform payout was: `gem.earnings.fromReceipt(change)` turns a
12382
+ receipt's `platform_grant` change into the shape `gem.gameConfig.preview`
12383
+ gives, so "you will earn" and "you earned" draw with one component.
12384
+
11777
12385
  <a id="gem-purchases"></a>
11778
12386
 
11779
12387
  ## Gem.purchases
@@ -15178,6 +15786,67 @@ engine's WebTransport without a datagram lane (WebKit today): that is a
15178
15786
  fact about the browser too, and one worth remembering — see
15179
15787
  [webTransportRefusal](#webtransportrefusal).
15180
15788
 
15789
+ <a id="gamepass"></a>
15790
+
15791
+ ## GamePass
15792
+
15793
+ ```ts
15794
+ interface GamePass
15795
+ ```
15796
+
15797
+ One pass games may see, and whether this player holds it. Listed whether they
15798
+ hold it or not, and this is everything a game learns about it.
15799
+
15800
+ <a id="gamepass-pass"></a>
15801
+
15802
+ ## GamePass.pass
15803
+
15804
+ ```ts
15805
+ readonly pass: string
15806
+ ```
15807
+
15808
+ The pass's offering slug, such as `vip`. Stable and not localized: key on this.
15809
+
15810
+ <a id="gamepass-held"></a>
15811
+
15812
+ ## GamePass.held
15813
+
15814
+ ```ts
15815
+ readonly held: boolean
15816
+ ```
15817
+
15818
+ Whether the player holds it right now.
15819
+
15820
+ <a id="gamepass-daysleft"></a>
15821
+
15822
+ ## GamePass.daysLeft
15823
+
15824
+ ```ts
15825
+ readonly daysLeft: number
15826
+ ```
15827
+
15828
+ Whole days the player keeps it, rounded up so a pass with any time left
15829
+ reads at least `1`, and **capped at 7**: `7` means "7 or more", and is
15830
+ also what a pass held with no end reads. `0` when it is not held. Show
15831
+ `7` as "7+ days left", never as an exact end.
15832
+
15833
+ <a id="gamepass-endsat"></a>
15834
+
15835
+ ## GamePass.endsAt
15836
+
15837
+ ```ts
15838
+ readonly endsAt: string | null
15839
+ ```
15840
+
15841
+ **Deprecated.** Use [GamePass.daysLeft](#gamepass-daysleft). An exact instant is a value two
15842
+ games could match to link one player's identities, so the platform will
15843
+ stop sending it; it reads `null` then. See
15844
+ [the deprecation policy](../deprecation-policy.md).
15845
+
15846
+ When the player stops holding it, RFC 3339 in UTC, grace and any queued
15847
+ period included. `null` when it is not held, and when it is held with no
15848
+ end, so read [GamePass.held](#gamepass-held) first: `null` alone never means ended.
15849
+
15181
15850
  <a id="platformcurrencyaccess"></a>
15182
15851
 
15183
15852
  ## PlatformCurrencyAccess
@@ -16349,6 +17018,47 @@ Resolves with what the platform half did: `ok`, `already_ended` (this
16349
17018
  match was already over, or you asked twice), `refused` (the allocation was
16350
17019
  not ready yet) or `failed` (the platform could not be reached).
16351
17020
 
17021
+ <a id="gemserverhost-passes"></a>
17022
+
17023
+ ## GemServerHost.passes
17024
+
17025
+ ```ts
17026
+ passes(playerId: string): Promise<GemServerHostRead>
17027
+ ```
17028
+
17029
+ Ask the platform half which game-visible passes a player in this match
17030
+ holds: the match pass read, made with the allocation's own proof.
17031
+
17032
+ MAY BE ABSENT on older platform images, exactly as `endMatch` may, and a
17033
+ listen host never has it. `gem.match.playerPasses(playerId)` is this call;
17034
+ you rarely call it yourself.
17035
+
17036
+ Resolves with what the platform answered — see [GemServerHostRead](#gemserverhostread)
17037
+ — and rejects only when the platform half could not be asked at all.
17038
+
17039
+ | Parameter | Type |
17040
+ | --- | --- |
17041
+ | `playerId` | `string` |
17042
+
17043
+ <a id="gemserverhost-boost"></a>
17044
+
17045
+ ## GemServerHost.boost
17046
+
17047
+ ```ts
17048
+ boost(playerId: string): Promise<GemServerHostRead>
17049
+ ```
17050
+
17051
+ Ask the platform half for a player's Boost, for display: the match boost
17052
+ read, made with the allocation's own proof.
17053
+
17054
+ MAY BE ABSENT on older platform images and on a listen host.
17055
+ `gem.match.playerBoost(playerId)` is this call; you rarely call it
17056
+ yourself. Resolves and rejects as [GemServerHost.passes](#gemserverhost-passes) does.
17057
+
17058
+ | Parameter | Type |
17059
+ | --- | --- |
17060
+ | `playerId` | `string` |
17061
+
16352
17062
  <a id="gemserverhost-log"></a>
16353
17063
 
16354
17064
  ## GemServerHost.log
@@ -16381,6 +17091,41 @@ Like [GemServerHost.log](#gemserverhost-log), marked as a warning.
16381
17091
  | --- | --- |
16382
17092
  | `message` | `string` |
16383
17093
 
17094
+ <a id="gemserverhostread"></a>
17095
+
17096
+ ## GemServerHostRead
17097
+
17098
+ ```ts
17099
+ interface GemServerHostRead
17100
+ ```
17101
+
17102
+ What the platform half answered to a player read it made for this match:
17103
+ the HTTP status and the decoded JSON body, untouched.
17104
+
17105
+ The runtime adds only the allocation's proof, which never enters the bundle
17106
+ process; the SDK reads the body and turns a refusal into a `GemApiError`, so
17107
+ a runtime never has to know what the route's answer means.
17108
+
17109
+ <a id="gemserverhostread-status"></a>
17110
+
17111
+ ## GemServerHostRead.status
17112
+
17113
+ ```ts
17114
+ readonly status: number
17115
+ ```
17116
+
17117
+ The HTTP status the platform answered with.
17118
+
17119
+ <a id="gemserverhostread-body"></a>
17120
+
17121
+ ## GemServerHostRead.body
17122
+
17123
+ ```ts
17124
+ readonly body: unknown
17125
+ ```
17126
+
17127
+ The decoded JSON body, or `null` when there was none.
17128
+
16384
17129
  <a id="gemserverbundle"></a>
16385
17130
 
16386
17131
  ## GemServerBundle