Skip to content

feat(keywords): implement Reinforce keyword (CR 702.77a) - #12

Closed
Whovencroft wants to merge 1 commit into
mainfrom
card/reinforce-keyword
Closed

feat(keywords): implement Reinforce keyword (CR 702.77a)#12
Whovencroft wants to merge 1 commit into
mainfrom
card/reinforce-keyword

Conversation

@Whovencroft

Copy link
Copy Markdown
Owner

What

Implement the Reinforce keyword (CR 702.77a) end-to-end: parsing, deserialization, Oracle text recognition, and activated-ability synthesis.

Why

Reinforce is a gap-1 keyword — implementing it unlocks 5 cards that have no other unimplemented mechanics:

  • Earthbrawn
  • Hunting Triad
  • Break Ties
  • Fowl Strike
  • Swell of Courage

How

crates/engine/src/types/keywords.rs

  • Added Keyword::Reinforce { count: u32, cost: ManaCost } variant
  • Added to kind() catch-all list
  • Added FromStr parsing for the "reinforce N {cost}" format (MTGJSON string representation)
  • Added keyword_from_tagged deserialization for the { "count": N, "cost": "..." } JSON shape

crates/engine/src/parser/oracle_keyword.rs

  • Added Oracle text parser for "reinforce N—{cost}" using nom::bytes::complete::tag combinator
  • Em-dash separators use strip_prefix with allow-noncombinator annotations (punctuation, not parsing dispatch)
  • Added keyword_display_name arm for Keyword::Reinforce

crates/engine/src/database/synthesis.rs

  • Added synthesize_reinforce() function that produces an activated ability:
    • Cost: Composite { Mana { cost }, Discard { count: 1, self_ref: true } }
    • Effect: PutCounter { Plus1Plus1, Fixed(N), target: Creature }
    • Zone: activation_zone = Some(Zone::Hand) (activated from hand, discard as cost)
  • Added call in synthesize_all()
  • Added reinforce_synthesis_tests module (4 tests)

Tests

  • cargo test -p engine --lib -- reinforce → 4 new tests pass
  • cargo test -p engine --lib -- synthesis_tests → 135 pass (no regressions)
  • cargo test -p engine --lib -- keyword → 364 pass (no regressions)
  • cargo test -p engine --lib -- oracle_keyword → 73 pass (no regressions)
  • cargo test -p engine --lib -- coverage → 67 pass (no regressions)
  • cargo fmt -- --check → clean
  • cargo clippy -p engine → clean
  • bash scripts/check-parser-combinators.sh → EXIT 0

CR Reference

CR 702.77a — "Reinforce N—[cost]" means "[Cost], Discard this card: Put N +1/+1 counters on target creature." A player activates a reinforce ability only while the card with reinforce is in their hand.

Cards Unlocked

Card Set Reinforce Cost
Earthbrawn MOR Reinforce 1—{1}{G}
Hunting Triad MOR Reinforce 3—{3}{G}
Break Ties MOR Reinforce 1—{1}{W}
Fowl Strike MOR Reinforce 2—{1}{U}
Swell of Courage MOR Reinforce X—{X}{W}{W}

CR 702.77a: "Reinforce N—[cost]" means "[Cost], Discard this card:
Put N +1/+1 counters on target creature."

Implementation:
- types/keywords.rs: Add Keyword::Reinforce { count, cost } variant with
  FromStr parsing and MTGJSON deserialization support
- parser/oracle_keyword.rs: Add Oracle text parser for "reinforce N—{cost}"
  using nom tag combinator (allow-noncombinator for em-dash separators)
- database/synthesis.rs: Add synthesize_reinforce() producing an activated
  ability with composite cost (mana + self-discard), +1/+1 counter effect,
  and activation_zone = Some(Zone::Hand)

