@indiegems/gem-web-sdk 13.1.0 → 13.2.1

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.
package/dist/sdk.d.ts CHANGED
@@ -87,6 +87,7 @@ interface RequestOptions {
87
87
  readonly keepalive?: boolean;
88
88
  readonly ifMatch?: string;
89
89
  readonly replaySafe?: boolean;
90
+ readonly retryRateLimited?: boolean;
90
91
  }
91
92
  interface VersionedGetOptions {
92
93
  readonly signal?: AbortSignal;
@@ -2264,6 +2265,12 @@ interface GemAdResult {
2264
2265
  readonly requestId: string;
2265
2266
  readonly outcome: "shown" | "no_ad" | "unavailable";
2266
2267
  }
2268
+ interface GemMomentResult {
2269
+ readonly gem: typeof PROTOCOL;
2270
+ readonly type: "gem.momentResult";
2271
+ readonly requestId: string;
2272
+ readonly shown: boolean;
2273
+ }
2267
2274
  interface GemSignInResult {
2268
2275
  readonly gem: typeof PROTOCOL;
2269
2276
  readonly type: "gem.signInResult";
@@ -4500,6 +4507,7 @@ interface FrameCallbacks {
4500
4507
  onDeviceSettings?(msg: GemDeviceSettings): void;
4501
4508
  onDeviceSettingsResult?(msg: GemDeviceSettingsResult): void;
4502
4509
  onAdResult?(msg: GemAdResult): void;
4510
+ onMomentResult?(msg: GemMomentResult): void;
4503
4511
  onSignInResult?(msg: GemSignInResult): void;
4504
4512
  onFriendChallengeResult?(msg: GemFriendChallengeResult): void;
4505
4513
  onError?(code: GemErrorCode, message?: string): void;
@@ -4574,6 +4582,8 @@ declare class GemFrame {
4574
4582
  requestAvatar3dPurchase(requestId: string, itemId: string, currencySlug: string): boolean;
4575
4583
  putDeviceSettings(requestId: string, settings: Readonly<Record<string, unknown>>): boolean;
4576
4584
  requestAd(requestId: string, placement: string, format: "interstitial" | "rewarded"): boolean;
4585
+ signalMoment(requestId: string, kind: string, placement?: string): boolean;
4586
+ readyCalled(): boolean;
4577
4587
  requestSignIn(requestId: string): boolean;
4578
4588
  requestFriendChallenge(requestId: string, playerId: string, template: string, params?: Readonly<Record<string, string | number>>): boolean;
4579
4589
  close(): void;
@@ -4651,6 +4661,17 @@ interface AdsApi {
4651
4661
  requestAd(req: AdRequest): Promise<AdOutcome>;
4652
4662
  rewardedStatus(placement: string): Promise<RewardedStatus>;
4653
4663
  }
4664
+ type MomentKind = "level_end" | "run_end" | "match_end" | "menu_return" | "other_break";
4665
+ interface MomentSignal {
4666
+ kind: MomentKind;
4667
+ placement?: string;
4668
+ }
4669
+ interface MomentResult {
4670
+ readonly shown: boolean;
4671
+ }
4672
+ interface MomentsApi {
4673
+ signal(moment: MomentSignal): Promise<MomentResult>;
4674
+ }
4654
4675
  type DeviceSettings = Readonly<Record<string, unknown>>;
4655
4676
  type DeviceSettingsPutOutcome = "stored" | "too_large" | "unavailable";
4656
4677
  interface DeviceSettingsApi {
@@ -4785,6 +4806,7 @@ interface Gem {
4785
4806
  readonly gameConfig: GameConfigApi;
4786
4807
  readonly purchases: PurchaseApi;
4787
4808
  readonly deviceSettings: DeviceSettingsApi;
4809
+ readonly moments: MomentsApi;
4788
4810
  readonly ads: AdsApi;
4789
4811
  readonly account: AccountApi;
4790
4812
  readonly share: ShareApi;
@@ -4840,4 +4862,4 @@ interface ExitAnalytics {
4840
4862
  }
4841
4863
  declare function leaveAndExit(rooms: ExitRooms, bridge: ExitBridge | null, reason?: string, analytics?: ExitAnalytics): Promise<void>;
4842
4864
  declare function createGem(options?: GemOptions): Gem;
4843
- export { ARCADE_EMBEDDERS, AVATAR_RIGS, type AccountApi, type AdFormat, type AdOutcome, type AdOutcomeKind, type AdRequest, type AdsApi, AnalyticsApi, type AnalyticsFlushOptions, type AnalyticsFlushResult, type AnalyticsOptions, type AnalyticsRejection$1 as AnalyticsRejection, type AnalyticsValue, AuthError, type AuthFailure, type AuthState, type Avatar3dFamily, type Avatar3dOffer, type Avatar3dPrice, type Avatar3dPurchaseRequest, type AvatarRigNode, type AvatarRigs, type Avatars3dApi, type Base64String, type BootMark, type BootTimeline, type BroadcastOptions, type BytesResponse, CONDITIONAL_HEADERS, type Challenge, type ChallengeWindow, ChallengesApi, type ClientOptions, type Clock, type ConditionalRequestHeaders, type ConfiguredPurchasePayment, type ConfiguredPurchaseRequest, type ConfiguredPurchaseResult, type ControlsApi, DEVICE_SETTINGS_MAX_BYTES, DedicatedServerError, type DedicatedServerFailure, type DedicatedServerSession, type DevToolsOpener, type DeviceSettings, type DeviceSettingsApi, type DeviceSettingsPutOutcome, type DirectPurchaseRequest, type DirectPurchaseResult, type EarnAllowance, type EarnAllowanceEvent, type EarnAllowancePeriod, type EarnAllowanceReadOptions, type EntityLeaderboard, type EntityLeaderboardEntry, type EntityLeaderboardOptions, type EntityType$1 as EntityType, type ExitAnalytics, type ExitBridge, type ExitRooms, type FrameState, type Friend, type FriendChallenge, FriendChallengeError, type FriendChallengeErrorCode, type FriendChallengeOutcome, type FriendChallengeOutcomeKind, type FriendChallengeRequest, type FriendChallengesPage, FriendsApi, type FriendsPage, type FunctionFailure, type FunctionInvokeOptions, type FunctionResult, FunctionsApi, type GameApi, type GameConfig, type GameMenu, type GameMenuItem, type GameServerLink, type Gem, GemApiError, GemClient, type GemCohort, type GemCohorts, GemConfigError, type GemCorner, type GemDevTools, type GemErrorReason, GemFrame, GemFunctionError, type GemGraphicsQuality, type GemIdentity, type GemInsets, type GemLook, type GemLookBase, type GemLookDelta, type GemLookOptions, type GemLookSpeed, type GemMatchApi, type GemMatchServer, type GemMatchServerModule, type GemOptions, type GemPlayer, type GemPrefs, type GemRecommendedSettings, type GemReservedArea, GemSaveError, type GemServer, type GemServerBundle, type GemServerFactory, type GemServerHost, type GemServerHostProfile, type GemServerIdentity, type GemServerLifecycle, type GemServerOptions, type GemServerProfile, type GemTerminationNotice, GemUnframedError, type GemViewport, type GetLeaderboardOptions, type HostTokenSource, type HostTokenSourceOptions, type JoinWelcome, type Leaderboard, type LeaderboardEntry, type LeaderboardPeriod, LeaderboardsApi, type ListChallengesOptions, type ListFriendChallengesOptions, type ListFriendsOptions, MAX_PERIODS_BACK, type MatchBackfillState, type MatchConnectOptions, type MatchConnection, type MatchHosting, type MatchHostingMode, type MatchPlayerReport, type MatchPlayerReportResult, type MatchServeOptions, type MatchTransport, type MessageMeta, type MultiTransportSession, PLATFORM_EVENTS, type PeriodOptions, PlatformApi, PlatformCurrenciesApi, type PlatformCurrency, type PlatformCurrencyAccess, type PlatformCurrencyPurchasePayment, type PlatformCurrencyPurchaseRequest, type PlatformCurrencyPurchaseResult, type PlatformCurrencyReadOptions, PlatformEarnAllowanceApi, type PlatformEventName, type PlayerBadge$1 as PlayerBadge, type PlayerBoost, type PlayerBoostMultiplier, type PlayerIdentity, type PlayerProfile, PlayerText, type ProfileCallOptions, type ProfileShowcaseEntry$1 as ProfileShowcaseEntry, ProfilesApi, type PurchaseApi, type PurchaseOptions, type PurchaseOutcome, PurchaseRefusal, type PurchaseRefusalCode, type PurchaseRequest, type PutBytesOptions, type PutSaveOptions, type RankedRow, type RecipeAvailability, type RecipeLock, type RequestOptions, type RewardCue, type RewardedStatus, type RoomArgValue, type SavePage, type SaveRecord, type SendOptions$1 as SendOptions, type ServerApi, type ServerBundleHost, type ServerTokenGrant, type SessionEndReason, type ShareApi, type ShareRequest, ShowcaseApi, type ShowcaseCallOptions, type ShowcaseFriendEntry, type ShowcaseFriends, type ShowcaseValue, type SignInOutcome, type SignInOutcomeKind, type SnapshotCurrency, type SnapshotItem, type TokenGrant, type Transport, type TransportAdvert, type TransportKind, TransportRefusal, type TransportRequest, type TransportResponse, UNRELIABLE_CHANNEL_LABEL, type VersionedGetOptions, type VersionedResponse, type VibrationApi, type WalletVisibleOptions, type WornAvatar3d$1 as WornAvatar3d, asPlayerText, createBootTimeline, createGem, createGemServer, createHostTokenSource, createMatchServer, defineServer, leaveAndExit, mapIdentity, markOnResolve, platformIconUrl, presentJoinCredential, readTemplateTransports, readTransportAdvert, reportOnResolve, systemClock, text, toBase64, webTransportAvailable };
4865
+ export { ARCADE_EMBEDDERS, AVATAR_RIGS, type AccountApi, type AdFormat, type AdOutcome, type AdOutcomeKind, type AdRequest, type AdsApi, AnalyticsApi, type AnalyticsFlushOptions, type AnalyticsFlushResult, type AnalyticsOptions, type AnalyticsRejection$1 as AnalyticsRejection, type AnalyticsValue, AuthError, type AuthFailure, type AuthState, type Avatar3dFamily, type Avatar3dOffer, type Avatar3dPrice, type Avatar3dPurchaseRequest, type AvatarRigNode, type AvatarRigs, type Avatars3dApi, type Base64String, type BootMark, type BootTimeline, type BroadcastOptions, type BytesResponse, CONDITIONAL_HEADERS, type Challenge, type ChallengeWindow, ChallengesApi, type ClientOptions, type Clock, type ConditionalRequestHeaders, type ConfiguredPurchasePayment, type ConfiguredPurchaseRequest, type ConfiguredPurchaseResult, type ControlsApi, DEVICE_SETTINGS_MAX_BYTES, DedicatedServerError, type DedicatedServerFailure, type DedicatedServerSession, type DevToolsOpener, type DeviceSettings, type DeviceSettingsApi, type DeviceSettingsPutOutcome, type DirectPurchaseRequest, type DirectPurchaseResult, type EarnAllowance, type EarnAllowanceEvent, type EarnAllowancePeriod, type EarnAllowanceReadOptions, type EntityLeaderboard, type EntityLeaderboardEntry, type EntityLeaderboardOptions, type EntityType$1 as EntityType, type ExitAnalytics, type ExitBridge, type ExitRooms, type FrameState, type Friend, type FriendChallenge, FriendChallengeError, type FriendChallengeErrorCode, type FriendChallengeOutcome, type FriendChallengeOutcomeKind, type FriendChallengeRequest, type FriendChallengesPage, FriendsApi, type FriendsPage, type FunctionFailure, type FunctionInvokeOptions, type FunctionResult, FunctionsApi, type GameApi, type GameConfig, type GameMenu, type GameMenuItem, type GameServerLink, type Gem, GemApiError, GemClient, type GemCohort, type GemCohorts, GemConfigError, type GemCorner, type GemDevTools, type GemErrorReason, GemFrame, GemFunctionError, type GemGraphicsQuality, type GemIdentity, type GemInsets, type GemLook, type GemLookBase, type GemLookDelta, type GemLookOptions, type GemLookSpeed, type GemMatchApi, type GemMatchServer, type GemMatchServerModule, type GemOptions, type GemPlayer, type GemPrefs, type GemRecommendedSettings, type GemReservedArea, GemSaveError, type GemServer, type GemServerBundle, type GemServerFactory, type GemServerHost, type GemServerHostProfile, type GemServerIdentity, type GemServerLifecycle, type GemServerOptions, type GemServerProfile, type GemTerminationNotice, GemUnframedError, type GemViewport, type GetLeaderboardOptions, type HostTokenSource, type HostTokenSourceOptions, type JoinWelcome, type Leaderboard, type LeaderboardEntry, type LeaderboardPeriod, LeaderboardsApi, type ListChallengesOptions, type ListFriendChallengesOptions, type ListFriendsOptions, MAX_PERIODS_BACK, type MatchBackfillState, type MatchConnectOptions, type MatchConnection, type MatchHosting, type MatchHostingMode, type MatchPlayerReport, type MatchPlayerReportResult, type MatchServeOptions, type MatchTransport, type MessageMeta, type MomentKind, type MomentResult, type MomentSignal, type MomentsApi, type MultiTransportSession, PLATFORM_EVENTS, type PeriodOptions, PlatformApi, PlatformCurrenciesApi, type PlatformCurrency, type PlatformCurrencyAccess, type PlatformCurrencyPurchasePayment, type PlatformCurrencyPurchaseRequest, type PlatformCurrencyPurchaseResult, type PlatformCurrencyReadOptions, PlatformEarnAllowanceApi, type PlatformEventName, type PlayerBadge$1 as PlayerBadge, type PlayerBoost, type PlayerBoostMultiplier, type PlayerIdentity, type PlayerProfile, PlayerText, type ProfileCallOptions, type ProfileShowcaseEntry$1 as ProfileShowcaseEntry, ProfilesApi, type PurchaseApi, type PurchaseOptions, type PurchaseOutcome, PurchaseRefusal, type PurchaseRefusalCode, type PurchaseRequest, type PutBytesOptions, type PutSaveOptions, type RankedRow, type RecipeAvailability, type RecipeLock, type RequestOptions, type RewardCue, type RewardedStatus, type RoomArgValue, type SavePage, type SaveRecord, type SendOptions$1 as SendOptions, type ServerApi, type ServerBundleHost, type ServerTokenGrant, type SessionEndReason, type ShareApi, type ShareRequest, ShowcaseApi, type ShowcaseCallOptions, type ShowcaseFriendEntry, type ShowcaseFriends, type ShowcaseValue, type SignInOutcome, type SignInOutcomeKind, type SnapshotCurrency, type SnapshotItem, type TokenGrant, type Transport, type TransportAdvert, type TransportKind, TransportRefusal, type TransportRequest, type TransportResponse, UNRELIABLE_CHANNEL_LABEL, type VersionedGetOptions, type VersionedResponse, type VibrationApi, type WalletVisibleOptions, type WornAvatar3d$1 as WornAvatar3d, asPlayerText, createBootTimeline, createGem, createGemServer, createHostTokenSource, createMatchServer, defineServer, leaveAndExit, mapIdentity, markOnResolve, platformIconUrl, presentJoinCredential, readTemplateTransports, readTransportAdvert, reportOnResolve, systemClock, text, toBase64, webTransportAvailable };
@@ -40,6 +40,7 @@ interface RequestOptions {
40
40
  readonly keepalive?: boolean;
41
41
  readonly ifMatch?: string;
42
42
  readonly replaySafe?: boolean;
43
+ readonly retryRateLimited?: boolean;
43
44
  }
44
45
  interface VersionedGetOptions {
45
46
  readonly signal?: AbortSignal;
package/dist/server.d.ts CHANGED
@@ -69,6 +69,7 @@ interface RequestOptions {
69
69
  readonly keepalive?: boolean;
70
70
  readonly ifMatch?: string;
71
71
  readonly replaySafe?: boolean;
72
+ readonly retryRateLimited?: boolean;
72
73
  }
73
74
  interface VersionedGetOptions {
74
75
  readonly signal?: AbortSignal;
package/docs/analytics.md CHANGED
@@ -11,9 +11,9 @@ you design around them:
11
11
  - **Nothing is recorded that you did not declare.** There is no free-text property. Every event,
12
12
  every dimension and every value a dimension may take is published in your game config ahead of
13
13
  time, and anything else is refused per event rather than stored.
14
- - **Detail older than 30 days is gone.** Raw events live 30 days. The daily counters built from
14
+ - **Detail older than 60 days is gone.** Raw events live 60 days. The daily counters built from
15
15
  them live 400. A question you can only answer by reading individual events is a question you can
16
- only ask about the last month.
16
+ only ask about the last two months.
17
17
 
18
18
  What you record here is **yours**. It feeds your own reporting and nothing the platform treats as
19
19
  authoritative — see [section 8](#8-what-analytics-is-not) before you attach a reward to it.
@@ -253,7 +253,10 @@ Two results are not refusals and should not be treated as one:
253
253
  being counted twice.
254
254
 
255
255
  A rate limit or a temporarily unreachable backend **keeps the queue** and rejects the `flush`
256
- promise, so the next send carries the same events. A refusal that would fail identically forever
256
+ promise, so the next send carries the same events. The SDK's own sends then wait for the longer
257
+ of the platform's `Retry-After` and a cooldown that starts at 10 seconds and doubles with each
258
+ refusal in a row, up to five minutes; the send on the way out of the page goes regardless. Only one
259
+ `flush` is on the wire at a time. A refusal that would fail identically forever
257
260
  discards its batch instead, because a queue that can never empty fills up and starts dropping the
258
261
  events that would explain the problem.
259
262
 
@@ -1,6 +1,6 @@
1
1
  # The function surface — API reference
2
2
 
3
- Generated from `@indiegems/gem-web-sdk/function` v13.1.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/function` v13.2.1, 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/function` exports exactly the symbols on this page and nothing else.
6
6
 
@@ -8,6 +8,8 @@ Generated from `@indiegems/gem-web-sdk/function` v13.1.0, the version of the pac
8
8
  import { createGem } from '@indiegems/gem-web-sdk/function';
9
9
  ```
10
10
 
11
+ ⚠️ **JavaScript functions are disabled for now, due to performance issues.** Uploading a new JavaScript function is refused with `400 js_functions_disabled`: write a new function in Rust, as [serverless-functions](../serverless-functions.md#3-writing-one) shows. A JavaScript function already published keeps running, and this page is what it imports.
12
+
11
13
  A function imports the same package your game already depends on, written out in [getting-started](../getting-started.md#3-add-the-sdk) — the only place the install is written out. Your function’s build bundles this module, so the version your function runs is the one your `package.json` names: pin the version your game pins. A function published importing the SDK from the engine runs the engine’s built-in copy instead; [serverless-functions-js](../serverless-functions-js.md) says what that means.
12
14
 
13
15
  <a id="analyticsvalue"></a>
@@ -243,7 +245,7 @@ is what says which events exist, which dimensions each has and which values
243
245
  each dimension may take. An undeclared name or key is not stored — it comes
244
246
  back as a rejection, which the SDK prints to the console during development.
245
247
 
246
- ⚠️ **Detail older than 30 days is gone.** Raw events live 30 days; the daily
248
+ ⚠️ **Detail older than 60 days is gone.** Raw events live 60 days; the daily
247
249
  counters live 400. Anything you want to be able to ask about a year from now
248
250
  has to be a declared dimension today.
249
251
 
@@ -355,9 +357,14 @@ the page.
355
357
  A refusal that could clear on its own — a 429 from the rate limiter, a 503
356
358
  while the platform's idempotency store is unreachable — KEEPS the queue
357
359
  and rejects, so the next flush sends the same events rather than losing
358
- them. A refusal that cannot clear discards the batch it was sent for,
360
+ them. The SDK's own flushes then hold off for the longer of the server's
361
+ `Retry-After` and a cooldown that doubles from 10 seconds, up to five
362
+ minutes. A refusal that cannot clear discards the batch it was sent for,
359
363
  because resending it can only fail identically.
360
364
 
365
+ One flush at a time: called while another is still on the wire, this
366
+ resolves at once with nothing sent, and what is queued goes out after it.
367
+
361
368
  | Parameter | Type |
362
369
  | --- | --- |
363
370
  | `options` | `AnalyticsFlushOptions` |
@@ -666,6 +673,22 @@ It buys the same budget a read gets — see [ClientOptions.maxRetries](#clientop
666
673
  final: only the failures [GemApiError.retryable](#gemapierror-retryable) describes, and a
667
674
  connection that died with no answer, are attempted again.
668
675
 
676
+ <a id="requestoptions-retryratelimited"></a>
677
+
678
+ ## RequestOptions.retryRateLimited
679
+
680
+ ```ts
681
+ readonly retryRateLimited?: boolean
682
+ ```
683
+
684
+ Set `false` to make a 429 final for this call instead of waiting out
685
+ `Retry-After` and trying again.
686
+
687
+ For a caller that keeps its own queue and its own cooldown, such as a
688
+ batch sender on a timer: retrying inside the call only spends more of the
689
+ same limit, and holds the caller's queue while it does. Every other
690
+ failure is retried exactly as before. Defaults to `true`.
691
+
669
692
  <a id="versionedgetoptions"></a>
670
693
 
671
694
  ## VersionedGetOptions
@@ -1,6 +1,6 @@
1
1
  # The browser surface — API reference
2
2
 
3
- Generated from `@indiegems/gem-web-sdk/sdk` v13.1.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` v13.2.1, 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
 
@@ -286,7 +286,7 @@ the button could pay right now is [AdsApi.rewardedStatus](#adsapi-rewardedstatus
286
286
 
287
287
  Absent when your game declares none.
288
288
 
289
- **Guides.** [cross-promotion](../cross-promotion.md)
289
+ **Guides.** [rewarded-ads](../rewarded-ads.md)
290
290
 
291
291
  <a id="gameconfig-version"></a>
292
292
 
@@ -2902,6 +2902,9 @@ type AdFormat = "interstitial" | "rewarded"
2902
2902
  Which kind of break this is.
2903
2903
 
2904
2904
  `interstitial` is a panel the player dismisses and nothing follows from.
2905
+ **Deprecated:** signal a platform moment with `gem.moments.signal` instead.
2906
+ It keeps working — the arcade treats it as a platform moment — and `14.0.0`
2907
+ is the earliest release that may remove it (`docs/deprecation-policy.md`).
2905
2908
 
2906
2909
  `rewarded` is a button the player pressed for a reward, such as a revive or
2907
2910
  an extra attempt, that your game config declares for the placement in
@@ -3021,7 +3024,7 @@ Which of the three answers this is.
3021
3024
  interface AdsApi
3022
3025
  ```
3023
3026
 
3024
- The cross-promotion surface. `gem.ads` is one of these.
3027
+ Rewarded ads, and the deprecated interstitial ask. `gem.ads` is one of these.
3025
3028
 
3026
3029
  <a id="adsapi-requestad"></a>
3027
3030
 
@@ -3033,6 +3036,10 @@ requestAd(req: AdRequest): Promise<AdOutcome>
3033
3036
 
3034
3037
  Ask whether the arcade has anything to show at this break in play.
3035
3038
 
3039
+ **For a break in play, signal a platform moment with
3040
+ `gem.moments.signal` instead: `format: 'interstitial'` is deprecated.** It
3041
+ keeps working until at least `14.0.0`. A `rewarded` ask is unchanged.
3042
+
3036
3043
  NOTHING REJECTS, and the answer is usually `no_ad`. Every local refusal —
3037
3044
  an arcade that does not do this at all, a call while another is still in
3038
3045
  flight, a `placement` this module will not send — resolves `no_ad` rather
@@ -3070,16 +3077,6 @@ An `interstitial` needs no press.
3070
3077
 
3071
3078
  **Returns.** The arcade's answer. Always resolves; never rejects.
3072
3079
 
3073
- ```ts
3074
- const { outcome } = await gem.ads.requestAd({
3075
- placement: 'level-complete',
3076
- format: 'interstitial',
3077
- });
3078
- // `no_ad` is the ordinary answer. There is nothing to show and nothing
3079
- // to draw; carry straight on to the next level.
3080
- startNextLevel();
3081
- ```
3082
-
3083
3080
  ```ts
3084
3081
  // A rewarded ask, from the button's own click: the ask comes first, and
3085
3082
  // the inventory read comes after it.
@@ -3142,7 +3139,7 @@ if (status.nextEligibleAt !== undefined) {
3142
3139
  }
3143
3140
  ```
3144
3141
 
3145
- **Guides.** [cross-promotion](../cross-promotion.md)
3142
+ **Guides.** [rewarded-ads](../rewarded-ads.md)
3146
3143
 
3147
3144
  <a id="rewardedstatus"></a>
3148
3145
 
@@ -3482,7 +3479,7 @@ is what says which events exist, which dimensions each has and which values
3482
3479
  each dimension may take. An undeclared name or key is not stored — it comes
3483
3480
  back as a rejection, which the SDK prints to the console during development.
3484
3481
 
3485
- ⚠️ **Detail older than 30 days is gone.** Raw events live 30 days; the daily
3482
+ ⚠️ **Detail older than 60 days is gone.** Raw events live 60 days; the daily
3486
3483
  counters live 400. Anything you want to be able to ask about a year from now
3487
3484
  has to be a declared dimension today.
3488
3485
 
@@ -3594,9 +3591,14 @@ the page.
3594
3591
  A refusal that could clear on its own — a 429 from the rate limiter, a 503
3595
3592
  while the platform's idempotency store is unreachable — KEEPS the queue
3596
3593
  and rejects, so the next flush sends the same events rather than losing
3597
- them. A refusal that cannot clear discards the batch it was sent for,
3594
+ them. The SDK's own flushes then hold off for the longer of the server's
3595
+ `Retry-After` and a cooldown that doubles from 10 seconds, up to five
3596
+ minutes. A refusal that cannot clear discards the batch it was sent for,
3598
3597
  because resending it can only fail identically.
3599
3598
 
3599
+ One flush at a time: called while another is still on the wire, this
3600
+ resolves at once with nothing sent, and what is queued goes out after it.
3601
+
3600
3602
  | Parameter | Type |
3601
3603
  | --- | --- |
3602
3604
  | `options` | `AnalyticsFlushOptions` |
@@ -7219,9 +7221,8 @@ const { accepted, why } = result.json();
7219
7221
  ARCADE_EMBEDDERS: readonly string[]
7220
7222
  ```
7221
7223
 
7222
- The origins the platform embeds a game from, which every game accepts
7223
- without naming them: the Gem Arcade website, the Meme Games website, and
7224
- the Gem Arcade app.
7224
+ The origins the Gem Arcade embeds a game from, which every game accepts
7225
+ without naming them: the website, and the Gem Arcade app.
7225
7226
 
7226
7227
  You do not pass these. They are always allowed, and
7227
7228
  [GemOptions.allowedEmbedders](#gemoptions-allowedembedders) adds to them rather than replacing them.
@@ -7250,7 +7251,7 @@ readonly allowedEmbedders?: readonly string[]
7250
7251
  Extra origins allowed to embed your game, **added to** the ones the Gem
7251
7252
  Arcade uses. Most games leave this out.
7252
7253
 
7253
- The platform's websites and the Gem Arcade app are always allowed — see
7254
+ The website and the Gem Arcade app are always allowed — see
7254
7255
  [ARCADE_EMBEDDERS](#arcade_embedders) — so do not list them. Set this only when a site
7255
7256
  of your own embeds your game.
7256
7257
 
@@ -8951,6 +8952,25 @@ every account on the device shares it, and it can vanish at any time. A
8951
8952
  game must boot correctly from `null`. Progress belongs in `gem.me.saves`;
8952
8953
  a setting that must follow the player belongs in `gem.me.settings`.
8953
8954
 
8955
+ <a id="gem-moments"></a>
8956
+
8957
+ ## Gem.moments
8958
+
8959
+ ```ts
8960
+ readonly moments: MomentsApi
8961
+ ```
8962
+
8963
+ Platform moments — **every game, at every break.** Signal one from the
8964
+ player's press that leaves a natural break in play (Next, Retry, Back to
8965
+ menu), and the arcade may show one of its own offers over your paused
8966
+ game. It usually shows nothing.
8967
+
8968
+ Required by the release checklist at every important break, whether or
8969
+ not your game has a shop of its own. Never resolves a reason, never
8970
+ rejects, and `shown` grants nothing.
8971
+
8972
+ **Guides.** [platform-moments](../platform-moments.md)
8973
+
8954
8974
  <a id="gem-ads"></a>
8955
8975
 
8956
8976
  ## Gem.ads
@@ -8959,13 +8979,15 @@ a setting that must follow the player belongs in `gem.me.settings`.
8959
8979
  readonly ads: AdsApi
8960
8980
  ```
8961
8981
 
8962
- One full-screen cross-promotion at a natural break in play, rendered by
8963
- the arcade over your paused game.
8982
+ Rewarded ads, and the deprecated interstitial ask.
8964
8983
 
8965
- Ask at a break the player already expects — a level ending, a round
8966
- finishing — never mid-input. **The answer is usually no, and that is
8967
- ordinary rather than a failure:** carry straight on, and draw no
8968
- placeholder while you wait.
8984
+ **`format: 'interstitial'` is deprecated: signal a platform moment with
8985
+ `gem.moments.signal` instead.** It keeps working until at least `14.0.0`
8986
+ (`docs/deprecation-policy.md`); the arcade treats it as a platform moment.
8987
+
8988
+ A rewarded ask comes from the player's press on your own button. **The
8989
+ answer is usually no, and that is ordinary rather than a failure:** carry
8990
+ straight on, and draw no placeholder while you wait.
8969
8991
 
8970
8992
  Nothing here rejects. The arcade pauses your game while a panel is up and
8971
8993
  resumes it when the promise settles, so you do not pause anything yourself.
@@ -8978,7 +9000,7 @@ about the player. What you can read is your own economy:
8978
9000
  `rewardedStatus(placement)` says whether a rewarded placement could pay
8979
9001
  right now under your own declared caps — never whether an ad will play.
8980
9002
 
8981
- **Guides.** [cross-promotion](../cross-promotion.md)
9003
+ **Guides.** [rewarded-ads](../rewarded-ads.md)
8982
9004
 
8983
9005
  <a id="gem-account"></a>
8984
9006
 
@@ -11032,6 +11054,152 @@ so portraits appear only against production and loopback.
11032
11054
  | --- | --- |
11033
11055
  | `url` | `string \| undefined` |
11034
11056
 
11057
+ <a id="momentkind"></a>
11058
+
11059
+ ## MomentKind
11060
+
11061
+ ```ts
11062
+ type MomentKind = "level_end" | "run_end" | "match_end" | "menu_return" | "other_break"
11063
+ ```
11064
+
11065
+ Which kind of break a platform moment is. A closed set — there will not be a
11066
+ sixth:
11067
+
11068
+ - `level_end` — a level, stage, wave or puzzle finished, won or lost.
11069
+ - `run_end` — game over, death, or a run banked, in an endless, arcade or
11070
+ roguelike game.
11071
+ - `match_end` — a multiplayer round or match finished and the player is
11072
+ leaving its results.
11073
+ - `menu_return` — the player chose to go back to the title, a main menu or
11074
+ level select. A return, never the first arrival.
11075
+ - `other_break` — any other natural stop: a story chapter, a between-waves
11076
+ shop.
11077
+
11078
+ It is a label, for the arcade's analytics, the dev host's log and the release
11079
+ checklist. It never changes what the arcade decides for your game.
11080
+
11081
+ **Guides.** [platform-moments](../platform-moments.md)
11082
+
11083
+ <a id="momentsignal"></a>
11084
+
11085
+ ## MomentSignal
11086
+
11087
+ ```ts
11088
+ interface MomentSignal
11089
+ ```
11090
+
11091
+ A platform moment: which kind of break the player is leaving, and optionally
11092
+ your own name for the screen.
11093
+
11094
+ <a id="momentsignal-kind"></a>
11095
+
11096
+ ## MomentSignal.kind
11097
+
11098
+ ```ts
11099
+ kind: MomentKind
11100
+ ```
11101
+
11102
+ Which kind of break this is. Required.
11103
+
11104
+ <a id="momentsignal-placement"></a>
11105
+
11106
+ ## MomentSignal.placement
11107
+
11108
+ ```ts
11109
+ placement?: string
11110
+ ```
11111
+
11112
+ Optional: your own name for the screen the player is leaving —
11113
+ `world-1-3`, `results`. **1 to 32 characters of `a-z`, `0-9`, `-` and
11114
+ `_`.** It appears only in the dev host's log, so a reviewer can map each
11115
+ moment to a screen. It is never sent to the platform, never stored, never
11116
+ shown to a player and never sent to an ad network.
11117
+
11118
+ <a id="momentresult"></a>
11119
+
11120
+ ## MomentResult
11121
+
11122
+ ```ts
11123
+ interface MomentResult
11124
+ ```
11125
+
11126
+ What the arcade answers at a platform moment.
11127
+
11128
+ <a id="momentresult-shown"></a>
11129
+
11130
+ ## MomentResult.shown
11131
+
11132
+ ```ts
11133
+ readonly shown: boolean
11134
+ ```
11135
+
11136
+ Whether the arcade showed anything of its own at this break. Usually
11137
+ `false`. Either way, carry on exactly as you would have. **It grants
11138
+ nothing**, and it never says what was shown or why nothing was.
11139
+
11140
+ <a id="momentsapi"></a>
11141
+
11142
+ ## MomentsApi
11143
+
11144
+ ```ts
11145
+ interface MomentsApi
11146
+ ```
11147
+
11148
+ Platform moments. `gem.moments` is one of these.
11149
+
11150
+ <a id="momentsapi-signal"></a>
11151
+
11152
+ ## MomentsApi.signal
11153
+
11154
+ ```ts
11155
+ signal(moment: MomentSignal): Promise<MomentResult>
11156
+ ```
11157
+
11158
+ Signal a platform moment: the player is leaving a natural break in play.
11159
+ The arcade may show one of its own offers over your paused game, and
11160
+ usually shows nothing.
11161
+
11162
+ **Call it from the player's press that LEAVES the break** — Next, Retry,
11163
+ Back to menu — once per break. Start your own transition in parallel, and
11164
+ start play when this settles. **Never** mid-input, during a timed
11165
+ sequence, on a timer, on boot or the first arrival at the title, or from a
11166
+ player-opened pause menu (choosing Quit to menu from it is a
11167
+ `menu_return`). No press is required by the SDK; the rule is about when a
11168
+ break really is one.
11169
+
11170
+ NOTHING REJECTS. It resolves `{ shown: false }` without asking for an
11171
+ arcade that does not do platform moments, a `kind` or `placement` outside
11172
+ the grammar, a call before `gem.frame.ready()`, a call while another is in
11173
+ flight, a call within 10 seconds of the last answer, or no answer within
11174
+ three minutes — and each of those, except an arcade that does not do it
11175
+ (said once a session), is reported to your `onError` handler with code
11176
+ `'internal'` and a message beginning with a stable token:
11177
+ `moments_unsupported`, `moments_invalid_argument`, `moments_before_ready`,
11178
+ `moments_in_flight`, `moments_called_repeatedly` or `moments_timeout`.
11179
+
11180
+ The arcade pauses and resumes your game through the ordinary
11181
+ `gem.pause`, so you do not pause anything yourself.
11182
+
11183
+ ⚠️ `shown` grants nothing. Never hand out a reward, an unlock or a currency
11184
+ on it, and never pop your own offer at a break that settled `shown: true`.
11185
+
11186
+ | Parameter | Type | Description |
11187
+ | --- | --- | --- |
11188
+ | `moment` | `MomentSignal` | Which kind of break, and optionally your name for the screen. |
11189
+
11190
+ **Returns.** Whether the arcade showed anything. Always resolves; never rejects.
11191
+
11192
+ ```ts
11193
+ // From the press that LEAVES the break — Next, Retry, Back to menu.
11194
+ async function onNextPressed() {
11195
+ startLevelTransition(); // your fade or loading beat, in parallel
11196
+ await gem.moments.signal({ kind: 'level_end', placement: 'world-1-3' });
11197
+ startNextLevel(); // settled either way; never grant anything on `shown`
11198
+ }
11199
+ ```
11200
+
11201
+ **Guides.** [platform-moments](../platform-moments.md)
11202
+
11035
11203
  <a id="dedicatedserverfailure"></a>
11036
11204
 
11037
11205
  ## DedicatedServerFailure
@@ -1,6 +1,6 @@
1
1
  # The server surface — API reference
2
2
 
3
- Generated from `@indiegems/gem-web-sdk/sdk` v13.1.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` v13.2.1, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
4
4
 
5
5
  `@indiegems/gem-web-sdk/sdk` is the one import a game writes, wherever its code runs. `@indiegems/gem-web-sdk/server` exports exactly the symbols on this page and nothing else, for a build that wants only this half.
6
6
 
@@ -440,6 +440,22 @@ It buys the same budget a read gets — see [ClientOptions.maxRetries](#clientop
440
440
  final: only the failures [GemApiError.retryable](#gemapierror-retryable) describes, and a
441
441
  connection that died with no answer, are attempted again.
442
442
 
443
+ <a id="requestoptions-retryratelimited"></a>
444
+
445
+ ## RequestOptions.retryRateLimited
446
+
447
+ ```ts
448
+ readonly retryRateLimited?: boolean
449
+ ```
450
+
451
+ Set `false` to make a 429 final for this call instead of waiting out
452
+ `Retry-After` and trying again.
453
+
454
+ For a caller that keeps its own queue and its own cooldown, such as a
455
+ batch sender on a timer: retrying inside the call only spends more of the
456
+ same limit, and holds the caller's queue while it does. Every other
457
+ failure is retried exactly as before. Defaults to `true`.
458
+
443
459
  <a id="versionedgetoptions"></a>
444
460
 
445
461
  ## VersionedGetOptions
@@ -2523,9 +2539,14 @@ Send what is queued now, and find out what happened to it.
2523
2539
 
2524
2540
  Resolves with the platform's own answer, INCLUDING per-event rejections, which
2525
2541
  arrive inside a 200 rather than as an error. A refusal that could clear on
2526
- its own — a 429, a 503 — keeps the queue and rejects; one that cannot
2542
+ its own — a 429, a 503 — keeps the queue and rejects, and the SDK's own
2543
+ flushes then hold off for the longer of the server's `Retry-After` and a
2544
+ cooldown that doubles from 10 seconds, up to five minutes; one that cannot
2527
2545
  discards the batch, because resending it can only fail the same way.
2528
2546
 
2547
+ One flush at a time: called while another is on the wire, this waits for
2548
+ it and then sends what is left.
2549
+
2529
2550
  | Parameter | Type | Description |
2530
2551
  | --- | --- | --- |
2531
2552
  | `signal` | `AbortSignal` | Cancels the call — not the queue. |