@indiegems/gem-web-sdk 8.1.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +1 -0
- package/dist/function.d.ts +1 -1
- package/dist/game.d.ts +1 -1
- package/dist/gem-web-sdk.esm.js +1 -1
- package/dist/gem-web-sdk.function.js +1 -1
- package/dist/gem-web-sdk.game.js +1 -1
- package/dist/gem-web-sdk.min.js +1 -1
- package/dist/gem-web-sdk.sdk.js +1 -1
- package/dist/gem-web-sdk.server.js +1 -1
- package/dist/sdk.d.ts +1 -1
- package/dist/server.d.ts +1 -1
- package/docs/api/api-reference-game.md +36 -25
- package/docs/api/api-reference-server.md +6 -2
- package/docs/api/reference.json +19 -19
- package/docs/changelog.md +8 -0
- package/docs/cross-promotion.md +50 -30
- package/docs/getting-started.md +1 -1
- package/docs/manifest.json +23 -18
- package/docs/match-session.md +1 -1
- package/docs/rewarded-ads.md +464 -0
- package/docs/room-shapes.md +54 -76
- package/docs/rooms-and-matches.md +2 -3
- package/docs/server-bundles.md +2 -2
- package/docs/serverless-functions.md +42 -32
- package/docs/what-a-game-must-do.md +8 -2
- package/package.json +1 -1
- package/schema/bridge.v1.json +30 -6
package/docs/api/reference.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"source": {
|
|
4
4
|
"repo": "IndieGems/gem-web-sdk",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "9.0.0"
|
|
6
6
|
},
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -3978,14 +3978,20 @@
|
|
|
3978
3978
|
"url": "https://gemarcade.com/developer/api/game#adformat",
|
|
3979
3979
|
"signature": "type AdFormat = \"interstitial\" | \"rewarded\"",
|
|
3980
3980
|
"documented": true,
|
|
3981
|
-
"summary": "Which kind of break this is.\n\n`interstitial` is a panel the player dismisses and nothing follows from.\n\n
|
|
3981
|
+
"summary": "Which kind of break this is.\n\n`interstitial` is a panel the player dismisses and nothing follows from.\n\n`rewarded` is a button the player pressed for a reward — a revive, an extra\nattempt — that your game config declares for the placement in\n`rewarded_placements`. It is served where this arcade serves it, and answered\n`no_ad` where it does not. ⚠️ The outcome grants nothing: the platform grants\nthe reward, against its own record, never your game reading `shown`. It\narrives on your game's inventory for this player, written by the platform,\nso re-read `gem.me.inventory` after a `rewarded` ask settles rather than\nwaiting for a signal on this pair; there will not be one.",
|
|
3982
3982
|
"deprecated": null,
|
|
3983
3983
|
"params": [],
|
|
3984
3984
|
"returns": null,
|
|
3985
3985
|
"throws": null,
|
|
3986
3986
|
"example": null,
|
|
3987
3987
|
"defaultValue": null,
|
|
3988
|
-
"guides": [
|
|
3988
|
+
"guides": [
|
|
3989
|
+
{
|
|
3990
|
+
"slug": "rewarded-ads",
|
|
3991
|
+
"anchor": null,
|
|
3992
|
+
"url": "https://gemarcade.com/developer/docs/rewarded-ads"
|
|
3993
|
+
}
|
|
3994
|
+
]
|
|
3989
3995
|
},
|
|
3990
3996
|
{
|
|
3991
3997
|
"id": "AdRequest",
|
|
@@ -4012,7 +4018,7 @@
|
|
|
4012
4018
|
"url": "https://gemarcade.com/developer/api/game#adrequest-placement",
|
|
4013
4019
|
"signature": "placement: string",
|
|
4014
4020
|
"documented": true,
|
|
4015
|
-
"summary": "Your own name for the break — `level-complete`, `between-rounds`.\n\n**1 to 32 characters of `a-z`, `0-9`, `-` and `_`.** Lowercase, no spaces,\nno accents: `'Boss Fight'` is not a placement, `'boss-fight'` is. The\ngrammar is the platform's own, and
|
|
4021
|
+
"summary": "Your own name for the break — `level-complete`, `between-rounds`.\n\n**1 to 32 characters of `a-z`, `0-9`, `-` and `_`.** Lowercase, no spaces,\nno accents: `'Boss Fight'` is not a placement, `'boss-fight'` is. The\ngrammar is the platform's own, and the SDK enforces it so a name that does\nnot fit is refused at your keyboard rather than in the field. A placement\noutside it is answered `no_ad` and reported to your `onError` handler.\n\nFor `rewarded`, it names the reward your game config declares in\n`rewarded_placements`: the platform matches it against that config, and a\nplacement the config does not declare is answered `no_ad`. For\n`interstitial`, the platform matches it against nothing; there it is for\nyour own telemetry, so two break points in your game can be told apart.\nEither way it is never shown to a player and **never sent to an ad\nnetwork**.",
|
|
4016
4022
|
"deprecated": null,
|
|
4017
4023
|
"params": [],
|
|
4018
4024
|
"returns": null,
|
|
@@ -4046,7 +4052,7 @@
|
|
|
4046
4052
|
"url": "https://gemarcade.com/developer/api/game#adoutcomekind",
|
|
4047
4053
|
"signature": "type AdOutcomeKind = \"shown\" | \"no_ad\" | \"unavailable\"",
|
|
4048
4054
|
"documented": true,
|
|
4049
|
-
"summary": "The complete vocabulary of answers. Three values, and there will not be a\nfourth.\n\n- `shown` — a panel was painted and has now closed. Resume. **It grants\n nothing.**\n- `no_ad` — nothing was painted. Carry on exactly as if you had not asked.\n This is the common answer.\n- `unavailable` — the arcade could not render just now. Not a softer no; read\n it exactly as you read `no_ad`.\n\nNo reason is given for a `no_ad`, and one will never be added: a reason would\ntell your game something about the player — an age, a consent state, a\nsubscription — that game code is deliberately not told.\n\nThe one thing you can learn is about **your own economy**, and not from this\nanswer: [AdsApi.rewardedStatus](https://gemarcade.com/developer/api/game#adsapi-rewardedstatus) reports your own declared rewarded caps\nas the platform holds them (a platform-wide per-placement maximum, where one\nis set, can lower a cap). It never reports anything about the player.",
|
|
4055
|
+
"summary": "The complete vocabulary of answers. Three values, and there will not be a\nfourth.\n\n- `shown` — a panel was painted and has now closed. Resume. **It grants\n nothing.**\n- `no_ad` — nothing was painted. Carry on exactly as if you had not asked.\n This is the common answer. After a `rewarded` ask the reward may still\n have landed, with no ad, so re-read your inventory either way.\n- `unavailable` — the arcade could not render just now. Not a softer no; read\n it exactly as you read `no_ad`.\n\nNo reason is given for a `no_ad`, and one will never be added: a reason would\ntell your game something about the player — an age, a consent state, a\nsubscription — that game code is deliberately not told.\n\nThe one thing you can learn is about **your own economy**, and not from this\nanswer: [AdsApi.rewardedStatus](https://gemarcade.com/developer/api/game#adsapi-rewardedstatus) reports your own declared rewarded caps\nas the platform holds them (a platform-wide per-placement maximum, where one\nis set, can lower a cap). It never reports anything about the player.",
|
|
4050
4056
|
"deprecated": null,
|
|
4051
4057
|
"params": [],
|
|
4052
4058
|
"returns": null,
|
|
@@ -4114,7 +4120,7 @@
|
|
|
4114
4120
|
"url": "https://gemarcade.com/developer/api/game#adsapi-requestad",
|
|
4115
4121
|
"signature": "requestAd(req: AdRequest): Promise<AdOutcome>",
|
|
4116
4122
|
"documented": true,
|
|
4117
|
-
"summary": "Ask whether the arcade has anything to show at this break in play.\n\nNOTHING REJECTS, and the answer is usually `no_ad`. Every local refusal —\nan arcade that does not do this at all, a call while another is still in\nflight, a `placement` this module will not send — resolves `no_ad` rather\nthan throwing, so a game needs exactly one branch. Only \"no answer ever\narrived\" resolves `unavailable`.\n\nThe arcade pauses your game while a panel is up and resumes it when this\nsettles, so you do not need to pause anything yourself.\n\n⚠️ `shown` grants nothing. Never hand out a reward, an unlock or a currency\non the strength of it.",
|
|
4123
|
+
"summary": "Ask whether the arcade has anything to show at this break in play.\n\nNOTHING REJECTS, and the answer is usually `no_ad`. Every local refusal —\nan arcade that does not do this at all, a call while another is still in\nflight, a `placement` this module will not send — resolves `no_ad` rather\nthan throwing, so a game needs exactly one branch. Only \"no answer ever\narrived\" resolves `unavailable`.\n\nThe arcade pauses your game while a panel is up and resumes it when this\nsettles, so you do not need to pause anything yourself.\n\n⚠️ `shown` grants nothing. Never hand out a reward, an unlock or a currency\non the strength of it. A `rewarded` placement's reward is granted by the\nplatform into your game's inventory; re-read `gem.me.inventory` once this\nsettles, whatever it settled as.",
|
|
4118
4124
|
"deprecated": null,
|
|
4119
4125
|
"params": [
|
|
4120
4126
|
{
|
|
@@ -4128,13 +4134,7 @@
|
|
|
4128
4134
|
"throws": null,
|
|
4129
4135
|
"example": "```ts\nconst { outcome } = await gem.ads.requestAd({\n placement: 'level-complete',\n format: 'interstitial',\n});\n// `no_ad` is the ordinary answer. There is nothing to show and nothing\n// to draw; carry straight on to the next level.\nstartNextLevel();\n```",
|
|
4130
4136
|
"defaultValue": null,
|
|
4131
|
-
"guides": [
|
|
4132
|
-
{
|
|
4133
|
-
"slug": "cross-promotion",
|
|
4134
|
-
"anchor": null,
|
|
4135
|
-
"url": "https://gemarcade.com/developer/docs/cross-promotion"
|
|
4136
|
-
}
|
|
4137
|
-
]
|
|
4137
|
+
"guides": []
|
|
4138
4138
|
},
|
|
4139
4139
|
{
|
|
4140
4140
|
"id": "AdsApi.rewardedStatus",
|
|
@@ -7534,9 +7534,9 @@
|
|
|
7534
7534
|
"anchor": "functionfailure",
|
|
7535
7535
|
"group": "functions",
|
|
7536
7536
|
"url": "https://gemarcade.com/developer/api/game#functionfailure",
|
|
7537
|
-
"signature": "type FunctionFailure = \"trap\" | \"memory_exceeded\" | \"cpu_budget_exceeded\" | \"host_call_limit\"",
|
|
7537
|
+
"signature": "type FunctionFailure = \"trap\" | \"memory_exceeded\" | \"cpu_budget_exceeded\" | \"host_call_limit\" | \"output_too_large\" | \"argument_too_large\"",
|
|
7538
7538
|
"documented": true,
|
|
7539
|
-
"summary": "Why a call that reached your function did not finish — carried on\n[GemFunctionError.failure](https://gemarcade.com/developer/api/game#gemfunctionerror-failure).\n\n- `trap` — a panic, an `unreachable`, an out-of-bounds access or a stack\n overflow. No stack trace comes back.\n- `memory_exceeded` — past the memory limit.\n- `cpu_budget_exceeded` — past the CPU budget.\n- `host_call_limit` — more platform API calls than one invocation may make.",
|
|
7539
|
+
"summary": "Why a call that reached your function did not finish — carried on\n[GemFunctionError.failure](https://gemarcade.com/developer/api/game#gemfunctionerror-failure).\n\n- `trap` — a panic, an `unreachable`, an out-of-bounds access or a stack\n overflow. No stack trace comes back.\n- `memory_exceeded` — past the memory limit.\n- `cpu_budget_exceeded` — past the CPU budget.\n- `host_call_limit` — more platform API calls than one invocation may make.\n- `output_too_large` — your function's result was over the 1 MiB output cap.\n- `argument_too_large` — a value your function handed to a platform call\n (`log`, `call-api`, `download-link` or `upload-link`) was over the 16 MiB\n per-call limit.",
|
|
7540
7540
|
"deprecated": null,
|
|
7541
7541
|
"params": [],
|
|
7542
7542
|
"returns": null,
|
|
@@ -7770,7 +7770,7 @@
|
|
|
7770
7770
|
"url": "https://gemarcade.com/developer/api/game#arcade_embedders",
|
|
7771
7771
|
"signature": "ARCADE_EMBEDDERS: readonly string[]",
|
|
7772
7772
|
"documented": true,
|
|
7773
|
-
"summary": "The origins the
|
|
7773
|
+
"summary": "The origins the platform embeds a game from, which every game accepts\nwithout naming them: the Gem Arcade website, the Meme Games website, and\nthe Gem Arcade app.\n\nYou do not pass these. They are always allowed, and\n[GemOptions.allowedEmbedders](https://gemarcade.com/developer/api/game#gemoptions-allowedembedders) adds to them rather than replacing them.",
|
|
7774
7774
|
"deprecated": null,
|
|
7775
7775
|
"params": [],
|
|
7776
7776
|
"returns": null,
|
|
@@ -7804,7 +7804,7 @@
|
|
|
7804
7804
|
"url": "https://gemarcade.com/developer/api/game#gemoptions-allowedembedders",
|
|
7805
7805
|
"signature": "readonly allowedEmbedders?: readonly string[]",
|
|
7806
7806
|
"documented": true,
|
|
7807
|
-
"summary": "Extra origins allowed to embed your game, **added to** the ones the Gem\nArcade uses. Most games leave this out.\n\nThe
|
|
7807
|
+
"summary": "Extra origins allowed to embed your game, **added to** the ones the Gem\nArcade uses. Most games leave this out.\n\nThe platform's websites and the Gem Arcade app are always allowed — see\n[ARCADE_EMBEDDERS](https://gemarcade.com/developer/api/game#arcade_embedders) — so do not list them. Set this only when a site\nof your own embeds your game.\n\n```js\ncreateGem({ allowedEmbedders: ['https://my-own-site.example'] })\n```\n\nA page whose origin is not allowed gets no connection, and — deliberately —\nis told nothing about why. That means a typo here looks exactly like a\nhandshake that has not arrived: pass [GemOptions.onRefused](https://gemarcade.com/developer/api/game#gemoptions-onrefused) while you\nare integrating, because it is the only place the difference shows.\n\n⚠️ **Bake these in. Never build them from anything the page can\ninfluence** — not `location`, not a query parameter, not\n`document.referrer`. A value the embedder supplies is a check that can never\nfail. Do not add a `localhost:<port>` entry for your dev host either; the\nSDK admits one on your own machine by rule.",
|
|
7808
7808
|
"deprecated": null,
|
|
7809
7809
|
"params": [],
|
|
7810
7810
|
"returns": null,
|
|
@@ -21951,9 +21951,9 @@
|
|
|
21951
21951
|
"anchor": "functionfailure",
|
|
21952
21952
|
"group": "functions",
|
|
21953
21953
|
"url": "https://gemarcade.com/developer/api/server#functionfailure",
|
|
21954
|
-
"signature": "type FunctionFailure = \"trap\" | \"memory_exceeded\" | \"cpu_budget_exceeded\" | \"host_call_limit\"",
|
|
21954
|
+
"signature": "type FunctionFailure = \"trap\" | \"memory_exceeded\" | \"cpu_budget_exceeded\" | \"host_call_limit\" | \"output_too_large\" | \"argument_too_large\"",
|
|
21955
21955
|
"documented": true,
|
|
21956
|
-
"summary": "Why a call that reached your function did not finish — carried on\n[GemFunctionError.failure](https://gemarcade.com/developer/api/server#gemfunctionerror-failure).\n\n- `trap` — a panic, an `unreachable`, an out-of-bounds access or a stack\n overflow. No stack trace comes back.\n- `memory_exceeded` — past the memory limit.\n- `cpu_budget_exceeded` — past the CPU budget.\n- `host_call_limit` — more platform API calls than one invocation may make.",
|
|
21956
|
+
"summary": "Why a call that reached your function did not finish — carried on\n[GemFunctionError.failure](https://gemarcade.com/developer/api/server#gemfunctionerror-failure).\n\n- `trap` — a panic, an `unreachable`, an out-of-bounds access or a stack\n overflow. No stack trace comes back.\n- `memory_exceeded` — past the memory limit.\n- `cpu_budget_exceeded` — past the CPU budget.\n- `host_call_limit` — more platform API calls than one invocation may make.\n- `output_too_large` — your function's result was over the 1 MiB output cap.\n- `argument_too_large` — a value your function handed to a platform call\n (`log`, `call-api`, `download-link` or `upload-link`) was over the 16 MiB\n per-call limit.",
|
|
21957
21957
|
"deprecated": null,
|
|
21958
21958
|
"params": [],
|
|
21959
21959
|
"returns": null,
|
package/docs/changelog.md
CHANGED
|
@@ -16,6 +16,14 @@ 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
|
+
## 9.0.0
|
|
20
|
+
|
|
21
|
+
Upgrading from 8.1.0. Impact: required.
|
|
22
|
+
|
|
23
|
+
### Must change
|
|
24
|
+
|
|
25
|
+
- FunctionFailure now has two more reasons, output_too_large and argument_too_large. A function that returns more than the output cap, or passes a platform call a value over the per-call size limit, used to fail as trap. It now fails with the matching new reason. Update any code that switches on FunctionFailure so it handles both new reasons. An exhaustive check over the old four reasons will stop compiling until you do. If your game treated trap as the sign of an oversized output or argument, handle the new reasons there too. Then rebuild your game against this release.
|
|
26
|
+
|
|
19
27
|
## 8.1.0
|
|
20
28
|
|
|
21
29
|
Upgrading from 8.0.0. Impact: required.
|
package/docs/cross-promotion.md
CHANGED
|
@@ -4,6 +4,11 @@ The Arcade can show a **full-screen panel about another game** at a break in you
|
|
|
4
4
|
for one; the Arcade decides whether to show anything, draws it in its own chrome over your paused
|
|
5
5
|
game, and tells you when it is done.
|
|
6
6
|
|
|
7
|
+
The same call with `format: 'rewarded'` is a button the player presses for something your game
|
|
8
|
+
gives — a revive, an extra attempt. That one has its own page,
|
|
9
|
+
[`rewarded-ads.md`](rewarded-ads.md). This page is about the interstitial; where a rule here reads
|
|
10
|
+
differently for a rewarded ask, it says so.
|
|
11
|
+
|
|
7
12
|
```js
|
|
8
13
|
const { outcome } = await gem.ads.requestAd({
|
|
9
14
|
placement: 'level-complete',
|
|
@@ -37,6 +42,12 @@ So write the `no_ad` path as your ordinary path:
|
|
|
37
42
|
- Do **not** hold up your own flow waiting for a fill. Ask, and continue when the promise settles.
|
|
38
43
|
- Do **not** retry on a no. A second ask at the same break gets the same answer and spends a slot.
|
|
39
44
|
|
|
45
|
+
**A rewarded ask reads differently, because the player pressed your button and is waiting on it.**
|
|
46
|
+
Its `no_ad` can arrive with the reward already granted (§3), so branch on your inventory, never on
|
|
47
|
+
the outcome; and where nothing landed, a neutral line in your own UI — "No revive this time" — is
|
|
48
|
+
right, with no reason given. [`rewarded-ads.md`
|
|
49
|
+
§5](rewarded-ads.md#5-draw-the-button-and-name-the-reward) is that button.
|
|
50
|
+
|
|
40
51
|
## 2. Nothing rejects
|
|
41
52
|
|
|
42
53
|
`requestAd` always resolves. There is no error branch and no `try`/`catch` to write. Even a refusal
|
|
@@ -49,36 +60,38 @@ resolves `no_ad`, so your game keeps exactly one branch. (The mistake is still r
|
|
|
49
60
|
`shown` says a panel was painted and has closed. It is **not** a receipt, **not** an entitlement,
|
|
50
61
|
and **not** permission to hand anything out.
|
|
51
62
|
|
|
52
|
-
`format: 'rewarded'`
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
frame receives, and a game that unlocks a level or grants a currency on the
|
|
56
|
-
unlock that any page embedding your game can forge.
|
|
63
|
+
That holds for `format: 'rewarded'` too, the one format that pays the player. The platform grants a
|
|
64
|
+
rewarded placement's reward itself, against its own record of what the player did on the Arcade's
|
|
65
|
+
offer sheet — never your game, by reading an outcome. An outcome travels over the same channel as
|
|
66
|
+
everything else your frame receives, and a game that unlocks a level or grants a currency on the
|
|
67
|
+
strength of one has an unlock that any page embedding your game can forge.
|
|
57
68
|
|
|
58
69
|
Treat a break as a break. If your game wants to give the player something for reaching the end of a
|
|
59
70
|
level, give it to them for reaching the end of the level.
|
|
60
71
|
|
|
61
|
-
**Where a rewarded grant
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
settles, whatever it settled as:
|
|
72
|
+
**Where a rewarded grant arrives.** On your game's own inventory for this player, written by the
|
|
73
|
+
platform — never by your game — and read through `gem.me.inventory`, the same place a purchase
|
|
74
|
+
lands, read the same way. (That is the game-scoped inventory `gem.me` shows you, not the player's
|
|
75
|
+
platform wallet.) There is **no fourth outcome and no extra field** on the answer. `outcome` is
|
|
76
|
+
pinned at three values because a new value would break every game bundle already in the field: its
|
|
77
|
+
validator refuses the whole message. A new field would not break anything — validators ignore
|
|
78
|
+
fields they do not know — but it is not coming either: anything on that answer can be forged by the
|
|
79
|
+
page embedding your game, so a reward carried there would be a reward anyone could hand out. So
|
|
80
|
+
re-read your snapshot once a rewarded ask settles, whatever it settled as:
|
|
71
81
|
|
|
72
82
|
```js
|
|
73
83
|
await gem.ads.requestAd({ placement: 'run-banked', format: 'rewarded' });
|
|
74
84
|
gem.me.inventory.refresh(); // drop what is cached
|
|
75
|
-
const snap = await gem.me.inventory.snapshot(); //
|
|
85
|
+
const snap = await gem.me.inventory.snapshot(); // a reward granted before the answer is here
|
|
76
86
|
```
|
|
77
87
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
A `no_ad` can come back with the reward already in that snapshot, because the platform grants
|
|
89
|
+
without an ad in some cases, so branch on the inventory and never on the outcome. And a reward that
|
|
90
|
+
an ad provider confirms from its own server can land after this re-read, so read again when the
|
|
91
|
+
screen that spends it opens; [`inventory-and-catalogs.md`
|
|
92
|
+
§6](inventory-and-catalogs.md#6-when-to-re-read) is the general form of this.
|
|
93
|
+
[`rewarded-ads.md`](rewarded-ads.md) is the rest: declaring what a placement grants, spending it,
|
|
94
|
+
drawing the button, and testing.
|
|
82
95
|
|
|
83
96
|
**Whether a rewarded button could pay right now.** A rewarded placement is a button the player
|
|
84
97
|
presses, so you need to know whether to draw it. `gem.ads.rewardedStatus(placement)` reads one
|
|
@@ -124,20 +137,23 @@ If you have not wired the pause at all yet, that is the thing to fix first;
|
|
|
124
137
|
|
|
125
138
|
At a break **the player already expects**: a level ending, a round finishing, a run being banked, a
|
|
126
139
|
return to a menu. Never mid-input, never during a timed sequence, and never on a screen the player
|
|
127
|
-
reached by accident.
|
|
140
|
+
reached by accident. (A rewarded ask goes where the player pressed your button for it, and nowhere
|
|
141
|
+
else; [`rewarded-ads.md`](rewarded-ads.md) covers the button.)
|
|
128
142
|
|
|
129
|
-
`placement` is your own name for that break — `level-complete`, `run-banked`, `back-to-menu`.
|
|
130
|
-
platform matches it against nothing
|
|
131
|
-
decision on it
|
|
132
|
-
yourself.
|
|
143
|
+
`placement` is your own name for that break — `level-complete`, `run-banked`, `back-to-menu`. For
|
|
144
|
+
an interstitial, the platform matches it against nothing, never shows it to a player, and does not
|
|
145
|
+
vary its decision on it; it exists so your own analytics can tell two break points apart, so name
|
|
146
|
+
it for yourself. For `format: 'rewarded'`, the placement names the reward your game config declares
|
|
147
|
+
in `rewarded_placements`, and the platform matches it against that config. Either way it **never
|
|
148
|
+
reaches an ad network**, and neither does any other string your game writes.
|
|
133
149
|
|
|
134
150
|
**It has a grammar, and it is narrow on purpose: 1 to 32 characters of `a-z`, `0-9`, `-` and `_`.**
|
|
135
151
|
Lowercase, no spaces, no accents — `'Boss Fight'` is not a placement, `'boss-fight'` is. Anything
|
|
136
152
|
else resolves `no_ad` and is reported to your `onError` handler, so you meet it while you are
|
|
137
|
-
building rather than later. The
|
|
138
|
-
|
|
139
|
-
a name that
|
|
140
|
-
|
|
153
|
+
building rather than later. The platform holds the same grammar where it reads the name: for a
|
|
154
|
+
rewarded ask the Arcade forwards the placement unchanged, and a config that declares one outside
|
|
155
|
+
the grammar is refused at upload, so a name that did not fit would be a reward your shipped game
|
|
156
|
+
could never reach. The bound is the platform's, not the SDK's, and the two agree.
|
|
141
157
|
|
|
142
158
|
```js
|
|
143
159
|
async function onLevelComplete() {
|
|
@@ -160,7 +176,11 @@ schedule that does not depend on which branch produced it, so there is nothing i
|
|
|
160
176
|
read either. (`shown` is not held back — how long that one takes is how long your player spent
|
|
161
177
|
looking at a panel.)
|
|
162
178
|
|
|
163
|
-
|
|
179
|
+
A rewarded ask is the one case where something else reaches your game: the reward itself, in your
|
|
180
|
+
own inventory, when one was granted. That is what the ask is for. It says that a reward landed,
|
|
181
|
+
never why, so do not read a reason into it.
|
|
182
|
+
|
|
183
|
+
The other thing you can read is your own economy, and never from the answer:
|
|
164
184
|
`gem.ads.rewardedStatus` reports **your own declared** rewarded caps as the platform holds them —
|
|
165
185
|
the platform can hold a placement's daily cap below what you declared, never above — so you can
|
|
166
186
|
tell when a rewarded `no_ad` was your own cap. It never reports anything about the player, the
|
package/docs/getting-started.md
CHANGED
package/docs/manifest.json
CHANGED
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"file": "changelog.md",
|
|
36
|
-
"sha256": "
|
|
37
|
-
"updated": "2026-10-
|
|
36
|
+
"sha256": "dd8afde50d0e34052ebdb8b894d30faa4814d4209fc62644fdc5ab53a1395e32",
|
|
37
|
+
"updated": "2026-10-08T00:55:05Z"
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
"file": "cross-promotion.md",
|
|
41
|
-
"sha256": "
|
|
42
|
-
"updated": "2026-10-
|
|
41
|
+
"sha256": "4336d72595115950919361c36ab1af9beaa787c3c57d27fea27d42dac69d81cf",
|
|
42
|
+
"updated": "2026-10-07T19:15:38Z"
|
|
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": "95f645a73bfb522537fe14562d4a87b10a7f06af036c5658996c05bddced875b",
|
|
72
|
+
"updated": "2026-10-08T00:55:05Z"
|
|
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-
|
|
86
|
+
"sha256": "33fdef4edc170cadd2284ff399fcf16455b7691d9aeac52bd09bade24b8b051a",
|
|
87
|
+
"updated": "2026-10-07T19:27:16Z"
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
90
|
"file": "p2p-and-parties.md",
|
|
@@ -101,15 +101,20 @@
|
|
|
101
101
|
"sha256": "df1e39628ccba04b6f7bcd0b90381dfa34a4480330a6af041d9b71bcaefddb5f",
|
|
102
102
|
"updated": "2026-09-17T20:51:57Z"
|
|
103
103
|
},
|
|
104
|
+
{
|
|
105
|
+
"file": "rewarded-ads.md",
|
|
106
|
+
"sha256": "39332bd7b08adbb33d3b6382142d214e17d8f24a7776061a97f372d658e48974",
|
|
107
|
+
"updated": "2026-10-07T19:17:55Z"
|
|
108
|
+
},
|
|
104
109
|
{
|
|
105
110
|
"file": "room-shapes.md",
|
|
106
|
-
"sha256": "
|
|
107
|
-
"updated": "2026-
|
|
111
|
+
"sha256": "78fad1eee7602b10a9b39f100d3daedfed6c313a884f95382233434a3206930b",
|
|
112
|
+
"updated": "2026-10-07T19:27:16Z"
|
|
108
113
|
},
|
|
109
114
|
{
|
|
110
115
|
"file": "rooms-and-matches.md",
|
|
111
|
-
"sha256": "
|
|
112
|
-
"updated": "2026-
|
|
116
|
+
"sha256": "0abf3717a42c94ec832dfa1310c2d93a9f58236fd75b0aacaab301976bffe286",
|
|
117
|
+
"updated": "2026-10-07T19:27:16Z"
|
|
113
118
|
},
|
|
114
119
|
{
|
|
115
120
|
"file": "saving-player-progress.md",
|
|
@@ -118,8 +123,8 @@
|
|
|
118
123
|
},
|
|
119
124
|
{
|
|
120
125
|
"file": "server-bundles.md",
|
|
121
|
-
"sha256": "
|
|
122
|
-
"updated": "2026-10-
|
|
126
|
+
"sha256": "584ace965c1be20ca90ee1cd3beff7c1473f1ca9bccef428b3efb6846b2315a7",
|
|
127
|
+
"updated": "2026-10-08T00:55:05Z"
|
|
123
128
|
},
|
|
124
129
|
{
|
|
125
130
|
"file": "serverless-functions-js.md",
|
|
@@ -128,8 +133,8 @@
|
|
|
128
133
|
},
|
|
129
134
|
{
|
|
130
135
|
"file": "serverless-functions.md",
|
|
131
|
-
"sha256": "
|
|
132
|
-
"updated": "2026-10-
|
|
136
|
+
"sha256": "b1562b4442b69bb6895198def7b00ccd7a8c56133862a939c2cce9b6e9d30290",
|
|
137
|
+
"updated": "2026-10-07T19:27:16Z"
|
|
133
138
|
},
|
|
134
139
|
{
|
|
135
140
|
"file": "trading-and-gifting.md",
|
|
@@ -143,8 +148,8 @@
|
|
|
143
148
|
},
|
|
144
149
|
{
|
|
145
150
|
"file": "what-a-game-must-do.md",
|
|
146
|
-
"sha256": "
|
|
147
|
-
"updated": "2026-10-
|
|
151
|
+
"sha256": "7dc6bdc55e3cfbc88d667bbc2ebf908e357a1b6d768d5e3f4ce486ded614c1bf",
|
|
152
|
+
"updated": "2026-10-07T19:15:38Z"
|
|
148
153
|
}
|
|
149
154
|
]
|
|
150
155
|
}
|
package/docs/match-session.md
CHANGED
|
@@ -204,7 +204,7 @@ next round is one call:
|
|
|
204
204
|
|
|
205
205
|
**A template's `auto_start_host` starts the first match, not the second.** It fires when the room
|
|
206
206
|
reaches `min_players`; a reopened room already has them, and nothing fires again. A shape whose
|
|
207
|
-
first match started itself — a hub, a
|
|
207
|
+
first match started itself — a hub, a matchmade lobby — still needs a client to call
|
|
208
208
|
`startMatch()` for the next one, and that client is whoever `gem.rooms.isHost` says. Watch
|
|
209
209
|
`hostChanged`: if the host closed their tab during the match, the room has a new host, and the
|
|
210
210
|
start belongs to them now.
|