Skip to content
Merged
148 changes: 96 additions & 52 deletions crates/engine/src/database/synthesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7217,44 +7217,64 @@ pub fn synthesize_sunburst(face: &mut CardFace) {
.filter(|r| is_sunburst_etb_replacement(r, &counter_type))
.count();

let counter_phrase = match &counter_type {
for _ in existing..instances {
face.replacements
.push(sunburst_replacement_definition(&counter_type));
}
}

/// CR 702.44a + CR 702.44d + CR 601.2h: the single per-instance Sunburst ETB
/// replacement definition — one `Moved`→Battlefield replacement on `SelfRef`
/// whose execute places one counter of `counter_type` per distinct color of
/// mana spent to cast the object.
///
/// The single authority for building a Sunburst replacement, shared by:
/// - build-time synthesis (`synthesize_sunburst`) for PRINTED Sunburst, and
/// - the runtime granted-keyword replacement path
/// (`granted_sunburst_replacements` → `find_applicable_replacements`), which
/// surfaces one virtual candidate per *granted* Sunburst instance so a grant
/// ("that spell gains sunburst": Solar Array / Lux Artillery) also places
/// counters at entry (#5337).
///
/// Because both callers build identical definitions, printed + granted
/// instances each yield a distinct candidate and apply separately, exactly as
/// CR 702.44d ("If an object has multiple instances of sunburst, each one works
/// separately") requires.
pub(crate) fn sunburst_replacement_definition(counter_type: &CounterType) -> ReplacementDefinition {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
let counter_phrase = match counter_type {
CounterType::Plus1Plus1 => "+1/+1",
_ => "charge",
};

for _ in existing..instances {
let etb_counters = AbilityDefinition::new(
AbilityKind::Spell,
Effect::PutCounter {
counter_type: counter_type.clone(),
// CR 702.44a + CR 601.2h: one counter per *color* (max 5) of mana
// spent to cast this object — the distinct-colors metric, not the
// total amount.
count: QuantityExpr::Ref {
qty: QuantityRef::ManaSpentToCast {
scope: CastManaObjectScope::SelfObject,
metric: CastManaSpentMetric::DistinctColors,
},
let etb_counters = AbilityDefinition::new(
AbilityKind::Spell,
Effect::PutCounter {
counter_type: counter_type.clone(),
// CR 702.44a + CR 601.2h: one counter per *color* (max 5) of mana
// spent to cast this object — the distinct-colors metric, not the
// total amount.
count: QuantityExpr::Ref {
qty: QuantityRef::ManaSpentToCast {
scope: CastManaObjectScope::SelfObject,
metric: CastManaSpentMetric::DistinctColors,
},
target: TargetFilter::SelfRef,
},
)
.description(format!(
"This permanent enters with a {counter_phrase} counter on it for each color of mana spent to cast it"
));
target: TargetFilter::SelfRef,
},
)
.description(format!(
"This permanent enters with a {counter_phrase} counter on it for each color of mana spent to cast it"
));

let replacement = ReplacementDefinition {
event: ReplacementEvent::Moved,
execute: Some(Box::new(etb_counters)),
valid_card: Some(TargetFilter::SelfRef),
// CR 614.1c: battlefield-entry-scoped (departure gate).
destination_zone: Some(Zone::Battlefield),
description: Some(format!(
"CR 702.44a: Sunburst — this permanent enters with a {counter_phrase} counter on it for each color of mana spent to cast it."
)),
..ReplacementDefinition::new(ReplacementEvent::Moved)
};
face.replacements.push(replacement);
ReplacementDefinition {
event: ReplacementEvent::Moved,
execute: Some(Box::new(etb_counters)),
valid_card: Some(TargetFilter::SelfRef),
// CR 614.1c: battlefield-entry-scoped (departure gate).
destination_zone: Some(Zone::Battlefield),
description: Some(format!(
"CR 702.44a: Sunburst — this permanent enters with a {counter_phrase} counter on it for each color of mana spent to cast it."
)),
..ReplacementDefinition::new(ReplacementEvent::Moved)
}
}

Expand Down Expand Up @@ -8115,27 +8135,51 @@ pub fn synthesize_bloodthirst(face: &mut CardFace) {
if existing >= needed {
continue;
}
let etb_counters = AbilityDefinition::new(
AbilityKind::Spell,
Effect::PutCounter {
counter_type: CounterType::Plus1Plus1,
count: bloodthirst_counter_quantity(value),
target: TargetFilter::SelfRef,
},
)
.description(bloodthirst_execute_description(value));
face.replacements
.push(bloodthirst_replacement_definition(value));
}
}

let replacement = ReplacementDefinition {
event: ReplacementEvent::Moved,
execute: Some(Box::new(etb_counters)),
valid_card: Some(TargetFilter::SelfRef),
condition: bloodthirst_condition(value),
// CR 614.1c: battlefield-entry-scoped (departure gate).
destination_zone: Some(Zone::Battlefield),
description: Some(bloodthirst_replacement_description(value)),
..ReplacementDefinition::new(ReplacementEvent::Moved)
};
face.replacements.push(replacement);
/// CR 702.54a + CR 702.54b + CR 702.54c: the single per-instance Bloodthirst ETB
/// replacement definition — one `Moved`→Battlefield replacement on `SelfRef`
/// whose execute places `bloodthirst_counter_quantity(value)` +1/+1 counters,
/// gated by `bloodthirst_condition(value)` (the fixed-N form is conditional on an
/// opponent having been dealt damage this turn; the X form is unconditional and
/// its count reads the damage total directly).
///
/// The single authority for building a Bloodthirst replacement, shared by:
/// - build-time synthesis (`synthesize_bloodthirst`) for PRINTED Bloodthirst, and
/// - the runtime granted-keyword replacement path
/// (`granted_bloodthirst_instances` → `find_applicable_replacements`), which
/// surfaces one virtual candidate per *granted* Bloodthirst instance so a grant
/// ("it gains bloodthirst 3": Bloodlord of Vaasgoth) also places counters at
/// entry (mirrors `sunburst_replacement_definition`, #5802).
///
/// Because both callers build identical definitions, printed + granted instances
/// each yield a distinct candidate and apply separately per CR 702.54c ("each
/// instance works separately").
pub(crate) fn bloodthirst_replacement_definition(
value: &BloodthirstValue,
) -> ReplacementDefinition {
let etb_counters = AbilityDefinition::new(
AbilityKind::Spell,
Effect::PutCounter {
counter_type: CounterType::Plus1Plus1,
count: bloodthirst_counter_quantity(value),
target: TargetFilter::SelfRef,
},
)
.description(bloodthirst_execute_description(value));

ReplacementDefinition {
event: ReplacementEvent::Moved,
execute: Some(Box::new(etb_counters)),
valid_card: Some(TargetFilter::SelfRef),
condition: bloodthirst_condition(value),
// CR 614.1c: battlefield-entry-scoped (departure gate).
destination_zone: Some(Zone::Battlefield),
description: Some(bloodthirst_replacement_description(value)),
..ReplacementDefinition::new(ReplacementEvent::Moved)
}
}

Expand Down
Loading
Loading