@indiegems/gem-web-sdk 12.0.0 → 13.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` v12.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.
3
+ Generated from `@indiegems/gem-web-sdk/sdk` v13.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
 
@@ -3669,6 +3669,262 @@ The runtime companion to [PlatformEventName](#platformeventname), for a game tha
3669
3669
  show what it gets for free, or to check a funnel step it authored against
3670
3670
  what the platform actually emits.
3671
3671
 
3672
+ <a id="avatar3dprice"></a>
3673
+
3674
+ ## Avatar3dPrice
3675
+
3676
+ ```ts
3677
+ interface Avatar3dPrice
3678
+ ```
3679
+
3680
+ One price of a 3D avatar on sale, in one platform currency.
3681
+
3682
+ <a id="avatar3dprice-currencyslug"></a>
3683
+
3684
+ ## Avatar3dPrice.currencySlug
3685
+
3686
+ ```ts
3687
+ readonly currencySlug: string
3688
+ ```
3689
+
3690
+ `GEMS` or `ARCADE_TICKETS` — what to pass as `currencySlug`.
3691
+
3692
+ <a id="avatar3dprice-price"></a>
3693
+
3694
+ ## Avatar3dPrice.price
3695
+
3696
+ ```ts
3697
+ readonly price: number
3698
+ ```
3699
+
3700
+ The price, a whole number of that currency.
3701
+
3702
+ <a id="avatar3doffer"></a>
3703
+
3704
+ ## Avatar3dOffer
3705
+
3706
+ ```ts
3707
+ interface Avatar3dOffer
3708
+ ```
3709
+
3710
+ One of your game's 3D avatars on sale, as the platform prices it.
3711
+
3712
+ <a id="avatar3doffer-itemid"></a>
3713
+
3714
+ ## Avatar3dOffer.itemId
3715
+
3716
+ ```ts
3717
+ readonly itemId: string
3718
+ ```
3719
+
3720
+ The avatar's item id — what [Avatars3dApi.purchase](#avatars3dapi-purchase) takes.
3721
+
3722
+ <a id="avatar3doffer-slug"></a>
3723
+
3724
+ ## Avatar3dOffer.slug
3725
+
3726
+ ```ts
3727
+ readonly slug: string
3728
+ ```
3729
+
3730
+ The slug you uploaded it under. A key, not display text.
3731
+
3732
+ <a id="avatar3doffer-family"></a>
3733
+
3734
+ ## Avatar3dOffer.family
3735
+
3736
+ ```ts
3737
+ readonly family: Avatar3dFamily
3738
+ ```
3739
+
3740
+ The rig family the model is built on.
3741
+
3742
+ <a id="avatar3doffer-displayname"></a>
3743
+
3744
+ ## Avatar3dOffer.displayName
3745
+
3746
+ ```ts
3747
+ readonly displayName: string
3748
+ ```
3749
+
3750
+ The avatar's name, as you uploaded it.
3751
+
3752
+ <a id="avatar3doffer-description"></a>
3753
+
3754
+ ## Avatar3dOffer.description
3755
+
3756
+ ```ts
3757
+ readonly description?: string
3758
+ ```
3759
+
3760
+ The avatar's description, when it has one.
3761
+
3762
+ <a id="avatar3doffer-iconurl"></a>
3763
+
3764
+ ## Avatar3dOffer.iconUrl
3765
+
3766
+ ```ts
3767
+ readonly iconUrl: string
3768
+ ```
3769
+
3770
+ A rendered portrait of the avatar, for a shop tile. Pass it through
3771
+ `platformIconUrl` before putting it in an `<img src>`.
3772
+
3773
+ <a id="avatar3doffer-modelurl"></a>
3774
+
3775
+ ## Avatar3dOffer.modelUrl
3776
+
3777
+ ```ts
3778
+ readonly modelUrl: string
3779
+ ```
3780
+
3781
+ The avatar's model, for a preview. Pass it through `platformIconUrl`
3782
+ before loading it.
3783
+
3784
+ <a id="avatar3doffer-prices"></a>
3785
+
3786
+ ## Avatar3dOffer.prices
3787
+
3788
+ ```ts
3789
+ readonly prices: readonly Avatar3dPrice[]
3790
+ ```
3791
+
3792
+ One or two prices, each in a currency a player can pay with. Show each
3793
+ one the player can choose; pass the chosen one's `currencySlug` to
3794
+ [Avatars3dApi.purchase](#avatars3dapi-purchase).
3795
+
3796
+ <a id="avatars3dapi"></a>
3797
+
3798
+ ## Avatars3dApi
3799
+
3800
+ ```ts
3801
+ interface Avatars3dApi
3802
+ ```
3803
+
3804
+ Arcade 3D avatars — `gem.avatars3d`. What the player wears, the rig families
3805
+ your game accepts, the avatars you sell, and buying one.
3806
+
3807
+ ```js
3808
+ function draw() {
3809
+ const worn = gem.avatars3d.worn();
3810
+ if (worn) loadCharacter(platformIconUrl(worn.modelUrl));
3811
+ else useDefaultCharacter();
3812
+ }
3813
+ draw();
3814
+ gem.avatars3d.onChange(draw);
3815
+ ```
3816
+
3817
+ The guide is `docs/arcade-avatars.md`: the rig, the config, uploading and
3818
+ selling with gemctl, and loading the model at runtime.
3819
+
3820
+ <a id="avatars3dapi-families"></a>
3821
+
3822
+ ## Avatars3dApi.families
3823
+
3824
+ ```ts
3825
+ families(): Promise<readonly Avatar3dFamily[]>
3826
+ ```
3827
+
3828
+ The rig families your game accepts, in the order your published config
3829
+ declares them (`avatars_3d.families`) — the first is your default.
3830
+
3831
+ Resolves `[]` when your game has no published config on this channel, or
3832
+ its config declares no families. Loads the config through
3833
+ `gem.gameConfig.load()` and shares that one fetch; rejects when the config
3834
+ cannot be loaded, as `load()` does.
3835
+
3836
+ <a id="avatars3dapi-worn"></a>
3837
+
3838
+ ## Avatars3dApi.worn
3839
+
3840
+ ```ts
3841
+ worn(family?: Avatar3dFamily): WornAvatar3d | undefined
3842
+ ```
3843
+
3844
+ The 3D avatar the player wears — in `family` when you name one, otherwise
3845
+ in your game's first declared family that has one. `undefined` when none
3846
+ is worn there, before the player arrives, and outside the Arcade: draw
3847
+ your default look.
3848
+
3849
+ Read live from [Gem.player](#gem-player), so it changes when the player wears
3850
+ another avatar or buys one; [Avatars3dApi.onChange](#avatars3dapi-onchange) tells you when.
3851
+
3852
+ | Parameter | Type |
3853
+ | --- | --- |
3854
+ | `family` | `Avatar3dFamily` |
3855
+
3856
+ <a id="avatars3dapi-offers"></a>
3857
+
3858
+ ## Avatars3dApi.offers
3859
+
3860
+ ```ts
3861
+ offers(): Promise<readonly Avatar3dOffer[]>
3862
+ ```
3863
+
3864
+ Your game's 3D avatars on sale on this channel, with their prices, read
3865
+ from the platform with your game's own token. Render your shop from this,
3866
+ never from prices of your own.
3867
+
3868
+ Resolves `[]` when you have nothing on sale. Rejects with a `GemApiError`
3869
+ when the platform cannot be read — a shop that cannot load says so.
3870
+
3871
+ <a id="avatars3dapi-purchase"></a>
3872
+
3873
+ ## Avatars3dApi.purchase
3874
+
3875
+ ```ts
3876
+ purchase(request: Avatar3dPurchaseRequest): Promise<PurchaseOutcome>
3877
+ ```
3878
+
3879
+ Buy one of your 3D avatars for the player and put it on. The Arcade
3880
+ prices it again, shows its own confirmation sheet, takes the currency and
3881
+ wears the avatar; your game then receives the player again, with the new
3882
+ avatar worn, and [Avatars3dApi.onChange](#avatars3dapi-onchange) runs.
3883
+
3884
+ Call it from the player's own click or key press — the Arcade shows no
3885
+ sheet without one and answers `declined` / `busy`.
3886
+
3887
+ NOTHING REJECTS. Every outcome resolves, exactly as on
3888
+ `gem.purchases.request`:
3889
+
3890
+ - `ok` — bought and worn, and the platform confirmed the player owns it.
3891
+ - `declined` — definitely not bought. `reason` says why:
3892
+ `already_owned` (the player has it — offer to wear it with
3893
+ `gem.openAvatarPicker({ tab: '3d' })`), `cancelled`, `insufficient_funds`,
3894
+ `no_price_in_currency`, `not_purchasable` (not on sale, or not an id from
3895
+ [Avatars3dApi.offers](#avatars3dapi-offers)), `busy`, and the rest of
3896
+ `PurchaseRefusalCode`. Return to your shop silently.
3897
+ - `unavailable` — no reliable answer. Show one neutral message, never
3898
+ retry on your own, and read [Avatars3dApi.worn](#avatars3dapi-worn) again.
3899
+
3900
+ **Outside the Arcade** — opened at the top level of a page with no Arcade
3901
+ around it, or under an Arcade that does not sell 3D avatars yet — it
3902
+ resolves `unavailable` with reason `error` at once and asks nobody.
3903
+
3904
+ | Parameter | Type |
3905
+ | --- | --- |
3906
+ | `request` | `Avatar3dPurchaseRequest` |
3907
+
3908
+ <a id="avatars3dapi-onchange"></a>
3909
+
3910
+ ## Avatars3dApi.onChange
3911
+
3912
+ ```ts
3913
+ onChange(listener: (worn: readonly WornAvatar3d[]) => void): () => void
3914
+ ```
3915
+
3916
+ Call `listener` whenever the avatars the player wears change — they wore
3917
+ another one in the Arcade's picker, bought one, or their avatars arrived
3918
+ for the first time — with every worn avatar, one per family. Not called
3919
+ for the avatars already worn when you subscribe: read
3920
+ [Avatars3dApi.worn](#avatars3dapi-worn) for those.
3921
+
3922
+ | Parameter | Type |
3923
+ | --- | --- |
3924
+ | `listener` | `(worn: readonly WornAvatar3d[]) => void` |
3925
+
3926
+ **Returns.** a function that stops the calls. Calling it twice is harmless.
3927
+
3672
3928
  <a id="gemapierror"></a>
3673
3929
 
3674
3930
  ## GemApiError
@@ -4851,50 +5107,11 @@ a key you leave out is unchanged.
4851
5107
  interface ControlsApi
4852
5108
  ```
