# Project Plan: Energy, Tools, and Evolution Stack Refactor

**Created:** 2026-01-25  
**Updated:** 2026-01-25  
**Status:** COMPLETE (All phases done, 765 tests passing)  
**Priority:** Critical (Phase 1 fixes from SYSTEM_REVIEW.md)

---

## Overview

Refactor the card attachment and evolution system to properly track:
1. **Energy cards** as `CardInstance` objects attached to Pokemon (not just IDs)
2. **Tool cards** as `CardInstance` objects attached to Pokemon (not just IDs)
3. **Evolution stack** - previous evolution stages stored underneath the current Pokemon

This fixes critical bugs where energy cards "disappeared" and enables proper devolve mechanics.

---

## Key Design Decisions

| Aspect | Decision |
|--------|----------|
| `attached_energy` type | `list[CardInstance]` (was `list[str]`) |
| `attached_tools` type | `list[CardInstance]` (was `list[str]`) |
| Evolution behavior | Previous stages stored in `cards_underneath` (was discarded) |
| Energy on KO | Moves to owner's discard pile |
| Tools on KO | Moves to owner's discard pile |
| Evolution stack on KO | All cards in stack move to owner's discard |
| Devolve mechanic | Effect only (not player action) |
| Devolve stages | Configurable per effect (default 1) |
| Devolve destination | Configurable: "hand" (default) or "discard" |
| Devolve KO check | Immediate - if damage >= new HP, Pokemon is KO'd |
| Energy/tools on devolve | Stay attached to the devolved Pokemon |
| Damage on evolve/devolve | Carries over (enables sneaky KO strategies) |

---

## Implementation Phases

### Phase 1: Update CardInstance Model
**File:** `app/core/models/card.py`  
**Status:** [x] COMPLETE

- [x] Change `attached_energy: list[str]` to `list["CardInstance"]`
- [x] Change `attached_tools: list[str]` to `list["CardInstance"]`
- [x] Add `cards_underneath: list["CardInstance"]` field
- [x] Update `attach_energy()` method signature to accept `CardInstance`
- [x] Update `detach_energy()` to return `CardInstance | None`
- [x] Add `attach_tool()` method
- [x] Add `detach_tool()` method returning `CardInstance | None`
- [x] Update docstrings for all affected fields/methods
- [x] Add `CardInstance.model_rebuild()` for self-referential types

---

### Phase 2: Revert Incorrect Fix
**File:** `app/core/engine.py`  
**Status:** [x] COMPLETE

- [x] Remove `player.discard.add(energy_card)` from `_execute_attach_energy`

---

### Phase 3: Update Energy Attachment
**File:** `app/core/engine.py`  
**Status:** [x] COMPLETE

- [x] Change `target.attach_energy(energy_card.instance_id)` to `target.attach_energy(energy_card)`

---

### Phase 4: Update Evolution Execution
**File:** `app/core/engine.py`  
**Status:** [x] COMPLETE

- [x] Transfer `attached_energy` list (not copy IDs)
- [x] Transfer `attached_tools` list
- [x] Transfer `damage` and `status_conditions`
- [x] Build `cards_underneath` stack (copy existing + append target)
- [x] Clear target's attached lists after transfer
- [x] Remove `player.discard.add(target)` - target stays in stack
- [x] Update `turn_played` tracking on evo_card

---

### Phase 5: Update Retreat Execution
**File:** `app/core/engine.py`  
**Status:** [x] COMPLETE

- [x] Call `detach_energy()` which returns `CardInstance`
- [x] Add returned energy to `player.discard`

---

### Phase 6: Update find_card_instance
**File:** `app/core/models/game_state.py`  
**Status:** [x] COMPLETE

- [x] Search `pokemon.attached_energy` for each Pokemon in play
- [x] Search `pokemon.attached_tools` for each Pokemon in play
- [x] Search `pokemon.cards_underneath` for each Pokemon in play
- [x] Return appropriate zone names: `"attached_energy"`, `"attached_tools"`, `"cards_underneath"`

---

### Phase 7: Update Knockout Processing
**File:** `app/core/turn_manager.py`  
**Status:** [x] COMPLETE

- [x] Add TODO comment for future hook point (pre_knockout_discard event)
- [x] Discard all `attached_energy` to owner's discard
- [x] Discard all `attached_tools` to owner's discard
- [x] Discard all `cards_underneath` to owner's discard
- [x] Clear lists before discarding Pokemon
- [x] Apply to both active and bench knockout sections

---

### Phase 8: Update discard_energy Effect Handler
**File:** `app/core/effects/handlers.py`  
**Status:** [x] COMPLETE

- [x] Find owner of target Pokemon (for discard pile access)
- [x] Update to work with `CardInstance` objects
- [x] Call `detach_energy()` and add result to owner's discard
- [x] Update docstring

---

