File size: 2,955 Bytes
616ae7b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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);
}