Friend challenges
Host ≥ 1.2.0. Players can challenge each other in your game — high score, fastest time, or win streak — wagering Gamma Coins in escrow. The winner takes the pot; ties and expiries refund. When your game is launched in challenge mode the Gamma Games app injects the context; your job is to read it, report progress, and submit one final result.
gamma.challenges lives under the main gamma object and is promise-first like
everything else:
import {gamma} from '@swiftware/gamma-sdk';
// drop-in: global `gamma`
Branch once at startup
Call getActive() after ready(). It returns null for a normal play session —
and on hosts older than 1.2.0 — so the same build works everywhere.
await gamma.ready();
const challenge = await gamma.challenges.getActive();
if (challenge) {
// {
// challengeId, type: 'high_score' | 'fastest_time' | 'level_streak',
// mode: 'live' | 'async', param, wagerCoins, deadline,
// myRole: 'challenger' | 'opponent',
// opponent: { displayName, avatar } | null
// }
startCompetitiveRun(challenge);
} else {
startNormalGame();
}
Report progress while playing
submitScore(value) reports the current progressive value — score, elapsed
milliseconds, or streak, matching the challenge type. It powers the live opponent
overlay and, for higher-wins types, the disconnect fallback. Call it as often as
you like; the host throttles.
function onScoreChanged(currentScore) {
gamma.challenges.submitScore(currentScore); // fire-and-forget
}
Submit the final result
Call complete(result) exactly once, at game over. Set the key that matches
challenge.type:
| Challenge type | Result key | Example |
|---|---|---|
high_score | { score } | { score: 1240 } |
fastest_time | { timeMs } | { timeMs: 53120 } |
level_streak | { streak } | { streak: 9 } |
const res = await gamma.challenges.complete({score: finalScore});
// res.outcome: 'won' | 'lost' | 'tie' | 'pending_opponent'
// res.settled: true once both players have finished
// res.potCoins: coins won (winner only)
if (res.ok && res.settled) {
showOutcome(res.outcome, res.potCoins);
}
Rules that matter
getActive()returnsnullfor a normal session and on hosts < 1.2.0 — always handle the non-challenge path so one build runs everywhere.complete()is accepted once per attempt; later calls returnalready_completed. Results are validated and settled server-side — never grant a payout client-side.- In live mode, opponents race in real time: don't pause the run on your own ads or interstitials.
wagerCoinsis held in escrow by the host. Your game doesn't move coins for the wager — just play and report. (UsespendCoins/earnCoinsonly for your normal in-game economy.)
Test outside the app
Seed the dev mock to exercise the flow in a plain browser:
gamma.configure({mock: {challenge: {type: 'high_score', wagerCoins: 100}}});
await gamma.ready();
const challenge = await gamma.challenges.getActive(); // mocked context
The mock pretends the opponent already finished and you won, so complete()
resolves with {settled: true, outcome: 'won'}. Don't ship mock enabled.
See also the full API reference, error codes, and App-only & offline.