export type RarityTier = | 'common' | 'uncommon' | 'notable' | 'rare' | 'elite' | 'exotic' | 'legendary' | 'mythic' | 'primordial'; // Ascending order (weakest -> apex). Index positions double as a numeric rank. export const RARITY_TIERS: RarityTier[] = [ 'common', 'uncommon', 'notable', 'rare', 'elite', 'exotic', 'legendary', 'mythic', 'primordial', ]; export type RarityInput = { thc: number; cbd: number; yield: number; growTime: number; stability: number; generation: number; vibrance: number; bloom: number; canAttempt: boolean; }; function clamp01(value: number): number { return Math.max(0, Math.min(1, value)); } // Market valuation: unbounded, drives pricing (commonCloneEquivalent) and the // leaderboard "Rarity" metric. Left intentionally as-is so the economy is untouched. export function rarityScore(input: RarityInput): number { return Math.round(( input.thc * 1.35 + input.cbd * 0.55 + input.yield * 0.18 + Math.max(0, 90 - input.growTime) * 0.18 + input.stability * 18 + input.generation * 4 + input.vibrance * 0.16 + input.bloom * 0.14 + (input.canAttempt ? 5 : 0) ) * 10) / 10; } // Bounded 0-100 collectibility index that drives the visual rarity TIER only. // Every term is normalized to 0..1 and generation saturates (gen/(gen+6)) so // breeding deeper cannot inflate a plant into the top tier forever -- the top // tiers stay rare permanently, unlike the raw score. export function rarityIndex(input: RarityInput): number { const potency = clamp01(input.thc / 40); const cannabinoid = clamp01(input.cbd / 18); const yieldN = clamp01((input.yield - 25) / 135); const speed = clamp01((90 - input.growTime) / 50); const stability = clamp01(input.stability); const bloomN = clamp01(input.bloom / 100); const vibranceN = clamp01(input.vibrance / 100); const genN = input.generation / (input.generation + 6); const blended = potency * 0.2 + bloomN * 0.18 + genN * 0.15 + vibranceN * 0.12 + yieldN * 0.1 + stability * 0.1 + speed * 0.09 + cannabinoid * 0.06; return Math.round(clamp01(blended) * 1000) / 10; } // Thresholds calibrated against the simulated index distribution (the index tops // out near 80 in practice) so every tier is reachable and legendary+ stay scarce: // veterans land ~58% exotic / 12% legendary / 2% mythic / 0.1% primordial, while // early players span common->rare with headroom to climb. export function rarityTier(index: number): RarityTier { if (index >= 77) return 'primordial'; if (index >= 74) return 'mythic'; if (index >= 70) return 'legendary'; if (index >= 65) return 'exotic'; if (index >= 60) return 'elite'; if (index >= 55) return 'rare'; if (index >= 49) return 'notable'; if (index >= 43) return 'uncommon'; return 'common'; } export function rarityRank(tier: RarityTier): number { return RARITY_TIERS.indexOf(tier); }