4853
5109
 
4854
- `gem.controls` — the player's camera and controls settings: tell the Arcade
4855
- which ones your game leaves to the player, and turn look input into a camera
4856
- turn at the player's sensitivity.
5110
+ `gem.controls` — the player's camera and controls settings: turn look input
5111
+ into a camera turn at the player's sensitivity.
4857
5112
 
4858
5113
  **Guides.** [what-a-game-must-do](../what-a-game-must-do.md)
4859
5114
 
4860
- <a id="controlsapi-declare"></a>
4861
-
4862
- ## ControlsApi.declare
4863
-
4864
- ```ts
4865
- declare(uses: GemControlsDeclaration): boolean
4866
- ```
4867
-
4868
- Tell the Arcade which of the player's camera and controls settings your
4869
- game uses, and for each one who decides it: `'player'` — you apply the
4870
- Arcade's value from `gem.prefs.recommended` (or the player's own choice
4871
- in your settings, which wins) — or `'game'` — you decide it yourself, and
4872
- the Arcade's Settings shows that row as "Set by this game". Leave out a
4873
- setting your game does not use.
4874
-
4875
- ```js
4876
- gem.controls.declare({
4877
- lookSensitivity: 'player',
4878
- gamepadLookSensitivity: 'player',
4879
- invertLookY: 'player',
4880
- touchMovement: 'game', // this game always uses a fixed stick
4881
- });
4882
- ```
4883
-
4884
- **It is screen state, not a toggle.** Each call is the whole answer and
4885
- replaces the last. The SDK holds it and sends it again after every
4886
- reconnect, so call it once at boot, and again only when the answer
4887
- changes. Any other key or value is dropped.
4888
-
4889
- | Parameter | Type |
4890
- | --- | --- |
4891
- | `uses` | `GemControlsDeclaration` |
4892
-
4893
- **Returns.** `true` when the Arcade was told now; `false` before the bridge is
4894
- up (the declaration is held and sent when it connects), outside the
4895
- Arcade, and under an embedder that does not show these settings. Never
4896
- throws.
4897
-
4898
5115
  <a id="controlsapi-look"></a>
