Leaderboards
A leaderboard holds each player’s best time for one distance and one car class. Only times the race server measured itself get on a board: it replays the run from the player’s inputs, so a modified game cannot send a time of its own. Career races and online races both count. Roll races do not, because their cars start moving.
Where a time comes from
Section titled “Where a time comes from”| Race | Recorded in | Time |
|---|---|---|
| Career event, wager, bracket | judgeSingleRace (party/src/race/judgedRace.ts) |
playerTime from replayEventRace, in seconds |
| Boss fight or tournament round | judgeSeriesRound (party/src/race/seriesRound.ts), at the round’s own distance |
the same |
| Online heat | RaceRoom.recordTimes (party/src/RaceRoom.ts) once the verdict is out |
each runner’s judged finish, in frames |
careerTime() drops roll races and runs without a finish; onlineTimes() (party/src/race/onlineTimes.ts) drops runners without a finish. Online, ownedEntry() returns the racer (account and car model) next to the entry; the room keeps it on the server and never sends it to clients.
Storage
Section titled “Storage”best_time (migrations 0007_leaderboard.sql, 0008_board_season.sql) has one row per player and board, keyed by user, season, distance and car class. recordBestTimes() (party/src/db/bestTime.ts) inserts the time, or replaces the row only when the new time is faster. A board read goes along the index (season, distance, car_class, finish_ms).
- Car class is the model’s
carClass(1 = C, 2 = S, 3 = R, 4 = X,BOARD_CLASSESinplayer/leaderboard.ts). Boss cars and the GT have one too, unlike the career districts’fits. - Season is
BOARD_SEASONinplayer/leaderboard.ts. Bump it by hand when the physics change enough that old times no longer compare; every board then starts empty. It is notSIM_VERSION, which also changes with server-only commits.
Deleting an account deletes its rows (USER_TABLES), and the data export lists them. When a guest links Apple or Google, its best times move to the account; where both have a time on the same board, the faster one stays (party/src/db/linkGuest.ts).
party/src/api/leaderboardApi.ts:
GET /api/leaderboard?distance=quarter&class=1&page=1: public, 50 rows per page (BOARD_PAGE_SIZE). Equal times share a rank within the page. With a bearer token the answer also hasyou, the caller’s own row and rank wherever it ranks.PUT /api/profile/nickname { nickname }: sets the name the boards show. Answers{ nickname }, or{ refused }withlength,charactersorblocked(422) ortaken(409).
Nicknames
Section titled “Nicknames”player/nickname.ts holds the rules both sides run: 3 to 16 letters or digits with single spaces, dots, dashes or underscores between them, and a short blocklist (matched inside the name with digits read as letters, plus a few words matched only whole). The unique index on profile.nickname (case-insensitive) decides when two players want the same name. A player without a nickname shows as defaultNickname(userId), for example “Racer 4F2A”.
Game and site
Section titled “Game and site”- Game: the
leaderboardfeature module.Leaderboard(from the main menu) picks a distance and class with neon toggles, shows the top 10 and the player’s own row (view/boardTable.ts), and the name bar (view/nicknameBar.ts) changes the nickname through a DOM input (ui/textEntry.ts). Requests go throughplayer/account/leaderboardRequests.ts. - Site:
site/src/routes/leaderboard.tsx, the first page that hydrates. The board is in the URL (?distance=&class=&page=);BoardTablefetches it fromSITE.apiUrlin the browser.
Checking it
Section titled “Checking it”npm --prefix party run test:leaderboard (against a running wrangler dev on port 8788) races a guest’s career event and checks the board, a slower run, a run without a finish, the public answer and the nickname rules. party/scripts/twoClients.ts fills the online boards.