§
    ¸¾j­2  ã                  ó*  — 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m	Z	m
Z
mZ ddlZddlmZmZmZmZ ddlmZmZ ddlmZmZmZmZmZmZ  ej        d¬	¦  «        dd„¦   «         Zdd„Z G d„ d¦  «        Z G d„ d¦  «        Z  G d„ d¦  «        Z!dS )aÕ  
Empirical Adversarial Verification Suite for Milestone M1-2:
Cross-Language Binary Compatibility between Python map_binary_serializer.py
and JavaScript (Node.js) tile_grid_loader.js.

Verifies:
1. All 9 canonical wilderness zones across multiple seeds serialize in Python
   and deserialize in Node.js with 100% byte-for-byte and tile-for-tile fidelity.
2. Buffer truncation at every critical boundary (header, POI, encounter, grid).
3. Header corruption handling (magic bytes, dimensions, biomes).
4. Out-of-bounds getTileAt query edge cases (negative, large, float, NaN).
5. In-place tile mutation via setTileAt (boss gate breach to floor).
6. Performance metrics: serialization/deserialization latency and memory budget.
é    )ÚannotationsN)ÚDictÚAnyÚListÚTuple)ÚTileTypeÚTileCellÚMapGridDataÚEncounterZone)ÚWildernessMapGeneratorÚZONE_DEFAULT_SIZES)Úserialize_map_gridÚdeserialize_map_gridÚHEADER_MAGICÚHEADER_SIZEÚBIOME_TO_CODEÚCODE_TO_BIOMEÚmodule)ÚscopeÚreturnÚboolc                 ó.   — t          j        d¦  «        d uS )NÚnode)ÚshutilÚwhich© ó    úFC:\Projects\FreeExile\tests\unit\test_challenger_m1_2_binary_compat.pyÚnode_availabler   ,   s   € åŒ<˜ÑÔ tÐ+Ð+r   Újs_codeÚstrú subprocess.CompletedProcess[str]c                óD   — d| › d�}t          j        dd|gddd¬¦  «        S )z;Helper to run a Node.js snippet with global.window defined.zƒ
    global.window = {};
    const { TileGridLoader, BIOME_CODES } = require('./client/webapp/js/engine/tile_grid_loader.js');
    z
    r   z-eTzc:\Projects\FreeExile)Úcapture_outputÚtextÚcwd)Ú
subprocessÚrun)r    Úwrappers     r   Úrun_node_evalr*   1   sK   € ðð ðð ð €Gõ
 Œ>Ø	��wÐØØØ$ð	ñ ô ð r   c                  ó�   — e Zd ZdZej                             d e ej	        ¦   «         ¦  «        ¦  «        d	d„¦   «         Z
d
d„ZdS )ÚTestCrossLanguageBinaryFidelityzBVerifies Python serialization -> Node.js deserialization fidelity.Úzone_idr!   r   ÚNonec                ó’  — t          j        d¦  «        st          j        d¦  «         t	          j        |¦  «        }|                     d¬¦  «        }t          |¦  «        }||› d�z  }|                     |¦  «         t          |¦  «         
                    dd¦  «        }d|› d	|j        › d
|j        › d|j        d         › d|j        d         › dt          j        |j        ¦  «        › dt#          |j        ¦  «        › dt#          |j        ¦  «        › d�}t)          |¦  «        }	|	j        dk    sJ d|› d|	j        › �¦   «         ‚d S )Nr   úNode.js not availablei90  ©Úseedz.binú\ú/úH
        const fs = require('fs');
        const buf = fs.readFileSync('a
  ');
        const meta = TileGridLoader.loadBinaryMap(buf);
        if (!meta) {
            console.error("loadBinaryMap returned null");
            process.exit(1);
        }
        if (meta.version !== 1) process.exit(2);
        if (window.currentMapWidth !== z;) process.exit(3);
        if (window.currentMapHeight !== z0) process.exit(4);
        if (meta.spawn.x !== r   z || meta.spawn.y !== é   zL) process.exit(5);

        // Check boss gate
        const expectedBoss = a/  ;
        if (expectedBoss) {
            if (!meta.bossGate || meta.bossGate.x !== expectedBoss[0] || meta.bossGate.y !== expectedBoss[1]) process.exit(6);
        } else {
            if (meta.bossGate !== null) process.exit(7);
        }

        // Check POI counts
        if (meta.pois.length !== z>) process.exit(8);
        if (meta.encounterZones.length !== za) process.exit(9);

        // Spot check samples or full tiles
        process.exit(0);
        zNode verification failed for z: )r   r   ÚpytestÚskipr   Úfor_zoneÚgenerater   Úwrite_bytesr!   ÚreplaceÚwidthÚheightÚspawn_pointÚjsonÚdumpsÚ	boss_gateÚlenÚ
