TipTap Games

The player and their device

The player

Put the player into the game — their avatar on the podium, their name on the trophy, a face on the rival they are chasing. Write the signed-out version first: most of the feed is signed out, so an absent avatar is the common case rather than an edge case, and a signed-out player must get the same game with less decoration, never a worse game. Never render a broken image, the word null, or a gap where a face should be.

This is the same handle and picture already on every leaderboard and profile page, so nothing private crosses the boundary — and your game runs under connect-src 'none', with no network at all, so the data cannot leave the frame it was drawn in.

TipTap.getPlayer(cb) → { handle, displayName, avatarDataUrl }

The current player's public identity. Also returns a Promise where one exists, so await works. The reply is cached for the life of the document, so calling it again is free.

Every field is independently null

The callback always receives an object, so the player itself never needs a null check — but handle, displayName and avatarDataUrl each do. A signed-out player has no handle and no avatar, and a signed-in one may still have no avatar. avatarDataUrl, when present, is a 96×96 data: URL.

p.locale and p.timezone

The player's language tag (e.g. 'en-GB') and IANA timezone (e.g. 'Europe/London'), each also nullable. Use them for formatting numbers and dates, or for a sky that matches their local hour — not for anything they would notice being wrong. An engine without Intl answers null rather than guessing.

Saving progress

Your game has no localStorageand no network, so this is the only way to keep anything between runs. It is per player per game, it follows a signed-in player to their other devices, and a signed-out player's save is kept against their device and carried into their account if they sign in later.

TipTap.saveState(obj)

Persists this game's save data for this player. Calls are debounced and coalesced, so call it whenever your state changes — every pickup is fine — and one write goes out shortly after with the latest value. It is flushed automatically when the player leaves the panel.

TipTap.loadState(cb) → the saved object, or null

Reads it back. Also returns a Promise where one exists, so await works, and the reply is cached for the life of the document. Always handle null — most players are new — and always handle a save written by an older version of your game, which happens the first time you ship an update.

8KB serialised, JSON only, rejected whole if it is over

Nothing is stored and nothing is truncated: half a save is worse than none. You get a console warning and an onStateError callback with reason 'too_large', 'not_json' or 'rejected'. Save progress, not scenery — a level number, a currency, a list of unlock ids.

TipTap.onStateError(cb)

Fires when a save could not be stored, so your game can degrade honestly — tell the player their progress is not being kept, stop offering to continue — rather than promising a continue that will not be there.

Haptics

A buzz is the one bit of feedback a phone can give that a screen cannot. Used sparingly it makes a hit feel like a hit; used constantly it is why people put the phone down.

TipTap.vibrate(pattern)

A number of milliseconds, or an array alternating buzz and pause. Total buzz time is capped at 1000ms and roughly one call every 300ms gets through. A no-op on desktop, on iOS Safari, wherever the browser declines, and for players who asked for reduced motion — there is no way to detect success and you should not try. A 20ms tick on a hit lands beautifully; a buzz every frame is why people turn their phone face down.

Environment

Facts about the player and the surface your game is drawn on that the platform knows and your sandboxed game cannot see reliably for itself. All of it is on the handshake snapshot and free to read.

TipTap.isReducedMotion() → boolean

The player's system setting. Honour it. Screen shake, parallax, particle bursts, full-screen flashes and long easing are what this is about — for a lot of people they are not a preference but the difference between playing and feeling ill. Keep the feedback and change its form: a flash of colour instead of a shake. Haptics are suppressed for you automatically.

TipTap.getInputClass() → 'touch' | 'pointer'

A hint for layout — bigger targets and on-screen controls for touch, cursor affordances and keyboard hints for pointer. It is not a promise about which events will fire: a tablet with a trackpad and a laptop with a touchscreen both exist, so keep handling both.

TipTap.takeKeyboardFocus() → boolean

Take DOM keyboard focus for your game. You almost certainly do not need it: keydown only fires while something inside your iframe holds focus, and the platform claims it for you on every tap in the game and whenever its own overlays close. Call it after tearing down an overlay of YOUR OWN that took focus. Ignored while your game is off-screen or paused, so a game one swipe away can never take keys from the one being played.