@indiegems/gem-web-sdk 15.0.0 → 15.1.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 +15 -0
- package/dist/function.d.ts +3 -0
- package/dist/game.d.ts +6 -1
- package/dist/gem-web-sdk.esm.js +1 -1
- package/dist/gem-web-sdk.function.js +3 -3
- 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/sdk.d.ts +6 -1
- package/docs/analytics.md +71 -7
- package/docs/api/api-reference-function.md +67 -1
- package/docs/api/api-reference-game.md +108 -1
- package/docs/api/api-reference-server.md +1 -1
- package/docs/api/reference.json +94 -1
- package/docs/changelog.md +15 -0
- package/docs/getting-started.md +1 -1
- package/docs/manifest.json +12 -12
- package/docs/match-session.md +16 -1
- package/docs/server-bundles.md +2 -2
- package/docs/what-a-game-must-do.md +31 -5
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The browser surface — API reference
|
|
2
2
|
|
|
3
|
-
Generated from `@indiegems/gem-web-sdk/sdk` v15.
|
|
3
|
+
Generated from `@indiegems/gem-web-sdk/sdk` v15.1.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
|
|
|
@@ -3553,6 +3553,72 @@ them, whatever you set here.
|
|
|
3553
3553
|
| --- | --- |
|
|
3554
3554
|
| `props` | `Readonly<Record<string, AnalyticsValue>>` |
|
|
3555
3555
|
|
|
3556
|
+
<a id="analyticsapi-experiment"></a>
|
|
3557
|
+
|
|
3558
|
+
## AnalyticsApi.experiment
|
|
3559
|
+
|
|
3560
|
+
```ts
|
|
3561
|
+
experiment(key: string, count: number): Promise<number>
|
|
3562
|
+
```
|
|
3563
|
+
|
|
3564
|
+
Which arm of one of your experiments this player gets — the same answer on
|
|
3565
|
+
every call, every refresh and every device. Use it wherever you would have
|
|
3566
|
+
picked an arm with `Math.random()`, which deals a player a new arm on each
|
|
3567
|
+
visit and makes the test unreadable.
|
|
3568
|
+
|
|
3569
|
+
Pass a COUNT and get an index, `0` to `count - 1`; pass a LIST of strings
|
|
3570
|
+
and get one of them. A string listed twice is twice as likely.
|
|
3571
|
+
|
|
3572
|
+
```js
|
|
3573
|
+
const tutorial = await gem.analytics.experiment('tutorial_length', ['short', 'long']);
|
|
3574
|
+
const layout = await gem.analytics.experiment('shop_layout', 3); // 0, 1 or 2
|
|
3575
|
+
```
|
|
3576
|
+
|
|
3577
|
+
The selection is a hash of the key and the signed-in player's id — FNV-1a
|
|
3578
|
+
32-bit over `"<key>:<playerId>"`, then MurmurHash3's 32-bit finalizer, mod
|
|
3579
|
+
the arm count; see
|
|
3580
|
+
[experimentSelection](#experimentselection), which computes the same answer for an id you
|
|
3581
|
+
hold. Hashing the key in is what keeps your experiments independent: the
|
|
3582
|
+
players in one experiment's first arm are not the same players in every
|
|
3583
|
+
other experiment's first arm.
|
|
3584
|
+
|
|
3585
|
+
⚠️ **Keep the key and the arms fixed for the life of the experiment.** A new
|
|
3586
|
+
count or list re-deals every player. To start over on purpose, use a new
|
|
3587
|
+
key.
|
|
3588
|
+
|
|
3589
|
+
⚠️ **NOTHING IS RECORDED FOR YOU.** The selection is not sent anywhere, and
|
|
3590
|
+
the SDK does not add it to your events. To analyse an experiment, declare a
|
|
3591
|
+
dimension for it on the events you want to break down, with the arms as its
|
|
3592
|
+
values, named after the experiment, and pass the selection there yourself:
|
|
3593
|
+
`gem.analytics.track('tutorial_finished', { tutorial_length: tutorial })`.
|
|
3594
|
+
Never the platform's `variant` field, which belongs to store-page
|
|
3595
|
+
experiments.
|
|
3596
|
+
|
|
3597
|
+
WAITS FOR THE PLAYER. The id arrives with the first play token, so a call
|
|
3598
|
+
during boot waits for it, and rejects as [Gem.awaitIdentity](#gem-awaitidentity) does
|
|
3599
|
+
when none arrives — outside the Arcade, the Gem Editor or the dev host.
|
|
3600
|
+
There is no random fallback: catch the rejection and play your control arm.
|
|
3601
|
+
A guest is selected by their guest id, which is stable for as long as their
|
|
3602
|
+
guest account lasts; once they sign in to an account they are that
|
|
3603
|
+
account's player, and its selection applies.
|
|
3604
|
+
|
|
3605
|
+
In a serverless function it selects for the player who invoked it, with
|
|
3606
|
+
no wait, and rejects when a game server invoked it.
|
|
3607
|
+
|
|
3608
|
+
Works with analytics turned off: it reads no setting and sends nothing.
|
|
3609
|
+
|
|
3610
|
+
| Parameter | Type | Description |
|
|
3611
|
+
| --- | --- | --- |
|
|
3612
|
+
| `key` | `string` | Names the experiment — any non-empty string. |
|
|
3613
|
+
| `count` | `number` | How many arms, an integer from 1 to 2048 — or, in the list
|
|
3614
|
+
form, `options`: the arms themselves, 1 to 2048 strings. |
|
|
3615
|
+
|
|
3616
|
+
**Returns.** The arm's index, or the chosen string.
|
|
3617
|
+
|
|
3618
|
+
**Throws.** TypeError for an empty key or a list holding something other than
|
|
3619
|
+
strings; RangeError for a count or list size outside 1 to 2048. Both
|
|
3620
|
+
reject before waiting for the player.
|
|
3621
|
+
|
|
3556
3622
|
<a id="analyticsapi-environment"></a>
|
|
3557
3623
|
|
|
3558
3624
|
## AnalyticsApi.environment
|
|
@@ -3680,6 +3746,47 @@ The runtime companion to [PlatformEventName](#platformeventname), for a game tha
|
|
|
3680
3746
|
show what it gets for free, or to check a funnel step it authored against
|
|
3681
3747
|
what the platform actually emits.
|
|
3682
3748
|
|
|
3749
|
+
<a id="experimentselection"></a>
|
|
3750
|
+
|
|
3751
|
+
## experimentSelection
|
|
3752
|
+
|
|
3753
|
+
```ts
|
|
3754
|
+
function experimentSelection(key: string, playerId: string, count: number): number
|
|
3755
|
+
```
|
|
3756
|
+
|
|
3757
|
+
Which arm of an experiment a player gets — the same answer for the same key
|
|
3758
|
+
and player, every time, anywhere.
|
|
3759
|
+
|
|
3760
|
+
In a game, call [AnalyticsApi.experiment](#analyticsapi-experiment) instead: it is this, with
|
|
3761
|
+
the signed-in player's id filled in. This form takes the id, for code that
|
|
3762
|
+
has one in hand — a dedicated server reproducing what a member's client
|
|
3763
|
+
picked, or a test.
|
|
3764
|
+
|
|
3765
|
+
Pass a COUNT and get an index, `0` to `count - 1`; pass a LIST of strings
|
|
3766
|
+
and get one of them. A string listed twice is twice as likely, which is how
|
|
3767
|
+
to weight an arm.
|
|
3768
|
+
|
|
3769
|
+
The answer is `fmix32(fnv1a32("<key>:<playerId>")) % count`: FNV-1a 32-bit
|
|
3770
|
+
over the UTF-8 bytes of the key, a colon and the player id, then
|
|
3771
|
+
MurmurHash3's 32-bit finalizer, as an unsigned integer — with a list, the
|
|
3772
|
+
index into it. The hash is fixed, so any other language can reproduce a
|
|
3773
|
+
selection; docs/analytics.md has it written out.
|
|
3774
|
+
|
|
3775
|
+
⚠️ **Keep the key and the arms fixed for the life of the experiment.** A new
|
|
3776
|
+
count or list re-deals every player. To start over on purpose, use a new
|
|
3777
|
+
key.
|
|
3778
|
+
|
|
3779
|
+
| Parameter | Type | Description |
|
|
3780
|
+
| --- | --- | --- |
|
|
3781
|
+
| `key` | `string` | Names the experiment — any non-empty string, and the only thing
|
|
3782
|
+
that makes one experiment's split independent of another's. |
|
|
3783
|
+
| `playerId` | `string` | The player to select for — [GemIdentity.playerId](#gemidentity-playerid). |
|
|
3784
|
+
| `count` | `number` | How many arms, an integer from 1 to 2048 — or, in the list
|
|
3785
|
+
form, `options`: the arms themselves, 1 to 2048 strings. |
|
|
3786
|
+
|
|
3787
|
+
**Throws.** TypeError for an empty key or player id, or a list holding something
|
|
3788
|
+
other than strings; RangeError for a count or list size outside 1 to 2048.
|
|
3789
|
+
|
|
3683
3790
|
<a id="avatar3dprice"></a>
|
|
3684
3791
|
|
|
3685
3792
|
## Avatar3dPrice
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The server surface — API reference
|
|
2
2
|
|
|
3
|
-
Generated from `@indiegems/gem-web-sdk/sdk` v15.
|
|
3
|
+
Generated from `@indiegems/gem-web-sdk/sdk` v15.1.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": "15.
|
|
5
|
+
"version": "15.1.1"
|
|
6
6
|
},
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -4393,6 +4393,35 @@
|
|
|
4393
4393
|
"defaultValue": null,
|
|
4394
4394
|
"guides": []
|
|
4395
4395
|
},
|
|
4396
|
+
{
|
|
4397
|
+
"id": "AnalyticsApi.experiment",
|
|
4398
|
+
"name": "experiment",
|
|
4399
|
+
"anchor": "analyticsapi-experiment",
|
|
4400
|
+
"group": "analytics",
|
|
4401
|
+
"signature": "experiment(key: string, count: number): Promise<number>",
|
|
4402
|
+
"documented": true,
|
|
4403
|
+
"summary": "Which arm of one of your experiments this player gets — the same answer on\nevery call, every refresh and every device. Use it wherever you would have\npicked an arm with `Math.random()`, which deals a player a new arm on each\nvisit and makes the test unreadable.\n\nPass a COUNT and get an index, `0` to `count - 1`; pass a LIST of strings\nand get one of them. A string listed twice is twice as likely.\n\n```js\nconst tutorial = await gem.analytics.experiment('tutorial_length', ['short', 'long']);\nconst layout = await gem.analytics.experiment('shop_layout', 3); // 0, 1 or 2\n```\n\nThe selection is a hash of the key and the signed-in player's id — FNV-1a\n32-bit over `\"<key>:<playerId>\"`, then MurmurHash3's 32-bit finalizer, mod\nthe arm count; see\n[experimentSelection](api-reference-game.md#experimentselection), which computes the same answer for an id you\nhold. Hashing the key in is what keeps your experiments independent: the\nplayers in one experiment's first arm are not the same players in every\nother experiment's first arm.\n\n⚠️ **Keep the key and the arms fixed for the life of the experiment.** A new\ncount or list re-deals every player. To start over on purpose, use a new\nkey.\n\n⚠️ **NOTHING IS RECORDED FOR YOU.** The selection is not sent anywhere, and\nthe SDK does not add it to your events. To analyse an experiment, declare a\ndimension for it on the events you want to break down, with the arms as its\nvalues, named after the experiment, and pass the selection there yourself:\n`gem.analytics.track('tutorial_finished', { tutorial_length: tutorial })`.\nNever the platform's `variant` field, which belongs to store-page\nexperiments.\n\nWAITS FOR THE PLAYER. The id arrives with the first play token, so a call\nduring boot waits for it, and rejects as [Gem.awaitIdentity](api-reference-game.md#gem-awaitidentity) does\nwhen none arrives — outside the Arcade, the Gem Editor or the dev host.\nThere is no random fallback: catch the rejection and play your control arm.\nA guest is selected by their guest id, which is stable for as long as their\nguest account lasts; once they sign in to an account they are that\naccount's player, and its selection applies.\n\nIn a serverless function it selects for the player who invoked it, with\nno wait, and rejects when a game server invoked it.\n\nWorks with analytics turned off: it reads no setting and sends nothing.",
|
|
4404
|
+
"deprecated": null,
|
|
4405
|
+
"params": [
|
|
4406
|
+
{
|
|
4407
|
+
"name": "key",
|
|
4408
|
+
"type": "string",
|
|
4409
|
+
"optional": false,
|
|
4410
|
+
"summary": "Names the experiment — any non-empty string."
|
|
4411
|
+
},
|
|
4412
|
+
{
|
|
4413
|
+
"name": "count",
|
|
4414
|
+
"type": "number",
|
|
4415
|
+
"optional": false,
|
|
4416
|
+
"summary": "How many arms, an integer from 1 to 2048 — or, in the list\n form, `options`: the arms themselves, 1 to 2048 strings."
|
|
4417
|
+
}
|
|
4418
|
+
],
|
|
4419
|
+
"returns": "The arm's index, or the chosen string.",
|
|
4420
|
+
"throws": "TypeError for an empty key or a list holding something other than\n strings; RangeError for a count or list size outside 1 to 2048. Both\n reject before waiting for the player.",
|
|
4421
|
+
"example": null,
|
|
4422
|
+
"defaultValue": null,
|
|
4423
|
+
"guides": []
|
|
4424
|
+
},
|
|
4396
4425
|
{
|
|
4397
4426
|
"id": "AnalyticsApi.environment",
|
|
4398
4427
|
"name": "environment",
|
|
@@ -4496,6 +4525,41 @@
|
|
|
4496
4525
|
"defaultValue": null,
|
|
4497
4526
|
"guides": []
|
|
4498
4527
|
},
|
|
4528
|
+
{
|
|
4529
|
+
"id": "experimentSelection",
|
|
4530
|
+
"name": "experimentSelection",
|
|
4531
|
+
"anchor": "experimentselection",
|
|
4532
|
+
"group": "analytics",
|
|
4533
|
+
"signature": "function experimentSelection(key: string, playerId: string, count: number): number",
|
|
4534
|
+
"documented": true,
|
|
4535
|
+
"summary": "Which arm of an experiment a player gets — the same answer for the same key\nand player, every time, anywhere.\n\nIn a game, call [AnalyticsApi.experiment](api-reference-game.md#analyticsapi-experiment) instead: it is this, with\nthe signed-in player's id filled in. This form takes the id, for code that\nhas one in hand — a dedicated server reproducing what a member's client\npicked, or a test.\n\nPass a COUNT and get an index, `0` to `count - 1`; pass a LIST of strings\nand get one of them. A string listed twice is twice as likely, which is how\nto weight an arm.\n\nThe answer is `fmix32(fnv1a32(\"<key>:<playerId>\")) % count`: FNV-1a 32-bit\nover the UTF-8 bytes of the key, a colon and the player id, then\nMurmurHash3's 32-bit finalizer, as an unsigned integer — with a list, the\nindex into it. The hash is fixed, so any other language can reproduce a\nselection; docs/analytics.md has it written out.\n\n⚠️ **Keep the key and the arms fixed for the life of the experiment.** A new\ncount or list re-deals every player. To start over on purpose, use a new\nkey.",
|
|
4536
|
+
"deprecated": null,
|
|
4537
|
+
"params": [
|
|
4538
|
+
{
|
|
4539
|
+
"name": "key",
|
|
4540
|
+
"type": "string",
|
|
4541
|
+
"optional": false,
|
|
4542
|
+
"summary": "Names the experiment — any non-empty string, and the only thing\n that makes one experiment's split independent of another's."
|
|
4543
|
+
},
|
|
4544
|
+
{
|
|
4545
|
+
"name": "playerId",
|
|
4546
|
+
"type": "string",
|
|
4547
|
+
"optional": false,
|
|
4548
|
+
"summary": "The player to select for — [GemIdentity.playerId](api-reference-game.md#gemidentity-playerid)."
|
|
4549
|
+
},
|
|
4550
|
+
{
|
|
4551
|
+
"name": "count",
|
|
4552
|
+
"type": "number",
|
|
4553
|
+
"optional": false,
|
|
4554
|
+
"summary": "How many arms, an integer from 1 to 2048 — or, in the list\n form, `options`: the arms themselves, 1 to 2048 strings."
|
|
4555
|
+
}
|
|
4556
|
+
],
|
|
4557
|
+
"returns": null,
|
|
4558
|
+
"throws": "TypeError for an empty key or player id, or a list holding something\n other than strings; RangeError for a count or list size outside 1 to 2048.",
|
|
4559
|
+
"example": null,
|
|
4560
|
+
"defaultValue": null,
|
|
4561
|
+
"guides": []
|
|
4562
|
+
},
|
|
4499
4563
|
{
|
|
4500
4564
|
"id": "Avatar3dPrice",
|
|
4501
4565
|
"name": "Avatar3dPrice",
|
|
@@ -28032,6 +28096,35 @@
|
|
|
28032
28096
|
"defaultValue": null,
|
|
28033
28097
|
"guides": []
|
|
28034
28098
|
},
|
|
28099
|
+
{
|
|
28100
|
+
"id": "AnalyticsApi.experiment",
|
|
28101
|
+
"name": "experiment",
|
|
28102
|
+
"anchor": "analyticsapi-experiment",
|
|
28103
|
+
"group": "analytics",
|
|
28104
|
+
"signature": "experiment(key: string, count: number): Promise<number>",
|
|
28105
|
+
"documented": true,
|
|
28106
|
+
"summary": "Which arm of one of your experiments this player gets — the same answer on\nevery call, every refresh and every device. Use it wherever you would have\npicked an arm with `Math.random()`, which deals a player a new arm on each\nvisit and makes the test unreadable.\n\nPass a COUNT and get an index, `0` to `count - 1`; pass a LIST of strings\nand get one of them. A string listed twice is twice as likely.\n\n```js\nconst tutorial = await gem.analytics.experiment('tutorial_length', ['short', 'long']);\nconst layout = await gem.analytics.experiment('shop_layout', 3); // 0, 1 or 2\n```\n\nThe selection is a hash of the key and the signed-in player's id — FNV-1a\n32-bit over `\"<key>:<playerId>\"`, then MurmurHash3's 32-bit finalizer, mod\nthe arm count; see\n`experimentSelection`, which computes the same answer for an id you\nhold. Hashing the key in is what keeps your experiments independent: the\nplayers in one experiment's first arm are not the same players in every\nother experiment's first arm.\n\n⚠️ **Keep the key and the arms fixed for the life of the experiment.** A new\ncount or list re-deals every player. To start over on purpose, use a new\nkey.\n\n⚠️ **NOTHING IS RECORDED FOR YOU.** The selection is not sent anywhere, and\nthe SDK does not add it to your events. To analyse an experiment, declare a\ndimension for it on the events you want to break down, with the arms as its\nvalues, named after the experiment, and pass the selection there yourself:\n`gem.analytics.track('tutorial_finished', { tutorial_length: tutorial })`.\nNever the platform's `variant` field, which belongs to store-page\nexperiments.\n\nWAITS FOR THE PLAYER. The id arrives with the first play token, so a call\nduring boot waits for it, and rejects as `Gem.awaitIdentity` does\nwhen none arrives — outside the Arcade, the Gem Editor or the dev host.\nThere is no random fallback: catch the rejection and play your control arm.\nA guest is selected by their guest id, which is stable for as long as their\nguest account lasts; once they sign in to an account they are that\naccount's player, and its selection applies.\n\nIn a serverless function it selects for the player who invoked it, with\nno wait, and rejects when a game server invoked it.\n\nWorks with analytics turned off: it reads no setting and sends nothing.",
|
|
28107
|
+
"deprecated": null,
|
|
28108
|
+
"params": [
|
|
28109
|
+
{
|
|
28110
|
+
"name": "key",
|
|
28111
|
+
"type": "string",
|
|
28112
|
+
"optional": false,
|
|
28113
|
+
"summary": "Names the experiment — any non-empty string."
|
|
28114
|
+
},
|
|
28115
|
+
{
|
|
28116
|
+
"name": "count",
|
|
28117
|
+
"type": "number",
|
|
28118
|
+
"optional": false,
|
|
28119
|
+
"summary": "How many arms, an integer from 1 to 2048 — or, in the list\n form, `options`: the arms themselves, 1 to 2048 strings."
|
|
28120
|
+
}
|
|
28121
|
+
],
|
|
28122
|
+
"returns": "The arm's index, or the chosen string.",
|
|
28123
|
+
"throws": "TypeError for an empty key or a list holding something other than\n strings; RangeError for a count or list size outside 1 to 2048. Both\n reject before waiting for the player.",
|
|
28124
|
+
"example": null,
|
|
28125
|
+
"defaultValue": null,
|
|
28126
|
+
"guides": []
|
|
28127
|
+
},
|
|
28035
28128
|
{
|
|
28036
28129
|
"id": "AnalyticsApi.environment",
|
|
28037
28130
|
"name": "environment",
|
package/docs/changelog.md
CHANGED
|
@@ -16,6 +16,21 @@ 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
|
+
## 15.1.1
|
|
20
|
+
|
|
21
|
+
Upgrading from 15.1.0. Impact: none.
|
|
22
|
+
|
|
23
|
+
Nothing in this release asks anything of a game.
|
|
24
|
+
|
|
25
|
+
## 15.1.0
|
|
26
|
+
|
|
27
|
+
Upgrading from 15.0.0. Impact: none.
|
|
28
|
+
|
|
29
|
+
### New capability
|
|
30
|
+
|
|
31
|
+
- AnalyticsApi.experiment gives a player a stable experiment arm. Pass an experiment key and either an arm count or a list of options. It returns the same arm for that player on every call, refresh and device: an index from zero up to one less than the count, or one of the strings in the list. Games that picked an arm with a random number landed a player in a different arm on every refresh, so no test could be read. Switching to this call fixes that. Different experiment keys are independent of each other. Nothing is recorded for you. To analyse an experiment, declare a dimension on the events you choose and pass the selection yourself. The call rejects when there is no player.
|
|
32
|
+
- experimentSelection computes the same arm as AnalyticsApi.experiment from an experiment key, a player id you hold, and an arm count or list of options. Use it where the SDK is not running, such as a dedicated game server or an analytics query, to reproduce which arm a player was given.
|
|
33
|
+
|
|
19
34
|
## 15.0.0
|
|
20
35
|
|
|
21
36
|
*Written from the API facts alone; no summary was generated for this release.*
|
package/docs/getting-started.md
CHANGED
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": "afced8f94514818eca6de3e72bdd988b2cee10925086c3bfa47c0a8ec8967454",
|
|
17
|
+
"updated": "2026-10-23T00:13:14Z"
|
|
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": "0132ed26864b9c47309c3ab4df30329c5514eaa4dadabde25f2417f6a71a9a2c",
|
|
42
|
+
"updated": "2026-10-23T02:41:11Z"
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
"file": "daily-challenges.md",
|
|
@@ -68,8 +68,8 @@
|
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
"file": "getting-started.md",
|
|
71
|
-
"sha256": "
|
|
72
|
-
"updated": "2026-10-
|
|
71
|
+
"sha256": "f741da3ef391fd4641cd6a3c6ad99dc995f3a2895409965f6c01bc7d045db9e7",
|
|
72
|
+
"updated": "2026-10-23T02:41:11Z"
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
"file": "inventory-and-catalogs.md",
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
85
|
"file": "match-session.md",
|
|
86
|
-
"sha256": "
|
|
87
|
-
"updated": "2026-10-
|
|
86
|
+
"sha256": "cfc2f76e310f4a851ac0b9185687d284cced6533fe12fb359ce0cb2845c5e5ef",
|
|
87
|
+
"updated": "2026-10-23T01:58:45Z"
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
90
|
"file": "p2p-and-parties.md",
|
|
@@ -128,8 +128,8 @@
|
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
130
|
"file": "server-bundles.md",
|
|
131
|
-
"sha256": "
|
|
132
|
-
"updated": "2026-10-
|
|
131
|
+
"sha256": "1ca3430270d7c180304bcb297484bf4c8a22689b7728fce753d9a2bf6b6248ad",
|
|
132
|
+
"updated": "2026-10-23T02:41:11Z"
|
|
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": "645ed81d6d89996759880f9faf5a7ecb587953b9205bad2d32a30001d3238552",
|
|
157
|
+
"updated": "2026-10-23T01:58:45Z"
|
|
158
158
|
}
|
|
159
159
|
]
|
|
160
160
|
}
|
package/docs/match-session.md
CHANGED
|
@@ -272,7 +272,22 @@ wiki keeps a live match live. Only the transport says a link dropped.
|
|
|
272
272
|
The two messages the arcade pushes about the page — `onPause` on both edges, and `onWillTerminate`
|
|
273
273
|
with its grace budget — are the game's to honour and are on
|
|
274
274
|
[what-a-game-must-do.md](what-a-game-must-do.md#what-the-parent-says-about-the-session-prefs-pause-and-terminate).
|
|
275
|
-
Neither ends a match
|
|
275
|
+
Neither ends a match, and a game must not leave its room on `onPause`: the same pause fires when
|
|
276
|
+
the phone locks.
|
|
277
|
+
|
|
278
|
+
**Leave game is the exception, and the platform handles it.** A player who chooses Leave game in
|
|
279
|
+
the Arcade's menu is taken out of every room of the game, on every device, by the platform when
|
|
280
|
+
they choose it — not by a request from the game's page as it unloads, so a game needs no
|
|
281
|
+
`pagehide` leave for this. Each room is left the way `gem.rooms.leave()` leaves it, so a server
|
|
282
|
+
holding the seat for a reconnect hears the player left and frees it. On a phone the game is still
|
|
283
|
+
parked; a player who comes back and presses Continue resumes without a seat, and the room's next
|
|
284
|
+
read says so — `kicked` while the room plays on without them, `matchEnded` and then `closed` when
|
|
285
|
+
they were the last one in it. Go back to your menu on either.
|
|
286
|
+
|
|
287
|
+
A room the player's party leader seated them in stays: only the leader can take the party out,
|
|
288
|
+
Leave game keeps that room without an error, and reopening the game puts the player back in their
|
|
289
|
+
party's match. [what-a-game-must-do.md](what-a-game-must-do.md#the-arcades-menu) has the whole
|
|
290
|
+
rule.
|
|
276
291
|
|
|
277
292
|
## 8. When it goes wrong
|
|
278
293
|
|
package/docs/server-bundles.md
CHANGED
|
@@ -80,8 +80,8 @@ To check the download yourself: the package npm serves is, byte for byte, the fi
|
|
|
80
80
|
also publishes on its download host, with the hash beside it:
|
|
81
81
|
|
|
82
82
|
```bash
|
|
83
|
-
curl -fsSL -O https://downloads.gemarcade.com/sdk/v15.
|
|
84
|
-
curl -fsSL https://downloads.gemarcade.com/sdk/v15.
|
|
83
|
+
curl -fsSL -O https://downloads.gemarcade.com/sdk/v15.1.1/gem-web-sdk.tgz
|
|
84
|
+
curl -fsSL https://downloads.gemarcade.com/sdk/v15.1.1/gem-web-sdk.tgz.sri
|
|
85
85
|
openssl dgst -sha384 -binary gem-web-sdk.tgz | openssl base64 -A
|
|
86
86
|
```
|
|
87
87
|
|
|
@@ -610,7 +610,7 @@ there is no route by which the frame could request one. `createGem` forwards all
|
|
|
610
610
|
arcade cannot release yours from outside the frame. Keep it captured and the player cannot click
|
|
611
611
|
the dialog sitting on top of your game.
|
|
612
612
|
|
|
613
|
-
**Save on pause.** When a player on a phone chooses
|
|
613
|
+
**Save on pause.** When a player on a phone chooses Leave game in
|
|
614
614
|
[the Arcade's menu](#the-arcades-menu), or presses the device's Back, your
|
|
615
615
|
game is **parked**, not closed:
|
|
616
616
|
it receives `onPause(true)` and stays loaded while the player browses. They may come back and
|
|
@@ -620,6 +620,14 @@ there is no route by which the frame could request one. `createGem` forwards all
|
|
|
620
620
|
nothing after the pause. So the pause edge is your last guaranteed moment. Write anything the
|
|
621
621
|
player would mind losing there, and never hold it back for a terminate warning that may not come.
|
|
622
622
|
|
|
623
|
+
**Leave game has already left the rooms.** A parked game is still loaded, but a player who chose
|
|
624
|
+
Leave game is no longer in its rooms: the platform took them out when they chose it
|
|
625
|
+
([the Arcade's menu](#the-arcades-menu) says which rooms). If they come back and press Continue,
|
|
626
|
+
your game resumes without a seat, and the room's next read says so: `kicked` while the room plays
|
|
627
|
+
on without them, `matchEnded` and then `closed` when they were the last one in it. Go back to your
|
|
628
|
+
own menu on either, as you already do for a match that ended while your game was away. The
|
|
629
|
+
device's Back parks the game without leaving anything.
|
|
630
|
+
|
|
623
631
|
**Re-read on resume.** Your game is told that it was paused and resumed, never why, and never
|
|
624
632
|
what the player did in the meantime. Their Gems and Coins can change while you are paused, so
|
|
625
633
|
if your game keeps anything worked out from a balance, such as a Buy button it greyed out or a
|
|
@@ -642,7 +650,10 @@ there is no route by which the frame could request one. `createGem` forwards all
|
|
|
642
650
|
Neither `onPause` nor `onWillTerminate` touches a match. A match whose link dropped while the phone was locked
|
|
643
651
|
comes back through the SDK's own reconnect on the next visible edge, and one that ended meanwhile
|
|
644
652
|
raises `matchEnded` on the same edge — [match-session.md](match-session.md) §7. Pause the loop
|
|
645
|
-
on `onPause`; do not leave the room.
|
|
653
|
+
on `onPause`; do not leave the room. The same pause fires when the phone locks, and a player who
|
|
654
|
+
locked their phone mid-match expects to be in it when they unlock. The one pause that does take the
|
|
655
|
+
player out of their rooms is Leave game, and the platform does that itself
|
|
656
|
+
([the Arcade's menu](#the-arcades-menu)), not your game.
|
|
646
657
|
|
|
647
658
|
🔴 **The assembly used to drop all three.** `GemFrame` parsed them and `createGem` wired none, so a
|
|
648
659
|
game built the one-call way could not hear them — and could not add a handler afterwards either,
|
|
@@ -734,7 +745,7 @@ game, a framed or full-window game on a desktop, a tablet or a TV in native full
|
|
|
734
745
|
|
|
735
746
|
- **The Gem button**, the Gem Arcade's gem, always there (a 44px square touch target), in
|
|
736
747
|
the **top-left corner** unless your game [asks for another](#moving-the-gem-gemsetgemcorner). It
|
|
737
|
-
opens [the Arcade's menu](#the-arcades-menu):
|
|
748
|
+
opens [the Arcade's menu](#the-arcades-menu): Leave game, the player's Gems, Coins and
|
|
738
749
|
achievement score, mute, the player's Arcade-wide settings and the rest. Not your settings: those
|
|
739
750
|
are [your own button](#game-settings).
|
|
740
751
|
- **Nothing else.** The player's wallets (Gems, Coins, their achievement score) are **not** drawn
|
|
@@ -1397,7 +1408,7 @@ What it does, so you know what to expect:
|
|
|
1397
1408
|
|
|
1398
1409
|
The Arcade keeps one button over every game, on every device: the Gem, top-left unless your game
|
|
1399
1410
|
[asks for another corner](#moving-the-gem-gemsetgemcorner). Its menu is
|
|
1400
|
-
how a player leaves (**
|
|
1411
|
+
how a player leaves (**Leave game**), sees their Gems, Coins and achievement score, mutes,
|
|
1401
1412
|
goes fullscreen, reports a problem and changes their Arcade-wide settings — the ones that reach you as [`gem.prefs.recommended`](#game-settings). You can add up to
|
|
1402
1413
|
four entries of your own:
|
|
1403
1414
|
|
|
@@ -1421,10 +1432,25 @@ gem.setMenu({
|
|
|
1421
1432
|
and sent when it does, and again after any reconnect.
|
|
1422
1433
|
- **The Arcade pauses your game while its menu is open** — `onPause(true)`, then `onPause(false)`
|
|
1423
1434
|
before a handler of yours runs. Nothing else is needed from you.
|
|
1424
|
-
- **You cannot remove or reorder the Arcade's entries**, and
|
|
1435
|
+
- **You cannot remove or reorder the Arcade's entries**, and Leave game is always there.
|
|
1425
1436
|
On a phone it **parks** your game, as above; on a desktop, a tablet and a television it ends the
|
|
1426
1437
|
session, with `onWillTerminate` first. Either way your game heard `onPause(true)` when the menu
|
|
1427
1438
|
opened: save there — it is the edge you are sure to get.
|
|
1439
|
+
- **Leave game takes the player out of every room of your game**, on every device, and the
|
|
1440
|
+
platform does it, not your page. It leaves each room the way `gem.rooms.leave()` does, so a
|
|
1441
|
+
match server holding the seat hears the player left and frees it rather than waiting out its
|
|
1442
|
+
reconnect budget. It leaves open rooms, matches in progress and custom lobbies alike; rooms of
|
|
1443
|
+
other games, and the player's Arcade party, are untouched. So your game needs no leave of its own
|
|
1444
|
+
for Leave game: not on `onPause`, not on `onWillTerminate` and not from a `pagehide` handler,
|
|
1445
|
+
which a parked game never gets. The SDK's own `pagehide` leave stays, for the tab closed or
|
|
1446
|
+
reloaded out from under you
|
|
1447
|
+
([rooms-and-matches.md](rooms-and-matches.md) §4.3a).
|
|
1448
|
+
- **A room the player's party leader seated them in stays.** A party member cannot leave the
|
|
1449
|
+
room their leader took the party into; only the leader can take the party out. Leave game leaves
|
|
1450
|
+
every other room and keeps that one, and that is not an error. When the player opens your game
|
|
1451
|
+
again, they are back in their party's match — by design: they left the game, not their friends.
|
|
1452
|
+
A game that follows its party into the leader's match
|
|
1453
|
+
([p2p-and-parties.md](p2p-and-parties.md) §7.4) needs nothing more for this.
|
|
1428
1454
|
|
|
1429
1455
|
`gem.setMenu` returns `false` under an embedder with no menu, and outside any embedder. Your
|
|
1430
1456
|
settings button is there either way; what such a game also needs is a way out of its own, a control
|