Cross-promotion
The Arcade can show a full-screen panel about another game at a break in yours. Your game asks for one; the Arcade decides whether to show anything, draws it in its own chrome over your paused game, and tells you when it is done.
const { outcome } = await gem.ads.requestAd({
placement: 'level-complete',
format: 'interstitial',
});
// Nothing to handle. Carry on.
startNextLevel();That is the whole surface. The rest of this page is the four things a game gets wrong when nobody tells it.
1. The answer is usually no, and no is ordinary#
outcome is one of exactly three values:
| outcome | what happened | what your game does |
|---|---|---|
no_ad | nothing was shown | carry on exactly as if you had not asked |
shown | a panel was painted and has closed | carry on — and grant nothing |
unavailable | the Arcade could not draw one just now | carry on; read it identically to no_ad |
no_ad is the common answer, not an edge case. There may be nothing suitable, the player may be somebody the Arcade will not interrupt, or the break may simply be too soon after the last one. You are not told which, and there is nothing to infer.
So write the no_ad path as your ordinary path:
Do not render a placeholder, a spinner, or a "nothing to show right now" message. Empty means nothing appears.
Do not hold up your own flow waiting for a fill. Ask, and continue when the promise settles.
Do not retry on a no. A second ask at the same break gets the same answer and spends a slot.
2. Nothing rejects#
requestAd always resolves. There is no error branch and no try/catch to write. Even a refusal of your own arguments — a placement outside the grammar below, a format outside the two — resolves no_ad, so your game keeps exactly one branch. (The mistake is still reported to your onError handler, so you find out in development.)
3. ⚠️ shown grants nothing#
shown says a panel was painted and has closed. It is not a receipt, not an entitlement, and not permission to hand anything out.
format: 'rewarded' exists in the vocabulary and is answered no_ad today. Even when that changes, a reward will be granted by the platform against its own record of what was shown — never by your game reading an outcome. An outcome travels over the same channel as everything else your frame receives, and a game that unlocks a level or grants a currency on the strength of one has an unlock that any page embedding your game can forge.
Treat a break as a break. If your game wants to give the player something for reaching the end of a level, give it to them for reaching the end of the level.
Where a rewarded grant will arrive, so you can write the code now. On your player's platform inventory, through gem.me.inventory — the same place a purchase lands, read the same way. There will be no fourth outcome and no extra field on the answer: outcome is pinned at three values for every game bundle already in the field, so a signal added later would break games rather than reach them. If you serve rewarded, re-read your snapshot once the ask settles, whatever it settled as:
await gem.ads.requestAd({ placement: 'run-banked', format: 'rewarded' });
gem.me.inventory.refresh(); // drop what is cached
const snap = await gem.me.inventory.snapshot(); // the grant, if there was one, is hereToday that re-read finds nothing new, because nothing is served. It costs one request at a break your player is already sitting in, and it is the whole of what changes on the day one is — no rebuild. inventory-and-catalogs.md §6 is the general form of this.
4. The Arcade pauses your game for you#
While a panel is up, your game is paused the same way it is paused when a player switches tabs — the Arcade sends the pause your game already handles, and lifts it when the panel closes. You do not need to pause anything yourself, and you should not resume on a timer: resume when the promise settles.
If you have not wired the pause at all yet, that is the thing to fix first; what-a-game-must-do.md covers it.
Where to put the call#
At a break the player already expects: a level ending, a round finishing, a run being banked, a return to a menu. Never mid-input, never during a timed sequence, and never on a screen the player reached by accident.
placement is your own name for that break — level-complete, run-banked, back-to-menu. The platform matches it against nothing today, never shows it to a player, and does not vary its decision on it. It exists so your own analytics can tell two break points apart, so name it for yourself.
It has a grammar, and it is narrow on purpose: 1 to 32 characters of a-z, 0-9, - and _. Lowercase, no spaces, no accents — 'Boss Fight' is not a placement, 'boss-fight' is. Anything else resolves no_ad and is reported to your onError handler, so you meet it while you are building rather than later. The reason it is enforced now, while nothing depends on it, is that this is the field a promotion is eventually keyed by: the Arcade will forward it unchanged, and a name that does not fit the platform's grammar would be a name that stopped working after your game had shipped. The bound is the platform's, not the SDK's, and the two agree.
async function onLevelComplete() {
showResultsScreen();
await gem.ads.requestAd({ placement: 'level-complete', format: 'interstitial' });
// Settled either way. The results screen is still up; the player taps Next.
enableNextButton();
}What never crosses#
Your game never learns what was shown — no title, no image, no id, nothing about the other game. You receive an outcome and nothing else.
You are also never told why the answer was no. That is deliberate and permanent: a reason would tell your game something about the player that game code is not told, and there will not be a fourth outcome value naming one. The Arcade also settles every no_ad and unavailable on a schedule that does not depend on which branch produced it, so there is nothing in the timing to read either. (shown is not held back — how long that one takes is how long your player spent looking at a panel.)
If you do nothing#
Nothing happens. A game that never calls gem.ads.requestAd is never interrupted, and the call is not on the list of things a game must do to ship. This page exists for games that want a break to be worth something.
