Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions PR_6499.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Closes #6499

## Summary

Discord report: **Flickering Ward** could not stay attached after choosing a color.

Oracle: `Enchanted creature has protection from the chosen color. This effect doesn't remove this Aura.`

Choosing white (the Aura's color) grants protection from white. Without the CR 702.16n rider, SBA CR 704.5m / CR 702.16c puts the Aura in the graveyard — which matches the report.

## Root cause

Coverage claimed the protection grant was supported, but the parser deliberately dropped `"This effect doesn't remove this Aura"` as inert prose so the keyword would parse. The exemption was never modeled, so runtime treated the grant like ordinary protection.

## Changes

- **`ProtectionDoesNotRemove`** on `StaticDefinition` — `Source` / `Auras` / `ControlledAttachmentsAlreadyAttached` (CR 702.16n / 702.16p class).
- **Parser** — recover the rider onto the continuous static (`parse_protection_does_not_remove`); still strip it from the keyword leg so `Protection(ChosenColor)` parses cleanly. Source matches both `"this Aura"` and post-normalization `~`.
- **Runtime** — `attachment_illegality` walks continuous protection grants and honors per-grant exemptions (other protection instances still apply).
- **Tests** — parse Source exemption; choose white → Aura stays attached; printed protection without rider still blocks.

## Test Plan

- [ ] `cargo fmt --all -- --check`
- [ ] `cargo test -p engine --lib -- protection_chosen_color_drops_trailing_this_aura`
- [ ] `cargo test -p engine --test integration -- flickering_ward`
2 changes: 2 additions & 0 deletions crates/engine/src/ai_support/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4809,6 +4809,7 @@ mod tests {
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
};
obj.static_definitions = vec![def].into();
}
Expand Down Expand Up @@ -4931,6 +4932,7 @@ mod tests {
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
};
obj.static_definitions = vec![def].into();
}
Expand Down
4 changes: 4 additions & 0 deletions crates/engine/src/game/casting_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3467,6 +3467,7 @@ fn granted_freerunning_static_surfaces_freerunning_variant() {
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
};
obj.static_definitions = vec![def].into();
}
Expand Down Expand Up @@ -11960,6 +11961,7 @@ fn x_cost_max_accounts_for_granted_affinity_exceeding_fixed_generic() {
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
}]
.into();
}
Expand Down Expand Up @@ -14737,6 +14739,7 @@ fn witherbloom_grants_affinity_to_instant_and_sorcery_spells() {
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
};
obj.static_definitions = vec![def].into();
}
Expand Down Expand Up @@ -14854,6 +14857,7 @@ fn add_witherbloom_affinity_source(state: &mut GameState, player: PlayerId) -> O
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
}]
.into();
}
Expand Down
12 changes: 12 additions & 0 deletions crates/engine/src/game/coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12178,6 +12178,7 @@ mod tests {
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
}],
duration: Some(Duration::UntilEndOfTurn),
target: None,
Expand Down Expand Up @@ -12225,6 +12226,7 @@ mod tests {
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
}],
duration: Some(Duration::UntilEndOfTurn),
target: None,
Expand Down Expand Up @@ -13360,6 +13362,7 @@ mod tests {
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
});

assert!(audit_card_lines(oracle, &face).is_empty());
Expand Down Expand Up @@ -13394,6 +13397,7 @@ mod tests {
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
});

assert!(audit_card_lines(oracle, &face).is_empty());
Expand Down Expand Up @@ -13426,6 +13430,7 @@ mod tests {
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
});

let findings = audit_card_lines(oracle, &face);
Expand Down Expand Up @@ -13576,6 +13581,7 @@ mod tests {
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
});

assert!(
Expand Down Expand Up @@ -13608,6 +13614,7 @@ mod tests {
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
});

assert!(
Expand Down Expand Up @@ -13650,6 +13657,7 @@ mod tests {
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
});

let gaps = card_face_gaps(&face);
Expand Down Expand Up @@ -13683,6 +13691,7 @@ mod tests {
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
});

let gaps = card_face_gaps(&face);
Expand Down Expand Up @@ -13718,6 +13727,7 @@ mod tests {
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
});

let gaps = card_face_gaps(&face);
Expand Down Expand Up @@ -13759,6 +13769,7 @@ mod tests {
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
});
}

Expand Down Expand Up @@ -13927,6 +13938,7 @@ mod tests {
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
});

assert!(
Expand Down
1 change: 1 addition & 0 deletions crates/engine/src/game/dungeon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,7 @@ pub fn room_effects(
source_controller: None,
source_object: None,
bypass_beneficiary: None,
protection_does_not_remove: None,
}],
triggers: Vec::new(),
},
Expand Down
Loading
Loading