XP and levels
Every finished race pays XP next to its cash. XP decides the player’s level, and the level decides which decals the decal shop sells; every paint, a custom colour included, is free from level 1. Levels change nothing in the race itself, and districts still open through stars and bosses (see Career). The design and the reasons behind it are in docs/2026-11-07-top-bar-and-levels-design.md.
Where XP comes from
Section titled “Where XP comes from”XP is GarageState.xp, kept by the race server like cash: the game never sends it (player/garageSync.ts leaves it out).
| Race | XP | Settled in |
|---|---|---|
| Career, first win | 60 m, plus 20 m per star | settleRace (player/career/progress.ts) |
| Career, boss beaten the first time | 120 m | the same |
| Career, replay with new stars | 20 m per new star | the same |
| Career, replay without new stars | 20 m | the same |
| Career, loss after crossing the line | 10 m | the same |
| Online heat, win / finished loss | 40 / 15 | payOnlineXp (party/src/race/onlineXp.ts) |
m is the district’s weight: Street 1, Harbor 2, Chinatown 3, Strip 4. The numbers are in careerRaceXp (player/level/xp.ts). Single races, tournaments and boss fights all go through settleRace, which puts the XP on the RaceAward; each caller adds it to the garage next to the cash.
Online, the server writes the XP into each racer’s garage after it judged the heat, next to the leaderboard times. Only the first ONLINE_HEATS_PER_DAY (20) heats of a UTC day pay (settleOnlineHeat, player/level/onlineXp.ts, counter in GarageState.onlineXp), so two accounts racing each other cannot farm levels. The game reloads its garage when it leaves the online screens (net/leaveToMenu.ts).
The level curve
Section titled “The level curve”Going from level n to n + 1 takes 100 + 25 (n - 1) XP (levelOf in player/level/xp.ts). There is no cap. Each level-up fills the fuel tank (addXp, see Fuel). Winning every career race with all stars and beating every boss pays 13,200 XP:
| After | XP | Level |
|---|---|---|
| Street | 1,320 | 8 |
| Harbor | 3,960 | 15 |
| Chinatown | 7,920 | 22 |
| Strip | 13,200 | 30 |
tests/xp.test.ts keeps the full career between level 25 and 35.
What a level opens
Section titled “What a level opens”player/level/unlocks.ts lists the level each decal needs; anything not listed is free from level 1 (the number styles, two stripes and some sponsors and graphics). Paints have no level: the paint shop sells all of them, and any colour from its picker, from the start. Everything opens by level 30.
- Decal shop.
DecalPickerdraws a padlock and the level over a locked decal (ui/lockBadge.ts). A locked decal can still be picked and previewed on the car; Apply then refuses with “Digital camo needs level 28.” - Rules. The
decalsgarage action (player/garageActions.ts) refuses a design holding a locked decal or stripe, and the race server runs the same rules, so a modified game cannot buy one.
Rank badge
Section titled “Rank badge”In the top bar, past the name and the XP bar, a shield with a car shows the level as a rank (player/level/rank.ts): the metal changes every 15 levels (bronze, silver, gold, platinum, then diamond with wings from level 61), and inside a metal the chevrons go 1, 2, 3 every 5 levels. Diamond stays at 3 chevrons from level 71 on. It is drawn in code (menu/topBar/rankBadge.ts, the shield shape in shield.ts) until there is art for it.
Where the player sees it
Section titled “Where the player sees it”- Main menu top bar (
menu/topBar/): portrait,LVL n | NAME, the bar, then the rank badge and the gold achievements shield toward the next level. The name is the leaderboard nickname. Tapping the portrait opens the picker of the six racer portraits (player/portraits.ts), a free field the game syncs. - Result screens: the career results, tournament, boss round and pink slip screens show the XP the race earned with
addXpGain(ui/xpGain.ts): the bar fills, a level-up turns the line into “LEVEL UP”, and the new decals are listed under it. Online result screens show nothing: the daily cap is applied on the server after the race.