Commit Graph

57 Commits

Author SHA1 Message Date
Claude
7885b272a4
Honor RulesConfig for prize cards vs points in frontend game board
The game board now conditionally renders prize card zones based on
the RulesConfig sent from the backend:

- Add rules_config field to VisibleGameState in backend (visibility.py)
- Add rules_config to frontend game types and game store
- Update layout.ts to accept LayoutOptions with usePrizeCards and prizeCount
- Update StateRenderer to conditionally create PrizeZone objects
- Update Board to handle empty prize position arrays gracefully
- Add game store computed properties: rulesConfig, usePrizeCards, prizeCount
- Add tests for conditional prize zone rendering

When use_prize_cards is false (Mantimon TCG points system), the prize
zones are not rendered, saving screen space. When true (classic Pokemon
TCG mode), the correct number of prize slots is rendered based on
the rules config's prize count.

https://claude.ai/code/session_01AAxKmpq2AGde327eX1nzUC
2026-02-02 09:22:44 +00:00
Cal Corum
52c8edcf93 Fix WebSocket game:join to emit game:state event
Backend was returning the state but not emitting the game:state
event that the frontend listens for. Added explicit emit call
to send game:state to the client after successful join.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-01 12:27:01 -06:00
Cal Corum
50bd3f1591 Add config API endpoint for frontend game settings
Expose game configuration (energy types, card types, rule constants) via
/api/config endpoint so frontend can dynamically load game rules without
hardcoding values.
2026-01-31 15:43:41 -06:00
Cal Corum
1123d61067 Extend core card models with rarity tiers and card subtypes
Add CardRarityTier enum for pull rate calculations (common through
crown_rare). Add CardSubtype enum for Pokemon classifications (basic,
stage1, stage2, ex, etc.). Update CardDefinition model with new fields
for subtypes and rarity display.
2026-01-31 15:43:07 -06:00
Cal Corum
ca3aca2b38 Add has_starter_deck to user profile API response
The frontend routing guard checks has_starter_deck to decide whether to
redirect users to starter selection. The field was missing from the API
response, causing authenticated users with a starter deck to be
incorrectly redirected to /starter on page refresh.

- Add has_starter_deck computed property to User model
- Add has_starter_deck field to UserResponse schema
- Add unit tests for User model properties
- Add API tests for has_starter_deck in profile response

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 23:14:04 -06:00
Cal Corum
cd3efcb528 Implement ProfilePage and profanity filter for display names (F1-006)
ProfilePage implementation:
- Full profile page with avatar, editable display name, session count
- LinkedAccountCard and DisplayNameEditor components
- useProfile composable wrapping user store operations
- Support for linking/unlinking OAuth providers
- Logout and logout-all-devices functionality

Profanity service with bypass detection:
- Uses better-profanity library for base detection
- Enhanced to catch common bypass attempts:
  - Number suffixes/prefixes (shit123, 69fuck)
  - Leet-speak substitutions (sh1t, f@ck, $hit)
  - Separator characters (s.h.i.t, f-u-c-k)
- Integrated into PATCH /api/users/me endpoint
- 17 unit tests covering all normalization strategies

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 16:06:42 -06:00
Cal Corum
3cc8d6645e Implement auth composables and starter selection (F1-003, F1-004, F1-005)
Features:
- Add useAuth composable with OAuth flow and token management
- Add useStarter composable with API integration and dev mock fallback
- Implement app auth initialization blocking navigation until ready
- Complete StarterSelectionPage with 5 themed deck options

Bug fixes:
- Fix CORS by adding localhost:3001 to allowed origins
- Fix OAuth URL to include redirect_uri parameter
- Fix emoji rendering in nav components (use actual chars, not escapes)
- Fix requireStarter guard timing by allowing navigation from /starter
- Fix starter "already selected" detection for 400 status code

Documentation:
- Update dev-server skill to use `docker compose` (newer CLI syntax)
- Update .env.example with port 3001 in CORS comment

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 15:36:14 -06:00
Cal Corum
f452e69999 Complete Phase 4 implementation files
- TurnTimeoutService with percentage-based warnings (35 tests)
- ConnectionManager enhancements for spectators and reconnection
- GameService with timer integration, spectator support, handle_timeout
- GameNamespace with spectate/leave_spectate handlers, reconnection
- WebSocket message schemas for spectator events
- WinConditionsConfig additions for turn timer thresholds
- 83 GameService tests, 37 ConnectionManager tests, 37 GameNamespace tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 08:03:43 -06:00
Cal Corum
7fcb86ff51 Implement UserRepository pattern with dependency injection
- Add UserRepository and LinkedAccountRepository protocols to protocols.py
- Add UserEntry and LinkedAccountEntry DTOs for service layer decoupling
- Implement PostgresUserRepository and PostgresLinkedAccountRepository
- Refactor UserService to use constructor-injected repositories
- Add get_user_service factory and UserServiceDep to API deps
- Update auth.py and users.py endpoints to use UserServiceDep
- Rewrite tests to use FastAPI dependency overrides (no monkey patching)

This follows the established repository pattern used by DeckService and
CollectionService, enabling future offline fork support.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 07:30:16 -06:00
Cal Corum
cc0254d5ab Implement REST endpoints for game management (API-001)
Add REST API endpoints for game lifecycle operations:
- POST /games - Create new game between two players
- GET /games/{game_id} - Get game info for reconnection
- GET /games/me/active - List user's active games
- POST /games/{game_id}/resign - Resign from game via HTTP

Includes proper reverse proxy support for WebSocket URL generation
(X-Forwarded-* headers -> settings.base_url -> Host header fallback).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 23:09:12 -06:00
Cal Corum
154d466ff1 Implement /game namespace event handlers (WS-005, WS-006)
Add GameNamespaceHandler with full event handling for real-time gameplay:
- handle_join: Join/rejoin games with visibility-filtered state
- handle_action: Execute actions and broadcast state to participants
- handle_resign: Process resignation and end game
- handle_disconnect: Notify opponent of disconnection
- Broadcast helpers for state, game over, and opponent status

Includes 28 unit tests covering all handler methods.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 20:40:06 -06:00
Cal Corum
531d3e1e79 Implement GameService.end_game with history archival (GS-005)
- Add GameEndResult dataclass with winner, loser, final views, duration
- Add _map_end_reason() to map core GameEndReason to DB EndReason
  (raises ValueError for unknown reasons to catch missing enum sync)
- Enhance end_game() to build replay data and return comprehensive result
- Add archive_to_history() to GameStateManager for complete game archival:
  - Creates GameHistory record with replay data
  - Deletes ActiveGame record
  - Clears Redis cache
  - All in single transaction
- Add ArchiveResult dataclass for archive operation metadata
- Add TODO for session_factory DI refactor in GameStateManager
- Update tests: 5 new end_game tests, 6 new archive_to_history tests

Phase 4 progress: 10/18 tasks complete

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 20:10:27 -06:00
Cal Corum
d5460ff418 Enhance GameService.join_game for reconnection support (GS-004)
Add pending forced action and game_over fields to GameJoinResult:
- pending_forced_action: Included when player must complete a forced
  action (e.g., select new active after KO). Essential for reconnection
  so client knows what action is required.
- game_over: Boolean indicating if game has already ended.
- is_your_turn: Now True when player has pending forced action, even if
  it's technically opponent's turn.

The join_game method now handles both initial joins and reconnections
(resume). The last_event_id parameter is accepted for future event
replay support.

Tests: 4 new tests for forced action handling and game_over flag.
Total 51 tests for GameService.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 19:30:44 -06:00
Cal Corum
0a9cd73daf Validate connection data integrity in ConnectionManager
Fix audit warnings about empty string defaults hiding data corruption:

1. get_connection_info(): Validate required fields (user_id, connected_at,
   last_seen) exist before creating ConnectionInfo. Return None and log
   warning for corrupted records instead of returning invalid data.

2. unregister_connection(): Log warning if user_id is missing during
   cleanup. Existing code safely handles this (skips cleanup), but now
   we have visibility into data corruption.

Test added for corrupted data case.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 19:03:17 -06:00
Cal Corum
55e02ceb21 Replace silent error hiding with explicit failures
Three changes to fail fast instead of silently degrading:

1. GameService.create_game: Raise GameCreationError when energy card
   definition not found instead of logging warning and continuing.
   A deck with missing energy cards is fundamentally broken.

2. CardService.load_all: Collect all card file load failures and raise
   CardServiceLoadError at end with comprehensive error report. Prevents
   startup with partial card data that causes cryptic runtime errors.
   New exceptions: CardLoadError, CardServiceLoadError

3. GameStateManager.recover_active_games: Return RecoveryResult dataclass
   with recovered count, failed game IDs with error messages, and total.
   Enables proper monitoring and alerting for corrupted game state.