poi_pointsÚencounter_zonesr*   Ú
returncodeÚstderr)
Úselfr-   Útmp_pathÚgenÚmap_dataÚraw_binÚbin_fileÚbin_path_strr    Úress
             r   Ú0test_all_canonical_zones_full_tile_grid_fidelityzPTestCrossLanguageBinaryFidelity.test_all_canonical_zones_full_tile_grid_fidelityC   s˜  € õ
 Œ|˜FÑ#Ô#ð 	1ÝŒKÐ/Ñ0Ô0Ð0å$Ô-¨gÑ6Ô6ˆØ—<’< U�<Ñ+Ô+ˆÝ$ XÑ.Ô.ˆà Ð.Ð.Ð.Ñ.ˆØ×Ò˜WÑ%Ô%Ð%Ý˜8‘}”}×,Ò,¨T°3Ñ7Ô7ˆðà&2ðð ð )1¬ðð ð *2¬ðð ð 'Ô2°1Ô5ðð ð MUÔL`ÐabÔLcðð õ #œj¨Ô);Ñ<Ô<ðð õ, #& hÔ&9Ñ":Ô":ð-ð õ. -0°Ô0HÑ,IÔ,Ið/ð ð ˆõ8 ˜GÑ$Ô$ˆØŒ~ Ò"Ð"Ð"Ð$[ÀGÐ$[Ð$[ÈsÌzÐ$[Ð$[Ñ"Ô"Ð"Ð"Ð"r   c                óŒ  ‡— t          j        d¦  «        st          j        d¦  «         t	          j        d¦  «        }|                     d¬¦  «        Št          ‰¦  «        }|dz  }|                     |¦  «         t          |¦  «         
                    dd¦  «        }ˆfd	„t          ‰j        ¦  «        D ¦   «         }|d
z  }|                     t          j        |¦  «        ¦  «         t          |¦  «         
                    dd¦  «        }d|› d|› d�}	t!          |	¦  «        }
