From eec07026e857555474d965eac6492e62ed6869a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com>
Date: Mon, 11 Jul 2022 04:09:16 +0100
Subject: [PATCH] feat: support for frame index

---
 examples/sdl/src/main.rs |   5 ++++-
 examples/web/index.ts    |  27 +++++++++++++++++++--------
 res/roms/20y.gb          | Bin 0 -> 65536 bytes
 src/gb.rs                |  14 +++++++++++++-
 src/ppu.rs               |  21 +++++++++++++++++++++
 5 files changed, 57 insertions(+), 10 deletions(-)
 create mode 100644 res/roms/20y.gb

diff --git a/examples/sdl/src/main.rs b/examples/sdl/src/main.rs
index 06651793..846e1f28 100644
--- a/examples/sdl/src/main.rs
+++ b/examples/sdl/src/main.rs
@@ -43,7 +43,10 @@ impl Emulator {
 
     pub fn load_rom(&mut self, path: &str) {
         let rom = self.system.load_rom_file(path);
-        println!("==== Cartridge ====\n{}\n===================", rom);
+        println!(
+            "========= Cartridge =========\n{}\n=============================\n",
+            rom
+        );
     }
 
     pub fn run(&mut self) {
diff --git a/examples/web/index.ts b/examples/web/index.ts
index f2e4f018..b1a01042 100644
--- a/examples/web/index.ts
+++ b/examples/web/index.ts
@@ -1,4 +1,4 @@
-import { default as wasm, GameBoy, PadKey } from "./lib/boytacean.js";
+import { default as wasm, GameBoy, PadKey, PpuMode } from "./lib/boytacean.js";
 import info from "./package.json";
 
 const PIXEL_UNSET_COLOR = 0x1b1a17ff;
@@ -41,7 +41,7 @@ const KEYS: Record<string, number> = {
 };
 
 // @ts-ignore: ts(2580)
-const ROM_PATH = require("../../res/roms/firstwhite.gb");
+const ROM_PATH = require("../../res/roms/20y.gb");
 
 // Enumeration that describes the multiple pixel
 // formats and the associated byte size.
@@ -217,6 +217,8 @@ const tick = (currentTime: number) => {
 
     let counterTicks = 0;
 
+    let lastFrame = -1;
+
     while (true) {
         // limits the number of ticks to the typical number
         // of ticks required to do a complete PPU draw
@@ -227,11 +229,20 @@ const tick = (currentTime: number) => {
         // runs the Game Boy clock, this operations should
         // include the advance of both the CPU and the PPU
         counterTicks += state.gameBoy.clock();
-    }
 
-    // updates the canvas object with the new
-    // visual information coming in
-    updateCanvas(state.gameBoy.frame_buffer_eager(), PixelFormat.RGB);
+        // in case the current PPU mode is VBlank and the
+        // fram is different from the previously rendered
+        // one then it's time to update the canvas
+        if (
+            state.gameBoy.ppu_mode() == PpuMode.VBlank &&
+            state.gameBoy.ppu_frame() != lastFrame
+        ) {
+            // updates the canvas object with the new
+            // visual information coming in
+            updateCanvas(state.gameBoy.frame_buffer_eager(), PixelFormat.RGB);
+            lastFrame = state.gameBoy.ppu_frame();
+        }
+    }
 
     // increments the number of frames rendered in the current
     // section, this value is going to be used to calculate FPS
@@ -544,7 +555,7 @@ const registerButtons = () => {
             /**
              * Draws the tile at the given index to the proper
              * vertical offset in the given context and buffer.
-             * 
+             *
              * @param index The index of the sprite to be drawn.
              * @param format The pixel format of the sprite.
              */
@@ -552,7 +563,7 @@ const registerButtons = () => {
                 index: number,
                 context: CanvasRenderingContext2D,
                 buffer: DataView,
-                format: PixelFormat = PixelFormat.RGB,
+                format: PixelFormat = PixelFormat.RGB
             ) => {
                 const pixels = state.gameBoy.get_tile_buffer(index);
                 const line = Math.floor(index / 16);
diff --git a/res/roms/20y.gb b/res/roms/20y.gb
new file mode 100644
index 0000000000000000000000000000000000000000..139c684a227553640090a1db0dc765384be07f61
GIT binary patch
literal 65536
zcmeHw3t&{m)$rVX=e4hFk|iYUCP0W0A)p{)*euUPu@EQ_t!TpACqY3H1d~POWvzhP
zR$E)pzAUyz`~4LKl!ZtlA|h>3@x?_1<nqYny=;<u|8r*U&E8#tsI^t=&du)3nK^T2
z&Y3ea=gwo(KsZt}=}OW7KDw{&zfbe+rr(5|=ZnLnmEQ2y9P$)L3_g;ZRP&-Y1Yp4W
zjyU*$&Ks`J%}tpn5%2M>Idi%?$O^8^1POX?xWJi;$S1buB<DKFQ&y+R)t+GVo-(=S
zHzrs3$V25R-h*3};=j7rP{zG|bo%tSQb>}1HhF*~lN4fcc$O|B){owrdEn!ZcR#s)
z{fsFytw-Ma_@gIRt$*cmU*EzTyu+s5S><I8%j7_Mmy^3Y_de;<3B<dd__yo*K5lz5
zd;x4F+b0pq<=GtazC-4G4Y~7k@2&15?}3Ev50mY`C%z+miEkzE7v*g`wl(pxIC$Gx
zez1@5!v!uV=z@YF=vyi1gTB{F1QN6k$szaEmP78Axhm5IpT<4|KfP3OhW&Z-Z>i;n
z?iGg@@x#{*coPVhxL-fLW&bXv#)D~1ckbPEJWPT`MNmdn36`$r4C{hYApfc`7aRl?
zf(GmAvp~SNjQ8hQav`+{Dk&MfW?ujTZsVe=TE1lOPRAPEuCP8>zT3BnF9{nUvmq#z
zdGmZ@AOH!21`PtHB+2Un+ylB@D4%5>MLxSV`GHO3r7O6v-~)C;BZtiT0=$2FH8w;J
zc>)x%NOF0@CfO?22;}~G0(XCnpaWkIz5#qA_$Khp;9I~?06!7@B=D2LPXXTwzU_Wc
zZIjFDWV-3|=2G3fuQ9pGM;@L5x(S6}>-z&gvR<$U1xHYD1vO2)XWLq6<Q0N*Q@xP7
z37a4&+O{p(ww$*?zQd>x4hz{uYyH0Cq(KqPPVsP25r4R1(4g{N_54sk9U_o`zy$<*
zKyU;EZ$QWi2!#QmPeAYoguwx!EFg>t2$cb0Qb3p$5bg>Hfq<|uAgl-oD+9vnfbeuc
zcp)IX7!ZO1VN*bOCm`$$2z)@;8xX{Ra6BN8g#x!wurCxG3kB~&A!ngbxKQY`P#j(Y
z3L}wxvpDQ!Z;QJOE8~+(c4lX1r|r5uZ$|BHi!0KnWzKMAR;5>Fj&^01%=_aDx%2*5
z;>vuX+Gkr-RmgRl*PDX5ieC+*27#fWgdgrsg7AvMl9EBa`5_me*_#sW?V3xFFp$y^
zR_L<Q-2d0GR^O4%``!oTbZ!D=l-lym&>H;k<GX>8;m{sodr;!?y<r#Vt}7@7y#sxl
zAP{x{?7)st0L^9(`i?{29SIZ$4dO>iiC)+7osPSVG5QQdWxgbbNY>TL=vRGTBUkl`
z_ju0hmpI5$ZkCP3tAk(86<0;ZJ{T<v8UxN$qxXb%^xF^GG5L1i!rTs=BbjvSAUgM(
ziudt+sV^)L$>$O{$wvg8<jWF_5)3LP$=6LtkbDLqQS$W^QY4>Auu8rw1cwA8hg0$u
z32BlqQAn43!-P(fFImWxeAfxtlFur1k$mHX9LZ-FawQ<&P4Z0?x=TK%kSFEXZ*h<S
z=jWmY<x4E79}R+_qion%gENZKYbKhNUPCogf}vnJ@3#^YM|-3bd!&#W6ud#Gsv`*F
z3@UO+9P%#54>7o#!_J@-@}A2fxA^#S-)DS@iUC96;@*!I@k3m$p3nte^%^h3)-}{f
zE4&}3xSKjsw6Hf=BrVL(gwiC=Yx13gQ8-0P_9h@<7&*O)y7inVEzeI#o`*y55UPqp
zsFqomcliZKg#-cOf}mnu&AWDkYD$Iq<|1HZdru4IpmokS6$#Fud&KrTEb~ev**ge$
zCQxY@+_@lBco34|%Ym_WPH0c2s@q^WPj&Z$%2$O`VW3Y9N)~T2j96+fs0tHl5om#6
zkOYe)Buj!_5?qqtmV_)x@Ji5vd$;ZnbCR?s|3~ja)upge0^cZw6VOkP!YSydNMQ&1
z4k?_5ewq~SgnlO}oQ;0A6wX0EM+$dGzdMj+J?aTCFPKIsM#c%SF*s4_PW9C8IGIE4
zagZmhc9Xqbd5mC5*ylIeRZAi-Xxfy%xjt9tOwO7QN_5&6g5gZ$ht>;ZtH5m)bXx_(
zRv5vTE0gpp9KjV$n`u`y4aRMspZ`1m612v0_^g1>O86}DCG%UrU*@ag8-26*Cf{BB
z8{+at9lu;$@ve?vA+BuH^DD(=Mf@_!>jjI6U$%W>QkZP!*M_;x{3~JIW`2E`H1IEn
zxdwh+Sl7UB*gnIyeM^eCwnTh+wD?N7xUOQm$*O?s%f$^8e5FKOH(FG|?XOtFdZcMU
znnt9lN16ttY5dwW#g+Ur@xewDzmk6tsx2<#SB6bkj2VluU@;~v#*D>SzGg8FTQ~t}
zCL+xwq?v#;6Om@p*QVLUl0)9%NWf?38$dhI0M#X*|5vL^uJ6lSb%El<hCzIT_f5=M
zUp3m$Eqil%Qp2JIab=0PVud6R(7h}3kBTcw#FYUBdQ4odfgVT9<tpaSG3_xG^B3Zy
znzUbH+M{aP6BJX;_as8?<^766D(0`nNAqS^jSwHJstxc@iI3&o4Zx#SwJZ4Fh%2gU
zxAowEn>Snh1w=kkHA;LE5}-!E1mtbKOY-g(e-4q4SJkcz@T(xU9VxJ|1oG6bK&07#
zFbWWB7Y6v%kmc8a_A4ylX&}dP04nAHve7w!>@&3NCxHT{LfKEWm#vTuEaZO&h5QO=
z{TiA0y|}1CY%I^@17Z_=FBIQ^@B76-iTKQlyg6WdE~eoCgvG@rP>&@dl>Iw34a1SN
zrQ-c1;_uZo47aD{ERh%IER`4Y59G~3ichV8N<I|;VU&xjFuV%G6-fLy2>u4a`^&}E
z2&_infeNJlTZDhBBtDJ6(=>7P5b=S=KvmxDwfrOHd2^sKD(;1Z#~QsQ_pU@>Aq7UK
zZw7{IoaqhooZ_M)eo?u&W!>g=$vC8J71zxWH&lr)Zy2B-<~F@rs4sCREO>Q*VR*x*
zDZ^^`P3`H0G<^U|FNAcR71B3vtywp~R5PS}-pGo3SMVEW%v=9Kck8^z_~3>QmRK7`
zTIa9C;H_3MIKxLCuiE}cYqGfUXN5Wg4bOjN^Oe?yKWI`@G^z8}ZyrxmH>!CJVi58s
z)4cb-ym@v<sVQpeN=)6lKQB0MNmb2jg<N&ln%4$!)qA(*1;tHEstQVKN9WyfOKthB
zwH50&AF$@#c}s0c-ppHTM^DD&ny0(IRNY{((P$dewdSSjZVT2I8hvuL`%L=G8!}(2
z7B|ljHx==lHf(;y+9BORjDVIdko}2<>=#mX*x-FP=pgdF_|o?S{%k$ZA_|kOXbyFQ
zxm8L4bLcrRhcYA^gnmg4BR3dEQ>bA?wuU*t74wZS3A--|8)(KHn2W&_VdyTJ5lJLU
zZioY0%x%OM+THlI)6r&+F==4d2D3HFb|r!y%~k{2v#J$K1T&hg7K~HvSt2B%*_w=T
zs%1+A8-jMUfWgMKqm4_1RHWy^IIwn|Xzdaq1L?Uj4(whR+Py^RjP$ZF4lLkww1A1=
z!Bj6209)9LwlER8A_xX?H#CTI!61gX9!M`A<1WFk0*t#9>GeeLGRc=G^uoB_NUspB
z@jjBTQ0R+smm|s*7}rno^%eSK+?7ag0LEQ~p@A58HPS1>xIvPyzu?2TVx;Fsl);kE
zFATxBp-68S#toNzLxd3+SAt+EeqDo9uEn^K7`hJQekA!u2%|7=G}0@>H-j;fuS^(=
zaX&_S<1lW#<QpqYz_@bEc0I=3fG-L^!MGcd-b9SMN%Bn)ZpOF@q*sXvlO$iIFd5^f
zAib#=H%;<Q7N%p|45W7p#@#CUrV6)V-0cY7fnRq@zUjhDjH^O=v+ynDF3C4bxEteU
zBfU8ocaP+|TbPS+)kv=f<K{`eIl{ddHy=qYz_|M)-&~;<<LW?aXe8G2i}+>ya()HB
zvfaw`%>@Io8Vtl5{td7!wsc@}f`*quNAcsDi6KKVJ=={*{g*W+!47;i#-c=H@U`uO
z)6qO!i{{}gXvVEaGwxNi5no0daUI%n8_<@cs1XwtGOfo<TB?v~17@PAV8gCO^HWO|
z4B3~l*p8?I{wr7&upZl~0{%Lz$_A_jr3&V;LY3OHw60(yD^$^_ZLcd>qHEEJd<Bij
z^=L%CistCcXi2U^OL7BRlCN!VNed?<Y6_xS5!HsM$%vYQs8&R^DX0;ukZC<;(o%&?
z8!!{43dOF)VzpGE*q5=`j;K;wmr{jVC{(GzOQ}LFC{=0}Q>sw5QdeplQ>svQdtDor
zq~lw|?({G9*6?b%_!@nQSYIM;7%i$GyhLpM2B^UN_>CIhkEJ*KBUQ0^UMoklhy!bg
zyKmh9?p|S&3=xG8Q3w%*+`VA^oZhl)fB1E|2;Pfdmq(lCC6?TfesAXJn#KVzH_U@?
zPO51vg*gGkMhIUIa{-1EAbbnV0T@n!@BkS5=Qa9a@JD<X4X06jHw|~9_*pcZP4T@D
zE``Az@w?G*cZ#1+!<SI}o-}+J#V>?#84TWte+3Qqqxb`8_$rEDM8kt9z8}KZ!{Chg
z!)SOo#V@7dYbgG8H2foqUk2ey7<>_b91V}B_}A0$4HSPO4c|oZD<OOf46cYjm4>HL
z{99=FR*HWI4c|%eXF+%t44#NThlcN=_%$><kK!+&;rk@THt31A0Z#E7`BJwBrual3
zF5%Py<bY{D#rG;egJ2+p70RtqYuVIbAk1JG9oA8Up%C8Rfm(z{e@YQd1xi03D0V}`
zfex*(kiC_e4ud+ym=1e)f>{9z$&pnDjxI5Dxa!b42v;50{spe#;R;^#{zcAH194HS
zQs2HVLAO58K!KG6&C-K@tM3h17G4NzzOZ0~1Anhfb>NR|9PHr-n@ATz7ph>{v?Q-i
zJUWO!+UTmP{dt9Wq=-LKHGlEX4-^OZAq^jQIuFeb?;Y(}y(t=O9W)p+Vbz&G%pZXj
z&Z8#s1w=ct^I-G9E)K=j(V?)cVx-MQTS#Ut<ii$Xt;3eZE~Dlgw6(h{w2B8?rM|sE
z44Je+-d35XCf;sM{nXmU^1=@RR?e><w-uJ-bnc$oN?_5QX(e!#jwu@m9V>yWZ(GB9
zP$|?e{Z{u^tNqH(G3ta`wuv)jZrM#&k~VQf$Cj*M*)hNoWx1+-6|3T6t5{lN0rnU6
zfkG?GSYW38T^3%F!JJ$!`$S=d40gy${8}Un%Vgi9!WP*#RA`jJT4|Df!-Y3wpW%W_
zt9`nt%D4PoJuYFvLKZAxL6|LJeaDtSZi3Z=_9c*s0K_bT+;pKOkcnSr3FM}zC6I}l
zC6K(E|3ga$%c}DHwRty>TTu}RRAv6+mPUWc{6#A(7A^#!(LY*TRF$`BLbsc{O=sgu
z=I@>9gRjq=0WZ_A4Fhk!iz?DLEpWQ-bGmAJJ3ClTX<uoWo{cLF4W(J<cb#RjG#<L=
zdRW&S0VZn+n5?B>vKm0YP_do`6?6BN)_CnOW^eIb11Q%5%1HhVSdz|Q!|PLBI_MA4
z!z<f4K9S=eivy*oJ8z)Gu~1aiF^62|sQgvbxXBOUhq@)Tf9t*1VLuj!!16s#G;ER?
zfH?}B^BcK}Le7Pw6LfRfCp=OQzHjUXuf^wo)vE{Uxc6L`;PO4S&Z~#cNQVM^kJYoe
z>KPa?*n8|42&KUIr7rO6&wmL<8SFk!t~6HN+Ku~h3H(PSAj=YUq;lov3m3rY^ag{`
zWR7515)6t<Oj2Sk$!e6rkfKCdBT)u}jUw8$Q3itpBb^3Asw*u$!`&$}v$MgFg>l({
z*Tv(_$?e*$d!E721LF*a{7VWh?Ri<R-i3YoUVepv#r5ld<$$XOUR^ZESL`>?xWSNj
z$k1WKN0gLa1Ho$%ZzKe+`_ZV;Wn;$v7=q(4Zaf4uZUV-Ua!rikdKyJ;_(>%4#)&MB
z+;Ec`d9xY?DHWBI42H>5rYcBZ$<ym?w&G&UqSt#ovf>CSE>^=G`GmB#YU43~Fc=D{
zaR@UP42oiFs|umdae6(LW;S~~#l>}Xm<Mz7d??h~DoG5E<Ol?UL9^Lri(tz#&x>LN
zTb7X;VlbS#I;4ge3`de!xmYq{nyFR=zy-na;B3@kexwlLomK`bMBOU6i~L*qFxVzf
zr_T%}U4^af@C=zfEJBnIz$3}3=tL#|0R>-$rz@~5*C~%k9rA-5wYFj%>ojhajL&c3
z!<>nhG+nkSFsnrbHpmCznbDqpm3)%M@CrWG6Q5ZM%=nxxiwZnTu7xK8tMycpWvnCX
z<?P!ACFzwXSx%N4VjfJ#2OXwsU7=h^K@mz1!h^o*isj-c7~@qsDo!jt%+soQh)f_R
z@)XHMYF6rFy&L3L<q`5v=;u{{kfvH^^pPKxKMZ4f?1OaG2YJ#ym<M@7ANf=1VFgsW
z+VZrtF<zyMKJufbjd4g*eUP3;b?Q);D5EkjzfJXM6$9ZL+ZDA|>v(8)twXJ6P6FMQ
z;CPb?>*aa$(QDCxw<n`ute;j_RQ+bXk@d_cXs>%)-mB!Sk?)35rk|&uYRRg;sy)-B
z=&RZ@S-s|gB$#^=>V!{&2F5nPUa|vZf^}lOkM%?xLvSqNz~R_}V+f8VIHn+s@+~e#
z4s15WME)2BQAAA4iFm4G#j-q@gTX8h%faYs@fkK!Cj{{sHd4pY+h)Uf)iHXq?D4Qz
z6-FONe=UrowF-wqNDW7OQDh;NVzf_{4i)dbN8%)|Rgdw!Cs4<dZ6d=f4%$R>NZ-my
zGDp?iEUS-N0o9M9%jh8uiQ_$(#OT2^q6C<yLTb7>q{gWD9-fo5`7ypYXb!dN@sYR)
zUYET#IN4!3N8~^q?+F(3p2}5)I|_FUXsTHfGGpy)@2|bzx4Gzbd|0)yg^E4JHjmPt
zDl=@3Yi(@_9o)O8;nn5yJ=iiZ1l6e+YPrZ^t3G5FZCGZYPTy({*~F}USZ8xc#VQUW
z%zH#z$joYv@P!_fek83ph=>tbw6TmHUX>Q|s}jQ&L)?%V>%?k>@PIeer)Fu*(vZcY
z_i!23%<L-%RL$BmLv|xyb@HmZ(Hs9%@jH1;eLJ5RfI;J2=%jdX@7oQ(SzMf4*J)$j
zzPcuPNwZmAUS(^xRoSZ4(s&Ph1~e7!nEi0|Qheq=SRBM?o49<I`mp+;%us3(?l_4_
zP~U13i-UCmm9J<Y`HQqYrXdBCgQ|H%W|TZU%X!Xn&eCEzX+LQ{;W%qQkRx|)v$a)k
z99r?a`Y{zx*N+LwA1rwoo~6q(Qy%~Ik^e(!6w4kuhoH)X45Hi%wR{cP`?>8!sabrA
z|K~oGLGK+Et5#L8u^Od-^2heZ8WabUPhuVM(YMOPdOT}i*1iEvvz8-fuvkq)A3CJu
z;XyDl{7W3uGbS}{j@YnPUob6LjD3dnB!&x-@I+!6%)aXa9{!;BpjRxEtk~}*on*Lq
z^~?U_oR<zNI52*&|F4_hBQ`RE`>SpOS5NdHqg444`LQ}qPLz$ZyeCRrO8;kL4fdBn
zown6cCfM%SckoeL4e_ymwd$j>v278nIJn>eCKD!Q53g@!{4s4{T7ufA#-bmn3l`(3
zrj3&gFhUUXIrBN&anw~p0wfsp@_WhVz+PUokKnAFL2u>KbdPY^Iv<xP58wDq#qf>D
zcM|GHl-mgU$$B4at6(wG#1>T_HVQ<(>2O9RfKo7ruvaXsWwH9!g|%!HXefI6W;TNG
z9`sos08s|iaT>Wp{@7T<aBCOVF2r&XPkltYXg^LrAq!NWWb4)$=Opo-gYIH8m|nyt
z50HE8k&4@pFA!86<!>YOij6dcjHtv=jw)ZsCq5C*umwS~z!P$#>Y!RjY`3~Vaj;ci
zvxN0oRR^Q{e!JjksYI?_sB*%5maF}{eO`bVzZ`%^+t?gvbsc>uFR@?Ea&!B9#WYaj
zaP&eMN4Q`dsAD>&tGr=Rth~B_S}e{JAv2o`)i_jcHrlEBj=5NgERFREEEQ_3<q4Wu
z4$3^VlsZhIBHCccigNfSb6h%n`pn@S@^VP7sIRX;`+G;R%9rZ1RzNP)o)Ih#fnKN&
z3k2%4F!n(eX8Nf@GGXnn_JUwB>(|(4SwFzuraYXaYfT2*C|N9Q@c%48hZxe^Ri0mK
z|HzwPcD>hYzl$_`W_bPtpC0g8Ypb@g%uIebY9L(HfE4#-ZSp;G7fV;$i1*;UsO|Br
zY#iwrrcxIGSer#)i9-y8TlJz%9jV!P6lp(=wgNwG1|>fySwrHi;0t~Vdw3XEm<t#F
zE0|BpR4~TZkwQqr&$ICH5<A&wtI)hX%d*<{kVAZ!WLe+CR*ZzO_Bb=*6Ft{r3O=ZP
zYM983lL*X^R2VEl8pOV84n_1<!#_=LGfCzaOPi(Df+LM+=SdJo)Q+4EZW$#KBhf)(
zA{kGffzNZ?C0x0_$_!k4HWt^_$uf>;>Wqq`n%WytTe05-i&eXb)z2JaHDa>|>Y>Uz
zvnwLHz$A#da2#TmiU&q~_Jr&qd#gpVMAzIK1sb8=X{3OpahK{VY#TjT-wN}_V!1<y
zeX@NSM!~3))v=t_KGGgcgON9^9kwj1ISdK#U@ybbHPQ}@545`36XBZ8tL=7$q0JOd
z3MZVj@AdNDpnn`$Lr#-gC}T$<ltspK&p``p)VG+=+M0@+6ka5#xd_8fP^tHLITwbd
zTzPyuA2>sx?L=K`v)C-24V?|OwDK|>6QzTIkoI2wSyHT6S7?}Xpr6Q#$~*d0iKyO&
z=qMCvFHDh1$jmVC8|`fLakTXC8cl>T6Koe<tD)5p^t1Of18u8B@)}n`dXpPTKXNtk
zKxj$bd%f$+-|KxiqdE1xOAciOWt=qdSxCRv0FMRmsh2C{wepR?9-9mIFiz!`)^Mvt
zA8IYjKLxE;Um#)Qjxa6z{f%$-`>gXU+so#VC8YKYDu08bQ99@e*c&RyMpABh&NVjy
z$DOe8#nHPieXe}O`qdR>^{XrV8?jzau(upA?2ndTgL;+I8ew(RIU2)~d`><q?FgNf
zn_E_$EZzsN%yhW|>xS>bLF#zE#{85%3+$WG<WFQM)I8|N2Qiq#-U%y%w3lLNPSU-`
zHeDUmvV>SUSj7eTbMofmKU56axUkN0wnDyC&Y;p-BfDe^)CqNGpS+{hBNp?;dso4?
z+$d*(nNb4?t-uD(Up6qgJ)jzO_`Z&gz0HdvAr|6-KIQ@+9TW#oJzUOvj?kAaoF6q0
zMWXf9!?&JvHuOszXsjR`^flBnd;Ip&wW=%`F}s`(($q_O$=qsdt!t2%&?%GEr<koR
zsH=Nz@CNRH+Ki{4HQ?hq19h+}ZZt8+nJo#4iAl*RR-4`7gt*9hc2+i9#x}t^wy6hQ
zz&2l|E@8vkwW)padN5nFHkF!8+%?yZgw^Uqx?DYO{Dg8K$z7j$!%uFUc+<@lm6?-Z
zg?SprP0yTh%dNNFe#f0NujZ;|-F5eDlW9(7<~`ip>Y91?&R@XYS6dfYXfoZOnVI<j
z_u!(%OO`I<xQBlF@XsJ`<|E5jtbFvb$AA8dUq12VuYUd1Zy@fst5!e#%<q2xhd=)5
z**}vt&;13<`0MlaFTD7-{{x=?-``(a`?AUO$~tcShF4$P7~BMLn;Tx=(%AII);BqB
z+gsb;-ti}nduQiA{`s$6@4ol`ZYn4G^}&Z9ef-HDe($IIB5|MXKXCB#L&D)BM~|tI
z%|twY;^Zll>GT=au90ulajk1<JsmnMp5zbm4Z%l(B>~iuHr*a_I&r>XS3$GAV)aIU
z9gRH;DeuvbMD8IiWEW|Mkw75j<aw@^`>5->;rBcgHWtG>u}%I{g<k%1dHHjoVbsCP
zAiNW!dmylgBoQ<Dna*k4pwBdpUsGQ;d|iE6#^=wCOA~tSWO{m<RIHY`iN6b(U)9Yw
zGz~av_waxBR|R&Fdmd`G@9`oQ)@3}I^V2Z&D-7QReJc%yI&^jL;8$H8tW03Z@Wq^H
zyB_rUmW&gy{xD=?<*GDc;FdAZ7wiH8vQADoGv@hEvqrqUqViej%&S3c&qEmlL3h^>
zKS5X^IP&A$4gos@(}#mj+ab<CE`bh@Wvazvi4V!2?b!_D|EKaPIV`tH549RX`7ph&
zRt8u$PGE0kE};0T*A<rdVL=Qw13b3go?=gH=u{{q@?sE{#R9?CW6;$R)UIUrgAZd6
zXVhEFE}Pdgrg&prh1^>%fkADk+@oa-6$+INZ68@8fyaTb@lc;}U|HM)`EjI0y4ZKi
z!AHwuJXuO60?(KZFqfuJFip-b8Sv0OE2{jo3CBJ6&^;4>_iVZ8+>kbNXtbFAxlPQ3
z+4xz~M%IC_50j1liq*Y#uAyPf(qcGkd49}u<P5wahs<j&`?5aq{<X}*58P502pj7G
z1D`oReYSj8!RHyL6Q8;1bmE%v*RLKFEM5JgpC|8-r^zM`#82g4M>&J9fUxW5LQvix
z7UgqNt6Z`A`7t*=GjL07plU@J>JTDp{m+p-(2f<9{$cpuNJA3WrVAU-Se_gAiMPHi
zL^k-3kPx|_Zn;2nm@}X~JjF_vqobHv?SIsVv5XF7+DI7I=$NLB<6y(CGs%jI;<5>j
zcTsV453fhQO&e)fcJj1w9NpII7ZvsW2SCxj(~jeGIz3KhIEP`ot4)oqo0SdIm&fB}
z&g1FQB|9stb7m)ZMtYjd>9E_ZDM^V57PHA{H0X6YXf4Jo1c0i+$f&Y;QJoVzXTh8Z
zRNIMqx@2d!6HRqGfM~Kp)u7i$h{DtxT~(ZHm5t*Lh(c96XJS>;TtL(wO%$j?O;wv`
zq2lgW+yjbxP;rYCw^(sY6t`4y%M|yJ;(n^QhZXlT#XX|9<%;{Z@Bat<Uc1Gh<GwBb
z+rIzLaR~_~J=YdKd-iO&jnkVFoQ@PTckX2KvF4NKICF}lQ-(8vJA3TV!9&N+atY3i
zE}heoxYLLCf4cwhX)Y<PvnM+}nL8!y+q+LV#U-a_$EL^TkEI`5{(pC<@a+9G#vP1t
z$6_1^D$2!<&j~Tk8RI&|xGpj7zso<&ve4$(2C||9GPF6ik*w%|3~i2WC@VT3Lz`n8
z%Zd)j(B`NEvZ4d$rN`!vr5{uNf0_SQkboq$Q6sahtp$K^OG_9cVUxA3rKRoMxp2$b
zGiT3*Ag)bnYikX+&~;*XFQ-N^-99@9S<XV9wpO&q+R$b_edg>L_=C5owwBhGHUkda
zoDR->7&#rx6($bO&ghLsuntYegv121#iYkc$Yj(L9dl;DN&u4uMkfwu79Gq8iSTE!
zSoB5%;C&M=!C*;rxE$7`1eeQo=ImLR6F&d^^Mh%w47<~5w>eXxZi!GIOM(RsL2-KS
z)bZnDc2=f4!{y3w!}OTxc4uaNhOU!6&1O$Yv87lc9$sGXpbG~JHfCaTS`8Lf2rYt^
za2|b?x=NQ3MVcLx{3h6k8tM&_q=SVyJeDKLIs$FnCLt&hXlRIQgD-dqm)rCbtUwW1
z`5<r_4CZBQ6wD-HyJORGMr>b$0dxTK&@apjt34d-EaB=O7Qsq5kG@J>rOSvS&5lVT
z<$v29IdSa7nb4unj~qF10#5U^NUbd`p_VhpSUkiX`TWSCV`mN=I%G<;B&JvmdXp(J
z(Q0+T7~ybO9Vr$T4{;{F$zVy*84RYBgcOHMuQQudQc_dh8K5i~t_(*4jZcL*v(Btf
zaKNdnf4lnM00C2?i5P&V#N@<8lYz8I1|peEgtVM(37tM03bll!mKI`=jvhUJ>iALd
z#HmxKPM<n;LOgotuz2+F@#9C2!qj+hKg=434jw*q=%{$u;mF9!aHM5)2IH}F=ZrK*
zVoI7LB_qS(NQA-+u%eJ?N=Zy~q@{cV)&Dn1bk6SVhB`XZtV!^3KpiuZ&B+$9XDvw<
ztHTX($cQ63IWZ~0oRDaS@r%HRzSzS1APk+*A0&WLPt6Gl*rqy2AgyP@Cz|1NruEF3
zR`IZS<jCPeN1BhEIMW7kVOUf;gDYL%W#`Q&oucaJ>5<pt(w^S@{2na&QuPNARKI5w
zdOMKd%1$LZqtQx`q>*!`5|Wx=)f<V+VkEX?bwSk3r5JU#P6UjwR2NC*GJpbNB*XOJ
zA~w)du2W}>vq2Li8+A_3g(MPy9Dx%yTxx>NN)ian4G9J#5CuFZ;Bl$#SWvFT=5zr{
ziVZ1U*rnK@3Q%N%i)3(E062rqiYo^CB$8kyz(z8N2c*$0i|Eo^P_WgQZgc{k&IYx1
zLJgn-c1&@>IGdadpxKBiB+cqdNG8N;%lP6-&{AQUA{|8Mve?qh7NnKJLAg33jHWKm
zpfJQ)fnSbf7>&fjrMirU&I)BJ9*|H<C*pD@Ks}9AE&!Uibel5)VRWgTAXf@<Olz8u
zV(hqTAYL1>*z88q$(F{l5~DTI)(O~6aCMwVK_jVFo70|VHGeS{{LYuEPpW?bz0%tZ
zAO+yuY;qG_r(U!iwGg$?3-~E%snA!|ObQaz&yZ|0!sz#f?#tHCW#OzzsjOOGM9|Jz
zso<?Hi__+2n2E5mOiWIjCBq2H#vGSyOiE24Mxze&L2=kW^xbkvs@@37$(+fVV6+-i
zsc1}GN&@PsIn8P}CP1Sl*%H*e9r<6Dw~q40<9_fGsPj#kG-*;@9eja<vzN61!DrH>
zDd59dNKZ}StjfyDS(P<~#U4Ux7tEN9In?yJy2{F$8bAP7SBGaeBXDIU%U?ZX(xkg4
z&FfR_;W*5xmWJgeM>))oCqvPhb+ro!nKEg{<e5_!)XbcU<q|>*QogXf*^}yK*WGz1
zk_ATylyld8w^mJ?F$HT0^n9~v%A`qv=|lRg=8OcUGj3);j=JJVx?0|XqDp_A509&w
z^(c?JI&FmsQHX;#ds1;16`nHb%3Pl-%VJT|@0?V3XWi`CP@gG-t7(eQRh>I!FvG{3
zT_{<kZ`RvPU6PC3HH9+p7jbTI(U6-i<YPPwvD7=jMeqi=2MnMX^b6XrX!;1NLcbRH
zsjUU53z&E&s|n>j0$i;RVRV@8_t!44LYSo&fjnv#i~_8gBWjB<9aF5yS;<*Y0@5D^
z@kj-I1*Y^^{@S9Mm<UeE5AcKubLY<OZ#DS<AD!|#!T|Wb5?ng~fg|7|Ffv96Wv^Wx
zn9AboYN6eHmHq`l8}Z`z|Ns485s2Uar}yFG_y6Pf|Ks=n+5K6(a38<_55Io>pT7T3
zG`n=T`3D<yaDoMP)!+@>l?GQbGu-Y@cq<bYb6_hFw(W4^F0vu0Y~gun93}z|><@Ov
zeYZ|-Solg$OLM{Q8*IP9Rw3@d;l5sEyO7}Gb8AaDd@gkM>={^CI(g#6ak07i*s-Iq
zU?K>I4#8^d!2<{O@8AE~XZ!Z;!_A7l=;(=|$QO;nM8Ns%Gr&G@;NZc}KR*PAOAa4_
z<0Z$MML1Ui2TM-F!gMGEi`p%%ZLnDVwd$|g?(De!;(x9FO8c2`?>5qYusWHF?U#Ph
z_KVm(uzW!GY+;>VkL$az`ff6tEl4&o33i3yWE1YTf?l9nu&v?t`?>U36P{^;Te<z<
zHZEMoE%px?I($UwH6yPZHG0h0aTCgKxN+jmm6NATn{n%Hcg&o1_nf&k_b#ZdU2t#B
z+&Op8nt8`<x6YV0Wpd@s6K}kse8RY~V@8j<ZsawkBZdzh;x8UlboEsOuIzWk<$Vi#
z_qy!Tf=lvy<aO`XH8;l#JICtAF>dnWhBRy*Q#DIWKt(f~aA#UsuGZmJGN<L4*u91R
zvdUS36{}wezQc>0Bd5>cF_Z3Exbo>2H|^YeT<6T~J7nzSyYGMWnZIrR$EPRssa-E0
z`r|3HA9(C{|JU%(eJ2gBZdVK&H+9Z~kN^Jvz5cJyP8rj>_ZvQb+C7VY{)fNA&kCJ3
zrRVh@F=6`L#lQIDOO5XyIAhM}ab-#QjOr!7{L|W|_YR)5xbp{;UVlr?(kGsM`HlBK
z4<&TE<f?0KxOLvLC;$A)*4>BBC1w^3y!I!z-TTn5)~tK;10kH$`O>RL-gx``pZ@x}
z_1iu?+>)Htv*@~scPx1LslRM^>!TyBDcP3|`q52y-uJWL{PorCA0KVAcIo9Cb@R;H
zM}GVKYj1yYOtN`;7mu!}s$0IQe&dck%`yoDc`*b_nBP@Ds<FO#pkY#>I~g{BzpMT?
z&v7)xKa9P^m1xv)-&OxVeg7Zyz7jNG@psi{@2<tg=wHbFJNN%dz`wdI|M6?Tt3KS>
zELQne&p|Tyo4Nn5p8xr_eD&PXx8<v&fn9N`JyfVTzv9Xir>>XHQk*Kc6^dJ}xEB-`
zRNOm?Q!Td0S#MT@Z>#*g{DPjB_3m?dzy1RTUJXAWf=94Nl)%r1Tzl=vk=HRda^$tw
zUQ=3Hg0{$Dzi-gh0|)f)cX^-Qy?Pep=XJ}?@nm=I<W6^`;t8|%Q)_5yD8gkY;pq7~
zu&}K*JhGP2sdH8rZ*I4|{7Wu{^7~%VAB`qfKK<E}*z&Kz@`tJAUkT+G((=1^#qu-V
z>1nk5WOf#h9mQ)eAC4|4=lj5rv)O6+nOJ_;b1i5EBO=riHZa*qRk#%bYlTYK+^XJi
zf><>j^C;m+2v*ue(h3{<aCSQA=`bA*1K@!OIDNfwq52ea#bjdupH-_O^aBAK;Ms`T
z++Gr{<f&9!^>81c!4FSRxgNsF=GNAO<g7jVIhiUJ9;|E%E(w@n$2~}RxP!r-j_R+7
zjCyK1y%fgEmAeoRr#|g>5b)NlsWmw?famqF|BK^O{QiGLEtnkDh6C}?pgE+*C~}6I
zP3%^s3Zws@zW*O{SPfrxzQ842z-ia;)o@Czvqn4iTN=u@I{bkT$4V5}esC^Tad1Ql
zT^e)g=rX9ItJU;e9bEIkcXfDK$G3HSU1#s=IXOAGxm~+<>(;G%_q@Cw@V<6Q0sN9u
zPjy`0sknbA?w^YLm*RFQ?p?*br?~eOw_9-^DDFeWeWbXL759na_9zbTbAFrm|K+-3
zkIk%yyAR)vk9U9D@9C)>0{s8h{eR`PPyV)iyuHf$JG-5Xw;Tg?8bQ$Cc0jEE%>Dmk
z#7X~-If?U!c>n(xLC%>J=($c1eH~6<^oHR^8VbTy*+3n8ux0S(EI!J$Q=Z-bX7^LS
zZ}<P%z{{*r^?p3wy#1lv|0iJdnJfutmfCS?k1p)Nxjl8>!1n=|LGacGtm!IODPScx
zIVr&cXLa$oXiGSB_Vg)OwS^_y!-qaU2y3;U!8&bZjTS<%E(?pYc-ar{`oT)<DL7gP
zCyAwRyZrxk{w^k8t-bq$T>m9@yVqM-==YbE!Ksv4BnIX<ha)GaPoKeq$BcnfCwIld
zI-S#*o7=bVkRfBoPM&=C-LbG<pPJgW>*bdZ9s1)RPnj}%b}Vc#xLn=3U2(;*VdKV4
zojPYuENnEUrFHM#uix<D<Ht{%cF#Sru*sC3o|o6Z{|GqsFn#*mxv{X>oRQI^$CX!>
zl$4jxm{DCF3!gY)wc@9_+2O#?5AFW{2`hV=9qhqh0r(-^|HpTd6X5WbqZu5o=lqcF
z|6go;Ja2x0bsoH**gpTs9nXLJ&Y%C3%kD7$HRJquCWLD*c>ls=hWQtlW$67#+-!}#
zbQyK)lHHF?O0<}b1|5OB7vXbf&zw35yQVPz3UmU7ol=~DmBlt?{#7sAL;2!~leB!i
zt^?bzu>X246oT#7)2B|Jgo&JPUji}Qe`VXRIH#-ouXL+br5!1Pmcq7QajJ*ySD5V&
z3Aq1yl<vPChkO07|9S@YU-42ATra|vCuM1h-aTde&$!Ll!Tu$Cqs1*|y4Q>iqj6tp
z|5>|#ORto&El(qEdD1;k*z}|e0r0wyuk-Xxp1!@G|GFM|b5~#JDI#XUthitR3kpCF
zt}VguR6+sDb|hRXqI;2GV#5|VT&#j-fPL`rxpUAKuo({5r=UTgNnlSL9rn@_#}%h8
zpK5(IjUv)4cvS-&5CU>Q6i7n>EwJ_7ikEzFJ)N%mDO<ij#QXm!eCFDPflH-uYng7S
zCsGAa+MjyZq|l!2M;Ks{SLs5u57A}=fip+NjzVW5M~S1sg@zV72x9-k^AFJGaQXo|
zUo)7|v=<)$Gn(1atjFQm2G)n6N6{_}CUk3?%;|6hNsQdvhTVBQw1Ex+><9+le^zf*
z<LL`^fjGiHs}lSE;oSdM#$S*gj=yQ?ZdjMkhMzCMA03bSt#SCF7C6@cKQn%HQPH45
zgM8p%Yf5nl4H{HbboJE(2VON`z?J>`;aP<~cvzt)J*<Fd6+B(wqypYWkD5Os?@!Ig
z@bw8={nNdBKKb~=4|c!*-mZWB^B+6k+41)Fx3;~xb?X~Vjg4Ejyx!2zuzB+)aO&Pl
zFo@9R&6^t<UWcDz!QW!p`X>D;7Ug$X-rxPfhw$4g@Y5^%58xXZn}FyGSi=N=!%}|(
z?{6T38$S0B9X$4$>#iF;%3F5b;2VaI^-h>}_v~A%W_tVdD)i=0xuh@b3V8?LG41x*
zw|a+9n>=l%H-FlllWx6Z;3OL7pE7gG)LzqXy~G#b^XhZ#vNbU`H^tkM6Kt}&X$bzI
zSvTQdP4Mqi8k>Z-nuLEe3A>wwVAEDWsn7M9bA4QHeQwX*)x8?GHEtUP|8K#<JAs<3
z*IVsy2Ed(q{iF6k%Ned3aFXiztX>Clt9t|CUc1}TasaI>()DrG?iX@S_~9SY3tO7D
z!b;ypPsOS|?9Hb>=cA_Fg0=a*x7B&O7A!vCxvZr203WEX$}6cA_BKJRsb1LE<hJ=_
z^enyjg2lN7k5uP0HWjShd~9&P&F>9%ywQ|dl~;Pew$__^-?l3c*cyYQO(lsXV)p~b
zUf{deuFW4?azH!-oYWk!WjnhSZ0o(vjh`hmY7PW>$9-8&(}=9h2S<lLZrWDjwI%YY
zS<d9dEaw?u+&wZeIm?-qv$tt6pPK0GweJxc+ppV*-p}SI+;>0>IIFt5f33<1>b0%W
zosk2SM&#QaZ#HFS_1X(LGa)1I%6SWG{f8m_Ha{}N@pgNyU7A`i!D@dTRqc!ptDPQI
z?a^3mcYC!vX{+s4tKG@nLA7afLdDc-kAiByqg2~XtKBK*A6V_b4(+%3v7wG#SnX?3
z*lDcV@1U@k(0Z?@_4Z~)3+E^(2$0y;*znh(&=A?9GhfLcynmjvD(|{_KC^%?Ea3YT
z@O=yT%M17`3iy5neE$Oe$^w2s0e@8iKd^wmx_~b#;0G1(@E=TyL9H@{kDB&^P`dn6
z+nC$(p*bu0toyp`(zIu}*Oka;Gr@qaE#|w_^PS!OB8{3&r4vcY#I~D|pXzu|5kfW<
z0%ptE4RY|_+|PTX<3p6fk5CHfObR@c0u@6wYralQ>vELl<8z_Kuv56RqUG~1P#)S_
zfc~~d=rILrg9ZKaP1}0E7IbsfJ?mfcX7%iO#m(uXGuL|US(&}pdW}WcX?s^!xQ|@c
zH@&G~dKL5`cbBc(O7e{dB&R#YS)1+5_HoYaOaOb*#2pt->?+Cmqq@ASBDh#?$(h~T
zSuY%D0wQ5b#CkyL@?nH-K}nZ}`<yjv^R3wHmNfEtBdVY(hlG!twstw_GdgP<MJARk
zXYG>4plQVDta@iwBK&LbM5p`tngiw~+pf&{q^WOx^;Qs)u&1dWvqCV3Z-Q|sw^{*v
zQy75Uo_+hv6k$_SZZ*ZO7aE!rG{rj;zyKRxzU$vT$#006yPez0b=6htnsiS6_4;4y
zKhR$&KHV_N@POg(hW!SI@oM7~<1*u4jBgu{8k0;tOhZgJn(j6&GyT@|H`D8;cTERP
zXTh9ynRCpSnXfVrGmkdkV4iHg-8|bo-~532A@d6JFU`L(KV$xr`7h=d%`chPnO`$+
zGH)@zVcuqb+q~2KFY~+R-R2L?ADj1FFdptae&KV&@N0*U8QwZ9VYq8}*Wvw!4;g+#
z)C289O&)qMkQ!uYk1+=(vT7ge;I2E{(Z|!OXr@z}4&UG4M{dwwM5`WtsfM(*;M*8F
z_|b%xHn1;@)RKf30JBlg5%>|T<M5&eXHuJwp9;0YJx&XnZfKh&T8ugpKGiH7{0t7V
zes)l3J{2Z<bCMO`3{#zUYm!+{LgJx)pL__rZXbTK?~oW$_X-^jJBNQ?)!hwFtD_f#
zAC1B$WGekXDiI1(hg}f?AT)XG<gwZaJm>ONV80d_!m%*>mn{*byhxCSVqt9JD3_d-
zPd7P~FOux?M#EV}T3FXt3&}FHcvM6zOn|-?17U53i<PJR2wi)YSXlj^2`a+HU~OG6
z59|u(Xyp-mCB2(A5yGcp;O{%whuitIDm(OJ<$Ez$oA>(}j&4EZK^vp;q{gYRiYFiY
z_^tJ8o?Kczef+S?a~-7l=*QdE{~6(F!-nSOa62STdSAPKqqJ@P`ZZ51t)4r6*s!5?
z!)vni(Yhy>F0HPfKCZ8wxL!RbA3OTix+mt2yFAB19Nk)vY+JwX&xk*5{IH>Y3#IMb
zwyi<>01qA7)!zK!h7bR|=85X1bH`5`dc_bZ@69LIV14F}pD=x#UH_VVblns18(mAP
ztLKjELtJme3$IEaerYiEZ_lh;sq%*YAG833?}~w=kRk2C2j|S03Qk4ax%ovk_`UE4
z4=JgLYEvx$;n?xwJlK&-&%>+P;CD&Y;P=8W8&fv+$K!qsKd?ICdi-(KiTLxXH&+nl
z7azVCZTRn2vXu0Jo4cwjg@A(srFNVeCKQ%}iA4y;qnlV*XxFBfLP{T&M88KBmeTZ6
zQb_ZURr0GS7?1f&0T1by+FAb6LWVJ_a3D*!kBut7JwL=_{;_~Z^Lt~<@6YmkN3nFJ
z{9y|HQix~e_owN|*$<O{)~^5C{8v_jo{_<-@=(80fl#9>!Qj!h|8#$}{!jNu>;F`L
zwEj;8e69Xf`lI!~(jTq=69HeV{{TnnKjzo!|J3&KS*0fc{uk;0^^jky|3E)V{~`Yu
z>i?)1{!81-XZ^o4hJTcvmVZK``Nw{$>KWr8n*W+I#{Zl$*8gXgG5)7RJof*YWla9l
z%UJHIG~Pif%UJ&h*o);?D(Mp;Kk{D*@!0<-0$nVBLK*A-V`;nt;8I0SBSdZc$MRq4
z2MvP!s3)q6jDLMdZ2M1)Y5xf^?T_@e?GN&^Gx;(8$Huh(gm(F}(n~MW{-a{r|2BUq
zt^c*C=i+gdg?1l)U1N1A83W^g%?pKvIRx~7R%sy}A7>R7jsbY)3#|QSuEuoWSCfwL
zbjXk6@N~#OhE!rY8((J?+7XV<5BeC%kL}~1gXKg1IWG)k`2%BF`2mEdYVr?+@<D$C
zO#e#*1A#svw<+b{_CkNazusTEVie220^y08{QX(}70UR(g7uSV{-J#$^@IGSn*7K=
z$X}D5Ln<qy`3EUS^IthLN`9bCk^I2_gfhne*fPdH=q2SJ^p^5pS;q2DEStF+`3JqG
z{Da<8{wvEE|5M8t|IlwJ|1-fy{t?FZV{i_cQ&vg&uK~Z5&huZ)e{B0j)gS5;+y1nD
zHSIqkMt)K91^%JkquPHgwy&oBwfT{MkpCC9KhTeA|H^jxmuh-26sx#7Wi@`(|Cwc#
zer(^{%2@k=yr}-)RyGsTIgsN7nhtWO?FVv&bl|@XXAEqYQIHP$hUvgRv=h~TXg8XF
zf|4J`ADSQ98PanIv^$nhMwLzUqrP8<?F;;m!uCb|Q}SO2`H_E&NBsvnwEZWPK@W%W
zuP+<xr~0n6Ka@|~f2yYbsQ<tpZGWl{Kqr=e7>8;7U|gpBFHrK=DD_{Uq}S5)9I`;+
zzqX9=e?O)JU7BtmL+)4dFIMu?bl`6><_G>CR`_46@c%IK2k9%2Kj8miO@8E`rUQM*
zPx-%J;eWBhKhUS`59L$-)$(hheB>YMPxT(tQSJ+r_J@8!+ZW_Q`A0p#_EhsPQ0lMB
z2l24|YZd+%DE!lOs2@!S{%e%_&r$M6(Fgt)DE!wb{6l`qKj2aRwdEuK&@Qz7X*$rK
zL;1)4oeTPnaA_sl?=@wp;K(2JSg?O;0md2pe#Kv`_%%Vdi26VM9YT<wIYGb!`Pc9a
zo{2E%|D2fgnNjIbu97|-@sWQ_2mUG%#{Q#C2l|ytdJRp7aSGCrzgYld`(ybC-$v6p
zGD}IHj_E-EHcSWlwKSdO$M!{fIQ~zEbcEIVS3-V-r(-(g$NHiD|HI}V<b2Wghmqtf
zwLgr2ihRFZ`@={Gdiv$te{9)TYyVNS{$IcS|JV4RP^Q&?auKIJ6@sW-48A6vZKTAc
ze;H7_P=wnlS{VO*fELEzxX{9QCPxe7zu(cqknGgLc*IT%<Mk;m4CTAEFwpCyg`xaR
zEe!NKYhfUlrG@c-|7c-I@2!P%;9q0Jz}>Yl;OA*!{Gkmkd}+_idTC+&2O3)V3OKi-
zg@N7xEu0!7uT)!Fl>7j`N{bKt57ff&LmxgZT<jk_L<<iEr-et9j40K@_-~H1@Q+4~
zF4Mwez-i%lI4*&>1mY5iOW=P)0$;{Hh~va15SKt)0&xk%B@mau|Cj_;lH%jU`v8Ie
zfl0_oLSTzZCOIW4%sq=R{AdtOhyPamtR3K7J<&6KJx%Ybq@N`ENm_h>XUD+5M7Uc!
zJ{BpH?(OA~HxSO-rj}1$Mz}`=j;ar2o`dP|kCD~-!@uE>N&b>{nDSQ;;SZ2ya%nrh
zeh0#u`oz+Y!zkZKd&TAPza@{aIGo8c^f`@d=Dl_eNRqVxhV|y?C_@Y!i+>R~n%G5<
z<H;A5fPwyx=5{nod*}iIIE8<KlnY0jW0JHm{4%fshU^QvnEG81@9QS~r|Ey)di}TZ
zjgL8-b+K|`)#G9)Un%`Tn84c)?{<)L5t-aU>^B$nRr&wsO8#z8GCA<9QM<F4qCoh6
zKJ7^?@Fn_x6a~Bwqa_gw!@s17a#52Bq{P&Z#HL4O{u)3m{jWjddz}S-bu_B}vBUf4
zQK?`nU%dZE6NFvKXcEzIOkxN7o6+QA@uO*fbtsnquTJ6no#XDwk#%e^@;h*tuAQ*~
zB>-vwKh(r#0@^+=L|{q+)0Fi8HQ3nY7ld<U6EjmxFXb=(6_-F<0&xk%B@mZDTmo?k
z#3c}yKwJWG3B)B3mq1(saS6mF5SKt)0&xk%B@mZDTmo?k#3c}yKwJWG3B)B3mq1(s
zaS6mF5SKt)0&xk%B@mZDTmo?k#3c}yKwJWG3B)B3mq1(saS6mF5SKt)0&xk%B@mZD
cTmo?k#3c}yKwJWG3B)B3mq1(s-(v~<e;Q~XlmGw#

literal 0
HcmV?d00001

diff --git a/src/gb.rs b/src/gb.rs
index 954ecc37..7ab861d6 100644
--- a/src/gb.rs
+++ b/src/gb.rs
@@ -3,7 +3,7 @@ use crate::{
     data::{BootRom, DMG_BOOT, DMG_BOOTIX, MGB_BOOTIX, SGB_BOOT},
     mmu::Mmu,
     pad::{Pad, PadKey},
-    ppu::{Ppu, Tile, FRAME_BUFFER_SIZE},
+    ppu::{Ppu, PpuMode, Tile, FRAME_BUFFER_SIZE},
     rom::Cartridge,
     timer::Timer,
     util::read_file,
@@ -50,6 +50,18 @@ impl GameBoy {
         self.cpu.pc()
     }
 
+    pub fn ppu_ly(&mut self) -> u8 {
+        self.ppu().ly()
+    }
+
+    pub fn ppu_mode(&mut self) -> PpuMode {
+        self.ppu().mode()
+    }
+
+    pub fn ppu_frame(&mut self) -> u16 {
+        self.ppu().frame_index()
+    }
+
     pub fn clock(&mut self) -> u8 {
         let cycles = self.cpu_clock();
         self.ppu_clock(cycles);
diff --git a/src/ppu.rs b/src/ppu.rs
index d39e0d49..1b051ed1 100644
--- a/src/ppu.rs
+++ b/src/ppu.rs
@@ -242,6 +242,11 @@ pub struct Ppu {
     /// first one, preventing actions.
     first_frame: bool,
 
+    /// Almost unique identifier of the frame that can be used to debug
+    /// and uniquely identify the frame that is currently ind drawing,
+    /// the identifier wraps on the u16 edges.
+    frame_index: u16,
+
     stat_hblank: bool,
     stat_vblank: bool,
     stat_oam: bool,
@@ -256,6 +261,7 @@ pub struct Ppu {
     int_stat: bool,
 }
 
+#[cfg_attr(feature = "wasm", wasm_bindgen)]
 #[derive(Clone, Copy, PartialEq)]
 pub enum PpuMode {
     HBlank = 0,
@@ -304,6 +310,7 @@ impl Ppu {
             window_map: false,
             switch_lcd: false,
             first_frame: false,
+            frame_index: 0,
             stat_hblank: false,
             stat_vblank: false,
             stat_oam: false,
@@ -338,6 +345,7 @@ impl Ppu {
         self.window_map = false;
         self.switch_lcd = false;
         self.first_frame = false;
+        self.frame_index = 0;
         self.stat_hblank = false;
         self.stat_vblank = false;
         self.stat_oam = false;
@@ -404,6 +412,7 @@ impl Ppu {
                         self.mode = PpuMode::OamRead;
                         self.ly = 0;
                         self.first_frame = false;
+                        self.frame_index = self.frame_index.wrapping_add(1);
                     }
 
                     self.mode_clock -= 456;
@@ -556,6 +565,18 @@ impl Ppu {
         self.palette_obj_1
     }
 
+    pub fn ly(&self) -> u8 {
+        self.ly
+    }
+
+    pub fn mode(&self) -> PpuMode {
+        self.mode
+    }
+
+    pub fn frame_index(&self) -> u16 {
+        self.frame_index
+    }
+
     pub fn int_vblank(&self) -> bool {
         self.int_vblank
     }
-- 
GitLab