§
    CÍ½jç9  ã                  óè   — d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlZddlmZ  ee¦  «                             ¦   «         j        j        j        Zdd	„Z G d
„ d¦  «        Z G d„ d¦  «        ZdS )a  
Empirical Adversarial Challenge Suite for Milestone 2: Character Animation & Visceral Combat Feel Engine.
Author: challenger_m2_1_gen3
Roles: critic, specialist

This suite stress-tests assumptions and empirically reproduces bugs in:
1. Kinetic state machine phase synchronization across frame rates (15-240 FPS) and attack speeds (0.5-2.0x).
2. Action cancellation and clip reset in AnimationEngine.cancelAction.
3. Hit-stop timer decay in CombatFeelEngine.update.
4. Directional screen shake offset retrieval via CombatFeelEngine.getShakeOffset.
5. Hit-stop frame micro-freeze duration and DamageNumberPool zero-allocation ring buffer.
é    )ÚannotationsN)ÚPathÚcodeÚstrÚreturnÚdictc                ó¶  — d| › d�}t          j        ddd|gt          t          ¦  «        dd¬¦  «        }|j        dk    r't          d	|j        › d
|j        › d|j        › �¦  «        ‚d„ |j                             ¦   «          	                    ¦   «         D ¦   «         }d„ |D ¦   «         }|st          d|j        › �¦  «        ‚t          j        |d         ¦  «        S )z8Executes a JS snippet in Node.js and parses JSON stdout.zR
    globalThis.window = globalThis;
    (async () => {
        try {
            zn
        } catch (err) {
            console.error(err);
            process.exit(1);
        }
    })();
    Únodez--input-type=modulez-eT)ÚcwdÚcapture_outputÚtextr   zNode execution failed (code z):
STDOUT: z	
STDERR: c                ó^   — g | ]*}|                      ¦   «         ¯|                      ¦   «         ‘Œ+S © )Ústrip)Ú.0Úlines     úBC:\Projects\FreeExile\tests\unit\test_challenger_m2_adversarial.pyú
<listcomp>z$run_node_snippet.<locals>.<listcomp>-   s-   € ÐVÐVÐV˜dÈÏÊÉÌÐVˆT�ZŠZ‰\Œ\ÐVÐVÐVó    c                óf   — g | ].}|                      d ¦  «        ¯|                     d¦  «        ¯,|‘Œ/S )Ú{Ú})Ú
startswithÚendswith)r   Úls     r   r   z$run_node_snippet.<locals>.<listcomp>.   s8   € ÐLÐLÐL˜ a§l¢l°3Ñ&7Ô&7ÐL¸A¿JºJÀs¹O¼OÐL�!ÐLÐLÐLr   zNo JSON returned by script:
éÿÿÿÿ)Ú
subprocessÚrunr   ÚPROJECT_ROOTÚ
returncodeÚRuntimeErrorÚstdoutÚstderrr   Ú
splitlinesÚ
ValueErrorÚjsonÚloads)r   ÚrunnerÚresÚlinesÚ
json_liness        r   Úrun_node_snippetr,      s	  € ð
ð ð	
ð 
ð 
€Fõ Œ.Ø	Ð&¨¨fÐ5Ý•ÑÔØØð	ñ ô €Cð „~˜ÒÐÝÐx¸#¼.ÐxÐxÐVYÔV`ÐxÐxÐloÔlvÐxÐxÑyÔyÐyàVÐV c¤j×&6Ò&6Ñ&8Ô&8×&CÒ&CÑ&EÔ&EÐVÑVÔV€EØLÐL˜UÐLÑLÔL€JØð GÝÐE¸¼ÐEÐEÑFÔFÐFÝŒ:�j ”nÑ%Ô%Ð%r   c                  óô   — e Zd ZdZej                             dg d¢¦  «        ej                             dg d¢¦  «        ej                             dg d¢¦  «        dd„¦   «         ¦   «         ¦   «         Zdd„ZdS )Ú"TestAdversarialKineticStateMachinezNStress-tests 5-phase kinetic machine transitions across FPS and attack speeds.zfps,dt))é   g±?)é   g¡?)é<   g‘?)éx   g�?)éð   gq?Úaction_speed)g      à?g      ð?g      ø?g       @Úarchetype_clip)Úattack_slashÚattack_thrustÚattack_slamÚattack_boltÚfpsÚintÚdtÚfloatr   r   ÚNonec                óV  — d|› d|› d|› d|› d|› d|› d�}t          |¦  «        }|d         }d	}||k    }	|	sŠt          j        d
|	fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }
t          j        d|d         › �¦  «        dz   d|
iz  }t          t          j        |¦  «        ¦  «        ‚dx}x}	}|d         }d	}||k    }	|	sŠt          j        d
|	fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }
t          j        d|d         › �¦  «        dz   d|
iz  }t          t          j        |¦  «        ¦  «        ‚dx}x}	}|d         }d}||u }	|	sŠt          j        d|	fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }
t          j        d|› d|› d|› �¦  «        dz   d|
iz  }t          t          j        |¦  «        ¦  «        ‚dx}x}	}|d         }d}||u }	|	sŠt          j        d|	fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }
t          j        d|› d|› d|› �¦  «        dz   d|
iz  }t          t          j        |¦  «        ¦  «        ‚dx}x}	}dS )z\Verifies full lifecycle transitions: idle -> run -> wind_up -> (impact) -> recovery -> idle.a¹  
        await import('./client/webapp/js/engine/animation_engine.js');
        const { createAnimationState, updateAnimation, playAction, KINETIC_PHASES } = window.AnimationEngine;
        
        const state = createAnimationState('hero');
        const recordedPhases = [];
        
        // 1. Initial state
        recordedPhases.push(state.kineticPhase);
        
        // 2. Transition to Run
        updateAnimation(state, 3.2, zƒ);
        recordedPhases.push(state.kineticPhase);
        
        // 3. Stop moving -> Idle
        updateAnimation(state, 0.0, zœ);
        recordedPhases.push(state.kineticPhase);
        
        // 4. Trigger attack clip
        let hitTriggered = false;
        playAction(state, 'z', {
            actionSpeed: aB  ,
            onHit: () => { hitTriggered = true; }
        });
        recordedPhases.push(state.kineticPhase);
        
        // 5. Advance frames through the full action
        let iterations = 0;
        let visitedImpact = false;
        let visitedRecovery = false;
        
        while (state.currentClip === 'zA' && iterations < 300) {
            updateAnimation(state, 0.0, aœ  );
            if (state.kineticPhase === 'impact') visitedImpact = true;
            if (state.kineticPhase === 'recovery') visitedRecovery = true;
            recordedPhases.push(state.kineticPhase);
            iterations++;
        }
        
        // Final state should auto-recover to idle
        recordedPhases.push(state.kineticPhase);
        
        console.log(JSON.stringify({
            finalClip: state.currentClip,
            finalPhase: state.kineticPhase,
            hitTriggered: hitTriggered,
            visitedImpact: visitedImpact,
            visitedRecovery: visitedRecovery,
            totalIterations: iterations
        }));
        Ú	finalClipÚidle©ú==©z%(py1)s == %(py4)s©Úpy1Úpy4z$Expected auto-recovery to idle, got ú
