@indiegems/gem-web-sdk 8.0.0 → 9.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` v8.0.0. Every symbol below links to its page on the Gem Arcade developer site.
3
+ Generated from `@indiegems/gem-web-sdk/sdk` v9.0.0. Every symbol below links to its page on the Gem Arcade developer site.
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
 
@@ -247,6 +247,47 @@ check that a funnel you declared names events you are actually sending.
247
247
 
248
248
  Absent when your game declares none.
249
249
 
250
+ ## GameConfig.rewarded_placements
251
+
252
+ [GameConfig.rewarded_placements](https://gemarcade.com/developer/api/game#gameconfig-rewarded_placements)
253
+
254
+ ```ts
255
+ rewarded_placements?: SnapshotRewardedPlacement[]
256
+ ```
257
+
258
+ Your rewarded placements — what pressing a rewarded button grants. An
259
+ array, one row per placement, each naming itself in `placement`, with its
260
+ `grants`, the caps the platform enforces on it (`max_per_day`,
261
+ `cooldown_seconds`, `max_held`) and what happens where no ad serves
262
+ (`without_ads`).
263
+
264
+ Read it to LABEL your button — "+1 Revive" — never to decide a reward. A
265
+ grant is an `item_grant`, with a `quantity` of the item its
266
+ `item_definition_id` names, or a `currency_grant`, with an `amount` of the
267
+ currency its `currency_id` names; the word on the button is that
268
+ definition's `display_name`, from [GameConfig.item](https://gemarcade.com/developer/api/game#gameconfig-item) or
269
+ [GameConfig.currency](https://gemarcade.com/developer/api/game#gameconfig-currency):
270
+
271
+ ```js
272
+ const row = config?.rewarded_placements?.find((p) => p.placement === 'classic_revive');
273
+ const grant = row?.grants[0];
274
+ const label =
275
+ grant?.type === 'item_grant'
276
+ ? `+${grant.quantity} ${config.item(grant.item_definition_id)?.display_name ?? ''}`
277
+ : grant?.type === 'currency_grant'
278
+ ? `+${grant.amount} ${config.currency(grant.currency_id)?.display_name ?? ''}`
279
+ : undefined;
280
+ ```
281
+
282
+ Whether an ask grants anything is the platform's decision, made against
283
+ this config server-side; the grant lands on your game's inventory, and
284
+ re-reading `gem.me.inventory` is the only way to learn that it did. Whether
285
+ the button could pay right now is [AdsApi.rewardedStatus](https://gemarcade.com/developer/api/game#adsapi-rewardedstatus).
286
+
287
+ Absent when your game declares none.
288
+
289
+ **Guides.** [cross-promotion](https://gemarcade.com/developer/docs/cross-promotion)
290
+
250
291
  ## GameConfig.version
251
292
 
252
293
  [GameConfig.version](https://gemarcade.com/developer/api/game#gameconfig-version)
@@ -2862,14 +2903,16 @@ Which kind of break this is.
2862
2903
 
2863
2904
  `interstitial` is a panel the player dismisses and nothing follows from.
2864
2905
 
2865
- ⚠️ `rewarded` is part of the vocabulary and is answered `no_ad` today. It is
2866
- here so a game's own analytics can distinguish the two kinds of break, and so
2867
- this request has the shape an ad request has everywhere. Even when it is
2868
- served, the outcome grants nothing — a reward is granted by the platform,
2869
- against its own record, never by your game reading `shown`. It arrives on
2870
- your game's inventory for this player, written by the platform, so re-read
2871
- `gem.me.inventory` after a `rewarded` ask settles rather than waiting for a
2872
- signal on this pair; there will not be one.
2906
+ `rewarded` is a button the player pressed for a reward — a revive, an extra
2907
+ attempt — that your game config declares for the placement in
2908
+ `rewarded_placements`. It is served where this arcade serves it, and answered
2909
+ `no_ad` where it does not. ⚠️ The outcome grants nothing: the platform grants
2910
+ the reward, against its own record, never your game reading `shown`. It
2911
+ arrives on your game's inventory for this player, written by the platform,
2912
+ so re-read `gem.me.inventory` after a `rewarded` ask settles rather than
2913
+ waiting for a signal on this pair; there will not be one.
2914
+
2915
+ **Guides.** [rewarded-ads](https://gemarcade.com/developer/docs/rewarded-ads)
2873
2916
 
2874
2917
  ## AdRequest
2875
2918
 
@@ -2893,14 +2936,17 @@ Your own name for the break — `level-complete`, `between-rounds`.
2893
2936
 
2894
2937
  **1 to 32 characters of `a-z`, `0-9`, `-` and `_`.** Lowercase, no spaces,
2895
2938
  no accents: `'Boss Fight'` is not a placement, `'boss-fight'` is. The
2896
- grammar is the platform's own, and it is narrow deliberately — this is the
2897
- field an ad unit is keyed by, so a name that does not fit it is a name that
2898
- would stop working later rather than now. A placement outside it is
2899
- answered `no_ad` and reported to your `onError` handler.
2900
-
2901
- The platform matches it against nothing today and never shows it to a
2902
- player. It is for your own telemetry, so two break points in your game can
2903
- be told apart.
2939
+ grammar is the platform's own, and the SDK enforces it so a name that does
2940
+ not fit is refused at your keyboard rather than in the field. A placement
2941
+ outside it is answered `no_ad` and reported to your `onError` handler.
2942
+
2943
+ For `rewarded`, it names the reward your game config declares in
2944
+ `rewarded_placements`: the platform matches it against that config, and a
2945
+ placement the config does not declare is answered `no_ad`. For
2946
+ `interstitial`, the platform matches it against nothing; there it is for
2947
+ your own telemetry, so two break points in your game can be told apart.
2948
+ Either way it is never shown to a player and **never sent to an ad
2949
+ network**.
2904
2950
 
2905
2951
  ## AdRequest.format
2906
2952
 
@@ -2926,7 +2972,8 @@ fourth.
2926
2972
  - `shown` — a panel was painted and has now closed. Resume. **It grants
2927
2973
  nothing.**
2928
2974
  - `no_ad` — nothing was painted. Carry on exactly as if you had not asked.
2929
- This is the common answer.
2975
+ This is the common answer. After a `rewarded` ask the reward may still
2976
+ have landed, with no ad, so re-read your inventory either way.
2930
2977
  - `unavailable` — the arcade could not render just now. Not a softer no; read
2931
2978
  it exactly as you read `no_ad`.
2932
2979
 
@@ -2934,6 +2981,11 @@ No reason is given for a `no_ad`, and one will never be added: a reason would
2934
2981
  tell your game something about the player — an age, a consent state, a
2935
2982
  subscription — that game code is deliberately not told.
2936
2983
 
2984
+ The one thing you can learn is about **your own economy**, and not from this
2985
+ answer: [AdsApi.rewardedStatus](https://gemarcade.com/developer/api/game#adsapi-rewardedstatus) reports your own declared rewarded caps
2986
+ as the platform holds them (a platform-wide per-placement maximum, where one
2987
+ is set, can lower a cap). It never reports anything about the player.
2988
+
2937
2989
  ## AdOutcome
2938
2990
 
2939
2991
  [AdOutcome](https://gemarcade.com/developer/api/game#adoutcome)
@@ -2989,7 +3041,9 @@ The arcade pauses your game while a panel is up and resumes it when this
2989
3041
  settles, so you do not need to pause anything yourself.
2990
3042
 
2991
3043
  ⚠️ `shown` grants nothing. Never hand out a reward, an unlock or a currency
2992
- on the strength of it.
3044
+ on the strength of it. A `rewarded` placement's reward is granted by the
3045
+ platform into your game's inventory; re-read `gem.me.inventory` once this
3046
+ settles, whatever it settled as.
2993
3047
 
2994
3048
  | Parameter | Type | Description |
2995
3049
  | --- | --- | --- |
@@ -3007,8 +3061,160 @@ const { outcome } = await gem.ads.requestAd({
3007
3061
  startNextLevel();
3008
3062
  ```