### Phase 9: Add devolve Effect Handler
**File:** `app/core/effects/handlers.py`  
**Status:** [x] COMPLETE

- [x] Create `@effect_handler("devolve")` decorator
- [x] Accept `stages` param (int, default 1)
- [x] Accept `destination` param ("hand" default, or "discard")
- [x] Validate target is evolved (has cards_underneath)
- [x] Find owner and zone of target Pokemon
- [x] Implement stage removal loop:
  - Pop previous from cards_underneath
  - Transfer all state (energy, tools, remaining stack, damage, status, modifiers)
  - Create CardInstance for removed evolution
  - Send to destination (hand or discard)
  - Swap in zone
- [x] Check for KO after devolve (damage >= new HP)
- [x] Return result with knockout flag if applicable

---

### Phase 10: Update Rules Validator
**File:** `app/core/rules_validator.py`  
**Status:** [x] COMPLETE

- [x] Update `_get_attached_energy_types()` to iterate CardInstance objects
- [x] Update retreat energy validation to check instance_id in CardInstance list

---

### Phase 11: Update Existing Tests
**Status:** [x] COMPLETE

Files updated:
- [x] `tests/core/test_models/test_card.py` - attach/detach signatures, serialization
- [x] `tests/core/test_engine.py` - energy attachment, evolution assertions
- [x] `tests/core/test_effects/test_handlers.py` - discard_energy tests
- [x] `tests/core/test_rules_validator.py` - energy requirement checks
- [x] `tests/core/conftest.py` - game_in_main_phase and game_in_attack_phase fixtures

---

### Phase 12: Add New Tests
**Status:** [x] COMPLETE
**Test File:** `tests/core/test_evolution_stack.py` (28 tests)

Evolution Stack Tests:
- [x] Basic → Stage 1 creates correct stack
- [x] Stage 1 → Stage 2 preserves full stack
- [x] Energy/tools transfer on evolution
- [x] Damage carries over on evolution
- [x] Status conditions clear on evolution (Pokemon TCG standard)

Devolve Effect Tests:
- [x] Devolve Stage 2 → Stage 1 (stages=1)
- [x] Devolve Stage 2 → Basic (stages=2)
- [x] Devolve to hand (default destination)
- [x] Devolve to discard
- [x] Devolve triggers KO when damage >= new HP
- [x] Cannot devolve Basic (failure)
- [x] Energy/tools remain attached after devolve

Knockout Tests:
- [x] Attached energy goes to discard on KO
- [x] Attached tools go to discard on KO
- [x] Evolution stack goes to discard on KO
- [x] All attachments discard together on KO

find_card_instance Tests:
- [x] Find attached energy by ID
- [x] Find attached tool by ID
- [x] Find card in evolution stack
- [x] Find attached card on bench Pokemon
- [x] Returns None for nonexistent card

CardInstance Method Tests:
- [x] attach_energy adds CardInstance
- [x] detach_energy removes and returns CardInstance
- [x] detach_energy returns None for not attached
- [x] attach_tool adds CardInstance
- [x] detach_tool removes and returns CardInstance
- [x] get_all_attached_cards returns energy and tools
- [x] Multiple energy attachment works

---

### Phase 13: Final Verification
**Status:** [x] COMPLETE

- [x] Run full test suite: `uv run pytest` - 765 tests pass (including 28 new Phase 12 tests)
- [x] Run linter: `uv run ruff check .` - Clean (only pre-existing issues in references/)
- [ ] Run type checker: `uv run mypy app` - Not run (pre-existing type issues exist)
- [x] Review all changes

---

## Files Modified

| File | Change Type |
|------|-------------|
| `app/core/models/card.py` | Model fields + methods |
| `app/core/engine.py` | Attach/evolve/retreat execution + status clear fix |
| `app/core/models/game_state.py` | find_card_instance search |
| `app/core/turn_manager.py` | Knockout processing |
| `app/core/effects/handlers.py` | discard_energy update + new devolve + EffectType fix |
| `app/core/rules_validator.py` | Energy type checking |
| `tests/core/test_models/test_card.py` | Test updates |
| `tests/core/test_engine.py` | Test updates |
| `tests/core/test_effects/test_handlers.py` | Test updates + new tests |
| `tests/core/test_rules_validator.py` | Test updates |
| `tests/core/conftest.py` | Fixture updates (energy attachment) |
| `tests/core/test_evolution_stack.py` | NEW: 28 comprehensive tests for Phase 12 |

---

## Related Issues

- SYSTEM_REVIEW.md Issue #5: Energy Attachment Bug - Energy Card Disappears
- SYSTEM_REVIEW.md Issue #8: Energy Discard Handler Doesn't Move Cards

---

## Notes

- Future hook point needed for effects that redirect energy/tools on knockout
- Event system design should be planned holistically for the entire engine
- Devolve is effect-only; no DevolveAction needed
