leaderboard
You declare boards per game in the console: name (^[a-z][a-z0-9_]{0,31}$), sort (desc or asc),
min and max, keep (best or latest) and a rate per minute (10 by default). The API rejects a score
outside the bounds. Periods are day, week and all.
leaderboard.submit(board, score, meta?): Promise<SubmitResult>
Section titled “leaderboard.submit(board, score, meta?): Promise<SubmitResult>”Once per finished run, after gameplay.stop(), in a run that called gameplay.start(). score is a safe
integer. meta is at most 512 bytes of JSON with string or number values, shown as the entry’s detail
(tries, time). A board or a meta key named time is in seconds; the hub formats it as m:ss.
async function endRun(metres, tries) { sdk.gameplay.stop(); const result = await sdk.leaderboard.submit('height', metres, { tries }); if (result.status === 'accepted') showRank(result.rank.week, result.best); else if (result.status === 'rejected') console.warn('score rejected:', result.reason);}import type { SubmitResult } from '@filudi/game-sdk';
async function endRun(metres: number, tries: number): Promise<void> { sdk.gameplay.stop(); const result: SubmitResult = await sdk.leaderboard.submit('height', metres, { tries }); if (result.status === 'accepted') showRank(result.rank.week, result.best); else if (result.status === 'rejected') console.warn('score rejected:', result.reason);}leaderboard.top(board, period): Promise<TopResult>
Section titled “leaderboard.top(board, period): Promise<TopResult>”Up to 10 entries, ranked. The hub draws the top 5 and the player’s row itself; the SDK gives the raw list.
total is the number of entries on the board for that period.
leaderboard.me(board, period): Promise<MeResult>
Section titled “leaderboard.me(board, period): Promise<MeResult>”The player’s own entry for that period, or null when they have none.
const top = await sdk.leaderboard.top('height', 'week');const me = await sdk.leaderboard.me('height', 'week');if (top.status === 'unavailable') return hideBoard();drawRows(top.entries.slice(0, 5)); // { rank, nickname, score, meta, isMe }if (me.status === 'ok' && me.entry) drawMyRow(me.entry);import type { LeaderboardEntry, MeResult, TopResult } from '@filudi/game-sdk';
const top: TopResult = await sdk.leaderboard.top('height', 'week');const me: MeResult = await sdk.leaderboard.me('height', 'week');if (top.status === 'unavailable') return hideBoard();const rows: LeaderboardEntry[] = top.entries.slice(0, 5);drawRows(rows);if (me.status === 'ok' && me.entry) drawMyRow(me.entry);// Period: 'day' | 'week' | 'all'// LeaderboardEntry: { rank, nickname, score, meta, isMe }// SubmitResult: { status: 'accepted', rank: { day, week, all }, best }// | { status: 'rejected', reason } 'bounds' | 'rate' | 'signature' | 'board' | 'session' | 'nonce' | 'clock' | 'meta'// | { status: 'unavailable' }// | { status: 'error', message } meta not serializable, network failure// TopResult: { status: 'ok', entries, period, total } | { status: 'unavailable' }// MeResult: { status: 'ok', entry, period } | { status: 'unavailable' }type Period = 'day' | 'week' | 'all';
interface LeaderboardEntry { rank: number; nickname: string; score: number; meta: Record<string, string | number> | null; isMe: boolean;}
type SubmitResult = | { status: 'accepted'; rank: { day: number; week: number; all: number }; best: boolean } | { status: 'rejected'; reason: 'bounds' | 'rate' | 'signature' | 'board' | 'session' | 'nonce' | 'clock' | 'meta' } | { status: 'unavailable' } | { status: 'error'; message: string }; // meta not serializable, network failure
type TopResult = | { status: 'ok'; entries: LeaderboardEntry[]; period: Period; total: number } | { status: 'unavailable' };
type MeResult = | { status: 'ok'; entry: LeaderboardEntry | null; period: Period } | { status: 'unavailable' };Signed scores on filudi.com
Section titled “Signed scores on filudi.com”gameplay.start() opens a play session and receives a session key. The SDK signs
pid|gameId|board|score|nonce|ts with HMAC-SHA256 through crypto.subtle and sends the signature with the
score. Replaying a nonce or submitting outside a session is rejected. The game does nothing for this;
it only brackets play. Without crypto.subtle (an old browser on plain HTTP) submit answers rejected
with reason signature and everything else works.
Rejected reasons
Section titled “Rejected reasons”| Reason | What it means |
|---|---|
bounds | The score is outside the board’s min and max, or not a safe integer. Also the mapping of an unknown reason |
rate | More scores than the board’s rate per minute (a 429) |
session | No play session: gameplay.start() was not called in this run |
signature, nonce, clock | The signature failed, the nonce was replayed, or the clock is off by too much |
board | No board of that name on this game |
meta | meta is over 512 bytes or has a value that is not a string or a number |
Per host
Section titled “Per host”| Host | submit | top and me |
|---|---|---|
| filudi.com | Signed, to the board of the game | Work, by period |
| CrazyGames | Encrypted for user.submitScore when init({ crazygames: { leaderboardKey } }) is given, else unavailable; rank values are 0 | unavailable: the portal draws its own |
| Poki | unavailable | unavailable |
| Playgama | Through bridge.leaderboards unless its type is not_available; rank values are 0 | Work when the type is in_game |
| mock | Accepted against the seed entries of mock.leaderboard; standalone answers unavailable | Same |
features.leaderboard from init() says ahead of time whether a board is worth drawing. Which boards to
declare and how to pick their bounds is in leaderboards.