diff --git a/CHANGELOG.md b/CHANGELOG.md
index 954e339..b5f4ce0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,16 +9,27 @@ This format follows Keep a Changelog principles and aims for Semantic Versioning
## [Unreleased]
### Summary
-- _No unreleased changes yet_
+- Keyword normalization reduces specialty keyword noise by 96% while maintaining theme catalog quality
+- Protection tag now focuses on cards that grant shields to others, not just those with inherent protection
+- Web UI improvements: faster polling, fixed progress display, and theme refresh stability
### Added
-- _None_
+- Keyword normalization system with smart filtering of one-off specialty mechanics
+- Allowlist preserves important keywords like Flying, Myriad, and Transform
+- Protection grant detection identifies cards that give Hexproof, Ward, or Indestructible to other permanents
+- Automatic tagging for creature-type-specific protection (e.g., "Knights Gain Protection")
### Changed
-- _None_
+- Keywords now consolidate variants (e.g., "Commander ninjutsu" becomes "Ninjutsu")
+- Setup progress polling reduced from 3s to 5-10s intervals for better performance
+- Theme catalog streamlined from 753 to 736 themes (-2.3%) with improved quality
+- Protection tag refined to focus on 329 cards that grant shields (down from 1,166 with inherent effects)
### Fixed
-- _None_
+- Setup progress now shows 100% completion instead of getting stuck at 99%
+- Theme catalog no longer continuously regenerates after setup completes
+- Health indicator polling optimized to reduce server load
+- Protection detection now correctly excludes creatures with only inherent keywords
## [2.5.2] - 2025-10-08
### Summary
diff --git a/README.md b/README.md
index ee62409..7ad729e 100644
--- a/README.md
+++ b/README.md
@@ -99,7 +99,7 @@ Execute saved configs without manual input.
### Initial Setup
Refresh data and caches when formats shift.
-- Runs card downloads, CSV regeneration, tagging, and commander catalog rebuilds.
+- Runs card downloads, CSV regeneration, smart tagging (keywords + protection grants), and commander catalog rebuilds.
- Controlled by `SHOW_SETUP=1` (on by default in compose).
- Force a rebuild manually:
```powershell
diff --git a/RELEASE_NOTES_TEMPLATE.md b/RELEASE_NOTES_TEMPLATE.md
index 1c99b55..d35313a 100644
--- a/RELEASE_NOTES_TEMPLATE.md
+++ b/RELEASE_NOTES_TEMPLATE.md
@@ -1,10 +1,29 @@
-# MTG Python Deckbuilder ${VERSION}
+# MTG Pyt### Added
+- Keywo### Changed
+- Keywords consolidate variants (e.g., "Commander ninjutsu" → "Ninjutsu") for consistent theme matching
+- Protection tag refined to focus on shield-granting cards (329 cards vs 1,166 previously)
+- Theme catalog streamlined with improved quality (736 themes, down 2.3%)
+- Commander search and theme picker now share an intelligent debounce to prevent redundant requests while typing
+- Card grids adopt modern containment rules to minimize layout recalculations on large decks
+- Include/exclude buttons respond immediately with optimistic updates, reconciling gracefully if the server disagrees
+- Frequently accessed views, like the commander catalog default, now pull from an in-memory cache for sub-200 ms reloads
+- Deck review loads in focused chunks, keeping the initial page lean while analytics stream progressively
+- Chart hover zones expand to full column width for easier interactionnup filters out one-off specialty mechanics (like set-specific ability words) while keeping evergreen abilities
+- Protection grant detection identifies cards that give Hexproof, Ward, or other shields to your permanents
+- Creature-type-specific protection automatically tagged (e.g., "Knights Gain Protection" for tribal strategies)
+- Skeleton placeholders accept `data-skeleton-label` microcopy and only surface after ~400 ms across the build wizard, stage navigator, and alternatives panel
+- Must-have toggle API (`/build/must-haves/toggle`), telemetry ingestion route (`/telemetry/events`), and structured logging helpers capture include/exclude beacons
+- Commander catalog results wrap in a deferred skeleton list while commander art lazy-loads via a new `IntersectionObserver` helper in `code/web/static/app.js`
+- Collapsible accordions for Mana Overview and Test Hand sections defer heavy analytics until they are expanded
+- Click-to-pin chart tooltips keep comparisons anchored and add copy-friendly working buttons
+- Virtualized card lists automatically render only visible items once 12+ cards are presentkbuilder ${VERSION}
### Summary
-- Builder responsiveness upgrades: smarter HTMX caching, shared debounce helpers, and virtualization hints keep long card lists responsive.
-- Commander catalog now ships skeleton placeholders, lazy commander art loading, and cached default results for faster repeat visits.
-- Deck summary streams via an HTMX fragment while virtualization powers summary lists without loading every row up front.
-- Mana analytics load on demand with collapsible sections and interactive chart tooltips that support click-to-pin comparisons.
+- Smarter card tagging: Keywords are cleaner (96% noise reduction) and Protection now highlights cards that actually grant shields to your board
+- Builder responsiveness upgrades: smarter HTMX caching, shared debounce helpers, and virtualization hints keep long card lists responsive
+- Commander catalog now ships skeleton placeholders, lazy commander art loading, and cached default results for faster repeat visits
+- Deck summary streams via an HTMX fragment while virtualization powers summary lists without loading every row up front
+- Mana analytics load on demand with collapsible sections and interactive chart tooltips that support click-to-pin comparisons
### Added
- Skeleton placeholders accept `data-skeleton-label` microcopy and only surface after ~400 ms across the build wizard, stage navigator, and alternatives panel.
diff --git a/code/scripts/audit_protection_full_v2.py b/code/scripts/audit_protection_full_v2.py
new file mode 100644
index 0000000..a10d415
--- /dev/null
+++ b/code/scripts/audit_protection_full_v2.py
@@ -0,0 +1,203 @@
+"""
+Full audit of Protection-tagged cards with kindred metadata support (M2 Phase 2).
+
+Created: October 8, 2025
+Purpose: Audit and validate Protection tag precision after implementing grant detection.
+ Can be re-run periodically to check tagging quality.
+
+This script audits ALL Protection-tagged cards and categorizes them:
+- Grant: Gives broad protection to other permanents YOU control
+- Kindred: Gives protection to specific creature types (metadata tags)
+- Mixed: Both broad and kindred/inherent
+- Inherent: Only has protection itself
+- ConditionalSelf: Only conditionally grants to itself
+- Opponent: Grants to opponent's permanents
+- Neither: False positive
+
+Outputs:
+- m2_audit_v2.json: Full analysis with summary
+- m2_audit_v2_grant.csv: Cards for main Protection tag
+- m2_audit_v2_kindred.csv: Cards for kindred metadata tags
+- m2_audit_v2_mixed.csv: Cards with both broad and kindred grants
+- m2_audit_v2_conditional.csv: Conditional self-grants (exclude)
+- m2_audit_v2_inherent.csv: Inherent protection only (exclude)
+- m2_audit_v2_opponent.csv: Opponent grants (exclude)
+- m2_audit_v2_neither.csv: False positives (exclude)
+- m2_audit_v2_all.csv: All cards combined
+"""
+
+import sys
+from pathlib import Path
+import pandas as pd
+import json
+
+# Add project root to path
+project_root = Path(__file__).parent.parent.parent
+sys.path.insert(0, str(project_root))
+
+from code.tagging.protection_grant_detection import (
+ categorize_protection_card,
+ get_kindred_protection_tags,
+ is_granting_protection,
+)
+
+def load_all_cards():
+ """Load all cards from color/identity CSV files."""
+ csv_dir = project_root / 'csv_files'
+
+ # Get all color/identity CSVs (not the raw cards.csv)
+ csv_files = list(csv_dir.glob('*_cards.csv'))
+ csv_files = [f for f in csv_files if f.stem not in ['cards', 'testdata']]
+
+ all_cards = []
+ for csv_file in csv_files:
+ try:
+ df = pd.read_csv(csv_file)
+ all_cards.append(df)
+ except Exception as e:
+ print(f"Warning: Could not load {csv_file.name}: {e}")
+
+ # Combine all DataFrames
+ combined = pd.concat(all_cards, ignore_index=True)
+
+ # Drop duplicates (cards appear in multiple color files)
+ combined = combined.drop_duplicates(subset=['name'], keep='first')
+
+ return combined
+
+def audit_all_protection_cards():
+ """Audit all Protection-tagged cards."""
+ print("Loading all cards...")
+ df = load_all_cards()
+
+ print(f"Total cards loaded: {len(df)}")
+
+ # Filter to Protection-tagged cards (column is 'themeTags' in color CSVs)
+ df_prot = df[df['themeTags'].str.contains('Protection', case=False, na=False)].copy()
+
+ print(f"Protection-tagged cards: {len(df_prot)}")
+
+ # Categorize each card
+ categories = []
+ grants_list = []
+ kindred_tags_list = []
+
+ for idx, row in df_prot.iterrows():
+ name = row['name']
+ text = str(row.get('text', '')).replace('\\n', '\n') # Convert escaped newlines to real newlines
+ keywords = str(row.get('keywords', ''))
+ card_type = str(row.get('type', ''))
+
+ # Categorize with kindred exclusion enabled
+ category = categorize_protection_card(name, text, keywords, card_type, exclude_kindred=True)
+
+ # Check if it grants broadly
+ grants_broad = is_granting_protection(text, keywords, exclude_kindred=True)
+
+ # Get kindred tags
+ kindred_tags = get_kindred_protection_tags(text)
+
+ categories.append(category)
+ grants_list.append(grants_broad)
+ kindred_tags_list.append(', '.join(sorted(kindred_tags)) if kindred_tags else '')
+
+ df_prot['category'] = categories
+ df_prot['grants_broad'] = grants_list
+ df_prot['kindred_tags'] = kindred_tags_list
+
+ # Generate summary (convert numpy types to native Python for JSON serialization)
+ summary = {
+ 'total': int(len(df_prot)),
+ 'categories': {k: int(v) for k, v in df_prot['category'].value_counts().to_dict().items()},
+ 'grants_broad_count': int(df_prot['grants_broad'].sum()),
+ 'kindred_cards_count': int((df_prot['kindred_tags'] != '').sum()),
+ }
+
+ # Calculate keep vs remove
+ keep_categories = {'Grant', 'Mixed'}
+ kindred_only = df_prot[df_prot['category'] == 'Kindred']
+ keep_count = len(df_prot[df_prot['category'].isin(keep_categories)])
+ remove_count = len(df_prot[~df_prot['category'].isin(keep_categories | {'Kindred'})])
+
+ summary['keep_main_tag'] = keep_count
+ summary['kindred_metadata'] = len(kindred_only)
+ summary['remove'] = remove_count
+ summary['precision_estimate'] = round((keep_count / len(df_prot)) * 100, 1) if len(df_prot) > 0 else 0
+
+ # Print summary
+ print(f"\n{'='*60}")
+ print("AUDIT SUMMARY")
+ print(f"{'='*60}")
+ print(f"Total Protection-tagged cards: {summary['total']}")
+ print(f"\nCategories:")
+ for cat, count in sorted(summary['categories'].items()):
+ pct = (count / summary['total']) * 100
+ print(f" {cat:20s} {count:4d} ({pct:5.1f}%)")
+
+ print(f"\n{'='*60}")
+ print(f"Main Protection tag: {keep_count:4d} ({keep_count/len(df_prot)*100:5.1f}%)")
+ print(f"Kindred metadata only: {len(kindred_only):4d} ({len(kindred_only)/len(df_prot)*100:5.1f}%)")
+ print(f"Remove: {remove_count:4d} ({remove_count/len(df_prot)*100:5.1f}%)")
+ print(f"{'='*60}")
+ print(f"Precision estimate: {summary['precision_estimate']}%")
+ print(f"{'='*60}\n")
+
+ # Export results
+ output_dir = project_root / 'logs' / 'roadmaps' / 'source' / 'tagging_refinement'
+ output_dir.mkdir(parents=True, exist_ok=True)
+
+ # Export JSON summary
+ with open(output_dir / 'm2_audit_v2.json', 'w') as f:
+ json.dump({
+ 'summary': summary,
+ 'cards': df_prot[['name', 'type', 'category', 'grants_broad', 'kindred_tags', 'keywords', 'text']].to_dict(orient='records')
+ }, f, indent=2)
+
+ # Export CSVs by category
+ export_cols = ['name', 'type', 'category', 'grants_broad', 'kindred_tags', 'keywords', 'text']
+
+ # Grant category
+ df_grant = df_prot[df_prot['category'] == 'Grant']
+ df_grant[export_cols].to_csv(output_dir / 'm2_audit_v2_grant.csv', index=False)
+ print(f"Exported {len(df_grant)} Grant cards to m2_audit_v2_grant.csv")
+
+ # Kindred category
+ df_kindred = df_prot[df_prot['category'] == 'Kindred']
+ df_kindred[export_cols].to_csv(output_dir / 'm2_audit_v2_kindred.csv', index=False)
+ print(f"Exported {len(df_kindred)} Kindred cards to m2_audit_v2_kindred.csv")
+
+ # Mixed category
+ df_mixed = df_prot[df_prot['category'] == 'Mixed']
+ df_mixed[export_cols].to_csv(output_dir / 'm2_audit_v2_mixed.csv', index=False)
+ print(f"Exported {len(df_mixed)} Mixed cards to m2_audit_v2_mixed.csv")
+
+ # ConditionalSelf category
+ df_conditional = df_prot[df_prot['category'] == 'ConditionalSelf']
+ df_conditional[export_cols].to_csv(output_dir / 'm2_audit_v2_conditional.csv', index=False)
+ print(f"Exported {len(df_conditional)} ConditionalSelf cards to m2_audit_v2_conditional.csv")
+
+ # Inherent category
+ df_inherent = df_prot[df_prot['category'] == 'Inherent']
+ df_inherent[export_cols].to_csv(output_dir / 'm2_audit_v2_inherent.csv', index=False)
+ print(f"Exported {len(df_inherent)} Inherent cards to m2_audit_v2_inherent.csv")
+
+ # Opponent category
+ df_opponent = df_prot[df_prot['category'] == 'Opponent']
+ df_opponent[export_cols].to_csv(output_dir / 'm2_audit_v2_opponent.csv', index=False)
+ print(f"Exported {len(df_opponent)} Opponent cards to m2_audit_v2_opponent.csv")
+
+ # Neither category
+ df_neither = df_prot[df_prot['category'] == 'Neither']
+ df_neither[export_cols].to_csv(output_dir / 'm2_audit_v2_neither.csv', index=False)
+ print(f"Exported {len(df_neither)} Neither cards to m2_audit_v2_neither.csv")
+
+ # All cards
+ df_prot[export_cols].to_csv(output_dir / 'm2_audit_v2_all.csv', index=False)
+ print(f"Exported {len(df_prot)} total cards to m2_audit_v2_all.csv")
+
+ print(f"\nAll files saved to: {output_dir}")
+
+ return df_prot, summary
+
+if __name__ == '__main__':
+ df_results, summary = audit_all_protection_cards()
diff --git a/code/settings.py b/code/settings.py
index 0807378..5731031 100644
--- a/code/settings.py
+++ b/code/settings.py
@@ -1,6 +1,7 @@
from __future__ import annotations
# Standard library imports
+import os
from typing import Dict, List, Optional
# ----------------------------------------------------------------------------------
@@ -98,4 +99,17 @@ CSV_DIRECTORY: str = 'csv_files'
FILL_NA_COLUMNS: Dict[str, Optional[str]] = {
'colorIdentity': 'Colorless', # Default color identity for cards without one
'faceName': None # Use card's name column value when face name is not available
-}
\ No newline at end of file
+}
+
+# ----------------------------------------------------------------------------------
+# TAGGING REFINEMENT FEATURE FLAGS (M1-M3)
+# ----------------------------------------------------------------------------------
+
+# M1: Enable keyword normalization and singleton pruning
+TAG_NORMALIZE_KEYWORDS = os.getenv('TAG_NORMALIZE_KEYWORDS', '1').lower() not in ('0', 'false', 'off', 'disabled')
+
+# M2: Enable protection grant detection (planned)
+TAG_PROTECTION_GRANTS = os.getenv('TAG_PROTECT ION_GRANTS', '0').lower() not in ('0', 'false', 'off', 'disabled')
+
+# M3: Enable metadata/theme partition (planned)
+TAG_METADATA_SPLIT = os.getenv('TAG_METADATA_SPLIT', '0').lower() not in ('0', 'false', 'off', 'disabled')
\ No newline at end of file
diff --git a/code/tagging/protection_grant_detection.py b/code/tagging/protection_grant_detection.py
new file mode 100644
index 0000000..dca37b4
--- /dev/null
+++ b/code/tagging/protection_grant_detection.py
@@ -0,0 +1,493 @@
+"""
+Protection grant detection implementation for M2.
+
+This module provides helpers to distinguish cards that grant protection effects
+from cards that have inherent protection effects.
+
+Usage in tagger.py:
+ from code.tagging.protection_grant_detection import is_granting_protection
+
+ if is_granting_protection(text, keywords):
+ # Tag as Protection
+"""
+
+import re
+from typing import Set, List, Pattern
+
+from code.tagging.tag_constants import CREATURE_TYPES
+
+
+# Pre-compile kindred detection patterns at module load for performance
+# Pattern: (compiled_regex, tag_name_template)
+KINDRED_PATTERNS: List[tuple[Pattern, str]] = []
+
+def _init_kindred_patterns():
+ """Initialize pre-compiled kindred patterns for all creature types."""
+ global KINDRED_PATTERNS
+ if KINDRED_PATTERNS:
+ return # Already initialized
+
+ for creature_type in CREATURE_TYPES:
+ creature_lower = creature_type.lower()
+ creature_escaped = re.escape(creature_lower)
+ tag_name = f"{creature_type}s Gain Protection"
+
+ # Create 3 patterns per type
+ patterns_to_compile = [
+ (rf'\bother {creature_escaped}s?\b.*\b(have|gain)\b', tag_name),
+ (rf'\b{creature_escaped} creatures?\b.*\b(have|gain)\b', tag_name),
+ (rf'\btarget {creature_escaped}\b.*\bgains?\b', tag_name),
+ ]
+
+ for pattern_str, tag in patterns_to_compile:
+ try:
+ compiled = re.compile(pattern_str, re.IGNORECASE)
+ KINDRED_PATTERNS.append((compiled, tag))
+ except re.error:
+ # Skip patterns that fail to compile
+ pass
+
+
+# Grant verb patterns - cards that give protection to other permanents
+# These patterns look for grant verbs that affect OTHER permanents, not self
+GRANT_VERB_PATTERNS = [
+ r'\bgain[s]?\b.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'\bgive[s]?\b.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'\bgrant[s]?\b.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'\bget[s]?\b.*\+.*\b(hexproof|shroud|indestructible|ward|protection)\b', # "gets +X/+X and has" pattern
+]
+
+# Self-reference patterns that should NOT count as granting
+# Reminder text and keyword lines only
+SELF_REFERENCE_PATTERNS = [
+ r'^\s*(hexproof|shroud|indestructible|ward|protection)', # Start of text (keyword ability)
+ r'\([^)]*\b(hexproof|shroud|indestructible|ward|protection)[^)]*\)', # Reminder text in parens
+]
+
+# Conditional self-grant patterns - activated/triggered abilities that grant to self
+CONDITIONAL_SELF_GRANT_PATTERNS = [
+ # Activated abilities
+ r'\{[^}]*\}.*:.*\bthis (creature|permanent|artifact|enchantment)\b.*\bgain[s]?\b.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'discard.*:.*\bthis (creature|permanent|artifact|enchantment)\b.*\bgain[s]?\b',
+ r'\{t\}.*:.*\bthis (creature|permanent|artifact|enchantment)\b.*\bgain[s]?\b',
+ r'sacrifice.*:.*\bthis (creature|permanent|artifact|enchantment)\b.*\bgain[s]?\b',
+ r'pay.*life.*:.*\bthis (creature|permanent|artifact|enchantment)\b.*\bgain[s]?\b',
+ # Triggered abilities that grant to self only
+ r'whenever.*\b(this creature|this permanent|it)\b.*\bgain[s]?\b.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'whenever you (cast|play|attack|cycle|discard|commit).*\b(this creature|this permanent|it)\b.*\bgain[s]?\b.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'at the beginning.*\b(this creature|this permanent|it)\b.*\bgain[s]?\b.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'whenever.*\b(this creature|this permanent)\b (attacks|enters|becomes).*\b(this creature|this permanent|it)\b.*\bgain[s]?\b',
+ # Named self-references (e.g., "Pristine Skywise gains")
+ r'whenever you cast.*[A-Z][a-z]+.*gains.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'whenever you.*[A-Z][a-z]+.*gains.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ # Static conditional abilities (as long as, if you control X)
+ r'as long as.*\b(this creature|this permanent|it|has)\b.*(has|gains?).*\b(hexproof|shroud|indestructible|ward|protection)\b',
+]
+
+# Mass grant patterns - affects multiple creatures YOU control
+MASS_GRANT_PATTERNS = [
+ r'creatures you control (have|gain|get)',
+ r'other .* you control (have|gain|get)',
+ r'(artifacts?|enchantments?|permanents?) you control (have|gain|get)', # Artifacts you control have...
+ r'other (creatures?|artifacts?|enchantments?) (have|gain|get)', # Other creatures have...
+ r'all (creatures?|slivers?|permanents?) (have|gain|get)', # All creatures/slivers have...
+]
+
+# Targeted grant patterns - must specify "you control"
+TARGETED_GRANT_PATTERNS = [
+ r'target .* you control (gains?|gets?|has)',
+ r'equipped creature (gains?|gets?|has)',
+ r'enchanted creature (gains?|gets?|has)',
+]
+
+# Exclusion patterns - cards that remove or prevent protection
+EXCLUSION_PATTERNS = [
+ r"can't have (hexproof|indestructible|ward|shroud)",
+ r"lose[s]? (hexproof|indestructible|ward|shroud|protection)",
+ r"without (hexproof|indestructible|ward|shroud)",
+ r"protection from.*can't",
+]
+
+# Opponent grant patterns - grants to opponent's permanents (EXCLUDE these)
+OPPONENT_GRANT_PATTERNS = [
+ r'target opponent',
+ r'each opponent',
+ r'all creatures', # "all creatures" without "you control"
+ r'all permanents', # "all permanents" without "you control"
+ r'each player',
+ r'each creature', # "each creature" without "you control"
+]
+
+# Kindred-specific grant patterns for metadata tagging
+KINDRED_GRANT_PATTERNS = {
+ 'Knights Gain Protection': [
+ r'knight[s]? you control.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'other knight[s]?.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ ],
+ 'Merfolk Gain Protection': [
+ r'merfolk you control.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'other merfolk.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ ],
+ 'Zombies Gain Protection': [
+ r'zombie[s]? you control.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'other zombie[s]?.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'target.*zombie.*\bgain[s]?\b.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ ],
+ 'Vampires Gain Protection': [
+ r'vampire[s]? you control.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'other vampire[s]?.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ ],
+ 'Elves Gain Protection': [
+ r'el(f|ves) you control.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'other el(f|ves).*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ ],
+ 'Dragons Gain Protection': [
+ r'dragon[s]? you control.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'other dragon[s]?.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ ],
+ 'Goblins Gain Protection': [
+ r'goblin[s]? you control.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'other goblin[s]?.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ ],
+ 'Slivers Gain Protection': [
+ r'sliver[s]? you control.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'all sliver[s]?.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'other sliver[s]?.*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ ],
+ 'Artifacts Gain Protection': [
+ r'artifact[s]? you control (have|gain).*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'other artifact[s]? (have|gain).*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ ],
+ 'Enchantments Gain Protection': [
+ r'enchantment[s]? you control (have|gain).*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ r'other enchantment[s]? (have|gain).*\b(hexproof|shroud|indestructible|ward|protection)\b',
+ ],
+}
+
+# Protection keyword patterns for inherent check
+PROTECTION_KEYWORDS = {
+ 'hexproof',
+ 'shroud',
+ 'indestructible',
+ 'ward',
+ 'protection from',
+ 'protection',
+}
+
+
+def get_kindred_protection_tags(text: str) -> Set[str]:
+ """
+ Identify kindred-specific protection grants for metadata tagging.
+
+ Returns a set of metadata tag names like "Knights Gain Protection".
+
+ Uses both predefined patterns and dynamic creature type detection.
+ """
+ if not text:
+ return set()
+
+ # Initialize pre-compiled patterns if needed
+ _init_kindred_patterns()
+
+ text_lower = text.lower()
+ tags = set()
+
+ # Check predefined patterns (specific kindred types we track)
+ for tag_name, patterns in KINDRED_GRANT_PATTERNS.items():
+ for pattern in patterns:
+ if re.search(pattern, text_lower, re.IGNORECASE):
+ tags.add(tag_name)
+ break # Found match for this kindred type, move to next
+
+ # Only check dynamic patterns if protection keywords present (performance optimization)
+ if not any(keyword in text_lower for keyword in ['hexproof', 'shroud', 'indestructible', 'ward', 'protection']):
+ return tags
+
+ # Use pre-compiled patterns for all creature types
+ for compiled_pattern, tag_name in KINDRED_PATTERNS:
+ if compiled_pattern.search(text_lower):
+ tags.add(tag_name)
+ # Don't break - a card could grant to multiple creature types
+
+ return tags
+
+
+def is_opponent_grant(text: str) -> bool:
+ """
+ Check if card grants protection to opponent's permanents or all permanents.
+
+ Returns True if this grants to opponents (should be excluded from Protection tag).
+ """
+ if not text:
+ return False
+
+ text_lower = text.lower()
+
+ # Check for opponent grant patterns
+ for pattern in OPPONENT_GRANT_PATTERNS:
+ if re.search(pattern, text_lower, re.IGNORECASE):
+ # Make sure it's not "target opponent" for a different effect
+ # Must be in context of granting protection
+ if any(prot in text_lower for prot in ['hexproof', 'shroud', 'indestructible', 'ward', 'protection']):
+ # Check if "you control" appears in same sentence
+ if 'you control' not in text_lower.split('.')[0]:
+ return True
+
+ return False
+
+
+def has_conditional_self_grant(text: str) -> bool:
+ """
+ Check if card has any conditional self-grant patterns.
+ This does NOT check if it ALSO grants to others.
+ """
+ if not text:
+ return False
+
+ text_lower = text.lower()
+
+ # Check for conditional self-grant patterns (activated/triggered abilities)
+ for pattern in CONDITIONAL_SELF_GRANT_PATTERNS:
+ if re.search(pattern, text_lower, re.IGNORECASE):
+ return True
+
+ return False
+
+
+def is_conditional_self_grant(text: str) -> bool:
+ """
+ Check if card only conditionally grants protection to itself.
+
+ Examples:
+ - "{B}, Discard a card: This creature gains hexproof until end of turn."
+ - "Whenever you cast a noncreature spell, untap this creature. It gains protection..."
+ - "Whenever this creature attacks, it gains indestructible until end of turn."
+
+ These should be excluded as they don't provide protection to OTHER permanents.
+ """
+ if not text:
+ return False
+
+ text_lower = text.lower()
+
+ # Check if it has conditional self-grant patterns
+ found_conditional_self = has_conditional_self_grant(text)
+
+ if not found_conditional_self:
+ return False
+
+ # If we found a conditional self-grant, check if there's ALSO a grant to others
+ # Look for patterns that grant to creatures besides itself
+ has_other_grant = any(re.search(pattern, text_lower, re.IGNORECASE) for pattern in [
+ r'other creatures',
+ r'creatures you control (have|gain)',
+ r'target (creature|permanent) you control gains',
+ r'another target (creature|permanent)',
+ r'equipped creature (has|gains)',
+ r'enchanted creature (has|gains)',
+ r'target legendary',
+ r'permanents you control gain',
+ ])
+
+ # Return True only if it's ONLY conditional self-grants (no other grants)
+ return not has_other_grant
+
+
+def is_granting_protection(text: str, keywords: str, exclude_kindred: bool = False) -> bool:
+ """
+ Determine if a card grants protection effects to other permanents.
+
+ Returns True if the card gives/grants protection to other cards unconditionally.
+ Returns False if:
+ - Card only has inherent protection
+ - Card only conditionally grants to itself
+ - Card grants to opponent's permanents
+ - Card grants only to specific kindred types (when exclude_kindred=True)
+ - Card creates tokens with protection (not granting to existing permanents)
+ - Card only modifies non-protection stats of other permanents
+
+ Args:
+ text: Card text to analyze
+ keywords: Card keywords (comma-separated)
+ exclude_kindred: If True, exclude kindred-specific grants
+
+ Returns:
+ True if card grants broad protection, False otherwise
+ """
+ if not text:
+ return False
+
+ text_lower = text.lower()
+
+ # EXCLUDE: Opponent grants
+ if is_opponent_grant(text):
+ return False
+
+ # EXCLUDE: Conditional self-grants only
+ if is_conditional_self_grant(text):
+ return False
+
+ # EXCLUDE: Cards that remove protection
+ for pattern in EXCLUSION_PATTERNS:
+ if re.search(pattern, text_lower, re.IGNORECASE):
+ return False
+
+ # EXCLUDE: Token creation with protection (not granting to existing permanents)
+ if re.search(r'create.*token.*with.*(hexproof|shroud|indestructible|ward|protection)', text_lower, re.IGNORECASE):
+ # Check if there's ALSO granting to other permanents
+ has_grant_to_others = any(re.search(pattern, text_lower, re.IGNORECASE) for pattern in MASS_GRANT_PATTERNS)
+ if not has_grant_to_others:
+ return False
+
+ # EXCLUDE: Kindred-specific grants if requested
+ if exclude_kindred:
+ kindred_tags = get_kindred_protection_tags(text)
+ if kindred_tags:
+ # If we detected kindred tags, check if there's ALSO a non-kindred grant
+ # Look for grant patterns that explicitly grant to ALL creatures/permanents broadly
+ has_broad_grant = False
+
+ # Patterns that indicate truly broad grants (not type-specific)
+ broad_only_patterns = [
+ r'\bcreatures you control (have|gain)\b(?!.*(knight|merfolk|zombie|elf|dragon|goblin|sliver))', # Only if not followed by type
+ r'\bpermanents you control (have|gain)\b',
+ r'\beach (creature|permanent) you control',
+ r'\ball (creatures?|permanents?)',
+ ]
+
+ for pattern in broad_only_patterns:
+ if re.search(pattern, text_lower, re.IGNORECASE):
+ has_broad_grant = True
+ break
+
+ if not has_broad_grant:
+ return False # Only kindred grants, exclude
+
+ # Check if card has inherent protection keywords
+ has_inherent = False
+ if keywords:
+ keywords_lower = keywords.lower()
+ has_inherent = any(k in keywords_lower for k in PROTECTION_KEYWORDS)
+
+ # Check for explicit grants with protection keywords
+ found_grant = False
+
+ # Mass grant patterns (creatures you control have/gain)
+ for pattern in MASS_GRANT_PATTERNS:
+ match = re.search(pattern, text_lower, re.IGNORECASE)
+ if match:
+ # Check if protection keyword appears in the same sentence or nearby (within 70 chars AFTER the match)
+ # This ensures we're looking at "creatures you control HAVE hexproof" not just having both phrases
+ context_start = match.start()
+ context_end = min(len(text_lower), match.end() + 70)
+ context = text_lower[context_start:context_end]
+
+ if any(prot in context for prot in PROTECTION_KEYWORDS):
+ found_grant = True
+ break
+
+ # Targeted grant patterns (target creature gains)
+ if not found_grant:
+ for pattern in TARGETED_GRANT_PATTERNS:
+ match = re.search(pattern, text_lower, re.IGNORECASE)
+ if match:
+ # Check if protection keyword appears after the grant verb (within 70 chars)
+ context_start = match.start()
+ context_end = min(len(text_lower), match.end() + 70)
+ context = text_lower[context_start:context_end]
+
+ if any(prot in context for prot in PROTECTION_KEYWORDS):
+ found_grant = True
+ break
+
+ # Grant verb patterns (creature gains/gets hexproof)
+ if not found_grant:
+ for pattern in GRANT_VERB_PATTERNS:
+ if re.search(pattern, text_lower, re.IGNORECASE):
+ found_grant = True
+ break
+
+ # If we have inherent protection and the ONLY text is about stats (no grant words), exclude
+ if has_inherent and not found_grant:
+ # Check if text only talks about other stats (power/toughness, +X/+X)
+ has_stat_only = bool(re.search(r'(get[s]?|gain[s]?)\s+[+\-][0-9X]+/[+\-][0-9X]+', text_lower))
+ # Check if text mentions "other" without protection keywords
+ mentions_other_without_prot = 'other' in text_lower and not any(prot in text_lower for prot in PROTECTION_KEYWORDS if prot in text_lower[text_lower.find('other'):])
+
+ if has_stat_only or mentions_other_without_prot:
+ return False
+
+ return found_grant
+
+
+def categorize_protection_card(name: str, text: str, keywords: str, card_type: str, exclude_kindred: bool = False) -> str:
+ """
+ Categorize a Protection-tagged card for audit purposes.
+
+ Args:
+ name: Card name
+ text: Card text
+ keywords: Card keywords
+ card_type: Card type line
+ exclude_kindred: If True, kindred-specific grants are categorized as metadata, not Grant
+
+ Returns:
+ 'Grant' - gives broad protection to others
+ 'Kindred' - gives kindred-specific protection (metadata tag)
+ 'Inherent' - has protection itself
+ 'ConditionalSelf' - only conditionally grants to itself
+ 'Opponent' - grants to opponent's permanents
+ 'Neither' - false positive
+ """
+ keywords_lower = keywords.lower() if keywords else ''
+
+ # Check for opponent grants first
+ if is_opponent_grant(text):
+ return 'Opponent'
+
+ # Check for conditional self-grants (ONLY self, no other grants)
+ if is_conditional_self_grant(text):
+ return 'ConditionalSelf'
+
+ # Check if it has conditional self-grant (may also have other grants)
+ has_cond_self = has_conditional_self_grant(text)
+
+ # Check if it has inherent protection
+ has_inherent = any(k in keywords_lower for k in PROTECTION_KEYWORDS)
+
+ # Check for kindred-specific grants
+ kindred_tags = get_kindred_protection_tags(text)
+ if kindred_tags and exclude_kindred:
+ # Check if there's ALSO a broad grant (excluding kindred)
+ grants_broad = is_granting_protection(text, keywords, exclude_kindred=True)
+
+ if grants_broad and has_inherent:
+ # Has inherent + kindred + broad grants
+ return 'Mixed'
+ elif grants_broad:
+ # Has kindred + broad grants (but no inherent)
+ # This is just Grant with kindred metadata tags
+ return 'Grant'
+ elif has_inherent:
+ # Has inherent + kindred only (not broad)
+ # This is still just Kindred category (inherent is separate from granting)
+ return 'Kindred'
+ else:
+ # Only kindred grants, no inherent or broad
+ return 'Kindred'
+
+ # Check if it grants protection broadly (not kindred-specific)
+ grants_protection = is_granting_protection(text, keywords, exclude_kindred=exclude_kindred)
+
+ # Categorize based on what it does
+ if grants_protection and has_cond_self:
+ # Has conditional self-grant + grants to others = Mixed
+ return 'Mixed'
+ elif grants_protection and has_inherent:
+ return 'Mixed' # Has inherent + grants broadly
+ elif grants_protection:
+ return 'Grant' # Only grants broadly
+ elif has_inherent:
+ return 'Inherent' # Only has inherent
+ else:
+ return 'Neither' # False positive
diff --git a/code/tagging/tag_constants.py b/code/tagging/tag_constants.py
index 30d70dc..6e5f3c4 100644
--- a/code/tagging/tag_constants.py
+++ b/code/tagging/tag_constants.py
@@ -849,4 +849,89 @@ TOPDECK_EXCLUSION_PATTERNS: List[str] = [
'from the top of their library',
'look at the top card of target player\'s library',
'reveal the top card of target player\'s library'
+]
+
+# ==============================================================================
+# Keyword Normalization (M1 - Tagging Refinement)
+# ==============================================================================
+
+# Keyword normalization map: variant -> canonical
+# Maps Commander-specific and variant keywords to their canonical forms
+KEYWORD_NORMALIZATION_MAP: Dict[str, str] = {
+ # Commander variants
+ 'Commander ninjutsu': 'Ninjutsu',
+ 'Commander Ninjutsu': 'Ninjutsu',
+
+ # Partner variants (already excluded but mapped for reference)
+ 'Partner with': 'Partner',
+ 'Choose a Background': 'Choose a Background', # Keep distinct
+ "Doctor's Companion": "Doctor's Companion", # Keep distinct
+
+ # Case normalization for common keywords (most are already correct)
+ 'flying': 'Flying',
+ 'trample': 'Trample',
+ 'vigilance': 'Vigilance',
+ 'haste': 'Haste',
+ 'deathtouch': 'Deathtouch',
+ 'lifelink': 'Lifelink',
+ 'menace': 'Menace',
+ 'reach': 'Reach',
+}
+
+# Keywords that should never appear in theme tags
+# Already excluded during keyword tagging, but documented here
+KEYWORD_EXCLUSION_SET: set[str] = {
+ 'partner', # Already excluded in tag_for_keywords
+}
+
+# Keyword allowlist - keywords that should survive singleton pruning
+# Seeded from top keywords and theme whitelist
+KEYWORD_ALLOWLIST: set[str] = {
+ # Evergreen keywords (top 50 from baseline)
+ 'Flying', 'Enchant', 'Trample', 'Vigilance', 'Haste', 'Equip', 'Flash',
+ 'Mill', 'Scry', 'Transform', 'Cycling', 'First strike', 'Reach', 'Menace',
+ 'Lifelink', 'Treasure', 'Defender', 'Deathtouch', 'Kicker', 'Flashback',
+ 'Protection', 'Surveil', 'Landfall', 'Crew', 'Ward', 'Morph', 'Devoid',
+ 'Investigate', 'Fight', 'Food', 'Partner', 'Double strike', 'Indestructible',
+ 'Threshold', 'Proliferate', 'Convoke', 'Hexproof', 'Cumulative upkeep',
+ 'Goad', 'Delirium', 'Prowess', 'Suspend', 'Affinity', 'Madness', 'Manifest',
+ 'Amass', 'Domain', 'Unearth', 'Explore', 'Changeling',
+
+ # Additional important mechanics
+ 'Myriad', 'Cascade', 'Storm', 'Dredge', 'Delve', 'Escape', 'Mutate',
+ 'Ninjutsu', 'Overload', 'Rebound', 'Retrace', 'Bloodrush', 'Cipher',
+ 'Extort', 'Evolve', 'Undying', 'Persist', 'Wither', 'Infect', 'Annihilator',
+ 'Exalted', 'Phasing', 'Shadow', 'Horsemanship', 'Banding', 'Rampage',
+ 'Shroud', 'Split second', 'Totem armor', 'Living weapon', 'Undaunted',
+ 'Improvise', 'Surge', 'Emerge', 'Escalate', 'Meld', 'Partner', 'Afflict',
+ 'Aftermath', 'Embalm', 'Eternalize', 'Exert', 'Fabricate', 'Improvise',
+ 'Assist', 'Jump-start', 'Mentor', 'Riot', 'Spectacle', 'Addendum',
+ 'Afterlife', 'Adapt', 'Enrage', 'Ascend', 'Learn', 'Boast', 'Foretell',
+ 'Squad', 'Encore', 'Daybound', 'Nightbound', 'Disturb', 'Cleave', 'Training',
+ 'Reconfigure', 'Blitz', 'Casualty', 'Connive', 'Hideaway', 'Prototype',
+ 'Read ahead', 'Living metal', 'More than meets the eye', 'Ravenous',
+ 'Squad', 'Toxic', 'For Mirrodin!', 'Backup', 'Bargain', 'Craft', 'Freerunning',
+ 'Plot', 'Spree', 'Offspring', 'Bestow', 'Monstrosity', 'Tribute',
+
+ # Partner mechanics (distinct types)
+ 'Choose a Background', "Doctor's Companion",
+
+ # Token types (frequently used)
+ 'Blood', 'Clue', 'Food', 'Gold', 'Treasure', 'Powerstone',
+
+ # Common ability words
+ 'Landfall', 'Raid', 'Revolt', 'Threshold', 'Metalcraft', 'Morbid',
+ 'Bloodthirst', 'Battalion', 'Channel', 'Grandeur', 'Kinship', 'Sweep',
+ 'Radiance', 'Join forces', 'Fateful hour', 'Inspired', 'Heroic',
+ 'Constellation', 'Strive', 'Prowess', 'Ferocious', 'Formidable', 'Renown',
+ 'Tempting offer', 'Will of the council', 'Parley', 'Adamant', 'Devotion',
+}
+
+# Metadata tag prefixes (for M3 - metadata partition)
+# Tags matching these patterns should be classified as metadata, not themes
+METADATA_TAG_PREFIXES: List[str] = [
+ 'Applied:',
+ 'Bracket:',
+ 'Diagnostic:',
+ 'Internal:',
]
\ No newline at end of file
diff --git a/code/tagging/tag_utils.py b/code/tagging/tag_utils.py
index 156f0f5..e731f07 100644
--- a/code/tagging/tag_utils.py
+++ b/code/tagging/tag_utils.py
@@ -509,4 +509,77 @@ def create_mass_damage_mask(df: pd.DataFrame) -> pd.Series[bool]:
damage_mask = create_text_mask(df, number_patterns)
target_mask = create_text_mask(df, target_patterns)
- return damage_mask & target_mask
\ No newline at end of file
+ return damage_mask & target_mask
+
+
+# ==============================================================================
+# Keyword Normalization (M1 - Tagging Refinement)
+# ==============================================================================
+
+def normalize_keywords(
+ raw: Union[List[str], Set[str], Tuple[str, ...]],
+ allowlist: Set[str],
+ frequency_map: dict[str, int]
+) -> list[str]:
+ """Normalize keyword strings for theme tagging.
+
+ Applies normalization rules:
+ 1. Case normalization (via normalization map)
+ 2. Canonical mapping (e.g., "Commander Ninjutsu" -> "Ninjutsu")
+ 3. Singleton pruning (unless allowlisted)
+ 4. Deduplication
+ 5. Exclusion of blacklisted keywords
+
+ Args:
+ raw: Iterable of raw keyword strings
+ allowlist: Set of keywords that should survive singleton pruning
+ frequency_map: Dict mapping keywords to their occurrence count
+
+ Returns:
+ Deduplicated list of normalized keywords
+
+ Raises:
+ ValueError: If raw is not iterable
+
+ Examples:
+ >>> normalize_keywords(
+ ... ['Commander Ninjutsu', 'Flying', 'Allons-y!'],
+ ... {'Flying', 'Ninjutsu'},
+ ... {'Commander Ninjutsu': 2, 'Flying': 100, 'Allons-y!': 1}
+ ... )
+ ['Ninjutsu', 'Flying'] # 'Allons-y!' pruned as singleton
+ """
+ if not hasattr(raw, '__iter__') or isinstance(raw, (str, bytes)):
+ raise ValueError(f"raw must be iterable, got {type(raw)}")
+
+ normalized_keywords: set[str] = set()
+
+ for keyword in raw:
+ # Skip non-string entries
+ if not isinstance(keyword, str):
+ continue
+
+ # Skip empty strings
+ keyword = keyword.strip()
+ if not keyword:
+ continue
+
+ # Skip excluded keywords
+ if keyword.lower() in tag_constants.KEYWORD_EXCLUSION_SET:
+ continue
+
+ # Apply normalization map
+ normalized = tag_constants.KEYWORD_NORMALIZATION_MAP.get(keyword, keyword)
+
+ # Check if singleton (unless allowlisted)
+ frequency = frequency_map.get(keyword, 0)
+ is_singleton = frequency == 1
+ is_allowlisted = normalized in allowlist or keyword in allowlist
+
+ # Prune singletons that aren't allowlisted
+ if is_singleton and not is_allowlisted:
+ continue
+
+ normalized_keywords.add(normalized)
+
+ return sorted(list(normalized_keywords))
\ No newline at end of file
diff --git a/code/tagging/tagger.py b/code/tagging/tagger.py
index 6d3c21e..b2b3f0b 100644
--- a/code/tagging/tagger.py
+++ b/code/tagging/tagger.py
@@ -580,6 +580,11 @@ def add_creatures_to_tags(df: pd.DataFrame, color: str) -> None:
## Add keywords to theme tags
def tag_for_keywords(df: pd.DataFrame, color: str) -> None:
"""Tag cards based on their keywords using vectorized operations.
+
+ When TAG_NORMALIZE_KEYWORDS is enabled, applies normalization:
+ - Canonical mapping (e.g., "Commander Ninjutsu" -> "Ninjutsu")
+ - Singleton pruning (unless allowlisted)
+ - Case normalization
Args:
df: DataFrame containing card data
@@ -589,6 +594,20 @@ def tag_for_keywords(df: pd.DataFrame, color: str) -> None:
start_time = pd.Timestamp.now()
try:
+ from settings import TAG_NORMALIZE_KEYWORDS
+
+ # Load frequency map if normalization is enabled
+ frequency_map: dict[str, int] = {}
+ if TAG_NORMALIZE_KEYWORDS:
+ freq_map_path = Path(__file__).parent / 'keyword_frequency_map.json'
+ if freq_map_path.exists():
+ with open(freq_map_path, 'r', encoding='utf-8') as f:
+ frequency_map = json.load(f)
+ logger.info('Loaded keyword frequency map with %d entries', len(frequency_map))
+ else:
+ logger.warning('Keyword frequency map not found, normalization disabled for this run')
+ TAG_NORMALIZE_KEYWORDS = False
+
# Create mask for valid keywords
has_keywords = pd.notna(df['keywords'])
@@ -608,17 +627,29 @@ def tag_for_keywords(df: pd.DataFrame, color: str) -> None:
else:
keywords_iterable = []
- filtered_keywords = [
- kw for kw in keywords_iterable
- if kw and kw.lower() not in exclusion_keywords
- ]
-
- return sorted(list(set(base_tags + filtered_keywords)))
+ # Apply normalization if enabled
+ if TAG_NORMALIZE_KEYWORDS and frequency_map:
+ normalized_keywords = tag_utils.normalize_keywords(
+ keywords_iterable,
+ tag_constants.KEYWORD_ALLOWLIST,
+ frequency_map
+ )
+ return sorted(list(set(base_tags + normalized_keywords)))
+ else:
+ # Legacy behavior: simple exclusion filter
+ filtered_keywords = [
+ kw for kw in keywords_iterable
+ if kw and kw.lower() not in exclusion_keywords
+ ]
+ return sorted(list(set(base_tags + filtered_keywords)))
df.loc[has_keywords, 'themeTags'] = keywords_df.apply(_merge_keywords, axis=1)
duration = (pd.Timestamp.now() - start_time).total_seconds()
logger.info('Tagged %d cards with keywords in %.2f seconds', has_keywords.sum(), duration)
+
+ if TAG_NORMALIZE_KEYWORDS:
+ logger.info('Keyword normalization enabled for %s', color)
except Exception as e:
logger.error('Error tagging keywords: %s', str(e))
@@ -7000,6 +7031,9 @@ def tag_for_protection(df: pd.DataFrame, color: str) -> None:
- Ward
- Phase out
+ With TAG_PROTECTION_GRANTS=1, only tags cards that grant protection to other
+ permanents, filtering out cards with inherent protection.
+
The function uses helper functions to identify different types of protection
and applies tags consistently using vectorized operations.
@@ -7025,13 +7059,47 @@ def tag_for_protection(df: pd.DataFrame, color: str) -> None:
required_cols = {'text', 'themeTags', 'keywords'}
tag_utils.validate_dataframe_columns(df, required_cols)
- # Create masks for different protection patterns
- text_mask = create_protection_text_mask(df)
- keyword_mask = create_protection_keyword_mask(df)
- exclusion_mask = create_protection_exclusion_mask(df)
+ # Check if grant detection is enabled (M2 feature flag)
+ use_grant_detection = os.getenv('TAG_PROTECTION_GRANTS', '1').lower() in ('1', 'true', 'yes')
- # Combine masks
- final_mask = (text_mask | keyword_mask) & ~exclusion_mask
+ if use_grant_detection:
+ # M2: Use grant detection to filter out inherent-only protection
+ from code.tagging.protection_grant_detection import is_granting_protection, get_kindred_protection_tags
+
+ # Create a grant detection mask
+ grant_mask = df.apply(
+ lambda row: is_granting_protection(
+ str(row.get('text', '')),
+ str(row.get('keywords', ''))
+ ),
+ axis=1
+ )
+
+ final_mask = grant_mask
+ logger.info(f'Using M2 grant detection (TAG_PROTECTION_GRANTS=1)')
+
+ # Apply kindred metadata tags for creature-type-specific grants
+ kindred_count = 0
+ for idx, row in df[final_mask].iterrows():
+ text = str(row.get('text', ''))
+ kindred_tags = get_kindred_protection_tags(text)
+
+ if kindred_tags:
+ # Add kindred-specific metadata tags
+ current_tags = str(row.get('metadataTags', ''))
+ existing = set(t.strip() for t in current_tags.split(',') if t.strip())
+ existing.update(kindred_tags)
+ df.at[idx, 'metadataTags'] = ', '.join(sorted(existing))
+ kindred_count += 1
+
+ if kindred_count > 0:
+ logger.info(f'Applied kindred metadata tags to {kindred_count} cards')
+ else:
+ # Legacy: Use original text/keyword patterns
+ text_mask = create_protection_text_mask(df)
+ keyword_mask = create_protection_keyword_mask(df)
+ exclusion_mask = create_protection_exclusion_mask(df)
+ final_mask = (text_mask | keyword_mask) & ~exclusion_mask
# Apply tags via rules engine
tag_utils.apply_rules(df, rules=[
diff --git a/code/tests/test_keyword_normalization.py b/code/tests/test_keyword_normalization.py
new file mode 100644
index 0000000..002adc8
--- /dev/null
+++ b/code/tests/test_keyword_normalization.py
@@ -0,0 +1,182 @@
+"""Tests for keyword normalization (M1 - Tagging Refinement)."""
+from __future__ import annotations
+
+import pytest
+
+from code.tagging import tag_utils, tag_constants
+
+
+class TestKeywordNormalization:
+ """Test suite for normalize_keywords function."""
+
+ def test_canonical_mappings(self):
+ """Test that variant keywords map to canonical forms."""
+ raw = ['Commander Ninjutsu', 'Flying', 'Trample']
+ allowlist = tag_constants.KEYWORD_ALLOWLIST
+ frequency_map = {
+ 'Commander Ninjutsu': 2,
+ 'Flying': 100,
+ 'Trample': 50
+ }
+
+ result = tag_utils.normalize_keywords(raw, allowlist, frequency_map)
+
+ assert 'Ninjutsu' in result
+ assert 'Flying' in result
+ assert 'Trample' in result
+ assert 'Commander Ninjutsu' not in result
+
+ def test_singleton_pruning(self):
+ """Test that singleton keywords are pruned unless allowlisted."""
+ raw = ['Allons-y!', 'Flying', 'Take 59 Flights of Stairs']
+ allowlist = {'Flying'} # Only Flying is allowlisted
+ frequency_map = {
+ 'Allons-y!': 1,
+ 'Flying': 100,
+ 'Take 59 Flights of Stairs': 1
+ }
+
+ result = tag_utils.normalize_keywords(raw, allowlist, frequency_map)
+
+ assert 'Flying' in result
+ assert 'Allons-y!' not in result
+ assert 'Take 59 Flights of Stairs' not in result
+
+ def test_case_normalization(self):
+ """Test that keywords are normalized to proper case."""
+ raw = ['flying', 'TRAMPLE', 'vigilance']
+ allowlist = {'Flying', 'Trample', 'Vigilance'}
+ frequency_map = {
+ 'flying': 100,
+ 'TRAMPLE': 50,
+ 'vigilance': 75
+ }
+
+ result = tag_utils.normalize_keywords(raw, allowlist, frequency_map)
+
+ # Case normalization happens via the map
+ # If not in map, original case is preserved
+ assert len(result) == 3
+
+ def test_partner_exclusion(self):
+ """Test that partner keywords remain excluded."""
+ raw = ['Partner', 'Flying', 'Trample']
+ allowlist = {'Flying', 'Trample'}
+ frequency_map = {
+ 'Partner': 50,
+ 'Flying': 100,
+ 'Trample': 50
+ }
+
+ result = tag_utils.normalize_keywords(raw, allowlist, frequency_map)
+
+ assert 'Flying' in result
+ assert 'Trample' in result
+ assert 'Partner' not in result # Excluded
+ assert 'partner' not in result
+
+ def test_empty_input(self):
+ """Test that empty input returns empty list."""
+ result = tag_utils.normalize_keywords([], set(), {})
+ assert result == []
+
+ def test_whitespace_handling(self):
+ """Test that whitespace is properly stripped."""
+ raw = [' Flying ', 'Trample ', ' Vigilance']
+ allowlist = {'Flying', 'Trample', 'Vigilance'}
+ frequency_map = {
+ 'Flying': 100,
+ 'Trample': 50,
+ 'Vigilance': 75
+ }
+
+ result = tag_utils.normalize_keywords(raw, allowlist, frequency_map)
+
+ assert 'Flying' in result
+ assert 'Trample' in result
+ assert 'Vigilance' in result
+
+ def test_deduplication(self):
+ """Test that duplicate keywords are deduplicated."""
+ raw = ['Flying', 'Flying', 'Trample', 'Flying']
+ allowlist = {'Flying', 'Trample'}
+ frequency_map = {
+ 'Flying': 100,
+ 'Trample': 50
+ }
+
+ result = tag_utils.normalize_keywords(raw, allowlist, frequency_map)
+
+ assert result.count('Flying') == 1
+ assert result.count('Trample') == 1
+
+ def test_non_string_entries_skipped(self):
+ """Test that non-string entries are safely skipped."""
+ raw = ['Flying', None, 123, 'Trample', '']
+ allowlist = {'Flying', 'Trample'}
+ frequency_map = {
+ 'Flying': 100,
+ 'Trample': 50
+ }
+
+ result = tag_utils.normalize_keywords(raw, allowlist, frequency_map)
+
+ assert 'Flying' in result
+ assert 'Trample' in result
+ assert len(result) == 2
+
+ def test_invalid_input_raises_error(self):
+ """Test that non-iterable input raises ValueError."""
+ with pytest.raises(ValueError, match="raw must be iterable"):
+ tag_utils.normalize_keywords("not-a-list", set(), {})
+
+ def test_allowlist_preserves_singletons(self):
+ """Test that allowlisted keywords survive even if they're singletons."""
+ raw = ['Myriad', 'Flying', 'Cascade']
+ allowlist = {'Flying', 'Myriad', 'Cascade'} # All allowlisted
+ frequency_map = {
+ 'Myriad': 1, # Singleton
+ 'Flying': 100,
+ 'Cascade': 1 # Singleton
+ }
+
+ result = tag_utils.normalize_keywords(raw, allowlist, frequency_map)
+
+ assert 'Myriad' in result # Preserved despite being singleton
+ assert 'Flying' in result
+ assert 'Cascade' in result # Preserved despite being singleton
+
+
+class TestKeywordIntegration:
+ """Integration tests for keyword normalization in tagging flow."""
+
+ def test_normalization_preserves_evergreen_keywords(self):
+ """Test that common evergreen keywords are always preserved."""
+ evergreen = ['Flying', 'Trample', 'Vigilance', 'Haste', 'Deathtouch', 'Lifelink']
+ allowlist = tag_constants.KEYWORD_ALLOWLIST
+ frequency_map = {kw: 100 for kw in evergreen} # All common
+
+ result = tag_utils.normalize_keywords(evergreen, allowlist, frequency_map)
+
+ for kw in evergreen:
+ assert kw in result
+
+ def test_crossover_keywords_pruned(self):
+ """Test that crossover-specific singletons are pruned."""
+ crossover_singletons = [
+ 'Gae Bolg', # Final Fantasy
+ 'Psychic Defense', # Warhammer 40K
+ 'Allons-y!', # Doctor Who
+ 'Flying' # Evergreen (control)
+ ]
+ allowlist = {'Flying'} # Only Flying allowed
+ frequency_map = {
+ 'Gae Bolg': 1,
+ 'Psychic Defense': 1,
+ 'Allons-y!': 1,
+ 'Flying': 100
+ }
+
+ result = tag_utils.normalize_keywords(crossover_singletons, allowlist, frequency_map)
+
+ assert result == ['Flying'] # Only evergreen survived
diff --git a/code/tests/test_protection_grant_detection.py b/code/tests/test_protection_grant_detection.py
new file mode 100644
index 0000000..435377b
--- /dev/null
+++ b/code/tests/test_protection_grant_detection.py
@@ -0,0 +1,169 @@
+"""
+Tests for protection grant detection (M2).
+
+Tests the ability to distinguish between cards that grant protection
+and cards that have inherent protection.
+"""
+
+import pytest
+from code.tagging.protection_grant_detection import (
+ is_granting_protection,
+ categorize_protection_card
+)
+
+
+class TestGrantDetection:
+ """Test grant verb detection."""
+
+ def test_gains_hexproof(self):
+ """Cards with 'gains hexproof' should be detected as granting."""
+ text = "Target creature gains hexproof until end of turn."
+ assert is_granting_protection(text, "")
+
+ def test_gives_indestructible(self):
+ """Cards with 'gives indestructible' should be detected as granting."""
+ text = "This creature gives target creature indestructible."
+ assert is_granting_protection(text, "")
+
+ def test_creatures_you_control_have(self):
+ """Mass grant pattern should be detected."""
+ text = "Creatures you control have hexproof."
+ assert is_granting_protection(text, "")
+
+ def test_equipped_creature_gets(self):
+ """Equipment grant pattern should be detected."""
+ text = "Equipped creature gets +2/+2 and has indestructible."
+ assert is_granting_protection(text, "")
+
+
+class TestInherentDetection:
+ """Test inherent protection detection."""
+
+ def test_creature_with_hexproof_keyword(self):
+ """Creature with hexproof keyword should not be detected as granting."""
+ text = "Hexproof (This creature can't be the target of spells or abilities.)"
+ keywords = "Hexproof"
+ assert not is_granting_protection(text, keywords)
+
+ def test_indestructible_artifact(self):
+ """Artifact with indestructible keyword should not be detected as granting."""
+ text = "Indestructible"
+ keywords = "Indestructible"
+ assert not is_granting_protection(text, keywords)
+
+ def test_ward_creature(self):
+ """Creature with Ward should not be detected as granting (unless it grants to others)."""
+ text = "Ward {2}"
+ keywords = "Ward"
+ assert not is_granting_protection(text, keywords)
+
+
+class TestMixedCases:
+ """Test cards that both grant and have protection."""
+
+ def test_creature_with_self_grant(self):
+ """Creature that grants itself protection should be detected."""
+ text = "This creature gains indestructible until end of turn."
+ keywords = ""
+ assert is_granting_protection(text, keywords)
+
+ def test_equipment_with_inherent_and_grant(self):
+ """Equipment with indestructible that grants protection."""
+ text = "Indestructible. Equipped creature has hexproof."
+ keywords = "Indestructible"
+ # Should be detected as granting because of "has hexproof"
+ assert is_granting_protection(text, keywords)
+
+
+class TestExclusions:
+ """Test exclusion patterns."""
+
+ def test_cant_have_hexproof(self):
+ """Cards that prevent protection should not be tagged."""
+ text = "Creatures your opponents control can't have hexproof."
+ assert not is_granting_protection(text, "")
+
+ def test_loses_indestructible(self):
+ """Cards that remove protection should not be tagged."""
+ text = "Target creature loses indestructible until end of turn."
+ assert not is_granting_protection(text, "")
+
+
+class TestEdgeCases:
+ """Test edge cases and special patterns."""
+
+ def test_protection_from_color(self):
+ """Protection from [quality] in keywords without grant text."""
+ text = "Protection from red"
+ keywords = "Protection from red"
+ assert not is_granting_protection(text, keywords)
+
+ def test_empty_text(self):
+ """Empty text should return False."""
+ assert not is_granting_protection("", "")
+
+ def test_none_text(self):
+ """None text should return False."""
+ assert not is_granting_protection(None, "")
+
+
+class TestCategorization:
+ """Test full card categorization."""
+
+ def test_shell_shield_is_grant(self):
+ """Shell Shield grants hexproof - should be Grant."""
+ text = "Target creature gets +0/+3 and gains hexproof until end of turn."
+ cat = categorize_protection_card("Shell Shield", text, "", "Instant")
+ assert cat == "Grant"
+
+ def test_geist_of_saint_traft_is_mixed(self):
+ """Geist has hexproof and creates tokens - Mixed."""
+ text = "Hexproof. Whenever this attacks, create a token."
+ keywords = "Hexproof"
+ cat = categorize_protection_card("Geist", text, keywords, "Creature")
+ # Has hexproof keyword, so inherent
+ assert cat in ("Inherent", "Mixed")
+
+ def test_darksteel_brute_is_inherent(self):
+ """Darksteel Brute has indestructible - should be Inherent."""
+ text = "Indestructible"
+ keywords = "Indestructible"
+ cat = categorize_protection_card("Darksteel Brute", text, keywords, "Artifact")
+ assert cat == "Inherent"
+
+ def test_scion_of_oona_is_grant(self):
+ """Scion of Oona grants shroud to other faeries - should be Grant."""
+ text = "Other Faeries you control have shroud."
+ keywords = "Flying, Flash"
+ cat = categorize_protection_card("Scion of Oona", text, keywords, "Creature")
+ assert cat == "Grant"
+
+
+class TestRealWorldCards:
+ """Test against actual card samples from baseline audit."""
+
+ def test_bulwark_ox(self):
+ """Bulwark Ox - grants hexproof and indestructible."""
+ text = "Sacrifice: Creatures you control with counters gain hexproof and indestructible"
+ assert is_granting_protection(text, "")
+
+ def test_bloodsworn_squire(self):
+ """Bloodsworn Squire - grants itself indestructible."""
+ text = "This creature gains indestructible until end of turn"
+ assert is_granting_protection(text, "")
+
+ def test_kaldra_compleat(self):
+ """Kaldra Compleat - equipment with indestructible that grants."""
+ text = "Indestructible. Equipped creature gets +5/+5 and has indestructible"
+ keywords = "Indestructible"
+ assert is_granting_protection(text, keywords)
+
+ def test_ward_sliver(self):
+ """Ward Sliver - grants protection to all slivers."""
+ text = "All Slivers have protection from the chosen color"
+ assert is_granting_protection(text, "")
+
+ def test_rebbec(self):
+ """Rebbec - grants protection to artifacts."""
+ text = "Artifacts you control have protection from each mana value"
+ assert is_granting_protection(text, "")
diff --git a/code/web/routes/build.py b/code/web/routes/build.py
index e058ed6..676ae71 100644
--- a/code/web/routes/build.py
+++ b/code/web/routes/build.py
@@ -170,7 +170,7 @@ def _step5_summary_placeholder_html(token: int, *, message: str | None = None) -
return (
f'
{% if summary_ready %}Loading deck summary…{% else %}Deck summary will appear after the build completes.{% endif %}
diff --git a/code/web/templates/setup/running.html b/code/web/templates/setup/running.html
index eb94c8a..e272a3a 100644
--- a/code/web/templates/setup/running.html
+++ b/code/web/templates/setup/running.html
@@ -127,7 +127,8 @@
.then(update)
.catch(function(){});
}
- setInterval(poll, 3000);
+ // Poll every 5 seconds instead of 3 to reduce server load
+ setInterval(poll, 5000);
poll();
})();
diff --git a/config/themes/theme_list.json b/config/themes/theme_list.json
index 1047f02..ba5fa1d 100644
--- a/config/themes/theme_list.json
+++ b/config/themes/theme_list.json
@@ -47,9 +47,9 @@
"primary_color": "Black",
"secondary_color": "Blue",
"example_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Rishkar, Peema Renegade - Synergy (Counters Matter)",
- "Krenko, Tin Street Kingpin - Synergy (Counters Matter)"
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
+ "Yawgmoth, Thran Physician - Synergy (Counters Matter)"
],
"example_cards": [
"Wall of Roots",
@@ -118,9 +118,6 @@
"Yawgmoth, Thran Physician",
"Tezzeret's Gambit"
],
- "synergy_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Infect)"
- ],
"popularity_bucket": "Common",
"editorial_quality": "draft",
"description": "Spreads -1/-1 counters for removal, attrition, and loop engines leveraging death & sacrifice triggers. Synergies like Proliferate and Counters Matter reinforce the plan."
@@ -155,7 +152,7 @@
"Silverflame Ritual"
],
"synergy_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Yawgmoth, Thran Physician - Synergy (Counters Matter)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -191,8 +188,8 @@
"Jetfire, Ingenious Scientist // Jetfire, Air Guardian"
],
"synergy_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Yahenni, Undying Partisan - Synergy (Counters Matter)",
+ "Heliod, Sun-Crowned - Synergy (Counters Matter)",
"Sram, Senior Edificer - Synergy (Voltron)"
],
"popularity_bucket": "Rare",
@@ -211,8 +208,8 @@
"secondary_color": "Blue",
"example_commanders": [
"Syr Konrad, the Grim - Synergy (Interaction)",
- "Toski, Bearer of Secrets - Synergy (Interaction)",
"Purphoros, God of the Forge - Synergy (Interaction)",
+ "Boromir, Warden of the Tower - Synergy (Interaction)",
"Lotho, Corrupt Shirriff - Synergy (Spells Matter)",
"Birgi, God of Storytelling // Harnfel, Horn of Bounty - Synergy (Spells Matter)"
],
@@ -490,7 +487,7 @@
"Cosima, God of the Voyage // The Omenkeel"
],
"synergy_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Rishkar, Peema Renegade - Synergy (Counters Matter)"
],
"popularity_bucket": "Niche",
"editorial_quality": "draft",
@@ -560,11 +557,11 @@
"id": "alien-kindred",
"theme": "Alien Kindred",
"synergies": [
+ "Clones",
"Horror Kindred",
"Exile Matters",
"Trample",
- "Protection",
- "Counters Matter"
+ "Soldier Kindred"
],
"primary_color": "Blue",
"secondary_color": "Green",
@@ -586,12 +583,12 @@
"Time Beetle"
],
"synergy_commanders": [
- "Mondrak, Glory Dominus - Synergy (Horror Kindred)",
+ "Mondrak, Glory Dominus - Synergy (Clones)",
+ "Kiki-Jiki, Mirror Breaker - Synergy (Clones)",
+ "Sakashima of a Thousand Faces - Synergy (Clones)",
"Solphim, Mayhem Dominus - Synergy (Horror Kindred)",
"Zopandrel, Hunger Dominus - Synergy (Horror Kindred)",
- "Etali, Primal Storm - Synergy (Exile Matters)",
- "Ragavan, Nimble Pilferer - Synergy (Exile Matters)",
- "Ghalta, Primal Hunger - Synergy (Trample)"
+ "Etali, Primal Storm - Synergy (Exile Matters)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -725,8 +722,8 @@
"Rishkar, Peema Renegade - Synergy (+1/+1 Counters)",
"Krenko, Tin Street Kingpin - Synergy (+1/+1 Counters)",
"Yawgmoth, Thran Physician - Synergy (+1/+1 Counters)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
- "Yahenni, Undying Partisan - Synergy (Counters Matter)"
+ "Yahenni, Undying Partisan - Synergy (Counters Matter)",
+ "Heliod, Sun-Crowned - Synergy (Counters Matter)"
],
"example_cards": [
"Kilnmouth Dragon",
@@ -848,7 +845,7 @@
"Artifacts Matter",
"Big Mana",
"Toughness Matters",
- "Aggro"
+ "Interaction"
],
"primary_color": "Green",
"secondary_color": "Red",
@@ -2435,7 +2432,7 @@
"Ziatora's Envoy",
"Mezzio Mugger",
"Sabin, Master Monk",
- "Night Clubber"
+ "Riveteers Requisitioner"
],
"synergy_commanders": [
"Kutzil, Malamet Exemplar - Synergy (Warrior Kindred)",
@@ -2459,21 +2456,21 @@
"primary_color": "Red",
"secondary_color": "Black",
"example_commanders": [
- "Edgar, Charmed Groom // Edgar Markov's Coffin",
"Old Rutstein",
"Kamber, the Plunderer",
"Strefan, Maurer Progenitor",
- "Anje, Maid of Dishonor"
+ "Anje, Maid of Dishonor",
+ "Shilgengar, Sire of Famine"
],
"example_cards": [
"Voldaren Estate",
"Blood for the Blood God!",
- "Edgar, Charmed Groom // Edgar Markov's Coffin",
"Old Rutstein",
"Transmutation Font",
"Glass-Cast Heart",
"Voldaren Epicure",
- "Font of Agonies"
+ "Font of Agonies",
+ "Exsanguinator Cavalry"
],
"synergy_commanders": [
"Indoraptor, the Perfect Hybrid - Synergy (Bloodthirst)",
@@ -2494,9 +2491,9 @@
"primary_color": "Red",
"secondary_color": "Green",
"example_commanders": [
- "Edgar, Charmed Groom // Edgar Markov's Coffin - Synergy (Blood Token)",
"Old Rutstein - Synergy (Blood Token)",
"Kamber, the Plunderer - Synergy (Blood Token)",
+ "Strefan, Maurer Progenitor - Synergy (Blood Token)",
"Etali, Primal Storm - Synergy (Aggro)",
"Ragavan, Nimble Pilferer - Synergy (Aggro)"
],
@@ -2531,9 +2528,9 @@
"secondary_color": "Green",
"example_commanders": [
"Indoraptor, the Perfect Hybrid",
- "Edgar, Charmed Groom // Edgar Markov's Coffin - Synergy (Blood Token)",
"Old Rutstein - Synergy (Blood Token)",
"Kamber, the Plunderer - Synergy (Blood Token)",
+ "Strefan, Maurer Progenitor - Synergy (Blood Token)",
"Rishkar, Peema Renegade - Synergy (+1/+1 Counters)"
],
"example_cards": [
@@ -2626,8 +2623,7 @@
"synergy_commanders": [
"Hokori, Dust Drinker - Synergy (Bracket:MassLandDenial)",
"Myojin of Infinite Rage - Synergy (Bracket:MassLandDenial)",
- "Elas il-Kor, Sadistic Pilgrim - Synergy (Pingers)",
- "Toski, Bearer of Secrets - Synergy (Interaction)"
+ "Elas il-Kor, Sadistic Pilgrim - Synergy (Pingers)"
],
"popularity_bucket": "Very Common",
"editorial_quality": "draft",
@@ -2705,7 +2701,7 @@
"Yawgmoth, Thran Physician - Synergy (+1/+1 Counters)",
"Samut, Voice of Dissent - Synergy (Combat Tricks)",
"Naru Meha, Master Wizard - Synergy (Combat Tricks)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Yahenni, Undying Partisan - Synergy (Counters Matter)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -2841,8 +2837,8 @@
"Static Orb"
],
"synergy_commanders": [
- "Toski, Bearer of Secrets - Synergy (Interaction)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Interaction)",
+ "Boromir, Warden of the Tower - Synergy (Interaction)",
+ "Avacyn, Angel of Hope - Synergy (Interaction)",
"Lotho, Corrupt Shirriff - Synergy (Spells Matter)"
],
"popularity_bucket": "Niche",
@@ -3197,7 +3193,6 @@
"Seasoned Dungeoneer"
],
"synergy_commanders": [
- "Kellan, Daring Traveler // Journey On - Synergy (Map Token)",
"Selvala, Heart of the Wilds - Synergy (Scout Kindred)"
],
"popularity_bucket": "Niche",
@@ -3354,8 +3349,8 @@
"Kutzil, Malamet Exemplar",
"Felidar Retreat",
"Displacer Kitten",
- "Temur Sabertooth",
"Enduring Curiosity",
+ "Temur Sabertooth",
"Lion Sash",
"Ocelot Pride",
"Felidar Guardian"
@@ -3580,7 +3575,7 @@
"synergy_commanders": [
"Codsworth, Handy Helper - Synergy (Mana Rock)",
"Karn, Legacy Reforged - Synergy (Mana Rock)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Rishkar, Peema Renegade - Synergy (Counters Matter)"
],
"popularity_bucket": "Niche",
"editorial_quality": "draft",
@@ -4478,8 +4473,8 @@
"id": "convoke",
"theme": "Convoke",
"synergies": [
- "Knight Kindred",
"Big Mana",
+ "Knight Kindred",
"Toolbox",
"Combat Tricks",
"Removal"
@@ -4491,7 +4486,7 @@
"The Wandering Rescuer",
"Hogaak, Arisen Necropolis",
"Kasla, the Broken Halo",
- "Syr Konrad, the Grim - Synergy (Knight Kindred)"
+ "Syr Konrad, the Grim - Synergy (Big Mana)"
],
"example_cards": [
"Chord of Calling",
@@ -4504,10 +4499,10 @@
"March of the Multitudes"
],
"synergy_commanders": [
- "Adeline, Resplendent Cathar - Synergy (Knight Kindred)",
- "Danitha Capashen, Paragon - Synergy (Knight Kindred)",
"Etali, Primal Storm - Synergy (Big Mana)",
"Tatyova, Benthic Druid - Synergy (Big Mana)",
+ "Adeline, Resplendent Cathar - Synergy (Knight Kindred)",
+ "Danitha Capashen, Paragon - Synergy (Knight Kindred)",
"Junji, the Midnight Sky - Synergy (Toolbox)"
],
"popularity_bucket": "Niche",
@@ -4546,9 +4541,9 @@
"secondary_color": "Green",
"example_commanders": [
"Ixhel, Scion of Atraxa",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Poison Counters)",
"Skrelv, Defector Mite - Synergy (Poison Counters)",
"Skithiryx, the Blight Dragon - Synergy (Poison Counters)",
+ "Fynn, the Fangbearer - Synergy (Poison Counters)",
"Yawgmoth, Thran Physician - Synergy (Infect)"
],
"example_cards": [
@@ -4687,11 +4682,11 @@
"primary_color": "Green",
"secondary_color": "White",
"example_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness",
"Rishkar, Peema Renegade",
"Krenko, Tin Street Kingpin",
"Yawgmoth, Thran Physician",
- "Yahenni, Undying Partisan"
+ "Yahenni, Undying Partisan",
+ "Heliod, Sun-Crowned"
],
"example_cards": [
"The One Ring",
@@ -4993,9 +4988,9 @@
"example_commanders": [
"The Pride of Hull Clade",
"Kalakscion, Hunger Tyrant",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Rishkar, Peema Renegade - Synergy (Counters Matter)",
- "Krenko, Tin Street Kingpin - Synergy (Counters Matter)"
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
+ "Yawgmoth, Thran Physician - Synergy (Counters Matter)"
],
"example_cards": [
"The Pride of Hull Clade",
@@ -5008,8 +5003,8 @@
"Algae Gharial"
],
"synergy_commanders": [
- "Yawgmoth, Thran Physician - Synergy (+1/+1 Counters)",
"Yahenni, Undying Partisan - Synergy (+1/+1 Counters)",
+ "Heliod, Sun-Crowned - Synergy (+1/+1 Counters)",
"Azusa, Lost but Seeking - Synergy (Toughness Matters)"
],
"popularity_bucket": "Rare",
@@ -5046,8 +5041,8 @@
"Elephant Grass"
],
"synergy_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Rishkar, Peema Renegade - Synergy (Counters Matter)",
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
"Sram, Senior Edificer - Synergy (Enchantments Matter)"
],
"popularity_bucket": "Niche",
@@ -5590,8 +5585,8 @@
"Azusa, Lost but Seeking - Synergy (Lands Matter)",
"Tatyova, Benthic Druid - Synergy (Lands Matter)",
"Sheoldred, Whispering One - Synergy (Lands Matter)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
- "Rishkar, Peema Renegade - Synergy (Counters Matter)"
+ "Rishkar, Peema Renegade - Synergy (Counters Matter)",
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)"
],
"example_cards": [
"Sandstone Needle",
@@ -5745,7 +5740,7 @@
"Rishkar, Peema Renegade - Synergy (+1/+1 Counters)",
"Krenko, Tin Street Kingpin - Synergy (+1/+1 Counters)",
"Yawgmoth, Thran Physician - Synergy (+1/+1 Counters)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Yahenni, Undying Partisan - Synergy (Counters Matter)"
],
"example_cards": [
"Scourge of the Throne",
@@ -5758,7 +5753,7 @@
"Marchesa's Emissary"
],
"synergy_commanders": [
- "Yahenni, Undying Partisan - Synergy (Counters Matter)",
+ "Heliod, Sun-Crowned - Synergy (Counters Matter)",
"Sram, Senior Edificer - Synergy (Voltron)"
],
"popularity_bucket": "Rare",
@@ -5867,7 +5862,7 @@
"Rishkar, Peema Renegade - Synergy (+1/+1 Counters)",
"Krenko, Tin Street Kingpin - Synergy (+1/+1 Counters)",
"Yawgmoth, Thran Physician - Synergy (+1/+1 Counters)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Yahenni, Undying Partisan - Synergy (Counters Matter)"
],
"example_cards": [
"Mycoloth",
@@ -5880,7 +5875,7 @@
"Voracious Dragon"
],
"synergy_commanders": [
- "Yahenni, Undying Partisan - Synergy (Counters Matter)",
+ "Heliod, Sun-Crowned - Synergy (Counters Matter)",
"Sram, Senior Edificer - Synergy (Voltron)"
],
"popularity_bucket": "Rare",
@@ -6086,10 +6081,8 @@
"id": "divinity-counters",
"theme": "Divinity Counters",
"synergies": [
- "Protection",
"Spirit Kindred",
"Counters Matter",
- "Interaction",
"Big Mana"
],
"primary_color": "White",
@@ -6111,12 +6104,12 @@
"Myojin of Infinite Rage"
],
"synergy_commanders": [
- "Toski, Bearer of Secrets - Synergy (Protection)",
- "Purphoros, God of the Forge - Synergy (Protection)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Protection)",
"Kodama of the West Tree - Synergy (Spirit Kindred)",
"Kodama of the East Tree - Synergy (Spirit Kindred)",
- "Rishkar, Peema Renegade - Synergy (Counters Matter)"
+ "Junji, the Midnight Sky - Synergy (Spirit Kindred)",
+ "Rishkar, Peema Renegade - Synergy (Counters Matter)",
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
+ "Syr Konrad, the Grim - Synergy (Big Mana)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -6783,7 +6776,6 @@
"synergies": [
"Flying",
"Burn",
- "Interaction",
"Little Fellas"
],
"primary_color": "Red",
@@ -6793,7 +6785,7 @@
"Plargg and Nassari",
"Yusri, Fortune's Flame",
"Najal, the Storm Runner",
- "Uvilda, Dean of Perfection // Nassari, Dean of Expression"
+ "Niv-Mizzet, Parun - Synergy (Flying)"
],
"example_cards": [
"Veyran, Voice of Duality",
@@ -6802,16 +6794,15 @@
"Najal, the Storm Runner",
"Frenetic Efreet",
"Efreet Flamepainter",
- "Uvilda, Dean of Perfection // Nassari, Dean of Expression",
- "Emissary of Grudges"
+ "Emissary of Grudges",
+ "Capricious Efreet"
],
"synergy_commanders": [
- "Niv-Mizzet, Parun - Synergy (Flying)",
"Avacyn, Angel of Hope - Synergy (Flying)",
"Old Gnawbone - Synergy (Flying)",
"Syr Konrad, the Grim - Synergy (Burn)",
"Braids, Arisen Nightmare - Synergy (Burn)",
- "Toski, Bearer of Secrets - Synergy (Interaction)"
+ "Ragavan, Nimble Pilferer - Synergy (Little Fellas)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -7568,7 +7559,7 @@
"Combat Tricks",
"Spells Matter",
"Spellslinger",
- "Removal"
+ "Interaction"
],
"primary_color": "Green",
"secondary_color": "Black",
@@ -7757,8 +7748,8 @@
"secondary_color": "White",
"example_commanders": [
"Syr Konrad, the Grim - Synergy (Interaction)",
- "Toski, Bearer of Secrets - Synergy (Interaction)",
"Purphoros, God of the Forge - Synergy (Interaction)",
+ "Boromir, Warden of the Tower - Synergy (Interaction)",
"Lotho, Corrupt Shirriff - Synergy (Spells Matter)",
"Birgi, God of Storytelling // Harnfel, Horn of Bounty - Synergy (Spells Matter)"
],
@@ -7904,7 +7895,7 @@
"Rishkar, Peema Renegade - Synergy (+1/+1 Counters)",
"Krenko, Tin Street Kingpin - Synergy (+1/+1 Counters)",
"Yawgmoth, Thran Physician - Synergy (+1/+1 Counters)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Yahenni, Undying Partisan - Synergy (Counters Matter)"
],
"example_cards": [
"Gyre Sage",
@@ -7917,7 +7908,7 @@
"Experiment One"
],
"synergy_commanders": [
- "Yahenni, Undying Partisan - Synergy (Counters Matter)",
+ "Heliod, Sun-Crowned - Synergy (Counters Matter)",
"Sram, Senior Edificer - Synergy (Voltron)"
],
"popularity_bucket": "Rare",
@@ -8029,7 +8020,7 @@
"The Indomitable - Synergy (Vehicles)",
"Rishkar, Peema Renegade - Synergy (+1/+1 Counters)",
"Krenko, Tin Street Kingpin - Synergy (+1/+1 Counters)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Yawgmoth, Thran Physician - Synergy (Counters Matter)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -8168,7 +8159,6 @@
"Nicanzil, Current Conductor"
],
"synergy_commanders": [
- "Kellan, Daring Traveler // Journey On - Synergy (Map Token)",
"Selvala, Heart of the Wilds - Synergy (Scout Kindred)"
],
"popularity_bucket": "Niche",
@@ -8290,14 +8280,13 @@
"synergies": [
"Fading",
"Counters Matter",
- "Enchantments Matter",
- "Interaction"
+ "Enchantments Matter"
],
"primary_color": "Green",
"secondary_color": "Black",
"example_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Rishkar, Peema Renegade - Synergy (Counters Matter)",
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
"Sram, Senior Edificer - Synergy (Enchantments Matter)"
],
"example_cards": [
@@ -8320,14 +8309,13 @@
"synergies": [
"Fade Counters",
"Counters Matter",
- "Enchantments Matter",
- "Interaction"
+ "Enchantments Matter"
],
"primary_color": "Green",
"secondary_color": "Black",
"example_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Rishkar, Peema Renegade - Synergy (Counters Matter)",
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
"Sram, Senior Edificer - Synergy (Enchantments Matter)"
],
"example_cards": [
@@ -8490,8 +8478,7 @@
"synergies": [
"Big Mana",
"Spells Matter",
- "Spellslinger",
- "Interaction"
+ "Spellslinger"
],
"primary_color": "Green",
"secondary_color": "Red",
@@ -8604,8 +8591,8 @@
"Syr Konrad, the Grim - Synergy (Mill)",
"Emry, Lurker of the Loch - Synergy (Mill)",
"Sheoldred, Whispering One - Synergy (Mill)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Rishkar, Peema Renegade - Synergy (Counters Matter)",
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
"Six - Synergy (Reanimate)"
],
"popularity_bucket": "Rare",
@@ -8658,8 +8645,8 @@
"Banding",
"Kithkin Kindred",
"Knight Kindred",
- "Minotaur Kindred",
- "Angel Kindred"
+ "Partner",
+ "Minotaur Kindred"
],
"primary_color": "White",
"secondary_color": "Red",
@@ -8940,7 +8927,6 @@
],
"synergy_commanders": [
"Otharri, Suns' Glory - Synergy (Phoenix Kindred)",
- "Joshua, Phoenix's Dominant // Phoenix, Warden of Fire - Synergy (Phoenix Kindred)",
"Syrix, Carrier of the Flame - Synergy (Phoenix Kindred)",
"Ezrim, Agency Chief - Synergy (Archon Kindred)",
"Krond the Dawn-Clad - Synergy (Archon Kindred)",
@@ -9346,8 +9332,8 @@
],
"synergy_commanders": [
"Yawgmoth, Thran Physician - Synergy (+1/+1 Counters)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Yahenni, Undying Partisan - Synergy (Counters Matter)",
+ "Heliod, Sun-Crowned - Synergy (Counters Matter)",
"Sram, Senior Edificer - Synergy (Voltron)"
],
"popularity_bucket": "Rare",
@@ -9902,10 +9888,10 @@
"theme": "God Kindred",
"synergies": [
"Indestructible",
- "Protection",
"Transform",
"Midrange",
- "Exile Matters"
+ "Exile Matters",
+ "Sacrifice Matters"
],
"primary_color": "Black",
"secondary_color": "White",
@@ -9929,8 +9915,8 @@
"synergy_commanders": [
"Toski, Bearer of Secrets - Synergy (Indestructible)",
"Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Indestructible)",
- "Boromir, Warden of the Tower - Synergy (Protection)",
- "Avacyn, Angel of Hope - Synergy (Protection)"
+ "Veyran, Voice of Duality - Synergy (Transform)",
+ "Rishkar, Peema Renegade - Synergy (Midrange)"
],
"popularity_bucket": "Niche",
"editorial_quality": "draft",
@@ -10088,7 +10074,7 @@
"Novijen Sages"
],
"synergy_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Yawgmoth, Thran Physician - Synergy (Counters Matter)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -10486,7 +10472,7 @@
"Ulasht, the Hate Seed - Synergy (Hellion Kindred)",
"Thromok the Insatiable - Synergy (Hellion Kindred)",
"Otharri, Suns' Glory - Synergy (Phoenix Kindred)",
- "Joshua, Phoenix's Dominant // Phoenix, Warden of Fire - Synergy (Phoenix Kindred)"
+ "Syrix, Carrier of the Flame - Synergy (Phoenix Kindred)"
],
"popularity_bucket": "Common",
"editorial_quality": "draft",
@@ -10714,10 +10700,10 @@
"theme": "Hexproof",
"synergies": [
"Hexproof from",
- "Protection",
"Stax",
- "Interaction",
- "Beast Kindred"
+ "Beast Kindred",
+ "Elemental Kindred",
+ "Outlaw Kindred"
],
"primary_color": "Green",
"secondary_color": "Blue",
@@ -10740,9 +10726,9 @@
],
"synergy_commanders": [
"Niv-Mizzet, Guildpact - Synergy (Hexproof from)",
- "Toski, Bearer of Secrets - Synergy (Protection)",
- "Purphoros, God of the Forge - Synergy (Protection)",
- "Kutzil, Malamet Exemplar - Synergy (Stax)"
+ "Kutzil, Malamet Exemplar - Synergy (Stax)",
+ "Lotho, Corrupt Shirriff - Synergy (Stax)",
+ "Loot, Exuberant Explorer - Synergy (Beast Kindred)"
],
"popularity_bucket": "Niche",
"editorial_quality": "draft",
@@ -10752,9 +10738,7 @@
"id": "hexproof-from",
"theme": "Hexproof from",
"synergies": [
- "Hexproof",
- "Protection",
- "Interaction"
+ "Hexproof"
],
"primary_color": "Black",
"secondary_color": "Green",
@@ -10776,10 +10760,7 @@
"Niv-Mizzet, Supreme"
],
"synergy_commanders": [
- "Silumgar, the Drifting Death - Synergy (Hexproof)",
- "Toski, Bearer of Secrets - Synergy (Protection)",
- "Purphoros, God of the Forge - Synergy (Protection)",
- "Syr Konrad, the Grim - Synergy (Interaction)"
+ "Silumgar, the Drifting Death - Synergy (Hexproof)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -10875,9 +10856,9 @@
"synergies": [
"Legends Matter",
"Backgrounds Matter",
- "Partner",
"Choose a background",
- "Doctor's companion"
+ "Doctor's companion",
+ "Shrines Matter"
],
"primary_color": "White",
"secondary_color": "Black",
@@ -10901,7 +10882,7 @@
"synergy_commanders": [
"Jaheira, Friend of the Forest - Synergy (Backgrounds Matter)",
"Karlach, Fury of Avernus - Synergy (Backgrounds Matter)",
- "Kodama of the East Tree - Synergy (Partner)"
+ "Lae'zel, Vlaakith's Champion - Synergy (Choose a background)"
],
"popularity_bucket": "Very Common",
"editorial_quality": "draft",
@@ -10976,7 +10957,7 @@
"Curious Homunculus // Voracious Reader",
"Filigree Attendant",
"Riddlekeeper",
- "Bonded Fetch"
+ "Zndrsplt, Eye of Wisdom"
],
"synergy_commanders": [
"Ragavan, Nimble Pilferer - Synergy (Little Fellas)",
@@ -11093,8 +11074,8 @@
"Xiahou Dun, the One-Eyed",
"Wu Scout",
"Wei Scout",
- "Lu Bu, Master-at-Arms",
"Wu Light Cavalry",
+ "Lu Bu, Master-at-Arms",
"Guan Yu, Sainted Warrior"
],
"synergy_commanders": [
@@ -11234,9 +11215,9 @@
"primary_color": "Blue",
"secondary_color": "Black",
"example_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Rishkar, Peema Renegade - Synergy (Counters Matter)",
- "Krenko, Tin Street Kingpin - Synergy (Counters Matter)"
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
+ "Yawgmoth, Thran Physician - Synergy (Counters Matter)"
],
"example_cards": [
"Dark Depths",
@@ -11570,9 +11551,9 @@
"synergies": [
"God Kindred",
"Protection",
- "Interaction",
"Lifegain",
- "Life Matters"
+ "Life Matters",
+ "Stax"
],
"primary_color": "White",
"secondary_color": "Black",
@@ -11596,7 +11577,8 @@
"synergy_commanders": [
"Birgi, God of Storytelling // Harnfel, Horn of Bounty - Synergy (God Kindred)",
"Ojer Taq, Deepest Foundation // Temple of Civilization - Synergy (God Kindred)",
- "Syr Konrad, the Grim - Synergy (Interaction)"
+ "Boromir, Warden of the Tower - Synergy (Protection)",
+ "Tatyova, Benthic Druid - Synergy (Lifegain)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -11615,25 +11597,25 @@
"primary_color": "Green",
"secondary_color": "Black",
"example_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness",
"Yawgmoth, Thran Physician",
"Skrelv, Defector Mite",
"Vorinclex, Monstrous Raider",
- "Lae'zel, Vlaakith's Champion"
+ "Lae'zel, Vlaakith's Champion",
+ "Tekuthal, Inquiry Dominus"
],
"example_cards": [
"Karn's Bastion",
"Doubling Season",
"Evolution Sage",
"Cankerbloom",
- "Etali, Primal Conqueror // Etali, Primal Sickness",
"Thrummingbird",
"Yawgmoth, Thran Physician",
- "Tezzeret's Gambit"
+ "Tezzeret's Gambit",
+ "Innkeeper's Talent"
],
"synergy_commanders": [
"Skithiryx, the Blight Dragon - Synergy (Poison Counters)",
- "Tekuthal, Inquiry Dominus - Synergy (Proliferate)",
+ "Fynn, the Fangbearer - Synergy (Poison Counters)",
"Karumonix, the Rat King - Synergy (Toxic)"
],
"popularity_bucket": "Uncommon",
@@ -11744,8 +11726,8 @@
"Tatyova, Benthic Druid - Synergy (Landfall)",
"Aesi, Tyrant of Gyre Strait - Synergy (Landfall)",
"Bristly Bill, Spine Sower - Synergy (Landfall)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Poison Counters)",
"Skrelv, Defector Mite - Synergy (Poison Counters)",
+ "Skithiryx, the Blight Dragon - Synergy (Poison Counters)",
"Rishkar, Peema Renegade - Synergy (Druid Kindred)"
],
"popularity_bucket": "Common",
@@ -11795,28 +11777,28 @@
"synergies": [
"Removal",
"Combat Tricks",
- "Protection",
"Board Wipes",
- "Counterspells"
+ "Counterspells",
+ "Soulshift"
],
"primary_color": "White",
"secondary_color": "Blue",
"example_commanders": [
"Syr Konrad, the Grim",
- "Toski, Bearer of Secrets",
"Purphoros, God of the Forge",
- "Etali, Primal Conqueror // Etali, Primal Sickness",
- "Boromir, Warden of the Tower"
+ "Boromir, Warden of the Tower",
+ "Avacyn, Angel of Hope",
+ "Padeem, Consul of Innovation"
],
"example_cards": [
"Swords to Plowshares",
+ "Swiftfoot Boots",
+ "Lightning Greaves",
"Path to Exile",
"Counterspell",
"Blasphemous Act",
"Beast Within",
- "Bojuka Bog",
- "Heroic Intervention",
- "Cyclonic Rift"
+ "Bojuka Bog"
],
"synergy_commanders": [
"Ulamog, the Infinite Gyre - Synergy (Removal)",
@@ -12326,8 +12308,8 @@
"Kodama of the West Tree - Synergy (Spirit Kindred)",
"Kodama of the East Tree - Synergy (Spirit Kindred)",
"Junji, the Midnight Sky - Synergy (Spirit Kindred)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
- "Rishkar, Peema Renegade - Synergy (Counters Matter)"
+ "Rishkar, Peema Renegade - Synergy (Counters Matter)",
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)"
],
"example_cards": [
"Petalmane Baku",
@@ -12622,9 +12604,9 @@
"synergies": [
"Draw Triggers",
"Wheels",
- "Protection",
"Creature Tokens",
- "Stax"
+ "Stax",
+ "Big Mana"
],
"primary_color": "Blue",
"secondary_color": "Black",
@@ -12651,7 +12633,7 @@
"Sheoldred, the Apocalypse - Synergy (Draw Triggers)",
"Selvala, Heart of the Wilds - Synergy (Wheels)",
"Niv-Mizzet, Parun - Synergy (Wheels)",
- "Toski, Bearer of Secrets - Synergy (Protection)"
+ "Adeline, Resplendent Cathar - Synergy (Creature Tokens)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -12978,9 +12960,9 @@
"synergies": [
"Historics Matter",
"Backgrounds Matter",
- "Partner",
"Choose a background",
- "Doctor's companion"
+ "Doctor's companion",
+ "Shrines Matter"
],
"primary_color": "White",
"secondary_color": "Black",
@@ -13004,7 +12986,7 @@
"synergy_commanders": [
"Jaheira, Friend of the Forest - Synergy (Backgrounds Matter)",
"Karlach, Fury of Avernus - Synergy (Backgrounds Matter)",
- "Kodama of the East Tree - Synergy (Partner)"
+ "Lae'zel, Vlaakith's Champion - Synergy (Choose a background)"
],
"popularity_bucket": "Very Common",
"editorial_quality": "draft",
@@ -13023,8 +13005,8 @@
"primary_color": "Blue",
"secondary_color": "White",
"example_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Rishkar, Peema Renegade - Synergy (Counters Matter)",
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
"Emry, Lurker of the Loch - Synergy (Wizard Kindred)"
],
"example_cards": [
@@ -13054,8 +13036,8 @@
"primary_color": "Blue",
"secondary_color": "White",
"example_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Rishkar, Peema Renegade - Synergy (Counters Matter)",
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
"Kutzil, Malamet Exemplar - Synergy (Warrior Kindred)"
],
"example_cards": [
@@ -13709,10 +13691,10 @@
"secondary_color": "Black",
"example_commanders": [
"Jeska, Thrice Reborn",
- "Ral, Monsoon Mage // Ral, Leyline Prodigy",
"Commodore Guff",
"Mila, Crafty Companion // Lukka, Wayward Bonder",
- "Heart of Kiran"
+ "Heart of Kiran",
+ "Adeline, Resplendent Cathar - Synergy (Planeswalkers)"
],
"example_cards": [
"Spark Double",
@@ -13725,12 +13707,11 @@
"Ral, Crackling Wit"
],
"synergy_commanders": [
- "Adeline, Resplendent Cathar - Synergy (Planeswalkers)",
"Yawgmoth, Thran Physician - Synergy (Planeswalkers)",
"Vorinclex, Monstrous Raider - Synergy (Planeswalkers)",
"Lae'zel, Vlaakith's Champion - Synergy (Superfriends)",
"Tekuthal, Inquiry Dominus - Synergy (Superfriends)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Rishkar, Peema Renegade - Synergy (Counters Matter)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -14013,11 +13994,11 @@
"primary_color": "Blue",
"secondary_color": "Green",
"example_commanders": [
- "Kellan, Daring Traveler // Journey On",
"Hakbal of the Surging Soul - Synergy (Explore)",
"Amalia Benavides Aguirre - Synergy (Explore)",
"Nicanzil, Current Conductor - Synergy (Explore)",
- "Astrid Peth - Synergy (Card Selection)"
+ "Astrid Peth - Synergy (Card Selection)",
+ "Francisco, Fowl Marauder - Synergy (Card Selection)"
],
"example_cards": [
"Get Lost",
@@ -14030,7 +14011,6 @@
"Spyglass Siren"
],
"synergy_commanders": [
- "Francisco, Fowl Marauder - Synergy (Card Selection)",
"Ragavan, Nimble Pilferer - Synergy (Artifact Tokens)"
],
"popularity_bucket": "Rare",
@@ -14245,8 +14225,8 @@
"Saryth, the Viper's Fang - Synergy (Warlock Kindred)",
"Honest Rutstein - Synergy (Warlock Kindred)",
"Breena, the Demagogue - Synergy (Warlock Kindred)",
- "Edgar, Charmed Groom // Edgar Markov's Coffin - Synergy (Blood Token)",
"Old Rutstein - Synergy (Blood Token)",
+ "Kamber, the Plunderer - Synergy (Blood Token)",
"Ragavan, Nimble Pilferer - Synergy (Pirate Kindred)"
],
"popularity_bucket": "Common",
@@ -14287,7 +14267,7 @@
"Thalia, Heretic Cathar - Synergy (Soldier Kindred)",
"Rishkar, Peema Renegade - Synergy (+1/+1 Counters)",
"Krenko, Tin Street Kingpin - Synergy (+1/+1 Counters)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Yawgmoth, Thran Physician - Synergy (Counters Matter)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -14647,8 +14627,8 @@
"Skrelv, Defector Mite",
"Vishgraz, the Doomhive",
"Ria Ivor, Bane of Bladehold",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Poison Counters)",
- "Skithiryx, the Blight Dragon - Synergy (Poison Counters)"
+ "Skithiryx, the Blight Dragon - Synergy (Poison Counters)",
+ "Fynn, the Fangbearer - Synergy (Poison Counters)"
],
"example_cards": [
"Skrelv, Defector Mite",
@@ -14991,7 +14971,7 @@
"Bonny Pall, Clearcutter - Synergy (Giant Kindred)",
"Rishkar, Peema Renegade - Synergy (+1/+1 Counters)",
"Krenko, Tin Street Kingpin - Synergy (+1/+1 Counters)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Yawgmoth, Thran Physician - Synergy (Counters Matter)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -15053,8 +15033,8 @@
"Rishkar, Peema Renegade - Synergy (+1/+1 Counters)",
"Krenko, Tin Street Kingpin - Synergy (+1/+1 Counters)",
"Yawgmoth, Thran Physician - Synergy (+1/+1 Counters)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
- "Yahenni, Undying Partisan - Synergy (Counters Matter)"
+ "Yahenni, Undying Partisan - Synergy (Counters Matter)",
+ "Heliod, Sun-Crowned - Synergy (Counters Matter)"
],
"example_cards": [
"Tragic Slip",
@@ -15306,7 +15286,7 @@
"Rishkar, Peema Renegade - Synergy (+1/+1 Counters)",
"Krenko, Tin Street Kingpin - Synergy (+1/+1 Counters)",
"Yawgmoth, Thran Physician - Synergy (+1/+1 Counters)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Yahenni, Undying Partisan - Synergy (Counters Matter)"
],
"example_cards": [
"Everflowing Chalice",
@@ -15319,7 +15299,7 @@
"Bloodhusk Ritualist"
],
"synergy_commanders": [
- "Yahenni, Undying Partisan - Synergy (Counters Matter)",
+ "Heliod, Sun-Crowned - Synergy (Counters Matter)",
"Selvala, Heart of the Wilds - Synergy (Blink)"
],
"popularity_bucket": "Rare",
@@ -15735,7 +15715,6 @@
"Ingenious Infiltrator"
],
"synergy_commanders": [
- "Higure, the Still Wind - Synergy (Ninjutsu)",
"Lord Skitter, Sewer King - Synergy (Rat Kindred)",
"Marrow-Gnawer - Synergy (Rat Kindred)",
"Syr Konrad, the Grim - Synergy (Human Kindred)"
@@ -15758,20 +15737,20 @@
"secondary_color": "Blue",
"example_commanders": [
"Nashi, Moon Sage's Scion",
+ "Yuriko, the Tiger's Shadow",
"Ink-Eyes, Servant of Oni",
"Higure, the Still Wind",
- "Yuffie, Materia Hunter",
- "Yuriko, the Tiger's Shadow - Synergy (Ninja Kindred)"
+ "Yuffie, Materia Hunter"
],
"example_cards": [
"Nashi, Moon Sage's Scion",
"Fallen Shinobi",
"Prosperous Thief",
"Thousand-Faced Shadow",
+ "Yuriko, the Tiger's Shadow",
"Silver-Fur Master",
"Silent-Blade Oni",
- "Ingenious Infiltrator",
- "Ink-Eyes, Servant of Oni"
+ "Ingenious Infiltrator"
],
"synergy_commanders": [
"Lord Skitter, Sewer King - Synergy (Rat Kindred)",
@@ -16066,7 +16045,7 @@
"Mondrak, Glory Dominus - Synergy (Phyrexian Kindred)",
"Sheoldred, the Apocalypse - Synergy (Phyrexian Kindred)",
"Elas il-Kor, Sadistic Pilgrim - Synergy (Phyrexian Kindred)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Rishkar, Peema Renegade - Synergy (Counters Matter)"
],
"example_cards": [
"Vat of Rebirth",
@@ -16079,7 +16058,7 @@
"Sawblade Scamp"
],
"synergy_commanders": [
- "Rishkar, Peema Renegade - Synergy (Counters Matter)",
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
"Ragavan, Nimble Pilferer - Synergy (Artifacts Matter)"
],
"popularity_bucket": "Rare",
@@ -16136,7 +16115,7 @@
"Sakashima of a Thousand Faces - Synergy (Clones)",
"Rishkar, Peema Renegade - Synergy (+1/+1 Counters)",
"Krenko, Tin Street Kingpin - Synergy (+1/+1 Counters)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Yawgmoth, Thran Physician - Synergy (Counters Matter)"
],
"popularity_bucket": "Niche",
"editorial_quality": "draft",
@@ -16368,8 +16347,8 @@
"Rishkar, Peema Renegade - Synergy (+1/+1 Counters)",
"Krenko, Tin Street Kingpin - Synergy (+1/+1 Counters)",
"Yawgmoth, Thran Physician - Synergy (+1/+1 Counters)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
- "Yahenni, Undying Partisan - Synergy (Counters Matter)"
+ "Yahenni, Undying Partisan - Synergy (Counters Matter)",
+ "Heliod, Sun-Crowned - Synergy (Counters Matter)"
],
"example_cards": [
"Abzan Falconer",
@@ -16638,14 +16617,14 @@
"id": "partner",
"theme": "Partner",
"synergies": [
+ "Partner with",
"Pirate Kindred",
"Artificer Kindred",
- "Outlaw Kindred",
- "+1/+1 Counters",
- "Toughness Matters"
+ "First strike",
+ "Elf Kindred"
],
- "primary_color": "White",
- "secondary_color": "Black",
+ "primary_color": "Blue",
+ "secondary_color": "White",
"example_commanders": [
"Kodama of the East Tree",
"Sakashima of a Thousand Faces",
@@ -16664,16 +16643,16 @@
"Prava of the Steel Legion"
],
"synergy_commanders": [
+ "Pippin, Warden of Isengard - Synergy (Partner with)",
+ "Merry, Warden of Isengard - Synergy (Partner with)",
+ "Pir, Imaginative Rascal - Synergy (Partner with)",
"Ragavan, Nimble Pilferer - Synergy (Pirate Kindred)",
"Captain Lannery Storm - Synergy (Pirate Kindred)",
- "Malcolm, Alluring Scoundrel - Synergy (Pirate Kindred)",
- "Loran of the Third Path - Synergy (Artificer Kindred)",
- "Sai, Master Thopterist - Synergy (Artificer Kindred)",
- "Lotho, Corrupt Shirriff - Synergy (Outlaw Kindred)"
+ "Loran of the Third Path - Synergy (Artificer Kindred)"
],
- "popularity_bucket": "Rare",
+ "popularity_bucket": "Niche",
"editorial_quality": "draft",
- "description": "Builds around Partner leveraging synergies with Pirate Kindred and Artificer Kindred."
+ "description": "Builds around Partner leveraging synergies with Partner with and Pirate Kindred."
},
{
"id": "partner-father-son",
@@ -16695,11 +16674,11 @@
"id": "partner-with",
"theme": "Partner with",
"synergies": [
+ "Partner",
"Blink",
"Enter the Battlefield",
"Leave the Battlefield",
- "Warrior Kindred",
- "Outlaw Kindred"
+ "Conditional Draw"
],
"primary_color": "Blue",
"secondary_color": "Red",
@@ -16721,16 +16700,16 @@
"Sam, Loyal Attendant"
],
"synergy_commanders": [
+ "Kodama of the East Tree - Synergy (Partner)",
+ "Sakashima of a Thousand Faces - Synergy (Partner)",
+ "Kediss, Emberclaw Familiar - Synergy (Partner)",
"Selvala, Heart of the Wilds - Synergy (Blink)",
"Sheoldred, Whispering One - Synergy (Blink)",
- "Ojer Taq, Deepest Foundation // Temple of Civilization - Synergy (Blink)",
- "Elesh Norn, Mother of Machines - Synergy (Enter the Battlefield)",
- "Kodama of the East Tree - Synergy (Enter the Battlefield)",
- "Nezahal, Primal Tide - Synergy (Leave the Battlefield)"
+ "Ojer Taq, Deepest Foundation // Temple of Civilization - Synergy (Enter the Battlefield)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
- "description": "Builds around Partner with leveraging synergies with Blink and Enter the Battlefield."
+ "description": "Builds around Partner with leveraging synergies with Partner and Blink."
},
{
"id": "peasant-kindred",
@@ -16967,23 +16946,22 @@
"primary_color": "Red",
"example_commanders": [
"Otharri, Suns' Glory",
- "Joshua, Phoenix's Dominant // Phoenix, Warden of Fire",
"Syrix, Carrier of the Flame",
"Aurelia, the Warleader - Synergy (Haste)",
- "Yahenni, Undying Partisan - Synergy (Haste)"
+ "Yahenni, Undying Partisan - Synergy (Haste)",
+ "Kiki-Jiki, Mirror Breaker - Synergy (Haste)"
],
"example_cards": [
"Otharri, Suns' Glory",
"Aurora Phoenix",
"Phoenix Chick",
"Jaya's Phoenix",
- "Joshua, Phoenix's Dominant // Phoenix, Warden of Fire",
"Detective's Phoenix",
"Flamewake Phoenix",
- "Everquill Phoenix"
+ "Everquill Phoenix",
+ "Ashcloud Phoenix"
],
"synergy_commanders": [
- "Kiki-Jiki, Mirror Breaker - Synergy (Haste)",
"Niv-Mizzet, Parun - Synergy (Flying)",
"Avacyn, Angel of Hope - Synergy (Flying)",
"Rishkar, Peema Renegade - Synergy (Midrange)"
@@ -17009,7 +16987,7 @@
"Sheoldred, the Apocalypse",
"Elas il-Kor, Sadistic Pilgrim",
"Sheoldred, Whispering One",
- "Etali, Primal Conqueror // Etali, Primal Sickness"
+ "Elesh Norn, Grand Cenobite"
],
"example_cards": [
"Phyrexian Metamorph",
@@ -17044,9 +17022,9 @@
"example_commanders": [
"Baird, Steward of Argive",
"Teysa, Envoy of Ghosts",
- "Tamiyo, Inquisitive Student // Tamiyo, Seasoned Scholar",
"Sivitri, Dragon Master",
- "Isperia, Supreme Judge"
+ "Isperia, Supreme Judge",
+ "Thantis, the Warweaver"
],
"example_cards": [
"Propaganda",
@@ -17096,8 +17074,8 @@
"Cid, Freeflier Pilot",
"Kotori, Pilot Prodigy",
"Tannuk, Memorial Ensign",
- "Prodigy's Prototype",
- "Defend the Rider"
+ "Defend the Rider",
+ "Prodigy's Prototype"
],
"synergy_commanders": [
"Sram, Senior Edificer - Synergy (Vehicles)",
@@ -17118,7 +17096,7 @@
"Devil Kindred",
"Offspring",
"Burn",
- "Board Wipes"
+ "Role token"
],
"primary_color": "Red",
"secondary_color": "Black",
@@ -17155,8 +17133,8 @@
"Siren Kindred",
"Raid",
"Encore",
- "Explore",
- "Outlaw Kindred"
+ "Outlaw Kindred",
+ "Explore"
],
"primary_color": "Blue",
"secondary_color": "Red",
@@ -17382,24 +17360,23 @@
"primary_color": "Black",
"secondary_color": "Green",
"example_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness",
"Skrelv, Defector Mite",
"Skithiryx, the Blight Dragon",
"Fynn, the Fangbearer",
- "Karumonix, the Rat King"
+ "Karumonix, the Rat King",
+ "Ixhel, Scion of Atraxa"
],
"example_cards": [
- "Etali, Primal Conqueror // Etali, Primal Sickness",
"Skrelv, Defector Mite",
"Triumph of the Hordes",
"Vraska, Betrayal's Sting",
"White Sun's Twilight",
"Skrelv's Hive",
"Plague Myr",
- "Grafted Exoskeleton"
+ "Grafted Exoskeleton",
+ "Vraska's Fall"
],
"synergy_commanders": [
- "Ixhel, Scion of Atraxa - Synergy (Toxic)",
"Vishgraz, the Doomhive - Synergy (Mite Kindred)"
],
"popularity_bucket": "Uncommon",
@@ -17670,7 +17647,7 @@
"Krenko, Tin Street Kingpin - Synergy (+1/+1 Counters)",
"Adeline, Resplendent Cathar - Synergy (Planeswalkers)",
"Vorinclex, Monstrous Raider - Synergy (Planeswalkers)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Yahenni, Undying Partisan - Synergy (Counters Matter)"
],
"popularity_bucket": "Niche",
"editorial_quality": "draft",
@@ -17680,37 +17657,37 @@
"id": "protection",
"theme": "Protection",
"synergies": [
- "Ward",
- "Hexproof",
"Indestructible",
- "Shroud",
- "Divinity Counters"
+ "Angel Kindred",
+ "Interaction",
+ "Knight Kindred",
+ "Combat Tricks"
],
"primary_color": "White",
"secondary_color": "Green",
"example_commanders": [
- "Toski, Bearer of Secrets",
- "Purphoros, God of the Forge",
- "Etali, Primal Conqueror // Etali, Primal Sickness",
"Boromir, Warden of the Tower",
- "Avacyn, Angel of Hope"
+ "Avacyn, Angel of Hope",
+ "Yawgmoth, Thran Physician",
+ "Padeem, Consul of Innovation",
+ "Yahenni, Undying Partisan"
],
"example_cards": [
+ "Swiftfoot Boots",
+ "Lightning Greaves",
"Heroic Intervention",
"The One Ring",
- "Teferi's Protection",
- "Roaming Throne",
"Boros Charm",
"Flawless Maneuver",
"Akroma's Will",
"Mithril Coat"
],
"synergy_commanders": [
- "Adrix and Nev, Twincasters - Synergy (Ward)",
- "Miirym, Sentinel Wyrm - Synergy (Ward)",
- "Ulamog, the Defiler - Synergy (Ward)",
- "General Ferrous Rokiric - Synergy (Hexproof)",
- "Silumgar, the Drifting Death - Synergy (Hexproof)"
+ "Toski, Bearer of Secrets - Synergy (Indestructible)",
+ "Purphoros, God of the Forge - Synergy (Indestructible)",
+ "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Indestructible)",
+ "Aurelia, the Warleader - Synergy (Angel Kindred)",
+ "Syr Konrad, the Grim - Synergy (Interaction)"
],
"popularity_bucket": "Very Common",
"editorial_quality": "draft",
@@ -17894,8 +17871,8 @@
"Tatyova, Benthic Druid - Synergy (Landfall)",
"Aesi, Tyrant of Gyre Strait - Synergy (Landfall)",
"Bristly Bill, Spine Sower - Synergy (Landfall)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Rishkar, Peema Renegade - Synergy (Counters Matter)",
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
"Sram, Senior Edificer - Synergy (Enchantments Matter)"
],
"popularity_bucket": "Rare",
@@ -18263,9 +18240,9 @@
"Swarmyard Massacre"
],
"synergy_commanders": [
- "Higure, the Still Wind - Synergy (Ninjutsu)",
- "Yuriko, the Tiger's Shadow - Synergy (Ninja Kindred)",
+ "Yuriko, the Tiger's Shadow - Synergy (Ninjutsu)",
"Satoru, the Infiltrator - Synergy (Ninja Kindred)",
+ "Satoru Umezawa - Synergy (Ninja Kindred)",
"Kiora, the Rising Tide - Synergy (Threshold)"
],
"popularity_bucket": "Uncommon",
@@ -18407,8 +18384,8 @@
"Reanimate",
"Faithless Looting",
"Victimize",
- "Takenuma, Abandoned Mire",
"Animate Dead",
+ "Takenuma, Abandoned Mire",
"Syr Konrad, the Grim",
"Gray Merchant of Asphodel",
"Guardian Project"
@@ -18562,7 +18539,7 @@
"Krovikan Rot"
],
"synergy_commanders": [
- "Toski, Bearer of Secrets - Synergy (Interaction)"
+ "Purphoros, God of the Forge - Synergy (Interaction)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -18604,8 +18581,8 @@
"Rishkar, Peema Renegade - Synergy (+1/+1 Counters)",
"Krenko, Tin Street Kingpin - Synergy (+1/+1 Counters)",
"Yawgmoth, Thran Physician - Synergy (+1/+1 Counters)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
- "Yahenni, Undying Partisan - Synergy (Counters Matter)"
+ "Yahenni, Undying Partisan - Synergy (Counters Matter)",
+ "Heliod, Sun-Crowned - Synergy (Counters Matter)"
],
"example_cards": [
"Wren's Run Hydra",
@@ -18656,7 +18633,7 @@
"synergy_commanders": [
"He Who Hungers - Synergy (Soulshift)",
"Syr Konrad, the Grim - Synergy (Interaction)",
- "Toski, Bearer of Secrets - Synergy (Interaction)",
+ "Purphoros, God of the Forge - Synergy (Interaction)",
"Lotho, Corrupt Shirriff - Synergy (Control)"
],
"popularity_bucket": "Very Common",
@@ -18693,7 +18670,7 @@
"Alchemist's Assistant"
],
"synergy_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Yawgmoth, Thran Physician - Synergy (Counters Matter)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -18729,7 +18706,7 @@
"Valeron Wardens"
],
"synergy_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Yahenni, Undying Partisan - Synergy (Counters Matter)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -18975,7 +18952,7 @@
"Rishkar, Peema Renegade - Synergy (+1/+1 Counters)",
"Krenko, Tin Street Kingpin - Synergy (+1/+1 Counters)",
"Yawgmoth, Thran Physician - Synergy (+1/+1 Counters)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Yahenni, Undying Partisan - Synergy (Counters Matter)"
],
"example_cards": [
"Spider-Punk",
@@ -18988,7 +18965,7 @@
"Wrecking Beast"
],
"synergy_commanders": [
- "Yahenni, Undying Partisan - Synergy (Counters Matter)",
+ "Heliod, Sun-Crowned - Synergy (Counters Matter)",
"Sram, Senior Edificer - Synergy (Voltron)"
],
"popularity_bucket": "Rare",
@@ -19254,7 +19231,7 @@
"Piper Wright, Publick Reporter - Synergy (Clue Token)",
"Tivit, Seller of Secrets - Synergy (Investigate)",
"Kellan, Inquisitive Prodigy // Tail the Suspect - Synergy (Investigate)",
- "Edgar, Charmed Groom // Edgar Markov's Coffin - Synergy (Blood Token)"
+ "Old Rutstein - Synergy (Blood Token)"
],
"popularity_bucket": "Common",
"editorial_quality": "draft",
@@ -19366,7 +19343,7 @@
"synergy_commanders": [
"Syr Konrad, the Grim - Synergy (Mill)",
"Emry, Lurker of the Loch - Synergy (Mill)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Rishkar, Peema Renegade - Synergy (Counters Matter)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -19563,7 +19540,7 @@
"Sewer Shambler"
],
"synergy_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Yahenni, Undying Partisan - Synergy (Counters Matter)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -20005,7 +19982,7 @@
"Clones",
"Flash",
"Little Fellas",
- "Protection"
+ "Outlaw Kindred"
],
"primary_color": "Blue",
"secondary_color": "Green",
@@ -20126,8 +20103,8 @@
"synergy_commanders": [
"Anim Pakal, Thousandth Moon - Synergy (Soldier Kindred)",
"Thalia, Heretic Cathar - Synergy (Soldier Kindred)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Rishkar, Peema Renegade - Synergy (Counters Matter)",
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
"Tatyova, Benthic Druid - Synergy (Lifegain)"
],
"popularity_bucket": "Rare",
@@ -20172,11 +20149,10 @@
"id": "shroud",
"theme": "Shroud",
"synergies": [
- "Protection",
- "Interaction",
"Toughness Matters",
"Big Mana",
- "Counters Matter"
+ "Counters Matter",
+ "Little Fellas"
],
"primary_color": "Green",
"secondary_color": "Blue",
@@ -20184,8 +20160,8 @@
"Multani, Maro-Sorcerer",
"Kodama of the North Tree",
"Autumn Willow",
- "Toski, Bearer of Secrets - Synergy (Protection)",
- "Purphoros, God of the Forge - Synergy (Protection)"
+ "Azusa, Lost but Seeking - Synergy (Toughness Matters)",
+ "Sheoldred, the Apocalypse - Synergy (Toughness Matters)"
],
"example_cards": [
"Argothian Enchantress",
@@ -20198,10 +20174,10 @@
"Neurok Commando"
],
"synergy_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Protection)",
- "Syr Konrad, the Grim - Synergy (Interaction)",
- "Boromir, Warden of the Tower - Synergy (Interaction)",
- "Azusa, Lost but Seeking - Synergy (Toughness Matters)"
+ "Vito, Thorn of the Dusk Rose - Synergy (Toughness Matters)",
+ "Syr Konrad, the Grim - Synergy (Big Mana)",
+ "Etali, Primal Storm - Synergy (Big Mana)",
+ "Rishkar, Peema Renegade - Synergy (Counters Matter)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -20215,7 +20191,7 @@
"Outlaw Kindred",
"Flying",
"Artifacts Matter",
- "Little Fellas"
+ "Toughness Matters"
],
"primary_color": "Blue",
"example_commanders": [
@@ -20232,8 +20208,8 @@
"Spyglass Siren",
"Storm Fleet Negotiator",
"Malcolm, the Eyes",
- "Oaken Siren",
- "Hypnotic Siren"
+ "Zephyr Singer",
+ "Oaken Siren"
],
"synergy_commanders": [
"Captain Lannery Storm - Synergy (Pirate Kindred)",
@@ -20368,8 +20344,8 @@
"Rishkar, Peema Renegade - Synergy (+1/+1 Counters)",
"Krenko, Tin Street Kingpin - Synergy (+1/+1 Counters)",
"Yawgmoth, Thran Physician - Synergy (+1/+1 Counters)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
- "Yahenni, Undying Partisan - Synergy (Counters Matter)"
+ "Yahenni, Undying Partisan - Synergy (Counters Matter)",
+ "Heliod, Sun-Crowned - Synergy (Counters Matter)"
],
"example_cards": [
"Arcbound Slith",
@@ -21088,8 +21064,8 @@
"Rishkar, Peema Renegade - Synergy (+1/+1 Counters)",
"Krenko, Tin Street Kingpin - Synergy (+1/+1 Counters)",
"Yawgmoth, Thran Physician - Synergy (+1/+1 Counters)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
- "Yahenni, Undying Partisan - Synergy (Counters Matter)"
+ "Yahenni, Undying Partisan - Synergy (Counters Matter)",
+ "Heliod, Sun-Crowned - Synergy (Counters Matter)"
],
"example_cards": [
"Spike Feeder",
@@ -21575,7 +21551,7 @@
"Bottomless Vault"
],
"synergy_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Rishkar, Peema Renegade - Synergy (Counters Matter)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -21679,9 +21655,9 @@
"Lulu, Stern Guardian"
],
"synergy_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Rishkar, Peema Renegade - Synergy (Counters Matter)",
"Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
+ "Yawgmoth, Thran Physician - Synergy (Counters Matter)",
"Kutzil, Malamet Exemplar - Synergy (Stax)",
"Lotho, Corrupt Shirriff - Synergy (Stax)",
"Baral, Chief of Compliance - Synergy (Loot)"
@@ -21757,7 +21733,7 @@
"Shoulder to Shoulder"
],
"synergy_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Heliod, Sun-Crowned - Synergy (Counters Matter)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -22288,7 +22264,7 @@
"Kiora, the Rising Tide",
"Far Wanderings",
"Barbarian Ring",
- "Cabal Pit"
+ "Putrid Imp"
],
"popularity_bucket": "Niche",
"editorial_quality": "draft",
@@ -22442,8 +22418,8 @@
],
"example_cards": [
"The Tenth Doctor",
- "Wibbly-wobbly, Timey-wimey",
"Time Beetle",
+ "Wibbly-wobbly, Timey-wimey",
"Rotating Fireplace",
"The Parting of the Ways",
"All of History, All at Once",
@@ -22718,8 +22694,8 @@
"Karumonix, the Rat King"
],
"synergy_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Poison Counters)",
"Skithiryx, the Blight Dragon - Synergy (Poison Counters)",
+ "Fynn, the Fangbearer - Synergy (Poison Counters)",
"Yawgmoth, Thran Physician - Synergy (Infect)",
"Vorinclex, Monstrous Raider - Synergy (Infect)",
"Mondrak, Glory Dominus - Synergy (Phyrexian Kindred)"
@@ -22795,7 +22771,7 @@
"Yawgmoth, Thran Physician - Synergy (+1/+1 Counters)",
"Syr Konrad, the Grim - Synergy (Human Kindred)",
"Azusa, Lost but Seeking - Synergy (Human Kindred)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Yahenni, Undying Partisan - Synergy (Counters Matter)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -23092,8 +23068,8 @@
"Shaman Kindred",
"Trample",
"Warrior Kindred",
- "Protection",
- "+1/+1 Counters"
+ "+1/+1 Counters",
+ "Counters Matter"
],
"primary_color": "Green",
"secondary_color": "Black",
@@ -23131,10 +23107,10 @@
"theme": "Turtle Kindred",
"synergies": [
"Ward",
- "Protection",
"Toughness Matters",
"Stax",
- "Interaction"
+ "Little Fellas",
+ "Big Mana"
],
"primary_color": "Blue",
"secondary_color": "Green",
@@ -23159,9 +23135,9 @@
"Adrix and Nev, Twincasters - Synergy (Ward)",
"Miirym, Sentinel Wyrm - Synergy (Ward)",
"Ulamog, the Defiler - Synergy (Ward)",
- "Toski, Bearer of Secrets - Synergy (Protection)",
- "Purphoros, God of the Forge - Synergy (Protection)",
- "Azusa, Lost but Seeking - Synergy (Toughness Matters)"
+ "Azusa, Lost but Seeking - Synergy (Toughness Matters)",
+ "Sheoldred, the Apocalypse - Synergy (Toughness Matters)",
+ "Kutzil, Malamet Exemplar - Synergy (Stax)"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -23492,8 +23468,8 @@
"Hellhole Flailer"
],
"synergy_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Yahenni, Undying Partisan - Synergy (Counters Matter)",
+ "Heliod, Sun-Crowned - Synergy (Counters Matter)",
"Sram, Senior Edificer - Synergy (Voltron)"
],
"popularity_bucket": "Rare",
@@ -23557,9 +23533,9 @@
"Twilight Prophet"
],
"synergy_commanders": [
- "Edgar, Charmed Groom // Edgar Markov's Coffin - Synergy (Blood Token)",
"Old Rutstein - Synergy (Blood Token)",
"Kamber, the Plunderer - Synergy (Blood Token)",
+ "Strefan, Maurer Progenitor - Synergy (Blood Token)",
"Heliod, Sun-Crowned - Synergy (Lifegain Triggers)",
"Emrakul, the World Anew - Synergy (Madness)"
],
@@ -23582,7 +23558,7 @@
"Ojer Pakpatiq, Deepest Epoch // Temple of Cyclical Time - Synergy (Time Counters)",
"The Tenth Doctor - Synergy (Time Counters)",
"Jhoira of the Ghitu - Synergy (Time Counters)",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)"
+ "Rishkar, Peema Renegade - Synergy (Counters Matter)"
],
"example_cards": [
"Dreamtide Whale",
@@ -23595,7 +23571,7 @@
"Chronozoa"
],
"synergy_commanders": [
- "Rishkar, Peema Renegade - Synergy (Counters Matter)",
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
"Sram, Senior Edificer - Synergy (Enchantments Matter)"
],
"popularity_bucket": "Rare",
@@ -23746,9 +23722,9 @@
"secondary_color": "Green",
"example_commanders": [
"Yisan, the Wanderer Bard",
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Rishkar, Peema Renegade - Synergy (Counters Matter)",
"Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
+ "Yawgmoth, Thran Physician - Synergy (Counters Matter)",
"Sram, Senior Edificer - Synergy (Enchantments Matter)"
],
"example_cards": [
@@ -23936,10 +23912,10 @@
"theme": "Ward",
"synergies": [
"Turtle Kindred",
- "Protection",
"Dinosaur Kindred",
"Dragon Kindred",
- "Interaction"
+ "Stax",
+ "Reach"
],
"primary_color": "Blue",
"secondary_color": "Green",
@@ -23956,17 +23932,17 @@
"Adrix and Nev, Twincasters",
"Miirym, Sentinel Wyrm",
"Bronze Guardian",
- "Hulking Raptor",
"Ulamog, the Defiler",
+ "Hulking Raptor",
"Valgavoth, Terror Eater"
],
"synergy_commanders": [
"Kogla and Yidaro - Synergy (Turtle Kindred)",
"The Pride of Hull Clade - Synergy (Turtle Kindred)",
"Archelos, Lagoon Mystic - Synergy (Turtle Kindred)",
- "Toski, Bearer of Secrets - Synergy (Protection)",
- "Purphoros, God of the Forge - Synergy (Protection)",
- "Etali, Primal Storm - Synergy (Dinosaur Kindred)"
+ "Etali, Primal Storm - Synergy (Dinosaur Kindred)",
+ "Ghalta, Primal Hunger - Synergy (Dinosaur Kindred)",
+ "Niv-Mizzet, Parun - Synergy (Dragon Kindred)"
],
"popularity_bucket": "Niche",
"editorial_quality": "draft",
@@ -24631,8 +24607,8 @@
"Minas Morgul, Dark Fortress",
"Witch-king of Angmar",
"Nazgûl",
- "Lord of the Nazgûl",
"Accursed Duneyard",
+ "Lord of the Nazgûl",
"In the Darkness Bind Them",
"Street Wraith",
"Sauron, the Necromancer"
@@ -24777,8 +24753,8 @@
"Gray Merchant of Asphodel",
"Field of the Dead",
"Fanatic of Rhonas",
- "Carrion Feeder",
"Warren Soultrader",
+ "Carrion Feeder",
"Accursed Marauder",
"Stitcher's Supplier",
"Fleshbag Marauder"
@@ -24834,9 +24810,9 @@
"primary_color": "White",
"secondary_color": "Blue",
"example_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Rishkar, Peema Renegade - Synergy (Counters Matter)",
- "Krenko, Tin Street Kingpin - Synergy (Counters Matter)"
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
+ "Yawgmoth, Thran Physician - Synergy (Counters Matter)"
],
"example_cards": [
"Dwarven Armorer",
@@ -24860,9 +24836,9 @@
"primary_color": "Red",
"secondary_color": "Black",
"example_commanders": [
- "Etali, Primal Conqueror // Etali, Primal Sickness - Synergy (Counters Matter)",
"Rishkar, Peema Renegade - Synergy (Counters Matter)",
- "Krenko, Tin Street Kingpin - Synergy (Counters Matter)"
+ "Krenko, Tin Street Kingpin - Synergy (Counters Matter)",
+ "Yawgmoth, Thran Physician - Synergy (Counters Matter)"
],
"example_cards": [
"Dwarven Armorer",
@@ -24872,7 +24848,7 @@
"Clockwork Beast",
"Clockwork Avian",
"Consuming Ferocity",
- "Clockwork Swarm"
+ "Balduvian Hydra"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -24893,9 +24869,9 @@
"Dwarven Armory",
"Tin-Wing Chimera",
"Brass-Talon Chimera",
- "Fungus Elemental",
"Iron-Heart Chimera",
- "Lead-Belly Chimera"
+ "Lead-Belly Chimera",
+ "Fungus Elemental"
],
"popularity_bucket": "Rare",
"editorial_quality": "draft",
@@ -24930,7 +24906,7 @@
"Cantrips": 88,
"Card Draw": 309,
"Combat Tricks": 214,
- "Interaction": 1056,
+ "Interaction": 904,
"Unconditional Draw": 133,
"Bending": 5,
"Cost Reduction": 68,
@@ -24956,7 +24932,7 @@
"Sloth Kindred": 3,
"Lands Matter": 169,
"Gargoyle Kindred": 11,
- "Protection": 331,
+ "Protection": 231,
"Griffin Kindred": 43,
"Cleric Kindred": 365,
"Backgrounds Matter": 11,
@@ -25020,8 +24996,6 @@
"Lifegain Triggers": 37,
"Hero Kindred": 24,
"Stun Counters": 5,
- "Take 59 Flights of Stairs": 1,
- "Take the Elevator": 1,
"Pilot Kindred": 18,
"Artificer Kindred": 49,
"Energy": 21,
@@ -25046,7 +25020,7 @@
"Loot": 71,
"Haste": 1,
"Trample": 15,
- "Partner": 9,
+ "Partner": 16,
"Dragon Kindred": 27,
"Land Types Matter": 40,
"Phyrexian Kindred": 64,
@@ -25142,7 +25116,6 @@
"Noble Kindred": 23,
"Spell Copy": 10,
"Storm": 3,
- "Brand-new Sky": 1,
"Card Selection": 7,
"Explore": 7,
"Eye Kindred": 4,
@@ -25169,12 +25142,10 @@
"Shapeshifter Kindred": 9,
"Boast": 4,
"Detain": 5,
- "Wind Walk": 1,
"Miracle": 6,
"Doctor Kindred": 10,
"Doctor's Companion": 8,
"Doctor's companion": 8,
- "History Teacher": 1,
"Thopter Kindred": 3,
"Ox Kindred": 13,
"Extort": 4,
@@ -25184,12 +25155,7 @@
"Myriad": 5,
"Treasure": 11,
"Treasure Token": 13,
- "Ability": 1,
- "Attack": 1,
- "Item": 1,
- "Magic": 1,
"Finality Counters": 2,
- "Lure the Unwary": 1,
"Insect Kindred": 6,
"Bat Kindred": 11,
"Enrage": 3,
@@ -25241,7 +25207,6 @@
"Detective Kindred": 17,
"Bestow": 11,
"Omen Counters": 1,
- "Healing Tears": 1,
"Retrace": 1,
"Champion": 2,
"Sweep": 2,
@@ -25263,7 +25228,6 @@
"Cumulative upkeep": 13,
"Hideaway": 3,
"Inkling Kindred": 1,
- "Crash Landing": 1,
"Impulse": 3,
"Junk Token": 1,
"Junk Tokens": 2,
@@ -25287,7 +25251,6 @@
"Evoke": 7,
"Demigod Kindred": 1,
"Chimera Kindred": 1,
- "Mold Earth": 1,
"Fade Counters": 2,
"Fading": 2,
"Astartes Kindred": 6,
@@ -25295,14 +25258,12 @@
"God Kindred": 11,
"Delay Counters": 1,
"Exert": 7,
- "Dragonfire Dive": 1,
"Jackal Kindred": 1,
"Freerunning": 1,
"Intervention Counters": 1,
"Toy Kindred": 4,
"Sculpture Kindred": 1,
"Prowess": 5,
- "Gae Bolg": 1,
"Bracket:GameChanger": 6,
"Coyote Kindred": 1,
"Aftermath": 1,
@@ -25339,13 +25300,11 @@
"Storage Counters": 2,
"Madness": 2,
"Healing Counters": 2,
- "The Allagan Eye": 1,
"Squad": 5,
"Map Token": 1,
"Spell mastery": 3,
"Meld": 1,
"Gith Kindred": 2,
- "Psychic Defense": 1,
"Basic landcycling": 2,
"Landcycling": 2,
"For Mirrodin!": 5,
@@ -25357,7 +25316,6 @@
"Plainswalk": 2,
"Powerstone Token": 4,
"Demon Kindred": 3,
- "Rites of Banishment": 1,
"Training": 5,
"Horsemanship": 7,
"Snake Kindred": 1,
@@ -25375,11 +25333,9 @@
"Hoofprint Counters": 1,
"Monstrosity": 4,
"Soulshift": 5,
- "Science Teacher": 1,
"Scientist Kindred": 2,
"Javelin Counters": 1,
"Credit Counters": 1,
- "Protection Fighting Style": 1,
"Tiefling Kindred": 1,
"Connive": 2,
"Ascend": 6,
@@ -25410,7 +25366,6 @@
"Aegis Counters": 1,
"Read Ahead": 2,
"Quest Counters": 6,
- "Machina": 1,
"Reprieve Counters": 1,
"Germ Kindred": 1,
"Living weapon": 1,
@@ -25429,7 +25384,6 @@
"Carrion Counters": 1,
"Behold": 1,
"Impending": 1,
- "First Contact": 1,
"Synth Kindred": 1,
"Forecast": 5,
"Fungus Kindred": 1,
@@ -25448,21 +25402,14 @@
"Study Counters": 1,
"Isolation Counters": 1,
"Coward Kindred": 1,
- "Natural Shelter": 1,
- "Cura": 1,
- "Curaga": 1,
- "Cure": 1,
"Egg Kindred": 1,
- "Bad Wolf": 1,
"Wolf Kindred": 2,
"Parley": 1,
"\\+0/\\+1 Counters": 3,
- "Keen Sight": 1,
"Training Counters": 1,
"Verse Counters": 2,
"Shade Kindred": 1,
"Shaman Kindred": 1,
- "The Nuka-Cola Challenge": 1,
"Blood Token": 1,
"Zubera Kindred": 1,
"Illusion Kindred": 2,
@@ -25471,8 +25418,6 @@
"Soltari Kindred": 9,
"Echo Counters": 1,
"Feather Counters": 1,
- "Grav-cannon": 1,
- "Concealed Position": 1,
"Intimidate": 1,
"Reflection Kindred": 1,
"Story Counters": 1,
@@ -25481,66 +25426,55 @@
"Harpy Kindred": 1,
"Recover": 1,
"Ripple": 1,
- "Brave Heart": 1,
"Tempest Hawk": 1,
"Tempting offer": 2,
"Collect evidence": 1,
"Enlightened Counters": 1,
"Time Travel": 2,
- "Crushing Teeth": 1,
"Currency Counters": 1,
"Trap Counters": 1,
"Companion": 1,
- "Praesidium Protectiva": 1,
"Hyena Kindred": 1,
"Cloak": 2,
"Manifest dread": 1,
"Bear Kindred": 1,
- "Blessing of Light": 1,
- "Aegis of the Emperor": 1,
"Custodes Kindred": 1,
"Berserker Kindred": 1,
"Invitation Counters": 1,
- "Look to the Stars": 1,
"Monger Kindred": 1,
- "Ice Counters": 1,
- "Wild Card": 1,
- "Call for Aid": 1,
- "Stall for Time": 1,
- "Pray for Protection": 1,
- "Strike a Deal": 1
+ "Ice Counters": 1
},
"blue": {
- "Blink": 569,
- "Enter the Battlefield": 569,
+ "Blink": 573,
+ "Enter the Battlefield": 573,
"Guest Kindred": 3,
- "Human Kindred": 540,
- "Leave the Battlefield": 569,
- "Little Fellas": 1430,
- "Outlaw Kindred": 217,
- "Rogue Kindred": 150,
+ "Human Kindred": 546,
+ "Leave the Battlefield": 573,
+ "Little Fellas": 1439,
+ "Outlaw Kindred": 219,
+ "Rogue Kindred": 151,
"Casualty": 5,
"Spell Copy": 78,
- "Spells Matter": 1722,
- "Spellslinger": 1722,
+ "Spells Matter": 1726,
+ "Spellslinger": 1726,
"Topdeck": 414,
"Bird Kindred": 148,
- "Flying": 767,
- "Toughness Matters": 905,
- "Aggro": 894,
+ "Flying": 771,
+ "Toughness Matters": 908,
+ "Aggro": 898,
"Aristocrats": 119,
- "Auras": 346,
- "Combat Matters": 894,
- "Enchant": 303,
- "Enchantments Matter": 733,
+ "Auras": 347,
+ "Combat Matters": 898,
+ "Enchant": 305,
+ "Enchantments Matter": 735,
"Midrange": 54,
"Sacrifice Matters": 110,
"Theft": 114,
- "Voltron": 595,
- "Big Mana": 1217,
+ "Voltron": 598,
+ "Big Mana": 1224,
"Elf Kindred": 11,
- "Mill": 562,
- "Reanimate": 493,
+ "Mill": 564,
+ "Reanimate": 495,
"Shaman Kindred": 11,
"Horror Kindred": 48,
"Insect Kindred": 7,
@@ -25550,16 +25484,15 @@
"Manifest dread": 9,
"Control": 666,
"Counterspells": 348,
- "Interaction": 896,
- "Stax": 914,
+ "Interaction": 793,
+ "Stax": 915,
"Fish Kindred": 43,
- "Flash": 167,
- "Probing Telepathy": 1,
- "Protection": 158,
+ "Flash": 169,
"Ward": 39,
+ "Protection": 64,
"Threshold": 9,
- "Historics Matter": 289,
- "Legends Matter": 289,
+ "Historics Matter": 292,
+ "Legends Matter": 292,
"Noble Kindred": 13,
"Octopus Kindred": 42,
"Removal": 249,
@@ -25570,28 +25503,28 @@
"Scion Kindred": 6,
"Token Creation": 271,
"Tokens Matter": 272,
- "+1/+1 Counters": 221,
- "Counters Matter": 475,
+ "+1/+1 Counters": 223,
+ "Counters Matter": 478,
"Drake Kindred": 75,
"Kicker": 29,
- "Card Draw": 1046,
- "Discard Matters": 325,
- "Loot": 245,
- "Wizard Kindred": 525,
+ "Card Draw": 1050,
+ "Discard Matters": 326,
+ "Loot": 246,
+ "Wizard Kindred": 526,
"Cost Reduction": 144,
- "Artifacts Matter": 620,
+ "Artifacts Matter": 621,
"Equipment Matters": 90,
"Lands Matter": 198,
- "Conditional Draw": 194,
+ "Conditional Draw": 196,
"Defender": 69,
- "Draw Triggers": 170,
+ "Draw Triggers": 171,
"Wall Kindred": 41,
- "Wheels": 210,
+ "Wheels": 211,
"Artifact Tokens": 107,
"Thopter Kindred": 17,
- "Cantrips": 191,
- "Unconditional Draw": 448,
- "Board Wipes": 55,
+ "Cantrips": 192,
+ "Unconditional Draw": 449,
+ "Board Wipes": 56,
"Bracket:MassLandDenial": 8,
"Equipment": 25,
"Reconfigure": 3,
@@ -25602,17 +25535,16 @@
"Doctor Kindred": 9,
"Doctor's Companion": 7,
"Doctor's companion": 6,
- "Ultimate Sacrifice": 1,
"Drone Kindred": 22,
"Zombie Kindred": 83,
"Turtle Kindred": 21,
"Avatar Kindred": 14,
- "Exile Matters": 140,
+ "Exile Matters": 141,
"Suspend": 24,
"Time Counters": 32,
"Impulse": 11,
- "Soldier Kindred": 80,
- "Combat Tricks": 129,
+ "Soldier Kindred": 83,
+ "Combat Tricks": 131,
"Strive": 4,
"Cleric Kindred": 24,
"Enchantment Tokens": 11,
@@ -25620,7 +25552,7 @@
"Life Matters": 38,
"Lifegain": 38,
"Beast Kindred": 47,
- "Elemental Kindred": 109,
+ "Elemental Kindred": 110,
"Toolbox": 70,
"Energy": 24,
"Energy Counters": 22,
@@ -25630,34 +25562,35 @@
"Politics": 43,
"Servo Kindred": 1,
"Vedalken Kindred": 55,
- "Burn": 78,
+ "Burn": 79,
"Max speed": 4,
"Start your engines!": 4,
"Scry": 138,
"X Spells": 109,
- "Shapeshifter Kindred": 57,
+ "Shapeshifter Kindred": 58,
"Evoke": 6,
"Leviathan Kindred": 21,
"Whale Kindred": 17,
"Detective Kindred": 20,
"Sphinx Kindred": 61,
"Renew": 3,
- "Advisor Kindred": 31,
+ "Advisor Kindred": 32,
"Merfolk Kindred": 215,
"Robot Kindred": 20,
- "Stun Counters": 45,
+ "Stun Counters": 46,
"Cleave": 4,
"Spellshaper Kindred": 11,
"Reflection Kindred": 2,
"Storm": 9,
"Time Travel": 3,
"Domain": 6,
- "Siren Kindred": 19,
+ "Siren Kindred": 20,
"Backgrounds Matter": 13,
"Choose a background": 7,
"Halfling Kindred": 1,
- "Partner with": 8,
- "Vigilance": 49,
+ "Partner": 17,
+ "Partner with": 9,
+ "Vigilance": 50,
"Bracket:ExtraTurn": 29,
"Foretell": 13,
"God Kindred": 8,
@@ -25666,7 +25599,7 @@
"Frog Kindred": 20,
"Salamander Kindred": 8,
"Encore": 4,
- "Pirate Kindred": 67,
+ "Pirate Kindred": 68,
"Warrior Kindred": 44,
"Treasure": 13,
"Treasure Token": 15,
@@ -25680,17 +25613,16 @@
"Dragon Kindred": 45,
"Elder Kindred": 4,
"Hexproof": 21,
- "Faerie Kindred": 80,
+ "Faerie Kindred": 81,
"Mana Dork": 47,
"Morph": 43,
- "Pingers": 22,
+ "Pingers": 23,
"Flood Counters": 3,
"Manifestation Counters": 1,
- "Clones": 143,
+ "Clones": 145,
"Cipher": 7,
"Prototype": 4,
"Learn": 4,
- "Aura Swap": 1,
"Mutate": 5,
"Monarch": 8,
"Quest Counters": 4,
@@ -25701,9 +25633,9 @@
"Metalcraft": 8,
"Addendum": 3,
"Heroic": 10,
- "Convoke": 10,
+ "Convoke": 11,
"Angel Kindred": 3,
- "Spirit Kindred": 148,
+ "Spirit Kindred": 149,
"Nightmare Kindred": 17,
"Role token": 6,
"Infect": 34,
@@ -25713,11 +25645,10 @@
"Incubate": 4,
"Incubator Token": 4,
"Phyrexian Kindred": 51,
- "Project Image": 1,
"Hero Kindred": 7,
"Job select": 4,
"Oil Counters": 12,
- "Alien Kindred": 7,
+ "Alien Kindred": 8,
"Planeswalkers": 72,
"Superfriends": 72,
"Amass": 13,
@@ -25758,7 +25689,7 @@
"Mana Rock": 22,
"Overload": 6,
"Haste": 2,
- "Homunculus Kindred": 20,
+ "Homunculus Kindred": 21,
"Rooms Matter": 12,
"Card Selection": 10,
"Explore": 10,
@@ -25770,7 +25701,6 @@
"Phasing": 10,
"Converge": 4,
"Hag Kindred": 2,
- "Partner": 8,
"Corrupted": 2,
"Clash": 7,
"Madness": 7,
@@ -25812,7 +25742,7 @@
"Awaken": 5,
"Undaunted": 1,
"Kavu Kindred": 2,
- "Golem Kindred": 4,
+ "Golem Kindred": 5,
"Warp": 7,
"Lhurgoyf Kindred": 1,
"Pillowfort": 4,
@@ -25865,7 +25795,6 @@
"Bargain": 5,
"Warlock Kindred": 8,
"Behold": 1,
- "Avoidance": 1,
"Exploit": 8,
"Transmute": 6,
"Plot": 10,
@@ -25881,8 +25810,7 @@
"Trilobite Kindred": 3,
"Freerunning": 2,
"Tiefling Kindred": 2,
- "Two-Headed Coin": 1,
- "Monk Kindred": 19,
+ "Monk Kindred": 20,
"Pilot Kindred": 7,
"Multikicker": 3,
"Glimmer Kindred": 2,
@@ -25911,10 +25839,7 @@
"Exalted": 2,
"Hippogriff Kindred": 2,
"Assist": 4,
- "Neurotraumal Rod": 1,
"Tyranid Kindred": 2,
- "Children of the Cult": 1,
- "Genestealer's Kiss": 1,
"Infection Counters": 1,
"Powerstone Token": 6,
"Undying": 4,
@@ -25930,7 +25855,6 @@
"Nymph Kindred": 4,
"Forecast": 3,
"Crocodile Kindred": 3,
- "Aberrant Tinkering": 1,
"Germ Kindred": 1,
"Samurai Kindred": 1,
"Incarnation Kindred": 3,
@@ -25938,17 +25862,13 @@
"Efreet Kindred": 4,
"Horsemanship": 7,
"Demon Kindred": 2,
- "Discover": 2,
+ "Discover": 3,
"Tide Counters": 2,
"Camarid Kindred": 1,
"Weird Kindred": 4,
"Ooze Kindred": 2,
- "Blizzaga": 1,
- "Blizzara": 1,
- "Blizzard": 1,
"Ice Counters": 3,
"Lizard Kindred": 4,
- "Ceremorphosis": 1,
"First strike": 3,
"Split second": 5,
"Detain": 3,
@@ -25960,22 +25880,17 @@
"Graveyard Matters": 5,
"Loyalty Counters": 7,
"Compleated": 1,
- "Replacement Draw": 2,
+ "Replacement Draw": 3,
"Cost Scaling": 5,
"Modal": 5,
"Spree": 5,
- "Come Fly With Me": 1,
"Convert": 1,
"Living metal": 1,
"More Than Meets the Eye": 1,
"Praetor Kindred": 3,
- "Confounding Clouds": 1,
- "Affirmative": 1,
- "Negative": 1,
"Experience Counters": 1,
"Exhaust": 6,
"Indestructible": 3,
- "Homunculus Servant": 1,
"Kithkin Kindred": 1,
"Flanking": 1,
"Minotaur Kindred": 1,
@@ -25983,7 +25898,6 @@
"Treasure Counters": 1,
"Verse Counters": 3,
"Grandeur": 1,
- "Architect of Deception": 1,
"Lieutenant": 2,
"Hatchling Counters": 1,
"Werewolf Kindred": 1,
@@ -25994,7 +25908,6 @@
"Lifegain Triggers": 1,
"Lifeloss": 1,
"Lifeloss Triggers": 1,
- "Woman Who Walked the Earth": 1,
"Basic landcycling": 2,
"Fateseal": 2,
"Rabbit Kindred": 2,
@@ -26010,10 +25923,8 @@
"Divinity Counters": 1,
"Tentacle Kindred": 2,
"Synth Kindred": 2,
- "Bigby's Hand": 1,
"Fox Kindred": 1,
"Annihilator": 1,
- "Sonic Booster": 1,
"Foreshadow Counters": 1,
"Paradox": 2,
"Impending": 1,
@@ -26030,12 +25941,10 @@
"Ape Kindred": 1,
"Page Counters": 1,
"Constellation": 6,
- "Blue Magic": 1,
"Ranger Kindred": 3,
"Echo": 1,
"Demonstrate": 1,
"Dwarf Kindred": 1,
- "Hagneia": 1,
"Backup": 1,
"Monger Kindred": 1,
"Storage Counters": 2,
@@ -26045,11 +25954,9 @@
"Troll Kindred": 1,
"Lifelink": 1,
"Hideaway": 3,
- "Benediction of the Omnissiah": 1,
"Squad": 2,
"Starfish Kindred": 2,
"Tribute": 1,
- "Psychic Abomination": 1,
"Slith Kindred": 1,
"Slime Counters": 1,
"Elk Kindred": 2,
@@ -26075,28 +25982,23 @@
"Tempting offer": 1,
"Juggernaut Kindred": 1,
"Thalakos Kindred": 7,
- "Water Always Wins": 1,
"Knowledge Counters": 1,
"Sponge Kindred": 2,
"Minion Kindred": 1,
- "Parallel Universe": 1,
"Rejection Counters": 1,
"Secret council": 1,
"Adamant": 3,
- "Sleight of Hand": 1,
"Toy Kindred": 1,
"Toxic": 1,
"Harmonize": 3,
"Possession Counters": 1,
"Astartes Kindred": 1,
- "Suppressing Fire": 1,
"Sleep Counters": 1,
"Hexproof from": 1,
"Menace": 1,
- "Gust of Wind": 1,
"Coin Counters": 1,
"Archer Kindred": 1,
- "Hive Mind": 1
+ "Body-print": 1
},
"black": {
"Blink": 757,
@@ -26128,7 +26030,7 @@
"Token Creation": 415,
"Tokens Matter": 416,
"Combat Tricks": 174,
- "Interaction": 873,
+ "Interaction": 805,
"Midrange": 69,
"Horror Kindred": 184,
"Basic landcycling": 2,
@@ -26157,7 +26059,7 @@
"Trample": 54,
"Specter Kindred": 21,
"Centaur Kindred": 3,
- "Protection": 93,
+ "Protection": 51,
"Warrior Kindred": 168,
"Intimidate": 13,
"Spirit Kindred": 145,
@@ -26249,7 +26151,6 @@
"Corrupted": 7,
"Infect": 59,
"Poison Counters": 48,
- "Lord of the Pyrrhian Legions": 1,
"Necron Kindred": 25,
"Beast Kindred": 37,
"Frog Kindred": 8,
@@ -26265,16 +26166,14 @@
"Oil Counters": 3,
"Archon Kindred": 1,
"Backup": 4,
- "Endurant": 1,
"Squad": 3,
"Noble Kindred": 31,
- "Starscourge": 1,
"Blood Token": 27,
"Life to Draw": 8,
"Planeswalkers": 58,
"Superfriends": 58,
"Golem Kindred": 5,
- "Partner": 8,
+ "Partner": 15,
"Thrull Kindred": 22,
"\\+1/\\+2 Counters": 1,
"Flashback": 22,
@@ -26344,9 +26243,6 @@
"Equip": 32,
"Equipment": 35,
"Job select": 4,
- "Buy Information": 1,
- "Hire a Mercenary": 1,
- "Sell Contraband": 1,
"Treasure": 47,
"Treasure Token": 49,
"Treefolk Kindred": 6,
@@ -26408,7 +26304,6 @@
"Freerunning": 6,
"Buyback": 9,
"Choose a background": 6,
- "Tunnel Snakes Rule!": 1,
"Undying": 8,
"Flanking": 4,
"Changeling": 8,
@@ -26431,12 +26326,10 @@
"Nymph Kindred": 3,
"Mutate": 5,
"Hideaway": 2,
- "Animate Chains": 1,
"Finality Counters": 11,
"Suspend": 11,
"Time Counters": 14,
"Escape": 10,
- "Atomic Transmutation": 1,
"Fathomless descent": 3,
"Wither": 6,
"Goat Kindred": 3,
@@ -26472,9 +26365,7 @@
"Earthbending": 1,
"Dredge": 6,
"Dalek Kindred": 4,
- "Exterminate!": 1,
"Spell mastery": 4,
- "Chaosbringer": 1,
"Offspring": 4,
"Dauthi Kindred": 11,
"Shadow": 15,
@@ -26555,9 +26446,7 @@
"Devour": 3,
"Forage": 1,
"Exploit": 12,
- "Flesh Flayer": 1,
"Gremlin Kindred": 2,
- "Transfigure": 1,
" Blood Counters": 1,
"Investigate": 8,
"Inspired": 5,
@@ -26567,8 +26456,6 @@
"Max speed": 6,
"Start your engines!": 8,
"Manifest": 7,
- "Death Ray": 1,
- "Disintegration Ray": 1,
"Vigilance": 1,
"Channel": 3,
"Gold Token": 2,
@@ -26588,7 +26475,6 @@
"Meld": 1,
"Lamia Kindred": 2,
"Scout Kindred": 9,
- "Reverberating Summons": 1,
"-0/-2 Counters": 2,
"Evoke": 5,
"Dinosaur Kindred": 8,
@@ -26597,16 +26483,12 @@
"Level Counters": 4,
"Level Up": 4,
"Ritual Counters": 1,
- "Multi-threat Eliminator": 1,
"Discover": 2,
"Ki Counters": 2,
"Boar Kindred": 3,
"Exhaust": 1,
"Soul Counters": 4,
"Monstrosity": 3,
- "Secrets of the Soul": 1,
- "Grand Strategist": 1,
- "Phaeron": 1,
"Demonstrate": 1,
"Kirin Kindred": 1,
"Manifest dread": 2,
@@ -26614,7 +26496,6 @@
"Modal": 4,
"Spree": 4,
"Body Thief": 1,
- "Devour Intellect": 1,
"Battles Matter": 4,
"Efreet Kindred": 1,
"Jump": 1,
@@ -26627,11 +26508,8 @@
"Hippo Kindred": 1,
"Myr Kindred": 2,
"Persist": 4,
- "Enmitic Exterminator": 1,
"Undergrowth": 4,
- "Guardian Protocols": 1,
"Mannequin Counters": 1,
- "Bad Breath": 1,
"Plant Kindred": 2,
"Manticore Kindred": 1,
"Hit Counters": 2,
@@ -26639,26 +26517,19 @@
"Hour Counters": 1,
"Processor Kindred": 2,
"Awaken": 3,
- "Mold Harvest": 1,
"Nautilus Kindred": 1,
"Rigger Kindred": 1,
"Astartes Kindred": 4,
"Primarch Kindred": 1,
- "Primarch of the Death Guard": 1,
"Divinity Counters": 1,
- "Psychic Blades": 1,
"Feeding Counters": 1,
"Multiple Copies": 4,
"Nazgûl": 1,
"Atog Kindred": 1,
- "Synaptic Disintegrator": 1,
- "Relentless March": 1,
"Aftermath": 1,
"Epic": 1,
"Kinship": 2,
"Revival Counters": 1,
- "Mutsunokami": 1,
- "Weird Insight": 1,
"Weird Kindred": 1,
"Scarecrow Kindred": 3,
"Eon Counters": 1,
@@ -26671,15 +26542,9 @@
"Offering": 1,
"Depletion Counters": 1,
"Carrier Kindred": 5,
- "Rot Fly": 1,
- "Dynastic Advisor": 1,
- "Curse of the Walking Pox": 1,
- "Executioner Round": 1,
- "Hyperfrag Round": 1,
"Mayhem": 3,
"Magecraft": 2,
"Populate": 1,
- "Harbinger of Despair": 1,
"Octopus Kindred": 2,
"Starfish Kindred": 2,
"Kithkin Kindred": 1,
@@ -26687,73 +26552,43 @@
"Retrace": 2,
"Mole Kindred": 1,
"Relentless Rats": 1,
- "Decayed": 1,
"Kraken Kindred": 1,
"Blight Counters": 1,
- "Phalanx Commander": 1,
- "Blood Chalice": 1,
- "Elite Troops": 1,
"Monger Kindred": 1,
"Coward Kindred": 1,
"Serf Kindred": 1,
- "Super Nova": 1,
"Shadowborn Apostle": 1,
"C'tan Kindred": 2,
- "Drain Life": 1,
- "Matter Absorption": 1,
- "Spear of the Void Dragon": 1,
"Join forces": 1,
"Surrakar Kindred": 2,
"Tribute": 1,
"Ape Kindred": 2,
"Sweep": 1,
- "Hyperphase Threshers": 1,
- "Command Protocols": 1,
"Snail Kindred": 1,
"Cascade": 1,
- "Jolly Gutpipes": 1,
"Spike Kindred": 1,
"Mite Kindred": 1,
- "Blood Drain": 1,
"Ripple": 1,
- "My Will Be Done": 1,
- "The Seven-fold Chant": 1,
"Bracket:ExtraTurn": 1,
"Tempting offer": 1,
"Prey Counters": 1,
"Firebending": 1,
"Necrodermis Counters": 1,
"Varmint Kindred": 1,
- "Consume Anomaly": 1,
"Stash Counters": 1,
"Pegasus Kindred": 1,
- "Chef's Knife": 1,
"Stun Counters": 2,
"Plague Counters": 2,
- "Prismatic Gallery": 1,
- "Dynastic Codes": 1,
- "Targeting Relay": 1,
"Demigod Kindred": 1,
- "Horrific Symbiosis": 1,
"Chroma": 1,
"Barbarian Kindred": 2,
- "Rat Tail": 1,
- "Devourer of Souls": 1,
- "Spiked Retribution": 1,
- "Death Gigas": 1,
- "Galian Beast": 1,
- "Hellmasker": 1,
- "Deal with the Black Guardian": 1,
"Doctor Kindred": 1,
"Doctor's Companion": 1,
"Doctor's companion": 1,
"Compleated": 1,
- "Toxic Spores": 1,
"Wish Counters": 1,
"Camel Kindred": 1,
- "Petrification Counters": 1,
- "Burning Chains": 1,
- "My First Friend": 1
+ "Petrification Counters": 1
},
"red": {
"Burn": 1537,
@@ -26770,7 +26605,7 @@
"Combat Matters": 1406,
"Combat Tricks": 160,
"Discard Matters": 303,
- "Interaction": 652,
+ "Interaction": 631,
"Madness": 18,
"Mill": 341,
"Reanimate": 261,
@@ -26792,7 +26627,6 @@
"Warrior Kindred": 363,
"Cantrips": 79,
"Draw Triggers": 54,
- "Heavy Rock Cutter": 1,
"Tyranid Kindred": 4,
"Wheels": 58,
"+1/+1 Counters": 248,
@@ -26857,7 +26691,7 @@
"Equipment Matters": 141,
"Samurai Kindred": 20,
"Shaman Kindred": 175,
- "Protection": 31,
+ "Protection": 20,
"Conditional Draw": 42,
"Phyrexian Kindred": 44,
"Ally Kindred": 19,
@@ -26877,7 +26711,7 @@
"Wizard Kindred": 94,
"Treasure": 108,
"Treasure Token": 111,
- "Partner": 7,
+ "Partner": 15,
"-1/-1 Counters": 27,
"Infect": 7,
"Ore Counters": 33,
@@ -26931,7 +26765,6 @@
"Flash": 30,
"Astartes Kindred": 5,
"Demon Kindred": 15,
- "Ruinous Ascension": 1,
"Amass": 11,
"Army Kindred": 10,
"Robot Kindred": 18,
@@ -26986,7 +26819,6 @@
"Horror Kindred": 13,
"Celebration": 5,
"Wurm Kindred": 4,
- "Scorching Ray": 1,
"God Kindred": 10,
"Metalcraft": 6,
"Hellbent": 7,
@@ -26997,15 +26829,12 @@
"Offering": 2,
"Flanking": 6,
"Knight Kindred": 54,
- "Blow Up": 1,
"Strive": 4,
"Construct Kindred": 13,
"Prototype": 4,
"Fight": 16,
"Bloodthirst": 8,
- "Crown of Madness": 1,
"Delirium": 12,
- "Devastating Charge": 1,
"Unleash": 5,
"Ooze Kindred": 4,
"Wolverine Kindred": 7,
@@ -27081,7 +26910,6 @@
"Shapeshifter Kindred": 5,
"Harmonize": 3,
"Imp Kindred": 2,
- "Lord of Chaos": 1,
"Fury Counters": 1,
"Peasant Kindred": 6,
"Rat Kindred": 8,
@@ -27118,7 +26946,6 @@
"Coyote Kindred": 1,
"Gold Token": 2,
"Hero Kindred": 11,
- "Gift of Chaos": 1,
"Warlock Kindred": 9,
"Beholder Kindred": 1,
"Monstrosity": 7,
@@ -27149,7 +26976,6 @@
"Dragon's Approach": 1,
"Multiple Copies": 2,
"Surveil": 2,
- "Hunters for Hire": 1,
"Quest Counters": 5,
"\\+0/\\+1 Counters": 1,
"\\+2/\\+2 Counters": 1,
@@ -27172,7 +26998,6 @@
"Dethrone": 4,
"Escape": 5,
"Powerstone Token": 5,
- "Bio-plasmic Barrage": 1,
"Ravenous": 1,
"Cloak": 1,
"Spell mastery": 3,
@@ -27189,10 +27014,6 @@
"Manifest": 4,
"Chroma": 3,
"Bracket:ExtraTurn": 3,
- "Enthralling Performance": 1,
- "Fira": 1,
- "Firaga": 1,
- "Fire": 1,
"Bending": 5,
"Firebending": 5,
"Snake Kindred": 1,
@@ -27230,10 +27051,8 @@
"Wither": 6,
"Embalm": 1,
"Pressure Counters": 1,
- "Locus of Slaanesh": 1,
"Emerge": 1,
"Annihilator": 1,
- "Slivercycling": 1,
"Hyena Kindred": 2,
"Recover": 1,
"Doom Counters": 2,
@@ -27242,35 +27061,26 @@
"Eerie": 1,
"Clue Token": 3,
"Investigate": 3,
- "Vicious Mockery": 1,
"Imprint": 1,
"Battles Matter": 5,
"Alien Kindred": 3,
"Blitz": 8,
"Converge": 2,
"Void": 3,
- "Symphony of Pain": 1,
"Vanishing": 2,
- "Berzerker": 1,
- "Sigil of Corruption": 1,
- "The Betrayer": 1,
"Venture into the dungeon": 2,
"Amplify": 1,
- "Frenzied Rampage": 1,
"Rhino Kindred": 2,
"Forestwalk": 1,
"Serpent Kindred": 2,
"Assist": 2,
"Spectacle": 3,
- "Loud Ruckus": 1,
"Lieutenant": 3,
"Scorpion Kindred": 2,
"Stun Counters": 1,
"Delve": 1,
"Join forces": 1,
"Illusion Kindred": 1,
- "Detonate": 1,
- "Disarm": 1,
"Worm Kindred": 2,
"Mine Counters": 1,
"Performer Kindred": 3,
@@ -27282,7 +27092,6 @@
"Kinship": 3,
"Divinity Counters": 1,
"Banding": 1,
- "Sonic Blaster": 1,
"Elephant Kindred": 2,
"Pangolin Kindred": 1,
"Impending": 1,
@@ -27290,7 +27099,6 @@
"Squad": 2,
"Support": 1,
"Plant Kindred": 2,
- "Selfie Shot": 1,
"Bloodrush": 6,
"Replicate": 4,
"Porcupine Kindred": 1,
@@ -27305,18 +27113,10 @@
"Badger Kindred": 2,
"Wage Counters": 1,
"Leech Kindred": 1,
- "Murasame": 1,
"Depletion Counters": 1,
- "Bio-Plasmic Scream": 1,
- "Family Gathering": 1,
- "Family gathering": 1,
- "Allure of Slaanesh": 1,
- "Fire Cross": 1,
"Seven Dwarves": 1,
"Dredge": 1,
"Mobilize": 3,
- "Temporal Foresight": 1,
- "Double Overdrive": 1,
"Split second": 4,
"Grandeur": 2,
"Kirin Kindred": 1,
@@ -27327,19 +27127,14 @@
"Slith Kindred": 1,
"Ember Counters": 1,
"Hideaway": 1,
- "Mantle of Inspiration": 1,
"Ascend": 2,
"Ripple": 1,
"Synth Kindred": 1,
"Vigilance": 2,
"Tempting offer": 2,
"Read Ahead": 2,
- "Advanced Species": 1,
"Summon": 1,
"Slug Kindred": 1,
- "Thundaga": 1,
- "Thundara": 1,
- "Thunder": 1,
"Manifest dread": 2,
"Contested Counters": 1,
"Epic": 1,
@@ -27351,8 +27146,6 @@
"Centaur Kindred": 1,
"Token Modification": 1,
"Turtle Kindred": 1,
- "Bribe the Guards": 1,
- "Threaten the Merchant": 1,
"Ninja Kindred": 1,
"Ninjutsu": 1
},
@@ -27377,8 +27170,7 @@
"Token Creation": 520,
"Tokens Matter": 529,
"Artifacts Matter": 449,
- "Heavy Power Hammer": 1,
- "Interaction": 662,
+ "Interaction": 538,
"Little Fellas": 1380,
"Mutant Kindred": 27,
"Ravenous": 7,
@@ -27416,7 +27208,6 @@
"Fight": 74,
"Historics Matter": 263,
"Legends Matter": 263,
- "Nitro-9": 1,
"Rebel Kindred": 3,
"Equipment Matters": 79,
"Reach": 219,
@@ -27469,11 +27260,11 @@
"Cycling": 52,
"Discard Matters": 87,
"Loot": 52,
+ "Protection": 96,
"Vehicles": 25,
"Revolt": 6,
"Scout Kindred": 97,
"Stax": 271,
- "Protection": 193,
"Faerie Kindred": 13,
"Soldier Kindred": 37,
"Mount Kindred": 14,
@@ -27499,7 +27290,7 @@
"Swampwalk": 10,
"Bracket:TutorNonland": 65,
"Collect evidence": 6,
- "Partner": 8,
+ "Partner": 13,
"Treasure": 26,
"Treasure Token": 25,
"Turtle Kindred": 12,
@@ -27611,7 +27402,6 @@
"Quest Counters": 4,
"Delve": 2,
"Intimidate": 2,
- "Genomic Enhancement": 1,
"Wizard Kindred": 22,
"Morph": 26,
"Drone Kindred": 13,
@@ -27652,7 +27442,6 @@
"Provoke": 3,
"Sliver Kindred": 18,
"Warp": 8,
- "Brood Telepathy": 1,
"Cleric Kindred": 23,
"Ki Counters": 2,
"Hippo Kindred": 5,
@@ -27690,10 +27479,7 @@
"Acorn Counters": 1,
"Bracket:MassLandDenial": 6,
"Backup": 6,
- "Natural Recovery": 1,
- "Proclamator Hailer": 1,
"Fateful hour": 2,
- "Gathered Swarm": 1,
"Cockatrice Kindred": 1,
"Pupa Counters": 1,
"Ninja Kindred": 4,
@@ -27735,7 +27521,6 @@
"Wolverine Kindred": 4,
"Pilot Kindred": 4,
"Sand Kindred": 2,
- "Immune": 1,
"Egg Kindred": 2,
"Soulbond": 8,
"Employee Kindred": 3,
@@ -27746,7 +27531,6 @@
"Rabbit Kindred": 10,
"Pillowfort": 6,
"Nymph Kindred": 4,
- "Nonbasic landwalk": 1,
"Choose a background": 6,
"Endure": 3,
"Awaken": 1,
@@ -27806,8 +27590,6 @@
"Riot": 3,
"Kithkin Kindred": 3,
"Slime Counters": 1,
- "Devouring Monster": 1,
- "Rapacious Hunger": 1,
"Replicate": 1,
"Demonstrate": 1,
"Samurai Kindred": 5,
@@ -27815,13 +27597,10 @@
"Mite Kindred": 1,
"Depletion Counters": 1,
"Cloak": 1,
- "Frenzied Metabolism": 1,
- "Titanic": 1,
"Storage Counters": 2,
"Renown": 6,
"Embalm": 1,
"Boast": 1,
- "Endless Swarm": 1,
"Undying": 4,
"Rat Kindred": 1,
"Efreet Kindred": 2,
@@ -27836,8 +27615,6 @@
"Graveyard Matters": 2,
"Flanking": 1,
"Ferret Kindred": 1,
- "000 Needles": 1,
- "10": 1,
"Wither": 3,
"Yeti Kindred": 3,
"Phasing": 1,
@@ -27847,7 +27624,6 @@
"Horsemanship": 1,
"Kinship": 3,
"Lhurgoyf Kindred": 5,
- "Pheromone Trail": 1,
"Awakening Counters": 1,
"Construct Kindred": 6,
"Vitality Counters": 1,
@@ -27863,21 +27639,17 @@
"Growth Counters": 2,
"Horse Kindred": 9,
"Aftermath": 1,
- "Infesting Spores": 1,
"Divinity Counters": 1,
"Harmonize": 3,
"Tribute": 3,
- "Strategic Coordinator": 1,
"Compleated": 1,
"Unicorn Kindred": 2,
"Nomad Kindred": 1,
"Licid Kindred": 2,
- "Fast Healing": 1,
"Council's dilemma": 3,
"Basic landcycling": 3,
"Landcycling": 3,
"Impending": 1,
- "Mama's Coming": 1,
"Dethrone": 1,
"Will of the Planeswalkers": 1,
"Offering": 1,
@@ -27889,7 +27661,6 @@
"Rebound": 3,
"Ribbon Counters": 1,
"Scientist Kindred": 2,
- "Vanguard Species": 1,
"Camel Kindred": 1,
"Wombat Kindred": 1,
"Possum Kindred": 2,
@@ -27902,20 +27673,15 @@
"Undaunted": 1,
"Bracket:ExtraTurn": 1,
"Map Token": 2,
- "Conjure": 1,
- "Conjure Elemental": 1,
"Multiple Copies": 1,
"Slime Against Humanity": 1,
"Slith Kindred": 1,
- "Animal May-Ham": 1,
"Web-slinging": 2,
"Spike Kindred": 10,
"Armadillo Kindred": 1,
- "Spore Chimney": 1,
"Monger Kindred": 1,
"Mouse Kindred": 1,
"Supply Counters": 1,
- "Abraxas": 1,
"Ripple": 1,
"Replacement Draw": 1,
"For Mirrodin!": 1,
@@ -27924,23 +27690,16 @@
"Mystic Kindred": 2,
"Tempting offer": 1,
"Ascend": 2,
- "Death Frenzy": 1,
"Hatching Counters": 1,
"Gold Token": 1,
"Read Ahead": 2,
- "Bear Witness": 1,
- "Final Heaven": 1,
- "Meteor Strikes": 1,
- "Somersault": 1,
"Banding": 1,
"Meld": 1,
"Velocity Counters": 1,
- "Hypertoxic Miasma": 1,
"Dash": 1,
"Mentor": 1,
"Nest Counters": 1,
"Toy Kindred": 1,
- "Shieldwall": 1,
"Freerunning": 1,
"Menace": 1,
"Processor Kindred": 1,
@@ -27948,20 +27707,18 @@
"Praetor Kindred": 3,
"-0/-1 Counters": 1,
"Scarecrow Kindred": 1,
- "Gather Your Courage": 1,
- "Run and Hide": 1,
"Plainswalk": 1
}
},
"generated_from": "merge (analytics + curated YAML + whitelist)",
"metadata_info": {
"mode": "merge",
- "generated_at": "2025-10-07T18:22:00",
+ "generated_at": "2025-10-09T00:37:26",
"curated_yaml_files": 735,
"synergy_cap": 5,
"inference": "pmi",
"version": "phase-b-merge-v1",
- "catalog_hash": "ae79af02508e2f9184aa74d63db5c9987fd65cfa87ce7adb50aec2f6ae8397c5"
+ "catalog_hash": "9f938ea43a438ed3d924bdb971bf4efa34b80fa3728877878729db50c9afa2fd"
},
"description_fallback_summary": null
}
\ No newline at end of file