# 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.

```js
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:

```js
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 here
```

Today 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](inventory-and-catalogs.md#6-when-to-re-read) 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`](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.

```js
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.
