import numpy as np import pytest from engine.game.enums import Phase from engine.game.game_state import GameState, LiveCard, MemberCard from engine.models.ability import Effect, EffectType class TestPerformanceLogs: @pytest.fixture(autouse=True) def setup(self): self.game = GameState(verbose=True) self.p0 = self.game.players[0] # Pink:0, Red:1, Yellow:2, Green:3, Blue:4, Purple:5, Any:6 self.game.member_db[1] = MemberCard( card_id=1, card_no="M1", name="Member 1", cost=1, hearts=np.array([1, 0, 0, 0, 0, 0, 0]), blade_hearts=np.array([0, 0, 0, 0, 0, 0, 0]), blades=1, abilities=[], img_path="m1.png", ) # Red Member self.game.member_db[2] = MemberCard( card_id=2, card_no="M2", name="Member 2", cost=1, hearts=np.array([0, 1, 0, 0, 0, 0, 0]), blade_hearts=np.array([0, 0, 0, 0, 0, 0, 0]), blades=1, abilities=[], img_path="m2.png", ) self.game.live_db[100] = LiveCard( card_id=100, card_no="L1", name="Live 1", score=1, required_hearts=np.array([1, 1, 0, 0, 0, 0, 0]), abilities=[], img_path="l1.png", ) def test_performance_requirement_reduction_log(self): """Verify that heart requirement reductions are logged in performance results.""" self.p0.stage[0] = 1 # Member 1 (Pink) # We need 1 Pink and 1 Red. We have 1 Pink. # Add a continuous effect that reduces Red requirement by 1. self.p0.continuous_effects.append({ "source_name": "Reduction Effect", "effect": Effect( effect_type=EffectType.REDUCE_HEART_REQ, value=1, params={"color": 2} # Red is 2 in 1-6 mapping ) }) self.p0.live_zone = [100] self.game.phase = Phase.PERFORMANCE_P1 self.game.current_player = 0 self.game._do_performance(0) res = self.game.performance_results[0] assert res["success"] is True # Check breakdown assert "requirements" in res["breakdown"] req_logs = res["breakdown"]["requirements"] assert len(req_logs) > 0 # One of them should be our reduction reduction_found = False for log in req_logs: if log["source"] == "Reduction Effect": reduction_found = True assert log["type"] == "req_mod" # Value should be negative vector: Pink:0, Red:-1 assert log["value"][1] == -1 assert reduction_found def test_performance_transform_color_log(self): """Verify that color transforms are logged in performance results.""" self.p0.stage[0] = 1 # Member 1 (Pink) # We need 1 Red. We have 1 Pink. self.game.live_db[101] = LiveCard( card_id=101, card_no="L2", name="Live 2", score=1, required_hearts=np.array([0, 1, 0, 0, 0, 0, 0]), abilities=[], img_path="l2.png", ) self.p0.live_zone = [101] # Add a transform effect: Pink (1) -> Red (2) self.p0.continuous_effects.append({ "source_name": "Transform Effect", "effect": Effect( effect_type=EffectType.TRANSFORM_COLOR, value=0, params={"from_color": 1, "to_color": 2} ) }) self.game.phase = Phase.PERFORMANCE_P1 self.game.current_player = 0 self.game._do_performance(0) res = self.game.performance_results[0] assert res["success"] is True # Check breakdown assert "transforms" in res["breakdown"] trans_logs = res["breakdown"]["transforms"] assert len(trans_logs) > 0 # Check for transform log transform_found = False for log in trans_logs: if log["source"] == "Transform Effect": transform_found = True assert log["type"] == "transform" assert "Transform 1 Yells to 2" in log["desc"] assert transform_found # Also check heart_breakdown (it should still have it for legacy UI too) heart_found = False for log in res["breakdown"]["hearts"]: if log.get("source") == "Transform Effect" and log.get("type") == "transform": heart_found = True assert heart_found def test_performance_score_modifier_log(self): """Verify that ability score modifiers are logged in performance results.""" self.p0.stage[0] = 1 # Member 1 (Pink) self.p0.live_zone = [100] # Add a score modifier effect from engine.models.ability import Effect, EffectType self.p0.continuous_effects.append({ "source_name": "Score Booster", "source_id": 999, "effect": Effect( effect_type=EffectType.MODIFY_SCORE_RULE, value=5, params={} ) }) self.game.phase = Phase.PERFORMANCE_P1 self.game.current_player = 0 self.game._do_performance(0) # This populates results # _do_performance calls _advance_performance -> _do_live_result # But _do_performance itself doesn't call _do_live_result if it returns to process triggers. # However, PhaseMixin calls _do_live_result at the end of the performance cycle. # In this simple test, we need to ensure _do_live_result is called to populate the modifiers. self.game._do_live_result() # performance_results is cleared at the end of _do_live_result, but copied to last_performance_results assert 0 in self.game.last_performance_results res = self.game.last_performance_results[0] assert "score_modifiers" in res["breakdown"] mods = res["breakdown"]["score_modifiers"] assert len(mods) > 0 found_mod = False for m in mods: if m["source"] == "Score Booster": found_mod = True assert m["value"] == 5 assert m["source_id"] == 999 assert found_mod