@indiegems/gem-web-sdk 12.0.0 → 13.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
  "schema_version": 2,
3
3
  "source": {
4
4
  "repo": "IndieGems/gem-web-sdk",
5
- "version": "12.0.0"
5
+ "version": "13.1.0"
6
6
  },
7
7
  "entries": [
8
8
  {
@@ -45,6 +45,10 @@
45
45
  "key": "purchases",
46
46
  "title": "Selling for platform currency"
47
47
  },
48
+ {
49
+ "key": "avatars3d",
50
+ "title": "Arcade 3D avatars"
51
+ },
48
52
  {
49
53
  "key": "deviceSettings",
50
54
  "title": "Settings kept on this device"
@@ -4484,6 +4488,427 @@
4484
4488
  "defaultValue": null,
4485
4489
  "guides": []
4486
4490
  },
4491
+ {
4492
+ "id": "Avatar3dPrice",
4493
+ "name": "Avatar3dPrice",
4494
+ "anchor": "avatar3dprice",
4495
+ "group": "avatars3d",
4496
+ "signature": "interface Avatar3dPrice",
4497
+ "documented": true,
4498
+ "summary": "One price of a 3D avatar on sale, in one platform currency.",
4499
+ "deprecated": null,
4500
+ "params": [],
4501
+ "returns": null,
4502
+ "throws": null,
4503
+ "example": null,
4504
+ "defaultValue": null,
4505
+ "guides": []
4506
+ },
4507
+ {
4508
+ "id": "Avatar3dPrice.currencySlug",
4509
+ "name": "currencySlug",
4510
+ "anchor": "avatar3dprice-currencyslug",
4511
+ "group": "avatars3d",
4512
+ "signature": "readonly currencySlug: string",
4513
+ "documented": true,
4514
+ "summary": "`GEMS` or `ARCADE_TICKETS` — what to pass as `currencySlug`.",
4515
+ "deprecated": null,
4516
+ "params": [],
4517
+ "returns": null,
4518
+ "throws": null,
4519
+ "example": null,
4520
+ "defaultValue": null,
4521
+ "guides": []
4522
+ },
4523
+ {
4524
+ "id": "Avatar3dPrice.price",
4525
+ "name": "price",
4526
+ "anchor": "avatar3dprice-price",
4527
+ "group": "avatars3d",
4528
+ "signature": "readonly price: number",
4529
+ "documented": true,
4530
+ "summary": "The price, a whole number of that currency.",
4531
+ "deprecated": null,
4532
+ "params": [],
4533
+ "returns": null,
4534
+ "throws": null,
4535
+ "example": null,
4536
+ "defaultValue": null,
4537
+ "guides": []
4538
+ },
4539
+ {
4540
+ "id": "Avatar3dOffer",
4541
+ "name": "Avatar3dOffer",
4542
+ "anchor": "avatar3doffer",
4543
+ "group": "avatars3d",
4544
+ "signature": "interface Avatar3dOffer",
4545
+ "documented": true,
4546
+ "summary": "One of your game's 3D avatars on sale, as the platform prices it.",
4547
+ "deprecated": null,
4548
+ "params": [],
4549
+ "returns": null,
4550
+ "throws": null,
4551
+ "example": null,
4552
+ "defaultValue": null,
4553
+ "guides": []
4554
+ },
4555
+ {
4556
+ "id": "Avatar3dOffer.itemId",
4557
+ "name": "itemId",
4558
+ "anchor": "avatar3doffer-itemid",
4559
+ "group": "avatars3d",
4560
+ "signature": "readonly itemId: string",
4561
+ "documented": true,
4562
+ "summary": "The avatar's item id — what [Avatars3dApi.purchase](api-reference-game.md#avatars3dapi-purchase) takes.",
4563
+ "deprecated": null,
4564
+ "params": [],
4565
+ "returns": null,
4566
+ "throws": null,
4567
+ "example": null,
4568
+ "defaultValue": null,
4569
+ "guides": []
4570
+ },
4571
+ {
4572
+ "id": "Avatar3dOffer.slug",
4573
+ "name": "slug",
4574
+ "anchor": "avatar3doffer-slug",
4575
+ "group": "avatars3d",
4576
+ "signature": "readonly slug: string",
4577
+ "documented": true,
4578
+ "summary": "The slug you uploaded it under. A key, not display text.",
4579
+ "deprecated": null,
4580
+ "params": [],
4581
+ "returns": null,
4582
+ "throws": null,
4583
+ "example": null,
4584
+ "defaultValue": null,
4585
+ "guides": []
4586
+ },
4587
+ {
4588
+ "id": "Avatar3dOffer.family",
4589
+ "name": "family",
4590
+ "anchor": "avatar3doffer-family",
4591
+ "group": "avatars3d",
4592
+ "signature": "readonly family: Avatar3dFamily",
4593
+ "documented": true,
4594
+ "summary": "The rig family the model is built on.",
4595
+ "deprecated": null,
4596
+ "params": [],
4597
+ "returns": null,
4598
+ "throws": null,
4599
+ "example": null,
4600
+ "defaultValue": null,
4601
+ "guides": []
4602
+ },
4603
+ {
4604
+ "id": "Avatar3dOffer.displayName",
4605
+ "name": "displayName",
4606
+ "anchor": "avatar3doffer-displayname",
4607
+ "group": "avatars3d",
4608
+ "signature": "readonly displayName: string",
4609
+ "documented": true,
4610
+ "summary": "The avatar's name, as you uploaded it.",
4611
+ "deprecated": null,
4612
+ "params": [],
4613
+ "returns": null,
4614
+ "throws": null,
4615
+ "example": null,
4616
+ "defaultValue": null,
4617
+ "guides": []
4618
+ },
4619
+ {
4620
+ "id": "Avatar3dOffer.description",
4621
+ "name": "description",
4622
+ "anchor": "avatar3doffer-description",
4623
+ "group": "avatars3d",
4624
+ "signature": "readonly description?: string",
4625
+ "documented": true,
4626
+ "summary": "The avatar's description, when it has one.",
4627
+ "deprecated": null,
4628
+ "params": [],
4629
+ "returns": null,
4630
+ "throws": null,
4631
+ "example": null,
4632
+ "defaultValue": null,
4633
+ "guides": []
4634
+ },
4635
+ {
4636
+ "id": "Avatar3dOffer.iconUrl",
4637
+ "name": "iconUrl",
4638
+ "anchor": "avatar3doffer-iconurl",
4639
+ "group": "avatars3d",
4640
+ "signature": "readonly iconUrl: string",
4641
+ "documented": true,
4642
+ "summary": "A rendered portrait of the avatar, for a shop tile. Pass it through\n`platformIconUrl` before putting it in an `<img src>`.",
4643
+ "deprecated": null,
4644
+ "params": [],
4645
+ "returns": null,
4646
+ "throws": null,
4647
+ "example": null,
4648
+ "defaultValue": null,
4649
+ "guides": []
4650
+ },
4651
+ {
4652
+ "id": "Avatar3dOffer.modelUrl",
4653
+ "name": "modelUrl",
4654
+ "anchor": "avatar3doffer-modelurl",
4655
+ "group": "avatars3d",
4656
+ "signature": "readonly modelUrl: string",
4657
+ "documented": true,
4658
+ "summary": "The avatar's model, for a preview. Pass it through `platformIconUrl`\nbefore loading it.",
4659
+ "deprecated": null,
4660
+ "params": [],
4661
+ "returns": null,
4662
+ "throws": null,
4663
+ "example": null,
4664
+ "defaultValue": null,
4665
+ "guides": []
4666
+ },
4667
+ {
4668
+ "id": "Avatar3dOffer.prices",
4669
+ "name": "prices",
4670
+ "anchor": "avatar3doffer-prices",
4671
+ "group": "avatars3d",
4672
+ "signature": "readonly prices: readonly Avatar3dPrice[]",
4673
+ "documented": true,
4674
+ "summary": "One or two prices, each in a currency a player can pay with. Show each\none the player can choose; pass the chosen one's `currencySlug` to\n[Avatars3dApi.purchase](api-reference-game.md#avatars3dapi-purchase).",
4675
+ "deprecated": null,
4676
+ "params": [],
4677
+ "returns": null,
4678
+ "throws": null,
4679
+ "example": null,
4680
+ "defaultValue": null,
4681
+ "guides": []
4682
+ },
4683
+ {
4684
+ "id": "Avatars3dApi",
4685
+ "name": "Avatars3dApi",
4686
+ "anchor": "avatars3dapi",
4687
+ "group": "avatars3d",
4688
+ "signature": "interface Avatars3dApi",
4689
+ "documented": true,
4690
+ "summary": "Arcade 3D avatars — `gem.avatars3d`. What the player wears, the rig families\nyour game accepts, the avatars you sell, and buying one.\n\n```js\nfunction draw() {\n const worn = gem.avatars3d.worn();\n if (worn) loadCharacter(platformIconUrl(worn.modelUrl));\n else useDefaultCharacter();\n}\ndraw();\ngem.avatars3d.onChange(draw);\n```\n\nThe guide is `docs/arcade-avatars.md`: the rig, the config, uploading and\nselling with gemctl, and loading the model at runtime.",
4691
+ "deprecated": null,
4692
+ "params": [],
4693
+ "returns": null,
4694
+ "throws": null,
4695
+ "example": null,
4696
+ "defaultValue": null,
4697
+ "guides": []
4698
+ },
4699
+ {
4700
+ "id": "Avatars3dApi.families",
4701
+ "name": "families",
4702
+ "anchor": "avatars3dapi-families",
4703
+ "group": "avatars3d",
4704
+ "signature": "families(): Promise<readonly Avatar3dFamily[]>",
4705
+ "documented": true,
4706
+ "summary": "The rig families your game accepts, in the order your published config\ndeclares them (`avatars_3d.families`) — the first is your default.\n\nResolves `[]` when your game has no published config on this channel, or\nits config declares no families. Loads the config through\n`gem.gameConfig.load()` and shares that one fetch; rejects when the config\ncannot be loaded, as `load()` does.",
4707
+ "deprecated": null,
4708
+ "params": [],
4709
+ "returns": null,
4710
+ "throws": null,
4711
+ "example": null,
4712
+ "defaultValue": null,
4713
+ "guides": []
4714
+ },
4715
+ {
4716
+ "id": "Avatars3dApi.worn",
4717
+ "name": "worn",
4718
+ "anchor": "avatars3dapi-worn",
4719
+ "group": "avatars3d",
4720
+ "signature": "worn(family?: Avatar3dFamily): WornAvatar3d | undefined",
4721
+ "documented": true,
4722
+ "summary": "The 3D avatar the player wears — in `family` when you name one, otherwise\nin your game's first declared family that has one. `undefined` when none\nis worn there, before the player arrives, and outside the Arcade: draw\nyour default look.\n\nRead live from [Gem.player](api-reference-game.md#gem-player), so it changes when the player wears\nanother avatar or buys one; [Avatars3dApi.onChange](api-reference-game.md#avatars3dapi-onchange) tells you when.",
4723
+ "deprecated": null,
4724
+ "params": [
4725
+ {
4726
+ "name": "family",
4727
+ "type": "Avatar3dFamily",
4728
+ "optional": true,
4729
+ "summary": null
4730
+ }
4731
+ ],
4732
+ "returns": null,
4733
+ "throws": null,
4734
+ "example": null,
4735
+ "defaultValue": null,
4736
+ "guides": []
4737
+ },
4738
+ {
4739
+ "id": "Avatars3dApi.offers",
4740
+ "name": "offers",
4741
+ "anchor": "avatars3dapi-offers",
4742
+ "group": "avatars3d",
4743
+ "signature": "offers(): Promise<readonly Avatar3dOffer[]>",
4744
+ "documented": true,
4745
+ "summary": "Your game's 3D avatars on sale on this channel, with their prices, read\nfrom the platform with your game's own token. Render your shop from this,\nnever from prices of your own.\n\nResolves `[]` when you have nothing on sale. Rejects with a `GemApiError`\nwhen the platform cannot be read — a shop that cannot load says so.",
4746
+ "deprecated": null,
4747
+ "params": [],
4748
+ "returns": null,
4749
+ "throws": null,
4750
+ "example": null,
4751
+ "defaultValue": null,
4752
+ "guides": []
4753
+ },
4754
+ {
4755
+ "id": "Avatars3dApi.purchase",
4756
+ "name": "purchase",
4757
+ "anchor": "avatars3dapi-purchase",
4758
+ "group": "avatars3d",
4759
+ "signature": "purchase(request: Avatar3dPurchaseRequest): Promise<PurchaseOutcome>",
4760
+ "documented": true,
4761
+ "summary": "Buy one of your 3D avatars for the player and put it on. The Arcade\nprices it again, shows its own confirmation sheet, takes the currency and\nwears the avatar; your game then receives the player again, with the new\navatar worn, and [Avatars3dApi.onChange](api-reference-game.md#avatars3dapi-onchange) runs.\n\nCall it from the player's own click or key press — the Arcade shows no\nsheet without one and answers `declined` / `busy`.\n\nNOTHING REJECTS. Every outcome resolves, exactly as on\n`gem.purchases.request`:\n\n- `ok` — bought and worn, and the platform confirmed the player owns it.\n- `declined` — definitely not bought. `reason` says why:\n `already_owned` (the player has it — offer to wear it with\n `gem.openAvatarPicker({ tab: '3d' })`), `cancelled`, `insufficient_funds`,\n `no_price_in_currency`, `not_purchasable` (not on sale, or not an id from\n [Avatars3dApi.offers](api-reference-game.md#avatars3dapi-offers)), `busy`, and the rest of\n `PurchaseRefusalCode`. Return to your shop silently.\n- `unavailable` — no reliable answer. Show one neutral message, never\n retry on your own, and read [Avatars3dApi.worn](api-reference-game.md#avatars3dapi-worn) again.\n\n**Outside the Arcade** — opened at the top level of a page with no Arcade\naround it, or under an Arcade that does not sell 3D avatars yet — it\nresolves `unavailable` with reason `error` at once and asks nobody.",
4762
+ "deprecated": null,
4763
+ "params": [
4764
+ {
4765
+ "name": "request",
4766
+ "type": "Avatar3dPurchaseRequest",
4767
+ "optional": false,
4768
+ "summary": null
4769
+ }
4770
+ ],
4771
+ "returns": null,
4772
+ "throws": null,
4773
+ "example": null,
4774
+ "defaultValue": null,
4775
+ "guides": []
4776
+ },
4777
+ {
4778
+ "id": "Avatars3dApi.onChange",
4779
+ "name": "onChange",
4780
+ "anchor": "avatars3dapi-onchange",
4781
+ "group": "avatars3d",
4782
+ "signature": "onChange(listener: (worn: readonly WornAvatar3d[]) => void): () => void",
4783
+ "documented": true,
4784
+ "summary": "Call `listener` whenever the avatars the player wears change — they wore\nanother one in the Arcade's picker, bought one, or their avatars arrived\nfor the first time — with every worn avatar, one per family. Not called\nfor the avatars already worn when you subscribe: read\n[Avatars3dApi.worn](api-reference-game.md#avatars3dapi-worn) for those.",
4785
+ "deprecated": null,
4786
+ "params": [
4787
+ {
4788
+ "name": "listener",
4789
+ "type": "(worn: readonly WornAvatar3d[]) => void",
4790
+ "optional": false,
4791
+ "summary": null
4792
+ }
4793
+ ],
4794
+ "returns": "a function that stops the calls. Calling it twice is harmless.",
4795
+ "throws": null,
4796
+ "example": null,
4797
+ "defaultValue": null,
4798
+ "guides": []
4799
+ },
4800
+ {
4801
+ "id": "AvatarRigNode",
4802
+ "name": "AvatarRigNode",
4803
+ "anchor": "avatarrignode",
4804
+ "group": "avatars3d",
4805
+ "signature": "interface AvatarRigNode",
4806
+ "documented": true,
4807
+ "summary": "One node of a rig family's tree: its exact, case-sensitive name, and the\nname of the node it is a DIRECT child of. `parent` is `null` for the rig's\nroot, which is a root node of the scene.",
4808
+ "deprecated": null,
4809
+ "params": [],
4810
+ "returns": null,
4811
+ "throws": null,
4812
+ "example": null,
4813
+ "defaultValue": null,
4814
+ "guides": []
4815
+ },
4816
+ {
4817
+ "id": "AvatarRigNode.node",
4818
+ "name": "node",
4819
+ "anchor": "avatarrignode-node",
4820
+ "group": "avatars3d",
4821
+ "signature": "readonly node: string",
4822
+ "documented": true,
4823
+ "summary": "The node's name, exact and case-sensitive.",
4824
+ "deprecated": null,
4825
+ "params": [],
4826
+ "returns": null,
4827
+ "throws": null,
4828
+ "example": null,
4829
+ "defaultValue": null,
4830
+ "guides": []
4831
+ },
4832
+ {
4833
+ "id": "AvatarRigNode.parent",
4834
+ "name": "parent",
4835
+ "anchor": "avatarrignode-parent",
4836
+ "group": "avatars3d",
4837
+ "signature": "readonly parent: string | null",
4838
+ "documented": true,
4839
+ "summary": "The node it is a direct child of; `null` for the rig's root.",
4840
+ "deprecated": null,
4841
+ "params": [],
4842
+ "returns": null,
4843
+ "throws": null,
4844
+ "example": null,
4845
+ "defaultValue": null,
4846
+ "guides": []
4847
+ },
4848
+ {
4849
+ "id": "AvatarRigs",
4850
+ "name": "AvatarRigs",
4851
+ "anchor": "avatarrigs",
4852
+ "group": "avatars3d",
4853
+ "signature": "interface AvatarRigs",
4854
+ "documented": true,
4855
+ "summary": "The rig families this SDK knows, by name.",
4856
+ "deprecated": null,
4857
+ "params": [],
4858
+ "returns": null,
4859
+ "throws": null,
4860
+ "example": null,
4861
+ "defaultValue": null,
4862
+ "guides": []
4863
+ },
4864
+ {
4865
+ "id": "AvatarRigs.small_human",
4866
+ "name": "small_human",
4867
+ "anchor": "avatarrigs-small_human",
4868
+ "group": "avatars3d",
4869
+ "signature": "readonly small_human: readonly AvatarRigNode[]",
4870
+ "documented": true,
4871
+ "summary": "The toy rig: rigid limbs on a body, eight nodes.",
4872
+ "deprecated": null,
4873
+ "params": [],
4874
+ "returns": null,
4875
+ "throws": null,
4876
+ "example": null,
4877
+ "defaultValue": null,
4878
+ "guides": []
4879
+ },
4880
+ {
4881
+ "id": "AvatarRigs.adult_human",
4882
+ "name": "adult_human",
4883
+ "anchor": "avatarrigs-adult_human",
4884
+ "group": "avatars3d",
4885
+ "signature": "readonly adult_human: readonly AvatarRigNode[]",
4886
+ "documented": true,
4887
+ "summary": "The adult humanoid with fingers: 56 nodes, world-aligned at rest.",
4888
+ "deprecated": null,
4889
+ "params": [],
4890
+ "returns": null,
4891
+ "throws": null,
4892
+ "example": null,
4893
+ "defaultValue": null,
4894
+ "guides": []
4895
+ },
4896
+ {
4897
+ "id": "AVATAR_RIGS",
4898
+ "name": "AVATAR_RIGS",
4899
+ "anchor": "avatar_rigs",
4900
+ "group": "avatars3d",
4901
+ "signature": "AVATAR_RIGS: AvatarRigs",
4902
+ "documented": true,
4903
+ "summary": "Every node each rig family requires, with the node it is a direct child of,\nparents before children. Frozen: read it, never change it.\n\nThe platform refuses an upload whose model is missing one of these, has one\nunder another parent, or has one twice (`rig_mismatch`). Extra nodes are\nallowed anywhere. `adult_human` also requires every one of its nodes to be\nworld-aligned at rest; that rule is not in this list, see the guide.\n\n```js\nimport { AVATAR_RIGS } from '@indiegems/gem-web-sdk/sdk';\n\n// Every joint of the worn avatar, by name.\nconst joints = new Map(\n AVATAR_RIGS.adult_human.map(({ node }) => [node, gltf.scene.getObjectByName(node)]),\n);\n\n// A test that one of your own models is on the rig.\nfor (const { node, parent } of AVATAR_RIGS.adult_human) {\n const found = gltf.scene.getObjectByName(node);\n if (!found) throw new Error(`missing ${node}`);\n if (parent !== null && found.parent?.name !== parent) throw new Error(`${node} is not under ${parent}`);\n}\n```",
4904
+ "deprecated": null,
4905
+ "params": [],
4906
+ "returns": null,
4907
+ "throws": null,
4908
+ "example": null,
4909
+ "defaultValue": null,
4910
+ "guides": []
4911
+ },
4487
4912
  {
4488
4913
  "id": "GemApiError",
4489
4914
  "name": "GemApiError",
@@ -5783,7 +6208,7 @@
5783
6208
  "group": "controls",
5784
6209
  "signature": "interface ControlsApi",
5785
6210
  "documented": true,
5786
- "summary": "`gem.controls` — the player's camera and controls settings: tell the Arcade\nwhich ones your game leaves to the player, and turn look input into a camera\nturn at the player's sensitivity.",
6211
+ "summary": "`gem.controls` — the player's camera and controls settings: turn look input\ninto a camera turn at the player's sensitivity.",
5787
6212
  "deprecated": null,
5788
6213
  "params": [],
5789
6214
  "returns": null,
@@ -5797,29 +6222,6 @@
5797
6222
  }
5798
6223
  ]
