§
    ÎÐ½jiS  ã                  ó(  — d Z ddlmZ ddlZddlmZ ddlZddlmZm	Z	m
Z
 ddlmZ ddlmZ ddlmZ ej        dd„¦   «         Z G d„ d¦  «        Z G d„ d¦  «        Z G d„ d¦  «        Z G d„ d¦  «        Z G d„ d¦  «        Z G d„ d¦  «        ZdS )zÞ
Unit Test Suite for FreeExile Level Progression Service & Death Penalty Engine.
Verifies LevelProgressionService, Piecewise EXP Curve, Level Gap Decay,
and Tiered Death Penalty Safe Floor adhering to PoE2 2026 Standards.
é    )ÚannotationsN)Ú	Generator)ÚCombatActorÚCombatEngineÚDamageEventResult©ÚLevelProgressionService)ÚDeathPenaltyResult)ÚFiveElementsÚreturnú.Generator[LevelProgressionService, None, None]c               #  ó.   K  — t          ¦   «         } | V — dS )z)Provides a fresh LevelProgressionService.Nr   )Úservices    úBC:\Projects\FreeExile\tests\unit\test_level_progression_service.pyÚprogression_servicer      s    è è € õ &Ñ'Ô'€GØ
€M€M€M€M€Mó    c                  óB   — e Zd ZdZdd„Zdd„Zdd„Zdd	„Zdd
„Zdd„Z	dS )ÚTestLevelGapDecayFormulazJVerifies exponential decay formula: eta(Delta) = exp(-0.60 * (Delta - 5)).r   r	   r   ÚNonec                ó>   — |                      dd¦  «        dk    sJ ‚dS )z;Player level == Monster level yields 100% (1.0) multiplier.éP   ç      ð?N©Úcalculate_level_gap_multiplier©Úselfr   s     r   Ú#test_level_gap_exact_match_full_expz<TestLevelGapDecayFormula.test_level_gap_exact_match_full_exp   s+   € ð #×AÒAÀ"ÀbÑIÔIÈSÒPÐPÐPÐPÐPÐPr   c                ón   — t          dd¦  «        D ]#}|                     dd|z   ¦  «        }|dk    sJ ‚Œ$dS )z8Level differences within [-5, +5] receive 100% full EXP.éûÿÿÿé   r   r   N)Úranger   )r   r   ÚdiffÚmults       r   Ú)test_level_gap_safe_range_within_5_levelszBTestLevelGapDecayFormula.test_level_gap_safe_range_within_5_levels"   sM   € õ ˜"˜a‘L”Lð 	ð 	ˆDØ&×EÒEÀbÈ"ÈtÉ)ÑTÔTˆDØ˜3’;�;�;�;�;ð	ð 	r   c                ó†   — |                      dd¦  «        }t          |t          j        d¦  «        z
  ¦  «        dk     sJ ‚dS )zAGap of 6 levels triggers exponential decay: exp(-0.60) ~= 0.5488.éV   r   g333333ã¿ç-Cëâ6?N©r   ÚabsÚmathÚexp©r   r   r#   s      r   Ú&test_level_gap_decay_boundary_6_levelsz?TestLevelGapDecayFormula.test_level_gap_decay_boundary_6_levels*   sE   € ð #×AÒAÀ"ÀbÑIÔIˆÝ�4�$œ( 5™/œ/Ñ)Ñ*Ô*¨TÒ1Ð1Ð1Ð1Ð1Ð1r   c                ó–   — |                      dd¦  «        }|dk    sJ ‚t          |t          j        d¦  «        z
  ¦  «        dk     sJ ‚dS )zHMonster 10 levels lower yields <= 5% EXP (exp(-3.0) ~= 0.04978 <= 0.05).éZ   r   çš™™™™™©?g      Àr'   Nr(   r,   s      r   Ú7test_level_gap_decay_10_levels_lower_under_five_percentzPTestLevelGapDecayFormula.test_level_gap_decay_10_levels_lower_under_five_percent1   sR   € ð #×AÒAÀ"ÀbÑIÔIˆØ�tŠ|ˆ|ˆ|ˆ|Ý�4�$œ( 4™.œ.Ñ(Ñ)Ô)¨DÒ0Ð0Ð0Ð0Ð0Ð0r   c                óB   — |                      dd¦  «        }|dk    sJ ‚dS )z5Extreme level gap clamps to absolute floor 0.01 (1%).é_   é   g{®Gáz„?Nr   r,   s      r   Ú&test_level_gap_extreme_overlevel_clampz?TestLevelGapDecayFormula.test_level_gap_extreme_overlevel_clamp9   s*   € ð #×AÒAÀ"ÀbÑIÔIˆØ�tŠ|ˆ|ˆ|ˆ|ˆ|ˆ|r   c                óT   — |                      dd¦  «        }d|cxk    rdk    sn J ‚dS )zDUnderleveled player decay prevents power-leveling (clamped to 0.05).r4   r   r0   çš™™™™™¹?Nr   r,   s      r   Ú)test_level_gap_underleveled_anti_boostingzBTestLevelGapDecayFormula.test_level_gap_underleveled_anti_boosting@   sD   € ð #×AÒAÀ"ÀbÑIÔIˆØ�tÐ#Ð#Ò#Ð#˜tÒ#Ð#Ð#Ð#Ð#Ð#Ð#Ð#r   N©r   r	   r   r   )
Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   r$   r-   r1   r5   r8   © r   r   r   r      s–   € € € € € ØTÐTðQð Qð Qð Qðð ð ð ð2ð 2ð 2ð 2ð1ð 1ð 1ð 1ðð ð ð ð$ð $ð $ð $ð $ð $r   r   c                  ó‚  — e Zd ZdZej                             dg d¢¦  «        dd	„¦   «         Zej                             dg d
¢¦  «        dd„¦   «         Zej                             dg d¢¦  «        dd„¦   «         Z	ej                             dg d¢¦  «        dd„¦   «         Z
dd„Zdd„Zdd„Zdd„Zdd„Zdd„Zdd„ZdS )ÚTestTieredDeathPenaltyzNVerifies tiered percentage penalties and absolute safe floor (never de-level).Úlevel)é   r4   é-   é<   r   r	   Úintr   r   c                ó<   — |                      |¦  «        dk    sJ ‚dS )z4Levels 1 to 60 have 0% death penalty (grace period).ç        N©Úget_death_penalty_ratio©r   r   rA   s      r   Ú-test_death_penalty_grace_period_level_1_to_60zDTestTieredDeathPenalty.test_death_penalty_grace_period_level_1_to_60K   s)   € ð
 #×:Ò:¸5ÑAÔAÀSÒHÐHÐHÐHÐHÐHr   )é=   éF   r   c                ó<   — |                      |¦  «        dk    sJ ‚dS )z1Levels 61 to 80 lose 5% of current level EXP bar.r0   NrH   rJ   s      r   Ú$test_death_penalty_mid_tier_61_to_80z;TestTieredDeathPenalty.test_death_penalty_mid_tier_61_to_80R   ó)   € ð
 #×:Ò:¸5ÑAÔAÀTÒIÐIÐIÐIÐIÐIr   )éQ   éU   éY   c                ó<   — |                      |¦  «        dk    sJ ‚dS )z2Levels 81 to 89 lose 10% of current level EXP bar.r7   NrH   rJ   s      r   Ú%test_death_penalty_high_tier_81_to_89z<TestTieredDeathPenalty.test_death_penalty_high_tier_81_to_89Y   rP   r   )r/   r3   éb   c                ó<   — |                      |¦  «        dk    sJ ‚dS )z2Levels 90 to 98 lose 15% of current level EXP bar.ç333333Ã?NrH   rJ   s      r   Ú(test_death_penalty_endgame_tier_90_to_98z?TestTieredDeathPenalty.test_death_penalty_endgame_tier_90_to_98`   rP   r   c                ó<   — |                      d¦  «        dk    sJ ‚dS )z,Level 99 loses 25% of current level EXP bar.éc   g      Ð?NrH   r   s     r   Ú#test_death_penalty_pinnacle_tier_99z:TestTieredDeathPenalty.test_death_penalty_pinnacle_tier_99g   s)   € ð #×:Ò:¸2Ñ>Ô>À$ÒFÐFÐFÐFÐFÐFr   c                ó<   — |                      d¦  «        dk    sJ ‚dS )z.Level 100 has 0% death penalty (immortal cap).éd   rG   NrH   r   s     r   Ú#test_death_penalty_godhood_tier_100z:TestTieredDeathPenalty.test_death_penalty_godhood_tier_100m   s)   € ð #×:Ò:¸3Ñ?Ô?À3ÒFÐFÐFÐFÐFÐFr   c                óî   — d}|                      |dd¬¦  «         |                     |¦  «        }|j        dk    sJ ‚|j        dk    sJ ‚|j        du sJ ‚|                     |¦  «        j        dk    sJ ‚dS )zFDying at 0% EXP results in 0 lost EXP; player never drops below level.Úplayer_zero_expr3   r   ©rA   Úcurrent_expFN)Úset_player_stateÚapply_death_penaltyÚexp_lostÚnew_expÚ
