Full Config — Progression Rules
The complete configs/server/progression.lua for Smash Down: Los Santos — reward limits, shop rotation, resets, and seasons.
TIP
See the setup guide for explanations and examples. Download this file as a reference, and merge changes with the config shipped in your installed version.
Download server/progression.lua
lua
-- SERVER-ONLY (not in fxmanifest files{}, never ships to clients): the progression system's
-- rules. The public catalog - prices, rewards, the season pass, achievements, the earn table - is
-- configs/shared/progression.lua; this file holds what a client has no business reading or
-- guessing: anti-farm caps, the challenge generator, reset times, the item shop's rotation and
-- discounts, and the season's dates.
return {
-- Master switch. false = no profiles, no rewards, no shop: every fighter and skin is unlocked
-- for everyone (the UI is told by leaving `profile` out of OpenPayload and `catalog` out of
-- BootReply). Wagers, credits and the leaderboard are unaffected either way.
Enabled = true,
-- ── Match rewards (configs/shared/progression.lua Earn has the amounts) ────────────────────
-- vs-CPU matches (the only human in the match) that pay rewards per character per day. Past
-- this they still count toward stats, challenges and achievements, but pay nothing
-- (RewardSummary.note = 'cpu_cap').
CpuDailyCap = 15,
-- Online win streak bonus: + PerWin of the win's base Credits/XP for each consecutive ONLINE
-- win before this one, up to Max (0.10 / 0.50 = the 6th straight win and beyond earn +50%).
-- Only online matches that pay build or break it: CPU wins never count toward it, and an
-- online loss, draw or forfeit resets it.
StreakBonus = { PerWin = 0.10, Max = 0.50 },
-- Seconds of actual play (after the 3-2-1 countdown) a match needs to pay anything
-- (RewardSummary.note = 'too_short'). Stops "start, self-destruct, repeat" farming.
-- It is also the bar for counting as a PLAYER: a match is "online" only when at least two
-- humans took part - finished it, or forfeited after this much play. A second human who joins
-- and quits at once leaves a vs-CPU match behind (CPU-level scaled, CPU-capped).
MinPlaySeconds = 30,
-- Online matches per day that pay (and count toward online wins, the online streak and online
-- challenges) against the SAME opponent. Past it the match pays nothing
-- (RewardSummary.note = 'opponent_cap') but still counts toward lifetime stats. Stops two
-- friends trading wins for Credits all day. Resets with the daily reset. 0 = no limit.
SameOpponentDailyCap = 10,
-- Offline matches (one human vs CPUs) are replayed from the input recording the client sends,
-- which a modified client could fabricate - so the recording may not be longer than the real
-- time since START (60 frames a second) plus this many frames of slack for timer drift and
-- latency. A longer one is audited as suspect and the match earns nothing ('unverified').
OfflineClockSlackFrames = 300,
-- A match's rewards that can't be written right away (the profile is busy, the database is
-- down) are retried on every reset check (ResetCheckSeconds) this many times before they are
-- given up (logged as an error and audited, with the amounts, for manual reconciliation).
RewardRetries = 20,
-- Frames of the pre-match countdown in the game's simulation (web/src/game/sim/const.ts
-- COUNTDOWN). Only change it if the game's does.
CountdownFrames = 180,
-- Largest MatchResult.inputs an offline match may send (numbers in the RLE array: a
-- [bits, count] pair is 2). Bigger payloads are dropped and the match earns nothing.
MaxRleNumbers = 40000,
-- A fighter or skin granted to someone who already owns it pays this fraction of its shop
-- price in Credits instead (0.5 = half). Pass-exclusive skins count at the mythic price.
DuplicateRate = 0.5,
-- ── Daily / weekly resets ───────────────────────────────────────────────────────────────────
-- When "the day" rolls over: daily challenges, the free reroll, the CPU cap, the first-win
-- bonus and the shop's daily sections all reset at Hour:00 in the UTC+UtcOffset timezone.
-- Weekly challenges and the shop's weekly sections reset at the same time on Mondays.
-- (Hour = 6, UtcOffset = -5 is 06:00 EST.)
Reset = { Hour = 6, UtcOffset = 0 },
-- Seconds between checks for a day/week rollover while players are seated (their profile is
-- refreshed and pushed the moment it happens).
ResetCheckSeconds = 30,
-- ── Season ───────────────────────────────────────────────────────────────────────────────
-- The running season (its id, tiers and rewards are in configs/shared/progression.lua).
-- StartDate is 'YYYY-MM-DD' at 00:00 UTC; the season ends LengthDays later. After the end,
-- no more season XP is earned and the premium pass can't be bought; reached rewards can
-- still be claimed until the season id changes.
Season = {
StartDate = '2026-09-01',
LengthDays = 60,
},
-- ── Shop ─────────────────────────────────────────────────────────────────────────────────
-- The item shop (docs/SHOP.md): sections that rotate on the clock, the same for everyone and a
-- pure function of the game day / week (seeded like the challenges - no database, identical
-- across restarts). Only fighters and skins that are FOR SALE ever appear (never a starter, a
-- pass-exclusive skin, a title or a banner), and Featured, Daily, Spotlight and Deals never
-- list the same item on the same day (a bundle may contain items also listed on their own).
-- Nothing gets harder to buy: every for-sale fighter/skin stays buyable at full price from the
-- Collection screen - the shop is discovery, discounts and bundles.
--
-- Daily sections (Featured, Daily, Deals) roll over with the daily reset. Each slides through
-- its own shuffled list of everything it can show, Count / Days new items a day on average (a
-- 1-card section with Days = 2 changes every other day), so an item stays ~Days days and
-- LEAVING SOON (= offered nowhere in the shop tomorrow at today's price or less) marks only the
-- ones really on their way out. Weekly sections (Spotlight, Bundles) roll over with the Monday
-- reset and tag what won't be back next week as LEAVING SOON on the week's last day.
Shop = {
-- Hero cards: `Bundles` of them are a fighter's Complete Set (full set price, see Bundles),
-- the rest standout single items (a legendary or mythic fighter or skin) at full price.
-- Sets already in this week's Bundles section are skipped.
Featured = { Count = 3, Bundles = 2, Days = 2 },
-- Fighters and skins at full price.
Daily = { Count = 10, Days = 2 },
-- Count items (about half fighters, half skins) from one THEME a week. Themes take turns
-- in this order, one per week (round-robin on the week number). A theme is a fighter
-- `series` (configs/shared/progression.lua Fighters: creator, vi, classic, story, pop) or
-- a `rarity` (every for-sale fighter and skin of that rarity). Its name is the locale key
-- progression.shop.themes.<id>; its Pack's is progression.shop.packs.<id>.
Spotlight = {
Count = 6,
Themes = {
{ id = 'creator', series = 'creator' },
{ id = 'vi', series = 'vi' },
{ id = 'legendary', rarity = 'legendary' },
{ id = 'classic', series = 'classic' },
{ id = 'story', series = 'story' },
{ id = 'mythic', rarity = 'mythic' },
{ id = 'pop', series = 'pop' },
},
},
-- Discounted fighters and skins (the old "Today's Deals"), each MinPercent-MaxPercent off,
-- rounded to RoundTo Credits - and kept inside that band after rounding whenever a multiple
-- of RoundTo fits (500 at 25 % is 350, never 400 = 20 %). While an item is a deal it costs
-- the deal price wherever it is bought (shop or Collection). A config without Shop.Deals
-- still reads the pre-item-shop top-level `Featured = { Count, MinPercent, MaxPercent,
-- RoundTo }` table here.
Deals = { Count = 6, Days = 2, MinPercent = 25, MaxPercent = 40, RoundTo = 50 },
-- Weekly bundles: Count - Packs Complete Sets (a fighter + every skin of it that is for
-- sale) and `Packs` Packs (PackSize[1]-PackSize[2] fighters sharing a Spotlight theme; this
-- week's theme gets the first one). A player pays the full price of the items they DON'T
-- own yet minus Discount %, rounded to RoundTo (at least RoundTo); a bundle they own
-- completely can't be bought.
Bundles = { Count = 4, Packs = 2, PackSize = { 2, 4 }, Discount = 30, RoundTo = 50 },
-- Fighters tagged NEW - them, their skins and any bundle with one of them in it - until
-- NewUntil ('YYYY-MM-DD', 00:00 UTC; false = no end date).
NewFighters = { 'seventeen', 'chippy', 'solaire', 'k4mb1', 'ntanis' },
NewUntil = '2026-10-31',
},
-- ── Challenges ───────────────────────────────────────────────────────────────────────────
-- Every player gets Count daily + Count weekly challenges, drawn without repeats from the pool
-- (weight = how often a template comes up), deterministically from their character id and the
-- day/week - so the set is stable across reconnects and restarts. A drawn challenge picks:
-- goal an integer in [goal[1], goal[2]]
-- reward season XP and Credits in the reward ranges (in steps of 50 XP / 10 CR)
-- fighter (templates with `fighter = true`) one the player owns
-- stage (templates with `stage = true`) any enabled stage, evenly (configs/shared/rules.lua
-- Stages: all 25 by default - the neutral, mechanics and phase-shift stages alike)
-- Templates (kind):
-- win, win_online, play, play_online, ko, ko_as, play_as, win_as, damage, win_stage,
-- flawless, streak (win N in a row), play_items (play N matches with items on)
-- Labels: progression.challenges.<kind> with {n} / {fighter} / {stage}.
Challenges = {
RerollsPerDay = 1, -- daily challenges only; weeklies can't be rerolled
Daily = {
Count = 3,
Reward = { seasonXp = { 500, 900 }, credits = { 50, 150 } },
Pool = {
{ kind = 'win', weight = 10, goal = { 2, 3 } },
{ kind = 'play', weight = 10, goal = { 3, 5 } },
{ kind = 'ko', weight = 10, goal = { 5, 10 } },
{ kind = 'damage', weight = 8, goal = { 300, 600 } },
{ kind = 'ko_as', weight = 6, goal = { 3, 6 }, fighter = true },
{ kind = 'play_as', weight = 6, goal = { 2, 3 }, fighter = true },
{ kind = 'win_as', weight = 5, goal = { 1, 2 }, fighter = true },
{ kind = 'win_stage', weight = 5, goal = { 1, 1 }, stage = true },
{ kind = 'flawless', weight = 3, goal = { 1, 1 } },
{ kind = 'play_items', weight = 4, goal = { 2, 3 } },
{ kind = 'win_online', weight = 2, goal = { 1, 1 } },
{ kind = 'play_online', weight = 3, goal = { 1, 2 } },
{ kind = 'streak', weight = 2, goal = { 2, 2 } },
},
},
Weekly = {
Count = 3,
Reward = { seasonXp = { 2500, 4000 }, credits = { 300, 600 } },
Pool = {
{ kind = 'win', weight = 10, goal = { 10, 15 } },
{ kind = 'play', weight = 8, goal = { 15, 25 } },
{ kind = 'ko', weight = 10, goal = { 40, 60 } },
{ kind = 'damage', weight = 8, goal = { 3000, 5000 } },
{ kind = 'ko_as', weight = 5, goal = { 15, 25 }, fighter = true },
{ kind = 'play_as', weight = 5, goal = { 8, 12 }, fighter = true },
{ kind = 'win_as', weight = 5, goal = { 4, 6 }, fighter = true },
{ kind = 'win_stage', weight = 4, goal = { 3, 5 }, stage = true },
{ kind = 'flawless', weight = 3, goal = { 3, 5 } },
{ kind = 'streak', weight = 3, goal = { 3, 4 } },
{ kind = 'win_online', weight = 2, goal = { 3, 5 } },
{ kind = 'play_online', weight = 3, goal = { 5, 10 } },
},
},
},
}