5799
6224
  },
5800
- {
5801
- "id": "ControlsApi.declare",
5802
- "name": "declare",
5803
- "anchor": "controlsapi-declare",
5804
- "group": "controls",
5805
- "signature": "declare(uses: GemControlsDeclaration): boolean",
5806
- "documented": true,
5807
- "summary": "Tell the Arcade which of the player's camera and controls settings your\ngame uses, and for each one who decides it: `'player'` — you apply the\nArcade's value from `gem.prefs.recommended` (or the player's own choice\nin your settings, which wins) — or `'game'` — you decide it yourself, and\nthe Arcade's Settings shows that row as \"Set by this game\". Leave out a\nsetting your game does not use.\n\n```js\ngem.controls.declare({\n lookSensitivity: 'player',\n gamepadLookSensitivity: 'player',\n invertLookY: 'player',\n touchMovement: 'game', // this game always uses a fixed stick\n});\n```\n\n**It is screen state, not a toggle.** Each call is the whole answer and\nreplaces the last. The SDK holds it and sends it again after every\nreconnect, so call it once at boot, and again only when the answer\nchanges. Any other key or value is dropped.",
5808
- "deprecated": null,
5809
- "params": [
5810
- {
5811
- "name": "uses",
5812
- "type": "GemControlsDeclaration",
5813
- "optional": false,
5814
- "summary": null
5815
- }
5816
- ],
5817
- "returns": "`true` when the Arcade was told now; `false` before the bridge is\n up (the declaration is held and sent when it connects), outside the\n Arcade, and under an embedder that does not show these settings. Never\n throws.",
5818
- "throws": null,
5819
- "example": null,
5820
- "defaultValue": null,
5821
- "guides": []
5822
- },
5823
6225
  {
5824
6226
  "id": "ControlsApi.look",
5825
6227
  "name": "look",
@@ -5859,102 +6261,6 @@
5859
6261
  "defaultValue": null,
5860
6262
  "guides": []
5861
6263
  },