de_leveledÚget_level_inforA   ©r   r   ÚpidÚress       r   Ú)test_death_penalty_safe_floor_at_zero_expz@TestTieredDeathPenalty.test_death_penalty_safe_floor_at_zero_exps   s˜   € ð  ˆØ×,Ò,¨S¸ÈÐ,ÑJÔJÐJØ!×5Ò5°cÑ:Ô:ˆØŒ|˜qÒ Ð Ð Ð ØŒ{˜aÒÐÐÐØŒ~ Ð&Ð&Ð&Ð&Ø"×1Ò1°#Ñ6Ô6Ô<ÀÒBÐBÐBÐBÐBÐBr   c                ó<  — d}|                      d¦  «        }t          |dz  ¦  «        }|                     |d|¬¦  «         |                     |¦  «        }|j        dk    sJ ‚|j        |k    sJ ‚|j        du sJ ‚|                     |¦  «        j        dk    sJ ‚dS )zLDying at 10% EXP at Level 99 (25% penalty) clamps to 0% without de-leveling.Úplayer_clamp_testr[   r7   rb   r   FN)	Úget_delta_exprE   rd   re   rg   rf   rh   ri   rA   )r   r   rk   Úd99Úinitial_exprl   s         r   Ú&test_death_penalty_safe_floor_clampingz=TestTieredDeathPenalty.test_death_penalty_safe_floor_clamping   sº   € ð "ˆØ!×/Ò/°Ñ3Ô3ˆÝ˜# ™*‘o”oˆØ×,Ò,¨S¸ÈÐ,ÑTÔTÐTØ!×5Ò5°cÑ:Ô:ˆØŒ{˜aÒÐÐÐØŒ|˜{Ò*Ð*Ð*Ð*ØŒ~ Ð&Ð&Ð&Ð&Ø"×1Ò1°#Ñ6Ô6Ô<ÀÒBÐBÐBÐBÐBÐBr   c                óJ  — d}|                      d¦  «        }|                     |dt          |dz  ¦  «        ¬¦  «         t          d¦  «        D ]}|                     |¦  «         Œ|                     |¦  «        }|j        dk    sJ ‚|j        dk    sJ ‚|j        dk    sJ ‚dS )zD5 consecutive deaths at Level 99 clamp at 0% and track deaths_count.Úplayer_streakr[   ç      à?rb   é   r   N)	rp   rd   rE   r!   re   ri   rA   rc   Údeaths_count)r   r   rk   rq   Ú_Ústates         r   Ú+test_death_penalty_consecutive_death_streakzBTestTieredDeathPenalty.test_death_penalty_consecutive_death_streak�   sÂ   € ð ˆØ!×/Ò/°Ñ3Ô3ˆØ×,Ò,¨S¸ÍÈCÐRVÉJÉÌÐ,ÑXÔXÐXÝ�q‘”ð 	9ð 	9ˆAØ×3Ò3°CÑ8Ô8Ð8Ð8Ø#×2Ò2°3Ñ7Ô7ˆØŒ{˜bÒ Ð Ð Ð ØÔ  AÒ%Ð%Ð%Ð%ØÔ! QÒ&Ð&Ð&Ð&Ð&Ð&r   c                ó:  ‡— d}|                      d¦  «        }|                     |ddd¬¦  «         g Š|                     ˆfd„¦  «         |                     |¦  «        }|j        |k    r|j        dk    sJ ‚|j        dk    r|j        dk    sJ ‚|j        dk    r|j	        dk    r|j
        rJ ‚|                     |¦  «        }|j        dk    r|j        dk    sJ ‚|j        dk    r|j        |j        k    sJ ‚t          ‰¦  «        dk    r‰d         |k    sJ ‚dS )	zMLevel 100 death increments deaths_count, loses 0 EXP, and notifies listeners.Úplayer_lv100_death_telemetryr^   r   ©rA   rc   rx   c                ó.   •— ‰                      | ¦  «        S ©N©Úappend©ÚrÚeventss    €r   ú<lambda>z]TestTieredDeathPenalty.test_level_100_death_handling_telemetry_and_listener.<locals>.<lambda>£   ó   ø€ ÀÇÂÈqÑAQÔAQ€ r   rG   rB   N)Úget_canonical_benchmarkrd   Úadd_death_penalty_listenerre   Ú	player_idrA   rf   Úpenalty_ratioÚcurrent_exp_beforeÚcurrent_exp_afterrh   ri   rx   rc   Úcumulative_expÚlen)r   r   rk   Úb100rl   rz   r…   s         @r   Ú4test_level_100_death_handling_telemetry_and_listenerzKTestTieredDeathPenalty.test_level_100_death_handling_telemetry_and_listener›   s\  ø€ ð -ˆØ"×:Ò:¸3Ñ?Ô?ˆØ×,Ò,¨S¸ÈÐYZÐ,Ñ[Ô[Ð[Ø+-ˆØ×6Ò6Ð7QÐ7QÐ7QÐ7QÑRÔRÐRà!×5Ò5°cÑ:Ô:ˆØŒ} Ò#Ð#¨¬	°SÒ(8Ð(8Ð(8Ð8ØŒ|˜qÒ Ð  SÔ%6¸#Ò%=Ð%=Ð%=Ð=ØÔ%¨Ò*Ð*¨sÔ/DÈÒ/IÐ/IÐRUÔR`Ð/IÐ/IÐ`à#×2Ò2°3Ñ7Ô7ˆØÔ! QÒ&Ð&¨5¬;¸#Ò+=Ð+=Ð+=Ð=ØÔ  AÒ%Ð%¨%Ô*>À$ÔBUÒ*UÐ*UÐ*UÐUÝ�6‰{Œ{˜aÒÐ F¨1¤I°Ò$4Ð$4Ð$4Ð4Ð$4Ð$4r   c                óF  ‡— d}|                      |ddd¬¦  «         g Š|                     ˆfd„¦  «         t          dd¦  «        D ]D}|                     |¦  «        }|j        dk    sJ ‚|                     |¦  «        j        |k    sJ ‚ŒEt          ‰¦  «        dk    sJ ‚d	S )
zPConsecutive deaths at Level 100 increment deaths_count and dispatch every event.Úplayer_lv100_streakr^   r   r~   c                ó.   •— ‰                      | ¦  «        S r€   r�   rƒ   s    €r   r†   zhTestTieredDeathPenalty.test_level_100_consecutive_death_streak_increments_deaths_count.<locals>.<lambda>¶   r‡   r   rB   é   é   N)rd   r‰   r!   re   rf   ri   rx   r�   )r   r   rk   Úirl   r…   s        @r   Ú?test_level_100_consecutive_death_streak_increments_deaths_countzVTestTieredDeathPenalty.test_level_100_consecutive_death_streak_increments_deaths_count¯   sÌ   ø€ ð $ˆØ×,Ò,¨S¸ÈÐYZÐ,Ñ[Ô[Ð[Ø+-ˆØ×6Ò6Ð7QÐ7QÐ7QÐ7QÑRÔRÐRå�q˜!‘”ð 	Mð 	MˆAØ%×9Ò9¸#Ñ>Ô>ˆCØ”< 1Ò$Ð$Ð$Ð$Ø&×5Ò5°cÑ:Ô:ÔGÈ1ÒLÐLÐLÐLÐLÝ�6‰{Œ{˜aÒÐÐÐÐÐr   N)r   r	   rA   rE   r   r   r9   )r:   r;   r<   r=   ÚpytestÚmarkÚparametrizerK   rO   rU   rY   r\   r_   rm   rs   r{   r‘   r˜   r>   r   r   r@   r@   H   s   € € € € € ØXÐXà„[×Ò˜W o o oÑ6Ô6ðIð Ið Iñ 7Ô6ðIð „[×Ò˜W l l lÑ3Ô3ðJð Jð Jñ 4Ô3ðJð „[×Ò˜W l l lÑ3Ô3ðJð Jð Jñ 4Ô3ðJð „[×Ò˜W l l lÑ3Ô3ðJð Jð Jñ 4Ô3ðJðGð Gð Gð GðGð Gð Gð Gð
Cð 
Cð 
Cð 
CðCð Cð Cð Cð'ð 'ð 'ð 'ð5ð 5ð 5ð 5ð( ð  ð  ð  ð  ð  r   r@   c                  ó2   — e Zd ZdZdd„Zdd„Zdd„Zdd	„Zd
S )ÚTestLevelUpTransitionszBVerifies level advancement, rollover EXP, and passive/stat grants.r   r	   r   r   c                ó„   — d}|                      |ddd¦  «        }|j        du sJ ‚|j        dk    sJ ‚|j        dk    sJ ‚dS )zEAwarding exactly 600 EXP advances Level 1 to Level 2 with 0 rollover.Úplayer_exactrB   éX  Té   r   N©Úaward_monster_expÚlevel_up_occurredÚ	new_levelrc   rj   s       r   Útest_level_up_1_to_2_exact_expz5TestLevelUpTransitions.test_level_up_1_to_2_exact_expÂ   sb   € ð ˆØ!×3Ò3°C¸¸A¸sÑCÔCˆØÔ$¨Ð,Ð,Ð,Ð,ØŒ} Ò!Ð!Ð!Ð!ØŒ !Ò#Ð#Ð#Ð#Ð#Ð#r   c                ó„   — d}|                      |ddd¦  «        }|j        du sJ ‚|j        dk    sJ ‚|j        dk    sJ ‚dS )zBAwarding 650 EXP advances Level 1 to Level 2 with 50 rollover EXP.Úplayer_rolloverrB   iŠ  Tr¡   é2   Nr¢   rj   s       r   Ú"test_level_up_1_to_2_with_rolloverz9TestLevelUpTransitions.test_level_up_1_to_2_with_rolloverÌ   sb   € ð  ˆØ!×3Ò3°C¸¸A¸sÑCÔCˆØÔ$¨Ð,Ð,Ð,Ð,ØŒ} Ò!Ð!Ð!Ð!ØŒ "Ò$Ð$Ð$Ð$Ð$Ð$r   c                ó˜   — d}|                      |ddd¦  «         |                     |¦  «        }|j        dk    sJ ‚|j        dk    sJ ‚dS )z;Level up grants +5 stat points and +1 unspent talent point.Úplayer_statsrB   r    N)r£   ri   Úunspent_talent_pointsÚtotal_talent_points)r   r   rk   rz   s       r   Ú%test_level_up_stats_and_talent_pointsz<TestLevelUpTransitions.test_level_up_stats_and_talent_pointsÖ   sd   € ð ˆØ×-Ò-¨c°1°a¸Ñ=Ô=Ð=Ø#×2Ò2°3Ñ7Ô7ˆØÔ*¨aÒ/Ð/Ð/Ð/ØÔ(¨AÒ-Ð-Ð-Ð-Ð-Ð-r   c                óè   — d}|                      d¦  «        }|                      d¦  «        }|                     |dd||z   dz   ¦  «        }|j        dk    sJ ‚|j        dk    sJ ‚|j        dk    sJ ‚dS )z<Large EXP award cascades through multiple levels accurately.Úplayer_multirB   r¡   r^   r–   N)rp   r£   r¥   rc   Úlevels_gained)r   r   rk   Úd1Úd2rl   s         r   Útest_multi_level_jumpz,TestLevelUpTransitions.test_multi_level_jumpà   s�   € ð ˆØ ×.Ò.¨qÑ1Ô1ˆØ ×.Ò.¨qÑ1Ô1ˆØ!×3Ò3°C¸¸A¸rÀB¹wÈ¹}ÑMÔMˆØŒ} Ò!Ð!Ð!Ð!ØŒ #Ò%Ð%Ð%Ð%ØÔ  AÒ%Ð%Ð%Ð%Ð%Ð%r   Nr9   )r:   r;   r<   r=   r¦   rª   r¯   rµ   r>   r   r   r�   r�   ¿   sj   € € € € € ØLÐLð$ð $ð $ð $ð%ð %ð %ð %ð.ð .ð .ð .ð
&ð 
&ð 
&ð 
&ð 
&ð 
&r   r�   c                  ó:   — e Zd ZdZdd„Zdd„Zdd„Zdd	„Zdd
„ZdS )ÚTestLevelCapAndTerminalStatez0Verifies strict Level 100 cap and zero overflow.r   r	   r   r   c                ó´   — d}|                      |dd¬¦  «         |                     |ddd¦  «        }|j        dk    sJ ‚|j        du sJ ‚|j        dk    sJ ‚dS )z)Kills at Level 100 award 0 effective EXP.Úplayer_lv100r^   r   rb   i † FN)rd   r£   Úeffective_expr¤   r¥   rj   s       r   Ú&test_level_100_cap_no_further_exp_gainzCTestLevelCapAndTerminalState.test_level_100_cap_no_further_exp_gainð   s~   € ð ˆØ×,Ò,¨S¸ÈÐ,ÑKÔKÐKØ!×3Ò3°C¸¸cÀ6ÑJÔJˆØÔ  AÒ%Ð%Ð%Ð%ØÔ$¨Ð-Ð-Ð-Ð-ØŒ} Ò#Ð#Ð#Ð#Ð#Ð#r   c                óØ   — d}|                      |dd¬¦  «         |                     |¦  «        }|j        dk    sJ ‚|j        dk    sJ ‚|                     |¦  «        j        dk    sJ ‚dS )z.Level 100 player suffers 0 penalty upon death.Úplayer_lv100_deathr^   r   rb   N)rd   re   rf   rg   ri   rA   rj   s       r   Ú%test_level_100_death_penalty_immunityzBTestLevelCapAndTerminalState.test_level_100_death_penalty_immunityû   s…   € ð #ˆØ×,Ò,¨S¸ÈÐ,ÑKÔKÐKØ!×5Ò5°cÑ:Ô:ˆØŒ|˜qÒ Ð Ð Ð ØŒ{˜aÒÐÐÐØ"×1Ò1°#Ñ6Ô6Ô<ÀÒCÐCÐCÐCÐCÐCr   c                óÜ  — d}|                      d¦  «        j        }|                     d¦  «        }|                     |d|dz
  ¬¦  «         |                     |¦  «        j        |dz
  k    sJ ‚|                     |dd¬¦  «        }|j        dk    r|j        r|j        dk    sJ ‚|                     |¦  «        }|j	        dk    r|j        dk    sJ ‚|j        |k    r|j
        |k    sJ ‚d	S )
