# 7. Card Model Changes and Variant System

[< Back to Index](README.md) | [Next: Display and Visual Identity >](08-display.md)

---

## 7.1 How the Game Engine Resolves Ratings and Images for a Card

When the bot builds a game lineup or displays a card, it checks `card.variant`:

**For ratings (game engine):**
1. Read `card.variant` from the card instance
2. Call `GET battingcardratings/player/{player_id}?variant={card.variant}` — the API filters by
   `(player_id, variant)` and returns the matching `battingcard` row with its nested vL/vR ratings
3. Each variant has its own distinct `battingcard` row (with `battingcard.variant` matching
   `card.variant`). The ratings rows themselves don't carry a variant — they belong to a specific
   `battingcard`, and the variant distinguishes which `battingcard` to use
4. `variant = 0` is the base card (unchanged behavior); evolved/cosmetic variants use higher values

> **Current implementation:** `card.variant` does not exist, yet, but `battingcard.variant` column does already exist
> (default 0). The API endpoint and game engine query path (`gameplay_queries.py →
> get_batter_scouting_or_none`) already pass variant through. The local Postgres cache
> (`batterscouting`) keys on `battingcard_id`, which is inherently variant-specific since each
> variant produces a separate `battingcard` row. Same pattern for `pitchingcard`/`pitcherscouting`.

**For images (card display):**
1. Read `card.variant` from the card instance
2. If `variant` is not 0: fetch `battingcard.image_url` for that variant — the pre-rendered
   image (PNG or APNG) with cosmetics and evolution visuals baked in
3. If `variant` is 0: use `player.image` / `player.image2` (base card, unchanged behavior)

> **Migration required:** `battingcard.image_url` and `pitchingcard.image_url` columns do not
> exist yet — they must be added (nullable varchar). Current image resolution uses `player.image`
> and `player.image2` fields exclusively (checked via `Player.batter_card_url` /
> `Player.pitcher_card_url` properties in `gameplay_models.py`, and `helpers.player_bcard` /
> `helpers.player_pcard` in the legacy cog system). Bot display logic must be updated to check
> `battingcard.image_url` first when `card.variant != 0`, falling back to `player.image`.

The variant field on the card instance acts as a simple pointer. The bot does not need to know
about evolution tiers, cosmetics, or boost profiles — it just reads the variant and gets the
right ratings and image. All complexity is in the variant creation/update path, not the read path.

**All card instances of the same `player_id` on the same team share the same variant.** The
variant is stored authoritatively on `evolution_card_state` and propagated to `card.variant` on
all matching instances when it changes. Duplicate cards are not differentiated.

## 7.2 Evolved Card Naming

When `evolution_card_state.fully_evolved = true`, the card display name is modified:
- Base name: `"Mike Trout"`
- Evolved name: `"[TeamName]'s Evolved Mike Trout"`

The `player.p_name` field is NOT modified (it is a shared blueprint). The evolved name is
computed dynamically from the card state record at display time. This preserves data integrity
and allows future re-display if the naming convention changes.

## 7.3 Card Uniqueness

**Within a team:** All copies of the same `player_id` are identical — same evolution state, same
variant, same boosts, same cosmetics. Duplicates are not differentiated in any way.

**Across teams:** Two teams that evolve the same `player_id` with the same boost profile and
same cosmetics will share the same variant hash — and therefore the same ratings rows and S3
image. This is by design: shared variants avoid redundant storage and rendering.

Uniqueness diverges when teams purchase different cosmetics — each distinct combination produces
a different variant hash, creating a genuinely different card with its own rendered image.
Premium cosmetic customization is the path to a truly unique card. Rating boosts are determined
by auto-detected card profile and are not customizable.
