From 6d772b29fedd550f9fc9f761002272db3c0eb2f7 Mon Sep 17 00:00:00 2001 From: DCCONSTRUCTIONS Date: Fri, 25 Sep 2026 16:37:57 +0300 Subject: [PATCH] feat(control-station): add rover scene telemetry and guarded keyboard control --- .../public/rover-scene/dcd006-v020.glb | 3 + .../public/rover-scene/dcd006-v020.json | 39 + .../public/rover-scene/environment-map.png | Bin 0 -> 256580 bytes apps/control-station/src/App.tsx | 2 +- .../src/components/rover/RoverTelemetry.tsx | 25 + .../src/components/rover/RoverView.tsx | 70 + .../rover/playcanvas/PROVENANCE.json | 16 + .../rover/playcanvas/PlayCanvasViewer.tsx | 2043 +++++++++++++++++ .../rover/playcanvas/playcanvasPostFx.ts | 393 ++++ .../components/rover/playcanvas/sceneTree.ts | 20 + .../src/components/rover/rover.css | 20 + .../src/composition/devicePlugins.ts | 3 +- .../src/core/fleet/boardLayout.ts | 13 + .../src/core/fleet/roverHoldInput.ts | 66 + .../src/core/fleet/roverInput.ts | 20 + .../src/core/fleet/sensorTransport.ts | 1 + .../src/core/fleet/useRoverControl.ts | 79 + .../src/workspaces/Workspaces.tsx | 2 +- .../src/workspaces/fleet/VehicleSensors.tsx | 8 +- .../workspaces/fleet/VehiclesWorkspace.tsx | 16 +- .../observation/BoardObservationCenter.tsx | 18 +- .../fleet/observation/ObservationDeck.tsx | 4 +- .../control-station/test/boardLayout.test.mjs | 57 + .../test/roverControl.test.mjs | 157 ++ .../test/roverControlFocus.test.mjs | 49 + .../test/roverControlStartup.test.mjs | 49 + .../test/roverHoldInput.test.mjs | 85 + apps/control-station/test/roverInput.test.mjs | 23 + apps/control-station/test/vescUi.test.mjs | 70 + .../tools/rover-control-preview/build.mjs | 18 + .../tools/rover-control-preview/main.tsx | 69 + .../tools/rover-control-preview/preview.css | 16 + .../tools/rover-control-preview/trace.ts | 38 + .../tools/rover-control-preview/tsconfig.json | 5 + tools/rover-scene/export_rover.py | 49 + tools/rover-scene/inspect_model.py | 11 + 36 files changed, 3536 insertions(+), 21 deletions(-) create mode 100644 apps/control-station/public/rover-scene/dcd006-v020.glb create mode 100644 apps/control-station/public/rover-scene/dcd006-v020.json create mode 100644 apps/control-station/public/rover-scene/environment-map.png create mode 100644 apps/control-station/src/components/rover/RoverTelemetry.tsx create mode 100644 apps/control-station/src/components/rover/RoverView.tsx create mode 100644 apps/control-station/src/components/rover/playcanvas/PROVENANCE.json create mode 100644 apps/control-station/src/components/rover/playcanvas/PlayCanvasViewer.tsx create mode 100644 apps/control-station/src/components/rover/playcanvas/playcanvasPostFx.ts create mode 100644 apps/control-station/src/components/rover/playcanvas/sceneTree.ts create mode 100644 apps/control-station/src/components/rover/rover.css create mode 100644 apps/control-station/src/core/fleet/boardLayout.ts create mode 100644 apps/control-station/src/core/fleet/roverHoldInput.ts create mode 100644 apps/control-station/src/core/fleet/roverInput.ts create mode 100644 apps/control-station/src/core/fleet/useRoverControl.ts create mode 100644 apps/control-station/test/boardLayout.test.mjs create mode 100644 apps/control-station/test/roverControl.test.mjs create mode 100644 apps/control-station/test/roverControlFocus.test.mjs create mode 100644 apps/control-station/test/roverControlStartup.test.mjs create mode 100644 apps/control-station/test/roverHoldInput.test.mjs create mode 100644 apps/control-station/test/roverInput.test.mjs create mode 100644 apps/control-station/test/vescUi.test.mjs create mode 100644 apps/control-station/tools/rover-control-preview/build.mjs create mode 100644 apps/control-station/tools/rover-control-preview/main.tsx create mode 100644 apps/control-station/tools/rover-control-preview/preview.css create mode 100644 apps/control-station/tools/rover-control-preview/trace.ts create mode 100644 apps/control-station/tools/rover-control-preview/tsconfig.json create mode 100644 tools/rover-scene/export_rover.py create mode 100644 tools/rover-scene/inspect_model.py diff --git a/apps/control-station/public/rover-scene/dcd006-v020.glb b/apps/control-station/public/rover-scene/dcd006-v020.glb new file mode 100644 index 0000000..068a892 --- /dev/null +++ b/apps/control-station/public/rover-scene/dcd006-v020.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15f17f5b5b014e0f65273aa1565b7fb8e46381ca389a3c3d3ca89a32806bfb79 +size 54960576 diff --git a/apps/control-station/public/rover-scene/dcd006-v020.json b/apps/control-station/public/rover-scene/dcd006-v020.json new file mode 100644 index 0000000..f14fdb4 --- /dev/null +++ b/apps/control-station/public/rover-scene/dcd006-v020.json @@ -0,0 +1,39 @@ +{ + "source": "DCD-006_rover_v020.blend", + "source_sha256": "c49a0470974bbe65b4e65b4b5c06cb887730715a1ddfa231b86426fed6de8f79", + "model": "DCD-006 v020 complete rover", + "source_object_count": 1426, + "render_mesh_count": 1, + "original_bounds": [ + [ + -0.527999997138977, + -0.45249998569488525, + 0.0 + ], + [ + 0.6350000500679016, + 0.45249998569488525, + 0.734000027179718 + ] + ], + "dimensions_m": [ + 1.1630001068115234, + 0.9049999713897705, + 0.734000027179718 + ], + "translation_m": [ + -0.05350002646446228, + -0.0, + -0.0 + ], + "glb_bytes": 54960576, + "glb_sha256": "15f17f5b5b014e0f65273aa1565b7fb8e46381ca389a3c3d3ca89a32806bfb79", + "material_source_sha256": "ac50e29fe9fe7ab4012628c52fce30e262e9e0bba4a4d49abe59a90c84391b2e", + "material_rules": { + "metal": 1, + "rubber": 2, + "steel": 6, + "paint": 3 + }, + "source_saved": false +} diff --git a/apps/control-station/public/rover-scene/environment-map.png b/apps/control-station/public/rover-scene/environment-map.png new file mode 100644 index 0000000000000000000000000000000000000000..2b51a5876e824cb16d64c96541a80cba19a126c5 GIT binary patch literal 256580 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

9c)n|UnvKuN=7xJDq^gWW&)Mqj zyeFo?U?FD;4;!$@dS7I3C0zO5>-=~ueD=qvZ~m>wuV--Y2_9PLA36UJRs`*3Uc!KN z#$gPXWe&R@!4BwBm*?{(3_IQz;lERu;K^~^h188cRA`>fk>H4~gi(6*d7- zG94S|?4XDRtep$G#aV1!mWxR`?Y|jH*XnG40hz$e)|a9+l=z_BUrAfSQ;nTBaa%&S zdZ6;_x;*yts$Op-p{YZHs14Y$49Csb*i_o4O{;dig1MMKl#uAGP&bSZl70CBV*71c z!WpViDP)Lu6@bg28#DO7u?c@~g3nb=?84DTj3Wh|!VE)**dX}6*Qr)mwP=b3Dt=E3 z&kcCn%gubU!!TP%t%tln88n)B*z~alY8yy3_v?9m4%x#tH-yeLFv@lowpAhZ4EimV zxt2#l6_?hh8kYN$uTeeFP`7;c-}$~9c9=;YPNz%AiJXC3WvkNf}c>OuLzD5 zCID@kCRvjJYoS9(bX|S5hR{Ax?*`q{%o;pyBGqzM7`YA$xb>{6&E~wJGNZs4|0N3! z0Uy>|Ep(&p6AkPm<~Re+Gqm4^JwbhobMn0nr>!!qjym?Mn;KT+4FM|AVh!Zj48Qtf z8&a_e+UT5Z+0*)`(MBDKi(G$`J~=DB-sZrZeV3J!?mhomd+0jdYLB7TP+RosGs)Id z4fo1jwGf_)cAx2j0iKMNmMHZMNxi25SDP$+CDSll-qV3SJVU%5rE$)gmwjEUuQNR< zP4tkh#+}>L*cqJrj@Ob`@e>ut_jSEU(BF(>xZCZ*5wHIKz67cK-Q7w3eC9HZ;mhe1 zF3{xj8!k4DvD~d^^xEA+9oA!y=(WLnoML5EwB)t> zxqodt!zF7zM%6{_U&eZ}aNYp6GS`4ale>shV-0kC;AR9*quC<%(s(^)*Q_0z!153@ z3Shau21=*RVBj%)V{ZeECEgoj>G%~;HD+4i`2Eo`u-n8-wfh?!!SfL1002WcMeyZG zwSU)yYY7~0dZ+=XQ1zhq{am=Qz>|M_e0UE5eV*5FY3d;;Iqc5*)_9~Cr2yA`pWmRI zy)hhiV<)|yq1<(i2#{XSZR<*4TUa}^3v5EsW359@l^Wv4Ezqp54y$e7n>si8(r4T7 z@)}|T$LylYhOStF+TLG+S9IB&>8eKsB3!SWGip!Tb?sh)wan4i%{KE508BERyPfMY zA;d|KT)nC0#(b>y^Z6|AEB2Ws^e;BIRiTdG`d(q+fnPL{O|KrX;i^sx0^@xR@zQgf zU9+ZxT|Rg2&e|HCx2r~S%fuCILGJ99M(KzzY*g_t%+wtwhz1T z^ZR#Uw_$Ls=Mb-&-caxD;Sg^2`!HjkUry)n?Rvl|+`xjc zXXj`@4<|QaXk?bGPr6ZTg70#Byg!imV5YXf73B6}Xiw?4e5|+U^Lz<^c=tYh*dLKKv0C3UBQ$eeQNX}3$2#r@KJ)YKt<3+^(-VGQ z!??edXXZ4Ar!eB4HSBuxo%PPwcMm4dg%X2$PT_a)xt^!%nZR5pSwEGZngjN{`MYw+ z{A!FC3S5E!A1jZXq#gO(tN9s558n5bZux>F_)VlTjd>Mm)pChZnd9gB8R5? z_~1&2HUTCB#I1LO8tC(iy1bWBgMe%F{njil02({`T&y42dsyaC!_?oo9)q9jFUzWP zmpvM|8L~V;8>_UWfAtrefYoL6X_x;tM4H;J*Rz>z4(%z}Xtx45UInu|K>xa~YD8y` z{)dtrb;b?YTV4BKPxwgBvxJ+iF0*jZ*7%E6TP6IePXw&F0$tW%NvJjA17X+iG?e%o z!;a7OO?s8kH}J+r_kM<$P5a(M3t)a-*Hu3B1V1!U!+e`U)Q8;bGql#}ZQFDB-mE#d z{fNp0zTO}A;R9fs09#dX1VrhlK1{3fhP~)5XtJFnT!o_qf`wbF9Z$Q~zlu?-x2U=< zsmyE6o|Tgt$XWLHIvoSP({}k-DG7*yIK8;4QE)pcdL_j z{n(G{^Pqb08E}o4X$lvdB}SaF8;S>dd_K@ej+@Xt7#d-U@^B67c#r*_9(%q!9K#8$ zJTJ5KErJ7M2Y#ZD=Q+!>F%Ds|tYnRy!KSFsv(?V+StN6(_LF6$)ac>wY-@7Jm|9if z%&%S>cxf#l)(1@9Bup_Ki{du|z?Y>c5`#wujC;Bo>sv&wE zbZ&ZqZMIUmr%SzohJi`p5XBI;fZ-aJ@C71Mv1lF1pyUBvgDw@3`@NNY4H~R}^|??? zNQ0%+3E1a(mH-%r!CH>0Qw^-zxLnq&l&Z5YFl;1x9sC}CTTH$2*BT^m?`eZ>3t?X_ zHg(f*Z-6Q`+RRt`TC?&sR55>@1y5_C&lQpf{i}=;jcG`0OSWWa2}q4(dOUmo2(zcUkBYsytaH(4;8I>^^I8NN}B^g zbAQMMg7pD5IJ;m?lr(o+oQJFeC0s!kPp(E%14d}9wQXVBeI{Rx(#vHvz}*$!Ar8 znztQ@dT3qU`iSfe591&;ewmjBP70MS*O>$}7c;c<#H!UgC`9dPQhIH`#(Oll^z$%? z5+LF=Ow%lD4+yy)MQQl7BDMZUQSrq=G;UcJ_KvuAq_gN%b;bd-FZR!bf{v7SSTErFg}J(p*bEY2h=Ac%8%Ybd*ZfexVS1)*N4r>`%x)!gj zPyhLfy{+NvbRHz|jcZ!{ms&uy@lyKkm8d+uuf|3TfY@-fBpEihX z{+0>Kg;;gsp;&(8ToB06(;xx5ge5JZ8;@bPzYY6+3KK&4vMgag45YKI>-m;kExK+q zLk2nD7kqr4r!e9B;cy83Fye_Mb@GP>mK&D}6Ql`PAXQx!mB<72+@hm0 zr~b8W?HOeg_?;V}d5^{1`auNT+alQ;nrj3{H@FSM1uY`Dg04cNI&1?8gO3lFPit(p zPv}3{!ma?Yv(;S3Rzp#AHe}{%d^@OyrY)R{0X>XsAg=glS{1-CdEhtZkIj2gt_D+d z*qhN9y0HOKF?nuh5jR0uqMMTTE!1BcQs5K6etF*|ul{EIP=O&ZmZw zL8XCCnUe|Bd!oUhA-b$fm=}l}#_C#lXDGRJX{o}Dh2X3JW1blCfzrV%aO zVHitaHU&`E^buj3v^OLv0XkY!+)5x=N=FbX2M2 zCHGQD`I+Q5)9?=O5775WttF#g6U5DzP4Mf5+|0BbSobYqV7lpwL5hcpip)HfQZ+<9 z7_zS)>pjsjA~+YlInlY9C7T=|UZ#J}n-Th4tG+$hwFv7-<8Nn%tQG6ouOunoI|Zk( zT93fuEtIYAzF%aR#9ld2wAw5tp{^k%8jx_;AkM3PS3TR>a<4IpI)(PsvsP_vsF1g; z@XXQ>+j=}&anai2VanxL!*Mr;%e;me=j`%=eP_}7<@_A}+h4whk9T+BC%lfs0M2%% zAN5feYK`o92Aew5^Mdc+rz!mT+c#)?33s=*;)7*brBB1KV`r;RnX8?JyY~qNo^;vA#WsKRZ=N*Wfv~`{Fmmyz;od+q4=nW3MkbL0H%n1k=YY~}%B5uN< z2#&v4Q+3NEI*{S;FvUo9?wvuZh=7BLjX+}Ew~ZKAibKZKSQ?<7%mq=|$dw&*VxrBS91QP$sVre>w#bOsQ4bBnJTD{sn=8-Pl`o8UJCi_T#hg|(i&N3B2I972A%gy#Yo zH8l<9AaC-kh)>Ve zYZx1lT_>xF!}a0kC`M?4za3LMPv2+r*Y0@^xl=)!hb#V()XQ$sldn_;$F9+(p6er zyS-S>0aStmS|RM)NqCUsp%K4-7>95`Ej2!!KTS_6ts1{5V|Zart3uHFmRfE}7*sFi zCg59sS-}Rz+;W*7}%yY3|AO5b~-W4Q!sr^)40Q8t)&*5&QS0 z&Sdj+_D9z&Cy=c_O}Kl1x+}*PvSq=$F!7GON_|LU4EKi^o-QfuZ|=j*?Om8Im+;Sz z-$RdkJ|M)8yPYtO{Fk#?UEFJdZx($0emRHVzI_j2?80Gxz*+8%RT-C9Q6Fybe#FPV zAN1^9tJ83y&*}W4VS5)on@C*mL$txKHi&3Y?M%r9 z#L+^oH8)Kbt57=kL)lo1<2@}9<(ia%@USpSC6((MB|K%FDl%=51!7i^wF57qUJPD? zzj#uN7)oFT3?@Q=65i8AZs7MOSqgG=2Dw1e{}hTU@?d9Ny*JiZeI}PdmN0G%T%_Gl ztAu!00n2&+c-Vz}I)^7L(-}1VPr%-o|8?-@N{?tRu_H0i+j|N)C|FG}tI5DU=-w;f z&X>=>yV{?7bOmEvqu`E@W?Q^g9E>))Fo^%pjb+lI_@>t9=8jE872`z*6^(747LYqE++#(TrAAAcXscVeP~yqFqWr4;XW-KV zy#;38Bw-`Y=J>JMGhcV5O(WB4N|hy@*UIxG?X`>sHQibG_vZOFe9Ks8+C+>7p_on0`S%2{hL@om-FRjf;ZGXv<~Y^?BL_7snpZHTFG zv~b*sT>>p`HK@gaf1OXYp4_sFf2lB|VS&`6#Nf$xVAVaYKd&h~p2({OoW6wNa0vHz z`|$X*;`I{N^CgVS94>fYmnGaDk6{=_x&QI`31=C??r@a7hMQv;$Gv4>k~6a(^y4jR zzQLY-*YY_wxmSACQ+^pY#P)_DHTvH^Z>n8x*8>z={Vds}xPSot?F0KKs- zn7y$KMu1&57#NmyGG%C?0^5z0-C)rSU;-*)m(w{GwO9p<1$SiO;$JB&43z@QE?yIRsmsH^0dM*2V9|qV=oOTsZw(H!eJFTp4C=k1h$+_I zA!L1^ghg9T2YSrmhy5b? zq3>0GytYCw^Rmiw3>Oh8Z|kaX!&Nk$NPtI3FhkwhXHf~$~RR3FhEw5a^4J>k# z`AYh79@=6pL3r$grY*@}0NXyE$rXl~z2L6i$< zs<*VCDpdcygl}#@ZJX4VxxdygFN&Dqz>k2lbflf`-B1DbvR&4t$ z&m`qpQY)Yk2nI1oCqRzqvx4n9%r(GbIcKitZO*S|GPdRhwqfflzejZuN=V3A zEp)?HMFt-SZN7@w_Ai&fYJ1={c<}qId|yJh4H@mD!Z5cK$W<6cz7B*YV1oy?fruJj zm$l5#;o9|D3u|81RlQ{lXuYH$Zf9D<|62ckR)7(VRAuybA5 z8Iy&7fxT%hx;6RVinZ6tm=tT{n)$EPQtS(JYLU4KuDl&@Y$opamNaU^KNikaPS`f4 zQIF|Z$l!gjc^3)jT%29i!&NtV(?suky-wa#q1Z&qpiFP2P!i2*vqgM1QUm6eR@IR2 zS=is{S;!W8DkDJoSv_6-TD9$&5t<=N@RS*lz@7LliY_I<=o8a*@P7OHtdV^6{s_-= z?Sp9=4wd;;b;L06IlLagryJCLhF30l&DejDvoB#?FXAIuCUpBj))5|H?-5!ZN37GQ z@O+wZ-yp2pIh$2p0|FP9ts2yLlEcFNqycE zRwzHXgRA%b1k+;QVQ_ljN)b*;~^fQlrdrh@#249mux~D8* z!KkMi9_eTN!%HpScShZZH~RZ)=SK*f(L4$qoy|`&FP0P|Z0jYQ+;9`KazK#A{z|c~o zp9VofQIFT(!PxW+?z<4y^wjsaqx|{h*1=hWB{KvhG<(^dH z24+P$FAYN$cq7d2@1=E5+-@)B=>4uUG@%Y#rKyG}xL(i$=%>EM=x6KCj6KGh^fL5z z%>8}tszt{fN01|#=bqy}(^rH{=N3nRPoCcAZMS}wMEwLOI$%*=zbNCkTW8Rfmx; z1R0?W(RPx})wLvd-Wq1iT)2Vw6$qq*gM&y}7Tf|(2_(7CGZuvQaABOoO$rrbRtpXV zAH%SQP^ujYgvI@t_a1n)KXWuYxS|@Gl?pRZa7tl^7RGkfS{n4gO*fiY72b1i0&~G< z`C00R#31N{r#gVfs&n9qoOJ>rq;c<9`;Xlix8A3Jxj)L+zTrLRj*6yE@zz7b6=!Sn z)4k?A^fcD15uhpCIrog9X69@H5BvUKxph$T;X)qo`@Ng|E(H%`5lOA4M+R6$y|Vt% zH88+5f|2$4svfh~_9zqxcQ}zN*D8v2R2w7d? z{`Ri@2)9bz_LMSS`}k-w%Z`Nkgg>Kei0$-jqhAy`LFmtEx|=z1 zCi%}E7DWTpCNN>$?xnV>L71gv-^G+`s8jkbLWNltHOg}T!tIy<8EE9n2yKJYSmrSx zc7K=PnzdH{%qeh1wIb@+^TS$fM!J3bugRh9nQ9@F!AnGNuR;=exF0_Js@O)uFD$E(EwYqH#-$iK6ds?E81vVIEzQ;V}-ss~o0vfa8>FBcoSt)_ zh(HD$j^77IXOD(Y^jxZOWL@@Yt{4Wfa}UD1l^ z*nYd2S!fM^h2uu14jym5!ZRvjpVMp^>MLa*IViNVJ^UfPNg5;157^6P=0_^VebbvV z2Jl#eOof3>GPto1)qozxa6rAiISee!DJ4Ir0JgoF>8St^8L?zxX``nMBRyEvErxJ? zP7Hvv>lD|=zpp(=|ND1e%D=w7m%ntE-L(${i_ah&=I-|jIHI)zESrtth!dPJ1Oz4~ z#z=Zb@>2RKt!GeAAkkjh9%gjzb>H>6ooK*m8esgjRY?O{!;qW&awy0=gES-uH1h~_ z-x!>Rj$>Cf9{Lx5w$vACt=`9RZaI(qZQi#C0O~az6NIA2mzSgb-~ZSDS^n+c|GoUr z|M`#eckhb>fmCNLf|W!gwcNa}HCaaA3iOJgKUO0`$Cepq4Q+CWUiDz``O+`{!XgW@`ap>zO8LdU@RWwXgFs=3T%$HZ_nX+S%;I{i#ZibdQAo zz5C|jS|34a{%lo4dwhNn1S~LA=bXGw(R0J0%&Y49p>z8)`c7P(-{=zyD)WUgi8Y1e z%%T<1UYtjs75$B7%>66mgmFZ_qjzPG5>Jxzn?6r~g!9XbO-*rMcFkKB z*cnX^q&|=AkS8sxNOhjIUSgId_%oir*{h0*e_rK zfkk-QRUGJ+;&3#`#>8};Jb->}x_X}JA6{R|%lmuzm$yfGmt=Kd7>M5AA2R@tq#>qrz4cz&XS^pQ zG1lbrzy9%`%a8y0qukS=v8GQCY6Uk*2*|iuWJt)Byw<~=`#1*Tk? zQzt>wVSG}5iS@X=c8|0Xs`~u<)C!kd*YnVpLE1(~VTyvt$dyE235GJ{+vc;U2}qBc zu%PyuqJI`b)qOio!b4v(T!6|PzL|QpKqU(dnNPLzYOe<;yw5hD`5joHpB@kKMAh|X z?Xxs}nowipHDoGEjk6XCQj4cV3*+;cFW0cbxc<^>|8r;HJXcgKpF=beWFlNhuZ7N2 zQ=v0w5enbtz_EvW+wYAmh`IIivG53)ikW8|IjZ;+waiN{`g!A~1m&9<#37@UXEnG5 z4>o}GK#bwHcFBUMOHW|PZiHD>%o*4vW;bKlwg`yWcQ``~I$V-qT>`WOt|DY5Kf<9! zz~?~6$m6q3Sw*|#Su(in&oM~e3n|PBjaZD%0QU$-N=dEM8j9n|5qu+X=j1pG)b@_~)C@h zMyQSuSOg%zixwR>uV#>uw{O)GOmp7Xl%wu9?bocY_wbMZ{lAr; z{_;b4`{4&*xrNZ^MT^kaI=l(`Sr|}*_uB6i-mdM)O#*J27rb}<^597#2Vp45y3@`(E|kR4V=X)4AzD;+Xx`( zq8X4<2$~eAo3H|mP2?D@3dLj*^8_YC(K4**Ah+dUADhH?x1;q41I+YJjv}2)HuT?Hmll-~|}ZO-&KIay8;?R!+81^m64Kzcx&5(^b z7=W?YWwXD8OmPo;U=Yl&|GgbYc|0C|?(o*Kw)*JtXsJKQjtFM?eFWt99sGal;s2%o zb+6d({mb}00|t@80eB8zj#LjKgjOivlBi58)Kx#ihJ>)tm+2F`J3~Nfpi%ehYbjs9 z7HZbNQto|T@4cT7V`u;GrQiGIxPgf^@_?5~!1F7onkEB#ubYK9WwngI|M8Fi=z5X$ zqZK)*LIX|vF!aR`5-?yn_k62%O}85G#2CoNThOQeSk3PRpr&Ul?u&7u&RLHC>U>J> zpML9l9q4V^Gs1&<|JeJ>poQks#ITgt@Ici*6c5lik)44(AM|OfpJQG+KJUdaBCqP^ zoLgnb4Tk&!ttP#qh7fv`^BO-J&u#zPfRlX@a72XGjUWuf2%FhB51~I zlX3DxM4}9kT+Hk%#kqCR4FXb#=1nhYfB~e=wZ5)dR;~)*BX|Vnwx5W)!WZuX1m{vn8GihUpryz0Q`HCdFpwN0p&iRFa7&J zzun7E8i_uYs^R0|HjH6LjlURHR!fN0#A`Fqz-tlQ5%PRWF#^No8I>*R!kXZ09!?u0kNXBx>{$lGGr;Nu0nV@>0IKwp9ozyd)d7bW z*nl0x@-tb`+v`5`syo#xzY$o|)*qfST^S&)kKBYGRKv?X?q=}+>Gjrwo=#`uO)!t? zeCLB#>q#0g<4Z+Nlx9Rqu; zW#P}j-LxF3W~Vl!cbxoW^gZF;bvK^lC8XQ^fw=^1A=d*TW5@RiYUK?@D6C;qPrWG6 zH}#5Yl~!$=nUr0q!O=@L{c;}SV36cMcx>`T8E+?Y4RG{v&Ay^%m=o5_VP%92E?6U9 zcsXxN-z`=|V>T7(Bjzd4O@BpmfiN7Y`gzPq`$kC%lyc9!CoQT{(c~QY=B`BZZ~wp6 zL-UNLSCz{l+H9&QL7F`pJw!B8vwC1vJ1xMN-TZoKtc8<78kT@;G{nF|0p_q{65QI~ z7$yi(hfmHbX~N;VM{_MeL^L`GSBU};%ePvf=^k$&_m!@ zm>Xj?+#ws zM`m^O=fCvtf9~4)(nCDZn1@K|^kJ(v5pChV4h^B%nPy{A3Y2EzK1uVUj0F!WyOllo zP-by0z7 zO^^25fXEC{oIhx1YSECSK4J?A;E{6)ew$it4_Rk$W9ERiNY^6>p6GaZc-~)IXUq70 z%=3*tNq})MLVH8t$;XsKf6AC{!wK+1pvTPI=c?Uk@d25Wb(s{a+f>hwVC1-CG7(m> z9!qUCd13yo!G7r*&YdCfA>=fhY|eAv{vT_P#w^<4e#_h%J|4>+U8ApnE)9&Z6?G0)9`4?LjtpmwP@YO%wR*ddU+n)rAQu~ z?y>>GfGL9#IHN3-a0-dJQj4=22nhS;vW{S*C{7em!E&C6<% z3H1K`PhVck?`}u=x3~B5pVN(w6Bm)~Ir=vAG9cG!KLtNOPuM9C`|Q?=tXBhwvxhYc zS`JMq{2Q3m8k#~pg*k!-`zAdYUuC8lM4bOQ_1yGfIAa8p-9xdinW}(oJ%~9VJo-kc z#|9m9h@tT*vlN5{;QKH>*Iv@bkoKQuDdc2@pMcP&+gS_D6zfa*Qb{g3|l&o8$$S_~9v%|P8ceh7cnF0Fn*u3B8Oryp?YEG!9< znECYEynpnzCEZm$a}}USKhz3ZrVa)$xxCO6}}yPPN#ax^5cR*-yXMJU`YL zTv44N&+q-7ul@QL$xok&=^8Z~A8X83j#DUMSU$JPeC7Vv;gD;$9F;ARdmR~$0J z)B?bYv4PA`a&fZjrz8NE1jHp1>#@S`C>#lBu;gS-k)g@&e-dZWz~LzDq*O!5At z0b>86`Lu)>C!E`<@g83AAIF;O*K)e;a3W6lX|@Mlpy`187(HT`7@kv!dd@n|FC${)H8{`C4%ZbuDX45rlZzG=gB)zsYPl-NT9Q_ngz z%?q#zU`+TWGY36jwcEL-2XV%zJC1nHf{^*nUiE9nDy-c+daD*sw!d4#%$_yi$j}v( z7uGxX565p_D+rdHZNWZU&-GH3kBL$g`M2nMp08hg-0$W6{jJ;|_kO;kyu5xX^^kB8 z?RpH&IWvTM@~!E3T!3uz{2Dt*ojc2+k>^i4L!g)s6IutyO@+>i))P>CJV#^6CY_YkAv z&;**x(7=o_F!p{pUlam_jRe8Tx!H~AJ>@XIn_+x+z%9^Gc*}Ba8jx%u!dh&=aG66; zHJy=ih*Fq)T>tLnrTmW`{y+6_|F^e$`8sJ_r%7kuyI?x{BIEna-``De1o}bfnvRFr zATSv^4%y|;S)ClB#&92>t3y`iV}T~Oaf;Xz)Zva%_gIVFFrhO$+W@`=IfM}*Wg1w5LH#@*iI((Jqo+LA7^}F)Q;rBGY^zO+DKU)jIF)@9%P^&cEZh6+om8FKXh1 zRPANrGl8}&bIRiH@Idmjme0Me>H;juhtv9fY-6>|w7#UoyC+nM*V- zV+%mF7#L0(-zfvzjAsARGUO=|2C|KP8iu3|&}pN?^R7+Zx+86nI^YIaOY|&Z!11hb zL~AktY5})CtKav}-*qGW@P04<-i>h|bxv&r&{K3xvb8zq(1vV3=E@jPz$dvkaO9>V zofCtx^nMF>2`~2b8~_04f)CzrC&r4u_oT<%WydHhMM+ctmAM+&lxiMh8jw zD64)*JdzVWKc9XwpL1pcAydapMt{dyriVg9j6t%jF7^YLh^N&0rjT1x0 z56_DR`>N)(r6cr*mWMB@>WP5oj(rA>Y1T-OZC1z-yq_hPP$OPTN!LYd?B}tWhrY)d zt6mXd;%}DGS$<7@!b_%cxtX}foz0-eU|I+!33-Nqgs`_F&Aebxj#_(hc9;A%8$Ln; zfyAq`&=ApP>$L;aMDH3Cbg(Ok7k!HSJ<)AZirW!NSB4jVxwT_C1EGel4S>wd;`BKn z>(Lad8qAuDJ*ghr7HqWdn;Ed6fH;RKihLp0t^fY}*OzIGuiY3wJX(48iBpD@!haZO z2DcV60;P2e-flNFNk5x9dw|6HB=drHC*^Qle@GFQ36tEXq@r;qn1D3j`Cf91A<&v1T&1a0`}443?Xr80FU5441b%;P}7Tr z9PBsFSxuJyr!3=4)+o8!-tJ=8UE!Z>|7!!01!lUEP`I8bHTpqAY=! z5;8ytIjek#08GK){;T1At--xf;kJ@Ps(?d~I|Qb*5tFf?$|szC1d{n#`#{3e&_`Kf zw|K9|ZMQL>+e-?>wN^@Nu}3Nlt4%@XcoiGIpKVS{?Gn)Ee)mt=g^)? z6Za7(Z^s_)M@$6XFuml!O`2LZMhdcX8GMQs^Zlx`q{8`!XR7AMs!{7-ni2lPMvxMI z(7A|Pbm00(VvBMQbsFIhCBzO%r!tpSn?877UwWd9?eCjCa#(v=Z=z)l92Dn`(}7KX zdNA&XY$^;3<_T9DvO=G+z_{JRKZXrLawB9rn{nc(oM4Q>E6vy1bh@OUIT@ZsdA?N$ zGVPrR4&gk`(G*r*qf;Hi2t*6RlDwC`wH<`UdxmQmy&zOKp@gNPb}UHL(&aJw*YEjq zJIbwxz}w?de(cxZIObeb(D#SH+d{WinfF}hF@Aq|keg=4JrzaKI7W0&+g0XYx&C$Klb|`Wl9k|E(25l_H%zZsQmso%I|Nry!O9i z2H}6z3U`YYF%xThXg*fQILMjC{0NCzl+@J>_ zDW@=1BC0^6_6UVq4671q=zRb+r`2ShV^3$(l6rQ;q4{yRlm1nLHtM6-fMSW;3CyF*(AYd@ujCsI(Jwlmsw*PC3>LXi7EG zqFhv9HnQFtQ7}3gLnRs=D9DB+hfjff1P0Caa8n6nzw|)3^bng#qgOLHhE;Oob6>kNFYbv%n9rp}xDjG29X zyZ86I!?kL?6RKMOZOpdU^T*fM^5u4v!;gv~u3%H>831G+GIQw}Cw#EZqk{LwB@5K5 z{=s`fH`&Cprud)0^KlOW*b#)I4wl+<7|A*jgDf95Y-11FEsniC%H=4OwP=nG?gybe@q3qy`zB??Y#S`v?en|9j}qx~fz&9(}1?zUu)#0{yM;|Mk%V`UXho#e|TZ zP5XAA;RXGEh|p_|0JP6i3K~P899p$~Db8FXupIS}(^f~AsX`EEqfL3K?%7~H0nBbf zdscnu;cuaX^_x~8HzbgYgnEVnLl0Vn+U%!5t5{%Iu?+s2J*^>a)!lb}zdsze^5v*~ zo)HGg{>}CVKKPFmXHTP!yJX(@JNeA){;HbL1Ey=B^7*OiDVuqvYNHwS2D5KHe5BOD zBR1~OYZWoz=Z2ZHXO_%eHRyUGNmhfeYZNqwNSWbbJ(;(W1T#iAMStu;K?9fj?I?V^ zmE-Hz9;%!IflyZqOPr@FNQiD>U&(=GT8lA;2}GmeMe$5#OYuHDJQErO85Iz4$Y5K$g6-J(DL%>urmH^nAt}h8!mo`;t#LJCH#?C>Q{lLQbJvJk$YQq=kdp#5(WFl>%9}iyRPTT|@nZ4opy9vUBkRo&n7&xR45Y{UL!ndli zqH9}+4+Yhz|N9~M?#lb4m7n_GA0GG8`g3FAO#vwZBw9N<$R3Z!)CBfljMJvTeQpnZ zzW1fp0PQqI)taFlu4|K;o7_)%sSpd)ac%~gqGtMu5=pA|g^@6Nn5LQv2zAi4*ki1F zZ~6C-hz4Qr+cFjL|xi+Cc?L9sD#@rDGa%wlNK^kL8v&rtTE3%w?Q*I=>t zQ7JY*460d32&kLay)uhH+l)ifevDc9xxH7J5yHaW)A#pM#vA-@p%PtDo}&b8AJ`kE zy?fc77{F5pP0tGT%5CJ#^Ak7J=`uZByT2a&?@#^jt-pU4H=n~*ues)pSEkDezu~Ml@=gxb*e+lmetsby`GMuyOY3RG@OnhJSv}KlkPCZ=uV!d`>d4L3uKxo1OWwD(T z`~~=j9OH5ux84VAi@?0XiJNWCt5bAA{n5x(;XK^6Cj!uM&k}lizR^3G{(ysqtinQaSJoZ6NI=wUGGCd&)X>4@wFk=Y z`sY8kTe~|QX;JxtC+^yJ*1J5a##)A!?XG%y!D4tn0`0B|=SF)+}2$`GhitC5RRRYgBbr#vY% zgsp_gR10i74^qqu)c|o#NEhU2aC*>g)kBM~Gz`5Kn!0e#F>+n@sM^DjIb%#g@MZl8 zUm6w<^IfFN(U;f$_XWMgI8{t!&df}BU7$-Rlc;{qS3S5@m!>se2mC$Af^`4IKSK zb=od1Uy33F2i$K^9B#tkmkrE_J)qhzJ$l%3|4drr2xZcKk@l9Ck+_qU@57?-z4D?j!+@4fi4MrN`N#CJP2Zo1U z$&fMcR1uE!z>II&j)3>Q&>q^STmSn)5&EzF?_o3Ztfhe73JRNr)&Ws8Ykkb%>jC0p zQNM8p&v>#94@Yc#xoGTu$h=`ptJ?w*Cz8izvU^8Zqc_I=vGxfwEY>5c=JJ!gncnhad5HdJgt7)Wn_UnN^4s!VKW;%xG|;yyk_BQ zTnr*NDm=m(-U3xIi-#&>Da12*{|J3Al;7ghw@S$k5=CL zdj#pd-}hsaP`#^&33Pzk9kXYh6++*5N*3I-2$}*Kb9DHZ{ob}SYC%B)~Ftw@-m;NIz;M}^eGoyy(T&Em46AZTgf}T7$!@JgUQjKEo zQ`jdWXU02sykF3F(@@Hg$S1i@i##bFb~ZxrTd(XNogLOelZzgcvm0}^X&$kVrwnCY zg?EI>QwWeJ0U3$|nk-D}(FqeM#UZU2N+}{-)Mp9BVXM!g10)7g0hk<~6*R4Nqcj5v z3>t^x0UDGu(+0e9fFpFGtDMYr4vBz@8>U(htrr9U5aC<+W;dDXMvWC@iDCoe_Zj{4 zA3w)oIGpGTkR*Towgy-6`PHYabe@M-_0*u$5~B&@QRRikvFM z!SKsy&Bk{VmIzaq!GwwaTI~*?#U}Ivf(EiRSZp`hE9q+VuHRexvm@wVdlJ9Utp|SX zp-iqV^MpuAG}CmwYT$8YH6;%KV!So<1v*kM1{bNXtwu`0!N|r~2fS8wPD2xrHNgae zeog^}PAh1^$w|4#O6ss`CVYfViO`B!+gYs6s}}{Uj4fqh3IuB;6ZRiUBZwp9Q#=;C z>hIp;q5pKu2y>J-&Om&lI`{0*x*_Bk*oV)jnR_^CKa~$QPeMKy5C&$wUYD(Kb{xGX z(dBeAG>+{)&lku5;I-x3=+=Gk=5e1adbz!b&Y{c8bP|gF)r4uLxtPuQW-ZUNKmiR| zL}_NG(YdPkf@0>zA!Bn31?*0O-$y>&Y_0^*6Cs5o9iTZ%!_;FSUNJ%>%BVUtcqq)G z@IisIC65hKgUUc&z-n~zH~j8YDbQpUqo*^3nL){f1VQ2+n#PdzBz#JOj1SENA@((x zv%#G~*w}LbtyD_EXzeYV(M(v=IMvs#x;`bLWN}sN31-$bo;)x?+kbhmgv1Og(u#CO zLvtw4IOy6sxe2+BfMUEn1APf~haG2EK`iJaCWR>)V)7 zk~H-|$<=0eN+E;6aagc`CS98(8gswRwObC4f{?N>E-56M`Ag@aZYqHH z;?cu~Ev$QJZf3penZPIEEk@7k9-=i$z)zsnXY1`Iq1?5V11O$(@4wxf{C=y)?6-Nr z&t0;F> zKgZ&7U`}hhb_f+%BxeHn(Jjt#feL5V-8}`D`zUjp&f(?PZ_2pZXMB|-coqWRV}*pw zA(WeGm(k)&&B3sOb2CkH9M_-lHgU=rTqdgpLn`XK*-)3`hS@Acd^0($33tk^0XPA6 z4$f*S=Cvd}=m-&oanm zcpqmN*#5T1m}_MX?66H3%ad{#SPXIWFUQNRyz1V6e7()zAN|Jx=TXzW|MS!Pd%sr7 z+oR1MjCXTW2rxz`YF)6bpoub#_IWgay6XE-z*aLONwANg zAFCa+zvEt8-{ueSbHIt-*aIklpJ=1DoD&zCm1FeG!Om-B$$1*6gTP|iWX4)17h=!G zQeo9zSZxfQo?b+T0t|iMQRl*D^QMi-Je6!kIdJEi4gFDJpXmL#&Q`beeEK>#Wcw3@ z5hX31EI0imNQ-dIVQoEct}kCsJu=p~_urfOT)kM-JER(ux)F+m(s7nI3ns{8TC-pY z5?03Xa!!Ieo!tS{O@Km_!5nzCtA*jbA+o5Y?ox0W^20g4gmmqi8v2i#$j_b_>F3Oz za{l`si$K>nO2D>kT-MW0hK@|*WTBJ$S#nP~V#DV~OU|25ZUn)48q1QWLttVqHNmXM zz^4(y(Qtp7ojX0~n-?~WWq>9da#2}h5#~^%O;#d15N2qSE8J6|t^Z&!k&7Tvti&h5I9{uy9 ze`wkpxz;AU?)~r3?bcrta~dybW?dP-$N!sKUnuz>X0TjHNgu$9d*?oAPGPnp2?bm&v+%RNss2 z{MwUd{YUYG@L4^G=Sm!51hnXr4Ejq*$&J-n^yq&d{S5cVqdfY1YU(&76x}Zr;gJ@K zAi&E*lWd{OK_59Ir&>Ab;j9Dqf`}YaDVRyib5jFNXS;~KrG2!obF2@uhw#6Ij+37f zr%WRh*;-(h+C%dka!zK-z%bb-I?V33mE~5q-7jm9uOE3AKyT3dxE`~R{MtlzIy!r{ z0#rPMZk*693pW$56<~w4wJm5{WG$nC*fu6N)Lao7i%l9Z)iIo5sL-~s#-~ z(6@Wxdu}SFAWLBJaTplr1n(K{1~CmD`;I=PT{s&i`yh>lp!R@N3VdxmhYxHRY?Mpu zC+WsK?6b{~pa(2|A#Z5^H1B0KGM%(+vzY((Suz`Vcz(C@i@!CfApk^2&Cla)ZX5Bs zrU2MBt5Li5L+Y2cJT!`hK-q_r);0kjxDJ+4oEzgpchTj>+4QqYgHRtO>z9#LO?$9< zNNXnb4yS$doQ-o5h+JNJ=)c|$m=T1Qwzgr|a`1Xq+Cuhhk*^FLy)?fKpN>Ay|N8Z} z*?XSh;aD7;?q{6Ox!?N#El2O#gA%v;KMW#|TnV-@^iL7*J(0Ce26IgrXC{3tAKaF~ z_;n5z^Sk=uqIj*J)NQQI$`L5(u2DdzF0ex^h4hbP`VMGB^hvMSxF-Pka!BH@TyoTNg;v{eFUUY*17HA9! zXVqJOPpFs)CF%N&)eB^7&(a_JWb4|^5`+l0>h}?7Ce1*=p66sn((8OqdeqPYQD4KuHas`^ru@U+RMSSt zAE@RA2o2-bw5kml?|+Lkn3w+F@PbBS609n)55MK>3$!@TP@syr0MJ4CSuj|`tZ)o_ zZ-xg>|9u93ud74l{&?&4-Ag&fBehh1DMuw~EI|;BEoz2#I*0F>K^k>vNMxrCHZ8h<@8nJ7q#vsrp@hA&XKmEgI&8wEg{~8flv!WSxx$+u5RZo^tLqD9@2N*g z*5lqv5=0dOa`vo&rl6wNuLk9Rt+k%Gfmhfc#>YR1tx<(pr@uUIfubI(wtNI~_x1LK`kQEP1(b^aogPMqpNrn%?QLvJs&OD=n!I3rRwP3)a7w~yt z81#d2v6O+FxsOnUS7YB5>L;v5zLVPALg8n)fxk~fMO7s^^QS^R{sKF8CRTDW#I*25Q_GCwM#e(ewG;tI zSQu@{U=mc2xUhYBhKluU<2(>tqelo*tZm*^O)m)E@qM_OmX0oX)qu9z30ghp#^)n7 z-G_i5WeK{lnb5Prp=k|k@UFK34Qd+1aA$O9oG60O>La+fL{O&@a7KdyKh}`DMO~t0 zd>u#CcJqRS;8n^W2m)h;ODelbe%s8z0=Oz)ttX5@r2XSnn>OoBA9C-5fwquonLs;a zK3L`Nd!GYumutlW9{S-NAqniF`L_2O#aa2R^|%cj2Y|8Oi&tY};=eSK#8SA7;iEmF zMW4}Yt735KR_#CphQM%P5Q0{Yx|WO~g{uXFY{Cj&6uF}TR7smb*`ee&O5y7d^xtc5 zQdJ|sgW_F>HVzd<5a(>Z|Hjh~Xk^1w5d6`1yWJ(jG z)=r7SWicdyxj6w;j71s{paJbWyBHwRk&G#WH+=-{v8|`S9krHYoOxbvXK0=vs)1Z` zn2u|wfK5xlLJmlQ2JkBYp<(H@0yuIi#FttFsO0;=jB*FVCzL&l;bUO4C)|2fm|-3$ zOEA;Ib++JOtB6mnrA@oZlQlGo;o`polUV05d+7aJchW~3aGd>9#6RL-J~P%rbAcaQ zfWx_ko1)>-%fsqy&Fh|QY04DWGYOIl%oFT9dpPFWHQf4}`udjBb$Ng^5hS?4A0eA9-FjspHryk7{o$(Raa=-qRb;=(W$P3dXIAh#}F}zRp{e< zozamTtRDyUTA&{cnMfx+ar#YscLe|X>sAar93V9T7u5kR)2Jq9{oc9|t=$FJFtY+V z2p%LD-pS6qV^xF@qvI_|7G&^(1RETrr~f+cZd!94p*EW)`&19Nf$92N6~z zzco!z--YpMI`usD#|BCfQi0n7A|{6!L;vW^oe2;()xZs_nbfdkeTN`u&>u4%flcPG z7#uje^O{7`EWD;ml6s{>lL2bhuF)Zr?Jz89%6Mi!3@GvH0|W<`dX_5NNj;fx@F zy_MDOVV^tBUx1c_70OyVY*`{TjZBqc%L8qJ=8mpdFHXVcPbrB-zhrtah{1kS;~Xq- zP0eez9t0>V!5J!_8TKJ%iTztvR4^!sR;K8j^*oV9bcP#SO-n$Sb{T?y>b_(x@TzTj zhCp8#)Burmj;L{lJ4GKE8MDm5Si~?#Idcvrs#<^l?fv#r__&pF9Od}>Q=xl1WeCT{ zCy0RbQ?yE&>l)OOvMQeE!6XkX*3T*$?(ty_{3UCJ$`$WjQg3f!&PRQLyFn9Be)jm}i*);{zV*1Awdq zyI-(dWg3U%W+S= zznA)WOaz(+5G!t8(qPip(3^~ppw^fLt+@|afCv-2xyQ6dT70FRBWQODklbO1YONyD z2u(_UUc@we(DW?WH}euF;ATBD2Caime7QuIdY2>Y(Q6dFwA&=-Dg3R^c{LOsJArrQ zBCIVQ$-gzPO?!^M$ODb+!}$(PMbfxt#-GMI`#xavs?C8@eWU@npD_M1?t6KED`mVP zj3Qt(M519Z_h6pXLRk~QdOts5o$-n#DfJHYMRF14BiCix!1ROJ-RAp@<4f%~doLMGY2saFg9)gvj5F*^F^+dNyJe6Yr+j_jZNVaKsyBk2HBZz5G(=8G#(B|Q3u;N!=trwZ;#UYUxsip zA`tj1X_yZwo^7Z*CWFS}B_gjqDGhERdJ?icti) zd4)FfIQ-mrvW&acH@p2Dgzib`?<}_JcdeHI;2<;8vIM2&54-J(=^6Wn-r}ek5Hgv` zMp2Go3wu#VoTo~8mRYMyAHbSBo;6t8S95@>!729-e_Hj)>OcQ&o|)%)Xq^`&BI*ZD z0UlMY%vnPeawqwaV#aPLj$^?hL`DLgm2+v;uRK?=0A;;A+p>SjLiEgdLVfD5IHyDx z#@QPn&pC!+PTos>P9axkP1V;-IvxKtea0WkEEU-f22#0k4?pjuLW!2aS_|}NH%-D5 z0kCFar~^tv98G3_Kny&BXMnYup)oU-aF1qYEbcyq@cSM;_{+;{DPO)!Xw%zUxqbaw z+WY&!p$~MdJAe03qD=H82VQ)s!%KCSE$qp5Bb-d`?ZnsnIi(cky3LkSS_Fd_t~o}CvUCSxzT5}{${ z0-|D|A(?!?1#kYHyJ-*sdcOI4%>^tki!>N>0;wjEoY{hT{xy@^UQrXN(TGnGkpWZ! zigqepI%|AspzW7Z)mxYh8rajzYecYj`@9#!nuAHX){>k~yBOiKZwy$BBToU9J!LoY z61xQ747Q`b>|d}GXQ`3RPygaN+OT>pcR*``3SkJRn8h` z$mD(!Hj#kG6lZZad8wI=>%5Vpv_X=mwFTRT@lKc2pPj-mnAe6u@B?p58U*~w-qL%< zy>=rokr&QlUL|raJR-b*vK_j`+tt&23AAC~DhS!wJ9>NDHy!#jgRp9YdEZzNl|#2h zQ6?VOgBsHXG|WVS$P>xq7Y`+?CuOX0)0-V&EGEo8XzrmuZmy5}{CgZX7kkn;1Y$>%xKQkor81t|-5#GGmUzhIFQ~=W15DdFE3;-47+^5c7;5;$l z46cU9(MY0zInX$phj&ndNM?nA)-`&@OaHJzBKw+Q*R*E!M1o7&znlU-W^~rythCyB z@%oy#D}2`l)2K>L6Tq>;wXtcLPCPn+4lgF(gg@L1XzEpu48cO$>$DGKUH@Bag>!O_ zBZ7iCG}BKOGqNDjxrXeZP~iN>e|? z5h7%?4hC+Y#1Y|+j~i!bjcwX%HOJ}TLG{csUo#wr&PtA1Fa65C$@QDh2>iFoAong_ z6@->4N!|k{@dHM_=N}5E?3zH)5U7$4QByS?}*K5DE!02%q&6Er1z+X=jr)$nlz1@8$=| zV?Zqi|ML6q%kTgE$8x{l%a8x@L%Dx_FRir!K z)07}wvvk4#3lVZX_!P@z=YI!5P)vC1y=GkOotHvs0vKx+)Yxnf_5u(~A+^!8Z*Dpz z4>8jnL*Nnu%bTKo<`vQgJ=Hv^&Kbgg(G;0q0FtI3?f~%8R+u3~@g6rCMUR`VZ;;}I zp`6LEPXs+Lpv~4GubpDnlr4b`PFcl(y@Uadv_8R`7a)h%w}980kW0uTf_XTSdF@98 zG4j-dMdyqCB!D|1qaawf$RUpfc>-_bSc6WX!yDZ%*U47q2$WcCGYek&O*$6Qgav1a zFRmOH36(&|1v8rWX^Z+CwBiM;Eyo+M%`V)#B-4 zH(yF5gDy^xHbz!ZFRH^{Si>`H!;pLuz<52djd3k3l}67SG3qS;k?l z3LJfl>KWAU&o8~)@h&Tz7cS-f5-&3HTyK65F7zr1jjh z#c?`I=rgjY;xaWt*Zx&Y;1oUJ{m#6$)ybQ-K7YZUC#On>+4@AwIhF=w&cva~>RYw# zeqG9iEFjjZc$+|%K~G8VDyY>j_$ZwfoIOrSrpRFSdt~Of$Yl0{Q*=;(nb#fl&{IH! zWTT|NfQ6`KuHPX+3O`0CgK^$Dg{vNdJM^t$I?Z#ZRPZ;8Fw) zuqT&2xIgoQ*Xf2O3qN|OK0=g?H8g>BWvRurl0<}|A3`_j9Q}P8{TXY`z^4!k!MQe4 zOEA|E8y9W-ta?aiGgT~L#nKj_!h$K6Ro~v<^v#$rwt${oI z#>$F(te8;DpzlM%9NJ?pJs-lmiu8+oE?LLhdnq$9MOy#T*aBVIHr|{=66++;mvL!4 zKUg?tV?9m*)AF^X`OtaN2ac38%SLZq-SE8BAF3b-yznC@+a+Z(WW~bA`An5Pa%&?I zr4J=sw_?b(mG{s5QwQ)l%eX;R1YM(b>hb(#(q{U5hPoe9VRlM_nMb0GvMTf;tJh(4 zDkC9`mtwY3Z>~}M;kLpxk0ib@cK)d4_WGsN*O$`X-b?%Xb)*$iHYc@#N5M54)l@|a zFAGgK@&f3R0wv-MrA`Z23M?6QQ68Vw!{=vt9fpR&Srrt)os^2Re~Xs|w?5W^9!`RtpJ^&V9P1w-#wH-dNIJQhsZ zCs7*|GT(J+(wLV+2sGBvn+2`N=kRfjFTPQqV9%6c;dh%w-w|i+-D+74bACvELZEmVoSAR75s&eJ&Iu4fJKb4YyO;ap-diRCUi-pTA?tqNTtDfy7Cy8y9%kCOlXD)N zLc+8;YvPW-$0hWScoPwP7qb-uaq(P6#Lh@I(+wy`xS7U0SW@ z7c!5eVC)ObAIBJ4#-0I&73i3!-Dt|H5P&Je_CiO>vOZX(kXIxgK;_i6F*)?7U9E<5lRsb@dd=Mq7M27u)QE zz+?yk3VS<~#$*QY$Ga*Ns=@vQW|<==_mxaR5{=MaQcyexv@iT&`Z>XHkq4tjYRr&L z+v^ zna?8x4740=?e98%IbO=Gzj>|M^%Nmqyg$nIH zR?5sX zhqH1&=WSZX+qi^}*J|0pnHQ%_-x6(E^E6Y`%4Rjnck{X@9)Q9f?lB5o-dbzk)Kj*C z32YDns;QdoIZqtJ(B!j#tdRu+oX)M|HSDYoXvisG=QETV7d5gsd3<6cic2Phi3jDh zQ3gIkS}>?ewyul)u{YXZKKHJs`Ru{^{1r_jsH-u0jp8qzqij8E3jM9d$@htwK`%KB zow?Btlm)AyPN>d4tK1T zoo%9pOm+snnm>@4Hnh+Wwms6fl14epyljJ!0FNS8kzP=__1g~BdbAfNl)^J=Va!{k z$czi=iOhDaK~EThIk1KS`NXyuX3*HIor5)}{$zi7#f(}_i-yN7U(6yL3d#$|J;qtS zgf^w@pbZn)v)J9T}#H-GFF0 zJ+d?mOD4Ey`JEOxTQ+Tij1=+-4kgy)6!4pXPBrsM098P$zpE*F22>~l0z-mLXwW|q z6$B%PDAMd;Ud@Rzh=A(_g2y^7&6rY8*K=`0QSIiOYrwJyxS52ykszLo-NXSHn`1-M z(i)S5j{wv;&QA+aPZ>rh7-jh8@S`=PRVTvmcJ=`F8A6U=4&eou`KDmgsP&KFMmcti zy%h==lzaC=Sn2Kzo+9wJK{ftipWj%dE*^v@&fekV7S})R@}n%!S19sge}i-Mz=oK` zN}%VAlIRz2m^ru@VGHL>45Fz)UT}+{67%HX46Qv{nCe1Dr|6ug{)Ayizj_lG#xQtq*22LX z&$uN&80Tzq;zix4N6I@VmE}<@o^lq+|Aa6PZs@$dV2+;MEZ}l0_M;~ajf-4A{SX1Q zQ3qqZ)1F=)x&~=V&EQQTjN;eEN5jd1b$*5~WKM7f40}qUwDSOe)KM4&E#@VKEH7bt z5*hZ7;(XiFm;rVbFFl~A_7B01xzJAecx{@7&SIu@Yj$0u0U>P^xFtiMA<#T<&gaAH z9;U1L`v1-5!b`z+nU;`Ih!^t`n2b_*L8vm!h@hgYB*(}%ziK}SN(COu-%96cE?-Qm ztcGFTBLR<~K0D*07_S{XM>q#$14VFVV2u6RnMZV9BOis9Kk%J%M`b*Eu6%<1+7lwt zC-_Z=$NGFqJm{aNsK@h^^6%rKyGT?UJ zBcRBTVgPdpq7=MnFkL?f+$N+Q5a+e|WV>?+l8Kr_{CIW(9q?{V!aFX&zWe3+$J1}F zagvrsPiU#v%*h)PfQcbP_l2y`*?0y*7$0%xSpN{#lfQjl?GfRCL0YrpOo!<-F0Ggb zZEK^w_ESsf#0z|M$=Y)eGJtn|4l%`fo12mG52ul1v+&+B05U_@Ub5G`^NQl>7(vpx&8Y_Lc>=ABNh zeaq1X_CY_V3W@<wdI0fJQ%&SC`qR%Ar!OCljBte=o=qBk5B$WG^gw8NqP6pYUmUpLo>H@fO7Ru}$ z)m_ZZ``dEvlm0GVXOq%p&NGJTqES*4UGA0y%X%|H)I2IPNWn#h@fbLxEeg~C!r)DC z&2OXkcn=N4b;jyFtYNG8HaT1C>v9!RtP(eNw%(2}!xDl7O*-GCBEmnJmGcc5==k6ya#lUBhoP0q{#ny5lrk z31u@JtQ<&u8CE!o~TF*XXB84h1 z;H8xVC5L;0s||(onk$5{9@_qkUIyo|Aa$A%Jz(zGVwvQAQewvkQ$;_WW)I>kmU-E%oHR%1+rn8{TD3 zf%&rOb)T!_^34Ufv6vlh{-qGb*w_TeI%ryPWf1X#=?r4p1T&vQpEkAAQsXZ8PvOeD zv0nwtE_dUUwB{OZu%gW%t%DQ*lzKtu;HD2t+GOS$l5{@*D)e36;q_A;qm`ebe8 z6cf7}_(T(!FT>1u2XC!S%K~I?PWmiaXm2`$C)BrVg&oTrLdlS_2*Ft-Hv|G~8sUH; zFF>+-f+INsINfxzT~a@Khc-%B;m;c=Im7#Z$cFhB&Pfocgnz;13&TTRz@8Mqr?K){ za1KI3Hm#9DC*GW0vIGZRBqW9Q;x7x2tuJeDG)!cFinC)vFAb^}L6I$VC5+`6wOPh@{^3i z2RWc<%HiFK0FksqNqGtq=$brU*F|_6)Hs8Sj0OcU1b@_N;^!qu%T~ub?|~l(K!(TV zwM+D*-1wE(mFAwg*0OUWUukPInIKLcjlwe0I zYtjb1b$6lzEi=;q+GSK(U``M^Ug;L#^h|`xgB1e#i5Z9x5|W~SCbjILWj?^ zfcc&O<4(ay`@|8wRnJrL6rmBE(lyyg|7zp%rro=drr-IYRNWA0}E5X@tD5c!dIjXX17 z#hBBKz=Rw)hZA$g1#P{(G>i9vbuq;C^9jVR{oPm=Q?1|zq0<&n&#F`7``lm+2MeDJ z$Xm%IGOgVY^qm<*1k|RVr=WzPW1tx*jI|L=tjEe!)jU+m9ID|Q*O^Zb)JGVZlw z6mNV%a7MoB1zJEp-O$Nt(Jj2M!F%=Mj~4+W64bT_g*QA+h_5%IbN6<-$_?9}%lyT< z1}B1^r%4wXfdl3|m1P;aj0J|gNz7LY^GTLl=cc*|i1={~Z)))eL$zm<6IvXKKEr}f z1U^rgua0!86W3`QxnK|Yqa*M6##j|?w&uO5d8hjf8M-)sX4u8x5Gb_i4#C;C+T(mQ z{}b1PuVfND_1HDZOCL5{o~O3a(n`g$@v?J;^)GFori~m1VmO69ZHwE%_&EhITsE_1 zB-Iim^la7WE8)yOLhu~kHj>MQBKLe<^~)+;29K|NMdq@x2UB&E(tK4XrAXNIly(|f zwnDX(O|Y+6;>+&P#~fXcqY}HNh&;Mx=-SUheyb zL5Eg-^1-MVoL@(E7oPqUB<1yJS=y<80&dI*Eh8EgxV~aKcaoIgO`Gha2 zys}ZAtxu37{%LYn+U&b+A3v#^pDVLcG@Lf)Ifc}wM|{1{BIT18pNv;W!znoDq&No2 z#*huY7z&_Ov==S=#3au;p=(G`Y672ebwUKKAo^TUPjiDm&o2Jl9@(ccQPO(HpWE$8 znDbIGe;Dgew5`I(+N87KXaN>f!k4|_ZNl=(VgjS^mBi^e1D_t3)%>)0j<;*j6JhY_ zkWV(Ik1p^=OSEv0R`;Kg&-)zvoWgN}y!1eJpwsnFgyYlOp4F4@I|qDb&}p@&zUi;V zPg#V|(=+32>PUfjO4I#0iE1A21T_0+ULm-J?{2Rr`+ox9v_`^{{djwgFRam-abX$y zghu%4NoPN8XSrR8*AU(gRQbf$;cznE%2S4vzT(??nM~UFK)W++^QhNB=l#e^f6|`M zc;SkW)hrfH0YH5sbfx!8j`s)ZlP0ateSkHj+Rmc5){Q?MZhG)helTX%W@rT3TgZJrUbU|7uhVT#yS=v3HKI@;=iJav8jbTThKK9e+x63e$ zmOqn4mpiaR@G~VsOCk)nzwh*c3ywYc+*kAUvz~0|sUH3Ey=-5OCv<* zyfB8hF4-gP{5z$Z_+0Z74bu~q^R7pHzK0ifJ{!cIUyF2i9^5f-2?^RDaXYh5fNA#S zGXV)%_DxY!&OQZI#y_-}X*m#I2HeNh-0<0t4NH4g@qXjAuKoJ-!60aVEZWB5H;#Zl z52+7#;Y&~}1(r%_Z+CtkzVvfywSjC7P0xV)Q4h<;rJsuZb!Xl{Zx66^mGeDhN2QPV zOd+|z?&%WSS$(h@@2>J?+nz2za**<~Y&G!({P{{&^wfJN!0;NzyDrhEop?c&YX$LV z=8BBC<_KK`?-dw6>pupK@Fsou=?VBW5~izX>4PHaQ#GgV^BT&${q1Q_eg>~Tvh5{D zclsUwFGi+Is^k}MdU+{Bej?5^e%_KlF7cA9=b`g6CS2+8V!s>FMZofPgZSxHib>Gb za}vCKxft(3|Lnz|3;$h%ei@Nc81)+vL7(pLW&Ca8`=VHaWP8%_&({9g?$DF$eL7*< zi~3V&`Q#kv3GnF~H{idj4g@qO8Pj%M_6zN^3A=mo4iGCVXaiaLg-Fl4BmDv|{$^^? z3Etm$82XHDkba-ofpdT9Th7bhQa^v_X!?Yn=_z4<%@>f1S06tY7H@Ln)729CHi6{R zZqK{XC%Hk-e)=ZQjn{2Q8M*-CFG5@>fIef+Z`z)InM(f}&Hl9<+-y!C0g*%o^w}QJ zPHVggf{QS?)F*wr1o&H8%um<+`Mtgd^vAU0(*$VgKYg>bWX0e&Z(2Y}YrL+`->kOJ zH+3w}>jt}l^3RUzzh=(#b5!wj#KhlrD*mP)_!}WZA0LH2e{lMlZQ{Qo7e4)>>w5UJ z+w45cKetZ)`07WR*(^a`+%Et{z3=u{h21B*^GhuI%L4xIXJh(=s_9!+`RHbT@;p7s zS^CC2;a|GN-;SG~1(eex{Pc!PmDAr)!Ji@YlOE8MANiN^1%KOw>9ZUDW`OwUMZY0; zKEYnSf#~B6f2DZ%Wgg=5RsOZh*u1_ebNS~i@-qdp+57e@jrbRN@!w35`l7!LQ+}Bv z@ZY8*D7 zcl>{Yfzto_C;sOr(f}-vg|w%Tl<_7(;eQJFKZ^N$SR7III~2W)L@NnwYyuA z8%Em=xa~hJz&5%e2p)bIw*T8O;6L544G*wo3xZpQD7VxW$ksqriKL_=t61}#nU&Ma zH{LO(Gi`t0T5F%!5jXC8FSC+^q`ogtM#MS$?7h~vzTv<6%m4l>psxP?t002k?*e|G z{z&J(|Gj7RzjQMCJuLWNI&9v6d>>zozt6h={TOxiz8w0TO};Pv zg!lfqZGR6ieK*bh0Tgppy|?lFp11M`5W9Z`N|myBf8s5^*T%#1WfZc9s_FPHJtDlj z5D32;p&y@fbbOcGiN8;L3%@(o_%GdG{DY-))-rN0kMF{as`x&35#FsKh8KAM8DihJ z{<)za-e+PyYqR*fr-=A{D*wF(|GzHj_gnd7veT^Zzy{qvd zm5sOj+O)$f+iSx$-}h)=y&_z#Ej}l(2kpdX%;g!{*LLFd%l7S2?V5Z1{x}idg_ngK zuRkFAjqk!zU9ml@$m@bSKD!VC zs6@?|g%>C*d>}P(|FwDwBh=~!ycb_o?pb?cHWC!ydHPn zW8HC|C&XvZ2<9GhZSSt%(sX=}wBEPq8CYEBBQH$Z_&zTC-Oz1#LBGEyla%{A?^J32 z*VX;3+3n+2e8%$^m}m7~fQ#=1yk~{k3k2D|HAw_v56FW#Jj1cB3(@c#;=k+k++#Y= z#V|9kz2(Pi=8fHk3b zciG^c6|aQt_$)ukAa$``~*a?#35tD%6OgU#}}JHwHui4gC^fiR=tR(<2AfUzqj}i-7<% z(IDPs-FfI=!6CeVfb6SlpCP{JyWh`H;`h_Y`-qNWE1%VH;mm$Jea8g5yqo zX@8xUU#W)Sy2cCk>8NWr%k_Eo-VpNF)Rb^)yA9sY(^Ghk2M)ECS9u(t<#EsZi+ga0 zSNGXFza4E1y~h1@?BibCnWf?$t~mo=O0O}W_m%qM&TPm9=)Y}tW}3a1mRHXrYIvTZ z$7{m!U4(jgzYrfy+3cxLKzy#q9h0m~rn%m~72?M`(FZ`MrH-UK{@L z*@qIx!O))pOnA3g?CB+czBl~i`;2dR!Q0>O5MN}QWE^_}IbLH-8v60s0knI+kJ%Y1 zME3OFCZmcwn)N#OdPT_I3s*z?-S|yz`q70MvcOM#$=8+Tb|~-qO>V$iV&TDGnEbB| zf5(fz`FwBiKQrN;^LPi_t$Fo30hssF`J;H%@MBiDuG&WJ8Cx?6dunlPdhFHX zlb9i_b?@AVq7Q|$xQ>qr%T#yz&a3P91Z>=RI{CHh4Y@ysJqDf#p1&LSY3(_`<8wqD zvpI(hvn}3*ZG+k38?Qd2m!A)uL+|+PH7Vx;`8;qTTdy-t8F~LvJlR+Lv&5L5wT+AW z1p@v13HtD^A3SGy*uFQIlx!QymcYJ`bBDk8DL9^Gj(ZI=`@ThZ*(0tOokRA6_=WFt zHhb%rYr;Rif3SXE+=}nRh~u;0jXRDLcb;9r<{9C??~i7`YdZNk;TawfvhcZbzH4X* zgcP!2V^F-kYxui95q5Mr)aIWdX*F#3zPnF;MvhPFqX?VuFU&Y&x>CsFzSY`q!ZP1` zD`aFprZn#@jN>*os-+jy&R##u&W=7`hVO3gDRlSNBUgI~DO9qVGhlkZmw!*^q;vCq zhO>6?*qu)btt%zPXM>UEzUxWPl0=ow^0|LI8VuL@?Y@JZXIJLDqm7VpHqGx!;AfbX zI|08-zEoS9P0p3-dBHybx-aa*Rfo7P=)?CDobOYh#>_}l+&$yfLzL+Ih#FrE(wNtl{UCnfISnYT@1n=B=iMRfc4ipfB^7p!Gp{YY*Fju| zVZwfNjiKe{9iL$y*|Xi?zhVr}8|043L^lV?!Zhq?HtxU?vJg+RxiVv{DT~coYKq}G zn3nkOJ>ezEDB(N1yuA`A`7^)Y?bK3ccnzA*-eK(wcnz{%gJ0L6?jfvnMEFvqdc4Mv zne6T?LWZdZa^@ZSS!~ZuaZI>6PKsZ<9lpCkkoXZZqi48=aywX4?{QC6 zoL=cMU)=Tv<;wj> z@nm24r(*Vn_qz@G@GL`(?;+3M%RA5b%j?d+4@2KIrajHQm(YGsp(W2Z`&?joSZs+t z?tec%uek7Hv)y-pU+`WRxO;slUNuYE?d4t0d#i?U4If_MZ@0#4e`oi%=5o#KWXE*N zJ;5K}gAfL-!^SjR5JW|=ivd)ma4WVAPTbVvzG0kZJjTvw=M-> z#*>&h5uejf+Baoa!a8okG-UV=XQmo9nozv+I`(Ntxu5FjH?`)q<>8*=oMTe$;FZ&c zbt?Dz&3lD&zRYI4`xp1@6?Z&Xv3~neJPCV-k~bRm7~{3Uv~#0PA-d=9O5^bbee79= zAKzoJUT6McGl;G5zwZ0<68kx39bcr@=LDlodTUNwnbv)VGIXJEW9-19kyePO<9 zBs(UzcMi`B*jzyIdQ9ZHy`NFT^Xhq_Iqbu6AFey0up9g_3r5)qvv|$y_x|nak)BWE z>v{|8-ak`l=YbwFgH2|X8(x=uBMZkO?NRt)xj{?TSJ3wv1Yt1940jFUKI53zfc*pR zt0|`SVb}Hc;4PVn=bkat>^Sh;Jc#%=ghR21+u1?%(NT3ZbaU*sv=^@iUkXLm!HauV z-s=@XdLZp`U|(S7GfHI-T#8q@6nBryW}9(I%EH2F_l(_*Qt+}T)Ex2Nz_qQqG7aQ` zH-vAo=_&PDrFD6bcH6v8_Y*hMd0TuBn>k+bF+a2PFlH?w`>j0Q3Gv=T+(eh1k?})& z=r_LBXTd+rFXPY1{XO@w9O^2YdF~a7=T&|~@}LXX822-b|3&}y1^@HBYwIsuv-n+x z|E@9Xwqg4_r^rv%%wFTZu)oe^_k>7%R{hTkuV(~te9nIRf+Y#5^zh{GB;4JQz4qYT z42p~zJ0Tx;0yON@8=eumgE7bLUcg+2{GJs;+up6`ZuvALHATbhUFTd~6p=X9U&51YMu-!~6j z-HS8mxADrcZ5#K(Vq3O-_Rceszew<33Hlw8yxXwe z`?nkV@dfsMMyQ3|Am8~Uxk4Uf`_{^K0KUt1bhz;a0ooS?>2nD8%%BD%sWOv|7KDiy zyM`9`W&btY7znt-pM*vFp03XCU5DY`aL+!69{1GF&&r?gn|GQ`c#(PUt1rAb&*Gl@ zahJ2HpWOVi-l}Y7IVQUp$u5U?IJxr~H0TQpHySEGI}a9`UJAzDn)9#KPH5+xOlHht zQV8Wn-n+5o#+bfqxZywSZ#ZND!}n;yg$G&A@7=)Geu~ZWt-5yR*|~X37J9@XnYy^K zciv0tEo59LuXBwQ{yu+~nbz1(uEo^Tl>nFRKCb8@Zigt(7{YyL?kT7k89Xb0H)L!2 zJ^OC?-k!f*++v1p(t44Clv_vdXNRkaZ;A7XBW2FBxal?K!RA_Nnr**He`$6+yDT>K z_I6KFa4XhXVCVf2H|R!EKDxUJ;ixuY4Yf3;d)`6UP;ok)ONI zqZ9wKdJ|9bOlB>WJnPTNH+*LI_J;e51oX3v@tTl%meXXoJ#(uWlO zN$}BsQy8raTQX|t_l#>X6G+^h<4#z{9plf_>Ysa`+H+v136o5NM zod4cS(yYNSb=DaNQ&<#P zz$Y$pAKvTzI}BI%^OnO^` zJ`&Dyv4f94 z)0z{HIX}sQf8eWZ^v`ophn3!&Zc?2*gFP4S6Y2{)!5o~tI(OeQWL#?#c42!ixmI-V z#0Lg(vEf(wjNs4xGtE~LcG=8xjwPP(91T9hnQQf4_NiwQLiEM{xKG&fee)Hrr?12` z2QKOTK+cvJ==`%FFI`uOPYHY7qxZVAweC~=uFn$(JMSSsbI35~e@^bg{kY5hm!ACG zfJ=rP_qg`+{&oX+Patm@(>1{wb{2;H9xExB2BBs_vK5Bmdc(ZVDZ`$_!wa;pLfaSU z@x@_$;lFIdYF{{J;a_<0D@~kiYKq-pLWB5nGpI*Wd!_JC8cdiG zuegKnP=MDMcl<~(j+;Qt-_dY(voDNKtgA4wSUv1twNe>|Twl6wSl$ra#ZU%u2yXO| z@1(k@UDrX>^%*b9qy<7*SZE}jD~CO>bzSEi51Zf|0;4j%$25!1&&hdf`q3U*_kx^$ z-s6@>>iIF#T&q2V;9;10OM4E!lktspCQm5b>v%xv0grVt^}r_@_-ca{VA@Q26+~FH~8r`&%%z^2G*FoEoNB}GvDgHzb%8f#uun3c$m&M zueI7W1e#Fm16EFlJqLvSp|khaPSEWd)Rv&)HDR9#N&yX5{VX-SYv@A_>74{n*xysw zId@C!E|RVDv+r9Z2-}7!*VOx7|6&%1`JQ_NayR@bs?LZ;Gz+&RJfpyd&wTG>VruAO zL8vhLy#!(KP%M(E1Q)Icr4q3;!CN+Ogx$HB#(ndRS@>jqX~@eaGx4Ombe0CP1;5@;ShD6VKS+KWhQJM5-@WhL&T+rII6J>_d3i1$ z{n$r4;s`j`z_i5+<54N&F3JMuHtGewzj{IPm_3?Jnyx&6JYa1e^bW(*XyE;q9=Jk% zId1IR=V@WQBQRp;T+`=#PrWW`UF^STdTc$C3hOF7FSx#5qg52sqp16@epep7{f4aO zmvT6n$S42EPsrVmezb0Tsc~)(+&k3VmyRWCDT`rNgU4zQuD_9ob>b0wE4+5D&9fw7 zmb5i+rq8%1dxAgr8y{-elT(Sii3{QWqj(}a0k#!-I|j1PjCLI+nc1%K#htzLGkH$P z0(al%-T;cphcg`L#+S9{zzn>NUabGCxt7{khcr_r0a7;b!3IB52 zkcm;iy@DYLQWg+k0r7CXhGyxZ!ubrLA1n-UugCi6Uax^jkx~#U@M04%Bz*AiTO@2Z zzpu=F-)ysYyvlIYTTO1&bi9ru*2KJ11Rw@Ze~!X@DCfV#;oUjUPY$Gw1#K3j!T+(!YXM9 zcw0aHW^dVqxbB&7?R{<1i_V+hI}7jue`p`q!T<3;`QMFR`R13UX&NQ>&D(dtsj-|q zeImz4NAlqx{9*Yk|D*p2_qo9P-4*Z2u*m0JByD&foJ*R2-q#-2(^(|$6{&yUFF()E z4DcR0+bEPW>4S_PJUj`H9zKw#PoBtfv6T5@F3aT-Gwe3pRz6oUnM~E_I*^A(F@kxF zCu8}OfBH|!(cz>)5O+l{E7Z^xc7Ec;FhkavJU7Q8VKsa=zUaJ$`$|fiZQ(QHUb)Zf z<%4|+? z6HI*H4N*Xy&@xkq&z`!5fJ~opBb3ymp6g*o<7E(Nz-K{-{(CiJ_aN2>j`wPKBj>-F zZnQ$&Obdf_;A(GN#k-MnO(B$3@NgeqN&8w;v>7CdVU9!iWPwmVy#HYIjjw)DO8jmp8hqY=^G%tIN2rBB zH1g)J{}cJj@BFs>xqtI7AOEF)_up%vi{w)fykwW%26Rbl6tv^LOgHB-8vbJFheE4r~V_*t`?fY!~h*H#L(^a6t5 zT#I@g!)mou5wFq55dJg*DlHFxY*M0-N_!m3bUKxTgM$!Zel%oWE_(Hg6oP}mI~A-2 zOUS}2Z2D6U#{DzRXDj>W9yVfDW89Tcyk5h*I>^`c9&W?l**ofyaWl_R<$)K>c%x&W zwFK?m_|$I)lg_l+=&10pQm$m-pX(s;G`L}ENV|cW^*z(GOqbDn3emM$cu(SZ z^!{A&>8uaFhaM9CIs?BnShjr2YvxtBKZu{hwF2sY^(()FIjwbg)cpt`RuFH8gQyvx zs)f!k&*i`P<-aN4_}Ujo|M6e@UyY^*(@Q*VQ)o-3^De_9WO`0`Crk#vb1so!X+G*5 z-k)<;5_c5uLdQN?)cxV_{Lbg)@#Dv`S}cqZ4D!|kzcaFEKipiCGnjFJ5InyDQOn8_ znD=2L(lo6Ni2la^)6ZHB>X1A%)kMBT59s6#UGm+X>`IHe9luM#?tAANh{?m#nJ7DP z?@AUBv)vpSI9S+wRc;2?W_&OK?^}?E|5ivpCzSV{mBf+ibv6p3szx%I&g1}V=JT6!>(=e!;9x}a@7i8_ z>v4u+5>hz!Ch!!F>{-KQPmk%H{9aCa4^bLrz`6n=)bNMM3YN(4P1}XVVlK<&LRRaw z)VLZC|DJ{RmI^j1OZO5RQTU_L2 z$@?DuwuCFj^C75123SV9xU022eB9+R;h=fv#+7rtFdwyNoL^@0^iUdk=b=xWk~!^a zsqmoe{Ja!QYJk0mc;3V2eDZe)3H5G2XD~E|WVZ=+@{ng}UtVkKL#$^#KVj^1)qQ;3 zP;23}Uhx(&b1vMVCD-s~h;h*3j=l#hT%Y;=yL#7Jd&?}xfVwRbmTBL_)8PFC58+fB zgGKdNnyxUn;PZ-2Q(I_blQfGOgsVym#Ja9A?_SB*w6%QWt6!A=<^TMDDF5TX`oB2( zp-=wECC+Pl4|D3j%6upJn|mL%tFuEt(LG{=v24Pf_bZ<%GdP8=2X0{aw?Pg+_xUf5 z-g)Ofa;nw(Tk5=PlqelVgO~c=Jd8fT=RKa8kf8sv1D+N7#l7APC^}h269A2aBDd|u zm>rDkX+(t=-g}L~d5ZNB&r~02u*${Kay`wQB_V7X8xAHr4N;9QJ8hhEO+i>MbBUn-}M&+=6FrnRWC)4STervo1DR zCNoSS9XuFS1E?_pMBRJ9rND@Jje4ekdd>RBfRW9fF=S?`VVD9F&r9NB^e{7JFTM4` z^Gerg(wt=c)iCEgiw)PgzO{f3>5SmHlK4#BC4ZtJE3OvELuhLLdSI(|lCL=rc-PO- zI)-{|E`jt_c%Ab29fz}Iz%-tnJ_TOjyYe=uZny!1lDZj^S`uL&z@BcUd zYx(c~_x{S!pZL>%c8TR{_Qx~$kNputZkqU!f^)Fseway2e#d)BbCS(A_c!caG`-9| z_oXk6-oF2~9?(1WRd6>x?)59hkhw5}K%5ea;U^+i`Iuw7f@q#FKqC@z4w=XyPvDB(3csR&pbE!u=71;-<6M*=XKcfp_u0x z@ZQc2f2L=nmnt`?A$0Zy*Ixk{yM|k4Ywhz%Z-PUaivEOn`hRy(+5qM=U?eB?CCY^BtX^|@+R!w=a8<{!&<#L26=QbTnT^Km~pyF z57L+m&&Z^Quos3sL$hA3WR39GkjK5&osMBxV&MwUEL>{@StED4m%{P!%@SX^4`s3qO;~7 z1E|k5mH^=xFi3dg9BN(fv69}+UoyW^bJoo_#@~5wh2KLaD}S7_>ILas z;-Mks9vS_7h7I29llEffdwAY|`SbrA?>2HUoyu}C*T82`s%Wu@8q)e0l7))^fAh<8 z`G5Z3{u5cPSF=C&Z~R-VbxmEe{=(1bO}q3u4bQ^gO?^ZU%oMEZQ^pD9d6~S-FxO{I zC)Ib~d290K{kIVMjr3{|LABf9cU?DLA_AWWB+xnd!52}e5+AkEE6{&z;0Y*d3LI|T zz9)b9kNhObQM2_SkWp9Z9vVE@J(rTd4fEhV4fpI`m-=QYVZTn}HU5z}z?O8md%AMh zPXVqm_b10zIUG_p;q1z1Yll(rjfYO$AI#U(dL{6~v&`taRL$<~dz1__nM%46(Gyt1 zbAONF+PWBr_jUu>!BWr9hY+s@5`*#?)_b+zeZOONo8)8Im)X9)W-zz7gHhZLpgc^| zS|#1?I^63Ef9^Htyz!N0-$FPH%^KIbUTcWf>lNx~Js^p9331gbG76?c@iYoJa(L;b zTNQNI!jM>dhciMl)L=r0X-zX#|7wG5mR9;~pFG9=~^Pd^86-df^&LV`)94X}au3>+95Clv`;I{JuBP z!?l!9u8N0R*b~+ve?L5fgU{e(u4%2mF#K~aiSvR1=;o#dl62&zvZ=-ANV87apg~q_ z>L2zUZ2~=U(}SCkZ=Ub)*tlm{Gr)WAi*;()l2g$_ndiDe@_X=*;}u=wJzMr53fPZ1 zP&macBjRA(<}*p39$=l<#Q^9Mj&aiu&!rJ3?4zw$oh1#VNqFE5293ZQ3|{Lu90u$? zvxc$qeXTVNI!U$YzqrALyD@Ne1J=jA@NW3X4}4rsPM)Y{3M}q4wWAhVf(Qzn1JkEO z(^M3A#Y%qpZ~b+d&StY;{9}KH=hg#{!)BHmQXZ1(=como;mixx*&1g;9;QBX!%Q}w z5DS^S^7^arPIwcpwy~?nFzPkJ951nt!Z(cgoqUWUD@h_HG4!wZJM?V8&?nlikmDP- zWi%c$8=It?BFoIew7Y%6h!w$hx^G>V{ z$Zo8ii!qr~*l8#?zVto+OjvZ@*Dfx*pZknHe=qL$=Pl4vEh)TV3?XG0^{K({Yx2Oq z(vKSiv1y&Kl?%|#M}wRKVW>9@Ph54q)=Ps_544aR!oM>?(G-G*>CIa=N*9N64tnf! z;wqWL6%*V)a_@^;rE#)X)~`K_8CPAmvRE!;nXc7R*6S7O_Zsm6#hvPm4vf~THJ4UD z?$H#80ekIxi>Rg!nvbOin`FV2FfN^6iHC`wrJHxi$U*+Z?J>*l_y@l31pjrv-_2j% z1AS54m{Lea=SlSNOSKXX)|HCaK-#GR&xfMRr1NyHb8`sUT@ghK|HSWPmdONr$9v$A z2YU)hj#f?3gIDjF`B_j|NZUJD3tgz)FxTjx`stpMW?fiD=y^kErTO<30K}p6&z!fz z^W-~zcHw^I+$M(6LqDW|Pg>?1*r%sim)4{1p=XtbiMS8&k0BrI8aU{+nC^Lgxd{f> zAZsjMy7xiVHJ+eW4l*MACh(cg!cu2ClGxN5kkpRtO^`({&d=m;{y+XtGMUU~Kl5|H zNXfNBEzYZeK!wbq)t$0xr>#5QtV zeTp6M-4^RB9KXtY)ct)D zm;Y3dZUiIjQ?H>|Ff@gG1EKSxxcJ#QG3baTLz2H;&AeBki7^agHa!UCs8VBL4{D&T z38HheR1U=JIg52b?d&=fV8gUOP=N5(VW*Yneiy{4En(N6}d()o!&0Z?UunCl;liu~82TAIK zc%rUic5tMJR{*kx2TaFf4SIsr>mZ_PiHq4-??cf*^mU8HT>kDa|4sSXpZmo_ymHPN z6wZ-yqZ{oxac^Y4u<=#dU>h?1r1=lNfTLRg)M$7|sn=`i5J?Ef*Q0oM}ZPz+r0=`&WXulU1=W=lCwgzU-=`JO1n!S5-noIK6)B`cl;18RO zO2dSrT_qqg;Eb(hB;Ig{`ww1iAmp; zrip%=z-d;wsFHRDa}U;E;y@mHY7`l#!k$3Sc^Y=sg?)O9+abTphx9u)pwi6POgIHr z4i$}~+%}ma(+drp94vOYpLw-)@!x6pW*hnQ8BvY2bKBFwiwr(hfMox{7>m z9{xH%4N(s*;@UZ0mHXpzy>w1S)j9xsK!m>+(34ucT%djiepfT}qm0HAEhIXDdma#a zHK7K6(Wy~IX(?6|$D?MmiPWp5+&I3)ITUDJDVX@5>PmR#Tpz?5H_y_{RriJKI&APr zTq~UaoEg{iv-5mR!Jjr&UOFd}z7sb)*Yc1LJ6XvZswCKCGASJQh3xL%uK6T0=Vvt< zgtaA-kb|>hTxyKZIInjQVn(88)gwDWzj5L1cY|LF)J{p2n^H2BWZ225^0Sj6cMdoS z5_UFJW5@y`?g^CM2TSwfw)oDFDKIn|hel`E)WdVsvMBLwbF|L3?3Md{2zsqM4)->cWmin zsV)jL^)&=@*bcTT_2(SPLw=V-yZi90gqdU_4%vQNW|Qv4opbiAPiCQ^5mM_F(VrT0zMAC^8I&p!9RFXDmI~LA{NE_5+3*jCm+3 zhMbkJab$w)W-v%@llbVksK&TWbj~Sey@hJhTcVa~kh-kgQ;k{?eweTdszH9IQOAw> zyDw-Uau4W7%f$VFz6;QK@+-gccjex_56ph{=YF2lT%!Mu^U|R2w{@R9P2BVoCsT@r zY-V^rK6-dx30~JLeO}_w78~f{1j5rMrnC_w3JDw2cdRAIyvP5@l;~$f_qD|FH1C8d zH7!|i<)4LzXYx3>V4O{6gHQFSlX~wJ%+5W}#L6}OdVzi_=oPgAj0k`YqhR5Wn)Mm# z?=jqJJl5;Rt=sSyoKcN+d75){&K1eeGVF6M=K6O0^$zF4p7|cjOu^2%qc?IZq&2PM z(A)1}U+&lWz0xv!;AkHB>D^G;{(R=SC{;4Etzhk+oBewOI0?YTvlZ4F269dK=cXht z_BWiJow2jkCfWDAJD5NiKCTongluKv8h&d8Zvs?&AX~v-I0iBp%MkD$UOL>u*QtQL z77e_@G~~v^?*-Ntc(0&IaCkI7ir;gr_wz_bBTQzTtjEoJFmlcWCTk%DlkdO|x(Wj@ zg}#0FQ0z9LALtmqd4-J{4P9J~1#juWlKN*oYGz(r9+c1fBmT)a_^E+f*mRp!jkDJl z{2Kl%ziJqRg*d~%PcG{T0M^j&d2O&zti7L+2}iljx(esK8@d|85O8rL z)SOg>)js^rgE8Tv^UwFS^JC$hPR6L_8n^WmGycXr{Qazu>4N1k$U5g^2GL+%(L*ln zQx*QA&BW@?^d21>UG4>htSI)xuZ8ncHRC}!1e9!t7XOG1Je(e_Ii%e09rGP+YuNZ~ z*k5WZX*BhyCd_*69S!Kj+Y~m@bzeAbm=j{bY`$jiNm`fYPknNWP`L&w*PmmZ{K-u@ z1lJ|^4Q~7n9aoZ`^5pR&dG*!r$ZvoCpO-)J<3D}$i@)?I7#|DFuVbH(-eB-}-*kui zw5QDXl9s4{erG=y=cgLu-W{)cck;>VztKR~woz}7 zdSe;E(@rXaip?t2&mPz76&BG48m%CdX3QmwW`1#j=SwLM$3sx(a_%-kZhmh39kieF zBl&LLlXA>F+;rjd=6zU~?x!4ej%aoyPYBx@<@4TjN^9QA+N4M4UCLknJ6}O*t7>=_Zqg zmBf@xF`4ra=&Y->??as)%4#@fJRCwu1ElEFxqUK&elRXI0jwz@cJ<51KpJU}jQSq^ zkOX~c+02>~Da>@khJ-Y;A^2MpWltB;z)OLT3&OF>LXpLDqoOcIlaUmA-5`kdYE*n} z+skO2;>tr)Ho;YTQ04=sM)UdTTn!t;BWyl{_dz4#nN8#2596vK=(?`GhYlFVfqZmb z9?Xksh(4XZhe`B>nnurySm0p2TAOvMJq4xqQEcwz;!NVbmfJ4!Jp9vpnNP41;drZQJwX=c7A!G>-d^tA2#7Xc&KdV&a->>^WL;%e&imYnxao~ z2Bj3ed1^RDtpNj_9305y$%#BXc_Q=0Tn-No<<{K~N;w+i{W0DtWHgxqv$Y=3tWiHe zVB;+|riYkM;NpP$l^h%%;h-bbgmgwh03FJI~Cr=XRCw^0tih0ueRxZau`}(u8RaedFii^a} zCO&{6gxSPSeqR0K+VITm-wN*C5Z+qnhio;prfprn8v%wnhS{8q?o@^Gct z3KKrXq@Vsg?ry_#l4&Ioq`(+zOJX&%)nhT-g5kfIFT#9&sR38u=43K0K>7ltX1_h& zW9IdEH)UMRjakK5c>PIu_@4>@SF6F_>i#Xj?b5};YnWPK>KNJrK~||DHBIeTW3?6I z0r%2Fg}AtBZiN8<(i+Lah0l7&fdWIln&C7=0^4C=S|@Ha5OR(4X=Lm5TJN!Df?+`x ze3PIC7GvqjL8lE;_W13o!%qbffYUI!ghC!wCb(o=Dg0HaJ9BJPFki=e7wHht9?Ur( z(!OCk{M8)N^RzbC%$2h<4d|Z!yYSz;Womz=Ju96n$(R(Lt9;(puW0ydh^G+CdCe>F zAO)7vk-e8L?~N7){)TIOBke#omlrXURG-tJpi9Jcb$+GiB~ww8kNQ4F2>f-1t0XvkHzkXG^a0?#anhx&QVX z8a^e8oXrlv^C181H$Nj^{?ZqUpZv+6J^Z;(f4XUFSz~XlAqBASr8ILD}&(TL5$B<_Q=TIc? zacpzW8{)=+=-OBAJKmM^h`e2Qjoka-M`bd4DiL)sC13jROxDYlRMP_m_w?kEZZbVM zlMe04X=+z9G4r}2d19WUyk8lYgZm75+&f;aW1=4K_gRzTC!1^(uXopLszFF$ zk5BEYEoS+f0zGa&&+_ZaUg65ktLJRh60Vk^dtY;3d_cJ=ggmrmCty8vx{Y85Q-3B7 zzORzO!p)2)Zpym||Dv`D;S2sIUL=i#rP!^QgyQeM5g zabY~bXUXt~>(q6+*7`Y_j%jLoB}{ua=OXpH%e$=cG0qZH7XZZ;*W-K3gy&Sv4^5b z^l`)GB*=6X**+=UgX5*)za8XyZZf9Cd1A&-4S*dD>{40z*zY_ub7OxgAX5O^85=`) zK5XkNaan^n+Z$La?4=vv;3q>dc8_NzZm5ZtcJ&_AX+PGOf-;$1X@(ioDmChUv#;-| zMw-kSbrlR+=tec7>xzvN`|Me>YO>hqCVEg3tC;Yo5sOo)w7KAI%T-=lAD@+0VmdQ% zo}9yb%6s<-?Pfl|kT>6Y17SOs4}S0?a(sMKCX=aLUY^Tu{Kl`z>#x5$`lUbir$=}1 zytE)ilGnIps~@rUHr37EjAI4-Lpjd^%_E{vJtYkU`tH5(4#iR9GXy?;Cv-K25OotZ z!A-DRaJwGVTjM55VjdqJPCPeeUL9>_F@-ASOwQw9VW2UAqw2bc2UMfmvRtj@^;f?o zX9$1#zyvjZ_`@HQGMJ`S56i@Q=6%S#b zmGQeU?}pVVuE`G)=AHA7eRb}?9E9%0?QH(0uVPyRDFd~|VE$JopXoP#!CBH5GW`v? zlmwP))~s?2d9O+$%ma5fc;k+N4Idt2;v&7)?DjklJ72;z0U#M6uAG03sj{XaOm|Bf z*tI!IiOD(paCYn9^JI`3{BEj+8;FK@Wu_u7FwGGQiy`nmfI}W`IW8RTmZ@fcy@y}D zUdeJfhu|;pxzK=Pu+b1MXXkqV#*Lfd*3H{xSt=cR5RXQq2IVy(5Fz< z(9go(K6~}^5X`N}# zg-gru$AcD!w2jqJua~k0P7Qwdu0R?@iDW3%`Aa8@6~!WZPm&&oS@7G{@e>%q>lCZ# z!i&!R6#i1}v;*yiD0xY*Oah3^}#(NUVK^0U|k7Y!mWrF6cV zQy9jH%Y0XBsf>#nWW8ZsdLZ^blPrisX3#^eDG1_jy_tbZ(p=bJlrT`uxXDI0NiqGY zxusxn%+(ZQc2Lr(*OT-&)WQ8>+{!KOCv0S?vv3T1zu9NCDTRXKr1PQvgZVU{M_K@G z^!VN|o4nrw`BLF~1zHBjT=S@IbPZ|B&`?9?^JMD8o}si_E~Np-7mK-^ot?@jKJg>e zJ3?+MBLv!8Z@n&m@9+LC`H7$Wxr0CY>7Q@$>Y5VH_!xTElgCA^AB^;ugX2K-y?9ENo z7o3cF_^SZ~3t1XOZa8kTgA8Lvjuakhy2)&M6DJ3p>R``G=Ji^`e?Gs22pZj^0>Ctd z#bO~#enO}O&VTNGFO0h8b8LB3X6#l$A{p=mMpL0lDO593fOLI>b zdqbJZ0}Y)xh;rb~z)9yvn_;!9#dLNNH?5`hMP?Lb#^>}m@ZI-GXD2@Pu9=OP)m#Si ziwQapNYz4T#vL=Ch)Jvd-Bf7-noNt@Np(o(9l3dtt1$~iaXj77QvZYHBvX!?fQk8B zSi7I0*lXY$=ctaTVZYg|U|^%Sf<3~dBST7!6z7BA`kuCpy|4oVw`aJc?w*&v8+FTM zzMMBdd-x5{*SYoX6InJYuKdoR?fO=eg9UIxmlx;2tyKQcrZbr!tV9;_+n@UldGO%v z>M#6-zg&$I84 zM~|QAp7-wFmGyclpZT?4m4E*~_$wonOs606g*_*K9=y5O3@$wzYIFV)S;#r|?h`06 zu%QMEgAasctlc_w~GrMsQP=Z!Ng|^^})j`&%~rRpRpt#Px`{866Z1OHP>tMNOA8gGu_hvsD)&| z>YuJj%_7&bY7F-}{~pun$%`D zeQ+l=-MC%vl^_Y(EF4n?a?{otaw~-GI*}JItyW_V;niv>%jFWE3w+*|Tet3%vl(S< zxt7AF>D0V<76cbP@EQK?mGHMf^k76GA#=wTjn`$Z!e1`t2>)|AKR=Zl=Qri+yYBH>@GCD+He^;3OY8?_ zw%Bjfa{d;^)M`i};%3h2%i-aXJbd`THBZA4=x&W`rJOH8(o#VXdF##B#dc@CsMmOzg{ZAN)T(q`uRvvt^s zJ#(Y^7jr1FdSY~W0@TsTfRm3MYom)y%fNz#k=&7utM+6QZ8}$ zcrwKvYZ;BFgAb{nhm98}-fPgBujPIoQnqC|K)nR4FVWcaYhU>-VD$4c9-*9G+5+?8 z8rqifDh%04{U$yew;c!SD(PCW6w998XPP1;I@g11SMvtEwFHK!*_%rMo%yho0 zs)qoq`5XdSEfLDgh1|S(E8r>%GeR1b*2(igKm;{O19ZzAZbKu&n?ItwLku?fwv!@*(9aT9v%-#Mszt=6os)u7L> z4hviMpy^8YEv;cy0spZFPoADdv9B-$fBwE+#GoH>sm?SBvCMklXCBrW=e74}BPxJ@ zL9Zbrn!3Z>OPVIj?41ui*7k}+v1J~1o~;ReJMhA&)}p*%Jmjc|lbWg3rge7C?~uNm z!NfA1kqoWen6)M3=Gnl_Vq1%?@7=jEY4|(8)sT{&eZEP1Zd5@d&wV7}i?QfA)NImZ zg+04$Kh~P`pSY4{5OXiU#dq6uuJx2ithF=wSTk$WJ#lqrvPt9gDV;U5E`>P0cMN}8 z3f?i2Sq&AkCAjwaufX?`&@c!ZtD$Pv(y1Y3eyz%-EPyYBX{mDsL7F`diq9UDC;_iSM^-Uv0 zAtgevWMep|W0BCrWIB-*yhUOM?v3DqrM`T2ejzoy=jE4PQmij8&gB36pZ;&<|L}kP zTT^(E_tY)F!}<>Rw75^0BGxf~=`k#S2z>Xeq%E~V>b^>WI26h{i=906AN^MPZv+-S zpf%`Rg!#R#(9VNYu0D%9$3_qO#?62G;da<>l^im&as$XO4W4A!oBl%v{O~Mfdvcxl z(AA*b%vcSEpuLL`ghs;pRn*`ixx!(nN%FbkG==E3XMt=q-H!Hm6Wrt<(|yA_@qdMWt)>OA~;CCAOc zE?Nqu*0Yv4XFk8sYXPF)dFd7nZ@@P?3DqGh4fZ(9ewSm8gA&+36>Oao4Ztp|cjxNO z2o(3ewD0L+A6tA0!+Y(3F~8eA6(Qy@s;q~^g%r&DIOuxN@Fzr=dAVM-VSafIoR*4Z ziKb?=gK&J~CcRd<;bAJtpa(v2pF;Ia{|c-H+@i_!co)ttJ%htX6?21iqc^Q*+-H5{ zu|t)N*27>&i^kPq^tz7+FAYz*A2<3=#vaq{ECAFXy%v5(GpOP0WU%CZs)?_+{RZ1& z6PO!s%1THU5%Zf~6Z_DfW#cRS&3ec)?X}n(MeZhpXZUjtNe<%?-U-t3QSdOW8?^?` zyn?}%a-}#gBL#+|@W6KC$gcQCQ(fITK;;#^&as!uK^psBsO=E;t@BGqto+{Cyx?7rSZo0+6w$`>+1bVDcGZLUK;+m82=#)xUPeP;{dzb z&DGc^ju?cV2Uc)xB#{n%IKRmQ^vbX(R?9Y=o<5d245+DBiYE=dsn`0>(ecfYax!tS zPkUk?h`YeK^!9QTq-dIm8UoA;<)9^qoE`Pf(H{IYDn;XP;&HBPyjPTF3Tnje2Kqnc zKh3#tBQ`Ar52nDY*q!91zGwGJ+Xc#( zXg2jWXg3TYn>4MwBc}=v^|;CT$S=yY@sGVefrn&*f4X#-p)JCKJ`* z6ra>-!Qwom`B`I=2an#8SHATX`Jo^CDc0A;%?uFCct^g`YP@&v zD?CHoTX~Nd2fV{$;a`n<&8?R{AeWDNsZk%jpkexwpQt+u&Ii~G^0TLp<=)F5lr`Ql zK4BBA(UIa{j;M?Hj*H$kos{sSv8?f3a1g@4fO3 zt!)hPaue#eg(t_+1US`l?*ktl`jspW$zS%ZyaUHhP9DpnNAIY=)$sqiUQJ*Az(=OH zZ{O}PvYN36EF*cIberl^t;CJ)t07e5R>T@ngM}QwQ!Lt0YfD*V@BrNj>OshPsBY!1 z6G4H=?hR$cD>PTih+Sd#@_ z0EI9Cp=YxLnH@}MKDs+a*JbLm>eFZKx+Dp8AuLZTU}B>)_@V9+;1 zAMJ3p$*eV;2ksT758lstm-y`g8E9PenzPdj4gdM&IWVCKMew=O&(U}+hlj^7IA(|P zz}oZ~@m!iT&SI<=C#UByvZbCm8jZuj!C^U>j1dxU>c+7?WP0Amr4-c4$6z#%W;&{S z*Qkb}2U94r;CEfB!RUO9=hTvM+o=LW(l&g4%JZa`WO8a!z1>fI@SxVqL%#l4c_gIm+moI4)qh;T3XyJt1&L~4NW;8UXPa!W`~OX zY&wzSqa%4tpa5=<+KS;^zKZi8e&?3VpIs7)&uakb_%y<>2NGxrv%C=a*VTg+aFUip@zuvymQq*BEY) z)w-4dtmqjUd*eAIUrDO^1Y-N^`@i^ z*IuRf&#%%KKRFu1BBpdRC?7Qr( zI_pUX7dPQEyz55qMq(lF2AS6GnxY<=aPT3f6*N~2h+G@6PA^m6i9{LsxxboQ3Q`rB zhaCgT1lN0b@>y@;?+#n4NsJ->l?F78SbV>+hU_Njxu=DH3e|q=(*3nL)YNMmxL;W4 zqLy~JEI@gGGAaXfHw<5myi`MtsrNL`n9Q94&<-J_^Q0f8e>AgBFAsgz*ZJlB4B@Wp zU<5^Hsx9K+&Iuz5O7~>#NClc#>Y;6IpJ+j;H7O?wGo-{XKTdSEJV03j@vnN|zr?lB z&!$=H;tr-$V7T>qEj7$$Hk+YXGv^d&>Lt;7M@xZwTwb2ba$%-l!e!?33pqZ%8SdP@ zSt-ssb2X}dpN=fe-KYu+oFdb$hm~@b-^thB0yrQ|%seWsQ_tx=E7JW)!!o0c$>>wq zS040f#{A8F+GMJ-!IB_!>ARb6Z@w>SznFPTv}a+MgXwM1QZn!Kn01A6*v>+#vLGsq z8VoHBV-EovF9RNv(w-^1-n>%M*ZHNBEAaN<@CeIW*ZWJ)M5lie|1B+q#SWWJAVy*Jbmz%^s5U5?MfDlrS7wa zc2=vkV#VLh>`J{hr-obK9xA zS($3cKY4*Gaab32Trn9~+_W1udn<6-Uv^)E&@m&xNyy@cRl4{GV`cCqe8Z-8-J|n! zIjqVAWAZnss0nLjCfK-a1ez%}DK@}iNrNK|LAQAaI&}sZ!Jpx5rh<3VW zJM=eK-<$dLDe06@of}4Hq0A6X;jA^5YV_WZ1!%7!v6)!hh)9EBWJbYYC=IiX0q86` z6;78h=)>Tl`kcQD4=JbT+&W4rc)`(_4&Q{9=I>u$KnI@?@dmzgS#q zZ3T*0ttZ+q`@41Ps3ecnyPf`VkBm~VXGW%>ow79_dJh=b*^XSqH?To z@8iAF>cjg5-&6Cquv=Zz z>xcZnlXB!n?VPKa1Xwhhj4|(}hFzyxk8*f?LynJcfn$v<)(d&?&Rg>G2RCClm zW^P(W!NjJ}kmyVI>w3LHvkX=8-9?8Y%3vPS;=Rf2Ko-j-__vUQqZ_L8$4{Qf7_}YY znd!^$r6-Y-8iwMYSJ;She({-S&SriLs(uq%t@9_%R`UpbQ8VReiuING9uO>))KNp6 zz0&iv@1g=RXEHjP$!s)JJW2r2hPjfk9F;PeOl35l!f2Tjoh;_(>SwH>H%L?`T1;2DMs?lPK^C1}IX37KJfBN(>!hWS5 zlGxv2X6%tOx_|!-`Hj#1qv)TNA4?kD{e8{JpQAV5x%K?e~sCB;p~ZH4%r z_#DhH5A`9~<7Vh=%0;ym{MjoTmh1`st-o;9*oGdS6aHb>Ofoa$P}Kx1^ESaF8^bSr z5AP?E%H1Ny_ zHkdmrRZI{s2Gg}3{>rYX_2=t*4}aUQw*-mysn2>_lgzl_Gjzv$Os&4N_bv2WH3uU+ zgW38lozN-#btXyNahpFhNjFdp4X#&sG*IKPcbm@K3I7}yvb`u9U~KLYW9gk*x83JwcYP z8yO?;3$3jm-ld#-=Y3!>ca{sf4}SOD>*(bhaa4p*WBxiU8aDLD@2llpPESwd==i3L zrw5o#sopT3U&!I%k*=X{P$TYh)E|3{?sdjb&W&L>9uLM{(i=z|z8gC^m`%a6ksKc# zfolhH`{qrAZG+$(S-53nBCnHXQIbANo<}}$K7H%i;Xaz4E9c6MSLb}N@Fr0T=wYEc zP4359+SWX-#L(3T=m+qNcD=;AQ9nsm&9?R0%(ca@dMPK^t13q_JGu?;okI8Y6X$Q@ zsMmp-mG=YpM^+-2sm_9FffsdNp`H?p=)RgE@EU%Z%nqfgYh{z-(r@&aIF0h<{NhxW zi%Z9&Fki;zMUi|s$is(kD^%QrehYk=KUza!nPxq3?y8%e(3hR?-3{jK_h8l}ai2`v z4TJwvKzvO|qU`NiS`+u{`pRKjC5G$Yx$e=fY2+7;HW?$2BuJf-%nVQaBRS)Sj9ZSA zNE;03;SBL$x9=34e%J8t?K2r+IwyFr@RNb4B|)gb5`-&t*eSGAICmU=%)s*R+^p09 z^AKZbLJ)kfnv@JEmh1taU)||%E`4%s77KUEX`uV%zj2h!apETnp z0xZ-->v|oV?iz^IVra)B^dpuU6PR8>C3jH|;}|4ll$+(Qb^bR(HC_29ut zj!NSn9G>DUDe!_GjR;XP#Tqu(f~Fd_YZPZ-=0vcf{ixH$sJQ4&YLUzz8U! zKr;;Q<#MiC8;@o(yLq7exV$(+KrZFp2R;OimZ}dAE^;{plR!~7Hv7qRChf`-3k%81 zHG>U}6VUYvVTr{{hE+9&K5-Aafb&CxDDj<` zca3RX^CEdhwFxhLFV;A(!@@qAuA}Rn8&+9btB-WGT%0_TlSlXELm&GDw8kxsENET* zjU;Puc+;H9Xnct0rOb||7!Z9Hb;N;JgEK?9v=9cEBc=N9KxTUy)#^~ zPUqV>RtH0w7X!~Oo~^+ij4OO!!@t5ZB-0`AF|-T=FdVK)Z=HWH=60IVT;eM&x|N(W6+FqBI&m?s@d6D-Q>`Y zRSkYYo^a1}KbdFizLCyI`qvt9zx3=trf!B-*paPc!GMakMQ<@LYaUVbeY>oRZh3StIYyR_CmwRG;mj;gI#DabyRoz^Rq3@1e$1R2 zE@=wU03V});~O`%{>P6W>b<-7J|H)5-Bpb|eexJ<%+ytvt!0A-EDmRQx8(ZrQV;8R z7?CV0`Z{_?;DA@7@dWSUg}7FnOEkX5dnGk#yGBwqAK2%oC`n}HroAU1I>8V`K=Ky>nKk%tPOnE6T z5jY~2MQ20xqkww7ls8|0RqlP@WAbwNs9c_1vc`hvD#xO6yZ7&?1)EuI9@qi+iubB@ zH(t+rUCS8e-r->}E;Z~qSOz#}OKqXwg6E{%O1h-)Ef$yRsApwKUwuw7dz*(nkYM|G#T!Dc8U z-*fkh`f2C7%|>%3Hq#?OHT(f`+okYdYZ&oTpDhe`_84=6_L(NE$vZ94o+GS>^=huR zPpjd_Pxfa0b$0cBK~(&-Hlq%pYfH?f)ZnFyr{rT1>Q1=$yRJ52%04V`Dc-5-1nsFH zcexO~4-KT&;2rkS;70fZAJ)sW3)ZB#v7c6rVl=LXK<5APit>W@5}vGuhQW8z+$tQ( z`*m$blI6Y%Jk^{6mAWt7WE@-NDeqGwZaPX8?&p`T=XV-g(mQ+gkW>$FQw+I&G&iDo zKyd7mmn12<_lG{?LThzzg?iFXs>j126bI^KK#iYB`;Z*-42rD%4nbmLp6JXNG^$ zy;23WG32MS16eE<2tdj)4fsqwHwI5oJlwa5IV1yK)T(cuDUJgJykYw4;GuOi%8+mB zB|d98n2uz9apL}Dp1}fu^pGox-D)oHy!~Aq+DISTP0(aoNqXqLK3YvQ&sR<*zVe+G z=hdi}0ZptzQAs(SXpMZi8qI)9OneasEoE)=tPS(a3t6p}G6C*1LV9%~GCg{=uH}dT zlVqh7o%<_l)4EgkCcmvxo~otCt9eEN4*ups)8e9J()@6L(m%gfoR4L)I)<)(_&3x`)Cj)3>*~R=TMb!y@fJ` zlk%dltTh6A*mFPSW1+aHk*1K9MCM{JuMpf2Q>Ny9#taCa%K=)jWg*9~2FMhC7zVgW z_Rs|nNQ6vp;jUrmnDFer*f2r^-q&eky@gAo221^Jq1|Y$ zG_D@t^E+qMI(BL>V%!~S)bMY-7ORK*@EtX39Nbi_kIz`M_K~!HMN3<{p@u!slhINe z=~KhL+vr5`auZBjy8m*o0_)4lj8UVp_dZe*^q%w~lRPy`U!dPa4`qJhUZRG+z%@KO zxtQvpq%)X+5A)(t2sSA+m)AJx0mje6HSr zPxMaT3!&0?RoVDmBFu{r4=QlE(@uIuF!qFO0^9^^dhDUSYuz{l&kDzjnki16K9P&F zGcBM*Bg#80V8DK}Gq2hR5hcrBg7WdLdIo_tu-<0XJBKwL)C z1Mt1Zcf1!sa16SyTlGTrPv2nuq|2js?#nqa_s9u}BW@LXChJ!Im06Ai<6Y7SYdtri3){C$53_<=0Go(+2t>#*RDApxMhF|-R6vFb*@qaN^^pCK8EgRXzv zk0$5?2=MfnTs8UHV3?&2Dmrm3{G;tvj%;pjUDjW3q#;L@Je{{6H-@QadWNYn;71BO z4%`CI?qMe>P(T?=G~`1^Q|WmM$mj_k@-Xml-wsd@d<6xr&S?<94A~%K@S;eQbQ8Azx%X<>TA-s&hW>Inm-*#6 zaF_#!mF~s5N{WVN+WC6orr22s#ZB+lf4$al*Pgv^H36+5dW~zG!D}Vc$xL++ckos2 z1;ZBh>%Tgk1P zxA18-uR8|18olNhIZO+Nt)7Lb)pfL{k!hKG4fyJJ%VOmq?^Q~$ zWD0T1BZhSCJ&4o=f(eJDp)kO);{5DFE@^NOKEdOQ6_h!f9mwI)jc{~yOuwao2$t?z z81XFU!bW_<-@bcU_~*5z+BpQp3w15dm#FDXjgZgA<8e4RI1D##+~jf+Us(XL$27d) zfutsYV7P$L11j z`1%=?%lb@n37?IVsP&-hhO4zf7dWPb$W8Y|>!uH$euo&>&hZWrd4#sMp6ZY#tWQw7?P;($YMz!o+LeHwfX-qG1>AJz-^C$BYNn~@m{N#hR9J=>Py zTWKxf|EU(5xHi#&-nlK*Z(i_jjw$P zvmHY#B)dTO2eMpWGAPPl_=P`7-S=B#ruo2qS`Tmj?i2sr>{FdI{ZykGFKZ*+Ekx9t zSTmn@mRZHOzVVgvZ~v|TyS)AOeT3jlKKikbhoAY`U#R}npZW8wr3qQMXiwCBzcE`1 znPJ6tNTtr`uQfO5WinFl=+!GWvIuMX2zmh4%>*8UwFUO*%Mr;KD(+La@Lkh2y@l(0 zu1QjgomrZCk)>JzZz(S?m*MsAy@oJ9lhJglyrem6&;StS766^i4rMW)W8Rf|FdO1p z7D$bf%lIIsMQ4EkUtzydI`A=ejhU!3kX9K9xaulGla*pRh0%>r_#tXhuH&jSD1vID z!r-XM2~rW%u$d8K^J(f^1wVg>Hwp>-Lx=at` zIgjCdrElyZ{+ixnNOP+IU&nejwx)i=R-Oja4mFf?UnOz#Z;T*C^E&7Mth!!d?_aB!sN#{G^CkHeMVmS+S(Uih<~yO2o=k{I(U zPEIc5>61qg%84wOb2a`E7Hg1Sw z(aj$0pZliIz$YP*X2kc%Hw}Lkwm+t`U?q@!4li|GQH)lfOx_DUyr_cHuqqC zY6u8@4Ws1(7e4X*s62EgZ47?cFWuape;U8yvRE=%TQ7JP%T3}$6Tj*J>pFZ%`?Vg-7U&Jk#spGexN5xElh4TgQZn0+@XsMQ!* zr(JEc)*3ie?HdH(zIwqCX`&4I66RS}%ogJYv9q3+-gB+BbxcqVG9(R+1{@qX|3ttJb9YJ&Xa`Mfv_az+D~0ai-+brC;>+-ahvZ`T3;)f(G=lcK^sbtx>pq>K{#V*P z>W($j%7WDwTG9%%se<+q;XA}I!iS=_z&p)ogq>TYLVJ0R=szi|Bt`hIDvUCRvkAQGj*M~0<@vb{L=D8W$hkj!&;$3e+`4@ap*vDu(bx_S z4pj@w$*V@U4{&d@?;maN%t9 zT43>L1pjM-{?>g+SdtvIHn`D*QV)+2z@pW4b15TuSAhfcFyK^IZT?3yyJAL{X-aDj91EXA+T1Q={;qBwU6* zhoGccEEaNpb}FZ*Pvq?MMCKqLg~l{20q&%pDsVKi(P#p^M=*tHioq?PPeE`c`!fLS zKoh@#hW+uc4^(j!Bfz`7Txne=Cr{+$>0?+IlM!-ytc*LUUU_+|l4sL*II z^wMBFp+6JESQuCwO5`UwRT)t&{>Tk$NdG}|tUF?05xgS=eb{X^U^qAf=jq-dTcE~k z=ZD4uy67w~R8_3KX*MCwFw@Z0&@*NAy&bFvVY908FvEKcjs_vraGx6QqZtOa242_b zUYh_-AsRjS(|5IK2fi?d`9wp}N{PK#C&QZf&*zs2feXxkhI&fl!FJ%-fHNXw&-KoF z9o5Vi%en4#ad9E@#ia!z!YMN zAf&(tA88!?bj_&;y!pnP;f>c{fyq@e1@URV9Tqlti8z()5m*xg^_J3tJo!v#bQ*s1 zQ=hx`sX2J%HcyC0Yt#$Qna(wu_(N|Z{@i@MUT9qf7Ce3w<@3Mw8*<~uo$w34^e0Nd z+7}xmXa9k9^uDJWFGH%eI(t8pIG_f~fqJDc==<29FKuL)kyOxLMfuDQW(P=_UaG?b zDGm;Mqh4&0GsGRiM|yTVE0{sE1{(*SkZLgNu)wAbiYZ1T1XPvr$sE2%(c}?>k@V)@ z(a|x&dLnD^MJdYwWaGWs14G%pmd1|5+qHsAQ)`lb&@^XEVYJbLmL!>U_MDol;U#4W z{-Fu#FL@7n`sDE=;zRbt%IDwt+>_6f8>Dcw+|kU|phu)28_h4GakXjt@RhHBQBKcJ zbn*1y5ZZWx0BmKkf=4V?T0_BxvFT(=Ett{T9DA7^&ZMo;kA`m0e(9m)%@Ur~5ifFh z!Z2+y&n5P&G_3hP_bNCPHigN5?JJ*`Z+-m>(C3-1pG_xY&-_$>r461{>FbQ zAN_$(RzLYOzbMDYHyUP^b2XH%C!SFygUtrX$Taziq3MCY7l z%Gx!sv+(C&v|c5M=m2}iiwA3vhXlI|!G9~J&J(*D4crC}&-Z9J4J#f$d|UqEul%yydFkcwLqGh7iew|MXGwar8QFe3lVeN4 z&#>~}MOH5VSJdVmaa2O__?(Sn*VV|`@r@fw&aUo?3NGFI){v-D0wY9EWpDc;uvIGB zUTlq~r{|aQMJ!ckIENwIf~${DPUQBDWBAxc8|;fU{PW-ZHTmhE`^E61 zKlY>Qvxy_5$e?}HLQf<=**|g6=E;%Hesf0kx|6RV{J-|iFUgy4y`fxR&QCP7BWeaU zr9iLLm+F`U!_@*%);c``4(qwBnx%52Yf0*jAlPS(B3B^ZXp94)Bl^y2xss|Js~-CS zZ(;*1fc4`?Z)45n;2rem3dNTIcg?dWBj8-f3bQzvO$5=WS}M?Z7dyGAgB%~U>UTvM zigX0pfUl`-gn#r8{tJ2Id*6ZPZp#F@N>aojC5ctgGf2qui!*uSjaTFcKK@C$0qP~X zD6S+PFs1G*&NGRJ;Om0U#K-UJwV5wNB8C3~U|YzZsxk35O_-u*QR6Ygo&y`f^k)iS zRgJZnDwJxwv$~2M_;d2saLj zea&VEsCg1fFs#(NKyd=^=0tw*T!ueMOCs_aKM55Hst#HhW>}|?vOEZ=$9y@@o?7iJ zOKq=yFgh*>y*mj<;EsaO$B;(Y1-zaw5ZoY8T{mjF?5oSdrE}AbvCw|o76e2i4Fb6$ zkukr;+-Aoh8KHqOLRsgE84W?&T9C#eL{sobzY=528KbAR)&N8b0M-E4)oP*qI6Ixm z6g(gwh!pqiW}Syq!sGC;qPZg?im2IOfuHx3xVui+09o=ldi5t{qDUP1I{==%vYcLJ`@QAa(5Y%-!!YqtGqfFqd1 zbb6pOK03M?CX<2?C~s-RZZJicg|E#~$E5sJE|Q;(=0~8`&(2QdyWjqby!GZQ>H+MP zf9IG7XGLDM==tFIK<8^%_6YMh2%P^n^TPf$>klfJB0Uc_7yiM&^@Wi6g9d~9c7C6) zot!*XK1ZKF4VL(hVQ{hPxGOE` zE3_l_k8CTJ>qhQ<@FQ~brPt)~!*>vF^xGmumpTqTm0vsby<9F8oeF*Tv6LFWdu-N$ z!zF45Js2)JYlalR6wqjoKnn~L$F=Z;Ac`XtM0yk`h~8qp(8~du<=inF zfIr`-rpYU{3tpxEMz4j2i-j}lZ*i9!HyZkD81EVYTR<>imoNaD+sD3_ zHerY|JZN4c0Lb?yAYQ4))+-qa3d6v{w(thR)k`+Y4m-&oQdpof4u&DXnl50J3<(Vf z7$whVZCuZuIuA_>gdxi32Zu*8na*GkW2=iptF%t=fwcyjp-xakzq6sxIJDG4^+Lgd z(8lQT2n0HW5h9oHPEQViFFk#Zdm$UtJczsQ*_-k5DlDtCP0k8i29#4Qfa4luuhB=_ z7$H;WrHdPqm?~xz))ZD;iEFtm>D;EzLq z@m?^`F(cP!HkUTEm}!k#SF(Ue&w%Sl=WU@I8LYUbzvyazlC!9R5jOd zf1Pzd=)v~lOzlkWU%^!;6NE4@Hhs^izhJu4kfG6PN!*TOK$(h#{>B>CLtZ?7{6OA@ zkw1CzNFF?RTTV|Nfk%(k^oYah*_m>4GMUPUKm1YXX$nrwQQAm-V=`Tu_He)$zMv!V zWFC; z`w<(UB@YpxpZjN@kstdbKOH{wp^pUElG1@XWGEuv)GVoUY)hTNmd$faa4IIYb>)Tn+whi z=jZ40#_Qjc3t&yt0d-V>iR08JdCP*xKhvrOGC5dy|NrP$ei=MkRR7L@>%RkwD{I!X zz>F9c6$?X1S-_bNv6G(1o9d;QSK;B`p;+>11}$^8m2!*WWcPX1&04dhvfLiA^g`oj$y>?p!0A@}|X_Mp!n0L%_vRaH~Qk z&+qggpPYK#a=Fsl4i66C4e&7h?$FZ={9mY#LtFTyhs+mi_~BFe+~@u&Jo!*&2Zx$R zm?!+dmf!sKe<+V0yn{@wRBIFZL$Ef9ah{VEh+$atICqt#hWoP7lUV*#7b?=igS10P zHXiy*2!Dy9=N|kE4OIP(i>AQMllhX>92&(l39sj>sd1fwZ`c^$ zxYpW?Tft9$r>kp612?rkoZVn9wQ6C7daafg&r&_gxz;$xzIs=n4weB}K@Tto8ph@P z5}cdD*hZ=uH5S$6()i5qS9AB&Q-TEBhd5U?>S**F-5*5a9@O+~cA&;F!bJn*^Bc#r zz?tzlh7CDKBd3NK`ft7YJ$Zz%eDvtPy#3Z2ia&#By;`Y$Xlxp)!TPy%(>!20ovQJ5 zeT#QaVQ9y|Xf5CP`WNNnAOEDhbngSw7nZ8btipLJiL2lX{-^hP4>+@fO}}>0-Dq_Y z_*}|weeSdJ>MP&0`QzRQeMX%@k5C^$gq6VY1cH@+^pF3({EL6_S-E}tu6+2z9}6G; z=nu-vFMmi5k8Y?5Q~%T`H93&qO4RQRW>Ts_`+x86{q5qFSH3NeA3Z=&Jysv!K7apL ze?>m^$xqGx>VN##nxY6Bs!-o_LlE{`9+CI9eOep$Zu)i1~|{L-Hq|Jnb>za{6Gk%m18pyBJ9 z<|p@5jPZSqV$9#|J~>JsH;fJE;J!l*gx>hjfGeN*)xRy@{L*jAkN?p>Er0$m{AJ^~ zm(JSMZ?F$*pueSnWzF=F&tbL?AG{5&j&$v4Jdy=?`sBe|a_jg=2U8F{20TNNeuSDd zu=yMJXCs7TSEDXu5&A9N8`LYbr6}XsL|W8QPR7ao(5X6;Bg}u%>O=i!Pw&eb+~c|Q z9@RMZ9wV%AC^BX#=a&n)z||o1Z{E16-g&t=K}m&N%-4$D>G`F+{Sb2iZq)h`zBfBK z)R~{1o}dTd94a|Io1>&l&rYkRLSN#1?^KUL7OPg?e)}!?@W(z`93CEmf2Eps18589 zSao)CqC?c-tI@0w`U`}^9ED%PXy-%l(}*d2lFCe%M$--gWkCd`LJ>_fR&%5&vKbcH zz?`^dgm7h(eK*myg*K7|?SYk81zET?1jcma0z&Uu4%%& znzlyRt+dAp%xVJDO@U9bOmNXK4={i!K6zhjO-5UmfUUq;t%eZ?M04cXE%qkqrr7V~ z1`HG7`oRx;sGLm3A!TQ2!j7rGqy-E|jfJ0VndXCXLya4TeXVn{K-vzO-b35yIe3AG zEigcs^$awG6tSYolVGFKSogiSIMW$x&|#(;|7i~Vq>vPI**I6Q86>e%Nc?N{o!arj zg}HLJG!pwPbpKgr=PL(-%M530XtHPUha9{(KU0%p&5L;of6mtexAVZ&fY;#f)v==2 zqu-0TxzGL-pTZi^|A3u2pYBCoHP~B2o)^B818N9(mpIjpalFy1wz;4dnlI^S#8t82zBr$IbI&w{q?Lh}cy~0a)j(UE!;9g6 z2o4?~)W~UGWAL52zi~Wch)LZ>)Ym{aON9M9^5rl6w!Hbqs~Y}K9zVhiz!&T>hcBp} zGCeAXHJmqiv!1FdlwYsC`VBccx~Z6b{Y#&d8XUU;6AaD`3wi4yg&BVnGYkE^Pg@rw z=Y}(YTj^o6#C*wZ} zCU1JACV1JVYv(L@p&tJ>{OYMZeCI7_J<82ncfplTmcaaAI?>taeqwkRm{X0{nz zO!L8>4LG$%zD$TahTfu=ltKJEdHMj}JRf+UG|rs~!DKR$?yQsZi%Xe9L-+5$r8+U* z_o{jEe2V=m+D}=PGM&x_ZR=X%*tf2&k$XjDpQB1=UNYl=4|Q>Qxsc!d&Cked-+K-E zJCdWrBjnjp`1mJ289w=`Pboarlc$f=jQJ7|rUB4!HT>sU@VnV^7zC*u49*&5fh>l> z*Nn%5c`}$O9-^I$Z8C0bz<8g9lPD~lB;#lBfJoy};SM3QF&-7Q8mt+Yg}>FTVY#-z zG!cjyPGO>G@D7b}xtuG22BLw=u%Aw+Fv>$YI=%t$j&&F%`eUE0afBX8c$y)JeRA?d zPEVgGZX_v*JjR+i>RGb}2=v~)4-_|U9v0*n+ha>9q-iXedC(3GP9+a}BqYmo%t8BM z5E=eF+b$EGhIa3!;N;gp24KTnN{09hRUK3zWyl>yWt7g1}7M_B`_uqa)zK5{-+Sk7Vek|kyn6J_M zhHIu<7H$C+RMV_Md)GCaKG*A7dxCu_c0cfmPs&U8?tvqnjKK|1f`Mx0tr|^Xh@N$p z#=q#j@=H}91QGV?A&_FF)5pq-4}JJ!;m+-2<<(+-p+0BW^g1W?Dm8gx6Xgu% zkK8F3G&~?#87Qs)!|GgU99 zXBPUK-cU}a)~5y>eQT|}CdUfrECY(oF;mzOe>Y7laZ>5M`>%dozW7_8m7nz@h-Ytg*vvTLe6@p>Avz^`|i|6&l2$K;3&AUL^(wOzs_&jIH7*{PGERukbq#7_g8VFo;JoZZ5{hXvo&fnl&3geY(g;*$vM zv&0}Pyie@79}S%ETTjg}hh)ea4sJZWl>9Wet}sI;DR5R>2842fx*+J$LIuFSDMaqO zf-o8lRX6(~6jRXKKecPqfqmFW^a?ybnz7PwY9}zypEIDGoa|l2hjlb44L&zB_D6G} z=;ttJnk>JQqqT=Wap`C*bjRL;Up)AoAe&G9Zh=q3fUoG~TGlg_(;Dumoq>k^IlmRo zT`n%=0EWZb`J6lPIcw3HQau{}W{`tM{8GWDaMIa$P~a@~-eXp6(Qd+DYl;?Ryn5%p zn@_vD*j`;vV1t6z{ukKd85USXXe zXr9Vsa)2U=flIuYQfz;WkhqI?$#Htc$;nfz)hlETHO$xywu3I_^6GcKD(}EdKlGuG zsh^->ykFr61`LBDNa+r$=Ipc4`QU7Z$O`i~gqbl268q8&&reU#TWvKszNp&#hqY5z zK8MaZoj71|W_T<9ct$VzozMS<933CXkNuIKgT^o9@BHn*A?@-~{*Ax*@5`tE=r0k6 zz;z51@{@BX4`RZL=V80=e*0T;>&ETTO_=cE@r@3nQo2%lz&mz;_?LcLKPURCv`SIzC%u< zF0Pw^FT|G5*XyNx?F*lkkA3`y<@nY;4fGy7!`r$BXrLc@PXw-weCONWkSC8HD8DDu z1Nh`hF@1D}wO|Inx86uO?Ho~Njry00ETeyD*=1GB5vV$(%p*BbmObS!XE zu_*wwAQ)@YmRQes8LWj4f?{Yew6ZYc=XYKsh7X9uASOXcR1S%khOHV&tHES7v~JST z3>#qRa}p88a0x=3U!1|L=L1Ptx78~q6kaXsV(`**jLak#KY8(N)>dPWj(eb=)okt} zC^VQ44h~?DQw`nA%X69I+&MqzmwGM_r1`QgVo0G}UYx6dXE3k{M8kc!hG(W>YJd1Q z0v6oAq@iQr>}|PNXxPv^yKEM@LGwys*RxlW2mLCE{Ax>*)J+%Xvv)cS#n)m_;=Gx4 zKNv0Rvi&)5m~|p9L=%IZslUS}k01uo8%=3l3<#nmbf4c52jrqQktcfvn?a!tBmFs5=l-$L5@Wet6o0v@x+>{c-M z^$K$vkHqak=S(xJQ5X5FTq9;_E}UlyZx4SBIbyANyMcS4b5^cveb%MDvhb(1Y6$B$ z-<9?2hxN+(J77s|bf|-$)DJa7Ev-;9=TGda(M0~?Km5<-?YG~Q3Bveg1VF%SRx6k( z!`KZbx{;@F;Vd3JxUZUEtu$M%(Rrtt1pemiFXjH5-$b~>L*{34cKQJCtexLupIg-4 zr?~gwk9k#VJY5@B(NAp1kNAC;2g+{ zHOO5pO6{3AQ=l9P&t$I@E9>X_H4K%(OYBy_zh>Cdbg_?K%Gou<-l}0U*m?eNd9WH) ziIMfxwiYbJjPuv;-ggRQ?1puPp0URKSO>2$YUDorQ*MH5oDFBrnhmqW(G6E)!jT0r z{2~0lFD&?JTEvC@0}n!|yhVuUd)k1{Q(rH#fHdK2-6750erq5Td(PoZa;=UT?o1LB6rRf)j9kC(iUazJpwOKdj1;kT>!u)B2w_(Vh-gxaB za&h`Z?mxM}+L8RoCw~mae+mvP<&|%LQTqCV(d!)RH0c|(5i2ZB6) z@D>A8j&C2!?C3zf;X@z$6y6W=ZQybazR_pr=)Y!NICVOm%B@?s^_=9D#Pam?6nQb0 zA!G=O(L>1k7X{M5ETq%$=b%_~sqfMdJ7805U{^}~4nz+TYnqXg zWd(5S)rBP>36-C8a&gAfbnUGEw+U2xwXYv~CbFz0TZK(p){^-zzd z7KW)Vnrge!@twVA_c zVX{0|W;Z2HkED^AoX=PC`0_rEY-Pk`S+3;dH~xv%TEO$Z^~!hU;e)s3fA&B9kK_P8 z_~SqJ$+s|* z$B!S&&6~IM`K{Y`RG&P1c6cZ)-d!QI4S2j=(%UAogjQ-0rLULf1@seKC9fiC_&s^> z27K=V9GXglna|IjDhH;sLwWq>+j4e#B5$0X$xnU!lXCN=d$O7z%cI8+rK=FOID4_Y z)GX4x!WnDK^T|{2>*$8|0KjmcU_pO@0SswJV5`wU?ixaKp!i`Z#FxS8#j(sgVeRw3ti0TbD2U3X5h&!6~6={F~5MvZX8?z9(a85RO3_x*PXJ zfh3=WEeoq0e0Mhv97PlCrZH2WOw450G+zclGX23+P2dzigIdLka~cOu{^wl8SlLEkqs$9~ON#xNX)6a|J@M~{^XPlIWNU#b&Dt70+1w5Yjhzy-{bJfQfvJNd@m zCX=a}4)@~xIQz@XOOX4*IIyyyt-*uFuNM1p2E|Ytd1|35G_q6?>+@g`%ohKXlK0TG zt{rmO`k>~yFN1K6bC!!sIftRKhYF_7x>;k58dsQm4Uu&alxfEJ$F<~>!D1XEA5t9* zTb-f8*PC_*Zd+dwT!SL5H^t26J@lx1CGDl!d({K+;aW8)H!Yr#X2^9H=TBwC;Iknq zo-6Rzc*WpEC?LGG1JJcB&-Pq*TJ)MF2PIVWPmkUKzVu=mBqnbZi64!V9a zsgxHrYCgPiOYYtr09_*%I!%hlM(a`O?KS_nLl|; zmJP5fClW>n#&aArn;f9emHHZdUV9y*uA|u$hCY!mf8n3Xmwx-#WsG&VZrzp|_+o3h z%a8@Xn=Yy*$dkH;Cvl7n$jLa#Qab#PJXO{s43UjEuU9O`+FLINP|z6qD@zM{evfin z%5kmxavyqjz&tCgp+~JSujR_PGM&!c&ZFYYnpUfo2GPO6q1=A?1DNfZ&IMIV#QODW zEpNU3rhMkJ|5!fs!4Jw0f8vL+cOh%cW3gCBg)n8%uc5CH8x7(b^G3_k!wcSd^9|)C z&oaUms^{6^fsAnG;o*^7%+C?Bx`&|gg-=QHSUf=pVk zFLf@DA3gwvg=P%Yi;Cr{94t-NMNvA>t^-ILc|eMP?VwQneAfAkOiAvroaR&6ia z1>QfDhmYQXX3oHaLSBFKP1JgTJ#HeH@5{-_V|nuEE!2rYGsBv>=4dCC`Lg-~f*!hM zPH-;F5ptb9l+Z2ny8tfq&8Lqa%J;ta9eMotiRycX3@hSVbzjlAl4&FZN#Uw$PKHWa zSZWLblEP;@Ot}Zmt)<@a5(@81H9iY_GyeQNh5;LB05%P`#gG}atc7MzBGvUu1D*gI z02WUAT|=7Z(0C}gUVrx<=qZ>s(G~L_ZmPv5)Ib95ylmAJgPOEo!~#8r2s48b(d=0t zfv#Z|E$Zwvl+0**nspn7z|&7AErv^@qM@?)0>j|?eKtQ8oYCa{N}hmG#2Spz@_l_pxp#7 z@e3-Z0&q4OUWPA?i9}^j420=)3Ii~+<*a#siTiVe!V-e78UEl#(-=p{72?nNC!oyW z*;@gUM%==>=S6f;YDh%zreYA zN^fWJ564*ug2#OyV0178+*|?Y%lS%XG(@rc)7vm%m_1FPszw^XHGYn(Ah&LeWWHEK zcjwp#e898Octm=r4kL`FUcDvj)33^tiJT919clYg zUVY_T8bGhT{sv6`yQcfTK@F^JE;o;0b{*b@VXnZRX*iK_IF+WFAs{ExDi-uk_yE-o z%*~gmM;PR|lx77!B4|0oTJ;)bg0Pu@gC%gdeELY5#Rbx(mJ!~c9^JtCy&MDImu^jD zQbk#upUYyol&^jLTk^Fpe^o9XKajgK;xf^B+guA7p_UPFIYM&P=y~0sk7X(Ccq&Je ziQJrxWi6%r)X)8*{LYuZAZOtG`0fuW_b(owgYO7$aPHxwhp6!gD@~~d>biUDmIfJh z##-jf6`qXc?(N&MTEL62P`TvS(~H{pp!|NgPeZGbrG8A>jj?Ia`O1G%w{uLt{XY8dpVzf3p4+= zeB^^4RQ`;oGkN&V+j9TyH{}lI{P^Tl&Q6}n99y;U18Re7))yDR8D7D&@VP|Jg!2Fhx3=sXt~;1%Z60hoYykTPRQ&xf~zfl=D?156;lj;YZ}ocr6`z6Y%rpyA1hOe&r)gI*HZFL?dvowwxalgDxj9O+ZcVNx^czT#j9gD-|AjvzkTmDEr}(w+Gkw8I1Fwzk59*u9s-`KQ4eKcTx z8qsY{zg4dVJ%J&Kcy0B<|3a2ZDde(DV`%x(f9YA<+?;Ye=Qc(ww*HH?_yPc!31jEM~|t21ssSrfkz^VD;1 zbx7i^O~z*<%40LDUSHQo%p7J)Pe5QdO=m$7OqXgz#z50#y@b(U z$k_s3Gdh-6zxRq9j1ORb#|YUsfN3Ka{akL`;2>_xn{UA|*R7nKo=CrjuDX>xIlT{m zK)B(YQJAP!%O1K!;9r765!YxOq{VzBj68(K2)8+Er*`uL@fAZQlgO)EF1h1^@eDrH39 zGX~9pAbzDUC+JILF~3w#B)$g+H{|%n4ef&zu2<*iqgVdboG1MP*Bbsbq0eB0@u*a9 zn<9Mf+`TK0&?Cx%hb`stqjymA$FvUPVV{)jTs7XPe(DDG07IO!n4e>wQ{@r{r5eTm z5O&~ELk_^(nkY~DwH#pyq7pq&Pb+D$?W?bx%iFJiTkZnG$ERm9Mu;CC9LhV7Po%*b z@{{M62;>!dU$1J-42C;xsKyKm%y|W_tuebFUxE?RgWXL1M7iy$Kzx5bcvV1 zrk83umN9Yi&>Z888%Gm41J(zJhqA8c(&68evom@6^eH%eBy~HJ_3VT4sXNd)Fu%kc zm*DJbd5%5S*rQbZCzC_GS(?^x)*2dm_~@}bIXRK40w1xCTzT;Du^i53Qg>^))ZXZ& zH828B4fKZJW9)>$mb3GxmK*4O0`8Rfz5?%w(fs^WX5b6;K;BjBrD{5qzyWiqfEjf_ z{c~288r~)qH^I)I<>0CRfA;!BNp>W=^Mn035KG>*ZdG@a&F>?}MrIUcGLvMc_kRMt zf+*A2ki(_+yW|!D_|aM2j{}Izyjykm45JD+A_0f{S^nq$GXXS;f*EJ_A1(}YRen!E zOr~H~TlW!<<}>W_vwZ^*Gc}FPgo9@>&DAi~5Q_oa4HSF-mot4BBj~XUBDH)C(8`e_ z53pz~eiq+ku@y4B6UQQE&rP}~k=~QgPa+^TPL7rHcQfER32X)aU;v@NheCs{#!Y-s z5LqHVfEt4(B4$6=dU4!2tLaMINd@K4eQ>pI0phj(wKdis?4I{)+wgOkia?8*0YIkW z^->a@!E1s8W+c>;5G3)M22=CXx!hh&UR!-r!?K(5oc-Nm&f#1zyCJ9dO499?b46m|6(%bbCN(6$?nf- zOfO#T=mDtd6AX3)hgaYHeY*bn$Mm1`DgBr8i}XMLFMpq25|}=Gc$=Obcj;e$J&`^z zy?*t#Y@zg!RR4QO?{81( z*MIu2>FU+D>H6kFx_*C?;QEJjdz#YEH#r@yo~JyP^oPHGLx7~;fAe?g-P@lDUYK6I z{6;`{zP~1OXN}yYTgo4)e!qM3Z5j^;+Vhr5H&LEr+L5P`>Bu}B4g-PvLpoBuZ*Q;D zhnx45H|6%zk20g|x#!cJ)SK%*fq5Cv1ts)`(t3S=PX9*xvtIcb=g68VKEpJ-1J{+- z4;0H4eaoIyyS;c<7TI|z3ep1X}8}?Uw0JW zAO3JoSNoDa{5w_PRy~m8I2%8bvx;goj&$$tj_ytAly>Qk{fJ`Zd1rl}NTcs5k-z)T z|B#*^GXZs{Ork!%d;cqW(k{)nG!y^wf2Du?ck&9VKhGH~pt#3jPx-y1Ud-&e6o)d2 zR4B1Bqw>HWGE<%O-eh$m%8M5O#V3AZJeeql^Qp+(kKBNU_4YvrhymBZ*|5R@ND4Jr z?!X6w24FwI`4!=i)z}oabOMWx1cN3fUON{ZdTw-!#mVNuy8Cl1GXT95(=t`8ff$1w}o9i}r485<3M&l7{6g*NNp(D0u*X8O&$@c=NO2l4By z`O=7B1vTo~(Bk3SU0rQmf3;pHyExZDg${;|3cd!227|VPfSB^i25`<(L8>oFee5Ov z#bDGc3ta~uIC{)N<*OK=!G@gII*52jdCr0)ixpfm*yn*HqXw$+DrNau=Gf>SYi^L1 z%S6-4!TN7C5i<4_DnkEUHO=d#L>3q4>BcQ!M6IP50>>|L=UM~qKlhe<&A)38?Nm2O zDOUx#f!ivMR-f8ltACpyjj;kg-k0-=3dXLum$n~uZCqt}X3L%Ly?psHok+V3BsLjH|fn^{r{*QZ>TRN{h$B) z|0bIM@yGAe{9peYne{~eb0*`aGv42(o8c<`{OeoFWs=@;Ot%d0+Z&n>stwg||NC!J zrn;~vzIZXx886J!_7W4F@uKe-M%V`tv`hAAdg6^MSzmoO;T-$~tS*x0%kI=L3!46#<;}VX&rH zM*`K6*By^?e?%%2>14kzHJba$U# zy?8~rW!2Q}ZTe^G_0`o?+LMk(dKXC_X{WQmesSjfsZTqxDfJt6l<9=&84c$!?$b;y zzqz@#c~s}v?f1e&wnnOU+V4lY4x*iLG{`OJUxCSHiDp<>K~yoXFmZ|}gL^b2Y`@uC z)nC=fhgUd>K|GMiGSbRbh^v*tb>4rG}79X%V<{Xj-SW;yH> zGR2_FdS=7q_9M+e)&WmQX2Wru*~n?lOy67+hWB6p%^xK$hQ*Q0pZ7VP&S_wn5^Uf7 z^b>V>PS4+bLtxQ5AIUiR3^qlXS~0vV5FwkScZWLHa+YX~v_yRw>3jx^*Hpkr>lw(e zUcV&Z5_BlfD~jdn)mcD$fBQE5%Rl`;lHWu#zhiK*w#Zy3Hs<{|0#ODOeWee#r*ush zpP8v1-+#~f%wl*l_g9B>I3CjTZ;t7kZ@)`_`=9<3f%;|oyTASaOZ_>h>lf+gU*4s=`*S)?C(+XWa3wi#ZaeDBZnu;AN)OE2xJIrMusP&9 zoX@B9f`F|)m#Jp6oXOvOE^CH;;nk~Ga@I)u;_+Ex^j*>Ty?*^V-QG_rkKfQ!>KFB4 zBp~gmj(duOJ#Zk++}seb56|cx*B|N3L%JoOnaD$*Q9RFR%}n*9-P1_(^@8pd?jgN@ zO?|&PlQvL&j$@kVoUWcxuE*b{;rt=(XN4T=n{ywAXWA#3UG;)9)suhksK4yhWIySO zv?sNoeP4`}%iS&Ys}P9UgGmdAV|xDVc{*M_6My;c+gGCVs5WM#<(~){~bA2N-#>_j@8m`a8!YLF7trKsh-1ijYK%!?2@95(GWZIkg z0CZNSI&u&*^@#iVj5Kw1q`dP^=7izD-|eMdh2oy5&#dbJd_aT0H>5e9`!U!{|IrMdbtMKbTmG$JsX(rIq`WE?|^C$0wL}X z1B{H8^&^o>EUSd8cB>g~^xtEU8c@Gc_6C8C&xgjg7!A{i)+x*TRrj_^YT(bRG@BH{_}7}-C3=Hyk|oB|^>vyFvL$QP735gwPR7J$ZzLm^+-c>3%%At?8qegQ zY)~_qxT{xmV9=12xrtV+N4#yJeRM`qOityZId}~?C;!a=XOs3>=6vL6UQfVbvmeMndH?$p z_dAn`9t6VE`4^Lf`j*T!L9USAMw;P5HDm2(`sP^ndqxqYm*2igfBc7kpuhLEe*8bz zXis~;c=<9-^v=6?@5F0-;2gfZ?@Y$e9>k!eeZKmUfN`BNaCmGfck0J(57H~7+D)fBIpg{BtMtw57wJIx9uE8J z;|!_i&yMLO<;!|`OZnd3fMN_AAO?Oi-7w>e-6?{iudX&J*PUIxn~s5m^GF+ueb)Yy1B-; z+#ea6BF|alnd-Zvn}v7*fvL<8?RSyh4pgJVc(D0n4emw1qs#`)`)QCK`P`2Tv6ZuI zfK_J{J1-J*h``ZYq#zD=;e;8U!%&A0sp)3y{<`phjaGrn!eF8Bxjaz7mp~`?ac;zo zU0i+K-niJjxFF)d_mVh@CFo$`SWy|q*)sGHH1lG^Bgaco6#kG$9YVUeNi1IBzKC@W zIWcq;fDWMgTP|OxMkK6}s^dmgLLV1J!`9qd0EniZyh}g+_+xte!}saUQ4Q?w{zN{IqXS8tgWa3wbCq(P51B)U7aQ$hnp7Vu zp&o9yz*>UPSDr1(hu>vzxTd{k>P=+sQa9S;e4?0mZMOB4FC{+He2-+{9B(!sV_v*{ z``+}#J~P|73WaQ5#}Lpu7h)1=n|zyd=e_w1o_xv0OoyM%RNr*gVyxx>S{|wDS?eji zv%lMNtyk4I{l31tOScR*KIU{zcegikA;kt%_tWVDOQ1 zrZ?Zbk-cB-#&k7~lFM`0nP*ZTcn|5Tdc#P!4^+P^D$5;RH?;naYBnAY>FVl`2GUKs znS@PfxroLjSF}8|CL^86e$4ZI&sHQ}W0NxO(wX}~`AH9kbIJtF+uPf8^WlT!pVbU? zu2}QuQrRHALA|(x2g*g(iVi3j3NTAXsJx`VfqQknlTM-O%dc$alR@XMB1|Am;YnWf zW76Kuk~1-sW@g;or&q6E%bv_s?#Uh1l|Lg7^V)3I-h&$k1O{{;M=#$4 z(R+<7Hmy~MGBXc-Px)9{4T_oQqT+rShF}Cz)JqmWkHSDt8RI!XG=datUoPD&RfVcA z?#*m~p3U5{wN7HIdg);gG15wS6So<99KzOr>~cw6_riVLMz_(U%h211o0^7=U*7Am zl@6ss4NYBUf~#4fdpEZVEB>v}Cu0;N&K3S>U{NS=nCYZb;`2WqDZxhnYFi=J^ zW}ck}GpGO;pt)wl3}w5+1!lmC;Sz{<1SAGBuQ{F1((71*Jw4aBCWCFWN`^RC%{*s4 zD_K6x1NH=`moHw)o*Z}veOFqLRc{V($Q(nlc~p>8eXyR<`Y7PX&gZ0K7C-NW)^~Z2 z{dA=t-QRmG%A@t1oA{vVSVz3n+0Qor*Vi}1n>ii#WBU2$pVRHlt<;LwIak1oM*E!4 zT*p~FgWo+KkMj3O^?JG62``f6F7;#qv&KjhGtZ%DOy~bVk9HL1vpwm8oa5gK%>Op* zXbrtbxpw|9x=8B1HrM?0d^*d$(e$q%Z$`?XzB_TQ4zQ z;Z;1p<6O-l*dc706|Mzj{Dca5sNf&$w;Qws>nx}Vq*g@pF6ePDYgjNYXnmSnqf#)y zMaZb10q~L*-m;Yj& zf(;Ug2NyHIU~W~GdXLQ-L#$_lAsdK;+07{G2Mj8O%*FranP=!98TthJ!bN9tMuyXdAg?a@9ypi8p%Lv@C60D4YgH6 zXDnNOIzkM^QniXmkSOPL25~0=jKngy0dpND%byKZ&+|IWlsg-!ds6&}R~gVbE?&ob z=(pCAW0AQi{k#bju$^P$QseMzH;gsLnIOvGuf3sIcLWknbT$}r{QQ}xS)PNzN5R#6 zfI(t5t!E0zl1*8dXNe2bSkz13y?rld?8aTyOx2hJm13D@-TfH)e z*WBE5RR;ubo&l+Gt}`{!e$zDRY%!cgTRDxQE%K(x`;yyOvc9Gh^%HCD;tcvaQI~1a zp$^t}{>6RZcx8s=y}@2AmY(jxT-=liPaKJFNE7|d1!0?+-GsSebul)oKWLW0DA534rM_f=H8{1xBWc5&jU3u zem)Lr7y<`d)F8y#o)u_SQ&4??`*)y!uBh(M2>g5c9t>nN1IlW#&vXX;xZ3S&U>VFb z%^-*Up4R2`d^e`6!#-W#-bjDX25^oy(~T?^0a1aWR%?x`j-bv0ubnn9^yHsb_Tk^6mzNb-GqjCT7q;A3}I3Xqg~ zi76~`rhUZ#qL`mRv`Y&%fUlj=qk8bQ7Bs*~oC^>_&B?lK^cJ%+-KcgNxWLI3KqI@- zV90s40e@$c0ZUy5#{V_|`8C(yO+)%#dnZ)4(GvrXC5cuz>>h0OG4=0<8CuK8;g8*n zXtC0x9|eBxxPAo#gvt#p^LtlH4=Bhb`{+^0|r1!4IoDO7OgK1hiMui@1 zmf6gzUL9kW7J%iA+RID^zRSB*c4UV8F&&TlbSCq>A>d_#7n`Ne5ro+U*I%dzR^ga|^Cn>PU!@k5&fGTZ;KN!fN>b!YQs}(kHt{##5 znMe;or00#cr%VQ5E8Pp`HUllIx#rrU&}Xwe%z8UeW!F-DPbaNK4Pk zn9jSB?n&2olV0itZ6+xUjW6seB`I1jpACL4_43~Hc~r+~D!L9->!m331lq;?D%6Gb zd=BSVU#GZk@9uRLNardQJ~OMH3F@cQT>In}=~|UJv)JZB>wHG4fBVc}<wevY}|x`UIz3 zM|0t}8!5Cf>u2$f16~^L@Gk-64u3TYty(NZ*yo||#o7i(18+SGyLZ=98F>$vp>KU~ zAvW`CfZ)EMiQZw_4}WZp!Cy3^;Acn-6c5AS3T(TAQVR0+`g`Jd=SjxICRK_h|2Ns1T~pX!Zvj(qXr! zb8>o3@BM1McilO>%+i9Bqz)*x>WezM;m*Gr8o6|&SQ{Vc<*VN=$b3+q;|NiYz$uD^TBW&>BTquooJ*=*XpK#$29JbwDwln zG8j~&Z&B@8AFRKL@@5ZE8`1pP8}_8H=M?)yv7Tt%iN0@X?frR{d$K3C*j%q@J)I}b zk+qG*dR@VaX&Ai6ATcoD=UH5*>a$ZO_A}@jI8#meZ|<-11ogN1S$u1t`5OkEo5&LG zOU$FrdG+!*4^-a@cJtd@;oo5I8uGQ-p4V>!zluBhkOhJUY6ynbdwZu8r~{@Kn@oXb z)DXmkP8WR0DLdkA$@8+i`g7cR7LCQAUoV3)sGtRqMV-b$);n~70|rb4Gmkl9ESv<; zz~4<=H!fS{fcm@DJay(K!pZu#9EoOdHaw(x5xWooJ)Q+>uQnGLe} z#8P!!!%lcxqrdSSYRz#mJ*xS(ZE53c(%^jN`P@vHVNGU@IJaSESk?fv#&ITj|IBMx z%K{Q{5c;jlCi0j2yQW3<8!m{{g1>34`l9w1)poWEV72Z9pPC0SXm-0X?FrP+pIyms z%~zqw^*B#+y1uzfA3j_sob@nVn49}G-(k<-8_x;MS9I|nzSQwP+dlbplAKh!uF<9o z&W+i#%L+pVaG?uWf2>{W34cb?(4P9iI()SoQaM7pr*+roDc#a_p3T?L*n+_U*bkFF zx3*PTu>Va|1J(avGo|r%GYPKgrB1Q);pfkevUaA8r4G@DEf;19pV@l`cKVgMsOOM= zO4GOdJC|Pq&EUW{sGCW{!VuJW%t+jyLNzeBom+VO!_3@`eOU^FoddNQ6~|(iiv%NU z9yZF(fgfGhf&qiadi}y`Oe{zhE_T&>k`({#7dP+W^D)pO{;r0!@+=Gy){4tuED6!X z5eL1QD45V#%xbRw4V_?lz-ZK}e`;>H7@XAE0N47G{nq=<6Pw*M$Y9WH1BJ^BtuC(Rw5g)*B?!=7lwtD&+j4|x z;I*bY(_H&(vj_g|Ff!P3Pj>Xrfr_}J=cBx%Ge6K;);1dx0_j}LnJPv1cpt9c%szr@ zz+CH@lc2xFSjnVS`dXjDYe{EBGjUPY5P5K##~rO5(;0@8*#PN%KKli&zc0#JeNSe@`Vs$jNbu(@#^3(- zy{q3vxNvW%1|z|T|GXD?hVEOfR@ppopMn4E*|YTg`Bgd|4rwG{W&>CQjr#fR?R~nw zxz+eKsuSOtsNURX*46J0I|6t1?$lgr&h$0&JU@G$%7^#z*kdSluDRZ{hPL6ohLNC7 zckihOyQDMk%FsG9%d5uSTGbiU`lt_FU*8z?bMp{v9;4eqWy2JC3QOMEjME%S{H8ho zj61o974~cp*-Y9c1S`*RDB*?^K`2ma4rXo!^4$mPx%cj;=Vu!Yg+FRc7zhww-2mR| zQ`bEV;fpVA^6Zq1?B3EtO&+v~K#}wx7jX{|?=ebH*@|Za^QJjdHFE zcgqhPHvBuaZ@RzBWXANzo}TX+`dOhrlKGf=jFW0~GQa#X&*Dr_WAmc5g`h2cEwku6 z*$kAh2MLDy4gxC$GF6`5Mbb|-XRbc&q4UmVC}hs8yOQ?wY){`Oey0&!jJ23n+j!UF z`;?~6LF>SQKkdp2UB}wI;>rgWo&(L8lfh;j#|Hm~daaLmT8O~v;Cn0qt)b`kj?D?L zI#0#w*hjB_K<8Kbt!HqI4EzIu{_5%|*YS8v!=PTkwa=bc(Dv#6oPPQBZTkL)AJXm3 zZF=$IWrKI5g=>K?@(upH+K=fOffp@*(Ko>x&e-djp38i)9%T$5O|ulHo3?2>3or+| zjs)xdF-zR64FRzD3W1z*!eLDLd}2|Xozfhz=IPnOF;(?S)%@-VfO-ip4z$hNhNY|v!5Bdu)( z(W{sVf|&`Q$Og>?Z|E2y}7va)n z>pktCY8F&`!Go|zeodRt9td)~-I$IH{AX9`<%{QO7)LW}jh*{cr2o4AdD4FP{C~K< zNk9GgQ~LMszfY&rDZP62QrFK47=;hiIxx!=Xw-N5_q*f4V!}3~XNzAn=s-i!T@HJ3 zY}xWH5;AIBbjdUEwB{c(#ijE>u@TJ6bT6@RyfeLfCSCEiegn z*Ti`+ocT<;Ci>0i@@JqqQ7uwzG(ZAeSD|~5H>Ie@RRLLF!RrP~odz8*e+SP%uQxp( z2KD#dNEhX{r}##$A6+}H6TL-kcc3<#s^E8X!L>QH8B@c{u$Y)IAD1y(+S4W2f|xZn zy#^~j2VWcvAW!W?U^^hJFWrf&V$3PY(Z5p~?UOJ3EWfc@q>WV(tc^>~+LN=FQd2 zkh&svHD45X6R}ZO5&pGTtG=p1J2)VKK84>U{I1_1bKTZpJ3d4n!iTMh`zO$T!vlX; z4RpZQT(XTNBhzgeZ0?U>X3gK$Ycxa4t{u7u?>@*qem?OUx_?JO->}I|=d`0|S7bPB zEZ#QNBm>{z&YCj~N)LYdIh}s}B@LnO*;#EcWbah9>UwYJ zgL=Mr$<|myxQjjRweRsgtzqX6xL8-T+k8pRWV{``vG<`k0l4tP>h~ckIe!tahYcZG z{*QY&j=Qwq^R-XM2j{9b#kQpflrr&-C}ekf%5g z^v!d{*GNxy^tW=|yg>())?$_1%8)x4`an4F>N9JT)X=jspqN-Sa&s!|%^BeWWerCD z4F-L%q*zq2+$(L)@P!Y=ackSa1xGeHuGjdmH^8^B7C^V>YnSgKv3%$92MtyVe-SJV z)_YzhB` z8LaI3LCB}hQL(ADg1&w?#||$;18Ib*>%RK3?_nlvlIJ;)y4g1xB=p%@o6FNzZU9jt z&`e+x{`!3NyWZnj@q0Fv_IH`)HY#49Vo()mn}%$@8MwUmMA|sBu`pyj_5yYM~9J|kkRoH{2vd8v}3@J zV>%FQrS_n3V?Yc9=-qBqWk{@0!O!dbT#mU=6B+DpzyC4)`s=Uh=bwK`KmGhuy1TuT z8t-;H@`#ddu5Z)vi1K&I9B2{kIH6pxd)ohZ$AfrB2^y$C_p)-&Db{SB$$meRCyA(J z?ljgov!(h83Y?+(QS-gbd!3O0uDa(}_`dW_Ja87h4db59Zav8h!yb}qRk6mP&uON3 zC(euN!DmjpgVc}rMS9M^`98)B!i;A#jB0c;@^A~bsvt8YsO{f zw?>%{x@=}uChF0^{cZMGy>LuRVFq_98)MPQQij^i3fe#^+3T!fXIgFG)F!tntpkKX z?E2vbwoCP3R@@ouCW0-rFwza?=@R&DJp?@CI#!7nmrk)T8!3yLF)qyGA=qEKU+Zax zmq~yl4ZRq_FjV;K5*@~Bj@3LI{(KcN0=}k6!QY@y*JR+Q=OSh^cOJwFIyd}iq;{IX zzk)Rac?7i2FcKGV0}2tM0mh+83mrV~ta4i4CMzY)*W+&SYza zhVR#zK(@Hd100zAY@&Zg)~pDr*vPx_m(CORbmogRr}tu_Ihc{wl+W+AZy83|cd>GtNP z`Vv%aag9fX9nH7GgJb5wB!JBdC4PtJ|N7=O{qoDN>FwLM^8Nh}--{0nRR3|@5$yNr zK=~by$8^~5}6r8oPXz-_WML)D3S7^tTBb4xxm@ZVHZVm3jnr15;B zGxYp!ev*Q)n3k0dC7lWAnI5ntC;mOr^{VAPD`!g!6`97T_=Z#sfTn7~l^rkQQ zD)RS0t8yOnRLYCrW1zE_%DlKIuxIi)?vq*Xmm#odmlgc1TdF1JzdE#k;c8o?|dO4@d?BHlc?B z{tyf;*9*1LsRwTl>YlV}__%iVZS0#_<7%B*S%8YJ>m+HS?rQ69@{zFLyzk(t{skg- z9#9?<7Ilio73!kqs|FjvFOWYI_$LDXiNUYGEBI$KXWKWLYy)QOnGPoFeDJ;nXw`~E zgY&#Pv}3OU=v#tY`eArjj;be(iDy)Sl{b2t8q9)K_Xk;iSXEwUszNO&0WZu^%=5As zRk8l4LBDNMYi?tT1=>-9U z)kn`zczHfzjAY1hKGeTT^$H&wKKs>fOgE&@GH4H%^HInI^=Ry7FnMpAh}*^;Q1liA zpnGWGV1Sl?9@O-!eOleFuC9V?aMgfzBJ{9_rID=-t9m3o!WTU(0ovXoeY5yc+{Q6KEuT3jLnv{d7tnuCLSk_t)v&yLah_AATVC{~{j1@9zZs zbP@0m`$Ia=dHnZC{mtw<;FH?I*11UOiiY;ZIQ0GUIVSxuiNNwc(bpa4HSP)41U{V8 zf&M-cY-a-@gV&8fU(?>QW@nq@cXzkx`u;@lFX^6gczryk*VL2UFsA3bgZP8au)2YK z*Ezz<3uTzHrj)tP0cJgq;VfY1vpGKZ1Nw*S0_4TOf^^@N_%fY8tIkqmg4BjV&-bNQ z#k8q~RIYe|eNG}?q0``U-kM)w8hOCh9ukuLOlndz*@F zHEXjN-@z61*A*rcW5bE{N~Raz3vFTzpb9(ISrN!*F*BbvHqmVLRn4)E_{~v(!zy5t zBx|Ghd2iJz&>lrJ;^R|H9ME@PK=YEmf2Mu&U{G1Wbf%YCbLw$?hMKXtr)FMaEui_8 z{TFi%$-=wz${CDr(r?+q4%Jt#;Lpq&K66{F8vyXU#+v%0fG4s|Y;&7yV*pD)O=jw%W84o#r)($0?P`s}w z=#53SYg9Y;FMdAJ9^ySdAGAj)Sv@lkwIrNtmYNOpeAYRz+Q`1tOYY0*7hIo$o-?cyv9iJ_m11AR7O zh4Xokii(#-#bz(!;^Cha@)Lu9Quqt>>DkP}!5;jv7IQUin)B5Dlr-7dH zcMjTs>gkrJn6-&DdNDYJ8t00A$ZMYcG-0F>?kDI{80Ob0p zS7m$-5wcByGl;(uCGy26@>!xyKWR;!5nyJq8KwlUx(o^uaoj z$W@zsPv6IyQPi>bD;7$CO*&NH4Yj3p&&b6;+@I6Q&IJRuc$4W+jMdwG= z+lpWHQ1gzS31KchGYFiEuAs5Mc>dB1J9t^N1dQ*leE_#~1WnLh7yfe~NM{1p4?p}Q zOJ2QxnFgxSf#Tq~o@a&I-Tgh4w502soAmzu2fE&;>+75Jj=n$8b#r^8KEgB0z$I`E z<1XzD{(S8i{Paxw-8>*!X`{t)&0kR*R}@=KK>*k%nS(yK8avB#CpyJ6R__FXt^9Od z1+k)e$w=|NdiG4bhhw;7Sd$)J9FL}#R!ayz#;>zK%HC9mK?2oK0kaM&gf3tB;L-s?3=h4-0^^~cfd@hjlLuY}jrxqiFTiK;&kBBn zz5JHv1O&dSc-*j{LO*_Yc?M&1$Y&dDhkss}WP}#lP|NtE799ilyI0mY>lS!>`819J zZBAj?V)PMOYbL_jOW}RSMPDF7d>Kas39gj`VGgQ>aWQpZXGFkn4v2zou}KHo2%hOY z1psRn4Z>=;fqgCC0Q+h(whIFn7$gjgY%tUr5F?VNs79-q)w;RSLz>N`v0@el zLT^ad|4KQ)ZfBabZdA~Knq$?erZBQ5>0-^7&%giL%^LTZY zUc7vj4#z8*IoI~eD|%aO&%kfM#yi-+A1=+GpiIN|{f|GTGtKnNmoMcF2K@E)b-F*D z(#_2+T{i^!+jMt#pWeTJCx4&lnanJKb4S4EZ-(fOz`vvVj&yG%kdM?$eg-*sKD1u@ zP5zgm$F=@uzth>bUIVCq)TDf-&}UbB!DsONk9L&iTIvZ}*Vcy`hv-k|O|(kabBdYQ z&-6YPjX^Y+n!iv z8dyDF10S~Gx|o2N5Oyx#gb#~LfI`o>)WzPzustm9RYSLd4(^>6hxZwn@ze&v8o<$D zo0wuw+e5$g*#bUp)U4iT5p!7Fd>b%sb~ZtC(`N%dn*y8uM5Z)N4fvfnu;r1==yLte zI+Wp_vA^bB!7uP#!+%RBYWfX4>eLOO=EB=`xga+MkeMiM9qRB|1GV)U*aNM>h=Lvy@r1h0<$%9^1hN zDo3Efyc%#bQeK$NykdjuT*mvB2ZkP~mI%~jU~Il@WX?t2^J>3Oujjk;mUJzq$EF@7 z!|~vt+BOmJd)z-UY{_}u2M27`QFggdJ~_f4!n{(PR5Py2>({TPmhaxZ6?hH=1S?01 zV%+QEFNqDa}0dblnp0uWznt-L3rQ z>r8+X{nGl;puw>U{0aDbihZ}+)1}XNo#@1L1m!8ihjdXit?a}^7e#-ADYs@drhwIGQGxkuVBIOcTB=I7} zSn55kxz3-@A^Ua-UX7+N@p`&=rdi)J#WYX4cDy>u9G!0OB;PUmB5VRXz#z_Rv+GUl zZ+?BnMqNxOuw{9u+y}Iwtp#6Y-eQC{q+oDeLCDMkJxmi5 zhQZ}O#B^YbM3>67eO7mqhryaM)mt>cP&9TrbS?^i2LI`Nrr&1~#0vV&@4{f@0Qx2P z7ni-B%><+`pjYm!AGTysa~3fhoS{!VVlQ1{qV^3oK^*IbOO@afpbbPA^vGjb!d5ce zs5v$RgSt--wT+k-aIC_v!BTKHZVV#q=zm zI=5D0ff5;`%qE>(Ot)RXq6=HG)qG(EV%Y~Zew}-edE%I^j#u)V;VWqm5jLCdRAV%y z=^R82)n410;00$o=l#2PX(V7D_Is%@P)|!ar@Om5nf2?NoAfXL`fpULTdSSITzW=p z2l+h`>~{qJQSOg}e&->}bAgc6eeD5SJMSY0_-Y)|GkTvPo%L1mU;Y%+xPTiJcfN1b zc^plMPGLInF6(@p6DUFuzw^>+IRwU#bV!lye@a=E)t2bLceI*8qcHIBVXU z*Klt9-#qI%6MycSt^D1=ahLHg@(ltK{e4W_^B4}ur=l4K9K*Bd@1_@ngPq9hXEQu`7EPC1J4Z8gZ(%dj+u8MN+4JAC z>6&#r)A<|+ui)?B?|13H-JQ}zFAQREjsXTuD>4|Q#bmR^P1`WX@j{(BSMTM@6QMza z_tlyt0w@Duurr_o__lePCz?+zeF|5z08PI(kLUB0-v07S`u4luk+~1jSA}VtUx5n6 z#84Xu`dmAP+sGF|P2kVaAN83)KWi-=@?iRC1=0Svzd4Mq=$zNPv4M?iEc!|f+=>JJ z4IyW2rO^(PXe85n9^TjT+O~btK#D{uoxM8sz&A@yuEEinZ`yue7?<<$? zoiTT&Gm}UryXILJv z*{SIwnNz2qwf>^!F4euyWBI%08cYM-m^!!X&+oPV5d5{r^)L+k#qUKq*3q`Z#K;YZ zKoH=`nk7!^J%N;sJy-4K+AlFFgBzP3n^`qr0w&s;+?kmwr5NQMoDB1!8mE$?xUYD= zDHt5>IfxOqIXe)rzS|Ayr>Tg>DHVCZYgp^hnqT`f(Yj3cpm=Nza&i8O-?KIWsKRYajT!$$q^P>(HYUW8PtS=ba#KBe*Wpl^zC=w8KhG;(+vI{ zfp8#LG3ZD7*589X8x3MPE97&4lIuldZ}D@!T5FWv^7lx8zu~>juPVQUVY%}*tFpO4 zn-9sb&I_#ku=$rQ;dA&8)7Knl(R#u0P17Vfs~0!?2(ZHq*KRhL-=5DB(%_lPa3&^a( zEE!wAS$fFd&7bIf)`Sj&)MK>_{{Jp=>jtw;`o_Qvw#?!MGKJ^((4IabiMR}>f# zJY3FVzvFw&;I}OBVZcAQ1l#@|bBi4=<`#|MV?Gi7@wfjidow(XlZr)Yz!&hFNuhNK zqUk+y#a_DsK-5WpWxoQGS`Dp`=AvE`G{7OKVIwwlY}W%fA-(==!!JVrA^30Y)++@< z?$DpO0<-=urX6iz%f#Qujl8uFj?vT=7M2>R?P-02&3ag0p~cOwvro9$qnn`tMb4Pj z6tlrtdQRV-SMnYPKY&F}6-u_R&jbdzkTr(cRo#eEDv&YF1pR>w@a1kyCo<=^(@bzz zGwyt&*vzqcGT_9()lfMGH(=J_(ihk8Y2feY-)Af4-0J(3k4-{TgLx*HmQ<_ir@oRw z$q+x&{oC8S^y@Fbq}_fm08qG5CFxt>pU1SLTJ3f_fj;NX-&tcR)NS*i`BHk{mCb&+lclP=+i}y<1K^fY4Q57d?W)ph0 z#(g@PfAo(w*h~1sYFFI~AN1D_Y89?aaxY9##Dxldx&|8`hd*u{x%wL~J?Txq=feU& zxV+Q<#$YOL7Ed(=T+l7n2^Y{>8|~nKmJbrFhhVJ5B?xM&*f+jNtaTgu=e>s7J_Y_i%HGfZAp z>})l-Fg2Ctre$nk5L2l}*=FSqUN&jnI*N-c8lLNx3#V;p4DzeZ!`_X^SMt9bN>7K z`X(JHr`>L!rg=)|(>YD&Ded@xe?&!+d=9}Kj0sK+B+Eb`fj=z1S`wOo<(mXJP z-rmo|r%d1^rQ}dg=^HG36$Rw1Iqp$j2w0_nktALH-J_Ou)qCM@&d2nqK-TxG-a;cY z!?Vsd<%qK$z;i+0vul|?1L+0~+$yY8d*t=y+eB$uKXH9d_vwau#I@Ve3oj3cbUYqq zJ->T5ozpe-?H#STrutotb9%ksQ{RS0TIT>wA?p$c`c-JAX@B)5^(Q$Wn^*AP{29T# zctw97g67bj(Nxew_-zkXlGc;GE(>UG7s~KN1NXp8 zgZq)U^svxb9hhYBhX8)-*|MbglwNe2>Fhi}zrnxuE|xLf?olveTB9I?SO{EPJ{ zOpncv8Nck^G|Ycy+sBRNGVDVhi)D3xnVCU%XN~YZ{}wYR$P+;2-WC+FpI$YtnsoxmI0J{C9V!bbWK1&gU~d zV-LWz+wG(to})s)&2^k@eWrcGwT$W=hbOOlF>3u8a3`+4oZ(&)a_RmyKwF(f@v$H@ zOc3UlCa3DC1TK}RN87V3zEhx^{O|C$9Fo;hG;aClO!~>K21q(bDEiDb&-X%2(0guy z7PR&B=b85ZnR;_W-!sAeyJuJF_2DSB@^{%k4%DmHM@;YU?$h@-*Xf4dK9^JacE6Kn z(9IBmKkE=osQLIjYHiiSGxgqk$2AyxI|y#!j*|lXeUP_(Wt)Z&j2AC>E3Pd)!V%-7 zh9QeDL>LYBC|+fO4hY+9qGho+KFs2e_+U73gW~Z>9njZreFVJVR_RJWyqPxc)u+ab3dz|N0Dj9WFLW zfq{+m(7w@X3mB5c5O2^rh7pOQQX{~1KDbd0h(Td6&kNx1b6kduL>w1fnt3kifWweoDRS_ybDfNtq1s_SAeil*HcOPePC z+h1o5NDQ?GScg?-tEUOC!^k{)#r42V9t_63{!Gw6Q7wzb!6?twdyKI`KUcdkfz+AL zO7#9&>dWUsXdYJZ)7^2jxzkUfXJ9dSfPDVgItpt;>704Psg`THfm(z7!OzloJ5ag| z*3^c@?`$FQFyjK|rQ5H89h&8ClKtZ$zkIJRQY#-W(|KyC7ExiZy$gHl07m0%v8Wk9 zXW%}p(IhJPbgt6v!U%_7mlg2R%#00`&ek8ZT zIpo?O$!VZIm4zom%VQw$@zk;gmS$Dvsq+KYrNf{9KG3socY|odU)woIgO}8^p~YKg z{lcW+!iyU4v)m|+$IYxb#t2obWAhfQdvcU{Xy*-KsvXir#aVk>=DB()(94-O$nIJC zuAEUUf73p1j!TNdhG}U!0b|P_W?;Tf4u!3@rj4QLPuem~To_hQIqdTl zW6;OLhJlwy0MZ?HX&o+{_(>mJ06ay^r3bRUydy57!_9~t>zG-2en?kbQLzv&K3c$nKd9Y3!-L(3_!hGB8EC~pgTkB>{wFQg=wa{&> zONbonyO*UVfEE}*?N5q*vRUMP>Ret{dX3F{pDixLA|UI|p^sXJ_PO47K*e?8cV97( z*c!yR_E`*+565Y$X1QDJU);#6BUxR;?3Y?>o^nY0VMu><*rk8FKM@bfaLG(F8DOhj z(?Im5m=D8H0AQ3+e}Riqh&AK)Tt^tR*xrR8L&FmTIhlcNOmx!&0eHr+x4JKSPh$+c zC&2-zF6{rD3fB`1ZUfax>tbAnhvqERdU$Sos?nR>m=1$GF18pz>*u=ioO7sg1-i|R zXrzREp-0V9Rostad%J(QH#U#dp7Gz#2lqYItHPBpQ5xxA_w+bz!VI9mx$<80&p`T> z`=PI1C)E@*pDEg>msBs9Fx`_TalcDHp6=35_xCB6IURO;iepY^j)C@_DUPe-f%Kfy zYl8n_7`;*TNbS1;9^+tS>239x1sBE@fPw?T(&NxZ4}7Np6ESZEzc+Ksl(!)t#f_Vu z)$N5#6Az+`W@Tzvo>?yW_X?=dA8;Mk^zppS_3=Kg&qC{$7=anZRy<%& zVA20A_zyBT5&i~#6J>*czh__ZW>6_w`=yeA8ULnkwr}FLn0m$G_AYQ?@7TZ8{a*I8 zhcG&?#W>WEiks?%q+<;o{;kHawnK0uZL`?Nv0P8R%R%FH#`#eWYYi?89x^94RM8B_ zgKhZKT%p!ajL<@02sC5lSZ4+H9BieVNB$JsPxmYmroC@w-unapuRvfZ{ncTgemPI+ zmhR7FxSZE8Slo_IP+0h081y5xXA`vr6YmjV?|mq2?%9D7z~4cCORLyGjZgD~I5%o@ z&|TKomTUvO@`@DSs$JZSycL5pQRLI~Cb zUP3DHAVeMtatklpliqtVUWVLLK6n8bi~A6J;)0DT=okjgq{47$9kH6PK%TxC_(OpI zumBTWtVD{GSee?t{)@&#sKO-x4PorvSok}49!ti90(})1xS`5(2u@pRptUWUDgVw3n8o>= z>lV%4z@StD(CPqJPgu^QzJDaA`;M&c$J068)8D1cVu}M9>QZ(9%@h>)u-KK}GuSy0 zMZa0iA1HX#hE>?{0DVA$zn{vLrgP~y_K;lyK9(-Z%l!%sR=vEjf}XEZtQQhvmcH>l ziQ8V9>GvRzhSf~!`If7FNBDoc-;uv+UwCap{o4CHr-C!BGl(u2(>yx3vgHFIw#Xkp zmt*KRi~FMhej?!Rc6-USxNfA+y3s(Ol{v89a-NyiW9Sn^4MOuIG>v=QtP`1as%?!c zw<%*Tjr8nl+({2Oo)Y##nz@no7#aGItH0Ko1}|TFhfP|T75$yfr?W*lrggo#8%;`~ zktv8|;dv4K+xGV~@~zq1r|5}c5Y5u#axlYDhA(W+y|OiB6+hOYTb1NN_&*`*jmQ1S z;M1#w7eE&Q#zHc4M(33zi9Kc@0H=e0G<^jI3up}RkD`K(kFX7>g-&`1Y$qv7gP=pa zwDX1PE#bcuScS+kiVetDussm#_GZD<9pnao(`g?&)Z-!4^Kw69X8U0VD4#$p)Z%&&2qhHR?Epz7?`S z0GJgNu2T#Gf*zt?1913Yh{59C?{*4#-ZR#PpVO_Z?x_3Z)+<;@UO^boOfvf~sh%%) zqx|N@12o8i#S*X-GI&O`8yATXj^oTjfTls#4sYUT#r!bEmOCOaU`IkTNfWD&Q8=P94S?;07+!>1|!7m=?yl6b$}@6!G}rC+9*0Fxx{c?N+?uHp|C8G#ck z8N4ss8qBx=J%h6-8>SK@q_N3+rfWW@I7IBcI`b7!7_nA3qDw z7!1#52V-IIQNMheZ7&SF119#62;rny$=s30Oqc$ivELULpu!Cc-Gct=9z0S;d>XJm zq8r=m+Z@<@LI=YoU?5%*{tW!2-wJ=d@9SU-a!dB%20-y~sMfZca#ho?dFBNHMb@w2 zzy55%AGShYA8wZZq<%l7qhRqH?=WafsO)iHqi5ZdNqb{tpJtQ%zY7FlBkiNT0D9te|bfiV1D?#FbX@1F^d zw?)^@1c<_#7RLsrYa3*;f?GD~mn=SgaE9Cy^i-1@%Mxb4l6&x~ zd&2{U0hb@ESb9fa!un%Bc##H=Z5f8ee!6V=hLpTE7}JbuF69E7aKo@4GjMzeQ26wW zFP-)H>v#cc;nx=U1N;XuF$I48Z3dZxaLqRB`wuCkzoRgN$3VV%U9Aq5&FIp`C%}HI zd{_tfq3E}J0^6ZHaN>6A@W5Xo8vMBFS>CRoR=Vjt2&~!}uxJhhQ>}i$@Z6YDg|zoM zdRwcW7#y_c3c%9VhuGHfhx+JfSD>)DrVS%x-5$m<{eg`B{dr1nr#Ve@Prye;z|R># z3>vI}x4v5L$^hFRqO=+WqZYu8d9(ipMQHiDLFWtFh%LcDmb^Hj6pbINrlU7&uG(+T zy`;I64to52pCP>-hxB|miVuXAOWr4XhnA!r!O#@5(#&cO9c~-5Bt4`(E3l~#BmJC6 z6Nc_qb6E_{%h0Qi3h;SeSfZ%|TTh}UMW=i=>ic;ZVbLe#5 z&y>yswvJ$fxa%aewKD?WQ~T7I@Dp9rWnO4Rsw{;KIh1SPkdFT4F@qRz-2u4mDLbS~Ho&F*?TZQKN#7 z4hC!v{YOmOgTU_~<#}R*kT1=Pza!=1J%?k?X8IWB5PDdygD<- zCHsOQ2e)?+SzkJR)gJJB5cX;DmFkl%&j<>SD}H7Ie<8Sfu&#Zc|5pV57vq?AL)!ym z?sV%w_48TfCE5Gy_c{4AAf>=Qp9yE__3mcl@*3*Ep{DSXa z-6sVpzjK~uHBC_c1LRsC2reV18gQ1ZtMCumFPPK?RDqQOQLocNB)&*uU+0-3~P_a2qdl{=Nx;ER%?t?W1(3O zAvA}-@&$c%^MF2eO&4KUROhyi`+!y})jx(QG{5A!Z2|3zd_D{QxG?a?55UJfewCx% zWitad4X^=X__MI2h-3t4X5hsQ`?cD^qbS(2VECAF;o}8{N1(ET{s#DS1iwSy2Ehjs zHcTa+y(Qvxv)}>IT~H)mPV`|7yGdWDa~&q=*Q`RcQ!X|dKYJW-p^L9GUn}pBo^Zd> zg;8f}?ex*l2Qo_q8dd{!quZ=aFfNsWq06Ba%qn=U3{#*}$3X|2jp|%W8zduHJZV4JRuT@T>g+2-B zs?q!w>>TudSg*iLith|^PK2O+L)+()Z|OeJ<>=MM0QZDNXD5JoxmSd+*Io6=buKpd zttPR?82u8PhN~{C$lCLQb0-1_-Wf(Pjh+Q=8r~;<@N+y1pRgFapARi<@Q2Ih%8)Zd za}(l*^6+zO)CBZzw9$zkhZ`ojOaazmZ!_o#z<;bqZ~^{H=<{y}KAV$#NAL&WLl|gS z3`~C?F2=F~9X=$uM`*v1G=451;VJdS{ue%_BOmwq0&IU$5BzDi1*~;&Fb7>%NQl?* z6qwmVEV^6TMg|ao-#I%}xU&&9v)hgv7r!Au)BjQcsFn*h(}lRS`bG)E+NOA~ z>56ha6-@WE>`c#R(>&L}p#q^6?ZWB}F>Va8RzT3-r3M9E-32toU{!&lXJREH?UkDk zf(?TBRMDGVP3iGG&s`y|2=*`NdPd+EaD#yy;#5QRK7KdCGcWw178+tql}-X|hGvcb ziE|H~cP#Wtxt8z>a!**RUhlfIdXB&oG>_H`V*&R#2&}Uyd1f538uGtWSCa@Oec7i& z^~u^Sy@zmL=pF<;c6x=jCoJ?NDy~{L&xZD{=C~Xjo%^vXIBr1eW4!H2;K$E!KI9t z9r*DcMDSMuFF?Nu|6XK|O9yqj!Unj?51)p$FHOyl0yF+*V24kP`=R$HGRj;XqAiYw z>%r#$2-i7Q+F>`BsvW=AK-FSW3kM97f|uj15DEjPM?>&6-AlRIM2Hs*LqHHtlEx%Xjnb>du2y|EKJT@74d@wUw9+i__<;_R>`f%LE zU{dq296Pg*^|RTgfYXr%{h8!9q}>4G0Z{@{6-o(etoB^u^uj~+2))-aFK7XKB86UP z33}*xjVdssliJ#1Te>fy2j|{A&$XtWmVkW$1BWPtg;<|Apw`+Kp@E&(dU2v3`&`GM z#*2%5M5`DTXKRYBU)I8NE^R+}z+1p;fQv?SN$4Y<`|%;fUGp%W}(azn`03C*$UeNQIjPs7J6aD7zDSE5}s`xop9x;6dO$WWy z1`L9~HTbojbY5Z$F~YR6!%?HLQw22soC*9{^4QV0%sQy&CHH!ZS!yBP*8>T{VLkjq zx3R@8H3;wOTI(+a;zG;W3(#tW71Yt=+@Ja1>PRN6ARAMH78rs-qw|3NF4n-<6^y`) zx%PHN_o%KAG~Df=cT3Q^Kf^e0c6RLNqS=ewtLQD|em{sFz1Z7p$@fOQXyWk6ZiZWn zKiLhj|An7skrrd9|0%Ml9P?}xXy!TXcNMM8b?#FDt;Kvozu+R*@kxlsF9-H7KyCcwr|`+|ttNIw?+ZKN zx(z*4)byfPM^kLSaUB5m-HpEB!0cZME1;QV1%ipafnFUQ7G~QffO*e@Nl^E_Es;#7 z&3B4{nY9XGermjy1JBY(R~`tc1kYVHQ%p1Mf2Qli-*o>(*F?|exg^L?YZ{@COLI5> zXhm!9dECps#5U-BYn*)T>D)c#xF7giV~1|2%`qruj8Nk^vp7W>x_&j>Itbl2JVp(# zcYpL)!#CFR?S0$7V3B+7TWJAX0U0{b*V@tjaWxm!je|S@+un`<&BOErb^^2>)y!~V zQedv$=xeJRc$-HzGO+gMGN*AEX^Y00I} z{4B!zEF;DBU-xe)@_)5gD8s)vpw0?(=QF4)#NCGs;64OcMV;EZjRC<86JG_r z3<;JN*TdZB#wBnG@)7hs(4p#n3rkH{kLv=qh4nz+V0{}NT=BIS{z)JF?33}c-s@`w z4{zPX)Acm!k0BX7Rjf+@u@tF!1vdXfp;;eS<+d+0!7I!#_${t^-n=&K?3^ z*31I>BA!$H9fLEUt9vr&`W!$Dng+@v(;W_BBv5lK{Cfxbn~tH&09`?Vn;MWRKyNvc zSt(T)81kgXFW}424oZt!imY^=#y-^=hWCs5tLQ=Mk1JpDVD(&w{@QG)!fBc(0&GbG z)rr?-yD<2o3Mpy^DxB?s`EHvkbiLLa_BJ?-OM^@2nbvE2b&!7L7I)VRSb3nr9=l#G zd*@}qZuZ(HwO+R0_^`9$$Jr8oc}x6^!#6+Pl7{ixRkyLtcQ7^~2su^5v0<@}pmj4g z0xRKLw7ARx+ga%%g!^G@f43Od_Zsjw_j?5V-4H$r#PFn!!6SE;`osrL`5dj)5rwC2 zhF|2|ukndf3zvEknGnp30q5XYnn^W7T3;%lyfJRZw3^)6w-m_8GhUl*mgrtC3ChsT zpu=xX`w$0Sb{O|x=$*8#v+y*xX!_7V$KMRr6o6hretL#M&_Q5(`MClb+F~3B+2aN7 zx`=f@hfMGaf>rYg-e)n7c7C0E8*;CE^A?LmZefM+>f)HJEcLlyEf&;@__;Hk(fZP6FwQ1;&Ekw%EaQSb zF~`S07oG1jz>i<8CdXzbIVkR14y#BD>9sD^J zimw=?y2*FbrRPRnHC~MN>mbre7hqdlEj633x(9*9_8%PT3vm|%2d@#3$y2RIt9yI} zJCcHfD4G@qBj}e?kMxJ@fFCrFP@D`y2yiy&cjT>r)xRS&IgjYKT^Jb08#X-BG-dni z_S!Ak&to-sQ}tvYjyb`ot6V1?;QT&@-e_Q%bg?rHVgxw03~<95FvJ?CEmWrE3LEcR zA9!_%i)$QS&lY#?W(XP?LV@F6v3LCLm^MN5bfbR~^#24V`jjK1C?in&rZ4$}kAn|_ zpr0BWT^H$_EyI;AYdHGpIj-#6RJ) zF5u8(ubcnQhND^fx&UA`M_imhI?F>kz_HymNJ7|80g6@EEd~^m!Z1zPH6w^h92JAE z(f{wWF%wr%>(d4|tOf}dmzZqmH*8G^0Rt9uD>-?&9H!X2WUHG|+e;Gm&k*;5HvieT z#172LdaQ(E?n6g?sN}D^IlOTJTg!{@mr~k&>lH#@4YXQffR+Qgk2B5`R*#YMG1L~? z&Kg^U8?M0VyYZj70=?x%8PNVVuNFfY1{#7f<>{<@V({1;bSir^FRZn~4ii?|-VHEz zJq$?4nc5%h>YBV=5MD}JQ`fitG<|{9Y02@b9nH;87$0)g%ck~3yZaNl;)BO7+#0%e z;R*m4wb&6MRt_J8$YSJI@ZAg^R#x!|5Do%+1o)fg2A^~0lNog3Hg4~R9~sOS_&^8) zSFP^BfL~km_z4ZUs8t|S#4ZC22{xPXw;k3{ArLce#t*@yu|K=UuV>G5NfW`v;XBC1 z;K5*23J?tMLOr8J;PCE9vy_a1-3`1!g+XX%gxVI1D{WZ_dTzKF>K0EIKp)6zFVB;I z7kYOZ2~6{x1}sMMc!sWZ|LU2+YnOxKB*6f3w}$w4Py;UXT;XJ~SVO#bUXNaM(9-wy zd|s`q_9J>~^I@oa>&~_l5gr2IW13ldmMRSOkEWt-ce*{$b1VdZ(qQrJLNOtOK>$O_ zt%hhgu&QXAES)+j*lU7wDqg6p9jt4xpHYmuuJyLPJs>*NYXtn8_U!fp>yg!-l9fL4 zukovp`ZotZtN@N5K`0md+9Gk=XJN@9_=xD9N>BJAoP#GngG=oH#IxYz1KRxj35fqt zj?nqxMtbX5@k`geS{S>v&Y?-;BZL! z_BM^vB&!(vIY%5d=sV~<&QfO+Ld;mru{9{T0kebm$yp6MG*IF)^RGasD-#&J9vId~ zp3hdd9H3bJ!(m&!U7?*i(5Q(k53&%1{~F4yIYBe{wBbz=uD)lhPG3i_7aA=2?;QJ$ zTZ(PHgGwN*5OK(B37beFNWFPchaPi-e9MJ@50-a*)3sO6qxVXHaqToVldj@UtAU{=~jZu{rPwrGSUtbG`m1toVG`Z@>3x(0}}VeA23q z&jUiaKfdstzdi6ed{kukMaBs}W+6l&sLNK^nekNvK{LkSfORPr?Nk6m6m{K${~_q( zG^c@n4rIQS)ty179^fIXcGsaq`yBRZe|1QA?{89>=Z)b~=y@D7o3h0^Fia@^3O^4> zz^4Z^Q0nQ$iZgzy^g zbsEnH(>r*RI8LyF()XCY^k?ZjUl3>?9|V5UT;c#fwsr8cCz~_$UiFpW|E2bXk2>g! z#RH$ZH#~9|J`26@ND%nst>KZVzwAI|Gw9xy&0Yq=GdVGyCxsvMW>>+mjMa+a#^0mP zjE(-x!p*IGbzfZIznL0@IGZU>_b0jhT{S*vb7(&5Ck_EH3>1_pKmV2?kAV~1R5IWBdN3u;z% zNG%&w*4|Zv3wc2UJZ$-BfD`?bYe}`o=lN{+v-DDO$qrE8FL&D17%UbKi(~hDo1zUs z#h~%jKpfK3T41agcAnV!o8`G`iC?2wxo5tn8V6d~4u8sd*^xR%8c6ZZ_&{OPxr4;# z)O#s0aLu#M{g}S?=VP7!3;7#-Hg@o%^1{zZ8y5R~eRzMwTky5dfrp>KZxzC$!CCybfjqNad0}N6WkRSAxV= zJp(f|)KW;1>SFU{boocMus%5eZ;LqAKeW#FyQ<`}Zoih@cvu_$= ztQ!gRapeiw^98Mq*F^I`DP>o~A(QN|)hwQ9?ct82=|!c3U0$ueJ`$`|T=@*P1x>GY z3rJ)7bNhS^`h9L7_!(h;b%6f%ANT^R;EN^t+f)o5JO@7$!`o@W1AZ7z~}WC?Sprg?hS~zQEsN z#MmzfZ?>A0`kw5QBc7}l&|vPR!T`AnK99uqaKJ6Q>)>}yNiVDx*=rK}Z&a+YIVB(VxeS@Gt2De?E-)3;e*N_4#t8gHL<>n@AEK2m!xb z7+dAWt<#=9qYBbsL)EMd_R)CE7}v0F#$G|y)uq9v*%4N^)@^hTH&_2|Fx5|v@dP7o z#vLGKr)Gj18!0-p1UU3n45Dt9VE3`bs{7_f0w;{!!)1FscZ&%+K5dwIh5lM8v0Az| z*9|L!bF7EKvo-^o9wh%%V@eSgR0m9R>y^a55Ss(3-iHqOa+1}*wxN?9red|gpiqlAC=DKFy5SXAnxC89G5|D?F3!pu!3waF!O2}7 zqiI5by#t3Kz!&{;!}jysWc_t9X5CNE3I!2u^jOB4L--pTsQPuyfUCp6veQs=z@<5h z=ZC|V=Xz`l;9!6yRA>iwK<*%m#z}#j^)LsxNo~^5!M?@1jG>*1zB#tDqEYviL^R-fzj<%z9az2?+8Ywk+GDR7@2>{c z)uA__zUD2CR5hwqiS-24co#;P35ywP-f+QZ=&ypIkGvAo_;t+tOMT$W00zD;s{Z^w z_&6y(^7GqN4!%mu9@_#x<>gPNcmxi9`ZgaKGh8^RxOtl~zyda%nRl==_`?Ef6~YU% z+9;l~qjc=Ui=*3c!N1rP?N>AHjib=q936OzJv(!au?GWgP=v0aP|4A9Z{VpnC5XPP zcv<9;TwCv0z8ZqVk?YX|Djq0EVd!^3;0h>UEsY@Nzl+t6wc?P;1~D)It;G`3si8wV zPxflB_Bw%3!>|t+97Q)+wVS=ZwRVjg&^NO!G2nym=QCpcM_(T_-t+>Uru-Wg^An-n zCkv~Vt_sKbZI{#sr_&3pioKjEs}c6DGyd5z@gYb2bBp!Q@B@F5g@Qk6uQzP*E4{Sd z1R@ZB;}R=GU>lsPSC$Z4tc5KbUz#Cq4m$=@ilA;p>S5Uo9D8Oif`*84H%C8fwKo{h zyM*Wq8x$Aq#^v4{9rV0cX>{4d9<-eL?L0XQoP&-h-D&}K|9(_kWslX-P+0T#bg1F z05Kov}#F%Qeq5CtIt)IaVhb@9opfUZqeg4we^&hE! z`D|eeKwtlZpPR~OZU7ge!LOM#m<2&RsKEmwT`)drrUk3mm;0?V#PHBfJ)U;wum8l`7zcw z*F1ViTQ@L}JZKirF%=rP5owAk2Z@1W%|I8~PbqZ@*E1ekI2>rVF1hX}(|8 z!2Uu$|6{@Z(&yuRi!HbuHkv1Z`q*)B`YJ2Kg9`#B-OQ6EW41gtwkLVlMDJ;TRtZg{mdbT=jgXs`R zjt@*QZk4T;N-NjRTQ-$0;$(osg}I7xEOj22jj(AiU)C<4V$y$U2K^sEzx4S6mBFWP z{U_DUpIo%?*{iT4<>frf3fBf=c%UTFwK3Q$t|68lfr&dgEWmIPQSdocTfiFD&){hl zH6QLxee*=vk`t^%))_uNE=F7c3~L`AsR(Rj3fua}t@9t4Vcg8Do!Ov;>+Q+|MZ7ek zxX6LexjYy9#07FQ2!i1 ze`a0zlLPm^!;ixhAC|#GO2DV62tHj)ANM9!6I~_%TQTF4qJMhs&`xgvgGXM(NWJaT zAFUrehzI`(8~fmc@QizW>;Nu10S}iI`&ho*xW^nqOz>E|U(lBoe~RKB`e3N(*UaPVsvSONn+LQweytMxHlP0h{{Oh2rKtZy3%}Lp z>s5LS0P*`T=mYsr9YOefx!U2UzVv`-V6{7bZ9;#=59;+vp~G)JbntIK;x8Yu{{LgI zPhu_0vhzNC?bFu!6_+5Wz@ z);?$7bMK3Y%*tX@ad*Ud=iJ?_y_&th-*eMGw0wV9hCYP55cj(`ed>9QPj(?Z>-oK6 zJUl~=!Us*ulVZcb$37a4<M^#s|OMK0IrCmb`@#*=CPxC1}#qaRFJm80R zXumt#`0iZ=!6)6n?;3*P175|ku7qco+tM>hr+fF@E(eS1qH(@HFb{&~EL&UE z67t;d>>2lL6^H!AJ$@up)suYLsY`e|d^XLX`5!v$_9Swg`1mYKfZeVe!7yZVvkSf1a{yDTaX_~YT6XaCa{3`o-+Z;*?pR{|_+mdxUdLch z&`)AhP^7{jK56XG4()kvWw{Mhwy^Kg1;6Jqf_i?3d`*{q>%Gh23g8#Pf8(jJKY`2X zh2PDsR_th3OkfL{o4uAK=u>AR{%4w_vBpeS!pL?U_D@`4thrBNtaIMlJ<#6L+AG$@ zaVLJ4#mKGBeBa4U+_sUwli8k+`J627YP;(1zbPCt=!a*3*9X?!unSujsB!>hxgG-E z2lhQ-w>ukevP0I#8Fu&GSA(7V>>cS~@o50Xm_f29aj*j&#>V6J z<;yOferrzsch`D6A&<^c6 zYhAj#*?r6JQFLKHuUJk%)44}HE7l5!Qz!CUU9TN0A2Yyd`-!sqk1Z%oXg!o<8eu#Yz4tw8ncqiCD z@c^Y5M$b6^hjwVsTchsjH4hn}lI~>S+ql;7H=i_j3!H8zZZoOBr->KT1MW-k#|(JU z;m^jg-Z$dOp(h!5bEp!A+SQF>5nBgaF}E9Cm$rcJ@TR^baF*t4r2fQ=k)`{d0)>`n zOE$MfcF>!og-vVrTlXvEIyK5&g8I02+7Ek-J!Fd*ALn=eOnPem9PnhEJ)mHJci;x<6H$Q_43 zgOPA2l+xAUeI`Ka${?Fy+QXiAs!#~^KvxZMC>1^?nHCV1NqZHfVsG5{7#)Z@pLe+! z*7m$jT28$veSKxunLa`n^_}eSL+82vCn(0GhauA#pTn4XEridELp-)LmwDn|Oats0 zV4Ext#h`86zw|v*9vNTfIqy=RC%&^d`n>9R>4+YD?Bk?fG6x!4VL9e{?5mA&Fz21? zY4AE$Iq>_>xCeH7`syUIqZXbO{NZZmKgnQTIE9EGCKAE{O|_f}c528=g1zSAIUD zT{PXV8RzfN4(&N^=^pT25b}hu{wrKFlf-oPc7Ui7ME_kxyLXueE6mcpRoHem#Cu6_ zI;a&$y_RzigRR1;^tr+BdeT3R>)p5g)V4x04ot{(f{rl0(lxknXuD#n&M;n~{&<`z zp3;99_c0*0xF2`Hmm8L60sK|s`z)fI4rCa5AmrNNLREec)5UX;=_P9YfTTSVnXX^j(>tG#krBiZy-9p&iQCIrM!=hYWuAkQEaovp{Jk>?pJh8RX=^- z&SA@c4r~`djlrN?WHGw-Bn^v$UQse9Kli1-bKJq`?y+W3JV>BVMIUH?2&TtERer`z^VD{~q*ijXbroTFh1JfAot#E}#0; z$Cj~?MF}do=Bs7}-Rx>i;HN6Y9Dp%{$exxvbmh0? zn>_i@rW05u0L5+Kvmh)^0&qHXX)A|-j`c}&vt!6%ohF%>hAN0U? zwzn2Pm|f0EzV)^XJ1}V<{?;z^c`^Ho<(IABpZ)Y-FWl%7MZXNe4+Yf;q<%pjpIBRILgghu19S#N+EZlExDI;ugGJ zgIleERL^=y#0wS1hbMfeyu4uMPC}IQ-!hznjMTJB~5YNJ`gjr$bAr@aKC3dnBSX z=nsAA8-Z}FJW8O;U67>8Kn44 zTM6FOC<2N)=QcW*EY&XbsrFH))F&B01;As_cvmH$Ise3Ut~3azZygNoyKdImX6d)? zNwC7>&1Nn4@4XBC+4#aB_ujiBfAfF%@8m20{QsH#$A9@hqf%TVmd4yk@&dXM0rzMrJXtwV4t zApcCD(Stzv28Ep-#?0lA4u<9t#V>t4Sdw+TuC=ZPcP0^FMlvJ!kE!5UvcjHE`m^3> zL3;bVP%I!GwU2$I2Qv4-!+P@VVa1eJUL?Ith3!vY3J1Sl62DCfY|m+Os87AurQZ{6 zemO_ZdXNSh?erVx$M@Q7hP~30-@rD3A9*VfAI>!nIe?GDyk52XZgD#m?p=a^4*o_j zL(|$j%_IDo@aPfri3|z;coXUwI@hm&tFQYg;Kc3P z`p~)7sV4I~*P&?a=p1}_N7r~=o8%&Cm5PaJY1k01n00bIk@M5XaI|x!iO{5t>eAP~ z`pfb+{`!Ag|GWSGe{u~rE`#Z`>LTCnFJo7M(*4ngOoXnG*)RO!FUZBkxh&@kSuR(W z48osDz&F=!Rx5SuV;_G-{?Hfy2-jo8!!OKt8V4lube|BVN{E!5w1qlVk$3Bjq|tUEj0coOJ4!ao1rdI4(-sM=Z34<5eTh8TJ?__LYRsJ)-}f9k?Az&QCB18 z5_-;0ZPXaoXI@w8RnnqR8q^}LBlW9OPB_ebOt&WJC`YP!)HTehsr{Y##y7Q&ELH6^ z|J8R&827-K@<91e*gG#2j(lwPRuO&waTmhwx#@N-hvlj<-1JO9xHV{2-3 zij;AwOldN+e86s3O7-~cR4y;)vb>l}OE_U>vsq(b0}VBa-yIyCFBjMcQyTFIJw^w1 z^j`9a`O-R-P91id{Knw*NCKVQW&BNNJVPxx4~%peWSYK9_i>AT{97$Jl=sa~Kq9I^0gTuiCqzT}gfotMKf9qT%zF4oLtk)Y^ zE_fTF0-~zwFqsy5hyiLntjRxuJAs&uK4`J{&-brj7qtzmWeeLY*f3z|enOwW9sb%!Rp4B;8T+%)!#`M%Ipk9eVboWm=so!L zU1sr?ABKh=+M)ffZF-NR1K0slxQ-VFQogk>L02K?Ya4@=^%>Ljt}2Lv0wSYp!?J)>X*e3`v*PR_vqn$t}VERySq;z0h(INdDvIstg?u)n19x<^j} ze>bheId1bgKI36Z#ZL>`j@Z#|h6>%8z0~k=C zz(YXz#*7{mFY4eWQ_ksKXObd8lPD}fh_4_{RcyNwk*ebWb+ z3SRIY^o8fuW3~g4<$Zp6z72c_tHItD75TTnbH8}jquD|R?Y3K=d=9;@eGCqG=C^R@mpBym<{00FE774H+Vk6ReIl+w z#FfOIFb;GMlfnS3ozeBjp_i^D?$}@luve~dK5cOp$8nvxp8BaW$*YZVNqcs-j><_L zi_|O3Ezz&n-p7?s*r((5@611ODGll2K7erXQ$O{$!uk2>cG1a|-(H>%2G}(r@MQ6o+zb&_ufSw>vM-z}bhZPTq zv)4~mBDk$1;FuKT(vlbk2=PE@LRvj|JX8#}P@sxKHG1*z7xuJZ9A7S1vRuq%xm?P6 zy;2BqJ`Y1ZnPBfmGkSb{6yQUFpFD(pF>&DZLTGYBzh9^)Qe0kcWC7cJma7$ty+$GJ z)DTV+804w690eSLx*#L{P=$2)qF+AEmlt2b?)xV2r|-0eIbz;AI{;d19$w~|-(Nu#MhYx|@Iq+LTz5n*#jlCOxw+N=;SYvZ(BWAZ- z^}*qv{L$~NxA|xfijR|@pkJ2N0RI$A;p%m0hxVK{)fWeBt`Vg{)*uv2H-mwbS6v+D z20qt;BF%xl)}W;@EM4mrN=Y4EI0#T)Uk8@`E>yp@(H3FFXHx0ARjtSW=-Xj0xNB>i zVa4sY-;lT8epBwg_pZGD`giId`>{V<|K1<^kpKC=5s-=ZyA03~NRiUWhtLI5$ww#E|kT#moF8Ex|j=~gcPai*)cfS1%X*MhQ+~fH;Qh-lzoNifuSF)M$s2P`RPdX5EKGWPrYZ=+z{u!*x|VLmo_dnv#XObjRQF1? z-V2xDythT$AVK*3z@IJI4xT85dJ3wr-%M=bhXsMh;1y2u2`wHx#_a$L?plJQ9ac=o zZ*ro*p-?mQ+iROx3mCTw{N-{X%f&+9{H?&(3KmjtCVD8COFDLPGV4na9RA!hdTg5w zax^g?R;vh`OB}F;%yGz8_+F#JJX|zEAT^lW`Dv%a?5nCmFV|#(6CuVy7M8P^AMVQ> z!M&(3XWpXbPfiaGD>u8sDj*pM!_S>(+?~Q8i{rh+2op;_o`m=xb&$sAW zV&7z21(rI}sfL;EUoMt%9{N_6m3=W!?iV+Rr@E*1$;jUEny zc>zE#mkaz}q0o(Hjm4=EumL(aP%7+KsV}KG>EPpFX<;^N^JTSK%4)SxyUX7GM*ZL# z+G<+cD>(ewSDqY)1Z;gXw#stTS8hmix;%PG`yAS3qEAuR+BuJ&D?1YCygWbK0%8K` z0Nb$DSEMlqaNY=iLYr>|KS7>=@37zcZnpc=+ys9;?Rr36Rl?IBjR8I~m-N-3U)t}~ zhu{_N(Fw0gtV!o0bZbT^^BYzhFyQ{(cjd4Bmw!e6 z%3uAjD#{dnr#}hI+&9Ugb4$)I&Y-X7a(VVhHtU7z+~vi&oLsx60^GK1JVDl*r5w#B zM6~9Qx}3D%Wv$nCm9QZ7uYc{!)$O-mlh6E~AC%wwqko_URu+`bsy6mv?v(T4`VyvE ze$!9ZCYrQr{wu(tU;)yG3Ya(|4F0HL@6QYRw8JG9gnpL}aSevaE6pj|EfbhBu%ZsJ zu)`E{sZM^>RS$bhT(E**eqcS|a{<1G`Ft+x6~Lb0;u+{r!3}6lP%kWfWmFq&*L84r zcPPb*Lve=|DO%j!-Q6kB7B5~LN^vW$!KHYS1b26b0Lhp8dB3%0t^A$LHFIR|efA;4 zGQgk7MRW8eXJI}5xxqt@{Vp7sn`?~sDs2CLMXo-;t)Ah(kCJ z?0i6IIqajmZg-H|f4`hyo4+zuw9Yhh<2M&}a=|Pk6$@M_=Wg21_@>&U1anz&gRh|% znqfKQuY(x9-s{KhKL3}-m*@qU>hc`DHIlEFuNu+B1hPQ1dIAU%5g+PI4`$q`d1|H^ zPsLE;92on)o~U!a8P(J(EtX&CgR7+3MzQjg!T*4WR!sZp_#xZ$k#yDs4?2^6*{q<1 zcMRUaIGZgq|Ev~nQFD5p&t0C5fH$qXaX=W}V%Q@K%$ewu{ibEDyP#l}v}UVd;Q{3d zb1G#>-fHNH4@0mKp7MYd7v>Ir2q5*nJmilt7iSpY^B>NSoD1HS-<%kh^PKi6k-4oI zGBe3MZ-$!@8$LSMA(pqZ3WRp(LQkb$Iv4U*>Kr3zr9$ZoVEsuMMXb&8)fm^Nutqb; zNa{$^`8ZkF3(HAT$V)ovKG_80T|slibkmB?%-ZJ1x$Wo_W_j*HugZ}r`bi`}!D24^ zk0v3JD5T%@Ah`vKHs^@xhk?}lEjL853`XT#^8$KN0?^FDGnJQTMA z*Q;?LSt>!uba`wV&4fYt+NA?NqIsx*RYxr_sVUN{?xhgwt2xs*+Mm529}`~;>!8s} zA!)Pu7f(Y5s5jWhB@a_tyIxu}r(gK$#4m%Ilp@CjCl`^xi@x-U?BfJP>uvb};&4D&*-A%B=M$)8fqfPrp-CG6EH~+TfGNiD5)Y$J;uv+|4Ic z{OGc9$!^H>BN7Lc6Zmr+{UAxFaC_5MmnOQ&bRbqaQ?pQ_Ej({qg3lI zNM6IVL6|7lxvf+F_aUtJo3EjqJQW*hjY?YFZ@6Jwn$qE)_d>O1a&~3kTl;o#q6wD& zArKRP!a?3=>(h_9YR0mAPtz}#z^e0~4l&j)#rwAs0KH;8r|4k4xNlS`!YtofiT{}Z&#NfR7hpuk*Io5>QQr{4aTw2)#a9i z1NHa#kZwa&yGFKp^u^4ONy}1ER^2r1KgeMl`eMYxJq%J=)Agl2K~6xX-6J6y&gw^9 z*zGXc*W-pV$kZAz4xE@sR{TL8_>L5juS2ET$^#v@oLcMKOq@x@BXvX zJ7-fJK05z-V-WWwCg_NvDQbzJFi&S1*B&Fx(Kri}48LzcW7_&Hc27)T@!c`uOOzQo z)O3owwKtnyig!XPG$bg$66AsTeg9v{rV z`5ZKT@xMeb!-r`q^Or)OB<@#D^oN&4592xMdksQ1i5g8Z5PGjdfn-{_%CX1vncWYx zo{EFC@_9Vk>(=w=lk(>_Zh{b#p-^0}_#bSjLLCwnIZxJ_pXHgWFy2up~i$`@fG&Fi4J6b-r@T012dg}NhVxa|Tx2_Hl+b z?VH-YoS+lRN6SzhVc}dOecPe2ZKVs}wL9Nfyr_SD{*S?Q3nZexcW%hM zEKH`?SMD|4Xsg88W5eL_5s#?zx%8;^?A2%-lbS>7)hFJk@yRFrBywV)NuFgMk6)%5 zEsbqNnUbjY8C}qeV6RbMb}m7J;JFoIVjeaAR7$O0v;;Os zge=qtd5UYjp57@&^rTqwY1Le4mx!V~fqL$3o=U<_S%~RqLby230}<6)P{VFf>^W{} zCk(i^ymyw#by7ixEWJk7fz5`>s~pW2r`Ot3YIG4=q6x;oIhC#zf%R@4?`w907e=iE z-wF__s^ROUg<%>sg6;>mn$qE*rlVmRLYSaa%jo#Out zwwzKw@gz{=q)A@atx6_CTxrhKDC>se^C;FJtfww##qPeNaHLCWN%(od;1EBJ5YT%y zyNP(uIyHsei}lvBA@@XRO9hOK#>weZPFbX4&+ouaos09Xx0vWRho~RB#y4am6v7~h!z zaUOkVt2f+^@1G6&3c@;**z$J^UU3C;hrlBt|7+3fOBBhJnn^ZP?LblH1Jh&EFf+3s zZlODRMR)8^NM_{q&N~lfsYHsv(y374bXcUT%JAxI0HR3-Pl4HAR5iZdaExCa4&9^M*VN16pZ)olafIa- zs|!a=R-zp(Qz=G{c1Errw@ljoE^vRhcs@WSDw1OyJ+XmemkE@$=aCeILODziBZlC& z^t|o?0TErwN_&ir?VM+NCRA8&zJB}!p{hR$Oz2G+8={uy$Z;%xi%t?ra?8(SPg@N3 zdUMoD7rDH(QOrkbZl}bmEKPM;>0|Lr3(zV{EBFSFbA~EM+_STZu-$wdGi=@~@ z8s=TShN?Y?{;v3@9Hf6xH82W#m8!+nb?jzzr5T@)w^30=iKBOL5a(xj&8!q`N&Z>( z9&B0AQCYoh!hk$6-K$Fqzv!_Nk91DCl5W-TJj9hrIur`Gjgs_D6=orl!e_=euX7|F zdcmEMNylXBZx6&iJWU#^GHXfbS1n*pr>K-QYJgIEJA^J-w)kS70Z{gyhgV+$B{Ofi z@v==i+OZAEp6)g&wGch>5Vxd?edo^X@B*Kh8uEeBoBz^AT{<(bub$Fn{~fUqveh2< z>s$fa3-C=6tz_OK2eU!BT2{LHg*wiD`h)KS}K(tB8F8NswTbpYA`~1r-+7c zu|!ojL>CN>u>~Hw{oIs;MIwdL=X*CP4Zjv4hAKq6|I>RLVQt^r!{$?7Ux*%de3Rq$ z!rZP!;I^CdH|N(2^OqK8wSNbkUhDN>Hefh78t_|??HM{MjU>KgtQ zT0~Z4LDe}{5_$V2f*9hq`O+x4fH&ekSpY>|H30j#137MV_8%n50L3S1QFD!b*r&m1lBjnnzoN^ z957yta6(7`ZbRKN(ox6R{E()#R>UjtoX@KMV5@k4=+8W2K0_JJbW8a4? z+57+#7s1a|*Joz1pM6BBzZ)yMkk8;;mjwb%4NEGZrGlBX_BHu~OK^%r@&LLDaa}j$ zCmaxL<<--++6Xui+OvaB13AOa&7QD^YK(-mQ49y-kECcX6VNec9x$y?7GuDpm6eI_ z3QLd}W#=k3Y??j}RmzyL-&wZ*8#=w%bZT5GAJ&QzJKQdbeXz!S!Dq2fJz@)2q|pme z!!98eTjKrp^q9^4+VnE04$a;ko;+Eu3-XJC!SRdCuov}EdDEa*o%S|iByhO>gesL={%DJ7w|J7oK1Upe;7v={ z%eQGV78cOn`kLSFLFj&2UtXi>&e%6Jyz{aDQ0)UrSTv%m@Bsb={1y@LkjRnLk{!^U zB4-dr6_ulXYhs?XADIW0W7W5`7YCV)>IXIjSpgeOW4yYlo4*n|^68Tenf-}$Q#)`U zX*z2A{TX~v6t*nMPBqMx!8&;(hQwX7E(54TKw)6=+?WYHqXuPr)Q~Eg+$iW=0bX?%k*uR=1j`FTx5a{s=c3%~bK1|j{Ef$vpFATI z7uKSw6fcHqWL<5e4mBqo;w?1NiPG(O_t#%~ke>)Pt?u(4>`KH|5}vn5JB4S$$k%>S z#|UfzU~*#bTWaHMKBKm`8IQx;xgT!9J5|^BMs?$B59gzK@IG(p$jW}nxYi8FCFHuM zjnf(Oj1%y3*b{u3uKqWHN~kC-`k3vmOmPFh5f`GFZezcg5PV={mMLTgYjLEqaYTV{ zSk;5IcR`2U{<71-v>$KnB;mhK@_0Vmt zMi`{-wfQKRN!eYRyJ1{~zKp@n1glG9+Gs(ypMnE357J{;`+>RiWm%}_4eZJ6Ref(7 zRuyJ(z3dihBtWOet}XlRW5VYjlz;sd{r_BEZ)^A5`oS`Y|BceYv|xmmYzr9Y&89Ba z&8B~)o=XcdK^8aLFWOvJ8l6ayxLh?&WJ2r_-x~q`H&|kfb~q5BoYMU!7oqwq{8eMB zH-&p-WM1td2em8w@CS0x6MUo`MpxaQyt6|jA{B%^i6(ZXaWIAs-vGi6#Aft)oUoHL z>bXtH{9Hx9FcAAdLYpyKj^9`|+luh!*w!XH++}9pFZx}Ye^xm%eKe3I-WOd5g*|*J zauJi8mati2lnB}!Ikw&`YN`_${+@#AR8NP1ryvQ^FCj#AlYNh|t_*lMJ;k18`{7fI z&m5l_7hR2E)*nKjGMpLgyh%m3tSs3Q-h^lbdK>@)Ec=5I3n-+JgrpWRpHT`o2Ex8| z&yL6~nRA;WP92~yOl(DOkNu@s82N4L6nx|d3Xtf6FW?D$97TLUOE0}o9q!;$uU@c5 zd{ig(R~MMp8T(oKI?@z^NiB!EN<;lX=83i;lGD04xYn-M+=jO1vC_<5J(zNH6*9UD zM?xO#o|i#Am6Sfu_c@qgkZF+4dDl&bBWrVfpvf@Vp9aI)sz%V`ro`bDTa$YQ{i^xY&IsP_>1|5a^ZCkk&BkK~92895 zcz6yo4n2&80nEEo;7Jl&$h3{Y3^uH{HIcr&{bd%af|Eb-E_0ek+v8Gtr?#SF(B z4Hi=keqjv~ocYx_Q2C@%iU7qy8*`M#4hNnWYprQyWH+V6Jns?2EADpxb;P0CAG1En zjp^#rli?A-xM8W>W6E?-S-~%J1*sVN%`!0CxzQZAu6K2z2RVQ9_qH`ZIS5D%Vtm9IE$9QuFUIM*0^yRP^Tq-=;q+3@x5E|u9#wgo)${81%Bmf<$$&t=u;Laa z;5F%Yu&U|XU-v^44>mSCgef8=kv<)Ij<$zo_DE0@bayx#MN7?5i@=MP;G)*+jua&c zFrj*JN!3iX^+gXaKd~=!z)2G)=D;K}cPHV*r1_P%?1GyllfTLRJ={-&jvPJ0DRx{M zRHV<*8`%5<#XKk=fbBr>c<8B}G;7R2G@NLaq|jgVL5r>**hrm~)JH!?ZS7QiP=19a zYetR_y82gL4m==+-mrZ35C(CjwkQs_PXqI~BID@3Lei-D049IQduFfh}E9 za8yLs_xwH0Z1+zsi$68=C-Bq3tuLRXBuV0xfk(H5L--BcpjpRyx^sZ!W+VeIUJ5QY zv3x$(gPH~0t!$*(b|)*89fbe2iq^yGE}CjDmID?!jf*thsh9@cnJIdCAee}ycKX$_ zRogXY2VccW;jS5to~^hc%qKCW33&T4jXy=d?7M|x-4{EO3j`+w4Cj6o@Up+Fa5^#M znx>PD{V4f#M3+nl|8x;nS3E-@iX#X5cOX~}OcuIH2eBGbrVA-;yW3Xw^nC4D4RY6m z&KEq+Z4FNv?Y%zZEu645xk{QH=FfMu^I5WKg+Kgao&a4Zn7x*Hdhd`pY_9ALl~xzc zwz_t+-VDh~dU|r;Y&!fgq=%Lc4(s1vLdCAt!=M-A1yCqtmT)r3<@yQr3oL|SWA zb0c`Iy=zQ>>ofCX3r+IiDk$$G5i>Q*jF%X8hK38i@f=vLZI;W=S~Kn+14@v93nA1M zI7oi>BWgd2sc19*@*_e;Z216f4We%X(#ZBCH@AmAw>yB#rU;)o9EAHBF^yOLj%jGM z8R_Lo{Q)~84mnKh7wS<)G^&0!($wBE4(>=`^mrsvZ!B6--?}*UL^PtY*QaCzs7b}l zNbJh6@V<*i7vj@rb?`xG*MhCB@4$kyKpU40$SlI~VIRZ_Njr}AJ2tN@EYXP(6fz${oXb1ID}ZukwVivnh}WivRg2(}1mj5%KT-2N6N_~-f; z_Gn`Om~3+xOUU(|nB>C}UGhy_ySV?QwC!yYy_6(s7`YtA=6c4(%P?p!w&zkV{Q84n z=7A^lu_srmqijM%fdg+Uxh{wMFLB^%->_}>4<`Rh2$kE|9hqrzpyoJcJ2;$ZZVdT^ zuJ?Qee4!sAA|de+>XdE1J`igz05!=s8?7O$RmQ4wTkYa1cwHs?^^-Ym5|T&dNn!`kN{7JHCYa%~Y|T zEXc0EOmY8RBJ2nwgD(V8#-WhxxmS8pkb1@gOd?mvED-uyGu#dL;DVAPJc#m8^E7H>cf{Au+qRjar>WbD9}ZU#0-z5T2c3 z&?T7M_c2uw9PY94Mgi)d<2$i_d(9xz%%tvX4`~>wXZsHk`cMzv)Ygs1#ICDFKfoj0 ziifLlhpaEZ^r+jDk5%uyJILsKHB?!*DxKO&eX-kcVtR<6Dq2HWfKByebjkbR!iu5 zdvbboyH7$yBme=sQ@f2o`fiufyETQ<>3B)CqA+YBkZ~^XH-x0{W9A8e#fc$vIQvm&D4h{E;DoufxLwjbB z(V_$Q8j;sy-}pH5yntJRJvm<3Wit0i|*wtv7`hnIwroQ)1_b7R_)cSq_a zT)XON&v@#4yNBp^hr6eiYzi!OtY(!ZRyJEhOYPU$9QG?;A1Mbrr@#(Abs2Hda*HA} z15<^B)Lkg~wLGk?P-gSMN3@*Dbd1U`As~v|yEVD^!#_;>@hes`f44dnlQUgdSstsV zB}y9T2xstnS@%)V9HWq0uiQ{P=Z>jwYyePzZd4_-%mGo<{zHe-%0P!sp`S24O%dgU zUNb@`=kRiiObXS66Y(*JTCe_P_xlX{hKh?GH0pu*-`r{GgTsMgbE}g4`BZf}`HT?T zyLYi&m1q5*eE$m~(=N|o1<7s5n@Xd31-8UOh2lU1=_H-HA3bu7U>1)AzceDi+2IZy z8W=0PpkRDwq)ZcWVnoPQQ6Pu45R7i=IAtvw)#Fq7nO`~qqC+13q1Fr}l0@6Za!`f` zI4*5Ekrzxg`hzIFArYj&HPu&jr0j|PxByaqibyicObWYB<;$DYo;HOH;*Ap4uRCwj zHNkFAHD3#8){>P>#;rBkUUaCihDh@?ql4MtlZcJzHx1Me%9rVIz4k;Ob3MJ9dop?+XDr4oeMnvE5f9 z4HLlbmIOp<8_Ide6U&*_Oys@F*-%Cq{5xx-h#*lgzvgcwO z2D1QS2#%e_R_5esiZRV3S($P9(m9l5B=ZCx^-rmRmO~{q%m4od4+I@65VBN%v3NQjx>u_O1kiPV}oOh z(-u%R!(csK4^u7yS9_T#P3ed;MQd*ynvcVNc!J)F&x(9TOk}!EyWB4cq<&YCRI9|{ z$aaFpC*r#IxUaCg-0U=rSq+43rW{N7`~*&#?Ew19UcQ$?>esVy=TIO+5{A`ht7Vmy zO?a-O!8lUM@e8($CY_&OR}1SZ(dRzFaW)Qa>by~fM3(Si2F09e5c_BVyOy(Gh~XHM zCqFmKn<|oP$LhW*^9l!n&)AtWI^!KOwEN0S@ADk^UH$x-8hh@f`45u#X)W1{Wn#yf9HUz9FQ?e5QT#ae0Wrw%$?& z6}CmOS+E{;mMq0lJrF_#{dZvl#VMIRV;OBMcM%I_xny~~!Ox;=;X)8tL%#PQYgP(J zHJbaF79ShO!;S7gXr|Bb3fZLZ7tAm0yj?eZ%hk%J}Uf85|ec>_Z`d9*cNwyiHb zbY=hAk+1hT4M&X!8c++PN9zdh{Wq32lRqtJ=1NQtspF9-3nhRe{L~BA*0P6QA>;KO zEK>s(e^(RNznEA~DI_J?++Qo~QTyrvOa-#Zt%>wCwJPE&l`6gFnHA~nB5y@Yy=c^K z#9{+Y)UAEr^J~~G*Lm@*OP>rGz{U1>e-RIX$Du-pVq442i|R<2nlEwt4OiGFf+x5d zD6=N~IjA8y^1mzfLk%RvHfKC5g~bUXCHrKTrRIr3HSOPd&p-RTCr3SmFROIB z8;Ga_$n=S-e+UdjXE{vTx&S^~?p=CjDgVg~!Rss2vZ8s-J=JDPX6a=Cvs@Zo@jnLj zy=C_u?X8YwO&DT;O0NX+no~vZM`L8XjdD{#`*+XUfdD-^!m32;RgWN@`c6i(8bEbT z`mF7oVDxDoa@m5GkK6qBSa5m!e~-GA6wO3H7?J$ZNG=oo*?`3Qd!iBru`N>>k~u+# z9wA4T^62#OCuSkjWTz3W3NA9wm;hwAQ=hdVW)Sr$*=ws~GarYuQ#z*Ptq=mWFQ_WL z0Ryt~*>33G(ZtCsr0P>YId=H8OVuxJvhU{N^i@O-Z83sXm72ZGiTL*|i{8Q#B%#3Y zHeRI=j(TSAXox(pd_lm|`>kzRo_2T;OV#gmWpQQ57i#xf13XH)n;D-e^B1;p!cDhQ znRvF4HTl4qgZ4e+TBR;?mZcRzbHF`g9l4c7a`L+tRlM!3bZo-l7MXX}cq|2EAtwDf zGIS`PFGrxQrB=djAt!iWkSfr^LN%!;DC?8HtWgYmBL(BWOhr%!9lE(~M6JKMX2WP| z;Eun&@=9b9iGd3w_TDlfN%YWdL@P|Pg>Z?#UB1Bse*1SIkWCew(g*zfR<{ZnRxKG~ zdIO<3gqNiEt3nG(heYO5Y}oe#ecy@zlhuHDX<<1r!N)aGR@Rq&(V~-;2qwmzlM(`3zN^u{4r`$65E-Ptz@L|3nnzU%OVVSNV#I zM%Nlb{8$_JW!sxCCDm!fGgy2MO4=&{%o?}#T1V0mnOpJ-(PT7>zVjW9)6miUQ|Hw! zuZFdo+{I;4Hw?aNN+bI%`f)M*+GcK)>AD$Vck-gG^a(E4j59{H`{BuI2o+#{$x&eu zl`nAlG<+jmUmJdA9V@99`3sk46!Qm!I?$0NcEo-X@SrWt`xwWRyOBG$r?Dc6!@y70 zGE3KZak_b$lXSWy9lL+Aj~y?0S=kH z4f`kaqlS}@E~<8I>N7PH=X2ga{F~yS#A1`;WZdZIBO1^DAIVW%@hyWZf88L97JkTb z7NqKJNW+|AQltw1Dvgyz_t@j1)e+~#k1Fwwx9p4WWQXoz{h}?8U^~TomfenxlWQLC zx10y)_j^JzlR_a{BtA8&7wejJ8!NBFxQ=0LjZ4~UYKeCed5fB$^r=I@-a;U!9a*L) zY(3EP?HVN% zBcdg9==UHi;BdAQ6;-uEL-EN3>6V#&O?=JUA;;$H30&+$pN$*!f@8kIMH%Mkg>Foq zpW(}7EZF9)empI=m*`?%#E-bmTu2TOmqc8zVH=96A@Wa>Qm#81;%#}^9{$QPD>dy$ z8cZ_}&}D|WHTO$lTNS_2GBl;Z+a7KRLQ+H7mHXqWJcLX9zE2Fv_w==2h{4!$;PO8ESAh)21cL4hcPuzz>cRR-F#hF~pi(>cQ&-~>UGo+gu z+e=n+mc6{*IAlj%L=y!kcu8xMf=hv?|@^57h*?(O~y} zrw*EF(G$P5Dp;CTSctL_R~wI1nCHGtIjqRL+%PE8737bdt@!PpuRZ*#bZiej>riRv zLF`pkFX=)yCjy`ow6o4X;UehbhII59zZ2rAbD=ab>V=U44|14#*ICJy{{G3ZIu)pS zm-08Oy-%D9?+1rI!`x(QUDkY`u<8{StGkm`ao1MD;0f_{)_SlO0iF!e!X_G z2b1No@{Drf6mSS@toU|9`SL*$P46K3n*?Cwa>JEZA!wCm+9Yu$%(s_r?nO9$XxFY4 z4LU0k?|W#an08lYHER&U&bLMBS4jCxMAL#B^d|=?bk_;*^;Ya2xb`eDc0Imex9Iwn z#<0%g(}!SGAK@lA@py5v=&~rmQ652sh^?aN$;XD!U7UvgTi~$f?_Zxm0TT=oV({sS zv4ZkMdyIoqyj8_LK?bHwspo8!j%Fir=$L}BmX1$D{UEe*R*8Ns3E_o}Z_eY~57{4< zKOHI`(S`7J2|g0bL*Ik*($SVR-u;ZXJ1Ml$|4h42>gnyx(YQp=>&DBnqE(%@wFJ1m zjizQbNBf$ILe0LysxT^NVi-S+w~L1_m1FkTvN6SIWCQ@kR{2mHzK7XBQVY_}Qe!@s zV$8*d{1UP|^4oU81m73u>4sS5qfwIF>Nt%Xj^yKz2p#k1*{lf@p#Dqj@$!GrS9VYO z_6Os)?)Lne&BXH7OqQxqFRk(FwzH&!s}c5!1&-Hkmv4yXzgE)sNVkB4tC+l^7Auc4 ziu&%IdGwdg%k@^iJCtds(TV69D=BhJ)6~r;Az(?(N5rP!fEv6rcrt5u!Rdup94fFT z5$`4KJz-{4vwL5cGG|t~B_t+M=ZP4H83KT?ixA&*n)X9K%VVjuI;O2CO`uC0mu2_4 zrEK)8oq~eC-mN&;R!#+cv@~GX=&(DL*Li)VC;4vfNc-pSbOHL{cEjf`Pg4hP&{pl-BWLEB36>+P67Ij>w@M?r%OPBgT!Q>ss~2UyKGb8gbN|+{jBr5YjZh_F zy-O*=DF(CPf=SmnvckhqI9X8m-OZIo3B1MBHo&+oCMV7aad4R?Lo@jhd~zpmSPg{p zG{b?Rp}3b)Y5#F_1AZW1%IVot{i5kI-%BzSI*4nKvD^m1HU9PL*KVv+0UV^J6-kO* zDr?b-Utn*+;jTG(#Q>SGm1S}j=Sz;10VU8V@xaHXE*{?8fya2I`?`Ui=*)g+RFp1{ z$z$U;q~|M^1>wTN8t|zr*_a;u_sRdNq-Wh_D>q4 z+HYuDgGNp}0Hs*E-|54{&1GfrP*ztjW#KSihYm7&|znfxxJ=z6Ln zyH{fdY+&g_@SnXqV!cqTPNsUPcwpRRP=OJg=`C zaYM3|9Y1zK%?aPQ(8pRFOeU5E`ipX{Ko zdC-A({9D;^y?gpDcG~i(wZNDxuIX&f zW%Jx0Rt>1G!+kxy{+EE9*a77I0))ch7zm1m8ky)p(ZDF4$alE|f&kx5WEq#oPYH(L zF(n8N$c0a2&4GK@_}VUS@j$m9IxlJBy@ z=CrMXzq*YkD7+|Y@j9z*UJx%F^#F7Z>mm z@A?<)<#%3_(B#G!e}#T3twXAlwD;nG`)awkIuYrqW_EB^VUpl)T7?LoNXccqyfc~~ z1%@|j?{Ef>*9Mq&)7pQKd5|q)Kt;eWU%upPANlq83M;lxUInRivOq}cB4L1FiYNFx zxOwSUj`pyW?~)vw7ysq8E1^?FxdB)}s!7dQ>qdpQB)v+b{PBOuWpp;K$(Ec_xZz-$vCwXpP9Woro081W&A=yir$s>f!Xd^0p+GJ5u8^V=n!oWE*ogi z*edAGr(kZ?g?BaIN(IZ)H;@_U-mFf@GdTDS$qVb29)==ntlekw!qiB9TEkAAOw50{ z?QAqJ8L5REd0zd4xm9AIJDDo@Okgs5^O_PCkDm*p%gWI@3Q_G?L%=Qj{bBvjFMOk( zjVTIMrPO-OT|9YXJ}T!tN3)h}WRvoyA2xnf&NII{3H@7XJgf#52lXZ3S+^9UCXKu_ zSeF$$Te+k2Cral;asMi`%*Y^kvCOD%3;XW8<2Lw6vuRU5^)mK~wZz8X8;gCfvOxY~ z;qLl8H#wO1vWpf7x@#M^Uy=anC7cyVEjyhQKbHDSq5dyBJl7E{*P_geEDTRI6-aCM zj;KfxpUy&xt9&BOPrX%Zn}-tt_LLutCfWgCl*8X!_o~Go33P1m-6Ce0ubkAtrgE+v z4@;!b%g`K#e2Du092ZSA{4lqMyN?shOpGb}FoCv$rebJ3)_+GGRUHTlKq7XT=WpAX zerbg=4yQ68cK^cRum(Qa%6L`}HM&w76gSFBIslIhYcMua_3wL#ZQ6%=c8!|xFe}z2 zULFWp&u!dQ+-VT>R5xjY@qV-4%84P8 z3OxLo2(N5Ts3lVtdDX}4-=2#Z1W~;%*)ewNtdI`lnz}&#*YIV^`{Q%qrX%z1g`fCD z%0L9cRB3?{9z1>&kDV$`?J?+q1oWJOHO5VxV+Zw%D@X)^4p0SF<$jnSCBIrDQ_;evE&iOC$ z72w>%bjBj52L5fs#620w@l#$+i0WvOW*;qEJDqwbJFO+723?+eO~5M)ZcJkS^=}OO z=D*HvG6xAfE6j$v%7OJ~!VhN>vSzDzcP5V)B8XR&V*?jFm>lu>Z-NfgrR=bvY?aCX zPW*--1L=>`J=^Q{drIBy7dsSe1))ZD`Vtl64P`c3u(OlhwByi_*sh( zpoP>{yaE?sQdP$DW>U!tjB-i~01V5*$;wMNF)3bBrIbuMTd3}@&xlqhSeytp)m|H@ z7Rj3d3a4-be!_Yd1oauOw|l(+r)gx#?ypY@;SMPd4w!j@SP4Fxv}Xd~R_5Tncc|`v z&T>2_H$Frn#$aF zCft-BZ`m1y%3&tJ5KH%)G}Ytd$&L&q`(5w3zkfc`iON~FKC&S+OORZ)<{Odn^ ze{i|MFoCNG5pBx}m>s0SToQs&y`%(hl!a}FzQ0Fv@+Qwmes#HA0h>NPUsxi;2e=C^ z-V0mCzI`If$Jx57q}%H3yE$i=R2@o#D{SD*^|?`eo&zsRJqxbDL=)G&x$?}{?%ynV zs4dvyKl+$r{i(AE#iF=f3bfNz;$+o?5^`Jf-n*BwQb)FnjhT*WbFK^we<#5B=SMC4 zNUvUqax8UDGm8}2ja7|8jQXly9xF5e(`aifKE zzgaVF0UxMf2aHqc<7E4m^07YGg!8n5P_V#>$g*+`iNJ5OnXbg+!vE7PX@|m+`la9{ z-Ujcp1hGD37xe=7**}{ph%9;qs3kM!x7Duo80)c--*vx<_r&JD5z~YB{W1irU`e;l z(H87$1_#vOR~Ll?HAhqoKG$~g@*)V^Q-six^4p4vM`49Hv(Y1$#q<8JA0;iRalg}1 z087l{7U9;Gz@0IZNeW{AsoOTHZ+4P&(iiCJDLZ_lSR}n>>6*l}DigO-4>Fpee0ITV zDU*jfq(S!3!g0(a>~#w#7mFDsQcGI1Q6e!O(cN!&Vyg)aL5@pRMZy7C>HxLXoA&7E z&pkWt#@pwRH>Klwbm3BIe69s`vyxZYtnD~3Xs`=eJFI0uH26RrK95hrn%8X3D~{1S zop^Hz-2}4GY<-v50hj*oEV+yjJx7uIn(Dxm7bv9%j{iF98KvR+r%c{(3YqBsv274$G+bN>n4#|T^9O<*b%l+ zzty+}b3mtxq?{as3?w$LxSkmN$#v`7IH^a1wIwx_6j)4)p#e{$VE*UeTC=ZLAHj%j z{=k`OuNNrjbQxXG3)(64KQ0p(mm@oeK$U=nx|mgH8%g?ucCUI=YCDng0Vo$!^eZ*G zig{}|1mi<=O@I3j#_;yDH;|HQ=|0p{T^E*|S|s8Ug!vHL6XPR7?{>U;E+)xO-t*xv z#K}yjPA|4*qZU5{GKp6=-8>$Q#}`YVTu!~uGYWs_CgLkf#l?%n%fT1@(zPr_(3Qdt zZ^&9J@Fk|O6(q!D<>z<3fXgSQXC=}vfZL>?u#voo`aR>t12l%3$p|{HoB&zNHp29+ z?;8vx9>{)Ia{SbvJF)+eK|NMdXh!X#U&51LXTa1kt6%WO_u(<9D8%gjoHj@tBz>g9 z+tENs&;@DIB(_Pqe#LaY_k6|8jzQBiq)HkfXTX?zGhf;N@{>4flrrAMl`7~j>jW{h zhm0X7f1aXNvp1PQ^hWiHB!>D9+Z|oOuVeI-IWVh0#2@K;3CE*SX_|IDLvLCRHy``) z*c9w5CLR&MTW6*sG7-ON65=2J{Yj*qZ`$xwczgdad{^ny*w^gYHX;x5t%qaEaq|7w zr%_Dmmbmr}Q_?;)9*U+?;4NN;cmS0U8HocA88cc=B=E~xm_ z4S_)_htAY5YY`!)6SE+aoEV9mz32T{2J5J;$}2l1b#Tz>19tQUB6QlY`O?Jb|L}*6 zMW4^@JHkiJ@7yFdM}Eo^#>!gG^~$@8tsGYuZi!?2s-AQTQ%Cg`Typ#vW;qmbCqhS4 z+!5qxbM2pDF0jiMe$hd8g3R`btKkQwL<=c=_Dv?iDgbfoTrcM_myVx zOOKcVKhN_mZq_OFer*xMoMxnJEYW3&yGH3Z-iM;t6~KLyU&>-R9`D?h%vx%Na3qj| zwIw^b?&RUN;ZXF%Yq@sesn_Yk?zTn_Z?EXcbC=t`=PC#}H=l^%V>S~v6F;uD>iHmb z?y=ca3wXLFKTtM5g+V?tseiNH~M$&@tqo!WO1KHw%ZTDtDH*vy=?%<6w zhudCJ+#*Opq*uBDPU8Ds(}Xt8%js27!=k;**M(L^;BQj4Gf5bmV+C0N+V^;~VX>h% z^(2-P*O^`emd&_9L!7=bRpivZB9!@>564lmuucbcODk%e&^i&yo;VV9W2!K$T$t;! zo7~#}%6C6qel9XpezellKGnme^uA=`Bvj~OJ?4u9z8f+%zqP+l`4inMnX@WlNyGby z_lf3Ys63ER22Nb0)z-z^UYwo7_>C9P1yP_DWwsnJ2|McJr@t9N`wGaT{mD%nbPVmC z<7jzw5Mggv+S^GVIzymuxzg$D_a*;{lU#9+&g!5$3%U1_1a((IfZYoltc|>|t5%U- zq}~7B97c^hc}x~TDJWqEi50<=Pm!68@sLn^q~6E#JYus2pE9^nWQ^`Cp3%H4G{=?; zw=Ic6oyaAK)i#w-g+l!lLHvuh0-uNCdl<^aCIA2L5`^nnj2 z9Dk%6107Jm&m1sUZR_U8aSVTw+`YH%iQZ^XfpG3zU(6%_FB*A)`7Xz^Y%u~EXED{a zo~ri0NT7%OkXi}(c&=oY`Rg{hoQ9~J^%l6h`TV#k)+1iU{)umO3*vA16ul%>Lg5` z27vjcB>T4gGNY0Wz7y`uF``?!`O0GH4N?|lmAN0diYF}jn<#%(pFV?uOtS~|ee)h( z#SZe}zR5ZZ3(kT$YpRp)=|9T7`P))RE{F{dMsP_dF2=Z>vd?7Hvj66*1mr~q zU+skBxep&#VLI6OKqUJz&}@a;OwZ<;?}1fBV)^8C^IiIbw^n4e$eBT>AP-l=v@xrq43k#0Vv}yaqv8zwb-{`Tn|8^yoanrV) zV=lZmUkw`;?!8lTQ-n4$H)9(e76a5j`fvLOJI{wQR)*!E^QM zlT&o=7M>h#{9YWUdOi@qB${I&FE3DBkw%WD#-tSfgzZ#N!%XQZ0R9%T1H3$Py;?yC zjl6>Eyd^44{K2%&yN#2Cx`{j~>NNzwO)KLmkyybM`c?v}T4aHUfj9N46ImXYOk)IZ zM!;2oT5RBR>+myQnhb!kqDK`relBVg>T&Qy9pkZbIy$W+Wh@MeD zXD*YAFexJZwv`5Oc!qfDQ4R_I^#AD%qO!ZUSO*gu$&*^~$AQfrQHTb4*jzTVf~VXS zOc0!JvP~}49Ho0=a=uz2c-k6s_Eg+2kAxdjtxOtF5k7~Ut}LPcv_;U){C_=NXH*l> z)(yQkkrE6bq98~QEeHffiXxyQ0@9_|geD*@7y+d#N{7&!bdX*Gh=35P(n}~3I-w&a ze3AFwH-F~GtTk)yy=U)p_PKMnMry}vP`)bE3gaCLlQkc zfzyGI{O`n)W>pTIku>bRx!(mw2Lj;G-KWNZ)5s`Zm!0F+JNNX7rsXBu3F=OK==Dy} z;wLEl39*G3x(UHxB}mEzz+}frgt-6@mD^B9q{iPrLBqY<2kW9i#f8U<7iYYF#Da&z zWkDN@`!!*65Q~l7wDQr698O(B8}0+yn(b1>gI{sg6>b`U6D%@ewWD=ci*?;0(x+wK z#R7k7Xut-X`<{BV`SN<6?M5RfDIH(x`4a0&7dqW9yQfE(q$f>R{ zW7;nnC}qk&e@V~vflzuLEglXz5?-dZ+gO&R?A>}TkHxM|_@2p_!v#*S@VW5sRcd-> zv14*=Gdh)P@e&(JPx~Y(mB4I+xK@j9I=Z+aw~cG?)i%>}-9*rRUGXRd}v1XT&J( zl2nq2W-uOGbGAP!Rb8`kmEyOw;xLH;BOI+#B#pu$b19?TWhs6slmHQJI|m78WK0Yn zcrKsQzjn*)V{eZuY%Y@{{X%&r=mPo6*eLVMZ+#(v$u~3~ zf@`AdW3R$!$7y&bs`K@`unGb;F=F-BeKL0*{dYklpl{pDcsWv7Xr*GTqG9F0o}qN3 z)LF%{VT}qapt%Pwo8*(6IS+izxS7>>(=FALi2taQ%pH#{>S_Z*}G_J54t)QcM`!!kR`_)QCKXelIWJB z`!ZsF(bMRrZ?~~A6^vTwc4ARqupm|T4sZf9*2(;obf&ox7C6rl=&;G#ylx6U4i2-) zk{z1eVh=dzMP)Nxtd-exvqi?Fl3rn;y_XbxcqCgmwTbYH&tuhUc8&R)mK#qV<*VMD zB3CwIJbS3ew#E&9F{Bmh5AO44uY)ZMnp1OrrS>9GlyFp&+H&F0l@CI>~}h^IpC6>k4xZ^;XL?2=bKfye z4J-JWCLY1Pocd5ivX5mPp+@@b2R{-8e@1R|9>?t=$%`m9@}oJ`LWC{QI+jj2#0v86sB1(mW@Sn9ge*>wn945?9oP z9%VAQX|umh?K|77ZPl6bHp{Jrp6QjQe-!=d_-6=uVReEp3(WD@nmz%zHN#OrVT^{) z0G}9UF~4}BL)y=A%lL5jb`k)Ui1Ggs6C#r3*83srm*q38Q6?)yfY0x`&%J=b7_r%BPIv3L5FU=O>49Vj}BY3bWgZuem6B+ zg^*qnsCycOBGN*kj)nz-%mP^+!D8JFkYhKISxp}Zc~$iZ9oPK;JtGy$sY^fH_lay{ zJ({0s1Pf;bX~V~o^sOZlj^?~ey9Rj}sicV}C;_-aPTQup>9p|K1WjZMzVwio&c6CT z3wW#;##T?*4m>&8q?RBCeL&JVQ-2`U)q~K9kl^#M4n?Y#yFa4xKCOPUl{(enmGatK z)_$>H`=x0>jOtAK>FW^(7w!fodKcHy&s;ZZ_z=eZ$LSH;arA-o#I)R^h9*oHUV=BI z?v0c!pP%62Ib1Cd(nJRG#z^GsSl=gZsb*}gbD}4y_Z5k)kDHi|Z?L^BHwE3=^`%L+ zp*xi)KUu=lCiPm!E4`|>w+qjbNMGDDF!<*YlCCl@AUGui7F=Y%?w0wlK5g(n5Zxbv z?zI6#eB*rV0amDK!T^Ud?4sv1C|=CG-XtYb@j5RerI^wBZgdeT*=y7C8HEUG^w8Fp znD!4VEEc7nUiyq7N@Ar%ITUFm;z||L0fnVZFI?SyR75p*I&)$`9_`Szj)J`N^~LkUBnY zq2K$4Yt=O;45LQkbQRTGk9#jf{JJ`}f4=$ZR6amf{<(GkXVgaX_M3POVyX%OX=~vEi+nCFGwh=Qn-M4y!LI6{Bfvo_|7lcfPDDa_Z~8fb)zkYz zOzC{4HTRpx(S(LFzG}F3@jllR+9eWyymEW13}?+5fZl=dMdM?bCBTlRCCP2vxHta6 zEVx<=5lD(YrQOlS7jWUHJA7vVde30?jB#>iN^olRNx?k> zcnKwx_wpKEJ5a zm6mB{8hpb_oAEa`H@2wd{F>PmYFl*fo>J|vMO_=Notl|telH~rB#^$I>N6r0LPRSC zN{|v3evaGUL3kOx6Auj$O#$ZsC+Yw0_(V!(@nHw{tflf+Jsm_-q|PJTB?<#Xs~;{4!|WHzGpG6FyEQ zXXiQJqCaT+NW2w)(`Cj#Iz4dBxVk0qBN4Ta=KeOq2CGF1Q+4d6IrRWmoLl^W^Q}YC z2Di<-`=fH^S&lf0H(q;2Co{P8oZoZ_x3^2*+C`t7tG--6w3p`^a+b>KcrrZi^XOZ~ zz~I_XVb1swPuX0OSIUHObIoWFwLc+A&><)&W~~kJLV+9Y_t-ck17uyVdR0$L01&^9m3jblUCd z3AwkrZIjqCY-w7Y`(sPjlYhDkl%sS6q$!UD6YYGyPdLXT%m#v8Ki*k<#ro?O6h|-l z0_^|(u!0f+jmUsGE|7|h4gIpSYU;~PqM9O3*O#@&KLMXi0pI)z0TS!oV({Qh|3TC< z*p-6h&z@9IWJpY2$sQ2JmLN9t*PRPUQ7Jk01IxDu>x8U%-hFHUc^+d|6t%i~FrNmr+*+)^Q zm*#sGhU&ggp9l^(*x5zAu0drgYMfg;P@CJX9*#!iN5@OC3Y%MNdY9(u6_^W4Yc zVi$mY(jB~j; zlYV@KlJiD!mJ`~L(=C1~c%{pa;`VP|^-m&vE32AG;`wxN%0<(W4IFbf zE0`EhH_We>6TtSAdk$0$tfzvhLTS^)G$0@RrFP# zB@eQ!*iaM2R$hQwUg2^dQu>Fy{aTrGWl&sF{;)9Fr>)C2 zCzkxpNP6MKNE6iut114tmP=KqPQi?mWiAKVNo1l^!7-1?@9yE|Yg71 z+cf%ph{gA`zvsVWIK|kGP`{4kn$8L}>h7_NMCsIKD0){{4zqq{pycOb?7~`UJ9UpN za$)O-H)+6kL36+&G#dCS+DVr+vM5<{Ha8ELIN8l9{_T6zRS_BaRJT@Fg*8EyDX`3Nz-8>|De@VP$F-L2i4NywBoXF_kJSA z-Z2ZZpW?M9`(w zsMiNM7UYH+spDZ9Chw!sXP=ND-;X0i2zx0|u%$6VaNh~#bNQGkQLZRKcT~p(sdHk~ zN2D7y`ePGc0&*rG&15&eTk;@S04z@5c?AhH(Tg_{hA(5+ePka=6rls7K|FPTBqrk^H)b7|DJJ(=Fw z;@tp{;%`Izx6ZmH5tn|)qQolw$PST0;P_*Z7U?a@DhE}{7W%~wq-qiK02+Qi4RNPA zlaw3c8oU27?%M~DP3S`is4kiYSyreHD&(zr;bu3)#C6Z=!TCgFLsF)YGpN6zo8LQl zy>bC}-&8bMQg%eZirnsT|6mNQW^rp$UfC$Ngfud-0kBVsEpv%iF5XJ0W#24*xW>b`dNj?2pv*fH(Pr#XC6FYlOl& zPj!cxbPguw5^Y-7$vz!SXx{3N1sMeQmM=3n>1r7bvAPM-FFiV7Et92njLo`Yk$lTF z#dA&~^22c3JX!y+nb8n6vvsW86+#I0MBKP_=O}ILEoIP~AH(Nqsi_Ym9V1zcHGz)D zw@`!dUlExYlW-ffjjj9s7T`qj6;p)3vZO>kb{%q)-wM;T0?!GCEU5|?{mw)Lxq{dO zduk5eN*dc|@D78(166cq*`|9k&oEt;>0OhdS%jYa-YW}sFIkIp-M}rDuSZ%+ z9xcnW6j$F|F-ghEJQom}d73WXF3RaV`C3cqofQCP*k_ZtTfOEE=H=nx)gt3hN z?fJ?8!+)6v_A!;*b!lcP?pS!Qj5VdQNE&-|8+Ix5kk>>mEXoL?Ly~3W{xh2W(}#yoP>bZyq<74Y zy{7h9FmR(7k!(`u}{K>@Le^>oX?b8UdsttxmNWxL4Nmu{9)EfRtenO^W zl{Lwb%{32amzRN-TMoGBW$J8F-_X|ByMGhofIsoBxFd!>?MUFU9H?wSW33Ayblk*s z$k3}}Yjv0YSY)8*oxt+PF)YAFtu05xfvKhV(R8ke25*UJjE9hhlS#l}aWf0A;U(Gd z3eHT@H)19y)mv1=H8ma#{wU683Ugp9rpz1_U>ln2a4;SS(dXazNw{>hD z5+1zo7_x$w3uv;I3*8v#si8}!Q#R2vdWwy`{pi)6eYpN*)EfEIng;L1#wX@{bKXhf zg{>tRJPMhI*@KfU2S6N@r`=8vyHk2_4cua-W44_4 z)6YmS1V-zOHUCl(Jl%OiG<@GnF|Ju@v-V#W;LF-CYtA!z`1>}ov#S=@f|YC{;bi2< zB2-8_JvAur0K8}%ubr{P*Nt!K+) z+o$`y=VJ$rVaUBcyAKR45m0woUr{8GgrXne6Z#;YiY$Gh`5Oj{1;|gi2hjUXXNw@x zhd~ly@^W@~FU^f7IlUh4sZ~9Gle{XIYdG>G{VAZ%313dhtAt*2+u%>45zfolyS+u4h4tLC63$l@Nx85ZIUVyZ<2sz2S7H7qcB)CG>Il7 zx)3G{#SjQB#^z9sS~=H2OiI~cN~swp;%)d$ ziMB$}{H_noJKK9F_zqeB`qmcjBx!B`A5Nf+=1bzyL@pBGC^;)q#=x49iRu?rF^7<$ zn>`4yH;lCT(aL8hm^HbUnFN5n{L-Wb3b)v#_s~v%dR$zmzn^>jqR?|f{Poj?-dl8= zSbe>RPZK1Bx;D9|F;a!?PlI)bQc9JdpPk3ek)xbWl9I4N_xo3MS6k^L{TD@qtyA6F}8K8otE_wEBdZa z;}C^zlC!K8W~?|E_aDTGtj%4_w#rnk_9>nAjqD$KH+?CEUg%oZEW;h|IxcYN9f%i`Ac_1cBThc$c7#-wPqi+T&OOm z%pD86z@`C+52faQ$>Dt?U|p!rNM#=M6m^mI;>GC7k>Bj zwIoS^d!FB>Q?hoxrhL}Cz2?6~h@<)PC(?ftf>JTRx{z!Ie;l2L^W~!WLqWMqScV0B zPd(IlatZCIQH`L*?6%-2zF)s6oW{a=C{l(9_JG+pyL{B*bZ`%!at)3$liZ>rqACqr zYiMt{Nj9o5?d`6?N8fzxAaGNSjP^ zMs|hs=36Qw)}1+aIZNh&8#S2XrQVz7uP3+5E}>Oepr`ZUkt)yNzVE%Z;LO<(NaRBb zI5LkNeK_tX;iHfOSb`Uds6R9|p86b|y*|TOf{V7rx_7LDcEEk_ANjhrZ6&W^KT>Be zUk{yLu`tJq#LCjGP~BuSq%_jfq2)u@#;cf7cJQ{r`v=41uEVTdUTXh*xzGioWH3tF zbZBk8L`(jS6dP;w0KKAhOFY0wbI`17yi7QGA$2|P7 zagnF$`c7`>*(KFg30mHMj{K^>8Iq6jW%B8hQa%4kh7GoDv4k&ea-mz3jb4(SftO|b zm0l%Meg)le5m9$7IhJg^R^O2Nlx)Q|91kl|b>d~0vu!aOm#xCii1a`9fK3j{<)0J& z!|CBSBDEK+4Jn2C=~PY$Z+95#YD<$+*+pbN6e`liP*lIS8}_xAG?t&Wt>MGvA{LpU z4RQ`v(MHqRk_4^1zOzBJa+7k0` z9(j#cS8%^(odV8~JbsM@S2fx`-%C16s@H-8f!h?gA8nbzO|a|c`YgLiQL$ZFKEQ~|f5eYwe}+Bahjx!ZUL zI$?ftpU3NpZn#5yoeFFl8w7fBOw-~||AoW3&(6tvfLiQ>;t@CQ&t3cZm6ikIs|?*F z{qMt-KY`oCQiPkO#S=|axkJ{P=jQ=G;X|vQf6l7up8v<*vJ-k=ys55j9Z^ATwc-JP yX2hZdR3Vo!-zD$-XJG$K^Kz#C|8}rgctIza63iK`9nb?HUJuoD9+cj8}g4HhH- literal 0 HcmV?d00001 diff --git a/apps/control-station/src/App.tsx b/apps/control-station/src/App.tsx index 30e6c14..08bcc2d 100644 --- a/apps/control-station/src/App.tsx +++ b/apps/control-station/src/App.tsx @@ -822,7 +822,7 @@ export default function App() { /> ) : activeDefinition.kind === "missions" ? (

- ) : activeDefinition.kind === "vehicles" ? null : activeDefinition.kind === "datasets" ? ( + ) : activeDefinition.kind === "vehicles" ?
: activeDefinition.kind === "datasets" ? ( Offline evaluation ) : activeDefinition.kind === "lab-archive" ? ( laboratoryAnnotation.control diff --git a/apps/control-station/src/components/rover/RoverTelemetry.tsx b/apps/control-station/src/components/rover/RoverTelemetry.tsx new file mode 100644 index 0000000..482630d --- /dev/null +++ b/apps/control-station/src/components/rover/RoverTelemetry.tsx @@ -0,0 +1,25 @@ +import type {RoverState} from '../../core/fleet/useRoverControl'; +import {drivePositions} from '../../../../../plugins/vesc/frontend/src/model'; +const number=(v:number|undefined,digits=1)=>typeof v==='number'&&Number.isFinite(v)?v.toLocaleString('ru-RU',{maximumFractionDigits:digits}):'—'; +export function RoverTelemetry({state}:{state:RoverState}){ + const devices=state.snapshot.devices??[],profile=state.snapshot.profile; + const positions=drivePositions(profile?.layout??null); + const assigned=Object.entries(profile?.bindings??{}).map(([slot,binding])=>({ + id:binding.device_id,uuid:binding.uuid,label:positions[slot]??slot, + reading:devices.find(d=>d.id===binding.device_id&&d.uuid===binding.uuid), + })); + const readings=[...assigned,...devices.filter(d=>!assigned.some(a=>a.id===d.id&&a.uuid===d.uuid)).map(d=>({...d,reading:d}))]; + return
+ {!state.fresh?

Нет свежих показаний VESC.

:!readings.length?

Получение показаний VESC…

:readings.map(d=>{ + const fresh=!!d.reading&&d.reading.age_ms<1000,v=fresh?d.reading?.values:undefined; + return
{d.label}VESC {d.uuid.slice(0,6).toUpperCase()}{!d.reading?' · Нет связи':!fresh?' · Нет свежих данных':''} +
Ток мотора
{number(v?.motor_current_a)} А
+
Ток батареи
{number(v?.input_current_a)} А
+
Напряжение
{number(v?.input_voltage_v)} В
+
Обороты, ERPM
{number(v?.erpm,0)}
+
Контроллер
{number(v?.mos_temperature_c)} °C
+
Ошибка VESC
{v?v.fault_code===0?'Нет':String(v.fault_code):'—'}
+
; + })} +
; +} diff --git a/apps/control-station/src/components/rover/RoverView.tsx b/apps/control-station/src/components/rover/RoverView.tsx new file mode 100644 index 0000000..7759f4d --- /dev/null +++ b/apps/control-station/src/components/rover/RoverView.tsx @@ -0,0 +1,70 @@ +import {lazy,Suspense,useCallback,useEffect,useRef,useState} from 'react'; +import {createPortal} from 'react-dom'; +import {Button,Icon,IconButton,KeyButton,LoadingRegion,Select,StatusBadge,Switch,TextField,ToastStack,Window} from '@nodedc/ui-react'; +import {keysFor,roverSettings,type RoverMode} from '../../core/fleet/roverInput'; +import {bindRoverHoldInput} from '../../core/fleet/roverHoldInput'; +import type {RoverController} from '../../core/fleet/useRoverControl'; +import type {ObservationHeaderTargets} from '../../../../../packages/sensor-ui/src/observation'; +import './rover.css'; +const Scene=lazy(()=>import('./playcanvas/PlayCanvasViewer')); +const labels:Record={observing:'Наблюдение',preparing:'Подготовка управления',ready:'Управление включено',driving:'Команда движения',receiver:'Управление с пульта',stopping:'Остановка',stopped:'Управление выключено',fault:'Управление остановлено'}; + +export function RoverView({vehicleID,controller:c,header,active}:{vehicleID:string;controller:RoverController;header:ObservationHeaderTargets;active:boolean}){ + const storage=`missioncore.rover-view.v1:${vehicleID}`; + const [settings,setSettings]=useState(()=>{try{return roverSettings(JSON.parse(localStorage.getItem(storage)??'null'));}catch{return roverSettings(null);}}); + const [open,setOpen]=useState(false),[confirmed,setConfirmed]=useState(false),[held,setHeld]=useState>(new Set()); + const [modelState,setModelState]=useState<'loading'|'ready'|'error'>('loading'); + const stage=useRef(null),input=useRef|null>(null); + const stop=useCallback(()=>{input.current?.dispose();input.current=null;c.setInputGuard(null);setHeld(new Set());c.stop();setConfirmed(false);},[c.stop,c.setInputGuard]); + useEffect(()=>{try{localStorage.setItem(storage,JSON.stringify(settings));}catch{}},[storage,settings]); + useEffect(()=>{if(!active)stop();},[active,stop]); + useEffect(()=>{ + if(!c.ready||!active||open||!stage.current)return; + const binding=bindRoverHoldInput(stage.current,settings.mode,{held:setHeld,demand:c.setDemand,pause:c.pauseInput,stop}); + input.current=binding;c.setInputGuard(binding.valid); + return()=>{c.setInputGuard(null);binding.dispose();if(input.current===binding)input.current=null;}; + },[c.ready,active,open,settings.mode,c.setDemand,c.setInputGuard,c.pauseInput,stop]); + const showSettings=()=>{stop();setOpen(true);}; + const preparing=c.pending||(c.armed&&!c.ready); + const availability=c.connecting?'Синхронизация с бортом':!c.state.fresh?'Нет свежих данных с борта':!c.state.snapshot.supported?'Управление на борту недоступно':c.state.controlling&&!c.armed?'Управление уже включено':!c.canArm&&!c.armed?'Завершение предыдущего управления':null; + const tone=preparing||availability?'warning':c.ready?'success':c.state.snapshot.state==='fault'?'warning':'neutral'; + const status=preparing?'Подготовка управления':availability??(labels[c.state.snapshot.state??'']??'Наблюдение'); + return <> + {createPortal(,header.actionsTarget)} + {createPortal(,header.statusTarget)} +
{if(c.armed&&!e.currentTarget.contains(e.relatedTarget as Node|null))c.pauseInput();}}> + {settings.model==='dcd006-v020'? + + {modelState==='error'&&

Не удалось загрузить модель ровера.

} +
:

Выберите модель аппарата.

} +
{status}
+ {c.ready&&
+
+ {keysFor(settings.mode).map(code=>{if(!c.ready||!e.isTrusted||e.button!==0)return;e.preventDefault();e.currentTarget.setPointerCapture(e.pointerId);stage.current?.focus();input.current?.pointerDown(e.pointerId,code);}} + onPointerUp={e=>input.current?.pointerUp(e.pointerId)} + onPointerCancel={e=>input.current?.pointerCancel(e.pointerId)} + onLostPointerCapture={e=>input.current?.pointerCancel(e.pointerId)}>{code.slice(3)})} +
+ {settings.mode==='arcade'?'Аркадный':'Танковый'} · пробел — стоп +
} +
+ {c.armed||c.pending?:} +
+
+ setOpen(false)} title="Управление ровером" size="md">
+ setSettings(v=>({...v,model}))}/> + setSettings(v=>({...v,currentA:Number(e.target.value)}))}/> + setSettings(v=>({...v,maxErpm:Number(e.target.value)}))}/> + + {availability&&{availability}} +

Движение — только при удержании клавиш или кнопок. При уходе из окна команда снимается. Команда с пульта прекращает удалённое управление.

+ +
+ + ; +} diff --git a/apps/control-station/src/components/rover/playcanvas/PROVENANCE.json b/apps/control-station/src/components/rover/playcanvas/PROVENANCE.json new file mode 100644 index 0000000..53ba8be --- /dev/null +++ b/apps/control-station/src/components/rover/playcanvas/PROVENANCE.json @@ -0,0 +1,16 @@ +{ + "source": "NodeDC ThreeDAssetNode / PlayCanvasViewer", + "files": { + "PlayCanvasViewer.tsx": "34cd6df2e47a7714288aeb5afb2859b1058eb91dc1819abbf296c6a8937a1533", + "playcanvasPostFx.ts": "91cfb7f8ba821fef14681c6c25a61f430ce25aae65b0f181d359f38e37ccd014", + "sceneTree.ts": "eead378c2402f69e2c68cd1fc2a5ec9028555dced53858c600c599f613dda516", + "environment-map.png": "793f72dce207c1a4d2bdb262610f688bd655a79066298b672b642f228cf7d230" + }, + "adaptations": [ + "Domain asset URL and error/loading callbacks", + "No fallback to a different vehicle", + "Removed unused node-editor type helper; rendering settings unchanged", + "Observe the host pane on resize; preserve responsive canvas CSS sizing", + "Frame the complete rover using the donor 28-degree FOV and current viewport aspect" + ] +} diff --git a/apps/control-station/src/components/rover/playcanvas/PlayCanvasViewer.tsx b/apps/control-station/src/components/rover/playcanvas/PlayCanvasViewer.tsx new file mode 100644 index 0000000..dc639ae --- /dev/null +++ b/apps/control-station/src/components/rover/playcanvas/PlayCanvasViewer.tsx @@ -0,0 +1,2043 @@ +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import * as pc from 'playcanvas' +import { buildSceneTree } from './sceneTree' +import { mergePostFxDefaults, type PostFxSettings } from './playcanvasPostFx' + +const DEFAULT_MODEL_URL = '' +const PIXELFORMAT_RGBA8 = (pc as any).PIXELFORMAT_RGBA8 ?? 7 +const PIXELFORMAT_111110F = (pc as any).PIXELFORMAT_111110F ?? 18 +const PIXELFORMAT_RGBA16F = (pc as any).PIXELFORMAT_RGBA16F ?? 12 +const PIXELFORMAT_RGBA32F = (pc as any).PIXELFORMAT_RGBA32F ?? 14 + +export type PlayCanvasViewerProps = { + modelUrl: string | null + postFx?: Partial | null + viewportSize?: { width: number; height: number } + onSceneReady?: (api: { app: pc.Application; camera: pc.Entity; modelRoot: pc.Entity }) => void + onSelectionChanged?: (selectedIds: string[]) => void + onSceneGraph?: (tree: ReturnType | null, map?: Map) => void + exposeSceneGraph?: boolean + cameraState?: CameraState | null + cameraResetToken?: number | null + cameraZoomStrength?: number | null + cameraInertiaFactor?: number | null + cameraInertiaTime?: number | null + cameraAutoRotate?: boolean | null + onCameraChange?: (state: CameraState, commit?: boolean) => void + onModelState?: (state: "loading" | "ready" | "error") => void + muteLogs?: boolean +} + +export type CameraState = { + pivot: { x: number; y: number; z: number } + distance: number + azimuthDeg: number + elevationDeg: number +} + +// ------------------------------------------------------------ +// Fallback texture handler +class SimpleTextureHandler { + private gd: pc.GraphicsDevice + constructor(gd: pc.GraphicsDevice) { + this.gd = gd + } + load(url: string, callback: (err: any, data?: any) => void) { + ;(async () => { + const res = await fetch(url) + if (!res.ok) throw new Error(`Failed to fetch texture: ${url} (${res.status})`) + const blob = await res.blob() + const bmp = await createImageBitmap(blob) + callback(null, bmp) + })().catch((e) => callback(e)) + } + open(_url: string, data: ImageBitmap) { + const tex = new pc.Texture(this.gd, { + mipmaps: true, + } as any) + // ImageBitmap is supported by the pinned PlayCanvas runtime. + tex.setSource(data as unknown as HTMLImageElement) + return tex + } + patch() {} +} + +function ensureTextureHandler(app: pc.Application) { + try { + if (!app?.graphicsDevice) return + + const fallback = new SimpleTextureHandler(app.graphicsDevice) + const textureCtor = (pc as any).TextureHandler + const buildNative = () => { + if (!textureCtor) return null + try { return new textureCtor(app.graphicsDevice, (app as any).assets) } catch {} + try { return new textureCtor(app.graphicsDevice) } catch {} + try { return new textureCtor(app) } catch {} + return null + } + const nativeHandler = buildNative() + const handler = nativeHandler || fallback + const candidates: any[] = [] + if ((app as any)?.loader) candidates.push((app as any).loader) + if ((app as any)?.assets?.loader) candidates.push((app as any).assets.loader) + if ((app as any)?.assets?._loader) candidates.push((app as any).assets._loader) + if ((app as any)?.assets) candidates.push((app as any).assets) + + for (const loader of candidates) { + if (!loader) continue + + const hasGet = typeof loader.getHandler === 'function' + const hasAdd = typeof loader.addHandler === 'function' + + if (!hasGet || !hasAdd) { + if (loader._handlers && !loader._handlers.texture) { + loader._handlers.texture = handler + } + continue + } + + if (!loader.getHandler('texture')) { + try { loader.addHandler('texture', handler) } catch {} + } + if (loader._handlers && !loader._handlers.texture) { + loader._handlers.texture = handler + } + } + } catch (e) { + console.warn('[PlayCanvasViewer] Failed to register fallback texture handler', e) + } +} + +// ------------------------------------------------------------ +// Environment / HDR helpers +function destroyTexture(texture: pc.Texture | null) { + if (!texture) return + try { texture.destroy?.() } catch {} +} + +function createGradientEquirectTexture( + app: pc.Application, + colorA: string, + colorB: string, + size = 256 +): pc.Texture | null { + try { + const gd: any = (app as any).graphicsDevice ?? (app as any).device + if (!gd || !(pc as any).Texture) return null + + const width = size * 2 + const height = size + const canvas = document.createElement('canvas') + canvas.width = width + canvas.height = height + const ctx = canvas.getContext('2d') + if (!ctx) return null + + const grad = ctx.createLinearGradient(0, 0, width, 0) + grad.addColorStop(0, colorA) + grad.addColorStop(1, colorB) + ctx.fillStyle = grad + ctx.fillRect(0, 0, width, height) + + const tex = new pc.Texture(gd, { + width, + height, + mipmaps: true, + format: (pc as any).PIXELFORMAT_RGBA8 ?? undefined, + addressU: pc.ADDRESS_CLAMP_TO_EDGE, + addressV: pc.ADDRESS_CLAMP_TO_EDGE, + } as any) + + if ((pc as any).TEXTUREPROJECTION_EQUIRECT) { + ;(tex as any).projection = (pc as any).TEXTUREPROJECTION_EQUIRECT + } + + tex.setSource(canvas) + return tex + } catch (e) { + console.warn('[PlayCanvasViewer] Failed to create gradient equirect texture', e) + return null + } +} + +function generateSkyboxResources( + app: pc.Application, + colorA: string, + colorB: string +): { + source: pc.Texture | null + lightingSource: pc.Texture | null + cubemap: pc.Texture | null + atlas: pc.Texture | null +} { + const source = createGradientEquirectTexture(app, colorA, colorB) + if (!source) return { source: null, lightingSource: null, cubemap: null, atlas: null } + + try { + const env: any = (pc as any).EnvLighting + if (!env?.generateLightingSource || !env?.generateAtlas) { + return { source, lightingSource: null, cubemap: null, atlas: null } + } + + const lightingSource = env.generateLightingSource(source, { size: 128 }) + const atlas = env.generateAtlas(lightingSource) + const cubemap = env.generateSkyboxCubemap + ? env.generateSkyboxCubemap(source, 128) + : lightingSource + + return { source, lightingSource, cubemap, atlas } + } catch (e) { + console.warn('[PlayCanvasViewer] Failed to generate skybox resources', e) + return { source, lightingSource: null, cubemap: null, atlas: null } + } +} + +function applySceneEnvironment( + app: pc.Application, + opts: { + envAtlas: pc.Texture | null + skybox: pc.Texture | null + intensity: number + mip: number + rotationDeg: number + } +) { + const scene: any = app.scene + if (!scene) return + + scene.envAtlas = opts.envAtlas || null + scene.skybox = opts.skybox || null + if (typeof scene.skyboxIntensity === 'number') scene.skyboxIntensity = opts.intensity + if (typeof scene.skyboxMip === 'number') scene.skyboxMip = opts.mip + + const rotRad = (opts.rotationDeg * Math.PI) / 180 + try { + if (scene.skyboxRotation && (pc as any).Quat) { + const q = new (pc as any).Quat() + q.setFromEulerAngles(0, opts.rotationDeg, 0) + scene.skyboxRotation = q + } else if (typeof scene.setSkyboxRotation === 'function') { + scene.setSkyboxRotation(0, rotRad, 0) + } + } catch {} +} + +function clamp01(v: number) { + return Math.max(0, Math.min(1, v)) +} + +function toneMapColor(x: number, mode: number) { + const v = Math.max(0, x) + switch (mode) { + case 1: { + const A = 0.15 + const B = 0.50 + const C = 0.10 + const D = 0.20 + const E = 0.02 + const F = 0.30 + const num = v * (A * v + C * B) + D * E + const den = v * (A * v + B) + D * F + return clamp01(num / den - E / F) + } + case 2: { + const t = Math.max(0, v - 0.004) + return clamp01((t * (6.2 * t + 0.5)) / (t * (6.2 * t + 1.7) + 0.06)) + } + case 3: + case 4: { + return clamp01((v * (2.51 * v + 0.03)) / (v * (2.43 * v + 0.59) + 0.14)) + } + case 5: { + return clamp01(v / (1 + v)) + } + case 0: + default: + return clamp01(v) + } +} + +function applyMaterialEnvOverride( + app: pc.Application, + opts: { + enabled: boolean + envAtlas: pc.Texture | null + cubeMap: pc.Texture | null + useSkybox?: boolean + reflectivityScale?: number | null + } +) { + const anyApp = app as any + const store: WeakMap = + anyApp.__ENV_OVERRIDE_STORE__ ?? + (anyApp.__ENV_OVERRIDE_STORE__ = new WeakMap()) + + const setMat = (mat: any) => { + if (!mat || typeof mat !== 'object') return + + if (!store.has(mat)) { + store.set(mat, { + envAtlas: mat.envAtlas ?? null, + cubeMap: mat.cubeMap ?? null, + useSkybox: typeof mat.useSkybox === 'boolean' ? mat.useSkybox : undefined, + reflectivity: typeof mat.reflectivity === 'number' ? mat.reflectivity : undefined, + }) + } + + if (!opts.enabled) { + const prev = store.get(mat) + if (prev) { + mat.envAtlas = prev.envAtlas ?? null + mat.cubeMap = prev.cubeMap ?? null + if (typeof prev.useSkybox === 'boolean') mat.useSkybox = prev.useSkybox + if (typeof prev.reflectivity === 'number') mat.reflectivity = prev.reflectivity + mat.update?.() + } + return + } + + mat.envAtlas = opts.envAtlas ?? null + mat.cubeMap = opts.cubeMap ?? null + if (typeof opts.useSkybox === 'boolean') mat.useSkybox = opts.useSkybox + if (typeof opts.reflectivityScale === 'number') { + const base = store.get(mat)?.reflectivity + if (typeof base === 'number') { + mat.reflectivity = Math.max(0, Math.min(1, base * opts.reflectivityScale)) + } + } + mat.update?.() + } + + const traverse = (e: pc.Entity) => { + const r = (e as any).render + if (r?.meshInstances?.length) { + for (const mi of r.meshInstances) setMat(mi.material) + } + + const m = (e as any).model + if (m?.meshInstances?.length) { + for (const mi of m.meshInstances) setMat(mi.material) + } + + for (const c of e.children ?? []) if (c instanceof pc.Entity) traverse(c) + } + + const modelRoot = app.root.findByName('ModelRoot') as pc.Entity | null + traverse(modelRoot ?? app.root) +} + +function resolveShadowType(type: string) { + const anyPc: any = pc + switch (type) { + case 'vsm32': + return anyPc.SHADOW_VSM_32F ?? anyPc.SHADOW_VSM32 ?? anyPc.SHADOW_VSM_16F ?? pc.SHADOW_VSM_16F + case 'pcf1': + return anyPc.SHADOW_PCF1 ?? pc.SHADOW_PCF1 + case 'pcf3': + return anyPc.SHADOW_PCF3 ?? pc.SHADOW_PCF3 + case 'vsm16': + default: + return anyPc.SHADOW_VSM_16F ?? anyPc.SHADOW_VSM16 ?? pc.SHADOW_VSM_16F + } +} + +function applyLightAngles(entity: pc.Entity | null, azimuthDeg: number, elevationDeg: number) { + if (!entity) return + entity.setLocalEulerAngles(elevationDeg, azimuthDeg, 0) +} + +type ColorInput = string | { r: number; g: number; b: number; a?: number } | pc.Color | null | undefined + +function toColor(value: ColorInput, fallback?: pc.Color): pc.Color { + if (value instanceof pc.Color) { + return new pc.Color(value.r, value.g, value.b, value.a) + } + if (value && typeof value === 'object') { + const r = Number((value as any).r) + const g = Number((value as any).g) + const b = Number((value as any).b) + const a = Number.isFinite((value as any).a) ? Number((value as any).a) : 1 + if (Number.isFinite(r) && Number.isFinite(g) && Number.isFinite(b)) { + return new pc.Color( + Math.max(0, Math.min(1, r)), + Math.max(0, Math.min(1, g)), + Math.max(0, Math.min(1, b)), + Math.max(0, Math.min(1, a)) + ) + } + } + if (typeof value === 'string') { + const hex = value.trim() + const full = /^#?([0-9a-f]{6})$/i.exec(hex) + const short = /^#?([0-9a-f]{3})$/i.exec(hex) + if (full) { + const n = parseInt(full[1], 16) + const r = ((n >> 16) & 255) / 255 + const g = ((n >> 8) & 255) / 255 + const b = (n & 255) / 255 + return new pc.Color(r, g, b) + } + if (short) { + const s = short[1] + const n = parseInt(`${s[0]}${s[0]}${s[1]}${s[1]}${s[2]}${s[2]}`, 16) + const r = ((n >> 16) & 255) / 255 + const g = ((n >> 8) & 255) / 255 + const b = (n & 255) / 255 + return new pc.Color(r, g, b) + } + } + return fallback ? new pc.Color(fallback.r, fallback.g, fallback.b, fallback.a) : new pc.Color(0, 0, 0) +} + +function setWireframeForScene(app: pc.Application, enabled: boolean) { + const renders = app.root.findComponents('render') as any[] + for (const r of renders) { + const meshInstances = r?.meshInstances ?? [] + for (const mi of meshInstances) { + const mat = mi.material as any + if (mat && 'wireframe' in mat) { + mat.wireframe = enabled + mat.update?.() + } + } + } +} + +function collectMeshInstances(entity: pc.Entity | null, out: any[] = []): any[] { + if (!entity) return out + const render = (entity as any).render + const model = (entity as any).model + if (render?.meshInstances?.length) out.push(...render.meshInstances) + if (model?.meshInstances?.length) out.push(...model.meshInstances) + for (const child of entity.children ?? []) collectMeshInstances(child as any, out) + return out +} + +function disableInvalidMeshes(root: pc.Entity | null) { + if (!root) return + const meshInstances = collectMeshInstances(root) + meshInstances.forEach((mi: any) => { + const mesh = mi?.mesh + const vb = mesh?.vertexBuffer + const numVertices = vb?.numVertices ?? 0 + if (!mesh || !numVertices) return + const hasIndex = Array.isArray(mesh.indexBuffer) ? mesh.indexBuffer.length > 0 : !!mesh.indexBuffer + if (!hasIndex) return + try { + const indices: number[] = [] + const count = mesh.getIndices(indices) + if (!count || !indices.length) return + let maxIndex = -1 + for (let i = 0; i < count; i += 1) { + const v = indices[i] ?? -1 + if (v > maxIndex) maxIndex = v + } + if (maxIndex >= numVertices) { + mi.visible = false + // eslint-disable-next-line no-console + console.warn('[PlayCanvasViewer] Disabled mesh with invalid indices', { + numVertices, + maxIndex, + mesh: mesh?.name, + }) + } + } catch {} + }) +} + +function computeAabb(entity: pc.Entity): pc.BoundingBox | null { + const renders = entity.findComponents('render') as any[] + if (!renders?.length) return null + + let out: pc.BoundingBox | null = null + for (const r of renders) { + const meshInstances = r?.meshInstances ?? [] + for (const mi of meshInstances) { + const aabb = mi?.aabb as pc.BoundingBox | undefined + if (!aabb) continue + if (!out) out = aabb.clone() + else out.add(aabb) + } + } + return out +} + +type OrbitOptions = { + pivot: pc.Vec3 + distance: number + minDistance: number + maxDistance: number + zoomStrength?: number + minElevationDeg?: number + maxElevationDeg?: number + inertiaFactor?: number + inertiaTime?: number + initialAzimuthDeg?: number + initialElevationDeg?: number + autoRotate?: boolean + autoRotateSpeed?: number + autoRotateDelay?: number + autoRotateFade?: number + autoRotatePitchSpeed?: number + autoRotatePitchAmount?: number + onChange?: (state: CameraState, commit: boolean) => void +} + +type OrbitController = { + setPivot(v: pc.Vec3): void + setDistance(d: number): void + setZoomStrength(value?: number | null): void + setInertiaFactor(value?: number | null): void + setInertiaTime(value?: number | null): void + setAutoRotate(value?: boolean | null): void + setState(state: CameraState): void + getState(): CameraState + destroy(): void +} + +function makeOrbitController(app: pc.Application, cameraEntity: pc.Entity, opts: OrbitOptions): OrbitController { + const canvas = app.graphicsDevice.canvas as HTMLCanvasElement + + let pivot = opts.pivot.clone() + let distance = opts.distance + let azimuth = ((opts.initialAzimuthDeg ?? 45) * Math.PI) / 180 + let elevation = ((opts.initialElevationDeg ?? 25) * Math.PI) / 180 + + let targetAzimuth = azimuth + let targetElevation = elevation + let targetDistance = distance + + const minEl = ((opts.minElevationDeg ?? 5) * Math.PI) / 180 + const maxEl = ((opts.maxElevationDeg ?? 89) * Math.PI) / 180 + + let dragging = false + let panning = false + let lastX = 0 + let lastY = 0 + let idleTime = 0 + let wheelCommitTimer: number | null = null + + const toDeg = (rad: number) => (rad * 180) / Math.PI + const toRad = (deg: number) => (deg * Math.PI) / 180 + const clampZoom = (value?: number | null) => { + const n = Number(value) + if (!Number.isFinite(n)) return 0.04 + return Math.max(0.001, Math.min(0.5, n)) + } + const clampInertia = (value?: number | null) => { + const n = Number(value) + if (!Number.isFinite(n)) return 0 + return Math.max(0, Math.min(1, n)) + } + const clampInertiaTime = (value?: number | null) => { + const n = Number(value) + if (!Number.isFinite(n)) return 0.05 + return Math.max(0.02, Math.min(10, n)) + } + let zoomStrength = clampZoom(opts.zoomStrength) + let inertia = clampInertia(opts.inertiaFactor) + let inertiaTime = clampInertiaTime(opts.inertiaTime) + let autoRotate = opts.autoRotate !== false + + const snapshot = (useTarget = true): CameraState => ({ + pivot: { x: pivot.x, y: pivot.y, z: pivot.z }, + distance: useTarget ? targetDistance : distance, + azimuthDeg: toDeg(useTarget ? targetAzimuth : azimuth), + elevationDeg: toDeg(useTarget ? targetElevation : elevation), + }) + + const emitChange = (commit: boolean) => { + if (!opts.onChange) return + opts.onChange(snapshot(true), commit) + } + + const updateCamera = (dt = 0) => { + if (autoRotate && !dragging && !panning) { + idleTime += dt + const delay = Math.max(0, opts.autoRotateDelay ?? 0) + if (idleTime > delay) { + const t = idleTime - delay + const fadeTime = Math.max(0.001, opts.autoRotateFade ?? 0.001) + const x = t / fadeTime + const fade = x <= 0 ? 0 : x >= 1 ? 1 : 0.5 * Math.sin((x - 0.5) * Math.PI) + 0.5 + const speed = ((opts.autoRotateSpeed ?? 0) * Math.PI) / 180 + const pitchSpeed = Number(opts.autoRotatePitchSpeed ?? 0) + const pitchAmount = ((opts.autoRotatePitchAmount ?? 0) * Math.PI) / 180 + targetAzimuth += speed * fade * dt + if (pitchSpeed && pitchAmount) { + targetElevation += Math.sin(t * pitchSpeed) * fade * dt * pitchAmount + } + } + } + + targetElevation = Math.max(minEl, Math.min(maxEl, targetElevation)) + targetDistance = Math.max(opts.minDistance, Math.min(opts.maxDistance, targetDistance)) + + const step = dt > 0 ? dt : 1 / 60 + const timeFactor = inertiaTime > 0 ? 1 - Math.exp(-step / inertiaTime) : 1 + const smooth = inertia <= 0 ? 1 : (1 - inertia) + inertia * timeFactor + + azimuth += (targetAzimuth - azimuth) * smooth + elevation += (targetElevation - elevation) * smooth + distance += (targetDistance - distance) * smooth + + const x = pivot.x + distance * Math.cos(elevation) * Math.sin(azimuth) + const y = pivot.y + distance * Math.sin(elevation) + const z = pivot.z + distance * Math.cos(elevation) * Math.cos(azimuth) + + cameraEntity.setPosition(x, y, z) + cameraEntity.lookAt(pivot) + } + + const onMouseDown = (e: MouseEvent) => { + if (e.button === 0) dragging = true + if (e.button === 2) panning = true + lastX = e.clientX + lastY = e.clientY + idleTime = 0 + } + + const onMouseMove = (e: MouseEvent) => { + if (!dragging && !panning) return + const dx = e.clientX - lastX + const dy = e.clientY - lastY + lastX = e.clientX + lastY = e.clientY + idleTime = 0 + + if (dragging) { + targetAzimuth -= dx * 0.005 + targetElevation -= dy * 0.005 + } else if (panning) { + const s = distance * 0.0018 + const right = cameraEntity.right.clone().mulScalar(-dx * s) + const up = cameraEntity.up.clone().mulScalar(dy * s) + pivot = pivot.add(right).add(up) + } + updateCamera(0) + emitChange(false) + } + + const onMouseUp = () => { + dragging = false + panning = false + idleTime = 0 + emitChange(true) + } + + const onWheel = (e: WheelEvent) => { + idleTime = 0 + const dir = Math.sign(e.deltaY) + if (dir) targetDistance *= 1 + dir * zoomStrength + updateCamera(0) + emitChange(false) + if (wheelCommitTimer) window.clearTimeout(wheelCommitTimer) + wheelCommitTimer = window.setTimeout(() => { + wheelCommitTimer = null + emitChange(true) + }, 180) + } + + const onContextMenu = (e: Event) => e.preventDefault() + + canvas.addEventListener('mousedown', onMouseDown) + window.addEventListener('mousemove', onMouseMove) + window.addEventListener('mouseup', onMouseUp) + canvas.addEventListener('wheel', onWheel, { passive: true }) + canvas.addEventListener('contextmenu', onContextMenu) + + app.on('update', updateCamera as any) + updateCamera(0) + + const api: OrbitController = { + setPivot(v: pc.Vec3) { + pivot = v.clone() + updateCamera() + }, + setDistance(d: number) { + targetDistance = d + updateCamera() + }, + setZoomStrength(value?: number | null) { + zoomStrength = clampZoom(value) + }, + setInertiaFactor(value?: number | null) { + inertia = clampInertia(value) + }, + setInertiaTime(value?: number | null) { + inertiaTime = clampInertiaTime(value) + }, + setAutoRotate(value?: boolean | null) { + autoRotate = value !== false + if (!autoRotate) idleTime = 0 + }, + setState(state: CameraState) { + if (!state) return + const nextPivot = state.pivot || { x: 0, y: 0, z: 0 } + pivot = new pc.Vec3( + Number(nextPivot.x) || 0, + Number(nextPivot.y) || 0, + Number(nextPivot.z) || 0, + ) + const nextAz = toRad(Number(state.azimuthDeg) || 0) + const nextEl = toRad(Number(state.elevationDeg) || 0) + azimuth = nextAz + elevation = nextEl + targetAzimuth = nextAz + targetElevation = nextEl + const nextDistance = Number(state.distance) || distance + distance = nextDistance + targetDistance = nextDistance + updateCamera(0) + }, + getState() { + return snapshot(true) + }, + destroy() { + canvas.removeEventListener('mousedown', onMouseDown) + window.removeEventListener('mousemove', onMouseMove) + window.removeEventListener('mouseup', onMouseUp) + canvas.removeEventListener('wheel', onWheel as any) + canvas.removeEventListener('contextmenu', onContextMenu) + app.off('update', updateCamera) + if (wheelCommitTimer) window.clearTimeout(wheelCommitTimer) + }, + } + + ;(app as any).__orbit = api + return api +} + +type RenderOverrides = { renderFormats: number[]; samples: number } + +function resolveRenderOverrides(fx: PostFxSettings, device: pc.GraphicsDevice | null): RenderOverrides { + const maxSamples = Number.isFinite((device as any)?.maxSamples) ? Number((device as any).maxSamples) : 1 + const rawSamples = Number(fx.rendering.samples) + let samples = Number.isFinite(rawSamples) ? Math.round(rawSamples) : 1 + samples = Math.max(1, samples || 1) + samples = Math.min(samples, Math.max(1, maxSamples)) + const originalSamples = samples + const forceSingleSample = samples > 1 + if (forceSingleSample) samples = 1 + + const primary = Number(fx.rendering.renderFormat) + if (primary === PIXELFORMAT_RGBA8) { + return { renderFormats: [PIXELFORMAT_RGBA8], samples } + } + + const candidates = [ + primary, + fx.rendering.renderFormatFallback0, + fx.rendering.renderFormatFallback1, + ] + + const canUseFormat = (fmt: number, sampleCount: number) => { + if (!device) return fmt !== PIXELFORMAT_RGBA8 + if (fmt === PIXELFORMAT_111110F) return !!(device as any).textureRG11B10Renderable + if (fmt === PIXELFORMAT_RGBA16F) return !!(device as any).textureHalfFloatRenderable + if (fmt === PIXELFORMAT_RGBA32F) { + const floatRenderable = !!(device as any).textureFloatRenderable + const floatFilterable = (device as any).textureFloatFilterable !== false + if (!floatRenderable || !floatFilterable) return false + if ((device as any).isWebGPU && sampleCount > 1) return false + return true + } + return fmt !== PIXELFORMAT_RGBA8 + } + + const buildFormats = (sampleCount: number) => { + const list: number[] = [] + for (const candidate of candidates) { + const fmt = Number(candidate) + if (!Number.isFinite(fmt)) continue + if (!canUseFormat(fmt, sampleCount)) continue + if (!list.includes(fmt)) list.push(fmt) + } + return list + } + + let renderFormats = buildFormats(samples) + if (!renderFormats.length && samples > 1) { + const singleSampleFormats = buildFormats(1) + if (singleSampleFormats.length) { + samples = 1 + renderFormats = singleSampleFormats + } + } + if (samples > 1 && renderFormats.length > 0) { + samples = 1 + } + if (!renderFormats.length) { + samples = forceSingleSample ? 1 : originalSamples + renderFormats = [PIXELFORMAT_RGBA8] + } + + return { renderFormats, samples } +} + +function applyPostFx( + frame: any, + fx: PostFxSettings, + lutTexture: pc.Texture | null, + overrides?: RenderOverrides +) { + if (!frame) return + + if (frame.rendering) { + if (Array.isArray(frame.rendering.renderFormats)) { + const formats = Array.isArray(overrides?.renderFormats) ? overrides?.renderFormats : (() => { + const list: number[] = [] + if (Number.isFinite(fx.rendering.renderFormat)) list.push(fx.rendering.renderFormat) + if (Number.isFinite(fx.rendering.renderFormatFallback0)) list.push(fx.rendering.renderFormatFallback0) + if (Number.isFinite(fx.rendering.renderFormatFallback1)) list.push(fx.rendering.renderFormatFallback1) + return list + })() + if (formats.length) { + frame.rendering.renderFormats.length = 0 + frame.rendering.renderFormats.push(...formats) + } + } else if (Array.isArray(overrides?.renderFormats) && overrides?.renderFormats.length) { + const fmt = overrides.renderFormats[0] + if (Number.isFinite(fmt)) frame.rendering.renderFormat = fmt + } + if (typeof frame.rendering.stencil === 'boolean') { + frame.rendering.stencil = !!fx.rendering.stencil + } + frame.rendering.renderTargetScale = fx.rendering.renderTargetScale + frame.rendering.samples = Number.isFinite(overrides?.samples) ? Number(overrides?.samples) : fx.rendering.samples + frame.rendering.sceneColorMap = true + frame.rendering.sceneDepthMap = !!fx.rendering.sceneDepthMap + frame.rendering.toneMapping = fx.rendering.toneMapping + frame.rendering.sharpness = fx.rendering.sharpness + } + + if (frame.ssao) { + frame.ssao.type = fx.ssao.type + frame.ssao.blurEnabled = !!fx.ssao.blurEnabled + if (fx.ssao.type !== 'none') { + frame.ssao.intensity = fx.ssao.intensity + frame.ssao.radius = fx.ssao.radius + frame.ssao.samples = fx.ssao.samples + frame.ssao.power = fx.ssao.power + frame.ssao.minAngle = fx.ssao.minAngle + frame.ssao.scale = fx.ssao.scale + } + } + + if (frame.bloom) { + const enabled = !!fx.bloom.enabled + frame.bloom.enabled = enabled + frame.bloom.intensity = enabled ? fx.bloom.intensity : 0 + if (enabled) { + const blurLevel = + Number.isFinite((fx.bloom as any).lastMipLevel) + ? Number((fx.bloom as any).lastMipLevel) + : Number((fx.bloom as any).blurLevel) + if (Number.isFinite(blurLevel)) frame.bloom.blurLevel = blurLevel + } + } + + if (frame.grading) { + frame.grading.enabled = !!fx.grading.enabled + if (fx.grading.enabled) { + frame.grading.brightness = fx.grading.brightness + frame.grading.contrast = fx.grading.contrast + frame.grading.saturation = fx.grading.saturation + const c = toColor(fx.grading.tint) + frame.grading.tint?.copy?.(c) ?? (frame.grading.tint = c) + } + } + + if (frame.colorLUT) { + frame.colorLUT.texture = lutTexture || null + frame.colorLUT.intensity = fx.lut.intensity + } + + if (frame.vignette) { + const enabled = !!fx.vignette.enabled + frame.vignette.enabled = enabled + frame.vignette.intensity = enabled ? fx.vignette.intensity : 0 + if (enabled) { + frame.vignette.inner = fx.vignette.inner + frame.vignette.outer = fx.vignette.outer + frame.vignette.curvature = fx.vignette.curvature + const c = toColor(fx.vignette.color) + frame.vignette.color?.copy?.(c) ?? (frame.vignette.color = c) + } + } + + if (frame.taa) { + frame.taa.enabled = !!fx.taa.enabled + if (fx.taa.enabled) frame.taa.jitter = fx.taa.jitter + } + + if (frame.fringing) { + const enabled = !!fx.chromaticAberration.enabled + frame.fringing.enabled = enabled + frame.fringing.intensity = enabled ? fx.chromaticAberration.intensity : 0 + } + + frame.update?.() +} + +export default function PlayCanvasViewer({ + modelUrl, + postFx, + viewportSize, + onSceneReady, + onSelectionChanged, + onSceneGraph, + exposeSceneGraph = false, + cameraState, + cameraResetToken, + cameraZoomStrength, + cameraInertiaFactor, + cameraInertiaTime, + cameraAutoRotate, + onCameraChange, + muteLogs = false, + onModelState, +}: PlayCanvasViewerProps) { + const canvasRef = useRef(null) + const appRef = useRef(null) + const cameraEntityRef = useRef(null) + const cameraFrameRef = useRef(null) + const modelAssetRef = useRef(null) + const envAtlasRef = useRef(null) + const envAtlasImageRef = useRef(null) + const envAtlasAdjustKeyRef = useRef(null) + const skyboxSourceRef = useRef(null) + const skyboxLightingSourceRef = useRef(null) + const skyboxCubemapRef = useRef(null) + const skyboxAtlasRef = useRef(null) + const lutTextureRef = useRef(null) + const directionalLightRef = useRef(null) + const shadowCatcherRef = useRef(null) + const shadowCatcherLightRef = useRef(null) + const shadowCatcherPlaneRef = useRef(null) + const gridEntityRef = useRef(null) + const gridMaterialRef = useRef(null) + const sceneMapRef = useRef>(new Map()) + const loadTokenRef = useRef(0) + const cameraStateRef = useRef(null) + const onCameraChangeRef = useRef(null) + const logWarn = useCallback((...args: any[]) => { if (!muteLogs) console.warn(...args) }, [muteLogs]) + const logError = useCallback((...args: any[]) => { if (!muteLogs) console.error(...args) }, [muteLogs]) + const [appReady, setAppReady] = useState(false) + const [envAtlasVersion, setEnvAtlasVersion] = useState(0) + const [skyboxVersion, setSkyboxVersion] = useState(0) + const [modelVersion, setModelVersion] = useState(0) + + const fx = useMemo(() => mergePostFxDefaults(postFx), [postFx]) + + useEffect(() => { + cameraStateRef.current = cameraState ?? null + }, [cameraState]) + + useEffect(() => { + onCameraChangeRef.current = onCameraChange ?? null + }, [onCameraChange]) + + const resizeCanvas = React.useCallback((width?: number, height?: number) => { + const canvas = canvasRef.current + const inst = appRef.current + if (!canvas || !inst) return + const w = Math.max(1, Math.round(width ?? canvas.parentElement?.clientWidth ?? canvas.clientWidth)) + const h = Math.max(1, Math.round(height ?? canvas.parentElement?.clientHeight ?? canvas.clientHeight)) + if (canvas.width !== w) canvas.width = w + if (canvas.height !== h) canvas.height = h + try { + if (inst.resizeCanvas.length >= 2) { + inst.resizeCanvas(w, h) + } else { + inst.resizeCanvas() + } + } catch {} + // PlayCanvas writes fixed pixel CSS sizes; the hosted pane remains fluid. + canvas.style.width = '100%' + canvas.style.height = '100%' + const model = inst.root.findByName('Model') as pc.Entity | null + const bounds = model ? computeAabb(model) : null + const orbit = (inst as any).__orbit as OrbitController | undefined + if (bounds && orbit) { + const vertical = (cameraEntityRef.current?.camera?.fov ?? 28) * Math.PI / 360 + const horizontal = Math.atan(Math.tan(vertical) * w / h) + orbit.setDistance(Math.max(1.8, bounds.halfExtents.length() / Math.sin(Math.min(vertical, horizontal)) * 1.1)) + } + }, []) + + useEffect(() => { + const canvas = canvasRef.current + if (!canvas) return + + const app = new pc.Application(canvas, { + mouse: new pc.Mouse(canvas), + touch: new pc.TouchDevice(canvas), + keyboard: new pc.Keyboard(window), + }) + + appRef.current = app + + app.setCanvasFillMode((pc as any).FILLMODE_NONE ?? pc.FILLMODE_NONE) + app.setCanvasResolution(pc.RESOLUTION_AUTO) + app.scene.exposure = 1.0 + const renderOverrides = resolveRenderOverrides(fx, (app as any).graphicsDevice ?? (app as any).device ?? null) + + const ensureHandler = (type: string, Ctor: any) => { + const loaderAny: any = (app as any).loader + if (!loaderAny || !Ctor) return + + try { + if (typeof loaderAny.getHandler === 'function' && loaderAny.getHandler(type)) return + } catch {} + + const tryCreate = () => { + try { return new Ctor((app as any).graphicsDevice ?? (app as any).device ?? app) } catch {} + try { return new Ctor(app) } catch {} + try { return new Ctor((app as any).graphicsDevice, (app as any).assets) } catch {} + try { return new Ctor((app as any).graphicsDevice, (app as any).assets, (app as any).loader) } catch {} + return null + } + + const handler = tryCreate() + if (!handler) return + + try { + loaderAny.addHandler(type, handler) + } catch (e) { + logWarn(`[PlayCanvasViewer] Failed to register handler ${type}`, e) + } + } + + ensureHandler('texture', (pc as any).TextureHandler) + ensureHandler('container', (pc as any).ContainerHandler) + ensureTextureHandler(app) + + const modelRoot = new pc.Entity('ModelRoot') + app.root.addChild(modelRoot) + + app.scene.ambientLight = new pc.Color(0, 0, 0) + + const camera = new pc.Entity('Camera') + camera.addComponent('camera', { + clearColor: toColor(fx.rendering.backgroundColor), + fov: 28, + farClip: 500, + nearClip: 0.05, + }) + app.root.addChild(camera) + cameraEntityRef.current = camera + + try { + const CameraFrameCtor: any = (pc as any).CameraFrame + if (CameraFrameCtor) { + const cf = new CameraFrameCtor(app, camera.camera) + cameraFrameRef.current = cf + applyPostFx(cf, fx, lutTextureRef.current, renderOverrides) + try { cf.update?.() } catch {} + } + } catch (e) { + logWarn('[PlayCanvasViewer] CameraFrame init failed:', e) + cameraFrameRef.current = null + } + + const cam = camera.camera! + ;(cam as any).gammaCorrection = pc.GAMMA_SRGB + ;(cam as any).toneMapping = fx.rendering.toneMapping + + if (typeof (cam as any).requestSceneColorMap === 'function') { + ;(cam as any).requestSceneColorMap(true) + } + if (typeof (cam as any).requestSceneDepthMap === 'function') { + ;(cam as any).requestSceneDepthMap(true) + } + if (Array.isArray((cam as any).layers) && !(cam as any).layers.includes(pc.LAYERID_DEPTH)) { + ;(cam as any).layers = [...(cam as any).layers, pc.LAYERID_DEPTH] + } + + const initialCamera = cameraStateRef.current + const orbit = makeOrbitController(app, camera, { + pivot: initialCamera + ? new pc.Vec3( + Number(initialCamera.pivot?.x) || 0, + Number(initialCamera.pivot?.y) || 0, + Number(initialCamera.pivot?.z) || 0, + ) + : new pc.Vec3(0, 0, 0), + distance: Number(initialCamera?.distance) || 6, + minDistance: 0.05, + maxDistance: 500, + minElevationDeg: 5, + maxElevationDeg: 90, + initialAzimuthDeg: Number.isFinite(initialCamera?.azimuthDeg) ? Number(initialCamera?.azimuthDeg) : 45, + initialElevationDeg: Number.isFinite(initialCamera?.elevationDeg) ? Number(initialCamera?.elevationDeg) : 10, + autoRotate: cameraAutoRotate !== false, + autoRotateSpeed: 4, + autoRotateDelay: 4, + autoRotateFade: 5, + autoRotatePitchSpeed: 0, + autoRotatePitchAmount: 1, + zoomStrength: cameraZoomStrength ?? undefined, + inertiaFactor: cameraInertiaFactor ?? undefined, + inertiaTime: cameraInertiaTime ?? undefined, + onChange: (state, commit) => { + onCameraChangeRef.current?.(state, commit) + }, + }) + + const directionalLight = new pc.Entity('DirectionalLight') + directionalLight.addComponent('light', { + type: 'directional', + }) + app.root.addChild(directionalLight) + directionalLightRef.current = directionalLight + + const gridEntity = new pc.Entity('Grid') + gridEntity.addComponent('render', { castShadows: false }) + const gridMaterial = new pc.ShaderMaterial({ + uniqueName: 'grid-shader', + vertexGLSL: ` + attribute vec3 vertex_position; + attribute vec2 aUv0; + + uniform mat4 matrix_model; + uniform mat4 matrix_viewProjection; + + varying vec2 uv0; + + void main(void) { + gl_Position = matrix_viewProjection * matrix_model * vec4(vertex_position, 1.0); + uv0 = aUv0; + } +`, + fragmentGLSL: ` + uniform vec2 uHalfExtents; + uniform vec3 uColorX; + uniform vec3 uColorZ; + uniform vec3 uColorMain; + uniform float uAlphaX; + uniform float uAlphaZ; + uniform float uAlphaMain; + uniform int uResolution; + uniform vec3 uDotColor; + uniform float uDotAlpha; + uniform float uDotSize; + uniform vec3 uCrossColor; + uniform float uCrossAlpha; + uniform float uCrossLength; + uniform float uCrossWidth; + uniform float uFadeStart; + uniform float uFadeEnd; + + varying vec2 uv0; + + // https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8#1e7c + float pristineGrid(in vec2 uv, in vec2 ddx, in vec2 ddy, vec2 lineWidth) { + vec2 uvDeriv = vec2(length(vec2(ddx.x, ddy.x)), length(vec2(ddx.y, ddy.y))); + bvec2 invertLine = bvec2(lineWidth.x > 0.5, lineWidth.y > 0.5); + vec2 targetWidth = vec2( + invertLine.x ? 1.0 - lineWidth.x : lineWidth.x, + invertLine.y ? 1.0 - lineWidth.y : lineWidth.y + ); + vec2 drawWidth = clamp(targetWidth, uvDeriv, vec2(0.5)); + vec2 lineAA = uvDeriv * 1.5; + vec2 gridUV = abs(fract(uv) * 2.0 - 1.0); + gridUV.x = invertLine.x ? gridUV.x : 1.0 - gridUV.x; + gridUV.y = invertLine.y ? gridUV.y : 1.0 - gridUV.y; + vec2 grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV); + + grid2 *= clamp(targetWidth / drawWidth, 0.0, 1.0); + grid2 = mix(grid2, targetWidth, clamp(uvDeriv * 2.0 - 1.0, 0.0, 1.0)); + grid2.x = invertLine.x ? 1.0 - grid2.x : grid2.x; + grid2.y = invertLine.y ? 1.0 - grid2.y : grid2.y; + + return mix(grid2.x, 1.0, grid2.y); + } + + float dotMask(vec2 levelPos, float radius) { + if (radius <= 0.0) { + return 0.0; + } + vec2 dist = min(fract(levelPos), 1.0 - fract(levelPos)); + float d = length(dist); + float aa = max(fwidth(levelPos.x), fwidth(levelPos.y)); + return 1.0 - smoothstep(radius - aa, radius + aa, d); + } + + float crossMask(vec2 levelPos, float halfLen, float halfWidth) { + if (halfLen <= 0.0 || halfWidth <= 0.0) { + return 0.0; + } + vec2 dist = min(fract(levelPos), 1.0 - fract(levelPos)); + float dx = dist.x; + float dy = dist.y; + float aa = max(fwidth(levelPos.x), fwidth(levelPos.y)); + float hor = (1.0 - smoothstep(halfWidth - aa, halfWidth + aa, dy)) * + (1.0 - smoothstep(halfLen - aa, halfLen + aa, dx)); + float ver = (1.0 - smoothstep(halfWidth - aa, halfWidth + aa, dx)) * + (1.0 - smoothstep(halfLen - aa, halfLen + aa, dy)); + return max(hor, ver); + } + + void main(void) { + vec2 uv = uv0; + + vec2 pos = (uv * 2.0 - 1.0) * uHalfExtents; + vec2 ddx = dFdx(pos); + vec2 ddy = dFdy(pos); + + float epsilon = 1.0 / 255.0; + float res = float(uResolution); + + vec2 levelPos; + float levelSize; + float levelAlpha; + vec3 baseColor = vec3(0.0); + float baseAlpha = 0.0; + float hasBase = 0.0; + + levelPos = pos * 0.1; + levelSize = 2.0 / 1000.0; + levelAlpha = pristineGrid(levelPos, ddx * 0.1, ddy * 0.1, vec2(levelSize)); + if (levelAlpha > epsilon) { + vec3 color; + float alphaMul = uAlphaMain; + if (abs(levelPos.x) < levelSize) { + if (abs(levelPos.y) < levelSize) { + color = vec3(1.0); + } else { + color = uColorZ; + alphaMul = uAlphaZ; + } + } else if (abs(levelPos.y) < levelSize) { + color = uColorX; + alphaMul = uAlphaX; + } else { + color = uColorMain; + } + baseColor = color; + baseAlpha = levelAlpha * alphaMul; + hasBase = 1.0; + } + + if (hasBase < 0.5 && res >= 1.0) { + levelPos = pos; + levelSize = 1.0 / 100.0; + levelAlpha = pristineGrid(levelPos, ddx, ddy, vec2(levelSize)); + if (levelAlpha > epsilon) { + baseColor = uColorMain; + baseAlpha = levelAlpha * uAlphaMain; + hasBase = 1.0; + } + } + + if (hasBase < 0.5 && res >= 2.0) { + levelPos = pos * 10.0; + levelSize = 1.0 / 100.0; + levelAlpha = pristineGrid(levelPos, ddx * 10.0, ddy * 10.0, vec2(levelSize)); + if (levelAlpha > epsilon) { + baseColor = uColorMain; + baseAlpha = levelAlpha * uAlphaMain; + hasBase = 1.0; + } + } + + float dotRadius = max(0.0, uDotSize) * 0.5; + float crossHalfLen = max(0.0, uCrossLength) * 0.5; + float crossHalfWidth = max(0.0, uCrossWidth) * 0.5; + float dotMaskAll = 0.0; + float crossMaskAll = 0.0; + + if (res >= 1.0) { + levelPos = pos; + dotMaskAll = dotMask(levelPos, dotRadius); + crossMaskAll = crossMask(levelPos, crossHalfLen, crossHalfWidth); + } + + vec3 outColor = baseColor; + float outAlpha = baseAlpha; + float dotAlpha = clamp(uDotAlpha, 0.0, 1.0) * dotMaskAll; + float crossAlpha = clamp(uCrossAlpha, 0.0, 1.0) * crossMaskAll; + float fadeStart = max(0.0, uFadeStart); + float fadeEnd = max(0.0, uFadeEnd); + float fade = 1.0; + if (fadeEnd > fadeStart && fadeEnd > 0.0) { + float d = length(pos); + fade = 1.0 - smoothstep(fadeStart, fadeEnd, d); + } + outAlpha *= fade; + dotAlpha *= fade; + crossAlpha *= fade; + if (dotAlpha > 0.0) { + outColor = mix(outColor, uDotColor, dotAlpha); + outAlpha = max(outAlpha, dotAlpha); + } + if (crossAlpha > 0.0) { + outColor = mix(outColor, uCrossColor, crossAlpha); + outAlpha = max(outAlpha, crossAlpha); + } + + if (outAlpha <= epsilon) { + discard; + } + gl_FragColor = vec4(outColor, outAlpha); + } +`, + attributes: { + vertex_position: (pc as any).SEMANTIC_POSITION ?? pc.SEMANTIC_POSITION, + aUv0: (pc as any).SEMANTIC_TEXCOORD0 ?? pc.SEMANTIC_TEXCOORD0, + }, + }) + gridMaterial.blendType = pc.BLEND_NORMAL + gridMaterial.cull = pc.CULLFACE_NONE + gridMaterial.update() + const gridMesh = pc.Mesh.fromGeometry(app.graphicsDevice, new pc.PlaneGeometry()) + const gridMeshInstance = new pc.MeshInstance(gridMesh, gridMaterial) + gridEntity.render!.meshInstances = [gridMeshInstance] + gridEntity.setLocalScale(1000, 1, 1000) + gridEntity.setLocalPosition(0, 0, 0) + gridMaterial.setParameter('uColorX', [1, 0.3, 0.3]) + gridMaterial.setParameter('uColorZ', [0.3, 0.3, 1]) + gridMaterial.setParameter('uColorMain', [0.9, 0.9, 0.9]) + gridMaterial.setParameter('uAlphaX', 1) + gridMaterial.setParameter('uAlphaZ', 1) + gridMaterial.setParameter('uAlphaMain', 1) + gridMaterial.setParameter('uDotColor', [1, 1, 1]) + gridMaterial.setParameter('uDotAlpha', 0) + gridMaterial.setParameter('uDotSize', 0) + gridMaterial.setParameter('uCrossColor', [1, 1, 1]) + gridMaterial.setParameter('uCrossAlpha', 0) + gridMaterial.setParameter('uCrossLength', 0) + gridMaterial.setParameter('uCrossWidth', 0) + gridMaterial.setParameter('uFadeStart', 0) + gridMaterial.setParameter('uFadeEnd', 0) + gridMaterial.setParameter('uResolution', 2) + const gridHalfExtents = new pc.Vec2(500, 500) + gridMaterial.setParameter('uHalfExtents', [gridHalfExtents.x, gridHalfExtents.y]) + gridMaterial.update() + app.root.addChild(gridEntity) + gridEntityRef.current = gridEntity + gridMaterialRef.current = gridMaterial + + const updateGridHalfExtents = () => { + const scale = gridEntity.getLocalScale() + const half = new pc.Vec2(scale.x / 2, scale.z / 2) + if (gridHalfExtents.distance(half) > 0.001) { + gridHalfExtents.copy(half) + gridMaterial.setParameter('uHalfExtents', [gridHalfExtents.x, gridHalfExtents.y]) + gridMaterial.update() + } + } + app.on('prerender', updateGridHalfExtents) + + const shadowCatcher = new pc.Entity('ShadowCatcher') + shadowCatcherRef.current = shadowCatcher + shadowCatcher.setLocalPosition(0, fx.shadowCatcher.yOffset, 0) + + const shadowLight = new pc.Entity('ShadowCatcherLight') + shadowLight.addComponent('light', { + type: 'directional', + castShadows: true, + shadowUpdateMode: pc.SHADOWUPDATE_REALTIME, + }) + shadowCatcherLightRef.current = shadowLight + shadowCatcher.addChild(shadowLight) + + const shadowPlane = new pc.Entity('ShadowCatcherGeometry') + const shadowMat = new pc.StandardMaterial() + shadowMat.blendType = pc.BLEND_MULTIPLICATIVE + shadowMat.shadowCatcher = true + shadowMat.useSkybox = false + shadowMat.depthWrite = false + shadowMat.diffuse.set(0, 0, 0) + shadowMat.specular.set(0, 0, 0) + shadowMat.update() + shadowPlane.addComponent('render', { type: 'plane', castShadows: false, material: shadowMat }) + shadowPlane.setLocalScale(fx.shadowCatcher.size, 1, fx.shadowCatcher.size) + shadowCatcherPlaneRef.current = shadowPlane + shadowPlane.render?.meshInstances?.forEach((mi: any) => { + mi.drawOrder = -1 + mi.material = shadowMat + }) + shadowCatcher.addChild(shadowPlane) + app.root.addChild(shadowCatcher) + + let started = false + try { + app.start() + started = true + } catch (err) { + logError('[PlayCanvasViewer] App start failed:', err) + } + + if (!started) { + try { app.destroy() } catch {} + appRef.current = null + return () => {} + } + + setAppReady(true) + + try { + const atlasUrl = '/rover-scene/environment-map.png' + const asset = new pc.Asset( + 'envAtlas_default', + 'texture', + { url: atlasUrl }, + { + type: 'rgbp', + addressu: 'clamp', + addressv: 'clamp', + mipmaps: false, + } as any + ) + app.assets.add(asset) + app.assets.load(asset) + + asset.ready(() => { + envAtlasRef.current = asset.resource as any + ;(async () => { + try { + const res = await fetch(atlasUrl) + if (!res.ok) throw new Error(`Failed to fetch ${atlasUrl} (${res.status})`) + const blob = await res.blob() + const bmp = await createImageBitmap(blob) + envAtlasImageRef.current = bmp + } catch (err) { + try { + const img = new Image() + img.src = atlasUrl + await new Promise((resolve, reject) => { + img.onload = () => resolve() + img.onerror = () => reject(new Error('Failed to load env atlas image')) + }) + envAtlasImageRef.current = img + } catch (fallbackErr) { + logWarn('[PlayCanvasViewer] Failed to load env atlas image', fallbackErr) + } + } finally { + setEnvAtlasVersion((v) => v + 1) + } + })() + }) + } catch (e) { + logWarn('[PlayCanvasViewer] Failed to init default environment-map.png', e) + } + + onSceneReady?.({ app, camera, modelRoot }) + + const ro = new ResizeObserver(() => resizeCanvas()) + ro.observe(canvas.parentElement ?? canvas) + resizeCanvas() + + return () => { + try { ro.disconnect() } catch {} + orbit.destroy() + cameraEntityRef.current = null + directionalLightRef.current = null + shadowCatcherRef.current = null + shadowCatcherLightRef.current = null + shadowCatcherPlaneRef.current = null + try { cameraFrameRef.current?.destroy?.() } catch {} + try { lutTextureRef.current?.destroy?.() } catch {} + destroyTexture(envAtlasRef.current) + destroyTexture(skyboxSourceRef.current) + destroyTexture(skyboxLightingSourceRef.current) + destroyTexture(skyboxCubemapRef.current) + destroyTexture(skyboxAtlasRef.current) + envAtlasImageRef.current = null + try { app.off('prerender', updateGridHalfExtents) } catch {} + try { gridMaterial.destroy?.() } catch {} + try { shadowMat.destroy?.() } catch {} + + app.destroy() + appRef.current = null + modelAssetRef.current = null + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + useEffect(() => { + if (!viewportSize) return + const w = Math.max(1, Math.round(viewportSize.width)) + const h = Math.max(1, Math.round(viewportSize.height)) + const id = window.requestAnimationFrame(() => resizeCanvas(w, h)) + return () => window.cancelAnimationFrame(id) + }, [viewportSize?.width, viewportSize?.height, resizeCanvas]) + + const resolvedUrl = modelUrl || DEFAULT_MODEL_URL + + useEffect(() => { + const app = appRef.current + if (!app || !resolvedUrl) return + const modelRoot = app.root.findByName('ModelRoot') as pc.Entity | null + if (!modelRoot) return + + let cancelled = false + + const loadGlb = async (url: string) => { + const loadToken = ++loadTokenRef.current + let asset: pc.Asset | null = null + try { + if (!app?.assets) return + + const prevAsset = modelAssetRef.current + const prevChildren = modelRoot.children.slice() + + ensureTextureHandler(app) + + asset = new pc.Asset('model', 'container', { url }) + app.assets.add(asset) + + const loadedAsset = asset + await new Promise((resolve, reject) => { + loadedAsset.ready(() => resolve()) + loadedAsset.on('error', (err: unknown) => reject(err)) + app.assets.load(loadedAsset) + }) + + if (cancelled || loadTokenRef.current !== loadToken) { + try { + if (asset) { + app.assets.remove(asset) + asset.unload?.() + } + } catch {} + return + } + + prevChildren.forEach((c) => c.destroy()) + if (prevAsset) { + try { + app.assets.remove(prevAsset) + prevAsset.unload?.() + } catch {} + } + if (!asset) return + modelAssetRef.current = asset + const resource = asset.resource as any + const entity: pc.Entity = resource.instantiateRenderEntity() + entity.name = 'Model' + modelRoot.addChild(entity) + disableInvalidMeshes(entity) + + const orbit = (app as any).__orbit as OrbitController | undefined + const aabb = computeAabb(entity) + if (orbit && aabb && !cameraStateRef.current) { + orbit.setPivot(aabb.center.clone()) + const cam = cameraEntityRef.current?.camera + const vertical = (cam?.fov ?? 28) * Math.PI / 360 + const horizontal = Math.atan(Math.tan(vertical) * (cam?.aspectRatio ?? 1)) + orbit.setDistance(Math.max(1.8, aabb.halfExtents.length() / Math.sin(Math.min(vertical, horizontal)) * 1.1)) + } + + if (exposeSceneGraph || onSceneGraph) { + const map = new Map() + sceneMapRef.current = map + const graphRoot = modelRoot ?? app.root + const tree = buildSceneTree(graphRoot, map) + onSceneGraph?.(tree, map) + } + setModelVersion((v) => v + 1) + onModelState?.("ready") + } catch (e) { + logError('[PlayCanvasViewer] Failed to load model:', e) + if (!cancelled && asset && app?.assets) { + try { + app.assets.remove(asset) + asset.unload?.() + } catch {} + } + if (!cancelled) onModelState?.("error") + } + } + + onModelState?.("loading") + loadGlb(resolvedUrl) + + return () => { + cancelled = true + } + }, [resolvedUrl, exposeSceneGraph, onSceneGraph]) + + useEffect(() => { + if (!appReady || !cameraState) return + const app = appRef.current + if (!app) return + const orbit = (app as any).__orbit as OrbitController | undefined + if (!orbit) return + orbit.setState(cameraState) + }, [appReady, cameraState]) + + useEffect(() => { + if (!appReady) return + const app = appRef.current + if (!app) return + const orbit = (app as any).__orbit as OrbitController | undefined + if (!orbit) return + orbit.setZoomStrength(cameraZoomStrength ?? undefined) + }, [appReady, cameraZoomStrength]) + + useEffect(() => { + if (!appReady) return + const app = appRef.current + if (!app) return + const orbit = (app as any).__orbit as OrbitController | undefined + if (!orbit) return + orbit.setInertiaFactor(cameraInertiaFactor ?? undefined) + }, [appReady, cameraInertiaFactor]) + + useEffect(() => { + if (!appReady) return + const app = appRef.current + if (!app) return + const orbit = (app as any).__orbit as OrbitController | undefined + if (!orbit) return + orbit.setInertiaTime(cameraInertiaTime ?? undefined) + }, [appReady, cameraInertiaTime]) + + useEffect(() => { + if (!appReady) return + const app = appRef.current + if (!app) return + const orbit = (app as any).__orbit as OrbitController | undefined + if (!orbit) return + orbit.setAutoRotate(cameraAutoRotate ?? undefined) + }, [appReady, cameraAutoRotate]) + useEffect(() => { + if (!appReady || !cameraResetToken) return + const app = appRef.current + if (!app) return + const orbit = (app as any).__orbit as OrbitController | undefined + if (!orbit) return + const modelRoot = app.root.findByName('ModelRoot') as pc.Entity | null + if (!modelRoot) return + const modelEntity = (modelRoot as any).findByName?.('Model') as pc.Entity | null + const aabb = computeAabb(modelEntity || modelRoot) + if (!aabb) return + orbit.setState({ + pivot: { x: aabb.center.x, y: aabb.center.y, z: aabb.center.z }, + distance: Math.max(1.8, aabb.halfExtents.length() * 2.5), + azimuthDeg: 45, + elevationDeg: 10, + }) + }, [appReady, cameraResetToken, modelVersion]) + + useEffect(() => { + const app = appRef.current + if (!app) return + const camEnt = app.root.findByName('Camera') as pc.Entity | null + const cam = camEnt?.camera as any + if (cam) cam.clearColor = toColor(fx.rendering.backgroundColor) + }, [fx.rendering.backgroundColor]) + + useEffect(() => { + const grid = gridEntityRef.current + const mat = gridMaterialRef.current + if (!grid || !mat) return + + const gridVisible = !!fx.grid.enabled || !!fx.grid.dotsEnabled || !!fx.grid.crossEnabled + grid.enabled = gridVisible + const cX = toColor(fx.grid.colorX) + const cZ = toColor(fx.grid.colorZ) + const cMain = toColor(fx.grid.colorMain) + const dotColor = toColor(fx.grid.dotsColor) + const crossColor = toColor(fx.grid.crossColor) + mat.setParameter('uColorX', [cX.r, cX.g, cX.b]) + mat.setParameter('uColorZ', [cZ.r, cZ.g, cZ.b]) + mat.setParameter('uColorMain', [cMain.r, cMain.g, cMain.b]) + const linesEnabled = !!fx.grid.enabled + mat.setParameter('uAlphaX', linesEnabled ? clamp01(fx.grid.alphaX) : 0) + mat.setParameter('uAlphaZ', linesEnabled ? clamp01(fx.grid.alphaZ) : 0) + mat.setParameter('uAlphaMain', linesEnabled ? clamp01(fx.grid.alphaMain) : 0) + mat.setParameter('uDotColor', [dotColor.r, dotColor.g, dotColor.b]) + mat.setParameter('uDotAlpha', fx.grid.dotsEnabled ? clamp01(fx.grid.dotsAlpha) : 0) + mat.setParameter('uDotSize', Math.max(0, Number(fx.grid.dotsDiameter) || 0)) + mat.setParameter('uCrossColor', [crossColor.r, crossColor.g, crossColor.b]) + mat.setParameter('uCrossAlpha', fx.grid.crossEnabled ? clamp01(fx.grid.crossAlpha) : 0) + mat.setParameter('uCrossLength', Math.max(0, Number(fx.grid.crossLength) || 0)) + mat.setParameter('uCrossWidth', Math.max(0, Number(fx.grid.crossWidth) || 0)) + mat.setParameter('uFadeStart', Math.max(0, Number(fx.grid.fadeStart) || 0)) + mat.setParameter('uFadeEnd', Math.max(0, Number(fx.grid.fadeEnd) || 0)) + mat.update() + }, [ + fx.grid.enabled, + fx.grid.colorX, + fx.grid.colorZ, + fx.grid.colorMain, + fx.grid.alphaX, + fx.grid.alphaZ, + fx.grid.alphaMain, + fx.grid.dotsEnabled, + fx.grid.dotsColor, + fx.grid.dotsAlpha, + fx.grid.dotsDiameter, + fx.grid.crossEnabled, + fx.grid.crossColor, + fx.grid.crossAlpha, + fx.grid.crossLength, + fx.grid.crossWidth, + fx.grid.fadeStart, + fx.grid.fadeEnd, + ]) + + useEffect(() => { + const cam = cameraEntityRef.current?.camera as any + if (cam) cam.toneMapping = fx.rendering.toneMapping + }, [fx.rendering.toneMapping]) + + useEffect(() => { + const app = appRef.current + if (!app) return + setWireframeForScene(app, fx.rendering.wireframe) + }, [fx.rendering.wireframe]) + + useEffect(() => { + const frame: any = cameraFrameRef.current + if (!frame) return + const overrides = resolveRenderOverrides(fx, appRef.current?.graphicsDevice ?? null) + applyPostFx(frame, fx, lutTextureRef.current, overrides) + }, [fx]) + + useEffect(() => { + const app = appRef.current + if (!app) return + if (fx.lighting) { + app.scene.exposure = fx.lighting.exposure + } + }, [fx.lighting?.exposure]) + + useEffect(() => { + const app = appRef.current + if (!app) return + + const scene: any = app.scene + const fogType = fx.rendering.fog + const fogValue = + fogType === 'linear' ? pc.FOG_LINEAR : + fogType === 'exp' ? pc.FOG_EXP : + fogType === 'exp2' ? pc.FOG_EXP2 : + pc.FOG_NONE + + const fog = scene?.fog + if (fog && typeof fog === 'object') { + fog.type = fogValue + const fogColor = toColor(fx.rendering.fogColor) + if (fog.color?.copy) fog.color.copy(fogColor) + else fog.color = fogColor + + if (Number.isFinite(fx.rendering.fogDensity)) { + fog.density = fx.rendering.fogDensity + } + + const range = fx.rendering.fogRange + const start = Number.isFinite(fx.rendering.fogStart) ? fx.rendering.fogStart : (Array.isArray(range) ? range[0] : undefined) + const end = Number.isFinite(fx.rendering.fogEnd) ? fx.rendering.fogEnd : (Array.isArray(range) ? range[1] : undefined) + if (Number.isFinite(start)) fog.start = start + if (Number.isFinite(end)) fog.end = end + } else { + scene.fog = fogValue + + const fogColor = toColor(fx.rendering.fogColor) + if (scene.fogColor?.copy) scene.fogColor.copy(fogColor) + else scene.fogColor = fogColor + + if (Number.isFinite(fx.rendering.fogDensity)) { + scene.fogDensity = fx.rendering.fogDensity + } + + const range = fx.rendering.fogRange + const start = Number.isFinite(fx.rendering.fogStart) ? fx.rendering.fogStart : (Array.isArray(range) ? range[0] : undefined) + const end = Number.isFinite(fx.rendering.fogEnd) ? fx.rendering.fogEnd : (Array.isArray(range) ? range[1] : undefined) + if (Number.isFinite(start)) scene.fogStart = start + if (Number.isFinite(end)) scene.fogEnd = end + } + }, [ + fx.rendering.fog, + fx.rendering.fogColor, + fx.rendering.fogDensity, + fx.rendering.fogRange, + fx.rendering.fogStart, + fx.rendering.fogEnd, + ]) + + useEffect(() => { + const app = appRef.current + if (!app || !appReady) return + + const next = generateSkyboxResources(app, fx.skybox.colorA, fx.skybox.colorB) + if (!next.source && !next.cubemap && !next.atlas) return + + destroyTexture(skyboxSourceRef.current) + destroyTexture(skyboxLightingSourceRef.current) + destroyTexture(skyboxCubemapRef.current) + destroyTexture(skyboxAtlasRef.current) + + skyboxSourceRef.current = next.source + skyboxLightingSourceRef.current = next.lightingSource + skyboxCubemapRef.current = next.cubemap + skyboxAtlasRef.current = next.atlas + setSkyboxVersion((v) => v + 1) + }, [appReady, fx.skybox.colorA, fx.skybox.colorB]) + + useEffect(() => { + const app = appRef.current + if (!app || !appReady) return + + if (envAtlasRef.current && envAtlasImageRef.current) { + const key = [ + fx.envAtlas.brightness, + fx.envAtlas.contrast, + fx.envAtlas.saturation, + fx.envAtlas.toneMapping, + ].join('|') + if (envAtlasAdjustKeyRef.current !== key) { + envAtlasAdjustKeyRef.current = key + const base = envAtlasImageRef.current + const width = + 'width' in base + ? (base as ImageBitmap).width + : (base as HTMLImageElement).naturalWidth + const height = + 'height' in base + ? (base as ImageBitmap).height + : (base as HTMLImageElement).naturalHeight + const canvas = document.createElement('canvas') + canvas.width = width + canvas.height = height + const ctx = canvas.getContext('2d') + if (ctx) { + ctx.drawImage(base as any, 0, 0, width, height) + const img = ctx.getImageData(0, 0, width, height) + const data = img.data + const brightness = fx.envAtlas.brightness + const contrast = fx.envAtlas.contrast + const saturation = fx.envAtlas.saturation + const tone = fx.envAtlas.toneMapping + const neutral = + Math.abs(brightness - 1) < 0.0001 && + Math.abs(contrast - 1) < 0.0001 && + Math.abs(saturation - 1) < 0.0001 && + Math.abs(tone) < 0.0001 + + if (!neutral) { + for (let i = 0; i < data.length; i += 4) { + let r = data[i] / 255 + let g = data[i + 1] / 255 + let b = data[i + 2] / 255 + + r *= brightness + g *= brightness + b *= brightness + + r = (r - 0.5) * contrast + 0.5 + g = (g - 0.5) * contrast + 0.5 + b = (b - 0.5) * contrast + 0.5 + + const l = r * 0.2126 + g * 0.7152 + b * 0.0722 + r = l + (r - l) * saturation + g = l + (g - l) * saturation + b = l + (b - l) * saturation + + r = toneMapColor(r, tone) + g = toneMapColor(g, tone) + b = toneMapColor(b, tone) + + data[i] = Math.round(clamp01(r) * 255) + data[i + 1] = Math.round(clamp01(g) * 255) + data[i + 2] = Math.round(clamp01(b) * 255) + } + ctx.putImageData(img, 0, 0) + } + envAtlasRef.current.setSource(canvas as any) + } + } + } + + const atlasTex = envAtlasRef.current + const skyboxAtlas = skyboxAtlasRef.current + const skyboxCube = skyboxCubemapRef.current + + const atlasActive = !!fx.envAtlas.enabled + const skyboxActive = !atlasActive && !!fx.skybox.enabled + + let intensity = 0 + let mip = 0 + let rotation = 0 + + let sceneEnvAtlas: pc.Texture | null = null + let sceneSkybox: pc.Texture | null = null + let overrideEnvAtlas: pc.Texture | null = null + let overrideUseSkybox: boolean | undefined = false + let overrideReflectivityScale = 1 + let overrideEnabled = true + + if (atlasActive) { + const backgroundOn = !!fx.envAtlas.background && !!atlasTex + const reflectionOn = !!fx.envAtlas.reflection && !!atlasTex + + intensity = fx.envAtlas.intensity * (fx.lighting?.skyBoxIntensity ?? 1) + mip = fx.envAtlas.mip + rotation = fx.envAtlas.rotation + + const reflectionIntensity = fx.envAtlas.reflectionIntensity * (fx.lighting?.skyBoxIntensity ?? 1) + overrideReflectivityScale = reflectionIntensity + + if (backgroundOn) sceneEnvAtlas = atlasTex + overrideEnvAtlas = reflectionOn ? atlasTex : null + } else if (skyboxActive) { + const backgroundOn = !!fx.skybox.background && !!skyboxCube + const reflectionOn = !!fx.skybox.reflection && !!skyboxAtlas + + intensity = fx.skybox.intensity * (fx.lighting?.skyBoxIntensity ?? 1) + mip = fx.skybox.mip + rotation = fx.skybox.rotation + + const reflectionIntensity = fx.skybox.reflectionIntensity * (fx.lighting?.skyBoxIntensity ?? 1) + overrideReflectivityScale = reflectionIntensity + + if (backgroundOn) sceneSkybox = skyboxCube + if (backgroundOn) sceneEnvAtlas = skyboxAtlas + overrideEnvAtlas = reflectionOn ? skyboxAtlas : null + } else { + overrideEnvAtlas = null + overrideReflectivityScale = 1 + } + + applySceneEnvironment(app, { + envAtlas: sceneEnvAtlas, + skybox: sceneSkybox, + intensity, + mip, + rotationDeg: rotation, + }) + + applyMaterialEnvOverride(app, { + enabled: overrideEnabled, + envAtlas: overrideEnvAtlas, + cubeMap: null, + useSkybox: overrideUseSkybox, + reflectivityScale: overrideReflectivityScale, + }) + }, [ + appReady, + envAtlasVersion, + skyboxVersion, + modelVersion, + fx.envAtlas.enabled, + fx.envAtlas.background, + fx.envAtlas.reflection, + fx.envAtlas.intensity, + fx.envAtlas.reflectionIntensity, + fx.envAtlas.brightness, + fx.envAtlas.contrast, + fx.envAtlas.saturation, + fx.envAtlas.toneMapping, + fx.envAtlas.mip, + fx.envAtlas.rotation, + fx.skybox.enabled, + fx.skybox.background, + fx.skybox.reflection, + fx.skybox.intensity, + fx.skybox.reflectionIntensity, + fx.skybox.mip, + fx.skybox.rotation, + fx.lighting?.skyBoxIntensity, + ]) + + useEffect(() => { + const lightEntity = directionalLightRef.current + if (!lightEntity) return + + lightEntity.enabled = !!fx.directionalLight.enabled + applyLightAngles(lightEntity, fx.directionalLight.azimuth, fx.directionalLight.elevation) + + const light = (lightEntity as any).light + if (!light) return + + light.color?.copy?.(toColor(fx.directionalLight.color)) + light.intensity = fx.directionalLight.intensity + light.castShadows = !!fx.directionalLight.castShadows + light.shadowIntensity = fx.directionalLight.shadowIntensity + light.shadowDistance = fx.directionalLight.shadowDistance + light.shadowResolution = fx.directionalLight.shadowResolution + light.shadowBias = fx.directionalLight.shadowBias + light.normalOffsetBias = fx.directionalLight.normalOffsetBias + light.shadowType = resolveShadowType(fx.directionalLight.shadowType) + light.vsmBlurSize = fx.directionalLight.vsmBlurSize + if (light.castShadows && typeof light.shadowUpdateMode === 'number') { + light.shadowUpdateMode = pc.SHADOWUPDATE_REALTIME + } + }, [ + fx.directionalLight.enabled, + fx.directionalLight.color, + fx.directionalLight.intensity, + fx.directionalLight.azimuth, + fx.directionalLight.elevation, + fx.directionalLight.castShadows, + fx.directionalLight.shadowIntensity, + fx.directionalLight.shadowDistance, + fx.directionalLight.shadowResolution, + fx.directionalLight.shadowBias, + fx.directionalLight.normalOffsetBias, + fx.directionalLight.shadowType, + fx.directionalLight.vsmBlurSize, + ]) + + useEffect(() => { + const catcher = shadowCatcherRef.current + const lightEntity = shadowCatcherLightRef.current + const planeEntity = shadowCatcherPlaneRef.current + if (!catcher || !lightEntity || !planeEntity) return + + catcher.enabled = !!fx.shadowCatcher.enabled + catcher.setLocalPosition(0, fx.shadowCatcher.yOffset, 0) + planeEntity.setLocalScale(fx.shadowCatcher.size, 1, fx.shadowCatcher.size) + applyLightAngles(lightEntity, fx.shadowCatcher.lightAzimuth, fx.shadowCatcher.lightElevation) + + const light = (lightEntity as any).light + if (!light) return + + light.color?.copy?.(toColor(fx.shadowCatcher.lightColor)) + light.intensity = fx.shadowCatcher.lightIntensity + light.castShadows = true + light.shadowIntensity = fx.shadowCatcher.shadowIntensity + light.shadowDistance = fx.shadowCatcher.shadowDistance + light.shadowResolution = fx.shadowCatcher.shadowResolution + light.shadowBias = fx.shadowCatcher.shadowBias + light.normalOffsetBias = fx.shadowCatcher.normalOffsetBias + light.shadowType = resolveShadowType(fx.shadowCatcher.shadowType) + light.vsmBlurSize = fx.shadowCatcher.vsmBlurSize + if (typeof light.shadowUpdateMode === 'number') { + light.shadowUpdateMode = pc.SHADOWUPDATE_REALTIME + } + }, [ + fx.shadowCatcher.enabled, + fx.shadowCatcher.size, + fx.shadowCatcher.yOffset, + fx.shadowCatcher.lightIntensity, + fx.shadowCatcher.lightColor, + fx.shadowCatcher.lightAzimuth, + fx.shadowCatcher.lightElevation, + fx.shadowCatcher.shadowIntensity, + fx.shadowCatcher.shadowDistance, + fx.shadowCatcher.shadowResolution, + fx.shadowCatcher.shadowBias, + fx.shadowCatcher.normalOffsetBias, + fx.shadowCatcher.shadowType, + fx.shadowCatcher.vsmBlurSize, + ]) + + // reflection overrides are managed in the environment effect above + + useEffect(() => { + if (!onSelectionChanged) return + onSelectionChanged([]) + }, [onSelectionChanged]) + + return ( +
+ +
+ ) +} diff --git a/apps/control-station/src/components/rover/playcanvas/playcanvasPostFx.ts b/apps/control-station/src/components/rover/playcanvas/playcanvasPostFx.ts new file mode 100644 index 0000000..053e745 --- /dev/null +++ b/apps/control-station/src/components/rover/playcanvas/playcanvasPostFx.ts @@ -0,0 +1,393 @@ + +export type PostFxSettings = { + lighting: { + exposure: number + skyBoxIntensity: number + } + envAtlas: { + enabled: boolean + background: boolean + reflection: boolean + intensity: number + reflectionIntensity: number + brightness: number + contrast: number + saturation: number + toneMapping: number + mip: number + rotation: number + } + skybox: { + enabled: boolean + background: boolean + reflection: boolean + intensity: number + reflectionIntensity: number + mip: number + rotation: number + colorA: string + colorB: string + } + rendering: { + backgroundColor: string + wireframe: boolean + renderFormat: number + renderFormatFallback0: number + renderFormatFallback1: number + stencil: boolean + renderTargetScale: number + samples: number + sharpness: number + toneMapping: number + sceneColorMap: boolean + sceneDepthMap: boolean + fog: 'none' | 'linear' | 'exp' | 'exp2' + fogColor: string + fogRange: [number, number] + fogDensity: number + fogStart: number + fogEnd: number + } + grid: { + enabled: boolean + colorX: string + colorZ: string + colorMain: string + alphaX: number + alphaZ: number + alphaMain: number + dotsEnabled: boolean + dotsColor: string + dotsAlpha: number + dotsDiameter: number + crossEnabled: boolean + crossColor: string + crossAlpha: number + crossLength: number + crossWidth: number + fadeStart: number + fadeEnd: number + } + ssao: { + type: 'none' | 'lighting' | 'combine' + blurEnabled: boolean + intensity: number + radius: number + samples: number + power: number + minAngle: number + scale: number + } + bloom: { + enabled: boolean + intensity: number + lastMipLevel: number + } + chromaticAberration: { + enabled: boolean + intensity: number + } + taa: { + enabled: boolean + jitter: number + } + grading: { + enabled: boolean + brightness: number + contrast: number + saturation: number + tint: string + } + lut: { + intensity: number + textureUrl?: string | null + } + vignette: { + enabled: boolean + intensity: number + inner: number + outer: number + curvature: number + color: string + } + directionalLight: { + enabled: boolean + color: string + intensity: number + azimuth: number + elevation: number + castShadows: boolean + shadowIntensity: number + shadowDistance: number + shadowResolution: number + shadowBias: number + normalOffsetBias: number + shadowType: 'vsm16' | 'vsm32' | 'pcf1' | 'pcf3' + vsmBlurSize: number + } + shadowCatcher: { + enabled: boolean + size: number + yOffset: number + lightIntensity: number + lightColor: string + lightAzimuth: number + lightElevation: number + shadowIntensity: number + shadowDistance: number + shadowResolution: number + shadowBias: number + normalOffsetBias: number + shadowType: 'vsm16' | 'vsm32' | 'pcf1' | 'pcf3' + vsmBlurSize: number + } +} + +export const DEFAULT_POSTFX: PostFxSettings = { + lighting: { + exposure: 1.21, + skyBoxIntensity: 0.86, + }, + envAtlas: { + enabled: true, + background: false, + reflection: true, + intensity: 0.76, + reflectionIntensity: 0.6, + brightness: 1.65, + contrast: 1, + saturation: 1, + toneMapping: 0, + mip: 1, + rotation: 247, + }, + skybox: { + enabled: false, + background: false, + reflection: true, + intensity: 0.62, + reflectionIntensity: 1, + mip: 0, + rotation: 0, + colorA: '#c5bfbf', + colorB: '#c7bcbc', + }, + rendering: { + backgroundColor: '#111113', + wireframe: false, + renderFormat: 18, + renderFormatFallback0: 12, + renderFormatFallback1: 14, + stencil: false, + renderTargetScale: 1, + samples: 4, + sharpness: 0, + toneMapping: 4, + sceneColorMap: false, + sceneDepthMap: false, + fog: 'exp', + fogColor: '#dcc2ff', + fogRange: [0, 100], + fogDensity: 0.008, + fogStart: 0, + fogEnd: 100, + }, + grid: { + enabled: true, + colorX: '#ffffff', + colorZ: '#ffffff', + colorMain: '#7a3cff', + alphaX: 0.18, + alphaZ: 0.18, + alphaMain: 0.6, + dotsEnabled: false, + dotsColor: '#ffffff', + dotsAlpha: 0.5, + dotsDiameter: 0.06, + crossEnabled: false, + crossColor: '#ffffff', + crossAlpha: 0.5, + crossLength: 0.5, + crossWidth: 0.06, + fadeStart: 0, + fadeEnd: 0, + }, + ssao: { + type: 'none', + blurEnabled: true, + intensity: 0.5, + radius: 30, + samples: 12, + power: 6, + minAngle: 10, + scale: 1, + }, + bloom: { + enabled: true, + intensity: 0.03, + lastMipLevel: 4, + }, + chromaticAberration: { + enabled: true, + intensity: 30, + }, + taa: { + enabled: false, + jitter: 1, + }, + grading: { + enabled: true, + brightness: 0.837, + contrast: 1.1, + saturation: 1.126, + tint: '#ffffff', + }, + lut: { + intensity: 1, + textureUrl: null, + }, + vignette: { + enabled: true, + intensity: 1, + inner: 0.25, + outer: 1.52, + curvature: 0.78, + color: '#000000', + }, + directionalLight: { + enabled: true, + color: '#ffffff', + intensity: 0.4, + azimuth: 0, + elevation: 0, + castShadows: false, + shadowIntensity: 0.5, + shadowDistance: 16, + shadowResolution: 1024, + shadowBias: 0, + normalOffsetBias: 0, + shadowType: 'vsm16', + vsmBlurSize: 8, + }, + shadowCatcher: { + enabled: true, + size: 9, + yOffset: 0.001, + lightIntensity: 0.3, + lightColor: '#ffffff', + lightAzimuth: 20, + lightElevation: 60, + shadowIntensity: 0.29, + shadowDistance: 16, + shadowResolution: 2048, + shadowBias: 0, + normalOffsetBias: 0, + shadowType: 'vsm16', + vsmBlurSize: 8, + }, +} + +function isObject(value: any): value is Record { + return !!value && typeof value === 'object' && !Array.isArray(value) +} + +export function mergePostFxDefaults(raw?: Partial | null): PostFxSettings { + if (!raw) return JSON.parse(JSON.stringify(DEFAULT_POSTFX)) as PostFxSettings + + const out: any = JSON.parse(JSON.stringify(DEFAULT_POSTFX)) + const merge = (target: any, source: any) => { + if (!isObject(source)) return + for (const [key, value] of Object.entries(source)) { + if (isObject(value)) { + if (!isObject(target[key])) target[key] = {} + merge(target[key], value) + } else { + target[key] = value + } + } + } + merge(out, raw) + + const legacy: any = raw as any + if (legacy?.hdrLight) { + const src = legacy.hdrLight + const env = legacy?.envAtlas ?? {} + if (env?.enabled === undefined && typeof src.enabled === 'boolean') out.envAtlas.enabled = src.enabled + if (env?.background === undefined && typeof src.showSkybox === 'boolean') out.envAtlas.background = src.showSkybox + if (env?.intensity === undefined && typeof src.intensity === 'number') out.envAtlas.intensity = src.intensity + if (env?.mip === undefined && typeof src.mip === 'number') out.envAtlas.mip = src.mip + if (env?.rotation === undefined && typeof src.rotation === 'number') out.envAtlas.rotation = src.rotation + } + if (legacy?.hdrReflection && (legacy?.envAtlas?.reflection === undefined)) { + if (typeof legacy.hdrReflection.enabled === 'boolean') { + out.envAtlas.reflection = legacy.hdrReflection.enabled + } + } + if (out.envAtlas && typeof out.envAtlas.reflectionIntensity !== 'number') { + out.envAtlas.reflectionIntensity = out.envAtlas.intensity + } + if (out.envAtlas && typeof out.envAtlas.brightness !== 'number') { + out.envAtlas.brightness = 1 + } + if (out.envAtlas && typeof out.envAtlas.contrast !== 'number') { + out.envAtlas.contrast = 1 + } + if (out.envAtlas && typeof out.envAtlas.saturation !== 'number') { + out.envAtlas.saturation = 1 + } + if (out.envAtlas && typeof out.envAtlas.toneMapping !== 'number') { + out.envAtlas.toneMapping = 0 + } + if (out.skybox && typeof out.skybox.reflectionIntensity !== 'number') { + out.skybox.reflectionIntensity = out.skybox.intensity + } + if (out.grid && typeof out.grid.enabled !== 'boolean') { + out.grid.enabled = DEFAULT_POSTFX.grid.enabled + } + if (out.grid && typeof out.grid.colorX !== 'string') { + out.grid.colorX = DEFAULT_POSTFX.grid.colorX + } + if (out.grid && typeof out.grid.colorZ !== 'string') { + out.grid.colorZ = DEFAULT_POSTFX.grid.colorZ + } + if (out.grid && typeof out.grid.colorMain !== 'string') { + out.grid.colorMain = DEFAULT_POSTFX.grid.colorMain + } + if (out.grid && typeof out.grid.alphaX !== 'number') { + out.grid.alphaX = DEFAULT_POSTFX.grid.alphaX + } + if (out.grid && typeof out.grid.alphaZ !== 'number') { + out.grid.alphaZ = DEFAULT_POSTFX.grid.alphaZ + } + if (out.grid && typeof out.grid.alphaMain !== 'number') { + out.grid.alphaMain = DEFAULT_POSTFX.grid.alphaMain + } + if (out.grid && typeof out.grid.dotsEnabled !== 'boolean') { + out.grid.dotsEnabled = DEFAULT_POSTFX.grid.dotsEnabled + } + if (out.grid && typeof out.grid.dotsColor !== 'string') { + out.grid.dotsColor = DEFAULT_POSTFX.grid.dotsColor + } + if (out.grid && typeof out.grid.dotsAlpha !== 'number') { + out.grid.dotsAlpha = DEFAULT_POSTFX.grid.dotsAlpha + } + if (out.grid && typeof out.grid.dotsDiameter !== 'number') { + out.grid.dotsDiameter = DEFAULT_POSTFX.grid.dotsDiameter + } + if (out.grid && typeof out.grid.crossEnabled !== 'boolean') { + out.grid.crossEnabled = DEFAULT_POSTFX.grid.crossEnabled + } + if (out.grid && typeof out.grid.crossColor !== 'string') { + out.grid.crossColor = DEFAULT_POSTFX.grid.crossColor + } + if (out.grid && typeof out.grid.crossAlpha !== 'number') { + out.grid.crossAlpha = DEFAULT_POSTFX.grid.crossAlpha + } + if (out.grid && typeof out.grid.crossLength !== 'number') { + out.grid.crossLength = DEFAULT_POSTFX.grid.crossLength + } + if (out.grid && typeof out.grid.crossWidth !== 'number') { + out.grid.crossWidth = DEFAULT_POSTFX.grid.crossWidth + } + return out as PostFxSettings +} diff --git a/apps/control-station/src/components/rover/playcanvas/sceneTree.ts b/apps/control-station/src/components/rover/playcanvas/sceneTree.ts new file mode 100644 index 0000000..dacdf59 --- /dev/null +++ b/apps/control-station/src/components/rover/playcanvas/sceneTree.ts @@ -0,0 +1,20 @@ +import type * as pc from 'playcanvas' + +export type SceneTreeNode = { + id: string + name: string + enabled: boolean + children: SceneTreeNode[] +} + +export function buildSceneTree(root: pc.GraphNode, map: Map): SceneTreeNode { + const id = String((root as any).getGuid?.() ?? (root as any)._guid ?? root.name) + const children = (root.children || []).map((c) => buildSceneTree(c, map)) + map.set(id, root as pc.Entity) + return { + id, + name: root.name || 'Entity', + enabled: root.enabled !== false, + children, + } +} diff --git a/apps/control-station/src/components/rover/rover.css b/apps/control-station/src/components/rover/rover.css new file mode 100644 index 0000000..a8d3263 --- /dev/null +++ b/apps/control-station/src/components/rover/rover.css @@ -0,0 +1,20 @@ +.rover-view { position:relative; height:100%; min-height:200px; overflow:hidden; container-type:inline-size; } +.rover-view:focus-visible { outline:2px solid var(--nodedc-text-secondary); outline-offset:-2px; } +.rover-view .rover-view__scene { position:absolute; inset:0; } +.rover-view__empty { display:grid; place-content:center; height:100%; text-align:center; font-size:var(--nodedc-font-size-sm); color:var(--nodedc-text-muted); } +.rover-view__status { position:absolute; top:var(--nodedc-space-3); left:var(--nodedc-space-3); } +.rover-view__controls { position:absolute; right:var(--nodedc-space-3); bottom:var(--nodedc-space-3); display:flex; flex-direction:column; gap:var(--nodedc-space-2); align-items:center; } +.rover-view__controls > span { font-size:var(--nodedc-font-size-xs); color:var(--nodedc-text-secondary); } +.rover-keys { display:grid; gap:var(--nodedc-space-1); grid-template-columns:repeat(3,46px); } +.rover-key-KeyW { grid-column:2; }.rover-key-KeyA { grid-column:1; grid-row:2; }.rover-key-KeyS { grid-column:2; grid-row:2; }.rover-key-KeyD { grid-column:3; grid-row:2; } +.rover-key-KeyQ { grid-column:1; }.rover-key-KeyE { grid-column:3; } +.rover-view__authority { position:absolute; left:var(--nodedc-space-3); bottom:var(--nodedc-space-3); } +@container (max-width: 380px) { + .rover-view__authority { top:48px; bottom:auto; } +} +.rover-settings { display:flex; flex-direction:column; gap:var(--nodedc-space-3); } +.rover-settings p { font-size:var(--nodedc-font-size-sm); color:var(--nodedc-text-secondary); } +.rover-telemetry { height:100%; overflow:auto; padding:var(--nodedc-space-3); display:flex; flex-wrap:wrap; align-content:start; gap:var(--nodedc-space-4); font-size:var(--nodedc-font-size-sm); } +.rover-telemetry section { flex:1; min-width:170px; }.rover-telemetry section > span { display:block; color:var(--nodedc-text-muted); font-size:var(--nodedc-font-size-xs); margin-top:var(--nodedc-space-1); } +.rover-telemetry dl { display:grid; gap:var(--nodedc-space-1); margin-bottom:0; }.rover-telemetry dl > div { display:flex; justify-content:space-between; gap:var(--nodedc-space-3); } +.rover-telemetry dt { color:var(--nodedc-text-secondary); }.rover-telemetry dd { margin:0; font-variant-numeric:tabular-nums; } diff --git a/apps/control-station/src/composition/devicePlugins.ts b/apps/control-station/src/composition/devicePlugins.ts index 2091dcb..c242a83 100644 --- a/apps/control-station/src/composition/devicePlugins.ts +++ b/apps/control-station/src/composition/devicePlugins.ts @@ -1,3 +1,4 @@ +import {vescSensorUi} from '../../../../plugins/vesc/frontend/src/plugin'; import type { DeviceUiPlugin } from "../core/device-plugins/contracts"; import { xgridsK1Plugin } from "@xgrids-k1/frontend/plugin"; import {insta360X4SensorUi} from '../../../../plugins/insta360-x4/frontend/src/plugin'; @@ -10,4 +11,4 @@ export const installedDevicePlugins: readonly DeviceUiPlugin[] = Object.freeze([ // Node-executed camera controls use the existing sensor contribution contract; // they do not register a desktop capture/AI runtime or a new product workspace. -export const installedNodeSensorContributions = Object.freeze([insta360X4SensorUi]); +export const installedNodeSensorContributions = Object.freeze([insta360X4SensorUi,vescSensorUi]); diff --git a/apps/control-station/src/core/fleet/boardLayout.ts b/apps/control-station/src/core/fleet/boardLayout.ts new file mode 100644 index 0000000..309eb83 --- /dev/null +++ b/apps/control-station/src/core/fleet/boardLayout.ts @@ -0,0 +1,13 @@ +import {createBoardLayoutStore,type BoardLayoutStore} from '../../../../../packages/sensor-ui/src/boardLayout'; +import {fleetRequest} from './useFleet'; + +const layouts=new Map(); +export function boardLayout(vehicleID:string):BoardLayoutStore { + let layout=layouts.get(vehicleID); + if(!layout){ + const path=`/${encodeURIComponent(vehicleID)}/board-layout`; + layout=createBoardLayoutStore({read:()=>fleetRequest(path),patch:(section,open)=>fleetRequest(path,'PATCH',{section,open})}); + layouts.set(vehicleID,layout); + } + return layout; +} diff --git a/apps/control-station/src/core/fleet/roverHoldInput.ts b/apps/control-station/src/core/fleet/roverHoldInput.ts new file mode 100644 index 0000000..7ebd75c --- /dev/null +++ b/apps/control-station/src/core/fleet/roverHoldInput.ts @@ -0,0 +1,66 @@ +import {keyDemand,keysFor,type Demand,type RoverMode} from './roverInput'; + +// The network heartbeat is not evidence that a physical key is still held. +// Allow the initial OS repeat delay, then require continuing keyboard evidence. +export const firstKeyLeaseMs=1000,repeatKeyLeaseMs=300; +export function bindRoverHoldInput(scope:HTMLElement,mode:RoverMode,callbacks:{ + held:(keys:Set)=>void;demand:(value:Demand)=>void;pause:()=>void;stop:()=>void; +},now:()=>number=()=>performance.now()){ + const doc=scope.ownerDocument,win=doc.defaultView!; + const keyboard=new Set(),pointers=new Map(); + let deadline=0,closed=false,suspended=false; + const clear=()=>{keyboard.clear();pointers.clear();deadline=0;callbacks.held(new Set());}; + const pause=()=>{if(closed||suspended)return;suspended=true;clear();callbacks.pause();}; + const stop=()=>{if(closed)return;closed=true;clear();callbacks.stop();}; + const focused=()=>!doc.hidden&&doc.hasFocus()&&scope.contains(doc.activeElement); + const valid=()=>{ + if(closed)return false; + if(!focused()||(keyboard.size>0&&now()>=deadline)){pause();return false;} + return true; + }; + const publish=()=>{const held=new Set([...keyboard,...pointers.values()]);callbacks.held(held);callbacks.demand(keyDemand(mode,held));}; + const down=(e:KeyboardEvent)=>{ + if(!e.isTrusted||closed)return; + if(e.code==='Space'||e.code==='Escape'){e.preventDefault();stop();return;} + if(e.altKey||e.ctrlKey||e.metaKey){pause();return;} + if(!keysFor(mode).includes(e.code)||!valid())return; + if((e.target as HTMLElement)?.closest('input,textarea,select,[role=dialog],[role=listbox]')){pause();return;} + // A repeat delivered after focus returns cannot become a fresh press. + if(e.repeat&&!keyboard.has(e.code))return; + e.preventDefault();suspended=false;keyboard.add(e.code); + deadline=now()+(e.repeat?repeatKeyLeaseMs:firstKeyLeaseMs);publish(); + }; + const up=(e:KeyboardEvent)=>{ + if(!e.isTrusted||closed||!keyboard.delete(e.code))return; + e.preventDefault();if(!keyboard.size)deadline=0;publish(); + }; + const blur=(e:Event)=>{if(e.target===win)pause();}; + const focusOut=(e:FocusEvent)=>{if(scope.contains(e.target as Node)&&!scope.contains(e.relatedTarget as Node|null))pause();}; + const focusIn=()=>{if(!focused())pause();}; + const hidden=()=>{if(doc.hidden)pause();}; + const outside=(e:Event)=>{if(!scope.contains(e.target as Node))pause();}; + const pointerUp=(e:PointerEvent)=>{if(!closed&&pointers.delete(e.pointerId))publish();}; + const pointerCancel=(e:PointerEvent)=>{if(pointers.has(e.pointerId))pause();}; + win.addEventListener('keydown',down,true);win.addEventListener('keyup',up,true); + win.addEventListener('blur',blur,true);win.addEventListener('pagehide',stop); + doc.addEventListener('focusout',focusOut,true);doc.addEventListener('focusin',focusIn,true); + doc.addEventListener('visibilitychange',hidden);doc.addEventListener('pointerdown',outside,true); + doc.addEventListener('pointerup',pointerUp,true);doc.addEventListener('pointercancel',pointerCancel,true); + doc.addEventListener('contextmenu',pause,true); + const timer=win.setInterval(valid,50); + return { + valid, + pointerDown(id:number,code:string){if(valid()){suspended=false;pointers.set(id,code);publish();}}, + pointerUp(id:number){if(!closed&&pointers.delete(id))publish();}, + pointerCancel(id:number){if(pointers.has(id))pause();}, + dispose(){ + closed=true;clear();callbacks.demand({left:0,right:0});win.clearInterval(timer); + win.removeEventListener('keydown',down,true);win.removeEventListener('keyup',up,true); + win.removeEventListener('blur',blur,true);win.removeEventListener('pagehide',stop); + doc.removeEventListener('focusout',focusOut,true);doc.removeEventListener('focusin',focusIn,true); + doc.removeEventListener('visibilitychange',hidden);doc.removeEventListener('pointerdown',outside,true); + doc.removeEventListener('pointerup',pointerUp,true);doc.removeEventListener('pointercancel',pointerCancel,true); + doc.removeEventListener('contextmenu',pause,true); + }, + }; +} diff --git a/apps/control-station/src/core/fleet/roverInput.ts b/apps/control-station/src/core/fleet/roverInput.ts new file mode 100644 index 0000000..547d1f3 --- /dev/null +++ b/apps/control-station/src/core/fleet/roverInput.ts @@ -0,0 +1,20 @@ +export type RoverMode='arcade'|'tank'; +export type Demand={left:number;right:number}; +export const keysFor=(mode:RoverMode)=>mode==='arcade'?['KeyW','KeyA','KeyS','KeyD']:['KeyQ','KeyE','KeyA','KeyD']; +export function keyDemand(mode:RoverMode,held:ReadonlySet):Demand { + const axis=(positive:string,negative:string)=>Number(held.has(positive))-Number(held.has(negative)); + if(mode==='tank')return {left:axis('KeyQ','KeyA'),right:axis('KeyE','KeyD')}; + const forward=axis('KeyW','KeyS'),turn=axis('KeyD','KeyA'); + const scale=Math.max(1,Math.abs(forward)+Math.abs(turn)); + return {left:(forward+turn)/scale,right:(forward-turn)/scale}; +} +export const defaultRoverSettings={version:1,mode:'arcade' as RoverMode,currentA:30,maxErpm:2000,model:''}; +export function roverSettings(value:unknown):typeof defaultRoverSettings { + const v=value as Partial|null; + return {...defaultRoverSettings,...(v?.version===1?{ + mode:v.mode==='tank'?'tank':'arcade', + currentA:typeof v.currentA==='number'&&Number.isFinite(v.currentA)?Math.max(.5,Math.min(30,v.currentA)):30, + maxErpm:typeof v.maxErpm==='number'&&Number.isFinite(v.maxErpm)?Math.max(300,Math.min(3000,v.maxErpm)):2000, + model:v.model==='dcd006-v020'?v.model:'', + }:{} )}; +} diff --git a/apps/control-station/src/core/fleet/sensorTransport.ts b/apps/control-station/src/core/fleet/sensorTransport.ts index 9ee8749..d557b97 100644 --- a/apps/control-station/src/core/fleet/sensorTransport.ts +++ b/apps/control-station/src/core/fleet/sensorTransport.ts @@ -9,6 +9,7 @@ export function createFleetSensorTransport(vehicleID:string):SensorTransport { return {...value.sensor_state,fresh:value.connectivity==='online'&&value.enrollment==='paired'}; }; return { + configurationArchive:{list:(device,before)=>fleetRequest(`${path}/${encodeURIComponent(device)}/configurations${before?'?before='+encodeURIComponent(before):''}`),read:(device,version)=>fleetRequest(`${path}/${encodeURIComponent(device)}/configurations/${encodeURIComponent(version)}`)}, enrollment:{state:()=>fleetRequest(`${path}/enrollment`),submit:value=>fleetRequest(`${path}/enrollment/operations`,'POST',value),operation:id=>fleetRequest(`${path}/enrollment/operations/${encodeURIComponent(id)}`)}, inventory:async()=>inventory(await fleetRequest()), subscribe:(receive,unavailable)=>{const events=new EventSource('/api/v1/fleet/events');events.onmessage=e=>{try{receive(inventory(JSON.parse(e.data)));}catch{unavailable();}};events.onerror=unavailable;return()=>events.close();}, diff --git a/apps/control-station/src/core/fleet/useRoverControl.ts b/apps/control-station/src/core/fleet/useRoverControl.ts new file mode 100644 index 0000000..a8a8ad1 --- /dev/null +++ b/apps/control-station/src/core/fleet/useRoverControl.ts @@ -0,0 +1,79 @@ +import {useCallback,useEffect,useRef,useState} from 'react'; +import type {Demand} from './roverInput'; + +export interface RoverReading { + id:string;uuid:string;slot:string|null;label:string;age_ms:number; + values:{motor_current_a:number;input_current_a:number;input_voltage_v:number;erpm:number;duty:number;mos_temperature_c:number;fault_code:number}; +} +export interface RoverState { + fresh:boolean;controlling:boolean; + snapshot:{supported?:boolean;instance?:string;state?:string;session_id?:string|null;message?:string|null;devices?:RoverReading[]; + profile?:{layout:null|'1x1'|'2x2';bindings:Record}}; +} +const empty:RoverState={fresh:false,controlling:false,snapshot:{}}; +async function request(url:string,body?:unknown,keepalive=false):Promise{ + const response=await fetch(url,{method:body?'POST':'GET',headers:body?{'Content-Type':'application/json'}:undefined, + body:body?JSON.stringify(body):undefined,cache:'no-store',signal:AbortSignal.timeout(body?350:1000),keepalive}); + const result=await response.json(); + if(!response.ok)throw new Error(typeof result.detail==='string'?result.detail:'Канал управления недоступен.'); + return result; +} +export function useRoverControl(vehicleID:string,visible:boolean){ + const url=`/api/v1/fleet/${encodeURIComponent(vehicleID)}/rover`; + const [state,setState]=useState(empty),[armed,setArmed]=useState(false),[pending,setPending]=useState(false),[error,setError]=useState(null); + const [connecting,setConnecting]=useState(true),armPending=useRef(false); + const session=useRef(null),seq=useRef(0),demand=useRef({left:0,right:0}),sending=useRef(false),generation=useRef(0); + const inputGuard=useRef<(()=>boolean)|null>(null); + const setInputGuard=useCallback((guard:(()=>boolean)|null)=>{inputGuard.current=guard;},[]); + const stop=useCallback(()=>{ + generation.current++;armPending.current=false;const id=session.current;session.current=null;demand.current={left:0,right:0};setArmed(false);setPending(false); + if(id)void request(url+'/command',{session_id:id,sequence:++seq.current,left:0,right:0,stop:true},true).catch(()=>{}); + },[url]); + const send=useCallback(async()=>{ + const id=session.current;if(!id)return; + // Check on every heartbeat, even when a previous request is still pending. + const inputValid=(demand.current.left===0&&demand.current.right===0)||inputGuard.current?.(); + if(document.hidden||!document.hasFocus()||!inputValid)demand.current={left:0,right:0}; + if(sending.current)return; + sending.current=true; + try{await request(url+'/command',{session_id:id,sequence:++seq.current,...demand.current,stop:false});} + catch(e){if(session.current===id){stop();setError(e instanceof Error?e.message:'Команда не подтверждена.');}} + finally{sending.current=false;} + },[url,stop]); + const setDemand=useCallback((next:Demand)=>{demand.current=next;void send();},[send]); + const pauseInput=useCallback(()=>{demand.current={left:0,right:0};void send();},[send]); + const arm=useCallback(async(currentA:number,maxErpm:number)=>{ + if(session.current||armPending.current)return false;const g=++generation.current;armPending.current=true;setError(null);setPending(true); + try{ + const result=await request<{session_id:string}>(url+'/arm',{standstill_confirmed:true,current_a:currentA,max_erpm:maxErpm}); + if(g!==generation.current){void request(url+'/command',{session_id:result.session_id,sequence:1,left:0,right:0,stop:true},true).catch(()=>{});return false;} + // Retire every read started before this acknowledgement, including reads + // begun while POST /arm was pending. They describe the previous authority. + generation.current++;armPending.current=false;setPending(false); + seq.current=0;demand.current={left:0,right:0};session.current=result.session_id;setArmed(true);void send(); + return true; + }catch(e){if(g===generation.current)setError(e instanceof Error?e.message:'Не удалось включить управление.');return false;} + finally{if(g===generation.current){armPending.current=false;setPending(false);}} + },[url,send]); + useEffect(()=>{ + if(!visible){stop();setState(empty);setConnecting(false);return;} + let alive=true,busy=false,lastFault='';const started=performance.now();setConnecting(true); + const poll=async()=>{ + if(busy)return;busy=true;const before=generation.current; + try{const value=await request(url);if(alive&&before===generation.current){setState(value); + setConnecting(!value.fresh&&performance.now()-started<2000); + if(session.current&&(!value.fresh||!value.controlling)){stop();} + const fault=value.snapshot.state==='fault'?value.snapshot.message??'':''; + if(fault&&lastFault!==fault){lastFault=fault;setError(fault);} + }}catch{if(alive&&before===generation.current){setState(empty);setConnecting(false);if(session.current)stop();}}finally{busy=false;} + }; + void poll();const timer=setInterval(()=>void poll(),200),heartbeat=setInterval(()=>void send(),100); + const blur=()=>pauseInput(),hidden=()=>{if(document.hidden)pauseInput();}; + window.addEventListener('blur',blur);window.addEventListener('pagehide',stop);document.addEventListener('visibilitychange',hidden); + return()=>{alive=false;clearInterval(timer);clearInterval(heartbeat);window.removeEventListener('blur',blur);window.removeEventListener('pagehide',stop);document.removeEventListener('visibilitychange',hidden);stop();}; + },[url,visible,stop,send,pauseInput]); + const ready=armed&&state.fresh&&state.snapshot.session_id===session.current&&['ready','driving'].includes(state.snapshot.state??''); + const canArm=state.fresh&&state.snapshot.supported===true&&!state.controlling&&!['preparing','ready','driving','stopping'].includes(state.snapshot.state??''); + return {state,armed,pending,error,connecting,canArm,arm,stop,pauseInput,setDemand,setInputGuard,ready,clearError:()=>setError(null)}; +} +export type RoverController=ReturnType; diff --git a/apps/control-station/src/workspaces/Workspaces.tsx b/apps/control-station/src/workspaces/Workspaces.tsx index 8733927..6f79938 100644 --- a/apps/control-station/src/workspaces/Workspaces.tsx +++ b/apps/control-station/src/workspaces/Workspaces.tsx @@ -431,7 +431,7 @@ export function WorkspaceRenderer(props: WorkspaceRendererProps) { case "missions": return ; case "vehicles": - return ; + return ; case "catalog": return ; case "contour-health": diff --git a/apps/control-station/src/workspaces/fleet/VehicleSensors.tsx b/apps/control-station/src/workspaces/fleet/VehicleSensors.tsx index 10bcd75..10a64c0 100644 --- a/apps/control-station/src/workspaces/fleet/VehicleSensors.tsx +++ b/apps/control-station/src/workspaces/fleet/VehicleSensors.tsx @@ -1,11 +1,13 @@ -import {useMemo} from 'react'; +import {useMemo,type ReactNode} from 'react'; +import {boardLayout} from '../../core/fleet/boardLayout'; import {useDevicePluginHost} from '../../core/device-plugins/DevicePluginHost'; import {createIsolatedRerunHost} from '../../components/rerun/isolatedRerunHost'; import {SensorWorkspace} from '../../../../../packages/sensor-ui/src/SensorWorkspace'; import {createFleetSensorTransport} from '../../core/fleet/sensorTransport'; -export function VehicleSensors({vehicleID,enabled,onDetailChange}:{vehicleID:string;enabled:boolean;onDetailChange:(open:boolean)=>void}){ +export function VehicleSensors({vehicleID,enabled,computer,description}:{vehicleID:string;enabled:boolean;computer:ReactNode;description:string}){ const {registry}=useDevicePluginHost(); const sensorContributions=registry.sensorContributions; + const layout=useMemo(()=>boardLayout(vehicleID),[vehicleID]); const transport=useMemo(()=>createFleetSensorTransport(vehicleID),[vehicleID]); - return ; + return ; } diff --git a/apps/control-station/src/workspaces/fleet/VehiclesWorkspace.tsx b/apps/control-station/src/workspaces/fleet/VehiclesWorkspace.tsx index 9bde6da..9668bcd 100644 --- a/apps/control-station/src/workspaces/fleet/VehiclesWorkspace.tsx +++ b/apps/control-station/src/workspaces/fleet/VehiclesWorkspace.tsx @@ -10,7 +10,7 @@ const platforms = [{ value: "ugv", label: "Наземный (UGV)" }, { value: " const platformLabel = (value: string) => platforms.find(item => item.value === value)?.label ?? value; function statusLabel(item: Vehicle) { return item.enrollment === "pending" ? "Подтверждаем привязку" : item.enrollment === "revoked" ? "Доверие отозвано" : item.enrollment === "failed" ? "Привязка не завершена" : item.connectivity === "online" ? "В сети" : "Нет связи"; } -export function VehiclesWorkspace({ createRequest = 0 }: { createRequest?: number }) { +export function VehiclesWorkspace({ createRequest = 0, headerToolsHost }: { createRequest?: number; headerToolsHost?: HTMLElement|null }) { const fleet = useFleet(); const [adding, setAdding] = useState(false); const [code, setCode] = useState(""); @@ -20,7 +20,6 @@ export function VehiclesWorkspace({ createRequest = 0 }: { createRequest?: numbe const [pending, setPending] = useState(false); const [error, setError] = useState(""); const [selected, setSelected] = useState(null); - const [sensorOpen, setSensorOpen] = useState(false); const [monitorOpen,setMonitorOpen]=useState(false); const [observationOpen,setObservationOpen]=useState(false); const [revoking, setRevoking] = useState(null); @@ -44,25 +43,24 @@ export function VehiclesWorkspace({ createRequest = 0 }: { createRequest?: numbe finally { setPending(false); } } const detail = fleet.items?.find(item => item.id === selected); - if(detail&&observationOpen)return {setObservationOpen(false);setSensorOpen(false);}}/>; + if(detail&&observationOpen)return {setObservationOpen(false);setSelected(null);}} configure={()=>setObservationOpen(false)} headerToolsHost={headerToolsHost}/>; if(detail&&monitorOpen)return setMonitorOpen(false)}/>; return
{fleet.error &&

{fleet.error}

} {!adding && error &&

{error}

} {detail ? <> -
- {!sensorOpen && {fleet.error ? "Нет свежих данных" : statusLabel(detail)}}> +
+ {fleet.error ? "Нет свежих данных" : statusLabel(detail)}}> {detail.notice &&

{detail.notice}

}
Бортовой компьютер
{detail.node_id}
Последняя связь
{detail.last_seen ? new Date(detail.last_seen * 1000).toLocaleString("ru-RU") : "Соединение ещё не получено"}
{detail.host && <>
Имя БК в системе
{detail.host.hostname}
Операционная система
{detail.host.os}
Архитектура
{detail.host.architecture}
Процессоры
{detail.host.cpus}
Память
{detail.host.memory_kib ? `${(detail.host.memory_kib / 1024 / 1024).toFixed(1)} ГиБ` : "Нет сведений"}
}
- + {detail.enrollment !== "revoked" && }
-
} - - : !fleet.items ? : fleet.items.length === 0 ? : {fleet.items.map(item =>
  • } title={item.name} description={`${platformLabel(item.platform)} · бортовой компьютер`} status={{fleet.error ? "Нет свежих данных" : statusLabel(item)}} actions={<> setSelected(item.id)}> {setSelected(item.id);setObservationOpen(true);}}>} />
  • )}
    } + }/> + : !fleet.items ? : fleet.items.length === 0 ? : {fleet.items.map(item =>
  • } title={item.name} description={`${platformLabel(item.platform)} · бортовой компьютер`} status={{fleet.error ? "Нет свежих данных" : statusLabel(item)}} actions={<> setSelected(item.id)}> {setSelected(item.id);setObservationOpen(true);}}>} />
  • )}
    } {preview ? : }}>
    setLayout(current=>({...current,layers:{...current.layers,[source.key]:value}}))} variant="inline"/>} - {source.key!=='map'&&{setFocused(source.key);setLayersOpen(true);}}>} + {source.deviceID&&{setFocused(source.key);setLayersOpen(true);}}>} setFull(value=>value===source.key?null:source.key)}> setVisible(source.key,false)}>
    @@ -63,6 +69,8 @@ export function BoardObservationCenter({vehicleID,name,enabled,back}:{vehicleID: {groups.map(({device,views,Session})=>visible.some(key=>views.some(view=>key===observationKey(device.id,view.id)))&&({id:view.id,layer:layerFor(observationKey(device.id,view.id),view),target:media.current.get(observationKey(device.id,view.id))!,...headers.current.get(observationKey(device.id,view.id))!}))}/>)} {visible.includes('map')&&createPortal(,media.current.get('map')!,'map-session')} + {visible.includes('rover')&&createPortal(,media.current.get('rover')!,'rover-session')} + {visible.includes('telemetry')&&createPortal(,media.current.get('telemetry')!,'telemetry-session')} setLayersOpen(false)} title={active?active.view.label:'Доступные слои'} size="md">
    {!active&&({value:String(i),label:`Окно ${i+1}`}))} onChange={value=>setLayout(current=>moveObservation(current,ids,id,ids[Number(value)]))}/>
    ;})} diff --git a/apps/control-station/src/workspaces/fleet/observation/ObservationDeck.tsx b/apps/control-station/src/workspaces/fleet/observation/ObservationDeck.tsx index 4575e4e..ee52e85 100644 --- a/apps/control-station/src/workspaces/fleet/observation/ObservationDeck.tsx +++ b/apps/control-station/src/workspaces/fleet/observation/ObservationDeck.tsx @@ -11,6 +11,8 @@ export function ObservationMount({element,className='observation-mount'}:{elemen export function ObservationDeck({ids,mounts,layout,onSplit,depth=0}:{ids:string[];mounts:Map;layout:ObservationLayout;onSplit:(key:string,value:number)=>void;depth?:number}){ if(!ids.length)return null; if(ids.length===1)return ; - const mid=Math.ceil(ids.length/2),key=JSON.stringify(ids),orientation=layout.arrangement==='rows'?'horizontal':layout.arrangement==='columns'?'vertical':depth%2?'horizontal':'vertical'; + const defaultColumns=depth===0&&layout.arrangement==='auto'&&!layout.order.some(id=>id==='rover'||id==='telemetry')&&ids.some(id=>!['map','rover','telemetry'].includes(id)); + const cameraCount=ids.filter(id=>!['map','rover','telemetry'].includes(id)).length; + const mid=defaultColumns&&cameraCountonSplit(key,value)} separatorLabel="Изменить размеры окон" primary={} secondary={}/>; } diff --git a/apps/control-station/test/boardLayout.test.mjs b/apps/control-station/test/boardLayout.test.mjs new file mode 100644 index 0000000..e99a342 --- /dev/null +++ b/apps/control-station/test/boardLayout.test.mjs @@ -0,0 +1,57 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {readFileSync} from 'node:fs'; +import ts from 'typescript'; +const code=ts.transpileModule(readFileSync(new URL('../../../packages/sensor-ui/src/boardLayout.ts',import.meta.url),'utf8'),{compilerOptions:{module:ts.ModuleKind.ESNext,target:ts.ScriptTarget.ES2022}}).outputText; +const {createBoardLayoutStore,defaultBoardLayout}=await import('data:text/javascript;base64,'+Buffer.from(code).toString('base64')); +const tick=()=>new Promise(resolve=>setImmediate(resolve)); +function server(){ + let value=structuredClone(defaultBoardLayout);const calls=[]; + return {calls,read:async()=>structuredClone(value),patch:async(section,open)=>{ + calls.push({section,open});await tick(); + value={...value,revision:value.revision+1,open_sections:defaultBoardLayout.open_sections.filter(id=>id===section?open:value.open_sections.includes(id))}; + return structuredClone(value); + }}; +} +test('rapid toggles persist after navigation and preserve all-closed state',async()=>{ + const api=server();const store=createBoardLayoutStore(api);await store.load(); + const unsubscribe=store.subscribe(()=>{}); + store.change(['settings','devices']);store.change(['devices']);store.change([]); + unsubscribe(); + while(store.getSnapshot().saving)await tick(); + const reopened=createBoardLayoutStore(api);await reopened.load(); + assert.deepEqual(reopened.getSnapshot().value.open_sections,[]); + assert.equal(api.calls.length,3); +}); +test('a later toggle wins while an older patch is still in flight',async()=>{ + const api=server();const store=createBoardLayoutStore(api);await store.load(); + store.change(['settings','devices']);store.change(['computer','settings','devices']); + while(store.getSnapshot().saving)await tick(); + assert.deepEqual((await api.read()).open_sections,defaultBoardLayout.open_sections); +}); +test('different vehicles and simultaneous section changes do not clobber one another',async()=>{ + const api=server(),other=server();const a=createBoardLayoutStore(api),b=createBoardLayoutStore(api),c=createBoardLayoutStore(other); + await Promise.all([a.load(),b.load(),c.load()]); + a.change(['settings','devices']);b.change(['computer','devices']); + while(a.getSnapshot().saving||b.getSnapshot().saving)await tick(); + assert.deepEqual((await api.read()).open_sections,['devices']); + assert.deepEqual(c.getSnapshot().value.open_sections,defaultBoardLayout.open_sections); +}); +test('failed save reports failure, rolls back, and explicit reload recovers',async()=>{ + const api=server();let fail=true; + const store=createBoardLayoutStore({...api,patch:(...args)=>fail?Promise.reject(new Error('offline')):api.patch(...args)}); + await store.load();store.change([]); + while(store.getSnapshot().saving)await tick(); + assert.ok(store.getSnapshot().error); + assert.deepEqual(store.getSnapshot().value.open_sections,defaultBoardLayout.open_sections); + fail=false;await store.load();store.change(['devices']); + while(store.getSnapshot().saving)await tick(); + assert.equal(store.getSnapshot().error,null); + assert.deepEqual((await api.read()).open_sections,['devices']); +}); +test('invalid layout cannot overwrite stored preferences',async()=>{ + let writes=0; + const store=createBoardLayoutStore({read:async()=>({...defaultBoardLayout,open_sections:['motor-start']}),patch:async()=>{writes++;return defaultBoardLayout;}}); + await store.load();store.change([]); + assert.equal(store.getSnapshot().ready,false);assert.ok(store.getSnapshot().error);assert.equal(writes,0); +}); diff --git a/apps/control-station/test/roverControl.test.mjs b/apps/control-station/test/roverControl.test.mjs new file mode 100644 index 0000000..3dcdd28 --- /dev/null +++ b/apps/control-station/test/roverControl.test.mjs @@ -0,0 +1,157 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {build} from 'esbuild'; + +async function moduleAt(path) { + const result = await build({entryPoints:[new URL(path,import.meta.url).pathname],bundle:true,write:false,format:'esm',platform:'node'}); + return import('data:text/javascript;base64,'+Buffer.from(result.outputFiles[0].contents).toString('base64')); +} +const {defaultProfile:base,mix,motorDemands,parseProfile}=await moduleAt('../../../packages/rover-control/src/profile.ts'); +const {AuthorityModel}=await moduleAt('../../../packages/rover-control/src/authority.ts'); +const axes=(leftY=0,rightY=0,leftX=0,rightX=0)=>({leftY,rightY,leftX,rightX}); +const profile={...base,deadband:0}; +const arcade={...profile,mode:'arcade'}; +const policy={maxAgeMs:100,neutralMs:200,maxCommandMs:100,monitoredAxes:['leftY','rightY','leftX','rightX']}; +const motors=['a','b','c','d']; +function frame(now,values=axes(),extra={}) { + return {now,link:{state:'live',at:now}, + axes:Object.fromEntries(Object.entries(values).map(([key,value])=>[key,{value,at:now,sequence:now}])), + drives:Object.fromEntries(motors.map(id=>[id,{at:now,healthy:true,stopped:true}])),...extra}; +} +function ready(p=base) { + const model=new AuthorityModel(p,motors,policy,'boot-A'); + for(let t=0;t<=200;t+=50)model.step(frame(t)); + return model; +} +function core() { + const model=ready(); + const {token}=model.step(frame(250,axes(),{requestCore:{owner:'autonomy',sequence:1,at:250}})); + assert.ok(token);return {model,token}; +} +const command=(token,at,sequence=0)=>({token,at,expires:at+75,sequence,demand:{left:.5,right:.5}}); + +test('tank keeps the two motor sides independent, including reversal',()=>{ + assert.deepEqual(mix(profile,axes(.8,-.4)),{left:.8,right:-.4}); + assert.deepEqual(mix(profile,axes(0,.4)),{left:0,right:.4}); +}); +test('arcade cardinal, diagonal and reverse vectors preserve yaw sign',()=>{ + for(const [y,x,left,right] of [[0,0,0,0],[1,0,1,1],[-1,0,-1,-1],[0,1,1,-1],[0,-1,-1,1],[1,1,1,0],[1,-1,0,1],[-1,1,0,-1],[-1,-1,-1,0]]) + assert.deepEqual(mix(arcade,axes(0,y,0,x)),{left,right}); + assert.deepEqual(mix(arcade,axes(0,.5,0,.5)),{left:.5,right:0}); +}); +test('selected stick defines the axes, not the number or position of motors',()=>{ + assert.deepEqual(mix({...arcade,stick:'left'},axes(.5,-1,.5,-1)),{left:.5,right:0}); +}); +test('deadband is continuous and rescaled; response and output scale are separate',()=>{ + assert.deepEqual(mix(base,axes(-.064,.074)),{left:0,right:0}); + assert.deepEqual(mix({...profile,deadband:.2,response:'squared',outputScale:.5},axes(.6,-1)),{left:.12499999999999997,right:-.5}); + assert.ok(Math.abs(mix(base,axes(.150001)).left)<.000002); +}); +test('every point in the input square stays bounded and changes sign symmetrically',()=>{ + for(let i=-20;i<=20;i++)for(let j=-20;j<=20;j++){ + const a=mix({...arcade,outputScale:.8},axes(0,i/20,0,j/20)); + const b=mix({...arcade,outputScale:.8},axes(0,-i/20,0,-j/20)); + assert.ok(Math.abs(a.left)<=.8+1e-12 && Math.abs(a.right)<=.8+1e-12); + assert.ok(Math.abs(a.left+b.left)<1e-12 && Math.abs(a.right+b.right)<1e-12); + } +}); +test('profile JSON rejects unknown fields, malformed numbers and unknown schema',()=>{ + assert.deepEqual(parseProfile(JSON.parse(JSON.stringify(base))),base); + for(const bad of [null,[],{...base,schema:'future'},{...base,revision:1.5},{...base,revision:-1},{...base,deadband:NaN},{...base,outputScale:1.1},{...base,writeVesc:true}])assert.throws(()=>parseProfile(bad)); + for(const value of [NaN,Infinity,1.01,undefined])assert.throws(()=>mix(profile,{...axes(),leftY:value})); +}); +test('2, 4 and 10 motors route by identity and explicit direction, never USB position',()=>{ + for(const n of [2,4,10]){ + const bindings=Array.from({length:n},(_,i)=>({uuid:i.toString(16).padStart(24,'0'),side:imotorDemands({left:1,right:1},[b])); + assert.throws(()=>motorDemands({left:1,right:1},[b,{...b,side:'right'}])); +}); +test('boot with held stick never permits motion; stable stopped neutral required',()=>{ + const model=new AuthorityModel(base,motors,policy,'boot'); + for(let t=0;t<1000;t+=50){const d=model.step(frame(t,axes(1)));assert.equal(d.state,'hold');assert.deepEqual(d.demand,{left:0,right:0});} + for(let t=1000;t<1200;t+=50)assert.equal(model.step(frame(t)).state,'hold'); + assert.equal(model.step(frame(1200)).state,'rc-ready'); + assert.equal(model.step(frame(1250,axes(1))).state,'rc-manual'); +}); +test('first RC gesture stops Core immediately, held packets never count as second gesture',()=>{ + const {model,token}=core(); + assert.equal(model.step(frame(300,axes(),{command:command(token,300)})).state,'core'); + const first=model.step(frame(350,axes(.5),{command:command(token,350,1)})); + assert.equal(first.reason,'rc-takeover');assert.ok(first.stopAll&&first.flushMotionQueue&&first.cancelMotionTasks); + for(let t=400;t<1500;t+=50)assert.deepEqual(model.step(frame(t,axes(.5))).demand,{left:0,right:0}); + for(let t=1500;t<=1700;t+=50)model.step(frame(t)); + const second=model.step(frame(1750,axes(.5))); + assert.equal(second.state,'rc-manual');assert.ok(second.demand.left>0); + assert.equal(model.step(frame(1800)).state,'rc-manual'); + assert.equal(model.step(frame(1850,axes(0,.5),{command:command(token,1850,2)})).state,'rc-manual'); +}); +test('neutral of one channel or a single moving member never completes rearming',()=>{ + for(const kind of ['axis','drive']){ + const {model}=core();model.step(frame(300,axes(1))); + for(let t=350;t<=1500;t+=50){const f=frame(t,kind==='axis'?axes(0,.2):axes());if(kind==='drive')f.drives.d.stopped=false;assert.equal(model.step(f).state,'hold');} + } +}); +test('loss of any of four controllers stops all sides and invalidates Core',()=>{ + for(const id of motors){const {model,token}=core();const f=frame(300,axes(),{command:command(token,300)});delete f.drives[id];const d=model.step(f);assert.equal(d.reason,'drive-unverified');assert.ok(d.cancelMotionTasks);assert.deepEqual(d.demand,{left:0,right:0});} +}); +test('unknown/lost radio cannot be treated as neutral even with fresh zero PWM reads',()=>{ + for(const state of ['lost','unknown']){const {model,token}=core();const d=model.step(frame(300,axes(),{link:{state,at:300},command:command(token,300)}));assert.equal(d.reason,'receiver-unverified');assert.ok(d.stopAll);} +}); +test('old decoded PPM, future samples, modified repeats and sequence rollback are rejected',()=>{ + for(const patch of [{at:0},{at:351},{sequence:249},{value:.4,at:250,sequence:250},{value:NaN}]){ + const {model}=core();const f=frame(350);Object.assign(f.axes.leftY,patch);assert.equal(model.step(f).reason,'axis-invalid'); + } +}); +test('one repeated neutral sample cannot qualify stable neutral',()=>{ + const model=new AuthorityModel(base,motors,{...policy,neutralMs:50},'boot'); + model.step(frame(0));const f=frame(50);f.axes.leftY={at:0,sequence:0,value:0}; + assert.equal(model.step(f).state,'hold'); +}); +test('receiver reconnect while held stays stopped until neutral and a new gesture',()=>{ + const model=ready();model.step(frame(250,axes(.5)));model.step(frame(300,axes(),{link:{state:'lost',at:300}})); + for(let t=350;t<1000;t+=50)assert.equal(model.step(frame(t,axes(.5))).state,'hold'); + for(let t=1000;t<=1200;t+=50)model.step(frame(t)); + assert.equal(model.step(frame(1250,axes(.5))).state,'rc-manual'); +}); +test('Core expiry/replay/wrong boot token/missing commands all require rearming',()=>{ + for(const change of [c=>({...c,expires:300}),c=>({...c,expires:500}),c=>({...c,token:'old-boot:1'}),()=>undefined,c=>({...c,demand:{left:Infinity,right:0}})]){ + const {model,token}=core();assert.equal(model.step(frame(300,axes(),{command:change(command(token,300))})).reason,'core-command-invalid'); + } + const {model,token}=core();model.step(frame(300,axes(),{command:command(token,300,1)})); + assert.equal(model.step(frame(350,axes(),{command:command(token,350,1)})).reason,'core-command-invalid'); +}); +test('clock rewind and missing observation interval cannot bypass the stop gate',()=>{ + for(const time of [249,500,NaN]){const {model,token}=core();const d=model.step(frame(time,axes(),{command:command(token,time)}));assert.equal(d.state,'hold');assert.ok(d.cancelMotionTasks);} +}); +test('premature or replayed Core request does not acquire authority after neutral',()=>{ + const model=new AuthorityModel(base,motors,policy,'boot'); + for(let t=0;t<=300;t+=50){const d=model.step(frame(t,axes(),{requestCore:{owner:'remote',sequence:1,at:t}}));assert.notEqual(d.state,'core');} + assert.equal(model.step(frame(350,axes(),{requestCore:{owner:'remote',sequence:2,at:350}})).state,'core'); +}); +test('reboot cannot restore authority from serialized profile or old grant',()=>{ + const {token}=core(), model=ready(); + assert.equal(model.step(frame(250,axes(),{command:command(token,250)})).state,'rc-ready'); + const next=new AuthorityModel(base,motors,policy,'boot-B'); + for(let t=0;t<=200;t+=50)next.step(frame(t)); + const grant=next.step(frame(250,axes(),{requestCore:{owner:'remote',sequence:1,at:250}})); + assert.notEqual(grant.token,token); + assert.equal(next.step(frame(300,axes(),{command:command(token,300)})).state,'hold'); +}); +test('profile limits do not mask RC takeover intent at zero output scale',()=>{ + const model=ready({...base,outputScale:0});const grant=model.step(frame(250,axes(),{requestCore:{owner:'remote',sequence:1,at:250}})); + assert.ok(grant.token);assert.equal(model.step(frame(300,axes(.5))).reason,'rc-takeover'); +}); + +test('non-driving stick still takes over in Arcade and every monitored axis must return',()=>{ + const model=ready({...base,mode:'arcade',stick:'right'}); + model.step(frame(250,axes(),{requestCore:{owner:'autonomy',sequence:1,at:250}})); + assert.equal(model.step(frame(300,axes(.5))).reason,'rc-takeover'); + for(let t=350;t<800;t+=50)assert.equal(model.step(frame(t,axes(0,0,.5))).state,'hold'); + const f=frame(800);delete f.axes.leftX;assert.equal(model.step(f).reason,'axis-invalid'); + assert.throws(()=>new AuthorityModel(base,motors,{...policy,monitoredAxes:['leftY']},'boot')); +}); diff --git a/apps/control-station/test/roverControlFocus.test.mjs b/apps/control-station/test/roverControlFocus.test.mjs new file mode 100644 index 0000000..c3fd7a7 --- /dev/null +++ b/apps/control-station/test/roverControlFocus.test.mjs @@ -0,0 +1,49 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {build} from 'esbuild'; +const result=await build({entryPoints:[new URL('../src/core/fleet/useRoverControl.ts',import.meta.url).pathname],bundle:true,write:false,platform:'node',format:'esm',plugins:[{name:'hook-fixture',setup(b){ + b.onResolve({filter:/^react$/},()=>({path:'react',namespace:'fixture'})); + b.onLoad({filter:/.*/,namespace:'fixture'},()=>({contents:`export const useRef=v=>({current:v});export const useState=v=>[v,()=>{}];export const useCallback=f=>f;export const useEffect=f=>globalThis.roverEffects.push(f);`})); +}}]}); +const {useRoverControl}=await import('data:text/javascript;base64,'+Buffer.from(result.outputFiles[0].contents).toString('base64')); +const flush=()=>new Promise(resolve=>setImmediate(resolve)); +async function fixture(run){ + const originals=Object.fromEntries(['window','document','fetch','roverEffects','setInterval','clearInterval'].map(k=>[k,globalThis[k]])); + let focus=true,hidden=false;const calls=[],timers=[];globalThis.roverEffects=[]; + globalThis.window={addEventListener(){},removeEventListener(){}}; + globalThis.document={get hidden(){return hidden;},hasFocus:()=>focus,addEventListener(){},removeEventListener(){}}; + globalThis.setInterval=(f,ms)=>{const timer={f,ms};timers.push(timer);return timer;};globalThis.clearInterval=()=>{}; + globalThis.fetch=async(url,init)=>{const body=init.body?JSON.parse(init.body):null;calls.push({url,body});return {ok:true,json:async()=>url.endsWith('/arm')?{session_id:'fixture-session'}:{fresh:true,controlling:true,snapshot:{state:'ready'}}};}; + const c=useRoverControl('fixture',true);const cleanup=globalThis.roverEffects.map(f=>f()); + try{await flush();await c.arm(30,2000);await flush();await run({c,calls,timers,blur:()=>{focus=false;},focus:()=>{focus=true;},hide:()=>{hidden=true;}});} + finally{cleanup.forEach(f=>f?.());await flush();for(const [key,value]of Object.entries(originals)){if(value===undefined)delete globalThis[key];else globalThis[key]=value;}} +} +test('command heartbeat sends neutral on lost blur, keeps session, and accepts new focused input',async()=>fixture(async f=>{ + f.c.setInputGuard(()=>true);f.c.setDemand({left:1,right:-1});await flush(); + assert.equal(f.calls.at(-1).body.left,1);f.blur();f.timers.find(t=>t.ms===100).f();await flush(); + assert.equal(f.calls.at(-1).body.stop,false);assert.equal(f.calls.at(-1).body.left,0); + f.c.setDemand({left:1,right:1});await flush();assert.equal(f.calls.at(-1).body.left,0); + f.focus();f.timers.find(t=>t.ms===100).f();await flush();assert.equal(f.calls.at(-1).body.left,0); + f.c.setDemand({left:1,right:1});await flush();assert.equal(f.calls.at(-1).body.left,1); + assert.equal(f.calls.filter(x=>x.url.endsWith('/arm')).length,1); +})); +test('expired physical input prevents heartbeat from renewing remembered demand',async()=>fixture(async f=>{ + let valid=true;f.c.setInputGuard(()=>valid);f.c.setDemand({left:1,right:-1});await flush();valid=false; + f.timers.find(t=>t.ms===100).f();await flush();assert.equal(f.calls.at(-1).body.stop,false); +})); +test('no registered input scope cannot issue motion',async()=>fixture(async f=>{ + f.c.setDemand({left:1,right:1});await flush();assert.equal(f.calls.at(-1).body.stop,false); + assert.equal(f.calls.some(x=>x.body?.left===1),false); +})); +test('hidden document sends only neutral without a visibility event',async()=>fixture(async f=>{ + f.c.setInputGuard(()=>true);f.hide();f.c.setDemand({left:1,right:1});await flush(); + assert.equal(f.calls.at(-1).body.stop,false);assert.equal(f.calls.some(x=>x.body?.left===1),false); +})); + +test('explicit pause clears demand without revoking session; explicit stop still revokes it',async()=>fixture(async f=>{ + f.c.setInputGuard(()=>true);f.c.setDemand({left:1,right:-1});await flush();f.c.pauseInput();await flush(); + assert.equal(f.calls.at(-1).body.stop,false);assert.equal(f.calls.at(-1).body.left,0); + f.c.setDemand({left:1,right:1});await flush();assert.equal(f.calls.at(-1).body.left,1); + f.c.stop();await flush();assert.equal(f.calls.at(-1).body.stop,true); + const count=f.calls.length;f.c.setDemand({left:1,right:1});await flush();assert.equal(f.calls.length,count); +})); diff --git a/apps/control-station/test/roverControlStartup.test.mjs b/apps/control-station/test/roverControlStartup.test.mjs new file mode 100644 index 0000000..05b371e --- /dev/null +++ b/apps/control-station/test/roverControlStartup.test.mjs @@ -0,0 +1,49 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {build} from 'esbuild'; +const result=await build({entryPoints:[new URL('../src/core/fleet/useRoverControl.ts',import.meta.url).pathname],bundle:true,write:false,platform:'node',format:'esm',plugins:[{name:'hook-fixture',setup(b){ + b.onResolve({filter:/^react$/},()=>({path:'react',namespace:'fixture'})); + b.onLoad({filter:/.*/,namespace:'fixture'},()=>({contents:`export const useRef=v=>({current:v});export const useState=v=>[v,()=>{}];export const useCallback=f=>f;export const useEffect=f=>globalThis.roverEffects.push(f);`})); +}}]}); +const {useRoverControl}=await import('data:text/javascript;base64,'+Buffer.from(result.outputFiles[0].contents).toString('base64')); +const flush=()=>new Promise(resolve=>setImmediate(resolve)); +const deferred=()=>{let resolve;const promise=new Promise(r=>resolve=r);return {promise,resolve};}; +const response=value=>({ok:true,json:async()=>value}); +const idle={fresh:true,controlling:false,snapshot:{supported:true,state:'observing'}}; +async function fixture(run){ + const originals=Object.fromEntries(['window','document','fetch','roverEffects','setInterval','clearInterval'].map(k=>[k,globalThis[k]])); + const calls=[],timers=[];globalThis.roverEffects=[]; + globalThis.window={addEventListener(){},removeEventListener(){}};globalThis.document={hidden:false,hasFocus:()=>true,addEventListener(){},removeEventListener(){}}; + globalThis.setInterval=(f,ms)=>{timers.push({f,ms});return timers.length;};globalThis.clearInterval=()=>{}; + let armResponse=null,nextPoll=null; + globalThis.fetch=async(url,init)=>{const body=init.body?JSON.parse(init.body):null;calls.push({url,body}); + if(url.endsWith('/arm'))return armResponse?armResponse.promise:response({session_id:'new-session'}); + if(!body)return nextPoll?nextPoll.promise:response(idle); + return response({accepted_sequence:body.sequence}); + }; + const c=useRoverControl('fixture',true),cleanup=globalThis.roverEffects.map(f=>f()); + try{await flush();await run({c,calls,poll:()=>timers.find(t=>t.ms===200).f(),heartbeat:()=>timers.find(t=>t.ms===100).f(),delayArm:()=>armResponse=deferred(),delayPoll:()=>nextPoll=deferred()});} + finally{cleanup.forEach(f=>f?.());await flush();for(const [key,value]of Object.entries(originals)){if(value===undefined)delete globalThis[key];else globalThis[key]=value;}} +} +test('idle poll begun during pending arm cannot revoke newly acknowledged session',async()=>fixture(async f=>{ + const a=f.delayArm();const arming=f.c.arm(30,2000);const p=f.delayPoll();f.poll(); + a.resolve(response({session_id:'new-session'}));assert.equal(await arming,true);await flush(); + p.resolve(response(idle));await flush();f.heartbeat();await flush(); + assert.equal(f.calls.filter(x=>x.body?.stop===true).length,0,'old idle poll revoked the new session'); + assert.equal(f.calls.at(-1).body.session_id,'new-session'); +})); +test('failed poll begun during pending arm cannot revoke newly acknowledged session',async()=>fixture(async f=>{ + const a=f.delayArm();const arming=f.c.arm(30,2000);const p=f.delayPoll();f.poll(); + a.resolve(response({session_id:'new-session'}));assert.equal(await arming,true);await flush(); + p.resolve({ok:false,json:async()=>({detail:'stale read failure'})});await flush(); + assert.equal(f.calls.filter(x=>x.body?.stop===true).length,0,'old failed poll revoked the new session'); +})); +test('a current poll still revokes control when server reports lease lost',async()=>fixture(async f=>{ + assert.equal(await f.c.arm(30,2000),true);await flush();f.poll();await flush(); + assert.equal(f.calls.at(-1).body.stop,true); +})); +test('duplicate arm clicks while request pending create exactly one lease request',async()=>fixture(async f=>{ + const a=f.delayArm();const first=f.c.arm(30,2000),second=f.c.arm(30,2000); + a.resolve(response({session_id:'new-session'}));await Promise.all([first,second]);await flush(); + assert.equal(f.calls.filter(x=>x.url.endsWith('/arm')).length,1); +})); diff --git a/apps/control-station/test/roverHoldInput.test.mjs b/apps/control-station/test/roverHoldInput.test.mjs new file mode 100644 index 0000000..5b2af77 --- /dev/null +++ b/apps/control-station/test/roverHoldInput.test.mjs @@ -0,0 +1,85 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {build} from 'esbuild'; +const result=await build({entryPoints:[new URL('../src/core/fleet/roverHoldInput.ts',import.meta.url).pathname],bundle:true,write:false,platform:'node',format:'esm'}); +const {bindRoverHoldInput}=await import('data:text/javascript;base64,'+Buffer.from(result.outputFiles[0].contents).toString('base64')); +class Events { + listeners=new Map();timers=new Map();next=0; + addEventListener(type,fn){if(!this.listeners.has(type))this.listeners.set(type,new Set());this.listeners.get(type).add(fn);} + removeEventListener(type,fn){this.listeners.get(type)?.delete(fn);} + emit(type,detail={}){const e={target:this,isTrusted:true,preventDefault(){},...detail};for(const fn of [...(this.listeners.get(type)??[])])fn(e);} + setInterval(fn){const id=++this.next;this.timers.set(id,fn);return id;} + clearInterval(id){this.timers.delete(id);} + tick(){for(const fn of this.timers.values())fn();} +} +function fixture(mode='arcade'){ + let time=0,focus=true;const win=new Events(),doc=new Events();doc.defaultView=win;doc.hidden=false;doc.hasFocus=()=>focus; + const scope={ownerDocument:doc,closest:()=>null},child={closest:()=>null};scope.contains=x=>x===scope||x===child;doc.activeElement=scope; + const states=[],demands=[];let stops=0,pauses=0; + const binding=bindRoverHoldInput(scope,mode,{held:k=>states.push([...k]),demand:d=>demands.push(d),stop:()=>stops++,pause:()=>{pauses++;demands.push({left:0,right:0});}},()=>time); + const key=(code,repeat=false)=>win.emit('keydown',{code,repeat,target:scope}); + return {win,doc,scope,child,binding,states,demands,key,stops:()=>stops,pauses:()=>pauses,advance:ms=>{time+=ms;win.tick();},loseFocus:()=>{focus=false;},restoreFocus:()=>{focus=true;},up:code=>win.emit('keyup',{code,target:scope})}; +} +test('blur clears D, ignores a late repeat, but accepts a fresh press without rearming',()=>{ + const f=fixture();f.key('KeyD');assert.deepEqual(f.demands.at(-1),{left:1,right:-1}); + f.win.emit('blur');assert.equal(f.pauses(),1);assert.equal(f.stops(),0);assert.deepEqual(f.states.at(-1),[]); + assert.deepEqual(f.demands.at(-1),{left:0,right:0});const count=f.demands.length; + f.key('KeyD',true);assert.equal(f.demands.length,count); + f.key('KeyW');assert.deepEqual(f.demands.at(-1),{left:1,right:1});assert.equal(f.binding.valid(),true);f.binding.dispose(); +}); +test('heartbeat validity catches missing blur event using document focus',()=>{ + const f=fixture();f.key('KeyD');f.loseFocus();assert.equal(f.binding.valid(),false);assert.equal(f.pauses(),1); + f.restoreFocus();assert.equal(f.binding.valid(),true);f.binding.dispose(); +}); +test('focus polling catches missing event or moving to another control',()=>{ + const f=fixture();f.key('KeyW');f.doc.activeElement={};f.advance(50);assert.equal(f.pauses(),1);f.binding.dispose(); +}); +test('lost keyup AND missing focus notifications still expire repeated D',()=>{ + const f=fixture();f.key('KeyD');f.advance(500);f.key('KeyD',true);f.advance(299);assert.equal(f.pauses(),0); + f.advance(1);assert.equal(f.pauses(),1);assert.deepEqual(f.states.at(-1),[]);f.key('KeyD',true);assert.equal(f.pauses(),1);f.binding.dispose(); +}); +test('first press allows OS repeat delay but never an indefinite hold',()=>{ + const f=fixture();f.key('KeyW');f.advance(999);assert.equal(f.pauses(),0);f.advance(1);assert.equal(f.pauses(),1);f.binding.dispose(); +}); +test('continuous repeat renews hold; keyup immediately sends neutral',()=>{ + const f=fixture();f.key('KeyW');f.advance(600); + for(let i=0;i<30;i++){f.key('KeyW',true);f.advance(100);} + assert.equal(f.pauses(),0);f.up('KeyW');assert.deepEqual(f.demands.at(-1),{left:0,right:0});f.advance(1200);assert.equal(f.pauses(),0);f.binding.dispose(); +}); +test('diagonal input survives single-key OS repeat; release recomputes demand',()=>{ + const f=fixture();f.key('KeyW');f.key('KeyA'); + for(let i=0;i<20;i++){f.key('KeyA',true);f.advance(100);} + assert.equal(f.pauses(),0);assert.deepEqual(f.demands.at(-1),{left:0,right:1}); + f.up('KeyA');assert.deepEqual(f.demands.at(-1),{left:1,right:1});f.up('KeyW');f.binding.dispose(); +}); +test('repeat without a fresh press and untrusted events never command motion',()=>{ + const f=fixture();f.key('KeyD',true);f.win.emit('keydown',{code:'KeyW',isTrusted:false,target:f.scope});assert.equal(f.demands.length,0);f.binding.dispose(); +}); +test('focus inside the view is allowed; leaving or hiding it stops',()=>{ + for(const event of ['focusout','visibilitychange','pagehide','outside']){ + const f=fixture();f.key('KeyW');f.doc.emit('focusout',{target:f.scope,relatedTarget:f.child});assert.equal(f.pauses(),0); + if(event==='focusout')f.doc.emit(event,{target:f.scope,relatedTarget:null}); + else if(event==='visibilitychange'){f.doc.hidden=true;f.doc.emit(event);} + else if(event==='pagehide')f.win.emit(event); + else f.doc.emit('pointerdown',{target:{}}); + assert.equal(event==='pagehide'?f.stops():f.pauses(),1,event);f.binding.dispose(); + } +}); +test('pointer capture lost or cancelled releases every input',()=>{ + for(const mode of ['arcade','tank']){ + const f=fixture(mode);f.binding.pointerDown(1,mode==='arcade'?'KeyW':'KeyQ');f.binding.pointerCancel(1); + assert.equal(f.pauses(),1);assert.deepEqual(f.states.at(-1),[]);f.binding.dispose(); + } +}); +test('document pointerup releases even if button handler does not receive it',()=>{ + const f=fixture();f.binding.pointerDown(1,'KeyD');f.doc.emit('pointerup',{pointerId:1});assert.deepEqual(f.demands.at(-1),{left:0,right:0});assert.equal(f.pauses(),0);f.binding.dispose(); +}); +test('modifier shortcuts pause; cleanup removes listeners, timer and held demand',()=>{ + const f=fixture();f.key('KeyD');f.win.emit('keydown',{code:'Tab',metaKey:true,target:f.scope});assert.equal(f.pauses(),1);f.binding.dispose(); + assert.deepEqual(f.demands.at(-1),{left:0,right:0});assert.equal(f.win.timers.size,0); + assert.equal([...f.win.listeners.values(),...f.doc.listeners.values()].reduce((sum,x)=>sum+x.size,0),0); +}); + +test('Space and Escape remain explicit session stops',()=>{ + for(const code of ['Space','Escape']){const f=fixture();f.key('KeyW');f.key(code);assert.equal(f.stops(),1);assert.equal(f.binding.valid(),false);f.binding.dispose();} +}); diff --git a/apps/control-station/test/roverInput.test.mjs b/apps/control-station/test/roverInput.test.mjs new file mode 100644 index 0000000..f1d41f0 --- /dev/null +++ b/apps/control-station/test/roverInput.test.mjs @@ -0,0 +1,23 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {build} from 'esbuild'; +const result=await build({entryPoints:[new URL('../src/core/fleet/roverInput.ts',import.meta.url).pathname],bundle:true,write:false,platform:'node',format:'esm'}); +const {keyDemand,roverSettings}=await import('data:text/javascript;base64,'+Buffer.from(result.outputFiles[0].contents).toString('base64')); +const keys=(...k)=>new Set(k.map(v=>'Key'+v)); +test('arcade mixes directions and opposite keys cancel',()=>{ + assert.deepEqual(keyDemand('arcade',keys('W')),{left:1,right:1}); + assert.deepEqual(keyDemand('arcade',keys('A')),{left:-1,right:1}); + assert.deepEqual(keyDemand('arcade',keys('W','A')),{left:0,right:1}); + assert.deepEqual(keyDemand('arcade',keys('W','S','A','D')),{left:0,right:0}); +}); +test('tank sides and backwards remain independent',()=>{ + assert.deepEqual(keyDemand('tank',keys('Q','D')),{left:1,right:-1}); + assert.deepEqual(keyDemand('tank',keys('A','D')),{left:-1,right:-1}); + assert.deepEqual(keyDemand('tank',keys('Q','A')),{left:0,right:0}); +}); +test('invalid stored settings never grant motion or invalid limits',()=>{ + assert.equal(roverSettings({version:1,currentA:Infinity}).currentA,30); + assert.equal(roverSettings({version:1,maxErpm:999999}).maxErpm,3000); + assert.equal(roverSettings({version:1,model:'https://other/model.glb'}).model,''); + assert.deepEqual(keyDemand('arcade',new Set()),{left:0,right:0}); +}); diff --git a/apps/control-station/test/vescUi.test.mjs b/apps/control-station/test/vescUi.test.mjs new file mode 100644 index 0000000..b599a23 --- /dev/null +++ b/apps/control-station/test/vescUi.test.mjs @@ -0,0 +1,70 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {readFileSync} from 'node:fs'; +import ts from 'typescript'; + +const source=readFileSync(new URL('../../../plugins/vesc/frontend/src/model.ts',import.meta.url),'utf8'); +const code=ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.ESNext}}).outputText; +const {vescLabel,testInput,speedInput,rotationResult,driveTestIds}=await import('data:text/javascript;base64,'+Buffer.from(code).toString('base64')); +const controller={online:true,prepared:true,verified:true,vesc_status:{identity:{uuid:'synthetic'},readable:true}}; + +test('speed mode requires board capability and reports measured rotation time',()=>{ + assert.ok(speedInput('1200',undefined).error); + const bounds={min_erpm:300,max_erpm:3000,duration_basis:'measured_speed'}; + assert.equal(speedInput('1200',bounds).error,null); + for(const value of ['', 'NaN','299','3001'])assert.ok(speedInput(value,bounds).error); + const text=rotationResult({outcome:'stopped',rotation_s:2.5,release_confirmed:true,limits_restored:true}); + assert.match(text,/2,5 с/); + assert.match(text,/Проверка остановлена/); + assert.doesNotMatch(text,/время вращения набрано/); +}); + +test('USB discovery and model preparation never claim a verified VESC',()=>{ + assert.equal(vescLabel({...controller,prepared:false},true).label,'Требуется подготовка'); + assert.equal(vescLabel({...controller,vesc_status:{identity:null,readable:false}},true).tone,'warning'); +}); +test('fresh read capability is distinct from unsupported firmware and offline state',()=>{ + assert.equal(vescLabel(controller,true).label,'Готов к чтению'); + assert.equal(vescLabel(controller,false).label,'Нет связи'); + assert.equal(vescLabel({...controller,online:false},true).label,'Нет связи'); + assert.equal(vescLabel({...controller,vesc_status:{identity:{uuid:'synthetic'},readable:false}},true).label,'Прошивка не поддерживается'); +}); + +test('test fields accept entered values only inside the board capability bounds',()=>{ + const bounds={min_current_a:0.5,max_current_a:5,min_duration_s:0.5,max_duration_s:10}; + assert.equal(testInput('3.7','7.2',bounds).valid,true); + assert.equal(testInput('5','10',bounds).valid,true); + for(const [amps,seconds] of [['','5'],['5',''],['60','10'],['5','11'],['NaN','1'],['Infinity','1']])assert.equal(testInput(amps,seconds,bounds).valid,false); + assert.equal(testInput('2','1.5',undefined).valid,false); + const overlong=testInput('5','15',bounds); + assert.equal(overlong.valid,false); + assert.equal(overlong.currentError,null); + assert.equal(overlong.durationError,'Длительность должна быть от 0,5 до 10 с.'); + assert.equal(testInput('5','10',bounds).durationError,null); +}); + +// Capabilities come from the installed board: a newer Core must retain old bounds. +test('extended board accepts 30 A / 30 s without changing older board bounds',()=>{ + const old={min_current_a:0.5,max_current_a:5,min_duration_s:0.5,max_duration_s:10}; + const expanded={...old,max_current_a:30,max_duration_s:30,continuous_current:true}; + assert.equal(testInput('30','30',expanded).valid,true); + assert.equal(testInput('30.1','30',expanded).valid,false); + assert.equal(testInput('30','30.1',expanded).valid,false); + assert.equal(testInput('30','30',old).valid,false); +}); + + +test('group spin requires a complete unique profile containing the selected controller',()=>{ + const profile={layout:'1x1',revision:3,bindings:{'left.1':{device_id:'left',uuid:'a'},'right.1':{device_id:'right',uuid:'b'}}}; + assert.deepEqual(driveTestIds(profile,'left'),['left','right']); + assert.deepEqual(driveTestIds(profile,'unassigned'),[]); + assert.deepEqual(driveTestIds({...profile,bindings:{'left.1':profile.bindings['left.1']}},'left'),[]); + assert.deepEqual(driveTestIds({...profile,bindings:{...profile.bindings,'right.1':profile.bindings['left.1']}},'left'),[]); + assert.deepEqual(driveTestIds(undefined,'left'),[]); +}); + +test('four-wheel profile preserves front and rear membership for common testing',()=>{ + const ids=['lf','lr','rf','rr']; + const bindings=Object.fromEntries(['left.1','left.2','right.1','right.2'].map((slot,i)=>[slot,{device_id:ids[i],uuid:ids[i]}])); + assert.deepEqual(driveTestIds({layout:'2x2',revision:4,bindings},'rf'),ids); +}); diff --git a/apps/control-station/tools/rover-control-preview/build.mjs b/apps/control-station/tools/rover-control-preview/build.mjs new file mode 100644 index 0000000..aa32ec2 --- /dev/null +++ b/apps/control-station/tools/rover-control-preview/build.mjs @@ -0,0 +1,18 @@ +import {build} from 'esbuild'; +import {mkdir,writeFile} from 'node:fs/promises'; +import {fileURLToPath} from 'node:url'; +import {resolve,join} from 'node:path'; + +const root=fileURLToPath(new URL('.',import.meta.url)); +const destination=process.argv[2]; +if(!destination)throw Error('Pass an artifact output directory; no server is started.'); +const result=await build({entryPoints:[join(root,'main.tsx')],bundle:true,write:false,minify:true, + format:'iife',platform:'browser',jsx:'automatic',outfile:'preview.js',define:{'process.env.NODE_ENV':'"production"'}, + alias:{'@nodedc/ui-react':resolve(root,'../../node_modules/@nodedc/ui-react/dist/index.js')}, + nodePaths:[resolve(root,'../../node_modules')],logLevel:'warning'}); +const js=result.outputFiles.find(f=>f.path.endsWith('.js')).text.replaceAll('f.path.endsWith('.css')).text.replaceAll('Mission Core · Профили управления · Прототип
    `; +await writeFile(join(destination,'rover-control-preview.html'),html); +console.log(JSON.stringify({artifact:join(destination,'rover-control-preview.html'),bytes:Buffer.byteLength(html),network:'disabled by CSP',hardware:'no adapter'})); diff --git a/apps/control-station/tools/rover-control-preview/main.tsx b/apps/control-station/tools/rover-control-preview/main.tsx new file mode 100644 index 0000000..ab36fa7 --- /dev/null +++ b/apps/control-station/tools/rover-control-preview/main.tsx @@ -0,0 +1,69 @@ +import {useState} from 'react'; +import {createRoot} from 'react-dom/client'; +import {Button,Inspector,InspectorSelectField,RangeControl,SettingsCard,StatusBadge} from '@nodedc/ui-react'; +import '@nodedc/ui-core/styles.css'; +import {defaultProfile,mix,parseProfile,type Axes,type ControlProfile} from '../../../../packages/rover-control/src/profile'; +import {trace} from './trace'; +import './preview.css'; + +const emptyAxes:Axes={leftY:0,rightY:0,leftX:0,rightX:0}; +const percent=(n:number)=>`${Math.round(n*100)}%`; +const states={'hold':'Остановка / ожидание нейтрали','rc-ready':'Пульт готов','rc-manual':'Ручное управление','core':'Mission Core'}; +const storageKey='missioncore.rover-control-preview.v1'; +function load():ControlProfile { + try{const raw=localStorage.getItem(storageKey);return raw?parseProfile(JSON.parse(raw)):{...defaultProfile};} + catch{return {...defaultProfile};} +} +function App(){ + const [profile,setProfile]=useState(load); + const [axes,setAxes]=useState(emptyAxes); + const [open,setOpen]=useState(['settings','preview']); + const [saved,setSaved]=useState(''); + const [light,setLight]=useState(false); + const change=(patch:Partial)=>{ + setProfile(parseProfile({...profile,...patch,revision:profile.revision+1}));setAxes(emptyAxes);setSaved(''); + }; + const result=mix(profile,axes); + const y=profile.mode==='tank'?'leftY':profile.stick==='right'?'rightY':'leftY'; + const x=profile.mode==='tank'?'rightY':profile.stick==='right'?'rightX':'leftX'; + const field=(key:keyof Axes,label:string)=>setAxes({...axes,[key]:value})}/>; + function save(){ + try{localStorage.setItem(storageKey,JSON.stringify(profile));setSaved('Черновик сохранён в этом браузере.');} + catch{setSaved('Браузер не сохранил черновик. Используйте скачивание JSON.');} + } + function download(){ + const url=URL.createObjectURL(new Blob([JSON.stringify(profile,null,2)+'\n'],{type:'application/json'})); + const a=document.createElement('a');a.href=url;a.download='rover-control-draft.json';a.click(); + setTimeout(()=>URL.revokeObjectURL(url),1000); + } + return
    +

    MISSION CORE · ПРОТОТИП

    Настройки управления ровером

    +

    Интерактивный макет без подключения к роверу. Все значения ниже — расчёт на экране; кнопки не обращаются к борту и VESC.

    + + change({mode})}/> + {profile.mode==='arcade'&&change({stick})}/>} +

    {profile.mode==='tank'?'Левый рычаг управляет левой стороной, правый — правой.':'Вперёд/назад задаёт движение; влево/вправо — поворот, включая разворот на месте. При движении назад знак поворота корпуса сохраняется.'}

    + change({response})}/> + change({deadband})}/> + change({outputScale})}/> +

    Масштаб команды — относительный отклик рычага. Он не задаёт амперы, ватты или паспортный предел двигателя.

    +
    + {saved&&

    {saved}

    } + }, + {id:'preview',label:'Проверка профиля на экране',content: + {field(y,profile.mode==='tank'?'Левый рычаг · вперёд / назад':'Движение · вперёд / назад')} + {field(x,profile.mode==='tank'?'Правый рычаг · вперёд / назад':'Поворот · влево / вправо')} +
    +
    {(['left','right'] as const).map(side=>
    {side==='left'?'Левая сторона':'Правая сторона'}{percent(result[side])}{result[side]===0?'Нейтраль':result[side]>0?'Вперёд':'Назад'}
    )}
    +

    Команда стороны распространяется на все назначенные ей моторы: два, четыре, шесть и более. Связь — по UUID, а направление проверяется отдельно для каждого мотора.

    +
    }, + {id:'takeover',label:'Перехват пультом · проверка сценария',content: +
    {trace(profile).map(({label,decision},i)=>
    {i+1}. {label}{states[decision.state]}{percent(decision.demand.left)} / {percent(decision.demand.right)}
    )}
    +

    Первый жест отменяет допуск и очередь Mission Core. Новый допуск требует явного действия: нейтраль и восстановление связи не возобновляют автономную задачу.

    +
    }, + ]}/> +
    ; +} +document.documentElement.dataset.nodedcTheme='dark'; +createRoot(document.getElementById('root')!).render(); diff --git a/apps/control-station/tools/rover-control-preview/preview.css b/apps/control-station/tools/rover-control-preview/preview.css new file mode 100644 index 0000000..6a26ea5 --- /dev/null +++ b/apps/control-station/tools/rover-control-preview/preview.css @@ -0,0 +1,16 @@ +/* Domain composition only; all controls come from the Design Guideline. */ +body { margin:0; background:var(--nodedc-canvas); color:var(--nodedc-text-primary); font-family:var(--nodedc-font-family); font-size:var(--nodedc-font-size-md); } +main { max-width:960px; margin:0 auto; padding:32px 24px 64px; } +main>header { display:flex; align-items:center; justify-content:space-between; gap:24px; margin-bottom:24px; } +h1 { font-size:var(--nodedc-font-size-title); font-weight:600; margin:8px 0; } +p { font-size:var(--nodedc-font-size-sm); line-height:1.6; color:var(--nodedc-text-secondary); } +main>header p { font-size:var(--nodedc-font-size-xs); } +.notice { margin-bottom:24px; } +.actions { display:flex; flex-wrap:wrap; gap:12px; margin:16px 0; } +.demands { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:24px; margin:24px 0; } +.demands>div { display:flex; flex-direction:column; gap:8px; } +.demands strong { font-size:var(--nodedc-font-size-lg); font-variant-numeric:tabular-nums; } +.demands span { font-size:var(--nodedc-font-size-sm); } +.trace { display:grid; gap:20px; } +.trace-row { display:grid; grid-template-columns:minmax(0,1fr) 250px 100px; align-items:center; gap:16px; font-size:var(--nodedc-font-size-sm); } +@media (max-width:720px) { main {padding:20px 12px 40px;} .trace-row {grid-template-columns:1fr;} } diff --git a/apps/control-station/tools/rover-control-preview/trace.ts b/apps/control-station/tools/rover-control-preview/trace.ts new file mode 100644 index 0000000..ee8765b --- /dev/null +++ b/apps/control-station/tools/rover-control-preview/trace.ts @@ -0,0 +1,38 @@ +import {AuthorityModel, type Decision} from '../../../../packages/rover-control/src/authority'; +import {type ControlProfile} from '../../../../packages/rover-control/src/profile'; + +/** Synthetic event replay, with no wall clock, I/O, timers or hardware adapter. */ +export function trace(profile: ControlProfile): {label: string; decision: Decision}[] { + const model = new AuthorityModel(profile, ['left', 'right'], { + maxAgeMs:100, neutralMs:200, maxCommandMs:100, monitoredAxes:['leftY','rightY','leftX','rightX'], + }, 'preview'); + const rows: {label:string;decision:Decision}[] = []; + let now = -50, token = ''; + function step(value=0, link: 'live'|'lost'='live', core=false, request=false) { + now+=50; + const axes = {leftY:0,rightY:0,leftX:0,rightX:0}; + axes[profile.mode==='tank'?'leftY':profile.stick==='left'?'leftY':'rightY']=value; + return model.step({now, link:{state:link,at:now}, + axes:Object.fromEntries(Object.entries(axes).map(([key,v])=>[key,{value:v,at:now,sequence:now}])), + drives:{left:{healthy:true,stopped:value===0,at:now},right:{healthy:true,stopped:value===0,at:now}}, + requestCore:request?{owner:'autonomy',at:now,sequence:1}:undefined, + command:core?{token,at:now,expires:now+75,sequence:now,demand:{left:.4,right:.4}}:undefined, + }); + } + const add=(label:string,decision:Decision)=>rows.push({label,decision}); + for(let i=0;i<5;i++)step(); + const grant=step(0,'live',false,true);token=grant.token??''; + add('Mission Core получил явный допуск',grant); + add('Автономная команда движения',step(0,'live',true)); + add('Первое отклонение рычага',step(.8,'live',true)); + for(let i=0;i<20;i++)step(.8); + add('Тот же рычаг удерживается',step(.8)); + add('Остановка подтверждена, оба рычага отпущены',step()); + for(let i=0;i<3;i++)step(); + add('Непрерывная нейтраль подтверждена',step()); + add('Второе отклонение — ручное движение',step(.8)); + add('Запоздавшая команда прежней задачи',step(.8,'live',true)); + add('Радиосвязь потеряна',step(0,'lost')); + add('Связь вернулась с отклонённым рычагом',step(.8)); + return rows; +} diff --git a/apps/control-station/tools/rover-control-preview/tsconfig.json b/apps/control-station/tools/rover-control-preview/tsconfig.json new file mode 100644 index 0000000..f19d0c0 --- /dev/null +++ b/apps/control-station/tools/rover-control-preview/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends":"../../tsconfig.app.json", + "compilerOptions":{"incremental":false,"tsBuildInfoFile":null}, + "include":["*.tsx","*.ts","../../../../packages/rover-control/src/*.ts"] +} diff --git a/tools/rover-scene/export_rover.py b/tools/rover-scene/export_rover.py new file mode 100644 index 0000000..401ef69 --- /dev/null +++ b/tools/rover-scene/export_rover.py @@ -0,0 +1,49 @@ +"""Immutable-source evaluated geometry export, owner-selected complete v020.""" +import bpy,json,hashlib,struct +from pathlib import Path +from mathutils import Vector,Matrix +root=Path.cwd();source=Path(bpy.data.filepath) +donor=root.parent/'NODEDC_ENGINE_INFRA/nodedc-source/public/3dassetnode/model.glb' +with donor.open('rb') as f: + f.read(12);n,t=struct.unpack('