@indiegems/gem-web-sdk 13.1.0 → 13.2.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 +16 -0
- package/README.md +4 -3
- package/dist/function.d.ts +1 -0
- package/dist/game.d.ts +23 -1
- package/dist/gem-web-sdk.bridge.js +1 -1
- package/dist/gem-web-sdk.esm.js +2 -2
- package/dist/gem-web-sdk.function.js +2 -2
- package/dist/gem-web-sdk.game.js +2 -2
- package/dist/gem-web-sdk.min.js +2 -2
- package/dist/gem-web-sdk.sdk.js +2 -2
- package/dist/gem-web-sdk.server-api.js +1 -1
- package/dist/gem-web-sdk.server.js +2 -2
- package/dist/index.d.ts +27 -4
- package/dist/sdk.d.ts +23 -1
- package/dist/server-api.d.ts +1 -0
- package/dist/server.d.ts +1 -0
- package/docs/analytics.md +6 -3
- package/docs/api/api-reference-function.md +26 -3
- package/docs/api/api-reference-game.md +192 -23
- package/docs/api/api-reference-server.md +23 -2
- package/docs/api/reference.json +218 -16
- package/docs/changelog.md +16 -0
- package/docs/deprecation-policy.md +6 -0
- package/docs/getting-started.md +7 -4
- package/docs/manifest.json +23 -23
- package/docs/platform-moments.md +266 -0
- package/docs/rewarded-ads.md +26 -9
- package/docs/server-bundles.md +2 -2
- package/docs/serverless-functions-js.md +17 -1
- package/docs/serverless-functions.md +13 -7
- package/docs/what-a-game-must-do.md +29 -13
- package/package.json +3 -1
- package/schema/bridge.v1.json +107 -1
- package/schema/capabilities.public.json +10 -0
- package/schema/moment-log-line.json +109 -0
- package/docs/cross-promotion.md +0 -232
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": [
|
|
3
|
+
"The one structured log line a development host writes for each platform",
|
|
4
|
+
"moment: `gem.signalMoment` from the game, and the deprecated",
|
|
5
|
+
"`gem.requestAd` with `format: 'interstitial'` as `kind=legacy`. Every dev",
|
|
6
|
+
"host writes it exactly as below, and asserts the `cases` here, so that a",
|
|
7
|
+
"release checklist reading the log of one host reads every host's.",
|
|
8
|
+
"",
|
|
9
|
+
"WRITTEN ONCE PER MOMENT, WHEN THE HOST ANSWERS IT. Fields in this order,",
|
|
10
|
+
"separated by one space, `name=value`, nothing before `moment` and nothing",
|
|
11
|
+
"after `answer`. A value that is absent is written `-`.",
|
|
12
|
+
"",
|
|
13
|
+
" kind the moment's kind, or `legacy` for an interstitial ask.",
|
|
14
|
+
" placement the game's own name for the screen, when it gave one",
|
|
15
|
+
" that fits `^[a-z0-9_-]{1,32}$`; `-` when it gave none,",
|
|
16
|
+
" and `?` when it gave one outside that grammar. The value",
|
|
17
|
+
" is untrusted text: a host never prints one that fails",
|
|
18
|
+
" the grammar, and never forwards one anywhere.",
|
|
19
|
+
" since_ready_ms whole milliseconds from the frame's `gem.ready` to this",
|
|
20
|
+
" moment's arrival; `-` when `gem.ready` has not arrived.",
|
|
21
|
+
" since_prev_ms whole milliseconds from the previous moment's arrival in",
|
|
22
|
+
" this session to this one's; `-` for the first.",
|
|
23
|
+
" answer `shown` when the host painted something, `not_shown`",
|
|
24
|
+
" otherwise.",
|
|
25
|
+
"",
|
|
26
|
+
"A CONTRACT, NOT A WIRE MESSAGE. It never crosses the bridge and no game",
|
|
27
|
+
"reads it. Adding a field is a new version of this file; a host that",
|
|
28
|
+
"writes this version writes exactly these five."
|
|
29
|
+
],
|
|
30
|
+
"version": 1,
|
|
31
|
+
"prefix": "moment",
|
|
32
|
+
"fields": [
|
|
33
|
+
{
|
|
34
|
+
"name": "kind",
|
|
35
|
+
"values": ["level_end", "run_end", "match_end", "menu_return", "other_break", "legacy"]
|
|
36
|
+
},
|
|
37
|
+
{ "name": "placement", "pattern": "^[a-z0-9_-]{1,32}$", "absent": "-", "outsideGrammar": "?" },
|
|
38
|
+
{ "name": "since_ready_ms", "pattern": "^(0|[1-9][0-9]*)$", "absent": "-" },
|
|
39
|
+
{ "name": "since_prev_ms", "pattern": "^(0|[1-9][0-9]*)$", "absent": "-" },
|
|
40
|
+
{ "name": "answer", "values": ["shown", "not_shown"] }
|
|
41
|
+
],
|
|
42
|
+
"pattern": "^moment kind=(level_end|run_end|match_end|menu_return|other_break|legacy) placement=([a-z0-9_-]{1,32}|-|\\?) since_ready_ms=(0|[1-9][0-9]*|-) since_prev_ms=(0|[1-9][0-9]*|-) answer=(shown|not_shown)$",
|
|
43
|
+
"cases": [
|
|
44
|
+
{
|
|
45
|
+
"$comment": "The ordinary moment: a level ended, a placement given, nothing shown.",
|
|
46
|
+
"moment": {
|
|
47
|
+
"kind": "level_end",
|
|
48
|
+
"placement": "world-1-3",
|
|
49
|
+
"sinceReadyMs": 84210,
|
|
50
|
+
"sincePrevMs": 61005,
|
|
51
|
+
"shown": false
|
|
52
|
+
},
|
|
53
|
+
"line": "moment kind=level_end placement=world-1-3 since_ready_ms=84210 since_prev_ms=61005 answer=not_shown"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"$comment": "The first moment of a session, with no placement, and a panel shown.",
|
|
57
|
+
"moment": { "kind": "run_end", "sinceReadyMs": 152000, "shown": true },
|
|
58
|
+
"line": "moment kind=run_end placement=- since_ready_ms=152000 since_prev_ms=- answer=shown"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"$comment": "A return to the menu.",
|
|
62
|
+
"moment": {
|
|
63
|
+
"kind": "menu_return",
|
|
64
|
+
"placement": "pause_quit",
|
|
65
|
+
"sinceReadyMs": 301442,
|
|
66
|
+
"sincePrevMs": 90010,
|
|
67
|
+
"shown": false
|
|
68
|
+
},
|
|
69
|
+
"line": "moment kind=menu_return placement=pause_quit since_ready_ms=301442 since_prev_ms=90010 answer=not_shown"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"$comment": "A placement outside the grammar, from a frame that posted on the port directly: never printed.",
|
|
73
|
+
"moment": {
|
|
74
|
+
"kind": "match_end",
|
|
75
|
+
"placement": "Round 3\nanswer=shown",
|
|
76
|
+
"sinceReadyMs": 600000,
|
|
77
|
+
"sincePrevMs": 240000,
|
|
78
|
+
"shown": false
|
|
79
|
+
},
|
|
80
|
+
"line": "moment kind=match_end placement=? since_ready_ms=600000 since_prev_ms=240000 answer=not_shown"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"$comment": "A moment before `gem.ready`, which the SDK never sends but a frame on the port can.",
|
|
84
|
+
"moment": { "kind": "other_break", "shown": false },
|
|
85
|
+
"line": "moment kind=other_break placement=- since_ready_ms=- since_prev_ms=- answer=not_shown"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"$comment": "The deprecated interstitial ask, logged on the same line so a checklist sees it.",
|
|
89
|
+
"moment": {
|
|
90
|
+
"kind": "legacy",
|
|
91
|
+
"placement": "level-complete",
|
|
92
|
+
"sinceReadyMs": 45000,
|
|
93
|
+
"sincePrevMs": 0,
|
|
94
|
+
"shown": false
|
|
95
|
+
},
|
|
96
|
+
"line": "moment kind=legacy placement=level-complete since_ready_ms=45000 since_prev_ms=0 answer=not_shown"
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
"notLines": [
|
|
100
|
+
"moment kind=boss_end placement=- since_ready_ms=1 since_prev_ms=- answer=not_shown",
|
|
101
|
+
"moment kind=level_end placement=World-1 since_ready_ms=1 since_prev_ms=- answer=not_shown",
|
|
102
|
+
"moment kind=level_end placement=- since_ready_ms=-5 since_prev_ms=- answer=not_shown",
|
|
103
|
+
"moment kind=level_end placement=- since_ready_ms=1.5 since_prev_ms=- answer=not_shown",
|
|
104
|
+
"moment kind=level_end placement=- since_ready_ms=1 since_prev_ms=- answer=true",
|
|
105
|
+
"moment kind=level_end since_ready_ms=1 placement=- since_prev_ms=- answer=not_shown",
|
|
106
|
+
"moment kind=level_end placement=- since_ready_ms=1 since_prev_ms=- answer=not_shown ",
|
|
107
|
+
"[gem] moment kind=level_end placement=- since_ready_ms=1 since_prev_ms=- answer=not_shown"
|
|
108
|
+
]
|
|
109
|
+
}
|
package/docs/cross-promotion.md
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
# Cross-promotion
|
|
2
|
-
|
|
3
|
-
The Arcade can show a **full-screen panel about another game** at a break in yours. Your game asks
|
|
4
|
-
for one; the Arcade decides whether to show anything, draws it in its own chrome over your paused
|
|
5
|
-
game, and tells you when it is done.
|
|
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
|
-
|
|
12
|
-
```js
|
|
13
|
-
const { outcome } = await gem.ads.requestAd({
|
|
14
|
-
placement: 'level-complete',
|
|
15
|
-
format: 'interstitial',
|
|
16
|
-
});
|
|
17
|
-
// Nothing to handle. Carry on.
|
|
18
|
-
startNextLevel();
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
That is the whole surface, apart from one read that tells a rewarded button whether it could pay
|
|
22
|
-
(§3). The rest of this page is the four things a game gets wrong when nobody tells it.
|
|
23
|
-
|
|
24
|
-
## 1. The answer is usually no, and no is ordinary
|
|
25
|
-
|
|
26
|
-
`outcome` is one of exactly three values:
|
|
27
|
-
|
|
28
|
-
| outcome | what happened | what your game does |
|
|
29
|
-
| ------------- | -------------------------------------- | ---------------------------------------- |
|
|
30
|
-
| `no_ad` | nothing was shown | carry on exactly as if you had not asked |
|
|
31
|
-
| `shown` | a panel was painted and has closed | carry on — and grant nothing |
|
|
32
|
-
| `unavailable` | the Arcade could not draw one just now | carry on; read it identically to `no_ad` |
|
|
33
|
-
|
|
34
|
-
**`no_ad` is the common answer**, not an edge case. There may be nothing suitable, the player may
|
|
35
|
-
be somebody the Arcade will not interrupt, or the break may simply be too soon after the last one.
|
|
36
|
-
You are not told which, and there is nothing to infer.
|
|
37
|
-
|
|
38
|
-
So write the `no_ad` path as your ordinary path:
|
|
39
|
-
|
|
40
|
-
- Do **not** render a placeholder, a spinner, or a "nothing to show right now" message. Empty means
|
|
41
|
-
nothing appears.
|
|
42
|
-
- Do **not** hold up your own flow waiting for a fill. Ask, and continue when the promise settles.
|
|
43
|
-
- Do **not** retry on a no. A second ask at the same break gets the same answer and spends a slot.
|
|
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. (When no ad was available and nothing was granted, the Arcade has
|
|
49
|
-
already shown "No ad available right now" itself.) Draw the button only when
|
|
50
|
-
`gem.ads.rewardedStatus` says it can pay (§3); [`rewarded-ads.md`
|
|
51
|
-
§5](rewarded-ads.md#5-draw-the-button-only-when-it-can-pay) is that button.
|
|
52
|
-
|
|
53
|
-
## 2. Nothing rejects
|
|
54
|
-
|
|
55
|
-
`requestAd` always resolves. There is no error branch and no `try`/`catch` to write. Even a refusal
|
|
56
|
-
of your own arguments — a `placement` outside the grammar below, a `format` outside the two —
|
|
57
|
-
resolves `no_ad`, and so does a rewarded ask made without the player's press (see
|
|
58
|
-
[Where to put the call](#where-to-put-the-call)), so your game keeps exactly one branch. (The
|
|
59
|
-
mistake is still reported to your `onError` handler, so you find out in development.)
|
|
60
|
-
|
|
61
|
-
## 3. ⚠️ `shown` grants nothing
|
|
62
|
-
|
|
63
|
-
`shown` says a panel was painted and has closed. It is **not** a receipt, **not** an entitlement,
|
|
64
|
-
and **not** permission to hand anything out.
|
|
65
|
-
|
|
66
|
-
That holds for `format: 'rewarded'` too, the one format that pays the player. The platform grants a
|
|
67
|
-
rewarded placement's reward itself, against its own record of the ad the Arcade played and how it
|
|
68
|
-
ended — never your game, by reading an outcome. An outcome travels over the same channel as
|
|
69
|
-
everything else your frame receives, and a game that unlocks a level or grants a currency on the
|
|
70
|
-
strength of one has an unlock that any page embedding your game can forge.
|
|
71
|
-
|
|
72
|
-
Treat a break as a break. If your game wants to give the player something for reaching the end of a
|
|
73
|
-
level, give it to them for reaching the end of the level.
|
|
74
|
-
|
|
75
|
-
**Where a rewarded grant arrives.** On your game's own inventory for this player, written by the
|
|
76
|
-
platform — never by your game — and read through `gem.me.inventory`, the same place a purchase
|
|
77
|
-
lands, read the same way. (That is the game-scoped inventory `gem.me` shows you, not the player's
|
|
78
|
-
platform wallet.) There is **no fourth outcome and no extra field** on the answer. `outcome` is
|
|
79
|
-
pinned at three values because a new value would break every game bundle already in the field: its
|
|
80
|
-
validator refuses the whole message. A new field would not break anything — validators ignore
|
|
81
|
-
fields they do not know — but it is not coming either: anything on that answer can be forged by the
|
|
82
|
-
page embedding your game, so a reward carried there would be a reward anyone could hand out. So
|
|
83
|
-
re-read your snapshot once a rewarded ask settles, whatever it settled as:
|
|
84
|
-
|
|
85
|
-
```js
|
|
86
|
-
await gem.ads.requestAd({ placement: 'run-banked', format: 'rewarded' });
|
|
87
|
-
gem.me.inventory.refresh(); // drop what is cached
|
|
88
|
-
const snap = await gem.me.inventory.snapshot(); // a reward granted before the answer is here
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
A `no_ad` can come back with the reward already in that snapshot, because the platform grants
|
|
92
|
-
without an ad in some cases, so branch on the inventory and never on the outcome. And a reward that
|
|
93
|
-
an ad provider confirms from its own server can land after this re-read, so read again when the
|
|
94
|
-
screen that spends it opens; [`inventory-and-catalogs.md`
|
|
95
|
-
§6](inventory-and-catalogs.md#6-when-to-re-read) is the general form of this.
|
|
96
|
-
[`rewarded-ads.md`](rewarded-ads.md) is the rest: declaring what a placement grants, spending it,
|
|
97
|
-
drawing the button, and testing.
|
|
98
|
-
|
|
99
|
-
**Whether a rewarded button could pay right now.** A rewarded placement is a button the player
|
|
100
|
-
presses, so you need to know whether to draw it. `gem.ads.rewardedStatus(placement)` reads one
|
|
101
|
-
placement's state from **your own** `rewarded_placements` config — its daily cap, cooldown and
|
|
102
|
-
`max_held` — applied to this player's use of it:
|
|
103
|
-
|
|
104
|
-
```js
|
|
105
|
-
const status = await gem.ads.rewardedStatus('run-banked');
|
|
106
|
-
bankButton.disabled = !status.grantable;
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
| field | what it says |
|
|
110
|
-
| ------------------ | -------------------------------------------------------------------------- |
|
|
111
|
-
| `declared` | the config promoted to the channel being played names this placement |
|
|
112
|
-
| `grantable` | pressing the button could pay right now — **not** that an ad will play |
|
|
113
|
-
| `remainingToday` | grants left today under the daily cap; absent when the placement has none |
|
|
114
|
-
| `nextEligibleAt` | when a running cooldown ends, in epoch milliseconds; absent when none runs |
|
|
115
|
-
| `heldLimitReached` | the player already holds the placement's `max_held` — spend one first |
|
|
116
|
-
| `resetsAt` | when today's count resets, the next 00:00 UTC, in epoch milliseconds |
|
|
117
|
-
|
|
118
|
-
**`grantable` is not a promise.** A player can press a `grantable` button and get `no_ad`, for
|
|
119
|
-
reasons your game is not told. Read that `no_ad` like any other (§1) — never guess a reason — and
|
|
120
|
-
re-read your inventory whatever the ask settled as. The status grants nothing and reserves
|
|
121
|
-
nothing.
|
|
122
|
-
|
|
123
|
-
It never rejects, like the rest of `gem.ads`. Without a play token, when the read fails or takes
|
|
124
|
-
longer than ten seconds, or for a placement outside the grammar below, it answers not declared and
|
|
125
|
-
not grantable with no `resetsAt`, and the reason goes to your `onError` handler. It is a read of
|
|
126
|
-
the platform, so call it when you draw the button — on a results or death screen — and not every
|
|
127
|
-
frame, and draw the rest of the screen first rather than holding it on the answer.
|
|
128
|
-
|
|
129
|
-
## 4. The Arcade pauses your game for you
|
|
130
|
-
|
|
131
|
-
While a panel is up, your game is paused the same way it is paused when a player switches tabs —
|
|
132
|
-
the Arcade sends the pause your game already handles, and lifts it when the panel closes. You do
|
|
133
|
-
not need to pause anything yourself, and you should not resume on a timer: resume when the promise
|
|
134
|
-
settles.
|
|
135
|
-
|
|
136
|
-
If you have not wired the pause at all yet, that is the thing to fix first;
|
|
137
|
-
[`what-a-game-must-do.md`](what-a-game-must-do.md) covers it.
|
|
138
|
-
|
|
139
|
-
## Where to put the call
|
|
140
|
-
|
|
141
|
-
At a break **the player already expects**: a level ending, a round finishing, a run being banked, a
|
|
142
|
-
return to a menu. Never mid-input, never during a timed sequence, and never on a screen the player
|
|
143
|
-
reached by accident.
|
|
144
|
-
|
|
145
|
-
**A rewarded ask comes from the player's press, and from nowhere else.** Your button is the
|
|
146
|
-
player's opt-in. The Arcade starts the ad as soon as the ask arrives, with no prompt of its own
|
|
147
|
-
first, so an ask the player did not make is an ad they did not choose. Call `requestAd` from the
|
|
148
|
-
button's own `click`, `pointerup`, `touchend` or `keydown` handler, or straight from the
|
|
149
|
-
controller press that chose it. Ask first, before any `await` or timer that could outlast the
|
|
150
|
-
press: a browser counts a tap, click or key press for only a few seconds, and the SDK counts a
|
|
151
|
-
controller press for one.
|
|
152
|
-
|
|
153
|
-
⚠️ **Not from `pointerdown` or `touchstart`.** On a touchscreen they run before the browser counts
|
|
154
|
-
the tap, so the ask is refused on a phone. With a mouse, `pointerdown` counts, so a desk test passes
|
|
155
|
-
and hides it.
|
|
156
|
-
|
|
157
|
-
Where the browser reports user activation, your frame holds none, and no controller was used in
|
|
158
|
-
the last second, the SDK answers `no_ad` without asking. It writes a `[gem] rewarded_requires_user_gesture` line to the console once, and
|
|
159
|
-
calls your `onError` handler with code `'internal'` and a message that begins
|
|
160
|
-
`rewarded_requires_user_gesture`, which is the one to match in your own error reporting:
|
|
161
|
-
|
|
162
|
-
```js
|
|
163
|
-
const gem = createGem({
|
|
164
|
-
onError: (code, message) => {
|
|
165
|
-
if (message?.startsWith('rewarded_requires_user_gesture')) {
|
|
166
|
-
reportBug('rewarded ask outside a press', message); // your own telemetry
|
|
167
|
-
}
|
|
168
|
-
},
|
|
169
|
-
});
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
Where the browser cannot tell, the ask goes ahead. The check is there to catch a mistake while you
|
|
173
|
-
build; it is not a control. It catches an ask with no recent input, and cannot tell which input
|
|
174
|
-
caused the ask, so an ask from another button's handler goes through. An interstitial needs no
|
|
175
|
-
press.
|
|
176
|
-
|
|
177
|
-
**The button says it is an ad, and names the reward:** "Watch an ad for 3 dice rolls", "Watch an ad
|
|
178
|
-
to revive". Never use a label that hides the ad, or one that reads as the only way forward, such as
|
|
179
|
-
"Tap to continue". [`rewarded-ads.md` §5](rewarded-ads.md#5-draw-the-button-only-when-it-can-pay)
|
|
180
|
-
covers the button.
|
|
181
|
-
|
|
182
|
-
`placement` is your own name for that break — `level-complete`, `run-banked`, `back-to-menu`. For
|
|
183
|
-
an interstitial, the platform matches it against nothing, never shows it to a player, and does not
|
|
184
|
-
vary its decision on it; it exists so your own analytics can tell two break points apart, so name
|
|
185
|
-
it for yourself. For `format: 'rewarded'`, the placement names the reward your game config declares
|
|
186
|
-
in `rewarded_placements`, and the platform matches it against that config. Either way it **never
|
|
187
|
-
reaches an ad network**, and neither does any other string your game writes.
|
|
188
|
-
|
|
189
|
-
**It has a grammar, and it is narrow on purpose: 1 to 32 characters of `a-z`, `0-9`, `-` and `_`.**
|
|
190
|
-
Lowercase, no spaces, no accents — `'Boss Fight'` is not a placement, `'boss-fight'` is. Anything
|
|
191
|
-
else resolves `no_ad` and is reported to your `onError` handler, so you meet it while you are
|
|
192
|
-
building rather than later. The platform holds the same grammar where it reads the name: for a
|
|
193
|
-
rewarded ask the Arcade forwards the placement unchanged, and a config that declares one outside
|
|
194
|
-
the grammar is refused at upload, so a name that did not fit would be a reward your shipped game
|
|
195
|
-
could never reach. The bound is the platform's, not the SDK's, and the two agree.
|
|
196
|
-
|
|
197
|
-
```js
|
|
198
|
-
async function onLevelComplete() {
|
|
199
|
-
showResultsScreen();
|
|
200
|
-
await gem.ads.requestAd({ placement: 'level-complete', format: 'interstitial' });
|
|
201
|
-
// Settled either way. The results screen is still up; the player taps Next.
|
|
202
|
-
enableNextButton();
|
|
203
|
-
}
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
## What never crosses
|
|
207
|
-
|
|
208
|
-
Your game never learns what was shown — no title, no image, no id, nothing about the other game.
|
|
209
|
-
You receive an outcome and nothing else.
|
|
210
|
-
|
|
211
|
-
You are also never told _why_ the answer was no. That is deliberate and permanent: a reason would
|
|
212
|
-
tell your game something about the player that game code is not told, and there will not be a
|
|
213
|
-
fourth outcome value naming one. The Arcade also settles every `no_ad` and `unavailable` on a
|
|
214
|
-
schedule that does not depend on which branch produced it, so there is nothing in the timing to
|
|
215
|
-
read either. (`shown` is not held back — how long that one takes is how long your player spent
|
|
216
|
-
looking at a panel.)
|
|
217
|
-
|
|
218
|
-
A rewarded ask is the one case where something else reaches your game: the reward itself, in your
|
|
219
|
-
own inventory, when one was granted. That is what the ask is for. It says that a reward landed,
|
|
220
|
-
never why, so do not read a reason into it.
|
|
221
|
-
|
|
222
|
-
The other thing you can read is your own economy, and never from the answer:
|
|
223
|
-
`gem.ads.rewardedStatus` reports **your own declared** rewarded caps as the platform holds them —
|
|
224
|
-
the platform can hold a placement's daily cap below what you declared, never above — so you can
|
|
225
|
-
tell when a rewarded `no_ad` was your own cap. It never reports anything about the player, the
|
|
226
|
-
platform's spacing between offers, or any limit that spans games.
|
|
227
|
-
|
|
228
|
-
## If you do nothing
|
|
229
|
-
|
|
230
|
-
Nothing happens. A game that never calls `gem.ads.requestAd` is never interrupted, and the call is
|
|
231
|
-
not on the list of things a game must do to ship. This page exists for games that want a break to
|
|
232
|
-
be worth something.
|