# 13. Implementation Phases and Checklists

[< Back to Index](README.md) | [Next: Risks and Open Questions >](14-risks.md)

---

## Phase 0 — Render Pipeline Optimization (Week 0, Pre-Requisite)

Focus: Persistent browser, self-hosted fonts, concurrent upload pipeline. This phase is
**independent of evolution** and benefits all existing card generation workflows immediately.
Should be implemented before any evolution work begins.

**Deliverables:**
- Persistent Chromium browser instance in API server (replaces per-request browser launch)
- FastAPI lifespan hooks for browser startup/shutdown with auto-reconnect
- Self-hosted fonts: download Source Sans 3 + Open Sans WOFF2 files, base64-embed in template
- Remove Google Fonts CDN `@import` from `style.html`
- Concurrent upload pipeline in `pd_cards/core/upload.py` using `asyncio.Semaphore(8)` + `asyncio.gather`
- Increase `fetch_card_image` timeout from 6s to 10s to account for render queue contention
- Benchmark: measure per-card render time before/after, total run time for full cardset

**Implementation details:** See [02-architecture.md § Card Render Pipeline Optimization](02-architecture.md#card-render-pipeline-optimization)

**Success Criteria:** Per-card render time drops from ~3s to ~0.6-1.0s. Full 800-card upload
run completes in under 5 minutes (down from ~40 minutes). No external font CDN dependency.

**Files changed:**

| File | Change |
|---|---|
| `database/app/routers_v2/players.py` | Persistent browser, page-per-request pattern |
| `database/app/main.py` | FastAPI lifespan hooks |
| `database/storage/templates/style.html` | `@font-face` with base64 WOFF2 |
| `database/storage/fonts/` | Font files (new directory) |
| `pd_cards/core/upload.py` | `asyncio.gather` with semaphore |
| `check_cards_and_upload.py` | Same concurrency pattern (legacy script) |

---

## 13.1 Phase 1 — Foundation (Weeks 1-3)

Focus: Database schema, lazy evaluation engine, tier badge display, basic Discord commands.

**Deliverables:**
- `player_season_stats` materialized aggregate table created (reference Major Domo implementation)
- Incremental update logic in post-game callback for `player_season_stats`
- Backfill script to populate `player_season_stats` from existing `stratplay`/`decision` data
- All evolution database tables created with migrations
- `evolution_card_state` initialization on card acquisition (pack opening hook): create if no `(player_id, team_id)` state exists, otherwise inherit existing state
- API endpoints: track catalog, card state GET, evaluate POST
- Post-game callback integration: evaluate participating cards after each game
- Milestone evaluation engine for all stratplay/decision/stratgame milestone types
- Bot command: `!evo status` (roster-wide view and per-card view)
- Basic Discord embed: tier badge on card name
- No rating boosts yet — tier advances are tracked but boosts deferred to Phase 2
- Three universal evolution tracks seeded in database (Batter, Starting Pitcher, Relief Pitcher)
- Backfill job: initialize `evolution_card_state` for all unique `(player_id, team_id)` pairs with `progress_since = earliest card.created_at`

**Success Criteria:** All cards have evolution state. Players can see tier progress. Milestones
complete automatically after games.

## 13.2 Phase 2 — Rating Boosts (Weeks 4-6)

Focus: Apply rating boosts on tier completion, variant card API.

**Deliverables:**
- `apply_evolution_boosts()` function in card-creation repo
- Batter and pitcher player profile detection from existing ratings
- Tier boost application on tier completion: writes new variant rows to battingcardratings/pitchingcardratings
- `evolution_tier_boost` audit table populated
- Updated card API: `card_id` param for variant resolution
- Discord notification on milestone and tier completion (with boost summary)
- T4 rarity upgrade (universal, capped at HoF)
- Evolved card display name (`[Team]'s Evolved [Player]`)

**Success Criteria:** Tier completion triggers rating boost. Evolved cards show higher ratings
in card embed.

## 13.3 Phase 3 — Static Cosmetics (Weeks 7-10)

Focus: Premium static cosmetics, visual differentiation, economy tuning.

**Deliverables:**
- Cosmetics catalog seeded in database (static cosmetics only: frames, themes, badges)
- Cosmetics purchase and display endpoints
- Cosmetic CSS injection in card HTML template (frame overlays, background colors, badges)
- Variant render pipeline: Playwright renders cards with cosmetic CSS, uploads to S3
- `battingcard.image_url` column populated for variant rows
- Bot image resolution updated: variant → `battingcard.image_url` fallback to `player.image`
- Verify all three tracks work correctly across all rarity levels (Rep through HoF)
- Discord embed accent colors per tier (T2 gold, T3 purple, T4 teal)
- Analytics query for admin: evolution completion rates, milestone drop-off, cosmetic revenue
- Economy review: check cosmetics pricing against actual currency circulation data

**Success Criteria:** Static cosmetics purchasable and visually distinct. Full T0-T4 evolution
journey works end to end including cosmetics.

## 13.4 Phase 4 — Animated Cosmetics (Weeks 11-13)

Focus: APNG render pipeline, animated cosmetic effects.

**Prerequisite:** Phase 0 (persistent browser) must be complete. The APNG pipeline depends on
the persistent browser for efficient multi-frame capture.

**Deliverables:**
- Deterministic multi-frame capture using `--anim-progress` CSS custom property
- `capture_animated_frames()` function using persistent browser page pool
- APNG assembly from frame buffers using `apng` Python library (`pip install apng`)
- CSS custom property-driven animations in card HTML template (no `@keyframes` for animated cosmetics)
- Animated cosmetics added to catalog: Holographic Frame, Rarity Glow (Subtle/Strong), T4 Badge Sparkle
- S3 upload with `.apng` extension and `image/apng` content type and `CacheControl: public, max-age=31536000`
- File size validation (target <4 MB per animated card)
- Bot display verified: APNG auto-plays in Discord embeds (desktop + mobile)
- Performance benchmarking: target ~4.5s for 8-frame, ~6s for 12-frame animated variant
- pngquant integration for frame compression if file sizes exceed budget

**Success Criteria:** Animated cosmetics render correctly as APNG, auto-play in Discord embeds,
file sizes within budget, render time <8 seconds per animated variant.

---

## 13.5 Render Pipeline Optimization Checklist (Phase 0)

- [ ] Download Source Sans 3 (400, 700) and Open Sans (300, 400, 700) WOFF2 files
- [ ] Base64-encode font files and create `@font-face` declarations
- [ ] Replace `@import url('fonts.googleapis.com/...')` in `style.html` with local `@font-face` — PR #96 (next-release)
- [ ] Verify card renders identically with self-hosted fonts (visual diff)
- [x] Create `get_browser()` / `shutdown_browser()` functions in players.py — merged 2026.3.17
- [x] Add FastAPI lifespan hooks for browser startup/shutdown — merged 2026.3.17
- [x] Replace `async with async_playwright()` block with persistent browser + page-per-request — merged 2026.3.17
- [x] Add `is_connected()` check with automatic browser reconnect — implemented via `asyncio.Lock`, merged 2026.3.17
- [ ] Test: render 10 cards sequentially, verify no browser leaks (page count stays at 0 between renders)
- [ ] Test: concurrent renders (4 simultaneous requests) complete without errors
- [ ] Benchmark: measure per-card render time (target: <1.0s, down from ~3.0s) — script in PR #95 (next-release)
- [ ] Refactor `pd_cards/core/upload.py` loop to use `asyncio.Semaphore(8)` + `asyncio.gather`
- [ ] Add error handling: individual card failures don't abort the batch
- [ ] Add progress reporting: log every N completions (not every 20 starts)
- [ ] Increase `fetch_card_image` timeout from 6s to 10s
- [ ] Benchmark: full 800-card upload run (target: <5 minutes)
- [ ] Update `check_cards_and_upload.py` legacy script with same concurrency pattern
- [ ] Deploy to dev, run full cardset upload, verify all cards render correctly
- [x] Deploy to production — persistent browser deployed 2026.3.17

## 13.6 Database Checklist

- [ ] Write Peewee model for `evolution_track` — pending PR #84 (card-evolution)
- [ ] Write Peewee model for `evolution_milestone` — pending PR #84 (card-evolution)
- [ ] Write Peewee model for `evolution_card_state` — pending PR #84 (card-evolution)
- [ ] Write Peewee model for `evolution_progress`
- [ ] Write Peewee model for `evolution_tier_boost` — stub schema in PR #84
- [ ] Write Peewee model for `evolution_cosmetic` — stub schema in PR #84
- [ ] Write migration scripts for all new tables (idempotent) — pending PR #84 (card-evolution)
- [x] Write Peewee model for `player_season_stats` — split into `BattingSeasonStats` + `PitchingSeasonStats` in `app/models/season_stats.py`, merged 2026.3.17
- [ ] Implement incremental update in post-game callback (delta-based, not full recompute)
- [ ] Add `last_game_id` check to prevent double-counting on callback retries
- [ ] Write backfill script to populate from existing `stratplay`/`decision` rows
- [ ] Verify backfill totals match on-demand `GROUP BY` queries for a sample of players
- [ ] Reference Major Domo implementation for patterns and edge cases
- [x] Seed three universal evolution tracks (Batter, SP, RP) — seed data + JSON in `app/seed/evolution_tracks.py`, merged 2026.3.17; runnable once migration (PR #84) lands
- [ ] Add `image_url` nullable column to `battingcard` and `pitchingcard` — in PR #84 migration
- [ ] Add `variant` column to `card` table (integer, default 0) — in PR #84 migration
- [ ] Verify `battingcard.variant` UNIQUE constraint behavior with hash-based integers
- [ ] Index all FK columns and query-hot columns on new tables — indexes in PR #84 migration
- [ ] Ensure `player_season_stats` has appropriate indexes for `(team_id, player_id)` lookups — indexes in PR #84 migration
- [ ] Test schema in dev environment before production migration
- [ ] Write and test backfill script: create `evolution_card_state` for each unique `(player_id, team_id)` pair, propagate variant to all matching card instances

## 13.7 Card Creation System Checklist

- [ ] Implement `apply_evolution_boosts(card_ratings_df, boost_tier, player_profile)` in `creation_helpers.py` or `batters/calcs_batter.py`
- [ ] Implement batter player profile detection (power, contact, patient) from existing ratings
- [ ] Implement pitcher player profile detection (SP power, SP control, RP) from existing ratings
- [ ] Write unit tests for boost distribution: verify column sums remain valid post-boost
- [ ] Write unit tests for rating cap enforcement: verify boosts truncate at caps (e.g., hold at -5)
- [ ] Write unit tests for cap truncation: verify excess budget is discarded (not redistributed) when a stat hits its cap
- [ ] Write unit test for rarity upgrade eligibility check (HoF ceiling)
- [ ] Integrate `apply_evolution_boosts()` with API endpoint trigger (or standalone script callable by API service)
- [ ] Implement `compute_variant_hash()` function for deterministic variant numbering
- [ ] Handle variant field correctly: check for existing variant row before creating new one
- [ ] Implement cosmetic CSS injection in card HTML template (frames, backgrounds, badges)
- [ ] Implement CSS `--anim-progress` custom property animations for animated cosmetics (holographic, glow pulse, sparkle)
- [ ] Implement multi-frame Playwright capture for animated variants
- [ ] Implement APNG assembly from frame buffers
- [ ] Add file size validation for animated output
- [ ] Add CLI command: `pd-cards evolution apply-boost --card-id <id> --tier <n>` for admin use
- [ ] Add CLI command: `pd-cards evolution render-variant --player-id <id> --variant <hash>` for manual re-renders

## 13.8 API Development Checklist

- [x] Implement track catalog endpoints (GET list, GET single) — `GET /api/v2/evolution/tracks` and `/tracks/{id}` in `app/routers_v2/evolution.py`, merged 2026.3.17; returns 500 until PR #84 (EvolutionTrack model) lands
- [ ] Implement track milestones endpoint (GET milestones for track)
- [x] Implement season stats CRUD — full REST endpoints in `app/routers_v2/season_stats.py`, merged 2026.3.17
- [x] Formula engine — `app/services/formula_engine.py`, batter OPS + pitcher ERA/WHIP/K formulas, merged 2026.3.17
- [ ] Implement card state endpoints (GET by team, GET by card)
- [ ] Implement POST evaluate endpoint — in PR #98 (card-evolution); `POST /api/v2/evolution/cards/{card_id}/evaluate` with evaluator service
- [ ] Implement cosmetics endpoints (GET catalog, GET card cosmetics, POST purchase, DELETE deactivate)
- [ ] Modify battingcard/pitchingcard endpoints to accept optional `card_id` param
- [ ] Implement variant resolution logic in card endpoints using `evolution_card_state`
- [ ] Implement admin endpoints for track/milestone management and manual boost application
- [ ] Implement idempotent milestone evaluator (recalculate from source, not increment)
- [ ] Render endpoint: detect animated vs static cosmetics, route to correct pipeline
- [ ] Write API integration tests for full T0-T4 evolution lifecycle
- [ ] Write API integration tests for cosmetics purchase flow (static + animated)
- [ ] Document all new endpoints in API reference

## 13.9 Bot Integration Checklist

- [ ] Add `!evo status` command with roster-wide formatted embed
- [ ] Add `!evo card <card_id>` command for detailed single-card milestone breakdown
- [ ] Add `!evo cosmetics <card_id>` command for premium cosmetics browsing
- [ ] Integrate post-game evolution evaluator in post-game processing pipeline
- [ ] Verify evaluator fires after game resolution in all modes (campaign, gauntlet, exhibition, unlimited)
- [ ] Add milestone completion notification to team channel
- [ ] Add tier completion notification with rating boost summary
- [ ] Add Full Evolution notification with rarity upgrade announcement
- [ ] Update card display embed to show tier badge from `evolution_card_state.current_tier`
- [ ] Update card display embed to show evolved name for fully evolved cards
- [ ] Update card display embed to apply active cosmetic styling
- [ ] Verify APNG auto-plays correctly in Discord embeds across desktop and mobile
- [ ] Update trade processing: set traded card's variant to 0, check for existing state on new team's `(player_id, team_id)`, create fresh state if none exists

## 13.10 Testing Checklist

- [ ] Unit tests: `apply_evolution_boosts()` for all player profiles and tiers
- [ ] Unit tests: milestone evaluation logic for all challenge types
- [ ] Unit tests: variant resolution in card endpoint
- [ ] Unit tests: idempotent evaluator (calling twice yields same result)
- [ ] Unit tests: APNG assembly produces valid APNG output
- [ ] Integration tests: game -> post-game callback -> milestone completion -> tier boost full flow
- [ ] Integration tests: T4 rarity upgrade across all rarity levels (Rep→Res, Sta→All, MVP→HoF, HoF stays HoF)
- [ ] Integration tests: evolved card state and name update correctly after trade
- [ ] Integration tests: cosmetics remain on selling team's `(player_id, team_id)` state after trade
- [ ] Integration tests: animated cosmetic purchase triggers APNG render pipeline
- [ ] Load test: milestone query latency against player_season_stats with large dataset
- [ ] Regression test: base card ratings unaffected by evolution variant writes
- [ ] Regression test: scouting report generation ignores variant rows
- [ ] Visual test: animated APNG renders correctly in Discord (desktop + mobile)
- [ ] Visual test: APNG graceful degradation (first frame displays if client doesn't support animation)

## 13.11 Security and Balance Checklist

- [ ] Verify cosmetic purchase currency deducted atomically (wallet check + deduction in single transaction)
- [ ] Verify card ownership validated before cosmetic purchase (card.team_id == requesting team_id)
- [ ] Verify cosmetics are non-transferable on trade (team_id check in display layer)
- [ ] Review milestone thresholds against expected games-per-week before launch
- [ ] Verify rarity cap enforcement (no evolution beyond HoF)
- [ ] Verify individual stat column caps enforced during boost application (e.g., hold ≥ -5)
- [ ] Verify evolved card stats cannot exceed designed boost budget (4.0 chances/108 total) via any combination of track + boost

## 13.12 Deployment Checklist

- [ ] Run schema migration in dev, verify all tables and indexes created correctly — blocked on PR #84
- [ ] Seed evolution tracks and milestones in dev — seed script ready, blocked on schema
- [ ] Run backfill job on dev copy of card inventory, verify all cards get state records
- [ ] Run full Phase 1 test suite in dev environment
- [ ] Deploy API changes to dev, smoke test all new endpoints — season stats + formula engine deployed to prod 2026.3.17; track catalog deployed but non-functional (missing model)
- [ ] Deploy bot changes to dev instance, run a game to verify post-game evaluator fires
- [ ] Demo full T0 -> T1 progression flow in dev
- [ ] Cal approves dev demo
- [ ] Run schema migration in production during low-traffic window
- [ ] Run backfill job in production for existing card inventory
- [ ] Deploy API changes to production
- [ ] Deploy bot changes to production
- [ ] Monitor milestone query latency for first 48 hours
- [ ] Monitor tier completion rate and cosmetic purchase rate after first week
- [ ] Schedule economy review at Day 30
