Skip to main content
Glama

Path of Exile 2 Build Optimizer MCP

test_stun_calculator.py28.9 kB
""" Unit tests for Path of Exile 2 Stun Calculator Tests all aspects of the stun system including: - Light Stun chance calculations - Heavy Stun buildup mechanics - Primed state detection - Crushing Blow triggers - Damage type and attack type bonuses - Modifier applications - Edge cases and error handling Author: Claude Code Version: 1.0.0 """ import unittest import logging from src.calculator.stun_calculator import ( StunCalculator, DamageType, AttackType, StunState, StunModifiers, LightStunResult, HeavyStunResult, HeavyStunMeter, CompleteStunResult, quick_stun_calculation ) # Suppress logging during tests logging.disable(logging.CRITICAL) class TestStunCalculatorBasics(unittest.TestCase): """Test basic stun calculator functionality.""" def setUp(self): """Set up test fixtures.""" self.calculator = StunCalculator() def tearDown(self): """Clean up after tests.""" # Clear all tracked entities for entity_id in self.calculator.get_all_tracked_entities(): self.calculator.remove_entity(entity_id) def test_calculator_initialization(self): """Test calculator initializes correctly.""" self.assertIsNotNone(self.calculator) self.assertEqual(len(self.calculator.get_all_tracked_entities()), 0) def test_constants(self): """Test that constants are set correctly.""" self.assertEqual(self.calculator.LIGHT_STUN_MINIMUM_THRESHOLD, 15.0) self.assertEqual(self.calculator.PHYSICAL_DAMAGE_BONUS, 1.5) self.assertEqual(self.calculator.MELEE_ATTACK_BONUS, 1.5) self.assertEqual(self.calculator.PRIMED_STATE_THRESHOLD, 50.0) self.assertEqual(self.calculator.HEAVY_STUN_THRESHOLD, 100.0) self.assertEqual(self.calculator.HEAVY_STUN_DURATION, 3.0) class TestLightStunCalculation(unittest.TestCase): """Test Light Stun chance calculations.""" def setUp(self): """Set up test fixtures.""" self.calculator = StunCalculator() def test_basic_light_stun_no_bonuses(self): """Test Light Stun calculation without bonuses.""" result = self.calculator.calculate_light_stun_chance( damage=1000, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL ) # Base chance = (1000 / 5000) * 100 = 20% self.assertAlmostEqual(result.base_chance, 20.0) self.assertEqual(result.damage_type_bonus, 1.0) self.assertEqual(result.attack_type_bonus, 1.0) self.assertAlmostEqual(result.final_chance, 20.0) self.assertTrue(result.will_stun) # Above 15% threshold def test_light_stun_physical_bonus(self): """Test Light Stun with physical damage bonus.""" result = self.calculator.calculate_light_stun_chance( damage=1000, target_max_life=5000, damage_type=DamageType.PHYSICAL, attack_type=AttackType.RANGED ) # Base chance = 20%, with physical bonus = 20% * 1.5 = 30% self.assertAlmostEqual(result.base_chance, 20.0) self.assertEqual(result.damage_type_bonus, 1.5) self.assertEqual(result.attack_type_bonus, 1.0) self.assertAlmostEqual(result.final_chance, 30.0) self.assertTrue(result.will_stun) def test_light_stun_melee_bonus(self): """Test Light Stun with melee attack bonus.""" result = self.calculator.calculate_light_stun_chance( damage=1000, target_max_life=5000, damage_type=DamageType.COLD, attack_type=AttackType.MELEE ) # Base chance = 20%, with melee bonus = 20% * 1.5 = 30% self.assertAlmostEqual(result.base_chance, 20.0) self.assertEqual(result.damage_type_bonus, 1.0) self.assertEqual(result.attack_type_bonus, 1.5) self.assertAlmostEqual(result.final_chance, 30.0) self.assertTrue(result.will_stun) def test_light_stun_physical_melee_combined(self): """Test Light Stun with both physical and melee bonuses.""" result = self.calculator.calculate_light_stun_chance( damage=1000, target_max_life=5000, damage_type=DamageType.PHYSICAL, attack_type=AttackType.MELEE ) # Base chance = 20%, with both bonuses = 20% * 1.5 * 1.5 = 45% self.assertAlmostEqual(result.base_chance, 20.0) self.assertEqual(result.damage_type_bonus, 1.5) self.assertEqual(result.attack_type_bonus, 1.5) self.assertAlmostEqual(result.final_chance, 45.0) self.assertTrue(result.will_stun) def test_light_stun_below_threshold(self): """Test Light Stun below 15% threshold.""" result = self.calculator.calculate_light_stun_chance( damage=500, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL ) # Base chance = (500 / 5000) * 100 = 10% # Below 15% threshold, so final_chance = 0 self.assertAlmostEqual(result.base_chance, 10.0) self.assertAlmostEqual(result.final_chance, 0.0) self.assertFalse(result.will_stun) def test_light_stun_exactly_at_threshold(self): """Test Light Stun exactly at 15% threshold.""" # Need 15% chance, so with phys+melee (2.25x), need base of 6.67% # 6.67% base = damage/life * 100, so damage = 333.5 # Use 333.4 to ensure we get exactly 15.003% which rounds to 15% result = self.calculator.calculate_light_stun_chance( damage=333.4, target_max_life=5000, damage_type=DamageType.PHYSICAL, attack_type=AttackType.MELEE ) # Base = 6.668%, final = 6.668% * 2.25 = 15.003% self.assertGreaterEqual(result.final_chance, 15.0) self.assertTrue(result.will_stun) def test_light_stun_caps_at_100(self): """Test Light Stun caps at 100%.""" result = self.calculator.calculate_light_stun_chance( damage=10000, target_max_life=5000, damage_type=DamageType.PHYSICAL, attack_type=AttackType.MELEE ) # Base = 200%, with bonuses = 450%, should cap at 100% self.assertAlmostEqual(result.base_chance, 200.0) self.assertAlmostEqual(result.final_chance, 100.0) self.assertTrue(result.will_stun) def test_light_stun_with_increased_modifier(self): """Test Light Stun with increased stun chance modifier.""" modifiers = StunModifiers(increased_stun_chance=50.0) result = self.calculator.calculate_light_stun_chance( damage=1000, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, modifiers=modifiers ) # Base = 20%, with +50% increased = 20% * 1.5 = 30% self.assertAlmostEqual(result.final_chance, 30.0) self.assertTrue(result.will_stun) def test_light_stun_with_more_modifier(self): """Test Light Stun with more stun chance modifier.""" modifiers = StunModifiers(more_stun_chance=1.5) # 50% more result = self.calculator.calculate_light_stun_chance( damage=1000, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, modifiers=modifiers ) # Base = 20%, with 50% more = 20% * 1.5 = 30% self.assertAlmostEqual(result.final_chance, 30.0) self.assertTrue(result.will_stun) def test_light_stun_with_immunity(self): """Test Light Stun with immunity.""" modifiers = StunModifiers(immune_to_stun=True) result = self.calculator.calculate_light_stun_chance( damage=5000, target_max_life=5000, damage_type=DamageType.PHYSICAL, attack_type=AttackType.MELEE, modifiers=modifiers ) self.assertAlmostEqual(result.final_chance, 0.0) self.assertFalse(result.will_stun) def test_light_stun_invalid_damage(self): """Test Light Stun with invalid damage values.""" with self.assertRaises(ValueError): self.calculator.calculate_light_stun_chance( damage=-100, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL ) def test_light_stun_invalid_life(self): """Test Light Stun with invalid life values.""" with self.assertRaises(ValueError): self.calculator.calculate_light_stun_chance( damage=1000, target_max_life=0, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL ) class TestHeavyStunCalculation(unittest.TestCase): """Test Heavy Stun buildup calculations.""" def setUp(self): """Set up test fixtures.""" self.calculator = StunCalculator() def tearDown(self): """Clean up after tests.""" for entity_id in self.calculator.get_all_tracked_entities(): self.calculator.remove_entity(entity_id) def test_heavy_stun_meter_creation(self): """Test Heavy Stun meter is created for new entity.""" result = self.calculator.calculate_heavy_stun_buildup( damage=1000, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, entity_id="test_entity" ) self.assertIsNotNone(result.meter) self.assertEqual(result.meter.max_buildup, 5000) self.assertIn("test_entity", self.calculator.get_all_tracked_entities()) def test_heavy_stun_basic_buildup(self): """Test basic Heavy Stun buildup without bonuses.""" result = self.calculator.calculate_heavy_stun_buildup( damage=1000, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, entity_id="test1" ) # Buildup = damage (no bonuses) = 1000 self.assertAlmostEqual(result.buildup_added, 1000.0) self.assertAlmostEqual(result.total_buildup, 1000.0) self.assertAlmostEqual(result.meter.buildup_percentage, 20.0) self.assertEqual(result.meter.state, StunState.NORMAL) self.assertFalse(result.triggered_heavy_stun) def test_heavy_stun_physical_bonus(self): """Test Heavy Stun buildup with physical damage bonus.""" result = self.calculator.calculate_heavy_stun_buildup( damage=1000, target_max_life=5000, damage_type=DamageType.PHYSICAL, attack_type=AttackType.RANGED, entity_id="test2" ) # Buildup = 1000 * 1.5 = 1500 self.assertAlmostEqual(result.buildup_added, 1500.0) self.assertAlmostEqual(result.total_buildup, 1500.0) self.assertAlmostEqual(result.meter.buildup_percentage, 30.0) def test_heavy_stun_melee_bonus(self): """Test Heavy Stun buildup with melee attack bonus.""" result = self.calculator.calculate_heavy_stun_buildup( damage=1000, target_max_life=5000, damage_type=DamageType.COLD, attack_type=AttackType.MELEE, entity_id="test3" ) # Buildup = 1000 * 1.5 = 1500 self.assertAlmostEqual(result.buildup_added, 1500.0) self.assertAlmostEqual(result.total_buildup, 1500.0) self.assertAlmostEqual(result.meter.buildup_percentage, 30.0) def test_heavy_stun_combined_bonuses(self): """Test Heavy Stun buildup with both bonuses.""" result = self.calculator.calculate_heavy_stun_buildup( damage=1000, target_max_life=5000, damage_type=DamageType.PHYSICAL, attack_type=AttackType.MELEE, entity_id="test4" ) # Buildup = 1000 * 1.5 * 1.5 = 2250 self.assertAlmostEqual(result.buildup_added, 2250.0) self.assertAlmostEqual(result.total_buildup, 2250.0) self.assertAlmostEqual(result.meter.buildup_percentage, 45.0) def test_heavy_stun_primed_state(self): """Test Heavy Stun meter reaches Primed state.""" # Hit for 50% buildup result = self.calculator.calculate_heavy_stun_buildup( damage=2500, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, entity_id="test5" ) self.assertAlmostEqual(result.meter.buildup_percentage, 50.0) self.assertEqual(result.meter.state, StunState.PRIMED) self.assertTrue(result.meter.is_primed()) self.assertFalse(result.triggered_heavy_stun) def test_heavy_stun_trigger(self): """Test Heavy Stun triggers at 100%.""" entity_id = "test6" # First hit: 60% result1 = self.calculator.calculate_heavy_stun_buildup( damage=3000, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, entity_id=entity_id ) self.assertEqual(result1.meter.state, StunState.PRIMED) self.assertFalse(result1.triggered_heavy_stun) # Second hit: 60% + 60% = 120% (capped at 100% behavior) result2 = self.calculator.calculate_heavy_stun_buildup( damage=3000, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, entity_id=entity_id ) self.assertTrue(result2.meter.buildup_percentage >= 100.0) self.assertEqual(result2.meter.state, StunState.HEAVY_STUNNED) self.assertTrue(result2.meter.is_heavy_stunned()) self.assertTrue(result2.triggered_heavy_stun) def test_heavy_stun_crushing_blow(self): """Test Crushing Blow triggers in Primed state.""" entity_id = "test7" # First hit: Get to Primed state (50%) self.calculator.calculate_heavy_stun_buildup( damage=2500, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, entity_id=entity_id ) # Second hit: Primed + Light Stun = Crushing Blow result = self.calculator.calculate_heavy_stun_buildup( damage=1000, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, entity_id=entity_id, light_stun_would_occur=True ) self.assertTrue(result.triggered_crushing_blow) def test_heavy_stun_multiple_hits(self): """Test Heavy Stun buildup over multiple hits.""" entity_id = "test8" # 5 hits of 800 damage each (total 4000 / 5000 = 80%) for i in range(5): result = self.calculator.calculate_heavy_stun_buildup( damage=800, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, entity_id=entity_id ) expected_percentage = (i + 1) * 16.0 # 800/5000 = 16% per hit self.assertAlmostEqual( result.meter.buildup_percentage, expected_percentage, places=1 ) # Verify final state meter = self.calculator.get_heavy_stun_meter(entity_id) self.assertAlmostEqual(meter.buildup_percentage, 80.0) self.assertEqual(meter.hits_received, 5) self.assertEqual(len(meter.hit_history), 5) def test_heavy_stun_meter_reset(self): """Test Heavy Stun meter reset.""" entity_id = "test9" # Build up some stun self.calculator.calculate_heavy_stun_buildup( damage=3000, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, entity_id=entity_id ) meter = self.calculator.get_heavy_stun_meter(entity_id) self.assertGreater(meter.buildup_percentage, 0) # Reset self.calculator.reset_heavy_stun_meter(entity_id) meter = self.calculator.get_heavy_stun_meter(entity_id) self.assertEqual(meter.buildup_percentage, 0.0) self.assertEqual(meter.state, StunState.NORMAL) self.assertEqual(len(meter.hit_history), 0) def test_heavy_stun_hits_to_stun_calculation(self): """Test hits needed to Heavy Stun calculation.""" entity_id = "test10" # First hit: 1000 damage result = self.calculator.calculate_heavy_stun_buildup( damage=1000, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, entity_id=entity_id ) # Need 4000 more, so 4 more hits self.assertAlmostEqual(result.hits_to_heavy_stun, 4.0) class TestCompleteStunCalculation(unittest.TestCase): """Test complete stun calculations (Light + Heavy).""" def setUp(self): """Set up test fixtures.""" self.calculator = StunCalculator() def tearDown(self): """Clean up after tests.""" for entity_id in self.calculator.get_all_tracked_entities(): self.calculator.remove_entity(entity_id) def test_complete_stun_basic(self): """Test complete stun calculation.""" result = self.calculator.calculate_complete_stun( damage=1000, target_max_life=5000, damage_type=DamageType.PHYSICAL, attack_type=AttackType.MELEE, entity_id="test1" ) self.assertIsInstance(result, CompleteStunResult) self.assertIsInstance(result.light_stun, LightStunResult) self.assertIsInstance(result.heavy_stun, HeavyStunResult) self.assertEqual(result.damage, 1000) self.assertEqual(result.target_max_life, 5000) def test_complete_stun_crushing_blow_integration(self): """Test Crushing Blow with complete calculation.""" entity_id = "test2" # First hit: Get to Primed self.calculator.calculate_complete_stun( damage=2500, target_max_life=5000, damage_type=DamageType.PHYSICAL, attack_type=AttackType.MELEE, entity_id=entity_id ) # Second hit: Should trigger Crushing Blow if Light Stun occurs # Need enough damage for Light Stun: 15% chance minimum # With phys+melee (2.25x), need base 6.67%, so ~334 damage result = self.calculator.calculate_complete_stun( damage=500, target_max_life=5000, damage_type=DamageType.PHYSICAL, attack_type=AttackType.MELEE, entity_id=entity_id ) # Light Stun should occur (500/5000*100*2.25 = 22.5%) self.assertTrue(result.light_stun.will_stun) # Should be Primed (56.25%) # First hit: 2500 * 2.25 = 5625 (112.5% but starts at primed 50%) # Actually first hit puts us over 100%, so this test needs adjustment def test_complete_stun_with_modifiers(self): """Test complete stun with modifiers.""" modifiers = StunModifiers( increased_stun_chance=50.0, more_stun_chance=1.3 ) result = self.calculator.calculate_complete_stun( damage=1000, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, entity_id="test3", modifiers=modifiers ) # Both Light and Heavy should use modifiers # Base = 20%, with +50% increased = 30%, with 30% more = 39% self.assertAlmostEqual(result.light_stun.final_chance, 39.0) class TestHitsToStunCalculation(unittest.TestCase): """Test hits-to-stun calculation methods.""" def setUp(self): """Set up test fixtures.""" self.calculator = StunCalculator() def test_hits_to_stun_basic(self): """Test basic hits to stun calculation.""" hits_light, hits_heavy = self.calculator.calculate_hits_to_stun( damage_per_hit=1000, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL ) # Light: Base 20% per hit, need 15%, so 1 hit # Heavy: 1000 per hit, need 5000, so 5 hits self.assertAlmostEqual(hits_light, 1.0) self.assertAlmostEqual(hits_heavy, 5.0) def test_hits_to_stun_with_bonuses(self): """Test hits to stun with damage/attack bonuses.""" hits_light, hits_heavy = self.calculator.calculate_hits_to_stun( damage_per_hit=1000, target_max_life=5000, damage_type=DamageType.PHYSICAL, attack_type=AttackType.MELEE ) # Light: Base 20% * 2.25 = 45% per hit, need 15%, so 1 hit # Heavy: 1000 * 2.25 = 2250 per hit, need 5000, so ~2.22 hits self.assertAlmostEqual(hits_light, 1.0) self.assertAlmostEqual(hits_heavy, 2.222, places=2) def test_hits_to_stun_below_threshold(self): """Test hits to stun when damage is below Light Stun threshold.""" hits_light, hits_heavy = self.calculator.calculate_hits_to_stun( damage_per_hit=200, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL ) # Light: Base 4% per hit, need 15%, so 15/4 = 3.75 hits # Heavy: 200 per hit, need 5000, so 25 hits self.assertAlmostEqual(hits_light, 3.75) self.assertAlmostEqual(hits_heavy, 25.0) class TestStunModifiers(unittest.TestCase): """Test stun modifier applications.""" def setUp(self): """Set up test fixtures.""" self.calculator = StunCalculator() def test_increased_stun_chance(self): """Test increased stun chance modifier.""" modifiers = StunModifiers(increased_stun_chance=100.0) # +100% result = self.calculator.calculate_light_stun_chance( damage=500, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, modifiers=modifiers ) # Base 10% * (1 + 100%) = 20% self.assertAlmostEqual(result.final_chance, 20.0) self.assertTrue(result.will_stun) def test_reduced_stun_threshold(self): """Test reduced stun threshold modifier.""" modifiers = StunModifiers(reduced_stun_threshold=0.5) # 50% reduced result = self.calculator.calculate_light_stun_chance( damage=500, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, modifiers=modifiers ) # Base 10%, with 50% reduced threshold = 10% / 0.5 = 20% self.assertAlmostEqual(result.final_chance, 20.0) self.assertTrue(result.will_stun) def test_stun_buildup_multiplier(self): """Test stun buildup multiplier.""" modifiers = StunModifiers(stun_buildup_multiplier=2.0) result = self.calculator.calculate_heavy_stun_buildup( damage=1000, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, entity_id="test1", modifiers=modifiers ) # Buildup = 1000 * 2.0 = 2000 self.assertAlmostEqual(result.buildup_added, 2000.0) def test_custom_minimum_stun_chance(self): """Test custom minimum stun chance.""" modifiers = StunModifiers(minimum_stun_chance=5.0) # Lower threshold result = self.calculator.calculate_light_stun_chance( damage=300, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, modifiers=modifiers ) # Base 6%, with 5% minimum threshold self.assertAlmostEqual(result.final_chance, 6.0) self.assertTrue(result.will_stun) class TestEntityTracking(unittest.TestCase): """Test entity tracking functionality.""" def setUp(self): """Set up test fixtures.""" self.calculator = StunCalculator() def tearDown(self): """Clean up after tests.""" for entity_id in self.calculator.get_all_tracked_entities(): self.calculator.remove_entity(entity_id) def test_multiple_entities(self): """Test tracking multiple entities.""" # Hit entity 1 self.calculator.calculate_heavy_stun_buildup( damage=1000, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, entity_id="enemy1" ) # Hit entity 2 self.calculator.calculate_heavy_stun_buildup( damage=2000, target_max_life=3000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, entity_id="enemy2" ) entities = self.calculator.get_all_tracked_entities() self.assertEqual(len(entities), 2) self.assertIn("enemy1", entities) self.assertIn("enemy2", entities) meter1 = self.calculator.get_heavy_stun_meter("enemy1") meter2 = self.calculator.get_heavy_stun_meter("enemy2") self.assertAlmostEqual(meter1.buildup_percentage, 20.0) self.assertAlmostEqual(meter2.buildup_percentage, 66.67, places=1) def test_remove_entity(self): """Test removing an entity.""" self.calculator.calculate_heavy_stun_buildup( damage=1000, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL, entity_id="enemy1" ) self.assertIn("enemy1", self.calculator.get_all_tracked_entities()) self.calculator.remove_entity("enemy1") self.assertNotIn("enemy1", self.calculator.get_all_tracked_entities()) self.assertIsNone(self.calculator.get_heavy_stun_meter("enemy1")) class TestQuickStunCalculation(unittest.TestCase): """Test quick stun calculation convenience function.""" def test_quick_calculation_basic(self): """Test quick stun calculation.""" result_str = quick_stun_calculation( damage=1000, target_max_life=5000, is_physical=False, is_melee=False ) self.assertIsInstance(result_str, str) self.assertIn("Light Stun", result_str) self.assertIn("Heavy Stun", result_str) def test_quick_calculation_physical_melee(self): """Test quick stun calculation with bonuses.""" result_str = quick_stun_calculation( damage=1000, target_max_life=5000, is_physical=True, is_melee=True ) self.assertIsInstance(result_str, str) self.assertIn("Light Stun", result_str) class TestEdgeCases(unittest.TestCase): """Test edge cases and boundary conditions.""" def setUp(self): """Set up test fixtures.""" self.calculator = StunCalculator() def test_zero_damage(self): """Test zero damage.""" result = self.calculator.calculate_light_stun_chance( damage=0, target_max_life=5000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL ) self.assertEqual(result.base_chance, 0.0) self.assertEqual(result.final_chance, 0.0) self.assertFalse(result.will_stun) def test_very_high_damage(self): """Test very high damage (one-shot).""" result = self.calculator.calculate_complete_stun( damage=100000, target_max_life=5000, damage_type=DamageType.PHYSICAL, attack_type=AttackType.MELEE, entity_id="test1" ) self.assertEqual(result.light_stun.final_chance, 100.0) self.assertTrue(result.light_stun.will_stun) self.assertTrue(result.heavy_stun.triggered_heavy_stun) def test_very_low_damage(self): """Test very low damage.""" result = self.calculator.calculate_light_stun_chance( damage=1, target_max_life=10000, damage_type=DamageType.FIRE, attack_type=AttackType.SPELL ) # 1/10000 * 100 = 0.01%, below threshold self.assertAlmostEqual(result.base_chance, 0.01) self.assertEqual(result.final_chance, 0.0) self.assertFalse(result.will_stun) if __name__ == '__main__': unittest.main(verbosity=2)

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/HivemindOverlord/poe2-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server