>assert %(py6)sÚpy6NÚ
finalPhasezExpected final phase idle, got ÚhitTriggeredT©Úis©z%(py1)s is %(py4)sz*Attack hit callback failed to trigger for z at z FPS, speed ÚvisitedRecoveryz!Recovery phase never reached for ©r,   Ú
@pytest_arÚ_call_reprcompareÚ	_safereprÚ_format_assertmsgÚAssertionErrorÚ_format_explanation)Úselfr:   r<   r4   r5   Újs_coder)   Ú@py_assert0Ú@py_assert3Ú@py_assert2Ú@py_format5Ú@py_format7s               r   Ú*test_kinetic_transition_phases_progressionzMTestAdversarialKineticStateMachine.test_kinetic_transition_phases_progression7   s0  € ð2ð &(ð2ð 2ð &(ð2ð 2ð( +ð)2ð 2ð* 'ð+2ð 2ð> (6ð?2ð 2ð@ *,ðA2ð 2ð 2ˆõf ˜wÑ'Ô'ˆØ�;ÔÐd 6ÐdÐ 6Ò)ÐdÐdÐdÕdÔdÐdÐdÐdÐdÐ 6ÐdÑdÔdÕdÔdÐÑdÔdÕdÔd 6ÑdÔdÐdÐdÑdÐdÕdÔdÐ+dÐRUÐVaÔRbÐ+dÐ+dÑdÔdÐdÑdÐdÐdÐdÑdÐdÕdÕdÔdÐdÑdÔdÑdÔdÐdÐdÐdÐdÐdÐdÐdØ�<Ô Ða FÐaÐ  FÒ*ÐaÐaÐaÕaÔaÐaÐaÐaÐaÐ  FÐaÑaÔaÕaÔaÐ ÑaÔaÕaÔa FÑaÔaÐaÐaÑaÐaÕaÔaÐ,aÈcÐR^ÔN_Ð,aÐ,aÑaÔaÐaÑaÐaÐaÐaÑaÐaÕaÕaÔaÐaÑaÔaÑaÔaÐaÐaÐaÐaÐaÐaÐaØ�>Ô"ð  	M dð  	MÐ" dÐ*ð  	Mð  	Mð  	Mõ  	Mô  	Mð  	Mð  	Mð  	Mð  	MÐ" dð  	Mñ  	Mô  	Mõ  	Mô  	MÐ"ñ  	Mô  	Mõ  	Mô  	M dñ  	Mô  	Mð  	Mð  	Mñ  	Mð  	Mõ  	Mô  	Mð  -MÐYgð  -Mð  -MÐmpð  -Mð  -Mð  Kð  -Mð  -Mñ  	Mô  	Mð  	Mñ  	Mð  	Mð  	Mð  	Mñ  	Mð  	Mõ  	Mõ  	Mô  	Mð  	Mñ  	Mô  	Mñ  	Mô  	Mð  	Mð  	Mð  	Mð  	Mð  	Mð  	Mð  	MØÐ$Ô%ð  	G¨ð  	GÐ%¨Ð-ð  	Gð  	Gð  	Gõ  	Gô  	Gð  	Gð  	Gð  	Gð  	GÐ%¨ð  	Gñ  	Gô  	Gõ  	Gô  	GÐ%ñ  	Gô  	Gõ  	Gô  	G¨ñ  	Gô  	Gð  	Gð  	Gñ  	Gð  	Gõ  	Gô  	Gð  0GÐSað  0Gð  0GÐgjð  0Gð  0Gð  yEð  0Gð  0Gñ  	Gô  	Gð  	Gñ  	Gð  	Gð  	Gð  	Gñ  	Gð  	Gõ  	Gõ  	Gô  	Gð  	Gñ  	Gô  	Gñ  	Gô  	Gð  	Gð  	Gð  	Gð  	Gð  	Gð  	Gð  	Gð  	Gð  	Gr   c                ón  — d}t          |¦  «        }|d         }d}||k    }|s‹t          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }t          j        d|d         › d�¦  «        d	z   d
|iz  }t          t          j        |¦  «        ¦  «        ‚dx}x}}dS )zÞ
        BUG REPRODUCTION:
        Verifies whether kineticPhase is synchronized with frameIndex in the SAME tick,
        rather than lagging 1 tick behind due to premature phase calculation before frame advance.
        a°  
        await import('./client/webapp/js/engine/animation_engine.js');
        const { createAnimationState, updateAnimation, playAction } = window.AnimationEngine;
        const state = createAnimationState('hero');
        
        // attack_slash has hit_frame = 2
        playAction(state, 'attack_slash', { actionSpeed: 1.0 });
        
        let desyncCount = 0;
        // Advance 20 ticks at 60 FPS
        for (let i = 0; i < 20; i++) {
            updateAnimation(state, 0.0, 0.01667);
            if (state.currentClip === 'attack_slash') {
                if (state.frameIndex < 2 && state.kineticPhase !== 'wind_up') desyncCount++;
                if (state.frameIndex === 2 && state.kineticPhase !== 'impact') desyncCount++;
                if (state.frameIndex > 2 && state.kineticPhase !== 'recovery') desyncCount++;
            }
        }
        
        console.log(JSON.stringify({ desyncCount: desyncCount }));
        ÚdesyncCountr   rB   rD   rE   z	Detected z; de-synchronized ticks between frameIndex and kineticPhase!rH   rI   NrP   ©rW   rX   r)   rY   rZ   r[   r\   r]   s           r   Ú.test_kinetic_phase_frame_index_synchronizationzQTestAdversarialKineticStateMachine.test_kinetic_phase_frame_index_synchronization{   s—  € ðˆõ* ˜wÑ'Ô'ˆØ�=Ô!ð  	D Qð  	DÐ! QÒ&ð  	Dð  	Dð  	Dõ  	Dô  	Dð  	Dð  	Dð  	Dð  	DÐ! Qð  	Dñ  	Dô  	Dõ  	Dô  	DÐ!ñ  	Dô  	Dõ  	Dô  	D Qñ  	Dô  	Dð  	Dð  	Dñ  	Dð  	Dõ  	Dô  	Dð  )D°C¸Ô4Fð  )Dð  )Dð  )Dñ  	Dô  	Dð  	Dñ  	Dð  	Dð  	Dð  	Dñ  	Dð  	Dõ  	Dõ  	Dô  	Dð  	Dñ  	Dô  	Dñ  	Dô  	Dð  	Dð  	Dð  	Dð  	Dð  	Dð  	Dð  	Dð  	Dð  	Dr   N)
