From 23bc253e30edde6c083ebd442d2dd24f7684e8e5 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Thu, 7 May 2026 10:36:07 -0400 Subject: [PATCH 01/25] Choreo Folder README.md done --- src/main/deploy/choreo/README.md | 17 +++++++++++++++++ src/main/deploy/choreo/images/choreoLogo.png | Bin 0 -> 14559 bytes src/main/deploy/choreo/images/choreoPath.png | Bin 0 -> 272852 bytes 3 files changed, 17 insertions(+) create mode 100644 src/main/deploy/choreo/README.md create mode 100644 src/main/deploy/choreo/images/choreoLogo.png create mode 100644 src/main/deploy/choreo/images/choreoPath.png diff --git a/src/main/deploy/choreo/README.md b/src/main/deploy/choreo/README.md new file mode 100644 index 00000000..190d3c2a --- /dev/null +++ b/src/main/deploy/choreo/README.md @@ -0,0 +1,17 @@ +## Autonomous Paths +This folder is where all the paths that we made for autonomous pathing exist. Such paths were made using **CHOREO** , a path making software. Choreo is how we make all of our autonomous paths, and this year, we opted to make seperate paths, and chain them together for our routines. +![Choreo Logo](images/choreoLogo.png) + +### Example of one of our autons +```java + addMultiAuton(kRightTrenchTwoCycleBumpReturn, + new AdaptableAutonInfo(AutonK.kRightOneBumpReturn, AutonK.kSOTMTimeout, true, 0), + new AdaptableAutonInfo(AutonK.kRightTwoBumpToTrench, AutonK.kSOTMTimeout, true, 0), + new AdaptableAutonInfo(AutonK.kRightTwoBumpReturn, AutonK.kSOTMTimeout, true, 0), + new AdaptableAutonInfo(AutonK.kRightTwoBumpToTrench, AutonK.kSOTMTimeout, true, 0), + new AdaptableAutonInfo(AutonK.kRightTwoGoOut, AutonK.kSOTMTimeout, true, 0)); +``` +Note that we chain 5 paths together to create an auton, in which we run the RIGHT_one_bumpReturn, the RIGHT_two_bumpToTrench, the RIGHT_two_bumpReturn, followed by the RIGHT_two_bumpToTrench, finishing with the RIGHT_two_goOut. All of these paths exist as traj's in this folder, and that is where the code pulls from; such paths are, once again, made in choreo. So in order to make a new auton, you must make a plethora of new paths, and make it so that they chain together using the logic seen above. + +### Example of one of our paths (LEFT_one_bumpReturn) +![Choreo Path](images/choreoPath.png) diff --git a/src/main/deploy/choreo/images/choreoLogo.png b/src/main/deploy/choreo/images/choreoLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..f88b90207097706f4d370575b12838356910aa20 GIT binary patch literal 14559 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 literal 0 HcmV?d00001 diff --git a/src/main/deploy/choreo/images/choreoPath.png b/src/main/deploy/choreo/images/choreoPath.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf5b828e65a6fd8d7a2841acebd1bcfb4d1b925 GIT binary patch literal 272852 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

~OTadT0WS638q>2HYeJ+-0s) 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 X1I`cAHHQB$$e25WLN={*&A~qiTP?} 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≧@X*yztmF^Zfk_s0 z0NQubjfKrENZq|^xxrum*yX2M=SAk*;-tidXOPkAm%)z#GX_%Z|7OOJc+_XH_(gfG zI9VXG0Y7cYpZ?S~R&L|tK(J@sTsoaYi?F{2!~YMbh5MMEuOkEBBd|D12i((iFzrk@9_Gi`o*DQOSI5-dqDaK?`)=2nKlw@iA$63R|s;Yw@6XrD& z`I!l+2zIRFue58ef2q5|nSwb8KaJA5C1cU)G++=6KjPV48WErQv%^OdFpF z00lW`JoU$=7SI{uA)C8nUclc9?1G{**~jO%b9#4IeI!W?$Uhm5T0jz1Z&${QT^%nX zW2p+C0di<ZTXH%f@Hc8Yb4gQO zB?`Z@Ib1tGOHr<^O=!R7R_=l*b18ek4KYH7$^rKRqji}nnXS=QAEV;SS`L89v};`R z0`=d1I>+bn@e2f8w_GHoz}vnoA&Gr2w044$_7F9Cgz$bt&s`(f64qyqQf^MC=BggO zN#yA-#~q?rd!cRQThSV&7eZWQUu#s|SY%iE`_p5Kg3SHxoIwx>O(H%JyYyZm08b?? z0*ILCAFHpn)8Y26PGhU{)L3_?2US%9FMVc>VO|<>D@9U>u5)u8q3veQJvq5A`o5y1 zrW)peyEDvOtCca$JIC+ao_Hx}Jy+lHrBDB>>AUBxLBsix%HARY9oT;i>nt#6^BZn{ zz4R&%5%2{Eg9+F2u;t4IQq{S8|MuTH&!mE)qW9z;t0JKKva*1~g@%>i9PZ!X2&&j` zNu530g(&9p&q4zVcR1iq$t@{{=$W75I_+y$VEf^n`l2_PSsop;UX2gbOZTtsMckOJ zj4~O1JloRpx$cL1J`6{{2wQ}&liYb3F?!*x)*906dF3j?dTqvN< zNqVe^6a=3+y^DG1If`c8=#SKJul6!}+2l{rU#-y$6Y6W;5T&Vc6oif3KSgQ29JyaY zX{~${PtR<~{o&e^Wsxf+FBcR$0gW(iZH!llz$|NaV*tCU--`6?r_&MXRcELeIVZ>d zdJxXi>^`P|Q<1c&J%P0t=>HO%sHVpPQcJy;_ZqP&3iQBJWCoj^c$*@e8S}1GnkNZ` zR!7G(=VwbqyJpB(fCU;>c1ZZN7y|Sgy*Te(QewO@F2jX@CEQ81IY~ULu05Y*}wDzdaO`B zw&6~pX9fLeZT(tZjXpmww6+cl5@Ru1ON{-^yDCoMB?#)-RV&q2+WJ#rjxIw|M4O$x zcO?0%ZlzJDyhD6Kl!$-%{m9%GK&WMCfKe^f&FKN3CS6xdCVTb9Tcmuo2k{Ajc1z}F zh)i}khAQIMpP8vs0n0~DZjNUCAuZ_Pc3&)5@+B?>R2b^~;ECIU#S`Z~hI>~KGUR~} zI6kM~N5`|3S;pIwG|O~w`&D$)`j85HS{ZY9@o_9;d2o@zD38|jYGQ7vW8KAbrglu& z4@FD*-e4e=UA|v!s*8wy+WY=sNSn!k%x?W4_W|6SM`M%1E1b@5^-dqk9R=vJtc>03 z5qsnD8`HYNRA14k0@~(DQf&_bAH6iI4RXH}Lz9~nb$q|aaT0!R04RnB)*NgvQ3QDZ zx>7iJo}|^DNverQ;gO zW5a?Pl|6Sn#Sw_<@;2d%Q8;c2taYhe?|C!eP)&RB+lF+%M}CX`!gB%bcM*iaV zN{L0|{6sGk$+CE)Zwma7bo?C4j3k+-^q}#g2E4CzYKLcVY=25TOOhxnBQMW&dU{Gt zOZ%J*Z%>fg5ED}&At}jQZ1oIXmj$7gkQ4xTFtJ$_UeR_5N!oDL>?LNaGKUC0es~Y>j9rm zw$UjjM%z3Ughs;JE6dpWVA-qwu$+!YvyU#K73`l7ctC$lqsw4}x)UbqvkSReKenf0 z=n{MbCBH4`7wya5z-#Tux3Sg#d;kjFY-YOkGuYlF@^BA;35=Wx1qBK_No&^i)ixt63X`TBY2$sfW?#%Mx(7ugd-lptg4I6HkC8IR z{LpsYegEb)tXaJmgNwy$^!XpGU6gkc3{@E{KQev0tnzbImY|};c2m%EJzE_j{{j-_ zqz!a9hv*H@13d7uJNCBjk*yt1K-_H*pTne`TEdP}t0-(-V<7PYiugE4^fw;bKa+U-9iBwU@zw}av1 z9!XJ)R>r;HKge{DxBneargj2W&ZNff$}-691qH>gmmUDFI+7v?ZtxtJ$p71eHxWw| z7?gL-gW*fhI2Y7CQ4@E#K+WjOYM(O+JDX^moL#FuJLb;JYAIH^SoF7lCc|J&tAKer z2MljsOlB;x?w2p|Qnl{w@Jcn*O&Z|BWl$AOO~NYn31CiqOD2qw+9)U|ZSN&)~OM&!R}EIthOtj0t~~w1E2;Onq-5yLPB6;`k+} zg)Cu%?i8NORbjvZ)jFWa#WU!s6n4v*Y z`}LTP_vfi`Euy8oYL2@S7>MIoBZ(XN*rlYHjv^qg4_8TuFL1N*C)26OiVk?k``~7d zOeT_aTW@bB3D^w*5rI*^CAd(D{NsED)!(LfAB1j2VmpD*h2&poy!^44M;=Y_-PGAm zxi9ZT1Oc~lEs&1_j_<-4fgq}HV%Zngj>vD(7ZT$^4<9|DxU+FRe^Trc5`*dj5)=#P7^3+623z=2{rlnHb+qv&v>S6E7jkgt$^_qv4HWkp~;^1z` z$jZN^#SqdwI8xTb)}#$Uc>6p#d&KD00zcR3z1yspD8U4wkly~)+FX!eHrs~-@T=pf zCs%ZMy8JZhwRu047^fR(RP0aND;fE(skNIBuC^_LwH?t0H=Dz~YXXe7Xc|)Tt$3gB z(qnvDe#DH!epZ<*gLgq9Z z+ENV`GY%IZg}@WS-&b?Y-k<9?XN(n+w$hq#%uQDJe_1w!!DEJv*-r71sbrJ(E~WEt z{xW#inCy1=Ymj?ZU)p*;RKz~QPSfcf(jR&LNuuwspT`d?{C!b^16lg?*(F2HIb0m| z7M}@mUR%tMJa;2Y?Ls`5@tM$Fh|*Mwa&y;_iu!$J)~P=oYbl*;w8EGRaV{z;xgwh{ ze3cO$OEuZ#^`Itn{Zn|+5zb*gn)*)G2}gM^Nc0KzbmbH1XTT{8JPgs$fQ&F06BV=N zPoNvDcDUVJVEyR^6jWl2RdAjg?!1MUGh~bRqh`;h)kq5~)*J1eJA&yB-YXD$IK+SF z7=XrcJyrlgnOJ0r#xLgE#lCLI6YlBplc6^0c2x#`5mov#{hNK5q|@zC013j%?)H)Q z4cVVxJX!Ou$e-(6dC>0L3=8qt$zarudcc0kEuci-bQldJ)pLB|)rc{F_=>t>^tfj3 z6CfV_mf;(CNy1ZQyhU7Z2mfl)uQ)zFj<_vf3JM8f_ghT1J9xZul2L|2`jYEp75~-i zT8kgVp(oz}uTN;d^9_*H@Hn{LF*WlssMJCQ8()PAf9mHlMr}yW?YqET?Ye{y_dgqu zLQ%;)uCboGy)Y1O@B9y`{3OPn=8qi!K*$0y_BtMFLM3O(x7JV;Yb-Qo&&27JYB_uo z+=EsV&$~RJ86V5EM8CI?hl)?whVOO)4cCB9lOqqH8cq0Ge=Fia`f#x+4Tul4vb~gF zASt?T0#zw8Hl0q`*uK}Km3b57Y*Z5-*``~W>1Y^4;KX;ssi{hn z^~{DZbZ3y)1B&ur-W&1%@-}ptZA92&F8a^Pd|!5mj1eu#A6*xvuh!O$BrV+NcyK55 zj#1h;MI?z0jeiModEnyajrXtrtk=?Ryj23bv0U=HI=WBI$vM|7{}L4S$f+rvQe3%l zJv?R6bEkaK{a9}#jW^`tf{QEli-O}~R(NXr>?(9L#TZN+gydS^6KVB)&A>4~sJZVa zjM9(MHy=kZ?$K<76R~hizC$xw^?VqY7Q5vwNEI_ikxocp39!E8L)3??k=w6awCWP8 z_rG)myGHSZGoiFs)DEt19=OrymP=bCjZ(-F~k!PD87}G6EFYCKi#K z&m07Q*61z&tarW*Y-SKfU9i79}03QqmlfCz<~Ff=hEsx1bht*_%#NAaT^E7 z^T=#8r+^KmwpQR*EaZJWW##zz)^}rPua4l!N3-XnB1W&YkI05~HkH%wN07-yU4mzs zKS-LH1JRAyMI-W8VLy=;fsK6&7PqpRg$a0#cI8N)o9OUxz&bA<(h0n9`%;^AP4lnQ zx*hIA59}u;8*LopP~_WXAX5A9v(Kjc+bR9;J}pT&ZFF>qm#OJ!i1gu9mS`*gp>SN- zdma}(hBS9$4?*7dMMcH)N~t(9ix7~>e`QNspcgS1=?s`FgL@#^EF->=3A#I;sgf%& zGXv*-p)iZY!pM0(F+;p!-T{>M-&{v(+?>=h@LH%UQ@8H2mVkvh|6LtTYxChT;^3em21_@moI#ly-C(Ub znB=2!d~OwxU+ek9bSXt4WD-%kRu{$dH-L?3V^(|l{Yn(GIvcWAx75F2!>2Wl5SVO- zJDh7=KGygIn%A7ua%}z|3R zs{_ggb&emp;{G`Tu*aPS2;HujBfef}MD*#YH$>kTO@QwBslH>EOfH4$z@Qn`yvwEwylM65XJiS{k zgO`ssBeP*YZhlVSJIN}4uxb`_OQcOmk9&zl3Kjk->ee@4ppmG&GL0uCm|^!&-&_kG zS^_9PeSNJ(6Xq3O5yqVeq)fx5tf?LO*4FC4zp_4f_7*07dMIh1ovkh!88J{#xIj&u z1>7LrW7==pe%|(1{M_K2F|IV2&=JlrcakgE zkgIC>_*WZ(RthT?2R{eMtaPmJsmaLAvR<90W+t+FClSO@`Ahd@VluzJVczKEd6R1r z4#?=@~!zHvy**V=O6+ z|D$0}lkfg+hd$BBp-28~{cwHlctHY(xd&SqJLBS8tNft&r&9Z=hj0EdcDRFV|M7Kn zd?{jkY7&eRPYRO4z2m`=dH(3QOfRr^-kYAZM1#$(F*-X;+f=!zq~>=>yHi%>tVX5c zSn`^{WSq+Y9^ml!l+I|GpI3bxfx>0Y0JPhPPtB1mPf(sUr2|#g%^J1`=&~4f4H!%E4$|snX(RA@e#rghYi47zpObT>eSFgYno1PkLiXQ4IBO39ur#nxI#~&!&#{4c($1gQH57Wu zFbuz?#f0s5=<;Vs$=lMQp{4Etm{#@gtW$qjhs#t5^DBug)VG~C&{{$!pOCv%nS3YO zs5We%kdhe-OKcZsp|6=6Tzi9bFgxQ^F)sGuTf!#gGNjTjB;wl#u)t3sxG0uLz^;`h z)vTfaIq~bl_GLvAal{m|Kak-|l3SIyzfX*g9zGzLp#)giqyk=0<9rPerZj%VMU2VL zK9N^g$nJS2ffI*=ijEGz;>H{)jpkB(vm>FVKzUmg%NVyYokzPjez2LKUU!*1=DGgM zJ9bbAZgF>#5h*wOoTT_QyZY#fDf6u%e`1|FFuMnZ058$gn%4uQc!zT=QVO@vnX@}( zft#JYaHkQgNJi52b?${cQ8^upvZPpKc&vB;DyI7QxUqOH2A%7#47^Rn>A0VpxWsyTk{G^AG{q?4~n4AHZ%OGoi1=5&_sd zXG6JZE%>0V9u5HA>9+YmALwv?m9Eejm&;=-3Lb+|0vfgpWnaBID!9f~QPLOE_ z9OLO8$zsCH92#1&u+i=Py4Qk~QoUVsA4k&2-zlf3HonIL+$ZZfs*SoIJ|6gcofkRK z07By4);*(VGAFIU#WKV=#zJ@}?LcmYSiqL9C|!7Ip)?!UM9tE;1g1R;<5I@yR6tQFfq)mQ^FbeimvX=)%%?;791B|&qITck^YHXoZesk!z|7q0tJO9KT z2u1+qQw*so99?wM zc?J_9>@Q;ypRmqPNSd?c^oP^glVPDSGc&8Isyc0tXq|!aKd9)06hi{#X~j!1Tlbmx z=>O0tLkVbk)V(<~MX@p&1Js$g5XSt3>@Z^57_98Fc`*|?jR9e+Mc;wE%%7ArZi31( zvk7g04<^9_bE+={*wN%yR52AL$QS`A+DI1pyZ7Hy@#Qt1lTFm=1ynIz zWbm4a5+??5?zA!K>?mODbmpV=NS1tiPp$6@SVuUd_a+WD(-|)^?L5uR>0=hZONd3H zr%Zddu(f@4r1~0$uHrOe5q=<(dwF*^W*WBv8Q5Ab^$mJ52=v4b?IFbL_bF+UE_`}= z-V!GxH6Ldf4m z@h3Vn$J1Mnvh27soUJ`GB0Y~AH)*@bT3~9l}1Ib40WsUA75ROIO%%6Sk2N(si7Qct8FTvnK zKt$(OgNrQzZuaJy4lWu5Mz)*RQ>x|XETo)uqW!0C_;LUT{Rg@Ogqq~ee2aH$gRhU; zwy1eWhPR^pRX^xKi&ceVZKy>iIHBdNLGoQppaSP+LuAC=?0bCHFbVRvY+jU*S>A6+ z?!iE9xZVa6HN6j3`0XkVlH94(zE?Uvh*`?d>%|w(w|erFd5YgYIdCGePXR-UDp9rx zSf7ywH|^4Q&=@Stg0D!f-Q1^ylu%RoIg*P_;Mj(bNsoXMUUyzC=ukahIl{Q zi%2|2H95f-C4nsZ2u@J&ol%}yjdTgd3RDkzf=pjGHMzCxBJ&?VX(s~&nth?!nQ!k3 zZuB}G{R^ToDV_5y5blQyZ-JX=4p1E~-poj`-^&2gQRW=mcU`<7DZF0{g*}>e8n0OX zz`v_ct_T2M*q?2j4;U|<{e057cO)fw6zuO3%u|B^Dx-tLNQz@EY<1fBN)H+8-xo+R zE2DEByWgQZ2lZw6 zh2--;So^A=I<}}y0tpg=1PCr6SO^x}AwYl-+}+*X-92~+9^BnMxV!7Y-QjSs1JmT* z`&Z4>Jj}y9Ky`I>pWb`->a~}A>sxpG`4+1!NkD0vtcps=(2V<9sU4S?$w}i63*iC? z{uq>+3(P5;&j+zQnY%*DlprCQ)Nk5+9R7vgd1Xr)a3=w@j(aX@!6TONsCf#te(##HMKwX8gGxS^N^P zF;p$zO|yA@kvrOK(@ZQ?TI%CS4Qw01S;@L(08;cQKD~MNDi-OinSu0x+0d|e4#SbE z^w`>azk4Ewf$?cKh#I^dY#ps;#TP{djdE@rwWD(jt|b!RQtpfsG_!o?nzGEk|4K$5id z-wzt}6v&}lfJ@o|(ngusY47o6O#=;h7%;&3qY0B*`amOmMpQBey-QmW(ylR&{>GD8tYIcZWi6O* zA_DxU1ivh7%#Sqqfv-|a^=H6OsrSukqc#7s-4E%yv}XzTqrq@p`{_WAfpku1`Nr}r zpd1E1(hr+^TI8U@1VKqG6X`-iba_^7cj=X9KpRjiz6ei%#0Xd1^6De4C4tR0-Je1~ zC6W7ud?=^eVt;@@OOU7(-Y=G>-7x|0RaI@V6I0umi1#;S|MkIXKz~jtHEIZfd`GAN zlEVL#1;5O0_~Om|kRh#_O}sENKsW~)r2cfk^)`FzhQHg2nX}PN3zy+x>@%t-Z>aR_ zkM#*rj0UfcKgA3PLnmkHMQRkxm{}#1O3Hs^pt^EbeIc`{Lgghw^e7MAHC@KMYxZ=Q zg_ZcqYT{M${?ipAE9{KAor%W@*{YmaR^R?ObjLCLxH?kS!CMEg@4o5by%{`LqGgC&ERMDlyz>ZPtR23fE*NDwYF$}N@(k7Qo#!IRo;Zk zG_H(KGo8I26(7hMaI;dy?pdZ06cgK5tyNGqB>T|P`n@h~31yUx%J3cA^^v(AWEK1R z$hu?w%su2WQaIq#Ep#mCmhFDqVW^VBuad)`Q$3TM=L3A=zE*oe8+78+o4qYblWj?p zEfLTOG2eph5Rg(12>w{Hn92i*(ip|AcevK(+{779zC-+O2zqCYfZ9GVTK>8nVq+h3}I`QAkqt zkBY6{%?BvSJ1lHBDWC__>LS_Mu}R_p`5Q4M+b7M+mdi!NiztcpO+`E&7h@JvA%>$s zIDohLwK%LggCeEvZ+VtXhxAQoZH8{)l7B#lVp8(4+rgsTM@Fgl?XK9{rFbl=o0W|Z z!8m=F>I1x+NgI%i;!7?#5WtX!wt7pdJo4z+?#u8eVqnO1>v5#J$A$hgoq6SX%1953 zInNYFbBql=Q;7bp$d4TeiOBu;@6E9}H8h?n_l_)}{z?I!)ygUVC3;AN@8<35yHVeM zUQ3|%?`^)l4h|{dkTu0_&D(71if~vite?Mx*8n!%zuyli`mbjB*Zf4>V9Oe~L2ox05$ngU`u>}%kMCd|#ZFwLW)DJtmNC5}#@O^OL z9$$C>_hAV-#cS91=EP{~Y$v_T?lv34$SV@0V0T=~vbF-C9PTe!cN9izC9*0E@fGtN z-qe|-(!T=W43qPE`n$fAY@Vuv9s0E}dk!%GtE(nJ&wArEz$Yw{V|!Xk6B0`N>d*Qo z=BGtf5y^^V`xpTa&$7seOUmnA7I`Jh^OY9miO?i+lnkNCGtRG6MgZeZQORQGW2zCr zv%}#;c?MGt@UD;gqX4L=elF}AT8lh3DsD8E3E#dlBx6DKq8D4)NW*EFqNVsZ>ks?(lx!y#FAJdy8B@amwI)ow zf?9=h@r}aZBBJ69dN!Sbi47`shiK>oR;KdIx)Z#TWJCtoM=?g3$$j=sCz;-@iMaBs zo7h+0A3o_d%+>i$nshdtHR0Z=fXR)0@Do^LG7arH!>1aIDzBm8*E8u|u|Ueh6JfjAi;jsII&$Lb zwb{2U2yj<`GT|_An1g7Hc@|xt#+r*=9UbxV>dMRxFYuAQ0pO_?$@=C5vj`9X9pBgI zUUIlo0S-A9MYm5DFqoe&aIKG?o8H-)nK3lT*yEL<`^b7Zr45V-IT7wbIsfB%L+AL*a3gBv=27NOK3p*jlz+)fwj+F5Xv!3%| z!3$exxioHAX(c2A__RyENl56794G?Gdzae8L}eQYFGNmDYkkIxbgSf|qO2(uSo|2sO@ z6|7zMf91PTMW=V2qTEli7e-{JM2O>5+nrI?Fe9bNGgh?G%?#BQGb^v#%$L|oXR^Jj zNy^S2iB|%*i4=W>jf++L9tH#RbS3sxKtc3SOmY$^CCwhW3xkyBt3XPRu&ppb-$f+LJyNVA7u^Ko1c2I%<6`*{9sP04AF{sU49ab2 z!1W;k)mPeUu3C3mSZzH$G9tkwNr-$kP_{Xwp%d%s*A?g%Ew*V~QJ7X4pfStMne3d* zWk0_&_lDp<^-Gf&Lhn!j?{DB6$_G-BK*4)#^p}6Q&2e-q-9EKI#Uf;xx#&W=Ro*cb z1{}Z>-0n(lWt{T*z=wp&Fw$#^%B8{wY6t?f(tdiF`C)gf^gx%3o3|b0eZ{ggIn%5Q z)E`;xTQXSvh5WZ8NZt-dzeK^WjR*F`KnlyP)DzwY_;FJM1EW1WpaVs5%%q-;jDpb) zK+|T`Zkz*9c_b9YRfyX61}8xhtEf;W$P4bntd!Ss<%I=3C%WMWB$buIW$G;2NMOQul(=HKCQ>V8r1=P}>4R9h0#guKtui&;{Zj4`VlHCDGlQ z^r_{^o`7-v`;HWf@p8y}KgaZ4edmS&8Vf^fDQ4qvC_;m-+pEjGKxQw4&zT+R$AnV- z{GdUA>x@{jjmQBX@6tGc*;e86I%#@1(y~=Q1Doyp`lMy8Pd^i0Q!7iO8E=^IPONL< zkYfEz=Cmnk91GhvHx~Kq>VQ}&tGNX59L6}d1eV*wanF$>@;m$&^9k@Z1E}tf?ZN7? zN?l{Y6wa$-%ym^&Tb(#MNWOU7PP72^8`$i=Be>1IIwQZ-QfKPQgk@cWaoZI4>)!oC zorje;*wjId5zF{|J;Q7GQv2>oQz5GIQ9uOKDon zP?etWFWh?cBplM(H+vi%xhE>l_?COtbMJc1y^_I?W$$uVO4VrT_Q@?Fzegs$C)m5? z$*(O#c<+AX-)<)Y|AZr7gG9r3g5Q!98{Bp45QZS+~rZ1W|y^O#lDf+v#zz0!{hdISt>vu3gx54l&Nk}_1283y_)wYZKOZn zTaHyUcL8OsC94YCGe3Xktfy0)2LxZWIh|KBi>yLxI?X@rb_1L;bUz@k-94Z2RV&go zjQ54~I4?7w+qzl;ZCzjL{>DEjw!JIyjmeog3$GzfgGl2=lzgOz;FD?aZ1zR9(+YuB zqtNMn?s})Dr%6c0OWV$CP80EPh3KvM&R@2hwL=XxiD$bxX}ukJrM;{1^4l{Vu@qiZ z;w>+gvhD{d#QGFmnI}z@LRZ&AzYrN7HnVEI&NE;aMf9ArCljZ6{cl+G*S#}>X zo~I(cGf!+(hmJ8wmrO}rS<%u_a&GYtkr{zoT~81VUiwF4mzDsrLJTA0Z@;)P^4 zo<|d+PKkVUJu3U!xk&Tv1(xT_2%=6QtcROA@{OiI4+vf8DeKuQnIZL1q%tEZ5zYl| z@6jJ=&0P0_LKv;RUh*sLt9uG`aU#x&c-qWlEdZ47JzZW<7N#bS1y0)hG}#&0cp@PN z=s5)>mvbiW0BD~N5Y4Y}u~OKDP8168*vNZi0A@YVFslLF^lS9K3xgFvVs-NY*FA&V zti?Qbp$#7?_}l0T0nDGyL%+^X&}6N5m*4h-i>lu9t^K-O#9BM8HPQdMy#7-V*-pcO z{}03$Cq09m_1lbPDFuCQ4k*Z8*RPA&Ec9kbPIr~He+ns3il1B(J&j4|R`-52c&yFdhCY<5H>?7d< zr3c$R*Nng(4PdZg8f?8-GVj{pc2I%P9Jbbk_u79!NC_%u6OV!|yibS!~b$IR6~+VGw`oGG5iQT`== zD_1;uqrXdG8b{&EC4&=21v3@_wG>SkA-2fU60Rkyt$aY-F1CmRpHxb2?x6sK51aU0 zl_Gv^s!m#J5~g5M0%ye~CVjrYeSV$I$uzcx)amb5{%WSKU!0^h_R!TYBgyE4Ta2rR z`|!_=?0}?5%d8b@H^6rV*lKtQ2fV_j-bRwI$v$ZebVvN@^75gfbY)=tN6ycL7#V2; z0xKtmBhDUESVVI5bS+qr_2JrGq|xck_KKAP8LfA`?GLli8XPAOhEr}qTI=O%?|LIb zHO<3De%;&zA_bFxB{G*M<98d|k5KsTy7Xd88cXgrF&_?kB&QGZs~@CkR94^m@!fp@ zO20!7j_}hxH;^8y$`B`UF0VitE58aqdGVb2d#kA{j!(zVim6s9=U{@etuKqI($`6FZ0HtA zvbN*oWjTj9!9T2{{?l6g4uKTdvzC@y{Mz)!>(a=-Y|M!ve4gp-v#da>cH*EJt6ZA3 zV&TA(Z1HE7#v%oE8uNFo=TBKyYQwSDOJm*;_$A`OuZDCRe->%q#~7bx2*q)$%?8ZJG> z8G37U^`exYR|n6=>_!-DNVb-0rU<(d?Zq*P!5T@3eYK|lu$v}H_>t+iblNC}4|V8Q zS!q|L`wuJZxmjg2dP`xsJkUGJG5ywLVWLojWtfT|($dpHdhn3ZYm2I_#fh)7QYU?? zX;D_Zl|k;_SHMlOK@MubJ;x%_!t(X2U2iXz4u(7cwhN(@=WP1}4 z4t1q>BDS~PD@mC$qz8KWNm5qS;*_X)zE)ncuqrQ1dw~^OhCY9CpalD-;jB%%zGZ&p z_(#rC#R+RUS(ZZgOZV9*UhO+pD{p%Rw8_TFa_%R7+8+5(eB!J<=TdV-p9EM zFzb9D^6Wq;ZnqN?Uia4mj}$lzOvZ|$4b%z$WqJ+%v(x_u7(kChccoa|+4QO{_Bw^? z=J?l1qx<{i%xr$0JBgp|Hy;@4L0+B^hb$D;mfPtlS2vPBwK)_p6patL?l%oqnE40q zKlLyRU!Tz$>@ETffk`sP=n!B2U0gu8;L zvDLI-QN_Ga(9`VCB8vo1Z6ERgPqFv3VSWYgKh*#o$Cr*d>K2CzHY1K*poC4kX{PQ$ zrfEv*eM;^MIhr!2vRGJ%uo#Hs=nKo<`m*D-uD-FJi%BHz_cEAzti%(!Ht$g{vbf`el*mX2W0R#=^08 z)F$5wUzU^;e`jfJz1@F~rkE|dGu+s!Ft-Kw+1w6sb%nKryE%SDUM&r0@ofnw&m{DP zgNu?_ld}76KkP0q4y;R&eE`5ZE*gjq42PcT$BAV2{*v2b;8}OUD^Jl#3V#rV)=u=C|C;QjVJhvYRoVH?Gk84-K zmLw4%q;; zil4h530H4Ur*mf>I9BCv&_R)6oM}Cc)tuJcaGg-g>lGBZrh)pk_;Xly_`Qu|IBBO( zk7hMyNVP?)SZ~c&*^}=_kX|{xT790*^XjfE%)XJUSM8Za5_ZJ@PQbdQY|de)HN@Yp z+B2%h3*EFePIknqHz}+!+piF^PlQkLjHcE8?cF^;QLgRDlqHm%shFMpd!oa!y31_| zLUf~@Z%bASSu1XR@&>oGk(}%nTcnoD&9x9~ed&J*D@a+8671MEICxvM9u=tOS{r#e zlg>Zdnsh!>=7oapZttK?X%{`$Mxw4{~oF7GE`jELt#af+aRc}^SyL4f! z09hHs35?gFv`~B<+9day{}4&UI!0 z{QNEuNqH0$=SCZ=*c>mT7fYO~q*!dTDtf3#e&;BP3d8}OsN7KJ@cJ#THkRPYnh#t0 zEg37yd%8;*l+F^fiME{a2M*3Co4dZ+Ci>EPa73VS;#;G--CmX^^1HVT6JA{JRz#7s z-_-o*V#BmI{^$!@ysP%8+|<5z;)8W(e(YYz@`NGpzO~;@ZToF4<%KDFnS2-NByF*g z&`qgTXhl3b(L!l=!3zS#$V&nl1toU0!*5!ceIixuagu~29gk92bmI%IyEm>$91FwvRR1QwMd0x{_`5^HE`Y)}6`eao9n&6cne|>Gj~BUoAdVyR3}<8O@v$-{zAzLc97hW703O@FXSPAt(#jArc0pGO@A+C57(08(z+^wlvCB-G#!2 z)@-@hAt_An^c_jHuYYQft_?llM-0e^b_tqFfz(=-OKJ>TA><6F!-|VPlWnzT39j3t z1zuFrYR4enF0^E-anEu7CK$q`4*kMP!EJl*_%@sFCjxuP?vJko_u35^q97v5XokP< zC;*+_BTcs03V%}h-h8i$dw#v61vNOR6f`RlVK z)PUM#{Z3j*0xQfmk=VQmP3^oHm}B<300(@Pl67uYP+b~58FrJxVzI9phgztJ+vDsb z0QG{4B1&b#E5~x8`Tl7L2hUHlR`PFh|Hh3rJ+ZtvpnSi=e+|RUWVT<$ePiYw*<}7L zy^Z~^atlC*i!>XbGb=XUt$&I_<8Kdt{!0Tr^5@fP6_=(%Ri9Se-MweMwby91edK0a zYg5cJu!sQhseI&})YcHe)tTs{F4WQFHQSd zzUoqvuRv;Fo&WAd@4

59 zVzH1*OQ4`;x(#KZnHGX~Y-&hAtEbjKB;dZ;sR#@{IY2JRJ9ST|Tk?{0Z@#ApcJ{c; z@ThAo$Cx8xr~GVh_ZemeOe7U`+*Fp(RTiVYa>a=_>8%j^6D)K9k1} z>y=asXkXx^`7hp-8>=9;U%#C#)gHq%$1ojsiF0gUK55}kxU$dniR=`z=5l_ZNa{^L2u8DK?TYBedgPvo2Jw!+cUh9Jw@n0jpEKl~` zl@hJA^42Q-&)W>TS~xM|dn~f+huNLGKXkdb-c4IvM!NH|FG!!`b9kg{Vkq`~TJlH_ z%9F~!eq{Q=c9!4%^Gf?Mv5Sg!3umd(P!-hFsLFYI**Aw%efpMp6|VfMuDr#Gu%2G|z3zULA9gYQ)Q4nSRf0x!Bm>YLkEEGp1?WPzk z$Ox}rSIe)XToP$I8Z@+P)2i1U2PeQo`KoIZWfdXBwlPlyCwHr6R!2x>2~gosl6%YSEgxu(sBR15^X zq|&*%$j{DBXbQ4VE$7%_f!FDYwH?kN5*DtF)Xz`hTuF;O7>qFR&Lh9UAh!7PLSeQh zPJLd&N*B3U8Pr)^-m=B=xGw6|L6u=)WR}Gsj|mMy1b3D_F|Q%~(OzvwDlVNcmGZ{y zyZx-6MG~Vc;-FSgc)B&cfL$fsgn9%^-tERrwh*;vg9+anv={_1*VRV5dhEHL$E*$~ zABj3Nyvgfp#gmS$Hr##QH98W2~Hn;pxc;RwF@Fi~La`1=CyLFi0R_pQ0!hY+TuX-$xL zV4W17smx$u5V> zf42A-xq!OKo)|LG)Wvk~^(Y?5vNsn%4&O^YrL196Nvm;=c=&h&4_mD?NDQ+jK|f<}&XYD?KaOXW^IImHsUMm=MtomBp}RLQ1TxpuL+AulD`m2^ zy_lc8(3`M6U!B3@@xkI$06(c4riMrJ<&`H+AA}M5~c(Mb*cGz zpyX;OvuB*Xm#-AFjpjntcY;$drPXF)i>Rfa!az_vT3+3I#_`zVp~SCKZuiY{f_P`c zV;FjV-9?eoHQ*;+8PEmxU>`Z{%HzAW`idd>mT0p21~hbJ=%>t#x@#-E>Ce%O#qKVh z6XmD{&&|BbFV4D|1GR2WKc?iL>g&01a%pO-Ki)oN4wb!>jmsw4wB~*-Hs(MdlgiXH ztV#3XoUO5q_^Enj25we2J$Y|vgzvGrbt zm&DSU4-Jz*SMPX4w)-n?+T#6Czp2iq@hC;TkpG+IW~Sr-6>C7rS4p#;UxNl^D4GyS z709Om{7_PxAfmKb-f6BhlV@{n&FuK1QnuB#ioN+ex>69-iyrON&U%p?gKETJz{iK- zPs(x%ml$lD-naJKF$R^c)6)f-BT5%R&g5*2JA}LBXeCj;}wJsd0T?Lq>mh(v?g=d1f6o zX1IAN?t06v9LuFoNSYZFAlhv+S40f}6UQ9z&R(6dncnUnnB++-3^KrJ@EY&=Fs!_| z9FW~kb0Pu zf$-Q308WHE8bOs>mJ*1QL_iEMA|j?WWIz(ofngrC5)rQTjGfrkqLx z5MYDjY-@ZYGn8$FzrUwuX4iH8h^Jb{(i`G6(`$*g0LtLhuNVAR*5YUUdvaQ6iblH zr{*Xp^=h|%9|`R=CA`ZN{&#Xq;~h;IRH&--YRB!XJfdQO3Eq#*=iMR=8F-M%UJO7% zpAFTXOAzM0vG?}AZHPFMmwZ^UtQ=0B#vvqm{u<6Ko!}tLSOb2lqq_+~7yF}TZKF1$ zCfFNx?7|2lk#e6_wTmgfuwMy(08!N{(Rjhb^{$uwx+SJQ-h25+xmZ^>`f@kV;O;f) z^s~fLCAC2voa$s%<1W+KkZxZ8_3y=fyVg;DxY%@0E#yKwES!SbxioKlu1_zG>JhGq zX-K=jDCeW3*%sI7D7_aj@|O=P;x>}JAY<$7&eCP_n4$b}sk^~JQg&hEUHO($j`H4q zh7jT(;ZWY?4y<;#9B;~;m3iLT{4N&py~nuEsSI{5yq)m(-=csl74N)^Wm*7b10RQ? zy`P)5vNUZn!B>lb0pQFE-W2dO;3|=`RjduWxB0SlUxJAwjd#(EkY^4VISJtw+sD+$ zDw#OpN6Ik(168C)7zvo@zb@FurGI|M({{k4QM&jOThHFk5CZU~cjvv%pFu zuMJIq-K|AmVKfDH3``zm8CZ$*wW0X8!^Iy5bFx0luke&mBY6SmZZA_(0p0v|I3;NM5yAkgq4o1#f?ZxZA_`l?#{wdJllLtBmveawa#Gc~8o*=6-@|44rk^s5YfGC{ z>|VZcLy^rS!iSH&{k*m_f$4vCGj(k=|-=a4B*}{O%v?{v!)g=zzah zF(%ABAFGUjtlz8oI5ikgW@70k)lzGYYj^ry$HIkor1-egE|EG;XLoNJb^f zA0$;=0(Gyei7dKv+8INX8NpREwB&AlHKM`Hud>3Lvok9-i8gD08T;9BF8T-+fyAv}uppiCDE4u5^3wSL>;?R`v}z{&<#lZmC&T;C?$uJO1qv@$bYgufP~c)I{ywe1-m zox7CYQ_O=3EtTUEx=N3wECoY`)GZ}nX%^9-ZF;oa8cyDY1+wm6nen?~UBRvV3M z^v94%S&p@ziKe@s@TUPhMHpA_1S2^A3UY%&gyQ(zka$-sBuI}5dWL+S%5AKFI4gT& zNh+XC3V*YU(2Gg1|JYQRritnW4W8n0aHmD>N+Evbnc-O(tzM31d~L9c3F^9q9vU~A zNuev}aAd^cj)tFNGjn!C9pboQV|i|FGIheXs?<(b|2A2ym4=CK+(bK|$NjK(59pdA zg2mfrb$tcqpzE1OE~zxroka7^8BL)(wq{@6;6v8~fic)eQ7P8*b7jSc>%`E5GSck~ z^X*zRao3a3&^^Z!gyQcsB@eqPyVE0r8nYradjw4uc&q2>o^>813#CefAa4@)zMj?_ z89!hvb^bOEnS^+v+%@qO8&tAzSpFlIA>m|nBjxOCOS48#9ab=#eFS3P?fCb|0~&0c zhzkHGV*k+E-1M}f`tVCB5UglsJ~TBs(3uk$yLLWPAv08Db0R8Hc1GC;oGdAv+A8j9 z>|&t3Z+{>~DIxF7S2rUf6l{n;%nMS?Z@-+#0GfXC_uWt(Gyh!wt6{Dm>1J6KIf^UC zu`{zhC&OyM57<4_VDLE2mHC6?6&E;MN@M{E(nL5k0iNlcXWXYZ_hOs(5?r6&YRm&* zLP_c0WCR4P`dBNgFQGZXjVDX|+8vf{g$1V8jTSy60hqG5bg}z10q7fYjMzzSnVD`U z4P-BUvPIPw>dVD=w-7dG)%`;wSY)(81KLjqs$c%-1DALIwrb_`qc9aX*m?0I!)m7% z?VbtQ%js0vr1m?~Rwk1ta+vz6hBXZ8TE67BJ01LttkfX_ILd z7_&j?vl)b_>=_OQ5gwJi)8f;FX!pEVKK-8=yj-Pu9^1MSD<6k;E)90oU=Xj^&xYJL zk&u}Rf>F*^ao%~PlxlQaGacM)x7v0Kzww?J*-4u@8s58)c${fHC+|Ufa<*Ao#pip< z%Y$4C)%P)ZW@SwM>c}yky#yT510zy;_w1N`f<_~hpg&(0X=nOPB7!_DMmJm!W{aoVuN7*j)=H7c<}y0%Po zI;VZSP6)c5gXe=Hh5G%g9r$X7;tp!SzAD^Z2YjBh3vKb#MJJ=_i$|PyHO?Fe!uQ;6 zMWKo8*RMSG>BS$e^LI_H9Mc-zt11Hjo_tYdPxs&|j|UbqIKlo|b0OF6!|>%K30rGb zMNqi(kkv4pro;fJ2LkW$LNGfoMiWcgwr5Si#s|<(TC4LoXRithtafuFdxVT0X4ScF zF7v8Zfgcc(M*!UV9RppEXCZ67(>d^1cfd|GMs9DZ49L#c&{mmV)!l_Md`g9FgJF28 z4H(wI1PH3A$AuikL>*|Zev#M6c5*BtPuHC(65qxui4w1d3(FeAB+Dbwm8n? zBbT5M%2!c>{-Ns0RX&gRre9ecztlD0Y0clnmC2qZ0JXngd?k72Nz~547%IO+fJHd@uYC0@r zR!<373L#LvR-2F28UdoQ`+I~O=N<`>yukz}X8YY%!9S!Bz(1v)HQS}N*?d!e5Zlrp z9Xmx!Za|Gx);LVgywcQk!cXQJ;9lyi^Q?WaGzS0f*aCcO0O0q#t zK$b@PxO_YNIDwp8a(5@i+94S-v2)yVJNKsOU~wO~$k2J*svYkPb}hmA>8s)KA02ah za~uRaeCqPLiHY^!L#QLAX{mJ8RrfMPrygojz!k$4z+NmwMT?cFzJF9RWf=T(mOgB# z^oUbqEQoG9{tZUJ$(}>BXH_V3uBC$mN5v9RjoLAm^8C&9H@H&^>RpLYIrv)sU^u&r zlz#rXu+pv{U8t5y+_?#CK)~wa#CSXP0B8K<^s4}KW8lcVg%hy%DW#-cm=PdEsoosU z+Sy%r&*N|-oofb|lB(F-GI@d@9|JU;iGV&dKOa1je~>7n-Rf2@**Z9F5a9%qevbap z#KlDb|5)0L&gJvbI$M6J@mgw-8~$-J)_EZZv$VtWfEj*6fH=z_wz6S zhQ5n)c`F~TbzGf$I+NBEgUse>u|57$hE%caj~<^s)At-Yd%L*!1ufXE<>HWs^_{?% z_=x*_@VtFwEPuZA+g!SlWLLFUyl3e2d-DrQ@f8Wq?1?Xj1B-g=nt^KSE2b)4%l>;1G4&@?l}s1HC>++ge?1arqx*S%6z^ zTo1?hSzPdHfHh_G%p#eJefY=)5|IzTH0}T+`@7mm(r)UvoL|cB;9aTt!Jz;l88XK( zeqa|qpi7gwEKA4i&UWsLK|l0gP>#~d@Ay53j-{h>@lqQw%zjsur6s%V9=yIZetEXH zVhh;l;yxL~ty)PqfQb#bWXQ&^CUqf3`{3fV9ip2ZPcMgrAN!b4#H%^|aq|LmHQ%nx zm#o%mH6-HlG=_?!x!L*TcNdq2KeGy!InCKg zM^i*k!^kW}U^;x^-?-6nrNQ%)5CPM8wwcn>oJQlhjE{Q*2mpRJTa6@)Zzj<=T0tkS zt6V?>yhbJp^fVqQd2*d{4ThA9GRW%e_iwm9DORm1h^U}(Gno>jAtM3?>4u*v6+21azXd8 zLQY}J4P}5Nt1DRD#6(8QT-gSw=QC`v$fSSH_$zaSbgUzbp}{w zb7c`mZzIrQI?rnG^Nl9Ijuu&m$W&6Xr`@^vCILC=dHeQ&P)0`GRgz-}C-8a|6`AR* zU(>c*6x^!h4~!47USkQ;bbq_&a#O|;yK%2o+(Da!(+xy?z^k*StUMmZv{M+tj}h7& z+&&p4Ka_xEBmD>a@2_FfA|q9P5Xw3~{Z3gk#J7N;P*BQewigTJ3K zST^CMr5K}q@M~=Vb5ZPZ(G5i9Ui!}n!5dyr*&l-6&L3;m)X&7lsSloN@iHAlsg`g}ec=UIgSvZPY`s zjQ;jW3vVZ|8tz+4SxdW;HVL1FPowAo7IXOS^{cu-bet$qJT)#EStj=o#BO?Cb5zaz zGx}%aAO9ExTK2c%9Tun#i~kr@=pao41MO&Ln&QAax816o#PCPFQK^TOQE#>j!3>cEkrbF;y?xhe})^er>2n}5SelbO}~ zDXKd#yzNRY5OYGZNw-v zt#RMNWN*agI*kWUKj0o`{rROQazVUj{=R)g(zfb!yIwjvCsg7V9zZi+8F12Umj%w=VBVYB%cry-BNvwJ6S3YM# zuBXYh^W1RJUvYS!!0e6@$*`lDo5hmI$MJYuO<_QParw>J14|=M`9?F@^>ZAiM-t=2 zB{VXeDTd2&?_%8_355xLu3VxXeWUYGd-TR`huNuFhPH{#+N{R*s*gP=s4_v=|M_f;j{u2Ye4?~8~lGTbz zboax$OsrD^JAjpT%=i*Eero^JlZ{8WdS?TT`>FW|3ewH}bt`sehgJYWqjFnAdtMMz zGXCK7NY`t$ix1Nyb^`#@G%h0A`GX2>Yt7fFGnI%d++GovM~++6Q(4mPvlcz|Ti|@mB`bKND*0FAP$e?yKUH z79vKBg1yS4fE6Ivr=TQjW$QU7+8xOt8cp5LgqfaxXZQZ_>FnYH?85EpMqnIv{o*s< zYz9GMHy8wvjc|yZ>9FD(UdDH=-;9<=<`a_fg&e>`B5rJp2m;V?B&6AjSB#pE{%*kA zx^xT(1J}Sf&fz`)uJ&JX7wK4T^m_Sn4~D~G?}GktIb&pJkM5F3*Dt2KJI3%Q5^0c< z=3JSTow#kF;>y;~*ozp=c{w%1+?gT`#Hu{)y3-TuCj@bM;r5slAfx|u_2~U{v`(y8 zd#o_y2w+~eNwA{4Wm=kCRTLeG6lUDKXw43|3knox1t4FGXQWIMb}wPVXhfXeh>P5$ z-@ZgaBdNE^o4#BF0U7Jmk0t(q?`N z%rErAv9O-+Mpnwo2>bV1GLgh;cWpQY)*s<@9!uJW*8yY)i~Ui}ZWOaoC#)VNEg;*6 z^N8Asz`X->gaZI7M6{wE^VJC97{p^WX_5_+aWv>ZxZ1IcC3+`NEA5@*OQ8-46|b=MX;IW`&l~Bb>IWi`!p1-_ZT0h9OT8 zMyz$W`eD!^u?xEQr~YWQ9YqjNtLl@n0%27fnUB}z{{_a@K2kyW13;mdy>~^x(J%NG z?p|_I@x53Jb49a-ZsgAhiAdUA>F?EyJZx_=RY_BK%$+l?KG<3E04WhYAsRRDAjt^M}$J1Du?8W+=es-(!bmWSR zcg5cLuyCU)Z2f*bp+p0@yhV9qdUm0(q?#LhCwEoaA;&IruWJP%qYT4Tol<2>n4oDw|XKL26REkVd+@L%K`p5RmTf?(UZE=5XllZtmcF-&^Zj z>-&R+Zcgm8&+M7!`PDekD`rtDF1eTQ_b}J6KN`L5JFH%}5>C%A>-Sw(gD1XwAaSpe zon*@=(v=+|&3xS?veE~=WTA5oJ9||1HQtP%b~F;xyk$y}z8e2pt{LBpHylky_t5@5qxi;JACWJR>F5Jroo#{#AQv2wnR=YA;T8z|F6of zl~daihf>OltJz4vz-YQn{@orwh&gRk8%)^jS*R0jy|nNsAiloo0UcQ9pOp;pOiD*VsybA0Mhc zURAPPeyo+5hO+q+{4v|;##3Ytt2s`DVu(P`G@J5Qn$DIJ zjpdv$k#oF#aSvW)UQLlZmQ5fIhw#VOl4pC=K5X=bh311<7$pF{5%i=g03`*8^vrSggoTf6WnonQHjL5zMqkBd<+ z{%jp%FDNEz1sL7v+`_lH{Nw8*zCxQNK%S1FXD1wgAm@<<*hn_+L!Vrlt z^CwF9!WUtJQ0J|fhHH>rO{Y%3dyJhFZ*M)H;mF&D-xL}y!CVtwEnc0OBBcL{=jHhD zq8)_KR#pvgt`J*Lq+ti0tSXkf`P2e3q4gPh>!RDdw!@NT*hs@=>uKhpN64P;ql7?5 zzoW56&)z+G^UZWwRb$^ae-?By+G(vNx5Nz>)`PtD44zlRVr22+li^S^y_VVI{^u!= z@3N z#)8p(`1%BA)V9fY_Kf_Yo6X9$Sx7mKaaae^ozFrdnb1`qe0D$lUE$(=$k6l=TXO(- zSbXs`yXM3D{e1z$X-)#X^>YU!%bA8ypN?l2UrI&~^&QwP&!CY4`2_PMXTNKx*qos# z9ea{-h6sCoYJRW;w!_4p9gFvi*0wFvTkeNwOj3vp);y-;_N*)-t;b1;l9-|TK zP_BQc1X}8JXNk7j(>oce z0)!5EM=q{CNCviU{|3>P^TT*iVY!dTU1&2DzQxrNr|pZ|EWj@m9yY7Gm}}&o9Svkq z4#YAaIYrCQIX?Cy4@Gf!R1YMs_3Zb*M>-;QZCco=w;y7oI3eDBT$y;FlX{3WYT=LB zU1w#rJ6XO!wmwM|BMS{Z-WbAv9}qy#S#@btHFv#LfXo=m5%VnsnaO%O@EdujGgs9m z07Z{R6{GeBt$9PS2(*?C@JZvC+sk$6{x8Qk1R#D5);S9~U2+ln7a_Yq0lfXf=5AFy%(!k-f+T(((R;vv@%JO0xFKl|y!kWP0SVT>CTXkqa>6zP* zDM{9$OB#5;$%VgPV4Gn8gyn7}MX;JK(PUmhaecaEUPcQJYqx0(1k$CY*V365r(e-m ztca-r&g1#rIk%%Kbp^%wi)Y<}30GY_R!e+O44LJsfZ588d zSAD-l5A3=;81NAS*y*3*Bsxo5vS&7R`UwvbfOp;I9V#e6b1-pPD}EBsz=+|%*$hjZ z@TmKv2o>xPo>WCgff+=gI{#C=)F=6m_lAS*m|KajKY&U~ZAa+*ETH#o=NQ#P@Vtd{ z=+jS@;$4F5(X<|_j`j}U#iB;+O(+gI`yEP_x3#X@Wz>VI#%c$&kZdS=kgLTlyl2`X zt*`MXg2y8^#s%CLkFw;=scfwQ#lD>fZCMhslC|xaWd7@I{C+R|sZ*`^fOZFu{#B{w z*6JJjXzmNcMWyi?Qwm7i=1hQT)I6Ug$!HIMTZqJK1$>djdG&{@=)BIG&mwKv2LZsL zafwk%RvU={?Bxw5J|2(M!*tJC%{RZYbPg)bE53Igd>%d^9bOS|50Tiw4GN%fjj)q? zh<0E!p&wG}w|Tr*x;(WywZoNr-+zcodbtlhxw?^9QyCCfzkR#g-BulwynIXnC7XX9 zO=@v+6)}8NUif0KEKf|ad?z~U?hXoFz0N9AVq9Ezz@hI~^e6rl+&$BP1n8k1O0k8} z<#$Zu92H`<6il+pk7L79{f$ndab_f)4i!K;f^zc$(~r>#SVMQvr-tJ9hFMM#FjDio zgyU+#>;*#}a*4|-y+F#{88jbF+14`i4XZ|Dli>4SnL!tvCv4=QeUIcraC-$5Mp`~% zxY_G*-a?K4@DZaM)btnYeGiQiDhG|D_pPhT>TX$Cw~vpO2aOEn4o%Sc7~vIt%frY4 zLaO(O==3n922S3xmaZRbG;k!%GGEO!n=6wolN7^_-xt+>iVRqdLdL04C;s9prwJZ3 zJ%C{_rT2mTE+b1@oM2NU3RP+`2K1{hhT^=26t9U)di!J?P}r2p%Pnm`;+S2)jez;z z{}AQUU0dy);((K9>|dv*8r}DsR$Yk{i8rJj2Iq?*s0r&6heV{>H6Ndq%hwdHZ4Nhg zS0l*K&mqq~y2E4k*Wcn`O~nuHnjRWL=2frGCMlF3h#T_IRa(7<3*na9UIe_QTt~CncF{pxXoxDQ>@lKw1z^PTDcOS`j94vlbe_L-Lw6U zwgf*~XuS$Z?)iSKjh-K#Y!?idTHDZW>4>Nxf54r;wzXkVfH7rB`LQ_ka4~HaqE%$i3mSPjNRzl(7kyvYSaWs`xbxQAKQS;K%lU948#tQQC zn&Dp5;jCfu*H+s$8{){*B@uguiuAJ>Cl59No*O7>rZeksJ|2VtnHeZBu`$Z{yfk)S zk4QW!^j<(9_nJRh*u>wxpRgl03k67bp6s9+58GQ!i%UM;c>H16Kc|#0I8w$I95sLf zprj_GU=u!qb8^rA-J8!jlcnr5{**|~9R@r3G?FMfYLNkv2U>*<#Ud!=%zs&cHrn!S%X%;mQI@cy+Z;dAY#%ZXqKFYQFKBNUa3!Z?a}fGQ||_yM^$!-4=KM zoXh9bq?GU<`!+D{y+RX!GX$@_oAN`r=J3Z(q4#WijzLhK#@{}xOXod`6?eskk>UL) zI!M6#l2*so+6WSL^G)deK3p`cK143jYL_iYQWu2lM)f>vbkD_9ReH}x?MXr2<(c*K zKA|DU+})RjZSijg-FZX7;3V@!!}a#OawKN|1Q!krN-YV;Yh`{Z#|!OZu1a@^(Z-;h&smaiy8=<|_cGyC!=TL0)z;eHd=8HZX3{+~u|N`;jdFO=C!vj{HuAaoC>FuZ!Bc825^qav!XIRe6BRoYcxVH0*q`|mU2qfb zB~rGA&jOMSx1~f*bi3(KqiKH|Sf{`GP0f=sm_~~KckuT4&m&BW9}U`D)X;})ifaZQ zuiD2K)8@5%R?g zK)>9(rv&G~Zio(Z5rU3~aT%3{lWfXq|NBBk-|ZCUKiDA;qsbTOcirA42NBVq)C|V2 z0wN5;%h%l9YqAX6cE7d{Xc-qONG$M2y`cBALTD9pINnNekkyx54x!N0>j>d4uW6cU zXx{fK)BYLf)3Sv^&)4!m8I3CYa_2nVL;~O6;zdQ+v}u20wxLtDmAUB_IUmn40g)fCir zT}nHFlY*vMie4Ei)>+R`$$Sr$X-=Z&WcQDoJ78|%Uf z4u^(VFWg9fqVoQwlo#cO#(h&$q`lSLuCu^NAB-*2HgC(A7W`cl9)2KhqKLHqG`inn z31wun^)&hFl~-=6x+Rc1e*5ek)ObEgd}z^( zjose{_f78(h*R#mlAP<^`ugs~@yu78Q~O{B&fTEf0p2ZN$o`p_^7`M6q>^$SQwIy0 zGZr>7_7`R`nv;()5ia?=b9u$UnTXL(M-Si9`qu8WRH(k?rW-xqgvUd|eBXXZF@$>6 zBD2TxKcjTTdXrBV-WOA!&|J*Ex?a^4foHZ^CF5lUK;!^{z(b$^Z-_qGiVoi7aU9S-+_an9{mh3hj><;$rb`ED|fN01t&%hjktXGfm-2Ym+ZbKJ&< zLs4IIXJeM`HGS^K#g@}kRRwEkM2VF}Aa0;_6pI^MQ*W^=z7^F}#lBRi6_keR`nsj1 z$8WgE5E2BY=KW0x$;e-$Mx8RTX$y)qQkOVd#|%1qpVm85ZtVy@W#KX9T{d^s`%Ry8 z-duk3u)@{;`JH&4t$6FrMT*=58{6sNv#g}x3gUP7!9BHy(N)~X-KyN|^ZxsP?H}a4 z!QB3LCKY8sR%Ufw@cNh}-^6ORQH?h;Mc^5g4Zp33RX&K|J>bBF=xiqZc z*NTor%-rtvak7RyEifAsvkH>0YZZz3sAoF&sUoej8Nz{C|TFk4UA>i9zwAw1EZSPxN5CUJg2|njX zTy0Ag|Ngisy&ZL%>hl5Ixj;GGcPm`mxdpp}i5DrnO>DKaPyIuF_nMiA6vrk*#Wu;0 z$7!0#x3aZ|v*+B8YohHm;Cq|6Z(O{Ge7ujjtoUI=*^O)JsB`Z;b$|c-xoFL)Ur(|g z9}F({{a>cWWNnhWIbo$5cOI!ikxYhFiK%<*hSLYRvEiuatjHKm+Y84rX2`0)PGrVz zYb>1oHCvK`KbSqcR+ybkG8KSn3phKfh|;h z1dk#J+Mj8-yCK%xLP2jhuKzvI(AfL|C+JY_z~zGSU`y3m1aO(;&li0zW{|np zzXesVAOgeeEyCcVA~Iw<_o3HR&3@>=e826ajs}l*x*-y0NFwz9Uh9c>aVg&^V|FOx z8n1BjlTymfEju108Fs%+`Dm5~Y;6|kY`G0;^>){A{GQ@7_2RBJGZpvpQ*(d+8tX|Z z@DL^)#F$)Mpph7Ni-sE%cFs@V!+ZuNRUYMYy)|Z;ZSAQ8HosktXlUn?|9SdPGsnVb zZ4oL8Zd7IuV9N`{!w2seP@_2W-GA_AIRGqdHw?8j(kxvQ zT9oCVOT1EiXx6-iHT&xvXk6Dqv92MXs_g#*0QEcBBp?3a#7a|KOcN54;fk6!>s=oA zyB-_IJ?`rh)q3a8C6jr-xWHDr*bfP)!H_(=Trp1sb8Y^ERHXxIE8%HGa${XWB9HmDN2`$7fQNye8PuNfuMurPQ}uk)+hUHUj? zBW65KF2c&HS!1KE%pTMk{HMwHt4Jz^&M?sfC@WLD?kFbMj+*XPp)TvWd=K^EXI_06fuJUT zw1l#w-YhG&LV!At?s5VK6ML^q_p%G9uo@)=W`PzQ=o_Cc9TsdpQ9Im@S5?$ZA*`jx zr^qm8oM@pCzL2YL5#R)8bK1@$%)@LN%nH>wjP3VGzn-)RDdA z=cgAG)J8<_#>D(NHIE;xfnm>OpAbB>VZgbwTP82BO-0o)I+@iR#IVVxkNibZ1L^br zP*Pi~q0V%_dIAb|9W5h*UU)y^80t zofxDHKH7q9Vt$Kcm(-5vDA%sMoVtg$RJbMv@0s7jHuL+JjVYOA?R;fI(;uv??qi)( zRgAG|Z8tF|r0+IpID5_n+Rl_Zo2Yks`uaT|aE^jQ*~-n7VK8`Jw@I+QU^EP&!y11|3vlV5Xq8Mv<%p>*45SI=F`GXhJRi|p`n6lAp5 zqb^00rt0slrO;Bc6uE+B3kuCQrSQd^0XPF(=+{QKBRHXKN}P`8P0fHe`bWJLq}l+> z>QptYWTeoOHdY4<$10sUeu{PaOW=^CpDf5#djz~zvm(EjzJHM7&(P{iAZrDJ$U9?Q zq^K$Z1PtI3I6%7^Yl=RiI*gKG6wf;VC$tZa5dQW!zkh`+=1coBDOnkcfAnH&&-$q4 zFa}+flWWz;>q!oqmRvYKw6P&Z+poB}DGcU}uMjJQumVM_TCHczy}dx{pL%&JkTIRX z5J&Iry~Oi_0O)6tRI7i_q}+M`$r5Aex=IB@yalw6{B6S%y8ZGba6I=Zv*B`P|)%&}L-?$rNYn#oOe%)n-C z+Et0i{B9w{~aDTIc2c9z=6NMPAI47FfubEV%TD;+t^eE_hLK1e*_DES5Pgn ztcH#vCrZFirBr3#4Xb-3l5$baCK&f8As1e6zs<|mKR~MP%goUh<1`NY?e}FjR8cCS zDk%L_0X?boPtM< z&+KQHoTM>iDsVr}#H`=A3dqHVQOP`y<8Jd?f}!K&Bup%+wmOUSfe65*0skGfD3T6v zTc-Vjuir!OE0_oKzxYiWXG|Le#)C&O3!Z^xGzKtX1n3`XUGHp}pal84*MahlH+^D~ zHbm}gIA*sivc<{x1Hi!s3?pfypMkcw&WC2NH$SJ$_y`%f=LhocE@hhjD{7LKn@tshD5u>X9uT9~()C`oNevaYzVHY_Co04B*w&kzZ`se{HiKIa@TvcZw}W)BtiP3Y0w()?a=_d zGh>EsFm$$@?7|<*+&k|y_Ljq`Jh+^3J#)6v$=wK^59gEjTOm7jF8T9)xhY$VC*@4F ztSsM|$aegVeZJ{@Ug7RmvSCJjancTnXN2u=LB2&!esNNuN&;gII99*BDL?%ww4}US zNWLg|M+KjyV`x`8SyP?sjU9MFdujP;NxP;&{Z^dRvOHNQ^PRc8T>0ugm38}FOq>=V zt(Kdl)OFdApRAHqpFVidcJAYbS8KU{h|{UGzZjz zUC!J>*$@}OqH+s;kB)(Y!_<07j*Y(KT}x9Bmh(OrF%I881^JRjR(}7NBS!SYaj-wn z@2w|mN!YmOE^AHGEv=aPs5w%Tjz`3 zaFTLtE}7NK%t=Wx3QpW4on0hx%`5yTj2}&FeGp38dKxVDw@-H1%Bo^uM;6oM!k*>< z?|5J&6pN0Hw8Xglbzei{LJ!wXq=ksY?)q|1UsMejm zSy80j#6*RQ19KS+e7n(6jRk8lOc+0O7Mr7`cX2xZ#LE3(XUy!b;^}^fOKmbnHCkbP zm7WHD4K)(n4=xfot?F>$6?vQKX)Nib*@>D1|?c^9ASa4Ra z?YA?sK~Z&l3_{+K{}7%db*4L5j`-xp-seFMoWEM?NB(4zNH+y$qmFp}mtTD^lrm`vg&Sq$~G%QW%pq3;&^Qw(5KhJbe zYy3hKxNq!DFc|S`Z3M|=v7&|zsROzv7vdS$QgFhTfoeWvPqBxkXcIZS+veKb;A?)n=IKEKzxrvM zO=~D|@l}U>ikojydP(Jt5|UbJDqBO%_6!3a261;(teWllzQbUmcO zxCH--gDD>M*|V>U?p!c5Ig;ON97>%3Nf;L1$2G?^ADU*D(0*jsOR9g_YGnIRrc+Vp z(f;?xVP;m<375J3t;4O(JMXK9tJxyaKSRJ4+j90;OQT}@0R?Bk!Q>Xo*CN_={fnZ% zv+E7c`CF1-RP=8XrSxuYvoVdD@l0J_R$rXL7*(mby6Q=MBl$|)#ER!d+m<&t_Z^)b z0X;n(Rn4tRM(65i85uN3@Kq33sg4n`LRnZt13$%M@w3FB!y~VGjev*<*PAy< zz%Jq+%J%AYW=^T4Sw(z8eEre+{ z(|_2%BHouU^A0OqXb$53(fBDcYT7#u_x=1Q^7gM_!9oZ%Iz9(S6A1lL|WMmXExq-0l8K-N5>ejsoC5yQ)3v2j17w^DWS@$XPvZfR7lvoS`O zaz2pL5UKF-6bLol%UF{|RuM2>!*1E(WlNoPO#Rq2Q-%s&wbW81$3?({KY*S9q)nth zlf=1vQT^yHfojYI4qt+J1h1O!JtCB+)SWQeDu@bh8#}Pi5t|h1K9^ljP{!NzJUp)f zMa^*nbHGqGxrxI;=JlG$y4ae-#CJ_8Qw<_cO-=E#vMYdD7Zhd|=2pr`ugCxlhNPx&0nvi+3Y7pI@D_1sv}xm8=IPUFIzsK;cL;;^VQPfR?JNejlxg& z7yAcMF*1hx5xdXi(U7jfxLh9CfO5F@;hgNL@dSXGoRsqBh5=VxoUX$P(mQJL;b1m+ z9cB{_+uS%pE^2{jTEc+vSNJKeGGJdq1Q8MP;s!}1$Iz^Sf#}6e4vXYnEpN>9;Y4GD z=mDD~yICkm;ZRV^Qv1rr(iVeX|LDOEQZ+2+&-(Q7K|I$n)}>KFXqj*E3)hUZLUb0Ggr?Bg+@0PbGk!!tFh9W2FNc_e5jeM+ZUwL$99`axi;I zNq~WxfMn1*_P%m`^~$IuKmDd+nD*+X;bKT~$NsQ8JSlj}h|KKTx{)7p>tb659=>HJ zLmSBGK#cK(qslxPO8;GGZs05lm{D3MyJ_>1nAyi+ZQVhlqeGik$N4#$`+=!K|A?^% z=bMcUPKw*qXL)&q-rhxOCHm^9S1Ky?e+T45lgU5e;q#d&n zRV8#2&?N6iGy(C)lfdR71Opt@(F3Pui{PsW@4hb8)4*I5x|FgZ>vCZ-Ehm1mQ7~d? zAUo5eP5LDE-g`3DX^u!26&`MdysP;2!MF`XngGrDsa9jK3g|fB8z+X&|C%5Igf$l7 z54{I^+!_FN6oW$|7M;pa`xxhz#<6#?__KkM93C0~ zH4hMYx_4NRId#plgQzcH7Nk_4QN)VbdElbU&FVkX+I z`NqV<)AMX?qxu~FGaY%t;Lyc>aAmbUsM!0xk#zBm28VuRRVz^H@>nd+z^D1tP6&Fx z4G6NN8?W&DQlfnz=MnDr5or%($?zF`aCtbi2Fh_J3MXC*A@CbuT)-70%v@4OT-ZIkgp?(N)HRvG~WsJku0)F1uA@_Z8Pk+v)#{l`ULh$fo0`C7>7 zKv8X(Z|x>IBW;u2+h;qDW4!5ACcj$9dw${QH!QsNdXL53Jy)rd&JWj^#Kt=``g;Mn z(xhRehz%u7&MVvXC~sf}6v~0)-dn z(Zxi4aZC!5$!4VQJ`0=d{C&=k0}ikbC4Oft9v{1-cb1-$_wNt*;f-Uo1!*ACgMk`ZMCMO)E z=a;S@fHE?Vux)j34~QsrW!W0^J9{j>|4~;fenW}zf!$(tA1%P?SE<|?4#2Pxi?ud7 zc|#pZvt!@R0ak|F)_;etIrSt$DtE5j=LTpNffXq!6yw?H#>+C3H^Ae34q-2gjv95| zKn4GCjfj|pWb}nc`E>m7J7@*>7b3%y8vK*w%>S^a3=L#-HJ8A<+Z+9?WhadwcY?J% z(t5&idXvf+oyui6PiM+l?P4PSVDj+Ye8+tG5*`_)$ZSEWA51-o+_OT0f?O;tgprYz zHdmG?39OUHlZwHWl`|qD$LsN?13L{iV-pjCKYri~Wkica2r2ZP^5+%plX0mc-2wlF zfMabW;v4>=+6B(^)U9PK?7jjX3_Kz_qwG|@t>N4@U^OJEk-om%3y?KbVYll(MX{Dl zGF@In+z{*@{=n#7415n1!^}223*VA8nR134dFcS!)L4-`O!d)t)$!M6i5Q3i?VwoD zv`!TU@nv1>qLc8Tsw^p`zsr$;Z4HkQ5LQoe{+oLuAt_|aJZ8P~I%!bALkLdOjb$T_ zY1_m+%YK^n6Prc1^%~+-IpdtEX_>sjSYnJYfbZm7uSkjvhgaSZNzO>H0rXD+niR3I zptj}Ct(#YnmBn_o4|*|j3{iQIi19gD^Xj8_{C`6}-U>-w7`eGoC@UwYaJT#clloUd zr|-aP<5ah6_NfTFxPcain3zUcnZ|5>>YG5v$g>nst-79lC!7Q_^|%!tv#Gs2**>J3 z%*-;ho3W(*`MG?!RX>*nezoFejy2`dcceLC&SD4+7?TAJzL62rUyXgDnuvy|KK}Ud zs7B>d&YhsBU`%=|hvkCdqL5VxJjoUn_%kv4v)o>zF`e6N5$yK{s6xZM78m+#PiZpe zL@u0LG_U2Q77kbfE1kxM!dE}(P>w_)ZqA|D1KseHQG&-X-9*BH6 zIcuWqTu;yOE^8_pEE<>#8gyxggHe%8QE=d3$jM$wNECOGK#NLo7d6#RbM}u2=@v3^ z*lV>u`^afw!5>Ziy?LNss~|VEsUA;PZ>==04JQG>uAKOhEu_Hn2;4q}+n$~RQ+fD4 zm^#e&dsB!YbOZ-VC(ovCjZIS@?t`+yRn>GeS0ERy$XOLIx_ZckKqmloDJZ*Ee-#`fZ4LY~FT+J z;LIEsB_~u=FE?Q8XqoP)lo{w`vUTEBxjq({khVfUyGF`TYJc1(wAX;jiF=GXo7rCj0;uS{C*;pss2#q=ieH|UUWR@DMosy)9ZaMMY(aa z5rbDZs4X)m0iNU!H%_xZFAKnjKbSug!3v1u!T)&!ivVg+na5LHc@;W3&yxB;$)GF; zbx50|kMAq%L6({M`Mie}l=}FEMMsejGubUE&kj~(+psPl zxn{aZjuI)X|B(n1+R&mwFl1ANUP`W&Nef`)4r&?N@`_qmO({y6JOb~Phj5G*Yso^0 zOYGG@P5f{;x8oaoow-FJ7@JXv@8vdOaZ z-bx-s%3M)bM$>I)5QKiGb@i+TlhD8P#P05ga^xWxmL`XXtzn-uTsPs8l9C^jUaj~Y zq&zGWAE68!54t1Yb^_|61$q37B zIN&#>m#)@^wclOJf;P2x1SnMnPE{D@LCORmJUi{tbUm+egbhupHcG1b6%{R@(Wg;+ zd0~O@K=jvj9ebr8F7y4_l6O`>&5-&{uQZ<=lfl3{3W}b*{cD_uzc$17IkNLvLQgtm z-2;S#?b{tHIfan@$?O;=hkx4e!Fl#)nunB4HHKL#0U_TdXI$^Ad;sdtN7}GfZ+&&f`U6FAaZBV9syT4|6_q!m)^w7w_0CI#>Xbtm(J>h zc<<}|r}@|gZFXBdhW$e1ZK^AAK{{kB3@LmQwjV5V5RGfH#y|?|+L#uu<8e>eg38G- ziw^x3BD2e~{>U@S;>rNJ7RvRknowDJuA|I{vOCU@lw#qM@mEWSKE1gERFoDQyKef8 zXG2J$Ll|nZCwkhj%5_ynRd~7hZ|Gq<&qO87ZlfKw+2fVCGcWgn)_54 zgHt{wHNR3}Nb%4rRk%2u%cC9mXx7FFVfjoGRJmrNtcb*H5O^1zJqLs;RnVp`XhSxq zPaif7ua1J4X7*_xVv($HRT~`{dSxRIGpnGktKX~lx>k7Hxj|W|a&lr+f_oJQpE|mW z)O4W&w1K&hI$6j*wy+`&X9SY&S2%SE3EE423)7Coa8F=MyY55+jm2)I(oq68Zc!VI z3Qc`}^YGzaHg=#YkLIQZqLot^2nZ1S`V}se`@~;1;h%>I|0!7$^$B%-8}Ja<{Op(d zf^Cp>+21nVBBH2o&9OvIWY2@LA{?Ma@($-J)zm*h+mXOFZS!!~3iG=mnifs#L=P*) zT}8@xd+8D|NL~5zUlQ3zyAL2S-56uZd%AudZN!gP!aKVN@W$SlZsJ0O3aXky;SEBZ z!&_MyO&z?cnGnn08l0S5`?gMR$<7#!%H|1eos~QnwMIN)Ik^Y;S+=TDe}$kAZ*1G) zf$O+WG&F9^vURRvwhxH?U?H!Vwo@lpKV7~tCw?_aPfyRQ$r5)Tp^UT-d@1!7&EGL_ zu>d_A0PO5?9>*_0$!Es~)4gTXZqXPPJ)V;{-=qrJX9Z~0G84CPbKRv=KfzLf`tUW#{;0TGQ!~%u zXsJa$t3;2-V#KUfg-%$Dh$>e;o}UkoF;QPotj^pd zoH1Ie*Zaz@29sYu^Gj{mP(_u46TwlIOL7h%Nv2UQn3qsQN2bPq(tZ4*BL&#gBz5#q z@Jrpk$NzZ^de7gDW5SU8Z<|O~9LnW-NO-~Ueh^7rh3I;hfi3aA`hyGBY1{%|n5;w> z;iBCjEXZgCWZuT3c&12U#ApQ1PqV6)#CN5sbd1ujUKztEPH;tjFYb^(~mzDsGB zmMW7;W{4)9%kvb{^{u^5?V8KQ6e^xjL7M>AmsRs-a)f~-NmM()8`|Ejk(y% z8mH9tWq)Mbxx2GfbJBkW19rB{DlL`%?>WjUDg7PI5PtSeqZTtMCB^ygOc@naph^Hv zi4dG&l}6v?R@m6qxl!fZ;;l{AW*Iwe7+pG8V~*;KVYd22N24&*G}WIpcCr7VwP3=pn=Vj<_?F7Wnxi_X3k|@qgFUoJ6#noHvqf zcZEl91Su4>EnEIcPA)#c=Fg4&T`aYPp`QsbU{2=vG`=2q-LtW@acAD){!~Gz+pjxb?+_51-`e>>G-~8+QNdclea`>P&8ZW+NsZ}#Op|1P>iIMnOv%F^Y zG;$HGMvb3xp^MORXt&Kk2M@vK_ec4#n}iD@qk0Q~7A7W9^YJFd>8?zZqiQgoB_c6V zSJGVP?5p1+k2|m1VbOniZE!=;{B=dsZYyxH$Qdx#?X%T~luIP+f{DYViQAZXDq$pw=ui}Em{n%r zuswF2nvH|C&bw*#mmOd~U$`qOiZQXi1P;f__~G#&V{`nw^hj?^~JNI6PGOaTZrc76S~s$KPi}N zIRQ%`5P&HE{5@C+%cUkt%`GUi)@gPqVc_DMYOSoeZ47;15+1;QcFPDRr7*oV(Zn?j zeERd1<8Ga?2=}Rh&r6&vwb+T7iJ`Ja&gO^e-?BblDIKG`%nOaS1Skf6OuSRXKfxLZ zmahsM4_uC%oe5cIh^+)SitPJuzu7WIKNnO*ER3J;^?lqtIyN~!(&)$TvR+R_4DWuC zI&NHA?O>H(`U`^4H=ClKM?~ulgH5L~z%s1{`yb*vjXoo=r3>2{CYj+q3wEj{C;!S3U``V19os9tn&DiMn`@GEQ5W0>J}$e)F%QHwf>+xnIIZLrPcXJagc*~-NN;Klb7K%WC-pDmyVRR;mWZou^9cXov#$P;m~C7m zRz@Cu4W;!{-GB(eVAxRmpUdgK!_|Tt1u}lSate>qJ}o5 zwFI0BsNfqV-0TkS-O$8E5!dFZbaL}6&=EVIx(JV(TV=BDgyZ9bgw0J|t(7a7Xw5fb zOWtg&~oMwzD<;L$ljy zql7bg=Z($&8-^J|OPM#V65tavfoF5lRI*`8I$)c-g7~Km;Z^++D{3f5D?Mm842dN4 z+x9G<4~o7c01*HnkSV5MEFF}IE8l(;jW;5dJ>2z^QmQ3k3tFazC6k$ZX^7c+(ySZ3)xn_BKaB$JNEiryD*U<^ zFj&SNE*549corP=bKa^S=-rr9aX$BIR-L7MilnTWc6AM&=Bnxv|7&=xlhwHZ(81TQ zYtes(_yQw<5|E3*BE)mJIvx!${Ct6O_f6vb!@ov0S^ex4W~%OByw-0a8e(@PF}w36 ziutV=r@L*gf!v)iM&N>WgU2}Tg4o9JCht|f6SPs6ev@&v52VergXlE`Q3Da zPh}+~jtvMwJ_fFiR3g@&S#=TT7T7Z5sc`OxbsWBed2Kt|u$2xo*M}{MX_@>Sl+8Cc z--_T&`6+oGm1?o z0StANU*2C2DIPs(VpnC!n>IWc14+iyqoh@^MMsss;s?#W@3iDe?29oy9*L<$jJYKj zil@DbZ(uqFolisgBrFSD&lU?gS=|-jKO3duNb&yhpS8FWT){{&S_=-1Je+S{2t;{# zLbJKGdhtXBlo}`z|2O~S!a{6D#s#X=z0Dlmu0WXfc2qqB)`+-m&Qb#0?XIF4`0pxn zLpdo^1r5Sc8YRn%E&|8LqcTqzN|E1BwR3tBTGMG7RD(&D!~4k7f^8n3N_vIzrazln9d+x2!)P)NzU@{+sk?P5VR3 zsTQY@6=W)@dkCD)dtZ#q3&-n{4*uwlTNkVczIK`SsI z&7>dOEULJ_A_1@^-z2h{vVG%ATCxjfn-0^S85_qM4yI=mhXf8Lv>n`dw)+#`BZ4X7 z6RUZ8hPK(Ts&J9A7cc#vi$2kzAG7?TrKg)4#pG0estB42pF#JRUzC?xAm-T|Q+{YV z;&3I#&67F0rQ`%LU2xc~KCCQiyzm$`y6Iy>A`uccGed0{caTx~vst+>2^BFj`|Vu& zRNg6XA-Tf8*p6fBQH1d1@9c6>(pgY&FMO7h(^QsXX&`KVs1KV`-~xU;YE7cgT1z8w zDp(ZpwSc1m-##E0SV2lW=os}yV#|iy;h)6)J;K*cObe?Z$lUIHVVUX2ttvg!PeAN; z+1&OG)ah=JdFYTzP@h<8$TVW~uDaEJnyhdFE@O}{USSk++O^w(giQYw=dPIT$Xe1d z#lL(!{E_=1?kQEAO7?l(`wt^esp23(EK~gA^5YVUrMw9ycpYx!tLOhR#xtKX##=#r z8(bV3(F<4HHWyj{ejh;f!W7l8ZA{2L_0*IZdt6rO_5A@KsDeP?<%eB|$xp7Y{F+OF zI{5S7s%f2MmLO27n2e7DXEh!v=`k`&hr)M;jc1x5A!Z9Us)aNd(gO+#@3&Mq+*`Z5 zSfZ)3O|Jw6kWk--mW0X(Ng-NU)nP3VQosGDp`#TQHCtP!{2)9&=6ZbEu%wy6W4#mk zN-v3_Llv@~SzR5`a$l6(*N6TJOJc!B3K1})^!kBpVPP8HzIgsYr#eFfnp1^H8#lYK zKpGRC1Zgi;7l>Ioy_{yS%V%RvNNQ{Z6oaq#mWarA&tndbA~|`E5`jba%%v z@LqG@&+{JN5AQ$l9S0l?!)DKpYp*!hI?v^O`!+R=1}Leg34oSNdGIriaC6}7<&be) z-YYuS9A?W4v1drBbcOn#Z!t!4L6JR&fDz@@P;wZpmjmZdo1P)4Q{}RVH@8n~h6LSC z%Qc$fyQji5G(gbf>o>H7rQg{{-1f7olEw>z-o18?_{MJX3nyj?g%_(q$01Nx$^jW;?X}tao z8$}Hu%y0#YajM|n88d+Xe+S=p3v?iZ`Szoj(O08z;%6-XnS9BQ{k0x`@W{w3a6cp2 z6rPJa=wAY&5a#!HR(g7SAnvQ%Am^!tk#|seKC1j^iFdjQe@5-J#Xi;)cz;BHwK1sq z8AA?IH9En!^_?q=fnC{~jX&v4g+1QYtrk8@Y!w4;*W)59|p3rhUCgI!CR}YNpO{9#h zNxb>J(|8ewK9!;8=VQ)Iu;?u7g|D7XO}sWIbGDwKp*~BaY1Q;NLd$ z_2qY?ktvE_S0vQ8 z32q-}{6twUa@4kbDJeNTk|MIx<4zRW{&}6FJRS&VA7E4&1$ANe>ub==lXtU%J?K3D zjh<0X%>8}P>4s$M=#+UUDOuR!%k_C3b1hLS<4=dVnjHKrqd@>QT$Ff7=D}RNs z%V8L>SU;SgT3ONSPYWm~bq%1U-zmYicpZEDDhcT^gnIiO+RRi3ts>oPoI!>D_Lg7j z%{iOy%3X*lJ^en22r6B3L-yw=t~ZhxAXN5$T42qUXI7Pv!cwhNcLk)i)Dw+#iq)RW%c};>9GXVvSxsH1 z-l(*zm>6W@(;57u2+eVbgAq^<>Lf~~c>}cB>MMTv=cKYy%`_o98|o-|(*z~ulyT}3 zOnt#ziKpaGDN&vu^|Fq@8$D6YtlvBwsIMPCoI_TM$Nf)nkTTh#OktEwHqXBP#M8sV z0#$oi;1zu!Z>-hF;?dJX@D`n!gHW>{p}msVE*7xF4ah{g+FuIH{qG~$+ z(c+)ypQ!&iLMQ5A-_|=^Xf<7!=L;C8TGx>4kKYRe9Bm+gj?EQt--PyQ)_GOD! zf`hk~w*#(IcXrtlGjU>D(-QM$eY>OLq(tAzwA$#D_3HJI-+y4xw}AD?#buFNDL*7RG2p!~zNwIrRZ!qRCS#)TViq&Rg8sozA)@|(DM(0Z-Nu?Dc z;EsSm&c>DPP-PorUBI>I?P=JjsKPqN=L-NA z1Ie`J&tHzygw(-}oBjUXM%afirB(pAl+1H-U0qKVfQA8bI(G^V4!HBV;8_Tl;{h}lsSc2DLL=mIv9jJK!KU5Prd3Wx|l`V5WIL2z0 z54*;S?6qeE{xZD2Nz~pMbFGS3qDpUQDz)kT>>QOJ!krGpETBhhV?cefrkNDLwDW$h z&p{Rb%Cr zcxk5TA(R{pZ=*w`Tc3h;qWXSZ;ik=JR+Q=3=>2@xi248+GN9`0BGJeDitSf$KI_}~ zhR=V?q}^*3K?}qv&#s_PLBi)1e(<{^zX)j7W@fTm*J-gvT|N8ao?KetvZRC5b8LJ% zJ~BMs1IBIwf+Cn0Fai|$cM;W~v%>p^nqcLl1v_&_u}I?t=nWc#%7IQVJ`1Z+wfCR2 zbx2JG=0CwMSNw5t;(5?r?N}}@JV0RS;`X1GL)$7nWG~)dJuO)f(frqt$CFK@9}>By zK4(zV^+bKn@R1q_jv;d}HcXt_H}1YYpfi2eLLqSbwO$@58P za-lW8Y|_EV*jHi~25ZkP74JE@pH_Asen9PKMUR5Z@wVeFt#QdU`=JA@y>F;!W1YP| zx+-2C6_Bo}4NP(@;H_mFwLsQmL)+^tNO{d8B+BPUVYNLEQ!$#W0hY_(_9~tJ;t&gj zB{m$JJ)VH14C^_2>?=E_0Vd6yg{W`&4L=>7i#N8lYnk9aTwmvVf#f3J01LCbj5%=;!k=4G|j(ajq%5>#Xz z!mJ1o#BbocZxtK880+R$4HtSQi*bMu{>I?>>a6|1);S439uQdB(M_fHjrzQN$C0=?qdG;W;VJlR^qI4_v~NV^VI9a&!9<8HdCveNi#V*Ukm zRsO>~kmYN*gv2P%MSI7%@87!H*ZXv*gV6xj9~!w!0S)M~Rx2;`N$==#PWqg!OmA> zU#g{2{l5G?>PB1rKXaC;0*84kK(jphZmUq?9Z9`*$#tI?-&(3_MSJVC`l`n#VT6fT zs7g3uk%+bh7g)t=A4GhdH(H*eN-m&0H?{x6Y`nHo;^Nw&T@2GIW%N2W17h{lj{F6+ zj#S|%IJzmNS=nwz>3jPf^?SwjoBO5J9QNeAuVpR@VQW)7SmnREaYSG%p17Rm z%Yt5r;|aFbU9vxGC!clN%iinR9N@evRbc*8K;F+$dZzigF}U-iNO_S0M3Wl*=q5wT z!8B~X3?nt35Igq4h4Vpb%{AHNycb<$%lKglzxLR@@Pf+XU}i9owy8(a`HRz&@jkwd z=HeovyjtT@AGao2!h+<1X$O1f{*>j12#xbdxzt*dHYFXkR(`G*E|JBXEzJBEtrt&9 z3q?Tp9R`bbUl(;rv=o#`e`fNc_W%v9; zCs9C=0ogYI=>kGbjq@d&{+`)4$c5RChBL}jD!ZJFw&uTKcn<)HBx=YL*T3EGaM|as zsmD8_v)I?}>T0)uc4}KJeylqU#_C}==ba9@tkE)Y^h^ozUDF^4oYnbUycmy=Blq5Q z-V8NZ6aam6aRRZ!CfgLh-ACK)X=2r;wuG^Z((5AP_=(j=oFnFchp<1K9S1~83liuv z@`>6ab(?XGiXW>?9G+C~ivf#!w9D)r@kLC}fh`5gYHdmRmnm^J`dtg-*Hvw!J$)34 z-TR)#cFOE2P3N_zdlI9U-j4RQr@0aIltSymc=nF9r?5#Zj{@$h`!gdhbXSAGi@E)$ zNRCW#0rtbYru^{c(9o9drPBpIzuRj3DllHMw6gq}q6a@pz;8zL@Nf-WGWTi`&r!Eq zLC`(hu2u9LwBPdH9NsNVGtvfqn{)c-xgsrWXPpIH{y*O|O5*2hg6D($3!% zDTi_V@BOTur9X^}pHL|9p2Nn;6ugFsPo+>Y1yjhfZ;9Bkk==3tfflj3?1XH#`m{yI z?d#BAZ{x|>A|?vL^z$lEHeMZ|cx`h~BTk(P`kCjC*M2xm|B}0?nev&c^?$|x?sMb$*>i`P zBzdQyo3_XHAHL@w_{Z&K9^eoCnB?Xo#;P#hCRwBE@Wv(%kN0!AcRvn(xA0t)`v5iI za+NSVr182+_z!&L0gvq8t;Q(LTDGoptWjh|x^9}ACuw^J!Uu7$;e74r$Ou|FKdwkq zfY92q?*S;8e3m7=JwH`fU|b9g6a;GEX4SJZ<^S4Y%_UzRXXS66JK62L;2CpY0>-V* z5pH)ikcKq_sMnxxlc}&u_5FPBV7Tz){W>L$oArFbADW_7V`CPJz7o5SjhjTVRbakS zDj zb$K(A-nMY-T6_z&xAyi_6w<9H26T4982uhmr{#P8G;4(M;K+E6!G}>}^(2Aa`;QRh zOVj#szsIAk}TYYp8`|)nIx^gXzBA#7e)qFLQ;=9*smKyj7H8+?7_&hpqWng$8 zBM;(gJ1FJ}pnEpm1Yq971DN5FydO%6A^@dT=vvufR;#BW4jX+S*|FN=cmrzLR8<}f z6bzEt9kZ%Ndx7GF_>5|2LmU>E(elNI+|aOWx$>itS8hBQ;|+$ds)1I{OC@YDkomNf zlc<5M2hmoefV|j1(VB@#Ya<1ytxk&d`hJ*e$>4mAC-r1JK1Rm6@?_=%vjzMCuZ0Ct z9nnyW^^V61&;N1(M6r;(wMx*5Z7Q%J-97qe3xTi|_l^QcBA|!ex?1s`H^Cq!9OU$U z+88cD8Z!>yYur;%+qHaVzu8}^&VKxJi{ZucA_G;nzRaatg(aPOV^nApsf4Zz@X=L|>Y|m(?63@dh)@#GVkjH)B z9}+WbyMpzPt!KWmc+-~^zG zjq;S*p=()q`#lC%aG42b`jk@VUr9K`{D&# zcRKaXOWwCL#$IJ4zi4GGoyx$tntwys*s9dVV06I|9-S$ztW;=bgK6<(CB~ciyV8ps$2@X6)pBs z?cY<|UzTH19q{4s;}K^zF+9tVeIXS+WmjiTO6>6bg^tj{FWTOg7z7RvXZLHJvXA{S ztJZ*`nj!gLiYhU6_J1g<9|UdoI8GhDL%IZnbj8e+s*=T(*K?G4sU#qiHXX@{3gZi& zNFu-;kF{C&cPaaiEu^YhQ|3TXK|m;Bsj!q`1LljV+d!zUK{$Exv7q(p^Nl~ejDZZl~mbUfq4)$!e};Er=98Hzu89 zW_oCT@*tITs?>WxAtyxA{Z|FrL4(T2?uCZb5EL0Qzp^`8ehdBQ+`mFz%gbg|&qV7zck0y$p9t}r(GL*%!g*@{ia~|`DfS;BfBWN)m7}reu-{Eh zJleMUb`0sxk5J&o-RYlnVoTo!-#WnF7pLq}4o9qqMN6kAMy1q&t~%ATIJms3@%v$_ zJ&VC`tx&tE-O1b1qtX5iQA{A|NY*RZP`oc9LvGy zx|u>NdiMSikZu_Kg8TLO=-;#3vs_aoUlyC>hJeI6yS)6zo6tmneUbw@ZM&VjVXQmv zwp$`>s=lf!rAc&ZT_XgqWWiPaB~Z(R&Tm_N?neVs7(==3JnYHhsy=|9PI@Mc)_GlN^Nx^HLM*+F7LKuEhI4goHv+faUWEQ#sFgW6;lY$f_oR`epmJCN`>BK1yuG>PO1~Ui*({{z znEcyB_U!g1;ZaTmj;1G_%ZKO4oyxdok`iu4N|qvdDQQe%t~nv6U_W4Pjc%-fQ8|0-G{a_v5K+zT? zF51kS3OzjR+scr9gnqM>HHK$(W;tp@C*l1vRqoJ*%P-E(SxhwEJ?Rpi+DtoNwI6*0Y?GHuJF}Jq1XZfLG=`=e@tfLZmoqTaGGOGV6qf%-L z4;*|^Ae>NLDl9vFz{(^i;XqyL>=k2uXx#l>)49^`^Rtg*RQ5McK5@+?jvb5DU-tvO zAw-v)YctvcN5}T|I*1L?^LLG2aaB^oyXWy0QB;u=R=dBHlu&)nE*^~D!Tu~uqpp+6 z>$N~8R5({d_$a0?H$*d0npF%oJ?aD3yS)LG&G@4Wyv_@X=JASw!{WS`o-Hxo2`P0ITo;fcxEyaJ$j}uxlGPZByO`S~N+IF_ zdQ0d2_#MmOFsdZ>^pvZrj)YIlN~!-g|GpL##lX!?F6dPTiIgZ=`-00N=+Up_?|-?i z)vEHF5gng!P3H~h4n9y%#iBKUFF!=Y^OyLd%xIcPJ(n;d4~;BwmEOy`?BMqOc!^R` z7<~1~Dd>2meq?_8<)=Ls@yWkDksZvk0UULFQNAgUej0X4UmgFJTissF<$0AVMjwv8 z>5lun5sUBU2F}ETHx5_9+Ev_gG{Hf!!W;l;`QN0{ z`8Q4+IqJDgi{)QF2j2be74t14u8VIzV!Hd7L{11T^0-_!T!4WH>ioY$vx0B&Duo7(3G$O9T;dmM))bVu$h5S6*~N zb>{emJr;+k&Cg>WoSDZSwI}iw-Hq@{VR;;dZfdza`eYGs-mh4s%>PX6l1!^q_aC+* zex7nb+=Rqj#(Y|DOW)&95k&-tEEIRY6k;DUahMoS-Q?t6t^4N3XXccfP4tQSYCotc zz4(nOjcYJ!`R@V05fh59FyoZ_{c5+Hhs0w{l`@}!K2Z_Yr*kv7SL>XxttX)if*i2x zPt|jyT#p$lR@OnD~_auKkyR(9uHO^*D_=V%3{x<ct zli9Br!ZEbsQg&$UO^#C_xOI$?Tn$gY&&h+ro$B$fSTgLa9HviBR_?>giHs9H)B>zo}lPjzYS#Gr^H(rFOw&2v) z1EV;R1THfaw3;<6+&+vpjy*j--mZz5FM+W?A3nZ3I+x`UG>spVc?P=BXs{U6BUS=b zK0h$m5xo~f;deHcZ?4y9mM?8_yic0rHCk+j$i#Ze>s&Q*oHOdAoxI2z4TJ`-7XS$| ze%%C;@jpiNPsmC*yxG?gTsu+iWt~%ZXDh9@>w!nNuJ<{4%JHJ9>Wmh>X@`o7TQtuI zhrJ!wiAvk-`qUk=#6e)MdYjt(j-6`$i;U%+PTV;mf8s6`C4?Y>+zYO{|43mp3Km-Ohb*SS) z8>yD6;fJ-ZUQ2^EvQKvWvvX(feI+9G8NWtFPrG}+n7V7p|M~07gLt8l%A+IWG!g=` z4QLZtbEK>mMd0ojO0w`#Y(u`b$Wu)<)rIb?%L`S3^PQz0yTMJJs}cMm+0LBoB*70A zU(-srN}YBzWg!VFp0USiDmZEa=Paciy>k?&*QJ3J1>kR%$^@;~im?rB9m~ z`5h}OqYSt}X8zS|xQ9>3c^s7;@@?Nc(3PbAqLV9gKID+uKNq;`wl76QW_lD~8_C{$ zXJvIOrK3aku|KM3CcmbW1h|qV^=HzWGeLEYk}9O+*&%lNnePB%dWUZW;GN z%0JArqNEZp)017x9?Cm}`UQPe!sHY3I$V7X^?cOgzByR`1bMvQ4(B|{Z#Yu#sadI~ z4@Qdpqf3uE*i!x1g|w6+GaZH~MDH2h8}Ukrh=}qP#aNh_G~$pI@g&&s>iM(-x%G>7QCvUNo6fBX`dY8KQs;0~F#L%|Q(A5d6Z`m< zPc{V3Jc;k2?XOIG&X?NCdEyahO4}mcr~{oMYfg)88G(i5!&3hRjKI!MFUW~jth7BZ z9OOuClV3?Lu(gC-R7dGM@GChkH~c3MIlWDZ#-d*F~$DO!z*rG{-33t^+@4j(}}T#^=4k*f`x^_(o|t!76)=Jm4H}Mam*@| zDrjw~4Gj(F-REAgSQPjjo5&3R6x z>}s8NUUl^Zd~lYQBdS@-T4=AV)(WT~&9c`nx z6TmGnfR}h}&CS|vR;H*_tR@BCYt!nZQCQN2; zuGVMh!-&_DUnyc@S`DdAkIO8ba+ZDaK3>eY)@E@sT)w#H|7nd;pgVJN&>8t>C$bDTiq zENzLrw&>Yo8z-mqs+IoQ20j7bOEguaX9BBD9d5JO{nofEGH&T|E}om7R6+vU%}=(@ z+EJ^nVU?BLUO8M=P4q51p?lD?NcSMjHEu`9GvCP2W82S4%({6iE>(A^UNQAPSuhT~ zP#U_N@?7G*d77NV(YUsMH)vQux9}stnsdSx_9Fmtx_N!vKUn*LyLfVSq=lY{|9h`! zqj(6O0w1HwgY@(BeGIk`&(F~CHUUw}hVab)96S1={Qpkf8(~RuA@KgJ6{dOpC?;7A&1sP-qBqey3sT^x@uG>HS;dui=IrKg)k$ zU~LyV9sH_q1G75{-pUL7+HAAY@l|AnM`|PgKZJ5c?ah-3ZwyNqv z$MQHi__zz|(k?Sc(;-#pP3FPL*({-y+mwO&n}zK$lt&SiVd4z3&0!zD3@)bqtf}^6 zpPA7)JsC&3GrTFb>qiRzy-BP^{#B)=&YNc)?L7Nq=2KI(U|8>F)_5@90)1Ku?RAwe zE$oj0cG^BO`jVS0m2YGN$!S5Jmc!0d>Aq+S}mnGv9&v(bc9a?VbxTHK*j2Rns0DYoDi9A zw3ur;+%`OFY~K9UDD)WT_WGZwLSxZm-!(U!Luc0ZQQ{I|bsf7^mHS@C+B}|qQTGf; zbsFJ&{i&kqn!=L;*>{_r1aqyfv*Xiu1VSk!i@QEm)$RjDi0n`5B*JoY8^pTTW34C5 zZ2R5{=0B=KE^CQgvK61}=}mN}N%Yg@rf<@h?_Fr|2N<@un5S&cXgF_z<1&O0!YXnN zTvC$~^{;oo`VAWG`DY(#qq9sm>64A$yT1`<98>z9IGei0a(5mA4{F3~2V6%;`si>e zP0*Xrf?B-hqaP3ko3*znJMYqR@?~3oz6h@KQApJDs+=EpnNu4(UG$YYlO`diF4r@B zG`x%R9nPQRySPUPL9kp(pVO=d?%Rnxt{YEsRFVqv5c`MDs%mSnw+~&`AEUqP z7^*#;Ehv^ys%+LOkI*WlfBsr_36oYB=ua*eQe@}NBz{L7dNa4O!XO*|YH2f#xRC$2A+av7h?j6gn$WkaJ`5XEaDk@t^W? zqGGKtXnA~d_3`E#yAO#R7PQ}e`MvzayHhJlWy;LaL9+011lrDk2e&Kxo}fWbu1H^A ztsBj`o`%)x2L*uQiRcxh6{Gdzuv?Juma*1b0>zU$E8#g zBG!1S!Jqfsq8+~!hpcWYMMuxsI6f|Z_r_HnyK_B)Qo`O0vDx`<3dhOLiJrb0a*NN( z&c2lOg`v^h?V{&QkKa$Xmzi-)uC}Uu8{2)2kk`c7#lU`m{XK~J@=x9Unwq1{tz!by zuxW*5Y-tU$RO1Zoii$dhb8*Gn+OA}!kq!kWefIozQvaBX+?GEnf4;h}yskMk@ss$S zkIeo@bDmD+g+OCWkdS)-hV04JDl1X-+FBN+sMlN7#!293kWL54Xgu`rWraqHh>IRB zE=Fd&i7_r998^xg4<5<;!ZBunOg+EaDDd!qtDOqWgE)F6k2{rZ^ z0_2W!)e{~AHXm*y3j?Gu4yz+C&M}`dSeR|@J<7C3#l$3CSxMi&s-C6K2E$BU5F=0C zkxs%zsKG!<$e$(&Swl6_8bMvdhK<{kxcj!#Aw*Ipzv+qByA;9Vys*Mt=d(x*tgWV+ zlkZa|)$7bPBinA$(p@ZeAf8KtS##gDxDrra`t8N{!e>gMtm8l8WD zVUf_ltij|)jiJ^SrVLmnrHn7M3WIncOl`cO!EL6!wS_IGqO07=u~GkbyO)U(Zv*vc z_k1Rr_-#dSCigmFk9t=y5R&3WYhJ~EX(waJFMiXIJxAEHD*NbkxJf5mauBYmA)u-w z5PxrUiYtSV&_?_WXq%D#DwIal4vg9=6+PANLJbAs=FsTl^0Npn2!_3N^~_9FR?(cz zbjyfOzz0vZhc`E@JNtX=#eNN0;SZT7zWYe$aYn)xLNgxv-lf#Is|YnlfpwLRLPOom z7(4RDHX8Gw7>(5)^n`~ zp}6SjUaPW+%8M(91xc^dLaEysUp~^8$FxifEOGKkdK`~OU6i+9T={Wy)V~WI>5EJL zUs;FCG1Y%YIDizRc0^o{k5>cQ5f-##HPRW)40+wgrJyH!jdg73bq1R@0?98oqgddkf@y!$GLGYrnjtKKl#X)vDlZW<2H+Lu)|+ zSLAdLcO@)r>xpu1B(CXoyEu5eDkMk3ze?gE-2H0~0k{FJ)LT>LOW1g18+~iZ@Cp;96waBN>&c^MTRQVN@K05!*=L;*{KT5fL|yU zHbqbOhj4}@QK4_?&#wvQ6H3%gXe69+T8whW304%6#KnUF>N;VW`}GWW=}u?_WN^>= z_n1w0l#{aET#F|(;Vmqf}2aPeW09qdYp$!CGfErJYBx!?XBm+9 z*BRF%kI+57t~Tq63M5|XK!+yKH#_l&4su}~`HyR5xgFA16%}w{WMjvqCH<-RT~{?G zaB!GUwikW>&O6Oczn3Xv53b`*9N2)^rZ&DaWQ%r_lnj1Jk80B5^-N#&mk$BB5GxI| zX@bCH0>gMrB@!^NWY20-4O!B5n-cG>EhqpX4>B6}G==E!cbnRy96c1(Ui+>_6G(CcLP=S1E3!OaD8hFDHA!;-4fm3|F!^Hf3@73 zFKbxuuRyA5+Cm*#UYRRc0tCTC8?8i}`H@p~Q^b|ktIZ2M$@X^*-l|2dMxIShLVg;6 z+gxY#f1`KLqLM)vm*H{uue-DTxl`VBfh;cb+c1a+B|*ZZ?zcnPMVn?batlvoaz~2~ z21DwYmlkJb&t-9rZxuPhKw>R6Uik_CITvmBI$_6PI7qrlId6zIUaH6cF5+n9waW8^ zTAmF*$0umoCQ-^Hfm85xG_DE>4yJ`=`KFD|B*bI*m#Q0L(>6^}Tik%DmZEmi4c9qV zTXS&$Tu*n@+uXSEIcGrP1@1CZ|Kq1hJ`*tR9S?{i)^1K0?0{7h`*wuXh%61 zPy17&I=sM}`$cu(U3n^cCyL9+4TSlp*fWZZLwCc4RKN-Z1X1tSh8@wJf>QtkWd8FH zgB;x&NPoS?r#SFtA~zYvC)r;R3O`(35ZXIj=Ud(9V+r0$p=S*JI&XjyE-!C`SZl5J zXd_@i8Ft_{RZ!LxKqkdgR6~pM|6MV(O{3!9tBoZjWFcn<|IR1|)_Nqj&LOq*+`Zc0 z4k7U0)U38m2YDQIa;t4;{f6FfQ9klvuaWxd376Ao*{tKcdb{T7;QZZ$#F3Zf{N4Y(EKxs5D7uH7gKPL4#B_a6WC^ME`=-lL z0gPmsk(nvq9EP`om0xsx&|3OgEvaEGwq5N|cAq3hubi(Yua6@}b2hKw{FYBkgHKIs z|Ehj3Y-u>)QRszw9N-o?WoP!DA~YxXRkgOgCG^d)Wh`MS*v>Dib6u-0fXzm`O_cjH^y098ZHF?V{Ux+vveOm ztX4|E&vR@$<}OUT@G#P zyzM)?-IVmHNvLAuys=D_zcdT+mE^EdEDE@aMq!dqOsuYoM@#2w07z?)jz5TiZzsnnMq^sg zrzZ9bqrHutN1$7IpvfalbCRf69<+Bs&vrJb`S@x-99iJV4Na=4Ezj6YKV-BM^vq99 zegHkh_1u?GRrYAvl)Skkn26Sz7IYcRcG{T`fEH3t^~(!g;I+H5D=Sl>=F*C% zero#y?h&D%)Eh1c4elo)x`+C<`Tt}3=>PX@wP%i3Hy}SN{i}8dta_uLqkR8CA+LDq zM+mBDuHa05e}pyTah6W^ZlqYEP)b;@bTePdI>&19oosVJ(YI_9&i90NjN+{eoHxc& zAjp&TJ61-fz2r%HR~DgJ#_}N%Dznu8sFG%HCME?ckL@qv^aFQf!YMT1OTIEN$LV0C zt5h++*!e35z`7Yl6h)*~R6L?br`L>LBRU0$sA!LKMHIQJ_vd272YpM(v6_ERun=If>c?JxPdf=UCO%#4zm>m3TmGbWV=(zNL&u=gw0crCY z{_lzFPHl%bLcE%Fm6A!{+T=ZLQ1S#$L-?+~zT5IsQmo}haU@n*tsCU;{ZXIU1=X%t zd|!X%(d1XZ9rg>zZ0v&No}fnl^gDKNZ9u-O4zs-3-bZJhu9}wllptftXpyh?fnovgLb7sqBHd- ztLm;CV)1FM{t=!OC}PDDI}pK?W=FD%=R9lFd$*bb6$98W*uVOYS@uy)v!#Q?G+A<- zw!rZe&NauEX{u0`nTp2Kx+(p=rfS->{w7#!a^;~8A5DPnyvrhNv{#>o=x6K%eCUcU z*Y0|AjPxlA!b`^**di%- zN@@dcUxznjA8p7z-Wlby-smS&O3N`A+jR~M{1KH2llXwYn6mUY?CRRXNWc0#)V79I zVrks2`q3jn*+fj*M_|SQO-qC1_r&6S(j(3Bl3-94M8oCB!%L^<)sXYK892^R|4;^L zJiwC43JMQLdzW?~_Vy?-2a8EtI|T1KVW5#B3Rq~ti_P3lC+9zP?XoBNAYIjxuK8`7z*>MT0HEocvw{OX z1I$YH;%F`Yk9!X6htBK_V7{x3o}=By-yU){9H8%0MfyZfPoJvopof>>{w6b+hon(u z)4;mM^-2S$`g{OrNE}UxwD_s3&UNGi=-uD2H>~P_u8pFIol;C)(bADXIE6T{nYfs^ zz&=UHwH%@>(kJLw03V-?P-ah7;!#x_`4&0o^3+`q2cRs;jTTmYsoWPmRfAGzObXb` zBhC6!+nevR)v~gH-u<5-P0W^Np8|NicrMhP z4T@2KMcVKVkB+ir`Gs6jYfkd2sgAvsai?3?gxr_rIb{~qT;kpp+L1GtI%PIB{5mOM zU@1rfcw}}(ACsXqi#>SlxCk3*t!N1AO@zTUCxod%IWCD3USyly9uW}3moij}I0yS> zk$qK|u(SW}hEGj*Y>ju=Y0AXWc6Vw9THeZ_bS@S+?Cfmop8UgUs4|R&B?NS<}k@VW1lVa5~kY^a^1DdGl;oFe7UJKzmqV49C)zCwXe z7jLfh9KJf6uz0Y&Q-JpXnT3GlRwGV?QXzjitCY%K+`}D|A`j9ClvN%MLDKk+OF4g( z&&5bUt&}hoBdeQ)OQgRFJjDC@<&5Lpu!W#c zu&sKUY}&&It!=w|Ys-#z@xS-QWUL+q1&InuBu-;&svm5|=rGqpmICj}v{dgSNm5_- z{*=7eSr50}S734Ut7s0#j*F2vy^+2?KKiVKzmjqf$>--Qx0NHa=A2~SS*C44cHVS9 z+O%&6X40>rFq*(|cee)nXzKV&qZygN3msMGDS*pv7+O*)Xa?M6joEsg9f&JYn`?~E zRoAq~idpWtR|K4bbh`hoT0qPAMBJjrk$aqNUI<=_?2gH|noycL?N2VZUQ!Io57KW7 zOUsCf3k2#-K{al>(T!Q}OAv@PSv(FjoIiHze8ODrO{?_XElRHHCNTtl%7+{1d0y-< z{K;#+>!Ja|FZZhBC47U6F8 z_co>1?Jw4b*3|{<3;+|3am)_z6F$-^rBpw26(I@B@2lsU+5&;KL8W+^a=7zkWA;nf zujrVgbE&_pj~~>tM|HaTKJ`Ooh~zo}*)LJG(AWzdA8iMapZ=|T)+P$K5&c@T{T0jA-di#TKX>bw(zEo;v{eu=!mkynti|8SdJ5~GSt zmYqzr{j#0kn>_ik*_zAYYk7JRGoEBArbOFfk1R>k*S`EuNm>RJK@UHN1HC4%RXFK3 z_A7JQSF2q@7bC;Nr)L>cB7uQ&Tb1T=>Eo>LPv>mC=$eccQHyKQ+|L0Y6g_pw9(R0;8MqkN)Q{WH>oTLt^q| z^!Og(RrW&kKv}CLI9{? zd`VWmtP;i}zDsj46IPq8cd-%qut9a)PA;E3xG|p3xwBBT)~rrT87=^w@{)YdZhY$j zW(l%>E+9=FpV3AY2xm&GV&m~#B=7mG?|c7IQv1+fYt2o}l-9{=@%*jvClGmt#jjq? zO~Ho!_m3$;1{Y~QHMnl($=^9im`D8r*Ve`S=dYG`l!6!f9)72X)C*53;l($m^Pqvg z_g5IWxZMpOzK-uZpR%r7doF(2oOauIpgU)z5o_Jj)6$RqvbFiWZlEoXZi4mnS#rf~ zbJ8jIZmR)w=Mdoq6HPS;Y=XO|cn9s5=GOv99i&h@SI0g@j`lJ)aC_%zioff9HHYG$ z`Ygz2ZC3|sRw7Pee}}aD_SIwPBn%Vygu;hSjGfX}X6|yrF*A&-Qs;8(Xt*DQQ4L z44J%!FO~^>A}%Bv>-k+k_Z;}ad7YNAUoEikaRfgiuo3Y9SME>6MGUS@ngesd3Afg~ zL=e-cuJ5H;KvLn3+eF9l+%BCW?BbS{G^noF zSjS|&tM{F+@8G+px`ZIV#XW$X! zZ~n=hrxBYw_*bfM)n=G_-+Xp_flr-G@@4WLjROeZtS_QpeSY9KuG~E9TlKwzRA(Q%v zGoD3>zSeU2Nj?*!v%NVfe?&!xPF-7<`^>288zUR`Mhl%c-|LyG$A#3{8b>B? ztIqjHV@0(!YH4>1_l`{9LB(6?`p^tHYes#uXc3TfMrz+UzM3}o_ag0G`d%S49#MJ7 zDXMpNL2Hy|uKB3Gb=D(@F%-3djeoE?$$0%i$MMa<0vp!!(CJv3S>cXLi3I+QPwBp% z4NSWIHf}sZJ33cg)Hpvs<=3#bpWMs;8jvUcokYka1RUaTjkB<%wnpGK!tSG5o9ZC`RL~v4IU^5I0qA%K zC-(wImn|m?97~{|j^&Bcy6z6ze}YOda5-u4-?Q5NDskBP*ml!y?m73;aDzl)}B9Uw=QEO2;+#8RZl{P*J(*jKK^IOnfc+UVq_2`JUeS^w^mg-&t`Tp{;d% zo9Gx#mV3N*IC*U0iva|znra*#-29*^^O<0|D`Up~%D$!@=<~}9WyR|wf;=_Z;rZ?t zZLfd{3WrjN*@bK1W(s{uqm~VJ+Ui#h*Lh>1pkNe&ghqj1nk9^;qXdu}L|Dh;*8x`W z`DT@}J5QBYB2Awn?6utY>~A8CHqI0N=1^tFyt$3H6l92h5UXVV?b*qY!%jT+te_RK zi|%2C&jf2znn6%NKerJa;p$il9avq_cyJmg`|A$ID%xsAu+2ah)|YC2jxv3h8wZZ7 zF+1_E+m2zX@i~4VEbD;NhgjzAb5epd+Jym55+uxm#9H($i#^8*;-bN%nEbl=Mq8gB ztPZ?7K0T~Wt(Fvl0k&L$u-JDkGu@9q!HqY1Y-LY8Zy6Phu0Iv)-}S1bPD?k4 zMPScZy&7$ZU0_{2FkM3e| zb(;N{?Nw%mBh#QNXiFC^E+wD+@BIZNbWc(B7k$D)^hYI0pg)CvrZs&j*6%HUpwlUARhW-J+%oCGG8 zQ_)++MvD!#R2k5WA_Yk}W2*$_G=!3eY$jC&C;6B8MiU9b3|)!AP1I;8@pv+qmZ4n#oN8D(L{s<8vQaBhsG${Zju~7w9E=TlT}D?wEdj zjskK=bK(oGQ?Kg`eH-V(3A{cIkQ+HSHkUb&kK|ZP-kmT9xZ{~nzqvuJ&%e#EzpAsP zuwBRF`^zPBGP}m+@UqP0QaAv9CYpo$sd|Xh>W+?6D90d`SdNeZt0J5V^)wUnadgZV zE|!eGho0*go?+itX;*Z3*#?hAKUFhUl*!PSnJ_fH5`C(aKshry>DZ^pRu6XyILYO} zt^Sid%i&qu5+^0@@{%Lv|IqdpP*t^mwzgpOAu0)*xS8Zqt;CMuEO`(;_9F45m$a9){Q1kCqooMGLi+F>)pkEgF|n(LUZ{$ zOHCb^-y^6}`LacaN8_Ll9B`9jr{w$CTXP9=EV*B~N$x5t zFqA^(ezxncL3zs8EeRfyL=L##7gSCTCHJ$(Z277JpX0u%qTthb1-^rkm5PypC8z2_ zrY27&q$n$v7duK(T!!fHHqV!peVTT%mEi7fb=R(<%f@NOoJ&*F%hNhROWc7|H9T7j z>E2Y8##kVu!U_P5hvWE5ctJ-@pH0v``X5LhPbPd5(GD9(RrojBkI!fl%zIVHA&MsG zVTwZc_Hy2wb;D1UOp=>h3+9ZFJJ^}lKosKzqhCr#qZ##?CT4>+t%#d+=2NH|TUqnp zdKy(}Pn?1E(qtMdB9OPdi$3B^1DkkU9+wuQaC?N+dy1r*TxgEJ-Dc(Cqr{D$xRU)= zNsDeP91oYMIFCzoUh2|+mPr^gyxoReTD-$Lv5&`M`Ic^U4EA3aqUyYvVg8XjrLfqA zB5#{)N$Ah_BEv=A!tl!J^auwGE9v^%)B;;?i=X!QD^_O3hFeZ;yZX3-v5av9Z0WCG z+!bzW#AtcMPGoPLST8HMtM}H6-=VSS@>DR^zH5|<c!@Lp$~piot?=5+Mhhjr0H5O_|BQ95C1uBL@5MWRcEMXPfN%u zIB=R%{}x5~^V)4F$6ID#5jGdR(uBfaXzOD2*^<{(Sj%T+jz55Y{(gsaAUR5hK?z{LN{xuj|v zGk~_UbUI&UJ7pSHlFZa2`<<6a$KerToNK@Y z5PjOM2&jgnpPQ(Uw}~Chnv|Kiv3mBsA^()h`8vd0#W#s`)w#5?db zEpH)B>$AX)R-yA%HGA5YA7;C=Ik>F~XU8@t#!*-woV9hG`486Sg|9vex9Y(a;`2Pf z{e1G}V3hn+x<{UIx+IL;h``T#l1rCpD67GUIwSqN&_o&L&PKnf^U`*rcHnuH#~{3?gokJeq*|2|jT1kF(=Sh`K>Dv*@;0@01#E#bjo=_s9$Tt6zth|y zGZrXWG|mZ7$}Q5}WuTt+x>)e>(rtnB5ZJD~6ppJd9RWo5atkKV-1Y3vb=wIb_vi)R zo*xFCXQuKR0Je2vdV?sj-pYo!q$%Zd|8#7qa}{*G(RYfHHr)NffU-J!x0-<@!R4CvUYrQhUy&;hP$KV4=C&y#1(mStWc+ zShM}wjWKkQxS6&xeS%h3RU^lAvfvCBWnk{kJ|HAWiKb-pB(mrIt=={!eYa z)?)t*e!C*svn#kmDRqu~OtFlu(7fSWj~@GY^wVhsi)Pt z!MmF0GTmOE`Pfglq%$UV%e9b4E7Iuw-JuK!VbsYf3l9C=XPuHG2Tq0F?!>HxQ_W4h zt9Te|UW)>dUL@n20l}+8@5@bCn0{_$nCNFS6@P5o$Z1= zrT*-8)CFoR28t7CN=F-DEod~on1Gzd1dE541Vx^S$0hwVspt_6h+=n#f9n+6-hf^L zj&hK(lGNP;iAq%&Jh$W|WO2dOtFKG2pr*-5J=AbATGrr8Gj=}ewHw|oPYkofUe_?; zI3z>#cJVk+=X{K-elVQ4<%vyb-mp!YO5~?ZVw=Po$#igYg3GGS9Rz-U4WJW8< zRSZfdx;2b9GAK%6Y&vs;(<-7%gQhSZ;?gL+ z#tSlB`H0rsn_F}oxYmo1>rIy}3m+o-Y-nxLv5R>0MNS!$TZToigQdXXJ^XyWQA-)v6D>?d+zli}+GVt4QYN#L;^_>`lpYzcxv)O0)}iBKl&4 ztA_{2^oo@{Yl_uo<=8lc$cZkZfg^D_LWybS0n`%5$IXNwq%0zOx_X1D8e02#6<%My zIc99Es}S!V%Vtzl_q*ypbiN*frr+nh_j>+(^-5MTiOo$YN6v9Tm#Vn9X;}2hv+sw~ zJ&d~k<8e8Y5;>!IcvBPX*?$sy;Az*}W!5g*c9k&lj@^MLFP zNYf0*?CPm+qPy-z5M8wM%}wN|fY%?Y%A2PYSOffW>Kv?rACZ@jkDUowlk%z~7jUn4 zt;CT5X`iH`8m6XUVI7@NJ8;8y#TPI@Q>=o7OZDt37yPCIu#C$(Bu>0U!=Q-=n$rw+I ze7|PM2F6ZWquPXLg3nvE6S1yNl6~n_3K{5;lHMKN1PYOw>M$~XKQ9j#2J;f={u^D7 zZJ$R~7X3JnFFuC9j3 z7J;S2)sdKPXG|;p8)CPurlyVlx^C98gH_yv(#r4)^4JG$k@3!ncm-I?MME7d+D#G0MWb&t&Ep^Db9yg0E1{c1YZ)cd zd@&>E#6y5!J|>cJIi5!;kM3T%SKb#F~_xo#O_i?cYgrf34T&)RwiA8?D+fsI|xr;40yl2gKkJk<)dKrJNW z^oW|Ntt>KVr4xKUax$~{o(!Sx>Jfwb&vkjt)QB&q{ z#V*&84lu&03YE@gXd#~IEre(YA$lAN6DY)|Qh8mkwCH zk@Ly$p1sI%B_|C&Ix+XtV4yAw4zU#psN{^LU9C+n``Sw^n8(ClV!fj%^NF&8E{NS+ z@&OHgvOLCi7`tTW7Q@25&xiDRK@2G=`^Upup*&mv)%xfM|_`%}QXUWiNd43Y<;Be-<{59w+nE!s z^*lk5;#SPI1TOab%PxIDHL$n(5sS_wd5eWnF1SI=U0c}6OPs4>$Qy{4G>KQBK>iGt$ZlQYUZvCHhA%}Uyc?ft zOg9{AD*)+w3k<|JC<0@|enc2*=q*h#NfKCKgN5b2wavLl{W@8uNm686=N2+jB{U4f zhw~FKhqZ0)!&BLS3E#9{+;^_28xbbCpor+mwjSlhU!5*2r4@N%tae0%wE*o-kr53Blk1qzsrQ3h$_? zj(3GP(A`ftDAY@-O>E6E=Zo2x+8o{RBof-N#*C~ZEgGnB%fii41#*t?&Wta(DftU+ z{P@Ncn0r`C=3tR|!lxjd@a~m$bnKxFT=H$5)a)3ee2MJer|7lIpF{pT zq`V(X?kh*d?#|^7k|=SeZL{3ehwqz@aLkf=SyWY(8p|`Ff%2pMVlrX@2>g*CY2AUU z9ZBVUDE9^m7OjG~YWbi-xL(Zh4e$qfAB(!?FxM9trY<`)rR2AOS53o6o zx6et;%&nJ4b#``bRu|ru!v?ev7EYZXV+Ia?d0Wof0qvZjsl|=S@|;HnMdFPiN+@ZW z;0~V+IH-({-@2tKu{H|;j%}3xyBS{n_tOZ0+B|2g9QbesXicZdEveq{k-X{!)Y%;gXC39LIC+Geh1Ujim~JACSZd3IY6UA zehTQ(GoTe~=NSN0kP)nDz*rin6VF%Uo{%KD2s;QzYpXQzr5cK*+J`y6Cu2h6-JgdZRtRj2hRIAzxVH5cbj#s zrG-YdAQX)!Nd*WkgLiu(Dc6v%zVM1}T<*H?)Qa|($syAhCVuFSpGdp}r{DIFPPsIE_VHHzl&4F4zqFy&J<#)nU1(T20~&P|EL@T!Tv?u#MSn4QnKKSTM#Oek*Fd$Q zr#AH1a$2!tVQgoVf-oM&=RNRAHG_I=EmUlGZNf4L5Z;z^DjiLRvo{aH^A1c;ewZ)) zz4mz5zHiUy)>xXr=IovMh#7Q@%~$sFG{JxDn1}~!yK7iciKYvoHa+K_J{UzspRE%% zlK7+Eh0o4?W*>_G-aOz9VEoyIf^p{u3+mUqEnbyjT#i>WjdSD$#k9jjuS+9Mm453L zD{E`R*M zZoLU`52^GZ4992#3_`z-XITue`kYe=UC%%n%yF;XSrRdb6Ijf1>oY$*R1^CSzj?M1 z=sZ(WQPet@uDuG@Y-9grt%r$Z*3#nozJOxwkK-s->XlfA2gImYQHh)oZe(w&dM(j& z!8lP&KRyYL42khmY7yTD8MtwQ1on4HB|(>3>ouhRdSRy3_A>HnQChqKZIH`0 zCH!p1{OtRSK-{}ed0KFRdlZ9~XM_X9oNgmSw_hzHiP`yrf&T@l<`|f&qEl7m!Q|N< zy1QBgy^<YG{5;yVXr3d4K7)W?__^cHg*NXJlDNn|W6oU`{y-c=ccffCkKLqXsAMStk3gabl(l~eUmJnYO; zo_JmE<&uYqIERl!X5GCP;!gO$^=Q}AeFO)K?D-v7`jFH8gKgkB?W^`$X34oGcqzH& zQS?8c3Zun8g(4VR-Vm_hukbXW%mIKx4h!2R^8LD>y13U$8(XUDcRiGZ>XJBG$XQQM z+zMnC$+L!|%s@3>=6s^wd~%AUV0jL*9^k-Egc4+>F>jXKeiYP(I6QoeMCq(hlB|m5 z{m*8fm)_sI^mIchq^4i)X5OexkWJBh%9Zq1k|YWSz8%HK7nR1kE3YlHY60fneefle z^6C`=YY%LS_~9;Gadds&wD$rfkviHnoCnEEoaVvy#9t{z$xv>lA^Nw2qtZ)>T$<;} z6!j%G;K*MXC9uj14Mis6w>~<~!hWyeT7L+{E>9%{YBIjU)qIwgQPHmk;uD^>1^buI6Z4b%Tgl_Nlz)M05>%HN2*OSHY zrQHd{(HI&qApyKLfE2b@W7^kwIiCttylzKxP2@kh2Re3MI1j{9Sk#C%uV%QFQ!PPZ1ru5nBcN_r(W{YQ7zOu(!_|>p#I-^3Kzrg z{PTmLwB;_VN4K5w)DBJfs-cGNgBe088f-M^KK^ln*#Q~j=41WBb>KoTPCWPXjo> z=S%Ly#DxUFY&MgJhx#@Un^47854Snw1mRIEPHf)R<1SKdB_D0s=l!5D{e`Gy9`{O415W?*$8e9gBMX+MMu(_i3FXM~vkJ{YWdLTNoZUo#w&`~aJ zSBAOx-dz`7L%+`seiGg#q*#QpFPGCFd20sVx3wWLt1{3%LB(kN-4)-_+5JA0p7!rC zfCI7fIyhe6-}WiFr+W*6=FaF)HS=yqV!-fumowVnW*A&*L;jE2>vC zvD$*t)YR@B9$WihvZm!gI>rC7qEtSLDAUlNUGefMc~t;I4p>z)9^O_hp{ zQ&E03o`b&*aH-T4hipvK0d&|r{|wzf1K5^;20OPH;MEmO!B}0g(O;^$xhD;jF9HY= zOIpbus1S<3TlfFt?6Z~(KdDF{72U4+b4Skfl^-yxTcrl2Gpugr9z1y@JIM7_&s-&5 zr!E23+}>*f_SC11P1vyI!af0PGmpk?gcOk-0O#oRRYk40U$JQOc#Q8Z8gmjlkb;9b zkS;>mZG;NUlO&}|fMuwBddq0pMM`u6Ww z^(njDcc41E&*>whFG-31>VnOCB@zTVP21&uX?Vy;P>|AJPhK)k4o}Wqc^P3iW`%-3 zVEvzI!WZF#D0AwZ3Uwkg$DV@?W(jf}GHnUH!)X51xXd*S7fCGrcepa-lo#eDlAHb5MQ?0Ib2J1j?UxFgce)P^ySO+jtEZVzez9%(3AuD=!D+xq=OfE|7c|6Mslob^4pNKi6z&k~dgm0sPbaep|R1lq>qCgP5{R6n7WyOaj)5g>Um|CM zh*f_2$$rymE{BSK!PwB+#UTy2=o4s^R-&Fx{*S685d4~&8>KZ%u$}n}>bu zc_w}b)ETjciTOG83 zmnA@=9rlELXd+GVm47e*I<^SCp9DjkQVz?YXYnKKFPfJ1ndVP>Z2@wRm%Xe;lA2$>}V2 z`%rhipb7YQ+y!6ILq9aZ4W|QpYs3*Ho zJUqX~%NEs)MqZUoHvlNj!HveW2kArTXT>73u{b2 zG^9mj8GQVF(&(frr|EvJ1BB6w)Bjt`1V~^CAFnQ-n?#WExG?jYF_^7icH}=;tdEX$ z69W`uOVq-~7q!1HucD}-(+iaLc88dgDFK9%28Sz0yhhLE#+z<;#8YN)VojoXWwL>g#QA+{~&Q+)!TK> z8Zb!J+uI`I_P`krAPA>32r9hei=@v#&+Luq-Ud&+>wLrqup-V+LgTZ~kE6S0xI7>9 zQ5{S3I??bXjbQik3{IchG5Qt)9FY7td6db7t%Xts8ysk+EMsMEF2u3iv#&M+Ks9UC zag|d(d64IDDwX4NkL3p43Ruzp?7Q8*Ht!5X6CyCBf#sH+AN$F93rq>bAI0GO91S|U zWwAvby(xiE&aW?PH-AaCckMaTJ`T5psqNTb*ie50l_>#GnF5k%hsMAr`1-e|63xoX z^tlRH0l{wChz&%Kic!g_{{ORX-)KdsRgZ`A$#DxtIS2GN1QbC2zM^egi=+%t)H%RZ zRzFu;vESF)$i<|Km{mrniID7a{RvZ;5#{{`Fv;U%aId>Al--a6Zjcn!R$&l$JL%>1 z_!y7|uHMVOC`XB4PSD}lE4$B{o`oM*)$VTr0G7bb5ay#bN10O0c^39JhZY5lWI&g; z5npXfoRp6>MLz-o%tC=EP+&U;fwZp!o#guSN6pS4%YU$0$9>$D^Vpjqkt!62D60wB zED=9n6xf44n7-J3KuvDOK`V2#Ilel2l2wSu7aZvy998RMT{#NgC_a=oMZLIEr7~hW zcbfPWg&L@SnW20J8v!}CzRy-+?n}wTh@Cjp6JIq`TM7I4{%@OBy}FK96x@w%yRm zO(#!fHhGIqPL=m)<<74hZ7L@u{s!hk1b<06vVS!}wE69HH53SdcU|=w`ynx)P_4%M z_dwc?X#jf@M9`MoE7zYw@Uzk`g1GtO^}0r_ByrD)5eWB%$UEKoV(?R>(6$2JrYHft zMGw(Hs@-zv#j{_&+uf)r4!)8@ZsvLjZU@!sf|1&*3#`4hSucSdR`9Fgg#rJ)V!wN; z5!)9aLKk2>`KTYKW(hYyty$gKX7!4qu^#u8@{dB!4yu%Nxc}k%x`6V5+{guR)lW!3 zp2T}ay%s-mv275^k{F$te5aV6k?=T6K8C^v$Zcs_n?`xJuSvM9OnP=u8-7R{N8<)b+HG2fTP zaD~+NxtRg0k~>f>nZ>9-?g=qZ-nhMi0%@Z6QY(!G3G&{G=yM1Uv zJ*l2Dm1PhB5d$m|e7E%7X_1ob4f4)(xFyxOOuSh-RJ-+V`-cUMOLOE-HG>OlRBSnD z`!rpPTUE4vJ{fG-b#T(-AVY?>-1)VeKH^6xY*RWo5?#wcKno`QNAlk(_zDs;Tj*n_ z3R|)Hn(X$`%f=a}iaf)j!nwr29U%$pw<-75i=SvXUuF@j@c%yv{vq@el=O^;;@PA> z`L@N4^%kaa3BNotraAWSRQD+Iy7A&KK(m{@jeSDR{$n-oOUfPGedE4TeaM^O>cfdm zBHkwnPjL}9BD_OVPf0~v)^woJKV|lSVpM15&0M<|03!Gj#KfZIt9dydUqF@h^7dew z>AUo3JDP?`ILL3lrpR{O=>|7Em=oxP+K@vOAU~tj(6Q|A=(Y%|vAa9nYAnI5-YXj( z=#&ryKRs)KvoRMQ*sRw;d-SGus?r3q6^e_jNhxc8TZvrtzg)D+Yq;)to~l<7&auRm zdO0hQYKbf6Qx`Z9C;P%@|E6k*CNSd||11r-+>mE|i2H&}zF4pI>sK5juf4m2CGP&) zY$|6Nw0og zPoF@p+uXrjAaKHse8YNpA)`Nff20R6807;D>GA)30_jMP&JOm9;9mFm@dDii+{j6?!q~Dk|Zy&l=cZ(0ao*gXk*)9!5r=Qt!$;;gUkH* zvqK6epW(0Kqe1$W@rEg5%e@$#(BH?#Jx&FG=e-B%yvX;-CGDF%U1B?tzT4)L&1mR2 zsThH1HxU>3%N<<3=71(yG@PYehOvl9$osHfsKn}I=5M9fZN0lz7v5)2-DMR~gPXWAEe0W3Uu}3eZp>fByV-w>z9hfy{-P$LzZnNT z_)5rrm4aURu<~KwJTpd-=-E>Zap|*pN(r zmI!JGDp5BqEl3RLKNVNu)O$aoD_8|0j&=5tC1O2<=cKWz2QU9?bNaRCp@(jU=D&wb zc;Cs;OWX7hpMr^tdcIEGeKadI!|S%b=$5?NT|F1smQ~%7EuJ_~8AYrQdDvNk4^KNg z;PrTwbEzISfKvId`tL$MQk*>7=~IKbl9!fXw#QD9a{caZ~v+Ob^HDUJ0=o>Rac7UX?%^z^oQzf__{;@GA?Ln8b>vD{09)tNe7pLZDC z+Jcl@8eLI>Z9J|(z&fMv7Ey*P(Y$=*-dE^uP9NXLAhpKsi&;X+*(>MHi)#4}@f#A^ z*Oi?5-d_EoeGw|cg+0?V_O~Ue%$7MMa6k1`;*#Om7)8sI?N<%O{FgR6CS}@~)@Lx<5>K z6u;kI9AzkdA*npL`Q{(R_F8Q!fZ$tFz0dG;tP-)4Za_LHA3uuspqMvV(dg>4QHa;+ zL2*JBs(*g4Wf(m426WfMahpP5Uda;T1k2(-j2J6onb*}x`*Sa;)L@}nyiR_R1wd&j zU;=S)V4Mm_g^K)L`@GI6Ah=M4G!JL*C^3^;jVR16)**Yk(`RnE`zxyGNmk;q#a5IR zmG8R9v-=#VKU@*%D4CmVESG^%q%|jhT(xEgfol!}B0f69hoE&*-axZ0d7moE(mgXC((rzM`J_-=u2^ z4n7MfwOs7wk=%V-WMt$)z(x7C=;zLq^uL&(Tlc-T$AW^{+d*{p=4%YBA8>%t_LH&y{U38 z<`@cJ3b=FU1^>ARczlBkEVj?lZ%h7;TU`_gm3|n4?OX0guEYC}Tn^Ut0zo*i1wCmC zM%W3ziQmP>3k)~fogg1jUAj?v?aEEaT2FaZbt>pw9XCo5Mu4<*;g{_%0_N^r#l3oC zd43SFVYI3>>C%Vvbsrmx{q*|Y)2HvLGO~-6fuaHgUzQ&-j{#?iain`(Q7SDUPG_>* zfeho^5`sX=nU2|Q6)w3}Qfj#XqQT_au5*7cQ%^S}7U(_DN`bn8xo8Ii2J}Rp`{5aB z|JEfQY66PeDo)r9M!ycCq&i>Gk+UXNeO7E3XYPq|k@YV}_B_)+u0zIP$;@}-{wi}{ zen}wQ7`T1SADX*i^s8xu$e_ODI~lUnt)Xc_CAHCqno+f}|KN^9m#jR9gYcZFY1ik6)TxA?0M@fq?%OphJzhrZ5W0viSURI z#K(v+d}?+speouJM)=E8AD(m=jD94d)6zTxwg26)l(yap-{z^R@V>;~@>oS}JdAKo zqw_1s=9b%@XRWRA(rz%kDM*A*Snj_3kf2`~5=zl{feFJtk~td|nA4~V6PJhfR1=z_ z-rn^E(KG+xz;FO?$fw9-GZbwJpH*>JmpUs^pX>O|xrj2@^KVpCJ^tmT+6DrBMvt26 zYlHiQCPq)ZkDTu0usX`oUk(o5m2xn2Ul=@z`RbYNV6+DnVWp#;J?@$zY0g_?uk3?*W8K@Z$azqi-CxRa}$j zOBX`bJIop7*&dUg&od%WfB56ka?F6UAj(RVw!@S)*1h%{$j&b9yeR6wG6J8$>hqi)@@D{-|f-f2bMb=L$n7+A)Fgw<*SOY!= zNR{>%;!}QF)wAL%qoSu+emhd)=qQ^Qw#L(f&6-9{Tf5lP`e>(h^3(rtHB+Gh`(L&p z#jcZ$2c?fdaBy3W{j)vaB)}mrMQ$FJ<`##3HH&cYSkyjcYpgC#1hYZu3T1p=zDjBi zh!}UX+BOD)6@4{XQ!DFIU{3pT?MC0Z?uK%Iffb0W+@FpPyA~vWl>T2mEH>KAwKQxT zpVsxtYQEL1D;b3)*}$$>c)}-KN(`fybsiF26-y%ltq@)%fFLP0%l#_R#OcYV{4x=PS@u1Zm1T4$n zq@;E!UgU$W9_-Y^l8&D!K{B$@@;83BxiRK3Wo+DiT83fCG)d7dX4~2!AQsS!&yauR z=a~@oAyY4mLX#Rg*%QKr52`-@i<;yC2$D35i9Qp-9C~4p7e3~QUv)hD>Yx}@De33} z*-F!Hq<}_$BvMkmiYhQ;BqP0}>PA@%8rt=d)N3(ifdsx0$rj%ifKZK5;ra5-I=xRy zvN$e=sBh@T$2DXnaQoYBcrv#y7bC)nN!o%xQK19E6;$o|b2FO5^Pq3;-F<&F^a35} zG&Y9%OY&OrV#FP4vWG_^O!sjV;!Ct_OqRQ!EgKvXg}04#=YoIv^SaF>Kve__GsZIGh*u*U8W5 zo|25qa)AhXr%b<;Hv%~ytO@5!mab}tL36Hj>-4F)p5oS6xi=RVYg|lCoZmp;F7P;P zNfp1uw+|g1f>fQbIb`#vt(?Dj%bnyBH*C3kwck4nkpNT)-q~86&q=grb{y~y)P*-- z)Ht~FxyVGe!H2xiA>cY{ox~_vZBUR5etXmb+Gv5YP;L?`X$-spfE3B=_ckc_&v0}4 zl|ImO!Uu*Q6u0X5$q_II_12+J>1O0-UpqGiu%4*dc89M+{})X>3_1Jrapl}(OR+-z z8^}qsn(KRl!KMQ;#_TtW)vGwEYE9RVTA0PfXLE2ir9eGnTH)_w8@!1kiM*>Dpnzti zqMM0Cwa>atjQ)Iq(j+hLe*|QyFE}PttS@5aHN(a$tXq4bomhMzeHO(^+v?r}8u;fM z09@<3TKa;GGy|T0VSD(Gjz#$SOe7%%={(n77W1Tzt;JJ$;YT^SphU1YV|Wu4?6V?+ zZTj+BXzOGEcW3)Vg6?P##f{bg%5{~3bcp$b*TxS%f*F!^K;$Oy@O%Q>dJ*%S%?t3Q zObx=*WKZ9)-!!jaCPT(J+1lFiZgV7kUe?os3%{wCN4k;tDbHjL6c>hc432JK~>HGS%+t{({ysn_&ez413rJ0nRc< z&x9;xspo<1!@x-66Qy0};$hf=P8>Z>ykb+j8q{qW+BGns7+>GrRnuIeb}gZU>XiUy zM(rh#e_N=Ydr!*4e$XoVX%$9sv_TUXA4)Fk2JtjOWz_wYjV}Fv%-Z;%i5rj3qf`3^yB|9#eDl;oYOBC8=HISti7C zFMn?Ux4wbuZwAh<#i1DP=GJxpvQ(~BXBl+V9KKY9xycE3Cfe@e$k`EyHVuDvnlck1`2 zr?=b)Pl5V4CgFi3c!I9H?pb4$ZUdQ|DG*zMO$hqgxwJ`a)E>s&2E)FSnnm_;HYrL! zEy5xTsa@9!@_rhsy|*VK>i?Tnq4v7C|0Yh6ZJowpku}b2eBFP1#MGY%!IMBFsLhWo zW@N|n4kIG#`mMNw6;ruw(Gsjr7w#2#~96q(_)77$nQL$=~q`}7*_V7Z(#}5ZS z7dL1_sE6=fPnaypHvW>@xMCQW@bgM0hhx%0t45 zLeFFH{DGp;R(QIupsZCG9{=}hyj&MV6=MDl@ol^9;7B{4-4zAb`l~Wb%oC?3IXjz0#KkByauu21!=j+#ECxg;K51x+h&oY{+S%r&8`iR- zMFgaIrV65idU`+#)atGdt>qOU52ILgpEfsbXq5Q3&-?#f-o^I{MpBe&t_K@e`gk>W zawzl+6GR)kJ(j~-tGY!bbp0W7SXDlw2y`xCX#P&y9L^mv?kI43?_ly4rmLO4FBR*c zmv8*)9Lz%bFGyXOKNrOLi?LD9mD&b#|Hc88zGgG`9e| ztofFg^q)p3@xS|qm!2Wukf1qN7A0ow1 z$YqV-!+;6aIL9%14B|-oJhAz8kLtB2U#O<8B75>l_|uloB~2XsQ&`i zXP6_riN$x3a@+uKj&|X&{hb011f|oT2}P0%Zm&&D{+{}<0@@a!G8>l`a%VGgEPy>A zyJjXE1ja^>a)Ypq?gKVoQTzOkr3t#f+>*iu`w@4d`J;cu9n|1Mn}if1^5&3xyP;mo zXr9Ni7+J9Zz`~|4wuRn_TSp@@c!6j7<~&f}0Ih+OLnK&cc7i1pcSeLVu0*1D8Pu=m z8y(B<1b+J-U>bNm()#HQE{%{o5zQA0Ro>IJt~^cfVP2uxB9I$^^xLe|5*qG|`=Lbv zMIPjxzsSKx@8<}J44R2n?{qPjmmf+#cI1!Wc~(@pKh7I5G48!x)2a;bi^rOQ>;AiY zwEru|7d#%;WZCY5yVfk}zryEF8;it^s%1T9fPxzs@oTwt4uK&Nyqz61=Yg01DYVUL z4*|?+AhytH=6W@50C{SH_<0u;;%4ox8;b%mjcf0Rost)rAbt$Yqt@iiZP$uo zo7g;bt*k(aPQqpI7;7(iRwDPtao&vqIsXSUr!cg`QQGv;KoAe0=IlYf66o;h)@-_U zoi+FL;ONM6sAIsIY*k*!SFN(`iSdtA|NQmxAWzd#MDs#oeg9bT1eHvZQSz40TMm2(WHcG3=4Kcm zsBvMn;BvrSPV6R7A=$Bs^K)ugFoGL+cyjEmOeTd-W4Rs}Be3$Cbf`oOepVumI1bHd zJ-&6e`N--|omwlR--YXR&I6KeHTwo0<+(+sxZr&Y;~Afjt|H>g!Jz z4hl07%*Yu*? zzK$vBV_?yHK$^ETF+JTAtyBd*jQVnSLXYmjq6y(nhH<9YIehzgOWlb%q=U-OD@GJi ztTui^z)={H_K{kEIRgoTELl)8{-=7@c)(mGrL6okQncxvqO_by&rkm#;4uw1v29@+ zhk>REHFfXiC^J|X?r9|#f?L4*zg)x~XA=(HOkK^6imzsA#eq~e*dpdX%`V(wL0pjE zHy*5=QZ?04qf}8vWl`n%IBlI*FJ~QR^KC9anRsN^F@0JSgMYXw6#@_y;(Zwp3Q>i| z>qLGDzjtW3K|}+jtz45^Mn)43ETc})C@M;jl~4P8CLU7@J29R)`CUNU0FW-#7{7c5 z9E4olkS{9wz`!(-VUOcZ&e;+4fOI)%_I_=EYDBVKqi1ZSTx(b+8<2TcR#$$}dcUlo zRny?Qk8C)e5M`+LxkyYacRM6NL#bk^e)&oi{P;fB@gv6k+jg<2bD{`O%njtdHAjVa zb6r1_s?@EDZ~B2(>(e%j`OSKv{*1N6_eAviO`47>3p8vw7HC)&_z&+Z)>7((V_=E3LOPo1pLPc zEDt5Rc{)eXor{7y;BB;b{PFHcN?{%;=)2HZ@g$LL1Z3TU%%6x?vB^Da6pMkkfRoqx z(Vy(bXL*%nhqhVo%~zeT+?i40Wr=sE>t74ZNvGU0^J&7i-xiG90?1^YL>36#ZJ_;; zDF~54L^x5dW83Z)KC@0E2`q`U|5Vy;ZaO&p96iIviR9v2?|qa9^akd#Bs_bC;Dn-R z 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 J^3KEO{~s&!5;6b) literal 0 HcmV?d00001 From 375a4d4aace8f6f52f8e2f6cb976c28c53fc0fc4 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Thu, 7 May 2026 10:52:25 -0400 Subject: [PATCH 02/25] choreo fork readme.md and comments --- src/main/deploy/choreo/README.md | 2 +- src/main/java/choreo/README.md | 47 +++++++++++++++++++ src/main/java/choreo/auto/AutoTrajectory.java | 21 ++++++--- src/main/java/frc/robot/Constants.java | 1 + 4 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 src/main/java/choreo/README.md diff --git a/src/main/deploy/choreo/README.md b/src/main/deploy/choreo/README.md index 190d3c2a..3b87d355 100644 --- a/src/main/deploy/choreo/README.md +++ b/src/main/deploy/choreo/README.md @@ -1,5 +1,5 @@ ## Autonomous Paths -This folder is where all the paths that we made for autonomous pathing exist. Such paths were made using **CHOREO** , a path making software. Choreo is how we make all of our autonomous paths, and this year, we opted to make seperate paths, and chain them together for our routines. +This folder is where all the paths that we made for autonomous pathing exist. Such paths were made using [Choreo](https://choreo.autos/), a path making software. Choreo is how we make all of our autonomous paths, and this year, we opted to make seperate paths, and chain them together for our routines. ![Choreo Logo](images/choreoLogo.png) ### Example of one of our autons diff --git a/src/main/java/choreo/README.md b/src/main/java/choreo/README.md new file mode 100644 index 00000000..27a4e0b4 --- /dev/null +++ b/src/main/java/choreo/README.md @@ -0,0 +1,47 @@ +# Choreo + +This folder contains our wrapper and utility classes for integrating [Choreo](https://choreo.autos/) into our robot code. Choreo is the path-planning software we use to generate and follow autonomous trajectories. + +## Why we forked/duplicated Choreo's library + +We duplicated Choreo's library classes locally so we could experiment with **pose correction** during path following. The idea was that if the robot drifts off its expected pose mid-path, we would pause the Choreo timer, drive the robot back to the correct pose, and then resume the timer once the robot is back on track. We weren't able to fully get this working in time, but the groundwork is here. +```java + if(kUsePoseCorrection) { + if (poseSupplier.get().getTranslation().getDistance(sample.getPose().getTranslation()) > 0.2) { + activeTimer.stop(); + outOfBounds.set(true); + } else if (poseSupplier.get().getTranslation().getDistance(sample.getPose().getTranslation()) < 0.05) { + activeTimer.start(); + outOfBounds.set(false); + } + } +``` + +## Structure + +### auto/ +Handles autonomous routine selection and execution. + +- `AutoChooser.java` - lets drivers select an auto routine from the dashboard +- `AutoFactory.java` - constructs auto routines by chaining trajectory segments together +- `AutoRoutine.java` - represents a full autonomous routine made up of multiple trajectories +- `AutoTrajectory.java` - wraps a single Choreo trajectory for command-based execution + +### trajectory/ +Core trajectory representation and sampling logic. + +- `Trajectory.java` - the main trajectory class, holds a list of samples and handles time-based lookups +- `TrajectorySample.java` - a single sampled state along a trajectory (pose, velocity, etc.) +- `SwerveSample.java` - trajectory sample specific to swerve drivetrains +- `DifferentialSample.java` - trajectory sample specific to differential drivetrains +- `EventMarker.java` - represents a timed event trigger within a trajectory + +### util/ +Utility and configuration classes. + +- `Choreo.java` - main entry point for loading trajectories from deploy files +- `ChoreoAlert.java` - handles driver station alerts related to Choreo +- `ChoreoAllianceFlipUtil.java` - flips trajectories for red/blue alliance mirroring +- `ChoreoArrayUtil.java` - array helper methods used internally +- `FieldDimensions.java` - field size constants used for alliance flipping +- `TrajSchemaVersion.java` - version tracking for the trajectory file format \ No newline at end of file diff --git a/src/main/java/choreo/auto/AutoTrajectory.java b/src/main/java/choreo/auto/AutoTrajectory.java index ab2c2074..88e1b1e3 100644 --- a/src/main/java/choreo/auto/AutoTrajectory.java +++ b/src/main/java/choreo/auto/AutoTrajectory.java @@ -10,6 +10,7 @@ import static choreo.util.ChoreoAlert.allianceNotReady; import static edu.wpi.first.wpilibj.Alert.AlertType.kError; import static edu.wpi.first.wpilibj.Alert.AlertType.kWarning; +import static frc.robot.Constants.*; import choreo.Choreo.TrajectoryLogger; import choreo.auto.AutoFactory.AllianceContext; @@ -178,13 +179,19 @@ private void cmdExecute() { return; } var sample = sampleOpt.get(); - // if (poseSupplier.get().getTranslation().getDistance(sample.getPose().getTranslation()) > 0.2) { - // activeTimer.stop(); - // outOfBounds.set(true); - // } else if (poseSupplier.get().getTranslation().getDistance(sample.getPose().getTranslation()) < 0.05) { - // activeTimer.start(); - // outOfBounds.set(false); - // } + /* + * The entire idea of this block is to see that if we are outside of a certain range (0.2 meters in this case), we would pause the autonomous timer - * the timer just tells the robot where it should be, its nothing to + * do with pose based, as choreo is time based * - until the robot reaches the pose (0.05 meters within tolerance in this case). + */ + if(kUsePoseCorrection) { + if (poseSupplier.get().getTranslation().getDistance(sample.getPose().getTranslation()) > 0.2) { + activeTimer.stop(); + outOfBounds.set(true); + } else if (poseSupplier.get().getTranslation().getDistance(sample.getPose().getTranslation()) < 0.05) { + activeTimer.start(); + outOfBounds.set(false); + } + } if (sample instanceof SwerveSample swerveSample) { var swerveController = (Consumer) this.controller; swerveController.accept(swerveSample); diff --git a/src/main/java/frc/robot/Constants.java b/src/main/java/frc/robot/Constants.java index d16f44ca..cacf6149 100644 --- a/src/main/java/frc/robot/Constants.java +++ b/src/main/java/frc/robot/Constants.java @@ -60,6 +60,7 @@ public class Constants { public static final boolean kDebugLoggingEnabled = false; public static final boolean kDataLoggingEnabled = true; + public static final boolean kUsePoseCorrection = false; public static final double kSimPeriodicUpdateInterval = 0.020; public static final CANBus kRioBus = CANBus.roboRIO(); From 84722dfed3fbe08d493c6678cf7c2040d618fd63 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Thu, 7 May 2026 23:24:37 -0400 Subject: [PATCH 03/25] betterer formatting --- src/main/deploy/choreo/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/deploy/choreo/README.md b/src/main/deploy/choreo/README.md index 3b87d355..ea36dd77 100644 --- a/src/main/deploy/choreo/README.md +++ b/src/main/deploy/choreo/README.md @@ -1,5 +1,7 @@ ## Autonomous Paths This folder is where all the paths that we made for autonomous pathing exist. Such paths were made using [Choreo](https://choreo.autos/), a path making software. Choreo is how we make all of our autonomous paths, and this year, we opted to make seperate paths, and chain them together for our routines. + + ![Choreo Logo](images/choreoLogo.png) ### Example of one of our autons From df47bbab846cdad4ed0156e08fdcd66166113a8d Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Wed, 13 May 2026 13:23:17 -0400 Subject: [PATCH 04/25] moved shot calc to own folder and made robot readme --- src/main/java/frc/README.md | 84 +++++++++++++++++++ .../frc/robot/subsystems/shooter/Shooter.java | 3 +- .../shooter/{ => calc}/ShooterCalc.java | 4 +- .../shooter/{ => calc}/ShotCalculator.java | 2 +- src/main/java/frc/util/HubShiftUtil.java | 2 +- .../subsystems/shooter/ShotCalcPerfTest.java | 5 +- .../subsystems/shooter/ShotCalcTest.java | 10 ++- 7 files changed, 99 insertions(+), 11 deletions(-) create mode 100644 src/main/java/frc/README.md rename src/main/java/frc/robot/subsystems/shooter/{ => calc}/ShooterCalc.java (99%) rename src/main/java/frc/robot/subsystems/shooter/{ => calc}/ShotCalculator.java (99%) diff --git a/src/main/java/frc/README.md b/src/main/java/frc/README.md new file mode 100644 index 00000000..61191226 --- /dev/null +++ b/src/main/java/frc/README.md @@ -0,0 +1,84 @@ +# Robot Code + +This is the main codebase for Team 2974 (Walton Robotics). Everything here controls how the robot behaves during a match, from driving around to shooting game pieces at targets. + +If you're new to the team, welcome! Hopefully this will help you figure out where things live so you're not completely lost staring at the file tree, like I was ~~two years ago~~all those years ago. + +--- + +## How the code is organized + +The code is split into folders by responsibility. Each folder handles one aspect of the robot. If something breaks, you'll know exactly where to look (and who to blame). + +--- + +## Top-level files + +- `Robot.java` — Main robot class. Subsystems, commands, and bindings all get wired up here. Start here for the big picture. +- `Main.java` — Program entry point. You ~~probably~~ never need to touch this. +- `Constants.java` — Robot-wide constants (motor ports, PID values, speed limits, etc.). If you're looking for a magic number, it's probably here. NO MAGIC NUMBERS OUTSIDE OF HERE!!!! +- `FieldConstants.java` — Field dimensions and target positions. All thanks to our goats, Mechanical Advantage (6328) + +### [autons/](autons/) — Autonomous routines + +Defines what the robot does during the 15-second autonomous period at the start of a match, where no driver input is allowed. this is where we start praying we work + +- `WaltAdaptableAutonFactory.java` — Builds auto routines by chaining Choreo trajectory segments together. + +--- + +### [dashboards/](dashboards/) — Driver station UI + +Controls what drivers see on their dashboard laptop. + +- `AutonChooser.java` — Dropdown that lets drivers pick which auto routine to run before a match. + +--- + +### [generated/](generated/) — Auto-generated files + +Created by external tools, not written by hand. Don't edit these directly unless you know what you're doing LOL + +- `TunerConstants.java` — Swerve drive tuning constants from CTRE's Tuner X (THANK GOD FOR TUNERX). + +--- + +### [subsystems/](subsystems/) — Robot mechanisms + +Each file represents a physical mechanism on the robot and the code that controls it. This is where most of the action happens. + +#### [subsystems/shooter/](subsystems/shooter/) — Shooting mechanism + +The fun part. I had SO much fun on this part :D + +- `Shooter.java` — Master control over the entire shooter subsystem. +- `Hood.java` — Adjustable hood angle and other variety. +- `Turret.java` — Turret rotation and tracking. +- `TurretVisualizer.java` — 3D visualization for turret state. +- `FuelSim.java` — Game piece physics simulation for testing. + +##### [subsystems/shooter/calc](subsystems/shooter/calc) — Shot calculations + +this is where it hit the fan D: + +- `ShooterCalc.java` — Shot math and distance-based calculations on its own thread. +- `ShotCalculator.java` — Other shot math and distance-based calculations. + +#### Other subsystems + +- `Intake.java` — Picks up game pieces from the ground. +- `Indexer.java` — Feeds game pieces from the intake into the shooter. The middleman. +- `Superstructure.java` — Coordinates intake, indexer, and shooter together so nothing fires when it shouldn't. +- `Swerve.java` — Swerve drivetrain. Makes the robot go vroom in any direction. + +--- + +### [vision/](vision/) — Target tracking + +Uses cameras to detect and track field targets for automatic aiming. The robot can see better than most of us at this point. + +- `WaltCamera.java` — Camera wrapper. +- `Detection.java` — Detects game pieces. +- `VisionSim.java` — Simulates vision for testing. + +--- \ No newline at end of file diff --git a/src/main/java/frc/robot/subsystems/shooter/Shooter.java b/src/main/java/frc/robot/subsystems/shooter/Shooter.java index 415c6c97..d16754a5 100644 --- a/src/main/java/frc/robot/subsystems/shooter/Shooter.java +++ b/src/main/java/frc/robot/subsystems/shooter/Shooter.java @@ -38,7 +38,8 @@ import java.util.function.Supplier; import frc.robot.Constants; -import frc.robot.subsystems.shooter.ShooterCalc.ShotCalcOutputs; +import frc.robot.subsystems.shooter.calc.ShooterCalc; +import frc.robot.subsystems.shooter.calc.ShooterCalc.ShotCalcOutputs; import frc.util.SignalManager; import frc.util.WaltMotorSim; import frc.util.WaltTunable; diff --git a/src/main/java/frc/robot/subsystems/shooter/ShooterCalc.java b/src/main/java/frc/robot/subsystems/shooter/calc/ShooterCalc.java similarity index 99% rename from src/main/java/frc/robot/subsystems/shooter/ShooterCalc.java rename to src/main/java/frc/robot/subsystems/shooter/calc/ShooterCalc.java index 117de745..41b35ea8 100644 --- a/src/main/java/frc/robot/subsystems/shooter/ShooterCalc.java +++ b/src/main/java/frc/robot/subsystems/shooter/calc/ShooterCalc.java @@ -1,4 +1,4 @@ -package frc.robot.subsystems.shooter; +package frc.robot.subsystems.shooter.calc; import java.util.function.BooleanSupplier; import java.util.function.DoubleSupplier; @@ -24,7 +24,7 @@ import frc.robot.Constants.WpiK; import frc.robot.generated.TunerConstants; import frc.robot.FieldConstants; -import frc.robot.subsystems.shooter.ShotCalculator.ShotDataLerp; +import frc.robot.subsystems.shooter.calc.ShotCalculator.ShotDataLerp; import frc.util.AllianceFlipUtil; import frc.util.AllianceZoneUtil; import frc.util.WaltLogger; diff --git a/src/main/java/frc/robot/subsystems/shooter/ShotCalculator.java b/src/main/java/frc/robot/subsystems/shooter/calc/ShotCalculator.java similarity index 99% rename from src/main/java/frc/robot/subsystems/shooter/ShotCalculator.java rename to src/main/java/frc/robot/subsystems/shooter/calc/ShotCalculator.java index c93cb777..13aa6bef 100644 --- a/src/main/java/frc/robot/subsystems/shooter/ShotCalculator.java +++ b/src/main/java/frc/robot/subsystems/shooter/calc/ShotCalculator.java @@ -1,5 +1,5 @@ -package frc.robot.subsystems.shooter; +package frc.robot.subsystems.shooter.calc; import static edu.wpi.first.units.Units.InchesPerSecond; import static edu.wpi.first.units.Units.Meters; diff --git a/src/main/java/frc/util/HubShiftUtil.java b/src/main/java/frc/util/HubShiftUtil.java index ceca48af..a8d8cda2 100644 --- a/src/main/java/frc/util/HubShiftUtil.java +++ b/src/main/java/frc/util/HubShiftUtil.java @@ -13,7 +13,7 @@ import java.util.Optional; import java.util.function.BooleanSupplier; import java.util.function.Supplier; -import frc.robot.subsystems.shooter.ShotCalculator; +import frc.robot.subsystems.shooter.calc.ShotCalculator; public class HubShiftUtil { public enum ShiftEnum { diff --git a/src/test/java/frc/robot/subsystems/shooter/ShotCalcPerfTest.java b/src/test/java/frc/robot/subsystems/shooter/ShotCalcPerfTest.java index b12082df..0e899617 100644 --- a/src/test/java/frc/robot/subsystems/shooter/ShotCalcPerfTest.java +++ b/src/test/java/frc/robot/subsystems/shooter/ShotCalcPerfTest.java @@ -14,8 +14,9 @@ import frc.robot.Constants.ShooterK; import frc.robot.FieldConstants; -import frc.robot.subsystems.shooter.ShotCalculator.ShotData; -import frc.robot.subsystems.shooter.ShotCalculator.ShotDataLerp; +import frc.robot.subsystems.shooter.calc.ShotCalculator; +import frc.robot.subsystems.shooter.calc.ShotCalculator.ShotData; +import frc.robot.subsystems.shooter.calc.ShotCalculator.ShotDataLerp; import org.junit.jupiter.api.*; diff --git a/src/test/java/frc/robot/subsystems/shooter/ShotCalcTest.java b/src/test/java/frc/robot/subsystems/shooter/ShotCalcTest.java index c882536d..c53fb1fa 100644 --- a/src/test/java/frc/robot/subsystems/shooter/ShotCalcTest.java +++ b/src/test/java/frc/robot/subsystems/shooter/ShotCalcTest.java @@ -10,10 +10,12 @@ import edu.wpi.first.math.kinematics.ChassisSpeeds; import edu.wpi.first.units.measure.*; -import frc.robot.subsystems.shooter.ShotCalculator.ShotData; -import frc.robot.subsystems.shooter.ShotCalculator.ShotDataLerp; -import frc.robot.subsystems.shooter.ShooterCalc.AzimuthCalcDetails; -import frc.robot.subsystems.shooter.ShooterCalc.ShotCalcOutputs; +import frc.robot.subsystems.shooter.calc.ShotCalculator.ShotData; +import frc.robot.subsystems.shooter.calc.ShotCalculator.ShotDataLerp; +import frc.robot.subsystems.shooter.calc.ShooterCalc; +import frc.robot.subsystems.shooter.calc.ShooterCalc.AzimuthCalcDetails; +import frc.robot.subsystems.shooter.calc.ShooterCalc.ShotCalcOutputs; +import frc.robot.subsystems.shooter.calc.ShotCalculator; import org.junit.jupiter.api.*; From 302d06277142bed95a8a83c864959c620a4685fa Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Wed, 13 May 2026 13:26:54 -0400 Subject: [PATCH 05/25] oops wrong folder --- src/main/java/frc/{ => robot}/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/main/java/frc/{ => robot}/README.md (91%) diff --git a/src/main/java/frc/README.md b/src/main/java/frc/robot/README.md similarity index 91% rename from src/main/java/frc/README.md rename to src/main/java/frc/robot/README.md index 61191226..24107bc4 100644 --- a/src/main/java/frc/README.md +++ b/src/main/java/frc/robot/README.md @@ -1,6 +1,6 @@ # Robot Code -This is the main codebase for Team 2974 (Walton Robotics). Everything here controls how the robot behaves during a match, from driving around to shooting game pieces at targets. +This is the main codebase for Team 2974 (Walton Robotics) for the ReBuilt Season. Typically, everything here is all you'll need, as everything here controls how the robot behaves during a match, from driving around to shooting game pieces at targets. If you're new to the team, welcome! Hopefully this will help you figure out where things live so you're not completely lost staring at the file tree, like I was ~~two years ago~~all those years ago. @@ -8,7 +8,7 @@ If you're new to the team, welcome! Hopefully this will help you figure out wher ## How the code is organized -The code is split into folders by responsibility. Each folder handles one aspect of the robot. If something breaks, you'll know exactly where to look (and who to blame). +The code is split into folders by responsibility. Each folder handles one aspect of the robot. If something breaks, you'll know exactly where to look (and who to blame(thank you gitblame)). --- From e8ced24f5955cb8f9daa55d43a0bbb7a90d826c4 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Wed, 13 May 2026 13:28:58 -0400 Subject: [PATCH 06/25] i dont like some of this stuff ew --- src/main/java/frc/robot/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/frc/robot/README.md b/src/main/java/frc/robot/README.md index 24107bc4..d0c2adf1 100644 --- a/src/main/java/frc/robot/README.md +++ b/src/main/java/frc/robot/README.md @@ -2,7 +2,7 @@ This is the main codebase for Team 2974 (Walton Robotics) for the ReBuilt Season. Typically, everything here is all you'll need, as everything here controls how the robot behaves during a match, from driving around to shooting game pieces at targets. -If you're new to the team, welcome! Hopefully this will help you figure out where things live so you're not completely lost staring at the file tree, like I was ~~two years ago~~all those years ago. +If you're new to the team, welcome! Hopefully this will help you figure out where things live so you're not completely lost staring at the file tree, like I was ~~two years ago~~ all those years ago. --- @@ -21,7 +21,8 @@ The code is split into folders by responsibility. Each folder handles one aspect ### [autons/](autons/) — Autonomous routines -Defines what the robot does during the 15-second autonomous period at the start of a match, where no driver input is allowed. this is where we start praying we work +Defines what the robot does during the 15-second autonomous period at the start of a match, where no driver input is allowed. +#### this is where we start praying - `WaltAdaptableAutonFactory.java` — Builds auto routines by chaining Choreo trajectory segments together. From c8187f82fa72261e3203a467bb796da11d3d1acf Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Wed, 13 May 2026 14:38:13 -0400 Subject: [PATCH 07/25] I FIGURED IT OUT I LOVE THE INTERNET --- src/main/java/frc/robot/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/frc/robot/README.md b/src/main/java/frc/robot/README.md index d0c2adf1..2515718b 100644 --- a/src/main/java/frc/robot/README.md +++ b/src/main/java/frc/robot/README.md @@ -1,6 +1,6 @@ -# Robot Code +# /robot -This is the main codebase for Team 2974 (Walton Robotics) for the ReBuilt Season. Typically, everything here is all you'll need, as everything here controls how the robot behaves during a match, from driving around to shooting game pieces at targets. +This is the main area for the ReBuilt Season. Typically, everything here is all you'll need, as everything here controls how the robot behaves during a match, from driving around to shooting game pieces at targets. If you're new to the team, welcome! Hopefully this will help you figure out where things live so you're not completely lost staring at the file tree, like I was ~~two years ago~~ all those years ago. @@ -14,7 +14,7 @@ The code is split into folders by responsibility. Each folder handles one aspect ## Top-level files -- `Robot.java` — Main robot class. Subsystems, commands, and bindings all get wired up here. Start here for the big picture. +- `Robot.java` — Main robot class. Subsystems, commands, and bindings all get wired up here. This will have almost every concept that you would need to understand how to program in FRC! :wink: - `Main.java` — Program entry point. You ~~probably~~ never need to touch this. - `Constants.java` — Robot-wide constants (motor ports, PID values, speed limits, etc.). If you're looking for a magic number, it's probably here. NO MAGIC NUMBERS OUTSIDE OF HERE!!!! - `FieldConstants.java` — Field dimensions and target positions. All thanks to our goats, Mechanical Advantage (6328) @@ -22,7 +22,7 @@ The code is split into folders by responsibility. Each folder handles one aspect ### [autons/](autons/) — Autonomous routines Defines what the robot does during the 15-second autonomous period at the start of a match, where no driver input is allowed. -#### this is where we start praying +#### this is where we start praying :pray: - `WaltAdaptableAutonFactory.java` — Builds auto routines by chaining Choreo trajectory segments together. @@ -60,7 +60,7 @@ The fun part. I had SO much fun on this part :D ##### [subsystems/shooter/calc](subsystems/shooter/calc) — Shot calculations -this is where it hit the fan D: +this is where it hit the fan :scream: :scream: :scream: - `ShooterCalc.java` — Shot math and distance-based calculations on its own thread. - `ShotCalculator.java` — Other shot math and distance-based calculations. From 9b6660efcfb4ce659e37c883ba1853dda658c414 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Wed, 13 May 2026 15:14:45 -0400 Subject: [PATCH 08/25] took me an hour and a half but now this class is readable :face_holding_back_tears: also i cant believe im doing this for every single class get me OUT --- .../autons/WaltAdaptableAutonFactory.java | 503 +++++++++++------- 1 file changed, 315 insertions(+), 188 deletions(-) diff --git a/src/main/java/frc/robot/autons/WaltAdaptableAutonFactory.java b/src/main/java/frc/robot/autons/WaltAdaptableAutonFactory.java index 02743435..1117e36b 100644 --- a/src/main/java/frc/robot/autons/WaltAdaptableAutonFactory.java +++ b/src/main/java/frc/robot/autons/WaltAdaptableAutonFactory.java @@ -1,7 +1,5 @@ package frc.robot.autons; -import static frc.robot.Constants.IntakeK.kIntakeRollersIntakeVolts; - import java.util.Set; import java.util.function.Supplier; @@ -23,132 +21,269 @@ import frc.util.WaltLogger.Pose2dArrayLogger; import frc.util.WaltLogger.StringLogger; +/* + * WaltAdaptableAutonFactory + * + * This class is responsible for building all of our autonomous routines. + * Rather than writing a brand new command sequence for every single auton we + * want to run, this factory takes a list of trajectory segments + settings and + * assembles the full routine automatically — hence "adaptable". + * + * Quick vocabulary for new programmers: + * Trajectory — a pre-planned path the robot drives along, created in the + * Choreo desktop app and saved as a JSON file on the robot. + * Event marker / waypoint — a named timestamp placed inside Choreo. + * When the robot's path timer hits that timestamp, a Trigger + * fires and we run a command (e.g. start intaking, start shooting). + * Trigger — a WPILib class that watches a boolean condition and runs + * commands when that condition becomes true or false. + * SOTM — "Shoot On The Move". Instead of stopping the robot to shoot, + * SOTM mode lets the robot keep driving while the shooter is active. + * + * The two main entry points are: + * adaptableAuton(...) — builds a routine that follows a single path + * multiAdaptableAuton(...) — builds a routine that chains multiple paths together + * + * AutonChooser.java is where the actual routines are defined (which paths to use, + * what timeouts, SOTM or not, etc.) and put on the dashboard for the driver to pick. + */ public class WaltAdaptableAutonFactory { - private final Superstructure m_superstructure; - public final AutoFactory m_autoFactory; - private final Intake m_intake; - private final Shooter m_shooter; - private final Swerve m_drivetrain; - - public Timer autonTimer = new Timer(); - // state logger - // private final DoubleLogger log_autonState = WaltLogger.logDouble(kLogTab, "State"); - - // waypoint constants - private final String kIntakeWaypoint = "intake"; - private final String kStopIntakeWaypoint = "stopIntake"; - private final String kShootWaypoint = "shoot"; - private final String kStopShootWaypoint = "stopShoot"; - private final String kIntakeAndShootWaypoint = "intakeAndShoot"; - private final String kStopIntakeAndShootWaypoint = "stopIntakeAndShoot"; - - // trajectory logger - private final StringLogger log_trajectoryName = new StringLogger(AutonK.kLogTab, "trajectoryName"); - private final Pose2dArrayLogger log_trajectoryPoses = new Pose2dArrayLogger(AutonK.kLogTab, "trajectoryPoses"); - private final BooleanLogger log_isAtStopShoot = new BooleanLogger(AutonK.kLogTab, "isAtStopShoot"); - private final BooleanLogger log_isAtStopIntake = new BooleanLogger(AutonK.kLogTab, "isAtStopIntake"); + // ---- SUBSYSTEMS ---- + // These are passed in through the constructor so we can tell them what to do + // without this class being responsible for creating them. + + private final Superstructure m_superstructure; // controls shooting + intaking at a high level + private final AutoFactory m_autoFactory; // Choreo: loads trajectory files and creates routines + private final Intake m_intake; // intake arm + rollers + private final Shooter m_shooter; // flywheels + shot calculator + private final Swerve m_drivetrain; // swerve drive (used to lock wheels at the end) + + // ---- WAYPOINT NAMES ---- + // These strings must EXACTLY match the event marker names you place in Choreo. + // If a marker is named "intake" in Choreo, it has to be "intake" here too. + + private static final String kIntakeWaypoint = "intake"; + private static final String kStopIntakeWaypoint = "stopIntake"; + private static final String kShootWaypoint = "shoot"; + private static final String kStopShootWaypoint = "stopShoot"; + private static final String kIntakeAndShootWaypoint = "intakeAndShoot"; + private static final String kStopIntakeAndShootWaypoint = "stopIntakeAndShoot"; + + // ---- loggers ---- + // WaltLogger pushes data to NetworkTables, which AdvantageScope can read + // and replay after a match so we can see exactly what happened and when. + + private final StringLogger log_trajectoryName = new StringLogger(AutonK.kLogTab, "trajectoryName"); + private final Pose2dArrayLogger log_trajectoryPoses = new Pose2dArrayLogger(AutonK.kLogTab, "trajectoryPoses"); + private final BooleanLogger log_isAtStopShoot = new BooleanLogger(AutonK.kLogTab, "isAtStopShoot"); + private final BooleanLogger log_isAtStopIntake = new BooleanLogger(AutonK.kLogTab, "isAtStopIntake"); private final DoubleLogger log_autonActionTimes = new DoubleLogger(AutonK.kLogTab, "autonActionTimes"); + private final StringLogger log_autonEventMarker = WaltLogger.logString("Auton", "autonTriggerCall"); - // logic booleans + // ---- state flags ---- + // These get flipped by trajectory event markers at runtime and are exposed + // as Triggers so we can use them as end conditions for running commands. + + // Toggled by the "stopShoot" marker. Acts as a manual override to end shooting + // early — useful when the robot has driven past the point where shooting makes sense + // but the ball sensor hasn't confirmed the shot yet. + // Uses onChange (fires on both the rising and falling edge of the marker window) + // so the flag automatically resets itself once the window closes. private boolean m_isAtStopShoot = false; + + // Goes true while the robot is inside the "stopIntake" marker window, then + // back to false when it leaves. The intake command runs until this goes true. private boolean m_isAtStopIntake = false; - private Trigger trg_isAtStopShoot = new Trigger(() -> m_isAtStopShoot); - private Trigger trg_isAtStopIntake = new Trigger(() -> m_isAtStopIntake); - public WaltAdaptableAutonFactory(Superstructure superstructure, AutoFactory autoFactory, Intake intake, Shooter shooter, Swerve swerve) { + private final Trigger trg_isAtStopShoot = new Trigger(() -> m_isAtStopShoot); + private final Trigger trg_isAtStopIntake = new Trigger(() -> m_isAtStopIntake); + + // ---- auton timer ---- + // Tracks time since auton started. Used to timestamp logged events so we + // know exactly when each action fired during a match. + + public Timer autonTimer = new Timer(); + + + // ============================================================= + // CONSTRUCTOR + // ============================================================= + + public WaltAdaptableAutonFactory( + Superstructure superstructure, + AutoFactory autoFactory, + Intake intake, + Shooter shooter, + Swerve swerve) { m_superstructure = superstructure; - m_autoFactory = autoFactory; - m_intake = intake; - m_shooter = shooter; - m_drivetrain = swerve; + m_autoFactory = autoFactory; + m_intake = intake; + m_shooter = shooter; + m_drivetrain = swerve; } - //---UTILITY METHODS - // private Command logState(double state) { - // return Commands.runOnce(() -> log_autonState.accept(state)); - // } - private Command tp(String message) { + // ============================================================= + // PRIVATE UTILITY METHODS + // ============================================================= + + // Short helper to print a timestamped message when a command runs. + // Used throughout to trace the auton flow in the console/logs. + private Command timedPrint(String message) { return WaltLogger.timedPrintCmd(message); } - private Command waitIntakeHomedCmd() { - return Commands.waitUntil(m_intake.intakeHomedSupp); + // Defers evaluating the string until the command actually *runs*, not when + // it's constructed. This matters for anything that reads a live value like + // a timer — without defer, the value would be captured at build time (t=0). + // Thank you grac + private static Command printLater(Supplier stringSup) { + return Commands.defer(() -> Commands.print(stringSup.get()), Set.of()); } + // Waits for the intake arm to find its home position (mechanical zero) + // before the auton relies on any intake movement. Times out after 5 s so + // a bad homing wont screw our path over private Command homingCmd() { - return Commands.sequence(tp("intakeArmHoming.START"), waitIntakeHomedCmd(), tp("intakeArmHoming.END")) - .withTimeout(5); + return Commands.sequence( + timedPrint("intakeArmHoming.START"), + Commands.waitUntil(m_intake.intakeHomedSupp), + timedPrint("intakeArmHoming.END") + ).withTimeout(5); } - public AutoRoutine preheater() { - System.out.println("PREHEAT MADE"); - return adaptableAuton("PreHeat", new AdaptableAutonInfo("PreHeat", AutonK.kShootingTimeout, false, 0)); + // Builds the command that fires when trajectory[i] finishes and trajectory[i+1] should start. + // + // SOTM mode → drive into the next path right away (optionally after a short delay). + // Shooting is already happening while driving, so no wait needed. + // + // Normal mode → wait for the shooter to confirm the ball has left the robot + // (or for the stopShoot override, or until the timeout expires), + // then optionally delay, then start the next path. + private Command buildTransitionCmd( + Command nextTrajCmd, + double nextDelay, + double shooterTimeout, + boolean shootOnTheMove) { + + // If there's a pre-path delay, prepend it to whatever we're about to start. + Command startNext = nextDelay > 0 + ? Commands.sequence(Commands.waitSeconds(nextDelay), nextTrajCmd) + : nextTrajCmd; + + if (shootOnTheMove) { + return startNext; // SOTM — just go, no waiting needed + } + + // Non-SOTM: race shot confirmation against the timeout so we never + // get stuck waiting if a ball gets stuck or the sensor misses. + Command waitForShot = Commands.race( + Commands.waitUntil(m_shooter.getBallShotDebounceTrg().or(trg_isAtStopShoot)), + Commands.waitSeconds(shooterTimeout) + ); + + return Commands.sequence( + timedPrint("WAITING FOR SHOOTING DONE"), + waitForShot, + startNext + ); } - /** - * Eagerly loads every given trajectory into the AutoFactory's TrajectoryCache. - * After this runs, {@code routine.trajectory(name)} during autonomousInit is a - * HashMap hit instead of a disk read + GSON parse. Call once during robotInit. - */ + // Logs a named waypoint event to NetworkTables so post-match replays in + // AdvantageScope show which markers fired and in what order. + private Command logEventMarker(String markerName) { + return Commands.runOnce(() -> log_autonEventMarker.accept(markerName)); + } + + + // ============================================================= + // PUBLIC UTILITIES + // ============================================================= + + // Eagerly parses and caches every trajectory during robotInit. + // Without this, the first time a trajectory is requested during auton it + // has to be read off the filesystem + parsed from JSON, which takes time. + // After preloading, all lookups are fast HashMap hits instead. + // Call this once during robotInit, before building any auton routines. public void preloadAllTrajectories(String[] names) { var cache = m_autoFactory.cache(); long totalStart = System.nanoTime(); + for (String name : names) { long ts = System.nanoTime(); cache.loadTrajectory(name); long elapsed = System.nanoTime() - ts; - if (elapsed > 5_000_000) { // only log if > 5ms + + // only print slow (> 5 ms) crine because rio cant handle too many prints + if (elapsed > 5_000_000) { System.out.printf("[PRELOAD] %s: %.1f ms%n", name, elapsed * 1e-6); } } + System.out.printf("[PRELOAD] %d trajectories total: %.1f ms%n", names.length, (System.nanoTime() - totalStart) * 1e-6); } - //thank you grac - private static Command printLater(Supplier stringSup) { - return Commands.defer(() -> { - return Commands.print(stringSup.get()); - }, Set.of()); - } - + // Logs an elapsed-time snapshot for a named event. + // Used to timestamp when specific things happened during auton for post-match analysis. public Command logTimer(String epochName, Supplier timerSup) { return printLater(() -> { var timer = timerSup.get(); log_autonActionTimes.accept(autonTimer.get()); return epochName + " at " + timer.get() + " s"; }); - } + } + // Start the auton-wide timer. Call this at the top of autonomousInit. public void startAutonTimer() { autonTimer.start(); } - //---AUTOROUTINE TRAJECTORY HELPER + + // ============================================================= + // TRAJECTORY BUILDER HELPER + // ============================================================= + + // Creates an AutoTrajectory and wires up start/end logging. + // Poses are pulled from the cache (populated by preloadAllTrajectories) + // so we're doing a fast lookup, not re-parsing the JSON file from disk. private AutoTrajectory createTraj(AutoRoutine routine, String name) { AutoTrajectory traj = routine.trajectory(name); - // Go through the factory's TrajectoryCache so this is a HashMap hit after preload, - // instead of a second disk read + GSON parse on top of routine.trajectory(name)'s load. var poses = m_autoFactory.cache().loadTrajectory(name).get().getPoses(); + traj.active().onTrue(Commands.sequence( Commands.runOnce(() -> { log_trajectoryName.accept(name); log_trajectoryPoses.accept(poses); }), - tp("traj.START(" + name + ")") + timedPrint("traj.START(" + name + ")") )); - traj.done().onTrue(tp("traj.END(" + name + ")")); + + traj.done().onTrue(timedPrint("traj.END(" + name + ")")); + return traj; } - //---ADAPTABLE AUTON MAKER + + // ============================================================= + // AUTON ROUTINE BUILDERS + // ============================================================= + + // Builds a "preheat" routine that runs while the robot is still disabled + // before a match. Ensures we dont stall for like 0.4 seconds at the start of auton + public AutoRoutine preheater() { + System.out.println("PREHEAT MADE"); + return adaptableAuton("PreHeat", new AdaptableAutonInfo("PreHeat", AutonK.kShootingTimeout, false, 0)); + } + + // Builds a single-segment auton: follow one trajectory and fire waypoint + // actions at the event markers embedded in it. + // The intake arm homing sequence runs alongside the path so homing doesn't + // cost us any auton time. public AutoRoutine adaptableAuton(String routineName, AdaptableAutonInfo autonInfo) { AutoRoutine routine = m_autoFactory.newRoutine(routineName); - - String path = autonInfo.autonName(); - AutoTrajectory traj = createTraj(routine, path); + AutoTrajectory traj = createTraj(routine, autonInfo.autonName()); routine.active().onTrue( traj.cmd().alongWith(homingCmd()) @@ -159,131 +294,116 @@ public AutoRoutine adaptableAuton(String routineName, AdaptableAutonInfo autonIn return routine; } + // Builds a multi-segment auton by chaining several trajectories end-to-end. + // How the transition between segments works depends on SOTM mode — see buildTransitionCmd. + // After the last segment finishes, the drivetrain locks into an X-brake so the robot + // doesn't slide. + /** + * NEW TERMINOLOGY: COAST OUT -- Coasting out means to let the motors keep their momentum when they stop, instead of + * simply going to zero (stopping abruptly). + */ + // Something to think about is to coast out the swerve at the end of auto, to cover more ground to get closer to + // fuel to pick up faster? + // EX: See Citrus's (1678) autos public AutoRoutine multiAdaptableAuton(String routineName, AdaptableAutonInfo[] autonInfos) { - System.out.println("================== adaptableBuilder Start =================="); - - AutoTrajectory[] autonTrajs = new AutoTrajectory[autonInfos.length]; AutoRoutine routine = m_autoFactory.newRoutine(routineName); + // Step 1: create all trajectory objects before wiring any triggers. + // This ensures every trajectory is in memory before anything tries to + // reference the next one. + AutoTrajectory[] autonTrajs = new AutoTrajectory[autonInfos.length]; for (int i = 0; i < autonInfos.length; i++) { - String path = autonInfos[i].autonName(); - autonTrajs[i] = createTraj(routine, path); + autonTrajs[i] = createTraj(routine, autonInfos[i].autonName()); } - System.out.println("trajs built"); - + // Step 2: attach waypoint triggers to every segment. for (int i = 0; i < autonTrajs.length; i++) { - AutoTrajectory thisTraj = autonTrajs[i]; - var thisInfo = autonInfos[i]; - System.out.println("traj idx " + i + " (" + thisInfo.autonName + ") .done().onTrue() built"); - - setUpTrajTriggers(thisTraj, thisInfo.shooterTimeout(), autonInfos[i].SOTM()); + setUpTrajTriggers(autonTrajs[i], autonInfos[i].shooterTimeout(), autonInfos[i].SOTM()); } - System.out.println("trajTriggers built"); - if (autonInfos[0].delay() != 0) { - routine.active().onTrue( - Commands.sequence( - Commands.waitSeconds(autonInfos[0].delay()), - autonTrajs[0].cmd().alongWith(homingCmd()) - ) - ); - } else { - routine.active().onTrue( - autonTrajs[0].cmd().alongWith(homingCmd()) - ); - } - System.out.println("routine active built"); + // Step 3: start the first trajectory when the routine becomes active. + // Home the intake arm in parallel so we don't waste time waiting for it. + Command firstPath = autonTrajs[0].cmd().alongWith(homingCmd()); + double firstDelay = autonInfos[0].delay(); + routine.active().onTrue( + firstDelay > 0 + ? Commands.sequence(Commands.waitSeconds(firstDelay), firstPath) + : firstPath + ); - System.out.println("traj idx onTrue pre-built"); + // Step 4: chain each segment into the next. + // For every segment except the last: "when this path ends, run the transition into the next one." for (int i = 0; i < autonTrajs.length - 1; i++) { - AutoTrajectory thisTraj = autonTrajs[i]; - var thisInfo = autonInfos[i]; - System.out.println("traj idx " + i + " (" + thisInfo.autonName + ") .done().onTrue() built"); - // thisTraj.active().whileTrue( - // Commands.run(() -> { - // var sample = thisTraj.getRawTrajectory().sampleAt(i, m_isAtStopIntake); - // })) - // ) - if (autonInfos[i + 1].delay() != 0) { - thisTraj.done().onTrue( - thisInfo.SOTM ? Commands.sequence( - Commands.waitSeconds(autonInfos[i + 1].delay()), - autonTrajs[i + 1].cmd()) : - Commands.sequence( - tp("WAITING FOR SHOOTING DONE"), - Commands.race( - Commands.waitUntil(m_shooter.getBallShotDebounceTrg().or(trg_isAtStopShoot)), - Commands.waitSeconds(thisInfo.shooterTimeout()) - ), - tp("TRAJ " + (i + 1) + " DELAY STARTED"), - Commands.waitSeconds(autonInfos[i + 1].delay()), - tp("TRAJ " + (i + 1) + " STARTED"), - autonTrajs[i + 1].cmd() - )); - } else { - thisTraj.done().onTrue( - thisInfo.SOTM ? autonTrajs[i + 1].cmd() : - Commands.sequence( - tp("WAITING FOR SHOOTING DONE"), - Commands.race( - Commands.waitUntil(m_shooter.getBallShotDebounceTrg().or(trg_isAtStopShoot)), - Commands.waitSeconds(thisInfo.shooterTimeout()) - ), - tp("TRAJ " + (i + 1) + " STARTED"), - autonTrajs[i + 1].cmd() - )); - } + Command transition = buildTransitionCmd( + autonTrajs[i + 1].cmd(), + autonInfos[i + 1].delay(), + autonInfos[i].shooterTimeout(), + autonInfos[i].SOTM() + ); + autonTrajs[i].done().onTrue(transition); } + // Step 5: lock wheels once the final segment finishes. autonTrajs[autonTrajs.length - 1].done().onTrue( m_drivetrain.xBrakeCmd() ); - System.out.println("================== full routine built =================="); - return routine; } - private StringLogger log_autonEventMarker = WaltLogger.logString("Auton", "autonTriggerCall"); - - public Command updateAutonEventMarkerLogger(String update) { - return Commands.runOnce(() -> log_autonEventMarker.accept(update)); - } - /* CHECK IF TURRET IS ABLE TO SHOOT FOR PASSING PLEASEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ - public void setUpTrajTriggers(AutoTrajectory traj, double shooterTimeout, boolean SOTM) { - //---TRIGGER ACTIONS + // ============================================================= + // TRAJECTORY TRIGGER SETUP + // ============================================================= + + // Registers all the waypoint-triggered commands for a single trajectory segment. + // + // Choreo fires a Trigger each time the path timer reaches a named event marker. + // That's how the robot knows *when* to intake, shoot, etc. based on where it is + // along the path — we're not polling position, just reacting to timestamps. + // + // Supported marker names (type these exactly in the Choreo editor): + // "intake" start intaking; runs until stopIntake fires + // "stopIntake" stop the active intake command + // "shoot" start shooting (behavior differs by SOTM flag, see below) + // "stopShoot" force-stop shooting early (override if ball sensor misses) + // "intakeAndShoot" run intake + shooter at the same time + // "stopIntakeAndShoot" stop the simultaneous intake+shoot + // + // shootOnTheMove (SOTM): + // false → robot waits for shot confirmation before moving on (timeout is the safety cutoff) + // true → robot keeps driving while shooting; shooting ends when ball exits or stopShoot fires + public void setUpTrajTriggers(AutoTrajectory traj, double shooterTimeout, boolean shootOnTheMove) { + + // "intake" — start intaking, stop when the stopIntake marker fires traj.atTime(kIntakeWaypoint).onTrue( m_superstructure.intake(() -> false, () -> false).until(trg_isAtStopIntake) ); - // traj.atTime(kIntakeWaypoint).onTrue( - // logTimer("intaking", () -> autonTimer) - // ); - - traj.atTime(kShootWaypoint).and(() -> !SOTM).onTrue( - // stopShoot acts as an override STOP SHOOTING to continue the pathing whereas the debounceTrg can help us move on faster if we're already outta balls + // "shoot" (non-SOTM) — race: shoot until ball exits OR timeout expires. + // The timeout is a safety cutoff so auton never stalls if a ball gets stuck. + traj.atTime(kShootWaypoint).and(() -> !shootOnTheMove).onTrue( Commands.race( m_superstructure.activateOuttakeShotCalc().until(m_shooter.getBallShotDebounceTrg()), Commands.waitSeconds(shooterTimeout) ) - // (m_superstructure.activateOuttakeShotCalc().until(m_shooter.getBallShotDebounceTrg())).withTimeout(shooterTimeout) ); - traj.atTime(kShootWaypoint).and(() -> SOTM).onTrue( - // stopShoot acts as an override STOP SHOOTING to continue the pathing whereas the debounceTrg can help us move on faster if we're already outta balls - m_superstructure.activateOuttakeShotCalc().until(m_shooter.getBallShotDebounceTrg().or(trg_isAtStopShoot)) + // "shoot" (SOTM) — robot keeps driving; shooting ends when ball exits or stopShoot fires + traj.atTime(kShootWaypoint).and(() -> shootOnTheMove).onTrue( + m_superstructure.activateOuttakeShotCalc() + .until(m_shooter.getBallShotDebounceTrg().or(trg_isAtStopShoot)) ); + // "shoot" (both modes) — shimmy the intake to help feed the ball into the shooter traj.atTime(kShootWaypoint).onTrue( m_superstructure.intakeShimmy(() -> true) ); - // traj.atTime(kShootWaypoint).onTrue( - // logTimer(kShootWaypoint, () -> autonTimer) - // ); - + // "stopShoot" — toggle the override flag on both the rising AND falling + // edge of the marker window (onChange fires twice per pass-through). + // This means the flag flips on as the robot enters the window, then + // automatically flips back off as it leaves — a self-resetting override. traj.atTime(kStopShootWaypoint).onChange( Commands.runOnce(() -> { m_isAtStopShoot = !m_isAtStopShoot; @@ -291,13 +411,14 @@ public void setUpTrajTriggers(AutoTrajectory traj, double shooterTimeout, boolea }) ); + // "stopIntake" — true while inside the marker window, false once outside. + // The intake command above uses trg_isAtStopIntake as its end condition. traj.atTime(kStopIntakeWaypoint).onTrue( Commands.runOnce(() -> { m_isAtStopIntake = true; log_isAtStopIntake.accept(m_isAtStopIntake); }) ); - traj.atTime(kStopIntakeWaypoint).onFalse( Commands.runOnce(() -> { m_isAtStopIntake = false; @@ -305,44 +426,50 @@ public void setUpTrajTriggers(AutoTrajectory traj, double shooterTimeout, boolea }) ); + // "intakeAndShoot" / "stopIntakeAndShoot" — run intake + shooter together + // until the stop marker fires. Used for bump/passing actions. traj.atTime(kIntakeAndShootWaypoint).onTrue( - m_superstructure.intake(() -> true, () -> false).until(traj.atTime(kStopIntakeAndShootWaypoint)) - ); - - traj.atTime(kIntakeAndShootWaypoint).onTrue( - m_superstructure.activateOuttakeShotCalc().until(traj.atTime(kStopIntakeAndShootWaypoint)) + m_superstructure.intake(() -> true, () -> false) + .until(traj.atTime(kStopIntakeAndShootWaypoint)) ); - - //---TRIGGER LOGGERS - traj.atTime(kIntakeWaypoint).onTrue( - updateAutonEventMarkerLogger(kIntakeWaypoint) - ); - traj.atTime(kIntakeAndShootWaypoint).onTrue( - updateAutonEventMarkerLogger(kIntakeAndShootWaypoint) + m_superstructure.activateOuttakeShotCalc() + .until(traj.atTime(kStopIntakeAndShootWaypoint)) ); - traj.atTime(kStopIntakeAndShootWaypoint).onTrue( - updateAutonEventMarkerLogger(kStopIntakeAndShootWaypoint) - ); - - traj.atTime(kStopIntakeWaypoint).onTrue( - updateAutonEventMarkerLogger(kStopIntakeWaypoint) - ); + // Log every marker event so post-match AdvantageScope replays show + // which events fired and in what order. + traj.atTime(kIntakeWaypoint).onTrue(logEventMarker(kIntakeWaypoint)); + traj.atTime(kStopIntakeWaypoint).onTrue(logEventMarker(kStopIntakeWaypoint)); + traj.atTime(kShootWaypoint).onTrue(logEventMarker(kShootWaypoint)); + traj.atTime(kStopShootWaypoint).onTrue(logEventMarker(kStopShootWaypoint)); + traj.atTime(kIntakeAndShootWaypoint).onTrue(logEventMarker(kIntakeAndShootWaypoint)); + traj.atTime(kStopIntakeAndShootWaypoint).onTrue(logEventMarker(kStopIntakeAndShootWaypoint)); + } - traj.atTime(kShootWaypoint).onTrue( - updateAutonEventMarkerLogger(kShootWaypoint) - ); - traj.atTime(kStopShootWaypoint).onTrue( - updateAutonEventMarkerLogger(kStopShootWaypoint) - ); - } - + // ============================================================= + // DATA RECORD + // ============================================================= + + /* + * AdaptableAutonInfo + * + * Holds all the settings for one trajectory segment of an auton routine. + * Think of it as the "config card" you hand to the factory for each path. + * + * autonName — file name of the Choreo trajectory (no .traj extension) + * shooterTimeout — max seconds to wait for shot confirmation before giving up + * and moving on (only matters in non-SOTM mode) + * SOTM — Shoot On The Move: true = shoot while driving, + * false = stop and wait for shot confirmation + * delay — seconds to wait before starting this segment (0 = start right away). + * Useful when following another robot or letting defense clear out. + */ public final record AdaptableAutonInfo( - String autonName, - double shooterTimeout, + String autonName, + double shooterTimeout, boolean SOTM, - double delay + double delay ) {} -}; \ No newline at end of file +} From 2ab86b9941c27eccfc1958cd4f7297cf7b9f6511 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Wed, 13 May 2026 15:18:42 -0400 Subject: [PATCH 09/25] im not commit farming this space was just pissing me off --- src/main/java/frc/robot/autons/WaltAdaptableAutonFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/frc/robot/autons/WaltAdaptableAutonFactory.java b/src/main/java/frc/robot/autons/WaltAdaptableAutonFactory.java index 1117e36b..65156953 100644 --- a/src/main/java/frc/robot/autons/WaltAdaptableAutonFactory.java +++ b/src/main/java/frc/robot/autons/WaltAdaptableAutonFactory.java @@ -301,7 +301,7 @@ public AutoRoutine adaptableAuton(String routineName, AdaptableAutonInfo autonIn /** * NEW TERMINOLOGY: COAST OUT -- Coasting out means to let the motors keep their momentum when they stop, instead of * simply going to zero (stopping abruptly). - */ + */ // Something to think about is to coast out the swerve at the end of auto, to cover more ground to get closer to // fuel to pick up faster? // EX: See Citrus's (1678) autos From be1c0fa31195d834be87524dc38536e952e4a2e6 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Sat, 16 May 2026 18:37:30 -0400 Subject: [PATCH 10/25] format --- .../frc/robot/dashboards/AutonChooser.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/frc/robot/dashboards/AutonChooser.java b/src/main/java/frc/robot/dashboards/AutonChooser.java index cd381842..c5ed5af3 100644 --- a/src/main/java/frc/robot/dashboards/AutonChooser.java +++ b/src/main/java/frc/robot/dashboards/AutonChooser.java @@ -27,7 +27,10 @@ private record AutonEntry(String name, AdaptableAutonInfo infos) {} private record MultiAutonEntry(String name, AdaptableAutonInfo[] infos) {} private static final List s_multiAutons = new ArrayList<>(); - /* OLD AUTON NAMES */ + // ============================================================= + // OLD AUTON NAMES + // ============================================================= + //---1.5 CYCLES private final static String kLeftShootAndSweep = "LEFT Sweep 1.5 Cycle"; private final static String kRightShootAndSweep = "RIGHT Sweep 1.5 Cycle"; @@ -57,7 +60,10 @@ private record MultiAutonEntry(String name, AdaptableAutonInfo[] infos) {} private final static String kRightStressTestOverlap = "RIGHT Overlap Stress Test"; private final static String kRightStressTestTenTimes = "RIGHT Five Times Stress Test"; - /* NEW AUTON NAMES */ + // ============================================================= + // NEW AUTON NAMES + // ============================================================= + //---2 CYCLES private final static String kRightTrenchTwoCycleBumpReturn = "RIGHT Trench 2 Cycle Bump Return"; private final static String kRightTrenchTwoCycleBumpReturnFollow = "RIGHT Trench 2 Cycle Bump Return FOLLOW"; @@ -89,7 +95,10 @@ public static void initialize(WaltAdaptableAutonFactory adaptableAutonFactory) { m_chooser = new AutoChooser(); s_multiAutons.clear(); - /* OLD AUTON OPTIONS */ + // ============================================================= + // OLD AUTON OPTIONS + // old being relative -- from when we were still bump bot + // ============================================================= //---MAIN AUTONS addMultiAuton(kLeftShootAndSweep, new AdaptableAutonInfo(AutonK.kLeftOneJab, AutonK.kSweepShootingTimeout, false, 0), @@ -165,7 +174,9 @@ public static void initialize(WaltAdaptableAutonFactory adaptableAutonFactory) { new AdaptableAutonInfo(AutonK.kRightOneReverse, AutonK.kShootingTimeout, false, 0), new AdaptableAutonInfo(AutonK.kRightTwoJab, AutonK.kShootingTimeout, false, 0)); - /* NEW AUTON OPTIONS */ + // ============================================================= + // NEW AUTON OPTIONS + // ============================================================= //---2 CYCLES addMultiAuton(kRightTrenchTwoCycleBumpReturn, new AdaptableAutonInfo(AutonK.kRightOneBumpReturn, AutonK.kSOTMTimeout, true, 0), From a2ef16dd788220e308e8c9fe0b2ea1fea054d696 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Sat, 16 May 2026 18:37:49 -0400 Subject: [PATCH 11/25] formatting --- .../frc/robot/dashboards/AutonChooser.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/frc/robot/dashboards/AutonChooser.java b/src/main/java/frc/robot/dashboards/AutonChooser.java index cd381842..c5ed5af3 100644 --- a/src/main/java/frc/robot/dashboards/AutonChooser.java +++ b/src/main/java/frc/robot/dashboards/AutonChooser.java @@ -27,7 +27,10 @@ private record AutonEntry(String name, AdaptableAutonInfo infos) {} private record MultiAutonEntry(String name, AdaptableAutonInfo[] infos) {} private static final List s_multiAutons = new ArrayList<>(); - /* OLD AUTON NAMES */ + // ============================================================= + // OLD AUTON NAMES + // ============================================================= + //---1.5 CYCLES private final static String kLeftShootAndSweep = "LEFT Sweep 1.5 Cycle"; private final static String kRightShootAndSweep = "RIGHT Sweep 1.5 Cycle"; @@ -57,7 +60,10 @@ private record MultiAutonEntry(String name, AdaptableAutonInfo[] infos) {} private final static String kRightStressTestOverlap = "RIGHT Overlap Stress Test"; private final static String kRightStressTestTenTimes = "RIGHT Five Times Stress Test"; - /* NEW AUTON NAMES */ + // ============================================================= + // NEW AUTON NAMES + // ============================================================= + //---2 CYCLES private final static String kRightTrenchTwoCycleBumpReturn = "RIGHT Trench 2 Cycle Bump Return"; private final static String kRightTrenchTwoCycleBumpReturnFollow = "RIGHT Trench 2 Cycle Bump Return FOLLOW"; @@ -89,7 +95,10 @@ public static void initialize(WaltAdaptableAutonFactory adaptableAutonFactory) { m_chooser = new AutoChooser(); s_multiAutons.clear(); - /* OLD AUTON OPTIONS */ + // ============================================================= + // OLD AUTON OPTIONS + // old being relative -- from when we were still bump bot + // ============================================================= //---MAIN AUTONS addMultiAuton(kLeftShootAndSweep, new AdaptableAutonInfo(AutonK.kLeftOneJab, AutonK.kSweepShootingTimeout, false, 0), @@ -165,7 +174,9 @@ public static void initialize(WaltAdaptableAutonFactory adaptableAutonFactory) { new AdaptableAutonInfo(AutonK.kRightOneReverse, AutonK.kShootingTimeout, false, 0), new AdaptableAutonInfo(AutonK.kRightTwoJab, AutonK.kShootingTimeout, false, 0)); - /* NEW AUTON OPTIONS */ + // ============================================================= + // NEW AUTON OPTIONS + // ============================================================= //---2 CYCLES addMultiAuton(kRightTrenchTwoCycleBumpReturn, new AdaptableAutonInfo(AutonK.kRightOneBumpReturn, AutonK.kSOTMTimeout, true, 0), From 325d5b465caf4216dd556026b6b77f565fb456bf Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Sat, 16 May 2026 18:44:17 -0400 Subject: [PATCH 12/25] more formatting of autonchooser --- src/main/java/frc/robot/dashboards/AutonChooser.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/frc/robot/dashboards/AutonChooser.java b/src/main/java/frc/robot/dashboards/AutonChooser.java index c5ed5af3..5878b1a6 100644 --- a/src/main/java/frc/robot/dashboards/AutonChooser.java +++ b/src/main/java/frc/robot/dashboards/AutonChooser.java @@ -5,7 +5,6 @@ import java.util.List; import choreo.auto.AutoChooser; -import edu.wpi.first.hal.simulation.AddressableLEDDataJNI; import edu.wpi.first.wpilibj.DriverStation; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; import edu.wpi.first.wpilibj2.command.Command; @@ -56,9 +55,9 @@ private record MultiAutonEntry(String name, AdaptableAutonInfo[] infos) {} private final static String kLeftSweepAndDepot = "LEFT Bump Sweep and Depot"; //---STRESS TEST - private final static String kRightStressTestLong = "RIGHT Long Stress Test"; - private final static String kRightStressTestOverlap = "RIGHT Overlap Stress Test"; - private final static String kRightStressTestTenTimes = "RIGHT Five Times Stress Test"; + // private final static String kRightStressTestLong = "RIGHT Long Stress Test"; + // private final static String kRightStressTestOverlap = "RIGHT Overlap Stress Test"; + // private final static String kRightStressTestTenTimes = "RIGHT Five Times Stress Test"; // ============================================================= // NEW AUTON NAMES From c4bb985535a7892ca28aac1b5a2f5793044a0212 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Sat, 16 May 2026 19:29:23 -0400 Subject: [PATCH 13/25] EVEN MORE FORMATTING >:D --- .../frc/robot/dashboards/AutonChooser.java | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/main/java/frc/robot/dashboards/AutonChooser.java b/src/main/java/frc/robot/dashboards/AutonChooser.java index 5878b1a6..ffa73f40 100644 --- a/src/main/java/frc/robot/dashboards/AutonChooser.java +++ b/src/main/java/frc/robot/dashboards/AutonChooser.java @@ -13,6 +13,29 @@ import frc.robot.autons.WaltAdaptableAutonFactory; import frc.robot.autons.WaltAdaptableAutonFactory.AdaptableAutonInfo; +/** + * AutonChooser + * + * This class is responsible for deciding which autons we want to run, + * and in what order do those paths go in. + * + * FORMATTING: + * We like to keep things the same around here so nobody can get confused + * on the field and pick the wrong auton ;). We follow a format with how + * these autons are named, and thats in the following format: + * STARTING POSITION: Left or Right, or maybe even center. + * Are we starting in the Trench, on + * the bump, hell maybe even on the hub?! + * Knowing this prior to the match starting allows for the people + * setting the robot on the field know where they need to align it to. + * + * NUMBER OF CYCLES: One cycle, Two Cycle, or even Three. + * + * RETURN POSITION: Are we coming back over the bump? Or under the trench? + * + * MODIFIERS: Things such as a follow auto, delayed auto, going to the depot, + * etc. + */ public class AutonChooser { private static final String kPreheatTrajectory = "PreHeat"; @@ -26,6 +49,10 @@ private record AutonEntry(String name, AdaptableAutonInfo infos) {} private record MultiAutonEntry(String name, AdaptableAutonInfo[] infos) {} private static final List s_multiAutons = new ArrayList<>(); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // AUTON NAMES + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // ============================================================= // OLD AUTON NAMES // ============================================================= @@ -69,7 +96,7 @@ private record MultiAutonEntry(String name, AdaptableAutonInfo[] infos) {} private final static String kLeftTrenchTwoCycleBumpReturn = "LEFT Trench 2 Cycle Bump Return"; private final static String kLeftTrenchTwoCycleBumpReturnFollow = "LEFT Trench 2 Cycle Bump Return FOLLOW"; private final static String kRightTrechTwoCycleTrenchReturn = "RIGHT Trench 2 Cycle Trench Return"; - private final static String kRightTrechTwoCycleTrenchReturnDelay = "RIGHT Trench 2 Cycle Trench Return 5sec DELAY"; + private final static String kRightTrenchTwoCycleTrenchReturnDelay = "RIGHT Trench 2 Cycle Trench Return 5sec DELAY"; private final static String kLeftTrenchTwoCycleTrenchReturn = "LEFT Trench 2 Cycle Trench Return"; private final static String kRightBumpTwoCycleReverseToTrenchPlusTrenchOnly = "RIGHT Bump 2 Cycle Reverse to Trench + Trench ONLY"; private final static String kLeftBumpTwoCycleReverseToTrenchPlusTrenchOnly = "LEFT Bump 2 Cycle Reverse to Trench + Trench ONLY"; @@ -88,7 +115,11 @@ private record MultiAutonEntry(String name, AdaptableAutonInfo[] infos) {} private final static String kCenterPreload = "CENTER Preload"; // private final static String kRightDelayTest = "DELAY TEST - NOT FOR ACTUAL USE"; + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // ============================================================================ + // -- UTILITY METHODS + // ============================================================================ public static void initialize(WaltAdaptableAutonFactory adaptableAutonFactory) { m_adaptableAutonFactory = adaptableAutonFactory; m_chooser = new AutoChooser(); @@ -210,7 +241,7 @@ public static void initialize(WaltAdaptableAutonFactory adaptableAutonFactory) { new AdaptableAutonInfo(AutonK.kRightTwoTrenchReturn, AutonK.kShootingTimeout, false, 0), new AdaptableAutonInfo(AutonK.kRightTwoGoOut, AutonK.kSOTMTimeout, false, 0)); - addMultiAuton(kRightTrechTwoCycleTrenchReturnDelay, + addMultiAuton(kRightTrenchTwoCycleTrenchReturnDelay, new AdaptableAutonInfo(AutonK.kRightOneTrenchReturn, AutonK.kShootingTimeout, false, 5), new AdaptableAutonInfo(AutonK.kRightTwoTrenchReturn, AutonK.kShootingTimeout, false, 0), new AdaptableAutonInfo(AutonK.kRightTwoGoOut, AutonK.kSOTMTimeout, false, 0)); From bf43f38d3e201972dafa6377d5160fe32e59c8a5 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Sat, 16 May 2026 19:40:04 -0400 Subject: [PATCH 14/25] changed markdowns --- src/main/java/choreo/README.md | 30 +-- src/main/java/frc/robot/README.md | 58 ++--- .../frc/robot/subsystems/shooter/README.MD | 159 +++++++++++++ .../robot/subsystems/shooter/calc/README.MD | 225 ++++++++++++++++++ 4 files changed, 428 insertions(+), 44 deletions(-) create mode 100644 src/main/java/frc/robot/subsystems/shooter/README.MD create mode 100644 src/main/java/frc/robot/subsystems/shooter/calc/README.MD diff --git a/src/main/java/choreo/README.md b/src/main/java/choreo/README.md index 27a4e0b4..aa8d197c 100644 --- a/src/main/java/choreo/README.md +++ b/src/main/java/choreo/README.md @@ -22,26 +22,26 @@ We duplicated Choreo's library classes locally so we could experiment with **pos ### auto/ Handles autonomous routine selection and execution. -- `AutoChooser.java` - lets drivers select an auto routine from the dashboard -- `AutoFactory.java` - constructs auto routines by chaining trajectory segments together -- `AutoRoutine.java` - represents a full autonomous routine made up of multiple trajectories -- `AutoTrajectory.java` - wraps a single Choreo trajectory for command-based execution +- `AutoChooser.java`: lets drivers select an auto routine from the dashboard +- `AutoFactory.java`: constructs auto routines by chaining trajectory segments together +- `AutoRoutine.java`: represents a full autonomous routine made up of multiple trajectories +- `AutoTrajectory.java`: wraps a single Choreo trajectory for command-based execution ### trajectory/ Core trajectory representation and sampling logic. -- `Trajectory.java` - the main trajectory class, holds a list of samples and handles time-based lookups -- `TrajectorySample.java` - a single sampled state along a trajectory (pose, velocity, etc.) -- `SwerveSample.java` - trajectory sample specific to swerve drivetrains -- `DifferentialSample.java` - trajectory sample specific to differential drivetrains -- `EventMarker.java` - represents a timed event trigger within a trajectory +- `Trajectory.java`: the main trajectory class, holds a list of samples and handles time-based lookups +- `TrajectorySample.java`: a single sampled state along a trajectory (pose, velocity, etc.) +- `SwerveSample.java`: trajectory sample specific to swerve drivetrains +- `DifferentialSample.java`: trajectory sample specific to differential drivetrains +- `EventMarker.java`: represents a timed event trigger within a trajectory ### util/ Utility and configuration classes. -- `Choreo.java` - main entry point for loading trajectories from deploy files -- `ChoreoAlert.java` - handles driver station alerts related to Choreo -- `ChoreoAllianceFlipUtil.java` - flips trajectories for red/blue alliance mirroring -- `ChoreoArrayUtil.java` - array helper methods used internally -- `FieldDimensions.java` - field size constants used for alliance flipping -- `TrajSchemaVersion.java` - version tracking for the trajectory file format \ No newline at end of file +- `Choreo.java`: main entry point for loading trajectories from deploy files +- `ChoreoAlert.java`: handles driver station alerts related to Choreo +- `ChoreoAllianceFlipUtil.java`: flips trajectories for red/blue alliance mirroring +- `ChoreoArrayUtil.java`: array helper methods used internally +- `FieldDimensions.java`: field size constants used for alliance flipping +- `TrajSchemaVersion.java`: version tracking for the trajectory file format diff --git a/src/main/java/frc/robot/README.md b/src/main/java/frc/robot/README.md index 2515718b..c80dcfdd 100644 --- a/src/main/java/frc/robot/README.md +++ b/src/main/java/frc/robot/README.md @@ -14,72 +14,72 @@ The code is split into folders by responsibility. Each folder handles one aspect ## Top-level files -- `Robot.java` — Main robot class. Subsystems, commands, and bindings all get wired up here. This will have almost every concept that you would need to understand how to program in FRC! :wink: -- `Main.java` — Program entry point. You ~~probably~~ never need to touch this. -- `Constants.java` — Robot-wide constants (motor ports, PID values, speed limits, etc.). If you're looking for a magic number, it's probably here. NO MAGIC NUMBERS OUTSIDE OF HERE!!!! -- `FieldConstants.java` — Field dimensions and target positions. All thanks to our goats, Mechanical Advantage (6328) +- `Robot.java`: Main robot class. Subsystems, commands, and bindings all get wired up here. This will have almost every concept that you would need to understand how to program in FRC! :wink: +- `Main.java`: Program entry point. You ~~probably~~ never need to touch this. +- `Constants.java`: Robot-wide constants (motor ports, PID values, speed limits, etc.). If you're looking for a magic number, it's probably here. NO MAGIC NUMBERS OUTSIDE OF HERE!!!! +- `FieldConstants.java`: Field dimensions and target positions. All thanks to our goats, Mechanical Advantage (6328) -### [autons/](autons/) — Autonomous routines +### [autons/](autons/): Autonomous routines Defines what the robot does during the 15-second autonomous period at the start of a match, where no driver input is allowed. #### this is where we start praying :pray: -- `WaltAdaptableAutonFactory.java` — Builds auto routines by chaining Choreo trajectory segments together. +- `WaltAdaptableAutonFactory.java`: Builds auto routines by chaining Choreo trajectory segments together. --- -### [dashboards/](dashboards/) — Driver station UI +### [dashboards/](dashboards/): Driver station UI Controls what drivers see on their dashboard laptop. -- `AutonChooser.java` — Dropdown that lets drivers pick which auto routine to run before a match. +- `AutonChooser.java`: Dropdown that lets drivers pick which auto routine to run before a match. --- -### [generated/](generated/) — Auto-generated files +### [generated/](generated/): Auto-generated files Created by external tools, not written by hand. Don't edit these directly unless you know what you're doing LOL -- `TunerConstants.java` — Swerve drive tuning constants from CTRE's Tuner X (THANK GOD FOR TUNERX). +- `TunerConstants.java`: Swerve drive tuning constants from CTRE's Tuner X (THANK GOD FOR TUNERX). --- -### [subsystems/](subsystems/) — Robot mechanisms +### [subsystems/](subsystems/): Robot mechanisms Each file represents a physical mechanism on the robot and the code that controls it. This is where most of the action happens. -#### [subsystems/shooter/](subsystems/shooter/) — Shooting mechanism +#### [subsystems/shooter/](subsystems/shooter/): Shooting mechanism The fun part. I had SO much fun on this part :D -- `Shooter.java` — Master control over the entire shooter subsystem. -- `Hood.java` — Adjustable hood angle and other variety. -- `Turret.java` — Turret rotation and tracking. -- `TurretVisualizer.java` — 3D visualization for turret state. -- `FuelSim.java` — Game piece physics simulation for testing. +- `Shooter.java`: Master control over the entire shooter subsystem. +- `Hood.java`: Adjustable hood angle and other variety. +- `Turret.java`: Turret rotation and tracking. +- `TurretVisualizer.java`: 3D visualization for turret state. +- `FuelSim.java`: Game piece physics simulation for testing. -##### [subsystems/shooter/calc](subsystems/shooter/calc) — Shot calculations +##### [subsystems/shooter/calc](subsystems/shooter/calc): Shot calculations this is where it hit the fan :scream: :scream: :scream: -- `ShooterCalc.java` — Shot math and distance-based calculations on its own thread. -- `ShotCalculator.java` — Other shot math and distance-based calculations. +- `ShooterCalc.java`: Shot math and distance-based calculations on its own thread. +- `ShotCalculator.java`: Other shot math and distance-based calculations. #### Other subsystems -- `Intake.java` — Picks up game pieces from the ground. -- `Indexer.java` — Feeds game pieces from the intake into the shooter. The middleman. -- `Superstructure.java` — Coordinates intake, indexer, and shooter together so nothing fires when it shouldn't. -- `Swerve.java` — Swerve drivetrain. Makes the robot go vroom in any direction. +- `Intake.java`: Picks up game pieces from the ground. +- `Indexer.java`: Feeds game pieces from the intake into the shooter. The middleman. +- `Superstructure.java`: Coordinates intake, indexer, and shooter together so nothing fires when it shouldn't. +- `Swerve.java`: Swerve drivetrain. Makes the robot go vroom in any direction. --- -### [vision/](vision/) — Target tracking +### [vision/](vision/): Target tracking Uses cameras to detect and track field targets for automatic aiming. The robot can see better than most of us at this point. -- `WaltCamera.java` — Camera wrapper. -- `Detection.java` — Detects game pieces. -- `VisionSim.java` — Simulates vision for testing. +- `WaltCamera.java`: Camera wrapper. +- `Detection.java`: Detects game pieces. +- `VisionSim.java`: Simulates vision for testing. ---- \ No newline at end of file +--- diff --git a/src/main/java/frc/robot/subsystems/shooter/README.MD b/src/main/java/frc/robot/subsystems/shooter/README.MD new file mode 100644 index 00000000..542d0ae8 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/shooter/README.MD @@ -0,0 +1,159 @@ +# /shooter + +The fun part. This folder contains everything responsible for launching game pieces: the flywheel, the adjustable hood, and a full-range turret. Together they handle spinning up to speed, aiming at the correct angle, tracking a target across the field, and knowing when a ball has actually left the robot (without a sensor in the ball path, yes, really). + +All of the actual shot math lives in [`calc/`](calc/), which runs on a background thread so it doesn't steal time from the robot loop. The classes here are purely responsible for controlling real hardware. + +--- + +## Files + +- `Shooter.java`: Master subsystem. Owns the flywheel motors, holds references to Hood and Turret, handles ball detection, and applies all shot parameters from the calc thread. +- `Turret.java`: Controls the turret's rotation. Homes itself using two absolute encoders with a fancy math algorithm (more on that below). +- `Hood.java`: Controls the hood's pitch angle. Homes by driving into a hard stop and watching the current spike. +- `FuelSim.java`: *(Commented out)* Physics simulation of ball trajectories for visualization in sim mode. Still here if we want it back. +- `TurretVisualizer.java`: *(Commented out)* 3D turret visualization for AdvantageScope. + +--- + +## Shooter.java + +The main subsystem. Two Kraken X44s spin a flywheel, with Motor B following Motor A in `Opposed` mode (they spin opposite directions but push the ball the same way). `Shooter` then drives `Hood` and `Turret` with setpoints received from `ShooterCalc`, which runs at 75 Hz on its own thread. + +### Flywheel Control + +Velocity is controlled via `VelocityTorqueCurrentFOC` (Slot 1). Torque-current feedback gives tighter velocity regulation under load vs plain voltage control. Commanding velocity = 0 switches to `CoastOut` instead, which prevents a stuck closed-loop error sitting at zero. + +```java +void setShooterVelocity(double rotPerSec) // direct +Command shootFromCalc() // uses m_calcFlywheelVelocityRotPerSec +``` + +`isShooterSpunUp()` returns true when the closed-loop error is within ±0.5 RPS of target. + +### Ball Shot Detection + +No optical sensor. The flywheel decelerates sharply when a ball engages the wheels, so we just watch the acceleration: + +``` +Threshold: acceleration <= -3.0 RPS/s +Gate: motor must actually be in velocity control mode +``` + +When a detection fires, a timer starts. The shot is "confirmed" after: +- The ball detection clears (acceleration recovers) +- 1.2 seconds have elapsed on the timer +- The motor is still in shoot mode + +This debounced trigger (`getBallShotDebounceTrg()`) is what tells the rest of the robot a ball has left, so it can advance the indexer or track shot count. Pretty neat for something with no sensor. + +### Driver RPS Tweaks + +Drivers can trim the computed flywheel speed without breaking anything: + +- **Dynamic**: ±5% of the calculated RPS +- **Static**: ±`kDriverRPSIncreaseD` per button press +- **Reset**: back to 0 on release + +All tweaks are clamped to `[0, kShooterMaxRPSd]`. Gated behind `kAllowDriverRPSTweak` if you want to turn it off. + +### What periodic() Does + +Every loop: +1. Caches turret position into a volatile field (ShooterCalc reads this from its thread). +2. Fetches the latest `ShotCalcOutputs` from the background thread. +3. Sends turret position + velocity feedforward to `Turret`. +4. Sends hood reference to `Hood` (if homed). +5. Applies driver RPS tweaks with clamping. +6. Refreshes the spun-up check. +7. Logs everything. + +A `Tracer` is wired up for epoch timing diagnostics if you're hunting loop overruns. + +### NT Tunable Overrides + +- `/Shooter/shooterRPSOverride`: force a specific flywheel RPS (must be enabled in NT) +- `/Shooter/hoodRotsOverride`: force a specific hood position (must be enabled in NT) + +Useful when building LERP tables. Leave them disabled during actual matches. + +--- + +## Turret.java + +The turret rotates on a Kraken X44 (FOC, on the Canivor bus) using `PositionVoltage` control. It also carries two absolute encoders, not for redundancy, but because one encoder alone can't tell you absolute position if the turret has more than one full rotation of range. + +### Dual-Encoder Absolute Position Recovery + +Two encoders are geared at different ratios: + +- **Encoder A** (CANcoder): 10-tooth driving a 100-tooth output, so one full encoder reading every 36° of turret travel +- **Encoder B** (DutyCycleEncoder, DIO 3): 19-tooth driving a 100-tooth output, so one full encoder reading every ~18.95° of turret travel + +Because these periods are incommensurate, at any given absolute turret angle, only one combination of (Encoder A reading, Encoder B reading) is possible. `calcTurretAngleLCM()` exploits this: + +1. Use Encoder A's reading to generate every candidate absolute angle within the turret's range (one per integer multiple of the 36° period). +2. For each candidate, predict what Encoder B *should* read at that angle. +3. Compare predicted vs actual Encoder B, wrapping the difference to ±180° to handle rollover. +4. Return the candidate with the smallest error. + +This runs on construction (`homeTurret(true)`) and also every loop for logging. If both encoders agree, you can trust the position even after a power cycle. + +### Snapback Logic + +The turret has soft limits but also a physical range. If the turret is commanded near one limit (e.g., it's at +0.4 rot heading toward +0.5 rot max) and the target wraps around the other side, `calcAzimuth` (in ShooterCalc) adds ±1 full rotation to the setpoint so the turret goes the short way around instead of slamming into a hardstop. `m_isSnappingBack` flags when the error is large enough that this is actively happening. + +### Key Methods + +```java +void homeTurret(boolean useLCM) // true = dual-encoder fusion; false = kInitPosition +void setTurretPos(double rots, double velFF) // main control path, called from Shooter.periodic() +void setTurretLock(boolean locked) // lock at current position +void lockAndSetTurretLockPos(double lockPos) // lock at a specific angle +void setIntaking(boolean intaking) // freeze the turret during intake +``` + +--- + +## Hood.java + +The hood controls launch angle. A TalonFXS drives it, and position is controlled via `PositionVoltage` (FOC disabled here). + +### Homing + +The hood homes by driving slowly into its physical lower limit and waiting for the stator current to spike above 5A (sustained for 0.125s via a `Debouncer`). When it stalls: +- Position is set to `kHoodAbsoluteMinRots`. +- Soft limits are re-applied. +- `m_isHoodHomed` flips to true. + +There's a 5-second timeout in case something goes wrong. If it times out or gets interrupted, the hood brakes and logs a warning. + +> Note: In the current build, the homing command is not set as a default command. The hood is initialized with `setPosition(0)` directly in the constructor and immediately considered homed. `hoodCurrentSenseHomingCmd()` is still available if you want to actually run the homing routine. + +### Key Methods + +```java +void setHoodPos(double rots) // set target position +Command hoodCurrentSenseHomingCmd() // full homing routine (returns a command) +boolean isHoodHomed() +boolean atPosition() // within kHoodMaxErrD of setpoint +``` + +--- + +## How It All Fits Together + +``` +75 Hz Thread (ShooterCalc) + reads: swerve state, volatile turret position + writes: volatile ShotCalcOutputs + +50 Hz Robot Loop (Shooter.periodic) + reads: ShotCalcOutputs + drives: Turret setpoint + velocity FF + drives: Hood setpoint + drives: Flywheel velocity (from operator commands, using calc output) + reads: flywheel accel -> ball detection -> debounced shot trigger +``` + +The calc thread running at 75 Hz means shot math never blocks motor outputs, even when the iterative convergence takes a bit longer on a given cycle. diff --git a/src/main/java/frc/robot/subsystems/shooter/calc/README.MD b/src/main/java/frc/robot/subsystems/shooter/calc/README.MD new file mode 100644 index 00000000..191f5ed6 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/shooter/calc/README.MD @@ -0,0 +1,225 @@ +# /calc + +This is where it all hit the fan :D (:sob: :sob: :sob:) + +This folder handles all of the math that turns "robot is at this position going this fast" into "set flywheel to X RPS, hood to Y rotations, turret to Z degrees." It runs on a **background thread** at 75 Hz so none of this touches the main robot loop. Two classes: `ShooterCalc` coordinates everything and manages the thread, and `ShotCalculator` contains the actual ballistics: tables, physics, iterative moving-shot convergence, the whole deal. + +--- + +## Files + +- `ShooterCalc.java`: The orchestrator. Runs the background Notifier, determines which target to aim at, calls into `ShotCalculator` for shot parameters, computes turret azimuth, and stores results in volatile fields for `Shooter.periodic()` to read. +- `ShotCalculator.java`: Pure ballistics utility. Contains the interpolation tables, physics equations, and the iterative moving-shot algorithm. No state of its own (mostly static methods and final records). + +--- + +## ShooterCalc.java + +### Threading + +`ShooterCalc` uses a WPILib `Notifier` to run `calcCallback()` at 75 Hz on a background thread: + +```java +m_notifier.startPeriodic(Hertz.of(75)); // runs calcCallback() in a loop +``` + +Results are stored in `volatile` fields so `Shooter.periodic()` (running on the main thread) can safely read them without locking: + +```java +private volatile ShotCalcOutputs m_shotCalcOutputs; +private static volatile Translation3d m_aimTarget; +private static volatile boolean m_isPassingFlag; +``` + +`Shooter.periodic()` caches the turret position into a `volatile double` at the top of every loop, and ShooterCalc reads this from its thread. This is intentional and noted in the code: do not move that cache line. + +### What calcCallback() Does Every Cycle + +1. Grabs the current `SwerveDriveState` from a thread-safe supplier. +2. Converts module states to field-relative `ChassisSpeeds`. +3. Determines the **target** via `calculateTarget()`. +4. Calls `calcShot()` to get flywheel speed, hood angle, and azimuth. +5. Logs everything. +6. Times itself and logs the loop duration. + +### Target Selection: `calculateTarget()` + +The robot shoots at different targets depending on where it is on the field: + +``` +Robot on own side of the hub -> SHOOTING MODE + Target: enemy hub center (alliance-flipped appropriately) + +Robot past the hub into opponent territory -> PASSING MODE + Target: a point near the sideline (~2m in from the wall) + - Robot left of field center -> left passing point + - Robot right of field center -> right passing point +``` + +`m_isPassingFlag` is set/cleared here and read by `ShotCalculator` to select the right LERP table. + +There's also a "no-passing zone" check (`robotInNoPassingZone`) that detects when the robot is near the opponent hub, where some passing angles may not be safe. The check uses field X/Y bounds and alliance color. + +### Turret Azimuth: `calcAzimuth()` + +Given a target position, robot pose, current turret heading, and field-relative chassis speeds, this computes the turret setpoint (in rotations) and a velocity feedforward. + +1. **Compute turret pivot position**: offset the robot center by `kTurretOffsetX_m` and `kTurretOffsetY_m` using the robot's heading angle. Uses raw `cos/sin` to avoid allocations. + +2. **Compute desired yaw**: `atan2` from turret pivot to target. + +3. **Convert to turret-relative rotations**: subtract the turret's zero direction (robot heading + `kTurretAngleOffsetRad`), normalize to `[-0.5, 0.5]` rotations. + +4. **Lateral bias compensation**: a sinusoidal correction applied based on the turret's angle relative to the robot: + ``` + bias = gain * sin(turretAngleRelToRobot) + ``` + This compensates for balls curving slightly left or right depending on which direction the turret is facing. Tunable via `/ShotCalc/lateralBiasGainRots`. + +5. **Snapback**: if the turret is currently on the positive side of center (`turretHeadingRots > 0`) and `desiredAngle + 1` is still within max range, add 1 rotation to the setpoint. Same logic minus 1 on the negative side. This keeps the turret tracking through a target that would otherwise require crossing a hard stop. + +6. **Velocity feedforward**: uses tangential velocity of the target relative to the turret pivot, minus robot rotational rate: + ``` + tangentialVel = (toTargetX * vx - toTargetY * vy) / distance + turretFFRadPerSec = tangentialVel / distance - omega + ``` + +### Output Records + +**`AzimuthCalcDetails`**: turret-specific outputs +- `turretReferenceRots`: final (snapback-safe) turret setpoint +- `turretVelocityFF`: feedforward in rad/s +- `turretX`, `turretY`: turret pivot in field coordinates (for logging) +- `fieldYawRad`: desired absolute yaw angle in radians +- `currentFieldYawRad`: current turret yaw in field coordinates +- `rawDesiredRotations`: pre-snapback angle (useful for debugging) + +**`ShotCalcOutputs`**: everything `Shooter.periodic()` needs +- `turretCalcDetails`: the `AzimuthCalcDetails` record above +- `shotData`: `ShotDataLerp` with flywheel RPS, hood angle, TOF, and predicted target +- `turretReferenceRots`: pulled up from `turretCalcDetails` for convenience +- `hoodReferenceRots`: hood angle converted from radians to rotations +- `shooterReferenceRps`: flywheel speed converted from rad/s to RPS + +--- + +## ShotCalculator.java + +Pure ballistics. No threads, no state besides the static tables and tunable constants. + +### Interpolation Tables: `ShotLerpTable` + +WPILib's `InterpolatingDoubleTreeMap` allocates on every lookup (it uses `TreeMap`). `ShotLerpTable` is a zero-allocation sorted-array replacement: + +- `keys[]`: distances in meters, sorted ascending +- `exitVels[]`: exit velocity in rad/s +- `hoodAngles[]`: hood angle in radians +- `tofs[]`: time of flight in seconds +- `drags[]`: drag coefficients + +Lookup is a binary search + linear interpolation. No objects created on the hot path. + +The Builder takes inputs in human-friendly units (rot/s, rotations) and converts to SI internally: +```java +shot.add(dist_m, rps, hoodRots, tof_s, drag); +// internally: rps * 2pi = rad/s, hoodRots * 2pi = radians +``` + +### Shot Tables + +There are two active tables: + +**`kShotTable`**: normal hub shots, 14 distance points from 0.985m to 8.627m +- Flywheel: ~39.8 to 68.8 RPS (raw values have a `kScoringRPSBoost` of -0.2 applied) +- Hood: 0 to 1.65 rad +- TOF: 0.5s at all distances (used for drag compensation math, not actual flight time) + +**`kPassingTable`**: passing to alliance partners, 29 distance points from 4.008m to 14.355m +- Flywheel: ~48.75 to 105.14 RPS (`kRPSBoost` = +0.75 applied; `kLongRangeRPSBoost` = +0.35 for distances >= ~10.4m) +- Hood: ~0.70 to 1.16 rad +- TOF: 0.254s at all distances + +~~**`kAngryTurretTable`**~~: flat-trajectory passing shots for when the hood physically can't go up. Commented out (32 points were in there). Preserved in case it's ever needed again. + +### Iterative Moving-Shot: `iterativeMovingShotFromInterpolationMap()` + +This is the core of on-the-move shooting. It converges on shot parameters that account for the robot moving during ball flight. Up to 8 iterations: + +``` +Start: table lookup at current distance to target + +Each iteration: + 1. Apply drag-compensated drift time to compute predicted target position: + driftT = (1 - e^(-c * tof)) / c where c = drag coefficient (default 0.5) + predX = targetX - vxLaunch * driftT + predY = targetY - vyLaunch * driftT + 2. Recalculate distance to predicted position. + 3. Look up new flywheel speed, hood angle, TOF from table. + 4. Check convergence: + |delta hood| < 0.05 rad + |delta exitVel| < 0.5 rad/s + |delta predPos| < 5mm + |delta TOF| < 5ms + 5. If converged, break early. +``` + +`vxLaunch` and `vyLaunch` are the actual velocity of the turret pivot point, accounting for both robot linear velocity and rotational velocity (the turret pivot is offset from the robot center, so rotation contributes a tangential component). + +The drag compensation is tunable via `/ShotCalc/sotmDragCoeff`. Setting it to 0 disables drag compensation entirely. The default 0.5 was empirically determined. There's a comment in the code that says `2974 IS OUR LUCKY NUMBER` and another that says `254 RAHHHHHHHHHHHHHHH` - both teams' drag coefficients were apparently considered at some point. We landed on 0.5. + +### Physics Utilities + +**`calculateAngleFromVelocity()`**: projectile motion formula to find launch angle from exit speed and distance. Uses the full two-solution quadratic (`vel^4` under the root), picking the low-angle solution. + +**`calculateTimeOfFlightSec()`**: simple horizontal flight time: +``` +tof = distance / (velocity * cos(launchAngle)) +where launchAngle = pi/2 - hoodAngle +``` + +**`calculateShotFromFunnelClearance()`**: 2D trajectory solver that finds the velocity and angle needed for a ball to simultaneously clear a funnel above the robot *and* land in the target funnel. Solves two simultaneous linear equations derived from parabolic motion, no iteration needed, closed-form algebra. See the Desmos link in the comments if you want to visualize it. + +**`getDistanceToTargetM()`**: zero-allocation hot-path version: takes raw doubles, manually applies the turret offset transform using precomputed `cos/sin`, returns Euclidean distance. Used everywhere on the critical path. + +### Data Records + +**`ShotData`**: base shot parameters +- `exitVelocity`: rad/s +- `hoodAngle`: radians +- `target`: `Translation3d` of the aim point + +**`ShotDataLerp`**: extends `ShotData` with `tofSec`, the time of flight in seconds from the LERP table. + +Both records have `acceptLogging()` methods that push their fields to NetworkTables. + +### New Fuel Adjustment *(currently disabled)* + +`kRPSReductionNeeded = false`. When enabled, a linear regression over `kReductionDistances`/`kReductionAmount` would compute a per-distance RPS reduction applied during table building. The idea was to slightly back off flywheel speed for certain fuel states. Not active. + +--- + +## Tuning Reference + +| NT Key | Default | What It Does | +|--------|---------|-------------| +| `/ShotCalc/sotmDragCoeff` | 0.5 | Drag coefficient for SOTM lateral compensation | +| `/ShotCalc/lateralBiasGainRots` | `kTurretLateralBiasGainRots` | Sinusoidal lateral bias correction gain | +| `Shooter/Calculator/RPSBoost` | 0.75 | Boost applied to passing table RPS | + +All tunable via `WaltTunable`, enable in NT and set a value. Changes apply to the next calc cycle. + +--- + +## Quick Data Flow + +``` +calcCallback() @ 75 Hz + calculateTarget() -> m_aimTarget (volatile) + iterativeMovingShotFromInterpolationMap() + ShotLerpTable lookup + convergence loop + dragCompensatedTOF() + -> ShotDataLerp (flywheel vel, hood angle, predicted target, TOF) + calcAzimuth() + -> AzimuthCalcDetails (turret setpoint, velocity FF) + -> ShotCalcOutputs (volatile) -> read by Shooter.periodic() +``` From 3300ee9171b784618eff2dbc33f5a305f34f51c0 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Sat, 16 May 2026 20:54:39 -0400 Subject: [PATCH 15/25] whole buncha MY slop fixed --- .../subsystems/shooter/calc/ShooterCalc.java | 336 +++++++++++------- .../shooter/calc/ShotCalculator.java | 183 +++++----- 2 files changed, 294 insertions(+), 225 deletions(-) diff --git a/src/main/java/frc/robot/subsystems/shooter/calc/ShooterCalc.java b/src/main/java/frc/robot/subsystems/shooter/calc/ShooterCalc.java index 41b35ea8..19f21465 100644 --- a/src/main/java/frc/robot/subsystems/shooter/calc/ShooterCalc.java +++ b/src/main/java/frc/robot/subsystems/shooter/calc/ShooterCalc.java @@ -13,9 +13,6 @@ import edu.wpi.first.math.geometry.Translation3d; import edu.wpi.first.math.kinematics.ChassisSpeeds; import edu.wpi.first.math.kinematics.SwerveDriveKinematics; -import edu.wpi.first.networktables.BooleanPublisher; -import edu.wpi.first.networktables.BooleanTopic; -import edu.wpi.first.networktables.NetworkTableInstance; import edu.wpi.first.wpilibj.Notifier; import edu.wpi.first.wpilibj.Timer; import edu.wpi.first.wpilibj.DriverStation.Alliance; @@ -38,15 +35,48 @@ import frc.util.WaltTunable; +/* + * ShooterCalc + * + * All the shot math runs here, on a background thread via WPILib's Notifier, + * so it never eats into the main 50 Hz robot loop. The thread runs at 75 Hz + * and drops the result into a volatile field. Shooter.periodic() just reads + * that field every loop, no locking needed. + * + * Quick vocabulary: + * SOTM (Shoot On The Move) - the iterative algorithm that compensates for + * the robot moving while the ball is in the air. If we're driving right + * at 1 m/s and the ball takes 0.3 s to reach the hub, we aim a bit left. + * Volatile - Java keyword that tells the JVM other threads may write this. + * Without it, the main thread might see a stale cached value. + * Snapback - when the turret is near a hardstop and the target wraps to the + * other side, we add +-1 full rotation to the setpoint so the turret goes + * the short way around instead of slamming into the limit. + * + * One important rule: Shooter.periodic() caches the turret position into a + * volatile double at the top of every loop. ShooterCalc reads it from the + * background thread. Don't move that line or the thread safety breaks. + */ public class ShooterCalc { private static final String kLogTab = "ShotCalc"; + + // Tunable lateral bias correction. Balls curve left/right depending on which way + // the turret faces (sinusoidal - no correction straight ahead, max at 90 degrees). + // Enable and tune via /ShotCalc/lateralBiasGainRots in NetworkTables. private static final WaltTunable kLateralBiasTuner = new WaltTunable("/ShotCalc/lateralBiasGainRots", kTurretLateralBiasGainRots); + // ---- INPUTS ---- + // Read every callback cycle. SwerveDriveState comes through a thread-safe supplier + // from CTRE's odometry thread. Turret position is written by Shooter.periodic() into + // a volatile field and read here. + private final Supplier m_threadsafeSwerveDriveStateSup; private final DoubleSupplier m_turretPosRotsSup; private final SwerveDriveKinematics m_swerveKinematics = new SwerveDriveKinematics(TunerConstants.moduleTranslations); + // ---- LOGGERS ---- + private final Pose3dLogger log_globalShotTarget = WaltLogger.logPose3d(kLogTab, "globalTarget"); // private final Pose3dLogger log_calculatedShotTarget = WaltLogger.logPose3d(kLogTab, "shotCalcTarget"); private final DoubleLogger log_rawDesiredTurretRot = WaltLogger.logDouble(kLogTab, "rawDesiredTurretRots"); @@ -60,11 +90,11 @@ public class ShooterCalc { // private static final Pose3dLogger log_turretFieldPose = WaltLogger.logPose3d(kLogTab, "turretFieldPose"); private final BooleanLogger log_robotPastOurZoneX = WaltLogger.logBoolean(kLogTab, "robotInOurZone"); private final BooleanLogger log_robotInHubPassingZone = WaltLogger.logBoolean(kLogTab, "robotInHubPassingZone"); - private final BooleanLogger log_canTurretShoot = WaltLogger.logBoolean(kLogTab, "canTurretShoot"); - private final BooleanLogger log_inTrenchZone = WaltLogger.logBoolean(kLogTab, "inTrenchZone"); - private final BooleanLogger log_underTrench = WaltLogger.logBoolean(kLogTab, "underTrench"); - // Precomputed doubles for calculateTarget zone checks + // ---- PRECOMPUTED CONSTANTS ---- + // Calculated once at startup and reused every callback to skip redundant lookups. + + // Hub X positions for the zone-boundary check in calculateTarget() private static final double kRedHubCenterX = AllianceZoneUtil.redHubCenter.getX(); private static final double kBlueHubCenterX = AllianceZoneUtil.blueHubCenter.getX(); private static final double kCenterFieldYM = AllianceZoneUtil.centerField_y_pos.baseUnitMagnitude(); @@ -74,49 +104,64 @@ public class ShooterCalc { new Translation3d(ShooterK.kPassingXAsDouble, FieldConstants.fieldWidth - 2, 0); private static final Translation3d kRightPassTarget = new Translation3d(ShooterK.kPassingXAsDouble, 2, 0); - + // private static final Translation3d kLeftPassPastHubTarget = // new Translation3d(ShooterK.kPassingXAsDouble + 0.5, FieldConstants.fieldWidth - 1.5, 0); // private static final Translation3d kRightPassPastHubTarget = // new Translation3d(ShooterK.kPassingXAsDouble + 0.5, 1.5, 0); - // Pre-allocated ballTrajectory log buffer — reused each callback to avoid array allocation + // Pre-allocated log buffer so we don't create a new Translation3d[] every callback private static final Translation3d[] m_ballTrajBuffer = new Translation3d[2]; - // Precomputed doubles for hot-path unit conversions + // Turret limits as raw doubles to skip .in(Rotations) calls on the hot path private static final double kTurretMinRotsD = kTurretMinRots.in(Rotations); private static final double kTurretMinRotsMagnitudeD = kTurretMinRots.magnitude(); private static final double kTurretMaxRotsD = kTurretMaxRots.in(Rotations); private static final double kTurretMaxRotsMagnitudeD = kTurretMaxRots.magnitude(); - private final ShotDataLerp kEmptyShotData = new ShotDataLerp(0.0, 0.0, new Translation3d(), 0.0); - private final AzimuthCalcDetails kEmptyAzimuthCalcDetails = new AzimuthCalcDetails(0, 0, 0, 0, 0, 0, 0); - private final ShotCalcOutputs kEmptyShotCalcOutputs = new ShotCalcOutputs(kEmptyAzimuthCalcDetails, kEmptyShotData, 0, 0, 0); + // ---- VOLATILE STATE ---- + // Written by the Notifier thread, read by the main thread. + // volatile means the main thread always reads the latest write, no lock needed. private volatile boolean m_useStaticShot = true; private static volatile Translation3d m_aimTarget = Translation3d.kZero; - private volatile ShotCalcOutputs m_shotCalcOutputs = kEmptyShotCalcOutputs; - private static volatile boolean m_isPassingFlag = false; + private volatile ShotCalcOutputs m_shotCalcOutputs; + private static volatile boolean m_isPassingFlag = false; // true when robot is past the hub private static final BooleanSupplier m_isPassing = () -> m_isPassingFlag; - private static volatile boolean m_canTurretShoot = false; - private static volatile boolean m_underTrench = false; + + // ---- THREAD + TIMER ---- private final Notifier m_notifier = new Notifier(this::calcCallback); - private final Timer m_calcTimer = new Timer(); + private final Timer m_calcTimer = new Timer(); // how long each callback takes + + // ---- RUNTIME STATE ---- private boolean isRed = WaltDriverStation.getAlliance().orElse(Alliance.Blue) == Alliance.Red; - // private double robotX; - // private double robotY; - private boolean robotInNoPassingZone; + private boolean robotInNoPassingZone; // true when near opponent hub in the no-pass zone + + + // ============================================================= + // CONSTRUCTOR + // ============================================================= public ShooterCalc(Supplier threadsafeSwerveDriveStateSup, DoubleSupplier turretPosSup) { m_threadsafeSwerveDriveStateSup = threadsafeSwerveDriveStateSup; m_turretPosRotsSup = turretPosSup; + // Initialize with empty values so Shooter.periodic() never gets null back + ShotDataLerp emptyShotData = new ShotDataLerp(0.0, 0.0, new Translation3d(), 0.0); + AzimuthCalcDetails emptyAzimuth = new AzimuthCalcDetails(0, 0, 0, 0, 0, 0, 0); + m_shotCalcOutputs = new ShotCalcOutputs(emptyAzimuth, emptyShotData, 0, 0, 0); + m_notifier.setName("ShooterCalc"); m_notifier.startPeriodic(Hertz.of(75)); // 2x slower than robot loop } + + // ============================================================= + // PUBLIC API + // ============================================================= + public void shouldUseStaticShot(boolean should) { m_useStaticShot = should; } @@ -125,43 +170,26 @@ public static Translation3d getLatestAimTarget() { return m_aimTarget; } + // Called by Shooter.periodic() every loop to grab the latest shot parameters. + // Reading one volatile reference is about as cheap as it gets. public ShotCalcOutputs getLatestShotCalcOutputs() { return m_shotCalcOutputs; } + // ShotCalculator reads this to pick kShotTable vs kPassingTable without + // needing to hold a reference back to this class. public static BooleanSupplier isPassing() { return m_isPassing; } - public static boolean canTurretShoot() { - return m_canTurretShoot; - } - - public static boolean getUnderTrench() { - return m_underTrench; - } - - /** - * first checks if we're passing. If we're not passing, then we can shoot whenever - * If we are passing, it checks if we're NOT in the unable-to-pass range. If we're not in it, then the turret can shoot! - * - * lowk should be checking if we are IN THE RANGE rather than greater than the outsides, but this is cope for now cuz sadness - */ - private void refreshCanTurretShoot() { - if (isPassing().getAsBoolean()) { - if (m_turretPosRotsSup.getAsDouble() > kTurretMinNotAbleToPassRange && m_turretPosRotsSup.getAsDouble() < kTurretMaxNotAbleToPassRange/* && !robotInNoPassingZone */) { - m_canTurretShoot = true; - } else { - m_canTurretShoot = false; - } - log_canTurretShoot.accept(m_canTurretShoot); - } else { - m_canTurretShoot = true; //the turret can shoot anytime we are not passing - log_canTurretShoot.accept(m_canTurretShoot); - } - } + // ============================================================= + // BACKGROUND THREAD LOOP + // ============================================================= + // Runs at 75 Hz on the Notifier's background thread. + // calculateTarget() must go first because it sets m_isPassingFlag, + // which calcShot() reads through ShotCalculator.isPassing() to pick the right LERP table. private void calcCallback() { m_calcTimer.restart(); // getters from outside @@ -170,12 +198,9 @@ private void calcCallback() { ChassisSpeeds robotChassisSpeeds = ChassisSpeeds.fromRobotRelativeSpeeds( m_swerveKinematics.toChassisSpeeds(swerveState.ModuleStates), robotPose.getRotation()); double turretPositionRots = m_turretPosRotsSup.getAsDouble(); - Pose3d turretPose = new Pose3d(robotPose).transformBy(kTurretTransform); - m_underTrench = underTrench(turretPose.toPose2d()); m_aimTarget = calculateTarget(robotPose); m_shotCalcOutputs = calcShot(robotPose, m_useStaticShot, m_aimTarget, turretPositionRots, robotChassisSpeeds); - refreshCanTurretShoot(); // Logging log_globalShotTarget.accept(m_aimTarget); @@ -191,11 +216,25 @@ private void calcCallback() { log_loopTime.accept(m_calcTimer.get() * 1000.0); } + + // ============================================================= + // TARGET SELECTION + // ============================================================= + + // Picks which field target to aim at based on where the robot is. + // + // Robot on own side of hub -> shoot at the enemy hub center + // Robot past the hub into neutral zone -> pass to a passing point + // about where the bump is, left or right depending on which side of field center + // + // m_isPassingFlag is set here and read by ShotCalculator to pick kPassingTable. + // robotInNoPassingZone tracks when passing angles near the opponent hub are unsafe. + /** * Sets the target to a Pose on the field relative to where the robot is. * EX: Robot in alliance zone red -> Red Hub Center * Executes Passing and Shooting aiming. - * + * * @param robotPose where the robot currently is * @return target pose */ @@ -230,61 +269,58 @@ private Translation3d calculateTarget(Pose2d robotPose) { return AllianceFlipUtil.apply(theTarget); } - private boolean underTrench(Pose2d turretPose) { - isRed = WaltDriverStation.getAlliance().orElse(Alliance.Blue) == Alliance.Red; + // private boolean underTrench(Pose2d turretPose) { + // isRed = WaltDriverStation.getAlliance().orElse(Alliance.Blue) == Alliance.Red; - double robotX = turretPose.getX(); - double robotY = turretPose.getY(); + // double robotX = turretPose.getX(); + // double robotY = turretPose.getY(); - boolean inTrenchZone = isRed - ? (robotY < FieldConstants.LinesHorizontal.rightTrenchOpenStart || robotY > FieldConstants.LinesHorizontal.leftTrenchOpenEnd) - : (robotY > FieldConstants.LinesHorizontal.rightTrenchOpenStart || robotY < FieldConstants.LinesHorizontal.leftTrenchOpenEnd); + // boolean inTrenchZone = isRed + // ? (robotY < FieldConstants.LinesHorizontal.rightTrenchOpenStart || robotY > FieldConstants.LinesHorizontal.leftTrenchOpenEnd) + // : (robotY > FieldConstants.LinesHorizontal.rightTrenchOpenStart || robotY < FieldConstants.LinesHorizontal.leftTrenchOpenEnd); - double trenchCenterX = isRed - ? FieldConstants.LinesVertical.oppHubCenter - : FieldConstants.LinesVertical.hubCenter; - double trenchHalfDepth = FieldConstants.LeftTrench.depth / 2.0; - boolean inTrenchX = Math.abs(robotX - trenchCenterX) < trenchHalfDepth + 0.3; // 0.3m buffer + // double trenchCenterX = isRed + // ? FieldConstants.LinesVertical.oppHubCenter + // : FieldConstants.LinesVertical.hubCenter; + // double trenchHalfDepth = FieldConstants.LeftTrench.depth / 2.0; + // boolean inTrenchX = Math.abs(robotX - trenchCenterX) < trenchHalfDepth + 0.3; // 0.3m buffer - log_inTrenchZone.accept(inTrenchZone); + // log_inTrenchZone.accept(inTrenchZone); - log_underTrench.accept(inTrenchZone && inTrenchX); + // log_underTrench.accept(inTrenchZone && inTrenchX); - if (inTrenchZone && inTrenchX) { - return true; - } else { - return false; - } - } + // if (inTrenchZone && inTrenchX) { + // return true; + // } else { + // return false; + // } + // } - public record AzimuthCalcDetails( - double turretReferenceRots, double turretVelocityFF, - double turretX, double turretY, - double fieldYawRad, double currentFieldYawRad, - double rawDesiredRotations - ) { - private static final String kCalcTab = "/AzimuthCalcDetails"; - private static final DoubleLogger log_turretReferenceRots = new DoubleLogger(kLogTab + kCalcTab, "turretReferenceRots"); - private static final DoubleLogger log_turretVelocityFF = new DoubleLogger(kLogTab + kCalcTab, "turretVelocityFF"); - private static final DoubleLogger log_turretX = new DoubleLogger(kLogTab + kCalcTab, "turretX"); - private static final DoubleLogger log_turretY = new DoubleLogger(kLogTab + kCalcTab, "turretY"); - private static final DoubleLogger log_fieldYawRad = new DoubleLogger(kLogTab + kCalcTab, "fieldYawRad"); - private static final DoubleLogger log_currentFieldYawRad = new DoubleLogger(kLogTab + kCalcTab, "currentFieldYawRad"); - private static final DoubleLogger log_rawDesiredRotations = new DoubleLogger(kLogTab + kCalcTab, "rawDesiredRotations"); - - public void acceptLogging(AzimuthCalcDetails details) { - log_turretReferenceRots.accept(details.turretReferenceRots); - log_turretVelocityFF.accept(details.turretVelocityFF); - log_turretX.accept(details.turretX); - log_turretY.accept(details.turretY); - log_fieldYawRad.accept(details.fieldYawRad); - log_currentFieldYawRad.accept(details.currentFieldYawRad); - log_rawDesiredRotations.accept(details.rawDesiredRotations); - } - } + // ============================================================= + // SHOT + AZIMUTH CALCULATION + // ============================================================= + // Precomputed for calcAzimuth — skip calling kTurretTransform.getTranslation().getZ() every cycle + private static final double kTurretOffsetZ_m = kTurretTransform.getTranslation().getZ(); + // private static final Rotation3d kTurretRealPoseRotation = + // new Rotation3d(0, 0, -(kTurretAngleOffset.plus(Rotation2d.kPi)).getRadians()); + + /** + * + * Computes the turret setpoint in rotations and velocity feedforward. + * Uses raw doubles to avoid Pose3d/Rotation2d allocations on the hot path. + * + * What it does: + * - Computes the turret pivot position in field coords from robot heading + physical offset + * - atan2 from pivot to target gives the desired field yaw + * - Subtracts turret zero direction, normalizes to [-0.5, 0.5] rotations + * - Applies sinusoidal lateral bias correction if enabled + * - Snapback: if near a soft limit, shifts the setpoint +-1 rotation so the + * turret wraps the short way around instead of crossing the hardstop + * - Velocity FF from tangential velocity of the target relative to the pivot + * * Calculates the turret's *TARGET* angle while ensuring it stays within * physical limits. * IF the turret is near a limit, snaps 360 degrees in the opposite direction to @@ -292,17 +328,12 @@ public void acceptLogging(AzimuthCalcDetails details) { * without hitting the hardstop. * Note that if you have less than 360 degrees on the turret, you will simply * snap back to the other hard limit. - * + * * @param target target position * @return safe rotation setpoint that is accurate to the target within bounds * of kTurretMaxAngle * and kTurretMinAngle */ - // Precomputed for calcAzimuth logging - private static final double kTurretOffsetZ_m = kTurretTransform.getTranslation().getZ(); - // private static final Rotation3d kTurretRealPoseRotation = - // new Rotation3d(0, 0, -(kTurretAngleOffset.plus(Rotation2d.kPi)).getRadians()); - public static AzimuthCalcDetails calcAzimuth(Translation3d target, Pose2d robotPose, double turretHeading, ChassisSpeeds fieldSpeeds) { // Compute turret pivot position and zero direction with raw doubles // (eliminates Pose3d(robotPose).transformBy() + Rotation2d allocations) @@ -379,30 +410,10 @@ public static AzimuthCalcDetails calcAzimuth(Translation3d target, Pose2d robotP return calcDetails; } - public final record ShotCalcOutputs( - AzimuthCalcDetails turretCalcDetails, - ShotDataLerp shotData, - double turretReferenceRots, - double hoodReferenceRots, - double shooterReferenceRps - ) { - private static final String kCalcTab = "/ShotCalcOutputs"; - - private static final DoubleLogger log_turretReferenceRots = new DoubleLogger(kLogTab + kCalcTab, "turretReferenceRots"); - private static final DoubleLogger log_hoodReferenceRots = new DoubleLogger(kLogTab + kCalcTab, "hoodReferenceRots"); - private static final DoubleLogger log_shooterReferenceRPS = new DoubleLogger(kLogTab + kCalcTab, "shooterReferenceRPS"); - - public void acceptLogging(ShotCalcOutputs outputs) { - log_turretReferenceRots.accept(outputs.turretReferenceRots); - log_hoodReferenceRots.accept(outputs.hoodReferenceRots); - log_shooterReferenceRPS.accept(outputs.shooterReferenceRps); - } - } - /** * Calculates the ideal shot to put the FUEL™ into the HUB™ * Accounts for moving speeds - * + * * @param robotPose current Robot position. */ public static ShotCalcOutputs calcShot( @@ -430,4 +441,81 @@ public static ShotCalcOutputs calcShot( outputs.acceptLogging(outputs); return outputs; } + + + // ============================================================= + // OUTPUT RECORDS + // ============================================================= + + /* + * AzimuthCalcDetails + * + * Everything calcAzimuth() knows about where the turret should point. + * This gets passed up through ShotCalcOutputs to Shooter.periodic(). + * + * turretReferenceRots - final snapback-safe setpoint in rotations + * turretVelocityFF - angular velocity feedforward in rad/s + * turretX / turretY - turret pivot in field coords (for logging) + * fieldYawRad - desired absolute yaw in radians + * currentFieldYawRad - current turret yaw in field coords (shows tracking lag) + * rawDesiredRotations - pre-snapback angle, useful for debugging wrap-around + */ + public record AzimuthCalcDetails( + double turretReferenceRots, double turretVelocityFF, + double turretX, double turretY, + double fieldYawRad, double currentFieldYawRad, + double rawDesiredRotations + ) { + private static final String kCalcTab = "/AzimuthCalcDetails"; + + private static final DoubleLogger log_turretReferenceRots = new DoubleLogger(kLogTab + kCalcTab, "turretReferenceRots"); + private static final DoubleLogger log_turretVelocityFF = new DoubleLogger(kLogTab + kCalcTab, "turretVelocityFF"); + private static final DoubleLogger log_turretX = new DoubleLogger(kLogTab + kCalcTab, "turretX"); + private static final DoubleLogger log_turretY = new DoubleLogger(kLogTab + kCalcTab, "turretY"); + private static final DoubleLogger log_fieldYawRad = new DoubleLogger(kLogTab + kCalcTab, "fieldYawRad"); + private static final DoubleLogger log_currentFieldYawRad = new DoubleLogger(kLogTab + kCalcTab, "currentFieldYawRad"); + private static final DoubleLogger log_rawDesiredRotations = new DoubleLogger(kLogTab + kCalcTab, "rawDesiredRotations"); + + public void acceptLogging(AzimuthCalcDetails details) { + log_turretReferenceRots.accept(details.turretReferenceRots); + log_turretVelocityFF.accept(details.turretVelocityFF); + log_turretX.accept(details.turretX); + log_turretY.accept(details.turretY); + log_fieldYawRad.accept(details.fieldYawRad); + log_currentFieldYawRad.accept(details.currentFieldYawRad); + log_rawDesiredRotations.accept(details.rawDesiredRotations); + } + } + + /* + * ShotCalcOutputs + * + * The full package of parameters Shooter.periodic() needs to drive the mechanism. + * Produced once per 75 Hz callback and stored in a volatile field. + * + * turretCalcDetails - turret angle + feedforward (from calcAzimuth) + * shotData - flywheel speed, hood angle, TOF, predicted target (from ShotCalculator) + * turretReferenceRots - pulled from turretCalcDetails for convenience + * hoodReferenceRots - hood angle converted from radians to rotations + * shooterReferenceRps - flywheel speed converted from rad/s to RPS + */ + public final record ShotCalcOutputs( + AzimuthCalcDetails turretCalcDetails, + ShotDataLerp shotData, + double turretReferenceRots, + double hoodReferenceRots, + double shooterReferenceRps + ) { + private static final String kCalcTab = "/ShotCalcOutputs"; + + private static final DoubleLogger log_turretReferenceRots = new DoubleLogger(kLogTab + kCalcTab, "turretReferenceRots"); + private static final DoubleLogger log_hoodReferenceRots = new DoubleLogger(kLogTab + kCalcTab, "hoodReferenceRots"); + private static final DoubleLogger log_shooterReferenceRPS = new DoubleLogger(kLogTab + kCalcTab, "shooterReferenceRPS"); + + public void acceptLogging(ShotCalcOutputs outputs) { + log_turretReferenceRots.accept(outputs.turretReferenceRots); + log_hoodReferenceRots.accept(outputs.hoodReferenceRots); + log_shooterReferenceRPS.accept(outputs.shooterReferenceRps); + } + } } diff --git a/src/main/java/frc/robot/subsystems/shooter/calc/ShotCalculator.java b/src/main/java/frc/robot/subsystems/shooter/calc/ShotCalculator.java index 13aa6bef..d25f866d 100644 --- a/src/main/java/frc/robot/subsystems/shooter/calc/ShotCalculator.java +++ b/src/main/java/frc/robot/subsystems/shooter/calc/ShotCalculator.java @@ -28,23 +28,42 @@ import java.util.DoubleSummaryStatistics; import java.util.TreeMap; +/* + * ShotCalculator - pure ballistics, no threads, no state. + * + * Everything here is static methods or records. The two main entry points: + * - iterativeMovingShotFromInterpolationMap: the SOTM workhorse - converges on params that account for robot velocity during ball flight + * - calculateShotFromFunnelClearance: 2D closed-form solver for clearing a funnel above the robot + * + * ShotLerpTable is the zero-allocation sorted-array replacement for InterpolatingDoubleTreeMap. + * Tables store SI units internally (rad/s, radians, seconds). You put in rot/s and rotations via the Builder. + * + * Vocab: + * SOTM - Shoot On The Move. iterative convergence that adjusts for robot velocity during ball flight. + * TOF - Time of Flight. how long the ball is in the air. + * drift - lateral ball displacement during flight. drag-compensated via (1 - e^(-c*t)) / c. + * passing - shooting at the passing target instead of the hub. selects kPassingTable. + */ public class ShotCalculator { + // ---- LOGGERS ---- private static final DoubleLogger log_distToTargetMeters = new DoubleLogger("Shooter/Calculator", "distTargetToMeters"); private static final BooleanLogger log_isPassingLerp = new BooleanLogger("Shooter/Calculator", "isPassingLERP"); - private static final DoubleLogger log_dragCoefficient = new DoubleLogger("Shooter/Calculator", "dragCoefficient"); private static final IntLogger log_lerpIterationCount = new IntLogger("Shooter/Calculator", "lerpIterationCount"); private static final BooleanLogger log_calcConvergedBreakout = new BooleanLogger("Shooter/Calculator", "calcConvergedBreakout"); + // ---- TUNING ---- private static final double kMetersToInches = 1.0 / 0.0254; - // Horizontal drag damping: actual drift = v * (1 - e^(-c*t)) / c < v*t - // c = 0 disables drag compensation. Enable via /ShotCalc/sotmDragCoeff/enabled. - //YAYAYAYYAYAYAY 2974 IS OUR LUCKY NUMBER HUZZAH YAY YIPPEE private static final WaltTunable kDragCoeffTuner = new WaltTunable("/ShotCalc/sotmDragCoeff", 0.5000, false); // private static final Tracer m_iterativeTracer = new Tracer(); - // private static final double kRedHubCenterX = AllianceZoneUtil.redHubCenter.getX(); - // private static final double kBlueHubCenterX = AllianceZoneUtil.blueHubCenter.getX(); + private static final double kRPSBoost = 0.75; + private static final double kLongRangeRPSBoost = 0.35; + private static final double kScoringRPSBoost = -0.2; + private static final WaltTunable kRPSBoostTuner = new WaltTunable("Shooter/Calculator/RPSBoostDelta", 0.0); + + // ---- TABLE CONSTANTS ---- + // kReductionDistances/kReductionAmount feed the linear regression in calcRPSReduction - currently unused (kRPSReductionNeeded = false) private static final double[] kReductionDistances = {1.48, 2.31, 4.12}; private static final double[] kReductionAmount = {0, 2, 4}; @@ -56,12 +75,7 @@ public class ShotCalculator { private static final boolean kRPSReductionNeeded = false; - private static double kRPSBoost = 0.75; - private static double kLongRangeRPSBoost = 0.35; - - private static double kScoringRPSBoost = -0.2; - private static final WaltTunable kRPSBoostTuner = new WaltTunable("Shooter/Calculator/RPSBoost", kRPSBoost); - + // ---- LERP TABLES ---- /** * Zero-allocation sorted-array interpolation tables replacing InterpolatingTreeMap. * All values stored in SI units (rad/s, radians, seconds). @@ -76,33 +90,17 @@ private static void addNewFuelAdjPoint(double distance) { kNewFuelAdjTable.put(distance, calcRPSReduction(distance)); } + // ============================================================= + // TABLE INITIALIZATION + // ============================================================= + static { //TODO: find the actual minDistance and maxDistance for shooting - minDistance = 1.168; - maxDistance = 5.672; - - kRPSBoost = kRPSBoostTuner.enabled() ? kRPSBoostTuner.get() : kRPSBoost; - + minDistance = 0.985; + maxDistance = 8.627; ShotLerpTable.Builder shot = new ShotLerpTable.Builder(); - // normal table - // shot.add(8.627, 69.000, 1.160, 1.65, 0.500); - // shot.add(7.801, 64.700, 1.134, 1.37, 0.500); - // shot.add(6.973, 62.300, 1.104, 1.33, 0.500); - // shot.add(6.126, 58.000, 1.071, 1.33, 0.500); - // shot.add(5.577, 57.600, 1.046, 1.19, 0.500); - // shot.add(4.555, 54.200, 0.994, 1.12, 0.500); - // shot.add(4.231, 54.400, 0.974, 1.09, 0.500); - // shot.add(3.798, 51.900, 0.852, 1.08, 0.500); - // shot.add(3.267, 47.600, 0.907, 0.95, 0.500); - // shot.add(2.826, 46.400, 0.869, 0.93, 0.500); - // shot.add(2.212, 42.500, 0.608, 0.98, 0.500); - // shot.add(1.929, 41.700, 0.500, 0.87, 0.500); - // shot.add(1.093, 43.450, 0.000, 1.02, 0.500); - // shot.add(0.985, 40.000, 0.000, 0.97, 0.500); - - //spoof table shot.add(8.627, 69.000 + kScoringRPSBoost, 1.160, 1.65, 0.500); shot.add(7.801, 65.865 + kScoringRPSBoost, 1.106, 1.37, 0.500); //1.524 shot.add(6.973, 62.723 + kScoringRPSBoost, 1.046, 1.33, 0.500); //1.411 @@ -167,47 +165,10 @@ private static void addNewFuelAdjPoint(double distance) { passing.add(14.355, 104.39 + kRPSBoost + kLongRangeRPSBoost, 1.16, 2.08, 0.254); kPassingTable = passing.build(); } - static { - //THIS IS ONLY USED IF THE TURRET IS IN A POSITION THAT IS UNABLE TO SHOOT WITH HOOD UP DURING PASSING - //NO ANGRY PASSING IN OPPOSING ALLIANCE ZONE - // ShotLerpTable.Builder angry = new ShotLerpTable.Builder(); - // angry.add(7.574, 92.5, 0.08, 2.08); - // angry.add(7.135, 86, 0.08, 2.06); - // angry.add(6.653, 81, 0.08, 2.04); - // angry.add(6.254, 78, 0.08, 2.02); - // //note that the points above have spoofed tofs - // angry.add(5.672, 82.50 - kRPSReduction, 0.08, 2.11); - // angry.add(5.321, 81.00 - kRPSReduction, 0.08, 1.94); - // angry.add(5.223, 75.75 - kRPSReduction, 0.08, 1.79); - // angry.add(5.167, 79.50 - kRPSReduction, 0.08, 2.03); - // angry.add(4.996, 78.00 - kRPSReduction, 0.08, 1.97); - // angry.add(4.869, 76.50 - kRPSReduction, 0.08, 1.80); - // angry.add(4.696, 75.00 - kRPSReduction, 0.08, 1.33); - // angry.add(4.546, 73.50 - kRPSReduction, 0.08, 1.83); - // angry.add(4.402, 72.50 - kRPSReduction, 0.08, 1.85); - // angry.add(4.258, 71.00 - kRPSReduction, 0.08, 1.64); - // angry.add(4.098, 69.00 - kRPSReduction, 0.08, 1.58); - // angry.add(3.932, 68.00 - kRPSReduction, 0.08, 1.71); - // angry.add(3.785, 66.90 - kRPSReduction, 0.08, 1.56); - // angry.add(3.611, 65.40 - kRPSReduction, 0.08, 1.56); - // angry.add(3.464, 63.90 - kRPSReduction, 0.08, 1.50); - // angry.add(3.293, 62.40 - kRPSReduction, 0.08, 1.50); - // angry.add(3.134, 60.90 - kRPSReduction, 0.08, 1.51); - // angry.add(2.995, 59.40 - kRPSReduction, 0.08, 1.37); - // angry.add(2.855, 57.90 - kRPSReduction, 0.08, 1.35); - // angry.add(2.704, 56.40 - kRPSReduction, 0.08, 1.38); - // angry.add(2.586, 54.90 - kRPSReduction, 0.08, 1.28); - // angry.add(2.395, 53.50 - kRPSReduction, 0.08, 1.29); - // angry.add(2.221, 52.00 - kRPSReduction, 0.08, 1.25); - // angry.add(2.083, 50.50 - kRPSReduction, 0.08, 1.14); - // angry.add(1.912, 49.00 - kRPSReduction, 0.08, 1.19); - // angry.add(1.691, 47.50 - kRPSReduction, 0.08, 0.98); - // angry.add(1.575, 47.50 - kRPSReduction, 0.08, 1.18); - // angry.add(1.528, 46.00 - kRPSReduction, 0.08, 1.20); - // angry.add(1.307, 44.50 - kRPSReduction, 0.08, 1.13); - // angry.add(1.168, 44.50 - kRPSReduction, 0.08, 1.16); - // kAngryTurretTable = angry.build(); - } + + // ============================================================= + // DISTANCE + VELOCITY UTILITIES + // ============================================================= /** * @param distance @@ -231,7 +192,7 @@ public static double calcRPSReduction(double distance) { } r /= (kReductionAmount.length - 1); - + double slope = r * (reductionSTDev/distanceSTDev); double intercept = reductionSummaryStats.getAverage() - slope * distanceSummaryStats.getAverage(); @@ -256,7 +217,7 @@ public static double getDistanceToTargetM(double robotX, double robotY, double r /** * Gets the Distance from current robot position to desired target. - * Allocates Pose3d/Distance — use {@link #getDistanceToTargetM} on hot paths. + * Allocates Pose3d/Distance - use {@link #getDistanceToTargetM} on hot paths. */ public static Distance getDistanceToTarget(Pose2d robot, Translation3d target) { Pose3d turretPose = new Pose3d(robot).transformBy(kTurretTransform); @@ -266,6 +227,28 @@ public static Distance getDistanceToTarget(Pose2d robot, Translation3d target) { return Meters.of(dist); } + public static AngularVelocity linearToAngularVelocity(LinearVelocity vel, Distance radius) { + return RadiansPerSecond.of(vel.in(MetersPerSecond) / radius.in(Meters)); + } + + public static LinearVelocity angularToLinearVelocity(AngularVelocity vel, Distance radius) { + return MetersPerSecond.of(vel.in(RadiansPerSecond) * radius.in(Meters)); + } + + /** Raw double: rad/s from m/s and radius in meters. */ + public static double linearToAngularVelocityRadPerSec(double mps, double radiusM) { + return mps / radiusM; + } + + /** Raw double: m/s from rad/s and radius in meters. */ + public static double angularToLinearVelocityMps(double radPerSec, double radiusM) { + return radPerSec * radiusM; + } + + // ============================================================= + // TRAJECTORY PHYSICS + // ============================================================= + // see https://www.desmos.com/geometry/l4edywkmha public static Angle calculateAngleFromVelocity(Pose2d robot, LinearVelocity velocity, Translation3d target) { @@ -298,24 +281,6 @@ public static double calculateTimeOfFlightSec(double velMps, double hoodAngleRad return tofSec; } - public static AngularVelocity linearToAngularVelocity(LinearVelocity vel, Distance radius) { - return RadiansPerSecond.of(vel.in(MetersPerSecond) / radius.in(Meters)); - } - - public static LinearVelocity angularToLinearVelocity(AngularVelocity vel, Distance radius) { - return MetersPerSecond.of(vel.in(RadiansPerSecond) * radius.in(Meters)); - } - - /** Raw double: rad/s from m/s and radius in meters. */ - public static double linearToAngularVelocityRadPerSec(double mps, double radiusM) { - return mps / radiusM; - } - - /** Raw double: m/s from rad/s and radius in meters. */ - public static double angularToLinearVelocityMps(double radPerSec, double radiusM) { - return radPerSec * radiusM; - } - /** Returns drag-compensated drift time: (1 - e^(-c*t)) / c, or t if drag is disabled. */ private static double dragCompensatedTOF(double tof, double dragCoeff) { // if (!kDragCoeffTuner.enabled()) return tof; @@ -332,6 +297,10 @@ public static double getMaxTimeOfFlight() { return kShotTable.tof(maxDistance); } + // ============================================================= + // SHOOT ON THE MOVE + // ============================================================= + /** * Move a target a set time in the future along a velocity defined by * fieldSpeeds @@ -362,7 +331,7 @@ public static ShotData calculateShotFromFunnelClearance(Pose2d robot, predictedTarget.getX(), predictedTarget.getY(), predictedTarget.getZ()); } - /** Raw-double overload — zero allocation in the hot loop. */ + /** Raw-double overload - zero allocation in the hot loop. */ static ShotData calculateShotFromFunnelClearance(Pose2d robot, double actualTargetX, double actualTargetY, double predX, double predY, double predZ) { @@ -397,7 +366,7 @@ static ShotData calculateShotFromFunnelClearance(Pose2d robot, theta = 0; } - // v0 is in inches/sec — convert to rad/s via flywheel radius in inches + // v0 is in inches/sec - convert to rad/s via flywheel radius in inches double exitVelRadPerSec = v0 / kFlywheelRadiusIn; return new ShotData(exitVelRadPerSec, Math.PI / 2 - theta, new Translation3d(predX, predY, predZ)); } @@ -496,10 +465,10 @@ public static ShotDataLerp iterativeMovingShotFromInterpolationMap(Pose2d robot, double prevPredX = predX; double prevPredY = predY; - // Inline predictTargetPos — no Translation3d/Time allocation + // Inline predictTargetPos - no Translation3d/Time allocation double coeffDrag = 0.5000; //used for SOTM movement SIDE TO SIDE // double coeffDrag = shotTable.drag(distance); - //2974 RAHHHHHHHHHHHHHHH – correction: more like 254 RAHHHHHHHHHHHHHHH + //2974 RAHHHHHHHHHHHHHHH - correction: more like 254 RAHHHHHHHHHHHHHHH // if ( (Math.abs(vx) <= 0.05) || (Math.abs(vy) <= 0.05) ) { //NOTE: not sure if these numbers are right // coeffDrag = 0.2974; //used during static shot (or when the robot is low speed and should be static shooting) // } @@ -520,6 +489,10 @@ public static ShotDataLerp iterativeMovingShotFromInterpolationMap(Pose2d robot, hoodAngle = shotTable.hoodAngle(distance); tofSec = passing ? kPassingTable.tof(distance) : kShotTable.tof(distance); + if (kRPSBoostTuner.enabled()) { + exitVel += (kRPSBoostTuner.get() * (2.0 * Math.PI)); + } + double dExitVel = prevExitVel - exitVel; double dHood = prevHoodAngle - hoodAngle; double dTOF = prevTOF - tofSec; @@ -542,6 +515,10 @@ public static ShotDataLerp iterativeMovingShotFromInterpolationMap(Pose2d robot, return data; } + // ============================================================= + // DATA RECORDS + // ============================================================= + public record ShotData (double exitVelocity, double hoodAngle, Translation3d target) { public ShotData(AngularVelocity exitVelocity, Angle hoodAngle, Translation3d target) { this(exitVelocity.in(RadiansPerSecond), hoodAngle.in(Radians), target); @@ -612,6 +589,10 @@ public void acceptLogging(ShotDataLerp data) { public double getTofSec() { return tofSec; } } + // ============================================================= + // LERP TABLE + // ============================================================= + /** * Zero-allocation sorted-array interpolation table. * Replaces InterpolatingTreeMap / InterpolatingDoubleTreeMap on hot paths. @@ -661,8 +642,8 @@ public static final class Builder { /** dist: meters, rps: rot/s (shooter), hoodRots: rotations, tof: seconds */ public void add(double dist, double rps, double hoodRots, double tof, double drag) { entries.put(dist, new double[]{ - rps * (2.0 * Math.PI), // rot/s → rad/s - hoodRots * (2.0 * Math.PI), // rotations → radians + rps * (2.0 * Math.PI), // rot/s -> rad/s + hoodRots * (2.0 * Math.PI), // rotations -> radians tof, drag }); From 6050ae122f56fa14197fbac3a7704e784a449408 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Sat, 16 May 2026 21:10:35 -0400 Subject: [PATCH 16/25] tunable shenanigans and also tunable for ball detection --- src/main/java/frc/robot/subsystems/Indexer.java | 12 ++++++------ .../frc/robot/subsystems/shooter/Shooter.java | 15 +++++++-------- .../subsystems/shooter/calc/ShotCalculator.java | 12 ++++++------ 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/main/java/frc/robot/subsystems/Indexer.java b/src/main/java/frc/robot/subsystems/Indexer.java index b47832f0..12ca67fe 100644 --- a/src/main/java/frc/robot/subsystems/Indexer.java +++ b/src/main/java/frc/robot/subsystems/Indexer.java @@ -63,8 +63,8 @@ public class Indexer extends SubsystemBase { /* TUNABLES */ - private static final WaltTunable kTunnelRatioMultiplier = new WaltTunable("/Indexer/Tunnel/tunnelRatioScalar", 1.0); - private static final WaltTunable kSpindexerRatioMultiplier = new WaltTunable("/Indexer/Spindexer/spindexerRatioScalar", 1.0); + private static final WaltTunable kTunnelRatioScalarTuner = new WaltTunable("/Indexer/Tunnel/tunnelRatioScalar", 1.0); + private static final WaltTunable kSpindexerRatioScalarTuner = new WaltTunable("/Indexer/Spindexer/spindexerRatioScalar", 1.0); /* LOGGERS */ private final String kTunnelLogTab = "/Tunnel"; @@ -188,7 +188,7 @@ public void setSpindexerVelocity(double RPS) { m_spindexer.setControl(m_spindexerVelocityRequest.withVelocity(0)); m_spindexer.setControl(m_spindexerMotorIdleReq); } else { - RPS = kSpindexerRPSOverride.enabled() ? kSpindexerRPSOverride.get() : RPS; + RPS = kSpindexerRPSOverride.getOr(RPS); m_spindexer.setControl(m_spindexerVelocityRequest.withVelocity(RPS)); } m_desiredSpindexerRPS = RPS; @@ -205,7 +205,7 @@ public void setTunnelVelocity(double RPS) { m_tunnel.setControl(m_tunnelVelocityRequest.withVelocity(0)); m_tunnel.setControl(m_tunnelMotorIdleReq); } else { - RPS = kTunnelRPSOverride.enabled() ? kTunnelRPSOverride.get() : RPS; + RPS = kTunnelRPSOverride.getOr(RPS); m_tunnel.setControl(m_tunnelVelocityRequest.withVelocity(RPS)); } m_desiredTunnelRPS = RPS; @@ -218,11 +218,11 @@ public Command setTunnelVelocityCmd(AngularVelocity RPS) { //STATICS for conversions public static DoubleSupplier tunnelRPSFromShooter(DoubleSupplier shooterRPS) { - return () -> Math.min(shooterRPS.getAsDouble() * kTunnelFromShooterRatio * (kTunnelRatioMultiplier.enabled() ? kTunnelRatioMultiplier.get() : 1.0), kTunnelMaxRPSD); + return () -> Math.min(shooterRPS.getAsDouble() * kTunnelFromShooterRatio * (kTunnelRatioScalarTuner.getOr(1.0)), kTunnelMaxRPSD); } public static DoubleSupplier spindexerRPSFromShooter(DoubleSupplier shooterRPS) { - return () -> Math.min(shooterRPS.getAsDouble() * kSpindexerFromShooterRatio * (kSpindexerRatioMultiplier.enabled() ? kSpindexerRatioMultiplier.get() : 1.0), kSpindexerMaxRPSD); + return () -> Math.min(shooterRPS.getAsDouble() * kSpindexerFromShooterRatio * (kSpindexerRatioScalarTuner.getOr(1.0)), kSpindexerMaxRPSD); } /* PERIODICS */ diff --git a/src/main/java/frc/robot/subsystems/shooter/Shooter.java b/src/main/java/frc/robot/subsystems/shooter/Shooter.java index d16754a5..6bdcb5bf 100644 --- a/src/main/java/frc/robot/subsystems/shooter/Shooter.java +++ b/src/main/java/frc/robot/subsystems/shooter/Shooter.java @@ -53,6 +53,8 @@ public class Shooter extends SubsystemBase { new WaltTunable("/Shooter/shooterRPSOverride", kShooterRPSd); private static final WaltTunable kHoodRotsOverride = new WaltTunable("/Shooter/hoodRotsOverride", 0.0); + private static final WaltTunable kBallDetectionTuner = + new WaltTunable("Shooter/ballDetectionTuner", 0); private static final double kHoodLockedPosRots = Rotations.of(0.33).magnitude(); private final Tracer m_periodicTracer = new Tracer(); @@ -278,8 +280,8 @@ public void intakeFuel() { } private boolean detectShot() { - boolean accelDrop = m_latestFlywheelAccelerationRotPerSec <= -3.0; - return accelDrop; + double detectionThreshold = kBallDetectionTuner.getOr(-3.0); + return m_latestFlywheelAccelerationRotPerSec <= detectionThreshold; } // /** @@ -325,6 +327,7 @@ public void periodic() { // set outputs var turretVelocityFF = calcData.turretCalcDetails().turretVelocityFF(); + double flywheelReference = calcData.shooterReferenceRps(); if (m_turret.getTurretLocked()) { m_turret.setTurretPos(m_turret.getTurretLockAngleRots(), 0.0); m_calcFlywheelVelocityRotPerSec = kShooterRPSd; @@ -334,9 +337,7 @@ public void periodic() { // m_turret.setTurretPos(Rotations.of(-0.250)); } else { m_turret.setTurretPos(turretReference, turretVelocityFF); - m_calcFlywheelVelocityRotPerSec = kShooterRPSOverride.enabled() - ? kShooterRPSOverride.get() - : calcData.shooterReferenceRps(); + m_calcFlywheelVelocityRotPerSec = kShooterRPSOverride.getOr(flywheelReference); if (kAllowDriverRPSTweak) { // ENABLE THIS TO ALLOW DRIVER RPS TWEAK m_calcFlywheelVelocityRotPerSec += m_driverRPSTweak; m_calcFlywheelVelocityRotPerSec = MathUtil.clamp(m_calcFlywheelVelocityRotPerSec, 0, kShooterMaxRPSd); //clamp here or clamp only when setShooterVel is called? @@ -352,9 +353,7 @@ public void periodic() { // m_hood.setHoodPos(kHoodLockedPosRots); } else { if (!m_turret.getHoldTurretAtIntake()) { - m_calcHoodRots = kHoodRotsOverride.enabled() - ? kHoodRotsOverride.get() - : hoodReference; + m_calcHoodRots = kHoodRotsOverride.getOr(hoodReference); // m_hood.setHoodPos(kHoodRotsOverride.enabled() // ? kHoodRotsOverride.get() // : hoodReference); diff --git a/src/main/java/frc/robot/subsystems/shooter/calc/ShotCalculator.java b/src/main/java/frc/robot/subsystems/shooter/calc/ShotCalculator.java index d25f866d..f2787189 100644 --- a/src/main/java/frc/robot/subsystems/shooter/calc/ShotCalculator.java +++ b/src/main/java/frc/robot/subsystems/shooter/calc/ShotCalculator.java @@ -60,7 +60,8 @@ public class ShotCalculator { private static final double kRPSBoost = 0.75; private static final double kLongRangeRPSBoost = 0.35; private static final double kScoringRPSBoost = -0.2; - private static final WaltTunable kRPSBoostTuner = new WaltTunable("Shooter/Calculator/RPSBoostDelta", 0.0); + //NOTE THAT THIS IS NOT TUNING THE VALUES IN THE TABLE -- IT IS ON TOP OF THE REST OF THE ADDITIONS + private static final WaltTunable kRPSOverallBoostTuner = new WaltTunable("Shooter/Calculator/RPSBoostDelta", 0.0); // ---- TABLE CONSTANTS ---- // kReductionDistances/kReductionAmount feed the linear regression in calcRPSReduction - currently unused (kRPSReductionNeeded = false) @@ -283,8 +284,7 @@ public static double calculateTimeOfFlightSec(double velMps, double hoodAngleRad /** Returns drag-compensated drift time: (1 - e^(-c*t)) / c, or t if drag is disabled. */ private static double dragCompensatedTOF(double tof, double dragCoeff) { - // if (!kDragCoeffTuner.enabled()) return tof; - double c = kDragCoeffTuner.enabled() ? kDragCoeffTuner.get() : dragCoeff; + double c = dragCoeff; if (c < 1e-6) return tof; return (1.0 - Math.exp(-c * tof)) / c; } @@ -476,7 +476,7 @@ public static ShotDataLerp iterativeMovingShotFromInterpolationMap(Pose2d robot, // coeffDrag = 0.53; //0.7 // } - coeffDrag = kDragCoeffTuner.enabled() ? kDragCoeffTuner.get(): coeffDrag; + coeffDrag = kDragCoeffTuner.getOr(coeffDrag); double driftT = dragCompensatedTOF(tofSec, coeffDrag); predX = targetX - vxLaunch * driftT; predY = targetY - vyLaunch * driftT; @@ -489,8 +489,8 @@ public static ShotDataLerp iterativeMovingShotFromInterpolationMap(Pose2d robot, hoodAngle = shotTable.hoodAngle(distance); tofSec = passing ? kPassingTable.tof(distance) : kShotTable.tof(distance); - if (kRPSBoostTuner.enabled()) { - exitVel += (kRPSBoostTuner.get() * (2.0 * Math.PI)); + if (kRPSOverallBoostTuner.enabled()) { + exitVel += (kRPSOverallBoostTuner.get() * (2.0 * Math.PI)); } double dExitVel = prevExitVel - exitVel; From 98532367095c7867104761c1791cc13fe7097274 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Sat, 16 May 2026 21:29:37 -0400 Subject: [PATCH 17/25] twas browsin through the repo and noticed that people wouldnt know how to get to our own file! --- src/main/deploy/choreo/README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/deploy/choreo/README.md b/src/main/deploy/choreo/README.md index ea36dd77..c5785b33 100644 --- a/src/main/deploy/choreo/README.md +++ b/src/main/deploy/choreo/README.md @@ -1,19 +1,23 @@ ## Autonomous Paths This folder is where all the paths that we made for autonomous pathing exist. Such paths were made using [Choreo](https://choreo.autos/), a path making software. Choreo is how we make all of our autonomous paths, and this year, we opted to make seperate paths, and chain them together for our routines. +### How to make your own paths! +If you would like to see what the paths look like for yourself, or even make your own, lucky for you its something thats very simple! First things first, you need to make sure that Choreo is installed([link for convienience](https://github.com/SleipnirGroup/Choreo/releases)), then navigate the menu (**LOCATED IN THE TOP LEFT**) to the *open project* tab. From there, navigate to where this cloned repository exists on *your* computer, and navigate to this folder. Select the ```Rebuilt.chor``` file and you're good to go! You can see the other paths there, and make your own from there as well. + ![Choreo Logo](images/choreoLogo.png) ### Example of one of our autons ```java - addMultiAuton(kRightTrenchTwoCycleBumpReturn, - new AdaptableAutonInfo(AutonK.kRightOneBumpReturn, AutonK.kSOTMTimeout, true, 0), - new AdaptableAutonInfo(AutonK.kRightTwoBumpToTrench, AutonK.kSOTMTimeout, true, 0), - new AdaptableAutonInfo(AutonK.kRightTwoBumpReturn, AutonK.kSOTMTimeout, true, 0), - new AdaptableAutonInfo(AutonK.kRightTwoBumpToTrench, AutonK.kSOTMTimeout, true, 0), - new AdaptableAutonInfo(AutonK.kRightTwoGoOut, AutonK.kSOTMTimeout, true, 0)); + addMultiAuton(kLeftTrenchTwoCycleBumpReturn, + new AdaptableAutonInfo(AutonK.kLeftOneBumpReturn, AutonK.kSOTMTimeout, true, 0), + new AdaptableAutonInfo(AutonK.kLeftTwoBumpToTrench, AutonK.kSOTMTimeout, true, 0), + new AdaptableAutonInfo(AutonK.kLeftTwoBumpReturn, AutonK.kSOTMTimeout, true, 0), + new AdaptableAutonInfo(AutonK.kLeftTwoBumpToTrench, AutonK.kSOTMTimeout, true, 0), + new AdaptableAutonInfo(AutonK.kLeftTwoGoOut, AutonK.kSOTMTimeout, true, 0)); ``` Note that we chain 5 paths together to create an auton, in which we run the RIGHT_one_bumpReturn, the RIGHT_two_bumpToTrench, the RIGHT_two_bumpReturn, followed by the RIGHT_two_bumpToTrench, finishing with the RIGHT_two_goOut. All of these paths exist as traj's in this folder, and that is where the code pulls from; such paths are, once again, made in choreo. So in order to make a new auton, you must make a plethora of new paths, and make it so that they chain together using the logic seen above. ### Example of one of our paths (LEFT_one_bumpReturn) +We start at the GREEN waypoint (waypoint one for those of you who are colorblind), and end at the RED waypoint (waypoint 7). The arrow on the square represents the front of our robot, and this year, that is our intake (counter-intuitive, tell me about it :sob:). ![Choreo Path](images/choreoPath.png) From 329f334fb0a19309ed036c55c7d0c26a919113da Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Sat, 16 May 2026 21:44:41 -0400 Subject: [PATCH 18/25] JARVIS, FOLDERIZE --- .../{ => LEFT}/LEFT_one_bumpPreload.traj | 0 .../{ => LEFT}/LEFT_one_bumpReturn.traj | 0 .../{ => LEFT}/LEFT_one_bumpReturnFollow.traj | 0 .../LEFT_one_bumpReverseToTrench.traj | 0 .../{ => LEFT}/LEFT_one_bumpToDepot.traj | 0 .../choreo/{ => LEFT}/LEFT_one_defense.traj | 0 .../choreo/{ => LEFT}/LEFT_one_jab.traj | 0 .../choreo/{ => LEFT}/LEFT_one_reverse.traj | 0 .../choreo/{ => LEFT}/LEFT_one_selfPass.traj | 0 .../{ => LEFT}/LEFT_one_sweepAndDepot.traj | 0 .../choreo/{ => LEFT}/LEFT_one_trench.traj | 0 .../{ => LEFT}/LEFT_one_trenchPreload.traj | 0 .../{ => LEFT}/LEFT_one_trenchReturn.traj | 0 .../{ => LEFT}/LEFT_one_trenchToDepot.traj | 0 .../{ => LEFT}/LEFT_three_depotToBump.traj | 0 .../{ => LEFT}/LEFT_two_bumpReturn.traj | 0 .../{ => LEFT}/LEFT_two_bumpToDepot.traj | 0 .../{ => LEFT}/LEFT_two_bumpToTrench.traj | 0 .../choreo/{ => LEFT}/LEFT_two_depot.traj | 0 .../{ => LEFT}/LEFT_two_depotToBump.traj | 0 .../{ => LEFT}/LEFT_two_depotToTrench.traj | 0 .../choreo/{ => LEFT}/LEFT_two_goOut.traj | 0 .../choreo/{ => LEFT}/LEFT_two_jab.traj | 0 .../choreo/{ => LEFT}/LEFT_two_passing.traj | 0 .../choreo/{ => LEFT}/LEFT_two_reverse.traj | 0 .../choreo/{ => LEFT}/LEFT_two_sotmDepot.traj | 0 .../choreo/{ => LEFT}/LEFT_two_sweep.traj | 0 .../{ => LEFT}/LEFT_two_trenchReturn.traj | 0 .../{ => LEFT}/LEFT_two_trenchToDepot.traj | 0 .../choreo/{ => MISC}/CENTER_one_preload.traj | 0 .../deploy/choreo/{ => MISC}/PreHeat.traj | 0 .../{ => RIGHT}/RIGHT_one_bumpPreload.traj | 0 .../{ => RIGHT}/RIGHT_one_bumpReturn.traj | 0 .../RIGHT_one_bumpReturnFollow.traj | 0 .../RIGHT_one_bumpReverseToTrench.traj | 0 .../{ => RIGHT}/RIGHT_one_bumpToOutpost.traj | 0 .../choreo/{ => RIGHT}/RIGHT_one_circle.traj | 0 .../choreo/{ => RIGHT}/RIGHT_one_defense.traj | 0 .../choreo/{ => RIGHT}/RIGHT_one_jab.traj | 0 .../choreo/{ => RIGHT}/RIGHT_one_reverse.traj | 0 .../{ => RIGHT}/RIGHT_one_selfPass.traj | 0 .../choreo/{ => RIGHT}/RIGHT_one_trench.traj | 0 .../{ => RIGHT}/RIGHT_one_trenchPreload.traj | 0 .../{ => RIGHT}/RIGHT_one_trenchReturn.traj | 0 .../RIGHT_one_trenchToOutpost.traj | 0 .../{ => RIGHT}/RIGHT_stress_test_long.traj | 0 .../RIGHT_stress_test_overlap.traj | 0 .../{ => RIGHT}/RIGHT_three_depotToBump.traj | 0 .../{ => RIGHT}/RIGHT_two_bumpReturn.traj | 0 .../{ => RIGHT}/RIGHT_two_bumpToOutpost.traj | 0 .../{ => RIGHT}/RIGHT_two_bumpToTrench.traj | 0 .../choreo/{ => RIGHT}/RIGHT_two_depot.traj | 0 .../choreo/{ => RIGHT}/RIGHT_two_goOut.traj | 0 .../choreo/{ => RIGHT}/RIGHT_two_jab.traj | 0 .../{ => RIGHT}/RIGHT_two_outpostToBump.traj | 0 .../RIGHT_two_outpostToTrench.traj | 0 .../choreo/{ => RIGHT}/RIGHT_two_passing.traj | 0 .../choreo/{ => RIGHT}/RIGHT_two_reverse.traj | 0 .../{ => RIGHT}/RIGHT_two_sotmDepot.traj | 0 .../choreo/{ => RIGHT}/RIGHT_two_sweep.traj | 0 .../{ => RIGHT}/RIGHT_two_trenchReturn.traj | 0 .../RIGHT_two_trenchToOutpost.traj | 0 src/main/java/frc/robot/Constants.java | 128 +++++++++--------- .../autons/WaltAdaptableAutonFactory.java | 2 +- .../frc/robot/dashboards/AutonChooser.java | 2 +- 65 files changed, 66 insertions(+), 66 deletions(-) rename src/main/deploy/choreo/{ => LEFT}/LEFT_one_bumpPreload.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_one_bumpReturn.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_one_bumpReturnFollow.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_one_bumpReverseToTrench.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_one_bumpToDepot.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_one_defense.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_one_jab.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_one_reverse.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_one_selfPass.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_one_sweepAndDepot.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_one_trench.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_one_trenchPreload.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_one_trenchReturn.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_one_trenchToDepot.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_three_depotToBump.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_two_bumpReturn.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_two_bumpToDepot.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_two_bumpToTrench.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_two_depot.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_two_depotToBump.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_two_depotToTrench.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_two_goOut.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_two_jab.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_two_passing.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_two_reverse.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_two_sotmDepot.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_two_sweep.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_two_trenchReturn.traj (100%) rename src/main/deploy/choreo/{ => LEFT}/LEFT_two_trenchToDepot.traj (100%) rename src/main/deploy/choreo/{ => MISC}/CENTER_one_preload.traj (100%) rename src/main/deploy/choreo/{ => MISC}/PreHeat.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_one_bumpPreload.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_one_bumpReturn.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_one_bumpReturnFollow.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_one_bumpReverseToTrench.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_one_bumpToOutpost.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_one_circle.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_one_defense.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_one_jab.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_one_reverse.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_one_selfPass.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_one_trench.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_one_trenchPreload.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_one_trenchReturn.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_one_trenchToOutpost.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_stress_test_long.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_stress_test_overlap.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_three_depotToBump.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_two_bumpReturn.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_two_bumpToOutpost.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_two_bumpToTrench.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_two_depot.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_two_goOut.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_two_jab.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_two_outpostToBump.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_two_outpostToTrench.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_two_passing.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_two_reverse.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_two_sotmDepot.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_two_sweep.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_two_trenchReturn.traj (100%) rename src/main/deploy/choreo/{ => RIGHT}/RIGHT_two_trenchToOutpost.traj (100%) diff --git a/src/main/deploy/choreo/LEFT_one_bumpPreload.traj b/src/main/deploy/choreo/LEFT/LEFT_one_bumpPreload.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_one_bumpPreload.traj rename to src/main/deploy/choreo/LEFT/LEFT_one_bumpPreload.traj diff --git a/src/main/deploy/choreo/LEFT_one_bumpReturn.traj b/src/main/deploy/choreo/LEFT/LEFT_one_bumpReturn.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_one_bumpReturn.traj rename to src/main/deploy/choreo/LEFT/LEFT_one_bumpReturn.traj diff --git a/src/main/deploy/choreo/LEFT_one_bumpReturnFollow.traj b/src/main/deploy/choreo/LEFT/LEFT_one_bumpReturnFollow.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_one_bumpReturnFollow.traj rename to src/main/deploy/choreo/LEFT/LEFT_one_bumpReturnFollow.traj diff --git a/src/main/deploy/choreo/LEFT_one_bumpReverseToTrench.traj b/src/main/deploy/choreo/LEFT/LEFT_one_bumpReverseToTrench.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_one_bumpReverseToTrench.traj rename to src/main/deploy/choreo/LEFT/LEFT_one_bumpReverseToTrench.traj diff --git a/src/main/deploy/choreo/LEFT_one_bumpToDepot.traj b/src/main/deploy/choreo/LEFT/LEFT_one_bumpToDepot.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_one_bumpToDepot.traj rename to src/main/deploy/choreo/LEFT/LEFT_one_bumpToDepot.traj diff --git a/src/main/deploy/choreo/LEFT_one_defense.traj b/src/main/deploy/choreo/LEFT/LEFT_one_defense.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_one_defense.traj rename to src/main/deploy/choreo/LEFT/LEFT_one_defense.traj diff --git a/src/main/deploy/choreo/LEFT_one_jab.traj b/src/main/deploy/choreo/LEFT/LEFT_one_jab.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_one_jab.traj rename to src/main/deploy/choreo/LEFT/LEFT_one_jab.traj diff --git a/src/main/deploy/choreo/LEFT_one_reverse.traj b/src/main/deploy/choreo/LEFT/LEFT_one_reverse.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_one_reverse.traj rename to src/main/deploy/choreo/LEFT/LEFT_one_reverse.traj diff --git a/src/main/deploy/choreo/LEFT_one_selfPass.traj b/src/main/deploy/choreo/LEFT/LEFT_one_selfPass.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_one_selfPass.traj rename to src/main/deploy/choreo/LEFT/LEFT_one_selfPass.traj diff --git a/src/main/deploy/choreo/LEFT_one_sweepAndDepot.traj b/src/main/deploy/choreo/LEFT/LEFT_one_sweepAndDepot.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_one_sweepAndDepot.traj rename to src/main/deploy/choreo/LEFT/LEFT_one_sweepAndDepot.traj diff --git a/src/main/deploy/choreo/LEFT_one_trench.traj b/src/main/deploy/choreo/LEFT/LEFT_one_trench.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_one_trench.traj rename to src/main/deploy/choreo/LEFT/LEFT_one_trench.traj diff --git a/src/main/deploy/choreo/LEFT_one_trenchPreload.traj b/src/main/deploy/choreo/LEFT/LEFT_one_trenchPreload.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_one_trenchPreload.traj rename to src/main/deploy/choreo/LEFT/LEFT_one_trenchPreload.traj diff --git a/src/main/deploy/choreo/LEFT_one_trenchReturn.traj b/src/main/deploy/choreo/LEFT/LEFT_one_trenchReturn.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_one_trenchReturn.traj rename to src/main/deploy/choreo/LEFT/LEFT_one_trenchReturn.traj diff --git a/src/main/deploy/choreo/LEFT_one_trenchToDepot.traj b/src/main/deploy/choreo/LEFT/LEFT_one_trenchToDepot.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_one_trenchToDepot.traj rename to src/main/deploy/choreo/LEFT/LEFT_one_trenchToDepot.traj diff --git a/src/main/deploy/choreo/LEFT_three_depotToBump.traj b/src/main/deploy/choreo/LEFT/LEFT_three_depotToBump.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_three_depotToBump.traj rename to src/main/deploy/choreo/LEFT/LEFT_three_depotToBump.traj diff --git a/src/main/deploy/choreo/LEFT_two_bumpReturn.traj b/src/main/deploy/choreo/LEFT/LEFT_two_bumpReturn.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_two_bumpReturn.traj rename to src/main/deploy/choreo/LEFT/LEFT_two_bumpReturn.traj diff --git a/src/main/deploy/choreo/LEFT_two_bumpToDepot.traj b/src/main/deploy/choreo/LEFT/LEFT_two_bumpToDepot.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_two_bumpToDepot.traj rename to src/main/deploy/choreo/LEFT/LEFT_two_bumpToDepot.traj diff --git a/src/main/deploy/choreo/LEFT_two_bumpToTrench.traj b/src/main/deploy/choreo/LEFT/LEFT_two_bumpToTrench.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_two_bumpToTrench.traj rename to src/main/deploy/choreo/LEFT/LEFT_two_bumpToTrench.traj diff --git a/src/main/deploy/choreo/LEFT_two_depot.traj b/src/main/deploy/choreo/LEFT/LEFT_two_depot.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_two_depot.traj rename to src/main/deploy/choreo/LEFT/LEFT_two_depot.traj diff --git a/src/main/deploy/choreo/LEFT_two_depotToBump.traj b/src/main/deploy/choreo/LEFT/LEFT_two_depotToBump.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_two_depotToBump.traj rename to src/main/deploy/choreo/LEFT/LEFT_two_depotToBump.traj diff --git a/src/main/deploy/choreo/LEFT_two_depotToTrench.traj b/src/main/deploy/choreo/LEFT/LEFT_two_depotToTrench.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_two_depotToTrench.traj rename to src/main/deploy/choreo/LEFT/LEFT_two_depotToTrench.traj diff --git a/src/main/deploy/choreo/LEFT_two_goOut.traj b/src/main/deploy/choreo/LEFT/LEFT_two_goOut.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_two_goOut.traj rename to src/main/deploy/choreo/LEFT/LEFT_two_goOut.traj diff --git a/src/main/deploy/choreo/LEFT_two_jab.traj b/src/main/deploy/choreo/LEFT/LEFT_two_jab.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_two_jab.traj rename to src/main/deploy/choreo/LEFT/LEFT_two_jab.traj diff --git a/src/main/deploy/choreo/LEFT_two_passing.traj b/src/main/deploy/choreo/LEFT/LEFT_two_passing.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_two_passing.traj rename to src/main/deploy/choreo/LEFT/LEFT_two_passing.traj diff --git a/src/main/deploy/choreo/LEFT_two_reverse.traj b/src/main/deploy/choreo/LEFT/LEFT_two_reverse.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_two_reverse.traj rename to src/main/deploy/choreo/LEFT/LEFT_two_reverse.traj diff --git a/src/main/deploy/choreo/LEFT_two_sotmDepot.traj b/src/main/deploy/choreo/LEFT/LEFT_two_sotmDepot.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_two_sotmDepot.traj rename to src/main/deploy/choreo/LEFT/LEFT_two_sotmDepot.traj diff --git a/src/main/deploy/choreo/LEFT_two_sweep.traj b/src/main/deploy/choreo/LEFT/LEFT_two_sweep.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_two_sweep.traj rename to src/main/deploy/choreo/LEFT/LEFT_two_sweep.traj diff --git a/src/main/deploy/choreo/LEFT_two_trenchReturn.traj b/src/main/deploy/choreo/LEFT/LEFT_two_trenchReturn.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_two_trenchReturn.traj rename to src/main/deploy/choreo/LEFT/LEFT_two_trenchReturn.traj diff --git a/src/main/deploy/choreo/LEFT_two_trenchToDepot.traj b/src/main/deploy/choreo/LEFT/LEFT_two_trenchToDepot.traj similarity index 100% rename from src/main/deploy/choreo/LEFT_two_trenchToDepot.traj rename to src/main/deploy/choreo/LEFT/LEFT_two_trenchToDepot.traj diff --git a/src/main/deploy/choreo/CENTER_one_preload.traj b/src/main/deploy/choreo/MISC/CENTER_one_preload.traj similarity index 100% rename from src/main/deploy/choreo/CENTER_one_preload.traj rename to src/main/deploy/choreo/MISC/CENTER_one_preload.traj diff --git a/src/main/deploy/choreo/PreHeat.traj b/src/main/deploy/choreo/MISC/PreHeat.traj similarity index 100% rename from src/main/deploy/choreo/PreHeat.traj rename to src/main/deploy/choreo/MISC/PreHeat.traj diff --git a/src/main/deploy/choreo/RIGHT_one_bumpPreload.traj b/src/main/deploy/choreo/RIGHT/RIGHT_one_bumpPreload.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_one_bumpPreload.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_one_bumpPreload.traj diff --git a/src/main/deploy/choreo/RIGHT_one_bumpReturn.traj b/src/main/deploy/choreo/RIGHT/RIGHT_one_bumpReturn.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_one_bumpReturn.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_one_bumpReturn.traj diff --git a/src/main/deploy/choreo/RIGHT_one_bumpReturnFollow.traj b/src/main/deploy/choreo/RIGHT/RIGHT_one_bumpReturnFollow.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_one_bumpReturnFollow.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_one_bumpReturnFollow.traj diff --git a/src/main/deploy/choreo/RIGHT_one_bumpReverseToTrench.traj b/src/main/deploy/choreo/RIGHT/RIGHT_one_bumpReverseToTrench.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_one_bumpReverseToTrench.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_one_bumpReverseToTrench.traj diff --git a/src/main/deploy/choreo/RIGHT_one_bumpToOutpost.traj b/src/main/deploy/choreo/RIGHT/RIGHT_one_bumpToOutpost.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_one_bumpToOutpost.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_one_bumpToOutpost.traj diff --git a/src/main/deploy/choreo/RIGHT_one_circle.traj b/src/main/deploy/choreo/RIGHT/RIGHT_one_circle.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_one_circle.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_one_circle.traj diff --git a/src/main/deploy/choreo/RIGHT_one_defense.traj b/src/main/deploy/choreo/RIGHT/RIGHT_one_defense.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_one_defense.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_one_defense.traj diff --git a/src/main/deploy/choreo/RIGHT_one_jab.traj b/src/main/deploy/choreo/RIGHT/RIGHT_one_jab.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_one_jab.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_one_jab.traj diff --git a/src/main/deploy/choreo/RIGHT_one_reverse.traj b/src/main/deploy/choreo/RIGHT/RIGHT_one_reverse.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_one_reverse.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_one_reverse.traj diff --git a/src/main/deploy/choreo/RIGHT_one_selfPass.traj b/src/main/deploy/choreo/RIGHT/RIGHT_one_selfPass.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_one_selfPass.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_one_selfPass.traj diff --git a/src/main/deploy/choreo/RIGHT_one_trench.traj b/src/main/deploy/choreo/RIGHT/RIGHT_one_trench.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_one_trench.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_one_trench.traj diff --git a/src/main/deploy/choreo/RIGHT_one_trenchPreload.traj b/src/main/deploy/choreo/RIGHT/RIGHT_one_trenchPreload.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_one_trenchPreload.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_one_trenchPreload.traj diff --git a/src/main/deploy/choreo/RIGHT_one_trenchReturn.traj b/src/main/deploy/choreo/RIGHT/RIGHT_one_trenchReturn.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_one_trenchReturn.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_one_trenchReturn.traj diff --git a/src/main/deploy/choreo/RIGHT_one_trenchToOutpost.traj b/src/main/deploy/choreo/RIGHT/RIGHT_one_trenchToOutpost.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_one_trenchToOutpost.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_one_trenchToOutpost.traj diff --git a/src/main/deploy/choreo/RIGHT_stress_test_long.traj b/src/main/deploy/choreo/RIGHT/RIGHT_stress_test_long.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_stress_test_long.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_stress_test_long.traj diff --git a/src/main/deploy/choreo/RIGHT_stress_test_overlap.traj b/src/main/deploy/choreo/RIGHT/RIGHT_stress_test_overlap.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_stress_test_overlap.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_stress_test_overlap.traj diff --git a/src/main/deploy/choreo/RIGHT_three_depotToBump.traj b/src/main/deploy/choreo/RIGHT/RIGHT_three_depotToBump.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_three_depotToBump.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_three_depotToBump.traj diff --git a/src/main/deploy/choreo/RIGHT_two_bumpReturn.traj b/src/main/deploy/choreo/RIGHT/RIGHT_two_bumpReturn.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_two_bumpReturn.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_two_bumpReturn.traj diff --git a/src/main/deploy/choreo/RIGHT_two_bumpToOutpost.traj b/src/main/deploy/choreo/RIGHT/RIGHT_two_bumpToOutpost.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_two_bumpToOutpost.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_two_bumpToOutpost.traj diff --git a/src/main/deploy/choreo/RIGHT_two_bumpToTrench.traj b/src/main/deploy/choreo/RIGHT/RIGHT_two_bumpToTrench.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_two_bumpToTrench.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_two_bumpToTrench.traj diff --git a/src/main/deploy/choreo/RIGHT_two_depot.traj b/src/main/deploy/choreo/RIGHT/RIGHT_two_depot.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_two_depot.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_two_depot.traj diff --git a/src/main/deploy/choreo/RIGHT_two_goOut.traj b/src/main/deploy/choreo/RIGHT/RIGHT_two_goOut.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_two_goOut.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_two_goOut.traj diff --git a/src/main/deploy/choreo/RIGHT_two_jab.traj b/src/main/deploy/choreo/RIGHT/RIGHT_two_jab.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_two_jab.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_two_jab.traj diff --git a/src/main/deploy/choreo/RIGHT_two_outpostToBump.traj b/src/main/deploy/choreo/RIGHT/RIGHT_two_outpostToBump.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_two_outpostToBump.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_two_outpostToBump.traj diff --git a/src/main/deploy/choreo/RIGHT_two_outpostToTrench.traj b/src/main/deploy/choreo/RIGHT/RIGHT_two_outpostToTrench.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_two_outpostToTrench.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_two_outpostToTrench.traj diff --git a/src/main/deploy/choreo/RIGHT_two_passing.traj b/src/main/deploy/choreo/RIGHT/RIGHT_two_passing.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_two_passing.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_two_passing.traj diff --git a/src/main/deploy/choreo/RIGHT_two_reverse.traj b/src/main/deploy/choreo/RIGHT/RIGHT_two_reverse.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_two_reverse.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_two_reverse.traj diff --git a/src/main/deploy/choreo/RIGHT_two_sotmDepot.traj b/src/main/deploy/choreo/RIGHT/RIGHT_two_sotmDepot.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_two_sotmDepot.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_two_sotmDepot.traj diff --git a/src/main/deploy/choreo/RIGHT_two_sweep.traj b/src/main/deploy/choreo/RIGHT/RIGHT_two_sweep.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_two_sweep.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_two_sweep.traj diff --git a/src/main/deploy/choreo/RIGHT_two_trenchReturn.traj b/src/main/deploy/choreo/RIGHT/RIGHT_two_trenchReturn.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_two_trenchReturn.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_two_trenchReturn.traj diff --git a/src/main/deploy/choreo/RIGHT_two_trenchToOutpost.traj b/src/main/deploy/choreo/RIGHT/RIGHT_two_trenchToOutpost.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT_two_trenchToOutpost.traj rename to src/main/deploy/choreo/RIGHT/RIGHT_two_trenchToOutpost.traj diff --git a/src/main/java/frc/robot/Constants.java b/src/main/java/frc/robot/Constants.java index cacf6149..b3270453 100644 --- a/src/main/java/frc/robot/Constants.java +++ b/src/main/java/frc/robot/Constants.java @@ -672,88 +672,88 @@ public static class AutonK { /* OLD PATHS */ //---RIGHT FIRST CYCLES - public static final String kRightOneJab = "RIGHT_one_jab"; - public static final String kRightOneTrench = "RIGHT_one_trench"; - public static final String kRightOneDefense = "RIGHT_one_defense"; - public static final String kRightOneReverse = "RIGHT_one_reverse"; + public static final String kRightOneJab = "RIGHT/RIGHT_one_jab"; + public static final String kRightOneTrench = "RIGHT/RIGHT_one_trench"; + public static final String kRightOneDefense = "RIGHT/RIGHT_one_defense"; + public static final String kRightOneReverse = "RIGHT/RIGHT_one_reverse"; //---RIGHT SECOND CYCLES - public static final String kRightTwoSotmDepot = "RIGHT_two_sotmDepot"; - public static final String kRightTwoDepot = "RIGHT_two_depot"; - public static final String kRightTwoSweep = "RIGHT_two_sweep"; - public static final String kRightTwoPassing = "RIGHT_two_passing"; - public static final String kRightTwoJab = "RIGHT_two_jab"; - public static final String kRightTwoReverse = "RIGHT_two_reverse"; + public static final String kRightTwoSotmDepot = "RIGHT/RIGHT_two_sotmDepot"; + public static final String kRightTwoDepot = "RIGHT/RIGHT_two_depot"; + public static final String kRightTwoSweep = "RIGHT/RIGHT_two_sweep"; + public static final String kRightTwoPassing = "RIGHT/RIGHT_two_passing"; + public static final String kRightTwoJab = "RIGHT/RIGHT_two_jab"; + public static final String kRightTwoReverse = "RIGHT/RIGHT_two_reverse"; //---LEFT FIRST CYCLES - public static final String kLeftOneJab = "LEFT_one_jab"; - public static final String kLeftOneTrench = "LEFT_one_trench"; - public static final String kLeftOneDefense = "LEFT_one_defense"; - public static final String kLeftOneReverse = "LEFT_one_reverse"; + public static final String kLeftOneJab = "LEFT/LEFT_one_jab"; + public static final String kLeftOneTrench = "LEFT/LEFT_one_trench"; + public static final String kLeftOneDefense = "LEFT/LEFT_one_defense"; + public static final String kLeftOneReverse = "LEFT/LEFT_one_reverse"; //---LEFT SECOND CYCLES - public static final String kLeftTwoSotmDepot = "LEFT_two_sotmDepot"; - public static final String kLeftTwoDepot = "LEFT_two_depot"; - public static final String kLeftTwoSweep = "LEFT_two_sweep"; - public static final String kLeftTwoPassing = "LEFT_two_passing"; - public static final String kLeftTwoJab = "LEFT_two_jab"; - public static final String kLeftTwoReverse = "LEFT_two_reverse"; + public static final String kLeftTwoSotmDepot = "LEFT/LEFT_two_sotmDepot"; + public static final String kLeftTwoDepot = "LEFT/LEFT_two_depot"; + public static final String kLeftTwoSweep = "LEFT/LEFT_two_sweep"; + public static final String kLeftTwoPassing = "LEFT/LEFT_two_passing"; + public static final String kLeftTwoJab = "LEFT/LEFT_two_jab"; + public static final String kLeftTwoReverse = "LEFT/LEFT_two_reverse"; //---MISC - public static final String kRightOneCircle = "RIGHT_one_circle"; - public static final String kLeftOneSweepAndDepot = "LEFT_one_sweepAndDepot"; - public static final String kLeftThreeDepotToBump = "LEFT_three_depotToBump"; - public static final String kRightThreeDepotToBump = "RIGHT_three_depotToBump"; + public static final String kRightOneCircle = "RIGHT/RIGHT_one_circle"; + public static final String kLeftOneSweepAndDepot = "LEFT/LEFT_one_sweepAndDepot"; + public static final String kLeftThreeDepotToBump = "LEFT/LEFT_three_depotToBump"; + public static final String kRightThreeDepotToBump = "RIGHT/RIGHT_three_depotToBump"; //---STRESS TEST - public static final String kRightStressTestLong = "RIGHT_stress_test_long"; - public static final String kRightStressTestOverlap = "RIGHT_stress_test_overlap"; - + public static final String kRightStressTestLong = "RIGHT/RIGHT_stress_test_long"; + public static final String kRightStressTestOverlap = "RIGHT/RIGHT_stress_test_overlap"; + /* NEW PATHS */ //---BUMP RETURN PATHS - public static final String kRightOneBumpReturn = "RIGHT_one_bumpReturn"; - public static final String kRightOneBumpReturnFollow = "RIGHT_one_bumpReturnFollow"; - public static final String kLeftOneBumpReturn = "LEFT_one_bumpReturn"; - public static final String kLeftOneBumpReturnFollow = "LEFT_one_bumpReturnFollow"; - public static final String kRightTwoBumpReturn = "RIGHT_two_bumpReturn"; - public static final String kLeftTwoBumpReturn = "LEFT_two_bumpReturn"; - public static final String kRightTwoBumpToTrench = "RIGHT_two_bumpToTrench"; - public static final String kLeftTwoBumpToTrench = "LEFT_two_bumpToTrench"; + public static final String kRightOneBumpReturn = "RIGHT/RIGHT_one_bumpReturn"; + public static final String kRightOneBumpReturnFollow = "RIGHT/RIGHT_one_bumpReturnFollow"; + public static final String kLeftOneBumpReturn = "LEFT/LEFT_one_bumpReturn"; + public static final String kLeftOneBumpReturnFollow = "LEFT/LEFT_one_bumpReturnFollow"; + public static final String kRightTwoBumpReturn = "RIGHT/RIGHT_two_bumpReturn"; + public static final String kLeftTwoBumpReturn = "LEFT/LEFT_two_bumpReturn"; + public static final String kRightTwoBumpToTrench = "RIGHT/RIGHT_two_bumpToTrench"; + public static final String kLeftTwoBumpToTrench = "LEFT/LEFT_two_bumpToTrench"; //---TRENCH RETURN PATHS - public static final String kRightOneTrenchReturn = "RIGHT_one_trenchReturn"; - public static final String kLeftOneTrenchReturn = "LEFT_one_trenchReturn"; - public static final String kRightTwoTrenchReturn = "RIGHT_two_trenchReturn"; - public static final String kLeftTwoTrenchReturn = "LEFT_two_trenchReturn"; - public static final String kRightOneBumpTrenchReturn = "RIGHT_one_bumpReverseToTrench"; - public static final String kLeftOneBumpTrenchReturn = "LEFT_one_bumpReverseToTrench"; - + public static final String kRightOneTrenchReturn = "RIGHT/RIGHT_one_trenchReturn"; + public static final String kLeftOneTrenchReturn = "LEFT/LEFT_one_trenchReturn"; + public static final String kRightTwoTrenchReturn = "RIGHT/RIGHT_two_trenchReturn"; + public static final String kLeftTwoTrenchReturn = "LEFT/LEFT_two_trenchReturn"; + public static final String kRightOneBumpTrenchReturn = "RIGHT/RIGHT_one_bumpReverseToTrench"; + public static final String kLeftOneBumpTrenchReturn = "LEFT/LEFT_one_bumpReverseToTrench"; + //---OUTPOST PATHS - public static final String kRightOneTrenchToOutpost = "RIGHT_one_trenchToOutpost"; - public static final String kRightTwoTrenchToOutpost = "RIGHT_two_trenchToOutpost"; - public static final String kRightTwoOutpostToTrench = "RIGHT_two_outpostToTrench"; - public static final String kRightOneBumpToOutpost = "RIGHT_one_bumpToOutpost"; - public static final String kRightTwoBumpToOutpost = "RIGHT_two_bumpToOutpost"; - public static final String kRightTwoOutpostToBump = "RIGHT_two_outpostToBump"; + public static final String kRightOneTrenchToOutpost = "RIGHT/RIGHT_one_trenchToOutpost"; + public static final String kRightTwoTrenchToOutpost = "RIGHT/RIGHT_two_trenchToOutpost"; + public static final String kRightTwoOutpostToTrench = "RIGHT/RIGHT_two_outpostToTrench"; + public static final String kRightOneBumpToOutpost = "RIGHT/RIGHT_one_bumpToOutpost"; + public static final String kRightTwoBumpToOutpost = "RIGHT/RIGHT_two_bumpToOutpost"; + public static final String kRightTwoOutpostToBump = "RIGHT/RIGHT_two_outpostToBump"; //---DEPOT PATHS - public static final String kLeftOneTrenchToDepot = "LEFT_one_trenchToDepot"; - public static final String kLeftTwoTrenchToDepot = "LEFT_two_trenchToDepot"; - public static final String kLeftTwoDepotToTrench = "LEFT_two_depotToTrench"; - public static final String kLeftOneBumpToDepot = "LEFT_one_bumpToDepot"; - public static final String kLeftTwoBumpToDepot = "LEFT_two_bumpToDepot"; - public static final String kLeftTwoDepotToBump = "LEFT_two_depotToBump"; + public static final String kLeftOneTrenchToDepot = "LEFT/LEFT_one_trenchToDepot"; + public static final String kLeftTwoTrenchToDepot = "LEFT/LEFT_two_trenchToDepot"; + public static final String kLeftTwoDepotToTrench = "LEFT/LEFT_two_depotToTrench"; + public static final String kLeftOneBumpToDepot = "LEFT/LEFT_one_bumpToDepot"; + public static final String kLeftTwoBumpToDepot = "LEFT/LEFT_two_bumpToDepot"; + public static final String kLeftTwoDepotToBump = "LEFT/LEFT_two_depotToBump"; //---MISC - public static final String kRightOneSelfPass = "RIGHT_one_selfPass"; - public static final String kLeftOneSelfPass = "LEFT_one_selfPass"; - public static final String kRightTwoGoOut = "RIGHT_two_goOut"; - public static final String kLeftTwoGoOut = "LEFT_two_goOut"; - public static final String kRightBumpPreload = "RIGHT_one_bumpPreload"; - public static final String kLeftBumpPreload = "LEFT_one_bumpPreload"; - public static final String kRightTrenchPreload = "RIGHT_one_trenchPreload"; - public static final String kLeftTrenchPreload = "LEFT_one_trenchPreload"; - - public static final String kCenterPreload = "CENTER_one_preload"; + public static final String kRightOneSelfPass = "RIGHT/RIGHT_one_selfPass"; + public static final String kLeftOneSelfPass = "LEFT/LEFT_one_selfPass"; + public static final String kRightTwoGoOut = "RIGHT/RIGHT_two_goOut"; + public static final String kLeftTwoGoOut = "LEFT/LEFT_two_goOut"; + public static final String kRightBumpPreload = "RIGHT/RIGHT_one_bumpPreload"; + public static final String kLeftBumpPreload = "LEFT/LEFT_one_bumpPreload"; + public static final String kRightTrenchPreload = "RIGHT/RIGHT_one_trenchPreload"; + public static final String kLeftTrenchPreload = "LEFT/LEFT_one_trenchPreload"; + + public static final String kCenterPreload = "MISC/CENTER_one_preload"; } } diff --git a/src/main/java/frc/robot/autons/WaltAdaptableAutonFactory.java b/src/main/java/frc/robot/autons/WaltAdaptableAutonFactory.java index 65156953..34ca03b3 100644 --- a/src/main/java/frc/robot/autons/WaltAdaptableAutonFactory.java +++ b/src/main/java/frc/robot/autons/WaltAdaptableAutonFactory.java @@ -274,7 +274,7 @@ private AutoTrajectory createTraj(AutoRoutine routine, String name) { // before a match. Ensures we dont stall for like 0.4 seconds at the start of auton public AutoRoutine preheater() { System.out.println("PREHEAT MADE"); - return adaptableAuton("PreHeat", new AdaptableAutonInfo("PreHeat", AutonK.kShootingTimeout, false, 0)); + return adaptableAuton("PreHeat", new AdaptableAutonInfo("MISC/PreHeat", AutonK.kShootingTimeout, false, 0)); } // Builds a single-segment auton: follow one trajectory and fire waypoint diff --git a/src/main/java/frc/robot/dashboards/AutonChooser.java b/src/main/java/frc/robot/dashboards/AutonChooser.java index ffa73f40..8ce861c0 100644 --- a/src/main/java/frc/robot/dashboards/AutonChooser.java +++ b/src/main/java/frc/robot/dashboards/AutonChooser.java @@ -37,7 +37,7 @@ * etc. */ public class AutonChooser { - private static final String kPreheatTrajectory = "PreHeat"; + private static final String kPreheatTrajectory = "MISC/PreHeat"; public static AutoChooser m_chooser; public static WaltAdaptableAutonFactory m_adaptableAutonFactory; From 4421fbdfe07e48653f3df54a1a15eff5cab0b798 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Sat, 16 May 2026 21:51:50 -0400 Subject: [PATCH 19/25] Revert "JARVIS, FOLDERIZE" This reverts commit 329f334fb0a19309ed036c55c7d0c26a919113da. --- .../choreo/{MISC => }/CENTER_one_preload.traj | 0 .../{LEFT => }/LEFT_one_bumpPreload.traj | 0 .../{LEFT => }/LEFT_one_bumpReturn.traj | 0 .../{LEFT => }/LEFT_one_bumpReturnFollow.traj | 0 .../LEFT_one_bumpReverseToTrench.traj | 0 .../{LEFT => }/LEFT_one_bumpToDepot.traj | 0 .../choreo/{LEFT => }/LEFT_one_defense.traj | 0 .../choreo/{LEFT => }/LEFT_one_jab.traj | 0 .../choreo/{LEFT => }/LEFT_one_reverse.traj | 0 .../choreo/{LEFT => }/LEFT_one_selfPass.traj | 0 .../{LEFT => }/LEFT_one_sweepAndDepot.traj | 0 .../choreo/{LEFT => }/LEFT_one_trench.traj | 0 .../{LEFT => }/LEFT_one_trenchPreload.traj | 0 .../{LEFT => }/LEFT_one_trenchReturn.traj | 0 .../{LEFT => }/LEFT_one_trenchToDepot.traj | 0 .../{LEFT => }/LEFT_three_depotToBump.traj | 0 .../{LEFT => }/LEFT_two_bumpReturn.traj | 0 .../{LEFT => }/LEFT_two_bumpToDepot.traj | 0 .../{LEFT => }/LEFT_two_bumpToTrench.traj | 0 .../choreo/{LEFT => }/LEFT_two_depot.traj | 0 .../{LEFT => }/LEFT_two_depotToBump.traj | 0 .../{LEFT => }/LEFT_two_depotToTrench.traj | 0 .../choreo/{LEFT => }/LEFT_two_goOut.traj | 0 .../choreo/{LEFT => }/LEFT_two_jab.traj | 0 .../choreo/{LEFT => }/LEFT_two_passing.traj | 0 .../choreo/{LEFT => }/LEFT_two_reverse.traj | 0 .../choreo/{LEFT => }/LEFT_two_sotmDepot.traj | 0 .../choreo/{LEFT => }/LEFT_two_sweep.traj | 0 .../{LEFT => }/LEFT_two_trenchReturn.traj | 0 .../{LEFT => }/LEFT_two_trenchToDepot.traj | 0 .../deploy/choreo/{MISC => }/PreHeat.traj | 0 .../{RIGHT => }/RIGHT_one_bumpPreload.traj | 0 .../{RIGHT => }/RIGHT_one_bumpReturn.traj | 0 .../RIGHT_one_bumpReturnFollow.traj | 0 .../RIGHT_one_bumpReverseToTrench.traj | 0 .../{RIGHT => }/RIGHT_one_bumpToOutpost.traj | 0 .../choreo/{RIGHT => }/RIGHT_one_circle.traj | 0 .../choreo/{RIGHT => }/RIGHT_one_defense.traj | 0 .../choreo/{RIGHT => }/RIGHT_one_jab.traj | 0 .../choreo/{RIGHT => }/RIGHT_one_reverse.traj | 0 .../{RIGHT => }/RIGHT_one_selfPass.traj | 0 .../choreo/{RIGHT => }/RIGHT_one_trench.traj | 0 .../{RIGHT => }/RIGHT_one_trenchPreload.traj | 0 .../{RIGHT => }/RIGHT_one_trenchReturn.traj | 0 .../RIGHT_one_trenchToOutpost.traj | 0 .../{RIGHT => }/RIGHT_stress_test_long.traj | 0 .../RIGHT_stress_test_overlap.traj | 0 .../{RIGHT => }/RIGHT_three_depotToBump.traj | 0 .../{RIGHT => }/RIGHT_two_bumpReturn.traj | 0 .../{RIGHT => }/RIGHT_two_bumpToOutpost.traj | 0 .../{RIGHT => }/RIGHT_two_bumpToTrench.traj | 0 .../choreo/{RIGHT => }/RIGHT_two_depot.traj | 0 .../choreo/{RIGHT => }/RIGHT_two_goOut.traj | 0 .../choreo/{RIGHT => }/RIGHT_two_jab.traj | 0 .../{RIGHT => }/RIGHT_two_outpostToBump.traj | 0 .../RIGHT_two_outpostToTrench.traj | 0 .../choreo/{RIGHT => }/RIGHT_two_passing.traj | 0 .../choreo/{RIGHT => }/RIGHT_two_reverse.traj | 0 .../{RIGHT => }/RIGHT_two_sotmDepot.traj | 0 .../choreo/{RIGHT => }/RIGHT_two_sweep.traj | 0 .../{RIGHT => }/RIGHT_two_trenchReturn.traj | 0 .../RIGHT_two_trenchToOutpost.traj | 0 src/main/java/frc/robot/Constants.java | 128 +++++++++--------- .../autons/WaltAdaptableAutonFactory.java | 2 +- .../frc/robot/dashboards/AutonChooser.java | 2 +- 65 files changed, 66 insertions(+), 66 deletions(-) rename src/main/deploy/choreo/{MISC => }/CENTER_one_preload.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_one_bumpPreload.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_one_bumpReturn.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_one_bumpReturnFollow.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_one_bumpReverseToTrench.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_one_bumpToDepot.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_one_defense.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_one_jab.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_one_reverse.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_one_selfPass.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_one_sweepAndDepot.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_one_trench.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_one_trenchPreload.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_one_trenchReturn.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_one_trenchToDepot.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_three_depotToBump.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_two_bumpReturn.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_two_bumpToDepot.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_two_bumpToTrench.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_two_depot.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_two_depotToBump.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_two_depotToTrench.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_two_goOut.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_two_jab.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_two_passing.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_two_reverse.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_two_sotmDepot.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_two_sweep.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_two_trenchReturn.traj (100%) rename src/main/deploy/choreo/{LEFT => }/LEFT_two_trenchToDepot.traj (100%) rename src/main/deploy/choreo/{MISC => }/PreHeat.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_one_bumpPreload.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_one_bumpReturn.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_one_bumpReturnFollow.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_one_bumpReverseToTrench.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_one_bumpToOutpost.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_one_circle.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_one_defense.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_one_jab.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_one_reverse.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_one_selfPass.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_one_trench.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_one_trenchPreload.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_one_trenchReturn.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_one_trenchToOutpost.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_stress_test_long.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_stress_test_overlap.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_three_depotToBump.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_two_bumpReturn.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_two_bumpToOutpost.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_two_bumpToTrench.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_two_depot.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_two_goOut.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_two_jab.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_two_outpostToBump.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_two_outpostToTrench.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_two_passing.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_two_reverse.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_two_sotmDepot.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_two_sweep.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_two_trenchReturn.traj (100%) rename src/main/deploy/choreo/{RIGHT => }/RIGHT_two_trenchToOutpost.traj (100%) diff --git a/src/main/deploy/choreo/MISC/CENTER_one_preload.traj b/src/main/deploy/choreo/CENTER_one_preload.traj similarity index 100% rename from src/main/deploy/choreo/MISC/CENTER_one_preload.traj rename to src/main/deploy/choreo/CENTER_one_preload.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_one_bumpPreload.traj b/src/main/deploy/choreo/LEFT_one_bumpPreload.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_one_bumpPreload.traj rename to src/main/deploy/choreo/LEFT_one_bumpPreload.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_one_bumpReturn.traj b/src/main/deploy/choreo/LEFT_one_bumpReturn.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_one_bumpReturn.traj rename to src/main/deploy/choreo/LEFT_one_bumpReturn.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_one_bumpReturnFollow.traj b/src/main/deploy/choreo/LEFT_one_bumpReturnFollow.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_one_bumpReturnFollow.traj rename to src/main/deploy/choreo/LEFT_one_bumpReturnFollow.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_one_bumpReverseToTrench.traj b/src/main/deploy/choreo/LEFT_one_bumpReverseToTrench.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_one_bumpReverseToTrench.traj rename to src/main/deploy/choreo/LEFT_one_bumpReverseToTrench.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_one_bumpToDepot.traj b/src/main/deploy/choreo/LEFT_one_bumpToDepot.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_one_bumpToDepot.traj rename to src/main/deploy/choreo/LEFT_one_bumpToDepot.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_one_defense.traj b/src/main/deploy/choreo/LEFT_one_defense.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_one_defense.traj rename to src/main/deploy/choreo/LEFT_one_defense.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_one_jab.traj b/src/main/deploy/choreo/LEFT_one_jab.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_one_jab.traj rename to src/main/deploy/choreo/LEFT_one_jab.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_one_reverse.traj b/src/main/deploy/choreo/LEFT_one_reverse.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_one_reverse.traj rename to src/main/deploy/choreo/LEFT_one_reverse.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_one_selfPass.traj b/src/main/deploy/choreo/LEFT_one_selfPass.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_one_selfPass.traj rename to src/main/deploy/choreo/LEFT_one_selfPass.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_one_sweepAndDepot.traj b/src/main/deploy/choreo/LEFT_one_sweepAndDepot.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_one_sweepAndDepot.traj rename to src/main/deploy/choreo/LEFT_one_sweepAndDepot.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_one_trench.traj b/src/main/deploy/choreo/LEFT_one_trench.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_one_trench.traj rename to src/main/deploy/choreo/LEFT_one_trench.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_one_trenchPreload.traj b/src/main/deploy/choreo/LEFT_one_trenchPreload.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_one_trenchPreload.traj rename to src/main/deploy/choreo/LEFT_one_trenchPreload.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_one_trenchReturn.traj b/src/main/deploy/choreo/LEFT_one_trenchReturn.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_one_trenchReturn.traj rename to src/main/deploy/choreo/LEFT_one_trenchReturn.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_one_trenchToDepot.traj b/src/main/deploy/choreo/LEFT_one_trenchToDepot.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_one_trenchToDepot.traj rename to src/main/deploy/choreo/LEFT_one_trenchToDepot.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_three_depotToBump.traj b/src/main/deploy/choreo/LEFT_three_depotToBump.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_three_depotToBump.traj rename to src/main/deploy/choreo/LEFT_three_depotToBump.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_two_bumpReturn.traj b/src/main/deploy/choreo/LEFT_two_bumpReturn.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_two_bumpReturn.traj rename to src/main/deploy/choreo/LEFT_two_bumpReturn.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_two_bumpToDepot.traj b/src/main/deploy/choreo/LEFT_two_bumpToDepot.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_two_bumpToDepot.traj rename to src/main/deploy/choreo/LEFT_two_bumpToDepot.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_two_bumpToTrench.traj b/src/main/deploy/choreo/LEFT_two_bumpToTrench.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_two_bumpToTrench.traj rename to src/main/deploy/choreo/LEFT_two_bumpToTrench.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_two_depot.traj b/src/main/deploy/choreo/LEFT_two_depot.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_two_depot.traj rename to src/main/deploy/choreo/LEFT_two_depot.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_two_depotToBump.traj b/src/main/deploy/choreo/LEFT_two_depotToBump.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_two_depotToBump.traj rename to src/main/deploy/choreo/LEFT_two_depotToBump.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_two_depotToTrench.traj b/src/main/deploy/choreo/LEFT_two_depotToTrench.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_two_depotToTrench.traj rename to src/main/deploy/choreo/LEFT_two_depotToTrench.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_two_goOut.traj b/src/main/deploy/choreo/LEFT_two_goOut.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_two_goOut.traj rename to src/main/deploy/choreo/LEFT_two_goOut.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_two_jab.traj b/src/main/deploy/choreo/LEFT_two_jab.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_two_jab.traj rename to src/main/deploy/choreo/LEFT_two_jab.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_two_passing.traj b/src/main/deploy/choreo/LEFT_two_passing.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_two_passing.traj rename to src/main/deploy/choreo/LEFT_two_passing.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_two_reverse.traj b/src/main/deploy/choreo/LEFT_two_reverse.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_two_reverse.traj rename to src/main/deploy/choreo/LEFT_two_reverse.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_two_sotmDepot.traj b/src/main/deploy/choreo/LEFT_two_sotmDepot.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_two_sotmDepot.traj rename to src/main/deploy/choreo/LEFT_two_sotmDepot.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_two_sweep.traj b/src/main/deploy/choreo/LEFT_two_sweep.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_two_sweep.traj rename to src/main/deploy/choreo/LEFT_two_sweep.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_two_trenchReturn.traj b/src/main/deploy/choreo/LEFT_two_trenchReturn.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_two_trenchReturn.traj rename to src/main/deploy/choreo/LEFT_two_trenchReturn.traj diff --git a/src/main/deploy/choreo/LEFT/LEFT_two_trenchToDepot.traj b/src/main/deploy/choreo/LEFT_two_trenchToDepot.traj similarity index 100% rename from src/main/deploy/choreo/LEFT/LEFT_two_trenchToDepot.traj rename to src/main/deploy/choreo/LEFT_two_trenchToDepot.traj diff --git a/src/main/deploy/choreo/MISC/PreHeat.traj b/src/main/deploy/choreo/PreHeat.traj similarity index 100% rename from src/main/deploy/choreo/MISC/PreHeat.traj rename to src/main/deploy/choreo/PreHeat.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_one_bumpPreload.traj b/src/main/deploy/choreo/RIGHT_one_bumpPreload.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_one_bumpPreload.traj rename to src/main/deploy/choreo/RIGHT_one_bumpPreload.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_one_bumpReturn.traj b/src/main/deploy/choreo/RIGHT_one_bumpReturn.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_one_bumpReturn.traj rename to src/main/deploy/choreo/RIGHT_one_bumpReturn.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_one_bumpReturnFollow.traj b/src/main/deploy/choreo/RIGHT_one_bumpReturnFollow.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_one_bumpReturnFollow.traj rename to src/main/deploy/choreo/RIGHT_one_bumpReturnFollow.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_one_bumpReverseToTrench.traj b/src/main/deploy/choreo/RIGHT_one_bumpReverseToTrench.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_one_bumpReverseToTrench.traj rename to src/main/deploy/choreo/RIGHT_one_bumpReverseToTrench.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_one_bumpToOutpost.traj b/src/main/deploy/choreo/RIGHT_one_bumpToOutpost.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_one_bumpToOutpost.traj rename to src/main/deploy/choreo/RIGHT_one_bumpToOutpost.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_one_circle.traj b/src/main/deploy/choreo/RIGHT_one_circle.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_one_circle.traj rename to src/main/deploy/choreo/RIGHT_one_circle.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_one_defense.traj b/src/main/deploy/choreo/RIGHT_one_defense.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_one_defense.traj rename to src/main/deploy/choreo/RIGHT_one_defense.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_one_jab.traj b/src/main/deploy/choreo/RIGHT_one_jab.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_one_jab.traj rename to src/main/deploy/choreo/RIGHT_one_jab.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_one_reverse.traj b/src/main/deploy/choreo/RIGHT_one_reverse.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_one_reverse.traj rename to src/main/deploy/choreo/RIGHT_one_reverse.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_one_selfPass.traj b/src/main/deploy/choreo/RIGHT_one_selfPass.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_one_selfPass.traj rename to src/main/deploy/choreo/RIGHT_one_selfPass.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_one_trench.traj b/src/main/deploy/choreo/RIGHT_one_trench.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_one_trench.traj rename to src/main/deploy/choreo/RIGHT_one_trench.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_one_trenchPreload.traj b/src/main/deploy/choreo/RIGHT_one_trenchPreload.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_one_trenchPreload.traj rename to src/main/deploy/choreo/RIGHT_one_trenchPreload.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_one_trenchReturn.traj b/src/main/deploy/choreo/RIGHT_one_trenchReturn.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_one_trenchReturn.traj rename to src/main/deploy/choreo/RIGHT_one_trenchReturn.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_one_trenchToOutpost.traj b/src/main/deploy/choreo/RIGHT_one_trenchToOutpost.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_one_trenchToOutpost.traj rename to src/main/deploy/choreo/RIGHT_one_trenchToOutpost.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_stress_test_long.traj b/src/main/deploy/choreo/RIGHT_stress_test_long.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_stress_test_long.traj rename to src/main/deploy/choreo/RIGHT_stress_test_long.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_stress_test_overlap.traj b/src/main/deploy/choreo/RIGHT_stress_test_overlap.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_stress_test_overlap.traj rename to src/main/deploy/choreo/RIGHT_stress_test_overlap.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_three_depotToBump.traj b/src/main/deploy/choreo/RIGHT_three_depotToBump.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_three_depotToBump.traj rename to src/main/deploy/choreo/RIGHT_three_depotToBump.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_two_bumpReturn.traj b/src/main/deploy/choreo/RIGHT_two_bumpReturn.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_two_bumpReturn.traj rename to src/main/deploy/choreo/RIGHT_two_bumpReturn.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_two_bumpToOutpost.traj b/src/main/deploy/choreo/RIGHT_two_bumpToOutpost.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_two_bumpToOutpost.traj rename to src/main/deploy/choreo/RIGHT_two_bumpToOutpost.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_two_bumpToTrench.traj b/src/main/deploy/choreo/RIGHT_two_bumpToTrench.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_two_bumpToTrench.traj rename to src/main/deploy/choreo/RIGHT_two_bumpToTrench.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_two_depot.traj b/src/main/deploy/choreo/RIGHT_two_depot.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_two_depot.traj rename to src/main/deploy/choreo/RIGHT_two_depot.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_two_goOut.traj b/src/main/deploy/choreo/RIGHT_two_goOut.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_two_goOut.traj rename to src/main/deploy/choreo/RIGHT_two_goOut.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_two_jab.traj b/src/main/deploy/choreo/RIGHT_two_jab.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_two_jab.traj rename to src/main/deploy/choreo/RIGHT_two_jab.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_two_outpostToBump.traj b/src/main/deploy/choreo/RIGHT_two_outpostToBump.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_two_outpostToBump.traj rename to src/main/deploy/choreo/RIGHT_two_outpostToBump.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_two_outpostToTrench.traj b/src/main/deploy/choreo/RIGHT_two_outpostToTrench.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_two_outpostToTrench.traj rename to src/main/deploy/choreo/RIGHT_two_outpostToTrench.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_two_passing.traj b/src/main/deploy/choreo/RIGHT_two_passing.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_two_passing.traj rename to src/main/deploy/choreo/RIGHT_two_passing.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_two_reverse.traj b/src/main/deploy/choreo/RIGHT_two_reverse.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_two_reverse.traj rename to src/main/deploy/choreo/RIGHT_two_reverse.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_two_sotmDepot.traj b/src/main/deploy/choreo/RIGHT_two_sotmDepot.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_two_sotmDepot.traj rename to src/main/deploy/choreo/RIGHT_two_sotmDepot.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_two_sweep.traj b/src/main/deploy/choreo/RIGHT_two_sweep.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_two_sweep.traj rename to src/main/deploy/choreo/RIGHT_two_sweep.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_two_trenchReturn.traj b/src/main/deploy/choreo/RIGHT_two_trenchReturn.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_two_trenchReturn.traj rename to src/main/deploy/choreo/RIGHT_two_trenchReturn.traj diff --git a/src/main/deploy/choreo/RIGHT/RIGHT_two_trenchToOutpost.traj b/src/main/deploy/choreo/RIGHT_two_trenchToOutpost.traj similarity index 100% rename from src/main/deploy/choreo/RIGHT/RIGHT_two_trenchToOutpost.traj rename to src/main/deploy/choreo/RIGHT_two_trenchToOutpost.traj diff --git a/src/main/java/frc/robot/Constants.java b/src/main/java/frc/robot/Constants.java index b3270453..cacf6149 100644 --- a/src/main/java/frc/robot/Constants.java +++ b/src/main/java/frc/robot/Constants.java @@ -672,88 +672,88 @@ public static class AutonK { /* OLD PATHS */ //---RIGHT FIRST CYCLES - public static final String kRightOneJab = "RIGHT/RIGHT_one_jab"; - public static final String kRightOneTrench = "RIGHT/RIGHT_one_trench"; - public static final String kRightOneDefense = "RIGHT/RIGHT_one_defense"; - public static final String kRightOneReverse = "RIGHT/RIGHT_one_reverse"; + public static final String kRightOneJab = "RIGHT_one_jab"; + public static final String kRightOneTrench = "RIGHT_one_trench"; + public static final String kRightOneDefense = "RIGHT_one_defense"; + public static final String kRightOneReverse = "RIGHT_one_reverse"; //---RIGHT SECOND CYCLES - public static final String kRightTwoSotmDepot = "RIGHT/RIGHT_two_sotmDepot"; - public static final String kRightTwoDepot = "RIGHT/RIGHT_two_depot"; - public static final String kRightTwoSweep = "RIGHT/RIGHT_two_sweep"; - public static final String kRightTwoPassing = "RIGHT/RIGHT_two_passing"; - public static final String kRightTwoJab = "RIGHT/RIGHT_two_jab"; - public static final String kRightTwoReverse = "RIGHT/RIGHT_two_reverse"; + public static final String kRightTwoSotmDepot = "RIGHT_two_sotmDepot"; + public static final String kRightTwoDepot = "RIGHT_two_depot"; + public static final String kRightTwoSweep = "RIGHT_two_sweep"; + public static final String kRightTwoPassing = "RIGHT_two_passing"; + public static final String kRightTwoJab = "RIGHT_two_jab"; + public static final String kRightTwoReverse = "RIGHT_two_reverse"; //---LEFT FIRST CYCLES - public static final String kLeftOneJab = "LEFT/LEFT_one_jab"; - public static final String kLeftOneTrench = "LEFT/LEFT_one_trench"; - public static final String kLeftOneDefense = "LEFT/LEFT_one_defense"; - public static final String kLeftOneReverse = "LEFT/LEFT_one_reverse"; + public static final String kLeftOneJab = "LEFT_one_jab"; + public static final String kLeftOneTrench = "LEFT_one_trench"; + public static final String kLeftOneDefense = "LEFT_one_defense"; + public static final String kLeftOneReverse = "LEFT_one_reverse"; //---LEFT SECOND CYCLES - public static final String kLeftTwoSotmDepot = "LEFT/LEFT_two_sotmDepot"; - public static final String kLeftTwoDepot = "LEFT/LEFT_two_depot"; - public static final String kLeftTwoSweep = "LEFT/LEFT_two_sweep"; - public static final String kLeftTwoPassing = "LEFT/LEFT_two_passing"; - public static final String kLeftTwoJab = "LEFT/LEFT_two_jab"; - public static final String kLeftTwoReverse = "LEFT/LEFT_two_reverse"; + public static final String kLeftTwoSotmDepot = "LEFT_two_sotmDepot"; + public static final String kLeftTwoDepot = "LEFT_two_depot"; + public static final String kLeftTwoSweep = "LEFT_two_sweep"; + public static final String kLeftTwoPassing = "LEFT_two_passing"; + public static final String kLeftTwoJab = "LEFT_two_jab"; + public static final String kLeftTwoReverse = "LEFT_two_reverse"; //---MISC - public static final String kRightOneCircle = "RIGHT/RIGHT_one_circle"; - public static final String kLeftOneSweepAndDepot = "LEFT/LEFT_one_sweepAndDepot"; - public static final String kLeftThreeDepotToBump = "LEFT/LEFT_three_depotToBump"; - public static final String kRightThreeDepotToBump = "RIGHT/RIGHT_three_depotToBump"; + public static final String kRightOneCircle = "RIGHT_one_circle"; + public static final String kLeftOneSweepAndDepot = "LEFT_one_sweepAndDepot"; + public static final String kLeftThreeDepotToBump = "LEFT_three_depotToBump"; + public static final String kRightThreeDepotToBump = "RIGHT_three_depotToBump"; //---STRESS TEST - public static final String kRightStressTestLong = "RIGHT/RIGHT_stress_test_long"; - public static final String kRightStressTestOverlap = "RIGHT/RIGHT_stress_test_overlap"; - + public static final String kRightStressTestLong = "RIGHT_stress_test_long"; + public static final String kRightStressTestOverlap = "RIGHT_stress_test_overlap"; + /* NEW PATHS */ //---BUMP RETURN PATHS - public static final String kRightOneBumpReturn = "RIGHT/RIGHT_one_bumpReturn"; - public static final String kRightOneBumpReturnFollow = "RIGHT/RIGHT_one_bumpReturnFollow"; - public static final String kLeftOneBumpReturn = "LEFT/LEFT_one_bumpReturn"; - public static final String kLeftOneBumpReturnFollow = "LEFT/LEFT_one_bumpReturnFollow"; - public static final String kRightTwoBumpReturn = "RIGHT/RIGHT_two_bumpReturn"; - public static final String kLeftTwoBumpReturn = "LEFT/LEFT_two_bumpReturn"; - public static final String kRightTwoBumpToTrench = "RIGHT/RIGHT_two_bumpToTrench"; - public static final String kLeftTwoBumpToTrench = "LEFT/LEFT_two_bumpToTrench"; + public static final String kRightOneBumpReturn = "RIGHT_one_bumpReturn"; + public static final String kRightOneBumpReturnFollow = "RIGHT_one_bumpReturnFollow"; + public static final String kLeftOneBumpReturn = "LEFT_one_bumpReturn"; + public static final String kLeftOneBumpReturnFollow = "LEFT_one_bumpReturnFollow"; + public static final String kRightTwoBumpReturn = "RIGHT_two_bumpReturn"; + public static final String kLeftTwoBumpReturn = "LEFT_two_bumpReturn"; + public static final String kRightTwoBumpToTrench = "RIGHT_two_bumpToTrench"; + public static final String kLeftTwoBumpToTrench = "LEFT_two_bumpToTrench"; //---TRENCH RETURN PATHS - public static final String kRightOneTrenchReturn = "RIGHT/RIGHT_one_trenchReturn"; - public static final String kLeftOneTrenchReturn = "LEFT/LEFT_one_trenchReturn"; - public static final String kRightTwoTrenchReturn = "RIGHT/RIGHT_two_trenchReturn"; - public static final String kLeftTwoTrenchReturn = "LEFT/LEFT_two_trenchReturn"; - public static final String kRightOneBumpTrenchReturn = "RIGHT/RIGHT_one_bumpReverseToTrench"; - public static final String kLeftOneBumpTrenchReturn = "LEFT/LEFT_one_bumpReverseToTrench"; - + public static final String kRightOneTrenchReturn = "RIGHT_one_trenchReturn"; + public static final String kLeftOneTrenchReturn = "LEFT_one_trenchReturn"; + public static final String kRightTwoTrenchReturn = "RIGHT_two_trenchReturn"; + public static final String kLeftTwoTrenchReturn = "LEFT_two_trenchReturn"; + public static final String kRightOneBumpTrenchReturn = "RIGHT_one_bumpReverseToTrench"; + public static final String kLeftOneBumpTrenchReturn = "LEFT_one_bumpReverseToTrench"; + //---OUTPOST PATHS - public static final String kRightOneTrenchToOutpost = "RIGHT/RIGHT_one_trenchToOutpost"; - public static final String kRightTwoTrenchToOutpost = "RIGHT/RIGHT_two_trenchToOutpost"; - public static final String kRightTwoOutpostToTrench = "RIGHT/RIGHT_two_outpostToTrench"; - public static final String kRightOneBumpToOutpost = "RIGHT/RIGHT_one_bumpToOutpost"; - public static final String kRightTwoBumpToOutpost = "RIGHT/RIGHT_two_bumpToOutpost"; - public static final String kRightTwoOutpostToBump = "RIGHT/RIGHT_two_outpostToBump"; + public static final String kRightOneTrenchToOutpost = "RIGHT_one_trenchToOutpost"; + public static final String kRightTwoTrenchToOutpost = "RIGHT_two_trenchToOutpost"; + public static final String kRightTwoOutpostToTrench = "RIGHT_two_outpostToTrench"; + public static final String kRightOneBumpToOutpost = "RIGHT_one_bumpToOutpost"; + public static final String kRightTwoBumpToOutpost = "RIGHT_two_bumpToOutpost"; + public static final String kRightTwoOutpostToBump = "RIGHT_two_outpostToBump"; //---DEPOT PATHS - public static final String kLeftOneTrenchToDepot = "LEFT/LEFT_one_trenchToDepot"; - public static final String kLeftTwoTrenchToDepot = "LEFT/LEFT_two_trenchToDepot"; - public static final String kLeftTwoDepotToTrench = "LEFT/LEFT_two_depotToTrench"; - public static final String kLeftOneBumpToDepot = "LEFT/LEFT_one_bumpToDepot"; - public static final String kLeftTwoBumpToDepot = "LEFT/LEFT_two_bumpToDepot"; - public static final String kLeftTwoDepotToBump = "LEFT/LEFT_two_depotToBump"; + public static final String kLeftOneTrenchToDepot = "LEFT_one_trenchToDepot"; + public static final String kLeftTwoTrenchToDepot = "LEFT_two_trenchToDepot"; + public static final String kLeftTwoDepotToTrench = "LEFT_two_depotToTrench"; + public static final String kLeftOneBumpToDepot = "LEFT_one_bumpToDepot"; + public static final String kLeftTwoBumpToDepot = "LEFT_two_bumpToDepot"; + public static final String kLeftTwoDepotToBump = "LEFT_two_depotToBump"; //---MISC - public static final String kRightOneSelfPass = "RIGHT/RIGHT_one_selfPass"; - public static final String kLeftOneSelfPass = "LEFT/LEFT_one_selfPass"; - public static final String kRightTwoGoOut = "RIGHT/RIGHT_two_goOut"; - public static final String kLeftTwoGoOut = "LEFT/LEFT_two_goOut"; - public static final String kRightBumpPreload = "RIGHT/RIGHT_one_bumpPreload"; - public static final String kLeftBumpPreload = "LEFT/LEFT_one_bumpPreload"; - public static final String kRightTrenchPreload = "RIGHT/RIGHT_one_trenchPreload"; - public static final String kLeftTrenchPreload = "LEFT/LEFT_one_trenchPreload"; - - public static final String kCenterPreload = "MISC/CENTER_one_preload"; + public static final String kRightOneSelfPass = "RIGHT_one_selfPass"; + public static final String kLeftOneSelfPass = "LEFT_one_selfPass"; + public static final String kRightTwoGoOut = "RIGHT_two_goOut"; + public static final String kLeftTwoGoOut = "LEFT_two_goOut"; + public static final String kRightBumpPreload = "RIGHT_one_bumpPreload"; + public static final String kLeftBumpPreload = "LEFT_one_bumpPreload"; + public static final String kRightTrenchPreload = "RIGHT_one_trenchPreload"; + public static final String kLeftTrenchPreload = "LEFT_one_trenchPreload"; + + public static final String kCenterPreload = "CENTER_one_preload"; } } diff --git a/src/main/java/frc/robot/autons/WaltAdaptableAutonFactory.java b/src/main/java/frc/robot/autons/WaltAdaptableAutonFactory.java index 34ca03b3..65156953 100644 --- a/src/main/java/frc/robot/autons/WaltAdaptableAutonFactory.java +++ b/src/main/java/frc/robot/autons/WaltAdaptableAutonFactory.java @@ -274,7 +274,7 @@ private AutoTrajectory createTraj(AutoRoutine routine, String name) { // before a match. Ensures we dont stall for like 0.4 seconds at the start of auton public AutoRoutine preheater() { System.out.println("PREHEAT MADE"); - return adaptableAuton("PreHeat", new AdaptableAutonInfo("MISC/PreHeat", AutonK.kShootingTimeout, false, 0)); + return adaptableAuton("PreHeat", new AdaptableAutonInfo("PreHeat", AutonK.kShootingTimeout, false, 0)); } // Builds a single-segment auton: follow one trajectory and fire waypoint diff --git a/src/main/java/frc/robot/dashboards/AutonChooser.java b/src/main/java/frc/robot/dashboards/AutonChooser.java index 8ce861c0..ffa73f40 100644 --- a/src/main/java/frc/robot/dashboards/AutonChooser.java +++ b/src/main/java/frc/robot/dashboards/AutonChooser.java @@ -37,7 +37,7 @@ * etc. */ public class AutonChooser { - private static final String kPreheatTrajectory = "MISC/PreHeat"; + private static final String kPreheatTrajectory = "PreHeat"; public static AutoChooser m_chooser; public static WaltAdaptableAutonFactory m_adaptableAutonFactory; From c60d61a514c98814d73304200f89289d4a59dc79 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Sat, 16 May 2026 22:16:33 -0400 Subject: [PATCH 20/25] edited shooter markdowns and using mermaid for graph --- .../frc/robot/subsystems/shooter/README.MD | 15 +++--- .../robot/subsystems/shooter/calc/README.MD | 50 ++++++++++++------- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/main/java/frc/robot/subsystems/shooter/README.MD b/src/main/java/frc/robot/subsystems/shooter/README.MD index 542d0ae8..7513c094 100644 --- a/src/main/java/frc/robot/subsystems/shooter/README.MD +++ b/src/main/java/frc/robot/subsystems/shooter/README.MD @@ -43,19 +43,20 @@ Gate: motor must actually be in velocity control mode When a detection fires, a timer starts. The shot is "confirmed" after: - The ball detection clears (acceleration recovers) - 1.2 seconds have elapsed on the timer -- The motor is still in shoot mode +- We're still controlling to shoot This debounced trigger (`getBallShotDebounceTrg()`) is what tells the rest of the robot a ball has left, so it can advance the indexer or track shot count. Pretty neat for something with no sensor. ### Driver RPS Tweaks -Drivers can trim the computed flywheel speed without breaking anything: +Drivers can trim the computed flywheel speed without breaking anything - **Dynamic**: ±5% of the calculated RPS - **Static**: ±`kDriverRPSIncreaseD` per button press - **Reset**: back to 0 on release All tweaks are clamped to `[0, kShooterMaxRPSd]`. Gated behind `kAllowDriverRPSTweak` if you want to turn it off. +It's not being used rn but if we need to cope harder this is what we do :sunglasses: ### What periodic() Does @@ -68,7 +69,7 @@ Every loop: 6. Refreshes the spun-up check. 7. Logs everything. -A `Tracer` is wired up for epoch timing diagnostics if you're hunting loop overruns. +A `Tracer` is wired up for epoch timing diagnostics if you're hunting loop overruns. (hopefully syscore doesn't fail us) ### NT Tunable Overrides @@ -81,7 +82,7 @@ Useful when building LERP tables. Leave them disabled during actual matches. ## Turret.java -The turret rotates on a Kraken X44 (FOC, on the Canivor bus) using `PositionVoltage` control. It also carries two absolute encoders, not for redundancy, but because one encoder alone can't tell you absolute position if the turret has more than one full rotation of range. +The turret rotates on a Kraken X44 (FOC, on the Canivore bus) using `PositionVoltage` control. It also carries two absolute encoders, not for redundancy, but because one encoder alone can't tell you absolute position if the turret has more than one full rotation of range. ### Dual-Encoder Absolute Position Recovery @@ -90,14 +91,14 @@ Two encoders are geared at different ratios: - **Encoder A** (CANcoder): 10-tooth driving a 100-tooth output, so one full encoder reading every 36° of turret travel - **Encoder B** (DutyCycleEncoder, DIO 3): 19-tooth driving a 100-tooth output, so one full encoder reading every ~18.95° of turret travel -Because these periods are incommensurate, at any given absolute turret angle, only one combination of (Encoder A reading, Encoder B reading) is possible. `calcTurretAngleLCM()` exploits this: +Because these periods are incommensurate, at any given absolute turret angle, only one combination of (Encoder A reading, Encoder B reading) is possible. `calcTurretAngleLCM()` exploits this through Least Common Modulus: 1. Use Encoder A's reading to generate every candidate absolute angle within the turret's range (one per integer multiple of the 36° period). 2. For each candidate, predict what Encoder B *should* read at that angle. 3. Compare predicted vs actual Encoder B, wrapping the difference to ±180° to handle rollover. 4. Return the candidate with the smallest error. -This runs on construction (`homeTurret(true)`) and also every loop for logging. If both encoders agree, you can trust the position even after a power cycle. +This runs on construction (`homeTurret(true)`) and also every loop for logging. If both encoders agree, you can trust the position even after a power cycle. It also exists as an override button on the manipulator so that incase something goes wrong with that homing, we wont get screwed over :pray: ### Snapback Logic @@ -128,7 +129,7 @@ The hood homes by driving slowly into its physical lower limit and waiting for t There's a 5-second timeout in case something goes wrong. If it times out or gets interrupted, the hood brakes and logs a warning. -> Note: In the current build, the homing command is not set as a default command. The hood is initialized with `setPosition(0)` directly in the constructor and immediately considered homed. `hoodCurrentSenseHomingCmd()` is still available if you want to actually run the homing routine. +> Note: In the current build, the homing command is not set as a default command. The hood is initialized with `setPosition(0)` directly in the constructor and immediately considered homed. `hoodCurrentSenseHomingCmd()` is still available if you want to actually run the homing routine, and still exists as an override button. ### Key Methods diff --git a/src/main/java/frc/robot/subsystems/shooter/calc/README.MD b/src/main/java/frc/robot/subsystems/shooter/calc/README.MD index 191f5ed6..319c6e63 100644 --- a/src/main/java/frc/robot/subsystems/shooter/calc/README.MD +++ b/src/main/java/frc/robot/subsystems/shooter/calc/README.MD @@ -1,6 +1,6 @@ # /calc -This is where it all hit the fan :D (:sob: :sob: :sob:) +This is where it all hit the fan :sob: :sob: :sob: This folder handles all of the math that turns "robot is at this position going this fast" into "set flywheel to X RPS, hood to Y rotations, turret to Z degrees." It runs on a **background thread** at 75 Hz so none of this touches the main robot loop. Two classes: `ShooterCalc` coordinates everything and manages the thread, and `ShotCalculator` contains the actual ballistics: tables, physics, iterative moving-shot convergence, the whole deal. @@ -50,8 +50,8 @@ The robot shoots at different targets depending on where it is on the field: Robot on own side of the hub -> SHOOTING MODE Target: enemy hub center (alliance-flipped appropriately) -Robot past the hub into opponent territory -> PASSING MODE - Target: a point near the sideline (~2m in from the wall) +Robot past the hub into neutral zone -> PASSING MODE + Target: right by/on the bump - Robot left of field center -> left passing point - Robot right of field center -> right passing point ``` @@ -59,6 +59,7 @@ Robot past the hub into opponent territory -> PASSING MODE `m_isPassingFlag` is set/cleared here and read by `ShotCalculator` to select the right LERP table. There's also a "no-passing zone" check (`robotInNoPassingZone`) that detects when the robot is near the opponent hub, where some passing angles may not be safe. The check uses field X/Y bounds and alliance color. +>We are not using this as of right now, as it somewhat screws with targeting ### Turret Azimuth: `calcAzimuth()` @@ -139,11 +140,9 @@ There are two active tables: - Hood: ~0.70 to 1.16 rad - TOF: 0.254s at all distances -~~**`kAngryTurretTable`**~~: flat-trajectory passing shots for when the hood physically can't go up. Commented out (32 points were in there). Preserved in case it's ever needed again. - ### Iterative Moving-Shot: `iterativeMovingShotFromInterpolationMap()` -This is the core of on-the-move shooting. It converges on shot parameters that account for the robot moving during ball flight. Up to 8 iterations: +This is the core of shoot on the move. It converges on shot parameters that account for the robot moving during ball flight. Up to 8 iterations: ``` Start: table lookup at current distance to target @@ -165,7 +164,7 @@ Each iteration: `vxLaunch` and `vyLaunch` are the actual velocity of the turret pivot point, accounting for both robot linear velocity and rotational velocity (the turret pivot is offset from the robot center, so rotation contributes a tangential component). -The drag compensation is tunable via `/ShotCalc/sotmDragCoeff`. Setting it to 0 disables drag compensation entirely. The default 0.5 was empirically determined. There's a comment in the code that says `2974 IS OUR LUCKY NUMBER` and another that says `254 RAHHHHHHHHHHHHHHH` - both teams' drag coefficients were apparently considered at some point. We landed on 0.5. +The drag compensation is tunable via `/ShotCalc/sotmDragCoeff`. Setting it to 0 disables drag compensation entirely. ### Physics Utilities @@ -203,7 +202,7 @@ Both records have `acceptLogging()` methods that push their fields to NetworkTab | NT Key | Default | What It Does | |--------|---------|-------------| | `/ShotCalc/sotmDragCoeff` | 0.5 | Drag coefficient for SOTM lateral compensation | -| `/ShotCalc/lateralBiasGainRots` | `kTurretLateralBiasGainRots` | Sinusoidal lateral bias correction gain | +| `/ShotCalc/lateralBiasGainRots` | `kTurretLateralBiasGainRots` | Sinusoidal lateral bias correction gain for the turret| | `Shooter/Calculator/RPSBoost` | 0.75 | Boost applied to passing table RPS | All tunable via `WaltTunable`, enable in NT and set a value. Changes apply to the next calc cycle. @@ -211,15 +210,28 @@ All tunable via `WaltTunable`, enable in NT and set a value. Changes apply to th --- ## Quick Data Flow - -``` -calcCallback() @ 75 Hz - calculateTarget() -> m_aimTarget (volatile) - iterativeMovingShotFromInterpolationMap() - ShotLerpTable lookup + convergence loop - dragCompensatedTOF() - -> ShotDataLerp (flywheel vel, hood angle, predicted target, TOF) - calcAzimuth() - -> AzimuthCalcDetails (turret setpoint, velocity FF) - -> ShotCalcOutputs (volatile) -> read by Shooter.periodic() +```mermaid +graph TD + Hz[calcCallback @ 75 Hz] --> P1 + + subgraph Calculation Thread + P1[calculateTarget] --> O1["m_aimTarget (volatile)"] + O1 --> P2 + + subgraph P2 [iterativeMovingShotFromInt] + P2a[ShotLerpTable lookup] --> P2b[convergence loop] + P2b --> P2c[dragCompensatedTOF] + end + + P2 --> O2["ShotDataLerp + flywheel vel | hood angle + predicted target | TOF"] + + O2 --> P3[calcAzimuth] + P3 --> O3["AzimuthCalcDetails + turret setpoint | velocity FF"] + end + + O3 --> O4["ShotCalcOutputs (volatile)"] + O4 --> P4["Shooter.periodic()"] ``` From bd7357fb13b0bc796680c2e7e315470a04b73959 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Thu, 21 May 2026 20:38:41 -0400 Subject: [PATCH 21/25] clean up & reformatting all of shooter subsystem --- src/main/java/frc/robot/README.md | 7 +- src/main/java/frc/robot/Robot.java | 1 - .../frc/robot/subsystems/shooter/Hood.java | 58 +++++-- .../frc/robot/subsystems/shooter/Shooter.java | 160 ++++++++++-------- .../frc/robot/subsystems/shooter/Turret.java | 127 +++++++++----- 5 files changed, 222 insertions(+), 131 deletions(-) diff --git a/src/main/java/frc/robot/README.md b/src/main/java/frc/robot/README.md index c80dcfdd..ab37d5be 100644 --- a/src/main/java/frc/robot/README.md +++ b/src/main/java/frc/robot/README.md @@ -21,9 +21,6 @@ The code is split into folders by responsibility. Each folder handles one aspect ### [autons/](autons/): Autonomous routines -Defines what the robot does during the 15-second autonomous period at the start of a match, where no driver input is allowed. -#### this is where we start praying :pray: - - `WaltAdaptableAutonFactory.java`: Builds auto routines by chaining Choreo trajectory segments together. --- @@ -46,7 +43,7 @@ Created by external tools, not written by hand. Don't edit these directly unless ### [subsystems/](subsystems/): Robot mechanisms -Each file represents a physical mechanism on the robot and the code that controls it. This is where most of the action happens. +Each file represents a physical mechanism on the robot(for the most part) and the code that controls it. This is where most of the action happens. #### [subsystems/shooter/](subsystems/shooter/): Shooting mechanism @@ -76,7 +73,7 @@ this is where it hit the fan :scream: :scream: :scream: ### [vision/](vision/): Target tracking -Uses cameras to detect and track field targets for automatic aiming. The robot can see better than most of us at this point. +Uses cameras to localize where we are on the field. - `WaltCamera.java`: Camera wrapper. - `Detection.java`: Detects game pieces. diff --git a/src/main/java/frc/robot/Robot.java b/src/main/java/frc/robot/Robot.java index 5cf9470f..bbd33e4f 100644 --- a/src/main/java/frc/robot/Robot.java +++ b/src/main/java/frc/robot/Robot.java @@ -20,7 +20,6 @@ import com.ctre.phoenix6.swerve.SwerveRequest; import choreo.auto.AutoFactory; -import edu.wpi.first.math.MathUtil; import edu.wpi.first.math.filter.SlewRateLimiter; import edu.wpi.first.math.geometry.Pose2d; import edu.wpi.first.units.measure.AngularVelocity; diff --git a/src/main/java/frc/robot/subsystems/shooter/Hood.java b/src/main/java/frc/robot/subsystems/shooter/Hood.java index aca76d92..b4f9a8eb 100644 --- a/src/main/java/frc/robot/subsystems/shooter/Hood.java +++ b/src/main/java/frc/robot/subsystems/shooter/Hood.java @@ -27,14 +27,33 @@ import frc.util.WaltLogger.BooleanLogger; import frc.util.WaltLogger.DoubleLogger; +/* + * Hood - TalonFXS-driven hood angle control. + * Zero is set at the hardstop via current-sense homing. Position is in rotations. + * + * Vocab: + * homing - drives to the hardstop at low voltage, detects stall via stator current spike through a Debouncer + * atPos - closed-loop error is within kHoodMaxErrD of the setpoint + */ public class Hood extends SubsystemBase { + // ---- CONSTANTS ---- private static final String kLogTab = "Shooter/Hood"; + // converts encoder rotations to physical hood angle degrees for logging private static final double kAbsoluteToPhysicalAngleRatio = (kPhysicalHoodMaxPosition_double - kPhysicalHoodMinPosition_double) / (360.0 * (kHoodMaxRots_double - kHoodMinRots_double)); + + // ---- MOTOR + CONTROLS ---- private final TalonFXS m_hood = new TalonFXS(kHoodCANID, kShooterBus); private final PositionVoltage m_hoodPVRequest = new PositionVoltage(0).withEnableFOC(false); private final VoltageOut m_hoodZeroReq = new VoltageOut(0); + private final StaticBrake m_BrakeReq = new StaticBrake(); + // ---- SIGNALS ---- + private final StatusSignal sig_hoodStatorCurrent = m_hood.getStatorCurrent(); + private final StatusSignal sig_hoodPos = m_hood.getPosition(); + private final StatusSignal sig_hoodCLErr = m_hood.getClosedLoopError(); + + // ---- LOGGERS ---- private final BooleanLogger log_hoodHomed = WaltLogger.logBoolean(kLogTab, "Homed"); private final DoubleLogger log_hoodControlPos = WaltLogger.logDouble(kLogTab, "controlPos"); private final DoubleLogger log_hoodCurrentPos = WaltLogger.logDouble(kLogTab, "currentPos"); @@ -42,19 +61,16 @@ public class Hood extends SubsystemBase { private final DoubleLogger log_hoodCLErr = WaltLogger.logDouble(kLogTab, "closedLoopErr"); private final BooleanLogger log_hoodAtPos = WaltLogger.logBoolean(kLogTab, "hoodAtPos"); + // ---- STATE ---- private Debouncer m_currentDebouncer = new Debouncer(0.125, DebounceType.kRising); - - private final StatusSignal sig_hoodStatorCurrent = m_hood.getStatorCurrent(); - private final StatusSignal sig_hoodPos = m_hood.getPosition(); - private final StatusSignal sig_hoodCLErr = m_hood.getClosedLoopError(); - private BooleanSupplier m_currentSpike = () -> sig_hoodStatorCurrent.getValueAsDouble() > 5.0; - - private final StaticBrake m_BrakeReq = new StaticBrake(); - private boolean m_isHoodHomed = false; private boolean m_hoodAtPos = false; + // ============================================================= + // CONSTRUCTOR + // ============================================================= + public Hood() { m_hood.getConfigurator().apply(kHoodTalonFXSConfiguration); @@ -68,7 +84,10 @@ public Hood() { // setDefaultCommand(hoodCurrentSenseHomingCmd()); } - // ---HOOD + // ============================================================= + // CONTROL + // ============================================================= + public void setHoodPos(double rots) { m_hood.setControl(m_hoodPVRequest.withPosition(rots)); log_hoodControlPos.accept(rots); @@ -78,10 +97,19 @@ public Command setHoodPosCmd(double rots) { return runOnce(() -> setHoodPos(rots)); } + // converts encoder rotations to physical degrees - only used for logging private static double getHoodAngleDeg(double posRots) { return kPhysicalHoodMinPosition_double + (posRots * 360.0 - kHoodMinRots_double * 360.0) * kAbsoluteToPhysicalAngleRatio; } + public void setHoodNeutralMode(NeutralModeValue value) { + m_hood.setNeutralMode(value); + } + + // ============================================================= + // STATE + // ============================================================= + public boolean isHoodHomed() { return m_isHoodHomed; } @@ -96,6 +124,10 @@ public boolean atPosition() { return m_hoodAtPos; } + // ============================================================= + // HOMING + // ============================================================= + public Command hoodCurrentSenseHomingCmd(){ Runnable init = () -> { m_hood.getConfigurator().apply(ShooterK.kHoodTalonFXSConfigurationNoSoftLimit); @@ -120,15 +152,15 @@ public Command hoodCurrentSenseHomingCmd(){ log_hoodHomed.accept(m_isHoodHomed); }; - BooleanSupplier isFinished = () -> + BooleanSupplier isFinished = () -> m_currentDebouncer.calculate(m_currentSpike.getAsBoolean()); return new FunctionalCommand(init, () -> {}, end, isFinished, this).withTimeout(5); } - public void setHoodNeutralMode(NeutralModeValue value) { - m_hood.setNeutralMode(value); - } + // ============================================================= + // PERIODIC + // ============================================================= @Override public void periodic() { diff --git a/src/main/java/frc/robot/subsystems/shooter/Shooter.java b/src/main/java/frc/robot/subsystems/shooter/Shooter.java index 6bdcb5bf..4b182299 100644 --- a/src/main/java/frc/robot/subsystems/shooter/Shooter.java +++ b/src/main/java/frc/robot/subsystems/shooter/Shooter.java @@ -18,12 +18,8 @@ import edu.wpi.first.math.MathUtil; import edu.wpi.first.math.geometry.Pose2d; import edu.wpi.first.math.kinematics.ChassisSpeeds; -import edu.wpi.first.math.system.plant.DCMotor; -import edu.wpi.first.math.system.plant.LinearSystemId; - import edu.wpi.first.wpilibj.Timer; import edu.wpi.first.wpilibj.Tracer; -import edu.wpi.first.wpilibj.simulation.FlywheelSim; import edu.wpi.first.wpilibj2.command.Command; import edu.wpi.first.wpilibj2.command.Commands; import edu.wpi.first.wpilibj2.command.SubsystemBase; @@ -47,60 +43,70 @@ import frc.util.WaltLogger.BooleanLogger; import frc.util.WaltLogger.DoubleLogger; +/* + * Shooter - flywheel velocity control + Hood + Turret coordination. + * Owns ShooterCalc (background 75 Hz thread) and pushes its outputs to each subsystem every periodic. + * Ball detection works by watching flywheel acceleration drop while in VelocityTorqueCurrentFOC mode. + * + * Vocab: + * spunUp - flywheel closed-loop error is within 0.5 RPS of setpoint + * ballDetected - acceleration drops below threshold, meaning a ball entered the flywheel + * shotDropSeen - latch set when a ball is first detected, cleared when exiting shoot control mode + * driverRPSTweak - small RPS offset the driver can add/remove at runtime for fine-tuning + */ public class Shooter extends SubsystemBase { + // ---- TUNING ---- // NT-tunable overrides for LERP table building (default off) private static final WaltTunable kShooterRPSOverride = new WaltTunable("/Shooter/shooterRPSOverride", kShooterRPSd); private static final WaltTunable kHoodRotsOverride = new WaltTunable("/Shooter/hoodRotsOverride", 0.0); - private static final WaltTunable kBallDetectionTuner = + private static final WaltTunable kBallDetectionTuner = new WaltTunable("Shooter/ballDetectionTuner", 0); private static final double kHoodLockedPosRots = Rotations.of(0.33).magnitude(); + // ---- MOTORS + CONTROLS ---- + private final TalonFX m_shooterA = new TalonFX(kShooterA_CANID, Constants.kShooterBus); // X44 + private final TalonFX m_shooterB = new TalonFX(kShooterB_CANID, Constants.kShooterBus); // X44 + // private final VelocityVoltage m_velocityRequest = new VelocityVoltage(0).withEnableFOC(true).withSlot(0); + private final VelocityTorqueCurrentFOC m_veloTQFOCReq = new VelocityTorqueCurrentFOC(0).withSlot(1); + private final CoastOut m_motorIdleReq = new CoastOut(); + + // ---- SUBSYSTEM REFS ---- + private final Supplier m_threadsafeSwerveSup; + public final Hood m_hood; + public final Turret m_turret; + private final ShooterCalc m_shooterCalc; + + // ---- RUNTIME STATE ---- private final Tracer m_periodicTracer = new Tracer(); - /* VARIABLES */ // boolean m_useShotCalculator = true; - private double m_currentFlywheelVelocityRotPerSec; private double m_latestFlywheelAccelerationRotPerSec; private boolean m_isShooterSpunUp = false; private boolean m_shotDropSeen = false; - private final Timer m_shotRecoveryTimer = new Timer(); - private int m_fuelStored = 8; // private final TurretVisualizer m_turretVisualizer; // private final FuelSim m_fuelSim; - // ---MOTORS + CONTROL REQUESTS - private final TalonFX m_shooterA = new TalonFX(kShooterA_CANID, Constants.kShooterBus); // X44 - private final TalonFX m_shooterB = new TalonFX(kShooterB_CANID, Constants.kShooterBus); // X44 - // private final VelocityVoltage m_velocityRequest = new VelocityVoltage(0).withEnableFOC(true).withSlot(0); - private final VelocityTorqueCurrentFOC m_veloTQFOCReq = new VelocityTorqueCurrentFOC(0).withSlot(1); - private final CoastOut m_motorIdleReq = new CoastOut(); - - private final Supplier m_threadsafeSwerveSup; - - public final Hood m_hood; - public final Turret m_turret; - - // thread copde + // ---- CALC STATE ---- + // read by ShooterCalc background thread - volatile so reads don't go stale + // thread co(p)de so that we don't overrun (syscore save us all please) private volatile double m_latestTurretPositionRots = 0.0; - private final ShooterCalc m_shooterCalc; - private double m_calcFlywheelVelocityRotPerSec = kShooterRPSd; private double m_calcHoodRots = kHoodRotsd; private double m_driverRPSTweak = 0.0; - private int m_ballsShot = 0; + // ---- SIGNALS ---- private final StatusSignal sig_shooterCLErr = m_shooterA.getClosedLoopError(); private final StatusSignal sig_shooterAVelo = m_shooterA.getVelocity(); private final StatusSignal sig_shooterAAccel = m_shooterA.getAcceleration(); private final StatusSignal sig_shooterACtrlMode = m_shooterA.getControlMode(); - // ---LOGIC BOOLEANS + // ---- TRIGGERS ---- private final Trigger trg_inShootCtrlMode = new Trigger(() -> {return sig_shooterACtrlMode.getValue() == ControlModeValue.VelocityVoltage; }); private final Trigger trg_ballDetected = new Trigger(() -> detectShot()).and(trg_inShootCtrlMode); private final Trigger trg_shotDropSeen = new Trigger(() -> m_shotDropSeen); @@ -111,34 +117,33 @@ public class Shooter extends SubsystemBase { .and(trg_ballDetected.negate()) .and(trg_debounceHit); - /* SIM OBJECTS */ - private final FlywheelSim m_shooterSim = new FlywheelSim(LinearSystemId.createFlywheelSystem( - DCMotor.getKrakenX44(2), kShooterMoI, kShooterGearing), DCMotor.getKrakenX60Foc(2) // returns gearbox - ); + // ---- SIM ---- + // private final FlywheelSim m_shooterSim = new FlywheelSim(LinearSystemId.createFlywheelSystem( + // DCMotor.getKrakenX44(2), kShooterMoI, kShooterGearing), DCMotor.getKrakenX60Foc(2) // returns gearbox + // ); // private final DCMotorSim m_turretSim = new DCMotorSim(LinearSystemId.createDCMotorSystem( // DCMotor.getKrakenX44Foc(1), kTurretMoI, kTurretGearing), DCMotor.getKrakenX44Foc(1) // returns gearbox // ); - /* LOGGERS */ + // ---- LOGGERS ---- private final DoubleLogger log_shooterVelocityRPS = WaltLogger.logDouble("Shooter/Flywheel", "velocityRPS"); private final DoubleLogger log_shooterAccelRPS = WaltLogger.logDouble("Shooter/Flywheel", "accelRPS"); private final DoubleLogger log_turretPositionRots = WaltLogger.logDouble("Turret", "positionRots"); private final DoubleLogger log_turretPositionRobotRelativeRots = WaltLogger.logDouble("Turret", "positionRobotRelativeRots"); - private final BooleanLogger log_spunUp = WaltLogger.logBoolean(kLogTab, "spunUp"); // private final BooleanLogger log_canTurretShoot = WaltLogger.logBoolean(kLogTab, "canTurretShoot"); - private final DoubleLogger log_shooterClosedLoopError = WaltLogger.logDouble("Shooter/Flywheel", "closedLoopError"); - private final BooleanLogger log_ballDetected = WaltLogger.logBoolean(kLogTab, "ballDetected"); private final BooleanLogger log_ballShotDebounce = WaltLogger.logBoolean(kLogTab, "ballShot"); - private final DoubleLogger log_ballsShot = new DoubleLogger("Shooter/Flywheel", "balls shot"); private final DoubleLogger log_calcFlywheelVelocity = new DoubleLogger("Shooter/Flywheel", "calcFlywheelVelocity"); private final DoubleLogger log_driverAddedRPS = WaltLogger.logDouble(kLogTab, "driverAddedRPS"); - /* CONSTRUCTOR */ + // ============================================================= + // CONSTRUCTOR + // ============================================================= + public Shooter(Supplier poseSupplier, Supplier threadsafeSwerveStateSup, Supplier fieldSpeedsSupplier) { m_hood = new Hood(); m_turret = new Turret(); @@ -174,7 +179,10 @@ public Shooter(Supplier poseSupplier, Supplier threads initSim(); } - // ---SHOOTER (Velocity Control) + // ============================================================= + // FLYWHEEL CONTROL + // ============================================================= + public Command setShooterVelocityCmd(AngularVelocity RPS) { return runOnce(() -> setShooterVelocity(RPS)); } @@ -242,11 +250,39 @@ private void refreshShooterSpunUp() { m_isShooterSpunUp = sig_shooterCLErr.isNear(0, 0.5); } + // ============================================================= + // BALL DETECTION + // ============================================================= + + // acceleration drops when a ball hits the flywheel - threshold is tunable via NT + private boolean detectShot() { + double detectionThreshold = kBallDetectionTuner.getOr(-3.0); + return m_latestFlywheelAccelerationRotPerSec <= detectionThreshold; + } + + // /** + // * Launches SIMULATION FUEL™ at the current Flywheel Velocity, current Hood + // * Angle, and the + // * current Turret Position. + // */ + // public void launchFuel() { + // if (m_fuelStored == 0) + // return; + // // m_fuelStored--; + // } + + public Trigger getBallShotDebounceTrg() { + return trg_ballShotDebounced; + } + + // ============================================================= + // GETTERS + // ============================================================= + public boolean isShooterSpunUp() { return m_isShooterSpunUp; } - /* GETTERS */ public double getShooterVelocityRotPerSec() { return m_currentFlywheelVelocityRotPerSec; } @@ -259,15 +295,6 @@ public DoubleSupplier getShooterDesiredRotPerSecSupp() { return () -> m_calcFlywheelVelocityRotPerSec; } - /* SIMULATION */ - public boolean simAbleToIntake() { - return canIntake(); - } - - public void simIntake() { - intakeFuel(); - } - /** * @return true if robot can store more fuel */ @@ -279,24 +306,16 @@ public void intakeFuel() { m_fuelStored++; } - private boolean detectShot() { - double detectionThreshold = kBallDetectionTuner.getOr(-3.0); - return m_latestFlywheelAccelerationRotPerSec <= detectionThreshold; - } + // ============================================================= + // SIMULATION + // ============================================================= - // /** - // * Launches SIMULATION FUEL™ at the current Flywheel Velocity, current Hood - // * Angle, and the - // * current Turret Position. - // */ - // public void launchFuel() { - // if (m_fuelStored == 0) - // return; - // // m_fuelStored--; - // } + public boolean simAbleToIntake() { + return canIntake(); + } - public Trigger getBallShotDebounceTrg() { - return trg_ballShotDebounced; + public void simIntake() { + intakeFuel(); } // TODO: update orientation values (if needed) @@ -305,7 +324,10 @@ private void initSim() { TalonFXSimState.MotorType.KrakenX60); } - /* PERIODICS */ + // ============================================================= + // PERIODIC + // ============================================================= + @Override public void periodic() { m_periodicTracer.addEpoch("Entry (Unused Time)"); @@ -383,8 +405,8 @@ public void periodic() { // m_periodicTracer.printEpochs(); } - @Override - public void simulationPeriodic() { - WaltMotorSim.updateSimFX(m_shooterA, m_shooterSim); - } -} \ No newline at end of file + // @Override + // public void simulationPeriodic() { + // WaltMotorSim.updateSimFX(m_shooterA, m_shooterSim); + // } +} diff --git a/src/main/java/frc/robot/subsystems/shooter/Turret.java b/src/main/java/frc/robot/subsystems/shooter/Turret.java index 6d21661f..b20dcee4 100644 --- a/src/main/java/frc/robot/subsystems/shooter/Turret.java +++ b/src/main/java/frc/robot/subsystems/shooter/Turret.java @@ -6,7 +6,6 @@ import com.ctre.phoenix6.hardware.TalonFX; import com.ctre.phoenix6.signals.NeutralModeValue; -import edu.wpi.first.networktables.DoubleSubscriber; import edu.wpi.first.units.measure.Angle; import edu.wpi.first.units.measure.AngularVelocity; import edu.wpi.first.wpilibj.DutyCycleEncoder; @@ -27,43 +26,60 @@ import frc.util.WaltLogger.IntLogger; import frc.util.WaltLogger.Pose3dLogger; +/* + * Turret - TalonFX position control with dual-encoder absolute position recovery. + * Two encoders with incommensurate gear ratios (10:1 and 19:1) let us uniquely resolve the turret + * angle without homing to a hardstop. calcTurretAngleLCM does the math. + * + * Vocab: + * LCM - the encoder fusion algorithm. searches for the turret angle that satisfies both encoder readings. + * snapback - turret wraps +-1 rotation to avoid crossing hardstops; isSnappingBack flags this in-progress + * locked - turret holds a fixed angle (used during intake to keep balls from getting stuck) + */ public class Turret extends SubsystemBase { + // ---- CONSTANTS ---- private static final double kTurretMaxRotsFromHomeDeg = kTurretMaxRotsFromHome.in(Degrees); - private boolean m_holdTurretAtIntakePos = false; - private boolean m_turretLocked = false; - private double m_turretLockAngleRots = 0.0; + // ---- MOTOR + CONTROLS ---- private final TalonFX m_turret = new TalonFX(kTurretCANID, Constants.kCanivoreBus); // X44Foc private final PositionVoltage m_PVRequest = new PositionVoltage(0).withEnableFOC(true); - // ---LOGIC BOOLEANS - private boolean m_isTurretHomed = true; - public BooleanSupplier turretHomedSupp = () -> m_isTurretHomed; - private boolean m_turretAtPos = false; - public BooleanSupplier turretAtPosSupp = () -> m_turretAtPos; - private boolean m_isSnappingBack = false; - private final BooleanSupplier supp_isSnappingBack = () -> m_isSnappingBack; - + // ---- ENCODERS ---- private final CANcoder m_lcmEncA = new CANcoder(19, Constants.kCanivoreBus); private final DutyCycleEncoder m_lcmEncB = new DutyCycleEncoder(3); + // ---- SIGNALS ---- + private final StatusSignal sig_turretCLErr = m_turret.getClosedLoopError(); + private final StatusSignal sig_turretPos = m_turret.getPosition(); + private final StatusSignal sig_lcmEncAAbsPos = m_lcmEncA.getAbsolutePosition(); + + // ---- LOGGERS ---- private final DoubleLogger log_lcmEncAPos = WaltLogger.logDouble(kLogTab, "EncA/Pos"); private final DoubleLogger log_lcmEncBPos = WaltLogger.logDouble(kLogTab, "EncB/Pos"); private final IntLogger log_lcmEncBFreq = WaltLogger.logInt(kLogTab, "EncB/Freq"); private final BooleanLogger log_lcmEncBConn = WaltLogger.logBoolean(kLogTab, "EncB/Conn"); - private final DoubleLogger log_turretControlPos = WaltLogger.logDouble(kLogTab, "turretControlPos"); private final DoubleLogger log_turretLCMPos = WaltLogger.logDouble(kLogTab, "turretLCMPos"); private final DoubleLogger log_turretClosedLoopError = WaltLogger.logDouble(kLogTab, "turretCLE"); private final BooleanLogger log_atPos = WaltLogger.logBoolean(kLogTab, "atPos"); private final BooleanLogger log_turretLocked = WaltLogger.logBoolean(kLogTab, "turretLocked"); private final Pose3dLogger log_turretTransform = WaltLogger.logPose3d(kLogTab, "turretTransform"); - private final BooleanLogger log_isTurretSnappingBack = WaltLogger.logBoolean(kLogTab, "snappingBack"); - private final StatusSignal sig_turretCLErr = m_turret.getClosedLoopError(); - private final StatusSignal sig_turretPos = m_turret.getPosition(); - private final StatusSignal sig_lcmEncAAbsPos = m_lcmEncA.getAbsolutePosition(); + // ---- STATE ---- + private boolean m_holdTurretAtIntakePos = false; + private boolean m_turretLocked = false; + private double m_turretLockAngleRots = 0.0; + private boolean m_isTurretHomed = true; + public BooleanSupplier turretHomedSupp = () -> m_isTurretHomed; + private boolean m_turretAtPos = false; + public BooleanSupplier turretAtPosSupp = () -> m_turretAtPos; + private boolean m_isSnappingBack = false; + private final BooleanSupplier supp_isSnappingBack = () -> m_isSnappingBack; + + // ============================================================= + // CONSTRUCTOR + // ============================================================= public Turret() { m_turret.getConfigurator().apply(kTurretTalonFXConfiguration); @@ -79,6 +95,11 @@ public Turret() { homeTurret(true); } + // ============================================================= + // POSITION CONTROL + // ============================================================= + + // seeds the TalonFX encoder using the LCM fusion result (or a fixed init position as fallback) public void homeTurret(boolean useLCM) { if (useLCM) { double lcmRots = calcTurretAngleLCM(sig_lcmEncAAbsPos.getValueAsDouble() * 360, -(m_lcmEncB.get() - kEncBOffset) * 360) / 360.0; @@ -88,26 +109,32 @@ public void homeTurret(boolean useLCM) { } } - private void refreshTurretCLErr() { - log_turretClosedLoopError.accept(sig_turretCLErr.getValueAsDouble()); - m_turretAtPos = sig_turretCLErr.isNear(0, kTurretMaxErrD); - m_isSnappingBack = sig_turretCLErr.getValueAsDouble() >= kTurretMaxErrDSpin; - log_atPos.accept(m_turretAtPos); - log_isTurretSnappingBack.accept(m_isSnappingBack); + public Command setTurretPosCmd(Angle rots) { + return runOnce(() -> setTurretPos(rots)); } - public BooleanSupplier isSnappingBack() { - return supp_isSnappingBack; + public void setTurretPos(Angle rots) { + setTurretPos(rots, RotationsPerSecond.zero()); } - public boolean atPosition() { - return m_turretAtPos; + public void setTurretPos(Angle rots, AngularVelocity velocityFF) { + m_turret.setControl(m_PVRequest.withPosition(rots).withVelocity(velocityFF)); + log_turretControlPos.accept(rots.in(Rotations)); } - public void setIntaking(boolean intaking) { - m_holdTurretAtIntakePos = intaking; + public void setTurretPos(double rots, double velocityFF) { + m_turret.setControl(m_PVRequest.withPosition(rots).withVelocity(velocityFF)); + log_turretControlPos.accept(rots); } + public void setTurretNeutralMode(NeutralModeValue value) { + m_turret.setNeutralMode(value); + } + + // ============================================================= + // LOCKING + INTAKE + // ============================================================= + public void setTurretLock(boolean locked) { m_turretLocked = locked; if (m_turretLocked) { @@ -117,6 +144,7 @@ public void setTurretLock(boolean locked) { log_turretLocked.accept(m_turretLocked); } + // locks at a specific angle instead of snapshotting current position public void lockAndSetTurretLockPos(double lockPos) { m_turretLocked = true; m_turretLockAngleRots = lockPos; @@ -128,26 +156,28 @@ public Command setTurretLockCmd(boolean locked) { return runOnce(() -> setTurretLock(locked)); } - public Command setTurretPosCmd(Angle rots) { - return runOnce(() -> setTurretPos(rots)); + public void setIntaking(boolean intaking) { + m_holdTurretAtIntakePos = intaking; } - public void setTurretPos(Angle rots) { - setTurretPos(rots, RotationsPerSecond.zero()); - } + // ============================================================= + // STATE + GETTERS + // ============================================================= - public void setTurretPos(Angle rots, AngularVelocity velocityFF) { - m_turret.setControl(m_PVRequest.withPosition(rots).withVelocity(velocityFF)); - log_turretControlPos.accept(rots.in(Rotations)); + private void refreshTurretCLErr() { + log_turretClosedLoopError.accept(sig_turretCLErr.getValueAsDouble()); + m_turretAtPos = sig_turretCLErr.isNear(0, kTurretMaxErrD); + m_isSnappingBack = sig_turretCLErr.getValueAsDouble() >= kTurretMaxErrDSpin; + log_atPos.accept(m_turretAtPos); + log_isTurretSnappingBack.accept(m_isSnappingBack); } - public void setTurretPos(double rots, double velocityFF) { - m_turret.setControl(m_PVRequest.withPosition(rots).withVelocity(velocityFF)); - log_turretControlPos.accept(rots); + public BooleanSupplier isSnappingBack() { + return supp_isSnappingBack; } - public void setTurretNeutralMode(NeutralModeValue value) { - m_turret.setNeutralMode(value); + public boolean atPosition() { + return m_turretAtPos; } public double getCurrTurretPos() { @@ -170,6 +200,10 @@ public boolean isTurretHomed() { return m_isTurretHomed; } + // ============================================================= + // PERIODIC + // ============================================================= + public void periodic() { double encAVal = sig_lcmEncAAbsPos.getValueAsDouble(); double encBVal = m_lcmEncB.get(); @@ -184,6 +218,13 @@ public void periodic() { log_turretLCMPos.accept(turretAngleDeg / 360.0); } + // ============================================================= + // LCM ALGORITHM + // ============================================================= + + // finds the turret angle (in degrees) that satisfies both encoder readings. + // searches over all integer multiples of encA's period within the turret's physical range, + // and picks the candidate with the smallest residual against encB. public static double calcTurretAngleLCM(double e1, double e2) { double encARatio = kGearZeroToothCount / kGearOneToothCount; // 100/10.0 double encBRatio = kGearZeroToothCount / kGearTwoToothCount; // 100/19 @@ -192,7 +233,7 @@ public static double calcTurretAngleLCM(double e1, double e2) { double bestAngle = 0; double bestError = Double.MAX_VALUE; - // Center the search on the expected LCM output at home, spanning ±turret range + // Center the search on the expected LCM output at home, spanning ± turret range double centerDeg = kLCMAtHomeRots * 360.0; double rangeDeg = kTurretMaxRotsFromHomeDeg; int kMin = (int) Math.floor((centerDeg - rangeDeg - e1 / encARatio) / encAPeriod); From 86217538b3148acc8377ea6b156762fa5947eabc Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Thu, 21 May 2026 21:00:11 -0400 Subject: [PATCH 22/25] OH MY GOD ROBOT IS DONE --- src/main/java/frc/robot/Robot.java | 106 ++++++++++++++++------------- 1 file changed, 60 insertions(+), 46 deletions(-) diff --git a/src/main/java/frc/robot/Robot.java b/src/main/java/frc/robot/Robot.java index bbd33e4f..8a962434 100644 --- a/src/main/java/frc/robot/Robot.java +++ b/src/main/java/frc/robot/Robot.java @@ -59,28 +59,29 @@ import frc.util.WaltLogger.DoubleLogger; import frc.util.WaltLogger.Pose2dLogger; +/* + * Robot - top-level TimedRobot. wires up all subsystems, controllers, triggers, and auton. + * All button binds live in configureBindings(). Sim wiring is in simulationPeriodic(). + * + * Startup profiling: kFieldInitStart captures a timestamp before field initializers run so + * the constructor can print how long subsystem/HW construction took. + */ public class Robot extends TimedRobot { - // Captured before any field initializers run (subsystem construction, etc.) + // ---- CONSTANTS ---- + // captured before any field initializers run (subsystem construction, etc.) private static final long kFieldInitStart = System.nanoTime(); - - /* CLASS VARIABLES */ - //---CONSTANTS private final LinearVelocity kMaxTranslationSpeed = TunerConstants.kSpeedAt12Volts; // kSpeedAt12Volts desired top speed private final AngularVelocity kMaxAngularRate = RotationsPerSecond.of(1.05); // 3/4 of a rotation per second max angular velocity - - // Pre-computed doubles for driveCommand hot path (avoids .times() measure allocations every tick) + // pre-computed doubles for driveCommand hot path (avoids .times() measure allocations every tick) private final double kMaxTranslationMps = kMaxTranslationSpeed.in(MetersPerSecond); private final double kMaxAngularRps = kMaxAngularRate.in(RadiansPerSecond); - private double m_visionSeenLastSec = Utils.getCurrentTimeSeconds(); - private final BooleanLogger log_visionSeenPastSecond = new BooleanLogger(kLogTab, "VisionSeenLastSec"); - - /* Setting up bindings for necessary control of the swerve drive platform */ + // ---- SWERVE REQUESTS ---- private final SwerveRequest.FieldCentric drive = new SwerveRequest.FieldCentric() .withDeadband(kMaxTranslationSpeed.times(0.1)).withRotationalDeadband(kMaxAngularRate.times(0.1)) // Add a 10% deadband .withDriveRequestType(DriveRequestType.Velocity); // Use open-loop control for drive motors - private final SwerveRequest.SwerveDriveBrake brake = new SwerveRequest.SwerveDriveBrake(); + // private final SwerveRequest.SwerveDriveBrake brake = new SwerveRequest.SwerveDriveBrake(); // private final SwerveRequest.PointWheelsAt point = new SwerveRequest.PointWheelsAt(); // private final SwerveRequest.RobotCentric tuneDrive = new SwerveRequest.RobotCentric() @@ -89,22 +90,20 @@ public class Robot extends TimedRobot { // private final Telemetry logger = new Telemetry(kMaxTranslationSpeed.in(MetersPerSecond)); + // ---- RATE LIMITERS ---- private final SlewRateLimiter limit_driverX = new SlewRateLimiter(0.30); private final SlewRateLimiter limit_driverY = new SlewRateLimiter(0.30); private final SlewRateLimiter limit_driverYawRate = new SlewRateLimiter(0.30); - //---CONTROLLERS + // ---- CONTROLLERS ---- private final CommandXboxController m_driver = new CommandXboxController(0); private final CommandXboxController m_manipulator = new CommandXboxController(1); - // Cached so the drive default-command lambda doesn't allocate a Trigger every tick. - private final Trigger trg_driverSlow = m_driver.leftTrigger(); - - //---INIT SUBSYSTEMS + // ---- SUBSYSTEMS ---- public final Swerve m_drivetrain = TunerConstants.createDrivetrain(); private final Shooter m_shooter = new Shooter( - () -> m_drivetrain.getState().Pose, + () -> m_drivetrain.getState().Pose, () -> m_drivetrain.getStateCopy(), () -> m_drivetrain.getChassisSpeeds()); @@ -114,57 +113,54 @@ public class Robot extends TimedRobot { // private final WaltVisualSim m_visualSim; private final Superstructure m_superstructure = new Superstructure(m_intake, m_indexer, m_shooter); - //---AUTONS + // ---- AUTONS ---- private final AutoFactory m_autoFactory = m_drivetrain.createAutoFactory(); private final WaltAdaptableAutonFactory m_adpatableAutonFactory = new WaltAdaptableAutonFactory(m_superstructure, m_autoFactory, m_intake, m_shooter, m_drivetrain); - //---VISION + // ---- HARDWARE ---- private PowerDistribution m_PDH = new PowerDistribution(); // private final NetworkPinger m_radioPinger = new NetworkPinger("Radio", "10.29.74.1", 0.2, 10); // private final NetworkPinger m_coprocessorPinger = new NetworkPinger("Coprocessor", "10.29.74.11", 0.2, 10); // private final VisionSim m_visionSim = new VisionSim(); - /* TRIGGERS */ + // ---- TRIGGERS ---- // private Trigger trg_optimalPrefireTime = new Trigger(HubShiftUtil.optimalPrefireTime()); // private Trigger trg_comebackTime = new Trigger(HubShiftUtil.comebackTime()); private final Trigger trg_snappingBack = new Trigger(m_shooter.m_turret.isSnappingBack()); private final Trigger trg_driverOverride = m_driver.b(); private final Trigger trg_manipOverride = m_manipulator.b(); - //---DRIVER BUTTONS + // driver private final Trigger trg_shoot = m_driver.rightTrigger().and(trg_driverOverride.negate()); private final Trigger trg_emergencyBarf = m_driver.rightTrigger().and(trg_driverOverride); private final Trigger trg_unjam = m_driver.rightBumper(); private final Trigger trg_resetPoseLeft = m_driver.leftBumper().and(trg_driverOverride.and(m_driver.povLeft())); private final Trigger trg_resetPoseRight = m_driver.leftBumper().and(trg_driverOverride.and(m_driver.povRight())); - private final Trigger trg_lockShooting = m_driver.povRight(); private final Trigger trg_unlockShooting = m_driver.povDown(); + private final Trigger trg_driverSlow = m_driver.leftTrigger(); - //---MANIPULATOR BUTTONS + // manipulator private final Trigger trg_intake = m_manipulator.rightTrigger().and(trg_manipOverride.negate()); private final Trigger trg_retractIntake = m_manipulator.rightBumper().and(trg_manipOverride.negate()); private final Trigger trg_intakeShimmy = m_manipulator.leftBumper(); - private final Trigger trg_emergencyIntakeOnlyBarf = m_manipulator.rightTrigger().and(trg_manipOverride); - - private final Trigger trg_homeIntake = m_manipulator.x().and(trg_manipOverride); + private final Trigger trg_homeIntake = m_manipulator.x().and(trg_manipOverride); private final Trigger trg_homeHood = m_manipulator.start().and(trg_manipOverride); private final Trigger trg_reseedTurret = m_manipulator.y().and(trg_manipOverride); - //---MISC TRGs + // mode private final Trigger trg_limitFPS = RobotModeTriggers.disabled(); private final Trigger trg_unlimitFps = RobotModeTriggers.autonomous().or(RobotModeTriggers.teleop()); - - + // ---- LOGGERS ---- + private final BooleanLogger log_visionSeenPastSecond = new BooleanLogger(kLogTab, "VisionSeenLastSec"); private final DoubleLogger log_miniPCCurrent = WaltLogger.logDouble(kLogTab, "MiniPC current"); private final DoubleLogger log_rioBusVoltage = WaltLogger.logDouble(kLogTab, "RioBusVoltage"); private final BooleanLogger log_rioBrownout = WaltLogger.logBoolean(kLogTab, "RioBrownout"); private final DoubleLogger log_pdhCurrentTotal = WaltLogger.logDouble(kLogTab, "PDHCurrTotal"); private final BooleanLogger log_isDSAttatched = WaltLogger.logBoolean(kLogTab, "isDSAttatched"); private final Pose2dLogger log_robotPose = WaltLogger.logPose2d("Drive", "Pose", true); - private final DoubleLogger log_autonTime = WaltLogger.logDouble("Auton", "autonTime"); //NOT DISPLAYING @@ -177,11 +173,19 @@ public class Robot extends TimedRobot { // private final DoubleLogger log_remainingFudgedTime = WaltLogger.logDouble("Util/Shift", "currentFudgedTime"); // private final BooleanLogger log_isActiveFudged = WaltLogger.logBoolean("Util/Shift", "isActiveFudged"); + // ---- RUNTIME STATE ---- + private double m_visionSeenLastSec = Utils.getCurrentTimeSeconds(); private final Tracer m_periodicTracer = new Tracer(); private final PerformanceMonitor m_perfMonitor = new PerformanceMonitor(false); - private final Command m_preheaterCommand; + private final Command m_preheaterCommand; // assigned in constructor after AutonChooser is ready + private final Timer m_fpsLimitTimer = new Timer(); + private final Timer lastGotTagMsmtTimer = new Timer(); + private final Timer m_disableChangeDelayTimer = new Timer(); + + // ============================================================= + // CONSTRUCTOR + // ============================================================= - /* CONSTRUCTOR */ public Robot() { long t0 = System.nanoTime(); long tPrev = t0; @@ -232,8 +236,6 @@ public Robot() { PhotonCamera.setVersionCheckEnabled(false); LiveWindow.disableAllTelemetry(); - - // MANUAL HOMING IS BEING USED // addPeriodic(m_shooter::fastPeriodic, 0.0025); @@ -252,9 +254,12 @@ public Robot() { System.out.printf("[INIT PROFILE] CONSTRUCTOR TOTAL: %7.1f ms%n", (tEnd - t0) * 1e-6); } - /* COMMANDS */ + // ============================================================= + // DRIVE COMMAND + // ============================================================= + /** - * + * * @param speedMult how much you want to limit speed as a decimal percentage of kMaxTranslation. 1 does nothing * @return swerve drive command */ @@ -288,7 +293,10 @@ private Command driveCommand(double speedMult, double rotationMult) { // m_manipulator.setRumble(type, intensity); // } - //---BINDINGS + // ============================================================= + // BINDINGS + // ============================================================= + private void configureBindings() { /* SET UP */ m_drivetrain.setDefaultCommand(driveCommand(RobotK.kRobotSpeedIntakingLimit, RobotK.kRobotEvasionLimit)); @@ -299,7 +307,7 @@ private void configureBindings() { m_drivetrain.applyRequest(() -> idle).ignoringDisable(true) ); - trg_limitFPS.onTrue(WaltCamera.setFpsLimitCmd(true)); + trg_limitFPS.onTrue(WaltCamera.setFpsLimitCmd(true)); trg_unlimitFps.onTrue(WaltCamera.setFpsLimitCmd(false)); /* BUTTON BIDNDS */ @@ -354,7 +362,7 @@ private void configureBindings() { // () -> // HubShiftUtil.getShiftedShiftInfo().active() // || m_shooter.getCurrentGoal().equals(ShooterGoal.PASSING)); - + // trg_optimalPrefireTime.whileTrue( // Commands.run(() -> setBothRumble(RumbleType.kBothRumble, 0.5)).finallyDo(() -> setBothRumble(RumbleType.kBothRumble, 0)) // ); @@ -364,7 +372,7 @@ private void configureBindings() { // ); // m_drivetrain.registerTelemetry(logger::telemeterize); //UNUSED - runs at 250hz which is burning CPU - + //-used when the shooter couldn't shoot while aiming close to the hopper wall // trg_turretInShootRange.whileFalse(Commands.run(() -> m_driver.setRumble(RumbleType.kBothRumble, 0.3)) // .finallyDo(() -> m_driver.setRumble(RumbleType.kBothRumble, 0)) @@ -388,7 +396,10 @@ private void configureTestBindings() { // ); } - /* PERIODICS */ + // ============================================================= + // ROBOT PERIODIC + // ============================================================= + @Override public void robotPeriodic() { m_perfMonitor.loopStart(); @@ -456,14 +467,13 @@ public void robotPeriodic() { // ) // ); - // m_periodicTracer.printEpochs(); m_perfMonitor.loopEnd(); } - private final Timer m_fpsLimitTimer = new Timer(); - private final Timer lastGotTagMsmtTimer = new Timer(); - private final Timer m_disableChangeDelayTimer = new Timer(); + // ============================================================= + // MODE LIFECYCLE + // ============================================================= @Override public void disabledInit() { @@ -524,7 +534,7 @@ public void teleopExit() {} @Override public void testInit() { CommandScheduler.getInstance().cancelAll(); - + CommandScheduler.getInstance().schedule( Commands.sequence( m_drivetrain.runOnce(m_drivetrain::seedFieldCentric), @@ -568,6 +578,10 @@ public void testPeriodic() {} @Override public void testExit() {} + // ============================================================= + // SIMULATION + // ============================================================= + @Override public void simulationInit() { // FuelSim.getInstance().start(); From 7618a76e2cb6a045bdf41049134f8b11bb8ce521 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Thu, 28 May 2026 23:27:33 -0400 Subject: [PATCH 23/25] i think thats everything? --- src/main/java/frc/robot/Constants.java | 595 +++++++++++------- .../java/frc/robot/subsystems/Indexer.java | 244 ++++--- .../java/frc/robot/subsystems/Intake.java | 233 +++++-- .../frc/robot/subsystems/Superstructure.java | 43 +- .../java/frc/robot/subsystems/Swerve.java | 491 +++++++-------- .../frc/robot/subsystems/shooter/Shooter.java | 2 +- 6 files changed, 932 insertions(+), 676 deletions(-) diff --git a/src/main/java/frc/robot/Constants.java b/src/main/java/frc/robot/Constants.java index cacf6149..4e885838 100644 --- a/src/main/java/frc/robot/Constants.java +++ b/src/main/java/frc/robot/Constants.java @@ -57,16 +57,61 @@ import frc.util.AllianceFlipUtil; import frc.util.VisionUtil; +/* + * Constants + * + * This file is the single source of truth for every tunable value, hardware ID, + * and configuration object in the robot code. Keeping everything here makes it + * easy to update a value in one place without hunting through multiple files. + * + * It's organized into nested static classes, one per subsystem or topic: + * MotorK — motor physical constants (free speeds, used for RPS calculations) + * WpiK — WPILib utility constants + * ShooterK — flywheel, turret, and hood settings + TalonFX configs + * VisionK — camera positions relative to the robot + * FieldK — field dimensions and April Tag layout + * RobotK — robot physical dimensions and misc settings + * SuperstructureK — log tab name for Superstructure + * IntakeK — intake arm + roller settings + TalonFX configs + * IndexerK — spindexer + tunnel settings, speed ratios, TalonFX configs + * TurretK — turret encoder offsets and gear tooth counts + * AutonK — auton timeouts and Choreo trajectory file names + * + * Quick note on CTRE motor configs: + * Each motor has a TalonFXConfiguration object built up from sub-configs. + * The PID gains (Slot0Configs) use this terminology: + * kS — static friction compensation (minimum voltage to move the motor at all) + * kV — velocity feedforward (voltage per RPS) + * kA — acceleration feedforward (voltage per RPS/s) + * kP — proportional gain (more error → more correction) + * kI — integral gain (corrects sustained steady-state error over time) + * kD — derivative gain (dampens oscillation) + * SensorToMechanismRatio in FeedbackConfigs accounts for gear reduction so the + * motor controller reports mechanism position/speed directly, not motor shaft position. + * + * CAN bus note: + * kRioBus — default RoboRIO CAN bus (lower bandwidth, used for intake) + * kCanivoreBus — high-speed CANivore bus ("fd"), used for indexer + drivetrain + * kShooterBus — dedicated CAN bus for the shooter subsystem ("shooter") + */ public class Constants { + // Global feature flags — toggle these without touching subsystem code. public static final boolean kDebugLoggingEnabled = false; public static final boolean kDataLoggingEnabled = true; public static final boolean kUsePoseCorrection = false; public static final double kSimPeriodicUpdateInterval = 0.020; + // CAN bus identifiers. Motors are assigned to specific buses based on bandwidth needs. public static final CANBus kRioBus = CANBus.roboRIO(); public static final CANBus kCanivoreBus = new CANBus("fd"); public static final CANBus kShooterBus = new CANBus("shooter"); + + // ============================================================= + // MOTOR CONSTANTS + // Free speed is the no-load max speed of each motor variant. + // These are used to compute max RPS for velocity scaling. + // ============================================================= public static final class MotorK { public static final double kX60MaxRadPerSec = DCMotor.getKrakenX60(1).freeSpeedRadPerSec; public static final AngularVelocity kX60MaxVelocity = RadiansPerSecond.of(kX60MaxRadPerSec); @@ -78,30 +123,47 @@ public static final class MotorK { public static final double kX44FOCMaxRadPerSec = DCMotor.getKrakenX44Foc(1).freeSpeedRadPerSec; public static final AngularVelocity kX44FOCMaxVelocity = RadiansPerSecond.of(kX44FOCMaxRadPerSec); } + + + // ============================================================= + // WPILib UTILITY CONSTANTS + // ============================================================= public static class WpiK { public static final ChassisSpeeds kZeroChassisSpeeds = new ChassisSpeeds(0, 0, 0); } + + + // ============================================================= + // SHOOTER CONSTANTS + // Flywheel, turret, and hood settings. + // ============================================================= public static class ShooterK { public static final String kLogTab = "Shooter"; - public static final Rotation2d kTurretAngleOffset = Rotation2d.fromRotations(0.106 + 0.0067); //4.87 //0.132324 // was 0.12, decreased 0.014 (~5deg) to fix consistent rightward aim error + + // ---- turret geometry ---- + // The turret is offset from the robot's center. These constants describe + // where it is and how it's rotated relative to the robot frame. + // kTurretAngleOffset corrects for the turret's mechanical zero not being + // aligned with the robot's forward direction. + public static final Rotation2d kTurretAngleOffset = Rotation2d.fromRotations(0.106 + 0.0067); public static final Rotation3d kTurretAngleOffset3d = new Rotation3d(kTurretAngleOffset); public static final Translation3d kTurretTranslation = new Translation3d(Inches.of(-4.744), Inches.of(-4.239), Inches.of(15.769)); public static final Transform3d kTurretTransformNoRotation = new Transform3d(kTurretTranslation, Rotation3d.kZero); public static final Transform3d kTurretTransform = new Transform3d(kTurretTranslation, kTurretAngleOffset3d); - public static final Distance kInchesAboveFunnel = Inches.of(20);// distance the ball must travel above the funnel opening to arc correctly into the hub + + // How far above the funnel opening the ball must travel to arc correctly into the hub. + public static final Distance kInchesAboveFunnel = Inches.of(20); + + // Turret position used when "barfing" (shooting at low speed to clear jams). public static final Angle kTurretBarfPos = Rotations.of(-0.113); public static final boolean kUseStaticShot = false; public static final boolean kAllowDriverRPSTweak = false; - // private static final Pose3dLogger log_turretTransform = WaltLogger.logPose3d(kLogTab, "TurretTransformRaw"); - // static { - // log_turretTransform.accept(kTurretTransform); - // } - public static final Distance kFlywheelRadius = Inches.of(1.5); - // Precomputed doubles for hot-path shot calc (avoid measure allocations) + // Pre-computed doubles for the shot calculator hot path. + // Using doubles directly avoids creating Measure objects on every loop iteration. public static final double kTurretOffsetX_m = kTurretTransform.getTranslation().getX(); public static final double kTurretOffsetY_m = kTurretTransform.getTranslation().getY(); public static final double kTurretAngleOffsetRad = kTurretAngleOffset.getRadians(); @@ -111,69 +173,72 @@ public static class ShooterK { public static final double kFunnelRadiusIn = FieldConstants.Hub.funnelRadius.in(Inches); public static final double kFunnelHeightPlusAboveIn = FieldConstants.Hub.funnelHeight.plus(kInchesAboveFunnel).in(Inches); - // Lateral bias compensation: balls drift left/right as a function of turret angle relative to robot. + // Lateral bias compensation: balls drift left/right as a function of turret angle. // sin(turretRelAngle) = 0 at 0/180°, +1 at 90° (left bias), -1 at 270° (right bias). - // This gain (in rotations) is subtracted * sin to counter the bias. Tune on robot. - public static final double kTurretLateralBiasGainRots = 0;//-0.005 + // This gain (in rotations) is subtracted * sin to counter the drift. Tune on robot. + public static final double kTurretLateralBiasGainRots = 0; public static final int kHopperCapacity = 55; //TODO: find true max public static final double kGravity = MetersPerSecondPerSecond.of(9.81).in(InchesPerSecondPerSecond); - //TODO: work out where our passing spots should be..? - // public static final Translation3d kPassingSpotRight = new Translation3d( - // Meters.of(2), Meters.of(1.5), Meters.zero()); - // public static final Translation3d kPassingSpotLeft = new Translation3d( - // Meters.of(2), Meters.of(6.5), Meters.zero()); + // Passing zone: the X coordinate beyond which the robot can pass to a partner. public static final Distance kPassingX = Meters.of(3.5); public static final double kPassingXAsDouble = kPassingX.in(Meters); + // No-pass zone: a region around the hub where passing is blocked. public static final double kNoPassZoneTopX = FieldConstants.Hub.blueInnerCenterPoint.getX() + 2; public static final double kNoPassZoneRightY = Meters.of(3.2).baseUnitMagnitude(); public static final double kNoPassZoneLeftY = FieldConstants.fieldWidth - 3.2; - + public static final double kShooterTimeout = 1.0; - public static final double kBallDetectedDebounceTime = 1.2; //0.9; + // How long ball detection must be triggered before we count it as a real shot (debounce). + public static final double kBallDetectedDebounceTime = 1.2; - /* MOTOR CONSTANTS */ - public static final double kShooterMoI = 0.000349 * 2.5; //J for 5 3" 0.53lb flywheels + // ---- motor constants ---- + public static final double kShooterMoI = 0.000349 * 2.5; // J for 5x 3" 0.53 lb flywheels public static final double kTurretMoI = 0.104506595; - public static final double kShooterGearing = 1/1; - public static final double kTurretGearing = 41.66666666/1; - public static final double kHoodGearing = 25.0/1; + public static final double kShooterGearing = 1.0 / 1; + public static final double kTurretGearing = 41.66666666 / 1; + public static final double kHoodGearing = 25.0 / 1; public static final int kPeakShooterVolts = 16; - public static final Angle kTurretMaxRotsFromHome = Rotations.of(0.55); //0.75 rots in each direction from home + // ---- turret limits ---- + // The turret can rotate ±0.55 rotations from its home position. + // Software limits prevent it from wrapping the wiring. + public static final Angle kTurretMaxRotsFromHome = Rotations.of(0.55); public static final Angle kTurretMinRots = Rotations.of(-kTurretMaxRotsFromHome.in(Rotations)); public static final Angle kTurretMaxRots = Rotations.of(kTurretMaxRotsFromHome.in(Rotations)); public static final double kTurretMaxErrD = Rotations.of(0.05).in(Rotations); public static final double kTurretMaxErrDSpin = Rotations.of(0.4).in(Rotations); - public static final double kTurretMaxNotAbleToPassRange = 0.23; //sorry for this horrible name i dont know a better one lol - public static final double kTurretMinNotAbleToPassRange = -0.23; //sorry for this horrible name i dont know a better one lol + // Range where the turret can't pass through (cable wrap / mechanical blockage). + public static final double kTurretMaxNotAbleToPassRange = 0.23; + public static final double kTurretMinNotAbleToPassRange = -0.23; + // ---- shooter RPS targets ---- public static final AngularVelocity kShooterMaxRPS = MotorK.kX44MaxVelocity.div(kShooterGearing); public static final double kShooterMaxRPSd = kShooterMaxRPS.in(RotationsPerSecond); - public static final AngularVelocity kShooterRPS = kShooterMaxRPS.times(0.65); //Kraken X44 Max RPM: 7758 + public static final AngularVelocity kShooterRPS = kShooterMaxRPS.times(0.65); public static final double kShooterRPSd = 42.90 + 1.25; - public static final AngularVelocity kShooterAutonCloseRPS = kShooterMaxRPS.times(0.60); //auton pose is closer to the hub than teleop scoring + public static final AngularVelocity kShooterAutonCloseRPS = kShooterMaxRPS.times(0.60); // auton is closer to hub public static final AngularVelocity kShooterAuton_EndSweep_RPS = kShooterMaxRPS.times(0.70); // end of sweep paths public static final AngularVelocity kShooterBarfRPS = kShooterMaxRPS.times(0.37); public static final AngularVelocity kShooterZeroRPS = RotationsPerSecond.zero(); public static final AngularVelocity kShooterSpunUpMinimum = RotationsPerSecond.of(10); - public static final Time kShooterSpunUpTimeout = Seconds.of(0.64); //double expected spinup time + public static final Time kShooterSpunUpTimeout = Seconds.of(0.64); // 2x expected spinup time as a safety margin public static final double kShooterSpunUpMinimumD = kShooterSpunUpMinimum.in(RotationsPerSecond); - public static final double kDriverRPSIncreaseD = 2.0; //biggest cope of the century + public static final double kDriverRPSIncreaseD = 2.0; - //---HOOD CONSTANTS + // ---- hood constants ---- public static final double kHoodMoI = 0.00027505; - public static final Angle kHoodAbsoluteMinRots = Rotations.of(0.0); //ABSOLUTE MIN - private static final Angle kHoodAbsoluteMaxRots = Rotations.of(1.174805); //ABSOLUTE MAX + public static final Angle kHoodAbsoluteMinRots = Rotations.of(0.0); + private static final Angle kHoodAbsoluteMaxRots = Rotations.of(1.174805); public static final Angle kHoodMaxDegs = Degrees.of(kHoodAbsoluteMaxRots.in(Degrees)); public static final Angle kHoodLockDegs = Degrees.of(kHoodMaxDegs.times(0.75).in(Degrees)); public static final double kHoodRotsd = 0.08; @@ -182,26 +247,26 @@ public static class ShooterK { public static final double kHoodMaxErrD = Rotations.of(0.01).in(Rotations); public static final double kHoodAtPosTimeout = 0.1; - //double versions + // double versions for use in hot-path calculations public static final double kHoodMinRots_double = 0.0; public static final double kHoodMaxRots_double = kHoodAbsoluteMaxRots.in(Rotations); public static final double kPhysicalHoodMinPosition_double = 0; public static final double kPhysicalHoodMaxPosition_double = 48; - //TODO: ensure this is the home value - // public static final Angle kHoodHomePosition = Degrees.of(10); public static final Angle kHoodTrenchPosition = Degrees.of(5); + // Custom DC motor model for the hood (NEO 550 with specific characteristics). public static final DCMotor khoodDCMotorGearbox = new DCMotor( - 6, - 0.047, - 2.5, - 0.2, - 24.0855, - 1 + 6, // nominal voltage + 0.047, // stall torque (N·m) + 2.5, // stall current (A) + 0.2, // free current (A) + 24.0855,// free speed (rad/s) + 1 // number of motors ); - /* HOMING */ + // ---- hood homing ---- + // The hood also uses current-sense homing (same principle as the intake arm). public static final Current kWireTugMinAmps = Amps.of(8); public static final double kWireTugMinSecs = 0.125; public static final double kHoodHomingVoltage = -0.75; @@ -209,21 +274,22 @@ public static class ShooterK { public static final Angle kHomePosition = Rotations.of(-0.2175); public static final Angle kInitPosition = Rotations.of(-0.145); - /* IDS */ + // ---- CAN IDs ---- public static final int kShooterA_CANID = 21; public static final int kShooterB_CANID = 20; public static final int kTurretCANID = 12; public static final int kHoodCANID = 22; - /* CONFIGS */ - // TODO: Check what more configs would be necessary - private static final Slot0Configs kShooterASlot0Configs = new Slot0Configs() //Note to self (hrehaan) (and saarth cuz i did the same thing): the default PID sets ZERO volts to a motor, which makes all sim effectively useless cuz the motor has ZERO supplyV - .withKS(0.37) - .withKV(0.1) + // ---- TalonFX configurations ---- + // Slot 0: main shooting PID (used during normal operation) + // Slot 1: alternate gains (used in specific scenarios, e.g. high-speed passing) + private static final Slot0Configs kShooterASlot0Configs = new Slot0Configs() + .withKS(0.37) // static friction: minimum voltage to overcome stiction + .withKV(0.1) // velocity FF: volts per RPS .withKA(0) - .withKP(0.5) + .withKP(0.5) // proportional: corrects speed error .withKI(0) - .withKD(0); // kP was causing the werid sinusoid behavior, kS and kA were adding inconsistency with the destination values + .withKD(0); private static final CurrentLimitsConfigs kShooterACurrentLimitConfigs = new CurrentLimitsConfigs() .withStatorCurrentLimit(80) .withSupplyCurrentLimit(50) @@ -252,15 +318,15 @@ public static class ShooterK { .withFeedback(kShooterAFeedbackConfigs) .withVoltage(kShooterAVoltageConfigs); - - + // Shooter B uses the same config as A but with a different inversion. private static final MotorOutputConfigs kShooterBOutputConfigs = new MotorOutputConfigs() .withInverted(InvertedValue.CounterClockwise_Positive) .withNeutralMode(NeutralModeValue.Coast); public static final TalonFXConfiguration kShooterBTalonFXConfiguration = kShooterATalonFXConfiguration.clone() .withMotorOutput(kShooterBOutputConfigs); - //---HOOD + // ---- hood TalonFXS configuration ---- + // The hood uses a TalonFXS (for NEO 550 compatibility) instead of a TalonFX. private static final Slot0Configs kHoodSlot0Configs = new Slot0Configs() .withKP(29) .withKI(0) @@ -268,7 +334,7 @@ public static class ShooterK { .withKS(0.5) .withKV(4) .withKA(0) - .withKG(0); + .withKG(0); // gravity FF (not needed here since hood axis is horizontal) private static final CurrentLimitsConfigs kHoodCurrentLimitConfig = new CurrentLimitsConfigs() .withStatorCurrentLimit(30) .withSupplyCurrentLimit(15) @@ -287,14 +353,17 @@ public static class ShooterK { .withMotorArrangement(MotorArrangementValue.NEO550_JST); private static final ExternalFeedbackConfigs kHoodFeedbackConfigs = new ExternalFeedbackConfigs() .withSensorToMechanismRatio(kHoodGearing); + + // Software limits prevent the hood from driving past its physical travel range. public static final SoftwareLimitSwitchConfigs kHoodSoftLimitConfigs = new SoftwareLimitSwitchConfigs() .withForwardSoftLimitThreshold(kHoodAbsoluteMaxRots.minus(Rotations.of(0.05))) .withReverseSoftLimitThreshold(kHoodAbsoluteMinRots.plus(Rotations.of(0.05))) .withForwardSoftLimitEnable(true) .withReverseSoftLimitEnable(true); + // Version with limits disabled — used during homing so the hood can reach its hard stop. public static final SoftwareLimitSwitchConfigs kHoodSoftLimitConfigsNoEnable = kHoodSoftLimitConfigs .withForwardSoftLimitEnable(false) - .withReverseSoftLimitEnable(false); + .withReverseSoftLimitEnable(false); public static final TalonFXSConfiguration kHoodTalonFXSConfiguration = new TalonFXSConfiguration() .withSlot0(kHoodSlot0Configs) .withCurrentLimits(kHoodCurrentLimitConfig) @@ -312,26 +381,26 @@ public static class ShooterK { .withCommutation(kHoodCommutationConfigs) .withSoftwareLimitSwitch(kHoodSoftLimitConfigsNoEnable); - //---TURRET + // ---- turret TalonFX configuration ---- private static final Slot0Configs kTurretSlot0Configs = new Slot0Configs() .withKS(0) .withKV(5) .withKA(0.02) - .withKP(300) //3 - testing values in Pheonix Tuner + .withKP(300) .withKI(0) - .withKD(5); // OLD: kP was too low making the slope less steep, kS kV and kA were causing rlly weird behavior (jumping up/down way further than targeted position) + .withKD(5); private static final CurrentLimitsConfigs kTurretCurrentLimitConfigs = new CurrentLimitsConfigs() .withStatorCurrentLimit(55) .withSupplyCurrentLimit(55) .withSupplyCurrentLowerLimit(15) - .withSupplyCurrentLowerTime(1.0) // drop to 15A after 1 second + .withSupplyCurrentLowerTime(1.0) // drop to 15A after 1 second of high current .withStatorCurrentLimitEnable(true) .withSupplyCurrentLimitEnable(true); private static final MotorOutputConfigs kTurretOutputConfigs = new MotorOutputConfigs() .withInverted(InvertedValue.CounterClockwise_Positive) .withNeutralMode(NeutralModeValue.Brake); private static final MotionMagicConfigs kTurretMotionMagicConfigs = new MotionMagicConfigs() - .withMotionMagicCruiseVelocity(110) //TODO: update MMV Configs + .withMotionMagicCruiseVelocity(110) //TODO: update cruise velocity after re-characterization .withMotionMagicAcceleration(20) .withMotionMagicJerk(0); private static final SoftwareLimitSwitchConfigs kTurretSoftwareLimitSwitchConfigs = new SoftwareLimitSwitchConfigs() @@ -353,12 +422,14 @@ public static class ShooterK { .withFeedback(kTurretFeedbackConfigs) .withVoltage(kTurretVoltageConfigs); + // Turret absolute encoder (CANcoder) configuration. public static final MagnetSensorConfigs kEncoderAMagnetSensorConfigs = new MagnetSensorConfigs() .withMagnetOffset(TurretK.kEncAMagnetOffset); public static final CANcoderConfiguration kEncoderAConfiguration = new CANcoderConfiguration() .withMagnetSensor(kEncoderAMagnetSensorConfigs); - //Left, Center (Climb), Center (Hub), Right - Driver POV + // Fixed shooter pose overrides — used when auton shooting from known positions. + // [Left, Center (Climb), Center (Hub), Right] from driver POV. public static final Pose2d kShooterOverridePose[] = { AllianceFlipUtil.apply(new Pose2d(FieldK.kFieldLengthMeters / 6, FieldK.kFieldWidthMeters * 2 / 3, new Rotation2d(0))), AllianceFlipUtil.apply(new Pose2d(Units.inchesToMeters(156.61 - 115.05 + 10), FieldK.kFieldWidthMeters / 2, new Rotation2d(0))), @@ -367,111 +438,105 @@ public static class ShooterK { }; } + + // ============================================================= + // VISION CONSTANTS + // Camera positions relative to the robot center (used by PhotonVision). + // Coordinates follow: ONSHAPE X = OUR Y, ONSHAPE Y = OUR X — don't mix these up. + // ============================================================= public static class VisionK { - // public static final Camera[] kCameras = new Camera[4]; - // private static final String kSimCameraSimVisualNames = /"VisionEstimation"; //suffixed to each camera name - // ONSHAPE X IS OUR Y -- ONSHAPE Y IS OUR X !!! NOTE THIS PLEASE DO NOT FORGET - public static final Transform3d kFrontLeftCTR = VisionUtil.transformToRobo(8.875, 12.18175, 20.45, 180, -20, 45); - public static final Transform3d kFrontRightCTR = VisionUtil.transformToRobo(8.875, -12.18175, 20.45, 180, -20, -45); - public static final Transform3d kBackLeftCTR = VisionUtil.transformToRobo(-11.375, 11.875, 20.5625, 0, -20, 135); - public static final Transform3d kBackRightCTR = VisionUtil.transformToRobo(-12.455, -12.055, 18.25, 180,-20, -135); - //Initialize cameras - // static { - // kCameras[0] = new Camera( - // new SimCameraProperties(), - // "FrontLeft", - // kSimCameraSimVisualNames, - // Camera.transformToRobo(10.413, 12.394, 28.844, 0, -10, 45) - // ); - // kCameras[0].setProps("ThriftyCam", 0, 0, 0, 0); - - // kCameras[1] = new Camera( - // new SimCameraProperties(), - // "FrontRight", - // kSimCameraSimVisualNames, - // Camera.transformToRobo(10.413, -12.394, 28.844, 0, -10, 315) - // ); - // kCameras[1].setProps("ThriftyCam", 0, 0, 0, 0); - - // kCameras[2] = new Camera( - // new SimCameraProperties(), - // "BackLeft", - // kSimCameraSimVisualNames, - // Camera.transformToRobo(-11.894, 12.394, 28.844, 0, -10, 135) - // ); - // kCameras[2].setProps("ThriftyCam", 0, 0, 0, 0); - - // kCameras[3] = new Camera( - // new SimCameraProperties(), - // "BackRight", - // kSimCameraSimVisualNames, - // Camera.transformToRobo(-11.894, -12.394, 28.844, 0, -10, 225) - // ); - // kCameras[3].setProps("ThriftyCam", 0, 0, 0, 0); - // } + public static final Transform3d kFrontLeftCTR = VisionUtil.transformToRobo(8.875, 12.18175, 20.45, 180, -20, 45); + public static final Transform3d kFrontRightCTR = VisionUtil.transformToRobo(8.875, -12.18175, 20.45, 180, -20, -45); + public static final Transform3d kBackLeftCTR = VisionUtil.transformToRobo(-11.375, 11.875, 20.5625, 0, -20, 135); + public static final Transform3d kBackRightCTR = VisionUtil.transformToRobo(-12.455, -12.055, 18.25, 180, -20, -135); } + + // ============================================================= + // FIELD CONSTANTS + // Field dimensions and the April Tag layout used for vision pose estimation. + // ============================================================= public static class FieldK { - // take with a grain of salt - pulled from field dimensions (welded) - public static final double kFieldLengthMeters = Units.inchesToMeters(651.22); + // Field dimensions pulled from the 2026 Rebuilt field spec (welded version). + public static final double kFieldLengthMeters = Units.inchesToMeters(651.22); public static final double kFieldWidthMeters = Units.inchesToMeters(317.69); - public static final Pose2d kLeftResetPose = new Pose2d(0.478, 8 - 0.392, Rotation2d.kZero); - public static final Pose2d kRightResetPose = new Pose2d(0.478, 0.392, Rotation2d.kZero); + // Reset poses for each alliance side (used when pressing the reset-pose button). + public static final Pose2d kLeftResetPose = new Pose2d(0.478, 8 - 0.392, Rotation2d.kZero); + public static final Pose2d kRightResetPose = new Pose2d(0.478, 0.392, Rotation2d.kZero); + // April Tag layout with trench tags excluded. We remove them because the trench + // area has poor camera coverage and including those tags hurts pose accuracy. public static final AprilTagFieldLayout kTagLayout; - - //Ignore trench April Tags static { - HashSet excludedAprilTagsID = new HashSet<> (Arrays.asList(1, 6, 7, 12, 17, 22, 23, 28)); + HashSet excludedAprilTagsID = new HashSet<>(Arrays.asList(1, 6, 7, 12, 17, 22, 23, 28)); AprilTagFieldLayout fieldLayout = AprilTagFieldLayout.loadField(AprilTagFields.k2026RebuiltWelded); - List tags = new ArrayList<> (fieldLayout.getTags()); + List tags = new ArrayList<>(fieldLayout.getTags()); tags.removeIf(tag -> excludedAprilTagsID.contains(tag.ID)); kTagLayout = new AprilTagFieldLayout(tags, fieldLayout.getFieldLength(), fieldLayout.getFieldWidth()); } } + + // ============================================================= + // ROBOT CONSTANTS + // Physical dimensions and misc settings. + // ============================================================= public static class RobotK { public static final String kLogTab = "Robot"; public static final int kMiniPCChannel = 14; - // real values - public static final Distance kRobotFullWidth = Inches.of(33.6875); + public static final Distance kRobotFullWidth = Inches.of(33.6875); public static final Distance kRobotFullLength = Inches.of(32.6875); - public static final Distance kBumperHeight = Inches.of(4.5); + public static final Distance kBumperHeight = Inches.of(4.5); + + // Max drive speed while actively intaking (keeps the ball from bouncing off). public static final double kRobotSpeedIntakingLimit = 0.31; + // Max drive speed while evading defense. public static final double kRobotEvasionLimit = 1.5; } + + // ============================================================= + // SUPERSTRUCTURE CONSTANTS + // ============================================================= public static class SuperstructureK { public static final String kLogTab = "Superstructure"; } + + // ============================================================= + // INTAKE CONSTANTS + // Arm and roller motor settings + TalonFX configurations. + // ============================================================= public static class IntakeK { public static final String kLogTab = "Intake"; - /* MOTOR CONSTANTS */ - public static final double kIntakeArmMOI = 0.0209; - public static final double kIntakeArmGearing = 125/1; + // ---- physical constants (used for simulation) ---- + public static final double kIntakeArmMOI = 0.0209; // moment of inertia (kg·m²) + public static final double kIntakeArmGearing = 125.0 / 1; // 125:1 reduction - public static final double kIntakeRollersMOI = 0.0001; // 0.00343880857 - public static final double kIntakeRollersGearing = 12.0/30; + public static final double kIntakeRollersMOI = 0.0001; + public static final double kIntakeRollersGearing = 12.0 / 30; // 0.4:1 (rollers spin faster than motor) - public static final AngularVelocity kIntakeRollersMaxRPS = MotorK.kX60FOCMaxVelocity.div(kIntakeRollersGearing); - public static final AngularVelocity kIntakeRollersShootRPS = kIntakeRollersMaxRPS.times(0.2); - public static final AngularVelocity kIntakeRollersShimmyRPS = kIntakeRollersMaxRPS.times(0.2); - public static final double kIntakeRollersBarfVolts = -12; + // ---- roller speed targets ---- + public static final AngularVelocity kIntakeRollersMaxRPS = MotorK.kX60FOCMaxVelocity.div(kIntakeRollersGearing); + public static final AngularVelocity kIntakeRollersShootRPS = kIntakeRollersMaxRPS.times(0.2); + public static final AngularVelocity kIntakeRollersShimmyRPS = kIntakeRollersMaxRPS.times(0.2); + public static final double kIntakeRollersBarfVolts = -12; public static final double kIntakeRollersIntakeVolts = 11; public static final double kIntakeRollersShimmyVolts = 5; - /* IDS */ - public static final int kIntakeArmCANID = 40; + // ---- CAN IDs ---- + public static final int kIntakeArmCANID = 40; public static final int kIntakeRollersA_CANID = 41; public static final int kIntakeRollersB_CANID = 42; - /* CONFIGS */ - // IntakeArm Motor + // ---- TalonFX configurations ---- + + // Intake arm: MotionMagic position control with soft current limiting. + // Low current limits because the arm is geared heavily and doesn't need much torque. private static final CurrentLimitsConfigs kIntakeArmCurrentLimitConfigs = new CurrentLimitsConfigs() .withStatorCurrentLimit(20) .withSupplyCurrentLimit(20) @@ -479,7 +544,7 @@ public static class IntakeK { .withStatorCurrentLimitEnable(true) .withSupplyCurrentLimitEnable(true); private static final Slot0Configs kIntakeArmSlot0Configs = new Slot0Configs() - .withKS(1.5) + .withKS(1.5) // high static friction — arm is heavy and geared .withKV(0) .withKA(0) .withKP(50) @@ -505,7 +570,8 @@ public static class IntakeK { .withVoltage(kIntakeArmVoltageConfigs) .withFeedback(kIntakeArmFeedbackConfigs); - // IntakeRollers Motors + // Intake rollers A: velocity control. Higher current limits since rollers + // need to grab and accelerate balls quickly. private static final CurrentLimitsConfigs kIntakeRollersACurrentLimitConfigs = new CurrentLimitsConfigs() .withStatorCurrentLimit(55) .withSupplyCurrentLimit(35) @@ -514,7 +580,7 @@ public static class IntakeK { .withSupplyCurrentLimitEnable(true); private static final Slot0Configs kIntakeRollersASlot0Configs = new Slot0Configs() .withKS(0) - .withKV(0.048) // 0.488599348534 + .withKV(0.048) // kV tuned to match voltage-to-velocity relationship .withKA(0) .withKP(0.05) .withKI(0) @@ -525,8 +591,8 @@ public static class IntakeK { public static final FeedbackConfigs kIntakeRollersAFeedbackConfigs = new FeedbackConfigs() .withSensorToMechanismRatio(kIntakeRollersGearing); private static final VoltageConfigs kIntakeRollersAVoltageConfigs = new VoltageConfigs() - .withPeakForwardVoltage(12) //1.2 - .withPeakReverseVoltage(-12); //-1.2 + .withPeakForwardVoltage(12) + .withPeakReverseVoltage(-12); public static final TalonFXConfiguration kIntakeRollersAConfiguration = new TalonFXConfiguration() .withCurrentLimits(kIntakeRollersACurrentLimitConfigs) .withSlot0(kIntakeRollersASlot0Configs) @@ -534,6 +600,7 @@ public static class IntakeK { .withFeedback(kIntakeRollersAFeedbackConfigs) .withVoltage(kIntakeRollersAVoltageConfigs); + // Roller B mirrors A but with opposite inversion (they face each other on the robot). public static final MotorOutputConfigs kIntakeRollersBMotorOutputConfigs = new MotorOutputConfigs() .withInverted(InvertedValue.CounterClockwise_Positive) .withNeutralMode(NeutralModeValue.Coast); @@ -541,47 +608,66 @@ public static class IntakeK { .withMotorOutput(kIntakeRollersBMotorOutputConfigs); } + + // ============================================================= + // INDEXER CONSTANTS + // Spindexer + tunnel motor settings and speed ratio math. + // ============================================================= public static class IndexerK { public static final String kLogTab = "Indexer"; - - /* IDS */ - //TODO: Make ids accurate + + // ---- CAN IDs ---- + //TODO: verify IDs match physical robot wiring public static final int kSpindexerCANID = 10; - public static final int kTunnelCANID = 11; + public static final int kTunnelCANID = 11; - public static final double kSpindexerGearing = 5.0 ; //5 - public static final double kTunnelGearing = 20.0/18.0; + // ---- gear reductions ---- + public static final double kSpindexerGearing = 5.0; // 5:1 + public static final double kTunnelGearing = 20.0 / 18.0; // ~1.11:1 + // ---- moments of inertia (for simulation) ---- public static final double kSpindexerMOI = 0.00166190059; - public static final double kTunnelMOI = 0.000215968064; - - public static final AngularVelocity kSpindexerMaxRPS = MotorK.kX60MaxVelocity.div(kSpindexerGearing); - public static final AngularVelocity kSpindexerIntakeRPS = kSpindexerMaxRPS.times(-0.10); + public static final double kTunnelMOI = 0.000215968064; + + // ---- spindexer speed targets ---- + public static final AngularVelocity kSpindexerMaxRPS = MotorK.kX60MaxVelocity.div(kSpindexerGearing); + public static final AngularVelocity kSpindexerIntakeRPS = kSpindexerMaxRPS.times(-0.10); // negative = reverse for intake public static final AngularVelocity kSpindexerShootRPS = kSpindexerMaxRPS.times(0.85); - public static final double kSpindexerMaxRPSD = kSpindexerMaxRPS.in(RotationsPerSecond); + public static final double kSpindexerMaxRPSD = kSpindexerMaxRPS.in(RotationsPerSecond); public static final double kSpindexerShootRPSD = kSpindexerShootRPS.in(RotationsPerSecond); public static final double kSpindexerIntakeRPSD = kSpindexerIntakeRPS.in(RotationsPerSecond); - public static final AngularVelocity kTunnelMaxRPS = MotorK.kX60FOCMaxVelocity.div(kTunnelGearing); - public static final AngularVelocity kTunnelShootRPS = kTunnelMaxRPS.times(0.77); //9V - public static final double kTunnelMaxRPSD = kTunnelMaxRPS.in(RotationsPerSecond); + // ---- tunnel speed targets ---- + public static final AngularVelocity kTunnelMaxRPS = MotorK.kX60FOCMaxVelocity.div(kTunnelGearing); + public static final AngularVelocity kTunnelShootRPS = kTunnelMaxRPS.times(0.77); + public static final double kTunnelMaxRPSD = kTunnelMaxRPS.in(RotationsPerSecond); public static final double kTunnelShootRPSD = kTunnelShootRPS.in(RotationsPerSecond); public static final AngularVelocity kTunnelSpunUpMinimum = RotationsPerSecond.of(10); public static final double kTunnelSpunUpMinimumD = 10.0; - public static final Time kTunnelSpunUpTimeout = Seconds.of(1); //double expected spinup time - - /* VELOCITY RATIO (shooter RPS → indexer RPS) */ + public static final Time kTunnelSpunUpTimeout = Seconds.of(1); + + // ---- speed ratio constants ---- + // These physical wheel radii are used to derive indexer speed from shooter speed. + // The goal: match surface speeds at every ball handoff point so the ball flows + // smoothly without getting slowed or jerked. + // + // Derivation (shooter → tunnel): + // surface speed = radius × angular velocity + // r_shooter × ω_shooter = r_tunnel_pulley × ω_tunnel + // → ratio = (r_bigFlywheel + r_smallFlywheel) / (2 × r_tunnelPulley) + // (average of the two flywheel radii since the ball contacts both) public static final double kR_bigFlywheel = ShooterK.kFlywheelRadiusM; // 0.0381 m - public static final double kR_smallFlywheel = 0.0215; // m - public static final double kR_tunnelPulley = 0.018; // m + public static final double kR_smallFlywheel = 0.0215; // m + public static final double kR_tunnelPulley = 0.018; // m public static final double kR_spindexerFloor = 6.5 * 0.0254; // 0.1651 m public static final double kTunnelFromShooterRatio = (kR_bigFlywheel + kR_smallFlywheel) / (2.0 * kR_tunnelPulley); public static final double kSpindexerFromShooterRatio = (kR_bigFlywheel + kR_smallFlywheel) / (2.0 * kR_spindexerFloor); - /* CONFIGS */ - //TODO: Make transfer configs accurate + // ---- TalonFX configurations ---- + + // Spindexer: velocity control, coast on stop. private static final Slot0Configs kSpindexerSlot0Configs = new Slot0Configs() .withKS(0.420) .withKV(0.560) @@ -602,8 +688,8 @@ public static class IndexerK { private static final FeedbackConfigs kSpindexerFeedbackConfigs = new FeedbackConfigs() .withSensorToMechanismRatio(kSpindexerGearing); private static final VoltageConfigs kSpindexerVoltageConfigs = new VoltageConfigs() - .withPeakForwardVoltage(16) //1.2 - .withPeakReverseVoltage(-16); //-1.2 + .withPeakForwardVoltage(16) + .withPeakReverseVoltage(-16); public static final TalonFXConfiguration kSpindexerTalonFXConfiguration = new TalonFXConfiguration() .withSlot0(kSpindexerSlot0Configs) .withCurrentLimits(kSpindexerCurrentLimitConfigs) @@ -611,6 +697,7 @@ public static class IndexerK { .withFeedback(kSpindexerFeedbackConfigs) .withVoltage(kSpindexerVoltageConfigs); + // Tunnel: velocity control with FOC, coast on stop. private static final Slot0Configs kTunnelSlot0Configs = new Slot0Configs() .withKS(0.2) .withKV(0.1337) @@ -631,8 +718,8 @@ public static class IndexerK { private static final FeedbackConfigs kTunnelFeedbackConfigs = new FeedbackConfigs() .withSensorToMechanismRatio(kTunnelGearing); private static final VoltageConfigs kTunnelVoltageConfigs = new VoltageConfigs() - .withPeakForwardVoltage(16) //1.2 - .withPeakReverseVoltage(-16); //-1.2 + .withPeakForwardVoltage(16) + .withPeakReverseVoltage(-16); public static final TalonFXConfiguration kTunnelTalonFXConfiguration = new TalonFXConfiguration() .withSlot0(kTunnelSlot0Configs) .withCurrentLimits(kTunnelCurrentLimitConfigs) @@ -641,118 +728,142 @@ public static class IndexerK { .withVoltage(kTunnelVoltageConfigs); } + + // ============================================================= + // TURRET CONSTANTS + // Encoder offsets and gear tooth counts for the turret position tracking system. + // ============================================================= public static class TurretK { public static final String kLogTab = "Turret"; - + + // Gear tooth counts for the turret's LCM (Least Common Multiple) absolute position tracking. + // The combination of gear tooth counts creates a unique pattern used to find absolute position. public static final double kGearZeroToothCount = 100; - public static final double kGearOneToothCount = 10; - public static final double kGearTwoToothCount = 19; + public static final double kGearOneToothCount = 10; + public static final double kGearTwoToothCount = 19; + + // The turret's LCM reading when it's at the mechanical home position. + // Measured empirically — log "turretLCMPos" and read the value when the turret is at home. + public static final double kLCMAtHomeRots = 0.251; - public static final double kLCMAtHomeRots = 0.251; // measure: turretLCMPos log value when turret is at home + // CANcoder magnet offset: corrects for the encoder not being physically zero-aligned. public static final double kEncAMagnetOffset = 0.320556640625; - public static final double kEncBOffset = 0.529614; // measure: encB reading when turret is at encA=0 //NEW ONE + + // Secondary encoder offset: the reading of encoder B when encoder A reads zero. + // Used as a cross-check for the LCM position calculation. + public static final double kEncBOffset = 0.529614; } + + + // ============================================================= + // AUTON CONSTANTS + // Timeouts and Choreo trajectory file names. + // ============================================================= public static class AutonK { public static final String kLogTab = "Auton"; - public static final Pose2d kRightNeutralPose = new Pose2d(Meters.of(6.924767017364502), + // ---- reference poses ---- + // Used for auton starting position reset and neutral-zone aiming. + public static final Pose2d kRightNeutralPose = new Pose2d(Meters.of(6.924767017364502), Meters.of(2.251265048980713), new Rotation2d(0)); - public static final Pose2d kRightDepotPose = new Pose2d(Meters.of(1.1576627492904663), + public static final Pose2d kRightDepotPose = new Pose2d(Meters.of(1.1576627492904663), Meters.of(5.958622932434082), new Rotation2d(Math.PI)); - - public static final Pose2d kLeftNeutralPose = new Pose2d(Meters.of(6.924767017364502), + public static final Pose2d kLeftNeutralPose = new Pose2d(Meters.of(6.924767017364502), Meters.of(5.437880039215088), new Rotation2d(0)); - public static final double kIntakeTimeout = 7.5; - public static final double kShootingTimeout = 4; //12 - public static final double kSOTMTimeout = 100; //12 + // ---- timeouts ---- + // How long each auton action is allowed to take before giving up and moving on. + // These are safety cutoffs — in ideal conditions the action ends earlier. + public static final double kIntakeTimeout = 7.5; + public static final double kShootingTimeout = 4; + public static final double kSOTMTimeout = 100; // effectively unlimited — SOTM doesn't block on shot confirmation public static final double kSweepShootingTimeout = 20; + // Delay (seconds) between segments when following another robot. public static final double kFollowDelay = 2; - /* OLD PATHS */ - //---RIGHT FIRST CYCLES - public static final String kRightOneJab = "RIGHT_one_jab"; - public static final String kRightOneTrench = "RIGHT_one_trench"; - public static final String kRightOneDefense = "RIGHT_one_defense"; - public static final String kRightOneReverse = "RIGHT_one_reverse"; + // ---- trajectory file names ---- + // These strings are the file names of the Choreo trajectory JSON files + // (without the .traj extension). They're used by WaltAdaptableAutonFactory + // and AutonChooser to load paths. + // + // Naming convention: + // SIDE_cycle_description + // e.g. "RIGHT_one_jab" = right-side start, first cycle, jab path + // "LEFT_two_sweep" = left-side start, second cycle, sweep path + + /* OLD PATHS (kept for reference / regression testing) */ + public static final String kRightOneJab = "RIGHT_one_jab"; + public static final String kRightOneTrench = "RIGHT_one_trench"; + public static final String kRightOneDefense = "RIGHT_one_defense"; + public static final String kRightOneReverse = "RIGHT_one_reverse"; - //---RIGHT SECOND CYCLES public static final String kRightTwoSotmDepot = "RIGHT_two_sotmDepot"; - public static final String kRightTwoDepot = "RIGHT_two_depot"; - public static final String kRightTwoSweep = "RIGHT_two_sweep"; - public static final String kRightTwoPassing = "RIGHT_two_passing"; - public static final String kRightTwoJab = "RIGHT_two_jab"; - public static final String kRightTwoReverse = "RIGHT_two_reverse"; - - //---LEFT FIRST CYCLES - public static final String kLeftOneJab = "LEFT_one_jab"; - public static final String kLeftOneTrench = "LEFT_one_trench"; - public static final String kLeftOneDefense = "LEFT_one_defense"; - public static final String kLeftOneReverse = "LEFT_one_reverse"; - - //---LEFT SECOND CYCLES + public static final String kRightTwoDepot = "RIGHT_two_depot"; + public static final String kRightTwoSweep = "RIGHT_two_sweep"; + public static final String kRightTwoPassing = "RIGHT_two_passing"; + public static final String kRightTwoJab = "RIGHT_two_jab"; + public static final String kRightTwoReverse = "RIGHT_two_reverse"; + + public static final String kLeftOneJab = "LEFT_one_jab"; + public static final String kLeftOneTrench = "LEFT_one_trench"; + public static final String kLeftOneDefense = "LEFT_one_defense"; + public static final String kLeftOneReverse = "LEFT_one_reverse"; + public static final String kLeftTwoSotmDepot = "LEFT_two_sotmDepot"; - public static final String kLeftTwoDepot = "LEFT_two_depot"; - public static final String kLeftTwoSweep = "LEFT_two_sweep"; - public static final String kLeftTwoPassing = "LEFT_two_passing"; - public static final String kLeftTwoJab = "LEFT_two_jab"; - public static final String kLeftTwoReverse = "LEFT_two_reverse"; - - //---MISC - public static final String kRightOneCircle = "RIGHT_one_circle"; - public static final String kLeftOneSweepAndDepot = "LEFT_one_sweepAndDepot"; - public static final String kLeftThreeDepotToBump = "LEFT_three_depotToBump"; + public static final String kLeftTwoDepot = "LEFT_two_depot"; + public static final String kLeftTwoSweep = "LEFT_two_sweep"; + public static final String kLeftTwoPassing = "LEFT_two_passing"; + public static final String kLeftTwoJab = "LEFT_two_jab"; + public static final String kLeftTwoReverse = "LEFT_two_reverse"; + + public static final String kRightOneCircle = "RIGHT_one_circle"; + public static final String kLeftOneSweepAndDepot = "LEFT_one_sweepAndDepot"; + public static final String kLeftThreeDepotToBump = "LEFT_three_depotToBump"; public static final String kRightThreeDepotToBump = "RIGHT_three_depotToBump"; - //---STRESS TEST - public static final String kRightStressTestLong = "RIGHT_stress_test_long"; + public static final String kRightStressTestLong = "RIGHT_stress_test_long"; public static final String kRightStressTestOverlap = "RIGHT_stress_test_overlap"; - + /* NEW PATHS */ - //---BUMP RETURN PATHS - public static final String kRightOneBumpReturn = "RIGHT_one_bumpReturn"; + public static final String kRightOneBumpReturn = "RIGHT_one_bumpReturn"; public static final String kRightOneBumpReturnFollow = "RIGHT_one_bumpReturnFollow"; - public static final String kLeftOneBumpReturn = "LEFT_one_bumpReturn"; - public static final String kLeftOneBumpReturnFollow = "LEFT_one_bumpReturnFollow"; - public static final String kRightTwoBumpReturn = "RIGHT_two_bumpReturn"; - public static final String kLeftTwoBumpReturn = "LEFT_two_bumpReturn"; - public static final String kRightTwoBumpToTrench = "RIGHT_two_bumpToTrench"; - public static final String kLeftTwoBumpToTrench = "LEFT_two_bumpToTrench"; - - //---TRENCH RETURN PATHS - public static final String kRightOneTrenchReturn = "RIGHT_one_trenchReturn"; - public static final String kLeftOneTrenchReturn = "LEFT_one_trenchReturn"; - public static final String kRightTwoTrenchReturn = "RIGHT_two_trenchReturn"; - public static final String kLeftTwoTrenchReturn = "LEFT_two_trenchReturn"; + public static final String kLeftOneBumpReturn = "LEFT_one_bumpReturn"; + public static final String kLeftOneBumpReturnFollow = "LEFT_one_bumpReturnFollow"; + public static final String kRightTwoBumpReturn = "RIGHT_two_bumpReturn"; + public static final String kLeftTwoBumpReturn = "LEFT_two_bumpReturn"; + public static final String kRightTwoBumpToTrench = "RIGHT_two_bumpToTrench"; + public static final String kLeftTwoBumpToTrench = "LEFT_two_bumpToTrench"; + + public static final String kRightOneTrenchReturn = "RIGHT_one_trenchReturn"; + public static final String kLeftOneTrenchReturn = "LEFT_one_trenchReturn"; + public static final String kRightTwoTrenchReturn = "RIGHT_two_trenchReturn"; + public static final String kLeftTwoTrenchReturn = "LEFT_two_trenchReturn"; public static final String kRightOneBumpTrenchReturn = "RIGHT_one_bumpReverseToTrench"; - public static final String kLeftOneBumpTrenchReturn = "LEFT_one_bumpReverseToTrench"; - - //---OUTPOST PATHS - public static final String kRightOneTrenchToOutpost = "RIGHT_one_trenchToOutpost"; - public static final String kRightTwoTrenchToOutpost = "RIGHT_two_trenchToOutpost"; - public static final String kRightTwoOutpostToTrench = "RIGHT_two_outpostToTrench"; - public static final String kRightOneBumpToOutpost = "RIGHT_one_bumpToOutpost"; - public static final String kRightTwoBumpToOutpost = "RIGHT_two_bumpToOutpost"; - public static final String kRightTwoOutpostToBump = "RIGHT_two_outpostToBump"; - - //---DEPOT PATHS - public static final String kLeftOneTrenchToDepot = "LEFT_one_trenchToDepot"; - public static final String kLeftTwoTrenchToDepot = "LEFT_two_trenchToDepot"; - public static final String kLeftTwoDepotToTrench = "LEFT_two_depotToTrench"; - public static final String kLeftOneBumpToDepot = "LEFT_one_bumpToDepot"; - public static final String kLeftTwoBumpToDepot = "LEFT_two_bumpToDepot"; - public static final String kLeftTwoDepotToBump = "LEFT_two_depotToBump"; - - //---MISC - public static final String kRightOneSelfPass = "RIGHT_one_selfPass"; - public static final String kLeftOneSelfPass = "LEFT_one_selfPass"; - public static final String kRightTwoGoOut = "RIGHT_two_goOut"; - public static final String kLeftTwoGoOut = "LEFT_two_goOut"; - public static final String kRightBumpPreload = "RIGHT_one_bumpPreload"; - public static final String kLeftBumpPreload = "LEFT_one_bumpPreload"; + public static final String kLeftOneBumpTrenchReturn = "LEFT_one_bumpReverseToTrench"; + + public static final String kRightOneTrenchToOutpost = "RIGHT_one_trenchToOutpost"; + public static final String kRightTwoTrenchToOutpost = "RIGHT_two_trenchToOutpost"; + public static final String kRightTwoOutpostToTrench = "RIGHT_two_outpostToTrench"; + public static final String kRightOneBumpToOutpost = "RIGHT_one_bumpToOutpost"; + public static final String kRightTwoBumpToOutpost = "RIGHT_two_bumpToOutpost"; + public static final String kRightTwoOutpostToBump = "RIGHT_two_outpostToBump"; + + public static final String kLeftOneTrenchToDepot = "LEFT_one_trenchToDepot"; + public static final String kLeftTwoTrenchToDepot = "LEFT_two_trenchToDepot"; + public static final String kLeftTwoDepotToTrench = "LEFT_two_depotToTrench"; + public static final String kLeftOneBumpToDepot = "LEFT_one_bumpToDepot"; + public static final String kLeftTwoBumpToDepot = "LEFT_two_bumpToDepot"; + public static final String kLeftTwoDepotToBump = "LEFT_two_depotToBump"; + + public static final String kRightOneSelfPass = "RIGHT_one_selfPass"; + public static final String kLeftOneSelfPass = "LEFT_one_selfPass"; + public static final String kRightTwoGoOut = "RIGHT_two_goOut"; + public static final String kLeftTwoGoOut = "LEFT_two_goOut"; + public static final String kRightBumpPreload = "RIGHT_one_bumpPreload"; + public static final String kLeftBumpPreload = "LEFT_one_bumpPreload"; public static final String kRightTrenchPreload = "RIGHT_one_trenchPreload"; - public static final String kLeftTrenchPreload = "LEFT_one_trenchPreload"; + public static final String kLeftTrenchPreload = "LEFT_one_trenchPreload"; public static final String kCenterPreload = "CENTER_one_preload"; } diff --git a/src/main/java/frc/robot/subsystems/Indexer.java b/src/main/java/frc/robot/subsystems/Indexer.java index 12ca67fe..2f25b745 100644 --- a/src/main/java/frc/robot/subsystems/Indexer.java +++ b/src/main/java/frc/robot/subsystems/Indexer.java @@ -7,7 +7,6 @@ import com.ctre.phoenix6.sim.ChassisReference; import com.ctre.phoenix6.sim.TalonFXSimState.MotorType; -import edu.wpi.first.networktables.DoubleSubscriber; import edu.wpi.first.units.measure.AngularVelocity; import edu.wpi.first.units.measure.Current; import edu.wpi.first.wpilibj2.command.Command; @@ -27,101 +26,126 @@ import frc.util.WaltLogger.BooleanLogger; import frc.util.WaltLogger.DoubleLogger; +/* + * Indexer + * + * The indexer sits between the intake and the shooter and is responsible for + * moving balls from the hopper into the shooter at the right speed. + * + * It has two motors: + * Spindexer — a spinning disk/floor underneath the ball hopper. It spins + * to rotate balls around and push them toward the tunnel entrance. + * Tunnel — a belt or tube that carries balls linearly from the hopper + * toward the shooter flywheel. It needs to spin fast enough to + * match the ball's exit speed. + * + * The tunnel speed is linked to the shooter flywheel speed via a geometric ratio + * derived from the physical radii of each wheel (see tunnelRPSFromShooter). + * This prevents the ball from being jerked or slowed as it enters the shooter. + */ public class Indexer extends SubsystemBase { - /* CLASS VARIABLES */ - //---MOTORS + CONTROL REQUESTS - private final TalonFX m_spindexer = new TalonFX(kSpindexerCANID, Constants.kCanivoreBus); // X60Foc - private final TalonFX m_tunnel = new TalonFX(kTunnelCANID, Constants.kCanivoreBus); // X60Foc + // ---- motors ---- + + // Both motors are Kraken X60s on the CANivore bus. + // X60Foc = Kraken X60 with Field-Oriented Control enabled (more torque at high speeds). + private final TalonFX m_spindexer = new TalonFX(kSpindexerCANID, Constants.kCanivoreBus); + private final TalonFX m_tunnel = new TalonFX(kTunnelCANID, Constants.kCanivoreBus); + + // ---- control requests ---- + // VelocityVoltage tells the motor controller to hold a specific speed (in RPS) + // using a PID loop + feedforward. We pre-create these and mutate them with + // .withVelocity() to avoid unnecessary object allocation on the hot path. + + // Spindexer uses non-FOC velocity (simpler, lower torque demands). private final VelocityVoltage m_spindexerVelocityRequest = new VelocityVoltage(0).withEnableFOC(false); + // Tunnel uses FOC velocity for smoother, more precise speed matching with the shooter. private final VelocityVoltage m_tunnelVelocityRequest = new VelocityVoltage(0).withEnableFOC(true); - private static final WaltTunable kTunnelRPSOverride = new WaltTunable("/Indexer/Tunnel/tunnelRPSOverride", kTunnelShootRPSD); - private static final WaltTunable kSpindexerRPSOverride = new WaltTunable("/Indexer/Spindexer/spindexerRPSOverride", kSpindexerShootRPSD); - + // CoastOut lets the motor spin freely to a stop instead of actively braking. + // We use this when stopping so the ball's momentum doesn't fight the motor. private final CoastOut m_spindexerMotorIdleReq = new CoastOut(); private final CoastOut m_tunnelMotorIdleReq = new CoastOut(); - /* SIM OBJECTS */ - // private final DCMotorSim m_spindexerSim = new DCMotorSim( - // LinearSystemId.createDCMotorSystem( - // DCMotor.getKrakenX60Foc(1), - // kSpindexerMOI, - // kSpindexerGearing - // ), - // DCMotor.getKrakenX60Foc(1) - // ); - - // private final DCMotorSim m_tunnelSim = new DCMotorSim( - // LinearSystemId.createDCMotorSystem( - // DCMotor.getKrakenX60Foc(1), - // kTunnelMOI, - // kTunnelGearing - // ), - // DCMotor.getKrakenX60Foc(1) - // ); - - - /* TUNABLES */ - private static final WaltTunable kTunnelRatioScalarTuner = new WaltTunable("/Indexer/Tunnel/tunnelRatioScalar", 1.0); - private static final WaltTunable kSpindexerRatioScalarTuner = new WaltTunable("/Indexer/Spindexer/spindexerRatioScalar", 1.0); - - /* LOGGERS */ + // ---- tunables ---- + // WaltTunable values can be adjusted at runtime through NetworkTables (no redeploy needed). + // If a tunable has been set, getOr() returns the tuned value; otherwise it returns the default. + // Useful during practice to dial in speeds without redeploying. + + private static final WaltTunable kTunnelRPSOverride = new WaltTunable("/Indexer/Tunnel/tunnelRPSOverride", kTunnelShootRPSD); + private static final WaltTunable kSpindexerRPSOverride = new WaltTunable("/Indexer/Spindexer/spindexerRPSOverride", kSpindexerShootRPSD); + private static final WaltTunable kTunnelRatioScalarTuner = new WaltTunable("/Indexer/Tunnel/tunnelRatioScalar", 1.0); + private static final WaltTunable kSpindexerRatioScalarTuner = new WaltTunable("/Indexer/Spindexer/spindexerRatioScalar", 1.0); + + // ---- loggers ---- + private final String kTunnelLogTab = "/Tunnel"; private final String kSpindexerLogTab = "/Spindexer"; private final DoubleLogger log_spindexerRPS = WaltLogger.logDouble(kLogTab + kSpindexerLogTab, "spindexerRPS"); - private final DoubleLogger log_tunnelRPS = WaltLogger.logDouble(kLogTab + kTunnelLogTab, "tunnelRPS"); - + private final DoubleLogger log_tunnelRPS = WaltLogger.logDouble(kLogTab + kTunnelLogTab, "tunnelRPS"); private final DoubleLogger log_desiredSpindexerRPS = WaltLogger.logDouble(kLogTab + kSpindexerLogTab, "desiredRPS"); - private final DoubleLogger log_desiredTunnelRPS = WaltLogger.logDouble(kLogTab + kTunnelLogTab, "desiredRPS"); + private final DoubleLogger log_desiredTunnelRPS = WaltLogger.logDouble(kLogTab + kTunnelLogTab, "desiredRPS"); private final DoubleLogger log_spindexerStatorCurrent = WaltLogger.logDouble(kLogTab + kSpindexerLogTab, "statorCurrent"); private final DoubleLogger log_spindexerSupplyCurrent = WaltLogger.logDouble(kLogTab + kSpindexerLogTab, "supplyCurrent"); + private final DoubleLogger log_tunnelClosedLoopError = WaltLogger.logDouble(kLogTab + kTunnelLogTab, "closedLoopError"); + private final BooleanLogger log_isTunnelSpunUp = WaltLogger.logBoolean(kLogTab + kTunnelLogTab, "spunUp"); + + // ---- status signals ---- + // StatusSignals are CTRE's way of efficiently reading motor data without spamming the CAN bus. + // We register them with SignalManager so they get refreshed at a consistent rate each loop. private final StatusSignal sig_spindexerVelo = m_spindexer.getVelocity(); private final StatusSignal sig_spindexerStatorCurrent = m_spindexer.getStatorCurrent(); - private final StatusSignal sig_spindexerSupplyCurrent = m_spindexer.getSupplyCurrent();; + private final StatusSignal sig_spindexerSupplyCurrent = m_spindexer.getSupplyCurrent(); private final StatusSignal sig_tunnelVelo = m_tunnel.getVelocity(); private final StatusSignal sig_tunnelCLErr = m_tunnel.getClosedLoopError(); - private final DoubleLogger log_tunnelClosedLoopError = WaltLogger.logDouble(kLogTab + kTunnelLogTab, "closedLoopError"); - private final BooleanLogger log_isTunnelSpunUp = WaltLogger.logBoolean(kLogTab + kTunnelLogTab, "spunUp"); + // ---- state ---- private boolean m_isTunnelSpunUp = false; private double m_tunnelVelocityRotPerSec = 0.0; private double m_desiredTunnelRPS = 0.0; private double m_desiredSpindexerRPS = 0.0; - /* CONSTRUCTOR */ + + // ============================================================= + // CONSTRUCTOR + // ============================================================= + public Indexer() { + // Apply the motor configurations defined in Constants.IndexerK. m_spindexer.getConfigurator().apply(kSpindexerTalonFXConfiguration); m_tunnel.getConfigurator().apply(kTunnelTalonFXConfiguration); - SignalManager.register(Constants.kCanivoreBus, sig_spindexerVelo, sig_tunnelVelo, sig_tunnelCLErr, sig_spindexerStatorCurrent, sig_spindexerSupplyCurrent); + // Register all status signals so they're refreshed every loop at the correct rate. + SignalManager.register(Constants.kCanivoreBus, + sig_spindexerVelo, sig_tunnelVelo, sig_tunnelCLErr, + sig_spindexerStatorCurrent, sig_spindexerSupplyCurrent); initSim(); } - //TODO: Change orientation if necessary + // Sets up the simulation models for both motors. + // TODO: Change orientation if mechanical setup changes. private void initSim() { WaltMotorSim.initSimFX(m_spindexer, ChassisReference.CounterClockwise_Positive, MotorType.KrakenX60); WaltMotorSim.initSimFX(m_tunnel, ChassisReference.CounterClockwise_Positive, MotorType.KrakenX60); } - /* COMMANDS */ - //---STARTS AND STOPS + + // ============================================================= + // COMMANDS — START / STOP + // ============================================================= + + // Starts both the tunnel and spindexer at their shoot speeds. public Command startIndexerCmd() { - return Commands.sequence( - startTunnelCmd(), - startSpindexerCmd() - ); + return Commands.sequence(startTunnelCmd(), startSpindexerCmd()); } + // Stops both motors by commanding 0 RPS (which triggers CoastOut in the setters). public Command stopIndexerCmd() { - return Commands.sequence( - stopTunnelCmd(), - stopSpindexerCmd() - ); + return Commands.sequence(stopTunnelCmd(), stopSpindexerCmd()); } public Command startSpindexerCmd() { @@ -136,11 +160,6 @@ public void stopSpindexer() { setSpindexerVelocity(0); } - public void setIndexerFromShooterRPS(DoubleSupplier shooterRPS) { - setTunnelVelocity(tunnelRPSFromShooter(shooterRPS).getAsDouble()); - setSpindexerVelocity(spindexerRPSFromShooter(shooterRPS).getAsDouble()); - } - public Command startTunnelCmd() { return setTunnelVelocityCmd(kTunnelShootRPS); } @@ -153,42 +172,27 @@ public void stopTunnel() { setTunnelVelocity(0); } - private void refreshTunnelState() { - m_tunnelVelocityRotPerSec = sig_tunnelVelo.getValueAsDouble(); - log_tunnelRPS.accept(m_tunnelVelocityRotPerSec); - - log_tunnelClosedLoopError.accept(sig_tunnelCLErr.getValueAsDouble()); - m_isTunnelSpunUp = sig_tunnelCLErr.isNear(0, 3); - if (m_desiredTunnelRPS > 80) { - m_isTunnelSpunUp = sig_tunnelCLErr.isNear(0, 6); - } - - log_isTunnelSpunUp.accept(m_isTunnelSpunUp); - } - - public boolean isTunnelSpunUp() { - return m_isTunnelSpunUp; - } - - public double getTunnelVelocityRotPerSec() { - return m_tunnelVelocityRotPerSec; + // Sets both indexer motors to speeds derived from the shooter's current RPS. + // This keeps the indexer surface speed matched to the flywheel so the ball + // doesn't get jerked when it enters the shooter. + public void setIndexerFromShooterRPS(DoubleSupplier shooterRPS) { + setTunnelVelocity(tunnelRPSFromShooter(shooterRPS).getAsDouble()); + setSpindexerVelocity(spindexerRPSFromShooter(shooterRPS).getAsDouble()); } - public double getDesiredTunnelVelocityRPS() { - return m_desiredTunnelRPS; - } - public double getDesiredSpindexerVelocityRPS() { - return m_desiredSpindexerRPS; - } + // ============================================================= + // SPINDEXER CONTROL + // ============================================================= - //---SPINDEXER + // When stopping (RPS == 0) we switch to CoastOut so the spindexer spins + // down freely public void setSpindexerVelocity(double RPS) { if (RPS == 0) { m_spindexer.setControl(m_spindexerVelocityRequest.withVelocity(0)); m_spindexer.setControl(m_spindexerMotorIdleReq); } else { - RPS = kSpindexerRPSOverride.getOr(RPS); + RPS = kSpindexerRPSOverride.getOr(RPS); // override if tuned at runtime m_spindexer.setControl(m_spindexerVelocityRequest.withVelocity(RPS)); } m_desiredSpindexerRPS = RPS; @@ -199,13 +203,18 @@ public Command setSpindexerVelocityCmd(AngularVelocity RPS) { return runOnce(() -> setSpindexerVelocity(RPS.in(RotationsPerSecond))); } - //---TUNNEL + + // ============================================================= + // TUNNEL CONTROL + // ============================================================= + + // Same coast-on-stop pattern as the spindexer. public void setTunnelVelocity(double RPS) { if (RPS == 0) { m_tunnel.setControl(m_tunnelVelocityRequest.withVelocity(0)); m_tunnel.setControl(m_tunnelMotorIdleReq); } else { - RPS = kTunnelRPSOverride.getOr(RPS); + RPS = kTunnelRPSOverride.getOr(RPS); // override if tuned at runtime m_tunnel.setControl(m_tunnelVelocityRequest.withVelocity(RPS)); } m_desiredTunnelRPS = RPS; @@ -216,16 +225,65 @@ public Command setTunnelVelocityCmd(AngularVelocity RPS) { return runOnce(() -> setTunnelVelocity(RPS.in(RotationsPerSecond))); } - //STATICS for conversions + // Polls the tunnel's speed and closed-loop error, then decides if it's "spun up." + // The closed-loop error is how far off the tunnel is from its target speed (in RPS). + // At very high speeds (> 80 RPS), we allow a larger error tolerance since the + // motor is working harder and small deviations matter less. + private void refreshTunnelState() { + m_tunnelVelocityRotPerSec = sig_tunnelVelo.getValueAsDouble(); + log_tunnelRPS.accept(m_tunnelVelocityRotPerSec); + log_tunnelClosedLoopError.accept(sig_tunnelCLErr.getValueAsDouble()); + + m_isTunnelSpunUp = sig_tunnelCLErr.isNear(0, 3); // within 3 RPS of target + if (m_desiredTunnelRPS > 80) { + m_isTunnelSpunUp = sig_tunnelCLErr.isNear(0, 6); // looser tolerance at high speed + } + + log_isTunnelSpunUp.accept(m_isTunnelSpunUp); + } + + public boolean isTunnelSpunUp() { return m_isTunnelSpunUp; } + public double getTunnelVelocityRotPerSec() { return m_tunnelVelocityRotPerSec; } + public double getDesiredTunnelVelocityRPS() { return m_desiredTunnelRPS; } + public double getDesiredSpindexerVelocityRPS() { return m_desiredSpindexerRPS; } + + + // ============================================================= + // SPEED RATIO CONVERSIONS + // ============================================================= + + // The tunnel and spindexer speeds are derived from the shooter flywheel speed + // using the physical radii of each wheel/pulley. The goal is to match surface + // speeds so the ball doesn't get grabbed or slowed when it transitions between + // the indexer and the shooter. + // + // Surface speed = radius × angular velocity + // For the ball to travel smoothly: r_shooter × ω_shooter = r_indexer × ω_indexer + // → ω_indexer = (r_shooter / r_indexer) × ω_shooter + // + // The ratio constants (kTunnelFromShooterRatio, kSpindexerFromShooterRatio) are + // pre-computed in Constants.IndexerK from the physical radii. + // kTunnelRatioScalarTuner allows fine-tuning that ratio at runtime. + public static DoubleSupplier tunnelRPSFromShooter(DoubleSupplier shooterRPS) { - return () -> Math.min(shooterRPS.getAsDouble() * kTunnelFromShooterRatio * (kTunnelRatioScalarTuner.getOr(1.0)), kTunnelMaxRPSD); + return () -> Math.min( + shooterRPS.getAsDouble() * kTunnelFromShooterRatio * kTunnelRatioScalarTuner.getOr(1.0), + kTunnelMaxRPSD + ); } public static DoubleSupplier spindexerRPSFromShooter(DoubleSupplier shooterRPS) { - return () -> Math.min(shooterRPS.getAsDouble() * kSpindexerFromShooterRatio * (kSpindexerRatioScalarTuner.getOr(1.0)), kSpindexerMaxRPSD); + return () -> Math.min( + shooterRPS.getAsDouble() * kSpindexerFromShooterRatio * kSpindexerRatioScalarTuner.getOr(1.0), + kSpindexerMaxRPSD + ); } - /* PERIODICS */ + + // ============================================================= + // PERIODIC + // ============================================================= + @Override public void periodic() { log_spindexerRPS.accept(sig_spindexerVelo.getValueAsDouble()); @@ -233,10 +291,4 @@ public void periodic() { log_spindexerSupplyCurrent.accept(sig_spindexerSupplyCurrent.getValueAsDouble()); refreshTunnelState(); } - - // @Override - // public void simulationPeriodic() { - // WaltMotorSim.updateSimFX(m_tunnel, m_tunnelSim); - // WaltMotorSim.updateSimFX(m_spindexer, m_spindexerSim); - // } } diff --git a/src/main/java/frc/robot/subsystems/Intake.java b/src/main/java/frc/robot/subsystems/Intake.java index f18a6e34..7e1549df 100644 --- a/src/main/java/frc/robot/subsystems/Intake.java +++ b/src/main/java/frc/robot/subsystems/Intake.java @@ -4,6 +4,7 @@ import com.ctre.phoenix6.controls.Follower; import com.ctre.phoenix6.controls.MotionMagicVoltage; import com.ctre.phoenix6.controls.VelocityVoltage; +import com.ctre.phoenix6.controls.VoltageOut; import com.ctre.phoenix6.hardware.TalonFX; import com.ctre.phoenix6.signals.MotorAlignmentValue; import com.ctre.phoenix6.signals.NeutralModeValue; @@ -19,20 +20,15 @@ import java.util.function.BooleanSupplier; import java.util.function.Consumer; -import com.ctre.phoenix6.controls.DynamicMotionMagicVoltage; -import com.ctre.phoenix6.controls.VoltageOut; - import edu.wpi.first.math.filter.Debouncer; import edu.wpi.first.math.filter.Debouncer.DebounceType; import edu.wpi.first.math.system.plant.DCMotor; import edu.wpi.first.math.system.plant.LinearSystemId; -import edu.wpi.first.networktables.DoubleSubscriber; import edu.wpi.first.units.measure.Angle; import edu.wpi.first.units.measure.AngularVelocity; import edu.wpi.first.units.measure.Current; import edu.wpi.first.wpilibj.simulation.DCMotorSim; import edu.wpi.first.wpilibj2.command.Command; -import edu.wpi.first.wpilibj2.command.Commands; import edu.wpi.first.wpilibj2.command.FunctionalCommand; import edu.wpi.first.wpilibj2.command.SubsystemBase; import frc.util.WaltLogger.BooleanLogger; @@ -42,79 +38,126 @@ import frc.util.SignalManager; import frc.util.WaltLogger; +/* + * Intake + * + * The intake is a ground-level collector that picks up fuel balls during a match. + * It has two parts that work together: + * + * Arm — A pivoting arm that swings down so the rollers can reach balls on + * the ground. It's controlled by a single motor using MotionMagic + * (CTRE's trapezoidal motion profiling) so it moves smoothly to each + * target position without slamming. + * + * Rollers — Two motors that spin to pull the ball in and pass it to the indexer. + * Motor B mirrors motor A (via Follower) but runs in the opposite + * direction so both rollers push the ball the same way. + * + * At the start of every match/auto, the arm needs to home itself — it doesn't + * have an absolute encoder, so it slowly drives into a mechanical hard stop, + * detects the stall via current + velocity sensing, and zeros the encoder there. + * See intakeArmCurrentSenseHoming(). + */ public class Intake extends SubsystemBase { - /* CLASS VARIABLES */ - //---MOTORS + CONTROL REQUESTS - private final TalonFX m_intakeArm = new TalonFX(kIntakeArmCANID); //x60Foc - private final TalonFX m_intakeRollersA = new TalonFX(kIntakeRollersA_CANID); //x60Foc - private final TalonFX m_intakeRollersB = new TalonFX(kIntakeRollersB_CANID); //x60Foc + // ---- motors ---- + + private final TalonFX m_intakeArm = new TalonFX(kIntakeArmCANID); // arm pivot (KrakenX44Foc on Rio bus) + private final TalonFX m_intakeRollersA = new TalonFX(kIntakeRollersA_CANID); // primary roller (KrakenX60Foc) + private final TalonFX m_intakeRollersB = new TalonFX(kIntakeRollersB_CANID); // follower roller (KrakenX60Foc) + + // ---- control requests ---- + // Pre-created and reused every loop to avoid unnecessary object allocation. + // MotionMagicVoltage: moves the arm smoothly to a target position. + // MotionMagic generates a trapezoidal velocity profile internally (accelerate, cruise, decelerate) + // so the arm doesn't slam into positions and cause mechanical stress or brownouts. private MotionMagicVoltage m_MMVReq = new MotionMagicVoltage(0).withEnableFOC(true); + + // VelocityVoltage: holds the rollers at a specific speed (RPS) using closed-loop control. private VelocityVoltage m_VelVoltReq = new VelocityVoltage(0).withEnableFOC(true); + + // VoltageOut: applies raw voltage directly to the arm motor. Used only during homing, + // where we intentionally drive into the hard stop at a low, safe voltage. private VoltageOut m_voltsReq = new VoltageOut(0).withEnableFOC(true); + // A second VoltageOut specifically for zeroing — kept separate from m_voltsReq so + // periodic logging (which reads m_voltsReq.Output) isn't contaminated by homing values. + private VoltageOut m_intakeArmZeroingReq = new VoltageOut(0); + + // ---- status signals ---- + private final StatusSignal sig_intakeArmStatorCurrent = m_intakeArm.getStatorCurrent(); private final StatusSignal sig_intakeArmVelo = m_intakeArm.getVelocity(); private final StatusSignal sig_intakeRollersAVelo = m_intakeRollersA.getVelocity(); private final StatusSignal sig_intakeArmPos = m_intakeArm.getPosition(); private final StatusSignal sig_intakeArmMMAtTarget = m_intakeArm.getMotionMagicAtTarget(); + // ---- homing detection helpers ---- + // These two conditions together confirm the arm has hit the mechanical hard stop: + // 1. Stator current spikes (motor is stalled / fighting the hard stop) + // 2. Arm velocity is near zero (it's not moving anymore) + private BooleanSupplier m_currentSpike = () -> sig_intakeArmStatorCurrent.getValueAsDouble() > 5.0; private BooleanSupplier m_veloIsNearZero = () -> Math.abs(sig_intakeArmVelo.getValueAsDouble()) < 0.005; + // Looser velocity threshold used during the shimmy motion, which has intentional slow movement. private BooleanSupplier m_shimmyVeloIsNearZero = () -> Math.abs(sig_intakeArmVelo.getValueAsDouble()) < 0.05; - - private VoltageOut m_intakeArmZeroingReq = new VoltageOut(0); - private Debouncer m_currentDebouncer = new Debouncer(0.100, DebounceType.kRising); - private Debouncer m_velocityDebouncer = new Debouncer(0.125, DebounceType.kRising); + // Debouncers filter out momentary spikes that don't represent a true stall. + // kRising means the output only goes true after the input has been true for the set duration. + private Debouncer m_currentDebouncer = new Debouncer(0.100, DebounceType.kRising); // 100 ms + private Debouncer m_velocityDebouncer = new Debouncer(0.125, DebounceType.kRising); // 125 ms private boolean m_isIntakeArmHomed = false; + // Public supplier so other classes (e.g. WaltAdaptableAutonFactory) can wait on homing. public final BooleanSupplier intakeHomedSupp = () -> m_isIntakeArmHomed; - /* SIM OBJECTS */ + // ---- simulation models ---- + // WPILib DCMotorSim models approximate the motor's physics during simulation. + // These use the physical MOI and gear ratios from Constants so sim behavior + // roughly matches the real robot. + private final DCMotorSim m_intakeArmSim = new DCMotorSim( - LinearSystemId.createDCMotorSystem( - DCMotor.getKrakenX60Foc(1), - kIntakeArmMOI, - kIntakeArmGearing - ), + LinearSystemId.createDCMotorSystem(DCMotor.getKrakenX60Foc(1), kIntakeArmMOI, kIntakeArmGearing), DCMotor.getKrakenX60Foc(1) ); private final DCMotorSim m_intakeRollersSim = new DCMotorSim( - LinearSystemId.createDCMotorSystem( - DCMotor.getKrakenX60Foc(2), - kIntakeRollersMOI, - kIntakeRollersGearing - ), - DCMotor.getKrakenX60Foc(2) // returns gearbox + LinearSystemId.createDCMotorSystem(DCMotor.getKrakenX60Foc(2), kIntakeRollersMOI, kIntakeRollersGearing), + DCMotor.getKrakenX60Foc(2) ); - /* LOGGERS */ + // ---- loggers ---- + private final DoubleLogger log_intakeArmRots = WaltLogger.logDouble(kLogTab, "intakeArmRots"); private final DoubleLogger log_targetIntakeArmRots = WaltLogger.logDouble(kLogTab, "targetIntakeArmRots"); - private final DoubleLogger log_intakeRollersRPS = WaltLogger.logDouble(kLogTab, "intakeRollersRPS"); private final DoubleLogger log_targetIntakeRollersRPS = WaltLogger.logDouble(kLogTab, "targetIntakeRollersRPS"); - private final BooleanLogger log_isIntakeArmHomed = WaltLogger.logBoolean(kLogTab, "isIntakeArmHomed"); - /* CONSTRUCTOR */ + + // ============================================================= + // CONSTRUCTOR + // ============================================================= + public Intake() { m_intakeArm.getConfigurator().apply(kIntakeArmConfiguration); - m_intakeRollersA.getConfigurator().apply(kIntakeRollersAConfiguration); m_intakeRollersB.getConfigurator().apply(kIntakeRollersBConfiguration); + // Motor B mirrors motor A but spins in the opposite direction so both rollers + // push the ball the same way (they're physically mirrored on the robot). m_intakeRollersB.setControl(new Follower(kIntakeRollersA_CANID, MotorAlignmentValue.Opposed)); - SignalManager.register(kRioBus, sig_intakeArmStatorCurrent, sig_intakeArmVelo, sig_intakeRollersAVelo, sig_intakeArmPos, sig_intakeArmMMAtTarget); + SignalManager.register(kRioBus, + sig_intakeArmStatorCurrent, sig_intakeArmVelo, + sig_intakeRollersAVelo, sig_intakeArmPos, sig_intakeArmMMAtTarget); + // On the real robot, start homing immediately on startup. + // In sim, homing via current sensing doesn't work so we skip it. if (Robot.isReal()) { setDefaultCommand(intakeArmCurrentSenseHoming()); - // setDefaultCommand(intakeArmHome()); } initSim(); @@ -125,13 +168,19 @@ private void initSim() { WaltMotorSim.initSimFX(m_intakeRollersA, ChassisReference.CounterClockwise_Positive, TalonFXSimState.MotorType.KrakenX60); } - /* COMMANDS */ + + // ============================================================= + // ARM POSITION CONTROL + // ============================================================= + + // These set the arm to a named position from the IntakeArmPosition enum. + // Uses MotionMagic for smooth, profiled movement. public void setIntakeArmPos(IntakeArmPosition rots) { - setIntakeArmPos(rots.rots); // rots == IntakeArmPosition.RETRACTED ? 36 : 18 + setIntakeArmPos(rots.rots); } public Command setIntakeArmPosCmd(IntakeArmPosition rots) { - return setIntakeArmPosCmd(rots.rots); // rots == IntakeArmPosition.RETRACTED ? 36 : 18 + return setIntakeArmPosCmd(rots.rots); } public Command setIntakeArmPosCmd(Angle rots) { @@ -142,49 +191,70 @@ public void setIntakeArmPos(Angle rots) { m_intakeArm.setControl(m_MMVReq.withPosition(rots)); } + // True when the arm has stopped moving (velocity near zero), used as a + // "are we there yet" check during shimmy movements. public boolean isIntakeArmAtDest() { - return m_shimmyVeloIsNearZero.getAsBoolean(); + return m_shimmyVeloIsNearZero.getAsBoolean(); } - public void setIntakeArmNeutralMode(NeutralModeValue value) { + public void setIntakeArmNeutralMode(NeutralModeValue value) { m_intakeArm.setNeutralMode(value); } - public Command startIntakeRollers(double volts) { - return setIntakeRollersVelocityCmd(volts); - } - public Command stopIntakeRollers() { - return setIntakeRollersVelocityCmd(0); - } + // ============================================================= + // ROLLER CONTROL + // ============================================================= + // NOTE: despite the parameter name being "volts", this method actually does + // velocity control. The volts value is treated as a 0-12V percentage and + // mapped to a target RPS (0 V = 0 RPS, 12 V = max RPS). This gives a + // convenient voltage-like API while still benefiting from closed-loop control. public void setIntakeRollersVelocity(double volts) { - m_intakeRollersA.setControl(m_VelVoltReq.withVelocity(volts / 12 * kIntakeRollersMaxRPS.in(RotationsPerSecond))); ///kV = 0.488599348534 - // m_intakeRollersA.setControl(m_voltsReq.withOutput(volts)); + m_intakeRollersA.setControl(m_VelVoltReq.withVelocity(volts / 12 * kIntakeRollersMaxRPS.in(RotationsPerSecond))); } public Command setIntakeRollersVelocityCmd(double volts) { return runOnce(() -> setIntakeRollersVelocity(volts)); } - // TESTING TO SEE IF WE CAN JUST SAY 0 AS 0 - public Command intakeArmHome() { - return Commands.parallel( - Commands.sequence( - runOnce(() -> m_intakeArm.setPosition(0)), - - runOnce(() -> m_isIntakeArmHomed = true), - runOnce(() -> log_isIntakeArmHomed.accept(m_isIntakeArmHomed)), + public Command startIntakeRollers(double volts) { + return setIntakeRollersVelocityCmd(volts); + } - runOnce(() -> removeDefaultCommand()) - ) - ); + public Command stopIntakeRollers() { + return setIntakeRollersVelocityCmd(0); } + + // ============================================================= + // HOMING + // ============================================================= + + /* + * intakeArmCurrentSenseHoming + * + * Finds the arm's mechanical zero position without an absolute encoder. + * + * How it works: + * 1. Drive the arm slowly toward the hard stop with a small negative voltage (-3.25 V). + * 2. When the arm hits the stop, the motor stalls: current spikes AND velocity drops to zero. + * 3. Both conditions are debounced (held for 100/125 ms) to filter out false positives + * from momentary bumps or noise during movement. + * 4. Once both conditions are confirmed, zero the encoder at that position, mark homed, + * retract the arm, and remove this as the default command. + * + * The whole thing has a 3 s timeout so a broken sensor or stuck arm doesn't stall the robot. + * + * FunctionalCommand is WPILib's way of building a command inline from four lambda functions: + * init — runs once when the command starts + * execute — runs every loop while the command is active (nothing to do here) + * onEnd — runs once when the command ends (whether it finished or was interrupted) + * isFinished — returns true when the command should stop + */ public Command intakeArmCurrentSenseHoming() { Runnable init = () -> { - m_intakeArm.setControl(m_intakeArmZeroingReq.withOutput(-3.25)); - + m_intakeArm.setControl(m_intakeArmZeroingReq.withOutput(-3.25)); // drive slowly into the hard stop m_isIntakeArmHomed = false; log_isIntakeArmHomed.accept(m_isIntakeArmHomed); }; @@ -192,22 +262,30 @@ public Command intakeArmCurrentSenseHoming() { Runnable execute = () -> {}; Consumer onEnd = (Boolean interrupted) -> { - m_intakeArm.setControl(m_intakeArmZeroingReq.withOutput(0)); - m_intakeArm.setPosition(0); + m_intakeArm.setControl(m_intakeArmZeroingReq.withOutput(0)); // stop applying voltage + m_intakeArm.setPosition(0); // zero the encoder here removeDefaultCommand(); - setIntakeArmPosCmd(IntakeArmPosition.RETRACTED); + setIntakeArmPosCmd(IntakeArmPosition.RETRACTED); // move to safe position m_isIntakeArmHomed = true; log_isIntakeArmHomed.accept(m_isIntakeArmHomed); }; + // Both current spike AND near-zero velocity must be true for their respective debounce + // durations before we declare the arm is at the hard stop. BooleanSupplier isFinished = () -> m_currentDebouncer.calculate(m_currentSpike.getAsBoolean()) && m_velocityDebouncer.calculate(m_veloIsNearZero.getAsBoolean()); - return new FunctionalCommand(init, execute, onEnd, isFinished, this).withTimeout(3).withName("intakeArm homing"); + return new FunctionalCommand(init, execute, onEnd, isFinished, this) + .withTimeout(3) + .withName("intakeArm homing"); } - /* PERIODICS */ + + // ============================================================= + // PERIODIC + // ============================================================= + @Override public void periodic() { log_targetIntakeArmRots.accept(m_MMVReq.Position); @@ -218,12 +296,30 @@ public void periodic() { @Override public void simulationPeriodic() { - WaltMotorSim.updateSimFX(m_intakeArm, m_intakeArmSim); + WaltMotorSim.updateSimFX(m_intakeArm, m_intakeArmSim); WaltMotorSim.updateSimFX(m_intakeRollersA, m_intakeRollersSim); } - /* ENUMS */ - public enum IntakeArmPosition{ + + // ============================================================= + // ENUMS + // ============================================================= + + /* + * IntakeArmPosition + * + * Named positions for the intake arm. All values are stored as both + * degrees (for human readability) and rotations (for motor commands). + * Note: these are mechanism rotations (arm angle), not motor rotations — + * the 125:1 gear ratio is baked into the TalonFX feedback config so the + * controller reports arm angle directly. + * + * RETRACTED — arm is fully up, tucked inside the robot frame. Safe for driving. + * DEPLOYED — arm is fully down to collect balls off the ground. + * SHIMMY — partway down, used to agitate balls already inside the robot. + * SAFE — just above DEPLOYED, used when approaching a ball cautiously. + */ + public enum IntakeArmPosition { RETRACTED(Rotations.of(0.061514).in(Degrees)), DEPLOYED(Rotations.of(0.289062 * 0.86).in(Degrees)), SHIMMY(Rotations.of(0.126025).in(Degrees)), @@ -237,5 +333,4 @@ private IntakeArmPosition(double degs) { this.rots = Rotations.of(this.degs.in(Rotations)); } } - -} \ No newline at end of file +} diff --git a/src/main/java/frc/robot/subsystems/Superstructure.java b/src/main/java/frc/robot/subsystems/Superstructure.java index f0a71978..3382cc7e 100644 --- a/src/main/java/frc/robot/subsystems/Superstructure.java +++ b/src/main/java/frc/robot/subsystems/Superstructure.java @@ -1,7 +1,6 @@ package frc.robot.subsystems; -import edu.wpi.first.units.measure.AngularVelocity; import edu.wpi.first.wpilibj2.command.Command; import edu.wpi.first.wpilibj2.command.Commands; import edu.wpi.first.wpilibj2.command.SubsystemBase; @@ -15,22 +14,27 @@ import static frc.robot.Constants.IntakeK.kIntakeRollersShimmyVolts; import java.util.function.BooleanSupplier; -import java.util.function.Supplier; public class Superstructure extends SubsystemBase { - /* SUBSYSTEMS */ + // --- SUBSYSTEM REFERENCES --- private final Intake m_intake; private final Indexer m_indexer; private final Shooter m_shooter; - /* CONSTRUCTOR */ + // ====================================================================== + // CONSTRUCTOR + // ====================================================================== public Superstructure(Intake intake, Indexer indexer, Shooter shooter) { m_intake = intake; m_indexer = indexer; m_shooter = shooter; } - /* BUTTON BIND SEQUENCES */ + // ====================================================================== + // SEQUENCES + // ====================================================================== + + // --- INTAKE --- /** * @param isShooting is if the robot is shooting * @return the intake Command @@ -67,6 +71,9 @@ public Command retractIntake() { ); } + // ~~~~~~~~~~~~~~~~~ + // OUTTAKE + // ~~~~~~~~~~~~~~~~~ // /** // * Turns on spinner and exhaust and sets shooter speed to RPS. // *

@@ -124,6 +131,7 @@ public Command activateOuttakeShotCalc() { }); } + // --- HOOD --- public Command activateHoodShotCalc() { return m_shooter.hoodFromCalc() .finallyDo(() -> { @@ -131,6 +139,7 @@ public Command activateHoodShotCalc() { }); } + // --- FLYWHEEL --- public Command spinUpFlywheel() { return m_shooter.shootFromCalc() .finallyDo(() -> { @@ -138,6 +147,7 @@ public Command spinUpFlywheel() { }); } + // --- HOOD && FLYWHEEL --- public Command hoodAndFlywheelShotCalc() { return Commands.parallel( m_shooter.hoodFromCalc(), @@ -150,7 +160,7 @@ public Command hoodAndFlywheelShotCalc() { } /** - * deactivates the outtake + * Stops the tunnel, spindexer, and the shooter. */ public void deactivateOuttake() { m_indexer.stopSpindexer(); @@ -158,16 +168,26 @@ public void deactivateOuttake() { m_shooter.setShooterVelocity(ShooterK.kShooterZeroRPS); } + // --- FLYWHEEL --- + /** + * Sets the Flywheel to a speed of 0 which will CoastOut + */ public void turnOffFlywheel() { m_shooter.setShooterVelocity(ShooterK.kShooterZeroRPS); } + // --- HOOD --- + /** + * Sets the Hood back to a position that is able to go underneath the trench + */ public void hoodBackToSafe() { m_shooter.m_hood.setHoodPos(ShooterK.kHoodEmergencyRotsD); } /** - * @return the emergency barf command + * Command to get all balls out of the hopper, one way or another. + * This reverses the intake rollers, and shoots out all the balls at a set LOCKED turret pose. + * @return Command above */ public Command emergencyBarf() { return Commands.startEnd( @@ -191,7 +211,7 @@ public Command emergencyBarf() { /** - * @return the emergency barf command + * @return Command that reverses only the intake rollers */ public Command emergencyBarfOnlyIntake() { return Commands.startEnd( @@ -205,6 +225,11 @@ public Command emergencyBarfOnlyIntake() { ); } + /** + * Shimmies the intake up and down while running the intake rollers at a slower speed + * @param isShooting is the robot in a shooting state + * @return the shimmy command + */ public Command intakeShimmy(BooleanSupplier isShooting) { return Commands.repeatingSequence( intake(isShooting, () -> true).withTimeout(0.5), @@ -217,6 +242,8 @@ public Command intakeShimmy(BooleanSupplier isShooting) { } /** + * Runs the indexer in reverse, and if the robot is shooting, then the shooter flywheel WONT reverse, + * otherwise flywheel will reverse * @param isShooting is if the robot is currently shooting * @return the unjam Command */ diff --git a/src/main/java/frc/robot/subsystems/Swerve.java b/src/main/java/frc/robot/subsystems/Swerve.java index f4199435..80bf16fd 100644 --- a/src/main/java/frc/robot/subsystems/Swerve.java +++ b/src/main/java/frc/robot/subsystems/Swerve.java @@ -46,43 +46,80 @@ import frc.util.WaltLogger.DoubleLogger; import frc.util.WaltLogger.Pose2dLogger; -/** - * CommandSwerveDrivetrain: Class that extends the Phoenix 6 SwerveDrivetrain class - * and implements Subsystem so it can easily be used in command-based projects. +/* + * Swerve * - * Generated by the 2026 Tuner X Swerve Project Generator + * This is our drivetrain subsystem. It extends TunerSwerveDrivetrain (generated + * by CTRE Tuner X) and adds our own behavior on top of it. + * + * Quick primer on swerve drive (skip if you know it already): + * A swerve drivetrain has four independently steered + driven wheel modules. + * Each module can point in any direction, so the robot can strafe, spin, and + * drive at the same time. We use "field-centric" control so pushing "forward" + * on the joystick always moves toward the opposing alliance wall, regardless + * of which way the robot happens to be facing. + * + * What this class adds on top of the CTRE base: + * - Alliance-aware "operator perspective" so field-centric works correctly for + * both Red and Blue (they face opposite directions on the field). + * - Choreo path following: followPath() is called by Choreo each loop tick + * during auton to track a pre-planned trajectory. + * - Helper commands: xBrakeCmd, roboToPose, roboToRotation, etc. + * - Logging of chassis speeds and odometry data to AdvantageScope. + * - SysId support for characterizing the drive motors. + * + * The hardware config (module positions, gear ratios, PID gains, CAN IDs, etc.) + * lives in TunerConstants, generated by Tuner X: * https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/index.html */ public class Swerve extends TunerSwerveDrivetrain implements Subsystem { + + // ---- simulation ---- + // The CTRE drivetrain sim needs to run faster than the main 20 ms robot loop + // so its PID loops behave like they do on the real robot. A Notifier fires + // the sim update at 4 ms on its own thread. private static final double kSimLoopPeriod = 0.004; // 4 ms private Notifier m_simNotifier = null; private double m_lastSimTime; + // Converts individual module states (speed + direction) to/from overall robot speed. + // Used in periodic() to compute and log total chassis speed. public SwerveDriveKinematics m_kinematics = new SwerveDriveKinematics(getModuleLocations()); + private final StatusSignal sig_gyroRoll = getPigeon2().getRoll(); private final StatusSignal sig_gyroYaw = getPigeon2().getYaw(); - /* Blue alliance sees forward as 0 degrees (toward red alliance wall) */ + // ---- alliance perspective ---- + // Field-centric drive uses a fixed field coordinate frame. + // Blue alliance treats 0° as forward (toward the red wall). + // Red alliance treats 180° as forward (toward the blue wall). + // We update this in periodic() so it re-applies automatically if the robot + // code restarts mid-match. private static final Rotation2d kBlueAlliancePerspectiveRotation = Rotation2d.kZero; - /* Red alliance sees forward as 180 degrees (toward blue alliance wall) */ private static final Rotation2d kRedAlliancePerspectiveRotation = Rotation2d.k180deg; - /* Keep track if we've ever applied the operator perspective before or not */ private boolean m_hasAppliedOperatorPerspective = false; - /* Swerve requests to apply during SysId characterization */ - private final SwerveRequest.SysIdSwerveTranslation m_translationCharacterization = new SwerveRequest.SysIdSwerveTranslation(); - // private final SwerveRequest.SysIdSwerveSteerGains m_steerCharacterization = new SwerveRequest.SysIdSwerveSteerGains(); - // private final SwerveRequest.SysIdSwerveRotation m_rotationCharacterization = new SwerveRequest.SysIdSwerveRotation(); - + // ---- path following PID controllers ---- + // During Choreo auton, these three controllers correct for the robot drifting + // off the planned trajectory. Choreo provides feed-forward velocities from the + // trajectory itself, and these PIDs add a correction term on top based on the + // error between our actual pose and the desired pose. + // X/Y controllers - correct position error in meters + // Theta controller - correct heading error in radians, with continuous input + // so it wraps at ±π (takes the short way around) private final PIDController m_pathXController = new PIDController(4.69, 0, 0); private final PIDController m_pathYController = new PIDController(4.69, 0, 0); private final PIDController m_pathThetaController = new PIDController(4.68, 0, 0); + + // The swerve request that actually sends corrected speeds to the modules. + // Velocity + Position request types give better tracking accuracy than open-loop. private final SwerveRequest.ApplyFieldSpeeds m_pathApplyFieldSpeeds = new SwerveRequest.ApplyFieldSpeeds() .withDriveRequestType(DriveRequestType.Velocity) .withSteerRequestType(SteerRequestType.Position); private final Detection detection = new Detection(); + // ---- loggers ---- private final DoubleLogger log_absoluteRobotSpeed = new WaltLogger.DoubleLogger("Swerve", "absoluteRobotSpeed"); private final DoubleLogger log_vxMPS = new DoubleLogger("Swerve", "vxMPS"); private final DoubleLogger log_vyMPS = new DoubleLogger("Swerve", "vyMPS"); @@ -90,18 +127,24 @@ public class Swerve extends TunerSwerveDrivetrain implements Subsystem { private final DoubleLogger log_currentAutonTotalTime = new DoubleLogger("Swerve/Auton", "currentTotalTime"); private final DoubleLogger log_sampleAt_velocityX = new DoubleLogger("Swerve/Auton", "sampleAt_velocityX"); private final DoubleLogger log_sampleAt_velocityY = new DoubleLogger("Swerve/Auton", "sampleAt_velocityY"); - + // Field-centric drive request used by the pose-targeting helpers. + // BlueAlliance perspective means "forward" = toward red wall for both alliances. private final SwerveRequest.FieldCentric swreq_drive = new SwerveRequest.FieldCentric() .withForwardPerspective(ForwardPerspectiveValue.BlueAlliance); - /* SysId routine for characterizing translation. This is used to find PID gains for the drive motors. */ + // ---- SysId ---- + // SysId (System Identification) is a WPILib tool that characterizes the drive + // motors by logging their response to known voltage inputs, then computing + // feedforward and PID gains from the data. Only translation is currently active. + private final SwerveRequest.SysIdSwerveTranslation m_translationCharacterization = + new SwerveRequest.SysIdSwerveTranslation(); + private final SysIdRoutine m_sysIdRoutineTranslation = new SysIdRoutine( new SysIdRoutine.Config( - null, // Use default ramp rate (1 V/s) - Volts.of(4), // Reduce dynamic step voltage to 4 V to prevent brownout - null, // Use default timeout (10 s) - // Log state with SignalLogger class + null, // default ramp rate (1 V/s) + Volts.of(4), // 4 V step to avoid brownout + null, // default timeout (10 s) state -> SignalLogger.writeString("SysIdTranslation_State", state.toString()) ), new SysIdRoutine.Mechanism( @@ -111,114 +154,37 @@ public class Swerve extends TunerSwerveDrivetrain implements Subsystem { ) ); - // /* SysId routine for characterizing steer. This is used to find PID gains for the steer motors. */ - // private final SysIdRoutine m_sysIdRoutineSteer = new SysIdRoutine( - // new SysIdRoutine.Config( - // null, // Use default ramp rate (1 V/s) - // Volts.of(7), // Use dynamic voltage of 7 V - // null, // Use default timeout (10 s) - // // Log state with SignalLogger class - // state -> SignalLogger.writeString("SysIdSteer_State", state.toString()) - // ), - // new SysIdRoutine.Mechanism( - // volts -> setControl(m_steerCharacterization.withVolts(volts)), - // null, - // this - // ) - // ); - - // /* - // * SysId routine for characterizing rotation. - // * This is used to find PID gains for the FieldCentricFacingAngle HeadingController. - // * See the documentation of SwerveRequest.SysIdSwerveRotation for info on importing the log to SysId. - // */ - // private final SysIdRoutine m_sysIdRoutineRotation = new SysIdRoutine( - // new SysIdRoutine.Config( - // /* This is in radians per second², but SysId only supports "volts per second" */ - // Volts.of(Math.PI / 6).per(Second), - // /* This is in radians per second, but SysId only supports "volts" */ - // Volts.of(Math.PI), - // null, // Use default timeout (10 s) - // // Log state with SignalLogger class - // state -> SignalLogger.writeString("SysIdRotation_State", state.toString()) - // ), - // new SysIdRoutine.Mechanism( - // output -> { - // /* output is actually radians per second, but SysId only supports "volts" */ - // setControl(m_rotationCharacterization.withRotationalRate(output.in(Volts))); - // /* also log the requested output for SysId */ - // SignalLogger.writeDouble("Rotational_Rate", output.in(Volts)); - // }, - // null, - // this - // ) - // ); - - /* The SysId routine to test */ private SysIdRoutine m_sysIdRoutineToApply = m_sysIdRoutineTranslation; - /** - * Constructs a CTRE SwerveDrivetrain using the specified constants. - *

- * This constructs the underlying hardware devices, so users should not construct - * the devices themselves. If they need the devices, they can access them through - * getters in the classes. - * - * @param drivetrainConstants Drivetrain-wide constants for the swerve drive - * @param modules Constants for each specific module - */ + + // ============================================================= + // CONSTRUCTORS + // (generated by CTRE Tuner X — the hardware config lives in TunerConstants) + // ============================================================= + + /** Standard constructor — uses default odometry update frequency. */ public Swerve( SwerveDrivetrainConstants drivetrainConstants, SwerveModuleConstants... modules ) { super(drivetrainConstants, modules); - if (Utils.isSimulation()) { - startSimThread(); - } + if (Utils.isSimulation()) startSimThread(); } - /** - * Constructs a CTRE SwerveDrivetrain using the specified constants. - *

- * This constructs the underlying hardware devices, so users should not construct - * the devices themselves. If they need the devices, they can access them through - * getters in the classes. - * - * @param drivetrainConstants Drivetrain-wide constants for the swerve drive - * @param odometryUpdateFrequency The frequency to run the odometry loop. If - * unspecified or set to 0 Hz, this is 250 Hz on - * CAN FD, and 100 Hz on CAN 2.0. - * @param modules Constants for each specific module - */ + /** Constructor with a custom odometry update frequency. */ public Swerve( SwerveDrivetrainConstants drivetrainConstants, double odometryUpdateFrequency, SwerveModuleConstants... modules ) { super(drivetrainConstants, odometryUpdateFrequency, modules); - if (Utils.isSimulation()) { - startSimThread(); - } + if (Utils.isSimulation()) startSimThread(); } /** - * Constructs a CTRE SwerveDrivetrain using the specified constants. - *

- * This constructs the underlying hardware devices, so users should not construct - * the devices themselves. If they need the devices, they can access them through - * getters in the classes. - * - * @param drivetrainConstants Drivetrain-wide constants for the swerve drive - * @param odometryUpdateFrequency The frequency to run the odometry loop. If - * unspecified or set to 0 Hz, this is 250 Hz on - * CAN FD, and 100 Hz on CAN 2.0. - * @param odometryStandardDeviation The standard deviation for odometry calculation - * in the form [x, y, theta]ᵀ, with units in meters - * and radians - * @param visionStandardDeviation The standard deviation for vision calculation - * in the form [x, y, theta]ᵀ, with units in meters - * and radians - * @param modules Constants for each specific module + * Constructor with custom odometry frequency and separate standard deviations + * for wheel odometry vs. vision pose estimates. The Kalman filter uses these + * to decide how much to trust each source. */ public Swerve( SwerveDrivetrainConstants drivetrainConstants, @@ -228,91 +194,61 @@ public Swerve( SwerveModuleConstants... modules ) { super(drivetrainConstants, odometryUpdateFrequency, odometryStandardDeviation, visionStandardDeviation, modules); - if (Utils.isSimulation()) { - startSimThread(); - } + if (Utils.isSimulation()) startSimThread(); } - /** - * Returns a command that applies the specified control request to this swerve drivetrain. - * - * @param request Function returning the request to apply - * @return Command to run - */ - public Command applyRequest(Supplier request) { - return run(() -> this.setControl(request.get())); - } - /** - * Runs the SysId Quasistatic test in the given direction for the routine - * specified by {@link #m_sysIdRoutineToApply}. - * - * @param direction Direction of the SysId Quasistatic test - * @return Command to run - */ + // ============================================================= + // SYSID + // ============================================================= + + /** Runs the quasistatic (slow voltage ramp) SysId test. */ public Command sysIdQuasistatic(SysIdRoutine.Direction direction) { return m_sysIdRoutineToApply.quasistatic(direction); } - /** - * Runs the SysId Dynamic test in the given direction for the routine - * specified by {@link #m_sysIdRoutineToApply}. - * - * @param direction Direction of the SysId Dynamic test - * @return Command to run - */ + /** Runs the dynamic (step voltage) SysId test. */ public Command sysIdDynamic(SysIdRoutine.Direction direction) { return m_sysIdRoutineToApply.dynamic(direction); } - @Override - public void periodic() { - /* - * Periodically try to apply the operator perspective. - * If we haven't applied the operator perspective before, then we should apply it regardless of DS state. - * This allows us to correct the perspective in case the robot code restarts mid-match. - * Otherwise, only check and apply the operator perspective if the DS is disabled. - * This ensures driving behavior doesn't change until an explicit disable event occurs during testing. - */ - if (!m_hasAppliedOperatorPerspective || DriverStation.isDisabled()) { - WaltDriverStation.getAlliance().ifPresent(allianceColor -> { - setOperatorPerspectiveForward( - allianceColor == Alliance.Red - ? kRedAlliancePerspectiveRotation - : kBlueAlliancePerspectiveRotation - ); - m_hasAppliedOperatorPerspective = true; - }); - } - var state = getState(); - var speeds = m_kinematics.toChassisSpeeds(state.ModuleStates); - log_vxMPS.accept(speeds.vxMetersPerSecond); - log_vyMPS.accept(speeds.vyMetersPerSecond); - log_absoluteRobotSpeed.accept(Math.hypot(speeds.vxMetersPerSecond, speeds.vyMetersPerSecond)); - } + // ============================================================= + // CORE COMMANDS + // ============================================================= - private void startSimThread() { - m_lastSimTime = Utils.getCurrentTimeSeconds(); + /** + * Applies a swerve request every loop while the command runs. + * Most driver commands use this pattern: + * applyRequest(() -> swreq_drive.withVelocityX(x).withVelocityY(y)) + */ + public Command applyRequest(Supplier request) { + return run(() -> this.setControl(request.get())); + } - /* Run simulation at a faster rate so PID gains behave more reasonably */ - m_simNotifier = new Notifier(() -> { - final double currentTime = Utils.getCurrentTimeSeconds(); - double deltaTime = currentTime - m_lastSimTime; - m_lastSimTime = currentTime; + /** + * Locks all four wheel modules into an X-pattern (each pointing 45° outward). + * This creates a passive brake — the robot resists being pushed in any direction. + * Used at the end of auton so the robot doesn't slide after stopping. + */ + public Command xBrakeCmd() { + final SwerveRequest.SwerveDriveBrake stopReq = new SwerveDriveBrake(); + return runOnce(() -> setControl(stopReq)); + } - /* use the measured time delta, get battery voltage from WPILib */ - updateSimState(deltaTime, RobotController.getBatteryVoltage()); - }); - m_simNotifier.startPeriodic(kSimLoopPeriod); + /** Returns the robot's current velocity as vx, vy, omega (ChassisSpeeds). */ + public ChassisSpeeds getChassisSpeeds() { + return m_kinematics.toChassisSpeeds(getState().ModuleStates); } + + // ============================================================= + // VISION + // ============================================================= + /** - * Adds a vision measurement to the Kalman Filter. This will correct the odometry pose estimate - * while still accounting for measurement noise. - * - * @param visionRobotPoseMeters The pose of the robot as measured by the vision camera. - * @param timestampSeconds The timestamp of the vision measurement in seconds. + * Fuses a PhotonVision pose measurement into the Kalman filter odometry. + * The FPGA timestamp conversion corrects for processing latency. */ @Override public void addVisionMeasurement(Pose2d visionRobotPoseMeters, double timestampSeconds) { @@ -320,17 +256,8 @@ public void addVisionMeasurement(Pose2d visionRobotPoseMeters, double timestampS } /** - * Adds a vision measurement to the Kalman Filter. This will correct the odometry pose estimate - * while still accounting for measurement noise. - *

- * Note that the vision measurement standard deviations passed into this method - * will continue to apply to future measurements until a subsequent call to - * {@link #setVisionMeasurementStdDevs(Matrix)} or this method. - * - * @param visionRobotPoseMeters The pose of the robot as measured by the vision camera. - * @param timestampSeconds The timestamp of the vision measurement in seconds. - * @param visionMeasurementStdDevs Standard deviations of the vision pose measurement - * in the form [x, y, theta]ᵀ, with units in meters and radians. + * Same as above but also updates the vision standard deviations, which control + * how much the filter trusts this measurement relative to wheel odometry. */ @Override public void addVisionMeasurement( @@ -342,38 +269,38 @@ public void addVisionMeasurement( } /** - * Return the pose at a given timestamp, if the buffer is not empty. - * - * @param timestampSeconds The timestamp of the pose in seconds. - * @return The pose at the given timestamp (or Optional.empty() if the buffer is empty). + * Looks up the estimated robot pose at a past timestamp by interpolating the + * odometry history buffer. Useful for matching vision detections to where the + * robot was when the camera frame was actually captured. */ @Override public Optional samplePoseAt(double timestampSeconds) { return super.samplePoseAt(Utils.fpgaToCurrentTime(timestampSeconds)); } - public ChassisSpeeds getChassisSpeeds() { - return m_kinematics.toChassisSpeeds(getState().ModuleStates); - } + // ============================================================= + // PATH FOLLOWING (CHOREO) + // ============================================================= + + // Called by Choreo every loop tick during auton. + // The SwerveSample has the desired pose + chassis speeds at this trajectory timestamp. + // We take Choreo's feed-forward velocities and add PID correction on top + // to stay on the planned path even if the robot drifts slightly. private void followPath(SwerveSample sample) { - m_pathThetaController.enableContinuousInput(-Math.PI, Math.PI); - var pose = getState().Pose; - // var samplePose = sample.getPose(); + m_pathThetaController.enableContinuousInput(-Math.PI, Math.PI); // wrap heading at ±π - // var speed = getState().Speeds; - var targetSpeeds = sample.getChassisSpeeds(); + var pose = getState().Pose; + var targetSpeeds = sample.getChassisSpeeds(); // feed-forward from Choreo - targetSpeeds.vxMetersPerSecond += m_pathXController.calculate( - pose.getX(), sample.x - ); - targetSpeeds.vyMetersPerSecond += m_pathYController.calculate( - pose.getY(), sample.y - ); + // Add PID correction for each axis of error. + targetSpeeds.vxMetersPerSecond += m_pathXController.calculate(pose.getX(), sample.x); + targetSpeeds.vyMetersPerSecond += m_pathYController.calculate(pose.getY(), sample.y); targetSpeeds.omegaRadiansPerSecond += m_pathThetaController.calculate( - pose.getRotation().getRadians(), sample.heading - ); + pose.getRotation().getRadians(), sample.heading); + // Apply to the swerve modules, including per-module force feedforwards from + // Choreo for better tracking on aggressive paths. setControl( m_pathApplyFieldSpeeds.withSpeeds(targetSpeeds) .withWheelForceFeedforwardsX(sample.moduleForcesX()) @@ -381,22 +308,17 @@ private void followPath(SwerveSample sample) { ); } - public Command xBrakeCmd() { - final SwerveRequest.SwerveDriveBrake stopReq = new SwerveDriveBrake(); - return runOnce(() -> setControl(stopReq)); - } - - /** - * Creates a new auto factory for this drivetrain. - * - * @return AutoFactory for this drivetrain + * Creates the Choreo AutoFactory used by WaltAdaptableAutonFactory. + * The trajectory logger fires at the start/end of each trajectory and + * logs a sample at t=4.25 s for pre-match path verification. */ public AutoFactory createAutoFactory() { return createAutoFactory((traj, isStart) -> { SwerveSample sample = traj.sampleAt(4.25, false).get(); WaltLogger.timedPrint(String.format("TrajLog - isStart: %b", isStart)); - WaltLogger.timedPrint("Sample at 4.25s (PRE BUMP); X:" + sample.getPose().getX() + " Y:" + traj.sampleAt(4.25, false).get().getPose().getY()); + WaltLogger.timedPrint("Sample at 4.25s (PRE BUMP); X:" + sample.getPose().getX() + + " Y:" + traj.sampleAt(4.25, false).get().getPose().getY()); log_sampleAt_pose.accept(sample.getPose()); log_sampleAt_velocityX.accept(sample.vx); log_sampleAt_velocityY.accept(sample.vy); @@ -404,69 +326,54 @@ public AutoFactory createAutoFactory() { }); } - /** - * Creates a new auto factory for this drivetrain with the given - * trajectory logger. - * - * @param trajLogger Logger for the trajectory - * @return AutoFactory for this drivetrain - */ + /** Creates a Choreo AutoFactory with a custom trajectory logger. */ public AutoFactory createAutoFactory(TrajectoryLogger trajLogger) { return new AutoFactory( () -> getState().Pose, this::resetPose, this::followPath, - true, + true, // auto-flip trajectories for red alliance this, trajLogger ); } + + // ============================================================= + // POSE-TARGETING HELPERS + // ============================================================= + // Simple PID-based "go to this pose" commands used outside of Choreo auton + // (e.g. auto-alignment, short position corrections). Not as smooth as a + // full Choreo path but useful for quick corrections. + /** - * @param desPose Posd2d to move to - * @return a Command that makes the robot move to the desired Pose2d + * Drives the robot to a target Pose2d using the path PID controllers. + * Stops once within `tolerance` meters of the target translation. */ public Command roboToPose(Pose2d desPose, double tolerance) { return Commands.runOnce(() -> { Pose2d curPose = getState().Pose; double xSpeed = m_pathXController.calculate(curPose.getX(), desPose.getX()); double ySpeed = m_pathYController.calculate(curPose.getY(), desPose.getY()); - double thetaSpeed = m_pathThetaController.calculate(curPose.getRotation().getRadians(), desPose.getRotation().getRadians()); + double thetaSpeed = m_pathThetaController.calculate( + curPose.getRotation().getRadians(), desPose.getRotation().getRadians()); setControl(swreq_drive.withVelocityX(xSpeed).withVelocityY(ySpeed).withRotationalRate(thetaSpeed)); }).andThen(Commands.waitUntil(() -> isNearPose(getState().Pose, desPose, tolerance))) .andThen(() -> setControl(swreq_drive.withVelocityX(0).withVelocityY(0).withRotationalRate(0))); } - public boolean isNearPose(Pose2d curPose, Pose2d desPose, double translationTolerance, double rotationTolerance) { - return isNearTranslation(curPose.getTranslation(), desPose.getTranslation(), translationTolerance) - && isNearRotation(curPose.getRotation(), desPose.getRotation(), rotationTolerance); - } - - public boolean isNearPose(Pose2d curPose, Pose2d desPose, double translationTolerance) { - return isNearTranslation(curPose.getTranslation(), desPose.getTranslation(), translationTolerance); - } - - /** - * @param desRotation Rotation2d to turn to - * @return a Command that makes the robot turn to the desired Rotation2d - */ + /** Rotates to a target heading only (no translation). Stops within `tolerance` radians. */ public Command roboToRotation(Rotation2d desRotation, double tolerance) { return Commands.runOnce(() -> { Rotation2d curRotation = getState().Pose.getRotation(); - double thetaSpeed = m_pathThetaController.calculate(curRotation.getRadians(), desRotation.getRadians()); + double thetaSpeed = m_pathThetaController.calculate( + curRotation.getRadians(), desRotation.getRadians()); setControl(swreq_drive.withRotationalRate(thetaSpeed)); }).andThen(Commands.waitUntil(() -> isNearRotation(getState().Pose.getRotation(), desRotation, tolerance))) .andThen(() -> setControl(swreq_drive.withRotationalRate(0))); } - public boolean isNearRotation(Rotation2d curRotation, Rotation2d desRotation, double tolerance) { - return Math.abs(MathUtil.angleModulus(curRotation.getRadians() - desRotation.getRadians())) <= tolerance; - } - - /** - * @param desTranslation Translation2d to go to - * @return a Command that makes the robot go to the desired Translation2d - */ + /** Translates to a target (x, y) only (no rotation). Stops within `tolerance` meters. */ public Command roboToTranslation(Translation2d desTranslation, double tolerance) { return Commands.runOnce(() -> { Translation2d curTranslation = getState().Pose.getTranslation(); @@ -477,6 +384,22 @@ public Command roboToTranslation(Translation2d desTranslation, double tolerance) .andThen(() -> setControl(swreq_drive.withVelocityX(0).withVelocityY(0).withRotationalRate(0))); } + // ---- proximity checks ---- + + public boolean isNearPose(Pose2d curPose, Pose2d desPose, double translationTolerance, double rotationTolerance) { + return isNearTranslation(curPose.getTranslation(), desPose.getTranslation(), translationTolerance) + && isNearRotation(curPose.getRotation(), desPose.getRotation(), rotationTolerance); + } + + public boolean isNearPose(Pose2d curPose, Pose2d desPose, double translationTolerance) { + return isNearTranslation(curPose.getTranslation(), desPose.getTranslation(), translationTolerance); + } + + public boolean isNearRotation(Rotation2d curRotation, Rotation2d desRotation, double tolerance) { + // angleModulus wraps the difference into (-π, π] so we always measure the shorter arc. + return Math.abs(MathUtil.angleModulus(curRotation.getRadians() - desRotation.getRadians())) <= tolerance; + } + public boolean isNearTranslation(Translation2d curTranslation, Translation2d desTranslation, double tolerance) { return Math.hypot( desTranslation.getX() - curTranslation.getX(), @@ -484,27 +407,75 @@ public boolean isNearTranslation(Translation2d curTranslation, Translation2d des ) <= tolerance; } - /** - * robot goes to detected target - */ - public Command swerveToObject() { + + // ============================================================= + // VISION-BASED OBJECT TRACKING + // ============================================================= + + /** Drives to the closest detected object (ball) on the field. */ + public Command swerveToObject() { PhotonTrackedTarget target = detection.getClosestObject(); Pose2d destination = detection.targetToPose(getState().Pose, target); detection.addFuel(destination); - return roboToPose(destination, 0.1); } + /** + * Computes the robot pose needed to face a fuel location with the intake. + * Moves the robot to the ball's (x, y) and rotates it so the intake side + * faces the ball — hence the +180° offset on the approach angle. + */ public static Pose2d faceFuelPose(Pose2d robotPose, Pose2d fuelLocation) { double dx = fuelLocation.getX() - robotPose.getX(); double dy = fuelLocation.getY() - robotPose.getY(); - Rotation2d desiredRotation = new Rotation2d(Math.atan2(dy, dx)).plus(Rotation2d.fromDegrees(180)); + return new Pose2d(fuelLocation.getX(), fuelLocation.getY(), desiredRotation); + } - return new Pose2d( - fuelLocation.getX(), - fuelLocation.getY(), - desiredRotation - ); + + // ============================================================= + // PERIODIC + // ============================================================= + + @Override + public void periodic() { + // Apply the alliance perspective so field-centric drive works correctly. + // We re-check every loop while disabled (in case the DS assigns an alliance + // late) but stop checking once enabled so perspective doesn't flip mid-match. + if (!m_hasAppliedOperatorPerspective || DriverStation.isDisabled()) { + WaltDriverStation.getAlliance().ifPresent(allianceColor -> { + setOperatorPerspectiveForward( + allianceColor == Alliance.Red + ? kRedAlliancePerspectiveRotation + : kBlueAlliancePerspectiveRotation + ); + m_hasAppliedOperatorPerspective = true; + }); + } + + var state = getState(); + var speeds = m_kinematics.toChassisSpeeds(state.ModuleStates); + log_vxMPS.accept(speeds.vxMetersPerSecond); + log_vyMPS.accept(speeds.vyMetersPerSecond); + log_absoluteRobotSpeed.accept(Math.hypot(speeds.vxMetersPerSecond, speeds.vyMetersPerSecond)); + } + + + // ============================================================= + // SIMULATION + // ============================================================= + + // Runs the CTRE drivetrain simulation at 4 ms intervals on a background thread. + // The faster rate is necessary for the simulated PID loops to behave like real hardware. + private void startSimThread() { + m_lastSimTime = Utils.getCurrentTimeSeconds(); + + m_simNotifier = new Notifier(() -> { + final double currentTime = Utils.getCurrentTimeSeconds(); + double deltaTime = currentTime - m_lastSimTime; + m_lastSimTime = currentTime; + updateSimState(deltaTime, RobotController.getBatteryVoltage()); + }); + m_simNotifier.startPeriodic(kSimLoopPeriod); } } diff --git a/src/main/java/frc/robot/subsystems/shooter/Shooter.java b/src/main/java/frc/robot/subsystems/shooter/Shooter.java index 4b182299..875f6df3 100644 --- a/src/main/java/frc/robot/subsystems/shooter/Shooter.java +++ b/src/main/java/frc/robot/subsystems/shooter/Shooter.java @@ -180,7 +180,7 @@ public Shooter(Supplier poseSupplier, Supplier threads } // ============================================================= - // FLYWHEEL CONTROL + // FLYWHEEL CONTROL and other misc // ============================================================= public Command setShooterVelocityCmd(AngularVelocity RPS) { From dc7ba0b87887c0440504dec26d35f55f17b9aac3 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Mon, 8 Jun 2026 16:55:43 -0400 Subject: [PATCH 24/25] shootercalc renamed --- .gitignore | 2 + src/main/java/frc/robot/README.md | 2 +- src/main/java/frc/robot/Robot.java | 2 +- .../frc/robot/subsystems/shooter/README.MD | 8 ++-- .../frc/robot/subsystems/shooter/Shooter.java | 16 +++---- .../robot/subsystems/shooter/calc/README.MD | 10 ++-- .../{ShooterCalc.java => ShotCalcMath.java} | 10 ++-- .../shooter/calc/ShotCalculator.java | 6 +-- .../subsystems/shooter/ShotCalcPerfTest.java | 4 +- .../subsystems/shooter/ShotCalcTest.java | 48 +++++++++---------- 10 files changed, 55 insertions(+), 53 deletions(-) rename src/main/java/frc/robot/subsystems/shooter/calc/{ShooterCalc.java => ShotCalcMath.java} (98%) diff --git a/.gitignore b/.gitignore index d4de2d76..9bfac946 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ simgui-ds.json simgui-window.json simgui.json networktables.json +bin/main/choreo/README.md +bin/main/frc/robot/README.md diff --git a/src/main/java/frc/robot/README.md b/src/main/java/frc/robot/README.md index ab37d5be..31fc0db1 100644 --- a/src/main/java/frc/robot/README.md +++ b/src/main/java/frc/robot/README.md @@ -59,7 +59,7 @@ The fun part. I had SO much fun on this part :D this is where it hit the fan :scream: :scream: :scream: -- `ShooterCalc.java`: Shot math and distance-based calculations on its own thread. +- `ShotCalcMath.java`: Shot math and distance-based calculations on its own thread. - `ShotCalculator.java`: Other shot math and distance-based calculations. #### Other subsystems diff --git a/src/main/java/frc/robot/Robot.java b/src/main/java/frc/robot/Robot.java index 8a962434..dccf3ee3 100644 --- a/src/main/java/frc/robot/Robot.java +++ b/src/main/java/frc/robot/Robot.java @@ -259,8 +259,8 @@ public Robot() { // ============================================================= /** - * * @param speedMult how much you want to limit speed as a decimal percentage of kMaxTranslation. 1 does nothing + * @param rotationMult how much you want to limit rotational-speed as a decimal percentage of kMaxAngularRPS. 1 does nothing. * @return swerve drive command */ private Command driveCommand(double speedMult, double rotationMult) { diff --git a/src/main/java/frc/robot/subsystems/shooter/README.MD b/src/main/java/frc/robot/subsystems/shooter/README.MD index 7513c094..b3ef575e 100644 --- a/src/main/java/frc/robot/subsystems/shooter/README.MD +++ b/src/main/java/frc/robot/subsystems/shooter/README.MD @@ -18,7 +18,7 @@ All of the actual shot math lives in [`calc/`](calc/), which runs on a backgroun ## Shooter.java -The main subsystem. Two Kraken X44s spin a flywheel, with Motor B following Motor A in `Opposed` mode (they spin opposite directions but push the ball the same way). `Shooter` then drives `Hood` and `Turret` with setpoints received from `ShooterCalc`, which runs at 75 Hz on its own thread. +The main subsystem. Two Kraken X44s spin a flywheel, with Motor B following Motor A in `Opposed` mode (they spin opposite directions but push the ball the same way). `Shooter` then drives `Hood` and `Turret` with setpoints received from `ShotCalcMath`, which runs at 75 Hz on its own thread. ### Flywheel Control @@ -61,7 +61,7 @@ It's not being used rn but if we need to cope harder this is what we do :sunglas ### What periodic() Does Every loop: -1. Caches turret position into a volatile field (ShooterCalc reads this from its thread). +1. Caches turret position into a volatile field (ShotCalcMath reads this from its thread). 2. Fetches the latest `ShotCalcOutputs` from the background thread. 3. Sends turret position + velocity feedforward to `Turret`. 4. Sends hood reference to `Hood` (if homed). @@ -102,7 +102,7 @@ This runs on construction (`homeTurret(true)`) and also every loop for logging. ### Snapback Logic -The turret has soft limits but also a physical range. If the turret is commanded near one limit (e.g., it's at +0.4 rot heading toward +0.5 rot max) and the target wraps around the other side, `calcAzimuth` (in ShooterCalc) adds ±1 full rotation to the setpoint so the turret goes the short way around instead of slamming into a hardstop. `m_isSnappingBack` flags when the error is large enough that this is actively happening. +The turret has soft limits but also a physical range. If the turret is commanded near one limit (e.g., it's at +0.4 rot heading toward +0.5 rot max) and the target wraps around the other side, `calcAzimuth` (in ShotCalcMath) adds ±1 full rotation to the setpoint so the turret goes the short way around instead of slamming into a hardstop. `m_isSnappingBack` flags when the error is large enough that this is actively happening. ### Key Methods @@ -145,7 +145,7 @@ boolean atPosition() // within kHoodMaxErrD of setpoint ## How It All Fits Together ``` -75 Hz Thread (ShooterCalc) +75 Hz Thread (ShotCalcMath) reads: swerve state, volatile turret position writes: volatile ShotCalcOutputs diff --git a/src/main/java/frc/robot/subsystems/shooter/Shooter.java b/src/main/java/frc/robot/subsystems/shooter/Shooter.java index 875f6df3..eafa672c 100644 --- a/src/main/java/frc/robot/subsystems/shooter/Shooter.java +++ b/src/main/java/frc/robot/subsystems/shooter/Shooter.java @@ -34,8 +34,8 @@ import java.util.function.Supplier; import frc.robot.Constants; -import frc.robot.subsystems.shooter.calc.ShooterCalc; -import frc.robot.subsystems.shooter.calc.ShooterCalc.ShotCalcOutputs; +import frc.robot.subsystems.shooter.calc.ShotCalcMath; +import frc.robot.subsystems.shooter.calc.ShotCalcMath.ShotCalcOutputs; import frc.util.SignalManager; import frc.util.WaltMotorSim; import frc.util.WaltTunable; @@ -45,7 +45,7 @@ /* * Shooter - flywheel velocity control + Hood + Turret coordination. - * Owns ShooterCalc (background 75 Hz thread) and pushes its outputs to each subsystem every periodic. + * Owns ShotCalcMath (background 75 Hz thread) and pushes its outputs to each subsystem every periodic. * Ball detection works by watching flywheel acceleration drop while in VelocityTorqueCurrentFOC mode. * * Vocab: @@ -76,7 +76,7 @@ public class Shooter extends SubsystemBase { private final Supplier m_threadsafeSwerveSup; public final Hood m_hood; public final Turret m_turret; - private final ShooterCalc m_shooterCalc; + private final ShotCalcMath m_ShotCalcMath; // ---- RUNTIME STATE ---- private final Tracer m_periodicTracer = new Tracer(); @@ -92,7 +92,7 @@ public class Shooter extends SubsystemBase { // private final FuelSim m_fuelSim; // ---- CALC STATE ---- - // read by ShooterCalc background thread - volatile so reads don't go stale + // read by ShotCalcMath background thread - volatile so reads don't go stale // thread co(p)de so that we don't overrun (syscore save us all please) private volatile double m_latestTurretPositionRots = 0.0; private double m_calcFlywheelVelocityRotPerSec = kShooterRPSd; @@ -148,9 +148,9 @@ public Shooter(Supplier poseSupplier, Supplier threads m_hood = new Hood(); m_turret = new Turret(); m_threadsafeSwerveSup = threadsafeSwerveStateSup; - m_shooterCalc = new ShooterCalc(m_threadsafeSwerveSup, () -> m_latestTurretPositionRots); + m_ShotCalcMath = new ShotCalcMath(m_threadsafeSwerveSup, () -> m_latestTurretPositionRots); - m_shooterCalc.shouldUseStaticShot(kUseStaticShot); + m_ShotCalcMath.shouldUseStaticShot(kUseStaticShot); m_shooterA.getConfigurator().apply(kShooterATalonFXConfiguration); m_shooterB.getConfigurator().apply(kShooterBTalonFXConfiguration); @@ -338,7 +338,7 @@ public void periodic() { m_currentFlywheelVelocityRotPerSec = sig_shooterAVelo.getValueAsDouble(); m_latestFlywheelAccelerationRotPerSec = sig_shooterAAccel.getValueAsDouble(); - ShotCalcOutputs calcData = m_shooterCalc.getLatestShotCalcOutputs(); + ShotCalcOutputs calcData = m_ShotCalcMath.getLatestShotCalcOutputs(); m_periodicTracer.addEpoch("Stashing ShotCalc data"); log_shooterClosedLoopError.accept(sig_shooterCLErr.getValueAsDouble()); diff --git a/src/main/java/frc/robot/subsystems/shooter/calc/README.MD b/src/main/java/frc/robot/subsystems/shooter/calc/README.MD index 319c6e63..fd1f822e 100644 --- a/src/main/java/frc/robot/subsystems/shooter/calc/README.MD +++ b/src/main/java/frc/robot/subsystems/shooter/calc/README.MD @@ -2,22 +2,22 @@ This is where it all hit the fan :sob: :sob: :sob: -This folder handles all of the math that turns "robot is at this position going this fast" into "set flywheel to X RPS, hood to Y rotations, turret to Z degrees." It runs on a **background thread** at 75 Hz so none of this touches the main robot loop. Two classes: `ShooterCalc` coordinates everything and manages the thread, and `ShotCalculator` contains the actual ballistics: tables, physics, iterative moving-shot convergence, the whole deal. +This folder handles all of the math that turns "robot is at this position going this fast" into "set flywheel to X RPS, hood to Y rotations, turret to Z degrees." It runs on a **background thread** at 75 Hz so none of this touches the main robot loop. Two classes: `ShotCalcMath` coordinates everything and manages the thread, and `ShotCalculator` contains the actual ballistics: tables, physics, iterative moving-shot convergence, the whole deal. --- ## Files -- `ShooterCalc.java`: The orchestrator. Runs the background Notifier, determines which target to aim at, calls into `ShotCalculator` for shot parameters, computes turret azimuth, and stores results in volatile fields for `Shooter.periodic()` to read. +- `ShotCalcMath.java`: The orchestrator. Runs the background Notifier, determines which target to aim at, calls into `ShotCalculator` for shot parameters, computes turret azimuth, and stores results in volatile fields for `Shooter.periodic()` to read. - `ShotCalculator.java`: Pure ballistics utility. Contains the interpolation tables, physics equations, and the iterative moving-shot algorithm. No state of its own (mostly static methods and final records). --- -## ShooterCalc.java +## ShotCalcMath.java ### Threading -`ShooterCalc` uses a WPILib `Notifier` to run `calcCallback()` at 75 Hz on a background thread: +`ShotCalcMath` uses a WPILib `Notifier` to run `calcCallback()` at 75 Hz on a background thread: ```java m_notifier.startPeriodic(Hertz.of(75)); // runs calcCallback() in a loop @@ -31,7 +31,7 @@ private static volatile Translation3d m_aimTarget; private static volatile boolean m_isPassingFlag; ``` -`Shooter.periodic()` caches the turret position into a `volatile double` at the top of every loop, and ShooterCalc reads this from its thread. This is intentional and noted in the code: do not move that cache line. +`Shooter.periodic()` caches the turret position into a `volatile double` at the top of every loop, and ShotCalcMath reads this from its thread. This is intentional and noted in the code: do not move that cache line. ### What calcCallback() Does Every Cycle diff --git a/src/main/java/frc/robot/subsystems/shooter/calc/ShooterCalc.java b/src/main/java/frc/robot/subsystems/shooter/calc/ShotCalcMath.java similarity index 98% rename from src/main/java/frc/robot/subsystems/shooter/calc/ShooterCalc.java rename to src/main/java/frc/robot/subsystems/shooter/calc/ShotCalcMath.java index 19f21465..68d47fe9 100644 --- a/src/main/java/frc/robot/subsystems/shooter/calc/ShooterCalc.java +++ b/src/main/java/frc/robot/subsystems/shooter/calc/ShotCalcMath.java @@ -36,7 +36,7 @@ import frc.util.WaltTunable; /* - * ShooterCalc + * ShotCalcMath * * All the shot math runs here, on a background thread via WPILib's Notifier, * so it never eats into the main 50 Hz robot loop. The thread runs at 75 Hz @@ -54,10 +54,10 @@ * the short way around instead of slamming into the limit. * * One important rule: Shooter.periodic() caches the turret position into a - * volatile double at the top of every loop. ShooterCalc reads it from the + * volatile double at the top of every loop. ShotCalcMath reads it from the * background thread. Don't move that line or the thread safety breaks. */ -public class ShooterCalc { +public class ShotCalcMath { private static final String kLogTab = "ShotCalc"; // Tunable lateral bias correction. Balls curve left/right depending on which way @@ -144,7 +144,7 @@ public class ShooterCalc { // CONSTRUCTOR // ============================================================= - public ShooterCalc(Supplier threadsafeSwerveDriveStateSup, DoubleSupplier turretPosSup) { + public ShotCalcMath(Supplier threadsafeSwerveDriveStateSup, DoubleSupplier turretPosSup) { m_threadsafeSwerveDriveStateSup = threadsafeSwerveDriveStateSup; m_turretPosRotsSup = turretPosSup; @@ -153,7 +153,7 @@ public ShooterCalc(Supplier threadsafeSwerveDriveStateSup, Dou AzimuthCalcDetails emptyAzimuth = new AzimuthCalcDetails(0, 0, 0, 0, 0, 0, 0); m_shotCalcOutputs = new ShotCalcOutputs(emptyAzimuth, emptyShotData, 0, 0, 0); - m_notifier.setName("ShooterCalc"); + m_notifier.setName("ShotCalcMath"); m_notifier.startPeriodic(Hertz.of(75)); // 2x slower than robot loop } diff --git a/src/main/java/frc/robot/subsystems/shooter/calc/ShotCalculator.java b/src/main/java/frc/robot/subsystems/shooter/calc/ShotCalculator.java index f2787189..4161941c 100644 --- a/src/main/java/frc/robot/subsystems/shooter/calc/ShotCalculator.java +++ b/src/main/java/frc/robot/subsystems/shooter/calc/ShotCalculator.java @@ -441,8 +441,8 @@ public static ShotDataLerp iterativeMovingShotFromInterpolationMap(Pose2d robot, double vyLaunch = vy + (turretX - robotX) * omega; double distance = getDistanceToTargetM(robotX, robotY, headingRad, targetX, targetY); - boolean passing = ShooterCalc.isPassing().getAsBoolean(); - // boolean canTurretShoot = ShooterCalc.canTurretShoot(); + boolean passing = ShotCalcMath.isPassing().getAsBoolean(); + // boolean canTurretShoot = ShotCalcMath.canTurretShoot(); // ShotLerpTable shotTable = passing ? (canTurretShoot ? kPassingTable : kAngryTurretTable) : kShotTable; ShotLerpTable shotTable = passing ? kPassingTable : kShotTable; @@ -482,7 +482,7 @@ public static ShotDataLerp iterativeMovingShotFromInterpolationMap(Pose2d robot, predY = targetY - vyLaunch * driftT; distance = getDistanceToTargetM(robotX, robotY, headingRad, predX, predY); - passing = ShooterCalc.isPassing().getAsBoolean(); + passing = ShotCalcMath.isPassing().getAsBoolean(); // shotTable = passing ? (canTurretShoot ? kPassingTable : kAngryTurretTable) : kShotTable; shotTable = passing ? kPassingTable : kShotTable; exitVel = shotTable.exitVelocity(distance); diff --git a/src/test/java/frc/robot/subsystems/shooter/ShotCalcPerfTest.java b/src/test/java/frc/robot/subsystems/shooter/ShotCalcPerfTest.java index 0e899617..45cab6a0 100644 --- a/src/test/java/frc/robot/subsystems/shooter/ShotCalcPerfTest.java +++ b/src/test/java/frc/robot/subsystems/shooter/ShotCalcPerfTest.java @@ -380,8 +380,8 @@ void benchmarkComparison() { System.out.println(" Raw Doubles: 1 object (the double[] result array)"); System.out.println(); - // Print at 25Hz (the ShooterCalc Notifier rate) - System.out.println("At 25Hz ShooterCalc rate:"); + // Print at 25Hz (the ShotCalcMath Notifier rate) + System.out.println("At 25Hz ShotCalcMath rate:"); System.out.printf(" Immutable Units: %.1f us/cycle (%.1f%% of 40ms budget)%n", immutableUs, immutableUs / 40000.0 * 100); System.out.printf(" Mutable Units: %.1f us/cycle (%.1f%% of 40ms budget)%n", diff --git a/src/test/java/frc/robot/subsystems/shooter/ShotCalcTest.java b/src/test/java/frc/robot/subsystems/shooter/ShotCalcTest.java index c53fb1fa..59140725 100644 --- a/src/test/java/frc/robot/subsystems/shooter/ShotCalcTest.java +++ b/src/test/java/frc/robot/subsystems/shooter/ShotCalcTest.java @@ -12,9 +12,9 @@ import frc.robot.subsystems.shooter.calc.ShotCalculator.ShotData; import frc.robot.subsystems.shooter.calc.ShotCalculator.ShotDataLerp; -import frc.robot.subsystems.shooter.calc.ShooterCalc; -import frc.robot.subsystems.shooter.calc.ShooterCalc.AzimuthCalcDetails; -import frc.robot.subsystems.shooter.calc.ShooterCalc.ShotCalcOutputs; +import frc.robot.subsystems.shooter.calc.ShotCalcMath; +import frc.robot.subsystems.shooter.calc.ShotCalcMath.AzimuthCalcDetails; +import frc.robot.subsystems.shooter.calc.ShotCalcMath.ShotCalcOutputs; import frc.robot.subsystems.shooter.calc.ShotCalculator; import org.junit.jupiter.api.*; @@ -304,7 +304,7 @@ void movingShot_targetDiffersFromStatic() { void moreIterations_convergesToSameResult() { // The iterative solver exhibits damped oscillation (alternating over/undershoot) // with a ~1/3 contraction ratio per step. At vx=2.0, vy=1.0 m/s it needs ~7 - // iterations to fully converge. Production uses 5 (ShooterCalc.calcShot), + // iterations to fully converge. Production uses 5 (ShotCalcMath.calcShot), // so we verify that 5 iterations lands close to the fully-converged answer. ShotDataLerp shot5 = ShotCalculator.iterativeMovingShotFromInterpolationMap( MID_POSE, TRANSLATING_SPEEDS, HUB_TARGET, 5); @@ -521,44 +521,44 @@ void movingRobot_returnsValidShot() { } // ================================================================ - // ShooterCalc.calcAzimuth (static method) + // ShotCalcMath.calcAzimuth (static method) // ================================================================ @Nested class CalcAzimuth { @Test void turretAtZero_closeTarget_returnsValidAzimuth() { - AzimuthCalcDetails details = ShooterCalc.calcAzimuth( + AzimuthCalcDetails details = ShotCalcMath.calcAzimuth( HUB_TARGET, CLOSE_POSE, 0.0, ZERO_SPEEDS); assertAzimuthValid(details); } @Test void turretAtZero_midTarget_returnsValidAzimuth() { - AzimuthCalcDetails details = ShooterCalc.calcAzimuth( + AzimuthCalcDetails details = ShotCalcMath.calcAzimuth( HUB_TARGET, MID_POSE, 0.0, ZERO_SPEEDS); assertAzimuthValid(details); } @Test void turretAtPositiveQuarter_returnsValidAzimuth() { - AzimuthCalcDetails details = ShooterCalc.calcAzimuth( + AzimuthCalcDetails details = ShotCalcMath.calcAzimuth( HUB_TARGET, MID_POSE, 0.25, ZERO_SPEEDS); assertAzimuthValid(details); } @Test void turretAtNegativeQuarter_returnsValidAzimuth() { - AzimuthCalcDetails details = ShooterCalc.calcAzimuth( + AzimuthCalcDetails details = ShotCalcMath.calcAzimuth( HUB_TARGET, MID_POSE, -0.25, ZERO_SPEEDS); assertAzimuthValid(details); } @Test void movingRobot_producesVelocityFF() { - AzimuthCalcDetails stationary = ShooterCalc.calcAzimuth( + AzimuthCalcDetails stationary = ShotCalcMath.calcAzimuth( HUB_TARGET, MID_POSE, 0.0, ZERO_SPEEDS); - AzimuthCalcDetails moving = ShooterCalc.calcAzimuth( + AzimuthCalcDetails moving = ShotCalcMath.calcAzimuth( HUB_TARGET, MID_POSE, 0.0, TRANSLATING_SPEEDS); // Moving robot should produce nonzero velocity feedforward assertNotEquals(0.0, moving.turretVelocityFF(), 1e-6, @@ -567,7 +567,7 @@ void movingRobot_producesVelocityFF() { @Test void stationaryRobot_zeroVelocityFF() { - AzimuthCalcDetails details = ShooterCalc.calcAzimuth( + AzimuthCalcDetails details = ShotCalcMath.calcAzimuth( HUB_TARGET, MID_POSE, 0.0, ZERO_SPEEDS); assertEquals(0.0, details.turretVelocityFF(), 1e-6, "Stationary robot should have zero velocity FF (omega=0, v=0)"); @@ -580,7 +580,7 @@ void turretReference_withinPhysicalLimits() { double[] turretRots = {-0.5, -0.25, 0, 0.25, 0.5}; for (Pose2d pose : poses) { for (double rot : turretRots) { - AzimuthCalcDetails d = ShooterCalc.calcAzimuth( + AzimuthCalcDetails d = ShotCalcMath.calcAzimuth( HUB_TARGET, pose, rot, ZERO_SPEEDS); double refRots = d.turretReferenceRots(); assertTrue(refRots >= -0.76 && refRots <= 0.76, @@ -592,58 +592,58 @@ void turretReference_withinPhysicalLimits() { } // ================================================================ - // ShooterCalc.calcShot (static method, full pipeline) + // ShotCalcMath.calcShot (static method, full pipeline) // ================================================================ @Nested class CalcShot { @Test void staticShot_close_returnsValidOutputs() { - ShotCalcOutputs out = ShooterCalc.calcShot( + ShotCalcOutputs out = ShotCalcMath.calcShot( CLOSE_POSE, true, HUB_TARGET, 0.0, ZERO_SPEEDS); assertShotCalcOutputsValid(out); } @Test void staticShot_mid_returnsValidOutputs() { - ShotCalcOutputs out = ShooterCalc.calcShot( + ShotCalcOutputs out = ShotCalcMath.calcShot( MID_POSE, true, HUB_TARGET, 0.0, ZERO_SPEEDS); assertShotCalcOutputsValid(out); } @Test void staticShot_far_returnsValidOutputs() { - ShotCalcOutputs out = ShooterCalc.calcShot( + ShotCalcOutputs out = ShotCalcMath.calcShot( FAR_POSE, true, HUB_TARGET, 0.0, ZERO_SPEEDS); assertShotCalcOutputsValid(out); } @Test void dynamicShot_translating_returnsValidOutputs() { - ShotCalcOutputs out = ShooterCalc.calcShot( + ShotCalcOutputs out = ShotCalcMath.calcShot( MID_POSE, false, HUB_TARGET, 0.0, TRANSLATING_SPEEDS); assertShotCalcOutputsValid(out); } @Test void dynamicShot_rotating_returnsValidOutputs() { - ShotCalcOutputs out = ShooterCalc.calcShot( + ShotCalcOutputs out = ShotCalcMath.calcShot( MID_POSE, false, HUB_TARGET, 0.0, ROTATING_SPEEDS); assertShotCalcOutputsValid(out); } @Test void dynamicShot_combined_returnsValidOutputs() { - ShotCalcOutputs out = ShooterCalc.calcShot( + ShotCalcOutputs out = ShotCalcMath.calcShot( MID_POSE, false, HUB_TARGET, 0.1, COMBINED_SPEEDS); assertShotCalcOutputsValid(out); } @Test void staticVsDynamic_sameWhenStationary() { - ShotCalcOutputs staticOut = ShooterCalc.calcShot( + ShotCalcOutputs staticOut = ShotCalcMath.calcShot( MID_POSE, true, HUB_TARGET, 0.0, ZERO_SPEEDS); - ShotCalcOutputs dynamicOut = ShooterCalc.calcShot( + ShotCalcOutputs dynamicOut = ShotCalcMath.calcShot( MID_POSE, false, HUB_TARGET, 0.0, ZERO_SPEEDS); // With zero chassis speeds, static and dynamic should produce identical results assertEquals( @@ -662,9 +662,9 @@ void staticVsDynamic_sameWhenStationary() { @Test void farShot_higherVelocityThanClose() { - ShotCalcOutputs closeOut = ShooterCalc.calcShot( + ShotCalcOutputs closeOut = ShotCalcMath.calcShot( CLOSE_POSE, true, HUB_TARGET, 0.0, ZERO_SPEEDS); - ShotCalcOutputs farOut = ShooterCalc.calcShot( + ShotCalcOutputs farOut = ShotCalcMath.calcShot( FAR_POSE, true, HUB_TARGET, 0.0, ZERO_SPEEDS); assertTrue( farOut.shooterReferenceRps() > From 658f543e9861359fead6359bcd3581d8a429ccb9 Mon Sep 17 00:00:00 2001 From: sohnshaik Date: Mon, 8 Jun 2026 17:02:49 -0400 Subject: [PATCH 25/25] made a little oopsies with TOF --- src/main/java/frc/robot/subsystems/shooter/calc/README.MD | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/frc/robot/subsystems/shooter/calc/README.MD b/src/main/java/frc/robot/subsystems/shooter/calc/README.MD index fd1f822e..4378b500 100644 --- a/src/main/java/frc/robot/subsystems/shooter/calc/README.MD +++ b/src/main/java/frc/robot/subsystems/shooter/calc/README.MD @@ -133,12 +133,15 @@ There are two active tables: **`kShotTable`**: normal hub shots, 14 distance points from 0.985m to 8.627m - Flywheel: ~39.8 to 68.8 RPS (raw values have a `kScoringRPSBoost` of -0.2 applied) - Hood: 0 to 1.65 rad -- TOF: 0.5s at all distances (used for drag compensation math, not actual flight time) +- TOF: 0.97 to 1.65s +- Drag: 0.500 at all distances (used for drag compensation math) + **`kPassingTable`**: passing to alliance partners, 29 distance points from 4.008m to 14.355m - Flywheel: ~48.75 to 105.14 RPS (`kRPSBoost` = +0.75 applied; `kLongRangeRPSBoost` = +0.35 for distances >= ~10.4m) - Hood: ~0.70 to 1.16 rad -- TOF: 0.254s at all distances +- TOF: 1.35 to 2.08s +- Drag: 0.254 at all distances (used for drag compensation math) ### Iterative Moving-Shot: `iterativeMovingShotFromInterpolationMap()`