4899
5116
 
4900
5117
  ## ControlsApi.look
@@ -4937,72 +5154,6 @@ readonly LOOK_BASE: GemLookBase
4937
5154
 
4938
5155
  The base speeds `look` applies at 1×, in radians. Frozen.
4939
5156
 
4940
- <a id="gemcontrolchoice"></a>
4941
-
4942
- ## GemControlChoice
4943
-
4944
- ```ts
4945
- type GemControlChoice = "player" | "game"
4946
- ```
4947
-
4948
- Who decides one camera or controls setting: `'player'` — your game applies
4949
- the Arcade's value, or the player's own in-game choice, which wins — or
4950
- `'game'` — your game decides it itself, and the Arcade's Settings shows the
4951
- row as "Set by this game".
4952
-
4953
- <a id="gemcontrolsdeclaration"></a>
4954
-
4955
- ## GemControlsDeclaration
4956
-
4957
- ```ts
4958
- interface GemControlsDeclaration
4959
- ```
4960
-
4961
- What `gem.controls.declare` takes: for each of the player's camera and
4962
- controls settings your game uses, who decides it. Leave a setting out when
4963
- your game does not use it at all — the Arcade then says "This game doesn't
4964
- use this" beside that row.
4965
-
4966
- <a id="gemcontrolsdeclaration-looksensitivity"></a>
4967
-
4968
- ## GemControlsDeclaration.lookSensitivity
4969
-
4970
- ```ts
4971
- readonly lookSensitivity?: GemControlChoice
4972
- ```
4973
-
4974
- Look speed for touch, mouse and pen — `gem.prefs.recommended.lookSensitivity`.
4975
-
4976
- <a id="gemcontrolsdeclaration-gamepadlooksensitivity"></a>
4977
-
4978
- ## GemControlsDeclaration.gamepadLookSensitivity
4979
-
4980
- ```ts
4981
- readonly gamepadLookSensitivity?: GemControlChoice
4982
- ```
4983
-
4984
- Look speed for a gamepad stick — `gem.prefs.recommended.gamepadLookSensitivity`.
4985
-
4986
- <a id="gemcontrolsdeclaration-invertlooky"></a>
4987
-
4988
- ## GemControlsDeclaration.invertLookY
4989
-
4990
- ```ts
4991
- readonly invertLookY?: GemControlChoice
4992
- ```
4993
-
4994
- Inverted up/down look — `gem.prefs.recommended.invertLookY`.
4995
-
4996
- <a id="gemcontrolsdeclaration-touchmovement"></a>
4997
-
4998
- ## GemControlsDeclaration.touchMovement
4999
-
5000
- ```ts
5001
- readonly touchMovement?: GemControlChoice
5002
- ```
5003
-
5004
- The touch movement thumbstick, floating or fixed — `gem.prefs.recommended.touchMovement`.
5005
-
5006
5157
  <a id="device_settings_max_bytes"></a>
