Skip to content

feat(engine): implement CountersCantBeRemoved static (Fear of Sleep Paralysis) - #50

Closed
Whovencroft wants to merge 1 commit into
mainfrom
card/fear-of-sleep-paralysis-counters-cant-be-removed
Closed

feat(engine): implement CountersCantBeRemoved static (Fear of Sleep Paralysis)#50
Whovencroft wants to merge 1 commit into
mainfrom
card/fear-of-sleep-paralysis-counters-cant-be-removed

Conversation

@Whovencroft

Copy link
Copy Markdown
Owner

Summary

Implements the missing static/replacement effect for Fear of Sleep Paralysis (DSC #12):

Stun counters can't be removed from permanents your opponents control.

This is a counter-removal prohibition governed by CR 122.1d (stun counter removal as a replacement for untapping) and CR 101.2 ("can't" overrides "can").

Changes

File Purpose
crates/engine/src/types/statics.rs Add StaticMode::CountersCantBeRemoved { counter_type: CounterType } variant with all required trait impls (Hash, Display, FromStr, to_kind, as_keyword).
crates/engine/src/game/coverage.rs Register in is_data_carrying_static() so the coverage classifier recognizes the new data-carrying variant.
crates/engine/src/game/turns.rs Add counter_removal_blocked() helper that checks active CountersCantBeRemoved statics against the target permanent; gate the untap-step stun-counter removal in both the main untap path and the Seedborn-Muse secondary untap path.
crates/engine/src/parser/oracle_static/dispatch.rs Add parse_counters_cant_be_removed_static() to parse the Oracle text pattern "<counter> counters can't be removed from <subject>".
crates/engine/src/game/turns.rs (tests) Two integration tests: (1) prohibition blocks stun counter removal, (2) baseline removal still works without the prohibition.

Design Decisions

  • Data-carrying variant: CountersCantBeRemoved carries a CounterType field so the same mechanism can be reused for future cards that protect other counter types (e.g., oil counters, shield counters).
  • Enforcement point: The gate is placed at the untap-step stun-counter-removal site in turns.rs (both main path and Seedborn path), which is the only place CR 122.1d removal currently occurs. Future counter-removal sites (e.g., explicit "remove a counter" effects) would need their own gate.
  • Filter matching: Uses static_filter_matches with a StaticCheckContext { target_id } to evaluate the affected filter (e.g., "permanents your opponents control") against the permanent whose counter would be removed.
  • Parser: Simple str::find anchor on " counters can't be removed from ", then parse_strict_counter_type for the prefix and parse_type_phrase for the subject suffix.

Comprehensive Rules

  • CR 122.1d: If a permanent with a stun counter would become untapped, instead a stun counter is removed from it.
  • CR 101.2: "Can't" effects take precedence — if a static says counters can't be removed, the CR 122.1d replacement doesn't happen.

Testing

  • execute_untap_honors_counters_cant_be_removed_static — verifies that with Fear of Sleep Paralysis on the battlefield, an opponent's stunned creature stays tapped AND keeps its stun counter.
  • execute_untap_removes_stun_counter_without_prohibition — baseline: without the prohibition, stun counter is removed normally per CR 122.1d.

@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: 02117a20ca

ℹ️ 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".

Comment thread crates/engine/src/game/turns.rs Outdated
Comment on lines +1330 to +1331
/// the given object for the given counter type. Used by the untap-step stun-
/// counter removal to skip removal when blocked (Fear of Sleep Paralysis).

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 Enforce counter-removal prohibition on all removal paths

This helper is only used by the two untap-step stun-counter branches in turns.rs, so the new supported CountersCantBeRemoved static does not affect other production counter-removal paths such as effects::counters::apply_counter_removal or move-counter/cost removals. In a game with Fear of Sleep Paralysis, an opponent can still remove or move a stun counter from their affected permanent through those paths even though the static says the counter can't be removed; please put this check at the central counter-removal authority or route all removals through it.

Useful? React with 👍 / 👎.

@Whovencroft
Whovencroft force-pushed the card/fear-of-sleep-paralysis-counters-cant-be-removed branch from 02117a2 to 7360750 Compare July 12, 2026 08:51
@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown

Parse changes introduced by this PR · 1 card(s), 2 signature(s) (baseline: main d1f7d05ea8a0)

1 card(s) · static/CountersCantBeRemoved(Stun) · added: CountersCantBeRemoved(Stun) (affects=opponent controls permanent)

Examples: Fear of Sleep Paralysis

1 card(s) · ability/unknown · removed: unknown

Examples: Fear of Sleep Paralysis

9 card(s) had Oracle-text changes (errata/reprint) — excluded as non-parser.

@Whovencroft
Whovencroft force-pushed the card/fear-of-sleep-paralysis-counters-cant-be-removed branch 3 times, most recently from cb15f98 to a9f58b6 Compare July 12, 2026 09:26
…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
Whovencroft force-pushed the card/fear-of-sleep-paralysis-counters-cant-be-removed branch from a9f58b6 to 9872246 Compare July 12, 2026 09:47
@Whovencroft
Whovencroft deleted the card/fear-of-sleep-paralysis-counters-cant-be-removed branch July 16, 2026 09:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant