From 5adf55e80f82b9bb43e9fc721302b1a3a8179159 Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Tue, 22 Sep 2026 18:12:37 -0500 Subject: [PATCH] Space Taxi is finally starting to look viable on the GS! --- 21_!.wav | Bin 0 -> 33460 bytes 31_1.wav | Bin 0 -> 12986 bytes 32_2.wav | Bin 0 -> 21446 bytes 33_3.wav | Bin 0 -> 25554 bytes 34_4.wav | Bin 0 -> 17962 bytes 35_5.wav | Bin 0 -> 20456 bytes 36_6.wav | Bin 0 -> 28952 bytes 37_7.wav | Bin 0 -> 18852 bytes 38_8.wav | Bin 0 -> 23360 bytes 39_9.wav | Bin 0 -> 14764 bytes 3F_x.wav | Bin 0 -> 24876 bytes 48_H.wav | Bin 0 -> 18072 bytes 50_P.wav | Bin 0 -> 24446 bytes 54_T.wav | Bin 0 -> 25694 bytes 55_U.wav | Bin 0 -> 25264 bytes assets/RawTitle.png | 3 + assets/TitleShr.png | 3 + assets/depackSpeechRam.py | 81 ++ assets/extractSpeech.py | 225 +++++ assets/genC64Data.py | 45 +- assets/genSongData.py | 76 ++ assets/genSpeechPcm.py | 106 +++ assets/sidCapture.py | 57 ++ assets/sidToSong.py | 111 +++ assets/songs/song0.song | 94 +++ assets/songs/song1.song | 94 +++ assets/songs/song2.song | 86 ++ assets/songs/song3.song | 86 ++ assets/songs/song4.song | 126 +++ assets/songs/song5.song | 736 +++++++++++++++++ assets/songs/song6.song | 840 +++++++++++++++++++ assets/songs/song7.song | 852 +++++++++++++++++++ assets/songs/song8.song | 1508 ++++++++++++++++++++++++++++++++++ spacetaxi.c | 479 +++++++++-- spacetaxi.h | 21 + stAudio.c | 421 +++++++++- stC64Data.c | 14 - stC64Data.h | 6 - stCels.h | 7 + stFare.c | 95 +-- stHookTables.h | 85 ++ stHooks.c | 1354 +++++++++++++++++++++++------- stLevelCels.c | 114 +++ stMarshalIigs.s | 354 ++++++++ stRender.c | 386 +++++++-- stSim.c | 729 +++++++++++----- stSim.h | 270 +++++- stSongData.h | 512 ++++++++++++ stSpeechData.c | 490 +++++++++++ stSpeechData.h | 26 + stTitle.c | 133 ++- tools/staxibake/staxibake.c | 103 ++- tools/staxiharness/Makefile | 36 + tools/staxiharness/harness.c | 183 +++++ tools/stsim/Makefile | 12 +- tools/stsim/celGate.sh | 34 + tools/stsim/gate.sh | 75 ++ tools/stsim/gateHashes.txt | 21 + tools/stsim/gateStreams.h | 460 +++++++++++ tools/stsim/recorder.c | 195 +++++ tools/stsim/stsim.c | 138 +++- 61 files changed, 11072 insertions(+), 810 deletions(-) create mode 100644 21_!.wav create mode 100644 31_1.wav create mode 100644 32_2.wav create mode 100644 33_3.wav create mode 100644 34_4.wav create mode 100644 35_5.wav create mode 100644 36_6.wav create mode 100644 37_7.wav create mode 100644 38_8.wav create mode 100644 39_9.wav create mode 100644 3F_x.wav create mode 100644 48_H.wav create mode 100644 50_P.wav create mode 100644 54_T.wav create mode 100644 55_U.wav create mode 100644 assets/RawTitle.png create mode 100644 assets/TitleShr.png create mode 100755 assets/depackSpeechRam.py create mode 100755 assets/extractSpeech.py create mode 100644 assets/genSongData.py create mode 100755 assets/genSpeechPcm.py create mode 100644 assets/sidCapture.py create mode 100644 assets/sidToSong.py create mode 100644 assets/songs/song0.song create mode 100644 assets/songs/song1.song create mode 100644 assets/songs/song2.song create mode 100644 assets/songs/song3.song create mode 100644 assets/songs/song4.song create mode 100644 assets/songs/song5.song create mode 100644 assets/songs/song6.song create mode 100644 assets/songs/song7.song create mode 100644 assets/songs/song8.song create mode 100644 stHookTables.h create mode 100644 stLevelCels.c create mode 100644 stMarshalIigs.s create mode 100644 stSongData.h create mode 100644 stSpeechData.c create mode 100644 stSpeechData.h create mode 100644 tools/staxiharness/Makefile create mode 100644 tools/staxiharness/harness.c create mode 100755 tools/stsim/celGate.sh create mode 100755 tools/stsim/gate.sh create mode 100644 tools/stsim/gateHashes.txt create mode 100644 tools/stsim/gateStreams.h create mode 100644 tools/stsim/recorder.c diff --git a/21_!.wav b/21_!.wav new file mode 100644 index 0000000000000000000000000000000000000000..3772b5ef2adbfaee060b9cdb69ff22f936954551 GIT binary patch literal 33460 zcmeI2&8<~O5QMKC0U;m$Y}bIzQ5-@|}f zNbbL{tLN0kJO23n&6`hu-)?{Y`u4ZKK74%n;&!|J^ySYlZ*RBPpKiCGZ!d1|-+g@d z&ll$9zxB`OSN*3QXb0MXcAy<-2ik#lpdDxj+JSbU9cTyIfp(xBXb0MXcAy<-2ik#l zpdDxj+JSbU9cTyIfp(xBXb0MXcAy<-2ik%A9jN~{aaHeLSnX&3Oyy|%k=@XJZ2xTg z^Q+a{@849#V-+QlA8)Lt*p9fvD1Vf%w4y)m9np`>#!Jyr#W~^38L#uqU3HwJqa1o3 zl|X(@e69E6xAYfjg*P+mG;&Tfs}yQwm}APw88Xf^%c*v1j(x^+BG^i0O%=~Zn)7jn z4w==xAeD+7+ZpQ=wNtDx_sDrON~y z?2Q>Is5_(c%zchZIrM6LD#l%!t7Mb3@t8mGh}iG%6Y6fEdg`8?uHBc*)IVQ6RdN!-S=pHlP#0`%#H78JwBqod_6P>fU5@hSIpcCe^ zi<~l>aoHnNFRpj$L^U$6MARb{on3OqWlwe2)X_v0kk5F`YTRRIRQ6PFO~Jtxp(>fr z$dN*4M&l-!hgDvlR1%}@n9cOBV9qngt?0{uVW>u>~&Fc<+ zwaR6G?Cy8+5_w+sj6`NWMO7)S!D!UE=SrRZO1{ZTHQ240Qp|(Os`6fQgRf-fEGq7x z^Jt`^W~r*3njhUIdql;Iq-JMZnClL6Olu@DX2H(8Rb{76C6KC%o<}7#cdAw(wU6Ey zmBP1152Vk}s@OVXR&!kRxT25xu8`H52c5(jz1HDAm>n%#KaNm*?ub{!ot@XO-=o`5 z^}idaIK7^&@B8Gs@@&I3f7ELB<>)=#H;1CDjUAsqMTPJ1I9GNit2x=-cAy>jJ{_3- zUw))Lv)rWSu0N~S@4jSw!TBa$i3qRGH;)KaTYWtkME+jrAl6IlGb4k0oS{SJD8n8i z-=$e9kbxpQV-_9$Mp@y%*nd3YM!pvMHO3t!aaTF?zRjpFWnRDQ+HZf)m#T}aYh>=b pkXuyI%x9R?7{B(_+t)X)UU+^U-vB?>7xzik2;J46p#a4^11*3 literal 0 HcmV?d00001 diff --git a/31_1.wav b/31_1.wav new file mode 100644 index 0000000000000000000000000000000000000000..d17740d077bd72bea121b05c125a419e64c54aea GIT binary patch literal 12986 zcmeH`&21Gy5QG;Za0nIw_&^3oV2LXSbGC%IadR9*!ih}Cz@gyLs8m(`v%634J;}0G zK1_G@SJks)``g$1`@bKT<@=|HFF$`h-QFzA^6us5!^5(C{bN3CTqmD=%qMe@BbieCF&_8w0sR@&#t?AW6ch==BL`bbkZj%#k%Lmgj`p3 z{!{sx)45ddHdeRo*?s$JzupFQO-2=Te@4gbYrbYxt$kBfj`f;j{qJ*Rth+mZ<>HO{ zs`3r3a@1@^c3PbPwRU0ZXU(eWJUS&3hR=JiPqgy{cFOGew&8S7?Q(|uQ29J};BaQ5Qb6N$2I`(uotf8Z{{FS;oNXm~ zf_Ly@Z#^Y7=IHcX_YKmCn9@yE*L^uJld82+t&ZH8vI6)N&??L%V#`SnJzjx=NKC76?RGp|xb<%_ACu61=sMj2m zabk4v#(n*Sj+_~1NQJ((zi0F=pw`a3h065t%Q>Nj>^VcnlOAfLqV6|Vy{fYjaCn2!&m2su)R+PWUQft$ z&W6&no;@~X7WdE-$*Rp76VIttxSbv9IuBpms5%p32pmx9jO098$FA zlu143^yuiC6};7qY)z?J)%5$fuydF4eLX$Q801&PotnF+p1XVUGIvA% z`DsXs=lRKd65>|x_aq$5@dC#ST=E6{fAE^!C2xIX9WU@UU*MYmf#+nu&DTD@>Ue?U L1&$YZ>o4#hN7}rM literal 0 HcmV?d00001 diff --git a/32_2.wav b/32_2.wav new file mode 100644 index 0000000000000000000000000000000000000000..b3b74acfbf8b47517d80638655d7630149fd10fb GIT binary patch literal 21446 zcmeHH+ig@)47^Z5N{A?c9*{yLenCpUBcc59mujei8t8!zD1}(oX#6_op1au zI)P506X*mwfliI)P506X*mwfm0Ir{a!z!l}`h?ao(SDeeSrwJ#tgrj_~DQ(K;XQsI_vwRgG}?seLMR#oOwR z@~t-a%xdP#?}aWq%@C-nI@2B7Rqlcvb2&E~ukyY?|Ni^%F8iWc55Ck@p4I&O{p?%& z-TA+(`m3+Mm8Z-SWW>vFUHRqM_t3ra5~kwz1sSp1l|J8k3Bp&3%ijISiSu0ll6Q`^ z`KEFehc2_m2sCx|zRavY%q3N@75y64e`a&OMaa#5un+BhYAt zlUBiqfJwx<5v`oK)ec_pkF#~=4xMVGODfC>+A+SyfEWkoTUQ`ew>xZIlf&tbOVz}cDENTFZajXp~oBWR?a{?(@(+% zQokK2S=w2hAiRq`5U*4e7&ua+j+kfr$+PGOmQ+Q@2vwfxC#(0p)$<-u&(%G#>IRfj zXH36WXK0~|b4&yKSiKv!@by*q6&5#f_8OgV4ZC{AwyIDdKhD}&O+?}>T(4I(u;^uH z%OqZ1;FcXOuv))}az_We+#-dLH{W1kss`I&m)MCuN_(YIF>S>Ra&SbbrH!eO*04kqsq zL8oj?QmL;y<@#NvCuX@oEhhb*t9@7I!j+Yt#Hud7V}Gt(ES4SYcGPFp92xl#rydv) zE>geLqPgtgqPd zc*gb{L6J`%@9uto-L^09K7aWB2~+}=!2gxN`d<#upUe8KrhNX3#}^#^7S)fZ`VKr~S95_gdpRvfnP-K|r(KTq zHyoSmy{px+9jB=ElDS{i$MJmr`3is4Y@Syit7fycZ*yhm-ezbV~)i$K4@9jOl z-gCB|SND~<$!619S9z*EQq2f^M?ZPw0w3LEq6Un;Q8~)S`?89&kW6GUpXb!mV=B=h zV+cGh7<~=<{RGsU6cIH0Z}fSFZt~4u(6 zLtsz4=iCX{j+;=gywL+Dze>NAU#lLEaRXT}?TsuMZX&nu#Y9!jcC2s7ly({Rt=&mg zLu+2Bvae}9oeAwe2bFh|Rrl;l?l-^p#)?~xAe*BOoeVzXcwhD<>LA8Ecv*v?R*~u= zrjk8yWUwJJrZpbE_EMR#s~o2gsdtR2k4RnCZ47nRXWfu`BIXqKGS0S86SrmzIQn=S zxX8d2Z-;j5g(7<4!3!nod}~ut(GYy#Sx2N!>-LP+n4NLWt-S6so{Z{jW@P-l%nOcQ zFe@)zJuNVm2fTH}k1n_u8{m^AlM0Z;x?H%IvdWydpmmDb2)?ADHVjw*F{JaT~d z>ir6y(RS3Y&Ex7fcGGI~p6wVq*4iD=|C<*55|czlsv zgbHtCU&fcJZpLRXv%AHMfy$fn`B~S@e0+m1|Pzh85l|Us>2~+}=KqXKK ZR05SiB~S@e0+m1|Pzh85mB8f^_y-I*bcg@| literal 0 HcmV?d00001 diff --git a/34_4.wav b/34_4.wav new file mode 100644 index 0000000000000000000000000000000000000000..ae9d522ab381a775e1fed59cb30fe3cf07b1f569 GIT binary patch literal 17962 zcmeH|O==uL423H>fsbH(fZQPv8?w#9SsP4t+4)$xOm2{qgrq2h{@zpfv^yRGV!)D~ z-lwY8{P@e~hljg|F}{9$y#Mz7<@RQb@$T>I?s1GyKgM`JZpQP|%hT`sF>ZhMt2fXa z=neD+dIPQFt^agqZy@B39Z=g4D?G60;Fkjcc zMW5dr=nb5?f%Uy+f6qJ#%|hoQAJ!0!HO%i6xn|_0|4H$je4Z%S&s3p!WaQL26+383^u!u5Sc_z?F{f%K zkfMH@A*X8AS?9{ItmgN>A9xNsRdh@tRR|UlqZ*^Ku8hyJQx)&5E2UUg@r*$_nuB~+ zLk!j|g>puuF@x2568^=PNn5P#3uB}0quhGy9MN8@c<-z$G^Zx&9maX} ztmyW2&DyT#q_U2ji)&BJbX541=&WlrrzXyPKlrSzJH$SEj;;SbnBi(X)7Hl*J3(?h zNxC>F|^ zyeM1b(I=I;a!!oW_c6Ly?I~U{UUWuEKSt)ydL(DP&lSl`;rHZ-&z|LZ+mYNmRwcds zblS05o?*KzkJMhD+ik#BUB^>fbO(**- z`c~~dxw@3XeyrxxM8W>4iX%tPNGCI*h$BaFC>W;xHy%64O(-Ks=yuGOnqc6mjensJBisDEX)SLMgJ zNY#7P^D|c|%FV2D&YpMMsv@mV^NO%NC46Q*EBAd=c?LPM=9yV#k5!`I_Rh`YNyq9l y?{w@2HFf6wu0QL4c-H!kynFrazSRxz4z^FnoL9G}yrsk6ZC3y8^nBLaD}Mk6uJ_Xb literal 0 HcmV?d00001 diff --git a/35_5.wav b/35_5.wav new file mode 100644 index 0000000000000000000000000000000000000000..ef53f4f90e7e9aada6a93fd4ee421d63544f4bb8 GIT binary patch literal 20456 zcmeH|OKy}w41|9;L5>h{fZPEIBC$eZ5!R6qJ9ar1N8l29v=dY*Ro8vg%z5ac8IF3)hkK_GubNqOGdi?Xs+&kJ`25JMff!aWApf*q&s14KxY6G=_+CXifHc%U=4b%o|1GRzLKyBbUHt=%i z|1L9^f5qQ-UiF@ryOk?CdS~v-T{BPa;BMw-J*zX%?Q=$Q@0_ld#o1Zy-^&>7dB^yb zj6_dUg@(CtM>RvS;uEupU)izyr;9xN5*%M*Q=GT{F+`9ni#)_mF{0?_qoTb z@lDNK^O1<1Q!>>PKbh*QH}c*vSGwQ4$1|#XHQtqPH)W!pb=0%^Rhd3_ z%~hQfGpP&xUa8t{p6I#0t3I#lEXY(B`prMnGTrn2u9GmM%J{~fR(sOx@jiE`8r`D~ z`#jS%?K0nLRpaN1gm-w4>TiF~JF+S9Mn?A@m|l6(>)D+>US}@mF=7-lu?zFS5v?_a*g`U zIn$8rNmYD`X4?FDH}i73x>%XbG;^tD)A?kd?m3^r_nyU_R^x@SvU*jwWr><+T|4J$ ze9?IG_tU!hX{@T&ZKo={jXa;dnSYkItMMD`;l9Rnl#(`$Oy zocv~vTy0$=`q??|sgo6*RXMxu^J`KG-0ZE<335C!GrdL{H1v8kQmpTJ-F8&DV;TKq zh3h?v9jDg%5^kD@eqJ%{3D#l_I%?c`J2(DS^jsv)NTzztQ^h3aBk4P1nw8mnEpg2% z(Qkg1-MeLhw+>Edd9_P|YgL|wF4O2*#VXgP<75>z^sL(DcD}8ak36ojm#?9R8E4ag zT7Q?L9L~tZ5I_q(if?8Z~4<=bLqlh*e+J3i0c?|v5@VCF1%vfiF2;+Bl=SJYt}5vb3( z2ihL{{LXo9#BZAYv}>&QTvuTi;;DOQ&r$LG$<13Hvpf3bJM-*X^sE<7!A6?WIGqYBane)nwb!O%&@^YSexpJmsSrt3h zuQia7s=wE9>sZm}N6l(m=Jg8utb;s9Xn>-g@jH(SW?+pYdCbO4qGXOUGDjISquv^W zvd5LKvw^HstIv+>FlF!E;Uw%kT}cHqOhaU+W?&o>IX%xaJR^K^c4q3ik_u#}a7R~F z{bOp}r9{-Y` u(Seztk#m{cF7wUIE_2CzD=tHvGu4p;azGBq0XZNCJJT0pS>8hq z8J|^n5;c9M&~!8w>mGCUsv{@QsPE;k-TN(=)5S9{oa?P~?l*FUba8XM^ezx$WZ#za zeqwd=4d>s+pLgp`y8XerSoL{3b|&WT^;Mm`evz}I#&UhQ2r<6_UEPj1D3VnszK-KZ z)Za|jc|WqczSJ|O;kFr3itHCFULw!ft+`la<19Lw4l;79zt@qU5%J*&sZ3s08LHkRsF7v<}&crim`F@KCjH!5=O zn#i4Lz@sAS;*cw%!d)&A$?Va~xxN|SaW}^*;;ydsx%pS3lc%q$akDtHD)DDK z;&=m?au~Vl)l6z-m9wf*F_T(*uGH%uUNxc`aS#~#<*dUmsBX=~fy=M9>Rj(qng6Bs zP3(L8n4kCc@)2=e$2h+a)6OfU7~gRYk<(*j*Y9?Do4MCRYUfk?&TJR{H~33(b!Hou z5&b<1k8_1y%A#tOn-Bf$3F^^%5gXYfQu)-GSu0qx2*2yEUW1M(JqXb{8xDg);;ClWvGZ9r#iwV&NTFEG`?Z-%?_0x>&&bUk%@n&Ff4PaMmC*! z%o^j3NUzR3sxw^IL~#B^ zyvSYKl1zExFS>!^smoJW@D)(5@Bl_D}x*BCjP zv1U0>%1dl9>jZV&L$zhmH+NzcSuu~C>K1wVS7WNwE3R1MgxGVBh%m=G5g(axK1?ub zi(q4nEb$_mHL+9ko(Vc<(}9^^O;-_~5X(SOs|JBjhdOG#r_Pw6COU&E&k`zJSoH4D zHHM>DAo$XuuJ07yWvGeHp*lB9{=HtPe6PjwUYfLit+_|8<>SN=LE{~&td+O)Vro*v z#O%~{SFzE`|C_G(wf9tS z+H>~>_62UeK>ct2QR-XYbwB<3BKMnfi&MYjEskfk{RR;|>-c}?$J=!IbAEsK9>?zS zs<}IzdrWy44>mj(@4L&T+4r6HFmCfVJj}e_b-u-!PVfD)(KGkU+?Rc~{rY`@^B2hf tK~Gto-+e3g1@;B@1@;B@1@;B@1@;B@1@;B@1@;B@1@;B@1zySp{spRnkvISV literal 0 HcmV?d00001 diff --git a/38_8.wav b/38_8.wav new file mode 100644 index 0000000000000000000000000000000000000000..22c374505e9c7243faed0ae57272f47fdcc98e8b GIT binary patch literal 23360 zcmeHHJ#Lgy4E*2(DL6pH0aDWC984=NX7d=C z`^d<$9L$)bmt)IBol&_0rivMPURCMl-ds0hQO!8OSTtN)&$ulkC{_fJ!{l4K4#3J+P=<_K8-Bk`)DY3 z!9kZbG2@KrD&sq_(NSYPn?T7?xvH6l;RFnJy+g!2i~LdU3VXT{MZMV#@ioL5H||A6 zM3r&lD2tlRE3N#ynd{Yj>PPkr%UNVpdDLc3e$GsRC{`I&Rm6_3sCejAtk^T0*UqpU z+;NNwIUjS{N#wP&x{eIb4Tr9Ctg6}Uf>B4})>%|oqYV)meknSfu9TeND6p%z(%GGl z_n87VGhgc(C!(OyM@O~z$nEo9W!#}YE^=(Iy4M+P;+{PnLzxzNIq&!IH{r0g^V4@kCF-J!Sm|Omjuq>i9yd7K z>pnk6n;dRAH>1|y$VRVj`|I7t0P38#f}3MhwMndFo>{AE=RR!1tDNJ?+{|pgGj6Pu z=j=R%TUjW-+VAdr!}2dsefZZ7Yq-%;_NBV0Xm)GIw2mD?F&9-&fDPd-)f< z?h9t#+3a;+uj}N19FPNYV9$ZofBV-m-+PTvazGBq0XZNCR^{41^z?AV-KeK=v$=AQD>^$vP5Z$1VrpFf6$YH^@U1-ygT_@zcYXA3vXOZ?4lxvT#!`*r#D^{Zd*a^3x-tlw51EYm|7^zB%Ep*r`lCMw#}vO#5Po(){AAQwiUAX53Ax_FeRv0ZaGu8Lo(h(=jFua zF^%)Vl%Q@ASXQs6?4t13Xl|NrXb#Y+9}|9+YLvjMn$xp5c{b1I1uB*alqQfix z&53nn6m^T-3tw>5)UVe;qmEgfx9i<>nr@-WdB&TJCW7zhsVcbT_fXRGnNDB5OUdtJ zN3QRhrxOo-oo=CS)g)yz@8+I;r?O6W%?Q_dgI)XQI-AQ>_u!1XFuPURg*7d9r4p~g z0h}4Cx+!+hDW+wO#qf)yW}y9yI0Z9uqSINCYm~5N-c39A$#`yy-6ayQ``5YW{`I z=DeSGwQt-DR0--9%`#O*ow}SZ(=B)o6Rzv;RSC14#J6ISYEZXG+3B)uYF4*RnCWI9 zSv|fCik!@PY2SVs?_;sLD9srY)^I()W3I?G>U_#9+m7qm-Qr%XiR{vHyhS%h-J&tm zD*lyc>#^_c<+wuJ4J6*Dk=TC;xxT5-@P>+6mvbl(HHLf~xx-xd$b%h5{FF7GjaVmU zb?v(T40nkC&&0RScSdEF!!hf4D|O$r-^U17y))Li=lHQtJ-+k*IQkZPk=_A)ebjef zW5wTnjc+^toBUe4epknzyd2jB&U*p;)<@BKcUjZc1=a=D1=a=D1=a=D1?mg@0|Wrm A?EnA( literal 0 HcmV?d00001 diff --git a/3F_x.wav b/3F_x.wav new file mode 100644 index 0000000000000000000000000000000000000000..181300750175b4d64dd66f959e9a10715c0253a8 GIT binary patch literal 24876 zcmeHJOKub~5H*}2M;Ia2xdReJV#^{~kA&E<%dxmjZjhspmSuI@U9bGA>>g%9X%=l) zyfoLEaIIaQo|Icxb^Oey%?B}E2dqs9K>g?yU319F19+}BIEyz)~ zQ9hsca!zZeQBOaA&7ht9`kAR|`*!w3oKLOm;nWJ+jZ?oKl>9Eed@`#y?Wz8o-zfc5 zHbk!ldCz#rOnHj*WVAk&v=BqWwll=-&08IG*L4uf`t8Fy>t>&1-Qm-o#Z0i?c7v6+ zsQnr9>dl@xb5eU*Zh8ta$EA}@c( z+1QOYGpk?Yylxt=c9?b39P!TLj#cJuEO|7nH<6}^cV4%ahaE;YtrLH@I`hgB((2~L zOWysN-L0=FA?!X|MpoQT^s|(Jo$1Q$mQ}^LR_z44C1~gB9_{>(%nGgMHI#Tkcjj zi?)unIx~3P5pNl7gu$xz4f3$e?XHDkamm=iJmP6jQy9K9h0Q;GK01qcM@U^CZ;>9J zV=eCndodfY>*DYR9|_E*z^69t?u~wGCBM% z4Aa*Fql_@TBT}%;c{c`Wajvn9dX1_1_FJts>t@ffGWr=-toPLS@FKj479Qg{qK~#Cqo3@kHmj9N}MxQTU3nF{wXSwUl_qWx0FFhUc)}{Np{gUPg zM~`O7Q@$^Y#yZ0%kJpi$Hfi=MKR@epJz3HemW5MemwY8to7Hn)wQ3*f#_3DU_dUzK naNl>m!`taee-CbZJ8v6Fqk(838i)p>foLEahz6p8+iu`56anwq literal 0 HcmV?d00001 diff --git a/48_H.wav b/48_H.wav new file mode 100644 index 0000000000000000000000000000000000000000..173de91f23867961f1f83bfba4553326517a3a54 GIT binary patch literal 18072 zcmeH{&8?nQ41`~(ASFZ;Ko3X|i7kt;j)YjjE`?AC&Cmg*5X&0PjO}y2AHE0_9bGB5 z$Isa3b#C(0kMG|7@$R~Q{_gz`zyAL5?VIbmzWVg@&HL;6{?F_B`g(Ky^5Nr$-##&K z|2jYC0_Otf0_Otf0_Otf0_Otf0_Otf0_Otf0_Otf0_Otf0_Otf0_Otf0_Otf0_Otf z0-xsse}DVm|9qY=J5$dE&IO*iz$^YocxLabJ-hGN?W(>abC}~Er6X6$b0@v0m2+N!n>Hz#Tvy=NsMIu9+2`i2 zT)8>AzE@v_F14K2|0Gefd)UrE!L+PVMFhd$Au7~B&Q5hiu9W7LV?9x!k;p7L&Ec$? zJ6BZS03RGRqTi#E?54_kbsZ9T6JmBaz1Gfg&T3OkQp$7g*-`Y6Pc7@n>mHEGxyGpz z1Gdg}mr|ZPhl-5ujJ)n~l2zp}88e(jrIhC&GG-5&litJMm->|V8K@#E<+)OxJExU% ztdv1syR7|H(9?%1BWBg;lvb5w4T{Px1D$V06^_W&sXhO9sg&k$^r%$LK|`-KbVT(1 z3ql1E5maiZGAgIJQ(7tIIVtE+P}k^vR1uYg4h?nX(2uB$=Sq1_j{0tgZb=6x=oe7k zQQX-nPSCmcI@X>#7xij#e_16_V^WVGdh9He&O!kZF*MZZGiC=hI9Jil=nN~Tydve9 zQ(8@2YD#79x;i@xMJdhEpQ3WiCz2Yu$Wl*@3^VOaPLjx+Vuk)3$swD$P(gT1KDF)z z+x?x@N$_c0Ir{9?YjlE6My`{T&0Hy7qjRe8C2%t;S94OHRZeS?eBHnKO@mi=M4o$6 zyUHM#JH>Z|NjR}Z%_>pt!IgwCcyhx4db zzR=I$eCkB@uy4t9Ody;b^? z=BznW%ID8~Ro&xNWnWeMmV2w*4toEVp!UQgUF`ID?}X=E_fF@(9p97H?J1KB+ATM@ zRowe$*WUAdci!ge^V7d9r(fk(^<{ngtH0*f^`if(o%if|oO#vj&YW|B|KA1v0SwI- ATmS$7 literal 0 HcmV?d00001 diff --git a/50_P.wav b/50_P.wav new file mode 100644 index 0000000000000000000000000000000000000000..e9ea5a4a352b030283761af9e43a7fe3cda80b06 GIT binary patch literal 24446 zcmeH`L5dw`41_y5fsbHvfZQRF7_#-kSsFvgE<2M` zRr;jApRnKm{_fpBACKdYUq1Zy*WaJtzB!KLhp&G>eK?L^|2>W$k2lAkpFV$j|CM?B zW&id9dx5>cUSKb<7uXBz1@;1afxW<9U@x#2*bD3h_5yo>y}({zFR&Nb3+x5<0(*hI zz+PZ4uou`1>;?7$dx5>cUSKb<7uXBz1@;1afxW<9U@!2RF7V&a-}3J@{qk?|txvt( z^d@&q-Pf7%H$7Fo&foj!tLG~`ntI&Vy^rZ6a^^9uyF*`1RQ8&_zv+s6`=!4+f9GA! zU&EBA-GO6OhUuh~Og&MVb=D`)4;uOO(tCS2=nzu1K6KpMaZq^s+h;nI_JvQ_8hczHR1^ zat;Y^$fPPx;VmZ4dQOKq(AjZE8hNdifUn$@#|*wGeN2SX$x5DG3LIwP>pt|el0uFI zey){*k2+6eN)A*{ly$bWQIR8qXGMo> z^q6*4XRGfMcXxA>t7#)LC&QsLzY3mID5RLK-syB?LV-{{ah$@$R7r6&xw+e4{+7Rs zucy*)Q0<&{R69j?htXf9{B)_<>`Xeo=&G~cPuHnP?vfKX?FBdOp1)xJhS=jgHhXmj z&Y8XoT-44YQR<4*FU{GU#GbQ?{g3*OwqegyxqIHinIz#kM-@G%;E|F=Quo2@B(vwM z`ZB%lM4A~KD#+^fThT+sOjOU{qAI0YnRAL0$}9cCaifkOp%<}>Hs@R-3{WTwPt z@y=AamwT9DA`>{&yBzqeoZ^f5`;;Ta6d4lusK}USOt$mj#%0hE{S77+{o$24psjlwNE7|ujA(vV8Bvp|L zr5)8y(J9k4xZXhm0u?zcI#Dytgannc)a-XfN>$~wr@C_VO5wZNRpsQ&R%WJhtF!#~ zs&95(r#cadQXrTid1~c`v)`q}q#wPAndIQkUc)wWRe|W$$=;t6pQ4*-q~xkfLQ_pJ z&k9nh%*a%QnWf@6GFrsjucbjpSH;z+PZ4uou`1>;?7$&%40?&koL2 literal 0 HcmV?d00001 diff --git a/54_T.wav b/54_T.wav new file mode 100644 index 0000000000000000000000000000000000000000..2614012419a8ab6266c73d84ded321c3b22a56d6 GIT binary patch literal 25694 zcmeHLOHLe74D93t90BD3EOLWL2~xIMkTnv@E<28u%WwmZ5-h7!{=NQ8_YfvnBVoH- zF8fu85d8RldwcgVO+UZfef#zM@%n0-rVlTlPj}Pw_0KeYoUW#a`^WpwFP!VA{0(M9Gv;L@J`NSl8DXFor}Wv{gHxaS{6Hs zQZKT6tvrim#CA2O`Y4MpvcB3G(Tg?rX$LiUsH<3WypGOgEY70#QKo?)$0fjX1cOmI zP+&!d&fUx7t5BfK9yM48fD=Ot$bmXF+o%=fiIuZrMTJz(HS1g5vLlFagK&YtqA zV_81;$|LQ_^0g{@%vap$abJ-YGvNVa&I~x?Sq|JF0Gtao1cwvTUvS_^armi(GdB zMI@@3{R)mm;5G-wSWU3289D4^j7ZF63K;UJtbOQ9g`5!@?D;7&H6x!Bk;BW#;hbd? z^%*#VB1g1LkD)@0Ow{Q6h^|K8vsLC18S}{Div+V1);HhtmCjHR8bDA>^pjx3y>SA( zh}+s zci`yOEx2B>{*HAo^zYqT`D?H-M*gevy}AiZzgJQAn0elw3Yrr275**Tv`?AU&Dg$e zpE`xOh-pu7kzHV;Mk-Z~t z9&4R?@mTSFcGmm&KK)veSHv;*xxJMcCf*z;52sM)vS QL|@Skv;*xxJFv}xzXGWd>i_@% literal 0 HcmV?d00001 diff --git a/55_U.wav b/55_U.wav new file mode 100644 index 0000000000000000000000000000000000000000..427a765959e600d204e1c887805250b0b6b84cbf GIT binary patch literal 25264 zcmeHFOKufG3>-K?j_||*xC2s%#Fj-^Cqmh=%MmyX3oerza1>%$maA+}zZqqPD7{77 z_3NI}`}*bX?$_hCefxC(`RT{=_uF>+d)wY`H{17z=ZBxKn#;?dkM(zX zS^w7h%I@v`j!;%6a6g%4Zhu?5kmU?yrmk3R8#{b+Rb{%jyC+S>IG5Sv80VWRXZ!3- z)F*dVv^G`w>AsFtS555>eD$pEnT*WeQ*W9)RnzzDE>`5$vm8kUPpD_6x57L+^Nx;k z)0Q!5Du#De&UWN+MlIs?R^}=j`Ir`Sb#`wk&!0WJBb-`!W_{}~tgfzz22wdCcg*#M zr*k!NLRB(5ZPju8OQw{L_1r{EotsVUXRhsm{rjxyQr_f%?guZEHN-)ff2W zMsDphWmV*xFlSOS-*dW$9sTY#k1XDXc+ZT6S;ZTsbv?7TxtnoL?C)6f$nNDbp6{q$ z+~XZ_x~qHlvKgO}Q^Bll$@!EDWD;FD)?Bc8E2hSbx5k_fL_E)Zf#(@{-5Z*!z`eW9 zv93PyFO1jhpYaxa!K6k$y6!qw#nyH4_E6Z_RmgUQv*yaFXG{p@9^v=%F=OJ9^BQ*_l%ObhWX0&?_j-e=wg1K;WkZ&PV_O;Z>&6eG1KN1 z$E+*Uk4~GDIX7X> z)BR`ssKqSAaYoJg8I%5{xqj*V_NwB2_6Su2YCsLB0X3io)PNdL18P7Gr~x&g2GoEW zPy=c}4X6P%pa#@{8c+jjKn`qlKE=1 literal 0 HcmV?d00001 diff --git a/assets/RawTitle.png b/assets/RawTitle.png new file mode 100644 index 0000000..2710f32 --- /dev/null +++ b/assets/RawTitle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:037ff3028d1e5e1e4022109199d53664812f21c258f2b95f1945792b99cbccdc +size 109832 diff --git a/assets/TitleShr.png b/assets/TitleShr.png new file mode 100644 index 0000000..627e4a0 --- /dev/null +++ b/assets/TitleShr.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6846cf0cc6fc16da5d290a2f395ed44cab1f98f38e652877744ebbd44c3429b8 +size 17887 diff --git a/assets/depackSpeechRam.py b/assets/depackSpeechRam.py new file mode 100755 index 0000000..765fac1 --- /dev/null +++ b/assets/depackSpeechRam.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +"""Run Space Taxi's depacker to produce a FLAT 64K RAM image. + +Why this exists: the disk's "SPACE TAXI+" is a crunched single file (it +matches the running game's memory in 0.6% of bytes), so the speech +samples cannot be read out of it statically. And a VICE memory dump is +no good either -- it captures the banked-IN view, so $A000-$BFFF comes +back as BASIC ROM and 13 of the 15 utterances are missing. Every +mem0000*.bin in this tree has that problem. + +The way out needs no emulator install: stuff/spacetaxi/cpu6502.py is a +plain 6502 core over a flat 64K bytearray with no banking and no I/O, +which is all a depacker needs. Load the .prg, run from the BASIC SYS +entry, and the depacked image IS the RAM -- $A000-$BFFF included. + +Feed the result to extractSpeech.py. + +Usage: depackSpeechRam.py [spaceTaxiPlus.prg] [out.bin] + Defaults resolve inside stuff/spacetaxi/ (gitignored research + area, same as genC64Data.py's inputs). +""" + +import os +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +STUFF = os.path.join(HERE, "..", "..", "..", "stuff", "spacetaxi") + +# The BASIC stub is `0B08 D307 9E "2059" 00` -- SYS 2059. +ENTRY = 0x080B +MAX_STEPS = 60000000 +CHECK_MASK = 0xFFFF # how often to test for completion + +# Completion test: the $9B00 index table is populated and the first +# utterance's $FE lead-in is in place. +TABLE_ADDR = 0x9B00 +FIRST_UTT = 0x9B40 + + +def main(): + prgPath = sys.argv[1] if len(sys.argv) > 1 else os.path.join(STUFF, "extract", "SPACE TAXI+.prg") + outPath = sys.argv[2] if len(sys.argv) > 2 else os.path.join(STUFF, "staxi-ram.bin") + + sys.path.insert(0, STUFF) + try: + from cpu6502 import Cpu6502 + except ImportError: + sys.exit("depackSpeechRam: need cpu6502.py in %s" % STUFF) + + prg = open(prgPath, "rb").read() + load = prg[0] | (prg[1] << 8) + ram = bytearray(65536) + ram[load:load + len(prg) - 2] = prg[2:] + print("loaded %s at $%04X..$%04X" % (os.path.basename(prgPath), load, load + len(prg) - 3)) + + cpu = Cpu6502(ram) + cpu.pc = ENTRY + steps = 0 + done = False + try: + while steps < MAX_STEPS: + cpu.step() + steps += 1 + if (steps & CHECK_MASK) == 0: + if ram[TABLE_ADDR] != 0 and ram[FIRST_UTT] == 0xFE: + done = True + break + except Exception as exc: + # A depacker that runs off into I/O or an undocumented opcode + # still usually gets the data down first, so report and carry on + # to the write rather than throwing the work away. + print("6502 stopped after %d steps at PC=$%04X: %s" % (steps, cpu.pc, exc)) + + open(outPath, "wb").write(bytes(ram)) + print("%d steps, depack %s -> %s" % (steps, "detected" if done else "NOT detected", outPath)) + if not done: + print("WARNING: completion check never fired; the image may be partial.") + + +if __name__ == "__main__": + main() diff --git a/assets/extractSpeech.py b/assets/extractSpeech.py new file mode 100755 index 0000000..e666e7f --- /dev/null +++ b/assets/extractSpeech.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 +"""Extract Space Taxi's digitised speech from a C64 memory dump. + +FORMAT (reverse-engineered from bankedSfxLoad at $9802, see +stuff/spacetaxi/live-level1.lst). It is NOT 4-bit $D418 digi and it is +NOT the SID speech synth. It is 1-BIT RUN-LENGTH audio: + + * An index table at $9B00 holds [code, ptrLo, ptrHi] triples, + terminated by a zero code. `code` is the PETSCII character the game + asks for -- 'H' 'T' '!' spell "Hey Taxi!", 'P' + digit + '?' spell + "Pad two, please", 'U' is "up". + * Each utterance is a byte stream. For every byte the player busy-waits + that many passes of a fixed delay loop and then TOGGLES bit 0 of + $D417, which flips voice 1 in and out of the filter -- a 1-bit + speaker. So each byte is the length of one half-cycle. + * $FE is a raster sync (the player waits on $D011), $FF ends the + utterance. + + The delay loop at $98A1..$98AA costs 27 cycles per pass and the + toggle/advance/fetch tail ($98AC..$987F) another 33, hence CYCLES(). + +LIMITATION: a plain VICE memory dump captures the BANKED-IN view, so +$A000-$BFFF comes back as BASIC ROM ("CBMBASIC" at $A004) and most of the +speech is missing -- the player banks BASIC out ($01 = $06) precisely +because the samples live in the RAM underneath. Dump it with the ROMs +banked out, e.g. in the VICE monitor: + + bank ram + save "staxi-ram.bin" 0 0000 ffff + +Usage: extractSpeech.py [outDir] + The dump may have a 2-byte load address or not; both are handled. +""" + +import os +import struct +import sys +import wave + +NTSC_HZ = 1022727.0 # the port already uses this (ST_SID_HZ_NUM) +OUT_RATE = 22050 +TABLE_ADDR = 0x9B00 +END_MARK = 0xFF +SYNC_MARK = 0xFE + + +def cyclesFor(count): + """Half-cycle length in CPU cycles for one stream byte.""" + return 27 * count + 33 + + +def loadDump(path): + """Return a 64K-addressable bytes object from a dump with or without + a load-address header.""" + raw = open(path, "rb").read() + if len(raw) in (65538, 65536 + 2): + return raw[2:] + if len(raw) == 65536: + return raw + # Headerless dump starting at $0801 (what mem0801.prg turned out to be). + pad = bytearray(65536) + pad[0x0801:0x0801 + len(raw)] = raw[:65536 - 0x0801] + return bytes(pad) + + +def readTable(mem): + """[(code, pointer)] from the $9B00 index.""" + out = [] + y = 0 + while y < 0x300: + code = mem[TABLE_ADDR + y] + if code == 0 or code == 0xFF: + break + out.append((code, mem[TABLE_ADDR + y + 1] | (mem[TABLE_ADDR + y + 2] << 8))) + y += 3 + return out + + +def decode(mem, ptr, limit=0x4000): + """Return (runs, byteCount, sawEnd). runs is [(level, cycles)].""" + runs = [] + level = 0 + addr = ptr + while addr - ptr < limit: + b = mem[addr] + addr += 1 + if b == END_MARK: + return runs, addr - ptr, True + if b == SYNC_MARK: + runs.append((level, int(NTSC_HZ / 60.0))) + continue + runs.append((level, cyclesFor(b))) + level ^= 1 + return runs, addr - ptr, False + + +def writeWav(runs, path): + samples = bytearray() + carry = 0.0 + perOut = NTSC_HZ / OUT_RATE + for level, cyc in runs: + carry += cyc + n = int(carry / perOut) + carry -= n * perOut + samples += struct.pack("= romLo: + continue + runs, nbytes, sawEnd = decode(mem, ptr) + nxt = next((q for _, q in sorted(table, key=lambda e: e[1]) if q > ptr), None) + if not sawEnd or (nxt is not None and nbytes > nxt - ptr): + continue + index.append((code, sum(len(b) for b in blobs), nbytes)) + blobs.append(bytes(mem[ptr:ptr + nbytes])) + data = b"".join(blobs) + + h = os.path.join(outDir, "stSpeechData.h") + c = os.path.join(outDir, "stSpeechData.c") + with open(h, "w") as fp: + fp.write("// Generated by assets/extractSpeech.py. Do not hand-edit.\n" + "//\n" + "// Space Taxi's digitised speech as the C64 stores it: 1-bit\n" + "// run-length streams. Each byte is one half-cycle length; the\n" + "// player toggles its output after each. $FE is a raster sync\n" + "// and $FF ends an utterance. stAudio.c expands these to PCM\n" + "// through the jlAudioPlaySfxStream fill callback.\n\n" + "#ifndef ST_SPEECH_DATA_H\n#define ST_SPEECH_DATA_H\n\n" + "#include \n\n" + "typedef struct {\n" + " uint8_t code; // PETSCII: 'H' 'T' '!' 'P' '1'..'9' '?' 'U'\n" + " uint16_t offset; // into kStSpeechRle\n" + " uint16_t length;\n" + "} StSpeechEntryT;\n\n" + "#define ST_SPEECH_COUNT %du\n" + "#define ST_SPEECH_RLE_BYTES %du\n\n" + "extern const StSpeechEntryT kStSpeechIndex[ST_SPEECH_COUNT];\n" + "extern const uint8_t kStSpeechRle[ST_SPEECH_RLE_BYTES];\n\n" + "#endif\n" % (len(index), len(data))) + with open(c, "w") as fp: + fp.write('// Generated by assets/extractSpeech.py. Do not hand-edit.\n\n' + '#include "stSpeechData.h"\n\n' + 'const StSpeechEntryT kStSpeechIndex[ST_SPEECH_COUNT] = {\n') + for code, off, ln in index: + fp.write(" { 0x%02Xu, %5du, %5du }, // '%s'\n" + % (code, off, ln, chr(code) if 33 <= code < 127 else "?")) + fp.write("};\n\nconst uint8_t kStSpeechRle[ST_SPEECH_RLE_BYTES] = {\n") + for i in range(0, len(data), 16): + fp.write(" " + " ".join("0x%02X," % b for b in data[i:i + 16]) + "\n") + fp.write("};\n") + print("\nemitted %s (%d entries) and %s (%d bytes)" % (h, len(index), c, len(data))) + + +def main(): + if len(sys.argv) < 2: + sys.exit(__doc__) + emit = "--c" in sys.argv + args = [a for a in sys.argv[1:] if a != "--c"] + mem = loadDump(args[0]) + outDir = args[1] if len(args) > 1 else "speech" + os.makedirs(outDir, exist_ok=True) + + if mem[0xA004:0xA00C] == b"CBMBASIC": + sys.stderr.write("WARNING: $A000 holds BASIC ROM, not the RAM under it.\n" + " Utterances at $A000 and above will not decode.\n" + " Re-dump with `bank ram` (see the module docstring).\n\n") + + table = readTable(mem) + romLo = 0xA000 if mem[0xA004:0xA00C] == b"CBMBASIC" else 0x10000 + ordered = sorted(p for _, p in table) + good = 0 + + print("%-4s %-6s %7s %7s %s" % ("code", "ptr", "bytes", "dur(s)", "file")) + for code, ptr in table: + label = chr(code) if 33 <= code < 127 else "?" + # Utterances are stored back to back, so the next pointer is the + # expected end. Both checks matter: BASIC ROM is full of $FF, so + # "found a terminator" alone happily reports success on garbage -- + # it claimed all 15 decoded from a ROM-shadowed dump. + nxt = next((q for q in ordered if q > ptr), None) + expected = (nxt - ptr) if nxt else None + if ptr >= romLo: + print("%-4s $%04X %7s %7s -- SKIPPED (shadowed by BASIC ROM in this dump)" + % (label, ptr, "-", "-")) + continue + runs, nbytes, sawEnd = decode(mem, ptr) + if not sawEnd: + print("%-4s $%04X %7d %7s -- SKIPPED (no $FF terminator)" % (label, ptr, nbytes, "-")) + continue + # The terminator must land BEFORE the next utterance starts. + # (Entries are back to back but may carry a pad byte, so this is + # <= rather than ==.) Overrunning means the $FF we found was not + # ours -- which is exactly what happens against a ROM shadow. + if expected is not None and nbytes > expected: + print("%-4s $%04X %7d %7s -- SKIPPED (ran %d bytes, next entry is %d away)" + % (label, ptr, nbytes, "-", nbytes, expected)) + continue + path = os.path.join(outDir, "%02X_%s.wav" % (code, label if label != "?" else "x")) + dur = writeWav(runs, path) + good += 1 + print("%-4s $%04X %7d %7.2f %s" % (label, ptr, nbytes, dur, os.path.basename(path))) + print("\n%d of %d utterances decoded into %s" % (good, len(table), outDir)) + if emit: + emitC(mem, table, romLo, outDir) + + +if __name__ == "__main__": + main() diff --git a/assets/genC64Data.py b/assets/genC64Data.py index c0feaa5..6c6f130 100644 --- a/assets/genC64Data.py +++ b/assets/genC64Data.py @@ -55,6 +55,21 @@ def c_bytes(data, per_line=16, indent=" "): return "\n".join(lines) +# The cross-segment accessors the IIgs needs (see the block this emits +# into stC64Data.c). These used to be hand-added to the GENERATED file, +# which meant re-running this script silently deleted them and broke the +# IIgs build -- the header kept declaring them and nothing defined them. +# Prototype and return expression per accessor; the header prints the +# prototypes aligned, so keep the spacing. +ACCESSORS = [ + ("const uint8_t *stC64Charset(void);", "&kStCharset[0][0]"), + ("const uint8_t *stC64SpriteBitmap(uint8_t idx);", "kStSpriteBitmaps[idx]"), + ("uint8_t stC64RngByte(uint8_t idx);", "kStRngTable[idx]"), + ("const uint8_t *stC64SfxProgram(uint8_t idx);", "kStSfxPrograms[idx]"), + ("uint8_t stC64FlameCel(uint8_t dirMask);", "kStFlameCelByDirMask[dirMask]"), +] + + def main(): raw = load_raw() sprite_count = SPRITE_PTR_LAST - SPRITE_PTR_FIRST + 1 @@ -100,9 +115,11 @@ def main(): h.append("// 8=RIGHT); 0 = no cel for that combination.") h.append("extern const uint8_t kStFlameCelByDirMask[16];") h.append("") - h.append("// Level-intro star velocities per sprite 0..6 ($450C / $4513).") - h.append("extern const int8_t kStIntroStarDx[7];") - h.append("extern const int8_t kStIntroStarDy[7];") + h.append("// Cross-segment-safe accessors (see stC64Data.c): reach the tables above") + h.append("// by an inter-segment call on the IIgs, where a direct 2-byte data") + h.append("// reference from another bank is not relocatable.") + for decl in ACCESSORS: + h.append(decl[0]) h.append("") h.append("#endif") @@ -143,11 +160,25 @@ def main(): c.append(c_bytes(flame)) c.append("};") c.append("") - dx = [b - 256 if b >= 128 else b for b in raw[0x450C:0x450C + 7]] - dy = [b - 256 if b >= 128 else b for b in raw[0x4513:0x4513 + 7]] - c.append("const int8_t kStIntroStarDx[7] = { " + ", ".join(str(v) for v in dx) + " };") - c.append("const int8_t kStIntroStarDy[7] = { " + ", ".join(str(v) for v in dy) + " };") c.append("") + c.append("// ---------------------------------------------------------------------------") + c.append("// Accessors. On the IIgs the program is split across banks and a 2-byte") + c.append("// data reference cannot cross a segment (only a 3-byte JSL/JML can). These") + c.append("// tiny functions live in the same segment as the tables, so their reads") + c.append("// are intra-segment; callers in other segments reach them by a supported") + c.append("// inter-segment call. On the other ports they inline away to nothing.") + c.append("// ---------------------------------------------------------------------------") + first = True + for decl, body in ACCESSORS: + c.append("") + if not first: + c.append("") + first = False + # The header pads return types to align its prototypes; the + # definitions here must not inherit that padding. + c.append(" ".join(decl[:-1].split()) + " {") + c.append(" return " + body + ";") + c.append("}") with open(OUT_H, "w", encoding="ascii") as fp: fp.write("\n".join(h) + "\n") diff --git a/assets/genSongData.py b/assets/genSongData.py new file mode 100644 index 0000000..8610eab --- /dev/null +++ b/assets/genSongData.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# Bake examples/spacetaxi/assets/songs/song*.song into stSongData.h. +# +# python3 examples/spacetaxi/assets/genSongData.py +# +# The .song sources come from the game's own player run under a 6502 +# emulator: assets/sidCapture.py logs every SID write per PAL frame and +# assets/sidToSong.py turns that into songbake text. +import os +import subprocess +import sys +import tempfile + +here = os.path.dirname(os.path.abspath(__file__)) +repo = os.path.abspath(os.path.join(here, "..", "..", "..")) +songbake = os.path.join(repo, "tools", "songbake", "songbake.py") +out = os.path.join(here, "..", "stSongData.h") +count = 9 + +header = """// Space Taxi (C64) tunes as JoeyLib JYM1 streams. GENERATED by +// assets/genSongData.py -- do not hand-edit. The songs were extracted by +// running the game's own music player under a 6502 emulator +// (assets/sidCapture.py logs every SID write per PAL frame; +// assets/sidToSong.py turns that into songbake text in assets/songs/). +// +// Where the C64 plays them: +// BLOCKING via $44CF (the game spins until the tune ends; the port +// parks in ST_STATE_JINGLE with the sim frozen): +// 0..3 start of every screen, rotating on $5E9B ($6906) +// 4 level 25 start ($6935) and the bonus cab ($70B1) +// 5 game over ($5C1F), after the two-second GAME OVER! wait +// NON-BLOCKING via $CB02 (started and left to the IRQ player): +// 6 the high-score name entry screen ($4E35) -- no port screen yet +// 7 the high-score table ($4C1F, joystick UP on the title) +// 8 the level intro while the cab flies in ($459C); endless, so +// the source is trimmed to 30 s and looped +#ifndef ST_SONG_DATA_H +#define ST_SONG_DATA_H + +#include + +""" + +footer = """typedef struct { + const uint8_t *data; + uint16_t length; +} StSongT; + +#define ST_SONG_COUNT %du +#define ST_SONG_BONUS 4u +#define ST_SONG_GAME_OVER 5u +#define ST_SONG_NAME_ENTRY 6u +#define ST_SONG_SCORES 7u +#define ST_SONG_INTRO 8u + +static const StSongT kStSongs[ST_SONG_COUNT] = { +%s}; + +#endif // ST_SONG_DATA_H +""" + +parts = [] +for n in range(count): + src = os.path.join(here, "songs", "song%d.song" % n) + with tempfile.NamedTemporaryFile(suffix=".h", delete=False) as tmp: + path = tmp.name + subprocess.run([sys.executable, songbake, "-c", "gStSong%d" % n, src, path], check=True, stderr=subprocess.DEVNULL) + body = open(path).read() + os.unlink(path) + body = "\n".join(l for l in body.split("\n") if l.startswith("static") or l.startswith(" ") or l.startswith("}")) + parts.append(body.rstrip() + "\n\n") + +table = "".join(" { gStSong%d, (uint16_t)sizeof(gStSong%d) },\n" % (n, n) for n in range(count)) +with open(out, "w") as fp: + fp.write(header + "".join(parts) + footer % (count, table)) +print("wrote", os.path.normpath(out)) diff --git a/assets/genSpeechPcm.py b/assets/genSpeechPcm.py new file mode 100755 index 0000000..e707924 --- /dev/null +++ b/assets/genSpeechPcm.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""genSpeechPcm.py - bake Space Taxi's speech to 8-bit PCM. +Reads the C64 run-length speech streams from stSpeechData.c (the output +of extractSpeech.py) and writes speech.bin: every utterance decoded to +5 kHz sample bytes for each passenger pitch the game can select, in BOTH +polarities. The decode is the exact algorithm stAudio.c used to run per +sample at play time; on the 65816 that took 2.4 s of CPU per second of +speech (three run bytes per sample, each a multiply) and stalled the ride +for half a second on every "HEY TAXI". A 1-bit intermediate format came +next, but expanding bits still needed a per-byte loop (and an asm copy of +it on the IIgs); with sample bytes the player is a block copy. +Polarity: an utterance is decoded from a +100 start. The C64 keeps the +output level running from one utterance into the next, so a phrase whose +first word ends on the low level plays its second word inverted. Both +versions are stored and the player picks by the running parity. +speech.bin (little-endian): + "STSP" u8 version(2) u8 pitchBase u8 pitchCount u8 count + u8 format (0 = signed +/-100, 1 = 0x80-biased for the IIgs raw stream) + u8 pad[3] u32 dataBytes + entries[pitchCount * count * 2]: u8 code, u8 parity, u16 samples, + u32 offset -- polarity is the innermost index (0 = as decoded, + 1 = inverted). parity = 1 when the utterance ends on the + opposite level from the one it started on. + data: sample bytes +usage: genSpeechPcm.py stSpeechData.c speech.bin [--biased] +""" +import re +import struct +import sys + +SID_HZ = 1022727 # ST_SID_HZ_NUM +RATE = 5000 # ST_SPEECH_RATE +CYC_PER_SAMPLE = SID_HZ // RATE +SYNC_CYCLES = SID_HZ // 60 // 8 +CYCLE_TAIL = 33 +SYNC = 0xFE +END = 0xFF +PITCH_BASE = 2 # stFare.c: stAudioSpeech(rng(3) + 2) +PITCH_COUNT = 3 + + +def parseSource(path): + text = open(path).read() + idx = re.search(r"kStSpeechIndex\[[^\]]*\]\s*=\s*\{(.*?)\};", text, re.S).group(1) + entries = [(int(c, 16), int(o), int(n)) for c, o, n in + re.findall(r"\{\s*0x([0-9A-Fa-f]+)u,\s*(\d+)u,\s*(\d+)u\s*\}", idx)] + rle = re.search(r"kStSpeechRle\[[^\]]*\]\s*=\s*\{(.*?)\};", text, re.S).group(1) + data = bytes(int(x, 16) for x in re.findall(r"0x([0-9A-Fa-f]{2})", rle)) + return entries, data + + +def decode(rle, perOn): + """speechDecode() for one utterance from a +100 start, remain 0.""" + remain = 0 + level = 100 + out = [] + i = 0 + n = len(rle) + while True: + while remain <= 0: + if i >= n: + return out, level + b = rle[i] + i += 1 + if b == END: + i = n + continue + if b == SYNC: + remain += SYNC_CYCLES + continue + remain += perOn * b + CYCLE_TAIL + level = -100 if level > 0 else 100 + out.append(level) + remain -= CYC_PER_SAMPLE + + +def main(): + args = [a for a in sys.argv[1:] if not a.startswith("--")] + biased = "--biased" in sys.argv[1:] + src, dst = args[0], args[1] + entries, data = parseSource(src) + records = [] + blob = bytearray() + for p in range(PITCH_COUNT): + perOn = 5 * (PITCH_BASE + p) + 12 + for code, off, length in entries: + samples, level = decode(data[off:off + length], perOn) + parity = 1 if level < 0 else 0 + for pol in range(2): + pcm = bytearray() + for s in samples: + v = -s if pol else s + pcm.append((v + 0x80) & 0xFF if biased else v & 0xFF) + records.append((code, parity, len(samples), len(blob))) + blob += pcm + with open(dst, "wb") as f: + f.write(b"STSP" + struct.pack(" RTS + cpu = SidTrap(ram) + cpu.call(0xCC00) # full reset first + ram[0xCB01] = song + cpu.call(0xCB00) # select + arm + for frame in range(MAX_FRAMES): + cpu.frame = frame + ram[0xA2] = (ram[0xA2] + 1) & 0xFF + cpu.call(0xCF52) + if ram[0xCB81] == 0 and ram[0xCB88] == 0 and ram[0xCB8F] == 0: + break + songs[song] = {"frames": frame + 1, "writes": cpu.log, + "ptr": ram[0xCB40 + song * 2] | (ram[0xCB41 + song * 2] << 8)} + print(f"song {song}: ptr=${songs[song]['ptr']:04X} frames={frame+1} " + f"({(frame+1)/50:.1f}s PAL) sidWrites={len(cpu.log)}") + +json.dump(songs, open(OUT, "w")) diff --git a/assets/sidToSong.py b/assets/sidToSong.py new file mode 100644 index 0000000..43ede3f --- /dev/null +++ b/assets/sidToSong.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""SID register log (sidCapture.py) -> JoeyLib .song text for songbake. + +One row per PAL frame (tickms 20). Per voice, a note-on is a gate rising +edge or a pitch change while gated; gate falling is 'off'. SID frequency -> +Hz exactly (@Hz cells), so no equal-temperament rounding. Attenuation comes +from the sustain nibble. A voice whose waveform is NOISE goes to the noise +column instead of a tone column, with a pitch scaled from its frequency. +Loop detection: the player is deterministic, so once the (frame-relative) +event rows repeat with a fixed period, that period is the loop. +""" +import json, sys + +PAL_CLOCK = 985248.0 +songs = json.load(open(sys.argv[1])) +outDir = sys.argv[2] + + +def sidHz(lo, hi): + return ((hi << 8) | lo) * PAL_CLOCK / 16777216.0 + + +def rowsFor(writes, nframes): + # replay register writes frame by frame, producing per-frame voice state + regs = [0] * 0x19 + state = [] + byFrame = {} + for f, r, v in writes: + byFrame.setdefault(f, []).append((r, v)) + for f in range(nframes): + for r, v in byFrame.get(f, []): + regs[r] = v + vs = [] + for vc in range(3): + b = vc * 7 + vs.append(dict(hz=sidHz(regs[b], regs[b + 1]), gate=regs[b + 4] & 1, + wave=regs[b + 4] & 0xF0, sus=regs[b + 6] >> 4)) + state.append(vs) + return state + + +def atten(sus): + # SID sustain 0..15 -> AGI attenuation 14..0 (0 loudest) + return max(0, min(14, round(14 - sus * 14 / 15))) + + +def noisePitch(hz): + # brighter hiss for higher SID "frequency"; 0..31, 0 = brightest + p = int(31 - min(31, hz / 400.0)) + return max(0, min(31, p)) + + +def toRows(state): + rows = [] + prev = [dict(hz=None, gate=0, wave=0, sus=0) for _ in range(3)] + for vs in state: + tone = ["---", "---", "---"] + noise = "---" + for vc in range(3): + cur, was = vs[vc], prev[vc] + isNoise = (cur["wave"] & 0x80) != 0 + on = cur["gate"] and (not was["gate"] or abs(cur["hz"] - was["hz"]) > 0.5 + or cur["wave"] != was["wave"]) + off = was["gate"] and not cur["gate"] + if on: + if isNoise: + noise = f"N{noisePitch(cur['hz'])}:{atten(cur['sus'])}" + else: + hz = max(1, min(65535, int(round(cur["hz"])))) + tone[vc] = f"@{hz}:{atten(cur['sus'])}" + elif off: + if (was["wave"] & 0x80): + noise = "off" + else: + tone[vc] = "off" + prev[vc] = dict(cur) + rows.append((tone, noise)) + return rows + + +def findLoop(rows): + # smallest (offset, period) with rows[o+k] == rows[o+p+k] for the tail + n = len(rows) + for p in range(8, n // 2): + for o in range(0, n - 2 * p): + if rows[o:o + p] == rows[o + p:o + 2 * p] and rows[o:n - p] == rows[o + p:n]: + return o, p + return None + + +for num, s in sorted(songs.items(), key=lambda kv: int(kv[0])): + num = int(num) + st = rowsFor(s["writes"], s["frames"]) + rows = toRows(st) + loop = findLoop(rows) if s["frames"] >= 4000 else None + if loop: + o, p = loop + rows = rows[:o + p] + lines = ["; Space Taxi (C64) song %d, extracted by running the game's own" % num, + "; player ($CB00/$CF52) under cpu6502.py and logging SID writes.", + "; One row = one PAL frame.", "tickms 20"] + for i, (tone, noise) in enumerate(rows): + if loop and i == loop[0]: + lines.append("loop") + cells = tone + ([noise] if noise != "---" or True else []) + lines.append(" ".join(cells)) + open(f"{outDir}/song{num}.song", "w").write("\n".join(lines) + "\n") + events = sum(1 for t, n in rows for c in t + [n] if c != "---") + waves = sorted({hex(v["wave"]) for vs in st for v in vs if v["gate"]}) + print(f"song {num}: {len(rows)} rows, {events} events, waveforms {waves}" + + (f", LOOP at row {loop[0]} period {loop[1]}" if loop else "")) diff --git a/assets/songs/song0.song b/assets/songs/song0.song new file mode 100644 index 0000000..7e6036a --- /dev/null +++ b/assets/songs/song0.song @@ -0,0 +1,94 @@ +; Space Taxi (C64) song 0, extracted by running the game's own +; player ($CB00/$CF52) under cpu6502.py and logging SID writes. +; One row = one PAL frame. +tickms 20 +--- --- --- --- +@336:2 @424:2 --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@378:2 @424:2 --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@336:2 @424:2 --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@378:2 @476:2 --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@318:2 @252:2 --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @476:2 --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@283:2 @252:2 --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @476:2 --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@252:2 @252:2 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- diff --git a/assets/songs/song1.song b/assets/songs/song1.song new file mode 100644 index 0000000..e067e38 --- /dev/null +++ b/assets/songs/song1.song @@ -0,0 +1,94 @@ +; Space Taxi (C64) song 1, extracted by running the game's own +; player ($CB00/$CF52) under cpu6502.py and logging SID writes. +; One row = one PAL frame. +tickms 20 +--- --- --- --- +@252:7 @159:7 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +@283:7 @168:7 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +@252:7 @189:7 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +@283:7 @212:7 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +@318:7 @189:7 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +@336:7 @168:7 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +@318:7 @159:7 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +@336:7 @168:7 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +@378:7 @189:7 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- diff --git a/assets/songs/song2.song b/assets/songs/song2.song new file mode 100644 index 0000000..fe58aa5 --- /dev/null +++ b/assets/songs/song2.song @@ -0,0 +1,86 @@ +; Space Taxi (C64) song 2, extracted by running the game's own +; player ($CB00/$CF52) under cpu6502.py and logging SID writes. +; One row = one PAL frame. +tickms 20 +--- --- --- --- +@126:0 @159:0 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +off --- --- --- +@126:0 @168:0 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +off --- --- --- +@141:0 @189:0 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +off --- --- --- +@159:0 @159:0 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +off --- --- --- +@159:0 @168:0 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +off --- --- --- +@168:0 @189:0 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +off --- --- --- +@168:0 @159:0 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +off --- --- --- +@189:0 @168:0 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +off --- --- --- +--- @159:0 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- @126:0 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- diff --git a/assets/songs/song3.song b/assets/songs/song3.song new file mode 100644 index 0000000..20a3f8d --- /dev/null +++ b/assets/songs/song3.song @@ -0,0 +1,86 @@ +; Space Taxi (C64) song 3, extracted by running the game's own +; player ($CB00/$CF52) under cpu6502.py and logging SID writes. +; One row = one PAL frame. +tickms 20 +--- --- --- --- +@252:0 @336:2 @336:2 --- +--- --- --- --- +--- --- --- --- +off off off --- +--- --- --- --- +@283:0 --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +@318:0 @336:2 @336:2 --- +--- --- --- --- +--- --- --- --- +off off off --- +--- --- --- --- +@252:0 --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +@283:0 @336:2 @336:2 --- +--- --- --- --- +--- --- --- --- +off off off --- +--- --- --- --- +@252:0 --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +@283:0 @336:2 @336:2 --- +--- --- --- --- +--- --- --- --- +off off off --- +--- --- --- --- +@476:0 --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- @318:2 @378:2 --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @336:2 @378:2 --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @283:2 @252:2 --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @252:2 @476:2 --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- diff --git a/assets/songs/song4.song b/assets/songs/song4.song new file mode 100644 index 0000000..e32f538 --- /dev/null +++ b/assets/songs/song4.song @@ -0,0 +1,126 @@ +; Space Taxi (C64) song 4, extracted by running the game's own +; player ($CB00/$CF52) under cpu6502.py and logging SID writes. +; One row = one PAL frame. +tickms 20 +--- --- --- --- +@504:5 @449:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@504:5 @449:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +@673:5 @504:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@504:5 @424:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@673:5 @504:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +@848:5 @673:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @635:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @673:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- diff --git a/assets/songs/song5.song b/assets/songs/song5.song new file mode 100644 index 0000000..ddf5ada --- /dev/null +++ b/assets/songs/song5.song @@ -0,0 +1,736 @@ +; Space Taxi (C64) song 5, extracted by running the game's own +; player ($CB00/$CF52) under cpu6502.py and logging SID writes. +; One row = one PAL frame. +tickms 20 +--- --- --- --- +@504:2 @159:2 @106:10 --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- @212:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @159:2 @106:10 --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- @212:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @159:2 @106:10 --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- @212:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @159:2 @106:10 --- +--- --- --- --- +--- --- --- --- +--- off --- --- +off --- --- --- +--- --- @212:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@504:2 @159:2 @106:10 --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- @212:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @159:2 @106:10 --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- @212:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @159:2 @106:10 --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- @212:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @159:2 @106:10 --- +--- --- --- --- +--- --- --- --- +--- off --- --- +off --- --- --- +--- --- @212:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@504:2 @159:2 @106:10 --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- @212:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @159:2 @106:10 --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- @212:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @159:2 @106:10 --- +--- --- --- --- +--- --- --- --- +--- off --- --- +off --- --- --- +--- --- @212:10 --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +@566:2 @141:2 @100:10 --- +--- --- --- --- +--- --- --- --- +--- off --- --- +off --- --- --- +--- --- @200:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@566:2 @141:2 @100:10 --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- @200:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @141:2 @100:10 --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- @200:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @141:2 @100:10 --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- @200:10 --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +--- @141:2 @100:10 --- +--- --- --- --- +--- --- --- --- +--- off --- --- +off --- --- --- +--- --- @200:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@635:2 @504:2 @94:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @94:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@673:2 @566:2 @84:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- @168:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @84:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @168:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@755:2 @635:2 @79:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- @159:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @79:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @159:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@800:2 @635:2 @71:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- @141:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @71:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @141:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@848:2 @635:2 @67:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @133:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @67:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @133:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @67:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @133:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @67:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @133:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@424:2 @318:2 @67:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- @133:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@848:2 @336:2 @252:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +@848:2 @336:2 @252:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +@848:2 @336:2 @252:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +@755:2 @318:2 @225:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +@755:2 @318:2 @225:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +@673:2 @283:2 @212:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +@635:2 @252:2 @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@566:2 @283:2 @168:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@755:2 @318:2 @126:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @126:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @126:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @126:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @126:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@635:2 --- @126:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@504:2 --- @126:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@635:2 --- @126:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +@755:2 @283:2 @119:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @119:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@848:2 --- @119:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @119:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@755:2 --- @119:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@673:2 --- @119:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@635:2 --- @119:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@566:2 --- @119:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- @189:10 --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +@504:2 @318:2 @126:10 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +--- --- --- --- diff --git a/assets/songs/song6.song b/assets/songs/song6.song new file mode 100644 index 0000000..f26cc4d --- /dev/null +++ b/assets/songs/song6.song @@ -0,0 +1,840 @@ +; Space Taxi (C64) song 6, extracted by running the game's own +; player ($CB00/$CF52) under cpu6502.py and logging SID writes. +; One row = one PAL frame. +tickms 20 +--- --- --- --- +--- @378:1 @504:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @378:1 @504:1 --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @378:1 @504:1 --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @378:1 @504:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@94:0 --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@126:0 --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@159:0 --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@189:0 @336:1 @449:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @300:1 @400:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @336:1 @449:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @378:1 @504:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @378:1 @504:1 --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off @378:1 @504:1 --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @378:1 @504:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@212:0 --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@189:0 --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@159:0 --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- @378:1 @504:1 --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @378:1 @504:1 --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@141:0 @424:1 @566:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @378:1 @504:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @424:1 @566:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@119:0 @476:1 @635:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @476:1 @635:1 --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @476:1 @635:1 --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @476:1 @635:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@59:0 --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @476:1 @635:1 --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @476:1 @635:1 --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @476:1 @635:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @424:1 @566:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @476:1 @635:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@126:0 @504:1 @673:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @504:1 @673:1 --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @504:1 @673:1 --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @504:1 @673:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@63:0 --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @504:1 @673:1 --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off @504:1 @673:1 --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@126:0 @566:1 @755:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @504:1 @673:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @566:1 @755:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@141:0 @635:1 @848:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@71:0 --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @635:1 @848:1 --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off @635:1 @848:1 --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@141:0 @566:1 @848:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @566:1 @848:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @566:1 @848:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- off off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@159:0 @534:1 @848:1 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- off --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- diff --git a/assets/songs/song7.song b/assets/songs/song7.song new file mode 100644 index 0000000..fea5ac3 --- /dev/null +++ b/assets/songs/song7.song @@ -0,0 +1,852 @@ +; Space Taxi (C64) song 7, extracted by running the game's own +; player ($CB00/$CF52) under cpu6502.py and logging SID writes. +; One row = one PAL frame. +tickms 20 +--- --- --- --- +@378:0 @1270:5 @126:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1346:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- @1132:5 @119:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:5 off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1346:5 --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@252:0 @1270:5 @106:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1346:5 off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1510:5 --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@283:0 @1008:5 @94:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@318:0 --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@336:0 --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1008:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@378:0 --- @126:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @952:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @848:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @755:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1132:5 @119:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:5 off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1346:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1510:5 @126:7 --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1510:5 off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1510:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@424:0 @1132:5 @94:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +@378:0 --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@336:0 @1132:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@378:0 @1270:5 @126:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- off --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@336:0 @952:5 @119:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +@318:0 --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@283:0 @952:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@318:0 @1008:5 @106:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- off --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@283:0 @755:5 @94:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +@252:0 --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@238:0 @755:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +@252:0 @1008:5 @126:7 --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @952:5 off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@318:0 @1008:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +@189:0 @1132:5 @119:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1008:5 off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1132:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:5 @106:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1132:5 off --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@238:0 @1346:5 @94:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@252:0 @1270:5 off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@283:0 @1346:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +@252:0 @1270:5 @126:7 --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@238:0 @1510:5 @119:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@252:0 --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@283:0 --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@318:0 --- @106:7 --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@283:0 @1510:5 @94:7 --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +@283:0 --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@378:0 @1132:5 @141:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1008:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @952:5 @119:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1008:5 off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1132:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @755:5 @106:7 --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@378:0 @1132:5 --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@378:0 --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@378:0 @1199:5 @94:7 --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@378:0 --- off --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +@378:0 --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@378:0 @1270:5 @126:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1510:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1510:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @94:7 --- +--- @1510:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1510:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1510:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- @126:7 --- +--- --- --- --- +--- @1270:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1510:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1510:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:5 --- --- +--- --- --- --- +--- --- @94:7 --- +--- --- --- --- +--- --- --- --- +--- @1510:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1510:5 --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:5 --- --- +--- --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- @126:7 --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +off --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- off --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- diff --git a/assets/songs/song8.song b/assets/songs/song8.song new file mode 100644 index 0000000..93a08c5 --- /dev/null +++ b/assets/songs/song8.song @@ -0,0 +1,1508 @@ +; Space Taxi (C64) song 8, extracted by running the game's own +; player ($CB00/$CF52) under cpu6502.py and logging SID writes. +; One row = one PAL frame. +; The C64 tune never ends (no state repeat in 12000 frames); the level +; intro it underscores lasts a few seconds, so the first 1500 rows (30 s) +; are kept and looped back to the start. +tickms 20 +loop +--- --- --- --- +@31:7 @126:1 --- N28:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @178:1 --- --- +--- --- --- --- +--- off --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- @212:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @252:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @336:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@33:7 --- --- N30:10 +--- --- --- --- +--- @449:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @504:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @599:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @755:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @898:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@35:7 --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1008:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- @1346:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1585:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:1 --- --- +--- --- --- --- +--- off --- N28:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- @1008:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +@33:7 --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @800:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @673:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @566:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @504:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @449:1 --- --- +--- --- --- --- +@31:7 off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @378:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @318:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @252:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @212:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@33:7 @159:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @126:1 --- --- +--- --- --- --- +--- off --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- @178:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N28:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @212:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @252:1 --- --- +--- --- --- --- +--- off --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@35:7 --- --- N29:10 +--- --- --- --- +--- @336:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @449:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @504:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @599:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @755:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@33:7 --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @898:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @1008:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1346:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1585:1 --- --- +--- --- --- --- +--- off --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- @1270:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +@31:7 --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1008:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N28:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @800:1 --- --- +--- --- --- --- +--- off --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- @673:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @566:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @504:1 --- --- +--- --- --- --- +@33:7 off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @449:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @378:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @318:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @252:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@35:7 @212:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @159:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @126:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @178:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @212:1 --- --- +--- --- --- --- +--- off --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@33:7 --- --- N28:10 +--- --- --- --- +--- @252:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @336:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @449:1 --- --- +--- --- --- --- +--- off --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @504:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @599:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@31:7 --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @755:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @898:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1008:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1346:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @1585:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +@33:7 --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1008:1 --- --- +--- --- --- --- +--- off --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- @800:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @673:1 --- N28:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @566:1 --- --- +--- --- --- --- +@35:7 off --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- @504:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @449:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @378:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @318:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@33:7 @252:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @212:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @159:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @126:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @178:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@31:7 --- --- N29:10 +--- --- --- --- +--- @212:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @252:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @336:1 --- --- +--- --- --- --- +--- off --- N28:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- @449:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @504:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@33:7 --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @599:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @755:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @898:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1008:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @1346:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +@35:7 --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1585:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @1008:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @800:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @673:1 --- --- +--- --- --- --- +@33:7 off --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- @566:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N28:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @504:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @449:1 --- --- +--- --- --- --- +--- off --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- @378:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@31:7 @318:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @252:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @212:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @159:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @126:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@33:7 --- --- N30:10 +--- --- --- --- +--- @178:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @212:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @252:1 --- --- +--- --- --- --- +--- off --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- @336:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @449:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@35:7 --- --- N28:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @504:1 --- --- +--- --- --- --- +--- off --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- @599:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @755:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @898:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @1008:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +@33:7 --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1346:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1585:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @1270:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1008:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @800:1 --- --- +--- --- --- --- +@31:7 off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @673:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @566:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @504:1 --- --- +--- --- --- --- +--- off --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N28:10 +--- --- --- --- +--- @449:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@33:7 @378:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @318:1 --- --- +--- --- --- --- +--- off --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @252:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @212:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @159:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@35:7 --- --- N30:10 +--- --- --- --- +--- @126:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @178:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @212:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @252:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @336:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@33:7 --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @449:1 --- --- +--- --- --- --- +--- off --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- @504:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @599:1 --- N28:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @755:1 --- --- +--- --- --- --- +--- off --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- @898:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +@31:7 --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1008:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1346:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @1585:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1270:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @1008:1 --- --- +--- --- --- --- +@33:7 off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- @800:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @673:1 --- N30:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @566:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- @504:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@35:7 @449:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @378:1 --- --- +--- --- --- --- +--- off --- N28:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- @318:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @252:1 --- N29:10 +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- N29:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- @212:1 --- --- +--- --- --- --- +--- off --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- +@33:7 --- --- N30:10 +--- --- --- --- +--- @159:1 --- --- +--- --- --- --- +--- off --- --- +--- --- --- --- +--- --- --- N30:10 +--- --- --- --- +--- --- --- --- +--- --- --- --- +--- --- --- --- diff --git a/spacetaxi.c b/spacetaxi.c index c326d5c..f9f063b 100644 --- a/spacetaxi.c +++ b/spacetaxi.c @@ -19,27 +19,20 @@ #include -#include "spacetaxi.h" -#include "stDemoStreams.h" +#include -// Diagnostics. The logger drags in vsnprintf (with newlib that pulls the -// whole formatted-output and float-conversion family, ~45 KB) plus a -// multi-KB ring buffer, and the game has no need of either. That is -// unaffordable on the IIgs, whose bank-0 BSS budget cannot spare the -// ring, and on the X68000, which ships on a Human68k floppy with about -// 330 KB of usable space for the binary AND its levels. Both compile it -// out; the ports with room keep it. -#if defined(__W65816__) || defined(JOEYLIB_PLATFORM_X68000) - #define ST_LOG_RESET() ((void)0) - #define ST_LOG(...) ((void)0) -#else - #define ST_LOG_RESET() jlLogReset() - #define ST_LOG(...) jlLogF(__VA_ARGS__) -#endif +#include "spacetaxi.h" +#include "stSongData.h" +#include "stDemoStreams.h" #define ST_TICK_HZ 30u #define ST_MAX_CATCHUP_TICKS 4u -#define ST_LEVEL_COUNT 24u +// Screens A..X, then screen Y -- the 25th, " MYSTERY SCREEN !!! " -- which +// $5167 loads once the 24-hour shifts have cleared all of A..X. Files are +// levels/level01.dat .. level25.dat, so Y is index 24. +#define ST_LEVEL_COUNT 24u // A..X, the screens a shift sweeps +#define ST_SCREEN_Y 24u // the 25th screen ($5171 LDA #$59) +#define ST_LEVEL_FILES 25u // level01.dat .. level25.dat // "GET READY" shows over the old screen while the C64 cycles the // leaving-passenger sprites (three busy waits, about half a second). #define ST_GET_READY_TICKS 15u @@ -48,6 +41,18 @@ // Menu joystick repeat delay ($4101 with X = $82). #define ST_MENU_REPEAT_TICKS 8u +// The immortal cabbies ($4A89 image, $4E35 screen). +#define ST_HS_ENTRIES 10u +#define ST_HS_NAME_CHARS 15u +#define ST_HS_ENTRY_BYTES 24u // score[7] 0 name[15] shift +#define ST_HS_POINT_AT 4u // "dddd.dd" +#define ST_HS_GREET_DIGIT_AT 23u // "CONGRATULATIONS CABBIE n," +#define ST_HS_NAME_COL 12u // $0664: row 15, column 12 +#define ST_HS_NAME_ROW 15u +#define ST_HS_CURSOR '%' // $4F4D flips it against blank +#define ST_HS_BLINK_TICKS 3u // $4101 x $1E between flips, ~55 ms +#define ST_HS_SAVE_NAME "IMMORTAL" + typedef enum { ST_STATE_TITLE = 0, ST_STATE_HIGHSCORES, @@ -57,7 +62,9 @@ typedef enum { ST_STATE_GET_READY, ST_STATE_INTRO, ST_STATE_PLAYING, - ST_STATE_GAME_OVER + ST_STATE_GAME_OVER, + ST_STATE_JINGLE, // a $44CF tune sounding; sim frozen until it ends + ST_STATE_NAME_ENTRY // $4E35: a top-ten cabbie types a name } StStateE; typedef struct { @@ -73,8 +80,39 @@ typedef struct { bool fireArmed; // $445D: FIRE must be released first uint8_t lastFrame; uint16_t paceAcc; + StStateE afterJingle; // where ST_STATE_JINGLE returns to + bool gameOverTune; // $5C24: song 5 started after the wait + uint8_t hsSlot; // $4D51 table slot being named + uint8_t hsLen; // $4D2C characters typed + uint8_t hsCursor; // $4F4D the blinking cursor's current glyph + uint8_t hsBlinkTicks; // ticks toward the next cursor flip + bool paused; // $4FCB: SHIFT is holding the game } StGameT; +// The immortal cabbies ($4A89): ten entries of a right-aligned "dddd.dd" +// score, a name of up to fifteen letters and the shift it was earned on. +// The C64 keeps this table on its own disk (the $9948 IEC routines load +// it at boot and save it after every entry); the port keeps the same +// 240-byte image in a save file. Shipped contents as the dump has them. +typedef struct { + uint8_t score[ST_NUMBER_CHARS + 1u]; // ASCII, NUL-terminated + uint8_t name[ST_HS_NAME_CHARS + 1u]; // ASCII, space-padded, NUL-terminated + uint8_t shift; // 1..5 +} StHighScoreT; + +static StHighScoreT gHighScores[ST_HS_ENTRIES] = { + { "3877.56", "MICHAEL PLATE ", 4u }, + { "1809.51", "MICHAEL PLATE ", 2u }, + { "1179.87", "ANDREAS PLATE ", 4u }, + { " 892.65", "MICHAEL PLATE ", 1u }, + { " 685.37", "ANDREAS PLATE ", 1u }, + { " 629.45", "MICHAEL PLATE ", 4u }, + { " 569.50", "MICHAEL PLATE ", 1u }, + { " 504.97", "ANDREAS PLATE ", 2u }, + { " 498.77", "ANDREAS PLATE ", 5u }, + { " 490.85", "ANDREAS PLATE ", 2u }, +}; + // Menu texts ($5347..$545D) in screen codes. static const uint8_t kTextGameVariations[] = "GAME VARIATIONS"; static const uint8_t kTextCabbies[] = "SELECT NUMBER OF CABBIES:"; @@ -94,18 +132,14 @@ static const uint8_t kTextGetReady[] = " GET READY FOR A RIDE "; static const uint8_t kTextOnTheTaxi[] = " ON THE SPACE TAXI! "; static const uint8_t kTextGameOver[] = " GAME OVER! "; static const uint8_t kTextBlank12[] = " "; -// $4C1F / $556A screens. +// $4C1F (the table) and $4E35 (the name entry) screens. static const uint8_t kTextImmortal[] = "THE IMMORTAL CABBIES"; -static const uint8_t *kHighScores[8] = { - (const uint8_t *)"3877.56 MICHAEL PLATE", - (const uint8_t *)"1809.51 MICHAEL PLATE", - (const uint8_t *)"1179.87 ANDREAS PLATE", - (const uint8_t *)" 892.65 MICHAEL PLATE", - (const uint8_t *)" 685.37 ANDREAS PLATE", - (const uint8_t *)" 629.45 MICHAEL PLATE", - (const uint8_t *)" 569.50 MICHAEL PLATE", - (const uint8_t *)" 504.97 ANDREAS PLATE", -}; +static const uint8_t kTextShift[] = "SHIFT"; +static const uint8_t kTextReturnTitle[] = "PRESS FIRE TO RETURN TO TITLE PAGE"; +static const uint8_t kTextCongrats[] = "CONGRATULATIONS CABBIE ,"; +static const uint8_t kTextRanks[] = "YOUR SCORE RANKS YOU AS ONE OF"; +static const uint8_t kTextTopTen[] = "THE TOP TEN CABBIES OF ALL TIME"; +static const uint8_t kTextEnterName[] = "ENTER YOUR NAME BELOW"; static const uint8_t *kAboutLines[7] = { (const uint8_t *)"THIS PROGRAM DEVELOPED AND WRITTEN BY", (const uint8_t *)"JOHN F. KUTCHER", @@ -117,8 +151,9 @@ static const uint8_t *kAboutLines[7] = { }; static const uint8_t kTextReturn[] = "PRESS FIRE TO RETURN"; -static StGameT gGame; -static StSimT gSim; +static StGameT gGame; +static StSimT gSim; +static jlSurfaceT *gStage; // One level buffer serves both gameplay and the title screen: the title // is another scene, and gameplay never needs it at the same time, so it // is loaded into gLevel on each return to the title. (Keeping a second @@ -126,17 +161,25 @@ static StSimT gSim; static StLevelT gLevel; -static void blankScreen(StSimT *sim); +static void blankScreen(StSimT *sim, uint8_t border, uint8_t bg); static void enterTitle(void); static void gameOverEnter(void); +static void gameOverFinish(void); +static void highScoreDraw(void); +static uint8_t highScoreInsert(const uint8_t *score); +static void highScoreLoad(void); +static void highScoreSave(void); static bool joyFire(void); static uint8_t joyMask(void); static bool loadLevel(uint8_t index); static uint8_t nextLevelForShift(void); static uint8_t paceTicks(void); +static bool serviceJingle(void); static void runCabbiesMenu(uint8_t ticks); static void runDemoEnd(void); static void runIntro(uint8_t ticks); +static void runNameEntry(uint8_t ticks); +static bool nameEntryStart(void); static void runPlaying(uint8_t ticks); static void runShiftMenu(uint8_t ticks); static void runTitle(uint8_t ticks); @@ -149,12 +192,12 @@ static void startNewScreen(void); // $40CA -- spaces everywhere, black. -static void blankScreen(StSimT *sim) { +static void blankScreen(StSimT *sim, uint8_t border, uint8_t bg) { memset(sim->screen, ST_CHAR_SPACE, ST_SCREEN_CELLS); memset(sim->color, 0, ST_SCREEN_CELLS); stSimDirtyAll(sim); - sim->bgColor = 0u; - sim->borderColor = 0u; + sim->bgColor = bg; + sim->borderColor = border; sim->frame.enableMask = 0u; stRenderSceneChanged(sim); } @@ -166,16 +209,239 @@ static void enterTitle(void) { if (!stLevelLoad(&gLevel, "levels/title.dat")) { ST_LOG("spacetaxi: ! title load FAILED"); } + stRenderLevelCels(gStage, 0); // the last level's cels go stTitleEnter(&gSim, &gLevel); stRenderSceneChanged(&gSim); stRenderTitleLogo(&gSim); stAudioNoise(false); stAudioSilence(); + jlMusicStop(); gGame.state = ST_STATE_TITLE; gGame.fireArmed = false; } +// $6BD0 -- one more player done; everybody done -> title. +static void gameOverFinish(void) { + gSim.playersDone++; + if (gSim.playersDone == gSim.playerCount) { + enterTitle(); + } else { + startNewScreen(); + } +} + + +// $4C1F -- the immortal cabbies: ten rows of shift, "$", score and name, +// each entry drawn only while its last score digit is a digit (a blank +// row is an unused slot). Song 7 plays under it until FIRE ($4D04). +static void highScoreDraw(void) { + uint8_t i; + + blankScreen(&gSim, 3u, 14u); + stSimDrawText(&gSim, 9u, 1u, kTextImmortal, 6u); + stSimDrawText(&gSim, 32u, 3u, kTextShift, 15u); + for (i = 0u; i < ST_HS_ENTRIES; i++) { + const StHighScoreT *e = &gHighScores[i]; + uint8_t row = (uint8_t)(4u + i * 2u); + if (e->score[ST_NUMBER_CHARS - 1u] < '0') { + continue; + } + stSimPutChar(&gSim, 34u, row, (uint8_t)('0' + e->shift)); + stSimPutColor(&gSim, 34u, row, 15u); + stSimPutChar(&gSim, 4u, row, '$'); + stSimPutColor(&gSim, 4u, row, 1u); + stSimDrawText(&gSim, 5u, row, e->score, 1u); + stSimDrawText(&gSim, 15u, row, e->name, 1u); + } + stSimDrawText(&gSim, 3u, 24u, kTextReturnTitle, 0u); + stAudioMusic(ST_SONG_SCORES, false); // $4C29 +} + + +// $4D92 -- where a score belongs in the table, or ST_HS_ENTRIES when it +// does not make it. The table is only opened to scores of $50.00 and up +// (second character a digit, or third character 5 or more). The scores +// are right-aligned text, so a character compare orders them; a tie goes +// above the older entry. The new entry is opened with a blank name. +static uint8_t highScoreInsert(const uint8_t *score) { + uint8_t slot; + uint8_t k; + + if (score[1] < '0' && score[2] < '5') { + return ST_HS_ENTRIES; + } + for (slot = 0u; slot < ST_HS_ENTRIES; slot++) { + const uint8_t *old = gHighScores[slot].score; + for (k = 0u; k < ST_NUMBER_CHARS; k++) { + if (score[k] != old[k]) { + break; + } + } + if (k == ST_NUMBER_CHARS || score[k] > old[k]) { + break; + } + } + if (slot == ST_HS_ENTRIES) { + return ST_HS_ENTRIES; + } + for (k = (uint8_t)(ST_HS_ENTRIES - 1u); k > slot; k--) { + gHighScores[k] = gHighScores[k - 1u]; + } + memcpy(gHighScores[slot].score, score, ST_NUMBER_CHARS + 1u); + memset(gHighScores[slot].name, ' ', ST_HS_NAME_CHARS); + gHighScores[slot].name[ST_HS_NAME_CHARS] = 0u; + gHighScores[slot].shift = 0u; + return slot; +} + + +// $23E5 -> $9948 at boot: the table from disk, when there is one. The +// image is the C64's own: per entry score[7], 0, name[15], shift. +static void highScoreLoad(void) { + uint8_t image[ST_HS_ENTRIES * ST_HS_ENTRY_BYTES]; + uint8_t i; + + if (jlSaveRead(ST_HS_SAVE_NAME, image, sizeof(image)) != sizeof(image)) { + return; + } + for (i = 0u; i < ST_HS_ENTRIES; i++) { + const uint8_t *e = &image[i * ST_HS_ENTRY_BYTES]; + if (e[ST_NUMBER_CHARS] != 0u) { + ST_LOG("spacetaxi: ! %s is not a score table", ST_HS_SAVE_NAME); + return; + } + } + for (i = 0u; i < ST_HS_ENTRIES; i++) { + const uint8_t *e = &image[i * ST_HS_ENTRY_BYTES]; + memcpy(gHighScores[i].score, e, ST_NUMBER_CHARS); + gHighScores[i].score[ST_NUMBER_CHARS] = 0u; + memcpy(gHighScores[i].name, &e[ST_NUMBER_CHARS + 1u], ST_HS_NAME_CHARS); + gHighScores[i].name[ST_HS_NAME_CHARS] = 0u; + gHighScores[i].shift = e[ST_HS_ENTRY_BYTES - 1u]; + } +} + + +// $4E11 -> $9948 after an entry: the table back to disk. +static void highScoreSave(void) { + uint8_t image[ST_HS_ENTRIES * ST_HS_ENTRY_BYTES]; + uint8_t i; + + for (i = 0u; i < ST_HS_ENTRIES; i++) { + uint8_t *e = &image[i * ST_HS_ENTRY_BYTES]; + memcpy(e, gHighScores[i].score, ST_NUMBER_CHARS); + e[ST_NUMBER_CHARS] = 0u; + memcpy(&e[ST_NUMBER_CHARS + 1u], gHighScores[i].name, ST_HS_NAME_CHARS); + e[ST_HS_ENTRY_BYTES - 1u] = gHighScores[i].shift; + } + if (!jlSaveWrite(ST_HS_SAVE_NAME, image, sizeof(image))) { + ST_LOG("spacetaxi: ! could not save %s", ST_HS_SAVE_NAME); + } +} + + +// $4D52 -- after a player's GAME OVER: their score as "dddd.dd" text, and +// if it ranks, the $4E35 screen: red border, grey field, the cabbie's +// number in the greeting, the score at the top, song 6, and a blinking +// cursor where the name goes. Returns false when the score did not rank +// and the game moves straight on. +static bool nameEntryStart(void) { + uint8_t score[ST_NUMBER_CHARS + 1u]; + uint8_t greet[sizeof(kTextCongrats)]; + uint8_t k; + + for (k = 0u; k < ST_NUMBER_CHARS; k++) { + uint8_t ch = gSim.scoreBackup[gSim.player][k]; + if (ch == ST_CHAR_BLANK) { + score[k] = ' '; + } else { + uint8_t d = (uint8_t)(ch - ST_CHAR_DIGIT_BASE); // $4345 + score[k] = (uint8_t)('0' + ((d == 10u) ? 0u : d)); + } + } + score[ST_HS_POINT_AT] = '.'; + score[ST_NUMBER_CHARS] = 0u; + gGame.hsSlot = highScoreInsert(score); + if (gGame.hsSlot == ST_HS_ENTRIES) { + return false; + } + blankScreen(&gSim, 2u, 15u); + while (jlInputGetChar() >= 0) { // $0277 = 0: nothing typed earlier counts + } + memcpy(greet, kTextCongrats, sizeof(greet)); + greet[ST_HS_GREET_DIGIT_AT] = (uint8_t)('1' + gSim.player); + stSimDrawText(&gSim, 7u, 3u, greet, 0u); + stSimDrawText(&gSim, 5u, 5u, kTextRanks, 0u); + stSimDrawText(&gSim, 4u, 7u, kTextTopTen, 0u); + stSimDrawText(&gSim, 9u, 9u, kTextEnterName, 0u); + stSimDrawText(&gSim, 16u, 0u, score, 6u); + stSimPutChar(&gSim, 15u, 0u, '$'); + stSimPutColor(&gSim, 15u, 0u, 6u); + stAudioMusic(ST_SONG_NAME_ENTRY, false); // $4EB6 + gGame.hsLen = 0u; + gGame.hsCursor = ST_HS_CURSOR; + gGame.hsBlinkTicks = 0u; + gGame.state = ST_STATE_NAME_ENTRY; + return true; +} + + +// $4ED8 -- the entry loop: the cursor flips between "%" and blank, then +// the keyboard: letters, space, ".", "!" and "," go in (fifteen at most), +// DEL takes one back, RETURN keeps the name (a "*" when none was typed), +// stamps the shift, stops the tune and saves the table. +static void runNameEntry(uint8_t ticks) { + StHighScoreT *e = &gHighScores[gGame.hsSlot]; + uint8_t col = (uint8_t)(ST_HS_NAME_COL + gGame.hsLen); + int c; + + gGame.hsBlinkTicks = (uint8_t)(gGame.hsBlinkTicks + ticks); + if (gGame.hsBlinkTicks >= ST_HS_BLINK_TICKS) { + gGame.hsBlinkTicks = 0u; + gGame.hsCursor ^= ST_HS_CURSOR ^ ' '; + stSimPutChar(&gSim, col, ST_HS_NAME_ROW, gGame.hsCursor); + stSimPutColor(&gSim, col, ST_HS_NAME_ROW, 6u); + } + while ((c = jlInputGetChar()) >= 0) { + if (c >= 'a' && c <= 'z') { + c = c - 'a' + 'A'; + } + if (c == JL_CHAR_RETURN) { + stSimPutChar(&gSim, col, ST_HS_NAME_ROW, ' '); + if (gGame.hsLen == 0u) { + e->name[0] = '*'; + } + e->shift = gGame.variation; + jlMusicStop(); // $44E1 + highScoreSave(); // $4E11 + gameOverFinish(); + return; + } + if (c == JL_CHAR_BACKSPACE || c == JL_CHAR_DELETE) { + if (gGame.hsLen != 0u) { + stSimPutChar(&gSim, col, ST_HS_NAME_ROW, ' '); + gGame.hsLen--; + col--; + e->name[gGame.hsLen] = ' '; + } + continue; + } + if (c != ' ' && c != '.' && c != '!' && c != ',' && (c < 'A' || c > 'Z')) { + continue; + } + if (gGame.hsLen == ST_HS_NAME_CHARS) { + continue; + } + e->name[gGame.hsLen] = (uint8_t)c; + stSimPutChar(&gSim, col, ST_HS_NAME_ROW, (uint8_t)c); + stSimPutColor(&gSim, col, ST_HS_NAME_ROW, 6u); + gGame.hsLen++; + col++; + } +} + + // $5FBB -- "GAME OVER!" over the play field, sprites hidden. static void gameOverEnter(void) { stSimDrawText(&gSim, 14u, 10u, kTextBlank12, 1u); @@ -184,8 +450,9 @@ static void gameOverEnter(void) { gSim.frame.enableMask = 0u; stAudioNoise(false); stAudioSilence(); - gGame.waitTicks = ST_GAME_OVER_TICKS; - gGame.state = ST_STATE_GAME_OVER; + gGame.waitTicks = ST_GAME_OVER_TICKS; + gGame.gameOverTune = false; + gGame.state = ST_STATE_GAME_OVER; } @@ -222,7 +489,7 @@ static uint8_t joyMask(void) { static bool loadLevel(uint8_t index) { char path[32]; - if (index >= ST_LEVEL_COUNT) { + if (index >= ST_LEVEL_FILES) { return false; } // Hand-rolled "levels/levelNN.dat" (NN = index+1, 01..24) so the @@ -238,6 +505,7 @@ static bool loadLevel(uint8_t index) { ST_LOG("spacetaxi: ! cannot load %s", path); return false; } + stRenderLevelCels(gStage, &gLevel); return true; } @@ -254,7 +522,13 @@ static uint8_t nextLevelForShift(void) { } return (uint8_t)((gGame.variation - 1u) * 8u + sim->levelState); } - if (sim->levelState >= ST_LEVEL_COUNT) { + // $5022/$5167: the 24-hour shifts play screen Y after A..X, and only + // then is the shift over -- which is also what lights the "finished" + // marker in the screens-completed field (levelState reaches 25). + if (sim->levelState == ST_LEVEL_COUNT) { + return ST_SCREEN_Y; + } + if (sim->levelState > ST_LEVEL_COUNT) { return 0xFFu; } if (gGame.variation == 4u) { @@ -355,6 +629,7 @@ static void runIntro(uint8_t ticks) { } for (k = 0u; k < (uint8_t)(ticks * 2u); k++) { if (stIntroStep(&gSim)) { + jlMusicStop(); // $4709 stSimEnterLevel(&gSim, &gLevel); stRenderSceneChanged(&gSim); if (gGame.demo) { @@ -362,6 +637,7 @@ static void runIntro(uint8_t ticks) { stSimSetDemoStream(&gSim, e->stream, e->length); } gGame.state = ST_STATE_PLAYING; + serviceJingle(); return; } } @@ -377,6 +653,9 @@ static void runPlaying(uint8_t ticks) { StTickResultE r = stSimTick(&gSim); stAudioTick(); if (r == ST_TICK_CONTINUE) { + if (serviceJingle()) { + return; // $70D4 bonus cab: the tune blocks + } continue; } if (gGame.demo) { @@ -390,6 +669,9 @@ static void runPlaying(uint8_t ticks) { case ST_TICK_LIFE_LOST: stSimRespawn(&gSim); stRenderSceneChanged(&gSim); + if (serviceJingle()) { + return; // $6906 on the new cab + } break; case ST_TICK_PLAYER_OUT: gameOverEnter(); @@ -401,6 +683,27 @@ static void runPlaying(uint8_t ticks) { } +// Start a tune the sim asked for and park the loop in ST_STATE_JINGLE +// until it ends. Returns true when a tune was started, so callers can +// stop ticking the sim for this frame -- the C64's $44CF blocks there. +static bool serviceJingle(void) { + uint8_t song; + + if (gSim.jingleRequest == 0u) { + return false; + } + song = (uint8_t)(gSim.jingleRequest - 1u); + gSim.jingleRequest = 0u; + stAudioJingle(song); + if (!stAudioJingleActive()) { + return false; // no audio: nothing to wait for + } + gGame.afterJingle = gGame.state; + gGame.state = ST_STATE_JINGLE; + return true; +} + + // $54F8 -- UP/DOWN pick the shift, FIRE confirms and starts the game. static void runShiftMenu(uint8_t ticks) { uint8_t mask = joyMask(); @@ -462,13 +765,7 @@ static void runTitle(uint8_t ticks) { return; } if ((mask & 0x01u) != 0u) { - uint8_t i; - blankScreen(&gSim); - stSimDrawText(&gSim, 10u, 1u, kTextImmortal, 1u); - for (i = 0u; i < 8u; i++) { - stSimDrawText(&gSim, 9u, (uint8_t)(4u + i * 2u), kHighScores[i], 1u); - } - stSimDrawText(&gSim, 10u, 22u, kTextReturn, 1u); + highScoreDraw(); gGame.state = ST_STATE_HIGHSCORES; gGame.fireArmed = false; return; @@ -477,7 +774,7 @@ static void runTitle(uint8_t ticks) { static const uint8_t kAboutCols[7] = { 1u, 12u, 15u, 4u, 5u, 6u, 1u }; static const uint8_t kAboutRows[7] = { 3u, 5u, 7u, 10u, 12u, 14u, 18u }; uint8_t i; - blankScreen(&gSim); + blankScreen(&gSim, 12u, 15u); // $558E: grey border, light-grey field for (i = 0u; i < 7u; i++) { stSimDrawText(&gSim, kAboutCols[i], kAboutRows[i], kAboutLines[i], 1u); } @@ -500,7 +797,7 @@ static void shiftMenuColor(uint8_t sel, uint8_t color) { // $5295 -- GAME VARIATIONS page 1. static void showCabbiesMenu(void) { - blankScreen(&gSim); + blankScreen(&gSim, 0u, 0u); stSimDrawText(&gSim, 13u, 0u, kTextGameVariations, 5u); stSimDrawText(&gSim, 1u, 2u, kTextCabbies, 7u); stSimDrawText(&gSim, 5u, 4u, kTextLeftRight, 11u); @@ -518,7 +815,7 @@ static void showCabbiesMenu(void) { static void showShiftMenu(void) { uint8_t i; - blankScreen(&gSim); + blankScreen(&gSim, 0u, 0u); stSimDrawText(&gSim, 1u, 8u, kTextSelectShift, 7u); for (i = 0u; i < 5u; i++) { stSimDrawText(&gSim, 2u, (uint8_t)(10u + i * 2u), kTextShifts[i], 6u); @@ -560,6 +857,11 @@ static void startDemoScreen(void) { static void startIntro(void) { stIntroEnter(&gSim, &gLevel); stRenderSceneChanged(&gSim); + // After the scene change, which turns it back off. + stRenderIntroStars(); + // $459C: the intro's tune plays under the fly-in, not blocking; the + // C64 leaves it to the IRQ player and stops it at $4709. + stAudioMusic(ST_SONG_INTRO, true); gGame.state = ST_STATE_INTRO; } @@ -601,9 +903,15 @@ int main(void) { jlConfigT config; jlSurfaceT *stage; -#if defined(JOEYLIB_PLATFORM_IIGS) - // One 64 KB bank: enough for the cab, exhaust and passenger cels. - config.codegenBytes = 60UL * 1024; + // The codegen arena holds every compiled cel, the baked ones too: the + // 37 cab/exhaust/passenger cels plus a level's hook cels (level I has + // 30). On the IIgs that is ~230 KB of 65816 code in 64 KB banks, and + // the arena grants as many banks as the machine has (a 1 MB machine + // gets one; whatever does not fit draws interpreted). The 68k ports' + // cels are far larger (9-13 KB each) and their JIT is fast, so they + // keep a modest arena and compile at level entry. +#if defined(JOEYLIB_PLATFORM_IIGS) || defined(JOEYLIB_PLATFORM_DOS) + config.codegenBytes = 256UL * 1024; #else config.codegenBytes = 160UL * 1024; #endif @@ -618,6 +926,7 @@ int main(void) { jlShutdown(); return 1; } + gStage = stage; memset(&gGame, 0, sizeof(gGame)); memset(&gSim, 0, sizeof(gSim)); stRenderInit(stage); @@ -630,6 +939,7 @@ int main(void) { stSimSeedDemoBuffer(&gSim, kDemoBufferInit, ST_DEMO_BUFFER_BYTES); gSim.waveIdx = 3u; stRenderPrewarm(stage, &gSim); + highScoreLoad(); // $23E5 enterTitle(); gGame.lastFrame = (uint8_t)jlFrameCount(); @@ -637,8 +947,39 @@ int main(void) { uint8_t ticks; jlInputPoll(); - if (jlKeyPressed(KEY_ESCAPE)) { - break; + // $4FCB runStopWatcher, on the game tick, the intro and the menus: + // SHIFT held freezes everything with the sound gated off and any + // tune stopped (the intro's restarts on release); RUN/STOP -- ESC + // here -- abandons the game for the title, and on the title it + // leaves the program (the C64 had nowhere else to go). + if (gGame.state != ST_STATE_JINGLE && gGame.state != ST_STATE_GAME_OVER && gGame.state != ST_STATE_NAME_ENTRY) { + if (jlKeyDown(KEY_LSHIFT) || jlKeyDown(KEY_RSHIFT)) { + if (!gGame.paused) { + gGame.paused = true; + stAudioSilence(); + stAudioNoise(false); + jlMusicStop(); + } + stRenderFrame(stage, &gSim); + jlAudioFrameTick(); + continue; + } + if (gGame.paused) { + gGame.paused = false; + gGame.lastFrame = (uint8_t)jlFrameCount(); // the held time is not owed + gGame.paceAcc = 0u; + if (gGame.state == ST_STATE_INTRO) { + stAudioMusic(ST_SONG_INTRO, true); // $46B5 + } + } + if (jlKeyPressed(KEY_ESCAPE)) { + if (gGame.state == ST_STATE_TITLE) { + break; + } + jlMusicStop(); // $5000 + gGame.demo = false; + enterTitle(); + } } ticks = paceTicks(); switch (gGame.state) { @@ -677,19 +1018,33 @@ int main(void) { case ST_STATE_PLAYING: runPlaying(ticks); break; + case ST_STATE_JINGLE: + // The C64 spins in $44D5 until the tune ends. A joystick + // press ends the attract demo here too ($4FE2 stops the + // music on input). + if (gGame.demo && (joyMask() & 0x1Fu) != 0u) { + jlMusicStop(); + runDemoEnd(); + } else if (!stAudioJingleActive()) { + gGame.state = gGame.afterJingle; + } + break; case ST_STATE_GAME_OVER: if (gGame.waitTicks > ticks) { gGame.waitTicks = (uint16_t)(gGame.waitTicks - ticks); - } else { - // $6BD0: one more player done; everybody done -> title. - gSim.playersDone++; - if (gSim.playersDone == gSim.playerCount) { - enterTitle(); - } else { - startNewScreen(); - } + } else if (!gGame.gameOverTune) { + // $5C24: after the GAME OVER! wait, song 5 plays out. + gGame.gameOverTune = true; + stAudioJingle(ST_SONG_GAME_OVER); + } else if (stAudioJingleActive()) { + // still sounding + } else if (!nameEntryStart()) { + gameOverFinish(); } break; + case ST_STATE_NAME_ENTRY: + runNameEntry(ticks); + break; default: enterTitle(); break; diff --git a/spacetaxi.h b/spacetaxi.h index 4acad39..bf9730d 100644 --- a/spacetaxi.h +++ b/spacetaxi.h @@ -15,6 +15,21 @@ #include "stSim.h" +// Diagnostics. The logger drags in vsnprintf (with newlib that pulls the +// whole formatted-output and float-conversion family, ~45 KB) plus a +// multi-KB ring buffer, and the game has no need of either. That is +// unaffordable on the IIgs, whose bank-0 BSS budget cannot spare the +// ring, and on the X68000, which ships on a Human68k floppy with about +// 330 KB of usable space for the binary AND its levels. Both compile it +// out; the ports with room keep it. +#if defined(__W65816__) || defined(JOEYLIB_PLATFORM_X68000) + #define ST_LOG_RESET() ((void)0) + #define ST_LOG(...) ((void)0) +#else + #define ST_LOG_RESET() jlLogReset() + #define ST_LOG(...) jlLogF(__VA_ARGS__) +#endif + // Level loading through the JoeyLib data path (DATA/levels/...). bool stLevelLoad(StLevelT *out, const char *path); @@ -35,7 +50,13 @@ void stRenderSceneChanged(StSimT *sim); // cycle are palette writes instead of repainting 103 (and 407) cells. // Call after stRenderSceneChanged, which turns it back off. void stRenderTitleLogo(StSimT *sim); +// Level intro only: cycle the starfield's ring palette slots so the warp +// animates without a single pixel moving. Call after +// stRenderSceneChanged, which turns it back off. +void stRenderIntroStars(void); void stRenderPrewarm(jlSurfaceT *stage, StSimT *sim); +// Ready a level's hook cels before its ride (evicts the previous level's). +void stRenderLevelCels(jlSurfaceT *stage, const StLevelT *level); // Audio sink setup and the per-tick release timers ($4320). void stAudioInit(void); diff --git a/stAudio.c b/stAudio.c index 5ac786a..8d3eefe 100644 --- a/stAudio.c +++ b/stAudio.c @@ -8,8 +8,18 @@ // time; envelopes and waveforms are approximated with an attenuation // ramp. The speech samples of the original are not extracted; each // spoken character becomes a short chirp so the cues still land. +// +// UPDATE: the speech IS extracted now (assets/extractSpeech.py), so the +// chirp is gone. stSpeechData.c carries the C64's own 1-bit run-length +// streams; assets/genSpeechPcm.py bakes them to sample bytes +// (DATA/speech.bin) and speechFill below streams those through +// jlAudioPlaySfxStream. + +#include #include "spacetaxi.h" +#include +#include "stSongData.h" // SID voice index in a program's byte 8 -> JoeyLib voice slot. @@ -25,10 +35,44 @@ #define ST_SID_HZ_NUM 1022727UL #define ST_SID_HZ_DEN 16777216UL -// Speech chirp: one short tone per character, pitched by the character. -#define ST_SPEECH_TICKS 3u -#define ST_SPEECH_BASE_HZ 300u -#define ST_SPEECH_STEP_HZ 9u +// ---- speech ---- +// +// The C64 player ($9802) busy-waits a delay loop per stream byte and then +// toggles its 1-bit output, so a byte is one half-cycle. The loop costs +// (5k + 12) cycles per pass plus a 33-cycle tail, where k is the inner +// count at $98A2 -- normally 3, giving 27. A code below $09 does not +// speak: it REWRITES k, which is how the original gives each fare a +// slightly different voice pitch. stAudioSpeech honours that. +#define ST_SPEECH_SLOT 0u // slots 1..3 are the SID voices +#define ST_SPEECH_RATE 5000u // enough for a 1-bit source +#define ST_SPEECH_PITCH_BASE 3u // $98A2's power-on value +#define ST_SPEECH_PITCH_MAX 15u // the player's AND #$0F +// Longest phrase is "PX?" -- pad, digit, please. +#define ST_SPEECH_QUEUE 4u +// The speech is baked to sample bytes per pitch, in both polarities, by +// assets/genSpeechPcm.py (DATA/speech.bin, format in that script), so +// playing it is a block copy. Decoding the C64's run-length stream at +// play time cost the 65816 2.4 s of CPU per second of speech and stalled +// the ride half a second on every "HEY TAXI"; the 1-bit intermediate +// format that followed still needed a per-byte expansion loop. +#define ST_SPEECH_FILE "speech.bin" +#define ST_SPEECH_VERSION 2u +#define ST_SPEECH_HEADER_BYTES 16u +#define ST_SPEECH_ENTRY_BYTES 8u +#define ST_SPEECH_POLARITIES 2u +#define ST_SPEECH_FORMAT_SIGNED 0u +#define ST_SPEECH_FORMAT_BIASED 1u +#define ST_SPEECH_LOAD_CHUNK 16384u +#define ST_SPEECH_STAGE_MASK (ST_SPEECH_STAGE_BYTES - 1u) +// Decode-ahead staging: a whole engine chunk (7168 at 5 kHz, 1.4 s) plus +// slack. The per-tick slice covers playback (5000/s is ~330 a tick) +// with room to spare and is cheap enough to never need a burst; a +// starved pull still copies ST_SPEECH_STARVED_SLICE on the spot rather +// than ending the stream. +#define ST_SPEECH_STAGE_BYTES 8192u +#define ST_SPEECH_TICK_SLICE 384u +#define ST_SPEECH_STARVED_SLICE 1024u +#define ST_SPEECH_ARM_SLICE 1024u // Thrust sweep register value -> Hz: the C64 writes the same byte to // both frequency bytes, so the word is value * 257. @@ -44,10 +88,46 @@ typedef struct { static StSfxSlotT gSfx; // SID voice 1 (programs + sweep) static StSfxSlotT gTone; // SID voice 2 static bool gNoiseOn; -static uint16_t gSpeechTicks; + +// Speech playback state. The fill callback walks a queue of utterances so +// a phrase like "HT" ("Hey" + "Taxi") runs as one continuous stream +// instead of each character cutting off the last. +static uint8_t gSpeechQueue[ST_SPEECH_QUEUE]; +static uint8_t gSpeechHead; +static uint8_t gSpeechTail; +static bool gSpeechActive; +static uint8_t gSpeechPitch = ST_SPEECH_PITCH_BASE; +// DATA/speech.bin, whole: header, entry table, sample bytes. The bytes +// are already in the port's raw stream format (jlAudioSfxRawFormat): +// signed +/-100, or 0x80-biased on the IIgs so a chunk needs no +// conversion; speechLoad refuses a file baked for the other one. +static uint8_t *gSpeechBlob; +static uint8_t gSpeechPitchBase; +static uint8_t gSpeechPitchCount; +static uint8_t gSpeechCount; // utterances per pitch +static const uint8_t *gSpeechPcm; // current utterance's samples +static uint16_t gSpeechSample; // next sample index in it +static uint16_t gSpeechSamples; // its length +static uint8_t gSpeechParity; // it ends on the opposite level +static uint8_t gSpeechPol; // 0 / 1: which baked polarity the next utterance plays +// Copied-ahead PCM ring for the stream (see speechFill). Allocated by +// stAudioInit rather than static: 8 KB of BSS is a fifth of the IIgs +// entry bank, which text, rodata, BSS and the C heap all share. +static uint8_t *gSpeechStage; +static uint16_t gSpeechStageRead; // byte index of the next unread sample +static uint16_t gSpeechStageWrite; // byte index of the next free byte +static uint16_t gSpeechStageCount; // bytes staged and unread +static bool gSpeechDecoding; // the decoder still has stream left static uint16_t sidHz(uint16_t word); +static void speechLoad(void); +static bool speechNext(void); +static void speechArm(void); +static uint16_t speechDecode(uint8_t *dst, uint16_t count); +static uint32_t speechFill(void *ctx, int8_t *dst, uint32_t count); +static void speechPumpSlice(uint16_t samples); +static bool speechQueue(uint8_t ch); static void voiceOff(StSfxSlotT *slot); @@ -61,6 +141,208 @@ static uint16_t sidHz(uint16_t word) { } +// Point the decoder at the next queued utterance. False when the queue +// is empty, which ends the stream. +// DATA/speech.bin into one jlAlloc block; without it the game is mute +// but otherwise fine. +static void speechLoad(void) { + FILE *fp = jlDataOpen(ST_SPEECH_FILE, "rb"); + uint8_t head[ST_SPEECH_HEADER_BYTES]; + uint8_t format; + uint32_t dataBytes; + uint32_t total; + uint32_t done; + + gSpeechBlob = 0; + if (fp == 0) { + return; + } + format = (jlAudioSfxRawFormat() == JL_SFX_RAW_UNSIGNED8_NOZERO) ? ST_SPEECH_FORMAT_BIASED : ST_SPEECH_FORMAT_SIGNED; + if (fread(head, 1u, sizeof(head), fp) != sizeof(head) || head[0] != 'S' || head[1] != 'T' || head[2] != 'S' || head[3] != 'P' || head[4] != ST_SPEECH_VERSION || head[8] != format) { + fclose(fp); + return; + } + gSpeechPitchBase = head[5]; + gSpeechPitchCount = head[6]; + gSpeechCount = head[7]; + dataBytes = (uint32_t)head[12] | ((uint32_t)head[13] << 8) | ((uint32_t)head[14] << 16) | ((uint32_t)head[15] << 24); + total = ST_SPEECH_HEADER_BYTES + (uint32_t)gSpeechPitchCount * gSpeechCount * ST_SPEECH_POLARITIES * ST_SPEECH_ENTRY_BYTES + dataBytes; + gSpeechBlob = (uint8_t *)jlAlloc(total); + if (gSpeechBlob == 0) { + fclose(fp); + return; + } + memcpy(gSpeechBlob, head, sizeof(head)); + // The block spans banks on the IIgs; read it in bank-safe pieces. + done = ST_SPEECH_HEADER_BYTES; + while (done < total) { + uint32_t want = total - done; + + if (want > ST_SPEECH_LOAD_CHUNK) { + want = ST_SPEECH_LOAD_CHUNK; + } + if (fread(gSpeechBlob + done, 1u, (size_t)want, fp) != (size_t)want) { + jlFree(gSpeechBlob); + gSpeechBlob = 0; + break; + } + done += want; + } + fclose(fp); +} + + +static bool speechNext(void) { + uint8_t row = 0u; + + if (gSpeechBlob == 0) { + return false; + } + if (gSpeechPitch > gSpeechPitchBase) { + row = (uint8_t)(gSpeechPitch - gSpeechPitchBase); + if (row >= gSpeechPitchCount) { + row = (uint8_t)(gSpeechPitchCount - 1u); + } + } + while (gSpeechHead != gSpeechTail) { + uint8_t code = gSpeechQueue[gSpeechHead]; + const uint8_t *e = gSpeechBlob + ST_SPEECH_HEADER_BYTES + (uint16_t)row * gSpeechCount * ST_SPEECH_POLARITIES * ST_SPEECH_ENTRY_BYTES; + uint8_t k; + + gSpeechHead = (uint8_t)((gSpeechHead + 1u) % ST_SPEECH_QUEUE); + for (k = 0u; k < gSpeechCount; k++, e += ST_SPEECH_POLARITIES * ST_SPEECH_ENTRY_BYTES) { + if (e[0] == code) { + const uint8_t *v = e + (uint16_t)gSpeechPol * ST_SPEECH_ENTRY_BYTES; + uint32_t offset = (uint32_t)v[4] | ((uint32_t)v[5] << 8) | ((uint32_t)v[6] << 16) | ((uint32_t)v[7] << 24); + + gSpeechParity = v[1]; + gSpeechSamples = (uint16_t)(v[2] | ((uint16_t)v[3] << 8)); + gSpeechSample = 0u; + gSpeechPcm = gSpeechBlob + ST_SPEECH_HEADER_BYTES + (uint32_t)gSpeechPitchCount * gSpeechCount * ST_SPEECH_POLARITIES * ST_SPEECH_ENTRY_BYTES + offset; + return true; + } + } + // Unknown code: skip it rather than dropping the whole phrase. + } + return false; +} + + +// Copy up to `count` samples of the queued utterances into `dst`, fewer +// when they run out (0 = none left). jlMemCopy is an MVN on the IIgs; +// the utterance bytes are already in the stream's format, so this is +// the whole of the play-time speech work. +static uint16_t speechDecode(uint8_t *dst, uint16_t count) { + uint16_t n = 0u; + + while (n < count) { + uint16_t left = (uint16_t)(gSpeechSamples - gSpeechSample); + uint16_t take = (uint16_t)(count - n); + + if (left == 0u) { + // The next utterance carries on from the level this one left. + if (gSpeechParity != 0u) { + gSpeechPol ^= 1u; + } + if (!speechNext()) { + return n; + } + continue; + } + if (take > left) { + take = left; + } + jlMemCopy(dst + n, gSpeechPcm + gSpeechSample, take); + gSpeechSample = (uint16_t)(gSpeechSample + take); + n = (uint16_t)(n + take); + } + return n; +} + + +// The audio engine's chunk pull. It used to decode the whole chunk right +// here -- 7168 samples, ~1.5 s of speech, in ONE frame: every "HEY +// TAXI" froze the ride for over a second on the IIgs. Now the chunk is +// copied out of the staging buffer that speechPumpSlice fills a slice at +// a time across the ticks before it is due, and only a stream that has +// outrun the pump decodes here, a bounded slice, so it never starves. +static uint32_t speechFill(void *ctx, int8_t *dst, uint32_t count) { + uint16_t want = (count > ST_SPEECH_STAGE_BYTES) ? (uint16_t)ST_SPEECH_STAGE_BYTES : (uint16_t)count; + uint16_t n; + + (void)ctx; + if (gSpeechStageCount == 0u && gSpeechDecoding) { + speechPumpSlice(ST_SPEECH_STARVED_SLICE); + } + n = (gSpeechStageCount < want) ? gSpeechStageCount : want; + if (n != 0u) { + // jlMemCopy (MVN on the IIgs; libc memcpy is a byte loop there), + // in two runs when the unread bytes wrap the ring's end. + uint8_t *to = (uint8_t *)dst; + uint16_t left = n; + + while (left != 0u) { + uint16_t run = (uint16_t)(ST_SPEECH_STAGE_BYTES - gSpeechStageRead); + + if (run > left) { + run = left; + } + jlMemCopy(to, gSpeechStage + gSpeechStageRead, run); + to += run; + gSpeechStageRead = (uint16_t)((gSpeechStageRead + run) & ST_SPEECH_STAGE_MASK); + left = (uint16_t)(left - run); + } + gSpeechStageCount = (uint16_t)(gSpeechStageCount - n); + } + if (n == 0u) { + // Out of queued utterances. Only END the stream if this chunk + // produced nothing at all -- a phrase is fed one character at a + // time ("HT" is "Hey" then "Taxi"), and the first arm decodes + // all of 'H' before 'T' has even been queued. Ending here would + // let the 'T' arm a NEW stream that cut 'H' off, so the player + // only ever heard the last character. Returning a short chunk + // keeps the slot armed and the next pull picks up whatever + // arrived since. + gSpeechActive = false; + } + return n; +} + + +// Decode up to `samples` more into the staging buffer. Once the decoder +// reports nothing left it stays quiet until the next speechArm. Reads +// and writes stay even so the chunk copy can move words. +static void speechPumpSlice(uint16_t samples) { + uint16_t room = (uint16_t)(ST_SPEECH_STAGE_BYTES - gSpeechStageCount); + + if (!gSpeechDecoding || room == 0u) { + return; + } + if (samples > room) { + samples = room; + } + // Fill towards the ring's end, then from its start. + while (samples != 0u) { + uint16_t run = (uint16_t)(ST_SPEECH_STAGE_BYTES - gSpeechStageWrite); + uint16_t got; + + if (run > samples) { + run = samples; + } + got = speechDecode(gSpeechStage + gSpeechStageWrite, run); + if (got < run) { + gSpeechDecoding = false; + } + gSpeechStageWrite = (uint16_t)((gSpeechStageWrite + got) & ST_SPEECH_STAGE_MASK); + gSpeechStageCount = (uint16_t)(gSpeechStageCount + got); + samples = (uint16_t)(samples - got); + if (!gSpeechDecoding) { + return; + } + } +} + + static void voiceOff(StSfxSlotT *slot) { if (slot->ticksLeft != 0u) { jlAudioVoice(slot->voice, 0u, ST_ATTEN_OFF); @@ -71,6 +353,9 @@ static void voiceOff(StSfxSlotT *slot) { // Once per game tick: run the release timers ($4320 sfxEnvelopeTick). void stAudioTick(void) { + if (gSpeechActive) { + speechPumpSlice(ST_SPEECH_TICK_SLICE); + } if (gSfx.ticksLeft != 0u) { gSfx.ticksLeft--; if (gSfx.ticksLeft == 0u) { @@ -83,23 +368,23 @@ void stAudioTick(void) { jlAudioVoice(gTone.voice, 0u, ST_ATTEN_OFF); } } - if (gSpeechTicks != 0u) { - gSpeechTicks--; - if (gSpeechTicks == 0u) { - jlAudioVoice(ST_VOICE_TONE, 0u, ST_ATTEN_OFF); - } - } } void stAudioInit(void) { (void)jlAudioInit(); + gSpeechStage = (uint8_t *)jlAlloc(ST_SPEECH_STAGE_BYTES); + speechLoad(); gSfx.voice = ST_VOICE_SFX; gSfx.ticksLeft = 0u; gTone.voice = ST_VOICE_TONE; gTone.ticksLeft = 0u; gNoiseOn = false; - gSpeechTicks = 0u; + gSpeechHead = 0u; + gSpeechTail = 0u; + gSpeechActive = false; + gSpeechPitch = ST_SPEECH_PITCH_BASE; + gSpeechPol = 0u; } @@ -108,6 +393,10 @@ void stAudioShutdown(void) { voiceOff(&gTone); jlAudioNoise(0u, ST_ATTEN_OFF); jlAudioShutdown(); + jlFree(gSpeechStage); + gSpeechStage = 0; + jlFree(gSpeechBlob); + gSpeechBlob = 0; } @@ -151,10 +440,112 @@ void stAudioSilence(void) { } -// $9802 -- one spoken character (a chirp here). +// $44CF -- play one of the game's tunes and block. The C64 silences the +// SID first ($5C1A / $6906) and then spins until the song ends; the host +// does the spinning (ST_STATE_JINGLE), so here it is just the start. +void stAudioJingle(uint8_t song) { + stAudioMusic(song, false); +} + + +// $CB02 -- start a tune and leave it to the player: the level intro's +// song and the high-score table's. Speech is cancelled too: it streams +// on the slot the tracker's voice 0 rides. +void stAudioMusic(uint8_t song, bool loop) { + if (song >= ST_SONG_COUNT) { + return; + } + stAudioSilence(); + stAudioNoise(false); + gSpeechActive = false; + gSpeechDecoding = false; + gSpeechStageRead = 0u; + gSpeechStageWrite = 0u; + gSpeechStageCount = 0u; + gSpeechHead = gSpeechTail; + jlAudioStopSfx(ST_SPEECH_SLOT); + jlMusicPlay(kStSongs[song].data, kStSongs[song].length, loop); +} + + +bool stAudioJingleActive(void) { + return jlMusicIsPlaying(); +} + + +// Queue one utterance code. Returns false when the code is not speech (a +// pitch change) or the queue is full. +static bool speechQueue(uint8_t ch) { + uint8_t next; + + if (ch < 9u) { + // Below $09 the original does not speak: it rewrites the delay + // loop's inner count, re-pitching every later utterance. That is + // where the passenger's voice variation comes from, so keep it. + gSpeechPitch = (uint8_t)(ch & ST_SPEECH_PITCH_MAX); + if (gSpeechPitch == 0u) { + gSpeechPitch = ST_SPEECH_PITCH_BASE; + } + return false; + } + next = (uint8_t)((gSpeechTail + 1u) % ST_SPEECH_QUEUE); + if (next == gSpeechHead) { + return false; // queue full; drop rather than stall + } + gSpeechQueue[gSpeechTail] = ch; + gSpeechTail = next; + return true; +} + + +// Start the stream if it is idle. Call this ONCE, after the whole phrase is +// queued: arming decodes everything queued so far into a single chunk, and +// each extra chunk costs a refill deadline of dead air, because the refill +// only happens when the game next calls jlAudioFrameTick -- once per rendered +// frame. Arming per character is what broke phrases into words with silence +// between them. +static void speechArm(void) { + if (gSpeechActive || gSpeechStage == 0 || gSpeechBlob == 0) { + return; + } + if (!speechNext()) { + return; + } + gSpeechPol = 0u; // a phrase starts on +100 + gSpeechActive = true; + gSpeechDecoding = true; + gSpeechStageRead = 0u; + gSpeechStageWrite = 0u; + gSpeechStageCount = 0u; + // A first slice now, so the opening chunk sounds this tick. + speechPumpSlice(ST_SPEECH_ARM_SLICE); + jlAudioPlaySfxStreamRaw(ST_SPEECH_SLOT, speechFill, (void *)0, ST_SPEECH_RATE); +} + + +// $9802 -- one spoken character. Speaks immediately; prefer +// stAudioSpeechPhrase for anything longer than a single code. void stAudioSpeech(uint8_t ch) { - jlAudioVoice(ST_VOICE_TONE, (uint16_t)(ST_SPEECH_BASE_HZ + (uint16_t)(ch & 0x1Fu) * ST_SPEECH_STEP_HZ), ST_ATTEN_LOUD); - gSpeechTicks = ST_SPEECH_TICKS; + if (speechQueue(ch)) { + speechArm(); + } +} + + +// A whole phrase: queue every code FIRST, then arm once, so the phrase +// decodes into as few chunks as it fits into. +void stAudioSpeechPhrase(const uint8_t *speech) { + bool any = false; + + while (*speech != 0u) { + if (speechQueue(*speech)) { + any = true; + } + speech++; + } + if (any) { + speechArm(); + } } diff --git a/stC64Data.c b/stC64Data.c index 53b7f05..b8fa792 100644 --- a/stC64Data.c +++ b/stC64Data.c @@ -1322,10 +1322,6 @@ const uint8_t kStFlameCelByDirMask[16] = { 0x00, 0xD8, 0xD4, 0x00, 0xD5, 0xD2, 0xD1, 0x00, 0xD7, 0xD3, 0xD6, 0x00, 0x00, 0x00, 0x00, 0x00, }; -const int8_t kStIntroStarDx[7] = { 0, 6, 6, 6, -6, -6, -6 }; -const int8_t kStIntroStarDy[7] = { -4, -4, 0, 4, 4, 0, -4 }; - - // --------------------------------------------------------------------------- // Accessors. On the IIgs the program is split across banks and a 2-byte @@ -1358,13 +1354,3 @@ const uint8_t *stC64SfxProgram(uint8_t idx) { uint8_t stC64FlameCel(uint8_t dirMask) { return kStFlameCelByDirMask[dirMask]; } - - -int8_t stC64IntroStarDx(uint8_t idx) { - return kStIntroStarDx[idx]; -} - - -int8_t stC64IntroStarDy(uint8_t idx) { - return kStIntroStarDy[idx]; -} diff --git a/stC64Data.h b/stC64Data.h index 69751ab..c30c083 100644 --- a/stC64Data.h +++ b/stC64Data.h @@ -44,10 +44,6 @@ extern const uint8_t kStSfxPrograms[ST_SFX_COUNT][ST_SFX_PROGRAM_BYTES]; // 8=RIGHT); 0 = no cel for that combination. extern const uint8_t kStFlameCelByDirMask[16]; -// Level-intro star velocities per sprite 0..6 ($450C / $4513). -extern const int8_t kStIntroStarDx[7]; -extern const int8_t kStIntroStarDy[7]; - // Cross-segment-safe accessors (see stC64Data.c): reach the tables above // by an inter-segment call on the IIgs, where a direct 2-byte data // reference from another bank is not relocatable. @@ -56,7 +52,5 @@ const uint8_t *stC64SpriteBitmap(uint8_t idx); uint8_t stC64RngByte(uint8_t idx); const uint8_t *stC64SfxProgram(uint8_t idx); uint8_t stC64FlameCel(uint8_t dirMask); -int8_t stC64IntroStarDx(uint8_t idx); -int8_t stC64IntroStarDy(uint8_t idx); #endif diff --git a/stCels.h b/stCels.h index e78e1e6..2ab7dc8 100644 --- a/stCels.h +++ b/stCels.h @@ -12,6 +12,8 @@ #include +#include "stSim.h" + // 3x3 tiles = 24x24 px; 32 bytes per 8x8 tile, tile-major (matches // jlSpriteCreateFromSurface / the .spr cel layout). #define ST_CEL_TILES 3u @@ -29,6 +31,11 @@ typedef struct { extern const StCelDefT kStCels[]; extern const uint16_t kStCelCount; +// The cels a level's hook program can show (stLevelCels.c): at most `max` +// into `out`, no duplicates. 0 for a level without hook sprites. +#define ST_LEVEL_CELS_MAX 40u +uint8_t stLevelCelList(const StLevelT *level, StCelDefT *out, uint8_t max); + // Expand a VIC bitmap (63 bytes, 21 rows x 3, or NULL) into the 288-byte // tile-major 4bpp-packed cel blob. Colours as in the C64 multicolour // rules; hires uses `color` for a set bit. diff --git a/stFare.c b/stFare.c index 99e1d76..508f2c1 100644 --- a/stFare.c +++ b/stFare.c @@ -11,17 +11,16 @@ #include "stSim.h" // Screen-code strings ($6C1E..$6C7D). All ASCII-compatible glyphs. -static const uint8_t kTextPadPlease[] = { 0x50, 0x41, 0x44, 0x20, 0x20, 0x20, 0x50, 0x4C, 0x45, 0x41, 0x53, 0x45, 0 }; // $6C1E "PAD PLEASE" -static const uint8_t kTextUpPlease[] = { 0x20, 0x55, 0x50, 0x20, 0x50, 0x4C, 0x45, 0x41, 0x53, 0x45, 0x21, 0x20, 0 }; // $6C2B " UP PLEASE! " -static const uint8_t kTextBlank[] = { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0 }; // $6C38 -static const uint8_t kTextHeyTaxi[] = { 0x20, 0x48, 0x45, 0x59, 0x2C, 0x20, 0x54, 0x41, 0x58, 0x49, 0x21, 0x20, 0 }; // $6C45 " HEY, TAXI! " -static const uint8_t kTextThanks[] = { 0x20, 0x20, 0x20, 0x54, 0x48, 0x41, 0x4E, 0x4B, 0x53, 0x20, 0x20, 0x20, 0 }; // $6C5F " THANKS " +static const uint8_t kTextPadPlease[] = "PAD PLEASE"; // $6C1E +static const uint8_t kTextUpPlease[] = " UP PLEASE! "; // $6C2B +static const uint8_t kTextHeyTaxi[] = " HEY, TAXI! "; // $6C45 +static const uint8_t kTextThanks[] = " THANKS "; // $6C5F // Speech cue strings fed to the sample player one character at a time. -static const uint8_t kSpeechHeyTaxi[] = { 0x48, 0x54, 0 }; // $6C6C "HT" -static const uint8_t kSpeechUp[] = { 0x55, 0x3F, 0 }; // $6C6F "U?" -static const uint8_t kSpeechPad[] = { 0x50, 0x58, 0x3F, 0 }; // $6C72 "PX?" (X = digit) -static const uint8_t kSpeechThanks[] = { 0x21, 0 }; // $6C76 "!" -static const uint8_t kSpeechHey[] = { 0x48, 0 }; // $6C78 "H" +static const uint8_t kSpeechHeyTaxi[] = "HT"; // $6C6C +static const uint8_t kSpeechUp[] = "U?"; // $6C6F +static const uint8_t kSpeechPad[] = "PX?"; // $6C72 (X = digit) +static const uint8_t kSpeechThanks[] = "!"; // $6C76 +static const uint8_t kSpeechHey[] = "H"; // $6C78 // Waiting wave cycle ($66D6) and the base-fare blob ($43B9 = +5.00). static const uint8_t kWaveCels[4] = { 0xC6, 0xC7, 0xD9, 0xC7 }; static const uint8_t kFareBlob[7] = { 0x66, 0x66, 0x66, 0x6F, 0x77, 0x74, 0x74 }; @@ -45,12 +44,12 @@ static void beamIn(StSimT *sim) { return; } sim->spr[1].ptr--; - if (sim->spr[1].ptr != 0xC7u) { + if (sim->spr[1].ptr != ST_SPRITE_PASS_STAND) { return; } if (sim->collisionPhase != 0u) { sim->stage = ST_STAGE_LEAVE; - sim->spr[1].ptr = 0xC7u; + sim->spr[1].ptr = ST_SPRITE_PASS_STAND; sim->spriteSlots[sim->activeSpriteIdx] = 0u; sim->activeSpriteIdx = 0u; sim->fareSlotCount--; @@ -60,15 +59,9 @@ static void beamIn(StSimT *sim) { stSimDrawText(sim, 14u, 24u, kTextHeyTaxi, 1u); stFareBoardingSound(sim, kSpeechHeyTaxi); // $43D1: the meter starts at $10.00..$19.00. - { - uint8_t k; - static const uint8_t kTemplate[7] = { 0x66, 0x66, 0x66, 0x74, 0x77, 0x74, 0x74 }; - for (k = 0u; k < ST_NUMBER_CHARS; k++) { - stSimPutChar(sim, (uint8_t)(32u + k), 24u, kTemplate[k]); - } - stSimPutChar(sim, 35u, 24u, (uint8_t)(ST_CHAR_DIGIT_BASE + stSimRng(sim, 10u))); - stSimPutChar(sim, 34u, 24u, (uint8_t)(ST_CHAR_DIGIT_BASE + 1u)); - } + stSimHudInit(sim); + stSimPutChar(sim, 35u, 24u, (uint8_t)(ST_CHAR_DIGIT_BASE + stSimRng(sim, 10u))); + stSimPutChar(sim, 34u, 24u, (uint8_t)(ST_CHAR_DIGIT_BASE + 1u)); } @@ -125,7 +118,7 @@ static void newFare(StSimT *sim) { sim->spr[1].enable = 1u; sim->stage = ST_STAGE_BEAM_IN; sim->decayTimer = sim->decayReload; - sim->spr[1].ptr = 0xCBu; + sim->spr[1].ptr = ST_SPRITE_PASS_BEAM; stAudioSpeech((uint8_t)(stSimRng(sim, 3u) + 2u)); } @@ -136,14 +129,14 @@ static void stageBoard(StSimT *sim) { return; } sim->spr[1].ptr++; - if (sim->spr[1].ptr != 0xCCu) { + if (sim->spr[1].ptr != ST_SPRITE_PASS_GONE) { return; } sim->spr[1].enable = 0u; sim->stage++; sim->activeDyingSlot = sim->activePad; (void)stFareChooseDestination(sim); - if (sim->activeSpriteIdx == 0x0Bu) { + if (sim->activeSpriteIdx == ST_FARE_DEST_UP) { return; } stSimDrawText(sim, 14u, 24u, kTextPadPlease, 1u); @@ -164,7 +157,7 @@ static void stageCleanup(StSimT *sim) { sim->deathInProgress = 0u; sim->stage = ST_STAGE_IDLE; stSimPadLight(sim, false); - stSimDrawText(sim, 14u, 24u, kTextBlank, 1u); + stSimClearMessage(sim); } @@ -176,7 +169,7 @@ static void stageDropIn(StSimT *sim) { return; } sim->spr[1].ptr--; - if (sim->spr[1].ptr != 0xC7u) { + if (sim->spr[1].ptr != ST_SPRITE_PASS_STAND) { return; } sim->stage++; @@ -184,13 +177,7 @@ static void stageDropIn(StSimT *sim) { stFareBoardingSound(sim, kSpeechThanks); stSimBcdAdd(sim, ST_CELL_SCORE, kFareBlob); stSimBcdAdd(sim, ST_CELL_SCORE, &sim->screen[ST_CELL_FARE]); - { - static const uint8_t kTemplate[7] = { 0x66, 0x66, 0x66, 0x74, 0x77, 0x74, 0x74 }; - uint8_t k; - for (k = 0u; k < ST_NUMBER_CHARS; k++) { - stSimPutChar(sim, (uint8_t)(32u + k), 24u, kTemplate[k]); - } - } + stSimHudInit(sim); if (sim->fareSlotCount != 0u) { return; } @@ -204,7 +191,7 @@ void stFareLeaveStep(StSimT *sim) { return; } sim->spr[1].ptr++; - if (sim->spr[1].ptr != 0xCCu) { + if (sim->spr[1].ptr != ST_SPRITE_PASS_GONE) { return; } sim->spr[1].enable = 0u; @@ -227,24 +214,19 @@ void stFareWaveStep(StSimT *sim) { void stFareWalkStep(StSimT *sim) { int16_t passX; int16_t targetX; - uint16_t sum; sim->walkParity ^= 1u; - passX = (int16_t)(((int16_t)sim->spr[1].msb << 8) | sim->spr[1].col); + passX = (int16_t)stSimSpriteX(sim, 1u); targetX = (int16_t)(((int16_t)sim->hoverXFrac << 8) | sim->hoverXCol); if ((int16_t)(passX - targetX) < 0) { // $6D30: walk right, cels $C2/$C3. - sim->spr[1].ptr = (uint8_t)(0xC2u + sim->walkParity); - sum = (uint16_t)((uint16_t)sim->spr[1].col + 2u); - sim->spr[1].col = (uint8_t)sum; - sim->spr[1].msb = (uint8_t)(sim->spr[1].msb + (uint8_t)(sum >> 8)); + sim->spr[1].ptr = (uint8_t)(ST_SPRITE_PASS_WALK_R + sim->walkParity); + stSimSpriteAddX(sim, 1u, 2u); return; } // $6D4B: walk left, cels $C4/$C5. - sim->spr[1].ptr = (uint8_t)(0xC4u + sim->walkParity); - sum = (uint16_t)((uint16_t)sim->spr[1].col + 0xFEu); - sim->spr[1].col = (uint8_t)sum; - sim->spr[1].msb = (uint8_t)(sim->spr[1].msb + 0xFFu + (uint8_t)(sum >> 8)); + sim->spr[1].ptr = (uint8_t)(ST_SPRITE_PASS_WALK_L + sim->walkParity); + stSimSpriteAddX(sim, 1u, 0xFEu); } @@ -255,7 +237,7 @@ static void walkToTarget(StSimT *sim) { return; } if ((sim->spr[1].col & 0xFEu) == (sim->hoverXCol & 0xFEu)) { - sim->spr[1].ptr = 0xC7u; + sim->spr[1].ptr = ST_SPRITE_PASS_STAND; sim->stage++; return; } @@ -272,17 +254,14 @@ void stFareBoardingSound(StSimT *sim, const uint8_t *speech) { (void)sim; stAudioSilence(); stAudioNoise(false); - while (*speech != 0u) { - stAudioSpeech(*speech); - speech++; - } + stAudioSpeechPhrase(speech); } // $6537 -- reserve a free fare pad by RNG (linear probe on collision), // or "UP PLEASE" once every fare pad is taken. Returns the pad number. uint8_t stFareChooseDestination(StSimT *sim) { - uint8_t special = sim->level->specialPad; + uint8_t special = sim->specialPad; uint8_t x; if (sim->fareSlotCount == special) { @@ -349,7 +328,7 @@ void stFarePostTickGate(StSimT *sim) { sim->spr[1].msb = sim->spr[0].msb; sim->spr[1].col = sim->spr[0].col; sim->spr[1].row = sim->spr[0].row; - sim->spr[1].ptr = 0xCBu; + sim->spr[1].ptr = ST_SPRITE_PASS_BEAM; sim->decayTimer = sim->decayReload; pad = &sim->pads[sim->activeSpriteIdx - 1u]; sim->hoverXFrac = pad->passMsb; @@ -374,7 +353,7 @@ void stFareSquashed(StSimT *sim) { sim->spriteSlots[sim->activeSpriteIdx] = 0u; sim->activeSpriteIdx = 0u; sim->fareSlotCount--; - stSimDrawText(sim, 14u, 24u, kTextBlank, 1u); + stSimClearMessage(sim); } sim->stage = ST_STAGE_LEAVE; stFareBoardingSound(sim, kSpeechHey); @@ -387,14 +366,8 @@ void stFareSquashed(StSimT *sim) { // $43F3 counts down in the input-mask byte and leaves it zero. sim->inputMask = 0u; } - sim->spr[1].ptr = 0xC7u; - { - static const uint8_t kTemplate[7] = { 0x66, 0x66, 0x66, 0x74, 0x77, 0x74, 0x74 }; - uint8_t k; - for (k = 0u; k < ST_NUMBER_CHARS; k++) { - stSimPutChar(sim, (uint8_t)(32u + k), 24u, kTemplate[k]); - } - } + sim->spr[1].ptr = ST_SPRITE_PASS_STAND; + stSimHudInit(sim); } @@ -442,7 +415,7 @@ void stFareStageDispatch(StSimT *sim) { void stFareUpPlease(StSimT *sim) { uint8_t k; - sim->activeSpriteIdx = 0x0Bu; + sim->activeSpriteIdx = ST_FARE_DEST_UP; stSimDrawText(sim, 14u, 24u, kTextUpPlease, 1u); for (k = 0u; k < 4u; k++) { stSimPutChar(sim, (uint8_t)(18u + k), 0u, ST_CHAR_SPACE); diff --git a/stHookTables.h b/stHookTables.h new file mode 100644 index 0000000..3b203cd --- /dev/null +++ b/stHookTables.h @@ -0,0 +1,85 @@ +// Space Taxi -- where each level's hook program keeps its tables. +// +// Every address is a byte in the level's shipped hook blob ($7D98..$7FFF, +// StLevelT.hookData), so the same numbers serve the hook programs +// (stHooks.c, reading through the live simulation) and the cel manifest +// (stLevelCels.c, reading the level file alone -- the offline cel baker +// has no simulation). One list, two readers. +#ifndef ST_HOOK_TABLES_H +#define ST_HOOK_TABLES_H + +// Level indices (0 = A). +#define ST_LEVEL_E 4u +#define ST_LEVEL_G 6u +#define ST_LEVEL_H 7u +#define ST_LEVEL_I 8u +#define ST_LEVEL_J 9u +#define ST_LEVEL_K 10u +#define ST_LEVEL_L 11u +#define ST_LEVEL_O 14u +#define ST_LEVEL_P 15u +#define ST_LEVEL_Q 16u +#define ST_LEVEL_R 17u +#define ST_LEVEL_S 18u +#define ST_LEVEL_T 19u +#define ST_LEVEL_U 20u +#define ST_LEVEL_V 21u +#define ST_LEVEL_W 22u +#define ST_LEVEL_X 23u + +// Hardware sprites 3..7 are the hook sprites on every level that has any. +#define ST_HOOK_FIRST_SPRITE 3u + +// ---- level G "TELEPORTS" ---- +#define ST_HK_G_CEL 0x7DD4u // 6 cels the orbs cycle through +#define ST_HK_G_CEL_PHASES 6u +#define ST_HK_G_ORB_COL 0x7DDAu // per sprite: start column, msb, row +#define ST_HK_G_ORB_MSB 0x7DE2u +#define ST_HK_G_ORB_ROW 0x7DEAu +#define ST_HK_G_SPOT_COL 0x7EF3u // 5 landing spots: column, msb, row +#define ST_HK_G_SPOT_MSB 0x7EF8u +#define ST_HK_G_SPOT_ROW 0x7EFDu +#define ST_HK_G_SFX_HOP 0x7F03u +#define ST_HK_G_ORB_COLOR 4u + +// ---- level H "PUZZLER" ---- +#define ST_HK_H_CEL 0x80u // the one sprite cel +#define ST_HK_H_COLOR_IDLE 0x01u +#define ST_HK_H_COLOR_LIT 0x0Au +#define ST_HK_H_COLOR_ACTIVE 0x05u + +// ---- level I "CROSSFIRE" ---- +#define ST_HK_I_CEL_RISE 0x7DD8u // 5 cels +#define ST_HK_I_RISE_PHASES 5u +#define ST_HK_I_CEL_BURST 0x7DE5u // 8 cels +#define ST_HK_I_BURST_PHASES 8u +#define ST_HK_I_COLOR 0x7E7Fu // 3 colours +#define ST_HK_I_COLORS 3u +#define ST_HK_I_COLOR_IDLE 0x02u + +// ---- levels J "SHOOTING STARS" and P "BLIZZARD" (the falling hazards) ---- +#define ST_HK_FALL_CEL 0x80u // falling: $80/$81 +#define ST_HK_FALL_CEL_BURST 0x82u // then the burst run from the table +#define ST_HK_J_CEL_BURST 0x7EC9u // phases 1..9 +#define ST_HK_J_BURST_PHASES 10u +#define ST_HK_J_COLOR 7u +#define ST_HK_P_CEL_BURST 0x7F09u // phases 1..4 +#define ST_HK_P_BURST_PHASES 5u +#define ST_HK_P_COLOR 3u + +// ---- level L "BLACK HOLE" ---- +#define ST_HK_L_CEL_FIRST 0x80u // $80..$82 spin +#define ST_HK_L_CEL_END 0x83u +#define ST_HK_L_COLOR 0u + +// ---- level Q "INTERFERENCE" ---- +#define ST_HK_Q_CEL 0x7DF2u // 12 cels, multicolour +#define ST_HK_Q_CEL_PHASES 12u +#define ST_HK_Q_COLOR 6u + +// ---- level U "REBOUND" ---- +#define ST_HK_U_CEL 0x7F4Bu // 4-entry walk over $80..$82 +#define ST_HK_U_CEL_PHASES 4u +#define ST_HK_U_COLOR 7u + +#endif // ST_HOOK_TABLES_H diff --git a/stHooks.c b/stHooks.c index a6bb4f8..ce3768d 100644 --- a/stHooks.c +++ b/stHooks.c @@ -6,38 +6,212 @@ // after the sprite flush), an input filter and the sprite-contact // verdict. Those programs are the level gimmicks -- the puzzle // switches, lasers, moving pads, trap doors. The port keeps the -// original bytes (sim->hookRam, for the tables the code indexes) and -// re-expresses each program here, reading its tables at the same -// addresses so nothing has to be transcribed by hand. +// original bytes in the level blob and re-expresses each program here, +// naming the tables it reads at the addresses the original used, so +// nothing has to be transcribed by hand. Only the bytes a hook WRITES +// are held in the port's own typed scratch (StHookScratchT). + +#include #include "stSim.h" +#include "stHookTables.h" -// Hook RAM accessors by C64 address. -#define HK(sim, addr) ((sim)->hookRam[(addr) - ST_HOOK_BASE]) -#define HKP(sim, addr) (&(sim)->hookRam[(addr) - ST_HOOK_BASE]) +// Keeping the two per-tick dispatch halves out of line is what lets the +// 65816 build use the default register allocator. Inlined back together +// they are one twelve-way switch whose live values exceed what greedy +// allocation can place on a single-accumulator machine, and the build +// then falls back to the basic allocator for this entire file. The call +// costs one JSL per game tick. +#if defined(__GNUC__) || defined(__clang__) + #define ST_NOINLINE __attribute__((noinline)) +#else + #define ST_NOINLINE +#endif -// Level indices (0 = A). -#define ST_LEVEL_E 4u -#define ST_LEVEL_G 6u -#define ST_LEVEL_H 7u -#define ST_LEVEL_I 8u -#define ST_LEVEL_J 9u -#define ST_LEVEL_K 10u -#define ST_LEVEL_L 11u -#define ST_LEVEL_O 14u -#define ST_LEVEL_P 15u -#define ST_LEVEL_Q 16u -#define ST_LEVEL_R 17u -#define ST_LEVEL_T 19u -#define ST_LEVEL_U 20u -#define ST_LEVEL_V 21u -#define ST_LEVEL_W 22u -#define ST_LEVEL_X 23u +// A level's read-only tables, straight out of the shipped level blob. +// Nothing writes these, so there is no working copy and no second source +// of truth: the .dat is the only place the values live. Every byte a +// hook WRITES lives in the typed per-level scratch (StHookScratchT), so +// the 616-byte C64-addressed working copy is gone. +#define HKT(sim, addr) ((sim)->level->hookData[(addr) - ST_HOOK_BASE]) +#define HKTP(sim, addr) (&(sim)->level->hookData[(addr) - ST_HOOK_BASE]) + +// ---- level H "PUZZLER" ($7E15/$7E4C/$7E53..$7E56 scratch) ---- +// Five parallel 11-entry segment tables sit back to back in the blob, +// then the pad and switch segment lists (four ids each, $80 = end). +#define H_SPR_COL(sim, k) HKT(sim, 0x7DCF + (k)) +#define H_SPR_MSB(sim, k) HKT(sim, 0x7DD7 + (k)) +#define H_SPR_ROW(sim, k) HKT(sim, 0x7DDF + (k)) +#define H_SEG_COL(sim, id) HKT(sim, 0x7DE7 + (id)) +#define H_SEG_ROW(sim, id) HKT(sim, 0x7DF2 + (id)) +#define H_SEG_SENSE(sim, id) HKT(sim, 0x7DFD + (id)) +#define H_SEG_GLYPH(sim, id) HKT(sim, 0x7E08 + (id)) +#define H_PAD_SEGS(sim, i) HKT(sim, 0x7E24 + (i)) +#define H_SFX_CHIME(sim) HKTP(sim, 0x7ED0) +// These two index PAST the table they name and must see live scratch, so +// they go through the byte-addressed view below -- see hookHByte. +#define H_SEG_FLAG(sim, id) hookHByte(sim, (uint16_t)(0x7E15u + (id))) +#define H_SWITCH_SEGS(sim, i) hookHByte(sim, (uint16_t)(0x7E38u + (i))) + +// ---- level G "TELEPORTS" ($7DD1..$7DD3/$7E00/$7F02 scratch) ---- +#define G_CEL(sim, ph) HKT(sim, ST_HK_G_CEL + (ph)) +#define G_ORB_COL(sim, x) HKT(sim, ST_HK_G_ORB_COL + (x)) +#define G_ORB_MSB(sim, x) HKT(sim, ST_HK_G_ORB_MSB + (x)) +#define G_ORB_ROW(sim, x) HKT(sim, ST_HK_G_ORB_ROW + (x)) +#define G_SPOT_COL(sim, i) HKT(sim, ST_HK_G_SPOT_COL + (i)) +#define G_SPOT_MSB(sim, i) HKT(sim, ST_HK_G_SPOT_MSB + (i)) +#define G_SPOT_ROW(sim, i) HKT(sim, ST_HK_G_SPOT_ROW + (i)) +#define G_SFX_HOP(sim) HKTP(sim, ST_HK_G_SFX_HOP) + +// ---- level I "CROSSFIRE" ($7DB8/$7DC0/$7DC8/$7DD0 scratch) ---- +#define I_CEL_RISE(sim, ph) HKT(sim, ST_HK_I_CEL_RISE + (ph)) +#define I_START_COL(sim, d) HKT(sim, 0x7DDD + (d)) +#define I_DX(sim, d) HKT(sim, 0x7DE1 + (d)) +#define I_CEL_BURST(sim, ph) HKT(sim, ST_HK_I_CEL_BURST + (ph)) +#define I_COLOR(sim, i) HKT(sim, ST_HK_I_COLOR + (i)) +#define I_SFX_FIRE(sim) HKTP(sim, 0x7E82) + +// ---- level Q "INTERFERENCE" ($7E83 scratch) ---- +#define Q_SPR_COL(sim, x) HKT(sim, 0x7DD2 + (x)) +#define Q_SPR_MSB(sim, x) HKT(sim, 0x7DDA + (x)) +#define Q_SPR_ROW(sim, x) HKT(sim, 0x7DE2 + (x)) +#define Q_INIT_PHASE(sim, x) HKT(sim, 0x7DEA + (x)) +#define Q_CEL(sim, ph) HKT(sim, ST_HK_Q_CEL + (ph)) +#define Q_SCRAMBLE(sim, i) HKT(sim, 0x7E7F + (i)) + +// ---- level T "FAST BREAK" ($7D9C scratch) ---- +#define T_SFX_BOUNCE(sim) HKTP(sim, 0x7E56) +#define T_SFX_OPEN(sim) HKTP(sim, 0x7E5F) +#define T_SFX_SHUT(sim) HKTP(sim, 0x7E68) + +// ---- level U "REBOUND" ($7F4F/$7F57/$7F5F/$7F6D/$7F6E scratch) ---- +#define U_ENTRY(sim, d) HKT(sim, 0x7F3B + (d)) +#define U_ENTRY_MSB(sim, d) HKT(sim, 0x7F3F + (d)) +#define U_DX(sim, d) HKT(sim, 0x7F43 + (d)) +#define U_DY(sim, d) HKT(sim, 0x7F47 + (d)) +#define U_CEL(sim, ph) HKT(sim, ST_HK_U_CEL + (ph)) +#define U_LIFE(sim, d) HKT(sim, 0x7F69 + (d)) +#define U_SFX_BOUNCE(sim) HKTP(sim, 0x7F76) + +// ---- level W "LASERS" ($7DAB/$7DAC/$7DB5/$7DE5 scratch) ---- +#define W_BEAM_GLYPH(sim, i) HKT(sim, 0x7DBD + (i)) +#define W_TOP_ROW(sim, i) HKT(sim, 0x7DC5 + (i)) +#define W_COL(sim, i) HKT(sim, 0x7DCD + (i)) +#define W_START_ROW(sim, i) HKT(sim, 0x7DD5 + (i)) +#define W_ROW_STEP(sim, i) HKT(sim, 0x7DDD + (i)) +#define W_FADE_COLOR(sim, ph) HKT(sim, 0x7DE6 + (ph)) +#define W_END_ROW(sim, i) HKT(sim, 0x7DE9 + (i)) +#define W_SFX_HUM(sim) HKTP(sim, 0x7DA2) + +// ---- level X "MOVING PADS" ($7D9C..$7D9E scratch) ---- +#define X_GLYPH(sim, col) HKT(sim, 0x7D9F + (col)) +#define X_PAD_SLOT(sim, k) HKTP(sim, 0x7FB8 + (k) * 8u) + +// ---- level E "BEANSTALK" ($7D98..$7D9B scratch) ---- +#define E_SFX_GROW(sim) HKTP(sim, 0x7EA0) + +// ---- level J "SHOOTING STARS" ($7ED3/$7EDB scratch) ---- +#define J_SFX_BURST(sim) HKTP(sim, 0x7DF8) + +// ---- level P "BLIZZARD" ($7F0E/$7F16/$7F1F scratch) ---- +#define P_SFX_WIND(sim) HKTP(sim, 0x7E3C) + +// ---- level S "THE SWITCH" ---- +#define S_DIR_MAP(sim, d) HKT(sim, 0x7DAC + (d)) + +// Levels J and P run the same falling-hazard program over hardware +// sprites 3..7 with their own tables: the per-sprite X drift, the +// per-sprite spawn column base and the burst (J) or melt (P) cel run. +typedef struct { + uint16_t dxTable; // blob address, indexed by sprite + uint16_t startTable; // blob address, indexed by sprite + uint16_t celTable; // blob address, indexed by phase 1..phaseEnd-1 + uint8_t phaseEnd; // the phase that ends the burst + bool avoidMiddle; // J rerolls a spawn over the middle band +} StHookFallT; + +static const StHookFallT kFallJ = { 0x7EB9u, 0x7EC1u, ST_HK_J_CEL_BURST, ST_HK_J_BURST_PHASES, true }; +static const StHookFallT kFallP = { 0x7EF9u, 0x7F01u, ST_HK_P_CEL_BURST, ST_HK_P_BURST_PHASES, false }; + +// Each level H switch drives four wall segments ($7E24 stride, $7E38 +// list), animated over four steps. +#define ST_HOOK_H_SEGMENTS 4u +#define ST_HOOK_H_STEPS 4u + +// Level U: hardware sprites 3..7 are the drifting orbs, $80 is the first +// of their three cels, and a bounce holds off for twelve ticks ($7E0E). +#define ST_HOOK_U_COOLDOWN 0x0Cu + +// Level E: pad 1 alone at first; while the fare wants UP a leaf cell +// sprouts every $57 ticks (the count starts at $1E, or $46 on the +// second screen), four to a side from columns 20/19, and the fifth +// step hangs the next pair of pads at the leaf ends, opens them and +// moves the growth four rows up, until pad 9 is out. +#define ST_HOOK_E_GROW_TICKS 0x57u +#define ST_HOOK_E_TICK_START 0x1Eu +#define ST_HOOK_E_TICK_SECOND 0x46u +#define ST_HOOK_E_LEAVES 5u +#define ST_HOOK_E_LAST_PAD 9u +#define ST_HOOK_E_FIRST_ROW 0x13u +#define ST_HOOK_E_ROW_STEP 4u +#define ST_HOOK_E_STALK_COL 20u +#define ST_HOOK_E_PAD_COL_RIGHT 25u +#define ST_HOOK_E_PAD_COL_LEFT 15u +#define ST_HOOK_E_CHAR_LEAF 0xD2u +#define ST_HOOK_E_CHAR_TIP_R 0xD7u +#define ST_HOOK_E_CHAR_TIP_L 0xD5u +#define ST_HOOK_E_COLOR 5u + +// Levels J and P: hardware sprites 3..7 are the falling hazards, cels +// $80/$81 falling and $82 the burst. An idle sprite spawns on rng($3C) +// < 3, on row $17; a scenery hit counts from row $38 down and drops +// the sprite four rows onto it. J rerolls a spawn column in $91..$CC. +#define ST_HOOK_FALL_SPRITES 0xF8u // $D01F bits 3..7 +#define ST_HOOK_FALL_SPAWN_ODDS 0x3Cu +#define ST_HOOK_FALL_SPAWN_HIT 3u +#define ST_HOOK_FALL_TOP_ROW 0x17u +#define ST_HOOK_FALL_HIT_ROW 0x38u +#define ST_HOOK_FALL_HIT_DROP 4u +#define ST_HOOK_FALL_BAND_LO 0x91u +#define ST_HOOK_FALL_BAND_HI 0xCDu + +// Level G: the five orbs (sprites 3..7) cycle a cel every fourth tick; +// the cab touching one hides it for $14 ticks under a falling tone that +// starts at $5A and drops 4 a tick, then lands it on one of five spots: +// the fare's destination pad half the time, else a random spot other +// than the last one used. +#define ST_HOOK_G_HOP_TICKS 0x14u +#define ST_HOOK_G_SWEEP_START 0x5Au +#define ST_HOOK_G_SWEEP_STEP 4u +#define ST_HOOK_G_SPOTS 5u +#define ST_HOOK_G_PAD_SPOTS 6u // a destination of 6+ (UP) lands on spot 5 + +// Level L: hardware sprite 3 is the hole at ($AC, $82), cels $80..$82. +// Gravity pulls the cab toward it: $10 (Y) and $0C (X) less an eighth +// of the distance, X measured in halved sprite units so it fits a +// byte. Touching it returns verdict $FF: a kill with no falling wreck. +#define ST_HOOK_L_HOLE 3u +#define ST_HOOK_L_HOLE_COL 0xACu +#define ST_HOOK_L_HOLE_ROW 0x82u +#define ST_HOOK_L_HOLE_X_HALF 0x5Bu +#define ST_HOOK_L_PULL_Y 0x10u +#define ST_HOOK_L_PULL_X 0x0Cu +#define ST_HOOK_L_VERDICT 0xFFu + +// Level P: the wind turns on rng($78) < 3 per tick, flipping the three +// windsock cells (glyph ^ 6) and the sign of the X gravity. +#define ST_HOOK_P_WIND_ODDS 0x78u +#define ST_HOOK_P_WIND_HIT 3u +#define ST_HOOK_P_SOCK_FLIP 0x06u + +// Level S: FIRE passes, the four direction bits go through a swap table. +#define ST_HOOK_S_FIRE 0x10u +#define ST_HOOK_S_DIRS 0x0Fu -static void addToSpriteCol(StSimT *sim, uint8_t idx, uint8_t delta); static void fillDownChar(StSimT *sim, uint8_t col, uint8_t row, uint8_t ch, uint8_t rows); static void fillDownColor(StSimT *sim, uint8_t col, uint8_t row, uint8_t color, uint8_t rows); +static uint8_t hookHByte(const StSimT *sim, uint16_t addr); static void hookHPerTick(StSimT *sim); static void hookHPrelude0(StSimT *sim); static void hookHSegment(StSimT *sim, uint8_t id); @@ -51,10 +225,10 @@ static void hookXPerTick(StSimT *sim); static void hookXPrelude0(StSimT *sim); static void hookXShiftRow(StSimT *sim, uint16_t rowCell, uint8_t from, uint8_t to, int8_t dir); static void setSpriteColors(StSimT *sim, uint8_t color); -#if !defined(__W65816__) static void hookKPerTick(StSimT *sim); static void hookOPerTick(StSimT *sim); -static void hookOTramp0(StSimT *sim); +ST_NOINLINE static void hookPerTickHigh(StSimT *sim); +ST_NOINLINE static void hookPerTickLow(StSimT *sim); static void hookRMaze(StSimT *sim, uint8_t ch); static void hookRPerTick(StSimT *sim); static void hookVPerTick(StSimT *sim); @@ -65,19 +239,28 @@ static void hookQPerTick(StSimT *sim); static uint8_t hookQInput(StSimT *sim, uint8_t input); static void hookIPrelude(StSimT *sim); static void hookIPerTick(StSimT *sim); +static void hookGHop(StSimT *sim); static void hookGPerTick(StSimT *sim); +static void hookGPrelude0(StSimT *sim); +static void hookUBounce(StSimT *sim); +static void hookUOrbs(StSimT *sim); static void hookUPerTick(StSimT *sim); -#endif - - -// $411B -- add a signed byte to a sprite's X (carry into the msb). -static void addToSpriteCol(StSimT *sim, uint8_t idx, uint8_t delta) { - uint16_t x = (uint16_t)(((uint16_t)sim->spr[idx].msb << 8) | sim->spr[idx].col); - - x = (uint16_t)(x + (uint16_t)(int16_t)(int8_t)delta); - sim->spr[idx].col = (uint8_t)x; - sim->spr[idx].msb = (uint8_t)(x >> 8); -} +static void hookUPrelude0(StSimT *sim); +static void hookEPerTick(StSimT *sim); +static void hookEPrelude0(StSimT *sim); +static void hookEPut(StSimT *sim, uint8_t col, uint8_t row, uint8_t ch); +static void hookETips(StSimT *sim); +static void hookFallBgHits(StSimT *sim, uint8_t *state, uint8_t *phase, const uint8_t *sfx); +static void hookFallTick(StSimT *sim, uint8_t *state, uint8_t *phase, const StHookFallT *d, uint8_t *parity); +static void hookJPerTick(StSimT *sim); +static void hookJPrelude0(StSimT *sim); +static void hookLPerTick(StSimT *sim); +static void hookLPrelude0(StSimT *sim); +static uint16_t hookLPull(uint8_t base, uint8_t dist); +static void hookPFlipSock(StSimT *sim, uint16_t cell); +static void hookPPerTick(StSimT *sim); +static void hookPPrelude0(StSimT *sim); +static uint8_t hookSInput(StSimT *sim, uint8_t input); // $41AD after $401B -- the same character into the `rows` cells below. @@ -104,12 +287,54 @@ static void fillDownColor(StSimT *sim, uint8_t col, uint8_t row, uint8_t color, } +// Level H's byte-addressed view of its own data block. +// +// Two of its reads index past the end of the table they name and land on +// bytes the level itself writes, so H cannot simply split into "const +// tables" plus "named scratch" the way the other levels do: +// +// * the pad lists ($7E24) end with $FF, and the landing check indexes +// the segment flags with that terminator unguarded -- $7E15 + $FF is +// $7F14, 380 bytes into the blob, holding $30. Non-zero, so the +// "no segment here" case reports a match, with switch id $FF. +// * `active` is therefore 6 on every landing, so the switch-segment +// read is always $7E38 + 20 = $7E4C -- which is where the id was just +// stored. The level writes a value and reads it back as table data. +// ($7E4D..$7E4F ship as $FF, so the $80 guard skips them and only the +// stored id is ever animated. That also bounds every write to ids +// 0..10, so the flags array is never indexed out of range.) +// +// Scratch where the level has written, the shipped blob everywhere else. +static uint8_t hookHByte(const StSimT *sim, uint16_t addr) { + const StHookHT *h = &sim->hookScratch.h; + + if (addr >= 0x7E15u && addr < 0x7E15u + ST_HOOK_H_SEGS) { + return h->segFlag[addr - 0x7E15u]; + } + switch (addr) { + case 0x7E4Cu: + return h->switchId; + case 0x7E53u: + return h->tick; + case 0x7E54u: + return h->active; + case 0x7E55u: + return h->step; + case 0x7E56u: + return h->tone; + default: + break; + } + return HKT(sim, addr); +} + + // Level H "PUZZLER" per-tick ($7E5A): touching a switch sprite, or // landing on a pad whose list holds an active segment, animates the // switch's four wall segments over four 8-tick steps with a rising // tone, then toggles their flags. static void hookHPerTick(StSimT *sim) { - if (HK(sim, 0x7E54) == 0u) { + if (sim->hookScratch.h.active == 0u) { if (sim->collisionPhase != 0u) { return; } @@ -118,10 +343,10 @@ static void hookHPerTick(StSimT *sim) { uint8_t n; bool found = false; for (n = 0u; n < 4u; n++) { - uint8_t y = HK(sim, 0x7E24 + x + n); - if (HK(sim, 0x7E15 + y) != 0u) { - HK(sim, 0x7E4C) = y; - HK(sim, 0x7E54) = 6u; + uint8_t y = H_PAD_SEGS(sim, x + n); + if (H_SEG_FLAG(sim, y) != 0u) { + sim->hookScratch.h.switchId = y; + sim->hookScratch.h.active = 6u; found = true; break; } @@ -143,47 +368,45 @@ static void hookHPerTick(StSimT *sim) { if (y == 0u) { return; } - HK(sim, 0x7E54) = y; + sim->hookScratch.h.active = y; setSpriteColors(sim, 0x0Au); sim->spr[2u + y].color = 0x05u; } // $7EB2: start the animation. - HK(sim, 0x7E53) = 0u; - HK(sim, 0x7E55) = 0xFFu; - HK(sim, 0x7E56) = 0x12u; - stAudioSfx(HKP(sim, 0x7ED0)); + sim->hookScratch.h.tick = 0u; + sim->hookScratch.h.step = 0xFFu; + sim->hookScratch.h.tone = 0x12u; + stAudioSfx(H_SFX_CHIME(sim)); } // $7ED9 - HK(sim, 0x7E56)++; - stAudioVoice2(HK(sim, 0x7E56), HK(sim, 0x7E56), 0xFFu); - HK(sim, 0x7E53)++; - if ((HK(sim, 0x7E53) & 7u) != 0u) { + sim->hookScratch.h.tone++; + stAudioVoice2(sim->hookScratch.h.tone, sim->hookScratch.h.tone, 0xFFu); + sim->hookScratch.h.tick++; + if ((sim->hookScratch.h.tick & 7u) != 0u) { return; } - HK(sim, 0x7E55)++; - HK(sim, 0x7E52) = (uint8_t)((HK(sim, 0x7E54) - 1u) * 4u); - HK(sim, 0x7E51) = 4u; - if (HK(sim, 0x7E55) == 4u) { - // $7F7C: finished -- flip the segment flags. - HK(sim, 0x7E54) = 0u; - setSpriteColors(sim, 0x01u); - while (HK(sim, 0x7E51) != 0u) { - uint8_t id = HK(sim, 0x7E38 + HK(sim, 0x7E52)); - if ((id & 0x80u) == 0u) { - HK(sim, 0x7E15 + id) ^= 1u; + sim->hookScratch.h.step++; + { + uint8_t idx = (uint8_t)((sim->hookScratch.h.active - 1u) * ST_HOOK_H_SEGMENTS); + bool last = (sim->hookScratch.h.step == ST_HOOK_H_STEPS); + uint8_t n; + + if (last) { + // $7F7C: finished -- flip the segment flags. + sim->hookScratch.h.active = 0u; + setSpriteColors(sim, 0x01u); + } + for (n = 0u; n < ST_HOOK_H_SEGMENTS; n++) { + uint8_t id = H_SWITCH_SEGS(sim, idx + n); + if ((id & 0x80u) != 0u) { + continue; + } + if (last) { + sim->hookScratch.h.segFlag[id] ^= 1u; + } else { + hookHSegment(sim, id); } - HK(sim, 0x7E52)++; - HK(sim, 0x7E51)--; } - return; - } - while (HK(sim, 0x7E51) != 0u) { - uint8_t id = HK(sim, 0x7E38 + HK(sim, 0x7E52)); - if ((id & 0x80u) == 0u) { - hookHSegment(sim, id); - } - HK(sim, 0x7E52)++; - HK(sim, 0x7E51)--; } } @@ -193,17 +416,17 @@ static void hookHPerTick(StSimT *sim) { static void hookHPrelude0(StSimT *sim) { uint8_t k; - for (k = 0u; k <= 10u; k++) { - HK(sim, 0x7E15 + k) = 0u; + for (k = 0u; k < ST_HOOK_H_SEGS; k++) { + sim->hookScratch.h.segFlag[k] = 0u; } - HK(sim, 0x7E54) = 0u; + sim->hookScratch.h.active = 0u; for (k = 7u; k >= 3u; k--) { sim->spr[k].color = 1u; sim->spr[k].enable = 1u; sim->spr[k].ptr = 0x80u; - sim->spr[k].col = HK(sim, 0x7DCF + k); - sim->spr[k].msb = HK(sim, 0x7DD7 + k); - sim->spr[k].row = HK(sim, 0x7DDF + k); + sim->spr[k].col = H_SPR_COL(sim, k); + sim->spr[k].msb = H_SPR_MSB(sim, k); + sim->spr[k].row = H_SPR_ROW(sim, k); } } @@ -216,22 +439,19 @@ static void hookHSegment(StSimT *sim, uint8_t id) { uint8_t row; uint8_t ch; - HK(sim, 0x7E50) = id; - if ((HK(sim, 0x7E15 + id) ^ HK(sim, 0x7DFD + id)) == 0u) { - step = HK(sim, 0x7E55); + if ((H_SEG_FLAG(sim, id) ^ H_SEG_SENSE(sim, id)) == 0u) { + step = sim->hookScratch.h.step; } else { - step = (uint8_t)(3u - HK(sim, 0x7E55)); + step = (uint8_t)(3u - sim->hookScratch.h.step); } - if (HK(sim, 0x7E08 + id) == 0x9Bu) { - row = (uint8_t)(step + HK(sim, 0x7DF2 + id)); - col = HK(sim, 0x7DE7 + id); + if (H_SEG_GLYPH(sim, id) == 0x9Bu) { + row = (uint8_t)(step + H_SEG_ROW(sim, id)); + col = H_SEG_COL(sim, id); } else { - col = (uint8_t)(step + HK(sim, 0x7DE7 + id)); - row = HK(sim, 0x7DF2 + id); + col = (uint8_t)(step + H_SEG_COL(sim, id)); + row = H_SEG_ROW(sim, id); } - HK(sim, 0x7E13) = col; - HK(sim, 0x7E14) = row; - ch = (HK(sim, 0x7E15 + id) != 0u) ? HK(sim, 0x7E08 + id) : ST_CHAR_SPACE; + ch = (H_SEG_FLAG(sim, id) != 0u) ? H_SEG_GLYPH(sim, id) : ST_CHAR_SPACE; stSimPutChar(sim, col, row, ch); } @@ -257,7 +477,7 @@ static void hookTGate(StSimT *sim, uint8_t ch) { // the ceiling; a fast climb into the top rows slams the centre gate // shut and opens the sides until the cab drops back down. static void hookTPerTick(StSimT *sim) { - if (HK(sim, 0x7D9C) != 0u) { + if (sim->hookScratch.t.gateShut != 0u) { if (sim->spr[0].row >= 0x5Fu) { hookTPrelude1(sim); } @@ -274,7 +494,7 @@ static void hookTPerTick(StSimT *sim) { return; } sim->velY = (int16_t)(-sim->velY); - stAudioSfx(HKP(sim, 0x7E56)); + stAudioSfx(T_SFX_BOUNCE(sim)); return; } if (sim->spr[0].row >= 0x3Fu) { @@ -283,16 +503,16 @@ static void hookTPerTick(StSimT *sim) { sim->velY = 0; sim->velX = 0; hookTGate(sim, 0x75u); - HK(sim, 0x7D9C)++; - stAudioSfx(HKP(sim, 0x7E68)); + sim->hookScratch.t.gateShut++; + stAudioSfx(T_SFX_SHUT(sim)); } // Level T prelude 1 ($7DC4): centre open, sides barred. static void hookTPrelude1(StSimT *sim) { hookTGate(sim, ST_CHAR_SPACE); - HK(sim, 0x7D9C) = 0u; - stAudioSfx(HKP(sim, 0x7E5F)); + sim->hookScratch.t.gateShut = 0u; + stAudioSfx(T_SFX_OPEN(sim)); } @@ -305,53 +525,52 @@ static void hookWLaserUpdate(StSimT *sim) { uint8_t k; for (k = 0u; k < 16u; k++) { - sim->charset[0x92u + (k >> 3)][k & 7u] = 0u; + stSimGlyphMut(sim, (uint8_t)(0x92u + (k >> 3)))[k & 7u] = 0u; } stSimMarkChar(sim, 0x92u); stSimMarkChar(sim, 0x93u); for (i = 0u; i < 8u; i++) { - uint8_t state = HK(sim, 0x7DAC + i); - uint8_t col = HK(sim, 0x7DCD + i); - HK(sim, 0x7DB4) = i; + uint8_t state = sim->hookScratch.w.state[i]; + uint8_t col = W_COL(sim, i); if (state == 0u) { if (stSimRng(sim, 12u) >= 3u) { continue; } - HK(sim, 0x7DAC + i) = 1u; - HK(sim, 0x7DB5 + i) = HK(sim, 0x7DD5 + i); - stSimPutChar(sim, col, HK(sim, 0x7DB5 + i), HK(sim, 0x7DBD + i)); - stSimPutColor(sim, col, HK(sim, 0x7DB5 + i), 2u); + sim->hookScratch.w.state[i] = 1u; + sim->hookScratch.w.row[i] = W_START_ROW(sim, i); + stSimPutChar(sim, col, sim->hookScratch.w.row[i], W_BEAM_GLYPH(sim, i)); + stSimPutColor(sim, col, sim->hookScratch.w.row[i], 2u); continue; } if (state == 1u) { - stSimPutChar(sim, col, HK(sim, 0x7DB5 + i), 0x91u); - if (HK(sim, 0x7DB5 + i) == HK(sim, 0x7DE9 + i)) { - HK(sim, 0x7DAC + i) = 2u; - HK(sim, 0x7DB5 + i) = 0u; + stSimPutChar(sim, col, sim->hookScratch.w.row[i], 0x91u); + if (sim->hookScratch.w.row[i] == W_END_ROW(sim, i)) { + sim->hookScratch.w.state[i] = 2u; + sim->hookScratch.w.row[i] = 0u; state = 2u; } else { - HK(sim, 0x7DB5 + i) = (uint8_t)(HK(sim, 0x7DB5 + i) + HK(sim, 0x7DDD + i)); - stSimPutChar(sim, col, HK(sim, 0x7DB5 + i), HK(sim, 0x7DBD + i)); - stSimPutColor(sim, col, HK(sim, 0x7DB5 + i), 2u); + sim->hookScratch.w.row[i] = (uint8_t)(sim->hookScratch.w.row[i] + W_ROW_STEP(sim, i)); + stSimPutChar(sim, col, sim->hookScratch.w.row[i], W_BEAM_GLYPH(sim, i)); + stSimPutColor(sim, col, sim->hookScratch.w.row[i], 2u); continue; } } if (state == 2u) { uint8_t phase; - HK(sim, 0x7DB5 + i)++; - phase = HK(sim, 0x7DB5 + i); + sim->hookScratch.w.row[i]++; + phase = sim->hookScratch.w.row[i]; if (phase == 3u) { - HK(sim, 0x7DAC + i) = 0u; - stSimPutChar(sim, col, HK(sim, 0x7DC5 + i), ST_CHAR_SPACE); - fillDownChar(sim, col, HK(sim, 0x7DC5 + i), ST_CHAR_SPACE, 7u); + sim->hookScratch.w.state[i] = 0u; + stSimPutChar(sim, col, W_TOP_ROW(sim, i), ST_CHAR_SPACE); + fillDownChar(sim, col, W_TOP_ROW(sim, i), ST_CHAR_SPACE, 7u); } else { - uint8_t color = HK(sim, 0x7DE6 + phase); - stSimPutColor(sim, col, HK(sim, 0x7DC5 + i), color); - fillDownColor(sim, col, HK(sim, 0x7DC5 + i), color, 7u); + uint8_t color = W_FADE_COLOR(sim, phase); + stSimPutColor(sim, col, W_TOP_ROW(sim, i), color); + fillDownColor(sim, col, W_TOP_ROW(sim, i), color, 7u); } continue; } - HK(sim, 0x7DAC + i) = 0u; + sim->hookScratch.w.state[i] = 0u; } } @@ -361,22 +580,22 @@ static void hookWLaserUpdate(StSimT *sim) { static void hookWPerTick(StSimT *sim) { for (;;) { uint8_t c; - if (HK(sim, 0x7DE5) != 0u) { - stAudioSfx(HKP(sim, 0x7DA2)); - HK(sim, 0x7DE5) = 0u; + if (sim->hookScratch.w.started != 0u) { + stAudioSfx(W_SFX_HUM(sim)); + sim->hookScratch.w.started = 0u; } c = (uint8_t)(stSimRng(sim, 0x6Eu) + 6u); stAudioVoice2(c, c, 0x81u); - HK(sim, 0x7DAB) = (uint8_t)((HK(sim, 0x7DAB) + 1u) & 7u); - c = HK(sim, 0x7DAB); + sim->hookScratch.w.colorPhase = (uint8_t)((sim->hookScratch.w.colorPhase + 1u) & 7u); + c = sim->hookScratch.w.colorPhase; if (c == 0u) { hookWLaserUpdate(sim); return; } // $2C8F + c (char $91 row 7 for c = 0, else char $92 rows 0..6) // and $2C98 + (8 - c) (char $93 rows 7..1) get a beam bar. - sim->charset[0x91u + ((7u + c) >> 3)][(7u + c) & 7u] = 0x3Cu; - sim->charset[0x93u][8u - c] = 0x3Cu; + stSimGlyphMut(sim, (uint8_t)(0x91u + ((7u + c) >> 3)))[(7u + c) & 7u] = 0x3Cu; + stSimGlyphMut(sim, 0x93u)[8u - c] = 0x3Cu; stSimMarkChar(sim, 0x92u); stSimMarkChar(sim, 0x93u); if ((c & 1u) == 0u) { @@ -389,11 +608,11 @@ static void hookWPerTick(StSimT *sim) { // Level X "ON THE MOVE" $7F53 -- shift every pad's X bounds and stand // column, the waiting passenger, and a parked cab, by the step. static void hookXMovePads(StSimT *sim) { - uint8_t dir = HK(sim, 0x7D9D); + uint8_t dir = sim->hookScratch.x.dir; uint8_t off = 0u; uint8_t phase = 0u; - addToSpriteCol(sim, 1u, dir); + stSimSpriteAddX(sim, 1u, dir); while (off < 0x48u) { uint8_t pad = (uint8_t)(off >> 3); uint8_t field = (uint8_t)(off & 7u); @@ -425,7 +644,7 @@ static void hookXMovePads(StSimT *sim) { if (sim->activePad == 0u) { return; } - addToSpriteCol(sim, 0u, dir); + stSimSpriteAddX(sim, 0u, dir); sim->posXmsb = sim->spr[0].msb; sim->hoverXFrac = sim->spr[0].msb; sim->posXcol = sim->spr[0].col; @@ -438,22 +657,22 @@ static void hookXMovePads(StSimT *sim) { static void hookXPerTick(StSimT *sim) { uint8_t col; - HK(sim, 0x7D9E)++; - if (HK(sim, 0x7D9E) != 0x10u) { + sim->hookScratch.x.tick++; + if (sim->hookScratch.x.tick != 0x10u) { return; } - HK(sim, 0x7D9E) = 0u; - col = HK(sim, 0x7D9C); - if ((HK(sim, 0x7D9D) & 0x80u) == 0u) { + sim->hookScratch.x.tick = 0u; + col = sim->hookScratch.x.col; + if ((sim->hookScratch.x.dir & 0x80u) == 0u) { // $7DF9: rightward. hookXShiftRow(sim, ST_CELL(5, 0), (uint8_t)(col + 17u), col, 1); hookXShiftRow(sim, ST_CELL(10, 0), (uint8_t)(col + 17u), col, 1); hookXShiftRow(sim, ST_CELL(15, 0), (uint8_t)(col + 17u), col, 1); hookXShiftRow(sim, ST_CELL(20, 0), (uint8_t)(col + 17u), col, 1); - stSimPutChar(sim, col, 5u, HK(sim, 0x7D9F + col)); - stSimPutChar(sim, col, 10u, HK(sim, 0x7D9F + col)); - stSimPutChar(sim, col, 15u, HK(sim, 0x7D9F + col)); - stSimPutChar(sim, col, 20u, HK(sim, 0x7D9F + col)); + stSimPutChar(sim, col, 5u, X_GLYPH(sim, col)); + stSimPutChar(sim, col, 10u, X_GLYPH(sim, col)); + stSimPutChar(sim, col, 15u, X_GLYPH(sim, col)); + stSimPutChar(sim, col, 20u, X_GLYPH(sim, col)); stSimPutColor(sim, col, 5u, 0x0Cu); stSimPutColor(sim, col, 10u, 0x0Cu); stSimPutColor(sim, col, 15u, 0x0Cu); @@ -462,18 +681,18 @@ static void hookXPerTick(StSimT *sim) { stSimPutChar(sim, (uint8_t)(col + 12u), 14u, sim->screen[ST_CELL(14, col + 11u)]); stSimPutChar(sim, (uint8_t)(col + 11u), 14u, ST_CHAR_SPACE); stSimPutColor(sim, (uint8_t)(col + 12u), 14u, 7u); - stSimPutChar(sim, (uint8_t)(col + 11u), 5u, HK(sim, 0x7D9F + col + 11u)); - stSimPutChar(sim, (uint8_t)(col + 11u), 10u, HK(sim, 0x7D9F + col + 11u)); - stSimPutChar(sim, (uint8_t)(col + 11u), 15u, HK(sim, 0x7D9F + col + 11u)); - stSimPutChar(sim, (uint8_t)(col + 11u), 20u, HK(sim, 0x7D9F + col + 11u)); + stSimPutChar(sim, (uint8_t)(col + 11u), 5u, X_GLYPH(sim, col + 11u)); + stSimPutChar(sim, (uint8_t)(col + 11u), 10u, X_GLYPH(sim, col + 11u)); + stSimPutChar(sim, (uint8_t)(col + 11u), 15u, X_GLYPH(sim, col + 11u)); + stSimPutChar(sim, (uint8_t)(col + 11u), 20u, X_GLYPH(sim, col + 11u)); stSimPutColor(sim, (uint8_t)(col + 11u), 5u, 0x0Cu); stSimPutColor(sim, (uint8_t)(col + 11u), 10u, 0x0Cu); stSimPutColor(sim, (uint8_t)(col + 11u), 15u, 0x0Cu); stSimPutColor(sim, (uint8_t)(col + 11u), 20u, 0x0Cu); hookXMovePads(sim); - HK(sim, 0x7D9C)++; - if (HK(sim, 0x7D9C) == 0x15u) { - HK(sim, 0x7D9D) = 0xF8u; + sim->hookScratch.x.col++; + if (sim->hookScratch.x.col == 0x15u) { + sim->hookScratch.x.dir = 0xF8u; } return; } @@ -482,10 +701,10 @@ static void hookXPerTick(StSimT *sim) { hookXShiftRow(sim, ST_CELL(10, 0), col, (uint8_t)(col + 17u), -1); hookXShiftRow(sim, ST_CELL(15, 0), col, (uint8_t)(col + 17u), -1); hookXShiftRow(sim, ST_CELL(20, 0), col, (uint8_t)(col + 17u), -1); - stSimPutChar(sim, (uint8_t)(col + 6u), 5u, HK(sim, 0x7D9F + col + 6u)); - stSimPutChar(sim, (uint8_t)(col + 6u), 10u, HK(sim, 0x7D9F + col + 6u)); - stSimPutChar(sim, (uint8_t)(col + 6u), 15u, HK(sim, 0x7D9F + col + 6u)); - stSimPutChar(sim, (uint8_t)(col + 6u), 20u, HK(sim, 0x7D9F + col + 6u)); + stSimPutChar(sim, (uint8_t)(col + 6u), 5u, X_GLYPH(sim, col + 6u)); + stSimPutChar(sim, (uint8_t)(col + 6u), 10u, X_GLYPH(sim, col + 6u)); + stSimPutChar(sim, (uint8_t)(col + 6u), 15u, X_GLYPH(sim, col + 6u)); + stSimPutChar(sim, (uint8_t)(col + 6u), 20u, X_GLYPH(sim, col + 6u)); stSimPutColor(sim, (uint8_t)(col + 6u), 5u, 0x0Cu); stSimPutColor(sim, (uint8_t)(col + 6u), 10u, 0x0Cu); stSimPutColor(sim, (uint8_t)(col + 6u), 15u, 0x0Cu); @@ -493,18 +712,18 @@ static void hookXPerTick(StSimT *sim) { stSimPutChar(sim, (uint8_t)(col + 10u), 14u, sim->screen[ST_CELL(14, col + 11u)]); stSimPutChar(sim, (uint8_t)(col + 11u), 14u, ST_CHAR_SPACE); stSimPutColor(sim, (uint8_t)(col + 10u), 14u, 7u); - stSimPutChar(sim, (uint8_t)(col + 17u), 5u, HK(sim, 0x7D9F + col + 17u)); - stSimPutChar(sim, (uint8_t)(col + 17u), 10u, HK(sim, 0x7D9F + col + 17u)); - stSimPutChar(sim, (uint8_t)(col + 17u), 15u, HK(sim, 0x7D9F + col + 17u)); - stSimPutChar(sim, (uint8_t)(col + 17u), 20u, HK(sim, 0x7D9F + col + 17u)); + stSimPutChar(sim, (uint8_t)(col + 17u), 5u, X_GLYPH(sim, col + 17u)); + stSimPutChar(sim, (uint8_t)(col + 17u), 10u, X_GLYPH(sim, col + 17u)); + stSimPutChar(sim, (uint8_t)(col + 17u), 15u, X_GLYPH(sim, col + 17u)); + stSimPutChar(sim, (uint8_t)(col + 17u), 20u, X_GLYPH(sim, col + 17u)); stSimPutColor(sim, (uint8_t)(col + 17u), 5u, 0x0Cu); stSimPutColor(sim, (uint8_t)(col + 17u), 10u, 0x0Cu); stSimPutColor(sim, (uint8_t)(col + 17u), 15u, 0x0Cu); stSimPutColor(sim, (uint8_t)(col + 17u), 20u, 0x0Cu); hookXMovePads(sim); - HK(sim, 0x7D9C)--; - if (HK(sim, 0x7D9C) == 1u) { - HK(sim, 0x7D9D) = 0x08u; + sim->hookScratch.x.col--; + if (sim->hookScratch.x.col == 1u) { + sim->hookScratch.x.dir = 0x08u; } } @@ -514,11 +733,11 @@ static void hookXPerTick(StSimT *sim) { static void hookXPrelude0(StSimT *sim) { uint8_t k; - HK(sim, 0x7D9D) = 0xF8u; - HK(sim, 0x7D9C) = 0x0Bu; - HK(sim, 0x7D9E) = 0u; + sim->hookScratch.x.dir = 0xF8u; + sim->hookScratch.x.col = 0x0Bu; + sim->hookScratch.x.tick = 0u; for (k = 0u; k < 8u; k++) { - const uint8_t *src = HKP(sim, 0x7FB8 + k * 8u); + const uint8_t *src = X_PAD_SLOT(sim, k); sim->pads[k].x1Hi = src[0]; sim->pads[k].x1Lo = src[1]; sim->pads[k].x2Hi = src[2]; @@ -565,7 +784,6 @@ static void setSpriteColors(StSimT *sim, uint8_t color) { // IIgs, whose bank-0 BSS/text budget is too tight for them; those // levels play without their hazard there (the 4 attract-demo levels // H/T/W/X keep their gimmicks everywhere). -#if !defined(__W65816__) // Level K "MAGNETS" per-tick ($7D9D): the level is globally anti-gravity // (its yGrav template is -7); the hook only animates the six magnet // poles by flipping bit 0 of their glyphs every other tick. @@ -573,8 +791,8 @@ static void hookKPerTick(StSimT *sim) { static const uint8_t kPole[6] = { 0x04u, 0x0Bu, 0x10u, 0x17u, 0x1Cu, 0x23u }; uint8_t k; - HK(sim, 0x7DBD) ^= 1u; - if ((HK(sim, 0x7DBD) & 1u) != 0u) { + sim->hookScratch.k.parity ^= 1u; + if ((sim->hookScratch.k.parity & 1u) != 0u) { return; } for (k = 0u; k < 6u; k++) { @@ -606,14 +824,14 @@ static void hookOScrollRow(StSimT *sim, uint16_t base) { static void hookOPerTick(StSimT *sim) { - hookOTramp0(sim); // $7E6E bitScroll of the barrier glyph - HK(sim, 0x7DBD) = (uint8_t)((HK(sim, 0x7DBD) + 1u) & 7u); - if ((HK(sim, 0x7DBD) & 3u) != 0u) { + stSimRotateGlyph(sim, ST_CHAR_ELECTROID); // $7E6E bitScroll of the barrier glyph + sim->hookScratch.o.tick = (uint8_t)((sim->hookScratch.o.tick + 1u) & 7u); + if ((sim->hookScratch.o.tick & 3u) != 0u) { return; } hookOScrollRow(sim, 0xC9u); // $04C9 row 5 col 1 hookOScrollRow(sim, 0x209u); // $0609 row 12 col 1 - if (HK(sim, 0x7DBD) != 4u) { + if (sim->hookScratch.o.tick != 4u) { return; } hookOScrollRow(sim, 0x169u); // $0569 row 9 col 1 @@ -621,18 +839,6 @@ static void hookOPerTick(StSimT *sim) { } -// $63D0-style rotate of the two electroid glyphs ($2BE0 / char $6F). -static void hookOTramp0(StSimT *sim) { - uint8_t k; - - for (k = 0u; k < 8u; k++) { - uint8_t b = sim->charset[0x6Fu][k]; - sim->charset[0x6Fu][k] = (uint8_t)((b >> 1) | (b << 7)); - } - stSimMarkChar(sim, 0x6Fu); -} - - // Level R "TAXI MAZE" wall program ($7D9C): put `ch` in the vertical // bar cells ($043E/$0466/$048E) and its complement (ch^$46) in the // gate cells; called with $66 to close, with the gate's own char to @@ -665,10 +871,10 @@ static void hookRPerTick(StSimT *sim) { } -// Level V "SHIFT-O-RAMA" per-tick ($7D9D): every screen tick, 18 rows -// (0..17) each scroll one column, alternating direction by row (bit 1 -// of the row index): even-band rows left, odd-band right, the edge -// cell wrapping around. +// Level V "SHIFT-O-RAMA" per-tick ($7D9D): every screen tick, the 12 +// rows 6..17 each scroll one column, alternating direction in bands of +// two (bit 1 of the row index): bands with the bit set go left, the +// others right, the edge cell wrapping around. static void hookVScrollLeft(StSimT *sim, uint8_t row) { uint16_t base = ST_CELL(row, 0); uint8_t chSave = sim->screen[base]; @@ -721,12 +927,12 @@ static void hookQPrelude(StSimT *sim) { sim->multiColorMask = 0xFFu; for (x = 7; x >= 3; x--) { - HK(sim, 0x7E83 + (uint8_t)x) = HK(sim, 0x7DEA + (uint8_t)x); - sim->spr[x].ptr = HK(sim, 0x7DF2 + HK(sim, 0x7E83 + (uint8_t)x)); + sim->hookScratch.q.phase[x] = Q_INIT_PHASE(sim, x); + sim->spr[x].ptr = Q_CEL(sim, sim->hookScratch.q.phase[x]); sim->spr[x].color = 0x06u; - sim->spr[x].col = HK(sim, 0x7DD2 + (uint8_t)x); - sim->spr[x].msb = HK(sim, 0x7DDA + (uint8_t)x); - sim->spr[x].row = HK(sim, 0x7DE2 + (uint8_t)x); + sim->spr[x].col = Q_SPR_COL(sim, x); + sim->spr[x].msb = Q_SPR_MSB(sim, x); + sim->spr[x].row = Q_SPR_ROW(sim, x); sim->spr[x].enable = 1u; } } @@ -736,11 +942,11 @@ static void hookQPerTick(StSimT *sim) { int8_t x; for (x = 7; x >= 3; x--) { - HK(sim, 0x7E83 + (uint8_t)x)++; - if (HK(sim, 0x7E83 + (uint8_t)x) == 0x0Cu) { - HK(sim, 0x7E83 + (uint8_t)x) = 0u; + sim->hookScratch.q.phase[x]++; + if (sim->hookScratch.q.phase[x] == 0x0Cu) { + sim->hookScratch.q.phase[x] = 0u; } - sim->spr[x].ptr = HK(sim, 0x7DF2 + HK(sim, 0x7E83 + (uint8_t)x)); + sim->spr[x].ptr = Q_CEL(sim, sim->hookScratch.q.phase[x]); } } @@ -755,7 +961,7 @@ static uint8_t hookQInput(StSimT *sim, uint8_t input) { if (stSimRng(sim, 10u) < 8u) { return input; } - return HK(sim, 0x7E7F + (uint8_t)(stSimRng(sim, 4u) - 1u)); + return Q_SCRAMBLE(sim, (uint8_t)(stSimRng(sim, 4u) - 1u)); } @@ -765,7 +971,7 @@ static void hookIPrelude(StSimT *sim) { int8_t x; for (x = 7; x >= 3; x--) { - HK(sim, 0x7DB8 + (uint8_t)x) = 0u; // state + sim->hookScratch.i.state[x] = 0u; // state sim->spr[x].enable = 0u; sim->spr[x].color = 0x02u; } @@ -777,105 +983,632 @@ static void hookIPerTick(StSimT *sim) { for (s = 3; s <= 7; s++) { uint8_t x = (uint8_t)s; - uint8_t state = HK(sim, 0x7DB8 + x); + uint8_t state = sim->hookScratch.i.state[x]; if (state == 0u) { uint8_t y; if (stSimRng(sim, 0x75u) >= 3u) { continue; } y = (uint8_t)(stSimRng(sim, 4u) - 1u); // 0..3 direction - HK(sim, 0x7DC8 + x) = HK(sim, 0x7DE1 + y); // dx - sim->spr[x].col = HK(sim, 0x7DDD + y); // start col + sim->hookScratch.i.dx[x] = I_DX(sim, y); // dx + sim->spr[x].col = I_START_COL(sim, y); // start col sim->spr[x].msb = 0u; sim->spr[x].row = 0xD1u; - HK(sim, 0x7DD0 + x) = (uint8_t)(stSimRng(sim, 2u) + 0xFDu); // dy = rng(2)-3 - HK(sim, 0x7DC0 + x) = 0u; // cel phase - HK(sim, 0x7DB8 + x) = 1u; + sim->hookScratch.i.dy[x] = (uint8_t)(stSimRng(sim, 2u) + 0xFDu); // dy = rng(2)-3 + sim->hookScratch.i.phase[x] = 0u; // cel phase + sim->hookScratch.i.state[x] = 1u; sim->spr[x].enable = 1u; sim->spr[x].ptr = 0x84u; - stAudioSfx(HKP(sim, 0x7E82)); - sim->spr[x].color = HK(sim, 0x7E7F + (uint8_t)(stSimRng(sim, 3u) - 1u)); + stAudioSfx(I_SFX_FIRE(sim)); + sim->spr[x].color = I_COLOR(sim, (uint8_t)(stSimRng(sim, 3u) - 1u)); } else if (state == 1u) { - uint8_t ph = (uint8_t)(HK(sim, 0x7DC0 + x) + 1u); - HK(sim, 0x7DC0 + x) = ph; + uint8_t ph = (uint8_t)(sim->hookScratch.i.phase[x] + 1u); + sim->hookScratch.i.phase[x] = ph; if (ph == 5u) { - HK(sim, 0x7DC0 + x) = 0u; + sim->hookScratch.i.phase[x] = 0u; sim->spr[x].ptr = 0x85u; - HK(sim, 0x7DB8 + x) = 2u; + sim->hookScratch.i.state[x] = 2u; } else { if (ph >= 3u) { - addToSpriteCol(sim, x, HK(sim, 0x7DC8 + x)); - sim->spr[x].row = (uint8_t)(sim->spr[x].row + HK(sim, 0x7DD0 + x)); + stSimSpriteAddX(sim, x, sim->hookScratch.i.dx[x]); + sim->spr[x].row = (uint8_t)(sim->spr[x].row + sim->hookScratch.i.dy[x]); } - sim->spr[x].ptr = HK(sim, 0x7DD8 + ph); + sim->spr[x].ptr = I_CEL_RISE(sim, ph); } } else { - uint8_t ph = (uint8_t)((HK(sim, 0x7DC0 + x) + 1u) & 7u); - HK(sim, 0x7DC0 + x) = ph; - sim->spr[x].ptr = HK(sim, 0x7DE5 + ph); - addToSpriteCol(sim, x, HK(sim, 0x7DC8 + x)); - sim->spr[x].row = (uint8_t)(sim->spr[x].row + HK(sim, 0x7DD0 + x)); + uint8_t ph = (uint8_t)((sim->hookScratch.i.phase[x] + 1u) & 7u); + sim->hookScratch.i.phase[x] = ph; + sim->spr[x].ptr = I_CEL_BURST(sim, ph); + stSimSpriteAddX(sim, x, sim->hookScratch.i.dx[x]); + sim->spr[x].row = (uint8_t)(sim->spr[x].row + sim->hookScratch.i.dy[x]); if (sim->spr[x].row < 0x25u) { sim->spr[x].enable = 0u; - HK(sim, 0x7DB8 + x) = 0u; + sim->hookScratch.i.state[x] = 0u; } } } } -// Level G "TELEPORTS" ($7DA0 prelude0, $7DF2 per-tick2): orbs (hw 2..7) -// drift; touching the cab teleports it. Faithful reduction: place and -// drift the orb sprites (the teleport hop is a rare event we leave to -// the collision system, which crashes on contact like the C64's -// undelivered case -- see MECHANICS). Placement only for now. -static void hookGPerTick(StSimT *sim) { - // The orb colour cycles through $7DD4[phase] every 4 ticks. - HK(sim, 0x7E00)++; - if ((HK(sim, 0x7E00) & 3u) != 0u) { - return; +// Level G "TELEPORTS" ($7DA0 prelude0, $7D9A prelude1, $7E01 per-tick2, +// $7DF2 verdict): five orbs on sprites 3..7 sit where the blob puts them +// and cycle their cel every fourth tick. The cab touching one starts a +// hop: it vanishes for $14 ticks with the stick dead and a falling tone, +// then reappears on a landing spot (hookGHop). Touching an orb is never +// a kill -- the verdict is 0 unless the scenery was hit, and a scenery +// hit also puts the cab sprite back. +static void hookGPrelude0(StSimT *sim) { + uint8_t x; + + for (x = ST_HOOK_FIRST_SPRITE; x < ST_HW_SPRITES; x++) { + sim->spr[x].color = ST_HK_G_ORB_COLOR; + sim->spr[x].ptr = HKT(sim, ST_HK_G_CEL); + sim->spr[x].col = G_ORB_COL(sim, x); + sim->spr[x].msb = G_ORB_MSB(sim, x); + sim->spr[x].row = G_ORB_ROW(sim, x); + sim->spr[x].enable = 1u; } - HK(sim, 0x7DD2)++; - if (HK(sim, 0x7DD2) == 6u) { - HK(sim, 0x7DD2) = 0u; - } - { - uint8_t c = HK(sim, 0x7DD4 + HK(sim, 0x7DD2)); - uint8_t k; - for (k = 2u; k < ST_HW_SPRITES; k++) { - sim->spr[k].color = c; + sim->hookScratch.g.celPhase = 0u; + sim->hookScratch.g.hop = 0u; +} + + +// $7E4A -- land: rng(2) odd with a fare aboard sends the cab to its +// destination pad's spot (UP and beyond = spot 5); otherwise a random +// spot, stepped down if it repeats the last one (wrapping 1 -> 5). +static void hookGHop(StSimT *sim) { + StHookGT *g = &sim->hookScratch.g; + uint8_t spot; + uint8_t i; + + if ((stSimRng(sim, 2u) & 1u) != 0u && sim->activeSpriteIdx != 0u) { + spot = sim->activeSpriteIdx; + if (spot >= ST_HOOK_G_PAD_SPOTS) { + spot = ST_HOOK_G_SPOTS; + } + } else { + spot = stSimRng(sim, ST_HOOK_G_SPOTS); + if (spot == g->lastSpot) { + spot--; + if (spot == 0u) { + spot = ST_HOOK_G_SPOTS; + } } } + g->lastSpot = spot; + i = (uint8_t)(spot - 1u); + sim->spr[0].col = G_SPOT_COL(sim, i); + sim->posXcol = sim->spr[0].col; + sim->spr[0].msb = G_SPOT_MSB(sim, i); + sim->posXmsb = sim->spr[0].msb; + sim->spr[0].row = G_SPOT_ROW(sim, i); + sim->posYrow = sim->spr[0].row; + sim->velX = 0; + sim->velY = 0; } -// Level U "REBOUND" ($7DAD per-tick2): the ceiling and side walls -// reflect the cab's velocity; hazard sprites (hw 3..7) drift. The wall -// bounce is the gimmick and is handled here on the taxi velocity. -static void hookUPerTick(StSimT *sim) { - if (sim->collisionPhase != 0u) { +static void hookGPerTick(StSimT *sim) { + StHookGT *g = &sim->hookScratch.g; + uint8_t x; + + g->tick++; + if ((g->tick & 3u) == 0u) { + uint8_t cel; + g->celPhase++; + if (g->celPhase == ST_HK_G_CEL_PHASES) { + g->celPhase = 0u; + } + cel = G_CEL(sim, g->celPhase); + for (x = ST_HOOK_FIRST_SPRITE; x < ST_HW_SPRITES; x++) { + sim->spr[x].ptr = cel; + } + } + if (g->hop != 0u) { + // $7ECB: the tone falls, the stick is dead, and at zero the cab + // is back and lands. + g->sweep = (uint8_t)(g->sweep - ST_HOOK_G_SWEEP_STEP); + stAudioVoice1Freq(g->sweep); + sim->dirMask = 0u; + g->hop--; + if (g->hop == 0u) { + sim->spr[0].enable = 1u; + hookGHop(sim); + } return; } - // $7DB2: near the top -> reflect Y downward. - if (sim->spr[0].row < 0x11u) { - sim->velY = (int16_t)(-sim->velY); - sim->spr[0].row = (uint8_t)(sim->spr[0].row + 2u); + if ((sim->spriteSpriteColl & ST_HOOK_FALL_SPRITES) == 0u) { + return; + } + // $7E3D: an orb took the cab. + sim->spr[0].enable = 0u; + g->hop = ST_HOOK_G_HOP_TICKS; + stAudioSfx(G_SFX_HOP(sim)); + g->sweep = ST_HOOK_G_SWEEP_START; + sim->velX = 0; + sim->velY = 0; +} + + +// Level U "REBOUND" ($7D9B prelude0, $7DAD per-tick2). Five orbs drift +// in from the four edges on their own cel walk; touching one bounces the +// cab by negating its velocity. The orb cels are the level's own sprite +// blocks $80..$82, and every table below is read from the shipped hook +// blob at the address the original indexes. +// +// $7D9B -- clear the orb states and colour the five hazard sprites. +static void hookUPrelude0(StSimT *sim) { + uint8_t x; + + for (x = (uint8_t)(ST_HW_SPRITES - 1u); x >= ST_HOOK_FIRST_SPRITE; x--) { + sim->hookScratch.u.state[x] = 0u; + sim->spr[x].color = 0x07u; } } -#endif /* !IIGS */ +// $7DDD -- a cab/orb contact bounces the cab, then holds off for twelve +// ticks. Which axes flip is a 1-in-3 roll: 1 negates both, 2 negates +// neither and 3 negates Y alone -- the original has no X-only case. +static void hookUBounce(StSimT *sim) { + uint8_t coll; + uint8_t hits = 0u; + uint8_t b; + uint8_t pick; + + if (sim->hookScratch.u.cooldown != 0u) { + sim->hookScratch.u.cooldown--; + return; + } + if ((sim->spriteSpriteColl & 1u) == 0u) { + return; + } + // $7DF8: count the orb sprites in the latch (bits 7..3). + coll = sim->spriteSpriteColl; + for (b = (uint8_t)(ST_HW_SPRITES - 1u); b >= ST_HOOK_FIRST_SPRITE; b--) { + if ((coll & 0x80u) != 0u) { + hits++; + } + coll = (uint8_t)(coll << 1); + } + if (hits == 0u) { + return; + } + sim->hookScratch.u.cooldown = ST_HOOK_U_COOLDOWN; + stAudioSfx(U_SFX_BOUNCE(sim)); + pick = stSimRng(sim, 3u); + if (pick == 1u) { + sim->velX = (int16_t)(-sim->velX); + } + if (pick == 2u) { + return; + } + sim->velY = (int16_t)(-sim->velY); +} + + +// $7E60 -- the orbs themselves: an idle one appears on a 2-in-30 roll +// from one of the four edges (0 up from the floor, 1 down from the +// ceiling, 2 in from the left, 3 in from the right), an active one +// drifts by its direction's step, walks its cel and expires. +static void hookUOrbs(StSimT *sim) { + uint8_t x; + + for (x = (uint8_t)(ST_HW_SPRITES - 1u); x >= ST_HOOK_FIRST_SPRITE; x--) { + uint8_t state = sim->hookScratch.u.state[x]; + uint8_t dir; + + if (state == 0u) { + // $7E84: nothing here yet -- maybe start one. + if (stSimRng(sim, 0x1Eu) >= 3u) { + continue; + } + dir = (uint8_t)(stSimRng(sim, 4u) - 1u); + sim->hookScratch.u.dir[x] = dir; + sim->hookScratch.u.life[x] = U_LIFE(sim, dir); + if ((dir & 2u) == 0u) { + // $7EA8: fixed row, random column. + uint16_t col = (uint16_t)(((uint16_t)stSimRng(sim, 0x90u) + 0x11u) << 1); + sim->spr[x].row = U_ENTRY(sim, dir); + sim->spr[x].col = (uint8_t)col; + sim->spr[x].msb = (uint8_t)(col >> 8); + } else { + // $7ECF: fixed column, random row. + sim->spr[x].col = U_ENTRY(sim, dir); + sim->spr[x].msb = U_ENTRY_MSB(sim, dir); + sim->spr[x].row = (uint8_t)((uint8_t)(stSimRng(sim, 0x4Fu) + 0x1Du) << 1); + } + sim->hookScratch.u.state[x] = 1u; + sim->spr[x].enable = 1u; + sim->spr[x].ptr = ST_HK_FALL_CEL; + sim->hookScratch.u.celPhase[x] = 0u; + continue; + } + if (state != 1u) { + sim->hookScratch.u.state[x] = 0u; + continue; + } + // $7F02: drift until the lifetime runs out. + sim->hookScratch.u.life[x]--; + if (sim->hookScratch.u.life[x] == 0u) { + sim->spr[x].enable = 0u; + sim->hookScratch.u.state[x] = 0u; + continue; + } + dir = sim->hookScratch.u.dir[x]; + stSimSpriteAddX(sim, x, U_DX(sim, dir)); + sim->spr[x].row = (uint8_t)(sim->spr[x].row + U_DY(sim, dir)); + sim->hookScratch.u.celPhase[x] = (uint8_t)((sim->hookScratch.u.celPhase[x] + 1u) & 3u); + sim->spr[x].ptr = U_CEL(sim, sim->hookScratch.u.celPhase[x]); + } +} + + +static void hookUPerTick(StSimT *sim) { + // $7DAD: the original opens with a ceiling reflect that only runs + // while a crash is in progress and only above row $11. The wreck + // sequence moves sprite 0 downward to $DA and never back up, so this + // cannot fire on the real machine either; it is kept as shipped. + if (sim->collisionPhase != 0u && sim->spr[0].row < 0x11u) { + sim->velY = (int16_t)(-sim->velY); + sim->posYrow = (uint8_t)(sim->posYrow + 2u); + } + hookUBounce(sim); + hookUOrbs(sim); +} + + // --------------------------------------------------------------------------- // Dispatch // --------------------------------------------------------------------------- +// $62F0 -- a converted level's scratch starts from the bytes the shipped +// blob holds at the addresses the original used, so the first tick sees +// exactly what the C64's did. +// Level E "BEANSTALK" ($7DA0 prelude0, $7DCA per-tick2): the screen +// opens with pad 1 only. While the fare aboard wants UP the stalk grows: +// every $57 ticks a leaf cell sprouts right along row `row` and left +// along row `row`-2, the green tips one cell beyond; the fifth step +// hangs pads N and N+1 at the leaf ends instead, opens them (pad count +// +2) and moves the growth four rows up, until pad 9 is out. $7DA0 runs +// from the respawn prelude, so a lost cab restarts the count from pad 1 +// while the leaves already drawn stay. +static void hookEPrelude0(StSimT *sim) { + StHookET *e = &sim->hookScratch.e; + + sim->specialPad = 1u; + sim->padCount = 1u; + e->seg = 0u; + e->padSlot = 2u; + e->row = ST_HOOK_E_FIRST_ROW; + e->tick = ST_HOOK_E_TICK_START; + if (sim->levelState == 1u) { + e->tick = ST_HOOK_E_TICK_SECOND; + } +} + + +// $7E89 -- one green cell. +static void hookEPut(StSimT *sim, uint8_t col, uint8_t row, uint8_t ch) { + stSimPutChar(sim, col, row, ch); + stSimPutColor(sim, col, row, ST_HOOK_E_COLOR); +} + + +// $7E39 -- the tips, one cell beyond this step's leaves. +static void hookETips(StSimT *sim) { + const StHookET *e = &sim->hookScratch.e; + + hookEPut(sim, (uint8_t)(ST_HOOK_E_STALK_COL + 1u + e->seg), e->row, ST_HOOK_E_CHAR_TIP_R); + hookEPut(sim, (uint8_t)(ST_HOOK_E_STALK_COL - 1u - e->seg), (uint8_t)(e->row - 2u), ST_HOOK_E_CHAR_TIP_L); +} + + +static void hookEPerTick(StSimT *sim) { + StHookET *e = &sim->hookScratch.e; + + if (sim->specialPad == ST_HOOK_E_LAST_PAD || sim->activeSpriteIdx != ST_FARE_DEST_UP) { + return; + } + e->tick++; + if (e->tick != ST_HOOK_E_GROW_TICKS) { + return; + } + e->tick = 0u; + e->seg++; + if (e->seg != ST_HOOK_E_LEAVES) { + hookEPut(sim, (uint8_t)(ST_HOOK_E_STALK_COL + e->seg), e->row, ST_HOOK_E_CHAR_LEAF); + hookEPut(sim, (uint8_t)(ST_HOOK_E_STALK_COL - e->seg), (uint8_t)(e->row - 2u), ST_HOOK_E_CHAR_LEAF); + hookETips(sim); + return; + } + // $7E15: the pads hang from the leaf ends, then the stalk climbs. + hookEPut(sim, ST_HOOK_E_PAD_COL_RIGHT, e->row, (uint8_t)(ST_CHAR_DIGIT_BASE + e->padSlot)); + hookEPut(sim, ST_HOOK_E_PAD_COL_LEFT, (uint8_t)(e->row - 2u), (uint8_t)(ST_CHAR_DIGIT_BASE + e->padSlot + 1u)); + hookETips(sim); + sim->specialPad = (uint8_t)(sim->specialPad + 2u); + sim->padCount = (uint8_t)(sim->padCount + 2u); + e->seg = 0u; + e->row = (uint8_t)(e->row - ST_HOOK_E_ROW_STEP); + e->padSlot = (uint8_t)(e->padSlot + 2u); + stAudioSfx(E_SFX_GROW(sim)); +} + + +// $7DAF (J) / $7DFE (P) -- a hazard that touched the scenery below row +// $38 bursts: cel $82, phase 0, dropped four rows onto what it hit. +// Sprites 7 down to 3, every one that hit. +static void hookFallBgHits(StSimT *sim, uint8_t *state, uint8_t *phase, const uint8_t *sfx) { + uint8_t x; + + for (x = (uint8_t)(ST_HW_SPRITES - 1u); x >= ST_HOOK_FIRST_SPRITE; x--) { + if ((sim->spriteBgColl & kStBit[x]) == 0u || sim->spr[x].row < ST_HOOK_FALL_HIT_ROW || state[x] == 2u) { + continue; + } + state[x] = 2u; + sim->spr[x].ptr = ST_HK_FALL_CEL_BURST; + phase[x] = 0u; + sim->spr[x].row = (uint8_t)(sim->spr[x].row + ST_HOOK_FALL_HIT_DROP); + if (sfx != 0) { + stAudioSfx(sfx); + } + } +} + + +// $7E01 (J) / $7E45 (P) -- the per-sprite machine, sprites 3..7. Idle: +// spawn on rng($3C) < 3 at rng($FF) + the sprite's column base (J +// rerolls one over the middle band), cel $80 on row $17. Falling: one +// row per tick, cel flipping $80/$81 and drifting sideways on the odd +// cel. Bursting: walk the cel run (P only every other count of a +// shared parity) and go idle at its end. +static void hookFallTick(StSimT *sim, uint8_t *state, uint8_t *phase, const StHookFallT *d, uint8_t *parity) { + uint8_t x; + + for (x = ST_HOOK_FIRST_SPRITE; x < ST_HW_SPRITES; x++) { + if (state[x] == 0u) { + uint16_t col; + if (stSimRng(sim, ST_HOOK_FALL_SPAWN_ODDS) >= ST_HOOK_FALL_SPAWN_HIT) { + continue; + } + do { + col = (uint16_t)(stSimRng(sim, 0xFFu) + HKT(sim, d->startTable + x)); + } while (d->avoidMiddle && col >= ST_HOOK_FALL_BAND_LO && col < ST_HOOK_FALL_BAND_HI); + sim->spr[x].col = (uint8_t)col; + sim->spr[x].msb = (uint8_t)(col >> 8); + sim->spr[x].ptr = ST_HK_FALL_CEL; + sim->spr[x].row = ST_HOOK_FALL_TOP_ROW; + sim->spr[x].enable = 1u; + state[x] = 1u; + } else if (state[x] == 1u) { + sim->spr[x].ptr ^= 1u; + if ((sim->spr[x].ptr & 1u) != 0u) { + stSimSpriteAddX(sim, x, HKT(sim, d->dxTable + x)); + } + sim->spr[x].row++; + } else if (state[x] == 2u) { + if (parity != 0) { + (*parity)++; + if ((*parity & 1u) != 0u) { + continue; + } + } + phase[x]++; + if (phase[x] == d->phaseEnd) { + state[x] = 0u; + sim->spr[x].enable = 0u; + } else { + sim->spr[x].ptr = HKT(sim, d->celTable + phase[x]); + } + } else { + state[x] = 0u; // $7E1C: anything else resets + } + } +} + + +// Level J "SHOOTING STARS" ($7D9D prelude0, $7DAF per-tick2): stars fall +// from the top and burst on the scenery (hookFall*). The hook reads the +// stars' own $D01F bits, so the prelude opts them into the collision +// test. +static void hookJPrelude0(StSimT *sim) { + uint8_t x; + + for (x = ST_HOOK_FIRST_SPRITE; x < ST_HW_SPRITES; x++) { + sim->hookScratch.j.state[x] = 0u; + sim->spr[x].color = ST_HK_J_COLOR; + } + sim->bgCollSprites = ST_HOOK_FALL_SPRITES; +} + + +static void hookJPerTick(StSimT *sim) { + StHookJT *j = &sim->hookScratch.j; + + hookFallBgHits(sim, j->state, j->phase, J_SFX_BURST(sim)); + hookFallTick(sim, j->state, j->phase, &kFallJ, 0); +} + + +// Level L "BLACK HOLE" ($7D9F prelude0, $7DBC per-tick2, $7D9B verdict): +// hardware sprite 3 spins through cels $80..$82 at ($AC, $82) and pulls +// the cab in -- each tick the gravity templates become the pull toward +// it, weaker with distance. +static void hookLPrelude0(StSimT *sim) { + StSpriteT *hole = &sim->spr[ST_HOOK_L_HOLE]; + + hole->color = ST_HK_L_COLOR; + hole->msb = 0u; + hole->col = ST_HOOK_L_HOLE_COL; + hole->row = ST_HOOK_L_HOLE_ROW; + hole->ptr = ST_HK_L_CEL_FIRST; + hole->enable = 1u; +} + + +// $7DCB / $7E01 -- base less |dist|/8 ($7E42), signed by the direction. +static uint16_t hookLPull(uint8_t base, uint8_t dist) { + uint8_t mag = dist; + uint16_t pull; + + if ((mag & 0x80u) != 0u) { + mag = (uint8_t)(0u - mag); + } + pull = (uint8_t)(base - (uint8_t)(mag >> 3)); + if ((dist & 0x80u) != 0u) { + pull = (uint16_t)(0u - pull); + } + return pull; +} + + +static void hookLPerTick(StSimT *sim) { + StSpriteT *hole = &sim->spr[ST_HOOK_L_HOLE]; + uint8_t dy; + uint8_t dx; + + hole->ptr++; + if (hole->ptr == ST_HK_L_CEL_END) { + hole->ptr = ST_HK_L_CEL_FIRST; + } + dy = (uint8_t)(ST_HOOK_L_HOLE_ROW - sim->spr[0].row); + sim->gravTemplateY = hookLPull(ST_HOOK_L_PULL_Y, dy); + dx = (uint8_t)(ST_HOOK_L_HOLE_X_HALF - (uint8_t)(stSimSpriteX(sim, 0u) >> 1)); + sim->gravTemplateX = hookLPull(ST_HOOK_L_PULL_X, dx); +} + + +// Level P "BLIZZARD" ($7DA8 prelude0, $7D9C prelude1, $7DBA per-tick2): +// snow falls and melts on the scenery (hookFall*), and now and then the +// wind turns: the windsocks flip and the X gravity changes sign. +static void hookPPrelude0(StSimT *sim) { + uint8_t x; + + for (x = ST_HOOK_FIRST_SPRITE; x < ST_HW_SPRITES; x++) { + sim->hookScratch.p.state[x] = 0u; + sim->spr[x].color = ST_HK_P_COLOR; + } + sim->bgCollSprites = ST_HOOK_FALL_SPRITES; +} + + +static void hookPFlipSock(StSimT *sim, uint16_t cell) { + stSimPutChar(sim, (uint8_t)(cell % ST_SCREEN_COLS), (uint8_t)(cell / ST_SCREEN_COLS), (uint8_t)(sim->screen[cell] ^ ST_HOOK_P_SOCK_FLIP)); +} + + +static void hookPPerTick(StSimT *sim) { + StHookPT *p = &sim->hookScratch.p; + + if (stSimRng(sim, ST_HOOK_P_WIND_ODDS) < ST_HOOK_P_WIND_HIT) { + stAudioSfx(P_SFX_WIND(sim)); + sim->gravTemplateX = (uint16_t)(0u - sim->gravTemplateX); + hookPFlipSock(sim, ST_CELL(7, 6)); // $051E + hookPFlipSock(sim, ST_CELL(9, 21)); // $057D + hookPFlipSock(sim, ST_CELL(15, 31)); // $0677 + } + hookFallBgHits(sim, p->state, p->phase, 0); + hookFallTick(sim, p->state, p->phase, &kFallP, &p->parity); +} + + +// Level S "THE SWITCH" ($7D9D input filter): the stick's direction bits +// come back swapped through the level's table; FIRE passes. +static uint8_t hookSInput(StSimT *sim, uint8_t input) { + return (uint8_t)((input & ST_HOOK_S_FIRE) | S_DIR_MAP(sim, input & ST_HOOK_S_DIRS)); +} + + +void stHookSceneLoad(StSimT *sim) { + uint8_t k; + + switch (sim->hookLevel) { + case ST_LEVEL_G: + sim->hookScratch.g.lastSpot = HKT(sim, 0x7DD1); + sim->hookScratch.g.celPhase = HKT(sim, 0x7DD2); + sim->hookScratch.g.hop = HKT(sim, 0x7DD3); + sim->hookScratch.g.tick = HKT(sim, 0x7E00); + sim->hookScratch.g.sweep = HKT(sim, 0x7F02); + break; + case ST_LEVEL_H: + sim->hookScratch.h.switchId = HKT(sim, 0x7E4C); + sim->hookScratch.h.tick = HKT(sim, 0x7E53); + sim->hookScratch.h.active = HKT(sim, 0x7E54); + sim->hookScratch.h.step = HKT(sim, 0x7E55); + sim->hookScratch.h.tone = HKT(sim, 0x7E56); + for (k = 0u; k < ST_HOOK_H_SEGS; k++) { + sim->hookScratch.h.segFlag[k] = HKT(sim, 0x7E15 + k); + } + break; + case ST_LEVEL_I: + for (k = 0u; k < ST_HW_SPRITES; k++) { + sim->hookScratch.i.state[k] = HKT(sim, 0x7DB8 + k); + sim->hookScratch.i.phase[k] = HKT(sim, 0x7DC0 + k); + sim->hookScratch.i.dx[k] = HKT(sim, 0x7DC8 + k); + sim->hookScratch.i.dy[k] = HKT(sim, 0x7DD0 + k); + } + break; + case ST_LEVEL_K: + sim->hookScratch.k.parity = HKT(sim, 0x7DBD); + break; + case ST_LEVEL_O: + sim->hookScratch.o.tick = HKT(sim, 0x7DBD); + break; + case ST_LEVEL_P: + sim->hookScratch.p.parity = HKT(sim, 0x7F1F); + break; + case ST_LEVEL_Q: + for (k = 0u; k < ST_HW_SPRITES; k++) { + sim->hookScratch.q.phase[k] = HKT(sim, 0x7E83 + k); + } + break; + case ST_LEVEL_T: + sim->hookScratch.t.gateShut = HKT(sim, 0x7D9C); + break; + case ST_LEVEL_U: + sim->hookScratch.u.cooldown = HKT(sim, 0x7F6D); + for (k = 0u; k < ST_HW_SPRITES; k++) { + sim->hookScratch.u.celPhase[k] = HKT(sim, 0x7F4F + k); + sim->hookScratch.u.state[k] = HKT(sim, 0x7F57 + k); + sim->hookScratch.u.dir[k] = HKT(sim, 0x7F5F + k); + sim->hookScratch.u.life[k] = HKT(sim, 0x7F6E + k); + } + break; + case ST_LEVEL_W: + sim->hookScratch.w.colorPhase = HKT(sim, 0x7DAB); + sim->hookScratch.w.started = HKT(sim, 0x7DE5); + for (k = 0u; k < ST_HOOK_W_LASERS; k++) { + sim->hookScratch.w.state[k] = HKT(sim, 0x7DAC + k); + sim->hookScratch.w.row[k] = HKT(sim, 0x7DB5 + k); + } + break; + case ST_LEVEL_X: + sim->hookScratch.x.col = HKT(sim, 0x7D9C); + sim->hookScratch.x.dir = HKT(sim, 0x7D9D); + sim->hookScratch.x.tick = HKT(sim, 0x7D9E); + break; + default: + break; + } +} + + // $7D75 -- 0 means sprite contact (passenger, level sprites) is safe. uint8_t stHookHitVerdict(StSimT *sim) { switch (sim->hookLevel) { + case ST_LEVEL_G: + // $7DF2: only the scenery kills; that also unhides the cab. + if ((sim->spriteBgColl & 1u) != 0u) { + sim->spr[0].enable = 1u; + return 1u; + } + return 0u; case ST_LEVEL_H: return 0u; + case ST_LEVEL_L: + return ST_HOOK_L_VERDICT; default: return 1u; } @@ -884,67 +1617,95 @@ uint8_t stHookHitVerdict(StSimT *sim) { // $7D72 -- the input filter. uint8_t stHookInput(StSimT *sim, uint8_t input) { -#if !defined(__W65816__) switch (sim->hookLevel) { case ST_LEVEL_Q: return hookQInput(sim, input); + case ST_LEVEL_S: + return hookSInput(sim, input); default: break; } -#else - (void)sim; -#endif return input; } -// $7D6C -- before the sprite flush. -void stHookPerTick2(StSimT *sim) { +// $7D6C -- before the sprite flush. Split in two: one switch over all +// sixteen levels left the 65816's single accumulator with more live +// values than greedy register allocation can place, and the build then +// falls back to the basic allocator for this whole file. +ST_NOINLINE static void hookPerTickLow(StSimT *sim) { switch (sim->hookLevel) { + case ST_LEVEL_E: + hookEPerTick(sim); + break; + case ST_LEVEL_G: + hookGPerTick(sim); + break; case ST_LEVEL_H: hookHPerTick(sim); break; + case ST_LEVEL_I: + hookIPerTick(sim); + break; + case ST_LEVEL_J: + hookJPerTick(sim); + break; + case ST_LEVEL_K: + hookKPerTick(sim); + break; + case ST_LEVEL_L: + hookLPerTick(sim); + break; + default: + break; + } +} + + +ST_NOINLINE static void hookPerTickHigh(StSimT *sim) { + switch (sim->hookLevel) { + case ST_LEVEL_O: + hookOPerTick(sim); + break; + case ST_LEVEL_P: + hookPPerTick(sim); + break; + case ST_LEVEL_Q: + hookQPerTick(sim); + break; + case ST_LEVEL_R: + hookRPerTick(sim); + break; case ST_LEVEL_T: hookTPerTick(sim); break; + case ST_LEVEL_U: + hookUPerTick(sim); + break; + case ST_LEVEL_V: + hookVPerTick(sim); + break; case ST_LEVEL_W: hookWPerTick(sim); break; case ST_LEVEL_X: hookXPerTick(sim); break; -#if !defined(__W65816__) - case ST_LEVEL_V: - hookVPerTick(sim); - break; - case ST_LEVEL_Q: - hookQPerTick(sim); - break; - case ST_LEVEL_I: - hookIPerTick(sim); - break; - case ST_LEVEL_G: - hookGPerTick(sim); - break; - case ST_LEVEL_U: - hookUPerTick(sim); - break; - case ST_LEVEL_K: - hookKPerTick(sim); - break; - case ST_LEVEL_O: - hookOPerTick(sim); - break; - case ST_LEVEL_R: - hookRPerTick(sim); - break; -#endif default: break; } } +void stHookPerTick2(StSimT *sim) { + if (sim->hookLevel <= ST_LEVEL_L) { + hookPerTickLow(sim); + return; + } + hookPerTickHigh(sim); +} + + // $7D6F -- after the sprite flush. void stHookPerTick3(StSimT *sim) { (void)sim; @@ -960,21 +1721,32 @@ void stHookPrelude0(StSimT *sim) { case ST_LEVEL_X: hookXPrelude0(sim); break; -#if !defined(__W65816__) + case ST_LEVEL_U: + hookUPrelude0(sim); + break; case ST_LEVEL_Q: hookQPrelude(sim); break; case ST_LEVEL_I: hookIPrelude(sim); break; -#endif case ST_LEVEL_W: - { - uint8_t k; - for (k = 1u; k <= 8u; k++) { - HK(sim, 0x7DAB + k) = 0u; - } - } + memset(sim->hookScratch.w.state, 0, sizeof(sim->hookScratch.w.state)); + break; + case ST_LEVEL_E: + hookEPrelude0(sim); + break; + case ST_LEVEL_G: + hookGPrelude0(sim); + break; + case ST_LEVEL_J: + hookJPrelude0(sim); + break; + case ST_LEVEL_L: + hookLPrelude0(sim); + break; + case ST_LEVEL_P: + hookPPrelude0(sim); break; default: break; @@ -989,13 +1761,17 @@ void stHookPrelude1(StSimT *sim) { hookTPrelude1(sim); break; case ST_LEVEL_W: - HK(sim, 0x7DE5) = 1u; + sim->hookScratch.w.started = 1u; break; -#if !defined(__W65816__) case ST_LEVEL_R: hookRMaze(sim, ST_CHAR_BLANK); break; -#endif + case ST_LEVEL_P: + stAudioSfx(P_SFX_WIND(sim)); + break; + case ST_LEVEL_G: + sim->hookScratch.g.lastSpot = ST_HOOK_G_SPOTS; // $7D9A + break; default: break; } diff --git a/stLevelCels.c b/stLevelCels.c new file mode 100644 index 0000000..bb916e7 --- /dev/null +++ b/stLevelCels.c @@ -0,0 +1,114 @@ +// Space Taxi -- the cels a level's hook program can put on screen. +// +// Hook sprites are built from the level's own VIC bitmaps in whatever +// colour the hook gives them, and compiling one on the 65816 costs about +// half a second -- far too long to do the first time a cel appears mid- +// ride. So every level lists its cels up front, from the same hook +// tables the hook program reads (stHookTables.h): the offline baker turns +// the list into a pre-compiled bank (sprites/levelNN.spc) and the game +// compiles whatever the bank does not supply BEFORE the ride starts. +// +// Only the level file is needed, no simulation, so the baker can link +// this on the host without the rest of the game. + +#include "stCels.h" +#include "stHookTables.h" + +#define HKL(level, addr) ((level)->hookData[(addr) - ST_HOOK_BASE]) + +static void celAdd(StCelDefT *out, uint8_t *count, uint8_t max, uint8_t ptr, uint8_t color, uint8_t multi, uint8_t mc0, uint8_t mc1); + + +// Append one cel unless the list already has it. +static void celAdd(StCelDefT *out, uint8_t *count, uint8_t max, uint8_t ptr, uint8_t color, uint8_t multi, uint8_t mc0, uint8_t mc1) { + uint8_t k; + + for (k = 0u; k < *count; k++) { + if (out[k].ptr == ptr && out[k].color == color && out[k].multi == multi && out[k].mc0 == mc0 && out[k].mc1 == mc1) { + return; + } + } + if (*count == max) { + return; + } + out[*count].ptr = ptr; + out[*count].color = color; + out[*count].multi = multi; + out[*count].mc0 = mc0; + out[*count].mc1 = mc1; + (*count)++; +} + + +uint8_t stLevelCelList(const StLevelT *level, StCelDefT *out, uint8_t max) { + uint8_t n = 0u; + uint8_t k; + uint8_t c; + + switch (level->levelIndex) { + case ST_LEVEL_G: + for (k = 0u; k < ST_HK_G_CEL_PHASES; k++) { + celAdd(out, &n, max, HKL(level, ST_HK_G_CEL + k), ST_HK_G_ORB_COLOR, 0u, 0u, 0u); + } + break; + case ST_LEVEL_H: + celAdd(out, &n, max, ST_HK_H_CEL, ST_HK_H_COLOR_IDLE, 0u, 0u, 0u); + celAdd(out, &n, max, ST_HK_H_CEL, ST_HK_H_COLOR_LIT, 0u, 0u, 0u); + celAdd(out, &n, max, ST_HK_H_CEL, ST_HK_H_COLOR_ACTIVE, 0u, 0u, 0u); + break; + case ST_LEVEL_I: + // Every cel in the idle colour and in each of the three the + // launcher picks at random. + for (c = 0u; c <= ST_HK_I_COLORS; c++) { + uint8_t color = (c == 0u) ? ST_HK_I_COLOR_IDLE : HKL(level, ST_HK_I_COLOR + c - 1u); + for (k = 0u; k < ST_HK_I_RISE_PHASES; k++) { + celAdd(out, &n, max, HKL(level, ST_HK_I_CEL_RISE + k), color, 0u, 0u, 0u); + } + for (k = 0u; k < ST_HK_I_BURST_PHASES; k++) { + celAdd(out, &n, max, HKL(level, ST_HK_I_CEL_BURST + k), color, 0u, 0u, 0u); + } + } + break; + case ST_LEVEL_J: + celAdd(out, &n, max, ST_HK_FALL_CEL, ST_HK_J_COLOR, 0u, 0u, 0u); + celAdd(out, &n, max, (uint8_t)(ST_HK_FALL_CEL + 1u), ST_HK_J_COLOR, 0u, 0u, 0u); + celAdd(out, &n, max, ST_HK_FALL_CEL_BURST, ST_HK_J_COLOR, 0u, 0u, 0u); + for (k = 1u; k < ST_HK_J_BURST_PHASES; k++) { + celAdd(out, &n, max, HKL(level, ST_HK_J_CEL_BURST + k), ST_HK_J_COLOR, 0u, 0u, 0u); + } + break; + case ST_LEVEL_L: + for (k = ST_HK_L_CEL_FIRST; k < ST_HK_L_CEL_END; k++) { + celAdd(out, &n, max, k, ST_HK_L_COLOR, 0u, 0u, 0u); + } + break; + case ST_LEVEL_P: + celAdd(out, &n, max, ST_HK_FALL_CEL, ST_HK_P_COLOR, 0u, 0u, 0u); + celAdd(out, &n, max, (uint8_t)(ST_HK_FALL_CEL + 1u), ST_HK_P_COLOR, 0u, 0u, 0u); + celAdd(out, &n, max, ST_HK_FALL_CEL_BURST, ST_HK_P_COLOR, 0u, 0u, 0u); + for (k = 1u; k < ST_HK_P_BURST_PHASES; k++) { + celAdd(out, &n, max, HKL(level, ST_HK_P_CEL_BURST + k), ST_HK_P_COLOR, 0u, 0u, 0u); + } + break; + case ST_LEVEL_Q: + // The hook's cels show HIRES in colour 6 for the whole recorded + // ride (tools/stsim STSIM_CELS census, 2026-09-22); the + // multicolour variant (the level's own $D025/$D026) stays + // listed for the phases the census did not reach. + for (k = 0u; k < ST_HK_Q_CEL_PHASES; k++) { + celAdd(out, &n, max, HKL(level, ST_HK_Q_CEL + k), ST_HK_Q_COLOR, 0u, 0u, 0u); + } + for (k = 0u; k < ST_HK_Q_CEL_PHASES; k++) { + celAdd(out, &n, max, HKL(level, ST_HK_Q_CEL + k), ST_HK_Q_COLOR, 1u, level->header[5], level->header[6]); + } + break; + case ST_LEVEL_U: + for (k = 0u; k < ST_HK_U_CEL_PHASES; k++) { + celAdd(out, &n, max, HKL(level, ST_HK_U_CEL + k), ST_HK_U_COLOR, 0u, 0u, 0u); + } + break; + default: + break; + } + return n; +} diff --git a/stMarshalIigs.s b/stMarshalIigs.s new file mode 100644 index 0000000..ea291c5 --- /dev/null +++ b/stMarshalIigs.s @@ -0,0 +1,354 @@ +; stMarshalIigs.s - Space Taxi's stSimMarshal for the Apple IIgs. +; +; GAS syntax for the llvm-mos w65816 toolchain (see src/iigs/peislam.s +; for the conventions). The C version copies eight sprites' shadow +; registers into the displayed frame through far pointers, which the +; w65816 backend turns into ~5000 cycles of pointer re-materialisation +; per tick (5% of a ride frame). This is the same copy as straight-line +; [dp],y moves: ~700 cycles. +; +; void stSimMarshalIigs(const StMarshalArgsT *args) +; args (A = low word, X = bank) points at { spr, frame, multi }: +; +0 const StSpriteT *spr (32-bit: low word, bank word) +; +4 StFrameT *frame +; +8 uint8_t multi +; StSpriteT (6 bytes): col, msb, row, enable, ptr, color. +; StFrameT: x[8] (u16, +0), y[8] (+16), ptr[8] (+24), color[8] (+32), +; enableMask (+40), multiMask (+41). +; stSim.c checks these layouts at compile time. +; +; Every store runs with M=16 and writes two bytes: the byte after each +; single-byte field gets a junk high byte that the NEXT store repairs, +; so the blocks run in order x, y, ptr, color and finish with the +; enable/multi word. The last colour store's junk lands on enableMask, +; which the final word overwrites. +; +; Direct-page $E0-$EF are the compiler's own per-function scratch +; (re-set before every use, never live across a call); $C0 is left alone. + .section .text.stSimMarshalIigs,"ax" + .globl stSimMarshalIigs +stSimMarshalIigs: + php + rep #0x30 + .a16 + .i16 + sta 0xe0 ; args low + txa + sta 0xe2 ; args bank + ldy #0 + lda [0xe0],y + sta 0xe4 ; spr low + ldy #2 + lda [0xe0],y + sta 0xe6 ; spr bank + ldy #4 + lda [0xe0],y + sta 0xe8 ; frame low + ldy #6 + lda [0xe0],y + sta 0xea ; frame bank + ldy #8 + lda [0xe0],y + and #0xff + sta 0xec ; multi + stz 0xee ; enable mask accumulator +; sprite 0: x = (msb ? 0x100 : 0) | col; enable bit + ldy #0 + lda [0xe4],y ; col | msb<<8 + sta 0xf0 + and #0xff00 + beq mX0clr + lda 0xf0 + and #0x00ff + ora #0x0100 + bra mX0st +mX0clr: + lda 0xf0 + and #0x00ff +mX0st: + ldy #0 + sta [0xe8],y + ldy #3 + lda [0xe4],y ; enable | ptr<<8 + and #0xff + beq mE0off + lda 0xee + ora #1 + sta 0xee +mE0off: +; sprite 1: x = (msb ? 0x100 : 0) | col; enable bit + ldy #6 + lda [0xe4],y ; col | msb<<8 + sta 0xf0 + and #0xff00 + beq mX1clr + lda 0xf0 + and #0x00ff + ora #0x0100 + bra mX1st +mX1clr: + lda 0xf0 + and #0x00ff +mX1st: + ldy #2 + sta [0xe8],y + ldy #9 + lda [0xe4],y ; enable | ptr<<8 + and #0xff + beq mE1off + lda 0xee + ora #2 + sta 0xee +mE1off: +; sprite 2: x = (msb ? 0x100 : 0) | col; enable bit + ldy #12 + lda [0xe4],y ; col | msb<<8 + sta 0xf0 + and #0xff00 + beq mX2clr + lda 0xf0 + and #0x00ff + ora #0x0100 + bra mX2st +mX2clr: + lda 0xf0 + and #0x00ff +mX2st: + ldy #4 + sta [0xe8],y + ldy #15 + lda [0xe4],y ; enable | ptr<<8 + and #0xff + beq mE2off + lda 0xee + ora #4 + sta 0xee +mE2off: +; sprite 3: x = (msb ? 0x100 : 0) | col; enable bit + ldy #18 + lda [0xe4],y ; col | msb<<8 + sta 0xf0 + and #0xff00 + beq mX3clr + lda 0xf0 + and #0x00ff + ora #0x0100 + bra mX3st +mX3clr: + lda 0xf0 + and #0x00ff +mX3st: + ldy #6 + sta [0xe8],y + ldy #21 + lda [0xe4],y ; enable | ptr<<8 + and #0xff + beq mE3off + lda 0xee + ora #8 + sta 0xee +mE3off: +; sprite 4: x = (msb ? 0x100 : 0) | col; enable bit + ldy #24 + lda [0xe4],y ; col | msb<<8 + sta 0xf0 + and #0xff00 + beq mX4clr + lda 0xf0 + and #0x00ff + ora #0x0100 + bra mX4st +mX4clr: + lda 0xf0 + and #0x00ff +mX4st: + ldy #8 + sta [0xe8],y + ldy #27 + lda [0xe4],y ; enable | ptr<<8 + and #0xff + beq mE4off + lda 0xee + ora #16 + sta 0xee +mE4off: +; sprite 5: x = (msb ? 0x100 : 0) | col; enable bit + ldy #30 + lda [0xe4],y ; col | msb<<8 + sta 0xf0 + and #0xff00 + beq mX5clr + lda 0xf0 + and #0x00ff + ora #0x0100 + bra mX5st +mX5clr: + lda 0xf0 + and #0x00ff +mX5st: + ldy #10 + sta [0xe8],y + ldy #33 + lda [0xe4],y ; enable | ptr<<8 + and #0xff + beq mE5off + lda 0xee + ora #32 + sta 0xee +mE5off: +; sprite 6: x = (msb ? 0x100 : 0) | col; enable bit + ldy #36 + lda [0xe4],y ; col | msb<<8 + sta 0xf0 + and #0xff00 + beq mX6clr + lda 0xf0 + and #0x00ff + ora #0x0100 + bra mX6st +mX6clr: + lda 0xf0 + and #0x00ff +mX6st: + ldy #12 + sta [0xe8],y + ldy #39 + lda [0xe4],y ; enable | ptr<<8 + and #0xff + beq mE6off + lda 0xee + ora #64 + sta 0xee +mE6off: +; sprite 7: x = (msb ? 0x100 : 0) | col; enable bit + ldy #42 + lda [0xe4],y ; col | msb<<8 + sta 0xf0 + and #0xff00 + beq mX7clr + lda 0xf0 + and #0x00ff + ora #0x0100 + bra mX7st +mX7clr: + lda 0xf0 + and #0x00ff +mX7st: + ldy #14 + sta [0xe8],y + ldy #45 + lda [0xe4],y ; enable | ptr<<8 + and #0xff + beq mE7off + lda 0xee + ora #128 + sta 0xee +mE7off: +; y[] block (16-bit stores; the junk high byte is repaired by the next store) + ldy #2 + lda [0xe4],y + ldy #16 + sta [0xe8],y + ldy #8 + lda [0xe4],y + ldy #17 + sta [0xe8],y + ldy #14 + lda [0xe4],y + ldy #18 + sta [0xe8],y + ldy #20 + lda [0xe4],y + ldy #19 + sta [0xe8],y + ldy #26 + lda [0xe4],y + ldy #20 + sta [0xe8],y + ldy #32 + lda [0xe4],y + ldy #21 + sta [0xe8],y + ldy #38 + lda [0xe4],y + ldy #22 + sta [0xe8],y + ldy #44 + lda [0xe4],y + ldy #23 + sta [0xe8],y +; ptr[] block (16-bit stores; the junk high byte is repaired by the next store) + ldy #4 + lda [0xe4],y + ldy #24 + sta [0xe8],y + ldy #10 + lda [0xe4],y + ldy #25 + sta [0xe8],y + ldy #16 + lda [0xe4],y + ldy #26 + sta [0xe8],y + ldy #22 + lda [0xe4],y + ldy #27 + sta [0xe8],y + ldy #28 + lda [0xe4],y + ldy #28 + sta [0xe8],y + ldy #34 + lda [0xe4],y + ldy #29 + sta [0xe8],y + ldy #40 + lda [0xe4],y + ldy #30 + sta [0xe8],y + ldy #46 + lda [0xe4],y + ldy #31 + sta [0xe8],y +; color[] block (16-bit stores; the junk high byte is repaired by the next store) + ldy #5 + lda [0xe4],y + ldy #32 + sta [0xe8],y + ldy #11 + lda [0xe4],y + ldy #33 + sta [0xe8],y + ldy #17 + lda [0xe4],y + ldy #34 + sta [0xe8],y + ldy #23 + lda [0xe4],y + ldy #35 + sta [0xe8],y + ldy #29 + lda [0xe4],y + ldy #36 + sta [0xe8],y + ldy #35 + lda [0xe4],y + ldy #37 + sta [0xe8],y + ldy #41 + lda [0xe4],y + ldy #38 + sta [0xe8],y + ldy #47 + lda [0xe4],y + ldy #39 + sta [0xe8],y +; enableMask | multiMask<<8 + lda 0xec + xba + and #0xff00 + ora 0xee + ldy #40 + sta [0xe8],y + plp + rtl diff --git a/stRender.c b/stRender.c index 65fce0e..9391631 100644 --- a/stRender.c +++ b/stRender.c @@ -40,11 +40,9 @@ // cab, exhaust and passenger sets plus the intro star) or a cel change // mid-play rebuilds and recompiles a sprite. Glyphs are direct-mapped // by character, so the count is a power of two. -#if defined(__W65816__) - #define ST_SPRITE_CACHE 48u -#else - #define ST_SPRITE_CACHE 72u -#endif +// Room for the baked cab/passenger/exhaust set (37) plus the largest +// level's hook cels (I: 30) at once. +#define ST_SPRITE_CACHE 80u #define ST_SPRITE_BACKUP_BYTES JOEY_SPRITE_BACKUP_BYTES(ST_SPRITE_TILES, ST_SPRITE_TILES) #define ST_SPRITE_PX (8 * ST_SPRITE_TILES) // Palette slots the title logo animation owns. The title screen draws @@ -71,7 +69,9 @@ typedef struct { bool used; bool pinned; // prewarmed moving cel: never evicted (its // compiled code is costly to re-emit on the 65816) + bool compiled; // jlSpriteCompile succeeded for this cel uint32_t lastUse; // for least-recently-used eviction + uint32_t retryAt; // cacheStamp at which a failed compile may retry } StSpriteCacheT; @@ -91,6 +91,9 @@ typedef struct { // Title logo (stRenderTitleLogo): all four flip-book frames in one // tile, animated by rewriting logoSlotMask[]'s palette slots. bool logoMode; + bool starMode; // level intro: cycling the starfield + uint8_t starPhase; // which ring the bright band is on + uint8_t starTick; // frames until the band steps outward bool logoTileOk; // the live glyph is one of the frames uint8_t logoSlotCount; uint8_t logoSlotMask[ST_LOGO_PAL_SLOTS]; // frames each slot is lit in @@ -100,8 +103,6 @@ typedef struct { jlTileT logoTile; // Cell range (inclusive) each character has been painted in since // the last full repaint: where a changed glyph's cells can be. - uint16_t glyphFirst[ST_CHARSET_CHARS]; - uint16_t glyphLast[ST_CHARSET_CHARS]; // Column band of the cells about to be repainted, per character row // (min > max = the row is clean), the same shape as the library's // own per-row dirty bands. @@ -111,7 +112,10 @@ typedef struct { StSpriteCacheT cache[ST_SPRITE_CACHE]; StSpriteCacheT *lastHit[ST_HW_SPRITES]; // the entry each hardware sprite used last jlSpriteBackupT backup[ST_HW_SPRITES]; - uint8_t backupMem[ST_HW_SPRITES][ST_SPRITE_BACKUP_BYTES] __attribute__((aligned(2))); + // ST_HW_SPRITES backups of ST_SPRITE_BACKUP_BYTES each, from jlAlloc: + // 4.5 KB that as BSS would not fit the IIgs entry bank (text, rodata, + // BSS and the C heap share its 64 KB). + uint8_t *backupMem; StDrawnT slot[ST_HW_SPRITES]; uint8_t lastPresentFrame; } StRenderStateT; @@ -132,9 +136,26 @@ static const uint16_t kC64Palette[16] = { // four of these are used; the rest are headroom if the frames change. static const uint8_t kLogoPalSlot[ST_LOGO_PAL_SLOTS] = { 4u, 8u, 9u, 10u, 13u, 14u, 15u }; +// Level-intro warp: how a star ring looks at each distance BEHIND the +// bright band, as C64 colour indices (white, light grey, grey, dark +// grey, then black). A ring this far behind the band or further is +// unlit, so a lit head with a short fading tail sweeps outward and the +// rest of the field stays dark. +static const uint8_t kStarRamp[] = { 1u, 15u, 12u, 11u }; +#define ST_STAR_RAMP_LEN (sizeof(kStarRamp) / sizeof(kStarRamp[0])) +// Frames per ring step. The intro runs at the game's 30 Hz tick, so a +// step every other frame sweeps the eight rings in about half a second. +#define ST_STAR_STEP_FRAMES 2u + static void buildSpriteCel(const uint8_t *bm, uint8_t multi, uint8_t color, uint8_t mc0, uint8_t mc1); static jlSpriteT *cachedSprite(StSimT *sim, uint8_t idx); +static void cacheCompile(StSpriteCacheT *e); +static StSpriteCacheT *cacheFreeSlot(void); +static void levelCelsEvict(void); +static const uint8_t *levelSpriteBitmap(const StLevelT *level, uint8_t ptr); +static void loadingBar(jlSurfaceT *stage, uint8_t done, uint8_t total); +static uint8_t cellCol(uint16_t cell, uint8_t row); static uint8_t cellRow(uint16_t cell); static void collectGlyphs(StSimT *sim); static void dirtyBands(const StSimT *sim); @@ -145,6 +166,7 @@ static bool loadPrecompiledCels(void); static bool logoBuildTile(const StSimT *sim); static uint8_t logoFrameIndex(const StSimT *sim); static void logoPaletteApply(const StSimT *sim, uint8_t frame); +static void starPaletteApply(void); static void paintCells(jlSurfaceT *stage, StSimT *sim); static void pasteCell(jlSurfaceT *stage, const StSimT *sim, uint16_t cell, uint8_t bx, uint8_t by); static bool spriteOnScreen(int16_t px, int16_t py); @@ -171,12 +193,27 @@ static void buildSpriteCel(const uint8_t *bm, uint8_t multi, uint8_t color, uint } +// Compile a cached cel if it is not already, backing off after a failure +// (arena full) so a cel that cannot fit is not re-staged every frame -- +// jlSpriteCompile's failure path runs the whole emitter twice. +#define ST_COMPILE_RETRY_STAMPS 4096u // ~8 frames' worth of cachedSprite calls per frame -> ~25 s +static void cacheCompile(StSpriteCacheT *e) { + if (e->compiled || gRender.cacheStamp < e->retryAt) { + return; + } + e->compiled = jlSpriteCompile(e->sprite); + if (!e->compiled) { + e->retryAt = gRender.cacheStamp + ST_COMPILE_RETRY_STAMPS; + } +} + + // The JoeyLib sprite for hardware sprite `idx` of the current frame, // built on first use from its VIC bitmap and colours. static jlSpriteT *cachedSprite(StSimT *sim, uint8_t idx) { uint8_t ptr = sim->frame.ptr[idx]; uint8_t color = drawColor(sim, idx); - uint8_t multi = (uint8_t)((sim->frame.multiMask >> idx) & 1u); + uint8_t multi = (uint8_t)((sim->frame.multiMask & kStBit[idx]) != 0u ? 1u : 0u); uint8_t mc0 = multi ? sim->spriteMc0 : 0u; uint8_t mc1 = multi ? sim->spriteMc1 : 0u; uint8_t level = (ptr < ST_SPRITE_PTR_FIRST && sim->level != 0) ? sim->level->levelIndex : 0xFFu; @@ -213,20 +250,36 @@ static jlSpriteT *cachedSprite(StSimT *sim, uint8_t idx) { // so fail into the interpreter path. return 0; } + // No bitmap for this pointer (hooks enable a sprite a tick before + // they aim it, leaving it at block 0): nothing to draw, and no cel + // to build and evict for. + if (stSimSpriteBitmap(sim, ptr) == 0) { + return 0; + } if (slot->used) { jlSpriteDestroy(slot->sprite); - slot->sprite = 0; - slot->used = false; + slot->sprite = 0; + slot->used = false; + slot->compiled = false; } buildSpriteCel(stSimSpriteBitmap(sim, ptr), multi, color, mc0, mc1); slot->sprite = jlSpriteCreateFromSurface(gRender.scratch, 0, 0, ST_SPRITE_TILES, ST_SPRITE_TILES); if (slot->sprite == 0) { return 0; } - // Only the cab, exhaust and passenger move every tick; the codegen - // arena is theirs. Everything else stays interpreted (drawn seldom). - if (idx <= 2u) { - (void)jlSpriteCompile(slot->sprite); + // Nothing compiles mid-ride: a compile is ~0.5 s on the 65816, a + // visible hitch. The cab, exhaust and passenger cels come baked + // (stRenderPrewarm), a level's hook cels come baked or are compiled + // at level entry (stRenderLevelCels), and a cel that still gets + // built here draws interpreted -- and is logged, because it means + // the level's cel list (stLevelCels.c) missed one. + slot->compiled = false; + slot->retryAt = 0u; + if (idx <= 2u && gPrewarming) { + cacheCompile(slot); + } + if (!gPrewarming) { + ST_LOG("spacetaxi: ! cel $%02X colour %u (level %u) built mid-ride -- add it to stLevelCelList", ptr, color, level); } slot->lastUse = gRender.cacheStamp; slot->ptr = ptr; @@ -257,6 +310,19 @@ static const uint8_t kCellRow[125] = { }; +// Column of a cell whose row is known: cell - row * 40 through a table, +// because the two-shift form of the multiply came back as a __mulhi3 +// library call on the 65816. +static uint8_t cellCol(uint16_t cell, uint8_t row) { + static const uint16_t kRowBase[ST_SCREEN_ROWS] = { + 0, 40, 80, 120, 160, 200, 240, 280, 320, 360, 400, 440, 480, + 520, 560, 600, 640, 680, 720, 760, 800, 840, 880, 920, 960 + }; + return (uint8_t)(cell - kRowBase[row]); +} + + + static uint8_t cellRow(uint16_t cell) { return kCellRow[cell >> 3]; } @@ -271,7 +337,7 @@ static uint8_t cellRow(uint16_t cell) { static void collectGlyphs(StSimT *sim) { uint8_t k; uint16_t cell; - uint16_t last; + uint16_t next; if (sim->charDirtyAll) { sim->charDirtyAll = false; @@ -283,18 +349,33 @@ static void collectGlyphs(StSimT *sim) { if (sim->dirtyAll) { continue; } - last = gRender.glyphLast[ch]; - for (cell = gRender.glyphFirst[ch]; cell <= last; cell++) { - if (sim->screen[cell] != ch || sim->cellDirty[cell] != 0u) { - continue; - } - sim->cellDirty[cell] = 1u; - if (sim->dirtyCount < (uint16_t)(sizeof(sim->dirtyList) / sizeof(sim->dirtyList[0]))) { - sim->dirtyList[sim->dirtyCount++] = cell; - } else { - sim->dirtyAll = true; - break; + // Exactly the cells showing `ch`, straight off the reverse index + // the sim maintains (StSimT in stSim.h). No scan, no screen[] + // test: a cell is in this list if and only if it shows `ch`. + // + // This replaced a walk of a per-glyph [first, last] span. That + // span only ever widened -- pasteCell pushed it out and nothing + // pulled it back -- so a glyph that once appeared in transient + // text kept a screen-wide span for the rest of the level and was + // rescanned on every animation frame, at 24-bit far-pointer cost + // per cell. It measured 22% of the whole program on level H. + // + // `next` is read before the body because the overflow arm below + // stops the walk, and because keeping the read up here makes the + // loop independent of anything the body does to the cell. + cell = sim->glyphHead[ch]; + while (cell != ST_GLYPH_CELL_NONE) { + next = sim->cellNext[cell]; + if (sim->cellDirty[cell] == 0u) { + sim->cellDirty[cell] = 1u; + if (sim->dirtyCount < (uint16_t)(sizeof(sim->dirtyList) / sizeof(sim->dirtyList[0]))) { + sim->dirtyList[sim->dirtyCount++] = cell; + } else { + sim->dirtyAll = true; + break; + } } + cell = next; } } sim->charDirtyCount = 0u; @@ -331,7 +412,7 @@ static void dirtyBands(const StSimT *sim) { for (k = 0u; k < sim->dirtyCount; k++) { uint16_t cell = sim->dirtyList[k]; uint8_t r = cellRow(cell); - uint8_t col = (uint8_t)(cell - ((uint16_t)r << 5) - ((uint16_t)r << 3)); + uint8_t col = cellCol(cell, r); if (col < gRender.dirtyColMin[r]) { gRender.dirtyColMin[r] = col; } @@ -422,7 +503,7 @@ static bool logoBuildTile(const StSimT *sim) { uint8_t f; uint8_t slot; for (f = 0u; f < ST_LOGO_FRAMES; f++) { - if (((sim->charset[ST_LOGO_FRAME_FIRST + f][row] >> (7u - col)) & 1u) != 0u) { + if (((stSimGlyphRow(sim, (uint8_t)(ST_LOGO_FRAME_FIRST + f), row) >> (7u - col)) & 1u) != 0u) { mask |= (uint8_t)(1u << f); } } @@ -454,7 +535,8 @@ static uint8_t logoFrameIndex(const StSimT *sim) { uint8_t f; for (f = 0u; f < ST_LOGO_FRAMES; f++) { - if (memcmp(sim->charset[ST_LOGO_CHAR], sim->charset[ST_LOGO_FRAME_FIRST + f], 8u) == 0) { + if (memcmp(stSimGlyph(sim, ST_LOGO_CHAR), + stSimGlyph(sim, (uint8_t)(ST_LOGO_FRAME_FIRST + f)), 8u) == 0) { return f; } } @@ -494,8 +576,6 @@ static void paintCells(jlSurfaceT *stage, StSimT *sim) { uint8_t by; if (sim->dirtyAll) { - memset(gRender.glyphFirst, 0xFF, sizeof(gRender.glyphFirst)); - memset(gRender.glyphLast, 0, sizeof(gRender.glyphLast)); cell = 0u; for (by = 0u; by < ST_SCREEN_ROWS; by++) { for (bx = 0u; bx < ST_SCREEN_COLS; bx++) { @@ -511,7 +591,7 @@ static void paintCells(jlSurfaceT *stage, StSimT *sim) { for (k = 0u; k < sim->dirtyCount; k++) { cell = sim->dirtyList[k]; by = cellRow(cell); - bx = (uint8_t)(cell - ((uint16_t)by << 5) - ((uint16_t)by << 3)); + bx = cellCol(cell, by); pasteCell(stage, sim, cell, bx, by); sim->cellDirty[cell] = 0u; } @@ -525,12 +605,6 @@ static void paintCells(jlSurfaceT *stage, StSimT *sim) { static void pasteCell(jlSurfaceT *stage, const StSimT *sim, uint16_t cell, uint8_t bx, uint8_t by) { uint8_t chr = sim->screen[cell]; - if (cell < gRender.glyphFirst[chr]) { - gRender.glyphFirst[chr] = cell; - } - if (cell > gRender.glyphLast[chr]) { - gRender.glyphLast[chr] = cell; - } // On the title the logo's cells carry all four flip-book frames at // once; the palette decides which one shows, so they are pasted as // they are and never touched again. @@ -538,7 +612,30 @@ static void pasteCell(jlSurfaceT *stage, const StSimT *sim, uint16_t cell, uint8 jlTilePaste(stage, bx, by, &gRender.logoTile); return; } - jlTilePasteGlyph(stage, bx, by, sim->charset[chr], sim->color[cell], sim->bgColor); + jlTilePasteGlyph(stage, bx, by, stSimGlyph(sim, chr), sim->color[cell], sim->bgColor); +} + + +// Light the starfield's ring colours for the current phase: the ring +// the band is on takes kStarRamp[0], the ones just inside it the rest +// of the ramp, and everything else the background. One palette write +// per step and not a single pixel moves -- that is the whole point of +// ST_STAR_RING_FIRST (stSim.h). +static void starPaletteApply(void) { + uint16_t pal[SURFACE_COLORS_PER_PALETTE]; + uint16_t color; + uint8_t behind; + uint8_t ring; + + jlPaletteGet(gRender.stage, 0u, pal); + for (ring = 0u; ring < ST_STAR_RING_COUNT; ring++) { + // How far this ring sits behind the band, wrapping so the band + // re-enters at the centre as it leaves the edge. + behind = (uint8_t)((gRender.starPhase - ring) & (ST_STAR_RING_COUNT - 1u)); + color = (behind < ST_STAR_RAMP_LEN) ? kC64Palette[kStarRamp[behind]] : kC64Palette[0]; + pal[ST_STAR_RING_FIRST + ring] = color; + } + jlPaletteSet(gRender.stage, 0u, pal); } @@ -598,15 +695,26 @@ void stRenderFrame(jlSurfaceT *stage, StSimT *sim) { } } } + if (gRender.starMode) { + // The warp is the only thing animating here, and it animates in + // the palette: no cells change, no sprite moves for it, so the + // present that ends this frame copies no pixels at all. + gRender.starTick++; + if (gRender.starTick >= ST_STAR_STEP_FRAMES) { + gRender.starTick = 0u; + gRender.starPhase = (uint8_t)((gRender.starPhase + 1u) & (ST_STAR_RING_COUNT - 1u)); + starPaletteApply(); + } + } collectGlyphs(sim); dirtyBands(sim); for (k = 0u; k < ST_HW_SPRITES; k++) { uint8_t idx = (uint8_t)(ST_HW_SPRITES - 1u - k); StDrawnT *d = &gRender.slot[k]; - uint8_t multi = (uint8_t)((sim->frame.multiMask >> idx) & 1u); + uint8_t multi = (uint8_t)((sim->frame.multiMask & kStBit[idx]) != 0u ? 1u : 0u); int16_t px = (int16_t)((int16_t)sim->frame.x[idx] - ST_SPRITE_X_ORIGIN); int16_t py = (int16_t)((int16_t)sim->frame.y[idx] - ST_SPRITE_Y_ORIGIN); - bool want = (sim->frame.enableMask & (uint8_t)(1u << idx)) != 0u && spriteOnScreen(px, py); + bool want = (sim->frame.enableMask & kStBit[idx]) != 0u && spriteOnScreen(px, py); bool same = (want == d->drawn); if (same && want) { same = (d->x == sim->frame.x[idx] && d->y == sim->frame.y[idx] && d->ptr == sim->frame.ptr[idx] && d->color == drawColor(sim, idx) && d->multi == multi); @@ -633,7 +741,7 @@ void stRenderFrame(jlSurfaceT *stage, StSimT *sim) { jlSpriteT *sp; int16_t px; int16_t py; - if ((sim->frame.enableMask & (uint8_t)(1u << idx)) == 0u) { + if ((sim->frame.enableMask & kStBit[idx]) == 0u) { continue; } px = (int16_t)((int16_t)sim->frame.x[idx] - ST_SPRITE_X_ORIGIN); @@ -651,7 +759,7 @@ void stRenderFrame(jlSurfaceT *stage, StSimT *sim) { d->y = sim->frame.y[idx]; d->ptr = sim->frame.ptr[idx]; d->color = drawColor(sim, idx); - d->multi = (uint8_t)((sim->frame.multiMask >> idx) & 1u); + d->multi = (uint8_t)((sim->frame.multiMask & kStBit[idx]) != 0u ? 1u : 0u); } { // Sync to the retrace unless the frame already spans more than @@ -667,13 +775,20 @@ void stRenderFrame(jlSurfaceT *stage, StSimT *sim) { void stRenderInit(jlSurfaceT *stage) { - uint8_t k; + uint8_t *backupMem = gRender.backupMem; + uint8_t k; memset(&gRender, 0, sizeof(gRender)); - memset(gRender.glyphFirst, 0xFF, sizeof(gRender.glyphFirst)); - gRender.stage = stage; + if (backupMem == 0) { + backupMem = (uint8_t *)jlAlloc((uint32_t)ST_HW_SPRITES * ST_SPRITE_BACKUP_BYTES); + if (backupMem == 0) { + ST_LOG("stRenderInit: no memory for sprite backups"); + } + } + gRender.backupMem = backupMem; + gRender.stage = stage; for (k = 0u; k < ST_HW_SPRITES; k++) { - gRender.backup[k].bytes = gRender.backupMem[k]; + gRender.backup[k].bytes = (backupMem == 0) ? 0 : backupMem + (uint16_t)k * ST_SPRITE_BACKUP_BYTES; } jlPaletteSet(stage, 0u, kC64Palette); jlScbSetRange(stage, 0u, (uint16_t)(SURFACE_HEIGHT - 1u), 0u); @@ -695,10 +810,145 @@ void stRenderInit(jlSurfaceT *stage) { // cache so no cel is JIT-compiled at boot (each 65816 compile costs // ~0.4 s). Every kStCels entry becomes a pinned cache slot keyed exactly // as the play lookups: system ptr (level 0xFF), the level-wide colours. +// The first unused cache slot, or NULL when the cache is full. +static StSpriteCacheT *cacheFreeSlot(void) { + uint8_t k; + + for (k = 0u; k < ST_SPRITE_CACHE; k++) { + if (!gRender.cache[k].used) { + return &gRender.cache[k]; + } + } + return 0; +} + + +// Drop every cel that belongs to a level (the baked set is level 0xFF and +// stays), freeing its arena code, and forget any hardware sprite's last +// hit on it. +static void levelCelsEvict(void) { + uint8_t k; + + for (k = 0u; k < ST_SPRITE_CACHE; k++) { + StSpriteCacheT *e = &gRender.cache[k]; + uint8_t h; + if (!e->used || e->level == 0xFFu) { + continue; + } + for (h = 0u; h < ST_HW_SPRITES; h++) { + if (gRender.lastHit[h] == e) { + gRender.lastHit[h] = 0; + } + } + jlSpriteDestroy(e->sprite); + e->sprite = 0; + e->used = false; + e->pinned = false; + e->compiled = false; + } +} + + +// A level's own sprite block, or NULL when the level does not ship it. +static const uint8_t *levelSpriteBitmap(const StLevelT *level, uint8_t ptr) { + uint8_t k; + + for (k = 0u; k < level->spriteCount; k++) { + if (level->sprites[k].ptr == ptr) { + return level->sprites[k].bitmap; + } + } + return 0; +} + + +static void loadingBar(jlSurfaceT *stage, uint8_t done, uint8_t total) { + jlFillRect(stage, 60, 110, (uint16_t)((uint16_t)done * 200u / total), 6u, 1u); + jlStagePresent(); +} + + +// Ready a level's hook cels BEFORE the ride: the previous level's cels +// go, then the level's list (stLevelCelList) is filled from its baked +// bank (sprites/levelNN.spc) or, when there is none, built and compiled +// here behind the LOADING bar. Either way nothing compiles once the +// level is running. Keyed exactly as cachedSprite keys them, so every +// lookup during the ride is a hit. +void stRenderLevelCels(jlSurfaceT *stage, const StLevelT *level) { + static StCelDefT list[ST_LEVEL_CELS_MAX]; + jlSpriteT *cels[ST_LEVEL_CELS_MAX]; + char path[24]; + uint8_t n; + uint8_t got; + uint8_t k; + + levelCelsEvict(); + if (level == 0) { + return; + } + n = stLevelCelList(level, list, ST_LEVEL_CELS_MAX); + if (n == 0u) { + return; + } + memcpy(path, "sprites/level", 13u); + path[13] = (char)('0' + (level->levelIndex + 1u) / 10u); + path[14] = (char)('0' + (level->levelIndex + 1u) % 10u); + memcpy(path + 15, ".spc", 5u); + got = (uint8_t)jlSpriteBankLoadPrecompiled(path, cels, n, 0); + if (got == n) { + ST_LOG("spacetaxi: level %u cels from %s (%u)", level->levelIndex, path, n); + } else { + for (k = 0u; k < got; k++) { + jlSpriteDestroy(cels[k]); + } + ST_LOG("spacetaxi: level %u: no usable %s -- compiling %u cels at entry", level->levelIndex, path, n); + stRenderLoading(stage); + } + gPrewarming = true; + for (k = 0u; k < n; k++) { + StSpriteCacheT *slot = cacheFreeSlot(); + if (slot == 0) { + ST_LOG("spacetaxi: ! sprite cache full at level cel %u", k); + break; + } + if (got == n) { + slot->sprite = cels[k]; + slot->compiled = true; + } else { + const uint8_t *bm = levelSpriteBitmap(level, list[k].ptr); + if (bm == 0) { + continue; + } + buildSpriteCel(bm, list[k].multi, list[k].color, list[k].mc0, list[k].mc1); + slot->sprite = jlSpriteCreateFromSurface(gRender.scratch, 0, 0, ST_SPRITE_TILES, ST_SPRITE_TILES); + if (slot->sprite == 0) { + continue; + } + slot->compiled = false; + slot->retryAt = 0u; + cacheCompile(slot); + loadingBar(stage, (uint8_t)(k + 1u), n); + } + slot->ptr = list[k].ptr; + slot->color = list[k].color; + slot->multi = list[k].multi; + slot->mc0 = list[k].mc0; + slot->mc1 = list[k].mc1; + slot->level = level->levelIndex; + slot->used = true; + slot->pinned = true; + slot->lastUse = gRender.cacheStamp; + } + gPrewarming = false; + if (got != n) { + jlFillRect(stage, 60, 110, 200u, 6u, 0u); + } +} + + // Returns false if the .spc is missing or built for another target/shift -// count (other ports, or a stale bake), so the caller falls back to the -// runtime JIT prewarm below. Its cross-platform contract makes this a -// no-op everywhere but the IIgs, where the bake is wired. +// count (a stale bake), so the caller falls back to the runtime JIT +// prewarm below. Every port stages its own bake (make/.mk). static bool loadPrecompiledCels(void) { jlSpriteT *cels[64]; uint16_t n; @@ -761,8 +1011,10 @@ void stRenderPrewarm(jlSurfaceT *stage, StSimT *sim) { // Precompiled bank: instant, no per-cel JIT. Falls through to the // runtime build below on any port without a matching .spc. if (loadPrecompiledCels()) { + ST_LOG("spacetaxi: cels from sprites/staxicels.spc"); return; } + ST_LOG("spacetaxi: ! no usable sprites/staxicels.spc -- JIT prewarm"); saved = sim->frame; gPrewarming = true; sim->frame.multiMask = 0x07u; @@ -773,24 +1025,21 @@ void stRenderPrewarm(jlSurfaceT *stage, StSimT *sim) { sim->frame.color[0] = 0x06u; (void)cachedSprite(sim, 0u); done++; - jlFillRect(stage, 60, 110, (uint16_t)((uint16_t)done * 200u / total), 6u, 1u); - jlStagePresent(); + loadingBar(stage, done, total); } for (k = 0u; k < sizeof(kPassPtrs); k++) { sim->frame.ptr[1] = kPassPtrs[k]; sim->frame.color[1] = 0x06u; (void)cachedSprite(sim, 1u); done++; - jlFillRect(stage, 60, 110, (uint16_t)((uint16_t)done * 200u / total), 6u, 1u); - jlStagePresent(); + loadingBar(stage, done, total); } for (k = 0u; k < sizeof(kFlamePtrs); k++) { sim->frame.ptr[2] = kFlamePtrs[k]; sim->frame.color[2] = 0x07u; (void)cachedSprite(sim, 2u); done++; - jlFillRect(stage, 60, 110, (uint16_t)((uint16_t)done * 200u / total), 6u, 1u); - jlStagePresent(); + loadingBar(stage, done, total); } // The intro star is a hires sprite in colour 7. Build it as a moving // (idx <= 2) cel so it compiles: the level-name intro draws seven of @@ -809,16 +1058,17 @@ void stRenderPrewarm(jlSurfaceT *stage, StSimT *sim) { void stRenderSceneChanged(StSimT *sim) { uint8_t k; - if (gRender.logoMode) { - // The logo's reserved slots go back to being C64 colours. + if (gRender.logoMode || gRender.starMode) { + // The borrowed slots go back to being C64 colours. gRender.logoMode = false; gRender.logoTileOk = false; + gRender.starMode = false; jlPaletteSet(gRender.stage, 0u, kC64Palette); } for (k = 0u; k < ST_HW_SPRITES; k++) { gRender.slot[k].drawn = false; } - stSimDirtyAll(sim); + stSimDirtyCells(sim); sim->charDirtyAll = true; sim->charDirtyCount = 0u; } @@ -838,7 +1088,19 @@ void stRenderTitleLogo(StSimT *sim) { gRender.logoMode = true; gRender.logoTileOk = (frame < ST_LOGO_FRAMES); logoPaletteApply(sim, gRender.logoTileOk ? frame : 0u); - stSimDirtyAll(sim); + stSimDirtyCells(sim); +} + + +// Turn on the level intro's palette-cycled starfield. Call it after +// stRenderSceneChanged (which turns it back off) on entry to the level +// intro. The cells were painted by stIntroEnter; from here the warp is +// one palette write every ST_STAR_STEP_FRAMES frames. +void stRenderIntroStars(void) { + gRender.starMode = true; + gRender.starPhase = 0u; + gRender.starTick = 0u; + starPaletteApply(); } @@ -848,4 +1110,6 @@ void stRenderShutdown(void) { jlSurfaceDestroy(gRender.scratch); gRender.scratch = 0; } + jlFree(gRender.backupMem); + gRender.backupMem = 0; } diff --git a/stSim.c b/stSim.c index af93ba1..3a6069a 100644 --- a/stSim.c +++ b/stSim.c @@ -11,24 +11,34 @@ #include "stSim.h" +// Jingle selection ($6935 / $70D4). Song numbers index kStSongs. +#define ST_JINGLE_SONG_BONUS 4u // level-25 start and the bonus cab +#define ST_JINGLE_LEVEL_25 0x19u // $7215 compared against #$19 + // Screen-code strings the engine writes (all ASCII-compatible glyphs). -static const uint8_t kTextBlank[] = { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0 }; // $6C38 -static const uint8_t kHudTemplate[7] = { 0x66, 0x66, 0x66, 0x74, 0x77, 0x74, 0x74 }; // $43B1 +// $6C38 -- the 11-character blank that clears the message row. +static const uint8_t kTextBlank[] = " "; +// $43B1 -- the fare meter's blank template: three leading blanks, then +// "0.00" in the inverse-video digit glyphs. +static const uint8_t kHudTemplate[ST_NUMBER_CHARS] = { 0x66, 0x66, 0x66, 0x74, 0x77, 0x74, 0x74 }; // Fuel-pump chirp frequencies while refuelling ($6DFA, indexed by the // mod-8 pump tick 1..4). static const uint8_t kPumpFreq[5] = { 0x04, 0x05, 0x07, 0x0A, 0x10 }; +const uint8_t kStBit[ST_HW_SPRITES] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }; static void applyVelocityX(StSimT *sim); static void applyVelocityY(StSimT *sim); -static uint32_t backgroundRowMask(const StSimT *sim, int16_t px, int16_t py); +static bool backgroundRowHits(const StSimT *sim, int16_t firstCol, int16_t py, const uint8_t *w); +static bool cabRowsTouchBackground(StSimT *sim, int16_t firstCol, int16_t py, uint8_t phase); +static const uint8_t *cabWindows(StSimT *sim, uint8_t s); static int16_t cellOfPixel(int16_t px); -static void bitScrollHatch(StSimT *sim); static void collisionDispatch(StSimT *sim); static void collisionPhase1(StSimT *sim); static void collisionPhase2(StSimT *sim); static StTickResultE collisionPhase3(StSimT *sim); static void computeCollisions(StSimT *sim); +static bool coveringCellsSolid(const StSimT *sim, int16_t firstCol, int16_t topRow, int16_t botRow); static void crashStart(StSimT *sim); static void drawCabIcons(StSimT *sim); static void drawScreensCount(StSimT *sim); @@ -37,19 +47,20 @@ static void fireButtonEdge(StSimT *sim); static void fuelBarHud(StSimT *sim); static void fuelTick(StSimT *sim); static void hudDraw(StSimT *sim); -static void hudInit(StSimT *sim); static void landedHandler(StSimT *sim); static bool levelEndCheck(const StSimT *sim); -static void marshal(StSimT *sim); +static void markCell(StSimT *sim, uint16_t cell); static void padDetect(StSimT *sim); static void padLandingBob(StSimT *sim); static void passengerArrTick(StSimT *sim); static void physicsTick(StSimT *sim); static uint8_t readInput(StSimT *sim); -static void markCell(StSimT *sim, uint16_t cell); +static void rowWindow(const uint8_t *row, uint8_t s, uint8_t *w); static void setCell(StSimT *sim, uint16_t cell, uint8_t ch); static void setColorCell(StSimT *sim, uint16_t cell, uint8_t color); static void spriteMasks(StSimT *sim, uint8_t idx); +static bool spriteRowsTouchBackground(const StSimT *sim, uint8_t idx, int16_t firstCol, int16_t py, uint8_t phase); +static bool spriteTouchesBackground(StSimT *sim, uint8_t idx); static void spritesOffReset(StSimT *sim); static void taxiSpawnInit(StSimT *sim); static void taxiSpriteCelSelect(StSimT *sim); @@ -87,30 +98,73 @@ static void applyVelocityY(StSimT *sim) { } -// The 24 background bits under a sprite row whose leftmost pixel sits -// at visible pixel (px, py); a set bit is character graphics data. -static uint32_t backgroundRowMask(const StSimT *sim, int16_t px, int16_t py) { - uint32_t window = 0u; - int16_t col; - int16_t firstCol; - uint8_t k; - uint8_t shift; +// Whether a 4-byte row window (rowWindow: byte j covers character +// column firstCol + j) meets character graphics on screen pixel row py. +static bool backgroundRowHits(const StSimT *sim, int16_t firstCol, int16_t py, const uint8_t *w) { + uint8_t glyphRow; + uint8_t j; if (py < 0 || py >= (int16_t)(ST_SCREEN_ROWS * 8u)) { - return 0u; + return false; } - // Floor division so a negative px lands on the column to its left. - firstCol = cellOfPixel(px); - shift = (uint8_t)(px - (int16_t)(firstCol << 3)); - for (k = 0u; k < 4u; k++) { - uint8_t byte = 0u; - col = (int16_t)(firstCol + (int16_t)k); - if (col >= 0 && col < (int16_t)ST_SCREEN_COLS) { - byte = sim->charset[sim->screen[ST_CELL((uint16_t)py >> 3, (uint16_t)col)]][py & 7]; + glyphRow = (uint8_t)(py & 7); + for (j = 0u; j < 4u; j++) { + int16_t col = (int16_t)(firstCol + (int16_t)j); + if (w[j] != 0u && col >= 0 && col < (int16_t)ST_SCREEN_COLS) { + if ((stSimGlyphRow(sim, sim->screen[ST_CELL((uint16_t)py >> 3, (uint16_t)col)], glyphRow) & w[j]) != 0u) { + return true; + } } - window = (window << 8) | byte; } - return (window >> (8u - shift)) & 0xFFFFFFu; + return false; +} + + +// The cab's 21 cached row windows at grid phase `phase` against the +// background rows under it. +static bool cabRowsTouchBackground(StSimT *sim, int16_t firstCol, int16_t py, uint8_t phase) { + uint8_t first = sim->collRowFirst[0]; + uint8_t left = (uint8_t)(sim->collRowLast[0] + 1u - first); + const uint8_t *win; + + if (first == ST_SPRITE_H) { + return false; + } + win = cabWindows(sim, phase) + (uint16_t)first * 4u; + py = (int16_t)(py + first); + // Counted down to zero: `dec a / bne` needs no compare. (It also + // dodged the llvm816 bug, fixed 2026-09-22, where the up-counting form + // compiled to `cmp #end / inc a / bne` with the inc trampling the + // compare's Z, so the loop ran past row 20 into the next phase's + // windows -- the false landing crash in the IIgs "Puzzles" demo.) + do { + if (backgroundRowHits(sim, firstCol, py, win)) { + return true; + } + win += 4; + py++; + left--; + } while (left != 0u); + return false; +} + + +// The cab's 21 row windows at grid phase s, 4 bytes per row, built on +// first use after the cab's mask changed and cached until it changes +// again. The cab is one side of every collision test, so no test +// shifts a row (a variable 32-bit shift is a library bit loop on the +// 65816 and this was the collision test's whole cost). +static const uint8_t *cabWindows(StSimT *sim, uint8_t s) { + uint8_t (*win)[4] = sim->cabWin[s]; + uint8_t r; + + if ((sim->cabWinValid & (uint8_t)(1u << s)) == 0u) { + for (r = 0u; r < ST_SPRITE_H; r++) { + rowWindow(sim->collMask[0][r], s, win[r]); + } + sim->cabWinValid |= (uint8_t)(1u << s); + } + return &win[0][0]; } @@ -125,19 +179,6 @@ static int16_t cellOfPixel(int16_t px) { } -// $63D0 -- rotate every row of the transporter hatch glyph one pixel -// right: the animated energy field in the top-wall opening. -static void bitScrollHatch(StSimT *sim) { - uint8_t row; - - for (row = 0u; row < 8u; row++) { - uint8_t b = sim->charset[ST_CHAR_TRANSPORTER][row]; - sim->charset[ST_CHAR_TRANSPORTER][row] = (uint8_t)((b >> 1) | (b << 7)); - } - stSimMarkChar(sim, ST_CHAR_TRANSPORTER); -} - - // $6966 -- latch the frame's collisions and run the crash phases. static void collisionDispatch(StSimT *sim) { computeCollisions(sim); @@ -181,7 +222,6 @@ static void collisionDispatch(StSimT *sim) { static void collisionPhase1(StSimT *sim) { uint8_t jitter; uint16_t sum; - uint16_t colFrac; if ((sim->hitDispatchResult & 0x80u) == 0u) { sim->phaseTimer--; @@ -193,11 +233,8 @@ static void collisionPhase1(StSimT *sim) { stAudioThrustSweep((uint8_t)(sim->thrustSweep >> 1)); sim->spr[0].ptr ^= 1u; // $6A98: rng(5) - 3 added to the sprite column (msb follows). - jitter = (uint8_t)(stSimRng(sim, 5u) - 3u); - colFrac = (uint16_t)(((uint16_t)sim->spr[0].msb << 8) | sim->spr[0].col); - colFrac = (uint16_t)(colFrac + (uint16_t)(int16_t)(int8_t)jitter); - sim->spr[0].col = (uint8_t)colFrac; - sim->spr[0].msb = (uint8_t)(colFrac >> 8); + jitter = (uint8_t)(stSimRng(sim, 5u) - 3u); + stSimSpriteAddX(sim, 0u, jitter); edgeReflect(sim); applyVelocityX(sim); // vy += 40 (low byte with carry into the high byte), row += vy hi. @@ -226,7 +263,7 @@ static void collisionPhase2(StSimT *sim) { sim->phaseReload++; sim->phaseTimer = sim->phaseReload; sim->spr[0].ptr++; - if (sim->spr[0].ptr != 0xD1u) { + if (sim->spr[0].ptr != ST_SPRITE_WRECK_LAST) { return; } sim->spr[0].enable = 0u; @@ -247,11 +284,11 @@ static StTickResultE collisionPhase3(StSimT *sim) { return ST_TICK_CRASH_DONE; } if (sim->stage == ST_STAGE_RIDING) { - hudInit(sim); + stSimHudInit(sim); if (sim->fareSlotCount != 0u) { sim->spriteSlots[sim->activeDyingSlot] = 0u; sim->fareSlotCount--; - if (sim->activeSpriteIdx == 0x0Bu) { + if (sim->activeSpriteIdx == ST_FARE_DEST_UP) { uint8_t k; for (k = 0u; k < 4u; k++) { setCell(sim, (uint16_t)(ST_CELL_TRANSPORTER + k), ST_CHAR_TRANSPORTER); @@ -263,7 +300,7 @@ static StTickResultE collisionPhase3(StSimT *sim) { } sim->activeSpriteIdx = 0u; sim->stage = ST_STAGE_IDLE; - stSimDrawText(sim, 14u, 24u, kTextBlank, 1u); + stSimClearMessage(sim); } // $6BBA p = sim->player; @@ -280,69 +317,56 @@ static StTickResultE collisionPhase3(StSimT *sim) { // The C64 reads two VIC collision registers each frame: $D01F (a sprite // touched the background) and $D01E (two sprites touched). Only the cab // (sprite 0) can drive a gameplay outcome from them: the crash dispatch -// returns unless the cab or the background is touched, and every hook -// reads a bit set by cab contact. So the full all-pairs register is not -// reproduced -- only sprite 0 versus the background and versus the other -// sprites, and the exhaust flame (sprite 2, the cab's own, always -// overlapping it) is skipped. Non-cab pairs (hazard vs hazard, passenger -// vs hazard with the cab clear) never change the dispatch outcome, so -// their bits are left unset. Proven against all four demo traces: the -// observable state (positions, crash phase, score) is byte-identical. +// returns unless the cab or the background is touched, and the hooks +// read a bit set by cab contact -- except levels J and P, which watch +// their own hazards hit the scenery and opt those sprites in through +// bgCollSprites. So the full all-pairs register is not reproduced -- +// only sprite 0 versus the background and versus the other sprites, +// plus the opted-in sprites versus the background, and the exhaust +// flame (sprite 2, the cab's own, always overlapping it) is skipped. +// Non-cab pairs (hazard vs hazard, passenger vs hazard with the cab +// clear) never change the dispatch outcome, so their bits are left +// unset. Proven against all four demo traces: the observable state +// (positions, crash phase, score) is byte-identical. static void computeCollisions(StSimT *sim) { - uint32_t (*mask)[ST_SPRITE_H] = sim->collMask; - uint8_t ss = 0u; - uint8_t bg = 0u; - int16_t cabX; - int16_t cabY; - uint8_t b; - uint8_t r; + uint8_t ss = 0u; + uint8_t bg = 0u; + uint8_t b; + uint8_t r; + for (b = 1u; b < ST_HW_SPRITES; b++) { + if ((sim->bgCollSprites & kStBit[b]) == 0u || (sim->frame.enableMask & kStBit[b]) == 0u) { + continue; + } + spriteMasks(sim, b); + if (spriteTouchesBackground(sim, b)) { + bg |= kStBit[b]; + } + } if ((sim->frame.enableMask & 1u) == 0u) { sim->spriteSpriteColl = 0u; - sim->spriteBgColl = 0u; + sim->spriteBgColl = bg; return; } spriteMasks(sim, 0u); - cabX = (int16_t)((int16_t)sim->frame.x[0] - ST_SPRITE_X_ORIGIN); - cabY = (int16_t)((int16_t)sim->frame.y[0] - ST_SPRITE_Y_ORIGIN); - // The background can only touch the cab where a covering character - // cell is non-blank. In open flight every covering cell is the space - // glyph (all-zero pixels), so the 21-row pixel test is skipped there; - // any non-space cell falls through to the exact test, so the result - // is unchanged (space is the only zero-pixel glyph the test can hit). - { - int16_t firstCol = cellOfPixel(cabX); - int16_t topRow = (cabY < 0) ? 0 : cellOfPixel(cabY); - int16_t botRow = cellOfPixel((int16_t)(cabY + (int16_t)ST_SPRITE_H - 1)); - bool solid = false; - int16_t rr; - int16_t cc; - - if (botRow >= (int16_t)ST_SCREEN_ROWS) { - botRow = (int16_t)(ST_SCREEN_ROWS - 1u); - } - for (rr = topRow; rr <= botRow && !solid; rr++) { - for (cc = firstCol; cc <= (int16_t)(firstCol + 3); cc++) { - if (cc >= 0 && cc < (int16_t)ST_SCREEN_COLS && sim->screen[ST_CELL((uint16_t)rr, (uint16_t)cc)] != ST_CHAR_SPACE) { - solid = true; - break; - } - } - } - if (solid) { - for (r = 0u; r < ST_SPRITE_H; r++) { - if (mask[0][r] != 0u && (mask[0][r] & backgroundRowMask(sim, cabX, (int16_t)(cabY + r))) != 0u) { - bg = 1u; - break; - } - } - } + if (spriteTouchesBackground(sim, 0u)) { + bg = 1u; } for (b = 1u; b < ST_HW_SPRITES; b++) { - int16_t dx; - int16_t dy; + int16_t dx; + int16_t dy; + uint8_t s; + int16_t q; + uint8_t kLo; + uint8_t kHi; + uint8_t rLo; + uint8_t rHi; + int16_t lo; + int16_t hi; + const uint8_t *win; + const uint8_t *bRow; // Skip the exhaust flame (sprite 2) and any disabled sprite. - if (b == 2u || (sim->frame.enableMask & (uint8_t)(1u << b)) == 0u) { + if (b == 2u || (sim->frame.enableMask & kStBit[b]) == 0u) { continue; } dx = (int16_t)((int16_t)sim->frame.x[b] - (int16_t)sim->frame.x[0]); @@ -351,20 +375,44 @@ static void computeCollisions(StSimT *sim) { continue; } spriteMasks(sim, b); - for (r = 0u; r < ST_SPRITE_H; r++) { - int16_t rb = (int16_t)((int16_t)r - dy); - uint32_t mb; - if (rb < 0 || rb >= (int16_t)ST_SPRITE_H) { - continue; + // Sprite b sits dx pixels right of the cab; with dx = 8q - s + // (s in 0..7) its row byte k lines up with byte k + q of the + // cab's phase-s windows, and only 0 <= k + q <= 3 can overlap. + s = (uint8_t)((0 - dx) & (ST_ROW_PHASES - 1)); + q = (int16_t)(((dx + (int16_t)s + 32) >> 3) - 4); + kLo = (q < 0) ? (uint8_t)(0 - q) : 0u; + kHi = (q > 1) ? (uint8_t)(3 - q) : 2u; + // Rows r of the cab meet rows r - dy of sprite b; walk only the + // rows where both have pixels, both pointers advancing in step. + if (sim->collRowFirst[b] == ST_SPRITE_H || sim->collRowFirst[0] == ST_SPRITE_H) { + continue; + } + lo = (int16_t)sim->collRowFirst[0]; + hi = (int16_t)(sim->collRowLast[0] + 1); + if ((int16_t)sim->collRowFirst[b] + dy > lo) { + lo = (int16_t)((int16_t)sim->collRowFirst[b] + dy); + } + if ((int16_t)sim->collRowLast[b] + 1 + dy < hi) { + hi = (int16_t)((int16_t)sim->collRowLast[b] + 1 + dy); + } + if (lo >= hi) { + continue; + } + rLo = (uint8_t)lo; + rHi = (uint8_t)hi; + win = cabWindows(sim, s) + (uint16_t)rLo * 4u; + bRow = sim->collMask[b][(uint8_t)((int16_t)rLo - dy)]; + for (r = rLo; r < rHi; r++, win += 4, bRow += 3) { + bool hit = false; + uint8_t k; + for (k = kLo; k <= kHi; k++) { + if ((bRow[k] & win[(uint8_t)((int16_t)k + q)]) != 0u) { + hit = true; + break; + } } - mb = mask[b][rb]; - if (dx >= 0) { - mb >>= dx; - } else { - mb = (mb << (uint8_t)(-dx)) & 0xFFFFFFu; - } - if ((mask[0][r] & mb) != 0u) { - ss |= (uint8_t)(1u | (1u << b)); + if (hit) { + ss |= (uint8_t)(1u | kStBit[b]); break; } } @@ -374,9 +422,26 @@ static void computeCollisions(StSimT *sim) { } +// Whether any character cell in rows topRow..botRow, columns firstCol.. +// firstCol+3 (the cells a sprite at that spot covers) is non-blank. +static bool coveringCellsSolid(const StSimT *sim, int16_t firstCol, int16_t topRow, int16_t botRow) { + int16_t rr; + int16_t cc; + + for (rr = topRow; rr <= botRow; rr++) { + for (cc = firstCol; cc <= (int16_t)(firstCol + 3); cc++) { + if (cc >= 0 && cc < (int16_t)ST_SCREEN_COLS && sim->screen[ST_CELL((uint16_t)rr, (uint16_t)cc)] != ST_CHAR_SPACE) { + return true; + } + } + } + return false; +} + + // $6A2B -- start the wreck sequence. static void crashStart(StSimT *sim) { - sim->spr[0].ptr = 0xCCu; + sim->spr[0].ptr = ST_SPRITE_WRECK_FIRST; sim->collisionPhase++; sim->phaseTimer = 2u; sim->phaseReload = 2u; @@ -471,7 +536,7 @@ static void fireButtonEdge(StSimT *sim) { // $6D6A -- sprite 2 is the exhaust: two columns left of the cab, // visible every other tick while a direction is held. void stSimFlameUpdate(StSimT *sim) { - uint16_t sum; + uint16_t x; if (sim->dirMask == 0u) { stAudioNoise(false); @@ -479,9 +544,9 @@ void stSimFlameUpdate(StSimT *sim) { return; } stAudioNoise(true); - sum = (uint16_t)((uint16_t)sim->spr[0].col + 0xFEu); - sim->spr[2].col = (uint8_t)sum; - sim->spr[2].msb = (uint8_t)(sim->spr[0].msb + 0xFFu + (uint8_t)(sum >> 8)); + x = (uint16_t)(stSimSpriteX(sim, 0u) - 2u); + sim->spr[2].col = (uint8_t)x; + sim->spr[2].msb = (uint8_t)(x >> 8); sim->spr[2].row = sim->spr[0].row; sim->flameParity ^= 1u; if (sim->flameParity == 0u) { @@ -529,7 +594,7 @@ static void fuelTick(StSimT *sim) { const StLevelT *L = sim->level; uint8_t x; - if (sim->activePad == L->padCount && sim->activePad != L->specialPad) { + if (sim->activePad == sim->padCount && sim->activePad != sim->specialPad) { if (sim->screen[ST_CELL_FUEL_LAST] == ST_CHAR_BLANK) { return; } @@ -590,16 +655,6 @@ static void hudDraw(StSimT *sim) { } -// $43A5 -- blank the fare meter. -static void hudInit(StSimT *sim) { - uint8_t k; - - for (k = 0u; k < ST_NUMBER_CHARS; k++) { - setCell(sim, (uint16_t)(ST_CELL_FARE + k), kHudTemplate[k]); - } -} - - // $657B -- parked on a pad: wait for UP to be released, then UP takes // off (velocities cleared, gear retracted). static void landedHandler(StSimT *sim) { @@ -631,26 +686,6 @@ static bool levelEndCheck(const StSimT *sim) { } -// $4253 + $4293 -- snapshot the shadow tables into the frame the VIC -// shows next. -static void marshal(StSimT *sim) { - uint8_t i; - uint8_t enable = 0u; - - for (i = 0u; i < ST_HW_SPRITES; i++) { - sim->frame.x[i] = (uint16_t)(((uint16_t)(sim->spr[i].msb != 0u ? 1u : 0u) << 8) | sim->spr[i].col); - sim->frame.y[i] = sim->spr[i].row; - sim->frame.ptr[i] = sim->spr[i].ptr; - sim->frame.color[i] = sim->spr[i].color; - if (sim->spr[i].enable != 0u) { - enable |= (uint8_t)(1u << i); - } - } - sim->frame.enableMask = enable; - sim->frame.multiMask = sim->multiColorMask; -} - - // $645C -- with the gear down and a slow descent, an exact row match // inside a pad's X bounds is a landing. static void padDetect(StSimT *sim) { @@ -665,9 +700,9 @@ static void padDetect(StSimT *sim) { if (((uint16_t)sim->velY >> 8) != 0u) { return; } - for (i = (int8_t)(sim->level->padCount - 1u); i >= 0; i--) { + for (i = (int8_t)(sim->padCount - 1u); i >= 0; i--) { const StPadT *p = &sim->pads[i]; - int16_t taxiX = (int16_t)(((int16_t)sim->posXmsb << 8) | sim->posXcol); + int16_t taxiX = (int16_t)(((uint16_t)sim->posXmsb << 8) | sim->posXcol); int16_t x1 = (int16_t)(((int16_t)p->x1Hi << 8) | p->x1Lo); int16_t x2 = (int16_t)(((int16_t)p->x2Hi << 8) | p->x2Lo); if (sim->spr[0].row != p->row) { @@ -684,7 +719,7 @@ static void padDetect(StSimT *sim) { sim->eventDispatchType = 0u; if (sim->activePad != sim->activeSpriteIdx) { if (sim->stage == ST_STAGE_RIDING) { - sim->eventDispatchType = (sim->activeSpriteIdx == 0x0Bu) ? 3u : 2u; + sim->eventDispatchType = (sim->activeSpriteIdx == ST_FARE_DEST_UP) ? 3u : 2u; } else if (sim->stage == ST_STAGE_WAIT) { sim->eventDispatchType = 1u; } @@ -700,7 +735,7 @@ static void padDetect(StSimT *sim) { sim->bobTimer = 8u; stAudioSfx(stC64SfxProgram(ST_SFX_LAND_HARD)); if (sim->stage == ST_STAGE_RIDING) { - hudInit(sim); + stSimHudInit(sim); } return; } @@ -739,6 +774,8 @@ static void passengerArrTick(StSimT *sim) { sim->cabs[p]++; sim->bonusLatch[p] = sim->cabs[p]; setCell(sim, (uint16_t)(ST_CELL_CAB_ICONS - 1u + sim->cabs[p]), ST_CHAR_CAB_ICON); + // $70D4: the bonus-cab jingle (song 4), blocking. + sim->jingleRequest = (uint8_t)(ST_JINGLE_SONG_BONUS + 1u); } @@ -755,8 +792,9 @@ static void physicsTick(StSimT *sim) { landedHandler(sim); return; } - if (sim->screen[ST_CELL_FUEL] == ST_CHAR_MENU_SENTINEL) { - // Out of fuel: only FIRE survives, no thrust at all. + if (sim->screen[ST_CELL_FUEL] == ST_CHAR_FUEL_HALF) { + // The leftmost cell is down to its last half: out of fuel, so + // only FIRE survives and there is no thrust at all. sim->inputMask &= 0x10u; } else { if ((sim->inputMask & 0x0Cu) != 0u) { @@ -820,9 +858,93 @@ static void markCell(StSimT *sim, uint16_t cell) { } +// ---- glyph reverse index (see StSimT in stSim.h) -------------------- + +// Take `cell` out of the list of whatever glyph it currently shows. +// Reads sim->screen[cell], so call it BEFORE the new character lands. +static void glyphIndexUnlink(StSimT *sim, uint16_t cell) { + uint16_t prev = sim->cellPrev[cell]; + uint16_t next = sim->cellNext[cell]; + + if (prev == ST_GLYPH_CELL_NONE) { + sim->glyphHead[sim->screen[cell]] = next; + } else { + sim->cellNext[prev] = next; + } + if (next != ST_GLYPH_CELL_NONE) { + sim->cellPrev[next] = prev; + } +} + + +// Push `cell` onto the front of glyph `ch`'s list. +static void glyphIndexLink(StSimT *sim, uint16_t cell, uint8_t ch) { + uint16_t head = sim->glyphHead[ch]; + + sim->cellPrev[cell] = ST_GLYPH_CELL_NONE; + sim->cellNext[cell] = head; + if (head != ST_GLYPH_CELL_NONE) { + sim->cellPrev[head] = cell; + } + sim->glyphHead[ch] = cell; +} + + +// Re-derive every list from screen[]. Bulk screen writes (level load, +// title, the blanked intro) bypass setCell, and all of them call +// stSimDirtyAll, so that is the one place this has to hang off. +static void glyphIndexRebuild(StSimT *sim) { + uint16_t cell; + uint16_t k; + + for (k = 0u; k < ST_CHARSET_CHARS; k++) { + sim->glyphHead[k] = ST_GLYPH_CELL_NONE; + } + // Built back to front, so each list comes out in ASCENDING cell + // order and the repaint walk moves forward through the stage the + // way the old span scan did. + cell = ST_SCREEN_CELLS; + while (cell != 0u) { + cell--; + glyphIndexLink(sim, cell, sim->screen[cell]); + } +} + + +// A sprite row (3 bytes, leftmost pixel = bit 7 of byte 0) shifted +// right by s (0..7) into a 4-byte window whose byte j holds the row's +// pixels 8j-s .. 8j-s+7: the row aligned to a grid of 8-pixel cells +// that its left edge sits s pixels into. Fixed shifts by 4, 2 and 1 +// on 16-bit halves -- a variable-count shift is a library bit loop on +// the 65816, and a byte carry chain gets folded back into one. +static void rowWindow(const uint8_t *row, uint8_t s, uint8_t *w) { + uint16_t hi = (uint16_t)(((uint16_t)row[0] << 8) | row[1]); + uint16_t lo = (uint16_t)((uint16_t)row[2] << 8); + + if ((s & 4u) != 0u) { + lo = (uint16_t)((lo >> 4) | (uint16_t)(hi << 12)); + hi = (uint16_t)(hi >> 4); + } + if ((s & 2u) != 0u) { + lo = (uint16_t)((lo >> 2) | (uint16_t)(hi << 14)); + hi = (uint16_t)(hi >> 2); + } + if ((s & 1u) != 0u) { + lo = (uint16_t)((lo >> 1) | (uint16_t)(hi << 15)); + hi = (uint16_t)(hi >> 1); + } + w[0] = (uint8_t)(hi >> 8); + w[1] = (uint8_t)hi; + w[2] = (uint8_t)(lo >> 8); + w[3] = (uint8_t)lo; +} + + static void setCell(StSimT *sim, uint16_t cell, uint8_t ch) { if (sim->screen[cell] != ch) { + glyphIndexUnlink(sim, cell); sim->screen[cell] = ch; + glyphIndexLink(sim, cell, ch); markCell(sim, cell); } } @@ -837,42 +959,112 @@ static void setColorCell(StSimT *sim, uint16_t cell, uint8_t color) { } -// 21 rows of 24-bit foreground masks for a displayed sprite, kept in +// 21 rows of 3-byte foreground masks for a displayed sprite, kept in // collMask[idx] and rebuilt only when its bitmap or multicolour mode // changes. A multicolour sprite's 2-bit pairs cover two pixels each: a // non-zero pair is two set mask bits (kPairMask maps a nibble's two -// pairs at once). +// pairs at once). The cab's cached windows die with its mask. static void spriteMasks(StSimT *sim, uint8_t idx) { static const uint8_t kPairMask[16] = { 0x0u, 0x3u, 0x3u, 0x3u, 0xCu, 0xFu, 0xFu, 0xFu, 0xCu, 0xFu, 0xFu, 0xFu, 0xCu, 0xFu, 0xFu, 0xFu }; const uint8_t *bm = stSimSpriteBitmap(sim, sim->frame.ptr[idx]); - uint8_t multi = (uint8_t)((sim->frame.multiMask >> idx) & 1u); - uint32_t *rows = sim->collMask[idx]; - uint8_t r; + uint8_t multi = (uint8_t)((sim->frame.multiMask & kStBit[idx]) != 0u ? 1u : 0u); + uint8_t *row = sim->collMask[idx][0]; + uint16_t n; + uint8_t first; + uint8_t last; if (bm != 0 && bm == sim->collMaskBm[idx] && multi == sim->collMaskMulti[idx]) { return; } - for (r = 0u; r < ST_SPRITE_H; r++) { - uint8_t b0 = 0u; - uint8_t b1 = 0u; - uint8_t b2 = 0u; + for (n = 0u; n < (uint16_t)(ST_SPRITE_H * 3u); n++) { + uint8_t b = 0u; if (bm != 0) { - b0 = bm[r * 3u]; - b1 = bm[r * 3u + 1u]; - b2 = bm[r * 3u + 2u]; + b = bm[n]; if (multi != 0u) { - b0 = (uint8_t)((kPairMask[b0 >> 4] << 4) | kPairMask[b0 & 0x0Fu]); - b1 = (uint8_t)((kPairMask[b1 >> 4] << 4) | kPairMask[b1 & 0x0Fu]); - b2 = (uint8_t)((kPairMask[b2 >> 4] << 4) | kPairMask[b2 & 0x0Fu]); + b = (uint8_t)((kPairMask[b >> 4] << 4) | kPairMask[b & 0x0Fu]); } } - rows[r] = ((uint32_t)b0 << 16) | ((uint32_t)b1 << 8) | b2; + row[n] = b; } + first = ST_SPRITE_H; + last = 0u; + for (n = 0u; n < ST_SPRITE_H; n++) { + if ((row[n * 3u] | row[n * 3u + 1u] | row[n * 3u + 2u]) != 0u) { + if (first == ST_SPRITE_H) { + first = (uint8_t)n; + } + last = (uint8_t)n; + } + } + sim->collRowFirst[idx] = first; + sim->collRowLast[idx] = last; sim->collMaskBm[idx] = bm; sim->collMaskMulti[idx] = multi; + if (idx == 0u) { + sim->cabWinValid = 0u; + } +} + + +// A non-cab sprite's rows (shifted here, only rows with pixels) against +// the background rows under it. +static bool spriteRowsTouchBackground(const StSimT *sim, uint8_t idx, int16_t firstCol, int16_t py, uint8_t phase) { + uint8_t first = sim->collRowFirst[idx]; + uint8_t left = (uint8_t)(sim->collRowLast[idx] + 1u - first); + const uint8_t *row; + + if (first == ST_SPRITE_H) { + return false; + } + row = sim->collMask[idx][first]; + py = (int16_t)(py + first); + // Counted down like cabRowsTouchBackground. + do { + if ((row[0] | row[1] | row[2]) != 0u) { + uint8_t w[4]; + rowWindow(row, phase, w); + if (backgroundRowHits(sim, firstCol, py, w)) { + return true; + } + } + row += 3; + py++; + left--; + } while (left != 0u); + return false; +} + + +// One displayed sprite against the background pixels ($D01F). The +// background can only touch it where a covering character cell is +// non-blank. In open flight every covering cell is the space glyph +// (all-zero pixels), so the 21-row pixel test is skipped there; any +// non-space cell falls through to the exact test, so the result is +// unchanged (space is the only zero-pixel glyph the test can hit). +// Needs spriteMasks(sim, idx) run for this frame first. The row tests +// live in cabRowsTouchBackground / spriteRowsTouchBackground, whose +// loops count DOWN -- see the note there before "simplifying" them. +static bool spriteTouchesBackground(StSimT *sim, uint8_t idx) { + int16_t px = (int16_t)((int16_t)sim->frame.x[idx] - ST_SPRITE_X_ORIGIN); + int16_t py = (int16_t)((int16_t)sim->frame.y[idx] - ST_SPRITE_Y_ORIGIN); + int16_t firstCol = cellOfPixel(px); + int16_t topRow = (py < 0) ? 0 : cellOfPixel(py); + int16_t botRow = cellOfPixel((int16_t)(py + (int16_t)ST_SPRITE_H - 1)); + uint8_t phase = (uint8_t)(px - (int16_t)(firstCol << 3)); + + if (botRow >= (int16_t)ST_SCREEN_ROWS) { + botRow = (int16_t)(ST_SCREEN_ROWS - 1u); + } + if (!coveringCellsSolid(sim, firstCol, topRow, botRow)) { + return false; + } + if (idx == 0u) { + return cabRowsTouchBackground(sim, firstCol, py, phase); + } + return spriteRowsTouchBackground(sim, idx, firstCol, py, phase); } @@ -897,7 +1089,7 @@ static void taxiSpawnInit(StSimT *sim) { sim->spr[0].enable = 1u; sim->multiColorMask = 0x07u; - sim->spr[0].ptr = 0xC0u; + sim->spr[0].ptr = ST_SPRITE_CAB_RIGHT; sim->posYlo = L->spawn[3]; sim->posYrow = L->spawn[4]; sim->posXlo = L->spawn[0]; @@ -930,9 +1122,9 @@ static void taxiSpriteCelSelect(StSimT *sim) { uint8_t base; if ((sim->dirMask & 0x04u) != 0u) { - base = 0xDCu; + base = ST_SPRITE_CAB_LEFT; } else if ((sim->dirMask & 0x08u) != 0u) { - base = 0xC0u; + base = ST_SPRITE_CAB_RIGHT; } else { return; } @@ -1014,6 +1206,12 @@ void stSimBcdAdd(StSimT *sim, uint16_t dstCell, const uint8_t *blob) { } +// $6C38 -- blank the message row (colour 1, as the original writes it). +void stSimClearMessage(StSimT *sim) { + stSimDrawText(sim, ST_MESSAGE_COL, ST_MESSAGE_ROW, kTextBlank, 1u); +} + + // $440B -- subtract one from the digit at `position` of the number at // `cell`, borrowing leftward. False when there is nothing to take. bool stSimDecrementNumber(StSimT *sim, uint16_t cell, uint8_t position) { @@ -1078,16 +1276,79 @@ void stSimDrawText(StSimT *sim, uint8_t col, uint8_t row, const uint8_t *text, u } +// A character's eight 1bpp rows: the game's edit of it, or the const ROM +// glyph when it has never been edited. +const uint8_t *stSimGlyph(const StSimT *sim, uint8_t ch) { + uint8_t slot = sim->glyphSlot[ch]; + + if (slot != 0u) { + return sim->glyphOverride[slot - 1u].rows; + } + return &stC64Charset()[(uint16_t)ch << 3]; +} + + +// The writable eight rows, copied out of ROM on the first edit. A full +// table parks the edit in the spill entry rather than stealing another +// character's slot: that glyph then simply stays at its ROM form. +uint8_t *stSimGlyphMut(StSimT *sim, uint8_t ch) { + uint8_t slot = sim->glyphSlot[ch]; + + if (slot == 0u) { + if (sim->glyphOverrideCount >= ST_GLYPH_OVERRIDES) { + return sim->glyphOverride[ST_GLYPH_OVERRIDES].rows; + } + sim->glyphOverrideCount++; + slot = sim->glyphOverrideCount; + sim->glyphSlot[ch] = slot; + memcpy(sim->glyphOverride[slot - 1u].rows, &stC64Charset()[(uint16_t)ch << 3], 8u); + } + return sim->glyphOverride[slot - 1u].rows; +} + + +// Every glyph back to the ROM set (a scene load). +void stSimGlyphReset(StSimT *sim) { + memset(sim->glyphSlot, 0, sizeof(sim->glyphSlot)); + sim->glyphOverrideCount = 0u; +} + + +// One row of a character's glyph. The collision background test reads +// four of these per sprite row, so it skips the pointer round trip. +uint8_t stSimGlyphRow(const StSimT *sim, uint8_t ch, uint8_t row) { + uint8_t slot = sim->glyphSlot[ch]; + + if (slot != 0u) { + return sim->glyphOverride[slot - 1u].rows[row]; + } + return stC64Charset()[((uint16_t)ch << 3) + row]; +} + + +// $43A5 -- blank the fare meter back to its template. +void stSimHudInit(StSimT *sim) { + uint8_t k; + + for (k = 0u; k < ST_NUMBER_CHARS; k++) { + setCell(sim, (uint16_t)(ST_CELL_FARE + k), kHudTemplate[k]); + } +} + + // $62F0 + $5F18 -- load the screen image and run the level prelude. void stSimEnterLevel(StSimT *sim, const StLevelT *level) { uint8_t k; sim->level = level; memcpy(sim->pads, level->pads, sizeof(sim->pads)); + sim->padCount = level->padCount; + sim->specialPad = level->specialPad; + sim->bgCollSprites = 0u; memcpy(sim->screen, level->screen, ST_SCREEN_CELLS); memcpy(sim->color, level->color, ST_SCREEN_CELLS); stSimDirtyAll(sim); - memcpy(sim->charset, stC64Charset(), sizeof(sim->charset)); + stSimGlyphReset(sim); sim->charDirtyAll = true; sim->charDirtyCount = 0u; // Level sprites live in the one level buffer, so the same bitmap @@ -1120,7 +1381,7 @@ void stSimEnterLevel(StSimT *sim, const StLevelT *level) { sim->spr[k].enable = 0u; } sim->hookLevel = level->levelIndex; - memcpy(sim->hookRam, level->hookData, ST_HOOK_BYTES); + stHookSceneLoad(sim); stSimRespawn(sim); } @@ -1134,19 +1395,27 @@ void stSimRespawn(StSimT *sim) { sim->eventDispatchType = 0u; sim->stage = ST_STAGE_IDLE; if (sim->levelState >= 2u) { - static const uint8_t kPadPlease[] = { 0x50, 0x41, 0x44, 0x20, 0x31, 0x20, 0x20, 0x50, 0x4C, 0x45, 0x41, 0x53, 0x45, 0 }; + static const uint8_t kPadPlease[] = "PAD 1 PLEASE"; sim->stage = ST_STAGE_RIDING; sim->activeSpriteIdx = 1u; sim->activeDyingSlot = 1u; - stSimDrawText(sim, 14u, 24u, kPadPlease, 1u); + stSimDrawText(sim, ST_MESSAGE_COL, ST_MESSAGE_ROW, kPadPlease, 1u); sim->eventDispatchType = 1u; } taxiSpawnInit(sim); stHookPrelude1(sim); - // $6906 framePresent: the start-of-screen jingle ends with the SID - // (and the demo RNG) reset. - marshal(sim); + // $6906 framePresent: the start-of-screen jingle, then the SID (and + // the demo RNG) reset. $6935: level 25 gets song 4, every other + // screen rotates songs 0..3 on $5E9B. The host plays it with the sim + // frozen, which is what the C64's blocking play-and-wait amounts to. + stSimMarshal(sim); stAudioSilence(); + sim->jingleRotate++; + if (sim->levelState == ST_JINGLE_LEVEL_25) { + sim->jingleRequest = (uint8_t)(ST_JINGLE_SONG_BONUS + 1u); + } else { + sim->jingleRequest = (uint8_t)((sim->jingleRotate & 3u) + 1u); + } sim->spriteSpriteColl = 0u; sim->spriteBgColl = 0u; sim->rngT1 = 0x06u; @@ -1218,7 +1487,65 @@ void stSimMarkChar(StSimT *sim, uint8_t ch) { } +// $4253 + $4293 -- snapshot the sprite shadows into the frame the VIC +// shows next. The title and level-intro loops flush the same way. +#if defined(__W65816__) +// The IIgs copies the frame in asm (stMarshalIigs.s); it takes one far +// pointer to this block so no stack-argument convention is involved. +typedef struct { + const StSpriteT *spr; + StFrameT *frame; + uint8_t multi; +} StMarshalArgsT; +extern void stSimMarshalIigs(const StMarshalArgsT *args); +// The asm hardcodes these layouts; a mismatch fails the build here. +typedef char stMarshalCheckSprite[(sizeof(StSpriteT) == 6u && offsetof(StSpriteT, col) == 0u && offsetof(StSpriteT, msb) == 1u && offsetof(StSpriteT, row) == 2u && offsetof(StSpriteT, enable) == 3u && offsetof(StSpriteT, ptr) == 4u && offsetof(StSpriteT, color) == 5u) ? 1 : -1]; +typedef char stMarshalCheckFrame[(offsetof(StFrameT, x) == 0u && offsetof(StFrameT, y) == 16u && offsetof(StFrameT, ptr) == 24u && offsetof(StFrameT, color) == 32u && offsetof(StFrameT, enableMask) == 40u && offsetof(StFrameT, multiMask) == 41u) ? 1 : -1]; +typedef char stMarshalCheckArgs[(sizeof(void *) == 4u && offsetof(StMarshalArgsT, frame) == 4u && offsetof(StMarshalArgsT, multi) == 8u) ? 1 : -1]; +#endif + + +void stSimMarshal(StSimT *sim) { +#if defined(__W65816__) + static StMarshalArgsT args; + + args.spr = sim->spr; + args.frame = &sim->frame; + args.multi = sim->multiColorMask; + stSimMarshalIigs(&args); +#else + uint8_t i; + uint8_t enable = 0u; + + for (i = 0u; i < ST_HW_SPRITES; i++) { + sim->frame.x[i] = (uint16_t)(((uint16_t)(sim->spr[i].msb != 0u ? 1u : 0u) << 8) | sim->spr[i].col); + sim->frame.y[i] = sim->spr[i].row; + sim->frame.ptr[i] = sim->spr[i].ptr; + sim->frame.color[i] = sim->spr[i].color; + if (sim->spr[i].enable != 0u) { + enable |= kStBit[i]; + } + } + sim->frame.enableMask = enable; + sim->frame.multiMask = sim->multiColorMask; +#endif +} + + +// Every cell repaints AND the glyph index is rebuilt: for callers that +// replaced the screen bytes wholesale (a level load, the title scene). void stSimDirtyAll(StSimT *sim) { + stSimDirtyCells(sim); + // Every bulk screen write calls this, and those bypass setCell, so + // this is where the glyph reverse index gets re-derived. + glyphIndexRebuild(sim); +} + + +// Every cell repaints; the screen bytes are as they were, so the glyph +// index stands. A rebuild is ~0.15 s on the IIgs (1000 far-pointer +// list links), and the level-entry path used to pay it twice. +void stSimDirtyCells(StSimT *sim) { memset(sim->cellDirty, 1, ST_SCREEN_CELLS); sim->dirtyCount = 0u; sim->dirtyAll = true; @@ -1273,6 +1600,21 @@ uint8_t stSimRng(StSimT *sim, uint8_t n) { } +// $63D0 -- rotate every row of a glyph one pixel right and mark every +// cell showing it for repaint: the transporter hatch and the level O +// electroids both animate this way. +void stSimRotateGlyph(StSimT *sim, uint8_t ch) { + uint8_t *rows = stSimGlyphMut(sim, ch); + uint8_t row; + + for (row = 0u; row < 8u; row++) { + uint8_t b = rows[row]; + rows[row] = (uint8_t)((b >> 1) | (b << 7)); + } + stSimMarkChar(sim, ch); +} + + // The score glyphs as pennies (for the high-score table). uint32_t stSimScorePennies(const StSimT *sim) { uint32_t v = 0u; @@ -1308,6 +1650,16 @@ void stSimSetDemoStream(StSimT *sim, const uint8_t *stream, uint16_t len) { } +// $411B -- add a signed byte to a sprite's 17-bit X, carrying into the +// msb shadow. +void stSimSpriteAddX(StSimT *sim, uint8_t idx, uint8_t delta) { + uint16_t x = (uint16_t)(stSimSpriteX(sim, idx) + (uint16_t)(int16_t)(int8_t)delta); + + sim->spr[idx].col = (uint8_t)x; + sim->spr[idx].msb = (uint8_t)(x >> 8); +} + + // Bitmap for a block pointer: the standard set, or the level's own. const uint8_t *stSimSpriteBitmap(const StSimT *sim, uint8_t ptr) { uint8_t k; @@ -1326,6 +1678,12 @@ const uint8_t *stSimSpriteBitmap(const StSimT *sim, uint8_t ptr) { } +// The 17-bit VIC sprite X the shadow tables hold as msb:col. +uint16_t stSimSpriteX(const StSimT *sim, uint8_t idx) { + return (uint16_t)(((uint16_t)sim->spr[idx].msb << 8) | sim->spr[idx].col); +} + + // $5F40 -- one iteration of the main loop. StTickResultE stSimTick(StSimT *sim) { StTickResultE result = ST_TICK_CONTINUE; @@ -1350,8 +1708,8 @@ StTickResultE stSimTick(StSimT *sim) { stFarePostTickGate(sim); stFareStageDispatch(sim); stHookPerTick2(sim); - marshal(sim); - bitScrollHatch(sim); + stSimMarshal(sim); + stSimRotateGlyph(sim, ST_CHAR_TRANSPORTER); stHookPerTick3(sim); fuelBarHud(sim); if (sim->collisionPhase == 0u) { @@ -1377,3 +1735,4 @@ StTickResultE stSimTick(StSimT *sim) { passengerArrTick(sim); return ST_TICK_CONTINUE; } + diff --git a/stSim.h b/stSim.h index 718a7df..9f3f541 100644 --- a/stSim.h +++ b/stSim.h @@ -31,6 +31,9 @@ #define ST_SCREEN_COLS 40u #define ST_SCREEN_ROWS 25u #define ST_SCREEN_CELLS (ST_SCREEN_COLS * ST_SCREEN_ROWS) +// End-of-list / not-linked sentinel for the glyph reverse index below. +// ST_SCREEN_CELLS is 1000, so no real cell index can collide with it. +#define ST_GLYPH_CELL_NONE 0xFFFFu // row * 40 + col. Written as shifts (40 == 32 + 8) because the 65816 has // no multiply: with a variable row the * form called the compiler's // software multiply helper, and the collision pre-check does that a @@ -43,6 +46,9 @@ #define ST_SPRITE_Y_ORIGIN 50 #define ST_SPRITE_W 24 #define ST_SPRITE_H 21 +// A sprite row shifted onto the 8-pixel character grid has one of 8 +// phases (its left edge 0..7 pixels into a cell) -- see rowWindow. +#define ST_ROW_PHASES 8 #define ST_HW_SPRITES 8u // Fixed screen cells the game writes directly (C64 screen addresses). @@ -53,6 +59,8 @@ #define ST_CELL_SCREENS ST_CELL(23, 32) // $07B8..$07BC #define ST_CELL_SCORE ST_CELL(24, 2) // $07C2..$07C8, 7 chars #define ST_CELL_MESSAGE ST_CELL(24, 14) // $07CE, 11 chars +#define ST_MESSAGE_COL 14u +#define ST_MESSAGE_ROW 24u #define ST_CELL_PAD_DIGIT ST_CELL(24, 18) // $07D2 #define ST_CELL_FARE ST_CELL(24, 32) // $07E0..$07E6, 7 chars #define ST_NUMBER_CHARS 7u @@ -71,7 +79,57 @@ #define ST_CHAR_FUEL_EMPTY 0x7Au #define ST_CHAR_TRANSPORTER 0x67u // animated top-wall hatch #define ST_CHAR_CAB_ICON 0xC8u -#define ST_CHAR_MENU_SENTINEL 0x7Bu // $07A6 == $7B: first fuel cell half = out of fuel +#define ST_CHAR_ELECTROID 0x6Fu // level O's scrolling barrier glyph +#define ST_FARE_DEST_UP 0x0Bu // $7D8E: the fare aboard wants UP + +// ---- level-intro starfield -------------------------------------------- +// +// The C64's level intro streams seven star SPRITES out of the centre of +// the screen. That is the most expensive thing this port asks a present +// for: eight sprites are save-under drawn and restored every frame, and +// because a row's dirty band is one min..max interval, two stars far +// apart on the same row widen that row to nearly full width. Radially +// spreading stars maximise exactly that, so the warp ends up presenting +// close to the whole framebuffer every frame -- which is what makes it +// the slowest thing the IIgs does, and wasted work everywhere else. +// +// So the warp is not animated in pixels at all. The starfield is painted +// ONCE into screen RAM and animated by cycling the colours its cells are +// drawn in -- the same trick the title logo already uses +// (stRenderTitleLogo). A frame costs one palette upload and NO dirty +// pixel rows. +// +// The contract between the two halves is just this colour range: +// stTitle.c colours a star's cell ST_STAR_RING_FIRST + ring (ring 0 is +// the centre), and the renderer cycles that range. These are C64 colour +// indices the intro screen never uses -- it draws in 0, 3 and, in demo +// mode, 4 and 5; the cab sprite adds 2, 6 and 7. +#define ST_STAR_RING_FIRST 8u +#define ST_STAR_RING_COUNT 8u + +// The renderer wraps the sweep's phase with a mask rather than a +// modulo, so the ring count has to stay a power of two. Changing it to +// something else must fail to compile here instead of quietly making +// the warp skip rings. +typedef char stStarRingCountIsPow2[((ST_STAR_RING_COUNT & (ST_STAR_RING_COUNT - 1u)) == 0u) ? 1 : -1]; +// Ring 0 is the centre, which in a warp is the VANISHING POINT: those +// stars are the far ones and get the small glyph. The big glyph belongs +// to the outer rings, the stars about to rush past the viewer. +#define ST_STAR_GLYPH_NEAR 0x2Au // '*', the outer rings +#define ST_STAR_GLYPH_FAR 0x2Eu // '.', the inner rings +#define ST_STAR_FAR_RINGS 5u // rings 0..4 are still distant + +// VIC sprite block pointers the game selects by name. The cab's bit 0 is +// the landing gear, so each facing is a pair. +#define ST_SPRITE_CAB_RIGHT 0xC0u // +1 = gear down +#define ST_SPRITE_CAB_LEFT 0xDCu // +1 = gear down +#define ST_SPRITE_PASS_WALK_R 0xC2u // +walkParity +#define ST_SPRITE_PASS_WALK_L 0xC4u // +walkParity +#define ST_SPRITE_PASS_STAND 0xC7u +#define ST_SPRITE_PASS_BEAM 0xCBu // counts down to PASS_STAND +#define ST_SPRITE_PASS_GONE 0xCCu // one past PASS_BEAM: fully shrunk +#define ST_SPRITE_WRECK_FIRST 0xCCu // the wreck cel walk, FIRST..LAST-1 +#define ST_SPRITE_WRECK_LAST 0xD1u // Title logo ($4827): the logo is one character repeated over rows // 1..11, and the flip-book animates it by copying one of four frame @@ -88,6 +146,16 @@ // Distinct characters whose glyph can change in one frame before the // renderer gives up and repaints everything. #define ST_CHAR_DIRTY_MAX 8u +// Characters that may hold an edited glyph at once. The game edits six +// across the whole game (the transporter hatch $67, level O's barrier +// $6F, the title logo $84 and level W's three laser glyphs $91..$93), so +// this is generous; an overflow parks the edit in the spill entry, where +// it is simply never displayed rather than corrupting another character. +#define ST_GLYPH_OVERRIDES 16u + +typedef struct { + uint8_t rows[8]; +} StGlyphT; // Level image as loaded from a .dat (STL4). Kept in the C64's units. #define ST_MAX_PADS 10u @@ -98,6 +166,113 @@ #define ST_HOOK_BASE 0x7D98u #define ST_HOOK_BYTES (0x8000u - ST_HOOK_BASE) +// Per-level hook scratch. The original keeps a hook's running state +// inside the level's own data block, at fixed C64 addresses interleaved +// with its tables -- and, for some levels, on top of the hook's own 6502 +// code, which this port never executes. A level reads its TABLES straight +// out of the shipped blob (the single source of truth) and keeps only the +// bytes it WRITES here. Seeded from +// the blob at scene load, so the first tick starts where the original's +// did. Every level is converted, so no C64-addressed working copy of the +// blob exists any more. +#define ST_HOOK_H_SEGS 11u // level H segment ids 0..10 +#define ST_HOOK_W_LASERS 8u + +typedef struct { + uint8_t segFlag[ST_HOOK_H_SEGS]; // $7E15..$7E1F wall segment up/down + uint8_t switchId; // $7E4C also read back as a table entry + uint8_t tick; // $7E53 + uint8_t active; // $7E54 switch being animated + uint8_t step; // $7E55 0..3 of the animation + uint8_t tone; // $7E56 rising chime +} StHookHT; + +typedef struct { + uint8_t celPhase; // $7DD2 orb cel cycle 0..5 + uint8_t tick; // $7E00 + uint8_t lastSpot; // $7DD1 landing spot used last, 1..5 + uint8_t hop; // $7DD3 ticks left in a teleport + uint8_t sweep; // $7F02 the hop's falling tone +} StHookGT; + +typedef struct { + uint8_t state[ST_HW_SPRITES]; // $7DB8 0 idle, 1 rising, 2 burst + uint8_t phase[ST_HW_SPRITES]; // $7DC0 cel phase + uint8_t dx[ST_HW_SPRITES]; // $7DC8 + uint8_t dy[ST_HW_SPRITES]; // $7DD0 +} StHookIT; + +typedef struct { + uint8_t parity; // $7DBD magnet pole flip +} StHookKT; + +typedef struct { + uint8_t tick; // $7DBD mod-8 scroll gate +} StHookOT; + +typedef struct { + uint8_t phase[ST_HW_SPRITES]; // $7E83 interference cel phase +} StHookQT; + +typedef struct { + uint8_t gateShut; // $7D9C +} StHookTT; + +typedef struct { + uint8_t colorPhase; // $7DAB + uint8_t state[ST_HOOK_W_LASERS]; // $7DAC 0 idle, 1 extending, 2 fading + uint8_t row[ST_HOOK_W_LASERS]; // $7DB5 beam head / fade phase + uint8_t started; // $7DE5 +} StHookWT; + +typedef struct { + uint8_t celPhase[ST_HW_SPRITES]; // $7F4F + uint8_t state[ST_HW_SPRITES]; // $7F57 0 idle, 1 active + uint8_t dir[ST_HW_SPRITES]; // $7F5F entry edge 0..3 + uint8_t life[ST_HW_SPRITES]; // $7F6E + uint8_t cooldown; // $7F6D ticks before the next bounce +} StHookUT; + +typedef struct { + uint8_t col; // $7D9C + uint8_t dir; // $7D9D + uint8_t tick; // $7D9E +} StHookXT; + +typedef struct { + uint8_t row; // $7D98 row the leaves grow along + uint8_t seg; // $7D99 leaves grown this cycle 0..4 + uint8_t tick; // $7D9A ticks toward the next leaf + uint8_t padSlot; // $7D9B number of the next pad to hang +} StHookET; + +typedef struct { + uint8_t state[ST_HW_SPRITES]; // $7EDB 0 idle, 1 falling, 2 bursting + uint8_t phase[ST_HW_SPRITES]; // $7ED3 burst cel phase +} StHookJT; + +typedef struct { + uint8_t state[ST_HW_SPRITES]; // $7F16 0 idle, 1 falling, 2 melting + uint8_t phase[ST_HW_SPRITES]; // $7F0E melt cel phase + uint8_t parity; // $7F1F a melt advances every other count +} StHookPT; + +typedef union { + StHookET e; + StHookGT g; + StHookHT h; + StHookIT i; + StHookJT j; + StHookKT k; + StHookOT o; + StHookPT p; + StHookQT q; + StHookTT t; + StHookUT u; + StHookWT w; + StHookXT x; +} StHookScratchT; + typedef struct { uint8_t x1Hi; // slot byte 0: landing X lower bound, high byte uint8_t x1Lo; // slot byte 1 @@ -128,7 +303,7 @@ typedef struct { StPadT pads[ST_MAX_PADS]; // $7D0A.. uint8_t screen[ST_SCREEN_CELLS]; // decompressed screen RAM uint8_t color[ST_SCREEN_CELLS]; // decompressed colour RAM - uint8_t levelIndex; // 0..23 = A..X, 24 = title + uint8_t levelIndex; // 0..23 = A..X, 24 = Y, 25 = title uint8_t spriteCount; StLevelSpriteT sprites[ST_MAX_LEVEL_SPRITES]; uint8_t hookData[ST_HOOK_BYTES]; // $7D98..$7FFF as shipped @@ -200,7 +375,34 @@ typedef struct { uint16_t dirtyList[512]; // cells marked since the last frame uint16_t dirtyCount; bool dirtyAll; // list overflowed / whole screen - uint8_t charset[ST_CHARSET_CHARS][8]; // $2800 working copy + // Reverse index: for each glyph, the exact set of cells showing it, + // as doubly-linked lists threaded through the cell array. + // + // The renderer has to repaint every cell showing a glyph whose + // BITMAP changed -- a chunky framebuffer bakes the pixels in at + // paste time, so unlike the C64's character mode there is no free + // re-composite. It used to find those cells by scanning a per-glyph + // [first, last] span, which PC profiling showed was the single + // hottest loop in the program (22% of all samples on level H). This + // gives the exact set instead: no scan, cost proportional to the + // cells that really show the glyph. + // + // screen[] is the only truth and these mirror it: setCell keeps them + // in step one cell at a time, and a bulk screen write re-derives + // them via stSimDirtyAll, which every bulk writer already calls. + // They live in the NOT-cleared block for the same reason screen[] + // does -- stSimNewGame must leave the live picture alone. + uint16_t glyphHead[ST_CHARSET_CHARS]; // ST_GLYPH_CELL_NONE = none + uint16_t cellNext[ST_SCREEN_CELLS]; + uint16_t cellPrev[ST_SCREEN_CELLS]; + // $2800. The charset is NOT copied: the ROM set is const and only the + // handful of glyphs the game rewrites at runtime are stored here, so + // this costs ~400 bytes instead of 2 KB and a scene load clears an + // index rather than copying 2 KB. Reach glyphs through stSimGlyph / + // stSimGlyphRow / stSimGlyphMut, never by indexing directly. + uint8_t glyphSlot[ST_CHARSET_CHARS]; // 0 = ROM glyph, else slot + 1 + StGlyphT glyphOverride[ST_GLYPH_OVERRIDES + 1u]; // last = spill + uint8_t glyphOverrideCount; // Characters whose glyph bits the game has edited since the last // frame -- the transporter hatch, the title logo flip, the laser // beams. A short list, not a 256-entry flag array: the renderer had @@ -221,6 +423,8 @@ typedef struct { // ---- level image (mutable copies: hooks edit the pad table) ---- const StLevelT *level; StPadT pads[ST_MAX_PADS]; // $7D0A working copy + uint8_t padCount; // $7D5A working copy (level E opens pads) + uint8_t specialPad; // $7D09 working copy // ---- input ---- uint8_t inputMask; // $7169 EOR $FF of $DC00: 1 = pressed @@ -255,12 +459,25 @@ typedef struct { uint8_t hitDispatchResult; // $721D uint8_t spriteSpriteColl; // $71CA latched $D01E uint8_t spriteBgColl; // $71CB latched $D01F - // Per-sprite 24-bit row masks of the displayed frame, rebuilt only - // when the sprite's bitmap or multicolour mode changes (derived - // state, not C64 memory). - uint32_t collMask[ST_HW_SPRITES][ST_SPRITE_H]; + // Sprites besides the cab whose $D01F bit a hook reads (levels J + // and P watch their hazards hit the scenery); the rest are never + // tested (derived state, not C64 memory). + uint8_t bgCollSprites; + // Per-sprite row masks of the displayed frame (3 bytes per row, the + // leftmost pixel is bit 7 of byte 0), rebuilt only when the sprite's + // bitmap or multicolour mode changes (derived state, not C64 memory). + uint8_t collMask[ST_HW_SPRITES][ST_SPRITE_H][3]; const uint8_t *collMaskBm[ST_HW_SPRITES]; uint8_t collMaskMulti[ST_HW_SPRITES]; + // First and last rows of collMask[idx] with any pixel (first == + // ST_SPRITE_H when the mask is empty): the row tests walk only these. + uint8_t collRowFirst[ST_HW_SPRITES]; + uint8_t collRowLast[ST_HW_SPRITES]; + // The cab's rows pre-shifted to each grid phase (cabWindows), built + // per phase on first use; bit s of cabWinValid says cabWin[s] + // matches collMask[0]. + uint8_t cabWin[ST_ROW_PHASES][ST_SPRITE_H][4]; + uint8_t cabWinValid; // ---- fuel ---- uint8_t fuelCountdown; // $71C7 @@ -300,6 +517,12 @@ typedef struct { uint8_t levelState; // $7215 screens completed uint8_t demoMode; // $721C uint8_t postMortem; // $5E9D + // Jingles. The C64 plays its tunes BLOCKING ($44CF spins until the + // song ends), so the sim only RAISES a request here and the host + // plays it while holding the sim frozen -- tick-exactness survives + // because no tick happens while the tune sounds. + uint8_t jingleRequest; // song number + 1 to play now; 0 = none + uint8_t jingleRotate; // $5E9B: start-of-screen tune = ++this & 3 // ---- demo playback ($48F2): the $0902 buffer a recording is // loaded into; a shorter recording leaves the previous tail behind @@ -318,8 +541,6 @@ typedef struct { uint8_t logoColorIdx; // $48A4 uint8_t logoCycleAux; // $48A5 uint8_t logoDiv; // $48A6 - uint8_t starTimer[7]; // $44F8 - uint8_t starReload[7]; // $4500 uint8_t introLoopCount; // $4508 uint8_t introCabDx; // $450A uint8_t introCabDxTimer; // $450B @@ -327,7 +548,7 @@ typedef struct { uint8_t spriteMc1; // $D026 // ---- per-level hook scratch ($7D9C.. and friends) ---- - uint8_t hookRam[ST_HOOK_BYTES]; // $7D98..$7FFF working copy + StHookScratchT hookScratch; // per-level hook state uint8_t hookLevel; // level index the hook state belongs to uint8_t warpCounter; // $5A66 uint8_t warpMask; // $5CE8 @@ -357,6 +578,29 @@ bool stSimAdvancePlayer(StSimT *sim); void stSimArchiveHud(StSimT *sim); // Mark every cell dirty (scene change). void stSimDirtyAll(StSimT *sim); +void stSimDirtyCells(StSimT *sim); // repaint all, glyph index kept +// $43A5 -- blank the fare meter back to its template. +void stSimHudInit(StSimT *sim); +// $4253 + $4293 -- snapshot the sprite shadows into the displayed frame. +void stSimMarshal(StSimT *sim); +// $63D0 -- rotate every row of a glyph one pixel right and mark it dirty. +void stSimRotateGlyph(StSimT *sim, uint8_t ch); +// A character's eight 1bpp rows: the ROM glyph, or the game's edit of it. +// kStBit[i] == 1 << i. A variable shift is a library bit loop on the +// 65816, so sprite-index bit tests go through this table instead. +extern const uint8_t kStBit[ST_HW_SPRITES]; + +const uint8_t *stSimGlyph(const StSimT *sim, uint8_t ch); +uint8_t stSimGlyphRow(const StSimT *sim, uint8_t ch, uint8_t row); +// The same eight rows, writable (copied from ROM on the first edit). +uint8_t *stSimGlyphMut(StSimT *sim, uint8_t ch); +// Drop every edit: the whole charset is the ROM set again (scene load). +void stSimGlyphReset(StSimT *sim); +// The 17-bit VIC sprite X (msb:col), read and signed-byte add ($411B). +uint16_t stSimSpriteX(const StSimT *sim, uint8_t idx); +void stSimSpriteAddX(StSimT *sim, uint8_t idx, uint8_t delta); +// $6C38 -- blank the 11-character message row. +void stSimClearMessage(StSimT *sim); // A glyph's bits changed: every cell showing it has to be repainted. void stSimMarkChar(StSimT *sim, uint8_t ch); // Text and HUD primitives (screen RAM writes). @@ -404,13 +648,19 @@ void stHookPerTick2(StSimT *sim); // $7D6C void stHookPerTick3(StSimT *sim); // $7D6F uint8_t stHookInput(StSimT *sim, uint8_t input); // $7D72 uint8_t stHookHitVerdict(StSimT *sim); // $7D75: 0 = passenger contact is safe +// Seed a converted level's scratch from the shipped blob (scene load). +void stHookSceneLoad(StSimT *sim); // ---- audio sink (host provides; stAudio.c / stsim stub) ---- void stAudioSfx(const uint8_t *program9); // $42E9 load void stAudioNoise(bool on); // $D412 = $81 / $80 void stAudioThrustSweep(uint8_t value); // $D400/$D401 = value void stAudioSpeech(uint8_t ch); // $9802 +void stAudioSpeechPhrase(const uint8_t *speech); void stAudioSilence(void); // voices 1+2 gate off +void stAudioJingle(uint8_t song); // $44CF: silence, then play song (caller blocks) +bool stAudioJingleActive(void); // still sounding? +void stAudioMusic(uint8_t song, bool loop); // $CB02: silence, then play song in the background void stAudioVoice2(uint8_t freqLo, uint8_t freqHi, uint8_t ctrl); // level-hook tones void stAudioVoice1Freq(uint8_t value); // $D400/$D401 pokes from the fuel pump diff --git a/stSongData.h b/stSongData.h new file mode 100644 index 0000000..f88621e --- /dev/null +++ b/stSongData.h @@ -0,0 +1,512 @@ +// Space Taxi (C64) tunes as JoeyLib JYM1 streams. GENERATED by +// assets/genSongData.py -- do not hand-edit. The songs were extracted by +// running the game's own music player under a 6502 emulator +// (assets/sidCapture.py logs every SID write per PAL frame; +// assets/sidToSong.py turns that into songbake text in assets/songs/). +// +// Where the C64 plays them: +// BLOCKING via $44CF (the game spins until the tune ends; the port +// parks in ST_STATE_JINGLE with the sim frozen): +// 0..3 start of every screen, rotating on $5E9B ($6906) +// 4 level 25 start ($6935) and the bonus cab ($70B1) +// 5 game over ($5C1F), after the two-second GAME OVER! wait +// NON-BLOCKING via $CB02 (started and left to the IRQ player): +// 6 the high-score name entry screen ($4E35) -- no port screen yet +// 7 the high-score table ($4C1F, joystick UP on the title) +// 8 the level intro while the cab flies in ($459C); endless, so +// the source is trimmed to 30 s and looped +#ifndef ST_SONG_DATA_H +#define ST_SONG_DATA_H + +#include + +static const uint8_t gStSong0[109] = { + 0x4A, 0x59, 0x4D, 0x31, 0x14, 0x00, 0xFF, 0xFF, 0x81, 0x10, 0x50, 0x01, + 0x02, 0x11, 0xA8, 0x01, 0x02, 0x83, 0x20, 0x21, 0x85, 0x10, 0x7A, 0x01, + 0x02, 0x11, 0xA8, 0x01, 0x02, 0x83, 0x20, 0x21, 0x85, 0x10, 0x50, 0x01, + 0x02, 0x11, 0xA8, 0x01, 0x02, 0x83, 0x20, 0x21, 0x85, 0x10, 0x7A, 0x01, + 0x02, 0x11, 0xDC, 0x01, 0x02, 0x83, 0x20, 0x21, 0x85, 0x10, 0x3E, 0x01, + 0x02, 0x11, 0xFC, 0x00, 0x02, 0x83, 0x21, 0x85, 0x11, 0xDC, 0x01, 0x02, + 0x83, 0x20, 0x21, 0x85, 0x10, 0x1B, 0x01, 0x02, 0x11, 0xFC, 0x00, 0x02, + 0x83, 0x21, 0x85, 0x11, 0xDC, 0x01, 0x02, 0x83, 0x20, 0x21, 0x85, 0x10, + 0xFC, 0x00, 0x02, 0x11, 0xFC, 0x00, 0x02, 0x8B, 0x21, 0x88, 0x20, 0x86, + 0x00, +}; + +static const uint8_t gStSong1[119] = { + 0x4A, 0x59, 0x4D, 0x31, 0x14, 0x00, 0xFF, 0xFF, 0x81, 0x10, 0xFC, 0x00, + 0x07, 0x11, 0x9F, 0x00, 0x07, 0x87, 0x20, 0x21, 0x81, 0x10, 0x1B, 0x01, + 0x07, 0x11, 0xA8, 0x00, 0x07, 0x87, 0x20, 0x21, 0x81, 0x10, 0xFC, 0x00, + 0x07, 0x11, 0xBD, 0x00, 0x07, 0x87, 0x20, 0x21, 0x81, 0x10, 0x1B, 0x01, + 0x07, 0x11, 0xD4, 0x00, 0x07, 0x87, 0x20, 0x21, 0x81, 0x10, 0x3E, 0x01, + 0x07, 0x11, 0xBD, 0x00, 0x07, 0x87, 0x20, 0x21, 0x81, 0x10, 0x50, 0x01, + 0x07, 0x11, 0xA8, 0x00, 0x07, 0x87, 0x20, 0x21, 0x81, 0x10, 0x3E, 0x01, + 0x07, 0x11, 0x9F, 0x00, 0x07, 0x87, 0x20, 0x21, 0x81, 0x10, 0x50, 0x01, + 0x07, 0x11, 0xA8, 0x00, 0x07, 0x87, 0x20, 0x21, 0x81, 0x10, 0x7A, 0x01, + 0x07, 0x11, 0xBD, 0x00, 0x07, 0x87, 0x21, 0x90, 0x20, 0x82, 0x00, +}; + +static const uint8_t gStSong2[128] = { + 0x4A, 0x59, 0x4D, 0x31, 0x14, 0x00, 0xFF, 0xFF, 0x81, 0x10, 0x7E, 0x00, + 0x00, 0x11, 0x9F, 0x00, 0x00, 0x85, 0x21, 0x82, 0x20, 0x81, 0x10, 0x7E, + 0x00, 0x00, 0x11, 0xA8, 0x00, 0x00, 0x85, 0x21, 0x82, 0x20, 0x81, 0x10, + 0x8D, 0x00, 0x00, 0x11, 0xBD, 0x00, 0x00, 0x85, 0x21, 0x82, 0x20, 0x81, + 0x10, 0x9F, 0x00, 0x00, 0x11, 0x9F, 0x00, 0x00, 0x85, 0x21, 0x82, 0x20, + 0x81, 0x10, 0x9F, 0x00, 0x00, 0x11, 0xA8, 0x00, 0x00, 0x85, 0x21, 0x82, + 0x20, 0x81, 0x10, 0xA8, 0x00, 0x00, 0x11, 0xBD, 0x00, 0x00, 0x85, 0x21, + 0x82, 0x20, 0x81, 0x10, 0xA8, 0x00, 0x00, 0x11, 0x9F, 0x00, 0x00, 0x85, + 0x21, 0x82, 0x20, 0x81, 0x10, 0xBD, 0x00, 0x00, 0x11, 0xA8, 0x00, 0x00, + 0x85, 0x21, 0x82, 0x20, 0x81, 0x11, 0x9F, 0x00, 0x00, 0x85, 0x21, 0x83, + 0x11, 0x7E, 0x00, 0x00, 0x85, 0x21, 0x84, 0x00, +}; + +static const uint8_t gStSong3[154] = { + 0x4A, 0x59, 0x4D, 0x31, 0x14, 0x00, 0xFF, 0xFF, 0x81, 0x10, 0xFC, 0x00, + 0x00, 0x11, 0x50, 0x01, 0x02, 0x12, 0x50, 0x01, 0x02, 0x83, 0x20, 0x21, + 0x22, 0x82, 0x10, 0x1B, 0x01, 0x00, 0x83, 0x20, 0x82, 0x10, 0x3E, 0x01, + 0x00, 0x11, 0x50, 0x01, 0x02, 0x12, 0x50, 0x01, 0x02, 0x83, 0x20, 0x21, + 0x22, 0x82, 0x10, 0xFC, 0x00, 0x00, 0x83, 0x20, 0x82, 0x10, 0x1B, 0x01, + 0x00, 0x11, 0x50, 0x01, 0x02, 0x12, 0x50, 0x01, 0x02, 0x83, 0x20, 0x21, + 0x22, 0x82, 0x10, 0xFC, 0x00, 0x00, 0x83, 0x20, 0x82, 0x10, 0x1B, 0x01, + 0x00, 0x11, 0x50, 0x01, 0x02, 0x12, 0x50, 0x01, 0x02, 0x83, 0x20, 0x21, + 0x22, 0x82, 0x10, 0xDC, 0x01, 0x00, 0x83, 0x20, 0x82, 0x11, 0x3E, 0x01, + 0x02, 0x12, 0x7A, 0x01, 0x02, 0x83, 0x21, 0x22, 0x87, 0x11, 0x50, 0x01, + 0x02, 0x12, 0x7A, 0x01, 0x02, 0x83, 0x21, 0x22, 0x87, 0x11, 0x1B, 0x01, + 0x02, 0x12, 0xFC, 0x00, 0x02, 0x83, 0x21, 0x22, 0x87, 0x11, 0xFC, 0x00, + 0x02, 0x12, 0xDC, 0x01, 0x02, 0x83, 0x21, 0x22, 0x88, 0x00, +}; + +static const uint8_t gStSong4[92] = { + 0x4A, 0x59, 0x4D, 0x31, 0x14, 0x00, 0xFF, 0xFF, 0x81, 0x10, 0xF8, 0x01, + 0x05, 0x11, 0xC1, 0x01, 0x05, 0x87, 0x20, 0x21, 0x89, 0x10, 0xF8, 0x01, + 0x05, 0x11, 0xC1, 0x01, 0x05, 0x87, 0x20, 0x21, 0x81, 0x10, 0xA1, 0x02, + 0x05, 0x11, 0xF8, 0x01, 0x05, 0x97, 0x20, 0x21, 0x99, 0x10, 0xF8, 0x01, + 0x05, 0x11, 0xA8, 0x01, 0x05, 0x87, 0x20, 0x21, 0x89, 0x10, 0xA1, 0x02, + 0x05, 0x11, 0xF8, 0x01, 0x05, 0x87, 0x20, 0x21, 0x81, 0x10, 0x50, 0x03, + 0x05, 0x11, 0xA1, 0x02, 0x05, 0x88, 0x11, 0x7B, 0x02, 0x05, 0x88, 0x11, + 0xA1, 0x02, 0x05, 0x87, 0x20, 0x21, 0x82, 0x00, +}; + +static const uint8_t gStSong5[890] = { + 0x4A, 0x59, 0x4D, 0x31, 0x14, 0x00, 0xFF, 0xFF, 0x81, 0x10, 0xF8, 0x01, + 0x02, 0x11, 0x9F, 0x00, 0x02, 0x12, 0x6A, 0x00, 0x0A, 0x83, 0x21, 0x82, + 0x12, 0xD4, 0x00, 0x0A, 0x85, 0x11, 0x9F, 0x00, 0x02, 0x12, 0x6A, 0x00, + 0x0A, 0x83, 0x21, 0x82, 0x12, 0xD4, 0x00, 0x0A, 0x85, 0x11, 0x9F, 0x00, + 0x02, 0x12, 0x6A, 0x00, 0x0A, 0x83, 0x21, 0x82, 0x12, 0xD4, 0x00, 0x0A, + 0x85, 0x11, 0x9F, 0x00, 0x02, 0x12, 0x6A, 0x00, 0x0A, 0x83, 0x21, 0x81, + 0x20, 0x81, 0x12, 0xD4, 0x00, 0x0A, 0x85, 0x10, 0xF8, 0x01, 0x02, 0x11, + 0x9F, 0x00, 0x02, 0x12, 0x6A, 0x00, 0x0A, 0x83, 0x21, 0x82, 0x12, 0xD4, + 0x00, 0x0A, 0x85, 0x11, 0x9F, 0x00, 0x02, 0x12, 0x6A, 0x00, 0x0A, 0x83, + 0x21, 0x82, 0x12, 0xD4, 0x00, 0x0A, 0x85, 0x11, 0x9F, 0x00, 0x02, 0x12, + 0x6A, 0x00, 0x0A, 0x83, 0x21, 0x82, 0x12, 0xD4, 0x00, 0x0A, 0x85, 0x11, + 0x9F, 0x00, 0x02, 0x12, 0x6A, 0x00, 0x0A, 0x83, 0x21, 0x81, 0x20, 0x81, + 0x12, 0xD4, 0x00, 0x0A, 0x85, 0x10, 0xF8, 0x01, 0x02, 0x11, 0x9F, 0x00, + 0x02, 0x12, 0x6A, 0x00, 0x0A, 0x83, 0x21, 0x82, 0x12, 0xD4, 0x00, 0x0A, + 0x85, 0x11, 0x9F, 0x00, 0x02, 0x12, 0x6A, 0x00, 0x0A, 0x83, 0x21, 0x82, + 0x12, 0xD4, 0x00, 0x0A, 0x85, 0x11, 0x9F, 0x00, 0x02, 0x12, 0x6A, 0x00, + 0x0A, 0x83, 0x21, 0x81, 0x20, 0x81, 0x12, 0xD4, 0x00, 0x0A, 0x83, 0x22, + 0x82, 0x10, 0x36, 0x02, 0x02, 0x11, 0x8D, 0x00, 0x02, 0x12, 0x64, 0x00, + 0x0A, 0x83, 0x21, 0x81, 0x20, 0x81, 0x12, 0xC8, 0x00, 0x0A, 0x85, 0x10, + 0x36, 0x02, 0x02, 0x11, 0x8D, 0x00, 0x02, 0x12, 0x64, 0x00, 0x0A, 0x83, + 0x21, 0x82, 0x12, 0xC8, 0x00, 0x0A, 0x85, 0x11, 0x8D, 0x00, 0x02, 0x12, + 0x64, 0x00, 0x0A, 0x83, 0x21, 0x82, 0x12, 0xC8, 0x00, 0x0A, 0x85, 0x11, + 0x8D, 0x00, 0x02, 0x12, 0x64, 0x00, 0x0A, 0x83, 0x21, 0x82, 0x12, 0xC8, + 0x00, 0x0A, 0x83, 0x22, 0x82, 0x11, 0x8D, 0x00, 0x02, 0x12, 0x64, 0x00, + 0x0A, 0x83, 0x21, 0x81, 0x20, 0x81, 0x12, 0xC8, 0x00, 0x0A, 0x85, 0x10, + 0x7B, 0x02, 0x02, 0x11, 0xF8, 0x01, 0x02, 0x12, 0x5E, 0x00, 0x0A, 0x84, + 0x20, 0x21, 0x81, 0x12, 0xBD, 0x00, 0x0A, 0x85, 0x12, 0x5E, 0x00, 0x0A, + 0x85, 0x12, 0xBD, 0x00, 0x0A, 0x85, 0x10, 0xA1, 0x02, 0x02, 0x11, 0x36, + 0x02, 0x02, 0x12, 0x54, 0x00, 0x0A, 0x84, 0x20, 0x21, 0x81, 0x12, 0xA8, + 0x00, 0x0A, 0x85, 0x12, 0x54, 0x00, 0x0A, 0x85, 0x12, 0xA8, 0x00, 0x0A, + 0x85, 0x10, 0xF3, 0x02, 0x02, 0x11, 0x7B, 0x02, 0x02, 0x12, 0x4F, 0x00, + 0x0A, 0x84, 0x20, 0x21, 0x81, 0x12, 0x9F, 0x00, 0x0A, 0x85, 0x12, 0x4F, + 0x00, 0x0A, 0x85, 0x12, 0x9F, 0x00, 0x0A, 0x85, 0x10, 0x20, 0x03, 0x02, + 0x11, 0x7B, 0x02, 0x02, 0x12, 0x47, 0x00, 0x0A, 0x84, 0x20, 0x21, 0x81, + 0x12, 0x8D, 0x00, 0x0A, 0x85, 0x12, 0x47, 0x00, 0x0A, 0x85, 0x12, 0x8D, + 0x00, 0x0A, 0x85, 0x10, 0x50, 0x03, 0x02, 0x11, 0x7B, 0x02, 0x02, 0x12, + 0x43, 0x00, 0x0A, 0x85, 0x12, 0x85, 0x00, 0x0A, 0x85, 0x12, 0x43, 0x00, + 0x0A, 0x85, 0x12, 0x85, 0x00, 0x0A, 0x85, 0x12, 0x43, 0x00, 0x0A, 0x85, + 0x12, 0x85, 0x00, 0x0A, 0x85, 0x12, 0x43, 0x00, 0x0A, 0x85, 0x12, 0x85, + 0x00, 0x0A, 0x85, 0x10, 0xA8, 0x01, 0x02, 0x11, 0x3E, 0x01, 0x02, 0x12, + 0x43, 0x00, 0x0A, 0x84, 0x20, 0x21, 0x81, 0x12, 0x85, 0x00, 0x0A, 0x85, + 0x22, 0x9E, 0x10, 0x50, 0x03, 0x02, 0x11, 0x50, 0x01, 0x02, 0x12, 0xFC, + 0x00, 0x0A, 0xA2, 0x20, 0x21, 0x84, 0x22, 0x82, 0x10, 0x50, 0x03, 0x02, + 0x11, 0x50, 0x01, 0x02, 0x12, 0xFC, 0x00, 0x0A, 0xA2, 0x20, 0x21, 0x84, + 0x22, 0x82, 0x10, 0x50, 0x03, 0x02, 0x11, 0x50, 0x01, 0x02, 0x12, 0xFC, + 0x00, 0x0A, 0x98, 0x20, 0x21, 0x84, 0x22, 0x82, 0x10, 0xF3, 0x02, 0x02, + 0x11, 0x3E, 0x01, 0x02, 0x12, 0xE1, 0x00, 0x0A, 0x84, 0x20, 0x21, 0x84, + 0x22, 0x82, 0x10, 0xF3, 0x02, 0x02, 0x11, 0x3E, 0x01, 0x02, 0x12, 0xE1, + 0x00, 0x0A, 0xA2, 0x20, 0x21, 0x84, 0x22, 0x82, 0x10, 0xA1, 0x02, 0x02, + 0x11, 0x1B, 0x01, 0x02, 0x12, 0xD4, 0x00, 0x0A, 0xA2, 0x21, 0x84, 0x22, + 0x82, 0x10, 0x7B, 0x02, 0x02, 0x11, 0xFC, 0x00, 0x02, 0x12, 0xBD, 0x00, + 0x0A, 0x84, 0x20, 0x21, 0x84, 0x22, 0x8C, 0x10, 0x36, 0x02, 0x02, 0x11, + 0x1B, 0x01, 0x02, 0x12, 0xA8, 0x00, 0x0A, 0x84, 0x20, 0x21, 0x84, 0x22, + 0x8C, 0x10, 0xF3, 0x02, 0x02, 0x11, 0x3E, 0x01, 0x02, 0x12, 0x7E, 0x00, + 0x0A, 0x85, 0x12, 0xBD, 0x00, 0x0A, 0x85, 0x12, 0x7E, 0x00, 0x0A, 0x85, + 0x12, 0xBD, 0x00, 0x0A, 0x85, 0x12, 0x7E, 0x00, 0x0A, 0x85, 0x12, 0xBD, + 0x00, 0x0A, 0x85, 0x12, 0x7E, 0x00, 0x0A, 0x85, 0x12, 0xBD, 0x00, 0x0A, + 0x85, 0x12, 0x7E, 0x00, 0x0A, 0x84, 0x20, 0x81, 0x12, 0xBD, 0x00, 0x0A, + 0x85, 0x10, 0x7B, 0x02, 0x02, 0x12, 0x7E, 0x00, 0x0A, 0x84, 0x20, 0x81, + 0x12, 0xBD, 0x00, 0x0A, 0x85, 0x10, 0xF8, 0x01, 0x02, 0x12, 0x7E, 0x00, + 0x0A, 0x84, 0x20, 0x81, 0x12, 0xBD, 0x00, 0x0A, 0x85, 0x10, 0x7B, 0x02, + 0x02, 0x12, 0x7E, 0x00, 0x0A, 0x84, 0x20, 0x21, 0x81, 0x12, 0xBD, 0x00, + 0x0A, 0x83, 0x22, 0x82, 0x10, 0xF3, 0x02, 0x02, 0x11, 0x1B, 0x01, 0x02, + 0x12, 0x77, 0x00, 0x0A, 0x85, 0x12, 0xBD, 0x00, 0x0A, 0x85, 0x12, 0x77, + 0x00, 0x0A, 0x84, 0x20, 0x81, 0x12, 0xBD, 0x00, 0x0A, 0x85, 0x10, 0x50, + 0x03, 0x02, 0x12, 0x77, 0x00, 0x0A, 0x85, 0x12, 0xBD, 0x00, 0x0A, 0x85, + 0x12, 0x77, 0x00, 0x0A, 0x84, 0x20, 0x81, 0x12, 0xBD, 0x00, 0x0A, 0x85, + 0x10, 0xF3, 0x02, 0x02, 0x12, 0x77, 0x00, 0x0A, 0x84, 0x20, 0x81, 0x12, + 0xBD, 0x00, 0x0A, 0x85, 0x10, 0xA1, 0x02, 0x02, 0x12, 0x77, 0x00, 0x0A, + 0x84, 0x20, 0x81, 0x12, 0xBD, 0x00, 0x0A, 0x85, 0x10, 0x7B, 0x02, 0x02, + 0x12, 0x77, 0x00, 0x0A, 0x84, 0x20, 0x81, 0x12, 0xBD, 0x00, 0x0A, 0x85, + 0x10, 0x36, 0x02, 0x02, 0x12, 0x77, 0x00, 0x0A, 0x84, 0x20, 0x21, 0x81, + 0x12, 0xBD, 0x00, 0x0A, 0x83, 0x22, 0x82, 0x10, 0xF8, 0x01, 0x02, 0x11, + 0x3E, 0x01, 0x02, 0x12, 0x7E, 0x00, 0x0A, 0x84, 0x20, 0x21, 0x84, 0x22, + 0x83, 0x00, +}; + +static const uint8_t gStSong6[611] = { + 0x4A, 0x59, 0x4D, 0x31, 0x14, 0x00, 0xFF, 0xFF, 0x81, 0x11, 0x7A, 0x01, + 0x01, 0x12, 0xF8, 0x01, 0x01, 0x88, 0x21, 0x22, 0x90, 0x11, 0x7A, 0x01, + 0x01, 0x12, 0xF8, 0x01, 0x01, 0x82, 0x21, 0x22, 0x84, 0x11, 0x7A, 0x01, + 0x01, 0x12, 0xF8, 0x01, 0x01, 0x82, 0x21, 0x22, 0x84, 0x11, 0x7A, 0x01, + 0x01, 0x12, 0xF8, 0x01, 0x01, 0xA0, 0x21, 0x22, 0x84, 0x10, 0x5E, 0x00, + 0x00, 0x86, 0x20, 0x86, 0x10, 0x7E, 0x00, 0x00, 0x86, 0x20, 0x86, 0x10, + 0x9F, 0x00, 0x00, 0x86, 0x20, 0x86, 0x10, 0xBD, 0x00, 0x00, 0x11, 0x50, + 0x01, 0x01, 0x12, 0xC1, 0x01, 0x01, 0x88, 0x21, 0x22, 0x84, 0x11, 0x2C, + 0x01, 0x01, 0x12, 0x90, 0x01, 0x01, 0x88, 0x21, 0x22, 0x84, 0x11, 0x50, + 0x01, 0x01, 0x12, 0xC1, 0x01, 0x01, 0x88, 0x21, 0x22, 0x84, 0x11, 0x7A, + 0x01, 0x01, 0x12, 0xF8, 0x01, 0x01, 0x88, 0x21, 0x22, 0x90, 0x11, 0x7A, + 0x01, 0x01, 0x12, 0xF8, 0x01, 0x01, 0x82, 0x21, 0x22, 0x84, 0x20, 0x11, + 0x7A, 0x01, 0x01, 0x12, 0xF8, 0x01, 0x01, 0x82, 0x21, 0x22, 0x84, 0x11, + 0x7A, 0x01, 0x01, 0x12, 0xF8, 0x01, 0x01, 0x92, 0x10, 0xD4, 0x00, 0x00, + 0x83, 0x20, 0x8B, 0x21, 0x22, 0x84, 0x10, 0xBD, 0x00, 0x00, 0x83, 0x20, + 0x8F, 0x10, 0x9F, 0x00, 0x00, 0x83, 0x20, 0x83, 0x11, 0x7A, 0x01, 0x01, + 0x12, 0xF8, 0x01, 0x01, 0x82, 0x21, 0x22, 0x84, 0x11, 0x7A, 0x01, 0x01, + 0x12, 0xF8, 0x01, 0x01, 0x82, 0x21, 0x22, 0x84, 0x10, 0x8D, 0x00, 0x00, + 0x11, 0xA8, 0x01, 0x01, 0x12, 0x36, 0x02, 0x01, 0x88, 0x21, 0x22, 0x84, + 0x11, 0x7A, 0x01, 0x01, 0x12, 0xF8, 0x01, 0x01, 0x88, 0x21, 0x22, 0x84, + 0x11, 0xA8, 0x01, 0x01, 0x12, 0x36, 0x02, 0x01, 0x86, 0x20, 0x82, 0x21, + 0x22, 0x84, 0x10, 0x77, 0x00, 0x00, 0x11, 0xDC, 0x01, 0x01, 0x12, 0x7B, + 0x02, 0x01, 0x88, 0x21, 0x22, 0x90, 0x11, 0xDC, 0x01, 0x01, 0x12, 0x7B, + 0x02, 0x01, 0x82, 0x21, 0x22, 0x84, 0x11, 0xDC, 0x01, 0x01, 0x12, 0x7B, + 0x02, 0x01, 0x82, 0x21, 0x22, 0x84, 0x11, 0xDC, 0x01, 0x01, 0x12, 0x7B, + 0x02, 0x01, 0x92, 0x20, 0x8E, 0x21, 0x22, 0x84, 0x10, 0x3B, 0x00, 0x00, + 0x98, 0x11, 0xDC, 0x01, 0x01, 0x12, 0x7B, 0x02, 0x01, 0x82, 0x21, 0x22, + 0x84, 0x11, 0xDC, 0x01, 0x01, 0x12, 0x7B, 0x02, 0x01, 0x82, 0x21, 0x22, + 0x84, 0x11, 0xDC, 0x01, 0x01, 0x12, 0x7B, 0x02, 0x01, 0x88, 0x21, 0x22, + 0x84, 0x11, 0xA8, 0x01, 0x01, 0x12, 0x36, 0x02, 0x01, 0x86, 0x20, 0x82, + 0x21, 0x22, 0x84, 0x11, 0xDC, 0x01, 0x01, 0x12, 0x7B, 0x02, 0x01, 0x88, + 0x21, 0x22, 0x84, 0x10, 0x7E, 0x00, 0x00, 0x11, 0xF8, 0x01, 0x01, 0x12, + 0xA1, 0x02, 0x01, 0x88, 0x21, 0x22, 0x90, 0x11, 0xF8, 0x01, 0x01, 0x12, + 0xA1, 0x02, 0x01, 0x82, 0x21, 0x22, 0x84, 0x11, 0xF8, 0x01, 0x01, 0x12, + 0xA1, 0x02, 0x01, 0x82, 0x21, 0x22, 0x84, 0x11, 0xF8, 0x01, 0x01, 0x12, + 0xA1, 0x02, 0x01, 0x92, 0x20, 0x8E, 0x21, 0x22, 0x84, 0x10, 0x3F, 0x00, + 0x00, 0x98, 0x11, 0xF8, 0x01, 0x01, 0x12, 0xA1, 0x02, 0x01, 0x82, 0x21, + 0x22, 0x84, 0x20, 0x11, 0xF8, 0x01, 0x01, 0x12, 0xA1, 0x02, 0x01, 0x82, + 0x21, 0x22, 0x84, 0x10, 0x7E, 0x00, 0x00, 0x11, 0x36, 0x02, 0x01, 0x12, + 0xF3, 0x02, 0x01, 0x88, 0x21, 0x22, 0x84, 0x11, 0xF8, 0x01, 0x01, 0x12, + 0xA1, 0x02, 0x01, 0x86, 0x20, 0x82, 0x21, 0x22, 0x84, 0x11, 0x36, 0x02, + 0x01, 0x12, 0xF3, 0x02, 0x01, 0x88, 0x21, 0x22, 0x84, 0x10, 0x8D, 0x00, + 0x00, 0x11, 0x7B, 0x02, 0x01, 0x12, 0x50, 0x03, 0x01, 0xB2, 0x21, 0x84, + 0x20, 0x82, 0x22, 0x90, 0x10, 0x47, 0x00, 0x00, 0x98, 0x11, 0x7B, 0x02, + 0x01, 0x12, 0x50, 0x03, 0x01, 0x82, 0x21, 0x22, 0x84, 0x20, 0x11, 0x7B, + 0x02, 0x01, 0x12, 0x50, 0x03, 0x01, 0x82, 0x21, 0x22, 0x84, 0x10, 0x8D, + 0x00, 0x00, 0x11, 0x36, 0x02, 0x01, 0x12, 0x50, 0x03, 0x01, 0x88, 0x21, + 0x22, 0x84, 0x11, 0x36, 0x02, 0x01, 0x12, 0x50, 0x03, 0x01, 0x88, 0x21, + 0x22, 0x84, 0x11, 0x36, 0x02, 0x01, 0x12, 0x50, 0x03, 0x01, 0x86, 0x20, + 0x82, 0x21, 0x22, 0x84, 0x10, 0x9F, 0x00, 0x00, 0x11, 0x16, 0x02, 0x01, + 0x12, 0x50, 0x03, 0x01, 0xEC, 0x20, 0x22, 0x82, 0x21, 0x85, 0x00, +}; + +static const uint8_t gStSong7[777] = { + 0x4A, 0x59, 0x4D, 0x31, 0x14, 0x00, 0xFF, 0xFF, 0x81, 0x10, 0x7A, 0x01, + 0x00, 0x11, 0xF6, 0x04, 0x05, 0x12, 0x7E, 0x00, 0x07, 0x8A, 0x22, 0x84, + 0x21, 0x86, 0x11, 0x42, 0x05, 0x05, 0x85, 0x11, 0xF6, 0x04, 0x05, 0x84, + 0x21, 0x81, 0x11, 0x6C, 0x04, 0x05, 0x12, 0x77, 0x00, 0x07, 0x84, 0x21, + 0x86, 0x11, 0xF6, 0x04, 0x05, 0x22, 0x84, 0x21, 0x86, 0x11, 0x42, 0x05, + 0x05, 0x83, 0x20, 0x81, 0x21, 0x86, 0x10, 0xFC, 0x00, 0x00, 0x11, 0xF6, + 0x04, 0x05, 0x12, 0x6A, 0x00, 0x07, 0x84, 0x21, 0x86, 0x11, 0x42, 0x05, + 0x05, 0x22, 0x84, 0x21, 0x86, 0x11, 0xE6, 0x05, 0x05, 0x83, 0x20, 0x81, + 0x21, 0x86, 0x10, 0x1B, 0x01, 0x00, 0x11, 0xF0, 0x03, 0x05, 0x12, 0x5E, + 0x00, 0x07, 0x8A, 0x10, 0x3E, 0x01, 0x00, 0x22, 0x84, 0x21, 0x86, 0x10, + 0x50, 0x01, 0x00, 0x85, 0x11, 0xF0, 0x03, 0x05, 0x85, 0x10, 0x7A, 0x01, + 0x00, 0x12, 0x7E, 0x00, 0x07, 0x8A, 0x22, 0x85, 0x11, 0xB8, 0x03, 0x05, + 0x85, 0x11, 0x50, 0x03, 0x05, 0x85, 0x11, 0xF3, 0x02, 0x05, 0x85, 0x11, + 0x6C, 0x04, 0x05, 0x12, 0x77, 0x00, 0x07, 0x84, 0x21, 0x86, 0x11, 0xF6, + 0x04, 0x05, 0x22, 0x84, 0x21, 0x86, 0x11, 0x42, 0x05, 0x05, 0x84, 0x21, + 0x86, 0x11, 0xE6, 0x05, 0x05, 0x12, 0x7E, 0x00, 0x07, 0x83, 0x20, 0x81, + 0x21, 0x86, 0x11, 0xE6, 0x05, 0x05, 0x22, 0x84, 0x21, 0x86, 0x11, 0xE6, + 0x05, 0x05, 0x84, 0x21, 0x86, 0x10, 0xA8, 0x01, 0x00, 0x11, 0x6C, 0x04, + 0x05, 0x12, 0x5E, 0x00, 0x07, 0x89, 0x21, 0x81, 0x10, 0x7A, 0x01, 0x00, + 0x22, 0x8A, 0x10, 0x50, 0x01, 0x00, 0x11, 0x6C, 0x04, 0x05, 0x8A, 0x10, + 0x7A, 0x01, 0x00, 0x11, 0xF6, 0x04, 0x05, 0x12, 0x7E, 0x00, 0x07, 0x89, + 0x21, 0x81, 0x22, 0x83, 0x20, 0x91, 0x10, 0x50, 0x01, 0x00, 0x11, 0xB8, + 0x03, 0x05, 0x12, 0x77, 0x00, 0x07, 0x89, 0x21, 0x81, 0x10, 0x3E, 0x01, + 0x00, 0x22, 0x8A, 0x10, 0x1B, 0x01, 0x00, 0x11, 0xB8, 0x03, 0x05, 0x8A, + 0x10, 0x3E, 0x01, 0x00, 0x11, 0xF0, 0x03, 0x05, 0x12, 0x6A, 0x00, 0x07, + 0x89, 0x21, 0x81, 0x22, 0x83, 0x20, 0x91, 0x10, 0x1B, 0x01, 0x00, 0x11, + 0xF3, 0x02, 0x05, 0x12, 0x5E, 0x00, 0x07, 0x89, 0x21, 0x81, 0x10, 0xFC, + 0x00, 0x00, 0x22, 0x8A, 0x10, 0xEE, 0x00, 0x00, 0x11, 0xF3, 0x02, 0x05, + 0x89, 0x21, 0x81, 0x10, 0xFC, 0x00, 0x00, 0x11, 0xF0, 0x03, 0x05, 0x12, + 0x7E, 0x00, 0x07, 0x83, 0x20, 0x87, 0x11, 0xB8, 0x03, 0x05, 0x22, 0x8A, + 0x10, 0x3E, 0x01, 0x00, 0x11, 0xF0, 0x03, 0x05, 0x89, 0x21, 0x81, 0x10, + 0xBD, 0x00, 0x00, 0x11, 0x6C, 0x04, 0x05, 0x12, 0x77, 0x00, 0x07, 0x8A, + 0x11, 0xF0, 0x03, 0x05, 0x22, 0x8A, 0x11, 0x6C, 0x04, 0x05, 0x8A, 0x11, + 0xF6, 0x04, 0x05, 0x12, 0x6A, 0x00, 0x07, 0x8A, 0x11, 0x6C, 0x04, 0x05, + 0x22, 0x83, 0x20, 0x87, 0x11, 0xF6, 0x04, 0x05, 0x8A, 0x10, 0xEE, 0x00, + 0x00, 0x11, 0x42, 0x05, 0x05, 0x12, 0x5E, 0x00, 0x07, 0x8A, 0x10, 0xFC, + 0x00, 0x00, 0x11, 0xF6, 0x04, 0x05, 0x22, 0x8A, 0x10, 0x1B, 0x01, 0x00, + 0x11, 0x42, 0x05, 0x05, 0x89, 0x21, 0x81, 0x10, 0xFC, 0x00, 0x00, 0x11, + 0xF6, 0x04, 0x05, 0x12, 0x7E, 0x00, 0x07, 0x83, 0x20, 0x86, 0x21, 0x81, + 0x22, 0x8A, 0x11, 0xF6, 0x04, 0x05, 0x8A, 0x10, 0xEE, 0x00, 0x00, 0x11, + 0xE6, 0x05, 0x05, 0x12, 0x77, 0x00, 0x07, 0x8A, 0x10, 0xFC, 0x00, 0x00, + 0x22, 0x8A, 0x10, 0x1B, 0x01, 0x00, 0x8A, 0x10, 0x3E, 0x01, 0x00, 0x12, + 0x6A, 0x00, 0x07, 0x83, 0x20, 0x86, 0x21, 0x81, 0x22, 0x94, 0x10, 0x1B, + 0x01, 0x00, 0x11, 0xE6, 0x05, 0x05, 0x12, 0x5E, 0x00, 0x07, 0x83, 0x20, + 0x87, 0x22, 0x89, 0x21, 0x81, 0x10, 0x1B, 0x01, 0x00, 0x8A, 0x10, 0x7A, + 0x01, 0x00, 0x11, 0x6C, 0x04, 0x05, 0x12, 0x8D, 0x00, 0x07, 0x89, 0x21, + 0x81, 0x22, 0x8A, 0x11, 0xF0, 0x03, 0x05, 0x8A, 0x11, 0xB8, 0x03, 0x05, + 0x12, 0x77, 0x00, 0x07, 0x8A, 0x11, 0xF0, 0x03, 0x05, 0x22, 0x8A, 0x11, + 0x6C, 0x04, 0x05, 0x8A, 0x11, 0xF3, 0x02, 0x05, 0x12, 0x6A, 0x00, 0x07, + 0x83, 0x20, 0x86, 0x21, 0x81, 0x22, 0x8A, 0x10, 0x7A, 0x01, 0x00, 0x11, + 0x6C, 0x04, 0x05, 0x81, 0x20, 0x84, 0x10, 0x7A, 0x01, 0x00, 0x81, 0x20, + 0x84, 0x10, 0x7A, 0x01, 0x00, 0x11, 0xAF, 0x04, 0x05, 0x12, 0x5E, 0x00, + 0x07, 0x83, 0x20, 0x87, 0x10, 0x7A, 0x01, 0x00, 0x22, 0x83, 0x20, 0x86, + 0x21, 0x81, 0x10, 0x7A, 0x01, 0x00, 0x83, 0x20, 0x87, 0x10, 0x7A, 0x01, + 0x00, 0x11, 0xF6, 0x04, 0x05, 0x12, 0x7E, 0x00, 0x07, 0x85, 0x11, 0xE6, + 0x05, 0x05, 0x85, 0x11, 0xF6, 0x04, 0x05, 0x85, 0x11, 0xE6, 0x05, 0x05, + 0x85, 0x11, 0xF6, 0x04, 0x05, 0x84, 0x12, 0x5E, 0x00, 0x07, 0x81, 0x11, + 0xE6, 0x05, 0x05, 0x85, 0x11, 0xF6, 0x04, 0x05, 0x85, 0x11, 0xE6, 0x05, + 0x05, 0x85, 0x11, 0xF6, 0x04, 0x05, 0x85, 0x11, 0xE6, 0x05, 0x05, 0x83, + 0x12, 0x7E, 0x00, 0x07, 0x82, 0x11, 0xF6, 0x04, 0x05, 0x85, 0x11, 0xE6, + 0x05, 0x05, 0x85, 0x11, 0xF6, 0x04, 0x05, 0x85, 0x11, 0xE6, 0x05, 0x05, + 0x85, 0x11, 0xF6, 0x04, 0x05, 0x82, 0x12, 0x5E, 0x00, 0x07, 0x83, 0x11, + 0xE6, 0x05, 0x05, 0x85, 0x11, 0xF6, 0x04, 0x05, 0x85, 0x11, 0xE6, 0x05, + 0x05, 0x85, 0x11, 0xF6, 0x04, 0x05, 0x81, 0x22, 0x85, 0x12, 0x7E, 0x00, + 0x07, 0x88, 0x21, 0x89, 0x20, 0x88, 0x22, 0x86, 0x00, +}; + +static const uint8_t gStSong8[2338] = { + 0x4A, 0x59, 0x4D, 0x31, 0x14, 0x00, 0x00, 0x00, 0x81, 0x10, 0x1F, 0x00, + 0x07, 0x11, 0x7E, 0x00, 0x01, 0x13, 0x1C, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1D, 0x00, 0x0A, 0x84, 0x11, 0xB2, 0x00, 0x01, 0x82, 0x21, 0x13, + 0x1D, 0x00, 0x0A, 0x86, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x11, 0xD4, 0x00, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x11, 0xFC, 0x00, + 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1E, 0x00, 0x0A, + 0x84, 0x11, 0x50, 0x01, 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, + 0x10, 0x21, 0x00, 0x07, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0xC1, 0x01, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x11, 0xF8, 0x01, + 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1E, 0x00, 0x0A, + 0x84, 0x11, 0x57, 0x02, 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, + 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0xF3, 0x02, 0x01, 0x82, 0x21, 0x82, + 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x11, 0x82, 0x03, 0x01, 0x13, 0x1E, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x10, 0x23, 0x00, 0x07, 0x13, 0x1E, 0x00, 0x0A, + 0x84, 0x11, 0xF0, 0x03, 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, + 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x11, 0x42, 0x05, 0x01, 0x82, 0x21, 0x82, + 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x11, 0x31, 0x06, 0x01, 0x13, 0x1D, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1D, 0x00, 0x0A, 0x84, 0x11, 0xF6, 0x04, + 0x01, 0x82, 0x21, 0x13, 0x1C, 0x00, 0x0A, 0x86, 0x13, 0x1D, 0x00, 0x0A, + 0x82, 0x11, 0xF0, 0x03, 0x01, 0x82, 0x21, 0x82, 0x10, 0x21, 0x00, 0x07, + 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x11, 0x20, 0x03, 0x01, 0x13, 0x1D, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1D, 0x00, 0x0A, 0x84, 0x11, 0xA1, 0x02, + 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, + 0x82, 0x11, 0x36, 0x02, 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, + 0x86, 0x11, 0xF8, 0x01, 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0xC1, 0x01, 0x01, 0x82, 0x10, 0x1F, + 0x00, 0x07, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, + 0x82, 0x11, 0x7A, 0x01, 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, + 0x86, 0x11, 0x3E, 0x01, 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0xFC, 0x00, 0x01, 0x82, 0x21, 0x13, + 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0xD4, 0x00, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x10, 0x21, 0x00, + 0x07, 0x11, 0x9F, 0x00, 0x01, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1D, 0x00, 0x0A, 0x84, 0x11, 0x7E, 0x00, 0x01, 0x82, 0x21, 0x13, + 0x1D, 0x00, 0x0A, 0x86, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x11, 0xB2, 0x00, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1C, 0x00, 0x0A, 0x86, 0x11, 0xD4, 0x00, + 0x01, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1D, 0x00, 0x0A, + 0x84, 0x11, 0xFC, 0x00, 0x01, 0x82, 0x21, 0x13, 0x1D, 0x00, 0x0A, 0x86, + 0x10, 0x23, 0x00, 0x07, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x11, 0x50, 0x01, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x11, 0xC1, 0x01, + 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1E, 0x00, 0x0A, + 0x84, 0x11, 0xF8, 0x01, 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, + 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0x57, 0x02, 0x01, 0x82, 0x21, 0x82, + 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x11, 0xF3, 0x02, 0x01, 0x13, 0x1E, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x10, 0x21, 0x00, 0x07, 0x13, 0x1E, 0x00, 0x0A, + 0x84, 0x11, 0x82, 0x03, 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, + 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0xF0, 0x03, 0x01, 0x82, 0x21, 0x82, + 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x11, 0x42, 0x05, 0x01, 0x13, 0x1E, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0x31, 0x06, + 0x01, 0x82, 0x21, 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x13, 0x1D, 0x00, 0x0A, + 0x82, 0x11, 0xF6, 0x04, 0x01, 0x82, 0x21, 0x82, 0x10, 0x1F, 0x00, 0x07, + 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x11, 0xF0, 0x03, 0x01, 0x13, 0x1D, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1C, 0x00, 0x0A, 0x84, 0x11, 0x20, 0x03, + 0x01, 0x82, 0x21, 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x13, 0x1D, 0x00, 0x0A, + 0x82, 0x11, 0xA1, 0x02, 0x01, 0x82, 0x21, 0x82, 0x13, 0x1D, 0x00, 0x0A, + 0x86, 0x11, 0x36, 0x02, 0x01, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0xF8, 0x01, 0x01, 0x82, 0x10, 0x21, + 0x00, 0x07, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, + 0x82, 0x11, 0xC1, 0x01, 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, + 0x86, 0x11, 0x7A, 0x01, 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0x3E, 0x01, 0x01, 0x82, 0x21, 0x13, + 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0xFC, 0x00, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x10, 0x23, 0x00, + 0x07, 0x11, 0xD4, 0x00, 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0x9F, 0x00, 0x01, 0x82, 0x21, 0x13, + 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0x7E, 0x00, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x11, 0xB2, 0x00, + 0x01, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1D, 0x00, 0x0A, + 0x84, 0x11, 0xD4, 0x00, 0x01, 0x82, 0x21, 0x13, 0x1D, 0x00, 0x0A, 0x86, + 0x10, 0x21, 0x00, 0x07, 0x13, 0x1C, 0x00, 0x0A, 0x82, 0x11, 0xFC, 0x00, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x11, 0x50, 0x01, + 0x01, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1D, 0x00, 0x0A, + 0x84, 0x11, 0xC1, 0x01, 0x01, 0x82, 0x21, 0x13, 0x1D, 0x00, 0x0A, 0x86, + 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0xF8, 0x01, 0x01, 0x82, 0x21, 0x82, + 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x11, 0x57, 0x02, 0x01, 0x13, 0x1E, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x10, 0x1F, 0x00, 0x07, 0x13, 0x1E, 0x00, 0x0A, + 0x84, 0x11, 0xF3, 0x02, 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, + 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0x82, 0x03, 0x01, 0x82, 0x21, 0x82, + 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x11, 0xF0, 0x03, 0x01, 0x13, 0x1E, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0x42, 0x05, + 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, + 0x82, 0x11, 0x31, 0x06, 0x01, 0x82, 0x21, 0x82, 0x10, 0x21, 0x00, 0x07, + 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x11, 0xF6, 0x04, 0x01, 0x13, 0x1E, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1D, 0x00, 0x0A, 0x84, 0x11, 0xF0, 0x03, + 0x01, 0x82, 0x21, 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x13, 0x1D, 0x00, 0x0A, + 0x82, 0x11, 0x20, 0x03, 0x01, 0x82, 0x21, 0x82, 0x13, 0x1D, 0x00, 0x0A, + 0x86, 0x11, 0xA1, 0x02, 0x01, 0x13, 0x1C, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1D, 0x00, 0x0A, 0x84, 0x11, 0x36, 0x02, 0x01, 0x82, 0x10, 0x23, + 0x00, 0x07, 0x21, 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x13, 0x1D, 0x00, 0x0A, + 0x82, 0x11, 0xF8, 0x01, 0x01, 0x82, 0x21, 0x82, 0x13, 0x1D, 0x00, 0x0A, + 0x86, 0x11, 0xC1, 0x01, 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0x7A, 0x01, 0x01, 0x82, 0x21, 0x13, + 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0x3E, 0x01, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x10, 0x21, 0x00, + 0x07, 0x11, 0xFC, 0x00, 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0xD4, 0x00, 0x01, 0x82, 0x21, 0x13, + 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0x9F, 0x00, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x11, 0x7E, 0x00, + 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1E, 0x00, 0x0A, + 0x84, 0x11, 0xB2, 0x00, 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, + 0x10, 0x1F, 0x00, 0x07, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x11, 0xD4, 0x00, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x11, 0xFC, 0x00, + 0x01, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1D, 0x00, 0x0A, + 0x84, 0x11, 0x50, 0x01, 0x01, 0x82, 0x21, 0x13, 0x1C, 0x00, 0x0A, 0x86, + 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x11, 0xC1, 0x01, 0x01, 0x82, 0x21, 0x82, + 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x11, 0xF8, 0x01, 0x01, 0x13, 0x1D, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x10, 0x21, 0x00, 0x07, 0x13, 0x1D, 0x00, 0x0A, + 0x84, 0x11, 0x57, 0x02, 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, + 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0xF3, 0x02, 0x01, 0x82, 0x21, 0x82, + 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x11, 0x82, 0x03, 0x01, 0x13, 0x1E, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0xF0, 0x03, + 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, + 0x82, 0x11, 0x42, 0x05, 0x01, 0x82, 0x21, 0x82, 0x10, 0x23, 0x00, 0x07, + 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x11, 0x31, 0x06, 0x01, 0x13, 0x1E, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0xF6, 0x04, + 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, + 0x82, 0x11, 0xF0, 0x03, 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, + 0x86, 0x11, 0x20, 0x03, 0x01, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1D, 0x00, 0x0A, 0x84, 0x11, 0xA1, 0x02, 0x01, 0x82, 0x10, 0x21, + 0x00, 0x07, 0x21, 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x13, 0x1D, 0x00, 0x0A, + 0x82, 0x11, 0x36, 0x02, 0x01, 0x82, 0x21, 0x82, 0x13, 0x1C, 0x00, 0x0A, + 0x86, 0x11, 0xF8, 0x01, 0x01, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1D, 0x00, 0x0A, 0x84, 0x11, 0xC1, 0x01, 0x01, 0x82, 0x21, 0x13, + 0x1D, 0x00, 0x0A, 0x86, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x11, 0x7A, 0x01, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x10, 0x1F, 0x00, + 0x07, 0x11, 0x3E, 0x01, 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0xFC, 0x00, 0x01, 0x82, 0x21, 0x13, + 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0xD4, 0x00, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x11, 0x9F, 0x00, + 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1E, 0x00, 0x0A, + 0x84, 0x11, 0x7E, 0x00, 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, + 0x10, 0x21, 0x00, 0x07, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0xB2, 0x00, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x11, 0xD4, 0x00, + 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1E, 0x00, 0x0A, + 0x84, 0x11, 0xFC, 0x00, 0x01, 0x82, 0x21, 0x13, 0x1D, 0x00, 0x0A, 0x86, + 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x11, 0x50, 0x01, 0x01, 0x82, 0x21, 0x82, + 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x11, 0xC1, 0x01, 0x01, 0x13, 0x1D, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x10, 0x23, 0x00, 0x07, 0x13, 0x1C, 0x00, 0x0A, + 0x84, 0x11, 0xF8, 0x01, 0x01, 0x82, 0x21, 0x13, 0x1D, 0x00, 0x0A, 0x86, + 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x11, 0x57, 0x02, 0x01, 0x82, 0x21, 0x82, + 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x11, 0xF3, 0x02, 0x01, 0x13, 0x1D, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0x82, 0x03, + 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, + 0x82, 0x11, 0xF0, 0x03, 0x01, 0x82, 0x21, 0x82, 0x10, 0x21, 0x00, 0x07, + 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x11, 0x42, 0x05, 0x01, 0x13, 0x1E, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0x31, 0x06, + 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, + 0x82, 0x11, 0xF6, 0x04, 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, + 0x86, 0x11, 0xF0, 0x03, 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0x20, 0x03, 0x01, 0x82, 0x10, 0x1F, + 0x00, 0x07, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, + 0x82, 0x11, 0xA1, 0x02, 0x01, 0x82, 0x21, 0x82, 0x13, 0x1D, 0x00, 0x0A, + 0x86, 0x11, 0x36, 0x02, 0x01, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1D, 0x00, 0x0A, 0x84, 0x11, 0xF8, 0x01, 0x01, 0x82, 0x21, 0x13, + 0x1D, 0x00, 0x0A, 0x86, 0x13, 0x1C, 0x00, 0x0A, 0x82, 0x11, 0xC1, 0x01, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x10, 0x21, 0x00, + 0x07, 0x11, 0x7A, 0x01, 0x01, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1D, 0x00, 0x0A, 0x84, 0x11, 0x3E, 0x01, 0x01, 0x82, 0x21, 0x13, + 0x1D, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0xFC, 0x00, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x11, 0xD4, 0x00, + 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1E, 0x00, 0x0A, + 0x84, 0x11, 0x9F, 0x00, 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, + 0x10, 0x23, 0x00, 0x07, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0x7E, 0x00, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x11, 0xB2, 0x00, + 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1E, 0x00, 0x0A, + 0x84, 0x11, 0xD4, 0x00, 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, + 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0xFC, 0x00, 0x01, 0x82, 0x21, 0x82, + 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x11, 0x50, 0x01, 0x01, 0x13, 0x1E, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x10, 0x21, 0x00, 0x07, 0x13, 0x1D, 0x00, 0x0A, + 0x84, 0x11, 0xC1, 0x01, 0x01, 0x82, 0x21, 0x13, 0x1D, 0x00, 0x0A, 0x86, + 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x11, 0xF8, 0x01, 0x01, 0x82, 0x21, 0x82, + 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x11, 0x57, 0x02, 0x01, 0x13, 0x1C, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1D, 0x00, 0x0A, 0x84, 0x11, 0xF3, 0x02, + 0x01, 0x82, 0x21, 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x13, 0x1D, 0x00, 0x0A, + 0x82, 0x11, 0x82, 0x03, 0x01, 0x82, 0x21, 0x82, 0x10, 0x1F, 0x00, 0x07, + 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x11, 0xF0, 0x03, 0x01, 0x13, 0x1E, 0x00, + 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0x42, 0x05, + 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, + 0x82, 0x11, 0x31, 0x06, 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, + 0x86, 0x11, 0xF6, 0x04, 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0xF0, 0x03, 0x01, 0x82, 0x10, 0x21, + 0x00, 0x07, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1E, 0x00, 0x0A, + 0x82, 0x11, 0x20, 0x03, 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, + 0x86, 0x11, 0xA1, 0x02, 0x01, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1E, 0x00, 0x0A, 0x84, 0x11, 0x36, 0x02, 0x01, 0x82, 0x21, 0x13, + 0x1E, 0x00, 0x0A, 0x86, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x11, 0xF8, 0x01, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x10, 0x23, 0x00, + 0x07, 0x11, 0xC1, 0x01, 0x01, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x21, 0x84, + 0x13, 0x1D, 0x00, 0x0A, 0x84, 0x11, 0x7A, 0x01, 0x01, 0x82, 0x21, 0x13, + 0x1C, 0x00, 0x0A, 0x86, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x11, 0x3E, 0x01, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1D, 0x00, 0x0A, 0x86, 0x11, 0xFC, 0x00, + 0x01, 0x13, 0x1D, 0x00, 0x0A, 0x82, 0x21, 0x84, 0x13, 0x1D, 0x00, 0x0A, + 0x84, 0x11, 0xD4, 0x00, 0x01, 0x82, 0x21, 0x13, 0x1E, 0x00, 0x0A, 0x86, + 0x10, 0x21, 0x00, 0x07, 0x13, 0x1E, 0x00, 0x0A, 0x82, 0x11, 0x9F, 0x00, + 0x01, 0x82, 0x21, 0x82, 0x13, 0x1E, 0x00, 0x0A, 0x85, 0x00, +}; + +typedef struct { + const uint8_t *data; + uint16_t length; +} StSongT; + +#define ST_SONG_COUNT 9u +#define ST_SONG_BONUS 4u +#define ST_SONG_GAME_OVER 5u +#define ST_SONG_NAME_ENTRY 6u +#define ST_SONG_SCORES 7u +#define ST_SONG_INTRO 8u + +static const StSongT kStSongs[ST_SONG_COUNT] = { + { gStSong0, (uint16_t)sizeof(gStSong0) }, + { gStSong1, (uint16_t)sizeof(gStSong1) }, + { gStSong2, (uint16_t)sizeof(gStSong2) }, + { gStSong3, (uint16_t)sizeof(gStSong3) }, + { gStSong4, (uint16_t)sizeof(gStSong4) }, + { gStSong5, (uint16_t)sizeof(gStSong5) }, + { gStSong6, (uint16_t)sizeof(gStSong6) }, + { gStSong7, (uint16_t)sizeof(gStSong7) }, + { gStSong8, (uint16_t)sizeof(gStSong8) }, +}; + +#endif // ST_SONG_DATA_H diff --git a/stSpeechData.c b/stSpeechData.c new file mode 100644 index 0000000..16b0c91 --- /dev/null +++ b/stSpeechData.c @@ -0,0 +1,490 @@ +// Generated by assets/extractSpeech.py. Do not hand-edit. + +#include "stSpeechData.h" + +const StSpeechEntryT kStSpeechIndex[ST_SPEECH_COUNT] = { + { 0x50u, 0u, 562u }, // 'P' + { 0x31u, 562u, 420u }, // '1' + { 0x3Fu, 982u, 266u }, // '?' + { 0x32u, 1248u, 480u }, // '2' + { 0x33u, 1728u, 463u }, // '3' + { 0x34u, 2191u, 315u }, // '4' + { 0x35u, 2506u, 550u }, // '5' + { 0x36u, 3056u, 527u }, // '6' + { 0x37u, 3583u, 758u }, // '7' + { 0x38u, 4341u, 385u }, // '8' + { 0x39u, 4726u, 491u }, // '9' + { 0x48u, 5217u, 520u }, // 'H' + { 0x54u, 5737u, 632u }, // 'T' + { 0x21u, 6369u, 708u }, // '!' + { 0x55u, 7077u, 368u }, // 'U' +}; + +const uint8_t kStSpeechRle[ST_SPEECH_RLE_BYTES] = { + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xF2, 0x42, 0xCD, 0x27, 0x10, 0x09, 0xAB, 0x31, 0xB6, 0x12, 0x37, 0x09, 0x22, 0x04, 0x1C, 0x0D, + 0x16, 0x0E, 0x2F, 0x0B, 0x19, 0x12, 0x0E, 0x12, 0x19, 0x06, 0x1A, 0x0F, 0x09, 0x19, 0x0D, 0x16, + 0x13, 0x06, 0x05, 0x03, 0x38, 0x08, 0x0F, 0x17, 0x0E, 0x12, 0x0F, 0x12, 0x16, 0x09, 0x15, 0x15, + 0x06, 0x13, 0x08, 0x15, 0x0C, 0x13, 0x18, 0x10, 0x15, 0x1C, 0x0C, 0x0C, 0x09, 0x02, 0x0C, 0x14, + 0x15, 0x14, 0x1D, 0x11, 0x11, 0x0B, 0x0A, 0x02, 0x0A, 0x15, 0x15, 0x0B, 0x15, 0x05, 0x06, 0x07, + 0x07, 0x07, 0x15, 0x08, 0x04, 0x08, 0x0B, 0x07, 0x03, 0x08, 0x0B, 0x04, 0x05, 0x0D, 0x13, 0x08, + 0x04, 0x06, 0x24, 0x08, 0x04, 0x03, 0x02, 0x03, 0x09, 0x07, 0x05, 0x07, 0x0B, 0x05, 0x05, 0x0A, + 0x14, 0x08, 0x06, 0x06, 0x18, 0x06, 0x06, 0x09, 0x17, 0x07, 0x05, 0x08, 0x0B, 0x04, 0x05, 0x0A, + 0x14, 0x09, 0x04, 0x07, 0x09, 0x06, 0x06, 0x09, 0x07, 0x08, 0x05, 0x03, 0x0E, 0x07, 0x04, 0x09, + 0x0B, 0x04, 0x06, 0x09, 0x15, 0x08, 0x04, 0x06, 0x17, 0x09, 0x07, 0x0C, 0x15, 0x0A, 0x01, 0x09, + 0x16, 0x09, 0x15, 0x09, 0x06, 0x03, 0x0C, 0x05, 0x05, 0x0A, 0x09, 0x08, 0x07, 0x04, 0x0B, 0x07, + 0x05, 0x08, 0x15, 0x0A, 0x14, 0x08, 0x05, 0x05, 0x0B, 0x04, 0x07, 0x0F, 0x04, 0x0A, 0x05, 0x08, + 0x09, 0x07, 0x03, 0x09, 0x15, 0x0A, 0x14, 0x09, 0x04, 0x05, 0x18, 0x0A, 0x0A, 0x0A, 0x06, 0x03, + 0x0C, 0x14, 0x15, 0x0A, 0x14, 0x14, 0x0A, 0x06, 0x05, 0x0E, 0x08, 0x09, 0x16, 0x08, 0x03, 0x08, + 0x16, 0x09, 0x15, 0x13, 0x16, 0x0F, 0x08, 0x09, 0x05, 0x06, 0x0A, 0x08, 0x04, 0x07, 0x16, 0x0A, + 0x06, 0x02, 0x0B, 0x13, 0x17, 0x09, 0x0F, 0x09, 0x07, 0x02, 0x0C, 0x08, 0x03, 0x08, 0x15, 0x0B, + 0x13, 0x14, 0x0A, 0x14, 0x13, 0x0A, 0x15, 0x13, 0x12, 0x0F, 0x14, 0x0F, 0x10, 0x11, 0x10, 0x10, + 0x15, 0x0A, 0x06, 0x02, 0x0D, 0x13, 0x14, 0x0B, 0x12, 0x11, 0x0E, 0x16, 0x15, 0x0A, 0x17, 0x07, + 0x04, 0x05, 0x19, 0x06, 0x14, 0x0B, 0x14, 0x18, 0x07, 0x04, 0x0C, 0x07, 0x05, 0x05, 0x10, 0x02, + 0x05, 0x07, 0x08, 0x02, 0x0C, 0x07, 0x06, 0x07, 0x08, 0x38, 0x16, 0x09, 0x05, 0x05, 0x0C, 0x06, + 0x05, 0x07, 0x16, 0x08, 0x07, 0x03, 0x0A, 0x13, 0x0B, 0x1C, 0x08, 0x0B, 0x04, 0x07, 0x06, 0x04, + 0x0D, 0x05, 0x06, 0x06, 0x0B, 0x02, 0x08, 0x07, 0x08, 0x05, 0x08, 0x08, 0x1F, 0x0A, 0x05, 0x08, + 0x06, 0x04, 0x0C, 0x07, 0x05, 0x05, 0x18, 0x0A, 0x07, 0x02, 0x09, 0x08, 0x06, 0x07, 0x05, 0x09, + 0x17, 0x10, 0x09, 0x07, 0x06, 0x06, 0x0C, 0x05, 0x05, 0x07, 0x17, 0x09, 0x07, 0x05, 0x0C, 0x13, + 0x06, 0x06, 0x08, 0x06, 0x06, 0x0B, 0x05, 0x02, 0x0D, 0x07, 0x06, 0x03, 0x0F, 0x05, 0x05, 0x07, + 0x17, 0x17, 0x07, 0x17, 0x24, 0x11, 0x11, 0x09, 0x17, 0x08, 0x03, 0x08, 0x16, 0x0B, 0x14, 0x13, + 0x18, 0x06, 0x16, 0x0C, 0x0E, 0x0A, 0x04, 0x03, 0x10, 0x12, 0x16, 0x12, 0x0D, 0x14, 0x15, 0x0B, + 0x16, 0x0C, 0x10, 0x0F, 0x15, 0x0A, 0x02, 0x05, 0x18, 0x0C, 0x13, 0x09, 0x05, 0x07, 0x30, 0x09, + 0x11, 0x1B, 0x1A, 0x05, 0x05, 0x06, 0x1A, 0x15, 0x14, 0x0C, 0x20, 0x14, 0x18, 0x16, 0x03, 0x09, + 0x05, 0x03, 0x0F, 0x05, 0x05, 0x09, 0x15, 0x16, 0x15, 0x13, 0x17, 0x09, 0x1E, 0x14, 0x0D, 0x0B, + 0x19, 0x05, 0x03, 0x0B, 0x14, 0x16, 0x15, 0x14, 0x18, 0x08, 0x26, 0x12, 0x11, 0x0B, 0x16, 0x14, + 0x15, 0x15, 0x16, 0x16, 0x0A, 0x23, 0x42, 0x0A, 0x18, 0x05, 0x05, 0x09, 0x24, 0x08, 0x18, 0x16, + 0x13, 0x17, 0x15, 0x18, 0x1A, 0x0A, 0x23, 0x09, 0x25, 0x15, 0x17, 0x18, 0x11, 0x14, 0x3C, 0x10, + 0x57, 0x0F, 0x16, 0x15, 0x18, 0x13, 0x48, 0x1A, 0x16, 0x13, 0x4E, 0x1C, 0x6B, 0x18, 0xFE, 0xFE, + 0xFE, 0xFF, 0xFE, 0x63, 0x1D, 0xC8, 0x29, 0xBE, 0x1B, 0x59, 0x14, 0x48, 0x19, 0x1A, 0x0E, 0x1E, + 0x1F, 0x20, 0x11, 0x0E, 0x23, 0x19, 0x10, 0x19, 0x22, 0x18, 0x12, 0x15, 0x20, 0x17, 0x13, 0x18, + 0x1E, 0x17, 0x13, 0x17, 0x1D, 0x16, 0x14, 0x1A, 0x1A, 0x16, 0x12, 0x17, 0x1E, 0x16, 0x13, 0x17, + 0x1D, 0x12, 0x15, 0x17, 0x1F, 0x16, 0x12, 0x17, 0x1C, 0x14, 0x14, 0x18, 0x1E, 0x15, 0x0F, 0x1A, + 0x19, 0x13, 0x10, 0x1E, 0x1D, 0x12, 0x11, 0x1C, 0x17, 0x11, 0x1A, 0x1A, 0x1A, 0x11, 0x11, 0x1D, + 0x14, 0x10, 0x11, 0x25, 0x19, 0x10, 0x12, 0x1C, 0x13, 0x10, 0x10, 0x0B, 0x09, 0x13, 0x18, 0x12, + 0x11, 0x17, 0x16, 0x10, 0x0E, 0x0D, 0x0F, 0x0E, 0x19, 0x11, 0x10, 0x17, 0x16, 0x10, 0x0D, 0x0E, + 0x10, 0x0D, 0x18, 0x10, 0x11, 0x18, 0x12, 0x14, 0x0D, 0x0F, 0x0C, 0x0F, 0x19, 0x10, 0x10, 0x19, + 0x15, 0x11, 0x0D, 0x0C, 0x12, 0x0E, 0x17, 0x0F, 0x11, 0x1A, 0x11, 0x0C, 0x14, 0x0C, 0x17, 0x0B, + 0x13, 0x01, 0x04, 0x0D, 0x10, 0x1B, 0x10, 0x0C, 0x0C, 0x0E, 0x0A, 0x0B, 0x0B, 0x0A, 0x14, 0x13, + 0x0C, 0x1F, 0x0D, 0x0F, 0x0B, 0x0E, 0x1A, 0x0D, 0x20, 0x0C, 0x0B, 0x24, 0x09, 0x0E, 0x0C, 0x0C, + 0x0B, 0x0A, 0x05, 0x0E, 0x22, 0x06, 0x01, 0x04, 0x0A, 0x0E, 0x03, 0x10, 0x0B, 0x0F, 0x0D, 0x0C, + 0x0B, 0x06, 0x0A, 0x0B, 0x16, 0x04, 0x0E, 0x0C, 0x09, 0x0F, 0x02, 0x10, 0x0A, 0x0C, 0x0C, 0x0C, + 0x1D, 0x0B, 0x09, 0x08, 0x09, 0x04, 0x0D, 0x0C, 0x0A, 0x0E, 0x02, 0x10, 0x0C, 0x0A, 0x0B, 0x02, + 0x07, 0x0D, 0x0E, 0x18, 0x13, 0x07, 0x0D, 0x0C, 0x08, 0x09, 0x0A, 0x0D, 0x0D, 0x07, 0x08, 0x09, + 0x0B, 0x09, 0x0B, 0x0B, 0x01, 0x0F, 0x0B, 0x10, 0x0F, 0x07, 0x0D, 0x07, 0x0A, 0x0D, 0x09, 0x0E, + 0x05, 0x05, 0x0F, 0x0D, 0x10, 0x26, 0x18, 0x03, 0x02, 0x05, 0x0A, 0x0B, 0x05, 0x0D, 0x08, 0x0A, + 0x09, 0x0A, 0x1B, 0x17, 0x08, 0x0D, 0x07, 0x0B, 0x04, 0x0A, 0x08, 0x03, 0x02, 0x05, 0x08, 0x0B, + 0x07, 0x0C, 0x04, 0x09, 0x0B, 0x0D, 0x06, 0x05, 0x0D, 0x19, 0x06, 0x1A, 0x05, 0x0D, 0x04, 0x07, + 0x08, 0x04, 0x02, 0x04, 0x05, 0x09, 0x0B, 0x0F, 0x01, 0x09, 0x0A, 0x09, 0x06, 0x08, 0x0A, 0x0A, + 0x07, 0x0C, 0x04, 0x1A, 0x06, 0x0E, 0x0B, 0x05, 0x0C, 0x0C, 0x04, 0x09, 0x0A, 0x1A, 0x06, 0x0A, + 0x07, 0x08, 0x09, 0x08, 0x0A, 0x2B, 0x04, 0x0B, 0x27, 0x03, 0x03, 0x05, 0x02, 0x0B, 0x0B, 0x05, + 0x02, 0x11, 0x05, 0x02, 0x0D, 0x0A, 0x14, 0x0A, 0x07, 0x1A, 0x09, 0x06, 0x03, 0x12, 0x1F, 0x04, + 0x02, 0x04, 0x03, 0x0B, 0x0A, 0x04, 0x08, 0x0D, 0x04, 0x05, 0x19, 0x0B, 0x1B, 0x18, 0x0F, 0x1E, + 0x12, 0x04, 0x0E, 0x04, 0x04, 0x19, 0x06, 0x18, 0x0B, 0x58, 0x14, 0x14, 0x0B, 0x0D, 0x19, 0x1B, + 0x04, 0x56, 0x16, 0x40, 0x29, 0x04, 0x15, 0x3A, 0x07, 0x37, 0x14, 0x55, 0x19, 0xFE, 0x80, 0x5B, + 0xFE, 0x3C, 0x80, 0xFE, 0xFE, 0xFF, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xA2, 0x40, 0x48, 0x1D, 0x3C, + 0x0B, 0xFE, 0xFE, 0xBB, 0x51, 0xFE, 0xFE, 0xFE, 0x0D, 0x13, 0x92, 0x24, 0x36, 0x30, 0x67, 0x23, + 0x37, 0x25, 0x6D, 0x23, 0x36, 0x2B, 0x67, 0x21, 0x35, 0x46, 0x29, 0x20, 0x0A, 0x1F, 0x2F, 0x97, + 0x11, 0x1A, 0x2E, 0x1C, 0x62, 0x44, 0x2D, 0x1E, 0x23, 0x15, 0x2B, 0x0F, 0x14, 0x1E, 0x2F, 0x20, + 0x19, 0x16, 0x25, 0x1E, 0x0E, 0x1E, 0x2D, 0x20, 0x15, 0x18, 0x22, 0x4F, 0x30, 0x19, 0x21, 0x12, + 0x27, 0x4B, 0x2F, 0x21, 0x15, 0x10, 0x30, 0x47, 0x2E, 0x16, 0x6D, 0x39, 0x2E, 0x2D, 0x48, 0x48, + 0x2D, 0x24, 0x55, 0x3C, 0x30, 0x2D, 0x3C, 0x4C, 0x31, 0x29, 0x34, 0x57, 0x2E, 0x29, 0x2E, 0x34, + 0x03, 0x24, 0x32, 0x26, 0x38, 0x29, 0x04, 0x23, 0x37, 0x24, 0x38, 0x50, 0x31, 0x21, 0x3B, 0x4F, + 0x3C, 0x13, 0x43, 0x42, 0x31, 0x2A, 0x46, 0x3F, 0x2F, 0x2C, 0x3D, 0x3F, 0x33, 0x28, 0x3E, 0x48, + 0x2F, 0x31, 0x41, 0x38, 0x31, 0x2A, 0x4E, 0x30, 0x2E, 0x2A, 0x46, 0x37, 0x2F, 0x2A, 0x46, 0x33, + 0x31, 0x29, 0x49, 0x37, 0x2E, 0x2A, 0x35, 0x47, 0x2F, 0x26, 0x41, 0x42, 0x30, 0x25, 0x50, 0x2C, + 0x32, 0x24, 0x38, 0x43, 0x31, 0x29, 0x35, 0x49, 0x32, 0x1D, 0x37, 0x4C, 0x32, 0x1E, 0x48, 0x32, + 0xFE, 0xD3, 0x48, 0x74, 0x24, 0xAE, 0x2C, 0x45, 0x03, 0x50, 0x02, 0x1D, 0x26, 0x12, 0x02, 0x21, + 0x02, 0x03, 0x02, 0x48, 0x02, 0x1F, 0x1B, 0x46, 0x2E, 0x13, 0x01, 0x2D, 0x02, 0x03, 0x03, 0x01, + 0x03, 0x01, 0x03, 0xCC, 0x1B, 0x4B, 0x03, 0x03, 0x02, 0x01, 0x09, 0x74, 0x08, 0x02, 0x00, 0x02, + 0x1F, 0x79, 0x0E, 0x03, 0x01, 0x03, 0x02, 0x03, 0x02, 0x04, 0x01, 0x24, 0x01, 0x36, 0x02, 0x03, + 0x3C, 0x04, 0x01, 0x1D, 0x1F, 0xCF, 0x03, 0x01, 0x38, 0x90, 0x39, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0x5C, 0x06, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, + 0x3F, 0x0E, 0x13, 0x08, 0x9B, 0x23, 0x2B, 0x36, 0x25, 0x02, 0x25, 0x01, 0x23, 0x03, 0x02, 0x02, + 0x15, 0x02, 0x1B, 0x0A, 0x23, 0x02, 0x25, 0x02, 0x3E, 0x01, 0x05, 0x02, 0x14, 0x0F, 0x25, 0x18, + 0x3E, 0x13, 0x6B, 0x05, 0x0B, 0x0A, 0x72, 0x21, 0x09, 0x16, 0xBA, 0x24, 0x45, 0x03, 0x05, 0x1A, + 0x04, 0x09, 0x02, 0x0F, 0x12, 0x02, 0x1B, 0x04, 0x08, 0x03, 0x0F, 0x04, 0x04, 0x08, 0x04, 0x32, + 0x42, 0x03, 0x07, 0x1A, 0x03, 0x08, 0x03, 0x23, 0x48, 0x0E, 0x05, 0x04, 0x05, 0x25, 0x18, 0x05, + 0x06, 0x06, 0x23, 0x19, 0x05, 0x0B, 0x02, 0x09, 0x03, 0x0A, 0x17, 0x06, 0x07, 0x09, 0x09, 0x02, + 0x19, 0x06, 0x05, 0x07, 0x06, 0x0A, 0x03, 0x09, 0x03, 0x0A, 0x0B, 0x03, 0x07, 0x05, 0x09, 0x08, + 0x08, 0x03, 0x19, 0x07, 0x04, 0x07, 0x07, 0x16, 0x02, 0x0B, 0x0C, 0x02, 0x07, 0x07, 0x06, 0x0A, + 0x08, 0x03, 0x09, 0x03, 0x0B, 0x07, 0x05, 0x06, 0x07, 0x0B, 0x02, 0x08, 0x03, 0x0A, 0x0C, 0x03, + 0x07, 0x07, 0x07, 0x09, 0x08, 0x03, 0x18, 0x06, 0x07, 0x05, 0x0A, 0x09, 0x02, 0x08, 0x03, 0x0A, + 0x0B, 0x04, 0x07, 0x06, 0x08, 0x09, 0x07, 0x04, 0x19, 0x05, 0x07, 0x05, 0x09, 0x08, 0x05, 0x08, + 0x03, 0x0A, 0x0A, 0x05, 0x06, 0x07, 0x07, 0x09, 0x08, 0x04, 0x0B, 0x02, 0x0A, 0x07, 0x05, 0x06, + 0x0B, 0x05, 0x06, 0x09, 0x04, 0x0A, 0x0B, 0x03, 0x07, 0x06, 0x08, 0x07, 0x0A, 0x04, 0x08, 0x05, + 0x06, 0x0B, 0x07, 0x04, 0x1B, 0x09, 0x03, 0x0B, 0x0D, 0x01, 0x07, 0x05, 0x0A, 0x0A, 0x06, 0x04, + 0x09, 0x03, 0x0A, 0x08, 0x05, 0x06, 0x07, 0x05, 0x06, 0x13, 0x02, 0x0F, 0x13, 0x04, 0x0A, 0x09, + 0x08, 0x03, 0x19, 0x05, 0x07, 0x05, 0x06, 0x03, 0x17, 0x08, 0x03, 0x0D, 0x0A, 0x03, 0x07, 0x05, + 0x0A, 0x0B, 0x04, 0x06, 0x09, 0x03, 0x0E, 0x04, 0x07, 0x04, 0x1B, 0x0F, 0x03, 0x0D, 0x0A, 0x02, + 0x08, 0x04, 0x0B, 0x0B, 0x04, 0x06, 0x28, 0x03, 0x1A, 0x22, 0x16, 0x04, 0x0A, 0x0C, 0x04, 0x05, + 0x1B, 0x04, 0x08, 0x04, 0x1A, 0x06, 0x04, 0x1A, 0x17, 0x08, 0x05, 0x0B, 0x05, 0x05, 0x1B, 0x05, + 0x08, 0x09, 0x15, 0x25, 0x18, 0x08, 0x04, 0x0C, 0x26, 0x16, 0x17, 0x26, 0x0C, 0x03, 0x08, 0x19, + 0x1A, 0x06, 0x06, 0x08, 0x1B, 0x04, 0x07, 0x19, 0x05, 0x09, 0x0C, 0x04, 0x07, 0x09, 0x09, 0x06, + 0x08, 0x07, 0x09, 0x08, 0x06, 0x08, 0x19, 0x2B, 0x03, 0x0A, 0x0D, 0x02, 0x09, 0x06, 0x08, 0x0A, + 0x08, 0x05, 0x0A, 0x0B, 0x05, 0x07, 0x18, 0x09, 0x06, 0x23, 0x03, 0x0C, 0x2C, 0x04, 0x1B, 0x19, + 0x08, 0x06, 0x1A, 0x07, 0x07, 0x09, 0x04, 0x09, 0x04, 0x0B, 0x2B, 0x06, 0x1C, 0x07, 0x07, 0x0E, + 0x03, 0x0A, 0x19, 0x05, 0x0A, 0x0A, 0x05, 0x08, 0x08, 0x06, 0x0D, 0x04, 0x0A, 0x0B, 0x07, 0x05, + 0x09, 0x0E, 0x03, 0x1D, 0x2A, 0x1A, 0x0F, 0x1C, 0x28, 0x1C, 0x09, 0x04, 0x0A, 0x18, 0x2F, 0x06, + 0x19, 0x1C, 0x1F, 0x05, 0x05, 0x1B, 0x1E, 0x14, 0x23, 0x1C, 0x19, 0x1D, 0x21, 0x07, 0x04, 0x0D, + 0x36, 0x13, 0x1F, 0x17, 0x1C, 0x24, 0x36, 0x15, 0x26, 0x1C, 0x5D, 0x20, 0x63, 0x2B, 0x53, 0x2A, + 0x7C, 0x28, 0x61, 0x2D, 0xFE, 0x23, 0x22, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0x5C, 0xFE, 0xFE, 0xFE, 0xFE, 0x76, 0x23, 0x6F, + 0x28, 0x84, 0x32, 0x22, 0x05, 0x08, 0x04, 0x1C, 0x16, 0x1B, 0x2E, 0x52, 0x0B, 0x26, 0x2C, 0x54, + 0x08, 0x1D, 0x34, 0x54, 0x0B, 0x1B, 0x32, 0x24, 0x05, 0x2C, 0x0A, 0x1B, 0x25, 0x03, 0x08, 0x25, + 0x05, 0x2C, 0x09, 0x0C, 0x04, 0x0A, 0x2F, 0x24, 0x09, 0x2A, 0x0A, 0x1B, 0x20, 0x02, 0x09, 0x25, + 0x08, 0x4F, 0x1C, 0x04, 0x09, 0x26, 0x07, 0x4F, 0x1B, 0x05, 0x09, 0x24, 0x08, 0x20, 0x08, 0x26, + 0x1B, 0x04, 0x08, 0x0E, 0x05, 0x09, 0x10, 0x52, 0x18, 0x04, 0x08, 0x0E, 0x06, 0x06, 0x13, 0x1E, + 0x18, 0x1B, 0x17, 0x05, 0x07, 0x10, 0x02, 0x09, 0x14, 0x1D, 0x0F, 0x25, 0x16, 0x05, 0x08, 0x0B, + 0x08, 0x07, 0x12, 0x1B, 0x09, 0x05, 0x09, 0x20, 0x18, 0x06, 0x07, 0x0C, 0x08, 0x05, 0x14, 0x19, + 0x17, 0x20, 0x1A, 0x07, 0x07, 0x0B, 0x08, 0x06, 0x0F, 0x1D, 0x08, 0x06, 0x08, 0x21, 0x1B, 0x07, + 0x07, 0x0A, 0x09, 0x06, 0x0B, 0x1E, 0x08, 0x06, 0x08, 0x22, 0x1C, 0x08, 0x06, 0x0B, 0x07, 0x07, + 0x0A, 0x1E, 0x0A, 0x04, 0x09, 0x1A, 0x27, 0x07, 0x07, 0x0A, 0x07, 0x07, 0x09, 0x1E, 0x09, 0x06, + 0x08, 0x1A, 0x2B, 0x07, 0x06, 0x0A, 0x07, 0x07, 0x09, 0x1D, 0x07, 0x07, 0x08, 0x19, 0x06, 0x05, + 0x24, 0x07, 0x07, 0x09, 0x07, 0x07, 0x09, 0x1D, 0x06, 0x07, 0x08, 0x08, 0x05, 0x0A, 0x06, 0x07, + 0x09, 0x07, 0x09, 0x03, 0x0B, 0x06, 0x07, 0x0A, 0x06, 0x08, 0x09, 0x08, 0x06, 0x0C, 0x06, 0x07, + 0x09, 0x08, 0x04, 0x0B, 0x05, 0x07, 0x09, 0x06, 0x08, 0x07, 0x0A, 0x08, 0x06, 0x09, 0x06, 0x08, + 0x09, 0x07, 0x07, 0x0B, 0x06, 0x07, 0x0A, 0x06, 0x05, 0x19, 0x2D, 0x05, 0x09, 0x09, 0x05, 0x09, + 0x07, 0x09, 0x04, 0x1B, 0x07, 0x05, 0x08, 0x18, 0x18, 0x07, 0x08, 0x05, 0x09, 0x05, 0x08, 0x09, + 0x04, 0x0A, 0x07, 0x09, 0x06, 0x09, 0x06, 0x08, 0x17, 0x1F, 0x17, 0x09, 0x05, 0x02, 0x07, 0x04, + 0x0A, 0x1A, 0x04, 0x0A, 0x04, 0x0C, 0x02, 0x09, 0x08, 0x05, 0x09, 0x06, 0x05, 0x07, 0x07, 0x06, + 0x08, 0x07, 0x06, 0x0B, 0x07, 0x05, 0x08, 0x05, 0x08, 0x19, 0x06, 0x07, 0x07, 0x08, 0x03, 0x0A, + 0x07, 0x05, 0x16, 0x09, 0x04, 0x09, 0x03, 0x0A, 0x05, 0x0A, 0x07, 0x0A, 0x09, 0x04, 0x08, 0x0A, + 0x02, 0x0B, 0x06, 0x07, 0x07, 0x08, 0x05, 0x08, 0x25, 0x15, 0x04, 0x0B, 0x02, 0x1A, 0x04, 0x06, + 0x07, 0x05, 0x08, 0x18, 0x06, 0x06, 0x08, 0x06, 0x05, 0x0A, 0x04, 0x08, 0x09, 0x04, 0x08, 0x55, + 0x08, 0x04, 0x09, 0x06, 0x05, 0x0C, 0x04, 0x08, 0x07, 0x05, 0x0B, 0x15, 0x1D, 0x52, 0x0A, 0x02, + 0x0B, 0x04, 0x04, 0x1B, 0x04, 0x06, 0x06, 0x24, 0x18, 0x26, 0x1C, 0x14, 0x0A, 0x02, 0x0B, 0x24, + 0x1F, 0x15, 0x1B, 0x24, 0x1A, 0x19, 0x0E, 0x02, 0x07, 0x27, 0x06, 0x05, 0x0C, 0x1B, 0x16, 0x6C, + 0x0B, 0x03, 0x06, 0x09, 0x03, 0x25, 0x16, 0x13, 0x05, 0x8A, 0x06, 0x03, 0x0C, 0x03, 0x03, 0x6E, + 0x1D, 0x56, 0x22, 0x2C, 0x16, 0x26, 0x1B, 0x5F, 0x24, 0x8C, 0x11, 0x5A, 0x20, 0x62, 0x44, 0x5B, + 0x23, 0x80, 0x1E, 0x70, 0x20, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xBF, 0x04, 0xFE, 0xCF, 0x1A, 0x51, 0x0F, 0x23, + 0x23, 0x36, 0x08, 0x18, 0x0E, 0x1C, 0x26, 0x56, 0x12, 0x16, 0x27, 0x1C, 0x16, 0x26, 0x10, 0x16, + 0x26, 0x1C, 0x14, 0x27, 0x12, 0x15, 0x25, 0x1C, 0x17, 0x24, 0x10, 0x1A, 0x22, 0x1D, 0x1A, 0x20, + 0x11, 0x19, 0x23, 0x1C, 0x19, 0x22, 0x11, 0x1B, 0x22, 0x1A, 0x19, 0x23, 0x13, 0x18, 0x24, 0x1B, + 0x17, 0x25, 0x11, 0x19, 0x23, 0x1B, 0x17, 0x24, 0x12, 0x18, 0x24, 0x1B, 0x16, 0x25, 0x11, 0x19, + 0x24, 0x1B, 0x17, 0x25, 0x10, 0x1A, 0x24, 0x19, 0x18, 0x24, 0x11, 0x19, 0x25, 0x1B, 0x15, 0x26, + 0x11, 0x1A, 0x25, 0x19, 0x15, 0x27, 0x12, 0x19, 0x27, 0x18, 0x14, 0x28, 0x12, 0x18, 0x2A, 0x18, + 0x13, 0x2B, 0x10, 0x18, 0x2B, 0x16, 0x14, 0x2E, 0x0B, 0x19, 0x2D, 0x15, 0x14, 0x2D, 0x0D, 0x19, + 0x2F, 0x15, 0x12, 0x2E, 0x10, 0x16, 0x1A, 0x02, 0x15, 0x14, 0x11, 0x2E, 0x11, 0x16, 0x18, 0x06, + 0x14, 0x15, 0x10, 0x2D, 0x10, 0x16, 0x37, 0x15, 0x0D, 0x2B, 0x11, 0x19, 0x1B, 0x0D, 0x15, 0x2B, + 0x0A, 0x13, 0x11, 0x22, 0x14, 0x11, 0x18, 0x1A, 0x12, 0x1B, 0x1A, 0x15, 0x16, 0x0F, 0x1E, 0x47, + 0x15, 0x17, 0x18, 0x1C, 0x17, 0x22, 0x10, 0x15, 0x18, 0x14, 0x12, 0x16, 0x26, 0x15, 0x1B, 0x16, + 0x1E, 0x12, 0x12, 0x21, 0x21, 0x1B, 0x13, 0x13, 0x24, 0x0F, 0x0F, 0x1E, 0x10, 0x10, 0x0F, 0x10, + 0x0A, 0x08, 0x0C, 0x0F, 0x11, 0x07, 0x0D, 0x11, 0x0E, 0x1D, 0x0E, 0x13, 0x16, 0x21, 0x0E, 0x16, + 0x1A, 0x0F, 0x15, 0x18, 0x0F, 0x1F, 0x17, 0x1E, 0x0F, 0x14, 0x1D, 0x14, 0x12, 0x1A, 0x11, 0x16, + 0x24, 0x15, 0x16, 0x15, 0x1B, 0x16, 0x15, 0x15, 0x13, 0x15, 0x2F, 0x15, 0x16, 0x1F, 0x11, 0x1E, + 0x10, 0x11, 0x1B, 0x13, 0x21, 0x05, 0x0F, 0x22, 0x0A, 0x20, 0x0E, 0x17, 0x16, 0x1C, 0x10, 0x1B, + 0x0F, 0x0C, 0x08, 0x09, 0x0E, 0x21, 0x0C, 0x20, 0x0E, 0x1F, 0x26, 0x0C, 0x19, 0x15, 0x17, 0x12, + 0x21, 0x13, 0x0F, 0x1C, 0x0B, 0x1F, 0x0E, 0x18, 0x0D, 0x4D, 0x3A, 0x40, 0x47, 0xB0, 0x18, 0x72, + 0x30, 0xC7, 0x11, 0x1D, 0x1E, 0x78, 0x0E, 0x4C, 0x70, 0xFF, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFE, 0xC4, 0x0E, 0x4A, 0x1A, 0x1D, 0x0F, 0x19, 0x14, 0x19, 0x11, 0x18, 0x1B, 0x11, + 0x19, 0x13, 0x1A, 0x11, 0x1B, 0x14, 0x1A, 0x0E, 0x1C, 0x10, 0x11, 0x18, 0x0D, 0x0D, 0x09, 0x0B, + 0x0A, 0x07, 0x0D, 0x0E, 0x1B, 0x0F, 0x10, 0x17, 0x0F, 0x0C, 0x11, 0x15, 0x10, 0x0D, 0x1A, 0x0F, + 0x0F, 0x17, 0x0D, 0x0E, 0x0E, 0x0D, 0x1A, 0x0E, 0x0E, 0x1A, 0x0E, 0x10, 0x15, 0x0D, 0x0F, 0x0D, + 0x19, 0x0E, 0x0E, 0x1B, 0x0D, 0x0D, 0x19, 0x0C, 0x0E, 0x0D, 0x1B, 0x0D, 0x0D, 0x1D, 0x0C, 0x0C, + 0x0D, 0x1A, 0x0D, 0x0D, 0x1D, 0x0C, 0x0E, 0x0C, 0x03, 0x0D, 0x0B, 0x0C, 0x0D, 0x1B, 0x0C, 0x0E, + 0x1E, 0x0B, 0x0E, 0x1D, 0x0C, 0x0C, 0x0B, 0x1D, 0x0D, 0x0B, 0x20, 0x0C, 0x0D, 0x0C, 0x05, 0x0C, + 0x0B, 0x0C, 0x0C, 0x0A, 0x07, 0x0A, 0x0E, 0x0D, 0x0F, 0x02, 0x0C, 0x0C, 0x0D, 0x0C, 0x05, 0x0C, + 0x0A, 0x0C, 0x0C, 0x20, 0x0B, 0x09, 0x21, 0x0C, 0x0D, 0x1D, 0x0B, 0x0B, 0x0C, 0x22, 0x09, 0x0D, + 0x04, 0x0A, 0x0D, 0x0C, 0x0E, 0x1C, 0x0B, 0x0B, 0x0D, 0x0C, 0x20, 0x15, 0x15, 0x0C, 0x0D, 0x1D, + 0x0A, 0x0C, 0x0B, 0x0D, 0x0C, 0x09, 0x0A, 0x16, 0x15, 0x0C, 0x0D, 0x0C, 0x05, 0x0B, 0x09, 0x0B, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0A, 0x16, 0x13, 0x0B, 0x0D, 0x0D, 0x06, 0x0B, 0x08, 0x0C, 0x0B, 0x0C, + 0x0C, 0x0C, 0x0E, 0x11, 0x13, 0x0B, 0x0E, 0x10, 0x02, 0x0A, 0x09, 0x0C, 0x0B, 0x0C, 0x0C, 0x0C, + 0x0D, 0x12, 0x14, 0x0B, 0x0D, 0x0E, 0x05, 0x0A, 0x09, 0x0C, 0x0B, 0x0A, 0x0C, 0x0E, 0x0C, 0x15, + 0x12, 0x0C, 0x0E, 0x0E, 0x04, 0x0B, 0x09, 0x0B, 0x0C, 0x0A, 0x0B, 0x0D, 0x0F, 0x14, 0x16, 0x0B, + 0x0D, 0x0B, 0x06, 0x0B, 0x09, 0x0B, 0x0C, 0x0C, 0x09, 0x0C, 0x30, 0x05, 0x09, 0x0B, 0x0C, 0x0C, + 0x06, 0x0B, 0x09, 0x0B, 0x0B, 0x0B, 0x0A, 0x0B, 0x17, 0x0D, 0x0C, 0x07, 0x0A, 0x0A, 0x0B, 0x0B, + 0x07, 0x0A, 0x0A, 0x0B, 0x0B, 0x0B, 0x08, 0x0A, 0x0D, 0x18, 0x0D, 0x0A, 0x0A, 0x0A, 0x0B, 0x0B, + 0x06, 0x0B, 0x0A, 0x0B, 0x0C, 0x0B, 0x06, 0x09, 0x0E, 0x26, 0x02, 0x09, 0x0B, 0x0A, 0x0C, 0x09, + 0x08, 0x09, 0x0B, 0x0B, 0x0E, 0x15, 0x0A, 0x12, 0x08, 0x0C, 0x0B, 0x09, 0x08, 0x0C, 0x0B, 0x0A, + 0x09, 0x0A, 0x09, 0x0B, 0x0B, 0x09, 0x03, 0x09, 0x0C, 0x0E, 0x0F, 0x09, 0x0B, 0x0B, 0x0B, 0x09, + 0x0B, 0x0A, 0x07, 0x0B, 0x0B, 0x0B, 0x16, 0x0A, 0x0C, 0x19, 0x03, 0x0D, 0x06, 0x0E, 0x09, 0x0B, + 0x0C, 0x0A, 0x09, 0x0B, 0x0A, 0x0C, 0x16, 0x0A, 0x0B, 0x18, 0x04, 0x0B, 0x08, 0x0D, 0x0A, 0x0C, + 0x0C, 0x09, 0x05, 0x0C, 0x0C, 0x0B, 0x0F, 0x12, 0x0B, 0x18, 0x06, 0x0B, 0x09, 0x0C, 0x0D, 0x0C, + 0x0E, 0x19, 0x0A, 0x0C, 0x0A, 0x09, 0x08, 0x0A, 0x10, 0x0F, 0x04, 0x0B, 0x0A, 0x0A, 0x12, 0x0B, + 0x0C, 0x09, 0x08, 0x0C, 0x0B, 0x0D, 0x05, 0x07, 0x0B, 0x0F, 0x0A, 0x44, 0x0A, 0x0C, 0x0C, 0x09, + 0x04, 0x0B, 0x0C, 0x0A, 0x09, 0x07, 0x0B, 0x0B, 0x09, 0x25, 0x0E, 0x18, 0x0C, 0x0B, 0x0C, 0x08, + 0x04, 0x0C, 0x0C, 0x17, 0x0D, 0x0A, 0x18, 0x1D, 0x07, 0x15, 0x0A, 0x04, 0x09, 0x0B, 0x0B, 0x1B, + 0x0B, 0x17, 0x0B, 0x10, 0x13, 0x1A, 0x0A, 0x13, 0x0E, 0x08, 0x08, 0x0C, 0x08, 0x0A, 0x06, 0x0D, + 0x0A, 0x18, 0x0C, 0x13, 0x0A, 0x4E, 0x13, 0x0B, 0x1A, 0x0A, 0x0C, 0x19, 0x08, 0x17, 0x08, 0x19, + 0x09, 0x18, 0x0A, 0x14, 0x0E, 0x0F, 0x14, 0x1C, 0x09, 0x0A, 0x0B, 0x19, 0x0B, 0x0F, 0x11, 0x2E, + 0x06, 0x08, 0x0E, 0x19, 0x08, 0x19, 0x0B, 0x1A, 0x08, 0x0F, 0x14, 0x0E, 0x11, 0x19, 0x08, 0x19, + 0x0C, 0x12, 0x13, 0x81, 0x07, 0x17, 0x09, 0x2F, 0x1A, 0x83, 0x0B, 0x12, 0x2D, 0x1E, 0x09, 0x54, + 0x0B, 0x37, 0x2C, 0xFE, 0x30, 0x36, 0x59, 0x54, 0xFE, 0xFE, 0x09, 0x44, 0xC5, 0x1A, 0xFE, 0xFF, + 0xFE, 0xFE, 0xFE, 0xFE, 0x02, 0x03, 0x02, 0x4C, 0x03, 0x03, 0x02, 0x5B, 0x03, 0x03, 0x02, 0x35, + 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x03, 0x02, 0x03, 0x02, 0x03, 0x03, 0x02, 0x04, 0x01, 0x11, + 0x01, 0x55, 0x0F, 0x01, 0x03, 0x03, 0x02, 0x6B, 0x15, 0x03, 0x02, 0x15, 0x03, 0x02, 0x03, 0x04, + 0x01, 0x20, 0x02, 0x01, 0x04, 0x02, 0x03, 0x03, 0x02, 0x04, 0x02, 0x21, 0x03, 0x2B, 0x03, 0x02, + 0x03, 0x01, 0x04, 0x09, 0x02, 0x03, 0x02, 0x38, 0x03, 0x79, 0x19, 0x02, 0x03, 0x57, 0x03, 0x02, + 0x02, 0x2C, 0x21, 0x01, 0x03, 0x10, 0x02, 0x2C, 0x02, 0x02, 0x03, 0x02, 0x03, 0x1A, 0x02, 0x0A, + 0x07, 0x02, 0x03, 0x02, 0x03, 0x23, 0x03, 0x01, 0x03, 0x03, 0x03, 0x03, 0x03, 0x11, 0x02, 0x02, + 0x03, 0x03, 0x02, 0x3B, 0x01, 0x11, 0x02, 0x04, 0x01, 0x03, 0x02, 0x1B, 0x07, 0x1C, 0x03, 0x02, + 0x04, 0x01, 0x0A, 0x01, 0x03, 0x1E, 0x02, 0x04, 0x01, 0x19, 0x01, 0x09, 0x04, 0x01, 0x03, 0x03, + 0x02, 0x04, 0x02, 0x22, 0x0A, 0x02, 0x02, 0x11, 0x02, 0x03, 0x02, 0x03, 0x03, 0x02, 0x03, 0x03, + 0x03, 0x03, 0x02, 0x03, 0x02, 0x03, 0x0F, 0x16, 0x02, 0x02, 0x03, 0x03, 0x01, 0x10, 0x02, 0x02, + 0x03, 0x02, 0x03, 0x02, 0x02, 0x0A, 0x03, 0x02, 0x03, 0x1E, 0x03, 0x01, 0x09, 0x10, 0x03, 0x01, + 0x09, 0x02, 0x03, 0x0E, 0x03, 0x01, 0x04, 0x02, 0x02, 0x03, 0x01, 0x11, 0x02, 0x39, 0x0C, 0x02, + 0x03, 0x29, 0x01, 0x02, 0x09, 0x01, 0x03, 0x03, 0x03, 0x03, 0x02, 0x22, 0x03, 0x1C, 0x14, 0x1A, + 0x03, 0x02, 0x03, 0x03, 0x03, 0xFE, 0x5B, 0x1A, 0x1C, 0x05, 0x05, 0x0C, 0x25, 0x08, 0x23, 0x1E, + 0x09, 0x03, 0x0E, 0x07, 0x05, 0x0A, 0x1A, 0x08, 0x05, 0x06, 0x1B, 0x2B, 0x0B, 0x02, 0x0D, 0x08, + 0x06, 0x04, 0x1A, 0x17, 0x18, 0x20, 0x02, 0x0B, 0x0A, 0x03, 0x0C, 0x08, 0x06, 0x04, 0x1A, 0x0B, + 0x23, 0x22, 0x01, 0x05, 0x02, 0x03, 0x0B, 0x03, 0x0C, 0x09, 0x06, 0x03, 0x19, 0x14, 0x19, 0x18, + 0x02, 0x09, 0x03, 0x04, 0x02, 0x04, 0x0A, 0x04, 0x07, 0x0B, 0x25, 0x12, 0x1B, 0x16, 0x06, 0x08, + 0x03, 0x06, 0x01, 0x03, 0x0B, 0x04, 0x06, 0x0B, 0x1A, 0x14, 0x23, 0x16, 0x07, 0x0B, 0x03, 0x0A, + 0x0B, 0x04, 0x06, 0x0B, 0x18, 0x16, 0x19, 0x1F, 0x0D, 0x0C, 0x02, 0x05, 0x03, 0x02, 0x0B, 0x04, + 0x04, 0x0C, 0x19, 0x15, 0x1B, 0x18, 0x13, 0x0F, 0x01, 0x05, 0x04, 0x01, 0x0B, 0x05, 0x03, 0x0C, + 0x1A, 0x14, 0x18, 0x15, 0x15, 0x13, 0x04, 0x03, 0x04, 0x01, 0x0B, 0x05, 0x04, 0x0B, 0x18, 0x16, + 0x17, 0x16, 0x15, 0x17, 0x05, 0x03, 0x11, 0x06, 0x04, 0x09, 0x18, 0x16, 0x17, 0x15, 0x16, 0x1A, + 0x05, 0x03, 0x11, 0x05, 0x06, 0x07, 0x1A, 0x17, 0x1A, 0x10, 0x19, 0x1A, 0x1A, 0x05, 0x29, 0x16, + 0x17, 0x14, 0x19, 0x16, 0x04, 0x07, 0x1A, 0x05, 0x06, 0x08, 0x1A, 0x17, 0x0C, 0x16, 0x41, 0x08, + 0x0B, 0x02, 0x0C, 0x07, 0x06, 0x07, 0x19, 0x16, 0x0E, 0x1A, 0x4E, 0x0B, 0x0A, 0x03, 0x0A, 0x16, + 0x19, 0x17, 0x18, 0x17, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xEE, 0x02, 0x03, 0x03, 0xFE, 0x3D, 0x08, 0x01, 0x03, 0x02, 0x03, 0x03, + 0x02, 0x03, 0x02, 0x65, 0x1A, 0xCF, 0x02, 0x3E, 0x02, 0x03, 0x02, 0x03, 0x02, 0x03, 0x02, 0x03, + 0x03, 0x3E, 0x03, 0x02, 0x02, 0x03, 0x02, 0x04, 0x02, 0x1B, 0x02, 0x16, 0x02, 0x03, 0x02, 0x5F, + 0x08, 0x01, 0x03, 0x03, 0x02, 0x04, 0x02, 0xED, 0x02, 0x03, 0x02, 0x16, 0x02, 0xFE, 0x4A, 0x40, + 0xE7, 0x14, 0x6F, 0x02, 0x03, 0x02, 0x80, 0x02, 0x03, 0x02, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, 0xFE, + 0x7C, 0x24, 0x9B, 0x03, 0x02, 0x03, 0x03, 0x02, 0x03, 0x03, 0x0E, 0x08, 0x01, 0x03, 0x52, 0x02, + 0x03, 0x09, 0x01, 0x03, 0x03, 0x02, 0x03, 0x03, 0x03, 0x01, 0x0A, 0x01, 0x03, 0x02, 0x1F, 0x02, + 0x02, 0x03, 0x03, 0x07, 0x14, 0x02, 0x03, 0x02, 0x03, 0x03, 0x3A, 0x03, 0x02, 0x09, 0x02, 0x02, + 0x3B, 0x01, 0x16, 0x01, 0x03, 0x03, 0x01, 0x03, 0x02, 0x03, 0x23, 0x03, 0x02, 0x03, 0x02, 0x03, + 0x03, 0x04, 0x02, 0x02, 0x1E, 0x08, 0x0E, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, 0x02, 0x41, 0x01, + 0x03, 0x02, 0x11, 0x1A, 0x02, 0x17, 0x02, 0x03, 0x03, 0x02, 0x0D, 0x03, 0x01, 0x03, 0x02, 0x03, + 0x02, 0x04, 0x01, 0x03, 0x10, 0x01, 0x03, 0x01, 0x34, 0x02, 0x03, 0x02, 0x04, 0x19, 0x01, 0x03, + 0x01, 0x0F, 0x02, 0x03, 0x02, 0x03, 0x02, 0x03, 0x02, 0x02, 0x10, 0x01, 0x03, 0x02, 0x03, 0x02, + 0x16, 0x02, 0x03, 0x02, 0x03, 0x03, 0x03, 0x02, 0x0E, 0x02, 0x02, 0x02, 0x03, 0x01, 0x0B, 0x02, + 0x02, 0x02, 0x03, 0x02, 0x03, 0x01, 0x0A, 0x03, 0x01, 0x03, 0x03, 0x02, 0x18, 0x02, 0x03, 0x02, + 0x1D, 0x02, 0x04, 0x01, 0x03, 0x03, 0x03, 0x02, 0x03, 0x02, 0x03, 0x03, 0x02, 0x03, 0x02, 0x03, + 0x02, 0x03, 0x15, 0x02, 0x03, 0x02, 0x04, 0x01, 0x0C, 0x02, 0x10, 0x02, 0x03, 0x02, 0x1A, 0x02, + 0x0F, 0x02, 0x02, 0x03, 0x01, 0x04, 0x02, 0x02, 0x14, 0x03, 0x02, 0x03, 0x02, 0x03, 0x04, 0x01, + 0x0B, 0x01, 0x03, 0x02, 0x03, 0x02, 0x15, 0x02, 0x02, 0x03, 0x03, 0x02, 0x05, 0x02, 0x02, 0x03, + 0x02, 0x03, 0x02, 0x03, 0x12, 0x01, 0x03, 0x02, 0x03, 0x02, 0x03, 0x01, 0x04, 0x01, 0x03, 0x01, + 0x16, 0x02, 0x28, 0x02, 0x02, 0x0E, 0x02, 0x03, 0x19, 0x03, 0x02, 0x03, 0x02, 0x02, 0x11, 0x01, + 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x04, 0x02, 0x04, 0x01, 0x04, 0x01, 0x04, 0x01, 0x18, 0x02, + 0x04, 0x02, 0x03, 0x02, 0x04, 0x01, 0x0A, 0x01, 0x0C, 0x02, 0x01, 0x0B, 0x02, 0x03, 0x02, 0x04, + 0x01, 0x03, 0x02, 0x03, 0x03, 0x03, 0x02, 0x03, 0x02, 0x03, 0x02, 0x02, 0x26, 0x02, 0x10, 0x02, + 0x0F, 0x03, 0x01, 0x04, 0x02, 0x03, 0x02, 0x03, 0x02, 0x02, 0x03, 0x02, 0x0B, 0x03, 0x02, 0x03, + 0x19, 0x02, 0x03, 0x02, 0x2A, 0x03, 0x01, 0x04, 0x01, 0x03, 0x03, 0x03, 0x34, 0x02, 0x02, 0x02, + 0x03, 0x02, 0x08, 0x02, 0x03, 0x03, 0x03, 0x02, 0x0C, 0x03, 0x01, 0x03, 0x02, 0x02, 0x14, 0x02, + 0x03, 0x02, 0x03, 0x01, 0x04, 0x02, 0x03, 0x02, 0x03, 0x02, 0x03, 0x01, 0x0C, 0x02, 0x02, 0x03, + 0x03, 0x01, 0x3E, 0x03, 0x01, 0x11, 0x02, 0x03, 0x02, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x03, + 0x02, 0x03, 0x03, 0x02, 0x2D, 0x02, 0x35, 0x37, 0x5D, 0x22, 0x1C, 0x15, 0x1D, 0x15, 0x1A, 0x28, + 0x13, 0x01, 0x05, 0x09, 0x0C, 0x02, 0x0C, 0x02, 0x0B, 0x0A, 0x22, 0x21, 0x03, 0x05, 0x0B, 0x0A, + 0x05, 0x08, 0x0C, 0x02, 0x0A, 0x0E, 0x02, 0x05, 0x18, 0x1A, 0x06, 0x10, 0x01, 0x05, 0x0B, 0x03, + 0x0C, 0x08, 0x0C, 0x03, 0x0A, 0x0B, 0x04, 0x05, 0x0A, 0x04, 0x0B, 0x05, 0x13, 0x03, 0x03, 0x11, + 0x02, 0x04, 0x0B, 0x0C, 0x03, 0x08, 0x19, 0x0D, 0x14, 0x15, 0x13, 0x14, 0x02, 0x02, 0x04, 0x03, + 0x0B, 0x16, 0x11, 0x02, 0x04, 0x0B, 0x17, 0x13, 0x0D, 0x1C, 0x0B, 0x03, 0x0A, 0x16, 0x11, 0x12, + 0x17, 0x09, 0x18, 0x1E, 0x0A, 0x05, 0x08, 0x0E, 0x05, 0x02, 0x11, 0x13, 0x18, 0x07, 0x17, 0x14, + 0x02, 0x09, 0x03, 0x03, 0x03, 0x04, 0x0A, 0x15, 0x10, 0x14, 0x0F, 0x01, 0x06, 0x08, 0x17, 0x14, + 0x02, 0x0B, 0x0A, 0x03, 0x09, 0x13, 0x12, 0x14, 0x0E, 0x02, 0x06, 0x08, 0x17, 0x0A, 0x06, 0x02, + 0x05, 0x0D, 0x08, 0x03, 0x09, 0x0F, 0x14, 0x15, 0x0F, 0x12, 0x16, 0x09, 0x0E, 0x0F, 0x14, 0x0F, + 0x14, 0x16, 0x0D, 0x12, 0x17, 0x0A, 0x0F, 0x0E, 0x09, 0x02, 0x09, 0x0D, 0x15, 0x0E, 0x03, 0x05, + 0x0B, 0x14, 0x0C, 0x13, 0x0A, 0x15, 0x0B, 0x03, 0x08, 0x0C, 0x16, 0x0C, 0x07, 0x03, 0x0B, 0x0A, + 0x15, 0x14, 0x0C, 0x14, 0x0C, 0x04, 0x07, 0x0B, 0x17, 0x0B, 0x08, 0x05, 0x0A, 0x08, 0x08, 0x05, + 0x08, 0x09, 0x07, 0x05, 0x09, 0x16, 0x0A, 0x06, 0x08, 0x0B, 0x18, 0x0B, 0x05, 0x06, 0x0C, 0x07, + 0x06, 0x08, 0x18, 0x06, 0x0A, 0x08, 0x03, 0x0A, 0x0B, 0x05, 0x08, 0x0C, 0x17, 0x0C, 0x04, 0x07, + 0x0A, 0x09, 0x05, 0x08, 0x09, 0x08, 0x06, 0x08, 0x08, 0x09, 0x04, 0x09, 0x0B, 0x05, 0x08, 0x0B, + 0x18, 0x0C, 0x04, 0x07, 0x0C, 0x05, 0x08, 0x09, 0x07, 0x07, 0x0A, 0x09, 0x05, 0x09, 0x06, 0x09, + 0x0A, 0x06, 0x09, 0x0B, 0x0B, 0x02, 0x0C, 0x0A, 0x03, 0x09, 0x0B, 0x04, 0x08, 0x0C, 0x04, 0x07, + 0x0B, 0x05, 0x08, 0x0B, 0x05, 0x08, 0x0B, 0x07, 0x08, 0x0B, 0x09, 0x05, 0x0E, 0x06, 0x07, 0x0D, + 0x15, 0x17, 0x17, 0x0C, 0x08, 0x08, 0x0B, 0x06, 0x0C, 0x0A, 0x0A, 0x04, 0x1B, 0x0D, 0x16, 0x19, + 0x1D, 0x05, 0x0E, 0x08, 0x0A, 0x05, 0x3F, 0x31, 0x43, 0x20, 0xB4, 0x23, 0xBE, 0x39, 0x80, 0x16, + 0x10, 0x24, 0xFE, 0xBF, 0x81, 0x75, 0x19, 0x73, 0x11, 0x5C, 0x1C, 0xE7, 0x1B, 0xA1, 0x15, 0x38, + 0x1C, 0x83, 0x2E, 0x42, 0x1A, 0x8B, 0x16, 0x5B, 0x3B, 0xEC, 0x31, 0xFE, 0x47, 0x75, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, 0xFE, 0xFE, 0x20, 0x03, 0x04, 0x05, 0x17, 0x1A, 0x22, 0x0A, 0x1F, + 0x1B, 0x3E, 0x1C, 0x18, 0x12, 0x20, 0x10, 0x1F, 0x0A, 0x17, 0x18, 0x05, 0x02, 0x19, 0x13, 0x17, + 0x17, 0x3A, 0x1B, 0x19, 0x12, 0x16, 0x09, 0x02, 0x09, 0x1E, 0x1B, 0x03, 0x0F, 0x02, 0x06, 0x19, + 0x04, 0x22, 0x05, 0x04, 0x0D, 0x26, 0x18, 0x01, 0x06, 0x03, 0x05, 0x03, 0x02, 0x13, 0x04, 0x05, + 0x05, 0x18, 0x04, 0x03, 0x15, 0x1F, 0x23, 0x02, 0x06, 0x02, 0x03, 0x06, 0x01, 0x0A, 0x04, 0x05, + 0x05, 0x18, 0x03, 0x07, 0x09, 0x03, 0x06, 0x1E, 0x19, 0x01, 0x07, 0x03, 0x05, 0x18, 0x05, 0x04, + 0x07, 0x16, 0x02, 0x07, 0x09, 0x05, 0x04, 0x20, 0x1F, 0x03, 0x05, 0x19, 0x04, 0x05, 0x07, 0x16, + 0x02, 0x0F, 0x02, 0x05, 0x05, 0x1D, 0x1D, 0x03, 0x05, 0x04, 0x02, 0x12, 0x05, 0x03, 0x13, 0x1B, + 0x0D, 0x20, 0x1A, 0x03, 0x04, 0x05, 0x01, 0x14, 0x03, 0x04, 0x05, 0x21, 0x03, 0x03, 0x11, 0x1B, + 0x1A, 0x02, 0x05, 0x04, 0x02, 0x1B, 0x15, 0x24, 0x09, 0x1F, 0x0F, 0x02, 0x06, 0x03, 0x02, 0x1B, + 0x19, 0x24, 0x09, 0x20, 0x0A, 0x02, 0x06, 0x03, 0x03, 0x1A, 0x04, 0x05, 0x16, 0x3D, 0x04, 0x03, + 0x0B, 0x03, 0x05, 0x03, 0x04, 0x1B, 0x02, 0x09, 0x15, 0x04, 0x03, 0x23, 0x25, 0x03, 0x05, 0x03, + 0x04, 0x2F, 0x13, 0x05, 0x02, 0x22, 0x03, 0x04, 0x04, 0x04, 0x12, 0x02, 0x06, 0x03, 0x04, 0x1C, + 0x01, 0x07, 0x03, 0x07, 0x11, 0x4A, 0x08, 0x02, 0x06, 0x03, 0x04, 0x25, 0x1D, 0x24, 0x0B, 0x03, + 0x03, 0x13, 0x0B, 0x03, 0x05, 0x03, 0x05, 0x23, 0x12, 0x27, 0x06, 0x03, 0x06, 0x17, 0x03, 0x03, + 0x10, 0x03, 0x05, 0x03, 0x04, 0x24, 0x04, 0x06, 0x04, 0x13, 0x03, 0x0A, 0x02, 0x04, 0x10, 0x24, + 0x11, 0x02, 0x05, 0x04, 0x04, 0x25, 0x03, 0x22, 0x04, 0x3F, 0x16, 0x03, 0x05, 0x03, 0x04, 0x47, + 0x10, 0x06, 0x03, 0x08, 0x44, 0x03, 0x04, 0x04, 0x04, 0x47, 0x05, 0x06, 0x04, 0x04, 0x04, 0x06, + 0x05, 0x39, 0x11, 0x02, 0x06, 0x02, 0x05, 0x2E, 0x05, 0x04, 0x13, 0x10, 0x1D, 0x3D, 0x07, 0x03, + 0x05, 0x03, 0x04, 0x39, 0x38, 0x50, 0x04, 0x03, 0x05, 0x03, 0x04, 0x04, 0x02, 0x1D, 0x02, 0x05, + 0x2C, 0x11, 0x03, 0x09, 0x02, 0x06, 0x01, 0x2D, 0x1E, 0x02, 0x05, 0x03, 0x05, 0x03, 0x02, 0x04, + 0x02, 0x1E, 0x09, 0x03, 0x03, 0x04, 0x03, 0x25, 0x03, 0x0B, 0x01, 0x05, 0x10, 0x1E, 0x03, 0x06, + 0x34, 0x32, 0x1E, 0x80, 0x04, 0x03, 0x05, 0x03, 0x05, 0xD7, 0x19, 0x03, 0x18, 0xF5, 0x1B, 0xFE, + 0xFE, 0x0D, 0x8F, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, 0xFE, 0xFE, 0x0A, 0x1A, 0xB8, 0x1B, 0xB9, 0x0D, 0x5F, 0x2F, + 0x33, 0x0D, 0x1A, 0x08, 0x1C, 0x09, 0x18, 0x0C, 0x55, 0x09, 0x0C, 0x07, 0x0A, 0x07, 0x11, 0x03, + 0x0A, 0x0A, 0x0E, 0x15, 0x0B, 0x0A, 0x1B, 0x13, 0x0E, 0x0A, 0x0B, 0x09, 0x0A, 0x07, 0x0E, 0x06, + 0x0A, 0x09, 0x0B, 0x0B, 0x05, 0x07, 0x0A, 0x09, 0x1C, 0x14, 0x09, 0x0E, 0x0B, 0x0B, 0x09, 0x06, + 0x0E, 0x07, 0x0A, 0x09, 0x0A, 0x18, 0x09, 0x0A, 0x1D, 0x0F, 0x0B, 0x0D, 0x0C, 0x0A, 0x09, 0x08, + 0x0B, 0x0B, 0x07, 0x0A, 0x0A, 0x0C, 0x56, 0x0D, 0x0C, 0x0A, 0x09, 0x08, 0x0C, 0x0A, 0x0A, 0x07, + 0x0B, 0x0C, 0x0F, 0x07, 0x2B, 0x0B, 0x07, 0x0B, 0x09, 0x0B, 0x0A, 0x09, 0x0C, 0x08, 0x0B, 0x09, + 0x0A, 0x0B, 0x0B, 0x08, 0x3E, 0x0A, 0x0A, 0x0B, 0x0C, 0x09, 0x0C, 0x08, 0x0B, 0x0A, 0x09, 0x0A, + 0x09, 0x0A, 0x0D, 0x09, 0x23, 0x0B, 0x0A, 0x0C, 0x0B, 0x07, 0x0D, 0x08, 0x0C, 0x09, 0x09, 0x0A, + 0x0B, 0x0B, 0x3B, 0x0B, 0x08, 0x0C, 0x0A, 0x09, 0x0C, 0x0A, 0x0B, 0x08, 0x0A, 0x0A, 0x0A, 0x0B, + 0x22, 0x09, 0x0D, 0x0D, 0x06, 0x0C, 0x0B, 0x09, 0x0C, 0x09, 0x0B, 0x09, 0x0A, 0x09, 0x0B, 0x0A, + 0x24, 0x08, 0x0D, 0x09, 0x0A, 0x0C, 0x0C, 0x08, 0x0B, 0x0A, 0x0B, 0x0A, 0x09, 0x0A, 0x09, 0x0B, + 0x10, 0x06, 0x0D, 0x08, 0x0F, 0x08, 0x0B, 0x0B, 0x0D, 0x08, 0x0B, 0x0A, 0x0B, 0x09, 0x09, 0x0A, + 0x0A, 0x0B, 0x0F, 0x07, 0x0C, 0x0A, 0x0C, 0x09, 0x0B, 0x0C, 0x0D, 0x07, 0x0C, 0x0A, 0x0A, 0x0A, + 0x0A, 0x09, 0x0A, 0x0A, 0x0F, 0x09, 0x0B, 0x0A, 0x0F, 0x0C, 0x06, 0x0C, 0x0C, 0x08, 0x0C, 0x0A, + 0x0A, 0x0A, 0x09, 0x0B, 0x09, 0x0B, 0x0C, 0x09, 0x0D, 0x0A, 0x0E, 0x0F, 0x07, 0x0C, 0x0B, 0x09, + 0x0B, 0x0A, 0x0B, 0x09, 0x09, 0x0A, 0x0A, 0x0B, 0x0C, 0x0A, 0x0D, 0x09, 0x15, 0x0C, 0x05, 0x04, + 0x02, 0x05, 0x0E, 0x06, 0x0C, 0x0A, 0x0B, 0x09, 0x09, 0x0B, 0x0A, 0x0A, 0x0C, 0x08, 0x0E, 0x08, + 0x0F, 0x05, 0x04, 0x0C, 0x0A, 0x09, 0x0B, 0x08, 0x0B, 0x0A, 0x0B, 0x09, 0x09, 0x0A, 0x0A, 0x0B, + 0x44, 0x0D, 0x06, 0x0C, 0x0D, 0x06, 0x0C, 0x0A, 0x0B, 0x09, 0x09, 0x0B, 0x0B, 0x0B, 0x45, 0x0C, + 0x0B, 0x0A, 0x0B, 0x07, 0x0C, 0x09, 0x0B, 0x08, 0x0A, 0x0A, 0x0C, 0x0B, 0x22, 0x09, 0x0D, 0x19, + 0x08, 0x0B, 0x11, 0x02, 0x0C, 0x0A, 0x0B, 0x08, 0x08, 0x0B, 0x0C, 0x0B, 0x3B, 0x1A, 0x06, 0x0C, + 0x21, 0x0A, 0x0B, 0x1C, 0x0D, 0x0C, 0x3A, 0x1B, 0x07, 0x0B, 0x0D, 0x07, 0x0C, 0x09, 0x0E, 0x09, + 0x4D, 0x09, 0x0D, 0x0F, 0x04, 0x0B, 0x08, 0x0A, 0x0D, 0x07, 0x0C, 0x0A, 0x0B, 0x0A, 0x0A, 0x0C, + 0x64, 0x0B, 0x09, 0x0A, 0x0B, 0x09, 0x0B, 0x09, 0x0C, 0x09, 0x0A, 0x08, 0x0B, 0x0B, 0x57, 0x0C, + 0x06, 0x0B, 0x0B, 0x09, 0x0C, 0x06, 0x0C, 0x09, 0x0A, 0x0A, 0x0A, 0x08, 0x0A, 0x09, 0x45, 0x0D, + 0x0B, 0x0B, 0x0A, 0x09, 0x0A, 0x09, 0x0A, 0x0A, 0x09, 0x09, 0x0C, 0x09, 0x65, 0x08, 0x0A, 0x0B, + 0x0B, 0x05, 0x0D, 0x08, 0xA4, 0x08, 0x0B, 0x0A, 0x0B, 0x05, 0x0E, 0x05, 0x0D, 0x05, 0x98, 0x08, + 0x0B, 0x0B, 0x0A, 0x04, 0x99, 0x1A, 0x08, 0x0E, 0x0B, 0x06, 0x10, 0x03, 0x1B, 0x0A, 0x2C, 0x09, + 0x5E, 0x12, 0x0C, 0x06, 0xD9, 0x09, 0x1A, 0x08, 0x3C, 0x07, 0x87, 0x07, 0x0E, 0x02, 0x09, 0x09, + 0x59, 0x07, 0x6A, 0x07, 0x1A, 0x16, 0xC4, 0x07, 0xFA, 0x07, 0xFE, 0x03, 0x17, 0xFE, 0xFE, 0xFE, + 0xFF, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0x9C, 0x6F, 0xFE, 0x97, 0x39, 0x70, + 0x18, 0x16, 0x0D, 0x17, 0x18, 0x1B, 0x12, 0x17, 0x15, 0x17, 0x0E, 0x18, 0x12, 0x20, 0x16, 0x0A, + 0x0B, 0x02, 0x06, 0x10, 0x04, 0x03, 0x0D, 0x15, 0x14, 0x14, 0x15, 0x0A, 0x05, 0x05, 0x0D, 0x04, + 0x03, 0x10, 0x06, 0x03, 0x0B, 0x16, 0x13, 0x14, 0x0C, 0x16, 0x18, 0x04, 0x03, 0x0F, 0x07, 0x04, + 0x0A, 0x15, 0x14, 0x14, 0x13, 0x0F, 0x1A, 0x16, 0x09, 0x03, 0x09, 0x15, 0x14, 0x15, 0x0A, 0x16, + 0x1A, 0x16, 0x08, 0x04, 0x09, 0x15, 0x14, 0x16, 0x08, 0x16, 0x17, 0x05, 0x02, 0x05, 0x01, 0x08, + 0x08, 0x03, 0x09, 0x15, 0x15, 0x15, 0x0A, 0x15, 0x17, 0x06, 0x02, 0x0D, 0x09, 0x03, 0x09, 0x15, + 0x0E, 0x1C, 0x09, 0x15, 0x07, 0x03, 0x0A, 0x04, 0x03, 0x03, 0x02, 0x08, 0x08, 0x03, 0x0A, 0x14, + 0x15, 0x0E, 0x0F, 0x15, 0x06, 0x04, 0x09, 0x06, 0x07, 0x09, 0x08, 0x03, 0x09, 0x15, 0x13, 0x15, + 0x09, 0x07, 0x02, 0x0C, 0x04, 0x04, 0x0A, 0x05, 0x07, 0x0A, 0x06, 0x04, 0x0A, 0x13, 0x14, 0x15, + 0x09, 0x07, 0x04, 0x09, 0x12, 0x06, 0x01, 0x04, 0x01, 0x09, 0x07, 0x04, 0x09, 0x14, 0x14, 0x15, + 0x0A, 0x1D, 0x08, 0x04, 0x03, 0x03, 0x02, 0x09, 0x06, 0x04, 0x0A, 0x13, 0x15, 0x15, 0x14, 0x12, + 0x08, 0x04, 0x03, 0x03, 0x02, 0x09, 0x07, 0x03, 0x0A, 0x14, 0x15, 0x15, 0x0B, 0x04, 0x06, 0x0F, + 0x08, 0x04, 0x04, 0x0E, 0x06, 0x04, 0x09, 0x14, 0x15, 0x16, 0x14, 0x0F, 0x08, 0x04, 0x09, 0x08, + 0x07, 0x04, 0x0A, 0x13, 0x15, 0x15, 0x07, 0x1F, 0x07, 0x03, 0x05, 0x03, 0x01, 0x09, 0x06, 0x04, + 0x09, 0x14, 0x14, 0x17, 0x06, 0x06, 0x06, 0x12, 0x08, 0x03, 0x09, 0x09, 0x06, 0x04, 0x0A, 0x13, + 0x15, 0x16, 0x07, 0x06, 0x05, 0x13, 0x07, 0x04, 0x09, 0x09, 0x05, 0x05, 0x0A, 0x13, 0x15, 0x16, + 0x06, 0x08, 0x03, 0x14, 0x08, 0x03, 0x09, 0x09, 0x06, 0x04, 0x0A, 0x14, 0x14, 0x15, 0x15, 0x13, + 0x09, 0x03, 0x05, 0x0E, 0x04, 0x05, 0x09, 0x14, 0x14, 0x16, 0x14, 0x15, 0x09, 0x03, 0x05, 0x0F, + 0x04, 0x05, 0x0B, 0x12, 0x14, 0x17, 0x13, 0x15, 0x0B, 0x03, 0x05, 0x0E, 0x04, 0x05, 0x0A, 0x14, + 0x14, 0x16, 0x13, 0x18, 0x0B, 0x03, 0x04, 0x10, 0x05, 0x03, 0x0B, 0x14, 0x13, 0x18, 0x0E, 0x16, + 0x11, 0x04, 0x04, 0x0F, 0x05, 0x03, 0x0A, 0x14, 0x15, 0x16, 0x11, 0x18, 0x10, 0x03, 0x04, 0x0F, + 0x05, 0x04, 0x07, 0x17, 0x05, 0x02, 0x0F, 0x16, 0x14, 0x17, 0x06, 0x01, 0x08, 0x03, 0x04, 0x10, + 0x04, 0x04, 0x0C, 0x13, 0x03, 0x04, 0x12, 0x13, 0x10, 0x17, 0x16, 0x04, 0x03, 0x10, 0x04, 0x05, + 0x0B, 0x13, 0x03, 0x04, 0x10, 0x14, 0x03, 0x04, 0x0C, 0x1F, 0x11, 0x03, 0x04, 0x10, 0x04, 0x04, + 0x0C, 0x13, 0x04, 0x02, 0x11, 0x18, 0x16, 0x16, 0x19, 0x02, 0x04, 0x1A, 0x06, 0x20, 0x17, 0x16, + 0x16, 0x16, 0x19, 0x04, 0x03, 0x11, 0x03, 0x05, 0x06, 0x18, 0x03, 0x0A, 0x12, 0x17, 0x19, 0x12, + 0x21, 0x04, 0x04, 0x1A, 0x04, 0x28, 0x11, 0x18, 0x16, 0x16, 0x14, 0x0C, 0x07, 0x02, 0x05, 0x19, + 0x05, 0x22, 0x17, 0x05, 0x02, 0x0D, 0x1A, 0x1E, 0x0B, 0x15, 0x05, 0x03, 0x05, 0x1A, 0x03, 0x05, + 0x0B, 0x09, 0x02, 0x0B, 0x1F, 0x40, 0x11, 0x13, 0x07, 0x02, 0x05, 0x04, 0x02, 0x12, 0x15, 0x20, + 0x11, 0x19, 0x10, 0x22, 0x2D, 0x03, 0x05, 0x19, 0x05, 0x03, 0x0C, 0x1F, 0x17, 0x4A, 0x2E, 0x03, + 0x05, 0x19, 0x03, 0x3E, 0x11, 0x22, 0x0B, 0x44, 0x18, 0x1B, 0x17, 0x1B, 0x19, 0x19, 0x12, 0x4B, + 0x1D, 0xE1, 0x27, 0xF4, 0x1E, 0x3C, 0x49, 0xFE, 0xFF, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xAA, 0x1C, 0xD0, 0x2F, 0x09, 0x03, 0x0F, 0x03, 0x04, 0x01, 0x0A, 0x03, 0x42, 0x17, 0x23, 0x16, + 0x1C, 0x0E, 0x10, 0x10, 0x32, 0x09, 0x12, 0x0F, 0x03, 0x02, 0x26, 0x02, 0x22, 0x13, 0x81, 0x08, + 0x20, 0x2B, 0x11, 0x0F, 0x16, 0x03, 0x2C, 0x04, 0x21, 0x03, 0x09, 0x08, 0x08, 0x04, 0x10, 0x0D, + 0x06, 0x06, 0x04, 0x03, 0x3A, 0x03, 0x09, 0x06, 0x06, 0x09, 0x02, 0x15, 0x11, 0x18, 0x4F, 0x02, + 0x1A, 0x1A, 0x09, 0x14, 0x18, 0x06, 0x04, 0x05, 0x1A, 0x05, 0x05, 0x3E, 0x0B, 0x10, 0x19, 0x07, + 0x24, 0x06, 0x0A, 0x04, 0x04, 0x0B, 0x02, 0x1B, 0x10, 0x11, 0x18, 0x08, 0x18, 0x04, 0x08, 0x05, + 0x0A, 0x18, 0x01, 0x1A, 0x09, 0x09, 0x03, 0x06, 0x17, 0x07, 0x19, 0x05, 0x08, 0x05, 0x11, 0x1E, + 0x02, 0x0C, 0x08, 0x09, 0x02, 0x08, 0x0D, 0x03, 0x05, 0x07, 0x18, 0x04, 0x0A, 0x04, 0x17, 0x1C, + 0x01, 0x0C, 0x08, 0x0A, 0x01, 0x09, 0x0C, 0x04, 0x05, 0x06, 0x17, 0x05, 0x0B, 0x03, 0x0B, 0x04, + 0x0A, 0x06, 0x05, 0x12, 0x01, 0x06, 0x03, 0x01, 0x07, 0x15, 0x0E, 0x03, 0x03, 0x08, 0x16, 0x08, + 0x16, 0x06, 0x07, 0x06, 0x0A, 0x12, 0x02, 0x05, 0x03, 0x02, 0x05, 0x15, 0x0E, 0x10, 0x17, 0x06, + 0x17, 0x08, 0x07, 0x03, 0x0B, 0x13, 0x03, 0x04, 0x03, 0x03, 0x03, 0x15, 0x0D, 0x12, 0x18, 0x04, + 0x17, 0x08, 0x16, 0x15, 0x02, 0x06, 0x02, 0x04, 0x03, 0x0E, 0x01, 0x04, 0x0E, 0x0B, 0x02, 0x03, + 0x1A, 0x04, 0x17, 0x08, 0x15, 0x14, 0x03, 0x04, 0x03, 0x04, 0x02, 0x0F, 0x01, 0x03, 0x0E, 0x10, + 0x17, 0x07, 0x17, 0x09, 0x0F, 0x1A, 0x02, 0x06, 0x03, 0x03, 0x03, 0x0D, 0x04, 0x01, 0x11, 0x07, + 0x23, 0x03, 0x16, 0x09, 0x07, 0x04, 0x05, 0x19, 0x02, 0x06, 0x02, 0x03, 0x04, 0x0C, 0x04, 0x02, + 0x0F, 0x09, 0x05, 0x03, 0x14, 0x09, 0x14, 0x08, 0x07, 0x07, 0x06, 0x15, 0x03, 0x04, 0x03, 0x02, + 0x05, 0x0C, 0x05, 0x02, 0x0F, 0x08, 0x04, 0x04, 0x14, 0x09, 0x15, 0x08, 0x06, 0x07, 0x09, 0x13, + 0x05, 0x03, 0x0C, 0x0A, 0x05, 0x04, 0x0D, 0x09, 0x02, 0x05, 0x13, 0x0B, 0x14, 0x0B, 0x02, 0x07, + 0x09, 0x16, 0x06, 0x02, 0x0B, 0x0B, 0x06, 0x03, 0x0C, 0x08, 0x05, 0x06, 0x09, 0x03, 0x07, 0x07, + 0x15, 0x09, 0x09, 0x03, 0x0A, 0x0A, 0x02, 0x0A, 0x06, 0x06, 0x07, 0x0A, 0x06, 0x04, 0x0A, 0x06, + 0x07, 0x06, 0x08, 0x06, 0x08, 0x06, 0x09, 0x05, 0x06, 0x0E, 0x13, 0x05, 0x09, 0x0A, 0x04, 0x08, + 0x06, 0x05, 0x09, 0x05, 0x18, 0x07, 0x06, 0x07, 0x08, 0x08, 0x06, 0x09, 0x05, 0x18, 0x11, 0x08, + 0x05, 0x07, 0x05, 0x07, 0x07, 0x08, 0x05, 0x08, 0x08, 0x04, 0x08, 0x07, 0x07, 0x05, 0x08, 0x08, + 0x15, 0x17, 0x14, 0x0B, 0x11, 0x07, 0x07, 0x05, 0x15, 0x07, 0x08, 0x02, 0x0C, 0x04, 0x07, 0x07, + 0x09, 0x05, 0x05, 0x1B, 0x08, 0x12, 0x3E, 0x09, 0x58, 0x09, 0x15, 0x0C, 0x13, 0x04, 0x3C, 0x09, + 0x20, 0x07, 0x7B, 0x16, 0x05, 0x26, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0x40, 0x04, 0x3B, 0x07, 0xCB, + 0x03, 0x07, 0x08, 0x72, 0x08, 0x19, 0x07, 0x04, 0x08, 0x03, 0x03, 0x0F, 0x03, 0x01, 0x03, 0x03, + 0x02, 0x02, 0x03, 0x02, 0x02, 0x2A, 0x02, 0x18, 0x02, 0x03, 0x02, 0x5D, 0x09, 0x01, 0x03, 0x03, + 0x02, 0x11, 0x1A, 0x20, 0x01, 0x1D, 0x03, 0x03, 0x01, 0x32, 0x38, 0x03, 0x02, 0x21, 0x02, 0x51, + 0x01, 0x0A, 0x02, 0x02, 0x02, 0x03, 0x02, 0x04, 0x02, 0x02, 0x08, 0x03, 0x02, 0x0E, 0x03, 0x01, + 0x02, 0x03, 0x02, 0x44, 0x03, 0x02, 0x03, 0x03, 0x01, 0x2F, 0x02, 0x29, 0x08, 0x2A, 0x07, 0x02, + 0x04, 0x01, 0x0A, 0x02, 0x03, 0x02, 0x03, 0x03, 0x02, 0x6C, 0x15, 0x02, 0x02, 0x02, 0x03, 0x10, + 0x01, 0x17, 0x02, 0x03, 0x01, 0x11, 0x02, 0x02, 0x03, 0x50, 0x02, 0x02, 0x02, 0x38, 0x03, 0x1A, + 0x29, 0x02, 0x03, 0x03, 0x02, 0x04, 0x01, 0x67, 0x01, 0x03, 0x02, 0x03, 0x01, 0x1A, 0x02, 0x04, + 0x01, 0x0A, 0x01, 0x04, 0x01, 0x04, 0x02, 0x1A, 0x01, 0x02, 0x03, 0x03, 0x02, 0x03, 0x02, 0x03, + 0x02, 0x27, 0x01, 0x02, 0x03, 0x03, 0x01, 0x22, 0x02, 0x03, 0x03, 0x03, 0x02, 0x39, 0x0F, 0x02, + 0x03, 0x0E, 0x03, 0x2A, 0x02, 0x03, 0x02, 0xFE, 0xFE, 0x34, 0x1B, 0xED, 0x20, 0xE7, 0x21, 0x40, + 0x59, 0x57, 0x2E, 0xE9, 0x23, 0xFE, 0x01, 0x27, 0x40, 0x5B, 0xFE, 0xFE, 0x16, 0x71, 0xFE, 0xFE, + 0xFF, 0x0F, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0x4D, 0x20, 0x22, 0x19, 0x1A, 0x16, 0x1B, 0x19, 0x1B, 0x19, 0x1C, 0x13, 0x1C, 0x1D, 0x19, 0x10, + 0x2E, 0x08, 0x14, 0x2B, 0x17, 0x0E, 0x4B, 0x12, 0x09, 0x13, 0x13, 0x10, 0x0E, 0x0C, 0x14, 0x09, + 0x12, 0x10, 0x0B, 0x12, 0x13, 0x10, 0x0F, 0x0A, 0x16, 0x04, 0x16, 0x0E, 0x0D, 0x12, 0x13, 0x11, + 0x0E, 0x0E, 0x12, 0x04, 0x17, 0x0E, 0x0D, 0x11, 0x13, 0x12, 0x0F, 0x08, 0x15, 0x08, 0x15, 0x0D, + 0x0B, 0x13, 0x13, 0x12, 0x0C, 0x07, 0x17, 0x04, 0x17, 0x2C, 0x22, 0x16, 0x18, 0x05, 0x17, 0x08, + 0x04, 0x07, 0x04, 0x14, 0x31, 0x06, 0x17, 0x06, 0x17, 0x28, 0x10, 0x0B, 0x13, 0x0B, 0x18, 0x05, + 0x17, 0x26, 0x12, 0x0B, 0x16, 0x06, 0x37, 0x05, 0x06, 0x18, 0x12, 0x0C, 0x12, 0x07, 0x18, 0x04, + 0x19, 0x07, 0x07, 0x04, 0x03, 0x0F, 0x12, 0x09, 0x0A, 0x03, 0x06, 0x07, 0x15, 0x0A, 0x16, 0x13, + 0x05, 0x0E, 0x12, 0x09, 0x0A, 0x03, 0x06, 0x07, 0x16, 0x09, 0x17, 0x10, 0x06, 0x0E, 0x12, 0x09, + 0x13, 0x09, 0x15, 0x0A, 0x18, 0x10, 0x04, 0x0D, 0x12, 0x0A, 0x0A, 0x03, 0x04, 0x09, 0x15, 0x0A, + 0x15, 0x08, 0x08, 0x01, 0x05, 0x0E, 0x12, 0x0A, 0x0A, 0x02, 0x05, 0x09, 0x15, 0x07, 0x17, 0x09, + 0x10, 0x0E, 0x11, 0x0A, 0x13, 0x09, 0x14, 0x09, 0x15, 0x13, 0x07, 0x0F, 0x10, 0x0A, 0x13, 0x09, + 0x14, 0x08, 0x17, 0x12, 0x08, 0x13, 0x0C, 0x0A, 0x13, 0x09, 0x15, 0x08, 0x16, 0x14, 0x06, 0x0E, + 0x12, 0x09, 0x13, 0x09, 0x15, 0x13, 0x0B, 0x13, 0x08, 0x0D, 0x13, 0x08, 0x16, 0x07, 0x16, 0x08, + 0x16, 0x12, 0x08, 0x15, 0x0B, 0x09, 0x15, 0x08, 0x15, 0x08, 0x06, 0x03, 0x0C, 0x13, 0x09, 0x15, + 0x0C, 0x08, 0x03, 0x06, 0x0A, 0x09, 0x05, 0x04, 0x0A, 0x13, 0x15, 0x09, 0x0B, 0x14, 0x17, 0x09, + 0x18, 0x04, 0x0C, 0x03, 0x06, 0x06, 0x15, 0x08, 0x10, 0x12, 0x13, 0x0E, 0x17, 0x05, 0x12, 0x08, + 0x14, 0x08, 0x04, 0x04, 0x09, 0x12, 0x0F, 0x12, 0x0F, 0x0D, 0x13, 0x07, 0x14, 0x11, 0x0A, 0x13, + 0x10, 0x10, 0x15, 0x09, 0x2F, 0x0D, 0x10, 0x13, 0x0F, 0x10, 0x16, 0x09, 0x2D, 0x0F, 0x10, 0x14, + 0x0F, 0x0F, 0x16, 0x08, 0x17, 0x04, 0x14, 0x0D, 0x11, 0x15, 0x11, 0x0E, 0x12, 0x0C, 0x26, 0x14, + 0x0F, 0x1E, 0x19, 0x06, 0x18, 0x06, 0x07, 0x02, 0x18, 0x06, 0x08, 0x05, 0x09, 0x13, 0x04, 0x08, + 0x03, 0x06, 0x17, 0x08, 0x05, 0x03, 0x0F, 0x03, 0x31, 0x05, 0x07, 0x14, 0x0B, 0x13, 0x16, 0x11, + 0x14, 0x0B, 0x2E, 0x08, 0x15, 0x12, 0x02, 0x05, 0x17, 0x0A, 0x05, 0x01, 0x19, 0x07, 0x3C, 0x13, + 0x04, 0x16, 0x16, 0x08, 0x1B, 0x12, 0x21, 0x11, 0x14, 0x16, 0x02, 0x0E, 0x2F, 0x04, 0x06, 0x0A, + 0x10, 0x14, 0x18, 0x07, 0x09, 0x14, 0x0B, 0x05, 0x02, 0x06, 0x26, 0x02, 0x2E, 0x12, 0x2B, 0x11, + 0x14, 0x07, 0x03, 0x05, 0x30, 0x02, 0x06, 0x04, 0x18, 0x1F, 0x58, 0x06, 0x02, 0x05, 0x2F, 0x04, + 0x04, 0x06, 0x09, 0x02, 0x04, 0x31, 0x22, 0x3A, 0xC5, 0x16, 0x3F, 0x03, 0x0A, 0x02, 0x51, 0x12, + 0x12, 0x10, 0xFE, 0xD0, 0x35, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0x63, 0x02, 0x04, 0x02, 0x03, 0x03, 0x81, 0x02, 0x03, 0x02, 0x47, 0x09, 0x0A, 0x02, + 0x02, 0x03, 0x03, 0x02, 0x2B, 0x01, 0x04, 0x02, 0x04, 0x02, 0x04, 0x01, 0x38, 0x02, 0x80, 0x03, + 0x03, 0x02, 0x63, 0x01, 0x03, 0x02, 0x03, 0x02, 0x56, 0x02, 0x03, 0x02, 0x03, 0x03, 0x19, 0x02, + 0x02, 0x03, 0x02, 0x03, 0x02, 0x03, 0x02, 0x03, 0x03, 0x01, 0x60, 0x2C, 0x01, 0x03, 0x3E, 0x01, + 0x04, 0x01, 0x32, 0x02, 0x02, 0x03, 0x02, 0x17, 0x01, 0x03, 0x3D, 0x02, 0x03, 0x03, 0x03, 0x02, + 0x0A, 0x02, 0x5B, 0x02, 0x1F, 0x03, 0x02, 0x03, 0x03, 0x02, 0x03, 0x02, 0x03, 0x02, 0x04, 0x01, + 0x09, 0x02, 0x03, 0x02, 0x02, 0x03, 0x02, 0x02, 0x04, 0x01, 0x03, 0x02, 0x03, 0x02, 0x04, 0x01, + 0x03, 0x02, 0x03, 0x02, 0x03, 0x02, 0x03, 0x02, 0x2C, 0x02, 0x02, 0x03, 0x03, 0x02, 0x03, 0x03, + 0x04, 0x01, 0x23, 0x02, 0x03, 0x02, 0x25, 0x03, 0x02, 0x02, 0x03, 0x02, 0x1B, 0x02, 0x03, 0x02, + 0x04, 0x01, 0x37, 0x02, 0x04, 0x02, 0x12, 0x02, 0x02, 0x03, 0x03, 0x02, 0x03, 0x02, 0x03, 0x02, + 0x10, 0x02, 0x04, 0x01, 0x0D, 0x01, 0x04, 0x01, 0x04, 0x01, 0x25, 0x02, 0x03, 0x02, 0x03, 0x02, + 0x04, 0x02, 0x0A, 0x02, 0x0B, 0x02, 0x04, 0x02, 0x04, 0x03, 0x02, 0x03, 0x03, 0x0E, 0x3E, 0x02, + 0x04, 0x18, 0x01, 0x03, 0x03, 0x02, 0x04, 0x0A, 0x87, 0x11, 0x03, 0x02, 0x03, 0x02, 0x52, 0x02, + 0x04, 0x02, 0x12, 0x02, 0x30, 0x02, 0x03, 0x02, 0x04, 0x02, 0x04, 0x01, 0x04, 0x02, 0x02, 0x03, + 0x02, 0x02, 0x02, 0x03, 0x03, 0x01, 0x27, 0x02, 0xA4, 0x02, 0x03, 0x02, 0x18, 0x02, 0x03, 0x02, + 0x24, 0x16, 0x01, 0x1B, 0xFF, 0x01, 0x0F, 0x1A, 0x0D, 0x0B, 0x20, 0x0E, 0x07, 0x0C, 0x0A, 0x0B, + 0x22, 0x0A, 0x0D, 0x09, 0x0A, 0x0B, 0x1E, 0x10, 0x04, 0x0D, 0x10, 0x0C, 0x1E, 0x0A, 0x0B, 0x08, + 0x0C, 0x0A, 0x10, 0x07, 0x09, 0x0C, 0x04, 0x0E, 0x0C, 0x0C, 0x1F, 0x0E, 0x1D, 0x08, 0x11, 0x1A, + 0x03, 0x0D, 0x0C, 0x0B, 0x21, 0x0C, 0x10, 0x13, 0x14, 0x12, 0x0A, 0x0D, 0x0B, 0x0B, 0x0E, 0x03, + 0x10, 0x0C, 0x1E, 0x09, 0x11, 0x13, 0x08, 0x0B, 0x0C, 0x0C, 0x0C, 0x04, 0x10, 0x0E, 0x3E, 0x1F, + 0x0A, 0x0D, 0x0F, 0x02, 0x0B, 0x0F, 0x0E, 0x0C, 0x20, 0x04, 0x03, 0x1E, 0x0B, 0x0D, 0x1C, 0x0E, + 0x0E, 0x0C, 0x1E, 0x17, 0x02, 0x0C, 0x0B, 0x0D, 0x1B, 0x11, 0x1D, 0x06, 0x20, 0x0F, 0x02, 0x0B, + 0x0B, 0x0E, 0x0C, 0x04, 0x0F, 0x0B, 0x10, 0x12, 0x1E, 0x20, 0x0B, 0x0B, 0x0E, 0x04, 0x09, 0x03, + 0x02, 0x0A, 0x0B, 0x0D, 0x04, 0x08, 0x1D, 0x10, 0x05, 0x0D, 0x0B, 0x0C, 0x0D, 0x04, 0x0F, 0x0B, + 0x0D, 0x0A, 0x0B, 0x08, 0x0D, 0x0E, 0x11, 0x12, 0x0B, 0x0B, 0x0E, 0x03, 0x0A, 0x0E, 0x0C, 0x0C, + 0x0B, 0x0A, 0x09, 0x0B, 0x0D, 0x1D, 0x0B, 0x0C, 0x0E, 0x04, 0x0A, 0x0D, 0x0C, 0x0D, 0x0B, 0x09, + 0x0B, 0x07, 0x0E, 0x0E, 0x03, 0x0B, 0x03, 0x02, 0x06, 0x01, 0x02, 0x0B, 0x0F, 0x03, 0x0B, 0x0B, + 0x0D, 0x0E, 0x0C, 0x07, 0x09, 0x0A, 0x0C, 0x0A, 0x0B, 0x0C, 0x04, 0x01, 0x05, 0x10, 0x10, 0x02, + 0x0B, 0x03, 0x02, 0x04, 0x0F, 0x09, 0x11, 0x09, 0x03, 0x0D, 0x0B, 0x0C, 0x0A, 0x12, 0x01, 0x03, + 0x0B, 0x0B, 0x1D, 0x02, 0x02, 0x09, 0x0F, 0x0C, 0x13, 0x12, 0x0F, 0x0F, 0x0E, 0x0D, 0x0B, 0x03, + 0x02, 0x05, 0x22, 0x09, 0x10, 0x0A, 0x02, 0x04, 0x10, 0x0E, 0x0D, 0x1B, 0x0A, 0x0C, 0x0C, 0x0B, + 0x0E, 0x03, 0x0F, 0x0C, 0x0D, 0x0B, 0x05, 0x0B, 0x0A, 0x0C, 0x0D, 0x0F, 0x09, 0x03, 0x0B, 0x0C, + 0x0C, 0x0C, 0x0C, 0x04, 0x0F, 0x0C, 0x0C, 0x0D, 0x06, 0x0C, 0x0D, 0x0A, 0x0B, 0x08, 0x0E, 0x09, + 0x0D, 0x0D, 0x0B, 0x0E, 0x1E, 0x0C, 0x0E, 0x1B, 0x0D, 0x0C, 0x0C, 0x0E, 0x1D, 0x09, 0x0B, 0x0A, + 0x0C, 0x0C, 0x0A, 0x0A, 0x0E, 0x0B, 0x0B, 0x09, 0x0C, 0x0A, 0x0E, 0x0F, 0x07, 0x0C, 0x2F, 0x0A, + 0x10, 0x09, 0x15, 0x0B, 0x19, 0x0E, 0x13, 0x10, 0x11, 0x11, 0x11, 0x14, 0x31, 0x1F, 0x54, 0x11, + 0x5B, 0x0F, 0x2F, 0x0A, 0x12, 0x34, 0x9E, 0x0C, 0x12, 0x18, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, +}; diff --git a/stSpeechData.h b/stSpeechData.h new file mode 100644 index 0000000..b34e046 --- /dev/null +++ b/stSpeechData.h @@ -0,0 +1,26 @@ +// Generated by assets/extractSpeech.py. Do not hand-edit. +// +// Space Taxi's digitised speech as the C64 stores it: 1-bit +// run-length streams. Each byte is one half-cycle length; the +// player toggles its output after each. $FE is a raster sync +// and $FF ends an utterance. stAudio.c expands these to PCM +// through the jlAudioPlaySfxStream fill callback. + +#ifndef ST_SPEECH_DATA_H +#define ST_SPEECH_DATA_H + +#include + +typedef struct { + uint8_t code; // PETSCII: 'H' 'T' '!' 'P' '1'..'9' '?' 'U' + uint16_t offset; // into kStSpeechRle + uint16_t length; +} StSpeechEntryT; + +#define ST_SPEECH_COUNT 15u +#define ST_SPEECH_RLE_BYTES 7445u + +extern const StSpeechEntryT kStSpeechIndex[ST_SPEECH_COUNT]; +extern const uint8_t kStSpeechRle[ST_SPEECH_RLE_BYTES]; + +#endif diff --git a/stTitle.c b/stTitle.c index 8536963..ecc37d3 100644 --- a/stTitle.c +++ b/stTitle.c @@ -11,6 +11,11 @@ // Level intro ($4523..$4727): a black screen with the level name, the // cab rising from the bottom and seven star sprites streaming out of // the centre, until the cab reaches mid-screen. +// +// The seven star sprites are replaced by a static starfield whose +// colours are cycled -- see ST_STAR_RING_FIRST in stSim.h for why and +// how. The cab still flies, and it is still what ends the intro, so the +// sequence keeps its length and its cue; only the warp changes. #include @@ -28,24 +33,49 @@ static const uint8_t kLogoColor[8] = { 0x02, 0x08, 0x07, 0x05, 0x06, 0x0E, 0x03 // Intro cab drift per loop count ($470D). static const int8_t kIntroCabDy[4] = { -2, 1, -1, 1 }; // Intro texts ($4624 / $463F). -static const uint8_t kTextDemoExit[] = { 0x44, 0x45, 0x4D, 0x4F, 0x2C, 0x20, 0x55, 0x53, 0x45, 0x20, 0x4A, 0x4F, 0x59, 0x53, 0x54, 0x49, 0x43, 0x4B, 0x20, 0x54, 0x4F, 0x20, 0x45, 0x58, 0x49, 0x54, 0 }; -static const uint8_t kTextScreens[] = { 0x54, 0x48, 0x49, 0x53, 0x20, 0x49, 0x53, 0x20, 0x31, 0x20, 0x4F, 0x46, 0x20, 0x32, 0x35, 0x20, 0x44, 0x49, 0x46, 0x46, 0x45, 0x52, 0x45, 0x4E, 0x54, 0x20, 0x53, 0x43, 0x52, 0x45, 0x45, 0x4E, 0x53, 0x21, 0 }; +static const uint8_t kTextDemoExit[] = "DEMO, USE JOYSTICK TO EXIT"; +static const uint8_t kTextScreens[] = "THIS IS 1 OF 25 DIFFERENT SCREENS!"; #define ST_TITLE_CAB_LIFT_ROW 0x14u #define ST_TITLE_SPARKLE_TICKS 0x5Au #define ST_TITLE_HOVER_COL 0x28u #define ST_INTRO_STAR_PTR 0xDAu -#define ST_INTRO_STAR_RELOAD 0x20u #define ST_INTRO_CAB_END_ROW 0x94u #define ST_INTRO_CAB_DX_RELOAD 0x28u +// The palette-cycled starfield. Seven stars per ring so the bright band +// stays the same weight all the way out, placed at fixed pseudo-random +// angles and clear of the three lines the intro screen writes (the level +// name on row 12, and the demo hints on rows 3 and 24). +typedef struct { + uint8_t col; + uint8_t row; + uint8_t ring; +} StStarT; + +static const StStarT kIntroStar[] = { + { 18u, 10u, 0u }, { 19u, 10u, 0u }, { 17u, 11u, 0u }, { 18u, 13u, 0u }, + { 21u, 13u, 0u }, { 22u, 14u, 0u }, { 18u, 15u, 0u }, { 18u, 8u, 1u }, + { 19u, 8u, 1u }, { 14u, 10u, 1u }, { 25u, 14u, 1u }, { 21u, 15u, 1u }, + { 25u, 15u, 1u }, { 20u, 17u, 1u }, { 26u, 7u, 2u }, { 12u, 10u, 2u }, + { 29u, 11u, 2u }, { 11u, 13u, 2u }, { 29u, 14u, 2u }, { 21u, 17u, 2u }, + { 23u, 18u, 2u }, { 21u, 4u, 3u }, { 24u, 4u, 3u }, { 11u, 8u, 3u }, + { 12u, 8u, 3u }, { 10u, 10u, 3u }, { 30u, 16u, 3u }, { 14u, 17u, 3u }, + { 15u, 2u, 4u }, { 8u, 8u, 4u }, { 32u, 9u, 4u }, { 9u, 17u, 4u }, + { 32u, 17u, 4u }, { 32u, 18u, 4u }, { 24u, 22u, 4u }, { 16u, 1u, 5u }, + { 28u, 1u, 5u }, { 31u, 2u, 5u }, { 36u, 13u, 5u }, { 6u, 15u, 5u }, + { 4u, 16u, 5u }, { 5u, 19u, 5u }, { 8u, 0u, 6u }, { 30u, 0u, 6u }, + { 1u, 8u, 6u }, { 2u, 12u, 6u }, { 37u, 16u, 6u }, { 7u, 21u, 6u }, + { 32u, 22u, 6u }, { 0u, 4u, 7u }, { 39u, 7u, 7u }, { 0u, 10u, 7u }, + { 38u, 20u, 7u }, { 38u, 21u, 7u }, { 4u, 22u, 7u }, { 35u, 22u, 7u }, +}; +#define ST_INTRO_STAR_COUNT (sizeof(kIntroStar) / sizeof(kIntroStar[0])) + static void addToRow(StSimT *sim, uint8_t idx, uint8_t delta); -static void addToSpriteX(StSimT *sim, uint8_t idx, uint8_t delta); static void logoColorCycle(StSimT *sim); static void logoFlip(StSimT *sim); -static void marshalFrame(StSimT *sim); -static void starRespawn(StSimT *sim, uint8_t idx); +static void paintStarfield(StSimT *sim); // $4113 @@ -54,16 +84,6 @@ static void addToRow(StSimT *sim, uint8_t idx, uint8_t delta) { } -// $411B -static void addToSpriteX(StSimT *sim, uint8_t idx, uint8_t delta) { - uint16_t x = (uint16_t)(((uint16_t)sim->spr[idx].msb << 8) | sim->spr[idx].col); - - x = (uint16_t)(x + (uint16_t)(int16_t)(int8_t)delta); - sim->spr[idx].col = (uint8_t)x; - sim->spr[idx].msb = (uint8_t)(x >> 8); -} - - // $4861 -- the next logo colour, onto sprites 3..7 and onto the logo // itself. The C64 writes the colour into all 407 cells of rows 1..11, // columns 1..37; only the cells showing the logo character take any @@ -90,7 +110,8 @@ static void logoFlip(StSimT *sim) { if (sim->logoDiv != 0u) { return; } - memcpy(sim->charset[ST_LOGO_CHAR], sim->charset[ST_LOGO_CHAR + kLogoFlip[sim->logoCycleAux]], 8u); + memcpy(stSimGlyphMut(sim, ST_LOGO_CHAR), + stSimGlyph(sim, (uint8_t)(ST_LOGO_CHAR + kLogoFlip[sim->logoCycleAux])), 8u); stSimMarkChar(sim, ST_LOGO_CHAR); sim->logoCycleAux++; if (sim->logoCycleAux == 6u) { @@ -103,32 +124,21 @@ static void logoFlip(StSimT *sim) { } -// $4253 + $4293 as the title and intro loops call them. -static void marshalFrame(StSimT *sim) { - uint8_t i; - uint8_t enable = 0u; +// Paint the level-intro starfield into screen RAM, once. A star's cell +// is coloured ST_STAR_RING_FIRST + its ring and never touched again -- +// the renderer animates the warp by cycling those colours, which costs +// no dirty rows at all. See ST_STAR_RING_FIRST in stSim.h. +static void paintStarfield(StSimT *sim) { + const StStarT *star; + uint8_t one[2]; + uint8_t k; - for (i = 0u; i < ST_HW_SPRITES; i++) { - sim->frame.x[i] = (uint16_t)(((uint16_t)(sim->spr[i].msb != 0u ? 1u : 0u) << 8) | sim->spr[i].col); - sim->frame.y[i] = sim->spr[i].row; - sim->frame.ptr[i] = sim->spr[i].ptr; - sim->frame.color[i] = sim->spr[i].color; - if (sim->spr[i].enable != 0u) { - enable |= (uint8_t)(1u << i); - } + one[1] = 0u; // stSimDrawText stops below screen code 6 + for (k = 0u; k < (uint8_t)ST_INTRO_STAR_COUNT; k++) { + star = &kIntroStar[k]; + one[0] = (star->ring < ST_STAR_FAR_RINGS) ? ST_STAR_GLYPH_FAR : ST_STAR_GLYPH_NEAR; + stSimDrawText(sim, star->col, star->row, one, (uint8_t)(ST_STAR_RING_FIRST + star->ring)); } - sim->frame.enableMask = enable; - sim->frame.multiMask = sim->multiColorMask; -} - - -// $4675 -- a star reappears near the centre, seeded by star 0's row. -static void starRespawn(StSimT *sim, uint8_t idx) { - sim->spr[idx].enable = 1u; - sim->spr[idx].msb = 0u; - sim->spr[idx].col = (uint8_t)((sim->spr[0].row & 7u) + 0xA6u); - sim->spr[idx].row = (uint8_t)(((sim->spr[0].row & 0x30u) >> 4) + 0x89u); - sim->starReload[idx] = ST_INTRO_STAR_RELOAD; } @@ -159,21 +169,26 @@ void stIntroEnter(StSimT *sim, const StLevelT *level) { sim->spr[idx].color = 0x07u; sim->spr[idx].ptr = ST_INTRO_STAR_PTR; sim->spr[idx].enable = 0u; - sim->starTimer[idx] = stSimRng(sim, 0x20u); - sim->starReload[idx] = 0u; + // $44F8/$4500 held each star's spawn timer and reload. Nothing + // reads them now that the warp is painted rather than flown, but + // the seven RNG draws MUST stay: they advance the shared RNG and + // the level that follows draws from it, so dropping them would + // change the game that comes after the intro. + (void)stSimRng(sim, 0x20u); } sim->borderColor = 0u; sim->bgColor = 0u; sim->multiColorMask = 0x80u; - marshalFrame(sim); + stSimMarshal(sim); // $40CA: blank screen. memset(sim->screen, ST_CHAR_SPACE, ST_SCREEN_CELLS); memset(sim->color, 0, ST_SCREEN_CELLS); stSimDirtyAll(sim); - memcpy(sim->charset, stC64Charset(), sizeof(sim->charset)); + stSimGlyphReset(sim); sim->charDirtyAll = true; sim->charDirtyCount = 0u; stSimDrawText(sim, 10u, 12u, level->name, 3u); + paintStarfield(sim); if (sim->demoMode != 0u && sim->postMortem == 0u) { stSimDrawText(sim, 7u, 3u, kTextDemoExit, 5u); stSimDrawText(sim, 3u, 24u, kTextScreens, 4u); @@ -187,27 +202,11 @@ void stIntroEnter(StSimT *sim, const StLevelT *level) { bool stIntroStep(StSimT *sim) { uint8_t k; - for (k = 0u; k < 7u; k++) { - uint8_t idx = (uint8_t)(6u - k); - if (sim->starTimer[idx] == 0u) { - addToSpriteX(sim, idx, (uint8_t)stC64IntroStarDx(idx)); - addToRow(sim, idx, (uint8_t)stC64IntroStarDy(idx)); - sim->starReload[idx]--; - if (sim->starReload[idx] == 0u) { - starRespawn(sim, idx); - } - continue; - } - sim->starTimer[idx]--; - if (sim->starTimer[idx] == 0u) { - starRespawn(sim, idx); - } - } - marshalFrame(sim); + stSimMarshal(sim); sim->introLoopCount = (uint8_t)((sim->introLoopCount + 1u) & 3u); addToRow(sim, 7u, (uint8_t)kIntroCabDy[sim->introLoopCount]); if ((sim->introLoopCount & 1u) == 0u) { - addToSpriteX(sim, 7u, sim->introCabDx); + stSimSpriteAddX(sim, 7u, sim->introCabDx); sim->introCabDxTimer--; if (sim->introCabDxTimer == 0u) { sim->introCabDxTimer = ST_INTRO_CAB_DX_RELOAD; @@ -222,7 +221,7 @@ bool stIntroStep(StSimT *sim) { for (k = 0u; k < ST_HW_SPRITES; k++) { sim->spr[k].enable = 0u; } - marshalFrame(sim); + stSimMarshal(sim); sim->rngT1 = 0x06u; sim->rngT2 = 0x17u; return true; @@ -237,7 +236,7 @@ void stTitleEnter(StSimT *sim, const StLevelT *title) { memcpy(sim->screen, title->screen, ST_SCREEN_CELLS); memcpy(sim->color, title->color, ST_SCREEN_CELLS); stSimDirtyAll(sim); - memcpy(sim->charset, stC64Charset(), sizeof(sim->charset)); + stSimGlyphReset(sim); sim->charDirtyAll = true; sim->charDirtyCount = 0u; logoColorCycle(sim); @@ -260,7 +259,7 @@ void stTitleEnter(StSimT *sim, const StLevelT *title) { sim->multiColorMask = 0x07u; sim->spriteMc1 = 0x07u; sim->spriteMc0 = 0x02u; - marshalFrame(sim); + stSimMarshal(sim); sim->stage = ST_STAGE_WAIT; sim->introFrameCount = ST_TITLE_SPARKLE_TICKS; sim->hoverXFrac = 0u; @@ -313,7 +312,7 @@ bool stTitleTick(StSimT *sim) { default: break; } - marshalFrame(sim); + stSimMarshal(sim); logoFlip(sim); return sim->stage == ST_STAGE_DROP_IN; } diff --git a/tools/staxibake/staxibake.c b/tools/staxibake/staxibake.c index 65c5500..d3a6621 100644 --- a/tools/staxibake/staxibake.c +++ b/tools/staxibake/staxibake.c @@ -6,7 +6,12 @@ // boot with jlSpriteBankLoadPrecompiled, so no cel is JIT-compiled on // the 65816 (which costs ~0.4 s each). // -// Usage: staxibake OUTPUT.spr +// Usage: staxibake OUTPUT.spr the cab/passenger/exhaust bank +// staxibake --level LEVEL.dat OUTPUT.spr one level's hook-sprite cels +// +// The per-level bank holds the cels stLevelCelList names for that level +// (its own VIC bitmaps in the colours its hook program uses), in list +// order -- the game rebuilds the same list at level entry to key them. #include #include @@ -26,39 +31,87 @@ #define SPR_OFF_HEIGHT 7u #define SPR_OFF_CELLS 8u +static const uint8_t *levelBitmap(const StLevelT *level, uint8_t ptr); +static bool writeHeader(FILE *out, uint16_t count); -int main(int argc, char **argv) { - uint8_t header[SPR_HEADER_BYTES]; - FILE *out; - uint16_t i; - if (argc < 2) { - fprintf(stderr, "usage: staxibake OUTPUT.spr\n"); - return 2; - } - out = fopen(argv[1], "wb"); - if (out == NULL) { - fprintf(stderr, "staxibake: cannot write %s\n", argv[1]); - return 1; +// A level's own sprite block, or NULL when the level does not ship it. +static const uint8_t *levelBitmap(const StLevelT *level, uint8_t ptr) { + uint8_t k; + + for (k = 0u; k < level->spriteCount; k++) { + if (level->sprites[k].ptr == ptr) { + return level->sprites[k].bitmap; + } } + return NULL; +} + + +static bool writeHeader(FILE *out, uint16_t count) { + uint8_t header[SPR_HEADER_BYTES]; + memset(header, 0, sizeof(header)); memcpy(header, "JSP1", 4); - header[SPR_OFF_TARGET] = 0u; // cross-platform chunky - header[SPR_OFF_HAS_PAL] = 0u; // the game owns its palette - header[SPR_OFF_WIDTH] = (uint8_t)ST_CEL_TILES; - header[SPR_OFF_HEIGHT] = (uint8_t)ST_CEL_TILES; - header[SPR_OFF_CELLS] = (uint8_t)(kStCelCount & 0xFFu); - header[SPR_OFF_CELLS + 1u] = (uint8_t)(kStCelCount >> 8); - if (fwrite(header, 1, SPR_HEADER_BYTES, out) != SPR_HEADER_BYTES) { + header[SPR_OFF_TARGET] = 0u; // cross-platform chunky + header[SPR_OFF_HAS_PAL] = 0u; // the game owns its palette + header[SPR_OFF_WIDTH] = (uint8_t)ST_CEL_TILES; + header[SPR_OFF_HEIGHT] = (uint8_t)ST_CEL_TILES; + header[SPR_OFF_CELLS] = (uint8_t)(count & 0xFFu); + header[SPR_OFF_CELLS + 1u] = (uint8_t)(count >> 8); + return fwrite(header, 1, SPR_HEADER_BYTES, out) == SPR_HEADER_BYTES; +} + + +int main(int argc, char **argv) { + static StLevelT level; + static StCelDefT levelCels[ST_LEVEL_CELS_MAX]; + const StCelDefT *cels = kStCels; + uint16_t count = kStCelCount; + const char *outPath; + FILE *out; + uint16_t i; + + if (argc == 4 && strcmp(argv[1], "--level") == 0) { + FILE *fp = fopen(argv[2], "rb"); + if (fp == NULL || !stLevelParse(&level, fp)) { + fprintf(stderr, "staxibake: cannot load level %s\n", argv[2]); + return 1; + } + fclose(fp); + cels = levelCels; + count = stLevelCelList(&level, levelCels, ST_LEVEL_CELS_MAX); + outPath = argv[3]; + } else if (argc == 2) { + outPath = argv[1]; + } else { + fprintf(stderr, "usage: staxibake OUTPUT.spr\n staxibake --level LEVEL.dat OUTPUT.spr\n"); + return 2; + } + out = fopen(outPath, "wb"); + if (out == NULL) { + fprintf(stderr, "staxibake: cannot write %s\n", outPath); + return 1; + } + if (!writeHeader(out, count)) { fprintf(stderr, "staxibake: header write failed\n"); fclose(out); return 1; } - for (i = 0u; i < kStCelCount; i++) { + for (i = 0u; i < count; i++) { uint8_t blob[ST_CEL_BYTES]; - const uint8_t *bm = stC64SpriteBitmap((uint8_t)(kStCels[i].ptr - ST_SPRITE_PTR_FIRST)); - - stCelBlob(bm, kStCels[i].multi, kStCels[i].color, kStCels[i].mc0, kStCels[i].mc1, blob); + const uint8_t *bm; + if (cels == levelCels) { + bm = levelBitmap(&level, cels[i].ptr); + if (bm == NULL) { + fprintf(stderr, "staxibake: level %s has no sprite block $%02X\n", argv[2], (unsigned)cels[i].ptr); + fclose(out); + return 1; + } + } else { + bm = stC64SpriteBitmap((uint8_t)(cels[i].ptr - ST_SPRITE_PTR_FIRST)); + } + stCelBlob(bm, cels[i].multi, cels[i].color, cels[i].mc0, cels[i].mc1, blob); if (fwrite(blob, 1, ST_CEL_BYTES, out) != ST_CEL_BYTES) { fprintf(stderr, "staxibake: cel %u write failed\n", (unsigned)i); fclose(out); @@ -66,6 +119,6 @@ int main(int argc, char **argv) { } } fclose(out); - fprintf(stderr, "staxibake: wrote %u cels (%ux%u tiles) -> %s\n", (unsigned)kStCelCount, (unsigned)ST_CEL_TILES, (unsigned)ST_CEL_TILES, argv[1]); + fprintf(stderr, "staxibake: wrote %u cels (%ux%u tiles) -> %s\n", (unsigned)count, (unsigned)ST_CEL_TILES, (unsigned)ST_CEL_TILES, outPath); return 0; } diff --git a/tools/staxiharness/Makefile b/tools/staxiharness/Makefile new file mode 100644 index 0000000..09c44a7 --- /dev/null +++ b/tools/staxiharness/Makefile @@ -0,0 +1,36 @@ +# Host-side checks of Space Taxi's front-end screens (the parts tools/stsim +# cannot reach: they live in spacetaxi.c, above the simulation). Builds the +# game on the BLANK port with spacetaxi.c INCLUDED into the harness so its +# statics are reachable, and wraps the keyboard queue and the save file +# with scripted stand-ins. +# +# make -C tools/staxiharness build + run + +REPO := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/../..) +ST := $(REPO)/examples/spacetaxi +OUT := $(REPO)/build/tools/staxiharness +SRCS := $(filter-out $(ST)/spacetaxi.c,$(wildcard $(ST)/*.c)) \ + $(wildcard $(REPO)/src/core/*.c) $(wildcard $(REPO)/src/generic/*.c) \ + $(REPO)/src/blank/blank.c +CFLAGS := -O0 -g -w -DJOEYLIB_PLATFORM_BLANK -I$(REPO)/include -I$(REPO)/src/core \ + -I$(REPO)/src/codegen -I$(ST) +WRAPS := -Wl,--wrap=jlInputGetChar -Wl,--wrap=jlSaveWrite -Wl,--wrap=jlSaveRead + +.PHONY: all run clean + +all: run + +$(OUT)/harness: harness.c $(ST)/spacetaxi.c $(SRCS) + @mkdir -p $(OUT) + $(CC) $(CFLAGS) harness.c $(SRCS) $(WRAPS) -o $@ + +# The title screen's level file is read through DATA/levels (the DOS bake +# is the cross-platform STL4 form), so run from a directory that has one. +run: $(OUT)/harness + @mkdir -p $(OUT)/DATA + @ln -sfn $(ST)/generated/dos/levels $(OUT)/DATA/levels + @ln -sfn $(ST)/generated/dos/speech.bin $(OUT)/DATA/speech.bin + cd $(OUT) && ./harness + +clean: + rm -rf $(OUT) diff --git a/tools/staxiharness/harness.c b/tools/staxiharness/harness.c new file mode 100644 index 0000000..11f5c30 --- /dev/null +++ b/tools/staxiharness/harness.c @@ -0,0 +1,183 @@ +// Space Taxi front-end harness: the immortal-cabbies table and the name +// entry screen, driven with scripted keys on the host (BLANK port). +// +// spacetaxi.c is INCLUDED so its static state and routines are visible; +// its main() is renamed away. jlInputGetChar, jlSaveWrite and jlSaveRead +// are replaced through the linker's --wrap with a key script and an +// in-memory save image (see the Makefile). +#define main staxiMain +#include "spacetaxi.c" +#undef main + +#include + +#define SAVE_IMAGE_MAX 512u + +bool __wrap_jlSaveWrite(const char *name, const void *buf, uint32_t len); +uint32_t __wrap_jlSaveRead(const char *name, void *buf, uint32_t max); +int __wrap_jlInputGetChar(void); + +static void check(bool ok, const char *what); +static void rowText(uint8_t row, uint8_t col, uint8_t n, char *out); +static void setScore(uint8_t player, const uint8_t *digits); + +static const char *gKeys = ""; +static uint8_t gSaveImage[SAVE_IMAGE_MAX]; +static uint32_t gSaveLen = 0u; +static int gFails = 0; + + +int __wrap_jlInputGetChar(void) { + if (*gKeys == 0) { + return -1; + } + return (unsigned char)*gKeys++; +} + + +uint32_t __wrap_jlSaveRead(const char *name, void *buf, uint32_t max) { + (void)name; + if (gSaveLen == 0u || gSaveLen > max) { + return 0u; + } + memcpy(buf, gSaveImage, gSaveLen); + return gSaveLen; +} + + +bool __wrap_jlSaveWrite(const char *name, const void *buf, uint32_t len) { + (void)name; + if (len > sizeof(gSaveImage)) { + return false; + } + memcpy(gSaveImage, buf, len); + gSaveLen = len; + return true; +} + + +static void check(bool ok, const char *what) { + printf("%s %s\n", ok ? "ok " : "FAIL", what); + if (!ok) { + gFails++; + } +} + + +// `n` screen cells of one row as a C string. +static void rowText(uint8_t row, uint8_t col, uint8_t n, char *out) { + uint8_t k; + + for (k = 0u; k < n; k++) { + out[k] = (char)gSim.screen[ST_CELL(row, col + k)]; + } + out[n] = 0; +} + + +// A player's score as the sim keeps it: seven screen-code digits with +// the point at ST_HS_POINT_AT. +static void setScore(uint8_t player, const uint8_t *digits) { + uint8_t k; + + for (k = 0u; k < ST_NUMBER_CHARS; k++) { + gSim.scoreBackup[player][k] = (uint8_t)(ST_CHAR_DIGIT_BASE + digits[k]); + } + gSim.scoreBackup[player][ST_HS_POINT_AT] = ST_CHAR_POINT; +} + + +int main(void) { + static const uint8_t kDigits[ST_NUMBER_CHARS] = { 1u, 2u, 3u, 4u, 0u, 5u, 6u }; + jlConfigT config; + jlSurfaceT *stage; + char buf[48]; + uint8_t k; + + config.codegenBytes = 160UL * 1024; + config.audioBytes = 32UL * 1024; + if (!jlInit(&config)) { + printf("jlInit failed\n"); + return 1; + } + stage = jlStageGet(); + memset(&gGame, 0, sizeof(gGame)); + memset(&gSim, 0, sizeof(gSim)); + stRenderInit(stage); + stAudioInit(); + stSimNewGame(&gSim, 2u, false); + + // ---- ranking ($4D92) ---- + check(highScoreInsert((const uint8_t *)" 49.99") == ST_HS_ENTRIES, "49.99 is below the $50 floor"); + check(highScoreInsert((const uint8_t *)" 50.00") == ST_HS_ENTRIES, "50.00 passes the floor but beats nobody"); + check(highScoreInsert((const uint8_t *)" 500.00") == 8u, "500.00 lands in slot 8 (under 504.97, above 498.77)"); + check(memcmp(gHighScores[9].score, " 498.77", 7) == 0 && memcmp(gHighScores[7].score, " 504.97", 7) == 0, "older entries shifted down, the last one dropped"); + check(highScoreInsert((const uint8_t *)"3877.56") == 0u, "a tie goes above the older entry"); + check(memcmp(gHighScores[1].score, "3877.56", 7) == 0 && memcmp(gHighScores[1].name, "MICHAEL PLATE ", 15) == 0, "the tied old entry is now second"); + + // ---- the entry screen for cabbie 2 with $1234.56 on shift 3 ($4D52 / $4E35) ---- + gSim.player = 1u; + gGame.variation = 3u; + setScore(1u, kDigits); + gKeys = "zz"; // typed before the screen: flushed like $0277 + check(nameEntryStart(), "1234.56 ranks"); + check(gGame.state == ST_STATE_NAME_ENTRY && gGame.hsSlot == 3u, "slot 3 (under the two 3877.56 and 1809.51)"); + check(gSim.borderColor == 2u && gSim.bgColor == 15u, "red border, grey field"); + rowText(3u, 7u, 25u, buf); + check(strcmp(buf, "CONGRATULATIONS CABBIE 2,") == 0, buf); + rowText(0u, 15u, 8u, buf); + check(strcmp(buf, "$1234.56") == 0, buf); + rowText(9u, 9u, 21u, buf); + check(strcmp(buf, "ENTER YOUR NAME BELOW") == 0, buf); + check(*gKeys == 0, "earlier keystrokes discarded"); + + // ---- typing ($4ED8): s c o 9 t t T ---- + gKeys = "sco9tt\bT"; + runNameEntry(1u); + rowText(ST_HS_NAME_ROW, ST_HS_NAME_COL, 5u, buf); + check(strcmp(buf, "SCOTT") == 0, buf); + check(gGame.hsLen == 5u, "five characters kept ('9' ignored, one deleted, one retyped)"); + check(gGame.state == ST_STATE_NAME_ENTRY, "still entering"); + for (k = 0u; k < 2u; k++) { + runNameEntry(1u); + } + check(gSim.screen[ST_CELL(ST_HS_NAME_ROW, ST_HS_NAME_COL + 5u)] == ' ', "first flip blanks the cursor ($4F4D)"); + for (k = 0u; k < 3u; k++) { + runNameEntry(1u); + } + check(gSim.screen[ST_CELL(ST_HS_NAME_ROW, ST_HS_NAME_COL + 5u)] == '%', "second flip shows it"); + gKeys = "\r"; + runNameEntry(1u); + check(gGame.state != ST_STATE_NAME_ENTRY, "RETURN leaves the screen"); + check(memcmp(gHighScores[3].name, "SCOTT ", 15) == 0 && gHighScores[3].shift == 3u, "name and shift stored"); + check(gSaveLen == 240u && gSaveImage[3 * 24 + 7] == 0 && memcmp(&gSaveImage[3 * 24 + 8], "SCOTT ", 6) == 0 && gSaveImage[3 * 24 + 23] == 3u, "saved in the C64 image layout"); + check(gSim.playersDone == 1u, "game-over bookkeeping continued"); + + // ---- reload from the image ($23E5) ---- + memset(gHighScores, 0, sizeof(gHighScores)); + highScoreLoad(); + check(memcmp(gHighScores[3].name, "SCOTT ", 15) == 0 && memcmp(gHighScores[0].score, "3877.56", 7) == 0, "table restored from the save"); + + // ---- an empty name becomes "*" ---- + gSim.player = 0u; + setScore(0u, kDigits); + check(nameEntryStart() && gGame.hsSlot == 3u, "second entry, a tie goes above"); + gKeys = "\r"; + runNameEntry(1u); + check(gHighScores[3].name[0] == '*' && memcmp(gHighScores[4].name, "SCOTT", 5) == 0, "an empty name is stored as *"); + + // ---- the table screen ($4C1F) ---- + highScoreDraw(); + rowText(1u, 9u, 20u, buf); + check(strcmp(buf, "THE IMMORTAL CABBIES") == 0, buf); + rowText(12u, 4u, 26u, buf); + check(strcmp(buf, "$1234.56 SCOTT ") == 0, buf); + check(gSim.screen[ST_CELL(12, 34)] == '3' && gSim.screen[ST_CELL(10, 15)] == '*', "shift digit in the SHIFT column, * name above"); + rowText(24u, 3u, 34u, buf); + check(strcmp(buf, "PRESS FIRE TO RETURN TO TITLE PAGE") == 0, buf); + check(jlMusicIsPlaying(), "song 7 under the table"); + + printf("%s (%d failures)\n", gFails ? "staxiharness: FAIL" : "staxiharness: PASS", gFails); + jlShutdown(); + return gFails ? 1 : 0; +} diff --git a/tools/stsim/Makefile b/tools/stsim/Makefile index ebf1bea..0101e3f 100644 --- a/tools/stsim/Makefile +++ b/tools/stsim/Makefile @@ -2,10 +2,16 @@ ST := ../../examples/spacetaxi CC ?= gcc CFLAGS := -O1 -g -Wall -Wextra -std=c99 -I$(ST) -SRCS := stsim.c $(ST)/stSim.c $(ST)/stFare.c $(ST)/stHooks.c $(ST)/stLevelFile.c $(ST)/stTitle.c $(ST)/stC64Data.c +SRCS := stsim.c $(ST)/stSim.c $(ST)/stFare.c $(ST)/stHooks.c $(ST)/stLevelFile.c $(ST)/stTitle.c $(ST)/stC64Data.c $(ST)/stLevelCels.c -stsim: $(SRCS) $(ST)/stSim.h $(ST)/stC64Data.h $(ST)/stDemoStreams.h +stsim: $(SRCS) $(ST)/stSim.h $(ST)/stC64Data.h $(ST)/stDemoStreams.h gateStreams.h $(CC) $(CFLAGS) $(SRCS) -o $@ +# The fixture generator for gateStreams.h. Not built by default: it runs +# a long search and is only needed when a level's stream is (re)made. +recorder: recorder.c $(SRCS:stsim.c=) $(ST)/stSim.h + $(CC) $(CFLAGS) recorder.c $(ST)/stSim.c $(ST)/stFare.c $(ST)/stHooks.c \ + $(ST)/stLevelFile.c $(ST)/stTitle.c $(ST)/stC64Data.c -o $@ + clean: - rm -f stsim + rm -f stsim recorder diff --git a/tools/stsim/celGate.sh b/tools/stsim/celGate.sh new file mode 100755 index 0000000..68f5e72 --- /dev/null +++ b/tools/stsim/celGate.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Cel gate for Space Taxi: every level cel a recorded ride DISPLAYS must be +# in the level's baked manifest (stLevelCelList in stLevelCels.c), or the +# real ports build it mid-ride and draw it interpreted -- the "compiling +# sprites on the fly" hitch. Runs the same rides gate.sh does with the +# stsim census (STSIM_CELS=1) and fails on any MISSING line. +# +# celGate.sh check every hook level +set -u + +here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +repo=$(cd "$here/../.." && pwd) +levels=$repo/examples/spacetaxi/generated/iigs/levels +ticks=4000 +pairs="H:08 T:20 W:23 X:24 G:07 I:09 K:11 O:15 Q:17 R:18 U:21 V:22 E:05 J:10 L:12 P:16 S:19" + +make -C "$here" >/dev/null || { echo "celGate: build failed" >&2; exit 2; } + +fail=0 +for p in $pairs; do + l=${p%%:*}; n=${p##*:} + out=$(STSIM_CELS=1 "$here/stsim" "$levels/level$n.dat" "$l" "$ticks" 2>&1 >/dev/null | grep -E '^(CEL|CENSUS)') + missing=$(echo "$out" | grep -c '^CEL MISSING') + census=$(echo "$out" | grep '^CENSUS' | sed 's/^CENSUS //') + if [ "$missing" -ne 0 ]; then + printf ' %s: *** %s cel(s) shown but not baked *** (%s)\n' "$l" "$missing" "$census" + echo "$out" | grep '^CEL MISSING' | sed 's/^/ /' + fail=1 + else + printf ' %s: ok (%s)\n' "$l" "$census" + fi +done +[ $fail -eq 0 ] && echo "celGate: PASS" || echo "celGate: FAIL" +exit $fail diff --git a/tools/stsim/gate.sh b/tools/stsim/gate.sh new file mode 100755 index 0000000..8320eb1 --- /dev/null +++ b/tools/stsim/gate.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Behaviour gate for the Space Taxi simulation. +# +# Runs every hook level through stsim and compares the per-tick trace +# against a golden capture. A refactor that changes nothing observable +# leaves all seventeen traces byte-identical; anything else is a regression. +# +# H, W, T and X ride the C64's own attract recordings (the ones checked +# against VICE traces). The other thirteen ride synthesised fixtures from +# gateStreams.h -- a regression gate only, not evidence of C64 fidelity. +# +# gate.sh capture [dir] record goldens from the CURRENT tree +# gate.sh check [dir] compare the current tree against them +# gate.sh hashes print one md5 per level (compact reference) +# +# Capture from a tree you trust, refactor, then check. +set -u + +here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +repo=$(cd "$here/../.." && pwd) +levels=$repo/examples/spacetaxi/generated/iigs/levels +mode=${1:-check} +dir=${2:-$here/golden} +ticks=4000 + +# level letter -> level file number +pairs="H:08 T:20 W:23 X:24 G:07 I:09 K:11 O:15 Q:17 R:18 U:21 V:22 E:05 J:10 L:12 P:16 S:19" + +make -C "$here" >/dev/null || { echo "gate: build failed" >&2; exit 2; } + +case "$mode" in +capture) + mkdir -p "$dir" + for p in $pairs; do + l=${p%%:*}; n=${p##*:} + "$here/stsim" "$levels/level$n.dat" "$l" "$ticks" > "$dir/demo$l.jsonl" 2>/dev/null + printf ' %s: %s ticks\n' "$l" "$(wc -l < "$dir/demo$l.jsonl")" + done + echo "gate: captured to $dir" + ;; +check) + fail=0 + for p in $pairs; do + l=${p%%:*}; n=${p##*:} + got=$(mktemp) + "$here/stsim" "$levels/level$n.dat" "$l" "$ticks" > "$got" 2>/dev/null + if [ ! -f "$dir/demo$l.jsonl" ]; then + echo " $l: no golden (run: gate.sh capture)" + elif cmp -s "$dir/demo$l.jsonl" "$got"; then + printf ' %s: IDENTICAL (%s ticks)\n' "$l" "$(wc -l < "$got")" + else + echo " $l: *** DIFFERS ***" + diff "$dir/demo$l.jsonl" "$got" | head -3 + fail=1 + fi + rm -f "$got" + done + [ $fail -eq 0 ] && echo "gate: PASS" || echo "gate: FAIL" + # The cel census rides the same recordings: a cel a ride displays that + # its manifest does not bake is a mid-ride interpreted sprite. + "$here/celGate.sh" || fail=1 + exit $fail + ;; +hashes) + for p in $pairs; do + l=${p%%:*}; n=${p##*:} + h=$("$here/stsim" "$levels/level$n.dat" "$l" "$ticks" 2>/dev/null | md5sum | cut -d' ' -f1) + printf '%s %s\n' "$l" "$h" + done + ;; +*) + sed -n '2,20p' "$0" + exit 2 + ;; +esac diff --git a/tools/stsim/gateHashes.txt b/tools/stsim/gateHashes.txt new file mode 100644 index 0000000..9c1b5c8 --- /dev/null +++ b/tools/stsim/gateHashes.txt @@ -0,0 +1,21 @@ +# Space Taxi behaviour gate -- md5 of each level's 4000-tick trace. +# Regenerate with: tools/stsim/gate.sh hashes +# H/W/T/X ride the C64's attract recordings; the rest ride +# synthesised fixtures from gateStreams.h (regression gate only). +H 760501b0e48f3922bfb0f23fd2831935 +T c0b3f9e9a74e0d526703cf9b1875557b +W 92226455989e8326577a6ea20df8c8ea +X 87251d616a1be6e0c9c9317987b0fe20 +G 73530651372c60042f1f46b5c0cbada0 +I 7fe4415429d2b72a50884424269cb685 +K 567be2ed37219356a055866459971345 +O 02455280933e915e57803a4059aaefd9 +Q a7732866d54ed06d52e5dc7406b5b229 +R 066a9be80107446893b4571d247ee5cc +U 02dd2f26245442ab016ef02e10fde611 +V cc2cb13c5e393d07fbece9c0c465e387 +E 21ff29d69d8318bcb2e870b9602e910e +J 4c22a7515949b1701ed4fedabb7823dd +L a0383cbde2800bb09353df15214934ef +P f345d0adc9e011662d582c4277ee663e +S daa3cd12320e32d3e9b6b97b19b0a6d0 diff --git a/tools/stsim/gateStreams.h b/tools/stsim/gateStreams.h new file mode 100644 index 0000000..8d179b5 --- /dev/null +++ b/tools/stsim/gateStreams.h @@ -0,0 +1,460 @@ +// Synthesised demo input streams -- a TEST FIXTURE, not game content. +// +// The C64 ships attract recordings for four screens only (H, W, T and X); +// those live in examples/spacetaxi/stDemoStreams.h and are the streams +// checked against VICE traces. The thirteen streams below were searched for +// by tools/stsim's recorder against this port's own simulation, scored on +// how long the cab survives and how much of the level's hook state, fare +// machine and screen animation the ride reaches. +// +// So they prove one thing and not another: they are a REGRESSION gate -- +// refactor the hooks and the tick-by-tick trace must not move -- but they +// are NOT evidence that a hook matches the original, because no C64 trace +// exists for these screens. Treat a change to these bytes as invalidating +// the goldens, and never put them in the attract rotation. + +#ifndef ST_GATE_STREAMS_H +#define ST_GATE_STREAMS_H + +#include + +// level G -- 1400 ticks, 5 hook bytes, 10 fare stages, 256 pictures, 13 landings +// regenerate: tools/stsim/recorder levels/level??.dat G 20260921 +static const uint8_t kGateStreamG[320] = { + 0x88, 0x05, 0x81, 0x09, 0x81, 0x05, 0x81, 0x04, 0x81, 0x06, 0x84, 0x0C, + 0x81, 0x01, 0x89, 0x02, 0x81, 0x09, 0x82, 0x0D, 0x88, 0x05, 0x81, 0x03, + 0x80, 0x09, 0x81, 0x01, 0x90, 0x02, 0x81, 0x04, 0x90, 0x08, 0x81, 0x03, + 0x90, 0x0A, 0x88, 0x0E, 0x88, 0x0C, 0x90, 0x0C, 0x81, 0x04, 0x81, 0x0D, + 0x90, 0x0A, 0x81, 0x0D, 0x84, 0x0E, 0x81, 0x09, 0x81, 0x08, 0x81, 0x0C, + 0x82, 0x04, 0x81, 0x02, 0x81, 0x0E, 0x81, 0x02, 0x84, 0x01, 0x88, 0x0A, + 0x88, 0x05, 0x90, 0x09, 0x81, 0x02, 0x84, 0x09, 0x84, 0x05, 0x81, 0x0D, + 0x84, 0x09, 0x88, 0x03, 0x84, 0x06, 0x84, 0x0D, 0x81, 0x05, 0x84, 0x0C, + 0x88, 0x0C, 0x88, 0x0C, 0x88, 0x03, 0x88, 0x09, 0x81, 0x0A, 0x81, 0x09, + 0x81, 0x06, 0x81, 0x07, 0x81, 0x05, 0x88, 0x09, 0x88, 0x02, 0x81, 0x0B, + 0x84, 0x0A, 0x84, 0x05, 0x88, 0x03, 0x81, 0x05, 0x81, 0x0B, 0x81, 0x05, + 0x90, 0x05, 0x84, 0x09, 0x81, 0x06, 0x81, 0x07, 0x90, 0x0D, 0x81, 0x04, + 0x81, 0x0E, 0x88, 0x0D, 0x88, 0x0D, 0x88, 0x06, 0x82, 0x02, 0x88, 0x0D, + 0x90, 0x0E, 0x81, 0x04, 0x81, 0x0B, 0x84, 0x0B, 0x81, 0x05, 0x84, 0x07, + 0x88, 0x0A, 0x80, 0x04, 0x84, 0x07, 0x84, 0x0B, 0x88, 0x0D, 0x90, 0x0E, + 0x90, 0x06, 0x88, 0x08, 0x82, 0x0C, 0x81, 0x02, 0x81, 0x0E, 0x88, 0x0A, + 0x90, 0x0B, 0x81, 0x04, 0x88, 0x08, 0x90, 0x07, 0x84, 0x03, 0x82, 0x08, + 0x81, 0x05, 0x81, 0x03, 0x81, 0x03, 0x81, 0x06, 0x90, 0x0B, 0x84, 0x0E, + 0x82, 0x0E, 0x88, 0x08, 0x88, 0x04, 0x81, 0x0D, 0x88, 0x04, 0x81, 0x08, + 0x88, 0x0C, 0x81, 0x06, 0x81, 0x04, 0x90, 0x0B, 0x81, 0x08, 0x84, 0x04, + 0x90, 0x08, 0x84, 0x09, 0x88, 0x09, 0x81, 0x0B, 0x81, 0x07, 0x84, 0x0D, + 0x88, 0x0A, 0x88, 0x09, 0x90, 0x0B, 0x90, 0x02, 0x84, 0x0D, 0x81, 0x0E, + 0x90, 0x0D, 0x84, 0x07, 0x82, 0x0C, 0x81, 0x0E, 0x81, 0x06, 0x88, 0x09, + 0x90, 0x02, 0x90, 0x04, 0x81, 0x09, 0x84, 0x0A, 0x90, 0x06, 0x81, 0x04, + 0x90, 0x03, 0x90, 0x08, 0x84, 0x0B, 0x81, 0x02, 0x90, 0x04, 0x84, 0x03, + 0x81, 0x03, 0x84, 0x05, 0x90, 0x0C, 0x81, 0x01, 0x90, 0x0D, 0x81, 0x02, + 0x90, 0x05, 0x90, 0x0E, 0x81, 0x03, 0x90, 0x0A, +}; + +// level I -- 1400 ticks, 20 hook bytes, 10 fare stages, 256 pictures, 22 landings +// regenerate: tools/stsim/recorder levels/level??.dat I 20260918 +static const uint8_t kGateStreamI[320] = { + 0x84, 0x0E, 0x81, 0x06, 0x84, 0x0E, 0x88, 0x0B, 0x88, 0x05, 0x81, 0x03, + 0x82, 0x07, 0x90, 0x0A, 0x81, 0x08, 0x88, 0x0E, 0x88, 0x0E, 0x90, 0x0B, + 0x82, 0x0D, 0x81, 0x01, 0x81, 0x0A, 0x90, 0x0A, 0x82, 0x03, 0x81, 0x07, + 0x82, 0x05, 0x81, 0x0B, 0x81, 0x05, 0x82, 0x05, 0x82, 0x0C, 0x81, 0x08, + 0x88, 0x0C, 0x90, 0x04, 0x81, 0x02, 0x88, 0x08, 0x81, 0x03, 0x81, 0x05, + 0x81, 0x0A, 0x88, 0x0E, 0x81, 0x06, 0x84, 0x0E, 0x82, 0x07, 0x82, 0x01, + 0x81, 0x04, 0x82, 0x08, 0x90, 0x0C, 0x90, 0x05, 0x81, 0x08, 0x82, 0x0D, + 0x90, 0x06, 0x81, 0x0A, 0x84, 0x0B, 0x84, 0x0C, 0x84, 0x0C, 0x88, 0x09, + 0x81, 0x06, 0x81, 0x0C, 0x81, 0x04, 0x82, 0x09, 0x88, 0x05, 0x82, 0x0E, + 0x81, 0x0C, 0x81, 0x07, 0x88, 0x01, 0x90, 0x0E, 0x81, 0x02, 0x90, 0x0E, + 0x81, 0x02, 0x90, 0x07, 0x84, 0x05, 0x84, 0x06, 0x88, 0x06, 0x88, 0x0C, + 0x81, 0x0C, 0x84, 0x0B, 0x82, 0x09, 0x81, 0x05, 0x81, 0x0B, 0x82, 0x09, + 0x90, 0x0E, 0x90, 0x06, 0x88, 0x0D, 0x84, 0x03, 0x82, 0x0B, 0x84, 0x0D, + 0x90, 0x0B, 0x90, 0x04, 0x81, 0x0B, 0x81, 0x08, 0x88, 0x08, 0x90, 0x0A, + 0x81, 0x03, 0x81, 0x01, 0x81, 0x0E, 0x81, 0x04, 0x88, 0x02, 0x90, 0x05, + 0x81, 0x07, 0x81, 0x01, 0x82, 0x09, 0x90, 0x06, 0x81, 0x09, 0x90, 0x0E, + 0x88, 0x05, 0x82, 0x0D, 0x88, 0x03, 0x81, 0x06, 0x84, 0x0C, 0x82, 0x01, + 0x81, 0x09, 0x82, 0x0E, 0x90, 0x01, 0x81, 0x01, 0x84, 0x05, 0x82, 0x06, + 0x88, 0x0A, 0x81, 0x05, 0x82, 0x03, 0x90, 0x07, 0x90, 0x08, 0x81, 0x02, + 0x90, 0x0D, 0x88, 0x0D, 0x81, 0x08, 0x90, 0x0E, 0x82, 0x0D, 0x88, 0x04, + 0x81, 0x0C, 0x82, 0x09, 0x90, 0x04, 0x81, 0x0C, 0x82, 0x06, 0x90, 0x0E, + 0x90, 0x0D, 0x90, 0x07, 0x90, 0x03, 0x82, 0x03, 0x88, 0x06, 0x81, 0x07, + 0x90, 0x08, 0x81, 0x02, 0x90, 0x09, 0x81, 0x04, 0x90, 0x06, 0x82, 0x0A, + 0x90, 0x05, 0x88, 0x05, 0x88, 0x06, 0x90, 0x0B, 0x81, 0x03, 0x82, 0x02, + 0x90, 0x0B, 0x81, 0x02, 0x90, 0x0C, 0x90, 0x07, 0x81, 0x01, 0x84, 0x08, + 0x90, 0x0C, 0x81, 0x0E, 0x88, 0x08, 0x88, 0x0C, 0x81, 0x0C, 0x90, 0x04, + 0x84, 0x0D, 0x81, 0x01, 0x88, 0x08, 0x88, 0x09, +}; + +// level K -- 1400 ticks, 1 hook bytes, 10 fare stages, 256 pictures, 16 landings +// regenerate: tools/stsim/recorder levels/level??.dat K 11 +static const uint8_t kGateStreamK[320] = { + 0x84, 0x06, 0x82, 0x0B, 0x90, 0x0C, 0x82, 0x0E, 0x90, 0x05, 0x88, 0x03, + 0x88, 0x02, 0x82, 0x0E, 0x88, 0x07, 0x84, 0x02, 0x84, 0x0B, 0x84, 0x08, + 0x90, 0x0A, 0x88, 0x07, 0x84, 0x07, 0x81, 0x03, 0x84, 0x08, 0x82, 0x0E, + 0x82, 0x0D, 0x84, 0x01, 0x81, 0x09, 0x81, 0x0E, 0x81, 0x0C, 0x81, 0x0B, + 0x82, 0x0C, 0x82, 0x06, 0x81, 0x01, 0x84, 0x01, 0x90, 0x06, 0x82, 0x0D, + 0x84, 0x0B, 0x82, 0x09, 0x88, 0x09, 0x88, 0x05, 0x82, 0x02, 0x84, 0x04, + 0x84, 0x09, 0x88, 0x08, 0x90, 0x09, 0x82, 0x08, 0x81, 0x01, 0x88, 0x01, + 0x81, 0x06, 0x90, 0x04, 0x82, 0x0A, 0x82, 0x09, 0x82, 0x09, 0x84, 0x0C, + 0x82, 0x07, 0x90, 0x0E, 0x90, 0x0E, 0x90, 0x07, 0x82, 0x0D, 0x90, 0x02, + 0x84, 0x04, 0x84, 0x09, 0x88, 0x05, 0x88, 0x03, 0x81, 0x05, 0x82, 0x0E, + 0x90, 0x04, 0x90, 0x08, 0x88, 0x0C, 0x90, 0x08, 0x81, 0x05, 0x82, 0x0B, + 0x90, 0x0B, 0x84, 0x05, 0x82, 0x0D, 0x82, 0x08, 0x84, 0x0D, 0x81, 0x01, + 0x90, 0x08, 0x82, 0x0C, 0x81, 0x09, 0x82, 0x05, 0x84, 0x01, 0x82, 0x0A, + 0x82, 0x0E, 0x88, 0x0A, 0x84, 0x04, 0x90, 0x01, 0x90, 0x0D, 0x82, 0x08, + 0x90, 0x0B, 0x84, 0x03, 0x81, 0x01, 0x90, 0x0D, 0x82, 0x0C, 0x82, 0x05, + 0x81, 0x02, 0x81, 0x06, 0x84, 0x08, 0x81, 0x01, 0x82, 0x06, 0x88, 0x0E, + 0x88, 0x02, 0x82, 0x0A, 0x90, 0x0E, 0x82, 0x0B, 0x81, 0x0E, 0x81, 0x0A, + 0x82, 0x08, 0x90, 0x01, 0x81, 0x04, 0x81, 0x0D, 0x90, 0x0C, 0x88, 0x0D, + 0x88, 0x07, 0x81, 0x05, 0x90, 0x02, 0x82, 0x07, 0x88, 0x02, 0x84, 0x0E, + 0x82, 0x0D, 0x90, 0x02, 0x88, 0x04, 0x90, 0x0D, 0x82, 0x0A, 0x82, 0x0D, + 0x88, 0x09, 0x81, 0x04, 0x84, 0x08, 0x90, 0x0B, 0x81, 0x01, 0x90, 0x0C, + 0x82, 0x0E, 0x84, 0x04, 0x82, 0x06, 0x90, 0x09, 0x84, 0x03, 0x90, 0x01, + 0x82, 0x0C, 0x81, 0x03, 0x82, 0x05, 0x90, 0x0E, 0x82, 0x0E, 0x84, 0x07, + 0x90, 0x06, 0x81, 0x02, 0x90, 0x03, 0x82, 0x09, 0x82, 0x0A, 0x81, 0x05, + 0x82, 0x0D, 0x84, 0x01, 0x90, 0x0A, 0x82, 0x09, 0x81, 0x04, 0x82, 0x02, + 0x82, 0x08, 0x90, 0x06, 0x90, 0x03, 0x84, 0x09, 0x82, 0x01, 0x82, 0x08, + 0x82, 0x09, 0x90, 0x01, 0x88, 0x07, 0x88, 0x08, +}; + +// level O -- 1400 ticks, 1 hook bytes, 3 fare stages, 256 pictures, 0 landings +// regenerate: tools/stsim/recorder levels/level??.dat O 20260918 +static const uint8_t kGateStreamO[320] = { + 0x84, 0x0E, 0x90, 0x08, 0x82, 0x07, 0x81, 0x0B, 0x84, 0x02, 0x81, 0x09, + 0x82, 0x06, 0x90, 0x0C, 0x81, 0x08, 0x88, 0x01, 0x88, 0x02, 0x90, 0x04, + 0x88, 0x06, 0x81, 0x04, 0x82, 0x0A, 0x81, 0x06, 0x81, 0x0E, 0x82, 0x08, + 0x88, 0x06, 0x82, 0x03, 0x81, 0x06, 0x81, 0x02, 0x88, 0x0D, 0x82, 0x04, + 0x84, 0x0B, 0x84, 0x03, 0x81, 0x04, 0x84, 0x06, 0x81, 0x04, 0x81, 0x02, + 0x84, 0x07, 0x82, 0x06, 0x81, 0x06, 0x90, 0x05, 0x88, 0x09, 0x81, 0x0A, + 0x81, 0x0C, 0x81, 0x02, 0x82, 0x09, 0x82, 0x0D, 0x81, 0x04, 0x81, 0x0B, + 0x84, 0x03, 0x88, 0x0A, 0x90, 0x0A, 0x81, 0x08, 0x82, 0x08, 0x81, 0x06, + 0x81, 0x03, 0x82, 0x0A, 0x81, 0x0C, 0x82, 0x08, 0x81, 0x0C, 0x82, 0x0E, + 0x81, 0x06, 0x81, 0x04, 0x81, 0x0A, 0x84, 0x05, 0x90, 0x0E, 0x84, 0x06, + 0x82, 0x0B, 0x81, 0x0E, 0x81, 0x03, 0x81, 0x05, 0x90, 0x08, 0x81, 0x01, + 0x90, 0x0E, 0x84, 0x0D, 0x88, 0x01, 0x90, 0x05, 0x84, 0x0B, 0x81, 0x04, + 0x81, 0x04, 0x82, 0x01, 0x90, 0x04, 0x82, 0x0D, 0x88, 0x07, 0x81, 0x0B, + 0x81, 0x07, 0x82, 0x01, 0x88, 0x0A, 0x81, 0x06, 0x90, 0x02, 0x82, 0x08, + 0x81, 0x01, 0x81, 0x0A, 0x88, 0x0E, 0x88, 0x0D, 0x81, 0x03, 0x88, 0x0D, + 0x81, 0x02, 0x88, 0x04, 0x84, 0x0E, 0x90, 0x01, 0x82, 0x0C, 0x81, 0x0B, + 0x81, 0x04, 0x88, 0x03, 0x81, 0x03, 0x88, 0x09, 0x84, 0x0E, 0x81, 0x02, + 0x81, 0x02, 0x81, 0x08, 0x84, 0x07, 0x84, 0x02, 0x88, 0x0C, 0x84, 0x08, + 0x90, 0x09, 0x90, 0x0D, 0x81, 0x02, 0x88, 0x0D, 0x84, 0x0C, 0x84, 0x0D, + 0x84, 0x01, 0x81, 0x07, 0x81, 0x0A, 0x81, 0x01, 0x84, 0x01, 0x84, 0x0D, + 0x82, 0x0E, 0x81, 0x02, 0x81, 0x06, 0x81, 0x09, 0x88, 0x0D, 0x88, 0x09, + 0x82, 0x0B, 0x81, 0x0B, 0x88, 0x02, 0x88, 0x0C, 0x81, 0x04, 0x84, 0x0B, + 0x81, 0x06, 0x88, 0x09, 0x84, 0x0C, 0x81, 0x03, 0x84, 0x0D, 0x84, 0x0D, + 0x88, 0x0E, 0x84, 0x04, 0x90, 0x0E, 0x81, 0x02, 0x88, 0x01, 0x88, 0x06, + 0x81, 0x0E, 0x82, 0x08, 0x81, 0x0D, 0x90, 0x0D, 0x88, 0x0E, 0x81, 0x03, + 0x8A, 0x09, 0x90, 0x0B, 0x84, 0x07, 0x88, 0x03, 0x81, 0x0C, 0x82, 0x07, + 0x81, 0x0E, 0x81, 0x08, 0x90, 0x0D, 0x84, 0x0D, +}; + +// level Q -- 1400 ticks, 5 hook bytes, 10 fare stages, 256 pictures, 10 landings +// regenerate: tools/stsim/recorder levels/level??.dat Q 20260918 +static const uint8_t kGateStreamQ[320] = { + 0x84, 0x0E, 0x81, 0x04, 0x81, 0x03, 0x84, 0x09, 0x88, 0x0A, 0x81, 0x09, + 0x82, 0x06, 0x88, 0x0C, 0x81, 0x08, 0x82, 0x06, 0x88, 0x0E, 0x84, 0x0B, + 0x88, 0x08, 0x81, 0x09, 0x81, 0x0A, 0x84, 0x05, 0x84, 0x01, 0x88, 0x01, + 0x81, 0x08, 0x84, 0x0B, 0x81, 0x05, 0x82, 0x05, 0x80, 0x0D, 0x81, 0x08, + 0x88, 0x0A, 0x88, 0x04, 0x84, 0x08, 0x81, 0x06, 0x81, 0x0C, 0x81, 0x01, + 0x90, 0x0A, 0x84, 0x08, 0x81, 0x0A, 0x81, 0x0A, 0x82, 0x08, 0x81, 0x06, + 0x81, 0x09, 0x90, 0x06, 0x81, 0x09, 0x90, 0x0D, 0x88, 0x04, 0x82, 0x05, + 0x81, 0x0B, 0x84, 0x08, 0x81, 0x08, 0x81, 0x03, 0x82, 0x0D, 0x82, 0x0A, + 0x84, 0x0E, 0x81, 0x05, 0x81, 0x0E, 0x81, 0x08, 0x84, 0x0A, 0x82, 0x0C, + 0x84, 0x09, 0x84, 0x01, 0x82, 0x06, 0x88, 0x0E, 0x81, 0x09, 0x90, 0x07, + 0x81, 0x09, 0x81, 0x07, 0x84, 0x03, 0x81, 0x0B, 0x84, 0x01, 0x90, 0x08, + 0x90, 0x06, 0x82, 0x03, 0x88, 0x06, 0x88, 0x0C, 0x84, 0x0A, 0x81, 0x03, + 0x82, 0x0E, 0x81, 0x07, 0x81, 0x04, 0x81, 0x0D, 0x84, 0x02, 0x81, 0x09, + 0x82, 0x08, 0x88, 0x04, 0x81, 0x09, 0x84, 0x0C, 0x81, 0x0B, 0x84, 0x0D, + 0x81, 0x02, 0x82, 0x03, 0x81, 0x05, 0x88, 0x0E, 0x84, 0x0D, 0x81, 0x0B, + 0x88, 0x08, 0x84, 0x0C, 0x90, 0x07, 0x82, 0x0B, 0x81, 0x03, 0x90, 0x05, + 0x84, 0x05, 0x84, 0x09, 0x81, 0x04, 0x81, 0x0B, 0x81, 0x08, 0x82, 0x0E, + 0x81, 0x0A, 0x81, 0x03, 0x81, 0x06, 0x88, 0x09, 0x84, 0x0A, 0x88, 0x0B, + 0x81, 0x05, 0x81, 0x0D, 0x88, 0x03, 0x88, 0x0D, 0x81, 0x01, 0x90, 0x06, + 0x88, 0x07, 0x82, 0x02, 0x81, 0x04, 0x84, 0x09, 0x81, 0x07, 0x90, 0x06, + 0x82, 0x03, 0x84, 0x07, 0x81, 0x05, 0x90, 0x0B, 0x81, 0x0B, 0x88, 0x05, + 0x81, 0x0D, 0x81, 0x0B, 0x92, 0x0E, 0x90, 0x05, 0x82, 0x02, 0x88, 0x0C, + 0x84, 0x08, 0x81, 0x0A, 0x81, 0x0D, 0x81, 0x05, 0x81, 0x07, 0x81, 0x01, + 0x81, 0x04, 0x88, 0x01, 0x81, 0x0E, 0x84, 0x04, 0x90, 0x03, 0x81, 0x0D, + 0x90, 0x09, 0x81, 0x02, 0x81, 0x08, 0x90, 0x07, 0x82, 0x0B, 0x90, 0x08, + 0x81, 0x03, 0x90, 0x0C, 0x81, 0x0B, 0x88, 0x08, 0x90, 0x04, 0x81, 0x02, + 0x90, 0x0C, 0x88, 0x05, 0x81, 0x01, 0x90, 0x03, +}; + +// level R -- 1400 ticks, 0 hook bytes, 3 fare stages, 256 pictures, 0 landings +// regenerate: tools/stsim/recorder levels/level??.dat R 20260918 +static const uint8_t kGateStreamR[320] = { + 0x90, 0x0E, 0x81, 0x02, 0x82, 0x07, 0x81, 0x05, 0x88, 0x0B, 0x88, 0x09, + 0x90, 0x0D, 0x90, 0x06, 0x81, 0x08, 0x81, 0x01, 0x90, 0x02, 0x88, 0x04, + 0x82, 0x07, 0x90, 0x06, 0x88, 0x07, 0x81, 0x06, 0x81, 0x06, 0x82, 0x05, + 0x84, 0x07, 0x82, 0x05, 0x81, 0x03, 0x84, 0x0E, 0x88, 0x05, 0x88, 0x09, + 0x88, 0x04, 0x81, 0x09, 0x81, 0x02, 0x82, 0x04, 0x84, 0x0B, 0x81, 0x04, + 0x82, 0x0C, 0x81, 0x03, 0x88, 0x06, 0x84, 0x08, 0x81, 0x0D, 0x88, 0x09, + 0x90, 0x0E, 0x82, 0x07, 0x82, 0x02, 0x84, 0x0D, 0x88, 0x0B, 0x81, 0x0B, + 0x81, 0x03, 0x82, 0x02, 0x90, 0x0B, 0x84, 0x03, 0x88, 0x02, 0x88, 0x09, + 0x81, 0x02, 0x82, 0x0A, 0x81, 0x06, 0x81, 0x02, 0x90, 0x0A, 0x82, 0x0E, + 0x81, 0x09, 0x88, 0x03, 0x81, 0x07, 0x84, 0x0D, 0x84, 0x0A, 0x81, 0x03, + 0x82, 0x07, 0x84, 0x0D, 0x81, 0x03, 0x81, 0x08, 0x84, 0x0E, 0x81, 0x02, + 0x82, 0x0E, 0x88, 0x03, 0x81, 0x01, 0x81, 0x05, 0x81, 0x06, 0x90, 0x0A, + 0x84, 0x0A, 0x81, 0x02, 0x82, 0x05, 0x81, 0x03, 0x88, 0x07, 0x84, 0x0C, + 0x81, 0x04, 0x88, 0x09, 0x90, 0x0E, 0x82, 0x0A, 0x88, 0x09, 0x82, 0x01, + 0x81, 0x06, 0x81, 0x0D, 0x84, 0x0A, 0x82, 0x0D, 0x88, 0x0A, 0x84, 0x0A, + 0x90, 0x02, 0x84, 0x02, 0x81, 0x09, 0x82, 0x0A, 0x81, 0x0B, 0x81, 0x0D, + 0x82, 0x05, 0x84, 0x09, 0x84, 0x04, 0x88, 0x06, 0x84, 0x04, 0x88, 0x0D, + 0x81, 0x02, 0x82, 0x0B, 0x81, 0x0E, 0x82, 0x0D, 0x82, 0x0A, 0x81, 0x02, + 0x81, 0x09, 0x81, 0x0D, 0x84, 0x04, 0x82, 0x09, 0x88, 0x04, 0x81, 0x07, + 0x90, 0x0A, 0x90, 0x0B, 0x81, 0x07, 0x84, 0x0D, 0x82, 0x0A, 0x81, 0x0C, + 0x88, 0x07, 0x84, 0x09, 0x82, 0x08, 0x90, 0x0A, 0x84, 0x06, 0x82, 0x0D, + 0x88, 0x05, 0x81, 0x06, 0x81, 0x05, 0x90, 0x0D, 0x90, 0x0B, 0x81, 0x0C, + 0x81, 0x04, 0x84, 0x06, 0x90, 0x08, 0x90, 0x08, 0x90, 0x08, 0x84, 0x0E, + 0x88, 0x0E, 0x81, 0x0E, 0x82, 0x0A, 0x90, 0x04, 0x81, 0x06, 0x82, 0x09, + 0x84, 0x08, 0x90, 0x07, 0x84, 0x0C, 0x90, 0x05, 0x88, 0x0C, 0x84, 0x06, + 0x84, 0x0A, 0x81, 0x08, 0x81, 0x01, 0x81, 0x01, 0x82, 0x08, 0x88, 0x0A, + 0x90, 0x0A, 0x90, 0x01, 0x84, 0x0A, 0x81, 0x0B, +}; + +// level U -- 896 ticks, 21 hook bytes, 3 fare stages, 256 pictures, 0 landings +// regenerate: tools/stsim/recorder levels/level??.dat U 44 +static const uint8_t kGateStreamU[320] = { + 0x90, 0x01, 0x90, 0x0B, 0x88, 0x08, 0x90, 0x07, 0x88, 0x0D, 0x82, 0x0A, + 0x82, 0x06, 0x88, 0x0C, 0x90, 0x03, 0x81, 0x04, 0x84, 0x0B, 0x81, 0x0C, + 0x88, 0x02, 0x84, 0x08, 0x84, 0x01, 0x81, 0x04, 0x90, 0x05, 0x84, 0x06, + 0x84, 0x08, 0x84, 0x05, 0x81, 0x03, 0x81, 0x09, 0x81, 0x08, 0x88, 0x07, + 0x81, 0x08, 0x84, 0x06, 0x81, 0x0B, 0x88, 0x0D, 0x90, 0x08, 0x81, 0x01, + 0x82, 0x01, 0x88, 0x03, 0x81, 0x0E, 0x82, 0x01, 0x81, 0x0D, 0x81, 0x07, + 0x88, 0x05, 0x88, 0x07, 0x88, 0x0E, 0x84, 0x07, 0x81, 0x03, 0x81, 0x01, + 0x84, 0x03, 0x81, 0x04, 0x81, 0x08, 0x90, 0x06, 0x81, 0x0C, 0x82, 0x05, + 0x81, 0x08, 0x82, 0x09, 0x84, 0x06, 0x81, 0x06, 0x90, 0x0C, 0x84, 0x0E, + 0x82, 0x0C, 0x81, 0x03, 0x81, 0x06, 0x84, 0x0D, 0x88, 0x03, 0x84, 0x0C, + 0x81, 0x02, 0x81, 0x09, 0x81, 0x0E, 0x88, 0x01, 0x90, 0x06, 0x88, 0x08, + 0x81, 0x08, 0x81, 0x0C, 0x81, 0x0C, 0x82, 0x06, 0x82, 0x08, 0x88, 0x01, + 0x81, 0x07, 0x88, 0x0D, 0x84, 0x03, 0x81, 0x02, 0x82, 0x06, 0x81, 0x05, + 0x81, 0x0E, 0x81, 0x04, 0x90, 0x01, 0x81, 0x02, 0x90, 0x03, 0x81, 0x0A, + 0x81, 0x04, 0x84, 0x02, 0x81, 0x06, 0x81, 0x0E, 0x81, 0x06, 0x84, 0x07, + 0x84, 0x04, 0x81, 0x07, 0x81, 0x0E, 0x81, 0x0D, 0x90, 0x0D, 0x90, 0x05, + 0x88, 0x0C, 0x88, 0x02, 0x81, 0x0B, 0x90, 0x07, 0x88, 0x06, 0x82, 0x0D, + 0x84, 0x08, 0x84, 0x08, 0x81, 0x01, 0x81, 0x06, 0x82, 0x05, 0x84, 0x06, + 0x81, 0x06, 0x84, 0x0D, 0x81, 0x04, 0x90, 0x07, 0x88, 0x0D, 0x81, 0x09, + 0x88, 0x0D, 0x81, 0x0E, 0x81, 0x03, 0x84, 0x0E, 0x81, 0x03, 0x81, 0x0C, + 0x84, 0x0A, 0x81, 0x06, 0x90, 0x02, 0x81, 0x01, 0x88, 0x02, 0x90, 0x02, + 0x81, 0x08, 0x84, 0x01, 0x88, 0x0B, 0x88, 0x0B, 0x81, 0x04, 0x81, 0x0C, + 0x81, 0x07, 0x82, 0x06, 0x88, 0x0C, 0x81, 0x03, 0x90, 0x02, 0x88, 0x0A, + 0x84, 0x08, 0x81, 0x0E, 0x81, 0x05, 0x88, 0x0C, 0x81, 0x0D, 0x81, 0x05, + 0x81, 0x04, 0x90, 0x0D, 0x88, 0x03, 0x88, 0x06, 0x81, 0x0B, 0x81, 0x0D, + 0x84, 0x0D, 0x81, 0x01, 0x81, 0x0D, 0x81, 0x01, 0x82, 0x07, 0x88, 0x02, + 0x81, 0x09, 0x90, 0x0B, 0x88, 0x0D, 0x82, 0x02, +}; + +// level V -- 1400 ticks, 0 hook bytes, 6 fare stages, 256 pictures, 23 landings +// regenerate: tools/stsim/recorder levels/level??.dat V 20260918 +static const uint8_t kGateStreamV[320] = { + 0x84, 0x0E, 0x81, 0x04, 0x81, 0x05, 0x88, 0x0B, 0x84, 0x01, 0x81, 0x03, + 0x82, 0x06, 0x84, 0x0C, 0x81, 0x08, 0x84, 0x07, 0x82, 0x03, 0x88, 0x0A, + 0x90, 0x03, 0x81, 0x0D, 0x81, 0x0D, 0x82, 0x05, 0x81, 0x02, 0x81, 0x0E, + 0x90, 0x06, 0x81, 0x05, 0x90, 0x05, 0x81, 0x0E, 0x82, 0x0D, 0x90, 0x0E, + 0x88, 0x0E, 0x81, 0x03, 0x90, 0x07, 0x84, 0x0A, 0x81, 0x02, 0x90, 0x07, + 0x82, 0x07, 0x81, 0x02, 0x81, 0x06, 0x82, 0x08, 0x81, 0x04, 0x88, 0x0C, + 0x81, 0x06, 0x88, 0x0D, 0x90, 0x0D, 0x81, 0x05, 0x90, 0x03, 0x84, 0x0D, + 0x84, 0x0A, 0x88, 0x0A, 0x88, 0x0D, 0x81, 0x08, 0x90, 0x08, 0x90, 0x01, + 0x84, 0x06, 0x82, 0x0B, 0x81, 0x0D, 0x82, 0x07, 0x82, 0x0C, 0x81, 0x01, + 0x90, 0x08, 0x81, 0x02, 0x81, 0x04, 0x90, 0x0C, 0x84, 0x04, 0x88, 0x04, + 0x82, 0x03, 0x88, 0x07, 0x81, 0x03, 0x90, 0x05, 0x81, 0x02, 0x81, 0x03, + 0x88, 0x01, 0x90, 0x05, 0x81, 0x08, 0x81, 0x06, 0x81, 0x07, 0x81, 0x08, + 0x81, 0x06, 0x81, 0x02, 0x84, 0x0B, 0x88, 0x0D, 0x81, 0x06, 0x90, 0x02, + 0x81, 0x07, 0x82, 0x0B, 0x88, 0x04, 0x84, 0x04, 0x84, 0x05, 0x81, 0x05, + 0x81, 0x02, 0x82, 0x09, 0x81, 0x03, 0x90, 0x0C, 0x88, 0x0B, 0x82, 0x01, + 0x88, 0x0D, 0x88, 0x05, 0x81, 0x02, 0x90, 0x0B, 0x81, 0x02, 0x90, 0x0A, + 0x88, 0x0C, 0x81, 0x01, 0x90, 0x0A, 0x88, 0x0E, 0x90, 0x0A, 0x84, 0x0B, + 0x81, 0x03, 0x90, 0x08, 0x88, 0x03, 0x82, 0x0E, 0x84, 0x06, 0x90, 0x0E, + 0x81, 0x03, 0x88, 0x0A, 0x90, 0x0A, 0x82, 0x09, 0x81, 0x04, 0x88, 0x0C, + 0x84, 0x04, 0x81, 0x07, 0x90, 0x03, 0x81, 0x01, 0x81, 0x01, 0x81, 0x01, + 0x82, 0x0E, 0x81, 0x05, 0x81, 0x06, 0x81, 0x08, 0x84, 0x08, 0x88, 0x0C, + 0x82, 0x09, 0x88, 0x05, 0x81, 0x02, 0x88, 0x04, 0x81, 0x04, 0x88, 0x0C, + 0x90, 0x07, 0x88, 0x0E, 0x81, 0x03, 0x81, 0x0A, 0x82, 0x0B, 0x81, 0x02, + 0x88, 0x02, 0x90, 0x0E, 0x88, 0x0D, 0x81, 0x03, 0x88, 0x09, 0x90, 0x04, + 0x84, 0x01, 0x88, 0x04, 0x90, 0x04, 0x84, 0x04, 0x81, 0x03, 0x90, 0x0C, + 0x88, 0x07, 0x84, 0x05, 0x81, 0x02, 0x90, 0x0B, 0x90, 0x04, 0x90, 0x09, + 0x81, 0x02, 0x81, 0x01, 0x90, 0x0B, 0x88, 0x04, +}; + +// level E -- 1400 ticks, 4 hook bytes, 8 fare stages, 256 pictures, 11 landings +// regenerate: tools/stsim/recorder levels/level??.dat E 20260921 +static const uint8_t kGateStreamE[320] = { + 0x84, 0x05, 0x82, 0x03, 0x82, 0x05, 0x81, 0x0B, 0x81, 0x07, 0x81, 0x02, + 0x81, 0x01, 0x89, 0x02, 0x90, 0x03, 0x81, 0x0A, 0x81, 0x05, 0x84, 0x0C, + 0x81, 0x02, 0x88, 0x07, 0x81, 0x0B, 0x90, 0x08, 0x90, 0x0A, 0x88, 0x02, + 0x81, 0x05, 0x84, 0x08, 0x81, 0x05, 0x90, 0x03, 0x84, 0x01, 0x84, 0x08, + 0x88, 0x0A, 0x84, 0x07, 0x81, 0x04, 0x84, 0x07, 0x90, 0x0C, 0x80, 0x08, + 0x90, 0x07, 0x88, 0x03, 0x90, 0x0B, 0x88, 0x01, 0x90, 0x07, 0x88, 0x04, + 0x81, 0x02, 0x88, 0x08, 0x84, 0x05, 0x81, 0x05, 0x81, 0x02, 0x81, 0x09, + 0x88, 0x05, 0x90, 0x0E, 0x81, 0x05, 0x81, 0x01, 0x90, 0x04, 0x90, 0x08, + 0x81, 0x06, 0x84, 0x0E, 0x81, 0x09, 0x81, 0x01, 0x84, 0x05, 0x81, 0x01, + 0x81, 0x02, 0x88, 0x0B, 0x90, 0x08, 0x88, 0x0C, 0x88, 0x05, 0x88, 0x08, + 0x84, 0x09, 0x88, 0x05, 0x90, 0x05, 0x82, 0x0A, 0x81, 0x07, 0x90, 0x0A, + 0x81, 0x07, 0x81, 0x03, 0x84, 0x0B, 0x84, 0x0E, 0x88, 0x09, 0x90, 0x02, + 0x81, 0x0E, 0x81, 0x06, 0x90, 0x0E, 0x81, 0x02, 0x90, 0x08, 0x84, 0x08, + 0x81, 0x0B, 0x88, 0x0D, 0x84, 0x0C, 0x81, 0x09, 0x90, 0x08, 0x82, 0x0B, + 0x88, 0x08, 0x81, 0x07, 0x82, 0x02, 0x84, 0x0B, 0x88, 0x0B, 0x81, 0x0D, + 0x84, 0x0E, 0x82, 0x01, 0x81, 0x03, 0x90, 0x0C, 0x81, 0x05, 0x90, 0x04, + 0x82, 0x0C, 0x81, 0x06, 0x90, 0x0E, 0x88, 0x0B, 0x88, 0x09, 0x81, 0x0E, + 0x82, 0x0E, 0x90, 0x0C, 0x81, 0x06, 0x81, 0x05, 0x84, 0x0E, 0x81, 0x0C, + 0x88, 0x0B, 0x90, 0x06, 0x82, 0x05, 0x81, 0x06, 0x88, 0x0D, 0x88, 0x07, + 0x81, 0x07, 0x84, 0x07, 0x88, 0x08, 0x81, 0x07, 0x90, 0x0C, 0x84, 0x04, + 0x84, 0x0A, 0x88, 0x0B, 0x80, 0x0A, 0x81, 0x0C, 0x81, 0x07, 0x88, 0x06, + 0x88, 0x09, 0x88, 0x09, 0x81, 0x0C, 0x80, 0x06, 0x81, 0x06, 0x81, 0x07, + 0x84, 0x08, 0x82, 0x09, 0x84, 0x0C, 0x90, 0x09, 0x90, 0x03, 0x90, 0x0D, + 0x81, 0x08, 0x84, 0x05, 0x84, 0x09, 0x88, 0x0B, 0x81, 0x01, 0x81, 0x0A, + 0x90, 0x0D, 0x88, 0x0E, 0x88, 0x0D, 0x88, 0x05, 0x84, 0x06, 0x81, 0x0E, + 0x81, 0x0A, 0x81, 0x08, 0x84, 0x0C, 0x88, 0x05, 0x81, 0x02, 0x90, 0x0E, + 0x84, 0x0D, 0x84, 0x0A, 0x81, 0x05, 0x90, 0x09, +}; + +// level J -- 1400 ticks, 10 hook bytes, 10 fare stages, 256 pictures, 8 landings +// regenerate: tools/stsim/recorder levels/level??.dat J 20260921 +static const uint8_t kGateStreamJ[320] = { + 0x88, 0x05, 0x82, 0x09, 0x81, 0x01, 0x81, 0x04, 0x90, 0x06, 0x81, 0x05, + 0x81, 0x01, 0x90, 0x09, 0x88, 0x05, 0x82, 0x08, 0x81, 0x0A, 0x81, 0x03, + 0x84, 0x07, 0x88, 0x0D, 0x81, 0x06, 0x81, 0x0A, 0x84, 0x05, 0x90, 0x08, + 0x81, 0x0B, 0x90, 0x0E, 0x84, 0x0E, 0x81, 0x03, 0x90, 0x09, 0x81, 0x02, + 0x88, 0x04, 0x88, 0x08, 0x84, 0x0E, 0x81, 0x04, 0x88, 0x0D, 0x90, 0x0E, + 0x84, 0x09, 0x90, 0x05, 0x81, 0x01, 0x81, 0x03, 0x88, 0x03, 0x88, 0x07, + 0x81, 0x0B, 0x90, 0x0B, 0x88, 0x08, 0x82, 0x03, 0x90, 0x09, 0x88, 0x03, + 0x84, 0x06, 0x88, 0x0A, 0x88, 0x0B, 0x84, 0x04, 0x81, 0x05, 0x88, 0x0E, + 0x81, 0x0A, 0x90, 0x0C, 0x85, 0x04, 0x81, 0x0E, 0x90, 0x02, 0x82, 0x0D, + 0x84, 0x01, 0x81, 0x02, 0x88, 0x09, 0x81, 0x0B, 0x81, 0x02, 0x82, 0x0C, + 0x84, 0x04, 0x81, 0x05, 0x81, 0x04, 0x81, 0x04, 0x81, 0x05, 0x90, 0x05, + 0x88, 0x09, 0x81, 0x02, 0x88, 0x01, 0x81, 0x05, 0x90, 0x04, 0x81, 0x0B, + 0x81, 0x05, 0x82, 0x0D, 0x88, 0x06, 0x88, 0x07, 0x88, 0x02, 0x84, 0x02, + 0x90, 0x07, 0x88, 0x09, 0x84, 0x0C, 0x81, 0x01, 0x88, 0x0B, 0x81, 0x0C, + 0x82, 0x0A, 0x81, 0x08, 0x82, 0x01, 0x88, 0x09, 0x84, 0x05, 0x81, 0x08, + 0x84, 0x0A, 0x84, 0x09, 0x88, 0x0E, 0x81, 0x01, 0x84, 0x03, 0x82, 0x0C, + 0x81, 0x0E, 0x84, 0x0A, 0x88, 0x02, 0x84, 0x0D, 0x81, 0x0A, 0x90, 0x0E, + 0x88, 0x09, 0x90, 0x09, 0x81, 0x05, 0x90, 0x03, 0x88, 0x09, 0x84, 0x06, + 0x81, 0x01, 0x88, 0x06, 0x81, 0x05, 0x81, 0x09, 0x81, 0x06, 0x90, 0x04, + 0x82, 0x0A, 0x90, 0x07, 0x81, 0x07, 0x81, 0x0A, 0x84, 0x0E, 0x84, 0x07, + 0x81, 0x0A, 0x90, 0x0C, 0x81, 0x01, 0x90, 0x01, 0x88, 0x0D, 0x81, 0x03, + 0x88, 0x05, 0x81, 0x03, 0x88, 0x01, 0x81, 0x01, 0x82, 0x02, 0x88, 0x07, + 0x83, 0x02, 0x90, 0x07, 0x81, 0x05, 0x81, 0x04, 0x81, 0x0E, 0x88, 0x09, + 0x81, 0x07, 0x90, 0x03, 0x82, 0x0B, 0x82, 0x0B, 0x90, 0x04, 0x81, 0x05, + 0x81, 0x03, 0x90, 0x0C, 0x82, 0x07, 0x88, 0x0E, 0x90, 0x04, 0x81, 0x0A, + 0x88, 0x02, 0x88, 0x0D, 0x82, 0x07, 0x90, 0x01, 0x81, 0x02, 0x81, 0x01, + 0x82, 0x07, 0x81, 0x08, 0x84, 0x0E, 0x81, 0x03, +}; + +// level L -- 1400 ticks, 0 hook bytes, 10 fare stages, 256 pictures, 8 landings +// regenerate: tools/stsim/recorder levels/level??.dat L 20260921 +static const uint8_t kGateStreamL[320] = { + 0x84, 0x05, 0x84, 0x09, 0x81, 0x06, 0x81, 0x04, 0x81, 0x08, 0x81, 0x05, + 0x81, 0x01, 0x84, 0x09, 0x90, 0x09, 0x82, 0x0D, 0x88, 0x07, 0x81, 0x06, + 0x81, 0x09, 0x82, 0x01, 0x81, 0x08, 0x88, 0x0B, 0x84, 0x0E, 0x81, 0x03, + 0x82, 0x02, 0x82, 0x06, 0x82, 0x03, 0x88, 0x0E, 0x84, 0x0C, 0x81, 0x06, + 0x88, 0x02, 0x81, 0x04, 0x88, 0x0E, 0x81, 0x04, 0x90, 0x0B, 0x81, 0x08, + 0x84, 0x09, 0x82, 0x09, 0x81, 0x01, 0x90, 0x03, 0x81, 0x05, 0x84, 0x07, + 0x81, 0x07, 0x90, 0x0B, 0x81, 0x07, 0x88, 0x09, 0x81, 0x0E, 0x88, 0x03, + 0x81, 0x01, 0x88, 0x09, 0x81, 0x07, 0x81, 0x0C, 0x88, 0x0C, 0x90, 0x0E, + 0x81, 0x0E, 0x90, 0x05, 0x84, 0x0E, 0x82, 0x0E, 0x82, 0x01, 0x84, 0x0A, + 0x88, 0x03, 0x90, 0x09, 0x82, 0x05, 0x81, 0x0E, 0x81, 0x05, 0x81, 0x07, + 0x81, 0x0D, 0x81, 0x0E, 0x90, 0x04, 0x90, 0x04, 0x81, 0x06, 0x81, 0x01, + 0x88, 0x0B, 0x81, 0x01, 0x90, 0x0B, 0x81, 0x05, 0x84, 0x0D, 0x81, 0x01, + 0x81, 0x0A, 0x84, 0x0D, 0x82, 0x09, 0x88, 0x06, 0x88, 0x04, 0x84, 0x0C, + 0x88, 0x0C, 0x84, 0x02, 0x81, 0x07, 0x82, 0x07, 0x84, 0x08, 0x88, 0x09, + 0x81, 0x01, 0x88, 0x01, 0x81, 0x0C, 0x81, 0x04, 0x84, 0x06, 0x82, 0x06, + 0x90, 0x0D, 0x81, 0x06, 0x81, 0x0D, 0x82, 0x0D, 0x82, 0x08, 0x81, 0x0C, + 0x84, 0x05, 0x82, 0x09, 0x81, 0x05, 0x84, 0x07, 0x82, 0x02, 0x84, 0x01, + 0x81, 0x02, 0x90, 0x02, 0x82, 0x06, 0x88, 0x03, 0x84, 0x0B, 0x81, 0x0E, + 0x88, 0x0E, 0x90, 0x07, 0x82, 0x08, 0x82, 0x02, 0x90, 0x02, 0x82, 0x03, + 0x84, 0x07, 0x82, 0x05, 0x84, 0x07, 0x81, 0x0B, 0x82, 0x0E, 0x90, 0x05, + 0x88, 0x04, 0x90, 0x05, 0x84, 0x0A, 0x82, 0x0A, 0x88, 0x0C, 0x81, 0x08, + 0x82, 0x0C, 0x81, 0x06, 0x84, 0x09, 0x81, 0x04, 0x84, 0x0B, 0x88, 0x0B, + 0x81, 0x0C, 0x81, 0x0C, 0x82, 0x0E, 0x81, 0x03, 0x82, 0x09, 0x88, 0x04, + 0x81, 0x05, 0x88, 0x04, 0x80, 0x0B, 0x84, 0x06, 0x81, 0x0E, 0x84, 0x0B, + 0x90, 0x0B, 0x90, 0x07, 0x82, 0x0D, 0x84, 0x0D, 0x81, 0x08, 0x81, 0x09, + 0x84, 0x0E, 0x81, 0x0B, 0x90, 0x04, 0x84, 0x0B, 0x84, 0x03, 0x81, 0x03, + 0x88, 0x0A, 0x81, 0x07, 0x84, 0x0B, 0x90, 0x0E, +}; + +// level P -- 1400 ticks, 11 hook bytes, 10 fare stages, 256 pictures, 4 landings +// regenerate: tools/stsim/recorder levels/level??.dat P 20260921 +static const uint8_t kGateStreamP[320] = { + 0x88, 0x05, 0x82, 0x09, 0x81, 0x0B, 0x81, 0x04, 0x81, 0x04, 0x84, 0x05, + 0x82, 0x01, 0x90, 0x0C, 0x81, 0x03, 0x81, 0x0D, 0x81, 0x01, 0x84, 0x0C, + 0x84, 0x0E, 0x88, 0x07, 0x81, 0x0B, 0x88, 0x0D, 0x90, 0x0A, 0x88, 0x02, + 0x81, 0x05, 0x84, 0x08, 0x81, 0x05, 0x90, 0x0C, 0x84, 0x01, 0x88, 0x08, + 0x88, 0x0A, 0x84, 0x0C, 0x81, 0x04, 0x84, 0x07, 0x90, 0x0C, 0x80, 0x09, + 0x90, 0x06, 0x81, 0x03, 0x81, 0x0C, 0x88, 0x01, 0x82, 0x03, 0x81, 0x04, + 0x84, 0x0D, 0x88, 0x09, 0x84, 0x05, 0x81, 0x05, 0x81, 0x02, 0x81, 0x09, + 0x81, 0x02, 0x90, 0x0E, 0x81, 0x05, 0x81, 0x01, 0x81, 0x03, 0x88, 0x0A, + 0x81, 0x06, 0x88, 0x05, 0x90, 0x0C, 0x90, 0x01, 0x84, 0x0E, 0x88, 0x01, + 0x81, 0x02, 0x82, 0x05, 0x90, 0x08, 0x88, 0x0A, 0x84, 0x0C, 0x90, 0x08, + 0x84, 0x0C, 0x81, 0x06, 0x81, 0x03, 0x88, 0x0A, 0x84, 0x08, 0x90, 0x0E, + 0x81, 0x02, 0x88, 0x0C, 0x81, 0x06, 0x84, 0x0C, 0x81, 0x09, 0x80, 0x07, + 0x88, 0x04, 0x81, 0x07, 0x81, 0x03, 0x81, 0x09, 0x81, 0x05, 0x86, 0x05, + 0x90, 0x09, 0x84, 0x06, 0x81, 0x03, 0x84, 0x09, 0x81, 0x0E, 0x88, 0x0B, + 0x90, 0x08, 0x88, 0x0A, 0x81, 0x04, 0x90, 0x0D, 0x88, 0x0E, 0x88, 0x09, + 0x81, 0x0B, 0x90, 0x0C, 0x84, 0x05, 0x81, 0x08, 0x84, 0x01, 0x82, 0x02, + 0x81, 0x03, 0x82, 0x0E, 0x88, 0x02, 0x88, 0x05, 0x81, 0x01, 0x81, 0x08, + 0x82, 0x03, 0x84, 0x0E, 0x81, 0x01, 0x81, 0x08, 0x84, 0x03, 0x82, 0x03, + 0x82, 0x05, 0x81, 0x0A, 0x81, 0x02, 0x90, 0x01, 0x81, 0x0E, 0x81, 0x0C, + 0x90, 0x0C, 0x88, 0x0A, 0x84, 0x03, 0x81, 0x03, 0x84, 0x0A, 0x84, 0x05, + 0x82, 0x09, 0x88, 0x0A, 0x81, 0x06, 0x84, 0x07, 0x84, 0x0C, 0x81, 0x0C, + 0x82, 0x07, 0x81, 0x06, 0x81, 0x03, 0x88, 0x09, 0x90, 0x01, 0x84, 0x0E, + 0x81, 0x01, 0x88, 0x06, 0x81, 0x0E, 0x81, 0x0A, 0x88, 0x0A, 0x81, 0x08, + 0x84, 0x08, 0x88, 0x0E, 0x81, 0x02, 0x84, 0x0E, 0x82, 0x05, 0x81, 0x06, + 0x88, 0x0C, 0x81, 0x0B, 0x81, 0x0D, 0x84, 0x02, 0x90, 0x0E, 0x84, 0x0E, + 0x81, 0x02, 0x90, 0x07, 0x88, 0x0C, 0x90, 0x0A, 0x81, 0x0E, 0x81, 0x0A, + 0x81, 0x0D, 0x81, 0x0B, 0x81, 0x08, 0x81, 0x03, +}; + +// level S -- 1400 ticks, 0 hook bytes, 10 fare stages, 256 pictures, 23 landings +// regenerate: tools/stsim/recorder levels/level??.dat S 20260921 +static const uint8_t kGateStreamS[320] = { + 0x90, 0x04, 0x90, 0x0E, 0x88, 0x08, 0x82, 0x0D, 0x88, 0x0C, 0x81, 0x03, + 0x81, 0x0A, 0x81, 0x0D, 0x88, 0x09, 0x88, 0x0C, 0x82, 0x0E, 0x81, 0x07, + 0x81, 0x0D, 0x81, 0x0A, 0x90, 0x0D, 0x84, 0x01, 0x90, 0x05, 0x88, 0x0E, + 0x81, 0x0A, 0x81, 0x0A, 0x90, 0x06, 0x88, 0x03, 0x82, 0x0B, 0x82, 0x0B, + 0x88, 0x03, 0x88, 0x0A, 0x90, 0x0C, 0x81, 0x0E, 0x81, 0x0E, 0x90, 0x06, + 0x88, 0x02, 0x82, 0x08, 0x81, 0x0A, 0x90, 0x0D, 0x90, 0x05, 0x81, 0x05, + 0x88, 0x0B, 0x81, 0x05, 0x90, 0x0B, 0x88, 0x0E, 0x82, 0x09, 0x90, 0x07, + 0x81, 0x03, 0x81, 0x0D, 0x88, 0x09, 0x88, 0x0E, 0x84, 0x0A, 0x88, 0x0A, + 0x82, 0x01, 0x90, 0x0C, 0x82, 0x01, 0x90, 0x0A, 0x82, 0x0A, 0x84, 0x03, + 0x81, 0x02, 0x90, 0x04, 0x84, 0x0E, 0x90, 0x09, 0x81, 0x0D, 0x90, 0x06, + 0x81, 0x02, 0x90, 0x0E, 0x90, 0x07, 0x82, 0x05, 0x81, 0x06, 0x90, 0x07, + 0x82, 0x03, 0x90, 0x07, 0x90, 0x0B, 0x82, 0x06, 0x88, 0x0E, 0x90, 0x08, + 0x84, 0x0A, 0x82, 0x07, 0x81, 0x04, 0x81, 0x03, 0x84, 0x05, 0x90, 0x08, + 0x81, 0x0C, 0x82, 0x01, 0x90, 0x0B, 0x81, 0x0A, 0x84, 0x05, 0x81, 0x02, + 0x82, 0x09, 0x90, 0x0E, 0x81, 0x02, 0x84, 0x03, 0x81, 0x0C, 0x81, 0x0C, + 0x81, 0x0D, 0x82, 0x08, 0x81, 0x03, 0x81, 0x0D, 0x81, 0x0A, 0x81, 0x0A, + 0x81, 0x0C, 0x82, 0x06, 0x90, 0x0D, 0x88, 0x0C, 0x84, 0x04, 0x81, 0x01, + 0x81, 0x0A, 0x81, 0x03, 0x90, 0x06, 0x88, 0x07, 0x88, 0x0E, 0x84, 0x0E, + 0x81, 0x0E, 0x82, 0x03, 0x90, 0x0E, 0x82, 0x05, 0x90, 0x02, 0x88, 0x03, + 0x81, 0x07, 0x81, 0x0A, 0x81, 0x0B, 0x81, 0x05, 0x84, 0x0D, 0x81, 0x01, + 0x82, 0x02, 0x90, 0x0C, 0x82, 0x03, 0x90, 0x08, 0x81, 0x05, 0x84, 0x08, + 0x84, 0x06, 0x88, 0x08, 0x81, 0x0E, 0x82, 0x01, 0x81, 0x01, 0x90, 0x08, + 0x82, 0x03, 0x90, 0x0C, 0x84, 0x02, 0x81, 0x09, 0x82, 0x04, 0x88, 0x0D, + 0x90, 0x07, 0x88, 0x0E, 0x82, 0x05, 0x81, 0x0C, 0x90, 0x0A, 0x81, 0x04, + 0x84, 0x06, 0x82, 0x02, 0x81, 0x09, 0x90, 0x0D, 0x82, 0x06, 0x82, 0x01, + 0x90, 0x09, 0x81, 0x0B, 0x84, 0x04, 0x88, 0x07, 0x81, 0x0B, 0x81, 0x05, + 0x82, 0x03, 0x81, 0x07, 0x90, 0x06, 0x82, 0x07, +}; + +typedef struct { + uint8_t level; // level letter the ride was searched for + const uint8_t *stream; + uint16_t length; +} StGateStreamT; + +static const StGateStreamT kGateStreams[] = { + { 'G', kGateStreamG, (uint16_t)sizeof(kGateStreamG) }, + { 'I', kGateStreamI, (uint16_t)sizeof(kGateStreamI) }, + { 'K', kGateStreamK, (uint16_t)sizeof(kGateStreamK) }, + { 'O', kGateStreamO, (uint16_t)sizeof(kGateStreamO) }, + { 'Q', kGateStreamQ, (uint16_t)sizeof(kGateStreamQ) }, + { 'R', kGateStreamR, (uint16_t)sizeof(kGateStreamR) }, + { 'U', kGateStreamU, (uint16_t)sizeof(kGateStreamU) }, + { 'V', kGateStreamV, (uint16_t)sizeof(kGateStreamV) }, + { 'E', kGateStreamE, (uint16_t)sizeof(kGateStreamE) }, + { 'J', kGateStreamJ, (uint16_t)sizeof(kGateStreamJ) }, + { 'L', kGateStreamL, (uint16_t)sizeof(kGateStreamL) }, + { 'P', kGateStreamP, (uint16_t)sizeof(kGateStreamP) }, + { 'S', kGateStreamS, (uint16_t)sizeof(kGateStreamS) }, +}; +#define ST_GATE_STREAMS (sizeof(kGateStreams) / sizeof(kGateStreams[0])) + +#endif diff --git a/tools/stsim/recorder.c b/tools/stsim/recorder.c new file mode 100644 index 0000000..f5be285 --- /dev/null +++ b/tools/stsim/recorder.c @@ -0,0 +1,195 @@ +// recorder -- search for a demo input stream that exercises a level. +// +// The C64 ships attract recordings for four screens (H, W, T, X). The +// other levels have none, so a refactor of their hooks had nothing to be +// checked against. This searches for a synthetic ride instead: random +// restarts plus hill climbing over (joystick mask, duration) pairs, +// scored on how long the cab survives and how much of the level it +// reaches -- hook state bytes written, fare-machine stages entered, +// distinct pictures drawn, pads landed on. +// +// The result is a REGRESSION fixture, not a recording: it pins current +// behaviour so a refactor that changes nothing observable leaves the +// trace identical. It says nothing about matching the real C64. +// +// Usage: recorder > stream.h +// Then assemble the per-level outputs into gateStreams.h. +// +// Determinism: a candidate is only useful if it replays identically from +// a cold start, so every evaluation clears the whole StSimT (see the note +// in evaluate) -- stSimNewGame deliberately preserves some cross-screen +// state, which would otherwise leak between candidates. + +#include +#include +#include +#include "stSim.h" +#include "stDemoStreams.h" + +static StLevelT gLevel; +static StSimT gSim; + +void stAudioSfx(const uint8_t *p){(void)p;} +void stAudioNoise(bool o){(void)o;} +void stAudioThrustSweep(uint8_t v){(void)v;} +void stAudioSpeech(uint8_t c){(void)c;} +void stAudioSpeechPhrase(const uint8_t *s){(void)s;} +void stAudioSilence(void){} +void stAudioVoice2(uint8_t a,uint8_t b,uint8_t c){(void)a;(void)b;(void)c;} +void stAudioVoice1Freq(uint8_t v){(void)v;} + +#define PAIRS 160 +#define STREAMLEN (PAIRS * 2) +#define MAXTICKS 1400 + +typedef struct { uint32_t score; uint32_t ticks; uint32_t cover; uint32_t lands; uint32_t stages; uint32_t scr; uint32_t near; } ResT; + +static uint32_t rs = 1; +static uint32_t rnd(void){ rs = rs*1664525u + 1013904223u; return rs>>8; } + +static ResT evaluate(const uint8_t *stream, uint8_t *touchOut) +{ + uint8_t touched[sizeof(StHookScratchT)]; + uint8_t base[sizeof(StHookScratchT)]; + uint8_t stageSeen[16]; + uint32_t t, cover = 0, lands = 0, stages = 0, scrSeen = 0; + uint32_t nearest = 255u; + uint32_t scrHash[256]; + uint8_t lastPad = 0; + ResT r; + + memset(stageSeen, 0, sizeof(stageSeen)); + memset(touched, 0, sizeof(touched)); + // stSimNewGame deliberately PRESERVES flameParity, waveIdx and + // walkParity (the C64 carries them from screen to screen), so a + // search that reuses one StSimT would score each candidate against + // whatever the previous ride left behind -- and the winning stream + // would then not reproduce from a cold start. Clear the whole struct + // so every evaluation begins exactly where the gate will. + memset(&gSim, 0, sizeof(gSim)); + stSimNewGame(&gSim, 1u, true); + stSimSeedDemoBuffer(&gSim, kDemoBufferInit, ST_DEMO_BUFFER_BYTES); + stSimEnterLevel(&gSim, &gLevel); + stSimSetDemoStream(&gSim, stream, STREAMLEN); + memcpy(base, &gSim.hookScratch, sizeof(base)); + for (t = 0; t < MAXTICKS; t++) { + uint16_t k; + if (stSimTick(&gSim) != ST_TICK_CONTINUE) { break; } + { const uint8_t *cur = (const uint8_t *)&gSim.hookScratch; + for (k = 0; k < (uint16_t)sizeof(base); k++) { + if (cur[k] != base[k]) { touched[k] = 1; } } } + if (gSim.activePad != 0u && lastPad == 0u) { lands++; } + lastPad = gSim.activePad; + if (gSim.stage < 16u) { stageSeen[gSim.stage] = 1u; } + /* Shaping: how close the ride came to an actual touchdown. Some + levels (R) only run their hook once a passenger has boarded, + which needs a landing, and a landing is far too precise for a + blind search to stumble on -- so reward getting near one. */ + if (gSim.activePad == 0u && (gSim.spr[0].ptr & 1u) != 0u) { + int16_t tx = (int16_t)(((uint16_t)gSim.posXmsb << 8) | gSim.posXcol); + uint8_t pi; + for (pi = 0u; pi < gLevel.padCount && pi < ST_MAX_PADS; pi++) { + int16_t x1 = (int16_t)(((uint16_t)gSim.pads[pi].x1Hi << 8) | gSim.pads[pi].x1Lo); + int16_t x2 = (int16_t)(((uint16_t)gSim.pads[pi].x2Hi << 8) | gSim.pads[pi].x2Lo); + int16_t dr = (int16_t)((int16_t)gSim.spr[0].row - (int16_t)gSim.pads[pi].row); + uint32_t ar = (uint32_t)(dr < 0 ? -dr : dr); + uint32_t ax = 0u; + uint32_t d; + if (tx < x1) { ax = (uint32_t)(x1 - tx); } + else if (tx > x2) { ax = (uint32_t)(tx - x2); } + /* Both axes matter: the row alone is easy to hit while + the cab sits at the far side of the screen. */ + d = ar * 4u + ax; + if (d < nearest) { nearest = d; } + } + } + /* distinct pictures the hook has produced (screen + colour) */ + { uint32_t h = 2166136261u; uint16_t c; uint32_t i2; int dup = 0; + for (c = 0; c < ST_SCREEN_CELLS; c++) { h = (h ^ gSim.screen[c]) * 16777619u; } + for (c = 0; c < ST_SCREEN_CELLS; c++) { h = (h ^ gSim.color[c]) * 16777619u; } + for (i2 = 0; i2 < scrSeen; i2++) { if (scrHash[i2] == h) { dup = 1; break; } } + if (!dup && scrSeen < 256u) { scrHash[scrSeen++] = h; } } + } + { uint16_t k; for (k = 0; k < (uint16_t)sizeof(touched); k++) { if (touched[k]) { cover++; } } } + { int k; for (k = 0; k < 16; k++) { if (stageSeen[k]) { stages++; } } } + if (touchOut) { memcpy(touchOut, touched, sizeof(touched)); } + r.ticks = t; r.cover = cover; r.lands = lands; r.stages = stages; r.scr = scrSeen; + r.near = nearest; + r.score = cover * 30000u + stages * 30000u + lands * 4000u + t * 100u + scrSeen * 200u + + (255u - nearest) * 60u; + return r; +} + +static void randomise(uint8_t *s, int from) +{ + int i; + for (i = from; i < PAIRS; i++) { + uint32_t d = rnd() % 100u; + uint8_t m = 0x80u; + if (d < 45u) { m |= 0x01u; } /* thrust up a lot */ + else if (d < 60u) { m |= 0x04u; } + else if (d < 75u) { m |= 0x08u; } + else if (d < 82u) { m |= 0x02u; } + else if (d < 88u) { m |= 0x10u; } + if (rnd() % 5u == 0u) { m |= (uint8_t)(1u << (rnd() % 4u)); } + s[i*2] = m; + s[i*2 + 1] = (uint8_t)(1u + rnd() % 14u); + } +} + +int main(int argc, char **argv) +{ + uint8_t best[STREAMLEN], cur[STREAMLEN], touched[ST_HOOK_BYTES]; + ResT bestR, curR; + FILE *fp; + int restart, iter; + + if (argc < 4) { fprintf(stderr, "usage: recorder \n"); return 2; } + fp = fopen(argv[1], "rb"); + if (!fp || !stLevelParse(&gLevel, fp)) { fprintf(stderr, "cannot load %s\n", argv[1]); return 1; } + fclose(fp); + rs = (uint32_t)strtoul(argv[3], NULL, 0); + + randomise(best, 0); + bestR = evaluate(best, NULL); + for (restart = 0; restart < 120; restart++) { + memcpy(cur, best, STREAMLEN); + if (restart > 0) { randomise(cur, (int)(rnd() % PAIRS)); } + curR = evaluate(cur, NULL); + for (iter = 0; iter < 3000; iter++) { + uint8_t trial[STREAMLEN]; + ResT tr; + int j, nmut = 1 + (int)(rnd() % 3u); + memcpy(trial, cur, STREAMLEN); + for (j = 0; j < nmut; j++) { + int i = (int)(rnd() % PAIRS); + if (rnd() % 2u) { + uint8_t m = 0x80u; + uint32_t d = rnd() % 100u; + if (d < 45u) { m |= 0x01u; } + else if (d < 62u) { m |= 0x04u; } + else if (d < 79u) { m |= 0x08u; } + else if (d < 88u) { m |= 0x02u; } + else { m |= 0x10u; } + trial[i*2] = m; + } else { + trial[i*2 + 1] = (uint8_t)(1u + rnd() % 14u); + } + } + tr = evaluate(trial, NULL); + if (tr.score >= curR.score) { memcpy(cur, trial, STREAMLEN); curR = tr; } + } + if (curR.score > bestR.score) { memcpy(best, cur, STREAMLEN); bestR = curR; } + } + bestR = evaluate(best, touched); + fprintf(stderr, "%s: ticks=%u hookBytes=%u stages=%u pictures=%u lands=%u nearPad=%u\n", + argv[2], bestR.ticks, bestR.cover, bestR.stages, bestR.scr, bestR.lands, bestR.near); + printf("// %s: %u ticks, %u hook bytes, %u fare stages, %u pictures, %u landings\n", + argv[2], bestR.ticks, bestR.cover, bestR.stages, bestR.scr, bestR.lands); + printf("static const uint8_t kGateStream%s[%d] = {", argv[2], STREAMLEN); + { int i; for (i = 0; i < STREAMLEN; i++) { + if (i % 12 == 0) { printf("\n "); } + printf("0x%02X,%s", best[i], (i % 12 == 11) ? "" : " "); } } + printf("\n};\n"); + return 0; +} diff --git a/tools/stsim/stsim.c b/tools/stsim/stsim.c index a6b8e88..7791b46 100644 --- a/tools/stsim/stsim.c +++ b/tools/stsim/stsim.c @@ -5,14 +5,19 @@ // the VICE tracer records (see stuff/spacetaxi and the compare script), // so the port's engine can be diffed against the real machine. // -// stsim +// stsim +// +// H, W, T and X use the C64's own attract recordings; every other +// letter uses a synthesised gate fixture from gateStreams.h. #include #include #include #include "../../examples/spacetaxi/stSim.h" +#include "../../examples/spacetaxi/stCels.h" #include "../../examples/spacetaxi/stDemoStreams.h" +#include "gateStreams.h" static StLevelT gLevel; @@ -23,11 +28,105 @@ void stAudioSfx(const uint8_t *program9) { (void)program9; } void stAudioNoise(bool on) { (void)on; } void stAudioThrustSweep(uint8_t value) { (void)value; } void stAudioSpeech(uint8_t ch) { (void)ch; } +void stAudioSpeechPhrase(const uint8_t *speech) { (void)speech; } void stAudioSilence(void) { } void stAudioVoice2(uint8_t freqLo, uint8_t freqHi, uint8_t ctrl) { (void)freqLo; (void)freqHi; (void)ctrl; } void stAudioVoice1Freq(uint8_t value) { (void)value; } +// FNV-1a over a byte span: the picture the player actually sees. The +// hooks that animate the screen (O, R, V, W, X) change screen, colour or +// charset bytes and nothing else this line used to capture. +static uint32_t hashBytes(const uint8_t *p, size_t n) { + uint32_t h = 2166136261u; + size_t i; + + for (i = 0u; i < n; i++) { + h = (h ^ p[i]) * 16777619u; + } + return h; +} + + +// The charset the game would be showing. The sim no longer holds a +// contiguous 2 KB copy -- edited glyphs live in an override table -- so +// rebuild the effective image here. Hashing this keeps the trace value +// identical to the pre-override captures, which is what lets the goldens +// prove the change is behaviour-preserving. +static uint32_t charsetHash(const StSimT *sim) { + uint8_t image[ST_CHARSET_CHARS * 8u]; + uint16_t ch; + + for (ch = 0u; ch < ST_CHARSET_CHARS; ch++) { + memcpy(&image[ch * 8u], stSimGlyph(sim, (uint8_t)ch), 8u); + } + return hashBytes(image, sizeof(image)); +} + + +// STSIM_CELS=1: every level cel (ptr below the baked set) a ride +// displays, as (ptr, colour, multi, mc0, mc1), against what +// stLevelCelList bakes for the level -- a cel shown but not listed is +// built and drawn interpreted mid-ride on the real ports. +#define CENSUS_MAX 64u +static StCelDefT gCensus[CENSUS_MAX]; +static uint8_t gCensusCount; + + +static void celCensusTick(const StSimT *sim) { + uint8_t i; + + for (i = 1u; i < ST_HW_SPRITES; i++) { + StCelDefT d; + uint8_t k; + + if ((sim->frame.enableMask & kStBit[i]) == 0u || sim->frame.ptr[i] >= ST_SPRITE_PTR_FIRST) { + continue; + } + // A pointer with no bitmap (a hook enables a sprite a tick before + // aiming it) draws nothing on every port -- not a cel. + if (stSimSpriteBitmap(sim, sim->frame.ptr[i]) == 0) { + continue; + } + d.ptr = sim->frame.ptr[i]; + d.color = sim->frame.color[i]; + d.multi = (sim->frame.multiMask & kStBit[i]) != 0u ? 1u : 0u; + d.mc0 = d.multi ? sim->spriteMc0 : 0u; + d.mc1 = d.multi ? sim->spriteMc1 : 0u; + for (k = 0u; k < gCensusCount; k++) { + if (memcmp(&gCensus[k], &d, sizeof(d)) == 0) { + break; + } + } + if (k == gCensusCount && gCensusCount < CENSUS_MAX) { + gCensus[gCensusCount++] = d; + } + } +} + + +static void celCensusReport(const StLevelT *level) { + StCelDefT listed[ST_LEVEL_CELS_MAX]; + uint8_t n = stLevelCelList(level, listed, ST_LEVEL_CELS_MAX); + uint8_t k; + uint8_t j; + + for (k = 0u; k < gCensusCount; k++) { + for (j = 0u; j < n; j++) { + if (memcmp(&listed[j], &gCensus[k], sizeof(StCelDefT)) == 0) { + break; + } + } + fprintf(stderr, "CEL %s ptr=%02X color=%u multi=%u mc=%u/%u\n", (j < n) ? "listed " : "MISSING", gCensus[k].ptr, gCensus[k].color, gCensus[k].multi, gCensus[k].mc0, gCensus[k].mc1); + } + fprintf(stderr, "CENSUS shown=%u listed=%u\n", gCensusCount, n); +} + + +static void celCensusReport(const StLevelT *level); +static void celCensusTick(const StSimT *sim); + + static void printState(const StSimT *sim, uint32_t tick) { uint8_t k; @@ -55,7 +154,11 @@ static void printState(const StSimT *sim, uint32_t tick) { for (k = 0u; k < ST_HW_SPRITES; k++) { printf("%03X%02X", sim->frame.x[k], sim->frame.y[k]); } - printf("\"}\n"); + printf("\",\"scr\":\"%08X\",\"col\":\"%08X\",\"chr\":\"%08X\",\"bd\":\"%02X\",\"bg\":\"%02X\"}\n", + hashBytes(sim->screen, ST_SCREEN_CELLS), + hashBytes(sim->color, ST_SCREEN_CELLS), + charsetHash(sim), + sim->borderColor, sim->bgColor); } @@ -63,11 +166,12 @@ int main(int argc, char **argv) { FILE *fp; const uint8_t *stream = kDemoStreamH; uint16_t len = (uint16_t)sizeof(kDemoStreamH); + uint8_t want; uint32_t ticks; uint32_t t; if (argc < 4) { - fprintf(stderr, "usage: stsim \n"); + fprintf(stderr, "usage: stsim \n"); return 2; } fp = fopen(argv[1], "rb"); @@ -76,11 +180,29 @@ int main(int argc, char **argv) { return 1; } fclose(fp); - switch (argv[2][0]) { + want = (uint8_t)argv[2][0]; + switch (want) { case 'W': stream = kDemoStreamW; len = (uint16_t)sizeof(kDemoStreamW); break; case 'T': stream = kDemoStreamT; len = (uint16_t)sizeof(kDemoStreamT); break; case 'X': stream = kDemoStreamX; len = (uint16_t)sizeof(kDemoStreamX); break; - default: break; + case 'H': break; + default: { + // A synthesised gate fixture for a level the C64 has no + // attract recording of (see gateStreams.h). + uint8_t g; + for (g = 0u; g < ST_GATE_STREAMS; g++) { + if (kGateStreams[g].level == want) { + stream = kGateStreams[g].stream; + len = kGateStreams[g].length; + break; + } + } + if (g == ST_GATE_STREAMS) { + fprintf(stderr, "stsim: no stream for '%c'\n", (char)want); + return 2; + } + break; + } } ticks = (uint32_t)strtoul(argv[3], NULL, 0); stSimNewGame(&gSim, 1u, true); @@ -101,10 +223,16 @@ int main(int argc, char **argv) { StTickResultE r; printState(&gSim, t); r = stSimTick(&gSim); + if (getenv("STSIM_CELS") != NULL) { + celCensusTick(&gSim); + } if (r != ST_TICK_CONTINUE) { printf("{\"tick\":%u,\"end\":%d}\n", t, (int)r); break; } } + if (getenv("STSIM_CELS") != NULL) { + celCensusReport(&gLevel); + } return 0; }