§
    ëM¾jí,  ã                  ó  — 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mZ  ee	¦  «         
                    ¦   «         j        j        j        Zedz  Z ee¦  «        ej        vr$ej                             d ee¦  «        ¦  «          ee¦  «        ej        vr$ej                             d ee¦  «        ¦  «         ddlmZ ddlmZmZmZmZ  G d„ d	ej        ¦  «        Z G d
„ dej        ¦  «        Zedk    r ej        ¦   «          dS dS )a  
Empirical Challenger Test Suite for Milestone 2 (R2: Waypoint Safe Radius & Healing).

Empirically challenges:
1. Exact boundary at distance 7.9 (inside) vs 8.1 (outside) across 8 radial angles (Server & Client).
2. Exact boundary at distance 8.0 (boundary included).
3. Target Dummy spawn and presence inside safe radius at (0, 0) (Server & Client).
4. Player health regeneration inside safe radius:
   - Low HP (50/100) -> 2% maxHp/sec regen.
   - Near Max HP (99/100) -> Clamped to maxHp.
   - At Max HP (100/100) -> No change / no overflow.
   - Dead player (0/100) -> No resurrection.
   - Outside safe radius (8.1, 0) -> No healing.
5. Monster leash state transition when player crosses safe radius boundary:
   - Aggro dropped (hasAggro -> False).
   - Leash engaged (isLeashing -> True, isInvulnerable -> True).
   - Attack execution blocked (0 damage inside safe radius).
   - Latch persistence: stays leashing even if player steps back outside until origin reached.
   - Origin arrival: reset isLeashing -> False, isInvulnerable -> False.
é    )ÚannotationsN)ÚPathÚserver)Ú
ZoneEngine)ÚZoneDefinitionÚZoneTypeÚZoneEnvironmentÚWaypointc                  ó2   — e Zd Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ ZdS )Ú'TestChallengerWaypointBoundaryEmpiricalc                ó  — t          ¦   «         | _        d| _        | j                             t	          | j        dt
          j        t          j        dddddd| j        dddt          dd| j        ddd	¬
¦  «        i¬¦  «        ¦  «         d S )NÚzone_empirical_wp_testu,   Báº£n Ä�á»“ Thá»­ Nghiá»‡m Tá»�a Ä�á»™ Gá»‘cé   éd   g     @Ÿ@ç        Ú	wp_originu   Trá»¥ Ä�Ã¡ Gá»‘cç       @)Úwaypoint_idÚnameÚzone_idÚxÚyÚsafe_radius)r   r   Ú	zone_typeÚenvironmentÚ	min_levelÚmax_playersÚbounds_widthÚbounds_heightÚdefault_spawn_xÚdefault_spawn_yÚrespawn_zone_idÚ	respawn_xÚ	respawn_yÚ	waypoints)
r   ÚengineÚtest_zone_idÚadd_zoner   r   Ú
OPEN_WORLDr	   ÚNORMALr
   )Úselfs    úIC:\Projects\FreeExile\tests\unit\test_challenger_m2_waypoint_empirical.pyÚsetUpz-TestChallengerWaypointBoundaryEmpirical.setUp)   s¬   € Ý ‘l”lˆŒà4ˆÔØŒ×ÒÝØÔ)ØCÝ"Ô-Ý+Ô2ØØØ#Ø$Ø #Ø #Ø $Ô 1ØØà¥Ø$/Ø/Ø $Ô 1ØØØ$'ð"ñ "ô "ð	ðñ ô ñ	
