@indiegems/gem-web-sdk 13.3.3 → 13.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/game.d.ts +17 -1
- package/dist/gem-web-sdk.bridge.js +1 -1
- package/dist/gem-web-sdk.esm.js +3 -3
- package/dist/gem-web-sdk.function.js +1 -1
- package/dist/gem-web-sdk.game.js +3 -3
- package/dist/gem-web-sdk.min.js +3 -3
- package/dist/gem-web-sdk.sdk.js +3 -3
- package/dist/gem-web-sdk.server.js +1 -1
- package/dist/index.d.ts +15 -1
- package/dist/sdk.d.ts +18 -1
- package/dist/server-api.d.ts +1 -0
- package/dist/server.d.ts +1 -0
- package/docs/analytics.md +44 -1
- package/docs/api/api-reference-function.md +1 -1
- package/docs/api/api-reference-game.md +130 -3
- package/docs/api/api-reference-server.md +1 -1
- package/docs/api/reference.json +132 -4
- package/docs/changelog.md +20 -0
- package/docs/getting-started.md +19 -13
- package/docs/inventory-and-catalogs.md +21 -0
- package/docs/manifest.json +18 -18
- package/docs/p2p-and-parties.md +32 -3
- package/docs/rewarded-ads.md +53 -32
- package/docs/rooms-and-matches.md +49 -1
- package/docs/server-bundles.md +2 -2
- package/docs/what-a-game-must-do.md +150 -51
- package/package.json +1 -1
- package/schema/bridge.v1.json +22 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The browser surface — API reference
|
|
2
2
|
|
|
3
|
-
Generated from `@indiegems/gem-web-sdk/sdk` v13.
|
|
3
|
+
Generated from `@indiegems/gem-web-sdk/sdk` v13.4.1, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
|
|
4
4
|
|
|
5
5
|
`@indiegems/gem-web-sdk/sdk` is the one import a game writes, wherever its code runs. `@indiegems/gem-web-sdk/game` exports exactly the symbols on this page and nothing else, for a build that wants only this half.
|
|
6
6
|
|
|
@@ -333,6 +333,15 @@ from whether the player holds one, which is on [EntityScope](#entityscope).
|
|
|
333
333
|
Indexed rather than scanned, so a bag render asking for forty definitions
|
|
334
334
|
in a frame stays cheap.
|
|
335
335
|
|
|
336
|
+
Its `icon_url` is the `icon:` file you uploaded with your config, hosted
|
|
337
|
+
by the platform — the picture the platform's purchase sheet shows. To show
|
|
338
|
+
it in your own shop, pass it through [platformIconUrl](#platformiconurl) and draw your
|
|
339
|
+
placeholder on `undefined`, which is what a development backend answers:
|
|
340
|
+
|
|
341
|
+
```js
|
|
342
|
+
const src = platformIconUrl(config.item('iron-sword')?.icon_url ?? undefined);
|
|
343
|
+
```
|
|
344
|
+
|
|
336
345
|
| Parameter | Type |
|
|
337
346
|
| --- | --- |
|
|
338
347
|
| `idOrSlug` | `string` |
|
|
@@ -7579,7 +7588,9 @@ numbers to `--gem-safe-top`, `--gem-safe-right`, `--gem-safe-bottom` and
|
|
|
7579
7588
|
stylesheet honours them with `var(--gem-safe-top, env(safe-area-inset-top, 0px))`
|
|
7580
7589
|
and no code — and the rectangles the arcade's controls cover to
|
|
7581
7590
|
`--gem-reserved-tl-width`, `--gem-reserved-tl-height` and the same pair for
|
|
7582
|
-
`tr`, `bl` and `br
|
|
7591
|
+
`tr`, `bl` and `br`, with each corner's control centre in
|
|
7592
|
+
`--gem-reserved-tl-center-y` and its height in
|
|
7593
|
+
`--gem-reserved-tl-control-height`. Handle it only for a HUD you position from script — a
|
|
7583
7594
|
canvas you draw into, an engine's own UI layer.
|
|
7584
7595
|
|
|
7585
7596
|
<a id="gemoptions-manageaudio"></a>
|
|
@@ -8439,8 +8450,13 @@ beside it while the player shows it; an embedder that draws wallets over
|
|
|
8439
8450
|
your game adds theirs (see `setWalletVisible`). Keep essential UI and touch
|
|
8440
8451
|
targets out of them. Empty when the arcade covers nothing.
|
|
8441
8452
|
|
|
8453
|
+
Each is a keep-out area, grown out to the frame edges it sits nearest — not
|
|
8454
|
+
the control's own box, and not something to line anything up with. Where
|
|
8455
|
+
the arcade says, `control` on each is the control itself.
|
|
8456
|
+
|
|
8442
8457
|
A stylesheet reads the per-corner custom properties the SDK stamps from
|
|
8443
|
-
these instead — `--gem-reserved-tl-height` and friends
|
|
8458
|
+
these instead — `--gem-reserved-tl-height` and friends, and
|
|
8459
|
+
`--gem-reserved-tl-center-y` to line a top bar up with the Gem.
|
|
8444
8460
|
|
|
8445
8461
|
<a id="gemreservedarea"></a>
|
|
8446
8462
|
|
|
@@ -8494,6 +8510,72 @@ readonly height: number
|
|
|
8494
8510
|
|
|
8495
8511
|
The rectangle's height.
|
|
8496
8512
|
|
|
8513
|
+
<a id="gemreservedarea-control"></a>
|
|
8514
|
+
|
|
8515
|
+
## GemReservedArea.control
|
|
8516
|
+
|
|
8517
|
+
```ts
|
|
8518
|
+
readonly control?: GemControlBox
|
|
8519
|
+
```
|
|
8520
|
+
|
|
8521
|
+
The box of the control this area keeps you out of, as it is drawn — the
|
|
8522
|
+
Gem button itself, not grown to the edges — in the same space. Line your
|
|
8523
|
+
own controls up with this, never with the area: on a notched phone the
|
|
8524
|
+
Gem's area runs up to the top edge, so its middle sits well above the
|
|
8525
|
+
button's. Absent under an embedder that does not say, which leaves you the
|
|
8526
|
+
keep-out area alone.
|
|
8527
|
+
|
|
8528
|
+
<a id="gemcontrolbox"></a>
|
|
8529
|
+
|
|
8530
|
+
## GemControlBox
|
|
8531
|
+
|
|
8532
|
+
```ts
|
|
8533
|
+
interface GemControlBox
|
|
8534
|
+
```
|
|
8535
|
+
|
|
8536
|
+
The box of one of the arcade's controls over your frame, in the same CSS
|
|
8537
|
+
pixels and space as `GemReservedArea`.
|
|
8538
|
+
|
|
8539
|
+
<a id="gemcontrolbox-x"></a>
|
|
8540
|
+
|
|
8541
|
+
## GemControlBox.x
|
|
8542
|
+
|
|
8543
|
+
```ts
|
|
8544
|
+
readonly x: number
|
|
8545
|
+
```
|
|
8546
|
+
|
|
8547
|
+
From your frame's left edge to the control's left edge.
|
|
8548
|
+
|
|
8549
|
+
<a id="gemcontrolbox-y"></a>
|
|
8550
|
+
|
|
8551
|
+
## GemControlBox.y
|
|
8552
|
+
|
|
8553
|
+
```ts
|
|
8554
|
+
readonly y: number
|
|
8555
|
+
```
|
|
8556
|
+
|
|
8557
|
+
From your frame's top edge to the control's top edge.
|
|
8558
|
+
|
|
8559
|
+
<a id="gemcontrolbox-width"></a>
|
|
8560
|
+
|
|
8561
|
+
## GemControlBox.width
|
|
8562
|
+
|
|
8563
|
+
```ts
|
|
8564
|
+
readonly width: number
|
|
8565
|
+
```
|
|
8566
|
+
|
|
8567
|
+
The control's width.
|
|
8568
|
+
|
|
8569
|
+
<a id="gemcontrolbox-height"></a>
|
|
8570
|
+
|
|
8571
|
+
## GemControlBox.height
|
|
8572
|
+
|
|
8573
|
+
```ts
|
|
8574
|
+
readonly height: number
|
|
8575
|
+
```
|
|
8576
|
+
|
|
8577
|
+
The control's height.
|
|
8578
|
+
|
|
8497
8579
|
<a id="walletvisibleoptions"></a>
|
|
8498
8580
|
|
|
8499
8581
|
## WalletVisibleOptions
|
|
@@ -16955,6 +17037,34 @@ readonly members: readonly RoomMember[]
|
|
|
16955
17037
|
|
|
16956
17038
|
Everyone in the current room, including this player. Empty when in none.
|
|
16957
17039
|
|
|
17040
|
+
<a id="roommanager-playwith"></a>
|
|
17041
|
+
|
|
17042
|
+
## RoomManager.playWith
|
|
17043
|
+
|
|
17044
|
+
```ts
|
|
17045
|
+
readonly playWith: PlayWithOutcome | null
|
|
17046
|
+
```
|
|
17047
|
+
|
|
17048
|
+
What the last [RoomManager.quickJoin](#roommanager-quickjoin) did when the Arcade opened
|
|
17049
|
+
your game to play with someone — a friend whose Join the player pressed,
|
|
17050
|
+
or their party's leader. Read-only.
|
|
17051
|
+
|
|
17052
|
+
- `'joined'` — the player was seated in that person's room, as a member
|
|
17053
|
+
sharing their `groupId`.
|
|
17054
|
+
- `'unavailable'` — that room could not be joined (no seat, a match that
|
|
17055
|
+
admits nobody late, another mode), so the quick-join went on as it
|
|
17056
|
+
always does. No reason is given.
|
|
17057
|
+
- `null` — no quick-join has asked: the Arcade did not open the game to
|
|
17058
|
+
play with anyone, or nothing has quick-joined yet.
|
|
17059
|
+
|
|
17060
|
+
Your game makes no call for this, names nobody and is told nobody's name:
|
|
17061
|
+
the Arcade adds a launch key of its own, an opaque ticket, and every
|
|
17062
|
+
`quickJoin` passes it on. Nothing else
|
|
17063
|
+
changes — the room is the one `quickJoin` answers with, `joined` fires as
|
|
17064
|
+
for any quick-join, and no `seated` follows, because your own call made
|
|
17065
|
+
the seat. Use it for a line of text ("You joined your friend's game") or
|
|
17066
|
+
to skip a mode picker, never to decide anything of value.
|
|
17067
|
+
|
|
16958
17068
|
<a id="roommanager-on"></a>
|
|
16959
17069
|
|
|
16960
17070
|
## RoomManager.on
|
|
@@ -17461,6 +17571,23 @@ Typed as an open string because the platform may add states. Match the ones
|
|
|
17461
17571
|
you handle and treat anything else as unknown rather than switching
|
|
17462
17572
|
exhaustively.
|
|
17463
17573
|
|
|
17574
|
+
<a id="playwithoutcome"></a>
|
|
17575
|
+
|
|
17576
|
+
## PlayWithOutcome
|
|
17577
|
+
|
|
17578
|
+
```ts
|
|
17579
|
+
type PlayWithOutcome = "joined" | "unavailable"
|
|
17580
|
+
```
|
|
17581
|
+
|
|
17582
|
+
What a quick-join did when the Arcade opened your game to play with someone
|
|
17583
|
+
— see `gem.rooms.playWith`.
|
|
17584
|
+
|
|
17585
|
+
- `joined` — the player was seated in the room of the friend or party mate
|
|
17586
|
+
the Arcade opened the game to play with.
|
|
17587
|
+
- `unavailable` — that room could not be joined (no seat, a match that
|
|
17588
|
+
admits nobody late, another mode), so the quick-join went on as it always
|
|
17589
|
+
does. No reason is given, by design.
|
|
17590
|
+
|
|
17464
17591
|
<a id="matchconnectionstate"></a>
|
|
17465
17592
|
|
|
17466
17593
|
## MatchConnectionState
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The server surface — API reference
|
|
2
2
|
|
|
3
|
-
Generated from `@indiegems/gem-web-sdk/sdk` v13.
|
|
3
|
+
Generated from `@indiegems/gem-web-sdk/sdk` v13.4.1, the version of the package this file ships in. Every symbol below carries an explicit anchor, its `anchor` in `reference.json` beside this file.
|
|
4
4
|
|
|
5
5
|
`@indiegems/gem-web-sdk/sdk` is the one import a game writes, wherever its code runs. `@indiegems/gem-web-sdk/server` exports exactly the symbols on this page and nothing else, for a build that wants only this half.
|
|
6
6
|
|
package/docs/api/reference.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema_version": 2,
|
|
3
3
|
"source": {
|
|
4
4
|
"repo": "IndieGems/gem-web-sdk",
|
|
5
|
-
"version": "13.
|
|
5
|
+
"version": "13.4.1"
|
|
6
6
|
},
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -430,7 +430,7 @@
|
|
|
430
430
|
"group": "config",
|
|
431
431
|
"signature": "item(idOrSlug: string): SnapshotItem | null",
|
|
432
432
|
"documented": true,
|
|
433
|
-
"summary": "One item definition, by id or by slug — either works.\n\n```js\nconst config = await gem.gameConfig.load();\nconst sword = config?.item('iron-sword');\n```\n\n`null` means your config does not define it. That is a different question\nfrom whether the player holds one, which is on [EntityScope](api-reference-game.md#entityscope).\n\nIndexed rather than scanned, so a bag render asking for forty definitions\nin a frame stays cheap.",
|
|
433
|
+
"summary": "One item definition, by id or by slug — either works.\n\n```js\nconst config = await gem.gameConfig.load();\nconst sword = config?.item('iron-sword');\n```\n\n`null` means your config does not define it. That is a different question\nfrom whether the player holds one, which is on [EntityScope](api-reference-game.md#entityscope).\n\nIndexed rather than scanned, so a bag render asking for forty definitions\nin a frame stays cheap.\n\nIts `icon_url` is the `icon:` file you uploaded with your config, hosted\nby the platform — the picture the platform's purchase sheet shows. To show\nit in your own shop, pass it through [platformIconUrl](api-reference-game.md#platformiconurl) and draw your\nplaceholder on `undefined`, which is what a development backend answers:\n\n```js\nconst src = platformIconUrl(config.item('iron-sword')?.icon_url ?? undefined);\n```",
|
|
434
434
|
"deprecated": null,
|
|
435
435
|
"params": [
|
|
436
436
|
{
|
|
@@ -8678,7 +8678,7 @@
|
|
|
8678
8678
|
"group": "start",
|
|
8679
8679
|
"signature": "readonly onViewport?: (viewport: GemViewport) => void",
|
|
8680
8680
|
"documented": true,
|
|
8681
|
-
"summary": "Which part of your game's own box is unsafe to draw essential UI in arrived,\nor changed — the display cutout, the home indicator, the arcade's own\ncontrols beside your frame.\n\n**Most games need no handler.** The SDK has already written the same four\nnumbers to `--gem-safe-top`, `--gem-safe-right`, `--gem-safe-bottom` and\n`--gem-safe-left` on your document's root before this fires, so a\nstylesheet honours them with `var(--gem-safe-top, env(safe-area-inset-top, 0px))`\nand no code — and the rectangles the arcade's controls cover to\n`--gem-reserved-tl-width`, `--gem-reserved-tl-height` and the same pair for\n`tr`, `bl` and `br`. Handle it only for a HUD you position from script — a\ncanvas you draw into, an engine's own UI layer.",
|
|
8681
|
+
"summary": "Which part of your game's own box is unsafe to draw essential UI in arrived,\nor changed — the display cutout, the home indicator, the arcade's own\ncontrols beside your frame.\n\n**Most games need no handler.** The SDK has already written the same four\nnumbers to `--gem-safe-top`, `--gem-safe-right`, `--gem-safe-bottom` and\n`--gem-safe-left` on your document's root before this fires, so a\nstylesheet honours them with `var(--gem-safe-top, env(safe-area-inset-top, 0px))`\nand no code — and the rectangles the arcade's controls cover to\n`--gem-reserved-tl-width`, `--gem-reserved-tl-height` and the same pair for\n`tr`, `bl` and `br`, with each corner's control centre in\n`--gem-reserved-tl-center-y` and its height in\n`--gem-reserved-tl-control-height`. Handle it only for a HUD you position from script — a\ncanvas you draw into, an engine's own UI layer.",
|
|
8682
8682
|
"deprecated": null,
|
|
8683
8683
|
"params": [],
|
|
8684
8684
|
"returns": null,
|
|
@@ -9556,7 +9556,7 @@
|
|
|
9556
9556
|
"group": "player",
|
|
9557
9557
|
"signature": "readonly reserved: readonly GemReservedArea[]",
|
|
9558
9558
|
"documented": true,
|
|
9559
|
-
"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.",
|
|
9559
|
+
"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\nEach is a keep-out area, grown out to the frame edges it sits nearest — not\nthe control's own box, and not something to line anything up with. Where\nthe arcade says, `control` on each is the control itself.\n\nA stylesheet reads the per-corner custom properties the SDK stamps from\nthese instead — `--gem-reserved-tl-height` and friends, and\n`--gem-reserved-tl-center-y` to line a top bar up with the Gem.",
|
|
9560
9560
|
"deprecated": null,
|
|
9561
9561
|
"params": [],
|
|
9562
9562
|
"returns": null,
|
|
@@ -9645,6 +9645,102 @@
|
|
|
9645
9645
|
"defaultValue": null,
|
|
9646
9646
|
"guides": []
|
|
9647
9647
|
},
|
|
9648
|
+
{
|
|
9649
|
+
"id": "GemReservedArea.control",
|
|
9650
|
+
"name": "control",
|
|
9651
|
+
"anchor": "gemreservedarea-control",
|
|
9652
|
+
"group": "player",
|
|
9653
|
+
"signature": "readonly control?: GemControlBox",
|
|
9654
|
+
"documented": true,
|
|
9655
|
+
"summary": "The box of the control this area keeps you out of, as it is drawn — the\nGem button itself, not grown to the edges — in the same space. Line your\nown controls up with this, never with the area: on a notched phone the\nGem's area runs up to the top edge, so its middle sits well above the\nbutton's. Absent under an embedder that does not say, which leaves you the\nkeep-out area alone.",
|
|
9656
|
+
"deprecated": null,
|
|
9657
|
+
"params": [],
|
|
9658
|
+
"returns": null,
|
|
9659
|
+
"throws": null,
|
|
9660
|
+
"example": null,
|
|
9661
|
+
"defaultValue": null,
|
|
9662
|
+
"guides": []
|
|
9663
|
+
},
|
|
9664
|
+
{
|
|
9665
|
+
"id": "GemControlBox",
|
|
9666
|
+
"name": "GemControlBox",
|
|
9667
|
+
"anchor": "gemcontrolbox",
|
|
9668
|
+
"group": "player",
|
|
9669
|
+
"signature": "interface GemControlBox",
|
|
9670
|
+
"documented": true,
|
|
9671
|
+
"summary": "The box of one of the arcade's controls over your frame, in the same CSS\npixels and space as `GemReservedArea`.",
|
|
9672
|
+
"deprecated": null,
|
|
9673
|
+
"params": [],
|
|
9674
|
+
"returns": null,
|
|
9675
|
+
"throws": null,
|
|
9676
|
+
"example": null,
|
|
9677
|
+
"defaultValue": null,
|
|
9678
|
+
"guides": []
|
|
9679
|
+
},
|
|
9680
|
+
{
|
|
9681
|
+
"id": "GemControlBox.x",
|
|
9682
|
+
"name": "x",
|
|
9683
|
+
"anchor": "gemcontrolbox-x",
|
|
9684
|
+
"group": "player",
|
|
9685
|
+
"signature": "readonly x: number",
|
|
9686
|
+
"documented": true,
|
|
9687
|
+
"summary": "From your frame's left edge to the control's left edge.",
|
|
9688
|
+
"deprecated": null,
|
|
9689
|
+
"params": [],
|
|
9690
|
+
"returns": null,
|
|
9691
|
+
"throws": null,
|
|
9692
|
+
"example": null,
|
|
9693
|
+
"defaultValue": null,
|
|
9694
|
+
"guides": []
|
|
9695
|
+
},
|
|
9696
|
+
{
|
|
9697
|
+
"id": "GemControlBox.y",
|
|
9698
|
+
"name": "y",
|
|
9699
|
+
"anchor": "gemcontrolbox-y",
|
|
9700
|
+
"group": "player",
|
|
9701
|
+
"signature": "readonly y: number",
|
|
9702
|
+
"documented": true,
|
|
9703
|
+
"summary": "From your frame's top edge to the control's top edge.",
|
|
9704
|
+
"deprecated": null,
|
|
9705
|
+
"params": [],
|
|
9706
|
+
"returns": null,
|
|
9707
|
+
"throws": null,
|
|
9708
|
+
"example": null,
|
|
9709
|
+
"defaultValue": null,
|
|
9710
|
+
"guides": []
|
|
9711
|
+
},
|
|
9712
|
+
{
|
|
9713
|
+
"id": "GemControlBox.width",
|
|
9714
|
+
"name": "width",
|
|
9715
|
+
"anchor": "gemcontrolbox-width",
|
|
9716
|
+
"group": "player",
|
|
9717
|
+
"signature": "readonly width: number",
|
|
9718
|
+
"documented": true,
|
|
9719
|
+
"summary": "The control's width.",
|
|
9720
|
+
"deprecated": null,
|
|
9721
|
+
"params": [],
|
|
9722
|
+
"returns": null,
|
|
9723
|
+
"throws": null,
|
|
9724
|
+
"example": null,
|
|
9725
|
+
"defaultValue": null,
|
|
9726
|
+
"guides": []
|
|
9727
|
+
},
|
|
9728
|
+
{
|
|
9729
|
+
"id": "GemControlBox.height",
|
|
9730
|
+
"name": "height",
|
|
9731
|
+
"anchor": "gemcontrolbox-height",
|
|
9732
|
+
"group": "player",
|
|
9733
|
+
"signature": "readonly height: number",
|
|
9734
|
+
"documented": true,
|
|
9735
|
+
"summary": "The control's height.",
|
|
9736
|
+
"deprecated": null,
|
|
9737
|
+
"params": [],
|
|
9738
|
+
"returns": null,
|
|
9739
|
+
"throws": null,
|
|
9740
|
+
"example": null,
|
|
9741
|
+
"defaultValue": null,
|
|
9742
|
+
"guides": []
|
|
9743
|
+
},
|
|
9648
9744
|
{
|
|
9649
9745
|
"id": "WalletVisibleOptions",
|
|
9650
9746
|
"name": "WalletVisibleOptions",
|
|
@@ -18878,6 +18974,22 @@
|
|
|
18878
18974
|
"defaultValue": null,
|
|
18879
18975
|
"guides": []
|
|
18880
18976
|
},
|
|
18977
|
+
{
|
|
18978
|
+
"id": "RoomManager.playWith",
|
|
18979
|
+
"name": "playWith",
|
|
18980
|
+
"anchor": "roommanager-playwith",
|
|
18981
|
+
"group": "rooms",
|
|
18982
|
+
"signature": "readonly playWith: PlayWithOutcome | null",
|
|
18983
|
+
"documented": true,
|
|
18984
|
+
"summary": "What the last [RoomManager.quickJoin](api-reference-game.md#roommanager-quickjoin) did when the Arcade opened\nyour game to play with someone — a friend whose Join the player pressed,\nor their party's leader. Read-only.\n\n- `'joined'` — the player was seated in that person's room, as a member\n sharing their `groupId`.\n- `'unavailable'` — that room could not be joined (no seat, a match that\n admits nobody late, another mode), so the quick-join went on as it\n always does. No reason is given.\n- `null` — no quick-join has asked: the Arcade did not open the game to\n play with anyone, or nothing has quick-joined yet.\n\nYour game makes no call for this, names nobody and is told nobody's name:\nthe Arcade adds a launch key of its own, an opaque ticket, and every\n`quickJoin` passes it on. Nothing else\nchanges — the room is the one `quickJoin` answers with, `joined` fires as\nfor any quick-join, and no `seated` follows, because your own call made\nthe seat. Use it for a line of text (\"You joined your friend's game\") or\nto skip a mode picker, never to decide anything of value.",
|
|
18985
|
+
"deprecated": null,
|
|
18986
|
+
"params": [],
|
|
18987
|
+
"returns": null,
|
|
18988
|
+
"throws": null,
|
|
18989
|
+
"example": null,
|
|
18990
|
+
"defaultValue": null,
|
|
18991
|
+
"guides": []
|
|
18992
|
+
},
|
|
18881
18993
|
{
|
|
18882
18994
|
"id": "RoomManager.on",
|
|
18883
18995
|
"name": "on",
|
|
@@ -19474,6 +19586,22 @@
|
|
|
19474
19586
|
"defaultValue": null,
|
|
19475
19587
|
"guides": []
|
|
19476
19588
|
},
|
|
19589
|
+
{
|
|
19590
|
+
"id": "PlayWithOutcome",
|
|
19591
|
+
"name": "PlayWithOutcome",
|
|
19592
|
+
"anchor": "playwithoutcome",
|
|
19593
|
+
"group": "rooms",
|
|
19594
|
+
"signature": "type PlayWithOutcome = \"joined\" | \"unavailable\"",
|
|
19595
|
+
"documented": true,
|
|
19596
|
+
"summary": "What a quick-join did when the Arcade opened your game to play with someone\n— see `gem.rooms.playWith`.\n\n- `joined` — the player was seated in the room of the friend or party mate\n the Arcade opened the game to play with.\n- `unavailable` — that room could not be joined (no seat, a match that\n admits nobody late, another mode), so the quick-join went on as it always\n does. No reason is given, by design.",
|
|
19597
|
+
"deprecated": null,
|
|
19598
|
+
"params": [],
|
|
19599
|
+
"returns": null,
|
|
19600
|
+
"throws": null,
|
|
19601
|
+
"example": null,
|
|
19602
|
+
"defaultValue": null,
|
|
19603
|
+
"guides": []
|
|
19604
|
+
},
|
|
19477
19605
|
{
|
|
19478
19606
|
"id": "MatchConnectionState",
|
|
19479
19607
|
"name": "MatchConnectionState",
|
package/docs/changelog.md
CHANGED
|
@@ -16,6 +16,26 @@ Its entries are grouped under up to five headings:
|
|
|
16
16
|
- **Rebuild**: a type changed. Recompile against the new version; code that compiles today keeps compiling unless the entry says otherwise.
|
|
17
17
|
- **New capability**: something a game can now do. Nothing changes unless you use it.
|
|
18
18
|
|
|
19
|
+
## 13.4.1
|
|
20
|
+
|
|
21
|
+
Upgrading from 13.4.0. Impact: none.
|
|
22
|
+
|
|
23
|
+
Nothing in this release asks anything of a game.
|
|
24
|
+
|
|
25
|
+
## 13.4.0
|
|
26
|
+
|
|
27
|
+
Upgrading from 13.3.3. Impact: required.
|
|
28
|
+
|
|
29
|
+
### Rebuild
|
|
30
|
+
|
|
31
|
+
- The reserved areas accepted by GemHost.sendViewport and the reserved list on GemViewport now allow an optional control box on each area. The field is additive and optional, so existing code keeps working. Recompile your game; no code change is needed unless you want to use the new field.
|
|
32
|
+
|
|
33
|
+
### New capability
|
|
34
|
+
|
|
35
|
+
- Each reserved area can now carry the box of the control inside it, with x, y, width and height. Reserved areas are keep-out zones that extend to the frame edges, so their middle is not the middle of the control. When you want to align your own interface with the Arcade control, for example to centre a top bar on it, read the control box from the area instead of deriving it from the keep-out zone. The field is absent for an area with no control, so check for it first.
|
|
36
|
+
- The room manager now exposes a read-only playWith value. When the Arcade opens your game to play with a friend or party member, a quick-join may land the player in that person's room. The value tells your game whether the player joined somebody or no such room was available. It is null when the game was not opened this way, and it never says who the other player is. Use it to adjust your lobby or welcome messaging.
|
|
37
|
+
- Catalog recipes now include an icon_url field with the platform-hosted icon. The Arcade purchase sheet already draws this icon, and your game can show the same icon in its own shop or inventory screens instead of shipping a separate copy.
|
|
38
|
+
|
|
19
39
|
## 13.3.3
|
|
20
40
|
|
|
21
41
|
Upgrading from 13.3.2. Impact: none.
|
package/docs/getting-started.md
CHANGED
|
@@ -50,7 +50,7 @@ either way.
|
|
|
50
50
|
```jsonc
|
|
51
51
|
// package.json
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@indiegems/gem-web-sdk": "13.
|
|
53
|
+
"@indiegems/gem-web-sdk": "13.4.1"
|
|
54
54
|
}
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -347,13 +347,18 @@ const jarIndex = Number.isInteger(n) && n >= 1 && n <= JAR_COUNT ? n : null;
|
|
|
347
347
|
|
|
348
348
|
That is the whole contract for your own keys. The bridge carries no launch message, and the SDK
|
|
349
349
|
has no API for them: the arcade does the forwarding, so a build you published on any SDK version
|
|
350
|
-
gets it without a code change.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
350
|
+
gets it without a code change. Two keys are not yours, and they are the platform's (below):
|
|
351
|
+
`gemChallenge`, which belongs to [friend challenges](friends.md#friend-challenges), and
|
|
352
|
+
`gemPlayWith`, an opaque ticket the arcade adds when it opens your game to play with a friend or a
|
|
353
|
+
party mate, which names nobody your game can recognise. Never define either, and never parse
|
|
354
|
+
either yourself. The SDK reads them for you —
|
|
355
|
+
`gem.friends.launchedChallenge()` answers the challenge `gemChallenge` names, or `null`, and every
|
|
356
|
+
`gem.rooms.quickJoin()` passes `gemPlayWith` on
|
|
357
|
+
([Landing in a friend's room](rooms-and-matches.md#5e-landing-in-a-friends-room-play-with)) — and
|
|
358
|
+
they are the only launch keys it reads. Each is read once, when `createGem` runs, so cleaning your
|
|
359
|
+
URL afterwards does not lose it. Call `createGem` before you clean the URL, or leave both keys in
|
|
360
|
+
place: a key removed before `createGem` runs is never seen, so `launchedChallenge()` resolves
|
|
361
|
+
`null` with no error and a quick-join plays with nobody.
|
|
357
362
|
|
|
358
363
|
What reaches the frame:
|
|
359
364
|
|
|
@@ -375,11 +380,12 @@ What reaches the frame:
|
|
|
375
380
|
A key starting with `_` can never be one of yours: the key rule below requires a leading
|
|
376
381
|
letter, so the arcade keeps that space for itself.
|
|
377
382
|
|
|
378
|
-
- **
|
|
379
|
-
opens a friend challenge
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
+
- **Two platform keys.** `gemChallenge` and `gemPlayWith` do reach the frame — the arcade adds the
|
|
384
|
+
first when a player opens a friend challenge, and the second when it opens your game to play
|
|
385
|
+
with someone — but they are the platform's, not yours: read the challenge with
|
|
386
|
+
`gem.friends.launchedChallenge()`, let `gem.rooms.quickJoin()` carry `gemPlayWith`, and never
|
|
387
|
+
read either from the URL. Do not define a key of either name, and treat every key beginning
|
|
388
|
+
with `gem` as reserved: a friend-challenge template refuses a param named that way.
|
|
383
389
|
- **Bounded.** At most 16 parameters. A key is a letter followed by up to 63 letters, digits,
|
|
384
390
|
`_` or `-`. A value is at most 512 characters with no control or format characters. An entry
|
|
385
391
|
that breaks a rule is dropped on its own; the rest of the link still arrives.
|
|
@@ -696,6 +696,27 @@ game `busy` for a short, growing interval — do not re-ask on `cancelled`.
|
|
|
696
696
|
Hide this specific affordance when `gem.frame.parentSupports('purchases')` is false. That check does
|
|
697
697
|
not apply to the direct SDK path.
|
|
698
698
|
|
|
699
|
+
The sheet shows the item's or recipe's picture from its `icon_url`. You never write that field: it
|
|
700
|
+
is the `icon:` file you upload with your config (`icon: icons/extra-life.png` in the YAML, uploaded
|
|
701
|
+
by `gemctl config upload`), which the platform hosts and fills in. A recipe with no `icon:` of its
|
|
702
|
+
own shows the icon of the one item it grants. With neither, the sheet shows its default picture.
|
|
703
|
+
|
|
704
|
+
Your own shop can show the same picture. Pass the URL through `platformIconUrl` first, from the
|
|
705
|
+
package you installed in [Getting started](getting-started.md), and draw your own placeholder when
|
|
706
|
+
that answers `undefined`:
|
|
707
|
+
|
|
708
|
+
```js
|
|
709
|
+
import { platformIconUrl } from '@indiegems/gem-web-sdk/sdk';
|
|
710
|
+
|
|
711
|
+
const src = platformIconUrl(config.item('extra-life')?.icon_url ?? undefined);
|
|
712
|
+
if (src) img.src = src;
|
|
713
|
+
else drawPlaceholder();
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
A recipe's icon is the `icon_url` on the recipe in `config.catalogs[].recipes`; read it the same
|
|
717
|
+
way. `platformIconUrl` passes only the platform's production image host, so against a development
|
|
718
|
+
backend it answers `undefined` by design: there, your placeholder is what you see.
|
|
719
|
+
|
|
699
720
|
#### Game-owned direct purchase
|
|
700
721
|
|
|
701
722
|
Use the direct path when your game provides its own purchase UI and does not want the platform sheet:
|
package/docs/manifest.json
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"file": "analytics.md",
|
|
16
|
-
"sha256": "
|
|
17
|
-
"updated": "2026-10-
|
|
16
|
+
"sha256": "94baa26268843ba926462633ddec8aaca56aabf79ee6639aaf77df68474debc7",
|
|
17
|
+
"updated": "2026-10-17T00:33:31Z"
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
"file": "arcade-avatars.md",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
"file": "changelog.md",
|
|
41
|
-
"sha256": "
|
|
42
|
-
"updated": "2026-10-
|
|
41
|
+
"sha256": "68726bcd339a18e0fc6b5b9fdfe5ca3666e1c1fb79293a64c361e09616c87a85",
|
|
42
|
+
"updated": "2026-10-18T17:04:31Z"
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
"file": "daily-challenges.md",
|
|
@@ -68,13 +68,13 @@
|
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
"file": "getting-started.md",
|
|
71
|
-
"sha256": "
|
|
72
|
-
"updated": "2026-10-
|
|
71
|
+
"sha256": "5f950537b4a4d1f952adbc294fdba8bdd05ebaf6d8a924951129ff604b31f3d8",
|
|
72
|
+
"updated": "2026-10-18T17:04:31Z"
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
"file": "inventory-and-catalogs.md",
|
|
76
|
-
"sha256": "
|
|
77
|
-
"updated": "2026-10-
|
|
76
|
+
"sha256": "fc2dbf113d2602fff90d03fc24e03d468c7b4ab3633844ed9d0df6b985e69385",
|
|
77
|
+
"updated": "2026-10-18T00:39:23Z"
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
"file": "leaderboards.md",
|
|
@@ -88,8 +88,8 @@
|
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
90
|
"file": "p2p-and-parties.md",
|
|
91
|
-
"sha256": "
|
|
92
|
-
"updated": "2026-
|
|
91
|
+
"sha256": "4cfe2a47def51eb617f154eaac39faf176d8aa7ca2c36907084b6349048ab1b9",
|
|
92
|
+
"updated": "2026-10-17T22:11:15Z"
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"file": "platform-moments.md",
|
|
@@ -108,8 +108,8 @@
|
|
|
108
108
|
},
|
|
109
109
|
{
|
|
110
110
|
"file": "rewarded-ads.md",
|
|
111
|
-
"sha256": "
|
|
112
|
-
"updated": "2026-10-
|
|
111
|
+
"sha256": "a111ae68fb0d443f8c183c7a963668078a6d155124cf19da45391218c0716e1f",
|
|
112
|
+
"updated": "2026-10-17T17:23:21Z"
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"file": "room-shapes.md",
|
|
@@ -118,8 +118,8 @@
|
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
120
|
"file": "rooms-and-matches.md",
|
|
121
|
-
"sha256": "
|
|
122
|
-
"updated": "2026-10-
|
|
121
|
+
"sha256": "8f5879028034a75dfd14dc6afa71d96e1c3203ba1969f00b39c2dad043818963",
|
|
122
|
+
"updated": "2026-10-17T22:11:15Z"
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"file": "saving-player-progress.md",
|
|
@@ -128,8 +128,8 @@
|
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
130
|
"file": "server-bundles.md",
|
|
131
|
-
"sha256": "
|
|
132
|
-
"updated": "2026-10-
|
|
131
|
+
"sha256": "056d725c06e61dc6ebdda3fe082809a416ce7f87f2e84879bc0971c6065ba4f9",
|
|
132
|
+
"updated": "2026-10-18T17:04:31Z"
|
|
133
133
|
},
|
|
134
134
|
{
|
|
135
135
|
"file": "serverless-functions-js.md",
|
|
@@ -153,8 +153,8 @@
|
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
"file": "what-a-game-must-do.md",
|
|
156
|
-
"sha256": "
|
|
157
|
-
"updated": "2026-10-
|
|
156
|
+
"sha256": "97f97817fb087dbe2c637b4ea7f22f1ab624496bd32e427e4619c211c65d4f21",
|
|
157
|
+
"updated": "2026-10-18T15:05:29Z"
|
|
158
158
|
}
|
|
159
159
|
]
|
|
160
160
|
}
|
package/docs/p2p-and-parties.md
CHANGED
|
@@ -568,9 +568,10 @@ one this SDK calls. A v2 member row carries `group_id`; no party id appears
|
|
|
568
568
|
anywhere in the response, at any level — not even the requester's own.
|
|
569
569
|
|
|
570
570
|
`RoomMember.groupId` is that value, read off the wire. Two members sharing it
|
|
571
|
-
arrived together
|
|
572
|
-
|
|
573
|
-
|
|
571
|
+
arrived together — as a party, or one coming to play with the other (§7.5 and
|
|
572
|
+
`rooms-and-matches.md` §5e); a member with no value arrived alone. It is opaque:
|
|
573
|
+
draw a badge with it, group a roster by it, compare two members' values — but do
|
|
574
|
+
not parse it, key anything off it, or keep it once the room is gone.
|
|
574
575
|
|
|
575
576
|
The stability across rooms is precisely what was removed: the marker is scoped to
|
|
576
577
|
one room, so the same party in two rooms carries two unrelated values. The useful
|
|
@@ -708,6 +709,34 @@ cleared, and the SDK raises a refusal that names the party cause — because a g
|
|
|
708
709
|
that just ran the documented recovery has every reason to think the room is gone.
|
|
709
710
|
Adopting is the answer there too: the room to be in is the one the party is in.
|
|
710
711
|
|
|
712
|
+
### 7.5 A member who opens the game after the leader: play-with
|
|
713
|
+
|
|
714
|
+
A member who was not in your game when their leader started it — browsing the
|
|
715
|
+
Arcade, or in another game — is not left to find it. The Arcade shows them a
|
|
716
|
+
prompt, and its Join opens your game **to play with the leader**: the launch key
|
|
717
|
+
`gemPlayWith` that `rooms-and-matches.md` §5e describes, an opaque ticket that
|
|
718
|
+
names nobody your game can recognise. Your game makes no call for it. Its own
|
|
719
|
+
`quickJoin()` carries the ticket, and the platform seats the member in the
|
|
720
|
+
leader's room when it has a seat, because the two are in one party.
|
|
721
|
+
|
|
722
|
+
What the member's game sees is an ordinary quick-join:
|
|
723
|
+
|
|
724
|
+
- **The room it asked for answers, and `gem.rooms.playWith` reads `'joined'`.**
|
|
725
|
+
`'unavailable'` means the leader's room could not be joined — full, of another
|
|
726
|
+
mode, or in a match that admits nobody late — and the quick-join went on as
|
|
727
|
+
usual.
|
|
728
|
+
- **No `seated` fires for it**, because the member's own call made the seat.
|
|
729
|
+
Keep the `seated` handler (§7.4) all the same: it is still how a member who
|
|
730
|
+
was already in your game when the leader started is brought in.
|
|
731
|
+
- **The member shares the party's `groupId`**, so a roster that groups the party
|
|
732
|
+
shows them with it.
|
|
733
|
+
- **It holds its own seat.** A member seated by the leader's call leaves only
|
|
734
|
+
with the group (§7.4); one who came in this way arrived by their own call, so
|
|
735
|
+
they can leave alone, and the leader taking the party out does not take them.
|
|
736
|
+
|
|
737
|
+
The leader's game does nothing new either. Its quick-join is the party's room
|
|
738
|
+
call, as before, and plays with nobody else.
|
|
739
|
+
|
|
711
740
|
---
|
|
712
741
|
|
|
713
742
|
## 8. Capabilities
|