|
j        dk    sJ d|
j        › �¦   «         ‚dS )zDExhaustively compares all 10,800 tiles in the largest zone (120x90).r   r0   Úzone_boundless_celestial_palaceiç  r1   zmax_zone.binr3   r4   c                óT   •‡— g | ]#Šˆˆfd „t          ‰j        ¦  «        D ¦   «         ‘Œ$S )c                óJ   •— g | ]}‰j         ‰         |         j        j        ‘Œ S r   )ÚtilesÚ	tile_typeÚvalue)Ú.0ÚxrK   Úys     €€r   ú
<listcomp>zgTestCrossLanguageBinaryFidelity.test_complete_grid_exhaustive_comparison.<locals>.<listcomp>.<listcomp>‚   s+   ø€ ÐQÐQÐQ°aˆXŒ^˜AÔ˜qÔ!Ô+Ô1ÐQÐQÐQr   )Úranger=   )rX   rZ   rK   s    @€r   r[   z\TestCrossLanguageBinaryFidelity.test_complete_grid_exhaustive_comparison.<locals>.<listcomp>�   sL   øø€ ð 
ð 
ð 
àð RÐQÐQÐQÐQ½5ÀÄÑ;PÔ;PÐQÑQÔQð
ð 
ð 
r   zexpected_tiles.jsonr5   z›');
        const meta = TileGridLoader.loadBinaryMap(buf);
        if (!meta) process.exit(1);

        const expectedTiles = JSON.parse(fs.readFileSync('aÉ  '));
        let mismatches = 0;
        for (let y = 0; y < window.currentMapHeight; y++) {
            for (let x = 0; x < window.currentMapWidth; x++) {
                const jsTile = TileGridLoader.getTileAt(x, y);
                const pyTile = expectedTiles[y][x];
                if (jsTile !== pyTile) {
                    mismatches++;
                    if (mismatches <= 5) {
                        console.error(`Mismatch at (${x}, ${y}): JS=${jsTile}, PY=${pyTile}`);
                    }
                }
            }
        }
        if (mismatches > 0) {
            console.error(`Total mismatches: ${mismatches}`);
            process.exit(10);
        }
        process.exit(0);
        r   z#Exhaustive tile comparison failed: N)r   r   r7   r8   r   r9   r:   r   r;   r!   r<   r\   r>   Ú
write_textr@   rA   r*   rF   rG   )rH   rI   rJ   rL   rM   rN   Úpy_tilesÚtiles_json_fileÚtiles_json_strr    rO   rK   s              @r   Ú(test_complete_grid_exhaustive_comparisonzHTestCrossLanguageBinaryFidelity.test_complete_grid_exhaustive_comparisons   sn  ø€ åŒ|˜FÑ#Ô#ð 	1ÝŒKÐ/Ñ0Ô0Ð0å$Ô-Ð.OÑPÔPˆØ—<’< S�<Ñ)Ô)ˆÝ$ XÑ.Ô.ˆà˜nÑ,ˆØ×Ò˜WÑ%Ô%Ð%Ý˜8‘}”}×,Ò,¨T°3Ñ7Ô7ˆð
ð 
ð 
ð 
å˜8œ?Ñ+Ô+ð
ñ 
ô 
ˆð #Ð%:Ñ:ˆØ×"Ò"¥4¤:¨hÑ#7Ô#7Ñ8Ô8Ð8Ý˜_Ñ-Ô-×5Ò5°d¸CÑ@Ô@ˆðà&2ðð ð <Jðð ð ˆõ4 ˜GÑ$Ô$ˆØŒ~ Ò"Ð"Ð"Ð$VÈ#Ì*Ð$VÐ$VÑ"Ô"Ð"Ð"Ð"r   N)r-   r!   r   r.   ©r   r.   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__r7   ÚmarkÚparametrizeÚlistr   ÚkeysrP   ra   r   r   r   r,   r,   @   s‰   € € € € € ØLÐLà„[×ÒØØˆÐ$ÐÔ$Ñ&Ô&Ñ'Ô'ñô ð*\ð *\ð *\ñ	ô ð*\ðX1Wð 1Wð 1Wð 1Wð 1Wð 1Wr   r,   c                  ó2   — e Zd ZdZd	d„Zd	d„Zd	d„Zd	d„ZdS )
ÚTestAdversarialStressCaseszOStress tests buffer truncation, corruption, OOB queries, and in-place mutation.r   r.   c                óv  — t          j        d¦  «        }|                     d¬¦  «        }t          |¦  «        }t	          |¦  «        }ddd|dz
  |dz
  dfD ]J}t          j        t          ¦  «        5  t          |d	|…         ¦  «         d	d	d	¦  «         n# 1 swxY w Y   ŒKt          j
        d
¦  «        st          j        d¦  «         |dz  }|                     |¦  «         t          |¦  «                             dd¦  «        }d|› d�}	t          |	¦  «        }
|
j        dk    sJ d|
j        › �¦   «         ‚d	S )zETests that truncated buffers are rejected by both Python and Node.js.Úzone_tang_kiem_nhaié*   r1   r   r6   é   é2   é   Nr   r0   z	valid.binr3   r4   r5   a©  ');
        const cuts = [0, 1, 15, buf.length - 1, buf.length - 50, 16];
        for (const cut of cuts) {
            const truncated = buf.subarray(0, cut);
            const res = TileGridLoader.loadBinaryMap(truncated);
            if (res !== null) {
                console.error(`Failed to reject truncation at ${cut} bytes`);
                process.exit(1);
            }
        }
        process.exit(0);
        zNode failed truncation tests: )r   r9   r:   r   rC   r7   ÚraisesÚ
ValueErrorr   r   r   r8   r;   r!   r<   r*   rF   rG   )rH   rI   rJ   rK   Ú	valid_bufÚ	total_lenÚcutrM   rN   r    rO   s              r   Ú test_buffer_truncation_detectionz;TestAdversarialStressCases.test_buffer_truncation_detectionª   s’  € å$Ô-Ð.CÑDÔDˆØ—<’< R�<Ñ(Ô(ˆÝ& xÑ0Ô0ˆ	Ý˜	‘N”Nˆ	ð �q˜"˜i¨!™m¨Y¸©^¸RÐ@ð 	6ð 	6ˆCÝ”�zÑ*Ô*ð 6ð 6Ý$ Y¨t°¨t¤_Ñ5Ô5Ð5ð6ð 6ð 6ñ 6ô 6ð 6ð 6ð 6ð 6ð 6ð 6øøøð 6ð 6ð 6ð 6øõ Œ|˜FÑ#Ô#ð 	1ÝŒKÐ/Ñ0Ô0Ð0à˜kÑ)ˆØ×Ò˜YÑ'Ô'Ð'Ý˜8‘}”}×,Ò,¨T°3Ñ7Ô7ˆðà&2ðð ð ˆõ ˜GÑ$Ô$ˆØŒ~ Ò"Ð"Ð"Ð$QÀSÄZÐ$QÐ$QÑ"Ô"Ð"Ð"Ð"s   Á2BÂB	ÂB	c                óT  — t          j        d¦  «        }|                     d¬¦  «        }t          t	          |¦  «        ¦  «        }t          |¦  «        }t          d¦  «        |d<   t          d¦  «        |d<   t          j        t          d¬¦  «        5  t          t          |¦  «        ¦  «         d	d	d	¦  «         n# 1 swxY w Y   t          j        d
¦  «        st          j        d¦  «         dt          |¦  «        › d�}t          |¦  «        }|j        dk    s
J d¦   «         ‚d	S )z0Verifies corruption of magic header is rejected.rn   éM   r1   ÚXr   r6   zInvalid map magic)ÚmatchNr   r0   z!
        const buf = Buffer.from(z†);
        const res = TileGridLoader.loadBinaryMap(buf);
        if (res !== null) process.exit(1);
        process.exit(0);
        z%Node failed to reject corrupted magic)r   r9   r:   Ú	bytearrayr   Úordr7   rs   rt   r   Úbytesr   r   r8   ri   r*   rF   )rH   rJ   rK   ru   Ú	corruptedr    rO   s          r   Ú%test_corrupted_magic_header_rejectionz@TestAdversarialStressCases.test_corrupted_magic_header_rejectionÏ   sa  € å$Ô-Ð.CÑDÔDˆØ—<’< R�<Ñ(Ô(ˆÝÕ0°Ñ:Ô:Ñ;Ô;ˆ	õ ˜iÑ(Ô(ˆ	Ý˜3‘x”xˆ	�!‰Ý˜3‘x”xˆ	�!‰õ Œ]�:Ð-@ÐAÑAÔAð 	3ð 	3Ý ¥ yÑ!1Ô!1Ñ2Ô2Ð2ð	3ð 	3ð 	3ñ 	3ô 	3ð 	3ð 	3ð 	3ð 	3ð 	3ð 	3øøøð 	3ð 	3ð 	3ð 	3õ Œ|˜FÑ#Ô#ð 	1ÝŒKÐ/Ñ0Ô0Ð0ðÝ!% i¡¤ðð ð ˆõ ˜GÑ$Ô$ˆØŒ~ Ò"Ð"Ð"Ð$KÑ"Ô"Ð"Ð"Ð"s   ÂB>Â>CÃCc                óÈ  — t          j        d¦  «        st          j        d¦  «         t	          j        d¦  «        }|                     d¬¦  «        }|j        €J ‚|j        \  }}|dz  }|                     t          |¦  «        ¦  «         t          |¦  «                             dd	¦  «        }d
|› d�}t          |¦  «        }	|	j        dk    sJ d|	j        › �¦   «         ‚dS )zHVerifies in-place mutation of BOSS_GATE (10) to FLOOR (1) via setTileAt.r   r0   rn   ix  r1   Nzboss_gate_test.binr3   r4   r5   a  ');
        const meta = TileGridLoader.loadBinaryMap(buf);
        if (!meta || !meta.bossGate) process.exit(1);

        const bx = meta.bossGate.x;
        const by = meta.bossGate.y;

        // Verify initial state is BOSS_GATE (10)
        const initialTile = TileGridLoader.getTileAt(bx, by);
        if (initialTile !== 10) {
            console.error(`Initial tile at (${bx}, ${by}) is ${initialTile}, expected 10`);
            process.exit(2);
        }

        // Mutate to FLOOR (1)
        const mutateOk = TileGridLoader.setTileAt(bx, by, 1);
        if (!mutateOk) process.exit(3);

        // Verify lookup immediately reflects FLOOR (1)
        const updatedTile = TileGridLoader.getTileAt(bx, by);
        if (updatedTile !== 1) process.exit(4);

        // Verify out-of-bounds mutation returns false
        if (TileGridLoader.setTileAt(-1, 0, 1) !== false) process.exit(5);
        if (TileGridLoader.setTileAt(window.currentMapWidth + 10, 0, 1) !== false) process.exit(6);

        process.exit(0);
        r   z$Node in-place mutation test failed: )r   r   r7   r8   r   r9   r:   rB   r;   r   r!   r<   r*   rF   rG   )
rH   rI   rJ   rK   Úbg_xÚbg_yrM   rN   r    rO   s
             r   Ú test_in_place_boss_gate_mutationz;TestAdversarialStressCases.test_in_place_boss_gate_mutationë   sû   € åŒ|˜FÑ#Ô#ð 	1ÝŒKÐ/Ñ0Ô0Ð0å$Ô-Ð.CÑDÔDˆØ—<’< S�<Ñ)Ô)ˆØÔ!Ð-Ð-Ð-ØÔ'‰
ˆˆdàÐ2Ñ2ˆØ×ÒÕ/°Ñ9Ô9Ñ:Ô:Ð:Ý˜8‘}”}×,Ò,¨T°3Ñ7Ô7ˆðà&2ðð ð ˆõ< ˜GÑ$Ô$ˆØŒ~ Ò"Ð"Ð"Ð$WÈ3Ì:Ð$WÐ$WÑ"Ô"Ð"Ð"Ð"r   c                ó²   — t          j        d¦  «        st          j        d¦  «         d}t	          |¦  «        }|j        dk    sJ d|j        › �¦   «         ‚dS )zMVerifies getTileAt returns 2 (WALL) for negative and beyond-boundary queries.r   r0   a‹  
        TileGridLoader.loadFallbackGrid(60, 45, 1);
        if (TileGridLoader.getTileAt(-1, 0) !== 2) process.exit(1);
        if (TileGridLoader.getTileAt(0, -1) !== 2) process.exit(2);
        if (TileGridLoader.getTileAt(60, 0) !== 2) process.exit(3);
        if (TileGridLoader.getTileAt(0, 45) !== 2) process.exit(4);
        if (TileGridLoader.getTileAt(-1e6, -1e6) !== 2) process.exit(5);
        if (TileGridLoader.getTileAt(1e6, 1e6) !== 2) process.exit(6);
        if (TileGridLoader.getTileAt(Infinity, 0) !== 2) process.exit(7);
        if (TileGridLoader.getTileAt(-Infinity, 0) !== 2) process.exit(8);
        process.exit(0);
        r   zNode OOB queries failed: N)r   r   r7   r8   r*   rF   rG   )rH   r    rO   s      r   Útest_out_of_bounds_queriesz5TestAdversarialStressCases.test_out_of_bounds_queries  sg   € åŒ|˜FÑ#Ô#ð 	1ÝŒKÐ/Ñ0Ô0Ð0ðˆõ ˜GÑ$Ô$ˆØŒ~ Ò"Ð"Ð"Ð$LÀÄ
Ð$LÐ$LÑ"Ô"Ð"Ð"Ð"r   Nrb   )rc   rd   re   rf   rx   r�   r…   r‡   r   r   r   rl   rl   §   s~   € € € € € ØYÐYð#Rð #Rð #Rð #RðJLð Lð Lð Lð8-Xð -Xð -Xð -Xð^Mð Mð Mð Mð Mð Mr   rl   c                  ó   — e Zd ZdZdd„ZdS )ÚTestPerformanceMetricsz*Measures decode latency and RAM footprint.r   r.   c                óØ  — t          j        d¦  «        }|                     d¬¦  «        }d}t          j        ¦   «         }t          |¦  «        D ]}t          |¦  «        }Œt          j        ¦   «         }||z
  |z  dz  }t          j        ¦   «         }t          |¦  «        D ]}t          |¦  «        }Œt          j        ¦   «         }||z
  |z  dz  }	t          d|d›d�¦  «         t          d	|	d›d�¦  «         t          d
t          |¦  «        › dt          |¦  «        dz  d›d�¦  «         |dk     s
J d¦   «         ‚|	dk     s
J d¦   «         ‚t          |¦  «        dk     s
J d¦   «         ‚dS )z;Benchmarks Python serialization and deserialization speeds.rR   i+  r1   iô  iè  z&
[BENCHMARK] Python Serialize 120x90: z.3fz ms/opz'[BENCHMARK] Python Deserialize 120x90: z![BENCHMARK] Binary payload size: z bytes (i   z.2fz KB)g      .@zSerialization must be < 15msg     ÀR@z%Python deserialization must be < 75msi � zPayload must be < 100 KBN)
r   r9   r:   ÚtimeÚperf_counterr\   r   r   ÚprintrC   )
rH   rJ   rK   ÚitersÚt0Ú_ÚbÚt1Ú	py_ser_msÚpy_deser_mss
             r   Útest_benchmark_metricsz-TestPerformanceMetrics.test_benchmark_metrics2  s   € å$Ô-Ð.OÑPÔPˆØ—<’< S�<Ñ)Ô)ˆð ˆÝÔÑ Ô ˆÝ�u‘”ð 	-ð 	-ˆAÝ" 8Ñ,Ô,ˆAˆAÝÔÑ Ô ˆØ˜2‘g Ñ&¨$Ñ.ˆ	õ ÔÑ Ô ˆÝ�u‘”ð 	(ð 	(ˆAÝ$ QÑ'Ô'ˆAˆAÝÔÑ Ô ˆØ˜R™ 5Ñ(¨DÑ0ˆåÐM¸	ÐMÐMÐMÐMÑNÔNÐNÝÐO¸ÐOÐOÐOÐOÑPÔPÐPÝÐWµ#°a±&´&ÐWÐWÅ#ÀaÁ&Ä&ÈÁ+ÐWÐWÐWÐWÑXÔXÐXà˜4ÒÐÐÐ!?ÑÔÐð ˜TÒ!Ð!Ð!Ð#JÑ!Ô!Ð!Ý�1‰vŒv˜
Ò"Ð"Ð"Ð$>Ñ"Ô"Ð"Ð"Ð"r   Nrb   )rc   rd   re   rf   r•   r   r   r   r‰   r‰   /  s.   € € € € € Ø4Ð4ð?ð ?ð ?ð ?ð ?ð ?r   r‰   )r   r   )r    r!   r   r"   )"rf   Ú
__future__r   r@   r   r'   r‹   Útypingr   r   r   r   r7   Úserver.world.map_data_typesr   r	   r
   r   Ú%server.world.wilderness_map_generatorr   r   Ú"server.world.map_binary_serializerr   r   r   r   r   r   Úfixturer   r*   r,   rl   r‰   r   r   r   ú<module>rœ      s  ððð ð #Ð "Ð "Ð "Ð "Ð "Ø €€€Ø €€€Ø Ð Ð Ð Ø €€€Ø )Ð )Ð )Ð )Ð )Ð )Ð )Ð )Ð )Ð )Ð )Ð )Ø €€€ðð ð ð ð ð ð ð ð ð ð ð ðð ð ð ð ð ð ð ðð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð €„�hÐÑÔð,ð ,ð ,ñ  Ôð,ðð ð ð ðdWð dWð dWð dWð dWñ dWô dWð dWðNEMð EMð EMð EMð EMñ EMô EMð EMðP?ð ?ð ?ð ?ð ?ñ ?ô ?ð ?ð ?ð ?r   