Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4.0 KiB
7. Card Model Changes and Variant System
< Back to Index | Next: Display and Visual Identity >
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):
- Read
card.variantfrom the card instance - Call
GET battingcardratings/player/{player_id}?variant={card.variant}— the API filters by(player_id, variant)and returns the matchingbattingcardrow with its nested vL/vR ratings - Each variant has its own distinct
battingcardrow (withbattingcard.variantmatchingcard.variant). The ratings rows themselves don't carry a variant — they belong to a specificbattingcard, and the variant distinguishes whichbattingcardto use variant = 0is the base card (unchanged behavior); evolved/cosmetic variants use higher values
Current implementation:
card.variantdoes not exist, yet, butbattingcard.variantcolumn 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 onbattingcard_id, which is inherently variant-specific since each variant produces a separatebattingcardrow. Same pattern forpitchingcard/pitcherscouting.
For images (card display):
- Read
card.variantfrom the card instance - If
variantis not 0: fetchbattingcard.image_urlfor that variant — the pre-rendered image (PNG or APNG) with cosmetics and evolution visuals baked in - If
variantis 0: useplayer.image/player.image2(base card, unchanged behavior)
Migration required:
battingcard.image_urlandpitchingcard.image_urlcolumns do not exist yet — they must be added (nullable varchar). Current image resolution usesplayer.imageandplayer.image2fields exclusively (checked viaPlayer.batter_card_url/Player.pitcher_card_urlproperties ingameplay_models.py, andhelpers.player_bcard/helpers.player_pcardin the legacy cog system). Bot display logic must be updated to checkbattingcard.image_urlfirst whencard.variant != 0, falling back toplayer.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.