§
    ‘Î½jæ4  ã                  óv  — d Z ddlmZ ddlZddlZddlZddlZddlZddlm	Z	 ddl
Z
 e	e¦  «                             ¦   «         j        j        j        Zedz  dz  Zedz  dz  Zedz  d	z  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 )a  
Unit Test Suite for Milestone 2: Character Animation & Visceral Combat Feel Engine.
Tests:
1. 5-Phase Kinetic State Machine & Archetype Clips (animation_engine.js)
2. Micro Hit-Stop, Screen Shake Clamping, and Damage Number Ring Buffer (combat_feel_engine.js)
3. Weapon Swing Archetypes Catalog (weapon_swing_catalog.js)
4. Animation Cancelling & Weapon Swing Triggers (combat_skills.js)
5. Canvas Renderer Game Loop Wiring (canvas_renderer.js)
6. Strict Quantitative Line Length Hygiene Caps (GEMINI.md & PROJECT.md)
é    )ÚannotationsN)ÚPathÚclientÚwebappÚjsÚengineÚdatac                  óx   — e Zd ZdZ ej        d¬¦  «        dd„¦   «         Zdd„Zdd„Zdd	„Z	dd
„Z
dd„Zdd„ZdS )Ú!TestAnimationEngineKineticMachinezVVerifies 5-phase kinetic machine and archetype animation clips in animation_engine.js.T©ÚautouseÚreturnÚNonec                óL   — t           dz                       d¬¦  «        | _        d S )Núanimation_engine.jsúutf-8©Úencoding©Ú
ENGINE_DIRÚ	read_textÚcode©Úselfs    úEC:\Projects\FreeExile\tests\unit\test_m2_animation_and_combat_feel.pyÚ
setup_codez,TestAnimationEngineKineticMachine.setup_code   s$   € åÐ"7Ñ7×BÒBÈGÐBÑTÔTˆŒ	ˆ	ˆ	ó    c                ó^   — d| j         v sJ ‚d| j         v sJ ‚d| j         v sJ ‚d| j         v sJ ‚dS )zKVerifies attack_slash, attack_thrust, attack_slam, attack_bolt clips exist.zattack_slash:zattack_thrust:zattack_slam:zattack_bolt:N©r   r   s    r   Ú&test_archetype_animation_clips_definedzHTestAnimationEngineKineticMachine.test_archetype_animation_clips_defined"   sT   € à $¤)Ð+Ð+Ð+Ð+Ø 4¤9Ð,Ð,Ð,Ð,Ø ¤Ð*Ð*Ð*Ð*Ø ¤Ð*Ð*Ð*Ð*Ð*Ð*r   c                óZ   — d| j         v sd| j         v sJ ‚d| j         v sJ ‚d| j         v sJ ‚dS )zSVerifies hit_frame defined and atlas_clip aliased to prevent sprite index overflow.zatlas_clip: 'attack_slash'zatlas_clip: "attack_slash"zhit_frame: 2zhit_frame: 3Nr   r   s    r   Ú+test_archetype_hit_frame_and_atlas_aliasingzMTestAnimationEngineKineticMachine.test_archetype_hit_frame_and_atlas_aliasing)   sP   € à+¨t¬yÐ8Ð8Ð<XÐ\`Ô\eÐ<eÐ<eÐ<eÐeà ¤Ð*Ð*Ð*Ð*Ø ¤Ð*Ð*Ð*Ð*Ð*Ð*r   c                ó<   — d| j         v sJ ‚dD ]}|| j         v sJ ‚ŒdS )z;Verifies KINETIC_PHASES enum contains all canonical phases.ÚKINETIC_PHASES)ÚidleÚrunÚwind_upÚimpactÚrecoveryÚhit_stopÚdodgeNr   )r   Úphases     r   Ú'test_kinetic_phases_constant_and_valueszITestAnimationEngineKineticMachine.test_kinetic_phases_constant_and_values0   sD   € à 4¤9Ð,Ð,Ð,Ð,ØZð 	&ð 	&ˆEØ˜DœIÐ%Ð%Ð%Ð%Ð%ð	&ð 	&r   c                ó    — d| j         v sJ ‚d| j         v sJ ‚d| j         v sJ ‚d| j         v sJ ‚d| j         v sJ ‚d| j         v sJ ‚d| j         v sJ ‚dS )	zVVerifies updateAnimation continuously computes state.kineticPhase across all 5 phases.z#animState.kineticPhase = 'hit_stop'z"animState.kineticPhase = 'wind_up'z!animState.kineticPhase = 'impact'z#animState.kineticPhase = 'recovery'zanimState.kineticPhase = 'run'zanimState.kineticPhase = 'idle'z animState.kineticPhase = 'dodge'Nr   r   s    r   Ú)test_continuous_kinetic_phase_computationzKTestAnimationEngineKineticMachine.test_continuous_kinetic_phase_computation6   s�   € à4¸¼	ÐAÐAÐAÐAØ3°t´yÐ@Ð@Ð@Ð@Ø2°d´iÐ?Ð?Ð?Ð?Ø4¸¼	ÐAÐAÐAÐAØ/°4´9Ð<Ð<Ð<Ð<Ø0°D´IÐ=Ð=Ð=Ð=Ø1°T´YÐ>Ð>Ð>Ð>Ð>Ð>r   c                óH   — d| j         v sJ ‚d| j         v sJ ‚d| j         v sJ ‚dS )z@Verifies cancelAction function resets action lock and callbacks.zfunction cancelActionÚcancelActionzisActionLocked = falseNr   r   s    r   Ú)test_cancel_action_resets_locks_and_phasezKTestAnimationEngineKineticMachine.test_cancel_action_resets_locks_and_phase@   sA   € à&¨$¬)Ð3Ð3Ð3Ð3Ø ¤Ð*Ð*Ð*Ð*Ø'¨4¬9Ð4Ð4Ð4Ð4Ð4Ð4r   c                ó2   — d| j         v sJ ‚d| j         v sJ ‚dS )zKVerifies AnimationEngine exports cancelAction and KINETIC_PHASES to window.zcancelAction,zKINETIC_PHASES,Nr   r   s    r   Ú$test_animation_engine_window_exportszFTestAnimationEngineKineticMachine.test_animation_engine_window_exportsF   s.   € à $¤)Ð+Ð+Ð+Ð+Ø  D¤IÐ-Ð-Ð-Ð-Ð-Ð-r   N©r   r   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__ÚpytestÚfixturer   r    r"   r-   r/   r2   r4   © r   r   r   r      sÄ   € € € € € Ø`Ð`à€V„^˜DÐ!Ñ!Ô!ðUð Uð Uñ "Ô!ðUð+ð +ð +ð +ð+ð +ð +ð +ð&ð &ð &ð &ð?ð ?ð ?ð ?ð5ð 5ð 5ð 5ð.ð .ð .ð .ð .ð .r   r   c                  óˆ   — e Zd ZdZ ej        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d„ZdS )ÚTestCombatFeelEnginezaVerifies hit-stop, camera shake clamping, and damage number ring buffer in combat_feel_engine.js.Tr   r   r   c                óL   — t           dz                       d¬¦  «        | _        d S )Núcombat_feel_engine.jsr   r   r   r   s    r   r   zTestCombatFeelEngine.setup_codeO   s$   € åÐ"9Ñ9×DÒDÈgÐDÑVÔVˆŒ	ˆ	ˆ	r   c                óZ   — d| j         v sd| j         v sJ ‚d| j         v sJ ‚d| j         v sJ ‚dS )zJVerifies isHitStopped guards against NaN / undefined dt with safe default.zisHitStopped(dt = 0.016)zisHitStopped(dt=0.016)z	isNaN(dt)zhitStopTimer -= actualDtNr   r   s    r   Ú#test_hit_stop_dt_safety_and_defaultz8TestCombatFeelEngine.test_hit_stop_dt_safety_and_defaultS   sP   € à)¨T¬YÐ6Ð6Ð:RÐVZÔV_Ð:_Ð:_Ð:_Ð_Ø˜dœiÐ'Ð'Ð'Ð'Ø)¨T¬YÐ6Ð6Ð6Ð6Ð6Ð6r   c                ó   — d| j         v sJ ‚dS )zMVerifies triggerHitStop computes duration = max(0.016 * frames, durationSec).z%Math.max(0.016 * frames, durationSec)Nr   r   s    r   Útest_hit_stop_duration_formulaz3TestCombatFeelEngine.test_hit_stop_duration_formulaY   s   € à6¸$¼)ÐCÐCÐCÐCÐCÐCr   c                ó2   — d| j         v sJ ‚d| j         v sJ ‚dS )z=Verifies triggerHitStop pauses both attacker.anim and target.zattacker.anim.hitStopTimerztarget.hitStopTimerNr   r   s    r   Ú.test_hit_stop_affects_both_attacker_and_targetzCTestCombatFeelEngine.test_hit_stop_affects_both_attacker_and_target]   s.   € à+¨t¬yÐ8Ð8Ð8Ð8Ø$¨¬	Ð1Ð1Ð1Ð1Ð1Ð1r   c                ó2   — d| j         v sJ ‚d| j         v sJ ‚dS )zSVerifies screen shake intensity clamped to [2.5, 8.5] and duration to [0.12, 0.45].z'Math.min(8.5, Math.max(2.5, intensity))z+Math.min(0.45, Math.max(0.12, durationSec))Nr   r   s    r   Ú#test_screen_shake_clamping_to_specsz8TestCombatFeelEngine.test_screen_shake_clamping_to_specsb   s.   € à8¸D¼IÐEÐEÐEÐEØ<ÀÄ	ÐIÐIÐIÐIÐIÐIr   c                óH   — d| j         v sJ ‚d| j         v sJ ‚d| j         v sJ ‚dS )zYVerifies triggerScreenShake synchronizes with global screenShake in iso_math.js / window.z1screenShake.intensity = this.screenShakeIntensityz/screenShake.duration = this.screenShakeDurationz)screenShake.timer = this.screenShakeTimerNr   r   s    r   Ú#test_screen_shake_syncs_with_globalz8TestCombatFeelEngine.test_screen_shake_syncs_with_globalg   sA   € àBÀdÄiÐOÐOÐOÐOØ@ÀDÄIÐMÐMÐMÐMØ:¸d¼iÐGÐGÐGÐGÐGÐGr   c                óZ   — d| j         v sJ ‚d| j         v sJ ‚d| j         v r	d| j         v sJ ‚dS )zKVerifies directional shake applies vector decomposition along strike angle.zMath.cos(this.shakeAngle)zMath.sin(this.shakeAngle)ÚshakeOffsetXÚshakeOffsetYNr   r   s    r   Ú$test_directional_shake_decompositionz9TestCombatFeelEngine.test_directional_shake_decompositionm   sM   € à*¨d¬iÐ7Ð7Ð7Ð7Ø*¨d¬iÐ7Ð7Ð7Ð7Ø ¤Ð*Ð*¨~ÀÄÐ/JÐ/JÐ/JÐJÐ/JÐ/Jr   c                ól   — d| j         v sJ ‚d| j         v r	d| j         v sJ ‚d| j         v r	d| j         v sJ ‚dS )z]Verifies pop-in bounce: standard 1.6x->1.0x (1.6 - p * 0.6), crit 2.0x->1.2x (2.0 - p * 0.8).z this.scale = isCrit ? 2.0 : 1.6;z1.6z0.6z2.0z0.8Nr   r   s    r   Ú&test_damage_number_pop_in_bounce_scalez;TestCombatFeelEngine.test_damage_number_pop_in_bounce_scales   sY   € à1°T´YÐ>Ð>Ð>Ð>Ø˜œ	Ð!Ð! e¨t¬yÐ&8Ð&8Ð&8Ð8Ø˜œ	Ð!Ð! e¨t¬yÐ&8Ð&8Ð&8Ð8Ð&8Ð&8r   c                óH   — d| j         v sJ ‚d| j         v sJ ‚d| j         v sJ ‚dS )zGVerifies pre-allocated 64-slot Ring Buffer without dynamic allocations.zMAX_DAMAGE_NUMBERS = 64z;this.damagePool = Array.from({ length: MAX_DAMAGE_NUMBERS }z8this.poolHead = (this.poolHead + 1) % MAX_DAMAGE_NUMBERSNr   r   s    r   Ú%test_static_ring_buffer_pool_capacityz:TestCombatFeelEngine.test_static_ring_buffer_pool_capacityy   sC   € à(¨D¬IÐ5Ð5Ð5Ð5ØLÐPTÔPYÐYÐYÐYÐYØIÈTÌYÐVÐVÐVÐVÐVÐVr   Nr5   )r6   r7   r8   r9   r:   r;   r   rB   rD   rF   rH   rJ   rN   rP   rR   r<   r   r   r>   r>   L   s  € € € € € ØkÐkà€V„^˜DÐ!Ñ!Ô!ðWð Wð Wñ "Ô!ðWð7ð 7ð 7ð 7ðDð Dð Dð Dð2ð 2ð 2ð 2ð
Jð Jð Jð Jð
Hð Hð Hð HðKð Kð Kð Kð9ð 9ð 9ð 9ðWð Wð Wð Wð Wð Wr   r>   c                  óh   — e Zd ZdZ ej        d¬¦  «        dd„¦   «         Zdd„Zdd„Zdd	„Z	dd
„Z
dS )ÚTestWeaponSwingCatalogz8Verifies 4 weapon archetypes in weapon_swing_catalog.js.Tr   r   r   c                óL   — t           dz                       d¬¦  «        | _        d S )Núweapon_swing_catalog.jsr   r   )ÚDATA_DIRr   r   r   s    r   r   z!TestWeaponSwingCatalog.setup_codeƒ   s$   € åÐ 9Ñ9×DÒDÈgÐDÑVÔVˆŒ	ˆ	ˆ	r   c                ó&   — dD ]}|| j         v sJ ‚ŒdS )zAVerifies SLASH, THRUST, SLAM, and BOLT archetypes are configured.)zSLASH:zTHRUST:zSLAM:zBOLT:Nr   )r   Úarchs     r   Ú%test_all_four_weapon_archetypes_existz<TestWeaponSwingCatalog.test_all_four_weapon_archetypes_exist‡   s1   € à;ð 	%ð 	%ˆDØ˜4œ9Ð$Ð$Ð$Ð$Ð$ð	%ð 	%r   c                ó^   — d| j         v sJ ‚d| j         v sJ ‚d| j         v sJ ‚d| j         v sJ ‚dS )z@Verifies style arc, cone, radial, and beam for the 4 archetypes.zstyle: 'arc'zstyle: 'cone'zstyle: 'radial'zstyle: 'beam'Nr   r   s    r   Útest_archetype_stylesz,TestWeaponSwingCatalog.test_archetype_stylesŒ   sT   € à ¤Ð*Ð*Ð*Ð*Ø $¤)Ð+Ð+Ð+Ð+Ø  D¤IÐ-Ð-Ð-Ð-Ø $¤)Ð+Ð+Ð+Ð+Ð+Ð+r   c                ó~   — d| j         v r	d| j         v sJ ‚d| j         v r	d| j         v sJ ‚d| j         v r	d| j         v sJ ‚dS )z:Verifies hitStopFrames and hitStopDuration for archetypes.zhitStopFrames: 3zhitStopDuration: 0.05zhitStopFrames: 2zhitStopDuration: 0.035zhitStopFrames: 4zhitStopDuration: 0.066Nr   r   s    r   Útest_archetype_hit_stop_timingsz6TestWeaponSwingCatalog.test_archetype_hit_stop_timings“   sh   € à! T¤YÐ.Ð.Ð3JÈdÌiÐ3WÐ3WÐ3WÐWØ! T¤YÐ.Ð.Ð3KÈtÌyÐ3XÐ3XÐ3XÐXØ! T¤YÐ.Ð.Ð3KÈtÌyÐ3XÐ3XÐ3XÐXÐ3XÐ3Xr   c                ó2   — d| j         v sJ ‚d| j         v sJ ‚dS )zUVerifies getWeaponSwingArchetype provides case-insensitive lookup and SLASH fallback.z'export function getWeaponSwingArchetypezWEAPON_SWING_ARCHETYPES.SLASHNr   r   s    r   Ú&test_get_archetype_lookup_and_fallbackz=TestWeaponSwingCatalog.test_get_archetype_lookup_and_fallback™   s.   € à8¸D¼IÐEÐEÐEÐEØ.°$´)Ð;Ð;Ð;Ð;Ð;Ð;r   Nr5   )r6   r7   r8   r9   r:   r;   r   rZ   r\   r^   r`   r<   r   r   rT   rT   €   s    € € € € € ØBÐBà€V„^˜DÐ!Ñ!Ô!ðWð Wð Wñ "Ô!ðWð%ð %ð %ð %ð
,ð ,ð ,ð ,ðYð Yð Yð Yð<ð <ð <ð <ð <ð <r   rT   c                  óh   — e Zd ZdZ ej        d¬¦  «        dd„¦   «         Zdd„Zdd„Zdd	„Z	dd
„Z
dS )Ú*TestCombatSkillsAnimationCancellingAndFeelzHVerifies animation cancelling and visceral triggers in combat_skills.js.Tr   r   r   c                óL   — t           dz                       d¬¦  «        | _        d S )Núcombat_skills.jsr   r   r   r   s    r   r   z5TestCombatSkillsAnimationCancellingAndFeel.setup_code¢   s$   € åÐ"4Ñ4×?Ò?ÈÐ?ÑQÔQˆŒ	ˆ	ˆ	r   c                óH   — d| j         v sJ ‚d| j         v sJ ‚d| j         v sJ ‚dS )zEVerifies doDodge immediately cancels attack wind-up and action locks.zplayer.attackTimer = 0;r1   zplayer.animState = 'dodge';Nr   r   s    r   Ú!test_dodge_cancels_attack_wind_upzLTestCombatSkillsAnimationCancellingAndFeel.test_dodge_cancels_attack_wind_up¦   sA   € à(¨D¬IÐ5Ð5Ð5Ð5Ø ¤Ð*Ð*Ð*Ð*Ø,°´	Ð9Ð9Ð9Ð9Ð9Ð9r   c                ó2   — d| j         v sJ ‚d| j         v sJ ‚dS )z7Verifies dodge charges decrement properly when dodging.zdodgeCharges--;z#window.dodgeCharges = dodgeCharges;Nr   r   s    r   Ú#test_dodge_charges_budget_decrementzNTestCombatSkillsAnimationCancellingAndFeel.test_dodge_charges_budget_decrement¬   s.   € à  D¤IÐ-Ð-Ð-Ð-Ø4¸¼	ÐAÐAÐAÐAÐAÐAr   c                ó^   — d| j         v sJ ‚d| j         v sJ ‚d| j         v sJ ‚d| j         v sJ ‚dS )zPVerifies primary attack and skills trigger SLASH, BOLT, THRUST, SLAM archetypes.ztriggerSwing('SLASH'ztriggerSwing('BOLT'ztriggerSwing('THRUST'ztriggerSwing('SLAM'Nr   r   s    r   Ú/test_skills_trigger_respective_swing_archetypeszZTestCombatSkillsAnimationCancellingAndFeel.test_skills_trigger_respective_swing_archetypes±   sT   € à%¨¬Ð2Ð2Ð2Ð2Ø$¨¬	Ð1Ð1Ð1Ð1Ø&¨$¬)Ð3Ð3Ð3Ð3Ø$¨¬	Ð1Ð1Ð1Ð1Ð1Ð1r   c                ó2   — d| j         v sJ ‚d| j         v sJ ‚dS )z=Verifies hitMonster triggers micro hit-stop freeze on impact.ÚtriggerHitStopzplayer.anim.hitStopTimerNr   r   s    r   Ú"test_hit_monster_triggers_hit_stopzMTestCombatSkillsAnimationCancellingAndFeel.test_hit_monster_triggers_hit_stop¸   s.   € à 4¤9Ð,Ð,Ð,Ð,Ø)¨T¬YÐ6Ð6Ð6Ð6Ð6Ð6r   Nr5   )r6   r7   r8   r9   r:   r;   r   rf   rh   rj   rm   r<   r   r   rb   rb   Ÿ   s    € € € € € ØRÐRà€V„^˜DÐ!Ñ!Ô!ðRð Rð Rñ "Ô!ðRð:ð :ð :ð :ðBð Bð Bð Bð
2ð 2ð 2ð 2ð7ð 7ð 7ð 7ð 7ð 7r   rb   c                  ó`   — e Zd ZdZ ej        d¬¦  «        dd„¦   «         Zdd„Zdd„Zdd	„Z	d
S )ÚTestCanvasRendererWiringz0Verifies game loop wiring in canvas_renderer.js.Tr   r   r   c                óL   — t           dz                       d¬¦  «        | _        d S )Núcanvas_renderer.jsr   r   r   r   s    r   r   z#TestCanvasRendererWiring.setup_codeÁ   s$   € åÐ"6Ñ6×AÒAÈ7ÐAÑSÔSˆŒ	ˆ	ˆ	r   c                ó2   — d| j         v sJ ‚d| j         v sJ ‚dS )zXVerifies canvas_renderer invokes combatFeelEngine.update and weaponSwingRenderer.update.z"window.combatFeelEngine.update(dt)z%window.weaponSwingRenderer.update(dt)Nr   r   s    r   Ú*test_combat_feel_and_swing_updates_in_loopzCTestCanvasRendererWiring.test_combat_feel_and_swing_updates_in_loopÅ   s.   € à3°t´yÐ@Ð@Ð@Ð@Ø6¸$¼)ÐCÐCÐCÐCÐCÐCr   c                ó2   — d| j         v sJ ‚d| j         v sJ ‚dS )zBVerifies canvas_renderer renders weapon swings and damage numbers.z.window.weaponSwingRenderer.render(ctx, player)z0window.combatFeelEngine.renderDamageNumbers(ctx)Nr   r   s    r   Ú-test_weapon_swing_and_damage_numbers_renderedzFTestCanvasRendererWiring.test_weapon_swing_and_damage_numbers_renderedÊ   s.   € à?À4Ä9ÐLÐLÐLÐLØAÀTÄYÐNÐNÐNÐNÐNÐNr   c                óÀ  — d| j         v sJ ‚d}d|› d�}t          j        ddd|gt          t          ¦  «        dd¬	¦  «        }|j        d
k    sJ d|j        › �¦   «         ‚t          j        |j	         
                    ¦   «         ¦  «        }|d         du sJ ‚t          |d         t          t          f¦  «        sJ ‚t          |d         t          t          f¦  «        sJ ‚dS )zTVerifies camera shake applies combat feel shake offset and executes runtime methods.zcombatFeelEngine.getShakeOffsetaõ  
        const { CombatFeelEngine } = await import('./client/webapp/js/engine/combat_feel_engine.js');
        const cfe = new CombatFeelEngine();
        cfe.triggerScreenShake(5.0, 0.25, 0.785);
        cfe.update(0.016);
        const offset = cfe.getShakeOffset();
        console.log(JSON.stringify({
            hasMethod: typeof cfe.getShakeOffset === 'function',
            offset: offset,
            x: offset ? offset.x : null,
            y: offset ? offset.y : null
        }));
        zb
        globalThis.window = globalThis;
        (async () => {
            try {
                z†
            } catch (err) {
                console.error(err);
                process.exit(1);
            }
        })();
        Únodez--input-type=modulez-eT)ÚcwdÚcapture_outputÚtextr   zNode execution failed:
Ú	hasMethodÚxÚyN)r   Ú
subprocessr&   ÚstrÚPROJECT_ROOTÚ
returncodeÚstderrÚjsonÚloadsÚstdoutÚstripÚ
isinstanceÚintÚfloat)r   Újs_codeÚrunnerÚresr	   s        r   Ú)test_camera_shake_uses_combat_feel_offsetzBTestCanvasRendererWiring.test_camera_shake_uses_combat_feel_offsetÏ   s
  € à0°D´IÐ=Ð=Ð=Ð=ðˆð
ð ð	
ð 
ð 
ˆõ ŒnØÐ*¨D°&Ð9Ý•LÑ!Ô!ØØð	
ñ 
ô 
ˆð Œ~ Ò"Ð"Ð"Ð$K¸s¼zÐ$KÐ$KÑ"Ô"Ð"ÝŒz˜#œ*×*Ò*Ñ,Ô,Ñ-Ô-ˆØ�KÔ  DÐ(Ð(Ð(Ð(Ý˜$˜sœ)¥c­5 \Ñ2Ô2Ð2Ð2Ð2Ý˜$˜sœ)¥c­5 \Ñ2Ô2Ð2Ð2Ð2Ð2Ð2r   Nr5   )
r6   r7   r8   r9   r:   r;   r   rs   ru   r�   r<   r   r   ro   ro   ¾   s�   € € € € € Ø:Ð:à€V„^˜DÐ!Ñ!Ô!ðTð Tð Tñ "Ô!ðTðDð Dð Dð Dð
Oð Oð Oð Oð
%3ð %3ð %3ð %3ð %3ð %3r   ro   c                  ó:   — e Zd ZdZd
d„Zd
d„Zd
d„Zd
d„Zd
d„Zd	S )ÚTestCodeHygieneLineCapszLEnforces strict quantitative line length limits from GEMINI.md & PROJECT.md.r   r   c                óÐ   — t          t          dz                       d¬¦  «                             ¦   «         ¦  «        }|dk    sJ d|› d�¦   «         ‚|dk    sJ d|› d�¦   «         ‚d	S )
z6animation_engine.js: <= 350 soft cap, <= 500 hard cap.r   r   r   é^  zanimation_engine.js has ú lines (exceeds 350 soft cap)éô  ú lines (exceeds 500 hard cap)N©Úlenr   r   Ú
splitlines©r   Úliness     r   Útest_animation_engine_line_capsz7TestCodeHygieneLineCaps.test_animation_engine_line_capsú   st   € å•ZÐ"7Ñ7×BÒBÈGÐBÑTÔT×_Ò_ÑaÔaÑbÔbˆØ˜Š|ˆ|ˆ|Ð\¸Ð\Ð\Ð\‰|Œ|ˆ|Ø˜Š|ˆ|ˆ|Ð\¸Ð\Ð\Ð\‰|Œ|ˆ|ˆ|ˆ|r   c                óÐ   — t          t          dz                       d¬¦  «                             ¦   «         ¦  «        }|dk    sJ d|› d�¦   «         ‚|dk    sJ d|› d�¦   «         ‚d	S )
z8combat_feel_engine.js: <= 350 soft cap, <= 500 hard cap.r@   r   r   r‘   zcombat_feel_engine.js has r’   r“   r”   Nr•   r˜   s     r   Ú!test_combat_feel_engine_line_capsz9TestCodeHygieneLineCaps.test_combat_feel_engine_line_caps   st   € å•ZÐ"9Ñ9×DÒDÈgÐDÑVÔV×aÒaÑcÔcÑdÔdˆØ˜Š|ˆ|ˆ|Ð^¸%Ð^Ð^Ð^‰|Œ|ˆ|Ø˜Š|ˆ|ˆ|Ð^¸%Ð^Ð^Ð^‰|Œ|ˆ|ˆ|ˆ|r   c                ó¨   — t          t          dz                       d¬¦  «                             ¦   «         ¦  «        }|dk    sJ d|› d�¦   «         ‚dS )z+combat_skills.js: strictly <= 500 hard cap.rd   r   r   r“   zcombat_skills.js has r”   Nr•   r˜   s     r   Útest_combat_skills_line_capsz4TestCodeHygieneLineCaps.test_combat_skills_line_caps  sU   € å•ZÐ"4Ñ4×?Ò?ÈÐ?ÑQÔQ×\Ò\Ñ^Ô^Ñ_Ô_ˆØ˜Š|ˆ|ˆ|ÐY°UÐYÐYÐY‰|Œ|ˆ|ˆ|ˆ|r   c                óÐ   — t          t          dz                       d¬¦  «                             ¦   «         ¦  «        }|dk    sJ d|› d�¦   «         ‚|dk    sJ d|› d�¦   «         ‚d	S )
z5canvas_renderer.js: <= 350 soft cap, <= 500 hard cap.rq   r   r   r‘   zcanvas_renderer.js has r’   r“   r”   Nr•   r˜   s     r   Útest_canvas_renderer_line_capsz6TestCodeHygieneLineCaps.test_canvas_renderer_line_caps  st   € å•ZÐ"6Ñ6×AÒAÈ7ÐAÑSÔS×^Ò^Ñ`Ô`ÑaÔaˆØ˜Š|ˆ|ˆ|Ð[°uÐ[Ð[Ð[‰|Œ|ˆ|Ø˜Š|ˆ|ˆ|Ð[°uÐ[Ð[Ð[‰|Œ|ˆ|ˆ|ˆ|r   c                óÐ   — t          t          dz                       d¬¦  «                             ¦   «         ¦  «        }|dk    sJ d|› d�¦   «         ‚|dk    sJ d|› d�¦   «         ‚d	S )
z;weapon_swing_catalog.js: <= 700 soft cap, <= 1000 hard cap.rV   r   r   i¼  zweapon_swing_catalog.js has z lines (exceeds 700 soft cap)iè  z lines (exceeds 1000 hard cap)N)r–   rW   r   r—   r˜   s     r   Ú#test_weapon_swing_catalog_line_capsz;TestCodeHygieneLineCaps.test_weapon_swing_catalog_line_caps  st   € å•XÐ 9Ñ9×DÒDÈgÐDÑVÔV×aÒaÑcÔcÑdÔdˆØ˜Š|ˆ|ˆ|Ð`¸EÐ`Ð`Ð`‰|Œ|ˆ|Ø˜Š}ˆ}ˆ}Ðb¸UÐbÐbÐb‰}Œ}ˆ}ˆ}ˆ}r   Nr5   )	r6   r7   r8   r9   rš   rœ   rž   r    r¢   r<   r   r   r�   r�   ÷   s”   € € € € € ØVÐVð]ð ]ð ]ð ]ð_ð _ð _ð _ðZð Zð Zð Zð
\ð \ð \ð \ðcð cð cð cð cð cr   r�   )r9   Ú
__future__r   rƒ   ÚmathÚosÚrer~   Úpathlibr   r:   Ú__file__ÚresolveÚparentr€   Ú
WEBAPP_DIRr   rW   r   r>   rT   rb   ro   r�   r<   r   r   ú<module>r¬      sÌ  ðð	ð 	ð #Ð "Ð "Ð "Ð "Ð "Ø €€€Ø €€€Ø 	€	€	€	Ø 	€	€	€	Ø Ð Ð Ð Ø Ð Ð Ð Ð Ð Ø €€€àˆt�H‰~Œ~×%Ò%Ñ'Ô'Ô.Ô5Ô<€Ø˜HÑ$ xÑ/€
Ø˜$Ñ Ñ)€
Ø˜Ñ˜vÑ%€ð..ð ..ð ..ð ..ð ..ñ ..ô ..ð ..ðb1Wð 1Wð 1Wð 1Wð 1Wñ 1Wô 1Wð 1Wðh<ð <ð <ð <ð <ñ <ô <ð <ð>7ð 7ð 7ð 7ð 7ñ 7ô 7ð 7ð>63ð 63ð 63ð 63ð 63ñ 63ô 63ð 63ðrcð cð cð cð cñ cô cð cð cð cr   