r:   r;   r<   r=   r4   r=   r5   r   r   r>   ©r   r>   )	Ú__name__Ú
__module__Ú__qualname__Ú__doc__ÚpytestÚmarkÚparametrizer^   rb   r   r   r   r.   r.   4   sÊ   € € € € € ØXÐXà„[×Ò˜Xð (ð (ð (ñ ô ð „[×Ò˜^Ð-AÐ-AÐ-AÑBÔBØ„[×ÒÐ-Ð/nÐ/nÐ/nÑoÔoð9Gð 9Gð 9Gñ pÔoñ CÔBñô ð9GðvDð Dð Dð Dð Dð Dr   r.   c                  ó€   — e Zd ZdZdd„Zdd„Zdd„Zej         	                    dg d¢¦  «        dd„¦   «         Z
dd„ZdS )ÚTestAdversarialCombatFeelEnginezCStress-tests hit-stop, camera shake, and damage number ring buffer.r   r>   c                óæ  — d}t          |¦  «        }|d         }d}||u }|s�t          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }t          j        d¦  «        dz   d	|iz  }t          t          j        |¦  «        ¦  «        ‚d
x}x}}g }|d         }d
}	||	u}
|
}|
r"d}|d         }||v }|}|rd}|d         }||v }|}|�syt          j        d|
fd||	f¦  «        t          j        |¦  «        t          j        |	¦  «        dœz  }dd|iz  }|                     |¦  «         |
rÂt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }|                     |¦  «         |r`t          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }|                     |¦  «         t          j        |d¦  «        i z  }t          j        d¦  «        dz   d|iz  }t          t          j        |¦  «        ¦  «        ‚d
x}x}x}x}
x}	x}x}x}x}x}}d
S ) a"  
        BUG REPRODUCTION:
        canvas_renderer.js calls window.combatFeelEngine.getShakeOffset?.().
        CombatFeelEngine computes shakeOffsetX and shakeOffsetY but must provide getShakeOffset()
        so the renderer can actually apply the directional camera shake matrix.
        ar  
        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 hasMethod = typeof cfe.getShakeOffset === 'function';
        const offset = hasMethod ? cfe.getShakeOffset() : null;
        
        console.log(JSON.stringify({
            hasMethod: hasMethod,
            offset: offset,
            hasOffsetX: typeof cfe.shakeOffsetX === 'number',
            hasOffsetY: typeof cfe.shakeOffsetY === 'number'
        }));
        Ú	hasMethodTrL   rN   rE   zqCombatFeelEngine.prototype.getShakeOffset is missing! Directional screen shake is dead code in canvas_renderer.jsrH   rI   NÚoffsetÚxÚy)úis not)z%(py3)s is not %(py6)s)Úpy3rI   z%(py8)sÚpy8)Úin)z%(py11)s in %(py14)s)Úpy11Úpy14z%(py16)sÚpy16)z%(py19)s in %(py22)s)Úpy19Úpy22z%(py24)sÚpy24r   z6getShakeOffset did not return {x, y} coordinate objectz
>assert %(py27)sÚpy27)	r,   rQ   rR   rS   rT   rU   rV   ÚappendÚ_format_boolop)rW   rX   r)   rY   rZ   r[   r\   r]   Ú@py_assert1Ú@py_assert5Ú@py_assert4Ú@py_assert10Ú@py_assert13Ú@py_assert12Ú@py_assert18Ú@py_assert21Ú@py_assert20Ú@py_format9Ú@py_format15Ú@py_format17Ú@py_format23Ú@py_format25Ú@py_format26Ú@py_format28s                           r   Ú$test_missing_get_shake_offset_defectzDTestAdversarialCombatFeelEngine.test_missing_get_shake_offset_defect�   s  € ðˆõ" ˜wÑ'Ô'ˆØ�;Ôð  	] 4ð  	]Ð 4Ð'ð  	]ð  	]ð  	]õ  	]ô  	]ð  	]ð  	]ð  	]ð  	]Ð 4ð  	]ñ  	]ô  	]õ  	]ô  	]Ðñ  	]ô  	]õ  	]ô  	] 4ñ  	]ô  	]ð  	]ð  	]ñ  	]ð  	]õ  	]ô  	]ð  *]ñ  	]ô  	]ð  	]ñ  	]ð  	]ð  	]ð  	]ñ  	]ð  	]õ  	]õ  	]ô  	]ð  	]ñ  	]ô  	]ñ  	]ô  	]ð  	]ð  	]ð  	]ð  	]ð  	]ð  	]ð  	]ð 	Uð  	Uˆs�8Œ}ð  	U Dð  	Uˆ} DÐ(ð  	Uð  	Uð  	Uð  	Uð  	U¨Sð  	U°C¸´Mð  	U¨S°MÐ-Að  	Uð  	Uð  	Uð  	Uð  	UÀcð  	UÈSÐQYÌ]ð  	UÀcÈ]ÐFZð  	Uð  	Uð  	Uð  	Uñ  	Uõ  	Uô  	Uð  	Uð  	Uð  	Uð  	Uˆ} Dð  	Uñ  	Uô  	Uõ  	Uô  	Uˆ}ñ  	Uô  	Uõ  	Uô  	U Dñ  	Uô  	Uð  	Uð  	Uñ  	Uð  	Uð  	Uð  	Uð  	Uð  	Uñ  	Uð  	Uð  	U÷  	Uò  	Uð  	Uñ  	Uô  	Uð  	Uð  	Uð  	Uõ  	Uô  	Uð  	Uð  	Uð  	Uð  	U¨S°Mð  	Uñ  	Uô  	Uõ  	Uô  	U¨Sñ  	Uô  	Uõ  	Uô  	U°Mñ  	Uô  	Uð  	Uð  	Uñ  	Uð  	Uð  	Uð  	Uð  	Uð  	Uñ  	Uð  	Uð  	U÷  	Uò  	Uð  	Uñ  	Uô  	Uð  	Uð  	Uð  	Uõ  	Uô  	Uð  	Uð  	Uð  	Uð  	UÀcÈ]ð  	Uñ  	Uô  	Uõ  	Uô  	UÀcñ  	Uô  	Uõ  	Uô  	UÈ]ñ  	Uô  	Uð  	Uð  	Uñ  	Uð  	Uð  	Uð  	Uð  	Uð  	Uñ  	Uð  	Uð  	U÷  	Uò  	Uð  	Uñ  	Uô  	Uð  	Uõ  	Uô  	Uð  	Uð  	Uñ  	Uô  	Uð  	Uñ  	Uð  	Uõ  	Uô  	Uð  ]Uñ  	Uô  	Uð  	Uñ  	Uð  	Uð  	Uð  	Uñ  	Uð  	Uõ  	Uõ  	Uô  	Uð  	Uñ  	Uô  	Uñ  	Uô  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Uð  	Ur   c                óú  — d}t          |¦  «        }|d         }d}||k    }|s‹t          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }t          j        d|d         › d�¦  «        d	z   d
|iz  }t          t          j        |¦  «        ¦  «        ‚dx}x}}|d         }d}||k    }|s‹t          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }t          j        d|d         › d�¦  «        d	z   d
|iz  }t          t          j        |¦  «        ¦  «        ‚dx}x}}|d         }d}||k    }|s‹t          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }t          j        d|d         › d�¦  «        d	z   d
|iz  }t          t          j        |¦  «        ¦  «        ‚dx}x}}dS )zÉ
        BUG REPRODUCTION:
        AnimationEngine.cancelAction must reset currentClip to 'idle', otherwise
        the subsequent updateAnimation call re-computes kineticPhase back to attack.
        a  
        await import('./client/webapp/js/engine/animation_engine.js');
        const { createAnimationState, updateAnimation, playAction, cancelAction } = window.AnimationEngine;
        const state = createAnimationState('hero');
        
        playAction(state, 'attack_slash');
        cancelAction(state);
        
        const clipAfterCancel = state.currentClip;
        
        // Tick updateAnimation after cancel
        updateAnimation(state, 0.0, 0.016);
        const clipAfterTick = state.currentClip;
        const phaseAfterTick = state.kineticPhase;
        
        console.log(JSON.stringify({
            clipAfterCancel: clipAfterCancel,
            clipAfterTick: clipAfterTick,
            phaseAfterTick: phaseAfterTick
        }));
        ÚclipAfterCancelrA   rB   rD   rE   z"cancelAction left currentClip as 'z', expected 'idle'rH   rI   NÚclipAfterTickz/updateAnimation after cancel reverted clip to 'ú'ÚphaseAfterTickz0updateAnimation after cancel reverted phase to 'rP   ra   s           r   Ú-test_cancel_action_resets_current_clip_defectzMTestAdversarialCombatFeelEngine.test_cancel_action_resets_current_clip_defect¹   sÝ  € ðˆõ* ˜wÑ'Ô'ˆØÐ$Ô%ð  	A¨ð  	AÐ%¨Ò/ð  	Að  	Að  	Aõ  	Aô  	Að  	Að  	Að  	Að  	AÐ%¨ð  	Añ  	Aô  	Aõ  	Aô  	AÐ%ñ  	Aô  	Aõ  	Aô  	A¨ñ  	Aô  	Að  	Að  	Añ  	Að  	Aõ  	Aô  	Að  2AÐVYÐZkÔVlð  2Að  2Að  2Añ  	Aô  	Að  	Añ  	Að  	Að  	Að  	Añ  	Að  	Aõ  	Aõ  	Aô  	Að  	Añ  	Aô  	Añ  	Aô  	Að  	Að  	Að  	Að  	Að  	Að  	Að  	AØ�?Ô#Ðx vÐxÐ# vÒ-ÐxÐxÐxÕxÔxÐxÐxÐxÐxÐ# vÐxÑxÔxÕxÔxÐ#ÑxÔxÕxÔx vÑxÔxÐxÐxÑxÐxÕxÔxÐ/xÐadÐetÔauÐ/xÐ/xÐ/xÑxÔxÐxÑxÐxÐxÐxÑxÐxÕxÕxÔxÐxÑxÔxÑxÔxÐxÐxÐxÐxÐxÐxÐxØÐ#Ô$ð  	M¨ð  	MÐ$¨Ò.ð  	Mð  	Mð  	Mõ  	Mô  	Mð  	Mð  	Mð  	Mð  	MÐ$¨ð  	Mñ  	Mô  	Mõ  	Mô  	MÐ$ñ  	Mô  	Mõ  	Mô  	M¨ñ  	Mô  	Mð  	Mð  	Mñ  	Mð  	Mõ  	Mô  	Mð  1MÐcfÐgwÔcxð  1Mð  1Mð  1Mñ  	Mô  	Mð  	Mñ  	Mð  	Mð  	Mð  	Mñ  	Mð  	Mõ  	Mõ  	Mô  	Mð  	Mñ  	Mô  	Mñ  	Mô  	Mð  	Mð  	Mð  	Mð  	Mð  	Mð  	Mð  	Mð  	Mð  	Mr   c                ó   — d}t          |¦  «        }|d         }d}||k    }|s�t          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }t          j        d¦  «        dz   d	|iz  }t          t          j        |¦  «        ¦  «        ‚d
x}x}}|d         }d}||k    }|s‹t          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }t          j        d|d         › d�¦  «        dz   d	|iz  }t          t          j        |¦  «        ¦  «        ‚d
x}x}}d
S )zà
        BUG REPRODUCTION:
        CombatFeelEngine.update(dt) must decrement hitStopTimer so that the hit-stop pause
        resolves in the simulation loop without requiring an external manual isHitStopped caller.
        aY  
        const { CombatFeelEngine } = await import('./client/webapp/js/engine/combat_feel_engine.js');
        const cfe = new CombatFeelEngine();
        
        cfe.triggerHitStop(3, 0.05);
        const initialTimer = cfe.hitStopTimer;
        
        // Run update loop for 0.1s (longer than 0.05s hit-stop)
        for (let i = 0; i < 6; i++) {
            cfe.update(0.016);
        }
        const timerAfterUpdates = cfe.hitStopTimer;
        
        console.log(JSON.stringify({
            initialTimer: initialTimer,
            timerAfterUpdates: timerAfterUpdates
        }));
        ÚinitialTimerr   )ú>)z%(py1)s > %(py4)srE   z hitStopTimer was not initializedrH   rI   NÚtimerAfterUpdatesrB   rD   z2hitStopTimer failed to decay in update(dt)! Still z after 0.1srP   ra   s           r   Ú0test_hit_stop_timer_decays_in_update_loop_defectzPTestAdversarialCombatFeelEngine.test_hit_stop_timer_decays_in_update_loop_defectÙ   sU  € ðˆõ$ ˜wÑ'Ô'ˆØ�>Ô"ÐJ QÐJÐ" QÒ&ÐJÐJÐJÕJÔJÐJÐJÐJÐJÐ" QÐJÑJÔJÕJÔJÐ"ÑJÔJÕJÔJ QÑJÔJÐJÐJÑJÐJÕJÔJÐ(JÑJÔJÐJÑJÐJÐJÐJÑJÐJÕJÕJÔJÐJÑJÔJÑJÔJÐJÐJÐJÐJÐJÐJÐJØÐ&Ô'ð  	I¨1ð  	IÐ'¨1Ò,ð  	Ið  	Ið  	Iõ  	Iô  	Ið  	Ið  	Ið  	Ið  	IÐ'¨1ð  	Iñ  	Iô  	Iõ  	Iô  	IÐ'ñ  	Iô  	Iõ  	Iô  	I¨1ñ  	Iô  	Ið  	Ið  	Iñ  	Ið  	Iõ  	Iô  	Ið  /IÐcfÐgzÔc{ð  /Ið  /Ið  /Iñ  	Iô  	Ið  	Iñ  	Ið  	Ið  	Ið  	Iñ  	Ið  	Iõ  	Iõ  	Iô  	Ið  	Iñ  	Iô  	Iñ  	Iô  	Ið  	Ið  	Ið  	Ið  	Ið  	Ið  	Ið  	Ið  	Ið  	Ir   z0archetype_name,expected_frames,expected_duration))ÚSLASHé   gš™™™™™©?)ÚTHRUSTé   çìQ¸…ë¡?)ÚSLAMé   çL7‰A`å°?)ÚBOLTrž   rŸ   Úarchetype_namer   Úexpected_framesr;   Úexpected_durationr=   c                ó¦  — d|› d�}t          |¦  «        }|d         }|d         }||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }	dd	|	iz  }
