From 19d4d4577def4ca865b24fd7d3fe177cf5c6906a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Martin?= Date: Tue, 13 Oct 2026 23:14:56 +0200 Subject: [PATCH] Devlog: Not finished until it plays Tetris The Games App of v0.24.0: why a firmware wants games, what Pong, Snake and Tetris carry, how little room they take, and what playing changed in the design. With five screenshots from the device. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0162FokPdvY2KsS4NBfwyWPk --- .../content/devlog/roro9stack-games/games.png | Bin 0 -> 1869 bytes site/content/devlog/roro9stack-games/index.md | 215 ++++++++++++++++++ .../devlog/roro9stack-games/launcher.png | Bin 0 -> 3533 bytes site/content/devlog/roro9stack-games/pong.png | Bin 0 -> 1522 bytes .../content/devlog/roro9stack-games/snake.png | Bin 0 -> 1723 bytes .../devlog/roro9stack-games/tetris.png | Bin 0 -> 3005 bytes 6 files changed, 215 insertions(+) create mode 100644 site/content/devlog/roro9stack-games/games.png create mode 100644 site/content/devlog/roro9stack-games/index.md create mode 100644 site/content/devlog/roro9stack-games/launcher.png create mode 100644 site/content/devlog/roro9stack-games/pong.png create mode 100644 site/content/devlog/roro9stack-games/snake.png create mode 100644 site/content/devlog/roro9stack-games/tetris.png diff --git a/site/content/devlog/roro9stack-games/games.png b/site/content/devlog/roro9stack-games/games.png new file mode 100644 index 0000000000000000000000000000000000000000..64a2a952f3bd8f9d2659738747fbabdea77b4875 GIT binary patch literal 1869 zcmb_bYgCfi8b(0VJZ6r!A{||sb~Un?QA_2U3dyjvvP?|OY!oY%8cmB(K3mN(H4H0I z6a6Oa?xis=BZb)M1f->CjZB)CG)Ej`ydegNoDbKUKQpt=pL6zFd#%0S{k+fnuIGLB z< z_80chOg4&NuQ9o>t9*jvePm;DTx0u1P0YlajOnjvSxpSXJl9KHm^JOGH_!7D`#FPO ziwkeL$;8VJq@cT(#pbYp6|un`31-@%Ybrjjp)6U1pZA^&X&6%}g>|dWoYIlh@j6b_ zWFMm^d+u#@G~U4Lno`~+whL2D>&2AY`W9Zoz&%gP+O=Ltq3C$Cy@O(+&#kN}zBMGN zlm4Y+Y(U5=HUqwo>&C`IUIPucoHQw)B8VIp;V~UO-zjoVbcG)|e@5PR#3wfJ3NVgc zPP=5f_37+OWG^|Mo?YqAw%sORHNMHy7ktPOO7cCuWtB_IG&ycHPsejvdT;-}oGoei z8eS7?CQ;Mc`)OMY_;oB1n&mqAID)?yuSGdqCw=VKkyxemFo`yYk7e`c={~lK%C_~s zI9aAg!O&RKV0J}++6#5Hx`^&|q7|9AI}b=sr38V$3#N#8MmxvaP_St}VZCJ1mNS|f z7Y0R=1|_tVlM!H2C4QZ(QMBs40asJUr{e{6T|5lEWP!o57D&E3ZGDhzO`PjzdrCr{ zzY1Vi+o-4IjuNTEAjql^@%UKHNNSa(s_dn9#|`P-_&E9u`N|3B9mmi0b3}fm5$yZ3 zv+PV_y`P#>*y1MBk;wQuMl@Qh$cS`4$q;0o8flmtj=<2n#mkK98E0ujjm=xT6`7dl zMTpnkM8W>~Kdg0H{pog-Ldy9dQ_Mi4s$em$`MXhfdn(gVEx&Wz{1fgb00zs#(85_Qr-f zRiW;T3-?*3*!5HL3I$)1IjKeNDUG0X?=u3bEpUFXraD;#Q88e~9_T|1hkN$=r^S}4 zlgQVqxC@i(q*b&{GLoSn6e{Y=!%|G&!A6v$i$`C79z1x}m{fTf`ru=IsO*oVK>x!J z{ja~X4_H1H^W+H`aeMeRQVL2MT}X zZdinHoEn_5p|)EbY0H%pA^+7hUo6E1RF^al2MW{KWx_Y4jMEVI@_k@DQhA|>3C!9k z&W%U4o)~FGqXqduJAIsdXj`<0tBip@-$$3%bZa()X#v^9*@GH)8^zYyW7mIf<64-f zxPVpPVFM%n;P{(|2&ujG>0$22K|r_T2KpfnYiUCTOMBr;+tFkwzM)%FvlMhtysJFh zMB5GHa(;<&Vt{7i`%af&DR5UGoUtY32A}PG*_Hqj#*7(SB&L z76S6dKZPeAb?@+9BjYX#gG@0T`WjQgywHJ~epvZ~okBZHh6h{s&;8I?|4HzB!fo!4 zQpELZ6!A(_=-%_obN>POj()pgGh(9>{qWm zut#fJq#!>_ye(1^?i8ymH+R%!xtEJH+ffro&U-ER_t=7eU)WKZ^3JifEr|Bp+%U-K z;`<9O-Vd7nw?E@B(Lw8_h?ABN)T1#_$st^IHk{|=Dnk&2TZ)lZG$w$uv%mc7S)SC< zja~Lz&ywaZU|?^0QZwarf=HV`3`-P?*gx~9_a4E3;29Zf)5{iVfKpC^Y#@Yfi2`jryA z&KH?m>`y^jz3{TOmx`eU&yv=rU3T M5%%t^+I}?aKk*zsUH||9 literal 0 HcmV?d00001 diff --git a/site/content/devlog/roro9stack-games/index.md b/site/content/devlog/roro9stack-games/index.md new file mode 100644 index 0000000..783a32e --- /dev/null +++ b/site/content/devlog/roro9stack-games/index.md @@ -0,0 +1,215 @@ ++++ +title = '''Not finished until it plays Tetris''' +description = '''roro9stack had a radio scanner, an SSH client and a VPN, and no games, which for a firmware means it wasn't finished. Now it has Pong, Snake and Tetris, with the tune, in 14.6 KB of flash. Seventeen design questions were answered with one word each, and then an evening of playing overturned three of the answers.''' +date = 2026-10-13T23:30:00+02:00 + +[extra] +topics = '''ESP32-S3 · Games · Chiptune''' +read_label = '''Read how three games got in →''' +uid = '''tetris: level 1, music 6/8''' +dek = "[roro9stack](/devlog/roro9stack/), my firmware for the M5Stack Cardputer, could sniff a LoRa mesh, browse Gemini and open a shell on a server. It could not play Snake. That is fixed: a Games App with Pong, Snake and Tetris, and the tune Tetris has always been played to. This post is about why a firmware needs them, what three very old games still do to people, and how little room fun takes." +byline = '''seventeen questions answered "Ok", then eight things changed by playing''' + +[extra.sign] +label = "Bytes of flash for three games and a folk song" +note = "The Tetris cartridge for the Game Boy was 32,768." +count = "14,616" + +[[extra.cast]] +name = "The Launcher" +role = "a grid of twelve tiles" +text = "Had eleven Apps in it. Three games would have made fourteen, so there is one Games App, and the grid is full." + +[[extra.cast]] +name = "The key mapper" +role = "turns the keyboard into events" +text = "Told Apps when a key went down and never that it was still down. Fine for a menu. Useless for a paddle." + +[[extra.cast]] +name = "Korobeiniki" +role = "a Russian folk song, 1861" +text = "The tune of Tetris. Fifty-eight notes in 232 bytes. I had decided there would be no music, and held to that until Tetris was running." + +[[extra.cast]] +name = "The 1 key" +role = "top left of the keyboard" +text = "Drops the piece. It was going to be Space, which on this keyboard sits right next to the arrow for 'right'. A drop cannot be taken back." + +[[extra.cast]] +name = "A script" +role = "playing over Wi-Fi" +text = "Presses keys through the Debug Console and reads the screen back. Played the Snake in this post's picture, and lost 35 games in the two runs I counted." ++++ + +## TL;DR + +- **A Games App with Pong, Snake and Tetris** (**v0.24.0**). +- **It costs 14.6 KB of flash and 8 bytes of static memory.** The games allocate their few hundred bytes when one is started. +- **Each game is a small state machine with no screen in it:** time and keys go in, the new state comes out, and a seed decides the dice. 44 tests play them on the PC. +- **Apps can now ask which keys are held.** Until this week they only heard presses. +- **Tetris plays Korobeiniki,** faster with each level. `0` switches it, `8` and `9` set its volume. +- **Playing changed what the design round had decided:** icons instead of a list, the drop moved off Space, and music after all. +- 673 host tests, 47 more than the firmware had without games. + +## The cast + +{{ cast() }} + +## It wasn't finished + +A firmware that can't play a game isn't done. I don't mean that as a joke. + +Look at what people do the moment a machine has a screen and a few buttons. Emacs has had `M-x tetris` for decades, next to `M-x snake` and `M-x pong`. The BSDs still ship `tetris(6)` and `snake(6)` in `/usr/games`. An HP oscilloscope from the nineties, a serious instrument that cost a serious amount, hides a Tetris behind a key sequence. The Flipper Zero comes with Snake. Chrome has a dinosaur for when the network is down. Nobody needed any of these. Somebody put each of them in anyway. + +A game is the first thing you write for yourself. Everything else in this firmware answers a need: a clock to set, a packet to decode, a file to move. A game answers nothing. It is there because the machine is yours and you wanted to play on it, and a device that has one feels owned in a way that a device with a perfect settings menu doesn't. + +{{ figure(src="launcher.png", alt="The Cardputer's Launcher at 2x: a grid of twelve cyan icons on black, four across and three down. The tenth, a game pad, is on a filled cyan tile, and the name under the grid reads Games.", width=480, height=270, caption="The Launcher, full: twelve tiles, and the game pad is one of them.") }} + +## What these three carry + +**Pong** is from 1972 and had no program at all. The first machines were wired from logic chips: no processor, nothing to load. Two paddles, a square ball, a score. It is still the clearest picture there is of what a video game is. + +**Tetris** was written in 1984 on a Soviet computer whose terminal could not draw, so the first blocks were pairs of brackets. Five years later it came in the box with the Game Boy, and with it came a folk tune from 1861 that half the planet now calls "the Tetris music". + +**Snake** shipped on a Nokia phone in 1997, on a screen 84 pixels wide, and turned waiting rooms and bus stops into something else for a decade. + +You don't have to have owned any of those machines for these games to pull at something. The sound of a line going, the snake one cell from its own tail, the ball coming back faster than you sent it: they are in the hands more than in the head. Nobody has to be told the rules of any of the three. That is rare enough in software to be worth an evening. + +And they are small. That is the part I can't get over. This device has a screen of 240 by 135 pixels, somewhat more than the Game Boy's 160 by 144, and 8 MB of flash where the Game Boy's Tetris had 32 KB to itself. Three games fit here in less than half of that cartridge. They were small when memory cost a fortune, and they stayed small because there was nothing in them to grow: a field, a rule, a clock that speeds up. + +{{ figure(src="games.png", alt="The Games App at 2x: three icons in a row on black. Two paddles and a ball; a snake with a head, on a filled cyan tile; stacked blocks over a floor, with a small orange dot at its corner. Under the row, Snake in bold, and below it, best 11.", width=480, height=270, caption="The three games, shown the way the Launcher shows Apps. The orange dot says a game of Tetris is waiting, paused. Best 11 is mine, by hand.") }} + +## Seventeen questions, all answered "Ok" + +The design round was seventeen questions, each with a recommended answer, and I took every recommendation. It went fast because the facts had been looked up first, and two of them shaped everything: + +**The Launcher was full.** Its grid shows twelve Apps and eleven were there. So: one Games App holding three games. In the project's vocabulary a **Game** is not an App. It has no tile, and it is found again paused when you leave in the middle of one. + +**Apps only heard key presses.** A key gave one event when it went down, and nothing while it stayed down. A Pong paddle moved by tapping is not Pong. The key mapper now answers "is this key held?", and three tests on the PC hold keys down to prove it. + +The rest, briefly: + +{% table() %} +| | Decided | +|---|---| +| Pong | Against the device, first to 7. Its paddle has a top speed, so a steep fast ball beats it | +| Snake | 38 by 19 cells. The walls kill | +| Tetris | 10 by 20, upright, 6 pixels a cell. Pieces dealt from a bag of seven, the next one shown | +| Difficulty | No choice. Each game starts gently and speeds up | +| Colours | The seven pieces take the theme's seven accent colours | +| Scores | One best for each game, kept in the device | +| Sound | Short beeps. **No music** | +{% end %} + +Each game lives in `lib/games` as a state machine that knows nothing about a screen or a keyboard: so many milliseconds and these keys go in, the new state and what happened (a bounce, a line) come out. A seed decides the dice, so the same seed is the same game. That is what lets a test say "a player who always meets the ball must take points off the device over five games", and have it checked on every build without anyone playing. + +{{ figure(src="pong.png", alt="Pong on the Cardputer at 2x: a cyan paddle on the left near the top, an orange one on the right, a dashed line down the middle, a small white ball left of centre, and the score 1 to 0 at the top.", width=480, height=270, caption="One-nil against the device. The cyan paddle is the player's.") }} + +## Then I played it + +A design round answers the questions you can think of from a chair. Then the thing is in your hands, and in a little over an hour I had asked for eight changes: + +1. **A list of three games looked wrong.** The Launcher shows icons; so should this. Three icons, drawn on a 16 by 16 grid and doubled, like the Apps'. The first snake looked like the digit 2 and was redrawn with a head. +2. **Space was dangerous.** On this keyboard it sits next to `/`, which is the arrow for "right". A hard drop can't be taken back, and one slip of a finger would do it. It is on `1` now, far from the arrows. +3. **"No music" lasted until Tetris was running.** I changed my mind and asked for the tune. See below. +4. **`8` and `9`** for the music's volume, once there was music. +5. **The tune speeds up with the level,** because the fall does. +6. **The top volume was too low.** The speaker had only ever been used for short beeps, at a quarter of what it can do. +7. **Then it was too high,** and the scale was cut to four fifths. +8. **The soft drop sometimes behaved like a hard drop.** That one was a bug, and is under "What went wrong". + +None of these would have come out of more questions. They came out of having the thing in my hands. + +{{ figure(src="tetris.png", alt="Tetris on the Cardputer at 2x: a well in the middle, with a pile of blocks in seven colours filling its lower half except the rightmost column, and a yellow square piece falling. On the left, Score 0, Best 56, Lines 0. On the right, Next with a long cyan piece, Level 1, and at the bottom, music 2/8.", width=480, height=270, caption="All seven colours, from the theme. The score is 0 because the script that played this let every piece fall by itself: a drop scores, and I didn't want a script's score kept as the best. The 56 is from a test.") }} + +## The tune is 232 bytes + +Korobeiniki is a song about a pedlar, from a poem of 1861, and it has been the sound of Tetris since the Game Boy. Here it is a table of 58 notes, each a frequency and a length in eighths, four bytes a note: the first part, played twice, then the slow second part. A player the size of a paragraph says when the next note starts, and the speaker's tone generator does the rest. There is no sample, no synthesiser and no file. + +It has a speaker channel of its own, so the beep of a line cleared doesn't cut a note short. At the first level it runs at 150 beats a minute, and each level takes 8 milliseconds off every eighth, down to about 270 beats a minute from the thirteenth level on. A new pace starts at the next note, never in the middle of one. The timing has its own tests: 9.9 seconds fed in steps of 33 ms must give exactly a hundred notes, not ninety-nine and a drift. + +`0` switches it off, and the device remembers. + +## What went wrong + +**The soft drop that wouldn't let go.** Holding `.` makes a piece fall faster. When it landed with the key still down, the next piece came in already falling fast, and so did the one after: a soft drop that felt like a hard one. Now the key that brought a piece down does nothing for the next piece until it is let go and pressed again. It was also simply too fast, a row every 50 ms; it is 90 now. + +**A dead snake was a cell short.** The tail was moved before checking whether the head had hit something, so the snake on the "Game over" screen was missing its last cell. One cell, and it looked wrong at once. + +**The pictures in this post tore.** The Debug Console's screenshot reads the frame while the App may be drawing it. For a settings page that never matters. A game redraws thirty times a second, and the first Tetris picture had a black band through the pile where a new frame was half drawn. The code has said so in a comment since the day it was written: "it can tear; it's for looking". The picture above is one of two taken back to back that agreed. + +**A script is bad at Snake.** I wanted a picture of a snake that had eaten something, and the console can press keys, so a script read the screen and steered. It lost 24 games in under five minutes and never got longer than six. The console goes quiet for a second or two now and then, and at a cell every 150 ms that is a wall. It went quiet most often right after the beep of a meal. With the sound off it lost 11 games in two and a half minutes and reached ten cells, which is the snake below. I haven't found out why a beep and a stalled connection go together. + +{{ figure(src="snake.png", alt="Snake on the Cardputer at 2x: a framed black field, a green snake ten cells long with a white head, folded back on itself in the middle, and an orange square of food near the bottom right corner. Above the field, the score 7 on the left and best 11 on the right.", width=480, height=270, caption="Ten cells, seven meals, played by a script over Wi-Fi, a beat late. The best of 11 it didn't touch.") }} + +## What I checked, and what I didn't + +Checked on the device, by playing: all three games start, pause, resume and end; the paddle follows a held key; Tetris's left and right repeat; the tune, its switch, its volume and its memory of both; the screen staying lit during a game and dimming again when one is paused. + +Checked on the PC, by 44 tests: the rules. Bounces, the first to seven, the device's paddle having a top speed and still losing points; turning back on itself not being a turn, following its own tail being allowed; the bag dealing each piece once in seven, a turn against a wall shifting the piece, four rows at once scoring 800. + +Not checked: Tetris past the first few levels, so nobody has heard the tune at its fastest. A long session on battery, with the screen redrawn thirty times a second. Whether the top of the volume scale distorts on every unit or only on mine. And in the Nord theme two of the seven pieces come out the same orange, because that theme's red and orange are neighbours on a screen with 256 colours. + +## Also since last time + +There was no post for the last three releases, and v0.24.0 has more in it than games: + +- **An SSH client** (**v0.22.0**): a real terminal on another machine, with `vim` and `top` in it, and `scp` from the Shell (**v0.22.1**). +- **MeshCore in the LoRa Scanner, a Launcher of icons, seven colour themes, Settings in groups** (**v0.23.0**). +- Also in **v0.24.0**: **a compass** in the GNSS App, which has no magnetometer and takes north from walking or from the sun; **updates that install themselves** after a 30-second question, if you ask for that; and **downloads that carry on** after the Wi-Fi drops instead of starting over. + +## By the numbers + +{% table() %} +| | | +|---|---| +| Games | 3 | +| Flash | 14,616 bytes | +| Static memory | 8 bytes | +| Lines of game rules, `lib/games` | 719 | +| Tests that play them | 44 | +| The tune | 58 notes, 232 bytes | +| Its pace | 150 to 272 beats a minute | +| Pictures a second while a game runs | 30 | +| Design questions | 17 | +| Answered "Ok" | 17 | +| Answers overturned by playing | 3 | +| Games of Snake lost by a script, in the two runs counted | 35 | +| Host tests | 673 | +{% end %} + +## Where it stands + +{% steps() %} +1. ~~M0 and M1: the skeleton, Wi-Fi, IRC, Wi-Fi Tools.~~ v0.1.0 to v0.2.1, [the first post](/devlog/roro9stack/). + +2. ~~Updates and debugging over the air.~~ v0.3.0, [Look, no cables](/devlog/roro9stack-ota/). + +3. ~~M2: GNSS.~~ v0.4.0, [Seventeen satellites](/devlog/roro9stack-gnss/). + +4. ~~G1: Gemini.~~ v0.5.0, [A browser in the RAM IRC left over](/devlog/roro9stack-gemini/). + +5. ~~M3: the LoRa radio, listening.~~ v0.6.0, [The loudest thing it hears is itself](/devlog/roro9stack-lora/). + +6. ~~S1: the card, fixed addresses, the System App.~~ v0.6.1 to v0.8.1, [One byte too early](/devlog/roro9stack-s1/). + +7. ~~F1 and the start of R1: files, notes, signed releases, updates from Gitea.~~ v0.9.0 to v0.11.0, [836 bytes](/devlog/roro9stack-f1-r1/). + +8. ~~W1: the website, and one firmware with the Debug Console in it.~~ v0.12.0, [It was off](/devlog/roro9stack-console/). + +9. ~~A help key, the Shell, notes of any size.~~ v0.13.0 to v0.15.0, [It said "No"](/devlog/roro9stack-shell/). + +10. ~~Pictures, a screenshot key, the card in a phone's browser.~~ v0.16.0 to v0.18.0, [Press w](/devlog/roro9stack-share/). + +11. ~~A WireGuard tunnel, and nine commands for a bad network day.~~ v0.19.0 to v0.21.0, [Six releases behind](/devlog/roro9stack-vpn/). + +12. ~~SSH, MeshCore, icons and themes.~~ v0.22.0 to v0.23.0. + +13. ~~A compass, updates that install themselves, and Pong, Snake and Tetris.~~ v0.24.0, this post. + +14. Next: M4, the mesh, which still wants a second node. +{% end %} + +{% signoff() %} +Fourteen thousand bytes, three games, one tune. Most of what this firmware does took measuring, and bit me first. This took an evening and asked for almost nothing. The device could already do a great deal. Now it is also something to pick up when there is nothing to do, and that was the missing feature. +{% end %} diff --git a/site/content/devlog/roro9stack-games/launcher.png b/site/content/devlog/roro9stack-games/launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4a006213cc1a0ad78717c0cbeb1ddb27f89d7342 GIT binary patch literal 3533 zcmbtW2~?BE7RC^epzJCI4N(@cMWBkvW*~~95e2LZi#5mt3pN4@QcMDYD1sJBDnSq$ zNGMVlme9%;Ac=ygh$Mk3s6jwkl8PY;ff$yglh9hvdF^}eoR@R{lbJhr{<-(N-*^AH z7~r>4PuE;mLqkIky=(gy8XAiR!K1GO0ha#v`#%TvT=e#>2hLE%FS;(RWG+wd(u*}Y znWKOEj;on`YgKJeM188Z!`noeTLR6Mx<=zHt64ML+6vYwSivvtH+{+WGYVxFK`%>w?+{0{lY_I(y);IqWGCbLJ!IBD8?E8Yz z54$@V7I0BsG{QcnM>efULm;@Ea+xi8)z8NZn4E~~XQrlW#d^{%BtpJ?XU9-4vFnrq zcf#$vVA}ouz~AiKg(E-rjZ=)T>I|ckEfa_Z zKJ5Bqn)Vsp#0K$zfJh&G{%B_T{o3u^nV~Ri{;injT|DLr5$)HUp>IP^)p!xbcSGg($gYWXrCpO`3(ioeaj{qbEOlBaD^8hZ;|Z4h7x|^K&#vSdiTqgu35o_( zy%$F|Cy+f6P{vU*hpKtPea`cV%`(LtmavKS>jXA!a_&fGc!z(K!Rd}kugJN0DK6X# zfsp&Q98m_8jW~HI8to&`NG)nBnq}oFO}_TMW%F7-?8KgXA?*5jVW^<=H#o^NWmwdd z9rH#w1W8scA4gz=`| zhnu94C z1DPp|uqVj-eDI>m|7Qi~tB?7UF;;x6j#OKaxt^bLYAeG?9s4<};0d@d<}f3VRgo3f z#$(!$)-G5a)j9aG!$DPT_$AQ7i1x(wxM-FtVae2(nL95YCZtrS>$cDC#vFDd_50EV zHdOx>^Utg$c4|^Ml49~tdcv^Rzi(!!Nx_0P9%+$VSCnj9?PH2FMp!x%S;-AHgcyemu&L*`{Bj5hSwTRPHC9jPm+ic&q0=KRQ#?_$Lt zI#k>);BrO`nBN``ZiUyrDI3`nQXD$ZZa%Ai4Fs8w8p!&)VMbwsy#M4y0_=2A+Wgvz zuso1;2#>;TzKH5I`fxrF1?V6P9xGIwN&pmYA)54jo{7qMjl2*{=4|aK$lB9^MIM}b z#uh)ao3`Ye=zv~RWd&pfdJp&q)NuG?Z?;NqKZteTg&WjtZ7XmU(iVVPE~5+>s`a6I zdB9e>%YUDBxJkJc&Rft^W)q=msi{PPHzHyDLCl~;tP3ghPEnBg0U7G21ER?`~(5EOsz8gX@G!LpI#C4IH&9ydMv>(NgME#`S~a<0wY4PknT z*8F&zH^?wOAW@<5t1H#%s!yQtba8ch*_Z5i*r`21t#Fw1OBlgFsCBc$t7|KLm=V$K zhpLq0k2Ax z1S<^gR{xLAn{ksPvj?|A?JO~h#>CS#ic744`91jW?rwT1Cy%Tq?p`ud)ypc)doVpd zKOWg_XW!u8Nb_EG*B@jy)tLC$_*uVtLz$KgEsMW!?)|6sxlI^Zc8H~*N!CO|GinAs zefhKPx)df@Xx?^FHYDVvQDe9 z=!5^RC1t336VR35!)OFISa%x}3!UizSDGrB=T)R~A1Jk^8o~LIcMF*uTwF6FCr`ZY zLd_>Z~B6QgF?Ql0+{G=ErQ>xYcVQQw|z^hwD%l}0n0N|nG9AUcTrEp;&b{6c5!)cuB;0tP$&O|uBw7)0-ELV?ZW*uR ziEjvN_`TLU_tOP1Kwbyyf10fIYeQ^Hq&9(DQpkAwu9GypLBR>2y3uh}ms{JS78~Fx zWN#V}Wr4~MTBsxF_+%r$176w(mX}K7?ysw7Wv$U<^=)_<4J3$iOw1~)3;ZHirTD+( z-6U>X7L__6!1(V|DIN_J|8aNtvwIvRAr^nbSTZ;THDAr$mwn9qsFUhmc~_$Z7!DBV zJf-D3QMr(_#SEu^yA`$xb^X#@(N3nrvr>X~gBEa|GGmtpWDhsIkeh2@t|GG!qGJe^ z$ZML_UHhOsO}v6SZAh*#9DE60_ZaZ*s(eeW{n!oH#i_0kJ;O?yQgK1??2+!J&4nn< cKVyJL16hu`+9ZAZ`k4&!F>kpKVy literal 0 HcmV?d00001 diff --git a/site/content/devlog/roro9stack-games/pong.png b/site/content/devlog/roro9stack-games/pong.png new file mode 100644 index 0000000000000000000000000000000000000000..eff48dd80fde950785c7f4a4e2026d956314f180 GIT binary patch literal 1522 zcmeAS@N?(olHy`uVBq!ia0y~yV0-|?d>l+bk$XRa4Hy_$cY3-whE&XXd*@-{Y%htn zz^T*Of@a+IVANAPt05UZx#48qzSgM~xpR&0_w$_K5PQAT_}kIx`S#tvr&u`h5|NYwfs?}d-o!TX9*?w`a`@5X~tHR53@2`|LE@#=bKlKn7 zcgc4dx9@U0H@7qWosln|JT2Js$-ce)mEZrp{ykapMc5SW>hEVitH7V@ zGw!&b4_@GVgrlVXbc?9i3ugCjzLNiMw~L4Eyz*~%NL=#1OK1Kn9=5%=w=VBF+pe!S zZ9XaYo%*KSD0lYGnystkm*3l1^;auh?}Azq$OT_AZu?JgcyHbQ&-T^*-T%Tv&;7A) zT*tos)BAtZe(4&<=coPcNRC%Z|NH!*#{zfzU0)a9J(nB4;QsPSk9Ri8-QARb^w;XW z@8f#zo&6WrXeOw9;cNew7Z>xMt6jLeRqC$w>%4E@`TkGZ01T)17rE^|${r+qB>-+2GTzoB*A#BKG`%5x&@4l_E3!clZ zEn2P>e$oEWIrh+B_hqKmhxgf3Z9e;a?x}vkUq5e)A3r7ds&U<(I_t&qx1MH4%|H0| zoAQ2@J;$51rfTnv=iRpYO8nt#wyWm+p9nGe?)^)@R(ZwbrK?`Jd-e7D#Mg46=l-84 z{h92EEzURpJ= zet4Q?`h39;DVHuOUtkb_nVGq^b1Rda%YtYYDT}j4CW|(QMDl90-I?gHox`MHR&LCb zG|vT%mpMx2J1{$f*$PFQRW3M8*m7si?*&~z!AnIzgUmB`3T8CS-g1Y-t8L32l|MTJ>vcMh3uQ=MTBLlzM!xac*>C+9wm*AgY&+ldVIa#a zbEepI-wF|kvlp^RJ!p7+`uI%u1&n+G8BmWYK5`VjA(SDI0rVh;2~Z8oETE5>e1U## zRGZ?^>>%v5z;OX5(Az*cg((iqEkZD@NMW)jM*5uH`rn@xaLr_yCEC~~!*sUG!I`fy z&2@odyTfLs3lp3dL<(k{P`t2)!{muTMi`4!5eGa#v6@^F(2C}eSMwU%{GAq@RJovV z0q6>ZlhK?7j=Xornze8{85+YNDuIiC6$4gJfwUtjY|yk>!Gx9+5H3ej$gu3g0xno$ qL8ySGBqWtH4vk1hGB7lpe<=Q9S898yP1GV#czC+{xvXl+bk$XRa4Hy{Mnmk<`Ln`LHy>q+pb{K3ERy2RIuf4_Tk?X`G@x6gv( z?tT^RtW_1f(eii8)3?X{S2urYxj*)docZJvwf%O_uO2@yzh3giR*myEyYl+NN7N-6$G`d!`fNYL?&IhFPI%ZA zK7aqV>ugnTt3RJ`moYxx&+^OT#JN>Ru7yYL-d6Z`Tii~E=p-gvyG>tnm};+?_PoE? z&1@@wXJ&kV|C#$jyYHX-)sY;pbpF@bfAcrn9X54O z+FQc^xK5q9{>v^Yo%>5aR_`e5Pg__2@1Cyug=sS!`uo@X4bQ&ceEKnyZQb`{?;qZ> zowf7d&+6@S>UB<@(XnbYGw$|VaQ4h1;SA%=FQ(aB0uvPjLxXx%{r`_I<7#6M{6Bj2 ztYzK5kCJ+O>KfVNychWI7HKG+%53{D`8D^y0PPD0j5)uwdGGv_${=^9=7`ngLr;s( z{rX_`QLx>C`MmlC^%#}|qSKy6x_$e2J^Q@;@xOPpZNC2ji?aN>aGE(LUG2i>-dX4W zPSl=L|AmE#cTbmtd{t$`YnlIw5C8NFoZq``a~+3eso?ZEQVAXl>_2|0Uw`Y&n`2=2 z`f+?&7|gIi;Q~V-6R&{kC^hth!1vJh`1|a?cb#ukW|`Fz6~Q`-sZnjASXkpShXtN1 zT0<9T3ST;=X?M8 QZcy>z>FVdQ&MBb@0B$IeApigX literal 0 HcmV?d00001 diff --git a/site/content/devlog/roro9stack-games/tetris.png b/site/content/devlog/roro9stack-games/tetris.png new file mode 100644 index 0000000000000000000000000000000000000000..53cff91be065bca7119755b12f045eb75731674c GIT binary patch literal 3005 zcmb7GeK?bQAD%F$=8$=bj^0SgDPmqm+G-*%cR4A=LQ;ge=24v| z^x`y>^m>*@>~=WBn6hH!Wo9#GHqU)ir@Bu4(XMOPwcYpk`?-Ig@8|RT+`ltF`{PuX z>MTVd5USo@9@`KIxmVD4nGzDZ!k*b~g+M50czd|*NW3!E_d1tjr;*-{{^L(>}j+i}b{ri7=nRpLgWXwV98x85E4GMa%6Q=xzp!^6>WBmRW`g zc=6!wd`sZ&+Lj^`y7u7it_oi&RAU;F38Xq5lLQ3QLouP0`91u|P`bnPdmP0hr%LLT z+{iu{*hj{!Gi;g~=`KsFp)o?svYSQ%aV(FaE}}|LB(tPvv{a$D=m44|$(7Sw&K

w{F-!L_d7EtVMIz_*l%^!RM?FLf zut7Smtsj7jj&?>dZ>HX1tU>2gKM$D~z6&6ur{zWUQwT4(B)<72W+Kz^~^J^BE{6l|47mW??S z^NA(0JS*Ag2(!ojcpSGYD`p3kqIU-4el(u9RM;-UQJ7+r*VlBy_p~?BpI^@64Rdz` zcPoRCC5^b8yRJ!lsf=-p!WWPCLM|z#J{HTEp}|uSH?T7(IO_ zu)HVYDr1n8Fi7US*rlV(?QRSq+l82ISL60?yihy*=fzCr?k_Eo*nB96Nzw;Ypp|p9cZ^dicL?7fB`j%o`(@!vABAl+Jm*p`9V3kCtrlqw@Y|Y>u`q zOpK=r7`_+KfxrDAycR}WypsXl2}ydqDY|%+4?ZzA4_U$OW^ktc=i>uBsN^shT&{G! zuG3||GwbQrLvMcH$Z#^>sW>an9d%w=Pm{3aR-=+IL%Y4H0Lp&P_l(er(sW<)W8Fn~ zz^Bx?t6>2m(uZwZgpJ188h_Bqfb+i%7wS}X}m(xDXP8?`MfVo}z z;=*avrpe6*l66D9+VounjKG*@^q$M+SL7fOks-}mZHU~PdeU9Ti$xbZAx_mtVFZX) zprd^4`Xphyhs6EdOiX|`)x5q}8%v!w01Nhxsi>hz^DfDvKYoND{%S)Y8-D<*-$X~I z69K&`lk&55dDy_ig!SoIo;%cc_nn6LJrCbPWZjQ*n3PV$zqr3BT-Xr2y(ZLg{~Y2s zXYN;s+n(G)UcKHKp8A6PkRC;rxt)3hqYm@DFH9mt6=}iN`ecSu4oj9au^Pun{K8g+ zUKfng(3G8DIHxdCm6WWz%K)~QuaYnXMjFyUrUb&R^%>ZKf(0jf44Q*v!_Pgaq9!&c z$%^`&7hLwy*8j+q@9;yAy*9-))ee%$XT)DO@#K+w1Mp#MRj~@77#G(cu4H?NIAZEf z1&a6BYa>tPX$BK>iQz3W!bTjGx0)+z)T~c4k~olKE6~C?R4~;@ zvVyzj+-#oJVYjCocG{Iy*3CWzfZma`gFLsu&R?OuZwkfCI~zN2j1f8ycD6NWru{m5 zT{bh@6T~NYil@Z*8@Fn9Vz?sd29ZlPM%Qc+mj&UT>0C1V-lzh~!qwbSc8*MdAH9{3 z2W0|PA(&cd?M}VJ0^O-(Ws>6~KK7pd02ay-5MtLd<23awgy~J-nLSV)RuVP<<4s#R zj3Jj2DD9!er-%OMVc_va%G%eQ{dIm?KQk;WI}!cyWj#WM#w?bP9< zsKv_XYv2JzQxg!`nx~g`Y&zhO1+dQ<9PjXR;5(|}fe35n)xLL4_P{=SkYYv?H6!6Y zY5}h0aKrB@jr&joRchxmdM_-jqv#-%fOF4kd>$BAz)=TB6P%U<&56fTY`f^7T{*?n zlOW(>63+ETjMg&7I~wcNq4dNm+qOWjCulu)RKEK6EWi5|w9S#N2R=N$`OE+Sn*z6e zUbZ_uzfm4?$}ihw*U6u(6hCvCLn+JF8W`e?vHhpM1+=>qg@ zt2GPV5Uqz53*$q78qD8sTUFyZ*=gFuys7Phmkj;fZXF@tKLd`~uXTaQZ@_={YR^ct zNj)x&y)k2_-sFOT*2&}u+JlhR z{XfkwSvE#%7}VNt4belY=4)_|$fpR2#vmWXz)&#adRxjS#h_DIWs(T5pp&H}ECq6$ z9PPm2oY&BqD?<5WmqcM!Jbd&v3!M|M={iJn#)|c7pu>LgcHiG;oX71L vr>c>zdrXI$r|I3%Po~C+%Mgg04f6