Tests added for energy card error case. Existing tests updated for
new RecoveryResult return type.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 18:48:06 -06:00
Cal Corum
cd3cc892f4 Implement GameService.execute_action enhancements (GS-003)
Add forced action handling, turn boundary detection, and DB persistence:
- Check for pending forced actions before allowing regular actions
- Only specified player can act during forced action (except resign)
- Only specified action type allowed during forced action
- Detect turn boundaries (turn number OR current player change)
- Persist to Postgres at turn boundaries for durability
- Include pending_forced_action in GameActionResult for client

New exceptions: ForcedActionRequiredError

Tests: 11 new tests covering forced actions, turn boundaries, and
pending action reporting. Total 47 tests for GameService.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 15:15:34 -06:00
Cal Corum
3c75ee0e00 Implement GameService.create_game (GS-002)
- Add create_game method that loads decks via DeckService, converts
  CardDefinitions to CardInstances, and persists to Redis/Postgres
- Build card registry from only the cards in play (not all cards)
- Add GameCreationError exception and GameCreateResult dataclass
- Add creation_engine_factory for DI-based testing (no monkey patching)
- Add helper methods: _cards_to_instances, _build_card_registry
- Update tests with proper mocks for success, deck failure, engine failure

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 14:09:11 -06:00
Cal Corum
f93f5b617a Remove legacy modifier field from WeaknessResistance
Migrate all usages to the proper mode/value fields:
- Weakness: mode=MULTIPLICATIVE, value=2
- Resistance: mode=ADDITIVE, value=-30

Remove backwards compatibility code and legacy test.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 13:16:51 -06:00
Cal Corum
f512c7b2b3 Refactor to dependency injection pattern - no monkey patching
- ConnectionManager: Add redis_factory constructor parameter
- GameService: Add engine_factory constructor parameter
- AuthHandler: New class replacing standalone functions with
  token_verifier and conn_manager injection
- Update all tests to use constructor DI instead of patch()
- Update CLAUDE.md with factory injection patterns
- Update services README with new patterns
- Add socketio README documenting AuthHandler and events

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 22:54:57 -06:00
Cal Corum
0c810e5b30 Add Phase 4 WebSocket infrastructure (WS-001 through GS-001)
WebSocket Message Schemas (WS-002):
- Add Pydantic models for all client/server WebSocket messages
- Implement discriminated unions for message type parsing
- Include JoinGame, Action, Resign, Heartbeat client messages
- Include GameState, ActionResult, Error, TurnStart server messages

Connection Manager (WS-003):
- Add Redis-backed WebSocket connection tracking
- Implement user-to-sid mapping with TTL management
- Support game room association and opponent lookup
- Add heartbeat tracking for connection health

Socket.IO Authentication (WS-004):
- Add JWT-based authentication middleware
- Support token extraction from multiple formats
- Implement session setup with ConnectionManager integration
- Add require_auth helper for event handlers

Socket.IO Server Setup (WS-001):
- Configure AsyncServer with ASGI mode
- Register /game namespace with event handlers
- Integrate with FastAPI via ASGIApp wrapper
- Configure CORS from application settings

Game Service (GS-001):
- Add stateless GameService for game lifecycle orchestration
- Create engine per-operation using rules from GameState
- Implement action-based RNG seeding for deterministic replay
- Add rng_seed field to GameState for replay support

Architecture verified:
- Core module independence (no forbidden imports)
- Config from request pattern (rules in GameState)
- Dependency injection (constructor deps, method config)
- All 1090 tests passing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 22:21:20 -06:00
Cal Corum
9e14ab906f Add services layer README documentation
Documents:
- Architecture principles (stateless, DI, repository protocol)
- Services overview table
- Key patterns (config from request, UNSET sentinel, repository injection)
- Service details with usage examples
- Testing approach with examples

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 15:40:56 -06:00
Cal Corum
7d397a2e22 Fix medium priority issues from code review
UNSET sentinel pattern:
- Add UNSET sentinel in protocols.py for nullable field updates
- Fix inability to clear deck description (UNSET=keep, None=clear)
- Fix repository inability to clear validation_errors

Starter deck improvements:
- Remove unused has_starter_deck from CollectionService
- Add deprecation notes to old starter deck methods

Validation improvements:
- Add energy type validation in deck_validator.py
- Add energy type validation in deck schemas
- Add VALID_ENERGY_TYPES constant

Game loading fix:
- Fix get_deck_for_game silently skipping invalid cards
- Now raises ValueError with clear error message

