From f4b344cccd5b7f489c379c138d4985f310fb2a88 Mon Sep 17 00:00:00 2001 From: "mr.one" Date: Sun, 19 Jul 2026 00:12:37 +0200 Subject: [PATCH] Implement NUFLI delta encoding with scanlines - Add tools/apply_scanlines.py for CRT scanline effect - Add tools/nufli_delta.py for delta encoding (base + bitmask deltas) - Delta format: 2880 byte bitmask + N byte values per screen - 40.1% size reduction vs raw NUFLI (69KB vs 115KB for 5 screens) - Base (23KB) at 000-FFF, title delta (7KB) at - - Other deltas use title as fallback (TODO: disk loading) - Update tasks.md with scanline and delta encoding documentation --- Makefile | 45 +++++----- src/data/nufli/nufli_base.base | Bin 0 -> 23040 bytes src/data/nufli/screen_title.delta | Bin 0 -> 7081 bytes src/data/nufli/screen_title.h | 12 --- src/data/nufli/screen_waiting1.delta | Bin 0 -> 9966 bytes src/data/nufli/screen_waiting1.h | 12 --- src/data/nufli/screen_waiting_2.delta | Bin 0 -> 9732 bytes src/data/nufli/screen_waiting_2.h | 12 --- src/data/nufli/screen_win_hare.delta | Bin 0 -> 9548 bytes src/data/nufli/screen_win_hare.h | 12 --- src/data/nufli/screen_win_scoot.delta | Bin 0 -> 9642 bytes src/data/nufli/screen_win_scoot.h | 12 --- src/game.c | 45 +++++----- src/main.c | 22 +---- src/nufli.c | 125 ++++++++++++++++++++++++++ src/nufli.h | 20 +++++ src/test_nufli.c | 17 ++++ src/test_nufli2.c | 18 ++++ src/test_simple.c | 16 ++++ tasks.md | 58 ++++++++++++ tools/apply_scanlines.py | 14 +++ tools/nufli_delta.py | 111 +++++++++++++++++++++++ 22 files changed, 426 insertions(+), 125 deletions(-) create mode 100644 src/data/nufli/nufli_base.base create mode 100644 src/data/nufli/screen_title.delta delete mode 100644 src/data/nufli/screen_title.h create mode 100644 src/data/nufli/screen_waiting1.delta delete mode 100644 src/data/nufli/screen_waiting1.h create mode 100644 src/data/nufli/screen_waiting_2.delta delete mode 100644 src/data/nufli/screen_waiting_2.h create mode 100644 src/data/nufli/screen_win_hare.delta delete mode 100644 src/data/nufli/screen_win_hare.h create mode 100644 src/data/nufli/screen_win_scoot.delta delete mode 100644 src/data/nufli/screen_win_scoot.h create mode 100644 src/nufli.c create mode 100644 src/nufli.h create mode 100644 src/test_nufli.c create mode 100644 src/test_nufli2.c create mode 100644 src/test_simple.c create mode 100644 tools/apply_scanlines.py create mode 100644 tools/nufli_delta.py diff --git a/Makefile b/Makefile index aba24d0..13d31fb 100644 --- a/Makefile +++ b/Makefile @@ -148,39 +148,44 @@ NUFLI_SCREENS := screen_title screen_waiting1 screen_waiting_2 screen_win_hare s # Generated files NUFLI_NUF_FILES := $(addprefix $(NUFLI_OUT_DIR)/,$(addsuffix .nuf,$(NUFLI_SCREENS))) -NUFLI_ASM_FILES := $(addprefix $(NUFLI_OUT_DIR)/,$(addsuffix .asm,$(NUFLI_SCREENS))) -NUFLI_HDR_FILES := $(addprefix $(NUFLI_OUT_DIR)/,$(addsuffix .h,$(NUFLI_SCREENS))) +NUFLI_DELTA_FILES := $(addprefix $(NUFLI_OUT_DIR)/,$(addsuffix .delta,$(NUFLI_SCREENS))) +NUFLI_BASE_FILE := $(NUFLI_OUT_DIR)/nufli_base.base -# Build all NUFLI images -nufli: $(NUFLI_ASM_FILES) $(NUFLI_HDR_FILES) - @echo "NUFLI images built in $(NUFLI_OUT_DIR)/" +# Build all NUFLI images (delta encoded) +nufli: $(NUFLI_BASE_FILE) $(NUFLI_DELTA_FILES) + @echo "NUFLI delta-encoded images built in $(NUFLI_OUT_DIR)/" + @ls -la $(NUFLI_BASE_FILE) $(NUFLI_DELTA_FILES) | awk '{print $$5, $$9}' # Clean NUFLI artifacts nufli-clean: - @rm -f $(NUFLI_OUT_DIR)/*.nuf $(NUFLI_OUT_DIR)/*.asm $(NUFLI_OUT_DIR)/*.h - @rm -f $(NUFLI_OUT_DIR)/*.bmp + @rm -f $(NUFLI_OUT_DIR)/*.nuf $(NUFLI_OUT_DIR)/*.delta $(NUFLI_OUT_DIR)/*.base + @rm -f $(NUFLI_OUT_DIR)/*.bmp $(NUFLI_OUT_DIR)/*.asm $(NUFLI_OUT_DIR)/*.h @echo "cleaned NUFLI images" -# PNG → BMP (Mufflon needs BMP input) -$(NUFLI_OUT_DIR)/%.bmp: $(NUFLI_SRC_DIR)/%.png | ensure-build-dir +# Explicit rules for each screen (no pattern chain, no intermediate files) +# Apply scanlines: darken every other row for CRT effect + smaller NUFLI output +define NUFLI_RULES +$(NUFLI_OUT_DIR)/$(1).bmp: $(NUFLI_SRC_DIR)/$(1).png | ensure-build-dir @mkdir -p $(NUFLI_OUT_DIR) - @echo "converting $< -> $@" - @python3 -c "from PIL import Image; Image.open('$<').resize((320,200), Image.LANCZOS).save('$@')" + @echo "converting $$< -> $$@ (with scanlines)" + @python3 $(TOOLS_DIR)/apply_scanlines.py $$< $$@ -# BMP → NUFLI (.nuf) -$(NUFLI_OUT_DIR)/%.nuf: $(NUFLI_OUT_DIR)/%.bmp | ensure-mufflon - @echo "converting $< -> $@" - @$(MUFFLON_BIN) $< -o $@ --shutup +$(NUFLI_OUT_DIR)/$(1).nuf: $(NUFLI_OUT_DIR)/$(1).bmp | ensure-mufflon + @echo "converting $$< -> $$@" + @$(MUFFLON_BIN) $$< -o $$@ --shutup +endef -# NUFLI → Assembly (.asm + .h) -$(NUFLI_OUT_DIR)/%.asm $(NUFLI_OUT_DIR)/%.h: $(NUFLI_OUT_DIR)/%.nuf - @echo "converting $< -> $@ + $(basename $@).h" - @python3 $(TOOLS_DIR)/nuf_to_asm.py $< $(NUFLI_OUT_DIR)/$* +$(foreach screen,$(NUFLI_SCREENS),$(eval $(call NUFLI_RULES,$(screen)))) + +# Delta encoding: all .nuf files -> base + per-screen deltas +$(NUFLI_BASE_FILE) $(NUFLI_DELTA_FILES) &: $(NUFLI_NUF_FILES) + @echo "delta encoding $(words $(NUFLI_NUF_FILES)) screens..." + @python3 $(TOOLS_DIR)/nufli_delta.py $(NUFLI_OUT_DIR)/nufli_base $(NUFLI_NUF_FILES) # ============================================================ # $(PRG) — compile main.c → nyuller.prg # ============================================================ -$(PRG): $(SRC_FILES) $(HDR_FILES) $(NUFLI_ASM_FILES) | ensure-build-dir ensure-oscar64 +$(PRG): $(SRC_FILES) $(HDR_FILES) $(NUFLI_BASE_FILE) $(NUFLI_DELTA_FILES) | ensure-build-dir ensure-oscar64 @echo "compiling $(SRC) with $(OSCAR64_BIN) -> $(BUILD_DIR)/" cd "$(SRC_DIR)" && "$(OSCAR64_BIN)" -i="$(OSCAR64_DIR)/include" -o="$(PRG)" $(OPT_FLAGS) "$(SRC)" diff --git a/src/data/nufli/nufli_base.base b/src/data/nufli/nufli_base.base new file mode 100644 index 0000000000000000000000000000000000000000..7fa2d699dbfcc76ada5fdd82bb3ffe208c3f5587 GIT binary patch literal 23040 zcmeHP4RBo5bw0b2Em_#sVj!0Jv7SN5`Z26QX<`V)LP^a}9EZ+iIyAq_>r!^tt{!bt z#ssjQ7rb^HOI9{yz5F=4C}1R}{G`)3c7m~-wqQsnY#Q2<1mXo8o2H3*Jd%|x|7^c= z@4IjRBwGv9na+gs^zJ$L+;h(TJ@?-C_C67P07OSxn)CTQ)gZ{{J$(qe(JXmde$oS4 zworawZT@hh+gkap4qtGx#B%3zI2!l}!u*(tz+A8#p+|@q^aIe2{+N(=je(G99M*o% zrC+yx;?nE(f>ioSzF;9g2zogr{4B5G+>#WOfCx(Mp%y;A!Ci+)@CLr#>U1bhU+!6 zJ|0hX7;!7yrt5kDI)-J16NY7kauFjDkH_1>VM{^)C_n}U$buYmjU7hLdg&P>98TuK z?Rp}NE3r3W00Nc~POv>lMRE^>t!;)8+Zhfgz_${KT+T2JE07C7MA|bVJ5~=r6);kv zTq2POBmywSf?)$?G6E2ka+YE2wD1=RCn6|b9KvxUuBWValpH2eMm9Hc7-ZiUmVl4^V*s|AkRx1HwiQp=BikTnAxF)R#q12f|*l(EfWj#5QjFnu$Ix zgJP*k=*C8ji%IxXu!Qj`(wJSA`Amii@k&mG$W)Bek5f^7kRUo$7cX_aTsj zm-25FTmZuPBg|*;sHwp-DkZ+Fbn>RKn3aQHdnupq6B@MO*a<0v=%vN30q&@Zv&5UL zU0aR6PPp^|P@-JYoI}^rc}%rA?kTy2aABeRzdO2I@=Np&C?`kSZ;MMI=wrVv=u0&> zbZnU^o}{+#>~rEGUD9$2fB{#hm~go49RVF48`pKMLHNIEYMQw@dNdwGny!b1KOBWa zrHmY$Fl-pf*aK}zFyLc}06fSFz{4Crv=R{`9EupRSTdZFLXfkJ-G*_LJs(cR{$Lmp zW6f&I*lSr28CD8LF`*=K>{RMXHtO02muxZQ+oCJ=r!wJGH6AE6q?^}s&$@|xr`5#A>h=Z9?#8$p2 zJ#}(_EwNB(u{fD6mqT$n(@3ZCQh_aIib-aRQgt?7$|;FCW7eYl1P@1!nw<#q8?!PA zp=EXFkE$VjFTMTtjDiU%M5de!ekh{f;n2Gf;hb><`O&2pA9G!R8IG;{Y1Hv8Q|7Fq zkZ7EJy^u3m{6S11Du1|?OFIST((s;x%x;;6aLdxQ2+!W9xg3@PoTd+SE#+qAqM0m|MUfP;SC60=W3~P`jFq=SRQ22Fh?{kU7t_MR; zVdH>|ksq(^m{A51AX|e5SvZGmMvR!Dqgbs*E@9h7+>Ymrt-SXLC!$;+ma|gHiQH3M zn{XJV7slf8u$W1N|A&!32?W}uP$Bo?BaiNQ zYUiNy%k3#UArYMK9fG(V<>_Ps=VRiS)B2(jmXx*T#AeFUx|8}2@BE0#YdzfzK z47R>=T=J!=OVfeFPX5aUF)QFiS?(uSLGFv^k0G2NTUXF2&Yj5XCP<RR|Ka9iZ;M3SLh