5007
5158
 
5008
5159
  ## DEVICE_SETTINGS_MAX_BYTES
@@ -5563,41 +5714,17 @@ wallet.
5563
5714
  **Returns.** whether the message left the frame — `false` before the bridge is
5564
5715
  up, for an arcade with no wallet, and past this frame's own limit.
5565
5716
 
5566
- <a id="gemframe-declarecontrols"></a>
5567
-
5568
- ## GemFrame.declareControls
5569
-
5570
- ```ts
5571
- declareControls(uses: GemControlsDeclaration): boolean
5572
- ```
5573
-
5574
- Tell the arcade which of the player's camera and controls settings your
5575
- game leaves to the player and which it decides itself — the whole answer
5576
- each time, the latest one winning.
5577
-
5578
- Prefer `gem.controls.declare`, which holds the declaration across a
5579
- reconnect. This one sends only while connected, and only to an arcade
5580
- that shows these settings.
5581
-
5582
- | Parameter | Type |
5583
- | --- | --- |
5584
- | `uses` | `GemControlsDeclaration` |
5585
-
5586
- **Returns.** whether the message left the frame — `false` before the bridge is
5587
- up, for an arcade that does not show these settings, and past this
5588
- frame's own limit.
5589
-
5590
5717
  <a id="gemframe-setmenu"></a>
5591
5718
 
5592
5719
  ## GemFrame.setMenu
5593
5720
 
5594
5721
  ```ts
5595
- setMenu(settings: boolean, items?: readonly { id: string; label: string }[]): boolean
5722
+ setMenu(items?: readonly { id: string; label: string }[]): boolean
5596
5723
  ```
5597
5724
 
5598
- Tell the arcade what your game adds to its menu: whether it has a settings
5599
- screen, and up to four entries of its own — the whole answer each time, the
5600
- latest one winning.
5725
+ Tell the arcade what your game adds to its menu: up to four entries of its
5726
+ own — the whole answer each time, the latest one winning. Your settings
5727
+ are not among them: a game opens its settings from its own button.
5601
5728
 
5602
5729
  Prefer `gem.setMenu`, which holds the state across a reconnect and calls
5603
5730
  your handlers when the player chooses an entry. This one sends only while
@@ -5605,7 +5732,6 @@ connected, and only to an arcade that draws a menu.
5605
5732
 
5606
5733
  | Parameter | Type |
5607
5734
  | --- | --- |
5608
- | `settings` | `boolean` |
5609
5735
  | `items` | `readonly { id: string; label: string }[]` |
5610
5736
 
5611
5737
  **Returns.** whether the message left the frame — `false` before the bridge is
