/** * Bilanciamento di gioco — TUTTI i parametri numerici delle regole vivono qui. * * Nessuna formula nel resto del codice deve usare costanti "magiche": * se serve un numero, va aggiunto in questo file e importato. */ export const balance = { /** Parametri del giocatore */ player: { /** Città di partenza dei nuovi giocatori (deve esistere nel seed) */ startingCityName: 'Milano', }, /** * Progressione esperienza. * XP necessari per passare dal livello L al livello L+1: * xpForNextLevel(L) = base * L ^ exponent * Es. livello 1→2: 100 XP, 2→3: ~283 XP, 3→4: ~520 XP. */ xp: { base: 100, exponent: 1.5, }, /** * Viaggio tra città. * Costo = baseCost + riskLevel(città di destinazione) * costPerRiskLevel * Es. città con riskLevel 3: 50 + 3*25 = 125. */ travel: { baseCost: 50, costPerRiskLevel: 25, }, /** Mercato dinamico (vedi blueprint §9) */ market: { /** buyPrice = round(rawPrice * buyMargin) */ buyMargin: 1.12, /** sellPrice = round(rawPrice * sellMargin) */ sellMargin: 0.88, /** rawPrice è limitato a [basePrice*minPriceFactor, basePrice*maxPriceFactor] */ minPriceFactor: 0.35, maxPriceFactor: 3.5, /** divisore minimo dell'offerta nella formula: demand / max(supply, minSupplyDivisor) */ minSupplyDivisor: 0.25, /** demand e supply restano sempre in [min, max] */ minDemandSupply: 0.25, maxDemandSupply: 4.0, /** variazioni per unità comprata */ demandDeltaPerBuyUnit: 0.02, supplyDeltaPerBuyUnit: -0.01, /** variazioni per unità venduta */ demandDeltaPerSellUnit: -0.01, supplyDeltaPerSellUnit: 0.02, /** ritorno verso l'equilibrio a ogni tick: v = v*decayFactor + equilibrium*(1-decayFactor) */ decayFactor: 0.98, equilibrium: 1.0, /** massimo numero di unità per singola transazione */ maxQuantityPerTrade: 100, }, /** Generazione e risoluzione missioni */ missions: { /** numero minimo di missioni attive (non scadute) per città */ minActivePerCity: 5, /** missioni attive contemporanee per giocatore */ maxActivePerPlayer: 3, /** scadenza missione generata: minuti casuali in [min, max] */ expiryMinutesMin: 30, expiryMinutesMax: 60, /** durata missione: secondi casuali in [min, max] */ durationSecondsMin: 60, durationSecondsMax: 900, /** difficoltà casuale in [min, max] */ difficultyMin: 1, difficultyMax: 5, /** * Rischio della missione (0..1): * risk = difficulty*riskPerDifficulty + (cityRiskLevel-3)*riskPerCityRiskLevel ± riskJitter * limitato a [riskMin, riskMax]. */ riskPerDifficulty: 0.12, riskPerCityRiskLevel: 0.03, riskJitter: 0.05, riskMin: 0.05, riskMax: 0.9, /** * Ricompensa in denaro: * reward = (base + difficulty*perDifficulty) * economyModifier * (1 + risk*riskRewardBonus) * Se la missione richiede oggetti, si aggiunge il loro valore * requiredItemRefund. */ rewardMoneyBase: 150, rewardMoneyPerDifficulty: 250, riskRewardBonus: 0.5, requiredItemRefund: 1.15, /** Ricompensa XP: base + difficulty * perDifficulty */ rewardXpBase: 20, rewardXpPerDifficulty: 30, /** probabilità che una missione DELIVERY/SMUGGLING richieda oggetti */ requiredItemChance: 0.4, requiredItemQuantityMax: 3, /** * Probabilità di successo al claim: * chance = baseChance * - risk * riskWeight * + level * levelBonus * + reputation * reputationBonus * - (policePressure*eventPoliceModifier - 1) * policeWeight * limitata a [minChance, maxChance]. */ success: { baseChance: 0.95, riskWeight: 0.6, levelBonus: 0.01, reputationBonus: 0.0005, policeWeight: 0.05, minChance: 0.05, maxChance: 0.95, }, /** reputazione: +difficulty*gain in caso di successo, -loss in caso di fallimento (mai sotto 0) */ reputationGainPerDifficulty: 2, reputationLossOnFail: 3, }, /** Eventi mondo (locali e globali) */ events: { /** probabilità di generare un evento locale a ogni ciclo da 15 minuti */ localChance: 0.25, /** probabilità di generare un evento globale a ogni ciclo orario */ globalChance: 0.2, /** durata eventi locali: minuti casuali in [min, max] */ localDurationMinutesMin: 15, localDurationMinutesMax: 45, /** durata eventi globali: minuti casuali in [min, max] */ globalDurationMinutesMin: 30, globalDurationMinutesMax: 90, /** range [min, max] dei moltiplicatori per tipo di evento */ types: { MARKET_BOOM: { priceModifier: [1.2, 1.5], policeModifier: [1.0, 1.0] }, POLICE_RAID: { priceModifier: [0.9, 1.1], policeModifier: [1.3, 1.8] }, SHORTAGE: { priceModifier: [1.3, 1.8], policeModifier: [1.0, 1.2] }, BLACKOUT: { priceModifier: [0.7, 0.9], policeModifier: [0.8, 1.0] }, }, }, /** Classifiche */ leaderboard: { /** numero massimo di posizioni restituite/memorizzate */ maxEntries: 100, /** limite di default per le richieste GET */ defaultLimit: 20, }, } as const; export type EventTypeBalance = keyof typeof balance.events.types;