Skip to content

Remote config

Some game rules can change from the server, without a store release: the ad caps, the daily first-win gold, the weekly event’s prizes, a scale on every career prize, and a pause switch for six features. They live in Cloudflare Flagship (feature flags read inside the worker), app neon-drag (id 6e85360e-73cf-4822-a2fd-016bf566cf74), in the Cloudflare dashboard under Compute > Flagship. Flagship is an open beta since 2026-05-26 with no published pricing yet (docs: https://developers.cloudflare.com/flagship/, read 2026-11-12).

Flag Holds Split?
live-config JSON with only the values that differ from the defaults yes: a split is an A/B test
paused JSON list of paused features never

live-config fields and their defaults (LIVE_DEFAULTS in src/game/core/liveConfig.ts):

Field Default Used by
fuelAdsPerDay 3 fuel ad (player/fuel/fuel.ts)
goldAdsPerDay 3 gold ad (player/gold/freeGold.ts)
dailyWinGold 10 the day’s first career win
dailyBonusMax 3 daily cash bonus (player/ads/dailyBonus.ts)
dailyBonusShare 0.25 the bonus as a share of the average event prize
weeklyRewards [{tokens:1,gold:10},{tokens:1,gold:15},{tokens:2,gold:25}] weekly ladder, one per rung (always 3)
prizeScale 1 every career prize, rounded to $10 (scaledPrize)

A field of the wrong type or out of range keeps its default, field by field (mergeLiveConfig), so a typo never breaks a rule. prizeScale also moves what follows the average event prize: the daily bonus, quick race pay and the gold-to-cash rate.

paused features: online (rooms and the room list), weekly, ads (rewarded ads), goldPacks, friends, leaderboards. A paused feature’s button stays in place, disabled, and a tap says it is paused (ui/pausedFeature.ts); the race server refuses its requests with 403 and the same text. Two exceptions: a paused ad offer simply does not show (as when no ad is loaded), and paused gold packs are only no longer sold; a pack already paid for is still credited. paused is its own flag because requests before sign-in (the public leaderboard, online rooms) carry no user id, and Flagship would bucket them at random in a split.

  1. Every request to the worker runs inside withLiveConfig (party/src/liveConfig.ts): both flags evaluated for no one, then, once the session is known, live-config again for the player (usePlayerConfig, context { userId }). A worker serves requests side by side, so each keeps its own copy in an AsyncLocalStorage; the shared rules read it with liveConfig().
  2. GET /api/config returns { config, group }: the merged config and the variation served, the player’s A/B group (default outside a test).
  3. The game fetches it at start, after the garage (player/account/liveConfigFetch.ts), so the menus show it. Until then, and offline, the game shows the defaults; the server judges with its own copy either way.
  4. When Flagship fails, the defaults hold.

In the dashboard, edit the default variation of live-config, for example {"goldAdsPerDay": 4}. Or with wrangler from party/:

Terminal window
npx wrangler flagship flags update 6e85360e-73cf-4822-a2fd-016bf566cf74 live-config -t json --set-variation 'default={"goldAdsPerDay":4}'
npx wrangler flagship flags update 6e85360e-73cf-4822-a2fd-016bf566cf74 paused -t json --set-variation 'none=["online"]'

Changes reach the worker within seconds, no deploy. Flagship keeps an audit trail of every change.

Add a second variation to live-config and a rule that serves it to a share of players, bucketed on userId:

Terminal window
npx wrangler flagship flags update 6e85360e-73cf-4822-a2fd-016bf566cf74 live-config -t json --set-variation 'more-gold-ads={"goldAdsPerDay":5}' --rule 'serve=more-gold-ads; rollout=50%@userId'

The same player always gets the same variation while the rule stays the same. The group is in GET /api/config (liveConfigGroup() in the game) and on every analytics event (ab_group); npm run stats compares the groups (Game statistics).

wrangler dev reads a local flag store, empty unless filled, so it runs on the defaults. Fill it with npx wrangler flagship flags pull <app id> or edit it with --local on the flag commands; both refuse while a dev session of the worker runs.