-- 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 } },
            },
        },
    },
}