zNLevel 99 with surplus EXP clamps exactly to 24,285,477,315 with zero overflow.Úplayer_lv99_clamp_testr^   r[   rB   rb   i@B ©Úmonster_levelÚbase_expr   N)rˆ   rŽ   rp   rd   ri   r£   r¥   Ú
leveled_uprc   rA   Úlifetime_exp)r   r   rk   Úexpected_caprq   rl   Úinfos          r   Ú2test_level_100_cumulative_exp_clamping_on_level_upzOTestLevelCapAndTerminalState.test_level_100_cumulative_exp_clamping_on_level_up  s  € ð 'ˆØ*×BÒBÀ3ÑGÔGÔVˆØ!×/Ò/°Ñ3Ô3ˆà×,Ò,¨S¸ÈÈaÉÐ,ÑPÔPÐPØ"×1Ò1°#Ñ6Ô6ÔEÈÐXYÑIYÒYÐYÐYÐYà!×3Ò3°CÀsÐU^Ð3Ñ_Ô_ˆØŒ} Ò#Ð#¨¬Ð#¸3¼?ÈaÒ;OÐ;OÐ;OÐOà"×1Ò1°#Ñ6Ô6ˆØŒz˜SÒ Ð  TÔ%5¸Ò%:Ð%:Ð%:Ð:ØÔ" lÒ2Ð2°tÔ7HÈLÒ7XÐ7XÐ7XÐXÐ7XÐ7Xr   c                óâ   — d}|                      d¦  «        j        }|                     |dd¬¦  «         |                     |¦  «        }|j        dk    r|j        dk    sJ ‚|j        |k    sJ ‚dS )zLDirect set_player_state at Level 100 clamps cumulative_exp to benchmark cap.Úplayer_direct_100r^   iˆ  rb   r   N)rˆ   rŽ   rd   ri   rA   rc   )r   r   rk   rÆ   rÇ   s        r   Ú7test_level_100_set_player_state_cumulative_exp_clampingzTTestLevelCapAndTerminalState.test_level_100_set_player_state_cumulative_exp_clamping  s‡   € ð "ˆØ*×BÒBÀ3ÑGÔGÔVˆØ×,Ò,¨S¸ÈÐ,ÑNÔNÐNØ"×1Ò1°#Ñ6Ô6ˆØŒz˜SÒ Ð  TÔ%5¸Ò%:Ð%:Ð%:Ð:ØÔ" lÒ2Ð2Ð2Ð2Ð2Ð2r   c                ó*  — d}|                      d¦  «        j        }|                     |dd¬¦  «         t          d¦  «        D ].}|                     |dd¬¦  «        }|j        dk    r|j        rJ ‚Œ/|                     |¦  «        j        |k    sJ ‚dS )	zNSubsequent monster kills at Level 100 award 0 EXP and maintain cumulative cap.Úplayer_100_grindr^   r   rb   rw   i ¡ rÁ   N)rˆ   rŽ   rd   r!   r£   rº   rÄ   ri   )r   r   rk   rÆ   ry   rl   s         r   Ú@test_level_100_further_monster_kills_maintain_cumulative_exp_capz]TestLevelCapAndTerminalState.test_level_100_further_monster_kills_maintain_cumulative_exp_cap#  s²   € ð !ˆØ*×BÒBÀ3ÑGÔGÔVˆØ×,Ò,¨S¸ÈÐ,ÑKÔKÐKÝ�q‘”ð 	Að 	AˆAØ%×7Ò7¸È3ÐY`Ð7ÑaÔaˆCØÔ$¨Ò)Ð)°#´.Ð)Ð)Ð@Ð)Ø"×1Ò1°#Ñ6Ô6ÔEÈÒUÐUÐUÐUÐUÐUr   Nr9   )	r:   r;   r<   r=   r»   r¾   rÈ   rË   rÎ   r>   r   r   r·   r·   í   sŒ   € € € € € Ø:Ð:ð	$ð 	$ð 	$ð 	$ð	Dð 	Dð 	Dð 	DðYð Yð Yð Yð$	3ð 	3ð 	3ð 	3ð
Vð 
Vð 
Vð 
Vð 
Vð 
Vr   r·   c                  ó"   — e Zd ZdZd	d„Zd	d„ZdS )
ÚTestServiceStateAndPersistencezBVerifies DTO properties, aliases, and polymorphic call signatures.r   r	   r   r   c                ó8  — d}|                      |ddd¦  «        }|j        |j        cxk    rdk    sn J ‚|j        |j        cxk    rdu sn J ‚|                     |¦  «        }|j        |j        k    sJ ‚|j        |j	        k    sJ ‚|j
        |j        k    sJ ‚dS )z@Verifies compatibility with E2E signatures and alias properties.Úplayer_aliasr   éè  FN)r£   rº   Úexp_awardedr¤   rÄ   re   rg   r�   Úpenalty_exp_lostrf   Úpenalty_percentager‹   )r   r   rk   rl   Ú	death_ress        r   Ú'test_interface_polymorphism_and_aliaseszFTestServiceStateAndPersistence.test_interface_polymorphism_and_aliases3  sÔ   € ð ˆØ!×3Ò3°C¸¸RÀÑFÔFˆØÔ  C¤OÐ;Ð;Ò;Ð;°tÒ;Ð;Ð;Ð;Ð;Ð;ØÔ$¨¬Ð?Ð?Ò?Ð?¸%Ð?Ð?Ð?Ð?Ð?Ð?à'×;Ò;¸CÑ@Ô@ˆ	ØÔ  IÔ$?Ò?Ð?Ð?Ð?ØÔ)¨YÔ-?Ò?Ð?Ð?Ð?ØÔ+¨yÔ/FÒFÐFÐFÐFÐFÐFr   c                óÚ   — d}|                      |dd¬¦  «         |                     |dd¬¦  «        }|j        dk    r|j        dk    sJ ‚|                     |¦  «        j        dk    sJ ‚dS )	zKNegative base_exp argument is clamped to 0, preventing unintended EXP loss.Úplayer_neg_expé
   r^   rb   iœÿÿÿrÁ   r   N)rd   r£   rº   rÔ   ri   rc   rj   s       r   Ú:test_award_monster_exp_negative_base_exp_sanitized_to_zerozYTestServiceStateAndPersistence.test_award_monster_exp_negative_base_exp_sanitized_to_zeroA  s‡   € ð ˆØ×,Ò,¨S¸ÈÐ,ÑLÔLÐLØ!×3Ò3°CÀrÐTXÐ3ÑYÔYˆØÔ  AÒ%Ð%¨#¬/¸QÒ*>Ð*>Ð*>Ð>Ø"×1Ò1°#Ñ6Ô6ÔBÀcÒIÐIÐIÐIÐIÐIr   Nr9   )r:   r;   r<   r=   rØ   rÜ   r>   r   r   rÐ   rÐ   0  sL   € € € € € ØLÐLðGð Gð Gð GðJð Jð Jð Jð Jð Jr   rÐ   c                  ó:   — e Zd ZdZdd„Zdd„Zdd„Zdd	„Zdd