Tests:
- Add TestEnergyTypeValidation test class
- Add TestGetDeckForGame test class
- Add tests for validate_energy_types utility function

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 14:32:08 -06:00
Cal Corum
3ec670753b Fix security and validation issues from code review
Critical fixes:
- Add admin API key authentication for admin endpoints
- Add race condition protection via unique partial index for starter decks
- Make starter deck selection atomic with combined method

Moderate fixes:
- Fix DI pattern violation in validate_deck_endpoint
- Add card ID format validation (regex pattern)
- Add card quantity validation (1-99 range)
- Fix exception chaining with from None (B904)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 14:16:07 -06:00
Cal Corum
58349c126a Phase 3: Collections + Decks - Services and DI architecture
Implemented with Repository Protocol pattern for offline fork support:
- CollectionService with PostgresCollectionRepository
- DeckService with PostgresDeckRepository
- DeckValidator with DeckConfig + CardService injection
- Starter deck definitions (5 types: grass, fire, water, psychic, lightning)
- Pydantic schemas for collection and deck APIs
- Unit tests for DeckValidator (32 tests passing)

Architecture follows pure dependency injection - no service locator patterns.
Added CLAUDE.md documenting DI requirements and patterns.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 11:27:14 -06:00
Cal Corum
f82bc8aa1f Fix OAuth callback redirect and deps import
- Discord OAuth callback now redirects to frontend with tokens in URL fragment
  (more secure than query params - fragment not sent to server)
- Error responses also redirect with error in query params
- Fix deps.py import: get_session_dependency -> get_session

Part of Phase 2 Authentication completion.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 00:17:27 -06:00
Cal Corum
5cf2198542 Add engine validation script with attack_coin_status effect handler
- Add attack_coin_status effect handler for coin-flip status conditions
  (e.g., Thunder Shock paralysis on heads)
- Create comprehensive engine_validation.py script (~1250 lines) that
  validates game engine behavior with 29 test cases:
  - Illegal moves (attack without energy, wrong turn, evolution rules)
  - Energy mechanics (attachment limits, cost validation)
  - Weakness calculation (+20 additive mode)
  - Status conditions (paralysis blocks actions, poison damage)
  - Knockout flow (points, forced actions, state cleanup)
  - Win conditions (4 points triggers game over)
- Update game_walkthrough.py Thunder Shock to use new effect handler
- Interactive prompts between sections (Enter to continue, q to quit)
- Uses seed=42 for deterministic, reproducible coin flips

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 00:15:12 -06:00
Cal Corum
3ad79a4860 Fix OAuth absolute URLs and add account linking endpoints
- Add base_url config setting for OAuth callback URLs
- Change OAuth callbacks from relative to absolute URLs
- Add account linking OAuth flow (GET /auth/link/{provider})
- Add unlink endpoint (DELETE /users/me/link/{provider})
- Add AccountLinkingError and service methods for linking
- Add 14 new tests for linking functionality
- Update Phase 2 plan to mark complete (1072 tests passing)
2026-01-27 22:06:22 -06:00
Cal Corum
996c43fbd9 Implement Phase 2: Authentication system
Complete OAuth-based authentication with JWT session management:

Core Services:
- JWT service for access/refresh token creation and verification
- Token store with Redis-backed refresh token revocation
- User service for CRUD operations and OAuth-based creation
- Google and Discord OAuth services with full flow support

API Endpoints:
- GET /api/auth/{google,discord} - Start OAuth flows
- GET /api/auth/{google,discord}/callback - Handle OAuth callbacks
- POST /api/auth/refresh - Exchange refresh token for new access token
- POST /api/auth/logout - Revoke single refresh token
- POST /api/auth/logout-all - Revoke all user sessions
- GET/PATCH /api/users/me - User profile management
- GET /api/users/me/linked-accounts - List OAuth providers
- GET /api/users/me/sessions - Count active sessions

Infrastructure:
- Pydantic schemas for auth/user request/response models
- FastAPI dependencies (get_current_user, get_current_premium_user)
- OAuthLinkedAccount model for multi-provider support
- Alembic migration for oauth_linked_accounts table

Dependencies added: email-validator, fakeredis (dev), respx (dev)

84 new tests, 1058 total passing
2026-01-27 21:49:59 -06:00
Cal Corum
2a95316f04 Add FastAPI lifespan hooks and fix Phase 1 gaps
- Add lifespan context manager to app/main.py with startup/shutdown hooks
- Wire startup: init_db(), init_redis(), CardService.load_all()
- Wire shutdown: close_db(), close_redis()
- Add /health/ready endpoint for readiness checks
- Add CORS middleware with configurable origins
- Disable docs in production (only available in dev)

