What your game can and cannot do
Read this first. It explains the one rule everything else follows from, and why the SDK is shaped the way it is.
Your game never decides who wins
Your game reports what the player did. The platform decides what that was worth.
This is not a policy we ask you to respect. It is enforced by the architecture, and it is enforced against first-party games in exactly the same way as third-party ones.
What your game is given
At init your game receives precisely three things:
{ seed: 1234567, tickRate: 60, config: { /* your own settings */ } }
That is all. Your game is never told:
- the stake, or that money is involved at all
- a player's balance or wallet
- who the other players are, or how they are doing
- the prize, the payout split, or the platform's fee
The manifest validator rejects a bundle whose config so much as mentions stake,
balance, wallet, payout or prize. There is nothing to leak because nothing is sent.
What your game reports
{ type: "complete", finalTick: 3600, displayScore: 4200, inputs: [ ... ] }
displayScore is display only. It is what your game shows the player while they play.
The platform does not use it to pay anybody.
What the platform does with it
The server takes the seed it issued and the inputs you submitted, runs your game
again from scratch, and uses the score it calculates.
your device our server
─────────── ──────────
seed 1234567 ──────────────────────▶ seed 1234567
player plays replays your inputs
inputs recorded ────────────────────▶ derives the score
displayScore: 4200 score: 4200 ← this one is paid
If the two disagree, the run is rejected. Not averaged, not disputed — rejected. A client claiming a score its inputs do not produce simply does not get paid.
Why cheating does not work
Editing the score in memory. The score you send is ignored. Only your inputs matter.
Sending fabricated inputs. They are replayed. Fabricated inputs produce whatever score those inputs actually produce — which is the honest answer to a dishonest question.
Modifying the game bundle. The player's copy is not what the server replays with. The server uses the certified bundle.
Slowing time down or speeding it up. The score comes from replaying the input log
through a fixed tick count, so a device that runs slowly produces the same result, just
later. Note that this is a statement about the score, not about how long a player gets:
a timed round is a number of ticks, and running them faster than the declared rate would
shorten it. That is what createLoop exists to prevent — see below.
Calling home for help. The sandbox blocks all network access.
Reading or writing storage. The sandbox blocks that too.
Reaching the hosting page. The frame is sandboxed without allow-same-origin, so
there is no page to reach.
What this means for how you build
Three rules follow from all of the above, and certification enforces every one:
1. Use the SDK's random generator, never the engine's
Math.random(), Defold's math.random(), GDevelop's random expressions — none can be
reproduced. Five players in a pool must face the identical puzzle, and the server must
be able to rebuild it. Use the seeded generator the SDK gives you.
2. Drive your simulation from ticks, never from elapsed time
Never delta, dt, Date.now() or performance.now() in game logic. One tick is one
step, regardless of how long the frame took. Rendering may use frame time freely — the
distinction is between what the player sees and what the game decides.
But a clock still decides how many ticks to run, and you must let it. These are two different questions and conflating them is the single most expensive mistake on this page:
| May read a clock? | |
|---|---|
| What happens on tick n | Never. This is what the server replays |
| How many ticks have happened by now | Yes. The server does not replay this at all |
The trap is requestAnimationFrame, which fires at the display's refresh rate. One
tick per callback gives 60 ticks a second on a 60Hz screen and 144 on a 144Hz one — the
same game, at two and a half times the speed, with a timed round over in 40% of the time.
Every first-party game here shipped that way, and Snake ran at 24 cells a second on a
gaming monitor against 10 on a phone.
Use createLoop from the SDK. It spends elapsed time on whole ticks, so your tick rate is
the one your manifest declares whatever the display does — and your update still never
sees a clock, so the replay is unchanged.
3. Make your score an integer
Floating-point results can differ in the last bit between devices. Two numbers that are "nearly equal" are not equal, and the comparison is exact.
Hidden information
For real-time games where players hold private state — a hand of cards — the server holds it, and each player is sent only their own view.
Your game implements viewFor(state, playerId), returning what that player may see. A card
game returns the player's own hand and only the count of everyone else's. The engine
never broadcasts raw state, so a game that forgets to redact leaks nothing by default; it
simply shows nothing.
Practice mode
Practice matches run the identical code path. There is no separate "free" build, no relaxed verification. The only difference is that no money moves.
That is deliberate: a game that behaves differently in practice than in a real match is a game whose testing tells you nothing.
Related
- Getting started — the path end to end
- Writing the server rules — the half that decides outcomes
- Determinism — proving your rules are reproducible
- Publishing lifecycle — review, updates, revenue
- Building a client — for a front end, not a game
- Server API reference — every RPC, and who may call it
- Engine guides: Excalibur.js · Defold · GDevelop
