claude-home/paper-dynasty/card-evolution-prd/07-variant-system.md
Cal Corum aafe527d51
All checks were successful
Reindex Knowledge Base / reindex (push) Successful in 5s
docs: add Major Domo and Paper Dynasty release notes and card evolution PRD
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 22:29:18 -05:00

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):

  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.