- Export get_session_dependency from app/db/__init__.py for FastAPI DI
- Add game_cache_ttl_seconds to Settings (configurable, was hardcoded)
- Fix datetime.utcnow() deprecation (4 occurrences) -> datetime.now(UTC)
- Update test to match S3 image URL (was placeholder CDN)

All 974 tests passing.
2026-01-27 15:37:19 -06:00
Cal Corum
934aa4c443 Add CardService and card data conversion pipeline
- Rename data/cards/ to data/raw/ for scraped data
- Add data/definitions/ as authoritative card data source
- Add convert_cards.py script to transform raw -> definitions
- Generate 378 card definitions (344 Pokemon, 24 Trainers, 10 Energy)
- Add CardService for loading and querying card definitions
  - In-memory indexes for fast lookups by type, set, pokemon_type
  - search() with multiple filter criteria
  - get_all_cards() for GameEngine integration
- Add SetInfo model for set metadata
- Update Attack model with damage_display field for variable damage
- Update CardDefinition with image_path, illustrator, flavor_text
- Add 45 tests (21 converter + 24 CardService)
- Update scraper output path to data/raw/

Card data is JSON-authoritative (no database) to support offline fork goal.
2026-01-27 14:16:40 -06:00
Cal Corum
29ab0b3d84 Add GameStateManager service with Redis/Postgres dual storage
- Implement GameStateManager with Redis-primary, Postgres-backup pattern
- Cache operations: save_to_cache, load_from_cache, delete_from_cache
- DB operations: persist_to_db, load_from_db, delete_from_db
- High-level: load_state (cache-first), delete_game, recover_active_games
- Query helpers: get_active_game_count, get_player_active_games
- Add 22 tests for GameStateManager (87% coverage)
- Add 6 __repr__ tests for all DB models (100% model coverage)
2026-01-27 10:59:58 -06:00
Cal Corum
50684a1b11 Add database infrastructure with SQLAlchemy models and test suite
Phase 1 Database Implementation (DB-001 through DB-012):

Models:
- User: OAuth support (Google/Discord), premium subscriptions
- Collection: Card ownership with CardSource enum
- Deck: JSONB cards/energy_cards, validation state
- CampaignProgress: One-to-one with User, medals/NPCs as JSONB
- ActiveGame: In-progress games with GameType enum
- GameHistory: Completed games with EndReason enum, replay data

Infrastructure:
- Alembic migrations with sync psycopg2 (avoids async issues)
- Docker Compose for Postgres (5433) and Redis (6380)
- App config with Pydantic settings
- Redis client helper

Test Infrastructure:
- 68 database tests (47 model + 21 relationship)
- Async factory pattern for test data creation
- Sync TRUNCATE cleanup (solves pytest-asyncio event loop mismatch)
- Uses dev containers instead of testcontainers for reliability

Key technical decisions:
- passive_deletes=True for ON DELETE SET NULL relationships
- NullPool for test sessions (no connection reuse)
- expire_on_commit=False with manual expire() for relationship tests
2026-01-27 10:17:30 -06:00
Cal Corum
3ed67ea16b Add Active Effects system design and module README files
Design Documentation:
- docs/ACTIVE_EFFECTS_DESIGN.md: Comprehensive design for persistent effect system
  - Data model (ActiveEffect, EffectTrigger, EffectScope, StackingMode)
  - Core operations (register, remove, query effects)
  - Integration points (damage calc, energy counting, retreat, lifecycle)
  - Effect categories from Pokemon Pocket card research (~372 cards)
  - Example implementations (Serperior, Greninja, Mr. Mime, Victreebel)
  - Post-launch TODO for generic modifier system

Module README Files:
- backend/app/core/README.md: Core engine overview and key classes
- backend/app/core/effects/README.md: Effects module index and quick reference
- backend/app/core/models/README.md: Models module with relationship diagram

Minor cleanup:
- Revert Bulbasaur weakness to Fire (was test change for Lightning)
- Clean up debug output in game walkthrough
2026-01-26 22:39:02 -06:00
Cal Corum
72bd1102df Add weakness/resistance support to attack damage calculation
- Add DamageCalculationResult model for transparent damage breakdown
- Implement _calculate_attack_damage with W/R modifiers (additive/multiplicative)
- Add _execute_attack_effect for future effect system integration
- Add _build_attack_message for detailed damage breakdown in messages
- Update _execute_attack to use new calculation pipeline
- Bulbasaur now properly weak to Lightning in walkthrough demo