5862
- {
5863
- "id": "GemControlChoice",
5864
- "name": "GemControlChoice",
5865
- "anchor": "gemcontrolchoice",
5866
- "group": "controls",
5867
- "signature": "type GemControlChoice = \"player\" | \"game\"",
5868
- "documented": true,
5869
- "summary": "Who decides one camera or controls setting: `'player'` — your game applies\nthe Arcade's value, or the player's own in-game choice, which wins — or\n`'game'` — your game decides it itself, and the Arcade's Settings shows the\nrow as \"Set by this game\".",
5870
- "deprecated": null,
5871
- "params": [],
5872
- "returns": null,
5873
- "throws": null,
5874
- "example": null,
5875
- "defaultValue": null,
5876
- "guides": []
5877
- },
5878
- {
5879
- "id": "GemControlsDeclaration",
5880
- "name": "GemControlsDeclaration",
5881
- "anchor": "gemcontrolsdeclaration",
5882
- "group": "controls",
5883
- "signature": "interface GemControlsDeclaration",
5884
- "documented": true,
5885
- "summary": "What `gem.controls.declare` takes: for each of the player's camera and\ncontrols settings your game uses, who decides it. Leave a setting out when\nyour game does not use it at all — the Arcade then says \"This game doesn't\nuse this\" beside that row.",
5886
- "deprecated": null,
5887
- "params": [],
5888
- "returns": null,
5889
- "throws": null,
5890
- "example": null,
5891
- "defaultValue": null,
5892
- "guides": []
5893
- },
5894
- {
5895
- "id": "GemControlsDeclaration.lookSensitivity",
5896
- "name": "lookSensitivity",
5897
- "anchor": "gemcontrolsdeclaration-looksensitivity",
5898
- "group": "controls",
5899
- "signature": "readonly lookSensitivity?: GemControlChoice",
5900
- "documented": true,
5901
- "summary": "Look speed for touch, mouse and pen — `gem.prefs.recommended.lookSensitivity`.",
5902
- "deprecated": null,
5903
- "params": [],
5904
- "returns": null,
5905
- "throws": null,
5906
- "example": null,
5907
- "defaultValue": null,
5908
- "guides": []
5909
- },
5910
- {
5911
- "id": "GemControlsDeclaration.gamepadLookSensitivity",
5912
- "name": "gamepadLookSensitivity",
5913
- "anchor": "gemcontrolsdeclaration-gamepadlooksensitivity",
5914
- "group": "controls",
5915
- "signature": "readonly gamepadLookSensitivity?: GemControlChoice",
5916
- "documented": true,
5917
- "summary": "Look speed for a gamepad stick — `gem.prefs.recommended.gamepadLookSensitivity`.",
5918
- "deprecated": null,
5919
- "params": [],
5920
- "returns": null,
5921
- "throws": null,
5922
- "example": null,
5923
- "defaultValue": null,
5924
- "guides": []
5925
- },
5926
- {
5927
- "id": "GemControlsDeclaration.invertLookY",
5928
- "name": "invertLookY",
5929
- "anchor": "gemcontrolsdeclaration-invertlooky",
5930
- "group": "controls",
5931
- "signature": "readonly invertLookY?: GemControlChoice",
5932
- "documented": true,
5933
- "summary": "Inverted up/down look — `gem.prefs.recommended.invertLookY`.",
5934
- "deprecated": null,
5935
- "params": [],
5936
- "returns": null,
5937
- "throws": null,
5938
- "example": null,
5939
- "defaultValue": null,
5940
- "guides": []
5941
- },
5942
- {
5943
- "id": "GemControlsDeclaration.touchMovement",
5944
- "name": "touchMovement",
5945
- "anchor": "gemcontrolsdeclaration-touchmovement",
5946
- "group": "controls",
5947
- "signature": "readonly touchMovement?: GemControlChoice",
5948
- "documented": true,
5949
- "summary": "The touch movement thumbstick, floating or fixed — `gem.prefs.recommended.touchMovement`.",
5950
- "deprecated": null,
5951
- "params": [],
5952
- "returns": null,
5953
- "throws": null,
5954
- "example": null,
5955
- "defaultValue": null,
5956
- "guides": []
5957
- },
5958
6264
  {
5959
6265
  "id": "DEVICE_SETTINGS_MAX_BYTES",
5960
6266
  "name": "DEVICE_SETTINGS_MAX_BYTES",
@@ -6152,7 +6458,7 @@
6152
6458
  "group": "platform",
6153
6459
  "signature": "interface RewardCue",
6154
6460
  "documented": true,
6155
- "summary": "A platform-currency reward on its way to the arcade — in the Gem Arcade, a\nflight into the Gem button at the top-left of your frame — what\n`gem.cueReward` takes.",
6461
+ "summary": "A platform-currency reward on its way to the arcade — in the Gem Arcade, a\nflight into the Gem button, in whichever corner of your frame it sits — what\n`gem.cueReward` takes.",
6156
6462
  "deprecated": null,
6157
6463
  "params": [],
6158
6464
  "returns": null,
@@ -6241,6 +6547,22 @@
6241
6547
  "defaultValue": null,
6242
6548
  "guides": []
6243
6549
  },
6550
+ {
6551
+ "id": "GemCorner",
6552
+ "name": "GemCorner",
6553
+ "anchor": "gemcorner",
6554
+ "group": "frame",
6555
+ "signature": "type GemCorner = \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\"",
6556
+ "documented": true,
6557
+ "summary": "A corner of your frame, for `gem.setGemCorner`: where you would like the\narcade's Gem button to sit. `'top-left'` is where it sits unless you ask.\nPhysical corners: `'top-left'` is the top-left in every language.",
6558
+ "deprecated": null,
6559
+ "params": [],
6560
+ "returns": null,
6561
+ "throws": null,
6562
+ "example": null,
6563
+ "defaultValue": null,
6564
+ "guides": []
6565
+ },
6244
6566
  {
6245
6567
  "id": "FrameState",
6246
6568
  "name": "FrameState",
@@ -6553,52 +6875,46 @@
6553
6875
  "guides": []
6554
6876
  },
6555
6877
  {
6556
- "id": "GemFrame.declareControls",
6557
- "name": "declareControls",
6558
- "anchor": "gemframe-declarecontrols",
6878
+ "id": "GemFrame.setMenu",
6879
+ "name": "setMenu",
6880
+ "anchor": "gemframe-setmenu",
6559
6881
  "group": "frame",
6560
- "signature": "declareControls(uses: GemControlsDeclaration): boolean",
6882
+ "signature": "setMenu(items?: readonly { id: string; label: string }[]): boolean",
6561
6883
  "documented": true,
6562
- "summary": "Tell the arcade which of the player's camera and controls settings your\ngame leaves to the player and which it decides itself — the whole answer\neach time, the latest one winning.\n\nPrefer `gem.controls.declare`, which holds the declaration across a\nreconnect. This one sends only while connected, and only to an arcade\nthat shows these settings.",
6884
+ "summary": "Tell the arcade what your game adds to its menu: up to four entries of its\nown — the whole answer each time, the latest one winning. Your settings\nare not among them: a game opens its settings from its own button.\n\nPrefer `gem.setMenu`, which holds the state across a reconnect and calls\nyour handlers when the player chooses an entry. This one sends only while\nconnected, and only to an arcade that draws a menu.",
6563
6885
  "deprecated": null,
6564
6886
  "params": [
6565
6887
  {
6566
- "name": "uses",
6567
- "type": "GemControlsDeclaration",
6568
- "optional": false,
6888
+ "name": "items",
6889
+ "type": "readonly { id: string; label: string }[]",
6890
+ "optional": true,
6569
6891
  "summary": null
6570
6892
  }
6571
6893
  ],
6572
- "returns": "whether the message left the frame — `false` before the bridge is\n up, for an arcade that does not show these settings, and past this\n frame's own limit.",
6894
+ "returns": "whether the message left the frame — `false` before the bridge is\n up, for an arcade with no menu, and past this frame's own limit.",
6573
6895
  "throws": null,
6574
6896
  "example": null,
6575
6897
  "defaultValue": null,
6576
6898
  "guides": []
6577
6899
  },