„ZdS )ÚTestCombatEngineIntegrationzAVerifies CombatEngine fatal damage callback and progression hook.r   r	   r   r   c           	     ó°  — t          ¦   «         }|                     |¦  «         |                     d¦  «        }|                     ddt	          |dz  ¦  «        ¬¦  «         t          ddt          j        d¬¦  «        }t          d	d
t          j        dddd¬¦  «        }|                     |¦  «         |                     |¦  «         | 	                    dd	dt          j        d¬¦  «        }|j
        du sJ ‚|j        dk    sJ ‚|                     d¦  «        }|j        dk    sJ ‚|j        t	          |dz  ¦  «        t	          |dz  ¦  «        z
  k    sJ ‚dS )zQWhen player suffers lethal damage, attached progression service penalizes player.rR   Úp_combat_dierv   rb   rÛ   u   Boss Tháº§n Maç    €‡Ã@©Úbase_attacké   zPlayer In Perilg     @@T©Ú
current_hpÚmax_hpÚ	is_playerrŠ   rÓ   ©Úcurrent_timestamp_msrG   rB   r7   N)r   Úattach_progression_servicerp   rd   rE   r   r   ÚHOAÚregister_actorÚcalculate_damageÚis_fatalræ   ri   rx   rc   )r   r   ÚengineÚd85ÚattackerÚdefenderÚdmg_resrz   s           r   Ú/test_combat_fatal_damage_triggers_death_penaltyzKTestCombatEngineIntegration.test_combat_fatal_damage_triggers_death_penaltyO  sq  € õ ‘”ˆØ×)Ò)Ð*=Ñ>Ô>Ð>à!×/Ò/°Ñ3Ô3ˆØ×,Ò,¨^À2ÕSVÐWZÐ]aÑWaÑSbÔSbÐ,ÑcÔcÐcå˜rÐ#3µ\Ô5EÐSYÐZÑZÔZˆÝØÐ!¥<Ô#3ÀØ D°Nð
ñ 
ô 
ˆð 	×Ò˜hÑ'Ô'Ð'Ø×Ò˜hÑ'Ô'Ð'à×)Ò)¨"¨b°&½,Ô:JÐaeÐ)ÑfÔfˆØÔ 4Ð'Ð'Ð'Ð'ØÔ" cÒ)Ð)Ð)Ð)à#×2Ò2°>ÑBÔBˆØÔ! QÒ&Ð&Ð&Ð&ØÔ ¥C¨¨d©
¡O¤Oµc¸#À¹*±o´oÑ$EÒEÐEÐEÐEÐEÐEr   c           	     ó  — t          ¦   «         }|                     |¦  «         |                     ddd¬¦  «         t          ddt          j        ddd¬	¦  «        }t          d
dt          j        dddd¬¦  «        }|                     |¦  «         |                     |¦  «         |                     dd
dt          j        d¬¦  «        }|j	        du sJ ‚|j
        dk    sJ ‚|                     d¦  «        j        dk    sJ ‚dS )zLWhen monster is defeated by player, attached progression service awards EXP.Úp_combat_slayerrB   r   rb   r4   zSlayer Playerç      Y@T©rã   rè   rŠ   é   zWild Skeletonç      I@F©ræ   rç   rè   rA   éÐ  ré   rG   N)r   rë   rd   r   r   ÚKIMÚMOCrí   rî   rï   ræ   ri   rc   )r   r   rð   rò   ró   rô   s         r   Ú,test_combat_monster_defeat_awards_player_expzHTestCombatEngineIntegration.test_combat_monster_defeat_awards_player_expi  s,  € õ ‘”ˆØ×)Ò)Ð*=Ñ>Ô>Ð>à×,Ò,Ð->ÀaÐUVÐ,ÑWÔWÐWåØ�¥Ô!1¸uØÐ&7ð
