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.
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.
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.
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.
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.
Issues #2, #3, #4, #6 are still open:
- #2: CardDefinition field validation not added
- #3: end_turn() knockout processing not fixed
- #4: Win condition timing not fixed
- #6: Engine doesn't call process_knockout for status KOs
The refactor improved process_knockout() internals but didn't fix the
callers that should invoke it.
Mark issues #1, #5, #7, #8 as FIXED:
- #1: find_card_instance now includes energy_zone + attached cards
- #5: Energy stored as CardInstance objects, not just IDs
- #7: Confusion handling added to attack execution
- #8: discard_energy handler moves cards to owner's discard
Update test count to 766 and add change log section.