6578
6900
  {
6579
- "id": "GemFrame.setMenu",
6580
- "name": "setMenu",
6581
- "anchor": "gemframe-setmenu",
6901
+ "id": "GemFrame.setGemCorner",
6902
+ "name": "setGemCorner",
6903
+ "anchor": "gemframe-setgemcorner",
6582
6904
  "group": "frame",
6583
- "signature": "setMenu(settings: boolean, items?: readonly { id: string; label: string }[]): boolean",
6905
+ "signature": "setGemCorner(corner: GemCorner): boolean",
6584
6906
  "documented": true,
6585
- "summary": "Tell the arcade what your game adds to its menu: whether it has a settings\nscreen, and up to four entries of its own — the whole answer each time, the\nlatest one winning.\n\nPrefer `gem.setMenu`, which holds the state across a reconnect and calls\nyour handlers when the player chooses an entry. This one sends only while\nconnected, and only to an arcade that draws a menu.",
6907
+ "summary": "Ask the arcade to put its own control over your game — the Gem button —\nin this corner of your frame: the whole answer each time, the latest one\nwinning.\n\nPrefer `gem.setGemCorner`, which holds the corner across a reconnect.\nThis one sends only while connected, and only to an arcade that can move\nits control.",
6586
6908
  "deprecated": null,
6587
6909
  "params": [
6588
6910
  {
6589
- "name": "settings",
6590
- "type": "boolean",
6911
+ "name": "corner",
6912
+ "type": "GemCorner",
6591
6913
  "optional": false,
6592
6914
  "summary": null
6593
- },
6594
- {
6595
- "name": "items",
6596
- "type": "readonly { id: string; label: string }[]",
6597
- "optional": true,
6598
- "summary": null
6599
6915
  }
6600
6916
  ],
6601
- "returns": "whether the message left the frame — `false` before the bridge is\n up, for an arcade with no menu, and past this frame's own limit.",
6917
+ "returns": "whether the message left the frame — `false` before the bridge is\n up, for an arcade that cannot move its control, for a value that is not\n one of the four corners, and past this frame's own limit.",
6602
6918
  "throws": null,
6603
6919
  "example": null,
6604
6920
  "defaultValue": null,
@@ -6879,6 +7195,22 @@
6879
7195
  "defaultValue": null,
6880
7196
  "guides": []
6881
7197
  },
7198
+ {
7199
+ "id": "Friend.avatars3d",
7200
+ "name": "avatars3d",
7201
+ "anchor": "friend-avatars3d",
7202
+ "group": "friends",
7203
+ "signature": "readonly avatars3d?: readonly WornAvatar3d[]",
7204
+ "documented": true,
7205
+ "summary": "Every 3D avatar the player wears, one per rig family, in the order the game\ndeclared its families. Absent or empty means none is worn — draw your\ndefault look.\n\n[PlayerIdentity.avatar3dId](api-reference-game.md#playeridentity-avatar3did) and its two URLs are one of these: the\navatar of the game's first declared family that has one. A game that\naccepts a single family can read either; one that accepts several picks by\n`family` here. Absent too where the platform does not send it yet — read\nthe flat fields then.",
7206
+ "deprecated": null,
7207
+ "params": [],
7208
+ "returns": null,
7209
+ "throws": null,
7210
+ "example": null,
7211
+ "defaultValue": null,
7212
+ "guides": []
7213
+ },
6882
7214
  {
6883
7215
  "id": "Friend.badges",
6884
7216
  "name": "badges",
@@ -8697,6 +9029,22 @@
8697
9029
  "defaultValue": null,
8698
9030
  "guides": []
8699
9031
  },
9032
+ {
9033
+ "id": "GemPlayer.avatars3d",
9034
+ "name": "avatars3d",
9035
+ "anchor": "gemplayer-avatars3d",
9036
+ "group": "player",
9037
+ "signature": "readonly avatars3d?: readonly WornAvatar3d[]",
9038
+ "documented": true,
9039
+ "summary": "Every 3D avatar the player wears, one per rig family, in the order the game\ndeclared its families. Absent or empty means none is worn — draw your\ndefault look.\n\n[PlayerIdentity.avatar3dId](api-reference-game.md#playeridentity-avatar3did) and its two URLs are one of these: the\navatar of the game's first declared family that has one. A game that\naccepts a single family can read either; one that accepts several picks by\n`family` here. Absent too where the platform does not send it yet — read\nthe flat fields then.",
9040
+ "deprecated": null,
9041
+ "params": [],
9042
+ "returns": null,
9043
+ "throws": null,
9044
+ "example": null,
9045
+ "defaultValue": null,
9046
+ "guides": []
9047
+ },
8700
9048
  {
8701
9049
  "id": "GemPlayer.badges",
8702
9050
  "name": "badges",
@@ -9204,7 +9552,7 @@
9204
9552
  "group": "player",
9205
9553
  "signature": "readonly reserved: readonly GemReservedArea[]",
9206
9554
  "documented": true,
9207
- "summary": "Rectangles of your frame the arcade's own controls cover — in the Gem\nArcade, its Gem button in the top-left corner, and the frame-rate readout\nbeside it while the player shows it; an embedder that draws wallets over\nyour game adds theirs (see `setWalletVisible`). Keep essential UI and touch\ntargets out of them. Empty when the arcade covers nothing.\n\nA stylesheet reads the per-corner custom properties the SDK stamps from\nthese instead — `--gem-reserved-tl-height` and friends.",
9555
+ "summary": "Rectangles of your frame the arcade's own controls cover — in the Gem\nArcade, its Gem button in whichever corner it sits, and the frame-rate readout\nbeside it while the player shows it; an embedder that draws wallets over\nyour game adds theirs (see `setWalletVisible`). Keep essential UI and touch\ntargets out of them. Empty when the arcade covers nothing.\n\nA stylesheet reads the per-corner custom properties the SDK stamps from\nthese instead — `--gem-reserved-tl-height` and friends.",
9208
9556
  "deprecated": null,
9209
9557
  "params": [],
9210
9558
  "returns": null,
@@ -9396,23 +9744,7 @@
9396
9744
  "group": "frame",
9397
9745
  "signature": "interface GameMenu",
9398
9746
  "documented": true,
9399
- "summary": "What your game adds to the arcade's menu — see `gem.setMenu`.",
9400
- "deprecated": null,
9401
- "params": [],
9402
- "returns": null,
9403
- "throws": null,
9404
- "example": null,
9405
- "defaultValue": null,
9406
- "guides": []
9407
- },
9408
- {
9409
- "id": "GameMenu.onSettings",
9410
- "name": "onSettings",
9411
- "anchor": "gamemenu-onsettings",
9412
- "group": "frame",
9413
- "signature": "readonly onSettings?: () => void",
9414
- "documented": true,
9415
- "summary": "Open your settings screen. Another embedder may show a Settings entry that\ncalls it, but **the Gem Arcade no longer does**: draw a settings button of\nyour own, always (top-right is the recommendation). Passing this is still\naccepted and harmless.",
9747
+ "summary": "What your game adds to the arcade's menu — see `gem.setMenu`. Your settings\nare not part of it: they open from your own settings button.",
9416
9748
  "deprecated": null,
9417
9749
  "params": [],
9418
9750
  "returns": null,
@@ -9877,6 +10209,22 @@
9877
10209
  "defaultValue": null,
9878
10210
  "guides": []
9879
10211
  },
