configs/
pet_facets.rs

1use essences::pet_facets::PetFacet;
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4use tsify_next::Tsify;
5
6/// One facet's approximate contribution to displayed Power (BAL-030), authored
7/// as the value ON A ROLL: the die is folded into the estimator separately, so
8/// this number is what the facet is worth in the phase it comes up in.
9#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, Tsify)]
10pub struct PetFacetPowerQ {
11    #[schemars(title = "Грань")]
12    pub facet: PetFacet,
13
14    #[schemars(
15        title = "Множитель Power на первом уровне пета",
16        description = "1.0 = грань не влияет на Power."
17    )]
18    pub q_first_level: f64,
19
20    #[schemars(
21        title = "Множитель Power на максимальном уровне пета",
22        description = "Промежуточные уровни интерполируются линейно по ln(q)."
23    )]
24    pub q_max_level: f64,
25}
26
27/// Tunables of Pet Facets / Team Die v0.
28///
29/// One flat settings singleton rather than a per-facet content catalog: the
30/// twenty facets are RULES, authored in code
31/// ([`essences::pet_facets::PetFacet`]), and only their numbers are balance. A
32/// catalog would have added twenty content objects, twenty translation records
33/// and a link column on `PetTemplate` without making a single number more
34/// tunable than it is here.
35///
36/// Percentages are whole percent (`35.0` = `+35%`); every `*_ticks` value is
37/// MILLISECONDS of fight clock (`6_000` = 6 s).
38#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, Tsify)]
39pub struct PetFacetSettings {
40    /// How much one pet level above the first raises every facet MAGNITUDE, in
41    /// permyriad (`500` = `+5%` per level). Counts, durations and charge
42    /// numbers are deliberately NOT scaled: rank scales the numbers of a facet,
43    /// it never changes its rules.
44    #[schemars(
45        title = "Прирост силы грани за уровень пета (доли от 10000)",
46        description = "На сколько сильнее становятся числа обеих граней за каждый уровень пета выше первого. Количество зарядов и длительности окон не масштабируются."
47    )]
48    pub rank_growth_permyriad: i64,
49
50    // ---- PET-01 Zip ----
51    #[schemars(
52        title = "Head Start: сокращение кулдаунов (мс)",
53        description = "На сколько миллисекунд уменьшаются все оставшиеся кулдауны скиллов."
54    )]
55    pub head_start_ticks: u64,
56
57    #[schemars(
58        title = "Second Spark: сила повтора (%)",
59        description = "Доля payload следующего original Skill, которую наносит derived-повтор."
60    )]
61    pub second_spark_percent: f64,
62
63    // ---- PET-02 Ledger Mimic ----
64    #[schemars(
65        title = "Budget Plan: множитель цены маны",
66        description = "Во сколько раз дешевеет мана следующих кастов."
67    )]
68    pub budget_plan_cost_multiplier: f64,
69
70    #[schemars(
71        title = "Budget Plan: количество кастов",
72        description = "Сколько ближайших original casts получают скидку."
73    )]
74    pub budget_plan_casts: i64,
75
76    #[schemars(
77        title = "Open Tab: максимальная доплата (%)",
78        description = "Сколько сверх цены каста может быть потрачено, если мана есть."
79    )]
80    pub open_tab_surcharge_percent: f64,
81
82    #[schemars(
83        title = "Open Tab: доля доплаты в payload (%)",
84        description = "Какая доля доплаты превращается в прибавку к payload каста."
85    )]
86    pub open_tab_payload_share_percent: f64,
87
88    // ---- PET-03 Springpaw ----
89    #[schemars(title = "Overtime: прибавка скорости атаки (%)")]
90    pub overtime_attack_speed_percent: f64,
91    #[schemars(title = "Overtime: длительность окна (мс)")]
92    pub overtime_duration_ticks: u64,
93
94    #[schemars(title = "Magic Volley: количество болтов")]
95    pub magic_volley_bolts: i64,
96    #[schemars(title = "Magic Volley: сила одного болта (% от Attack)")]
97    pub magic_volley_percent: f64,
98
99    // ---- PET-04 Bulwark Slime ----
100    #[schemars(title = "Safety Net: снижение входящего урона (%)")]
101    pub safety_net_reduction_percent: f64,
102    #[schemars(title = "Safety Net: длительность окна (мс)")]
103    pub safety_net_duration_ticks: u64,
104
105    #[schemars(
106        title = "Retaliation: сила площадного взрыва (% от Attack)",
107        description = "Взрыв срабатывает на следующем Core-попадании по герою."
108    )]
109    pub retaliation_percent: f64,
110
111    // ---- PET-05 Lucky Bat ----
112    #[schemars(title = "Calibration: прибавка шанса крита (%)")]
113    pub calibration_crit_percent: f64,
114    #[schemars(title = "Calibration: количество атак")]
115    pub calibration_attacks: i64,
116
117    #[schemars(title = "Lucky Star: сила взрыва (% от Attack)")]
118    pub lucky_star_percent: f64,
119
120    // ---- PET-06 Owl Auditor ----
121    #[schemars(title = "Lead Reading: прибавка Resonance (%)")]
122    pub lead_reading_resonance_percent: f64,
123    #[schemars(title = "Lead Reading: количество срабатываний закона")]
124    pub lead_reading_activations: i64,
125
126    #[schemars(title = "Wild Reading: усиление Effect закона (%)")]
127    pub wild_reading_effect_percent: f64,
128    #[schemars(title = "Wild Reading: длительность окна (мс)")]
129    pub wild_reading_duration_ticks: u64,
130
131    // ---- PET-08 Clock Crow ----
132    #[schemars(
133        title = "Advance Notice: стартовый Flip Gauge (%)",
134        description = "Доля порога флипа, с которой начинается новая фаза. Единственное исключение из правила «грань не создаёт Gauge»."
135    )]
136    pub advance_notice_gauge_percent: f64,
137
138    #[schemars(title = "Rising Gate: количество усиленных Trigger-проков")]
139    pub rising_gate_procs: i64,
140    #[schemars(
141        title = "Rising Gate: множитель Gauge прока на уровне пета 1",
142        description = "BAL-014: первые проки умножают награду Trigger, а не добавляют Gauge. Между уровнями 1 и 10 значение растёт линейно."
143    )]
144    pub rising_gate_multiplier_l1: f64,
145    #[schemars(title = "Rising Gate: множитель Gauge прока на уровне пета 10")]
146    pub rising_gate_multiplier_l10: f64,
147
148    // ---- PET-09 Toolbox Goblin ----
149    #[schemars(
150        title = "Prepared Slots: сила первого прока слота (%)",
151        description = "100 означает обычную силу; 140 — на 40% сильнее."
152    )]
153    pub prepared_slots_percent: f64,
154
155    #[schemars(title = "First Spell: сила повтора первого Effect (%)")]
156    pub first_spell_percent: f64,
157
158    // ---- PET-10 Mirror Moth ----
159    #[schemars(title = "Souvenir: количество проков с парным скрытым Effect")]
160    pub souvenir_procs: i64,
161    #[schemars(title = "Souvenir: сила парного скрытого Effect (%)")]
162    pub souvenir_percent: f64,
163
164    #[schemars(title = "Dream Reader: сила разбуженного скрытого закона (%)")]
165    pub dream_reader_percent: f64,
166
167    // ---- PET-11 Lunchbox Boar ----
168    #[schemars(
169        title = "Packed Lunch: лечение (% от Max HP)",
170        description = "Перелечивание сгорает."
171    )]
172    pub packed_lunch_heal_percent: f64,
173
174    #[schemars(
175        title = "Life Bloom: доля Core-урона в лечение на уровне пета 1 (%)",
176        description = "BAL-014: между уровнями 1 и 10 значение растёт линейно."
177    )]
178    pub life_bloom_percent_l1: f64,
179    #[schemars(title = "Life Bloom: доля Core-урона в лечение на уровне пета 10 (%)")]
180    pub life_bloom_percent_l10: f64,
181    #[schemars(title = "Life Bloom: длительность окна (мс)")]
182    pub life_bloom_duration_ticks: u64,
183
184    #[schemars(
185        title = "Множители Power граней",
186        description = "По одной строке на каждую из 20 граней (BAL-030). Порядок значения не имеет, пропуски запрещены."
187    )]
188    pub power_q: Vec<PetFacetPowerQ>,
189}
190
191impl PetFacetSettings {
192    /// Rank scaling of one facet magnitude: `+rank_growth_permyriad` per pet
193    /// level above the first. A level-1 pet plays exactly at the authored
194    /// numbers, which is what makes rank an amplifier rather than a gate.
195    pub fn rank_multiplier(&self, pet_level: i64) -> f64 {
196        let levels_above_first = (pet_level - 1).max(0) as f64;
197        1.0 + (self.rank_growth_permyriad.max(0) as f64 / 10_000.0) * levels_above_first
198    }
199
200    /// BAL-014: a facet magnitude authored as L1/L10 endpoints, interpolated
201    /// linearly over pet levels 1..=10 — independent of
202    /// [`Self::rank_multiplier`].
203    pub fn level_interpolated(l1: f64, l10: f64, pet_level: i64) -> f64 {
204        let t = (pet_level - 1).clamp(0, 9) as f64 / 9.0;
205        l1 + (l10 - l1) * t
206    }
207
208    /// The authored Power row of `facet`, or `None` when the catalog has none
209    /// (which `GameConfig::validate` refuses, so production always has one).
210    pub fn power_q_row(&self, facet: PetFacet) -> Option<&PetFacetPowerQ> {
211        self.power_q.iter().find(|row| row.facet == facet)
212    }
213}