New features:
- Weakness applies bonus damage (additive +X or multiplicative xN)
- Resistance reduces damage (minimum 0)
- State changes include weakness/resistance details for UI
- Messages show damage breakdown (e.g. 'base 10 +20 weakness')

Tests: 7 new tests covering additive/multiplicative W/R, type matching,
minimum damage floor, knockout triggers, and state change details
2026-01-26 16:04:41 -06:00
Cal Corum
2252931fb8 Add core module AGENTS.md documentation (DOCS-001)
Creates comprehensive documentation for AI agents working with app/core/:
- Architecture overview with component diagram
- Import patterns (correct enum imports from app.core.enums)
- Core patterns: game creation, action execution, effects, config
- Key classes: GameState, PlayerState, CardDefinition, CardInstance, Zone
- Testing patterns with SeededRandom for determinism
- Security rules for hidden information
- Module independence guidelines for offline fork support
- File organization and quick commands

Updates PROJECT_PLAN.json: 31/32 tasks complete.
2026-01-26 14:50:52 -06:00
Cal Corum
e7431e2d1f Move enums to app/core/enums.py and set up clean module exports
Architectural refactor to eliminate circular imports and establish clean
module boundaries:

- Move enums from app/core/models/enums.py to app/core/enums.py
  (foundational module with zero dependencies)
- Update all imports across 30 files to use new enum location
- Set up clean export structure:
  - app.core.enums: canonical source for all enums
  - app.core: convenience exports for full public API
  - app.core.models: exports models only (not enums)
- Add module exports to app/core/__init__.py and app/core/effects/__init__.py
- Remove circular import workarounds from game_state.py

This enables app.core.models to export GameState without circular import
issues, since enums no longer depend on the models package.

All 826 tests passing.
2026-01-26 14:45:26 -06:00
Cal Corum
5f1eb11344 Add test coverage for validation and confusion, document effect knockout handling
Issue #2 gap: Added 14 CardDefinition validation tests covering all
required field checks (hp, stage, pokemon_type, evolves_from, trainer_type,
energy_type) with both negative and positive test cases.

Issue #7 gap: Added 4 confusion attack engine tests covering heads/tails
outcomes, self-damage, self-KO with opponent scoring, and configurable
damage from RulesConfig.

Issue #13 documentation: Added TODO comments in engine.py and handlers.py
documenting the expected pattern for knockout detection when effect
execution is implemented. Effect handlers set knockout flags; engine
should process knockouts after all effects resolve.