d)wyKM;C(u81B9YstWM=X@uq)= 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 XoGMi&FHfEyzy~exK@0qmw!r@a9q~?( literal 0 HcmV?d00001 diff --git a/src/data/nufli/screen_title.delta b/src/data/nufli/screen_title.delta new file mode 100644 index 0000000000000000000000000000000000000000..6abb70393d3f737267239f8ec50f49b8cbd2dbf7 GIT binary patch literal 7081 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 DI~m?u literal 0 HcmV?d00001 diff --git a/src/data/nufli/screen_waiting1.h b/src/data/nufli/screen_waiting1.h deleted file mode 100644 index a74d486..0000000 --- a/src/data/nufli/screen_waiting1.h +++ /dev/null @@ -1,12 +0,0 @@ -/* NUFLI image data - generated by nuf_to_asm.py */ -#ifndef SCREEN_WAITING1_H -#define SCREEN_WAITING1_H - -/* NUFLI data size: 23040 bytes */ -extern const unsigned char nufli_data[23040]; -extern const unsigned int nufli_size; - -/* Display NUFLI image */ -void nufli_display(void); - -#endif diff --git a/src/data/nufli/screen_waiting_2.delta b/src/data/nufli/screen_waiting_2.delta new file mode 100644 index 0000000000000000000000000000000000000000..e5139d20c22de5fdd2f54c22da2ac07844bacc28 GIT binary patch literal 9732 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 literal 0 HcmV?d00001 diff --git a/src/data/nufli/screen_waiting_2.h b/src/data/nufli/screen_waiting_2.h deleted file mode 100644 index 9c221a7..0000000 --- a/src/data/nufli/screen_waiting_2.h +++ /dev/null @@ -1,12 +0,0 @@ -/* NUFLI image data - generated by nuf_to_asm.py */ -#ifndef SCREEN_WAITING_2_H -#define SCREEN_WAITING_2_H - -/* NUFLI data size: 23040 bytes */ -extern const unsigned char nufli_data[23040]; -extern const unsigned int nufli_size; - -/* Display NUFLI image */ -void nufli_display(void); - -#endif diff --git a/src/data/nufli/screen_win_hare.delta b/src/data/nufli/screen_win_hare.delta new file mode 100644 index 0000000000000000000000000000000000000000..29f8d4973d9e125d6b0df45d7b2970cfa8bd8717 GIT binary patch literal 9548 zcmd6NeRxwh-gI)3N`V93A?s?=yPM{p#zq<&N~yp?B)zEg#S2!>61t*4KPNseS*;_1Ct8 z=-8K^0E;0G>RJIT-1&xf2GSf4c6|lv1LgEi=IR4+yKB?y_pXcxFAlv$)2Y$6>)T%cbd|4%rmHVX4^k>0-ZuA? z2=L8p0QUg_7&t#Uuz9BKD=yCzY#BA38yA3rEeTsE*Uj+7)23`efcQr_`WV*+Tql>_ z%89^lIbKUpWLqxQGA({>c5trerO|blr-x`=iD}y?op-nnoL76CIsOZ2>PySZzM1WR z!PC{~DkyciT&w6wS1EBRL)n57m#d+)vDwws>~fVh;Gb?UjZF=uuJ@X-XR~X`(xpFC zmLhvyQ|XjaeYDFpp}wI(#D4XSE^KIA<#OqLQATr%%T-}_F<5ETTS^;U6R_XWBhO_| z4j+1d;GRYo&f#j9v;NSkg2wAyt6YYM=UnkyLkClb-N1Fd{^@yIb^ckP&w->1{BiT* z#n`X(e3Og8b-E_xKW%K(7lWO!#Z`jq(dD@s^o8jyu9g->UMpXtv^y0Am+xvQ_@S$` z6eVH5Cl4Jur1x<(=>tpioL($;H8mA9+;j4`S1|u@jPl2YEi#WUEG*(UMkf{t<@{gJ ze|5X>o8C1!$SliOs&d2hwmjEIDYfLebas>~d9KYx&vP0*I`p6k&O+P(<@@hl1Q}TO zTJJ^kpsE%$)jAIjm%~@JVF>JeQ`+s58&?{C$3L^zSPf zj>aPSPY<`yl%4ZekR1%+gvZQa4{WUH-N=#x7Rs;Qz>`VSi2dOW4B0-8Ov{h>=R)W< z92E1-XK45D-pg}+=}2yMo|7oeHji;>O4sJOO=0_aOc0%vvRDEuK&$oCo7_PTy17YK z=mqG$>n;f-H^kY+t)QLb4t_U3_MeMBxcSPN`DShOdxMwfxj^pR=X3Mi8A^v+@?3ZN zY5IdaSG(!ld30)Xbnvbl%(??9X4~6f9l*@HjVw%LVa~i_Gu%)N^VSs`q3(8AW6Y2J zR^vjJlOKFzVZd>H%heQQtJH2mbEu@`Lh4)nqbpthR|(EV?AmcGDOgGr~4H zM8}{vVcSjlPqok5+i%HpH9Y#&JXeb)Q)?1bNHfq@pSCi#Ht3n53Q%POp&x4D%;ikF zOh2#xjR=@Q%(rb)M5#T`9nP(!FXg#bdf>d81Lq>6=arD4${4rgFZ4Vn`4h=A){CId z2-m=r*-%&qml^X;U-0`^F~TW&iQg zbxozs=;xO-VZRAax?D?_##Z0cl3iVo&aT#-e8r=)Oz=qkv|(^n!krP`a@j~=DXu2tDXt1vQs zFPmznvDl%-iwhc|Nkn<|^{&N@u3R=&n*DXNtGQY4hMQcftX7)WSKw?e$jiepAY!Gl z@z5<#KICdHb(Jy`DoS0hubNz?*EN*pXV&GZ>OP$La`A(Ec0BppL-im&aH4my9vpR8 z^%!%Vt3kJO*Xomgw;Y2bDG$N=qB>yvOzM z$(6St3=QIS1*H{BT@8vZv9U2PuW4yhQ&Yp&+4p-~AO5`W*Y7vsx-d33G*~lH z*Y!4T%^NukW#1hJsN+SeiTX3ZWm*k3-Luzds>L#~h0)LbxV2qdPu~TaX(qv1#-71) zjI0tqhhoRti3S<%0y~O>@5P;3rX4+s$T}%S-$x&7D+_rOL?A^yBdL_7h-jK$k~F{H z;df&ZkU~<>y(^XKB1M{3RTZrAyWMWtt0Bp9C>Zj3!;x@UTONXdgpn#FNs?EVWl6)T z?Dcv>p*^xUDa&Ean+$nny$@0{gcOcqhj0(}4mo5qf*C($0)U(^mv0<c?#IDWX8zz&9^(d@&=a}4cUXS36Nmo2+q*8-(Q-|t9aN=PTywYW8}+wb=H z(Ara`B2&`gPs%}5#@0%=Zn(P1C(+{N)b4Igb0=}zW9L8zFc=K2=)+D@Af%&+HjQUM z#{;#Yp9SoiT;Xweq`a;GP*sR?Fc|b=e;Jh;!a0!ezyyc&bCA~x&WNHAVVn$`Lf=*h zekw_!WEe-eQ8}nH-2m`3>1^H__33rM(MeC5V6j(BE1LrGKsSIOq0wJWM9}nr8lh>5 z54ILK6NromR9%1FT+NAXN;(^BWeuDb$I6NYNBjkKYmc{QC@r9hg=Q@JI6ZF#pl6Od za|JOvr1a6l02LUvu(fO}<#I-gO&qj#(Uuc*&S12kQbJ4uViI!ioQ-I2+f9lJ6Mk8C z`2Bu2T5)JNgvO7MCcVq`J?4)%Zc5XC^}nEZxkr}YenlQe@DOi({ya%iB(&Tc3GG^r z9-=oC0Lbg6GJ??V!H!9^;=Fw)LvG#by}{vOU9Tub*GN)ta)*-Nh{NH!^Fp=ixwep|P{|x(mxp>~|J5-Mt1g#^2yk7j?u$QF{IA4RcMTWWWa5 zx0ur#+3`Pw8`Hco%P0^(b5I4tbjHb~A3C;yG?=$sp8CVw-GBJYj@vhxCO3G5;N^D; zB(SI!p`sRkt#+`sBgwaQQySM>xOW}x7aw@NfDcRLyYT2k1wV_z@1h>KO?kSW+-l60 z#=-kq^o37zv!NaSg)G+a-jOGS9)H=f%4st`|0BEnh zoQcvM1ZNP(Q~g1yY2|7Rw4gr6EsF;3n3v0SUZga=I}Uzm9lrCY)3e-fuOEKfDAtb5Z=Js+|va6#RrdvVk!fy4A(^KkF$cSAng#V>(O^%w=vrB`GkDRdQV*wwFBd zfHXkxj=;ctPlTx`{>$%8E1Rp`-?8R$_n=moZ1NC&Xc|yzJ@UlDlh(l2-!$7y7tdu5 zU!lHO>y4>jee!%es0W~OAp5}sugv;d2|vjNPpdUHZEp4u*9@=zIo&0LpfD9J!60^y%?oMd1xemMAkh@bKsjMI&&|xGBbdzISQ+ z>n)J^{MG|)ogyC88z|}Dj&qO`Siq)y^c&mKZIrfB+t~7{p)X``>pYk-`aohQYbvs& zQV-6olre||gH?${AW&$sq?FGv*AA1cvlT(6@Q^%df4#;5K3= zp+q7PlzYNKd9xM{I?$9QNl38!eJgxEha>3mh5I6*ez_l`Nq=~IvN|MJ262eL#}nGq z=k2TtqR;Hdc;gNRF+^2kUgGvA1VW1RG_t731cHEm+aJVMeN%yn z2Aoz46R)kabcwUhS>nOnB$nm;8I#Gvkx6fgiTUQ25dLm}FUaGWvhR27Pa}ELUIL=R;&L58LleID${fPkCm0W{2GlNn!;{=9RF%`-msB zqc7s^8}9Gl)7KXcMj{Esy~pS2Ona*Q{vd*iiEv1JDkx*15p+oW{&Y`|JTg*5h@^*I zDH8MtaaoD#w+W*of@X-Z(@fQ4>6dv6f$^#VT85hERghg@(UFX`RWX}tNty1y^1W~4 zrQhu}aje4U&@fNrEgn9jafT@qEytoZx|a&laxIk zRF&I}c%{tz3i*;gcfyTo_sGj$2zea|w>vV7rXvTIdqz@PI+c?52LcXX+P4$CR0XSL zuaL56vI92}L{$1Cqy15D|4;XaD`iJ05cKGK_N4vp?fu^V?Xnz{y^&yb(C^1PMt4uq zu3bLg?Ael}8=QvYG32A5D)y2ilisE0n0?%6r@wVxP5}IhKmhwO?)O>_=8Am=vc3lM zHD&uqee!y_zY*N^hfKn28*TY<=OW z;a~z~gxHkUE93SP2}fwf9yuB6>swwK3J3u-1An3#<>8&D|EW&72k%I@QAfhR+2`r; z`EdU~#r^9S9Nc}v;V`u3_L)0Vw-4+$&a;9hGGJGw25@E>6}Dyx*Z#;a<{A{0k-F_B z<`DC)d0wuHqlG5SqV(-OLY3GJZX60;0r*C;A(isZ5-670GUzSHB(XGDdTe9ra9mh*W%Ywey zVL9S(pku&OWl0t78ABnsXT@tF&D)Rq4ps}VY!2_NPPo1O`?v3y?R%v!6bJ?Vo2xuN zpKtRkJ*Waqo|EWK{EncodUT-fx$1h=ZrMOgV9BxK0#IJ#=+?||TqsaaoZe5!h3U2w zO*8T1igqPaGMY7TB4rp1=1h%3wO?DvjD?eBV|n(=$4#r{SjyQ37UdfR5^tgM%nGnc zee+2nDJK#;6M|qKB)ohvdH|g&W%sfNeU+GW>O1%1&V63bTYDor`l7SPjih!HR@NLN zJ{f(ZBdPDacVy(1KQ6CEQ9-vOWj!# zi5`#J=kZthgP2RA{|w=U4;|>? zTx}86;?y#gy=(&&88ZgIvnn;OTem`q5m1PXuQ_vP*u3Tw(w;&x$b%*c1aE6@JnacT zs+!I4sb%@?x@V&TeR|yRyYas6btj{}zU3L_V(h^Fc@g^a5(G`blXV;Lw)_`-9pgU z?@jIrZx8w8{_t`cy*vu;NvHK|LokxuBg@OZ4jdiC69Ev9D}?&3U@>*5#cTMgeH`U7 zA1@%9+3q9;D=AbcbJ_Oz&8=vnns!cBnQ>98_JP6V9CcnbN%;#s=LC8#PN{$g`P=l* zE#IWI`F15xpO!YZOD*96I0dQEL9e;=kT<%T+7mw1(SnT`-AgLiHu@>(aMF zjqu(uHBkC8rXN+)DBX98P+I-!bOJ>TdgGnb3}nwkLeX>9o-c7#9mQum z^%;IgFWVHW#}H)KYtf?mENjIewSUfD!_4Xf;-lUdC=D2IrW3Q)#jP2jim;d-*!A7q z(AxvfL7tzHJweXIZM4PmQ;CXc`($p-jC5{rRfOm2Xh#@x{%f5E3Vs?M9Yw<)iqg@P zOtYg9qw8LB#xRE;J%AC>pPZFSML#~aHl13R!T|Yk_VJ&mcYi!ks1-5yE2B@~*B(Q$ zTs8_rS;*zGnSDYE(DcUlGK3Wj<#^nnjQwTOyrG!M!GQfz%BKeiWrUHmeUe>M#qmQs z$wvn#t)3#o^50C1XXJ?-bInxK$=KDezI}ge^z(~S|N41U8T<(D{oClfM<)OH`Op_H z{G_F$!Sgx@-Ha%(LV$=%rWF{El51ym)U-c;=U>ymW=Wi=z`<8&WYT;PSy)7*>G+0) zVJ1O2t011Jj9e;5YpI%xtCnSdc>G@dTZz_B#>d<2kYoSD^~@7ofRJCsfw?r0B3E@X zFgTUvgXDYG`mb5T=PN}+OP%*u1%{?govc85F;9>VkX z3Orn~7%+?QwP(kY34CnE{=Xfl_x$l1)BX#}*+PaKQy@yB_s>$N#8rWIwLLS?v9Q}Z zBjUe0lD2MG@XiG$J7)Hkl4%vofR`CLHO1#TSq zU(NIa{O-mfI9F__ZGDa_l^(UtH-?r}5}}?kng1=kaOP8_B}%$S7()jsdhd%{B{WI) za`i&%-3;CirS7z@Z!x*wJtBKiJUNWrWM*q=^2`1**6JUVz}}qnTzwQ zqH*%&RcH5~g-Um=H77Eo1kKlpFMn7VHgx6))4}EnH%<3GYkI`WiDjP0Sbz9q6K^jQ z&y4?tlPNhN3ORO!mpYEc8R1wXlhzHY0>6H{%xw5;-2>ML`k0oONP*3r8#Pkx^lz#; z`xigTg54djJz=s}Dgjg4q~r>^tyXfC$h|L&y#H{HdGOk3;1sTGP2uh!xSP zzwhj*XIQ3WgH%Q$6Rop17Q)$=6u9Ey&z$j+dp69Qm1ZWlw_dSyQE^S0@DeR&B#lD0 z(C{pIseAgY-@2pK!-}86drPZ2wX%QN}N7kJ%TTW%w;d zfuDMG{N(S^R2lo~BTE@D8MlfbIc6^S(~$#zJUi)*k$GQQN&I@lWU$`G95ZB$(3e>U z)4TrVfx92rV`U8dg|%L>>7Bw0!hEN7PUJ~|hZoI6+rGZOEg*_*ZH59~6~*;L!~1dx z_cZ$&C$e~ftNM~@#kWb1PI^7XKP?daAAwxal(LCeJYldJp54YmecG@{{1cb_v9Qy9 z&%=K<0GbxD+B>qJ{^=8u<$<&U{&!5(1Ss(1rAIL3|BpqC39?vFP?#nSgk=d4&4nhD z(lUR|lEt5vv!rDX7}NMxlCGyJgDwaZI)M^qXo_ZsGZno`7wk3L>7}Xc*V?*?VJ2?0 z+q)iRViH|uyCcKnSDdt+rZ9uR_@)Fz$ee8Jw3asG%sV)xvpzgf%O zy#0sd|GjEZ1#oFRK_Dt3EZ zt9vF0IfgqhCq1Z5FMh_j$Qab>opDO%HD9Ci=0)$jSQ@{+;XZ#!G;*<2?dhXK8C;C^ z+oRMTEDyZ%MA6^(a7M)*qX5E4I~{5n2+a6yFi09tX#k((iOB=Ft z^vC~NdU_LRYXkD-MVp|sh}`Ng=JA~_N@-1WUu5(8=ta6AOYS=oudD5&J8VAMr<)cX zpfRai^I%{yxc&JfOK6A);3x$3-b9-)LF8K)^-H)eY`_;JhtWy<1oJR@c{= zs!bpDnyT=}RF#yayk^QUl`B+C<%xM!mDQ7at4z0^+V=aHz4!U|veX$kY%*15y^xu) zd)Co;8H!Mp9+T;=sgK@W9l1-Zt7%{F^X0w0iEbp11gnJa=GnJaXRVehBX%09Hbsu& z%Hx#f5|Cx8NUXciRJE|uRDnCHv2WeF+O)c%CNaKs>sI_-orPdZQs`wQl_@V*B7+%~ zI8>R~9Bv+Q6LOW5GzzCFpi)7aPNh>ZNeM_}(&jNRmq}w3x&6OD6gh54yuhEP71I-# ze2acF(if%lT1}!a`doxc32ggT8YxU**ZcaY(Ev9fTOA!PE>eR5&?qyrVHiI>X_=6H z28O#E(Ma1De56koKdnn(=u-Cn1a`iU z`s-C>64}XkRhlN0iXO&l9V+5g5##R~Ritq|G2V|;)dt`aZ~XH^SzkzWM}K=rn47@1 zhiQCd{hs5Lj$l#k(3FzI=s6Fi%Mutyn1~T%RV`e4Lcc-}BD(SXO|>9DfuC8mP*lXq zW2WHLb`@EWc;lZR9^l$NiSF9n2k$?Vz`FbFMI)kug9+*_Id!%tP=k)vp z7VW2ZN46UDQi@R;q5T^gsZ3z!8Yzt=Fhifc&!4~u6JU}^8I&>5h>}(vX9D;+d%TAU z1h~NRlQ>DOh7U({mw4mv49v)@bfX+VWRo8c#cn!!c$w4l{O zAwEH+g`^*rpikiE!3PtQ|NGJ$Yf>0soh3udp;GU2G8X(fBvTjWTGUbs~Ux@)tf$Aia>0!jPs#a8+ zR%YS6tgM<|(|b5nUTwlKSe|9VKx#5AG*wiVS797x%J6-hS83`U`+fVxk9xDpOj|H4 zS7nuBq*UT_rRi?PCsfQVH{mqE!@^Wn$z+YwnoNtzkN4i~{SS=DnGEyZ9qYJNi!#bG znQ{zLBXPfyVVJ7SGEWb*RV*}QS6AvynMy!5CFtRXaRsFMd}Wd;7NdRGuEq@u zf>5R~inFSebxlG=Qj)2%vaIaxaGhy&UnCa0Yg%nGsWYE{r_b|M)#|Dw= z40>qQ(1iY9EMA3_nXOEMo+8uos(RCk@=tnuE6d9(vp%Z-*!1wK`f#|fDrrY=r3vmN z=$UY7Q`Oc_Ox38lEVZe=clGKE-KK|sr^v|rsA<(|tpSQ5n0-e-4zs;%6{iIe$P^H3 zF?!Xe0;eHp!wew|CTqoLH-MI_1y-@&AixP;K;(ZEhKAK-7Ek{WXAWcL_e~+Ws;4yE zZl%Wplj+X0%+(x!4IQSp&JJc{2%$#5H_}?UcKZimyQmKsexSRH?ZimXPTxQr z4}t;NZ7y(`pDyT@y|OF;o3d};zQ|w-FFQMO^JNLaBbi%rts6jeiq0+(K?bKNOKvNE z+rqNdXOY4Khh1iuWOcjUk}QSY1+H#c>g<%?vf?xZS+dFqIBs?KwAk9Eu+40-Snvfi z_Jl>NTWaa>dkUJ`)=L&ive|I5WOX6Aa2N?Dz~9>Kd0LhSd=?x+LXw1&a35A>RfJqz zTiKnM;83T41_6JtL%|TBRmFZXG(DN#3c!MoEIeI|v0O(ZP4vYvL>L(2Prz_6d1!kn zODS_VmP8jaMjDD5=M2V9Xiqv6cmMQj;WLJSHh3Ek`AafWN+#a>mR-Y)+Y`Z)4D7F| z=n$R-a=dhBc>Vx=VmZB4(+*SOXrQ3uzL->8sy%4;qUeJ>JC3sPY^odM$0oiDFt`h` zR{T<()#@8$S+T3jnc%5C?nN^k9ulI z`sNA?M*G6vDA1m@Lzkj4qP+n15z0YqOzqOsjRVgqK$L1Ed49R@5SMs7(tP-jW zr#fM9&}Oq*EMA;~y4VmdM8R3nxXo6%$>%<}!G_>LS94pRMh6=Kl(hBmsZLv1YO}rF zY3sD1d|e*Yi~?rcpl5@})+D1VJ$=~PY4KRh1&I+wBZsXPn^{7&+H7953HQi5E-8$F zR29XbRfO^9zXE!S<~v8yO~M9#5GIZvr_0j87_10%I(?^IH5IG(OWSEOzk|jLe(Owi zp`c_ss0eP45I8#!oF0JKX|vh_GQ^%f@WV*|`-_K5D61QOmN{WhOLfKUDB0u2SB9IB zBoD{w@Ja4@LF!X&JJrVwUi0n(xV-!geM#wFGV?g~57E~G8TuJ2L7j@qQem=5Pwtcn zOOQ(IE$USkRx-9VasQOzpUPeoZA)@;D{(rz&}TrDU4>7s@8CtJtb|Qe2L=|a)#5(9 z5%tz0I^Aa3YHl*S!zh;Ib{{9dZSmS{lDt8dJ?Kkbw-SY98GmFxWsl9$gihf{ukzw< zB#X^rTmPg@YHG9D+BQfYv)N@;L>zWYHk{XqYByV=N(>wEov;${{M{ScFgnVu((k+@ z-DoRj+?E?PY<^%@2GQ(V zNvW8wJxViPoJ>U$3~ou)E@JvWi4KQH(<1^AohppC&aHAeEu_+7;>#*XJHO}>{e5@%EUa|=4Pv$TzL<)5;n+84As zV+f{GBjtFSfu+X{$VxX5k>nXpX&9^eA%?6|uVG)_JwXDp_@3ygi9B&kgB)hArrlWz zE2jIb7_CGwy7Gnt?V{$fm&aFB^2Lw+ZK9qW=RD)v*)9{WWy$CaE2QIvjq^%FItVL% z4pS$w7Lqz{CC>r638wsO%+Od>`)h+1^gF+|a`_LI>Cq;igOwWA2uo7g$LiGWv3ZS5 zL>!2gwmkUeWx8ZufV@h0q7Q1eH^8rE{~Ig(`(rHpMZW-`iER8AM%?+bK94N^bLNtD zh8f(kYQZoF=U7;bgb9Byyu{(rqm%~(zays*-uXN-e%aN-JGkZ-m*ubdH2>=Na!^<1 zNrw15hT&ToP__Hm!03LbVed8lo7|Jkv2byZwnBR;Kf9HxqqTE2uc=jxBZ zR>Ei5fiJuta-$j|Ia3$SR<|A3S3J{PedZdZiJmE|+F=*Z!uiQ-$~g9^tI4nM_muDT z1<$rvU*M*l=OQ;lHFfR3hsXhlU&_2$w8pUXsJL-We5kXrtFea+asD$ikFD|yJdk>C z?w#%hnyXxTo^ihA?c)LQ&Yd*t&TY;!ix)3m#r$LrURk)8er*Xn#ngiDM7l$76dqyl z1RY2syR*N1oD^PH&;ObD2zyF5_F?slBrn8hm^tKCC{fP{_K*kfU1t#5Xzu zxCAQ(dw)Or1xA0T{)ea%b*=Ef0dShsVgcJ=-K(gE0PupfZ&Yj2(fZSW+? zYq}@rt7ZHJ!s_8afh3>GFmhXeP~8Q^N@?D`at17JJTwsGA3gnc5gTB7NXsSyNA&fx z{$)-0Z+|YxN!Z$6E1L^_hH2n$SqKa6K>y)!@4jr6>3K?2RQ9?h|R^6{~WhXp=0KcRW2ku0k9L@d*%}P7K#h#8Xm~d>2o^mY2E@ zTg|pSON%2nllK-&DJ^C zLVVb1F=MRubado7p6)gmD74x3NFtSHlbv2mi!U@2MGg?Yzjn5LBl5ovZ60qqxK!0p zfQ=J|2UyZTLqbFos4pYw{e6Dv9OgLhQhMVAYahb|iv@9PQCe`2l<_nAFkxhbD9Vg2 zee15nf``8ZvY8J#EO{Lrp-`dAEf<Ty#(vqugiiWaCo993Tu;D+ORH+ zg6vH3rKFIM!)lhDlFOXu^LadZ7Aq1COVBR^ya?qaws|S1{bd2k?sPJ|q0 z8*W}!JVRk!bX#*3uLy-)?gC6e+PV)HbbRLtmW zjObD1v*CL#Oll;r!jHo(6?zHJM?Ic=EXj~HD>950fCFYsO3Ww^kHegw(6-Zu>JHnm z#`6`lSXv~A0jn5$<7r`+Zp`M?{o^q|09BBY(>B?!kG`;NOm^v*$#cgK{h9sjY0xeRFcASDz!>|LR0ny4M9NcO&Xruba&Cgtf{@!?XwFyo%r$$xd2P0~cRb!) zRH8!(5|~1nLk=I-r(UTW^D^)LcM6el1Xn(0dR{beOo>~YkXI~we2PdMUbi#Z>2$r6 zpRe#5mOVDb#}$zB2Ap}hjwYAGmuJaC4NG#0?$tW{0&FI`b@)I&6Yay2z_0#5)_U}h-3Vaq@O*@f1ksL$m_R(Y4t=V&rx#V<<*J{z)&BwSucKC;||x;?09 zHY|3f92^|PtX1;)q83Z*fpMP_255C?4i>{ILYbJ(=nrEWbvbzttxCHvL?^u&pl4~| z)^^n8JEz7A;HUKT(919}prwjBMMr@dU1*&XYtaOM*ucG;M}oGphrEOl${(OY=R3@f zgigI)lmJdL+stjqVt3E`hYLf6`5_ta8$8KQ)MJ}Rxh9YuO~@i14}=%ZO3ZTPI$R?K zRDloO71_^24$V==e)LuhWA4sQjFFrVmt97RNxV>+O<9#Jvp%Az2D9QTQ!qm&)UqHI zukiK+ytkvn-N;*TYaHttJ&$t+YhwEtW2p|aPGk|C(t_S1il{r(pHg+B;_%Z^5GwSz z-I&zB?^T!yb=dG^31JoPa$&pBWi}VuEc-nlpvM*RAr~*Y@$};PiMEN6+;>@%R76Rq@scU&X z!-E>FesUQHMZj{v2&t+J6`MJl&%f~8WY6L&k6e3vFYleV^ac9)=pve-RZ+uh%OXWr zXVJ;e+@hLfcS0qSy606nwQwB){x_G+*-!mVOy2p}x(xf&u?_J%`n|@pMbH?E1Ts%* zzGd0Q0|LEvTP@E0<>d@mst@s+@%1VLAxe?|A$}Leml8HC(2PQk>jA+zK{Ex~?lLLE zFt2ncXOA5VUW8AR`~XWS#14Nx{E3SmITMHB4X+GIFXi;b#BX8&!IE?1@?6zAdS?BO zzKQ(jv756S?i-3fGXEyTermtF@r`(>Kjwc!&gqXoeB2I0XoQ?YXRk1fJ_ zuDN4Bld9+NlJq!H6YYF`{hMXbu5Ne#ft`FXUo)lYo+RtL`n_|L8oyN=Cf}asyw^{D z4dz|Epjip`>yPUJ(^A6p{xHJ|w{49Nw+Ft$`w7-Rw18eYN$pXw` z{vj6b;rTgeec=H%nftXcE83aDUVdAB4jiip&SZbCevs+f!V!|rYBf!>x_r*{N0Fim z@B)*GY9O0wd?2nrH+zY|5p@sy8QGf*{EO`ICG&--wx)GW%Cm2LIn8kX>wLZS+ZsD@ zz%~(IKcHthaUGjHF-Lp!)Y_tw(fqF97}9Ly(@$Tq+et>t4|_D{nKv>yLxyI`0dj(r zESxPqW5@`S0ha%Y&@Wa7DAP`X>XoVoE&m_jFD?Chqf^+k+p7LB`q>h+U@ckmA+LUo zCw9!+Q~(cvI=~R#z-T7i$QCL#o=iR-o!0YmVC;i4&;F>Hv=#L{oqD2GKlXY3?>T)L z%T=%soT_FYWir;Tobm0|TFsi6(0{RBi`?K2m^i~2_Zi1w3=w$l`c&vy(LXTZx+a@z z56poq+^BAl4~kcJ_3e38c(kbgl$bkiHFGNMiB~qxo}$jAl8l{>!6qT5p`Q z{|op)pF?0m_m^z0c%>~M3iHQ3Rr2h*zzMM#We8AoXI#ME0DYhjY8jR8Tb7L4N%-V} z@uSF`-u4r~lGA@`3}`28ai(dxTxs-@DT}_Ga(vrYG>;x6L>_LYvq9kNFEK&^=!IDz zZh)y;O@=<_c}>N1hx#6F-(<36jryL?z{zOGS4>YcfN>>5gP^+fZ4`u|J9>JB;fWxy z3^Q)pzm5u!#zuyz6jue`QB;66d*Vuh(r%?>KtN3?QRPJ4s0Yi+4rt%fOy^&Rb(*Qb zE*#Bi0z}|;CGWfnd{9f&Yq(t1RDlGuzyGU0AJYFsy}mVnUE`K3a!PYx_Ha|mfr{Z; z)e#WaGn=55=-2B{ibVt-V4VwLnO=Rg_RK?W5Af}ArhLHJxxKEgEr;r5ARd-_s03tAyC z)D=^SDt>Z%BmyZI9fM@0da3{n`{L8TF0QNNhPie^*nk+UO?~u>fpIhTJ$0<~Jkup* z=-IPz_9u3sAT(3W%+<1jt3lncIdDB$VAOlnWFV747Q8u4$V`zRjh z#^BI$*sP((Vdx12;2wZ@5&Q)c_))5ulBvcUGMFS`59T=F7gZdaevPgT+&6a+^x{M( zaB7yn!Np(KPGV9o5>nL&%x?AY;^ARK|9yRmFZv6=P>g-lc7x830Ravb%2QMNYmCa! pj1)OLJ%LSU`I6<{H!~Yb&yE6bVP=C4T90JgOTjx!_LtV?{x4gR+SLF6 literal 0 HcmV?d00001 diff --git a/src/data/nufli/screen_win_scoot.h b/src/data/nufli/screen_win_scoot.h deleted file mode 100644 index 0e8daba..0000000 --- a/src/data/nufli/screen_win_scoot.h +++ /dev/null @@ -1,12 +0,0 @@ -/* NUFLI image data - generated by nuf_to_asm.py */ -#ifndef SCREEN_WIN_SCOOT_H -#define SCREEN_WIN_SCOOT_H - -/* NUFLI data size: 23040 bytes */ -extern const unsigned char nufli_data[23040]; -extern const unsigned int nufli_size; - -/* Display NUFLI image */ -void nufli_display(void); - -#endif diff --git a/src/game.c b/src/game.c index 431ff8f..8f6855b 100644 --- a/src/game.c +++ b/src/game.c @@ -28,6 +28,7 @@ #include "score.h" #include "banner.h" #include "draw.h" +#include "nufli.h" #include #include @@ -161,26 +162,20 @@ static byte last_winner; static void game_enter_title(void) { - show_screen(SCREEN_TITLE); + // Use NUFLI for title screen (higher quality) + nufli_show(NUFLI_SCREEN_TITLE); + nufli_exit(); + + show_screen(SCREEN_TITLE); // Restore multicolor mode score_p1 = 0; score_p2 = 0; score_render(); - // "PRESS FIRE" prompt: drawn in row 1 (below the score bar) of - // the title screen. Re-rendered on every TITLE entry so it - // stays visible after any state that may have overwritten the - // row (only GAMEOVER writes row 1, but show_screen() re-loads - // the full title bitmap which overwrites it with the title - // art — so we always re-render the prompt here). banner_render("PRESS FIRE", 1); enter_frame = frame_count; title_input = TITLE_IDLE; title_first_frame = 0; - // Border starts white (the "PRESS FIRE" prompt is visible). - // The per-frame flash in game_step_title() toggles it at 1 Hz - // (50 frames on, 50 frames off). vic.color_border = 1; audio_state_enter(STATE_TITLE); - // 5-frame transition stinger on voice 0 (free in TITLE). audio_play_stinger(stinger_voice_for_state(STATE_TITLE), STINGER_DURATION); } @@ -240,7 +235,11 @@ static void game_enter_draw(void) static void game_enter_win_p1(void) { - show_screen(SCREEN_WIN_HARE); + // Use NUFLI for win screen (higher quality) + nufli_show(NUFLI_SCREEN_WIN_HARE); + nufli_exit(); + + show_screen(SCREEN_WIN_HARE); // Restore multicolor mode score_p1++; score_render(); banner_clear(1); @@ -248,13 +247,16 @@ static void game_enter_win_p1(void) vic.color_border = 0; last_winner = 1; audio_state_enter(STATE_WIN_P1); - // 5-frame transition stinger on voice 2 (free in WIN). audio_play_stinger(stinger_voice_for_state(STATE_WIN_P1), STINGER_DURATION); } static void game_enter_win_p2(void) { - show_screen(SCREEN_WIN_SCOOT); + // Use NUFLI for win screen (higher quality) + nufli_show(NUFLI_SCREEN_WIN_SCOOT); + nufli_exit(); + + show_screen(SCREEN_WIN_SCOOT); // Restore multicolor mode score_p2++; score_render(); banner_clear(1); @@ -262,22 +264,18 @@ static void game_enter_win_p2(void) vic.color_border = 0; last_winner = 2; audio_state_enter(STATE_WIN_P2); - // 5-frame transition stinger on voice 2 (free in WIN). audio_play_stinger(stinger_voice_for_state(STATE_WIN_P2), STINGER_DURATION); } static void game_enter_gameover(void) { + // Use NUFLI for gameover screen (higher quality) + nufli_show(NUFLI_SCREEN_GAMEOVER); + nufli_exit(); + // Show the title screen with the final scores still displayed - // (5 : x or x : 5) and the winner banner ("HARE WINS!" or - // "SCOOT WINS!") in row 1 below the score bar. Scores are not - // reset here — they reset on the next TITLE entry. - show_screen(SCREEN_TITLE); + show_screen(SCREEN_TITLE); // Restore multicolor mode score_render(); - // "HARE WINS!" or "SCOOT WINS!" — driven by last_winner set in - // game_enter_win_p1/p2. show_screen(SCREEN_TITLE) just - // reloaded the title bitmap, so row 1 currently has the title - // art; banner_render() will clear and overwrite it. if (last_winner == 1) banner_render("HARE WINS!", 1); else @@ -285,7 +283,6 @@ static void game_enter_gameover(void) enter_frame = frame_count; vic.color_border = 0; audio_state_enter(STATE_GAMEOVER); - // 5-frame transition stinger on voice 2 (free in GAMEOVER). audio_play_stinger(stinger_voice_for_state(STATE_GAMEOVER), STINGER_DURATION); } diff --git a/src/main.c b/src/main.c index 429fbb2..515624d 100644 --- a/src/main.c +++ b/src/main.c @@ -13,13 +13,6 @@ // and calls game_step() once per frame. // 6. while (1) {} — idle. All per-frame work happens in // the IRQ handler. -// -// In Phase 4 step 6 was `while (1) game_step();` — a busy-wait that -// called game_step as fast as the CPU could, so the "frame counter" -// was CPU-bound. In Phase 5 the busy-wait is gone: game_step is -// called from the raster IRQ at exactly 50 Hz, so state durations -// are wall-clock-bound (60 frames = 1.2 sec, etc.) regardless of -// what the CPU is doing between IRQs. #include "memmap.h" #include "audio.h" @@ -27,18 +20,6 @@ #include "score.h" #include "tick.h" -// We don't malloc, so the heap is unused. Setting it to 0 frees the -// space for the screen data in the main region. -// -// The stack is set to 0x400 (1 KB) — this is the oscar64 default, -// pinned explicitly so the layout is predictable across `-O1` / -// `-O3` builds. With `-O3` the code section is larger, so the -// data section's spillover into the default stack/heap gap region -// leaves only ~0x400 for both. Anything larger (0x600+) makes -// `-O3` fail to link with "Cannot place stack section". 1 KB is -// plenty because the oscar64 software stack lives in zero-page -// (0xF7-0xFF, 9 bytes per the -O3 default); the spillover area -// only needs to hold the few locals/params that don't fit in ZP. #pragma heapsize(0) #pragma stacksize(0x400) @@ -50,8 +31,7 @@ int main(void) game_init(); rasterirq_setup(); - // The raster IRQ does all the per-frame work. The main loop - // is a deliberate spin: nothing to do between IRQs. + // The raster IRQ does all the per-frame work. for (;;) ; diff --git a/src/nufli.c b/src/nufli.c new file mode 100644 index 0000000..c087ec5 --- /dev/null +++ b/src/nufli.c @@ -0,0 +1,125 @@ +// nufli.c — NUFLI image display for static screens. +// +// Displays NUFLI format images using delta encoding: +// - A shared base (23040 bytes) is stored once +// - Per-screen deltas (bitmask + values) reconstruct each screen +// - Delta is applied to $2000, then NUFLI displayer is called + +#include "nufli.h" +#include +#include +#include + +// NUFLI data size +#define NUFLI_DATA_SIZE 23040 +#define BITMASK_SIZE 2880 // 23040 / 8 + +// Place NUFLI data in custom sections: +// - Base (23KB) at $1000-$7FFF +// - Title delta (7KB) at $A000-$BFFF (BASIC ROM area) +// Other deltas loaded from disk at runtime (TODO) +#pragma section( nufli_data, 0) +#pragma region( nufli_data, 0x1000, 0x8000, , , {nufli_data} ) +#pragma data(nufli_data) + +// Shared base data (consensus across all screens) +const unsigned char nufli_base[] = { + #embed NUFLI_DATA_SIZE 0 "data/nufli/nufli_base.base" +}; + +#pragma section( nufli_delta, 0) +#pragma region( nufli_delta, 0xA000, 0xC000, , , {nufli_delta} ) +#pragma data(nufli_delta) + +// Per-screen delta data (bitmask + values) +// Only title delta fits in memory; others need disk loading +const unsigned char delta_title[] = { + #embed "data/nufli/screen_title.delta" +}; + +#pragma data(data) + +// Screen delta pointers +// Only title delta is embedded; others use title as fallback for now +static const unsigned char *nufli_deltas[] = { + delta_title, + delta_title, // win_hare - TODO: load from disk + delta_title, // win_scoot - TODO: load from disk + delta_title, // gameover - reuses title +}; + +// VIC-II registers for restoration after NUFLI +static unsigned char saved_d011; +static unsigned char saved_d016; +static unsigned char saved_d018; +static unsigned char saved_dd00; + +// Apply delta to base at $2000 +// Delta format: 2880 bytes bitmask + N bytes values +// Each set bit in bitmask indicates a byte that differs from base +static void apply_delta(const unsigned char *delta) +{ + const unsigned char *bitmask = delta; + const unsigned char *values = delta + BITMASK_SIZE; + unsigned char *target = (unsigned char *)0x2000; + unsigned int value_idx = 0; + + for (unsigned int byte_idx = 0; byte_idx < NUFLI_DATA_SIZE; byte_idx++) { + unsigned char mask_byte = bitmask[byte_idx >> 3]; + unsigned char bit = 1 << (byte_idx & 7); + + if (mask_byte & bit) { + target[byte_idx] = values[value_idx++]; + } + } +} + +void nufli_show(int screen_id) +{ + // Save current VIC-II state + saved_d011 = vic.ctrl1; + saved_d016 = vic.ctrl2; + saved_d018 = vic.memptr; + saved_dd00 = cia2.pra; + + // Copy base to $2000 + memcpy((unsigned char *)0x2000, nufli_base, NUFLI_DATA_SIZE); + + // Apply delta to reconstruct this screen + if (screen_id >= 0 && screen_id < 4) { + apply_delta(nufli_deltas[screen_id]); + } + + // Switch to bank 3 and call NUFLI displayer + __asm { + sei + lda $dd00 + and #$fc + sta $dd00 + lda #$00 + sta $d015 + jsr $3000 + } +} + +void nufli_exit(void) +{ + // Restore VIC-II state + __asm { + sei + lda #$00 + sta $d015 + sta $d01d + sta $d017 + lda $dd00 + ora #$03 + sta $dd00 + cli + } + + // Restore saved VIC-II registers + vic.ctrl1 = saved_d011; + vic.ctrl2 = saved_d016; + vic.memptr = saved_d018; + cia2.pra = saved_dd00; +} diff --git a/src/nufli.h b/src/nufli.h new file mode 100644 index 0000000..686864d --- /dev/null +++ b/src/nufli.h @@ -0,0 +1,20 @@ +// nufli.h — NUFLI image display with delta encoding. +#ifndef NYULLER_NUFLI_H +#define NYULLER_NUFLI_H + +// Screen IDs +#define NUFLI_SCREEN_TITLE 0 +#define NUFLI_SCREEN_WIN_HARE 1 +#define NUFLI_SCREEN_WIN_SCOOT 2 +#define NUFLI_SCREEN_GAMEOVER 3 + +// Display a NUFLI screen by applying its delta to the shared base. +// screen_id: 0=title, 1=win_hare, 2=win_scoot, 3=gameover +void nufli_show(int screen_id); + +// Exit NUFLI mode and restore normal video. +void nufli_exit(void); + +#pragma compile("nufli.c") + +#endif diff --git a/src/test_nufli.c b/src/test_nufli.c new file mode 100644 index 0000000..92fe0db --- /dev/null +++ b/src/test_nufli.c @@ -0,0 +1,17 @@ +#include "memmap.h" +#include "nufli.h" +#include + +int main(void) +{ + memmap_setup(); + + // Just try to access the NUFLI data + const unsigned char *data = nufli_get_screen(0); + + // Infinite loop + for (;;) + ; + + return 0; +} diff --git a/src/test_nufli2.c b/src/test_nufli2.c new file mode 100644 index 0000000..1533e4e --- /dev/null +++ b/src/test_nufli2.c @@ -0,0 +1,18 @@ +#include "memmap.h" +#include "nufli.h" +#include + +int main(void) +{ + memmap_setup(); + + // Try to display NUFLI image + const unsigned char *data = nufli_get_screen(0); + nufli_show(data); + + // Infinite loop + for (;;) + ; + + return 0; +} diff --git a/src/test_simple.c b/src/test_simple.c new file mode 100644 index 0000000..ff940e4 --- /dev/null +++ b/src/test_simple.c @@ -0,0 +1,16 @@ +#include "memmap.h" +#include + +int main(void) +{ + memmap_setup(); + + // Set border color to red + vic.color_border = 2; + + // Infinite loop + for (;;) + ; + + return 0; +} diff --git a/tasks.md b/tasks.md index 4b4cdfa..0893ded 100644 --- a/tasks.md +++ b/tasks.md @@ -377,6 +377,64 @@ with ~10+ colors per block vs the current 160×200 with 4 colors per cell. - NUFLI uses all 8 sprites (no sprites for game objects) - Static screens don't need game logic, so NUFLI is perfect +### Art Direction: Shared Backgrounds + +**Problem:** Current source images have completely different backgrounds. +Only 2-4% pixel-identical across screens. NUFLI output is only 40% shared. + +**Solution:** All screens should share the same background (or a small set). +Only foreground elements (characters, text, UI) should vary between screens. + +**Recommendation for artist:** +1. Pick one background (e.g., the sunset/landscape from the title screen) +2. Use it as the base for ALL static screens +3. Only vary foreground elements (characters, text overlays) +4. With shared backgrounds, NUFLI overlap should reach 80-90% + +**Expected impact:** With shared backgrounds, storing one base + per-screen +delta becomes practical (~10KB base + ~5KB per screen delta vs 23KB each). + +### Scanline Rendering + +**Problem:** Each NUFLI screen is 23KB. Even with shared backgrounds, +fitting multiple screens in 64KB RAM is tight. + +**Solution:** Render with scanlines (every other line dark/black). +This creates a retro CRT aesthetic while reducing the effective pixel data. + +**Approach:** +1. Pre-process source images: darken every other scanline before conversion +2. The scanlined image has ~50% dark pixels → NUFLI bitmap has more uniform blocks +3. Mufflon's conversion produces more shared data between screens +4. Visual quality: retro CRT look, acceptable for static screens + +**Implementation:** +- `tools/apply_scanlines.py` pre-processes PNGs before Mufflon conversion +- `make nufli` pipeline: PNG → scanlined BMP → Mufflon → .nuf → delta encoding + +### Delta Encoding + +**Problem:** Even with scanlines, storing all NUFLI screens in 64KB RAM is tight. + +**Solution:** Delta encoding with bitmask format: +- Store a shared base (consensus across all screens) once +- Store per-screen deltas as bitmask + differing values + +**Format:** +- Base: 23040 bytes (consensus data) +- Delta: 2880 bytes bitmask + N bytes values +- Each set bit in bitmask = byte differs from base + +**Results (with scanlines):** +- Raw: 5 × 23040 = 115200 bytes +- Encoded: 23040 (base) + 45969 (deltas) = 69009 bytes +- Savings: 40.1% + +**Memory layout:** +- Base (23KB) at $1000-$7FFF (NUFLI display region) +- Title delta (7KB) at $A000-$BFFF (BASIC ROM area) +- Other deltas: TODO (disk loading or art direction to reduce size) + ### Implementation Steps #### Step 1: NUFLI display routine integration diff --git a/tools/apply_scanlines.py b/tools/apply_scanlines.py new file mode 100644 index 0000000..a114afa --- /dev/null +++ b/tools/apply_scanlines.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""Apply scanline effect: darken every odd row for CRT aesthetic.""" +import sys +from PIL import Image + +src, dst = sys.argv[1], sys.argv[2] +img = Image.open(src).resize((320, 200), Image.LANCZOS).convert('RGB') +px = img.load() +for y in range(200): + if y % 2 == 1: + for x in range(320): + r, g, b = px[x, y] + px[x, y] = (r // 4, g // 4, b // 4) +img.save(dst) diff --git a/tools/nufli_delta.py b/tools/nufli_delta.py new file mode 100644 index 0000000..ce728a3 --- /dev/null +++ b/tools/nufli_delta.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""NUFLI delta encoder — extract shared base + per-screen deltas. + +Given multiple .nuf files, finds the consensus base (most common byte +at each position) and generates per-screen delta files. + +Output format: + base.bin - consensus data (23040 bytes) + *.delta - per-screen patches using bitmask: + 2880 bytes bitmask (1 bit per byte position) + N bytes of differing values (in order) + +Usage: + python3 nufli_delta.py output_base screen1.nuf screen2.nuf ... +""" + +import argparse +import sys +from pathlib import Path +from collections import Counter + + +NUFLI_SIZE = 23040 +BITMASK_SIZE = (NUFLI_SIZE + 7) // 8 # 2880 bytes + + +def load_nuf(path): + """Load .nuf file, skip 2-byte load address.""" + data = Path(path).read_bytes() + if len(data) == NUFLI_SIZE + 2: + return data[2:] + elif len(data) == NUFLI_SIZE: + return data + else: + print(f"Warning: {path} is {len(data)} bytes, expected {NUFLI_SIZE} or {NUFLI_SIZE + 2}") + return data[:NUFLI_SIZE] + + +def find_base(screens): + """Find consensus base: most common byte at each position.""" + base = bytearray(NUFLI_SIZE) + for i in range(NUFLI_SIZE): + counts = Counter() + for s in screens: + counts[s[i]] += 1 + base[i] = counts.most_common(1)[0][0] + return bytes(base) + + +def compute_delta_bitmask(base, screen): + """Compute delta between base and screen using bitmask format. + Returns (bitmask, values) where bitmask indicates which bytes differ.""" + bitmask = bytearray(BITMASK_SIZE) + values = bytearray() + for i in range(NUFLI_SIZE): + if base[i] != screen[i]: + byte_idx = i // 8 + bit_idx = i % 8 + bitmask[byte_idx] |= (1 << bit_idx) + values.append(screen[i]) + return bytes(bitmask), bytes(values) + + +def main(): + ap = argparse.ArgumentParser(description="NUFLI delta encoder") + ap.add_argument("output_base", help="Output base path (no extension)") + ap.add_argument("inputs", nargs="+", help="Input .nuf files") + args = ap.parse_args() + + if len(args.inputs) < 2: + print("Error: need at least 2 input files for delta encoding") + sys.exit(1) + + # Load all screens + screens = [] + names = [] + for path in args.inputs: + screens.append(load_nuf(path)) + names.append(Path(path).stem) + + # Find consensus base + base = find_base(screens) + out_path = Path(args.output_base) + out_path.parent.mkdir(parents=True, exist_ok=True) + + # Write base + base_file = out_path.with_suffix(".base") + base_file.write_bytes(base) + print(f"Base: {base_file} ({len(base)} bytes)") + + # Compute and write deltas + total_delta_bytes = 0 + for name, screen in zip(names, screens): + bitmask, values = compute_delta_bitmask(base, screen) + delta_data = bitmask + values + delta_file = out_path.parent / f"{name}.delta" + delta_file.write_bytes(delta_data) + total_delta_bytes += len(delta_data) + print(f" {name}.delta: {len(values)} patches, {len(delta_data)} bytes " + f"(bitmask={len(bitmask)} + values={len(values)})") + + # Summary + total_raw = len(screens) * NUFLI_SIZE + total_encoded = len(base) + total_delta_bytes + print(f"\nTotal raw: {total_raw} bytes") + print(f"Total encoded: {total_encoded} bytes (base={len(base)} + deltas={total_delta_bytes})") + print(f"Savings: {total_raw - total_encoded} bytes ({100 * (1 - total_encoded / total_raw):.1f}%)") + + +if __name__ == "__main__": + main()