TipTap Games

Scores, challenges and daily seeds

Scores & leaderboard

Banking a score and showing the board are two separate calls, which is what lets a game with progression save a run without being torn down. The overlay has three controls: Continue closes it and hands control back to your still-running game — nothing is reloaded and no state is lost. Play again reloads your game from scratch. Next game moves on through the feed. If your game has progression, point players at Continue and only end a run when you mean it.

TipTap.submitScore(score: number, opts?)

Write the player's score to the leaderboard. This is SILENT — it shows no UI and does not interrupt your game. Triggers onResult with rank and percentile. Pass { showLeaderboard: true } to raise the leaderboard in the same call. Returns 400 if the score exceeds the game's scoreMax.

TipTap.updateScore(score: number)

Call during gameplay to show a live score in the platform top bar. No database write — fire and forget.

TipTap.onResult(callback)

Register a callback to receive the result after submitScore is processed. The callback receives the payload described below.

TipTap.showLeaderboard()

Raise the results and leaderboard overlay. Your game is paused underneath and is not unloaded. Wire this to your own 'Leaderboard' button, or call it right after submitScore for a classic arcade end screen.

TipTap.hideLeaderboard()

Dismiss the overlay and resume. Equivalent to the player pressing Continue.

TipTap.onLeaderboardClose(callback)

Fires when the player dismisses the overlay with Continue, X or Escape. Your game state is untouched — unpause and carry on. Also fires as the window event 'tiptap:leaderboardclose'.

The onResult payload

Your onResult callback receives a single object with these fields:

FieldTypeDescription
ranknumberThe player's rank on the leaderboard for this round.
percentilenumberPercentage of players whose score this round beats.
ghostScorenumber | nullThe top score from the player the platform selected as a ghost opponent. null if no ghost was set.
ghostHandlestring | nullThe handle of the ghost player. null if no ghost was set.
personalBestnumberThe player's all-time best score for this game.

Score cap

Each game has a server-enforced maximum score called scoreMax. Submitting a score higher than it returns a 400 error. Set a realistic scoreMax when uploading your game — it keeps the leaderboard fair and prevents exploits.

Challenges

When someone shares a score, the link carries a signed challenge. The player who opens it arrives with a number to beat, and your game can build the run around that instead of starting cold.

TipTap.getChallenge() → { handle, score } | null

The score this player was sent here to beat, non-null only when they followed a shared score link. handle is the sharer's public handle and may be null; score is always a number. The platform signs these, so a score that reaches you is one somebody really got.

TipTap.onChallenge(cb) — because it arrives with the handshake

It is not there on the first line your script runs. Read it when you build the run, or subscribe with onChallenge, which fires immediately if it is already known.

The platform draws the target too

A game that ignores this still shows the player what to beat, so implement it when you can do something better than a chip: a ghost line on the track, a marker on the score bar, a 'you need 400 more' at the end.

Daily seeds

This is what makes a daily puzzle work: everybody gets the same board on the same day, so they are all arguing about one thing and a shared score actually compares. It is on for every game with no setup — the platform derives a seed per game per day and delivers it with the handshake, so it costs no round trip and you can read it on your first frame.

TipTap.getDailySeed() → { seed, date } | null

One string per game per UTC day, identical for every player that day and different tomorrow. date is that day as YYYY-MM-DD. Every game gets one automatically — there is nothing to switch on and nothing to configure. Like getChallenge() it lands with the handshake, so TipTap.onDailySeed(cb) covers the case where you need it before your first frame.

Seed your own PRNG from it

Do not use Math.random() for anything the daily needs to reproduce, or two players comparing scores are comparing different games. A small deterministic generator seeded from the string is all it takes.

Authoring a seed overrides one date — it does not replace the schedule

The schedule on your game's Details tab starts empty and the automatic seed runs forever without it. Adding an entry swaps in your own string for that one date and leaves every other day on the automatic seed, so you can hand-build Friday's puzzle and ignore the rest of the week.

Today and the past are frozen

Once a date has arrived, people have played it — so an authored seed for today or any earlier date can no longer be edited or removed. Schedule the seed you mean, and only dates still ahead stay changeable.

Treat the string as opaque

Because a date can be hand-authored, the seed may be a hash today and a puzzle definition on Friday. Never parse it and never assume its length.