Cards unlocked (5): Earthbrawn, Hunting Triad, Break Ties, Fowl Strike,
Swell of Courage

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0505e3ebe1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// CR 702.77a: Reinforce N—{cost} — "[Cost], Discard this card: Put N +1/+1 counters
// on target creature." Same N—{cost} format as Suspend/Awaken.
if let Ok((rest, _)) = tag::<_, _, OracleError<'_>>("reinforce ").parse(text) {
if let Ok((after_count, count)) = nom_primitives::parse_number.parse(rest.trim()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Support X-valued reinforce counts

This only accepts fixed numeric counts, so printed cards with Reinforce X—... (for example Swell of Courage and Wren's Run Hydra in the repo data) still fail keyword extraction even though they are part of the reinforce mechanic being implemented. The count also needs to stay linked to the chosen X for the mana cost at activation/resolution; model it with a quantity/X-capable value rather than u32 and parse with the existing X-aware quantity primitive.

Useful? React with 👍 / 👎.

Whovencroft pushed a commit that referenced this pull request Jul 12, 2026
…aralysis)

Implements the missing static/replacement effect for Fear of Sleep
Paralysis: "Stun counters can't be removed from permanents your
opponents control."

Changes:
- types/statics.rs: Add StaticMode::CountersCantBeRemoved { counter_type }
  variant with all required trait impls (Hash, Display, FromStr, to_kind,
  as_keyword).
- game/coverage.rs: Register in is_data_carrying_static().
- game/turns.rs: Add counter_removal_blocked() helper gating the untap-step
  stun-counter removal (both main path and Seedborn-Muse secondary path).
- parser/oracle_static/dispatch.rs: Add parse_counters_cant_be_removed_static()
  to parse the Oracle text pattern.
- game/turns.rs (tests): Two integration tests verifying the prohibition
  blocks removal and the baseline still works.

Comprehensive Rules: CR 122.1d, CR 101.2
Card: Fear of Sleep Paralysis (DSC #12)
Whovencroft pushed a commit that referenced this pull request Jul 12, 2026
…aralysis)

Implements the missing static/replacement effect for Fear of Sleep
Paralysis: "Stun counters can't be removed from permanents your
opponents control."

Changes:
- types/statics.rs: Add StaticMode::CountersCantBeRemoved { counter_type }
  variant with all required trait impls (Hash, Display, FromStr, to_kind,
  as_keyword).
- game/coverage.rs: Register in is_data_carrying_static().
- game/turns.rs: Add counter_removal_blocked() helper gating the untap-step
  stun-counter removal (both main path and Seedborn-Muse secondary path).
- parser/oracle_static/dispatch.rs: Add parse_counters_cant_be_removed_static()
  to parse the Oracle text pattern.
- game/turns.rs (tests): Two integration tests verifying the prohibition
  blocks removal and the baseline still works.

Comprehensive Rules: CR 122.1d, CR 101.2
Card: Fear of Sleep Paralysis (DSC #12)
Whovencroft pushed a commit that referenced this pull request Jul 12, 2026
…aralysis)

Implements the missing static/replacement effect for Fear of Sleep
Paralysis: "Stun counters can't be removed from permanents your
opponents control."

Changes:
- types/statics.rs: Add StaticMode::CountersCantBeRemoved { counter_type }
  variant with all required trait impls (Hash, Display, FromStr, to_kind,
  as_keyword).
- game/coverage.rs: Register in is_data_carrying_static().
- game/turns.rs: Add counter_removal_blocked() helper gating the untap-step
  stun-counter removal (both main path and Seedborn-Muse secondary path).
- parser/oracle_static/dispatch.rs: Add parse_counters_cant_be_removed_static()
  to parse the Oracle text pattern.
- game/turns.rs (tests): Two integration tests verifying the prohibition
  blocks removal and the baseline still works.

Comprehensive Rules: CR 122.1d, CR 101.2
Card: Fear of Sleep Paralysis (DSC #12)
Whovencroft pushed a commit that referenced this pull request Jul 12, 2026
…aralysis)

Implements the missing static/replacement effect for Fear of Sleep
Paralysis: "Stun counters can't be removed from permanents your
opponents control."

Changes:
- types/statics.rs: Add StaticMode::CountersCantBeRemoved { counter_type }
  variant with all required trait impls (Hash, Display, FromStr, to_kind,
  as_keyword).
- game/coverage.rs: Register in is_data_carrying_static().
- game/turns.rs: Add counter_removal_blocked() helper gating the untap-step
  stun-counter removal (both main path and Seedborn-Muse secondary path).
- parser/oracle_static/dispatch.rs: Add parse_counters_cant_be_removed_static()
  to parse the Oracle text pattern.
- game/turns.rs (tests): Two integration tests verifying the prohibition
  blocks removal and the baseline still works.

Comprehensive Rules: CR 122.1d, CR 101.2
Card: Fear of Sleep Paralysis (DSC #12)
Whovencroft pushed a commit that referenced this pull request Jul 12, 2026
…aralysis)

Implements the missing static/replacement effect for Fear of Sleep
Paralysis: "Stun counters can't be removed from permanents your
opponents control."

Changes:
- types/statics.rs: Add StaticMode::CountersCantBeRemoved { counter_type }
  variant with all required trait impls (Hash, Display, FromStr, to_kind,
  as_keyword).
- game/coverage.rs: Register in is_data_carrying_static().
- game/turns.rs: Add counter_removal_blocked() helper gating the untap-step
  stun-counter removal (both main path and Seedborn-Muse secondary path).
- parser/oracle_static/dispatch.rs: Add parse_counters_cant_be_removed_static()
  to parse the Oracle text pattern.
- game/turns.rs (tests): Two integration tests verifying the prohibition
  blocks removal and the baseline still works.

Comprehensive Rules: CR 122.1d, CR 101.2
Card: Fear of Sleep Paralysis (DSC #12)
Whovencroft added a commit that referenced this pull request Jul 15, 2026
…aralysis) (phase-rs#5663)

Implements the missing static/replacement effect for Fear of Sleep
Paralysis: "Stun counters can't be removed from permanents your
opponents control."

Changes:
- types/statics.rs: Add StaticMode::CountersCantBeRemoved { counter_type }
  variant with all required trait impls (Hash, Display, FromStr, to_kind,
  as_keyword).
- game/coverage.rs: Register in is_data_carrying_static().
- game/turns.rs: Add counter_removal_blocked() helper gating the untap-step
  stun-counter removal (both main path and Seedborn-Muse secondary path).
- parser/oracle_static/dispatch.rs: Add parse_counters_cant_be_removed_static()
  to parse the Oracle text pattern.
- game/turns.rs (tests): Two integration tests verifying the prohibition
  blocks removal and the baseline still works.

Comprehensive Rules: CR 122.1d, CR 101.2
Card: Fear of Sleep Paralysis (DSC #12)

Co-authored-by: Whovencroft <6.60056e+06+Whovencroft@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant