import os import sys # Add project root to path # CWD should be loveca-copy sys.path.insert(0, os.getcwd()) print(f"DEBUG: sys.path[0] = {sys.path[0]}") print(f"DEBUG: CWD = {os.getcwd()}") import numpy as np from engine.game.game_state import GameState from engine.models.ability import Ability, AbilityCostType, Cost, Effect, EffectType, TriggerType from engine.models.card import MemberCard def test_pr_027_self_salvage(): """ Verify that a card with 'Cost: Send self to waiting room' and 'Effect: Add member from waiting room to hand' can target itself. """ # 1. Setup Game game = GameState() p1 = game.players[0] # 2. Mock Card # Ability: [Startup] Cost: Self to WR -> Effect: Salvage 1 Member # We construct the ability manually to match the expected parser output for: # "{{kidou.png|起動}}このメンバーをステージから控え室に置く:自分の控え室からメンバーカードを1枚手札に加える。" ability = Ability( raw_text="Activate: Self to WR -> Salvage 1", trigger=TriggerType.ACTIVATED, costs=[Cost(type=AbilityCostType.SACRIFICE_SELF, value=1)], effects=[Effect(effect_type=EffectType.ADD_TO_HAND, value=1, params={"from": "discard", "filter": "member"})], ) card = MemberCard( card_id=9999, card_no="PL!S-PR-027-PR", name="Matsuura Kanan", cost=1, hearts=np.zeros(7, dtype=np.int32), blade_hearts=np.zeros(7, dtype=np.int32), blades=1, abilities=[ability], ) # Register card game.member_db[9999] = card # 3. Setup State # Place card on stage p1.stage[0] = 9999 p1.hand = [] # Empty hand p1.discard = [] # Empty discard to ensure we only pick self p1.main_deck = [101, 102] # Non-empty deck to prevent auto-refresh from engine.game.enums import Phase game.phase = Phase.MAIN # 4. Action: Activate Ability # Action 200 = Activate Area 0 game._execute_action(200) # Cost should be paid: Card moves Stage -> Discard assert p1.stage[0] == -1, "Card should be removed from stage" assert len(p1.discard) == 1, "Card should be in discard" assert p1.discard[0] == 9999, "Card 9999 should be in discard" # Choice should be pending: Select card from discard assert len(game.pending_choices) > 0, "Should have pending choice for effect" choice_type, params = game.pending_choices[0] # Updated expectation: SELECT_FROM_DISCARD assert choice_type == "SELECT_FROM_DISCARD", f"Expected SELECT_FROM_DISCARD, got {choice_type}" assert params["filter"] == "member", "Should filter for members" # 5. Execute Choice: Select the card itself (which is in discard now) # SELECT_FROM_DISCARD choice handler uses actions 660-719. # params['cards'] should be [9999] in this case. # So we choose index 0 (Action 660). game._execute_action(660) # 6. Verify Result assert len(p1.hand) == 1, "Hand should have 1 card" assert p1.hand[0] == 9999, "Should have drawn self" assert len(p1.discard) == 0, "Discard should be empty" if __name__ == "__main__": test_pr_027_self_salvage()