ô 	
ð 	
ð 	
ð 	
ó    c                ó  — | j                              | j        dd¦  «        }|                      |d¦  «         | j                              | j        ddd¬¦  «        \  }}|                      |d¦  «         |                      d|¦  «         dS )	zRDistance 7.9 from waypoint (0, 0) must be in safe radius and reject hostile spawn.çš™™™™™@r   z'Distance 7.9 must be inside safe radiusF©Úis_dummyr   r   z$Hostile mob at 7.9m must be REJECTEDu   an toÃ nN)r&   Úis_in_waypoint_safe_radiusr'   Ú
assertTrueÚvalidate_monster_spawnÚassertFalseÚassertIn©r+   Ú	in_radiusÚallowedÚreasons       r,   Ú(test_server_boundary_7_9_inside_rejectedzPTestChallengerWaypointBoundaryEmpirical.test_server_boundary_7_9_inside_rejectedI   s�   € à”K×:Ò:¸4Ô;LÈcÐSVÑWÔWˆ	Ø�Š˜	Ð#LÑMÔMÐMàœ+×<Ò<¸TÔ=NÐY^ÐbeÐilÐ<ÑmÔm‰ˆ�Ø×Ò˜Ð"HÑIÔIÐIØ�Š�j &Ñ)Ô)Ð)Ð)Ð)r.   c                ó  — | j                              | j        dd¦  «        }|                      |d¦  «         | j                              | j        ddd¬¦  «        \  }}|                      |d¦  «         |                      d|¦  «         dS )	zVDistance 8.1 from waypoint (0, 0) must be outside safe radius and allow hostile spawn.ç333333 @r   z(Distance 8.1 must be outside safe radiusFr1   z#Hostile mob at 8.1m must be ALLOWEDu
   há»£p lá»‡N)r&   r3   r'   r6   r5   r4   r7   r8   s       r,   Ú(test_server_boundary_8_1_outside_allowedzPTestChallengerWaypointBoundaryEmpirical.test_server_boundary_8_1_outside_allowedR   s�   € à”K×:Ò:¸4Ô;LÈcÐSVÑWÔWˆ	Ø×Ò˜Ð$NÑOÔOÐOàœ+×<Ò<¸TÔ=NÐY^ÐbeÐilÐ<ÑmÔm‰ˆ�Ø�Š˜Ð!FÑGÔGÐGØ�Š�l FÑ+Ô+Ð+Ð+Ð+r.   c                óì   — | j                              | j        dd¦  «        }|                      |d¦  «         | j                              | j        ddd¬¦  «        \  }}|                      |d¦  «         dS )zRDistance 8.0 (exact boundary) must be inside safe radius and reject hostile spawn.r   r   z3Distance 8.0 must be inside safe radius (<= radius)Fr1   z*Hostile mob at exact 8.0m must be REJECTEDN)r&   r3   r'   r4   r5   r6   r8   s       r,   Ú'test_server_boundary_8_0_exact_boundaryzOTestChallengerWaypointBoundaryEmpirical.test_server_boundary_8_0_exact_boundary[   sy   € à”K×:Ò:¸4Ô;LÈcÐSVÑWÔWˆ	Ø�Š˜	Ð#XÑYÔYÐYàœ+×<Ò<¸TÔ=NÐY^ÐbeÐilÐ<ÑmÔm‰ˆ�Ø×Ò˜Ð"NÑOÔOÐOÐOÐOr.   c                óØ  — dD �]e}t          j        |¦  «        }dt          j        |¦  «        z  dt          j        |¦  «        z  }}dt          j        |¦  «        z  dt          j        |¦  «        z  }}|                      | j                             | j        ||¦  «        d|› d�¦  «         | j                             | j        d||¬¦  «        \  }}|  	                    |d|› d�¦  «         |  	                    | j                             | j        ||¦  «        d|› d	�¦  «         | j                             | j        d||¬¦  «        \  }	}|                      |	d|› d
�¦  «         �ŒgdS )z,Stress-test 8 radial angles at 7.9m vs 8.1m.)r   é-   éZ   é‡   é´   éá   i  i;  r0   r>   zAngle u!   Â° at 7.9m must be in safe radiusFr1   u$   Â° at 7.9m must reject hostile spawnu%   Â° at 8.1m must NOT be in safe radiusu#   Â° at 8.1m must allow hostile spawnN)
ÚmathÚradiansÚcosÚsinr4   r&   r3   r'   r5   r6   )
r+   ÚdegÚradÚx79Úy79Úx81Úy81Úok79Ú_Úok81s
             r,   Ú$test_server_radial_boundary_8_angleszLTestChallengerWaypointBoundaryEmpirical.test_server_radial_boundary_8_anglesc   s‹  € à7ð 	Uñ 	UˆCÝ”,˜sÑ#Ô#ˆCØ�TœX c™]œ]Ñ*¨Cµ$´(¸3±-´-Ñ,?�ˆCØ�TœX c™]œ]Ñ*¨Cµ$´(¸3±-´-Ñ,?�ˆCà�OŠOØ”×6Ò6°tÔ7HÈ#ÈsÑSÔSØ?˜Ð?Ð?Ð?ñô ð ð ”k×8Ò8¸Ô9JÐUZÐ^aÐehÐ8ÑiÔi‰GˆD�!Ø×Ò˜TÐ#U¨CÐ#UÐ#UÐ#UÑVÔVÐVà×ÒØ”×6Ò6°tÔ7HÈ#ÈsÑSÔSØC˜ÐCÐCÐCñô ð ð ”k×8Ò8¸Ô9JÐUZÐ^aÐehÐ8ÑiÔi‰GˆD�!Ø�OŠO˜DÐ"S¨3Ð"SÐ"SÐ"SÑTÔTÐTÑTð#	Uð 	Ur.   c                ó  — | j                              | j        ddd¬¦  «        \  }}|                      |d¦  «         |                      d|¦  «         | j                              dddd¬¦  «        \  }}|                      |d¦  «         dS )	zQTarget dummy at (0, 0) must SUCCEED regardless of safe radius or sanctuary rules.Tr   r1   z#Target dummy at (0, 0) must SUCCEEDzTarget DummyÚzone_boundless_sanctuaryz0Target dummy in sanctuary at (0, 0) must SUCCEEDN)r&   r5   r'   r4   r7   )r+   r:   r;   Úallowed_sancrS   s        r,   Ú*test_server_target_dummy_at_origin_allowedzRTestChallengerWaypointBoundaryEmpirical.test_server_target_dummy_at_origin_allowedx   s˜   € ð œ+×<Ò<¸TÔ=NÐY]ÐadÐhkÐ<ÑlÔl‰ˆ�Ø�Š˜Ð!FÑGÔGÐGØ�Š�n fÑ-Ô-Ð-ð œ+×<Ò<Ð=WÐbfÐjmÐqtÐ<ÑuÔu‰ˆ�aØ�Š˜Ð&XÑYÔYÐYÐYÐYr.   N)	Ú__name__Ú
__module__Ú__qualname__r-   r<   r?   rA   rU   rY   © r.   r,   r   r   (   sz   € € € € € ð
ð 
ð 
ð@*ð *ð *ð,ð ,ð ,ðPð Pð PðUð Uð Uð*	Zð 	Zð 	Zð 	Zð 	Zr.   r   c                  ó   — e Zd Zd„ ZdS )Ú$TestChallengerClientRuntimeEmpiricalc                óô   — d}t          j        ddd|gddt          t          ¦  «        ¬¦  «        }|                      |j        dd|j        › d	|j        › �¦  «         |                      d
|j        ¦  «         dS )a‘  
        Executes a dedicated Node.js harness that tests:
        1. Client isInWaypointSafeRadius at 7.9, 8.1, 8.0, and 8 radial angles.
        2. Preservation of target dummy at (0, 0).
        3. Player health regen: low HP, clamped maxHp, at maxHp, dead, and outside radius.
        4. Monster leashing across boundary: aggro drop, invulnerability, attack blocking, and persistent latch.
        a‘  
import assert from 'node:assert';
import fs from 'node:fs';
import { isInWaypointSafeRadius, ZONE_WAYPOINTS, isSafeHavenZone } from './client/webapp/js/data/wilderness_zone_packs.js';

globalThis.window = globalThis;
globalThis.isInWaypointSafeRadius = isInWaypointSafeRadius;
globalThis.ZONE_WAYPOINTS = ZONE_WAYPOINTS;
globalThis.isSafeHavenZone = isSafeHavenZone;
globalThis.currentZoneId = 'zone_tang_kiem_nhai';

globalThis.document = {
  getElementById: (id) => ({
    style: {},
    innerText: '',
    textContent: '',
    setAttribute() {},
    removeAttribute() {},
    classList: { add() {}, remove() {} }
  }),
  querySelector: () => null,
  querySelectorAll: () => []
};

globalThis.player = {
  wx: 0, wy: 0, hp: 100, maxHp: 100, level: 1, facing: 1,
  godMode: false, potionCooldown: 0, destination: { wx: 0, wy: 0, active: false }
};

globalThis.updatePlayerHpUI = () => {};
globalThis.cancelTelegraph = () => {};

const monsterCode = fs.readFileSync('./client/webapp/js/engine/monster_system.js', 'utf8');
new Function(monsterCode)();

// 1. Boundary tests in JS
assert.strictEqual(isInWaypointSafeRadius(7.9, 0, 'zone_tang_kiem_nhai'), true, '7.9 must be true');
assert.strictEqual(isInWaypointSafeRadius(8.1, 0, 'zone_tang_kiem_nhai'), false, '8.1 must be false');
assert.strictEqual(isInWaypointSafeRadius(8.0, 0, 'zone_tang_kiem_nhai'), true, '8.0 must be true');
for (const deg of [0, 45, 90, 135, 180, 225, 270, 315]) {
  const rad = (deg * Math.PI) / 180;
  assert.strictEqual(isInWaypointSafeRadius(7.9 * Math.cos(rad), 7.9 * Math.sin(rad), 'zone_tang_kiem_nhai'), true);
  assert.strictEqual(isInWaypointSafeRadius(8.1 * Math.cos(rad), 8.1 * Math.sin(rad), 'zone_tang_kiem_nhai'), false);
}

// 2. Target dummy preserved at (0, 0)
const mobs = window.getActiveMonsters();
mobs.push({ id: 'dummy_origin', isDummy: true, wx: 0, wy: 0 });
for (let i = mobs.length - 1; i >= 0; i--) {
  if (!mobs[i].isDummy && window.isInWaypointSafeRadius(mobs[i].wx, mobs[i].wy, 'zone_tang_kiem_nhai')) {
    mobs.splice(i, 1);
  }
}
assert.ok(mobs.find(m => m.id === 'dummy_origin'), 'Dummy at (0,0) must not be filtered');

// 3. Player health regen
player.wx = 0; player.wy = 0;
player.hp = 50; player.maxHp = 100;
window.updateMonstersTick(1.0);
assert.ok(Math.abs(player.hp - 52.0) < 0.001, '50 HP + 2% of 100 * 1s = 52 HP');

player.hp = 99; player.maxHp = 100;
window.updateMonstersTick(1.0);
assert.strictEqual(player.hp, 100, '99 HP clamped to 100 HP');

player.hp = 100; player.maxHp = 100;
window.updateMonstersTick(1.0);
assert.strictEqual(player.hp, 100, 'Max HP remains 100');

player.hp = 0; player.maxHp = 100;
window.updateMonstersTick(1.0);
assert.strictEqual(player.hp, 0, 'Dead player must not heal');

player.wx = 8.1; player.wy = 0;
player.hp = 50; player.maxHp = 100;
window.updateMonstersTick(1.0);
assert.strictEqual(player.hp, 50, 'Outside safe radius must not heal');

// 4. Leash state when crossing boundary
mobs.length = 0;
const wolf = {
  id: 'wolf_1', wx: 10.0, wy: 0.0, originWx: 12.0, originWy: 0.0,
  maxHp: 1000, hp: 500, hasAggro: true, isChasing: true, isNeutral: false,
  isDummy: false, isInvulnerable: false, isLeashing: false, attackCooldown: 0, attackDmg: 50
};
mobs.push(wolf);

player.wx = 9.0; player.wy = 0.0; player.hp = 100;
window.updateMonstersTick(0.1);
assert.strictEqual(wolf.hasAggro, true);
assert.strictEqual(wolf.isLeashing, false);

// Player enters safe zone (7.9)
player.wx = 7.9; player.wy = 0.0;
window.updateMonstersTick(0.1);
assert.strictEqual(wolf.hasAggro, false, 'hasAggro dropped');
assert.strictEqual(wolf.isLeashing, true, 'isLeashing engaged');
assert.strictEqual(wolf.isInvulnerable, true, 'isInvulnerable engaged');

// Player steps outside while mob returns
player.wx = 8.1; player.wy = 0.0;
window.updateMonstersTick(0.1);
assert.strictEqual(wolf.isLeashing, true, 'isLeashing latched');
assert.strictEqual(wolf.hasAggro, false, 'hasAggro stays false');

// Mob reaches origin
wolf.wx = wolf.originWx; wolf.wy = wolf.originWy;
window.updateMonstersTick(0.1);
assert.strictEqual(wolf.isLeashing, false, 'isLeashing cleared at origin');
assert.strictEqual(wolf.isInvulnerable, false, 'isInvulnerable cleared at origin');

console.log('HARNESS_ALL_PASSED');
Únodez--input-type=modulez-eT)Úcapture_outputÚtextÚcwdr   z*Node.js empirical harness failed:
STDOUT:
z	
STDERR:
ÚHARNESS_ALL_PASSEDN)	Ú
subprocessÚrunÚstrÚPROJECT_ROOTÚassertEqualÚ
returncodeÚstdoutÚstderrr7   )r+   ÚscriptÚprocs      r,   Ú(test_run_client_runtime_harness_via_nodezMTestChallengerClientRuntimeEmpirical.test_run_client_runtime_harness_via_node…   s›   € ðpˆõb Œ~ØÐ*¨D°&Ð9ØØÝ•LÑ!Ô!ð	
ñ 
ô 
ˆð 	×ÒØŒOØØ`¸4¼;Ð`Ð`ÐSWÔS^Ð`Ð`ñ	
ô 	
ð 	
ð
 	�ŠÐ*¨D¬KÑ8Ô8Ð8Ð8Ð8r.   N)rZ   r[   r\   rp   r]   r.   r,   r_   r_   „   s(   € € € € € ðD9ð D9ð D9ð D9ð D9r.   r_   Ú__main__)Ú__doc__Ú
__future__r   rH   rf   ÚsysÚunittestÚpathlibr   Ú__file__ÚresolveÚparentri   Ú
SERVER_DIRrh   ÚpathÚinsertÚserver.world.zone_enginer   Úserver.world.zone_typesr   r   r	   r
   ÚTestCaser   r_   rZ   Úmainr]   r.   r,   ú<module>r�      sÂ  ððð ð* #Ð "Ð "Ð "Ð "Ð "Ø €€€Ø Ð Ð Ð Ø 
€
€
€
Ø €€€Ø Ð Ð Ð Ð Ð àˆt�H‰~Œ~×%Ò%Ñ'Ô'Ô.Ô5Ô<€Ø˜HÑ$€
Ø€3€z�?„?˜#œ(Ð"Ð"Ø„H‡O‚O�A�s�s˜:‘”Ñ'Ô'Ð'Ø€3€|ÑÔ˜CœHÐ$Ð$Ø„H‡O‚O�A�s�s˜<Ñ(Ô(Ñ)Ô)Ð)à /Ð /Ð /Ð /Ð /Ð /Ø WÐ WÐ WÐ WÐ WÐ WÐ WÐ WÐ WÐ WÐ WÐ WðYZð YZð YZð YZð YZ¨hÔ.?ñ YZô YZð YZðxE9ð E9ð E9ð E9ð E9¨8Ô+<ñ E9ô E9ð E9ðP ˆzÒÐØ€H„M�O„O€O€O€Oð Ðr.   