3009
3063
 
3064
+ ## AdsApi.rewardedStatus
3065
+
3066
+ [AdsApi.rewardedStatus](https://gemarcade.com/developer/api/game#adsapi-rewardedstatus)
3067
+
3068
+ ```ts
3069
+ rewardedStatus(placement: string): Promise<RewardedStatus>
3070
+ ```
3071
+
3072
+ Whether a rewarded placement could pay right now, so you can decide whether
3073
+ to draw its button. **It never says whether an ad will play.**
3074
+
3075
+ The answer comes from your own `rewarded_placements` config — its daily
3076
+ cap, cooldown and `max_held` — applied to this player's use of it, as the
3077
+ platform holds them. Nothing in it describes the player. `grantable: true`
3078
+ means the platform would grant if an ad is watched or, where no ad serves,
3079
+ under the placement's `without_ads: grant`; a player can still press the
3080
+ button and get `no_ad`, for reasons your game is not told.
3081
+
3082
+ NOTHING REJECTS, like everything else on `gem.ads`. Without a play token,
3083
+ when the read fails or takes longer than ten seconds, or for a `placement`
3084
+ outside the grammar [AdRequest.placement](https://gemarcade.com/developer/api/game#adrequest-placement) documents, it resolves not
3085
+ declared and not grantable, with no `resetsAt`, and the reason goes to your
3086
+ `onError` handler.
3087
+
3088
+ It is a read of the platform, so call it when you draw the button — on a
3089
+ results or death screen — not every frame. Calls for one placement that
3090
+ overlap share one request. Draw the rest of the screen first rather than
3091
+ holding it on this answer.
3092
+
3093
+ ⚠️ It grants nothing and reserves nothing. The reward is granted by the
3094
+ platform when the player asks with `requestAd`; re-read `gem.me.inventory`
3095
+ after that ask settles, whatever it settled as.
3096
+
3097
+ | Parameter | Type | Description |
3098
+ | --- | --- | --- |
3099
+ | `placement` | `string` | A placement your `rewarded_placements` config declares. |
3100
+
3101
+ **Returns.** The placement's state. Always resolves; never rejects.
3102
+
3103
+ ```ts
3104
+ const status = await gem.ads.rewardedStatus('classic_revive');
3105
+ reviveButton.disabled = !status.grantable;
3106
+ if (status.nextEligibleAt !== undefined) {
3107
+ reviveButton.title = `Back at ${new Date(status.nextEligibleAt).toLocaleTimeString()}`;
3108
+ }
3109
+ ```
3110
+
3010
3111
  **Guides.** [cross-promotion](https://gemarcade.com/developer/docs/cross-promotion)
3011
3112
 
3113
+ ## RewardedStatus
3114
+
3115
+ [RewardedStatus](https://gemarcade.com/developer/api/game#rewardedstatus)
3116
+
3117
+ ```ts
3118
+ interface RewardedStatus
3119
+ ```
3120
+
3121
+ What [AdsApi.rewardedStatus](https://gemarcade.com/developer/api/game#adsapi-rewardedstatus) answers: the state of one rewarded
3122
+ placement for this player, from your own declared caps.
3123
+
3124
+ Every figure comes from **your** `rewarded_placements` config — `max_per_day`,
3125
+ `cooldown_seconds`, `max_held` — applied to this player's use of it, as the
3126
+ platform holds them. The platform can hold a placement's daily cap below what
3127
+ you declared, never above it. Nothing here describes the player.
3128
+
3129
+ ⚠️ **It never says whether an ad will play.** Whether one does depends on
3130
+ things your game is deliberately not told. A player can see your button,
3131
+ press it, and get `no_ad`: read that `no_ad` like any other, and never guess
3132
+ a reason.
3133
+
3134
+ ## RewardedStatus.declared
3135
+
3136
+ [RewardedStatus.declared](https://gemarcade.com/developer/api/game#rewardedstatus-declared)
3137
+
3138
+ ```ts
3139
+ readonly declared: boolean
3140
+ ```
3141
+
3142
+ Whether this channel's published config declares the placement.
3143
+
3144
+ `false` for a placement your config does not name, a config that is not
3145
+ promoted to the channel being played, and every answer the platform did not
3146
+ give (see [RewardedStatus.resetsAt](https://gemarcade.com/developer/api/game#rewardedstatus-resetsat)).
3147
+
3148
+ ## RewardedStatus.grantable
3149
+
3150
+ [RewardedStatus.grantable](https://gemarcade.com/developer/api/game#rewardedstatus-grantable)
3151
+
3152
+ ```ts
3153
+ readonly grantable: boolean
3154
+ ```
3155
+
3156
+ Whether pressing your button could pay right now: the platform would grant
3157
+ the reward if an ad is watched or, where no ad serves, under the
3158
+ placement's `without_ads: grant`.
3159
+
3160
+ **Not a promise that an ad will play, and not a promise of a reward.** It
3161
+ is `false` whenever a cap is spent, a cooldown is running, the player holds
3162
+ `max_held`, rewarded is off on this channel, or the arcade running your
3163
+ game does not take ad requests at all.
3164
+
3165
+ ## RewardedStatus.remainingToday
3166
+
3167
+ [RewardedStatus.remainingToday](https://gemarcade.com/developer/api/game#rewardedstatus-remainingtoday)
3168
+
3169
+ ```ts
3170
+ readonly remainingToday?: number
3171
+ ```
3172
+
3173
+ Grants left today under the placement's daily cap, a non-negative integer.
3174
+
3175
+ ABSENT — no key, never a sentinel — when the placement has no daily cap,
3176
+ and on any answer where `declared` is `false`.
3177
+
3178
+ ## RewardedStatus.nextEligibleAt
3179
+
3180
+ [RewardedStatus.nextEligibleAt](https://gemarcade.com/developer/api/game#rewardedstatus-nexteligibleat)
3181
+
3182
+ ```ts
3183
+ readonly nextEligibleAt?: number
3184
+ ```
3185
+
3186
+ When the placement's cooldown ends, in epoch milliseconds — render it with
3187
+ `new Date(nextEligibleAt)`.
3188
+
3189
+ ABSENT when no cooldown is running.
3190
+
3191
+ ## RewardedStatus.heldLimitReached
3192
+
3193
+ [RewardedStatus.heldLimitReached](https://gemarcade.com/developer/api/game#rewardedstatus-heldlimitreached)
3194
+
3195
+ ```ts
3196
+ readonly heldLimitReached: boolean
3197
+ ```
3198
+
3199
+ Whether the player already holds the placement's `max_held` of the item it
3200
+ grants. Spend one first.
3201
+
3202
+ ## RewardedStatus.resetsAt
3203
+
3204
+ [RewardedStatus.resetsAt](https://gemarcade.com/developer/api/game#rewardedstatus-resetsat)
3205
+
3206
+ ```ts
3207
+ readonly resetsAt?: number
3208
+ ```
3209
+
3210
+ When the daily count resets — the next 00:00 UTC — in epoch milliseconds.
3211
+ Take it from here rather than computing midnight in game code.
3212
+
3213
+ ABSENT when the platform gave no answer: no play token yet, a read that
3214
+ failed or took longer than ten seconds, or a platform that does not serve
3215
+ this read. Then the rest of the status reads not declared and not
3216
+ grantable, and the reason went to your `onError` handler.
3217
+
3012
3218
  ## AnalyticsValue
3013
3219
 
3014
3220
  [AnalyticsValue](https://gemarcade.com/developer/api/game#analyticsvalue)
@@ -4863,6 +5069,29 @@ sends only while connected, and only to an arcade that draws a wallet.
4863
5069
  **Returns.** whether it entered the send path — `false` before the bridge is
4864
5070
  up, and `false` for an arcade with no wallet to move.
4865
5071
 
5072
+ ## GemFrame.showPortraitBorders
5073
+
5074
+ [GemFrame.showPortraitBorders](https://gemarcade.com/developer/api/game#gemframe-showportraitborders)
5075
+
5076
+ ```ts
5077
+ showPortraitBorders(name: string | null): boolean
5078
+ ```
5079
+
5080
+ Tell the arcade which of your store page's portrait borders to paint beside
5081
+ your game, or `null` for none.
5082
+
5083
+ Prefer `gem.showPortraitBorders`, which holds your choice until the bridge
5084
+ is up and paces a burst of changes for you. This one sends every call it
5085
+ accepts, only while connected, and only to an arcade that paints borders.
5086
+
5087
+ | Parameter | Type |
5088
+ | --- | --- |
5089
+ | `name` | `string \| null` |
5090
+
5091
+ **Returns.** whether the message left the frame — `false` for a string that is
5092
+ not a border name (with a console warning), before the bridge is up, for
5093
+ an arcade that paints no borders, and past this frame's own limit.
5094
+
4866
5095
  ## GemFrame.setWalletVisible
4867
5096
 
4868
5097
  [GemFrame.setWalletVisible](https://gemarcade.com/developer/api/game#gemframe-setwalletvisible)
@@ -5091,8 +5320,9 @@ draw your own placeholder rather than a broken image. Pass it through
5091
5320
  readonly badges: readonly PlayerBadge[]
5092
5321
  ```
5093
5322
 
5094
- The player's equipped badges, in display order. Always a list, and empty
5095
- until platform achievements equip any.
5323
+ The player's equipped badges, at most two, in the order the player
5324
+ arranged them. Always a list — empty when they wear none, or when their
5325
+ identity is withheld — so draw what is there and key on `itemId`.
5096
5326
 
5097
5327
  ## FriendsPage
5098
5328
 
@@ -5930,7 +6160,7 @@ The answer parsed as JSON.
5930
6160
  [FunctionFailure](https://gemarcade.com/developer/api/game#functionfailure)
5931
6161
 
5932
6162
  ```ts
5933
- type FunctionFailure = "trap" | "memory_exceeded" | "cpu_budget_exceeded" | "host_call_limit"
6163
+ type FunctionFailure = "trap" | "memory_exceeded" | "cpu_budget_exceeded" | "host_call_limit" | "output_too_large" | "argument_too_large"
5934
6164
  ```
5935
6165
 
5936
6166
  Why a call that reached your function did not finish — carried on
@@ -5941,6 +6171,10 @@ Why a call that reached your function did not finish — carried on
5941
6171
  - `memory_exceeded` — past the memory limit.
5942
6172
  - `cpu_budget_exceeded` — past the CPU budget.
5943
6173
  - `host_call_limit` — more platform API calls than one invocation may make.
6174
+ - `output_too_large` — your function's result was over the 1 MiB output cap.
6175
+ - `argument_too_large` — a value your function handed to a platform call
6176
+ (`log`, `call-api`, `download-link` or `upload-link`) was over the 16 MiB
6177
+ per-call limit.
5944
6178
 
5945
6179
  ## GemFunctionError
5946
6180
 
@@ -6158,8 +6392,9 @@ const { accepted, why } = result.json();
6158
6392
  ARCADE_EMBEDDERS: readonly string[]
6159
6393
  ```
6160
6394
 
6161
- The origins the Gem Arcade embeds a game from, which every game accepts
6162
- without naming them: the website, and the Gem Arcade app.
6395
+ The origins the platform embeds a game from, which every game accepts
6396
+ without naming them: the Gem Arcade website, the Meme Games website, and
6397
+ the Gem Arcade app.
6163
6398
 
6164
6399
  You do not pass these. They are always allowed, and
6165
6400
  [GemOptions.allowedEmbedders](https://gemarcade.com/developer/api/game#gemoptions-allowedembedders) adds to them rather than replacing them.
@@ -6188,7 +6423,7 @@ readonly allowedEmbedders?: readonly string[]
6188
6423
  Extra origins allowed to embed your game, **added to** the ones the Gem
6189
6424
  Arcade uses. Most games leave this out.
6190
6425
 
6191
- The website and the Gem Arcade app are always allowed — see
6426
+ The platform's websites and the Gem Arcade app are always allowed — see
6192
6427
  [ARCADE_EMBEDDERS](https://gemarcade.com/developer/api/game#arcade_embedders) — so do not list them. Set this only when a site
6193
6428
  of your own embeds your game.
6194
6429
 
@@ -6883,8 +7118,9 @@ draw your own placeholder rather than a broken image. Pass it through
6883
7118
  readonly badges: readonly PlayerBadge[]
6884
7119
  ```
6885
7120
 
6886
- The player's equipped badges, in display order. Always a list, and empty
6887
- until platform achievements equip any.
7121
+ The player's equipped badges, at most two, in the order the player
7122
+ arranged them. Always a list — empty when they wear none, or when their
7123
+ identity is withheld — so draw what is there and key on `itemId`.
6888
7124
 
6889
7125
  ## GemPrefs
6890
7126
 
@@ -7664,7 +7900,10 @@ resumes it when the promise settles, so you do not pause anything yourself.
7664
7900
  ⚠️ `shown` **grants nothing.** It is not a receipt and not an entitlement,
7665
7901
  and a reward must never be handed out on the strength of it.
7666
7902
 
7667
- You are told no reason for a no, and there is nothing to infer from one.
7903
+ You are told no reason for a no, and there is nothing to infer from one
7904
+ about the player. What you can read is your own economy:
7905
+ `rewardedStatus(placement)` says whether a rewarded placement could pay
7906
+ right now under your own declared caps — never whether an ad will play.
7668
7907
 
7669
7908
  **Guides.** [cross-promotion](https://gemarcade.com/developer/docs/cross-promotion)
7670
7909
 
@@ -8059,6 +8298,51 @@ arcade just said is not sent.
8059
8298
  | --- | --- |
8060
8299
  | `muted` | `boolean` |
8061
8300
 
8301
+ ## Gem.showPortraitBorders
8302
+
8303
+ [Gem.showPortraitBorders](https://gemarcade.com/developer/api/game#gem-showportraitborders)
8304
+
8305
+ ```ts
8306
+ showPortraitBorders(name: string | null): void
8307
+ ```
8308
+
8309
+ Choose which of your store page's **portrait borders** the arcade paints on
8310
+ either side of your game, or `null` for none.
8311
+
8312
+ On a desktop browser, the arcade's desktop app or a television, the arcade
8313
+ shows a portrait-only game as a centred portrait column. With borders
8314
+ declared on your store page (`media.portrait_borders`), it paints one of
8315
+ those images beside the column instead of its own bars. Call this at
8316
+ start-up and on every theme change, with the border that goes with what is
8317
+ on screen:
8318
+
8319
+ ```js
8320
+ const BORDERS = new Set(['meadow', 'alpine']); // the names your store page declares
8321
+
8322
+ function applyTheme(id) {
8323
+ document.body.dataset.theme = id;
8324
+ gem.showPortraitBorders(BORDERS.has(id) ? id : null); // null: a theme with no art
8325
+ }
8326
+ ```
8327
+
8328
+ Until your first call the arcade shows the page's first border. Whether
8329
+ your game is letterboxed at all is decided by your store page's
8330
+ `orientations`, never by this call, so call it unconditionally: before the
8331
+ connection exists your choice is held and sent when it connects, a burst of
8332
+ changes (a picker previewing on hover) reaches the arcade as its final
8333
+ value, and an arcade that paints no borders is simply not told. The arcade
8334
+ owns the change itself — when and how the image swaps — and sends no
8335
+ acknowledgement.
8336
+
8337
+ A name is 1 to 40 characters of `a-z`, `0-9`, `_` and `-`, starting with a
8338
+ letter or digit — the store page's own rule. Anything else is refused with
8339
+ a console warning and never sent; a valid name your page does not declare
8340
+ is ignored by the arcade, which keeps the image it is showing.
8341
+
8342
+ | Parameter | Type |
8343
+ | --- | --- |
8344
+ | `name` | `string \| null` |
8345
+
8062
8346
  ## Gem.cueReward
8063
8347
 
8064
8348
  [Gem.cueReward](https://gemarcade.com/developer/api/game#gem-cuereward)
@@ -9159,7 +9443,8 @@ interface PlayerBadge
9159
9443
  One badge a player has equipped — an icon the platform shows beside their
9160
9444
  name.
9161
9445
 
9162
- Always an empty list today; platform achievements fill it with at most two.
9446
+ A player wears at most two, in the order they arranged them. Each is an icon
9447
+ they own — an achievement in any game may have awarded it.
9163
9448
 
9164
9449
  ## PlayerBadge.itemId
9165
9450
 
@@ -9283,8 +9568,9 @@ draw your own placeholder rather than a broken image. Pass it through
9283
9568
  readonly badges: readonly PlayerBadge[]
9284
9569
  ```
9285
9570
 
9286
- The player's equipped badges, in display order. Always a list, and empty
9287
- until platform achievements equip any.
9571
+ The player's equipped badges, at most two, in the order the player
9572
+ arranged them. Always a list — empty when they wear none, or when their
9573
+ identity is withheld — so draw what is there and key on `itemId`.
9288
9574
 
9289
9575
  ## mapIdentity
9290
9576
 
@@ -11831,8 +12117,9 @@ draw your own placeholder rather than a broken image. Pass it through
11831
12117
  readonly badges: readonly PlayerBadge[]
11832
12118
  ```
11833
12119
 
11834
- The player's equipped badges, in display order. Always a list, and empty
11835
- until platform achievements equip any.
12120
+ The player's equipped badges, at most two, in the order the player
12121
+ arranged them. Always a list — empty when they wear none, or when their
12122
+ identity is withheld — so draw what is there and key on `itemId`.
11836
12123
 
11837
12124
  ## LeaderboardEntry.rank
11838
12125
 
@@ -13272,8 +13559,9 @@ draw your own placeholder rather than a broken image. Pass it through
13272
13559
  readonly badges: readonly PlayerBadge[]
13273
13560
  ```
13274
13561
 
13275
- The player's equipped badges, in display order. Always a list, and empty
13276
- until platform achievements equip any.
13562
+ The player's equipped badges, at most two, in the order the player
13563
+ arranged them. Always a list — empty when they wear none, or when their
13564
+ identity is withheld — so draw what is there and key on `itemId`.
13277
13565
 
13278
13566
  ## PlayerProfile.playerId
13279
13567
 
@@ -15306,8 +15594,9 @@ draw your own placeholder rather than a broken image. Pass it through
15306
15594
  readonly badges: readonly PlayerBadge[]
15307
15595
  ```
15308
15596
 
15309
- The player's equipped badges, in display order. Always a list, and empty
15310
- until platform achievements equip any.
15597
+ The player's equipped badges, at most two, in the order the player
15598
+ arranged them. Always a list — empty when they wear none, or when their
15599
+ identity is withheld — so draw what is there and key on `itemId`.
15311
15600
 
15312
15601
  ## RoomMember.playerId
15313
15602
 
@@ -16070,8 +16359,9 @@ draw your own placeholder rather than a broken image. Pass it through
16070
16359
  readonly badges: readonly PlayerBadge[]
16071
16360
  ```
16072
16361
 
16073
- The player's equipped badges, in display order. Always a list, and empty
16074
- until platform achievements equip any.
16362
+ The player's equipped badges, at most two, in the order the player
16363
+ arranged them. Always a list — empty when they wear none, or when their
16364
+ identity is withheld — so draw what is there and key on `itemId`.
16075
16365
 
16076
16366
  ## ShowcaseFriendEntry.playerId
16077
16367
 
@@ -1,6 +1,6 @@
1
1
  # The server surface — API reference
2
2
 
3
- Generated from `@indiegems/gem-web-sdk/sdk` v8.0.0. Every symbol below links to its page on the Gem Arcade developer site.
3
+ Generated from `@indiegems/gem-web-sdk/sdk` v9.0.0. Every symbol below links to its page on the Gem Arcade developer site.
4
4
 
5
5
  `@indiegems/gem-web-sdk/sdk` is the one import a game writes, wherever its code runs. `@indiegems/gem-web-sdk/server` exports exactly the symbols on this page and nothing else, for a build that wants only this half.
6
6
 
@@ -1535,7 +1535,7 @@ The answer parsed as JSON.
1535
1535
  [FunctionFailure](https://gemarcade.com/developer/api/server#functionfailure)
1536
1536
 
1537
1537
  ```ts
1538
- type FunctionFailure = "trap" | "memory_exceeded" | "cpu_budget_exceeded" | "host_call_limit"
1538
+ type FunctionFailure = "trap" | "memory_exceeded" | "cpu_budget_exceeded" | "host_call_limit" | "output_too_large" | "argument_too_large"
1539
1539
  ```
1540
1540
 
1541
1541
  Why a call that reached your function did not finish — carried on
@@ -1546,6 +1546,10 @@ Why a call that reached your function did not finish — carried on
1546
1546
  - `memory_exceeded` — past the memory limit.
1547
1547
  - `cpu_budget_exceeded` — past the CPU budget.
1548
1548
  - `host_call_limit` — more platform API calls than one invocation may make.
1549
+ - `output_too_large` — your function's result was over the 1 MiB output cap.
1550
+ - `argument_too_large` — a value your function handed to a platform call
1551
+ (`log`, `call-api`, `download-link` or `upload-link`) was over the 16 MiB
1552
+ per-call limit.
1549
1553
 
1550
1554
  ## GemFunctionError
1551
1555