diff --git a/20260521_next_gen_steps_egress_review.pdf b/20260521_next_gen_steps_egress_review.pdf new file mode 100644 index 0000000..2abf601 --- /dev/null +++ b/20260521_next_gen_steps_egress_review.pdf @@ -0,0 +1,165 @@ +%PDF-1.4 +%“Œ‹ž ReportLab Generated PDF document (opensource) +1 0 obj +<< +/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 6 0 R +>> +endobj +2 0 obj +<< +/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font +>> +endobj +3 0 obj +<< +/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font +>> +endobj +4 0 obj +<< +/BaseFont /Symbol /Name /F3 /Subtype /Type1 /Type /Font +>> +endobj +5 0 obj +<< +/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 340 /Length 27546 /Subtype /Image + /Type /XObject /Width 1384 +>> +stream +Gb"-VO'c,aU&XXIl0duGHZu3I3#c"/W@0?6.9;=+W01nbcW/TNF8[F1c9V&dbqC29hTC.DlIZprcDWD(HNU]iMX;N\:mgjSYD]\E7RE+hr:it*@]-c#'HGWpAf8TN*(-g)a8Dt67m#U+j463n`f&4-XGKFgHU+bUCn#U+j463n`f&4-XGKFgHU+bUCn#U+j463n`f&4-XGKFgHU+bUCn#U+j463n`f&4-XGKFgHU+bY6PS3:)!p1u#Tj'9=R@ePgW#U+j463n`UbhI(9E+7g.!":V>[*S3uAG2$Y#U+j463n`UN5m.lap8!"N/sU,2^!bb63n`f/DSSk!/@4,+G::m#U+jPkg17KL8$ULKFgHU+bVE1cl2t>U,O3J&4-XGKIl=WLNE7o=l8%np?Z^dt_I`Dkbuita@,2)9s[&=d(!J\#EiYG,8kDa4ROKFgHU=jU<']g5T-kjDMK7^g?BF"d*V9Ph&4-XGKTHA.-.j1/\7Vo7Qni-Mgu"2H']kSD@l$trS%lrekR@qeDa4ROKFgHU=jUReEp-ERAV8/ard)kEAbLBa^8`oU=,/_d'6SJ/h=DkN]hO.b3KNDbHeQSp#>u'fd<;!Cs&AlN[g;9$\f'.h3_e@-$,T_9%h^])(\Mon7*7Z*JE2Z:d54t_!f3tRE$M`lkJj*8V>8GEQdC.,#(plYKLID>,2G?eNS/T!V2qthuN/_+:264"';@\uG7'C7(H;]Oe7%g;]4i#ZPqUjkbbL9`Hns%Nb%=qp$=>gn5X4*urMq";4Sur[f1\f(kOj[j6b9A8[%S]]';O$+<\m!8N\K'%2@"2S6?X.fRe06?X4k`6Q1HR-c:YWlscr]6f+Y5.NhEX6GeTAscYr*(P;<)pP1;.,,3k(QLbM(m^`KbXPXKb@>jBJL"$s>r95R8Nd&4-Y*crkt6J_)>F:0nnW]2[I2=GUMj9e4`2gP17kLKH_t0_Z@WRCm*&/V\,6-3a:ZYHKOG$+m$?2j3F;RX4"G7:*GkD6ZI1o\VLmI)^piX/R3=",\>t3V>!kn^8+GnR-3CmK\eumdqSW$OH\rLS;0/JeMCHPH7tTl.Ji%T\DCV$El?LMaAKUiOh],'^HDA/&]N#NOl*'3[>g4-Mj<#^-'f$e"%!8:KFgJkK$G"q%6>u8JH=YAd&0Qcq_*3e@(B0kM:)88':5Gj-6m0ucFlO/=iDDEVmCL,p>2HdC;c:M[C9Y:V0pU&gWK=&?:ifhb^=h+Uc56Bkm]+*Qcb"Y`qJ53`M\'8hT6@&Z+H\d0,QZ*3d*6mk&@[dkSP8D=k6:?Cas>o2`]CTc"Hm=OA5?OKFgHU+bYp\SL)o-`-s=JPkO*ap"21mV!m"VkfdfXV0I%6S,F_Qkff;Zc);q!%SZD.6Xdo=BK\pI+LM0_-Xqol[+ZkeNn-kXC8BJ%8!]9:nC^:Llb_aFY>CVhAXDq\0qM,Pbg%J7mp+fYo20:Y=5lG7cX'J*?1./4ru#JUPp%X!a1Dp'AjII.\cJ8s-k@qE9e?%\Gc^FaB6mKt$eqIds=A+bUCn(3GjADZI*HVE^AT_Rn+cO^oQI!?m+-63n`F6&4:sL71q.4?_L1kHd&AOtuj!l/TlLH36J>+bUCnaFXo8!=\(\*-'Uh1qQd`N@r2u5bU+ZKFgHU+bVE9cl2t>b#E9j%fDS(jZ;";"Oql!!&BNH37VckR@MZd2N&c!+>fi%>]k72ZO7TDZBb;JGGs!+=;j"!&,,#!It?u2]=5i4_q;SZWVsfFP22bXXKN0+-jC6VUV5(dt[s3WM)6n-AL@W0GM2$J;)0&3[/D+C.e)FhE;q;iR4/X(6R3I[sTW+#;s4g2>QMU3+;YS!%^#"#/nI,)ZYVe2ZU'IN@?*(Y,:-QU?]8p.\4e/oNpRXI_FSY=p$XkIV4hg-i,WlTihKiB%Y8:o@ZlLeL&"F@ZF>]fP!"nWq/"I'd`bgi@b=H\8cLAWpW%7OKh#4Js`>q<2pKTecqU2fMkeTOYmXjMe+'Mm;kI25D\oC,VXRF_hKi:=OOF&sa8`L?2V9iUgDVYC:E1`ng`'F7UPdiikI`Eb_DWg#oYsM2=T`':8KY!s%"BDa6u)]`DLhH34-TPgat>nR(SHOh3%!hc;4Q'Yes9++6"uGB-*-%27T@lL,15&Xul\iS,2._hN^SLKH_t0Jc)hQp&4C<0KZt8pBXTHDZ%AkaX#c&R3LadqA::b[KZ,#*i*7cX'."Uf^QX"eBfm'+j!41#k&/.EL!#UUM!5JY9e"[t"9?QW,cD0+PH0"W&QXZ$DHX[KY9A;'[Khs*,`QbWoj.[+%As7&>4H6T+e_/rC_0^(g%C?LX1XqhFef+`1Q6j3sWJ72eQlL*'*HDjtETAdZj=bGm=6C@p4GV=`&Vo\6\d"U/.jot)@qJ/J!5KTH#/nGjW])od1#0#Q=B#JX%*sFTTJlkT5\Zj/&8YYb44--`*n5+)&'oT?5J$>AoUN8A133Q=>&*BjNK4BQWTMIa-hbDkKe=!l6s;.DPFVLUtj(:!m+k)ZVPq]`BiBRHb7Gp94p@:!*'H%JhtBWQUq"M_H1SngZTY:7K=]oI^;ueVFeBa44agJmoJgi3eqTm3I6_ETqRo1p+h=)J\b?M?_3fB,0VWS=F$risQ:rS$mOd-'Z`GoGIX+)VX\%$[WO_jH.o/=f(B5a@TdX@\q+8`7.=0U<Z/+..*qlDt%910C&SK!)9E#9V&B4HjOmb6!MB(H5WmdFI>ATGdV2dZV_Zjt(/8\FQV9<\8PnkNC(?Yk7mcZSGUmcpbDie^)PKJ]^_l(d3h]eMZ/%iQNo8>PA?dR.SE7n+&8QbQ5PF>c-Q&1nf-;5g4cK8'ZpG;Nm/a"W!!+ZPP!AQ>$#!c)Y"or%gSqPEK!!"G>#/o$<)ZWIW)ZYC;HKYe<+;oJ-@UeO1J>DB)09!!!"\46R-p*("QD(lI/M!!!#0KAM.&+nPthU$)pc!!&*BkRGTQ6>li@MV\=5!!'Q^hLNfnoEKi>kR@Ll!0B;+%Ha)Mj5ZkIpR1FB/6lpBMqFH>77;pEe9V4H8?&W^8G6L9/;7+t93%r2$I!?fiNW1M[\:/,-O\+4KWnT*i@]2mK>("g]AB_*=c:2R9po9+6aBVPmZG+"P+LAT/#&uqla4T*[(?FJG0qZBe<@gmm#l=R3Vkk_Z90"O61lKp#^%H*X%N9&D/nlV5,.XE9Oj3Hje2UtD=m>Wcl2rb!$HL4)pC,1#4orgkRG0lMH!\Z@MS9i2'.g^V;9m4Wua$Wc='a+c\0L0OQG?_Pg1F@=Io+"q-@:_f$9j&GEX#<@j@,W1T8g7)DYD_BZJWrWXc'@E`ZPp1X+PleJUTTKkM?^,coXC-!C?-Q2VX,AsH@&V&otEa9]WEgRci3@(BRQ$.ju.O1ETdi3!!&*BkRHYlH@u$(%_]^7G?`m@kN0n$fSRW@#3s%%e^[SFM/[sbr9%,/1?t#jCiA8(9GIklgt-AhiFca[G#(a(EkC/)*\u'P2i'VrU4(N@Ca!^kiWTQu7B5jUQ]gHh'e-JlG+G*ap$`0E=Om$J!!!#?*LZ:(#0+^8mlW4*$`oNDZF40=I,"/cpm)"plp^5oA1b)Z8%,TC+$QkKKUs/*09EU^B\RDXfcl)K@[E,DX016#7'`k+'Y>lR`iknh*jH2[+0XOL;R2W[6EDiESia0)I!dpD^c'`rZt17qX&.IlZ2+2^?AIt7'>%MRAHu:&D!"X"/%GFSP)DHN`/JQ[]D-d(KG4&3S-/FLK&%eSZPk:Ui!e%I6Acg)]2X0g3pUhXuAmoNHB'5<5UW!2u*Aom_*ht;boRUq<)CtM8o2mm6cbm.fHc8`08\\9NQ!p]qpBm/$`>>h3E"/(d4h!W:`__LUsKkAIc8s1OY3E&Ue)8Tb)6OjR!&PY,UW9F.C(Nib[tDm%s!9Y:-dR>Lhd+aeWuQ3jG]+;I*+U\tK&2R3UUN;W7=;cSj8nRI6?*6E-igZ_=Ut6+nHgG"Z1h9raQ;]A#jQ3ZDhG-`eLV,J?Be=:t8_r/pSPL'tF0;%I`.Cag::ErNkb/&GJ83c\>N>s!/9=!+UsONnj'II@$Dl2rC[W?AE`D;7FdcIoR9<>UIB1C(&k&$Of;VFX8N:t!XE?"OD7L-3B:=gXQ+l"*anAdJLsper:%h\hB4L9Y&fS#brO=Yc0Yt78)JLe4\n66pqVF+\DG$OV5VH-iN=HHojRO_ZTd;]Xm>ECFg^rLWOP$1]GVSVQ"SMt/7#B\0)n1HUK8Lo*;P?+8kJ&nHDEPXj:)PmB]o#C're.B8.qL`=1!.4Va!PamdsKo>.QlArk8DGCQB`$qHM++c8k.-_3!Ym&!dKjef48)MH&0c/@>%nHb0)EMqm/%8Nh$1o;[S`;N8"Z%!Hh'KdOt]"1,`@U"6DXW]*bXVPs"8(InC>+*YP_3oDJpD.3?,oM]%Xkb8G2dG=(-$B9`Ea&62s<5!k.c(00*_,O($2ZNgX^uCIGhOm,)_l'sk^(N[m#dnP1]t6g/^YjP&(jY&Tbi/Tqe)Si.?gXB\9#UAlmeG(Kiru18qRKnZq:Kn_fi9^^T4iD>rCT4WkVphC_r7l*Z&otaQL3m^Y!@C1jghk8r)-']I/L<.ThoEEFS+aKUI&elo]HaEN/Y8BNT>q-QepLW*#$q\/,+jH552Ri:$7k8?PE0M5I!*7g9o7rcX2m3e`WB%CF=7cN//4\Id+F6rI'%E[H1lnk?U0WPqM(Nh#5(7($b8NY?d?LRm!p$\jq@7JSh#^KZiu_19iD0)S,F9LH-#jD1!C-ilmqCoalI]eN^HMX&o"K(LGb#B?uU*4SRWPdGK0:hQ?bIbVRR5-bfHBX)Yo@h7NEt#Cisf`tQc`^J4\&lDr7MrG60"rlQb;e^_4;oBb3(e4VI^N9q(]gjQYSTDN,NH@r6Cahl+"jt%T+(ll8C]f5fXHKp'oHDs`XO`f[\']eNYSlaHhL#c&@Ge4lbg7/&kA+N%G#lC*co=*'rYb[k5`L3h)6I>WH1U0u2a14P&+f@f]NYtGSh!SF%DD_Z(LLFXEolo=59Cd^HJmu*%NIp_mF-$92gX(8mY+^(GCWgERYcD>"1;5[&_?@':A3^oU;3]q7)*sAc3rnK.6mOKkk[S0k*Y[a(^k:Z.2cI,PH[kG'`g)J,Bg4PPlY9[r<8*uSKN_OFb(9>u_&X(CU(&d+J`&X`!>\n>O0"OY\FN>DJ^tr6A]^f0@DkOfA?r6UXr\E+FoW)UWBko.0d17;rp;J;?nC3cIiR,12Vn+(X4JpAs]/G/iRl0?[q.P>'8m.leASD'k2RKJ9FiW-QMC[5j)%%mo,%&F(Q[A1-D*bWTM"3\5c]oHu_m-^;-lQBmfh`gufmH>U,e"V]'_2]MH@U]BeF2bTb'T#5/':[2isq%VEgAgBSXCg3@iSep3VOK-DJANM,\Ok3\acUi;<4e]>DA?^_8.%^:ct:mbXbQJbF?jhbr:B$]5<*(=i]n+JRS`]DsL#)e0!!'RIT^Ph+%PZYgMsB>pXVFb+^pDtphAseXmsfh5A7T6I2#to4?i?sME;d`l?ar5M?%m@;rrV)@@-Q:SUBHN^N@23gtkM(h\lEVbdA'Zq'fQ2#oKD,c>K8C]LJ*WBk6@7gA=mK2<&,M2:/(tqUYJsB7H&5,US9/4VDrBoL.TCol2e_C@MS-U:VTYMPpHF<,pBF!I27M,IWAB_*pMhE^8d:31ZJ6>li(!.`jeg"rAR0YVJKQLeaLmlPr(=P(pY?d.n;t#d.D,E2h/8cQM2hALX4QED/695oT>^-c_"u7H*2#IcF=BDcp+!pD\._0l@=&WF6Cm:4+=&DqtBXA!NBW(p(Afnm`:g35XBPI>"NC;;eIJCm#.CK/[CV38'VBSk*ls)#,$%:`84nrHE4Ue:S'XTZMf;R86:gNC5;;%@6`@E*>fXGFo7c6Y,t`[FT#bBZIL=XKs6l!R;lt9_Kr6gSD67kr<3M,Y*%#";[qL"hOpgG0X`^_(tT_*gA;UnXXp`7p6M.BO3cA?OfYC3m?P*8ML&t)Dg1jbNn&I\KXEr=l2'[6>$Nq5h7f2XTR'bEbGP70h&Z=bK`&!m8q12eW%ZRQGX@:oXKJn;6(N(RX3E,1lC>6a=DNVgbUU1b)uBC8g:[UU[H`lp0^5*+0CITO5X"J!8t+Q^Q@?TX]76p/kp_f??aUGH'[4T^0)F^23f8OAXU=HDgM*5X8%ohn5%CZLBZYFe%#AUNm2IF2sb*'SQh\eOh5P3RH^ZJ3on.SGGCIC'%%)5kcVid-7M-$F&iM'Sni7Fq'#G%tB$0K_O*IGgVVBD(8r.W8OT1RGeX?8b[d7)+iR8Dng1g?gs0XP/R;;I(OG3@>iUM7l2QL@th6UZoUo12!q(Ff767=eX*p=P.4h6iN+aU*(\o6!./+]q^9-/p0_MZCE5$6#Ui:o`-f*6ts'=X%EW/3hgP*8H6IfknV<>Dij8HVDk/,!!!"$ZKBgZ9MS>h]Qiu)_1Pt#CtZ76>;-m6*ZbfF68rf;q!+o`&s)@8IM+m&+T7h@4tbNt?TGDUj.f5j;X4dB/>G-(!!#:PQpYdG3B;BYm+GHHH0)jI+0;QFgq7q(_1N!=T0N@25PuZ48$%V1F6a:.M8VjE_pfV85.)!FRU-!rr<$j:;.Fi6*dg4Eg)Dk09Ca\DrUh`l=cDO+-W<(TG\RDm4=MDZCW3h>dNU!4a=gD\*0W?@2(I!mbL,,nW059k<&.Fo;=hOBl6]teRQ_9PGOE;+,4I&2ZT#82ZNgX5VBQdj2-?LN=O"B:8e'HGMiAjTDq%RkE^dKVpH@_8^SIW!!'f&s8(@:WDc$2m'65a]nM4lj.P+jl8!!%PsqYPd8o[D?[2ch"[depSh?-tY[]`BiBoEIQYoEG9q!&2\'+*;DQ:pt@Rq71RedlBY+!AQ>$#!c)Y"onW'0Z())YMXj0:S)joa]*1M8;c8SQl(,O+=VU=!!!"0&3Fs(5oPLs6ag>)4K;RkRE?)Tf2t7U,N&P!5L]k9a0F!j2F+KZlD4'o"Y/]YNM5D#/nI,)ZWIW)ZTj<@*\cIcYII3n`"&>X`&--2'XoQ2]sH%h>g*(h>dNU!-<5Ni6-?"@EDJYTV4:e-AL@W8;c7(!!!#Ve`"ELh0%>?XfX6fI*V?g`5H2j2u(oV`mmF.?u\0?pBJ;3!!!#IFQ15iJqEomrN'GQT7!SuhLLu\9_AV+:PJsaJ48S^.(oW]!!$VNp?]&L/+7iLV4E,7lgKd:$G?Hp0q%;9!$k!O!!!#'=:G]6.OuT]]:Clu>Vpb0oEN7Pcl2t>U,N&P!*FVdhYc$nG!>cd]$ES+b0CXa%>]k72ZO7TDZBb;YfOKNg9/T@LX)JSQHb-3)\dNU@*s5^Sc?FiXue^,pLSj%oEN7Pcl2t>U,N&P!*B@fq8?K@]!AU2)2#XU@93")`5L"A!/@4,!!!"$d/NNirYJqCEB6%;N-oU,N&P!*G'TrjPt1=Phd.P3F4=cl3N06Z2rAMV\=5!+972qWi[T[;js0a!W2dR6HudH37VckR@MZd2N&c!%]70D0pa.e,+K=:RF>/3:mEn`6]G'L8$U,!!$+4qYO'l>'8a%rOYcG8Q,0b`5L"A!/@4,!!!#?I4r7;rNXDohmd[BoT>q9Da6u)]`DLhH34-IJ6[9oFF-GFl$Mm,R6D73oEN7Pcl2t>U,N&P!6@m?r9:(QO6kTnPj'Da#JC-m1%Y[#U$)pc!!%&dOX&+8q8n[^rqMhWO!IHl#/nI,)ZYVe2ZNgXa=5`DSU9kBTC1H8Du*#='7'ai@d"7$6dZhQ!!))?rVKrLm'64bYP+>IP4+:bJ=%1""2Ib*"onW'\R9CGrSlfOmHq*E5*EIM2]sH%h>f5D]`A*5JEcdqI=B(gZE^ZmVN595kRE?)Tf2r[7;i/+!+;OP!M@iOiTS[]R6HdZU,O2AL>Dna'YOMI!.\3YHYqB0O04].:JDqQr6ZGp'16[<(lI/M!!"/=IeUH1%m8e6m+c7M82>nUJ=%1""2Ib*"onW'=a'"*e&)^"]m6s1oTkR@Ll!4$3YeA$>&",l*d$kgr,&/.EL!#UUM!!!!A45/Fr?*gCf'`,oCOViZ+J=%1""2Ib*"onW'7[odTZt%.?DnPV7kp%nm]`BiBoEKi>kR@Ll!"=#@A?3VQr5t989HT+bkRE?)Tf2r[7;i/+!+;>!>;u!,]u0JNA-'@E0er;)J>)09!!!"XnDT.@hg=fT++87JoTkR@Ll!&AHdmN#(dosj8(a=nmnfc,KV]e"(Ga'%0/cC)ZTj]k72ZO7TDZBb;0Ro:MQRaR.g:r-Hkp%nj]`BiBoEKi>kR@Ll!9U8mqrk`fhXfE5?)E\$7.^]%`6]G'L8$U,!!&CI]"5"jpHENl0MFuT5YMU"!FR=%!rr=O\JZ7Rh0\TR52Tet8!48D%>]k72ZO7TDZBb;0F%gm1d:d"]#i(;VN2umcl3N06Z2rAMV\=5!8or,R(-+%^?4TCD_c_d3JH=?Ql(,O^ejY-!!!#`4*BiO%`e-JYhSf4j;N=H@UeO1!(5-Q!!!"pWM\g7(LGa3qClq']`BiBoEKi>kR@Ll!+lrO]P"S4rT6%/9clsPU,O2AL>Dna'YOMI!._e3qs(5_+.eo.RQcm>7W1Oc%(u[L.!bsq!.\$dhYamjFQJW0c)-OIpX'ok'16[<(lI/M!!",jO6=:\%ld$J4u@[5$Ne!B0er;)J>)09!!!!3D;Denjd11[5)]YuhLLt1H3:rZ\^h_toD_;Z0f9:h@"1+b^V5,g)E*)9!1EP1h[H!gF2e"u-^Q*HfF7"sbc!N7l#4:;smFR,7X#F^=cMZ4E%!qlO02*HT2Q73#rfcsYA79,uoEN'ZF'*jX1];1lRF8D6>4@a2HI=Q-3S5:@IJ48XX8iXUf3^'*T>"q6IN@aUBDCK`IkcAr!#a;Tru^-fk3lQ[g7B7t*r8+Mf*j>l(OU_02]rJkMl&H&rF/0CQo"\Y!L`MZ?[P:\&0#ot5c-I?Z@^p5a=MW>Z3Wd8JDZ3Wd8JDZ3Wd8JDZ3Wd8JDZ3Wd8JDZ3Wd8JDZ3Wd8JDZ3Wd8JDZ3Wd8JDZ3Wd8JDZ3Wd8JDZ3Wd8JDZ3Wd8JDZ3Wd8JDZ3Wd8JDZ3Wd8JDZ3Wd8JDZ3Wd8JDWJl(1f'`GG._@p!.2MKYpF;daPiQAkcCQ4b@aUH2UYgeMB@6cV!lHOSBbg2.O:$2+T&OA]*EOmL%R=O[SdeWAqnLl!t9)j!K"H@n$lpOJuQpJ2QjeHfTi9#=lrO9^3%)Ko_b61p"uWd:;K'%@/TMF*fI]hXk@%9h`_#'!cW]g6:%O[S;-HY%:k8aDbeWZLs-Ud('QVph5'V:O0NlV=-ZWkM/KFnN+bp(UOa=G;Z=l/;2gXg:IXg+d;"H\5S[d;o%@MR1fY"7MQVS(@C?_jF0:6OqUtP)D(5a(Rp6+\M6F('Coor#lgtsjd\L$+'`]ZFqhlK1#pCVJ'TJn!O+S5lE1AV$jl'X"6QLAp'AX9R32?mgZnLjK#a'.YK)>,s[D\/6=d2S=XNX6jQa#2=aGU1@uk.RSU&=LCRoW61aFTSeq'7RAI`-#eXfN;F\\qm/.k5:Dj.)C7TXB@?;r@!>ERIal)p7<6#Fo(:Ws13K2,#1Kmi7YG'AD'gFK)tQ4ij!UO=;^_X3Je%]^j[AWEJP2XM];FXDf:YtNRg[ea7Pe^A4b/=.VT=eX)=mo!e)d68.j-#-ka1MKIo,*;o((kYm1\Q%&2fhbeuTO,kU];;^E-L&]DP:g-R'rc6i;N7`o@4uY9(XZPEP]X^3+[lZLoS2[405GLiX*6s5FkeH@[>H`bgtpt\HgWL8.;E^:r8SlK)kCYIDkPtKg1pfUAh8]]h/@f(9aYa.(a7f(^keQUD9N_/t2SL*R&PGA:Ogt)b\H^IuDFQRW$/.L$tRG/\9EJNYl#>;Y>IM"Zh\DBJtW:JP$dbd6r>`TghLX,?#4J=QJ@7VC>Zg.SQ2`EhFNEN>k?c$Bn^C5:?MPg8<6G4mA2"0r3<&:4M;jbKmuOd4$cTak2=BlhmTqO1S]RS<#Y,fA?>#[flqZrM$<#Wk0fh?X3[4$:1%"pV;'Jcmi-5Sp.1@A^SP2K?qKBDTk*]%\GH%+>j]3k.\8Z]#q=0n@#;+%gTZ!!B77SE>1_TdlY>mFS1V$V7^gjWB0i64Hc(9>Kq_?a:o>+%U:\r9'H.>iNi<7j;H\d]d*gh=l0]WFEdLG)Mqg6Is8KikfHoekHS@52>@J=Qm<.^d<_mXXCU:U-L;K(RUHS\t)Y/W;-FDaN>nNV^B[=2YKjq9u@jj0:eegs3/#HL]c779)ges7V)@Z6K9fh#2P&U'&Q7!^BXM$pd.S[=.N7c@um1&XXQYC[%`Pmkh?iMc^r_tWIEtRa:ML%'mYO$#j;h$G1H(7HMNMJXuc:t(?&LQr"8N/,iD,k5H:35PY>@@=%(hm;HS[G87*L#oDrUQc0h+ObVV]%h[^/j?`ZjTN.X/jE.d+IQILQ$O@L:P"6pV6`VFYCAmXujeK6U2@6^*FmMX-_6Xe1T3-k;rUc`O%l7kW6_'02i(,ppBK>96b@+)$gRo^'paqD+ZXsWGCED;TJc9nl*4F,;CEMnIoCIHZ.B3F$UsAh:m/trLRE-8hfX,lYt+>h]:RR'gK:78cO!&oeQHGiKuX%C',?Kj+nGU)`I7;Vj<&QShE!AS_^W4=%sQQ&oO\'-ZdfqG[Qb8YHA\O^$QA<3N*A\e[utLjWE)mOS@)ErePAD,9Hr;,Lapb6u)Q#TD;6b\Yq\CV-N"kd8XJp)n72C)p9:mR4$mRc(8/_Q4]AcF00V4Si,;G31hiT`30agsL#oRo6F9Sa2Yb+4OGfcEL4VTMH';m;(usIA*luDU%&uk4T#ZrpTk.h955r_bsZAm.DDTXuELc3C/PbG6%SiZk?"I,Y>G]<>pM62S`(S]@2/Fes5SK==#XC&5N&*OGGD3]h/X9Bf\,C\ngZ!EfP.\@k0bpYZ"7s8MK,dm(T^:iDHX"%q?3J,0[(o(:**Wp78aEYM-?X3mn06V)>Q=66uW39O8!9BR1W=$-(bo7S#GHZP%nMr(iH:SK!V,qE9dKX^`*/kSs.^r]fVP,qZXVa/Pjr;>6uHUfT87b]U?ZrL'RoUYW@;V-ZeUf.lMRW3\m_@Ab#/TA1BpYP![96DrEb/<.s':nN<2rMVoRH>/j%\f[o`fXc2ioB3-rnOkp6gXfH.P"`"r`m/5IdPJ%+()5MZ?>J&m?dD7e`tI.Sp3[#F61OZdCiGKV`+SDIIDN&H^Q/Xn7PN+)YJCYYPRV,,'VY8^O>D&(9%el>:V7QI04nG5ui/C<+jLf>@#A\C_"-EiZk>%HfqT])lW,k5gi,!Nd7^_>IP?IHM/jhCW[dsbH5HF4tEldr>$rk<*qVV%*@D;,j'Vp"1ZrH$f+FcP2i]=:i'=h/Qagm#+MZFD?'_bd[bo4CKZiiYi3#dqTW5qO$7DG3/sdRDCUCl^^L8-[DKFmIU]cZB-bpLiPLZS!bG%;7]s4nf5!CXb8iW9?BlliVnfqCn%&Hb@_\G@.';96KpFd>?TK]%HgKJ(_3nOkUsn.M>7Jd9aU()g+cj'CQh3H3;t.PZ7DTks(hgHT*!D8nu&&Y\C<'8p)f=+7;jnF/m3Uo*iblO/6of&f#[8KWD"C]tYR[Em0[qoEG]+2e_GS$:V5dDc33h84Cd7'r>f[9b-p+oIGVnWB*35O`!Joom;FJ-DJANM2M"`fOk[T7fcm=Fj:H(-+:O*Du\a_h;sR\%Fcb;IfDZ_hgc]<\8?+TH3<'64$nX=DieG\EB@.$[!REN@n=3j8A92:dl"_hFJ,@a+/^jQ%`:WN!PAPG62R#46#)SD.Z9c1?XiHm?eQ^E]0pg`>oa[Is2pcu7b0E1n_>!+KDdA3JGadLIB-(NZouB2Y>ae4SE#(HJGkdhn-eD*e8!`7nKL@3-SJqC'c:P!Iqd#CFPIXE0pt=(h7TP*Q$dIgZ'%>_:_mm>N\7\jI%hM=YRIhA#dKb]_q5!Npmb8a0:@*IX^An0#hg`E$Kbp>0kDddsl4%hV;"X5PdsOE;V;5d!%fn`cE_5GaFCI;WTY>2[i:K]F7K/rghkZp=2ct%4J]fiP-!@T&#"=M;Fnb7RhWbqY,\tY$H_pFnQc+sU\Fjn"R4V%o.)>)8.MJ-3F,\!4?sIW3pjptP:V9t=;haKFr=(QpE9)^32;5Yt':1-)HD+*nYc"BHs(%:uehQOoVJ\Wu\$OU&6^k\SWho$_U:=??+/#`!btB%\7sjpMXd>)<4pTVEQ`;u8[>KoNme[^d^%WT8Tr4R!rld/oIeHp2@drP"aO=.k<#'[#nasGS"%fu#<'-5f9f)*q0^D3)Z`WN]eEP1^B+%E5*[AqqW3_e3Y%,n.:hPVG_bN(>UqK(\8aR80TND*R_YY$rn:MgIPSCh1qL0ih]1eY\dcenfDS8@&@nkaaC1BHB7D?Gocl%S`%\)`]kr_KS]he/^"gSE>&DsB.8WqXAr8]4ihR2[K.p"o2N;S&*SHfl-HiBgb6qIn;@[@hs%;*;?96qBj2_gk9:iYr&d-hFNG:9h"fSk%YE\)Nh\E&*W:sq="J*VqW9>=pP`Sp&_l"F:DQeSD:@]f-a=[cD)6XS_9]6@ZZqb2X)f-cfi7AdOaVUotqKN+E4-pW4?XmQo&G$"_%=M8?4=NdW:VB5QFoENd&CNc%HqYf:=rinefVbF\hgCrS<$`+bbIq3k\2Vm7Ld,GA=]Bci-B6?^.lnlJXZ-qMG*9mnWc;nQdC%_b1QEIQOZro7ef_W1-?@T2th-h$6m0R"VJ@1G*W@tN0"p=juaV)(/ghKF2)l-Y+7Vl[KJ"M>ca$7"NhsMI$piU\QdCk+Rl(&O*(AFEaKIn[q:8Ds[bdo#eV:qki[T39s3h;])Dh2WZA#+AjFk2`$F\K@Wp;s)/QDcTUSbpF:IBC1iBlV\%]5UDE:fo5Xhm\bOk4T#:qqh'JXh*uof,MI#lBCF"j*CfE`N&5[no`\=24Ye\X3i.p9*a3%Fm`+*h%kikVb%u%B"bBC]k8YK^iXaf1l!o/e#/e`GNoX#51UubQM0:3lB^>e*>!Cl2Cb8+kj5OQ/uD01lkSr>9DC/(W+Y\np5X>nrP"b#P84?ao1)3>35G4qRgM7Z+JT?-AQ4DRB>nFO%\aK.ol7+a]^5%;,V)nXED\EaS+(GUm*jnKTpDAKWD+PfIN.,"bbA-W7D"Bip,4^X,9!NI.j9Qm;p:qmgZYCQ+>VC?Rui\ht@ecM4__RC=mt"o\.Af:oVp%m'b!eWD$)hpQV\tJ2Vn\gS'$X@,,QsN?S9!1$R,/TN*)Ts"*hP'g[kO,ciq*^Jn+/_-[q>pO4_'YONM"BgPo5uNI2#,rY?FRJtRImEN4.RfIQ5b,R7/BXU!GFd6o@n\rHW)HE>l'uD.*H&Q'+QHf7AU]k1@p'*dJ/]$9DR53sGRb7?`/3`\1A.aLO!4dsC+>39j7LRC3Q[c*N]gp_GdpIK7loT3mO5@YpoEJEFkRB4YoEIn7)j!K"H3<4TH@r`S)]2m(*@i)bSe)[;Y10(&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c0(]?u9Q'oA"haqu=nDo/&VT!@\V]/c/fF!bg38OJfQS?jWAdWqk"NNo5c,O$*MGEJ9r<5haZg9m\-=KliK;p,BDD(Oq)09!!!"@I.PV0VG3-=kL,KXrt#/nI,)ZYVe2ZNgXR+9TZ5?khtJK$O#Qp-\'7;kFb%(u[L.!bsq!.]0E66E=5Fl2`LDjW_OgdH'G9FST8?mp='!!%QAmFbFMH#r)^rj]k72ZO7TDZBb;n=dNU@*WZHr4'+UjbXWU9HT,UkRE?)Tf2r[7;i/+!&.2cD6Ho?5Q&"*>qpaQ-DEFA-AL@W0GM2$!!'h4?+T[bO+(X,mQPC^8BaD=5YMU"!FR=%!rr=O9\H]2M-"&4cLbP4K&2$F(jZ;";"Oql!!!ndeb*N?62og#qkfE"bNKCkM4lj.$q5(7!!&*`J,J>F2f'mXD10OuO^o-=!AQ>$#(WE2%0-A.N,\:Eb>hOQNK8QuqFIoEhLLt1H3:rZoEG9q!!`8YMr3<6?f;XnQ:?0&@UeO1!(5-Q!!!!9lD9hjDVeXobr8]u+,Q]T%>]k72ZO7TDZBb;@'g:XhL51X[:dd,Qp)/Ccl3N06Z2rAMV\=5!$F@HmbD&OlS`9/#ZhI3][rDa6u)]`DLhH34-I!#(n_ET$B-.ft:r9HVA=d2NW16Z2rAMV\=5!5Mihr8V^rWup33H\^i1R1FTG@UeO1!(5-Q!!!#[Js.QdmIhXpp7u]`BiBoEKi>kR@Ll!-66ZmiSf:UopjZ9cqK$6>o+_%(u[L.!bsq!.\>GYC@!GpY=?ERk+O2Vr*q,Qo*20&/.EL!#UUM!!!!A2dO)kDF)+P8!!YnoT>r%hLLt1H3:rZoEG9q!4cq7hWN__9j`;<)q.DS(jZ;";"Oql!!#%IlIE3O4g]O]Sm'T5U4_)09!!!!7U-j^)$4HIOSip]^82TrC`5L"A!/@4,!!!"$T`4G9I^e)-jHGXbNEnAh0_>Rf`6]G'L8$U,!!$-J/m^[9pu<+6c1U3/KAM-G(jZ;";"Oql!!%pB8&r?Um^i#GG?`<1!5/Cf1%Y[#U$)pc!!!diDn[+C]mE7`l,l9L<;)YRDa6u)]`DLhH34-IJAl&0j?oj:iVNDKZi$L%oEN7Pcl2t>U,N&P!*FXHrY*8i.pdO9kMQ8^,s+bq2]sH%h>f5D]`A*5^g_HG`.9$/aEm@6b?7p_]`BiBoEKi>kR@Ll!#E&pesD[S]DLY\@m;`2hLLt1H3:rZoEG9q!/o]@s8'@`]6C>_rP5UdIFt2]cl3N0ZT*8`5MR7XI1V=`s8L?Kl!)b%d2N&c!%\`@E*mIaX0d!1fsA^S5')#P@U^*CDfDRpUndJSn%=)OMoh[HOfQ6n2jaob!<<,8JhStNU"es>^OFH'oC$4/[e'dB`%7A6+E2L%Q5(qtmi;ba)am&3!!!#'J6]Y5]k'jeo?V?u<-plK5YMU"!HJsh6#>`o!!!!7SDX;5N6AW6U8%:t&)WuAXQU+LM4ljncD!7R%D;cB!5JXn3;EK]D3$jPd-BNdk,gYiK\h6H(jZ;F/:)s$cl2rb!%_eN6TmMS4L'0Qja6I(c/I[S#/nI,)^&e@)oRKf!!!",&I=(SHJsRUJ,Ef'Hi#>npr?5-(Siu4-AL@W3K)`:2Y71R!!%qsTDq$XJ,bFPH$;/(e_[t7EB4o02]sH%h>fQohB4`K"onW'MpL-r^[])ANgF)+q=2M?e3>=o!AQ>$#09s>#-jj\!!!"$XSi(,2f3l:Uu_NahJ,XTjop&5@UeO1OL+-[@RG&6!!'75IJN"#]mHBtbIb*S_*'XIS%)D]%>]k72aGZ_2i%pV!<<,8'&iL)HK'cOb0/BV5Q8Z0s8"fo,/""31%Y[k=nDp'Tf2rO!*E-N_`o!!!"bZ;BbOs7K`Z7ils:rql/6mBtOo&4e]HDh+?HD\*k6!rr=OV(DI,Kpt#>G'8$mqWO\C77DRtmd9K3TDqha6M*W`J=K&H&0#ot!!%PG/Fi<@^A@,qUIUA/n\c)*qU(@qH01OhhKr%!T0j9$5j/Z=Zfhj-6Z2r)!3h^PHnOFYrpTl9nA,>_]^Wd6Ie&]qldp?&F9DYrq*4m),T:t2j=GV0f[(Q;!!"Q]Bmu\kPq,pr*BR;-?#?iKe=QsV*rPZZ:Zqa_PUu@O)^&e@)oRKf!!!",.@\6L0OD>4l/9C1O$33h*I$dHZik-@fB.ta\iM[!HJsh6#>`o!!!"bZ8qO]dJWU.s8;N<:Z$Qkc0a8pY?8'hrql0AZ"(hZA&eY%^n:VuVtiPU-&XtZ#3kW4Pc@oJA1n_8;J("g]AB_*=c:2R9c9@'T<)"V29Yq=hd.+%Ods/Z$6dTIXF,>Hf[(Q;!!"Q]oB?_DEqAG<.DkAOYK61to!W`8[()Udn/NC(7%Q]@f%#k],,.k66rA#8M+%mZkmc'2DZBb;JGIArD\/8=^]+)HKaVh5q.F4X]A'c.[)hRrZL='kEA4J.#l8N7\J@_$"MA\n$DO$?r*0]7kYBO#HeOqk$uVb@`^R/aM==\6f89+nMCJ^ZDT,V3tCZOqi9/?LiB6?LfpCIJ9;5s"3=!!!!QJ&_-cM&@6O]5Z`GZ"(g\IedQQe%'r+HAfa?>[1N>?2_VJ0-!*!nGW+=s7kW8LPLZbns@+%B1Ttr/@aQPC:u,^J(`u/Q'X,:(S,5EDpU\D#cZ6ZlUZ>:86/ut!II\?ODI?d9;fC"%Rp@55:A#s[6D^tYY)c4k)fI&lGSSR$#qJL6HFS;[h$![VQ@WMo%#)3Hn_W])g`iWW?al#U!;IuV]MEWSTE59n2Jq0TG4"#]r-YQq>$P<;=]r:Jc^rt)GOOG(s6.k*^\ZKacC[?$q<+AW^OH-BJ,J=krp^L47o&3=L"I>XLYd4OO.=Kc/mUN7(tfk<\eOhI52;Fa\6_79G1Hm\X``IU]rq&M+Vf2k)MY4@c_OiK\lrBFrdi'YY<3MEVa)Z3PV`k0,r%7;%1Q$F#Nl#i+r)sO8]&s?dQ.WXOK@**^8aLm\#7p:2rJN>OX*GH96nrG-_346;Om-j&n078K7=UB^,!ZFSCh3\\0;P7IFs?H35>@3"*]5GK3NnZE,dBoHb&PDpVdZ?dV(&[+jY-0>0Y4kR@Ll!&/o"pBIVfpV6_k*"IJlfk5cKs5_:cmHs:fp[3J/q<,5'g%*3/O/uNmWh?"-0/)e^]C0o&*33/EY,eV(Y--WmFmCj04B#Tos74>dn(Y>?nDV:M"Znb+hL>?=FXFm"eZX:FHHr*FX3U=RKaSuPr:-UVQhpUV#6tK`pUf=rBp$2&9nEB*"mskB7:ai.=r,OjM;rkrX)lZS9&802?H/d,BrRop$_s>o-W,CD#T`(&KkD^DsDa3JqMR+W/I;J7Zgr-]?`h&us=o1#FCoY.\4/;h`s8Mc-DmeH0EF\h!YQ*1HhOpliZ7p;14o7_WL*Q/#k\VkK&#+^AJ\6<.:fgrXGnT#4+A]`\TW6j!,Y+OR\f.-F4J>NOodba5&b\.PB5Xm^e/*ulc_\Q?VcoV&98W?Hm]J"Na:I/h:NonQp4b3/9pV]ahI*Rq>W\o@U^5p!lE!\0)ebd3CP=>kO4"ukMV\=5!!)VWZb8Ynq:jg+2DHq[*oO(hrII5lUQ7V8i.DP8T0DZ4G`Eb(db3ct3hMZ3VMit^9$:H^RVk,PG:ZsOl&oYJ%_C1l*%'/q1r8B@?GhQ^kF4%?p?^oJMo>NG5I6h2XT89*4-$]E.8mJ2qDs0m1WYAKd9(l/-iJZX=`k[JQD!J=Mi]K-2%:g__1.(f2lHO)ePH7HEfpeW&9D[D#G/p:sId[F&:AHb@'f?Lb5&h*O:6p%\PB4quX@QnBflZr7G'%n*9m(If"^IG(\bmD$lHGB9RE4lnb!70C60/Nm97=4[+5KOf,oQD)^*s\Jm*7q/2;%hL/;&/10ttm06X)2io:Vde0pc:n[#qK?='$hKnK@91t3;mX9J3IQ.[[ItR)m^9ZO#:6T,$rURq&o?#Mcc1ZE-R2ZWB4DLR5p[E2^aId6TS6:n;^Ti48bYI"jV`u"Q^Dfj!/!tXt`2Q7uqI4n4B^MABZ/F]:u9=A#>^$bmNhI6,cSQ9lR(W=kodh?WdD8(AD4$;efkCrX"W7gstme0+njEg-U1oX614;a+,j`VkCp3&B@]n\\?LHFhT.KAAV,JX6F?LA$N=b!:IiG*eDJ'dHR:MS-tU[mDA$ZEHm%"H%DU>=bmlirUu8\0@o!t#auDD8Jk]>=8AH>q_SITT0[=-B@VX*-DQL8R6H07MWi\c&aq0$[O]].8fOHi*CBC]1@sgBUMhNO>Q[c!K:D/e*kJ>aTIe*3F%+2gH$FBD$Fk]%nEd(<0-7]TjL_A`6[.GN866]RMtG\\"IS4Sqsg!ZQ%uY%4"N.Ai[I("gT)G[+PoZlirUu8[FrCJ-+C5<8-[CH$09]P,)7+DErd>"tFJ5K06bs,Y?E8B/cJKFp"!f]0W%E5a!C,QEqN,+M31sS)#45DBMN61s=P].$37MV9Nn>B/bnt8Xec-"r,-rT[IF2aKlgVOsJ:mFor(^2c+4p)s#Z],Y=/23P,:qZ5]aLfa[N=#*L6'Z^fj%!ne#^g?]#hFot=!Ufku\5_88+.W'?]4M-.i8^]/&2kWEZ"-kdV6/L4#^\?iVmsOfYh^(jW1kRr9n@*<0cLnrQKK!9t:hd6Tj?7tikj7ZNOsEVffm`nmL[0Fk$#dUsT[IF2!!!!ISD?H,c#*L]!)PYeJP)Ef!!!"lE-;>K2S/ti!'oN0"tFJ5!rr=O_V#Atg?]"D!!%_b)8!_;J,eu4pE+h]NurM.g1pS"2ZNgX.+ZYq%A#f]l.OOXFn6M70E,>127"5%!!#m.)7tH!g?]"D!'o'###`h7"onWgeEdB;d-Lr:!'o'###`h7"onWgeEdB;d-Lr:!'o'###`h7"onWgeEdB;d-Lr:!'o'###`h7"onWgeEdB;d-Lr:!'o'###`h7"onWgeEdB;d-Lr:!'o'###`h7"onWgeEdB;d-Lr:!'o'###`h7"onWgeEdB;d-Lr:!'o'###`h7"onWgeEdB;d-Lr:!'o'###`h7"onWgeEdB;d-Lr:!'o'###`h7"onWgeEdB;d-Lr:!'o'###`h7"onWgeEdB;d-Lr:!'o'###`h7"onWgeEdB;d-Lr:!'o'###`h7"onWgeEdB;d-Lr:!'o'###`h7"onWgeEdB;d-Lr:!'o'###`h7"onWgeEdB;d-Lr:!'o&fmE3nULQV`3b:d_E!<<*2YP\'pp[>#C_Y*<*s"+"23WK-[zzzzzzzzz5cO](5k0^A~>endstream +endobj +6 0 obj +<< +/BaseFont /ZapfDingbats /Name /F4 /Subtype /Type1 /Type /Font +>> +endobj +7 0 obj +<< +/Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.b047863372ef41b3004606a033908664 5 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +8 0 obj +<< +/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 400 /Length 24486 /Subtype /Image + /Type /XObject /Width 763 +>> +stream +Gb"07D3,]7#r+bfIDN>oR8bod'`SAu9EC/C#"8A$$boZ?I#[5;RjZi[p^HC-@c67&c_>VFGpQ`HBmF0l45n!M-cX)*/DlN&n6e]Th8)M&DX"SUB"C:42\7=Y;O.P_7?m'=IR9s!3W&mX))%SO;,Pm&S/+M*08S&Tj_!n@1C_O2F8.g`(p%fGfLa#p;@u%Dq.E&0CQMi?@n!iGeo)O*cNK3#mUl]g6n!]g6n5!5ddQ+?U,=iGicI*b'%!*[cL-\2O\S_a/P&)ha"!)hc6<+?]'$_W,SH*qolt+nc-!E<6E,H\:fI*/)AG6$S4!6$Sn"_W0Jq4:VVTEGlC0@W)f!-\1]r2[GJ^6$V0(KSS2!KZ=BA4RNZqj)>PNpBKS"p'0Jq!XOj6KCCi?KZCblG_j%!GancNj5(n@%L:s4D\*t!D\*s;JFL'-#`lkhGha6kLY*Q!`sEkNmL@%nE"F)b#2pa!#2sP'#`j=bn2MD@a1Xc`LqQ*!S,gFdhB4oj#N9Z@&0>;!&0<3Ln@1C_O2F8.g`(p%fGfLa#p;@u%Dq.E&0CQMi?@n!iGeo)O*cNK3#mUl]g6n!]g6n5!5ddQ+?U,=iGicI*b'%!*[cL-\2O\S_a/P&)ha"!)hc6<+?]'$_W,SH*qolt+nc-!E<;.8St-O9",!'XAU0oBCf"2,kB>KXo?i_p%.-fk;+dZp?/J@,6$OBp3>S4(@UQCmiMk5"Ggq<+4,;FSNZRK([RP>X5i:kk4i5jmG"-c4mHS1-RINZ+Mgi*a<_fXa(&HJ>+(I_f-o&qgM8%:'R;_1'_okiUidD$,DVO/I4!?uIF4H"+We#M)Tkf\G^S;Fe_A:[c]ulcrWlarih0dr5r:UTE7Z<>GPr+/pT-c'V5cGk5%6Sdg%oXr!3h')nI4U>H7k<=K7o2l.\$YW-Cpi;dCKGD.M87\\9qj'ucqm4:%#/g^DI(0q,^Ka?!.LkCCO.:ntS^X/6Ri_[sOXsf/gY^Fd\7X7,a.2i)4_*,DblZs7V8A3Y3Yc]T@XX]QB$FXm<*?#r"qGqFtpQVPq]A>?'6$OAEGD2qor?m#&9m)3E@5?-i>Jo2+orhW(.(sAFn+K[]/m>A4[.AskUC;B0iWp8jUH-=guJ%i\-e\q=3g>Qca0TFE`bb()3kW5t*7<-Y@u=ppnDK1p4*M*nUccf=;KtkbPfH=%O0WoGWbckV@+JJh;aP^K:KN3^6rf6gSiCui`5o(f:Z&`Oobp6GA=V9.Rcs;6Nc&%6F(Zs^\mo8aI;+#EA4;AGNBBn\&=?DP[,LcZJi^m6f(_Vf\0>PS>1&$KYF;@I[W5qA^LfB,cNEiH4Uejm%i&*ma&sWm]:bq10dIngO?cl@CbC=I[D^e\h!/0q_c&B;G^i=R.%m&A2,pE3S&NgNIEFa4^i(%PEF4O`'!^m.TB`KkJ`/hE=BlP_%f[n1%9q_K,b;=Q-6>d\Fh/aDOf"MD[Pgl"p#TjP]asaj3Gp.s[F\#`mCMb`/]MB7pe33=llpMQuE]>b>YSrG59i4Zk&+b%"u6L>E)(Y&f3a31R,W:N3/n^k(>,nGWHL14AG'ZH5'BDlrX[V29$sC,9"hFSZDGP$aOP`>@^med8*Y=Y9)+[H6=5GT&U:_K1/7Vn2BI[WQ%E=`O)LAq]=K`6#k@ZVu6_khdfe6leFr7StMON2^pFZQiEU=e)$k+IGV1hC00CHW_;(n/:T607\Gb&Fok*>=1d->/Z?@+0a)HP$^4#OTT1<%tB@Fig4FT.N^P[m;aL/=O/h8jfO%G$2%p5X>_NP_lap5A6e@m&pN,r\I[0,U!b5irjPSVM?V7GJ)@ir%Jn00QMrbX*fi(]`8CnqV+52"7a0Y^5p2q_B>eDp**:FPZeK1dE1n*,@hE&7&Asd>fUD-j?S-bbU&H8I-Ggu_0lTdBZq=1SepiIQrckAgKCBG!KCCi$KZCblGT741j5(n@`82V!:&s6mD\*tFKCHB0#S41!#`fWaGha6ka5J.'mL@$#lj^hl":uXK#2sP]#`j=bn.9r!n2LH%a1Xc`*"KjGhB4r!hB4pU!9%p9&0?UZn@1C_&$\&!NtXd'g`(pei?18N%DkL!%Dq-.&0CQMiGgg_O*cNK&:9'!3!"]Q]g6l_%`78_+?[U!+?WC!iGicI*f#C;\2O[(Yr;rL'0pft)hc8j+?]'$_F*Z!_W(b1*qoltEB"5bH\:f!H\:fI!JJM,6$O@Z_W0Jq4N-)!4AGn8EGlC0LP,*+2[Br!2[GHX5^;''KZE$o4RNZq6gP9!irfo7pBKSr3!bRlKCBG!KCCi$KZCblGT741j5(n@`82V!:&s6mD\*tFKCHB0#S41!#`fWaGha6ka5J.'mL@$#lj^hl":uXK#2sP]#`j=bn.9r!n2LH%a1Xc`*"KjGhB4r!hB4pU!9%p9&0?UZn@1C_&$\&!Nt]V%IH=<)c)qQ]dRK6Z3LB]qZ#5UFBZ]=2?H\DEhXtkTg;rpXJ,&K9:Nq-g/`b92Y32/#jSiQH*"++A-Q^g7cP):!hq6.c?2oG>On@_o#*\[Q`&)A9cYQTi=J,@]A?L@TVYs%!Uq0?t!mChn`Y.)@+4L"_5_TuQgh:s*a4`FTq_NC%mo5R1cqY092]'$-WgPG^JG@,X\iR]Su3Hmm-q8&WLUYGhJRC@[dBb196flQJ;io$8rq-CHK!p9mSj@`rM^&7e5[i5D,8e<%o%Y=,"]c]E;<^:m03X!;%p4Dc6=97@]kR\"1fF,Ou?j22gTL0ha?nVcrJ/&V:J.@/"JA!'f!*2@0!'18""87?9!*B9\!U((#&(h9,"(2:c#G=F,4CBfJ%tGB+*e4`On:81p49/DIGQ9c-_?9L`n,QPki;r-R#S5Pq_?40LKEn/;+?X$b#S2G#&0??3Jae3'+?TX)5^38i"Mt97K(&bB!T=(C&-rg!$c3!R"b[,R6QeL!0*i)='bDF:%Y=,"]c]E;<^:m03X!;%p4Dc6=97@]kR\"1fF,Ou?j22gTL0ha?nVcrJ/&V:J.@/"JA!'f!*6OAf0WU@;p$0W2dk]R/+=m.Dr%gb5$:/Rr1Y`[&Pfq(c/90aGVtBZ)OPVhX/l+*MJ0Cl(Dc.ZSj_WHF)^gV:P/$=u>:nA.nPGc5/-%2::R!==b.[)TC/h5$qbKVQ7+*fZY5FlYL8r!'2shbX$Kh?4\.X.s+X5`em7$(TL.Ug=p^"6L!1-VA\K3Ws(+piJI?Z<%8bYV"?oSQqop.PFl-Un;,%]61L?Ylg),QGa]+m%Lk=;&0A%lKEo:">nik?+grF-'0!e7L]nldBUg-hKLB1lVN"$]UT*NX.Sr36;T2)AF2e6"c9QWi?4\.X.s+a8`9c^:A,0rr+%`H#f<8tRWJFe7S11M=l4]'jk.c"rdWfBH)[NL5*q3p47+`n`M8Fos<&L;NV.2fd>`d-efeK#@CV$HEN]atTV)aLRr..C2e%B-o`CrtNlmA2Xfb^ElE]dRUI6o$_E58ON6?39Kg;p]A:_t0MXZ)E&G_N2/j]\p262(Q%eq6.L?Yro/.sd+H`2*Pd$$e1*X<4\ok8Ygt2=X2#XX.Y"/g%^;2`E1jA[k(bc7\Hp=WGAXXk$THrSFY/"09#Z=m@:Q120Wi)3!2;ZjEs<[ihWo#7qtl8UN_/sC]diWq8B%"Q@6B!N/^mr=t7<$ol!Z4mikYSVrt^^B6iGj84QF^\Wd18`6]NH7oU;td9G@XA\$.n1ePdsXMfdkolbS'*R]4+Bb@FsR7`YYV-p/h\Oo(`NMV/lSC+18f?nVcrJ/-A;n;*L>RIC>W`Tjjrg6j*fHU2lVXC+nMMIM/CUsbsc*jM-*RqC,B(S8Sn"R=0T_Y>0Ik/>Oo/m&s6uV-mlu5oB,'(#-n\A,b(`,VT,@0Ftk>G1>/2>>-d\h?5KBUF4Q>1[,->WD"UDggnNSM53@:VOg,['A(hS;6)%1Q^;VM"h(?F<[=%Wd:muo<"Bp`U'*'HUU)Obh1qNdA0bhRkn2?4sVa"T?UuB.C#UScJVD9BhJQ.f4?e`.#s.eo>`4oO"^=8eZR)KI)2\of=(1)*qWNPT@Obc:TG"$p5'V$flSd/bBZI3J?\%e*B5D%A[3tk2RI^!RLXiOK<<[)&0>dG#S2G#D#%:=A!=75:fPte>uSWAn'n=$=Klo+0l$;f,aHuoJa.)%?>V5/P!;5Gcd$4\l5dNoY^^[ClO>.U(PXE-WJWnS;`1a[;j1X>r>m"7]g^CKRN<$"8Vb!=58Pk26`XC,c?uanM@k_W<8Ao[)@u_q5]m"&j^sLZV#dKcHd_#ZrBsSJ@P(;S"$Sl3Lb/OV)T.0raN_[kaelhje*;Q9MEZl01u0fg'b,D#-WBK@Ud]LLg03l:WspYRkl=gJfU?[]i;kKYQDZDclQ7oFk+RSaAYkteS5,<#ODWQ!O)XB"4/*hWa9l%AKUm77g"<-/sfeT!5\j3K(&bB!T=&mO8@bm]&]#IY.EmJYLgC1n(u%f&0A%lKEo:"#S01IW/LH?LHg1^jd1JqlX-9RX%l4l&(h9,"(29]4DWNL=o>H^!49RR2>6FlL[]i-+?TX)OM\?qcF`KEJ?trKm9F\R,p2s(7m?O4cV.&-+Zs-c#S2G#\>0;T*g^5_q-qmbp=a9m*BGG^5^:(N&0:g%+?TYkU$MQ,ZT:lGq./Le#S5Pq_?40LKEn/5Y.EmJ@69*oK0j^LrYcs<9oNS,@8ofc-!G'l%8=2MQI,3HG8+;Q<3_:63H0IYQDZDOu!H+R%sVKRNIG'V@%lBeuNPNam)S^KioXjb5#"B$BLS@"3H.tC8kkhPKh$El!ubi/Xi1_\M^8^A:%K1AJo^PVN8`;Rja=/7#9p$.!#H=T%tGB+*fqpAGj84QF^\Wd18`6]NPZ@SPl7VjfNn^"M@iGBFC.kr1em_qA0rPD1W#:HE&=649Vradl"ohIOfccuC/q=ZRI!Qt?h(.BjcjdBhS[gLOld,Jci860_tri)*Vs,;)j$$S)O=2pM8l2hAaYY]P=RGl8"@nIKM,$i;p.a_?1E*Q0XQ;:fAmB>bt8r:>bW2bL2(lHC$8,Qtp\Tm2c%R;C/Z/_Tnc_I%f@C@PfuNK#c5J5*WR]54A[D&W2Q.LTW?1Z,aM,=riiQ*`NbM5.LF,Z_BYpm?S#Q2E%.0rAn%`]`#ggJZgq_jZ)BmZc\7Q#JeTg5sjpe7qajN#@3$u5^+.oVti9&?d].Ocf"hXFP8Dl`E*WR]54OD1%jK(!KcBF>o9SPVCMRA9\$d^9hgB@om:q:haGplRN<$"8Vb!ebAO4tL/t_7SG?>e'dFF9W4Gc@1`l@kJa!#+bKUr>?I`[Y-82uYj4_]&d"(DL1gdoh*c)!*2>T.$4"+?TX)5^38i"FiNJlil>KYQDZDclirAYSVrt^^:>X5_hUL^g7Qn!%T0S!$)."J[=@5Rr4[dSi0^Oi;p.a_?8..&0pp5J/+mL4T]nRsJ+ZlmQ-88)/:d3i=97@]kR\"1S/#+3H0*RX?i>gWHt'AG8.ZgB.j-qS*='$ll]027lI3:X[0*i)U,D=)sR5Rh.,G`"`14oiCZiS#Gp4Dda3=$/#(k`.J"5s9-6$U0bKSNY<_?91Vn:81p4CAZ96S&H#JbD?R^g7Qn0F-P[(5)*AHQYFY0*i)UKCBG16$T%c&0A%lKEo^eG_j<]4O>,T&(h9,"6L^g`*s9!BFfb$lil>KEtBC%0aJ;s#JpT:K(+:N#S40WKEp35i;n9kGeb;PLKFu%"7:[/JA!'f?k1%@/-b'ap-/03'V=-R\b-H.3I!=uoYAs-9Y"thDF5l7E`V5',="ZR!G,N#iS(P3En,7>%HEC.enb(Xg]cQ4pND4cpo.0k09',;T%qKW$#S5Pq_?8ruo?L!o%pZN:f^YWOO([/6:VPRC\YJJS[Da7N,!-;&>Q'*`c&Mb"PtDT/kQD/%fF,Ou-AZ+#mZ4JPXa&tTY)uZ8grXuFDb_kX/3@kGR]]H;(5),/'ncfQ!Q"s4K(+9s]2'98Mr$=?c+Apu]Bce*meAulHbHDZX.D!t-G.!m`kU\Bjh!uZhR0A%fu2.%)(0XYkI]^-'>5L_1IYHTq?-1)^NiPSo0TRF4Ce>Zmn@!a_93PSU=J_G.!>K"`_8^`(Q\#h+1\)SmsVmlZAk3bqq1[$oaM49gO/b_bn^dP0hsL6TL[)b*WXXa%XSotN;V,0\roP/b^@qcflS4DQSCq+`d?ablG&lL:Rr;4$hR?`hIcYoOjK:@I^$5)^$eO:LbMua6_W*@kIq`Ia)MMgD%jO3`*XE;/$c2um%-NR\T)WHkmRnM^hmqImp>J1i_;-C!5u6B4PA[DsR($[-a=Vn%LkGHuitZmkWqu2PC-f?ZX*lA_HKUetg$(j2J`J<[l5SEZ=)Jf&(nBid'/pF(NW]7-FsCcf.&;;QNXQM8Rh'?Bim54FoN5C&B2EnC*VC2GEW]BZ;9PcM]"GYHg6KZE]#CGpZ-m-dYC:J[Bqd>n3/GNZ,/Sn5p[29+'*Xm,CN$UofjpT79K/VU+9*GH4ZVEKn-5^OV9?nVcrR':T1gq-_qcc3!Sg`Y`FV=1EOOnZuglH,+uCrfoV/iggq4NeH#STpB1\le:/d,GAFeWT(f=(h#T$YSsOGM]`2=_9dEm5qG`k/P[7(";]]i:ua%Sp/`29/PMCA'tR,L1c!YhHtNqjc!ENV12Ch$&c;Epq_lGj.Uc<9NH)CI+,q9_UIA%1^[%-*_(CI4V/[=(PE#efK<$EUik\B%lMYInn\-lHfm%r[@.>%hgE&=/3B$,k/bBnlT+\2m>JBgA4cChf881+-G6js"m,FA=@bc$69f^3j)ci+a.s/BGfHqtW2]0ODpamM&_VSOpVg55V;bXdjiVcdceW]_eJBmrWj#?Fg=q`Gp+X>cm=FbW]c]FF_Z&gpbQ%$KhaR4m;lc6@B(gLrf>p;-1Dk>_UGlI&KYTrKEpiIi;jU,O/7>6Q/XeYrlu4dijtVZO/:Ir*\j&f[iO8e;;ONdc+Dl$D3$WJRSB))Y?t/`=*s9*H[$m;ji:,ePo:Hbp>2ib(Sl5r%8EA(mAEg+9\OGTiA'#[/cF/^0T,=RlJ4iU[k21a`oUD>SotW?>iIU\fGedVs*\ccLG$_2]pSnscB?N.SWrDMGV/$dkZMi;q*k][$8Ao&JlM?ur4j19^npT&dW,;q-+8E1^qa9X'=jjt^("_7ie4W!%'tcQ>;62YHKjuM)/cXlJQ;P6n%O4fk1dZ/l2*8^ElMY_VTUnM>Tc/rm7=#A2!n_)#MK:=!II+W^mt3b/b-quhYGV5\%hO4--YO<1&Yt%jego/^0gk99`90URQ_QHLF9aY5=;8;a"o9jS6H3f7-(E\FsDfmoY6f9><*\oY+tKZ>?l9IEJC%lG(6cWXWST*g820dQa^fh5d^P._Y9dD;OE%TCL]rk,UFH^/ZJ=_UJ4=1d75>\;(Y;BFfb$lil>KF84dh`+&N%U!Ohd&(h9,"6L^g`*s9!BFfb$lil>KEtBC%0aJ;s#JpT:K(+:N#S40WKEp35i;n9kGeb;PLKFu%"7:[/JA!'f?k1%@/-b'ap-:8#S49I_?9L`n:3M)&$Zt)#MK:=!II+W^`4L[:H\Ka-[ngZkL#n25PiH?f:1a[m+_8CKEp32i;n9kGefiE8,#1DlsA*iGMg!lXtTh[ULeW).f?O`n+fTd(.d90u[CXe5[Y+("]c]E;FfAWn8r?qcAM=KV^oGn3sZAL$c3!R"m67hElq3YIlaCI8R123(Go-oA0^4e/f(&:fM#CGccn:m0"4hnJabXMp4T=o]k9D4&(h9,"(2;q$Vr$P;,o1nVSCfCECs_?Gj93)`jR+ahDQ7I,XknZjiu)FQPP4>Z(.KN1_WGdU3hCm:hc@6ta0RSVP`Z(OrHaQ8+XKTg[UpZn7d%Jn5$Yu]U_J1q"r*m80U^/..6/!l451R#CGm&Lm0H7Qs.I::NR=Lt1&K&Wo*:X>p_oufK5dGShbmi?IO@H3!^&I$c3!R"m68S5<7iSXr`&BcGr_/q^Q:5k%_4s0IKB'egU2,2WAUAhDk-59#g[Hg=W\$Z3M!e*k7bf"OU.;q0lk4bf]S'%YLnl)PI1)AB8!7Y10-"d!q1*Tm::8Fp"M#1]b,2!i.od7PiCaNG99D(p9=g2Zg]?mon&YEaYf]:O].$5,7:Se-TQ*fhf7c=Z6(&aS-SrB0-<7B]>g>d2Ei0?Qg!-mX32ATGr?<.?2QJ"83RP,hY[nt]n,Y8/VN@uZaDC6ds4L?ab1_R`\kd9UM)-*>0m1YIJZ%BEih&p8Mq\YrW?Z>edB8m6@C]s^>&XD.ZCoolDCmo7cmCXpa+#2oT06$NC2i,IX(M!7f2WsN,i=,uKj\X_3eR=*3tRk+e%Y;)6L=*VTMZ#j"NlXXkSQs]MTj'L-;ancLUg8Us_f;bI6%)N,?K(&bB_;W7p.3"]k/n`&3ZD$TH9I;7g^QOYTB8%C[`.-Di4FpJ:WYp^OTP,0g-lpgfHobT$.er&7E5Q/<@`9:DMs3;@nf"i[PAN1_FQ!h6&ggF+Ul#W*cA4qi6CHMW=R=N>CF6o.^'c7s'1?nr=3+&YDGj',Y*67"RIU/s?1lLoY"P5P<3+kkg$.Zgdb.6OW=4"gS_GhU>r/n;,=HhH&H'3*Z)VGTN9f](JQ+,8l0cOW>,N7iaQ+'.qk&]_Pbs;`&6FQ,>/gofdbqfoc:$%C7TaQOI0sjM=<'-(Rd6U32@<0X'Udc9U\r/Wp:R=bKO!?9?GbRm$)ea/a-gpKCFE)+?TX)E4GC^j-0/8/R,ie(Kep(X)-ERYh9f19je>iXZs6=a'T@i4O@^hZl)^liCX8$c[b=b,9,AYs.XuiOp&L=A>A[@ej9nQtO:QZ\#)fsb.FC&';rKIaKr,:eqp`hl@P!`9L>ns,R<6N_P#bkt?oP,fZ8BfSNg]65e)"ljcV=]'!II+W!3G*hiK:$XjQ#Dmm=3Mc[XOVhPaN!2:B'd(VrSE^7gM19`_BW8"9hgKLBW/W(Ie0&daHC&RNl_q(Q7R=SeQ6c[:=q;msFFP$'Ca(G_t'Gc5Q:26U@@QZhld"F/Jf(!M(JJta$21L`2"MK55E;raq?EBacattjtj=(Z,[oL-/ifFQMgBQ5F!O%r8JN(8LBX`-!RirPpP>dmWW6af=pr9=gE,3:0I.IDgXp:OGKp#g8AW='pB`/.qk.9.n!WE`jSnrpG!FP:tOj>q8R$7ECY,pkunK-,NGgaeq>q.9Ri_;3lV'Z_*L/5QCQ*A]`$7Lb/AD:OXK\rqW2d=dIiin%H$f94.OY/G[S8^*?:ZDr30?m^V9e=cuCRA4[u-OKYQDZDclirAYSVrt^^:>X5_hUL^g7Qn!%T0S!$)."!II+W!3C_?!-AL"#ODWQ!O)XB"4/2&*j?H6#J^ZP%mYn8GedVs*WR]549-B'i;n9kGQ=gqn,RP9KEpiIi;p.a_?8..&0A%lKEo:"#S01U5^:(N&0:g%+?TWE!oX0,6$NC2JFEP\#_WHL"lTKd!l>&d+V5^!(PE%/$4%/-LHp+"?4\.X.j-qS*KYQDZDclirAYSVrt^^:>X=G(q5HD1AL+u\q22mrL$,QfmC'0\RFc,P1*KW=8;YEhgNigEBo(Xc87(A(XbRDsP^l%8=2MRS1$4eITg?8JT+'dXdhH34bijQ'Fm0P8R6,2\C'GAAeP,f3#rW_n"/(#DJ>i`1ukX@[cURBh,G9i[1CfXgU4>@7=.f'g;"m06ipUHr7t0P57d_hA<3"(2:c#HX#T2,l/l#jR\C>[t;UK0Uu[N!@bq<&cTZ>U(?`er&YP`36/(R96UGjUJ,@,XhUb<'EI2T2N@bZ(NEqU*;'Y:AKU?,U8^n-CdV.l\YLL4cRND,A8F`f>&^c^/=!*?,mn3Xo,#H!EWPe*/A@qQMqJk&as,f/-4rehq&jS-\*$C30R(r[kVDg9I9H&\deZ@2G`,M)FBs"9i;esF@m,M!<[Fc.]4QIM78>$KdcQ-4Gk/0FJ@jn!,$:TLD#AnHLYaNpeg+$"(A(@VWPt@1*aSK[f28=peOEY:%iBj9MN-Tc'%b31M^%3XQrNUILPSV@fC-?XH4Ub-li;p.a_?3gM52ao-nOP?r9R9&\:"=!fps.b-4;RVQo=8fqUM8rf$5l]C#t84VE/">(GMNm-B2q*^$#B*'H%>TRGQ=gqn:28EZUQ(#<->CAWZf-$.MK"XrJ5*!1iN%NiC)<_3@,pr=hCP0=h=)>2*Zno-:L(lrd[ej,U,^'W_#"Bn-$C1In8%qOADK@a3N*!i;niZ49/DIGi1]W^#)4;Q7Zj5^3:1+GJus5QA4m]10J=^)S".OY>U>o1`A._[SQGP1eMk=V?Y3AB>34]HBk]r8fTTOsl)'q\8[2B&cs9F3rNn-/^3t@/meYV_IO7_-`l8d9fDbaGAP"54C"6K"55:d%\bkg,>BSONIdr=sh0&+_^A&LsTDm(7\o)h?qMa5f2`K@@?,ORtBC)cSIdr?%jQ2_8DUi$"q8ucBFk>^8Ub'(BY.NEOY&&:\c_Q$YHf&Vh?&q/B=KCl$p"!>Yag9;W.nd;:j>CeRMnEgi=?MA!%W"[$_Tk+8INfN:"'lYc4sDc;>3k>#J^ZP%mYn8Gi5Z*]68$AeWCt:!*2@0!'18"")!GKu^k&]/V0QD=3Bk^H1kbsVA74^**:gS4t=`,D5ol]M//iPG2HIsk'3>l1s*GJ,ZeZZg(HN)(?Tn2*Rp@*EUl?@CiiV`Va1=EcTI!ArE8kNf5LK.D6JBM=cb5lE^=;N^[#cXWHoRa[>r3rL\(_a-A[YRm5`6jk4O4TS;G_V+9r@G[-Zi=,m4nH)B=@kX#O5EGlC0LP,*+2[Br!2[GHX5^;''KZE$o4RNZq6gP9!irfo7pBKSr3!bRlKCBG!KCCi$KZCblGT741j5(n@`82V!:&s6mD\*tFKCHB0#S41!#`fWaGha6ka5J.'mL@$#lj^hl":uXK#2sP]#`j=bn.9r!n2LH%a1Xc`*"KjGhB4r!hB4pU!9%p9&0?UZn@1C_&$\&!NtXd'g`(pei?18N%DkL!%Dq-.&0CQMiGgg_O*cNK&:9'!3!"]Q]g6l_%`78_+?[U!+?WC!iGicI*f#C;\2O[(Yr;rL'0pft)hc8j+?]'$_F*Z!_W(b1*qoltEB"5bH\:f!H\:fI!JJM,6$O@Z_W0Jq4N-)!4AGn8EGlC0LP,*+2[Br!2[GHX5^;''KZE$o4RNZq6gP9!irfo7pBKSr3!bRlKCBG!KCCi$KZCblGT741j5(n@`82V!:&s6mD\*tFKCHB0#S41!#`fWaGha6ka5J.'mL@$#lj^hl"<8pS4"'.p"ue'TnifLqTWX[-mBEYRUEr8Fg7;?@PY0!-^7//Tn2QqZ/3J^,^g=@b;klkQVut4e%^.N+4k,=*NTQ1N^=C\ZL-*(Idj8MKketm9)p_YGarih0dr-,;JpT!WAGt1&Y9@A#7HSjau0i\Te!stTq29j;&".\q4>unnp&qg1A^m"HZfk%*878kI]k879n2Qs+$;[Yt:\r"ul3t))=Vmh&Lo]m1UML>^f)^WaP+'E2O'eU#IgkEG]9e<=r\Pf0sYFs;-"i&OtI*&X+umDTYFi2Q0$K*D5VO^iORlcVXTC-mcdk5XS,C24Dij\Dc'"8XmG:HN,7:[F8+&&%RjN?+Lja/]+?#Z?Z9S;B@#.gg"[`nG^na6U7n-u'[Q@/_%f%UmMHp8H'F5`n(Ng$JLMXKu`@K^^smXpE@0WZ4/aEU`ISO^/Wl^3,:t22J!K33U"QdGsI3h6_Lkc;\b)a2lX(de&47.bV_Zi9:.@A\%_2;YtdG]`.S7:#af5O&A$/Z?U=/4t=:'c]([:#Z%"400JF3$\uhUV!AlA;=7-nm0`(Hbm_W8G2)9l0;L-LR-OfkpV05.6Jplb-RbYik*5&3BAA02@,A]l/GF\eJh4GT$a63V5$'B"%U#c;=jt@")"dA2Nq.@LZ\\J5e1"B8dE;>ej"O)Gd44+@'pHQmFi\C[1ftg>R4+Mh7@=RmDDL&c^mkEH]HB#"2igN+=N6aA(-">50rfrfq-31GYBTOa2kCm]aKj*k60a`3*d,fi(D=H"2!+Br<"*PqUF,-PAr53>3SmV'AF;PB!P%noGOYS^F],;0@%,1'1TaB>2F[BbJAo%1ii#ts#Yu]$\qe>n_l7X,Bd$Hn\_qun&DW$0>\H*(GXVC\\P55Y;JCMtqlt_;c-0B-04Q1g6TaH8)K(*ir`89\uU(1Xu&dsH2g7etrD`qc(AZ,WsLopDgY=u8THmkl+ZNGM_"$k+4Ok\$stiG"?*hR)Y7iO!!Dl;b0Ac&/4uq`G/1"bead7n@l=HN)u9m5"*iiVr+jqr1sIO0X)XVMj>Wr+^P,'bfPgCLp:I&UZU@>[=3%\HE3'6;#$]#^3.=4oT@NQrG.!J*bAf]?E1QYb2Jk83P&X!oau_VWAOd(l!X2=KSf2S,I_U/r]ZBUCN/sL?)\:..8S8>.6Jpl,!97qY!849[.?\HBDLNRlV,=/G\\E]hC%0[4]\n'1poW^F`b+7.]$`5<;"XhrnsABbNgsT/I**/dn"[)96]iD>.fC\:;([YJkK8`)U9%g4bjJhBT(;[EKB)s>e)u+ICO1fi\9/l2:p5f4'rC7o&KL]Q^g/o"^.6T3X'P2N&Q(i/trshi*$5">`8CrQ.6BnG'K!h3sBf43!%tW.([oW4s-A(PU7jgt?NDd)#8n;mc@R5kDK+$XPUq=M(hVlt+J_[Ue4LbRR\Y*o=n;sILiGgjTnPJrai?@n!iGeo)O*cNK3#mUl]g6n!]g6n5!5ddQ+?U,=iGicI*b'%!*[cL-\2O\S_a/P&)ha"!)hc6<+?]'$_W,SH*qolt+nc-!E<6E,H\:fI*/)AG6$S4!6$Sn"_W0Jq4:VVTEGlC0@W)f!-\1]r2[GJ^6$V0(KSS2!KZ=BA4RNZqj)>PNpBKS"p'0Jq!XOj6KCCi?KZCblG_j%!GancNj5(n@%L:s4D\*t!D\*s;JFL'-#`lkhGha6kLY*Q!`sEkNmL@%nE"F)b#2pa!#2sP'#`j=bn2MD@a1Xc`LqQ*!S,gFdhB4oj#N9Z@&0>;!&0<3Ln@1C_O2F8.g`(p%fGfLa#p;@u%Dq.E&0CQMi?@n!iGeo)O*cNK3#mUl]g6n!]g6n5!5ddQ+?U,=iGicI*b'%!*[cL-\2O\S_a/P&)ha"!)hc6<+?]'$_W,SH*qolt+nc-!E<6E,H\:fI*/)AG6$S4!6$Sn"_W0Jq4:VVTEGlC0@W)f!-]h!93r.\Q>osKNNVrA;PN1O]f'O%P/U??RY\lmr#bXhk-u"GU%/'0-e$2*Xq(`b7dg/nb=3(@pVRDgu9h\ZU,dBd5k>reZF)cZXXll&XXA4^cFY18So-HSL$MM6;mWZG\knJ+MMYNpZhg962JOG]Z[8Isb>qKh&l'LiQ4]r1>nYlo[3"jVQf(s.S2b#HCH^qc<^1df$.T`"`m5kR1<:]00n:8\Fmt78-Tb+#@GaH0!i1ZVSendstream +endobj +9 0 obj +<< +/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.dc6e6a3e11df0edc665d6adf6db5c37e 8 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +10 0 obj +<< +/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +11 0 obj +<< +/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +12 0 obj +<< +/PageMode /UseNone /Pages 14 0 R /Type /Catalog +>> +endobj +13 0 obj +<< +/Author (Android Cast project) /CreationDate (D:20260521190501+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260521190501+02'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (\(unspecified\)) /Title (Android Cast \204 Next-gen egress review) /Trapped /False +>> +endobj +14 0 obj +<< +/Count 4 /Kids [ 7 0 R 9 0 R 10 0 R 11 0 R ] /Type /Pages +>> +endobj +15 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1739 +>> +stream +Gat=+hfG8V&:X@\Z&`p?SWrF$%&OE:*9+d+#0,,UD^SSdl$A"\L?A1M>&[NK6[0d.&KW\b0C(JkJf72Cd_XS3BqMcKO';9"C@*AWOh%(lTSUP.?eCLDHgJbr3?nEkn[idK>SY0bJ&ci]3AW%'Jj]g6Lff^Z2Uh=nM@b\"k!;jl>e2*NsM_;I8[L,@+\&=lA5B2Oo.f)Fr@YB:*>R"JGt_KpoaG5HM?^&E=B*f[B'*8c5d:G+leN'G0q@!ZjhZ@uPHgWM7@`l%/M"*X1\A>XC0Q(Tm02t@(E/QN$qPa!@(OXWq^I2n(O=G/HGf+k__\I+rs$"5!MI$/c85Ka4`)A6Ji_3iD7Y72\@ASA1CJ\#ma'7@+'FeuHDfL+<^haVj4pP;!-WW#X]Wf!/;h]Y)hF'W3\VYNXriO+31atTY_e?G<`-T,"-@AYN&=/@pq4uZ\^W4;e:`]n_=cWMAI.(lC:SqcbK5KK>An]NXei]KoBa.M)G*:fcS?7CIXutN%Su[9qm_N[6<]i2L;qOX2Xu>`GAqi?#^(4%V2>3c;"m>lVZY_Nl]jbLbcHQ;N>N+f061iBft5.H39_iAU5=!4N*S$$oI]YrcLY*5f18Hc38#$U];-$69juD,#u]<_'L6DU<(Qia_&h`#GY[dftV(@ekWucK<4,&ri]Do8?Ar@8";9d'@%DRX&9hp:p9'R>V$T_Yuq6LdBR]0usRo>TQ6nMc+7'OWG>:;sCE5S^J"V*B)c)JKB1*^UAW%`+F\f#ft`SjHS!^fSpj58`A`+P\%iqgFa]`3Zb+i([aTQGNhc"d@k[:i(7q_RUgp/DM=1)/:UdB1LIOn`,]h\#@[^tB;Z>JY2$SbmrGp_n/HZ3fu@rIL14!'4ed/-~>endstream +endobj +16 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1512 +>> +stream +Gat=*hc&Pn&:Vr4Z&e#S_NJch.1[F;#c4@S*+@RHj@-#Ykd/#(H'\>ZcnUbhU&4Z&\^3M;Qi/^3V/7OmcUkPTO0FJ12X\SA0p&YV!,^LUTjcNZ6KrWRN:q;ao9V5jl+]a;'jq:R5!a7]Xpikk*2ibPjd0c1\oZD[TRa)CNk-;/(^GAY[-&dL)%7VZ'A]>S9YUjO\i*I*faN9p$@4E03n]s5WY?mW#hb>fjoLT,s?a7Or>6T/U40ijpg*<-HfN.**k\)M-1CWN[De*GF'P17=cg/OdV1cQte($g-EKHBk,3@N+B1V^uNqAP=]ikJY;P,8*H$FG6X&uTMsKCOh@a934_+CW'?5.)L?Hd6cMk91-\&)`6e3!o>Od&?n"3mgtC;VUg%olkJ5n$hW_+1plf]CeR#0W^+AbrFsmbomEGh+gU[O3aC9oe:B;+`4VgjecUO?i`/Co[EN/N;8.s0PleV.jK14[@[Q06Jm@:b,3i_:Tb(^%HBAil;]lSNr^AZ7pg*OU6[-)R)gTGJ[q91qJ'Z[TbM%eU==b_s/nJ03i<9g$I=-epm9_M/mF>JCWdZ(AsFdd\`]Cmt3gKLs-fR3)['RM;k$q2+uBJ&-]!koHF)ZkL@?lK0u$7H9`lrnE'Yf=IlZm*uA]8'F9ZYPT*+Mf+\%Cf/b@t4<*#G!).ZF:;8@SEq`k>E_?+meQaWN?D:m2^BbL19#m@jaTZq/P7QQj`MHusaa3r0:i"1GDD`$m%,hR/A#jub6r1WN8c9&cFFkWC`4lbj0VLi7+m_6"AsHT?ZKDlKlU/_d'T8'Rd=O7cQJ#)dW"4f!W+W1+^/bM'I~>endstream +endobj +17 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1245 +>> +stream +GatU29lo&I&A@C2lqpM.Q2j-LT,^Sb:8a[EZ/T#1JP!pL9G.a68_'ZP]aKpr8D1;=F>u0ImI%mH\'&%hnF7^jIoZW`T>PRh#nRI8-'6I]@K%'jR#etB8F%->Skk!+6BVm58:6#/B_J!"4cU))FJ(),9*D$rAH=6fE9"`d>:"'a=n+S)E@%"meDMeDJn/4!;V100mPig7G`[R-%6$OhVjA5=*\\EoT6'ruAg@S_B5iqG;\K\>`k)fN)V(7%k7"%I)qDH;rfPeuAK3T#XOlc"SP"tf&g97/t3>.j&1Vb-&j,Z[0;([E;Y>!*MsR9giKR\J@:DadSQ'iI8^$WZt@NTd#k].l(YD'pq].D^Mg.8c2OQVZr,*e)qK.KlOgKF%Hr%&U\H7G[/b\U>jISn8T5kKYH^$PbmRKlY-96P2]NllZ&R\XNGY`raj4#I;9?':?C_V6UhrSBS[HZ?@J],dTK"P%5I7&/!>Pf^i#[i4!#HNVt++08\mL,aM&6j*Vrc(hXD:,FD<'aC4lS61*5!UbB<(%PNLqHQ*Zk`smM&QbJLGegH0NPEWGeZ_r'WBjsLQs6\n6WS)>:8aPE(aklc`X'B24kM._gmA*h/M]7VErBTc]\`7s_L/&-LR:ifVE=(XCh@T/;TGYO(d;$E!E/2bW^S#&"k5!58D>[X2i;_<>dQ#,"8hB<63s8;"9q0MAqjH7l!gf%=)5;/ni\YAHn/29Zb,rT@gN?j]ao*I]"#=kHgH:]1)Jnktp0&V9!>kNe6H_7"[kbendstream +endobj +18 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1521 +>> +stream +Gat=+gN)%,&:N/3m%YQT1]cInLQ8+]Bq!]7=E:=1d7f)GaB"3r>jNPYH3Eq59AQj4d,7An'&<=IQ"(.rQ!u3Uge!tAVT&-b^"^+Yl_LA0+f_5466#,Y('`)9F!&;N!?/3WRh1GV^ga_^\iK3pO=u/0e'i)m)6>7*gONjsZ]KH$OMnAlQ>tinH*KCJRC]'eeW'NTli(9g+O_W=<))ED>o+?Ea<5;1C5rNJ)#m`5D_'bb[FQqeoN]&r'm3,L-bqVXbB$c5=@"lTk;#'YrU!GR[BKCTn@T`Z#eh@n@&]X+"m[FAP9jeoYZsNYrk4NWLV.T4icksO[$'W-Lfob=OGAVU_`Na"\1fj5iksC$D7?[\ao$nNmh<[;Q>R8d^YXI/-jI?*Q4gSe)kTH!mi+J0+]ZOl;;M]@\%U)AoQnu\MQ.8@GM^4;?#K^1@UM*':ofL+`.5XtN$:48_=uC8TI:4AJRp9j5qN`Yc0k?GOTM7d$8na4Ne*d=+,#SdJN;#VruYQnS6#uP4k-0JYUe\'IeK0kYT^!CW!9DCJ\5T4*h[cU1'"cm744BJiAYWc@gA!Kp*_7u=T5Cj"#f%&H'4kY!PPXN+7-$$?<8`-0Sk4#Hd50RKWA\H`d+"`"VuLQH:\`D1+\l2?Rrr`*4.0b8.5R@:B_X-(dC%&F2:C,@O+D81UWL&&m3Hg/!_PojpelPTj+!bTh8WC=8phnI:HAg\%PY&gKdu)_h-,KQPZ8m)R?"6<;\\GjS^uO3uEJ6.A\lf.qK+T5R*&2T&l5n9#E.kQ%i?a.LhAn'&qF*kN:p#SHH,gSbC2Y+'Q:Rg3%ipTp6/:4t*fC&sW0.!UsZo*=f'LO1,8R>]/%8=O@*E:3@Fc1)H7H1qkp\iS#']uokk"32q2Tj;2O2(+dk#\1)^UddBeJkkR-^c'p0".jf$@hH_GdtI-7,elB#K*OX&^nqbP[`j`8n)5N!p/AV.i5$0.Dg]FIYO%er@.Gs#W0*)~>endstream +endobj +xref +0 19 +0000000000 65535 f +0000000061 00000 n +0000000122 00000 n +0000000229 00000 n +0000000341 00000 n +0000000418 00000 n +0000028157 00000 n +0000028240 00000 n +0000028508 00000 n +0000053186 00000 n +0000053454 00000 n +0000053660 00000 n +0000053866 00000 n +0000053936 00000 n +0000054251 00000 n +0000054331 00000 n +0000056162 00000 n +0000057766 00000 n +0000059103 00000 n +trailer +<< +/ID +[<69103a44ba79ad224f4c4d9612cf06ea><69103a44ba79ad224f4c4d9612cf06ea>] +% ReportLab generated PDF document -- digest (opensource) + +/Info 13 0 R +/Root 12 0 R +/Size 19 +>> +startxref +60716 +%%EOF diff --git a/app/src/main/res/drawable/settings_app_info_divider.xml b/app/src/main/res/drawable/settings_app_info_divider.xml new file mode 100644 index 0000000..9542d46 --- /dev/null +++ b/app/src/main/res/drawable/settings_app_info_divider.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/layout/fragment_cast_settings.xml b/app/src/main/res/layout/fragment_cast_settings.xml index ffa68db..e6ef28f 100644 --- a/app/src/main/res/layout/fragment_cast_settings.xml +++ b/app/src/main/res/layout/fragment_cast_settings.xml @@ -285,6 +285,13 @@ + + #FF000000 #FFFFFFFF #99000000 + #66FFFFFF diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 749ca67..e788592 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -11,4 +11,5 @@ #FF212121 #AA000000 #FFFFFFFF + #66000000 diff --git a/docs/_pdf_build/build_egress_review_pdf.py b/docs/_pdf_build/build_egress_review_pdf.py new file mode 100644 index 0000000..16af3d5 --- /dev/null +++ b/docs/_pdf_build/build_egress_review_pdf.py @@ -0,0 +1,383 @@ +#!/usr/bin/env python3 +"""Build 20260521_next_gen_steps_egress_review.pdf from summary text and diagram PNGs.""" + +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + +from reportlab.lib import colors +from reportlab.lib.enums import TA_LEFT +from reportlab.lib.pagesizes import A4 +from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet +from reportlab.lib.units import cm +from reportlab.platypus import ( + Image, + PageBreak, + Paragraph, + SimpleDocTemplate, + Spacer, + Table, + TableStyle, +) + +ROOT = Path(__file__).resolve().parents[2] +BUILD = Path(__file__).resolve().parent +OUT_PDF = ROOT / "20260521_next_gen_steps_egress_review.pdf" + +MM = [ + ("diagram_current_arch.mmd", "diagram_current_arch.png", "Current sender path (1 encode, N unicast sends)"), + ("diagram_multicast_proposal.mmd", "diagram_multicast_proposal.png", "Proposed multicast setup (handshake then one media flow)"), +] + + +def render_mermaid() -> None: + for mmd, png, _ in MM: + src = BUILD / mmd + dst = BUILD / png + subprocess.run( + [ + "npx", + "--yes", + "@mermaid-js/mermaid-cli@11.4.0", + "-i", + str(src), + "-o", + str(dst), + "-b", + "white", + "-w", + "1400", + "-H", + "900", + ], + cwd=str(ROOT), + check=True, + timeout=180, + ) + + +def p(text: str, style) -> Paragraph: + return Paragraph(text.replace("\n", "
"), style) + + +def build_pdf() -> None: + styles = getSampleStyleSheet() + title = ParagraphStyle( + "DocTitle", + parent=styles["Title"], + fontSize=18, + spaceAfter=12, + textColor=colors.HexColor("#1565C0"), + ) + h1 = ParagraphStyle("H1", parent=styles["Heading1"], fontSize=14, spaceBefore=14, spaceAfter=8) + h2 = ParagraphStyle("H2", parent=styles["Heading2"], fontSize=12, spaceBefore=10, spaceAfter=6) + body = ParagraphStyle("Body", parent=styles["Normal"], fontSize=10, leading=14, alignment=TA_LEFT) + small = ParagraphStyle("Small", parent=body, fontSize=9, textColor=colors.grey) + + story = [] + story.append(p("Android Cast β€” Next-gen egress review", title)) + story.append( + p( + "Summary of sender 1:N behavior, bandwidth model, limitations, and feasibility " + "of single-stream multicast plus multi-tier (full / 720p / 480p) renditions. " + "Generated 2026-05-21.", + small, + ) + ) + story.append(Spacer(1, 0.4 * cm)) + + # --- Current state --- + story.append(p("1. Current sender behavior", h1)) + story.append( + p( + "How many receivers? Up to 5 when MULTI_RECEIVER_ENABLED " + "(CastConfig.MAX_RECEIVERS). Sender opens one CastSession per selected device; " + "failed handshakes are skipped if at least one peer connects.", + body, + ) + ) + story.append(Spacer(1, 0.2 * cm)) + story.append( + p( + "CPU / GPU / memory: Capture and encode run once per cast. " + "CastFanoutPump duplicates each encoded frame to every session. " + "Network send and per-peer UDP/FEC state scale with N; encode cost does not.", + body, + ) + ) + story.append(Spacer(1, 0.2 * cm)) + story.append( + p( + "Bandwidth: Per-receiver unicast duplicate of the same bitstream β€” not IP multicast. " + "Rough LAN egress β‰ˆ (video + ~96 kbps audio) Γ— N, plus per-peer protocol overhead.", + body, + ) + ) + story.append(Spacer(1, 0.5 * cm)) + + img1 = BUILD / MM[0][1] + if img1.exists(): + story.append(p(MM[0][2], h2)) + iw = 16 * cm + story.append(Image(str(img1), width=iw, height=iw * 0.45)) + story.append(Spacer(1, 0.4 * cm)) + + # Table current limits + story.append(p("1.1 Current limitations (sender-focused)", h2)) + limits = [ + ["Area", "Limitation"], + ["Primary peer", "First connected peer sets negotiated MIME, adaptive tuning, and network feedback."], + ["Codec", "Single encoder; all peers must tolerate the same bitstream."], + ["Send path", "One CastFanoutPump thread; serial fan-out to N sessions."], + ["Wi‑Fi", "Uplink often saturates before CPU on 3–5 receivers."], + ["Topology", "Sender dials out; no shared LAN broadcast stream today."], + ] + t = Table(limits, colWidths=[3.2 * cm, 13.3 * cm]) + t.setStyle( + TableStyle( + [ + ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1565C0")), + ("TEXTCOLOR", (0, 0), (-1, 0), colors.white), + ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"), + ("FONTSIZE", (0, 0), (-1, -1), 9), + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("GRID", (0, 0), (-1, -1), 0.5, colors.lightgrey), + ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#F5F5F5")]), + ("LEFTPADDING", (0, 0), (-1, -1), 6), + ("RIGHTPADDING", (0, 0), (-1, -1), 6), + ("TOPPADDING", (0, 0), (-1, -1), 4), + ("BOTTOMPADDING", (0, 0), (-1, -1), 4), + ] + ) + ) + story.append(t) + story.append(PageBreak()) + + # --- Proposal 1 --- + story.append(p("2. Proposal: single outgoing stream (multicast)", h1)) + story.append( + p( + "Goal: One encoded stream on the air; only handshake-authorized receivers consume it; " + "save ~(Nβˆ’1) Γ— bitrate vs today’s unicast fan-out.", + body, + ) + ) + story.append(Spacer(1, 0.2 * cm)) + story.append( + p( + "Feasible with UDP IP multicast + unicast handshake (group address, port, sessionId, PIN). " + "Handshake-only targeting without multicast does not reduce bandwidth. " + "TCP/QUIC cannot deliver true 1Γ— Wi‑Fi airtime.", + body, + ) + ) + story.append(Spacer(1, 0.3 * cm)) + + img2 = BUILD / MM[1][1] + if img2.exists(): + story.append(p(MM[1][2], h2)) + iw = 16 * cm + story.append(Image(str(img2), width=iw, height=iw * 0.55)) + story.append(Spacer(1, 0.4 * cm)) + + story.append(p("2.1 Approach comparison", h2)) + approaches = [ + ["Approach", "1Γ— sender airtime?", "Notes"], + ["Unicast fan-out (today)", "No", "Same encode; N Γ— send()."], + ["UDP multicast", "Yes on good LAN", "MulticastLock; AP client isolation risk."], + ["Sender server, N TCP clients", "No", "Flip connect direction; still N wire copies."], + ["LAN relay receiver", "Sender 1Γ—", "Relay pays duplicate bandwidth."], + ] + t2 = Table(approaches, colWidths=[4.5 * cm, 3.2 * cm, 8.8 * cm]) + t2.setStyle( + TableStyle( + [ + ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1565C0")), + ("TEXTCOLOR", (0, 0), (-1, 0), colors.white), + ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"), + ("FONTSIZE", (0, 0), (-1, -1), 9), + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("GRID", (0, 0), (-1, -1), 0.5, colors.lightgrey), + ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#F5F5F5")]), + ("LEFTPADDING", (0, 0), (-1, -1), 6), + ("RIGHTPADDING", (0, 0), (-1, -1), 6), + ("TOPPADDING", (0, 0), (-1, -1), 4), + ("BOTTOMPADDING", (0, 0), (-1, -1), 4), + ] + ) + ) + story.append(t2) + story.append(Spacer(1, 0.3 * cm)) + story.append( + p( + "Protocol notes: CastFramingContext.sessionId and CastSessionGate already support " + "session binding. FEC/NACK must become group-scoped (one retransmit cache, resend to multicast). " + "Existing code paths: MultiCastCoordinator, CastFanoutPump, UdpCastTransport.", + body, + ) + ) + story.append(PageBreak()) + + # --- Proposal 2 --- + story.append(p("3. Proposal: multi-tier streams (e.g. full + 720p + 480p)", h1)) + story.append( + p( + "Goal: Several encoded renditions from one capture; each receiver picks a tier at handshake " + "(e.g. full quality vs 480p for bandwidth-limited devices).", + body, + ) + ) + story.append(Spacer(1, 0.2 * cm)) + story.append( + p( + "Feasible via multiple encoders fed from one capture (EGL / scaled Surfaces into separate " + "MediaCodec instances). Cost scales with active tiers (~2–3 HW encoders typical phone limit; " + "3Γ— software VP9 is heavy). Audio usually stays one AAC stream.", + body, + ) + ) + story.append(Spacer(1, 0.2 * cm)) + story.append( + p( + "Bandwidth: Unicast per tier β†’ egress β‰ˆ sum of each peer’s tier bitrate. " + "Multicast per tier β†’ one group per tier; airtime β‰ˆ sum of tiers with β‰₯1 subscriber.", + body, + ) + ) + story.append(Spacer(1, 0.3 * cm)) + story.append(p("3.1 Tier architecture options", h2)) + tiers = [ + ["Option", "Verdict"], + ["Multiple HW encoders from one capture", "Recommended v1."], + ["One encode + CPU re-encode", "Too expensive for live cast."], + ["SVC / simulcast in one bitstream", "Poor Android HW support; defer."], + ["Receiver-only downscale", "Saves sender encode, not downlink bandwidth."], + ] + t3 = Table(tiers, colWidths=[6 * cm, 10.5 * cm]) + t3.setStyle( + TableStyle( + [ + ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1565C0")), + ("TEXTCOLOR", (0, 0), (-1, 0), colors.white), + ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"), + ("FONTSIZE", (0, 0), (-1, -1), 9), + ("GRID", (0, 0), (-1, -1), 0.5, colors.lightgrey), + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#F5F5F5")]), + ("LEFTPADDING", (0, 0), (-1, -1), 6), + ("RIGHTPADDING", (0, 0), (-1, -1), 6), + ("TOPPADDING", (0, 0), (-1, -1), 4), + ("BOTTOMPADDING", (0, 0), (-1, -1), 4), + ] + ) + ) + story.append(t3) + story.append(PageBreak()) + + # --- Rollout --- + story.append(p("4. Recommended rollout", h1)) + rollout = [ + ["Phase", "Deliverable", "Benefit"], + [ + "A β€” Tier selection (unicast)", + "Handshake tier; 720p + 480p encoders; send chosen tier per peer", + "Per-device quality without multicast; no AP surprises", + ], + [ + "B β€” Multicast one tier", + "UDP multicast publish after unicast handshake", + "~(Nβˆ’1) egress for same-quality 1:N", + ], + [ + "C β€” Multicast per tier", + "Separate multicast group per tier; lazy encoder start", + "Combines (1) and (2) on good LANs", + ], + ] + t4 = Table(rollout, colWidths=[3.5 * cm, 7.5 * cm, 5.5 * cm]) + t4.setStyle( + TableStyle( + [ + ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1565C0")), + ("TEXTCOLOR", (0, 0), (-1, 0), colors.white), + ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"), + ("FONTSIZE", (0, 0), (-1, -1), 9), + ("GRID", (0, 0), (-1, -1), 0.5, colors.lightgrey), + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#F5F5F5")]), + ("LEFTPADDING", (0, 0), (-1, -1), 6), + ("RIGHTPADDING", (0, 0), (-1, -1), 6), + ("TOPPADDING", (0, 0), (-1, -1), 4), + ("BOTTOMPADDING", (0, 0), (-1, -1), 4), + ] + ) + ) + story.append(t4) + story.append(Spacer(1, 0.4 * cm)) + + story.append(p("4.1 Summary judgment", h2)) + summary = [ + ["Idea", "Feasible?", "Main win", "Main cost"], + [ + "(1) Multicast + handshake gate", + "Yes (UDP)", + "~(Nβˆ’1) uplink on good LAN", + "Transport refactor; FEC/NACK; AP testing", + ], + ["(1) Unicast only", "Trivial", "Auth only", "No bandwidth win"], + ["(2) Multi-tier encode", "Yes", "Per-receiver quality", "2–3Γ— encode resources"], + ["(1) + (2) combined", "Yes", "Multicast per tier", "Highest complexity"], + ] + t5 = Table(summary, colWidths=[4.2 * cm, 2.2 * cm, 4.5 * cm, 4.6 * cm]) + t5.setStyle( + TableStyle( + [ + ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1565C0")), + ("TEXTCOLOR", (0, 0), (-1, 0), colors.white), + ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"), + ("FONTSIZE", (0, 0), (-1, -1), 8), + ("GRID", (0, 0), (-1, -1), 0.5, colors.lightgrey), + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#F5F5F5")]), + ("LEFTPADDING", (0, 0), (-1, -1), 4), + ("RIGHTPADDING", (0, 0), (-1, -1), 4), + ("TOPPADDING", (0, 0), (-1, -1), 4), + ("BOTTOMPADDING", (0, 0), (-1, -1), 4), + ] + ) + ) + story.append(t5) + story.append(Spacer(1, 0.5 * cm)) + story.append( + p( + "Key code paths today: CastConfig.MAX_RECEIVERS, MultiCastCoordinator, " + "CastFanoutPump, ScreenCastService (primary peer), UdpCastTransport, CastFramingContext, " + "CastSessionGate.", + small, + ) + ) + + doc = SimpleDocTemplate( + str(OUT_PDF), + pagesize=A4, + leftMargin=1.8 * cm, + rightMargin=1.8 * cm, + topMargin=1.5 * cm, + bottomMargin=1.5 * cm, + title="Android Cast β€” Next-gen egress review", + author="Android Cast project", + ) + doc.build(story) + print(f"Wrote {OUT_PDF}") + + +def main() -> int: + render_mermaid() + build_pdf() + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/docs/_pdf_build/diagram_current_arch.mmd b/docs/_pdf_build/diagram_current_arch.mmd new file mode 100644 index 0000000..d4d629e --- /dev/null +++ b/docs/_pdf_build/diagram_current_arch.mmd @@ -0,0 +1,15 @@ +flowchart LR + subgraph once [Once per cast] + Cap[MediaProjection capture] + Enc[Video and audio encoders] + Q[CastFanoutPump queues] + end + subgraph perPeer [Per receiver today] + S1[CastSession 1 UDP or TCP] + S2[CastSession 2] + SN[CastSession N] + end + Cap --> Enc --> Q + Q -->|same encoded payload| S1 + Q --> S2 + Q --> SN diff --git a/docs/_pdf_build/diagram_current_arch.png b/docs/_pdf_build/diagram_current_arch.png new file mode 100644 index 0000000..d88984e Binary files /dev/null and b/docs/_pdf_build/diagram_current_arch.png differ diff --git a/docs/_pdf_build/diagram_multicast_proposal.mmd b/docs/_pdf_build/diagram_multicast_proposal.mmd new file mode 100644 index 0000000..2ba8037 --- /dev/null +++ b/docs/_pdf_build/diagram_multicast_proposal.mmd @@ -0,0 +1,9 @@ +sequenceDiagram + participant S as Sender + participant R1 as Receiver 1 + participant R2 as Receiver N + S->>R1: Unicast handshake PIN codec tier + S->>R2: Unicast handshake + Note over S: One encoder one sessionId + S-->>R1: Multicast media packets + S-->>R2: Same packets diff --git a/docs/_pdf_build/diagram_multicast_proposal.png b/docs/_pdf_build/diagram_multicast_proposal.png new file mode 100644 index 0000000..4f75296 Binary files /dev/null and b/docs/_pdf_build/diagram_multicast_proposal.png differ