@@ -5860,6 +5986,24 @@ loading the model is too much. Not the 2D portrait
5860
5986
  ([PlayerIdentity.avatarIconUrl](#playeridentity-avatariconurl)), which is a separate choice. Pass it
5861
5987
  through [platformIconUrl](#platformiconurl) before putting it in an `<img src>`.
5862
5988
 
5989
+ <a id="friend-avatars3d"></a>
5990
+
5991
+ ## Friend.avatars3d
5992
+
5993
+ ```ts
5994
+ readonly avatars3d?: readonly WornAvatar3d[]
5995
+ ```
5996
+
5997
+ Every 3D avatar the player wears, one per rig family, in the order the game
5998
+ declared its families. Absent or empty means none is worn — draw your
5999
+ default look.
6000
+
6001
+ [PlayerIdentity.avatar3dId](#playeridentity-avatar3did) and its two URLs are one of these: the
6002
+ avatar of the game's first declared family that has one. A game that
6003
+ accepts a single family can read either; one that accepts several picks by
6004
+ `family` here. Absent too where the platform does not send it yet — read
6005
+ the flat fields then.
6006
+
5863
6007
  <a id="friend-badges"></a>
5864
6008
 
5865
6009
  ## Friend.badges
@@ -7721,6 +7865,24 @@ loading the model is too much. Not the 2D portrait
7721
7865
  ([PlayerIdentity.avatarIconUrl](#playeridentity-avatariconurl)), which is a separate choice. Pass it
7722
7866
  through [platformIconUrl](#platformiconurl) before putting it in an `<img src>`.
7723
7867
 
7868
+ <a id="gemplayer-avatars3d"></a>
7869
+
7870
+ ## GemPlayer.avatars3d
7871
+
7872
+ ```ts
7873
+ readonly avatars3d?: readonly WornAvatar3d[]
7874
+ ```
7875
+
7876
+ Every 3D avatar the player wears, one per rig family, in the order the game
7877
+ declared its families. Absent or empty means none is worn — draw your
7878
+ default look.
7879
+
7880
+ [PlayerIdentity.avatar3dId](#playeridentity-avatar3did) and its two URLs are one of these: the
7881
+ avatar of the game's first declared family that has one. A game that
7882
+ accepts a single family can read either; one that accepts several picks by
7883
+ `family` here. Absent too where the platform does not send it yet — read
7884
+ the flat fields then.
7885
+
7724
7886
  <a id="gemplayer-badges"></a>
7725
7887
 
7726
7888
  ## GemPlayer.badges
@@ -8279,20 +8441,8 @@ and your game is running again by the time this fires.
8279
8441
  interface GameMenu
8280
8442
  ```
8281
8443
 
8282
- What your game adds to the arcade's menu — see `gem.setMenu`.
8283
-
8284
- <a id="gamemenu-onsettings"></a>
8285
-
8286
- ## GameMenu.onSettings
8287
-
8288
- ```ts
8289
- readonly onSettings?: () => void
8290
- ```
8291
-
8292
- Open your settings screen. Another embedder may show a Settings entry that
8293
- calls it, but **the Gem Arcade no longer does**: draw a settings button of
8294
- your own, always (top-right is the recommendation). Passing this is still
8295
- accepted and harmless.
8444
+ What your game adds to the arcade's menu — see `gem.setMenu`. Your settings
8445
+ are not part of it: they open from your own settings button.
8296
8446
 
8297
8447
  <a id="gamemenu-items"></a>
8298
8448
 
@@ -8830,6 +8980,32 @@ client sends.
8830
8980
  up, under an embedder with no avatar picker, or past six asks a minute.
8831
8981
  It never throws and never waits.
8832
8982
 
8983
+ <a id="gem-avatars3d"></a>
8984
+
8985
+ ## Gem.avatars3d
8986
+
8987
+ ```ts
8988
+ readonly avatars3d: Avatars3dApi
8989
+ ```
8990
+
8991
+ Arcade 3D avatars: what the player wears, the rig families your game
8992
+ accepts, the avatars your game sells, and buying one — which wears it.
8993
+ 2D portraits are a separate system and are not here.
8994
+
8995
+ ```js
8996
+ const draw = () => {
8997
+ const worn = gem.avatars3d.worn();
8998
+ const url = worn && platformIconUrl(worn.modelUrl);
8999
+ if (url) loadCharacter(url);
9000
+ else useDefaultCharacter();
9001
+ };
9002
+ draw();
9003
+ gem.avatars3d.onChange(draw);
9004
+ ```
9005
+
9006
+ See [Arcade avatars](../arcade-avatars.md) for the rig, the config, and
9007
+ uploading and selling avatars with gemctl.
9008
+
8833
9009
  <a id="gem-vibration"></a>
8834
9010
 
8835
9011
  ## Gem.vibration
@@ -8874,13 +9050,10 @@ readonly controls: ControlsApi
8874
9050
 
8875
9051
  The player's camera and controls settings — look sensitivity, gamepad look
8876
9052
  sensitivity, invert look and touch movement, which arrive in
8877
- `gem.prefs.recommended`. Two things to do with them:
9053
+ `gem.prefs.recommended` as the player's Arcade-wide defaults. Turn look
9054
+ input into a camera turn at the player's sensitivity:
8878
9055
 
8879
9056
  ```js
8880
- // 1. Tell the Arcade which ones your game leaves to the player.
8881
- gem.controls.declare({ lookSensitivity: 'player', invertLookY: 'player' });
8882
-
8883
- // 2. Turn look input into a camera turn at the player's sensitivity.
8884
9057
  const look = gem.controls.look();
8885
9058
  // dx, dy: the pointer's move in CSS px since the last event.
8886
9059
  const t = look.fromPointer(dx, dy, e.pointerType, camera.pitch);
@@ -9359,10 +9532,8 @@ gem.setMenu({
9359
9532
  ```
9360
9533
 
9361
9534
  **Your settings are not in it: draw your own settings button, always** —
9362
- top-right is the recommendation. `onSettings` is still accepted, and
9363
- another embedder may show an entry for it, but the Gem Arcade no longer
9364
- shows a Settings entry, so a game whose settings open only from there has
9365
- none a player can reach.
9535
+ top-right, clear of `gem.viewport.reserved`. The Arcade's menu never opens
9536
+ your settings screen.
9366
9537
 
9367
9538
  **It is screen state, not a toggle.** Each call is the whole answer and
9368
9539
  replaces the last, so call it again when the entries change — a new
@@ -9376,7 +9547,7 @@ should not assume the game was paused by you.
9376
9547
 
9377
9548
  | Parameter | Type | Description |
9378
9549
  | --- | --- | --- |
9379
- | `menu` | `GameMenu` | Your settings handler and your own entries. |
9550
+ | `menu` | `GameMenu` | Your own entries. |
9380
9551
 
9381
9552
  **Returns.** `true` when the arcade was told now; `false` before the bridge is
9382
9553
  up (the menu is held and sent when it connects) and under an embedder
@@ -10368,6 +10539,76 @@ readonly iconUrl: string
10368
10539
  The badge's thumbnail. Pass it through [platformIconUrl](#platformiconurl) before
10369
10540
  putting it in an `<img src>`.
10370
10541
 
10542
+ <a id="avatar3dfamily"></a>
10543
+
10544
+ ## Avatar3dFamily
10545
+
10546
+ ```ts
10547
+ type Avatar3dFamily = "small_human" | string & object
10548
+ ```
10549
+
10550
+ A rig family — the skeleton contract a 3D avatar is built on, and what a
10551
+ game declares it can animate. `small_human` is the first: a `root` node
10552
+ with a `body` child, which holds `head`, `arm_l`, `arm_r`, `leg_l` and
10553
+ `leg_r`, and a `hand_socket` under `arm_r`.
10554
+
10555
+ Open-ended (`| (string & {})`) so a family the platform adds later does not
10556
+ break a `switch` you compiled against this version: keep a `default` arm and
10557
+ skip a family you do not draw.
10558
+
10559
+ <a id="wornavatar3d"></a>
10560
+
10561
+ ## WornAvatar3d
10562
+
10563
+ ```ts
10564
+ interface WornAvatar3d
10565
+ ```
10566
+
10567
+ One 3D avatar a player wears — at most one per [Avatar3dFamily](#avatar3dfamily).
10568
+
10569
+ <a id="wornavatar3d-family"></a>
10570
+
10571
+ ## WornAvatar3d.family
10572
+
10573
+ ```ts
10574
+ readonly family: Avatar3dFamily
10575
+ ```
10576
+
10577
+ The rig family this avatar is built on.
10578
+
10579
+ <a id="wornavatar3d-id"></a>
10580
+
10581
+ ## WornAvatar3d.id
10582
+
10583
+ ```ts
10584
+ readonly id: string
10585
+ ```
10586
+
10587
+ The avatar's item id — a stable key for the model, never something to
10588
+ show as text.
10589
+
10590
+ <a id="wornavatar3d-modelurl"></a>
10591
+
10592
+ ## WornAvatar3d.modelUrl
10593
+
10594
+ ```ts
10595
+ readonly modelUrl: string
10596
+ ```
10597
+
10598
+ The avatar's model, a binary glTF (`.glb`) on the family's rig. Pass it
10599
+ through [platformIconUrl](#platformiconurl) before loading it.
10600
+
10601
+ <a id="wornavatar3d-iconurl"></a>
10602
+
10603
+ ## WornAvatar3d.iconUrl
10604
+
10605
+ ```ts
10606
+ readonly iconUrl: string
10607
+ ```
10608
+
10609
+ A rendered portrait of the avatar, for a roster or a menu. Pass it through
10610
+ [platformIconUrl](#platformiconurl) before putting it in an `<img src>`.
10611
+
10371
10612
  <a id="playeridentity"></a>
10372
10613
 
10373
10614
  ## PlayerIdentity
@@ -10511,6 +10752,24 @@ loading the model is too much. Not the 2D portrait
10511
10752
  ([PlayerIdentity.avatarIconUrl](#playeridentity-avatariconurl)), which is a separate choice. Pass it
10512
10753
  through [platformIconUrl](#platformiconurl) before putting it in an `<img src>`.
10513
10754
 
10755
+ <a id="playeridentity-avatars3d"></a>
10756
+
10757
+ ## PlayerIdentity.avatars3d
10758
+
10759
+ ```ts
10760
+ readonly avatars3d?: readonly WornAvatar3d[]
10761
+ ```
10762
+
10763
+ Every 3D avatar the player wears, one per rig family, in the order the game
10764
+ declared its families. Absent or empty means none is worn — draw your
10765
+ default look.
10766
+
10767
+ [PlayerIdentity.avatar3dId](#playeridentity-avatar3did) and its two URLs are one of these: the
10768
+ avatar of the game's first declared family that has one. A game that
10769
+ accepts a single family can read either; one that accepts several picks by
10770
+ `family` here. Absent too where the platform does not send it yet — read
10771
+ the flat fields then.
10772
+
10514
10773
  <a id="playeridentity-badges"></a>
10515
10774
 
10516
10775
  ## PlayerIdentity.badges
@@ -10550,7 +10809,7 @@ for (const row of opponents) {
10550
10809
 
10551
10810
  Reads `player_id`, `display_name`, `discriminator`, `display_tag`,
10552
10811
  `avatar_icon_id`, `avatar_icon_url`, `avatar_3d_id`, `avatar_3d_model_url`,
10553
- `avatar_3d_icon_url` and `badges`, and nothing else from the row. Anything missing, empty or of the wrong type is absent in the result.
10812
+ `avatar_3d_icon_url`, `avatars_3d` and `badges`, and nothing else from the row. Anything missing, empty or of the wrong type is absent in the result.
10554
10813
 
10555
10814
  | Parameter | Type |
10556
10815
  | --- | --- |
@@ -11810,12 +12069,44 @@ a Buy button mostly does not write one.
11810
12069
  | --- | --- |
11811
12070
  | `req` | `PurchaseRequest` |
11812
12071
 
12072
+ <a id="avatar3dpurchaserequest"></a>
12073
+
12074
+ ## Avatar3dPurchaseRequest
12075
+
12076
+ ```ts
12077
+ interface Avatar3dPurchaseRequest
12078
+ ```
12079
+
12080
+ What a game asks for when it buys one of its own 3D avatars for the player
12081
+ with `gem.avatars3d.purchase`.
12082
+
12083
+ <a id="avatar3dpurchaserequest-itemid"></a>
12084
+
12085
+ ## Avatar3dPurchaseRequest.itemId
12086
+
12087
+ ```ts
12088
+ itemId: string
12089
+ ```
12090
+
12091
+ The avatar's item id — `itemId` on an `Avatar3dOffer`.
12092
+
12093
+ <a id="avatar3dpurchaserequest-currencyslug"></a>
12094
+
12095
+ ## Avatar3dPurchaseRequest.currencySlug
12096
+
12097
+ ```ts
12098
+ currencySlug: string
12099
+ ```
12100
+
12101
+ The platform currency to pay in, `GEMS` or `ARCADE_TICKETS`. The offer
12102
+ must carry a price in it.
12103
+
11813
12104
  <a id="purchaserefusalcode"></a>
11814
12105
 
11815
12106
  ## PurchaseRefusalCode
11816
12107
 
11817
12108
  ```ts
11818
- type PurchaseRefusalCode = "unknown_currency" | "currency_not_in_game_payable" | "not_purchasable" | "no_price_in_currency" | "busy" | "cancelled" | "insufficient_funds" | "price_mismatch" | "config_version_stale" | "platform_currency_unavailable" | "not_available" | "recipe_locked" | "recipe_on_cooldown" | "recipe_max_uses_exceeded" | "item_max_stack_exceeded" | "recipe_costs_unmet" | "quantity_invalid" | "error"
12109
+ type PurchaseRefusalCode = "unknown_currency" | "currency_not_in_game_payable" | "not_purchasable" | "no_price_in_currency" | "busy" | "cancelled" | "insufficient_funds" | "price_mismatch" | "config_version_stale" | "platform_currency_unavailable" | "not_available" | "recipe_locked" | "already_owned" | "recipe_on_cooldown" | "recipe_max_uses_exceeded" | "item_max_stack_exceeded" | "recipe_costs_unmet" | "quantity_invalid" | "error"
11819
12110
  ```
11820
12111
 
11821
12112
  Every reason a purchase can be refused, on either purchase path.
@@ -11841,6 +12132,9 @@ Every reason a purchase can be refused, on either purchase path.
11841
12132
  - `not_available` — the recipe is outside its availability window, or
11842
12133
  inactive; nothing was charged. Hide or disable it until its window opens.
11843
12134
  - `recipe_locked` — the recipe's prerequisites are not met.
12135
+ - `already_owned` — the player already owns the 3D avatar
12136
+ `gem.avatars3d.purchase` asked for; nothing was charged. Only that call
12137
+ answers it.
11844
12138
  - `recipe_on_cooldown`, `recipe_max_uses_exceeded`, `item_max_stack_exceeded`,
11845
12139
  `recipe_costs_unmet`, `quantity_invalid` — the recipe or item refused this
11846
12140
  purchase, before any charge.
@@ -13127,6 +13421,24 @@ loading the model is too much. Not the 2D portrait
13127
13421
  ([PlayerIdentity.avatarIconUrl](#playeridentity-avatariconurl)), which is a separate choice. Pass it
13128
13422
  through [platformIconUrl](#platformiconurl) before putting it in an `<img src>`.
13129
13423
 
13424
+ <a id="leaderboardentry-avatars3d"></a>
13425
+
13426
+ ## LeaderboardEntry.avatars3d
13427
+
13428
+ ```ts
13429
+ readonly avatars3d?: readonly WornAvatar3d[]
13430
+ ```
13431
+
13432
+ Every 3D avatar the player wears, one per rig family, in the order the game
13433
+ declared its families. Absent or empty means none is worn — draw your
13434
+ default look.
13435
+
13436
+ [PlayerIdentity.avatar3dId](#playeridentity-avatar3did) and its two URLs are one of these: the
13437
+ avatar of the game's first declared family that has one. A game that
13438
+ accepts a single family can read either; one that accepts several picks by
13439
+ `family` here. Absent too where the platform does not send it yet — read
13440
+ the flat fields then.
13441
+
13130
13442
  <a id="leaderboardentry-badges"></a>
13131
13443
 
13132
13444
  ## LeaderboardEntry.badges
@@ -14620,6 +14932,24 @@ loading the model is too much. Not the 2D portrait
14620
14932
  ([PlayerIdentity.avatarIconUrl](#playeridentity-avatariconurl)), which is a separate choice. Pass it
14621
14933
  through [platformIconUrl](#platformiconurl) before putting it in an `<img src>`.
14622
14934
 
14935
+ <a id="playerprofile-avatars3d"></a>
14936
+
14937
+ ## PlayerProfile.avatars3d
14938
+
14939
+ ```ts
14940
+ readonly avatars3d?: readonly WornAvatar3d[]
14941
+ ```
14942
+
14943
+ Every 3D avatar the player wears, one per rig family, in the order the game
14944
+ declared its families. Absent or empty means none is worn — draw your
14945
+ default look.
14946
+
14947
+ [PlayerIdentity.avatar3dId](#playeridentity-avatar3did) and its two URLs are one of these: the
14948
+ avatar of the game's first declared family that has one. A game that
14949
+ accepts a single family can read either; one that accepts several picks by
14950
+ `family` here. Absent too where the platform does not send it yet — read
14951
+ the flat fields then.
14952
+
14623
14953
  <a id="playerprofile-badges"></a>
14624
14954
 
14625
14955
  ## PlayerProfile.badges
@@ -17206,6 +17536,24 @@ loading the model is too much. Not the 2D portrait
17206
17536
  ([PlayerIdentity.avatarIconUrl](#playeridentity-avatariconurl)), which is a separate choice. Pass it
17207
17537
  through [platformIconUrl](#platformiconurl) before putting it in an `<img src>`.
17208
17538
 
17539
+ <a id="roommember-avatars3d"></a>
17540
+
17541
+ ## RoomMember.avatars3d
17542
+
17543
+ ```ts
17544
+ readonly avatars3d?: readonly WornAvatar3d[]
17545
+ ```
17546
+
17547
+ Every 3D avatar the player wears, one per rig family, in the order the game
17548
+ declared its families. Absent or empty means none is worn — draw your
17549
+ default look.
17550
+
17551
+ [PlayerIdentity.avatar3dId](#playeridentity-avatar3did) and its two URLs are one of these: the
17552
+ avatar of the game's first declared family that has one. A game that
17553
+ accepts a single family can read either; one that accepts several picks by
17554
+ `family` here. Absent too where the platform does not send it yet — read
17555
+ the flat fields then.
17556
+
17209
17557
  <a id="roommember-badges"></a>
17210
17558
 
17211
17559
  ## RoomMember.badges
@@ -18036,6 +18384,24 @@ loading the model is too much. Not the 2D portrait
18036
18384
  ([PlayerIdentity.avatarIconUrl](#playeridentity-avatariconurl)), which is a separate choice. Pass it
18037
18385
  through [platformIconUrl](#platformiconurl) before putting it in an `<img src>`.
18038
18386
 
18387
+ <a id="showcasefriendentry-avatars3d"></a>
18388
+
18389
+ ## ShowcaseFriendEntry.avatars3d
18390
+
18391
+ ```ts
18392
+ readonly avatars3d?: readonly WornAvatar3d[]
18393
+ ```
18394
+
18395
+ Every 3D avatar the player wears, one per rig family, in the order the game
18396
+ declared its families. Absent or empty means none is worn — draw your
18397
+ default look.
18398
+
18399
+ [PlayerIdentity.avatar3dId](#playeridentity-avatar3did) and its two URLs are one of these: the
18400
+ avatar of the game's first declared family that has one. A game that
18401
+ accepts a single family can read either; one that accepts several picks by
18402
+ `family` here. Absent too where the platform does not send it yet — read
18403
+ the flat fields then.
18404
+
18039
18405
  <a id="showcasefriendentry-badges"></a>
18040
18406
 
18041
18407
  ## ShowcaseFriendEntry.badges