-- demoscene (firehose): valley→stream→sky→night, bluesky events rain as particles parts={} -- firehose particle rain shoot={} -- shooting stars stars={} -- starfield (world space, high up) birds={} -- boids flock (screen space) demo_t=0 -- loop clock (s) df=0 -- frame counter (throttles the costly sun glow) demo_likes=0 -- cumulative firehose tallies, ones place (persist across loops) demo_posts=0 demo_lk=0 -- thousands place (16.16 fixed-point caps a single int at ~32767) demo_pk=0 cycle=87 -- loop length (s) demo_mb=0 -- prev mouse-button state fire_t=0 -- time() of last real (nonzero) firehose event demo_track=1 -- selected tune (persists across demo entries) track_lbl=0 -- show the tune name until this time() tunes={0,4,8,-1} -- music start patterns: gym,cdl,canon,off tunenames=split"gymnopedie,clair de lune,canon in d,music off" function lerp(a,b,t) return a+(b-a)*t end function eio(t) return t*t*(3-2*t) end function clmp(t) return mid(0,t,1) end -- altitude 0 (ground) .. 1 (night), eased; drives the upward camera pan function alt() return eio(clmp((demo_t-16)/34)) end function camtop() return lerp(312,0,alt()) end function fmtk(k,o) if k>0 then return k.."."..(o\100).."k" end return ""..o end -- carry ones>=1000 into thousands so totals never hit the fixed-point cap function roll() if demo_likes>=1000 then demo_lk+=demo_likes\1000 demo_likes%=1000 end if demo_posts>=1000 then demo_pk+=demo_posts\1000 demo_posts%=1000 end end function play_tune() local m=tunes[demo_track] if m>=0 then music(m) else music(-1) end track_lbl=time()+2 end -- pause-menu music (slot 1) + theme (slot 2); music is demo-only, synced via _update change-detect. function music_label() return chr(141).." "..tunenames[demo_track] end -- 141=♪ function theme_label() return "theme: "..tc("day","night") end function music_menu(b) -- ←=prev, →/🅾/❎=next; "music off" is part of the cycle if b&1>0 then demo_track=(demo_track+2)%4+1 else demo_track=demo_track%4+1 end if scr==scr_demo then play_tune() end -- audible only in the demo; elsewhere just arm it menuitem(nil,music_label()) return true end function theme_menu() theme=1-theme sfx(0) menuitem(nil,theme_label()) return true end -- feed switcher (slot 3): mutates active_feed; timeline reloads after menu closes. function feed_label() return "feed: "..feedsm[active_feed] end function feed_menu(b) if #feeds<2 then return true end if b&1>0 then active_feed=(active_feed-2+#feeds)%#feeds+1 else active_feed=active_feed%#feeds+1 end sfx(0) menuitem(nil,feed_label()) return true end function enter_demo() scr=scr_demo demo_t=0 df=0 parts={} shoot={} demo_likes=0 demo_posts=0 demo_lk=0 demo_pk=0 -- cumulative this session stars={} for _=1,40 do add(stars,{x=rnd(128),y=rnd(120),p=rnd(1)}) end init_birds() build_lut() enqueue(op_req_demo) demo_mb=0 fire_t=time() play_tune() track_lbl=time()+5 -- keep readable for ~2s after the 3s fade-in end function exit_demo() parts={} pal() music(-1) open_timeline(false) sfx(1) end function quit_demo() enqueue(op_req_demo_stop) exit_demo() end -- per-kind firehose particle: {y, vy base, vy*rnd range, colour}. kind 4 + kind-1 colour flip stay inline. sd={{-4,.4,.5,8},{132,-.5,-.6,12},{132,-.4,-.5,11}} function spawn(kind,n) -- stop spawning ~3s before credits vanish so particles drift off before the seam. if demo_t>=72 then return end local cap=alt()>0.8 and 14 or 46 for _=1,min(n,4) do if #parts>=cap then return end local p={kind=kind,t=rnd(1),x=rnd(128)} if kind<4 then local d=sd[kind] p.y=d[1] p.vy=d[2]+d[3]*rnd(1) p.c=d[4] if kind==1 and rnd(1)<.5 then p.c=14 end else p.y=20+rnd(90) p.vy=-.2-rnd(.3) p.c=10 end add(parts,p) end end function on_fire(po,li,re,fo,de) if po+li+re+fo>0 then fire_t=time() end demo_likes+=li demo_posts+=po roll() spawn(1,li) spawn(2,po) spawn(3,re) spawn(4,fo) for _=1,min(de,4) do if #parts>0 then deli(parts,1) end end end function sim_fire() local li=flr(rnd(3)) local po=rnd(1)<0.35 and 1 or 0 demo_likes+=li demo_posts+=po roll() spawn(1,li) spawn(2,po) spawn(3,rnd(1)<0.12 and 1 or 0) spawn(4,rnd(1)<0.25 and 1 or 0) end function update_demo() local mx,my,mb=stat(32),stat(33),stat(34)&1 local tap=mb==1 and demo_mb==0 and mx>=116 and mx<=127 and my>=1 and my<=12 demo_mb=mb -- ❎ maps to x/v/m; route desktop keys by char so "m" cycles music, x/v quits. while stat(30) do local c=stat(31) if c=="m" then demo_track=demo_track%4+1 play_tune() elseif c=="x" or c=="v" then quit_demo() return end end if tap or (mobile() and btnp(5)) then quit_demo() return end demo_t+=1/30 df=(df+1)%2 if demo_t>=cycle then demo_t-=cycle end update_birds() if time()-fire_t>1.5 then sim_fire() end if alt()>0.85 and rnd(1)<0.06 and #shoot<3 then add(shoot,{x=rnd(110),y=rnd(50),vx=2.5+rnd(2),vy=1+rnd(1.5)}) end for s in all(shoot) do s.x+=s.vx s.y+=s.vy if s.x>134 or s.y>80 then del(shoot,s) end end for p in all(parts) do p.t+=1/30 p.y+=p.vy p.x+=sin(p.t*0.4)*0.5 if p.kind==1 then if p.y>134 then del(parts,p) end elseif p.y<-6 then del(parts,p) end end end -- one continuous looped path: off-left -> rise to centre -> swell -> out left. -- start (j=0) and end (j=1) coincide so the loop is seamless. function bfly_pos() local bob=sin(time()*0.8)*3 local a=alt() if demo_t<75 then -- fly in from off-left, skim the valley, then rise return lerp(-20,64,eio(clmp(demo_t/24))),lerp(108,30,a)+bob,lerp(0.8,1,a) elseif demo_t<81 then -- finale: swell at centre with a slow flap local u=(demo_t-75)/6 return 64,lerp(30,56,eio(u)),lerp(1,5,eio(u)) else -- fly out off-screen left (loop seam) local u=(demo_t-81)/6 return lerp(64,-20,eio(u)),lerp(56,108,eio(u)),lerp(5,0.8,eio(u)) end end function bfly_sx(spd) return bfly_seq[flr(time()*spd)%4+1]*8 end function draw_bfly() local x,y,sc=bfly_pos() local w=16*sc local dx,dy=x-w/2,y-w/2 bflysh(bfly_sx(demo_t>75 and 2.5 or 6),dx,dy,w) end -- procedural fire-spiral sun: rotating arm in fire colours + bright core function fire_sun(cx,cy) for i=0,7 do -- subtle flicker corona, close to the disc local a=i/8+time()*0.1 local r=8+sin(time()*3+i)*1.5 pset(cx+cos(a)*r,cy+sin(a)*r,9) end circfill(cx,cy,7,9) -- orange rim circfill(cx,cy,5,10) -- yellow circfill(cx,cy,3,7) -- bright core end gpat={0x0000,0xa0a0,0xa5a5,0xf5f5,0xffff} -- vertical dithered gradient: colour b at the top, a at the bottom function grad(y0,y1,a,b) local h=(y1-y0)/5 for i=1,5 do fillp(gpat[i]) rectfill(0,y0+(i-1)*h,127,y0+i*h,a+b*16) end fillp() end -- tall atmosphere gradient drawn full so upward pan reveals it continuously. function draw_atmos() rectfill(0,0,127,150,0) -- deep space grad(150,212,1,0) -- space -> night blue grad(212,274,13,1) -- night -> dusk indigo grad(274,372,12,13) -- dusk -> light-blue day grad(372,440,7,12) -- day -> bright hazy horizon end function cloud(x,y) circfill(x,y,6,7) circfill(x+7,y+1,7,7) circfill(x+14,y,5,7) rectfill(x,y,x+14,y+5,7) end function draw_clouds() local s=demo_t*10 cloud((s*0.6)%190-30,272) -- drifts right cloud((-s*0.42+120)%190-30,300) -- drifts left cloud((s*0.3+50)%190-30,332) -- drifts right (slow) end function draw_ground() local s=demo_t*14 for x=0,127 do -- per column, back-to-front: far hill, near hill+grass, stream rectfill(x,402+sin((x+s*0.3)/40)*7,x,440,3) rectfill(x,410+sin((x+s*0.7)/30)*8,x,440,11) local ry=424+sin((x+s)/46)*4 rectfill(x,ry,x,ry+5,1) pset(x,ry,sin((x+s)/8)>0.55 and 12 or 1) end for i=0,11 do -- foreground flowers pset((12+i*12-s)%150-8,434+sin(i)*3,8+i%2*6) end end function draw_night_sky() if alt()<0.3 then return end for st in all(stars) do if sin(time()*0.6+st.p)>-0.2 then pset(st.x,st.y,rnd(1)<0.3 and 6 or 7) end end local mx=18+max(0,demo_t-34)*0.4 circfill(mx,24,8,7) circfill(mx+3,22,7,0) for s in all(shoot) do line(s.x,s.y,s.x-s.vx*3,s.y-s.vy*3,7) pset(s.x,s.y,10) end end -- credits during the night hold; show the running firehose totals function draw_credits() local a=clmp((demo_t-54)/3) local y=lerp(132,52,eio(a)) print_c("picosky",y,12) print_c("by vinnymac",y+9,7) print_c(fmtk(demo_lk,demo_likes).." likes "..fmtk(demo_pk,demo_posts).." posts",y+22,12) print_c("live stats provided by",y+31,6) print_c("bluesky firehose",y+38,6) print_c("thanks to bluesky, zep,",y+50,6) print_c("and daniel linssen for m3x6",y+58,6) end function draw_hud() spr(10,4,3) spr(8,42,3) print(fmtk(demo_lk,demo_likes),13,4,7) print(fmtk(demo_pk,demo_posts),51,4,7) if time()-fire_t>1.5 then print("sim",102,4,9) end end -- round exit button + aligned esc label function draw_exit() local cx,cy=122,6 circfill(cx,cy,3,6) -- pearl button, half size line(cx-1,cy-1,cx+1,cy+1,5) line(cx-1,cy+1,cx+1,cy-1,5) print("esc",cx-18,cy-2,6) end function heart(x,y,c) circfill(x-1,y,1,c) circfill(x+1,y,1,c) line(x-2,y+1,x+2,y+1,c) line(x-1,y+2,x+1,y+2,c) pset(x,y+3,c) end function draw_parts() for p in all(parts) do if p.kind==1 then heart(p.x,p.y,p.c) elseif p.kind==4 then pset(p.x,p.y,p.c) pset(p.x,p.y-1,10) else circ(p.x,p.y,2,p.c) pset(p.x-1,p.y-1,7) end end end -- a small flock of tiny "v" birds: simplified boids (separate/cohere/align) function init_birds() birds={} for _=1,6 do add(birds,{x=rnd(120),y=34+rnd(24),vx=0.3+rnd(0.3),vy=rnd(0.4)-0.2}) end end function update_birds() if alt()>0.6 then return end for b in all(birds) do local cx,cy,vx,vy,n=0,0,0,0,0 for o in all(birds) do local dx,dy=o.x-b.x,o.y-b.y local d=dx*dx+dy*dy if o~=b and d<400 then cx+=o.x cy+=o.y vx+=o.vx vy+=o.vy n+=1 if d<144 then b.vx-=dx*0.025 b.vy-=dy*0.025 end end end if n>0 then b.vx+=(cx/n-b.x)*0.002+(vx/n-b.vx)*0.06 b.vy+=(cy/n-b.y)*0.002+(vy/n-b.vy)*0.06 end b.vx=mid(0.2,b.vx,1) b.vy=mid(-0.5,b.vy,0.5) b.x+=b.vx b.y+=b.vy b.y=mid(22,b.y,74) if b.x>134 then b.x=-6-rnd(16) b.y=34+rnd(24) end end end function draw_birds() if alt()>0.55 then return end for b in all(birds) do local w=sin(time()*4+b.x)>0 and 1 or 0 line(b.x-2,b.y-w,b.x,b.y,5) line(b.x,b.y,b.x+2,b.y-w,5) end end -- screen palette darkening: lv 0=bright..6=black, smooth, no dither. drk=split"0,0,1,1,2,1,5,6,2,4,9,3,13,1,2,5" -- darken LUT, 1-based (drk[c+1] for colour c) function setlight(lv) for c=0,15 do local d=c for _=1,lv do d=drk[d+1] end pal(c,d,1) end end -- byte->darkened-byte LUTs (one per level) in free RAM, for the per-pixel pass function build_lut() for lv=0,6 do for b=0,255 do local lo,hi=b%16,b\16 for _=1,lv do lo=drk[lo+1] hi=drk[hi+1] end poke(0x4300+lv*256+b,lo+hi*16) end end end -- sun light: scene drawn, then darken every pixel by a far from the sun, easing -- to 0 at the centre (concentric rings). rows beyond the glow take a fast path. function light_pass(sx,sy,a) local r=40 local rr=r*r local cb=sx\2 for y=0,127 do local dy=y-sy local dy2=dy*dy local row=0x6000+y*64 if dy2>=rr then local l=0x4300+a*256 for ad=row,row+63 do poke(ad,peek(l+peek(ad))) end else local hw={[0]=0,[a+1]=64} for k=1,a do local q=(r*k/a)*(r*k/a)-dy2 hw[k]=q>0 and sqrt(q)\2 or 0 end for lv=0,a do local l=0x4300+lv*256 local i,o=hw[lv],hw[lv+1] for ad=row+mid(0,cb+i,63),row+mid(0,cb+o-1,63) do poke(ad,peek(l+peek(ad))) end for ad=row+mid(0,cb-o+1,63),row+mid(0,cb-max(i,1),63) do poke(ad,peek(l+peek(ad))) end end end end end -- loop-seam fade: to black over the last 3s, back from black over the first 3s function seam() if demo_t>=cycle-3 then return flr((demo_t-cycle+3)/3*6+.5) end if demo_t<3 then return flr((1-demo_t/3)*6+.5) end return 0 end -- dusk dimming: gradual, held through the day->night pan, released to 0 only -- once the night sky is fully in view (gated on alt, so no bright-blue flash) function amb() local t=demo_t if t<20 then return 0 end if t<28 then return flr((t-20)/8*2+.5) end if alt()<0.55 then return 2 end return flr(max(0,(0.66-alt())/0.11)*2+.5) end function draw_demo() local la,ls=amb(),seam() local suny=340-camtop() local glow=ls==0 and la>0 and suny<134 if glow and df~=0 then return end -- update the sun glow every other frame cls(0) camera(0,camtop()) draw_atmos() draw_night_sky() fire_sun(96-demo_t*0.4,340) draw_clouds() draw_ground() camera() draw_birds() draw_parts() draw_bfly() if demo_t>54 and demo_t<75 then draw_credits() end if demo_t<53 then draw_hud() end if glow then setlight(0) light_pass(96-demo_t*0.4,suny,la) else setlight(max(la,ls)) end if time()