fix: restored modified README.md
This commit is contained in:
matt 2025-10-02 17:14:34 -07:00
commit 783e2e2c0a
3 changed files with 42 additions and 13 deletions

View file

@ -28,5 +28,7 @@
- QA documentation: added `docs/qa/mdfc_staging_checklist.md` outlining the staging validation pass required before removing the MDFC compatibility guard.
## Fixed
- Documented friendly handling for missing `commander_cards.csv` data during manual QA drills to prevent white-screen failures.
- Headless runner commander validation now accepts fuzzy commander prefixes so automated builds using short commander names keep working.
- Setup filtering now applies security-stamp exclusions case-insensitively, preventing Acorn/Heart promo cards from entering Commander pools.
- Commander browser thumbnails restore the double-faced flip control so MDFC commanders expose both faces directly in the catalog.
- Commander browser thumbnails restore the double-faced flip control so MDFC commanders expose both faces directly in the catalog.

View file

@ -3,7 +3,9 @@ from __future__ import annotations
import argparse
import json
import os
import re
from dataclasses import asdict, dataclass, field
from functools import lru_cache
from typing import Any, Dict, List, Optional, Tuple
from deck_builder.builder import DeckBuilder
@ -11,7 +13,6 @@ from deck_builder import builder_constants as bc
from file_setup.setup import initial_setup
from tagging import tagger
from exceptions import CommanderValidationError
from commander_exclusions import lookup_commander_detail
def _is_stale(file1: str, file2: str) -> bool:
"""Return True if file2 is missing or older than file1."""
@ -73,7 +74,15 @@ def _normalize_commander_name(value: Any) -> str:
return str(value or "").strip().casefold()
def _load_commander_name_lookup() -> set[str]:
def _tokenize_commander_name(value: Any) -> List[str]:
normalized = _normalize_commander_name(value)
if not normalized:
return []
return [token for token in re.split(r"[^a-z0-9]+", normalized) if token]
@lru_cache(maxsize=1)
def _load_commander_name_lookup() -> Tuple[set[str], Tuple[str, ...]]:
builder = DeckBuilder(
headless=True,
log_outputs=False,
@ -81,16 +90,19 @@ def _load_commander_name_lookup() -> set[str]:
input_func=lambda *_: "",
)
df = builder.load_commander_data()
names: set[str] = set()
raw_names: List[str] = []
for column in ("name", "faceName"):
if column not in df.columns:
continue
series = df[column].dropna().astype(str)
for raw in series:
normalized = _normalize_commander_name(raw)
if normalized:
names.add(normalized)
return names
raw_names.extend(series.tolist())
normalized = {
norm
for norm in (_normalize_commander_name(name) for name in raw_names)
if norm
}
ordered_raw = tuple(dict.fromkeys(raw_names))
return normalized, ordered_raw
def _validate_commander_available(command_name: str) -> None:
@ -98,11 +110,27 @@ def _validate_commander_available(command_name: str) -> None:
if not normalized:
return
available = _load_commander_name_lookup()
available, raw_names = _load_commander_name_lookup()
if normalized in available:
return
info = lookup_commander_detail(command_name)
query_tokens = _tokenize_commander_name(command_name)
for candidate in raw_names:
candidate_norm = _normalize_commander_name(candidate)
if not candidate_norm:
continue
if candidate_norm.startswith(normalized):
return
candidate_tokens = _tokenize_commander_name(candidate)
if query_tokens and all(token in candidate_tokens for token in query_tokens):
return
try:
from commander_exclusions import lookup_commander_detail as _lookup_commander_detail # type: ignore[import-not-found]
except ImportError: # pragma: no cover
_lookup_commander_detail = None
info = _lookup_commander_detail(command_name) if _lookup_commander_detail else None
if info is not None:
primary_face = str(info.get("primary_face") or info.get("name") or "").strip()
eligible_faces = info.get("eligible_faces")
@ -170,7 +198,6 @@ def run(
trimmed_commander = (command_name or "").strip()
if trimmed_commander:
_validate_commander_available(trimmed_commander)
command_name = trimmed_commander
owned_prompt_inputs: List[str] = []
owned_files_available = _headless_list_owned_files()

View file

@ -1,6 +1,6 @@
name,faceName,edhrecRank,colorIdentity,colors,manaCost,manaValue,type,creatureTypes,text,power,toughness,keywords,themeTags,layout,side
Shock,,,,R,{R},1,Instant,,Deal 2 damage to any target.,,,,[Burn],normal,
Plains,,,,W,,0,Land,,{T}: Add {W}.,,,,[Land],normal,name,faceName,edhrecRank,colorIdentity,colors,manaCost,manaValue,type,creatureTypes,text,power,toughness,keywords,themeTags,layout,side
Plains,,,,W,,0,Land,,{T}: Add {W}.,,,,[Land],normal,
Sol Ring,,1,Colorless,,{1},{1},Artifact,,{T}: Add {C}{C}.,,,Mana,Utility,normal,
Llanowar Elves,,5000,G,G,{G},{1},Creature,Elf Druid,{T}: Add {G}.,1,1,Mana,Tribal;Ramp,normal,
Island,,9999,U,U,,,Land,,{T}: Add {U}.,,,Land,,normal,

1 name,faceName,edhrecRank,colorIdentity,colors,manaCost,manaValue,type,creatureTypes,text,power,toughness,keywords,themeTags,layout,side name faceName edhrecRank colorIdentity colors manaCost manaValue type creatureTypes text power toughness keywords themeTags layout side
2 Shock,,,,R,{R},1,Instant,,Deal 2 damage to any target.,,,,[Burn],normal, Shock R {R} 1 Instant Deal 2 damage to any target. [Burn] normal
3 Plains,,,,W,,0,Land,,{T}: Add {W}.,,,,[Land],normal,name,faceName,edhrecRank,colorIdentity,colors,manaCost,manaValue,type,creatureTypes,text,power,toughness,keywords,themeTags,layout,side Plains W 0 Land {T}: Add {W}. [Land] normal
4 Sol Ring,,1,Colorless,,{1},{1},Artifact,,{T}: Add {C}{C}.,,,Mana,Utility,normal, Sol Ring 1 Colorless {1} {1} Artifact {T}: Add {C}{C}. Mana Utility normal
5 Llanowar Elves,,5000,G,G,{G},{1},Creature,Elf Druid,{T}: Add {G}.,1,1,Mana,Tribal;Ramp,normal, Llanowar Elves 5000 G G {G} {1} Creature Elf Druid {T}: Add {G}. 1 1 Mana Tribal;Ramp normal
6 Island,,9999,U,U,,,Land,,{T}: Add {U}.,,,Land,,normal, Island 9999 U U Land {T}: Add {U}. Land normal