ñ 
ô 
ˆõ Ø�¥Ô!1¸dØ 5°ð
ñ 
ô 
ˆð 	×Ò˜hÑ'Ô'Ð'Ø×Ò˜hÑ'Ô'Ð'à×)Ò)¨"¨b°%½Ô9IÐ`dÐ)ÑeÔeˆØÔ 4Ð'Ð'Ð'Ð'ØÔ" cÒ)Ð)Ð)Ð)Ø"×1Ò1Ð2CÑDÔDÔPÐSTÒTÐTÐTÐTÐTÐTr   c                óÚ  ‡— t          ¦   «         }g Š|                     ˆfd„¦  «         t          ddt          j        d¬¦  «        }t          ddt          j        dd¬	¦  «        }|                     |¦  «         |                     |¦  «         |                     dddt          j        d
¬¦  «        }|j        du r|j	        dk    rt          ‰¦  «        dk    sJ ‚|                     dddt          j        d¬¦  «        }|j        du r|j	        dk    rt          ‰¦  «        dk    sJ ‚|                     dddt          j        d¬¦  «        }|j        du rt          ‰¦  «        dk    sJ ‚dS )zKStriking dead defender (HP=0.0) produces is_fatal=False and skips callback.c                ó.   •— ‰                      | ¦  «        S r€   r�   )rl   ÚatkÚdfnÚfatal_eventss      €r   r†   zlTestCombatEngineIntegration.test_combat_corpse_overkill_produces_not_fatal_and_no_callback.<locals>.<lambda>†  s   ø€ ¸<×;NÒ;NÈsÑ;SÔ;S€ r   rB   ÚAttackerrø   râ   r¡   ÚDefenderrû   )ræ   rç   rÓ   ré   TrG   éò  Frá   iü  N)r   Úset_fatal_damage_hookr   r   rþ   rÿ   rí   rî   rï   ræ   r�   )r   rð   rò   ró   Úres1Úres2Úres3r  s          @r   Ú>test_combat_corpse_overkill_produces_not_fatal_and_no_callbackzZTestCombatEngineIntegration.test_combat_corpse_overkill_produces_not_fatal_and_no_callback‚  s‰  ø€ å‘”ˆØ02ˆØ×$Ò$Ð%SÐ%SÐ%SÐ%SÑTÔTÐTå˜q *­lÔ.>ÈEÐRÑRÔRˆÝ˜q *­lÔ.>È4ÐX\Ð]Ñ]Ô]ˆØ×Ò˜hÑ'Ô'Ð'Ø×Ò˜hÑ'Ô'Ð'ð ×&Ò& q¨!¨UµLÔ4DÐ[_Ð&Ñ`Ô`ˆØŒ} Ð$Ð$¨Ô)<ÀÒ)CÐ)CÍÈLÑHYÔHYÐ]^ÒH^ÐH^ÐH^Ð^ð ×&Ò& q¨!¨UµLÔ4DÐ[_Ð&Ñ`Ô`ˆØŒ} Ð%Ð%¨(Ô*=ÀÒ*DÐ*DÍÈ\ÑIZÔIZÐ^_ÒI_ÐI_ÐI_Ð_ð ×&Ò& q¨!¨Vµ\Ô5EÐ\`Ð&ÑaÔaˆØŒ} Ð%Ð%­#¨lÑ*;Ô*;¸qÒ*@Ð*@Ð*@Ð@Ð*@Ð*@r   c           	     ó’  — t          ¦   «         }|                     |¦  «         d}|                     |dd¬¦  «         t          ddt          j        dd|¬	¦  «        }t          d
dt          j        dddd¬¦  «        }|                     |¦  «         |                     |¦  «         |                     dd
dt          j        d¬¦  «        }|j	        du sJ ‚| 
                    |¦  «        j        }|dk    sJ ‚|                     dd
dt          j        d¬¦  «        }|j	        du sJ ‚| 
                    |¦  «        j        |k    sJ ‚dS )zHStriking a dead monster corpse does not award duplicate progression EXP.Úp_corpse_slayerr©   r   rb   rÛ   ÚHerog      i@Trù   r4   ÚGobling      4@Frü   rÓ   ré   r  N)r   rë   rd   r   r   rþ   rÿ   rí   rî   rï   ri   rc   )	r   r   rð   rk   ÚheroÚgoblinr
  Úexp_after_killr  s	            r   Ú:test_combat_corpse_overkill_does_not_duplicate_monster_expzVTestCombatEngineIntegration.test_combat_corpse_overkill_does_not_duplicate_monster_exp™  sd  € õ ‘”ˆØ×)Ò)Ð*=Ñ>Ô>Ð>àˆØ×,Ò,¨S¸ÈÐ,ÑJÔJÐJÝ˜2˜v¥|Ô'7ÀUÐVZÐfiÐjÑjÔjˆÝ˜R ­<Ô+;ÈÐUYÐejÐrtÐuÑuÔuˆØ×Ò˜dÑ#Ô#Ð#Ø×Ò˜fÑ%Ô%Ð%à×&Ò& r¨2¨uµlÔ6FÐ]aÐ&ÑbÔbˆØŒ} Ð$Ð$Ð$Ð$Ø,×;Ò;¸CÑ@Ô@ÔLˆØ Ò!Ð!Ð!Ð!à×&Ò& r¨2¨uµlÔ6FÐ]aÐ&ÑbÔbˆØŒ} Ð%Ð%Ð%Ð%Ø"×1Ò1°#Ñ6Ô6ÔBÀnÒTÐTÐTÐTÐTÐTr   c           	     ó4  — t          ¦   «         }|                     |¦  «         d}|                     d¦  «        }|                     |d|d¬¦  «         t	          ddt
          j        ddd¬	¦  «        }t	          d
dt
          j        ddd|¬¦  «        }|                     |¦  «         |                     |¦  «         | 	                    dd
dt
          j        d¬¦  «        }| 	                    dd
dt
          j        d¬¦  «        }| 	                    dd
dt
          j        d¬¦  «        }	|j
        du r|j
        s|	j
        rJ ‚|                     |¦  «        }
|
j        dk    sJ ‚t          t          j        |dz  ¦  «        ¦  «        }|
j        ||z
  k    sJ ‚dS )zSMulti-hit barrage on player applies death penalty exactly once, not per projectile.Úp_victim_barrager3   r   r~   é   ÚBossrá   F)rã   rè   rA   é   ÚVictimrø   Trå   rý   ré   iÚ  iä  rB   rX   N)r   rë   rp   rd   r   r   rì   rþ   rí   rî   rï   ri   rx   rE   r*   Úfloorrc   )r   r   rð   rk   Úd95ÚbossÚplayerÚr1Úr2Úr3rz   Úexpected_penaltys               r   ÚLtest_combat_channeled_barrage_on_dead_player_does_not_multiply_death_penaltyzhTestCombatEngineIntegration.test_combat_channeled_barrage_on_dead_player_does_not_multiply_death_penalty°  s«  € õ ‘”ˆØ×)Ò)Ð*=Ñ>Ô>Ð>à ˆØ!×/Ò/°Ñ3Ô3ˆØ×,Ò,¨S¸ÈÐZ[Ð,Ñ\Ô\Ð\å˜2˜v¥|Ô'7ÀVÐW\ÐdfÐgÑgÔgˆÝ˜R ­<Ô+;ÈÐV[ÐgkÐwzÐ{Ñ{Ô{ˆØ×Ò˜dÑ#Ô#Ð#Ø×Ò˜fÑ%Ô%Ð%à×$Ò$ R¨¨Vµ\Ô5EÐ\`Ð$ÑaÔaˆØ×$Ò$ R¨¨Vµ\Ô5EÐ\`Ð$ÑaÔaˆØ×$Ò$ R¨¨Vµ\Ô5EÐ\`Ð$ÑaÔaˆàŒ{˜dÐ"Ð"¨2¬;Ð"¸r¼{Ð"Ð"ÐJØ#×2Ò2°3Ñ7Ô7ˆØÔ! QÒ&Ð&Ð&Ð&Ý�tœz¨#°©*Ñ5Ô5Ñ6Ô6ÐØÔ  CÐ*:Ñ$:Ò:Ð:Ð:Ð:Ð:Ð:r   Nr9   )r   r   )	r:   r;   r<   r=   rõ   r   r  r  r$  r>   r   r   rÞ   rÞ   L  sŽ   € € € € € ØKÐKðFð Fð Fð Fð4Uð Uð Uð Uð2Að Að Að Að.Uð Uð Uð Uð.;ð ;ð ;ð ;ð ;ð ;r   rÞ   )r   r   )r=   Ú
__future__r   r*   Útypingr   r™   Úserver.world.combat_enginer   r   r   Ú&server.world.level_progression_servicer	   Ú$server.world.level_progression_typesr
   Úserver.world.martial_matrixr   Úfixturer   r   r@   r�   r·   rÐ   rÞ   r>   r   r   ú<module>r,     så  ððð ð #Ð "Ð "Ð "Ð "Ð "Ø €€€Ø Ð Ð Ð Ð Ð Ø €€€à SÐ SÐ SÐ SÐ SÐ SÐ SÐ SÐ SÐ SØ JÐ JÐ JÐ JÐ JÐ JØ CÐ CÐ CÐ CÐ CÐ CØ 4Ð 4Ð 4Ð 4Ð 4Ð 4ð „ðð ð ñ „ðð,$ð ,$ð ,$ð ,$ð ,$ñ ,$ô ,$ð ,$ð^t ð t ð t ð t ð t ñ t ô t ð t ðn+&ð +&ð +&ð +&ð +&ñ +&ô +&ð +&ð\@Vð @Vð @Vð @Vð @Vñ @Vô @Vð @VðFJð Jð Jð Jð Jñ Jô Jð Jð8|;ð |;ð |;ð |;ð |;ñ |;ô |;ð |;ð |;ð |;r   