10212
+ {
10213
+ "id": "Gem.avatars3d",
10214
+ "name": "avatars3d",
10215
+ "anchor": "gem-avatars3d",
10216
+ "group": "start",
10217
+ "signature": "readonly avatars3d: Avatars3dApi",
10218
+ "documented": true,
10219
+ "summary": "Arcade 3D avatars: what the player wears, the rig families your game\naccepts, the avatars your game sells, and buying one — which wears it.\n2D portraits are a separate system and are not here.\n\n```js\nconst draw = () => {\n const worn = gem.avatars3d.worn();\n const url = worn && platformIconUrl(worn.modelUrl);\n if (url) loadCharacter(url);\n else useDefaultCharacter();\n};\ndraw();\ngem.avatars3d.onChange(draw);\n```\n\nSee [Arcade avatars](../arcade-avatars.md) for the rig, the config, and\nuploading and selling avatars with gemctl.",
10220
+ "deprecated": null,
10221
+ "params": [],
10222
+ "returns": null,
10223
+ "throws": null,
10224
+ "example": null,
10225
+ "defaultValue": null,
10226
+ "guides": []
10227
+ },
9880
10228
  {
9881
10229
  "id": "Gem.vibration",
9882
10230
  "name": "vibration",
@@ -9905,7 +10253,7 @@
9905
10253
  "group": "start",
9906
10254
  "signature": "readonly controls: ControlsApi",
9907
10255
  "documented": true,
9908
- "summary": "The player's camera and controls settings — look sensitivity, gamepad look\nsensitivity, invert look and touch movement, which arrive in\n`gem.prefs.recommended`. Two things to do with them:\n\n```js\n// 1. Tell the Arcade which ones your game leaves to the player.\ngem.controls.declare({ lookSensitivity: 'player', invertLookY: 'player' });\n\n// 2. Turn look input into a camera turn at the player's sensitivity.\nconst look = gem.controls.look();\n// dx, dy: the pointer's move in CSS px since the last event.\nconst t = look.fromPointer(dx, dy, e.pointerType, camera.pitch);\ncamera.yaw += t.yaw;\ncamera.pitch += t.pitch;\n```\n\nSee the guide's \"Camera and look controls\" section.",
10256
+ "summary": "The player's camera and controls settings — look sensitivity, gamepad look\nsensitivity, invert look and touch movement, which arrive in\n`gem.prefs.recommended` as the player's Arcade-wide defaults. Turn look\ninput into a camera turn at the player's sensitivity:\n\n```js\nconst look = gem.controls.look();\n// dx, dy: the pointer's move in CSS px since the last event.\nconst t = look.fromPointer(dx, dy, e.pointerType, camera.pitch);\ncamera.yaw += t.yaw;\ncamera.pitch += t.pitch;\n```\n\nSee the guide's \"Camera and look controls\" section.",
9909
10257
  "deprecated": null,
9910
10258
  "params": [],
9911
10259
  "returns": null,
@@ -10215,7 +10563,7 @@
10215
10563
  "group": "start",
10216
10564
  "signature": "cueReward(cue: RewardCue): boolean",
10217
10565
  "documented": true,
10218
- "summary": "Tell the arcade a platform-currency reward is on its way, so it **shows it\nlanding**: in the Gem Arcade the currency flies from `origin` into the Gem\nbutton at the top-left of your frame, which pops as each mark lands, and\nthe player's total moves once the platform confirms the grant. The Gem\nArcade draws no wallet over your game: the player sees their Gems and\nTickets in the Gem menu. A payout you do not cue still flies, from the\nmiddle of your frame, so cue the ones the player earns on screen.\n\nCall it the moment your game decides to award — when your server has moved\n(or is about to move) the progression mapped onto a platform currency —\nand pass the centre of that currency's icon (or hero) on your screen, so\nthe flight starts from the mark the player is looking at:\n\n```js\nconst { x, y, width, height } = rewardGem.getBoundingClientRect(); // the gem's own box\ngem.cueReward({\n currencySlug: 'GEMS', // or 'ARCADE_TICKETS', from the ticket\n expected: 15,\n origin: { x: x + width / 2, y: y + height / 2 },\n});\n```\n\n**This is the platform-currency animation, and the only one.** The display\nof every platform currency — Gems and Tickets alike — belongs to the\narcade: a game draws no flight or celebration of its own for one and keeps\nno counter of one. **If this returns `false`, do nothing** — there is no\nfallback celebration.\n\n**The arcade never takes your word for the amount.** What lands, and the\nplayer's new total, come from the platform itself; `expected` is drawn as\nprovisional, and a cue the platform never confirms fades on its own — a\ngrant clamped to zero, say, or one the receipt reports `suppressed`. So\nthe cue can only ever make the wallet move for a reward that really\nlanded — which it does on every channel, dev and test included.",
10566
+ "summary": "Tell the arcade a platform-currency reward is on its way, so it **shows it\nlanding**: in the Gem Arcade the currency flies from `origin` into the Gem\nbutton, wherever on your frame it sits, which pops as each mark lands, and\nthe player's total moves once the platform confirms the grant. The Gem\nArcade draws no wallet over your game: the player sees their Gems and\nTickets in the Gem menu. A payout you do not cue still flies, from the\nmiddle of your frame, so cue the ones the player earns on screen.\n\nCall it the moment your game decides to award — when your server has moved\n(or is about to move) the progression mapped onto a platform currency —\nand pass the centre of that currency's icon (or hero) on your screen, so\nthe flight starts from the mark the player is looking at:\n\n```js\nconst { x, y, width, height } = rewardGem.getBoundingClientRect(); // the gem's own box\ngem.cueReward({\n currencySlug: 'GEMS', // or 'ARCADE_TICKETS', from the ticket\n expected: 15,\n origin: { x: x + width / 2, y: y + height / 2 },\n});\n```\n\n**This is the platform-currency animation, and the only one.** The display\nof every platform currency — Gems and Tickets alike — belongs to the\narcade: a game draws no flight or celebration of its own for one and keeps\nno counter of one. **If this returns `false`, do nothing** — there is no\nfallback celebration.\n\n**The arcade never takes your word for the amount.** What lands, and the\nplayer's new total, come from the platform itself; `expected` is drawn as\nprovisional, and a cue the platform never confirms fades on its own — a\ngrant clamped to zero, say, or one the receipt reports `suppressed`. So\nthe cue can only ever make the wallet move for a reward that really\nlanded — which it does on every channel, dev and test included.",
10219
10567
  "deprecated": null,
10220
10568
  "params": [
10221
10569
  {
@@ -10267,14 +10615,14 @@
10267
10615
  "group": "start",
10268
10616
  "signature": "setMenu(menu: GameMenu): boolean",
10269
10617
  "documented": true,
10270
- "summary": "Put up to four entries of your own in the arcade's menu. The arcade keeps\none menu button over your game: it is how a player leaves, mutes, goes\nfullscreen, reports a problem and changes their Arcade-wide settings.\n\n```js\ngem.setMenu({\n items: [{ id: 'how-to-play', label: t('How to play'), onSelect: () => help.open() }],\n});\n```\n\n**Your settings are not in it: draw your own settings button, always** —\ntop-right is the recommendation. `onSettings` is still accepted, and\nanother embedder may show an entry for it, but the Gem Arcade no longer\nshows a Settings entry, so a game whose settings open only from there has\nnone a player can reach.\n\n**It is screen state, not a toggle.** Each call is the whole answer and\nreplaces the last, so call it again when the entries change — a new\nlanguage, a screen with different options. `gem.setMenu({})` takes\neverything of yours back out. The arcade's own entries are not yours to\nchange, and Return to the Arcade is always among them.\n\n**The arcade pauses your game while its menu is open** (`onPause`), and\nresumes it before a handler of yours runs. A handler opens a screen; it\nshould not assume the game was paused by you.",
10618
+ "summary": "Put up to four entries of your own in the arcade's menu. The arcade keeps\none menu button over your game: it is how a player leaves, mutes, goes\nfullscreen, reports a problem and changes their Arcade-wide settings.\n\n```js\ngem.setMenu({\n items: [{ id: 'how-to-play', label: t('How to play'), onSelect: () => help.open() }],\n});\n```\n\n**Your settings are not in it: draw your own settings button, always** —\ntop-right, with your Back button just to its left, clear of\n`gem.viewport.reserved`. The Arcade's menu never opens your settings\nscreen.\n\n**It is screen state, not a toggle.** Each call is the whole answer and\nreplaces the last, so call it again when the entries change — a new\nlanguage, a screen with different options. `gem.setMenu({})` takes\neverything of yours back out. The arcade's own entries are not yours to\nchange, and Return to the Arcade is always among them.\n\n**The arcade pauses your game while its menu is open** (`onPause`), and\nresumes it before a handler of yours runs. A handler opens a screen; it\nshould not assume the game was paused by you.",
10271
10619
  "deprecated": null,
10272
10620
  "params": [
10273
10621
  {
10274
10622
  "name": "menu",
10275
10623
  "type": "GameMenu",
10276
10624
  "optional": false,
10277
- "summary": "Your settings handler and your own entries."
10625
+ "summary": "Your own entries."
10278
10626
  }
10279
10627
  ],
10280
10628
  "returns": "`true` when the arcade was told now; `false` before the bridge is\n up (the menu is held and sent when it connects) and under an embedder\n that draws no menu.",
@@ -10283,6 +10631,29 @@
10283
10631
  "defaultValue": null,
10284
10632
  "guides": []
10285
10633
  },
10634
+ {
10635
+ "id": "Gem.setGemCorner",
10636
+ "name": "setGemCorner",
10637
+ "anchor": "gem-setgemcorner",
10638
+ "group": "start",
10639
+ "signature": "setGemCorner(corner: GemCorner): boolean",
10640
+ "documented": true,
10641
+ "summary": "Ask the arcade to put its Gem button — its one control over your game,\ntop-left unless you ask — in another corner of your frame.\n\n```js\n// This game's own HUD needs the top-left, so the Gem goes top-right.\ngem.setGemCorner('top-right');\n```\n\n**Leave it top-left unless your own HUD needs that corner**, and then pick\none corner for the whole game — at most one per orientation. A Gem that\nmoves between screens is a way out the player has to look for. In\nlandscape the bottom corners sit where a player's thumbs hold the sticks.\n\n**Lay out around `gem.viewport.reserved`, never around the corner you\nasked for.** The arcade moves the Gem on its own terms: not while the\nplayer is using it or its menu is open, not more often than every few\nseconds, and never over a corner the player chose for themselves. The\nrectangles say where it actually is, and the `--gem-reserved-*` CSS\nproperties follow them.\n\n**It is screen state, not a toggle.** Each call is the whole answer and\nreplaces the last. A value that is not one of the four corners is ignored,\nwith a warning on the console, and changes nothing.",
10642
+ "deprecated": null,
10643
+ "params": [
10644
+ {
10645
+ "name": "corner",
10646
+ "type": "GemCorner",
10647
+ "optional": false,
10648
+ "summary": "`'top-left'`, `'top-right'`, `'bottom-left'` or `'bottom-right'`."
10649
+ }
10650
+ ],
10651
+ "returns": "`true` when the arcade was told now; `false` before the bridge is\n up (the corner is held and sent when it connects), under an embedder that\n cannot move its control, and for a value that is not a corner.",
10652
+ "throws": null,
10653
+ "example": null,
10654
+ "defaultValue": null,
10655
+ "guides": []
10656
+ },
10286
10657
  {
10287
10658
  "id": "Gem.exit",
10288
10659
  "name": "exit",
@@ -11167,7 +11538,7 @@
11167
11538
  "name": "protocol",
11168
11539
  "anchor": "matchconnection-protocol",
11169
11540
  "group": "match",
11170
- "signature": "readonly protocol: \"dedicated\" | \"p2p\" | \"loopback\"",
11541
+ "signature": "readonly protocol: \"p2p\" | \"dedicated\" | \"loopback\"",
11171
11542
  "documented": true,
11172
11543
  "summary": "What is on the other end: `dedicated` (the platform's server), `p2p` (the\nhosting player's browser), or `loopback` (this browser — you are the host,\nand this is the in-process link to your own bundle).",
11173
11544
  "deprecated": null,
@@ -11417,6 +11788,102 @@
11417
11788
  "defaultValue": null,
11418
11789
  "guides": []
11419
11790
  },
11791
+ {
11792
+ "id": "Avatar3dFamily",
11793
+ "name": "Avatar3dFamily",
11794
+ "anchor": "avatar3dfamily",
11795
+ "group": "avatars3d",
11796
+ "signature": "type Avatar3dFamily = \"small_human\" | \"adult_human\" | string & object",
11797
+ "documented": true,
11798
+ "summary": "A rig family — the skeleton contract a 3D avatar is built on, and what a\ngame declares it can animate. Two today:\n\n- `small_human`, the toy rig: a `root` node with a `body` child, which holds\n `head`, `arm_l`, `arm_r`, `leg_l` and `leg_r`, and a `hand_socket` under\n `arm_r`.\n- `adult_human`, a full humanoid with fingers: 56 nodes from `root` and\n `hips` down to `toes_r`, every one world-aligned at rest, so one clip plays\n on every avatar in the family.\n\n`AVATAR_RIGS` (from `@indiegems/gem-web-sdk/sdk`) lists every node of\nboth, with its parent.\n\nOpen-ended (`| (string & {})`) so a family the platform adds later does not\nbreak a `switch` you compiled against this version: keep a `default` arm and\nskip a family you do not draw.",
11799
+ "deprecated": null,
11800
+ "params": [],
11801
+ "returns": null,
11802
+ "throws": null,
11803
+ "example": null,
11804
+ "defaultValue": null,
11805
+ "guides": []
11806
+ },
11807
+ {
11808
+ "id": "WornAvatar3d",
11809
+ "name": "WornAvatar3d",
11810
+ "anchor": "wornavatar3d",
11811
+ "group": "avatars3d",
11812
+ "signature": "interface WornAvatar3d",
11813
+ "documented": true,
11814
+ "summary": "One 3D avatar a player wears — at most one per [Avatar3dFamily](api-reference-game.md#avatar3dfamily).",
11815
+ "deprecated": null,
11816
+ "params": [],
11817
+ "returns": null,
11818
+ "throws": null,
11819
+ "example": null,
11820
+ "defaultValue": null,
11821
+ "guides": []
11822
+ },
11823
+ {
11824
+ "id": "WornAvatar3d.family",
11825
+ "name": "family",
11826
+ "anchor": "wornavatar3d-family",
11827
+ "group": "avatars3d",
11828
+ "signature": "readonly family: Avatar3dFamily",
11829
+ "documented": true,
11830
+ "summary": "The rig family this avatar is built on.",
11831
+ "deprecated": null,
11832
+ "params": [],
11833
+ "returns": null,
11834
+ "throws": null,
11835
+ "example": null,
11836
+ "defaultValue": null,
11837
+ "guides": []
11838
+ },
11839
+ {
11840
+ "id": "WornAvatar3d.id",
11841
+ "name": "id",
11842
+ "anchor": "wornavatar3d-id",
11843
+ "group": "avatars3d",
11844
+ "signature": "readonly id: string",
11845
+ "documented": true,
11846
+ "summary": "The avatar's item id — a stable key for the model, never something to\nshow as text.",
11847
+ "deprecated": null,
11848
+ "params": [],
11849
+ "returns": null,
11850
+ "throws": null,
11851
+ "example": null,
11852
+ "defaultValue": null,
11853
+ "guides": []
11854
+ },
11855
+ {
11856
+ "id": "WornAvatar3d.modelUrl",
11857
+ "name": "modelUrl",
11858
+ "anchor": "wornavatar3d-modelurl",
11859
+ "group": "avatars3d",
11860
+ "signature": "readonly modelUrl: string",
11861
+ "documented": true,
11862
+ "summary": "The avatar's model, a binary glTF (`.glb`) on the family's rig. Pass it\nthrough [platformIconUrl](api-reference-game.md#platformiconurl) before loading it.",
11863
+ "deprecated": null,
11864
+ "params": [],
11865
+ "returns": null,
11866
+ "throws": null,
11867
+ "example": null,
11868
+ "defaultValue": null,
11869
+ "guides": []
11870
+ },
11871
+ {
11872
+ "id": "WornAvatar3d.iconUrl",
11873
+ "name": "iconUrl",
11874
+ "anchor": "wornavatar3d-iconurl",
11875
+ "group": "avatars3d",
11876
+ "signature": "readonly iconUrl: string",
11877
+ "documented": true,
11878
+ "summary": "A rendered portrait of the avatar, for a roster or a menu. Pass it through\n[platformIconUrl](api-reference-game.md#platformiconurl) before putting it in an `<img src>`.",
11879
+ "deprecated": null,
11880
+ "params": [],
11881
+ "returns": null,
11882
+ "throws": null,
11883
+ "example": null,
11884
+ "defaultValue": null,
11885
+ "guides": []
11886
+ },
11420
11887
  {
11421
11888
  "id": "PlayerIdentity",
11422
11889
  "name": "PlayerIdentity",
@@ -11577,6 +12044,22 @@
11577
12044
  "defaultValue": null,
11578
12045
  "guides": []
11579
12046
  },
12047
+ {
12048
+ "id": "PlayerIdentity.avatars3d",
12049
+ "name": "avatars3d",
12050
+ "anchor": "playeridentity-avatars3d",
12051
+ "group": "player",
12052
+ "signature": "readonly avatars3d?: readonly WornAvatar3d[]",
12053
+ "documented": true,
12054
+ "summary": "Every 3D avatar the player wears, one per rig family, in the order the game\ndeclared its families. Absent or empty means none is worn — draw your\ndefault look.\n\n[PlayerIdentity.avatar3dId](api-reference-game.md#playeridentity-avatar3did) and its two URLs are one of these: the\navatar of the game's first declared family that has one. A game that\naccepts a single family can read either; one that accepts several picks by\n`family` here. Absent too where the platform does not send it yet — read\nthe flat fields then.",
12055
+ "deprecated": null,
12056
+ "params": [],
12057
+ "returns": null,
12058
+ "throws": null,
12059
+ "example": null,
12060
+ "defaultValue": null,
12061
+ "guides": []
12062
+ },
11580
12063
  {
11581
12064
  "id": "PlayerIdentity.badges",
11582
12065
  "name": "badges",
@@ -11600,7 +12083,7 @@
11600
12083
  "group": "player",
11601
12084
  "signature": "function mapIdentity(row: unknown): PlayerIdentity",
11602
12085
  "documented": true,
11603
- "summary": "Map one row's identity fields, as the platform's API sends them, to\n[PlayerIdentity](api-reference-game.md#playeridentity).\n\nEvery surface this SDK names maps its rows through this already. You need it\nonly for a row you fetched yourself through the raw client — an async\nopponent, for instance:\n\n```js\nimport { mapIdentity, text } from '@indiegems/gem-web-sdk/sdk';\n\nconst { opponents } = await gem.client.get('/v1/async-opponents?opponent_type=ghost-lap');\nfor (const row of opponents) {\n const who = mapIdentity(row);\n label(who.displayName ? text.toPlain(who.displayName) : 'Anonymous player');\n}\n```\n\nReads `player_id`, `display_name`, `discriminator`, `display_tag`,\n`avatar_icon_id`, `avatar_icon_url`, `avatar_3d_id`, `avatar_3d_model_url`,\n`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.",
12086
+ "summary": "Map one row's identity fields, as the platform's API sends them, to\n[PlayerIdentity](api-reference-game.md#playeridentity).\n\nEvery surface this SDK names maps its rows through this already. You need it\nonly for a row you fetched yourself through the raw client — an async\nopponent, for instance:\n\n```js\nimport { mapIdentity, text } from '@indiegems/gem-web-sdk/sdk';\n\nconst { opponents } = await gem.client.get('/v1/async-opponents?opponent_type=ghost-lap');\nfor (const row of opponents) {\n const who = mapIdentity(row);\n label(who.displayName ? text.toPlain(who.displayName) : 'Anonymous player');\n}\n```\n\nReads `player_id`, `display_name`, `discriminator`, `display_tag`,\n`avatar_icon_id`, `avatar_icon_url`, `avatar_3d_id`, `avatar_3d_model_url`,\n`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.",
11604
12087
  "deprecated": null,
11605
12088
  "params": [
11606
12089
  {
@@ -12920,14 +13403,62 @@
12920
13403
  "defaultValue": null,
12921
13404
  "guides": []
12922
13405
  },
13406
+ {
13407
+ "id": "Avatar3dPurchaseRequest",
13408
+ "name": "Avatar3dPurchaseRequest",
13409
+ "anchor": "avatar3dpurchaserequest",
13410
+ "group": "avatars3d",
13411
+ "signature": "interface Avatar3dPurchaseRequest",
13412
+ "documented": true,
13413
+ "summary": "What a game asks for when it buys one of its own 3D avatars for the player\nwith `gem.avatars3d.purchase`.",
13414
+ "deprecated": null,
13415
+ "params": [],
13416
+ "returns": null,
13417
+ "throws": null,
13418
+ "example": null,
13419
+ "defaultValue": null,
13420
+ "guides": []
13421
+ },
13422
+ {
13423
+ "id": "Avatar3dPurchaseRequest.itemId",
13424
+ "name": "itemId",
13425
+ "anchor": "avatar3dpurchaserequest-itemid",
13426
+ "group": "avatars3d",
13427
+ "signature": "itemId: string",
13428
+ "documented": true,
13429
+ "summary": "The avatar's item id — `itemId` on an `Avatar3dOffer`.",
13430
+ "deprecated": null,
13431
+ "params": [],
13432
+ "returns": null,
13433
+ "throws": null,
13434
+ "example": null,
13435
+ "defaultValue": null,
13436
+ "guides": []
13437
+ },
13438
+ {
13439
+ "id": "Avatar3dPurchaseRequest.currencySlug",
13440
+ "name": "currencySlug",
13441
+ "anchor": "avatar3dpurchaserequest-currencyslug",
13442
+ "group": "avatars3d",
13443
+ "signature": "currencySlug: string",
13444
+ "documented": true,
13445
+ "summary": "The platform currency to pay in, `GEMS` or `ARCADE_TICKETS`. The offer\nmust carry a price in it.",
13446
+ "deprecated": null,
13447
+ "params": [],
13448
+ "returns": null,
13449
+ "throws": null,
13450
+ "example": null,
13451
+ "defaultValue": null,
13452
+ "guides": []
13453
+ },
12923
13454
  {
12924
13455
  "id": "PurchaseRefusalCode",
12925
13456
  "name": "PurchaseRefusalCode",
12926
13457
  "anchor": "purchaserefusalcode",
12927
13458
  "group": "purchases",
12928
- "signature": "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\"",
13459
+ "signature": "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\"",
12929
13460
  "documented": true,
12930
- "summary": "Every reason a purchase can be refused, on either purchase path.\n\n- `unknown_currency` — the slug names no active platform currency. There are\n no aliases: `TICKETS` is the retired name for Gems and is refused, never\n resolved. Write `GEMS` or `ARCADE_TICKETS`.\n- `currency_not_in_game_payable` — the currency exists but cannot pay for game\n items.\n- `not_purchasable` — the target is not in the purchase catalog, or cannot be\n bought (not published on this channel, inactive, or its config does not\n convert).\n- `no_price_in_currency` — the target has prices, but none in that currency.\n- `busy` — another purchase is in flight, a sheet is already open, the\n platform is backing off after a cancel, or the ask came without a player's\n click or key press.\n- `cancelled` — the player dismissed the platform sheet.\n- `insufficient_funds` — the player cannot afford it.\n- `price_mismatch`, `config_version_stale` — the price or config changed\n between reading the catalog and buying; nothing was charged. Re-read.\n- `platform_currency_unavailable` — the currency is not available to this\n purchase right now.\n- `not_available` — the recipe is outside its availability window, or\n inactive; nothing was charged. Hide or disable it until its window opens.\n- `recipe_locked` — the recipe's prerequisites are not met.\n- `recipe_on_cooldown`, `recipe_max_uses_exceeded`, `item_max_stack_exceeded`,\n `recipe_costs_unmet`, `quantity_invalid` — the recipe or item refused this\n purchase, before any charge.\n- `error` — anything else, including a reason this SDK version does not know\n and an answer that never arrived.\n\nFOR YOUR LOGS AND YOUR SHOP'S STATE, NEVER FOR A PAYMENT SCREEN. Grey out a\nlocked recipe; fix a slug. Never draw a top-up, \"buy more\" or payment UI of\nyour own — only the arcade may, and a game screen that looks like one is an\nimitation of platform payment chrome.",
13461
+ "summary": "Every reason a purchase can be refused, on either purchase path.\n\n- `unknown_currency` — the slug names no active platform currency. There are\n no aliases: `TICKETS` is the retired name for Gems and is refused, never\n resolved. Write `GEMS` or `ARCADE_TICKETS`.\n- `currency_not_in_game_payable` — the currency exists but cannot pay for game\n items.\n- `not_purchasable` — the target is not in the purchase catalog, or cannot be\n bought (not published on this channel, inactive, or its config does not\n convert).\n- `no_price_in_currency` — the target has prices, but none in that currency.\n- `busy` — another purchase is in flight, a sheet is already open, the\n platform is backing off after a cancel, or the ask came without a player's\n click or key press.\n- `cancelled` — the player dismissed the platform sheet.\n- `insufficient_funds` — the player cannot afford it.\n- `price_mismatch`, `config_version_stale` — the price or config changed\n between reading the catalog and buying; nothing was charged. Re-read.\n- `platform_currency_unavailable` — the currency is not available to this\n purchase right now.\n- `not_available` — the recipe is outside its availability window, or\n inactive; nothing was charged. Hide or disable it until its window opens.\n- `recipe_locked` — the recipe's prerequisites are not met.\n- `already_owned` — the player already owns the 3D avatar\n `gem.avatars3d.purchase` asked for; nothing was charged. Only that call\n answers it.\n- `recipe_on_cooldown`, `recipe_max_uses_exceeded`, `item_max_stack_exceeded`,\n `recipe_costs_unmet`, `quantity_invalid` — the recipe or item refused this\n purchase, before any charge.\n- `error` — anything else, including a reason this SDK version does not know\n and an answer that never arrived.\n\nFOR YOUR LOGS AND YOUR SHOP'S STATE, NEVER FOR A PAYMENT SCREEN. Grey out a\nlocked recipe; fix a slug. Never draw a top-up, \"buy more\" or payment UI of\nyour own — only the arcade may, and a game screen that looks like one is an\nimitation of platform payment chrome.",
12931
13462
  "deprecated": null,
12932
13463
  "params": [],
12933
13464
  "returns": null,
@@ -14413,6 +14944,22 @@
14413
14944
  "defaultValue": null,
14414
14945
  "guides": []
14415
14946
  },
14947
+ {
14948
+ "id": "LeaderboardEntry.avatars3d",
14949
+ "name": "avatars3d",
14950
+ "anchor": "leaderboardentry-avatars3d",
14951
+ "group": "leaderboards",
14952
+ "signature": "readonly avatars3d?: readonly WornAvatar3d[]",
14953
+ "documented": true,
14954
+ "summary": "Every 3D avatar the player wears, one per rig family, in the order the game\ndeclared its families. Absent or empty means none is worn — draw your\ndefault look.\n\n[PlayerIdentity.avatar3dId](api-reference-game.md#playeridentity-avatar3did) and its two URLs are one of these: the\navatar of the game's first declared family that has one. A game that\naccepts a single family can read either; one that accepts several picks by\n`family` here. Absent too where the platform does not send it yet — read\nthe flat fields then.",
14955
+ "deprecated": null,
14956
+ "params": [],
14957
+ "returns": null,
14958
+ "throws": null,
14959
+ "example": null,
14960
+ "defaultValue": null,
14961
+ "guides": []
14962
+ },
14416
14963
  {
14417
14964
  "id": "LeaderboardEntry.badges",
14418
14965
  "name": "badges",
@@ -16224,6 +16771,22 @@
16224
16771
  "defaultValue": null,
16225
16772
  "guides": []
16226
16773
  },
16774
+ {
16775
+ "id": "PlayerProfile.avatars3d",
16776
+ "name": "avatars3d",
16777
+ "anchor": "playerprofile-avatars3d",
16778
+ "group": "profiles",
16779
+ "signature": "readonly avatars3d?: readonly WornAvatar3d[]",
16780
+ "documented": true,
16781
+ "summary": "Every 3D avatar the player wears, one per rig family, in the order the game\ndeclared its families. Absent or empty means none is worn — draw your\ndefault look.\n\n[PlayerIdentity.avatar3dId](api-reference-game.md#playeridentity-avatar3did) and its two URLs are one of these: the\navatar of the game's first declared family that has one. A game that\naccepts a single family can read either; one that accepts several picks by\n`family` here. Absent too where the platform does not send it yet — read\nthe flat fields then.",
16782
+ "deprecated": null,
16783
+ "params": [],
16784
+ "returns": null,
16785
+ "throws": null,
16786
+ "example": null,
16787
+ "defaultValue": null,
16788
+ "guides": []
16789
+ },
16227
16790
  {
16228
16791
  "id": "PlayerProfile.badges",
16229
16792
  "name": "badges",
@@ -19285,6 +19848,22 @@
19285
19848
  "defaultValue": null,
19286
19849
  "guides": []
19287
19850
  },
19851
+ {
19852
+ "id": "RoomMember.avatars3d",
19853
+ "name": "avatars3d",
19854
+ "anchor": "roommember-avatars3d",
19855
+ "group": "rooms",
19856
+ "signature": "readonly avatars3d?: readonly WornAvatar3d[]",
19857
+ "documented": true,
19858
+ "summary": "Every 3D avatar the player wears, one per rig family, in the order the game\ndeclared its families. Absent or empty means none is worn — draw your\ndefault look.\n\n[PlayerIdentity.avatar3dId](api-reference-game.md#playeridentity-avatar3did) and its two URLs are one of these: the\navatar of the game's first declared family that has one. A game that\naccepts a single family can read either; one that accepts several picks by\n`family` here. Absent too where the platform does not send it yet — read\nthe flat fields then.",
19859
+ "deprecated": null,
19860
+ "params": [],
19861
+ "returns": null,
19862
+ "throws": null,
19863
+ "example": null,
19864
+ "defaultValue": null,
19865
+ "guides": []
19866
+ },
19288
19867
  {
19289
19868
  "id": "RoomMember.badges",
19290
19869
  "name": "badges",
@@ -20431,6 +21010,22 @@
20431
21010
  "defaultValue": null,
20432
21011
  "guides": []
20433
21012
  },
21013
+ {
21014
+ "id": "ShowcaseFriendEntry.avatars3d",
21015
+ "name": "avatars3d",
21016
+ "anchor": "showcasefriendentry-avatars3d",
21017
+ "group": "showcase",
21018
+ "signature": "readonly avatars3d?: readonly WornAvatar3d[]",
21019
+ "documented": true,
21020
+ "summary": "Every 3D avatar the player wears, one per rig family, in the order the game\ndeclared its families. Absent or empty means none is worn — draw your\ndefault look.\n\n[PlayerIdentity.avatar3dId](api-reference-game.md#playeridentity-avatar3did) and its two URLs are one of these: the\navatar of the game's first declared family that has one. A game that\naccepts a single family can read either; one that accepts several picks by\n`family` here. Absent too where the platform does not send it yet — read\nthe flat fields then.",
21021
+ "deprecated": null,
21022
+ "params": [],
21023
+ "returns": null,
21024
+ "throws": null,
21025
+ "example": null,
21026
+ "defaultValue": null,
21027
+ "guides": []
21028
+ },
20434
21029
  {
20435
21030
  "id": "ShowcaseFriendEntry.badges",
20436
21031
  "name": "badges",
@@ -30711,6 +31306,22 @@
30711
31306
  "defaultValue": null,
30712
31307
  "guides": []
30713
31308
  },
31309
+ {
31310
+ "id": "Friend.avatars3d",
31311
+ "name": "avatars3d",
31312
+ "anchor": "friend-avatars3d",
31313
+ "group": "friends",
31314
+ "signature": "readonly avatars3d?: readonly WornAvatar3d[]",
31315
+ "documented": true,
31316
+ "summary": "Every 3D avatar the player wears, one per rig family, in the order the game\ndeclared its families. Absent or empty means none is worn — draw your\ndefault look.\n\n[PlayerIdentity.avatar3dId](api-reference-function.md#playeridentity-avatar3did) and its two URLs are one of these: the\navatar of the game's first declared family that has one. A game that\naccepts a single family can read either; one that accepts several picks by\n`family` here. Absent too where the platform does not send it yet — read\nthe flat fields then.",
31317
+ "deprecated": null,
31318
+ "params": [],
31319
+ "returns": null,
31320
+ "throws": null,
31321
+ "example": null,
31322
+ "defaultValue": null,
31323
+ "guides": []
31324
+ },
30714
31325
  {
30715
31326
  "id": "Friend.badges",
30716
31327
  "name": "badges",
@@ -31627,6 +32238,102 @@
31627
32238
  "defaultValue": null,
31628
32239
  "guides": []
31629
32240
  },
32241
+ {
32242
+ "id": "Avatar3dFamily",
32243
+ "name": "Avatar3dFamily",
32244
+ "anchor": "avatar3dfamily",
32245
+ "group": "identity",
32246
+ "signature": "type Avatar3dFamily = \"small_human\" | \"adult_human\" | string & object",
32247
+ "documented": true,
32248
+ "summary": "A rig family — the skeleton contract a 3D avatar is built on, and what a\ngame declares it can animate. Two today:\n\n- `small_human`, the toy rig: a `root` node with a `body` child, which holds\n `head`, `arm_l`, `arm_r`, `leg_l` and `leg_r`, and a `hand_socket` under\n `arm_r`.\n- `adult_human`, a full humanoid with fingers: 56 nodes from `root` and\n `hips` down to `toes_r`, every one world-aligned at rest, so one clip plays\n on every avatar in the family.\n\n`AVATAR_RIGS` (from `@indiegems/gem-web-sdk/sdk`) lists every node of\nboth, with its parent.\n\nOpen-ended (`| (string & {})`) so a family the platform adds later does not\nbreak a `switch` you compiled against this version: keep a `default` arm and\nskip a family you do not draw.",
32249
+ "deprecated": null,
32250
+ "params": [],
32251
+ "returns": null,
32252
+ "throws": null,
32253
+ "example": null,
32254
+ "defaultValue": null,
32255
+ "guides": []
32256
+ },
32257
+ {
32258
+ "id": "WornAvatar3d",
32259
+ "name": "WornAvatar3d",
32260
+ "anchor": "wornavatar3d",
32261
+ "group": "identity",
32262
+ "signature": "interface WornAvatar3d",
32263
+ "documented": true,
32264
+ "summary": "One 3D avatar a player wears — at most one per [Avatar3dFamily](api-reference-function.md#avatar3dfamily).",
32265
+ "deprecated": null,
32266
+ "params": [],
32267
+ "returns": null,
32268
+ "throws": null,
32269
+ "example": null,
32270
+ "defaultValue": null,
32271
+ "guides": []
32272
+ },
32273
+ {
32274
+ "id": "WornAvatar3d.family",
32275
+ "name": "family",
32276
+ "anchor": "wornavatar3d-family",
32277
+ "group": "identity",
32278
+ "signature": "readonly family: Avatar3dFamily",
32279
+ "documented": true,
32280
+ "summary": "The rig family this avatar is built on.",
32281
+ "deprecated": null,
32282
+ "params": [],
32283
+ "returns": null,
32284
+ "throws": null,
32285
+ "example": null,
32286
+ "defaultValue": null,
32287
+ "guides": []
32288
+ },
32289
+ {
32290
+ "id": "WornAvatar3d.id",
32291
+ "name": "id",
32292
+ "anchor": "wornavatar3d-id",
32293
+ "group": "identity",
32294
+ "signature": "readonly id: string",
32295
+ "documented": true,
32296
+ "summary": "The avatar's item id — a stable key for the model, never something to\nshow as text.",
32297
+ "deprecated": null,
32298
+ "params": [],
32299
+ "returns": null,
32300
+ "throws": null,
32301
+ "example": null,
32302
+ "defaultValue": null,
32303
+ "guides": []
32304
+ },
32305
+ {
32306
+ "id": "WornAvatar3d.modelUrl",
32307
+ "name": "modelUrl",
32308
+ "anchor": "wornavatar3d-modelurl",
32309
+ "group": "identity",
32310
+ "signature": "readonly modelUrl: string",
32311
+ "documented": true,
32312
+ "summary": "The avatar's model, a binary glTF (`.glb`) on the family's rig. Pass it\nthrough [platformIconUrl](api-reference-function.md#platformiconurl) before loading it.",
32313
+ "deprecated": null,
32314
+ "params": [],
32315
+ "returns": null,
32316
+ "throws": null,
32317
+ "example": null,
32318
+ "defaultValue": null,
32319
+ "guides": []
32320
+ },
32321
+ {
32322
+ "id": "WornAvatar3d.iconUrl",
32323
+ "name": "iconUrl",
32324
+ "anchor": "wornavatar3d-iconurl",
32325
+ "group": "identity",
32326
+ "signature": "readonly iconUrl: string",
32327
+ "documented": true,
32328
+ "summary": "A rendered portrait of the avatar, for a roster or a menu. Pass it through\n[platformIconUrl](api-reference-function.md#platformiconurl) before putting it in an `<img src>`.",
32329
+ "deprecated": null,
32330
+ "params": [],
32331
+ "returns": null,
32332
+ "throws": null,
32333
+ "example": null,
32334
+ "defaultValue": null,
32335
+ "guides": []
32336
+ },
31630
32337
  {
31631
32338
  "id": "PlayerIdentity",
31632
32339
  "name": "PlayerIdentity",
@@ -31787,6 +32494,22 @@
31787
32494
  "defaultValue": null,
31788
32495
  "guides": []
31789
32496
  },
32497
+ {
32498
+ "id": "PlayerIdentity.avatars3d",
32499
+ "name": "avatars3d",
32500
+ "anchor": "playeridentity-avatars3d",
32501
+ "group": "identity",
32502
+ "signature": "readonly avatars3d?: readonly WornAvatar3d[]",
32503
+ "documented": true,
32504
+ "summary": "Every 3D avatar the player wears, one per rig family, in the order the game\ndeclared its families. Absent or empty means none is worn — draw your\ndefault look.\n\n[PlayerIdentity.avatar3dId](api-reference-function.md#playeridentity-avatar3did) and its two URLs are one of these: the\navatar of the game's first declared family that has one. A game that\naccepts a single family can read either; one that accepts several picks by\n`family` here. Absent too where the platform does not send it yet — read\nthe flat fields then.",
32505
+ "deprecated": null,
32506
+ "params": [],
32507
+ "returns": null,
32508
+ "throws": null,
32509
+ "example": null,
32510
+ "defaultValue": null,
32511
+ "guides": []
32512
+ },
31790
32513
  {
31791
32514
  "id": "PlayerIdentity.badges",
31792
32515
  "name": "badges",
@@ -31810,7 +32533,7 @@
31810
32533
  "group": "identity",
31811
32534
  "signature": "function mapIdentity(row: unknown): PlayerIdentity",
31812
32535
  "documented": true,
31813
- "summary": "Map one row's identity fields, as the platform's API sends them, to\n[PlayerIdentity](api-reference-function.md#playeridentity).\n\nEvery surface this SDK names maps its rows through this already. You need it\nonly for a row you fetched yourself through the raw client — an async\nopponent, for instance:\n\n```js\nimport { mapIdentity, text } from '@indiegems/gem-web-sdk/sdk';\n\nconst { opponents } = await gem.client.get('/v1/async-opponents?opponent_type=ghost-lap');\nfor (const row of opponents) {\n const who = mapIdentity(row);\n label(who.displayName ? text.toPlain(who.displayName) : 'Anonymous player');\n}\n```\n\nReads `player_id`, `display_name`, `discriminator`, `display_tag`,\n`avatar_icon_id`, `avatar_icon_url`, `avatar_3d_id`, `avatar_3d_model_url`,\n`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.",
32536
+ "summary": "Map one row's identity fields, as the platform's API sends them, to\n[PlayerIdentity](api-reference-function.md#playeridentity).\n\nEvery surface this SDK names maps its rows through this already. You need it\nonly for a row you fetched yourself through the raw client — an async\nopponent, for instance:\n\n```js\nimport { mapIdentity, text } from '@indiegems/gem-web-sdk/sdk';\n\nconst { opponents } = await gem.client.get('/v1/async-opponents?opponent_type=ghost-lap');\nfor (const row of opponents) {\n const who = mapIdentity(row);\n label(who.displayName ? text.toPlain(who.displayName) : 'Anonymous player');\n}\n```\n\nReads `player_id`, `display_name`, `discriminator`, `display_tag`,\n`avatar_icon_id`, `avatar_icon_url`, `avatar_3d_id`, `avatar_3d_model_url`,\n`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.",
31814
32537
  "deprecated": null,
31815
32538
  "params": [
31816
32539
  {
@@ -32137,6 +32860,22 @@
32137
32860
  "defaultValue": null,
32138
32861
  "guides": []
32139
32862
  },
32863
+ {
32864
+ "id": "LeaderboardEntry.avatars3d",
32865
+ "name": "avatars3d",
32866
+ "anchor": "leaderboardentry-avatars3d",
32867
+ "group": "leaderboards",
32868
+ "signature": "readonly avatars3d?: readonly WornAvatar3d[]",
32869
+ "documented": true,
32870
+ "summary": "Every 3D avatar the player wears, one per rig family, in the order the game\ndeclared its families. Absent or empty means none is worn — draw your\ndefault look.\n\n[PlayerIdentity.avatar3dId](api-reference-function.md#playeridentity-avatar3did) and its two URLs are one of these: the\navatar of the game's first declared family that has one. A game that\naccepts a single family can read either; one that accepts several picks by\n`family` here. Absent too where the platform does not send it yet — read\nthe flat fields then.",
32871
+ "deprecated": null,
32872
+ "params": [],
32873
+ "returns": null,
32874
+ "throws": null,
32875
+ "example": null,
32876
+ "defaultValue": null,
32877
+ "guides": []
32878
+ },
32140
32879
  {
32141
32880
  "id": "LeaderboardEntry.badges",
32142
32881
  "name": "badges",
@@ -33939,6 +34678,22 @@
33939
34678
  "defaultValue": null,
33940
34679
  "guides": []
33941
34680
  },
34681
+ {
34682
+ "id": "PlayerProfile.avatars3d",
34683
+ "name": "avatars3d",
34684
+ "anchor": "playerprofile-avatars3d",
34685
+ "group": "profiles",
34686
+ "signature": "readonly avatars3d?: readonly WornAvatar3d[]",
34687
+ "documented": true,
34688
+ "summary": "Every 3D avatar the player wears, one per rig family, in the order the game\ndeclared its families. Absent or empty means none is worn — draw your\ndefault look.\n\n[PlayerIdentity.avatar3dId](api-reference-function.md#playeridentity-avatar3did) and its two URLs are one of these: the\navatar of the game's first declared family that has one. A game that\naccepts a single family can read either; one that accepts several picks by\n`family` here. Absent too where the platform does not send it yet — read\nthe flat fields then.",
34689
+ "deprecated": null,
34690
+ "params": [],
34691
+ "returns": null,
34692
+ "throws": null,
34693
+ "example": null,
34694
+ "defaultValue": null,
34695
+ "guides": []
34696
+ },
33942
34697
  {
33943
34698
  "id": "PlayerProfile.badges",
33944
34699
  "name": "badges",
@@ -34398,6 +35153,22 @@
34398
35153
  "defaultValue": null,
34399
35154
  "guides": []
34400
35155
  },
35156
+ {
35157
+ "id": "ShowcaseFriendEntry.avatars3d",
35158
+ "name": "avatars3d",
35159
+ "anchor": "showcasefriendentry-avatars3d",
35160
+ "group": "showcase",
35161
+ "signature": "readonly avatars3d?: readonly WornAvatar3d[]",
35162
+ "documented": true,
35163
+ "summary": "Every 3D avatar the player wears, one per rig family, in the order the game\ndeclared its families. Absent or empty means none is worn — draw your\ndefault look.\n\n[PlayerIdentity.avatar3dId](api-reference-function.md#playeridentity-avatar3did) and its two URLs are one of these: the\navatar of the game's first declared family that has one. A game that\naccepts a single family can read either; one that accepts several picks by\n`family` here. Absent too where the platform does not send it yet — read\nthe flat fields then.",
35164
+ "deprecated": null,
35165
+ "params": [],
35166
+ "returns": null,
35167
+ "throws": null,
35168
+ "example": null,
35169
+ "defaultValue": null,
35170
+ "guides": []
35171
+ },
34401
35172
  {
34402
35173
  "id": "ShowcaseFriendEntry.badges",
34403
35174
  "name": "badges",