825 tests passing (+17 new tests)
2026-01-26 14:16:15 -06:00
Cal Corum
939ae421aa Add exception logging to effect registry (Issue #14)
Effect handler exceptions now logged at ERROR level with full context:
- effect_id, source_player_id, source/target card IDs, params
- Full traceback via logger.exception()

Game still returns safe EffectResult.failure() to prevent crashes,
but debugging information is now preserved in logs.
2026-01-26 13:32:43 -06:00
Cal Corum
1fbd3d1cfa Add knockout detection to damage effect handlers (Issue #13)
Both deal_damage and attack_damage now check if the target is knocked out
after applying damage. If KO'd, EffectResult includes:
- details['knockout'] = True
- details['knockout_pokemon_id'] = target's instance_id
- Message includes 'knocked out!' notification

Knockout check correctly respects HP modifiers via effective_hp().

Added 9 tests covering knockout detection, HP modifier behavior,
weakness-triggered knockouts, and resistance preventing knockouts.
2026-01-26 11:44:38 -06:00
Cal Corum
554178dc6e Track stadium ownership for correct discard (Issue #12)
Added stadium_owner_id field to GameState to track who played the stadium:

- stadium_owner_id: str | None tracks the player who played the current stadium
- When a stadium is replaced, old stadium discards to OWNER's pile (not current player)
- Added stadium_owner_id to VisibleGameState for client visibility
- Updated existing test and added 2 new tests for stadium ownership

This fixes the bug where replacing an opponent's stadium would discard
to the current player's pile instead of the opponent's.

797 tests passing.
2026-01-26 11:38:38 -06:00
Cal Corum
9432499018 Add forced action queue for double knockouts (Issue #10)
Changed forced_action from single item to FIFO queue to support
scenarios where multiple forced actions are needed simultaneously:

- forced_actions: list[ForcedAction] replaces forced_action: ForcedAction | None
- Added queue management methods:
  - has_forced_action() - check if queue has pending actions
  - get_current_forced_action() - get first action without removing
  - add_forced_action(action) - add to end of queue
  - pop_forced_action() - remove and return first action
  - clear_forced_actions() - clear all pending actions
- Updated engine, turn_manager, rules_validator, and visibility filter
- Added 8 new tests for forced action queue including double knockout scenario

This fixes the bug where simultaneous knockouts (e.g., mutual poison damage)
would lose one player's select_active action due to overwriting.

795 tests passing.
2026-01-26 11:33:47 -06:00
Cal Corum
8e084d250a Fix per-ability usage tracking (Issue #9)
Changed ability_uses_this_turn from int to dict[int, int] to track each
ability's usage independently:

- ability_uses_this_turn: dict[int, int] maps ability index to use count
- can_use_ability() now requires ability_index parameter
- Added get_ability_uses() and increment_ability_uses() helper methods
- reset_turn_state() clears the dict instead of setting to 0

This fixes the bug where using one ability could incorrectly block
another ability on the same Pokemon from being used.

789 tests passing.
2026-01-26 11:11:23 -06:00
Cal Corum
0534c57430 Add SelectPrizeAction executor and turn limit check (Issues #11, #15)
Issue #11 - SelectPrizeAction:
- Add _execute_select_prize() method to GameEngine
- Add prize card mode support to process_knockout()
- Add _award_prize_cards() helper for random/player-choice selection
- Support multi-prize selection (EX/VMAX worth 2-3 prizes)

Issue #15 - Turn Limit Check:
- Add turn limit check at start of start_turn()
- Add GameEndReason.TURN_LIMIT enum value (distinct from TIMEOUT)
- Game ends when turn limit exceeded, winner determined by score
- Equal scores result in DRAW

Added 12 new tests for prize card mode and turn limit functionality.
788 tests passing.
2026-01-26 11:04:03 -06:00
Cal Corum
7fae1c61e8 Add CardDefinition validation for required fields (Issue #2)
- Add model_validator to enforce card-type-specific required fields
- Pokemon: require hp (positive), stage, pokemon_type
- Pokemon Stage 1/2 and VMAX/VSTAR: require evolves_from
- Trainer: require trainer_type
- Energy: require energy_type (auto-fills energy_provides)
- Update all test fixtures to include required fields
- Mark Issue #2 as FIXED in SYSTEM_REVIEW.md

765 tests passing
2026-01-26 10:28:37 -06:00
Cal Corum
2b8fac405f Implement energy/tools as CardInstance + evolution stack + devolve effect
Major refactor to properly track attached cards and evolution history:

Model Changes (app/core/models/card.py):
- Change attached_energy from list[str] to list[CardInstance]
- Change attached_tools from list[str] to list[CardInstance]
- Add cards_underneath field for evolution stack tracking
- Update attach_energy/detach_energy to work with CardInstance
- Add attach_tool/detach_tool methods
- Add get_all_attached_cards helper

Engine Changes (app/core/engine.py):
- _execute_attach_energy: Pass full CardInstance to attach_energy
- _execute_evolve: Build evolution stack, transfer attachments, clear status
- _execute_retreat: Detached energy goes to discard pile
- Fix: Evolution now clears status conditions (Pokemon TCG standard)

Game State (app/core/models/game_state.py):
- find_card_instance now searches attached_energy, attached_tools, cards_underneath

Turn Manager (app/core/turn_manager.py):
- process_knockout: Discard all attached energy, tools, and evolution stack

Effects (app/core/effects/handlers.py):
- discard_energy: Find owner's discard pile and move detached energy there
- NEW devolve effect: Remove evolution stages with configurable destination
- Fix: Use EffectType.SPECIAL instead of non-existent EffectType.ZONE

Rules Validator (app/core/rules_validator.py):
- Update energy type checking to iterate CardInstance objects

Tests:
- Update existing tests for new CardInstance-based energy attachment
- NEW test_evolution_stack.py with 28 comprehensive tests covering:
  - Evolution stack building (Basic -> Stage 1 -> Stage 2)
  - Energy/tool transfer and damage carryover on evolution
  - Devolve effect (single/multi stage, hand/discard destination, KO check)
  - Knockout processing with all attachments going to discard
  - find_card_instance for attached cards and evolution stack

All 765 tests pass.
2026-01-25 23:09:40 -06:00
Cal Corum
fe2e1091f9 Add comprehensive engine tests and fix action field name bugs
Bug fixes in engine.py:
- PlayPokemonAction: card_id -> card_instance_id
- PlayTrainerAction: card_id -> card_instance_id
- UseAbilityAction: pokemon_card_id -> pokemon_id
- SelectActiveAction: card_id -> pokemon_id
- record_ability_use() -> ability_uses_this_turn += 1

Added 26 new tests covering:
- Energy deck setup (Pokemon Pocket style)
- Prize card mode
- Deck size validation (too large)
- PlayPokemonAction (to active, to bench, not found)
- EvolvePokemonAction (success, not in hand, target not found)
- PlayTrainerAction (item, supporter, stadium, replacement)
- UseAbilityAction (success, not found, invalid index)
- SelectActiveAction (forced action, not on bench)
- Deck-out on turn start
- Attack edge cases (no energy, invalid index)
- Retreat without bench
- Attach energy from energy zone

Test count: 711 -> 737 (+26)
Coverage: 89% -> 94% overall, engine.py 55% -> 81%
2026-01-25 13:34:42 -06:00
Cal Corum
3f830b25b7 Add GameEngine orchestrator with full game lifecycle support
Implements the main public API for the core game engine:
- create_game(): deck validation, shuffling, dealing hands
- execute_action(): validates and executes all 11 action types
- start_turn()/end_turn(): turn management integration
- get_visible_state(): hidden info filtering for clients
- handle_timeout(): timeout handling for turn limits

Integrates turn_manager, rules_validator, win_conditions, and
visibility filter into a cohesive orchestration layer.

22 integration tests covering game creation, action execution,
visibility filtering, and error handling.

711 tests passing (29/32 tasks complete)
2026-01-25 13:21:41 -06:00
Cal Corum
cbc1da3c03 Add visibility filter for client-safe game state views
SECURITY: Implement hidden information filtering to prevent cheating.

- Create VisibleGameState, VisiblePlayerState, VisibleZone models
- get_visible_state(game, player_id): filtered view for a player
- get_spectator_state(game): filtered view for spectators

Hidden Information (NEVER exposed):
  - Opponent's hand contents (count only)
  - All deck contents and order
  - All prize card contents
  - Energy deck order

Public Information (always visible):
  - Active and benched Pokemon (full details)
  - Discard piles (full contents)
  - Energy zone (available energy)
  - Scores, turn info, phase
  - Stadium in play

- 44 security-critical tests verifying no information leakage
- Tests check JSON serialization for hidden card ID leaks
- Also adds test for configurable burn damage

Completes HIGH-008 and TEST-012 from PROJECT_PLAN.json
Updates security checklist: 4/5 items now verified
2026-01-25 13:11:06 -06:00
Cal Corum
eef857e972 Add turn manager with phase state machine and between-turn effects
- Implement TurnManager class for turn/phase state machine
- Phase transitions: SETUP -> DRAW -> MAIN -> ATTACK -> END
- Turn start: reset counters, draw card, flip energy (Pokemon Pocket style)
- Turn end: apply status damage (poison/burn), check recovery (sleep/burn flip)
- Between-turn paralysis auto-removal
- Knockout processing with scoring and forced action setup
- Integration with win condition checking (deck-out, no Pokemon, turn limit)
- 60 tests covering all functionality
- 644 total core tests passing at 97% coverage

Completes HIGH-007 and TEST-011 from PROJECT_PLAN.json
Week 4 (Game Logic) now complete - ready for Week 5 (Engine & Polish)
2026-01-25 13:02:56 -06:00
Cal Corum
5e99566560 Add rules validator, win conditions checker, and coverage gap tests
- Implement rules_validator.py with config-driven action validation for all 11 action types
- Implement win_conditions.py with point/prize-based, knockout, deck-out, turn limit, and timeout checks
- Add ForcedAction model to GameState for blocking actions (e.g., select new active after KO)
- Add ActiveConfig with max_active setting for future double-battle support
- Add TrainerConfig.stadium_same_name_replace option
- Add DeckConfig.starting_hand_size option
- Rename from_energy_deck to from_energy_zone for consistency
- Fix unreachable code bug in GameState.get_opponent_id()
- Add 16 coverage gap tests for edge cases (card registry corruption, forced actions, etc.)
- 584 tests passing at 97% coverage

Completes HIGH-005, HIGH-006, TEST-009, TEST-010 from PROJECT_PLAN.json
2026-01-25 12:57:06 -06:00