configs/artifacts.rs
1//! Artifact configuration — the two catalogs and every tunable the runtime
2//! reads.
3//!
4//! Everything a designer can retune from the LiveOps panel lives here: the
5//! ownership stat bonus per artifact level, the duplicate ladder, the socket
6//! unlock schedule, the artifact-stone upgrade ladder, and the (placeholder)
7//! acquisition sources.
8//!
9//! **Every number in this module is a placeholder.** The design doc leaves the
10//! ownership percentages, the unlock chapters, the drop chances and the stone
11//! upgrade step explicitly unassigned (§10), so they are config, not constants,
12//! and the shipped values exist only so the system can be exercised end to end.
13
14use essences::artifacts::{
15 ArtifactSocketColumn, ArtifactSocketSlot, ArtifactStoneTemplateId, ArtifactTemplateId,
16};
17use essences::items::AttributeId;
18use essences::stones::StoneTier;
19use schema_loader::attribute_link_id_schema;
20use schemars::JsonSchema;
21use serde::{Deserialize, Serialize};
22use tsify_next::Tsify;
23
24/// The one-line rule welded into an artifact, active only while it is worn.
25///
26/// A World Law reclassifies what the build already produces; it never adds a
27/// number (design §4). It is the only thing allowed to touch the gauge, the
28/// Core/Proc boundary, the flip law or the socket limits — the Aspect stones
29/// cannot.
30///
31/// The v0.2 pool is three artifacts: `ART-00 Plain Gate Key` (the starter
32/// «Пустая оправа», no law at all), `ART-03 Halfway Bell` — the only law that
33/// ships here — and `ART-02 Thin Mirror`, which reads hidden Laws and therefore
34/// waits for the cores/laws vertical. «Отражение» (a dodge additionally reading
35/// as a crit) was cut from the pool in v0.2: it is a rulebreaker, and those live
36/// in a separate `Artifacts v2` wave.
37///
38/// Variant docs live here rather than on the variants themselves: a documented
39/// variant makes the admin schema generate a `oneOf` of one-value enums instead
40/// of a plain picker.
41///
42/// * `HalfwayBell` — «Колокол на полпути» (`ART-03`): the Global Flip happens at
43/// a *share* of the configured threshold
44/// ([`ArtifactsSettings::halfway_bell_threshold_share`], `60%`). No other
45/// gauge source is amplified — the bar moves, the fill rate does not.
46/// * `ThinMirror` — «Тонкое зеркало» (`ART-02`): EVERY slotted law of the side
47/// that is currently down keeps evaluating its own Core conditions and, when
48/// one is met, applies its Effect at
49/// [`ArtifactsSettings::thin_mirror_share`] (`30%`) of Law Power. It is the
50/// most expensive rule in the pool — it switches the other half of the build
51/// back on — and the whole of its safety is the fourth clause of the
52/// intersection rule (design §3): a hidden activation produces NO Resonance,
53/// NO bridge charge and NO Flip Gauge, and its Effect is not a Core event.
54/// Without that clause it would double both the bridge fill and the flip
55/// tempo.
56#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, JsonSchema, Tsify)]
57#[tsify(from_wasm_abi, into_wasm_abi)]
58pub enum ArtifactWorldLaw {
59 HalfwayBell,
60 ThinMirror,
61}
62
63/// Which rule an artifact stone carries.
64///
65/// Five per socket (design §5) — a socket is a choice between readings of one
66/// moment, never between a small number and a big one. Rank scales the number
67/// and opens no new mechanic, which is why a level moves
68/// [`ArtifactStoneTemplate::magnitude`] and nothing else.
69///
70/// **Twenty-nine rules, not thirty.** The left (Aspect) column has fifteen and
71/// retunes the Trigger/Effect stones worn in gear; the right (Law) column has
72/// **fourteen** and retunes the laws and bridges worn in cores. The Bridge Law
73/// socket ships four: `BL-04 Priority Gate` was cut because it gave `+50%`
74/// Resonance "from its own source", which is a law-shaped effect in a bridge
75/// socket and double-covered the rewritten `BL-05` (post-merge plan §8). The
76/// numbering keeps the hole — `BL-01`, `BL-02`, `BL-03`, `BL-05` — as the
77/// catalog already does for the missing `ART-01`.
78///
79/// Variant docs live here rather than on the variants themselves: a documented
80/// variant makes the admin schema generate a `oneOf` of one-value enums instead
81/// of a plain picker.
82///
83/// Visible Aspect — how active gear effects behave:
84///
85/// * `Crescendo` (`VA-01`) — active effects ramp linearly with the gauge, from
86/// `secondary_magnitude`% on an empty gauge to `magnitude`% just before the
87/// flip (`85% → 130%`).
88/// * `Alternator` (`VA-02`) — a proc of a *different* gear slot runs at
89/// `magnitude`%, a repeat of the previous slot at `secondary_magnitude`%
90/// (`125% / 90%`). "Previous" means the previously resolved slot, in the
91/// `ItemType::iter()` order every slot rule shares.
92/// * `FocusedEngine` (`VA-03`) — repeating one slot adds `magnitude`% per
93/// repeat up to `secondary_magnitude`% (`+10%` per repeat, `+30%` cap);
94/// a proc of another slot resets the streak.
95/// * `OpeningFive` (`VA-04`) — the first proc of each of the five slots in a
96/// phase runs at `magnitude`% (`150%`), the rest are plain.
97/// * `SmallGears` (`VA-05`) — effects run at a share that depends on the tier of
98/// the **trigger** that lit them
99/// ([`ArtifactStoneTemplate::tier_magnitudes`]: `130 / 115 / 105 / 100`).
100///
101/// Hidden Aspect — what hidden gear effects do:
102///
103/// * `BackgroundVoice` (`HA-01`) — every trigger runs the active effect in full
104/// and the paired hidden one at `magnitude`% (`30%`).
105/// * `ShadowRound` (`HA-02`) — once every `rule_param` ticks the next hidden
106/// effect in slot order fires by itself at `magnitude`% (`60%`).
107/// * `CrossRelay` (`HA-03`) — a proc of slot `N` additionally runs the hidden
108/// effect of slot `N+1` at `magnitude`% (`35%`).
109/// * `SecondHalf` (`HA-04`) — past `secondary_magnitude`% of the gauge, the
110/// paired hidden effect accompanies every proc at `magnitude`% (`50%`).
111/// * `CatchUp` (`HA-05`) — when the active effect is wholly inapplicable, the
112/// paired hidden one runs at `magnitude`% (`100%`) instead.
113///
114/// Flip Aspect — the transition itself:
115///
116/// * `IncomingSalvo` (`FA-01`) — after a flip the five newly active effects each
117/// run once at `magnitude`% (`50%`), all on the flip tick.
118/// * `LastWord` (`FA-02`) — the five *departing* active effects each run once at
119/// `magnitude`% (`50%`) as the flip happens.
120/// * `WarmStart` (`FA-03`) — the first ordinary fire of each newly active effect
121/// is repeated at `magnitude`% (`60%`).
122/// * `Afterimage` (`FA-04`) — the first `rule_param` trigger procs after a flip
123/// are accompanied by the departing paired effect at `magnitude`% (`25%`).
124/// * `StaggeredEntrance` (`FA-05`) — the same five new effects as
125/// `IncomingSalvo`, at `magnitude`% (`70%`), spaced `rule_param` ticks apart
126/// in slot order.
127///
128/// Visible Law — the laws of the side that is currently UP. Each of these but
129/// `FirstArticle` acts on ONE law the player chose
130/// ([`essences::artifacts::ArtifactStone::law_target`]); a stone with no chosen
131/// law does nothing. Because the choice is a LAW and not a role, and a law's
132/// side is fixed, `SourceFocus` / `ExportPower` / `FullConfidence` only speak in
133/// their law's own phase and are silent for the other half of the fight — that
134/// half is what pays for the size of the bonus (post-merge plan §8):
135///
136/// * `SourceFocus` (`VL-01`) — the chosen law's Resonance `+magnitude`%
137/// (`+50%`), its Effect `secondary_magnitude`% (`−15%`). The catalog's "only
138/// into its own bridge" is dropped: a law's Resonance has no other address.
139/// * `CurrentPower` (`VL-02`) — the chosen law's Effect `+magnitude`% (`+35%`),
140/// its Resonance `secondary_magnitude`% (`−25%`). The mirror of `VL-01`.
141/// * `ExportPower` (`VL-03`) — the hard version of `VL-01`: Resonance
142/// `+magnitude`% (`+75%`), Effect `secondary_magnitude`% (`−25%`). Kept beside
143/// `VL-01` deliberately — same trade, two settings of it.
144/// * `FullConfidence` (`VL-04`) — once the chosen law has filled its outgoing
145/// bridge, that law runs at `+magnitude`% Effect (`+30%`) until the flip.
146/// * `FirstArticle` (`VL-05`) — the one rule of the column that addresses the
147/// whole build: the FIRST activation of EVERY law of the active side in a
148/// phase applies its Effect at `magnitude`% (`150%`), Resonance untouched. It
149/// changes the rhythm of a phase rather than the strength of one law, which is
150/// why it takes no target.
151///
152/// Hidden Law — the laws of the side that is currently DOWN. A hidden
153/// activation never produces Resonance, bridge charge or Flip Gauge, and its
154/// Effect is not a Core event ([`crate::artifacts::ArtifactWorldLaw::ThinMirror`]
155/// carries the same rule):
156///
157/// * `ProxyReader` (`HL-01`) — the chosen hidden law evaluates its own Core
158/// conditions and applies its Effect at `magnitude`% (`30%`).
159/// * `LinkedEcho` (`HL-02`) — when the chosen law activates, the law at the
160/// far end of its bridge applies its Effect at `magnitude`% (`20%`) without
161/// checking its own condition at all.
162/// * `LateAwakening` (`HL-03`) — past `secondary_magnitude`% of the gauge
163/// (`70%`) the chosen hidden law works at `magnitude`% (`50%`).
164/// * `SingleLesson` (`HL-04`) — the first Core event of a phase that satisfies
165/// the chosen hidden law wakes it at `magnitude`% (`100%`); the stone is then
166/// silent until the flip.
167/// * `ReadyTarget` (`HL-05`) — once the chosen law's bridge is full, the law at
168/// its far end works at `magnitude`% (`35%`) until the flip.
169///
170/// Bridge Law — the bridges themselves. `BL-01`…`BL-03` move EVERY bridge;
171/// `BL-05` is the only one that addresses a single bridge, through the law that
172/// sits in it:
173///
174/// * `ExtraSpan` (`BL-01`) — `rule_param` extra bridges over the
175/// `min(core level) − 1` budget, and every bridge's capacity `+magnitude`%
176/// (`+20%`).
177/// * `DeepSpan` (`BL-02`) — every bridge's capacity `+magnitude`% and its
178/// recipient amplification `+secondary_magnitude`% (`+40% / +40%`): a deeper
179/// bridge and a higher ceiling at the same price per unit.
180/// * `ShortSpan` (`BL-03`) — capacity `magnitude`% and amplification
181/// `secondary_magnitude`% (`−40% / −20%`). The minus signs are the design: a
182/// shorter bridge with a lower ceiling makes each unit worth MORE
183/// (`40% / 6 units` against `50% / 10`), so the cap arrives in three fires
184/// instead of five.
185/// * `ReciprocalGate` (`BL-05`) — `+magnitude`% Resonance (`+25%`) to BOTH
186/// directions of the chosen law's bridge, unconditionally. The catalog's
187/// original wording ("if both links A→B and B→A are chosen") describes a
188/// choice no version of the model offers — a bridge is one object carrying two
189/// directions — so it was rewritten (post-merge plan §8).
190#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, JsonSchema, Tsify)]
191#[tsify(from_wasm_abi, into_wasm_abi)]
192pub enum ArtifactStoneRule {
193 Crescendo,
194 Alternator,
195 FocusedEngine,
196 OpeningFive,
197 SmallGears,
198 BackgroundVoice,
199 ShadowRound,
200 CrossRelay,
201 SecondHalf,
202 CatchUp,
203 IncomingSalvo,
204 LastWord,
205 WarmStart,
206 Afterimage,
207 StaggeredEntrance,
208 SourceFocus,
209 CurrentPower,
210 ExportPower,
211 FullConfidence,
212 FirstArticle,
213 ProxyReader,
214 LinkedEcho,
215 LateAwakening,
216 SingleLesson,
217 ReadyTarget,
218 ExtraSpan,
219 DeepSpan,
220 ShortSpan,
221 ReciprocalGate,
222}
223
224impl ArtifactStoneRule {
225 /// The socket this rule belongs in. A stone's template names its socket
226 /// explicitly, and `GameConfig::validate_artifacts` checks the two agree —
227 /// so a mistyped catalog entry cannot ship a Flip rule into a Visible
228 /// socket.
229 pub const fn socket(self) -> ArtifactSocketSlot {
230 match self {
231 Self::Crescendo
232 | Self::Alternator
233 | Self::FocusedEngine
234 | Self::OpeningFive
235 | Self::SmallGears => ArtifactSocketSlot::VisibleAspect,
236 Self::BackgroundVoice
237 | Self::ShadowRound
238 | Self::CrossRelay
239 | Self::SecondHalf
240 | Self::CatchUp => ArtifactSocketSlot::HiddenAspect,
241 Self::IncomingSalvo
242 | Self::LastWord
243 | Self::WarmStart
244 | Self::Afterimage
245 | Self::StaggeredEntrance => ArtifactSocketSlot::FlipAspect,
246 Self::SourceFocus
247 | Self::CurrentPower
248 | Self::ExportPower
249 | Self::FullConfidence
250 | Self::FirstArticle => ArtifactSocketSlot::VisibleLaw,
251 Self::ProxyReader
252 | Self::LinkedEcho
253 | Self::LateAwakening
254 | Self::SingleLesson
255 | Self::ReadyTarget => ArtifactSocketSlot::HiddenLaw,
256 Self::ExtraSpan | Self::DeepSpan | Self::ShortSpan | Self::ReciprocalGate => {
257 ArtifactSocketSlot::BridgeLaw
258 }
259 }
260 }
261
262 /// Whether this rule is worthless until the player points its stone at a
263 /// law. Every Law-column rule but `VL-05 First Article` (which addresses the
264 /// whole build) and `BL-01`…`BL-03` (which move every bridge) needs one.
265 pub const fn needs_law_target(self) -> bool {
266 matches!(
267 self,
268 Self::SourceFocus
269 | Self::CurrentPower
270 | Self::ExportPower
271 | Self::FullConfidence
272 | Self::ProxyReader
273 | Self::LinkedEcho
274 | Self::LateAwakening
275 | Self::SingleLesson
276 | Self::ReadyTarget
277 | Self::ReciprocalGate
278 )
279 }
280
281 /// Whether this rule can wake a law of the side that is currently DOWN.
282 /// Those activations go through the intersection rule (design §3) and are
283 /// the only law fires that produce no Resonance and no bridge charge.
284 pub const fn wakes_hidden_law(self) -> bool {
285 matches!(
286 self,
287 Self::ProxyReader
288 | Self::LinkedEcho
289 | Self::LateAwakening
290 | Self::SingleLesson
291 | Self::ReadyTarget
292 )
293 }
294
295 /// Why this rule cannot run on this branch yet, or `None` when it can.
296 ///
297 /// Every rule now has the mechanic it reads, so nothing is blocked: the
298 /// left column's last three dependencies landed with the stones v0.2 merge,
299 /// and the whole right column's — laws, bridges, Resonance — landed with the
300 /// cores vertical. How the left column's were settled is worth keeping,
301 /// because the answers are the rules' semantics rather than incidental
302 /// history:
303 ///
304 /// * `Alternator` and `FocusedEngine` both say "another slot" / "a repeat of
305 /// the previous slot", and the shared Trigger Cooldown lets up to five
306 /// slots answer one Core event at once. **Resolved**: slots resolve in the
307 /// fixed `ItemType::iter()` order (Weapon → Torso → Head → Gloves →
308 /// Shoulders), the order `HA-03 Cross Relay` already ships on, and "the
309 /// previous slot" is the previously *resolved* one, carried across event
310 /// boundaries. The two alternatives were rejected on their merits: "the
311 /// last slot of the previous batch" makes the bonus depend on batch
312 /// boundaries the player cannot see, and "only when exactly one slot fired"
313 /// switches both rules off for any five-socket build — the build they
314 /// exist for.
315 /// * `SmallGears` keys off trigger tiers up to Legendary. **Resolved**: the
316 /// stones v0.2 catalog ships Legendary triggers.
317 ///
318 /// `GameConfig::validate_artifacts` refuses a catalog entry that ships a
319 /// blocked rule with `active: true`. With nothing blocked the guard has no
320 /// work to do, and it stays because the next rule to be entered ahead of its
321 /// dependency will need it again.
322 pub const fn unavailable_dependency(self) -> Option<&'static str> {
323 None
324 }
325}
326
327/// One rung of `VA-05 Small Gears`: how strongly effects run when the trigger
328/// that fired them has this tier.
329///
330/// Carried on the template rather than hard-coded so the rule's whole content is
331/// visible in the catalog while it waits for the Legendary tier to exist.
332#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Tsify)]
333#[tsify(from_wasm_abi, into_wasm_abi)]
334pub struct ArtifactTierMagnitude {
335 #[schemars(title = "Тир триггера")]
336 pub tier: StoneTier,
337
338 #[schemars(
339 title = "Сила эффекта, %",
340 description = "100 — обычная сила. Используется только правилом «Мелкие шестерни»."
341 )]
342 pub magnitude: f64,
343}
344
345/// Attribute codes whose `.mod` key is actually COMPOSED by the runtime.
346///
347/// An ownership bonus is written as `<code>.mod`, and only these codes are read
348/// back — combat composes them in `mechanics::fight::get_entity_stat` (plus `hp`
349/// through `attributes::compose_max_hp`) and the honest power scalar in
350/// `mechanics::balance::get_attr_from_attrs`. A bonus on any other attribute
351/// would be written, stored, shown in no stat and read by nothing: silently
352/// inert content. `GameConfig::validate_artifacts` refuses it instead.
353///
354/// This list lives here, not in `overlord_event_system`, because `configs` is
355/// the lower crate — the dependency only runs one way, so it cannot be derived
356/// from those functions and must be kept in sync with them by hand.
357///
358/// `game_config::tests::mod_capable_codes_cover_the_attributes_the_runtime_composes`
359/// pins the three codes the shipped catalog actually uses for ownership bonuses
360/// and the list's own shape (no duplicates, base codes rather than `.mod` keys).
361/// It deliberately does not assert full coverage: the direction that hurts is a
362/// code listed here that nothing reads (which would let inert content ship), and
363/// a code read by the runtime but missing here only costs a designer the ability
364/// to use it — a visible refusal, not a silent no-op.
365pub const MOD_CAPABLE_ATTRIBUTE_CODES: [&str; 14] = [
366 "armor",
367 "attack",
368 "block",
369 "bravery",
370 "counterattack_chance",
371 "crit_chance",
372 "crit_modifier",
373 "deceit",
374 "evasion",
375 "hp",
376 "multicast_chance",
377 "received_damage",
378 "regeneration_rate",
379 "speed",
380];
381
382/// One stat an artifact pays for being OWNED, in percent of the composed stat.
383///
384/// Mirrors the `StoneStat` idiom (`value` + `value_per_level`) so the two
385/// collections scale the same way and a reader who knows one knows the other.
386/// The difference is the unit: a stone stat is a flat attribute delta, this is a
387/// PERCENT written into `<code>.mod`.
388///
389/// An artifact carries a whole list of these, so "5% hp + 10% armor" is one
390/// template — the shared three-column table this replaced could only ever
391/// express one artifact-wide row per level.
392#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Tsify)]
393#[tsify(from_wasm_abi, into_wasm_abi)]
394pub struct ArtifactOwnershipBonusStat {
395 #[schemars(title = "Id атрибута", schema_with = "attribute_link_id_schema")]
396 pub attribute_id: AttributeId,
397
398 #[schemars(
399 title = "Прибавка на 1 уровне, %",
400 description = "Проценты к атрибуту за владение артефактом 1 уровня."
401 )]
402 pub percent: f64,
403
404 #[schemars(
405 title = "Прибавка за каждый уровень выше первого, %",
406 description = "Итог = процент + процент_за_уровень * (уровень - 1)."
407 )]
408 pub percent_per_level: f64,
409}
410
411impl ArtifactOwnershipBonusStat {
412 /// The percent this stat pays on an artifact of `level`. Level 1 (and any
413 /// nonsense below it) is the base percent.
414 pub fn percent_at_level(&self, level: i64) -> f64 {
415 let levels_above_first = if level > 1 { level - 1 } else { 0 };
416 self.percent + self.percent_per_level * levels_above_first as f64
417 }
418}
419
420/// One entry of the artifact catalog.
421#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Tsify, i18n::Translate)]
422#[tsify(from_wasm_abi, into_wasm_abi)]
423pub struct ArtifactTemplate {
424 #[schemars(schema_with = "schema_loader::id_schema")]
425 pub id: ArtifactTemplateId,
426
427 #[schemars(title = "Название")]
428 #[translate]
429 pub name: i18n::I18nString,
430
431 #[schemars(title = "Описание")]
432 #[translate]
433 pub description: i18n::I18nString,
434
435 /// The welded-in rule, or `None` for an artifact that is a bare frame.
436 #[schemars(
437 title = "World Law",
438 description = "Правило, вшитое в артефакт. Работает только пока артефакт надет. Пусто — артефакт без правила (стартовая «Пустая оправа»)."
439 )]
440 pub world_law: Option<ArtifactWorldLaw>,
441
442 /// What owning this artifact pays, per attribute. Empty = pays nothing.
443 ///
444 /// Fully per-template: any set of attributes, any values. Every owned
445 /// artifact pays — worn or not — which is the one thing that makes a second
446 /// artifact worth having once only one can be worn.
447 #[schemars(
448 title = "Бонусы за владение",
449 description = "Список атрибутов и процентов, которые артефакт даёт за владение. Пусто — артефакт не даёт статов."
450 )]
451 pub ownership_bonuses: Vec<ArtifactOwnershipBonusStat>,
452
453 #[schemars(
454 title = "Иконка",
455 schema_with = "schema_loader::asset_artifact_icon_schema"
456 )]
457 pub icon_path: String,
458
459 #[schemars(
460 title = "Множитель Power World Law",
461 description = "Приблизительный вклад World Law этого артефакта в displayed Power, пока артефакт надет (BAL-030). От ранга не зависит; 1.0 у артефакта без правила."
462 )]
463 pub power_q: f64,
464}
465
466/// One entry of the artifact-stone catalog.
467///
468/// The first copy carries the whole rule; `magnitude_per_level` is the only
469/// thing an upgrade moves (design §3), which is why the rule and `rule_param`
470/// are level-independent.
471#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Tsify, i18n::Translate)]
472#[tsify(from_wasm_abi, into_wasm_abi)]
473pub struct ArtifactStoneTemplate {
474 #[schemars(
475 title = "Множитель Power на первом ранге",
476 description = "Приблизительный вклад в displayed/matchmaking Power (BAL-030). 1.0 = не влияет. Промежуточные ранги интерполируются линейно по ln(q)."
477 )]
478 pub power_q_first_rank: f64,
479
480 #[schemars(
481 title = "Множитель Power на максимальном ранге",
482 description = "Значение того же множителя на последнем ранге. Должен быть не меньше значения на первом."
483 )]
484 pub power_q_max_rank: f64,
485
486 #[schemars(schema_with = "schema_loader::id_schema")]
487 pub id: ArtifactStoneTemplateId,
488
489 #[schemars(title = "Название")]
490 #[translate]
491 pub name: i18n::I18nString,
492
493 #[schemars(title = "Описание")]
494 #[translate]
495 pub description: i18n::I18nString,
496
497 #[schemars(title = "Сокет")]
498 pub socket: ArtifactSocketSlot,
499
500 #[schemars(title = "Правило")]
501 pub rule: ArtifactStoneRule,
502
503 #[schemars(
504 title = "Базовая величина, %",
505 description = "Величина правила на 1-м уровне, в процентах. Правило целиком даётся первой копией; уровень двигает только это число."
506 )]
507 pub magnitude: f64,
508
509 #[schemars(title = "Прибавка величины за уровень прокачки, %")]
510 pub magnitude_per_level: f64,
511
512 #[schemars(
513 title = "Вторая величина правила, %",
514 description = "Второй процент правила: нижняя точка разгона у «Крещендо», сила повтора у «Чередования», потолок у «Сфокусированного мотора», доля шкалы у «Второй половины». Остальным правилам не нужна (0). Прокачкой не меняется."
515 )]
516 pub secondary_magnitude: f64,
517
518 #[schemars(
519 title = "Параметр правила",
520 description = "Период в тиках для «Теневого круга» и «Поочерёдного входа», число срабатываний для «Послеобраза»; для остальных правил не используется (0). Прокачкой не меняется."
521 )]
522 pub rule_param: i64,
523
524 #[schemars(
525 title = "Сила по тирам триггера",
526 description = "Только для «Мелких шестерён» (VA-05). У остальных камней список пустой."
527 )]
528 pub tier_magnitudes: Vec<ArtifactTierMagnitude>,
529
530 #[schemars(
531 title = "Правило включено",
532 description = "Выключенный камень остаётся в каталоге со своими числами, но рантайм его не исполняет. Так заведены правила, которым нужна ещё не отгруженная механика."
533 )]
534 pub active: bool,
535
536 #[schemars(
537 title = "Иконка",
538 schema_with = "schema_loader::asset_artifact_stone_icon_schema"
539 )]
540 pub icon_path: String,
541}
542
543impl ArtifactStoneTemplate {
544 /// The rule's magnitude on a stone of upgrade level `level`, in percent.
545 /// Same shape as `configs::stones::StoneStat::value_at_level`, so both
546 /// systems scale with a level identically.
547 pub fn magnitude_at_level(&self, level: i64) -> f64 {
548 let levels_above_first = (level - 1).max(0) as f64;
549 self.magnitude + self.magnitude_per_level * levels_above_first
550 }
551
552 /// `VA-05 Small Gears` only: the share effects run at when the trigger that
553 /// fired them had `tier`. An unlisted tier reads as plain strength rather
554 /// than as zero, so a half-filled table can never silence a build.
555 pub fn tier_magnitude(&self, tier: StoneTier) -> f64 {
556 self.tier_magnitudes
557 .iter()
558 .find(|entry| entry.tier == tier)
559 .map_or(100.0, |entry| entry.magnitude)
560 }
561}
562
563/// Ownership stat bonus of ONE artifact at a given level, in percent.
564///
565/// One rung of an upgrade ladder: how many raw copies buy `level`.
566///
567/// Used by both ladders — artifacts and artifact stones. In both cases a
568/// duplicate banks a raw copy and an explicit player action spends the rung.
569#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Tsify)]
570#[tsify(from_wasm_abi, into_wasm_abi)]
571pub struct ArtifactUpgradeStep {
572 #[schemars(
573 title = "Уровень, который покупается",
574 description = "Лестница начинается со 2-го уровня и должна доходить до максимального без пропусков."
575 )]
576 pub level: i64,
577
578 #[schemars(
579 title = "Сколько сырых копий нужно",
580 description = "Сам предмет в это число не входит. Копии считаются штуками."
581 )]
582 pub copies: i64,
583}
584
585/// When one artifact socket opens.
586///
587/// The order is fixed by design — the whole Aspect column before the Law column
588/// — and `GameConfig::validate_artifacts` enforces it.
589///
590/// The three Law sockets used to be scheduled past the end of the campaign,
591/// because their stones had no laws to retune. The cores vertical shipped, so
592/// they carry real chapters now; the shipped schedule continues the left
593/// column's four-chapter cadence (see `artifacts_settings/_data.yaml` for the
594/// numbers and the reasoning).
595#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Tsify)]
596#[tsify(from_wasm_abi, into_wasm_abi)]
597pub struct ArtifactSocketUnlock {
598 #[schemars(title = "Сокет")]
599 pub socket: ArtifactSocketSlot,
600
601 #[schemars(
602 title = "Глава разблокировки",
603 description = "BAL-007: подписанные главы 91/111/131/151/171/191. Порядок фиксирован: сначала вся левая колонка (Aspect), потом правая (Law)."
604 )]
605 pub unlock_chapter: i64,
606}
607
608/// BAL-031 source: where artifact stones drop from (signed rates).
609///
610/// Deliberately a different faucet from the artifacts themselves (design §5), so
611/// the two collections never compete for the same drop.
612#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Tsify)]
613#[tsify(from_wasm_abi, into_wasm_abi)]
614pub struct ArtifactStoneDropSettings {
615 #[schemars(
616 title = "Шанс камня артефакта с босса главы",
617 description = "Доля в [0,1]. BAL-031: подписанное значение 0.02."
618 )]
619 pub chapter_boss_chance: f64,
620
621 #[schemars(
622 title = "Шанс камня артефакта за пройденное подземелье",
623 description = "Доля в [0,1]. BAL-031: подписанное значение 0.15."
624 )]
625 pub dungeon_chance: f64,
626
627 /// The third faucet, added by the acquisition pass: a small per-kill
628 /// chance alongside the boss and dungeon drops, so the collection has a
629 /// steady trickle instead of moving only on gate events. Scaled by the
630 /// dead mob's `wave_share` like every other per-kill payout.
631 #[schemars(
632 title = "Шанс камня артефакта с убийства моба",
633 description = "Доля в [0,1], домножается на wave_share убитого моба. Дополняет дроп с босса и подземелья, а не заменяет его."
634 )]
635 pub mob_kill_chance: f64,
636}
637
638/// All artifact tunables.
639#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Tsify)]
640#[tsify(from_wasm_abi, into_wasm_abi)]
641pub struct ArtifactsSettings {
642 /// Chapter at which the artifact navigation vertical becomes visible.
643 ///
644 /// UI only — it gates NOTHING about ownership. Since the acquisition pass
645 /// there is no per-template chapter gate at all: the starter is granted at
646 /// character creation, and every other artifact is a bundle reward (today,
647 /// the top-1 weekly rating bundles).
648 #[schemars(
649 title = "Глава открытия вертикали",
650 description = "Глава открытия навигации артефактов. Только UI: владение артефактами от неё не зависит."
651 )]
652 pub unlock_chapter: i64,
653
654 /// The guaranteed freebie. It exists so the stone system does not depend on
655 /// a rare drop, so it must always resolve to a real catalog entry —
656 /// `GameConfig::validate_artifacts` checks that.
657 #[schemars(
658 title = "Стартовый артефакт",
659 schema_with = "schema_loader::artifact_link_id_schema"
660 )]
661 pub starter_artifact_id: ArtifactTemplateId,
662
663 #[schemars(title = "Максимальный уровень артефакта")]
664 pub max_artifact_level: i64,
665
666 #[schemars(
667 title = "Лестница уровней артефакта: сырых копий за уровень",
668 description = "Явное улучшение тратит ровно стоимость следующего уровня. Получение дубликата только добавляет сырую копию, в том числе на максимальном уровне."
669 )]
670 pub upgrade_ladder: Vec<ArtifactUpgradeStep>,
671
672 #[schemars(title = "Расписание открытия сокетов")]
673 pub socket_unlocks: Vec<ArtifactSocketUnlock>,
674
675 #[schemars(title = "Максимальный уровень камня артефакта")]
676 pub max_stone_level: i64,
677
678 #[schemars(title = "Лестница прокачки камня артефакта: сырых копий за уровень")]
679 pub stone_upgrade_ladder: Vec<ArtifactUpgradeStep>,
680
681 /// `ART-03 Halfway Bell`'s only number, and deliberately a **share** rather
682 /// than an absolute threshold: `FlipSettings::progress_threshold` is a
683 /// balance knob that moves (the stones vertical rescales it), and a hard
684 /// number here would silently stop meaning "halfway" the moment it did.
685 #[schemars(
686 title = "Доля порога флипа под «Колоколом на полпути»",
687 description = "Доля обычного порога, на которой происходит Global Flip у носителя ART-03. 0.6 — «на 60% порога». Остальные источники Gauge не меняются."
688 )]
689 pub halfway_bell_threshold_share: f64,
690
691 /// `ART-02 Thin Mirror`'s only number: the share of Law Power a hidden law
692 /// woken by the World Law applies its effect at.
693 #[schemars(
694 title = "Доля силы скрытых законов под «Тонким зеркалом»",
695 description = "0.3 — скрытые законы применяют эффект на 30% силы. Скрытая активация не создаёт ни резонанс, ни заряд моста, ни шкалу флипа."
696 )]
697 pub thin_mirror_share: f64,
698
699 #[schemars(title = "ЗАГЛУШКА: источники камней артефакта")]
700 pub stone_drop: ArtifactStoneDropSettings,
701}
702
703impl ArtifactsSettings {
704 /// Whether `socket` is open at `chapter_level`. An unscheduled socket never
705 /// opens; `GameConfig::validate_artifacts` requires all six to be scheduled,
706 /// so this is a safety net rather than a reachable state.
707 pub fn is_socket_unlocked(&self, socket: ArtifactSocketSlot, chapter_level: i64) -> bool {
708 self.socket_unlocks
709 .iter()
710 .find(|unlock| unlock.socket == socket)
711 .is_some_and(|unlock| chapter_level >= unlock.unlock_chapter)
712 }
713
714 /// The share of the configured flip threshold `law` moves the bar to.
715 ///
716 /// `1.0` for every law but `ART-03`, so the caller never has to know which
717 /// law is which — and a nonsense configured share reads as "no change"
718 /// rather than as an instant or unreachable flip.
719 pub fn flip_threshold_share(&self, law: Option<ArtifactWorldLaw>) -> f64 {
720 match law {
721 Some(ArtifactWorldLaw::HalfwayBell) => {
722 let share = self.halfway_bell_threshold_share;
723 if share.is_finite() && share > 0.0 && share <= 1.0 {
724 share
725 } else {
726 1.0
727 }
728 }
729 Some(ArtifactWorldLaw::ThinMirror) | None => 1.0,
730 }
731 }
732
733 /// The share of Law Power hidden laws run at under `ART-02 Thin Mirror`, or
734 /// `0.0` for every other worn artifact (and for nothing worn).
735 ///
736 /// A nonsense configured share reads as "no wake" rather than as a free
737 /// full-strength second build — the conservative direction, since this rule
738 /// switches on half the build.
739 pub fn thin_mirror_share(&self, law: Option<ArtifactWorldLaw>) -> f64 {
740 match law {
741 Some(ArtifactWorldLaw::ThinMirror) => {
742 let share = self.thin_mirror_share;
743 if share.is_finite() && share > 0.0 && share <= 1.0 {
744 share
745 } else {
746 0.0
747 }
748 }
749 Some(ArtifactWorldLaw::HalfwayBell) | None => 0.0,
750 }
751 }
752
753 /// Raw copies needed to take an artifact **to** `target_level`. `None` means
754 /// the ladder has no such rung — at the cap, or a config gap that
755 /// validation rules out at load time.
756 pub fn upgrade_copies_required(&self, target_level: i64) -> Option<i64> {
757 self.upgrade_ladder
758 .iter()
759 .find(|step| step.level == target_level)
760 .map(|step| step.copies)
761 }
762
763 /// Same, for an artifact stone.
764 pub fn stone_upgrade_copies_required(&self, target_level: i64) -> Option<i64> {
765 self.stone_upgrade_ladder
766 .iter()
767 .find(|step| step.level == target_level)
768 .map(|step| step.copies)
769 }
770
771 /// The unlock chapter of the first socket of `column`, used by validation
772 /// and by tests to state the "left column first" rule without repeating the
773 /// slot list.
774 pub fn earliest_unlock_of(&self, column: ArtifactSocketColumn) -> Option<i64> {
775 self.socket_unlocks
776 .iter()
777 .filter(|unlock| unlock.socket.column() == column)
778 .map(|unlock| unlock.unlock_chapter)
779 .min()
780 }
781}
782
783#[cfg(test)]
784mod tests {
785 use super::*;
786
787 fn settings() -> ArtifactsSettings {
788 ArtifactsSettings {
789 unlock_chapter: 30,
790 starter_artifact_id: uuid::Uuid::from_u128(1),
791 max_artifact_level: 3,
792 upgrade_ladder: vec![
793 ArtifactUpgradeStep {
794 level: 2,
795 copies: 1,
796 },
797 ArtifactUpgradeStep {
798 level: 3,
799 copies: 2,
800 },
801 ],
802 socket_unlocks: vec![
803 ArtifactSocketUnlock {
804 socket: ArtifactSocketSlot::VisibleAspect,
805 unlock_chapter: 30,
806 },
807 ArtifactSocketUnlock {
808 socket: ArtifactSocketSlot::HiddenAspect,
809 unlock_chapter: 34,
810 },
811 ArtifactSocketUnlock {
812 socket: ArtifactSocketSlot::FlipAspect,
813 unlock_chapter: 38,
814 },
815 ArtifactSocketUnlock {
816 socket: ArtifactSocketSlot::VisibleLaw,
817 unlock_chapter: 42,
818 },
819 ArtifactSocketUnlock {
820 socket: ArtifactSocketSlot::HiddenLaw,
821 unlock_chapter: 46,
822 },
823 ArtifactSocketUnlock {
824 socket: ArtifactSocketSlot::BridgeLaw,
825 unlock_chapter: 50,
826 },
827 ],
828 max_stone_level: 5,
829 stone_upgrade_ladder: vec![ArtifactUpgradeStep {
830 level: 2,
831 copies: 2,
832 }],
833 halfway_bell_threshold_share: 0.6,
834 thin_mirror_share: 0.3,
835 stone_drop: ArtifactStoneDropSettings {
836 chapter_boss_chance: 0.1,
837 dungeon_chance: 0.2,
838 mob_kill_chance: 0.0,
839 },
840 }
841 }
842
843 /// The whole Aspect column opens before the first Law socket does, and every
844 /// Law socket now opens at a chapter a player can actually reach.
845 #[test]
846 fn the_aspect_column_opens_before_the_law_column() {
847 let settings = settings();
848 assert!(settings.is_socket_unlocked(ArtifactSocketSlot::VisibleAspect, 30));
849 assert!(!settings.is_socket_unlocked(ArtifactSocketSlot::FlipAspect, 30));
850 assert!(settings.is_socket_unlocked(ArtifactSocketSlot::FlipAspect, 38));
851
852 for law in [
853 ArtifactSocketSlot::VisibleLaw,
854 ArtifactSocketSlot::HiddenLaw,
855 ArtifactSocketSlot::BridgeLaw,
856 ] {
857 assert!(
858 !settings.is_socket_unlocked(law, 38),
859 "{law} must not open before the last Aspect socket"
860 );
861 assert!(
862 settings.is_socket_unlocked(law, 50),
863 "{law} must be reachable"
864 );
865 }
866 assert_eq!(
867 settings.earliest_unlock_of(ArtifactSocketColumn::Law),
868 Some(42)
869 );
870 }
871
872 #[test]
873 fn each_ladder_prices_its_own_levels() {
874 let settings = settings();
875 assert_eq!(settings.upgrade_copies_required(2), Some(1));
876 assert_eq!(settings.upgrade_copies_required(3), Some(2));
877 assert_eq!(settings.upgrade_copies_required(4), None);
878 assert_eq!(settings.stone_upgrade_copies_required(2), Some(2));
879 }
880
881 /// Acceptance criterion 9: a level moves the magnitude and nothing else.
882 #[test]
883 fn a_stone_level_scales_only_the_magnitude() {
884 let template = ArtifactStoneTemplate {
885 id: uuid::Uuid::from_u128(2),
886 name: i18n::I18nString::Translated(String::new()),
887 description: i18n::I18nString::Translated(String::new()),
888 socket: ArtifactSocketSlot::FlipAspect,
889 rule: ArtifactStoneRule::StaggeredEntrance,
890 magnitude: 70.0,
891 magnitude_per_level: 10.0,
892 secondary_magnitude: 0.0,
893 rule_param: 500,
894 tier_magnitudes: Vec::new(),
895 power_q_first_rank: 1.03,
896 power_q_max_rank: 1.06,
897 active: true,
898 icon_path: String::new(),
899 };
900
901 assert_eq!(template.magnitude_at_level(1), 70.0);
902 assert_eq!(template.magnitude_at_level(3), 90.0);
903 assert_eq!(template.magnitude_at_level(0), 70.0);
904 assert_eq!(
905 template.rule_param, 500,
906 "the rule's own parameter is level-independent"
907 );
908 assert_eq!(
909 template.secondary_magnitude, 0.0,
910 "and so is the second percentage"
911 );
912 }
913
914 /// `VA-05` carries its whole tier table in the catalog even while switched
915 /// off, and an unlisted tier reads as plain strength.
916 #[test]
917 fn the_tier_table_is_read_per_tier_and_defaults_to_plain() {
918 let template = ArtifactStoneTemplate {
919 id: uuid::Uuid::from_u128(3),
920 name: i18n::I18nString::Translated(String::new()),
921 description: i18n::I18nString::Translated(String::new()),
922 socket: ArtifactSocketSlot::VisibleAspect,
923 rule: ArtifactStoneRule::SmallGears,
924 magnitude: 130.0,
925 magnitude_per_level: 5.0,
926 secondary_magnitude: 0.0,
927 rule_param: 0,
928 tier_magnitudes: vec![
929 ArtifactTierMagnitude {
930 tier: StoneTier::Common,
931 magnitude: 130.0,
932 },
933 ArtifactTierMagnitude {
934 tier: StoneTier::Rare,
935 magnitude: 115.0,
936 },
937 ],
938 power_q_first_rank: 1.03,
939 power_q_max_rank: 1.06,
940 active: false,
941 icon_path: String::new(),
942 };
943
944 assert_eq!(template.tier_magnitude(StoneTier::Common), 130.0);
945 assert_eq!(template.tier_magnitude(StoneTier::Rare), 115.0);
946 assert_eq!(
947 template.tier_magnitude(StoneTier::Legendary),
948 100.0,
949 "an unlisted tier is plain strength, never silence"
950 );
951 }
952
953 #[test]
954 fn every_rule_declares_the_socket_it_belongs_to() {
955 use ArtifactStoneRule::*;
956 for rule in [
957 Crescendo,
958 Alternator,
959 FocusedEngine,
960 OpeningFive,
961 SmallGears,
962 ] {
963 assert_eq!(rule.socket(), ArtifactSocketSlot::VisibleAspect);
964 }
965 for rule in [
966 BackgroundVoice,
967 ShadowRound,
968 CrossRelay,
969 SecondHalf,
970 CatchUp,
971 ] {
972 assert_eq!(rule.socket(), ArtifactSocketSlot::HiddenAspect);
973 }
974 for rule in [
975 IncomingSalvo,
976 LastWord,
977 WarmStart,
978 Afterimage,
979 StaggeredEntrance,
980 ] {
981 assert_eq!(rule.socket(), ArtifactSocketSlot::FlipAspect);
982 }
983 for rule in [
984 SourceFocus,
985 CurrentPower,
986 ExportPower,
987 FullConfidence,
988 FirstArticle,
989 ] {
990 assert_eq!(rule.socket(), ArtifactSocketSlot::VisibleLaw);
991 }
992 for rule in [
993 ProxyReader,
994 LinkedEcho,
995 LateAwakening,
996 SingleLesson,
997 ReadyTarget,
998 ] {
999 assert_eq!(rule.socket(), ArtifactSocketSlot::HiddenLaw);
1000 }
1001 for rule in [ExtraSpan, DeepSpan, ShortSpan, ReciprocalGate] {
1002 assert_eq!(rule.socket(), ArtifactSocketSlot::BridgeLaw);
1003 }
1004 }
1005
1006 /// Nothing ships blocked any more: every left-column dependency landed with
1007 /// the stones v0.2 merge, and the right column's landed with the cores
1008 /// vertical. The guard itself stays for the next rule entered ahead of its
1009 /// mechanic.
1010 #[test]
1011 fn no_rule_declares_a_missing_dependency() {
1012 use ArtifactStoneRule::*;
1013 for rule in [
1014 Crescendo,
1015 Alternator,
1016 FocusedEngine,
1017 OpeningFive,
1018 SmallGears,
1019 BackgroundVoice,
1020 ShadowRound,
1021 CrossRelay,
1022 SecondHalf,
1023 CatchUp,
1024 IncomingSalvo,
1025 LastWord,
1026 WarmStart,
1027 Afterimage,
1028 StaggeredEntrance,
1029 SourceFocus,
1030 CurrentPower,
1031 ExportPower,
1032 FullConfidence,
1033 FirstArticle,
1034 ProxyReader,
1035 LinkedEcho,
1036 LateAwakening,
1037 SingleLesson,
1038 ReadyTarget,
1039 ExtraSpan,
1040 DeepSpan,
1041 ShortSpan,
1042 ReciprocalGate,
1043 ] {
1044 assert_eq!(rule.unavailable_dependency(), None, "{rule:?} runs today");
1045 }
1046 }
1047
1048 /// Which rules are worthless without a chosen law, and which wake a law of
1049 /// the side that is down. Both predicates gate real behaviour, so a rule
1050 /// added to the wrong list is a silently dead or a silently doubled stone.
1051 #[test]
1052 fn the_law_column_declares_which_rules_need_a_target_and_which_wake_hidden_laws() {
1053 use ArtifactStoneRule::*;
1054 for rule in [
1055 SourceFocus,
1056 CurrentPower,
1057 ExportPower,
1058 FullConfidence,
1059 ProxyReader,
1060 LinkedEcho,
1061 LateAwakening,
1062 SingleLesson,
1063 ReadyTarget,
1064 ReciprocalGate,
1065 ] {
1066 assert!(rule.needs_law_target(), "{rule:?} addresses one law");
1067 }
1068 // The build-wide rules: VL-05 changes the rhythm of a phase, BL-01..03
1069 // move every bridge.
1070 for rule in [FirstArticle, ExtraSpan, DeepSpan, ShortSpan, Crescendo] {
1071 assert!(!rule.needs_law_target(), "{rule:?} addresses no single law");
1072 }
1073
1074 for rule in [
1075 ProxyReader,
1076 LinkedEcho,
1077 LateAwakening,
1078 SingleLesson,
1079 ReadyTarget,
1080 ] {
1081 assert!(rule.wakes_hidden_law(), "{rule:?} is a Hidden Law rule");
1082 }
1083 for rule in [
1084 SourceFocus,
1085 CurrentPower,
1086 ExportPower,
1087 FullConfidence,
1088 FirstArticle,
1089 ExtraSpan,
1090 DeepSpan,
1091 ShortSpan,
1092 ReciprocalGate,
1093 BackgroundVoice,
1094 ] {
1095 assert!(
1096 !rule.wakes_hidden_law(),
1097 "{rule:?} must never produce a hidden law activation"
1098 );
1099 }
1100 }
1101
1102 /// `ART-03` moves the bar by a share of whatever threshold is configured;
1103 /// every other worn artifact leaves it alone, and a nonsense share is
1104 /// ignored rather than turned into an instant flip.
1105 #[test]
1106 fn the_halfway_bell_share_applies_only_to_its_own_law() {
1107 let mut settings = settings();
1108 assert_eq!(settings.flip_threshold_share(None), 1.0);
1109 assert_eq!(
1110 settings.flip_threshold_share(Some(ArtifactWorldLaw::ThinMirror)),
1111 1.0,
1112 "the other artifact does not touch the bar"
1113 );
1114 assert_eq!(
1115 settings.flip_threshold_share(Some(ArtifactWorldLaw::HalfwayBell)),
1116 0.6
1117 );
1118
1119 for broken in [0.0, -1.0, 1.5, f64::NAN] {
1120 settings.halfway_bell_threshold_share = broken;
1121 assert_eq!(
1122 settings.flip_threshold_share(Some(ArtifactWorldLaw::HalfwayBell)),
1123 1.0,
1124 "a broken share must read as 'no change'"
1125 );
1126 }
1127 }
1128
1129 /// `ART-02` wakes hidden laws only for its own wearer, and a nonsense share
1130 /// switches the rule OFF rather than granting a free second build.
1131 #[test]
1132 fn the_thin_mirror_share_applies_only_to_its_own_law() {
1133 let mut settings = settings();
1134 assert_eq!(settings.thin_mirror_share(None), 0.0);
1135 assert_eq!(
1136 settings.thin_mirror_share(Some(ArtifactWorldLaw::HalfwayBell)),
1137 0.0
1138 );
1139 assert_eq!(
1140 settings.thin_mirror_share(Some(ArtifactWorldLaw::ThinMirror)),
1141 0.3
1142 );
1143
1144 for broken in [0.0, -1.0, 1.5, f64::NAN] {
1145 settings.thin_mirror_share = broken;
1146 assert_eq!(
1147 settings.thin_mirror_share(Some(ArtifactWorldLaw::ThinMirror)),
1148 0.0,
1149 "a broken share must switch the rule off, never open it up"
1150 );
1151 }
1152 }
1153}