t	          t          j        |
¦  «        ¦  «        ‚d
x}x}}|d         }|d         }||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }	dd	|	iz  }
t	          t          j        |
¦  «        ¦  «        ‚d
x}x}}d}|d         }||k    }d}||k    }|r|s�t          j        d||fd|||f¦  «        t          j        |¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d
x}x}x}x}}d
S )zGVerifies attacker and target both halt frame advancement for 33ms-66ms.aÜ  
        await import('./client/webapp/js/engine/animation_engine.js');
        const { createAnimationState, updateAnimation, playAction } = window.AnimationEngine;
        const { CombatFeelEngine } = await import('./client/webapp/js/engine/combat_feel_engine.js');
        const { getWeaponSwingArchetype } = await import('./client/webapp/js/data/weapon_swing_catalog.js');
        
        const cfe = new CombatFeelEngine();
        const arch = getWeaponSwingArchetype('a¬  ');
        
        const attackerAnim = createAnimationState('hero');
        playAction(attackerAnim, 'attack_slash');
        attackerAnim.frameIndex = 2;
        attackerAnim.frameTime = 2.0;
        
        const targetAnim = createAnimationState('mob_skeleton_warrior');
        playAction(targetAnim, 'run');
        targetAnim.frameIndex = 1;
        targetAnim.frameTime = 1.0;
        
        const attacker = { anim: attackerAnim };
        const target = { anim: targetAnim, hitStopTimer: 0 };
        
        cfe.triggerHitStop(arch.hitStopFrames, arch.hitStopDuration, attacker, target);
        
        const dt = 0.016;
        let attackerFrozenTicks = 0;
        let targetFrozenTicks = 0;
        
        for (let tick = 0; tick < 10; tick++) {
            const prevAttackerFrame = attackerAnim.frameTime;
            const prevTargetFrame = targetAnim.frameTime;
            
            updateAnimation(attackerAnim, 0, dt);
            updateAnimation(targetAnim, 1.8, dt);
            
            if (attackerAnim.frameTime === prevAttackerFrame) attackerFrozenTicks++;
            if (targetAnim.frameTime === prevTargetFrame) targetFrozenTicks++;
        }
        
        console.log(JSON.stringify({
            expectedFrames: arch.hitStopFrames,
            attackerFrozenTicks: attackerFrozenTicks,
            targetFrozenTicks: targetFrozenTicks,
            duration: arch.hitStopDuration
        }));
        ÚattackerFrozenTicksÚexpectedFrames)ú>=)z%(py1)s >= %(py4)srE   úassert %(py6)srI   NÚtargetFrozenTicksgL7‰A`å ?Údurationr¢   )ú<=r®   )z%(py1)s <= %(py5)sz%(py5)s <= %(py7)s)rF   Úpy5Úpy7zassert %(py9)sÚpy9©r,   rQ   rR   rS   rU   rV   )rW   r¤   r¥   r¦   rX   r)   rY   rZ   r[   r\   r]   r�   Ú@py_assert6Ú@py_format8Ú@py_format10s                  r   Ú.test_attacker_and_target_frame_freeze_durationzNTestAdversarialCombatFeelEngine.test_attacker_and_target_frame_freeze_durationõ   sr  € ð-ð 0>ð-ð -ð -ˆõ\ ˜wÑ'Ô'ˆØÐ(Ô)ÐB¨SÐ1AÔ-BÐBÐ)Ð-BÒBÐBÐBÐBÕBÔBÐBÐBÐBÐBÐ)Ð-BÐBÑBÔBÕBÔBÐ)ÑBÔBÕBÔBÐ-BÑBÔBÐBÐBÑBÐBÐBÐBÐBÐBÑBÐBÕBÕBÔBÐBÑBÔBÑBÔBÐBÐBÐBÐBÐBÐBÐBØÐ&Ô'Ð@¨3Ð/?Ô+@Ð@Ð'Ð+@Ò@Ð@Ð@Ð@Õ@Ô@Ð@Ð@Ð@Ð@Ð'Ð+@Ð@Ñ@Ô@Õ@Ô@Ð'Ñ@Ô@Õ@Ô@Ð+@Ñ@Ô@Ð@Ð@Ñ@Ð@Ð@Ð@Ð@Ð@Ñ@Ð@Õ@Õ@Ô@Ð@Ñ@Ô@Ñ@Ô@Ð@Ð@Ð@Ð@Ð@Ð@Ð@ØÐ0˜˜JœÐ0ˆu˜Ò0Ð0¨5Ð0˜¨5Ò0Ð0Ð0Ð0Ð0Ð0Õ0Ô0Ð0Ð0Ð0Ð0Ð0ˆu˜¨5Ð0Ñ0Ô0Õ0Ô0ˆuÑ0Ô0Õ0Ô0˜Ñ0Ô0Õ0Ô0¨5Ñ0Ô0Ð0Ð0Ñ0Ð0Ð0Ð0Ð0Ð0Ñ0Ð0Õ0Õ0Ô0Ð0Ñ0Ô0Ñ0Ô0Ð0Ð0Ð0Ð0Ð0Ð0Ð0Ð0Ð0Ð0Ð0Ð0Ð0r   c                óL  — d}t          |¦  «        }|d         }d}||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d	x}x}}|d
         }d}||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d	x}x}}|d         }d}||k    }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d	x}x}}|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d	x}x}}|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d	x}x}}|d         }d}||u }|slt          j        d|fd||f¦  «        t          j        |¦  «        t          j        |¦  «        dœz  }dd|iz  }t	          t          j        |¦  «        ¦  «        ‚d	x}x}}d	S )zXSpawns 500 damage numbers; verifies strict 64-slot capacity, zero allocations, no leaks.al  
        const { CombatFeelEngine } = await import('./client/webapp/js/engine/combat_feel_engine.js');
        const cfe = new CombatFeelEngine();
        
        const initialCapacity = cfe.damagePool.length;
        
        for (let i = 0; i < 256; i++) {
            cfe.spawnDamageNumber(`-${i * 10}`, 100 + i, 200, i % 5 === 0 ? '#ef4444' : '#ffffff', i % 5 === 0);
        }
        
        const postSpawnCapacity = cfe.damagePool.length;
        const poolHead = cfe.poolHead;
        
        const allActive = cfe.damagePool.every(slot => slot.active === true);
        const allValidScales = cfe.damagePool.every(slot => typeof slot.scale === 'number' && !isNaN(slot.scale));
        
        cfe.update(0.1);
        const updatedValidScales = cfe.damagePool.every(slot => slot.scale >= 1.0);
        
        console.log(JSON.stringify({
            initialCapacity: initialCapacity,
            postSpawnCapacity: postSpawnCapacity,
            poolHead: poolHead,
            allActive: allActive,
            allValidScales: allValidScales,
            updatedValidScales: updatedValidScales
        }));
        ÚinitialCapacityé@   rB   rD   rE   r«   rI   NÚpostSpawnCapacityÚpoolHeadr   Ú	allActiveTrL   rN   ÚallValidScalesÚupdatedValidScalesr²   ra   s           r   Ú6test_damage_number_pool_wraparound_and_zero_allocationzVTestAdversarialCombatFeelEngine.test_damage_number_pool_wraparound_and_zero_allocation0  s  € ðˆõ8 ˜wÑ'Ô'ˆØÐ$Ô%Ð+¨Ð+Ð%¨Ò+Ð+Ð+Ð+Õ+Ô+Ð+Ð+Ð+Ð+Ð%¨Ð+Ñ+Ô+Õ+Ô+Ð%Ñ+Ô+Õ+Ô+¨Ñ+Ô+Ð+Ð+Ñ+Ð+Ð+Ð+Ð+Ð+Ñ+Ð+Õ+Õ+Ô+Ð+Ñ+Ô+Ñ+Ô+Ð+Ð+Ð+Ð+Ð+Ð+Ð+ØÐ&Ô'Ð-¨2Ð-Ð'¨2Ò-Ð-Ð-Ð-Õ-Ô-Ð-Ð-Ð-Ð-Ð'¨2Ð-Ñ-Ô-Õ-Ô-Ð'Ñ-Ô-Õ-Ô-¨2Ñ-Ô-Ð-Ð-Ñ-Ð-Ð-Ð-Ð-Ð-Ñ-Ð-Õ-Õ-Ô-Ð-Ñ-Ô-Ñ-Ô-Ð-Ð-Ð-Ð-Ð-Ð-Ð-Ø�:ŒÐ# !Ð#ˆ !Ò#Ð#Ð#Ð#Õ#Ô#Ð#Ð#Ð#Ð#ˆ !Ð#Ñ#Ô#Õ#Ô#ˆÑ#Ô#Õ#Ô# !Ñ#Ô#Ð#Ð#Ñ#Ð#Ð#Ð#Ð#Ð#Ñ#Ð#Õ#Õ#Ô#Ð#Ñ#Ô#Ñ#Ô#Ð#Ð#Ð#Ð#Ð#Ð#Ð#Ø�;ÔÐ' 4Ð'Ð 4Ð'Ð'Ð'Ð'Õ'Ô'Ð'Ð'Ð'Ð'Ð 4Ð'Ñ'Ô'Õ'Ô'ÐÑ'Ô'Õ'Ô' 4Ñ'Ô'Ð'Ð'Ñ'Ð'Ð'Ð'Ð'Ð'Ñ'Ð'Õ'Õ'Ô'Ð'Ñ'Ô'Ñ'Ô'Ð'Ð'Ð'Ð'Ð'Ð'Ð'ØÐ#Ô$Ð,¨Ð,Ð$¨Ð,Ð,Ð,Ð,Õ,Ô,Ð,Ð,Ð,Ð,Ð$¨Ð,Ñ,Ô,Õ,Ô,Ð$Ñ,Ô,Õ,Ô,¨Ñ,Ô,Ð,Ð,Ñ,Ð,Ð,Ð,Ð,Ð,Ñ,Ð,Õ,Õ,Ô,Ð,Ñ,Ô,Ñ,Ô,Ð,Ð,Ð,Ð,Ð,Ð,Ð,ØÐ'Ô(Ð0¨DÐ0Ð(¨DÐ0Ð0Ð0Ð0Õ0Ô0Ð0Ð0Ð0Ð0Ð(¨DÐ0Ñ0Ô0Õ0Ô0Ð(Ñ0Ô0Õ0Ô0¨DÑ0Ô0Ð0Ð0Ñ0Ð0Ð0Ð0Ð0Ð0Ñ0Ð0Õ0Õ0Ô0Ð0Ñ0Ô0Ñ0Ô0Ð0Ð0Ð0Ð0Ð0Ð0Ð0Ð0Ð0r   Nrc   )r¤   r   r¥   r;   r¦   r=   r   r>   )rd   re   rf   rg   r�   r•   rš   rh   ri   rj   r¶   r¿   r   r   r   rl   rl   š   sÂ   € € € € € ØMÐMðUð Uð Uð Uð8Mð Mð Mð Mð@Ið Ið Ið Ið8 „[×ÒÐOð Rð Rð Rñ ô ð31ð 31ð 31ñô ð31ðj$1ð $1ð $1ð $1ð $1ð $1r   rl   )r   r   r   r   )rg   Ú
__future__r   ÚbuiltinsÚ@py_builtinsÚ_pytest.assertion.rewriteÚ	assertionÚrewriterQ   r&   r   rh   Úpathlibr   Ú__file__ÚresolveÚparentr   r,   r.   rl   r   r   r   ú<module>rÊ      s  ððð ð #Ð "Ð "Ð "Ð "Ð "Ø  € € € € € € € € € € € € €€€€Ø Ð Ð Ð Ø €€€Ø Ð Ð Ð Ð Ð àˆt�H‰~Œ~×%Ò%Ñ'Ô'Ô.Ô5Ô<€ð&ð &ð &ð &ð:cDð cDð cDð cDð cDñ cDô cDð cDðLz1ð z1ð z1ð z1ð z1ñ z1ô z1ð z1ð z1ð z1r   